Найти в Дзене

Ассемблирование и линковка: собираем пазл из объектных файлов

Объектные файлы и линкер-скрипты: как микроконтроллер узнаёт, где что лежит После того как у нас есть ассемблерный код (main.s), его нужно превратить в настоящий машинный код. Это делает ассемблер (в составе LLVM). Он просто транслируеь каждую инсрукцию в байты и упаковывает их в объектный файл (main.o). Объектный файл - это контейнер с несколькими секциями: 🔴 .text - код программы. 🔴 .data - инициализированные глобальные переменные. 🔴 .bss - описание неинициализированных переменных (занимает место в памяти, но не в файле). 🔴 Таблица символов - имена функций и переменных, определённых в этом файле. 🔴 Записи релокаци - пометки о местах, куда позже нужно подставить реальные адреса. На этом этапе адреса ещё не назначены. Например, вызов функции может быть записан как "вызвать функцию по адресу 0", потому что реальное расположение функции станет известно только после зборки всех объектных файлов. Создать объектный файл в Clang можно так: clang -c main.s -o main.o Теперь у нас ес

Ассемблирование и линковка: собираем пазл из объектных файлов

Объектные файлы и линкер-скрипты: как микроконтроллер узнаёт, где что лежит

После того как у нас есть ассемблерный код (main.s), его нужно превратить в настоящий машинный код. Это делает ассемблер (в составе LLVM). Он просто транслируеь каждую инсрукцию в байты и упаковывает их в объектный файл (main.o).

Объектный файл - это контейнер с несколькими секциями:

🔴 .text - код программы.

🔴 .data - инициализированные глобальные переменные.

🔴 .bss - описание неинициализированных переменных (занимает место в памяти, но не в файле).

🔴 Таблица символов - имена функций и переменных, определённых в этом файле.

🔴 Записи релокаци - пометки о местах, куда позже нужно подставить реальные адреса.

На этом этапе адреса ещё не назначены. Например, вызов функции может быть записан как "вызвать функцию по адресу 0", потому что реальное расположение функции станет известно только после зборки всех объектных файлов.

Создать объектный файл в Clang можно так:

clang -c main.s -o main.o

Теперь у нас есть несколько объектных файлов (например, main.o, delay.o, uart.o). Их нужно объединить в одну программу. Это делает компоновщик (линкер) - в мире LLVM это lld.

Линковка решает две задачи:

🟡 Разрешение символов. Сопоставляет вызовы функций с их фактическими определениями. Если функция объявлена, но не определена - ошибка "undefined reference".

🟡 Релокация. Расставляет реальные адреса. В объектном файле вызов delay() был "вызови по адресу 0". Линкер знает, что функция delay будет лежать, скажем, по адресу 0x1000, и вписывает этот адрес в код.

Но для микроконтроллера этого недостаточно: нужно ещё сказать, куда именно класть код и данные. Ведь у микроконтроллера есть флеш-память (для кода) и RAM (для переменных). Для этого используется линкер-скрипт (.ld). Он описывает карту памяти: "флеш начинается с адреса 0x08000000, RAM - с 0x20000000; код клади во флеш, переменные - в RAM, а начальные значения переменых храни во флеше и копируй при старте".

Линковка с линкер-скриптом выглядит так:

clang main.o delay.o -T stm32f103.ld -o firmware.elf

На выходе получается ELF-файл - исполняемый образ, содержащий все байты кода и данных, а также отладочную информацию. Именно этот файл нужен отладчику (GDB, например), чтобы показывать вам исходный код во время отладки. Но микроконтроллеру ELF не подходит - ему нужен сырой дамп. Об этом - в заключительном посте.

#компоновка #линковка #ELF #объектныефайлы #LLVM #микроконтроллеры