Skip to content

Use unified kernel artifacts VHD layout - #41267

Merged
Ben Hillis (benhillis) merged 1 commit into
masterfrom
user/benhill/module_vhd_artifacts
Aug 20, 2026
Merged

Ben Hillis (benhillis) merged 1 commit into
masterfrom
user/benhill/module_vhd_artifacts

Conversation

@benhillis

@benhillis Ben Hillis (benhillis) commented Aug 6, 2026 •

Copy link
Copy Markdown
Member

Summary

  • update to the unified kernel package and package artifacts.vhd instead of modules.vhd
  • load kernel modules from the versioned <release>/modules directory for both standard WSL distributions and WSLC sessions
  • retain support for legacy flat module VHDs in the standard WSL boot path
  • update development, packaging, and test paths for the renamed VHD

Kernel headers and perf exposure are intentionally deferred to a follow-up PR.

Testing

  • full x64 Debug build
  • bin\x64\Debug\test.bat /name:*KernelModules* (1 passed)

Copilot AI lite review requested due to automatic review settings August 6, 2026 01:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates WSL2’s kernel packaging to ship a unified artifacts.vhd containing kernel modules plus additional developer artifacts (kernel headers and perf), and wires up the Linux init + Windows service paths to mount these version-matched resources into the distro environment.

Changes:

  • Switch kernel artifact packaging/paths from modules.vhd to a unified artifacts.vhd across build/dev shortcuts and MSI payloads.
  • Extend Linux init to detect the new versioned/nested artifacts layout and (when present) expose headers via /lib/modules/<release>/build and perf via /usr/bin/perf.
  • Add/adjust Windows tests and documentation to cover the new artifacts behavior and mounting locations.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
UserConfig.cmake.sample Updates dev-copy and ACL setup to use artifacts.vhd.
test/windows/UnitTests.cpp Updates modules mount expectation and adds a new KernelArtifacts test for headers + perf.
src/windows/service/exe/WslCoreVm.cpp Updates default kernel artifacts VHD filename to artifacts.vhd.
src/windows/service/exe/HcsVirtualMachine.cpp Updates default tools VHD filename to artifacts.vhd.
src/shared/inc/lxinitshared.h Adds env vars for passing headers/perf mount + target paths into distro init.
src/linux/init/main.cpp Adds nested-layout detection and binds headers/perf payloads from the artifacts VHD.
src/linux/init/config.cpp Moves mounts into the distro namespace and sets up /lib/modules/<release>/build and /usr/bin/perf.
packages.config Bumps Microsoft.WSL.Kernel dependency version.
msipackage/package.wix.in Ships artifacts.vhd in the MSI instead of modules.vhd.
doc/docs/technical-documentation/boot-process.md Documents the new headers/perf mounting behavior.
CMakeLists.txt Updates dev compile-time path definition to point at artifacts.vhd.

Comment thread src/linux/init/config.cpp Outdated
Copilot AI review requested due to automatic review settings August 6, 2026 03:13

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/linux/init/util.cpp:1745

  • UtilMountFile() now stats the source path, but it only checks for existence. If Source exists but is not a regular file (e.g., a directory due to a packaging/layout issue), the subsequent bind mount will fail after creating Destination, potentially leaving an empty executable stub behind (e.g., /usr/bin/perf). Validate the source is a regular file before creating/mutating the destination.
    struct stat sourceInfo{};
    THROW_LAST_ERROR_IF(stat(Source, &sourceInfo) < 0);

    // Is the file is a symlink, delete it since that would break the mount.
    if (std::filesystem::is_symlink(Destination))

