Почему, когда я обращаюсь к объекту, состоящему из трех целых чисел, он вычитается из базового указателя, а не из указателя стека?

Я хотел выяснить, как работают объекты, посмотрев результаты сборки программы. У меня есть класс под названием Numbers содержащий три ints,

class Numbers {

public:

int n1;
int n2;
int n3;

};

Внутри основной функции я создаю экземпляр с именем obj и установите каждую переменную в число.

int main() {

Numbers obj;

obj.n1 = 1;
obj.n2 = 2;
obj.n3 = 3;

}

Следующий код — это сгенерированная сборка:

int main() {
00935240  push        ebp
00935241  mov         ebp,esp
00935243  sub         esp,0D8h
00935249  push        ebx
0093524A  push        esi
0093524B  push        edi
0093524C  lea         edi,[ebp-0D8h]
00935252  mov         ecx,36h
00935257  mov         eax,0CCCCCCCCh
0093525C  rep stos    dword ptr es:[edi]
0093525E  mov         eax,dword ptr ds:[0093F000h]
00935263  xor         eax,ebp
00935265  mov         dword ptr [ebp-4],eax

Numbers obj;

obj.n1 = 1;
00935268  mov         dword ptr [obj],1       ; === Here ===
obj.n2 = 2;
0093526F  mov         dword ptr [ebp-10h],2   ; === Here ===
obj.n3 = 3;
00935276  mov         dword ptr [ebp-0Ch],3   ; === Here ===

return 0;
0093527D  xor         eax,eax
}

Я думал, что базовый указатель указывает на вершину стекового фрейма, а поскольку функция является главной, он указывает на начало программы. Как это может быть вычитание из базового указателя, когда указатель стека является то, что указывает на текущий адрес? Кроме того, почему он обращается к переменным не по порядку. Меняется n1затем вычитает 16 байтов, чтобы добраться до адреса n2, а затем 12 байтов, чтобы добраться до n3, Есть ли причина для этого?

Я использую Visual Studio 2013 с MASM в качестве ассемблера.

1

Решение

Между ожиданием медленного запуска Apache Pluto и ожиданием моего босса, чтобы закончить свой звонок, мне нравится бродить по этой пустоши вопросов, отмеченных .
Так что именно в таком скучном настроении я пишу другой, бесполезный ответ на этот и без того довольный ОП.

;PROLOG

push ebp                    ;Save the caller frame pointer
mov ebp, esp                ;Make our frame pointer

;ALLOCATE SPACE

sub esp, 0D8h               ;Reserve 216 bytes on the stack
;Why 216? I dunno, maybe this makes the compiler
;source code easy to write/read/mantain

;SAVE CALLER REGS

push ebx
push esi
push edi                    ;Save caller register that we must not clobber

;INIT ALLOCATED SPACE

lea edi, [ebp-0D8h]         ;EDI point to the start (the lower limit) of
;our reserved space (EDI = EBP-0d8h)

mov ecx, 36h                ;ECX is the number of DWORD to write,
;36h*4 = 0d8h = 216 bytes

mov eax, 0CCCCCCCCh         ;EAX is the DWORD to write, 0cccccccch comes
;from the fact that: 1) 0cch is the opcode for
;int 03h which is by convention the debug exception
;2) it is easy to spot 3) it is an invalid address to
;deference. This way an uninitialized var will misbehave
;when used (not for arithmetic). This is for debug purpose.

rep stos dword ptr es:[edi] ;Write ECX times EAX from ES:EDI upward (N.B. UPWARD)

;SET UP THE CANARY

mov eax, dword ptr ds:[0093F000h]   ;Take a value which is safe in memory and cannot be
;overwritten by stack overflow (those guys, grrrr...)

xor eax, ebp                        ;Compute a function of the frame pointer and the canary
;This can make the canary unique on every invocation.
;The function is a xor

mov dword ptr [ebp-4],eax           ;The canary is at the very beginning (ending?) or our
;allocated space. It is just below the frame pointer.;Set the object fields

mov dword ptr [obj], 1              ;I believe this obj is [ebp-14h]
mov dword ptr [ebp-10h], 2          ;Remember that [ebp-10h] is after [ebp-14h], just like
mov dword ptr [ebp-0Ch], 3          ;-10 is after (i.e. bigger than)  -14.;Return the value 0

xor eax,eax                         ;EAX have to hold the returned value at the end of the
;function, V XOR V = 0 for all V
2

Другие решения

Регистр ebp обычно указывает на начало начала кадра стека текущей функции (которая обычно содержит указатель на предыдущий кадр) в стеке.

В выводе сборки сначала сохраняется последний указатель кадра стека, затем адрес текущего указателя стека сохраняется в формате ebp. Это начало стекового фрейма для текущей функции. Затем некоторые байты вычитаются из esp, чтобы зарезервировать место в стеке для локальных переменных.

Порядок переменных правильный; адреса переменных находятся ниже текущего адреса ebp (стек увеличивается с высоких адресов на более низкие).

Я думал, что указатель базы указывает на верхнюю часть кадра стека,
и так как функция является главной, начало программы.

main() на самом деле не первая вызванная функция; Есть много функций запуска libc, вызываемых ранее (например, для инициализации глобального объекта).

3

По вопросам рекламы [email protected]