我已经使用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件事情:
mov $format, %rdi
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