szx*_*szx 7 c linux assembly x86-64 yasm
I have a function foo written in assembly and compiled with yasm and GCC on Linux (Ubuntu) 64-bit. It simply prints a message to stdout using puts(), here is how it looks:
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
Run Code Online (Sandbox Code Playgroud)
It is called by a C program compiled with GCC:
extern void foo();
int main() {
foo();
return 0;
}
Run Code Online (Sandbox Code Playgroud)
Build commands:
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
Run Code Online (Sandbox Code Playgroud)
Here is the problem:
When running the program it prints an error message and immediately segfaults during the call to puts:
./foo: Symbol `puts' causes overflow in R_X86_64_PC32 relocation
Segmentation fault
Run Code Online (Sandbox Code Playgroud)
After disassembling with objdump I see that the call is made with the wrong address:
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
Run Code Online (Sandbox Code Playgroud)
(671 is the address of the next instruction, not address of puts)
However, if I rewrite the same code in C the call is done differently:
645: e8 c6 fe ff ff callq 510 <puts@plt>
Run Code Online (Sandbox Code Playgroud)
i.e. it references puts from the PLT.
Is it possible to tell yasm to generate similar code?
您的gcc默认情况下正在构建PIE可执行文件(x86-64 Linux中不再允许使用32位绝对地址?)。
我不确定为什么,但是这样做时链接程序不会自动解析call puts为call puts@plt。仍然会puts生成一个PLT条目,但call不会去那里。
在运行时,动态链接器尝试puts直接解析为该名称的libc符号并修复call rel32。但是符号距离+ -2 ^ 31远,因此我们收到有关R_X86_64_PC32重定位溢出的警告。目标地址的低32位是正确的,但高位不是。(因此,您call跳到了错误的地址)。
如果使用编译,您的代码对我有用gcc -no-pie -fno-pie call-lib.c libcall.o。该-no-pie是关键的部分:它的连接选项。您的YASM命令无需更改。
在制作传统的与位置相关的可执行文件时,链接程序会为您puts将调用目标的符号转换puts@plt为您,因为我们链接的是动态可执行文件(而不是将libc与静态链接gcc -static -fno-pie,在这种情况下,call可以直接转到libc函数。 )
无论如何,这就是为什么gcc进行编译时会发出call puts@plt(GAS语法)-fpie(您的桌面上的默认设置,而不是https://godbolt.org/上的默认设置),而只是call puts在使用时进行编译的原因-fno-pie。
请参阅@plt在这里是什么意思?有关PLT的更多信息,以及几年前Linux上动态库的抱歉状态。(现代gcc -fno-plt就像该博客文章中的想法之一。)
顺便说一句,更准确/更具体的原型将使gcc避免在调用前将EAX归零foo:
extern void foo();在C中意味着extern void foo(...);
您可以将其声明为extern void foo(void);,这()在C ++中意味着。C ++不允许保留未指定args的函数声明。
asm改进
您也可以message输入section .rodata(只读数据,链接为文本段的一部分)。
您不需要堆栈框架,只需执行一些操作即可在调用之前将堆栈按16对齐。一个假人push rax会做。
或者我们可以puts通过跳转到它而不是调用它来进行尾调用,并且具有与该函数入口相同的堆栈位置。无论有无PIE,此功能均可使用。只需更换call用jmp,只要RSP是在你自己的返回地址指向。
call puts wrt ..plt -通过PLT显式调用。call [rel puts wrt ..got]-像gcc的-fno-plt代码生成样式一样,通过GOT条目进行间接调用。(使用相对于RIP的寻址模式来到达GOT,因此使用rel关键字)。WRT =关于。NASM手册文档wrt ..plt,另请参见第7.9.3节:特殊符号和WRT。
通常,您将使用default rel文件的顶部,以便可以实际使用call [puts wrt ..got]并且仍然获得相对于RIP的寻址模式。您不能在PIE或PIC代码中使用32位绝对寻址模式。
call [puts wrt ..got]使用动态链接存储在GOT中的函数指针将其汇编为内存间接调用。(早期绑定,而不是惰性动态链接。)
NASM文档..got在9.2.3节中获取变量的地址。其他库中的函数是相同的:您从GOT获取了一个指针,而不是直接调用它,因为偏移量不是链接时常数,并且可能不适合32位。
YASM也接受call [puts wrt ..GOTPCREL](如AT&T语法)call *puts@GOTPCREL(%rip),但NASM不接受。
; don't use BITS 64. You *want* an error if you try to assemble this into a 32-bit .o
default rel ; RIP-relative addressing instead of 32-bit absolute by default; makes the [rel ...] optional
section .rodata ; .rodata is best for constants, not .data
message:
db 'foo() called', 0
section .text
global foo
foo:
sub rsp, 8 ; align the stack by 16
; PIE with PLT
lea rdi, [rel message] ; needed for PIE
call puts WRT ..plt ; tailcall puts
;or
; PIE with -fno-plt style code, skips the PLT indirection
lea rdi, [rel message]
call [rel puts wrt ..got]
;or
; non-PIE
mov edi, message ; more efficient, but only works in non-PIE / non-PIC
call puts ; linker will rewrite it into call puts@plt
add rsp,8 ; remove the padding
ret
Run Code Online (Sandbox Code Playgroud)
在位置相关的可执行文件中,可以使用mov edi, messageRIP相对的LEA代替。它的代码较小,可以在大多数CPU的更多执行端口上运行。
在非PIE可执行文件中,您也可以使用call puts或jmp puts让链接器对其进行排序,除非您想要更有效的no-plt样式动态链接。但是,如果您选择静态链接libc,我认为这是将jmp直接连接到libc函数的唯一方法。
(我认为静态链接非PIE的可能性就是为什么 ld愿意为非PIE(而不是PIE或共享库)自动生成PLT存根的原因。它要求您说出链接ELF共享对象时的含义。)
如果您确实使用call puts了PIE(call rel32),则只有将与位置无关的实现静态链接puts到您的PIE中才能起作用,因此整个事情就是一个可执行文件,它将在运行时加载到随机地址(通常是动态-linker机制),但根本不依赖libc.so.6
该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具有相同的语法)。