avrmem Architecture

Status

Accepted

This document records the architectural decisions governing the avrmem project.


Problem statement

avrmem needs to interpret AVR ELF executables while remaining independent of any single MCU.

The original implementation contained MCU-specific constants directly in the executable:

FLASH size
SRAM range
EEPROM size
ELF DATA offset

That approach does not scale.

It also conflates several distinct concepts:

ELF/linker address
physical AVR address
CPU instruction/word address
load-memory address

The current architecture separates those concerns.


Architectural overview

The application is divided into the following layers:

                           +------------------+
                           |    avrmem.c      |
                           | CLI / presentation|
                           +--------+---------+
                                    |
                    +---------------+---------------+
                    |                               |
                    v                               v
             +-------------+                 +---------------+
             |  avr_elf.c  |                 | avr_device.c |
             | ELF semantics|                | device model |
             +------+------+                 +-------+-------+
                    |                                |
                    |                                v
                    |                       +-------------------+
                    |                       | avr_device_probe  |
                    |                       +---------+---------+
                    |                                 |
                    |                    +------------+------------+
                    |                    |                         |
                    |                    v                         v
                    |             avr-gcc -dM -E            avr-gcc linker
                    |             <avr/io.h>                generated map
                    |                    |                         |
                    |                    v                         v
                    |               avr_sfr.c              linker regions
                    |
                    v
                 ELF image

The responsibilities are intentionally separated.


avrmem.c

avrmem.c is the application frontend.

It owns:

It does not own:

The frontend consumes the public APIs exposed by the lower layers.


avr_elf.c

avr_elf.c owns ELF semantics.

It is responsible for:

It deliberately does not contain:

ATmega328P constants
SRAM offsets
FLASH sizes
EEPROM sizes
SFR tables

Those belong to the device layer.


avr_device.c

avr_device.c owns the runtime representation of one selected AVR device.

The device model contains two different classes of information.

Physical memory

FLASH
SRAM
EEPROM

These are actual target-memory regions.

ELF/linker address spaces

TEXT
DATA
EEPROM

These are the address ranges used by the AVR linker and therefore appear in ELF section and symbol addresses.

The two categories are deliberately represented separately.


Physical SRAM versus DATA linker space

SRAM is a physical memory.

The AVR linker may represent the same physical SRAM using a different address-space origin.

Therefore:

physical SRAM address
        !=
ELF DATA-space address

The conversion is owned by AvrDevice.

Conceptually:

ELF DATA address
       |
       v
DATA linker region
       |
       v
physical AVR DATA address
       |
       +----> register file
       +----> I/O
       +----> extended I/O
       +----> SRAM

.data, .bss, stack, heap, and .noinit are all runtime allocations within physical SRAM.

They are not separate physical memories.

Their differences are ELF/linker section semantics.


Device discovery

Device-specific information is not maintained in a source-level MCU table.

The selected device is passed to:

avr-gcc -mmcu=<device>

The toolchain is then used to discover the device description.

Two complementary mechanisms are used.

Preprocessor discovery

The probe asks AVR-GCC to preprocess <avr/io.h>:

avr-gcc -mmcu=<device> -dM -E ...

This provides the concrete macro environment selected for the device.

The probe extracts:

This means the project does not maintain its own database of those values.


Linker-memory discovery

The probe performs an actual AVR link and generates a linker map:

avr-gcc -mmcu=<device> ... -Wl,-Map=<map>

The generated map provides resolved linker memory-region information.

The probe extracts:

TEXT
DATA
EEPROM

origins and lengths from the map.

Why the linker map is used

The default AVR linker script can contain symbolic expressions and linker symbols instead of concrete numeric values.

The generated map represents the values after linker resolution.

Therefore the map is the authoritative source for the linker’s selected memory regions.

Two consequences are load-bearing for the implementation:

  1. The probe must run a full device link. Linking with -nostartfiles -nodefaultlibs drops the device-specific layout and the map falls back to the generic avr5 script (data ORIGIN=0x800060, 128K text), which mistranslates every DATA address. Only the resolved per-device Memory Configuration is valid input.
  2. The map’s DATA origin is not the translation base. avr-ld resolves the per-device DATA region to begin at physical SRAM start (origin = VMA base + RAMSTART), so avr_device_init() normalizes the stored DATA space to the VMA base (origin -= sram_start, length += sram_start, guarded) before classification and translation. The probe result itself keeps the raw map values.

avr_sfr.c

avr_sfr.c owns SFR parsing.

It consumes the concrete preprocessor output generated by the selected avr-libc environment.

Typical definitions include:

#define PORTB  _SFR_IO8(0x05)
#define UCSR0A _SFR_MEM8(0xC0)

The SFR parser normalizes both forms into physical AVR DATA-space addresses.

For _SFR_IO8():

encoded I/O address
        +
__SFR_OFFSET
        |
        v
physical DATA-space address

For _SFR_MEM8():

encoded memory address
        |
        v
physical DATA-space address

This allows callers to perform a uniform address lookup regardless of whether avr-libc defined the register using _SFR_IO8() or _SFR_MEM8().


SFRs are an auxiliary capability

SFR discovery must not invalidate an otherwise valid device description.

The device can still be useful for:

FLASH
SRAM
EEPROM
TEXT/DATA/EEPROM linker regions
ELF symbol resolution

even when a particular avr-libc release changes its SFR macro representation.

Therefore SFR discovery is intentionally treated as an auxiliary capability.


Address-resolution model

Address interpretation follows this model:

                    ELF symbol value
                           |
                           v
                +----------------------+
                |    AvrDevice         |
                | classify/translate   |
                +----------+-----------+
                           |
             +-------------+-------------+
             |             |             |
             v             v             v
           FLASH         DATA         EEPROM
                           |
             +-------------+-------------+
             |             |             |
             v             v             v
          REGISTER       I/O           SRAM

