detached 6e7b934

internal/fakecgo: implement setgid and friends on Linux (#426)

Implement and linkname the following functions:

```
//go:linkname _cgo_libc_setegid syscall.cgo_libc_setegid
//go:linkname _cgo_libc_seteuid syscall.cgo_libc_seteuid
//go:linkname _cgo_libc_setgid syscall.cgo_libc_setgid
//go:linkname _cgo_libc_setregid syscall.cgo_libc_setregid
//go:linkname _cgo_libc_setresgid syscall.cgo_libc_setresgid
//go:linkname _cgo_libc_setresuid syscall.cgo_libc_setresuid
//go:linkname _cgo_libc_setreuid syscall.cgo_libc_setreuid
//go:linkname _cgo_libc_setuid syscall.cgo_libc_setuid
//go:linkname _cgo_libc_setgroups syscall.cgo_libc_setgroups
```
commit
6e7b9347ed41b60a744d3193c754f53d419d9574
author
Quim Muntal · · 2026-03-04 10:17 UTC
committer
GitHub · · 2026-03-04 10:17 UTC
parent
012b8c4
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
19 files · +1114 -33
M .github/workflows/test.yml +24 -3
@@ -131,7 +131,15 @@ jobs:
131131 if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}
132132 run: |
133133 sudo apt-get update
134- sudo apt-get install -y qemu-user gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross
134+ sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross
135+
136+ # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2
137+ # which has a bug where inter-thread signal delivery (tgkill) hangs,
138+ # causing TestSetuidEtc to deadlock.
139+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_10.0.7+ds-0+deb13u1+b1_arm64.deb -O /tmp/qemu-user.deb
140+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_10.0.7+ds-0+deb13u1+b1_arm64.deb -O /tmp/qemu-user-binfmt.deb
141+ sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb
142+
135143 # Create symlink for the dynamic linker that QEMU expects
136144 sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
137145 go env -w CC=arm-linux-gnueabihf-gcc
@@ -180,8 +188,21 @@ jobs:
180188 gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu \
181189 gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \
182190 gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \
183- gcc-s390x-linux-gnu g++-s390x-linux-gnu \
184- qemu-user
191+ gcc-s390x-linux-gnu g++-s390x-linux-gnu
192+
193+ # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2
194+ # which has a bug where inter-thread signal delivery (tgkill) hangs,
195+ # causing TestSetuidEtc to deadlock.
196+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_10.0.7+ds-0+deb13u1+b1_amd64.deb -O /tmp/qemu-user.deb
197+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_10.0.7+ds-0+deb13u1+b1_amd64.deb -O /tmp/qemu-user-binfmt.deb
198+ sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb
199+ sudo apt-get -f install -y
200+
201+ # Verify that the expected QEMU binaries are installed and usable.
202+ qemu-loongarch64 --version
203+ qemu-ppc64le --version
204+ qemu-riscv64 --version
205+ qemu-s390x --version
185206
186207 - name: go test (Linux loong64)
187208 run: |
@@ -131,7 +131,15 @@ jobs:
131 if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}131 if: ${{ runner.os == 'Linux' && runner.arch == 'ARM64' }}
132 run: |132 run: |
133 sudo apt-get update133 sudo apt-get update
134- sudo apt-get install -y qemu-user gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross134+ sudo apt-get install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross
135+
136+ # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2
137+ # which has a bug where inter-thread signal delivery (tgkill) hangs,
138+ # causing TestSetuidEtc to deadlock.
139+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_10.0.7+ds-0+deb13u1+b1_arm64.deb -O /tmp/qemu-user.deb
140+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_10.0.7+ds-0+deb13u1+b1_arm64.deb -O /tmp/qemu-user-binfmt.deb
141+ sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb
142+
135 # Create symlink for the dynamic linker that QEMU expects143 # Create symlink for the dynamic linker that QEMU expects
136 sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3144 sudo ln -sf /usr/arm-linux-gnueabihf/lib/ld-linux-armhf.so.3 /lib/ld-linux.so.3
137 go env -w CC=arm-linux-gnueabihf-gcc145 go env -w CC=arm-linux-gnueabihf-gcc
@@ -180,8 +188,21 @@ jobs:
180 gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu \188 gcc-14-loongarch64-linux-gnu g++-14-loongarch64-linux-gnu \
181 gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \189 gcc-powerpc64le-linux-gnu g++-powerpc64le-linux-gnu \
182 gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \190 gcc-riscv64-linux-gnu g++-riscv64-linux-gnu \
183- gcc-s390x-linux-gnu g++-s390x-linux-gnu \191+ gcc-s390x-linux-gnu g++-s390x-linux-gnu
184- qemu-user192+
193+ # Install QEMU 10.x from Debian trixie. Ubuntu 24.04 ships QEMU 8.2
194+ # which has a bug where inter-thread signal delivery (tgkill) hangs,
195+ # causing TestSetuidEtc to deadlock.
196+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user_10.0.7+ds-0+deb13u1+b1_amd64.deb -O /tmp/qemu-user.deb
197+ wget -q http://ftp.debian.org/debian/pool/main/q/qemu/qemu-user-binfmt_10.0.7+ds-0+deb13u1+b1_amd64.deb -O /tmp/qemu-user-binfmt.deb
198+ sudo dpkg --force-depends -i /tmp/qemu-user.deb /tmp/qemu-user-binfmt.deb
199+ sudo apt-get -f install -y
200+
201+ # Verify that the expected QEMU binaries are installed and usable.
202+ qemu-loongarch64 --version
203+ qemu-ppc64le --version
204+ qemu-riscv64 --version
205+ qemu-s390x --version
185 206
186 - name: go test (Linux loong64)207 - name: go test (Linux loong64)
187 run: |208 run: |
M README.md +2 -0
@@ -112,8 +112,10 @@ This is a list of the copied files:
112112 * `internal/fakecgo/setenv.go` from package `runtime/cgo`
113113 * `internal/fakecgo/freebsd.go` from package `runtime/cgo`
114114 * `internal/fakecgo/netbsd.go` from package `runtime/cgo`
115+* `internal/fakecgo/linux.go` from package `runtime/cgo`
115116
116117 The `internal/fakecgo/go_GOOS.go` files were modified from `runtime/cgo/gcc_GOOS_GOARCH.go`.
118+The `internal/fakecgo/linux.go` file is a combination of `runtime/cgo/linux.go` and `runtime/cgo/linux_syscall.c`.
117119
118120 The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of
119121 `#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636))
@@ -112,8 +112,10 @@ This is a list of the copied files:
112 * `internal/fakecgo/setenv.go` from package `runtime/cgo`112 * `internal/fakecgo/setenv.go` from package `runtime/cgo`
113 * `internal/fakecgo/freebsd.go` from package `runtime/cgo`113 * `internal/fakecgo/freebsd.go` from package `runtime/cgo`
114 * `internal/fakecgo/netbsd.go` from package `runtime/cgo`114 * `internal/fakecgo/netbsd.go` from package `runtime/cgo`
115+* `internal/fakecgo/linux.go` from package `runtime/cgo`
115 116
116 The `internal/fakecgo/go_GOOS.go` files were modified from `runtime/cgo/gcc_GOOS_GOARCH.go`.117 The `internal/fakecgo/go_GOOS.go` files were modified from `runtime/cgo/gcc_GOOS_GOARCH.go`.
118+The `internal/fakecgo/linux.go` file is a combination of `runtime/cgo/linux.go` and `runtime/cgo/linux_syscall.c`.
117 119
118 The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of120 The files `abi_*.h` and `internal/fakecgo/abi_*.h` are the same because Bazel does not support cross-package use of
119 `#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636))121 `#include` so we need each one once per package. (cf. [issue](https://github.com/bazelbuild/rules_go/issues/3636))
M internal/fakecgo/gen.go +10 -0
@@ -121,6 +121,16 @@ var (
121121 {"nanosleep", [5]Arg{{"ts", "*syscall.Timespec"}, {"rem", "*syscall.Timespec"}}, "int32", nil},
122122 {"abort", [5]Arg{}, "", nil},
123123 {"sigaltstack", [5]Arg{{"ss", "*stack_t"}, {"old_ss", "*stack_t"}}, "int32", []string{"netbsd"}},
124+ {"__errno_location", [5]Arg{}, "uintptr", []string{"linux"}},
125+ {"setegid", [5]Arg{{"egid", "uint32"}}, "int32", []string{"linux"}},
126+ {"seteuid", [5]Arg{{"euid", "uint32"}}, "int32", []string{"linux"}},
127+ {"setgid", [5]Arg{{"gid", "uint32"}}, "int32", []string{"linux"}},
128+ {"setregid", [5]Arg{{"rgid", "uint32"}, {"egid", "uint32"}}, "int32", []string{"linux"}},
129+ {"setresgid", [5]Arg{{"rgid", "uint32"}, {"egid", "uint32"}, {"sgid", "uint32"}}, "int32", []string{"linux"}},
130+ {"setresuid", [5]Arg{{"ruid", "uint32"}, {"euid", "uint32"}, {"suid", "uint32"}}, "int32", []string{"linux"}},
131+ {"setreuid", [5]Arg{{"ruid", "uint32"}, {"euid", "uint32"}}, "int32", []string{"linux"}},
132+ {"setuid", [5]Arg{{"uid", "uint32"}}, "int32", []string{"linux"}},
133+ {"setgroups", [5]Arg{{"ngid", "uint32"}, {"gidset", "*uint32"}}, "int32", []string{"linux"}},
124134 }
125135 pthreadSymbols = []Symbol{
126136 {"pthread_attr_init", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", nil},
@@ -121,6 +121,16 @@ var (
121 {"nanosleep", [5]Arg{{"ts", "*syscall.Timespec"}, {"rem", "*syscall.Timespec"}}, "int32", nil},121 {"nanosleep", [5]Arg{{"ts", "*syscall.Timespec"}, {"rem", "*syscall.Timespec"}}, "int32", nil},
122 {"abort", [5]Arg{}, "", nil},122 {"abort", [5]Arg{}, "", nil},
123 {"sigaltstack", [5]Arg{{"ss", "*stack_t"}, {"old_ss", "*stack_t"}}, "int32", []string{"netbsd"}},123 {"sigaltstack", [5]Arg{{"ss", "*stack_t"}, {"old_ss", "*stack_t"}}, "int32", []string{"netbsd"}},
124+ {"__errno_location", [5]Arg{}, "uintptr", []string{"linux"}},
125+ {"setegid", [5]Arg{{"egid", "uint32"}}, "int32", []string{"linux"}},
126+ {"seteuid", [5]Arg{{"euid", "uint32"}}, "int32", []string{"linux"}},
127+ {"setgid", [5]Arg{{"gid", "uint32"}}, "int32", []string{"linux"}},
128+ {"setregid", [5]Arg{{"rgid", "uint32"}, {"egid", "uint32"}}, "int32", []string{"linux"}},
129+ {"setresgid", [5]Arg{{"rgid", "uint32"}, {"egid", "uint32"}, {"sgid", "uint32"}}, "int32", []string{"linux"}},
130+ {"setresuid", [5]Arg{{"ruid", "uint32"}, {"euid", "uint32"}, {"suid", "uint32"}}, "int32", []string{"linux"}},
131+ {"setreuid", [5]Arg{{"ruid", "uint32"}, {"euid", "uint32"}}, "int32", []string{"linux"}},
132+ {"setuid", [5]Arg{{"uid", "uint32"}}, "int32", []string{"linux"}},
133+ {"setgroups", [5]Arg{{"ngid", "uint32"}, {"gidset", "*uint32"}}, "int32", []string{"linux"}},
124 }134 }
125 pthreadSymbols = []Symbol{135 pthreadSymbols = []Symbol{
126 {"pthread_attr_init", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", nil},136 {"pthread_attr_init", [5]Arg{{"attr", "*pthread_attr_t"}}, "int32", nil},
A internal/fakecgo/linux.go +184 -0
@@ -0,0 +1,184 @@
1+// Copyright 2016 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+//go:build !cgo && linux
6+
7+package fakecgo
8+
9+import "unsafe"
10+
11+// argset matches runtime/cgocall.go:argset.
12+type argset struct {
13+ args *uintptr
14+ retval uintptr
15+}
16+
17+//go:nosplit
18+//go:norace
19+func (a *argset) arg(i int) unsafe.Pointer {
20+ return *(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(a.args), uintptr(i)*unsafe.Sizeof(uintptr(0))))
21+}
22+
23+//go:linkname _cgo_libc_setegid syscall.cgo_libc_setegid
24+//go:linkname _cgo_libc_seteuid syscall.cgo_libc_seteuid
25+//go:linkname _cgo_libc_setgid syscall.cgo_libc_setgid
26+//go:linkname _cgo_libc_setregid syscall.cgo_libc_setregid
27+//go:linkname _cgo_libc_setresgid syscall.cgo_libc_setresgid
28+//go:linkname _cgo_libc_setresuid syscall.cgo_libc_setresuid
29+//go:linkname _cgo_libc_setreuid syscall.cgo_libc_setreuid
30+//go:linkname _cgo_libc_setuid syscall.cgo_libc_setuid
31+//go:linkname _cgo_libc_setgroups syscall.cgo_libc_setgroups
32+
33+var _cgo_libc_setegid = &_cgo_purego_setegid_trampoline
34+var _cgo_libc_seteuid = &_cgo_purego_seteuid_trampoline
35+var _cgo_libc_setgid = &_cgo_purego_setgid_trampoline
36+var _cgo_libc_setregid = &_cgo_purego_setregid_trampoline
37+var _cgo_libc_setresgid = &_cgo_purego_setresgid_trampoline
38+var _cgo_libc_setresuid = &_cgo_purego_setresuid_trampoline
39+var _cgo_libc_setreuid = &_cgo_purego_setreuid_trampoline
40+var _cgo_libc_setuid = &_cgo_purego_setuid_trampoline
41+var _cgo_libc_setgroups = &_cgo_purego_setgroups_trampoline
42+
43+//go:nosplit
44+//go:norace
45+func errno() int32 {
46+ // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer
47+ loc := __errno_location()
48+ return **(**int32)(unsafe.Pointer(&loc))
49+}
50+
51+//go:linkname _cgo_purego_setegid_trampoline _cgo_purego_setegid_trampoline
52+var _cgo_purego_setegid_trampoline byte
53+var x_cgo_purego_setegid_call = x_cgo_purego_setegid
54+
55+//go:nosplit
56+//go:norace
57+func x_cgo_purego_setegid(c *argset) {
58+ ret := setegid(uint32(uintptr(c.arg(0))))
59+ if ret == -1 {
60+ c.retval = uintptr(errno())
61+ } else {
62+ c.retval = uintptr(ret)
63+ }
64+}
65+
66+//go:linkname _cgo_purego_seteuid_trampoline _cgo_purego_seteuid_trampoline
67+var _cgo_purego_seteuid_trampoline byte
68+var x_cgo_purego_seteuid_call = x_cgo_purego_seteuid
69+
70+//go:nosplit
71+//go:norace
72+func x_cgo_purego_seteuid(c *argset) {
73+ ret := seteuid(uint32(uintptr(c.arg(0))))
74+ if ret == -1 {
75+ c.retval = uintptr(errno())
76+ } else {
77+ c.retval = uintptr(ret)
78+ }
79+}
80+
81+//go:linkname _cgo_purego_setgid_trampoline _cgo_purego_setgid_trampoline
82+var _cgo_purego_setgid_trampoline byte
83+var x_cgo_purego_setgid_call = x_cgo_purego_setgid
84+
85+//go:nosplit
86+//go:norace
87+func x_cgo_purego_setgid(c *argset) {
88+ ret := setgid(uint32(uintptr(c.arg(0))))
89+ if ret == -1 {
90+ c.retval = uintptr(errno())
91+ } else {
92+ c.retval = uintptr(ret)
93+ }
94+}
95+
96+//go:linkname _cgo_purego_setregid_trampoline _cgo_purego_setregid_trampoline
97+var _cgo_purego_setregid_trampoline byte
98+var x_cgo_purego_setregid_call = x_cgo_purego_setregid
99+
100+//go:nosplit
101+//go:norace
102+func x_cgo_purego_setregid(c *argset) {
103+ ret := setregid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))))
104+ if ret == -1 {
105+ c.retval = uintptr(errno())
106+ } else {
107+ c.retval = uintptr(ret)
108+ }
109+}
110+
111+//go:linkname _cgo_purego_setresgid_trampoline _cgo_purego_setresgid_trampoline
112+var _cgo_purego_setresgid_trampoline byte
113+var x_cgo_purego_setresgid_call = x_cgo_purego_setresgid
114+
115+//go:nosplit
116+//go:norace
117+func x_cgo_purego_setresgid(c *argset) {
118+ ret := setresgid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))), uint32(uintptr(c.arg(2))))
119+ if ret == -1 {
120+ c.retval = uintptr(errno())
121+ } else {
122+ c.retval = uintptr(ret)
123+ }
124+}
125+
126+//go:linkname _cgo_purego_setresuid_trampoline _cgo_purego_setresuid_trampoline
127+var _cgo_purego_setresuid_trampoline byte
128+var x_cgo_purego_setresuid_call = x_cgo_purego_setresuid
129+
130+//go:nosplit
131+//go:norace
132+func x_cgo_purego_setresuid(c *argset) {
133+ ret := setresuid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))), uint32(uintptr(c.arg(2))))
134+ if ret == -1 {
135+ c.retval = uintptr(errno())
136+ } else {
137+ c.retval = uintptr(ret)
138+ }
139+}
140+
141+//go:linkname _cgo_purego_setreuid_trampoline _cgo_purego_setreuid_trampoline
142+var _cgo_purego_setreuid_trampoline byte
143+var x_cgo_purego_setreuid_call = x_cgo_purego_setreuid
144+
145+//go:nosplit
146+//go:norace
147+func x_cgo_purego_setreuid(c *argset) {
148+ ret := setreuid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))))
149+ if ret == -1 {
150+ c.retval = uintptr(errno())
151+ } else {
152+ c.retval = uintptr(ret)
153+ }
154+}
155+
156+//go:linkname _cgo_purego_setuid_trampoline _cgo_purego_setuid_trampoline
157+var _cgo_purego_setuid_trampoline byte
158+var x_cgo_purego_setuid_call = x_cgo_purego_setuid
159+
160+//go:nosplit
161+//go:norace
162+func x_cgo_purego_setuid(c *argset) {
163+ ret := setuid(uint32(uintptr(c.arg(0))))
164+ if ret == -1 {
165+ c.retval = uintptr(errno())
166+ } else {
167+ c.retval = uintptr(ret)
168+ }
169+}
170+
171+//go:linkname _cgo_purego_setgroups_trampoline _cgo_purego_setgroups_trampoline
172+var _cgo_purego_setgroups_trampoline byte
173+var x_cgo_purego_setgroups_call = x_cgo_purego_setgroups
174+
175+//go:nosplit
176+//go:norace
177+func x_cgo_purego_setgroups(c *argset) {
178+ ret := setgroups(uint32(uintptr(c.arg(0))), (*uint32)(c.arg(1)))
179+ if ret == -1 {
180+ c.retval = uintptr(errno())
181+ } else {
182+ c.retval = uintptr(ret)
183+ }
184+}
@@ -0,0 +1,184 @@
1+// Copyright 2016 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+//go:build !cgo && linux
6+
7+package fakecgo
8+
9+import "unsafe"
10+
11+// argset matches runtime/cgocall.go:argset.
12+type argset struct {
13+ args *uintptr
14+ retval uintptr
15+}
16+
17+//go:nosplit
18+//go:norace
19+func (a *argset) arg(i int) unsafe.Pointer {
20+ return *(*unsafe.Pointer)(unsafe.Add(unsafe.Pointer(a.args), uintptr(i)*unsafe.Sizeof(uintptr(0))))
21+}
22+
23+//go:linkname _cgo_libc_setegid syscall.cgo_libc_setegid
24+//go:linkname _cgo_libc_seteuid syscall.cgo_libc_seteuid
25+//go:linkname _cgo_libc_setgid syscall.cgo_libc_setgid
26+//go:linkname _cgo_libc_setregid syscall.cgo_libc_setregid
27+//go:linkname _cgo_libc_setresgid syscall.cgo_libc_setresgid
28+//go:linkname _cgo_libc_setresuid syscall.cgo_libc_setresuid
29+//go:linkname _cgo_libc_setreuid syscall.cgo_libc_setreuid
30+//go:linkname _cgo_libc_setuid syscall.cgo_libc_setuid
31+//go:linkname _cgo_libc_setgroups syscall.cgo_libc_setgroups
32+
33+var _cgo_libc_setegid = &_cgo_purego_setegid_trampoline
34+var _cgo_libc_seteuid = &_cgo_purego_seteuid_trampoline
35+var _cgo_libc_setgid = &_cgo_purego_setgid_trampoline
36+var _cgo_libc_setregid = &_cgo_purego_setregid_trampoline
37+var _cgo_libc_setresgid = &_cgo_purego_setresgid_trampoline
38+var _cgo_libc_setresuid = &_cgo_purego_setresuid_trampoline
39+var _cgo_libc_setreuid = &_cgo_purego_setreuid_trampoline
40+var _cgo_libc_setuid = &_cgo_purego_setuid_trampoline
41+var _cgo_libc_setgroups = &_cgo_purego_setgroups_trampoline
42+
43+//go:nosplit
44+//go:norace
45+func errno() int32 {
46+ // this indirection is to avoid go vet complaining about possible misuse of unsafe.Pointer
47+ loc := __errno_location()
48+ return **(**int32)(unsafe.Pointer(&loc))
49+}
50+
51+//go:linkname _cgo_purego_setegid_trampoline _cgo_purego_setegid_trampoline
52+var _cgo_purego_setegid_trampoline byte
53+var x_cgo_purego_setegid_call = x_cgo_purego_setegid
54+
55+//go:nosplit
56+//go:norace
57+func x_cgo_purego_setegid(c *argset) {
58+ ret := setegid(uint32(uintptr(c.arg(0))))
59+ if ret == -1 {
60+ c.retval = uintptr(errno())
61+ } else {
62+ c.retval = uintptr(ret)
63+ }
64+}
65+
66+//go:linkname _cgo_purego_seteuid_trampoline _cgo_purego_seteuid_trampoline
67+var _cgo_purego_seteuid_trampoline byte
68+var x_cgo_purego_seteuid_call = x_cgo_purego_seteuid
69+
70+//go:nosplit
71+//go:norace
72+func x_cgo_purego_seteuid(c *argset) {
73+ ret := seteuid(uint32(uintptr(c.arg(0))))
74+ if ret == -1 {
75+ c.retval = uintptr(errno())
76+ } else {
77+ c.retval = uintptr(ret)
78+ }
79+}
80+
81+//go:linkname _cgo_purego_setgid_trampoline _cgo_purego_setgid_trampoline
82+var _cgo_purego_setgid_trampoline byte
83+var x_cgo_purego_setgid_call = x_cgo_purego_setgid
84+
85+//go:nosplit
86+//go:norace
87+func x_cgo_purego_setgid(c *argset) {
88+ ret := setgid(uint32(uintptr(c.arg(0))))
89+ if ret == -1 {
90+ c.retval = uintptr(errno())
91+ } else {
92+ c.retval = uintptr(ret)
93+ }
94+}
95+
96+//go:linkname _cgo_purego_setregid_trampoline _cgo_purego_setregid_trampoline
97+var _cgo_purego_setregid_trampoline byte
98+var x_cgo_purego_setregid_call = x_cgo_purego_setregid
99+
100+//go:nosplit
101+//go:norace
102+func x_cgo_purego_setregid(c *argset) {
103+ ret := setregid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))))
104+ if ret == -1 {
105+ c.retval = uintptr(errno())
106+ } else {
107+ c.retval = uintptr(ret)
108+ }
109+}
110+
111+//go:linkname _cgo_purego_setresgid_trampoline _cgo_purego_setresgid_trampoline
112+var _cgo_purego_setresgid_trampoline byte
113+var x_cgo_purego_setresgid_call = x_cgo_purego_setresgid
114+
115+//go:nosplit
116+//go:norace
117+func x_cgo_purego_setresgid(c *argset) {
118+ ret := setresgid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))), uint32(uintptr(c.arg(2))))
119+ if ret == -1 {
120+ c.retval = uintptr(errno())
121+ } else {
122+ c.retval = uintptr(ret)
123+ }
124+}
125+
126+//go:linkname _cgo_purego_setresuid_trampoline _cgo_purego_setresuid_trampoline
127+var _cgo_purego_setresuid_trampoline byte
128+var x_cgo_purego_setresuid_call = x_cgo_purego_setresuid
129+
130+//go:nosplit
131+//go:norace
132+func x_cgo_purego_setresuid(c *argset) {
133+ ret := setresuid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))), uint32(uintptr(c.arg(2))))
134+ if ret == -1 {
135+ c.retval = uintptr(errno())
136+ } else {
137+ c.retval = uintptr(ret)
138+ }
139+}
140+
141+//go:linkname _cgo_purego_setreuid_trampoline _cgo_purego_setreuid_trampoline
142+var _cgo_purego_setreuid_trampoline byte
143+var x_cgo_purego_setreuid_call = x_cgo_purego_setreuid
144+
145+//go:nosplit
146+//go:norace
147+func x_cgo_purego_setreuid(c *argset) {
148+ ret := setreuid(uint32(uintptr(c.arg(0))), uint32(uintptr(c.arg(1))))
149+ if ret == -1 {
150+ c.retval = uintptr(errno())
151+ } else {
152+ c.retval = uintptr(ret)
153+ }
154+}
155+
156+//go:linkname _cgo_purego_setuid_trampoline _cgo_purego_setuid_trampoline
157+var _cgo_purego_setuid_trampoline byte
158+var x_cgo_purego_setuid_call = x_cgo_purego_setuid
159+
160+//go:nosplit
161+//go:norace
162+func x_cgo_purego_setuid(c *argset) {
163+ ret := setuid(uint32(uintptr(c.arg(0))))
164+ if ret == -1 {
165+ c.retval = uintptr(errno())
166+ } else {
167+ c.retval = uintptr(ret)
168+ }
169+}
170+
171+//go:linkname _cgo_purego_setgroups_trampoline _cgo_purego_setgroups_trampoline
172+var _cgo_purego_setgroups_trampoline byte
173+var x_cgo_purego_setgroups_call = x_cgo_purego_setgroups
174+
175+//go:nosplit
176+//go:norace
177+func x_cgo_purego_setgroups(c *argset) {
178+ ret := setgroups(uint32(uintptr(c.arg(0))), (*uint32)(c.arg(1)))
179+ if ret == -1 {
180+ c.retval = uintptr(errno())
181+ } else {
182+ c.retval = uintptr(ret)
183+ }
184+}
M internal/fakecgo/trampolines_386.s +18 -4
@@ -80,15 +80,29 @@ TEXT ·setg_trampoline(SB), NOSPLIT, $4-8
8080 CALL BX
8181 RET
8282
83-TEXT threadentry_trampoline(SB), NOSPLIT, $4-0
84- MOVL 8(SP), AX // first C arg
85- MOVL AX, 0(SP) // Go arg 1
83+TEXT threadentry_trampoline(SB), NOSPLIT, $24-4
84+ // Save callee-saved registers
85+ MOVL BP, 20(SP)
86+ MOVL BX, 16(SP)
87+ MOVL SI, 12(SP)
88+ MOVL DI, 8(SP)
89+
90+ // Move C argument (arg) to stack for Go function
91+ MOVL arg+0(FP), AX
92+ MOVL AX, 0(SP)
93+
8694 MOVL ·threadentry_call(SB), CX
8795 MOVL (CX), CX
8896 CALL CX
97+
98+ // Restore callee-saved registers
99+ MOVL 8(SP), DI
100+ MOVL 12(SP), SI
101+ MOVL 16(SP), BX
102+ MOVL 20(SP), BP
89103 RET
90104
91-TEXT ·call5(SB), NOSPLIT, $20-28
105+TEXT ·call5(SB), NOSPLIT, $24-28
92106 MOVL fn+0(FP), AX
93107 MOVL a1+4(FP), BX
94108 MOVL a2+8(FP), CX
@@ -80,15 +80,29 @@ TEXT ·setg_trampoline(SB), NOSPLIT, $4-8
80 CALL BX80 CALL BX
81 RET81 RET
82 82
83-TEXT threadentry_trampoline(SB), NOSPLIT, $4-083+TEXT threadentry_trampoline(SB), NOSPLIT, $24-4
84- MOVL 8(SP), AX // first C arg84+ // Save callee-saved registers
85- MOVL AX, 0(SP) // Go arg 185+ MOVL BP, 20(SP)
86+ MOVL BX, 16(SP)
87+ MOVL SI, 12(SP)
88+ MOVL DI, 8(SP)
89+
90+ // Move C argument (arg) to stack for Go function
91+ MOVL arg+0(FP), AX
92+ MOVL AX, 0(SP)
93+
86 MOVL ·threadentry_call(SB), CX94 MOVL ·threadentry_call(SB), CX
87 MOVL (CX), CX95 MOVL (CX), CX
88 CALL CX96 CALL CX
97+
98+ // Restore callee-saved registers
99+ MOVL 8(SP), DI
100+ MOVL 12(SP), SI
101+ MOVL 16(SP), BX
102+ MOVL 20(SP), BP
89 RET103 RET
90 104
91-TEXT ·call5(SB), NOSPLIT, $20-28105+TEXT ·call5(SB), NOSPLIT, $24-28
92 MOVL fn+0(FP), AX106 MOVL fn+0(FP), AX
93 MOVL a1+4(FP), BX107 MOVL a1+4(FP), BX
94 MOVL a2+8(FP), CX108 MOVL a2+8(FP), CX
M internal/fakecgo/trampolines_arm.s +44 -3
@@ -54,12 +54,53 @@ TEXT ·setg_trampoline(SB), NOSPLIT, $0-8
5454 BL (R12)
5555 RET
5656
57-TEXT threadentry_trampoline(SB), NOSPLIT, $8-0
58- // See crosscall2.
59- MOVW R0, 4(R13)
57+TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
58+ // Save C callee-saved registers at C-to-Go boundary.
59+ // See crosscall2 in asm_arm.s.
60+ // ARM AAPCS callee-saved: R4-R11 (includes g=R10), D8-D15.
61+ // LR is saved/restored by the Go-managed frame prologue/epilogue.
62+ MOVW R0, 4(R13) // arg for threadentry_call
63+
64+ MOVW R4, 8(R13)
65+ MOVW R5, 12(R13)
66+ MOVW R6, 16(R13)
67+ MOVW R7, 20(R13)
68+ MOVW R8, 24(R13)
69+ MOVW R9, 28(R13)
70+ MOVW g, 32(R13) // R10
71+ MOVW R11, 36(R13)
72+
73+ MOVD F8, 40(R13)
74+ MOVD F9, 48(R13)
75+ MOVD F10, 56(R13)
76+ MOVD F11, 64(R13)
77+ MOVD F12, 72(R13)
78+ MOVD F13, 80(R13)
79+ MOVD F14, 88(R13)
80+ MOVD F15, 96(R13)
81+
6082 MOVW ·threadentry_call(SB), R12
6183 MOVW (R12), R12
6284 CALL (R12)
85+
86+ MOVD 40(R13), F8
87+ MOVD 48(R13), F9
88+ MOVD 56(R13), F10
89+ MOVD 64(R13), F11
90+ MOVD 72(R13), F12
91+ MOVD 80(R13), F13
92+ MOVD 88(R13), F14
93+ MOVD 96(R13), F15
94+
95+ MOVW 8(R13), R4
96+ MOVW 12(R13), R5
97+ MOVW 16(R13), R6
98+ MOVW 20(R13), R7
99+ MOVW 24(R13), R8
100+ MOVW 28(R13), R9
101+ MOVW 32(R13), g
102+ MOVW 36(R13), R11
103+
63104 RET
64105
65106 TEXT ·call5(SB), NOSPLIT, $8-28
@@ -54,12 +54,53 @@ TEXT ·setg_trampoline(SB), NOSPLIT, $0-8
54 BL (R12)54 BL (R12)
55 RET55 RET
56 56
57-TEXT threadentry_trampoline(SB), NOSPLIT, $8-057+TEXT threadentry_trampoline(SB), NOSPLIT, $104-0
58- // See crosscall2.58+ // Save C callee-saved registers at C-to-Go boundary.
59- MOVW R0, 4(R13)59+ // See crosscall2 in asm_arm.s.
60+ // ARM AAPCS callee-saved: R4-R11 (includes g=R10), D8-D15.
61+ // LR is saved/restored by the Go-managed frame prologue/epilogue.
62+ MOVW R0, 4(R13) // arg for threadentry_call
63+
64+ MOVW R4, 8(R13)
65+ MOVW R5, 12(R13)
66+ MOVW R6, 16(R13)
67+ MOVW R7, 20(R13)
68+ MOVW R8, 24(R13)
69+ MOVW R9, 28(R13)
70+ MOVW g, 32(R13) // R10
71+ MOVW R11, 36(R13)
72+
73+ MOVD F8, 40(R13)
74+ MOVD F9, 48(R13)
75+ MOVD F10, 56(R13)
76+ MOVD F11, 64(R13)
77+ MOVD F12, 72(R13)
78+ MOVD F13, 80(R13)
79+ MOVD F14, 88(R13)
80+ MOVD F15, 96(R13)
81+
60 MOVW ·threadentry_call(SB), R1282 MOVW ·threadentry_call(SB), R12
61 MOVW (R12), R1283 MOVW (R12), R12
62 CALL (R12)84 CALL (R12)
85+
86+ MOVD 40(R13), F8
87+ MOVD 48(R13), F9
88+ MOVD 56(R13), F10
89+ MOVD 64(R13), F11
90+ MOVD 72(R13), F12
91+ MOVD 80(R13), F13
92+ MOVD 88(R13), F14
93+ MOVD 96(R13), F15
94+
95+ MOVW 8(R13), R4
96+ MOVW 12(R13), R5
97+ MOVW 16(R13), R6
98+ MOVW 20(R13), R7
99+ MOVW 24(R13), R8
100+ MOVW 28(R13), R9
101+ MOVW 32(R13), g
102+ MOVW 36(R13), R11
103+
63 RET104 RET
64 105
65 TEXT ·call5(SB), NOSPLIT, $8-28106 TEXT ·call5(SB), NOSPLIT, $8-28
M internal/fakecgo/trampolines_arm64.s +0 -3
@@ -58,7 +58,6 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $0-0
5858
5959 SAVE_R19_TO_R28(8*4)
6060 SAVE_F8_TO_F15(8*14)
61- STP (R29, R30), (8*22)(RSP)
6261
6362 MOVD ·threadentry_call(SB), R9
6463 MOVD (R9), R9
@@ -67,8 +66,6 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $0-0
6766
6867 RESTORE_R19_TO_R28(8*4)
6968 RESTORE_F8_TO_F15(8*14)
70- LDP (8*22)(RSP), (R29, R30)
71-
7269 ADD $(8*24), RSP
7370 RET
7471
@@ -58,7 +58,6 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $0-0
58 58
59 SAVE_R19_TO_R28(8*4)59 SAVE_R19_TO_R28(8*4)
60 SAVE_F8_TO_F15(8*14)60 SAVE_F8_TO_F15(8*14)
61- STP (R29, R30), (8*22)(RSP)
62 61
63 MOVD ·threadentry_call(SB), R962 MOVD ·threadentry_call(SB), R9
64 MOVD (R9), R963 MOVD (R9), R9
@@ -67,8 +66,6 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $0-0
67 66
68 RESTORE_R19_TO_R28(8*4)67 RESTORE_R19_TO_R28(8*4)
69 RESTORE_F8_TO_F15(8*14)68 RESTORE_F8_TO_F15(8*14)
70- LDP (8*22)(RSP), (R29, R30)
71-
72 ADD $(8*24), RSP69 ADD $(8*24), RSP
73 RET70 RET
74 71
A internal/fakecgo/trampolines_linux_386.s +78 -0
@@ -0,0 +1,78 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $4-0
9+ MOVL 8(SP), AX
10+ MOVL AX, 0(SP)
11+ MOVL ·x_cgo_purego_setegid_call(SB), CX
12+ MOVL (CX), CX
13+ CALL CX
14+ RET
15+
16+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $4-0
17+ MOVL 8(SP), AX
18+ MOVL AX, 0(SP)
19+ MOVL ·x_cgo_purego_seteuid_call(SB), CX
20+ MOVL (CX), CX
21+ CALL CX
22+ RET
23+
24+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $4-0
25+ MOVL 8(SP), AX
26+ MOVL AX, 0(SP)
27+ MOVL ·x_cgo_purego_setgid_call(SB), CX
28+ MOVL (CX), CX
29+ CALL CX
30+ RET
31+
32+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $4-0
33+ MOVL 8(SP), AX
34+ MOVL AX, 0(SP)
35+ MOVL ·x_cgo_purego_setregid_call(SB), CX
36+ MOVL (CX), CX
37+ CALL CX
38+ RET
39+
40+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $4-0
41+ MOVL 8(SP), AX
42+ MOVL AX, 0(SP)
43+ MOVL ·x_cgo_purego_setresgid_call(SB), CX
44+ MOVL (CX), CX
45+ CALL CX
46+ RET
47+
48+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $4-0
49+ MOVL 8(SP), AX
50+ MOVL AX, 0(SP)
51+ MOVL ·x_cgo_purego_setresuid_call(SB), CX
52+ MOVL (CX), CX
53+ CALL CX
54+ RET
55+
56+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $4-0
57+ MOVL 8(SP), AX
58+ MOVL AX, 0(SP)
59+ MOVL ·x_cgo_purego_setreuid_call(SB), CX
60+ MOVL (CX), CX
61+ CALL CX
62+ RET
63+
64+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $4-0
65+ MOVL 8(SP), AX
66+ MOVL AX, 0(SP)
67+ MOVL ·x_cgo_purego_setuid_call(SB), CX
68+ MOVL (CX), CX
69+ CALL CX
70+ RET
71+
72+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $4-0
73+ MOVL 8(SP), AX
74+ MOVL AX, 0(SP)
75+ MOVL ·x_cgo_purego_setgroups_call(SB), CX
76+ MOVL (CX), CX
77+ CALL CX
78+ RET
@@ -0,0 +1,78 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $4-0
9+ MOVL 8(SP), AX
10+ MOVL AX, 0(SP)
11+ MOVL ·x_cgo_purego_setegid_call(SB), CX
12+ MOVL (CX), CX
13+ CALL CX
14+ RET
15+
16+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $4-0
17+ MOVL 8(SP), AX
18+ MOVL AX, 0(SP)
19+ MOVL ·x_cgo_purego_seteuid_call(SB), CX
20+ MOVL (CX), CX
21+ CALL CX
22+ RET
23+
24+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $4-0
25+ MOVL 8(SP), AX
26+ MOVL AX, 0(SP)
27+ MOVL ·x_cgo_purego_setgid_call(SB), CX
28+ MOVL (CX), CX
29+ CALL CX
30+ RET
31+
32+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $4-0
33+ MOVL 8(SP), AX
34+ MOVL AX, 0(SP)
35+ MOVL ·x_cgo_purego_setregid_call(SB), CX
36+ MOVL (CX), CX
37+ CALL CX
38+ RET
39+
40+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $4-0
41+ MOVL 8(SP), AX
42+ MOVL AX, 0(SP)
43+ MOVL ·x_cgo_purego_setresgid_call(SB), CX
44+ MOVL (CX), CX
45+ CALL CX
46+ RET
47+
48+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $4-0
49+ MOVL 8(SP), AX
50+ MOVL AX, 0(SP)
51+ MOVL ·x_cgo_purego_setresuid_call(SB), CX
52+ MOVL (CX), CX
53+ CALL CX
54+ RET
55+
56+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $4-0
57+ MOVL 8(SP), AX
58+ MOVL AX, 0(SP)
59+ MOVL ·x_cgo_purego_setreuid_call(SB), CX
60+ MOVL (CX), CX
61+ CALL CX
62+ RET
63+
64+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $4-0
65+ MOVL 8(SP), AX
66+ MOVL AX, 0(SP)
67+ MOVL ·x_cgo_purego_setuid_call(SB), CX
68+ MOVL (CX), CX
69+ CALL CX
70+ RET
71+
72+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $4-0
73+ MOVL 8(SP), AX
74+ MOVL AX, 0(SP)
75+ MOVL ·x_cgo_purego_setgroups_call(SB), CX
76+ MOVL (CX), CX
77+ CALL CX
78+ RET
A internal/fakecgo/trampolines_linux_amd64.s +69 -0
@@ -0,0 +1,69 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8
9+ MOVQ DI, AX
10+ MOVQ ·x_cgo_purego_setegid_call(SB), R11
11+ MOVQ (R11), R11
12+ CALL R11
13+ RET
14+
15+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8
16+ MOVQ DI, AX
17+ MOVQ ·x_cgo_purego_seteuid_call(SB), R11
18+ MOVQ (R11), R11
19+ CALL R11
20+ RET
21+
22+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8
23+ MOVQ DI, AX
24+ MOVQ ·x_cgo_purego_setgid_call(SB), R11
25+ MOVQ (R11), R11
26+ CALL R11
27+ RET
28+
29+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8
30+ MOVQ DI, AX
31+ MOVQ ·x_cgo_purego_setregid_call(SB), R11
32+ MOVQ (R11), R11
33+ CALL R11
34+ RET
35+
36+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8
37+ MOVQ DI, AX
38+ MOVQ ·x_cgo_purego_setresgid_call(SB), R11
39+ MOVQ (R11), R11
40+ CALL R11
41+ RET
42+
43+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8
44+ MOVQ DI, AX
45+ MOVQ ·x_cgo_purego_setresuid_call(SB), R11
46+ MOVQ (R11), R11
47+ CALL R11
48+ RET
49+
50+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8
51+ MOVQ DI, AX
52+ MOVQ ·x_cgo_purego_setreuid_call(SB), R11
53+ MOVQ (R11), R11
54+ CALL R11
55+ RET
56+
57+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8
58+ MOVQ DI, AX
59+ MOVQ ·x_cgo_purego_setuid_call(SB), R11
60+ MOVQ (R11), R11
61+ CALL R11
62+ RET
63+
64+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8
65+ MOVQ DI, AX
66+ MOVQ ·x_cgo_purego_setgroups_call(SB), R11
67+ MOVQ (R11), R11
68+ CALL R11
69+ RET
@@ -0,0 +1,69 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8
9+ MOVQ DI, AX
10+ MOVQ ·x_cgo_purego_setegid_call(SB), R11
11+ MOVQ (R11), R11
12+ CALL R11
13+ RET
14+
15+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8
16+ MOVQ DI, AX
17+ MOVQ ·x_cgo_purego_seteuid_call(SB), R11
18+ MOVQ (R11), R11
19+ CALL R11
20+ RET
21+
22+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8
23+ MOVQ DI, AX
24+ MOVQ ·x_cgo_purego_setgid_call(SB), R11
25+ MOVQ (R11), R11
26+ CALL R11
27+ RET
28+
29+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8
30+ MOVQ DI, AX
31+ MOVQ ·x_cgo_purego_setregid_call(SB), R11
32+ MOVQ (R11), R11
33+ CALL R11
34+ RET
35+
36+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8
37+ MOVQ DI, AX
38+ MOVQ ·x_cgo_purego_setresgid_call(SB), R11
39+ MOVQ (R11), R11
40+ CALL R11
41+ RET
42+
43+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8
44+ MOVQ DI, AX
45+ MOVQ ·x_cgo_purego_setresuid_call(SB), R11
46+ MOVQ (R11), R11
47+ CALL R11
48+ RET
49+
50+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8
51+ MOVQ DI, AX
52+ MOVQ ·x_cgo_purego_setreuid_call(SB), R11
53+ MOVQ (R11), R11
54+ CALL R11
55+ RET
56+
57+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8
58+ MOVQ DI, AX
59+ MOVQ ·x_cgo_purego_setuid_call(SB), R11
60+ MOVQ (R11), R11
61+ CALL R11
62+ RET
63+
64+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8
65+ MOVQ DI, AX
66+ MOVQ ·x_cgo_purego_setgroups_call(SB), R11
67+ MOVQ (R11), R11
68+ CALL R11
69+ RET
A internal/fakecgo/trampolines_linux_arm.s +69 -0
@@ -0,0 +1,69 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8-0
9+ MOVW R0, 4(R13)
10+ MOVW ·x_cgo_purego_setegid_call(SB), R12
11+ MOVW (R12), R12
12+ CALL (R12)
13+ RET
14+
15+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8-0
16+ MOVW R0, 4(R13)
17+ MOVW ·x_cgo_purego_seteuid_call(SB), R12
18+ MOVW (R12), R12
19+ CALL (R12)
20+ RET
21+
22+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8-0
23+ MOVW R0, 4(R13)
24+ MOVW ·x_cgo_purego_setgid_call(SB), R12
25+ MOVW (R12), R12
26+ CALL (R12)
27+ RET
28+
29+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8-0
30+ MOVW R0, 4(R13)
31+ MOVW ·x_cgo_purego_setregid_call(SB), R12
32+ MOVW (R12), R12
33+ CALL (R12)
34+ RET
35+
36+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8-0
37+ MOVW R0, 4(R13)
38+ MOVW ·x_cgo_purego_setresgid_call(SB), R12
39+ MOVW (R12), R12
40+ CALL (R12)
41+ RET
42+
43+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8-0
44+ MOVW R0, 4(R13)
45+ MOVW ·x_cgo_purego_setresuid_call(SB), R12
46+ MOVW (R12), R12
47+ CALL (R12)
48+ RET
49+
50+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8-0
51+ MOVW R0, 4(R13)
52+ MOVW ·x_cgo_purego_setreuid_call(SB), R12
53+ MOVW (R12), R12
54+ CALL (R12)
55+ RET
56+
57+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8-0
58+ MOVW R0, 4(R13)
59+ MOVW ·x_cgo_purego_setuid_call(SB), R12
60+ MOVW (R12), R12
61+ CALL (R12)
62+ RET
63+
64+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8-0
65+ MOVW R0, 4(R13)
66+ MOVW ·x_cgo_purego_setgroups_call(SB), R12
67+ MOVW (R12), R12
68+ CALL (R12)
69+ RET
@@ -0,0 +1,69 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8-0
9+ MOVW R0, 4(R13)
10+ MOVW ·x_cgo_purego_setegid_call(SB), R12
11+ MOVW (R12), R12
12+ CALL (R12)
13+ RET
14+
15+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8-0
16+ MOVW R0, 4(R13)
17+ MOVW ·x_cgo_purego_seteuid_call(SB), R12
18+ MOVW (R12), R12
19+ CALL (R12)
20+ RET
21+
22+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8-0
23+ MOVW R0, 4(R13)
24+ MOVW ·x_cgo_purego_setgid_call(SB), R12
25+ MOVW (R12), R12
26+ CALL (R12)
27+ RET
28+
29+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8-0
30+ MOVW R0, 4(R13)
31+ MOVW ·x_cgo_purego_setregid_call(SB), R12
32+ MOVW (R12), R12
33+ CALL (R12)
34+ RET
35+
36+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8-0
37+ MOVW R0, 4(R13)
38+ MOVW ·x_cgo_purego_setresgid_call(SB), R12
39+ MOVW (R12), R12
40+ CALL (R12)
41+ RET
42+
43+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8-0
44+ MOVW R0, 4(R13)
45+ MOVW ·x_cgo_purego_setresuid_call(SB), R12
46+ MOVW (R12), R12
47+ CALL (R12)
48+ RET
49+
50+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8-0
51+ MOVW R0, 4(R13)
52+ MOVW ·x_cgo_purego_setreuid_call(SB), R12
53+ MOVW (R12), R12
54+ CALL (R12)
55+ RET
56+
57+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8-0
58+ MOVW R0, 4(R13)
59+ MOVW ·x_cgo_purego_setuid_call(SB), R12
60+ MOVW (R12), R12
61+ CALL (R12)
62+ RET
63+
64+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8-0
65+ MOVW R0, 4(R13)
66+ MOVW ·x_cgo_purego_setgroups_call(SB), R12
67+ MOVW (R12), R12
68+ CALL (R12)
69+ RET
A internal/fakecgo/trampolines_linux_arm64.s +60 -0
@@ -0,0 +1,60 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $0-0
9+ MOVD ·x_cgo_purego_setegid_call(SB), R9
10+ MOVD (R9), R9
11+ CALL R9
12+ RET
13+
14+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $0-0
15+ MOVD ·x_cgo_purego_seteuid_call(SB), R9
16+ MOVD (R9), R9
17+ CALL R9
18+ RET
19+
20+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $0-0
21+ MOVD ·x_cgo_purego_setgid_call(SB), R9
22+ MOVD (R9), R9
23+ CALL R9
24+ RET
25+
26+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $0-0
27+ MOVD ·x_cgo_purego_setregid_call(SB), R9
28+ MOVD (R9), R9
29+ CALL R9
30+ RET
31+
32+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $0-0
33+ MOVD ·x_cgo_purego_setresgid_call(SB), R9
34+ MOVD (R9), R9
35+ CALL R9
36+ RET
37+
38+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $0-0
39+ MOVD ·x_cgo_purego_setresuid_call(SB), R9
40+ MOVD (R9), R9
41+ CALL R9
42+ RET
43+
44+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $0-0
45+ MOVD ·x_cgo_purego_setreuid_call(SB), R9
46+ MOVD (R9), R9
47+ CALL R9
48+ RET
49+
50+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $0-0
51+ MOVD ·x_cgo_purego_setuid_call(SB), R9
52+ MOVD (R9), R9
53+ CALL R9
54+ RET
55+
56+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $0-0
57+ MOVD ·x_cgo_purego_setgroups_call(SB), R9
58+ MOVD (R9), R9
59+ CALL R9
60+ RET
@@ -0,0 +1,60 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $0-0
9+ MOVD ·x_cgo_purego_setegid_call(SB), R9
10+ MOVD (R9), R9
11+ CALL R9
12+ RET
13+
14+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $0-0
15+ MOVD ·x_cgo_purego_seteuid_call(SB), R9
16+ MOVD (R9), R9
17+ CALL R9
18+ RET
19+
20+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $0-0
21+ MOVD ·x_cgo_purego_setgid_call(SB), R9
22+ MOVD (R9), R9
23+ CALL R9
24+ RET
25+
26+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $0-0
27+ MOVD ·x_cgo_purego_setregid_call(SB), R9
28+ MOVD (R9), R9
29+ CALL R9
30+ RET
31+
32+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $0-0
33+ MOVD ·x_cgo_purego_setresgid_call(SB), R9
34+ MOVD (R9), R9
35+ CALL R9
36+ RET
37+
38+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $0-0
39+ MOVD ·x_cgo_purego_setresuid_call(SB), R9
40+ MOVD (R9), R9
41+ CALL R9
42+ RET
43+
44+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $0-0
45+ MOVD ·x_cgo_purego_setreuid_call(SB), R9
46+ MOVD (R9), R9
47+ CALL R9
48+ RET
49+
50+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $0-0
51+ MOVD ·x_cgo_purego_setuid_call(SB), R9
52+ MOVD (R9), R9
53+ CALL R9
54+ RET
55+
56+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $0-0
57+ MOVD ·x_cgo_purego_setgroups_call(SB), R9
58+ MOVD (R9), R9
59+ CALL R9
60+ RET
A internal/fakecgo/trampolines_linux_loong64.s +60 -0
@@ -0,0 +1,60 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8
9+ MOVV ·x_cgo_purego_setegid_call(SB), R23
10+ MOVV (R23), R23
11+ CALL (R23)
12+ RET
13+
14+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8
15+ MOVV ·x_cgo_purego_seteuid_call(SB), R23
16+ MOVV (R23), R23
17+ CALL (R23)
18+ RET
19+
20+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8
21+ MOVV ·x_cgo_purego_setgid_call(SB), R23
22+ MOVV (R23), R23
23+ CALL (R23)
24+ RET
25+
26+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8
27+ MOVV ·x_cgo_purego_setregid_call(SB), R23
28+ MOVV (R23), R23
29+ CALL (R23)
30+ RET
31+
32+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8
33+ MOVV ·x_cgo_purego_setresgid_call(SB), R23
34+ MOVV (R23), R23
35+ CALL (R23)
36+ RET
37+
38+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8
39+ MOVV ·x_cgo_purego_setresuid_call(SB), R23
40+ MOVV (R23), R23
41+ CALL (R23)
42+ RET
43+
44+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8
45+ MOVV ·x_cgo_purego_setreuid_call(SB), R23
46+ MOVV (R23), R23
47+ CALL (R23)
48+ RET
49+
50+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8
51+ MOVV ·x_cgo_purego_setuid_call(SB), R23
52+ MOVV (R23), R23
53+ CALL (R23)
54+ RET
55+
56+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8
57+ MOVV ·x_cgo_purego_setgroups_call(SB), R23
58+ MOVV (R23), R23
59+ CALL (R23)
60+ RET
@@ -0,0 +1,60 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8
9+ MOVV ·x_cgo_purego_setegid_call(SB), R23
10+ MOVV (R23), R23
11+ CALL (R23)
12+ RET
13+
14+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8
15+ MOVV ·x_cgo_purego_seteuid_call(SB), R23
16+ MOVV (R23), R23
17+ CALL (R23)
18+ RET
19+
20+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8
21+ MOVV ·x_cgo_purego_setgid_call(SB), R23
22+ MOVV (R23), R23
23+ CALL (R23)
24+ RET
25+
26+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8
27+ MOVV ·x_cgo_purego_setregid_call(SB), R23
28+ MOVV (R23), R23
29+ CALL (R23)
30+ RET
31+
32+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8
33+ MOVV ·x_cgo_purego_setresgid_call(SB), R23
34+ MOVV (R23), R23
35+ CALL (R23)
36+ RET
37+
38+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8
39+ MOVV ·x_cgo_purego_setresuid_call(SB), R23
40+ MOVV (R23), R23
41+ CALL (R23)
42+ RET
43+
44+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8
45+ MOVV ·x_cgo_purego_setreuid_call(SB), R23
46+ MOVV (R23), R23
47+ CALL (R23)
48+ RET
49+
50+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8
51+ MOVV ·x_cgo_purego_setuid_call(SB), R23
52+ MOVV (R23), R23
53+ CALL (R23)
54+ RET
55+
56+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8
57+ MOVV ·x_cgo_purego_setgroups_call(SB), R23
58+ MOVV (R23), R23
59+ CALL (R23)
60+ RET
A internal/fakecgo/trampolines_linux_ppc64le.s +69 -0
@@ -0,0 +1,69 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8-0
9+ MOVD ·x_cgo_purego_setegid_call(SB), R12
10+ MOVD (R12), R12
11+ MOVD R12, CTR
12+ CALL CTR
13+ RET
14+
15+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8-0
16+ MOVD ·x_cgo_purego_seteuid_call(SB), R12
17+ MOVD (R12), R12
18+ MOVD R12, CTR
19+ CALL CTR
20+ RET
21+
22+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8-0
23+ MOVD ·x_cgo_purego_setgid_call(SB), R12
24+ MOVD (R12), R12
25+ MOVD R12, CTR
26+ CALL CTR
27+ RET
28+
29+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8-0
30+ MOVD ·x_cgo_purego_setregid_call(SB), R12
31+ MOVD (R12), R12
32+ MOVD R12, CTR
33+ CALL CTR
34+ RET
35+
36+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8-0
37+ MOVD ·x_cgo_purego_setresgid_call(SB), R12
38+ MOVD (R12), R12
39+ MOVD R12, CTR
40+ CALL CTR
41+ RET
42+
43+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8-0
44+ MOVD ·x_cgo_purego_setresuid_call(SB), R12
45+ MOVD (R12), R12
46+ MOVD R12, CTR
47+ CALL CTR
48+ RET
49+
50+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8-0
51+ MOVD ·x_cgo_purego_setreuid_call(SB), R12
52+ MOVD (R12), R12
53+ MOVD R12, CTR
54+ CALL CTR
55+ RET
56+
57+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8-0
58+ MOVD ·x_cgo_purego_setuid_call(SB), R12
59+ MOVD (R12), R12
60+ MOVD R12, CTR
61+ CALL CTR
62+ RET
63+
64+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8-0
65+ MOVD ·x_cgo_purego_setgroups_call(SB), R12
66+ MOVD (R12), R12
67+ MOVD R12, CTR
68+ CALL CTR
69+ RET
@@ -0,0 +1,69 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8-0
9+ MOVD ·x_cgo_purego_setegid_call(SB), R12
10+ MOVD (R12), R12
11+ MOVD R12, CTR
12+ CALL CTR
13+ RET
14+
15+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8-0
16+ MOVD ·x_cgo_purego_seteuid_call(SB), R12
17+ MOVD (R12), R12
18+ MOVD R12, CTR
19+ CALL CTR
20+ RET
21+
22+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8-0
23+ MOVD ·x_cgo_purego_setgid_call(SB), R12
24+ MOVD (R12), R12
25+ MOVD R12, CTR
26+ CALL CTR
27+ RET
28+
29+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8-0
30+ MOVD ·x_cgo_purego_setregid_call(SB), R12
31+ MOVD (R12), R12
32+ MOVD R12, CTR
33+ CALL CTR
34+ RET
35+
36+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8-0
37+ MOVD ·x_cgo_purego_setresgid_call(SB), R12
38+ MOVD (R12), R12
39+ MOVD R12, CTR
40+ CALL CTR
41+ RET
42+
43+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8-0
44+ MOVD ·x_cgo_purego_setresuid_call(SB), R12
45+ MOVD (R12), R12
46+ MOVD R12, CTR
47+ CALL CTR
48+ RET
49+
50+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8-0
51+ MOVD ·x_cgo_purego_setreuid_call(SB), R12
52+ MOVD (R12), R12
53+ MOVD R12, CTR
54+ CALL CTR
55+ RET
56+
57+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8-0
58+ MOVD ·x_cgo_purego_setuid_call(SB), R12
59+ MOVD (R12), R12
60+ MOVD R12, CTR
61+ CALL CTR
62+ RET
63+
64+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8-0
65+ MOVD ·x_cgo_purego_setgroups_call(SB), R12
66+ MOVD (R12), R12
67+ MOVD R12, CTR
68+ CALL CTR
69+ RET
A internal/fakecgo/trampolines_linux_riscv64.s +60 -0
@@ -0,0 +1,60 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8
9+ MOV ·x_cgo_purego_setegid_call(SB), X5
10+ MOV (X5), X5
11+ CALL X5
12+ RET
13+
14+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8
15+ MOV ·x_cgo_purego_seteuid_call(SB), X5
16+ MOV (X5), X5
17+ CALL X5
18+ RET
19+
20+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8
21+ MOV ·x_cgo_purego_setgid_call(SB), X5
22+ MOV (X5), X5
23+ CALL X5
24+ RET
25+
26+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8
27+ MOV ·x_cgo_purego_setregid_call(SB), X5
28+ MOV (X5), X5
29+ CALL X5
30+ RET
31+
32+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8
33+ MOV ·x_cgo_purego_setresgid_call(SB), X5
34+ MOV (X5), X5
35+ CALL X5
36+ RET
37+
38+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8
39+ MOV ·x_cgo_purego_setresuid_call(SB), X5
40+ MOV (X5), X5
41+ CALL X5
42+ RET
43+
44+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8
45+ MOV ·x_cgo_purego_setreuid_call(SB), X5
46+ MOV (X5), X5
47+ CALL X5
48+ RET
49+
50+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8
51+ MOV ·x_cgo_purego_setuid_call(SB), X5
52+ MOV (X5), X5
53+ CALL X5
54+ RET
55+
56+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8
57+ MOV ·x_cgo_purego_setgroups_call(SB), X5
58+ MOV (X5), X5
59+ CALL X5
60+ RET
@@ -0,0 +1,60 @@
1+// SPDX-License-Identifier: Apache-2.0
2+// SPDX-FileCopyrightText: 2026 The Ebitengine Authors
3+
4+//go:build !cgo && linux
5+
6+#include "textflag.h"
7+
8+TEXT _cgo_purego_setegid_trampoline(SB), NOSPLIT, $8
9+ MOV ·x_cgo_purego_setegid_call(SB), X5
10+ MOV (X5), X5
11+ CALL X5
12+ RET
13+
14+TEXT _cgo_purego_seteuid_trampoline(SB), NOSPLIT, $8
15+ MOV ·x_cgo_purego_seteuid_call(SB), X5
16+ MOV (X5), X5
17+ CALL X5
18+ RET
19+
20+TEXT _cgo_purego_setgid_trampoline(SB), NOSPLIT, $8
21+ MOV ·x_cgo_purego_setgid_call(SB), X5
22+ MOV (X5), X5
23+ CALL X5
24+ RET
25+
26+TEXT _cgo_purego_setregid_trampoline(SB), NOSPLIT, $8
27+ MOV ·x_cgo_purego_setregid_call(SB), X5
28+ MOV (X5), X5
29+ CALL X5
30+ RET
31+
32+TEXT _cgo_purego_setresgid_trampoline(SB), NOSPLIT, $8
33+ MOV ·x_cgo_purego_setresgid_call(SB), X5
34+ MOV (X5), X5
35+ CALL X5
36+ RET
37+
38+TEXT _cgo_purego_setresuid_trampoline(SB), NOSPLIT, $8
39+ MOV ·x_cgo_purego_setresuid_call(SB), X5
40+ MOV (X5), X5
41+ CALL X5
42+ RET
43+
44+TEXT _cgo_purego_setreuid_trampoline(SB), NOSPLIT, $8
45+ MOV ·x_cgo_purego_setreuid_call(SB), X5
46+ MOV (X5), X5
47+ CALL X5
48+ RET
49+
50+TEXT _cgo_purego_setuid_trampoline(SB), NOSPLIT, $8
51+ MOV ·x_cgo_purego_setuid_call(SB), X5
52+ MOV (X5), X5
53+ CALL X5
54+ RET
55+
56+TEXT _cgo_purego_setgroups_trampoline(SB), NOSPLIT, $8
57+ MOV ·x_cgo_purego_setgroups_call(SB), X5
58+ MOV (X5), X5
59+ CALL X5
60+ RET
M internal/fakecgo/trampolines_loong64.s +1 -6
@@ -49,16 +49,14 @@ TEXT ·setg_trampoline(SB), NOSPLIT, $0
4949 CALL (R23)
5050 RET
5151
52-TEXT threadentry_trampoline(SB), NOSPLIT, $0
52+TEXT threadentry_trampoline(SB), NOSPLIT, $176
5353 // See crosscall2.
54- ADDV $(-23*8), R3
5554 MOVV R4, (1*8)(R3) // fn unsafe.Pointer
5655 MOVV R5, (2*8)(R3) // a unsafe.Pointer
5756 MOVV R7, (3*8)(R3) // ctxt uintptr
5857
5958 SAVE_R22_TO_R31((4*8))
6059 SAVE_F24_TO_F31((14*8))
61- MOVV R1, (22*8)(R3)
6260
6361 MOVV ·threadentry_call(SB), R23
6462 MOVV (R23), R23
@@ -66,9 +64,6 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $0
6664
6765 RESTORE_R22_TO_R31((4*8))
6866 RESTORE_F24_TO_F31((14*8))
69- MOVV (22*8)(R3), R1
70-
71- ADDV $(23*8), R3
7267 RET
7368
7469 TEXT ·call5(SB), NOSPLIT, $0-0
@@ -49,16 +49,14 @@ TEXT ·setg_trampoline(SB), NOSPLIT, $0
49 CALL (R23)49 CALL (R23)
50 RET50 RET
51 51
52-TEXT threadentry_trampoline(SB), NOSPLIT, $052+TEXT threadentry_trampoline(SB), NOSPLIT, $176
53 // See crosscall2.53 // See crosscall2.
54- ADDV $(-23*8), R3
55 MOVV R4, (1*8)(R3) // fn unsafe.Pointer54 MOVV R4, (1*8)(R3) // fn unsafe.Pointer
56 MOVV R5, (2*8)(R3) // a unsafe.Pointer55 MOVV R5, (2*8)(R3) // a unsafe.Pointer
57 MOVV R7, (3*8)(R3) // ctxt uintptr56 MOVV R7, (3*8)(R3) // ctxt uintptr
58 57
59 SAVE_R22_TO_R31((4*8))58 SAVE_R22_TO_R31((4*8))
60 SAVE_F24_TO_F31((14*8))59 SAVE_F24_TO_F31((14*8))
61- MOVV R1, (22*8)(R3)
62 60
63 MOVV ·threadentry_call(SB), R2361 MOVV ·threadentry_call(SB), R23
64 MOVV (R23), R2362 MOVV (R23), R23
@@ -66,9 +64,6 @@ TEXT threadentry_trampoline(SB), NOSPLIT, $0
66 64
67 RESTORE_R22_TO_R31((4*8))65 RESTORE_R22_TO_R31((4*8))
68 RESTORE_F24_TO_F31((14*8))66 RESTORE_F24_TO_F31((14*8))
69- MOVV (22*8)(R3), R1
70-
71- ADDV $(23*8), R3
72 RET67 RET
73 68
74 TEXT ·call5(SB), NOSPLIT, $0-069 TEXT ·call5(SB), NOSPLIT, $0-0
M internal/fakecgo/zsymbols_linux.go +110 -0
@@ -16,6 +16,16 @@ import "unsafe"
1616 //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6"
1717 //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6"
1818 //go:cgo_import_dynamic purego_abort abort "libc.so.6"
19+//go:cgo_import_dynamic purego___errno_location __errno_location "libc.so.6"
20+//go:cgo_import_dynamic purego_setegid setegid "libc.so.6"
21+//go:cgo_import_dynamic purego_seteuid seteuid "libc.so.6"
22+//go:cgo_import_dynamic purego_setgid setgid "libc.so.6"
23+//go:cgo_import_dynamic purego_setregid setregid "libc.so.6"
24+//go:cgo_import_dynamic purego_setresgid setresgid "libc.so.6"
25+//go:cgo_import_dynamic purego_setresuid setresuid "libc.so.6"
26+//go:cgo_import_dynamic purego_setreuid setreuid "libc.so.6"
27+//go:cgo_import_dynamic purego_setuid setuid "libc.so.6"
28+//go:cgo_import_dynamic purego_setgroups setgroups "libc.so.6"
1929 //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0"
2030 //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0"
2131 //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0"
@@ -27,6 +37,66 @@ import "unsafe"
2737 //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so.0"
2838 //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so.0"
2939
40+//go:nosplit
41+//go:norace
42+func __errno_location() uintptr {
43+ return uintptr(call5(__errno_locationABI0, 0, 0, 0, 0, 0))
44+}
45+
46+//go:nosplit
47+//go:norace
48+func setegid(egid uint32) int32 {
49+ return int32(call5(setegidABI0, uintptr(egid), 0, 0, 0, 0))
50+}
51+
52+//go:nosplit
53+//go:norace
54+func seteuid(euid uint32) int32 {
55+ return int32(call5(seteuidABI0, uintptr(euid), 0, 0, 0, 0))
56+}
57+
58+//go:nosplit
59+//go:norace
60+func setgid(gid uint32) int32 {
61+ return int32(call5(setgidABI0, uintptr(gid), 0, 0, 0, 0))
62+}
63+
64+//go:nosplit
65+//go:norace
66+func setregid(rgid uint32, egid uint32) int32 {
67+ return int32(call5(setregidABI0, uintptr(rgid), uintptr(egid), 0, 0, 0))
68+}
69+
70+//go:nosplit
71+//go:norace
72+func setresgid(rgid uint32, egid uint32, sgid uint32) int32 {
73+ return int32(call5(setresgidABI0, uintptr(rgid), uintptr(egid), uintptr(sgid), 0, 0))
74+}
75+
76+//go:nosplit
77+//go:norace
78+func setresuid(ruid uint32, euid uint32, suid uint32) int32 {
79+ return int32(call5(setresuidABI0, uintptr(ruid), uintptr(euid), uintptr(suid), 0, 0))
80+}
81+
82+//go:nosplit
83+//go:norace
84+func setreuid(ruid uint32, euid uint32) int32 {
85+ return int32(call5(setreuidABI0, uintptr(ruid), uintptr(euid), 0, 0, 0))
86+}
87+
88+//go:nosplit
89+//go:norace
90+func setuid(uid uint32) int32 {
91+ return int32(call5(setuidABI0, uintptr(uid), 0, 0, 0, 0))
92+}
93+
94+//go:nosplit
95+//go:norace
96+func setgroups(ngid uint32, gidset *uint32) int32 {
97+ return int32(call5(setgroupsABI0, uintptr(ngid), uintptr(unsafe.Pointer(gidset)), 0, 0, 0))
98+}
99+
30100 //go:nosplit
31101 //go:norace
32102 func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 {
@@ -39,6 +109,46 @@ func pthread_attr_destroy(attr *pthread_attr_t) int32 {
39109 return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
40110 }
41111
112+//go:linkname ___errno_location ___errno_location
113+var ___errno_location uint8
114+var __errno_locationABI0 = uintptr(unsafe.Pointer(&___errno_location))
115+
116+//go:linkname _setegid _setegid
117+var _setegid uint8
118+var setegidABI0 = uintptr(unsafe.Pointer(&_setegid))
119+
120+//go:linkname _seteuid _seteuid
121+var _seteuid uint8
122+var seteuidABI0 = uintptr(unsafe.Pointer(&_seteuid))
123+
124+//go:linkname _setgid _setgid
125+var _setgid uint8
126+var setgidABI0 = uintptr(unsafe.Pointer(&_setgid))
127+
128+//go:linkname _setregid _setregid
129+var _setregid uint8
130+var setregidABI0 = uintptr(unsafe.Pointer(&_setregid))
131+
132+//go:linkname _setresgid _setresgid
133+var _setresgid uint8
134+var setresgidABI0 = uintptr(unsafe.Pointer(&_setresgid))
135+
136+//go:linkname _setresuid _setresuid
137+var _setresuid uint8
138+var setresuidABI0 = uintptr(unsafe.Pointer(&_setresuid))
139+
140+//go:linkname _setreuid _setreuid
141+var _setreuid uint8
142+var setreuidABI0 = uintptr(unsafe.Pointer(&_setreuid))
143+
144+//go:linkname _setuid _setuid
145+var _setuid uint8
146+var setuidABI0 = uintptr(unsafe.Pointer(&_setuid))
147+
148+//go:linkname _setgroups _setgroups
149+var _setgroups uint8
150+var setgroupsABI0 = uintptr(unsafe.Pointer(&_setgroups))
151+
42152 //go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize
43153 var _pthread_attr_getstacksize uint8
44154 var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize))
@@ -16,6 +16,16 @@ import "unsafe"
16 //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6"16 //go:cgo_import_dynamic purego_sigfillset sigfillset "libc.so.6"
17 //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6"17 //go:cgo_import_dynamic purego_nanosleep nanosleep "libc.so.6"
18 //go:cgo_import_dynamic purego_abort abort "libc.so.6"18 //go:cgo_import_dynamic purego_abort abort "libc.so.6"
19+//go:cgo_import_dynamic purego___errno_location __errno_location "libc.so.6"
20+//go:cgo_import_dynamic purego_setegid setegid "libc.so.6"
21+//go:cgo_import_dynamic purego_seteuid seteuid "libc.so.6"
22+//go:cgo_import_dynamic purego_setgid setgid "libc.so.6"
23+//go:cgo_import_dynamic purego_setregid setregid "libc.so.6"
24+//go:cgo_import_dynamic purego_setresgid setresgid "libc.so.6"
25+//go:cgo_import_dynamic purego_setresuid setresuid "libc.so.6"
26+//go:cgo_import_dynamic purego_setreuid setreuid "libc.so.6"
27+//go:cgo_import_dynamic purego_setuid setuid "libc.so.6"
28+//go:cgo_import_dynamic purego_setgroups setgroups "libc.so.6"
19 //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0"29 //go:cgo_import_dynamic purego_pthread_attr_init pthread_attr_init "libpthread.so.0"
20 //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0"30 //go:cgo_import_dynamic purego_pthread_create pthread_create "libpthread.so.0"
21 //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0"31 //go:cgo_import_dynamic purego_pthread_detach pthread_detach "libpthread.so.0"
@@ -27,6 +37,66 @@ import "unsafe"
27 //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so.0"37 //go:cgo_import_dynamic purego_pthread_attr_getstacksize pthread_attr_getstacksize "libpthread.so.0"
28 //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so.0"38 //go:cgo_import_dynamic purego_pthread_attr_destroy pthread_attr_destroy "libpthread.so.0"
29 39
40+//go:nosplit
41+//go:norace
42+func __errno_location() uintptr {
43+ return uintptr(call5(__errno_locationABI0, 0, 0, 0, 0, 0))
44+}
45+
46+//go:nosplit
47+//go:norace
48+func setegid(egid uint32) int32 {
49+ return int32(call5(setegidABI0, uintptr(egid), 0, 0, 0, 0))
50+}
51+
52+//go:nosplit
53+//go:norace
54+func seteuid(euid uint32) int32 {
55+ return int32(call5(seteuidABI0, uintptr(euid), 0, 0, 0, 0))
56+}
57+
58+//go:nosplit
59+//go:norace
60+func setgid(gid uint32) int32 {
61+ return int32(call5(setgidABI0, uintptr(gid), 0, 0, 0, 0))
62+}
63+
64+//go:nosplit
65+//go:norace
66+func setregid(rgid uint32, egid uint32) int32 {
67+ return int32(call5(setregidABI0, uintptr(rgid), uintptr(egid), 0, 0, 0))
68+}
69+
70+//go:nosplit
71+//go:norace
72+func setresgid(rgid uint32, egid uint32, sgid uint32) int32 {
73+ return int32(call5(setresgidABI0, uintptr(rgid), uintptr(egid), uintptr(sgid), 0, 0))
74+}
75+
76+//go:nosplit
77+//go:norace
78+func setresuid(ruid uint32, euid uint32, suid uint32) int32 {
79+ return int32(call5(setresuidABI0, uintptr(ruid), uintptr(euid), uintptr(suid), 0, 0))
80+}
81+
82+//go:nosplit
83+//go:norace
84+func setreuid(ruid uint32, euid uint32) int32 {
85+ return int32(call5(setreuidABI0, uintptr(ruid), uintptr(euid), 0, 0, 0))
86+}
87+
88+//go:nosplit
89+//go:norace
90+func setuid(uid uint32) int32 {
91+ return int32(call5(setuidABI0, uintptr(uid), 0, 0, 0, 0))
92+}
93+
94+//go:nosplit
95+//go:norace
96+func setgroups(ngid uint32, gidset *uint32) int32 {
97+ return int32(call5(setgroupsABI0, uintptr(ngid), uintptr(unsafe.Pointer(gidset)), 0, 0, 0))
98+}
99+
30 //go:nosplit100 //go:nosplit
31 //go:norace101 //go:norace
32 func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 {102 func pthread_attr_getstacksize(attr *pthread_attr_t, stacksize *size_t) int32 {
@@ -39,6 +109,46 @@ func pthread_attr_destroy(attr *pthread_attr_t) int32 {
39 return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))109 return int32(call5(pthread_attr_destroyABI0, uintptr(unsafe.Pointer(attr)), 0, 0, 0, 0))
40 }110 }
41 111
112+//go:linkname ___errno_location ___errno_location
113+var ___errno_location uint8
114+var __errno_locationABI0 = uintptr(unsafe.Pointer(&___errno_location))
115+
116+//go:linkname _setegid _setegid
117+var _setegid uint8
118+var setegidABI0 = uintptr(unsafe.Pointer(&_setegid))
119+
120+//go:linkname _seteuid _seteuid
121+var _seteuid uint8
122+var seteuidABI0 = uintptr(unsafe.Pointer(&_seteuid))
123+
124+//go:linkname _setgid _setgid
125+var _setgid uint8
126+var setgidABI0 = uintptr(unsafe.Pointer(&_setgid))
127+
128+//go:linkname _setregid _setregid
129+var _setregid uint8
130+var setregidABI0 = uintptr(unsafe.Pointer(&_setregid))
131+
132+//go:linkname _setresgid _setresgid
133+var _setresgid uint8
134+var setresgidABI0 = uintptr(unsafe.Pointer(&_setresgid))
135+
136+//go:linkname _setresuid _setresuid
137+var _setresuid uint8
138+var setresuidABI0 = uintptr(unsafe.Pointer(&_setresuid))
139+
140+//go:linkname _setreuid _setreuid
141+var _setreuid uint8
142+var setreuidABI0 = uintptr(unsafe.Pointer(&_setreuid))
143+
144+//go:linkname _setuid _setuid
145+var _setuid uint8
146+var setuidABI0 = uintptr(unsafe.Pointer(&_setuid))
147+
148+//go:linkname _setgroups _setgroups
149+var _setgroups uint8
150+var setgroupsABI0 = uintptr(unsafe.Pointer(&_setgroups))
151+
42 //go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize152 //go:linkname _pthread_attr_getstacksize _pthread_attr_getstacksize
43 var _pthread_attr_getstacksize uint8153 var _pthread_attr_getstacksize uint8
44 var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize))154 var pthread_attr_getstacksizeABI0 = uintptr(unsafe.Pointer(&_pthread_attr_getstacksize))
M internal/fakecgo/ztrampolines_linux.s +30 -0
@@ -9,6 +9,36 @@
99
1010 // these stubs are here because it is not possible to go:linkname directly the C functions
1111
12+TEXT ___errno_location(SB), NOSPLIT|NOFRAME, $0-0
13+ JMP purego___errno_location(SB)
14+
15+TEXT _setegid(SB), NOSPLIT|NOFRAME, $0-0
16+ JMP purego_setegid(SB)
17+
18+TEXT _seteuid(SB), NOSPLIT|NOFRAME, $0-0
19+ JMP purego_seteuid(SB)
20+
21+TEXT _setgid(SB), NOSPLIT|NOFRAME, $0-0
22+ JMP purego_setgid(SB)
23+
24+TEXT _setregid(SB), NOSPLIT|NOFRAME, $0-0
25+ JMP purego_setregid(SB)
26+
27+TEXT _setresgid(SB), NOSPLIT|NOFRAME, $0-0
28+ JMP purego_setresgid(SB)
29+
30+TEXT _setresuid(SB), NOSPLIT|NOFRAME, $0-0
31+ JMP purego_setresuid(SB)
32+
33+TEXT _setreuid(SB), NOSPLIT|NOFRAME, $0-0
34+ JMP purego_setreuid(SB)
35+
36+TEXT _setuid(SB), NOSPLIT|NOFRAME, $0-0
37+ JMP purego_setuid(SB)
38+
39+TEXT _setgroups(SB), NOSPLIT|NOFRAME, $0-0
40+ JMP purego_setgroups(SB)
41+
1242 TEXT _pthread_attr_getstacksize(SB), NOSPLIT|NOFRAME, $0-0
1343 JMP purego_pthread_attr_getstacksize(SB)
1444
@@ -9,6 +9,36 @@
9 9
10 // these stubs are here because it is not possible to go:linkname directly the C functions10 // these stubs are here because it is not possible to go:linkname directly the C functions
11 11
12+TEXT ___errno_location(SB), NOSPLIT|NOFRAME, $0-0
13+ JMP purego___errno_location(SB)
14+
15+TEXT _setegid(SB), NOSPLIT|NOFRAME, $0-0
16+ JMP purego_setegid(SB)
17+
18+TEXT _seteuid(SB), NOSPLIT|NOFRAME, $0-0
19+ JMP purego_seteuid(SB)
20+
21+TEXT _setgid(SB), NOSPLIT|NOFRAME, $0-0
22+ JMP purego_setgid(SB)
23+
24+TEXT _setregid(SB), NOSPLIT|NOFRAME, $0-0
25+ JMP purego_setregid(SB)
26+
27+TEXT _setresgid(SB), NOSPLIT|NOFRAME, $0-0
28+ JMP purego_setresgid(SB)
29+
30+TEXT _setresuid(SB), NOSPLIT|NOFRAME, $0-0
31+ JMP purego_setresuid(SB)
32+
33+TEXT _setreuid(SB), NOSPLIT|NOFRAME, $0-0
34+ JMP purego_setreuid(SB)
35+
36+TEXT _setuid(SB), NOSPLIT|NOFRAME, $0-0
37+ JMP purego_setuid(SB)
38+
39+TEXT _setgroups(SB), NOSPLIT|NOFRAME, $0-0
40+ JMP purego_setgroups(SB)
41+
12 TEXT _pthread_attr_getstacksize(SB), NOSPLIT|NOFRAME, $0-042 TEXT _pthread_attr_getstacksize(SB), NOSPLIT|NOFRAME, $0-0
13 JMP purego_pthread_attr_getstacksize(SB)43 JMP purego_pthread_attr_getstacksize(SB)
14 44
M sys_amd64.s +8 -14
@@ -38,11 +38,8 @@
3838 // C calling convention (use libcCall).
3939 GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8
4040 DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)
41-TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
42- PUSHQ BP
43- MOVQ SP, BP
44- SUBQ $STACK_SIZE, SP
45- MOVQ DI, PTR_ADDRESS(BP) // save the pointer
41+TEXT syscall15X(SB), NOSPLIT, $STACK_SIZE
42+ MOVQ DI, PTR_ADDRESS(SP) // save the pointer
4643 MOVQ DI, R11
4744
4845 MOVQ syscall15Args_f1(R11), X0 // f1
@@ -61,7 +58,7 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
6158 MOVQ syscall15Args_a5(R11), R8 // a5
6259 MOVQ syscall15Args_a6(R11), R9 // a6
6360
64- // push the remaining paramters onto the stack
61+ // push the remaining parameters onto the stack
6562 MOVQ syscall15Args_a7(R11), R12
6663 MOVQ R12, 0(SP) // push a7
6764 MOVQ syscall15Args_a8(R11), R12
@@ -85,23 +82,20 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
8582 MOVQ syscall15Args_fn(R11), R10 // fn
8683 CALL R10
8784
88- MOVQ PTR_ADDRESS(BP), DI // get the pointer back
85+ MOVQ PTR_ADDRESS(SP), DI // get the pointer back
8986 MOVQ AX, syscall15Args_a1(DI) // r1
90- MOVQ DX, syscall15Args_a2(DI) // r3
87+ MOVQ DX, syscall15Args_a2(DI) // r2
9188 MOVQ X0, syscall15Args_f1(DI) // f1
9289 MOVQ X1, syscall15Args_f2(DI) // f2
9390
9491 #ifdef GOOS_darwin
9592 CALL purego_error(SB)
96- MOVD (AX), AX
97- MOVD AX, syscall15Args_a3(DI) // save errno
98-
93+ MOVQ PTR_ADDRESS(SP), DI // reload (DI clobbered by call)
94+ MOVQ (AX), AX
95+ MOVQ AX, syscall15Args_a3(DI) // save errno
9996 #endif
10097
10198 XORL AX, AX // no error (it's ignored anyway)
102- ADDQ $STACK_SIZE, SP
103- MOVQ BP, SP
104- POPQ BP
10599 RET
106100
107101 TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
@@ -38,11 +38,8 @@
38 // C calling convention (use libcCall).38 // C calling convention (use libcCall).
39 GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $839 GLOBL ·syscall15XABI0(SB), NOPTR|RODATA, $8
40 DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)40 DATA ·syscall15XABI0(SB)/8, $syscall15X(SB)
41-TEXT syscall15X(SB), NOSPLIT|NOFRAME, $041+TEXT syscall15X(SB), NOSPLIT, $STACK_SIZE
42- PUSHQ BP42+ MOVQ DI, PTR_ADDRESS(SP) // save the pointer
43- MOVQ SP, BP
44- SUBQ $STACK_SIZE, SP
45- MOVQ DI, PTR_ADDRESS(BP) // save the pointer
46 MOVQ DI, R1143 MOVQ DI, R11
47 44
48 MOVQ syscall15Args_f1(R11), X0 // f145 MOVQ syscall15Args_f1(R11), X0 // f1
@@ -61,7 +58,7 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
61 MOVQ syscall15Args_a5(R11), R8 // a558 MOVQ syscall15Args_a5(R11), R8 // a5
62 MOVQ syscall15Args_a6(R11), R9 // a659 MOVQ syscall15Args_a6(R11), R9 // a6
63 60
64- // push the remaining paramters onto the stack61+ // push the remaining parameters onto the stack
65 MOVQ syscall15Args_a7(R11), R1262 MOVQ syscall15Args_a7(R11), R12
66 MOVQ R12, 0(SP) // push a763 MOVQ R12, 0(SP) // push a7
67 MOVQ syscall15Args_a8(R11), R1264 MOVQ syscall15Args_a8(R11), R12
@@ -85,23 +82,20 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
85 MOVQ syscall15Args_fn(R11), R10 // fn82 MOVQ syscall15Args_fn(R11), R10 // fn
86 CALL R1083 CALL R10
87 84
88- MOVQ PTR_ADDRESS(BP), DI // get the pointer back85+ MOVQ PTR_ADDRESS(SP), DI // get the pointer back
89 MOVQ AX, syscall15Args_a1(DI) // r186 MOVQ AX, syscall15Args_a1(DI) // r1
90- MOVQ DX, syscall15Args_a2(DI) // r387+ MOVQ DX, syscall15Args_a2(DI) // r2
91 MOVQ X0, syscall15Args_f1(DI) // f188 MOVQ X0, syscall15Args_f1(DI) // f1
92 MOVQ X1, syscall15Args_f2(DI) // f289 MOVQ X1, syscall15Args_f2(DI) // f2
93 90
94 #ifdef GOOS_darwin91 #ifdef GOOS_darwin
95 CALL purego_error(SB)92 CALL purego_error(SB)
96- MOVD (AX), AX93+ MOVQ PTR_ADDRESS(SP), DI // reload (DI clobbered by call)
97- MOVD AX, syscall15Args_a3(DI) // save errno94+ MOVQ (AX), AX
98-95+ MOVQ AX, syscall15Args_a3(DI) // save errno
99 #endif96 #endif
100 97
101 XORL AX, AX // no error (it's ignored anyway)98 XORL AX, AX // no error (it's ignored anyway)
102- ADDQ $STACK_SIZE, SP
103- MOVQ BP, SP
104- POPQ BP
105 RET99 RET
106 100
107 TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0101 TEXT callbackasm1(SB), NOSPLIT|NOFRAME, $0
A syscall_linux_test.go +218 -0
@@ -0,0 +1,218 @@
1+// Copyright 2015 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+package purego_test
6+
7+import (
8+ "errors"
9+ "fmt"
10+ "os"
11+ "path/filepath"
12+ "runtime"
13+ "sort"
14+ "strings"
15+ "syscall"
16+ "testing"
17+ "unsafe"
18+
19+ "github.com/ebitengine/purego"
20+)
21+
22+func TestAllThreadsSyscall(t *testing.T) {
23+ _, _, err := syscall.AllThreadsSyscall(syscall.SYS_FCNTL, 0, 0, 0)
24+ if err != syscall.ENOTSUP {
25+ t.Errorf("AllThreadsSyscall should return ENOTSUP, got: %v", err)
26+ }
27+}
28+
29+// TestSetuidEtc performs tests on all of the wrapped system calls
30+// that mirror to the 9 glibc syscalls with POSIX semantics. The test
31+// here is considered authoritative and should compile and run
32+// CGO_ENABLED=0 or 1.
33+func TestSetuidEtc(t *testing.T) {
34+ vs := []struct {
35+ call string
36+ fn func() error
37+ filter, expect string
38+ }{
39+ {call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
40+ {call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
41+
42+ {call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
43+ {call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
44+
45+ {call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
46+ {call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
47+
48+ {call: "Setgroups([]int{0,1,2,3})", fn: func() error { return syscall.Setgroups([]int{0, 1, 2, 3}) }, filter: "Groups:", expect: "\t0 1 2 3"},
49+ {call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
50+ {call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
51+
52+ {call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
53+ {call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
54+ {call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
55+
56+ {call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
57+ {call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
58+ {call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
59+
60+ {call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
61+ {call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
62+ {call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
63+
64+ {call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
65+ {call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
66+ {call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
67+ }
68+
69+ for i, v := range vs {
70+ // Generate some thread churn.
71+ c := make(chan struct{})
72+ go killAThread(c)
73+ close(c)
74+
75+ if err := v.fn(); err != nil {
76+ if syscall.Getuid() == 0 && syscall.Getgid() == 0 {
77+ // If root, then the syscall should have succeeded.
78+ t.Errorf("[%d] %q setup failed: %v", i, v.call, err)
79+ } else if !errors.Is(err, syscall.EPERM) {
80+ // If not root, then EPERM is the only acceptable error.
81+ t.Errorf("[%d] %q unexpected error: %v", i, v.call, err)
82+ }
83+ continue
84+ }
85+ if err := compareStatus(v.filter, v.expect); err != nil {
86+ t.Errorf("[%d] %q comparison: %v", i, v.call, err)
87+ }
88+ }
89+}
90+
91+// killAThread locks the goroutine to an OS thread and exits; this
92+// causes an OS thread to terminate.
93+func killAThread(c <-chan struct{}) {
94+ runtime.LockOSThread()
95+ <-c
96+}
97+
98+// compareStatus is used to confirm the contents of the thread
99+// specific status files match expectations.
100+func compareStatus(filter, expect string) error {
101+ expected := filter + expect
102+ pid := syscall.Getpid()
103+ fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
104+ if err != nil {
105+ return fmt.Errorf("unable to find %d tasks: %v", pid, err)
106+ }
107+ expectedProc := fmt.Sprintf("Pid:\t%d", pid)
108+ foundAThread := false
109+ for _, f := range fs {
110+ tf := fmt.Sprintf("/proc/%s/status", f.Name())
111+ d, err := os.ReadFile(tf)
112+ if err != nil {
113+ // There are a surprising number of ways this
114+ // can error out on linux. We've seen all of
115+ // the following, so treat any error here as
116+ // equivalent to the "process is gone":
117+ // os.IsNotExist(err),
118+ // "... : no such process",
119+ // "... : bad file descriptor.
120+ continue
121+ }
122+ lines := strings.Split(string(d), "\n")
123+ for _, line := range lines {
124+ // Different kernel vintages pad differently.
125+ line = strings.TrimSpace(line)
126+ if strings.HasPrefix(line, "Pid:\t") {
127+ // On loaded systems, it is possible
128+ // for a TID to be reused really
129+ // quickly. As such, we need to
130+ // validate that the thread status
131+ // info we just read is a task of the
132+ // same process PID as we are
133+ // currently running, and not a
134+ // recently terminated thread
135+ // resurfaced in a different process.
136+ if line != expectedProc {
137+ break
138+ }
139+ // Fall through in the unlikely case
140+ // that filter at some point is
141+ // "Pid:\t".
142+ }
143+ if strings.HasPrefix(line, filter) {
144+ if line == expected {
145+ foundAThread = true
146+ break
147+ }
148+ if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") {
149+ // https://github.com/golang/go/issues/46145
150+ // Containers don't reliably output this line in sorted order so manually sort and compare that.
151+ a := strings.Split(line[8:], " ")
152+ sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
153+ got := strings.Join(a, " ")
154+ if got == expected[8:] {
155+ foundAThread = true
156+ break
157+ }
158+
159+ }
160+ return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
161+ }
162+ }
163+ }
164+ if !foundAThread {
165+ return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
166+ }
167+ return nil
168+}
169+
170+// TestDlopenThenAllThreadsSyscall reproduces a crash where loading a shared
171+// library and calling a C function that invokes a Go callback, followed by
172+// AllThreadsSyscall (used by Setuid etc.), causes a SIGSEGV on amd64.
173+func TestDlopenThenAllThreadsSyscall(t *testing.T) {
174+ // Step 1: Build and load a shared C library that calls back into Go.
175+ libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
176+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {
177+ t.Fatal(err)
178+ }
179+ defer os.Remove(libFileName)
180+
181+ lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
182+ if err != nil {
183+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
184+ }
185+
186+ var callCallback func(p uintptr, s string) int
187+ purego.RegisterLibFunc(&callCallback, lib, "callCallback")
188+
189+ goFunc := func(cstr *byte, n int) int {
190+ _ = string(unsafe.Slice(cstr, n))
191+ return 1
192+ }
193+
194+ cb := purego.NewCallback(goFunc)
195+ for i := 0; i < 10; i++ {
196+ callCallback(cb, "hello")
197+ }
198+
199+ // Step 2: Generate thread churn + AllThreadsSyscall (via Setuid).
200+ for i := 0; i < 20; i++ {
201+ c := make(chan struct{})
202+ go func() {
203+ runtime.LockOSThread()
204+ <-c
205+ }()
206+ close(c)
207+
208+ if err := syscall.Setuid(0); err != nil {
209+ if syscall.Getuid() == 0 && syscall.Getgid() == 0 {
210+ // If root, then the syscall should have succeeded.
211+ t.Errorf("setup failed: %v", err)
212+ } else if !errors.Is(err, syscall.EPERM) {
213+ // If not root, then EPERM is the only acceptable error.
214+ t.Errorf("unexpected error: %v", err)
215+ }
216+ }
217+ }
218+}
@@ -0,0 +1,218 @@
1+// Copyright 2015 The Go Authors. All rights reserved.
2+// Use of this source code is governed by a BSD-style
3+// license that can be found in the LICENSE file.
4+
5+package purego_test
6+
7+import (
8+ "errors"
9+ "fmt"
10+ "os"
11+ "path/filepath"
12+ "runtime"
13+ "sort"
14+ "strings"
15+ "syscall"
16+ "testing"
17+ "unsafe"
18+
19+ "github.com/ebitengine/purego"
20+)
21+
22+func TestAllThreadsSyscall(t *testing.T) {
23+ _, _, err := syscall.AllThreadsSyscall(syscall.SYS_FCNTL, 0, 0, 0)
24+ if err != syscall.ENOTSUP {
25+ t.Errorf("AllThreadsSyscall should return ENOTSUP, got: %v", err)
26+ }
27+}
28+
29+// TestSetuidEtc performs tests on all of the wrapped system calls
30+// that mirror to the 9 glibc syscalls with POSIX semantics. The test
31+// here is considered authoritative and should compile and run
32+// CGO_ENABLED=0 or 1.
33+func TestSetuidEtc(t *testing.T) {
34+ vs := []struct {
35+ call string
36+ fn func() error
37+ filter, expect string
38+ }{
39+ {call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
40+ {call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
41+
42+ {call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
43+ {call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
44+
45+ {call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
46+ {call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
47+
48+ {call: "Setgroups([]int{0,1,2,3})", fn: func() error { return syscall.Setgroups([]int{0, 1, 2, 3}) }, filter: "Groups:", expect: "\t0 1 2 3"},
49+ {call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
50+ {call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
51+
52+ {call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
53+ {call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
54+ {call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
55+
56+ {call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
57+ {call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
58+ {call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
59+
60+ {call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
61+ {call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
62+ {call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
63+
64+ {call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
65+ {call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
66+ {call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
67+ }
68+
69+ for i, v := range vs {
70+ // Generate some thread churn.
71+ c := make(chan struct{})
72+ go killAThread(c)
73+ close(c)
74+
75+ if err := v.fn(); err != nil {
76+ if syscall.Getuid() == 0 && syscall.Getgid() == 0 {
77+ // If root, then the syscall should have succeeded.
78+ t.Errorf("[%d] %q setup failed: %v", i, v.call, err)
79+ } else if !errors.Is(err, syscall.EPERM) {
80+ // If not root, then EPERM is the only acceptable error.
81+ t.Errorf("[%d] %q unexpected error: %v", i, v.call, err)
82+ }
83+ continue
84+ }
85+ if err := compareStatus(v.filter, v.expect); err != nil {
86+ t.Errorf("[%d] %q comparison: %v", i, v.call, err)
87+ }
88+ }
89+}
90+
91+// killAThread locks the goroutine to an OS thread and exits; this
92+// causes an OS thread to terminate.
93+func killAThread(c <-chan struct{}) {
94+ runtime.LockOSThread()
95+ <-c
96+}
97+
98+// compareStatus is used to confirm the contents of the thread
99+// specific status files match expectations.
100+func compareStatus(filter, expect string) error {
101+ expected := filter + expect
102+ pid := syscall.Getpid()
103+ fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
104+ if err != nil {
105+ return fmt.Errorf("unable to find %d tasks: %v", pid, err)
106+ }
107+ expectedProc := fmt.Sprintf("Pid:\t%d", pid)
108+ foundAThread := false
109+ for _, f := range fs {
110+ tf := fmt.Sprintf("/proc/%s/status", f.Name())
111+ d, err := os.ReadFile(tf)
112+ if err != nil {
113+ // There are a surprising number of ways this
114+ // can error out on linux. We've seen all of
115+ // the following, so treat any error here as
116+ // equivalent to the "process is gone":
117+ // os.IsNotExist(err),
118+ // "... : no such process",
119+ // "... : bad file descriptor.
120+ continue
121+ }
122+ lines := strings.Split(string(d), "\n")
123+ for _, line := range lines {
124+ // Different kernel vintages pad differently.
125+ line = strings.TrimSpace(line)
126+ if strings.HasPrefix(line, "Pid:\t") {
127+ // On loaded systems, it is possible
128+ // for a TID to be reused really
129+ // quickly. As such, we need to
130+ // validate that the thread status
131+ // info we just read is a task of the
132+ // same process PID as we are
133+ // currently running, and not a
134+ // recently terminated thread
135+ // resurfaced in a different process.
136+ if line != expectedProc {
137+ break
138+ }
139+ // Fall through in the unlikely case
140+ // that filter at some point is
141+ // "Pid:\t".
142+ }
143+ if strings.HasPrefix(line, filter) {
144+ if line == expected {
145+ foundAThread = true
146+ break
147+ }
148+ if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") {
149+ // https://github.com/golang/go/issues/46145
150+ // Containers don't reliably output this line in sorted order so manually sort and compare that.
151+ a := strings.Split(line[8:], " ")
152+ sort.Slice(a, func(i, j int) bool { return a[i] < a[j] })
153+ got := strings.Join(a, " ")
154+ if got == expected[8:] {
155+ foundAThread = true
156+ break
157+ }
158+
159+ }
160+ return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
161+ }
162+ }
163+ }
164+ if !foundAThread {
165+ return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
166+ }
167+ return nil
168+}
169+
170+// TestDlopenThenAllThreadsSyscall reproduces a crash where loading a shared
171+// library and calling a C function that invokes a Go callback, followed by
172+// AllThreadsSyscall (used by Setuid etc.), causes a SIGSEGV on amd64.
173+func TestDlopenThenAllThreadsSyscall(t *testing.T) {
174+ // Step 1: Build and load a shared C library that calls back into Go.
175+ libFileName := filepath.Join(t.TempDir(), "libcbtest.so")
176+ if err := buildSharedLib("CC", libFileName, filepath.Join("testdata", "libcbtest", "callback_test.c")); err != nil {
177+ t.Fatal(err)
178+ }
179+ defer os.Remove(libFileName)
180+
181+ lib, err := purego.Dlopen(libFileName, purego.RTLD_NOW|purego.RTLD_GLOBAL)
182+ if err != nil {
183+ t.Fatalf("Dlopen(%q) failed: %v", libFileName, err)
184+ }
185+
186+ var callCallback func(p uintptr, s string) int
187+ purego.RegisterLibFunc(&callCallback, lib, "callCallback")
188+
189+ goFunc := func(cstr *byte, n int) int {
190+ _ = string(unsafe.Slice(cstr, n))
191+ return 1
192+ }
193+
194+ cb := purego.NewCallback(goFunc)
195+ for i := 0; i < 10; i++ {
196+ callCallback(cb, "hello")
197+ }
198+
199+ // Step 2: Generate thread churn + AllThreadsSyscall (via Setuid).
200+ for i := 0; i < 20; i++ {
201+ c := make(chan struct{})
202+ go func() {
203+ runtime.LockOSThread()
204+ <-c
205+ }()
206+ close(c)
207+
208+ if err := syscall.Setuid(0); err != nil {
209+ if syscall.Getuid() == 0 && syscall.Getgid() == 0 {
210+ // If root, then the syscall should have succeeded.
211+ t.Errorf("setup failed: %v", err)
212+ } else if !errors.Is(err, syscall.EPERM) {
213+ // If not root, then EPERM is the only acceptable error.
214+ t.Errorf("unexpected error: %v", err)
215+ }
216+ }
217+ }
218+}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 102

↑↓ moveenter openesc close