Skip to content

Commit

Permalink
ppc64: Add gdb stack unwind support
Browse files Browse the repository at this point in the history
Currently, gdb passthroughs of 'bt', 'frame', 'up', 'down', 'info
locals' don't work. This is due to gdb not knowing the register values to
unwind the stack frames

Every gdb passthrough goes through `gdb_interface`. And then, gdb expects
`crash_target::fetch_registers` to give it the register values, which is
dependent on `machdep->get_current_task_reg` to read the register values for
specific architecture.

                                      ----------------------------
           gdb passthrough (eg. "bt") |                          |
   crash   -------------------------> |                          |
                                      |      gdb_interface       |
                                      |                          |
                                      |                          |
                                      |  ----------------------  |
                 fetch_registers      |  |                    |  |
crash_target<-------------------------+--|        gdb         |  |
            --------------------------+->|                    |  |
              Registers (SP,NIP, etc.)|  |                    |  |
                                      |  |                    |  |
                                      |  ----------------------  |
                                      ----------------------------

Implement `machdep->get_current_task_reg` on PPC64, so that crash provides the
register values to gdb to unwind stack frames properly

With these changes, on powerpc, 'bt' command output in gdb mode, will look
like this:

    gdb> bt
    #0  0xc0000000002a53e8 in crash_setup_regs (oldregs=<optimized out>, newregs=0xc00000000486f8d8) at ./arch/powerpc/include/asm/kexec.h:69
    crash-utility#1  __crash_kexec (regs=<optimized out>) at kernel/kexec_core.c:974
    crash-utility#2  0xc000000000168918 in panic (fmt=<optimized out>) at kernel/panic.c:358
    crash-utility#3  0xc000000000b735f8 in sysrq_handle_crash (key=<optimized out>) at drivers/tty/sysrq.c:155
    crash-utility#4  0xc000000000b742cc in __handle_sysrq (key=key@entry=99, check_mask=check_mask@entry=false) at drivers/tty/sysrq.c:602
    crash-utility#5  0xc000000000b7506c in write_sysrq_trigger (file=<optimized out>, buf=<optimized out>, count=2, ppos=<optimized out>) at drivers/tty/sysrq.c:1163
    crash-utility#6  0xc00000000069a7bc in pde_write (ppos=<optimized out>, count=<optimized out>, buf=<optimized out>, file=<optimized out>, pde=0xc000000009ed3a80) at fs/proc/inode.c:340
    crash-utility#7  proc_reg_write (file=<optimized out>, buf=<optimized out>, count=<optimized out>, ppos=<optimized out>) at fs/proc/inode.c:352
    crash-utility#8  0xc0000000005b3bbc in vfs_write (file=file@entry=0xc00000009dda7d00, buf=buf@entry=0xebcfc7c6040 <error: Cannot access memory at address 0xebcfc7c6040>, count=count@entry=2, pos=pos@entry=0xc00000000486fda0) at fs/read_write.c:582

instead of earlier output without this patch:

    gdb> bt
    #0  <unavailable> in ?? ()
    Backtrace stopped: previous frame identical to this frame (corrupt stack?)

Also, 'get_dumpfile_regs' has been introduced to get registers from
multiple supported vmcore formats. Correspondingly a flag 'BT_NO_PRINT_REGS'
has been introduced to tell helper functions to get registers, to not
print registers with every call to backtrace in gdb.

Note: This feature to support GDB unwinding doesn't support live debugging

[lijiang: squash these five patches(see the Link) into one patch]

Link: https://www.mail-archive.com/[email protected]/msg01084.html
Link: https://www.mail-archive.com/[email protected]/msg01083.html
Link: https://www.mail-archive.com/[email protected]/msg01089.html
Link: https://www.mail-archive.com/[email protected]/msg01090.html
Link: https://www.mail-archive.com/[email protected]/msg01091.html
Co-developed-by:: Tao Liu <[email protected]>
Signed-off-by: Aditya Gupta <[email protected]>
  • Loading branch information
adi-g15-ibm authored and liutgnu committed Dec 1, 2024
1 parent 799f60a commit 9b97fb1
Show file tree
Hide file tree
Showing 6 changed files with 437 additions and 13 deletions.
131 changes: 131 additions & 0 deletions defs.h
Original file line number Diff line number Diff line change
Expand Up @@ -982,6 +982,7 @@ struct bt_info {
ulong eframe_ip;
ulong radix;
ulong *cpumask;
bool need_free;
};

