Сборка - .data, .code и регистры ...? - PullRequest
27 голосов
/ 01 марта 2010

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

А теперь я начинаю собираться и начинаю понимать, как она работает.

Вещи, которые я хорошо понимаю, включают стек, прерывания, двоичный / шестнадцатеричный код и вообще то, что делает большинство основных операций (jmp, push, mov и т. Д.).

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

  1. Что именно происходит в разделе .data? Эти переменные мы объявляем?
  2. Если это так, можем ли мы объявить переменные позже в разделе кода? Если нет, то почему? Если да, то как и почему мы используем раздел данных?
  3. Что такое регистр? Как это сравнивается с переменной? Я имею в виду, я знаю, что это место, где хранится небольшая часть информации ... но для меня это звучит как переменная.
  4. Как мне сделать массив? Я знаю, что это кажется случайным, но мне любопытно, как бы я поступил так:
  5. Есть ли где-нибудь список распространенных практик, для которых должен использоваться каждый регистр? Я до сих пор не понимаю их полностью, но заметил, что некоторые люди говорят, например, что определенный регистр следует использовать для хранения «возвращаемых значений» из процедур - есть ли исчерпывающий или хотя бы информативный список таких практик?
  6. Одна из причин, по которой я изучаю ассемблер, заключается в том, чтобы лучше понять, что происходит за моим высокоуровневым кодом. Имея это в виду - когда я программирую на c ++, я часто думаю о стеке и куче. В сборке я знаю, что такое стек - где «куча»?

Некоторая информация: я использую masm32 с WinAsm в качестве IDE и работаю в Windows 7. У меня большой опыт программирования на языках более высокого уровня, таких как c ++ / java.


edit: Спасибо за помощь всем, как всегда чрезвычайно информативно! Качественный товар! И последнее: мне интересно, в чем разница между указателем стека и указателем базы, или ESP и EBP. Кто-нибудь может мне помочь?

edit: Я думаю, я понял это сейчас ... ESP всегда указывает на вершину стека. Тем не менее, вы можете указать EBP на все, что вы хотите. ESP автоматически обрабатывается, но вы можете делать все, что вы хотите с EBP. Например:

push 6
push 5
push 4
mov EBP, ESP
push 3
push 2

В этом сценарии EBP теперь указывает на адрес, содержащий 4, но ESP теперь указывает на адрес, содержащий 2.

В реальном приложении 6, 5 и 4 могли бы быть аргументами функции, тогда как 3 и 2 могли бы быть локальными переменными внутри этой функции.

Ответы [ 2 ]

32 голосов
/ 01 марта 2010

Давайте попробуем ответить по порядку!

  1. Раздел данных содержит все, что вы хотите, чтобы система автоматически инициализировала вас перед вызовом точки входа вашей программы. Вы правы, обычно глобальные переменные оказываются здесь. Инициализированные нулями данные, как правило, не включаются в исполняемый файл, поскольку нет причин для этого - пара директив загрузчику программы - это все, что необходимо для создания этого пространства. Как только ваша программа запускается, области ZI и данных обычно взаимозаменяемы. Википедия имеет гораздо больше информации.

  2. Переменные на самом деле не существуют при программировании на ассемблере, по крайней мере, в том смысле, в котором они существуют, когда вы пишете код на Си. Все, что у вас есть, это решения, которые вы приняли о том, как выложить свою память. Переменные могут находиться в стеке, где-то в памяти или просто жить только в регистрах.

  3. Регистры являются внутренним хранилищем данных процессора. В общем случае вы можете выполнять операции только со значениями в регистрах процессора. Вы можете загружать и хранить их содержимое в памяти и из памяти, что является основной операцией работы вашего компьютера. Вот быстрый пример. Этот код C:

    int a = 5;
    int b = 6;
    int *d = (int *)0x12345678; // assume 0x12345678 is a valid memory pointer
    *d = a + b;
    

    Может быть переведен в некоторую (упрощенную) сборку по направлениям:

    load  r1, 5
    load  r2, 6
    load  r4, 0x1234568
    add   r3, r1, r2
    store r4, r3
    

    В этом случае вы можете рассматривать регистры как переменные, но в целом необязательно, чтобы какая-либо одна переменная всегда оставалась в одном и том же регистре; в зависимости от того, насколько сложна ваша рутина, это может быть даже невозможно. Вам нужно будет поместить некоторые данные в стек, удалить другие данные и так далее. «Переменная» - это логическая часть данных, а не место, где они хранятся в памяти или регистрах и т. Д.

  4. Массив - это просто непрерывный блок памяти - для локального массива вы можете просто уменьшить указатель стека соответствующим образом. Для глобального массива вы можете объявить этот блок в разделе данных.

  5. Существует несколько соглашений о регистрах - проверьте ABI вашей платформы или документ соглашения о вызовах для получения подробной информации о том, как их правильно использовать. Ваша документация на ассемблере также может содержать информацию. Проверьте статью ABI в Википедии .

  6. Ваша сборочная программа может выполнять те же системные вызовы, что и любая программа на C, поэтому вы можете просто вызвать malloc(), чтобы получить память из кучи.

