2015-09-06 3 views
0

У меня есть плата ARM с ROM на 0x80000000 и оперативная память 0x20000000. Плата запускает исполняемый двоичный код с 0x80000000.ARM bare metal двоичное связывание

Мне удалось запустить простую программу ассемблера ARM, но я должен использовать C вместо ASM. Я знаю, что мне нужно будет использовать какой-то скрипт компоновщика, а затем вручную скопировать раздел .data в ОЗУ и очистить .bss, установить стек и т. Д., Но я не нашел надежного решения, как это сделать, особенно скрипт компоновщика (это очень грязно, на мой взгляд).

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

Заранее спасибо.

+3

Какая цепь инструментов вы используете (GNU, я полагаю?). Какую часть ARM вы используете (или, возможно, укажите доску)? Является ли ОЗУ встроенной или внешней? Будет ли код работать с ПЗУ или ОЗУ? Ответ на эти вопросы даст вам лучший ответ. Но, по крайней мере, теперь я могу сказать вам, что objcopy - это именно то, как вы генерируете необработанный двоичный файл (или шестнадцатеричный файл, если на то пошло); как правило, в вашем make-файле, или шаг после сборки в вашей среде IDE. – Clifford

+0

GNU toolchain, встроенная оперативная память, код будет выполнен с ROM. –

+0

Для мигания, если вы не используете статические переменные (только стек), вам не нужно копировать данные из FLASH в SRAM. – vlk

ответ

2

Этот пример для STM32F051 MCU:

Очень простое приложение, как "Блинки" (без задержек):

// define used registers 
#define RCC_AHB1 *(volatile unsigned int *)(0x40021014) 
#define GPIOC_MODER *(volatile unsigned int *)(0x48000800) 
#define GPIOC_BSRR *(volatile unsigned int *)(0x48000818) 

// main program 
void mainApp() { 
    RCC_AHB1 = 1 << 19; // enable clock for GPIOC 
    GPIOC_MODER = 1 << (9 * 2); // set output on GPIOC.P9 
    while (1) { 
     GPIOC_BSRR = 1 << 9; // set output on GPIOC.P9 
     GPIOC_BSRR = 1 << (9 + 16); // clear output on GPIOC.P9 
    } 
} 

// variables for testing memory initialisation 
int x = 10; 
int y = 0; 
int z; 

Здесь также очень простой файл запуска написан на C (также будет работать в C++), которые запускают приложение mainApp и инициализируют статические переменные .data, инициализированные из ROM и .bss, установлены только на ноль, есть переменные, инициализированные нулем и неинициализированные переменные.

extern void mainApp(); 

// external variables defined in linker script 
// address in FLASH where are stored initial data for .data section 
extern unsigned int _data_load; 
// defines start and end of .data section in RAM 
extern unsigned int _data_start; 
extern unsigned int _data_end; 
// defines start and end of .bss section in RAM 
extern unsigned int _bss_start; 
extern unsigned int _bss_end; 

void resetHandler() { 
    unsigned int *src, *dst; 

    // copy .data area 
    src = &_data_load; 
    dst = &_data_start; 
    while (dst < &_data_end) { 
     *dst++ = *src++; 
    } 

    // clear .bss area 
    dst = &_bss_start; 
    while (dst < &_bss_end) { 
     *dst++ = 0; 
    } 

    mainApp(); 

    while(1); 
} 

// _stacktop is defined in linker script 
extern unsigned int _stacktop; 

// vector table, will be placed on begin of FLASH memory, is defined in linker script 
// only reset vector defined, need add other used vectors (especially NMI) 
__attribute__((section(".vectors"), used)) void *isr_vectors[] = { 
    &_stacktop, // first vector is not vector but initial stack position 
    (void *)resetHandler, // vector which is called after MCU start 
}; 

И, наконец компоновщик скрипт, который определяет расположение и размеры памяти, секции для вектора, программы (текст), данные (.data и.ПБС) а stacktop

MEMORY { 
    FLASH(rx) : ORIGIN = 0x08000000, LENGTH = 64K 
    SRAM(rwx) : ORIGIN = 0x20000000, LENGTH = 8K 
} 

SECTIONS { 
    . = ORIGIN(FLASH); 
    .text : { 
     *(.vectors) 
     *(.text) 
    } >FLASH 

    . = ORIGIN(SRAM); 
    .data ALIGN(4) : { 
     _data_start = .; 
     *(.data) 
     . = ALIGN(4); 
     _data_end = .; 
    } >SRAM AT >FLASH 

    .bss ALIGN(4) (NOLOAD) : { 
     _bss_start = .; 
     *(.bss) 
     . = ALIGN(4); 
     _bss_end = .; 
    } >SRAM 

    _stacktop = ORIGIN(SRAM) + LENGTH(SRAM); 
    _data_load = LOADADDR(.data); 
} 

