From 4d832a33ebd80bd109cc5a47f98c6b35fdcbd956 Mon Sep 17 00:00:00 2001
From: lipengfei28 <[email protected]>
Date: Fri, 23 Jan 2026 16:24:07 +0800
Subject: [PATCH] arm64: Fix broken/incomplete gdb backtrace and unify output
format
This patch fixes multiple issues with 'gdb bt' on ARM64, where the backtrace
would be interrupted, contain garbage threads, or display fragmented output.
1. Fix Out-of-Bounds Read in Exception Frame Handling:
In `arm64_print_exception_frame`, the code previously used `memcpy` to copy
`sizeof(struct arm64_pt_regs)` bytes from a `struct arm64_stackframe *`
source.
Since `stackframe` is significantly smaller than `pt_regs`, this caused an
out-of-bounds read, populating the GDB thread registers with stack garbage
(often resulting in invalid addresses like -3/0xff...fd).
This is fixed by manually copying only the valid registers (PC, SP, FP,
etc.)
and properly initializing the bitmap.
2. Bridge the Gap Between IRQ and Process Stacks:
Previously, GDB unwinding would stop at `call_on_irq_stack` because it could
not automatically unwind through the assembly trampoline back to the process
stack.
Modified `arm64_switch_stack` (and the overflow variant) to "peek" one frame
ahead (reading the saved FP/PC of the caller) before registering the new
GDB substack. This effectively bridges the discontinuity, allowing GDB to
show frames like `do_interrupt_handler` that were previously missing.
3. Unify and Format GDB Output:
Modified `gdb_interface.c` to:
- Strip "Thread <id>" headers to present a continuous backtrace similar to
the native `crash bt`.
- Renumber stack frames sequentially (e.g., #0 to #30) instead of resetting
at each stack switch.
- Add indentation/alignment for frames where GDB omits the address (e.g.,
inline functions) to improve readability.
4. Prevent Invalid Thread Creation:
Added checks to ensure a GDB substack is only created if the Program Counter
(PC) is non-zero, preventing the display of "corrupt" or empty threads.
Tested on: Android 6.x ARM64
Signed-off-by: lipengfei28 <[email protected]>
---
arm64.c | 117 +++++++++++++++++++++++++++++++++++++------
gdb_interface.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 229 insertions(+), 18 deletions(-)
diff --git a/arm64.c b/arm64.c
index c125655..f842739 100644
--- a/arm64.c
+++ b/arm64.c
@@ -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,12 @@ 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->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 +3934,12 @@ 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->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++);
}
}
@@ -4263,8 +4273,40 @@ 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 {
+ 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));
+ struct user_regs_bitmap_struct *ur_ptr =
extra_stacks_regs[extra_stacks_idx];
+
+ ulong next_fp = GET_STACK_ULONG(frame->fp);
+ ulong next_pc = GET_STACK_ULONG(frame->fp + 8);
+ ulong 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++);
+ }
+ }
return KERNEL_MODE;
}
@@ -4300,8 +4342,40 @@ 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 {
+ 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));
+ struct user_regs_bitmap_struct *ur_ptr =
extra_stacks_regs[extra_stacks_idx];
+
+ ulong next_fp = GET_STACK_ULONG(frame->fp);
+ ulong next_pc = GET_STACK_ULONG(frame->fp + 8);
+ ulong 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++);
+ }
+ }
return KERNEL_MODE;
}
@@ -4556,16 +4630,27 @@ 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 !=
- ((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++);
+ {
+ struct user_regs_bitmap_struct *ur_ptr =
extra_stacks_regs[extra_stacks_idx];
+ int i;
+
+ 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 &&
+ ur_ptr->ur.pc !=
+ ((struct user_regs_bitmap_struct
*)(bt->machdep))->ur.pc))) {
+ gdb_add_substack (extra_stacks_idx++);
+ }
}
}
}
diff --git a/gdb_interface.c b/gdb_interface.c
index 9f76f85..d14144e 100644
--- a/gdb_interface.c
+++ b/gdb_interface.c
@@ -16,6 +16,7 @@
*/
#include "defs.h"
+#include <ctype.h>
#if !defined(GDB_10_2) && !defined(GDB_16_2)
static void exit_after_gdb_info(void);
@@ -779,22 +780,53 @@ strip_redirection(char *buf)
/*
* Command for passing strings directly to gdb.
*/
+static void format_and_print_gdb_bt(FILE *input_fp);
+
void
cmd_gdb(void)
{
char buf[BUFSIZE];
char **argv;
+ int i;
argv = STREQ(args[0], "gdb") ? &args[1] : &args[0];
if (*argv == NULL)
cmd_usage(pc->curcmd, SYNOPSIS);
+ if (STREQ(*argv, "bt")) {
+ FILE *tmp_fp;
+
+ if ((tmp_fp = tmpfile()) == NULL) {
+ error(FATAL, "cannot create temporary file for GDB
output\n");
+ }
+
+ strcpy(buf, "thread apply all bt");
+ /* Append any arguments that were passed to 'bt' */
+ /* args[0] is "gdb", args[1] is "bt", so options start at index 2
*/
+ for (i = 2; i < argcnt; i++) {
+ strcat(buf, " ");
+ strcat(buf, args[i]);
+ }
+
+ if (!gdb_pass_through(buf, tmp_fp, GNU_RETURN_ON_ERROR)) {
+ fclose(tmp_fp);
+ error(INFO, "gdb request failed: %s\n", buf);
+ return;
+ }
+
+ rewind(tmp_fp);
+ format_and_print_gdb_bt(tmp_fp);
+
+ fclose(tmp_fp);
+ return;
+ }
+
if (STREQ(*argv, "set") && argv[1]) {
/*
* Intercept set commands in case something has to be done
- * here or elsewhere.
- */
+ * here or elsewhere.
+ */
if (STREQ(argv[1], "gdb")) {
cmd_set();
return;
@@ -821,6 +853,100 @@ cmd_gdb(void)
}
}
+#define MAX_BT_LINES 1024
+#define MAX_THREAD_BLOCKS 64
+
+/*
+ * Helper function to parse 'thread apply all bt' output, reverse the
+ * thread blocks, and print the result.
+ */
+static void format_and_print_gdb_bt(FILE *input_fp)
+{
+ char *lines[MAX_BT_LINES];
+ int line_count = 0;
+ int thread_starts[MAX_THREAD_BLOCKS];
+ int thread_count = 0;
+ char line_buffer[BUFSIZE];
+ int i, j;
+ int global_frame_cnt = 0;
+
+ // Read all lines into memory
+ while (fgets(line_buffer, BUFSIZE, input_fp) && line_count < MAX_BT_LINES)
{
+ if (strstr(line_buffer, "Thread ") == line_buffer) {
+ if (thread_count < MAX_THREAD_BLOCKS) {
+ thread_starts[thread_count++] = line_count;
+ }
+ }
+ lines[line_count] = strdup(line_buffer);
+ if (!lines[line_count]) {
+ error(FATAL, "strdup failed while reading gdb output\n");
+ }
+ line_count++;
+ }
+
+ if (thread_count == 0) { // If no threads, just print everything as is.
+ for (i = 0; i < line_count; i++) {
+ fputs(lines[i], fp);
+ }
+ } else {
+ // Iterate threads in reverse order to fix the display order
+ for (i = thread_count - 1; i >= 0; i--) {
+ int start_line = thread_starts[i];
+ int end_line = (i == thread_count - 1) ? line_count :
thread_starts[i+1];
+
+ // Print thread frames, stripping header and renumbering
+ for (j = start_line; j < end_line; j++) {
+ char *line = lines[j];
+ // Skip "Thread " line
+ if (strncmp(line, "Thread ", 7) == 0) continue;
+ // Skip "Backtrace stopped" or similar noise if desired,
+ // but usually GDB prints "Backtrace stopped" at the very end.
+ // We'll keep it for info unless it's in the middle of our
merge.
+ // Actually, duplicate frames or stops might appear.
+ // For now, simple renumbering.
+
+ char *ptr = line;
+ while (*ptr == ' ' || *ptr == '\t') ptr++;
+
+ if (*ptr == '#') {
+ // Frame line: #0 0x...
+ char *rest = ptr + 1;
+ while (isdigit(*rest)) rest++; // Skip old number
+
+ // Parse content to check for address
+ char *p = rest;
+ while (*p == ' ' || *p == '\t') p++;
+
+ int has_addr = (strncmp(p, "0x", 2) == 0);
+
+ // Print number
+ fprintf(fp, "#%-3d", global_frame_cnt++);
+
+ // Align if address is missing
+ if (!has_addr) {
+ // Align function name with frames that have addresses
+ // 64-bit: 0x... (18 chars) + " in " (4 chars) = 22
chars
+ // 32-bit: 0x... (10 chars) + " in " (4 chars) = 14
chars
+ int width = (machdep->bits == 64) ? 22 : 14;
+ for (int k = 0; k < width; k++) fputc(' ', fp);
+ }
+
+ fputs(rest, fp);
+ } else {
+ // Other lines (e.g. variable info, code)
+ fputs(line, fp);
+ }
+ }
+ }
+ }
+
+ // Cleanup
+ for (i = 0; i < line_count; i++) {
+ free(lines[i]);
+ }
+}
+
+
/*
* The gdb target_xfer_memory() has a hook installed to re-route
* all memory accesses back here; reads of 1 or 4 bytes come primarily
--
2.34.1
#/******本邮件及其附件含有小米公司的保密信息,仅限于发送给上面地址中列出的个人或群组。禁止任何其他人以任何形式使用(包括但不限于全部或部分地泄露、复制、或散发)本邮件中的信息。如果您错收了本邮件,请您立即电话或邮件通知发件人并删除本邮件!
This e-mail and its attachments contain confidential information from XIAOMI,
which is intended only for the person or entity whose address is listed above.
Any use of the information contained herein in any way (including, but not
limited to, total or partial disclosure, reproduction, or dissemination) by
persons other than the intended recipient(s) is prohibited. If you receive this
e-mail in error, please notify the sender by phone or email immediately and
delete it!******/#
--
Crash-utility mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://${domain_name}/admin/lists/devel.lists.crash-utility.osci.io/
Contribution Guidelines: https://github.com/crash-utility/crash/wiki