Skip to content

ch32v: V20x links against Link.ld defaults (64K/20K) — stack pointer beyond physical RAM on CH32V203C6/G6, RB/208 capped at half #1103

Description

@zackees

Part of the CH32V pipeline audit (#1102).

What happens

For every CH32V20x-series board, the orchestrator links with the OpenWCH core's shared linker script:

crates/fbuild-build-mcu/src/ch32v/orchestrator.rs (~line 172):

let linker_script_path = framework_dir.join("system").join(&system_series)
    .join("SRC").join("Ld").join("Link.ld");

At the pinned core commit (d7671623), system/CH32V20x/SRC/Ld/Link.ld is parameterized:

__flash_size = DEFINED(__FLASH_SIZE) ? __FLASH_SIZE : 64K;
__ram_size   = DEFINED(__RAM_SIZE)   ? __RAM_SIZE   : 20K;

fbuild never passes --defsym __FLASH_SIZE=... / --defsym __RAM_SIZE=... (checked Ch32vLinker::link and configs/ch32v003.json linker_flags), so every V20x board links against 64K flash / 20K RAM regardless of what the chip actually has.

Why it matters — worked example

The script puts the startup stack at the top of the declared RAM: _eusrstack = ORIGIN(RAM) + LENGTH(RAM) = 0x2000_5000 with the 20K default.

  • genericCH32V203C6T6 / genericCH32V203G6U6 are 32K-flash / 10K-RAM parts (board JSONs correctly say ram: 10240, rom: 32768; matches the WCH datasheet and the commented-out CH32V203F6/G6/K6/C6 block in the same Link.ld). Physical RAM ends at 0x2000_2800. The startup code loads SP = 0x2000_5000the stack pointer points at memory that doesn't exist, so the very first push faults. Firmware built by fbuild for these two boards cannot run on real silicon, while the build and the size check both report success (size check compares against the board JSON's 32K/10K, which the link didn't enforce).
  • genericCH32V203RBT6 / genericCH32V208WBU6 are 128K-flash / 64K-RAM parts. They link against 64K/20K: a 70K firmware that the size check says "fits in 128K" fails at link, and 44K of RAM is unusable.

Suggested fix

Pass the board JSON memory through at link time for the V20x family, e.g.:

-Wl,--defsym=__FLASH_SIZE=<board.rom>,--defsym=__RAM_SIZE=<board.ram>

(the other series' scripts hardcode MEMORY and don't read these symbols, so this is safe to gate on the script actually being parameterized, or just always define the symbols — DEFINED() only consults them where the script opts in). Add a build-time assertion that _eusrstack <= ORIGIN(RAM) + board.ram would also have caught this class of bug.

Severity: HIGH (silent runtime brick on C6/G6 parts; inconsistent size accounting on RB/208).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions