2013-07-22 3 views
4

Я студент и только начал изучать язык ассемблера. Чтобы лучше понять это, я просто написал короткое слово на C и перевел его на ассемблерный язык. Удивительно, но я не понял.Что означает этот код языка ассемблера?

Код:

#include<stdio.h> 

int main() 
{ 
    int n; 
    n=4; 
    printf("%d",n); 
    return 0; 
} 

И соответствующий язык сборки:

.file "delta.c" 
    .section .rodata 
.LC0: 
    .string "%d" 
    .text 
    .globl main 
    .type main, @function 
main: 
.LFB0: 
    .cfi_startproc 
    pushl %ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    movl %esp, %ebp 
    .cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 
    movl $4, 28(%esp) 
    movl $.LC0, %eax 
    movl 28(%esp), %edx 
    movl %edx, 4(%esp) 
    movl %eax, (%esp) 
    call printf 
    movl $0, %eax 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" 
    .section .note.GNU-stack,"",@progbits 

Что означают эти?

+9

Какая часть дает вам проблемы? Мы не можем объяснить каждую строку, если вы на этом уровне, вам нужно начать с чтения книги, а не путем прямого перехода к чему-то непонятным. Расскажите нам, какие части вы понимаете и какие части вы не понимаете. – Gilles

+5

В вашем примере есть несколько основных понятий, которые будут затеряться в процессе объяснения инструкций по строкам. Если у вас мало или вообще нет понимания инструкций по сборке, вы должны получить книгу или какой-либо онлайн-материал, начиная с основ. Как только вы знакомы с работой инструкций, можно охватить более широкие концепции управления фреймом стека, правилами регистрации/памяти и функциональными вызовами. – lurker

+0

На самом деле, я мало знаю о языках ассемблера, все, что я знаю, это мотив, добавление и т. Д. Я бы лучше выбрал ваше мнение. –

ответ

26

Давайте разбить его:

.file "delta.c" 

Компилятор использует это, чтобы сказать вам исходный файл, сборка из. Это мало значит для ассемблера.

.section .rodata 

Это начинает новый раздел. «Родата» - это название раздела «Только для чтения». Этот раздел завершает запись данных в исполняемый файл, который получает память, отображаемую как данные только для чтения. Все страницы «.rodata» исполняемого образа в конечном итоге разделяются всеми процессами, загружающими изображение .

Как правило, любые «константы времени компиляции» в исходном коде, которые не могут быть оптимизированы в рамках встроенных функций, будут храниться в разделе «Только чтение данных».

.LC0: 
    .string "%d" 

.LC0" часть этикетки. Это доказывает символическое имя, которое ссылается на байты, которые происходят после него в файле. В этом случае «LC0» представляет строку «% d». Ассемблер GNU использует соглашение о том, что метки, начинающиеся с «L», считаются «локальными метками». Это имеет технический смысл, который в основном интересен людям, которые пишут компиляторы и линкеры. В этом случае он используется компилятором для обращения к символу, который является частным для конкретного объектного файла. В этом случае он представляет собой строчную константу.

.text 

Это начинает новый раздел. Раздел «текст» - это раздел в объектных файлах, в которых хранится исполняемый код.

.globl main 

«.global» директива указывает ассемблеру, чтобы добавить метку, которая следует за ним в списке ярлыков „экспортируемые“ генерируемым объектный файл. Это в основном означает «это символ, который должен быть видимым компоновщику». Например, «нестатическая» функция в «C» может быть вызвана любым c-файлом, который объявляет (или включает) прототип совместимой функции. Вот почему вы можете #include stdio.h, а затем позвонить printf. Когда какая-либо нестатическая C-функция компилируется, компилятор создает сборку, которая объявляет глобальную метку, указывающую в начале функции. Сравните это с вещами, которые не должны быть связаны, например, с строковыми литералами. Ассемблерный код в объектном файле по-прежнему нуждается в ярлыке для ссылки на литеральные данные. Это «местные» символы.

.type main, @function 

Я не знаю точно, как GAS (ГНУ ассемблер) обрабатывает «.Type» директивы. Однако это указывает ассемблеру, что метка «main» относится к исполняемому коду, а не к данным.

main: 

Это определяет точку входа для вашей «основной» функции.

.LFB0: 

Это «местная метка», которая относится к началу функции.

.cfi_startproc 

Это директива «Информация о кадре». Он инструктирует ассемблер испускать информацию для отладки формата карлика.

pushl %ebp 

Это стандартная часть функции «пролог» в ассемблере. Он сохраняет текущее значение регистра «ebp». Регистр «ebp» или «base» используется для хранения «базы» фрейма стека внутри функции. В то время как регистр esp («указатель стека») может меняться, поскольку функции вызываются внутри функции, «ebp» остается фиксированным. Любые аргументы функции всегда можно получить относительно «ebp». По соглашениям ABI, прежде чем functon сможет изменить регистр EBP, он должен сохранить его, чтобы исходное значение могло быть восстановлено до возвращения функции.

.cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 

Я не исследовал их подробно, но я считаю, что они связаны с DWARF отладочной информации.

movl %esp, %ebp 