src/linux/init/config.cpp:1158

  • When exposing kernel headers, the code creates /lib/modules//build as a symlink but ignores EEXIST. If the distro already has a stale/wrong symlink (or a leftover file) at that path, WSL will silently keep pointing at the wrong headers even though the correct headers were mounted. Consider removing an existing non-directory entry before creating the symlink, and log a clear warning if a directory prevents creating the symlink.
                        if (UtilMkdirPath(modulesDir.c_str(), 0755) == 0)
                        {
                            const std::string linkPath = modulesDir + "/build";
                            if ((symlink(headersRoot.c_str(), linkPath.c_str()) < 0) && (errno != EEXIST))
                            {

Copilot AI review requested due to automatic review settings August 6, 2026 03:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (1)

test/windows/UnitTests.cpp:3021

  • KernelArtifacts test compiles a C program with cc, which adds a dependency on a compiler toolchain being installed in the test distro. This is likely to be missing in minimal images and can make CI runs flaky/unrelated to kernel artifacts. Prefer validating the headers’ presence/recency by checking for expected symbols directly in the mounted header tree (no compiler required).
            LxsstuLaunchWsl(
                LR"BASH(bash -ec '
                    d=$(mktemp -d)
                    trap "rm -rf $d" EXIT
                    cat > "$d/t.c" <<EOF

Copilot AI review requested due to automatic review settings August 6, 2026 17:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@benhillis
Ben Hillis (benhillis) force-pushed the user/benhill/module_vhd_artifacts branch from 2c470ca to 7b17a3e Compare August 6, 2026 18:02
Copilot AI review requested due to automatic review settings August 6, 2026 18:02
@benhillis
Ben Hillis (benhillis) force-pushed the user/benhill/module_vhd_artifacts branch from 2c470ca to 7b17a3e Compare August 6, 2026 18:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@benhillis
Ben Hillis (benhillis) requested a lite review from Copilot August 6, 2026 19:52
Comment thread src/linux/init/config.cpp Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 15 out of 15 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/linux/init/main.cpp:1634

  • The comment says distro init will move the kernel headers mount to /usr/src/linux-headers-<uname -r>/include, but the code passes a target of /usr/src/linux-headers-<release> and later expects headers under .../include/.... This mismatch is confusing and makes it harder to reason about the mount layout.
    // If kernel headers were mounted, move them to a temporary location and pass the desired
    // target path to the distro init via an environment variable. Distro init will move the
    // mount to /usr/src/linux-headers-<uname -r>/include and create the
    // /lib/modules/<release>/build symlink.

test/windows/UnitTests.cpp:3033

  • stat -Lc dereferences symlinks, so this check can pass even if /usr/bin/perf is just a symlink (or otherwise not a mount point). The later umount /usr/bin/perf in this test would then fail. It’s more robust to first assert /usr/bin/perf is actually mounted (e.g., via /proc/self/mountinfo) and compare inode/device without -L.
        VERIFY_ARE_EQUAL(
            LxsstuLaunchWsl(
                L"test \"$(stat -Lc %d:%i /usr/bin/perf)\" = \"$(stat -Lc %d:%i /usr/lib/linux-tools/$(uname -r)/bin/perf)\"", nullptr, nullptr, nullptr, nullptr),
            0u);

Copilot AI review requested due to automatic review settings August 7, 2026 15:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/linux/init/main.cpp:1634

  • This comment says the headers mount is moved to /usr/src/linux-headers-<uname -r>/include, but the code sets Config.KernelHeadersTarget to /usr/src/linux-headers-<release> (no /include) and the tests expect /lib/modules/<release>/build/include/... to exist. The comment should match the actual mount target.
    // If kernel headers were mounted, move them to a temporary location and pass the desired
    // target path to the distro init via an environment variable. Distro init will move the
    // mount to /usr/src/linux-headers-<uname -r>/include and create the
    // /lib/modules/<release>/build symlink.

src/linux/init/WSLCInit.cpp:858

  • In WSLC_MOUNT_MODULES handling, g_state.ModulesMountPoint is assigned before the bind-mount succeeds. If UtilMount() throws after the assignment, ModulesMountPoint remains set and later chroot mounts will attempt to MS_MOVE a non-existent mount, and subsequent WSLC_MOUNT_MODULES requests will hit the assert(!ModulesMountPoint). Also, this path always bind-mounts "//modules" and doesn’t fall back to the legacy flat-layout VHD (modules at filesystem root), which previously worked via the old WSLC_MOUNT::KernelModules path.
        assert(!g_state.ModulesMountPoint.has_value());

        utsname unameBuffer{};
        THROW_LAST_ERROR_IF(uname(&unameBuffer) < 0);

        const char* source = wsl::shared::string::FromSpan(Buffer, Message.SourceIndex);
        THROW_LAST_ERROR_IF(UtilMount(source, c_kernelModulesVhdMountPoint, "ext4", MS_RDONLY, nullptr, c_defaultRetryTimeout) < 0);

Copilot AI review requested due to automatic review settings August 7, 2026 16:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/linux/init/util.cpp:1745

  • Grammar in this comment is incorrect and it refers to the destination path, not the source file.
    // Is the file is a symlink, delete it since that would break the mount.

src/linux/init/config.cpp:1192

  • The perf shadowing logic only bind-mounts over /usr/bin/perf when it is a regular file. On many distros /usr/bin/perf is a symlink (e.g. alternatives), so the kernel-matched perf won’t be used and the distro perf may be picked up instead, contradicting the intent to always expose the kernel-matched perf when the distro provides one.
        struct stat existing{};
        if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))
        {
            const std::string perfBinary = target + "/bin/perf";
            if (UtilMountFile(perfBinary.c_str(), PERF_BINARY_PATH) < 0)
            {
                LOG_ERROR("UtilMountFile({}, {}) failed {}", perfBinary, PERF_BINARY_PATH, errno);
            }
        }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/linux/init/config.cpp:1199

  • lstat classifies /usr/bin/perf symlinks as S_IFLNK, so this skips the bind mount for a distro whose perf entry is a symlink to a regular binary. Because ConfigAppendToPath appends the artifacts directory, that earlier /usr/bin/perf still wins and the kernel-matched tool is not used. Follow the symlink here; UtilMountFile already removes a symlink destination before creating the bind-mount target.
        if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))

