Hi Alexey,
On Thu, Mar 21, 2024 at 04:13:35PM -0700, Alexey Makhalov wrote:
Hi folks, please take a look on v2 of this patch and give it a try.
IMHO, Only remaining piece is to clean up x86_64_get_current_task_reg
around getting pt_regs.
Things done:
1. Rename machdep->get_cpu_reg to machdep->get_current_task_reg and rename
x86_64_get_cpu_reg to x86_64_get_current_task_reg, plus corresponding changes
for arm64 and ppc64.
2. Untied gdb's TPID from any tasks or CPUs ID. Always use CURRENT_CONTEXT
for regs and properties fetching.
3. When CURRENT switched, gdb_change_thread_context() must be called to
invalidate caches.
4. Update x86_64_get_current_task_reg to support inactive tasks for offline
debugging.
5. Fixed frame selection issue. It was caused by multiple calls of set_context()
which calls gdb_change_thread_context() unnecessarily dropping gdb caches.
TODO:
1. x86_64_get_current_task_reg is questionable about fetching pt_regs for
inactive tasks. How do you gyus get pt_regs structure on modern linux kernels?
I haven't look into your patch, but try to answer your pt_regs question,
don't know if it is what you want. For the approach I and Aditya use,
we did try to fulfill the pt_regs with registers value, which are got
from the stack. However not all registers can be get, or needed accroding
to my test.
The pt_regs is consumed in x86_64.c:x86_64_get_cpu_reg(), getting by:
pt_regs = (struct x86_64_user_regs_struct *)bt_info.machdep;
The pt_regs is created in x86_64.c:x86_64_get_stack_frame(), by:
user_regs = (struct x86_64_user_regs_struct *)GETBUF(sizeof(*user_regs));
...
bt->machdep = user_regs;
As you can see, what we do in x86_64.c:x86_64_get_stack_frame(), is to fulfill
the pt_regs by reg values. Please see the pseudo code as follows (abstracted):
if kernel have inactive_task_frame {
if task_is_inactive {
regs r12 - r15, rbx, rbp will be get from inactive_task_frame, and rip, rsp are get by
x86_64_get_pc/sp().
} else {
// task_is_active
if is_live_debugging {
only rip, rsp are get by x86_64_get_pc/sp().
} else {
// is_offline_debugging
get pt_regs from elf notes etc, we don't allocate pt_regs by ourself.
}
}
} else {
// kernel don't have inactive_task_frame
if task_is_inactive {
rip, rsp are get by x86_64_get_pc/sp(), rbp is get from stack.
} else {
// task_is_active
if is_live_debugging {
only rip, rsp are get by x86_64_get_pc/sp().
} else {
// is_offline_debugging
get pt_regs from elf notes etc, we don't allocate pt_regs by ourself.
}
}
}
As you see, 1) not all registers of pt_regs structure can be get from stack,
nor needed for stack unwinding, as long as we have "rip/rsp/rbp" we are good
to go. See [1] for arm64 case. We don't need as many regs as [2], and can
be shrinked less. I guess it is the same for ppc64, but haven't tried yet.
2) for cases where pt_regs can be get from elf notes, we don't need to anything.
Thanks,
Tao Liu
[1]:
https://github.com/liutgnu/crash-dev/blob/one-thread-v2/arm64.c#L165
[2]:
https://github.com/liutgnu/crash-dev/blob/one-thread-v2/x86_64.c#L9223
PS: I added my approach. More testing required.
Known issues
1. After switching from all tasks to one thread:
b. unwinding started to work differently around top (garbaged) frames - Not quite
an issue for me.
Signed-off-by: Alexey Makhalov <alexey.makhalov(a)broadcom.com>
---
arm64.c | 4 ++--
crash_target.c | 48 ++++++++++++++++++-------------------
defs.h | 10 +++-----
gdb_interface.c | 8 +++----
kernel.c | 23 ------------------
ppc64.c | 8 +++----
task.c | 16 ++++---------
x86_64.c | 64 ++++++++++++++++++++++++++++++++++++++++++++-----
8 files changed, 99 insertions(+), 82 deletions(-)
diff --git a/arm64.c b/arm64.c
index 8ff10bf..340db2d 100644
--- a/arm64.c
+++ b/arm64.c
@@ -153,7 +153,7 @@ static void arm64_calc_kernel_start(void)
}
static int
-arm64_get_cpu_reg(int cpu, int regno, const char *name,
+arm64_get_current_task_reg(int regno, const char *name,
int size, void *value)
{
struct bt_info bt_info, bt_setup;
@@ -511,7 +511,7 @@ arm64_init(int when)
machdep->dumpfile_init = NULL;
machdep->verify_line_number = NULL;
machdep->init_kernel_pgd = arm64_init_kernel_pgd;
- machdep->get_cpu_reg = arm64_get_cpu_reg;
+ machdep->get_current_task_reg = arm64_get_current_task_reg;
/* use machdep parameters */
arm64_calc_phys_offset();
diff --git a/crash_target.c b/crash_target.c
index fb3b634..6e96506 100644
--- a/crash_target.c
+++ b/crash_target.c
@@ -26,11 +26,8 @@
void crash_target_init (void);
extern "C" int gdb_readmem_callback(unsigned long, void *, int, int);
-extern "C" int crash_get_cpu_reg (int cpu, int regno, const char *regname,
+extern "C" int crash_get_current_task_reg (int regno, const char *regname,
int regsize, void *val);
-extern "C" void gdb_refresh_regcache(unsigned int cpu);
-extern "C" int set_cpu(int cpu, int print_context);
-extern "C" int crash_set_thread(ulong);
extern "C" int gdb_change_thread_context (ulong);
extern "C" void crash_get_current_task_info(unsigned long *pid, char **comm);
@@ -72,22 +69,27 @@ public:
};
-/* We just get all the registers, so we don't use regno. */
void
crash_target::fetch_registers (struct regcache *regcache, int regno)
{
+ int r;
gdb_byte regval[16];
- int cpu = inferior_ptid.tid();
struct gdbarch *arch = regcache->arch ();
- for (int r = 0; r < gdbarch_num_regs (arch); r++)
+ if (regno >= 0) {
+ r = regno;
+ goto onetime;
+ }
+
+ for (r = 0; regno == -1 && r < gdbarch_num_regs (arch); r++)
{
+onetime:
const char *regname = gdbarch_register_name(arch, r);
int regsize = register_size (arch, r);
if (regsize > sizeof (regval))
error (_("fatal error: buffer size is not enough to fit register
value"));
- if (crash_get_cpu_reg (cpu, r, regname, regsize, (void *)®val))
+ if (crash_get_current_task_reg (r, regname, regsize, (void *)®val))
regcache->raw_supply (r, regval);
else
regcache->raw_supply (r, NULL);
@@ -142,19 +144,17 @@ crash_target_init (void)
extern "C" int
gdb_change_thread_context (ulong task)
{
- inferior* inf = current_inferior();
- int cpu = crash_set_thread(task);
- if (cpu < 0)
- return FALSE;
-
- ptid_t ptid = ptid_t(CRASH_INFERIOR_PID, 0, cpu);
- thread_info *tp = find_thread_ptid (inf, ptid);
-
- if (tp == nullptr)
- return FALSE;
-
- target_fetch_registers(get_thread_regcache(tp), -1);
- switch_to_thread(tp);
- reinit_frame_cache ();
- return TRUE;
-}
\ No newline at end of file
+ static ulong prev_task = 0;
+
+ /*
+ * Crash calls this method even if CURRENT task is not updated.
+ * Ignore it and keep gdb caches active.
+ */
+ if (task == prev_task)
+ return TRUE;
+ prev_task = task;
+
+ registers_changed();
+ reinit_frame_cache();
+ return TRUE;
+}
diff --git a/defs.h b/defs.h
index d7e3b18..7596179 100644
--- a/defs.h
+++ b/defs.h
@@ -1081,7 +1081,7 @@ struct machdep_table {
void (*get_irq_affinity)(int);
void (*show_interrupts)(int, ulong *);
int (*is_page_ptr)(ulong, physaddr_t *);
- int (*get_cpu_reg)(int, int, const char *, int, void *);
+ int (*get_current_task_reg)(int, const char *, int, void *);
int (*is_cpu_prstatus_valid)(int cpu);
};
@@ -6124,7 +6124,6 @@ void dump_kernel_table(int);
void dump_bt_info(struct bt_info *, char *where);
void dump_log(int);
void parse_kernel_version(char *);
-int crash_set_thread(ulong);
#define LOG_LEVEL(v) ((v) & 0x07)
#define SHOW_LOG_LEVEL (0x1)
@@ -8023,7 +8022,7 @@ extern int have_full_symbols(void);
/*
* Register numbers must be in sync with gdb/features/i386/64bit-core.c
- * to make crash_target->fetch_registers() ---> machdep->get_cpu_reg()
+ * to make crash_target->fetch_registers() --->
machdep->get_current_task_reg()
* working properly.
*/
enum x86_64_regnum {
@@ -8081,7 +8080,7 @@ enum arm64_regnum {
/*
* Register numbers to make crash_target->fetch_registers()
- * ---> machdep->get_cpu_reg() work properly.
+ * ---> 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
@@ -8199,7 +8198,4 @@ enum ppc64_regnum {
PPC64_VRSAVE_REGNU = 139
};
-/* crash_target.c */
-extern void gdb_refresh_regcache (unsigned int cpu);
-
#endif /* !GDB_COMMON */
diff --git a/gdb_interface.c b/gdb_interface.c
index 28264c2..2e9117a 100644
--- a/gdb_interface.c
+++ b/gdb_interface.c
@@ -1073,14 +1073,14 @@ unsigned long crash_get_kaslr_offset(void)
}
/* Callbacks for crash_target */
-int crash_get_cpu_reg (int cpu, int regno, const char *regname,
+int crash_get_current_task_reg (int regno, const char *regname,
int regsize, void *val);
-int crash_get_cpu_reg (int cpu, int regno, const char *regname,
+int crash_get_current_task_reg (int regno, const char *regname,
int regsize, void *value)
{
- if (!machdep->get_cpu_reg)
+ if (!machdep->get_current_task_reg)
return FALSE;
- return machdep->get_cpu_reg(cpu, regno, regname, regsize, value);
+ return machdep->get_current_task_reg(regno, regname, regsize, value);
}
diff --git a/kernel.c b/kernel.c
index 446fd78..15b00dc 100644
--- a/kernel.c
+++ b/kernel.c
@@ -6543,29 +6543,6 @@ set_cpu(int cpu, int print_context)
show_context(CURRENT_CONTEXT());
}
-int
-crash_set_thread(ulong task)
-{
- bool found = FALSE;
- struct task_context *tc = FIRST_CONTEXT();
-
- for (int i = 0; i < RUNNING_TASKS(); i++, tc++) {
- if (tc->task == task) {
- found = TRUE;
- break;
- }
- }
-
- if (!found)
- return -1;
-
- if (CURRENT_TASK() == tc->task)
- return 0;
-
- set_context(tc->task, NO_PID, TRUE);
- return 0;
-}
-
/*
* Collect the irq_desc[] entry along with its associated handler and
* action structures.
diff --git a/ppc64.c b/ppc64.c
index a87e621..aeccb70 100644
--- a/ppc64.c
+++ b/ppc64.c
@@ -55,7 +55,7 @@ static void ppc64_set_bt_emergency_stack(enum emergency_stack_type
type,
static char * ppc64_check_eframe(struct ppc64_pt_regs *);
static void ppc64_print_eframe(char *, struct ppc64_pt_regs *,
struct bt_info *);
-static int ppc64_get_cpu_reg(int cpu, int regno, const char *name, int size,
+static int ppc64_get_current_task_reg(int regno, const char *name, int size,
void *value);
static void parse_cmdline_args(void);
static int ppc64_paca_percpu_offset_init(int);
@@ -706,7 +706,7 @@ ppc64_init(int when)
error(FATAL, "cannot malloc hwirqstack buffer space.");
}
- machdep->get_cpu_reg = ppc64_get_cpu_reg;
+ machdep->get_current_task_reg = ppc64_get_current_task_reg;
ppc64_init_paca_info();
@@ -2506,7 +2506,7 @@ ppc64_print_eframe(char *efrm_str, struct ppc64_pt_regs *regs,
}
static int
-ppc64_get_cpu_reg(int cpu, int regno, const char *name, int size,
+ppc64_get_current_task_reg(int regno, const char *name, int size,
void *value)
{
struct bt_info bt_info, bt_setup;
@@ -2555,7 +2555,7 @@ ppc64_get_cpu_reg(int cpu, int regno, const char *name, int size,
pt_regs = (struct ppc64_pt_regs *)bt_info.machdep;
if (!pt_regs) {
- error(WARNING, "pt_regs not available for cpu %d\n", cpu);
+ error(WARNING, "pt_regs not available for cpu %d\n", tc->processor);
return FALSE;
}
diff --git a/task.c b/task.c
index f7e5b05..5f1ddad 100644
--- a/task.c
+++ b/task.c
@@ -11286,19 +11286,11 @@ check_stack_end_magic:
fprintf(fp, "No stack overflows detected\n");
}
+void crash_get_current_task_info(unsigned long *pid, char **comm);
void crash_get_current_task_info(unsigned long *pid, char **comm)
{
- int i;
- struct task_context *tc;
+ struct task_context *tc = CURRENT_CONTEXT();
- tc = FIRST_CONTEXT();
- for (i = 0; i < RUNNING_TASKS(); i++, tc++)
- if (tc->task == CURRENT_TASK()) {
- *pid = tc->pid;
- *comm = tc->comm;
- return;
- }
- *pid = 0;
- *comm = NULL;
- return;
+ *pid = tc->pid;
+ *comm = tc->comm;
}
diff --git a/x86_64.c b/x86_64.c
index 41e67c6..73bb353 100644
--- a/x86_64.c
+++ b/x86_64.c
@@ -126,7 +126,7 @@ static int x86_64_get_framesize(struct bt_info *, ulong, ulong, char
*);
static void x86_64_framesize_debug(struct bt_info *);
static void x86_64_get_active_set(void);
static int x86_64_get_kvaddr_ranges(struct vaddr_range *);
-static int x86_64_get_cpu_reg(int, int, const char *, int, void *);
+static int x86_64_get_current_task_reg(int, const char *, int, void *);
static int x86_64_verify_paddr(uint64_t);
static void GART_init(void);
static void x86_64_exception_stacks_init(void);
@@ -207,7 +207,7 @@ x86_64_init(int when)
machdep->machspec->irq_eframe_link = UNINITIALIZED;
machdep->machspec->irq_stack_gap = UNINITIALIZED;
machdep->get_kvaddr_ranges = x86_64_get_kvaddr_ranges;
- machdep->get_cpu_reg = x86_64_get_cpu_reg;
+ machdep->get_current_task_reg = x86_64_get_current_task_reg;
if (machdep->cmdline_args[0])
parse_cmdline_args();
if ((string = pc->read_vmcoreinfo("relocate"))) {
@@ -898,7 +898,7 @@ x86_64_dump_machdep_table(ulong arg)
fprintf(fp, " is_page_ptr: x86_64_is_page_ptr()\n");
fprintf(fp, " verify_paddr: x86_64_verify_paddr()\n");
fprintf(fp, " get_kvaddr_ranges: x86_64_get_kvaddr_ranges()\n");
- fprintf(fp, " get_cpu_reg: x86_64_get_cpu_reg()\n");
+ fprintf(fp, "get_current_task_reg: x86_64_get_current_task_reg()\n");
fprintf(fp, " init_kernel_pgd: x86_64_init_kernel_pgd()\n");
fprintf(fp, "clear_machdep_cache: x86_64_clear_machdep_cache()\n");
fprintf(fp, " xendump_p2m_create: %s\n", PVOPS_XEN() ?
@@ -9186,7 +9186,7 @@ x86_64_get_kvaddr_ranges(struct vaddr_range *vrp)
}
static int
-x86_64_get_cpu_reg(int cpu, int regno, const char *name,
+x86_64_get_current_task_reg(int regno, const char *name,
int size, void *value)
{
struct bt_info bt_info, bt_setup;
@@ -9195,8 +9195,6 @@ x86_64_get_cpu_reg(int cpu, int regno, const char *name,
ulong ip, sp;
bool ret = FALSE;
- if (VMSS_DUMPFILE())
- return vmware_vmss_get_cpu_reg(cpu, regno, name, size, value);
switch (regno) {
case RAX_REGNUM ... GS_REGNUM:
case FS_BASE_REGNUM ... ORIG_RAX_REGNUM:
@@ -9208,6 +9206,60 @@ x86_64_get_cpu_reg(int cpu, int regno, const char *name,
tc = CURRENT_CONTEXT();
if (!tc)
return FALSE;
+
+ /*
+ * For inactive task, grab rip, rbp, rbx, r12, r13, r14 and r15 from
+ * inactive_task_frame (see __switch_to_asm). Other regs saved on
+ * regular frame.
+ */
+ if (!is_task_active(tc->task)) {
+ int frame_size = STRUCT_SIZE("inactive_task_frame");
+
+ /* Only modern kernels supported. */
+ if (tt->flags & THREAD_INFO && frame_size == 7 * 8) {
+ ulong rsp;
+ int offset = 0;
+ switch (regno) {
+ case RSP_REGNUM:
+ readmem(tc->task + OFFSET(task_struct_thread) +
+ OFFSET(thread_struct_rsp), KVADDR,
+ &rsp, sizeof(void *),
+ "thread_struct rsp", FAULT_ON_ERROR);
+ rsp += frame_size;
+ memcpy(value, &rsp, size);
+ return TRUE;
+ case RIP_REGNUM:
+ offset += 8;
+ case RBP_REGNUM:
+ offset += 8;
+ case RBX_REGNUM:
+ offset += 8;
+ case R12_REGNUM:
+ offset += 8;
+ case R13_REGNUM:
+ offset += 8;
+ case R14_REGNUM:
+ offset += 8;
+ case R15_REGNUM:
+ readmem(tc->task + OFFSET(task_struct_thread) +
+ OFFSET(thread_struct_rsp), KVADDR,
+ &rsp, sizeof(void *),
+ "thread_struct rsp", FAULT_ON_ERROR);
+ readmem(rsp + offset, KVADDR, value, sizeof(void *),
+ "inactive_thread_frame saved regs", FAULT_ON_ERROR);
+ return TRUE;
+ }
+ }
+ /* TBD: older kernels support. */
+ return FALSE;
+ }
+
+ /*
+ * Task is active, grab CPU's registers
+ */
+ if (VMSS_DUMPFILE())
+ return vmware_vmss_get_cpu_reg(tc->processor, regno, name, size, value);
+
BZERO(&bt_setup, sizeof(struct bt_info));
clone_bt_info(&bt_setup, &bt_info, tc);
fill_stackbuf(&bt_info);
--
2.39.0