使用GNU汇编器在x86_64中调用printf

我已经使用AT&T语法编写了一个用于GNU汇编程序的程序:

.data format: .ascii "%d\n" .text .global main main: mov $format, %rbx mov (%rbx), %rdi mov $1, %rsi call printf ret 

我使用GCC进行汇编和链接:

gcc -o main main.s

我用这个命令运行它:

。/主要

当我运行程序时,我得到一个seg故障。 通过使用gdb,它说printf找不到。 我试过“.extern printf”,这是行不通的。 有人build议我应该在调用printf之前存储堆栈指针,并在RET之前恢复,我该怎么做?

这个代码有很多问题。 64位Linux系统V ABI调用约定需要一些东西。 它要求在CALL之前,堆栈至少需要16字节(或32字节)对齐:

输入参数区域的末尾应该对齐在一个16(32,如果__m256传入堆栈)字节边界。

C运行时调用你的main函数之后,堆栈被错位8,因为返回指针被CALL放在堆栈上。 要重新调整为16字节的边界,只需将任何通用寄存器压入堆栈,然后将其关闭即可。

调用约定还要求RAX包含用于可变参数函数的向量寄存器的数量:

%rax用于指示传递给需要可变数量参数的函数的向量参数的数量

printf是一个可变参数函数,所以需要设置RAX 。 在这种情况下,您不会传递向量寄存器中的任何参数,因此您可以将RAX设置为0。

当它已经是一个地址时,你也可以解引用$ format指针。 所以这是错误的:

 mov $format, %rbx mov (%rbx), %rdi 

这需要格式的地址并将其放置在RBX中 。 然后你把这个地址的8个字节放在RBX中,并把它们放在RDI中RDI需要是一个字符串的指针 ,而不是字符本身。 这两行可以替换为:

 lea format(%rip), %rdi 

这使用RIP相对寻址,但在这种情况下,您也可以使用:

 mov $format, %rdi 

你也应该NUL终止你的字符串。 而不是使用.ascii您可以在x86平台上使用.asciz

您的程序的工作版本可能如下所示:

 # global data # .data format: .asciz "%d\n" .text .global main main: push %rbx lea format(%rip), %rdi mov $1, %esi # Writing to ESI zero extends to RSI. xor %eax, %eax # Writing to EAX zero extends to RAX. call printf pop %rbx ret 

其他建议/建议

你也应该从64位的Linux ABI知道,调用约定也需要你写的函数来保存某些寄存器。 登记册的清单和是否应该保留如下:

在这里输入图像描述

保留整个注册表列中的“ Yes任何注册表都是您必须确保在您的函数中保留的注册表 。 函数main就像其他的C函数一样。


如果你知道字符串/数据是只读的,你可以用.section .rodata而不是.data把它们放在.rodata部分


在64位模式下:如果目标操作数是32位寄存器,则CPU将在整个64位寄存器中扩展寄存器。 这可以节省指令编码的字节。

您可以查看从等效的c文件生成的汇编代码。
用test.c运行gcc -o - -S -fno-asynchronousous-unwind-tables test.c test.c

 #include <stdio.h> int main() { return printf("%d\n", 1); } 

这输出汇编代码:

  .file "test.c" .section .rodata .LC0: .string "%d\n" .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的汇编代码样本,然后你可以修改它。


与你的代码比较,你应该修改2件事情:

  • %rdi应该指向格式,你不应该不参考%rbx,这可以用mov $format, %rdi
  • printf具有可变数量的参数,那么你应该添加mov $0, %eax

应用这些修改会给出类似于:

  .data format: .ascii "%d\n" .text .global main main: mov $format, %rdi mov $1, %rsi mov $0, %eax call printf ret 

然后运行它打印:

1