runtime,runtime/cgo: port ios/arm64 working dir setup from C to Go

Darwin supports libc calls directly from Go, there is no need to
implement them using C.

While here, use CFBundleCopyBundleURL instead of CFBundleCopyResourceURL
+ path manipulation to get the bundle root directory. The former is
the right API for the job.

The resulting code is a bit more involved than expected because libc
wrappers are implemented manually, rather than autogenerated. But
there are already a many handcrafted libc calls wrappers, so this
change doesn't make things much worse.

Change-Id: Ica72c98c05262ee692f6fca0762136abaefbca34
Reviewed-on: https://go-review.googlesource.com/c/go/+/769360
Reviewed-by: Cherry Mui <cherryyz@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
This commit is contained in:
qmuntal 2026-04-21 15:16:48 +02:00 committed by Quim Muntal
parent 95e935b1b3
commit 47cc60743b
8 changed files with 273 additions and 87 deletions

View file

@ -12,7 +12,6 @@ package cgo
/*
#cgo darwin,!arm64 LDFLAGS: -lpthread
#cgo darwin,arm64 LDFLAGS: -framework CoreFoundation
#cgo dragonfly LDFLAGS: -lpthread
#cgo freebsd LDFLAGS: -lpthread
#cgo android LDFLAGS: -llog

View file

@ -1,82 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include <limits.h>
#include <string.h> /* for strerror */
#include <sys/param.h>
#include <unistd.h>
#include <stdlib.h>
#include "libcgo.h"
#include <CoreFoundation/CFBundle.h>
#include <CoreFoundation/CFString.h>
// init_working_dir sets the current working directory to the app root.
// By default ios/arm64 processes start in "/".
static void
init_working_dir()
{
CFBundleRef bundle;
CFURLRef url_ref;
CFStringRef url_str_ref;
char buf[MAXPATHLEN];
Boolean res;
int url_len;
char *dir;
CFStringRef wd_ref;
bundle = CFBundleGetMainBundle();
if (bundle == NULL) {
fprintf(stderr, "runtime/cgo: no main bundle\n");
return;
}
url_ref = CFBundleCopyResourceURL(bundle, CFSTR("Info"), CFSTR("plist"), NULL);
if (url_ref == NULL) {
// No Info.plist found. It can happen on Corellium virtual devices.
return;
}
url_str_ref = CFURLGetString(url_ref);
res = CFStringGetCString(url_str_ref, buf, sizeof(buf), kCFStringEncodingUTF8);
CFRelease(url_ref);
if (!res) {
fprintf(stderr, "runtime/cgo: cannot get URL string\n");
return;
}
// url is of the form "file:///path/to/Info.plist".
// strip it down to the working directory "/path/to".
url_len = strlen(buf);
if (url_len < sizeof("file://")+sizeof("/Info.plist")) {
fprintf(stderr, "runtime/cgo: bad URL: %s\n", buf);
return;
}
buf[url_len-sizeof("/Info.plist")+1] = 0;
dir = &buf[0] + sizeof("file://")-1;
if (chdir(dir) != 0) {
fprintf(stderr, "runtime/cgo: chdir(%s) failed\n", dir);
}
// The test harness in go_ios_exec passes the relative working directory
// in the GoExecWrapperWorkingDirectory property of the app bundle.
wd_ref = CFBundleGetValueForInfoDictionaryKey(bundle, CFSTR("GoExecWrapperWorkingDirectory"));
if (wd_ref != NULL) {
if (!CFStringGetCString(wd_ref, buf, sizeof(buf), kCFStringEncodingUTF8)) {
fprintf(stderr, "runtime/cgo: cannot get GoExecWrapperWorkingDirectory string\n");
return;
}
if (chdir(buf) != 0) {
fprintf(stderr, "runtime/cgo: chdir(%s) failed\n", buf);
}
}
}
static void
init_platform()
{
init_working_dir();
}
void (*x_cgo_init_platform)(void) = init_platform;

View file

@ -9,7 +9,6 @@
// Platform-specific hooks.
void (*x_cgo_inittls)(void **tlsg, void **tlsbase) __attribute__((weak));
void (*x_cgo_init_platform)(void) __attribute__((weak));
void (*x_cgo_threadentry_platform)(void) __attribute__((weak));
static void (*setg_gcc)(void*);
@ -43,9 +42,6 @@ x_cgo_init(G *g, void (*setg)(void*), void **tlsg, void **tlsbase)
if (x_cgo_inittls) {
x_cgo_inittls(tlsg, tlsbase);
}
if (x_cgo_init_platform) {
x_cgo_init_platform();
}
}
void (* _cgo_init)(G*, void (*)(void*), void **, void **) = x_cgo_init;

View file

