[v2,7/8] dracut: add support for cross initrd generation

Message ID 20260924072340.4038396-8-felix.moessbauer@siemens.com
State Under Review
Headers show
Series Add support to cross-compile a dracut initramfs | expand

Commit Message

MOESSBAUER, Felix Sept. 24, 2026, 7:23 a.m. UTC
For cross-initrd generation, a cross variant of the ldd tool needs to
be provided. As there is no such tool, but this is THE performance
critical part (it needs to scan each binary and all transitive dependencies),
we provide a custom one which uses readelf to determine the shared library
dependencies.

Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
---
 meta/classes-recipe/host-tooling.bbclass      |  4 +-
 meta/classes-recipe/initrd-dracut.bbclass     | 38 ++++++++++-
 meta/classes-recipe/rootfs.bbclass            |  4 +-
 .../dracut-cross-ldd/dracut-cross-ldd.bb      | 16 +++++
 .../dracut-cross-ldd/files/dracut-cross-ldd   | 65 +++++++++++++++++++
 5 files changed, 123 insertions(+), 4 deletions(-)
 create mode 100644 meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
 create mode 100644 meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd

Comments

Zhihang Wei Sept. 24, 2026, 1:28 p.m. UTC | #1
On 9/24/26 09:23, 'Felix Moessbauer' via isar-users wrote:
> For cross-initrd generation, a cross variant of the ldd tool needs to
> be provided. As there is no such tool, but this is THE performance
> critical part (it needs to scan each binary and all transitive dependencies),
> we provide a custom one which uses readelf to determine the shared library
> dependencies.
>
> Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
> ---
>   meta/classes-recipe/host-tooling.bbclass      |  4 +-
>   meta/classes-recipe/initrd-dracut.bbclass     | 38 ++++++++++-
>   meta/classes-recipe/rootfs.bbclass            |  4 +-
>   .../dracut-cross-ldd/dracut-cross-ldd.bb      | 16 +++++
>   .../dracut-cross-ldd/files/dracut-cross-ldd   | 65 +++++++++++++++++++
>   5 files changed, 123 insertions(+), 4 deletions(-)
>   create mode 100644 meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
>   create mode 100644 meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
>
> diff --git a/meta/classes-recipe/host-tooling.bbclass b/meta/classes-recipe/host-tooling.bbclass
> index b76f8898..5e81dfda 100644
> --- a/meta/classes-recipe/host-tooling.bbclass
> +++ b/meta/classes-recipe/host-tooling.bbclass
> @@ -5,8 +5,8 @@
>   LICENSE = "gpl-2.0"
>   LIC_FILES_CHKSUM = "file://${LAYERDIR_core}/licenses/COPYING.GPLv2;md5=751419260aa954499f7abaabaa882bbe"
>   
> -HOST_TOOLING_PACKAGES ?= ""
> -HOST_TOOLING_DEPENDS ?= ""
> +HOST_TOOLING_PACKAGES ?= "dracut-core binutils zstd dracut-cross-ldd"
> +HOST_TOOLING_DEPENDS ?= "dracut-cross-ldd"
>   
>   # dependency to build the host-tooling-chroot
>   HOST_TOOLING_DEP ?= "host-tooling-chroot:do_build"
> diff --git a/meta/classes-recipe/initrd-dracut.bbclass b/meta/classes-recipe/initrd-dracut.bbclass
> index 00999b91..e3c9f174 100644
> --- a/meta/classes-recipe/initrd-dracut.bbclass
> +++ b/meta/classes-recipe/initrd-dracut.bbclass
> @@ -56,8 +56,44 @@ def extend_dracut_cmdline(d):
>   ROOTFS_INITRAMFS_GENERATOR_CMDLINE = "dracut --force --kver $kernel_version -L 5 --reproducible"
>   ROOTFS_INITRAMFS_GENERATOR_CMDLINE:append = " ${@ extend_dracut_cmdline(d)}"
>   
> +# Cross-assemble the initramfs, requires preparation of dracut module definitions
> +def dracut_cross_default(d):
> +    return bb.utils.to_boolean(d.getVar('ISAR_CROSS_COMPILE')) \
> +        and bb.utils.to_boolean(d.getVar('ROOTFS_USE_DRACUT')) \
> +        and d.getVar('ROOTFS_ARCH') != d.getVar('HOST_ARCH')
> +
> +ROOTFS_DRACUT_CROSS ??= "${@ '1' if dracut_cross_default(d) else '0' }"
> +ROOTFS_DRACUT_CROSS:bookworm = "0"
> +OVERRIDES:append = "${@':dracut-cross' if bb.utils.to_boolean(d.getVar('ROOTFS_DRACUT_CROSS')) else ''}"
> +
> +ROOTFS_INSTALL_DEPENDS:append:dracut-cross = " ${@bb.utils.contains('ROOTFS_INSTALL_COMMAND', 'rootfs_generate_initramfs', '${HOST_TOOLING_DEP}', '', d)}"
> +ROOTFS_INITRAMFS_GENERATOR_CMDLINE:append:dracut-cross = " --sysroot /mnt/rootfs"
> +DRACUT_MOUNTS = ""
> +DRACUT_MOUNTS:append:dracut-cross = " ${ROOTFSDIR}:/mnt/rootfs"
> +DRACUT_ROOTFS = "${ROOTFSDIR}"
> +DRACUT_ROOTFS:dracut-cross = "${WORKDIR}/host-tooling"
> +DRACUT_ENV = "DRACUT_ARCH=${QEMU_ARCH}"
> +DRACUT_ENV:append:dracut-cross = " \
> +    DRACUT_INSTALL=/usr/lib/dracut/dracut-install \
> +    DRACUT_LDD=/usr/libexec/dracut-cross-ldd \
> +"
> +
>   run_initrd_generator() {
>       mods_total="$(find ${ROOTFSDIR}/usr/lib/dracut/modules.d/ -maxdepth 1 -type d | wc -l)"
>       echo "Total number of modules: $mods_total (dracut)"
> -    run_in_chroot "${ROOTFSDIR}" sh -c "${ROOTFS_INITRAMFS_GENERATOR_CMDLINE}"
> +    if [ "${ROOTFS_DRACUT_CROSS}" = "1" ]; then
> +        mkdir -p ${DRACUT_ROOTFS}
> +        run_privileged tar -xf ${HOST_TOOLING_CHROOT} -C ${DRACUT_ROOTFS}
> +        trap 'run_privileged rm -rf ${DRACUT_ROOTFS}' EXIT
> +    fi
> +    ${@ insert_isar_umounts(d, d.getVar('DRACUT_ROOTFS'), d.getVar('DRACUT_MOUNTS')) }
> +    run_privileged_heredoc <<'EOF'
> +        ${@ insert_isar_mounts(d, d.getVar('DRACUT_ROOTFS'), d.getVar('DRACUT_MOUNTS')) }
> +        export ${DRACUT_ENV}
> +        chroot ${DRACUT_ROOTFS} ${ROOTFS_INITRAMFS_GENERATOR_CMDLINE}
> +EOF
>   }
> +

