我有一个用汇编语言编写的foo函数,并在64位Linux(Ubuntu)上使用yasm和GCC进行了编译。它只是使用puts()将消息打印到stdout,如下所示:

bits 64

extern puts
global foo

section .data

message:
  db 'foo() called', 0

section .text

foo:
  push rbp
  mov rbp, rsp
  lea rdi, [rel message]
  call puts
  pop rbp
  ret

它由使用GCC编译的C程序调用:
extern void foo();

int main() {
    foo();
    return 0;
}

生成命令:
yasm -f elf64 foo_64_unix.asm
gcc -c foo_main.c -o foo_main.o
gcc foo_64_unix.o foo_main.o -o foo
./foo

这是问题所在:

运行该程序时,它会显示一条错误消息,并在调用puts期间立即进行段错误处理:
./foo: Symbol `puts' causes overflow in R_X86_64_PC32 relocation
Segmentation fault

用objdump拆解后,我看到调用是用错误的地址进行的:
0000000000000660 <foo>:
 660:   90                      nop
 661:   55                      push   %rbp
 662:   48 89 e5                mov    %rsp,%rbp
 665:   48 8d 3d a4 09 20 00    lea    0x2009a4(%rip),%rdi
 66c:   e8 00 00 00 00          callq  671 <foo+0x11>      <-- here
 671:   5d                      pop    %rbp
 672:   c3                      retq

(671是下一条指令的地址,而不是puts的地址)

但是,如果我用C重写相同的代码,则调用将以不同的方式进行:
645:   e8 c6 fe ff ff          callq  510 <puts@plt>

即它引用了PLT中的puts

是否可以告诉yasm生成类似代码?

最佳答案

0xe8操作码后跟一个有符号的偏移量,该偏移量将应用于PC(到那时该PC已经前进到下一条指令)以计算分支目标。因此,objdump将分支目标解释为0x671

YASM正在渲染零,因为它可能已在该偏移量上放置了重定位,这就是它要求加载程序在加载期间为puts填充正确的偏移量的方式。加载程序在计算重定位时遇到溢出,这可能表明puts与您的调用之间的偏移量比32位带符号偏移量所表示的偏移量还大。因此,加载程序无法修复此指令,从而导致崩溃。
66c: e8 00 00 00 00显示未填充的地址。如果查看重定位表,应该在0x66d上看到重定位。汇编器使用全零的重定位填充地址/偏移量并不少见。

This page建议YASM具有WRT指令,该指令可以控制.got.plt等的使用。

根据the NASM documentation上的S9.2.5,看起来您可以使用CALL puts WRT ..plt(假定YASM具有相同的语法)。

10-06 13:16
查看更多