如何在x86/32 GNU汇编中调用printf及相关PIE机制疑问
我编写了一个调用printf的x86/32汇编程序,使用gcc -m32 x.s编译:
.data format: .string "%d\n" .text .global main .type main, @function main: mov $56, %eax push %eax call get_pc_eax add $format - ., %eax push %eax # call printf@PLT call get_pc_eax add $_GLOBAL_OFFSET_TABLE_, %eax mov printf@GOT(%eax), %eax call *%eax add $8, %esp xor %eax, %eax ret get_pc_eax: mov (%esp), %eax ret .section .note.GNU-stack,"",@progbits
若启用注释行call printf@PLT替代其后4行代码,程序执行无输出。现提出以下疑问:
- 调用PLT前是否需要初始化?
- 这是位置无关代码中调用函数的推荐方式吗?感觉较为繁琐。
- 求讲解Linux下i386汇编调用libc及位置无关代码的gas/gcc教程内容。
更新:
经讨论后得到main函数的可行代码:
1 push %ebx 2 mov $56, %ebx 3 push %ebx 4 call get_pc_ebx 5 add $format - ., %ebx 6 push %ebx 7 call get_pc_ebx 8 add $_GLOBAL_OFFSET_TABLE_, %ebx 9 xor %eax, %eax 10 call printf@PLT 11 add $8, %esp 12 pop %ebx 13 xor %eax, %eax 14 ret
此代码可正常运行,若注释第7、8行的%ebx初始化则崩溃,推测%ebx需指向全局偏移表(GOT)。求讲解该机制的内容。
问题解答
1. 调用PLT前是否需要初始化?
在32位x86的位置无关代码(PIC)中,调用PLT函数必须先让EBX寄存器指向GOT的基地址。PLT的内部实现依赖GOT来解析函数的实际地址,而EBX是PIC代码中约定用来保存GOT基址的调用保存寄存器(调用者需要维护其值)。你之前直接调用printf@PLT时未设置EBX为GOT基址,导致PLT无法正确定位printf的地址,因此程序无输出。
2. 这是位置无关代码中调用函数的推荐方式吗?
手动通过get_pc_ebx获取GOT基址再调用函数的方式,是32位PIC汇编的标准手动实现逻辑,但确实较为繁琐。如果不需要手写PIC代码,推荐直接用GCC的-fPIC选项编译C代码生成PIC汇编;如果只是编译可执行文件(而非共享库),也可以不用严格遵循PIC规则,直接调用函数符号即可,具体取决于你的需求场景。
3. 相关教程内容推荐
- GNU汇编器(GAS)官方手册:通过
info as命令查看,其中包含i386架构汇编语法、PIC代码编写的详细说明。 - 《Programming from the Ground Up》:这本书详细讲解了Linux下x86汇编的基础,包括libc函数调用、内存管理等内容,对理解PIC机制也有帮助。
- GCC官方文档:关于PIC代码生成的章节,能帮助理解编译器如何自动处理GOT/PLT相关逻辑。
更新部分的疑问解析
第7、8行的作用是计算GOT的基址并存入EBX:get_pc_ebx获取当前指令的地址,加上_GLOBAL_OFFSET_TABLE_的偏移后,得到GOT的起始地址。printf@PLT的内部代码会通过EBX访问GOT中的条目来获取printf的实际运行地址,如果EBX未正确设置,访问的内存地址无效,直接导致程序崩溃。
内容的提问来源于stack exchange,提问作者hochl

