使用 GNU 汇编程序在 x86_64 中调用 printf [英] Calling printf in x86_64 using GNU assembler
问题描述
我已经使用 AT&T 语法编写了一个用于 GNU 汇编程序的程序:
I've written a program using AT&T syntax for use with GNU assembler:
.data
format: .ascii "%d
"
.text
.global main
main:
mov $format, %rbx
mov (%rbx), %rdi
mov $1, %rsi
call printf
ret
我使用 GCC 来组装和链接:
I use GCC to assemble and link with:
gcc -o main main.s
gcc -o main main.s
我用这个命令运行它:
./main
当我运行程序时,我遇到了段错误.通过使用 gdb,它说 printf
未找到.我试过.extern printf",它不起作用.有人建议我应该在调用 printf
之前存储堆栈指针并在 RET 之前恢复,我该怎么做?
When I run the program I get a seg fault. By using gdb, it says printf
not found. I have tried ".extern printf", which does not work. Someone suggested I should store the stack pointer before calling printf
and restore before RET, How do I do that?
推荐答案
此代码存在许多问题.AMD64 System V ABI
There are a number of issues with this code. The AMD64 System V ABI calling convention used by Linux requires a few things. It requires that just before a CALL that the stack be at least 16-byte (or 32-byte) aligned:
输入参数区域的末尾应对齐在 16(32,如果 __m256 是在堆栈上传递)字节边界.
The end of the input argument area shall be aligned on a 16 (32, if __m256 is passed on stack) byte boundary.
在 C 运行时调用您的 main
函数后,堆栈错位了 8,因为返回指针是通过 CALL 放置在堆栈上的.要重新对齐 16 字节边界,您可以简单地PUSH 任何 通用寄存器到堆栈上,然后在最后 POP 将其关闭.
After the C runtime calls your main
function the stack is misaligned by 8 because the return pointer was placed on the stack by CALL. To realign to 16-byte boundary you can simply PUSH any general purpose register onto the stack and POP it off at the end.
调用约定还要求 AL 包含用于可变参数函数的向量寄存器的数量:
The calling convention also requires that AL contain the number of vector registers used for a variable argument function:
%al 用于指示传递给需要可变参数数量的函数的向量参数数量
%al is used to indicate the number of vector arguments passed to a function requiring a variable number of arguments
printf
是可变参数函数,所以需要设置AL.在这种情况下,您不会在向量寄存器中传递任何参数,因此您可以将 AL 设置为 0.
printf
is a variable argument function, so AL needs to be set. In this case you don't pass any parameters in a vector register so you can set AL to 0.
当 $format 指针已经是地址时,您也可以取消引用它.所以这是错误的:
You also dereference the $format pointer when it is already an address. So this is wrong:
mov $format, %rbx
mov (%rbx), %rdi
这获取格式的地址并将其放入RBX.然后将 RBX 中该地址的 8 个字节放入 RDI.RDI 需要是指向字符串的指针,而不是字符本身.这两行可以替换为:
This takes the address of format and places it in RBX. Then you take the 8 bytes at that address in RBX and place them in RDI. RDI needs to be a pointer to a string of characters, not the characters themselves. The two lines could be replaced with:
lea format(%rip), %rdi
这使用 RIP 相对寻址.
This uses RIP Relative Addressing.
您还应该 NUL 终止您的字符串.您可以在 x86 平台上使用 .asciz
而不是使用 .ascii
.
You should also NUL terminate your strings. Rather than use .ascii
you can use .asciz
on the x86 platform.
您的程序的工作版本可能如下所示:
A working version of your program could look like:
# 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 中了解到,调用约定还需要您编写的函数来尊重某些寄存器的保存.寄存器列表以及是否应该保留如下:
Other Recommendations/Suggestions
You should also be aware from the 64-bit Linux ABI, that the calling convention also requires functions you write to honor the preservation of certain registers. The list of registers and whether they should preserved is as follows:
任何在 Preserved 中显示 Yes
的寄存器函数调用列是您必须确保在整个函数中保留的列.函数 main
就像任何其他 C 函数一样.
Any register that says Yes
in the Preserved across
function calls column are ones you must ensure are preserved across your function. Function main
is like any other C function.
如果您有字符串/数据,您知道它是只读的,您可以将它们放在 .rodata
部分,并使用 .section .rodata
而不是 .数据
If you have strings/data that you know will be read only you can place them in the .rodata
section with .section .rodata
rather than .data
在 64 位模式下:如果目标操作数是 32 位寄存器,CPU 会将寄存器零扩展到整个 64 位寄存器.这可以节省指令编码上的字节.
In 64-bit mode: if you have a destination operand that is a 32-bit register, the CPU will zero extend the register across the entire 64-bit register. This can save bytes on the instruction encoding.
您的可执行文件可能被编译为位置无关代码.您可能会收到类似于以下内容的错误:
It is possible your executable is being compiled as position independent code. You may receive an error similar to:
relocation R_X86_64_PC32针对符号`printf@@GLIBC_2.2.5'不能在制作共享对象时使用;用 -fPIC 重新编译
relocation R_X86_64_PC32 against symbol `printf@@GLIBC_2.2.5' can not be used when making a shared object; recompile with -fPIC
要解决此问题,您必须以这种方式调用外部函数 printf
:
To fix this you'll have to call the external function printf
this way:
call printf@plt
这通过过程链接表(PLT)
这篇关于使用 GNU 汇编程序在 x86_64 中调用 printf的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!