test/windows/UnitTests.cpp:3020

  • This prefix comparison can accept mismatched headers: for example, header version 6.18.4 matches a running release beginning with 6.18.40. Require a release-component/suffix boundary after $v so the test actually verifies the package's headers match the running kernel.
                    case "$(uname -r)" in "$v"*) exit 0 ;; *) exit 8 ;; esac

src/linux/init/main.cpp:3215

  • The new flat-layout branch is the backward-compatibility path for existing custom module-only VHDs, but the updated KernelModules test only exercises the nested artifact lowerdir and no test supplies a root-level modules.dep. Add a flat-layout VHD case that boots with custom kernel/modules and verifies module loading, otherwise this promised compatibility can regress unnoticed.
            const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
            const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/linux/init/main.cpp:1633

  • This comment names the wrong mount target: KernelHeadersTarget is /usr/src/linux-headers-<release>, and the test accesses its include child through the build symlink. Saying the mount itself moves to .../include makes the documented flow disagree with the implementation.
    // mount to /usr/src/linux-headers-<uname -r>/include and create the

src/linux/init/main.cpp:3215

  • The deprecated flat-layout fallback is a compatibility promise introduced here, but the updated KernelModules test only verifies the nested default artifact path. Add coverage that boots with a flat module-only VHD and verifies the overlay/module loading path before relying on this fallback for existing custom VHD users.
            const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);

src/linux/init/main.cpp:3210

  • The newly introduced locals in this artifacts-layout block use PascalCase (Release, ArtifactsBase, NestedModules, and others), while the project convention requires camelCase for local variables. Rename them (for example, release and artifactsBase) and update their references.
            const std::string Release{UnameBuffer.release};

            const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
            const std::string NestedModules = ArtifactsBase + "/modules";

src/linux/init/config.cpp:1205

  • lstat excludes distributions where /usr/bin/perf is a symlink to their packaged perf binary. Because the bundled tools directory is appended to PATH, that symlink continues to resolve first and users run the stale distro perf. Follow the symlink for classification and bind over its resolved regular-file target so the distro filesystem is restored when the mount disappears.
        if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))

Copilot AI review requested due to automatic review settings August 12, 2026 21:57
@benhillis
Ben Hillis (benhillis) force-pushed the user/benhill/module_vhd_artifacts branch from 4f8fe90 to 7d9b8f3 Compare August 12, 2026 21:57

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/linux/init/config.cpp:1199

  • A distro-provided perf can also be a symlink. lstat classifies that as S_IFLNK, so this branch skips the bind mount; because ConfigAppendToPath appends the artifacts directory after /usr/bin, the stale distro entry still wins. Include symlinks here—UtilMountFile already removes a destination symlink before mounting the matching binary.
        if ((lstat(PERF_BINARY_PATH, &existing) == 0) && S_ISREG(existing.st_mode))

src/linux/init/main.cpp:3268

  • The updated KernelModules test only uses the packaged nested artifacts.vhd; no test exercises this new legacy flat-layout branch. Since retaining that compatibility is an explicit behavior of this change, add coverage using a VHD with modules.dep at its root and verify that its modules still mount/load without headers or perf.
            const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
            const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
            const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);

src/linux/init/main.cpp:3264

  • These newly introduced local variables use PascalCase, while WSL's naming convention requires camelCase for locals. Rename these and the other new locals in this block (for example, release, artifactsBase, nestedModules, nestedLayout, headersSource, and perfSource).
            const std::string Release{UnameBuffer.release};

            const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
            const std::string NestedModules = ArtifactsBase + "/modules";

