Во-первых, стоит сказать, что эта сборка имеет синтаксическую версию AT & T x86_32 и что порядок аргументов операций обратен синтаксису Intel (используется с MASM, YASM и многими другими ассемблерами и отладчиками).
080483bb: push %ebp #push old frame pointer onto the stack
080483bc: mov %esp,%ebp #move the frame pointer down, to the position of stack pointer
080483be: sub $0x18,%esp # ???
Это входит в кадр стека.Кадр - это область памяти между указателем стека (esp) и базовым указателем (ebp).Эта область предназначена для использования локальными переменными, которые должны жить в стеке.ПРИМЕЧАНИЕ: стековые кадры не должны быть реализованы таким образом, и GCC имеет переключатель оптимизации -fomit-frame-pointer
, который устраняет его, за исключением случаев, когда используются alloca
или массивы переменного размера, потому что они реализуются путем изменения указателя стекапо произвольным значениям.Отказ от использования ebp в качестве указателя кадра позволяет использовать его в качестве дополнительного регистра общего назначения (обычно лучше использовать регистры общего назначения).
Использование базового указателя упрощает вычисления для компиляторов и отладчиков, посколькупеременные, расположенные относительно базы, не меняются в функции, но вы также можете индексировать их относительно указателя стека и получать те же результаты, хотя указатель стека имеет тенденцию изменяться, поэтому для одного и того же места может потребоваться другоеиндекс в разное время.
В этом коде 0x18 (или 24) байтов зарезервированы в стеке для локального использования.
Этот код до сих пор часто называют прологом функции (а непутать с языком программирования "пролог").
25 int x = 10;
080483c1: movl $0xa,-0x4(%ebp) #move the "x(10)" to 4 address below frame pointer (why not push?)
Эта строка перемещает постоянную 10 (0xA) в местоположение в текущем кадре стека относительно базового указателя.Поскольку базовый указатель находится ниже вершины стека и поскольку стек в оперативной памяти растет вниз, индекс скорее отрицательный, чем положительный.Если бы они были проиндексированы относительно указателя стека, использовался бы другой индекс, но он был бы положительным.
Вы правы, что это значение могло быть передано, а не скопировано таким образом.Я подозреваю, что это делается так, потому что вы не скомпилировали с включенными оптимизациями.По умолчанию gcc (который, как я полагаю, вы используете в зависимости от того, как вы используете gdb) не сильно оптимизируется, и поэтому этот код, вероятно, используется по умолчанию для кода «скопировать константу в местоположение в стековом фрейме».Это может быть не так, но это одно из возможных объяснений.
26 int y = 15;
080483c8: movl $0xf,-0x8(%ebp) #move the "y(15)" to 8 address below frame pointer (why not push?)
Аналогично предыдущей строке кода.Эти две строки кода помещают 10 и 15 в локальные переменные.Они находятся в стеке (а не в регистрах), потому что это неоптимизированный код.
28 absdiff(x,y);
Печать GDB означает, что выполняется строка исходного кода, а не выполняется эта функция (пока).
080483cf: mov -0x8(%ebp),%eax # -0x8(%ebp) == 15 = y, and move it into %eax
При подготовке к вызову функции значения, передаваемые в качестве аргументов, должны быть извлечены из их мест хранения (даже если они были просто размещены в этих местах, и их значения известны из-занет оптимизации)
080483d2: mov %eax,0x4(%esp) # from this point on, I am confused
Это вторая часть перемещения в стек значения одной из локальных переменных, чтобы его можно было использовать в качестве аргумента функции.Вы не можете (обычно) перемещаться с одного адреса памяти на другой в x86, поэтому вам нужно переместить его через регистр (eax
в данном случае).
080483d6: mov -0x4(%ebp),%eax
080483d9: mov %eax,(%esp)
Эти две строки делают то же самоевещь, за исключением другой переменной.Обратите внимание, что поскольку эта переменная перемещается в верхнюю часть стека, во второй инструкции смещение не используется.
080483dc: call 0x8048394 <absdiff>
При этом адрес возврата перемещается на вершину стека и переходит на адресиз absdiff
.
Вы не включили код для absdiff
, поэтому вы, вероятно, не прошли через это.
31 return EXIT_SUCCESS;
080483e1: mov $0x0,%eax
Программы C возвращают 0 при успехе, поэтому EXIT_SUCCESS былопределяется как 0 кем-то.Целочисленные возвращаемые значения помещаются в eax
, и некоторый код, вызвавший функцию main
, будет использовать это значение в качестве аргумента при вызове функции exit
.
32 }
Это конец. Причина, по которой gdb остановился здесь, состоит в том, что есть вещи, которые на самом деле происходят, чтобы очистить В C ++ часто встречается деструктор для вызываемых здесь экземпляров локальных классов, но в C вы, вероятно, просто увидите эпилог функции. Это дополнение к прологу функции, состоящее из возврата указателя стека и базового указателя на значения, в которых они были изначально. Иногда это делается с похожей математикой, но иногда это делается с помощью инструкции leave
. Есть также инструкция enter
, которую можно использовать для пролога, но gcc этого не делает (я не знаю почему). Если бы вы продолжали просматривать разборку здесь, вы бы увидели код эпилога и инструкцию ret
.
Что-то, что вас может заинтересовать, - это способность указывать gcc создавать файлы сборки. Если вы делаете:
gcc -S source_file.c
будет создан файл с именем source_file.s
с кодом ассемблера.
Если вы делаете:
gcc -S -O source_file.c
Тогда произойдет то же самое, но будут сделаны некоторые базовые оптимизации. Это, вероятно, облегчит чтение кода на ассемблере, поскольку в коде вряд ли будет столько странных инструкций, которые выглядят так, как будто они могли бы быть выполнены лучше (например, перемещение значений константы в стек, затем в регистр, затем в другое место на стек и никогда не использовать команду push).
Обычные флаги оптимизации для gcc:
-O0 default -- none
-O1 a few optimizations
-O the same as -O1
-O2 a lot of optimizations
-O3 a bunch more, some of which may take a long time and/or make the code a lot bigger
-Os optimize for size -- similar to -O2, but not quite
Если вы на самом деле пытаетесь отлаживать программы на C, вам, вероятно, понадобится как можно меньше оптимизаций, поскольку все будет происходить в том порядке, в каком они написаны в вашем коде, а переменные не исчезнут.
Вы должны взглянуть на справочную страницу gcc:
man gcc