From 1755551d34cc3ed08e1c63864153511e5c31ff50 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Google)" Date: Mon, 9 Nov 2026 20:15:58 +0000 Subject: [PATCH 1/5] create-diff-object: fix x86_64 ftrace callsite NOP replacement offset When a kernel is built with function prefix padding (such as CONFIG_CALL_PADDING / -fpatchable-function-entry=16,16 or CONFIG_FINEIBT), the function entry point and its __fentry__ NOP are located at insn_offset (rela->offset - 1) rather than at offset 0 of the function section. kpatch_create_ftrace_callsite_sections() already computes insn_offset from rela->offset - 1, but when converting an R_X86_64_NONE relocation into a call to __fentry__, it pointed insn at the start of the section buffer (newdata) instead of newdata + insn_offset, failing with: unexpected instruction at the start of the function Offset insn by insn_offset and drop the redundant reassignment to sym->sec->data->d_buf. Fixes: b958ed601c3f ("create-diff-object: Add ppc64le mcount support") Signed-off-by: Liam R. Howlett (Google) --- kpatch-build/create-diff-object.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index 3d01e71b..bf4cb728 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -4169,14 +4169,13 @@ static void kpatch_create_ftrace_callsite_sections(struct kpatch_elf *kelf) ERROR("malloc"); memcpy(newdata, sym->sec->data->d_buf, sym->sec->data->d_size); sym->sec->data->d_buf = newdata; - insn = newdata; + insn = newdata + insn_offset; /* * Replace the NOP with a call to fentry. The fentry * rela symbol is already there, just need to change * the relocation type accordingly. */ - insn = sym->sec->data->d_buf; if (insn[0] != 0xf) ERROR("%s: unexpected instruction at the start of the function", sym->name); insn[0] = 0xe8; From 115c1d3a5c1d677596fedeba842c6c1c047ce232 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Google)" Date: Mon, 9 Nov 2026 20:15:58 +0000 Subject: [PATCH 2/5] create-diff-object: ignore Clang .compoundliteral symbols in static local checks Clang emits local STT_OBJECT symbols named .compoundliteral (and .compoundliteral. when multiple compound literals exist in a translation unit) for C99 compound literals. since .compoundliteral. contains a dot and does not start with .L, kpatch_is_normal_static_local() identifies them as compiler generated objects as function static local variables and aborts in find_local_sym(). Don't include .compoundliteral* symbols in kpatch_is_normal_static_local(). Signed-off-by: Liam R. Howlett (Google) --- kpatch-build/create-diff-object.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index bf4cb728..13bae910 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -1399,7 +1399,8 @@ static bool kpatch_is_normal_static_local(struct symbol *sym) if (sym->type != STT_OBJECT || sym->bind != STB_LOCAL) return false; - if (!strncmp(sym->name, ".L", 2)) + if (!strncmp(sym->name, ".L", 2) || + !strncmp(sym->name, ".compoundliteral", 16)) return false; if (!strchr(sym->name, '.')) From 074c93669edc395cbdcdf7b803d70e48df158a3e Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Google)" Date: Mon, 9 Nov 2026 20:15:58 +0000 Subject: [PATCH 3/5] lookup: ignore __mod_device_table__ and discarded symbols in locals_match() MODULE_DEVICE_TABLE() generates local __mod_device_table__* symbols in per-object files that are discarded during linking vmlinux. Add __mod_device_table__* to maybe_discarded_sym() after __export_symbol_* and __gendwarfksyms_*, and skip the symbols in locals_match() by checking maybe_discarded_sym(). Signed-off-by: Liam R. Howlett (Google) --- kpatch-build/lookup.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/kpatch-build/lookup.c b/kpatch-build/lookup.c index ca84bf2a..8d25aeaa 100644 --- a/kpatch-build/lookup.c +++ b/kpatch-build/lookup.c @@ -89,6 +89,7 @@ static bool maybe_discarded_sym(const char *name) !strncmp(name, ".L__const", 9) || !strncmp(name, "__gendwarfksyms_", 16) || !strncmp(name, "__export_symbol_", 16) || + !strncmp(name, "__mod_device_table__", 20) || is_ubsan_sec(name)) return true; @@ -110,6 +111,8 @@ static bool locals_match(struct lookup_table *table, int idx, continue; if (table_sym->type != STT_FUNC && table_sym->type != STT_OBJECT) continue; + if (maybe_discarded_sym(table_sym->name)) + continue; found = 0; sym = file_sym; @@ -118,6 +121,8 @@ static bool locals_match(struct lookup_table *table, int idx, break; if (sym->bind != STB_LOCAL) continue; + if (maybe_discarded_sym(sym->name)) + continue; if (sym->type == table_sym->type && !strcmp(sym->name, table_sym->name)) { From 9ae46adc06e1ade3066c108ca44a5c95a281ff0f Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Google)" Date: Mon, 9 Nov 2026 20:17:49 +0000 Subject: [PATCH 4/5] kpatch-build: match mangled local symbols in lookup and record resolved name When building livepatches against Clang-compiled kernels compiler-generated local symbols such as .L.str. and .__UNIQUE_ID_ddebug can receive different numerical suffixes between the original vmlinux symbol table and the rebuilt object (for example, when rebuilding the objects for kpatch-build). Relocate the kpatch_mangled_strcmp() to kpatch-elf.c so that it can be used in locals_match() for correlating the mangled numeric suffixes and the dynamic debug ids from LLVM. Only fall back to the different numeric suffixes in lookup_local_symbol() if the exact match is not found within the STT_FILE. Finally, store the resolved symbol names in a struct lookup_result::name, allowing the lookup_local_symbol() to match the targets symbol name in .kpatch.strings and .symbols. Signed-off-by: Liam R. Howlett (Google) --- kpatch-build/create-diff-object.c | 93 +------------------------------ kpatch-build/kpatch-elf.c | 93 +++++++++++++++++++++++++++++++ kpatch-build/kpatch-elf.h | 1 + kpatch-build/lookup.c | 68 +++++++++++++++++++++- kpatch-build/lookup.h | 1 + 5 files changed, 161 insertions(+), 95 deletions(-) diff --git a/kpatch-build/create-diff-object.c b/kpatch-build/create-diff-object.c index 13bae910..9a346e47 100644 --- a/kpatch-build/create-diff-object.c +++ b/kpatch-build/create-diff-object.c @@ -479,97 +479,6 @@ static bool is_special_static(struct symbol *sym) return false; } -static bool has_digit_tail(char *tail) -{ - if (*tail != '.') - return false; - - while (isdigit(*++tail)) - ; - - if (!*tail) - return true; - - return false; -} - -/* - * Hack for __UNIQUE_ID(). The following should match: - * - * __UNIQUE_ID_ddebug1131.186 - * __UNIQUE_ID_ddebug1132.187 - */ -static int __kpatch_unique_id_strcmp(char *s1, char *s2) -{ - /* match '__UNIQUE_ID_ddebug' */ - while (*s1 == *s2) { - if (!*s1) - return 0; - s1++; - s2++; - } - - /* skip digits before '.' or EOL */ - while (isdigit(*s1)) - s1++; - while (isdigit(*s2)) - s2++; - - if ((!*s1 || has_digit_tail(s1)) && - (!*s2 || has_digit_tail(s2))) - return 0; - - return 1; -} - -/* - * This is like strcmp, but for gcc-mangled symbols. It skips the comparison - * of any substring which consists of '.' followed by any number of digits. - */ -static int kpatch_mangled_strcmp(char *s1, char *s2) -{ - /* - * ELF string sections aren't mangled, though they look that way. Just - * compare them normally. - */ - if (strstr(s1, ".str1.")) - return strcmp(s1, s2); - - if (!strncmp(s1, "__UNIQUE_ID_", 12)) - return __kpatch_unique_id_strcmp(s1, s2); - - /* - * With LLVM, dynamic debug unique id looks like - * .__UNIQUE_ID_ddebug. - * - * Use __kpatch_unique_id_strcmp to compare them. - */ - if (strstr(s1, "__UNIQUE_ID_ddebug")) - return __kpatch_unique_id_strcmp(s1, s2); - - while (*s1 == *s2) { - if (!*s1) - return 0; - if (*s1 == '.' && isdigit(s1[1])) { - if (!isdigit(s2[1])) - return 1; - while (isdigit(*++s1)) - ; - while (isdigit(*++s2)) - ; - } else { - s1++; - s2++; - } - } - - if ((!*s1 && has_digit_tail(s2)) || - (!*s2 && has_digit_tail(s1))) - return 0; - - return 1; -} - static bool rela_equal(struct rela *rela1, struct rela *rela2) { struct rela *rela_toc1, *rela_toc2; @@ -3891,7 +3800,7 @@ static void kpatch_create_intermediate_sections(struct kpatch_elf *kelf, ALLOC_LINK(rela2, &ksym_sec->rela->relas); rela2->sym = strsym; rela2->type = absolute_rela_type(kelf); - rela2->addend = offset_of_string(&kelf->strings, rela->sym->name); + rela2->addend = offset_of_string(&kelf->strings, symbol.name); rela2->offset = (unsigned int)(index * sizeof(*ksyms) + \ offsetof(struct kpatch_symbol, name)); diff --git a/kpatch-build/kpatch-elf.c b/kpatch-build/kpatch-elf.c index d418ecda..ca30be73 100755 --- a/kpatch-build/kpatch-elf.c +++ b/kpatch-build/kpatch-elf.c @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -769,6 +770,98 @@ bool is_ubsan_sec(const char *name) { return false; } +static bool has_digit_tail(char *tail) +{ + if (*tail != '.') + return false; + + while (isdigit((unsigned char)*++tail)) + ; + + if (!*tail) + return true; + + return false; +} + +/* + * Hack for __UNIQUE_ID(). The following should match: + * + * __UNIQUE_ID_ddebug1131.186 + * __UNIQUE_ID_ddebug1132.187 + */ +static int __kpatch_unique_id_strcmp(char *s1, char *s2) +{ + /* match '__UNIQUE_ID_ddebug' */ + while (*s1 == *s2) { + if (!*s1) + return 0; + s1++; + s2++; + } + + /* skip digits before '.' or EOL */ + while (isdigit((unsigned char)*s1)) + s1++; + while (isdigit((unsigned char)*s2)) + s2++; + + if ((!*s1 || has_digit_tail(s1)) && + (!*s2 || has_digit_tail(s2))) + return 0; + + return 1; +} + +/* + * This is like strcmp, but for gcc/clang-mangled symbols. It skips the + * comparison of any substring which consists of '.' followed by any number of + * digits. + */ +int kpatch_mangled_strcmp(char *s1, char *s2) +{ + /* + * ELF string sections aren't mangled, though they look that way. Just + * compare them normally. + */ + if (strstr(s1, ".str1.")) + return strcmp(s1, s2); + + if (!strncmp(s1, "__UNIQUE_ID_", 12)) + return __kpatch_unique_id_strcmp(s1, s2); + + /* + * With LLVM, dynamic debug unique id looks like + * .__UNIQUE_ID_ddebug. + * + * Use __kpatch_unique_id_strcmp to compare them. + */ + if (strstr(s1, "__UNIQUE_ID_ddebug")) + return __kpatch_unique_id_strcmp(s1, s2); + + while (*s1 == *s2) { + if (!*s1) + return 0; + if (*s1 == '.' && isdigit((unsigned char)s1[1])) { + if (!isdigit((unsigned char)s2[1])) + return 1; + while (isdigit((unsigned char)*++s1)) + ; + while (isdigit((unsigned char)*++s2)) + ; + } else { + s1++; + s2++; + } + } + + if ((!*s1 && has_digit_tail(s2)) || + (!*s2 && has_digit_tail(s1))) + return 0; + + return 1; +} + void print_strtab(char *buf, size_t size) { size_t i; diff --git a/kpatch-build/kpatch-elf.h b/kpatch-build/kpatch-elf.h index 92e3bb68..7e796225 100644 --- a/kpatch-build/kpatch-elf.h +++ b/kpatch-build/kpatch-elf.h @@ -186,6 +186,7 @@ bool is_file_sym(struct symbol *sym); bool is_local_func_sym(struct symbol *sym); bool is_local_sym(struct symbol *sym); bool is_ubsan_sec(const char *name); +int kpatch_mangled_strcmp(char *s1, char *s2); void print_strtab(char *buf, size_t size); void kpatch_create_shstrtab(struct kpatch_elf *kelf); diff --git a/kpatch-build/lookup.c b/kpatch-build/lookup.c index 8d25aeaa..b8377c21 100644 --- a/kpatch-build/lookup.c +++ b/kpatch-build/lookup.c @@ -96,6 +96,32 @@ static bool maybe_discarded_sym(const char *name) return false; } +/* + * Clang deduplicates .L.str literals differently between out-of-tree vmlinux + * builds and in-tree kpatch-build rebuilds. Match the local symbol names when + * there are different numerical suffixes. + */ +static bool mangled_local_sym_equal(const char *s1, const char *s2) +{ + while (*s1 == *s2) { + if (!*s1) + return true; + if (*s1 == '.' && isdigit((unsigned char)s1[1])) { + if (!isdigit((unsigned char)s2[1])) + return false; + while (isdigit((unsigned char)*++s1)) + ; + while (isdigit((unsigned char)*++s2)) + ; + } else { + s1++; + s2++; + } + } + + return false; +} + static bool locals_match(struct lookup_table *table, int idx, struct symbol *file_sym, struct list_head *sym_list) { @@ -125,7 +151,7 @@ static bool locals_match(struct lookup_table *table, int idx, continue; if (sym->type == table_sym->type && - !strcmp(sym->name, table_sym->name)) { + !kpatch_mangled_strcmp(sym->name, table_sym->name)) { found = 1; break; } @@ -161,7 +187,7 @@ static bool locals_match(struct lookup_table *table, int idx, continue; if (sym->type == table_sym->type && - !strcmp(sym->name, table_sym->name)) { + !kpatch_mangled_strcmp(sym->name, table_sym->name)) { found = 1; break; } @@ -509,6 +535,7 @@ static bool lookup_local_symbol(struct lookup_table *table, ERROR("duplicate local symbol found for %s", lookup_sym->name); + result->name = sym->name; result->objname = table->objname; result->addr = sym->addr; result->size = sym->size; @@ -517,6 +544,39 @@ static bool lookup_local_symbol(struct lookup_table *table, found = true; } } + + if (!found) { + in_file = 0; + for_each_obj_symbol(i, sym, table) { + if (lookup_sym->lookup_table_file_sym == sym) { + in_file = 1; + continue; + } + + if (!in_file) + continue; + + if (sym->type == STT_FILE) + break; + + if (sym->bind == STB_LOCAL && + sym->type == lookup_sym->type && + mangled_local_sym_equal(sym->name, lookup_sym->name)) { + if (found) + ERROR("duplicate local symbol found for %s", + lookup_sym->name); + + result->name = sym->name; + result->objname = table->objname; + result->addr = sym->addr; + result->size = sym->size; + result->global = false; + result->exported = false; + found = true; + } + } + } + if (!found) return false; @@ -528,7 +588,7 @@ static bool lookup_local_symbol(struct lookup_table *table, */ for_each_obj_symbol(i, sym, table) { if (sym->bind == STB_LOCAL && - !strcmp(sym->name, lookup_sym->name) && + !strcmp(sym->name, result->name) && sym->addr <= result->addr) sympos++; } @@ -554,6 +614,7 @@ static bool lookup_exported_symbol(struct lookup_table *table, char *name, if (result->objname) ERROR("duplicate exported symbol found for %s", name); + result->name = sym->name; result->objname = sym->objname; result->addr = 0; /* determined at runtime */ result->size = 0; /* not used for exported symbols */ @@ -585,6 +646,7 @@ static bool lookup_global_symbol(struct lookup_table *table, char *name, if (result->objname) ERROR("duplicate global symbol found for %s", name); + result->name = sym->name; result->objname = table->objname; result->addr = sym->addr; result->size = sym->size; diff --git a/kpatch-build/lookup.h b/kpatch-build/lookup.h index e1277f15..0413c1eb 100644 --- a/kpatch-build/lookup.h +++ b/kpatch-build/lookup.h @@ -7,6 +7,7 @@ struct lookup_table; struct lookup_result { + char *name; char *objname; unsigned long addr; unsigned long size; From 74f4ee0b915da7328f1360b4a95b68e48a8d2a60 Mon Sep 17 00:00:00 2001 From: "Liam R. Howlett (Google)" Date: Mon, 9 Nov 2026 20:17:49 +0000 Subject: [PATCH 5/5] kpatch-build: pass LLVM=1 for LLVM kernel builds and KCFLAGS Pass through LLVM setting if the correct flags are set (CONFIG_CC_IS_CLANG, CONFIG_LD_IS_LLD, and CONFIG_AS_IS_LLVM), and the llvm-objcopy command is accessible. Also pass any KCFLAGS from the environment when kpatch-build exports KCFLAGS for object compilation and the final module build. Signed-off-by: Liam R. Howlett (Google) --- kpatch-build/kpatch-build | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/kpatch-build/kpatch-build b/kpatch-build/kpatch-build index bbd1eee4..bdb730f0 100755 --- a/kpatch-build/kpatch-build +++ b/kpatch-build/kpatch-build @@ -55,6 +55,7 @@ SKIPCLEANUP=0 SKIPCOMPILERCHECK=0 ARCH_KCFLAGS="" DEBUG_KCFLAGS="" +USER_KCFLAGS="${KCFLAGS:-}" declare -a PATCH_LIST APPLIED_PATCHES=0 OOT_MODULE= @@ -1343,7 +1344,7 @@ if [[ "$ARCH" = "loongarch64" ]]; then fi export KCFLAGS="-I$DATADIR/patch -ffunction-sections -fdata-sections \ - $ARCH_KCFLAGS $DEBUG_KCFLAGS" + $ARCH_KCFLAGS $DEBUG_KCFLAGS${USER_KCFLAGS:+ $USER_KCFLAGS}" echo "Reading special section data" find_special_section_data @@ -1361,6 +1362,9 @@ declare -a MAKEVARS if [[ -n "$CONFIG_CC_IS_CLANG" ]]; then MAKEVARS+=("CC=${KPATCH_CC_PREFIX}${CLANG}") MAKEVARS+=("HOSTCC=clang") + if [[ -n "$CONFIG_LD_IS_LLD" && -n "$CONFIG_AS_IS_LLVM" ]] && command -v llvm-objcopy &>/dev/null; then + MAKEVARS+=("LLVM=${LLVM:-1}") + fi else MAKEVARS+=("CC=${KPATCH_CC_PREFIX}${GCC}") fi @@ -1549,7 +1553,7 @@ do done echo -export KCFLAGS="-I$DATADIR/patch $ARCH_KCFLAGS" +export KCFLAGS="-I$DATADIR/patch $ARCH_KCFLAGS${USER_KCFLAGS:+ $USER_KCFLAGS}" if [[ "$USE_KLP" -eq 0 ]]; then export KCPPFLAGS="-D__KPATCH_MODULE__" fi