Skip to content

Commit 13c4362

Browse files
Address review: use LibraryImport and cover macOS
- Convert the dladdr/dlopen P/Invokes from DllImport to source-generated LibraryImport (resolves SYSLIB1054). - Extend the module pin to macOS in addition to Linux: the same dlclose + NativeAOT pthread-destructor teardown crash applies. Select the correct RTLD_NOLOAD/RTLD_NODELETE flag values and system library (libc.so.6 on Linux, libSystem on macOS) per platform. macOS remains best-effort and is unvalidated (Linux verified: pin ok, repro exits 0 on Node 24.13 and 24.18). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 144c87db-8d78-474f-bff9-21031a23e3e7
1 parent f913197 commit 13c4362

1 file changed

Lines changed: 32 additions & 12 deletions

File tree

‎src/NodeApi/DotNetHost/NativeHost.cs‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,11 @@ public static void Trace(string msg)
5858
/// process crashes with SIGSEGV as the thread exits (glibc <c>__nptl_deallocate_tsd</c>).
5959
/// Keeping the module mapped for the lifetime of the process keeps that destructor valid.
6060
/// <para/>
61-
/// This only affects Unix (glibc) hosting; on Windows module/thread teardown does not hit
62-
/// this issue. The pin is best-effort: any failure is traced but does not block init.
61+
/// This affects Unix hosting (Linux and macOS), which unload modules via <c>dlclose</c> and
62+
/// run NativeAOT's per-thread destructors from the dynamic loader; on Windows module/thread
63+
/// teardown does not hit this issue. The macOS path mirrors the Linux one but uses that
64+
/// platform's <c>RTLD_*</c> flag values and system library. The pin is best-effort: any
65+
/// failure is traced but does not block init.
6366
/// </remarks>
6467
private static unsafe void PreventModuleUnload()
6568
{
@@ -70,7 +73,8 @@ private static unsafe void PreventModuleUnload()
7073

7174
s_moduleUnloadPrevented = true;
7275

73-
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
76+
bool isMacOS = RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
77+
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && !isMacOS)
7478
{
7579
return;
7680
}
@@ -83,15 +87,24 @@ private static unsafe void PreventModuleUnload()
8387
(nint)(delegate* unmanaged[Cdecl]<napi_env, napi_value, napi_value>)
8488
&InitializeModule;
8589

86-
if (dladdr(moduleFunction, out Dl_info info) != 0 && info.dli_fname != default)
90+
Dl_info info;
91+
int found = isMacOS
92+
? DlAddrMacOS(moduleFunction, out info)
93+
: DlAddrLinux(moduleFunction, out info);
94+
95+
if (found != 0 && info.dli_fname != default)
8796
{
8897
// RTLD_NOLOAD resolves the already-loaded module without loading a new copy;
8998
// RTLD_NODELETE keeps it mapped for the process lifetime. The extra (never
90-
// released) reference also prevents Node's dlclose from unmapping it.
99+
// released) reference also prevents Node's dlclose from unmapping it. The flag
100+
// values differ between glibc and macOS/dyld.
91101
const int RTLD_LAZY = 0x0001;
92-
const int RTLD_NOLOAD = 0x0004;
93-
const int RTLD_NODELETE = 0x1000;
94-
nint handle = dlopen(info.dli_fname, RTLD_LAZY | RTLD_NOLOAD | RTLD_NODELETE);
102+
int rtldNoLoad = isMacOS ? 0x0010 : 0x0004;
103+
int rtldNoDelete = isMacOS ? 0x0080 : 0x1000;
104+
int flags = RTLD_LAZY | rtldNoLoad | rtldNoDelete;
105+
nint handle = isMacOS
106+
? DlOpenMacOS(info.dli_fname, flags)
107+
: DlOpenLinux(info.dli_fname, flags);
95108
Trace($" Pinned native host module ({(handle != default ? "ok" : "no-op")}).");
96109
}
97110
else
@@ -114,11 +127,18 @@ private struct Dl_info
114127
public nint dli_saddr;
115128
}
116129

117-
[DllImport("libc.so.6")]
118-
private static extern int dladdr(nint addr, out Dl_info info);
130+
// dladdr / dlopen live in libc.so.6 on Linux (glibc) and libSystem on macOS.
131+
[LibraryImport("libc.so.6", EntryPoint = "dladdr")]
132+
private static partial int DlAddrLinux(nint addr, out Dl_info info);
133+
134+
[LibraryImport("libSystem", EntryPoint = "dladdr")]
135+
private static partial int DlAddrMacOS(nint addr, out Dl_info info);
136+
137+
[LibraryImport("libc.so.6", EntryPoint = "dlopen")]
138+
private static partial nint DlOpenLinux(nint filename, int flags);
119139

120-
[DllImport("libc.so.6")]
121-
private static extern nint dlopen(nint filename, int flags);
140+
[LibraryImport("libSystem", EntryPoint = "dlopen")]
141+
private static partial nint DlOpenMacOS(nint filename, int flags);
122142

123143
[UnmanagedCallersOnly(
124144
EntryPoint = nameof(napi_register_module_v1),

0 commit comments

Comments
 (0)