Dmi*_*try 0 64-bit x86 assembly gnu-assembler crt
这是我现在正在玩的代码:
# 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!"
Run Code Online (Sandbox Code Playgroud)
编译说明: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!"
Run Code Online (Sandbox Code Playgroud)
最终修订版(作品):
.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!"
Run Code Online (Sandbox Code Playgroud)
我很难理解为什么对 puts 的调用会成功,但对 printf 的调用会导致分段错误。
有人可以解释这种行为以及如何调用 printf 吗?
提前致谢。
总结:
puts
隐式附加换行符,并且 stdout 是行缓冲的(默认情况下在终端上)。所以来自的文本printf
可能只是坐在缓冲区中。您对 的调用_exit(2)
不会刷新缓冲区,因为它是exit_group(2)
系统调用,而不是exit(3)
库函数。(请参阅下面我的代码版本)。
您对 的调用printf(3)
也不完全正确,因为%al
在调用没有 FP 参数的 var-args 函数之前您没有归零。(很好的捕获@RossRidge,我错过了)。 xor %eax,%eax
是最好的方法。 %al
将是非零(来自puts()
的返回值),这大概是 printf 段错误的原因。我在我的系统上进行了测试,当堆栈未对齐时,printf 似乎并不介意(这是因为您在调用它之前推送了两次,与 puts 不同)。
此外,您不需要push
该代码中的任何说明。第一个 arg 进入%rdi
。前 6 个整数 args 进入寄存器,第 7 个和以后进入堆栈。您还忽略了在函数返回后弹出堆栈,这只是因为您的函数在弄乱堆栈后从不尝试返回。
ABI 确实需要将堆栈对齐 16B,而 apush
是一种方法,它实际上比sub $8, %rsp
最近的带有堆栈引擎的 Intel CPU更有效,并且占用的字节更少。(请参阅x86-64 SysV ABI和x86标签维基中的其他链接)。
改进的代码:
.text
.global main
main:
lea message, %rdi # or mov $message, %edi if you don't need the code to be position-independent: default code model has all labels in the low 2G, so you can use shorter 32bit instructions
push %rbx # align the stack for another call
mov %rdi, %rbx # save for later
call puts
xor %eax,%eax # %al = 0 = number of FP args for var-args functions
mov %rbx, %rdi # or mov %ebx, %edi will normally be safe, since the pointer is known to be pointing to static storage, which will be in the low 2G
call printf
# optionally putchar a '\n', or include it in the string you pass to printf
#xor %edi,%edi # exit with 0 status
#call exit # exit(3) does an fflush and other cleanup
pop %rbx # restore caller's rbx, and restore the stack
xor %eax,%eax # return 0
ret
.section .rodata # constants should go in .rodata
message: .asciz "Hello, World!"
Run Code Online (Sandbox Code Playgroud)
lea message, %rdi
很便宜,并且执行两次比使用 的两条mov
指令更少的指令%rbx
。但是由于我们需要将堆栈调整 8B 以严格遵循 ABI 的 16B 对齐保证,我们不妨通过保存调用保留寄存器来实现。 mov reg,reg
非常便宜且体积小,因此利用调用保留的 reg 是很自然的。
使用mov %edi, %ebx
类似的东西可以在机器代码编码中保存 REX 前缀。如果您不确定/不明白为什么只复制低 32 位,将高 32b 清零,然后使用 64 位寄存器是安全的。一旦您了解了发生了什么,您就会知道何时可以使用 32 位操作数大小来保存机器代码字节。
归档时间: |
|
查看次数: |
681 次 |
最近记录: |