Понимание счетчика местоположения сценариев GNU Linker

Я работаю над университетским проектом, где я пишу программное обеспечение для микроконтроллера Atmel SAM7S256 с нуля. Это более подробно, чем другие MCU, с которыми я работал раньше, поскольку на этот раз необходимо знание сценариев компоновщика и языка ассемблера.

Я действительно тщательно изучал примеры проектов для чипов SAM7S, чтобы полностью понять, как начать проект SAM7/ARM с нуля. Примечательным примером является Miro Samek "Построение Bare-Metal ARM Systems with GNU", который нашел здесь (где код в этом вопросе из). Я также потратил много времени на чтение документации компоновщика и ассемблера с сайта sourceware.org.

Я очень доволен, что по большей части я понимаю следующий компоновщик script. Есть только одна вещь, включающая счетчик местоположения, который не имеет для меня смысла. Ниже представлен компоновщик script с приведенным выше руководством:

OUTPUT_FORMAT("elf32-littlearm", "elf32-bigarm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_vectors)

MEMORY {                                       /* memory map of AT91SAM7S64 */
    ROM (rx)  : ORIGIN = 0x00100000, LENGTH = 64k
    RAM (rwx) : ORIGIN = 0x00200000, LENGTH = 16k
}

/* The sizes of the stacks used by the application. NOTE: you need to adjust */
C_STACK_SIZE   = 512;
IRQ_STACK_SIZE = 0;
FIQ_STACK_SIZE = 0;
SVC_STACK_SIZE = 0;
ABT_STACK_SIZE = 0;
UND_STACK_SIZE = 0;

/* The size of the heap used by the application. NOTE: you need to adjust   */
HEAP_SIZE = 0;

SECTIONS {

    .reset : {
        *startup.o (.text)  /* startup code (ARM vectors and reset handler) */
        . = ALIGN(0x4);
     } >ROM

    .ramvect : {                        /* used for vectors remapped to RAM */
        __ram_start = .;
        . = 0x40;
    } >RAM

    .fastcode : {
        __fastcode_load = LOADADDR (.fastcode);
        __fastcode_start = .;

        *(.glue_7t) *(.glue_7)
        *isr.o (.text.*)
        *(.text.fastcode)
        *(.text.Blinky_dispatch)
        /* add other modules here ... */

        . = ALIGN (4);
        __fastcode_end = .;
    } >RAM AT>ROM

    .text : {
        . = ALIGN(4);
        *(.text)                                   /* .text sections (code) */
        *(.text*)                                 /* .text* sections (code) */
        *(.rodata)           /* .rodata sections (constants, strings, etc.) */
        *(.rodata*)         /* .rodata* sections (constants, strings, etc.) */
        *(.glue_7) /* glue arm to thumb (NOTE: placed already in .fastcode) */
        *(.glue_7t)/* glue thumb to arm (NOTE: placed already in .fastcode) */

        KEEP (*(.init))
        KEEP (*(.fini))

        . = ALIGN(4);
        _etext = .;                         /* global symbol at end of code */
    } >ROM

    .preinit_array : {
        PROVIDE_HIDDEN (__preinit_array_start = .);
        KEEP (*(SORT(.preinit_array.*)))
        KEEP (*(.preinit_array*))
        PROVIDE_HIDDEN (__preinit_array_end = .);
    } >ROM

    .init_array : {
        PROVIDE_HIDDEN (__init_array_start = .);
        KEEP (*(SORT(.init_array.*)))
        KEEP (*(.init_array*))
        PROVIDE_HIDDEN (__init_array_end = .);
    } >ROM

    .fini_array : {
        PROVIDE_HIDDEN (__fini_array_start = .);
        KEEP (*(.fini_array*))
        KEEP (*(SORT(.fini_array.*)))
        PROVIDE_HIDDEN (__fini_array_end = .);
    } >ROM

    .data : {
        __data_load = LOADADDR (.data);
        __data_start = .;
        *(.data)                                          /* .data sections */
        *(.data*)                                        /* .data* sections */
        . = ALIGN(4);
        _edata = .;
    } >RAM AT>ROM

    .bss : {
        __bss_start__ = . ;
        *(.bss)
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = .;                     /* define a global symbol at bss end */
        __bss_end__ = .;
    } >RAM

    PROVIDE ( end = _ebss );
    PROVIDE ( _end = _ebss );
    PROVIDE ( __end__ = _ebss );

    .heap : {
        __heap_start__ = . ;
        . = . + HEAP_SIZE;
        . = ALIGN(4);
        __heap_end__ = . ;
    } >RAM

    .stack : {
        __stack_start__ = . ;

        . += IRQ_STACK_SIZE;
        . = ALIGN (4);
        __irq_stack_top__ = . ;

        . += FIQ_STACK_SIZE;
        . = ALIGN (4);
        __fiq_stack_top__ = . ;

        . += SVC_STACK_SIZE;
        . = ALIGN (4);
        __svc_stack_top__ = . ;

        . += ABT_STACK_SIZE;
        . = ALIGN (4);
        __abt_stack_top__ = . ;

        . += UND_STACK_SIZE;
        . = ALIGN (4);
        __und_stack_top__ = . ;

        . += C_STACK_SIZE;
        . = ALIGN (4);
        __c_stack_top__ = . ;

        __stack_end__ = .;
    } >RAM

    /* Remove information from the standard libraries */
    /DISCARD/ : {
        libc.a ( * )
        libm.a ( * )
        libgcc.a ( * )
    }
}

В этом примере (например, в разделах .ramvect,.fastcode и .stack) существуют обозначения символов, такие как __ram_start = .;. Эти адреса используются кодом начальной сборки и кодом инициализации C, чтобы инициализировать правильные местоположения в ОЗУ MCU.

У меня проблема с пониманием, так это то, как эти определения символов приводят к назначению правильных значений. Это действительно так, script правильный, я просто не понимаю, как это сделать.

Как я понимаю, когда вы используете счетчик местоположений в разделе, он содержит только относительное смещение от адреса виртуальной памяти (VMA) самого раздела.

Так, например, в строке __ram_start = .; я ожидал, что __ram_start будет присвоено значение 0x0 - поскольку ему присваивается значение счетчика местоположения в самом начале раздела .ramvect. Однако, чтобы код инициализации работал правильно (что он делает), __ram_start должен получать назначение как 0x00200000 (адрес для начала ОЗУ).

Я бы подумал, что это будет работать только по назначению, если строка была вместо __ram_start = ABSOLUTE(.); или __ram_start = ADDR(.ramvect);.

То же самое относится к __fastcode_start и __stack_start__. Они не могут быть определены как адрес 0x0, иначе программа не будет работать. Но документация связанная здесь, кажется, предполагает, что это должно произойти. Вот цитата из документации:

Примечание:. фактически относится к смещению байта от начала текущего содержащего объект. Обычно это оператор SECTIONS, чей начальный адрес равен 0. может использоваться как абсолютный адрес. Если. однако используется в описании раздела, но это относится к смещению байта от начала этого раздела, а не к абсолютному адресу.

Таким образом, значения счетчика местоположения во время этих присвоений символов должны быть смещениями из соответствующих разделов VMA. Значит, эти символы "_start" должны быть установлены в 0x0. Что нарушит программу.

Так что, очевидно, я что-то упускаю. Полагаю, это может быть просто то, что присвоение значению счетчика местоположения символу (внутри раздела) приводит к использованию ABSOLUTE() по умолчанию. Но я не смог найти четкое объяснение в любом месте, которое подтверждает это.

Заранее благодарим, если кто-нибудь может это очистить.

Ответы

Ответ 1

Думаю, я, возможно, понял ответ на свой вопрос. Я не уверен, что я прав, но первое объяснение, о котором я мог думать, на самом деле имеет смысл. То, что заставило меня переосмыслить, было на этой странице документации. В частности, эта цитата:

Адреса и символы могут относиться к разделу относительные или абсолютные. Секция относительный символ перемещается. Если вы запрашиваете перемещаемый вывод используя опцию "-r", дальнейшая операция ссылки может изменить значение относительного символа сечения. С другой стороны, абсолютный символ будет сохранять одно и то же значение во всех последующих операциях связи.

и эта цитата:

Вы можете использовать встроенную функцию ABSOLUTE, чтобы заставить выражение абсолютное, если в противном случае оно было бы относительным. Например, чтобы создать абсолютный символ, установленный на адрес конца секции вывода .data:

 SECTIONS
   {
     .data : { *(.data) _edata = ABSOLUTE(.); }
   }

Если ABSOLUTE не были использованы, _edata был бы относительно .dataраздел.

Я читал их раньше, но на этот раз я увидел их с новой точки зрения.

Итак, я думаю, что моя неправильная интерпретация думала, что символ, назначенный относительным адресом смещения байта, просто устанавливается на значение этого смещения, когда информация о базовом адресе теряется.

Это было основано на этой цитате из моего первоначального вопроса:

Примечание:. на самом деле относится к смещению байта с начала текущий объект. Обычно это инструкция SECTIONS, чей начальный адрес равен 0, следовательно. может использоваться как абсолютный адрес. Если. используется внутри описания раздела, однако оно относится к смещение байта от начала этого раздела, а не абсолютный адрес.

Вместо того, что я сейчас понимаю, это то, что информация о базовом адресе не теряется. Символ не просто получает значение смещения от базового адреса. Символ все равно будет окончательно разрешен к абсолютному адресу, но только тогда, когда нет возможности изменить его базовый адрес.

Итак, когда я думал, что что-то вроде __stack_start__ = . ; должно быть изменено на __stack_start__ = ABSOLUTE(.) ;, что действительно работает, теперь я считаю, что это не нужно. Что еще, я понимаю из первой цитаты в этом ответе, что вы можете повторно связать файл ELF?

Итак, если я использовал __stack_start__ = ABSOLUTE(.) ;, запустил компоновщик script, чтобы создать исполняемый файл ELF, затем попытался связать его и переместить раздел .stack где-то в другом месте, символ __stack_start__ все равно будет указывать на тот же абсолютный адрес из первой ссылки и, следовательно, быть неверным.

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

Ответ 2

Размещение раздела определяется областью памяти после замыкающей скобки (>RAM AT>ROM). Таким образом, адрес выполнения находится в ОЗУ на 0x00200000 и ниже, но адрес загрузки находится в ПЗУ (flash) на 0x00100000. Код запуска должен скопировать раздел вывода .fastcode с его загрузки на его адрес выполнения, для чего предназначены символы.

Обратите внимание, что это не обязательно должно быть на адресе 0, потому что AT91SAM7S перенаправляет RAM или ROM на адрес 0. Обычно он запускается с отображением ROM, а код запуска переключается в ОЗУ.