简单乘法函数中的推/弹分割错误

时间:2017-01-31 01:09:25

标签: linux assembly x86-64

我的老师正在和我们一起组装速成班,我没有任何经验。我应该写一个简单的函数,它接受四个变量并计算(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

2 个答案:

答案 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的逻辑。