0

I'm a beginner in Assembly, so I'm confused about passing parameters in function call from another function. Specifically I have code like this: Assembly:

bar:
pushl   %ebp
movl    %esp, %ebp
subl    $16, %esp
movl    $555, -4(%ebp)
movl    12(%ebp), %eax
movl    8(%ebp), %edx
addl    %edx, %eax
movl    %eax, -8(%ebp)
leave
ret
foo:
pushl   %ebp
movl    %esp, %ebp
subl    $8, %esp
movl    $222, 4(%esp)
movl    $111, (%esp)
call    bar
leave
ret

original C code:

void
bar(int a, int b)
{
 int x, y;

 x = 555;
 y = a+b;
 }

 void
 foo(void) {
 bar(111,222);
 }

In Assembly code, I don't understand why in foo()

movl $222, 4(%esp)
movl $111, (%esp)
call bar

can again be read by bar():

movl 12(%ebp), %eax
movl 8(%ebp), %edx

I tried to analyze but i still don't understand if esp - 8 bytes in foo() function can be equivalent to the position read in bar() function esp - 16 bytes ? Is offset numbers like 12, 8, 4 related between the two functions? I am really confused about this. If someone explain it to me or give me a specific example to understand this problem, I would be very grateful and appreciated. Thanks

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • 1
    Just trace stack usage. `call` puts 4 bytes of return address, `push ebp` uses another 4 bytes. Then `esp` is copied into `ebp`. Thus the difference is 8 bytes which is how you get `222` at `12(%ebp)` from `4(%esp)`. – Jester Aug 28 '23 at 10:59
  • Thanks for your answer, that's exactly what I was looking for – Võ Khắc Bảo Aug 28 '23 at 12:57

1 Answers1

3

Let's imagine some sample values and track what happens to ESP/EBP. Plus remember that pushing something (including the "push eip" hidden inside call) will decrement ESP.

bar:  ;ESP=88
pushl   %ebp    ;ESP=84
movl    %esp, %ebp   ;EBP=84
subl    $16, %esp    ;ESP=78
movl    $555, -4(%ebp)
movl    12(%ebp), %eax   ;@96->EAX
movl    8(%ebp), %edx    ;@92->EDX
addl    %edx, %eax
movl    %eax, -8(%ebp)
leave
ret
foo:
pushl   %ebp         ;starting here:
movl    %esp, %ebp   ;let's assume ESP=100,EBP=100
subl    $8, %esp     ;ESP=92
movl    $222, 4(%esp) ;222->96
movl    $111, (%esp)  ;111->92
call    bar
leave
ret
teapot418
  • 1,239
  • 1
  • 3
  • 9
  • Your answer is easy to understand. Thank you very much! – Võ Khắc Bảo Aug 28 '23 at 12:56
  • Fun fact: some ABIs (like modern versions of i386 System V on Linux) require 16-byte ESP alignment before a `call`, so ESP % 16 == 12 on function entry. But most other 32-bit calling conventions (including i386 System V on *BSD and macOS before macOS removed 32-bit support) only require 4-byte alignment so these ESP values are possible. (More plausible of they were like `0x7fff0058` or something, but small numbers make a good example, other than being in the same range as the example values.) – Peter Cordes Aug 28 '23 at 19:21
  • Fun fact: `sub $8, %esp` / 2x `mov` instead of just 2x `push` is a result of older GCC tuning for CPUs like P5, and early P6 before Pentium-M's stack engine made push/pop single-uop. The relevant options are `-maccumulate-outgoing-args` and `-mpush-args` - [Why does gcc use movl instead of push to pass function args?](https://stackoverflow.com/q/4534791), which also mentions that GCC changed the `-mtune=generic` default to favour `push` in 2014. – Peter Cordes Aug 28 '23 at 19:24