Hi Felix,

In privileged mode,mounts are left behind, e.g.:
[stdlog] 2026-09-24 14:46:59,368 avocado.test cibuilder        L0369 
INFO | WARNING: 
/isar/build/tmp/work/debian-trixie-arm64/isar-image-ci-qemuarm64/1.0-r0/host-tooling/mnt/rootfs 
left mounted, unmounting...
and
[stdlog] 2026-09-24 14:54:33,999 avocado.test cibuilder        L0369 
INFO | WARNING: 
/isar/build/tmp/work/debian-trixie-arm64/isar-dracut-qemuarm64/1.0-r0/host-tooling/mnt/rootfs 
left mounted, unmounting...

On Jenkins these are treated as errors.

Zhihang

> +HOST_TOOLING_DEP ??= ""
> +inherit_defer ${@'host-tooling' if bb.utils.to_boolean(d.getVar('ROOTFS_DRACUT_CROSS')) else ''}
> +do_generate_initramfs[depends] += "${HOST_TOOLING_DEP}"
> diff --git a/meta/classes-recipe/rootfs.bbclass b/meta/classes-recipe/rootfs.bbclass
> index 1de27d7b..cea85626 100644
> --- a/meta/classes-recipe/rootfs.bbclass
> +++ b/meta/classes-recipe/rootfs.bbclass
> @@ -452,6 +452,8 @@ rootfs_capture_apt_state() {
>           var/lib/dpkg/status
>   }
>   
> +ROOTFS_INSTALL_DEPENDS ?= ""
> +
>   do_rootfs_install[root_cleandirs] = "${ROOTFSDIR}"
>   do_rootfs_install[cleandirs] += "${DEPLOYDIR}"
>   do_rootfs_install[sstate-inputdirs] = "${DEPLOYDIR}"
> @@ -459,7 +461,7 @@ do_rootfs_install[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
>   do_rootfs_install[vardeps] += "${ROOTFS_CONFIGURE_COMMAND} ${ROOTFS_INSTALL_COMMAND} ${ROOTFS_POSTPROCESS_COMMAND} ${ROOTFS_VARDEPS}"
>   do_rootfs_install[vardepsexclude] += "IMAGE_ROOTFS"
>   do_rootfs_install[depends] = "bootstrap-${@'target' if d.getVar('ROOTFS_ARCH') == d.getVar('DISTRO_ARCH') else 'host'}:do_build"
> -do_rootfs_install[depends] += "base-apt:do_cache isar-apt:do_cache_config"
> +do_rootfs_install[depends] += "base-apt:do_cache isar-apt:do_cache_config ${ROOTFS_INSTALL_DEPENDS}"
>   do_rootfs_install[deptask] = "do_deploy_deb"
>   do_rootfs_install[rdeptask] = "do_deploy_deb"
>   do_rootfs_install[network] = "${TASK_USE_SUDO}"
> diff --git a/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb b/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
> new file mode 100644
> index 00000000..327a5c82
> --- /dev/null
> +++ b/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
> @@ -0,0 +1,16 @@
> +# This software is a part of Isar.
> +# Copyright (C) 2026 Siemens AG
> +
> +inherit dpkg-raw
> +
> +MAINTAINER = "isar-users <isar-users@googlegroups.com>"
> +DESCRIPTION = "Minimal ldd replacement for dracut cross builds"
> +
> +SRC_URI = "file://${BPN}"
> +
> +DEBIAN_DEPENDS .= ",python3, binutils"
> +
> +do_install[cleandirs] += "${D}/usr/libexec"
> +do_install() {
> +    install -v -m 755 "${WORKDIR}/${BPN}" "${D}/usr/libexec/${BPN}"
> +}
> diff --git a/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd b/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
> new file mode 100644
> index 00000000..ef60b3fa
> --- /dev/null
> +++ b/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
> @@ -0,0 +1,65 @@
> +#!/usr/bin/env python3
> +# This software is a part of Isar.
> +# Copyright (C) 2026 Siemens AG
> +import re
> +import subprocess
> +import sys
> +from pathlib import Path
> +
> +# glob patterns relative to the sysroot, including multiarch directories
> +LIB_DIRS = ["lib", "lib/*", "usr/lib", "usr/lib/*"]
> +
> +
> +def find_sysroot(binary):
> +    for parent in binary.parents:
> +        if (parent / "usr/lib").is_dir():
> +            return parent
> +    return Path("/")
> +
> +
> +def find_library(name, search_dirs, sysroot):
> +    for directory in search_dirs:
> +        for path in sysroot.glob(f"{directory.strip('/')}/{name}"):
> +            return "/" + str(path.relative_to(sysroot))
> +    return None
> +
> +
> +def read_dynamic_section(path):
> +    return subprocess.check_output(["readelf", "-d", path], text=True)
> +
> +
> +def get_needed(dynamic):
> +    runpaths = re.findall(r'\((?:RUNPATH|RPATH)\).*?\[(.*?)\]', dynamic)
> +    search_dirs = [d for paths in runpaths for d in paths.split(":")]
> +    needed = re.findall(r'\(NEEDED\).*?\[(.*?)\]', dynamic)
> +    return search_dirs + LIB_DIRS, needed
> +
> +
> +binary = Path(sys.argv[-1]).absolute()
> +try:
> +    dynamic = read_dynamic_section(binary)
> +except subprocess.SubprocessError:
> +    sys.exit(1)
> +
> +sysroot = find_sysroot(binary)
> +resolved = {}
> +pending = [get_needed(dynamic)]
> +
> +# ldd reports the whole dependency tree, so resolve it transitively
> +while pending:
> +    search_dirs, needed = pending.pop()
> +    for library in needed:
> +        if library in resolved:
> +            continue
> +        path = find_library(library, search_dirs, sysroot)
> +        resolved[library] = path
> +        if path:
> +            try:
> +                pending.append(get_needed(
> +                    read_dynamic_section(sysroot / path.strip("/"))))
> +            except subprocess.SubprocessError:
> +                pass
> +
> +for library, path in resolved.items():
> +    print(f"\t{library} => {path} (0x0000000000000000)" if path
> +          else f"\t{library} => not found")
MOESSBAUER, Felix Sept. 24, 2026, 2:01 p.m. UTC | #2
On Thu, 2026-09-24 at 15:28 +0200, Zhihang Wei wrote:
> On 9/24/26 09:23, 'Felix Moessbauer' via isar-users wrote:
> > For cross-initrd generation, a cross variant of the ldd tool needs to
> > be provided. As there is no such tool, but this is THE performance
> > critical part (it needs to scan each binary and all transitive dependencies),
> > we provide a custom one which uses readelf to determine the shared library
> > dependencies.
> > 
> > Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
> > ---
> >   meta/classes-recipe/host-tooling.bbclass      |  4 +-
> >   meta/classes-recipe/initrd-dracut.bbclass     | 38 ++++++++++-
> >   meta/classes-recipe/rootfs.bbclass            |  4 +-
> >   .../dracut-cross-ldd/dracut-cross-ldd.bb      | 16 +++++
> >   .../dracut-cross-ldd/files/dracut-cross-ldd   | 65 +++++++++++++++++++
> >   5 files changed, 123 insertions(+), 4 deletions(-)
> >   create mode 100644 meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
> >   create mode 100644 meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
> > 
> > diff --git a/meta/classes-recipe/host-tooling.bbclass b/meta/classes-recipe/host-tooling.bbclass
> > index b76f8898..5e81dfda 100644
> > --- a/meta/classes-recipe/host-tooling.bbclass
> > +++ b/meta/classes-recipe/host-tooling.bbclass
> > @@ -5,8 +5,8 @@
> >   LICENSE = "gpl-2.0"
> >   LIC_FILES_CHKSUM = "file://${LAYERDIR_core}/licenses/COPYING.GPLv2;md5=751419260aa954499f7abaabaa882bbe"
> >   
> > -HOST_TOOLING_PACKAGES ?= ""
> > -HOST_TOOLING_DEPENDS ?= ""
> > +HOST_TOOLING_PACKAGES ?= "dracut-core binutils zstd dracut-cross-ldd"
> > +HOST_TOOLING_DEPENDS ?= "dracut-cross-ldd"
> >   
> >   # dependency to build the host-tooling-chroot
> >   HOST_TOOLING_DEP ?= "host-tooling-chroot:do_build"
> > diff --git a/meta/classes-recipe/initrd-dracut.bbclass b/meta/classes-recipe/initrd-dracut.bbclass
> > index 00999b91..e3c9f174 100644
> > --- a/meta/classes-recipe/initrd-dracut.bbclass
> > +++ b/meta/classes-recipe/initrd-dracut.bbclass
> > @@ -56,8 +56,44 @@ def extend_dracut_cmdline(d):
> >   ROOTFS_INITRAMFS_GENERATOR_CMDLINE = "dracut --force --kver $kernel_version -L 5 --reproducible"
> >   ROOTFS_INITRAMFS_GENERATOR_CMDLINE:append = " ${@ extend_dracut_cmdline(d)}"
> >   
> > +# Cross-assemble the initramfs, requires preparation of dracut module definitions
> > +def dracut_cross_default(d):
> > +    return bb.utils.to_boolean(d.getVar('ISAR_CROSS_COMPILE')) \
> > +        and bb.utils.to_boolean(d.getVar('ROOTFS_USE_DRACUT')) \
> > +        and d.getVar('ROOTFS_ARCH') != d.getVar('HOST_ARCH')
> > +
> > +ROOTFS_DRACUT_CROSS ??= "${@ '1' if dracut_cross_default(d) else '0' }"
> > +ROOTFS_DRACUT_CROSS:bookworm = "0"
> > +OVERRIDES:append = "${@':dracut-cross' if bb.utils.to_boolean(d.getVar('ROOTFS_DRACUT_CROSS')) else ''}"
> > +
> > +ROOTFS_INSTALL_DEPENDS:append:dracut-cross = " ${@bb.utils.contains('ROOTFS_INSTALL_COMMAND', 'rootfs_generate_initramfs', '${HOST_TOOLING_DEP}', '', d)}"
> > +ROOTFS_INITRAMFS_GENERATOR_CMDLINE:append:dracut-cross = " --sysroot /mnt/rootfs"
> > +DRACUT_MOUNTS = ""
> > +DRACUT_MOUNTS:append:dracut-cross = " ${ROOTFSDIR}:/mnt/rootfs"
> > +DRACUT_ROOTFS = "${ROOTFSDIR}"
> > +DRACUT_ROOTFS:dracut-cross = "${WORKDIR}/host-tooling"
> > +DRACUT_ENV = "DRACUT_ARCH=${QEMU_ARCH}"
> > +DRACUT_ENV:append:dracut-cross = " \
> > +    DRACUT_INSTALL=/usr/lib/dracut/dracut-install \
> > +    DRACUT_LDD=/usr/libexec/dracut-cross-ldd \
> > +"
> > +
> >   run_initrd_generator() {
> >       mods_total="$(find ${ROOTFSDIR}/usr/lib/dracut/modules.d/ -maxdepth 1 -type d | wc -l)"
> >       echo "Total number of modules: $mods_total (dracut)"
> > -    run_in_chroot "${ROOTFSDIR}" sh -c "${ROOTFS_INITRAMFS_GENERATOR_CMDLINE}"
> > +    if [ "${ROOTFS_DRACUT_CROSS}" = "1" ]; then
> > +        mkdir -p ${DRACUT_ROOTFS}
> > +        run_privileged tar -xf ${HOST_TOOLING_CHROOT} -C ${DRACUT_ROOTFS}
> > +        trap 'run_privileged rm -rf ${DRACUT_ROOTFS}' EXIT
> > +    fi
> > +    ${@ insert_isar_umounts(d, d.getVar('DRACUT_ROOTFS'), d.getVar('DRACUT_MOUNTS')) }
> > +    run_privileged_heredoc <<'EOF'
> > +        ${@ insert_isar_mounts(d, d.getVar('DRACUT_ROOTFS'), d.getVar('DRACUT_MOUNTS')) }
> > +        export ${DRACUT_ENV}
> > +        chroot ${DRACUT_ROOTFS} ${ROOTFS_INITRAMFS_GENERATOR_CMDLINE}
> > +EOF
> >   }
> > +
> 
> Hi Felix,
> 
> In privileged mode,mounts are left behind, e.g.:
> [stdlog] 2026-09-24 14:46:59,368 avocado.test cibuilder        L0369 
> INFO | WARNING: 
> /isar/build/tmp/work/debian-trixie-arm64/isar-image-ci-qemuarm64/1.0-r0/host-tooling/mnt/rootfs 
> left mounted, unmounting...
> and
> [stdlog] 2026-09-24 14:54:33,999 avocado.test cibuilder        L0369 
> INFO | WARNING: 
> /isar/build/tmp/work/debian-trixie-arm64/isar-dracut-qemuarm64/1.0-r0/host-tooling/mnt/rootfs 
> left mounted, unmounting...
> 
> On Jenkins these are treated as errors.

They are errors :)

