【问题标题】:How can I link dynamically to glibc in Ubuntu如何在 Ubuntu 中动态链接到 glibc
【发布时间】:2020-08-09 10:30:56
【问题描述】:

我正在尝试在 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 组装和ld 链接。正如您可能知道的那样,代码使用 C 函数,因此它必须链接到 glibc。由于我的代码使用的是_start,而不是main,因此我决定链接到共享库会更好,因为如果链接了二进制文件,C 运行时需要一些启动代码才能在_start 中运行静态的。

问题是我无法让我的代码链接,很可能是因为我没有使用正确的 glibc .so。这是我组装和链接的方式:

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.sos:

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

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

【问题讨论】:

  • 链接使用gcc -nostartfiles -m32 -o hello hello.o。请注意,如果您直接调用ld,则将库文件本身放在命令行上是错误的。仅将库作为 -l 操作数提供。所以ld -m elf_i386 -o hello hello.o -lc 应该可以解决问题。
  • 谢谢! GCC 命令有效,但 LD 仍然会产生一个“幽灵”文件,在尝试运行它时找不到该文件。你为什么不发布你的答案? (顺便说一句,如果我使用 64 位目标文件而不是 -m32,则 gcc 会给我一个错误)
  • 尝试ldd hello 看看它试图拉入哪些共享库并发布结果。如果未找到其中任何一个,则可能会出现“没有此类文件或目录”错误消息。也可能是不正确的解释器。
  • 找到了罪魁祸首,它试图链接linux-gate.so.1,但是locate 没有显示任何相关信息。但是,我不知道该怎么办。
  • @DarkAtom linux-gate.so.1 是 vDSO。简短的解释:它是由 Linux 内核自动提供的,实际上并不存在于磁盘上。还缺少其他一些东西让你无法开始它。

标签: assembly nasm ld glibc


【解决方案1】:

ld 的 i386 的默认动态链接器是 /usr/lib/libc.so.1,这在当今大多数 Linux 系统上都是错误的。您确实尝试过覆盖它,但是您给出的路径也不正确。两种选择:

  1. 链接时手动传右边:ld hello.o -o hello -dynamic-linker /lib/ld-linux.so.2 -lc -m elf_i386
  2. 使用gcc 代替链接,as fuz mentioned: gcc -nostartfiles -m32 -o hello hello.o

如果您好奇我是如何知道选项 1 的正确动态链接器是什么,我首先执行选项 2 并检查它使用的是哪个。

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


编辑:您的代码还有另外两个潜在问题,这可能会导致实际代码出现问题,但在这个小例子中却没有发生:

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

其次,您没有正确对齐堆栈。在call 其他功能之前,您需要确保它与 16 字节边界对齐。在_start 的开头(如果您出于某种原因仍然使用它),堆栈已经像那样对齐了,所以您只需要维护它。在main 或任何其他函数的开头,4 字节的返回地址将被推送,因此您需要再推送 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,您可以直接从它返回而不是调用exit。您只需要确保将堆栈指针放回最初的位置即可。为此,请将call printf 之后的所有内容替换为:

    add esp, 268
    xor eax, eax
    ret

最后说明:如果您想知道我为什么使用xor eax, eax 而不是mov eax, 0,请参阅What is the best way to set a register to zero in x86 assembly: xor, mov or and?

【讨论】:

  • 你确定不是/usr/lib/ld.so.1
  • @fuz 既然你提到了它,一个名为libc 的文件似乎确实是一个不寻常的链接器名称选择,但这绝对是ld 放入.interp 的内容。
  • 在不使用 libc 的 crt0.o 的情况下链接 libc.so.6 完全不能保证有效。如果这个答案有效,那只是偶然。
  • @EmployedRussian:如果动态链接,glibc 会自行初始化。它“碰巧”工作,因为 glibc 使用动态链接器调用的类似构造函数的函数。当然,它确实会破坏静态链接,除非您的 _start 调用 glibc 的 init 函数。这个答案提到这一点可能是一个好主意,但如果我想“碰巧工作”的话,IDK。这个 hack 应该在未来继续在 GNU/Linux 上工作。我同意这有点像 hack 并且不是真的推荐,但是像 glibc 是如何使这项工作这样的细节是值得学习的有趣的东西。
  • 是的,现在看起来不错。绝对同意始终使用 GCC 来链接 libc 的建议,无论是 -nostartfiles 与您自己的 _start 还是不编写您自己的 main。我在Assembling 32-bit binaries on a 64-bit system (GNU toolchain) 中说了同样的话,这几乎是重复的。你可能想指定-no-pie:现代发行版通常配置gcc,所以-pie是默认值,这意味着你需要call printf wrt ..plt或其他任何东西,否则你会得到运行时文本重定位。 (或者对于 64 位代码,它只是失败了。)
猜你喜欢
  • 2010-10-01
  • 2018-03-30
  • 2022-01-23
  • 1970-01-01
  • 2012-05-09
  • 1970-01-01
  • 2013-01-12
  • 1970-01-01
相关资源
最近更新 更多