ГАЗ использует AT & T синтаксис, что в обратном направлении от того, что использует руководство Intel. Это означает, что «set ebp равен esp». Это в основном устанавливает «базовый указатель» для остальной части функции.

.cfi_def_cfa_register 5 
    andl $-16, %esp 
    subl $32, %esp 

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

movl $4, 28(%esp) 

Это загружает 32-битную целую константу 4 в слот в кадре стека.

movl $.LC0, %eax 

Это загружает константу строки "% d", определенную выше, в eax.

movl 28(%esp), %edx 

Это загружает значение «4», сохраненное в смещении 28 в стеке, в edx. Скорее всего, ваш код был скомпилирован с отключенными оптимизациями.

movl %edx, 4(%esp) 

Затем оно перемещает значение 4 в стек, в том месте, которое должно быть при вызове printf.

movl %eax, (%esp) 

Это загружает строку «% d» в место в стеке, которое должно быть при вызове printf.

call printf 

Это вызывает печатьf.

movl $0, %eax 

Это устанавливает EAX значение 0. Учитывая, что следующие инструкции «оставить» и «в отставке», это equavlent «вернуть 0» в коде C. Регистр EAX используется для хранения возвращаемого значения функции.

leave 

Эта инструкция очищает рамку вызова.Он возвращает ESP обратно в EBP, затем выталкивает EBP из модифицированного указателя стека. Как и следующая инструкция, это часть эпилога этой функции.

.cfi_restore 5 
    .cfi_def_cfa 4, 4 

Это более DWARF материал

ret 

Это фактическая команда возврата. Он возвращается из functon

.cfi_endproc 
.LFE0: 
    .size main, .-main 
    .ident "GCC: (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3" 
    .section .note.GNU-stack,"",@progbits 
+2

Конечно, в современной операционной системе каждый исполняемый файл (в первом приближении) является общим объектом. Таким образом, «rodata» не только разделяется между различными программами с использованием общей библиотеки, но и разделяется между разными экземплярами одной и той же программы, если одновременно запускаются несколько экземпляров. Ознакомьтесь с прекрасной книгой Джона Левина по теме связи и загрузки для получения дополнительной информации. http://www.iecc.com/linker/ – Pseudonym

+0

Да. Я знаю это. Благодарю. Я должен был быть более точным на моем языке. Спасибо, что указали это. –

+1

, если вы хотите знать, какой исходный код высокого уровня отвечает за все сгенерированные сборки, вы всегда можете использовать следующую команду для проверки: gcc -Wa, -adhln = delta.lst -g delta.c – rptr87

2

Для меня, синтаксис Intels легче читать, научиться генерировать INTELS синтаксис удобен для понимания программ C лучше;

gcc -S -masm=intel file.c 

В окнах ваша программа на C становится;

.file "file.c" 
    .intel_syntax noprefix 
    .def ___main; .scl 2; .type 32; .endef 
    .section .rdata,"dr" 
LC0: 
    .ascii "%d\0" 
    .text 
    .globl _main 
    .def _main; .scl 2; .type 32; .endef 
_main: 
LFB13: 
    .cfi_startproc 
    push ebp 
    .cfi_def_cfa_offset 8 
    .cfi_offset 5, -8 
    mov ebp, esp 
    .cfi_def_cfa_register 5 
    and esp, -16 
    sub esp, 32 
    call ___main 
    mov DWORD PTR [esp+28], 4 
    mov eax, DWORD PTR [esp+28] 
    mov DWORD PTR [esp+4], eax 
    mov DWORD PTR [esp], OFFSET FLAT:LC0 
    call _printf 
    mov eax, 0 
    leave 
    .cfi_restore 5 
    .cfi_def_cfa 4, 4 
    ret 
    .cfi_endproc 
LFE13: 
    .ident "GCC: (rev2, Built by MinGW-builds project) 4.8.1" 
    .def _printf; .scl 2; .type 32; .endef 

(опции компилятора должны быть такими же, как на убунту в окнах)

Помимо психотических этикетки, это больше похоже на сборку я читал в учебниках ..

Здесь это способ взглянуть на него;

call ___main 

    mov DWORD PTR [esp+28], 4 
    mov eax, DWORD PTR [esp+28]    ; int n = 4; 

    mov DWORD PTR [esp+4], eax 
    mov DWORD PTR [esp], OFFSET FLAT:LC0 
    call _printf       ; printf("%d",n); 

    mov eax, 0 
    leave         ; return 0; 
+1

Да, больше учебников использует формат Intel. Я начал программирование сборки (25 лет назад) в формате Intel. Сегодня я считаю, что формат AT & T легче читать отдельно от одного случая: безумные 386 режимы косвенной адресации. По любому расчёту '[ecx * 2 + 12]' легче читать, чем '12 (, ecx, 2)'. – Pseudonym

+0

Ahh, что интересно, это то, что стиль AT & Ts был более продуктивным, или вы просто оказались в синтаксисе больше, чем intels? Возможно, со временем я тоже начну выбирать способ AT & T. – James

+0

Я нашел его более продуктивным и в основном более легким для чтения. «DWORD PTR» доберутся до вас через COBOL, и x86-64 только усугубит ситуацию. – Pseudonym

Смежные вопросы