使用 GNU 汇编程序在 x86_64 中调用 printf
- 2024-10-11 08:36:00
- admin 原创
- 78
问题描述:
我使用 AT&T 语法编写了一个程序,用于 GNU 汇编器:
.data
format: .ascii "%d
"
.text
.global main
main:
mov $format, %rbx
mov (%rbx), %rdi
mov $1, %rsi
call printf
ret
我使用GCC来汇编和链接:
gcc -o main main.s
我使用以下命令运行它:
。/主要的
当我运行该程序时,我遇到了段错误。使用 gdb 时,它显示printf
未找到。我尝试了“.extern printf”,但它不起作用。有人建议我应该在调用之前存储堆栈指针,并在RETprintf
之前恢复,我该怎么做?
解决方案 1:
此代码存在一些问题。Linux使用的AMD64 System V ABI调用约定有几点要求。它要求在CALL之前堆栈至少对齐 16 字节(或 32 字节):
输入参数区域的末尾应与 16(如果 __m256 在堆栈上传递,则为 32)字节边界对齐。
在C运行时调用你的函数后,main
堆栈错位了 8,因为返回指针是通过CALL放在堆栈上的。要重新对齐到 16 字节边界,你只需将任何通用寄存器PUSH 到堆栈上,然后在最后将其POP掉即可。
调用约定还要求AL包含用于可变参数函数的向量寄存器的数量:
%al 用于指示传递给需要可变数量参数的函数的向量参数的数量
printf
是可变参数函数,因此需要设置AL 。在这种情况下,您不会在向量寄存器中传递任何参数,因此您可以将AL设置为 0。
当 $format 指针已经是一个地址时,您还会取消引用它。因此这是错误的:
mov $format, %rbx
mov (%rbx), %rdi
这将获取格式的地址并将其放置在RBX中。然后,您将RBX中该地址处的 8 个字节放入RDI中。RDI需要是指向字符串的指针,而不是字符本身。这两行可以替换为:
lea format(%rip), %rdi
这使用 RIP 相对寻址。
您还应该使用NUL终止字符串。您可以在 x86 平台上.ascii
使用,而不是使用。.asciz
你的程序的一个工作版本可能看起来像这样:
# global data #
.data
format: .asciz "%d
"
.text
.global main
main:
push %rbx
lea format(%rip), %rdi
mov $1, %esi # Writing to ESI zero extends to RSI.
xor %eax, %eax # Zeroing EAX is efficient way to clear AL.
call printf
pop %rbx
ret
其他建议
您还应该从 64 位 Linux ABI 中了解到,调用约定还要求您编写的函数遵守某些寄存器的保存。寄存器列表以及是否应保存它们如下:
您必须确保在函数调用过程中保留Yes
列中列出的任何寄存器都保留在函数中。函数与任何其他C函数一样。main
如果你有字符串/数据,你知道它们是只读的,你可以将它们放在.rodata
部分中,.section .rodata
而不是.data
在 64 位模式下:如果您的目标操作数是 32 位寄存器,则 CPU 将在整个 64 位寄存器上对该寄存器进行零扩展。这可以节省指令编码的字节数。
您的可执行文件可能被编译为与位置无关的代码。您可能会收到类似以下内容的错误:
在创建共享对象时不能使用针对符号“printf@@GLIBC_2.2.5”的重定位 R_X86_64_PC32;使用 -fPIC 重新编译
要解决此问题,您必须按printf
以下方式调用外部函数:
call printf@plt
这将通过过程链接表(PLT)调用外部库函数
解决方案 2:
您可以查看从等效 c 文件生成的汇编代码。使用 test.c
运行gcc -o - -S -fno-asynchronous-unwind-tables test.c
#include <stdio.h>
int main() {
return printf("%d
", 1);
}
这输出汇编代码:
.file "test.c"
.section .rodata
.LC0:
.string "%d
"
.text
.globl main
.type main, @function
main:
pushq %rbp
movq %rsp, %rbp
movl $1, %esi
movl $.LC0, %edi
movl $0, %eax
call printf
popq %rbp
ret
.size main, .-main
.ident "GCC: (GNU) 6.1.1 20160602"
.section .note.GNU-stack,"",@progbits
这为您提供了一个调用 printf 的汇编代码示例,您可以对其进行修改。
与您的代码相比,您应该修改两件事:
%rdi 应该指向格式,你不应该取消引用 %rbx,这可以通过以下方式完成
mov $format, %rdi
printf 有可变数量的参数,那么你应该添加
mov $0, %eax
应用这些修改将得到如下结果:
.data
format: .ascii "%d
"
.text
.global main
main:
mov $format, %rdi
mov $1, %rsi
mov $0, %eax
call printf
ret
然后运行打印:
1
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件