17 голосов
/ 01 марта 2010

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

Сегмент кода - .code, .text: http://en.wikipedia.org/wiki/Code_segment

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

Сегмент данных - .data: http://en.wikipedia.org/wiki/Data_segment

Сегмент данных является одним из разделов программы в объектном файле или в память, которая содержит глобальный переменные и статические переменные, которые инициализируются программистом. Это имеет фиксированный размер, так как все данные в этом разделе устанавливаются программист перед программой загружен. Тем не менее, это не только для чтения, поскольку значения переменных могут быть изменены во время выполнения. Это в контраст по родате (постоянный, данные только для чтения), а также сегмент кода (также известный как текст сегмент).

BSS: http://en.wikipedia.org/wiki/.bss

В компьютерном программировании .bss или bss (который первоначально обозначал Блок Started by Symbol) используется многими компиляторы и компоновщики как имя часть сегмента данных, содержащая статические переменные и глобальные переменные которые заполнены исключительно данные с нулевым значением изначально (т. е. когда начнется казнь). Это часто упоминается как "раздел BSS" или "BSS сегмент". Загрузчик программы инициализирует память, выделенную для раздел BSS, когда он загружает программа.

Регистры, как описано другими, представляют собой средства ЦП для хранения данных или адреса памяти. Операции выполняются над регистрами, такими как add eax, ebx и в зависимости от диалекта сборки, что означает разные вещи. В этом случае это означает добавление содержимого ebx в eax и сохранение его в eax (синтаксис NASM). Эквивалент в GNU AS (AT & T): movl $ebx, $eax. Разные диалекты сборки имеют разные правила и операторы. По этой причине я не фанат MASM - он сильно отличается от NASM, YASM и GNU AS.

На самом деле нет никакого общего взаимодействия с C. ABI, как это происходит; например, в x86 (unix) вы найдете аргументы метода, помещенные в стек, тогда как в x86-64 в Unix первые несколько аргументов будут расположены в регистрах. Оба ABI ожидают, что результат функции будет сохранен в регистре eax / rax.

Вот 32-разрядная процедура добавления, которая собирается как для Windows, так и для Linux.

_Add
    push    ebp             ; create stack frame
    mov     ebp, esp
    mov     eax, [ebp+8]    ; grab the first argument
    mov     ecx, [ebp+12]   ; grab the second argument
    add     eax, ecx        ; sum the arguments
    pop     ebp             ; restore the base pointer
    ret

Здесь вы можете понять, что я имею в виду. Возвращаемое значение находится в eax. Версия x64, напротив, будет выглядеть так:

_Add
    push    rbp             ; create stack frame
    mov     rbp, rsp
    mov     eax, edi        ; grab the first argument
    mov     ecx, esi        ; grab the second argument
    add     eax, ecx        ; sum the arguments
    pop     rbp             ; restore the base pointer
    ret

Есть документы, которые определяют подобные вещи. Вот UNIX x64 ABI: http://www.x86 -64.org / Documentation / abi-0.99.pdf . Я уверен, что вы могли бы найти ABI для любого процессора, платформы и т. Д. Вам нужно.

Как вы работаете с массивом в сборке? Указатель арифметики. Учитывая базовый адрес в eax, следующее сохраненное целое число будет в [eax+4], если целое число имеет размер 4 байта. Вы можете создать это пространство, используя вызовы до malloc / calloc, или вызвать системный вызов выделения памяти, что бы это ни было в вашей системе.

Что такое "куча"? Согласно Википедии, это область памяти, зарезервированная для динамического выделения памяти. Вы не увидите его в вашей программе сборки, пока не вызовете calloc, malloc или системный вызов выделения памяти, но он есть.

Извините за сочинение.

...