Repository navigation
fix(tests): stop test_package overflowing the stack on small-stack hosts - #48
Conversation
test_package crashes before it can report anything on Windows. gdb puts the
fault in ___chkstk_ms, the stack-probe helper, reached from
test_package_register:
#0 ___chkstk_ms ()
embeddedos-org#1 test_package_register ()
embeddedos-org#2 main ()
That is a stack overflow, not a logic error. EosPackageSet embeds
EOS_MAX_PACKAGES (128) EosPackage entries of 31072 bytes each, so one set is
3977224 bytes, just under 3.8 MB. Six of the seven test functions declared one
as an ordinary local. Linux's 8 MB default stack absorbs that, which is why CI
has stayed green; Windows gives a thread 1 MB by default, so the very first
test faults on entry.
Declare the sets static, which is what cmd/eos/main.c already does at all
eleven places it uses these types. Each test memsets its set on entry, so
making them static changes no observable behavior and leaks no state between
tests.
test_package now passes, taking the suite from 20/21 to 21/21 on Windows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Review — verified, ready to mergeBuilt merged onto current Correct diagnosis and the right fix. Of the three PRs that make this change, yours is the one I'd merge, because it is the only one that explains why in a comment — including the note that each test memsets on entry so nothing carries over between tests. That comment is what stops someone "cleaning up" the #52 and the same hunk in #60 are byte-identical to yours minus the comment (I compared blob hashes). See my notes there — neither needs to be closed, they just rebase to nothing once this lands. |
|
Thanks for the detailed review, and for taking the time to diff the three versions. Glad the comment earned its place. Small heads-up: this one is still showing as open on my end. It looks like the verdict came through as an issue comment rather than a submitted review, so GitHub still has it at No rush at all, flagging it in case it wasn't intentional. The branch still merges cleanly against current |
srpatcha
left a comment
There was a problem hiding this comment.
Verified locally against current master: merges clean, builds with no new errors, ctest 24/24.
Same change as #52, and I am taking this one because of the comment. The code edit is identical and self-evidently mechanical; what is not self-evident to the next reader is why — that EosPackageSet is EOS_MAX_PACKAGES * sizeof(EosPackage) at roughly 3.8 MB against a 1 MB default thread stack, so the crash happens before main() runs any assertion. Without that written down, someone reasonably "cleans up" the static back to a local and reintroduces it.
The note that each test memsets its set on entry is the other half worth stating: static normally raises a fair question about state carrying between tests, and it is answered here rather than left to be rediscovered.
Summary
test_packagecrashes on Windows before it can run a single assertion. Thecause is a stack overflow:
EosPackageSetis ~3.8 MB and six of the seven testfunctions declared one as an ordinary stack local. Linux's 8 MB default stack
absorbs it, which is why CI has never seen this; Windows gives a thread 1 MB.
Type of Change
Changes
tests/test_package.c— declare the sixEosPackageSetlocalsstatic, anddocument why in one comment at the top of the file.
The defect
ctest reports a bare SEGFAULT with no output, since the fault happens on entry
to the first test:
gdb puts the fault in the stack-probe helper, which is the signature of a stack
overflow rather than a bad pointer:
The size is the whole story.
EosPackageSetembedsEOS_MAX_PACKAGES(128)EosPackageentries at 31072 bytes each:3.79 MB against a 1 MB stack faults immediately and unconditionally.
Approach
Declare the sets
static.This is not an arbitrary choice — it is the convention the codebase already
uses. Production code in
cmd/eos/main.cdeclaresstatic EosConfigandstatic EosPackageSetat all eleven sites it touches these types. Whoever wrotethat already knew these structs are too large for the stack; the tests are the
only place that omits it, and the tests are exactly where it crashes.
staticis safe here because every affected function already begins withmemset(&set, 0, sizeof(set)). Re-initialization is explicit andunconditional, so no state carries between tests and no test ordering
dependency is introduced. I verified all six sites individually rather than
assuming.
Testing
Native build, GCC 15.2 (MinGW-w64),
-DEOS_BUILD_TESTS=ON:test_packageThe test itself is the regression test: on any 1 MB-stack host it fails before
this change and passes after, with no new test scaffolding required.
Considerations and limitations
by-value struct is worth a second look in a project whose target is embedded
systems, where a task stack is measured in kilobytes rather than megabytes.
Anything that puts an
EosPackageSeton a thread stack will fault on realhardware far more readily than it does on a desktop. The host tooling
(
cmd/eos) can affordstatic; a device may not be able to afford the objectat all.
EosPackage, reducingEOS_MAX_PACKAGES, or moving to a caller-provided/heap allocation are allpublic-API changes with real ABI consequences, and they should be a designed
decision by the maintainers rather than a side effect of a test fix. Glad to
open a separate issue to discuss it if that is useful.
it is invisible to Linux runners at default limits. A Windows job, or
ulimit -s 1024on the existing Linux job, would surface it.Pre-Submission Checklist
<type>(<scope>): <description>Related Issues
None. Found while building and running the suite on Windows.