Thanks for the feedback, I'm always only testing rootless.
But at least with the recent improvements in build speed (and some more
not sent to the list yet), I can execute the -t fast CI in ~20 mins
(with caching).

Will send a v3 with the fixes.

Felix

> 
> Zhihang
> 
> > +HOST_TOOLING_DEP ??= ""
> > +inherit_defer ${@'host-tooling' if bb.utils.to_boolean(d.getVar('ROOTFS_DRACUT_CROSS')) else ''}
> > +do_generate_initramfs[depends] += "${HOST_TOOLING_DEP}"
> > diff --git a/meta/classes-recipe/rootfs.bbclass b/meta/classes-recipe/rootfs.bbclass
> > index 1de27d7b..cea85626 100644
> > --- a/meta/classes-recipe/rootfs.bbclass
> > +++ b/meta/classes-recipe/rootfs.bbclass
> > @@ -452,6 +452,8 @@ rootfs_capture_apt_state() {
> >           var/lib/dpkg/status
> >   }
> >   
> > +ROOTFS_INSTALL_DEPENDS ?= ""
> > +
> >   do_rootfs_install[root_cleandirs] = "${ROOTFSDIR}"
> >   do_rootfs_install[cleandirs] += "${DEPLOYDIR}"
> >   do_rootfs_install[sstate-inputdirs] = "${DEPLOYDIR}"
> > @@ -459,7 +461,7 @@ do_rootfs_install[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
> >   do_rootfs_install[vardeps] += "${ROOTFS_CONFIGURE_COMMAND} ${ROOTFS_INSTALL_COMMAND} ${ROOTFS_POSTPROCESS_COMMAND} ${ROOTFS_VARDEPS}"
> >   do_rootfs_install[vardepsexclude] += "IMAGE_ROOTFS"
> >   do_rootfs_install[depends] = "bootstrap-${@'target' if d.getVar('ROOTFS_ARCH') == d.getVar('DISTRO_ARCH') else 'host'}:do_build"
> > -do_rootfs_install[depends] += "base-apt:do_cache isar-apt:do_cache_config"
> > +do_rootfs_install[depends] += "base-apt:do_cache isar-apt:do_cache_config ${ROOTFS_INSTALL_DEPENDS}"
> >   do_rootfs_install[deptask] = "do_deploy_deb"
> >   do_rootfs_install[rdeptask] = "do_deploy_deb"
> >   do_rootfs_install[network] = "${TASK_USE_SUDO}"
> > diff --git a/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb b/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
> > new file mode 100644
> > index 00000000..327a5c82
> > --- /dev/null
> > +++ b/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
> > @@ -0,0 +1,16 @@
> > +# This software is a part of Isar.
> > +# Copyright (C) 2026 Siemens AG
> > +
> > +inherit dpkg-raw
> > +
> > +MAINTAINER = "isar-users <isar-users@googlegroups.com>"
> > +DESCRIPTION = "Minimal ldd replacement for dracut cross builds"
> > +
> > +SRC_URI = "file://${BPN}"
> > +
> > +DEBIAN_DEPENDS .= ",python3, binutils"
> > +
> > +do_install[cleandirs] += "${D}/usr/libexec"
> > +do_install() {
> > +    install -v -m 755 "${WORKDIR}/${BPN}" "${D}/usr/libexec/${BPN}"
> > +}
> > diff --git a/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd b/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
> > new file mode 100644
> > index 00000000..ef60b3fa
> > --- /dev/null
> > +++ b/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
> > @@ -0,0 +1,65 @@
> > +#!/usr/bin/env python3
> > +# This software is a part of Isar.
> > +# Copyright (C) 2026 Siemens AG
> > +import re
> > +import subprocess
> > +import sys
> > +from pathlib import Path
> > +
> > +# glob patterns relative to the sysroot, including multiarch directories
> > +LIB_DIRS = ["lib", "lib/*", "usr/lib", "usr/lib/*"]
> > +
> > +
> > +def find_sysroot(binary):
> > +    for parent in binary.parents:
> > +        if (parent / "usr/lib").is_dir():
> > +            return parent
> > +    return Path("/")
> > +
> > +
> > +def find_library(name, search_dirs, sysroot):
> > +    for directory in search_dirs:
> > +        for path in sysroot.glob(f"{directory.strip('/')}/{name}"):
> > +            return "/" + str(path.relative_to(sysroot))
> > +    return None
> > +
> > +
> > +def read_dynamic_section(path):
> > +    return subprocess.check_output(["readelf", "-d", path], text=True)
> > +
> > +
> > +def get_needed(dynamic):
> > +    runpaths = re.findall(r'\((?:RUNPATH|RPATH)\).*?\[(.*?)\]', dynamic)
> > +    search_dirs = [d for paths in runpaths for d in paths.split(":")]
> > +    needed = re.findall(r'\(NEEDED\).*?\[(.*?)\]', dynamic)
> > +    return search_dirs + LIB_DIRS, needed
> > +
> > +
> > +binary = Path(sys.argv[-1]).absolute()
> > +try:
> > +    dynamic = read_dynamic_section(binary)
> > +except subprocess.SubprocessError:
> > +    sys.exit(1)
> > +
> > +sysroot = find_sysroot(binary)
> > +resolved = {}
> > +pending = [get_needed(dynamic)]
> > +
> > +# ldd reports the whole dependency tree, so resolve it transitively
> > +while pending:
> > +    search_dirs, needed = pending.pop()
> > +    for library in needed:
> > +        if library in resolved:
> > +            continue
> > +        path = find_library(library, search_dirs, sysroot)
> > +        resolved[library] = path
> > +        if path:
> > +            try:
> > +                pending.append(get_needed(
> > +                    read_dynamic_section(sysroot / path.strip("/"))))
> > +            except subprocess.SubprocessError:
> > +                pass
> > +
> > +for library, path in resolved.items():
> > +    print(f"\t{library} => {path} (0x0000000000000000)" if path
> > +          else f"\t{library} => not found")
> 
> -- 
> You received this message because you are subscribed to the Google Groups "isar-users" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to isar-users+unsubscribe@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/isar-users/4322fa2c-05cd-494b-93d3-dfd185c75ccf%40ilbers.de.

Patch

diff --git a/meta/classes-recipe/host-tooling.bbclass b/meta/classes-recipe/host-tooling.bbclass
index b76f8898..5e81dfda 100644
--- a/meta/classes-recipe/host-tooling.bbclass
+++ b/meta/classes-recipe/host-tooling.bbclass
@@ -5,8 +5,8 @@ 
 LICENSE = "gpl-2.0"
 LIC_FILES_CHKSUM = "file://${LAYERDIR_core}/licenses/COPYING.GPLv2;md5=751419260aa954499f7abaabaa882bbe"
 
-HOST_TOOLING_PACKAGES ?= ""
-HOST_TOOLING_DEPENDS ?= ""
+HOST_TOOLING_PACKAGES ?= "dracut-core binutils zstd dracut-cross-ldd"
+HOST_TOOLING_DEPENDS ?= "dracut-cross-ldd"
 
 # dependency to build the host-tooling-chroot
 HOST_TOOLING_DEP ?= "host-tooling-chroot:do_build"
diff --git a/meta/classes-recipe/initrd-dracut.bbclass b/meta/classes-recipe/initrd-dracut.bbclass
index 00999b91..e3c9f174 100644
--- a/meta/classes-recipe/initrd-dracut.bbclass
+++ b/meta/classes-recipe/initrd-dracut.bbclass
@@ -56,8 +56,44 @@  def extend_dracut_cmdline(d):
 ROOTFS_INITRAMFS_GENERATOR_CMDLINE = "dracut --force --kver $kernel_version -L 5 --reproducible"
 ROOTFS_INITRAMFS_GENERATOR_CMDLINE:append = " ${@ extend_dracut_cmdline(d)}"
 
+# Cross-assemble the initramfs, requires preparation of dracut module definitions
+def dracut_cross_default(d):
+    return bb.utils.to_boolean(d.getVar('ISAR_CROSS_COMPILE')) \
+        and bb.utils.to_boolean(d.getVar('ROOTFS_USE_DRACUT')) \
+        and d.getVar('ROOTFS_ARCH') != d.getVar('HOST_ARCH')
+
+ROOTFS_DRACUT_CROSS ??= "${@ '1' if dracut_cross_default(d) else '0' }"
+ROOTFS_DRACUT_CROSS:bookworm = "0"
+OVERRIDES:append = "${@':dracut-cross' if bb.utils.to_boolean(d.getVar('ROOTFS_DRACUT_CROSS')) else ''}"
+
+ROOTFS_INSTALL_DEPENDS:append:dracut-cross = " ${@bb.utils.contains('ROOTFS_INSTALL_COMMAND', 'rootfs_generate_initramfs', '${HOST_TOOLING_DEP}', '', d)}"
+ROOTFS_INITRAMFS_GENERATOR_CMDLINE:append:dracut-cross = " --sysroot /mnt/rootfs"
+DRACUT_MOUNTS = ""
+DRACUT_MOUNTS:append:dracut-cross = " ${ROOTFSDIR}:/mnt/rootfs"
+DRACUT_ROOTFS = "${ROOTFSDIR}"
+DRACUT_ROOTFS:dracut-cross = "${WORKDIR}/host-tooling"
+DRACUT_ENV = "DRACUT_ARCH=${QEMU_ARCH}"
+DRACUT_ENV:append:dracut-cross = " \
+    DRACUT_INSTALL=/usr/lib/dracut/dracut-install \
+    DRACUT_LDD=/usr/libexec/dracut-cross-ldd \
+"
+
 run_initrd_generator() {
     mods_total="$(find ${ROOTFSDIR}/usr/lib/dracut/modules.d/ -maxdepth 1 -type d | wc -l)"
     echo "Total number of modules: $mods_total (dracut)"
-    run_in_chroot "${ROOTFSDIR}" sh -c "${ROOTFS_INITRAMFS_GENERATOR_CMDLINE}"
+    if [ "${ROOTFS_DRACUT_CROSS}" = "1" ]; then
+        mkdir -p ${DRACUT_ROOTFS}
+        run_privileged tar -xf ${HOST_TOOLING_CHROOT} -C ${DRACUT_ROOTFS}
+        trap 'run_privileged rm -rf ${DRACUT_ROOTFS}' EXIT
+    fi
+    ${@ insert_isar_umounts(d, d.getVar('DRACUT_ROOTFS'), d.getVar('DRACUT_MOUNTS')) }
+    run_privileged_heredoc <<'EOF'
+        ${@ insert_isar_mounts(d, d.getVar('DRACUT_ROOTFS'), d.getVar('DRACUT_MOUNTS')) }
+        export ${DRACUT_ENV}
+        chroot ${DRACUT_ROOTFS} ${ROOTFS_INITRAMFS_GENERATOR_CMDLINE}
+EOF
 }
+
+HOST_TOOLING_DEP ??= ""
+inherit_defer ${@'host-tooling' if bb.utils.to_boolean(d.getVar('ROOTFS_DRACUT_CROSS')) else ''}
+do_generate_initramfs[depends] += "${HOST_TOOLING_DEP}"
diff --git a/meta/classes-recipe/rootfs.bbclass b/meta/classes-recipe/rootfs.bbclass
index 1de27d7b..cea85626 100644
--- a/meta/classes-recipe/rootfs.bbclass
+++ b/meta/classes-recipe/rootfs.bbclass
@@ -452,6 +452,8 @@  rootfs_capture_apt_state() {
         var/lib/dpkg/status
 }
 
+ROOTFS_INSTALL_DEPENDS ?= ""
+
 do_rootfs_install[root_cleandirs] = "${ROOTFSDIR}"
 do_rootfs_install[cleandirs] += "${DEPLOYDIR}"
 do_rootfs_install[sstate-inputdirs] = "${DEPLOYDIR}"
@@ -459,7 +461,7 @@  do_rootfs_install[sstate-outputdirs] = "${DEPLOY_DIR_IMAGE}"
 do_rootfs_install[vardeps] += "${ROOTFS_CONFIGURE_COMMAND} ${ROOTFS_INSTALL_COMMAND} ${ROOTFS_POSTPROCESS_COMMAND} ${ROOTFS_VARDEPS}"
 do_rootfs_install[vardepsexclude] += "IMAGE_ROOTFS"
 do_rootfs_install[depends] = "bootstrap-${@'target' if d.getVar('ROOTFS_ARCH') == d.getVar('DISTRO_ARCH') else 'host'}:do_build"
-do_rootfs_install[depends] += "base-apt:do_cache isar-apt:do_cache_config"
+do_rootfs_install[depends] += "base-apt:do_cache isar-apt:do_cache_config ${ROOTFS_INSTALL_DEPENDS}"
 do_rootfs_install[deptask] = "do_deploy_deb"
 do_rootfs_install[rdeptask] = "do_deploy_deb"
 do_rootfs_install[network] = "${TASK_USE_SUDO}"
diff --git a/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb b/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
new file mode 100644
index 00000000..327a5c82
--- /dev/null
+++ b/meta/recipes-devtools/dracut-cross-ldd/dracut-cross-ldd.bb
@@ -0,0 +1,16 @@ 
+# This software is a part of Isar.
+# Copyright (C) 2026 Siemens AG
+
+inherit dpkg-raw
+
+MAINTAINER = "isar-users <isar-users@googlegroups.com>"
+DESCRIPTION = "Minimal ldd replacement for dracut cross builds"
+
+SRC_URI = "file://${BPN}"
+
+DEBIAN_DEPENDS .= ",python3, binutils"
+
+do_install[cleandirs] += "${D}/usr/libexec"
+do_install() {
+    install -v -m 755 "${WORKDIR}/${BPN}" "${D}/usr/libexec/${BPN}"
+}
diff --git a/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd b/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
new file mode 100644
index 00000000..ef60b3fa
--- /dev/null
+++ b/meta/recipes-devtools/dracut-cross-ldd/files/dracut-cross-ldd
@@ -0,0 +1,65 @@ 
+#!/usr/bin/env python3
+# This software is a part of Isar.
+# Copyright (C) 2026 Siemens AG
+import re
+import subprocess
+import sys
+from pathlib import Path
+
+# glob patterns relative to the sysroot, including multiarch directories
+LIB_DIRS = ["lib", "lib/*", "usr/lib", "usr/lib/*"]
+
+
+def find_sysroot(binary):
+    for parent in binary.parents:
+        if (parent / "usr/lib").is_dir():
+            return parent
+    return Path("/")
+
+
+def find_library(name, search_dirs, sysroot):
+    for directory in search_dirs:
+        for path in sysroot.glob(f"{directory.strip('/')}/{name}"):
+            return "/" + str(path.relative_to(sysroot))
+    return None
+
+
+def read_dynamic_section(path):
+    return subprocess.check_output(["readelf", "-d", path], text=True)
+
+
+def get_needed(dynamic):
+    runpaths = re.findall(r'\((?:RUNPATH|RPATH)\).*?\[(.*?)\]', dynamic)
+    search_dirs = [d for paths in runpaths for d in paths.split(":")]
+    needed = re.findall(r'\(NEEDED\).*?\[(.*?)\]', dynamic)
+    return search_dirs + LIB_DIRS, needed
+
+
+binary = Path(sys.argv[-1]).absolute()
+try:
+    dynamic = read_dynamic_section(binary)
+except subprocess.SubprocessError:
+    sys.exit(1)
+
+sysroot = find_sysroot(binary)
+resolved = {}
+pending = [get_needed(dynamic)]
+
+# ldd reports the whole dependency tree, so resolve it transitively
+while pending:
+    search_dirs, needed = pending.pop()
+    for library in needed:
+        if library in resolved:
+            continue
+        path = find_library(library, search_dirs, sysroot)
+        resolved[library] = path
+        if path:
+            try:
+                pending.append(get_needed(
+                    read_dynamic_section(sysroot / path.strip("/"))))
+            except subprocess.SubprocessError:
+                pass
+
+for library, path in resolved.items():
+    print(f"\t{library} => {path} (0x0000000000000000)" if path
+          else f"\t{library} => not found")