一尘不染

无法通过汇编(yasm)代码在64位Linux上调用C标准库函数

linux

我有一个foo用汇编语言编写的函数,并在64位Linux(Ubuntu)上使用yasm和GCC进行了编译。它只是使用来向stdout打印一条消息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

./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生成类似的代码?


阅读 302

收藏
2020-06-02

共1个答案

一尘不染

0xe8操作码后面跟着一个符号偏移量被应用到PC(已经由时间推进到下一指令)来计算分支目标。因此objdump,将分支目标解释为0x671

YASM正在渲染零,因为它可能已在该偏移量上放置了重定位,这就是它要求加载程序puts在加载期间填充正确偏移量的方式。加载程序在计算重定位时遇到溢出,这可能表明它puts与您的调用之间的偏移量比32位带符号偏移量所表示的偏移量还大。因此,加载程序无法修复此指令,您将当机。

66c: e8 00 00 00 00显示未填充的地址。如果您在重定位表中查找,您应该在上看到重定位0x66d。汇编器使用全零的重定位填充地址/偏移量并不少见。

此页面提示YASM有一个WRT指令,可以控制使用.got.plt等等。

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

2020-06-02