Hi, I'd like to propose a new section flag, SHF_GNU_ABIND. The flag allows the virtual memory address of a section to be specified within relocatable files, in advance of the final link. When linking executable files, the linker will try to place sections marked with SHF_GNU_ABIND at the address specified by their sh_addr field. "ABIND" is short for "address bind", used to indicate that the section is "bound" to a specific address at run time. I've attached a Binutils patch that implements SHF_GNU_ABIND. I look forward to hearing your thoughts, Jozef =========================================================== ----------------------- Section Attribute Flags ----------------------- +-------------------------------------+ | Name | Value | +-------------------------------------+ | SHF_GNU_ABIND | 0x400000 (1 << 22) | +-------------------------------------+ SHF_GNU_ABIND The sh_addr field describes the virtual address at which the first byte of the section should reside at in memory. =========================================================== Outline: - Motivation - Placement using linker script - ABI details for SHF_GNU_ABIND runtime initialization - Implementation - Alternative Method to Implement the "location" attribute - Conclusion - Appendix - Examples ---------- Motivation ---------- Placement of function or variable declarations at specific addresses can be useful in a variety of situations. Many processors have areas of the memory map with special properties. The data stored in these areas may be treated by the hardware in some unique way. For example: - Slots in an interrupt vector table When an interrupt triggers, the processor will read the address of the associated interrupt service routine from a particular address within the vector table. - Memory-mapped peripheral registers To check the status, or modify the behavior, of peripherals, control registers are defined at specific addresses and can be read from, or written to. Programmers may also find it useful to store information at a memorable address. Perhaps some data is shared between a bootloader and the application itself, stored in a common area of memory. The requirement for SHF_GNU_ABIND is motivated by a broader proposal for a new attribute, "location", which can be set on function and variable declarations in the source code. This attribute will enable a declaration to be placed at a specific virtual address, and SHF_GNU_ABIND is used to convey this requirement in relocatable files. Similar attributes are implemented in other toolchains, such as ARM Keil's "at" attribute, and the "location" attribute implemented in TI toolchains across a variety of their processors. ----------------------------- Placement using linker script ----------------------------- To place a declaration of a function or variable at a specific address, the programmer must currently make modifications to the linker script. Linker script modifications for this are cumbersome, and must be coupled with modifications to the source code that place the declaration in a specific section. The programmer has to: - Apply the "section" attribute to the declaration they want to place at a specific address, so it gets placed in a uniquely named section. - Modify the linker script, creating a new output section with the desired VMA for the declaration, containing a rule to match the section they created for the declaration in the source code. However, there are some potential pitfalls the programmer can fall into when making linker script modifications to place their section at a specific address: - The programmer may not consider the best position to put their new output section within the list of output sections. In a linker script with multiple output sections, a sub-optimal decision about which sections they will place their new section between could result in large, empty gaps in their executable file. This limits the space available for their application, possibly resulting in the program failing to link, or heap/stack availability being low. * With SHF_GNU_ABIND, the optimal position of the new output section within the statement list can be automatically calculated. - The programmer may not consider whether an LMA region needs to be set for their new output section, if their desired address is within a non-loadable memory region. * With SHF_GNU_ABIND, the linker automatically decides if a separate LMA region is required and sets the load address accordingly. There are additional generic benefits to avoiding linker script modifications: - The requirement for placement of a declaration at a specific address may be application-specific, so consolidating this requirement to be contained entirely within the source code improves portability. - Generally avoiding linker script modifications can improve the user experience, when the programmer is inexperienced with the linker script format. The boilerplate linker script code required for standard application operation can make modifications error-prone and have unintended side-effects. Furthermore, with SHF_GNU_ABIND, the number of individual changes required to achieve placement at a specific address is reduced. With linker script modifications: *.ld: | SECTIONS | { | ... other sections ... | my_decl 0x1000 : { *(.data.my_decl_at_0x1000) } | ... other sections ... | } *.c: | int __attribute__((section(".data.my_decl_at_0x1000"))) = 0xABCD; With SHF_GNU_ABIND and the "location" attribute: *.c: | int __attribute__((location(0x1000))) = 0xABCD; Following are some further ABI details, describing the implementation required to support placement of sections with LMA not equal to VMA, and sections consisting of zero-initialized data. ================================================================ ---------------- Special Sections ---------------- .gnu.abind_init_array This section holds an array of ElfXX_AbindInitArray_Entry entries, which describe how to initialize the contents of SHF_GNU_ABIND sections at runtime, for sections that require runtime initialization. See "SHF_GNU_ABIND Section Initialization" for more details. ------------------------------------ SHF_GNU_ABIND Section Initialization ------------------------------------ Some SHF_GNU_ABIND sections will require initialization at runtime. The conditions for the initialization requirement are: - The program will run on a ROM based system, so SHF_GNU_ABIND sections with their virtual memory address (VMA) specified in a non-loadable memory region require their contents to be copied from their load memory address (LMA) to their VMA during program initialization. - The SHF_GNU_ABIND section has type SHT_NOBITS and would have been placed in a .bss output section, and its contents set to zero during program initialization. The specified VMA for the SHF_GNU_ABIND section may not be within the bounds of the zero-initialized block of other .bss sections, so the section will require it's contents to be explicitly set to zero at runtime. For targets that do not zero .bss, this is not required. For devices without non-loadable memory regions, and so will not have any sections with LMA not equal to VMA, it is possible to avoid the need for .gnu.abind_init_array. Defining any SHF_GNU_ABIND sections containing zero-initialized data explicitly as an SHT_PROGBITS ".data" section and initializing its contents to 0 will ensure the program loader sets its contents to 0, rather than relying on the application to zero it. .gnu.abind_init_array will therefore not be required in this case. If the compiler handles this automatically, .gnu.abind_init_array support could be entirely omitted for the target. The "__run_gnu_abind_init_array" function performs runtime initialization of SHF_GNU_ABIND sections that require it, as described by .gnu.abind_init_array entries. If .gnu.abind_init_array will be present in the executable file and has non-zero size, an entry for __run_gnu_abind_init_array must be placed in the ".init_array" section. ----------------------------- .gnu.abind_init_array Entries ----------------------------- typedef struct { Elf32_Addr vma; Elf32_Addr lma; Elf32_Word size; } Elf32_AbindInitArray_Entry; typedef struct { Elf64_Addr vma; Elf64_Addr lma; Elf64_Xword size; } Elf64_AbindInitArray_Entry; The bounds of .gnu.abind_init_array are described by the following symbols: __gnu_abind_init_array_start __gnu_abind_init_array_end To initialize the sections, __run_gnu_abind_init_array has a choice for each entry: - When lma != vma, copy "size" bytes from address "lma" to address "vma". - When lma == vma, this is an SHT_NOBITS section requiring zero-initialization. Set "size" bytes starting from address "vma" to 0. A sample implementation of __run_gnu_abind_init_array for a 32-bit target is shown below: void __run_gnu_abind_init_array (void) { void *ptr; for (ptr = &__gnu_abind_init_array_start; ptr != &__gnu_abind_init_array_end; ptr += sizeof (Elf32_AbindInitArray_Entry)) { Elf32_AbindInitArray_Entry *ent = ptr; if (ent->lma != ent->vma) __builtin_memcpy (ent->vma, ent->lma, ent->size); else __builtin_memset (ent->vma, 0, ent->size); } } ================================================================ -------------- Implementation -------------- GCC supports a new "location" attribute, which can be applied to function and variable declarations. The argument to the "location" attribute indicates the virtual memory address that the declaration should be placed at. int __attribute__((location(0x5000))) checksum = 0xABCD; GCC will place the declaration that the "location" attribute is applied to in its own, uniquely named section. GCC will then describe the requirement for placement at a specific address to the assembler. For systems which do not have any non-loadable memory regions, GCC can modify the type and name created for zero-initialized data. Placing this data in an SHT_PROGBITS section with the ".data" prefix, instead of an SHT_NOBITS sections with the ".bss" prefix, and explicitly initializing its contents to 0 will avoid the need for any runtime support for SHF_GNU_ABIND via .gnu.abind_init_array. GAS supports a new flag value "A" to the "flags" argument of the .section directive. The specified address is read from the "flag_specific_arguments" argument to .section. .section .data.checksum,"awA",0x5000 GAS will set the SHF_GNU_ABIND flag on the section, and the sh_addr field to the specified address. When loading object files to link an executable file, LD will "orphan" SHF_GNU_ABIND sections by renaming them, giving them the ".gnu.abind" prefix. This means they won't match any input section rules from the linker script. The "orphaned" SHF_GNU_ABIND input sections will now have their own output sections, which can be placed freely at the address specified by the sh_addr field of the corresponding input section. -------------------------------------------------------- Alternative Method to Implement the "location" attribute -------------------------------------------------------- An alternative method of conveying the requirement for placement of a section at a specific address, from the compiler to the linker, would be for the compiler to set a specific section name which captures both the status of the section as one requiring placement, and the address it should be placed at. For example: int __attribute__((location(0x5000))) checksum = 0xABCD; becomes .section .gnu_location_0x5000.data.checksum,"aw" In theory, the assembler and linker implementation does not need to be very different from what is required for SHF_GNU_ABIND, as these programs can set an internal flag on sections which have the .gnu_location prefix to treat them in a special way. However, the additional prefix for the section on entry to the assembler and linker might interfere with some common behavior. It is also not standard to convey information about a section in the name. This method should only be used if the proposed ABI modifications cannot be made for some reason. Additionally, it would result in the loss of opportunity for standardization of .gnu.abind_init_array and associated runtime functionality. ---------- Conclusion ---------- The "location" attribute, making use of SHF_GNU_ABIND, will improve the user experience and simplify development for programmers wishing to make use of the functionality to place function and variable declarations at specific addresses. The behavior can now be implemented in fewer steps, and without leaving the source code. Cumbersome and error-prone linker script modifications are avoided. For embedded devices, maintenance of board support packages is eased, as many hardware features that had to be described in both header file and linker scripts, with the corresponding definitions requiring precise alignment between the files, now require specification only in header files. Applications are more portable, as those which require declarations to be placed at a specific address can now be entirely described from the source code, without requiring an accompanying linker script. ------------------- Appendix - Examples ------------------- Below are some examples for the "location" attribute in real use cases. Mapping a Structure over Memory-mapped Peripheral Registers --------------------------------------------------------- Current methods for accessing memory-mapped registers are: - Set the address of a structure at runtime, in application code. * Requires additional code to setup the address of the structure before registers can be accessed. - Use a symbolic reference to the registers, defined in header files but resolved at link time. * Requires implementation in both header file and linker scripts. The "location" attribute allows consolidation of this information entirely within the source code, and without any runtime initialization required. typedef struct { char regA; char regB; char ctrl; } peripheral_reg; peripheral_reg __attribute__((location(0x65000))) pregs1; /* Above code can be hidden in a header file from a board support package for a particular device. */ void manip_regs (void) { pregs1.ctrl = 1; /* MOV #1, &0x65002 */ pregs1.regA = 10; /* MOV #10, &0x65000 */ pregs1.regB = 20; /* MOV #20, &0x65001 */ pregs1.ctrl = 0; /* MOV #0, &0x65002 */ } Placement in the Interrupt Vector Table --------------------------------------- Implementation of interrupt vectors currently requires explicit support between the source code and linker script: SECTIONS { __ivec_rtc 0xFF00 : { KEEP (*(__ivec_rtc)) } } /* "interrupt" attribute is required to create the "__ivec_rtc" section and place the address of "isr_rtc" within it. */ void __attribute__((interrupt("rtc")) isr_rtc (void) { clock_var++; } The name of the interrupt service routine must be aligned between the source code, compiler (a hard-coded prefix must be added) and the linker script. The "location" attribute, used in conjunction with "retain" greatly simplifies the procedure, and does not require any linker script modifications. /* If a board support package is available, refer to the address symbolically. */ #ifdef __BSP__ static void * __attribute__((retain,location(VEC_RTC))) #else static void * __attribute__((retain,location(0xFF00))) #endif __ivec_rtc = isr_rtc; void isr_rtc (void) { clock_var++; } As an extension to the proposal, a new "interrupt_loc" attribute could emit section declarations for vector table slots, setting SHF_GNU_RETAIN and SHF_GNU_ABIND on the section. This would allow an ISR to be completely specified with only a function declaration and possibly an associated definition from a header file. #ifdef __BSP__ void __attribute__((interrupt_loc(VEC_RTC))) #else void __attribute__((interrupt_loc(0xFF00))) #endif isr_rtc (void) { clock_var++; }