Hi Motomasa,
Sorry for the late reply. Since I'm not very familiar with live-patch
internals, I just have a few questions on your patch, please see
below.
On Fri, Nov 21, 2025 at 6:56 PM Motomasa Suzuki
<suzuki.motomasa(a)fujitsu.com> wrote:
This commit enhances the 'sys' command to show if a kernel livepatch is
currently in a transition phase, directly within the KERNEL output line.
Currently, diagnosing system state during or immediately after livepatch
operations can be ambiguous. While 'livepatch' is indicated by
'[LIVEPATCH]', there's no direct indicator within 'crash' itself to
show
if a livepatch is actively applying, reverting, or in some other
transient state. This lack of immediate visibility can complicate crash
analysis, as the system might be in an inconsistent state due to an
ongoing patch application/reversion.
This change introduces a new '[TRANSITION]' flag which appears next to
'[LIVEPATCH]' and '[TAINTED]' in the 'sys' command output. This
flag is
set if the livepatch subsystem indicates an in-progress transition
(e.g., as exposed via '/sys/kernel/livepatch/<patch_name>/transition').
Example 'sys' output with this change:
KERNEL: /usr/lib/debug/lib/modules/<version_name>/vmlinux [LIVEPATCH]
[TRANSITION] [TAINTED]
The TRANSITION mark indicating a live patch is activating or
deactivating or either? I mean, when people see the TRANSITION flag,
do they know the exact operation that is on the system, like it is
patching, or unpatching, or it doesn't distinguish the operation at
all?
This enhancement provides critical, at-a-glance information for
developers and administrators, allowing them to quickly ascertain if
ongoing livepatch activity might be influencing a system's behavior
during crash investigations. It directly leverages existing kernel
livepatch status infrastructure to enrich the crash utility's diagnostic
capabilities.
Signed-off-by: Motomasa Suzuki <suzuki.motomasa(a)fujitsu.com>
---
defs.h | 1 +
kernel.c | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++---
2 files changed, 51 insertions(+), 3 deletions(-)
diff --git a/defs.h b/defs.h
index ab4aee8..b459d4d 100644
--- a/defs.h
+++ b/defs.h
@@ -2280,6 +2280,7 @@ struct offset_table { /* stash of commonly-used
offsets */
long bpf_ringbuf_map_rb;
long bpf_ringbuf_consumer_pos;
long bpf_ringbuf_nr_pages;
+ long klp_patch_list;
};
struct size_table { /* stash of commonly-used sizes */
diff --git a/kernel.c b/kernel.c
index 13f3374..1ca18e4 100644
--- a/kernel.c
+++ b/kernel.c
@@ -461,7 +461,10 @@ kernel_init()
error(WARNING,
"list_head.next offset: %ld: list command may fail\n",
OFFSET(list_head_next));
-
+ if (STRUCT_EXISTS("klp_patch")) {
+ if (MEMBER_EXISTS("klp_patch", "list"))
+ MEMBER_OFFSET_INIT(klp_patch_list, "klp_patch",
"list");
+ }
MEMBER_OFFSET_INIT(hlist_node_next, "hlist_node", "next");
MEMBER_OFFSET_INIT(hlist_node_pprev, "hlist_node",
"pprev");
STRUCT_SIZE_INIT(hlist_head, "hlist_head");
@@ -5681,6 +5684,48 @@ is_livepatch(void)
return FALSE;
}
+#define KLP_PATCH_ITER_LIMIT 1024
+
+static int
+is_livepatch_transition(void)
+{
+ struct kernel_list_head head;
+ struct kernel_list_head node;
+ ulong transition_patch;
+ ulong list_addr;
+ ulong current;
+ ulong patch_addr;
+ int loops;
+
+ if (!try_get_symbol_data("klp_transition_patch", sizeof(ulong),
+ &transition_patch) ||
!transition_patch)
+ return FALSE;
+
+ if (!kernel_symbol_exists("klp_patches") ||
!VALID_MEMBER(klp_patch_list))
+ return FALSE;
+
+ list_addr = symbol_value("klp_patches");
+ if (!readmem(list_addr, KVADDR, &head, sizeof(head),
"klp_patches",
+ RETURN_ON_ERROR | QUIET))
+ return FALSE;
+
+ for (current = (ulong)head.next, loops = 0;
+ current && current != list_addr && loops <
KLP_PATCH_ITER_LIMIT;
+ current = (ulong)node.next, loops++) {
+
+ if (!readmem(list_addr, KVADDR, &head, sizeof(head),
+ "klp_patch list entry",
RETURN_ON_ERROR | QUIET))
+ return FALSE;
+
+ patch_addr = current - (ulong)OFFSET(klp_patch_list);
+
+ if (patch_addr == transition_patch)
+ return TRUE;
+ }
Your above code is 1) check if transition_patch is null. 2) check if
transition_patch is within the klp_patches list.
But I see in the kernel source, there are only lists to be added into
the klp_patches list, no one is to be deleted [1]. So my assumption
is, transition_patch will always be found within klp_patches. Because
for all livepatchs, they must go through klp_enable_patch() ->
klp_init_patch() -> list_add_tail(&patch->list, &klp_patches), and no
one to be deleted from klp_patches afterwards, so transition_patch
must be one of those. If my assumption is correct, is step 2)
necessary?
[1]:
https://elixir.bootlin.com/linux/v6.2.14/A/ident/klp_patches
And also for the list iteration in step 2), I suggest using the
do_list() function of crash utility, you can refer to
memory.c:dump_vmap_area() to get an example of do_list() usage.
Thanks,
Tao Liu
+
+ return FALSE;
+}
+
/*
* Display system stats at init-time or for the sys command.
*/
@@ -5724,17 +5769,19 @@ display_sys_stats(void)
}
} else {
if (pc->system_map) {
- fprintf(fp, " SYSTEM MAP: %s%s%s\n",
pc->system_map,
+ fprintf(fp, " SYSTEM MAP: %s%s%s%s\n",
pc->system_map,
is_livepatch() ? " [LIVEPATCH]" :
"",
+ is_livepatch_transition() ? " [TRANSITION]" :
"",
is_kernel_tainted() ? " [TAINTED]" :
"");
fprintf(fp, "DEBUG KERNEL: %s %s\n",
pc->namelist_orig ?
pc->namelist_orig : pc->namelist,
debug_kernel_version(pc->namelist));
} else
- fprintf(fp, " KERNEL: %s%s%s\n",
pc->namelist_orig ?
+ fprintf(fp, " KERNEL: %s%s%s%s\n",
pc->namelist_orig ?
pc->namelist_orig : pc->namelist,
is_livepatch() ? " [LIVEPATCH]" :
"",
+ is_livepatch_transition() ? " [TRANSITION]" :
"",
is_kernel_tainted() ? " [TAINTED]" :
"");
}
--
2.47.3
--
Crash-utility mailing list -- devel(a)lists.crash-utility.osci.io
To unsubscribe send an email to devel-leave(a)lists.crash-utility.osci.io
https://${domain_name}/admin/lists/devel.lists.crash-utility.osci.io/
Contribution Guidelines:
https://github.com/crash-utility/crash/wiki