Skip to content

fix(build): move boot_log into eboot_core so the core library links standalone - #39

Merged
srpatcha merged 2 commits into
embeddedos-org:masterfrom
Kartikey1306:fix/boot-log-core-layering
Aug 27, 2026
Merged

fix(build): move boot_log into eboot_core so the core library links standalone#39
srpatcha merged 2 commits into
embeddedos-org:masterfrom
Kartikey1306:fix/boot-log-core-layering

Conversation

@Kartikey1306

Copy link
Copy Markdown
Contributor

Summary

eboot_core cannot link on its own. core/recovery.c, core/boot_policy.c and
stage0/jump_stage1.c all call eos_boot_log_*(), but boot_log.c was
compiled into eboot_stage1 — which itself links PUBLIC against eboot_core.

That circular dependency breaks two targets:

  • test_recovery links only eboot_core and fails to build.
  • ebldr_stage0 compiles stage0/jump_stage1.c, which calls
    eos_boot_log_append() three times, and links
    eboot_core board_${EBLDR_BOARD} without eboot_stage1. It cannot link
    for any board.

The fix is to put the code in the layer that uses it.

The bug

eboot_stage1  ──links──▶  eboot_core
      ▲                        │
      └────── needs ───────────┘   core/recovery.c, core/boot_policy.c,
             (boot_log.c)          stage0/jump_stage1.c

On master:

$ cmake --build build -j
Undefined symbols for architecture arm64:
  "_eos_boot_log_append", referenced from:
      _eos_recovery_enter in libeboot_core.a[6](recovery.c.o)
      ...
  "_eos_boot_log_get_head", referenced from:
      _recovery_collect_boot_log_entries in libeboot_core.a[6](recovery.c.o)
  "_eos_boot_log_read", referenced from:
      _recovery_collect_boot_log_entries in libeboot_core.a[6](recovery.c.o)
ld: symbol(s) not found

The build exits 2 and ctest reports 13/14 with test_recovery Not Run.

The stage0 half is the part that matters

test_recovery is a test. ebldr_stage0 is firmware. It has exactly the
same unmet dependency, and it is invisible in CI because EBLDR_BOARD defaults
to "none", so the target is never created in a host build
(CMakeLists.txt:151). It would only surface on a real cross-compile.

Demonstrated by linking stage0's requirement against each version of the
library:

$ cat stage0_sim.c
extern void eos_boot_log_append(uint32_t event, uint32_t slot, uint32_t detail);
int main(void) { eos_boot_log_append(1, 0, 0); return 0; }

# against eboot_core on master
Undefined symbols: "_eos_boot_log_append"     ld: symbol(s) not found

# against eboot_core with this change
linked OK / ran OK

Approach

Move boot_log.c from stage1/ to core/.

It is core-layer code already: it includes only eos_types.h, eos_hal.h and
string.h, and implements a flash-backed circular log with no stage-1
dependency of any kind. Its callers are split across core, stage0 and stage1 —
the one layer it does not belong to is stage1.

The file is moved verbatim. Git records a pure rename and the SHA-256 is
identical before and after (b345d030…de99); the only edits are the two CMake
source-list lines.

Why not link eboot_stage1 into the test

That is what #35 does, and it does fix test_recovery. I went the other way for
two reasons:

  1. It leaves eboot_core non-self-contained, so ebldr_stage0 stays
    broken
    — the test passes but the firmware target still cannot link.
  2. It links a stage-1 library into a core-level unit test, which inverts the
    layering in the test as well.

Because this fixes the root cause, tests/CMakeLists.txt needs no change at
all, so there is no textual conflict with #35 — whichever lands first, the
other is either redundant or applies cleanly. Happy to close this in favour of
#35 if maintainers prefer the narrower fix, but the stage0 break would then want
its own issue.

Testing

$ cmake -S . -B build -DEBLDR_BUILD_TESTS=ON -DCMAKE_BUILD_TYPE=Debug
$ cmake --build build -j
BUILD=0                       # master exits 2

$ ctest --test-dir build
100% tests passed out of 14   # master: 13/14, test_recovery Not Run

test_recovery builds and passes. No other test changes behaviour.

