From 5146fdf10b1dcca35ba70de41459a9e34d632da4 Mon Sep 17 00:00:00 2001 From: Marco Blackwell Date: Mon, 22 Jun 2026 11:57:05 +0100 Subject: [PATCH] fix: preload libudev's dependencies so DDRMC firmware builds on Ubuntu 24.04 _environment_with_udev_preload forces LD_PRELOAD=libudev.so.1 onto the whole Vivado process tree as a container workaround. That preload is inherited by the bundled MicroBlaze cross-compiler that builds the DDRMC calibration firmware, which runs under its own OE-SDK loader with a restricted library path. On Ubuntu 24.04 the host libudev.so.1 is linked aginst libcap.so.2, which is absent from that path, so the cross-compiler aborts with "libcap.so.2: cannot open shared object file" and exits 127. The firmware never compiles, phy_ddrmc.elf is never produced, and synthesis fails later with a "File does not exist" for the missing ELF. The child stderr is lost, so the failure is silent. Ubuntu 22.04 is not affected because its libudev has no libcap dependency. Preload libudev together with its runtime dependency libcap.so.2 so the forced preload resolves inside the cross-compiler's restricted library path. Adding libcap is harmless where it is not needed. Relates to #117. Signed-off-by: Marco Blackwell --- linker/slashkit/emit/hw/project_gen.py | 33 +++++++++++--- linker/test/emit/__init__.py | 0 linker/test/emit/hw/__init__.py | 0 linker/test/emit/hw/test_project_gen.py | 60 +++++++++++++++++++++++++ 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 linker/test/emit/__init__.py create mode 100644 linker/test/emit/hw/__init__.py create mode 100644 linker/test/emit/hw/test_project_gen.py diff --git a/linker/slashkit/emit/hw/project_gen.py b/linker/slashkit/emit/hw/project_gen.py index dd9fddfa..80fbef62 100644 --- a/linker/slashkit/emit/hw/project_gen.py +++ b/linker/slashkit/emit/hw/project_gen.py @@ -27,7 +27,7 @@ import shutil import subprocess import importlib.resources as resources -from typing import Optional, Dict +from typing import List, Optional, Dict from contextlib import ExitStack from slashkit.emit.metadata.report_util import convert_report_utilization_to_xml @@ -139,6 +139,14 @@ def _generate_top_wrapper_pdi_with_bootgen(impl_dir: Path) -> Path: return output_pdi +def _first_existing(candidates: List[str]) -> Optional[str]: + """Return the first path in candidates that exists, or None.""" + for candidate in candidates: + if Path(candidate).is_file(): + return candidate + return None + + def _environment_with_udev_ld_preload() -> Dict[str, str]: """ Create a dictionary of environment variables (based on the current one), @@ -147,13 +155,26 @@ def _environment_with_udev_ld_preload() -> Dict[str, str]: Details: https://adaptivesupport.amd.com/s/question/0D54U00005Sgst2SAB/failed-batch-mode-execution-in-linux-docker-running-under-windows-host?language=en_US https://community.flexera.com/t5/InstallAnywhere-Forum/Issues-when-running-Xilinx-tools-or-Other-vendor-tools-in-docker/m-p/245820#M10647 + + The preload is inherited by the bundled MicroBlaze cross-compiler that builds + the DDRMC firmware (phy_ddrmc.elf) under a restricted library path, so a + forced library must bring its own dependencies. On Ubuntu 24.04 libudev.so.1 + needs libcap.so.2, so preloading libudev alone makes that compiler abort + ("libcap.so.2: cannot open shared object file") and silently drops the ELF. + Preload libcap alongside it. On 22.04 libudev has no libcap dependency, so + preloading it there has no effect. """ - possible_paths = [ - Path("/lib/x86_64-linux-gnu/libudev.so.1"), Path("/lib64/libudev.so.1")] - existing_paths = [str(path) for path in possible_paths if path.is_file()] env = dict(os.environ) - if len(existing_paths) > 0: - env["LD_PRELOAD"] = ":".join(existing_paths) + libudev = _first_existing( + ["/lib/x86_64-linux-gnu/libudev.so.1", "/lib64/libudev.so.1"]) + if libudev is None: + return env + preload = [libudev] + libcap = _first_existing( + ["/lib/x86_64-linux-gnu/libcap.so.2", "/lib64/libcap.so.2"]) + if libcap is not None: + preload.append(libcap) + env["LD_PRELOAD"] = ":".join(preload) return env diff --git a/linker/test/emit/__init__.py b/linker/test/emit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/linker/test/emit/hw/__init__.py b/linker/test/emit/hw/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/linker/test/emit/hw/test_project_gen.py b/linker/test/emit/hw/test_project_gen.py new file mode 100644 index 00000000..06a6cef4 --- /dev/null +++ b/linker/test/emit/hw/test_project_gen.py @@ -0,0 +1,60 @@ +# ################################################################################################## +# The MIT License (MIT) +# Copyright (c) 2025-2026 Advanced Micro Devices, Inc. All rights reserved. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software +# and associated documentation files (the "Software"), to deal in the Software without restriction, +# including without limitation the rights to use, copy, modify, merge, publish, distribute, +# sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in all copies or +# substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT +# NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# ################################################################################################## + +"""Tests for the LD_PRELOAD environment helper in emit.hw.project_gen.""" + +from pathlib import Path + +from slashkit.emit.hw import project_gen + +LIBUDEV = "/lib/x86_64-linux-gnu/libudev.so.1" +LIBCAP = "/lib/x86_64-linux-gnu/libcap.so.2" + + +def _only_present(monkeypatch, present): + """Make Path.is_file report True only for the given set of paths.""" + present_set = {str(Path(p)) for p in present} + monkeypatch.setattr( + project_gen.Path, "is_file", + lambda self: str(self) in present_set, raising=True) + + +def test_preload_includes_libcap_when_libudev_present(monkeypatch): + """On a 24.04-like host, libudev is preloaded together with libcap.""" + _only_present(monkeypatch, [LIBUDEV, LIBCAP]) + monkeypatch.delenv("LD_PRELOAD", raising=False) + env = project_gen._environment_with_udev_ld_preload() + assert env["LD_PRELOAD"] == f"{LIBUDEV}:{LIBCAP}" + + +def test_preload_libudev_only_when_libcap_absent(monkeypatch): + """On a 22.04-like host libudev has no libcap dependency, so libudev alone.""" + _only_present(monkeypatch, [LIBUDEV]) + monkeypatch.delenv("LD_PRELOAD", raising=False) + env = project_gen._environment_with_udev_ld_preload() + assert env["LD_PRELOAD"] == LIBUDEV + + +def test_no_preload_when_libudev_absent(monkeypatch): + """With no libudev the workaround does not apply and LD_PRELOAD is untouched.""" + _only_present(monkeypatch, [LIBCAP]) + monkeypatch.delenv("LD_PRELOAD", raising=False) + env = project_gen._environment_with_udev_ld_preload() + assert "LD_PRELOAD" not in env