Repository navigation
Description
PureGo Version
main @ 763bb99
Operating System
- Windows
- macOS
- Linux
- FreeBSD
- NetBSD
- Android
- iOS
Go Version (go version)
go1.26.7 linux/amd64 (finding is in 386-only assembly; verified via cross-build + disassembly, see below)
What steps will reproduce the problem?
The 386 callback trampoline (sys_unix_386.s: callbackasm1) has two coupled defects around callbackArgs.result:
- It allocates only one result word (
MOVL $0, 32(SP)) and returns onlyEAX(MOVL 32(SP), AX), while Go'scallbackArgshasresult [4]uintptrandsetInt64Result/setUint64Result(syscall_notstackargs.go:43-55) write the upper half toresult[1]on 32-bit platforms. i386 cdecl returns 64-bit values inEDX:EAX, so everyNewCallback(func(...) int64/uint64)silently loses its upper 32 bits (0x1_00000000becomes0). - The args copy starts at
36(SP)— overlappingresult[1..3]of the Go struct — so the copy and the result alias each other.
Contrast sys_unix_arm.s:70-71, which correctly returns both R0 and R1 for 64-bit callback results.
Verification on this box (no 32-bit execution environment, so proven at the binary level instead):
// GOOS=linux GOARCH=386 go build, then objdump the trampoline:
CALL crosscall2(SB)
ADDL $0x10, SP
MOVL 0x20(SP), AX // result[0] -> EAX only; no DX load anywhere before RET
...
RET
NewCallback accepts int64/uint64 returns (syscall_unix.go: compileCallback output switch), the Go side dutifully writes both halves — and the assembly drops the top half on the floor.
Repro (386 Linux or Windows/386 — needs a 32-bit host to execute):
cb := purego.NewCallback(func(a, b int32) int64 {
return int64(a)<<32 | int64(uint32(b)) // e.g. 0x1_00000002
})
// call cb from C as int64_t f(int32_t, int32_t); observe the return
// want 0x100000002; with the bug the upper half reads 0x0What is the expected result?
64-bit callback results round-trip intact (EDX:EAX), and the args area no longer overlaps the result area.
What happens instead?
Deterministic truncation: the upper 32 bits of every int64/uint64 callback return read as zero (whatever happened to be in EDX), and deep argument lists can clobber pending results.
Anything else you feel useful to add?
Minimized reproducible case: the NewCallback snippet above driven from C (or any 64-bit-returning callback on a 386 host). On this amd64 box I verified instead that (1) GOOS=linux GOARCH=386 and GOOS=windows GOARCH=386 builds pass, (2) the rebuilt trampoline disassembles to MOVL 0x20(SP), AX + MOVL 0x24(SP), DX before the register restores, and (3) the full test suite passes on amd64.
Related PR: #519.
Activity
purego: return 64-bit callback results on 386 (#519)
hajimehoshi commentedon Sep 18, 2026
@TotallyGamerJet Do you think this should be backported to 0.11?
TotallyGamerJet commentedon Sep 18, 2026
If possible yes
purego: return 64-bit callback results on 386 (#519)
hajimehoshi commentedon Sep 18, 2026
Sure, done.
purego: return 64-bit callback results on 386