printf从 ELF 入口点 ( )调用 libc 函数_start而不首先调用 glibc init 函数仅适用于动态链接的可执行文件;动态链接器调用 libc 的 init 钩子函数,因此它可以在执行到达您的_start.
但是,如果您链接静态可执行文件,那么在 printf 期望找到已分配/初始化的标准输出缓冲区之类的数据结构之前就不会发生这种情况。
_start这就是为什么通常不推荐并认为从main. 一些libc 实现不需要调用init 函数,例如MUSL 不需要,IIRC。但是 glibc 可以。
如果链接动态可执行文件,则需要指定正确的动态链接器路径,因为默认值在大多数现代系统上没有用。我很惊讶ld -o file -lc file.o在你的系统上工作;在我的 x86-64 Arch GNU/Linux 上,GNU Binutilsld的默认解释器路径/lib/ld64.so.1不存在。
使用readelf -l ./file并查看 INTERP 标头。例如,这是我从构建中得到的,gcc -nostartfiles -no-pie -o foo foo.o让它通过正确的选项来ld制作一个链接-lc但不链接 CRT 启动文件的动态可执行文件:
INTERP 0x0000000000000238 0x0000000000400238 0x0000000000400238
0x000000000000001c 0x000000000000001c R 0x1
[Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
如果您在尝试运行时得到“没有这样./file的文件或目录”,这通常是您手动使用链接器时的问题。 strace ./file将显示 execve 系统调用本身返回,-ENOENT但可以使用相同的路径字符串读取它。ls ./filereadelf -a ./file
ELF 解释器的工作方式类似于#!/bin/sh可执行文本文件顶部的行:内核解析该行并运行/bin/sh ./file. 但是对于 ELF 二进制文件,内核还将可执行文件映射到内存中,因此 ELF 解释器不必使用来自用户空间的系统调用来执行此操作。
可能的 ABI 违规会printf在打印前造成段错误
x86-64 System V ABI(和 Windows x64,顺便说一句)需要在函数RSP % 16 == 0 之前call,从而保证RSP % 16 == 8在进入函数时(在调用推送返回地址之后)。
这让函数可以movaps根据需要更有效地在堆栈上复制本地变量。(为什么 x86-64 / AMD64 System V ABI 要求 16 字节堆栈对齐?)
在 ELF 入口点,RSP % 16 == 0 由 x86-64 SysV ABI 保证;这不是一个功能。(RSP 指向argc,而不是返回地址)。 因此,如果这是您的整个实际代码,则 RSP 将正确对齐。
在调用像 printf 这样的可变参数函数时,还要求 AL >= XMM args 的数量,但不超过 8。
用于制作可变参数函数的真正旧 GCC 执行计算跳转以跳过将 XMM regs 转储到 VA_ARG 代码可以引用它们的数组的 movaps 存储的确切数量,但现代 GCC 仅使用test al,al/jz跳过所有 8 个或不跳过。那时严格遵循 ABI 的这一部分很重要,但这些天你可能会马虎。
RAX 将在入口处保留垃圾_start,因为动态链接器在到达之前在您的进程中运行。除非这是一个静态可执行文件,在这种情况下 ABI 不保证任何东西,但实际上 Linux 会将寄存器归零以避免泄漏内核信息。
因此,如果 AL 超出 0..8 范围,则 glibc 的现代构建将恰好可以工作,只要您重新打印任何 FP args 时它不为零。当然,最好传递 XMM regs 中的 FP args 的实际数量并遵循 ABI,例如xor eax,eaxormov eax,3或其他。
在实践中,最近构建的 glibc 确实使用movaps了 printf 中的堆栈,而不是用于转储 XMM 寄存器,所以现在你也无法避免违反 ABI 的那部分,即使 AL=0 用于打印非 FP 内容. (类似地,scanf 编译为恰好需要正确堆栈对齐的代码:glibc scanf 从不对齐 RSP 的函数调用时出现分段错误)
因此,如果您使用正确的链接器选项,这个确切的代码可能会在动态可执行文件中工作,只会在没有进行 _exit 系统调用的情况下从结尾处崩溃。 我测试了它,这就是发生的事情。
(当然,将输出重定向到文件会使其为空,因为在您发生段错误之前不会刷新完整缓冲的标准输出,因为您没有call exit。是的,您应该call exit,而不是进行 raw eax=231/ syscallexit_group 系统调用将退出而不调用 libc atexit 函数。)
但显然这不是您的完整代码,所以也许您在通话前搞砸了 RSP 对齐?但可能不是,因为您说当您将代码链接到带有ld. 否则您的系统很旧,因此您的 glibc 的 printf 恰好不需要 RSP 对齐。