Вывод сборки GCC пустой программы на x86, win32
Я пишу пустые программы, чтобы чертовски раздражать кодеров stackru, НЕ. Я просто изучаю набор инструментов GNU.
Теперь следующее может быть слишком глубоким для меня, но чтобы продолжить пустую сагу программы, я начал изучать вывод компилятора 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
руководство и мои минимальные знания x86 ASM:
.file "test.c"
директива для логического имени файла..def
: в соответствии с документацией "Начните определять отладочную информацию для имени символа". Что такое символ (имя функции / переменная?) И какая отладочная информация?.scl
: docs говорит: "Класс хранения может указывать, является ли символ статическим или внешним". Это то же самое статическое и внешнее, что я знаю из C? И что это за "2"?.type
: хранит параметр "как атрибут типа записи таблицы символов", я понятия не имею..endef
: нет проблем..text
Теперь это проблематично, кажется, что это называется разделом, и я читал, что это место для кода, но документы не сказали мне слишком много..globl
"делает символ видимым для ld." Руководство по этому вопросу вполне понятно._main:
Это может быть начальный адрес (?) Для моей основной функцииpushl_
: Длинный (32-битный) пуш, который помещает EBP в стекmovl
: 32-битный ход. Псевдо-C:EBP = ESP;
andl
Логическое И. Псевдо-C:ESP = -16 & ESP
Я не понимаю, в чем смысл этого.call
: Выталкивает IP в стек (чтобы вызываемая процедура могла найти свой путь назад) и продолжает там, где__main
является. (что такое __main?)movl
: этот ноль должен быть константой, которую я возвращаю в конце моего кода. MOV помещает этот ноль в EAX.leave
: восстанавливает стек после инструкции ENTER (?). Зачем?ret
: возвращается к адресу инструкции, сохраненному в стеке
Спасибо за помощь!
5 ответов
.file "test.c"
Команды, начинающиеся с. директивы ассемблеру. Это просто говорит, что это "file.c", эта информация может быть экспортирована в отладочную информацию exe.
.def ___main;.scl 2;.type 32;.endef
Директивы.def определяют символ отладки. scl 2 означает класс хранения 2(класс внешнего хранения) .type 32 говорит, что этот символ является функцией. Эти числа будут определены в ex-формате pe-coff
___main - это вызываемая функция, которая заботится о начальной загрузке, необходимой gcc (она будет выполнять такие вещи, как запуск статических инициализаторов C++ и другие необходимые служебные действия).
.text
Начинается текстовый раздел - код живет здесь.
.globl _main
определяет символ _main как глобальный, что делает его видимым для компоновщика и для других модулей, которые связаны в.
.def _main; .scl 2; .type 32; .endef
То же самое, что и _main, создает отладочные символы, утверждающие, что _main является функцией. Это может быть использовано отладчиками.
_главный:
Запускает новый ярлык (в конечном итоге это адрес). директива.globl выше делает этот адрес видимым для других объектов.
pushl %ebp
Сохраняет старый указатель кадра (регистр ebp) в стеке (чтобы его можно было вернуть на место после завершения этой функции)
movl %esp, %ebp
Перемещает указатель стека в регистр ebp. ebp часто называют указателем кадра, он указывает на верхнюю часть значений стека в текущем "кадре"(обычно это функция) (обращение к переменным в стеке через ebp может помочь отладчикам)
andl $ -16,% esp
Заканчивает стек с помощью fffffff0, который эффективно выравнивает его по 16-байтовой границе. Доступ к выровненным значениям в стеке намного быстрее, чем если бы они были выровнены. Все эти предыдущие инструкции в значительной степени являются стандартным прологом функции.
call ___main
Вызывает функцию ___main, которая выполнит инициализацию того, что нужно gcc. Call поместит указатель текущей инструкции в стек и перейдет к адресу ___main
movl $0, %eax
переместите 0 в регистр eax, (0 в ответ 0;) регистр eax используется для хранения значений, возвращаемых функцией для соглашения о вызовах stdcall.
Покидать
Инструкция по отпуску в значительной степени является сокращением для
movl ebp,esp popl ebp
то есть он "отменяет" работу, выполняемую в начале функции, - восстанавливая указатель кадра и укладывая его в прежнее состояние.
RET
Возвращает тому, кто вызвал эту функцию. Он извлечет указатель инструкций из стека (который будет помещена в него соответствующей инструкцией вызова) и перейдет туда.
Здесь очень похожее упражнение: http://en.wikibooks.org/wiki/X86_Assembly/GAS_Syntax
Вы выяснили большую часть этого - я просто сделаю дополнительные заметки для акцентов и дополнений.
__main
это подпрограмма в стандартной библиотеке GNU, которая заботится о различной инициализации при запуске. Это не является строго необходимым для программ на C, но требуется только в том случае, если код C связан с C++.
_main
ваша основная подпрограмма. Как оба _main
а также __main
являются местоположениями кода, у них есть тот же класс хранения и тип. Я еще не выкопал определения для .scl
а также .type
еще. Вы можете получить некоторое освещение, определив несколько глобальных переменных.
Первые три инструкции устанавливают фрейм стека, который является техническим термином для рабочего хранилища подпрограммы - по большей части локальных и временных переменных. Нажимать ebp
сохраняет базу кадра стека вызывающего. Ввод esp
в ebp
устанавливает основу нашего стекового фрейма. andl
выравнивает кадр стека по 16-байтовой границе на случай, если любые локальные переменные в стеке требуют 16-байтового выравнивания (для инструкций x86 SIMD требуется это выравнивание, но выравнивание действительно ускоряет обычные типы, такие как int
с и float
s.
В этот момент вы обычно ожидаете esp
перемещаться вниз в памяти, чтобы выделить место в стеке для локальных переменных. Ваш main
нет ни одного, так что GCC не беспокоит.
Призыв к __main
является особенным для основной точки входа и обычно не отображается в подпрограммах.
Остальное идет, как вы и предполагали. регистр eax
это место для целочисленных кодов возврата в двоичной спецификации. leave
отменяет кадр стека и ret
возвращается к звонящему. В этом случае вызывающая сторона - это низкоуровневая среда выполнения C, которая создаст дополнительную магию (например, вызов atexit()
функции, установите код выхода для процесса и попросите операционную систему прекратить процесс.
Относительно этого andl $ -16,% esp
- 32 бита: -16 в десятичной системе счисления равно 0xfffffff0 в шестнадцатеричном представлении
- 64 бита: -16 в десятичной системе счисления равно 0xfffffffffffffff0 в шестнадцатеричном представлении
Таким образом, он будет маскировать последние 4 бита ESP (между прочим, 2**4 равно 16) и сохранит все остальные биты (независимо от того, является ли целевая система 32 или 64 битами).
В дополнение к andl $-16,%esp
, это работает, потому что установка младших битов в ноль всегда будет корректироваться %esp
по стоимости, и стек растет на x86.
У меня нет ответов на все вопросы, но я могу объяснить, что я знаю.
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
попса eip
и продолжить поток потока с этой точки, где бы он ни находился (в качестве своей функции main() этот ret, вероятно, приводит к некоторой процедуре ядра, которая обрабатывает конец программы).
Большая часть всего этого об управлении стеком. Я написал подробное руководство о том, как стек используется некоторое время назад, было бы полезно объяснить, почему все эти вещи сделаны. Но это на португальском...