@ -148,6 +148,7 @@ func osinit() {
physPageSize = getPageSize()
osinit_hack()
initWorkingDir()
}
func sysctlbynameInt32(name []byte) (int32, int32) {

View file

@ -0,0 +1,64 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
import "unsafe"
const (
maxPathLen = 1024
_kCFStringEncodingUTF8 = 0x08000100
)
// initWorkingDir sets the current working directory to the app root on iOS.
// By default ios/arm64 processes start in "/".
func initWorkingDir() {
bundle := cfBundleGetMainBundle()
if bundle == 0 {
writeErrStr("runtime/cgo: no main bundle\n")
return
}
url := cfBundleCopyBundleURL(bundle)
if url == 0 {
// No app bundle URL found.
return
}
var buf [maxPathLen]byte
path := &buf[0]
ok := cfURLGetFileSystemRepresentation(url, true, path, uintptr(len(buf)))
cfRelease(url)
if !ok {
writeErrStr("runtime/cgo: cannot get bundle URL path\n")
return
}
if chdir(path) != 0 {
writeErrStr("runtime/cgo: chdir(")
writeErrData(path, int32(findnull(path)))
writeErrStr(") failed\n")
}
const goExecWrapperWorkingDirectoryKey = "GoExecWrapperWorkingDirectory\x00"
key := cfStringCreateWithCString(0, unsafe.StringData(goExecWrapperWorkingDirectoryKey), _kCFStringEncodingUTF8)
if key == 0 {
writeErrStr("runtime/cgo: cannot create GoExecWrapperWorkingDirectory string\n")
return
}
wd := cfBundleGetValueForInfoDictionaryKey(bundle, key)
cfRelease(key)
if wd == 0 {
return
}
if !cfStringGetCString(wd, path, uintptr(len(buf)), _kCFStringEncodingUTF8) {
writeErrStr("runtime/cgo: cannot get GoExecWrapperWorkingDirectory string\n")
return
}
if chdir(path) != 0 {
writeErrStr("runtime/cgo: chdir(")
writeErrData(path, int32(findnull(path)))
writeErrStr(") failed\n")
}
}

View file

@ -0,0 +1,9 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin && !(ios && arm64)
package runtime
func initWorkingDir() {}

View file

@ -0,0 +1,131 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime
import (
"internal/abi"
"unsafe"
)
// CoreFoundation linker flags for the external linker.
//
//go:cgo_ldflag "-framework"
//go:cgo_ldflag "CoreFoundation"
//go:nosplit
func chdir(path *byte) int32 {
ret := libcCall(unsafe.Pointer(abi.FuncPCABI0(chdir_trampoline)), unsafe.Pointer(&path))
KeepAlive(path)
return ret
}
func chdir_trampoline()
//go:nosplit
func cfBundleGetMainBundle() (bundle uintptr) {
libcCall(unsafe.Pointer(abi.FuncPCABI0(cfBundleGetMainBundle_trampoline)), unsafe.Pointer(&bundle))
return bundle
}
func cfBundleGetMainBundle_trampoline()
//go:nosplit
func cfBundleCopyBundleURL(bundle uintptr) uintptr {
args := struct {
bundle uintptr
ret uintptr
}{bundle: bundle}
libcCall(unsafe.Pointer(abi.FuncPCABI0(cfBundleCopyBundleURL_trampoline)), unsafe.Pointer(&args))
return args.ret
}
func cfBundleCopyBundleURL_trampoline()
//go:nosplit
func cfURLGetFileSystemRepresentation(url uintptr, resolveAgainstBase bool, path *byte, pathLen uintptr) bool {
args := struct {
url, resolveAgainstBase uintptr
path *byte
pathLen uintptr
ret uintptr
}{
url: url,
path: path,
pathLen: pathLen,
}
if resolveAgainstBase {
args.resolveAgainstBase = 1
}
libcCall(unsafe.Pointer(abi.FuncPCABI0(cfURLGetFileSystemRepresentation_trampoline)), unsafe.Pointer(&args))
KeepAlive(path)
return args.ret != 0
}
func cfURLGetFileSystemRepresentation_trampoline()
//go:nosplit
func cfStringCreateWithCString(alloc uintptr, str *byte, encoding uintptr) uintptr {
args := struct {
alloc uintptr
str *byte
encoding uintptr
ret uintptr
}{
alloc: alloc,
str: str,
encoding: encoding,
}
libcCall(unsafe.Pointer(abi.FuncPCABI0(cfStringCreateWithCString_trampoline)), unsafe.Pointer(&args))
KeepAlive(str)
return args.ret
}
func cfStringCreateWithCString_trampoline()
//go:nosplit
func cfBundleGetValueForInfoDictionaryKey(bundle, key uintptr) uintptr {
args := struct {
bundle uintptr
key uintptr
ret uintptr
}{
bundle: bundle,
key: key,
}
libcCall(unsafe.Pointer(abi.FuncPCABI0(cfBundleGetValueForInfoDictionaryKey_trampoline)), unsafe.Pointer(&args))
return args.ret
}
func cfBundleGetValueForInfoDictionaryKey_trampoline()
//go:nosplit
func cfStringGetCString(str uintptr, buf *byte, bufLen uintptr, encoding uintptr) bool {
args := struct {
str uintptr
buf *byte
bufLen uintptr
encoding uintptr
ret uintptr
}{
str: str,
buf: buf,
bufLen: bufLen,
encoding: encoding,
}
libcCall(unsafe.Pointer(abi.FuncPCABI0(cfStringGetCString_trampoline)), unsafe.Pointer(&args))
KeepAlive(buf)
return args.ret != 0
}
func cfStringGetCString_trampoline()
//go:nosplit
func cfRelease(ref uintptr) {
libcCall(unsafe.Pointer(abi.FuncPCABI0(cfRelease_trampoline)), unsafe.Pointer(&ref))
}
func cfRelease_trampoline()
//go:cgo_import_dynamic libc_chdir chdir "/usr/lib/libSystem.B.dylib"
//go:cgo_import_dynamic libc_CFBundleGetMainBundle CFBundleGetMainBundle "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
//go:cgo_import_dynamic libc_CFBundleCopyBundleURL CFBundleCopyBundleURL "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
//go:cgo_import_dynamic libc_CFURLGetFileSystemRepresentation CFURLGetFileSystemRepresentation "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
//go:cgo_import_dynamic libc_CFStringCreateWithCString CFStringCreateWithCString "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
//go:cgo_import_dynamic libc_CFBundleGetValueForInfoDictionaryKey CFBundleGetValueForInfoDictionaryKey "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
//go:cgo_import_dynamic libc_CFStringGetCString CFStringGetCString "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"
//go:cgo_import_dynamic libc_CFRelease CFRelease "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"

View file

@ -0,0 +1,68 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
#include "go_asm.h"
#include "go_tls.h"
#include "textflag.h"
#include "cgo/abi_arm64.h"
TEXT runtime·chdir_trampoline(SB),NOSPLIT,$0
MOVD 0(R0), R0 // arg 1 path
BL libc_chdir(SB)
RET
TEXT runtime·cfBundleGetMainBundle_trampoline(SB),NOSPLIT,$0
MOVD R0, R19
BL libc_CFBundleGetMainBundle(SB)
MOVD R0, 0(R19)
RET
TEXT runtime·cfBundleCopyBundleURL_trampoline(SB),NOSPLIT,$0
MOVD R0, R19
MOVD 0(R0), R0 // arg 1 bundle
BL libc_CFBundleCopyBundleURL(SB)
MOVD R0, 8(R19)
RET
TEXT runtime·cfURLGetFileSystemRepresentation_trampoline(SB),NOSPLIT,$0
MOVD R0, R19
MOVD 8(R0), R1 // arg 2 resolveAgainstBase
MOVD 16(R0), R2 // arg 3 path
MOVD 24(R0), R3 // arg 4 pathLen
MOVD 0(R0), R0 // arg 1 url
BL libc_CFURLGetFileSystemRepresentation(SB)
MOVD R0, 32(R19)
RET
TEXT runtime·cfStringCreateWithCString_trampoline(SB),NOSPLIT,$0
MOVD R0, R19
MOVD 8(R0), R1 // arg 2 str
MOVD 16(R0), R2 // arg 3 encoding
MOVD 0(R0), R0 // arg 1 alloc
BL libc_CFStringCreateWithCString(SB)
MOVD R0, 24(R19)
RET
TEXT runtime·cfBundleGetValueForInfoDictionaryKey_trampoline(SB),NOSPLIT,$0
MOVD R0, R19
MOVD 8(R0), R1 // arg 2 key
MOVD 0(R0), R0 // arg 1 bundle
BL libc_CFBundleGetValueForInfoDictionaryKey(SB)
MOVD R0, 16(R19)
RET
TEXT runtime·cfStringGetCString_trampoline(SB),NOSPLIT,$0
MOVD R0, R19
MOVD 8(R0), R1 // arg 2 buf
MOVD 16(R0), R2 // arg 3 bufLen
MOVD 24(R0), R3 // arg 4 encoding
MOVD 0(R0), R0 // arg 1 str
BL libc_CFStringGetCString(SB)
MOVD R0, 32(R19)
RET
TEXT runtime·cfRelease_trampoline(SB),NOSPLIT,$0
MOVD 0(R0), R0 // arg 1 ref
BL libc_CFRelease(SB)
RET