Coverage for cuda/pathfinder/_dynamic_libs/load_dl_linux.py: 72.64%

106 statements  

« prev     ^ index     » next       coverage.py v7.16.0, created at 2026-09-03 02:41 +0000

1# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. 

2# SPDX-License-Identifier: Apache-2.0 

3 

4from __future__ import annotations 

5 

6import contextlib 

7import ctypes 

8import ctypes.util 

9import os 

10import sys 

11from typing import TYPE_CHECKING, cast 

12 

13from cuda.pathfinder._dynamic_libs.load_dl_common import LoadedDL 

14 

15if TYPE_CHECKING: 

16 from cuda.pathfinder._dynamic_libs.lib_descriptor import LibDescriptor 

17 

18if sys.platform == "linux": 

19 CDLL_MODE = os.RTLD_NOW | os.RTLD_GLOBAL 

20else: 

21 CDLL_MODE = 0 

22 

23 

24def _load_libdl() -> ctypes.CDLL: 

25 # In normal glibc-based Linux environments, find_library("dl") should return 

26 # something like "libdl.so.2". In minimal or stripped-down environments 

27 # (no ldconfig/gcc, incomplete linker cache), this can return None even 

28 # though libdl is present. In that case, we fall back to the stable SONAME. 

29 name = ctypes.util.find_library("dl") or "libdl.so.2" 

30 try: 

31 return ctypes.CDLL(name) 

32 except OSError as e: 

33 raise RuntimeError(f"Could not load {name!r} (required for dlinfo/dlerror on Linux)") from e 

34 

35 

36LIBDL = _load_libdl() 

37 

38# dlinfo 

39LIBDL.dlinfo.argtypes = [ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p] 

40LIBDL.dlinfo.restype = ctypes.c_int 

41 

42# dlerror (thread-local error string; cleared after read) 

43LIBDL.dlerror.argtypes = [] 

44LIBDL.dlerror.restype = ctypes.c_char_p 

45 

46# First appeared in 2004-era glibc. Universally correct on Linux for all practical purposes. 

47RTLD_DI_LINKMAP = 2 

48RTLD_DI_ORIGIN = 6 

49 

50 

51class _LinkMapLNameView(ctypes.Structure): 

52 """ 

53 Prefix-only view of glibc's `struct link_map` used **solely** to read `l_name`. 

54 

55 Background: 

56 - `dlinfo(handle, RTLD_DI_LINKMAP, ...)` returns a `struct link_map*`. 

57 - The first few members of `struct link_map` (including `l_name`) have been 

58 stable on glibc for decades and are documented as debugger-visible. 

59 - We only need the offset/layout of `l_name`, not the full struct. 

60 

61 Safety constraints: 

62 - This is a **partial** definition (prefix). It must only be used via a pointer 

63 returned by `dlinfo(...)`. 

64 - Do **not** instantiate it or pass it **by value** to any C function. 

65 - Do **not** access any members beyond those declared here. 

66 - Do **not** rely on `ctypes.sizeof(LinkMapPrefix)` for allocation. 

67 

68 Rationale: 

69 - Defining only the leading fields avoids depending on internal/unstable 

70 tail members while keeping code more readable than raw pointer arithmetic. 

71 """ 

72 

73 _fields_ = ( 

74 ("l_addr", ctypes.c_void_p), # ElfW(Addr) 

75 ("l_name", ctypes.c_char_p), # char* 

76 ) 

77 

78 

79# Defensive assertions, mainly to document the invariants we depend on 

80assert _LinkMapLNameView.l_addr.offset == 0 

81assert _LinkMapLNameView.l_name.offset == ctypes.sizeof(ctypes.c_void_p) 

82 

83 

84def _dl_last_error() -> str | None: 

85 msg_bytes = cast(bytes | None, LIBDL.dlerror()) 

86 if not msg_bytes: 

87 return None # no pending error 

88 # Never raises; undecodable bytes are mapped to U+DC80..U+DCFF 

89 return msg_bytes.decode("utf-8", "surrogateescape") 

90 

91 

92def l_name_for_dynamic_library(libname: str, handle: ctypes.CDLL) -> str: 

93 lm_view = ctypes.POINTER(_LinkMapLNameView)() 1abc

94 rc = LIBDL.dlinfo(ctypes.c_void_p(handle._handle), RTLD_DI_LINKMAP, ctypes.byref(lm_view)) 1abc

95 if rc != 0: 1abc

96 err = _dl_last_error() 

97 raise OSError(f"dlinfo failed for {libname=!r} (rc={rc})" + (f": {err}" if err else "")) 

98 if not lm_view: # NULL link_map** 1abc

99 raise OSError(f"dlinfo returned NULL link_map pointer for {libname=!r}") 

100 

101 l_name_bytes = lm_view.contents.l_name 1abc

102 if not l_name_bytes: 1abc

103 raise OSError(f"dlinfo returned empty link_map->l_name for {libname=!r}") 

104 

105 path = os.fsdecode(l_name_bytes) 1abc

106 if not path: 1abc

107 raise OSError(f"dlinfo returned empty l_name string for {libname=!r}") 

108 

109 return path 1abc

110 

111 

