From ba8b3b3e54682da1801dc3b0b2ea52ae8b5ec2bf Mon Sep 17 00:00:00 2001
From: lipengfei28 <lipengfei28@xiaomi.com>
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 <lipengfei28@xiaomi.com>
---
 arm64.c         | 121 +++++++++++++++++++++++++++++++++++++-------
 gdb_interface.c | 130 +++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 232 insertions(+), 19 deletions(-)

diff --git a/arm64.c b/arm64.c
index c125655..a758e3c 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++);
 		}		
 	}
@@ -4263,8 +4275,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 +4344,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 +4632,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