The key property is that avr_elf.c does not perform the MCU-specific translation itself.

It asks AvrDevice to interpret the address.


Resolved symbol model

AvrResolvedSymbol combines information from both layers.

A normal symbol can therefore have:

ELF VMA
    |
    +--> memory space
    +--> AVR/physical address
    +--> physical address
    +--> section
    +--> LMA
    +--> FLASH CPU word address
    +--> SFR

The distinction between runtime and load address is critical.

For .data:

symbol VMA
    |
    +--> SRAM runtime location

symbol LMA
    |
    +--> FLASH initialization image

LMA resolution

ELF section headers do not directly contain a load-memory address.

The implementation derives LMA from file-backed PT_LOAD program headers.

For a section-relative offset:

LMA =
    PT_LOAD.p_paddr
    + (section.sh_offset - PT_LOAD.p_offset)
    + section_offset

This works for .data without requiring a special-case lookup of __data_load_start.

For SHT_NOBITS sections such as .bss, there is no file-backed initialization image and therefore no LMA.

Section residency is orthogonal to LMA: a section without SHF_ALLOC (such as .debug_*, .comment, or notes) is not resident in target memory, so its VMA is not a device address and section resolution reports UNKNOWN with no physical address — even though a file-backed LMA may still exist (as for .stab). Residency gates translation; file offsets gate LMA.


Device selection policy

The command-line frontend supports:

--device <name>

and automatic device selection.

Explicit device

When --device is supplied:

--device <name>
        |
        v
avr_device_init()

The explicit device controls device-specific address interpretation.

Automatic device

When --device is omitted:

ELF
 |
 +--> avr_elf_get_device_name()
 |
 v
avr_device_init()

The device name comes from AVR ELF metadata.

This keeps the normal invocation simple:

avrmem firmware.elf

while still allowing:

avrmem --device atmega328p firmware.elf

for explicit control.


Command-line parsing

avrmem uses getopt_long().

The canonical command-line structure is:

avrmem [options] <elf>

The option-string begins with + so GNU getopt_long() stops option processing at the first non-option argument.

Therefore:

avrmem --symbols firmware.elf

is valid, while:

avrmem firmware.elf --symbols

is rejected.

This keeps the CLI deterministic and matches the project’s requirement that all options precede the ELF filename.


Ownership and lifetime

Ownership is explicit throughout the design.

AvrDeviceProbeResult

Owns:

name
sfr_database

until ownership is transferred.

AvrDevice

After successful initialization, owns:

name
sfr_database

These are released by:

avr_device_destroy()

AvrElf

Owns:

ELF image
section arrays
symbol arrays
program headers
string tables
device-name metadata

Pointers returned from the ELF API are borrowed pointers.

They remain valid until:

avr_elf_close()

Error handling

Device probing is intentionally staged:

1. AVR-GCC macro probe
2. device-name extraction
3. physical-memory extraction
4. SFR extraction
5. linker-map generation
6. linker-region extraction
7. AvrDevice construction

Each stage should identify its own failure.

This is important because the toolchain is an external dependency and can fail due to:

The diagnostics should identify the actual stage rather than collapsing all failures into:

unable to initialize AVR device

POSIX host assumption

The current implementation deliberately assumes a POSIX host.

It uses interfaces such as:

popen()
pclose()
mkstemp()
fdopen()
unlink()
getopt_long()

Feature-test macros are provided where required so the intended POSIX APIs are visible under C11 compilation.

The design should allow future host abstraction without changing the public ELF or device APIs.

Potential future platforms include:

Darwin
Windows

but they are outside the current scope.


Rejected alternatives

Hardcoded MCU tables

Rejected.

A table of:

atmega328p
atmega2560
attiny...

would duplicate information already present in the AVR toolchain and would require source changes whenever support for another device is required.


MCU-specific initialization functions

Rejected.

Functions such as:

init_atmega328p();
init_atmega2560();

would violate the device abstraction.

The public initialization path must remain:

avr_device_init(
  &device,
  device_name
);

Parsing raw linker scripts as the primary source

Rejected.

The linker script may contain unresolved expressions, symbols, and toolchain-specific constructs.

The generated linker map reflects the resolved configuration selected by the actual linker invocation.


Hardcoded SFR tables

Rejected.

The SFR definitions already exist in avr-libc.

Duplicating them would create another source of truth.


Putting all address calculations in avr_elf.c

Rejected.

ELF semantics and MCU address semantics are different responsibilities.

avr_elf.c determines:

what the ELF says

avr_device.c determines:

what that address means for this AVR device

Architectural invariants

The following rules should remain true as the project evolves.

Invariant 1

No MCU-specific memory constants belong in avrmem.c.

Invariant 2

No MCU-specific memory constants belong in avr_elf.c.

Invariant 3

Physical memory and ELF/linker address spaces remain distinct.

Invariant 4

SRAM is one physical memory regardless of whether storage is .data, .bss, .noinit, stack, or heap.

Invariant 5

SFRs are resolved from the selected toolchain and normalized to physical DATA-space addresses.

Invariant 6

LMA is derived from ELF load mappings rather than guessed from VMA.

Invariant 7

AvrDevice owns device-specific interpretation.

Invariant 8

AvrElf owns ELF-specific interpretation.

Invariant 9

The CLI only orchestrates and presents.

Invariant 10

Adding support for another AVR device should not require a per-device initialization function or hardcoded memory table.


Future extensions

The current architecture leaves room for:

Such extensions should preserve the separation between:

ELF semantics
device semantics
toolchain discovery
CLI presentation