#define STACK_OFFSET_TYPE(OFF) \
Expand Down Expand Up @@ -6162,6 +6163,7 @@ int load_module_symbols_helper(char *);
void unlink_module(struct load_module *);
int check_specified_module_tree(char *, char *);
int is_system_call(char *, ulong);
void get_dumpfile_regs(struct bt_info*, ulong*, ulong*);
void generic_dump_irq(int);
void generic_get_irq_affinity(int);
void generic_show_interrupts(int, ulong *);
Expand Down Expand Up @@ -6261,6 +6263,7 @@ ulong cpu_map_addr(const char *type);
#define BT_REGS_NOT_FOUND (0x4000000000000ULL)
#define BT_OVERFLOW_STACK (0x8000000000000ULL)
#define BT_SKIP_IDLE (0x10000000000000ULL)
#define BT_NO_PRINT_REGS (0x20000000000000ULL)
#define BT_SYMBOL_OFFSET (BT_SYMBOLIC_ARGS)

#define BT_REF_HEXVAL (0x1)
Expand Down Expand Up @@ -7966,6 +7969,7 @@ extern unsigned char *gdb_prettyprint_arrays;
extern unsigned int *gdb_repeat_count_threshold;
extern unsigned char *gdb_stop_print_at_null;
extern unsigned int *gdb_output_radix;
int is_kvaddr(ulong);

/*
* gdb/top.c
Expand Down Expand Up @@ -8066,6 +8070,13 @@ extern int have_full_symbols(void);
#define XEN_HYPERVISOR_ARCH
#endif

#ifndef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
#endif
#define FIELD_SIZEOF(t, f) (sizeof(((t*)0)->f))
#define REG_SEQ(TYPE, MEMBER) \
(offsetof(struct TYPE, MEMBER) / FIELD_SIZEOF(struct TYPE, MEMBER))

/*
* Register numbers must be in sync with gdb/features/i386/64bit-core.c
* to make crash_target->fetch_registers() ---> machdep->get_cpu_reg()
Expand Down Expand Up @@ -8115,6 +8126,126 @@ enum x86_64_regnum {
LAST_REGNUM
};

/*
* Register numbers to make crash_target->fetch_registers()
* ---> machdep->get_current_task_reg() work properly.
*
* These register numbers and names are given according to output of
* `rs6000_register_name`, because that is what was being used by
* crash_target::fetch_registers in case of PPC64
*/
enum ppc64_regnum {
PPC64_R0_REGNUM = 0,
PPC64_R1_REGNUM,
PPC64_R2_REGNUM,
PPC64_R3_REGNUM,
PPC64_R4_REGNUM,
PPC64_R5_REGNUM,
PPC64_R6_REGNUM,
PPC64_R7_REGNUM,
PPC64_R8_REGNUM,
PPC64_R9_REGNUM,
PPC64_R10_REGNUM,
PPC64_R11_REGNUM,
PPC64_R12_REGNUM,
PPC64_R13_REGNUM,
PPC64_R14_REGNUM,
PPC64_R15_REGNUM,
PPC64_R16_REGNUM,
PPC64_R17_REGNUM,
PPC64_R18_REGNUM,
PPC64_R19_REGNUM,
PPC64_R20_REGNUM,
PPC64_R21_REGNUM,
PPC64_R22_REGNUM,
PPC64_R23_REGNUM,
PPC64_R24_REGNUM,
PPC64_R25_REGNUM,
PPC64_R26_REGNUM,
PPC64_R27_REGNUM,
PPC64_R28_REGNUM,
PPC64_R29_REGNUM,
PPC64_R30_REGNUM,
PPC64_R31_REGNUM,

PPC64_F0_REGNUM = 32,
PPC64_F1_REGNUM,
PPC64_F2_REGNUM,
PPC64_F3_REGNUM,
PPC64_F4_REGNUM,
PPC64_F5_REGNUM,
PPC64_F6_REGNUM,
PPC64_F7_REGNUM,
PPC64_F8_REGNUM,
PPC64_F9_REGNUM,
PPC64_F10_REGNUM,
PPC64_F11_REGNUM,
PPC64_F12_REGNUM,
PPC64_F13_REGNUM,
PPC64_F14_REGNUM,
PPC64_F15_REGNUM,
PPC64_F16_REGNUM,
PPC64_F17_REGNUM,
PPC64_F18_REGNUM,
PPC64_F19_REGNUM,
PPC64_F20_REGNUM,
PPC64_F21_REGNUM,
PPC64_F22_REGNUM,
PPC64_F23_REGNUM,
PPC64_F24_REGNUM,
PPC64_F25_REGNUM,
PPC64_F26_REGNUM,
PPC64_F27_REGNUM,
PPC64_F28_REGNUM,
PPC64_F29_REGNUM,
PPC64_F30_REGNUM,
PPC64_F31_REGNUM,

PPC64_PC_REGNUM = 64,
PPC64_MSR_REGNUM = 65,
PPC64_CR_REGNUM = 66,
PPC64_LR_REGNUM = 67,
PPC64_CTR_REGNUM = 68,
PPC64_XER_REGNUM = 69,
PPC64_FPSCR_REGNUM = 70,

PPC64_VR0_REGNUM = 106,
PPC64_VR1_REGNUM,
PPC64_VR2_REGNUM,
PPC64_VR3_REGNUM,
PPC64_VR4_REGNUM,
PPC64_VR5_REGNUM,
PPC64_VR6_REGNUM,
PPC64_VR7_REGNUM,
PPC64_VR8_REGNUM,
PPC64_VR9_REGNUM,
PPC64_VR10_REGNUM,
PPC64_VR11_REGNUM,
PPC64_VR12_REGNUM,
PPC64_VR13_REGNUM,
PPC64_VR14_REGNUM,
PPC64_VR15_REGNUM,
PPC64_VR16_REGNUM,
PPC64_VR17_REGNUM,
PPC64_VR18_REGNUM,
PPC64_VR19_REGNUM,
PPC64_VR20_REGNUM,
PPC64_VR21_REGNUM,
PPC64_VR22_REGNUM,
PPC64_VR23_REGNUM,
PPC64_VR24_REGNUM,
PPC64_VR25_REGNUM,
PPC64_VR26_REGNUM,
PPC64_VR27_REGNUM,
PPC64_VR28_REGNUM,
PPC64_VR29_REGNUM,
PPC64_VR30_REGNUM,
PPC64_VR31_REGNUM,

PPC64_VSCR_REGNUM = 138,
PPC64_VRSAVE_REGNU = 139
};

