Помогите расшифровать простой ассемблерный код - PullRequest
0 голосов
/ 15 ноября 2010

Я изучаю ассемблер с использованием GDB и Eclipse
Вот простой C-код.

int absdiff(int x, int y)
{  
if(x < y)  
  return y-x;
else  
  return x-y;
}

int main(void) {  
int x = 10;  
int y = 15;  
absdiff(x,y);
return EXIT_SUCCESS;  
}

Вот соответствующие инструкции по сборке для main ()

          main:
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  # ???
25         int x = 10;    
080483c1:   movl $0xa,-0x4(%ebp) #move the "x(10)" to 4 address below frame pointer (why not push?)
26         int y = 15;
080483c8:   movl $0xf,-0x8(%ebp) #move the "y(15)" to 8 address below frame pointer (why not push?)
28         absdiff(x,y);
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
080483d6:   mov -0x4(%ebp),%eax 
080483d9:   mov %eax,(%esp)
080483dc:   call 0x8048394 <absdiff>
31         return EXIT_SUCCESS;
080483e1:   mov $0x0,%eax
32        }

По сути, я прошу помочь мне разобраться в этом ассемблерном коде и почему он делает вещи именно в этом конкретном порядке. Точка, в которой я застрял, показана в комментариях сборки. Спасибо!

Ответы [ 4 ]

3 голосов
/ 15 ноября 2010

Строки 0x080483cf в 0x080483d9 копируют x и y из текущего кадра в стеке и помещают их обратно в стек в качестве аргументов для absdiff() (это типично; см., Например, http://en.wikipedia.org/wiki/X86_calling_conventions#cdecl). Если вы посмотрите на дизассемблер для absdiff() (начиная с 0x8048394), держу пари, вы увидите, что он берет эти значения из стека и использует их.

Это может выглядеть такв данном случае это пустая трата циклов, но это, вероятно, потому, что вы скомпилировали без оптимизации, поэтому компилятор делает буквально то, что вы просили. Если вы используете, например, -O2, вы, вероятно, увидите, что большая часть этого кода исчезнет.

2 голосов
/ 16 ноября 2010

Во-первых, стоит сказать, что эта сборка имеет синтаксическую версию 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
1 голос
/ 16 ноября 2010

Скомпилируйте с -fverbose-asm -g -save-temps для дополнительной информации с GCC.

1 голос
/ 16 ноября 2010

Помните, что если вы работаете в режиме отладки или в режиме отладки, компилятор оставляет за собой право вставлять любой код отладки, который ему нравится, и вносить другие бессмысленные изменения кода.

Например, это отладочная main () Visual Studio:

int main(void) {  
001F13D0  push        ebp  
001F13D1  mov         ebp,esp  
001F13D3  sub         esp,0D8h  
001F13D9  push        ebx  
001F13DA  push        esi  
001F13DB  push        edi  
001F13DC  lea         edi,[ebp-0D8h]  
001F13E2  mov         ecx,36h  
001F13E7  mov         eax,0CCCCCCCCh  
001F13EC  rep stos    dword ptr es:[edi]  
    int x = 10;  
001F13EE  mov         dword ptr [x],0Ah  
    int y = 15;  
001F13F5  mov         dword ptr [y],0Fh  
    absdiff(x,y);
001F13FC  mov         eax,dword ptr [y]  
001F13FF  push        eax  
001F1400  mov         ecx,dword ptr [x]  
001F1403  push        ecx  
001F1404  call        absdiff (1F10A0h)  
001F1409  add         esp,8  
    *(int*)nullptr = 5;
001F140C  mov         dword ptr ds:[0],5  
    return 0;  
001F1416  xor         eax,eax  
}
001F1418  pop         edi  
001F1419  pop         esi  
001F141A  pop         ebx  
001F141B  add         esp,0D8h  
001F1421  cmp         ebp,esp  
001F1423  call        @ILT+300(__RTC_CheckEsp) (1F1131h)  
001F1428  mov         esp,ebp  
001F142A  pop         ebp  
001F142B  ret  

Полезно размещает исходный код C ++ рядом с соответствующей сборкой. В этом случае вы можете довольно четко видеть, что x и y явно хранятся в стеке, и включается явная копия, а затем вызывается absdiff. Я явно удалил ссылку на nullptr, чтобы вызвать отладчик. Вы можете изменить компилятор.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...