Подтвердить что ты не робот

Вывод сборок 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: возвращается к адресу инструкции, который сохраняется в стеке

Благодарим за помощь!

4b9b3361

Ответ 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, вероятно, приводит к некоторой процедуре ядра, которая обрабатывает конец программы).

Большая часть всего касается управления стеком. Я написал подробный учебник о том, как стек используется некоторое время назад, было бы полезно объяснить, почему все эти вещи сделаны. Но его в португальском...