/* crash_target.c */
extern int gdb_change_thread_context (void);

Expand Down
100 changes: 99 additions & 1 deletion gdb-10.2.patch
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ tar xvzmf gdb-10.2.tar.gz \
gdb-10.2/gdb/dwarf2/read.c \
gdb-10.2/gdb/ada-lang.c \
gdb-10.2/gdb/objfiles.h \
gdb-10.2/bfd/elf-bfd.h
gdb-10.2/bfd/elf-bfd.h \
gdb-10.2/gdb/stack.c \
gdb-10.2/gdb/ui-file.h

# For newly added gdb files, remove them to be its original state.

Expand Down Expand Up @@ -16118,3 +16120,99 @@ exit 0
subclass (SYMBOL_NONE)
{
/* We can't use an initializer list for members of a base class, and
--- gdb-10.2/gdb/ui-file.h.orig
+++ gdb-10.2/gdb/ui-file.h
@@ -195,6 +195,7 @@ public:

bool can_emit_style_escape () override;

+ FILE *get_stream(void);
/* Sets the internal stream to FILE, and saves the FILE's file
descriptor in M_FD. */
void set_stream (FILE *file);
--- gdb-10.2/gdb/ui-file.c.orig
+++ gdb-10.2/gdb/ui-file.c
@@ -161,6 +161,12 @@ stdio_file::~stdio_file ()
fclose (m_file);
}

+FILE*
+stdio_file::get_stream(void)
+{
+ return m_file;
+}
+
void
stdio_file::set_stream (FILE *file)
{
--- gdb-10.2/gdb/symtab.c.orig
+++ gdb-10.2/gdb/symtab.c
@@ -6964,8 +6964,12 @@ void
gdb_command_funnel_1(struct gnu_request *req)
{
struct symbol *sym;
+ FILE *original_stdout_stream = nullptr;
+ FILE *original_stderr_stream = nullptr;

if (req->command != GNU_VERSION && req->command != GNU_USER_PRINT_OPTION) {
+ original_stdout_stream = (dynamic_cast< stdio_file * >gdb_stdout)->get_stream();
+ original_stderr_stream = (dynamic_cast< stdio_file * >gdb_stderr)->get_stream();
(dynamic_cast<stdio_file *>gdb_stdout)->set_stream(req->fp);
(dynamic_cast<stdio_file *>gdb_stderr)->set_stream(req->fp);
}
@@ -7068,6 +7072,12 @@ gdb_command_funnel_1(struct gnu_request *req)
req->flags |= GNU_COMMAND_FAILED;
break;
}
+
+ /* Restore the streams gdb output was using */
+ if (original_stdout_stream)
+ (dynamic_cast<stdio_file *>gdb_stdout)->set_stream(original_stdout_stream);
+ if (original_stderr_stream)
+ (dynamic_cast<stdio_file *>gdb_stderr)->set_stream(original_stderr_stream);
}