I specifically checked for a duplicate-symbol clash: test_boot_log links
eboot_core and defines its own eos_boot_log_* stubs. It still builds and
passes, because the linker only pulls boot_log.c.o from the archive to resolve
an undefined symbol, and that test defines them all itself.

Two findings I am reporting rather than fixing

Both turned up while tracing this and are out of scope for a layering fix:

  1. include/eos_boot_log.h does not describe the implementation. It
    declares a different, incompatible API under the same names:

    header boot_log.c
    eos_boot_log_init int (void) void (uint32_t head)
    eos_boot_log_read int (entry_t *, uint32_t) int (uint32_t, entry_t *)
    eos_boot_log_get_head absent uint32_t (void)
    count / flush / get_latest / event_name declared not implemented

    Nothing breaks today only because no non-test source includes the header —
    every caller writes its own local extern declaration. Anyone who does
    include it gets eos_boot_log_read(entries, max_count) compiled against an
    implementation whose first parameter is an index, i.e. a pointer
    reinterpreted as uint32_t.

  2. tests/unit/test_boot_log.c tests its own stubs. It includes
    eos_boot_log.h, then defines every function in that header itself, and
    asserts against those definitions. It exercises no line of boot_log.c.

Reconciling the header and rewriting that test against the real implementation
(it needs a simulated flash HAL, like test_image_verify.c has) is a
self-contained follow-up. I did not want to bundle a rewrite of a passing test
into a two-line build fix. Happy to open an issue or a follow-up PR.

Limitations and considerations

  • No behaviour change. Same code, same symbols, different archive. Nothing
    is added to or removed from any firmware image; eboot_stage1 still gets
    boot_log transitively through eboot_core.
  • Cross-compile not exercised here. I do not have arm-none-eabi-gcc on
    this machine, so ebldr_stage0 is NOT RUN — the break and the fix are
    demonstrated by reproducing stage0's link requirement natively (above) rather
    than by building the firmware target.
  • Line endings preserved. CMakeLists.txt is CRLF and CHANGELOG.md is
    mixed; edits were applied byte-wise so untouched lines keep their endings.

Type of Change

  • fix — Bug fix
  • build — Build system changes

Changes

  • stage1/boot_log.ccore/boot_log.c (verbatim move).
  • CMakeLists.txt — source listed under eboot_core instead of eboot_stage1.
  • CHANGELOG.mdUnreleased entry.

Pre-Submission Checklist

  • Compiles without warnings
  • All tests pass — 14/14, up from 13/14
  • Fixes a test that could not previously build
  • No behaviour change; file moved verbatim (identical SHA-256)
  • No conflict with fix: link eboot_stage1 into test_recovery #35

…tandalone

core/recovery.c, core/boot_policy.c and stage0/jump_stage1.c all call
eos_boot_log_append() and friends, but boot_log.c was compiled into
eboot_stage1 — which itself links PUBLIC against eboot_core.

That circular dependency left eboot_core unable to link on its own:

  - test_recovery links only eboot_core and fails to build with undefined
    _eos_boot_log_append / _eos_boot_log_get_head / _eos_boot_log_read.
  - ebldr_stage0 compiles stage0/jump_stage1.c, which calls
    eos_boot_log_append() three times, and links `eboot_core board_${BOARD}`
    without eboot_stage1. It could not link for any board.

The stage0 breakage is invisible in CI because EBLDR_BOARD defaults to "none",
so the target is only created on a cross-compile.

boot_log.c is core-layer code with no stage-1 dependencies — it includes only
eos_types.h, eos_hal.h and string.h, and implements a flash-backed circular
log. Moving it to core/ removes the cycle and makes eboot_core self-contained.

The file is moved verbatim (git records a pure rename, identical SHA-256); the
only edits are the two CMake source-list lines.

This also resolves the test_recovery failure without needing to link
eboot_stage1 into a core-level test, so tests/CMakeLists.txt is untouched.

Verified: full build succeeds where it previously exited 2, and ctest goes from
13/14 to 14/14.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
srpatcha
srpatcha previously approved these changes Aug 27, 2026
@srpatcha
srpatcha merged commit 4d5d126 into embeddedos-org:master Aug 27, 2026
1 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants