我的老师正在和我们一起组装速成班,我没有任何经验。我应该写一个简单的函数,它接受四个变量并计算(x + y) - (z + a),然后打印出答案。我知道这是一个简单的问题,但经过几个小时的研究后,我无处可去,任何正确方向的推动都会非常有帮助!我确实需要使用堆栈,因为一旦我超越这一点,我就会有更多的东西要添加到程序中,并且会有很多变量需要存储。我在linux中使用nasm和gcc进行编译。 (x86 64)
(旁边的问题,我的' 3'不会出现在寄存器r10中,但我在linux中,所以这应该是正确的注册...任何想法?)
到目前为止,这是我的代码:
global main
extern printf
segment .data
mulsub_str db "(%ld * %ld) - (%ld * %ld) = %ld",10,0
data dq 1, 2, 3, 4
segment .text
main:
call multiplyandsubtract
pop r9
mov rdi, mulsub_str
mov rsi, [data]
mov rdx, [data+8]
mov r10, [data+16]
mov r8, [data+24]
mov rax, 0
call printf
ret
multiplyandsubtract:
;;multiplies first function
mov rax, [data]
mov rdi, [data+8]
mul rdi
mov rbx, rdi
push rbx
;;multiplies second function
mov rax, [data+16]
mov rsi, [data+24]
mul rsi
mov rbx, rsi
push rbx
;;subtracts function 2 from function 1
pop rsi
pop rdi
sub rdi, rsi
push rdi
ret
答案 0 :(得分:3)
向正确的方向推进
很好的双关语!
你的问题是你似乎并不知道ret
正在使用堆栈作为返回地址。因此push rdi; ret
只会转到rdi
中的地址而不会返回给您的来电者。因为这不太可能是一个有效的代码地址,所以你会得到一个很好的段错误。
要从函数返回值,只需将结果保留在寄存器中,标准调用约定通常使用rax
。这是一个可能的版本:
global main
extern printf
segment .data
mulsub_str db "(%ld * %ld) - (%ld * %ld) = %ld",10,0
data dq 1, 2, 3, 4
segment .text
main:
sub rsp, 8
call multiplyandsubtract
mov r9, rax
mov rdi, mulsub_str
mov rsi, [data]
mov rdx, [data+8]
mov r10, [data+16]
mov r8, [data+24]
mov rax, 0
call printf
add rsp, 8
ret
multiplyandsubtract:
;;multiplies first function
mov rax, [data]
mov rdi, [data+8]
mul rdi
mov rbx, rdi
push rbx
;;multiplies second function
mov rax, [data+16]
mov rsi, [data+24]
mul rsi
mov rbx, rsi
push rbx
;;subtracts function 2 from function 1
pop rsi
pop rdi
sub rdi, rsi
mov rax, rdi
ret
PS:注意我还根据ABI修复了堆栈对齐。众所周知printf
对此也很挑剔。
答案 1 :(得分:0)
要从子例程返回超过64b(rax
是不够的),您可以选择删除整个标准的ABI约定(或实际遵循它,那里肯定是一个明确定义的方式如何返回更多比子程序中的64b,并使用其他寄存器,直到你用完它们。
一旦你的备用返回寄存器耗尽(或者你非常想使用堆栈内存),你可以按照C ++编译器的方式进行操作:
SUB rsp,<return_data_size + alignment>
CALL subroutine
...
MOV al,[rsp + <offset>] ; to access some value from returned data
; <offset> = 0 to return_data_size-1, as defined by you when defining
; the memory layout for returned data structure
...
ADD rsp,<return_data_size + alignment> ; restore stack pointer
subroutine:
MOV al,<result_value_1>
MOV [rsp + 8 + <offset>],al ; store it into allocated stack space
; the +8 is there to jump beyond return address, which was pushed
; at stack by "CALL" instruction. If you will push more registers/data
; at the stack inside the subroutine, you will have either to recalculate
; all offsets in following code, or use 32b C-like function prologue:
PUSH rbp
MOV rbp,rsp
MOV [rbp + 16 + <offset>],al ; now all offsets are constant relative to rbp
... other code ...
; epilogue code restoring stack
MOV rsp,rbp ; optional, when you did use RSP and didn't restore it yet
POP rbp
RET
因此在执行子程序的指令时,堆栈存储器布局如下:
rsp -> current_top_of_stack (some temporary push/pop as needed)
+x ...
rbp -> original rbp value (if prologue/epilogue code was used)
+8 return address to caller
+16 allocated space for returning values
+16+return_data_size
... padding to have rsp correctly aligned by ABI requirements ...
+16+return_data_size+alignment
... other caller stack data or it's own stack frame/return address ...
我不会检查ABI是如何定义它的,因为我太懒了,而且我希望这个答案对于你解释原理是可以理解的,所以你会认识到ABI的工作方式和调整...
然后,我强烈建议使用相当多的更简单的简单子程序,只返回单个值(在rax/eax/ax/al
中),尽可能尝试遵循SRP(单一责任原则)。上面的方法将强制你定义一些返回数据结构,这可能是太麻烦了,如果它只是一些临时的东西而且可以拆分成单值子程序(如果性能受到威胁,那么可能内联整个子程序甚至会超过分组返回值和单个CALL的逻辑。