mem_sections(3avr)
πΈ NAME πΈ
mem_sections β Memory Sections
okay bestie, so sections are literally how we organize code and data at the binary level. periodt. π
The compiler-generated assembly code assigns code, data and other stuff like debug info to so-called input sections. Those sections are the input to the linker, which bundles similar sections together into output sections like .text and .data, following the rules in the linker description file. β¨
The final ELF binary is then used by programming tools like avrdude, simulators, debuggers, and other programs β for example the GNU Binutils family icons avr-size, avr-objdump and avr-readelf. π
Sections can have extra properties like section alignment, section flags, section type, and rules to locate them or assign them to memory regions. π
Here's the map of what's inside:
- Concepts
- Named Sections
- Section Flags
- Section Type
- Section Alignment
- Subsections
- Orphan Sections
- LMA: Load Memory Address
- VMA: Virtual Memory Address
- Named Sections
- The Linker Script: Building Blocks
- Input Sections and Output Sections
- Memory Regions
- Output Sections of the Default Linker Script
.text.data.bss.noinit.rodata.eeprom.fuse,.lockand.signature.note.gnu.avr.deviceinfo
- Symbols in the Default Linker Script
- Output Sections and Code Size
- Using Sections
- In C/C++ Code
- In Assembly Code
π Concepts π
π· Named Sections π·
Named sections are exactly what they sound like: sections you can refer to by name. You give them a name and other properties with the .section directive, like this:
.section name, "flags", @type
Or you can use the .pushsection directive, which tells the assembler to assemble the following code into the named section. πͺ
A section that is not referred to by name is the COMMON section. To put an object there, you use special directives like .comm name,size or .lcomm name,size. π«Ά
Directives like .text are basically the same as .section .text, where the assembler assumes the right section flags and type. Same energy for .data and .bss. π«
π Section Flags π
You can specify section flags with the .section and .pushsection directives β check out section type for an example. Section flags of output sections can be set in the linker description file, and the linker uses heuristics to figure out the flags of output sections from the various input sections that go into them. π
aβ The section will be allocated, i.e. it occupies space on the target hardware.wβ The section contains data that can be written at run-time. Sections that only contain read-only entities don't have thewflag set.xβ The section contains executable code, though the section may also contain non-executable objects.Mβ A mergeable section.Sβ A string section.Gβ A section group, like used with comdat objects.
The last three flags are listed for completeness. They are used by the compiler, for example for header-only C++ modules and to ensure that multiple instantiations of the same template in different compilation units occur at most once in the executable file. π
πΈ Section Type πΈ
You can specify the section type with the .section and .pushsection directives, like in:
.section .text.myfunc,"ax",@progbits
.pushsection ".data.myvar", "a", "progbits"
On ELF level, the section type is stored in the section header like Elf32_Shdr.sh_type = SHT_PROGBITS. π§¬
@progbitsβ The section contains data that will be loaded to the target, like objects in the.textand.datasections.@nobitsβ The section does not contain data that needs to be transferred to the target device, like data in the.bssand.noinitsections. The section still occupies space on the target.@noteβ The section is a note, like for example the.note.gnu.avr.deviceinfosection.
π¦ Section Alignment π¦
The alignment of a section is the maximum over the alignments of the objects in the section. simple as that. β¨
π Subsections π
Subsections are compartments of named sections and are introduced with the .subsection directive. Subsections are located in order of increasing index in their input section. The default subsection after switching to a new section is subsection 0. π§
Note β A common misconception is that a section like.text.module.funcwere a subsection of.text.module. This is not the case. These two sections are independent, and there is no subset relation. The sections may have different flags and type, and they may be assigned to different output sections. π
π Orphan Sections π
Orphan sections are sections that are not mentioned in the linker description file. When an input section is orphan, the GNU linker implicitly generates an output section of the same name. The linker uses various heuristics to determine section flags, section type and location of orphaned sections. One use of orphan sections is to locate code to a fixed address. π
Like for any other output section, you can specify the start address by linking with:
-Wl,--section-start,secname=address
π LMA: Load Memory Address π
The LMA of an object is the address where a loader like avrdude puts the object when the binary is being uploaded to the target device. π¦
π VMA: Virtual Memory Address π
The VMA is the address of an object as used by the running program. πββοΈ
VMA and LMA may be different: Suppose a small ATmega8 program with executable code that extends from byte address 0x0 to 0x20f, and one variable my_var in static storage. The default linker script puts the content of the .data output section after the .text output section and into the text segment. The startup code then copies my_data from its LMA location beginning at 0x210 to its VMA location beginning at 0x800060, because C/C++ requires that all data in static storage must have been initialized when main is entered. π
The internal SRAM of ATmega8 starts at RAM address 0x60, which is offset by 0x800000 in order to linearize the address space (VMA 0x60 is a flash address). The AVR program only ever uses the lower 16 bits of VMAs in static storage so that the offset of 0x800000 is masked out. But code like "LDI r24,hh8(my_data)" actually sets R24 to 0x80 and reveals that my_data is an object located in RAM. π₯
π οΈ The Linker Script: Building Blocks π οΈ
The linker description file is the central hub to channel functions and static storage objects of a program to the various memory spaces and address ranges of a device. π§
π¦ Input Sections and Output Sections π¦
Input sections are sections that are inputs to the linker. Functions and static variables, but also additional notes and debug information, are assigned to different input sections by means of assembler directives like .section or .text. The linker takes all these sections and assigns them to output sections as specified in the linker script. π―
Output sections are defined in the linker description file. Contrary to the unlimited number of input sections a program can come up with, there is only a handful of output sections like .text and .data, that roughly correspond to the memory spaces of the target device. β¨
One step in the final link is to locate the sections β that is, the linker/locator determines at which memory location to put the output sections, and how to arrange the many input sections within their assigned output section. Locating means that the linker assigns Load Memory Addresses β addresses as used by a loader like avrdude β and Virtual Memory Addresses, which are the addresses as used by the running program. π
While it is possible to directly assign LMAs and VMAs to output sections in the linker script, the default linker scripts provided by Binutils assign memory regions (aka. memory segments) to the output sections. This has some advantages like a linker script that is easier to maintain. An output section can be assigned to more than one memory region. For example, non-zero data in static storage (.data) goes to:
- the data region (VMA), because such variables occupy RAM which has to be allocated
- the text region (LMA), because the initializers for such data have to be kept in some non-volatile memory (program ROM), so that the startup code can initialize that data so that the variables have their expected initial values when
main()is entered.
The SECTIONS{} portion of a linker script models the input and output section, and it assigns the output section to the memory regions defined in the MEMORY{} part. π
πΈ Memory Regions πΈ
The memory regions defined in the default linker script model and correspond to the different kinds of memories of a device.
The table has columns: Region, Virtual AddressΒΉ, Flags, Purpose.
textβ Virtual Address:0(see note 2); Flags:rx; Purpose: Executable code, vector table, data inPROGMEM,__flashand__memx, startup code, linker stubs, initializers for.datadataβ Virtual Address:0x800000(see note 2); Flags:rw; Purpose: Data in static storagerodata(see note 3) β Virtual Address:0xa00000(see note 2); Flags:r; Purpose: Read-only data in static storageeepromβ Virtual Address:0x810000; Flags:rw; Purpose: EEPROM datafuseβ Virtual Address:0x820000; Flags:rw; Purpose: Fuse byteslockβ Virtual Address:0x830000; Flags:rw; Purpose: Lock bytessignatureβ Virtual Address:0x840000; Flags:rw; Purpose: Device signatureuser_signaturesβ Virtual Address:0x850000; Flags:rw; Purpose: User signature
Notes
- The VMAs for regions other than
textare offset in order to linearize the non-linear memory address space of the AVR Harvard architecture. The target code only ever uses the lower 16 bits of the VMA to access objects in non-textregions. - The addresses for regions
text,dataandrodataare actually defined as symbols like__TEXT_REGION_ORIGIN__, so that they can be adjusted by means of, say-Wl,--defsym,__DATA_REGION_ORIGIN__=0x800060. Same applies for the lengths of all the regions, which is__NAME_REGION_LENGTH__for regionname. - The
rodataregion is only present in theavrxmega2_flmapandavrxmega4_flmapemulations, which is the case for Binutils sincev2.42for theAVR64andAVR128devices without-mrodata-in-ram.
π Output Sections of the Default Linker Script π
This section describes the various output sections defined in the default linker description files.
The table has columns: Output, Purpose, Memory Region, Section LMA, VMA.
.textβ Purpose:Executable code, data in progmem; Memory Region:text; Section LMA:text.dataβ Purpose:Non-zero data in static storage; Memory Region:text; Section LMA:data.bssβ Purpose:Zero data in static storage; Memory Region:---; Section LMA:data.noinitβ Purpose:Non-initialized data in static storage; Memory Region:---; Section LMA:data.rodataΒΉ β Purpose:Read-only data in static storage; Memory Region:text; Section LMA:LMA + offsetΒ³.rodataΒ² β Purpose:Read-only data in static storage; Memory Region:0x8000 * __flmapβ΄; Section LMA:rodata.eepromβ Purpose:Data in EEPROM; Memory Region:Noteβ΅; Section LMA:eeprom.fuseβ Purpose:Fuse bytes; Memory Region:fuse.lockβ Purpose:Lock bytes; Memory Region:lock.signatureβ Purpose:Signature bytes; Memory Region:signature; Section LMA:User signature bytes; VMA:user_signatures
Notes
- On
avrxmega3andavrtinydevices. - On AVR64 and AVR128 devices without
-mrodata-in-ram. - With an offset
__RODATA_PM_OFFSET__of0x4000or0x8000depending on the device. - The value of the symbol
__flmapdefaults to the last 32 KiB block of program memory β see the GCC v14 release notes, bestie, that's where the lore lives. π - The LMA actually equals the VMA, but is unused. The flash loader like
avrdudejust knows where to put the data. it's giving built different π
The .text Output Section π
Okay so the .text output section is holding the actual machine instructions that make up your program β the real main-character code β plus extra stuff like jump tables and lookup tables that live in program memory thanks to the PROGMEM attribute. β¨
Here's the guest list of input sections that feed into .text. Input sections that aren't used by the tools get left off. And a * wildcard means any sequence of characters, including an empty one, as long as it's valid in a section name. slay, flexible.
.vectorsβ this section holds the interrupt vector table, which is basically a bunch of jumps to weakly defined labels: to__initfor the first entry at index 0, and to__vector_Nfor the entry at indexN - 1. The default value for__vector_Nis__bad_interrupt, which jumps to the weakly defined__vector_default, which jumps to__vectors, which is the very start of the.vectorssection. it's giving relay race πββοΈ
Implementing an interrupt service routine (ISR) is done with the help of the ISR macro in C/C++ code. iykyk.
.progmem.data.progmem.data.*.progmem.gcc.*β this one is for read-only data declared with thePROGMEMattribute, and for data in address-space__flash. π
Heads up bestie: the compiler assumes the.progmemsections are located in the lower 64 KiB of program memory. When it doesn't fit in that lower 64 KiB block, the program reads straight-up garbage β unlesspgm_read_*_faris used. In that case, though, code can be located in the.progmemxsection, which does not have to live in the lower program memory. no cap. π
.trampolinesβ linker stubs for indirect jumps and calls on devices with more than 128 KiB of program memory. This section must sit in the same 128 KiB block as the interrupt vector table. For some background on linker stubs, go read the GCC documentation onEIND. π§.text.text.*β executable code. This is where like, almost all of the executable code of your application is going to go. main character energy fr fr π.ctors.dtorsβ tables with addresses of static constructors and destructors, like C++ static constructors and functions declared with theconstructorattribute. π
The .initN Sections π
These sections hold the startup code β everything from reset all the way up through the start of main(). iconic.
The .initN sections get executed in order from 0 to 9: the code from one init section falls through to the next higher init section. Which is exactly why code in these sections must be naked (more precisely, it must not contain return instructions), and why code in these sections must never be called explicitly. periodt. π«Ά
When several modules put code in the same init section, the order of execution is not specified. vibes are chaotic, that's just how it is. π
Section Β· Performs Β· Hosted By Β· Symbol
.init0β Performs: weakly defines the__initlabel, which is the jump target of the first vector in the interrupt vector table. When you define the__init()function yourself, it gets jumped to instead. Hosted By:AVR-LibCΒ².init1β Unused.- **
.init2β .init2β the multitasker: clears__zero_reg__, initializes the stack pointer to the value of the weak symbol__stack(whose default value isRAMEND, as defined inavr/io.h), initializesEINDtohh8(pm(__vectors))on devices that have it, and initializesRAMPX,RAMPY,RAMPZandRAMPDon devices that have all of them. Hosted by AVR-LibC..init3β initializes theNVMCTRLB.FLMAPbit-field on devices that have it, except when-mrodata-in-ramis specified. Hosted by AVR-LibC. Symbol:__do_flmap_init..init4β initializes data in static storage: initializes.dataand clears.bss. Hosted by libgcc. Symbols:__do_copy_dataand__do_clear_bss..init5β Unused. Hosted byβ..init6β runs static C++ constructors and functions defined with__attribute__((constructor)). Hosted by libgcc. Symbol:__do_global_ctors..init7β Unused. Hosted byβ..init8β Unused. Hosted byβ..init9β callsmainand then jumps toexit. Hosted by AVR-LibC.
Notes π
- Code in the
.init3,.init4and.init6sections is optional β it will only be present when there is something to do. This will be tracked by the compiler (or has to be tracked by the assembly programmer), which pulls in the code from the respective library by means of the mentioned symbols, e.g. by linking with-Wl,-u,__do_flmap_initor by means of
.global __do_copy_data
Conversely, when the respective code is not desired for some reason, the symbol can be satisfied by defining it with, say, -Wl,--defsym,__do_copy_data=0 so that the code is not pulled in any more.
- The code is provided by
gcrt1.Sβ bestie, that's thecrt1/gcrt1.Sfile over in avr-libc's repo on GitHub (avrdudes/avr-libc).
π« The .finiN Sections π«
Shutdown code. These sections hold the exit code that runs after returning from main() or after a call to exit(). The .finiN sections are executed in descending order from 9 to 0, in a fallthrough manner. πͺ
.fini9β Defines_exitand weakly defines theexitlabel. Hosted by libgcc..fini8β Runs functions registered withatexit(). Hosted by AVR-LibC..fini7β Unused. Hosted byβ..fini6β Runs static C++ destructors and functions defined with__attribute__((destructor)). Hosted by libgcc. Symbol:__do_global_dtors..fini5...1β Unused. Hosted byβ..fini0β Globally disables interrupts and enters an infinite loop to label__stop_program. Hosted by libgcc.
It's lowkey unlikely that ordinary code uses the fini sections. When there are no static destructors and atexit() isn't used, then the respective code is not pulled in from the libraries, and the fini code just consumes four bytes: a CLI and a RJMP to itself. π Common use cases of fini code are running the GCC test suite (where it reduces fallout) and simulators, to determine the (un)orderly termination of a simulated program.
π°οΈ .progmemx.* π°οΈ
Read-only data in program memory, with no requirement that it must reside in the lower 64 KiB. The compiler uses this section for data in the named address-space __memx. Data can be accessed with pgm_read_*_far when it is not in a named address-space:
#include <avr/pgmspace.h>
const __memx int array1[] = { 1, 4, 9, 16, 25, 36 };
PROGMEM_FAR
const int array2[] = { 2, 3, 5, 7, 11, 13, 17 };
int add (uint8_t id1, uint8_t id2)
{
uint_farptr_t p_array2 = pgm_get_far_address (array2);
int val2 = pgm_read_int_far (p_array2 + sizeof(int) * id2);
return val2 + array1[id1];
}
π€Έ .jumptables* π€Έ
Used to place jump tables in some cases. That's literally it, she's simple like that. β¨
π The .data Output Section π
This section contains data in static storage which has an initializer that is not all zeroes. It includes the following input sections:
.data*β Read-write data..rodata*β Read-only data. These input sections are only included on devices that host read-only data in RAM.
It is possible to tell the linker the SRAM address of the beginning of the .data section. This is accomplished by linking with
avr-gcc ... -Tdata addr -Wl,--defsym,__DATA_REGION_START__=addr
Note that addr must be offset by adding 0x800000 to the real SRAM address, so that the linker knows that the address is in the SRAM memory segment. So if you want the .data section to start at 0x1100, pass 0x801100 as the address to the linker. π
Note: when using malloc() in the application (which could even happen inside library calls), additional adjustments are required.
π§Ί The .bss Output Section π§Ί
Data in static storage that will be zeroed by the startup code. That means data objects without an explicit initializer, and data objects with initializers that are all zeroes.
Input sections are .bss* and COMMON. Common symbols are defined with the directives .comm or .lcomm.
π The .noinit Output Section π
Data objects in static storage that should not be initialized by the startup code. Since the C/C++ standard requires that all data in static storage is initialized β which includes data without an explicit initializer, which gets initialized to all zeroes β such objects have to be put into section .noinit by hand:
__attribute__ ((section (".noinit")))
int foo;
The only input section in this output section is .noinit. Only data without an initializer can be put in this section, no cap. π«
π The .rodata Output Section π
This one contains read-only data in static storage, coming from .rodata* input sections. This output section is only present for devices where read-only data remains in program memory β which are the devices where (parts of) the program memory are visible in the RAM address space. Currently this is the case for the emulations avrtiny, avrxmega3, avrxmega2_flmap and avrxmega4_flmap. π
π The .eeprom Output Section π
This is where EEPROM variables are stored β for example variables declared with the EEMEM attribute. The only input section (pattern) is .eeprom*.
π The .fuse, .lock and .signature Output Sections π
These sections contain fuse bytes, lock bytes and device signature bytes, respectively. The respective input section patterns are .fuse*, .lock* and .signature*.
π¦ The .note.gnu.avr.deviceinfo Section π¦
Okay so this section is actually not mentioned in the default linker script β which means it is an orphan section, and hence the respective output section is implicit. The startup code from AVR-LibC puts device information in that section, to be picked up by simulators or tools like avr-size, avr-objdump, avr-readelf, etc. The section is contained in the ELF file but is not loaded onto the target.
The source of the device specific information is the device header file and compiler builtin macros. The layout conforms to the standard ELF note section layout and is laid out as follows:
#include <elf.h>
typedef struct
{
Elf32_Word n_namesz; /* AVR_NOTE_NAME_LEN */
Elf32_Word n_descsz; /* size of avr_desc */
Elf32_Word n_type; /* 1 - the only AVR note type */
} Elf32_Nhdr;
#define AVR_NOTE_NAME_LEN 4
struct note_gnu_avr_deviceinfo
{
Elf32_Nhdr nhdr;
char note_name[AVR_NOTE_NAME_LEN]; /* = "AVR\0" */
struct
{
Elf32_Word flash_start;
Elf32_Word flash_size;
Elf32_Word sram_start;
Elf32_Word sram_size;
Elf32_Word eeprom_start;
Elf32_Word eeprom_size;
Elf32_Word offset_table_size;
/* Offset table containing byte offsets into
string table that immediately follows it.
index 0: Device name byte offset */
Elf32_Off offset_table[1];
/* Standard ELF string table.
index 0 : NULL
index 1 : Device name
index 2 : NULL */
char strtab[2 + strlen(__AVR_DEVICE_NAME__)];
} avr_desc;
};
The contents of this section can be displayed with:
avr-objdump -P avr-deviceinfo <file>, which is supported since Binutils v2.43.avr-readelf -n <file>, which displays all notes.
β Symbols in the Default Linker Script β
Most of the symbols like main are defined in the code of the application, but some symbols are defined in the default linker script:
__<name>_REGION_ORIGIN__β describes the physical properties of memory region<name>, where<name>is one ofTEXTorDATA. The address is a VMA and offset as explained above. The linker script only supplies a default for the symbol values when they have not been defined by other means β like, for example, in the startup code or by--defsym. For example, to let the code start at address0x100, one can link with
avr-gcc ... -Ttext=0x100 -Wl,--defsym,__TEXT_REGION_ORIGIN__=0x100
__name_REGION_LENGTH__β okay so this one describes the physical properties of memory region name, and name can be any one of these:TEXT,DATA,EEPROM,LOCK,FUSE,SIGNATUREorUSER_SIGNATURE. β¨ A default only gets supplied when the symbol isn't already defined by other means. Most of these symbols are weakly defined in the startup code, bestie π«Ά__data_start,__data_endβ the start and the (one past the) end VMA address of the.datasection in RAM. πΎ__data_load_start,__data_load_endβ the start and the (one past the) end LMA address of the.datasection initializers, which live up in program memory. The startup code pairs these with the VMA addresses above to copy data initializers from program memory to RAM. shipping the vibes from flash to SRAM fr fr π¦__bss_start,__bss_endβ the start and (one past the) end VMA address of the.bsssection. The startup code clears this part of the RAM. π§Ή__rodata_start,__rodata_end,__rodata_load_start,__rodata_load_endβ the start and (one past the) end VMA resp. LMA address of the.rodataoutput section. π Heads up though: these symbols are only defined when.rodatais not output to thetextregion β which is the case for emulationsavrxmega2_flmapandavrxmega4_flmap.__heap_startβ one past the last object located in static storage. It immediately follows the.noinitsection (which immediately follows.bss, which immediately follows.dataβ it's a whole little conga line πͺ©).malloc()and friends use it. π
So if your code computes a checksum over all the relevant code and data in program memory, it has to consider:
- The range from the beginning of the
.textsection (address0x0in the default layout) up to__data_load_start. - For emulations that have the
rodatamemory region, the range from__rodata_load_startto__rodata_load_endhas also to be taken into account.
π Output Sections and Code Size π
Bestie. Listen. The avr-size program (part of Binutils) comes from a Unix background, and it doesn't account for the .data initialization space that gets added to the .text section. So to actually know how much flash the final program will consume, you need to add the values for both .text and .data (but not .bss), while the amount of pre-allocated SRAM is the sum of .data and .bss. That's just how it is, no cap. π
Memory usage and free memory can also be displayed with:
avr-objdump -P mem-usage code.elf
π Using Sections π
πΈ In C/C++ Code πΈ
This next example shows how to read and reset the MCUCR special function register on ATmega328. This SFR holds reset sources like "watchdog reset" or "external reset", and it should be read early β prior to the initialization of RAM and the execution of static constructors, which may take some time. Which means the code has to be placed prior to .init4 (the one that initializes static storage), but after .init2, which initializes __zero_reg__. π
And since the code runs prior to the initialization of static storage, the variable mcucr must be placed in section .noinit so that it won't be overridden by that part of the startup code:
#include <avr/io.h>
__attribute__((section(".noinit")))
uint8_t mcucr;
__attribute__((used, unused, naked, section(".init3")))
static void read_MCUCR (void)
{
mcucr = MCUCR;
MCUCR = 0;
}
- The
usedattribute tells the compiler that the function is used although it is never called. - The
unusedattribute tells the compiler that it is fine that the function is unused, and silences respective diagnostics about the seemingly unused functions. - The
nakedattribute is required because the code is located in an init section. The function must not have aRETstatement because the function is never called. π«
According to the GCC documentation, the only code supported in naked functions is inline assembly β but the code above is simple enough so that GCC can deal with it. π
π¦ In Assembly Code π¦
Example:
#include <avr/io.h>
.section .init3,"ax",@progbits
lds r0, MCUCR
.pushsection .noinit,"a",@nobits
mcucr:
.type mcucr, @object
.size mcucr, 1
.space 1
.popsection
; Proceed with .init3
sts mcucr, r0
sts MCUCR, __zero_reg__ ; Initialized in .init2
.text
.global main
.type main, @function
lds r24, mcucr
clr r25
rjmp putchar
.size main, .-main
- The
"ax"flags tell that the section is allocatable (it consumes space on the target hardware) and is executable. β‘ - The
@progbitstype tells that the section contains bits that have to be uploaded to the target hardware. π€
For more details, see the gas user manual on the .section directive: https://sourceware.org/binutils/docs/as/Section.html#ELF-Version
And that's the whole page, bestie!! πβ¨ This one shipped with AVR-LibC, Version 2.2.1, and you're looking at mem_sections(3avr). Go forth and section your heart out, you absolute icon ππ
π¦ where this page comes from
- ubuntu resolute: avr-libc 1:2.2.1-1build1 Β· BSD-3-Clause
- ubuntu stonking: avr-libc 1:2.2.1-1build1 Β· BSD-3-Clause
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms