1

I am trying to correctly understand where and how I push and pop callee-saved registers like ebx onto/off the stack to restore them for later use.

Is this code correctly restoring the ebx register?

global main
extern printf

section .text:

print:
    mov     eax, 0x1
    add     eax, ebx

    push    eax
    push    message
    call    printf
    add     esp, 8
    ret

main:
    mov     ebx, 0x1
    push    ebx
    call    print
    pop     ebx
    ret

message db "result = %d", 10, 0       

Should I pop ebx directly after usage like so?:

global main
extern printf

section .text:

print:
    push    ebx
    mov     ebx, 0x1
    mov     eax, 0x1
    add     eax, ebx

    push    eax
    push    message
    call    printf
    add     esp, 8
    pop     ebx
    ret
main:
    call print
    ret

message db "result = %d", 10, 0       
kdi_342
  • 13
  • 4

1 Answers1

0

Both ways are fine, but the second is a little more conventional.

The rule is this: when a compiler generates code to call a function, it assumes that the contents of ebx will be the same after the call as before. In your program, the only situation where that applies is to the startup code which calls main. For both versions of your code, ebx has the same value when main returns as when it was entered, so all is well.

If you had a C function in your program which called print, then the first version would be bad, and the second version would be needed. But as it stands in the first version, print is only called from your hand-coded main function, and you know that print will clobber ebx and you are taking appropriate action to save and restore it, so that's fine.

To put it another way, in the first version of the code, print doesn't conform to standard C calling conventions. But since you never actually call it from C code, that isn't necessarily a problem. In the second version, it does conform, which is perhaps aesthetically nicer and may be less confusing to maintain.

Nate Eldredge
  • 48,811
  • 6
  • 54
  • 82
  • What other registers need to be `push`ed and `pop`ped in assembly if used in a similar manner? Just `eax` and `ebx`? – kdi_342 Nov 03 '20 at 01:11
  • @kdi_342: any call-preserved register should be treated the same. For 32-bit x86 calling conventions, that's all the integer regs except EAX, ECX, and EDX. See also [What are callee and caller saved registers?](https://stackoverflow.com/a/56178078) – Peter Cordes Nov 03 '20 at 02:21
  • The first way actually isn't fine: main writes EBX *before* saving/restoring it, which is totally pointless. @kdi_342, You already destroyed your caller's value, and if `print` follows the ABI then `call print` will keep the value. But it's not following the standard calling convention: it's apparently using EBX to pass an arg to `print`? The whole thing is super weird because there are no `ret` instructions anywhere, so both versions loop until they overflow the stack and crash. – Peter Cordes Nov 03 '20 at 02:26
  • @Nate: were you thinking of AT&T syntax? `add eax, ebx` reads EBX without writing it, so the first version of `print` doesn't clobber EBX. But it does read it. IDK what to say about this code because it's so weird, but your answer doesn't seem consistent with the code. – Peter Cordes Nov 03 '20 at 02:28
  • Yes, you're right, I didn't read carefully. Will try to fix when I have more time. – Nate Eldredge Nov 03 '20 at 02:35
  • My apologies if the code is weird I am attempting to learn assembly (in the process). I modified the code a bit, if there's anything wrong please point it out so I can correct myself. – kdi_342 Nov 03 '20 at 11:06
  • i.e the code is adding two integer numbers together and printing them to stdout. – kdi_342 Nov 03 '20 at 11:07