Стек растет вниз .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
в конце.Это должно быть легче для вас, чем разобранные версии.