diff --git a/misc/cgo/testcarchive/testdata/libgo/libgo.go b/misc/cgo/testcarchive/testdata/libgo/libgo.go index 37b30c14632dca..3a0684e512a6c4 100644 --- a/misc/cgo/testcarchive/testdata/libgo/libgo.go +++ b/misc/cgo/testcarchive/testdata/libgo/libgo.go @@ -46,7 +46,11 @@ func DidMainRun() bool { return ranMain } //export CheckArgs func CheckArgs() { - if len(os.Args) != 3 || os.Args[1] != "arg1" || os.Args[2] != "arg2" { + // Dynamic linkers which supply the library initialization functions with the + // main program's argc / argc should have 3 args here, else they should have + // none. + valid := (len(os.Args) == 3 && os.Args[1] == "arg1" && os.Args[2] == "arg2") || (len(os.Args) == 0) + if !valid { fmt.Printf("CheckArgs: want [_, arg1, arg2], got: %v\n", os.Args) os.Exit(2) } diff --git a/src/runtime/cgo.go b/src/runtime/cgo.go index d90468240df97c..fe1ca29b5e1398 100644 --- a/src/runtime/cgo.go +++ b/src/runtime/cgo.go @@ -12,6 +12,7 @@ import "unsafe" //go:linkname _cgo_init _cgo_init //go:linkname _cgo_thread_start _cgo_thread_start +//go:linkname _cgo_sys_lib_args_valid _cgo_sys_lib_args_valid //go:linkname _cgo_sys_thread_create _cgo_sys_thread_create //go:linkname _cgo_notify_runtime_init_done _cgo_notify_runtime_init_done //go:linkname _cgo_callers _cgo_callers @@ -21,6 +22,7 @@ import "unsafe" var ( _cgo_init unsafe.Pointer _cgo_thread_start unsafe.Pointer + _cgo_sys_lib_args_valid unsafe.Pointer _cgo_sys_thread_create unsafe.Pointer _cgo_notify_runtime_init_done unsafe.Pointer _cgo_callers unsafe.Pointer diff --git a/src/runtime/cgo/callbacks.go b/src/runtime/cgo/callbacks.go index cd8b79538745dd..452312d291516a 100644 --- a/src/runtime/cgo/callbacks.go +++ b/src/runtime/cgo/callbacks.go @@ -58,6 +58,14 @@ var _cgo_init = &x_cgo_init var x_cgo_thread_start byte var _cgo_thread_start = &x_cgo_thread_start +// Determines if the argc / argv passed to the library initialization functions +// are valid. +//go:cgo_import_static x_cgo_sys_lib_args_valid +//go:linkname x_cgo_sys_lib_args_valid x_cgo_sys_lib_args_valid +//go:linkname _cgo_sys_lib_args_valid _cgo_sys_lib_args_valid +var x_cgo_sys_lib_args_valid byte +var _cgo_sys_lib_args_valid = &x_cgo_sys_lib_args_valid + // Creates a new system thread without updating any Go state. // // This method is invoked during shared library loading to create a new OS diff --git a/src/runtime/cgo/gcc_libinit.c b/src/runtime/cgo/gcc_libinit.c index 3304d95fdf9684..e019ed8b027aad 100644 --- a/src/runtime/cgo/gcc_libinit.c +++ b/src/runtime/cgo/gcc_libinit.c @@ -7,6 +7,7 @@ #include #include +#include #include #include #include // strerror @@ -21,6 +22,20 @@ static int runtime_init_done; // The context function, used when tracing back C calls into Go. static void (*cgo_context_function)(struct context_arg*); +// Detect if using glibc +int +x_cgo_sys_lib_args_valid() +{ + // The ELF gABI doesn't require an argc / argv to be passed to the functions + // in the DT_INIT_ARRAY. However, glibc always does. + // Ignore uClibc masquerading as glibc. +#if defined(__GLIBC__) && !defined(__UCLIBC__) + return 1; +#else + return 0; +#endif +} + void x_cgo_sys_thread_create(void* (*func)(void*), void* arg) { pthread_t p; diff --git a/src/runtime/cgo/gcc_libinit_windows.c b/src/runtime/cgo/gcc_libinit_windows.c index ad5038667a5b62..b51f33bb8a0c37 100644 --- a/src/runtime/cgo/gcc_libinit_windows.c +++ b/src/runtime/cgo/gcc_libinit_windows.c @@ -51,6 +51,11 @@ _cgo_maybe_run_preinit() { } } +int +x_cgo_sys_lib_args_valid() { + return 1; +} + void x_cgo_sys_thread_create(void (*func)(void*), void* arg) { uintptr_t thandle; diff --git a/src/runtime/os_linux.go b/src/runtime/os_linux.go index eb8aa076e9f9b1..959c39b3d81dcc 100644 --- a/src/runtime/os_linux.go +++ b/src/runtime/os_linux.go @@ -216,21 +216,31 @@ var addrspace_vec [1]byte func mincore(addr unsafe.Pointer, n uintptr, dst *byte) int32 func sysargs(argc int32, argv **byte) { - n := argc + 1 - - // skip over argv, envp to get to auxv - for argv_index(argv, n) != nil { - n++ + argsValid := true + if islibrary || isarchive { + if !sysLibArgsValid() { + argsValid = false + } } - // skip NULL separator - n++ + if argsValid { + n := argc + 1 - // now argv+n is auxv - auxv := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*goarch.PtrSize)) - if sysauxv(auxv[:]) != 0 { - return + // skip over argv, envp to get to auxv + for argv_index(argv, n) != nil { + n++ + } + + // skip NULL separator + n++ + + // now argv+n is auxv + auxv := (*[1 << 28]uintptr)(add(unsafe.Pointer(argv), uintptr(n)*goarch.PtrSize)) + if sysauxv(auxv[:]) != 0 { + return + } } + // In some situations we don't get a loader-provided // auxv, such as when loaded as a library on Android. // Fall back to /proc/self/auxv. @@ -259,7 +269,7 @@ func sysargs(argc int32, argv **byte) { return } var buf [128]uintptr - n = read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf))) + n := read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf))) closefd(fd) if n < 0 { return diff --git a/src/runtime/runtime1.go b/src/runtime/runtime1.go index 65e1e0eebca1c1..6b7fd48043020e 100644 --- a/src/runtime/runtime1.go +++ b/src/runtime/runtime1.go @@ -52,15 +52,174 @@ var ( argv **byte ) +// when using -buildmode=c-archive or -buildmode=c-shared on linux +// we have to first make sure that glibc is being used or else +// we cannot rely on argc/argv/auxv to be accurate +func sysLibArgsValid() bool { + if _cgo_sys_lib_args_valid != nil { + ret := asmcgocall(_cgo_sys_lib_args_valid, nil) + if ret != 1 { + return false + } + } + return true +} + // nosplit for use in linux startup sysargs //go:nosplit func argv_index(argv **byte, i int32) *byte { + if islibrary || isarchive { + if !sysLibArgsValid() { + return nil + } + } return *(**byte)(add(unsafe.Pointer(argv), uintptr(i)*goarch.PtrSize)) } +// when using -buildmode=c-archive or -buildmode=c-shared on linux +// we have to first make sure that glibc is being used or else +// we cannot rely on argc/argv/auxv to be accurate +func sysLibArgsValid() bool { + if _cgo_sys_lib_args_valid != nil { + ret := asmcgocall(_cgo_sys_lib_args_valid, nil) + if ret != 1 { + return false + } + } + return true +} + +var procCmdline = []byte("/proc/self/cmdline\x00") +var procEnviron = []byte("/proc/self/environ\x00") + func args(c int32, v **byte) { - argc = c - argv = v + if sysLibArgsValid() { + argc = c + argv = v + } else if GOOS == "linux" { + argc = 0 + argv = nil + + // get argc and argv size + var argvSize int32 = 0 + fd := open(&procCmdline[0], 0 /* O_RDONLY */, 0) + if fd >= 0 { + for { + var buf [128]byte + c := read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf))) + if c <= 0 { + break + } + + argvSize += c + + i := c + for i = 0; i < c; i++ { + if buf[i] == 0 { + argc++ + } + } + } + + closefd(fd) + } + + var environSize int32 = 0 + var envc int32 = 0 + fd = open(&procEnviron[0], 0 /* O_RDONLY */, 0) + if fd >= 0 { + for { + var buf [128]byte + c := read(fd, noescape(unsafe.Pointer(&buf[0])), int32(unsafe.Sizeof(buf))) + if c <= 0 { + break + } + + environSize += c + + i := c + for i = 0; i < c; i++ { + if buf[i] == 0 { + envc++ + } + } + } + + closefd(fd) + } + + argv = (**byte)(unsafe.Pointer(persistentalloc(goarch.PtrSize*(uintptr(argc)+uintptr(envc)+1), 0, &memstats.other_sys))) + argvPtr := (**byte)(add(unsafe.Pointer(argv), goarch.PtrSize*(uintptr(argc)+uintptr(envc)+1))) + *argvPtr = (*byte)(nil) //null terminate array + + if argvSize > 0 { + argvBuf := unsafe.Pointer(persistentalloc(uintptr(argvSize), 0, &memstats.other_sys)) + fd := open(&procCmdline[0], 0 /* O_RDONLY */, 0) + if fd >= 0 { + c := read(fd, noescape(argvBuf), int32(argvSize)) + if c < 0 { + throw("failed to read arguments") + return + } + + if c != int32(argvSize) { + throw("short read arguments") + return + } + + strStart := int32(0) + strNum := 0 + + i := c + var b *byte + for i = 0; i < c; i++ { + b = (*byte)(add(argvBuf, uintptr(i))) + if *b == 0 { + argvPtr := (**byte)(add(unsafe.Pointer(argv), goarch.PtrSize*uintptr(strNum))) + *argvPtr = (*byte)(add(argvBuf, uintptr(strStart))) + strStart = i + 1 + strNum++ + } + } + + closefd(fd) + } + } + + if environSize > 0 { + environBuf := unsafe.Pointer(persistentalloc(uintptr(environSize), 0, &memstats.other_sys)) + fd := open(&procEnviron[0], 0 /* O_RDONLY */, 0) + if fd >= 0 { + c := read(fd, noescape(environBuf), int32(environSize)) + if c < 0 { + throw("failed to read environment") + return + } + + if c != int32(environSize) { + throw("short read environment") + return + } + + strStart := int32(0) + strNum := 0 + + i := c + var b *byte + for i = 0; i < c; i++ { + b = (*byte)(add(environBuf, uintptr(i))) + if *b == 0 { + argvPtr := (**byte)(add(unsafe.Pointer(argv), goarch.PtrSize*(uintptr(argc)+uintptr(strNum)))) + *argvPtr = (*byte)(add(environBuf, uintptr(strStart))) + strStart = i + 1 + strNum++ + } + } + + closefd(fd) + } + } + } sysargs(c, v) } @@ -68,6 +227,13 @@ func goargs() { if GOOS == "windows" { return } + + if islibrary || isarchive { + if !sysLibArgsValid() { + return + } + } + argslice = make([]string, argc) for i := int32(0); i < argc; i++ { argslice[i] = gostringnocopy(argv_index(argv, i)) @@ -75,6 +241,13 @@ func goargs() { } func goenvs_unix() { + if islibrary || isarchive { + if !sysLibArgsValid() { + envs = make([]string, 0) + return + } + } + // TODO(austin): ppc64 in dynamic linking mode doesn't // guarantee env[] will immediately follow argv. Might cause // problems.