Построить это с этими командами (построить и ссылку на один раз):

$ arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -nostartfiles main.c startup.c -T stm32f051x8.ld -o main.elf 

В таблице символов можно увидеть, где хранятся данные:

$ arm-none-eabi-nm -C -l -n -S main.elf 
08000000 00000008 T isr_vectors 
08000008 00000034 T mainApp 
0800003c 0000005c T resetHandler 
08000098 A _data_load 
20000000 D _data_start 
20000000 00000004 D x 
20000004 D _data_end 
20000004 B _bss_start 
20000004 00000004 B y 
20000008 B _bss_end 
20000008 00000004 B z 
20002000 A _stacktop 

Также вы можете ознакомиться с перечнем:

arm-none-eabi-objdump -S main.elf 

Необработанные двоичные файлы:

arm-none-eabi-objcopy -O binary main.elf main.bin 
1
MEMORY 
{ 
    bob : ORIGIN = 0x8000, LENGTH = 0x1000 
    ted : ORIGIN = 0xA000, LENGTH = 0x1000 
} 

SECTIONS 
{ 
    .text : { *(.text*) } > bob 
    __data_rom_start__ = .; 
    .data : { 
    __data_start__ = .; 
    *(.data*) 
    } > ted AT > bob 
    __data_end__ = .; 
    __data_size__ = __data_end__ - __data_start__; 
    .bss : { 
    __bss_start__ = .; 
    *(.bss*) 
    } > ted 
    __bss_end__ = .; 
    __bss_size__ = __bss_end__ - __bss_start__; 
} 

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

Если вы должны были сделать что-то вроде этого

MEMORY 
{ 
    rom : ORIGIN = 0x80000000, LENGTH = 0x1000 
    ram : ORIGIN = 0x20000000, LENGTH = 0x1000 
} 

SECTIONS 
{ 
    .text : { *(.text*) } > rom 
    .bss : { *(.bss*) } > ram 
    .rodata : { *(.rodata*) } > rom 
    .data : { *(.data*) } > ram 
} 

, а затем objcopy тогда вы будете в конечном итоге с огромным файлом, даже если у вас есть только одна команда из .text и один байт данных , потому что двоичный формат должен покрывать все, как в памяти, поэтому он сделает файл размером 0x80000000 + sizeof (text) -0x20000000. Если бы у вас была одна инструкция только в ram и один байт данных, тогда файл был бы 0x60000004 байта или 1,6 гигабайта. оставьте его как эльф и используйте objdump-D, пока у вас не будет разработан ваш скрипт компоновщика. THEN создайте .bin, если он вам действительно нужен. или сделать запись hex hex или s, и снова вы можете проверить ее, чтобы увидеть, все ли они в одном адресном пространстве, прежде чем пытаться выполнить двоичный код.

первый ключ к вашей проблеме является AT

MEMORY 
{ 
    bob : ORIGIN = 0x8000, LENGTH = 0x1000 
    ted : ORIGIN = 0xA000, LENGTH = 0x1000 
} 

SECTIONS 
{ 
    .text : { *(.text*) } > bob 
    .data : { *(.data*) } > ted AT > bob 
    .bss : { *(.bss*) } > bob 
} 

он говорит, что я хочу .data в адресном пространстве Ted, но поместить его в бинарном файле в бобе пространстве. он компилируется для адресного пространства ted, но бит загружается из пространства bob. именно то, что вы хотите. кроме того, что вы не знаете, сколько .data для копирования из rom в ram. вы должны быть очень осторожны, когда вы помещаете переменные в более сложные, или это не работает правильно.

+0

да скрипты компоновщика беспорядок, esp gnu ld. есть много примеров, если вы просто посмотрите, в том числе код начальной загрузки, который нули bss и копирует .data (в основном все они). скрипты компоновщика также очень специфичны для инструментальной цепочки, поэтому, если вы переходите от gnu к чему-то другому, тогда вы начинаете все начинать. Или вы можете написать свой код, чтобы он не имел никаких .data и не полагался на .bss, который был обнулен, а затем ваши скрипты компоновщика, если они есть (иногда может просто использовать командную строку, хотя в случае gnu это создает дорогостоящие пробелы). –

+0

objdump - ваш друг, используйте его часто, если не всегда. –

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