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 Unverified
B5690EEEBB952194The key is unknown
5 files · +47 -2
M dlfcn_darwin.go +1 -0
| @@ -17,3 +17,4 @@ const ( | |||
| 17 | 17 | //go:cgo_import_dynamic purego_dlsym dlsym "/usr/lib/libSystem.B.dylib" | |
| 18 | 18 | //go:cgo_import_dynamic purego_dlerror dlerror "/usr/lib/libSystem.B.dylib" | |
| 19 | 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" | |
| @@ -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 | |||
| 91 | 91 | MOVQ X0, syscall15Args_f1(DI) // f1 | |
| 92 | 92 | 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 | 100 | XORL AX, AX // no error (it's ignored anyway) | |
| 95 | 101 | ADDQ $STACK_SIZE, SP | |
| 96 | 102 | MOVQ BP, SP | |
| @@ -91,6 +91,12 @@ TEXT syscall15X(SB), NOSPLIT|NOFRAME, $0 | |||||
| 91 | MOVQ X0, syscall15Args_f1(DI) // f1 | 91 | MOVQ X0, syscall15Args_f1(DI) // f1 | ||
| 92 | MOVQ X1, syscall15Args_f2(DI) // f2 | 92 | 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, SP | 101 | ADDQ $STACK_SIZE, SP | ||
| 96 | MOVQ BP, SP | 102 | MOVQ BP, SP | ||
M sys_arm64.s +5 -0
| @@ -89,4 +89,9 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||
| 89 | 89 | FMOVD F2, syscall15Args_f3(R2) // save f2 | |
| 90 | 90 | 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 | 97 | RET | |
| @@ -89,4 +89,9 @@ TEXT syscall15X(SB), NOSPLIT, $0 | |||||
| 89 | FMOVD F2, syscall15Args_f3(R2) // save f2 | 89 | FMOVD F2, syscall15Args_f3(R2) // save f2 | ||
| 90 | FMOVD F3, syscall15Args_f4(R2) // save f3 | 90 | 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 | RET | 97 | 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 | |||
| 26 | 26 | } | |
| 27 | 27 | ||
| 28 | 28 | runtime_cgocall(syscall15XABI0, unsafe.Pointer(args)) | |
| 29 | - | return args.a1, args.a2, 0 | |
| 29 | + | return args.a1, args.a2, args.a3 | |
| 30 | 30 | } | |
| 31 | 31 | ||
| 32 | 32 | // 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, 0 | 29 | + | 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 | |||
| 5 | 5 | ||
| 6 | 6 | import ( | |
| 7 | 7 | "os" | |
| 8 | + | "runtime" | |
| 8 | 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 | 16 | func TestOS(t *testing.T) { | |
| @@ -21,3 +24,33 @@ func TestOS(t *testing.T) { | |||
| 21 | 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 | + | } | |
| @@ -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 | + | } | |||