这是我现在正在玩的代码:
# file-name: test.s
# 64-bit GNU as source code.
.global main
.section .text
main:
lea message, %rdi
push %rdi
call puts
lea message, %rdi
push %rdi
call printf
push $0
call _exit
.section .data
message: .asciz "Hello, World!"
字符串
编译说明:gcc test.s -o test
修订版1:
.global main
.section .text
main:
lea message, %rdi
call puts
lea message, %rdi
call printf
mov $0, %rdi
call _exit
.section .data
message: .asciz "Hello, World!"
型
最终版本(工程):
.global main
.section .text
main:
lea message, %rdi
call puts
mov $0, %rax
lea message, %rdi
call printf
# flush stdout buffer.
mov $0, %rdi
call fflush
# put newline to offset PS1 prompt when the program ends.
# - ironically, doing this makes the flush above redundant and can be removed.
# - The call to fflush is retained for display and
# to keep the block self contained.
mov $'\n', %rdi
call putchar
mov $0, %rdi
call _exit
.section .data
message: .asciz "Hello, World!"
型
我很难理解为什么对put的调用成功了,但对printf的调用却导致了Segmentation错误。
有人能解释一下这种行为以及如何调用printf吗?
提前感谢。
摘要:
- printf从%rdi中获取打印字符串,并在%rax的低位DWORD中获取附加参数的数量。
- printf的结果是看不到的,直到一个换行符被放入标准输出,或fflush(0)被调用。
1条答案
按热度按时间q7solyqu1#
puts
隐式追加一个换行符,并且stdout是行缓冲的(在终端上默认)。因此来自printf
的文本可能只是坐在缓冲区中。您的call to_exit(2)
不会刷新缓冲区,因为它是exit_group(2)
system call,而不是exit(3)
library function。(请参阅下面我的代码版本)。对
printf(3)
的调用也不完全正确,因为在调用不带FP参数的var-args函数之前没有将%al
置零。(很好的捕捉@RossRidge,我错过了).xor %eax,%eax
is the best way to do that.%al
将是非零的(来自puts()
的返回值),这大概就是为什么printf segfaults.我在我的系统上测试过,而且printf似乎并不介意堆栈是否未对齐(它确实不介意,因为在调用它之前需要压入两次,不像puts)。(更新:新版本的glibc * 即使AL=0,也会在printf中出现RSP未对齐的segfault,因为gcc更多地使用SSE来一次加载或存储16个字节,当然也利用了ABI保证的对齐。
另外,在这段代码中你不需要任何
push
指令。第一个参数放在%rdi
中。前6个整数参数放在寄存器中,第7个和以后的放在堆栈中。你还忽略了在函数返回后弹出堆栈,这只会起作用,因为你的函数在弄乱堆栈后从不尝试返回。ABI确实需要将堆栈对齐16 B,
push
是实现这一点的一种方法,在最新的英特尔CPU上,它实际上比sub $8, %rsp
更高效,并且占用的字节更少。(参见the x86-64 SysV ABI,以及x86标签wiki中的其他链接)。改进代码:
字符串
lea message(%rip), %rdi
很便宜,执行两次比使用%rbx
的两条mov
指令少。但是由于我们需要将堆栈调整8B以严格遵循ABI的16 B对齐保证,我们不妨通过保存调用保留寄存器来实现。mov reg,reg
非常便宜和小巧,所以利用调用保留的reg是很自然的。现代发行版现在默认使PIE可执行文件,因此即使是静态存储,指针也是64位的。您需要RIP相对莱亚,并且需要64位的操作数大小来复制它们。参见 * How to load address of function or label into register * 与非PIE中的
mov $message, %edi
。永远没有理由在32位绝对寻址模式下使用lea message, %rdi
,只有RIP相对莱亚或mov-immediate。