Полагаю, у каждого третьего лица свой сценарий или решение. Есть ряд проблем, которые нужно решить, разные линкеры решат их по-разному. Я думаю, что GNU сделал это слишком трудным, если не черной магией.
Для встроенной системы у вас часто будет флэш-память, eeprom или какая-либо другая форма постоянной памяти для загрузки. Как и другие процессоры, ARM имеет векторную таблицу, которая, по существу, сообщает ему, где находится код сброса и прерывания, и т. Д. Таким образом, эта таблица должна находиться в определенном месте, и вы должны указать компоновщику поместить его в это конкретное место (сначала). .
Один из сценариев, который мне нравится использовать:
MEMORY
{
bob (RX) : ORIGIN = 0x0000000, LENGTH = 32K
joe (WAIL) : ORIGIN = 0x2000000, LENGTH = 256K
}
SECTIONS
{
JANE : { startup.o } >bob
}
Я обычно использую ram и rom в качестве имен вместо bob и joe, но демонстрирую здесь, что не имеет значения, какие это имена, они просто метки.
Еще одна вариация на тему:
MEMORY
{
rom(RX) : ORIGIN = 0x00000000, LENGTH = 0x8000
ram(WAIL) : ORIGIN = 0x20000000, LENGTH = 0x2000
}
SECTIONS
{
.text : { *(.text*) } > rom
}
Первый позволяет вам помещать файлы в командную строку компоновщика в любом порядке, но вы должны иметь векторную таблицу в файле startup.o. Последний позволяет вам использовать любые имена файлов, но первый файл в скрипте компоновщика должен иметь таблицу векторов.
arm-thumb-elf-gcc -Wall $(COPS) vectors.o putget.o blinker2.c -T memmap -o blinker2.elf
Или напрямую с ld
arm-thumb-elf-ld vectors.o putget.o blinker2.o -T memmap -o blinker2.elf
RX говорит компоновщику поместить чтение и выполнение в этот раздел памяти, а WAIL - это в основном все остальное. Например, если у вас есть только один ОЗУ, вы можете поместить все флаги RXWAIL в строку, указывающую, где находится ОЗУ. В зависимости от вашего загрузчика в этом случае вы можете положиться на файл elf, сообщающий загрузчику, куда переходить, чтобы начать, или вы можете просто сделать точку входа началом двоичного файла, и загрузчик может быть проще. Руки (не cortex-m3) имеют инструкцию перехода в качестве первого вектора для вектора сброса, так что вы можете в любом случае притвориться, что строите таблицу векторов для решения оперативной памяти, и это просто сработает.
Существует ряд проблем с этим решением, которые меня не беспокоят. Я инициализирую переменные в своем коде, а не во время объявления.
Это
int rx;
int main ( void )
{
rx = 7;
вместо
int rx=7;
int main ( void )
{
Я также никогда не предполагаю, что переменная равна нулю, когда код запускается, я всегда инициализирую его чем-то перед тем, как начать. Ваш код запуска и скрипт компоновщика как команда могут работать вместе, чтобы упростить автоматизацию сброса кода bss и копирования ненулевых данных инициализации из rom в ram при загрузке. (что int rx = 7; выше требуется некоторый код, который копирует значение 7 откуда-то в rom и записывает его в ячейку памяти в ram, выделенную для переменной rx, чтобы при запуске main () значение 7 было здесь.
Мой загрузочный код также довольно прост в результате использования этого метода:
.globl _start
_start:
b reset
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
b hang
hang : b hang
reset:
ldr sp,=0x10004000
bl main
b hang
Вы увидите или прочитаете о решениях, которые позволяют коду запуска и скрипту компоновщика работать вместе, чтобы не приходилось жестко кодировать указатели стека, пространство кучи и тому подобное, опять же, вы можете много работать в сложном запуске Сценарии кода и компоновщика, чтобы получить некоторую автоматизацию и, возможно, сохранить некоторую работу, возможно, нет. Автоматизация, если / когда работа может и будет уменьшать человеческие ошибки, и это может быть хорошо, также, если вы часто переключаете микросхемы или пытаетесь написать один бит кода, который работает в семействе микросхем, вы можете также захотеть эту автоматизацию .
Суть в том, что ДА, вы можете жить только с одним сценарием компоновщика для всей вашей работы ARM. Но вы должны адаптировать свою работу к этому сценарию. Скорее всего, вы не найдете ни одного скрипта, который работает с каждым примером кода. Чем сложнее сценарий, тем сложнее его заимствовать. Да, мои вышеописанные сценарии, вероятно, могут быть выполнены в командной строке ld, но когда (gcc 2.95) я не мог заставить это работать, я разработал минимальный сценарий, описанный выше, и с тех пор использую их. По какой-то причине пришлось изменить второй скрипт, но с 4.x.x, конечно, 4.4.x Я могу использовать любой из них.