From cdf5c99a423d80c4e7368ec8b7c586358764afc0 Mon Sep 17 00:00:00 2001 From: Tao Liu Date: Tue, 11 Aug 2026 15:11:23 +1200 Subject: [PATCH] Rebase to upstream crash-9.0.2 resolves: RHEL-235439 Signed-off-by: Tao Liu --- 0001-crash-9.0.1-crash-9.0.2.patch | 99 ++++ ...0.3-development-phase-with-version-9.patch | 21 + ...roduce-riscv64_VTOP-and-riscv64_PTOV.patch | 91 ++++ ...etection-of-LIVEPATCH-on-old-kernels.patch | 39 ++ ...unwinding-with-32k-stacks-in-ppc64le.patch | 103 ++++ ...gister-feeding-for-exception-frames-.patch | 232 ++++++++ ...address-calculation-in-riscv64_vtop_.patch | 32 ++ ...-symname_hash-with-larger-table-and-.patch | 71 +++ ...ing-symbol-filter-in-riscv64_verify_.patch | 76 +++ ...get_xtime-for-kernel-6.13-and-higher.patch | 71 +++ ...ort-for-mod-symtab-with-combined-GPL.patch | 74 +++ 0012-add-folio_order-function.patch | 202 +++++++ 0013-xarray-add-large-folio-support.patch | 232 ++++++++ 0014-add-files-n-command-for-an-inode.patch | 216 ++++++++ ...ix-bt-command-for-noreturn-functions.patch | 178 ++++++ ...mmand-to-use-correct-ORC-register-va.patch | 100 ++++ ...n-to-display-swap-usage-on-Linux-6.1.patch | 38 ++ ...-swap-command-on-Linux-7.1-and-later.patch | 51 ++ ...CPU-registers-reading-from-dump-note.patch | 115 ++++ ...pt_regs-initialization-for-active-ta.patch | 31 ++ ...ort-backtracing-across-exception-bou.patch | 63 +++ ...stack-frame-loop-bounds-for-exceptio.patch | 41 ++ ...dummy-eframe_search-to-avoid-bt-e-se.patch | 52 ++ ...rint-exception-return-address-as-ERA.patch | 49 ++ ...lve-relocated-exception-vector-addre.patch | 152 ++++++ ...h64-add-initial-ORC-unwinder-support.patch | 515 ++++++++++++++++++ ...nd-dumpfile-active-tasks-from-IRQ-st.patch | 364 +++++++++++++ ...d-replacing-a-valid-RA-with-stack-no.patch | 51 ++ ...t-exception-registers-only-with-bt-f.patch | 43 ++ ...MAP-flag-to-fix-spurious-mem_map-war.patch | 55 ++ ...rbose-output-in-vtop-page-table-walk.patch | 93 ++++ ...kvaddr_ranges-callback-for-kernel-ad.patch | 98 ++++ ...-option-to-ps-command-to-exclude-idl.patch | 184 +++++++ ...-option-to-ps-command-to-display-sch.patch | 175 ++++++ ...unq-g-option-on-Linux-7.2-and-later-.patch | 180 ++++++ ...n-to-display-task_group-name-on-Linu.patch | 52 ++ ...REG_SP-and-ORC_REG_PREV_SP-independe.patch | 70 +++ ...-symval_hash_init-with-O-1-tail-inse.patch | 88 +++ ...ent-out-of-bounds-access-of-note_buf.patch | 74 +++ crash-9.0.0_build.patch | 8 +- crash.spec | 81 ++- lzo_snappy_zstd.patch | 8 +- sources | 2 +- 43 files changed, 4560 insertions(+), 10 deletions(-) create mode 100644 0001-crash-9.0.1-crash-9.0.2.patch create mode 100644 0002-Mark-start-of-9.0.3-development-phase-with-version-9.patch create mode 100644 0003-RISCV64-introduce-riscv64_VTOP-and-riscv64_PTOV.patch create mode 100644 0004-Fix-wrong-detection-of-LIVEPATCH-on-old-kernels.patch create mode 100644 0005-Fix-unwinding-with-32k-stacks-in-ppc64le.patch create mode 100644 0006-arm64-fix-gdb-register-feeding-for-exception-frames-.patch create mode 100644 0007-RISCV64-fix-pmd-address-calculation-in-riscv64_vtop_.patch create mode 100644 0008-symbols-optimize-symname_hash-with-larger-table-and-.patch create mode 100644 0009-RISCV64-add-mapping-symbol-filter-in-riscv64_verify_.patch create mode 100644 0010-Fix-get_xtime-for-kernel-6.13-and-higher.patch create mode 100644 0011-symbols-Add-support-for-mod-symtab-with-combined-GPL.patch create mode 100644 0012-add-folio_order-function.patch create mode 100644 0013-xarray-add-large-folio-support.patch create mode 100644 0014-add-files-n-command-for-an-inode.patch create mode 100644 0015-x86_64-Fix-bt-command-for-noreturn-functions.patch create mode 100644 0016-x86_64-Fix-bt-command-to-use-correct-ORC-register-va.patch create mode 100644 0017-Fix-kmem-i-option-to-display-swap-usage-on-Linux-6.1.patch create mode 100644 0018-Fix-swap-command-on-Linux-7.1-and-later.patch create mode 100644 0019-LoongArch64-Fix-CPU-registers-reading-from-dump-note.patch create mode 100644 0020-LoongArch64-Fix-pt_regs-initialization-for-active-ta.patch create mode 100644 0021-LoongArch64-Support-backtracing-across-exception-bou.patch create mode 100644 0022-LoongArch64-Fix-stack-frame-loop-bounds-for-exceptio.patch create mode 100644 0023-LoongArch64-Add-dummy-eframe_search-to-avoid-bt-e-se.patch create mode 100644 0024-LoongArch64-print-exception-return-address-as-ERA.patch create mode 100644 0025-LoongArch64-resolve-relocated-exception-vector-addre.patch create mode 100644 0026-LoongArch64-add-initial-ORC-unwinder-support.patch create mode 100644 0027-LoongArch64-unwind-dumpfile-active-tasks-from-IRQ-st.patch create mode 100644 0028-LoongArch64-avoid-replacing-a-valid-RA-with-stack-no.patch create mode 100644 0029-LoongArch64-print-exception-registers-only-with-bt-f.patch create mode 100644 0030-riscv64-Set-VMEMMAP-flag-to-fix-spurious-mem_map-war.patch create mode 100644 0031-riscv64-Guard-verbose-output-in-vtop-page-table-walk.patch create mode 100644 0032-riscv64-Add-get_kvaddr_ranges-callback-for-kernel-ad.patch create mode 100644 0033-task-Introduce-I-option-to-ps-command-to-exclude-idl.patch create mode 100644 0034-task-Introduce-Y-option-to-ps-command-to-display-sch.patch create mode 100644 0035-Fix-failure-of-runq-g-option-on-Linux-7.2-and-later-.patch create mode 100644 0036-Fix-runq-g-option-to-display-task_group-name-on-Linu.patch create mode 100644 0037-x86_64-Make-ORC_REG_SP-and-ORC_REG_PREV_SP-independe.patch create mode 100644 0038-symbols-optimize-symval_hash_init-with-O-1-tail-inse.patch create mode 100644 0039-Prevent-out-of-bounds-access-of-note_buf.patch diff --git a/0001-crash-9.0.1-crash-9.0.2.patch b/0001-crash-9.0.1-crash-9.0.2.patch new file mode 100644 index 0000000..e579d10 --- /dev/null +++ b/0001-crash-9.0.1-crash-9.0.2.patch @@ -0,0 +1,99 @@ +From 61fe107ff96a22e7df0029877529b7ce6da36850 Mon Sep 17 00:00:00 2001 +From: Tao Liu +Date: Fri, 24 Apr 2026 15:14:08 +1200 +Subject: [PATCH 01/39] crash-9.0.1 -> crash-9.0.2 + +Signed-off-by: Tao Liu +--- + .rh_rpm_package | 2 +- + README | 18 +++++++++--------- + help.c | 2 +- + 3 files changed, 11 insertions(+), 11 deletions(-) + +diff --git a/.rh_rpm_package b/.rh_rpm_package +index 1ef1fd1e0ed7..3beeadd423d3 100644 +--- a/.rh_rpm_package ++++ b/.rh_rpm_package +@@ -1 +1 @@ +-9.0.1++ ++9.0.2 +diff --git a/README b/README +index 9ef8c75d5485..e2d740a57f8f 100644 +--- a/README ++++ b/README +@@ -80,8 +80,8 @@ + + To build the crash utility: + +- $ tar -xf crash-9.0.1.tar.gz +- $ cd crash-9.0.1 ++ $ tar -xf crash-9.0.2.tar.gz ++ $ cd crash-9.0.2 + $ make + + To cross compile the crash utility for aarch64 on x86_64: +@@ -137,7 +137,7 @@ + If neither /dev/mem or /dev/crash are available, then /proc/kcore will be + be used as the live memory source. If /proc/kcore is also restricted, then + the Red Hat /dev/crash driver may be compiled and installed; its source +- is included in the crash-9.0.1/memory_driver subdirectory. ++ is included in the crash-9.0.2/memory_driver subdirectory. + + If the kernel file is stored in /boot, /, /boot/efi, or in any /usr/src + or /usr/lib/debug/lib/modules subdirectory, then no command line arguments +@@ -148,8 +148,8 @@ + + $ crash + +- crash 9.0.1 +- Copyright (C) 2002-2025 Red Hat, Inc. ++ crash 9.0.2 ++ Copyright (C) 2002-2026 Red Hat, Inc. + Copyright (C) 2004, 2005, 2006, 2010 IBM Corporation + Copyright (C) 1999-2006 Hewlett-Packard Co + Copyright (C) 2005, 2006, 2011, 2012 Fujitsu Limited +@@ -174,7 +174,7 @@ + KERNEL: /boot/vmlinux + DUMPFILE: /dev/mem + CPUS: 1 +- DATE: Tue Jun 10 18:03:09 CST 2025 ++ DATE: Fri Apr 24 10:38:29 CST 2026 + UPTIME: 10 days, 22:55:18 + LOAD AVERAGE: 0.08, 0.03, 0.01 + TASKS: 42 +@@ -216,8 +216,8 @@ + + $ crash vmlinux vmcore + +- crash 9.0.0 +- Copyright (C) 2002-2025 Red Hat, Inc. ++ crash 9.0.2 ++ Copyright (C) 2002-2026 Red Hat, Inc. + Copyright (C) 2004, 2005, 2006, 2010 IBM Corporation + Copyright (C) 1999-2006 Hewlett-Packard Co + Copyright (C) 2005, 2006, 2011, 2012 Fujitsu Limited +@@ -242,7 +242,7 @@ + KERNEL: vmlinux + DUMPFILE: vmcore + CPUS: 4 +- DATE: Tue Jun 10 18:03:09 CST 2025 ++ DATE: Fri Apr 24 10:38:29 CST 2026 + UPTIME: 00:02:40 + LOAD AVERAGE: 2.24, 0.96, 0.37 + TASKS: 70 +diff --git a/help.c b/help.c +index 91d155098993..810075b97f35 100644 +--- a/help.c ++++ b/help.c +@@ -8557,7 +8557,7 @@ display_version(void) + static + char *version_info[] = { + +-"Copyright (C) 2002-2025 Red Hat, Inc.", ++"Copyright (C) 2002-2026 Red Hat, Inc.", + "Copyright (C) 2004, 2005, 2006, 2010 IBM Corporation", + "Copyright (C) 1999-2006 Hewlett-Packard Co", + "Copyright (C) 2005, 2006, 2011, 2012 Fujitsu Limited", +-- +2.54.0 + diff --git a/0002-Mark-start-of-9.0.3-development-phase-with-version-9.patch b/0002-Mark-start-of-9.0.3-development-phase-with-version-9.patch new file mode 100644 index 0000000..f34eb68 --- /dev/null +++ b/0002-Mark-start-of-9.0.3-development-phase-with-version-9.patch @@ -0,0 +1,21 @@ +From 89d36e12d7b753028038dfb93068fdfd0f6f1501 Mon Sep 17 00:00:00 2001 +From: Tao Liu +Date: Fri, 24 Apr 2026 15:15:19 +1200 +Subject: [PATCH 02/39] Mark start of 9.0.3 development phase with version + 9.0.2++ + +Signed-off-by: Tao Liu +--- + .rh_rpm_package | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/.rh_rpm_package b/.rh_rpm_package +index 3beeadd423d3..a3cb14a5a842 100644 +--- a/.rh_rpm_package ++++ b/.rh_rpm_package +@@ -1 +1 @@ +-9.0.2 ++9.0.2++ +-- +2.54.0 + diff --git a/0003-RISCV64-introduce-riscv64_VTOP-and-riscv64_PTOV.patch b/0003-RISCV64-introduce-riscv64_VTOP-and-riscv64_PTOV.patch new file mode 100644 index 0000000..b5ad2c7 --- /dev/null +++ b/0003-RISCV64-introduce-riscv64_VTOP-and-riscv64_PTOV.patch @@ -0,0 +1,91 @@ +From 4e4dc1c89310e4106f7d0da8029084124c5e016d Mon Sep 17 00:00:00 2001 +From: Austin Kim +Date: Mon, 6 Apr 2026 13:53:37 +0900 +Subject: [PATCH 03/39] RISCV64: introduce riscv64_VTOP() and riscv64_PTOV() + +The VTOP and PTOV macros are typically implemented using architecture-specific +functions to improve maintainability and debugging capabilities. + +This patch moves the RISCV64-specific VTOP/PTOV logic from inline macros +in defs.h to dedicated functions in riscv64.c. This refactoring makes the +code easier to understand and provides a better location for adding +future debugging or validation checks. + +No functional changes are introduced by this patch. + +Signed-off-by: Austin Kim +--- + defs.h | 14 ++++---------- + riscv64.c | 24 ++++++++++++++++++++++++ + 2 files changed, 28 insertions(+), 10 deletions(-) + +diff --git a/defs.h b/defs.h +index a6f43725b6b8..54f1725d089a 100644 +--- a/defs.h ++++ b/defs.h +@@ -3803,16 +3803,8 @@ typedef signed int s32; + /* + * Direct memory mapping + */ +-#define PTOV(X) \ +- (((unsigned long)(X)+(machdep->kvbase)) - machdep->machspec->phys_base) +-#define VTOP(X) ({ \ +- ulong _X = X; \ +- (THIS_KERNEL_VERSION >= LINUX(5,13,0) && \ +- (_X) >= machdep->machspec->kernel_link_addr) ? \ +- ((unsigned long)(_X)-(machdep->machspec->va_kernel_pa_offset)): \ +- (((unsigned long)(_X)-(machdep->kvbase)) + \ +- machdep->machspec->phys_base); \ +- }) ++#define PTOV(X) riscv64_PTOV((ulong)(X)) ++#define VTOP(X) riscv64_VTOP((ulong)(X)) + #define PAGEBASE(X) (((ulong)(X)) & (ulong)machdep->pagemask) + + /* +@@ -7211,6 +7203,8 @@ void riscv64_display_regs_from_elf_notes(int, FILE *); + void riscv64_init(int); + void riscv64_dump_machdep_table(ulong); + int riscv64_IS_VMALLOC_ADDR(ulong); ++ulong riscv64_PTOV(ulong); ++ulong riscv64_VTOP(ulong); + + #define display_idt_table() \ + error(FATAL, "-d option is not applicable to RISCV64 architecture\n") +diff --git a/riscv64.c b/riscv64.c +index ee9d4e321f6b..ff7b19eeb0d7 100644 +--- a/riscv64.c ++++ b/riscv64.c +@@ -1606,6 +1606,30 @@ riscv64_uvtop(struct task_context *tc, ulong uvaddr, physaddr_t *paddr, int verb + } + } + ++ulong riscv64_PTOV(ulong paddr) ++{ ++ ulong vaddr; ++ ulong offset = paddr - machdep->machspec->phys_base; ++ ++ vaddr = offset + machdep->kvbase; ++ ++ return vaddr; ++} ++ ++ulong ++riscv64_VTOP(ulong addr) ++{ ++ ulong paddr; ++ ++ if ( (THIS_KERNEL_VERSION >= LINUX(5,13,0)) && ++ (addr >= machdep->machspec->kernel_link_addr)) ++ paddr = (addr - (machdep->machspec->va_kernel_pa_offset)); ++ else ++ paddr = (addr - (ulong)machdep->kvbase + machdep->machspec->phys_base); ++ ++ return paddr; ++} ++ + static int + riscv64_kvtop(struct task_context *tc, ulong kvaddr, physaddr_t *paddr, int verbose) + { +-- +2.54.0 + diff --git a/0004-Fix-wrong-detection-of-LIVEPATCH-on-old-kernels.patch b/0004-Fix-wrong-detection-of-LIVEPATCH-on-old-kernels.patch new file mode 100644 index 0000000..97b81ad --- /dev/null +++ b/0004-Fix-wrong-detection-of-LIVEPATCH-on-old-kernels.patch @@ -0,0 +1,39 @@ +From c0097c84fed048e3e073f73a72ec9f16f8232c4b Mon Sep 17 00:00:00 2001 +From: Kazuhito Hagio +Date: Thu, 16 Apr 2026 04:35:42 +0000 +Subject: [PATCH 04/39] Fix wrong detection of "[LIVEPATCH]" on old kernels + +The crash-utility wrongly prints "[LIVEPATCH]" occasionally on old +kernels: + + KERNEL: vmlinux [LIVEPATCH] + DUMPFILE: dump.d31 [PARTIAL DUMP] + ... + RELEASE: 2.6.24 + +In is_livepatch(), show_kernel_tains() does not set any value to buf on +kernels that have the "tainted" symbol, so it's printed when the +uninitialized buf has "K" by chance. On very old kernels, but this +issue affects makedumpfile tests. + +Signed-off-by: Kazuhito Hagio +--- + kernel.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/kernel.c b/kernel.c +index 8781d6a22414..1e639ec6a007 100644 +--- a/kernel.c ++++ b/kernel.c +@@ -5678,7 +5678,7 @@ is_livepatch(void) + { + int i; + struct load_module *lm; +- char buf[BUFSIZE]; ++ char buf[BUFSIZE] = {0}; + + show_kernel_taints(buf, !VERBOSE); + if (strstr(buf, "K")) /* TAINT_LIVEPATCH */ +-- +2.54.0 + diff --git a/0005-Fix-unwinding-with-32k-stacks-in-ppc64le.patch b/0005-Fix-unwinding-with-32k-stacks-in-ppc64le.patch new file mode 100644 index 0000000..4348636 --- /dev/null +++ b/0005-Fix-unwinding-with-32k-stacks-in-ppc64le.patch @@ -0,0 +1,103 @@ +From 5da4d159a5efec95988349abfa12050dfc6578a6 Mon Sep 17 00:00:00 2001 +From: Lucas Oakley +Date: Wed, 6 May 2026 09:26:51 -0400 +Subject: [PATCH 05/39] Fix unwinding with 32k stacks in ppc64le + +With commit 18f14afe2816 ("powerpc/64s: Increase default stack +size to 32KB") in upstream ppc64le, the stack size was adjusted +from 16k to 32k. As a result, ppc64_back_trace() can bail +prematurely when checking if the stack pointer exists in the +range of the range of the irq stacks, since SIZE(irq_ctx), used +in ppc64_in_irqstack(), is set to 16k. This patch ensures that +irq_ctx is updated to 32k if a 32k stack size is used. + +Tested against: +el6 x86_64 +el7 ppc64le s390x x86_64 +el8 aarch64 ppc64le s390x x86_64 +el9 aarch64 ppc64le s390x x86_64 +el10 aarch64 ppc64le s390x x86_64 + +Without the commit: + +crash> bt -c 3 +PID: 17524 TASK: c0000000b2c0e400 CPU: 3 COMMAND: "xyz" +cannot find the stack info. + +With the commit: + +crash> bt -c 3 +PID: 17524 TASK: c0000000b2c0e400 CPU: 3 COMMAND: "xyz" + #0 [c000001dff7d7c10] smp_call_function_single_async at c00000000028dd38 + #1 [c000001dff7d7d30] _raw_spin_lock_irqsave at c000000001023f1c + #2 [c000001dff7d7d60] ibmvscsi_handle_crq at c0080000044635ec [ibmvscsi] + #3 [c000001dff7d7de0] ibmvscsi_task at c008000004463804 [ibmvscsi] + #4 [c000001dff7d7e30] tasklet_action_common.constprop.0 at c0000000001624cc + #5 [c000001dff7d7e90] __do_softirq at c0000000010244cc + #6 [c000001dff7d7f90] do_softirq_own_stack at c000000000016480 + #7 [c000000140d67700] __irq_exit_rcu at c0000000001613b8 + #8 [c000000140d67730] irq_exit at c000000000162170 + #9 [c000000140d67750] do_IRQ at c000000000015fa4 + #10 [c000000140d67780] hardware_interrupt_common_virt at c000000000009080 + Hardware Interrupt [500] exception frame: + R0: c000000001023de0 R1: c000000140d67a90 R2: c000000002c02500 + R3: c00800000b30269c R4: 0000000000000001 R5: 0000000000000001 + R6: ffffffffffffffff R7: 0000000000000000 R8: 0000000000000000 + R9: fffffffffffe0000 R10: 0000000000000002 R11: 0000000048422824 + R12: c000000001023d70 R13: c000001dffffd480 R14: 00007ffd6ef6d238 + R15: 0000000000000028 R16: c000001dfc1e2280 R17: c000001dfc1e2280 + R18: 00000000ab97fa48 R19: 0000000000000000 R20: 0000000000000001 + R21: 0000001df9ff0000 R22: 0000000000000028 R23: c0000000021f2280 + R24: 0000000000000000 R25: c0000000021f2280 R26: c0000000021f2380 + R27: 0000000000000000 R28: c00800000b30269c R29: 0000000000000000 + R30: c000000002c47190 R31: 000000000020000b + NIP: c0000000000aea14 MSR: 800000000280b033 OR3: c0000000000ae944 + CTR: c000000001023d70 LR: c000000001023de0 XER: 0000000020040001 + CCR: 0000000088422824 MQ: 0000000000000000 DAR: 0000000000000001 + DSISR: c0080000073b1e94 Syscall Result: 0000000000000000 + [NIP : queued_spin_lock_slowpath+1204] + [LR : _raw_spin_lock+112] + #11 [c000000140d67a90] queued_spin_lock_slowpath at c0000000000aea14 + #12 [c000000140d67bb0] _raw_spin_lock at c000000001023de0 (unreliable) + #13 [c000000140d67bd0] dm_blk_close at c00800000b2c6850 [dm_mod] + #14 [c000000140d67c00] blkdev_put_whole at c0000000007d2738 + #15 [c000000140d67c30] bdev_release at c0000000007d3a38 + #16 [c000000140d67c90] blkdev_release at c0000000007d4224 + #17 [c000000140d67cb0] __fput at c0000000005d2e98 + #18 [c000000140d67d00] task_work_run at c00000000018fb14 + #19 [c000000140d67d50] do_notify_resume at c000000000020bd4 + #20 [c000000140d67d80] interrupt_exit_user_prepare_main at c00000000002ed98 + #21 [c000000140d67de0] syscall_exit_prepare at c00000000002f240 + #22 [c000000140d67e10] system_call_vectored_common at c00000000000bff4 + +Signed-off-by: Lucas Oakley +--- + task.c | 12 ++++++++++++ + 1 file changed, 12 insertions(+) + +diff --git a/task.c b/task.c +index ec04b556c337..d7f3c129a709 100644 +--- a/task.c ++++ b/task.c +@@ -739,6 +739,18 @@ irqstacks_init(void) + if (!(tt->softirq_tasks = (ulong *)calloc(NR_CPUS, sizeof(ulong)))) + error(FATAL, "cannot malloc softirq_tasks space."); + ++ /* ++ * With the stack size adjusted from 16k to 32k for ppc64le, ++ * such as rhel-9.4.z. We need to ensure that SIZE(irq_ctx) is ++ * correctly set so the unwinder doesn't prematurely bail ++ * when switching between the kernel stack and irq stacks. ++ * The stack size is updated in task_init(), which calls ++ * this routine, irqstacks_init() after checking for the ++ * existence of irq_ctx. ++ */ ++ if (STACKSIZE() > SIZE(irq_ctx)) ++ ASSIGN_SIZE(irq_ctx) = STACKSIZE(); ++ + thread_info_buf = GETBUF(SIZE(irq_ctx)); + + if ((hard_sp = per_cpu_symbol_search("per_cpu__hardirq_ctx")) || +-- +2.54.0 + diff --git a/0006-arm64-fix-gdb-register-feeding-for-exception-frames-.patch b/0006-arm64-fix-gdb-register-feeding-for-exception-frames-.patch new file mode 100644 index 0000000..dff96f3 --- /dev/null +++ b/0006-arm64-fix-gdb-register-feeding-for-exception-frames-.patch @@ -0,0 +1,232 @@ +From fa4ba91e0383343112478decce41cea3b499da15 Mon Sep 17 00:00:00 2001 +From: lipengfei28 +Date: Wed, 1 Apr 2026 10:32:35 +0800 +Subject: [PATCH 06/39] arm64: fix gdb register feeding for exception frames + and stack switching + +On ARM64, crash's gdb bt relies on feeding a register snapshot (PC/SP/FP, +etc.) into gdb and letting gdb unwind from there. Before this change, the +register snapshot handling across exception frames / stack switching could be +broken in several ways, leading to truncated backtraces, garbage substacks, or +invalid addresses (e.g. -3 / 0xff...fd) showing up in gdb output. + +Fixes included in this patch: + +Fix out-of-bounds read when populating gdb registers from an exception frame +In arm64_print_exception_frame() the previous code copied +sizeof(struct arm64_pt_regs) bytes from a smaller stackframe-derived +buffer, which could read past valid data and poison the gdb registers. +Replace this with explicit field/register assignment and initialize the +bitmap accordingly. + +Improve unwinding across IRQ/overflow stack transitions on newer kernels +When switching stacks (IRQ / overflow), gdb may stop at the trampoline (e.g. +call_on_irq_stack) because the discontinuity prevents it from recovering +the caller frame automatically. For UNW_4_14+, "peek" one frame ahead by +reading the saved FP/PC from the current frame, and register that as the +next gdb substack, so gdb can continue unwinding on the process stack. + +Avoid creating invalid/empty substacks +Only add a gdb substack when the recovered PC is non-zero, preventing bogus +threads from being created. + +This patch only changes ARM64 unwinding/register setup logic. It does not try +to reformat or merge gdb output. + +Tested on: Android Linux 6.x, arm64 + +Signed-off-by: lipengfei28 +--- + arm64.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++--------- + 1 file changed, 87 insertions(+), 17 deletions(-) + +diff --git a/arm64.c b/arm64.c +index c125655d6807..d17709299b2d 100644 +--- a/arm64.c ++++ b/arm64.c +@@ -228,7 +228,7 @@ arm64_get_current_task_reg(int regno, const char *name, + return FALSE; + + if (sid && sid <= extra_stacks_idx) { +- ur_bitmap = extra_stacks_regs[extra_stacks_idx - 1]; ++ ur_bitmap = extra_stacks_regs[sid - 1]; + goto get_sub; + } + +@@ -3026,6 +3026,9 @@ static char *arm64_exception_functions[] = { + "do_el0_irq_bp_hardening", + "do_sp_pc_abort", + "handle_bad_stack", ++ "el1h_64_sync", ++ "el1h_64_irq", ++ "el1h_64_error", + NULL + }; + +@@ -3123,6 +3126,11 @@ arm64_print_stackframe_entry(struct bt_info *bt, int level, struct arm64_stackfr + + fprintf(ofp, "\n"); + ++ if (STREQ(name, "el1h_64_irq") || STREQ(name, "el1h_64_sync")) { ++ if (arm64_is_kernel_exception_frame(bt, frame->sp)) ++ arm64_print_exception_frame(bt, frame->sp, KERNEL_MODE, ofp); ++ } ++ + if (bt->flags & BT_LINE_NUMBERS) { + get_line_number(branch_pc, buf, FALSE); + if (strlen(buf)) +@@ -3775,11 +3783,13 @@ arm64_back_trace_cmd(struct bt_info *bt) + REG_SEQ(arm64_pt_regs, pc)); + SET_BIT(extra_stacks_regs[extra_stacks_idx]->bitmap, + REG_SEQ(arm64_pt_regs, sp)); +- if (!bt->machdep || ++ if (extra_stacks_regs[extra_stacks_idx]->ur.pc && ++ (bt->task != tt->panic_task) && ++ (!bt->machdep || + (extra_stacks_regs[extra_stacks_idx]->ur.sp != + ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.sp && + extra_stacks_regs[extra_stacks_idx]->ur.pc != +- ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.pc)) { ++ ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.pc))) { + gdb_add_substack (extra_stacks_idx++); + } + } +@@ -3925,11 +3935,13 @@ arm64_back_trace_cmd_v2(struct bt_info *bt) + REG_SEQ(arm64_pt_regs, pc)); + SET_BIT(extra_stacks_regs[extra_stacks_idx]->bitmap, + REG_SEQ(arm64_pt_regs, sp)); +- if (!bt->machdep || ++ if (extra_stacks_regs[extra_stacks_idx]->ur.pc && ++ (bt->task != tt->panic_task) && ++ (!bt->machdep || + (extra_stacks_regs[extra_stacks_idx]->ur.sp != + ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.sp && + extra_stacks_regs[extra_stacks_idx]->ur.pc != +- ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.pc)) { ++ ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.pc))) { + gdb_add_substack (extra_stacks_idx++); + } + } +@@ -4232,6 +4244,46 @@ arm64_in_kdump_text_on_irq_stack(struct bt_info *bt) + return FALSE; + } + ++static void ++arm64_gdb_add_next_frame_substack(struct bt_info *bt, const struct arm64_stackframe *frame) ++{ ++ struct machine_specific *ms = machdep->machspec; ++ struct user_regs_bitmap_struct *ur_ptr; ++ ulong next_fp, next_pc, next_sp; ++ ++ if (!extra_stacks_regs[extra_stacks_idx]) { ++ extra_stacks_regs[extra_stacks_idx] = (struct user_regs_bitmap_struct *) ++ malloc(sizeof(struct user_regs_bitmap_struct)); ++ } ++ ++ memset(extra_stacks_regs[extra_stacks_idx], 0, sizeof(struct user_regs_bitmap_struct)); ++ ur_ptr = extra_stacks_regs[extra_stacks_idx]; ++ ++ next_fp = GET_STACK_ULONG(frame->fp); ++ next_pc = GET_STACK_ULONG(frame->fp + 8); ++ next_sp = frame->fp + 16; ++ ++ if (is_kernel_text(next_pc | ms->CONFIG_ARM64_KERNELPACMASK)) ++ next_pc |= ms->CONFIG_ARM64_KERNELPACMASK; ++ ++ ur_ptr->ur.pc = next_pc; ++ ur_ptr->ur.sp = next_sp; ++ ur_ptr->ur.regs[29] = next_fp; ++ ++ SET_BIT(ur_ptr->bitmap, REG_SEQ(arm64_pt_regs, pc)); ++ SET_BIT(ur_ptr->bitmap, REG_SEQ(arm64_pt_regs, sp)); ++ SET_BIT(ur_ptr->bitmap, REG_SEQ(arm64_pt_regs, regs[0]) + 29); ++ ++ if (ur_ptr->ur.pc && ++ (!bt->machdep || ++ (ur_ptr->ur.sp != ++ ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.sp && ++ ur_ptr->ur.pc != ++ ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.pc))) { ++ gdb_add_substack(extra_stacks_idx++); ++ } ++} ++ + static int + arm64_switch_stack(struct bt_info *bt, struct arm64_stackframe *frame, FILE *ofp) + { +@@ -4263,8 +4315,11 @@ arm64_switch_stack(struct bt_info *bt, struct arm64_stackframe *frame, FILE *ofp + if (frame->fp == 0) + return USER_MODE; + +- if (!(machdep->flags & UNW_4_14)) ++ if (!(machdep->flags & UNW_4_14)) { + arm64_print_exception_frame(bt, frame->sp, KERNEL_MODE, ofp); ++ } else { ++ arm64_gdb_add_next_frame_substack(bt, frame); ++ } + + return KERNEL_MODE; + } +@@ -4300,8 +4355,11 @@ arm64_switch_stack_from_overflow(struct bt_info *bt, struct arm64_stackframe *fr + if (frame->fp == 0) + return USER_MODE; + +- if (!(machdep->flags & UNW_4_14)) ++ if (!(machdep->flags & UNW_4_14)) { + arm64_print_exception_frame(bt, frame->sp, KERNEL_MODE, ofp); ++ } else { ++ arm64_gdb_add_next_frame_substack(bt, frame); ++ } + + return KERNEL_MODE; + } +@@ -4549,6 +4607,9 @@ arm64_print_exception_frame(struct bt_info *bt, ulong pt_regs, int mode, FILE *o + (ulong)regs->orig_x0, (ulong)regs->syscallno); + fprintf(ofp, " PSTATE: %08lx\n", (ulong)regs->pstate); + } else if (!(bt->flags & BT_EFRAME_SEARCH)) { ++ struct user_regs_bitmap_struct *ur_ptr; ++ int i; ++ + if (!extra_stacks_regs[extra_stacks_idx]) { + extra_stacks_regs[extra_stacks_idx] = + (struct user_regs_bitmap_struct *) +@@ -4556,17 +4617,26 @@ arm64_print_exception_frame(struct bt_info *bt, ulong pt_regs, int mode, FILE *o + } + memset(extra_stacks_regs[extra_stacks_idx], 0, + sizeof(struct user_regs_bitmap_struct)); +- memcpy(&extra_stacks_regs[extra_stacks_idx]->ur, regs, +- sizeof(struct arm64_pt_regs)); +- for (int i = 0; i < sizeof(struct arm64_pt_regs)/sizeof(long); i++) +- SET_BIT(extra_stacks_regs[extra_stacks_idx]->bitmap, i); +- if (!bt->machdep || +- (extra_stacks_regs[extra_stacks_idx]->ur.sp != ++ ++ ur_ptr = extra_stacks_regs[extra_stacks_idx]; ++ ++ ur_ptr->ur.pc = regs->pc; ++ ur_ptr->ur.sp = regs->sp; ++ ur_ptr->ur.pstate = regs->pstate; ++ for (i = 0; i < 31; i++) ++ ur_ptr->ur.regs[i] = regs->regs[i]; ++ ++ for (i = 0; i < 34; i++) ++ SET_BIT(ur_ptr->bitmap, i); ++ ++ if (ur_ptr->ur.pc && ++ (!bt->machdep || ++ (ur_ptr->ur.sp != + ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.sp && +- extra_stacks_regs[extra_stacks_idx]->ur.pc != +- ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.pc)) { +- gdb_add_substack (extra_stacks_idx++); +- } ++ ur_ptr->ur.pc != ++ ((struct user_regs_bitmap_struct *)(bt->machdep))->ur.pc))) { ++ gdb_add_substack(extra_stacks_idx++); ++ } + } + } + +-- +2.54.0 + diff --git a/0007-RISCV64-fix-pmd-address-calculation-in-riscv64_vtop_.patch b/0007-RISCV64-fix-pmd-address-calculation-in-riscv64_vtop_.patch new file mode 100644 index 0000000..ed2df8a --- /dev/null +++ b/0007-RISCV64-fix-pmd-address-calculation-in-riscv64_vtop_.patch @@ -0,0 +1,32 @@ +From 4f66cea2487d511dbc9b7ff10a7e9fb98337273f Mon Sep 17 00:00:00 2001 +From: Rui Qi +Date: Wed, 29 Apr 2026 14:13:25 +0800 +Subject: [PATCH 07/39] RISCV64: fix pmd address calculation in + riscv64_vtop_4level_4k + +Fix a bug in the PMD address calculation where the operator was +incorrectly using '+' instead of '*'. This caused the PMD index +to be added as an offset rather than multiplied by the entry size, +resulting in incorrect page table traversal for 4-level 4KB paging. + +Signed-off-by: Rui Qi +--- + riscv64.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/riscv64.c b/riscv64.c +index ff7b19eeb0d7..8c6aa9557ec5 100644 +--- a/riscv64.c ++++ b/riscv64.c +@@ -1241,7 +1241,7 @@ riscv64_vtop_4level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose) + + /* PMD */ + FILL_PMD(PAGEBASE(pmd_base), PHYSADDR, PAGESIZE()); +- pmd_addr = pmd_base + sizeof(pmd_t) + pmd_index_l4_4k(vaddr); ++ pmd_addr = pmd_base + sizeof(pmd_t) * pmd_index_l4_4k(vaddr); + pmd_val = ULONG(machdep->pmd + PAGEOFFSET(pmd_addr)); + if (verbose) + fprintf(fp, " PMD: %016lx => %016lx\n", pmd_addr, pmd_val); +-- +2.54.0 + diff --git a/0008-symbols-optimize-symname_hash-with-larger-table-and-.patch b/0008-symbols-optimize-symname_hash-with-larger-table-and-.patch new file mode 100644 index 0000000..d890cd4 --- /dev/null +++ b/0008-symbols-optimize-symname_hash-with-larger-table-and-.patch @@ -0,0 +1,71 @@ +From ce0fc6b73de86e448fd13541a4ea6b60d7af0e1c Mon Sep 17 00:00:00 2001 +From: Rui Qi +Date: Wed, 6 May 2026 12:27:29 +0800 +Subject: [PATCH 08/39] symbols: optimize symname_hash with larger table and + FNV-1a hash + +Optimize the symbol name hash table to reduce collision and improve +performance, especially on RISC-V architecture where symbol lookup +is a hotspot. + +Changes: +- Increase SYMNAME_HASH from 512 to 16384 (32x) to reduce collisions +- Replace simple hash with FNV-1a algorithm for better distribution +- Remove strlen() call, compute hash in single pass + +This reduces the average chain length in hash buckets significantly, +improving symbol lookup performance at the cost of ~248KB additional +memory. + +Signed-off-by: Rui Qi +--- + defs.h | 2 +- + symbols.c | 15 +++++++++------ + 2 files changed, 10 insertions(+), 7 deletions(-) + +diff --git a/defs.h b/defs.h +index 54f1725d089a..6a91343f0313 100644 +--- a/defs.h ++++ b/defs.h +@@ -2900,7 +2900,7 @@ struct downsized { + #define SYMVAL_HASH_INDEX(vaddr) \ + (((vaddr) >> machdep->pageshift) % SYMVAL_HASH) + +-#define SYMNAME_HASH (512) ++#define SYMNAME_HASH (16384) + + #define PATCH_KERNEL_SYMBOLS_START ((char *)(1)) + #define PATCH_KERNEL_SYMBOLS_STOP ((char *)(2)) +diff --git a/symbols.c b/symbols.c +index e6865cabef74..afdf4a61cea2 100644 +--- a/symbols.c ++++ b/symbols.c +@@ -1170,16 +1170,19 @@ symname_hash_init(void) + static unsigned int + symname_hash_index(char *name) + { +- unsigned int len, value; +- unsigned char *array = (unsigned char *)name; ++ unsigned int hash = 2166136261U; ++ unsigned char *p = (unsigned char *)name; + +- len = strlen(name); +- if (!len) ++ if (!*p) + error(FATAL, "The length of the symbol name is zero!\n"); + +- value = array[len - 1] * array[len / 2]; ++ /* FNV-1a hash algorithm for better distribution */ ++ while (*p) { ++ hash ^= *p++; ++ hash *= 16777619; ++ } + +- return (array[0] ^ value) % SYMNAME_HASH; ++ return hash % SYMNAME_HASH; + } + + /* +-- +2.54.0 + diff --git a/0009-RISCV64-add-mapping-symbol-filter-in-riscv64_verify_.patch b/0009-RISCV64-add-mapping-symbol-filter-in-riscv64_verify_.patch new file mode 100644 index 0000000..1fa917b --- /dev/null +++ b/0009-RISCV64-add-mapping-symbol-filter-in-riscv64_verify_.patch @@ -0,0 +1,76 @@ +From ad1e05f3d1ae96ce5862b2877db4bded9a9a6336 Mon Sep 17 00:00:00 2001 +From: Rui Qi +Date: Wed, 6 May 2026 12:27:30 +0800 +Subject: [PATCH 09/39] RISCV64: add mapping symbol filter in + riscv64_verify_symbol + +Add mapping symbol filter in riscv64_verify_symbol() to filter out +linker mapping symbols like '.L*', 'L0*' and '$*'. These symbols +should not end up in the symbol list. + +Also optimize riscv64_verify_symbol() by consolidating name validity +checks at the function entry. + +Changes: +- riscv64.c: Add mapping symbol filter and optimize name checks +- symbols.c: Add machine_type("RISCV64") to enable verify_symbol() + call in store_module_kallsyms_v2() + +Signed-off-by: Rui Qi +--- + riscv64.c | 14 +++++++++++--- + symbols.c | 5 ++++- + 2 files changed, 15 insertions(+), 4 deletions(-) + +diff --git a/riscv64.c b/riscv64.c +index 8c6aa9557ec5..f0783d570c7b 100644 +--- a/riscv64.c ++++ b/riscv64.c +@@ -209,15 +209,23 @@ riscv64_cmd_mach(void) + static int + riscv64_verify_symbol(const char *name, ulong value, char type) + { +- if (CRASHDEBUG(8) && name && strlen(name)) ++ if (!name || !strlen(name)) ++ return FALSE; ++ ++ if (CRASHDEBUG(8)) + fprintf(fp, "%08lx %s\n", value, name); + ++ /* Filter out mapping symbols */ ++ if ((name[0] == '.' && name[1] == 'L') || ++ (name[0] == 'L' && name[1] == '0') || ++ (name[0] == '$')) ++ return FALSE; ++ + if (!(machdep->flags & KSYMS_START)) { + if (STREQ(name, "_text") || STREQ(name, "_stext")) + machdep->flags |= KSYMS_START; + +- return (name && strlen(name) && !STRNEQ(name, "__func__.") && +- !STRNEQ(name, "__crc_")); ++ return (!STRNEQ(name, "__func__.") && !STRNEQ(name, "__crc_")); + } + + return TRUE; +diff --git a/symbols.c b/symbols.c +index afdf4a61cea2..8eb8b37abc23 100644 +--- a/symbols.c ++++ b/symbols.c +@@ -2990,9 +2990,12 @@ store_module_kallsyms_v2(struct load_module *lm, int start, int curr, + * or '$x' for ARM64, and '$d'. + * On LoongArch we have linker mapping symbols like '.L' + * or 'L0'. ++ * On RISCV64 we have linker mapping symbols like '.L', ++ * 'L0' or '$'. + * Make sure that these don't end up into our symbol list. + */ +- if ((machine_type("ARM") || machine_type("ARM64") || machine_type("LOONGARCH64")) && ++ if ((machine_type("ARM") || machine_type("ARM64") || machine_type("LOONGARCH64") || ++ machine_type("RISCV64")) && + !machdep->verify_symbol(nameptr, ec->st_value, ec->st_info)) + continue; + +-- +2.54.0 + diff --git a/0010-Fix-get_xtime-for-kernel-6.13-and-higher.patch b/0010-Fix-get_xtime-for-kernel-6.13-and-higher.patch new file mode 100644 index 0000000..a5333ef --- /dev/null +++ b/0010-Fix-get_xtime-for-kernel-6.13-and-higher.patch @@ -0,0 +1,71 @@ +From 7ff3146d46ea9afb0724b57b3425c9a965a67d12 Mon Sep 17 00:00:00 2001 +From: "Aureau, Georges (Kernel Tools ERT)" +Date: Thu, 7 May 2026 04:12:32 +0000 +Subject: [PATCH 10/39] Fix get_xtime() for kernel 6.13 and higher + +On kernels 6.13 and higher, DATE is not displayed properly: +crash> sys | grep -e DATE -e REL + DATE: Wed Dec 31 18:00:00 CST 1969 + RELEASE: 7.0.0-14-generic + +The function get_xtime() was enhanced to support: +- kernel 6.13 ("shadow_timekeeper" moved to "struct tk_data tk_core") +- kernel 6.17 ("tk_core" is now "timekeeper_data[0]). + +With this get_xtime() fix: +crash> sys | grep DATE + DATE: Mon Apr 27 09:19:50 CDT 2026 + +Signed-off-by: Georges Aureau +--- + defs.h | 1 + + kernel.c | 14 +++++++++++++- + 2 files changed, 14 insertions(+), 1 deletion(-) + +diff --git a/defs.h b/defs.h +index 6a91343f0313..89044b18cdbe 100644 +--- a/defs.h ++++ b/defs.h +@@ -2290,6 +2290,7 @@ struct offset_table { /* stash of commonly-used offsets */ + long bpf_ringbuf_nr_pages; + long hrtimer_clock_base_index; + long klp_patch_list; ++ long tk_data_timekeeper; + }; + + struct size_table { /* stash of commonly-used sizes */ +diff --git a/kernel.c b/kernel.c +index 1e639ec6a007..6dfc8714a1ff 100644 +--- a/kernel.c ++++ b/kernel.c +@@ -258,6 +258,7 @@ kernel_init() + + MEMBER_OFFSET_INIT(timekeeper_xtime, "timekeeper", "xtime"); + MEMBER_OFFSET_INIT(timekeeper_xtime_sec, "timekeeper", "xtime_sec"); ++ MEMBER_OFFSET_INIT(tk_data_timekeeper, "tk_data", "timekeeper"); + get_xtime(&kt->date); + if (CRASHDEBUG(1)) + fprintf(fp, "xtime timespec.tv_sec: %lx: %s\n", +@@ -11134,7 +11135,18 @@ get_xtime(struct timespec *date) + struct syment *sp; + uint64_t xtime_sec; + +- if (VALID_MEMBER(timekeeper_xtime) && ++ if (VALID_MEMBER(tk_data_timekeeper) && ++ VALID_MEMBER(timekeeper_xtime_sec)) { ++ long offset = OFFSET(tk_data_timekeeper) + ++ OFFSET(timekeeper_xtime_sec); ++ if ((sp = kernel_symbol_search("timekeeper_data")) || ++ (sp = kernel_symbol_search("tk_core"))) { ++ readmem(sp->value + offset, KVADDR, ++ &xtime_sec, sizeof(uint64_t), ++ "tk_data timekeeper xtime_sec", RETURN_ON_ERROR); ++ date->tv_sec = (__time_t)xtime_sec; ++ } ++ } else if (VALID_MEMBER(timekeeper_xtime) && + (sp = kernel_symbol_search("timekeeper"))) { + readmem(sp->value + OFFSET(timekeeper_xtime), KVADDR, + date, sizeof(struct timespec), +-- +2.54.0 + diff --git a/0011-symbols-Add-support-for-mod-symtab-with-combined-GPL.patch b/0011-symbols-Add-support-for-mod-symtab-with-combined-GPL.patch new file mode 100644 index 0000000..d31b3ec --- /dev/null +++ b/0011-symbols-Add-support-for-mod-symtab-with-combined-GPL.patch @@ -0,0 +1,74 @@ +From 3a415c07a18a465df2580365b43b4d8f8a5d815b Mon Sep 17 00:00:00 2001 +From: Alexander Egorenkov +Date: Tue, 28 Apr 2026 12:16:37 +0200 +Subject: [PATCH 11/39] symbols: Add support for mod symtab with combined GPL + and non-GPL symbols + +The commit b4760ff2a5e4 ("module: deprecate usage of *_gpl sections in module loader") +eliminated separate GPL symbol sections representing GPL only symbols. +Therefore, depending on whether the member gpl_syms is present +in struct module, decide whether GPL module symbols are separated or not. + +https://lore.kernel.org/all/20260326-kflagstab-v5-4-fa0796fe88d9@google.com + +Signed-off-by: Alexander Egorenkov +--- + kernel.c | 13 ++++++++----- + symbols.c | 9 +++++++-- + 2 files changed, 15 insertions(+), 7 deletions(-) + +diff --git a/kernel.c b/kernel.c +index 6dfc8714a1ff..eb9754c5e082 100644 +--- a/kernel.c ++++ b/kernel.c +@@ -3635,9 +3635,11 @@ module_init(void) + case KMOD_V2: + MEMBER_OFFSET_INIT(module_num_syms, "module", "num_syms"); + MEMBER_OFFSET_INIT(module_list, "module", "list"); +- MEMBER_OFFSET_INIT(module_gpl_syms, "module", "gpl_syms"); +- MEMBER_OFFSET_INIT(module_num_gpl_syms, "module", +- "num_gpl_syms"); ++ if (MEMBER_EXISTS("module", "gpl_syms")) { ++ MEMBER_OFFSET_INIT(module_gpl_syms, "module", "gpl_syms"); ++ MEMBER_OFFSET_INIT(module_num_gpl_syms, "module", ++ "num_gpl_syms"); ++ } + + if (MEMBER_EXISTS("module", "mem")) { /* 6.4 and later */ + kt->flags2 |= KMOD_MEMORY; /* MODULE_MEMORY() can be used. */ +@@ -3831,8 +3833,9 @@ module_init(void) + nsyms = UINT(modbuf + OFFSET(module_nsyms)); + break; + case KMOD_V2: +- nsyms = UINT(modbuf + OFFSET(module_num_syms)) + +- UINT(modbuf + OFFSET(module_num_gpl_syms)); ++ nsyms = UINT(modbuf + OFFSET(module_num_syms)); ++ if (VALID_MEMBER(module_num_gpl_syms)) ++ nsyms += UINT(modbuf + OFFSET(module_num_gpl_syms)); + break; + } + +diff --git a/symbols.c b/symbols.c +index 8eb8b37abc23..3c62f54d4a93 100644 +--- a/symbols.c ++++ b/symbols.c +@@ -1979,9 +1979,14 @@ store_module_symbols_6_4(ulong total, int mods_installed) + "module buffer", FAULT_ON_ERROR); + + syms = ULONG(modbuf + OFFSET(module_syms)); +- gpl_syms = ULONG(modbuf + OFFSET(module_gpl_syms)); + nsyms = UINT(modbuf + OFFSET(module_num_syms)); +- ngplsyms = UINT(modbuf + OFFSET(module_num_gpl_syms)); ++ if (VALID_MEMBER(module_gpl_syms)) { ++ gpl_syms = ULONG(modbuf + OFFSET(module_gpl_syms)); ++ ngplsyms = UINT(modbuf + OFFSET(module_num_gpl_syms)); ++ } else { ++ gpl_syms = 0; ++ ngplsyms = 0; ++ } + + nksyms = UINT(modbuf + OFFSET(module_num_symtab)); + +-- +2.54.0 + diff --git a/0012-add-folio_order-function.patch b/0012-add-folio_order-function.patch new file mode 100644 index 0000000..08c74d3 --- /dev/null +++ b/0012-add-folio_order-function.patch @@ -0,0 +1,202 @@ +From 744dfa3a190d3dd2b1cc42d3378a1cb299958c0a Mon Sep 17 00:00:00 2001 +From: Huang Shijie +Date: Mon, 18 May 2026 19:31:38 +0800 +Subject: [PATCH 12/39] add folio_order function + +The folio_order() has three versions: + 1.) In verion 1, folio_order() uses page[1].compound_order to + get the folio order. We use "folio_batch" as the indicator. + + Please refer to patch set: + https://lore.kernel.org/all/20211208042256.1923824-1-willy@infradead.org/ + + 2.) In version 2, the following patch introduces _folio_order: + c3a15bff46cb5149 "mm: reimplement folio_order() and folio_nr_pages()" + We use "_folio_order" as the indicator. + + Please refer to patch set: + https://lore.kernel.org/all/20220808193430.3378317-1-willy@infradead.org/ + + 3.) In version 3, the following patch replaces the _folio_order with _flags_1: + ebc1baf5c9b46c22 "mm: free up a word in the first tail page" + folio_order() uses _flags_1 to get the folio order. + + We use "free_huge_folio" as the indicator. + Please refer to patch set: + https://lore.kernel.org/linux-mm/20230816151201.3655946-1-willy@infradead.org/ + (Note, we do not use the struct modifications within the patch set as the indicator, + since they have been removed in later kernel.) + +This patch will be used in later patches. + +Signed-off-by: Huang Shijie +--- + defs.h | 8 ++++++ + memory.c | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ + symbols.c | 6 +++++ + 3 files changed, 93 insertions(+) + +diff --git a/defs.h b/defs.h +index 89044b18cdbe..bd996ef38707 100644 +--- a/defs.h ++++ b/defs.h +@@ -2291,6 +2291,9 @@ struct offset_table { /* stash of commonly-used offsets */ + long hrtimer_clock_base_index; + long klp_patch_list; + long tk_data_timekeeper; ++ long page_compound_order; ++ long folio__folio_order; ++ long folio__flags_1; + }; + + struct size_table { /* stash of commonly-used sizes */ +@@ -2470,6 +2473,9 @@ struct size_table { /* stash of commonly-used sizes */ + long cpumask_t; + long task_struct_exit_state; + long bpf_ringbuf_map; ++ long page_compound_order; ++ long folio__folio_order; ++ long folio__flags_1; + }; + + struct array_table { +@@ -5998,6 +6004,8 @@ ulong do_xarray(ulong, int, struct list_pair *); + #define XARRAY_TAG_MASK (3UL) + #define XARRAY_TAG_INTERNAL (2UL) + ++int folio_order(ulong folio); ++ + int file_dump(ulong, ulong, ulong, int, int); + #define DUMP_FULL_NAME 0x1 + #define DUMP_INODE_ONLY 0x2 +diff --git a/memory.c b/memory.c +index 17423a588464..502f8fdd0ea7 100644 +--- a/memory.c ++++ b/memory.c +@@ -410,6 +410,10 @@ mem_init(void) + DISPLAY_DEFAULT = (sizeof(long) == 8) ? DISPLAY_64 : DISPLAY_32; + } + ++#define FOLIO_ORDER_V1 1 ++#define FOLIO_ORDER_V2 2 ++#define FOLIO_ORDER_V3 3 ++static int folio_order_version; + + /* + * Stash a few popular offsets and some basic kernel virtual memory +@@ -547,6 +551,37 @@ vm_init(void) + MEMBER_OFFSET_INIT(page_freelist, "page", "freelist"); + MEMBER_OFFSET_INIT(page_page_type, "page", "page_type"); + ++ /* ++ * The "folio_batch" was introduced at patch set: ++ * https://lore.kernel.org/all/20211208042256.1923824-1-willy@infradead.org/ ++ * Use it as the indicator for folio_order() version 1. ++ */ ++ if (STRUCT_EXISTS("folio_batch")) ++ folio_order_version = FOLIO_ORDER_V1; ++ ++ MEMBER_OFFSET_INIT(page_compound_order, "page", "compound_order"); ++ MEMBER_SIZE_INIT(page_compound_order, "page", "compound_order"); ++ ++ /* ++ * The "_folio_order" was introduced at patch set: ++ * https://lore.kernel.org/all/20220808193430.3378317-1-willy@infradead.org/ ++ * Use it as the indicator for folio_order() version 2. ++ */ ++ MEMBER_SIZE_INIT(folio__folio_order, "folio", "_folio_order"); ++ MEMBER_OFFSET_INIT(folio__folio_order, "folio", "_folio_order"); ++ if (VALID_MEMBER(folio__folio_order)) ++ folio_order_version = FOLIO_ORDER_V2; ++ ++ /* ++ * The "free_huge_folio()" was introduced at patch set: ++ * https://lore.kernel.org/linux-mm/20230816151201.3655946-1-willy@infradead.org/ ++ * Use it as the indicator for folio_order() version 3. ++ */ ++ MEMBER_OFFSET_INIT(folio__flags_1, "folio", "_flags_1"); ++ MEMBER_SIZE_INIT(folio__flags_1, "folio", "_flags_1"); ++ if (kernel_symbol_exists("free_huge_folio")) ++ folio_order_version = FOLIO_ORDER_V3; ++ + MEMBER_OFFSET_INIT(mm_struct_pgd, "mm_struct", "pgd"); + + MEMBER_OFFSET_INIT(swap_info_struct_swap_file, +@@ -20426,6 +20461,50 @@ static unsigned int oo_objects(ulong oo) + return (oo & ((1 << 16) - 1)); + } + ++int ++folio_order(ulong folio) ++{ ++ ulong v = 0; ++ int PG_head = 16; ++ ++ if (folio_order_version == FOLIO_ORDER_V1) { ++ readmem(folio + OFFSET(page_flags), KVADDR, &v, sizeof(ulong), ++ "folio.page.flags", FAULT_ON_ERROR); ++ if (!(v & (1 << PG_head))) ++ return 0; ++ ++ readmem(folio + SIZE(page) + OFFSET(page_compound_order), KVADDR, &v, ++ SIZE(page_compound_order), "page[1].compound_order", FAULT_ON_ERROR); ++ ++ return v; ++ } else if (folio_order_version == FOLIO_ORDER_V2) { ++ readmem(folio + OFFSET(page_flags), KVADDR, &v, sizeof(ulong), ++ "folio.page.flags", FAULT_ON_ERROR); ++ if (!(v & (1 << PG_head))) ++ return 0; ++ ++ readmem(folio + OFFSET(folio__folio_order), KVADDR, &v, ++ SIZE(folio__folio_order), "folio->_folio_order", FAULT_ON_ERROR); ++ ++ return v; ++ } else if (folio_order_version == FOLIO_ORDER_V3) { ++ /* The PG_head changes to bit 6 in this version */ ++ PG_head = 6; ++ ++ readmem(folio + OFFSET(page_flags), KVADDR, &v, sizeof(ulong), ++ "folio.page.flags", FAULT_ON_ERROR); ++ if (!(v & (1 << PG_head))) ++ return 0; ++ ++ readmem(folio + OFFSET(folio__flags_1), KVADDR, &v, ++ SIZE(folio__flags_1), "folio->_flags_1", FAULT_ON_ERROR); ++ ++ return v & 0xff; ++ } else { ++ return 0; ++ } ++} ++ + #ifdef NOT_USED + ulong + slab_to_kmem_cache_node(struct meminfo *si, ulong slab_page) +diff --git a/symbols.c b/symbols.c +index 3c62f54d4a93..c0285058c5cb 100644 +--- a/symbols.c ++++ b/symbols.c +@@ -10462,6 +10462,9 @@ dump_offset_table(char *spec, ulong makestruct) + fprintf(fp, " page_private: %ld\n", OFFSET(page_private)); + fprintf(fp, " page_page_type: %ld\n", + OFFSET(page_page_type)); ++ fprintf(fp, " page_compound_order: %ld\n", OFFSET(page_compound_order)); ++ fprintf(fp, " folio__folio_order: %ld\n", OFFSET(folio__folio_order)); ++ fprintf(fp, " folio__flags_1: %ld\n", OFFSET(folio__flags_1)); + + fprintf(fp, " trace_print_flags_mask: %ld\n", + OFFSET(trace_print_flags_mask)); +@@ -11972,6 +11975,9 @@ dump_offset_table(char *spec, ulong makestruct) + fprintf(fp, "\n size_table:\n"); + fprintf(fp, " page: %ld\n", SIZE(page)); + fprintf(fp, " page_flags: %ld\n", SIZE(page_flags)); ++ fprintf(fp, " page_compound_order: %ld\n", SIZE(page_compound_order)); ++ fprintf(fp, " folio__folio_order: %ld\n", SIZE(folio__folio_order)); ++ fprintf(fp, " folio__flags_1: %ld\n", SIZE(folio__flags_1)); + fprintf(fp, " trace_print_flags: %ld\n", SIZE(trace_print_flags)); + fprintf(fp, " free_area_struct: %ld\n", + SIZE(free_area_struct)); +-- +2.54.0 + diff --git a/0013-xarray-add-large-folio-support.patch b/0013-xarray-add-large-folio-support.patch new file mode 100644 index 0000000..28cf08c --- /dev/null +++ b/0013-xarray-add-large-folio-support.patch @@ -0,0 +1,232 @@ +From 5d494e101bdd7d87add51d13bc1b42af8d8a151d Mon Sep 17 00:00:00 2001 +From: Huang Shijie +Date: Mon, 18 May 2026 19:31:39 +0800 +Subject: [PATCH 13/39] xarray: add large folio support + +We may meet a large folio at two cases: + 1.) when height is 1, the real data layout may looks like this: + -------------------------------------------- + crash> p *(struct xa_node*)0xffff889883a6a910 + $5 = { + shift = 0 '\000', + offset = 0 '\000', + count = 64 '@', + nr_values = 0 '\000', + ................. + slots = { 0xffffea0061d21000, 0x2, 0x2, 0x2, + 0xffffea0121d85c00, 0x12, 0x12, 0x12, + 0xffffea0121d9f400, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, 0x22, + 0xffffea0121583800, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, 0x42, + 0xffffea0121833e00, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, + 0xffffea0121eb3800, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, 0x82, + 0x82, 0x82, 0x82 + }, + ................. + } + -------------------------------------------- + The page orders for above folios: + 0xffffea0061d21000 --> order 2 + 0xffffea0121d85c00 --> order 2 + 0xffffea0121d9f400 --> order 3 + 0xffffea0121583800 --> order 3 + 0xffffea0121833e00 --> order 3 + 0xffffea0121eb3800 --> order 5 + +2.) when height is not 1, the real data layout may looks like this: + -------------------------------------------- + slot: { 0xffff8a2c866b745a, 0xffff8a2c866b16d2, 0xfffff9fcc4ea4000, 0xfffff9fcc4eb9000, ..} + -------------------------------------------- + The 0xffff8a2c866b745a and 0xffff8a2c866b16d2 is a internal node, not a folio. + The page orders for above folios: + 0xfffff9fcc4ea4000 --> order 6 + +Current code does not work correctly with large folio page cache. + This patch adds the large folio support with following: + + 1.) Add XARRAY_TYPE_PAGE_CACHE flag for do_xarray(). + 2.) Add update_count hook for do_xarray_info{}, + add update_off hook for xarray_ops{}. + 3.) changed the do_xarray_iter() to check the update_off + at proper place (height == 1 and height > 1). + 4.) Implement the folio_update_count/folio_xarray_update_off + hooks for large folio page cache. + +Signed-off-by: Huang Shijie +--- + defs.h | 5 +++++ + filesys.c | 51 ++++++++++++++++++++++++++++++++++++++++++++++----- + tools.c | 15 +++++++++++++-- + 3 files changed, 64 insertions(+), 7 deletions(-) + +diff --git a/defs.h b/defs.h +index bd996ef38707..e7e97e7d80b2 100644 +--- a/defs.h ++++ b/defs.h +@@ -5689,6 +5689,9 @@ int do_radix_tree_traverse(ulong ptr, int is_root, struct radix_tree_ops *ops); + struct xarray_ops { + void (*entry)(ulong node, ulong slot, const char *path, + ulong index, void *private); ++ uint (*update_off)(ulong node, uint height, char *path, ulong index, ++ ulong slot, uint off, ulong shift, struct xarray_ops *ops, ++ bool *should_continue); + uint radix; + void *private; + }; +@@ -6003,6 +6006,8 @@ ulong do_xarray(ulong, int, struct list_pair *); + #define XARRAY_DUMP_CB (5) + #define XARRAY_TAG_MASK (3UL) + #define XARRAY_TAG_INTERNAL (2UL) ++#define XARRAY_TYPE_PAGE_CACHE 0x8 ++extern ulong XA_CHUNK_SHIFT; + + int folio_order(ulong folio); + +diff --git a/filesys.c b/filesys.c +index 34944e2739aa..35d55a543739 100644 +--- a/filesys.c ++++ b/filesys.c +@@ -2289,7 +2289,7 @@ dump_inode_page_cache_info(ulong inode) + if (root_rnode) + count = do_radix_tree(root_rnode, RADIX_TREE_DUMP_CB, &lp); + else if (xarray) +- count = do_xarray(xarray, XARRAY_DUMP_CB, &lp); ++ count = do_xarray(xarray, XARRAY_DUMP_CB | XARRAY_TYPE_PAGE_CACHE, &lp); + + if (count != nrpages) + error(INFO, "%s page count: %ld nrpages: %ld\n", +@@ -4217,12 +4217,46 @@ struct do_xarray_info { + ulong maxcount; + ulong count; + void *data; ++ ulong (*update_count)(ulong); + }; ++ ++static ulong ++folio_update_count(ulong slot) ++{ ++ return 1 << folio_order(slot); ++} ++ ++static uint folio_xarray_update_off(ulong node, uint height, char *path, ulong index, ++ ulong slot, uint off, ulong shift, struct xarray_ops *ops, bool *should_continue) ++{ ++ uint order; ++ ++ *should_continue = false; ++ ++ if (height == 1) { ++ order = folio_order(slot) % XA_CHUNK_SHIFT; ++ return 1 << order; ++ } ++ ++ /* height > 1 */ ++ if ((slot & XARRAY_TAG_MASK) == 0) { ++ ops->entry(node, slot, path, index | off, ops->private); ++ *should_continue = true; ++ order = folio_order(slot) % XA_CHUNK_SHIFT; ++ return 1 << order; ++ } ++ return 1; ++} ++ + static void do_xarray_count(ulong node, ulong slot, const char *path, + ulong index, void *private) + { + struct do_xarray_info *info = private; +- info->count++; ++ ++ if (info->update_count) ++ info->count += info->update_count(slot); ++ else ++ info->count++; + } + static void do_xarray_search(ulong node, ulong slot, const char *path, + ulong index, void *private) +@@ -4239,8 +4273,9 @@ static void do_xarray_dump(ulong node, ulong slot, const char *path, + ulong index, void *private) + { + struct do_xarray_info *info = private; ++ + fprintf(fp, "[%ld] %lx\n", index, slot); +- info->count++; ++ do_xarray_count(node, slot, path, index, private); + } + static void do_xarray_gather(ulong node, ulong slot, const char *path, + ulong index, void *private) +@@ -4274,7 +4309,8 @@ static void do_xarray_dump_cb(ulong node, ulong slot, const char *path, + "operation failed: entry: %ld item: %lx\n", + info->count, slot); + } +- info->count++; ++ ++ do_xarray_count(node, slot, path, index, private); + } + + /* +@@ -4318,7 +4354,12 @@ do_xarray(ulong root, int flag, struct list_pair *xp) + .private = &info, + }; + +- switch (flag) ++ if (flag & XARRAY_TYPE_PAGE_CACHE) { ++ info.update_count = folio_update_count; ++ ops.update_off = folio_xarray_update_off; ++ } ++ ++ switch (flag & 0x7) + { + case XARRAY_COUNT: + ops.entry = do_xarray_count; +diff --git a/tools.c b/tools.c +index 69250c48fa91..7dc11aedf56b 100644 +--- a/tools.c ++++ b/tools.c +@@ -4728,7 +4728,7 @@ error_height: + return -1; + } + +-static ulong XA_CHUNK_SHIFT = UNINITIALIZED; ++ulong XA_CHUNK_SHIFT = UNINITIALIZED; + static ulong XA_CHUNK_SIZE = UNINITIALIZED; + static ulong XA_CHUNK_MASK = UNINITIALIZED; + +@@ -4737,21 +4737,32 @@ do_xarray_iter(ulong node, uint height, char *path, + ulong index, struct xarray_ops *ops) + { + uint off; ++ uint update_off; ++ bool should_continue; + + if (!hq_enter(node)) + error(FATAL, + "\nduplicate tree node: %lx\n", node); + +- for (off = 0; off < XA_CHUNK_SIZE; off++) { ++ for (off = 0; off < XA_CHUNK_SIZE; off += update_off) { + ulong slot; + ulong shift = (height - 1) * XA_CHUNK_SHIFT; + ++ update_off = 1; ++ + readmem(node + OFFSET(xa_node_slots) + + sizeof(void *) * off, KVADDR, &slot, sizeof(void *), + "xa_node.slots[off]", FAULT_ON_ERROR); + if (!slot) + continue; + ++ if (ops->update_off) { ++ update_off = ops->update_off(node, height, path, index, ++ slot, off, shift, ops, &should_continue); ++ if (should_continue) ++ continue; ++ } ++ + if ((slot & XARRAY_TAG_MASK) == XARRAY_TAG_INTERNAL) + slot &= ~XARRAY_TAG_INTERNAL; + +-- +2.54.0 + diff --git a/0014-add-files-n-command-for-an-inode.patch b/0014-add-files-n-command-for-an-inode.patch new file mode 100644 index 0000000..faed642 --- /dev/null +++ b/0014-add-files-n-command-for-an-inode.patch @@ -0,0 +1,216 @@ +From 28889183d3f55c581b0810aa8bdac31e20bbf364 Mon Sep 17 00:00:00 2001 +From: Huang Shijie +Date: Mon, 18 May 2026 19:31:40 +0800 +Subject: [PATCH 14/39] add "files -n" command for an inode + +In the NUMA machine, it is useful to know the memory distribution of +an inode page cache: + How many pages in the node 0? + How many pages in the node 1? + +Add "files -n" command to get the memory distribution information: + 1.) Add new argument for dump_inode_page_cache_info() + 2.) make page_to_nid() a global function. + 3.) Add summary_inode_page() to check each page's node + information. + 4.) Use print_inode_summary_info() to print the + memory distribution information of an inode. + +Signed-off-by: Huang Shijie +--- + defs.h | 1 + + filesys.c | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++----- + help.c | 24 +++++++++++++++++++++- + memory.c | 4 ++-- + 4 files changed, 81 insertions(+), 8 deletions(-) + +diff --git a/defs.h b/defs.h +index e7e97e7d80b2..6373ee1831af 100644 +--- a/defs.h ++++ b/defs.h +@@ -5948,6 +5948,7 @@ int dump_inode_page(ulong); + ulong valid_section_nr(ulong); + void display_memory_from_file_offset(ulonglong, long, void *); + void swap_info_init(void); ++int page_to_nid(ulong); + + /* + * filesys.c +diff --git a/filesys.c b/filesys.c +index 35d55a543739..7ff47876bbb7 100644 +--- a/filesys.c ++++ b/filesys.c +@@ -49,7 +49,7 @@ static int match_file_string(char *, char *, char *); + static ulong get_root_vfsmount(char *); + static void check_live_arch_mismatch(void); + static long get_inode_nrpages(ulong); +-static void dump_inode_page_cache_info(ulong); ++static void dump_inode_page_cache_info(ulong, void *callback); + + #define DENTRY_CACHE (20) + #define INODE_CACHE (20) +@@ -2240,8 +2240,38 @@ get_inode_nrpages(ulong i_mapping) + return nrpages; + } + ++/* Used to collect the numa information for an inode */ ++static ulong *numa_node; ++ + static void +-dump_inode_page_cache_info(ulong inode) ++print_inode_summary_info(void) ++{ ++ int i; ++ ++ fprintf(fp, " NODE PAGES\n"); ++ for (i = 0; i < vt->numnodes; i++) ++ fprintf(fp, " %2d %8ld\n", i, numa_node[i]); ++} ++ ++static int ++summary_inode_page(ulong page) ++{ ++ int node; ++ ++ if (!is_page_ptr(page, NULL)) ++ error(FATAL, "Invalid inode page(0x%lx)\n", page); ++ ++ node = page_to_nid(page); ++ if (node < 0 || node >= vt->numnodes) ++ error(FATAL, "Invalid node(%d) for page(0x%lx)\n", node, page); ++ ++ numa_node[node] += 1 << folio_order(page); ++ ++ return 1; ++} ++ ++static void ++dump_inode_page_cache_info(ulong inode, void *callback) + { + char *inode_buf; + ulong i_mapping, nrpages, root_rnode, xarray, count; +@@ -2284,7 +2314,7 @@ dump_inode_page_cache_info(ulong inode) + root_rnode = i_mapping + OFFSET(address_space_page_tree); + + lp.index = 0; +- lp.value = (void *)&dump_inode_page; ++ lp.value = callback; + + if (root_rnode) + count = do_radix_tree(root_rnode, RADIX_TREE_DUMP_CB, &lp); +@@ -2324,7 +2354,7 @@ cmd_files(void) + ref = NULL; + refarg = NULL; + +- while ((c = getopt(argcnt, args, "d:R:p:c")) != EOF) { ++ while ((c = getopt(argcnt, args, "d:n:R:p:c")) != EOF) { + switch(c) + { + case 'R': +@@ -2343,11 +2373,31 @@ cmd_files(void) + display_dentry_info(value); + return; + ++ case 'n': ++ if (VALID_MEMBER(address_space_page_tree) && ++ VALID_MEMBER(inode_i_mapping)) { ++ value = htol(optarg, FAULT_ON_ERROR, NULL); ++ ++ /* Allocate the array for this inode */ ++ numa_node = (ulong *)GETBUF(sizeof(ulong) * vt->numnodes); ++ BZERO(numa_node, sizeof(ulong) * vt->numnodes); ++ ++ dump_inode_page_cache_info(value, (void *)&summary_inode_page); ++ ++ /* Print out the NUMA node information for this inode */ ++ print_inode_summary_info(); ++ ++ FREEBUF(numa_node); ++ numa_node = NULL; ++ } else ++ option_not_supported('n'); ++ return; ++ + case 'p': + if (VALID_MEMBER(address_space_page_tree) && + VALID_MEMBER(inode_i_mapping)) { + value = htol(optarg, FAULT_ON_ERROR, NULL); +- dump_inode_page_cache_info(value); ++ dump_inode_page_cache_info(value, (void *)&dump_inode_page); + } else + option_not_supported('p'); + return; +diff --git a/help.c b/help.c +index 810075b97f35..5cd5c30e99a3 100644 +--- a/help.c ++++ b/help.c +@@ -7900,7 +7900,7 @@ NULL + char *help_files[] = { + "files", + "open files", +-"[-d dentry] | [-p inode] | [-c] [-R reference] [pid | taskp] ... ", ++"[-d dentry] | [-p inode] | [-n inode]|[-c] [-R reference] [pid | taskp] ... ", + " This command displays information about open files of a context.", + " It prints the context's current root directory and current working", + " directory, and then for each open file descriptor it prints a pointer", +@@ -7915,6 +7915,8 @@ char *help_files[] = { + " super block, file type, and full pathname.", + " -p inode given a hexadecimal inode address, dump all of its pages", + " that are in the page cache.", ++" -n inode given a hexadecimal inode address, check all the pages", ++" in the page cache, and display a NUMA node distribution.", + " -c for each open file descriptor, prints a pointer to its", + " inode, a pointer to the inode's i_mapping address_space", + " structure, the number of pages of the inode that are in", +@@ -8024,6 +8026,26 @@ char *help_files[] = { + " ca1ddde0 2eeef000 f59b91ac 3 2 82c referenced,uptodate,lru,private", + " ca36b300 3b598000 f59b91ac 4 2 82c referenced,uptodate,lru,private", + " ca202680 30134000 f59b91ac 5 2 82c referenced,uptodate,lru,private", ++" ", ++" For the inode at address ffff8898857c55a8, display the NUMA node", ++" distribution of its pages that are in the page cache:", ++" %s> files -n ffff8898857c55a8", ++" INODE NRPAGES", ++" ffff8898857c55a8 51748", ++" ", ++" NODE PAGES", ++" 0 51536", ++" 1 0", ++" 2 0", ++" 3 0", ++" 4 0", ++" 5 212", ++" 6 0", ++" 7 0", ++" 8 0", ++" 9 0", ++" 10 0", ++" 11 0", + " ", + NULL + }; +diff --git a/memory.c b/memory.c +index 502f8fdd0ea7..38c6a139e984 100644 +--- a/memory.c ++++ b/memory.c +@@ -301,7 +301,7 @@ static int dump_vm_event_state(void); + static int dump_page_states(void); + static int generic_read_dumpfile(ulonglong, void *, long, char *, ulong); + static int generic_write_dumpfile(ulonglong, void *, long, char *, ulong); +-static int page_to_nid(ulong); ++int page_to_nid(ulong); + static int get_kmem_cache_list(ulong **); + static int get_kmem_cache_root_list(ulong **); + static int get_kmem_cache_child_list(ulong **, ulong); +@@ -20093,7 +20093,7 @@ is_kmem_cache_addr_common(ulong vaddr, char *kbuf) + /* + * Kernel-config-neutral page-to-node evaluator. + */ +-static int ++int + page_to_nid(ulong page) + { + int i; +-- +2.54.0 + diff --git a/0015-x86_64-Fix-bt-command-for-noreturn-functions.patch b/0015-x86_64-Fix-bt-command-for-noreturn-functions.patch new file mode 100644 index 0000000..feefbb2 --- /dev/null +++ b/0015-x86_64-Fix-bt-command-for-noreturn-functions.patch @@ -0,0 +1,178 @@ +From 7b3f6e0f60be1dd2d4c1b20175be96137cf61438 Mon Sep 17 00:00:00 2001 +From: Kazuhito Hagio +Date: Fri, 29 May 2026 05:00:03 +0000 +Subject: [PATCH 15/39] x86_64: Fix "bt" command for noreturn functions + +On x86_64, the "bt" command resolves saved return addresses with +value_search(textaddr). However, a return address is the instruction +pointer after the call, not the call site itself. + +This becomes a problem when the caller ends with a call to a noreturn +function. In that case, the saved return address can match the start +address of the following symbol, and "bt" loses track of the call chain +and this can lead to very long session initialization. + +The same issue also affects symbol+offset formatting, line number +lookup, and ORC-based frame size resolution. + +Fix it by resolving normal backtrace return addresses with textaddr-1, +while keeping exact textaddr handling for real RIP values saved in +exception frames. Add value_to_symstr_trace() so the displayed +symbol+offset still reflects the original return address value. + +Suggested-by: Kosuke Tatsukawa +Signed-off-by: Kazuhito Hagio +--- + defs.h | 1 + + symbols.c | 24 +++++++++++++++++++++--- + x86_64.c | 31 ++++++++++++++++++++++++++----- + 3 files changed, 48 insertions(+), 8 deletions(-) + +diff --git a/defs.h b/defs.h +index 6373ee1831af..4a42bc962817 100644 +--- a/defs.h ++++ b/defs.h +@@ -5807,6 +5807,7 @@ struct syment *prev_symbol(char *, struct syment *); + void get_symbol_data(char *, long, void *); + int try_get_symbol_data(char *, long, void *); + char *value_to_symstr(ulong, char *, ulong); ++char *value_to_symstr_trace(ulong, char *, ulong); + char *value_symbol(ulong); + ulong symbol_value(char *); + ulong symbol_value_module(char *, char *); +diff --git a/symbols.c b/symbols.c +index c0285058c5cb..78e400ba3756 100644 +--- a/symbols.c ++++ b/symbols.c +@@ -104,6 +104,7 @@ static void free_structure(struct struct_elem *); + static unsigned char is_right_brace(const char *); + static struct struct_elem *find_node(struct struct_elem *, char *); + static void dump_node(struct struct_elem *, char *, unsigned char, unsigned char); ++static char *_value_to_symstr(ulong value, char *buf, ulong radix, int trace); + + static int module_mem_type(ulong, struct load_module *); + static ulong module_mem_end(ulong, struct load_module *); +@@ -5973,14 +5974,25 @@ generic_machdep_value_to_symbol(ulong value, ulong *offset) + return NULL; + } + ++char * ++value_to_symstr(ulong value, char *buf, ulong radix) ++{ ++ return _value_to_symstr(value, buf, radix, 0); ++} ++ ++char * ++value_to_symstr_trace(ulong value, char *buf, ulong radix) ++{ ++ return _value_to_symstr(value, buf, radix, 1); ++} + + /* + * For a given value, format a string containing the nearest symbol name + * plus the offset if appropriate. Display the offset in the specified + * radix (10 or 16) -- if it's 0, set it to the current pc->output_radix. + */ +-char * +-value_to_symstr(ulong value, char *buf, ulong radix) ++static char * ++_value_to_symstr(ulong value, char *buf, ulong radix, int trace) + { + struct syment *sp; + ulong offset; +@@ -5996,7 +6008,13 @@ value_to_symstr(ulong value, char *buf, ulong radix) + if ((radix != 10) && (radix != 16)) + radix = 16; + +- if ((sp = value_search(value, &offset))) { ++ if (trace) { ++ sp = value_search(value-1, &offset); ++ offset++; ++ } else ++ sp = value_search(value, &offset); ++ ++ if (sp) { + if (offset) + sprintf(buf, radix == 16 ? "%s+0x%lx" : "%s+%ld", + sp->name, offset); +diff --git a/x86_64.c b/x86_64.c +index b2cddbf8ba3d..ff283ed68191 100644 +--- a/x86_64.c ++++ b/x86_64.c +@@ -3229,14 +3229,23 @@ x86_64_print_stack_entry(struct bt_info *bt, FILE *ofp, int level, + if (!(bt->flags & BT_SAVE_EFRAME_IP)) + bt->eframe_ip = 0; + offset = 0; +- sp = value_search(text, &offset); ++ if (bt->flags & BT_SAVE_EFRAME_IP) ++ sp = value_search(text, &offset); ++ else { ++ sp = value_search(text-1, &offset); ++ offset++; ++ } + if (!sp) + return BACKTRACE_ENTRY_IGNORED; + + name = sp->name; + + if (offset && (bt->flags & BT_SYMBOL_OFFSET)) +- name_plus_offset = value_to_symstr(text, buf2, bt->radix); ++ if (bt->flags & BT_SAVE_EFRAME_IP) ++ name_plus_offset = value_to_symstr(text, buf2, bt->radix); ++ else ++ /* text-1 is used in the function */ ++ name_plus_offset = value_to_symstr_trace(text, buf2, bt->radix); + else + name_plus_offset = NULL; + +@@ -3337,7 +3346,10 @@ x86_64_print_stack_entry(struct bt_info *bt, FILE *ofp, int level, + fprintf(ofp, "\n"); + + if (bt->flags & BT_LINE_NUMBERS) { +- get_line_number(text, buf1, FALSE); ++ if (bt->flags & BT_SAVE_EFRAME_IP) ++ get_line_number(text, buf1, FALSE); ++ else ++ get_line_number(text-1, buf1, FALSE); + if (strlen(buf1)) + fprintf(ofp, " %s\n", buf1); + } +@@ -3864,8 +3876,10 @@ in_exception_stack: + } + + level++; ++ bt->flags |= BT_SAVE_EFRAME_IP; + if ((framesize = x86_64_get_framesize(bt, bt->instptr, rsp, NULL)) >= 0) + rsp += framesize; ++ bt->flags &= ~BT_SAVE_EFRAME_IP; + } + } + +@@ -8811,7 +8825,13 @@ x86_64_get_framesize(struct bt_info *bt, ulong textaddr, ulong rsp, char *stack_ + return 0; + } + +- if (!(sp = value_search(textaddr, &offset))) { ++ if (bt->flags & BT_SAVE_EFRAME_IP) ++ sp = value_search(textaddr, &offset); ++ else { ++ sp = value_search(textaddr-1, &offset); ++ offset++; ++ } ++ if (!sp) { + if (!(bt->flags & BT_FRAMESIZE_DEBUG)) + bt->flags |= BT_FRAMESIZE_DISABLE; + return 0; +@@ -8887,7 +8907,8 @@ x86_64_get_framesize(struct bt_info *bt, ulong textaddr, ulong rsp, char *stack_ + if ((sp->value >= kt->init_begin) && (sp->value < kt->init_end)) + return 0; + +- if ((machdep->flags & ORC) && (korc = orc_find(textaddr))) { ++ if ((machdep->flags & ORC) && ++ (korc = orc_find(bt->flags & BT_SAVE_EFRAME_IP ? textaddr : textaddr-1))) { + if (CRASHDEBUG(1)) { + struct ORC_data *orc = &machdep->machspec->orc; + fprintf(fp, +-- +2.54.0 + diff --git a/0016-x86_64-Fix-bt-command-to-use-correct-ORC-register-va.patch b/0016-x86_64-Fix-bt-command-to-use-correct-ORC-register-va.patch new file mode 100644 index 0000000..27b6add --- /dev/null +++ b/0016-x86_64-Fix-bt-command-to-use-correct-ORC-register-va.patch @@ -0,0 +1,100 @@ +From d0ee428664f90beaf498fa0edc129cdead204abc Mon Sep 17 00:00:00 2001 +From: Kazuhito Hagio +Date: Mon, 1 Jun 2026 05:38:11 +0000 +Subject: [PATCH 16/39] x86_64: Fix "bt" command to use correct ORC register + values on Linux 7.1 and later + +Kernel commit 1735858caa4b ("objtool/x86: Reorder ORC register numbering") +changed the ORC register numbering. Without the patch, crash can interpret ORC +entry incorrectly and the "bt" command may generate broken backtraces on Linux +7.1 and later kernels like this: + + crash> bt 1 + PID: 1 TASK: ffff8ab0009cd100 CPU: 2 COMMAND: "systemd" + #0 [ffffd2218003b9c8] __schedule at ffffffffaa9d862b + #1 [ffffd2218003ba20] schedule at ffffffffaa9d8993 + #2 [ffffd2218003ba30] schedule_hrtimeout_range_clock at ffffffffaa9df77b + #3 [ffffd2218003bab0] ep_poll at ffffffffaa1231e4 + #4 [ffffd2218003bb50] do_epoll_wait at ffffffffaa123272 + #5 [ffffd2218003bb88] __x64_sys_epoll_wait at ffffffffaa123b1f + #6 [ffffd2218003bbd8] do_syscall_64 at ffffffffaa9cca6c + #7 [ffffd2218003bc58] __memcg_slab_free_hook at ffffffffaa079da3 + #8 [ffffd2218003bcf0] __memcg_slab_free_hook at ffffffffaa079da3 + #9 [ffffd2218003bd50] __x64_sys_gettid at ffffffffa9ce1656 + #10 [ffffd2218003bd58] do_syscall_64 at ffffffffaa9ccaa4 + #11 [ffffd2218003bdc0] update_cfs_rq_load_avg at ffffffffa9d1bf59 + #12 [ffffd2218003be00] __update_blocked_fair at ffffffffa9d214b8 + #13 [ffffd2218003be70] sched_clock at ffffffffa9c460dc + #14 [ffffd2218003be78] sched_clock_cpu at ffffffffa9d4aeab + #15 [ffffd2218003be98] irqtime_account_irq at ffffffffa9d3af0d + #16 [ffffd2218003bec0] handle_softirqs at ffffffffa9cce5ac + #17 [ffffd2218003bf40] entry_SYSCALL_64_after_hwframe at ffffffffa9a0012b + +Fix this by making ORC_REG_SP and ORC_REG_PREV_SP depend on kernel version, as +no other way was found. + +Signed-off-by: Kazuhito Hagio +--- + defs.h | 6 ++++-- + x86_64.c | 11 +++++++++++ + 2 files changed, 15 insertions(+), 2 deletions(-) + +diff --git a/defs.h b/defs.h +index 4a42bc962817..a4f70b773cd7 100644 +--- a/defs.h ++++ b/defs.h +@@ -6650,6 +6650,8 @@ struct ORC_data { + orc_entry orc_entry_data; + int has_signal; + int has_end; ++ int reg_sp; ++ int reg_prev_sp; + }; + + #define ORC_TYPE_CALL ((machdep->flags & ORC_6_4) ? 2 : 0) +@@ -6660,11 +6662,11 @@ struct ORC_data { + #define UNWIND_HINT_TYPE_RESTORE 4 + + #define ORC_REG_UNDEFINED 0 +-#define ORC_REG_PREV_SP 1 ++#define ORC_REG_PREV_SP (machdep->machspec->orc.reg_prev_sp) + #define ORC_REG_DX 2 + #define ORC_REG_DI 3 + #define ORC_REG_BP 4 +-#define ORC_REG_SP 5 ++#define ORC_REG_SP (machdep->machspec->orc.reg_sp) + #define ORC_REG_R10 6 + #define ORC_REG_R13 7 + #define ORC_REG_BP_INDIRECT 8 +diff --git a/x86_64.c b/x86_64.c +index ff283ed68191..55648697baf3 100644 +--- a/x86_64.c ++++ b/x86_64.c +@@ -999,6 +999,8 @@ x86_64_dump_machdep_table(ulong arg) + fprintf(fp, " module_ORC: %s\n", ms->orc.module_ORC ? "TRUE" : "FALSE"); + fprintf(fp, " has_signal: %s\n", ms->orc.has_signal ? "TRUE" : "FALSE"); + fprintf(fp, " has_end: %s\n", ms->orc.has_end ? "TRUE" : "FALSE"); ++ fprintf(fp, " reg_sp: %d\n", ms->orc.reg_sp); ++ fprintf(fp, " reg_prev_sp: %d\n", ms->orc.reg_prev_sp); + fprintf(fp, " lookup_num_blocks: %d\n", ms->orc.lookup_num_blocks); + fprintf(fp, " __start_orc_unwind_ip: %lx\n", ms->orc.__start_orc_unwind_ip); + fprintf(fp, " __stop_orc_unwind_ip: %lx\n", ms->orc.__stop_orc_unwind_ip); +@@ -6734,6 +6736,15 @@ x86_64_ORC_init(void) + if (orc->has_signal && !orc->has_end) + machdep->flags |= ORC_6_4; + ++ /* See kernel commit 1735858caa4b */ ++ if (THIS_KERNEL_VERSION >= LINUX(7,1,0)) { ++ ORC_REG_SP = 3; ++ ORC_REG_PREV_SP = 8; ++ } else { ++ ORC_REG_SP = 5; ++ ORC_REG_PREV_SP = 1; ++ } ++ + machdep->flags |= ORC; + } + +-- +2.54.0 + diff --git a/0017-Fix-kmem-i-option-to-display-swap-usage-on-Linux-6.1.patch b/0017-Fix-kmem-i-option-to-display-swap-usage-on-Linux-6.1.patch new file mode 100644 index 0000000..6c147ce --- /dev/null +++ b/0017-Fix-kmem-i-option-to-display-swap-usage-on-Linux-6.1.patch @@ -0,0 +1,38 @@ +From c894f05d3cdc5d3e61775c3f67bd6a0a24362a92 Mon Sep 17 00:00:00 2001 +From: Kazuhito Hagio +Date: Wed, 3 Jun 2026 07:12:57 +0000 +Subject: [PATCH 17/39] Fix "kmem -i" option to display swap usage on Linux + 6.18 and later + +Kernel commit 8578e0c00dcf ("mm, swap: use the swap table to track the +swap count"), which is contained in Linux 6.18 and later kernels, +removed swapper_spaces symbol. + +As a result, "kmem -i" skips swap usage output because the existing +check only looks for swapper_space/swapper_spaces. + +Also check for the swap_info symbol so dump_swap_info() is called on +newer kernels as well. + +Signed-off-by: Kazuhito Hagko +--- + memory.c | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/memory.c b/memory.c +index 38c6a139e984..15946c58eaf2 100644 +--- a/memory.c ++++ b/memory.c +@@ -8871,7 +8871,8 @@ dump_kmeminfo(struct meminfo *mi) + * get swap data from dump_swap_info(). + */ + fprintf(fp, "\n"); +- if (symbol_exists("swapper_space") || symbol_exists("swapper_spaces")) { ++ if (symbol_exists("swap_info") || ++ symbol_exists("swapper_space") || symbol_exists("swapper_spaces")) { + if (dump_swap_info(RETURN_ON_ERROR, &totalswap_pages, + &totalused_pages)) { + fprintf(fp, "%13s %7ld %11s ----\n", +-- +2.54.0 + diff --git a/0018-Fix-swap-command-on-Linux-7.1-and-later.patch b/0018-Fix-swap-command-on-Linux-7.1-and-later.patch new file mode 100644 index 0000000..5f3d6e4 --- /dev/null +++ b/0018-Fix-swap-command-on-Linux-7.1-and-later.patch @@ -0,0 +1,51 @@ +From fdb94ed20ac85e8354224b0d691af342f4b63922 Mon Sep 17 00:00:00 2001 +From: Kazuhito Hagio +Date: Wed, 3 Jun 2026 07:12:58 +0000 +Subject: [PATCH 18/39] Fix "swap" command on Linux 7.1 and later + +Kernel commit 0d6af9bcf383 ("mm, swap: use the swap table to track the +swap count"), which is contained in Linux 7.1 and later kernels, removed +swap_info_struct.swap_map member. + +As a result, the "swap" command and "kmem -i" option fail with the +following error: + + swap: invalid structure member offset: swap_info_struct_swap_map + FILE: memory.c LINE: 16293 FUNCTION: dump_swap_info() + +Fix it by referencing swap_info_struct.swap_map only for SWAPINFO_V1, +where it is actually needed. Newer kernels no longer have that member, +and the command can use the existing inuse_pages handling instead. + +Signed-off-by: Kazuhito Hagio +--- + memory.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/memory.c b/memory.c +index 15946c58eaf2..3f3aa274ab5a 100644 +--- a/memory.c ++++ b/memory.c +@@ -16290,9 +16290,6 @@ dump_swap_info(ulong swapflags, ulong *totalswap_pages, ulong *totalused_pages) + OFFSET(swap_info_struct_inuse_pages)); + } + +- swap_map = ULONG(vt->swap_info_struct + +- OFFSET(swap_info_struct_swap_map)); +- + if (swap_file) { + if (VALID_MEMBER(swap_info_struct_swap_vfsmnt)) { + vfsmnt = ULONG(vt->swap_info_struct + +@@ -16322,6 +16319,9 @@ dump_swap_info(ulong swapflags, ulong *totalswap_pages, ulong *totalused_pages) + if (vt->flags & SWAPINFO_V1) { + smap = (ushort *)GETBUF(sizeof(ushort) * max); + ++ swap_map = ULONG(vt->swap_info_struct + ++ OFFSET(swap_info_struct_swap_map)); ++ + if (!readmem(swap_map, KVADDR, smap, + sizeof(ushort) * max, "swap_info swap_map data", + RETURN_ON_ERROR|QUIET)) { +-- +2.54.0 + diff --git a/0019-LoongArch64-Fix-CPU-registers-reading-from-dump-note.patch b/0019-LoongArch64-Fix-CPU-registers-reading-from-dump-note.patch new file mode 100644 index 0000000..81abe43 --- /dev/null +++ b/0019-LoongArch64-Fix-CPU-registers-reading-from-dump-note.patch @@ -0,0 +1,115 @@ +From 32ac79ead77839e6a87657edfdccffa943c6ea19 Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Thu, 4 Jun 2026 19:52:40 +0800 +Subject: [PATCH 19/39] LoongArch64: Fix CPU registers reading from dump notes + +LoongArch stores the register set exported in NT_PRSTATUS as an +elf_gregset_t. The special registers are laid out as orig_a0, csr_era, +csr_badvaddr, csr_crmd, csr_prmd, csr_euen, csr_ecfg and csr_estat +after the 32 GPRs. + +The old crash-local register layout put csr_crmd at the first +special-register slot and placed epc near the end. That made help -r +and active-task unwinding read nonsensical CSR values from dump notes. + +Match the local register structure and indexes to the kernel +pt_regs/elf_gregset_t layout. Also handle modern kernels where +crash_notes may refer to the per-cpu note storage directly, and use +NETDUMP ELF notes for libvirt/QEMU dump files when crash_notes does not +provide usable data. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 38 +++++++++++++++++++++++--------------- + 1 file changed, 23 insertions(+), 15 deletions(-) + +diff --git a/loongarch64.c b/loongarch64.c +index 6437c4aa4656..5adb7d8038db 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -18,20 +18,23 @@ + #include + #include "defs.h" + +-/* from arch/loongarch/include/asm/ptrace.h */ ++/* ++ * This matches the beginning of both kernel struct pt_regs and the ++ * elf_gregset_t layout exported in NT_PRSTATUS notes. ++ */ + struct loongarch64_pt_regs { + /* Saved main processor registers. */ + unsigned long regs[32]; + + /* Saved special registers. */ ++ unsigned long orig_a0; ++ unsigned long csr_epc; ++ unsigned long csr_badvaddr; + unsigned long csr_crmd; + unsigned long csr_prmd; + unsigned long csr_euen; + unsigned long csr_ecfg; + unsigned long csr_estat; +- unsigned long csr_epc; +- unsigned long csr_badvaddr; +- unsigned long orig_a0; + }; + + struct loongarch64_unwind_frame { +@@ -108,13 +111,14 @@ typedef struct { ulong pte; } pte_t; + #define LOONGARCH64_EF_RA 1 + #define LOONGARCH64_EF_SP 3 + #define LOONGARCH64_EF_FP 22 +-#define LOONGARCH64_EF_CSR_EPC 32 +-#define LOONGARCH64_EF_CSR_BADVADDR 33 +-#define LOONGARCH64_EF_CSR_CRMD 34 +-#define LOONGARCH64_EF_CSR_PRMD 35 +-#define LOONGARCH64_EF_CSR_EUEN 36 +-#define LOONGARCH64_EF_CSR_ECFG 37 +-#define LOONGARCH64_EF_CSR_ESTAT 38 ++#define LOONGARCH64_EF_ORIG_A0 32 ++#define LOONGARCH64_EF_CSR_EPC 33 ++#define LOONGARCH64_EF_CSR_BADVADDR 34 ++#define LOONGARCH64_EF_CSR_CRMD 35 ++#define LOONGARCH64_EF_CSR_PRMD 36 ++#define LOONGARCH64_EF_CSR_EUEN 37 ++#define LOONGARCH64_EF_CSR_ECFG 38 ++#define LOONGARCH64_EF_CSR_ESTAT 39 + + static struct machine_specific loongarch64_machine_specific = { 0 }; + +@@ -911,8 +915,12 @@ loongarch64_get_crash_notes(void) + /* + * Add __per_cpu_offset for each cpu to form the pointer to the notes + */ +- for (i = 0; i < kt->cpus; i++) +- notes_ptrs[i] = notes_ptrs[kt->cpus-1] + kt->__per_cpu_offset[i]; ++ for (i = 0; i < kt->cpus; i++) { ++ if (IS_KVADDR(notes_ptrs[kt->cpus-1])) ++ notes_ptrs[i] = notes_ptrs[kt->cpus-1] + kt->__per_cpu_offset[i]; ++ else ++ notes_ptrs[i] = crash_notes + kt->__per_cpu_offset[i]; ++ } + } + + buf = GETBUF(SIZE(note_buf)); +@@ -1009,7 +1017,7 @@ loongarch64_get_elf_notes(void) + struct machine_specific *ms = machdep->machspec; + int i; + +- if (!DISKDUMP_DUMPFILE() && !KDUMP_DUMPFILE()) ++ if (!DISKDUMP_DUMPFILE() && !KDUMP_DUMPFILE() && !NETDUMP_DUMPFILE()) + return FALSE; + + panic_task_regs = calloc(kt->cpus, sizeof(*panic_task_regs)); +@@ -1022,7 +1030,7 @@ loongarch64_get_elf_notes(void) + + if (DISKDUMP_DUMPFILE()) + note = diskdump_get_prstatus_percpu(i); +- else if (KDUMP_DUMPFILE()) ++ else if (KDUMP_DUMPFILE() || NETDUMP_DUMPFILE()) + note = netdump_get_prstatus_percpu(i); + + if (!note) { +-- +2.54.0 + diff --git a/0020-LoongArch64-Fix-pt_regs-initialization-for-active-ta.patch b/0020-LoongArch64-Fix-pt_regs-initialization-for-active-ta.patch new file mode 100644 index 0000000..e5d9516 --- /dev/null +++ b/0020-LoongArch64-Fix-pt_regs-initialization-for-active-ta.patch @@ -0,0 +1,31 @@ +From a6817d475c753f44d10bd5709be1d455768d496a Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Thu, 4 Jun 2026 19:52:41 +0800 +Subject: [PATCH 20/39] LoongArch64: Fix pt_regs initialization for active + tasks + +The offset of pt_regs.regs was missing, which prevented crash from +falling back to real-time CPU registers for the active task. Initialize +it correctly during architecture initialization. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/loongarch64.c b/loongarch64.c +index 5adb7d8038db..92481f3235b6 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -761,6 +761,8 @@ loongarch64_stackframe_init(void) + + ASSIGN_OFFSET(task_struct_thread_reg03) = + task_struct_thread + thread_reg03_sp; ++ MEMBER_OFFSET_INIT(pt_regs_regs, "pt_regs", "regs"); ++ STRUCT_SIZE_INIT(pt_regs, "pt_regs"); + ASSIGN_OFFSET(task_struct_thread_reg01) = + task_struct_thread + thread_reg01_ra; + +-- +2.54.0 + diff --git a/0021-LoongArch64-Support-backtracing-across-exception-bou.patch b/0021-LoongArch64-Support-backtracing-across-exception-bou.patch new file mode 100644 index 0000000..0c57f0e --- /dev/null +++ b/0021-LoongArch64-Support-backtracing-across-exception-bou.patch @@ -0,0 +1,63 @@ +From f3a02a49653b179a43b07a0befb82934af8ac542 Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Thu, 4 Jun 2026 19:52:42 +0800 +Subject: [PATCH 21/39] LoongArch64: Support backtracing across exception + boundaries + +LoongArch exception and syscall entry code saves pt_regs on the kernel +stack before calling the C handlers. When a backtrace reaches one of +these entry symbols, crash needs to recover the saved pt_regs and +continue unwinding from the interrupted context. + +Recognize the LoongArch entry symbols that leave a saved pt_regs at sp, +including syscall, exception, interrupt-vector and TLB page-fault paths +used by current upstream kernels and existing Loongnix variants. This +prevents backtraces from stopping at the first entry frame and allows +crash to print the saved pt_regs context without treating ordinary +handler call frames as exception frames. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 26 +++++++++++++++++++++++++- + 1 file changed, 25 insertions(+), 1 deletion(-) + +diff --git a/loongarch64.c b/loongarch64.c +index 92481f3235b6..3ec184c908f8 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -707,7 +707,31 @@ loongarch64_is_exception_entry(struct syment *sym) + return STREQ(sym->name, "ret_from_exception") || + STREQ(sym->name, "ret_from_irq") || + STREQ(sym->name, "work_resched") || +- STREQ(sym->name, "handle_sys"); ++ STREQ(sym->name, "handle_sys") || ++ STREQ(sym->name, "handle_syscall") || ++ STREQ(sym->name, "handle_ade") || ++ STREQ(sym->name, "handle_ale") || ++ STREQ(sym->name, "handle_bce") || ++ STREQ(sym->name, "handle_bp") || ++ STREQ(sym->name, "handle_fpe") || ++ STREQ(sym->name, "handle_fpu") || ++ STREQ(sym->name, "handle_iasub") || ++ STREQ(sym->name, "handle_ib") || ++ STREQ(sym->name, "handle_int") || ++ STREQ(sym->name, "handle_ipe") || ++ STREQ(sym->name, "handle_lbt") || ++ STREQ(sym->name, "handle_lsx") || ++ STREQ(sym->name, "handle_mcheck") || ++ STREQ(sym->name, "handle_oac") || ++ STREQ(sym->name, "handle_parchk") || ++ STREQ(sym->name, "handle_reserved") || ++ STREQ(sym->name, "handle_ri") || ++ STREQ(sym->name, "handle_tlb_protect") || ++ STREQ(sym->name, "tlb_do_page_fault_0") || ++ STREQ(sym->name, "tlb_do_page_fault_1") || ++ STREQ(sym->name, "handle_vint") || ++ STREQ(sym->name, "handle_watch") || ++ STREQ(sym->name, "handle_lasx"); + } + + /* +-- +2.54.0 + diff --git a/0022-LoongArch64-Fix-stack-frame-loop-bounds-for-exceptio.patch b/0022-LoongArch64-Fix-stack-frame-loop-bounds-for-exceptio.patch new file mode 100644 index 0000000..53557fe --- /dev/null +++ b/0022-LoongArch64-Fix-stack-frame-loop-bounds-for-exceptio.patch @@ -0,0 +1,41 @@ +From 40145cc1f001e7eedff7753377fb8e4b9e8787f7 Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Thu, 4 Jun 2026 19:52:43 +0800 +Subject: [PATCH 22/39] LoongArch64: Fix stack frame loop bounds for exception + frames + +Remove the incorrect -32 padding from stacktop limits in loongarch64.c. +In modern LoongArch64 kernels, pt_regs rests exactly at the top of the +stack, and this -32 padding forces the backtrace loop to terminate +prematurely before reaching the exception frame. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/loongarch64.c b/loongarch64.c +index 3ec184c908f8..9980b35bdff1 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -461,7 +461,7 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA]; + } + +- while (current.sp <= bt->stacktop - 32 - SIZE(pt_regs)) { ++ while (current.sp <= bt->stacktop - SIZE(pt_regs)) { + struct syment *symbol = NULL; + ulong offset; + +@@ -501,7 +501,7 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + * * ret_from_kernel_thread + */ + if (symbol && !STRNEQ(symbol->name, "ret_from") && !offset && +- !current.ra && current.sp < bt->stacktop - 32 - SIZE(pt_regs)) { ++ !current.ra && current.sp < bt->stacktop - SIZE(pt_regs)) { + if (CRASHDEBUG(8)) + fprintf(fp, "zero offset at %s, try previous symbol\n", + symbol->name); +-- +2.54.0 + diff --git a/0023-LoongArch64-Add-dummy-eframe_search-to-avoid-bt-e-se.patch b/0023-LoongArch64-Add-dummy-eframe_search-to-avoid-bt-e-se.patch new file mode 100644 index 0000000..5e0a0db --- /dev/null +++ b/0023-LoongArch64-Add-dummy-eframe_search-to-avoid-bt-e-se.patch @@ -0,0 +1,52 @@ +From 523981942d8962b5246efa5c92f52abfb0f62e86 Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Thu, 4 Jun 2026 19:52:44 +0800 +Subject: [PATCH 23/39] LoongArch64: Add dummy eframe_search to avoid bt -e + segfault + +The lack of machdep->eframe_search initialization causes the crash tool +to segment fault when executing `bt -e`. Provide a stub function that +returns a fatal error indicating the option is not supported, matching +the behavior of other architectures that lack this feature. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/loongarch64.c b/loongarch64.c +index 9980b35bdff1..b0ebf8b1d99e 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -64,6 +64,7 @@ static void loongarch64_dump_backtrace_entry(struct bt_info *bt, + struct loongarch64_unwind_frame *previous, int level); + static void loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs); + static int loongarch64_is_exception_entry(struct syment *sym); ++static int loongarch64_eframe_search(struct bt_info *bt); + static void loongarch64_display_full_frame(struct bt_info *bt, + struct loongarch64_unwind_frame *current, + struct loongarch64_unwind_frame *previous); +@@ -1267,6 +1268,7 @@ loongarch64_init(int when) + machdep->kvtop = loongarch64_kvtop; + machdep->cmd_mach = loongarch64_cmd_mach; + machdep->back_trace = loongarch64_back_trace_cmd; ++ machdep->eframe_search = loongarch64_eframe_search; + machdep->get_stack_frame = loongarch64_get_stack_frame; + machdep->vmalloc_start = loongarch64_vmalloc_start; + machdep->processor_speed = loongarch64_processor_speed; +@@ -1389,6 +1391,12 @@ loongarch64_display_regs_from_elf_notes(int cpu, FILE *ofp) + regs->csr_euen); + } + ++static int ++loongarch64_eframe_search(struct bt_info *bt) ++{ ++ return error(FATAL, "-e option not supported on this architecture\n"); ++} ++ + #else /* !LOONGARCH64 */ + + #include "defs.h" +-- +2.54.0 + diff --git a/0024-LoongArch64-print-exception-return-address-as-ERA.patch b/0024-LoongArch64-print-exception-return-address-as-ERA.patch new file mode 100644 index 0000000..1f55ca1 --- /dev/null +++ b/0024-LoongArch64-print-exception-return-address-as-ERA.patch @@ -0,0 +1,49 @@ +From bf279fbd474f1f0c8783e0a0c5589d1bc0d7a16b Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Tue, 16 Jun 2026 20:47:41 +0800 +Subject: [PATCH 24/39] LoongArch64: print exception return address as ERA + +LoongArch saves the exception return address in CSR.ERA. The crash +LoongArch64 code currently labels this field as EPC in register and +exception-frame output. That is misleading when comparing crash output +with the architecture documentation and the kernel pt_regs layout. + +Rename the displayed field to ERA and fix the CSR.EUEN label while +updating the same output block. This is a display-only change and does +not alter how the saved exception return address is collected or used by +the unwinder. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/loongarch64.c b/loongarch64.c +index b0ebf8b1d99e..830c81826ebd 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -688,7 +688,7 @@ loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs) + } + + value_to_symstr(regs->csr_epc, buf, 16); +- fprintf(fp, " epc : %016lx %s\n", regs->csr_epc, buf); ++ fprintf(fp, " era : %016lx %s\n", regs->csr_epc, buf); + + value_to_symstr(regs->regs[LOONGARCH64_EF_RA], buf, 16); + fprintf(fp, " ra : %016lx %s\n", regs->regs[LOONGARCH64_EF_RA], buf); +@@ -1346,10 +1346,10 @@ loongarch64_display_regs_from_elf_notes(int cpu, FILE *ofp) + " R24: %016lx R25: %016lx R26: %016lx\n" + " R27: %016lx R28: %016lx R29: %016lx\n" + " R30: %016lx R31: %016lx\n" +- " CSR epc : %016lx CSR badv: %016lx\n" ++ " CSR era : %016lx CSR badv: %016lx\n" + " CSR crmd: %08lx CSR prmd: %08lx\n" + " CSR ecfg: %08lx CSR estat: %08lx\n" +- " CSR eneu: %08lx", ++ " CSR euen: %08lx", + regs->regs[LOONGARCH64_EF_R0], + regs->regs[LOONGARCH64_EF_R0 + 1], + regs->regs[LOONGARCH64_EF_R0 + 2], +-- +2.54.0 + diff --git a/0025-LoongArch64-resolve-relocated-exception-vector-addre.patch b/0025-LoongArch64-resolve-relocated-exception-vector-addre.patch new file mode 100644 index 0000000..b22eba3 --- /dev/null +++ b/0025-LoongArch64-resolve-relocated-exception-vector-addre.patch @@ -0,0 +1,152 @@ +From 488777428c96d73964e659a3a6c229502f85d74d Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Tue, 16 Jun 2026 20:47:42 +0800 +Subject: [PATCH 25/39] LoongArch64: resolve relocated exception vector + addresses + +LoongArch copies exception entry code to the exception_handlers area and, +on SMP systems, may execute vectors from per-cpu pcpu_handlers. A PC +saved while running copied exception code therefore does not necessarily +match the canonical symbol address in vmlinux. + +Crash currently looks up those runtime addresses directly. That can +leave frames such as handle_vint unresolved and prevents exception-frame +handling from identifying the interrupted context reliably. + +Translate PCs in the relocated exception vector range back to their +canonical handler addresses before symbol lookup. Follow the kernel +layout by folding per-cpu handler addresses back to eentry, computing the +vector type and offset, mapping normal exceptions through +exception_table[], and mapping interrupt vectors to handle_vint. + +The per-cpu handler mapping is optional and exists only for NUMA non-RT +kernels. When pcpu_handlers is absent, the normal eentry mapping still +covers the boot-time exception handler area. + +Co-developed-by: Xianglai Li +Signed-off-by: Xianglai Li +Signed-off-by: Ming Wang +--- + loongarch64.c | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++- + 1 file changed, 77 insertions(+), 1 deletion(-) + +diff --git a/loongarch64.c b/loongarch64.c +index 830c81826ebd..0b108e062b2e 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -43,6 +43,11 @@ struct loongarch64_unwind_frame { + unsigned long ra; + }; + ++#define LOONGARCH64_VECSIZE 0x200 ++#define LOONGARCH64_EXCEPTION_VECTOR_NUM 128 ++#define LOONGARCH64_EXCCODE_INT_START 64 ++#define LOONGARCH64_EXCCODE_INT_END 78 ++ + static int loongarch64_pgd_vtop(ulong *pgd, ulong vaddr, + physaddr_t *paddr, int verbose); + static int loongarch64_uvtop(struct task_context *tc, ulong vaddr, +@@ -64,6 +69,7 @@ static void loongarch64_dump_backtrace_entry(struct bt_info *bt, + struct loongarch64_unwind_frame *previous, int level); + static void loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs); + static int loongarch64_is_exception_entry(struct syment *sym); ++static ulong loongarch64_exception_pc(int cpu, ulong pc); + static int loongarch64_eframe_search(struct bt_info *bt); + static void loongarch64_display_full_frame(struct bt_info *bt, + struct loongarch64_unwind_frame *current, +@@ -466,6 +472,9 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + struct syment *symbol = NULL; + ulong offset; + ++ current.pc = loongarch64_exception_pc(bt->tc->processor, ++ current.pc); ++ + if (CRASHDEBUG(8)) + fprintf(fp, "level %d pc %#lx ra %#lx sp %lx\n", + level, current.pc, current.ra, current.sp); +@@ -687,7 +696,8 @@ loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs) + regs->regs[i+2], regs->regs[i+3]); + } + +- value_to_symstr(regs->csr_epc, buf, 16); ++ value_to_symstr(loongarch64_exception_pc(bt->tc->processor, ++ regs->csr_epc), buf, 16); + fprintf(fp, " era : %016lx %s\n", regs->csr_epc, buf); + + value_to_symstr(regs->regs[LOONGARCH64_EF_RA], buf, 16); +@@ -735,6 +745,72 @@ loongarch64_is_exception_entry(struct syment *sym) + STREQ(sym->name, "handle_lasx"); + } + ++static ulong ++loongarch64_exception_pc(int cpu, ulong pc) ++{ ++ ulong eentry, pcpu_handler, offset, type, func; ++ ulong exception_table, exception_handlers; ++ int i; ++ ++ if (!IS_KVADDR(pc) || !symbol_exists("exception_handlers")) ++ return pc; ++ ++ exception_handlers = symbol_value("exception_handlers"); ++ eentry = exception_handlers; ++ if (symbol_exists("eentry")) ++ readmem(symbol_value("eentry"), KVADDR, &eentry, sizeof(eentry), ++ "LoongArch eentry", RETURN_ON_ERROR|QUIET); ++ ++ /* ++ * pcpu_handlers exists only for NUMA non-RT kernels. When absent, ++ * the boot-time eentry/exception_handlers mapping below is still valid. ++ */ ++ if (symbol_exists("pcpu_handlers")) { ++ for (i = 0; i < kt->cpus; i++) { ++ if (cpu >= 0 && cpu < kt->cpus && i != cpu) ++ continue; ++ if (!readmem(symbol_value("pcpu_handlers") + ++ (i * sizeof(ulong)), KVADDR, &pcpu_handler, ++ sizeof(pcpu_handler), "LoongArch pcpu_handlers", ++ RETURN_ON_ERROR|QUIET)) ++ continue; ++ if (!pcpu_handler) ++ continue; ++ if (pc >= pcpu_handler && ++ pc < pcpu_handler + LOONGARCH64_VECSIZE * ++ LOONGARCH64_EXCEPTION_VECTOR_NUM) { ++ pc = pc + eentry - pcpu_handler; ++ break; ++ } ++ } ++ } ++ ++ if (pc < eentry || pc >= eentry + LOONGARCH64_EXCCODE_INT_END * LOONGARCH64_VECSIZE) ++ return pc; ++ ++ offset = (pc - eentry) % LOONGARCH64_VECSIZE; ++ type = (pc - eentry) / LOONGARCH64_VECSIZE; ++ ++ if (type < LOONGARCH64_EXCCODE_INT_START && ++ symbol_exists("exception_table")) { ++ exception_table = symbol_value("exception_table"); ++ if (!readmem(exception_table + (type * sizeof(ulong)), KVADDR, ++ &func, sizeof(func), "LoongArch exception_table", ++ RETURN_ON_ERROR|QUIET)) ++ func = 0; ++ } else if (type >= LOONGARCH64_EXCCODE_INT_START && ++ type <= LOONGARCH64_EXCCODE_INT_END && ++ symbol_exists("handle_vint")) { ++ func = symbol_value("handle_vint"); ++ } else if (symbol_exists("handle_reserved")) { ++ func = symbol_value("handle_reserved"); ++ } else { ++ func = 0; ++ } ++ ++ return func ? func + offset : pc; ++} ++ + /* + * 'bt -f' commend output + * Display all stack data contained in a frame +-- +2.54.0 + diff --git a/0026-LoongArch64-add-initial-ORC-unwinder-support.patch b/0026-LoongArch64-add-initial-ORC-unwinder-support.patch new file mode 100644 index 0000000..4b50bbb --- /dev/null +++ b/0026-LoongArch64-add-initial-ORC-unwinder-support.patch @@ -0,0 +1,515 @@ +From 9e8d1784b910db417c993e9de7ab2cbd0e9d4fc8 Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Tue, 16 Jun 2026 20:47:43 +0800 +Subject: [PATCH 26/39] LoongArch64: add initial ORC unwinder support + +LoongArch kernels can provide ORC unwind tables. Without consuming those +tables, crash falls back to a small prologue scanner, which is fragile +around exception entries and functions whose frame layout does not match +the simple scanner. + +Add initial support for reading the kernel ORC IP table, lookup table and +orc_entry records, and use them while unwinding LoongArch64 kernel text +frames. The unwinder handles CALL and REGS entries, carries +frame-pointer state, and normalizes relocated exception-vector PCs before +using them as unwind targets. + +For CALL entries, convert the saved return address back to the call +instruction address before validation, matching the kernel unwinder +convention. This avoids looking up the instruction after the call as the +caller frame. + +This intentionally covers core kernel text first. Module and ftrace ORC +handling can be added separately. + +Signed-off-by: Ming Wang +--- + defs.h | 25 ++++ + loongarch64.c | 356 +++++++++++++++++++++++++++++++++++++++++++++++++- + 2 files changed, 378 insertions(+), 3 deletions(-) + +diff --git a/defs.h b/defs.h +index a4f70b773cd7..b349872f49a4 100644 +--- a/defs.h ++++ b/defs.h +@@ -7333,6 +7333,30 @@ void loongarch64_dump_machdep_table(ulong); + + #define KSYMS_START (0x1) + ++struct loongarch64_orc_entry { ++ short sp_offset; ++ short fp_offset; ++ short ra_offset; ++ uint sp_reg; ++ uint fp_reg; ++ uint ra_reg; ++ uint type; ++ uint signal; ++}; ++ ++struct loongarch64_ORC_data { ++ int enabled; ++ uint lookup_num_blocks; ++ ulong __start_orc_unwind_ip; ++ ulong __stop_orc_unwind_ip; ++ ulong __start_orc_unwind; ++ ulong __stop_orc_unwind; ++ ulong orc_lookup; ++ ulong ip_entry; ++ ulong orc_entry; ++ struct loongarch64_orc_entry orc_entry_data; ++}; ++ + struct machine_specific { + ulong phys_base; + ulong vmalloc_start_addr; +@@ -7340,6 +7364,7 @@ struct machine_specific { + ulong modules_end; + + struct loongarch64_pt_regs *crash_task_regs; ++ struct loongarch64_ORC_data orc; + }; + + /* +diff --git a/loongarch64.c b/loongarch64.c +index 0b108e062b2e..2e9e28602b9b 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -41,8 +41,33 @@ struct loongarch64_unwind_frame { + unsigned long sp; + unsigned long pc; + unsigned long ra; ++ unsigned long fp; + }; + ++typedef struct __attribute__((__packed__)) { ++ short sp_offset; ++ short fp_offset; ++ short ra_offset; ++ unsigned int sp_reg:4; ++ unsigned int fp_reg:4; ++ unsigned int ra_reg:4; ++ unsigned int type:3; ++ unsigned int signal:1; ++} loongarch64_kernel_orc_entry; ++ ++#define LOONGARCH64_ORC_REG_UNDEFINED 0 ++#define LOONGARCH64_ORC_REG_PREV_SP 1 ++#define LOONGARCH64_ORC_REG_SP 2 ++#define LOONGARCH64_ORC_REG_FP 3 ++ ++#define LOONGARCH64_ORC_TYPE_UNDEFINED 0 ++#define LOONGARCH64_ORC_TYPE_END_OF_STACK 1 ++#define LOONGARCH64_ORC_TYPE_CALL 2 ++#define LOONGARCH64_ORC_TYPE_REGS 3 ++ ++#define LOONGARCH64_LOOKUP_BLOCK_ORDER 8 ++#define LOONGARCH64_LOOKUP_BLOCK_SIZE (1 << LOONGARCH64_LOOKUP_BLOCK_ORDER) ++#define LOONGARCH64_INSN_SIZE 4 + #define LOONGARCH64_VECSIZE 0x200 + #define LOONGARCH64_EXCEPTION_VECTOR_NUM 128 + #define LOONGARCH64_EXCCODE_INT_START 64 +@@ -70,6 +95,7 @@ static void loongarch64_dump_backtrace_entry(struct bt_info *bt, + static void loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs); + static int loongarch64_is_exception_entry(struct syment *sym); + static ulong loongarch64_exception_pc(int cpu, ulong pc); ++static int loongarch64_is_unwind_text(ulong pc); + static int loongarch64_eframe_search(struct bt_info *bt); + static void loongarch64_display_full_frame(struct bt_info *bt, + struct loongarch64_unwind_frame *current, +@@ -82,6 +108,10 @@ static int loongarch64_get_frame(struct bt_info *bt, ulong *pcp, ulong *spp); + static int loongarch64_init_active_task_regs(void); + static int loongarch64_get_crash_notes(void); + static int loongarch64_get_elf_notes(void); ++static void loongarch64_ORC_init(void); ++static int loongarch64_orc_unwind(struct bt_info *bt, ++ struct loongarch64_unwind_frame *current, ++ struct loongarch64_unwind_frame *previous); + + /* + * 3 Levels paging PAGE_SIZE=16KB +@@ -454,11 +484,12 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + if (bt->flags & BT_REGS_NOT_FOUND) + return; + +- previous.sp = previous.pc = previous.ra = 0; ++ previous.sp = previous.pc = previous.ra = previous.fp = 0; + + current.pc = bt->instptr; + current.sp = bt->stkptr; + current.ra = 0; ++ current.fp = 0; + + if (!INSTACK(current.sp, bt)) + return; +@@ -466,6 +497,7 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + if (bt->machdep) { + regs = (struct loongarch64_pt_regs *)bt->machdep; + previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA]; ++ current.fp = regs->regs[LOONGARCH64_EF_FP]; + } + + while (current.sp <= bt->stacktop - SIZE(pt_regs)) { +@@ -523,12 +555,15 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + } + } + +- if (symbol && loongarch64_is_exception_entry(symbol)) { ++ if (loongarch64_orc_unwind(bt, ¤t, &previous)) { ++ /* ORC has already calculated the caller frame. */ ++ } else if (symbol && loongarch64_is_exception_entry(symbol)) { + + GET_STACK_DATA(current.sp, pt_regs, sizeof(pt_regs)); + regs = (struct loongarch64_pt_regs *) (pt_regs + OFFSET(pt_regs_regs)); + previous.ra = regs->regs[LOONGARCH64_EF_RA]; + previous.sp = regs->regs[LOONGARCH64_EF_SP]; ++ previous.fp = regs->regs[LOONGARCH64_EF_FP]; + current.ra = regs->csr_epc; + + if (CRASHDEBUG(8)) +@@ -554,12 +589,13 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + current.pc = current.ra; + current.sp = previous.sp; + current.ra = previous.ra; ++ current.fp = previous.fp; + + if (CRASHDEBUG(8)) + fprintf(fp, "next %d pc %#lx ra %#lx sp %lx\n", + level, current.pc, current.ra, current.sp); + +- previous.sp = previous.pc = previous.ra = 0; ++ previous.sp = previous.pc = previous.ra = previous.fp = 0; + } + } + +@@ -811,6 +847,319 @@ loongarch64_exception_pc(int cpu, ulong pc) + return func ? func + offset : pc; + } + ++ ++static int ++loongarch64_is_unwind_text(ulong pc) ++{ ++ struct syment *symbol; ++ ulong offset; ++ ++ if (!is_kernel_text(pc)) ++ return FALSE; ++ ++ symbol = value_search(pc, &offset); ++ if (!symbol || symbol->name[0] == '.' || ++ STREQ(symbol->name, "_PROCEDURE_LINKAGE_TABLE_") || ++ STREQ(symbol->name, "empty_zero_page") || ++ STRNEQ(symbol->name, "__start_") || ++ STRNEQ(symbol->name, "__end_")) ++ return FALSE; ++ ++ return TRUE; ++} ++ ++static int ++loongarch64_orc_ip(ulong ip_entry_addr, ulong *ip) ++{ ++ int ip_entry; ++ ++ if (!readmem(ip_entry_addr, KVADDR, &ip_entry, sizeof(ip_entry), ++ "LoongArch ORC ip", RETURN_ON_ERROR|QUIET)) ++ return FALSE; ++ ++ *ip = ip_entry_addr + ip_entry; ++ return TRUE; ++} ++ ++static struct loongarch64_orc_entry * ++loongarch64_orc_get_entry(struct loongarch64_ORC_data *orc) ++{ ++ loongarch64_kernel_orc_entry korc; ++ struct loongarch64_orc_entry *entry = &orc->orc_entry_data; ++ ++ if (!readmem(orc->orc_entry, KVADDR, &korc, sizeof(korc), ++ "LoongArch ORC entry", RETURN_ON_ERROR|QUIET)) ++ return NULL; ++ ++ entry->sp_offset = korc.sp_offset; ++ entry->fp_offset = korc.fp_offset; ++ entry->ra_offset = korc.ra_offset; ++ entry->sp_reg = korc.sp_reg; ++ entry->fp_reg = korc.fp_reg; ++ entry->ra_reg = korc.ra_reg; ++ entry->type = korc.type; ++ entry->signal = korc.signal; ++ ++ return entry; ++} ++ ++static struct loongarch64_orc_entry * ++loongarch64_orc_find_in_table(ulong ip_table, ulong orc_table, ++ uint num_entries, ulong ip) ++{ ++ int index; ++ ulong first, last, mid, found, vaddr; ++ struct machine_specific *ms = machdep->machspec; ++ struct loongarch64_ORC_data *orc = &ms->orc; ++ ++ if (!num_entries) ++ return NULL; ++ ++ first = ip_table; ++ last = ip_table + ((num_entries - 1) * sizeof(int)); ++ found = first; ++ ++ while (first <= last) { ++ mid = first + (((last - first) / sizeof(int)) / 2) * ++ sizeof(int); ++ ++ if (!loongarch64_orc_ip(mid, &vaddr)) ++ return NULL; ++ ++ if (vaddr <= ip) { ++ found = mid; ++ first = mid + sizeof(int); ++ } else { ++ if (mid == ip_table) ++ break; ++ last = mid - sizeof(int); ++ } ++ } ++ ++ index = (found - ip_table) / sizeof(int); ++ orc->ip_entry = found; ++ orc->orc_entry = orc_table + (index * SIZE(orc_entry)); ++ ++ return loongarch64_orc_get_entry(orc); ++} ++ ++static struct loongarch64_orc_entry * ++loongarch64_orc_find(ulong ip) ++{ ++ uint idx, start, stop, num_entries; ++ struct machine_specific *ms = machdep->machspec; ++ struct loongarch64_ORC_data *orc = &ms->orc; ++ ++ if (!orc->enabled) ++ return NULL; ++ ++ if ((ip >= kt->stext) && (ip < kt->etext)) { ++ if (orc->lookup_num_blocks < 2) ++ return NULL; ++ ++ idx = (ip - kt->stext) / LOONGARCH64_LOOKUP_BLOCK_SIZE; ++ if (idx >= orc->lookup_num_blocks - 1) ++ return NULL; ++ ++ if (!readmem(orc->orc_lookup + (idx * sizeof(uint)), KVADDR, ++ &start, sizeof(start), "LoongArch ORC lookup start", ++ RETURN_ON_ERROR|QUIET)) ++ return NULL; ++ if (!readmem(orc->orc_lookup + ((idx + 1) * sizeof(uint)), ++ KVADDR, &stop, sizeof(stop), "LoongArch ORC lookup stop", ++ RETURN_ON_ERROR|QUIET)) ++ return NULL; ++ stop++; ++ ++ if ((orc->__start_orc_unwind + (start * SIZE(orc_entry))) >= ++ orc->__stop_orc_unwind) ++ return NULL; ++ if ((orc->__start_orc_unwind + (stop * SIZE(orc_entry))) > ++ orc->__stop_orc_unwind) ++ return NULL; ++ ++ return loongarch64_orc_find_in_table( ++ orc->__start_orc_unwind_ip + (start * sizeof(int)), ++ orc->__start_orc_unwind + (start * SIZE(orc_entry)), ++ stop - start, ip); ++ } ++ ++ if (is_kernel_text(ip)) { ++ num_entries = (orc->__stop_orc_unwind_ip - ++ orc->__start_orc_unwind_ip) / sizeof(int); ++ return loongarch64_orc_find_in_table(orc->__start_orc_unwind_ip, ++ orc->__start_orc_unwind, num_entries, ip); ++ } ++ ++ return NULL; ++} ++ ++static int ++loongarch64_orc_read_stack(ulong addr, ulong *value) ++{ ++ return readmem(addr, KVADDR, value, sizeof(*value), ++ "LoongArch ORC stack", RETURN_ON_ERROR|QUIET); ++} ++ ++static ulong ++loongarch64_orc_adjust_pc(int cpu, ulong ra) ++{ ++ ra = loongarch64_exception_pc(cpu, ra); ++ return loongarch64_is_unwind_text(ra) ? ra : 0; ++} ++ ++static int ++loongarch64_orc_unwind(struct bt_info *bt, ++ struct loongarch64_unwind_frame *current, ++ struct loongarch64_unwind_frame *previous) ++{ ++ ulong cfa, pcval, fpval = current->fp; ++ struct loongarch64_orc_entry *orc; ++ struct loongarch64_pt_regs regs; ++ ++ orc = loongarch64_orc_find(current->pc); ++ if (!orc) ++ return FALSE; ++ ++ if (CRASHDEBUG(8)) ++ fprintf(fp, ++ "orc pc %lx sp %lx fp %lx -> spo %d fpo %d rao %d " ++ "spr %u fpr %u rar %u type %u\n", ++ current->pc, current->sp, current->fp, orc->sp_offset, ++ orc->fp_offset, orc->ra_offset, orc->sp_reg, orc->fp_reg, ++ orc->ra_reg, orc->type); ++ ++ if (orc->type == LOONGARCH64_ORC_TYPE_UNDEFINED || ++ orc->type == LOONGARCH64_ORC_TYPE_END_OF_STACK) ++ return FALSE; ++ ++ switch (orc->sp_reg) { ++ case LOONGARCH64_ORC_REG_SP: ++ cfa = current->sp + orc->sp_offset; ++ break; ++ case LOONGARCH64_ORC_REG_FP: ++ if (!current->fp) ++ return FALSE; ++ cfa = current->fp; ++ break; ++ default: ++ return FALSE; ++ } ++ ++ switch (orc->fp_reg) { ++ case LOONGARCH64_ORC_REG_PREV_SP: ++ if (!loongarch64_orc_read_stack(cfa + orc->fp_offset, &fpval)) ++ return FALSE; ++ break; ++ case LOONGARCH64_ORC_REG_UNDEFINED: ++ break; ++ default: ++ return FALSE; ++ } ++ ++ switch (orc->type) { ++ case LOONGARCH64_ORC_TYPE_CALL: ++ if (orc->ra_reg == LOONGARCH64_ORC_REG_PREV_SP) { ++ if (!loongarch64_orc_read_stack(cfa + orc->ra_offset, ++ &pcval)) ++ return FALSE; ++ } else if (orc->ra_reg == LOONGARCH64_ORC_REG_UNDEFINED) { ++ if (!current->ra || current->ra == current->pc) ++ return FALSE; ++ pcval = current->ra; ++ } else { ++ return FALSE; ++ } ++ if (pcval >= LOONGARCH64_INSN_SIZE) ++ pcval -= LOONGARCH64_INSN_SIZE; ++ pcval = loongarch64_orc_adjust_pc(bt->tc->processor, pcval); ++ if (!pcval && current->ra >= LOONGARCH64_INSN_SIZE) ++ pcval = loongarch64_orc_adjust_pc(bt->tc->processor, ++ current->ra - LOONGARCH64_INSN_SIZE); ++ if (!pcval) ++ return FALSE; ++ previous->sp = cfa; ++ previous->fp = fpval; ++ previous->ra = 0; ++ current->ra = pcval; ++ return TRUE; ++ ++ case LOONGARCH64_ORC_TYPE_REGS: ++ if (!readmem(cfa, KVADDR, ®s, sizeof(regs), ++ "LoongArch ORC pt_regs", RETURN_ON_ERROR|QUIET)) ++ return FALSE; ++ /* ++ * The kernel unwinder treats user-mode or empty pt_regs as a ++ * clean end. Crash does not need to distinguish that from a ++ * fallback here; a non-kernel ERA simply stops ORC unwinding. ++ */ ++ pcval = loongarch64_exception_pc(bt->tc->processor, regs.csr_epc); ++ if (!loongarch64_is_unwind_text(pcval)) ++ return FALSE; ++ previous->sp = regs.regs[LOONGARCH64_EF_SP]; ++ previous->fp = regs.regs[LOONGARCH64_EF_FP]; ++ previous->ra = regs.regs[LOONGARCH64_EF_RA]; ++ current->ra = pcval; ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ ++static void ++loongarch64_ORC_init(void) ++{ ++ int i; ++ char *orc_symbols[] = { ++ "lookup_num_blocks", ++ "__start_orc_unwind_ip", ++ "__stop_orc_unwind_ip", ++ "__start_orc_unwind", ++ "__stop_orc_unwind", ++ "orc_lookup", ++ NULL ++ }; ++ struct machine_specific *ms = machdep->machspec; ++ struct loongarch64_ORC_data *orc = &ms->orc; ++ ++ STRUCT_SIZE_INIT(orc_entry, "orc_entry"); ++ if (!VALID_STRUCT(orc_entry) || ++ SIZE(orc_entry) != sizeof(loongarch64_kernel_orc_entry)) { ++ error(WARNING, "LoongArch64 ORC unwinder: " ++ "orc_entry structure has changed\n"); ++ return; ++ } ++ ++ if (!MEMBER_EXISTS("orc_entry", "sp_offset") || ++ !MEMBER_EXISTS("orc_entry", "fp_offset") || ++ !MEMBER_EXISTS("orc_entry", "ra_offset") || ++ !MEMBER_EXISTS("orc_entry", "sp_reg") || ++ !MEMBER_EXISTS("orc_entry", "fp_reg") || ++ !MEMBER_EXISTS("orc_entry", "ra_reg") || ++ !MEMBER_EXISTS("orc_entry", "type")) { ++ error(WARNING, "LoongArch64 ORC unwinder: " ++ "orc_entry members have changed\n"); ++ return; ++ } ++ ++ for (i = 0; orc_symbols[i]; i++) { ++ if (!symbol_exists(orc_symbols[i])) ++ return; ++ } ++ ++ if (!readmem(symbol_value("lookup_num_blocks"), KVADDR, ++ &orc->lookup_num_blocks, sizeof(orc->lookup_num_blocks), ++ "LoongArch ORC lookup_num_blocks", RETURN_ON_ERROR|QUIET)) ++ return; ++ ++ orc->__start_orc_unwind_ip = symbol_value("__start_orc_unwind_ip"); ++ orc->__stop_orc_unwind_ip = symbol_value("__stop_orc_unwind_ip"); ++ orc->__start_orc_unwind = symbol_value("__start_orc_unwind"); ++ orc->__stop_orc_unwind = symbol_value("__stop_orc_unwind"); ++ orc->orc_lookup = symbol_value("orc_lookup"); ++ orc->enabled = TRUE; ++} ++ + /* + * 'bt -f' commend output + * Display all stack data contained in a frame +@@ -1374,6 +1723,7 @@ loongarch64_init(int when) + &machdep->nr_irqs); + + loongarch64_stackframe_init(); ++ loongarch64_ORC_init(); + + if (!machdep->hz) + machdep->hz = 250; +-- +2.54.0 + diff --git a/0027-LoongArch64-unwind-dumpfile-active-tasks-from-IRQ-st.patch b/0027-LoongArch64-unwind-dumpfile-active-tasks-from-IRQ-st.patch new file mode 100644 index 0000000..81ed0ab --- /dev/null +++ b/0027-LoongArch64-unwind-dumpfile-active-tasks-from-IRQ-st.patch @@ -0,0 +1,364 @@ +From a436361b2798534ea722ac696a4eeaa35d80ef33 Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Tue, 16 Jun 2026 20:47:44 +0800 +Subject: [PATCH 27/39] LoongArch64: unwind dumpfile active tasks from IRQ + stacks + +For dumpfile active tasks, the saved SP may point into the per-cpu IRQ +stack instead of the task stack. The existing LoongArch64 unwinder only +uses the task stack buffer, so it can stop early or pick stale task-stack +words as caller frames. + +Initialize the per-cpu IRQ stack addresses, switch the bt stack buffer +when an active task starts on an IRQ stack, and continue unwinding back to +the interrupted task stack using the task SP saved at the end of the IRQ +stack. This saved-SP slot matches the kernel IRQ_STACK_START layout. +When that saved task SP points to pt_regs, validate the saved ERA, SP and +RA before using them as the handoff frame. + +Hypervisor dump PRSTATUS RA values from IRQ stack contexts are not always +a reliable caller. Keep them only for the narrow IRQ leaf case that +preserves the in-flight generic_handle_domain_irq -> handle_cpu_irq +chain; otherwise let ORC, exception frames, or the saved pt_regs handoff +determine the next frame. + +Signed-off-by: Ming Wang +--- + defs.h | 2 + + loongarch64.c | 226 ++++++++++++++++++++++++++++++++++++++++++++++++-- + 2 files changed, 223 insertions(+), 5 deletions(-) + +diff --git a/defs.h b/defs.h +index b349872f49a4..bdb4eb3e0980 100644 +--- a/defs.h ++++ b/defs.h +@@ -7363,6 +7363,8 @@ struct machine_specific { + ulong modules_vaddr; + ulong modules_end; + ++ ulong irq_stack_size; ++ ulong *irq_stacks; + struct loongarch64_pt_regs *crash_task_regs; + struct loongarch64_ORC_data orc; + }; +diff --git a/loongarch64.c b/loongarch64.c +index 2e9e28602b9b..c25a3bbf6d6c 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -70,6 +70,7 @@ typedef struct __attribute__((__packed__)) { + #define LOONGARCH64_INSN_SIZE 4 + #define LOONGARCH64_VECSIZE 0x200 + #define LOONGARCH64_EXCEPTION_VECTOR_NUM 128 ++#define LOONGARCH64_IRQ_STACK_SAVED_SP_OFFSET 16 + #define LOONGARCH64_EXCCODE_INT_START 64 + #define LOONGARCH64_EXCCODE_INT_END 78 + +@@ -96,6 +97,9 @@ static void loongarch64_dump_exception_stack(struct bt_info *bt, char *pt_regs); + static int loongarch64_is_exception_entry(struct syment *sym); + static ulong loongarch64_exception_pc(int cpu, ulong pc); + static int loongarch64_is_unwind_text(ulong pc); ++static int loongarch64_use_irq_prstatus_ra(ulong pc, ulong ra); ++static int loongarch64_valid_saved_regs(struct bt_info *bt, ++ struct loongarch64_pt_regs *regs, ulong *pc); + static int loongarch64_eframe_search(struct bt_info *bt); + static void loongarch64_display_full_frame(struct bt_info *bt, + struct loongarch64_unwind_frame *current, +@@ -108,10 +112,17 @@ static int loongarch64_get_frame(struct bt_info *bt, ulong *pcp, ulong *spp); + static int loongarch64_init_active_task_regs(void); + static int loongarch64_get_crash_notes(void); + static int loongarch64_get_elf_notes(void); ++static void loongarch64_irq_stack_init(void); + static void loongarch64_ORC_init(void); + static int loongarch64_orc_unwind(struct bt_info *bt, + struct loongarch64_unwind_frame *current, + struct loongarch64_unwind_frame *previous); ++static int loongarch64_on_irq_stack(int cpu, ulong stkptr); ++static void loongarch64_set_irq_stack(struct bt_info *bt); ++static int loongarch64_switch_from_irq_stack(struct bt_info *bt, ++ struct loongarch64_unwind_frame *current); ++static int loongarch64_find_next_kernel_text(struct bt_info *bt, ++ struct loongarch64_unwind_frame *current); + + /* + * 3 Levels paging PAGE_SIZE=16KB +@@ -480,6 +491,7 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + char pt_regs[SIZE(pt_regs)]; + int level = 0; + int invalid_ok = 1; ++ int on_irq_stack; + + if (bt->flags & BT_REGS_NOT_FOUND) + return; +@@ -491,16 +503,30 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + current.ra = 0; + current.fp = 0; + ++ if (!INSTACK(current.sp, bt) && ++ (bt->flags & BT_REGS_NOT_FOUND) == 0 && ++ loongarch64_on_irq_stack(bt->tc->processor, current.sp)) ++ loongarch64_set_irq_stack(bt); ++ + if (!INSTACK(current.sp, bt)) + return; + ++ on_irq_stack = loongarch64_on_irq_stack(bt->tc->processor, current.sp); + if (bt->machdep) { + regs = (struct loongarch64_pt_regs *)bt->machdep; +- previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA]; ++ /* ++ * PRSTATUS RA from IRQ stacks is not always a reliable caller for ++ * hypervisor dumps. Use it only for IRQ leaf frames where it keeps ++ * the in-flight interrupt call chain before crossing stacks. ++ */ ++ if (!on_irq_stack || ++ loongarch64_use_irq_prstatus_ra(current.pc, ++ regs->regs[LOONGARCH64_EF_RA])) ++ previous.pc = current.ra = regs->regs[LOONGARCH64_EF_RA]; + current.fp = regs->regs[LOONGARCH64_EF_FP]; + } + +- while (current.sp <= bt->stacktop - SIZE(pt_regs)) { ++ while (current.sp < bt->stacktop) { + struct syment *symbol = NULL; + ulong offset; + +@@ -511,14 +537,28 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + fprintf(fp, "level %d pc %#lx ra %#lx sp %lx\n", + level, current.pc, current.ra, current.sp); + +- if (!IS_KVADDR(current.pc) && !invalid_ok) ++ if (!IS_KVADDR(current.pc) && !invalid_ok) { ++ if (loongarch64_switch_from_irq_stack(bt, ¤t) || ++ loongarch64_find_next_kernel_text(bt, ¤t)) { ++ invalid_ok = 1; ++ continue; ++ } + return; ++ } + + symbol = value_search(current.pc, &offset); +- if (!symbol && !invalid_ok) { ++ if ((!symbol || !loongarch64_is_unwind_text(current.pc)) && ++ !invalid_ok) { ++ if (loongarch64_switch_from_irq_stack(bt, ¤t) || ++ loongarch64_find_next_kernel_text(bt, ¤t)) { ++ invalid_ok = 1; ++ continue; ++ } + error(FATAL, "PC is unknown symbol (%lx)", current.pc); + return; + } ++ if (symbol && !loongarch64_is_unwind_text(current.pc)) ++ symbol = NULL; + invalid_ok = 0; + + /* +@@ -557,7 +597,8 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + + if (loongarch64_orc_unwind(bt, ¤t, &previous)) { + /* ORC has already calculated the caller frame. */ +- } else if (symbol && loongarch64_is_exception_entry(symbol)) { ++ } else if (symbol && loongarch64_is_exception_entry(symbol) && ++ current.sp <= bt->stacktop - SIZE(pt_regs)) { + + GET_STACK_DATA(current.sp, pt_regs, sizeof(pt_regs)); + regs = (struct loongarch64_pt_regs *) (pt_regs + OFFSET(pt_regs_regs)); +@@ -596,6 +637,12 @@ loongarch64_back_trace_cmd(struct bt_info *bt) + level, current.pc, current.ra, current.sp); + + previous.sp = previous.pc = previous.ra = previous.fp = 0; ++ ++ if (current.sp >= bt->stacktop && ++ loongarch64_switch_from_irq_stack(bt, ¤t)) { ++ invalid_ok = 1; ++ continue; ++ } + } + } + +@@ -868,6 +915,174 @@ loongarch64_is_unwind_text(ulong pc) + return TRUE; + } + ++ ++static int ++loongarch64_use_irq_prstatus_ra(ulong pc, ulong ra) ++{ ++ struct syment *pcsym, *rasym; ++ ulong offset; ++ ++ if (!loongarch64_is_unwind_text(pc) || ++ !loongarch64_is_unwind_text(ra)) ++ return FALSE; ++ ++ pcsym = value_search(pc, &offset); ++ rasym = value_search(ra, &offset); ++ if (!pcsym || !rasym) ++ return FALSE; ++ ++ return STREQ(pcsym->name, "generic_handle_domain_irq") && ++ STREQ(rasym->name, "handle_cpu_irq"); ++} ++ ++static int ++loongarch64_valid_saved_regs(struct bt_info *bt, ++ struct loongarch64_pt_regs *regs, ulong *pc) ++{ ++ ulong saved_pc; ++ ++ saved_pc = loongarch64_exception_pc(bt->tc->processor, regs->csr_epc); ++ if (!loongarch64_is_unwind_text(saved_pc)) ++ return FALSE; ++ ++ if (!INSTACK(regs->regs[LOONGARCH64_EF_SP], bt)) ++ return FALSE; ++ ++ if (regs->regs[LOONGARCH64_EF_RA] && ++ !loongarch64_is_unwind_text(regs->regs[LOONGARCH64_EF_RA])) ++ return FALSE; ++ ++ *pc = saved_pc; ++ return TRUE; ++} ++ ++static void ++loongarch64_irq_stack_init(void) ++{ ++ int i; ++ struct syment *sp; ++ struct machine_specific *ms = machdep->machspec; ++ ulong p; ++ ++ if (!(symbol_exists("irq_stack") && ++ (sp = per_cpu_symbol_search("irq_stack")))) ++ return; ++ ++ ms->irq_stack_size = machdep->stacksize; ++ if (!(ms->irq_stacks = (ulong *)malloc((size_t)(kt->cpus * ++ sizeof(ulong))))) ++ error(FATAL, "cannot malloc irq_stack addresses\n"); ++ ++ machdep->flags |= IRQSTACKS; ++ ++ for (i = 0; i < kt->cpus; i++) { ++ p = kt->__per_cpu_offset[i] + sp->value; ++ if (!readmem(p, KVADDR, &ms->irq_stacks[i], sizeof(ulong), ++ "IRQ stack pointer", RETURN_ON_ERROR)) ++ ms->irq_stacks[i] = 0; ++ } ++} ++ ++static int ++loongarch64_on_irq_stack(int cpu, ulong stkptr) ++{ ++ struct machine_specific *ms = machdep->machspec; ++ ++ if (cpu < 0 || cpu >= kt->cpus || !ms->irq_stacks || ++ !ms->irq_stacks[cpu] || !ms->irq_stack_size) ++ return FALSE; ++ ++ return (stkptr >= ms->irq_stacks[cpu]) && ++ (stkptr < (ms->irq_stacks[cpu] + ms->irq_stack_size)); ++} ++ ++static void ++loongarch64_set_irq_stack(struct bt_info *bt) ++{ ++ struct machine_specific *ms = machdep->machspec; ++ ++ bt->stackbase = ms->irq_stacks[bt->tc->processor]; ++ bt->stacktop = bt->stackbase + ms->irq_stack_size; ++ alter_stackbuf(bt); ++} ++ ++static int ++loongarch64_switch_from_irq_stack(struct bt_info *bt, ++ struct loongarch64_unwind_frame *current) ++{ ++ struct machine_specific *ms = machdep->machspec; ++ struct loongarch64_pt_regs *regs; ++ char pt_regs[SIZE(pt_regs)]; ++ ulong irq_stack, saved_sp_addr, saved_sp, saved_pc; ++ ulong task_stackbase, task_stacktop; ++ ++ if (bt->tc->processor < 0 || bt->tc->processor >= kt->cpus || ++ !ms->irq_stacks || !ms->irq_stack_size) ++ return FALSE; ++ ++ irq_stack = ms->irq_stacks[bt->tc->processor]; ++ if (!irq_stack || current->sp < irq_stack || ++ current->sp > irq_stack + ms->irq_stack_size + 64) ++ return FALSE; ++ ++ /* Matches the kernel's IRQ_STACK_START layout. */ ++ saved_sp_addr = irq_stack + ms->irq_stack_size - ++ LOONGARCH64_IRQ_STACK_SAVED_SP_OFFSET; ++ if (!readmem(saved_sp_addr, KVADDR, &saved_sp, sizeof(saved_sp), ++ "saved task stack pointer", RETURN_ON_ERROR)) ++ return FALSE; ++ ++ task_stackbase = GET_STACKBASE(bt->task); ++ task_stacktop = GET_STACKTOP(bt->task); ++ if (saved_sp < task_stackbase || saved_sp >= task_stacktop) ++ return FALSE; ++ ++ bt->stackbase = task_stackbase; ++ bt->stacktop = task_stacktop; ++ alter_stackbuf(bt); ++ if (saved_sp <= bt->stacktop - SIZE(pt_regs)) { ++ GET_STACK_DATA(saved_sp, pt_regs, sizeof(pt_regs)); ++ regs = (struct loongarch64_pt_regs *)(pt_regs + ++ OFFSET(pt_regs_regs)); ++ if (loongarch64_valid_saved_regs(bt, regs, &saved_pc)) { ++ current->pc = saved_pc; ++ current->sp = regs->regs[LOONGARCH64_EF_SP]; ++ current->ra = regs->regs[LOONGARCH64_EF_RA]; ++ current->fp = regs->regs[LOONGARCH64_EF_FP]; ++ return TRUE; ++ } ++ } ++ ++ current->sp = saved_sp; ++ current->ra = 0; ++ return loongarch64_find_next_kernel_text(bt, current); ++} ++ ++static int ++loongarch64_find_next_kernel_text(struct bt_info *bt, ++ struct loongarch64_unwind_frame *current) ++{ ++ ulong sp, pc; ++ ++ if (!INSTACK(current->sp, bt)) ++ return FALSE; ++ ++ sp = current->sp + sizeof(ulong); ++ sp = roundup(sp, sizeof(ulong)); ++ for (; sp < bt->stacktop; sp += sizeof(ulong)) { ++ GET_STACK_DATA(sp, &pc, sizeof(pc)); ++ pc = loongarch64_exception_pc(bt->tc->processor, pc); ++ if (!loongarch64_is_unwind_text(pc)) ++ continue; ++ ++ current->pc = pc; ++ current->sp = sp; ++ return TRUE; ++ } ++ ++ return FALSE; ++} ++ + static int + loongarch64_orc_ip(ulong ip_entry_addr, ulong *ip) + { +@@ -1218,6 +1433,7 @@ loongarch64_stackframe_init(void) + + MEMBER_OFFSET_INIT(elf_prstatus_pr_reg, "elf_prstatus", "pr_reg"); + STRUCT_SIZE_INIT(note_buf, "note_buf_t"); ++ loongarch64_irq_stack_init(); + } + + /* +-- +2.54.0 + diff --git a/0028-LoongArch64-avoid-replacing-a-valid-RA-with-stack-no.patch b/0028-LoongArch64-avoid-replacing-a-valid-RA-with-stack-no.patch new file mode 100644 index 0000000..7ac448e --- /dev/null +++ b/0028-LoongArch64-avoid-replacing-a-valid-RA-with-stack-no.patch @@ -0,0 +1,51 @@ +From a35fcc79393cbe84dbf00b663a3be705efb256e0 Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Tue, 16 Jun 2026 20:47:45 +0800 +Subject: [PATCH 28/39] LoongArch64: avoid replacing a valid RA with stack + noise + +The fallback prologue scanner may find a saved RA slot and read a word +from the stack. In corrupted, interrupted, or partially unwound frames, +that word is not guaranteed to be a kernel return address. + +Do not blindly replace the current RA with the scanned stack value. Keep +the existing RA unless the scanned value is a kernel address, or unless +the current RA is already not a kernel address. This keeps the fallback +scanner from discarding a better caller candidate with obvious stack +noise. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 16 +++++++++++----- + 1 file changed, 11 insertions(+), 5 deletions(-) + +diff --git a/loongarch64.c b/loongarch64.c +index c25a3bbf6d6c..34e29c9f2d67 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -702,11 +702,17 @@ loongarch64_analyze_function(ulong start, ulong offset, + + previous->sp = current->sp + spadjust; + +- if (rapos && !readmem(rapos, KVADDR, ¤t->ra, +- sizeof(current->ra), "RA from stack", +- RETURN_ON_ERROR)) { +- error(FATAL, "Cannot read RA from stack %lx", rapos); +- return; ++ if (rapos) { ++ ulong ra; ++ ++ if (!readmem(rapos, KVADDR, &ra, sizeof(ra), "RA from stack", ++ RETURN_ON_ERROR)) { ++ error(FATAL, "Cannot read RA from stack %lx", rapos); ++ return; ++ } ++ ++ if (IS_KVADDR(ra) || !IS_KVADDR(current->ra)) ++ current->ra = ra; + } + } + +-- +2.54.0 + diff --git a/0029-LoongArch64-print-exception-registers-only-with-bt-f.patch b/0029-LoongArch64-print-exception-registers-only-with-bt-f.patch new file mode 100644 index 0000000..1cdad3f --- /dev/null +++ b/0029-LoongArch64-print-exception-registers-only-with-bt-f.patch @@ -0,0 +1,43 @@ +From 2946d9bfa656a59156c20641e743494c316b323c Mon Sep 17 00:00:00 2001 +From: Ming Wang +Date: Tue, 16 Jun 2026 20:47:46 +0800 +Subject: [PATCH 29/39] LoongArch64: print exception registers only with bt -f + +LoongArch64 exception frames include a full pt_regs dump. Printing that +register block in normal bt output makes the backtrace harder to scan and +visually separates the exception frame from the frames that follow it. + +Keep normal bt output focused on stack frames and print the exception +register block only for full backtraces. The detailed data remains +available with bt -f, where crash already prints per-frame stack details. + +Signed-off-by: Ming Wang +--- + loongarch64.c | 9 ++++----- + 1 file changed, 4 insertions(+), 5 deletions(-) + +diff --git a/loongarch64.c b/loongarch64.c +index 34e29c9f2d67..39a05b854832 100644 +--- a/loongarch64.c ++++ b/loongarch64.c +@@ -755,13 +755,12 @@ loongarch64_dump_backtrace_entry(struct bt_info *bt, struct syment *sym, + fprintf(fp, " %s\n", buf); + } + +- if (sym && loongarch64_is_exception_entry(sym)) { +- GET_STACK_DATA(current->sp, &pt_regs, SIZE(pt_regs)); +- loongarch64_dump_exception_stack(bt, pt_regs); +- } +- + /* bt -f */ + if (bt->flags & BT_FULL) { ++ if (sym && loongarch64_is_exception_entry(sym)) { ++ GET_STACK_DATA(current->sp, &pt_regs, SIZE(pt_regs)); ++ loongarch64_dump_exception_stack(bt, pt_regs); ++ } + fprintf(fp, " " + "[PC: %016lx RA: %016lx SP: %016lx SIZE: %ld]\n", + current->pc, current->ra, current->sp, +-- +2.54.0 + diff --git a/0030-riscv64-Set-VMEMMAP-flag-to-fix-spurious-mem_map-war.patch b/0030-riscv64-Set-VMEMMAP-flag-to-fix-spurious-mem_map-war.patch new file mode 100644 index 0000000..9601d11 --- /dev/null +++ b/0030-riscv64-Set-VMEMMAP-flag-to-fix-spurious-mem_map-war.patch @@ -0,0 +1,55 @@ +From f6084e0dbf363d3d27075b3f3a193a9ae37c1df5 Mon Sep 17 00:00:00 2001 +From: Rui Qi +Date: Mon, 22 Jun 2026 21:26:39 +0800 +Subject: [PATCH 30/39] riscv64: Set VMEMMAP flag to fix spurious mem_map[] + warnings + +RISC-V kernels use CONFIG_SPARSEMEM_VMEMMAP, and the crash utility +correctly reads VMEMMAP_START/VMEMMAP_END from VMCOREINFO. However, +riscv64.c never sets the VMEMMAP flag on machdep->flags, unlike arm64 +and x86_64 which always set it. + +This causes fill_mem_map_cache() in memory.c to emit spurious warnings +when it fails to read page structs from vmemmap holes: + + WARNING: mem_map[] from ffff8d8002470000 to ffff8d8002471000 not accessible + +With SPARSEMEM_VMEMMAP, unmapped holes in the vmemmap region are +expected -- a mem_section that has no corresponding physical memory +simply has no page struct mapped. The VMEMMAP flag tells crash that +such read failures are normal, suppressing the inappropriate warnings. + +Set VMEMMAP in the PRE_GDB stage when vmemmap_vaddr is valid, and +display the flag in riscv64_dump_machdep_table() for debugging. + +Signed-off-by: Rui Qi +--- + riscv64.c | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/riscv64.c b/riscv64.c +index f0783d570c7b..a43afbdcebd2 100644 +--- a/riscv64.c ++++ b/riscv64.c +@@ -244,6 +244,8 @@ riscv64_dump_machdep_table(ulong arg) + fprintf(fp, "%sIRQ_STACKS", others++ ? "|" : ""); + if (machdep->flags & OVERFLOW_STACKS) + fprintf(fp, "%sOVERFLOW_STACKS", others++ ? "|" : ""); ++ if (machdep->flags & VMEMMAP) ++ fprintf(fp, "%sVMEMMAP", others++ ? "|" : ""); + fprintf(fp, ")\n"); + + fprintf(fp, " kvbase: %lx\n", machdep->kvbase); +@@ -1749,6 +1751,9 @@ riscv64_init(int when) + machdep->show_interrupts = generic_show_interrupts; + machdep->get_irq_affinity = generic_get_irq_affinity; + machdep->init_kernel_pgd = NULL; /* pgd set by symbol_value("swapper_pg_dir") */ ++ ++ if (machdep->machspec->vmemmap_vaddr) ++ machdep->flags |= VMEMMAP; + break; + + case POST_GDB: +-- +2.54.0 + diff --git a/0031-riscv64-Guard-verbose-output-in-vtop-page-table-walk.patch b/0031-riscv64-Guard-verbose-output-in-vtop-page-table-walk.patch new file mode 100644 index 0000000..3b68a99 --- /dev/null +++ b/0031-riscv64-Guard-verbose-output-in-vtop-page-table-walk.patch @@ -0,0 +1,93 @@ +From f5ce0c39b5b414727f8c2d7491714a80f8ff4396 Mon Sep 17 00:00:00 2001 +From: Rui Qi +Date: Mon, 22 Jun 2026 21:26:40 +0800 +Subject: [PATCH 31/39] riscv64: Guard verbose output in vtop page table walk + functions + +The riscv64_vtop_{3,4,5}level_4k() functions print "PAGE: not present" +and "invalid for address" messages unconditionally, even when called +with verbose=0. This produces noisy output during internal address +translations such as kmem -p or dump_mem_map, where kvtop() is called +non-verbosely to check whether a page struct is mapped. + +All other diagnostic output in these functions is already guarded by +"if (verbose)". Move the not-present and no_page messages inside +the verbose block to match. + +Also fix the 5-level function which used printf() instead of +fprintf(fp, ...) for the !_PAGE_PRESENT debug message, and replace +it with the same verbose-guarded format used by the 3-level and +4-level functions. + +Signed-off-by: Rui Qi +--- + riscv64.c | 15 +++++++++------ + 1 file changed, 9 insertions(+), 6 deletions(-) + +diff --git a/riscv64.c b/riscv64.c +index a43afbdcebd2..02210267862f 100644 +--- a/riscv64.c ++++ b/riscv64.c +@@ -683,8 +683,8 @@ riscv64_vtop_3level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose) + if (verbose) { + fprintf(fp, "\n"); + riscv64_translate_pte((ulong)pte_val, 0, 0); ++ fprintf(fp, " PAGE: %016lx not present\n\n", PAGEBASE(*paddr)); + } +- fprintf(fp, " PAGE: %016lx not present\n\n", PAGEBASE(*paddr)); + return FALSE; + } + +@@ -697,7 +697,8 @@ riscv64_vtop_3level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose) + + return TRUE; + no_page: +- fprintf(fp, "invalid for %lx address\n", vaddr); ++ if (verbose) ++ fprintf(fp, "invalid for %lx address\n", vaddr); + return FALSE; + } + +@@ -1275,8 +1276,8 @@ riscv64_vtop_4level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose) + if (verbose) { + fprintf(fp, "\n"); + riscv64_translate_pte((ulong)pte_val, 0, 0); ++ fprintf(fp, " PAGE: %016lx not present\n\n", PAGEBASE(*paddr)); + } +- fprintf(fp, " PAGE: %016lx not present\n\n", PAGEBASE(*paddr)); + return FALSE; + } + +@@ -1289,7 +1290,8 @@ riscv64_vtop_4level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose) + + return TRUE; + no_page: +- fprintf(fp, "invalid for %lx address\n", vaddr); ++ if (verbose) ++ fprintf(fp, "invalid for %lx address\n", vaddr); + return FALSE; + } + +@@ -1364,8 +1366,8 @@ riscv64_vtop_5level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose) + if (verbose) { + fprintf(fp, "\n"); + riscv64_translate_pte((ulong)pte_val, 0, 0); ++ fprintf(fp, " PAGE: %016lx not present\n\n", PAGEBASE(*paddr)); + } +- printf("!_PAGE_PRESENT\n"); + return FALSE; + } + +@@ -1378,7 +1380,8 @@ riscv64_vtop_5level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose) + + return TRUE; + no_page: +- fprintf(fp, "invalid for %lx address\n", vaddr); ++ if (verbose) ++ fprintf(fp, "invalid for %lx address\n", vaddr); + return FALSE; + } + +-- +2.54.0 + diff --git a/0032-riscv64-Add-get_kvaddr_ranges-callback-for-kernel-ad.patch b/0032-riscv64-Add-get_kvaddr_ranges-callback-for-kernel-ad.patch new file mode 100644 index 0000000..1439adf --- /dev/null +++ b/0032-riscv64-Add-get_kvaddr_ranges-callback-for-kernel-ad.patch @@ -0,0 +1,98 @@ +From 18d6e9f4852f19fddcd17fe14b7dfadae92fbc70 Mon Sep 17 00:00:00 2001 +From: Rui Qi +Date: Mon, 22 Jun 2026 21:26:41 +0800 +Subject: [PATCH 32/39] riscv64: Add get_kvaddr_ranges callback for kernel + address ranges + +Register an architecture-specific get_kvaddr_ranges callback so that +the search command can properly enumerate all kernel virtual address +regions including modules and vmemmap. + +Without this callback, riscv64 falls back to generic_get_kvaddr_ranges() +which only knows about KVADDR_UNITY_MAP and KVADDR_VMALLOC, missing +the modules and vmemmap regions. While the generic VMALLOC range covers +these addresses (its end is -1), the type information is lost, making +it impossible for consumers to distinguish between different kernel +address space regions. + +Signed-off-by: Rui Qi +--- + riscv64.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 48 insertions(+) + +diff --git a/riscv64.c b/riscv64.c +index 02210267862f..4dcecc2f6b2c 100644 +--- a/riscv64.c ++++ b/riscv64.c +@@ -62,6 +62,7 @@ static void riscv64_set_process_stack(struct bt_info *); + static void riscv64_set_irq_stack(struct bt_info *); + static int riscv64_on_overflow_stack(int, ulong); + static void riscv64_set_overflow_stack(struct bt_info *); ++static int riscv64_get_kvaddr_ranges(struct vaddr_range *); + + #define REG_FMT "%016lx" + #define SZ_2G 0x80000000 +@@ -1757,6 +1758,7 @@ riscv64_init(int when) + + if (machdep->machspec->vmemmap_vaddr) + machdep->flags |= VMEMMAP; ++ machdep->get_kvaddr_ranges = riscv64_get_kvaddr_ranges; + break; + + case POST_GDB: +@@ -2018,6 +2020,52 @@ riscv64_eframe_search(struct bt_info *bt) + return count; + } + ++static int ++compare_kvaddr(const void *v1, const void *v2) ++{ ++ struct vaddr_range *r1, *r2; ++ ++ r1 = (struct vaddr_range *)v1; ++ r2 = (struct vaddr_range *)v2; ++ ++ return (r1->start < r2->start ? -1 : ++ r1->start == r2->start ? 0 : 1); ++} ++ ++static int ++riscv64_get_kvaddr_ranges(struct vaddr_range *vrp) ++{ ++ int cnt; ++ ++ cnt = 0; ++ ++ vrp[cnt].type = KVADDR_UNITY_MAP; ++ vrp[cnt].start = machdep->machspec->page_offset; ++ vrp[cnt++].end = vt->high_memory; ++ ++ vrp[cnt].type = KVADDR_VMALLOC; ++ vrp[cnt].start = machdep->machspec->vmalloc_start_addr; ++ vrp[cnt++].end = last_vmalloc_address(); ++ ++ if (st->mods_installed) { ++ vrp[cnt].type = KVADDR_MODULES; ++ vrp[cnt].start = lowest_module_address(); ++ vrp[cnt++].end = roundup(highest_module_address(), ++ PAGESIZE()); ++ } ++ ++ if (machdep->flags & VMEMMAP) { ++ vrp[cnt].type = KVADDR_VMEMMAP; ++ vrp[cnt].start = machdep->machspec->vmemmap_vaddr; ++ vrp[cnt++].end = vt->node_table[vt->numnodes-1].mem_map + ++ (vt->node_table[vt->numnodes-1].size * SIZE(page)); ++ } ++ ++ qsort(vrp, cnt, sizeof(struct vaddr_range), compare_kvaddr); ++ ++ return cnt; ++} ++ + #else /* !RISCV64 */ + + void +-- +2.54.0 + diff --git a/0033-task-Introduce-I-option-to-ps-command-to-exclude-idl.patch b/0033-task-Introduce-I-option-to-ps-command-to-exclude-idl.patch new file mode 100644 index 0000000..eda06be --- /dev/null +++ b/0033-task-Introduce-I-option-to-ps-command-to-exclude-idl.patch @@ -0,0 +1,184 @@ +From 610e52023a425436d91168744dbb2ae047b63837 Mon Sep 17 00:00:00 2001 +From: Aaron Tomlin +Date: Sat, 4 Jul 2026 12:26:57 -0400 +Subject: [PATCH 33/39] task: Introduce -I option to ps command to exclude idle + tasks + +Adds a new option '-I' to the 'ps' command to filter out idle +threads/tasks from the output. This filters out the idle tasks from the +main ps list, CPU-specific block lists, and task time reports, while +enforcing mutual exclusivity with the summary (-S) option. + +Signed-off-by: Aaron Tomlin +--- + defs.h | 1 + + help.c | 3 ++- + task.c | 32 ++++++++++++++++++++++++-------- + 3 files changed, 27 insertions(+), 9 deletions(-) + +diff --git a/defs.h b/defs.h +index bdb4eb3e0980..ed91a5bc8e91 100644 +--- a/defs.h ++++ b/defs.h +@@ -5403,6 +5403,7 @@ extern long _ZOMBIE_; + #define PS_SUMMARY (0x40000) + #define PS_POLICY (0x80000) + #define PS_ACTIVE (0x100000) ++#define PS_EXCLUDE_IDLE (0x200000) + + #define PS_EXCLUSIVE (PS_TGID_LIST|PS_ARGV_ENVP|PS_TIMES|PS_CHILD_LIST|PS_PPID_LIST|PS_LAST_RUN|PS_RLIMIT|PS_MSECS|PS_SUMMARY|PS_ACTIVE) + +diff --git a/help.c b/help.c +index 5cd5c30e99a3..c88797e8ae70 100644 +--- a/help.c ++++ b/help.c +@@ -1392,7 +1392,7 @@ NULL + char *help_ps[] = { + "ps", + "display process status information", +-"[-k|-u|-G|-y policy] [-s] [-p|-c|-t|-[l|m][-C cpu]|-a|-g|-r|-S|-A|-H]\n [pid | task | command] ...", ++"[-k|-u|-G|-I|-y policy] [-s] [-p|-c|-t|-[l|m][-C cpu]|-a|-g|-r|-S|-A|-H]\n [pid | task | command] ...", + " This command displays process status for selected, or all, processes" , + " in the system. If no arguments are entered, the process data is", + " is displayed for all processes. Specific processes may be selected", +@@ -1409,6 +1409,7 @@ char *help_ps[] = { + " -k restrict the output to only kernel threads.", + " -u restrict the output to only user tasks.", + " -G display only the thread group leader in a thread group.", ++" -I exclude idle threads/tasks from the output.", + " -y policy restrict the output to tasks having a specified scheduling policy", + " expressed by its integer value or by its (case-insensitive) name;", + " multiple policies may be entered in a comma-separated list:", +diff --git a/task.c b/task.c +index d7f3c129a709..25b929d791b3 100644 +--- a/task.c ++++ b/task.c +@@ -107,8 +107,8 @@ static void foreach_cleanup(void *); + static void ps_cleanup(void *); + static char *task_pointer_string(struct task_context *, ulong, char *); + static int panic_context_adjusted(struct task_context *tc); +-static void show_last_run(struct task_context *, struct psinfo *); +-static void show_milliseconds(struct task_context *, struct psinfo *); ++static void show_last_run(ulong, struct task_context *, struct psinfo *); ++static void show_milliseconds(ulong, struct task_context *, struct psinfo *); + static char *translate_nanoseconds(ulonglong, char *); + static int sort_by_last_run(const void *arg1, const void *arg2); + static void sort_context_array_by_last_run(void); +@@ -3534,7 +3534,7 @@ cmd_ps(void) + cpuspec = NULL; + flag = 0; + +- while ((c = getopt(argcnt, args, "HASgstcpkuGlmarC:y:")) != EOF) { ++ while ((c = getopt(argcnt, args, "HAISgstcpkuGlmarC:y:")) != EOF) { + switch(c) + { + case 'k': +@@ -3645,6 +3645,10 @@ cmd_ps(void) + flag |= PS_ACTIVE; + break; + ++ case 'I': ++ flag |= PS_EXCLUDE_IDLE; ++ break; ++ + case 'H': + flag |= PS_NO_HEADER; + break; +@@ -3794,6 +3798,8 @@ show_ps_data(ulong flag, struct task_context *tc, struct psinfo *psi) + return; + if ((flag & PS_POLICY) && !has_sched_policy(tc->task, psi->policy)) + return; ++ if (tc && (flag & PS_EXCLUDE_IDLE) && is_idle_thread(tc->task)) ++ return; + if (flag & PS_GROUP) { + if (flag & (PS_LAST_RUN|PS_MSECS)) + error(FATAL, "-G not supported with -%c option\n", +@@ -3827,11 +3833,11 @@ show_ps_data(ulong flag, struct task_context *tc, struct psinfo *psi) + return; + } + if (flag & (PS_LAST_RUN)) { +- show_last_run(tc, psi); ++ show_last_run(flag, tc, psi); + return; + } + if (flag & (PS_MSECS)) { +- show_milliseconds(tc, psi); ++ show_milliseconds(flag, tc, psi); + return; + } + if (flag & PS_ARGV_ENVP) { +@@ -3988,7 +3994,7 @@ show_ps_summary(ulong flag) + char string[3]; + } ps_state[MAX_STATES]; + +- if (flag & (PS_USER|PS_KERNEL|PS_GROUP)) ++ if (flag & (PS_USER|PS_KERNEL|PS_GROUP|PS_EXCLUDE_IDLE)) + error(FATAL, "-S option cannot be used with other options\n"); + + for (s = 0; s < MAX_STATES; s++) +@@ -4023,7 +4029,7 @@ show_ps_summary(ulong flag) + * current state. + */ + static void +-show_last_run(struct task_context *tc, struct psinfo *psi) ++show_last_run(ulong flag, struct task_context *tc, struct psinfo *psi) + { + int i, c, others; + struct task_context *tcp; +@@ -4053,6 +4059,8 @@ show_last_run(struct task_context *tc, struct psinfo *psi) + for (i = 0; i < RUNNING_TASKS(); i++, tcp++) { + if (tcp->processor != c) + continue; ++ if ((flag & PS_EXCLUDE_IDLE) && is_idle_thread(tcp->task)) ++ continue; + fprintf(fp, format, task_last_run(tcp->task)); + fprintf(fp, "[%s] ", + task_state_string(tcp->task, buf, !VERBOSE)); +@@ -4066,6 +4074,8 @@ show_last_run(struct task_context *tc, struct psinfo *psi) + } else { + tcp = FIRST_CONTEXT(); + for (i = 0; i < RUNNING_TASKS(); i++, tcp++) { ++ if ((flag & PS_EXCLUDE_IDLE) && is_idle_thread(tcp->task)) ++ continue; + fprintf(fp, format, task_last_run(tcp->task)); + fprintf(fp, "[%s] ", task_state_string(tcp->task, buf, !VERBOSE)); + print_task_header(fp, tcp, FALSE); +@@ -4104,7 +4114,7 @@ translate_nanoseconds(ulonglong value, char *buf) + * sched_info.last_arrival and its current state. + */ + static void +-show_milliseconds(struct task_context *tc, struct psinfo *psi) ++show_milliseconds(ulong flag, struct task_context *tc, struct psinfo *psi) + { + int i, c, others, days, max_days; + struct task_context *tcp; +@@ -4154,6 +4164,8 @@ show_milliseconds(struct task_context *tc, struct psinfo *psi) + for (i = 0; i < RUNNING_TASKS(); i++, tcp++) { + if (tcp->processor != c) + continue; ++ if ((flag & PS_EXCLUDE_IDLE) && is_idle_thread(tcp->task)) ++ continue; + delta = rq_clock - task_last_run(tcp->task); + if (delta < 0) + delta = 0; +@@ -4188,6 +4200,8 @@ show_milliseconds(struct task_context *tc, struct psinfo *psi) + } else { + tcp = FIRST_CONTEXT(); + for (i = 0; i < RUNNING_TASKS(); i++, tcp++) { ++ if ((flag & PS_EXCLUDE_IDLE) && is_idle_thread(tcp->task)) ++ continue; + if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF)) + runq = rq_sp->value + + kt->__per_cpu_offset[tcp->processor]; +@@ -4562,6 +4576,8 @@ show_task_times(struct task_context *tcp, ulong flags) + continue; + if ((flags & PS_KERNEL) && !is_kernel_thread(tc->task)) + continue; ++ if ((flags & PS_EXCLUDE_IDLE) && is_idle_thread(tc->task)) ++ continue; + if (flags & PS_GROUP) { + tgid = task_tgid(tc->task); + if (tc->pid != tgid) { +-- +2.54.0 + diff --git a/0034-task-Introduce-Y-option-to-ps-command-to-display-sch.patch b/0034-task-Introduce-Y-option-to-ps-command-to-display-sch.patch new file mode 100644 index 0000000..5858885 --- /dev/null +++ b/0034-task-Introduce-Y-option-to-ps-command-to-display-sch.patch @@ -0,0 +1,175 @@ +From 97b7c0a6085212ccb2dcc5e1b302906db6080d51 Mon Sep 17 00:00:00 2001 +From: Aaron Tomlin +Date: Sat, 4 Jul 2026 14:14:50 -0400 +Subject: [PATCH 34/39] task: Introduce -Y option to ps command to display + scheduling policy and priority + +Adds a new option '-Y' to the 'ps' command to print scheduling policy +(e.g., NORMAL, FIFO, RR, etc.) and dynamic priority (prio) for each +task. + +Signed-off-by: Aaron Tomlin +--- + defs.h | 3 ++- + help.c | 3 ++- + task.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- + 3 files changed, 68 insertions(+), 4 deletions(-) + +diff --git a/defs.h b/defs.h +index ed91a5bc8e91..d7bcdd083671 100644 +--- a/defs.h ++++ b/defs.h +@@ -5404,8 +5404,9 @@ extern long _ZOMBIE_; + #define PS_POLICY (0x80000) + #define PS_ACTIVE (0x100000) + #define PS_EXCLUDE_IDLE (0x200000) ++#define PS_POLICY_DATA (0x400000) + +-#define PS_EXCLUSIVE (PS_TGID_LIST|PS_ARGV_ENVP|PS_TIMES|PS_CHILD_LIST|PS_PPID_LIST|PS_LAST_RUN|PS_RLIMIT|PS_MSECS|PS_SUMMARY|PS_ACTIVE) ++#define PS_EXCLUSIVE (PS_TGID_LIST|PS_ARGV_ENVP|PS_TIMES|PS_CHILD_LIST|PS_PPID_LIST|PS_LAST_RUN|PS_RLIMIT|PS_MSECS|PS_SUMMARY|PS_ACTIVE|PS_POLICY_DATA) + + #define MAX_PS_ARGS (100) /* maximum command-line specific requests */ + +diff --git a/help.c b/help.c +index c88797e8ae70..79bfa5ba0844 100644 +--- a/help.c ++++ b/help.c +@@ -1392,7 +1392,7 @@ NULL + char *help_ps[] = { + "ps", + "display process status information", +-"[-k|-u|-G|-I|-y policy] [-s] [-p|-c|-t|-[l|m][-C cpu]|-a|-g|-r|-S|-A|-H]\n [pid | task | command] ...", ++"[-k|-u|-G|-I|-y policy] [-s] [-p|-c|-t|-[l|m][-C cpu]|-a|-g|-r|-S|-A|-H|-Y]\n [pid | task | command] ...", + " This command displays process status for selected, or all, processes" , + " in the system. If no arguments are entered, the process data is", + " is displayed for all processes. Specific processes may be selected", +@@ -1473,6 +1473,7 @@ char *help_ps[] = { + " -S display a summary consisting of the number of tasks in a task state.", + " -A display only the active task on each cpu.", + " -H display no header line.", ++" -Y display the task's scheduling policy and priority.", + "\nEXAMPLES", + " Show the process status of all current tasks:\n", + " %s> ps", +diff --git a/task.c b/task.c +index 25b929d791b3..7dacb05b2406 100644 +--- a/task.c ++++ b/task.c +@@ -118,6 +118,8 @@ static void parse_task_thread(int argcnt, char *arglist[], struct task_context * + static void stack_overflow_check_init(void); + static int has_sched_policy(ulong, ulong); + static ulong task_policy(ulong); ++static const char *task_policy_name(ulong); ++static int task_prio(ulong); + static ulong sched_policy_bit_from_str(const char *); + static ulong make_sched_policy(const char *); + void crash_get_current_task_info(unsigned long *, char **); +@@ -3534,7 +3536,7 @@ cmd_ps(void) + cpuspec = NULL; + flag = 0; + +- while ((c = getopt(argcnt, args, "HAISgstcpkuGlmarC:y:")) != EOF) { ++ while ((c = getopt(argcnt, args, "HAISgstcpkuGlmarC:y:Y")) != EOF) { + switch(c) + { + case 'k': +@@ -3649,6 +3651,11 @@ cmd_ps(void) + flag |= PS_EXCLUDE_IDLE; + break; + ++ case 'Y': ++ check_ps_exclusive(flag, PS_POLICY_DATA); ++ flag |= PS_POLICY_DATA; ++ break; ++ + case 'H': + flag |= PS_NO_HEADER; + break; +@@ -3822,6 +3829,31 @@ show_ps_data(ulong flag, struct task_context *tc, struct psinfo *psi) + } + } + ++ if (flag & PS_POLICY_DATA) { ++ task_active = is_task_active(tc->task); ++ ++ if (task_active) { ++ if (hide_offline_cpu(tc->processor)) ++ fprintf(fp, "- "); ++ else ++ fprintf(fp, "> "); ++ } else ++ fprintf(fp, " "); ++ ++ fprintf(fp, "%7ld %7ld %3s %s %-12s %4d ", ++ tc->pid, task_to_pid(tc->ptask), ++ task_cpu(tc->processor, buf2, !VERBOSE), ++ task_pointer_string(tc, flag & PS_KSTACKP, buf3), ++ task_policy_name(tc->task), ++ task_prio(tc->task)); ++ ++ if (is_kernel_thread(tc->task)) ++ fprintf(fp, "[%s]\n", tc->comm); ++ else ++ fprintf(fp, "%s\n", tc->comm); ++ return; ++ } ++ + if (flag & PS_PPID_LIST) { + parent_list(tc->task); + fprintf(fp, "\n"); +@@ -3902,6 +3934,14 @@ show_ps(ulong flag, struct psinfo *psi) + mkstring(buf, VADDR_PRLEN, CENTER|RJUST, "KSTACKP") : + mkstring(buf, VADDR_PRLEN, CENTER, "TASK")); + ++ if ((flag & PS_POLICY_DATA) && !(flag & PS_NO_HEADER)) { ++ fprintf(fp, ++ " PID PPID CPU %s POLICY PRIO COMM\n", ++ flag & PS_KSTACKP ? ++ mkstring(buf, VADDR_PRLEN, CENTER|RJUST, "KSTACKP") : ++ mkstring(buf, VADDR_PRLEN, CENTER, "TASK")); ++ } ++ + if (flag & PS_SHOW_ALL) { + + if (flag & PS_TIMES) { +@@ -3994,7 +4034,7 @@ show_ps_summary(ulong flag) + char string[3]; + } ps_state[MAX_STATES]; + +- if (flag & (PS_USER|PS_KERNEL|PS_GROUP|PS_EXCLUDE_IDLE)) ++ if (flag & (PS_USER|PS_KERNEL|PS_GROUP|PS_EXCLUDE_IDLE|PS_POLICY_DATA)) + error(FATAL, "-S option cannot be used with other options\n"); + + for (s = 0; s < MAX_STATES; s++) +@@ -6050,6 +6090,28 @@ task_policy(ulong task) + return policy; + } + ++static const char * ++task_policy_name(ulong task) ++{ ++ ulong policy_bit = task_policy(task); ++ struct sched_policy_info *info; ++ ++ for (info = sched_policy_info; info->name; info++) { ++ if (policy_bit == (1UL << info->value)) ++ return info->name; ++ } ++ return "UNKNOWN"; ++} ++ ++static int ++task_prio(ulong task) ++{ ++ fill_task_struct(task); ++ if (!tt->last_task_read || INVALID_MEMBER(task_struct_prio)) ++ return 0; ++ return INT(tt->task_struct + OFFSET(task_struct_prio)); ++} ++ + /* + * Return a task's tgid. + */ +-- +2.54.0 + diff --git a/0035-Fix-failure-of-runq-g-option-on-Linux-7.2-and-later-.patch b/0035-Fix-failure-of-runq-g-option-on-Linux-7.2-and-later-.patch new file mode 100644 index 0000000..77077b8 --- /dev/null +++ b/0035-Fix-failure-of-runq-g-option-on-Linux-7.2-and-later-.patch @@ -0,0 +1,180 @@ +From 82039b0f21de4231d1fc03e8819c868a84b213f8 Mon Sep 17 00:00:00 2001 +From: Kazuhito Hagio +Date: Wed, 8 Jul 2026 02:45:50 +0000 +Subject: [PATCH 35/39] Fix failure of "runq -g" option on Linux 7.2 and later + kernels + +Kernel commit b8fea7af0e40 ("sched/fair: Allocate cfs_tg_state with +percpu allocator") changed task_group.cfs_rq from a pointer array to a +per-cpu variable allocated by the per-cpu allocator. + +Without the patch, the "runq -g" option fails with the following error +on the first time, and with a segmentation fault on the second time. + + crash> runq -g + CPU 0 + CURRENT: PID: 5687 TASK: ffff8b0bc175a2c0 COMMAND: "bash" + ROOT_TASK_GROUP: ffffffff93a55600 CFS_RQ: 0 + runq: invalid kernel virtual address: 58 type: "curr" + crash> runq -g + Segmentation fault (core dumped) + +Since there is no way to determine whether it is a per-cpu variable, +check wthether it is a pointer array or not. + +Signed-off-by: Kazuhito Hagio +--- + defs.h | 2 ++ + kernel.c | 14 ++++++++++++++ + symbols.c | 27 +++++++++++++++++++++++++++ + task.c | 30 ++++++++++++++++++++++-------- + 4 files changed, 65 insertions(+), 8 deletions(-) + +diff --git a/defs.h b/defs.h +index d7bcdd083671..e3027e2b9141 100644 +--- a/defs.h ++++ b/defs.h +@@ -691,6 +691,7 @@ struct new_utsname { + #define KMOD_PAX (0x100ULL) + #define KMOD_MEMORY (0x200ULL) + #define IRQ_DESC_TREE_MAPLE (0x400ULL) ++#define PER_CPU_CFS_RQ (0x800ULL) + + #define XEN() (kt->flags & ARCH_XEN) + #define OPENVZ() (kt->flags & ARCH_OPENVZ) +@@ -5886,6 +5887,7 @@ void parse_for_member_extended(struct datatype_member *, ulong); + void add_to_downsized(char *); + int is_downsized(char *); + int is_string(char *, char *); ++int is_ptrptr(char *, char *); + struct syment *symbol_complete_match(const char *, struct syment *); + + /* +diff --git a/kernel.c b/kernel.c +index eb9754c5e082..e53038d5d8db 100644 +--- a/kernel.c ++++ b/kernel.c +@@ -400,6 +400,18 @@ kernel_init() + MEMBER_OFFSET_INIT(task_group_rt_rq, "task_group", "rt_rq"); + MEMBER_OFFSET_INIT(task_group_parent, "task_group", "parent"); + ++ /* ++ * task_group.cfs_rq was changed from a pointer array to a per-cpu ++ * variable at Linux 7.2 (b8fea7af0e40). Since there is no way to ++ * determine it, we check whether it is a pointer array or not. ++ * - struct cfs_rq **cfs_rq; ++ * + struct cfs_rq __percpu *cfs_rq; ++ */ ++ if (VALID_MEMBER(task_group_cfs_rq)) { ++ if (!is_ptrptr("task_group", "cfs_rq")) ++ kt->flags2 |= PER_CPU_CFS_RQ; ++ } ++ + /* + * In 2.4, smp_send_stop() sets smp_num_cpus back to 1 + * in some, but not all, architectures. So if a count +@@ -6362,6 +6374,8 @@ dump_kernel_table(int verbose) + fprintf(fp, "%sKMOD_PAX", others++ ? "|" : ""); + if (kt->flags2 & KMOD_MEMORY) + fprintf(fp, "%sKMOD_MEMORY", others++ ? "|" : ""); ++ if (kt->flags2 & PER_CPU_CFS_RQ) ++ fprintf(fp, "%sPER_CPU_CFS_RQ", others++ ? "|" : ""); + fprintf(fp, ")\n"); + + fprintf(fp, " stext: %lx\n", kt->stext); +diff --git a/symbols.c b/symbols.c +index 78e400ba3756..03511c8cbe8c 100644 +--- a/symbols.c ++++ b/symbols.c +@@ -7933,6 +7933,33 @@ is_string(char *structure, char *member) + return retval; + } + ++int ++is_ptrptr(char *structure, char *member) ++{ ++ int retval; ++ char *t; ++ char buf[BUFSIZE]; ++ ++ retval = FALSE; ++ open_tmpfile(); ++ whatis_datatype(structure, STRUCT_REQUEST, pc->tmpfile); ++ rewind(pc->tmpfile); ++ while (fgets(buf, BUFSIZE, pc->tmpfile)) { ++ if (!(t = strstr(buf, "**"))) ++ continue; ++ t += 2; ++ if (t != strstr(t, member)) ++ continue; ++ t += strlen(member); ++ if (*t == ';') { ++ retval = TRUE; ++ break; ++ } ++ } ++ close_tmpfile(); ++ ++ return retval; ++} + + /* + * Generic function for dumping data structure declarations, with a small +diff --git a/task.c b/task.c +index 7dacb05b2406..b95d3d7fb2af 100644 +--- a/task.c ++++ b/task.c +@@ -9542,8 +9542,12 @@ print_parent_task_group_fair(void *t, int cpu) + readmem(tgi->task_group + OFFSET(task_group_cfs_rq), + KVADDR, &cfs_rq_c, sizeof(ulong), + "task_group cfs_rq", FAULT_ON_ERROR); +- readmem(cfs_rq_c + cpu * sizeof(ulong), KVADDR, &cfs_rq_p, +- sizeof(ulong), "task_group cfs_rq", FAULT_ON_ERROR); ++ ++ if (kt->flags2 & PER_CPU_CFS_RQ) ++ cfs_rq_p = cfs_rq_c + kt->__per_cpu_offset[cpu]; ++ else ++ readmem(cfs_rq_c + cpu * sizeof(ulong), KVADDR, &cfs_rq_p, ++ sizeof(ulong), "task_group cfs_rq", FAULT_ON_ERROR); + + print_group_header_fair(tgi->depth, cfs_rq_p, tgi); + tgi->use = 0; +@@ -9569,8 +9573,13 @@ dump_tasks_in_lower_dequeued_cfs_rq(int depth, ulong cfs_rq, int cpu, + readmem(tgi_array[i]->task_group + OFFSET(task_group_cfs_rq), + KVADDR, &cfs_rq_c, sizeof(ulong), "task_group cfs_rq", + FAULT_ON_ERROR); +- readmem(cfs_rq_c + cpu * sizeof(ulong), KVADDR, &cfs_rq_p, +- sizeof(ulong), "task_group cfs_rq", FAULT_ON_ERROR); ++ ++ if (kt->flags2 & PER_CPU_CFS_RQ) ++ cfs_rq_p = cfs_rq_c + kt->__per_cpu_offset[cpu]; ++ else ++ readmem(cfs_rq_c + cpu * sizeof(ulong), KVADDR, &cfs_rq_p, ++ sizeof(ulong), "task_group cfs_rq", FAULT_ON_ERROR); ++ + if (cfs_rq == cfs_rq_p) + continue; + +@@ -10433,10 +10442,15 @@ dump_tasks_by_task_group(void) + readmem(rt_rq + cpu * sizeof(ulong), KVADDR, + &rt_rq_p, sizeof(ulong), "task_group rt_rq", + FAULT_ON_ERROR); +- if (cfs_rq) +- readmem(cfs_rq + cpu * sizeof(ulong), KVADDR, +- &cfs_rq_p, sizeof(ulong), "task_group cfs_rq", +- FAULT_ON_ERROR); ++ if (cfs_rq) { ++ if (kt->flags2 & PER_CPU_CFS_RQ) ++ cfs_rq_p = cfs_rq + kt->__per_cpu_offset[cpu]; ++ else ++ readmem(cfs_rq + cpu * sizeof(ulong), KVADDR, ++ &cfs_rq_p, sizeof(ulong), ++ "task_group cfs_rq", FAULT_ON_ERROR); ++ } ++ + fprintf(fp, "%sCPU %d", displayed++ ? "\n" : "", cpu); + + if (hide_offline_cpu(cpu)) { +-- +2.54.0 + diff --git a/0036-Fix-runq-g-option-to-display-task_group-name-on-Linu.patch b/0036-Fix-runq-g-option-to-display-task_group-name-on-Linu.patch new file mode 100644 index 0000000..76fc42c --- /dev/null +++ b/0036-Fix-runq-g-option-to-display-task_group-name-on-Linu.patch @@ -0,0 +1,52 @@ +From f336dfa79ed94895914e132c31f2db7e09bf4ab1 Mon Sep 17 00:00:00 2001 +From: Kazuhito Hagio +Date: Wed, 8 Jul 2026 02:45:50 +0000 +Subject: [PATCH 36/39] Fix "runq -g" option to display task_group name on + Linux 6.15 and later + +Kernel commit 633488947ef66 ("kernfs: Use RCU to access +kernfs_node::parent.") changed the "parent" member name in struct +kernfs_node to "__parent". + +Without the patch, the "rung -g" option cannot display task_group's +name: + crash> runq -g + CPU 0 + CURRENT: PID: 5687 TASK: ffff8b0bc175a2c0 COMMAND: "bash" + ROOT_TASK_GROUP: ffffffff93a55600 CFS_RQ: ffff8b0cf8232240 + TASK_GROUP: ffff8b0b942a1e00 CFS_RQ: fffff1b5ffc0d540 + TASK_GROUP: ffff8b0bc0f27900 CFS_RQ: fffff1b5ffc23f80 + TASK_GROUP: ffff8b0b862fcc00 CFS_RQ: fffff1b5ffc29f00 + [120] PID: 5687 TASK: ffff8b0bc175a2c0 COMMAND: "bash" [CURRENT] + +With the patch: + crash> runq -g + CPU 0 + CURRENT: PID: 5687 TASK: ffff8b0bc175a2c0 COMMAND: "bash" + ROOT_TASK_GROUP: ffffffff93a55600 CFS_RQ: ffff8b0cf8232240 + TASK_GROUP: ffff8b0b942a1e00 CFS_RQ: fffff1b5ffc0d540 + TASK_GROUP: ffff8b0bc0f27900 CFS_RQ: fffff1b5ffc23f80 + TASK_GROUP: ffff8b0b862fcc00 CFS_RQ: fffff1b5ffc29f00 + [120] PID: 5687 TASK: ffff8b0bc175a2c0 COMMAND: "bash" [CURRENT] + +Signed-off-by: Kazuhito Hagio +--- + task.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/task.c b/task.c +index b95d3d7fb2af..a4118766fb42 100644 +--- a/task.c ++++ b/task.c +@@ -9879,6 +9879,8 @@ task_group_offset_init(void) + MEMBER_OFFSET_INIT(cgroup_kn, "cgroup", "kn"); + MEMBER_OFFSET_INIT(kernfs_node_name, "kernfs_node", "name"); + MEMBER_OFFSET_INIT(kernfs_node_parent, "kernfs_node", "parent"); ++ if (INVALID_MEMBER(kernfs_node_parent)) ++ MEMBER_OFFSET_INIT(kernfs_node_parent, "kernfs_node", "__parent"); + + MEMBER_OFFSET_INIT(task_group_siblings, "task_group", "siblings"); + MEMBER_OFFSET_INIT(task_group_children, "task_group", "children"); +-- +2.54.0 + diff --git a/0037-x86_64-Make-ORC_REG_SP-and-ORC_REG_PREV_SP-independe.patch b/0037-x86_64-Make-ORC_REG_SP-and-ORC_REG_PREV_SP-independe.patch new file mode 100644 index 0000000..1db9ec3 --- /dev/null +++ b/0037-x86_64-Make-ORC_REG_SP-and-ORC_REG_PREV_SP-independe.patch @@ -0,0 +1,70 @@ +From bfce25840cffabf0574825e796b0284ed36276bb Mon Sep 17 00:00:00 2001 +From: Tao Liu +Date: Tue, 4 Aug 2026 15:20:33 +1200 +Subject: [PATCH 37/39] x86_64: Make ORC_REG_SP and ORC_REG_PREV_SP independent + from kernel version + +Previously ORC_REG_SP and ORC_REG_PREV_SP will depend on kernel version +for their value, however this is fragile since distributions will +backport the upstream patch to a lower kernel version, thus break the +version assumption. + +This patch fixes by checking the value of ORC_REG_PREV_SP, which can be +get from orc_fp_entry. ORC_REG_PREV_SP's value can work as the indicator +of whether the current kernel have applied the upstream patch 1735858caa4b +("objtool/x86: Reorder ORC register numbering"). + +Fixes: d0ee428664f9 ("x86_64: Fix "bt" command to use correct ORC register + values on Linux 7.1 and later") + +Reviewed-by: HAGIO KAZUHITO +Reviewed-by: MUKESH KUMAR PILANIYA +Reviewed-by: Dave Young +Signed-off-by: Tao Liu +--- + x86_64.c | 29 ++++++++++++++++++++++------- + 1 file changed, 22 insertions(+), 7 deletions(-) + +diff --git a/x86_64.c b/x86_64.c +index 55648697baf3..62b27762a41f 100644 +--- a/x86_64.c ++++ b/x86_64.c +@@ -6736,13 +6736,28 @@ x86_64_ORC_init(void) + if (orc->has_signal && !orc->has_end) + machdep->flags |= ORC_6_4; + +- /* See kernel commit 1735858caa4b */ +- if (THIS_KERNEL_VERSION >= LINUX(7,1,0)) { +- ORC_REG_SP = 3; +- ORC_REG_PREV_SP = 8; +- } else { +- ORC_REG_SP = 5; +- ORC_REG_PREV_SP = 1; ++ /* Try get ORC_REG_(PREV)_SP */ ++ ORC_REG_SP = 5; ++ ORC_REG_PREV_SP = 1; ++ ++ if (kernel_symbol_exists("orc_fp_entry")) { ++ /* ++ * kernel_orc_entry_6_4 & kernel_orc_entry have the same ++ * offset of bp_reg. ++ */ ++ kernel_orc_entry_6_4 entry = {0}; ++ if (try_get_symbol_data("orc_fp_entry", sizeof(entry), &entry)) { ++ /* ++ * orc_fp_entry.bp_reg = ORC_REG_PREV_SP ++ * See kernel commit 1735858caa4b. Use ORC_REG_PREV_SP ++ * as the indicator of the commit. ++ */ ++ if (entry.bp_reg == 8) { ++ ORC_REG_SP = 3; ++ ORC_REG_PREV_SP = 8; ++ } ++ } else ++ error(WARNING, "Cannot get orc_fp_entry.bp_reg info"); + } + + machdep->flags |= ORC; +-- +2.54.0 + diff --git a/0038-symbols-optimize-symval_hash_init-with-O-1-tail-inse.patch b/0038-symbols-optimize-symval_hash_init-with-O-1-tail-inse.patch new file mode 100644 index 0000000..6d1a28d --- /dev/null +++ b/0038-symbols-optimize-symval_hash_init-with-O-1-tail-inse.patch @@ -0,0 +1,88 @@ +From 08e9d02d2c46011d1565a3bedf33c66f07cf8bde Mon Sep 17 00:00:00 2001 +From: Rui Qi +Date: Mon, 10 Aug 2026 11:30:23 +0800 +Subject: [PATCH 38/39] symbols: optimize symval_hash_init with O(1) tail + insertion + +Replace the O(n) tail traversal with O(1) tail insertion using a +dynamically-allocated per-bucket tail tracking array. The original code +traversed the entire linked list on every insert to find the tail, +resulting in O(n^2) complexity for hash table initialization. + +This reduces hash table initialization from O(n^2) to O(n). + +Benchmark on an x86_64 machine (kernel 5.10.135, ~112k symbols): + + Before: 5.54 s (mean, n=6, sigma=0.12) + After: 5.31 s (mean, n=6, sigma=0.12) + Speedup: 1.04x (-4.1%) + +Benchmark on an ARM64 Neoverse-N2 machine (kernel 5.15.152 arm64, +~132k nm symbols): + + Before: 3.4113 s (mean, n=48, sigma=0.0698) + After: 2.5073 s (mean, n=48, sigma=0.0394) + Speedup: 1.36x (-26.5%), with improved consistency. + User CPU: 4.1706 s -> 3.2768 s (-21.4%). + +Benchmark retest on a RISC-V machine (kernel +6.12.13 riscv64, ~198k nm symbols): + + $ printf 'q\n' | ./crash vmlinux /proc/kcore + + Before: 11.934 s (mean, n=3, sigma=0.880; samples: 12.921, 11.231, 11.651) + After: 10.255 s (mean, n=3, sigma=0.835; samples: 11.131, 9.467, 10.168) + Speedup: 1.16x (-14.1%) + +Signed-off-by: Rui Qi +Reviewed-by: Dave Young +--- + symbols.c | 19 ++++++++++--------- + 1 file changed, 10 insertions(+), 9 deletions(-) + +diff --git a/symbols.c b/symbols.c +index 03511c8cbe8c..a7ccdb101033 100644 +--- a/symbols.c ++++ b/symbols.c +@@ -1085,12 +1085,16 @@ symbol_value_from_proc_kallsyms(char *symname) + + /* + * Install all static kernel symbol values into the symval_hash. ++ * Uses a dynamically-allocated tails[] array for O(1) tail insertion. + */ + static void + symval_hash_init(void) + { + int index; +- struct syment *sp, *sph; ++ struct syment *sp, **tails; ++ ++ tails = (struct syment **)GETBUF(SYMVAL_HASH * sizeof(struct syment *)); ++ BZERO(tails, SYMVAL_HASH * sizeof(struct syment *)); + + for (sp = st->symtable; sp < st->symend; sp++) { + index = SYMVAL_HASH_INDEX(sp->value); +@@ -1098,15 +1102,12 @@ symval_hash_init(void) + if (st->symval_hash[index].val_hash_head == NULL) { + st->symval_hash[index].val_hash_head = sp; + st->symval_hash[index].val_hash_last = sp; +- continue; +- } +- +- sph = st->symval_hash[index].val_hash_head; +- while (sph->val_hash_next) +- sph = sph->val_hash_next; +- +- sph->val_hash_next = sp; ++ } else ++ tails[index]->val_hash_next = sp; ++ tails[index] = sp; + } ++ ++ FREEBUF(tails); + } + + /* +-- +2.54.0 + diff --git a/0039-Prevent-out-of-bounds-access-of-note_buf.patch b/0039-Prevent-out-of-bounds-access-of-note_buf.patch new file mode 100644 index 0000000..8200179 --- /dev/null +++ b/0039-Prevent-out-of-bounds-access-of-note_buf.patch @@ -0,0 +1,74 @@ +From 6bee9c7021df3d1d610ad50a45ad803bbcb9a724 Mon Sep 17 00:00:00 2001 +From: Tao Liu +Date: Wed, 29 Jul 2026 22:42:42 +1200 +Subject: [PATCH 39/39] Prevent out-of-bounds access of note_buf + +Add a boundary check for note_buf in case any protential +data access outside of the buffer. + +Signed-off-by: Tao Liu +--- + diskdump.c | 19 ++++++++++++++----- + 1 file changed, 14 insertions(+), 5 deletions(-) + +diff --git a/diskdump.c b/diskdump.c +index 20f8b19f1514..7263e2c3199c 100644 +--- a/diskdump.c ++++ b/diskdump.c +@@ -338,16 +338,20 @@ process_elf32_notes(void *note_buf, unsigned long size_note) + int num = 0; + int vmcoredd_num = 0; + int qemu_num = 0; ++ unsigned long left; + + for (index = 0; index < size_note; index += len) { + nt = note_buf + index; ++ left = size_note - index; + +- if (nt->n_type == NT_PRSTATUS) { ++ if (nt->n_type == NT_PRSTATUS && num < NR_CPUS) { + dd->nt_prstatus_percpu[num] = nt; + num++; + } + len = sizeof(Elf32_Nhdr); +- if (STRNEQ((char *)nt + len, "QEMU")) { ++ if ((left >= sizeof(Elf32_Nhdr) + MAX(nt->n_namesz + sizeof(ulong), 4)) && ++ STRNEQ((char *)nt + len, "QEMU") && ++ qemu_num < NR_CPUS) { + ulong *ptr = + (ulong *)((char *)nt + sizeof(Elf32_Nhdr) + nt->n_namesz); + dd->nt_qemucs_percpu[qemu_num] = +@@ -395,22 +399,27 @@ process_elf64_notes(void *note_buf, unsigned long size_note) + int num = 0; + int vmcoredd_num = 0; + int qemu_num = 0; ++ unsigned long left; + + for (index = 0; index < size_note; index += len) { + nt = note_buf + index; ++ left = size_note - index; + +- if (nt->n_type == NT_PRSTATUS) { ++ if (nt->n_type == NT_PRSTATUS && num < NR_CPUS) { + dd->nt_prstatus_percpu[num] = nt; + num++; + } +- if ((nt->n_type == NT_TASKSTRUCT) && ++ if ((nt->n_type == NT_TASKSTRUCT) && ++ (left >= sizeof(Elf64_Nhdr) + MAX(nt->n_namesz + sizeof(ulong), 4)) && + (STRNEQ((char *)nt + sizeof(Elf64_Nhdr), "SNAP"))) { + pc->flags2 |= (LIVE_DUMP|SNAP); + dd->snapshot_task = + *((ulong *)((char *)nt + sizeof(Elf64_Nhdr) + nt->n_namesz)); + } + len = sizeof(Elf64_Nhdr); +- if (STRNEQ((char *)nt + len, "QEMU")) { ++ if ((left >= sizeof(Elf64_Nhdr) + MAX(nt->n_namesz + sizeof(ulong), 4)) && ++ STRNEQ((char *)nt + len, "QEMU") && ++ qemu_num < NR_CPUS) { + ulong *ptr = + (ulong *)((char *)nt + sizeof(Elf64_Nhdr) + nt->n_namesz); + dd->nt_qemucs_percpu[qemu_num] = +-- +2.54.0 + diff --git a/crash-9.0.0_build.patch b/crash-9.0.0_build.patch index 9618cd2..c852ec7 100644 --- a/crash-9.0.0_build.patch +++ b/crash-9.0.0_build.patch @@ -1,5 +1,5 @@ ---- crash-9.0.1/Makefile.orig -+++ crash-9.0.1/Makefile +--- crash-9.0.2/Makefile.orig ++++ crash-9.0.2/Makefile @@ -204,7 +204,7 @@ GDB_FLAGS= # TARGET_CFLAGS will be configured automatically by configure TARGET_CFLAGS= @@ -18,8 +18,8 @@ @echo "../../${PROGRAM} ../../${PROGRAM}lib.a" > ${GDB}/gdb/mergeobj @rm -f ${PROGRAM} @if [ ! -f ${GDB}/config.status ]; then \ ---- crash-9.0.1/configure.c.orig -+++ crash-9.0.1/configure.c +--- crash-9.0.2/configure.c.orig ++++ crash-9.0.2/configure.c @@ -810,7 +810,8 @@ build_configure(struct supported_gdb_version *sp) fprintf(fp2, "%s\n", sp->GDB); sprintf(target_data.gdb_version, "%s", &sp->GDB[4]); diff --git a/crash.spec b/crash.spec index fdbfe4d..f7c801a 100644 --- a/crash.spec +++ b/crash.spec @@ -3,7 +3,7 @@ # Summary: Kernel analysis utility for live systems, netdump, diskdump, kdump, LKCD or mcore dumpfiles Name: crash -Version: 9.0.1 +Version: 9.0.2 Release: 1%{?dist} License: GPL-3.0-only Source0: https://github.com/crash-utility/crash/archive/crash-%{version}.tar.gz @@ -19,6 +19,44 @@ Provides: bundled(libiberty) Provides: bundled(gdb) = 16.2 Patch0: lzo_snappy_zstd.patch Patch1: crash-9.0.0_build.patch +Patch3: 0002-Mark-start-of-9.0.3-development-phase-with-version-9.patch +Patch4: 0003-RISCV64-introduce-riscv64_VTOP-and-riscv64_PTOV.patch +Patch5: 0004-Fix-wrong-detection-of-LIVEPATCH-on-old-kernels.patch +Patch6: 0005-Fix-unwinding-with-32k-stacks-in-ppc64le.patch +Patch7: 0006-arm64-fix-gdb-register-feeding-for-exception-frames-.patch +Patch8: 0007-RISCV64-fix-pmd-address-calculation-in-riscv64_vtop_.patch +Patch9: 0008-symbols-optimize-symname_hash-with-larger-table-and-.patch +Patch10: 0009-RISCV64-add-mapping-symbol-filter-in-riscv64_verify_.patch +Patch11: 0010-Fix-get_xtime-for-kernel-6.13-and-higher.patch +Patch12: 0011-symbols-Add-support-for-mod-symtab-with-combined-GPL.patch +Patch13: 0012-add-folio_order-function.patch +Patch14: 0013-xarray-add-large-folio-support.patch +Patch15: 0014-add-files-n-command-for-an-inode.patch +Patch16: 0015-x86_64-Fix-bt-command-for-noreturn-functions.patch +Patch17: 0016-x86_64-Fix-bt-command-to-use-correct-ORC-register-va.patch +Patch18: 0017-Fix-kmem-i-option-to-display-swap-usage-on-Linux-6.1.patch +Patch19: 0018-Fix-swap-command-on-Linux-7.1-and-later.patch +Patch20: 0019-LoongArch64-Fix-CPU-registers-reading-from-dump-note.patch +Patch21: 0020-LoongArch64-Fix-pt_regs-initialization-for-active-ta.patch +Patch22: 0021-LoongArch64-Support-backtracing-across-exception-bou.patch +Patch23: 0022-LoongArch64-Fix-stack-frame-loop-bounds-for-exceptio.patch +Patch24: 0023-LoongArch64-Add-dummy-eframe_search-to-avoid-bt-e-se.patch +Patch25: 0024-LoongArch64-print-exception-return-address-as-ERA.patch +Patch26: 0025-LoongArch64-resolve-relocated-exception-vector-addre.patch +Patch27: 0026-LoongArch64-add-initial-ORC-unwinder-support.patch +Patch28: 0027-LoongArch64-unwind-dumpfile-active-tasks-from-IRQ-st.patch +Patch29: 0028-LoongArch64-avoid-replacing-a-valid-RA-with-stack-no.patch +Patch30: 0029-LoongArch64-print-exception-registers-only-with-bt-f.patch +Patch31: 0030-riscv64-Set-VMEMMAP-flag-to-fix-spurious-mem_map-war.patch +Patch32: 0031-riscv64-Guard-verbose-output-in-vtop-page-table-walk.patch +Patch33: 0032-riscv64-Add-get_kvaddr_ranges-callback-for-kernel-ad.patch +Patch34: 0033-task-Introduce-I-option-to-ps-command-to-exclude-idl.patch +Patch35: 0034-task-Introduce-Y-option-to-ps-command-to-display-sch.patch +Patch36: 0035-Fix-failure-of-runq-g-option-on-Linux-7.2-and-later-.patch +Patch37: 0036-Fix-runq-g-option-to-display-task_group-name-on-Linu.patch +Patch38: 0037-x86_64-Make-ORC_REG_SP-and-ORC_REG_PREV_SP-independe.patch +Patch39: 0038-symbols-optimize-symval_hash_init-with-O-1-tail-inse.patch +Patch40: 0039-Prevent-out-of-bounds-access-of-note_buf.patch %description The core analysis suite is a self-contained tool that can be used to @@ -40,6 +78,44 @@ offered by Mission Critical Linux, or the LKCD kernel patch. %setup -n %{name}-%{version} -q %patch -P 0 -p1 -b lzo_snappy_zstd.patch %patch -P 1 -p1 +%patch -P 3 -p1 +%patch -P 4 -p1 +%patch -P 5 -p1 +%patch -P 6 -p1 +%patch -P 7 -p1 +%patch -P 8 -p1 +%patch -P 9 -p1 +%patch -P 10 -p1 +%patch -P 11 -p1 +%patch -P 12 -p1 +%patch -P 13 -p1 +%patch -P 14 -p1 +%patch -P 15 -p1 +%patch -P 16 -p1 +%patch -P 17 -p1 +%patch -P 18 -p1 +%patch -P 19 -p1 +%patch -P 20 -p1 +%patch -P 21 -p1 +%patch -P 22 -p1 +%patch -P 23 -p1 +%patch -P 24 -p1 +%patch -P 25 -p1 +%patch -P 26 -p1 +%patch -P 27 -p1 +%patch -P 28 -p1 +%patch -P 29 -p1 +%patch -P 30 -p1 +%patch -P 31 -p1 +%patch -P 32 -p1 +%patch -P 33 -p1 +%patch -P 34 -p1 +%patch -P 35 -p1 +%patch -P 36 -p1 +%patch -P 37 -p1 +%patch -P 38 -p1 +%patch -P 39 -p1 +%patch -P 40 -p1 %build @@ -65,6 +141,9 @@ cp -p defs.h %{buildroot}%{_includedir}/crash %{_includedir}/* %changelog +* Tue Aug 11 2026 Tao Liu - 9.0.1-2 +- Rebase to upstream crash 9.0.2 + * Mon Nov 24 2025 Tao Liu - 9.0.1-1 - Rebase to upstream crash 9.0.1 diff --git a/lzo_snappy_zstd.patch b/lzo_snappy_zstd.patch index c5bea12..24339a8 100644 --- a/lzo_snappy_zstd.patch +++ b/lzo_snappy_zstd.patch @@ -1,5 +1,5 @@ ---- crash-9.0.1/Makefile.orig -+++ crash-9.0.1/Makefile +--- crash-9.0.2/Makefile.orig ++++ crash-9.0.2/Makefile @@ -256,7 +256,7 @@ all: make_configure gdb_merge: force @if [ ! -f ${GDB}/README ]; then \ @@ -9,8 +9,8 @@ @echo "../../${PROGRAM} ../../${PROGRAM}lib.a" > ${GDB}/gdb/mergeobj @rm -f ${PROGRAM} @if [ ! -f ${GDB}/config.status ]; then \ ---- crash-9.0.1/diskdump.c.orig -+++ crash-9.0.1/diskdump.c +--- crash-9.0.2/diskdump.c.orig ++++ crash-9.0.2/diskdump.c @@ -23,6 +23,9 @@ * GNU General Public License for more details. */ diff --git a/sources b/sources index 48ee17d..5a355d8 100644 --- a/sources +++ b/sources @@ -1,2 +1,2 @@ -SHA512 (crash-9.0.1.tar.gz) = 3f49d9ce6ab82265f35e3ed539d0c310caa1fbfb68bd5edc84c033a2ccd5a48447eabd5663e5ea0a45100eee792f8867182ad4e4cae5ea4de3fc3abeda0e82b2 SHA512 (gdb-16.2.tar.gz) = c44a6cf6cdd6e92f1f4dda7d888ce3e7fdde65ca8736092e2b0a4c369ea09b499824a1d078f668f276fa6d00d311e9ec485231cb7b841479a4fabbfd65778084 +SHA512 (crash-9.0.2.tar.gz) = c51164065ad7c65e8fda91c01b514b464e38197674e738f496e32669f749c8a34c60c1e65f435cfc23b7b035f5f8022c7a3ef39fb4de8cb42adb3f0537b1f72d