Skip to content

Crash on musl/386 due to stack alignment #458

@divVerent

Description

@divVerent

PureGo Version

1512f32

Operating System

  • Windows
  • macOS
  • Linux
  • FreeBSD
  • NetBSD
  • Android
  • iOS

Go Version (go version)

go version go1.26.3 linux/386

What steps will reproduce the problem?

  1. Replace examples/libc/main.go by:
package main
import (
	"github.com/ebitengine/purego"
)
func main() {
	purego.Dlopen("libstdc++.so.6", 0)
}
  1. go run main go

What is the expected result?

No output.

What happens instead?

SIGSEGV: segmentation violation
PC=0xf77fa58b m=0 sigcode=128 addr=0x0
signal arrived during cgo execution

goroutine 1 gp=0x8408148 m=0 mp=0x81b7720 [syscall]:
runtime.cgocall(0x80e56d0, 0x8532000)
	/usr/lib/go/src/runtime/cgocall.go:167 +0x61 fp=0x845cb84 sp=0x845cb6c pc=0x80c60c1
github.com/ebitengine/purego.syscall_SyscallN(0xf7e7b800, {0x845cbe4, 0x20, 0x20}, {0x845cc78, 0x20, 0x20}, 0x0)
	/root/purego/syscall_32bit.go:41 +0x350 fp=0x845cb98 sp=0x845cb84 pc=0x80e46c0
github.com/ebitengine/purego.RegisterFunc.func4({0x851c468, 0x2, 0x2})
	/root/purego/func.go:325 +0x3da fp=0x845cda4 sp=0x845cb98 pc=0x80e358a
reflect.callReflect(0x851c378, 0x845cf78, 0x845cf70, 0x0)
	/usr/lib/go/src/reflect/value.go:772 +0x3f6 fp=0x845cf60 sp=0x845cda4 pc=0x80d8ca6
reflect.makeFuncStub()
	/usr/lib/go/src/reflect/asm_386.s:21 +0x28 fp=0x845cf78 sp=0x845cf60 pc=0x80dc548
github.com/ebitengine/purego.Dlopen({0x810b286, 0xe}, 0x0)
	/root/purego/dlfcn.go:41 +0x36 fp=0x845cf8c sp=0x845cf78 pc=0x80e2c86
main.main()
	/root/purego/examples/libc/main.go:13 +0x33 fp=0x845cfa8 sp=0x845cf8c pc=0x80ead03
runtime.main()
	/usr/lib/go/src/runtime/proc.go:290 +0x2dc fp=0x845cff0 sp=0x845cfa8 pc=0x8095a7c
runtime.goexit({})
	/usr/lib/go/src/runtime/asm_386.s:1389 +0x1 fp=0x845cff4 sp=0x845cff0 pc=0x80cce11

In gdb I learn more:

(gdb) bt
#0  0xf77e358b in std::ios_base::Init::Init() () from /usr/lib/libstdc++.so.6
#1  0xf77c8fdc in ?? () from /usr/lib/libstdc++.so.6
#2  0xf7e97ba2 in do_init_fini (queue=<optimized out>) at ldso/dynlink.c:1606
#3  0xf7e878ea in dlopen (file=0x9922070 "libstdc++.so.6", mode=0) at ldso/dynlink.c:2219
#4  0x080e57db in syscallX () at /root/purego/sys_386.s:126
#5  0x00000000 in ?? ()
(gdb) disassemble
[...]
   0xf77e357c <+124>:	movq   %xmm2,-0x58(%ebp)
   0xf77e3581 <+129>:	movd   %eax,%xmm5
   0xf77e3585 <+133>:	mov    0x1bf8(%esi),%eax
=> 0xf77e358b <+139>:	movaps %xmm5,-0x48(%ebp)
   0xf77e358f <+143>:	lea    0x20(%eax),%edx
   0xf77e3592 <+146>:	add    $0xc,%eax
   0xf77e3595 <+149>:	movd   %eax,%xmm1
[...]
(gdb) print $ebp
$1 = (void *) 0xffb5ede4

Thus, the address computed as -0x48(%ebp) is not 16-byte aligned, which is required by this instruction.

Anything else you feel useful to add?

Digging further, the issue appears to be that Go uses 4-byte stack alignment on 386, whereas libstdc++ at least on Alpine Linux requires 16-byte stack alignment.

It seems that this means changes are required to sys_386.s to align the stack to 16 bytes. It has to be aligned right before the CALL, but of course the alignment has to be done before pushing arguments.

Quite likely the same changes are needed for sys_amd64.s; even though I did not observe any crashes yet, x86-64 C ABI requires 16 bytes stack alignment but Go only ensures 8 bytes.

In both cases, cgo has code to perform the necessary translation by aligning the stack at the call boundary - purego needs something similar.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions