我如何 link 动态地在 Ubuntu 中使用 glibc

How can I link dynamically to glibc in Ubuntu

我正在尝试 assemble 和 link Linux (Ubuntu 18.04 LTS) 中的这个微小的 x86 汇编代码:

;hello.asm
global _start

extern scanf, printf, exit

section .data
    read_name db '%255s', 0
    msg db 'Hello, %s', 0

section .text
_start:
    sub esp, 256
    push esp
    push read_name
    call scanf
    add esp, 8
    push esp
    push msg
    call printf
    add esp, 264
    push dword 0
    call exit

我正在使用 nasm 到 assemble 和 ld 到 link。正如您可能知道的那样,该代码使用了 C 函数,因此必须将其 link 编辑为 glibc。由于我的代码使用的是 _start,而不是 main,因此我决定 link 到共享库会更好,因为 C 运行time 需要一些如果二进制文件是 link 静态编辑的 运行 in _start 启动代码。

问题是我无法将我的代码获取到 link,很可能是因为我没有使用正确的 glibc .so。这是我 assemble 和 link:

的方式
nasm -f elf32 hello.asm
ld hello.o -o hello -dynamic-linker /lib/libc.so.6 -lc -m elf_i386

输出文件已创建,但是当我尝试 运行 时,我得到的是:

./hello
bash: ./hello: No such file or directory

快速搜索了一下,发现这些都是我电脑上的libc.so

locate libc.so
/lib/x86_64-linux-gnu/libc.so.6
/snap/core/8268/lib/i386-linux-gnu/libc.so.6
/snap/core/8268/lib/x86_64-linux-gnu/libc.so.6
/snap/core/8689/lib/i386-linux-gnu/libc.so.6
/snap/core/8689/lib/x86_64-linux-gnu/libc.so.6
/snap/core18/1668/lib/i386-linux-gnu/libc.so.6
/snap/core18/1668/lib/x86_64-linux-gnu/libc.so.6
/usr/lib/x86_64-linux-gnu/libc.so

谁能告诉我如何 link 到 glibc? (对于 64 位代码,我也遇到了同样的问题)

ld 对于 i386 的默认动态 linker 是 /usr/lib/libc.so.1,这在今天的大多数 Linux 系统上是错误的。您确实尝试覆盖它,但您提供的路径也不正确。两个选项:

  1. 当你 link: ld hello.o -o hello -dynamic-linker /lib/ld-linux.so.2 -lc -m elf_i386
  2. 时手动传递正确的那个
  3. gcc代替link,gcc -nostartfiles -m32 -o hello hello.o

如果您好奇我是如何知道选项 1 的正确动态 linker 是什么,我通过先执行选项 2 一次并检查它使用了哪个来做到这一点。

另请参阅 Red Hat's Bug 868662 - /lib/ld64.so.1: bad ELF Interpreter: No such file or directory,其他人基本上遇到了与您遇到的问题完全相同的问题(但出于某种原因,他们收到的错误消息比您收到的更有帮助)。


编辑:您的代码还有另外两个潜在问题,它们可能会导致实际代码出现问题,但在这个小示例中恰好不会:

首先,与 一样,glibc 期望它自己的 crt 初始化代码将 运行 在您的应用程序代码开始调用其函数之前。你很幸运;由于您创建了一个动态 linked 二进制文件,因此动态 linker 对 glibc 的使用导致它为您初始化。如果您改为制作静态 linked 二进制文件,它就不会工作。为避免以这种方式依赖动态 linker,最简单的解决方案是使用 main 作为入口点而不是 _start,然后使用 gcc -m32 -o hello hello.o 到 link(注意我们不再使用 -nostartfiles)。理论上你仍然可以直接使用 ld 到 link,但它已经足够复杂了,基本上没有理由打扰。

其次,您没有正确对齐堆栈。在 call 其他函数之前,您需要确保它与 16 字节边界对齐。在 _start 的开头(如果出于某种原因你仍然使用它),堆栈已经像那样对齐了,所以你只需要维护它。在 main 或任何其他函数的开头,4 字节的 return 地址将被压入,因此您需要再压入 12 个字节才能重新对齐它。

通过上述两个修复,这是您的新 hello.asm:

;hello.asm
global main

extern scanf, printf, exit

section .data
    read_name db '%255s', 0
    msg db 'Hello, %s', 0

section .text
main:
    sub esp, 260 ; the 4 extra bytes here are padding for alignment. If you wanted to get value out of them, you could use %259s instead of %255s now
    push esp
    push read_name
    call scanf
    add esp, 8
    push esp
    push msg
    call printf
    add esp, 260 ; we pushed 268 bytes so far, but I'm leaving 8 bytes for alignment
    push dword 0
    call exit

此外,现在您使用的是 main 而不是 _start,您可以只从它 return 而不是调用 exit。您只需要确保将堆栈指针放回开始时的位置即可。为此,请将 call printf 之后的所有内容替换为:

    add esp, 268
    xor eax, eax
    ret

最后说明:如果您想知道我为什么选择 xor eax, eax 而不是 mov eax, 0,请参阅