#object #assembly #linker #machine-code
#объект #сборка #компоновщик #машинный код
Вопрос:
Я ищу краткое описание использования ассемблера при создании машинного кода.
Итак, я знаю, что ассемблер — это перевод машинного кода в соотношении 1: 1. Но я начинаю путаться в объектном коде и компоновщиках и в том, как они в него помещаются.
Мне не нужен сложный ответ, достаточно простого
Ответ №1:
И ассемблер, и компилятор преобразуют исходные файлы в объектные файлы.
Объектные файлы фактически являются промежуточным этапом перед окончательным исполняемым результатом (генерируемым компоновщиком).
Компоновщик принимает указанные объектные файлы и библиотеки (которые являются пакетами объектных файлов) и разрешает записи перемещения (или «исправления»).
Эти записи перемещения создаются, когда компилятор / ассемблер не знает адреса функции или переменной, используемой в исходном коде, и генерирует ссылку на него по имени, которое может быть разрешено компоновщиком.
Допустим, вы хотите, чтобы программа выводила на экран сообщение, разделенное на два исходных файла, и вы хотите собрать их отдельно и связать (пример использования системных вызовов Linux x86-64) —
main.asm :
bits 64
section .text
extern do_message
global _start
_start:
call do_message
mov rax, 1
int 0x80
сообщение.asm :
bits 64
section .text
global do_message
do_message:
mov rdi, message
mov rcx, dword -1
xor rax, rax
repnz scasb
sub rdi, message
mov rax, 4
mov rbx, 1
mov rcx, message
mov rdx, rdi
int 0x80
ret
section .data
message: db "hello world",10,0
Если вы соберете их и посмотрите на выходные данные объектного файла main.asm (например, objdump -d main.o), вы заметите, что ‘call do_message’ имеет адрес 00 00 00 00 — что недопустимо.
0000000000000000 <_start>:
0: e8 00 00 00 00 callq 5 <_start 0x5>
5: 48 c7 c0 01 00 00 00 mov $0x1,%rax
c: cd 80 int $0x80
Но для 4 байт адреса создается запись перемещения :
$ objdump -r main.o
main.o: file format elf64-x86-64
RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
0000000000000001 R_X86_64_PC32 do_message 0xfffffffffffffffc
000000000000000d R_X86_64_32 .data
Смещение равно ‘1’, а тип ‘R_X86_64_PC32’, который сообщает компоновщику разрешить эту ссылку и поместить разрешенный адрес в указанное смещение.
Когда вы связываете конечную программу с ‘ld -o program main.o message.o’, все перемещения разрешены, и если ничего не осталось неразрешенным, вы остаетесь с исполняемым файлом.
Когда мы ‘objdump -d’ создаем исполняемый файл, мы можем видеть разрешенный адрес :
00000000004000f0 <_start>:
4000f0: e8 0b 00 00 00 callq 400100 <do_message>
4000f5: 48 c7 c0 01 00 00 00 mov $0x1,%rax
4000fc: cd 80 int $0x80
Такие же перемещения используются как для переменных, так и для функций.
Тот же процесс происходит, когда вы связываете свою программу с несколькими большими библиотеками, такими как libc — вы определяете функцию с именем ‘main’, на которую libc имеет внешнюю ссылку — затем libc запускается перед вашей программой и вызывает вашу функцию ‘main’ при запуске исполняемого файла.
Комментарии:
1. asm -> объект -> компоновка … маленький аккуратный пример.
2. не могли бы вы, пожалуйста, объяснить, почему смещение в объектном файле равно единице и как это вычисляется? Извините, если это глупый вопрос.
3. @gavlaaaaaaa : смещение ссылки на перемещение do_message в main.o равно 1, потому что код начинается с 0, а смещение 0 — это код операции ‘e8’, который является инструкцией ‘callq’, смещение 1 — это начало следующего 32-разрядного значения, которое является операндом для инструкции ‘callq’.
Ответ №2:
Простое объяснение:
Как только язык ассемблера собран в объектный код, компоновщик используется для преобразования объектного кода в исполняемый файл команд, который компьютер может понять и выполнить. Сгенерированный машинный код может быть интерпретирован контроллером центрального процессора.