[Lldb-commits] [lldb] [llvm] [DO NOT MERGE YET] attempt to fix check-lldb in PR CI (PR #198630)
Charles Zablit via lldb-commits
lldb-commits at lists.llvm.org
Wed May 20 03:15:14 PDT 2026
https://github.com/charles-zablit updated https://github.com/llvm/llvm-project/pull/198630
>From f4e206954c105fb9e02e3857c1451a5e32e08f06 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 20 May 2026 09:43:50 +0100
Subject: [PATCH 1/5] [lldb][windows] Resolve DLL path for LOAD_DLL events with
NULL hFile
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Problem
The Windows debug API can deliver LOAD_DLL_DEBUG_EVENT with `hFile`
set to NULL. This is documented in MSDN's `LOAD_DLL_DEBUG_INFO`
reference (see [`LOAD_DLL_DEBUG_INFO`][1]). It happens in practice
inside Windows Server Core containers (e.g.
`ghcr.io/llvm/ci-windows-2022:latest`) for system DLLs like
`kernel32.dll`, `KernelBase.dll` and `ucrtbase.dll` — both during
attach (already-loaded modules) and during launch (early loader
events fired before the inferior's `PEB_LDR_DATA` is populated).
`DebuggerThread::HandleLoadDllEvent` previously dropped any such
event with a "NULL file handle, returning..." log line. The result
inside containers is that LLDB's module list contained only the
process exe and `ntdll.dll`, which broke:
- `process load <dll>` (needs `LoadLibrary` resolved through a
tracked module),
- expression evaluation that resolves Win32 exports such as
`GetCurrentProcessId`,
- stack unwinds through `Sleep`/`SleepEx` user→kernel transitions,
- module-list-based shell tests like
`lldb-shell :: Target/dependent-modules-nodupe-windows.test` and
`lldb-shell :: Process/Windows/process_load.cpp`.
These are the symptoms behind issue #132800 and the reason
`check-lldb` has been disabled in Windows premerge CI.
# Fix
`HandleLoadDllEvent` now resolves the path via several increasingly
indirect methods:
1. `GetFinalPathNameByHandle` on `info.hFile` (existing path).
2. `CreateFileMappingW` + `GetMappedFileNameW` on the same handle
(existing path).
3. `LOAD_DLL_DEBUG_INFO::lpImageName`, which is a pointer in the
inferior's address space to a string holding the DLL's name.
The kernel populates this for most NULL-`hFile` events,
including the early system-DLL loads we hit in containers.
This is also documented in MSDN as the proper fallback when
`hFile` is NULL but is not used by LLDB today.
4. `GetModuleFileNameExW` / `GetMappedFileNameW` on the load
address in the inferior — last resort if everything else fails
(e.g. attach to a process where the loader has fully populated
the PEB).
The NT-device-path → DOS-letter conversion that the existing
`GetFileNameFromHandleFallback` performs is factored out into
`ConvertNtDevicePathToDosPath` so the new `GetFileNameByLoadAddress`
helper can reuse it.
# Validation
Reproduced the broken state inside `ghcr.io/llvm/ci-windows-2022`
attaching LLDB to a sleeping `powershell.exe`: the module list
contained only the exe and `ntdll.dll` (2 modules). With this
patch applied the same inferior reports 69 modules, including
`kernel32.dll` and `KernelBase.dll`. Reproducing
`dependent-modules-nodupe-windows.test`'s `lldb -b -o "b main" -o
run -o "target modules list"` flow produces the expected `ntdll.dll`
+ `KERNEL32.DLL` ordering, and `process load kernel32.dll` now
prints `Loading "kernel32.dll"...ok` / `Image 0 loaded.`
[1]: https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-load_dll_debug_info
---
.../Process/Windows/Common/DebuggerThread.cpp | 208 +++++++++++++-----
1 file changed, 152 insertions(+), 56 deletions(-)
diff --git a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
index 8c35a2b9262cc..7c8f272432c2d 100644
--- a/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
+++ b/lldb/source/Plugins/Process/Windows/Common/DebuggerThread.cpp
@@ -483,6 +483,43 @@ DebuggerThread::HandleExitProcessEvent(const EXIT_PROCESS_DEBUG_INFO &info,
return DBG_CONTINUE;
}
+// Convert an NT device path (e.g. "\Device\HarddiskVolume3\Windows\...") into
+// its DOS form ("C:\Windows\..."). Returns std::nullopt if no drive letter
+// matches the device prefix.
+static std::optional<std::string>
+ConvertNtDevicePathToDosPath(llvm::ArrayRef<wchar_t> nt_path) {
+ // A series of null-terminated strings, plus an additional null character
+ std::array<wchar_t, 512> drive_strings;
+ drive_strings[0] = L'\0';
+ if (!::GetLogicalDriveStringsW(drive_strings.size(), drive_strings.data()))
+ return std::nullopt;
+
+ std::array<wchar_t, 3> drive = {L"_:"};
+ for (const wchar_t *it = drive_strings.data(); *it != L'\0';
+ it += wcslen(it) + 1) {
+ // Copy the drive letter to the template string
+ drive[0] = it[0];
+ std::array<wchar_t, MAX_PATH> device_name;
+ if (::QueryDosDeviceW(drive.data(), device_name.data(),
+ device_name.size())) {
+ size_t device_name_len = wcslen(device_name.data());
+ if (device_name_len < nt_path.size()) {
+ bool match =
+ _wcsnicmp(nt_path.data(), device_name.data(), device_name_len) == 0;
+ if (match && nt_path[device_name_len] == L'\\') {
+ // Replace device path with its drive letter
+ std::wstring rebuilt_path(drive.data());
+ rebuilt_path.append(&nt_path[device_name_len]);
+ std::string path_utf8;
+ llvm::convertWideToUTF8(rebuilt_path, path_utf8);
+ return path_utf8;
+ }
+ }
+ }
+ }
+ return std::nullopt;
+}
+
static std::optional<std::string> GetFileNameFromHandleFallback(HANDLE hFile) {
// Check that file is not empty as we cannot map a file with zero length.
DWORD dwFileSizeHi = 0;
@@ -506,48 +543,83 @@ static std::optional<std::string> GetFileNameFromHandleFallback(HANDLE hFile) {
mapped_filename.data(), mapped_filename.size()))
return std::nullopt;
- // A series of null-terminated strings, plus an additional null character
- std::array<wchar_t, 512> drive_strings;
- drive_strings[0] = L'\0';
- if (!::GetLogicalDriveStringsW(drive_strings.size(), drive_strings.data()))
+ return ConvertNtDevicePathToDosPath(mapped_filename);
+}
+
+// Look up a loaded module's path by its base address in the inferior. Used
+// when LOAD_DLL_DEBUG_EVENT is delivered with a NULL hFile, which can happen
+// on modern Windows for system DLLs and is common inside Windows containers.
+static std::optional<std::string> GetFileNameByLoadAddress(HANDLE hProcess,
+ LPVOID base_addr) {
+ // Module handles in Win32 are the image base address. GetModuleFileNameExW
+ // returns the DOS path of the module loaded at that base in `hProcess`.
+ std::array<wchar_t, MAX_PATH + 1> module_filename;
+ DWORD len =
+ ::GetModuleFileNameExW(hProcess, reinterpret_cast<HMODULE>(base_addr),
+ module_filename.data(), module_filename.size());
+ if (len > 0 && len < module_filename.size()) {
+ std::string path_utf8;
+ llvm::convertWideToUTF8(std::wstring(module_filename.data(), len),
+ path_utf8);
+ return path_utf8;
+ }
+
+ // Fallback: ask the kernel for the file backing the mapping at this address.
+ // This returns an NT device path which we then translate to a DOS path.
+ std::array<wchar_t, MAX_PATH + 1> mapped_filename;
+ if (!::GetMappedFileNameW(hProcess, base_addr, mapped_filename.data(),
+ mapped_filename.size()))
return std::nullopt;
+ return ConvertNtDevicePathToDosPath(mapped_filename);
+}
- std::array<wchar_t, 3> drive = {L"_:"};
- for (const wchar_t *it = drive_strings.data(); *it != L'\0';
- it += wcslen(it) + 1) {
- // Copy the drive letter to the template string
- drive[0] = it[0];
- std::array<wchar_t, MAX_PATH> device_name;
- if (::QueryDosDeviceW(drive.data(), device_name.data(),
- device_name.size())) {
- size_t device_name_len = wcslen(device_name.data());
- if (device_name_len < mapped_filename.size()) {
- bool match = _wcsnicmp(mapped_filename.data(), device_name.data(),
- device_name_len) == 0;
- if (match && mapped_filename[device_name_len] == L'\\') {
- // Replace device path with its drive letter
- std::wstring rebuilt_path(drive.data());
- rebuilt_path.append(&mapped_filename[device_name_len]);
- std::string path_utf8;
- llvm::convertWideToUTF8(rebuilt_path, path_utf8);
- return path_utf8;
- }
- }
- }
+// Resolve the LOAD_DLL_DEBUG_INFO::lpImageName field. Per MSDN, lpImageName is
+// a pointer (in the inferior's address space) to either a NULL pointer or a
+// pointer to a string holding the DLL's name. fUnicode determines whether the
+// string is wide or ANSI. The kernel populates this for many LOAD_DLL events
+// where hFile is NULL, including the early system-DLL loads we hit in Windows
+// containers.
+static std::optional<std::string>
+GetFileNameFromImageNameField(HANDLE hProcess,
+ const LOAD_DLL_DEBUG_INFO &info) {
+ if (info.lpImageName == nullptr)
+ return std::nullopt;
+
+ // The first dereference reads a pointer in the inferior.
+ LPVOID name_addr = nullptr;
+ SIZE_T bytes_read = 0;
+ if (!::ReadProcessMemory(hProcess, info.lpImageName, &name_addr,
+ sizeof(name_addr), &bytes_read) ||
+ bytes_read != sizeof(name_addr) || name_addr == nullptr)
+ return std::nullopt;
+
+ // The second dereference reads the actual string.
+ if (info.fUnicode) {
+ std::array<wchar_t, MAX_PATH + 1> wbuf{};
+ if (!::ReadProcessMemory(hProcess, name_addr, wbuf.data(),
+ (wbuf.size() - 1) * sizeof(wchar_t), &bytes_read))
+ return std::nullopt;
+ std::string path_utf8;
+ llvm::convertWideToUTF8(wbuf.data(), path_utf8);
+ if (path_utf8.empty())
+ return std::nullopt;
+ return path_utf8;
}
- return std::nullopt;
+
+ std::array<char, MAX_PATH + 1> abuf{};
+ if (!::ReadProcessMemory(hProcess, name_addr, abuf.data(), abuf.size() - 1,
+ &bytes_read))
+ return std::nullopt;
+ std::string path(abuf.data());
+ if (path.empty())
+ return std::nullopt;
+ return path;
}
DWORD
DebuggerThread::HandleLoadDllEvent(const LOAD_DLL_DEBUG_INFO &info,
DWORD thread_id) {
Log *log = GetLog(WindowsLog::Event);
- if (info.hFile == nullptr) {
- // Not sure what this is, so just ignore it.
- LLDB_LOG(log, "Warning: Inferior {0} has a NULL file handle, returning...",
- m_process.GetProcessId());
- return DBG_CONTINUE;
- }
auto on_load_dll = [&](llvm::StringRef path) {
FileSpec file_spec(path);
@@ -560,32 +632,56 @@ DebuggerThread::HandleLoadDllEvent(const LOAD_DLL_DEBUG_INFO &info,
m_debug_delegate->OnLoadDll(module_spec, load_addr);
};
- std::vector<wchar_t> buffer(1);
- DWORD required_size =
- GetFinalPathNameByHandleW(info.hFile, &buffer[0], 0, VOLUME_NAME_DOS);
- if (required_size > 0) {
- buffer.resize(required_size + 1);
- required_size = GetFinalPathNameByHandleW(info.hFile, &buffer[0],
- required_size, VOLUME_NAME_DOS);
- std::string path_str_utf8;
- llvm::convertWideToUTF8(buffer.data(), path_str_utf8);
- llvm::StringRef path_str = path_str_utf8;
- const char *path = path_str.data();
- if (path_str.starts_with("\\\\?\\"))
- path += 4;
-
- on_load_dll(path);
- } else if (std::optional<std::string> path =
- GetFileNameFromHandleFallback(info.hFile)) {
- on_load_dll(*path);
+ // Try resolving the DLL's path via several increasingly indirect methods:
+ // 1. GetFinalPathNameByHandle on info.hFile.
+ // 2. CreateFileMappingW + GetMappedFileNameW on the same hFile.
+ // 3. The LOAD_DLL_DEBUG_INFO::lpImageName field, which is a pointer (in
+ // the inferior's address space) to a string holding the DLL's name.
+ // The kernel populates this for most LOAD_DLL events where hFile is
+ // NULL, including the early system-DLL loads we hit inside Windows
+ // containers (the docker ci-windows-2022 image being the motivating
+ // example).
+ // 4. GetModuleFileNameExW / GetMappedFileNameW on the load address. Last
+ // resort if everything else fails.
+ std::optional<std::string> resolved_path;
+ if (info.hFile != nullptr) {
+ std::vector<wchar_t> buffer(1);
+ DWORD required_size =
+ GetFinalPathNameByHandleW(info.hFile, &buffer[0], 0, VOLUME_NAME_DOS);
+ if (required_size > 0) {
+ buffer.resize(required_size + 1);
+ GetFinalPathNameByHandleW(info.hFile, &buffer[0], required_size,
+ VOLUME_NAME_DOS);
+ std::string path_utf8;
+ llvm::convertWideToUTF8(buffer.data(), path_utf8);
+ llvm::StringRef path_str = path_utf8;
+ if (path_str.starts_with("\\\\?\\"))
+ path_str = path_str.drop_front(4);
+ resolved_path = path_str.str();
+ } else {
+ resolved_path = GetFileNameFromHandleFallback(info.hFile);
+ }
+ }
+
+ HANDLE hProcess = m_process.GetNativeProcess().GetSystemHandle();
+ if (!resolved_path)
+ resolved_path = GetFileNameFromImageNameField(hProcess, info);
+ if (!resolved_path)
+ resolved_path = GetFileNameByLoadAddress(hProcess, info.lpBaseOfDll);
+
+ if (resolved_path) {
+ on_load_dll(*resolved_path);
} else {
- LLDB_LOG(
- log,
- "Inferior {0} - Error {1} occurred calling GetFinalPathNameByHandle",
- m_process.GetProcessId(), ::GetLastError());
+ LLDB_LOG(log,
+ "Inferior {0} - could not resolve path for LOAD_DLL_DEBUG_EVENT "
+ "(hFile={1}, base={2:x}, last error={3})",
+ m_process.GetProcessId(), info.hFile, info.lpBaseOfDll,
+ ::GetLastError());
}
+
// Windows does not automatically close info.hFile, so we need to do it.
- ::CloseHandle(info.hFile);
+ if (info.hFile != nullptr)
+ ::CloseHandle(info.hFile);
return DBG_CONTINUE;
}
>From ca3229b7b137deacdbf3adde44f71a2d3e42b276 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 20 May 2026 09:44:03 +0100
Subject: [PATCH 2/5] [lldb][test] Make dependent-modules-nodupe-windows
case-insensitive
# Problem
`lldb-shell :: Target/dependent-modules-nodupe-windows.test` checks
the post-launch module list for substrings like `ntdll.dll` and
`kernel32.dll` using FileCheck's default case-sensitive matching.
The DLL path that LLDB reports is whatever the OS gave it. On bare
Windows machines that ends up as `kernel32.dll` (lowercase), via
`GetFinalPathNameByHandle`, which returns the on-disk case. In
Windows containers, the same DLL frequently shows up as
`KERNEL32.DLL` because the loader stores the import-table case in
`PEB_LDR_DATA::FullDllName` and the now-used `lpImageName` fallback
faithfully passes it through.
Both spellings refer to the same file; Windows path comparison is
case-insensitive. The test is asserting an OS-level behavior, not
a casing convention, so it should not be case-sensitive.
# Fix
Pass `--ignore-case` to FileCheck.
This matches what other LLDB shell tests on Windows do where the
loader is involved, and removes a spurious failure observed in
`ghcr.io/llvm/ci-windows-2022` after the LOAD_DLL hFile=NULL fix
correctly enumerates kernel32 in the module list.
---
lldb/test/Shell/Target/dependent-modules-nodupe-windows.test | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test b/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test
index 9c507dc9079b7..6c847124e0ae5 100644
--- a/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test
+++ b/lldb/test/Shell/Target/dependent-modules-nodupe-windows.test
@@ -9,7 +9,8 @@
# RUN: %clang_host -g0 -O0 %S/Inputs/main.c %t.shlib.lib -o %t.main.exe \
# RUN: %if windows-msvc %{-Wl,-debug:none%}
# RUN: %lldb -b -o "#before" -o "target modules list" -o "b main" -o run \
-# RUN: -o "#after" -o "target modules list" %t.main.exe | FileCheck %s
+# RUN: -o "#after" -o "target modules list" %t.main.exe \
+# RUN: | FileCheck --ignore-case %s
# CHECK-LABEL: #before
# CHECK-NEXT: target modules list
>From 5d49083d3a2f201dd5a37e92c501593fea970d55 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 20 May 2026 09:44:19 +0100
Subject: [PATCH 3/5] [ci][windows] Install GNU make in
github-action-ci-windows container
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
# Problem
The lldb-api test suite builds each test's inferior via
`lldb/packages/Python/lldbsuite/test/make/Makefile.rules`, which
needs GNU make. The current Windows CI container
(`ghcr.io/llvm/ci-windows-2022:latest`) does not install make, so
LLDB's CMake configure step ends up with
`LLDB_DEFAULT_TEST_MAKE-NOTFOUND` and every lldb-api test reports
`UNRESOLVED` with `FileNotFoundError: [WinError 2] The system
cannot find the file specified` from `subprocess.check_output` in
`lldbtest.py::runBuildCommand`.
In a recent Windows premerge run with `check-lldb` enabled, this
accounted for **588 unresolved** lldb-api tests — the entire suite
minus a handful of lit-only tests.
# Fix
Add `choco install -y make --version 4.4.1` to the existing
chocolatey install layer. This drops `make.exe` at
`C:\ProgramData\chocolatey\bin\make.exe`, which is already on
`PATH` per the container's existing layout, so cmake's
`find_program(LLDB_DEFAULT_TEST_MAKE make)` will succeed.
GNU make 4.4 is what lldb's `Makefile.rules` is written against
(it uses pattern rules, `$(eval)`, etc.), and the chocolatey `make`
package ships exactly that.
After this lands and a new `ci-windows-2022:latest` is published,
running `check-lldb` on Windows premerge takes the lldb-api test
count from 0 / 588 unresolved to ~2400+ passing.
Companion to the LOAD_DLL hFile=NULL fix
(`[lldb][windows] Resolve DLL path for LOAD_DLL events with NULL
hFile`) and the planned switch from `lldb lldb-dap` to `check-lldb`
in `.ci/compute_projects.py`.
---
.../workflows/containers/github-action-ci-windows/Dockerfile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/.github/workflows/containers/github-action-ci-windows/Dockerfile b/.github/workflows/containers/github-action-ci-windows/Dockerfile
index f7956413e5829..47fb6f5ff0d25 100644
--- a/.github/workflows/containers/github-action-ci-windows/Dockerfile
+++ b/.github/workflows/containers/github-action-ci-windows/Dockerfile
@@ -45,7 +45,8 @@ RUN regsvr32 /S "C:\BuildTools\DIA SDK\bin\amd64\msdia140.dll" & \
RUN choco install -y ninja --version 1.13.1 && \
choco install -y git --version 2.50.1 && \
choco install -y sccache --version 0.10.0 && \
- choco install -y python3 --version 3.12.3
+ choco install -y python3 --version 3.12.3 && \
+ choco install -y make --version 4.4.1
# Testing requires psutil
RUN pip install psutil
>From 8715917addbf557cc25cf065962e4ba9867e4720 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 20 May 2026 09:44:37 +0100
Subject: [PATCH 4/5] [ci][lldb] Run check-lldb on Windows premerge
# Background
When the Windows premerge job was added, lldb's `check-lldb` was
overridden in `.ci/compute_projects.py` to just `lldb lldb-dap`,
i.e. compile-only. The TODO comment pointed at issue #132800:
> LLDB tests need environment setup on Windows. In the meantime,
> at least compile lldb and lldb-dap to catch breakage.
Two concrete environmental issues kept `check-lldb` red:
1. The Windows debug API delivers `LOAD_DLL_DEBUG_EVENT` with
`hFile == NULL` for system DLLs inside Windows Server Core
containers (kernel32, KernelBase, ucrtbase, ...), and LLDB
dropped those events. The result was that LLDB's module list
contained only the inferior exe and `ntdll.dll`, breaking
`process load`, expression evaluation of Win32 exports, and
stack unwinds through `Sleep`/`SleepEx`. Fixed by
`[lldb][windows] Resolve DLL path for LOAD_DLL events with NULL
hFile`.
2. The CI container did not have `make.exe` installed, so cmake
configured with `LLDB_DEFAULT_TEST_MAKE-NOTFOUND` and every
lldb-api test was UNRESOLVED on a `FileNotFoundError` from the
test inferior build step. Fixed by
`[ci][windows] Install GNU make in github-action-ci-windows
container`.
With both fixes in and a new `ghcr.io/llvm/ci-windows-2022:latest`
image published, a full `check-lldb` run inside the container
reports ~32890 passing / 1 failing (a container-specific
`TestLoadUnload::test_static_init_during_load` that passes on bare
Windows hardware) / 1 unrelated XPASS in lldb-dap restart.
# Change
Remove the `"lldb": "lldb lldb-dap"` override from
`PROJECT_CHECK_TARGETS_OVERRIDE["Windows"]`. The default
`PROJECT_CHECK_TARGETS["lldb"] = "check-lldb"` then takes effect
on Windows the same as everywhere else, so PRs touching lldb run
the lit suite on Windows premerge.
The dictionary structure is preserved so other Windows-only
overrides can be added in the future.
The four `compute_projects_test.py` test cases that previously
asserted on the `lldb lldb-dap` build-only output are updated to
assert `check-lldb` (or, for the multi-project Windows cases, the
properly-sorted full check-target list with `check-lldb` slotted
in alphabetically).
---
.ci/compute_projects.py | 7 +------
.ci/compute_projects_test.py | 8 ++++----
2 files changed, 5 insertions(+), 10 deletions(-)
diff --git a/.ci/compute_projects.py b/.ci/compute_projects.py
index 97caec7ad947d..496fad5f5ed70 100644
--- a/.ci/compute_projects.py
+++ b/.ci/compute_projects.py
@@ -201,12 +201,7 @@
# where a project can be built but its tests are not yet stable on that
# platform, so we still want a compile-time signal.
PROJECT_CHECK_TARGETS_OVERRIDE = {
- "Windows": {
- # TODO(issues/132800): LLDB tests need environment setup on Windows.
- # In the meantime, at least compile lldb and lldb-dap to catch
- # breakage.
- "lldb": "lldb lldb-dap",
- },
+ "Windows": {},
}
diff --git a/.ci/compute_projects_test.py b/.ci/compute_projects_test.py
index 394ded6d563f0..2763623be12e1 100644
--- a/.ci/compute_projects_test.py
+++ b/.ci/compute_projects_test.py
@@ -43,7 +43,7 @@ def test_llvm_windows(self):
)
self.assertEqual(
env_variables["project_check_targets"],
- "check-clang check-clang-tools check-lld check-llvm check-mlir check-polly lldb lldb-dap",
+ "check-clang check-clang-tools check-lld check-lldb check-llvm check-mlir check-polly",
)
self.assertEqual(env_variables["runtimes_to_build"], "")
self.assertEqual(
@@ -116,7 +116,7 @@ def test_clang_windows(self):
)
self.assertEqual(
env_variables["project_check_targets"],
- "check-clang check-clang-tools lldb lldb-dap",
+ "check-clang check-clang-tools check-lldb",
)
self.assertEqual(env_variables["runtimes_to_build"], "compiler-rt")
self.assertEqual(
@@ -331,7 +331,7 @@ def test_windows_ci(self):
)
self.assertEqual(
env_variables["project_check_targets"],
- "check-clang check-clang-cir check-clang-tools check-lld check-llvm check-mlir check-polly lldb lldb-dap",
+ "check-clang check-clang-cir check-clang-tools check-lld check-lldb check-llvm check-mlir check-polly",
)
self.assertEqual(
env_variables["runtimes_to_build"],
@@ -472,7 +472,7 @@ def test_lldb_windows(self):
["lldb/CMakeLists.txt"], "Windows"
)
self.assertEqual(env_variables["projects_to_build"], "clang;lld;lldb;llvm")
- self.assertEqual(env_variables["project_check_targets"], "lldb lldb-dap")
+ self.assertEqual(env_variables["project_check_targets"], "check-lldb")
self.assertEqual(env_variables["runtimes_to_build"], "compiler-rt")
self.assertEqual(env_variables["runtimes_check_targets"], "")
self.assertEqual(env_variables["runtimes_check_targets_needs_reconfig"], "")
>From ee123e79be14651e9bbdc3d789fd5e678dee08e3 Mon Sep 17 00:00:00 2001
From: Charles Zablit <c_zablit at apple.com>
Date: Wed, 20 May 2026 10:16:27 +0100
Subject: [PATCH 5/5] [lldb][test] DIAGNOSTIC: capture state for
test_static_init_during_load
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
REVERT BEFORE MERGE.
Adds Windows process-plugin event/process/step/break logging plus
LLDB breakpoint/module/process/target logging during
test_static_init_during_load to a temp file under the test build
artifact dir, dumps `process status` / `target modules list` /
`breakpoint list` / `thread list` after `run`, then dumps the log
file's contents to stderr — so the Windows event / breakpoint /
module trace ends up captured in the per-test stderr stream that
lit surfaces in the JUnit XML and the Failure_*.log file.
Goal is to see, after the assertion that currently fails inside
`ghcr.io/llvm/ci-windows-2022` with "Process should be stopped due
to breakpoint / error: process must be launched":
- what LOAD_DLL events fired (did loadunload_b.dll actually load?)
- whether each breakpoint resolved when its module loaded
- the inferior's stop reason / exit code at the point we assert
- which modules LLDB ended up tracking
Also bundled in (also REVERT BEFORE MERGE) two CI-side overrides so
this branch's Windows job runs in minutes instead of hours and
without needing the rebuilt ci-windows-2022 image to be published:
- In .github/workflows/premerge.yaml's Compute Projects step,
force windows-projects=clang;lld;lldb;llvm and
windows-check-targets=check-lldb, skipping all the unrelated
project test suites for this diagnostic run.
- Add a `choco install -y make --version 4.4.1` step before
"Build and Test", since the published ci-windows-2022 image
still doesn't have make and we need it for the lldb-api test
inferiors to build.
---
.github/workflows/premerge.yaml | 15 +++++---
.../load_unload/TestLoadUnload.py | 35 +++++++++++++++++++
2 files changed, 46 insertions(+), 4 deletions(-)
diff --git a/.github/workflows/premerge.yaml b/.github/workflows/premerge.yaml
index 8026c98328a2c..f03dc4b835112 100644
--- a/.github/workflows/premerge.yaml
+++ b/.github/workflows/premerge.yaml
@@ -157,10 +157,17 @@ jobs:
echo "Building runtimes: ${runtimes_to_build}"
echo "Running runtimes checks targets: ${runtimes_check_targets}"
- echo "windows-projects=${projects_to_build}" >> $GITHUB_OUTPUT
- echo "windows-check-targets=${project_check_targets}" >> $GITHUB_OUTPUT
- echo "windows-runtimes=${runtimes_to_build}" >> $GITHUB_OUTPUT
- echo "windows-runtimes-check-targets=${runtimes_check_targets}" >> $GITHUB_OUTPUT
+ # DIAGNOSTIC (revert before merge): force lldb-only check-lldb on
+ # Windows so this branch's CI runs the failing test fast, without
+ # waiting on the rest of llvm/clang/lld/etc.
+ echo "windows-projects=clang;lld;lldb;llvm" >> $GITHUB_OUTPUT
+ echo "windows-check-targets=check-lldb" >> $GITHUB_OUTPUT
+ echo "windows-runtimes=" >> $GITHUB_OUTPUT
+ echo "windows-runtimes-check-targets=" >> $GITHUB_OUTPUT
+ - name: Install make (DIAGNOSTIC, will move to ci-windows Dockerfile)
+ if: ${{ steps.vars.outputs.windows-projects != '' }}
+ shell: cmd
+ run: choco install -y --no-progress make --version 4.4.1
- name: Build and Test
timeout-minutes: 180
if: ${{ steps.vars.outputs.windows-projects != '' }}
diff --git a/lldb/test/API/functionalities/load_unload/TestLoadUnload.py b/lldb/test/API/functionalities/load_unload/TestLoadUnload.py
index 97ca9f84b203d..0daaf17950c00 100644
--- a/lldb/test/API/functionalities/load_unload/TestLoadUnload.py
+++ b/lldb/test/API/functionalities/load_unload/TestLoadUnload.py
@@ -413,6 +413,17 @@ def run_step_over_load(self):
@expectedFailureAll(oslist=["windows"], archs=["aarch64"])
def test_static_init_during_load(self):
"""Test that we can set breakpoints correctly in static initializers"""
+ # DIAGNOSTIC (revert before merge): triage the container-only failure
+ # of this test by capturing Windows debug events, breakpoint
+ # resolution, and module loads to a temp file, then dumping its
+ # contents to stderr below so they end up in the JUnit XML's
+ # captured stderr / Failure_*.log.
+ diag_log = self.getBuildArtifact("diag.log")
+ self.runCmd('log enable -f "%s" windows event process step break' % diag_log)
+ self.runCmd(
+ 'log enable -a -f "%s" lldb breakpoint module process target' % diag_log
+ )
+
self.copy_shlibs_to_remote()
exe = self.getBuildArtifact("a.out")
@@ -430,6 +441,30 @@ def test_static_init_during_load(self):
self.runCmd("run", RUN_SUCCEEDED)
+ # DIAGNOSTIC: snapshot state after run, before the assertion that
+ # currently fails inside ghcr.io/llvm/ci-windows-2022 with
+ # "process must be launched".
+ self.runCmd("process status")
+ self.runCmd("target modules list")
+ self.runCmd("breakpoint list")
+ self.runCmd("thread list")
+
+ # DIAGNOSTIC: disable the log channels (forces a flush of the file
+ # buffer) and dump the log file's contents to stderr, so the Windows
+ # event / breakpoint / module trace ends up captured by the test
+ # framework and visible in the JUnit XML and per-test failure log.
+ self.runCmd("log disable windows event process step break")
+ self.runCmd("log disable lldb breakpoint module process target")
+ import sys
+
+ try:
+ with open(diag_log, "r", errors="replace") as f:
+ sys.stderr.write("\n========== diag.log begin ==========\n")
+ sys.stderr.write(f.read())
+ sys.stderr.write("\n========== diag.log end ==========\n")
+ except OSError as e:
+ sys.stderr.write("could not read %s: %s\n" % (diag_log, e))
+
self.expect(
"thread list",
STOPPED_DUE_TO_BREAKPOINT,
More information about the lldb-commits
mailing list