mirror of https://github.com/crash-utility/crash
1959 lines
52 KiB
C
1959 lines
52 KiB
C
/* riscv64.c - core analysis suite
|
|
*
|
|
* Copyright (C) 2022 Alibaba Group Holding Limited.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
#include "defs.h"
|
|
#ifdef RISCV64
|
|
|
|
#include <elf.h>
|
|
#include <math.h>
|
|
|
|
static ulong riscv64_get_page_size(void);
|
|
static int riscv64_vtop_3level_4k(ulong *pgd, ulong vaddr,
|
|
physaddr_t *paddr, int verbose);
|
|
static int riscv64_vtop_4level_4k(ulong *pgd, ulong vaddr,
|
|
physaddr_t *paddr, int verbose);
|
|
static int riscv64_vtop_5level_4k(ulong *pgd, ulong vaddr,
|
|
physaddr_t *paddr, int verbose);
|
|
static void riscv64_page_type_init(void);
|
|
static int riscv64_is_kvaddr(ulong vaddr);
|
|
static int riscv64_is_uvaddr(ulong vaddr, struct task_context *tc);
|
|
static int riscv64_uvtop(struct task_context *tc, ulong vaddr,
|
|
physaddr_t *paddr, int verbose);
|
|
static int riscv64_kvtop(struct task_context *tc, ulong kvaddr,
|
|
physaddr_t *paddr, int verbose);
|
|
static void riscv64_cmd_mach(void);
|
|
static void riscv64_irq_stack_init(void);
|
|
static void riscv64_overflow_stack_init(void);
|
|
static void riscv64_stackframe_init(void);
|
|
static void riscv64_back_trace_cmd(struct bt_info *bt);
|
|
static int riscv64_eframe_search(struct bt_info *bt);
|
|
static int riscv64_get_dumpfile_stack_frame(struct bt_info *bt,
|
|
ulong *nip, ulong *ksp);
|
|
static void riscv64_get_stack_frame(struct bt_info *bt, ulong *pcp,
|
|
ulong *spp);
|
|
static int riscv64_get_frame(struct bt_info *bt, ulong *pcp,
|
|
ulong *spp);
|
|
static void riscv64_display_full_frame(struct bt_info *bt,
|
|
struct riscv64_unwind_frame *current,
|
|
struct riscv64_unwind_frame *previous);
|
|
static int riscv64_translate_pte(ulong, void *, ulonglong);
|
|
static int riscv64_init_active_task_regs(void);
|
|
static int riscv64_get_crash_notes(void);
|
|
static int riscv64_get_elf_notes(void);
|
|
static void riscv64_get_va_range(struct machine_specific *ms);
|
|
static void riscv64_get_va_bits(struct machine_specific *ms);
|
|
static void riscv64_get_struct_page_size(struct machine_specific *ms);
|
|
static void riscv64_print_exception_frame(struct bt_info *, ulong , int );
|
|
static int riscv64_is_kernel_exception_frame(struct bt_info *, ulong );
|
|
static int riscv64_on_irq_stack(int , ulong);
|
|
static int riscv64_on_process_stack(struct bt_info *, ulong );
|
|
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 *);
|
|
|
|
#define REG_FMT "%016lx"
|
|
#define SZ_2G 0x80000000
|
|
#define USER_MODE (0)
|
|
#define KERNEL_MODE (1)
|
|
|
|
/*
|
|
* Holds registers during the crash.
|
|
*/
|
|
static struct riscv64_register *panic_task_regs;
|
|
|
|
/* from arch/riscv/include/asm/stacktrace.h */
|
|
struct stackframe {
|
|
ulong fp;
|
|
ulong ra;
|
|
};
|
|
|
|
static struct machine_specific riscv64_machine_specific = {
|
|
._page_present = (1 << 0),
|
|
._page_read = (1 << 1),
|
|
._page_write = (1 << 2),
|
|
._page_exec = (1 << 3),
|
|
._page_user = (1 << 4),
|
|
._page_global = (1 << 5),
|
|
._page_accessed = (1 << 6),
|
|
._page_dirty = (1 << 7),
|
|
._page_soft = (1 << 8),
|
|
|
|
.va_bits = 0,
|
|
.struct_page_size = 0,
|
|
};
|
|
|
|
static void
|
|
pt_level_alloc(char **lvl, char *name)
|
|
{
|
|
size_t sz = PAGESIZE();
|
|
void *pointer = malloc(sz);
|
|
|
|
if (!pointer)
|
|
error(FATAL, name);
|
|
*lvl = pointer;
|
|
}
|
|
|
|
static ulong
|
|
riscv64_get_page_size(void)
|
|
{
|
|
return memory_page_size();
|
|
}
|
|
|
|
static ulong
|
|
riscv64_vmalloc_start(void)
|
|
{
|
|
return ((ulong)VMALLOC_START);
|
|
}
|
|
|
|
/* Get the size of struct page {} */
|
|
static void riscv64_get_struct_page_size(struct machine_specific *ms)
|
|
{
|
|
char *string;
|
|
|
|
string = pc->read_vmcoreinfo("SIZE(page)");
|
|
if (string) {
|
|
ms->struct_page_size = atol(string);
|
|
free(string);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* "mach" command output.
|
|
*/
|
|
static void
|
|
riscv64_display_machine_stats(void)
|
|
{
|
|
struct new_utsname *uts;
|
|
char buf[BUFSIZE];
|
|
ulong mhz;
|
|
|
|
uts = &kt->utsname;
|
|
|
|
fprintf(fp, " MACHINE TYPE: %s\n", uts->machine);
|
|
fprintf(fp, " MEMORY SIZE: %s\n", get_memory_size(buf));
|
|
fprintf(fp, " CPUS: %d\n", get_cpus_to_display());
|
|
fprintf(fp, " PROCESSOR SPEED: ");
|
|
if ((mhz = machdep->processor_speed()))
|
|
fprintf(fp, "%ld Mhz\n", mhz);
|
|
else
|
|
fprintf(fp, "(unknown)\n");
|
|
fprintf(fp, " HZ: %d\n", machdep->hz);
|
|
fprintf(fp, " PAGE SIZE: %d\n", PAGESIZE());
|
|
fprintf(fp, " KERNEL STACK SIZE: %ld\n", STACKSIZE());
|
|
}
|
|
|
|
static void
|
|
riscv64_cmd_mach(void)
|
|
{
|
|
int c;
|
|
|
|
while ((c = getopt(argcnt, args, "cmo")) != EOF) {
|
|
switch (c) {
|
|
case 'c':
|
|
case 'm':
|
|
case 'o':
|
|
option_not_supported(c);
|
|
break;
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
riscv64_display_machine_stats();
|
|
}
|
|
|
|
/*
|
|
* Accept or reject a symbol from the kernel namelist.
|
|
*/
|
|
static int
|
|
riscv64_verify_symbol(const char *name, ulong value, char type)
|
|
{
|
|
if (CRASHDEBUG(8) && name && strlen(name))
|
|
fprintf(fp, "%08lx %s\n", value, name);
|
|
|
|
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 TRUE;
|
|
}
|
|
|
|
void
|
|
riscv64_dump_machdep_table(ulong arg)
|
|
{
|
|
const struct machine_specific *ms = machdep->machspec;
|
|
int others = 0, i = 0;
|
|
|
|
fprintf(fp, " flags: %lx (", machdep->flags);
|
|
if (machdep->flags & KSYMS_START)
|
|
fprintf(fp, "%sKSYMS_START", others++ ? "|" : "");
|
|
if (machdep->flags & IRQ_STACKS)
|
|
fprintf(fp, "%sIRQ_STACKS", others++ ? "|" : "");
|
|
if (machdep->flags & OVERFLOW_STACKS)
|
|
fprintf(fp, "%sOVERFLOW_STACKS", others++ ? "|" : "");
|
|
fprintf(fp, ")\n");
|
|
|
|
fprintf(fp, " kvbase: %lx\n", machdep->kvbase);
|
|
fprintf(fp, " identity_map_base: %lx\n", machdep->identity_map_base);
|
|
fprintf(fp, " pagesize: %d\n", machdep->pagesize);
|
|
fprintf(fp, " pageshift: %d\n", machdep->pageshift);
|
|
fprintf(fp, " pagemask: %llx\n", machdep->pagemask);
|
|
fprintf(fp, " pageoffset: %lx\n", machdep->pageoffset);
|
|
fprintf(fp, " pgdir_shift: %ld\n", machdep->machspec->va_bits - 9);
|
|
fprintf(fp, " ptrs_per_pgd: %u\n", PTRS_PER_PGD);
|
|
fprintf(fp, " ptrs_per_pte: %d\n", PTRS_PER_PTE);
|
|
fprintf(fp, " stacksize: %ld\n", machdep->stacksize);
|
|
fprintf(fp, " hz: %d\n", machdep->hz);
|
|
fprintf(fp, " memsize: %ld (0x%lx)\n",
|
|
machdep->memsize, machdep->memsize);
|
|
fprintf(fp, " bits: %d\n", machdep->bits);
|
|
fprintf(fp, " back_trace: riscv64_back_trace_cmd()\n");
|
|
fprintf(fp, " eframe_search: riscv64_eframe_search()\n");
|
|
fprintf(fp, " processor_speed: riscv64_processor_speed()\n");
|
|
fprintf(fp, " uvtop: riscv64_uvtop()\n");
|
|
fprintf(fp, " kvtop: riscv64_kvtop()\n");
|
|
fprintf(fp, " get_stack_frame: riscv64_get_stack_frame()\n");
|
|
fprintf(fp, " get_stackbase: generic_get_stackbase()\n");
|
|
fprintf(fp, " get_stacktop: generic_get_stacktop()\n");
|
|
fprintf(fp, " translate_pte: riscv64_translate_pte()\n");
|
|
fprintf(fp, " memory_size: generic_memory_size()\n");
|
|
fprintf(fp, " vmalloc_start: riscv64_vmalloc_start()\n");
|
|
fprintf(fp, " is_task_addr: riscv64_is_task_addr()\n");
|
|
fprintf(fp, " verify_symbol: riscv64_verify_symbol()\n");
|
|
fprintf(fp, " dis_filter: generic_dis_filter()\n");
|
|
fprintf(fp, " dump_irq: generic_dump_irq()\n");
|
|
fprintf(fp, " show_interrupts: generic_show_interrupts()\n");
|
|
fprintf(fp, " get_irq_affinity: generic_get_irq_affinity()\n");
|
|
fprintf(fp, " cmd_mach: riscv64_cmd_mach()\n");
|
|
fprintf(fp, " get_smp_cpus: riscv64_get_smp_cpus()\n");
|
|
fprintf(fp, " is_kvaddr: riscv64_is_kvaddr()\n");
|
|
fprintf(fp, " is_uvaddr: riscv64_is_uvaddr()\n");
|
|
fprintf(fp, " verify_paddr: generic_verify_paddr()\n");
|
|
fprintf(fp, " init_kernel_pgd: NULL\n");
|
|
fprintf(fp, " value_to_symbol: generic_machdep_value_to_symbol()\n");
|
|
fprintf(fp, " line_number_hooks: NULL\n");
|
|
fprintf(fp, " last_pgd_read: %lx\n", machdep->last_pgd_read);
|
|
fprintf(fp, " last_p4d_read: %lx\n", machdep->machspec->last_p4d_read);
|
|
fprintf(fp, " last_pud_read: %lx\n", machdep->last_pud_read);
|
|
fprintf(fp, " last_pmd_read: %lx\n", machdep->last_pmd_read);
|
|
fprintf(fp, " last_ptbl_read: %lx\n", machdep->last_ptbl_read);
|
|
fprintf(fp, " pgd: %lx\n", (ulong)machdep->pgd);
|
|
fprintf(fp, " p4d: %lx\n", (ulong)machdep->machspec->p4d);
|
|
fprintf(fp, " pud: %lx\n", (ulong)machdep->pud);
|
|
fprintf(fp, " pmd: %lx\n", (ulong)machdep->pmd);
|
|
fprintf(fp, " ptbl: %lx\n", (ulong)machdep->ptbl);
|
|
fprintf(fp, " section_size_bits: %ld\n", machdep->section_size_bits);
|
|
fprintf(fp, " max_physmem_bits: %ld\n", machdep->max_physmem_bits);
|
|
fprintf(fp, " sections_per_root: %ld\n", machdep->sections_per_root);
|
|
fprintf(fp, " machspec: %lx\n", (ulong)machdep->machspec);
|
|
if (machdep->flags & IRQ_STACKS) {
|
|
fprintf(fp, " irq_stack_size: %ld\n", ms->irq_stack_size);
|
|
for (i = 0; i < kt->cpus; i++)
|
|
fprintf(fp, " irq_stacks[%d]: %lx\n",
|
|
i, ms->irq_stacks[i]);
|
|
} else {
|
|
fprintf(fp, " irq_stack_size: (unused)\n");
|
|
fprintf(fp, " irq_stacks: (unused)\n");
|
|
}
|
|
if (machdep->flags & OVERFLOW_STACKS) {
|
|
fprintf(fp, " overflow_stack_size: %ld\n", ms->overflow_stack_size);
|
|
for (i = 0; i < kt->cpus; i++)
|
|
fprintf(fp, " overflow_stacks[%d]: %lx\n",
|
|
i, ms->overflow_stacks[i]);
|
|
} else {
|
|
fprintf(fp, " overflow_stack_size: (unused)\n");
|
|
fprintf(fp, " overflow_stacks: (unused)\n");
|
|
}
|
|
}
|
|
|
|
static ulong
|
|
riscv64_processor_speed(void)
|
|
{
|
|
/* TODO: */
|
|
return 0;
|
|
}
|
|
|
|
static unsigned long riscv64_get_kernel_version(void)
|
|
{
|
|
char *string;
|
|
|
|
if (THIS_KERNEL_VERSION)
|
|
return THIS_KERNEL_VERSION;
|
|
|
|
if ((string = pc->read_vmcoreinfo("OSRELEASE"))) {
|
|
parse_kernel_version(string);
|
|
free(string);
|
|
}
|
|
return THIS_KERNEL_VERSION;
|
|
}
|
|
|
|
static void
|
|
riscv64_get_phys_ram_base(struct machine_specific *ms)
|
|
{
|
|
unsigned long kernel_version = riscv64_get_kernel_version();
|
|
|
|
/*
|
|
* phys_ram_base is defined in Linux kernel since 5.14.
|
|
*/
|
|
if (kernel_version >= LINUX(5,14,0)) {
|
|
char *string;
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(phys_ram_base)"))) {
|
|
ms->phys_base = atol(string);
|
|
free(string);
|
|
} else
|
|
error(FATAL, "cannot read phys_ram_base\n");
|
|
} else
|
|
/*
|
|
* For qemu rv64 env and hardware platform, default phys base
|
|
* may different, eg,
|
|
* hardware platform: 0x200000
|
|
* qemu rv64 env: 0x80200000
|
|
*
|
|
* But we only can set one default value, in this case, qemu
|
|
* rv64 env may can't work.
|
|
*/
|
|
ms->phys_base = 0x200000;
|
|
}
|
|
|
|
static void riscv64_get_va_bits(struct machine_specific *ms)
|
|
{
|
|
unsigned long kernel_version = riscv64_get_kernel_version();
|
|
|
|
/*
|
|
* VA_BITS is defined in Linux kernel since 5.17. So we use the
|
|
* default va bits 39 when Linux version < 5.17.
|
|
*/
|
|
if (kernel_version >= LINUX(5,17,0)) {
|
|
char *string;
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(VA_BITS)"))) {
|
|
ms->va_bits = atol(string);
|
|
free(string);
|
|
}
|
|
} else
|
|
ms->va_bits = 39;
|
|
}
|
|
|
|
static void riscv64_get_va_range(struct machine_specific *ms)
|
|
{
|
|
unsigned long kernel_version = riscv64_get_kernel_version();
|
|
char *string;
|
|
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(PAGE_OFFSET)"))) {
|
|
ms->page_offset = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(VMALLOC_START)"))) {
|
|
ms->vmalloc_start_addr = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(VMALLOC_END)"))) {
|
|
ms->vmalloc_end = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(VMEMMAP_START)"))) {
|
|
ms->vmemmap_vaddr = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(VMEMMAP_END)"))) {
|
|
ms->vmemmap_end = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(KERNEL_LINK_ADDR)"))) {
|
|
ms->kernel_link_addr = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
|
|
if ((kt->flags2 & KASLR) && (kt->flags & RELOC_SET))
|
|
ms->kernel_link_addr += (kt->relocate * -1);
|
|
|
|
/*
|
|
* From Linux 5.13, the kernel mapping is moved to the last 2GB
|
|
* of the address space, modules use the 2GB memory range right
|
|
* before the kernel. Before Linux 5.13, modules area is embedded
|
|
* in vmalloc area.
|
|
*
|
|
*/
|
|
if (kernel_version >= LINUX(5,13,0)) {
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(MODULES_VADDR)"))) {
|
|
ms->modules_vaddr = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(MODULES_END)"))) {
|
|
ms->modules_end = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
goto error;
|
|
} else {
|
|
ms->modules_vaddr = ms->vmalloc_start_addr;
|
|
ms->modules_end = ms->vmalloc_end;
|
|
}
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "vmemmap : 0x%lx - 0x%lx\n",
|
|
ms->vmemmap_vaddr, ms->vmemmap_end);
|
|
fprintf(fp, "vmalloc : 0x%lx - 0x%lx\n",
|
|
ms->vmalloc_start_addr, ms->vmalloc_end);
|
|
fprintf(fp, "mudules : 0x%lx - 0x%lx\n",
|
|
ms->modules_vaddr, ms->modules_end);
|
|
fprintf(fp, "lowmem : 0x%lx -\n", ms->page_offset);
|
|
fprintf(fp, "kernel link addr : 0x%lx\n",
|
|
ms->kernel_link_addr);
|
|
}
|
|
return;
|
|
error:
|
|
error(FATAL, "cannot get vm layout\n");
|
|
}
|
|
|
|
static void
|
|
riscv64_get_va_kernel_pa_offset(struct machine_specific *ms)
|
|
{
|
|
unsigned long kernel_version = riscv64_get_kernel_version();
|
|
|
|
/*
|
|
* Since Linux v6.4 phys_base is not the physical start of the kernel,
|
|
* trying to use "va_kernel_pa_offset" to determine the offset between
|
|
* kernel virtual and physical addresses.
|
|
*/
|
|
if (kernel_version >= LINUX(6,4,0)) {
|
|
char *string;
|
|
if ((string = pc->read_vmcoreinfo("NUMBER(va_kernel_pa_offset)"))) {
|
|
ms->va_kernel_pa_offset = htol(string, QUIET, NULL);
|
|
free(string);
|
|
} else
|
|
error(FATAL, "cannot read va_kernel_pa_offset\n");
|
|
}
|
|
else
|
|
ms->va_kernel_pa_offset = ms->kernel_link_addr - ms->phys_base;
|
|
}
|
|
|
|
static int
|
|
riscv64_is_kvaddr(ulong vaddr)
|
|
{
|
|
if (IS_VMALLOC_ADDR(vaddr))
|
|
return TRUE;
|
|
|
|
return (vaddr >= machdep->kvbase);
|
|
}
|
|
|
|
static int
|
|
riscv64_is_uvaddr(ulong vaddr, struct task_context *unused)
|
|
{
|
|
if (IS_VMALLOC_ADDR(vaddr))
|
|
return FALSE;
|
|
|
|
return (vaddr < machdep->kvbase);
|
|
}
|
|
|
|
static int
|
|
riscv64_is_task_addr(ulong task)
|
|
{
|
|
if (tt->flags & THREAD_INFO)
|
|
return IS_KVADDR(task);
|
|
|
|
return (IS_KVADDR(task) && ALIGNED_STACK_OFFSET(task) == 0);
|
|
}
|
|
|
|
static int
|
|
riscv64_get_smp_cpus(void)
|
|
{
|
|
return (get_cpus_present() > 0) ? get_cpus_present() : kt->cpus;
|
|
}
|
|
|
|
/*
|
|
* Include both vmalloc'd and module address space as VMALLOC space.
|
|
*/
|
|
int
|
|
riscv64_IS_VMALLOC_ADDR(ulong vaddr)
|
|
{
|
|
return ((vaddr >= VMALLOC_START && vaddr <= VMALLOC_END) ||
|
|
(vaddr >= VMEMMAP_VADDR && vaddr <= VMEMMAP_END) ||
|
|
(vaddr >= MODULES_VADDR && vaddr <= MODULES_END));
|
|
}
|
|
|
|
/*
|
|
* Translate a PTE, returning TRUE if the page is present.
|
|
* If a physaddr pointer is passed in, don't print anything.
|
|
*/
|
|
static int
|
|
riscv64_translate_pte(ulong pte, void *physaddr, ulonglong unused)
|
|
{
|
|
char ptebuf[BUFSIZE];
|
|
char physbuf[BUFSIZE];
|
|
char buf[BUFSIZE];
|
|
int page_present;
|
|
int len1, len2, others;
|
|
ulong paddr;
|
|
|
|
paddr = PTOB(pte >> _PAGE_PFN_SHIFT);
|
|
page_present = !!(pte & _PAGE_PRESENT);
|
|
|
|
if (physaddr) {
|
|
*(ulong *)physaddr = paddr;
|
|
return page_present;
|
|
}
|
|
|
|
sprintf(ptebuf, "%lx", pte);
|
|
len1 = MAX(strlen(ptebuf), strlen("PTE"));
|
|
fprintf(fp, "%s ", mkstring(buf, len1, CENTER | LJUST, "PTE"));
|
|
|
|
if (!page_present)
|
|
return page_present;
|
|
|
|
sprintf(physbuf, "%lx", paddr);
|
|
len2 = MAX(strlen(physbuf), strlen("PHYSICAL"));
|
|
fprintf(fp, "%s ", mkstring(buf, len2, CENTER | LJUST, "PHYSICAL"));
|
|
|
|
fprintf(fp, "FLAGS\n");
|
|
fprintf(fp, "%s %s ",
|
|
mkstring(ptebuf, len1, CENTER | RJUST, NULL),
|
|
mkstring(physbuf, len2, CENTER | RJUST, NULL));
|
|
|
|
fprintf(fp, "(");
|
|
others = 0;
|
|
|
|
#define CHECK_PAGE_FLAG(flag) \
|
|
if ((_PAGE_##flag) && (pte & _PAGE_##flag)) \
|
|
fprintf(fp, "%s" #flag, others++ ? "|" : "")
|
|
if (pte) {
|
|
CHECK_PAGE_FLAG(PRESENT);
|
|
CHECK_PAGE_FLAG(READ);
|
|
CHECK_PAGE_FLAG(WRITE);
|
|
CHECK_PAGE_FLAG(EXEC);
|
|
CHECK_PAGE_FLAG(USER);
|
|
CHECK_PAGE_FLAG(GLOBAL);
|
|
CHECK_PAGE_FLAG(ACCESSED);
|
|
CHECK_PAGE_FLAG(DIRTY);
|
|
CHECK_PAGE_FLAG(SOFT);
|
|
} else {
|
|
fprintf(fp, "no mapping");
|
|
}
|
|
|
|
fprintf(fp, ")\n");
|
|
|
|
return page_present;
|
|
}
|
|
|
|
static void
|
|
riscv64_page_type_init(void)
|
|
{
|
|
ulong va_bits = machdep->machspec->va_bits;
|
|
|
|
/*
|
|
* For RISCV64 arch, any level of PTE may be a leaf PTE,
|
|
* so in addition to 4KiB pages,
|
|
* Sv39 supports 2 MiB megapages, 1 GiB gigapages;
|
|
* Sv48 supports 2 MiB megapages, 1 GiB gigapages, 512 GiB terapages;
|
|
* Sv57 supports 2 MiB megapages, 1 GiB gigapages, 512 GiB terapages, and 256 TiB petapages.
|
|
*
|
|
* refs to riscv-privileged spec.
|
|
*
|
|
* We just support 4KiB, 2MiB, 1GiB now.
|
|
*/
|
|
switch (machdep->pagesize)
|
|
{
|
|
case 0x1000: // 4 KiB
|
|
machdep->flags |= (va_bits == 57 ? VM_L5_4K :
|
|
(va_bits == 48 ? VM_L4_4K : VM_L3_4K));
|
|
break;
|
|
case 0x200000: // 2 MiB
|
|
/* TODO: */
|
|
case 0x40000000: // 1 GiB
|
|
/* TODO: */
|
|
default:
|
|
if (machdep->pagesize)
|
|
error(FATAL, "invalid/unsupported page size: %d\n",
|
|
machdep->pagesize);
|
|
else
|
|
error(FATAL, "cannot determine page size\n");
|
|
}
|
|
}
|
|
|
|
static int
|
|
riscv64_vtop_3level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose)
|
|
{
|
|
ulong *pgd_ptr, pgd_val;
|
|
ulong pmd_val;
|
|
ulong pte_val, pte_pfn;
|
|
ulong pt_phys;
|
|
|
|
/* PGD */
|
|
pgd_ptr = pgd + pgd_index_l3_4k(vaddr);
|
|
FILL_PGD(pgd, KVADDR, PAGESIZE());
|
|
pgd_val = ULONG(machdep->pgd + PAGEOFFSET(pgd_ptr));
|
|
if (verbose)
|
|
fprintf(fp, " PGD: %lx => %lx\n", (ulong)pgd_ptr, pgd_val);
|
|
if (!pgd_val)
|
|
goto no_page;
|
|
pgd_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pgd_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PMD */
|
|
FILL_PMD(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pmd_val = ULONG(machdep->pmd + PAGEOFFSET(sizeof(pmd_t) *
|
|
pmd_index_l3_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PMD: %016lx => %016lx\n", pt_phys, pmd_val);
|
|
if (!pmd_val)
|
|
goto no_page;
|
|
pmd_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pmd_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PTE */
|
|
FILL_PTBL(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pte_val = ULONG(machdep->ptbl + PAGEOFFSET(sizeof(pte_t) *
|
|
pte_index_l3_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PTE: %lx => %lx\n", pt_phys, pte_val);
|
|
if (!pte_val)
|
|
goto no_page;
|
|
pte_val &= PTE_PFN_PROT_MASK;
|
|
pte_pfn = pte_val >> _PAGE_PFN_SHIFT;
|
|
|
|
if (!(pte_val & _PAGE_PRESENT)) {
|
|
if (verbose) {
|
|
fprintf(fp, "\n");
|
|
riscv64_translate_pte((ulong)pte_val, 0, 0);
|
|
}
|
|
fprintf(fp, " PAGE: %016lx not present\n\n", PAGEBASE(*paddr));
|
|
return FALSE;
|
|
}
|
|
|
|
*paddr = PTOB(pte_pfn) + PAGEOFFSET(vaddr);
|
|
|
|
if (verbose) {
|
|
fprintf(fp, " PAGE: %016lx\n\n", PAGEBASE(*paddr));
|
|
riscv64_translate_pte(pte_val, 0, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
no_page:
|
|
fprintf(fp, "invalid\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* 'bt -f' command output
|
|
* Display all stack data contained in a frame
|
|
*/
|
|
static void
|
|
riscv64_display_full_frame(struct bt_info *bt, struct riscv64_unwind_frame *current,
|
|
struct riscv64_unwind_frame *previous)
|
|
{
|
|
int i, u_idx;
|
|
ulong *up;
|
|
ulong words, addr;
|
|
char buf[BUFSIZE];
|
|
|
|
if (previous->sp < current->sp)
|
|
return;
|
|
|
|
if (!(INSTACK(previous->sp, bt) && INSTACK(current->sp, bt)))
|
|
return;
|
|
|
|
words = (previous->sp - current->sp) / sizeof(ulong) + 1;
|
|
addr = current->sp;
|
|
u_idx = (current->sp - bt->stackbase) / sizeof(ulong);
|
|
|
|
for (i = 0; i < words; i++, u_idx++) {
|
|
if (!(i & 1))
|
|
fprintf(fp, "%s %lx: ", i ? "\n" : "", addr);
|
|
|
|
up = (ulong *)(&bt->stackbuf[u_idx*sizeof(ulong)]);
|
|
fprintf(fp, "%s ", format_stack_entry(bt, buf, *up, 0));
|
|
addr += sizeof(ulong);
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* Gather Overflow stack values.
|
|
*/
|
|
static void
|
|
riscv64_overflow_stack_init(void)
|
|
{
|
|
int i;
|
|
struct syment *sp;
|
|
struct gnu_request request, *req;
|
|
struct machine_specific *ms = machdep->machspec;
|
|
req = &request;
|
|
|
|
if (symbol_exists("overflow_stack") &&
|
|
(sp = per_cpu_symbol_search("overflow_stack")) &&
|
|
get_symbol_type("overflow_stack", NULL, req)) {
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "overflow_stack: \n");
|
|
fprintf(fp, " type: %x, %s\n",
|
|
(int)req->typecode,
|
|
(req->typecode == TYPE_CODE_ARRAY) ?
|
|
"TYPE_CODE_ARRAY" : "other");
|
|
fprintf(fp, " target_typecode: %x, %s\n",
|
|
(int)req->target_typecode,
|
|
req->target_typecode == TYPE_CODE_INT ?
|
|
"TYPE_CODE_INT" : "other");
|
|
fprintf(fp, " target_length: %ld\n",
|
|
req->target_length);
|
|
fprintf(fp, " length: %ld\n", req->length);
|
|
}
|
|
|
|
if (!(ms->overflow_stacks = (ulong *)malloc((size_t)(kt->cpus * sizeof(ulong)))))
|
|
error(FATAL, "cannot malloc overflow_stack addresses\n");
|
|
|
|
ms->overflow_stack_size = RISCV64_OVERFLOW_STACK_SIZE;
|
|
machdep->flags |= OVERFLOW_STACKS;
|
|
|
|
for (i = 0; i < kt->cpus; i++)
|
|
ms->overflow_stacks[i] = kt->__per_cpu_offset[i] + sp->value;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Gather IRQ stack values.
|
|
*/
|
|
static void
|
|
riscv64_irq_stack_init(void)
|
|
{
|
|
int i;
|
|
struct syment *sp;
|
|
struct gnu_request request, *req;
|
|
struct machine_specific *ms = machdep->machspec;
|
|
ulong p, sz;
|
|
req = &request;
|
|
|
|
if (symbol_exists("irq_stack_ptr") &&
|
|
(sp = per_cpu_symbol_search("irq_stack_ptr")) &&
|
|
get_symbol_type("irq_stack_ptr", NULL, req)) {
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "irq_stack_ptr: \n");
|
|
fprintf(fp, " type: %x, %s\n",
|
|
(int)req->typecode,
|
|
(req->typecode == TYPE_CODE_PTR) ?
|
|
"TYPE_CODE_PTR" : "other");
|
|
fprintf(fp, " target_typecode: %x, %s\n",
|
|
(int)req->target_typecode,
|
|
req->target_typecode == TYPE_CODE_INT ?
|
|
"TYPE_CODE_INT" : "other");
|
|
fprintf(fp, " target_length: %ld\n",
|
|
req->target_length);
|
|
fprintf(fp, " length: %ld\n", req->length);
|
|
}
|
|
|
|
if (!(ms->irq_stacks = (ulong *)malloc((size_t)(kt->cpus * sizeof(ulong)))))
|
|
error(FATAL, "cannot malloc irq_stack addresses\n");
|
|
|
|
/*
|
|
* find IRQ_STACK_SIZE (i.e. THREAD_SIZE) via thread_union.stack
|
|
* or set STACKSIZE() as default.
|
|
*/
|
|
if (MEMBER_EXISTS("thread_union", "stack")) {
|
|
if ((sz = MEMBER_SIZE("thread_union", "stack")) > 0)
|
|
ms->irq_stack_size = sz;
|
|
} else
|
|
ms->irq_stack_size = machdep->stacksize;
|
|
|
|
machdep->flags |= IRQ_STACKS;
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
p = kt->__per_cpu_offset[i] + sp->value;
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, " IRQ stack pointer[%d] is %lx\n", i, p);
|
|
readmem(p, KVADDR, &(ms->irq_stacks[i]), sizeof(ulong),
|
|
"IRQ stack pointer", RETURN_ON_ERROR);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
riscv64_on_irq_stack(int cpu, ulong stkptr)
|
|
{
|
|
struct machine_specific *ms = machdep->machspec;
|
|
ulong * stacks = ms->irq_stacks;
|
|
ulong stack_size = ms->irq_stack_size;
|
|
|
|
if ((cpu >= kt->cpus) || (stacks == NULL) || !stack_size)
|
|
return FALSE;
|
|
|
|
if ((stkptr >= stacks[cpu]) &&
|
|
(stkptr < (stacks[cpu] + stack_size)))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
riscv64_on_overflow_stack(int cpu, ulong stkptr)
|
|
{
|
|
struct machine_specific *ms = machdep->machspec;
|
|
ulong * stacks = ms->overflow_stacks;
|
|
ulong stack_size = ms->overflow_stack_size;
|
|
|
|
if ((cpu >= kt->cpus) || (stacks == NULL) || !stack_size)
|
|
return FALSE;
|
|
|
|
if ((stkptr >= stacks[cpu]) &&
|
|
(stkptr < (stacks[cpu] + stack_size)))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
riscv64_on_process_stack(struct bt_info *bt, ulong stkptr)
|
|
{
|
|
ulong stackbase, stacktop;
|
|
|
|
stackbase = GET_STACKBASE(bt->task);
|
|
stacktop = GET_STACKTOP(bt->task);
|
|
|
|
if ((stkptr >= stackbase) && (stkptr < stacktop))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static void
|
|
riscv64_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 void
|
|
riscv64_set_overflow_stack(struct bt_info *bt)
|
|
{
|
|
struct machine_specific *ms = machdep->machspec;
|
|
|
|
bt->stackbase = ms->overflow_stacks[bt->tc->processor];
|
|
bt->stacktop = bt->stackbase + ms->overflow_stack_size;
|
|
alter_stackbuf(bt);
|
|
}
|
|
|
|
static void
|
|
riscv64_set_process_stack(struct bt_info *bt)
|
|
{
|
|
bt->stackbase = GET_STACKBASE(bt->task);
|
|
bt->stacktop = GET_STACKTOP(bt->task);
|
|
alter_stackbuf(bt);
|
|
}
|
|
|
|
static void
|
|
riscv64_stackframe_init(void)
|
|
{
|
|
long task_struct_thread = MEMBER_OFFSET("task_struct", "thread");
|
|
|
|
/* from arch/riscv/include/asm/processor.h */
|
|
long thread_reg_ra = MEMBER_OFFSET("thread_struct", "ra");
|
|
long thread_reg_sp = MEMBER_OFFSET("thread_struct", "sp");
|
|
long thread_reg_fp = MEMBER_OFFSET("thread_struct", "s");
|
|
|
|
if ((task_struct_thread == INVALID_OFFSET) ||
|
|
(thread_reg_ra == INVALID_OFFSET) ||
|
|
(thread_reg_sp == INVALID_OFFSET) ||
|
|
(thread_reg_fp == INVALID_OFFSET) )
|
|
error(FATAL,
|
|
"cannot determine thread_struct offsets\n");
|
|
|
|
ASSIGN_OFFSET(task_struct_thread_context_pc) =
|
|
task_struct_thread + thread_reg_ra;
|
|
ASSIGN_OFFSET(task_struct_thread_context_sp) =
|
|
task_struct_thread + thread_reg_sp;
|
|
ASSIGN_OFFSET(task_struct_thread_context_fp) =
|
|
task_struct_thread + thread_reg_fp;
|
|
}
|
|
|
|
static void
|
|
riscv64_dump_backtrace_entry(struct bt_info *bt, struct syment *sym,
|
|
struct riscv64_unwind_frame *current,
|
|
struct riscv64_unwind_frame *previous, int level)
|
|
{
|
|
const char *name = sym ? sym->name : "(invalid)";
|
|
struct load_module *lm;
|
|
char *name_plus_offset = NULL;
|
|
struct syment *symp;
|
|
ulong symbol_offset;
|
|
char buf[BUFSIZE];
|
|
|
|
if (bt->flags & BT_SYMBOL_OFFSET) {
|
|
symp = value_search(current->pc, &symbol_offset);
|
|
|
|
if (symp && symbol_offset)
|
|
name_plus_offset =
|
|
value_to_symstr(current->pc, buf, bt->radix);
|
|
}
|
|
|
|
fprintf(fp, "%s#%d [%016lx] %s at %016lx",
|
|
level < 10 ? " " : "",
|
|
level,
|
|
current->sp,
|
|
name_plus_offset ? name_plus_offset : name,
|
|
current->pc);
|
|
|
|
if (module_symbol(current->pc, NULL, &lm, NULL, 0))
|
|
fprintf(fp, " [%s]", lm->mod_name);
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
/*
|
|
* 'bt -l', get a line number associated with a current pc address.
|
|
*/
|
|
if (bt->flags & BT_LINE_NUMBERS) {
|
|
get_line_number(current->pc, buf, FALSE);
|
|
if (strlen(buf))
|
|
fprintf(fp, " %s\n", buf);
|
|
}
|
|
|
|
/* bt -f */
|
|
if (bt->flags & BT_FULL) {
|
|
fprintf(fp, " "
|
|
"[PC: %016lx RA: %016lx SP: %016lx SIZE: %ld]\n",
|
|
current->pc,
|
|
previous->pc,
|
|
current->sp,
|
|
previous->sp - current->sp);
|
|
riscv64_display_full_frame(bt, current, previous);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Unroll a kernel stack.
|
|
*/
|
|
static void
|
|
riscv64_back_trace_cmd(struct bt_info *bt)
|
|
{
|
|
struct riscv64_unwind_frame current, previous;
|
|
struct stackframe curr_frame;
|
|
struct riscv64_register *regs, *irq_regs, *overflow_regs;
|
|
int level = 0;
|
|
|
|
if (bt->flags & BT_REGS_NOT_FOUND)
|
|
return;
|
|
|
|
regs = (struct riscv64_register *) bt->machdep;
|
|
|
|
if (riscv64_on_irq_stack(bt->tc->processor, bt->frameptr)) {
|
|
riscv64_set_irq_stack(bt);
|
|
bt->flags |= BT_IRQSTACK;
|
|
}
|
|
|
|
if (riscv64_on_overflow_stack(bt->tc->processor, bt->frameptr)) {
|
|
riscv64_set_overflow_stack(bt);
|
|
bt->flags |= BT_OVERFLOW_STACK;
|
|
}
|
|
|
|
current.pc = bt->instptr;
|
|
current.sp = bt->stkptr;
|
|
current.fp = bt->frameptr;
|
|
|
|
if (!INSTACK(current.sp, bt))
|
|
return;
|
|
|
|
for (;;) {
|
|
struct syment *symbol = NULL;
|
|
struct stackframe *frameptr;
|
|
ulong low, high;
|
|
ulong offset;
|
|
|
|
if (CRASHDEBUG(8))
|
|
fprintf(fp, "level %d pc %#lx sp %lx fp 0x%lx\n",
|
|
level, current.pc, current.sp, current.fp);
|
|
|
|
/* Validate frame pointer */
|
|
low = current.sp + sizeof(struct stackframe);
|
|
high = bt->stacktop;
|
|
if (current.fp < low || current.fp > high || current.fp & 0x7) {
|
|
if (CRASHDEBUG(8))
|
|
fprintf(fp, "fp 0x%lx sp 0x%lx low 0x%lx high 0x%lx\n",
|
|
current.fp, current.sp, low, high);
|
|
return;
|
|
}
|
|
|
|
symbol = value_search(current.pc, &offset);
|
|
if (!symbol)
|
|
return;
|
|
|
|
frameptr = (struct stackframe *)current.fp - 1;
|
|
if (!readmem((ulong)frameptr, KVADDR, &curr_frame,
|
|
sizeof(curr_frame), "get stack frame", RETURN_ON_ERROR))
|
|
return;
|
|
|
|
/* correct PC and FP of the second frame when the first frame has no callee */
|
|
|
|
if (regs && (regs->regs[RISCV64_REGS_EPC] == current.pc) && curr_frame.fp & 0x7){
|
|
previous.pc = regs->regs[RISCV64_REGS_RA];
|
|
previous.fp = curr_frame.ra;
|
|
} else {
|
|
previous.pc = curr_frame.ra;
|
|
previous.fp = curr_frame.fp;
|
|
}
|
|
|
|
previous.sp = current.fp;
|
|
|
|
riscv64_dump_backtrace_entry(bt, symbol, ¤t, &previous, level++);
|
|
|
|
current.pc = previous.pc;
|
|
current.fp = previous.fp;
|
|
current.sp = previous.sp;
|
|
|
|
/*
|
|
* When backtracing to do_irq(), find the original FP of do_irq()
|
|
* and then use the saved pt_regs in process stack to continue
|
|
*/
|
|
if ((bt->flags & BT_IRQSTACK) &&
|
|
!riscv64_on_irq_stack(bt->tc->processor, current.fp)){
|
|
if (riscv64_on_process_stack(bt, current.fp)){
|
|
|
|
frameptr = (struct stackframe *)current.fp - 1;
|
|
|
|
if (!readmem((ulong)frameptr, KVADDR, &curr_frame,
|
|
sizeof(curr_frame), "get do_irq stack frame", RETURN_ON_ERROR))
|
|
return;
|
|
|
|
riscv64_set_process_stack(bt);
|
|
|
|
irq_regs = (struct riscv64_register *)
|
|
&bt->stackbuf[(ulong)(STACK_OFFSET_TYPE(curr_frame.fp))];
|
|
|
|
current.pc = irq_regs->regs[RISCV64_REGS_EPC];
|
|
current.fp = irq_regs->regs[RISCV64_REGS_FP];
|
|
current.sp = irq_regs->regs[RISCV64_REGS_SP];
|
|
|
|
bt->flags &= ~BT_IRQSTACK;
|
|
riscv64_print_exception_frame(bt, curr_frame.fp, KERNEL_MODE);
|
|
fprintf(fp, "--- <IRQ stack> ---\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* When backtracing to handle_kernel_stack_overflow()
|
|
* use pt_regs saved in overflow stack to continue
|
|
*/
|
|
if ((bt->flags & BT_OVERFLOW_STACK) &&
|
|
!riscv64_on_overflow_stack(bt->tc->processor, current.fp)) {
|
|
|
|
overflow_regs = (struct riscv64_register *)
|
|
&bt->stackbuf[(ulong)(STACK_OFFSET_TYPE(current.sp))];
|
|
|
|
riscv64_print_exception_frame(bt, current.sp, KERNEL_MODE);
|
|
|
|
current.pc = overflow_regs->regs[RISCV64_REGS_EPC];
|
|
current.fp = overflow_regs->regs[RISCV64_REGS_FP];
|
|
current.sp = overflow_regs->regs[RISCV64_REGS_SP];
|
|
|
|
riscv64_set_process_stack(bt);
|
|
|
|
bt->flags &= ~BT_OVERFLOW_STACK;
|
|
fprintf(fp, "--- <OVERFLOW stack> ---\n");
|
|
}
|
|
|
|
if (CRASHDEBUG(8))
|
|
fprintf(fp, "next %d pc %#lx sp %#lx fp %lx\n",
|
|
level, current.pc, current.sp, current.fp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get a stack frame combination of pc and ra from the most relevant spot.
|
|
*/
|
|
static void
|
|
riscv64_get_stack_frame(struct bt_info *bt, ulong *pcp, ulong *spp)
|
|
{
|
|
ulong ksp = 0, nip = 0;
|
|
int ret = 0;
|
|
|
|
if (DUMPFILE() && is_task_active(bt->task))
|
|
ret = riscv64_get_dumpfile_stack_frame(bt, &nip, &ksp);
|
|
else
|
|
ret = riscv64_get_frame(bt, &nip, &ksp);
|
|
|
|
if (!ret)
|
|
error(WARNING, "cannot determine starting stack frame for task %lx\n",
|
|
bt->task);
|
|
|
|
if (pcp)
|
|
*pcp = nip;
|
|
if (spp)
|
|
*spp = ksp;
|
|
}
|
|
|
|
/*
|
|
* Get the starting point for the active cpu in a diskdump.
|
|
*/
|
|
static int
|
|
riscv64_get_dumpfile_stack_frame(struct bt_info *bt, ulong *nip, ulong *ksp)
|
|
{
|
|
const struct machine_specific *ms = machdep->machspec;
|
|
struct riscv64_register *regs;
|
|
ulong epc, sp;
|
|
|
|
if (!ms->crash_task_regs) {
|
|
bt->flags |= BT_REGS_NOT_FOUND;
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* We got registers for panic task from crash_notes. Just return them.
|
|
*/
|
|
regs = &ms->crash_task_regs[bt->tc->processor];
|
|
epc = regs->regs[RISCV64_REGS_EPC];
|
|
sp = regs->regs[RISCV64_REGS_SP];
|
|
|
|
/*
|
|
* Set stack frame ptr.
|
|
*/
|
|
bt->frameptr = regs->regs[RISCV64_REGS_FP];
|
|
|
|
if (nip)
|
|
*nip = epc;
|
|
if (ksp)
|
|
*ksp = sp;
|
|
|
|
bt->machdep = regs;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Do the work for riscv64_get_stack_frame() for non-active tasks.
|
|
* Get SP and PC values for idle tasks.
|
|
*/
|
|
static int
|
|
riscv64_get_frame(struct bt_info *bt, ulong *pcp, ulong *spp)
|
|
{
|
|
if (!bt->tc || !(tt->flags & THREAD_INFO))
|
|
return FALSE;
|
|
|
|
if (!readmem(bt->task + OFFSET(task_struct_thread_context_pc),
|
|
KVADDR, pcp, sizeof(*pcp),
|
|
"thread_struct.ra",
|
|
RETURN_ON_ERROR))
|
|
return FALSE;
|
|
|
|
if (!readmem(bt->task + OFFSET(task_struct_thread_context_sp),
|
|
KVADDR, spp, sizeof(*spp),
|
|
"thread_struct.sp",
|
|
RETURN_ON_ERROR))
|
|
return FALSE;
|
|
|
|
if (!readmem(bt->task + OFFSET(task_struct_thread_context_fp),
|
|
KVADDR, &bt->frameptr, sizeof(bt->frameptr),
|
|
"thread_struct.fp",
|
|
RETURN_ON_ERROR))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static int
|
|
riscv64_vtop_4level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose)
|
|
{
|
|
ulong *pgd_ptr, pgd_val;
|
|
ulong pud_val;
|
|
ulong pmd_val;
|
|
ulong pte_val, pte_pfn;
|
|
ulong pt_phys;
|
|
|
|
/* PGD */
|
|
pgd_ptr = pgd + pgd_index_l4_4k(vaddr);
|
|
FILL_PGD(pgd, KVADDR, PAGESIZE());
|
|
pgd_val = ULONG(machdep->pgd + PAGEOFFSET(pgd_ptr));
|
|
if (verbose)
|
|
fprintf(fp, " PGD: %lx => %lx\n", (ulong)pgd_ptr, pgd_val);
|
|
if (!pgd_val)
|
|
goto no_page;
|
|
pgd_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pgd_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PUD */
|
|
FILL_PUD(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pud_val = ULONG(machdep->pud + PAGEOFFSET(sizeof(pud_t) *
|
|
pud_index_l4_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PUD: %016lx => %016lx\n", pt_phys, pud_val);
|
|
if (!pud_val)
|
|
goto no_page;
|
|
pud_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pud_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PMD */
|
|
FILL_PMD(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pmd_val = ULONG(machdep->pmd + PAGEOFFSET(sizeof(pmd_t) *
|
|
pmd_index_l4_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PMD: %016lx => %016lx\n", pt_phys, pmd_val);
|
|
if (!pmd_val)
|
|
goto no_page;
|
|
pmd_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pmd_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PTE */
|
|
FILL_PTBL(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pte_val = ULONG(machdep->ptbl + PAGEOFFSET(sizeof(pte_t) *
|
|
pte_index_l4_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PTE: %lx => %lx\n", pt_phys, pte_val);
|
|
if (!pte_val)
|
|
goto no_page;
|
|
pte_val &= PTE_PFN_PROT_MASK;
|
|
pte_pfn = pte_val >> _PAGE_PFN_SHIFT;
|
|
|
|
if (!(pte_val & _PAGE_PRESENT)) {
|
|
if (verbose) {
|
|
fprintf(fp, "\n");
|
|
riscv64_translate_pte((ulong)pte_val, 0, 0);
|
|
}
|
|
fprintf(fp, " PAGE: %016lx not present\n\n", PAGEBASE(*paddr));
|
|
return FALSE;
|
|
}
|
|
|
|
*paddr = PTOB(pte_pfn) + PAGEOFFSET(vaddr);
|
|
|
|
if (verbose) {
|
|
fprintf(fp, " PAGE: %016lx\n\n", PAGEBASE(*paddr));
|
|
riscv64_translate_pte(pte_val, 0, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
no_page:
|
|
fprintf(fp, "invalid\n");
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
riscv64_vtop_5level_4k(ulong *pgd, ulong vaddr, physaddr_t *paddr, int verbose)
|
|
{
|
|
ulong *pgd_ptr, pgd_val;
|
|
ulong p4d_val;
|
|
ulong pud_val;
|
|
ulong pmd_val;
|
|
ulong pte_val, pte_pfn;
|
|
ulong pt_phys;
|
|
|
|
/* PGD */
|
|
pgd_ptr = pgd + pgd_index_l5_4k(vaddr);
|
|
FILL_PGD(pgd, KVADDR, PAGESIZE());
|
|
pgd_val = ULONG(machdep->pgd + PAGEOFFSET(pgd_ptr));
|
|
if (verbose)
|
|
fprintf(fp, " PGD: %lx => %lx\n", (ulong)pgd_ptr, pgd_val);
|
|
if (!pgd_val)
|
|
goto no_page;
|
|
pgd_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pgd_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* P4D */
|
|
FILL_P4D(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
p4d_val = ULONG(machdep->machspec->p4d + PAGEOFFSET(sizeof(p4d_t) *
|
|
p4d_index_l5_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " P4D: %016lx => %016lx\n", pt_phys, p4d_val);
|
|
if (!p4d_val)
|
|
goto no_page;
|
|
p4d_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (p4d_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PUD */
|
|
FILL_PUD(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pud_val = ULONG(machdep->pud + PAGEOFFSET(sizeof(pud_t) *
|
|
pud_index_l5_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PUD: %016lx => %016lx\n", pt_phys, pud_val);
|
|
if (!pud_val)
|
|
goto no_page;
|
|
pud_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pud_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PMD */
|
|
FILL_PMD(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pmd_val = ULONG(machdep->pmd + PAGEOFFSET(sizeof(pmd_t) *
|
|
pmd_index_l4_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PMD: %016lx => %016lx\n", pt_phys, pmd_val);
|
|
if (!pmd_val)
|
|
goto no_page;
|
|
pmd_val &= PTE_PFN_PROT_MASK;
|
|
pt_phys = (pmd_val >> _PAGE_PFN_SHIFT) << PAGESHIFT();
|
|
|
|
/* PTE */
|
|
FILL_PTBL(PAGEBASE(pt_phys), PHYSADDR, PAGESIZE());
|
|
pte_val = ULONG(machdep->ptbl + PAGEOFFSET(sizeof(pte_t) *
|
|
pte_index_l4_4k(vaddr)));
|
|
if (verbose)
|
|
fprintf(fp, " PTE: %lx => %lx\n", pt_phys, pte_val);
|
|
if (!pte_val)
|
|
goto no_page;
|
|
pte_val &= PTE_PFN_PROT_MASK;
|
|
pte_pfn = pte_val >> _PAGE_PFN_SHIFT;
|
|
|
|
if (!(pte_val & _PAGE_PRESENT)) {
|
|
if (verbose) {
|
|
fprintf(fp, "\n");
|
|
riscv64_translate_pte((ulong)pte_val, 0, 0);
|
|
}
|
|
printf("!_PAGE_PRESENT\n");
|
|
return FALSE;
|
|
}
|
|
|
|
*paddr = PTOB(pte_pfn) + PAGEOFFSET(vaddr);
|
|
|
|
if (verbose) {
|
|
fprintf(fp, " PAGE: %016lx\n\n", PAGEBASE(*paddr));
|
|
riscv64_translate_pte(pte_val, 0, 0);
|
|
}
|
|
|
|
return TRUE;
|
|
no_page:
|
|
fprintf(fp, "invalid\n");
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
riscv64_init_active_task_regs(void)
|
|
{
|
|
int retval;
|
|
|
|
retval = riscv64_get_crash_notes();
|
|
if (retval == TRUE)
|
|
return retval;
|
|
|
|
return riscv64_get_elf_notes();
|
|
}
|
|
|
|
/*
|
|
* Retrieve task registers for the time of the crash.
|
|
*/
|
|
static int
|
|
riscv64_get_crash_notes(void)
|
|
{
|
|
struct machine_specific *ms = machdep->machspec;
|
|
ulong crash_notes;
|
|
Elf64_Nhdr *note;
|
|
ulong offset;
|
|
char *buf, *p;
|
|
ulong *notes_ptrs;
|
|
ulong i;
|
|
|
|
/*
|
|
* crash_notes contains per cpu memory for storing cpu states
|
|
* in case of system crash.
|
|
*/
|
|
if (!symbol_exists("crash_notes"))
|
|
return FALSE;
|
|
|
|
crash_notes = symbol_value("crash_notes");
|
|
|
|
notes_ptrs = (ulong *)GETBUF(kt->cpus*sizeof(notes_ptrs[0]));
|
|
|
|
/*
|
|
* Read crash_notes for the first CPU. crash_notes are in standard ELF
|
|
* note format.
|
|
*/
|
|
if (!readmem(crash_notes, KVADDR, ¬es_ptrs[kt->cpus-1],
|
|
sizeof(notes_ptrs[kt->cpus-1]), "crash_notes",
|
|
RETURN_ON_ERROR)) {
|
|
error(WARNING, "cannot read crash_notes\n");
|
|
FREEBUF(notes_ptrs);
|
|
return FALSE;
|
|
}
|
|
|
|
if (symbol_exists("__per_cpu_offset")) {
|
|
|
|
/*
|
|
* 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];
|
|
}
|
|
|
|
buf = GETBUF(SIZE(note_buf));
|
|
|
|
if (!(panic_task_regs = calloc((size_t)kt->cpus, sizeof(*panic_task_regs))))
|
|
error(FATAL, "cannot calloc panic_task_regs space\n");
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
|
|
if (!readmem(notes_ptrs[i], KVADDR, buf, SIZE(note_buf), "note_buf_t",
|
|
RETURN_ON_ERROR)) {
|
|
error(WARNING,
|
|
"cannot find NT_PRSTATUS note for cpu: %d\n", i);
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Do some sanity checks for this note before reading registers from it.
|
|
*/
|
|
note = (Elf64_Nhdr *)buf;
|
|
p = buf + sizeof(Elf64_Nhdr);
|
|
|
|
/*
|
|
* dumpfiles created with qemu won't have crash_notes, but there will
|
|
* be elf notes; dumpfiles created by kdump do not create notes for
|
|
* offline cpus.
|
|
*/
|
|
if (note->n_namesz == 0 && (DISKDUMP_DUMPFILE() || KDUMP_DUMPFILE())) {
|
|
if (DISKDUMP_DUMPFILE())
|
|
note = diskdump_get_prstatus_percpu(i);
|
|
else if (KDUMP_DUMPFILE())
|
|
note = netdump_get_prstatus_percpu(i);
|
|
if (note) {
|
|
/*
|
|
* SIZE(note_buf) accounts for a "final note", which is a
|
|
* trailing empty elf note header.
|
|
*/
|
|
long notesz = SIZE(note_buf) - sizeof(Elf64_Nhdr);
|
|
|
|
if (sizeof(Elf64_Nhdr) + roundup(note->n_namesz, 4) +
|
|
note->n_descsz == notesz)
|
|
BCOPY((char *)note, buf, notesz);
|
|
} else {
|
|
error(WARNING,
|
|
"cannot find NT_PRSTATUS note for cpu: %d\n", i);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check the sanity of NT_PRSTATUS note only for each online cpu.
|
|
*/
|
|
if (note->n_type != NT_PRSTATUS) {
|
|
error(WARNING, "invalid NT_PRSTATUS note (n_type != NT_PRSTATUS)\n");
|
|
goto fail;
|
|
}
|
|
if (!STRNEQ(p, "CORE")) {
|
|
error(WARNING, "invalid NT_PRSTATUS note (name != \"CORE\"\n");
|
|
goto fail;
|
|
}
|
|
|
|
/*
|
|
* Find correct location of note data. This contains elf_prstatus
|
|
* structure which has registers etc. for the crashed task.
|
|
*/
|
|
offset = sizeof(Elf64_Nhdr);
|
|
offset = roundup(offset + note->n_namesz, 4);
|
|
p = buf + offset; /* start of elf_prstatus */
|
|
|
|
BCOPY(p + OFFSET(elf_prstatus_pr_reg), &panic_task_regs[i],
|
|
sizeof(panic_task_regs[i]));
|
|
}
|
|
|
|
/*
|
|
* And finally we have the registers for the crashed task. This is
|
|
* used later on when dumping backtrace.
|
|
*/
|
|
ms->crash_task_regs = panic_task_regs;
|
|
|
|
FREEBUF(buf);
|
|
FREEBUF(notes_ptrs);
|
|
return TRUE;
|
|
|
|
fail:
|
|
FREEBUF(buf);
|
|
FREEBUF(notes_ptrs);
|
|
free(panic_task_regs);
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
riscv64_get_elf_notes(void)
|
|
{
|
|
struct machine_specific *ms = machdep->machspec;
|
|
int i;
|
|
|
|
if (!DISKDUMP_DUMPFILE() && !KDUMP_DUMPFILE())
|
|
return FALSE;
|
|
|
|
panic_task_regs = calloc(kt->cpus, sizeof(*panic_task_regs));
|
|
if (!panic_task_regs)
|
|
error(FATAL, "cannot calloc panic_task_regs space\n");
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
Elf64_Nhdr *note = NULL;
|
|
size_t len;
|
|
|
|
if (DISKDUMP_DUMPFILE())
|
|
note = diskdump_get_prstatus_percpu(i);
|
|
else if (KDUMP_DUMPFILE())
|
|
note = netdump_get_prstatus_percpu(i);
|
|
|
|
if (!note) {
|
|
error(WARNING,
|
|
"cannot find NT_PRSTATUS note for cpu: %d\n", i);
|
|
continue;
|
|
}
|
|
|
|
len = sizeof(Elf64_Nhdr);
|
|
len = roundup(len + note->n_namesz, 4);
|
|
|
|
BCOPY((char *)note + len + OFFSET(elf_prstatus_pr_reg),
|
|
&panic_task_regs[i], sizeof(panic_task_regs[i]));
|
|
}
|
|
|
|
ms->crash_task_regs = panic_task_regs;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Translates a user virtual address to its physical address.
|
|
*/
|
|
static int
|
|
riscv64_uvtop(struct task_context *tc, ulong uvaddr, physaddr_t *paddr, int verbose)
|
|
{
|
|
ulong mm, active_mm;
|
|
ulong *pgd;
|
|
|
|
if (!tc)
|
|
error(FATAL, "current context invalid\n");
|
|
|
|
*paddr = 0;
|
|
|
|
if (is_kernel_thread(tc->task) && IS_KVADDR(uvaddr)) {
|
|
readmem(tc->task + OFFSET(task_struct_active_mm),
|
|
KVADDR, &active_mm, sizeof(void *),
|
|
"task active_mm contents", FAULT_ON_ERROR);
|
|
|
|
if (!active_mm)
|
|
error(FATAL,
|
|
"no active_mm for this kernel thread\n");
|
|
|
|
readmem(active_mm + OFFSET(mm_struct_pgd),
|
|
KVADDR, &pgd, sizeof(long),
|
|
"mm_struct pgd", FAULT_ON_ERROR);
|
|
} else {
|
|
if ((mm = task_mm(tc->task, TRUE)))
|
|
pgd = ULONG_PTR(tt->mm_struct + OFFSET(mm_struct_pgd));
|
|
else
|
|
readmem(tc->mm_struct + OFFSET(mm_struct_pgd),
|
|
KVADDR, &pgd, sizeof(long), "mm_struct pgd",
|
|
FAULT_ON_ERROR);
|
|
}
|
|
|
|
switch (machdep->flags & VM_FLAGS)
|
|
{
|
|
case VM_L3_4K:
|
|
return riscv64_vtop_3level_4k(pgd, uvaddr, paddr, verbose);
|
|
case VM_L4_4K:
|
|
return riscv64_vtop_4level_4k(pgd, uvaddr, paddr, verbose);
|
|
case VM_L5_4K:
|
|
return riscv64_vtop_5level_4k(pgd, uvaddr, paddr, verbose);
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
static int
|
|
riscv64_kvtop(struct task_context *tc, ulong kvaddr, physaddr_t *paddr, int verbose)
|
|
{
|
|
ulong kernel_pgd;
|
|
|
|
if (!IS_KVADDR(kvaddr))
|
|
return FALSE;
|
|
|
|
if (!vt->vmalloc_start) {
|
|
*paddr = VTOP(kvaddr);
|
|
return TRUE;
|
|
}
|
|
|
|
if (!IS_VMALLOC_ADDR(kvaddr)) {
|
|
*paddr = VTOP(kvaddr);
|
|
if (!verbose)
|
|
return TRUE;
|
|
}
|
|
|
|
kernel_pgd = vt->kernel_pgd[0];
|
|
*paddr = 0;
|
|
|
|
switch (machdep->flags & VM_FLAGS)
|
|
{
|
|
case VM_L3_4K:
|
|
return riscv64_vtop_3level_4k((ulong *)kernel_pgd, kvaddr, paddr, verbose);
|
|
case VM_L4_4K:
|
|
return riscv64_vtop_4level_4k((ulong *)kernel_pgd, kvaddr, paddr, verbose);
|
|
case VM_L5_4K:
|
|
return riscv64_vtop_5level_4k((ulong *)kernel_pgd, kvaddr, paddr, verbose);
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
void
|
|
riscv64_init(int when)
|
|
{
|
|
switch (when) {
|
|
case SETUP_ENV:
|
|
machdep->process_elf_notes = process_elf64_notes;
|
|
break;
|
|
|
|
case PRE_SYMTAB:
|
|
machdep->verify_symbol = riscv64_verify_symbol;
|
|
machdep->machspec = &riscv64_machine_specific;
|
|
if (pc->flags & KERNEL_DEBUG_QUERY)
|
|
return;
|
|
|
|
machdep->verify_paddr = generic_verify_paddr;
|
|
machdep->ptrs_per_pgd = PTRS_PER_PGD;
|
|
|
|
/*
|
|
* Even if CONFIG_RANDOMIZE_BASE is not configured,
|
|
* derive_kaslr_offset() should work and set
|
|
* kt->relocate to 0
|
|
*/
|
|
if (!kt->relocate && !(kt->flags2 & (RELOC_AUTO|KASLR)))
|
|
kt->flags2 |= (RELOC_AUTO|KASLR);
|
|
break;
|
|
|
|
case PRE_GDB:
|
|
machdep->pagesize = riscv64_get_page_size();
|
|
machdep->pageshift = ffs(machdep->pagesize) - 1;
|
|
machdep->pageoffset = machdep->pagesize - 1;
|
|
machdep->pagemask = ~((ulonglong)machdep->pageoffset);
|
|
machdep->stacksize = machdep->pagesize << THREAD_SIZE_ORDER;
|
|
|
|
riscv64_get_phys_ram_base(machdep->machspec);
|
|
riscv64_get_struct_page_size(machdep->machspec);
|
|
riscv64_get_va_bits(machdep->machspec);
|
|
riscv64_get_va_range(machdep->machspec);
|
|
riscv64_get_va_kernel_pa_offset(machdep->machspec);
|
|
|
|
pt_level_alloc(&machdep->pgd, "cannot malloc pgd space.");
|
|
pt_level_alloc(&machdep->machspec->p4d, "cannot malloc p4d space.");
|
|
pt_level_alloc(&machdep->pud, "cannot malloc pud space.");
|
|
pt_level_alloc(&machdep->pmd, "cannot malloc pmd space.");
|
|
pt_level_alloc(&machdep->ptbl, "cannot malloc ptbl space.");
|
|
|
|
machdep->last_pgd_read = 0;
|
|
machdep->machspec->last_p4d_read = 0;
|
|
machdep->last_pud_read = 0;
|
|
machdep->last_pmd_read = 0;
|
|
machdep->last_ptbl_read = 0;
|
|
|
|
machdep->kvbase = machdep->machspec->page_offset;
|
|
machdep->identity_map_base = machdep->kvbase;
|
|
machdep->is_kvaddr = riscv64_is_kvaddr;
|
|
machdep->is_uvaddr = riscv64_is_uvaddr;
|
|
machdep->uvtop = riscv64_uvtop;
|
|
machdep->kvtop = riscv64_kvtop;
|
|
machdep->cmd_mach = riscv64_cmd_mach;
|
|
machdep->get_stack_frame = riscv64_get_stack_frame;
|
|
machdep->back_trace = riscv64_back_trace_cmd;
|
|
machdep->eframe_search = riscv64_eframe_search;
|
|
|
|
machdep->vmalloc_start = riscv64_vmalloc_start;
|
|
machdep->processor_speed = riscv64_processor_speed;
|
|
machdep->get_stackbase = generic_get_stackbase;
|
|
machdep->get_stacktop = generic_get_stacktop;
|
|
machdep->translate_pte = riscv64_translate_pte;
|
|
machdep->memory_size = generic_memory_size;
|
|
machdep->is_task_addr = riscv64_is_task_addr;
|
|
machdep->get_smp_cpus = riscv64_get_smp_cpus;
|
|
machdep->value_to_symbol = generic_machdep_value_to_symbol;
|
|
machdep->dis_filter = generic_dis_filter;
|
|
machdep->dump_irq = generic_dump_irq;
|
|
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") */
|
|
break;
|
|
|
|
case POST_GDB:
|
|
machdep->section_size_bits = _SECTION_SIZE_BITS;
|
|
machdep->max_physmem_bits = _MAX_PHYSMEM_BITS;
|
|
|
|
riscv64_irq_stack_init();
|
|
riscv64_overflow_stack_init();
|
|
riscv64_stackframe_init();
|
|
riscv64_page_type_init();
|
|
|
|
if (!machdep->hz)
|
|
machdep->hz = 250;
|
|
|
|
if (symbol_exists("irq_desc"))
|
|
ARRAY_LENGTH_INIT(machdep->nr_irqs, irq_desc,
|
|
"irq_desc", NULL, 0);
|
|
else if (kernel_symbol_exists("nr_irqs"))
|
|
get_symbol_data("nr_irqs", sizeof(unsigned int),
|
|
&machdep->nr_irqs);
|
|
|
|
MEMBER_OFFSET_INIT(elf_prstatus_pr_reg, "elf_prstatus",
|
|
"pr_reg");
|
|
|
|
STRUCT_SIZE_INIT(note_buf, "note_buf_t");
|
|
break;
|
|
|
|
case POST_VM:
|
|
/*
|
|
* crash_notes contains machine specific information about the
|
|
* crash. In particular, it contains CPU registers at the time
|
|
* of the crash. We need this information to extract correct
|
|
* backtraces from the panic task.
|
|
*/
|
|
if (!ACTIVE() && !riscv64_init_active_task_regs())
|
|
error(WARNING,
|
|
"cannot retrieve registers for active task%s\n\n",
|
|
kt->cpus > 1 ? "s" : "");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* bool pt_regs : pass 1 to dump pt_regs , pass 0 to dump user_regs_struct */
|
|
static void
|
|
riscv64_dump_pt_regs(struct riscv64_register *regs, FILE *ofp, bool pt_regs)
|
|
{
|
|
|
|
/* Print riscv64 32 regs */
|
|
fprintf(ofp,
|
|
"epc : " REG_FMT " ra : " REG_FMT " sp : " REG_FMT "\n"
|
|
" gp : " REG_FMT " tp : " REG_FMT " t0 : " REG_FMT "\n"
|
|
" t1 : " REG_FMT " t2 : " REG_FMT " s0 : " REG_FMT "\n"
|
|
" s1 : " REG_FMT " a0 : " REG_FMT " a1 : " REG_FMT "\n"
|
|
" a2 : " REG_FMT " a3 : " REG_FMT " a4 : " REG_FMT "\n"
|
|
" a5 : " REG_FMT " a6 : " REG_FMT " a7 : " REG_FMT "\n"
|
|
" s2 : " REG_FMT " s3 : " REG_FMT " s4 : " REG_FMT "\n"
|
|
" s5 : " REG_FMT " s6 : " REG_FMT " s7 : " REG_FMT "\n"
|
|
" s8 : " REG_FMT " s9 : " REG_FMT " s10: " REG_FMT "\n"
|
|
" s11: " REG_FMT " t3 : " REG_FMT " t4 : " REG_FMT "\n"
|
|
" t5 : " REG_FMT " t6 : " REG_FMT "\n",
|
|
regs->regs[0], regs->regs[1], regs->regs[2],
|
|
regs->regs[3], regs->regs[4], regs->regs[5],
|
|
regs->regs[6], regs->regs[7], regs->regs[8],
|
|
regs->regs[9], regs->regs[10], regs->regs[11],
|
|
regs->regs[12], regs->regs[13], regs->regs[14],
|
|
regs->regs[15], regs->regs[16], regs->regs[17],
|
|
regs->regs[18], regs->regs[19], regs->regs[20],
|
|
regs->regs[21], regs->regs[22], regs->regs[23],
|
|
regs->regs[24], regs->regs[25], regs->regs[26],
|
|
regs->regs[27], regs->regs[28], regs->regs[29],
|
|
regs->regs[30], regs->regs[31]);
|
|
|
|
if (pt_regs)
|
|
fprintf(ofp,
|
|
" status: " REG_FMT " badaddr: " REG_FMT "\n"
|
|
" cause: " REG_FMT " orig_a0: " REG_FMT "\n",
|
|
regs->regs[32], regs->regs[33], regs->regs[34],
|
|
regs->regs[35]);
|
|
}
|
|
|
|
/*
|
|
* 'help -r' command output
|
|
*/
|
|
void
|
|
riscv64_display_regs_from_elf_notes(int cpu, FILE *ofp)
|
|
{
|
|
const struct machine_specific *ms = machdep->machspec;
|
|
struct riscv64_register *regs;
|
|
|
|
if (!ms->crash_task_regs) {
|
|
error(INFO, "registers not collected for cpu %d\n", cpu);
|
|
return;
|
|
}
|
|
|
|
regs = &ms->crash_task_regs[cpu];
|
|
if (!regs->regs[RISCV64_REGS_SP] && !regs->regs[RISCV64_REGS_EPC]) {
|
|
error(INFO, "registers not collected for cpu %d\n", cpu);
|
|
return;
|
|
}
|
|
|
|
riscv64_dump_pt_regs(regs, ofp, 0);
|
|
}
|
|
|
|
static void
|
|
riscv64_print_exception_frame(struct bt_info *bt, ulong ptr, int mode)
|
|
{
|
|
|
|
struct syment *sp;
|
|
ulong PC, RA, SP, offset;
|
|
struct riscv64_register *regs;
|
|
|
|
regs = (struct riscv64_register *)&bt->stackbuf[(ulong)(STACK_OFFSET_TYPE(ptr))];
|
|
|
|
PC = regs->regs[RISCV64_REGS_EPC];
|
|
RA = regs->regs[RISCV64_REGS_RA];
|
|
SP = regs->regs[RISCV64_REGS_SP];
|
|
|
|
switch (mode) {
|
|
case USER_MODE:
|
|
fprintf(fp,
|
|
" PC: %016lx RA: %016lx SP: %016lx\n"
|
|
" ORIG_A0: %016lx SYSCALLNO: %016lx\n",
|
|
PC, RA, SP, regs->regs[35], regs->regs[17]);
|
|
|
|
break;
|
|
|
|
case KERNEL_MODE:
|
|
fprintf(fp, " PC: %016lx ", PC);
|
|
if (is_kernel_text(PC) && (sp = value_search(PC, &offset))) {
|
|
fprintf(fp, "[%s", sp->name);
|
|
if (offset)
|
|
fprintf(fp, (*gdb_output_radix == 16) ?
|
|
"+0x%lx" : "+%ld", offset);
|
|
fprintf(fp, "]\n");
|
|
} else
|
|
fprintf(fp, "[unknown or invalid address]\n");
|
|
|
|
fprintf(fp, " RA: %016lx ", RA);
|
|
if (is_kernel_text(RA) && (sp = value_search(RA, &offset))) {
|
|
fprintf(fp, "[%s", sp->name);
|
|
if (offset)
|
|
fprintf(fp, (*gdb_output_radix == 16) ?
|
|
"+0x%lx" : "+%ld", offset);
|
|
fprintf(fp, "]\n");
|
|
} else
|
|
fprintf(fp, "[unknown or invalid address]\n");
|
|
|
|
fprintf(fp, " SP: %016lx CAUSE: %016lx\n",
|
|
SP, regs->regs[RISCV64_REGS_CAUSE]);
|
|
|
|
break;
|
|
}
|
|
|
|
riscv64_dump_pt_regs(regs, fp, 1);
|
|
|
|
}
|
|
|
|
static int
|
|
riscv64_is_kernel_exception_frame(struct bt_info *bt, ulong stkptr)
|
|
{
|
|
struct riscv64_register *regs;
|
|
|
|
if (stkptr > STACKSIZE() && !INSTACK(stkptr, bt)) {
|
|
if (CRASHDEBUG(1))
|
|
error(WARNING, "stkptr: %lx is outside the kernel stack range\n", stkptr);
|
|
return FALSE;
|
|
}
|
|
|
|
regs = (struct riscv64_register *)&bt->stackbuf[(ulong)(STACK_OFFSET_TYPE(stkptr))];
|
|
|
|
if (INSTACK(regs->regs[RISCV64_REGS_SP], bt) &&
|
|
INSTACK(regs->regs[RISCV64_REGS_FP], bt) &&
|
|
is_kernel_text(regs->regs[RISCV64_REGS_RA]) &&
|
|
is_kernel_text(regs->regs[RISCV64_REGS_EPC]) &&
|
|
((regs->regs[RISCV64_REGS_STATUS] >> 8) & 0x1) && // sstatus.SPP != 0
|
|
!((regs->regs[RISCV64_REGS_CAUSE] >> 63) & 0x1 ) && // scause.Interrupt != 1
|
|
!(regs->regs[RISCV64_REGS_CAUSE] == 0x00000008UL)) { // scause != ecall from U-mode
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int
|
|
riscv64_dump_kernel_eframes(struct bt_info *bt)
|
|
{
|
|
ulong ptr;
|
|
int count;
|
|
|
|
/*
|
|
* use old_regs to avoid the identical contiguous kernel exception frames
|
|
* created by Linux handle_exception() path ending at riscv_crash_save_regs()
|
|
*/
|
|
struct riscv64_register *regs, *old_regs;
|
|
|
|
count = 0;
|
|
old_regs = NULL;
|
|
|
|
for (ptr = bt->stackbase; ptr < bt->stacktop - SIZE(pt_regs); ptr++) {
|
|
|
|
regs = (struct riscv64_register *)&bt->stackbuf[(ulong)(STACK_OFFSET_TYPE(ptr))];
|
|
|
|
if (riscv64_is_kernel_exception_frame(bt, ptr)){
|
|
if (!old_regs || (old_regs &&
|
|
memcmp(old_regs, regs, sizeof(struct riscv64_register))) != 0){
|
|
old_regs = regs;
|
|
fprintf(fp, "\nKERNEL-MODE EXCEPTION FRAME AT: %lx\n", ptr);
|
|
riscv64_print_exception_frame(bt, ptr, KERNEL_MODE);
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static int
|
|
riscv64_eframe_search(struct bt_info *bt)
|
|
{
|
|
ulong ptr;
|
|
int count, c;
|
|
struct machine_specific *ms = machdep->machspec;
|
|
|
|
if (bt->flags & BT_EFRAME_SEARCH2) {
|
|
if (!(machdep->flags & IRQ_STACKS))
|
|
error(FATAL, "IRQ stacks do not exist in this kernel\n");
|
|
|
|
for (c = 0; c < kt->cpus; c++) {
|
|
if ((bt->flags & BT_CPUMASK) &&
|
|
!(NUM_IN_BITMAP(bt->cpumask, c)))
|
|
continue;
|
|
|
|
fprintf(fp, "CPU %d IRQ STACK: ", c);
|
|
bt->stackbase = ms->irq_stacks[c];
|
|
bt->stacktop = bt->stackbase + ms->irq_stack_size;
|
|
alter_stackbuf(bt);
|
|
|
|
count = riscv64_dump_kernel_eframes(bt);
|
|
|
|
if (count)
|
|
fprintf(fp, "\n");
|
|
else
|
|
fprintf(fp, "(none found)\n\n");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
count = riscv64_dump_kernel_eframes(bt);
|
|
|
|
if (is_kernel_thread(bt->tc->task))
|
|
return count;
|
|
|
|
ptr = bt->stacktop - SIZE(pt_regs);
|
|
fprintf(fp, "%sUSER-MODE EXCEPTION FRAME AT: %lx\n", count++ ? "\n" : "", ptr);
|
|
riscv64_print_exception_frame(bt, ptr, USER_MODE);
|
|
|
|
return count;
|
|
}
|
|
|
|
#else /* !RISCV64 */
|
|
|
|
void
|
|
riscv64_display_regs_from_elf_notes(int cpu, FILE *ofp)
|
|
{
|
|
return;
|
|
}
|
|
|
|
#endif /* !RISCV64 */
|