Понимание цели некоторых сборочных операторов - PullRequest
10 голосов
/ 19 ноября 2010

Я пытаюсь понять некоторый ассемблерный код, и мне удалось завершить большую его часть, за исключением нескольких строк.Я могу понять большую часть того, что происходит внутри, но не могу полностью понять, что (и почему это происходит) в начале и в конце кода.Может кто-нибудь пролить свет на это?

int main() {
    int a, b;
    a = 12;
    b = 20;
    b = a + 123;
    return 0;
}

Разобранная версия:

 8048394:8d 4c 24 04          lea    0x4(%esp),%ecx              ; ??
 8048398:83 e4 f0             and    $0xfffffff0,%esp            ; ??
 804839b:ff 71 fc             pushl  -0x4(%ecx)                  ; ??
 804839e:55                   push   %ebp                        ; Store the Base pointer
 804839f:89 e5                mov    %esp,%ebp                   ; Initialize the Base pointer with the stack pointer
 80483a1:51                   push   %ecx                        ; ??
 80483a2:83 ec 4c             sub    $0x4c,%esp                  ; ??
 80483a5:c7 45 f8 0c 00 00 00 movl   $0xc,-0x8(%ebp)             ; Move 12 into -0x8(%ebp)
 80483ac:c7 45 f4 14 00 00 00 movl   $0x14,-0xc(%ebp)            ; Move 20 into -0xc(%ebp)
 80483b3:8b 45 f8             mov    -0x8(%ebp),%eax             ; Move 12@-0x8(%ebp) into eax
 80483b6:83 c0 7b             add    $0x7b,%eax                  ; Add 123 to 12@eax
 80483b9:89 45 f4             mov    %eax,-0xc(%ebp)             ; Store the result into b@-0xc(%ebp)
 80483bc:b8 00 00 00 00       mov    $0x0,%eax                   ; Move 0 into eax
 80483c1:83 c4 10             add    $0x10,%esp                  ; ??
 80483c4:59                   pop    %ecx                        ; ??
 80483c5:5d                   pop    %ebp                        ; ??
 80483c6:8d 61 fc             lea    -0x4(%ecx),%esp             ; ??

Ответы [ 2 ]

29 голосов
/ 19 ноября 2010

Стек растет вниз .push вычитает из указателя стека (esp), а pop добавляет к esp.Вы должны помнить это, чтобы понять многое из этого.

8048394:8d 4c 24 04          lea    0x4(%esp),%ecx              ; ??

lea = Загрузить эффективный адрес

Это сохраняет адрес вещи, которая лежит в стеке на 4 байта.Поскольку это 32-битный (4-байтовое слово) код x86, то есть второй элемент в стеке.Так как это код функции (в данном случае основной), 4 байта, которые находятся на вершине стека, являются адресом возврата.

8048398:83 e4 f0             and    $0xfffffff0,%esp            ; ??

Этот код гарантирует, что стек выровнен по 16байт.После этой операции esp будет меньше или равен тому, что было до этой операции, поэтому стек может расти, что защищает все, что может уже находиться в стеке.Иногда это делается в main только в случае, если функция вызывается с невыровненным стеком, что может привести к очень медленной работе (16 байт - это ширина строки кэша на x86, я думаю, хотя 4-байтовое выравнивание - это то, что действительноважно здесь).Если main имеет невыровненный стек, остальная часть программы тоже будет.

 804839b:ff 71 fc             pushl  -0x4(%ecx)                  ; ??

Поскольку ecx был загружен ранее как указатель на объект на другой стороне адреса возврата из предыдущей вершины стека,так как он имеет индекс -4, это относится к обратному адресу для текущей функции, возвращаемой в верхнюю часть стека, чтобы main мог вернуться нормально.(Push - это волшебство, и кажется, что он может загружать и хранить в разных местах в ОЗУ в одной и той же инструкции).

 804839e:55                   push   %ebp                        ; Store the Base pointer
 804839f:89 e5                mov    %esp,%ebp                   ; Initialize the Base pointer with the stack pointer
 80483a1:51                   push   %ecx                        ; ??
 80483a2:83 ec 4c             sub    $0x4c,%esp                  ; ??

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

80483a5:c7 45 f8 0c 00 00 00 movl   $0xc,-0x8(%ebp)             ; Move 12 into -0x8(%ebp)
80483ac:c7 45 f4 14 00 00 00 movl   $0x14,-0xc(%ebp)            ; Move 20 into -0xc(%ebp)
80483b3:8b 45 f8             mov    -0x8(%ebp),%eax             ; Move 12@-0x8(%ebp) into eax
80483b6:83 c0 7b             add    $0x7b,%eax                  ; Add 123 to 12@eax
80483b9:89 45 f4             mov    %eax,-0xc(%ebp)             ; Store the result into b@-0xc(%ebp)

80483bc:b8 00 00 00 00       mov    $0x0,%eax                   ; Move 0 into eax

eax - это место, где хранятся целочисленные возвращаемые значения функции.Это настройка для возврата 0 из основного.

80483c1:83 c4 10             add    $0x10,%esp                  ; ??
80483c4:59                   pop    %ecx                        ; ??
80483c5:5d                   pop    %ebp                        ; ??
80483c6:8d 61 fc             lea    -0x4(%ecx),%esp             ; ??

Это эпилог функции.Это сложнее понять из-за странного кода выравнивания стека в начале.У меня возникли небольшие проблемы с выяснением, почему на этот раз стек настраивается на меньшее количество, чем в прологе.

Если очевидно, что этот конкретный код не был скомпилирован с оптимизацией.Если бы это было там, вероятно, не было бы много там, так как компилятор мог видеть, что даже если он не выполнял математические операции, перечисленные в вашем main, конечный результат программы тот же.С программами, которые действительно что-то делают (имеют побочные эффекты или результаты), иногда легче читать слегка оптимизированный код (аргументы -O1 или -0s для gcc).

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

Другая вещь, которая, вероятно,помочь вам - просто сгенерировать для вас gcc файлы сборки, а не разбирать их.Флаг -S говорит ему сгенерировать это (но не генерировать другие файлы), и называет файлы сборки с .s в конце.Это должно быть легче для вас, чем разобранные версии.

6 голосов
/ 19 ноября 2010

Не уверен, почему компилятор делает все это, но вот что я могу расшифровать:

 8048394:8d 4c 24 04          lea    0x4(%esp),%ecx              ; ecx := esp+4
 8048398:83 e4 f0             and    $0xfffffff0,%esp            ; align the stack to 16 bytes
 804839b:ff 71 fc             pushl  -0x4(%ecx)                  ; push [ecx-4] ([esp])
 80483a1:51                   push   %ecx                        ; push ecx
 80483a2:83 ec 4c             sub    $0x4c,%esp                  ; allocate 19 dwords on stack
 80483c1:83 c4 10             add    $0x10,%esp                  ; deallocate 4 dwords from stack
 80483c4:59                   pop    %ecx                        ; restore ecx
 80483c5:5d                   pop    %ebp                        ; and ebp
 80483c6:8d 61 fc             lea    -0x4(%ecx),%esp             ; esp := [ecx-4]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...