@@ -27,16 +27,37 @@ const (
2727 SystemLibPath = "/usr/lib/libSystem.B.dylib"
2828)
2929
30+ // Library handles are opened once and shared for the process lifetime.
31+ // Opening and closing them on every call causes SIGBUS/SIGSEGV crashes because
32+ // the Go runtime (GC, timers) can interact with invalidated library handles
33+ // after Dlclose. Sharing also keeps the resolved-symbol cache in fnMap warm,
34+ // so a symbol lookup is not repeated on every call.
35+ // See: https://github.com/shirou/gopsutil/issues/1832
36+ var (
37+ libCacheMu sync.Mutex
38+ libCache = make (map [string ]* library )
39+ )
40+
3041func newLibrary (path string ) (* library , error ) {
31- lib , err := purego .Dlopen (path , purego .RTLD_LAZY | purego .RTLD_GLOBAL )
42+ libCacheMu .Lock ()
43+ defer libCacheMu .Unlock ()
44+
45+ if lib , ok := libCache [path ]; ok {
46+ return lib , nil
47+ }
48+
49+ handle , err := purego .Dlopen (path , purego .RTLD_LAZY | purego .RTLD_GLOBAL )
3250 if err != nil {
3351 return nil , err
3452 }
3553
36- return & library {
37- handle : lib ,
54+ lib := & library {
55+ handle : handle ,
3856 fnMap : make (map [string ]any ),
39- }, nil
57+ }
58+ libCache [path ] = lib
59+
60+ return lib , nil
4061}
4162
4263func (lib * library ) Dlsym (symbol string ) (uintptr , error ) {
@@ -69,9 +90,10 @@ func getFunc[T any](lib *library, symbol string) T {
6990 return dlfun .fn
7091}
7192
72- func (lib * library ) Close () {
73- purego .Dlclose (lib .handle )
74- }
93+ // Close is a no-op, kept so that existing defer-Close call sites keep working.
94+ // Library handles are shared process-wide and are deliberately never unloaded;
95+ // see newLibrary for why.
96+ func (* library ) Close () {}
7597
7698type dlFunc [T any ] struct {
7799 sym string
@@ -103,12 +125,12 @@ func (c *CoreFoundationLib) CFGetTypeID(cf uintptr) int64 {
103125 return fn (cf )
104126}
105127
106- func (c * CoreFoundationLib ) CFNumberCreate (allocator uintptr , theType int64 , valuePtr uintptr ) unsafe.Pointer {
128+ func (c * CoreFoundationLib ) CFNumberCreate (allocator uintptr , theType int64 , valuePtr unsafe. Pointer ) unsafe.Pointer {
107129 fn := getFunc [CFNumberCreateFunc ](c .library , "CFNumberCreate" )
108130 return fn (allocator , theType , valuePtr )
109131}
110132
111- func (c * CoreFoundationLib ) CFNumberGetValue (num uintptr , theType int64 , valuePtr uintptr ) bool {
133+ func (c * CoreFoundationLib ) CFNumberGetValue (num uintptr , theType int64 , valuePtr unsafe. Pointer ) bool {
112134 fn := getFunc [CFNumberGetValueFunc ](c .library , "CFNumberGetValue" )
113135 return fn (num , theType , valuePtr )
114136}
@@ -247,7 +269,9 @@ func (l *IOKitLib) IOObjectRelease(object uint32) int32 {
247269 return fn (object )
248270}
249271
250- func (l * IOKitLib ) IOConnectCallStructMethod (connection , selector uint32 , inputStruct , inputStructCnt , outputStruct uintptr , outputStructCnt * uintptr ) int32 {
272+ func (l * IOKitLib ) IOConnectCallStructMethod (connection , selector uint32 , inputStruct unsafe.Pointer , inputStructCnt uintptr ,
273+ outputStruct unsafe.Pointer , outputStructCnt * uintptr ,
274+ ) int32 {
251275 fn := getFunc [IOConnectCallStructMethodFunc ](l .library , "IOConnectCallStructMethod" )
252276 return fn (connection , selector , inputStruct , inputStructCnt , outputStruct , outputStructCnt )
253277}
@@ -294,14 +318,14 @@ func NewSystemLib() (*SystemLib, error) {
294318 return & SystemLib {library }, nil
295319}
296320
297- func (s * SystemLib ) HostProcessorInfo (host uint32 , flavor int32 , outProcessorCount * uint32 , outProcessorInfo uintptr ,
321+ func (s * SystemLib ) HostProcessorInfo (host uint32 , flavor int32 , outProcessorCount * uint32 , outProcessorInfo unsafe. Pointer ,
298322 outProcessorInfoCnt * uint32 ,
299323) int32 {
300324 fn := getFunc [HostProcessorInfoFunc ](s .library , "host_processor_info" )
301325 return fn (host , flavor , outProcessorCount , outProcessorInfo , outProcessorInfoCnt )
302326}
303327
304- func (s * SystemLib ) HostStatistics (host uint32 , flavor int32 , hostInfoOut uintptr , hostInfoOutCnt * uint32 ) int32 {
328+ func (s * SystemLib ) HostStatistics (host uint32 , flavor int32 , hostInfoOut unsafe. Pointer , hostInfoOutCnt * uint32 ) int32 {
305329 fn := getFunc [HostStatisticsFunc ](s .library , "host_statistics" )
306330 return fn (host , flavor , hostInfoOut , hostInfoOutCnt )
307331}
@@ -316,7 +340,7 @@ func (s *SystemLib) MachTaskSelf() uint32 {
316340 return fn ()
317341}
318342
319- func (s * SystemLib ) MachTimeBaseInfo (info uintptr ) int32 {
343+ func (s * SystemLib ) MachTimeBaseInfo (info unsafe. Pointer ) int32 {
320344 fn := getFunc [MachTimeBaseInfoFunc ](s .library , "mach_timebase_info" )
321345 return fn (info )
322346}
@@ -326,12 +350,12 @@ func (s *SystemLib) VMDeallocate(targetTask uint32, vmAddress, vmSize uintptr) i
326350 return fn (targetTask , vmAddress , vmSize )
327351}
328352
329- func (s * SystemLib ) ProcPidPath (pid int32 , buffer uintptr , bufferSize uint32 ) int32 {
353+ func (s * SystemLib ) ProcPidPath (pid int32 , buffer unsafe. Pointer , bufferSize uint32 ) int32 {
330354 fn := getFunc [ProcPidPathFunc ](s .library , "proc_pidpath" )
331355 return fn (pid , buffer , bufferSize )
332356}
333357
334- func (s * SystemLib ) ProcPidInfo (pid , flavor int32 , arg uint64 , buffer uintptr , bufferSize int32 ) int32 {
358+ func (s * SystemLib ) ProcPidInfo (pid , flavor int32 , arg uint64 , buffer unsafe. Pointer , bufferSize int32 ) int32 {
335359 fn := getFunc [ProcPidInfoFunc ](s .library , "proc_pidinfo" )
336360 return fn (pid , flavor , arg , buffer , bufferSize )
337361}
@@ -341,16 +365,36 @@ func (s *SystemLib) ProcPidRusage(pid, flavor int32, buffer unsafe.Pointer) int3
341365 return fn (pid , flavor , buffer )
342366}
343367
344- func (s * SystemLib ) Errno () int32 {
368+ // ErrnoLocation returns a pointer to the calling OS thread's errno, as given by
369+ // libc's __error(). The pointer is thread-local, so callers must hold
370+ // runtime.LockOSThread() across this call, the libc call being checked, and the
371+ // dereference of the returned pointer.
372+ //
373+ // Resolve the pointer *before* the libc call whose errno is to be inspected.
374+ // Resolving a symbol performs a dlsym and allocates, and either may overwrite
375+ // errno; doing it afterwards would race with the value being read.
376+ func (s * SystemLib ) ErrnoLocation () * int32 {
345377 fn := getFunc [ErrnoFunc ](s .library , "__error" )
346- return * fn ()
378+ return fn ()
347379}
348380
349381// status codes
350382const (
351383 KERN_SUCCESS = 0
352384)
353385
386+ // Arguments that point at Go memory are declared as unsafe.Pointer, a Go
387+ // pointer type, or a slice -- never as uintptr. purego maps uintptr to
388+ // uintptr_t, i.e. a plain integer: it neither keeps the pointee alive nor makes
389+ // it escape, so a Go local stays on the goroutine stack and the address goes
390+ // stale the moment the stack grows. Every such call then reads or writes the old
391+ // stack and silently sees or produces zeroes. The other kinds are passed through
392+ // reflect.Value.Pointer() and stay reachable for the duration of the call.
393+ //
394+ // uintptr remains correct for handles that are not Go memory: CoreFoundation and
395+ // IOKit object references, mach ports, addresses of dylib data symbols obtained
396+ // through Dlsym, and kernel-allocated vm addresses.
397+
354398// IOKit types and constants.
355399type (
356400 IOServiceGetMatchingServiceFunc func (mainPort uint32 , matching uintptr ) uint32
@@ -365,7 +409,8 @@ type (
365409 IORegistryEntryCreateCFPropertiesFunc func (entry uint32 , properties unsafe.Pointer , allocator uintptr , options uint32 ) int32
366410 IOObjectConformsToFunc func (object uint32 , className string ) bool
367411 IOObjectReleaseFunc func (object uint32 ) int32
368- IOConnectCallStructMethodFunc func (connection , selector uint32 , inputStruct , inputStructCnt , outputStruct uintptr , outputStructCnt * uintptr ) int32
412+ IOConnectCallStructMethodFunc func (connection , selector uint32 , inputStruct unsafe.Pointer , inputStructCnt uintptr ,
413+ outputStruct unsafe.Pointer , outputStructCnt * uintptr ) int32
369414
370415 IOHIDEventSystemClientCreateFunc func (allocator uintptr ) unsafe.Pointer
371416 IOHIDEventSystemClientSetMatchingFunc func (client , match uintptr ) int32
@@ -390,10 +435,14 @@ const (
390435)
391436
392437// CoreFoundation types and constants.
438+ //
439+ // valuePtr on CFNumberCreate and CFNumberGetValue points at a caller-owned Go
440+ // value that CoreFoundation reads from or writes into, hence unsafe.Pointer; see
441+ // the note above the IOKit function types.
393442type (
394443 CFGetTypeIDFunc func (cf uintptr ) int64
395- CFNumberCreateFunc func (allocator uintptr , theType int64 , valuePtr uintptr ) unsafe.Pointer
396- CFNumberGetValueFunc func (num uintptr , theType int64 , valuePtr uintptr ) bool
444+ CFNumberCreateFunc func (allocator uintptr , theType int64 , valuePtr unsafe. Pointer ) unsafe.Pointer
445+ CFNumberGetValueFunc func (num uintptr , theType int64 , valuePtr unsafe. Pointer ) bool
397446 CFDictionaryCreateFunc func (allocator uintptr , keys , values * unsafe.Pointer , numValues int64 ,
398447 keyCallBacks , valueCallBacks uintptr ) unsafe.Pointer
399448 CFDictionaryAddValueFunc func (theDict , key , value uintptr )
@@ -423,13 +472,16 @@ type MachTimeBaseInfo struct {
423472 Denom uint32
424473}
425474
475+ // Buffers the kernel writes into are declared as unsafe.Pointer; see the note
476+ // above the IOKit function types. vmAddress on VMDeallocateFunc stays a uintptr
477+ // because it names kernel-allocated memory rather than Go memory.
426478type (
427- HostProcessorInfoFunc func (host uint32 , flavor int32 , outProcessorCount * uint32 , outProcessorInfo uintptr ,
479+ HostProcessorInfoFunc func (host uint32 , flavor int32 , outProcessorCount * uint32 , outProcessorInfo unsafe. Pointer ,
428480 outProcessorInfoCnt * uint32 ) int32
429- HostStatisticsFunc func (host uint32 , flavor int32 , hostInfoOut uintptr , hostInfoOutCnt * uint32 ) int32
481+ HostStatisticsFunc func (host uint32 , flavor int32 , hostInfoOut unsafe. Pointer , hostInfoOutCnt * uint32 ) int32
430482 MachHostSelfFunc func () uint32
431483 MachTaskSelfFunc func () uint32
432- MachTimeBaseInfoFunc func (info uintptr ) int32
484+ MachTimeBaseInfoFunc func (info unsafe. Pointer ) int32
433485 VMDeallocateFunc func (targetTask uint32 , vmAddress , vmSize uintptr ) int32
434486)
435487
@@ -450,8 +502,8 @@ const (
450502)
451503
452504type (
453- ProcPidPathFunc func (pid int32 , buffer uintptr , bufferSize uint32 ) int32
454- ProcPidInfoFunc func (pid , flavor int32 , arg uint64 , buffer uintptr , bufferSize int32 ) int32
505+ ProcPidPathFunc func (pid int32 , buffer unsafe. Pointer , bufferSize uint32 ) int32
506+ ProcPidInfoFunc func (pid , flavor int32 , arg uint64 , buffer unsafe. Pointer , bufferSize int32 ) int32
455507 ProcPidRusageFunc func (pid , flavor int32 , buffer unsafe.Pointer ) int32
456508 ErrnoFunc func () * int32
457509)
@@ -525,15 +577,18 @@ func NewSMC() (*SMC, error) {
525577 }, nil
526578}
527579
528- func (s * SMC ) CallStruct (selector uint32 , inputStruct , inputStructCnt , outputStruct uintptr , outputStructCnt * uintptr ) int32 {
580+ func (s * SMC ) CallStruct (selector uint32 , inputStruct unsafe.Pointer , inputStructCnt uintptr ,
581+ outputStruct unsafe.Pointer , outputStructCnt * uintptr ,
582+ ) int32 {
529583 return s .lib .IOConnectCallStructMethod (s .conn , selector , inputStruct , inputStructCnt , outputStruct , outputStructCnt )
530584}
531585
586+ // Close releases the SMC connection. The IOKit handle itself is shared
587+ // process-wide and is deliberately left open; see newLibrary.
532588func (s * SMC ) Close () error {
533589 if result := s .lib .IOServiceClose (s .conn ); result != 0 {
534590 return errors .New ("ERROR: IOServiceClose failed" )
535591 }
536- s .lib .Close ()
537592 return nil
538593}
539594
@@ -555,10 +610,6 @@ func (s CStr) Ptr() *byte {
555610 return & s [0 ]
556611}
557612
558- func (s CStr ) Addr () uintptr {
559- return uintptr (unsafe .Pointer (s .Ptr ()))
560- }
561-
562613func (s CStr ) GoString () string {
563614 if s == nil {
564615 return ""
0 commit comments