/*
--- gdb-10.2/gdb/stack.c.orig
+++ gdb-10.2/gdb/stack.c
@@ -1990,6 +1990,11 @@
/* Print briefly all stack frames or just the innermost COUNT_EXP
frames. */

+#ifdef CRASH_MERGE
+extern "C" int is_kvaddr(ulong);
+extern "C" int gdb_CRASHDEBUG(ulong);
+#endif
+
static void
backtrace_command_1 (const frame_print_options &fp_opts,
const backtrace_cmd_options &bt_opts,
@@ -2082,6 +2086,17 @@
hand, perhaps the code does or could be fixed to make sure
the frame->prev field gets set to NULL in that case). */

+#ifdef CRASH_MERGE
+ CORE_ADDR pc = 0;
+ get_frame_pc_if_available (fi, &pc);
+ if (!is_kvaddr(pc)) {
+ if (gdb_CRASHDEBUG(1)) {
+ printf_filtered (_("Backtrace stopped: due to non-kernel addr: 0x%lx\n"),pc);
+ }
+ fi = NULL;
+ break;
+ }
+#endif
print_frame_info (fp_opts, fi, 1, LOCATION, 1, 0);
if ((flags & PRINT_LOCALS) != 0)
{
--- gdb-10.2/gdb/stack.c.orig
+++ gdb-10.2/gdb/stack.c
@@ -2127,7 +2127,7 @@
enum unwind_stop_reason reason;

reason = get_frame_unwind_stop_reason (trailing);
- if (reason >= UNWIND_FIRST_ERROR)
+ if (reason >= UNWIND_FIRST_ERROR && gdb_CRASHDEBUG(1))
printf_filtered (_("Backtrace stopped: %s\n"),
frame_stop_reason_string (trailing));
}
8 changes: 7 additions & 1 deletion gdb_interface.c
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ static char *prohibited_list[] = {
"watch", "rwatch", "awatch", "attach", "continue", "c", "fg", "detach",
"finish", "handle", "interrupt", "jump", "kill", "next", "nexti",
"signal", "step", "s", "stepi", "target", "until", "delete",
"clear", "disable", "enable", "condition", "ignore", "frame", "catch",
"clear", "disable", "enable", "condition", "ignore", "catch",
"tcatch", "return", "file", "exec-file", "core-file", "symbol-file",
"load", "si", "ni", "shell", "sy",
NULL /* must be last */
Expand Down Expand Up @@ -947,6 +947,12 @@ gdb_lookup_module_symbol(ulong addr, ulong *offset)
}
}

int
is_kvaddr(ulong addr)
{
return IS_KVADDR(addr);
}

/*
* Used by gdb_interface() to catch gdb-related errors, if desired.
*/
Expand Down
33 changes: 33 additions & 0 deletions kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -3535,6 +3535,39 @@ get_lkcd_regs(struct bt_info *bt, ulong *eip, ulong *esp)
machdep->get_stack_frame(bt, eip, esp);
}

void
get_dumpfile_regs(struct bt_info *bt, ulong *eip, ulong *esp)
{
bt->flags |= BT_NO_PRINT_REGS;

if (NETDUMP_DUMPFILE())
get_netdump_regs(bt, eip, esp);
else if (KDUMP_DUMPFILE())
get_kdump_regs(bt, eip, esp);
else if (DISKDUMP_DUMPFILE())
get_diskdump_regs(bt, eip, esp);
else if (KVMDUMP_DUMPFILE())
get_kvmdump_regs(bt, eip, esp);
else if (LKCD_DUMPFILE())
get_lkcd_regs(bt, eip, esp);
else if (XENDUMP_DUMPFILE())
get_xendump_regs(bt, eip, esp);
else if (SADUMP_DUMPFILE())
get_sadump_regs(bt, eip, esp);
else if (VMSS_DUMPFILE())
get_vmware_vmss_regs(bt, eip, esp);
else if (REMOTE_PAUSED()) {
if (!is_task_active(bt->task) || !get_remote_regs(bt, eip, esp))
machdep->get_stack_frame(bt, eip, esp);
} else
machdep->get_stack_frame(bt, eip, esp);

bt->flags &= ~BT_NO_PRINT_REGS;

bt->instptr = *eip;
bt->stkptr = *esp;
}


/*
* Store the head of the kernel module list for future use.
Expand Down
Loading

0 comments on commit 9b97fb1

Please sign in to comment.