Посмотрите на это из контекста , вызывающего этой функции. Код, который делает это выглядит примерно так:
caller+...: push argN
caller+...: ...
caller+...: push arg0
caller+...: call func
т.е. аргументы помещаются в стек в таком порядке, что при входе в func()
стек будет иметь следующий формат:
[esp+(N*4)] : argN
... : arg(N-1)...arg2
[esp+4 ] : arg1
[esp ] : <return address to caller()+...>
Затем вы выполняете последовательность push %ebp; mov %esp, %ebp
, которая меняет %esp
(на -4), так что ваш макет теперь:
[ebp+4+(N*4)][esp+(N*4)] : argN
... : arg(N-1)...arg2
[ ebp+8 ][esp+8 ] : arg1
[ ebp+4 ][esp+4 ] : <return address to caller()+...>
[ ebp ][esp ] : <saved %ebp of caller>
Затем код отправляет еще несколько регистров в стек - так как он увеличивается каждый раз, когда %esp
изменяется на -4. В конечном итоге (что вы не показали в своей разборке, но она будет там) у вас будет инструкция subl $..., %esp
. Это то, что выделяет пространство для ваших локальных переменных. Окончательный макет стека выглядит примерно так:
[ebp+4+(N*4)][ ] : argN
... : arg(N-1)...arg2
[ ebp+8 ][ ] : arg1
[ ebp+4 ][ ] : <return address to caller()+...>
[ ebp ][ ] : <saved %ebp of caller>
[ ebp-4 ][ ] : <saved %ebx of caller>
[ ebp-8 ][ ] : ...
... : region for local variables
[ ebp-?? ][ esp ] : end of stack for func()
Любой адрес между [esp ... ebp-4]
находится внутри так называемого стекового фрейма для вашей функции и содержит регистр, сохраненный от имени вызывающей стороны (например, ebx
в случае показанной вами разборки), или локальные переменные .
Следовательно, когда в вашем коде вы видите какой-либо доступ к %ebp - XX
, он находится в пространстве локальной переменной, если вы видите %ebp + YY
, он находится внутри пространства, содержащего аргументы функции.