我有一个foo用汇编语言编写的函数,并在64位Linux(Ubuntu)上使用yasm和GCC进行了编译。它只是使用来向stdout打印一条消息puts(),如下所示:
foo
puts()
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:
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>
即它puts来自PLT。
是否可以告诉yasm生成类似的代码?
该0xe8操作码后面跟着一个符号偏移量被应用到PC(已经由时间推进到下一指令)来计算分支目标。因此objdump,将分支目标解释为0x671。
0xe8
objdump
0x671
YASM正在渲染零,因为它可能已在该偏移量上放置了重定位,这就是它要求加载程序puts在加载期间填充正确偏移量的方式。加载程序在计算重定位时遇到溢出,这可能表明它puts与您的调用之间的偏移量比32位带符号偏移量所表示的偏移量还大。因此,加载程序无法修复此指令,您将当机。
66c: e8 00 00 00 00显示未填充的地址。如果您在重定位表中查找,您应该在上看到重定位0x66d。汇编器使用全零的重定位填充地址/偏移量并不少见。
66c: e8 00 00 00 00
0x66d
此页面提示YASM有一个WRT指令,可以控制使用.got,.plt等等。
WRT
.got
.plt
根据NASM文档上的 S9.2.5 ,看起来您可以使用CALL puts WRT ..plt(假定YASM具有相同的语法)。
CALL puts WRT ..plt