detached d8dcb88

return errno value on darwin amd64/arm64 (#416)

It adds support to return the errno value on Darwin amd64/arm64. I don't recall why I removed it and it is needed if we want to ever return it as an error type from RegisterFunc calls. This also brings it back in line with he function doc for SyscallN that says it returns the errno but right now it doesn't.

Updates #244
Updates #390
commit
d8dcb88add90fa48b3fe17d8f9c861514a9b8f77
author
TotallyGamerJet · · 2026-02-14 14:10 UTC
committer
GitHub · · 2026-02-14 14:10 UTC
parent
587d406
signature
GPG UnverifiedB5690EEEBB952194 The key is unknown
5 files · +47 -2
M dlfcn_darwin.go +1 -0
@@ -17,3 +17,4 @@ const (
1717 //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib"
1818 //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib"
1919 //go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib"
20+//go:cgo_import_dynamic purego_error __error "/usr/lib/libSystem.B.dylib"
@@ -17,3 +17,4 @@ const (
17 //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib"17 //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib"
18 //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib"18 //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib"
19 //go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib"19 //go:cgo_import_dynamic purego_dlclose dlclose "/usr/lib/libSystem.B.dylib"
20+//go:cgo_import_dynamic purego_error __error "/usr/lib/libSystem.B.dylib"
M sys_amd64.s +6 -0
@@ -91,6 +91,12 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
9191 MOVQ X0, syscall15Args_f1(DI) // f1
9292 MOVQ X1, syscall15Args_f2(DI) // f2
9393
94+#ifdef GOOS_darwin
95+ CALL purego_error(SB)
96+ MOVD (AX), AX
97+ MOVD AX, syscall15Args_a3(DI) // save errno
98+#endif
99+
94100 XORL AX, AX // no error (it's ignored anyway)
95101 ADDQ $STACK_SIZE, SP
96102 MOVQ BP, SP
@@ -91,6 +91,12 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0
91 MOVQ X0, syscall15Args_f1(DI) // f191 MOVQ X0, syscall15Args_f1(DI) // f1
92 MOVQ X1, syscall15Args_f2(DI) // f292 MOVQ X1, syscall15Args_f2(DI) // f2
93 93
94+#ifdef GOOS_darwin
95+ CALL purego_error(SB)
96+ MOVD (AX), AX
97+ MOVD AX, syscall15Args_a3(DI) // save errno
98+#endif
99+
94 XORL AX, AX // no error (it's ignored anyway)100 XORL AX, AX // no error (it's ignored anyway)
95 ADDQ $STACK_SIZE, SP101 ADDQ $STACK_SIZE, SP
96 MOVQ BP, SP102 MOVQ BP, SP
M sys_arm64.s +5 -0
@@ -89,4 +89,9 @@ TEXT syscall15X(SB), NOSPLIT, $0
8989 FMOVD F2, syscall15Args_f3(R2) // save f2
9090 FMOVD F3, syscall15Args_f4(R2) // save f3
9191
92+#ifdef GOOS_darwin
93+ BL purego_error(SB)
94+ MOVD (R0), R0
95+ MOVD R0, syscall15Args_a3(R2) // save errno
96+#endif
9297 RET
@@ -89,4 +89,9 @@ TEXT syscall15X(SB), NOSPLIT, $0
89 FMOVD F2, syscall15Args_f3(R2) // save f289 FMOVD F2, syscall15Args_f3(R2) // save f2
90 FMOVD F3, syscall15Args_f4(R2) // save f390 FMOVD F3, syscall15Args_f4(R2) // save f3
91 91
92+#ifdef GOOS_darwin
93+ BL purego_error(SB)
94+ MOVD (R0), R0
95+ MOVD R0, syscall15Args_a3(R2) // save errno
96+#endif
92 RET97 RET
M syscall_sysv.go +1 -1
@@ -26,7 +26,7 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
2626 }
2727
2828 runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))
29- return args.a1, args.a2, 0
29+ return args.a1, args.a2, args.a3
3030 }
3131
3232 // NewCallback converts a Go function to a function pointer conforming to the C calling convention.
@@ -26,7 +26,7 @@ func syscall_syscall15X(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a
26 }26 }
27 27
28 runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))28 runtime_cgocall(syscall15XABI0, unsafe.Pointer(args))
29- return args.a1, args.a2, 029+ return args.a1, args.a2, args.a3
30 }30 }
31 31
32 // NewCallback converts a Go function to a function pointer conforming to the C calling convention.32 // NewCallback converts a Go function to a function pointer conforming to the C calling convention.
M syscall_test.go +34 -1
@@ -5,9 +5,12 @@ package purego_test
55
66 import (
77 "os"
8+ "runtime"
89 "testing"
10+ "unsafe"
911
10- _ "github.com/ebitengine/purego"
12+ "github.com/ebitengine/purego"
13+ "github.com/ebitengine/purego/internal/load"
1114 )
1215
1316 func TestOS(t *testing.T) {
@@ -21,3 +24,33 @@ func TestOS(t *testing.T) {
2124 t.Errorf("failed to Unsetenv: %s", err)
2225 }
2326 }
27+
28+func TestErrno(t *testing.T) {
29+ if runtime.GOOS != "darwin" {
30+ t.Skip("platform does not support returning errno from syscall")
31+ }
32+
33+ libc, err := load.OpenLibrary("/usr/lib/libSystem.B.dylib")
34+ if err != nil {
35+ t.Fatal(err)
36+ }
37+
38+ openSym, err := load.OpenSymbol(libc, "open")
39+ if err != nil {
40+ t.Fatal(err)
41+ }
42+
43+ r1, _, errno := purego.SyscallN(openSym, uintptr(unsafe.Pointer(&[]byte("_file_that_does_not_exist_\x00")[0])), uintptr(os.O_RDWR))
44+ if int32(r1) != -1 {
45+ t.Errorf("open returned %d, wanted -1", r1)
46+ }
47+
48+ var strerror func(int32) string
49+ purego.RegisterLibFunc(&strerror, libc, "strerror")
50+
51+ const expected = "No such file or directory"
52+ got := strerror(int32(errno))
53+ if got != expected {
54+ t.Errorf("strerror returned %q, wanted \"%s\"", got, expected)
55+ }
56+}
@@ -5,9 +5,12 @@ package purego_test
5 5
6 import (6 import (
7 "os"7 "os"
8+ "runtime"
8 "testing"9 "testing"
10+ "unsafe"
9 11
10- _ "github.com/ebitengine/purego"12+ "github.com/ebitengine/purego"
13+ "github.com/ebitengine/purego/internal/load"
11 )14 )
12 15
13 func TestOS(t *testing.T) {16 func TestOS(t *testing.T) {
@@ -21,3 +24,33 @@ func TestOS(t *testing.T) {
21 t.Errorf("failed to Unsetenv: %s", err)24 t.Errorf("failed to Unsetenv: %s", err)
22 }25 }
23 }26 }
27+
28+func TestErrno(t *testing.T) {
29+ if runtime.GOOS != "darwin" {
30+ t.Skip("platform does not support returning errno from syscall")
31+ }
32+
33+ libc, err := load.OpenLibrary("/usr/lib/libSystem.B.dylib")
34+ if err != nil {
35+ t.Fatal(err)
36+ }
37+
38+ openSym, err := load.OpenSymbol(libc, "open")
39+ if err != nil {
40+ t.Fatal(err)
41+ }
42+
43+ r1, _, errno := purego.SyscallN(openSym, uintptr(unsafe.Pointer(&[]byte("_file_that_does_not_exist_\x00")[0])), uintptr(os.O_RDWR))
44+ if int32(r1) != -1 {
45+ t.Errorf("open returned %d, wanted -1", r1)
46+ }
47+
48+ var strerror func(int32) string
49+ purego.RegisterLibFunc(&strerror, libc, "strerror")
50+
51+ const expected = "No such file or directory"
52+ got := strerror(int32(errno))
53+ if got != expected {
54+ t.Errorf("strerror returned %q, wanted \"%s\"", got, expected)
55+ }
56+}

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