112def l_origin_for_dynamic_library(libname: str, handle: ctypes.CDLL) -> str: 

113 l_origin_buf = ctypes.create_string_buffer(4096) 1abc

114 rc = LIBDL.dlinfo(ctypes.c_void_p(handle._handle), RTLD_DI_ORIGIN, l_origin_buf) 1abc

115 if rc != 0: 1abc

116 err = _dl_last_error() 

117 raise OSError(f"dlinfo failed for {libname=!r} (rc={rc})" + (f": {err}" if err else "")) 

118 

119 path = os.fsdecode(l_origin_buf.value) 1abc

120 if not path: 1abc

121 raise OSError(f"dlinfo returned empty l_origin string for {libname=!r}") 

122 

123 return path 1abc

124 

125 

126def abs_path_for_dynamic_library(libname: str, handle: ctypes.CDLL) -> str: 

127 l_name = l_name_for_dynamic_library(libname, handle) 1abc

128 l_origin = l_origin_for_dynamic_library(libname, handle) 1abc

129 return os.path.join(l_origin, os.path.basename(l_name)) 1abc

130 

131 

132if sys.platform == "linux": 

133 

134 def check_if_already_loaded_from_elsewhere(desc: LibDescriptor) -> LoadedDL | None: 

135 for soname in desc.linux_sonames: 1abcd

136 try: 1abcd

137 handle = ctypes.CDLL(soname, mode=os.RTLD_NOLOAD) 1abcd

138 except OSError: 1abcd

139 continue 1abcd

140 else: 

141 return LoadedDL( 

142 abs_path_for_dynamic_library(desc.name, handle), 

143 True, 

144 handle._handle, 

145 "was-already-loaded-from-elsewhere", 

146 ) 

147 return None 1abcd

148 

149 def _load_lib(desc: LibDescriptor, filename: str) -> ctypes.CDLL: 

150 cdll_mode = CDLL_MODE 1abc

151 if desc.requires_rtld_deepbind: 1abc

152 cdll_mode |= os.RTLD_DEEPBIND 

153 return ctypes.CDLL(filename, cdll_mode) 1abc

154else: 

155 

156 def check_if_already_loaded_from_elsewhere(_desc: LibDescriptor) -> LoadedDL | None: 

157 raise RuntimeError(f"check_if_already_loaded_from_elsewhere() is not supported on platform {sys.platform!r}") 

158 

159 def _load_lib(_desc: LibDescriptor, _filename: str) -> ctypes.CDLL: 

160 raise RuntimeError(f"_load_lib() is not supported on platform {sys.platform!r}") 

161 

162 

163def load_with_system_search(desc: LibDescriptor) -> LoadedDL | None: 

164 """Try to load a library using the native Linux dynamic-loader search path. 

165 

166 Args: 

167 desc: Descriptor for the library to load 

168 

169 Returns: 

170 A LoadedDL object if successful, None if the library cannot be loaded 

171 

172 """ 

173 for soname in desc.linux_sonames: 1abce

174 try: 1abce

175 handle = _load_lib(desc, soname) 1abce

176 except OSError: 1ae

177 pass 1ae

178 else: 

179 abs_path = abs_path_for_dynamic_library(desc.name, handle) 1abc

180 assert abs_path 1abc

181 return LoadedDL(abs_path, False, handle._handle, "system-search") 1abc

182 return None 1ae

183 

184 

185def _work_around_known_bugs(libname: str, found_path: str) -> None: 

186 if libname == "nvrtc": 

187 # Work around bug/oversight in 

188 # nvidia_cuda_nvrtc-13.0.48-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl 

189 # Issue: libnvrtc.so.13 RUNPATH is not set. 

190 # This workaround is highly specific 

191 # - for simplicity. 

192 # - to not mask bugs in future nvidia-cuda-nvrtc releases. 

193 # - because a more general workaround is complicated. 

194 dirname, basename = os.path.split(found_path) 

195 if basename == "libnvrtc.so.13": 

196 dep_basename = "libnvrtc-builtins.so.13.0" 

197 dep_path = os.path.join(dirname, dep_basename) 

198 if os.path.isfile(dep_path): 

199 # In case of failure, defer to primary load, which is almost certain to fail, too. 

200 with contextlib.suppress(OSError): 

201 ctypes.CDLL(dep_path, CDLL_MODE) 

202 

203 

204def load_with_abs_path(desc: LibDescriptor, found_path: str, found_via: str | None = None) -> LoadedDL: 

205 """Load a dynamic library from the given path. 

206 

207 Args: 

208 desc: Descriptor for the library to load. 

209 found_path: The absolute path to the library file. 

210 found_via: Label indicating how the path was discovered. 

211 

212 Returns: 

213 A LoadedDL object representing the loaded library. 

214 

215 Raises: 

216 RuntimeError: If the library cannot be loaded. 

217 """ 

218 _work_around_known_bugs(desc.name, found_path) 

219 try: 

220 handle = _load_lib(desc, found_path) 

221 except OSError as e: 

222 raise RuntimeError(f"Failed to dlopen {found_path}: {e}") from e 

223 return LoadedDL(found_path, False, handle._handle, found_via)