From 7e9693b306a6ea8fb8045cbdd023086e819f4983 Mon Sep 17 00:00:00 2001 From: Benjamin Drung Date: Thu, 11 Jul 2024 12:45:42 +0200 Subject: [PATCH] report: determine library search paths dynamically The library search order from `ld.so` differs from the hard-coded list: ``` $ ld.so --help [...] Shared library search path: (libraries located via /etc/ld.so.cache) /lib/x86_64-linux-gnu (system search path) /usr/lib/x86_64-linux-gnu (system search path) /lib (system search path) /usr/lib (system search path) ``` So determine the library search paths dynamically from `ld.so`. Signed-off-by: Benjamin Drung --- apport/report.py | 30 ++++++++++++++++------- tests/unit/test_report.py | 50 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 8 deletions(-) diff --git a/apport/report.py b/apport/report.py index 10bbfcc49..f6a5ab1cf 100644 --- a/apport/report.py +++ b/apport/report.py @@ -282,6 +282,21 @@ def _get_gdb_path(gdb_sandbox: str | None, same_arch: bool) -> str: return gdb_path +def _get_ld_search_paths(ld_so: str = "ld.so") -> list[str]: + """Query the given ld.so binary for the shared library search paths.""" + diagnostics = subprocess.run( + [ld_so, "--list-diagnostics"], check=True, stdout=subprocess.PIPE, text=True + ) + # Example matching line: path.system_dirs[0x0]="/lib/x86_64-linux-gnu/" + matches = re.findall( + r'^path\.system_dirs\[(0x[0-9a-f]+)\]="([^"]+)"$', + diagnostics.stdout, + flags=re.MULTILINE, + ) + ld_search_paths = {int(index, 0): path for (index, path) in matches} + return [path for (index, path) in sorted(ld_search_paths.items())] + + def _run_hook(report, ui, hook): if not os.path.exists(hook): return False @@ -1890,6 +1905,8 @@ def gdb_command( if sandbox: native_multiarch = "x86_64-linux-gnu" + host_library_paths = _get_ld_search_paths() + sandbox_library_paths = [f"{sandbox}{path}" for path in host_library_paths] # N.B. set solib-absolute-prefix is an alias for set sysroot command += [ "--ex", @@ -1900,16 +1917,13 @@ def gdb_command( "add-auto-load-safe-path " + sandbox, # needed to fix /lib64/ld-linux-x86-64.so.2 broken symlink "--ex", - f"set solib-search-path {sandbox}/lib/{native_multiarch}" - f":{sandbox}/usr/lib/{native_multiarch}", + f"set solib-search-path {':'.join(sandbox_library_paths)}", ] if gdb_sandbox: - ld_lib_path = ( - f"{gdb_sandbox}/lib" - f":{gdb_sandbox}/lib/{native_multiarch}" - f":{gdb_sandbox}/usr/lib/{native_multiarch}" - f":{gdb_sandbox}/usr/lib" - ) + gdb_sandbox_library_paths = [ + f"{gdb_sandbox}{path}" for path in host_library_paths + ] + ld_lib_path = ":".join(gdb_sandbox_library_paths) pyhome = f"{gdb_sandbox}/usr" # env settings need to be modified for gdb in a sandbox environ |= { diff --git a/tests/unit/test_report.py b/tests/unit/test_report.py index a59f20464..0747be5f1 100644 --- a/tests/unit/test_report.py +++ b/tests/unit/test_report.py @@ -1528,3 +1528,53 @@ def test_add_kernel_crash_info_fail(self, run_mock: MagicMock) -> None: tmpfile = called_cmd[-1] self.assertRegex(tmpfile, r"^/.*/apport_vmcore_\w{8}$") self.assertFalse(pathlib.Path(tmpfile).exists()) + + @unittest.mock.patch("subprocess.run") + def test_get_ld_search_paths(self, run_mock: MagicMock) -> None: + run_mock.return_value = subprocess.CompletedProcess( + args=MagicMock(), + returncode=0, + stdout=textwrap.dedent( + """\ + path.prefix="/usr" + path.sysconfdir="/etc" + path.system_dirs[0x0]="/lib/x86_64-linux-gnu/" + path.system_dirs[0x1]="/usr/lib/x86_64-linux-gnu/" + path.system_dirs[0x2]="/lib/" + path.system_dirs[0x3]="/usr/lib/" + """ + ), + ) + ld_search_paths = apport.report._get_ld_search_paths() + self.assertEqual( + ld_search_paths, + [ + "/lib/x86_64-linux-gnu/", + "/usr/lib/x86_64-linux-gnu/", + "/lib/", + "/usr/lib/", + ], + ) + run_mock.assert_called_once_with( + ["ld.so", "--list-diagnostics"], + check=True, + stdout=subprocess.PIPE, + text=True, + ) + + @unittest.mock.patch("subprocess.run") + def test_get_ld_search_paths_sorting(self, run_mock: MagicMock) -> None: + run_mock.return_value = subprocess.CompletedProcess( + args=MagicMock(), + returncode=0, + stdout=textwrap.dedent( + """\ + path.system_dirs[0x2]="third" + path.system_dirs[0x1]="second" + path.system_dirs[0x0]="first" + """ + ), + ) + ld_search_paths = apport.report._get_ld_search_paths("ld.so") + self.assertEqual(ld_search_paths, ["first", "second", "third"]) + run_mock.assert_called_once()