[RFC PATCH v2 05/11] objtool/klp: Support reduced and split DWARF
From: Longjun Luo
Date: Wed Nov 04 2026 - 08:37:48 EST
Read split CUs through libdw and save matching DWO files in a DWP before
another build overwrites them. Keep the DWP beside each saved object.
Reading saved DWP files requires libdw 0.191 or newer.
Read fixed-width array bounds as unsigned so reduced type information
does not make nonempty arrays appear empty.
Full LTO with --plugin-opt=dwo_dir requires
-mllvm -split-dwarf-cross-cu-references=true to preserve separate CUs
and their source paths. Kbuild does not enable this DWO output mode.
Validated the available objtool tests with GCC 16.2.1 and Clang
22.1.8. No failures.
Assisted-by: LLM
Signed-off-by: Longjun Luo <luolongjuna@xxxxxxxxx>
---
scripts/livepatch/klp-build | 18 +++++++++++++++
tools/objtool/klp-dwarf.c | 45 ++++++++++++++++++++++++++++---------
2 files changed, 53 insertions(+), 10 deletions(-)
diff --git a/scripts/livepatch/klp-build b/scripts/livepatch/klp-build
index e373928123e8..95bea5b2057e 100755
--- a/scripts/livepatch/klp-build
+++ b/scripts/livepatch/klp-build
@@ -34,6 +34,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FIX_PATCH_LINES="$SCRIPT_DIR/fix-patch-lines"
OBJTOOL="$PWD/tools/objtool/objtool"
+DWP="${DWP:-llvm-dwp}"
CONFIG="$PWD/.config"
TMP_DIR="$PWD/klp-tmp"
@@ -265,6 +266,11 @@ validate_config() {
[[ -v CONFIG_KLP_BUILD ]] || \
die "CONFIG_KLP_BUILD not enabled"
+ if [[ -v CONFIG_DEBUG_INFO_SPLIT ]]; then
+ command -v "$DWP" &>/dev/null ||
+ die "split DWARF requires llvm-dwp (or set DWP)"
+ fi
+
[[ -v CONFIG_GCC_PLUGIN_LATENT_ENTROPY ]] && \
die "kernel option 'CONFIG_GCC_PLUGIN_LATENT_ENTROPY' not supported"
@@ -588,6 +594,15 @@ find_objects() {
-printf '%P\n'
}
+# Save the matching DWOs before the next build overwrites them.
+save_split_dwarf() {
+ local file="$1"
+ local dest="$2"
+
+ [[ -v CONFIG_DEBUG_INFO_SPLIT ]] || return 0
+ "$DWP" -e "$file" -o "$dest.dwp" || die "cannot save split DWARF for $file"
+}
+
# Copy all .o archives to $ORIG_DIR
copy_orig_objects() {
local files=()
@@ -608,6 +623,7 @@ copy_orig_objects() {
mkdir -p "$orig_dir"
cp -f "$file" "$orig_dir"
+ save_split_dwarf "$file" "$orig_file"
done
xtrace_restore
@@ -647,6 +663,7 @@ copy_patched_objects() {
mkdir -p "$patched_dir"
cp -f "$file" "$patched_dir"
+ save_split_dwarf "$file" "$patched_file"
found=1
done
xtrace_restore
@@ -680,6 +697,7 @@ generate_checksums() {
mkdir -p "$(dirname "$dest")"
cp -f "$file" "$dest"
+ [[ -f "$file.dwp" ]] && cp -f "$file.dwp" "$dest.dwp"
"$OBJTOOL" klp checksum "$dest"
done
diff --git a/tools/objtool/klp-dwarf.c b/tools/objtool/klp-dwarf.c
index 99b098ee7904..98d00ea19b7c 100644
--- a/tools/objtool/klp-dwarf.c
+++ b/tools/objtool/klp-dwarf.c
@@ -138,6 +138,10 @@ static void normalize_source(char *path)
*write = '\0';
}
+/*
+ * Full LTO with --plugin-opt=dwo_dir can merge CUs and lose TU source
+ * paths. Kbuild does not enable this output mode.
+ */
static int read_unit_source(struct identity_cu *cu, Dwarf_Die *die)
{
Dwarf_Attribute attr;
@@ -146,12 +150,12 @@ static int read_unit_source(struct identity_cu *cu, Dwarf_Die *die)
if (dwarf_tag(die) != DW_TAG_compile_unit)
return 0;
- if (dwarf_attr(die, DW_AT_name, &attr)) {
+ if (dwarf_attr_integrate(die, DW_AT_name, &attr)) {
name = dwarf_formstring(&attr);
if (!name)
return die_error(die, "invalid compilation unit name");
}
- if (dwarf_attr(die, DW_AT_comp_dir, &attr)) {
+ if (dwarf_attr_integrate(die, DW_AT_comp_dir, &attr)) {
dir = dwarf_formstring(&attr);
if (!dir)
return die_error(die, "invalid compilation directory");
@@ -472,6 +476,23 @@ static int add_address(struct identity_reader *reader, struct identity_die *entr
return 0;
}
+/*
+ * Reduced DWARF can omit the element size of idt_table. Reading its
+ * DW_FORM_data1 upper bound (255) as -1 would misidentify it as empty.
+ */
+static int read_array_bound(Dwarf_Attribute *attr, Dwarf_Sword *bound)
+{
+ Dwarf_Word value;
+
+ if (dwarf_whatform(attr) == DW_FORM_sdata ||
+ dwarf_whatform(attr) == DW_FORM_implicit_const)
+ return dwarf_formsdata(attr, bound);
+ if (dwarf_formudata(attr, &value) || value > INT64_MAX)
+ return -1;
+ *bound = value;
+ return 0;
+}
+
/*
* The empty features[] in virtio_input.c can share an address with the
* nonempty features[] in trans_virtio.c. Exclude the empty object's DIE
@@ -498,10 +519,10 @@ static bool is_empty_type(Dwarf_Die *type)
return true;
lower = 0;
if (dwarf_attr(&range, DW_AT_lower_bound, &attr) &&
- dwarf_formsdata(&attr, &lower))
+ read_array_bound(&attr, &lower))
continue;
if (dwarf_attr(&range, DW_AT_upper_bound, &attr) &&
- !dwarf_formsdata(&attr, &upper) && upper < lower)
+ !read_array_bound(&attr, &upper) && upper < lower)
return true;
} while (!dwarf_siblingof(&range, &range));
return false;
@@ -752,7 +773,7 @@ static int build_identities(struct identity_reader *reader)
static const Dwfl_Callbacks callbacks = { .section_address = dwfl_offline_section_address };
Dwfl *dwfl = dwfl_begin(&callbacks);
Dwarf_CU *cu = NULL, *next;
- Dwarf_Die die;
+ Dwarf_Die die, split;
Dwarf *dwarf;
struct identity_die *entry;
unsigned int bucket;
@@ -767,11 +788,15 @@ static int build_identities(struct identity_reader *reader)
dwarf = dwfl_module_getdwarf(reader->module, &reader->bias);
if (!dwarf || check_relocations(dwarf))
goto out;
- while (!(ret = dwarf_get_units(dwarf, cu, &next, NULL, &unit_type, &die, NULL))) {
- if (unit_type == DW_UT_skeleton ||
- unit_type == DW_UT_split_compile || unit_type == DW_UT_split_type) {
- ret = die_error(&die, "split DWARF is not supported yet");
- goto out;
+ while (!(ret = dwarf_get_units(dwarf, cu, &next, NULL, &unit_type, &die, &split))) {
+ if (unit_type == DW_UT_skeleton) {
+ /* libdw checks the DWO ID and resolves addresses through the skeleton. */
+ if (!split.addr) {
+ ret = die_error(&die,
+ "missing or mismatched split DWARF file (saved .dwp files require libdw >= 0.191)");
+ goto out;
+ }
+ die = split;
}
if (!is_unit(dwarf_tag(&die))) {
ret = die_error(&die, "invalid unit root");
--
2.55.0