Я не собираюсь делать все это для вас, но вот подробное объяснение того, как выполнить то, что происходит.
При входе в main
стек выглядит так:
: (whatever) :
+-----------------------------------+
| return address (in main's caller) | <- %esp
+-----------------------------------+
Стандартный код пролога:
pushl %ebp
movl %esp, %ebp
: (whatever) :
+-----------------------------------+
| return address (in main's caller) |
+-----------------------------------+
| saved %ebp | <- new %ebp = %esp
+-----------------------------------+
Это выравнивает стек до 16-байтовой границы, обнуляя младшие 4 бита
из %esp
:
andl $-16, %esp
: (whatever) :
+-----------------------------------+
| return address (in main's caller) |
+-----------------------------------+
| saved %ebp | <- new %ebp
+-----------------------------------+
: some unknown amount of space :
: (0, 4, 8 or 12 bytes) : <- %esp
+-----------------------------------+
... куда вы попали. Продолжение:
Это вычитает 16 байтов из указателя стека, что создает 16 байтов зарезервированного пространства для использования main
:
subl $16, %esp
: (whatever) :
+-----------------------------------+
| return address (in main's caller) |
+-----------------------------------+
| saved %ebp | <- %ebp
+-----------------------------------+
: some unknown amount of space :
: (0, 4, 8 or 12 bytes) :
+-----------------------------------+
| 16 bytes of reserved space |
| |
| |
| | <- %esp
+-----------------------------------+
Сейчас main
звонит firstCall
; инструкция call
возвращает адрес возврата, поэтому в точке сразу после ввода firstCall
стек будет выглядеть следующим образом:
call firstCall
: (whatever) :
+-----------------------------------+
| return address (in main's caller) |
+-----------------------------------+
| saved %ebp | <- %ebp
+-----------------------------------+
: some unknown amount of space :
: (0, 4, 8 or 12 bytes) :
+-----------------------------------+
| 16 bytes of reserved space |
| |
| |
| |
+-----------------------------------+
| return address (in main) | <- %esp
+-----------------------------------+
При возврате к main
обратный адрес будет снова удален из-за инструкции ret
в конце firstCall
.
... и так далее. Просто продолжайте трассировать код таким же образом, следуя действиям %esp
.
Другая вещь, которая, возможно, нуждается в объяснении, - это leave
, который появляется в
код эпилога различных подпрограмм. Вот как это работает для main
:
Непосредственно перед leave
ближе к концу main
стек выглядит следующим образом (мы вернулись с firstCall
и сохранил значение в зарезервированном пространстве):
: (whatever) :
+-----------------------------------+
| return address (to main's caller) |
+-----------------------------------+
| saved %ebp | <- %ebp
+-----------------------------------+
: some unknown amount of space :
: (0, 4, 8 or 12 bytes) :
+-----------------------------------+
| %eax returned by firstCall |
| (and 12 bytes that were never |
| used) |
| | <- %esp
+-----------------------------------+
leave
эквивалентно movl %ebp, %esp
, за которым следует popl %ebp
. Итак:
movl %ebp, %esp ; (first part of "leave")
: (whatever) :
+-----------------------------------+
| return address (in main's caller) |
+-----------------------------------+
| saved %ebp | <- %esp = current %ebp
+-----------------------------------+
: some unknown amount of space : }
: (0, 4, 8 or 12 bytes) : }
+-----------------------------------+ } all of this stuff is
| %eax returned by firstCall | } irrelevant now
| (and 12 bytes that were never | }
| used) | }
| | }
+-----------------------------------+
popl %ebp ; (second part of "leave")
: (whatever) :
+-----------------------------------+
| return address (in main's caller) | <- %esp (%ebp has now been restored to the
+-----------------------------------+ value it had on entry to "main")
(and now-irrelevant stuff below)
И, наконец, ret
выскакивает адрес возврата, и выполнение продолжается внутри
как называется main
.