Skip to content

[darwin][process]: fix errno handling and library lifetime on darwin#2119

Open
shirou wants to merge 2 commits into
masterfrom
fix/darwin-errno-and-libcache
Open

[darwin][process]: fix errno handling and library lifetime on darwin#2119
shirou wants to merge 2 commits into
masterfrom
fix/darwin-errno-and-libcache

Conversation

@shirou

@shirou shirou commented Jul 19, 2026

Copy link
Copy Markdown
Owner

This is a follow-up PR on #2117.

Read errno through __error() resolved before the libc call, so that the dlsym and allocation performed by symbol resolution cannot overwrite it first. Apply the same to CwdWithContext, which additionally read errno without checking for failure and could surface a stale EPERM.

Share dlopen handles process-wide instead of opening and closing them per call, completing for libSystem what #1832 did for IOKit and CoreFoundation.

Stop populating ReadBytes/WriteBytes on darwin, which #2117 filled with the disk figures. They mean all I/O including cache on other platforms, so reusing the disk value there would silently change what the field means depending on the platform.

Read errno through __error() resolved before the libc call, so that the
dlsym and allocation performed by symbol resolution cannot overwrite it
first. Apply the same to CwdWithContext, which additionally read errno
without checking for failure and could surface a stale EPERM.

Share dlopen handles process-wide instead of opening and closing them per
call, completing for libSystem what #1832 did for IOKit and CoreFoundation.

Stop populating ReadBytes/WriteBytes on darwin, which #2117 filled with the
disk figures. They mean all I/O including cache on other platforms, so
reusing the disk value there would silently change what the field means
depending on the platform.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Darwin process handling by making errno retrieval reliable around libc calls, sharing purego dlopen handles for the lifetime of the process to avoid dlclose-related crashes, and aligning IOCounters semantics with other platforms by not mislabeling disk-only bytes as generic I/O bytes.

Changes:

  • Fix errno handling on Darwin by resolving __error() (errno location) before the libc call and reading it only after failure, while holding runtime.LockOSThread().
  • Make Darwin dynamic library handles shared process-wide (no dlclose), with a thread-safe cached symbol-resolution map.
  • Stop populating ReadBytes/WriteBytes on Darwin IOCounters (leave them at 0) and update docs/tests accordingly.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
process/process.go Updates IOCounters field docs to clarify Darwin semantics vs platforms that expose all-I/O byte counts.
process/process_darwin.go Fixes errno read timing/thread affinity for IOCountersWithContext and CwdWithContext, and stops setting generic Read/Write bytes on Darwin.
process/process_darwin_test.go Strengthens struct layout validation and updates IOCounters tests to reflect disk-only semantics on Darwin.
internal/common/common_darwin.go Introduces process-wide dylib handle caching (no dlclose) and thread-safe symbol caching; adds ErrnoLocation() helper.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

purego maps uintptr to uintptr_t, a plain integer: it neither keeps the
pointee alive nor makes it escape. A Go local therefore stays on the
goroutine stack, and its address goes stale as soon as the stack grows,
leaving the kernel to write into the old stack while the caller reads
zeroes.

This was latent until the shared library handle removed the per-call
Dlopen, whose deep call chain had been growing the stack before the
address was taken and hiding the bug. TestMemoryInfo and TestExe caught
it; TestNumThread had been asserting only n >= 0 and passing on zero.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (1)

internal/common/common_darwin.go:132

  • CFNumberCreate was updated to accept unsafe.Pointer to avoid passing stack addresses as uintptr into purego (which can go stale on stack growth). CFNumberGetValue (and CFNumberGetValueFunc) still take valuePtr as uintptr even though it is an output buffer written by CoreFoundation, so it can suffer the same stale-address/zeroed-results issue (e.g., callers pass pointers into local structs). Consider switching CFNumberGetValue to unsafe.Pointer and updating call sites accordingly.
func (c *CoreFoundationLib) CFNumberCreate(allocator uintptr, theType int64, valuePtr unsafe.Pointer) unsafe.Pointer {
	fn := getFunc[CFNumberCreateFunc](c.library, "CFNumberCreate")
	return fn(allocator, theType, valuePtr)
}

func TestIOCountersDarwinNonExistentProcess(t *testing.T) {
p := &Process{Pid: 999999}
func TestIOCounters_NonExistent(t *testing.T) {
p := &Process{Pid: 99999}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants