我有一个用汇编语言编写的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具有相同的语法)。