null

Создание скриптов компоновщика ld

Мой студенческий проект в проедшем семестре был связан с разработкой под микроконтроллеры семейства Cortex M3 (архитектура ARMv7M). В отличие от x86 систем с общим адресным пространством и большой оперативной памятью (причем учитывая прогресс Grub'а в последнее время, достаточно просто сделать multi-boot совместимый образ, все остальное за нас сделает grub), в микроконтроллерах нужно распихивать данные между Flash и RAM явно посредством компоновщика. Учитывая что система не имеет виртуальной памяти (и относительной адресации), пришлось реализовать ld-скрипт, явно указывающий по каким адресам должен положить компоновщик образ ядра и пользовательские данные.

Итак, образ состоит из четырех основных сегментов: текст, данные, статические переменные (BSS) и стек. Для ядра у меня получился скрипт примерно следующего вида:
    

MEMORY
{
  MFlash512 (rx) : ORIGIN = 0x0, LENGTH = 0x80000 /* 512k */
  RamLoc32 (rwx) : ORIGIN = 0x10000000, LENGTH = 0x8000 /* 32k */
  RamAHB32 (rwx) : ORIGIN = 0x2007c000, LENGTH = 0x8000 /* 32k */
  CANAccFilterRAM (rwx) : ORIGIN = 0x40038000, LENGTH = 0x800 /* 2k */
}

ENTRY (__l4_start)

KERNEL_BASE = 0x00001000;
KERNEL_STACK_SIZE = 0x00000800;

SECTIONS {
    .text 0x00000000:
    {
        KEEP(*(.isr_vector))
        . = KERNEL_BASE;
        kernel_text_start = .;
        *(.text*)
        *(.rodata*)
        kernel_text_end = .;
    } > MFlash512
    
    .stack :
    {
        . += KERNEL_STACK_SIZE;
        kernel_stack_addr = .;
    } > RamLoc32
    
    .data :
    AT( ADDR(.text) + SIZEOF(.text) )
    ALIGN (128)
    {
        kernel_data_start = .;
        kip_start = .;
        *(.kip*)
        kip_end = .;
        /*Only kernel data*/
        *(.data*)
        kernel_data_end = .;
    } >RamLoc32

    /* zero initialized data */
    .bss (NOLOAD) :
    {
        kernel_bss_start = .;
        *(.bss*)
        *(.ktable*)            /* Statically allocated kernel table */
        *(COMMON)
        kernel_bss_end = .;
    } > RamLoc32

    .data_AHB (NOLOAD) :
    {
        kernel_ahb_start = .;
        *(.bitmap*)
        kernel_ahb_end = .;
    } > RamAHB32
}



Директива ENTRY указывает компоновщику точку входа. В моем случае это весьма условно, так как точка входа итак задается по нулевому смещению в векторе прерываний.

Точка в ld обозначает текущий счетчик расположения (location counter). Присваивание ему значения обозначает смещение относительно смещения самого сегмента (и таким образом, я например, резервирую место под стек). С другой стороны, можно присваивать его значение псевдо-переменным (как например kernel_data_start), чтобы затем получая их адрес, можно было точно знать, где расположен сегмент данных и, например, явно запрещать доступ пользовательского приложения к операционной системе. В коде это выглядит следующим образом:
extern uint32_t kernel_text_start;
Теперь при взятии адреса &kernel_data_start мы получим точный адрес начала сегмента данных ядра.

В конце объявления секции мы указываем, в какую из областей памяти размещать сегмент. Особый интерес здесь представляет сегмент данных, для которого начальные данные помещаются в Flash контроллера сразу за сегментом .text, а сами данные располагаются в RAM. Для этого и предусмотрена директива AT. С другой стороны, для BSS, данные инициализируются нулями и надо явно указать параметр NOLOAD (иначе в моем случае утилита программирования контроллера решала, что по адресу 512 Мб у нее расположены данные и сходила с ума, т.к. лицензии хватает только на прошивку 128k.

При старте системы нужно инициализировать BSS и data, что я собственно и делаю:
    

void init_zero_seg(uint32_t* dst, uint32_t* dst_end) {
    while(dst != dst_end) {
            *dst++ = 0;
    }
}

void init_copy_seg(uint32_t* src, uint32_t* dst, uint32_t* dst_end) {
    while(dst != dst_end) {
            *dst++ = *src++;
    }
}

void __l4_start() {
    /*DATA (ROM) -> DATA (RAM)*/
    init_copy_seg(&kernel_text_end, &kernel_data_start, &kernel_data_end);        

    /*Fill bss with zeroes*/
    init_zero_seg(&kernel_bss_start, &kernel_bss_end);
    init_zero_seg(&kernel_ahb_start, &kernel_ahb_end);

    SystemInit();

    main();
}



И наконец, маленькое замечание по поводу сегмента .data_AHB. В Cortex M3 есть специальный раздел RAM-памяти, к которой можно обращаться и как к обычной и побитово (т.н. Bit-bang region), и, разумеется, там удобно размещать разного рода битовые карты. Поэтому создаем отдельный сегмент, а в коде явно указываем секцию:

#define __BITMAP        __attribute__ ((section(".bitmap")))
    static __BITMAP char some_bitmap[16];



Ну вот и все, осталось скормить скрипт в свойства проекта и можно тестировать образ.

К списку статей

 

Интересуюсь по большей части системным анализом программного обеспечения: поиском багов и анализом неисправностей, а также системным программированием (и не оставляю надежд запилить свою операционку, хотя нехватка времени сказывается :) ). Программированием увлекаюсь с 12 лет, но так уж получилось, что стал я инженером.

Основная сфера моей деятельности связана с поддержкой Solaris и оборудования Sun/Oracle, хотя в последнее время к ним прибавились технологии виртуализации (линейка Citrix Xen) и всякое разное от IBM - от xSeries до Power. Учусь на кафедре Вычислительной Техники НИУ ИТМО.

See you...out there!

http://www.facebook.com/profile.php?id=100001947776045
https://twitter.com/AnnoyingBugs