Вывод сборок GCC пустой программы на x86, win32
Я пишу пустые программы, чтобы раздражать ад из кодеков stackoverflow, NOT. Я просто изучаю gnu toolchain.
Теперь для меня может быть слишком глубоко, но для продолжения пустой саги программы я начал изучать вывод компилятора C, материал GNU, который потребляет.
gcc version 4.4.0 (TDM-1 mingw32)
test.c:
int main()
{
return 0;
}
gcc -S test.c
.file "test.c"
.def ___main; .scl 2; .type 32; .endef
.text
.globl _main
.def _main; .scl 2; .type 32; .endef
_main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
call ___main
movl $0, %eax
leave
ret
Можете ли вы объяснить, что здесь происходит? Вот мои усилия, чтобы понять это. Я использовал руководство as
и мои минимальные знания ASM x86:
-
.file "test.c"
- это директива для логического имени файла.
-
.def
: согласно документам "Начать определение информации отладки для имени символа". Что такое символ (имя/переменная функции?) И какая отладочная информация?
-
.scl
: docs говорят: "Класс хранения может указывать, является ли символ статическим или внешним". Является ли это тем же статическим и внешним, что я знаю из C? И что это за "2"?
-
.type
: сохраняет параметр "как атрибут типа записи в таблице символов", я понятия не имею.
-
.endef
: нет проблем.
-
.text
: Теперь это проблематично, кажется, что-то называется секцией, и я прочитал, что это место для кода, но документы не слишком мне говорили.
-
.globl
"делает символ видимым для ld.", руководство здесь совершенно ясно.
-
_main:
Это может быть начальный адрес (?) для моей основной функции
-
pushl_
: длинный (32-битный) push, который помещает EBP в стек
-
movl
: 32-битное перемещение. Псевдо-C: EBP = ESP;
-
andl
: Логическое И. Pseudo-C: ESP = -16 & ESP
, я действительно не понимаю, в чем смысл этого.
-
call
: помещает IP в стек (так называемая процедура может вернуться назад) и продолжается там, где __main
. (что такое __main?)
-
movl
: этот нуль должен быть константой, которую я возвращаю в конце моего кода. MOV помещает этот ноль в EAX.
-
leave
: восстанавливает стек после команды ENTER (?). Почему?
-
ret
: возвращается к адресу инструкции, который сохраняется в стеке
Благодарим за помощь!
Ответы
Ответ 1
.file "test.c"
Команды, начинающиеся с. являются директивами для ассемблера. Это просто говорит, что это "file.c", эта информация может быть экспортирована в информацию об отладке exe.
.def ___main;.scl 2;.type 32;.endef
Директивы .def определяют отладочный символ. scl 2 означает класс хранения 2 (внешний класс хранения). type 32 говорит, что это sumbol является функцией. Эти числа будут определяться exe-форматом pe-coff
___ main - это функция, которая отвечает за загрузку, требуемую gcc (она будет делать такие вещи, как запуск статических инициализаторов С++ и другое домашнее хозяйство).
.text
Начинается текстовый раздел - код живет здесь.
.globl _main
определяет символ _main как глобальный, который сделает его видимым для компоновщика и других модулей, которые связаны.
.def _main; .scl 2; .type 32; .endef
То же, что и _main, создает отладочные символы, указывающие, что _main является функцией. Это можно использовать отладчиками.
_main:
Запускает новый ярлык (он будет содержать адрес). директива .globl выше делает этот адрес видимым для других объектов.
pushl %ebp
Сохраняет старый стек (регистр ebp) в стеке (поэтому его можно вернуть на место после завершения этой функции)
movl %esp, %ebp
Перемещает указатель стека к регистру ebp. ebp часто называют указателем кадра, он указывает на вершину значений стека в текущем "фрейме" (обычно функция) (обращение к переменным в стеке через ebp может помочь отладчикам)
andl $-16,% esp
И добавляет стек с fffffff0, который эффектно выравнивает его по 16-байтовой границе. Доступ к выровненным значениям в стеке намного быстрее, чем если бы они были неровными. Все эти предыдущие инструкции в значительной степени являются проломом стандартной функции.
call ___main
Вызывает функцию ___main, которая будет инициализировать материал, необходимый gcc. Вызов будет вызывать текущий указатель инструкции в стеке и перейти к адресу ___ main
movl $0, %eax
переместить 0 в регистр eax (0 в обратном 0;), регистр eax используется для хранения возвращаемых значений функции для соглашения о вызове stdcall.
отпуск
Инструкция для отпуска в значительной степени сокращена для
movl ebp,esp
popl ebp
то есть. это "отменяет" материал, сделанный в начале функции - восстановление указателя кадра и стека до его прежнего состояния.
RET
Возвращает тот, кто вызывает эту функцию. Он вытащит указатель инструкции из стека (который соответствующая команда вызова поместит там) и перепрыгнет туда.
Ответ 2
Здесь очень похожее упражнение, описанное здесь: http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax
Вы многое поняли - я просто сделаю дополнительные заметки для акцентов и дополнений.
__ main
является подпрограммой в стандартной библиотеке GNU, которая заботится о различной инициализации запуска. Это не является строго необходимым для программ на C, но требуется только в том случае, если код C связывается с С++.
_main
- ваша основная подпрограмма. Поскольку оба _main
и __ main
являются кодами, у них одинаковый класс и тип хранения. Я еще не выкопал определения для .scl
и .type
. Вы можете получить некоторое освещение, указав несколько глобальных переменных.
Первые три инструкции устанавливают фрейм стека, который по большей части является техническим термином для рабочего хранилища подпрограммы - локальные и временные переменные. Нажатие ebp
сохраняет базу кадра стека вызывающего абонента. Помещение esp
в ebp
устанавливает базу нашего фрейма стека. andl
выравнивает фрейм стека с границей 16 байтов, если любые локальные переменные в стеке требуют 16-байтового выравнивания (для инструкций x86 SIMD требуется выравнивание, но выравнивание ускоряет обычные типы, такие как int
и float
s.
В этот момент вы обычно ожидаете, что esp
будет перемещен вниз в памяти, чтобы выделить пространство стека для локальных переменных. У вашего основного
нет ни одного, поэтому gcc не беспокоит.
Вызов __ main
является особенным для основной точки входа и обычно не отображается в подпрограммах.
Остальное идет, как вы догадались. Register eax
- это место, где нужно поместить целые коды возврата в двоичную спецификацию. leave
отменяет фрейм стека, а ret
возвращается к вызывающему. В этом случае вызывающим является низкоуровневая C-среда выполнения, которая будет выполнять дополнительную магию (например, вызывать функции atexit()
, устанавливать код выхода для процесса и запрашивать операционную систему для завершения процесса.
Ответ 3
Относительно andl $-16,% esp
- 32 бит: -16 в десятичном значении равно 0xfffffff0 в шестнадцатеричном представлении
- 64 бит: -16 в десятичном значении равно 0xfffffffffffffff0 в шестнадцатеричном представлении
Таким образом, он будет маскировать последние 4 бита ESP (бит: 2 ** 4 равно 16) и сохранит все остальные биты (независимо от того, будет ли целевая система 32 или 64 бита).
Ответ 4
В дополнение к andl $-16,%esp
, это работает, потому что установка младших бит в ноль всегда будет корректировать %esp
вниз по значению, а стек растет вниз на x86.
Ответ 5
У меня нет ответов, но я могу объяснить, что знаю.
ebp
используется функцией для хранения начального состояния esp
во время его потока, ссылка на где аргументы передаются функции, а где - собственные локальные переменные. Первое, что делает функция, заключается в сохранении статуса заданного ebp
выполнения pushl %ebp
, это жизненно важно для функции, выполняющей вызов, и вместо нее заменяет ее текущая позиция текущего стека esp
, выполняющая movl %esp, %ebp
, Обнуление последних 4 бит ebp
на данный момент является специфичным для GCC, я не знаю, почему этот компилятор делает это. Он работал бы без этого. Теперь, наконец, мы занимаемся бизнесом, call ___main
, кто __main? Я даже не знаю... возможно, больше специальных процедур GCC, и, наконец, единственное, что делает ваш main(), установить возвращаемое значение как 0 с помощью movl $0, %eax
и leave
, что аналогично выполнению movl %ebp, %esp; popl %ebp
для восстановления ebp
, затем ret
. ret
pops eip
и продолжить поток потока из этой точки, где бы он ни находился (в качестве основного() этот ret, вероятно, приводит к некоторой процедуре ядра, которая обрабатывает конец программы).
Большая часть всего касается управления стеком. Я написал подробный учебник о том, как стек используется некоторое время назад, было бы полезно объяснить, почему все эти вещи сделаны. Но его в португальском...