了解汇编、nasm、x86 中的 printf 函数。我不知道为啥这段代码没有打印出任何东西
Posted
技术标签:
【中文标题】了解汇编、nasm、x86 中的 printf 函数。我不知道为啥这段代码没有打印出任何东西【英文标题】:Understanding the printf function in assembly, nasm, x86. I don't know why this code isn't printing anything out了解汇编、nasm、x86 中的 printf 函数。我不知道为什么这段代码没有打印出任何东西 【发布时间】:2021-02-21 01:10:17 【问题描述】:BITS 64;
global main
extern printf
section .text
main: ; This is called by the C library startup code
push rbx ; required to make printf 16-byte aligned.
; For now we only do this once!
push qword 300
call sum
mov rdi, dfmt ; set 1st parameter (format)
mov rsi, rax ; set 2nd parameter (low byte)
xor rax, rax ; because printf is varargs we must clear rax
call printf ; printf(format, current_number)
pop rbx ; must restore rbx for Linux
ret ; Return from main back into C library wrapper
sum:
push rbp ; retrieve parameter
push rbx ; save previous
mov rbp, rsp
add rsp, 24 ; create room for integer
mov rbx, [rbp] ; rbx = param
cmp rbx, 0 ; if (n <= 0)
je base
dec rbx ; decrement rbx to place in stack
push rbx ; put (rbx - 1) in stack
inc rbx
call sum ; calculate sum(n - 1)
add rax, rbx
pop rbx
jmp end
base:
mov rax, 1
end:
pop rbx
pop rbp
ret
dfmt:
db "%ld", 10, 0
【问题讨论】:
请注意,push qword 300
再次未对齐堆栈,您甚至没有从堆栈中删除该参数。可能是也可能不是您的打印问题的原因,但它肯定会导致您的程序崩溃。 add rsp, 24
也不是分配空间,而是释放空间。你想要一个sub
。再说一次,您不需要任何堆栈空间,那有什么意义呢? mov rbx, [rbp]
也不是参数。
另外,sum
是一团糟,从 add rsp,24
开始(不是子),所以它后来推送覆盖 main 的堆栈帧。以及它自己的,递归的,因为它调用自己。而[rbp]
不是它的堆栈 arg 所在的位置。 (通常你会在寄存器中传递参数)。我假设这个段错误,但你还没有确切地说会发生什么(minimal reproducible example 的必要部分)。使用调试器单步执行它,并为自己找到一个工作递归阶乘或其他微不足道的示例,因为有很多误解需要澄清。如果你用谷歌搜索,Stack Overflow 上应该有一些。
【参考方案1】:
让我们一点一点来。
在main
函数中有push qword 300
。我认为这是为了将参数传递给sum
?如果是这样......那不是你在做什么。我建议改为遵循 AMD64 调用约定并使用mov rdi, 300
,然后使用call sum
,它希望在rdi
中找到它的第一个参数。
无论如何,在sum
中,您首先要做的是push rbp
,这是相当标准的。然后你push rbx
。那是一个被调用者保存的寄存器,很好。然后是mov rbp, rsp
,这又是相当标准的。
现在你释放一些堆栈与add rsp,24
,我不明白。堆栈向低地址增长,因此要保留一些堆栈,您可以从rsp
中减去。但也许这不是你正在做的?
现在我真的很困惑。你可以使用mov rbx, [rbp]
,但由于rbp
没有被main
函数修改,它不会指向我们想要的任何地方。我只能假设这是试图获取堆栈上的参数?
老实说,此时我被难住了。
通常使用以下样板启动您的函数。
push rbp
mov rbp, rsp
原因是为了保护堆栈。现在可以针对rbp
完成所有引用,它在您的代码中永远不会更改(它始终指向您的 堆栈框架)。它是一个被调用者保存的寄存器,所以无论如何我们都必须保存它,并且在设置它之前在堆栈上这样做。
通常使用以下样板来结束您的函数。
mov rsp, rbp
pop rbp
ret
这会将堆栈指针和rbp
恢复为它们在入口时的状态,这是 AMD64 调用约定所要求的。有一个同义词:leave
。下面的代码是等价的。
leave
ret
现在,当您使用printf
(或任何可能使用xmm
寄存器的函数)时,您还必须在调用之前确保堆栈在16 字节边界上对齐。您可以假设堆栈在进入main
时对齐。 push rbp
会把它搞砸(减 8),但是当你执行 call printf
时,你会再推 8 个字节(返回地址),然后!堆栈已对齐。
我最后的建议是使用 AMD64 调用约定而不是使用堆栈。将参数放入rdi
,获取rax
中的返回值。
如果不知道您要做什么,我无法提供更多帮助。
【讨论】:
同意,从mov rbx, [rbp] ;rbx = param
的评论来看,他们正在尝试加载他们在 main 中推送的堆栈 arg,但显然他们没有理解或考虑过任何指令在做什么。他们正在重新加载保存的 RBX 值(因为他们在 mov rbp, rsp
将 RBP 设置为帧指针之前推送了 RBX,该帧指针没有指向堆栈帧中的旧帧指针的通常位置)。我还想知道add rsp, 24
是否与应该是[rbp + 24]
混淆了。但如此混乱,它应该被抛弃并用新的理解代替:/以上是关于了解汇编、nasm、x86 中的 printf 函数。我不知道为啥这段代码没有打印出任何东西的主要内容,如果未能解决你的问题,请参考以下文章