Comment thread src/linux/init/config.cpp Outdated
Comment thread src/linux/init/config.cpp Outdated
Copilot AI review requested due to automatic review settings August 19, 2026 22:35
@benhillis
Ben Hillis (benhillis) force-pushed the user/benhill/module_vhd_artifacts branch from 7d9b8f3 to de84ca5 Compare August 19, 2026 22:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/linux/init/main.cpp:3277

  • The legacy compatibility branch is not covered by the updated kernel tests: every test now uses the packaged nested artifacts.vhd, and no test supplies a flat VHD with modules.dep at its root. Because this fallback preserves existing custom kernelModules configurations, add a regression test that boots with the legacy layout and verifies that its modules are mounted or loaded.
            const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
            const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
            const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);

Update the packaged kernel dependency from modules.vhd to artifacts.vhd and load kernel modules from the versioned <release>/modules directory. Preserve support for legacy flat module VHDs in the standard WSL boot path and teach WSLC sessions to mount modules from the nested layout.

Update development paths and kernel module tests for the renamed VHD and new mount source.

Co-authored-by: Copilot <[email protected]>

Copilot-Session: a7cc3086-6311-4bc8-a591-49fd0e6b03fd
Copilot AI review requested due to automatic review settings August 20, 2026 00:12
@benhillis Ben Hillis (benhillis) changed the title WSL2: ship kernel headers and perf in the kernel artifacts VHD Use unified kernel artifacts VHD layout Aug 20, 2026
@benhillis
Ben Hillis (benhillis) force-pushed the user/benhill/module_vhd_artifacts branch from de84ca5 to 1f307a9 Compare August 20, 2026 00:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/linux/init/main.cpp:3246

  • The new locals Error, NestedLayout, ModulesLower, and LegacyLayout also use PascalCase instead of the required camelCase local-variable convention. Rename them and all subsequent references.
            std::error_code Error{};
            const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
            const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
            const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);

src/linux/init/main.cpp:3241

  • The newly introduced local variables Release, ArtifactsBase, and NestedModules use PascalCase, while WSL local variables use camelCase. Rename these variables and their references accordingly.

This issue also appears on line 3243 of the same file.

            const std::string Release{UnameBuffer.release};

            const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
            const std::string NestedModules = ArtifactsBase + "/modules";

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.

Suppressed comments (4)

src/linux/init/main.cpp:3246

  • These newly introduced locals also violate the required camelCase convention. Please rename Error, NestedLayout, ModulesLower, and LegacyLayout and update all uses in this branch.
            std::error_code Error{};
            const bool NestedLayout = std::filesystem::is_directory(NestedModules, Error);
            const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
            const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);

src/linux/init/main.cpp:3246

  • The compatibility branch for legacy flat module VHDs is not exercised by the updated KernelModules test, which only verifies the new nested package layout. Please add a regression case using a flat VHD with modules.dep at its root and verify that the overlay uses that root and modules still load; otherwise this promised backward-compatibility path can regress unnoticed.
            const std::string ModulesLower = NestedLayout ? NestedModules : std::string{KERNEL_MODULES_VHD_PATH};
            const bool LegacyLayout = !NestedLayout && std::filesystem::is_regular_file(ModulesLower + "/modules.dep", Error);

src/windows/wslcsession/WSLCVirtualMachine.cpp:367

  • The new WSLC-specific mount protocol is not directly covered by a test. Existing session tests prove that a session starts, but do not verify that /lib/modules/$(uname -r) exposes the nested <release>/modules tree. Please add a WSLC session/E2E assertion for a known module artifact (for example modules.dep) so this second boot path is validated.
    MountModules(m_initChannel, modulesDevice.c_str());

src/linux/init/main.cpp:3241

  • These newly introduced local variables use PascalCase, while the WSL coding guideline requires local variables to use camelCase. Please rename Release, ArtifactsBase, and NestedModules (and update their uses) to release, artifactsBase, and nestedModules.

This issue also appears on line 3243 of the same file.

            const std::string Release{UnameBuffer.release};

            const std::string ArtifactsBase = std::format("{}/{}", KERNEL_MODULES_VHD_PATH, Release);
            const std::string NestedModules = ArtifactsBase + "/modules";

@OneBlue Blue (OneBlue) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

Reading this I also realized that the whole ModulesMountPoint logic isn't useful anymore since we always mount the modules after chroot, but that's outside the scope of this change.
I'll clean this up once merged

@benhillis
Ben Hillis (benhillis) merged commit 9540481 into master Aug 20, 2026
13 checks passed
@benhillis
Ben Hillis (benhillis) deleted the user/benhill/module_vhd_artifacts branch August 20, 2026 17:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants