Skip to content

all: align the stack for C calls on linux/386 - #558

Merged
hajimehoshi merged 1 commit into
ebitengine:mainfrom
hajimehoshi:claude/linux-386-stack-align
Nov 1, 2026
Merged

hajimehoshi merged 1 commit into
ebitengine:mainfrom
hajimehoshi:claude/linux-386-stack-align

Conversation

@hajimehoshi

Copy link
Copy Markdown
Member

What issue is this addressing?

Closes #458

What type of issue is this addressing?

bug

What this PR does | solves

The i386 System V ABI requires the stack pointer to be 16-byte aligned at a call, and compilers rely on it for aligned SSE stack accesses such as movaps, which fault with sigcode=128 addr=0x0 otherwise. Two linux/386 trampolines broke this:

  • syscallX (sys_386.s) is entered on an aligned stack from runtime.asmcgocall, but its four PUSHLs and SUBL $160 left ESP at 12 mod 16 at CALL AX. linux/386 uses this path with Cgo enabled too, since syscall_cgo_linux.go excludes 386.
  • call5 (internal/fakecgo/trampolines_386.s), used for pthread_create, malloc, setenv and others, did not realign at all, so its alignment depended on the calling Go frame.

Besides the musl/libstdc++ crash in the issue, this crashes Ebitengine's GOARCH=386 CI on Ubuntu 26.04 (Mesa 26.0.8) in glXQueryExtensionsString.

Both trampolines now save SP in the callee-saved BP and align SP with ANDL $~15, SP before placing the arguments, as call5 in trampolines_amd64.s already does.

TestRegisterFunc_StackAlignment calls a naked C function that returns the caller's stack pointer modulo 16 at the call. It runs on linux/386 and on amd64, where the alignment in syscallX depends on its frame size.

The 386 setg_trampoline and call5 FP operands are also renamed to match their Go declarations (G, ret), which fixes GOARCH=386 go vet's asmdecl reports. The generated code is byte-identical.

Verification:

  • In an Ubuntu 26.04 container (gcc 15.2, qemu-i386), the new test fails before the fix with "got 12, want 0" (Cgo on and off) and passes after. The root package passes on linux/386 and linux/amd64 with Cgo on and off.
  • On darwin/amd64 (Rosetta), the test passes, and it fails with "got 8, want 0" when syscallX's frame is shifted by 8 bytes.
  • Ebitengine's internal/graphicscommand 386 tests crash with the CI signature before the fix and pass after.
  • The Alpine/musl reproduction in the issue was not rerun.

Filed by Claude (Claude Code), on behalf of @hajimehoshi

🤖 Generated with Claude Code

@hajimehoshi
hajimehoshi requested review from TotallyGamerJet and a balanced review from Copilot November 1, 2026 10:34
@hajimehoshi

Copy link
Copy Markdown
Member Author

@TotallyGamerJet PTAL

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The Linux/386 C-call stack-alignment fix has no active review issues.

No open issues 🎉

What changed in this PR

Fixes Linux/386 C-call stack alignment in PureGo’s trampolines, addressing the ABI violation behind the crashes reported in #458.

Changes:

  • Aligns the stack to 16 bytes before C calls in syscallX and fakecgo’s call5, preserving the original stack pointer for restoration.
  • Adds a regression test for call-site stack alignment on Linux/386 and amd64.
  • Renames 386 assembly operands to match their Go declarations and resolve go vet reports.
File Description
testdata/​abitest/​abi_test.c Adds a C helper that measures call-site stack alignment.
sys_386.s Aligns the C-call stack and restores the saved stack pointer.
internal/​fakecgo/​trampolines_386.s Aligns call5’s stack and corrects assembly operand names.
func_test.go Adds the stack-alignment regression test.

Review effort: Balanced

@TotallyGamerJet TotallyGamerJet left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

The i386 System V ABI requires the stack pointer to be 16-byte aligned
at a call, and compilers rely on it for aligned SSE stack accesses such
as movaps, which fault otherwise. Two linux/386 trampolines broke this.
syscallX is entered on an aligned stack from runtime.asmcgocall, but
its register saves and fixed frame left ESP at 12 mod 16 at the call.
linux/386 uses this path with Cgo enabled too. call5 in fakecgo, which
calls pthread_create, malloc, setenv and others, did not realign at
all, so its alignment depended on the calling Go frame.

Save SP in the callee-saved BP and align SP with ANDL before placing
the arguments in both trampolines, as call5 already does on amd64.

Add a test that calls a naked C function returning the caller's stack
pointer modulo 16 at the call. It runs on linux/386 and on amd64, where
the alignment in syscallX depends on its frame size.

Also rename the FP operands in the 386 setg_trampoline and call5 to
match their Go declarations, which fixes go vet's asmdecl reports. The
generated code is unchanged.

Closes ebitengine#458

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hajimehoshi
hajimehoshi force-pushed the claude/linux-386-stack-align branch from 16f7f60 to 852ac27 Compare November 1, 2026 13:02
@hajimehoshi
hajimehoshi merged commit ff029b4 into ebitengine:main Nov 1, 2026
25 checks passed
@hajimehoshi
hajimehoshi deleted the claude/linux-386-stack-align branch November 1, 2026 13:45
hajimehoshi added a commit that referenced this pull request Nov 1, 2026
The i386 System V ABI requires the stack pointer to be 16-byte aligned
at a call, and compilers rely on it for aligned SSE stack accesses such
as movaps, which fault otherwise. Two linux/386 trampolines broke this.
syscallX is entered on an aligned stack from runtime.asmcgocall, but
its register saves and fixed frame left ESP at 12 mod 16 at the call.
linux/386 uses this path with Cgo enabled too. call5 in fakecgo, which
calls pthread_create, malloc, setenv and others, did not realign at
all, so its alignment depended on the calling Go frame.

Save SP in the callee-saved BP and align SP with ANDL before placing
the arguments in both trampolines, as call5 already does on amd64.

Add a test that calls a naked C function returning the caller's stack
pointer modulo 16 at the call. It runs on linux/386 and on amd64, where
the alignment in syscallX depends on its frame size.

Also rename the FP operands in the 386 setg_trampoline and call5 to
match their Go declarations, which fixes go vet's asmdecl reports. The
generated code is unchanged.

Closes #458

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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.

Crash on musl/386 due to stack alignment

3 participants