mirror of
https://github.com/crash-utility/crash
synced 2024-12-14 13:14:31 +00:00
4119e19053
"sys" command to account for the Linux 3.17 change that moved the "timekeeper" symbol and structure into a containing tk_core structure; the "shadow_timekeeper" timekeeper will be used as an alternative. Without the patch, the DATE shows something within a few hours of the Linux epoch, such as "Wed Dec 31 18:00:00 1969". (kmcmartin@redhat.com)
9593 lines
266 KiB
C
9593 lines
266 KiB
C
/* kernel.c - core analysis suite
|
|
*
|
|
* Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
|
|
* Copyright (C) 2002-2015 David Anderson
|
|
* Copyright (C) 2002-2015 Red Hat, Inc. All rights reserved.
|
|
*
|
|
* 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"
|
|
#include "xen_hyper_defs.h"
|
|
#include <elf.h>
|
|
#include <libgen.h>
|
|
#include <ctype.h>
|
|
|
|
static void do_module_cmd(ulong, char *, ulong, char *, char *);
|
|
static void show_module_taint(void);
|
|
static char *find_module_objfile(char *, char *, char *);
|
|
static char *module_objfile_search(char *, char *, char *);
|
|
static char *get_loadavg(char *);
|
|
static void get_lkcd_regs(struct bt_info *, ulong *, ulong *);
|
|
static void dump_sys_call_table(char *, int);
|
|
static int get_NR_syscalls(int *);
|
|
static ulong get_irq_desc_addr(int);
|
|
static void display_cpu_affinity(ulong *);
|
|
static void display_bh_1(void);
|
|
static void display_bh_2(void);
|
|
static void display_bh_3(void);
|
|
static void display_bh_4(void);
|
|
static void dump_hrtimer_data(void);
|
|
static void dump_hrtimer_clock_base(const void *, const int);
|
|
static void dump_hrtimer_base(const void *, const int);
|
|
static void dump_active_timers(const void *, ulonglong);
|
|
static int get_expires_len(const int, const ulong *, const int);
|
|
static void print_timer(const void *);
|
|
static ulonglong ktime_to_ns(const void *);
|
|
static void dump_timer_data(void);
|
|
static void dump_timer_data_tvec_bases_v1(void);
|
|
static void dump_timer_data_tvec_bases_v2(void);
|
|
struct tv_range;
|
|
static void init_tv_ranges(struct tv_range *, int, int, int);
|
|
static int do_timer_list(ulong,int, ulong *, void *,ulong *,struct tv_range *);
|
|
static int compare_timer_data(const void *, const void *);
|
|
static void panic_this_kernel(void);
|
|
static void dump_waitq(ulong, char *);
|
|
static void reinit_modules(void);
|
|
static int verify_modules(void);
|
|
static void verify_namelist(void);
|
|
static char *debug_kernel_version(char *);
|
|
static int restore_stack(struct bt_info *);
|
|
static ulong __xen_m2p(ulonglong, ulong);
|
|
static ulong __xen_pvops_m2p_l2(ulonglong, ulong);
|
|
static ulong __xen_pvops_m2p_l3(ulonglong, ulong);
|
|
static int search_mapping_page(ulong, ulong *, ulong *, ulong *);
|
|
static void read_in_kernel_config_err(int, char *);
|
|
static void BUG_bytes_init(void);
|
|
static int BUG_x86(void);
|
|
static int BUG_x86_64(void);
|
|
static void cpu_maps_init(void);
|
|
static void get_xtime(struct timespec *);
|
|
static char *log_from_idx(uint32_t, char *);
|
|
static uint32_t log_next(uint32_t, char *);
|
|
static void dump_log_entry(char *, int);
|
|
static void dump_variable_length_record_log(int);
|
|
static void hypervisor_init(void);
|
|
static void dump_log_legacy(void);
|
|
static void dump_variable_length_record(void);
|
|
static int is_livepatch(void);
|
|
static void show_kernel_taints(char *, int);
|
|
|
|
|
|
/*
|
|
* Gather a few kernel basics.
|
|
*/
|
|
void
|
|
kernel_init()
|
|
{
|
|
int i, c;
|
|
char *p1, *p2, buf[BUFSIZE];
|
|
struct syment *sp1, *sp2;
|
|
char *rqstruct;
|
|
char *rq_timestamp_name = NULL;
|
|
char *irq_desc_type_name;
|
|
ulong pv_init_ops;
|
|
|
|
if (pc->flags & KERNEL_DEBUG_QUERY)
|
|
return;
|
|
|
|
if (!(kt->cpu_flags = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc cpu_flags array");
|
|
|
|
cpu_maps_init();
|
|
|
|
kt->stext = symbol_value("_stext");
|
|
kt->etext = symbol_value("_etext");
|
|
get_text_init_space();
|
|
if (symbol_exists("__init_begin")) {
|
|
kt->init_begin = symbol_value("__init_begin");
|
|
kt->init_end = symbol_value("__init_end");
|
|
}
|
|
if (symbol_exists("_end"))
|
|
kt->end = symbol_value("_end");
|
|
else
|
|
kt->end = highest_bss_symbol();
|
|
|
|
/*
|
|
* For the traditional (non-pv_ops) Xen architecture, default to writable
|
|
* page tables unless:
|
|
*
|
|
* (1) it's an "xm save" CANONICAL_PAGE_TABLES dumpfile, or
|
|
* (2) the --shadow_page_tables option was explicitly entered.
|
|
*
|
|
* But if the "phys_to_maching_mapping" array does not exist, and
|
|
* it's not an "xm save" canonical dumpfile, then we have no choice
|
|
* but to presume shadow page tables.
|
|
*/
|
|
if (!PVOPS() && symbol_exists("xen_start_info")) {
|
|
kt->flags |= ARCH_XEN;
|
|
if (!(kt->xen_flags & (SHADOW_PAGE_TABLES|CANONICAL_PAGE_TABLES)))
|
|
kt->xen_flags |= WRITABLE_PAGE_TABLES;
|
|
if (symbol_exists("phys_to_machine_mapping"))
|
|
get_symbol_data("phys_to_machine_mapping", sizeof(ulong),
|
|
&kt->phys_to_machine_mapping);
|
|
else if (!(kt->xen_flags & CANONICAL_PAGE_TABLES)) {
|
|
kt->xen_flags &= ~WRITABLE_PAGE_TABLES;
|
|
kt->xen_flags |= SHADOW_PAGE_TABLES;
|
|
}
|
|
if (machine_type("X86"))
|
|
get_symbol_data("max_pfn", sizeof(ulong), &kt->p2m_table_size);
|
|
if (machine_type("X86_64")) {
|
|
/*
|
|
* kernel version < 2.6.27 => end_pfn
|
|
* kernel version >= 2.6.27 => max_pfn
|
|
*/
|
|
if (!try_get_symbol_data("end_pfn", sizeof(ulong), &kt->p2m_table_size))
|
|
get_symbol_data("max_pfn", sizeof(ulong), &kt->p2m_table_size);
|
|
}
|
|
if ((kt->m2p_page = (char *)malloc(PAGESIZE())) == NULL)
|
|
error(FATAL, "cannot malloc m2p page.");
|
|
}
|
|
|
|
if (PVOPS() && readmem(symbol_value("pv_init_ops"), KVADDR, &pv_init_ops,
|
|
sizeof(void *), "pv_init_ops", RETURN_ON_ERROR) &&
|
|
(p1 = value_symbol(pv_init_ops)) &&
|
|
STREQ(p1, "xen_patch")) {
|
|
kt->flags |= ARCH_XEN | ARCH_PVOPS_XEN;
|
|
kt->xen_flags |= WRITABLE_PAGE_TABLES;
|
|
if (machine_type("X86"))
|
|
get_symbol_data("max_pfn", sizeof(ulong), &kt->p2m_table_size);
|
|
if (machine_type("X86_64")) {
|
|
if (!try_get_symbol_data("end_pfn", sizeof(ulong), &kt->p2m_table_size))
|
|
get_symbol_data("max_pfn", sizeof(ulong), &kt->p2m_table_size);
|
|
}
|
|
if ((kt->m2p_page = (char *)malloc(PAGESIZE())) == NULL)
|
|
error(FATAL, "cannot malloc m2p page.");
|
|
|
|
if (symbol_exists("p2m_mid_missing")) {
|
|
kt->pvops_xen.p2m_top_entries = XEN_P2M_TOP_PER_PAGE;
|
|
get_symbol_data("p2m_top", sizeof(ulong),
|
|
&kt->pvops_xen.p2m_top);
|
|
get_symbol_data("p2m_mid_missing", sizeof(ulong),
|
|
&kt->pvops_xen.p2m_mid_missing);
|
|
get_symbol_data("p2m_missing", sizeof(ulong),
|
|
&kt->pvops_xen.p2m_missing);
|
|
} else {
|
|
kt->pvops_xen.p2m_top_entries = get_array_length("p2m_top", NULL, 0);
|
|
kt->pvops_xen.p2m_top = symbol_value("p2m_top");
|
|
kt->pvops_xen.p2m_missing = symbol_value("p2m_missing");
|
|
}
|
|
}
|
|
|
|
if (symbol_exists("smp_num_cpus")) {
|
|
kt->flags |= SMP;
|
|
get_symbol_data("smp_num_cpus", sizeof(int), &kt->cpus);
|
|
if (kt->cpus < 1 || kt->cpus > NR_CPUS)
|
|
error(WARNING,
|
|
"invalid value: smp_num_cpus: %d\n",
|
|
kt->cpus);
|
|
} else if (symbol_exists("__per_cpu_offset")) {
|
|
kt->flags |= SMP;
|
|
kt->cpus = 1;
|
|
} else
|
|
kt->cpus = 1;
|
|
|
|
if ((sp1 = symbol_search("__per_cpu_start")) &&
|
|
(sp2 = symbol_search("__per_cpu_end")) &&
|
|
(sp1->type == 'A' || sp1->type == 'D') &&
|
|
(sp2->type == 'A' || sp2->type == 'D') &&
|
|
(sp2->value > sp1->value))
|
|
kt->flags |= SMP|PER_CPU_OFF;
|
|
|
|
MEMBER_OFFSET_INIT(timekeeper_xtime, "timekeeper", "xtime");
|
|
MEMBER_OFFSET_INIT(timekeeper_xtime_sec, "timekeeper", "xtime_sec");
|
|
get_xtime(&kt->date);
|
|
|
|
if (kt->flags2 & GET_TIMESTAMP) {
|
|
fprintf(fp, "%s\n\n",
|
|
strip_linefeeds(ctime(&kt->date.tv_sec)));
|
|
clean_exit(0);
|
|
}
|
|
|
|
if (symbol_exists("system_utsname"))
|
|
readmem(symbol_value("system_utsname"), KVADDR, &kt->utsname,
|
|
sizeof(struct new_utsname), "system_utsname",
|
|
RETURN_ON_ERROR);
|
|
else if (symbol_exists("init_uts_ns"))
|
|
readmem(symbol_value("init_uts_ns") + sizeof(int),
|
|
KVADDR, &kt->utsname, sizeof(struct new_utsname),
|
|
"init_uts_ns", RETURN_ON_ERROR);
|
|
else
|
|
error(INFO, "cannot access utsname information\n\n");
|
|
|
|
strncpy(buf, kt->utsname.release, MIN(strlen(kt->utsname.release), 65));
|
|
if (ascii_string(kt->utsname.release)) {
|
|
char separator;
|
|
|
|
p1 = p2 = buf;
|
|
while (*p2 != '.')
|
|
p2++;
|
|
*p2 = NULLCHAR;
|
|
kt->kernel_version[0] = atoi(p1);
|
|
p1 = ++p2;
|
|
while (*p2 != '.' && *p2 != '-' && *p2 != '\0')
|
|
p2++;
|
|
separator = *p2;
|
|
*p2 = NULLCHAR;
|
|
kt->kernel_version[1] = atoi(p1);
|
|
*p2 = separator;
|
|
if (*p2 == '.') {
|
|
p1 = ++p2;
|
|
while ((*p2 >= '0') && (*p2 <= '9'))
|
|
p2++;
|
|
*p2 = NULLCHAR;
|
|
kt->kernel_version[2] = atoi(p1);
|
|
} else
|
|
kt->kernel_version[2] = 0;
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "base kernel version: %d.%d.%d\n",
|
|
kt->kernel_version[0],
|
|
kt->kernel_version[1],
|
|
kt->kernel_version[2]);
|
|
} else
|
|
error(INFO, "cannot determine base kernel version\n");
|
|
|
|
|
|
verify_version();
|
|
|
|
if (symbol_exists("__per_cpu_offset")) {
|
|
if (LKCD_KERNTYPES())
|
|
i = get_cpus_possible();
|
|
else
|
|
i = get_array_length("__per_cpu_offset", NULL, 0);
|
|
get_symbol_data("__per_cpu_offset",
|
|
sizeof(long)*((i && (i <= NR_CPUS)) ? i : NR_CPUS),
|
|
&kt->__per_cpu_offset[0]);
|
|
kt->flags |= PER_CPU_OFF;
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(percpu_counter_count, "percpu_counter", "count");
|
|
|
|
if (STRUCT_EXISTS("runqueue")) {
|
|
rqstruct = "runqueue";
|
|
rq_timestamp_name = "timestamp_last_tick";
|
|
} else if (STRUCT_EXISTS("rq")) {
|
|
rqstruct = "rq";
|
|
if (MEMBER_EXISTS("rq", "clock"))
|
|
rq_timestamp_name = "clock";
|
|
else if (MEMBER_EXISTS("rq", "most_recent_timestamp"))
|
|
rq_timestamp_name = "most_recent_timestamp";
|
|
else if (MEMBER_EXISTS("rq", "timestamp_last_tick"))
|
|
rq_timestamp_name = "timestamp_last_tick";
|
|
} else {
|
|
rqstruct = NULL;
|
|
error(FATAL, "neither runqueue nor rq structures exist\n");
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(runqueue_cpu, rqstruct, "cpu");
|
|
/*
|
|
* 'cpu' does not exist in 'struct rq'.
|
|
*/
|
|
if (VALID_MEMBER(runqueue_cpu) &&
|
|
(get_array_length("runqueue.cpu", NULL, 0) > 0)) {
|
|
MEMBER_OFFSET_INIT(cpu_s_curr, "cpu_s", "curr");
|
|
MEMBER_OFFSET_INIT(cpu_s_idle, "cpu_s", "idle");
|
|
STRUCT_SIZE_INIT(cpu_s, "cpu_s");
|
|
kt->runq_siblings = get_array_length("runqueue.cpu",
|
|
NULL, 0);
|
|
if (symbol_exists("__cpu_idx") &&
|
|
symbol_exists("__rq_idx")) {
|
|
if (!(kt->__cpu_idx = (long *)
|
|
calloc(NR_CPUS, sizeof(long))))
|
|
error(FATAL, "cannot malloc __cpu_idx array");
|
|
if (!(kt->__rq_idx = (long *)
|
|
calloc(NR_CPUS, sizeof(long))))
|
|
error(FATAL, "cannot malloc __rq_idx array");
|
|
if (!readmem(symbol_value("__cpu_idx"), KVADDR,
|
|
&kt->__cpu_idx[0], sizeof(long) * NR_CPUS,
|
|
"__cpu_idx[NR_CPUS]", RETURN_ON_ERROR))
|
|
error(INFO,
|
|
"cannot read __cpu_idx[NR_CPUS] array\n");
|
|
if (!readmem(symbol_value("__rq_idx"), KVADDR,
|
|
&kt->__rq_idx[0], sizeof(long) * NR_CPUS,
|
|
"__rq_idx[NR_CPUS]", RETURN_ON_ERROR))
|
|
error(INFO,
|
|
"cannot read __rq_idx[NR_CPUS] array\n");
|
|
} else if (kt->runq_siblings > 1)
|
|
error(INFO,
|
|
"runq_siblings: %d: __cpu_idx and __rq_idx arrays don't exist?\n",
|
|
kt->runq_siblings);
|
|
} else {
|
|
MEMBER_OFFSET_INIT(runqueue_idle, rqstruct, "idle");
|
|
MEMBER_OFFSET_INIT(runqueue_curr, rqstruct, "curr");
|
|
ASSIGN_OFFSET(runqueue_cpu) = INVALID_OFFSET;
|
|
}
|
|
MEMBER_OFFSET_INIT(runqueue_active, rqstruct, "active");
|
|
MEMBER_OFFSET_INIT(runqueue_expired, rqstruct, "expired");
|
|
MEMBER_OFFSET_INIT(runqueue_arrays, rqstruct, "arrays");
|
|
MEMBER_OFFSET_INIT(rq_timestamp, rqstruct, rq_timestamp_name);
|
|
MEMBER_OFFSET_INIT(prio_array_queue, "prio_array", "queue");
|
|
MEMBER_OFFSET_INIT(prio_array_nr_active, "prio_array", "nr_active");
|
|
STRUCT_SIZE_INIT(runqueue, rqstruct);
|
|
STRUCT_SIZE_INIT(prio_array, "prio_array");
|
|
|
|
MEMBER_OFFSET_INIT(rq_cfs, "rq", "cfs");
|
|
MEMBER_OFFSET_INIT(task_group_cfs_rq, "task_group", "cfs_rq");
|
|
MEMBER_OFFSET_INIT(task_group_rt_rq, "task_group", "rt_rq");
|
|
MEMBER_OFFSET_INIT(task_group_parent, "task_group", "parent");
|
|
|
|
/*
|
|
* In 2.4, smp_send_stop() sets smp_num_cpus back to 1
|
|
* in some, but not all, architectures. So if a count
|
|
* of 1 is found, be suspicious, and check the
|
|
* init_tasks[NR_CPUS] array (also intro'd in 2.4),
|
|
* for idle thread addresses. For 2.2, prepare for the
|
|
* eventuality by verifying the cpu count with the machine
|
|
* dependent count.
|
|
*/
|
|
if ((kt->flags & SMP) && DUMPFILE() && (kt->cpus == 1)) {
|
|
if (symbol_exists("init_tasks")) {
|
|
ulong init_tasks[NR_CPUS];
|
|
int nr_cpus;
|
|
|
|
BZERO(&init_tasks[0], sizeof(ulong) * NR_CPUS);
|
|
|
|
nr_cpus = get_array_length("init_tasks", NULL, 0);
|
|
if ((nr_cpus < 1) || (nr_cpus > NR_CPUS))
|
|
nr_cpus = NR_CPUS;
|
|
|
|
get_idle_threads(&init_tasks[0], nr_cpus);
|
|
|
|
for (i = kt->cpus = 0; i < nr_cpus; i++)
|
|
if (init_tasks[i])
|
|
kt->cpus++;
|
|
} else
|
|
kt->cpus = machdep->get_smp_cpus();
|
|
}
|
|
|
|
if ((kt->flags & SMP) && ACTIVE() && (kt->cpus == 1) &&
|
|
(kt->flags & PER_CPU_OFF))
|
|
kt->cpus = machdep->get_smp_cpus();
|
|
|
|
if (kt->cpus_override && (c = atoi(kt->cpus_override))) {
|
|
error(WARNING, "forcing cpu count to: %d\n\n", c);
|
|
kt->cpus = c;
|
|
}
|
|
|
|
if (kt->cpus > NR_CPUS) {
|
|
error(WARNING,
|
|
"%s number of cpus (%d) greater than compiled-in NR_CPUS (%d)\n",
|
|
kt->cpus_override && atoi(kt->cpus_override) ?
|
|
"configured" : "calculated", kt->cpus, NR_CPUS);
|
|
error(FATAL, "recompile crash with larger NR_CPUS\n");
|
|
}
|
|
|
|
hypervisor_init();
|
|
|
|
STRUCT_SIZE_INIT(spinlock_t, "spinlock_t");
|
|
verify_spinlock();
|
|
|
|
if (STRUCT_EXISTS("atomic_t"))
|
|
if (MEMBER_EXISTS("atomic_t", "counter"))
|
|
MEMBER_OFFSET_INIT(atomic_t_counter,
|
|
"atomic_t", "counter");
|
|
|
|
STRUCT_SIZE_INIT(list_head, "list_head");
|
|
MEMBER_OFFSET_INIT(list_head_next, "list_head", "next");
|
|
MEMBER_OFFSET_INIT(list_head_prev, "list_head", "prev");
|
|
if (OFFSET(list_head_next) != 0)
|
|
error(WARNING,
|
|
"list_head.next offset: %ld: list command may fail\n",
|
|
OFFSET(list_head_next));
|
|
|
|
MEMBER_OFFSET_INIT(hlist_node_next, "hlist_node", "next");
|
|
MEMBER_OFFSET_INIT(hlist_node_pprev, "hlist_node", "pprev");
|
|
STRUCT_SIZE_INIT(hlist_head, "hlist_head");
|
|
STRUCT_SIZE_INIT(hlist_node, "hlist_node");
|
|
|
|
if (STRUCT_EXISTS("irq_desc_t"))
|
|
irq_desc_type_name = "irq_desc_t";
|
|
else
|
|
irq_desc_type_name = "irq_desc";
|
|
|
|
STRUCT_SIZE_INIT(irq_desc_t, irq_desc_type_name);
|
|
if (MEMBER_EXISTS(irq_desc_type_name, "irq_data"))
|
|
MEMBER_OFFSET_INIT(irq_desc_t_irq_data, irq_desc_type_name, "irq_data");
|
|
else
|
|
MEMBER_OFFSET_INIT(irq_desc_t_affinity, irq_desc_type_name, "affinity");
|
|
if (MEMBER_EXISTS(irq_desc_type_name, "kstat_irqs"))
|
|
MEMBER_OFFSET_INIT(irq_desc_t_kstat_irqs, irq_desc_type_name, "kstat_irqs");
|
|
MEMBER_OFFSET_INIT(irq_desc_t_name, irq_desc_type_name, "name");
|
|
MEMBER_OFFSET_INIT(irq_desc_t_status, irq_desc_type_name, "status");
|
|
if (MEMBER_EXISTS(irq_desc_type_name, "handler"))
|
|
MEMBER_OFFSET_INIT(irq_desc_t_handler, irq_desc_type_name, "handler");
|
|
else if (MEMBER_EXISTS(irq_desc_type_name, "chip"))
|
|
MEMBER_OFFSET_INIT(irq_desc_t_chip, irq_desc_type_name, "chip");
|
|
MEMBER_OFFSET_INIT(irq_desc_t_action, irq_desc_type_name, "action");
|
|
MEMBER_OFFSET_INIT(irq_desc_t_depth, irq_desc_type_name, "depth");
|
|
|
|
STRUCT_SIZE_INIT(kernel_stat, "kernel_stat");
|
|
MEMBER_OFFSET_INIT(kernel_stat_irqs, "kernel_stat", "irqs");
|
|
|
|
if (STRUCT_EXISTS("hw_interrupt_type")) {
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_typename,
|
|
"hw_interrupt_type", "typename");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_startup,
|
|
"hw_interrupt_type", "startup");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_shutdown,
|
|
"hw_interrupt_type", "shutdown");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_handle,
|
|
"hw_interrupt_type", "handle");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_enable,
|
|
"hw_interrupt_type", "enable");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_disable,
|
|
"hw_interrupt_type", "disable");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_ack,
|
|
"hw_interrupt_type", "ack");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_end,
|
|
"hw_interrupt_type", "end");
|
|
MEMBER_OFFSET_INIT(hw_interrupt_type_set_affinity,
|
|
"hw_interrupt_type", "set_affinity");
|
|
} else { /*
|
|
* On later kernels where hw_interrupt_type was replaced
|
|
* by irq_chip
|
|
*/
|
|
MEMBER_OFFSET_INIT(irq_chip_typename,
|
|
"irq_chip", "name");
|
|
MEMBER_OFFSET_INIT(irq_chip_startup,
|
|
"irq_chip", "startup");
|
|
MEMBER_OFFSET_INIT(irq_chip_shutdown,
|
|
"irq_chip", "shutdown");
|
|
MEMBER_OFFSET_INIT(irq_chip_enable,
|
|
"irq_chip", "enable");
|
|
MEMBER_OFFSET_INIT(irq_chip_disable,
|
|
"irq_chip", "disable");
|
|
MEMBER_OFFSET_INIT(irq_chip_ack,
|
|
"irq_chip", "ack");
|
|
MEMBER_OFFSET_INIT(irq_chip_mask,
|
|
"irq_chip", "mask");
|
|
MEMBER_OFFSET_INIT(irq_chip_mask_ack,
|
|
"irq_chip", "mask_ack");
|
|
MEMBER_OFFSET_INIT(irq_chip_unmask,
|
|
"irq_chip", "unmask");
|
|
MEMBER_OFFSET_INIT(irq_chip_eoi,
|
|
"irq_chip", "eoi");
|
|
MEMBER_OFFSET_INIT(irq_chip_end,
|
|
"irq_chip", "end");
|
|
MEMBER_OFFSET_INIT(irq_chip_set_affinity,
|
|
"irq_chip", "set_affinity");
|
|
MEMBER_OFFSET_INIT(irq_chip_retrigger,
|
|
"irq_chip", "retrigger");
|
|
MEMBER_OFFSET_INIT(irq_chip_set_type,
|
|
"irq_chip", "set_type");
|
|
MEMBER_OFFSET_INIT(irq_chip_set_wake,
|
|
"irq_chip", "set_wake");
|
|
}
|
|
MEMBER_OFFSET_INIT(irqaction_handler, "irqaction", "handler");
|
|
MEMBER_OFFSET_INIT(irqaction_flags, "irqaction", "flags");
|
|
MEMBER_OFFSET_INIT(irqaction_mask, "irqaction", "mask");
|
|
MEMBER_OFFSET_INIT(irqaction_name, "irqaction", "name");
|
|
MEMBER_OFFSET_INIT(irqaction_dev_id, "irqaction", "dev_id");
|
|
MEMBER_OFFSET_INIT(irqaction_next, "irqaction", "next");
|
|
|
|
if (kernel_symbol_exists("irq_desc_tree"))
|
|
kt->flags |= IRQ_DESC_TREE;
|
|
STRUCT_SIZE_INIT(irq_data, "irq_data");
|
|
if (VALID_STRUCT(irq_data)) {
|
|
MEMBER_OFFSET_INIT(irq_data_chip, "irq_data", "chip");
|
|
MEMBER_OFFSET_INIT(irq_data_affinity, "irq_data", "affinity");
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(irq_cpustat_t, "irq_cpustat_t");
|
|
MEMBER_OFFSET_INIT(irq_cpustat_t___softirq_active,
|
|
"irq_cpustat_t", "__softirq_active");
|
|
MEMBER_OFFSET_INIT(irq_cpustat_t___softirq_mask,
|
|
"irq_cpustat_t", "__softirq_mask");
|
|
|
|
STRUCT_SIZE_INIT(timer_list, "timer_list");
|
|
MEMBER_OFFSET_INIT(timer_list_list, "timer_list", "list");
|
|
MEMBER_OFFSET_INIT(timer_list_next, "timer_list", "next");
|
|
MEMBER_OFFSET_INIT(timer_list_entry, "timer_list", "entry");
|
|
MEMBER_OFFSET_INIT(timer_list_expires, "timer_list", "expires");
|
|
MEMBER_OFFSET_INIT(timer_list_function, "timer_list", "function");
|
|
STRUCT_SIZE_INIT(timer_vec_root, "timer_vec_root");
|
|
if (VALID_STRUCT(timer_vec_root))
|
|
MEMBER_OFFSET_INIT(timer_vec_root_vec,
|
|
"timer_vec_root", "vec");
|
|
STRUCT_SIZE_INIT(timer_vec, "timer_vec");
|
|
if (VALID_STRUCT(timer_vec))
|
|
MEMBER_OFFSET_INIT(timer_vec_vec, "timer_vec", "vec");
|
|
|
|
STRUCT_SIZE_INIT(tvec_root_s, "tvec_root_s");
|
|
if (VALID_STRUCT(tvec_root_s)) {
|
|
STRUCT_SIZE_INIT(tvec_t_base_s, "tvec_t_base_s");
|
|
MEMBER_OFFSET_INIT(tvec_t_base_s_tv1,
|
|
"tvec_t_base_s", "tv1");
|
|
MEMBER_OFFSET_INIT(tvec_root_s_vec,
|
|
"tvec_root_s", "vec");
|
|
STRUCT_SIZE_INIT(tvec_s, "tvec_s");
|
|
MEMBER_OFFSET_INIT(tvec_s_vec, "tvec_s", "vec");
|
|
} else {
|
|
STRUCT_SIZE_INIT(tvec_root_s, "tvec_root");
|
|
if (VALID_STRUCT(tvec_root_s)) {
|
|
STRUCT_SIZE_INIT(tvec_t_base_s, "tvec_base");
|
|
MEMBER_OFFSET_INIT(tvec_t_base_s_tv1,
|
|
"tvec_base", "tv1");
|
|
MEMBER_OFFSET_INIT(tvec_root_s_vec,
|
|
"tvec_root", "vec");
|
|
STRUCT_SIZE_INIT(tvec_s, "tvec");
|
|
MEMBER_OFFSET_INIT(tvec_s_vec, "tvec", "vec");
|
|
}
|
|
}
|
|
STRUCT_SIZE_INIT(__wait_queue, "__wait_queue");
|
|
if (VALID_STRUCT(__wait_queue)) {
|
|
if (MEMBER_EXISTS("__wait_queue", "task"))
|
|
MEMBER_OFFSET_INIT(__wait_queue_task,
|
|
"__wait_queue", "task");
|
|
else
|
|
MEMBER_OFFSET_INIT(__wait_queue_task,
|
|
"__wait_queue", "private");
|
|
MEMBER_OFFSET_INIT(__wait_queue_head_task_list,
|
|
"__wait_queue_head", "task_list");
|
|
MEMBER_OFFSET_INIT(__wait_queue_task_list,
|
|
"__wait_queue", "task_list");
|
|
} else {
|
|
STRUCT_SIZE_INIT(wait_queue, "wait_queue");
|
|
if (VALID_STRUCT(wait_queue)) {
|
|
MEMBER_OFFSET_INIT(wait_queue_task,
|
|
"wait_queue", "task");
|
|
MEMBER_OFFSET_INIT(wait_queue_next,
|
|
"wait_queue", "next");
|
|
}
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(pt_regs, "pt_regs");
|
|
STRUCT_SIZE_INIT(softirq_state, "softirq_state");
|
|
STRUCT_SIZE_INIT(softirq_action, "softirq_action");
|
|
STRUCT_SIZE_INIT(desc_struct, "desc_struct");
|
|
|
|
STRUCT_SIZE_INIT(char_device_struct, "char_device_struct");
|
|
if (VALID_STRUCT(char_device_struct)) {
|
|
MEMBER_OFFSET_INIT(char_device_struct_next,
|
|
"char_device_struct", "next");
|
|
MEMBER_OFFSET_INIT(char_device_struct_name,
|
|
"char_device_struct", "name");
|
|
MEMBER_OFFSET_INIT(char_device_struct_fops,
|
|
"char_device_struct", "fops");
|
|
MEMBER_OFFSET_INIT(char_device_struct_major,
|
|
"char_device_struct", "major");
|
|
MEMBER_OFFSET_INIT(char_device_struct_baseminor,
|
|
"char_device_struct", "baseminor");
|
|
MEMBER_OFFSET_INIT(char_device_struct_cdev,
|
|
"char_device_struct", "cdev");
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(cdev, "cdev");
|
|
if (VALID_STRUCT(cdev))
|
|
MEMBER_OFFSET_INIT(cdev_ops, "cdev", "ops");
|
|
|
|
STRUCT_SIZE_INIT(probe, "probe");
|
|
if (VALID_STRUCT(probe)) {
|
|
MEMBER_OFFSET_INIT(probe_next, "probe", "next");
|
|
MEMBER_OFFSET_INIT(probe_dev, "probe", "dev");
|
|
MEMBER_OFFSET_INIT(probe_data, "probe", "data");
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(kobj_map, "kobj_map");
|
|
if (VALID_STRUCT(kobj_map))
|
|
MEMBER_OFFSET_INIT(kobj_map_probes, "kobj_map", "probes");
|
|
|
|
MEMBER_OFFSET_INIT(module_kallsyms_start, "module",
|
|
"kallsyms_start");
|
|
|
|
STRUCT_SIZE_INIT(kallsyms_header, "kallsyms_header");
|
|
|
|
if (VALID_MEMBER(module_kallsyms_start) &&
|
|
VALID_SIZE(kallsyms_header)) {
|
|
MEMBER_OFFSET_INIT(kallsyms_header_sections,
|
|
"kallsyms_header", "sections");
|
|
MEMBER_OFFSET_INIT(kallsyms_header_section_off,
|
|
"kallsyms_header", "section_off");
|
|
MEMBER_OFFSET_INIT(kallsyms_header_symbols,
|
|
"kallsyms_header", "symbols");
|
|
MEMBER_OFFSET_INIT(kallsyms_header_symbol_off,
|
|
"kallsyms_header", "symbol_off");
|
|
MEMBER_OFFSET_INIT(kallsyms_header_string_off,
|
|
"kallsyms_header", "string_off");
|
|
MEMBER_OFFSET_INIT(kallsyms_symbol_section_off,
|
|
"kallsyms_symbol", "section_off");
|
|
MEMBER_OFFSET_INIT(kallsyms_symbol_symbol_addr,
|
|
"kallsyms_symbol", "symbol_addr");
|
|
MEMBER_OFFSET_INIT(kallsyms_symbol_name_off,
|
|
"kallsyms_symbol", "name_off");
|
|
MEMBER_OFFSET_INIT(kallsyms_section_start,
|
|
"kallsyms_section", "start");
|
|
MEMBER_OFFSET_INIT(kallsyms_section_size,
|
|
"kallsyms_section", "size");
|
|
MEMBER_OFFSET_INIT(kallsyms_section_name_off,
|
|
"kallsyms_section", "name_off");
|
|
STRUCT_SIZE_INIT(kallsyms_symbol, "kallsyms_symbol");
|
|
STRUCT_SIZE_INIT(kallsyms_section, "kallsyms_section");
|
|
|
|
if (!(kt->flags & NO_KALLSYMS))
|
|
kt->flags |= KALLSYMS_V1;
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(module_num_symtab, "module", "num_symtab");
|
|
|
|
if (VALID_MEMBER(module_num_symtab)) {
|
|
MEMBER_OFFSET_INIT(module_symtab, "module", "symtab");
|
|
MEMBER_OFFSET_INIT(module_strtab, "module", "strtab");
|
|
|
|
if (!(kt->flags & NO_KALLSYMS))
|
|
kt->flags |= KALLSYMS_V2;
|
|
}
|
|
|
|
if (!(kt->flags & DWARF_UNWIND))
|
|
kt->flags |= NO_DWARF_UNWIND;
|
|
|
|
/*
|
|
* OpenVZ
|
|
*/
|
|
if (kernel_symbol_exists("pcpu_info") &&
|
|
STRUCT_EXISTS("pcpu_info") && STRUCT_EXISTS("vcpu_struct")) {
|
|
MEMBER_OFFSET_INIT(pcpu_info_vcpu, "pcpu_info", "vcpu");
|
|
MEMBER_OFFSET_INIT(pcpu_info_idle, "pcpu_info", "idle");
|
|
MEMBER_OFFSET_INIT(vcpu_struct_rq, "vcpu_struct", "rq");
|
|
STRUCT_SIZE_INIT(pcpu_info, "pcpu_info");
|
|
STRUCT_SIZE_INIT(vcpu_struct, "vcpu_struct");
|
|
kt->flags |= ARCH_OPENVZ;
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(mem_section, "mem_section");
|
|
|
|
BUG_bytes_init();
|
|
|
|
/*
|
|
* for hrtimer
|
|
*/
|
|
STRUCT_SIZE_INIT(hrtimer_clock_base, "hrtimer_clock_base");
|
|
if (VALID_STRUCT(hrtimer_clock_base)) {
|
|
MEMBER_OFFSET_INIT(hrtimer_clock_base_offset,
|
|
"hrtimer_clock_base", "offset");
|
|
MEMBER_OFFSET_INIT(hrtimer_clock_base_active,
|
|
"hrtimer_clock_base", "active");
|
|
MEMBER_OFFSET_INIT(hrtimer_clock_base_first,
|
|
"hrtimer_clock_base", "first");
|
|
MEMBER_OFFSET_INIT(hrtimer_clock_base_get_time,
|
|
"hrtimer_clock_base", "get_time");
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(hrtimer_base, "hrtimer_base");
|
|
if (VALID_STRUCT(hrtimer_base)) {
|
|
MEMBER_OFFSET_INIT(hrtimer_base_first,
|
|
"hrtimer_base", "first");
|
|
MEMBER_OFFSET_INIT(hrtimer_base_pending,
|
|
"hrtimer_base", "pending");
|
|
MEMBER_OFFSET_INIT(hrtimer_base_get_time,
|
|
"hrtimer_base", "get_time");
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(hrtimer_cpu_base_clock_base, "hrtimer_cpu_base",
|
|
"clock_base");
|
|
|
|
MEMBER_OFFSET_INIT(hrtimer_node, "hrtimer", "node");
|
|
MEMBER_OFFSET_INIT(hrtimer_list, "hrtimer", "list");
|
|
MEMBER_OFFSET_INIT(hrtimer_expires, "hrtimer", "expires");
|
|
if (INVALID_MEMBER(hrtimer_expires))
|
|
MEMBER_OFFSET_INIT(hrtimer_expires, "hrtimer", "_expires");
|
|
if (INVALID_MEMBER(hrtimer_expires)) {
|
|
MEMBER_OFFSET_INIT(timerqueue_head_next,
|
|
"timerqueue_head", "next");
|
|
MEMBER_OFFSET_INIT(timerqueue_node_expires,
|
|
"timerqueue_node", "expires");
|
|
MEMBER_OFFSET_INIT(timerqueue_node_node,
|
|
"timerqueue_node_node", "node");
|
|
}
|
|
MEMBER_OFFSET_INIT(hrtimer_softexpires, "hrtimer", "_softexpires");
|
|
MEMBER_OFFSET_INIT(hrtimer_function, "hrtimer", "function");
|
|
|
|
MEMBER_OFFSET_INIT(ktime_t_tv64, "ktime", "tv64");
|
|
if (INVALID_MEMBER(ktime_t_tv64))
|
|
MEMBER_OFFSET_INIT(ktime_t_tv64, "ktime_t", "tv64");
|
|
MEMBER_OFFSET_INIT(ktime_t_sec, "ktime", "sec");
|
|
if (INVALID_MEMBER(ktime_t_sec))
|
|
MEMBER_OFFSET_INIT(ktime_t_sec, "ktime_t", "sec");
|
|
MEMBER_OFFSET_INIT(ktime_t_nsec, "ktime", "nsec");
|
|
if (INVALID_MEMBER(ktime_t_nsec))
|
|
MEMBER_OFFSET_INIT(ktime_t_nsec, "ktime_t", "nsec");
|
|
|
|
kt->flags &= ~PRE_KERNEL_INIT;
|
|
}
|
|
|
|
/*
|
|
* Get cpu map address. Types are: possible, online, present and active.
|
|
* They exist as either:
|
|
*
|
|
* (1) cpu_<type>_map symbols, or
|
|
* (2) what is pointed to by cpu_<type>_mask
|
|
*/
|
|
ulong
|
|
cpu_map_addr(const char *type)
|
|
{
|
|
char map_symbol[32];
|
|
ulong addr;
|
|
|
|
sprintf(map_symbol, "cpu_%s_map", type);
|
|
if (kernel_symbol_exists(map_symbol))
|
|
return symbol_value(map_symbol);
|
|
|
|
sprintf(map_symbol, "cpu_%s_mask", type);
|
|
if (kernel_symbol_exists(map_symbol)) {
|
|
get_symbol_data(map_symbol, sizeof(ulong), &addr);
|
|
return addr;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static char *
|
|
cpu_map_type(char *name)
|
|
{
|
|
char map_symbol[32];
|
|
|
|
sprintf(map_symbol, "cpu_%s_map", name);
|
|
if (kernel_symbol_exists(map_symbol))
|
|
return "map";
|
|
|
|
sprintf(map_symbol, "cpu_%s_mask", name);
|
|
if (kernel_symbol_exists(map_symbol))
|
|
return "mask";
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Get cpu map (possible, online, etc.) size
|
|
*/
|
|
static int
|
|
cpu_map_size(const char *type)
|
|
{
|
|
int len;
|
|
char map_symbol[32];
|
|
struct gnu_request req;
|
|
|
|
if (LKCD_KERNTYPES()) {
|
|
if ((len = STRUCT_SIZE("cpumask_t")) < 0)
|
|
error(FATAL, "cannot determine type cpumask_t\n");
|
|
return len;
|
|
}
|
|
|
|
sprintf(map_symbol, "cpu_%s_map", type);
|
|
if (kernel_symbol_exists(map_symbol)) {
|
|
len = get_symbol_type(map_symbol, NULL, &req) ==
|
|
TYPE_CODE_UNDEF ? sizeof(ulong) : req.length;
|
|
return len;
|
|
}
|
|
|
|
len = STRUCT_SIZE("cpumask_t");
|
|
if (len < 0)
|
|
return sizeof(ulong);
|
|
else
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* If the cpu_present_map, cpu_online_map and cpu_possible_maps exist,
|
|
* set up the kt->cpu_flags[NR_CPUS] with their settings.
|
|
*/
|
|
static void
|
|
cpu_maps_init(void)
|
|
{
|
|
int i, c, m, cpu, len;
|
|
char *buf;
|
|
ulong *maskptr, addr;
|
|
struct mapinfo {
|
|
ulong cpu_flag;
|
|
char *name;
|
|
} mapinfo[] = {
|
|
{ POSSIBLE_MAP, "possible" },
|
|
{ PRESENT_MAP, "present" },
|
|
{ ONLINE_MAP, "online" },
|
|
{ ACTIVE_MAP, "active" },
|
|
};
|
|
|
|
if ((len = STRUCT_SIZE("cpumask_t")) < 0)
|
|
len = sizeof(ulong);
|
|
|
|
buf = GETBUF(len);
|
|
|
|
for (m = 0; m < sizeof(mapinfo)/sizeof(struct mapinfo); m++) {
|
|
if (!(addr = cpu_map_addr(mapinfo[m].name)))
|
|
continue;
|
|
|
|
if (!readmem(addr, KVADDR, buf, len,
|
|
mapinfo[m].name, RETURN_ON_ERROR)) {
|
|
error(WARNING, "cannot read cpu_%s_map\n",
|
|
mapinfo[m].name);
|
|
continue;
|
|
}
|
|
|
|
maskptr = (ulong *)buf;
|
|
for (i = 0; i < (len/sizeof(ulong)); i++, maskptr++) {
|
|
if (*maskptr == 0)
|
|
continue;
|
|
for (c = 0; c < BITS_PER_LONG; c++)
|
|
if (*maskptr & (0x1UL << c)) {
|
|
cpu = (i * BITS_PER_LONG) + c;
|
|
if (cpu >= NR_CPUS) {
|
|
error(WARNING,
|
|
"cpu_%s_%s indicates more than"
|
|
" %d (NR_CPUS) cpus\n",
|
|
mapinfo[m].name,
|
|
cpu_map_type(mapinfo[m].name),
|
|
NR_CPUS);
|
|
break;
|
|
}
|
|
kt->cpu_flags[cpu] |= mapinfo[m].cpu_flag;
|
|
}
|
|
}
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "cpu_%s_%s: ", mapinfo[m].name,
|
|
cpu_map_type(mapinfo[m].name));
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (kt->cpu_flags[i] & mapinfo[m].cpu_flag)
|
|
fprintf(fp, "%d ", i);
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
}
|
|
|
|
/*
|
|
* Determine whether a cpu is in one of the cpu masks.
|
|
*/
|
|
int
|
|
in_cpu_map(int map, int cpu)
|
|
{
|
|
if (cpu >= (kt->kernel_NR_CPUS ? kt->kernel_NR_CPUS : NR_CPUS)) {
|
|
error(INFO, "in_cpu_map: invalid cpu: %d\n", cpu);
|
|
return FALSE;
|
|
}
|
|
|
|
switch (map)
|
|
{
|
|
case POSSIBLE_MAP:
|
|
if (!cpu_map_addr("possible")) {
|
|
error(INFO, "cpu_possible_map does not exist\n");
|
|
return FALSE;
|
|
}
|
|
return (kt->cpu_flags[cpu] & POSSIBLE_MAP);
|
|
|
|
case PRESENT_MAP:
|
|
if (!cpu_map_addr("present")) {
|
|
error(INFO, "cpu_present_map does not exist\n");
|
|
return FALSE;
|
|
}
|
|
return (kt->cpu_flags[cpu] & PRESENT_MAP);
|
|
|
|
case ONLINE_MAP:
|
|
if (!cpu_map_addr("online")) {
|
|
error(INFO, "cpu_online_map does not exist\n");
|
|
return FALSE;
|
|
}
|
|
return (kt->cpu_flags[cpu] & ONLINE_MAP);
|
|
|
|
case ACTIVE_MAP:
|
|
if (!cpu_map_addr("active")) {
|
|
error(INFO, "cpu_active_map does not exist\n");
|
|
return FALSE;
|
|
}
|
|
return (kt->cpu_flags[cpu] & ACTIVE_MAP);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* For lack of a better manner of verifying that the namelist and dumpfile
|
|
* (or live kernel) match up, verify that the Linux banner is where
|
|
* the namelist says it is. Since this is common place to bail, extra
|
|
* debug statements are available.
|
|
*/
|
|
void
|
|
verify_version(void)
|
|
{
|
|
char buf[BUFSIZE];
|
|
ulong linux_banner;
|
|
int argc, len;
|
|
char *arglist[MAXARGS];
|
|
char *p1, *p2;
|
|
struct syment *sp;
|
|
|
|
if (pc->flags & KERNEL_DEBUG_QUERY)
|
|
return;
|
|
|
|
BZERO(buf, BUFSIZE);
|
|
|
|
if (!(sp = symbol_search("linux_banner")))
|
|
error(FATAL, "linux_banner symbol does not exist?\n");
|
|
else if ((sp->type == 'R') || (sp->type == 'r') ||
|
|
(machine_type("ARM") && sp->type == 'T') ||
|
|
(machine_type("ARM64")))
|
|
linux_banner = symbol_value("linux_banner");
|
|
else
|
|
get_symbol_data("linux_banner", sizeof(ulong), &linux_banner);
|
|
|
|
if (!IS_KVADDR(linux_banner))
|
|
error(WARNING, "invalid linux_banner pointer: %lx\n",
|
|
linux_banner);
|
|
|
|
if (!accessible(linux_banner))
|
|
goto bad_match;
|
|
|
|
if (!read_string(linux_banner, buf, BUFSIZE-1))
|
|
error(WARNING, "cannot read linux_banner string\n");
|
|
|
|
if (ACTIVE()) {
|
|
len = strlen(kt->proc_version);
|
|
if ((len > 0) && (strncmp(buf, kt->proc_version, len) != 0)) {
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "/proc/version:\n%s\n",
|
|
kt->proc_version);
|
|
fprintf(fp, "linux_banner:\n%s\n", buf);
|
|
}
|
|
goto bad_match;
|
|
} else if (CRASHDEBUG(1))
|
|
fprintf(fp, "linux_banner:\n%s\n", buf);
|
|
}
|
|
|
|
if (DUMPFILE()) {
|
|
if (!STRNEQ(buf, "Linux version")) {
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "linux_banner:\n%s\n", buf);
|
|
goto bad_match;
|
|
}
|
|
strcpy(kt->proc_version, strip_linefeeds(buf));
|
|
}
|
|
|
|
verify_namelist();
|
|
|
|
if (strstr(kt->proc_version, "gcc version 3.3.3"))
|
|
kt->flags |= GCC_3_3_3;
|
|
if (strstr(kt->proc_version, "gcc version 3.3.2"))
|
|
kt->flags |= GCC_3_3_2;
|
|
else if (strstr(kt->proc_version, "gcc version 3.2.3"))
|
|
kt->flags |= GCC_3_2_3;
|
|
else if (strstr(kt->proc_version, "gcc version 3.2"))
|
|
kt->flags |= GCC_3_2;
|
|
else if (strstr(kt->proc_version, "gcc version 2.96"))
|
|
kt->flags |= GCC_2_96;
|
|
|
|
/*
|
|
* Keeping the gcc version with #define's is getting out of hand.
|
|
*/
|
|
if ((p1 = strstr(kt->proc_version, "gcc version "))) {
|
|
BZERO(buf, BUFSIZE);
|
|
p1 += strlen("gcc version ");
|
|
p2 = buf;
|
|
while (((*p1 >= '0') && (*p1 <= '9')) || (*p1 == '.')) {
|
|
if (*p1 == '.')
|
|
*p2++ = ' ';
|
|
else
|
|
*p2++ = *p1;
|
|
p1++;
|
|
}
|
|
argc = parse_line(buf, arglist);
|
|
|
|
switch (argc)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
break;
|
|
case 2:
|
|
kt->gcc_version[0] = atoi(arglist[0]);
|
|
kt->gcc_version[1] = atoi(arglist[1]);
|
|
break;
|
|
default:
|
|
kt->gcc_version[0] = atoi(arglist[0]);
|
|
kt->gcc_version[1] = atoi(arglist[1]);
|
|
kt->gcc_version[2] = atoi(arglist[2]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (CRASHDEBUG(1))
|
|
gdb_readnow_warning();
|
|
|
|
return;
|
|
|
|
bad_match:
|
|
if (REMOTE())
|
|
sprintf(buf, "%s:%s", pc->server, pc->server_memsrc);
|
|
else
|
|
sprintf(buf, "%s", ACTIVE() ? pc->live_memsrc : pc->dumpfile);
|
|
|
|
error(INFO, "%s and %s do not match!\n",
|
|
pc->system_map ? pc->system_map :
|
|
pc->namelist_debug ? pc->namelist_debug : pc->namelist, buf);
|
|
|
|
program_usage(SHORT_FORM);
|
|
}
|
|
|
|
/*
|
|
* Quick test to verify that we're not using a UP debug kernel on
|
|
* an SMP system.
|
|
*/
|
|
void
|
|
verify_spinlock(void)
|
|
{
|
|
char buf[BUFSIZE];
|
|
|
|
if ((kt->flags & SMP) && (SIZE(spinlock_t) == 0)) {
|
|
error(INFO,
|
|
"debug data shows spinlock_t as an incomplete type (undefined),\n");
|
|
fprintf(fp, "%sbut \"%s\" is an SMP kernel.\n",
|
|
space(strlen(pc->program_name)+2),
|
|
pc->namelist);
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "\ngdb> ptype spinlock_t\n");
|
|
sprintf(buf, "ptype spinlock_t");
|
|
gdb_pass_through(buf, NULL, GNU_RETURN_ON_ERROR);
|
|
}
|
|
non_matching_kernel();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Something doesn't jive.
|
|
*/
|
|
void
|
|
non_matching_kernel(void)
|
|
{
|
|
int kernels = 0;
|
|
|
|
if (pc->namelist)
|
|
kernels++;
|
|
if (pc->namelist_debug)
|
|
kernels++;
|
|
if (pc->debuginfo_file)
|
|
kernels++;
|
|
|
|
fprintf(fp,
|
|
"\nErrors like the one above typically occur when the kernel%s and memory source\ndo not match. These are the files being used:\n\n", kernels > 1 ? "s" : "");
|
|
|
|
if (REMOTE()) {
|
|
switch (pc->flags &
|
|
(NAMELIST_LOCAL|NAMELIST_UNLINKED|NAMELIST_SAVED))
|
|
{
|
|
case NAMELIST_UNLINKED:
|
|
fprintf(fp, " KERNEL: %s (temporary)\n",
|
|
pc->namelist);
|
|
break;
|
|
|
|
case (NAMELIST_UNLINKED|NAMELIST_SAVED):
|
|
fprintf(fp, " KERNEL: %s\n", pc->namelist);
|
|
break;
|
|
|
|
case NAMELIST_LOCAL:
|
|
fprintf(fp, " KERNEL: %s\n", pc->namelist);
|
|
break;
|
|
}
|
|
} else {
|
|
if (pc->system_map) {
|
|
fprintf(fp, " SYSTEM MAP: %s\n", pc->system_map);
|
|
fprintf(fp, "DEBUG KERNEL: %s %s\n", pc->namelist,
|
|
debug_kernel_version(pc->namelist));
|
|
|
|
} else
|
|
fprintf(fp, " KERNEL: %s\n", pc->namelist);
|
|
if (pc->namelist_orig)
|
|
fprintf(fp, " (uncompressed from %s)\n",
|
|
pc->namelist_orig);
|
|
}
|
|
|
|
if (pc->debuginfo_file) {
|
|
fprintf(fp, " DEBUGINFO: %s\n", pc->debuginfo_file);
|
|
if (STREQ(pc->debuginfo_file, pc->namelist_debug) &&
|
|
pc->namelist_debug_orig)
|
|
fprintf(fp, " (uncompressed from %s)\n",
|
|
pc->namelist_debug_orig);
|
|
} else if (pc->namelist_debug) {
|
|
fprintf(fp, "DEBUG KERNEL: %s %s\n", pc->namelist_debug,
|
|
debug_kernel_version(pc->namelist_debug));
|
|
if (pc->namelist_debug_orig)
|
|
fprintf(fp, " (uncompressed from %s)\n",
|
|
pc->namelist_debug_orig);
|
|
}
|
|
|
|
if (dumpfile_is_split() || sadump_is_diskset() || is_ramdump_image())
|
|
fprintf(fp, " DUMPFILES: ");
|
|
else
|
|
fprintf(fp, " DUMPFILE: ");
|
|
if (ACTIVE()) {
|
|
if (REMOTE_ACTIVE())
|
|
fprintf(fp, "%s@%s (remote live system)\n",
|
|
pc->server_memsrc, pc->server);
|
|
else
|
|
fprintf(fp, "%s\n", pc->live_memsrc);
|
|
} else {
|
|
if (REMOTE_DUMPFILE())
|
|
fprintf(fp, "%s@%s (remote dumpfile)\n",
|
|
pc->server_memsrc, pc->server);
|
|
else if (REMOTE_PAUSED())
|
|
fprintf(fp, "%s %s (remote paused system)\n",
|
|
pc->server_memsrc, pc->server);
|
|
else {
|
|
if (dumpfile_is_split())
|
|
show_split_dumpfiles();
|
|
else if (sadump_is_diskset())
|
|
sadump_show_diskset();
|
|
else if (is_ramdump_image())
|
|
show_ramdump_files();
|
|
else
|
|
fprintf(fp, "%s", pc->dumpfile);
|
|
}
|
|
if (LIVE())
|
|
fprintf(fp, " [LIVE DUMP]");
|
|
}
|
|
|
|
fprintf(fp, "\n\n");
|
|
|
|
if ((pc->flags & FINDKERNEL) && !(pc->system_map)) {
|
|
fprintf(fp,
|
|
"The kernel \"%s\" is most likely incorrect.\n",
|
|
pc->namelist);
|
|
fprintf(fp,
|
|
"Try a different kernel name, or use a System.map file argument.\n\n");
|
|
}
|
|
|
|
clean_exit(1);
|
|
}
|
|
|
|
/*
|
|
* Only two checks are made here:
|
|
*
|
|
* 1. if the namelist is SMP and the memory source isn't, bail out.
|
|
* 2. if the basic gcc versions differ, issue a warning only.
|
|
*/
|
|
static void
|
|
verify_namelist()
|
|
{
|
|
int i;
|
|
char command[BUFSIZE];
|
|
char buffer[BUFSIZE];
|
|
char buffer2[BUFSIZE];
|
|
char buffer3[BUFSIZE];
|
|
char buffer4[BUFSIZE];
|
|
char buffer5[BUFSIZE];
|
|
char *p1;
|
|
FILE *pipe;
|
|
int found;
|
|
char *namelist;
|
|
int namelist_smp;
|
|
int target_smp;
|
|
|
|
if (pc->flags & KERNEL_DEBUG_QUERY)
|
|
return;
|
|
|
|
/* the kerntypes may not match in terms of gcc version or SMP */
|
|
if (LKCD_KERNTYPES())
|
|
return;
|
|
|
|
if (!strlen(kt->utsname.version))
|
|
return;
|
|
|
|
namelist = pc->namelist ? pc->namelist : pc->namelist_debug;
|
|
target_smp = strstr(kt->utsname.version, " SMP ") ? TRUE : FALSE;
|
|
namelist_smp = FALSE;
|
|
|
|
sprintf(command, "/usr/bin/strings %s", namelist);
|
|
if ((pipe = popen(command, "r")) == NULL) {
|
|
error(INFO, "%s: %s\n", namelist, strerror(errno));
|
|
return;
|
|
}
|
|
|
|
found = FALSE;
|
|
sprintf(buffer3, "(unknown)");
|
|
while (fgets(buffer, BUFSIZE-1, pipe)) {
|
|
if (!strstr(buffer, "Linux version 2.") &&
|
|
!strstr(buffer, "Linux version 3.") &&
|
|
!strstr(buffer, "Linux version 4.") &&
|
|
!strstr(buffer, "Linux version 5."))
|
|
continue;
|
|
|
|
if (strstr(buffer, kt->proc_version)) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (strstr(buffer, " SMP ")) {
|
|
namelist_smp = TRUE;
|
|
strcpy(buffer2, buffer);
|
|
}
|
|
|
|
if ((p1 = strstr(buffer, "(gcc version "))) {
|
|
p1 += strlen("(gcc version ");
|
|
i = 0;
|
|
while (*p1 != ' ')
|
|
buffer3[i++] = *p1++;
|
|
buffer3[i] = NULLCHAR;
|
|
}
|
|
|
|
break;
|
|
}
|
|
pclose(pipe);
|
|
|
|
if (!found && (p1 = strstr(kt->proc_version, "(gcc version "))) {
|
|
p1 += strlen("(gcc version ");
|
|
i = 0;
|
|
while (*p1 != ' ')
|
|
buffer4[i++] = *p1++;
|
|
buffer4[i] = NULLCHAR;
|
|
if (!STREQ(buffer3, buffer4)) {
|
|
if (REMOTE())
|
|
sprintf(buffer, "%s:%s kernel",
|
|
pc->server, pc->server_memsrc);
|
|
else
|
|
sprintf(buffer, "%s kernel", ACTIVE() ?
|
|
"live system" : pc->dumpfile);
|
|
sprintf(buffer5, " %s: %s\n %s: %s\n\n",
|
|
namelist, buffer3,
|
|
buffer, buffer4);
|
|
error(WARNING,
|
|
"kernels compiled by different gcc versions:\n%s",
|
|
buffer5);
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "verify_namelist:\n");
|
|
fprintf(fp, "%s /proc/version:\n%s\n",
|
|
ACTIVE() ? "live memory" : "dumpfile",
|
|
kt->proc_version);
|
|
fprintf(fp, "%s:\n%s\n", namelist, buffer);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!(pc->flags & SYSMAP_ARG))
|
|
error(WARNING,
|
|
"kernel version inconsistency between vmlinux and %s\n\n",
|
|
ACTIVE() ? "live memory" : "dumpfile");
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
error(WARNING,
|
|
"\ncannot find matching kernel version in %s file:\n\n",
|
|
namelist);
|
|
|
|
fprintf(fp, "verify_namelist:\n");
|
|
fprintf(fp, "%s /proc/version:\n%s\n",
|
|
ACTIVE() ? "live memory" : "dumpfile",
|
|
kt->proc_version);
|
|
fprintf(fp, "%s:\n%s\n", namelist, buffer2);
|
|
}
|
|
|
|
if (target_smp == namelist_smp)
|
|
return;
|
|
|
|
if (REMOTE())
|
|
sprintf(buffer, "%s:%s", pc->server, pc->server_memsrc);
|
|
else
|
|
sprintf(buffer, "%s", ACTIVE() ? "live system" : pc->dumpfile);
|
|
|
|
sprintf(buffer2, " %s is %s -- %s is %s\n",
|
|
namelist, namelist_smp ? "SMP" : "not SMP",
|
|
buffer, target_smp ? "SMP" : "not SMP");
|
|
|
|
error(INFO, "incompatible arguments: %s%s",
|
|
strlen(buffer2) > 48 ? "\n " : "", buffer2);
|
|
|
|
program_usage(SHORT_FORM);
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
* This routine disassembles text in one of four manners. A starting
|
|
* address, an expression, or symbol must be entered. Then:
|
|
*
|
|
* 1. if a count is appended, disassemble that many instructions starting
|
|
* at the target address.
|
|
* 2. if a count is NOT entered, and the target address is the starting
|
|
* address of a function, disassemble the whole function.
|
|
* 3. if the target address is other than the starting address of a
|
|
* function, and no count argument is appended, then disassemble one
|
|
* instruction.
|
|
* 4. If the -r option is used, disassemble all instructions in a routine
|
|
* up to and including the target address.
|
|
* 5. If -u option, just pass the user address and count, ignoring any of
|
|
* the above.
|
|
*/
|
|
|
|
static char *dis_err = "gdb unable to disassemble kernel virtual address %lx\n";
|
|
|
|
void
|
|
cmd_dis(void)
|
|
{
|
|
int c;
|
|
int do_load_module_filter, do_machdep_filter, reverse;
|
|
int unfiltered, user_mode, count_entered, bug_bytes_entered;
|
|
unsigned int radix;
|
|
ulong curaddr;
|
|
ulong revtarget;
|
|
ulong count;
|
|
ulong offset;
|
|
struct syment *sp;
|
|
struct gnu_request *req;
|
|
char *savename;
|
|
char *ret ATTRIBUTE_UNUSED;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
char buf4[BUFSIZE];
|
|
char buf5[BUFSIZE];
|
|
|
|
if ((argcnt == 2) && STREQ(args[1], "-b")) {
|
|
fprintf(fp, "encoded bytes being skipped after ud2a: ");
|
|
if (kt->BUG_bytes < 0)
|
|
fprintf(fp, "undetermined\n");
|
|
else
|
|
fprintf(fp, "%d\n", kt->BUG_bytes);
|
|
return;
|
|
}
|
|
|
|
reverse = count_entered = bug_bytes_entered = FALSE;
|
|
sp = NULL;
|
|
unfiltered = user_mode = do_machdep_filter = do_load_module_filter = 0;
|
|
radix = 0;
|
|
|
|
req = (struct gnu_request *)getbuf(sizeof(struct gnu_request));
|
|
req->buf = GETBUF(BUFSIZE);
|
|
req->flags |= GNU_FROM_TTY_OFF|GNU_RETURN_ON_ERROR;
|
|
req->count = 1;
|
|
|
|
while ((c = getopt(argcnt, args, "dxhulrUb:B:")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'd':
|
|
if (radix == 16)
|
|
error(FATAL,
|
|
"-d and -x are mutually exclusive\n");
|
|
radix = 10;
|
|
break;
|
|
|
|
case 'x':
|
|
case 'h':
|
|
if (radix == 10)
|
|
error(FATAL,
|
|
"-d and -x are mutually exclusive\n");
|
|
radix = 16;
|
|
break;
|
|
|
|
case 'U':
|
|
unfiltered = TRUE;
|
|
break;
|
|
|
|
case 'u':
|
|
user_mode = TRUE;
|
|
break;
|
|
|
|
case 'r':
|
|
reverse = TRUE;
|
|
break;
|
|
|
|
case 'l':
|
|
if (NO_LINE_NUMBERS())
|
|
error(INFO, "line numbers are not available\n");
|
|
else
|
|
req->flags |= GNU_PRINT_LINE_NUMBERS;
|
|
BZERO(buf4, BUFSIZE);
|
|
break;
|
|
|
|
case 'B':
|
|
case 'b':
|
|
kt->BUG_bytes = atoi(optarg);
|
|
bug_bytes_entered = TRUE;
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (!radix)
|
|
radix = pc->output_radix;
|
|
|
|
if (args[optind]) {
|
|
if (can_eval(args[optind]))
|
|
req->addr = eval(args[optind], FAULT_ON_ERROR, NULL);
|
|
else if (hexadecimal(args[optind], 0)) {
|
|
req->addr = htol(args[optind], FAULT_ON_ERROR, NULL);
|
|
if (!user_mode &&
|
|
!(sp = value_search(req->addr, &offset))) {
|
|
error(WARNING,
|
|
"%lx: no associated kernel symbol found\n",
|
|
req->addr);
|
|
unfiltered = TRUE;
|
|
}
|
|
} else if ((sp = symbol_search(args[optind]))) {
|
|
req->addr = sp->value;
|
|
req->flags |= GNU_FUNCTION_ONLY;
|
|
} else {
|
|
fprintf(fp, "symbol not found: %s\n", args[optind]);
|
|
fprintf(fp, "possible alternatives:\n");
|
|
if (!symbol_query(args[optind], " ", NULL))
|
|
fprintf(fp, " (none found)\n");
|
|
FREEBUF(req->buf);
|
|
FREEBUF(req);
|
|
return;
|
|
}
|
|
|
|
if (args[++optind]) {
|
|
if (reverse) {
|
|
error(INFO,
|
|
"count argument ignored with -r option\n");
|
|
} else {
|
|
req->count = stol(args[optind],
|
|
FAULT_ON_ERROR, NULL);
|
|
req->flags &= ~GNU_FUNCTION_ONLY;
|
|
count_entered++;
|
|
}
|
|
}
|
|
|
|
if (unfiltered) {
|
|
sprintf(buf1, "x/%ldi 0x%lx",
|
|
req->count ? req->count : 1, req->addr);
|
|
gdb_pass_through(buf1, NULL, GNU_RETURN_ON_ERROR);
|
|
return;
|
|
}
|
|
|
|
if (!user_mode && !IS_KVADDR(req->addr))
|
|
error(FATAL, "%lx is not a kernel virtual address\n",
|
|
req->addr);
|
|
|
|
if (user_mode) {
|
|
sprintf(buf1, "x/%ldi 0x%lx",
|
|
req->count ? req->count : 1, req->addr);
|
|
pc->curcmd_flags |= MEMTYPE_UVADDR;
|
|
gdb_pass_through(buf1, NULL, GNU_RETURN_ON_ERROR);
|
|
return;
|
|
}
|
|
|
|
do_load_module_filter = module_symbol(req->addr, NULL, NULL,
|
|
NULL, *gdb_output_radix);
|
|
|
|
if (!reverse) {
|
|
req->command = GNU_RESOLVE_TEXT_ADDR;
|
|
gdb_interface(req);
|
|
if ((req->flags & GNU_COMMAND_FAILED) ||
|
|
do_load_module_filter ||
|
|
(req->flags & GNU_FUNCTION_ONLY)) {
|
|
req->flags &= ~GNU_COMMAND_FAILED;
|
|
if (sp) {
|
|
savename = sp->name;
|
|
if ((sp = next_symbol(NULL, sp)))
|
|
req->addr2 = sp->value;
|
|
else
|
|
error(FATAL,
|
|
"unable to determine symbol after %s\n",
|
|
savename);
|
|
} else {
|
|
if ((sp = value_search(req->addr, NULL))
|
|
&& (sp = next_symbol(NULL, sp)))
|
|
req->addr2 = sp->value;
|
|
else
|
|
error(FATAL, dis_err, req->addr);
|
|
}
|
|
}
|
|
|
|
do_machdep_filter = machdep->dis_filter(req->addr, NULL, radix);
|
|
count = 0;
|
|
open_tmpfile();
|
|
#ifdef OLDWAY
|
|
req->command = GNU_DISASSEMBLE;
|
|
req->fp = pc->tmpfile;
|
|
gdb_interface(req);
|
|
#else
|
|
sprintf(buf1, "x/%ldi 0x%lx",
|
|
count_entered && req->count ? req->count :
|
|
req->flags & GNU_FUNCTION_ONLY ?
|
|
req->addr2 - req->addr : 1,
|
|
req->addr);
|
|
gdb_pass_through(buf1, NULL, GNU_RETURN_ON_ERROR);
|
|
#endif
|
|
if (req->flags & GNU_COMMAND_FAILED) {
|
|
close_tmpfile();
|
|
error(FATAL, dis_err, req->addr);
|
|
}
|
|
|
|
rewind(pc->tmpfile);
|
|
while (fgets(buf2, BUFSIZE, pc->tmpfile)) {
|
|
if (STRNEQ(buf2, "Dump of") ||
|
|
STRNEQ(buf2, "End of"))
|
|
continue;
|
|
|
|
strip_beginning_whitespace(buf2);
|
|
|
|
if (do_load_module_filter)
|
|
load_module_filter(buf2, LM_DIS_FILTER);
|
|
|
|
if (STRNEQ(buf2, "0x"))
|
|
extract_hex(buf2, &curaddr, ':', TRUE);
|
|
|
|
if ((req->flags & GNU_FUNCTION_ONLY) &&
|
|
(curaddr >= req->addr2))
|
|
break;
|
|
|
|
if (do_machdep_filter)
|
|
machdep->dis_filter(curaddr, buf2, radix);
|
|
|
|
if (req->flags & GNU_FUNCTION_ONLY) {
|
|
if (req->flags &
|
|
GNU_PRINT_LINE_NUMBERS) {
|
|
get_line_number(curaddr, buf3,
|
|
FALSE);
|
|
if (!STREQ(buf3, buf4)) {
|
|
print_verbatim(
|
|
pc->saved_fp, buf3);
|
|
print_verbatim(
|
|
pc->saved_fp, "\n");
|
|
strcpy(buf4, buf3);
|
|
}
|
|
}
|
|
|
|
print_verbatim(pc->saved_fp, buf2);
|
|
continue;
|
|
} else {
|
|
if (curaddr < req->addr)
|
|
continue;
|
|
|
|
if (req->flags &
|
|
GNU_PRINT_LINE_NUMBERS) {
|
|
get_line_number(curaddr, buf3,
|
|
FALSE);
|
|
if (!STREQ(buf3, buf4)) {
|
|
print_verbatim(
|
|
pc->saved_fp, buf3);
|
|
print_verbatim(
|
|
pc->saved_fp, "\n");
|
|
strcpy(buf4, buf3);
|
|
}
|
|
}
|
|
|
|
print_verbatim(pc->saved_fp, buf2);
|
|
|
|
if (LASTCHAR(clean_line(buf2))
|
|
!= ':') {
|
|
if (++count == req->count)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
close_tmpfile();
|
|
}
|
|
}
|
|
else if (bug_bytes_entered)
|
|
return;
|
|
else cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (!reverse) {
|
|
FREEBUF(req->buf);
|
|
FREEBUF(req);
|
|
return;
|
|
}
|
|
|
|
revtarget = req->addr;
|
|
if ((sp = value_search(revtarget, NULL)) == NULL)
|
|
error(FATAL, "cannot resolve address: %lx\n", revtarget);
|
|
|
|
sprintf(buf1, "0x%lx", revtarget);
|
|
|
|
open_tmpfile();
|
|
|
|
req->addr = sp->value;
|
|
req->flags |= GNU_FUNCTION_ONLY;
|
|
req->command = GNU_RESOLVE_TEXT_ADDR;
|
|
gdb_interface(req);
|
|
req->flags &= ~GNU_COMMAND_FAILED;
|
|
savename = sp->name;
|
|
if ((sp = next_symbol(NULL, sp)))
|
|
req->addr2 = sp->value;
|
|
else {
|
|
close_tmpfile();
|
|
error(FATAL, "unable to determine symbol after %s\n", savename);
|
|
}
|
|
|
|
do_machdep_filter = machdep->dis_filter(req->addr, NULL, radix);
|
|
#ifdef OLDWAY
|
|
req->command = GNU_DISASSEMBLE;
|
|
req->fp = pc->tmpfile;
|
|
gdb_interface(req);
|
|
#else
|
|
sprintf(buf5, "x/%ldi 0x%lx",
|
|
(revtarget - req->addr) ? revtarget - req->addr : 1,
|
|
req->addr);
|
|
gdb_pass_through(buf5, NULL, GNU_RETURN_ON_ERROR);
|
|
#endif
|
|
if (req->flags & GNU_COMMAND_FAILED) {
|
|
close_tmpfile();
|
|
error(FATAL, dis_err, req->addr);
|
|
}
|
|
|
|
rewind(pc->tmpfile);
|
|
while (fgets(buf2, BUFSIZE, pc->tmpfile)) {
|
|
if (STRNEQ(buf2, "Dump of") || STRNEQ(buf2, "End of"))
|
|
continue;
|
|
|
|
strip_beginning_whitespace(buf2);
|
|
|
|
if (do_load_module_filter)
|
|
load_module_filter(buf2, LM_DIS_FILTER);
|
|
|
|
if (STRNEQ(buf2, "0x"))
|
|
extract_hex(buf2, &curaddr, ':', TRUE);
|
|
|
|
if (do_machdep_filter)
|
|
machdep->dis_filter(curaddr, buf2, radix);
|
|
|
|
if (req->flags & GNU_PRINT_LINE_NUMBERS) {
|
|
get_line_number(curaddr, buf3, FALSE);
|
|
if (!STREQ(buf3, buf4)) {
|
|
print_verbatim(pc->saved_fp, buf3);
|
|
print_verbatim(pc->saved_fp, "\n");
|
|
strcpy(buf4, buf3);
|
|
}
|
|
}
|
|
|
|
print_verbatim(pc->saved_fp, buf2);
|
|
if (STRNEQ(buf2, buf1)) {
|
|
if (LASTCHAR(clean_line(buf2)) != ':')
|
|
break;
|
|
|
|
ret = fgets(buf2, BUFSIZE, pc->tmpfile);
|
|
|
|
if (do_load_module_filter)
|
|
load_module_filter(buf2, LM_DIS_FILTER);
|
|
|
|
if (do_machdep_filter)
|
|
machdep->dis_filter(curaddr, buf2, radix);
|
|
|
|
print_verbatim(pc->saved_fp, buf2);
|
|
break;
|
|
}
|
|
}
|
|
|
|
close_tmpfile();
|
|
FREEBUF(req->buf);
|
|
FREEBUF(req);
|
|
}
|
|
|
|
/*
|
|
* x86 and x86_64 kernels may have file/line-number encoding
|
|
* asm()'d in just after the "ud2a" instruction, which confuses
|
|
* the disassembler and the x86 backtracer. Determine the
|
|
* number of bytes to skip.
|
|
*/
|
|
static void
|
|
BUG_bytes_init(void)
|
|
{
|
|
if (machine_type("X86"))
|
|
kt->BUG_bytes = BUG_x86();
|
|
else if (machine_type("X86_64"))
|
|
kt->BUG_bytes = BUG_x86_64();
|
|
}
|
|
|
|
static int
|
|
BUG_x86(void)
|
|
{
|
|
struct syment *sp, *spn;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char *arglist[MAXARGS];
|
|
ulong vaddr, fileptr;
|
|
int found;
|
|
|
|
/*
|
|
* Prior to 2.4.19, a call to do_BUG() preceded
|
|
* the standalone ud2a instruction.
|
|
*/
|
|
if (THIS_KERNEL_VERSION < LINUX(2,4,19))
|
|
return 0;
|
|
|
|
/*
|
|
* 2.6.20 introduced __bug_table support for i386,
|
|
* but even if CONFIG_DEBUG_BUGVERBOSE is not configured,
|
|
* the ud2a stands alone.
|
|
*/
|
|
if (THIS_KERNEL_VERSION >= LINUX(2,6,20))
|
|
return 0;
|
|
|
|
/*
|
|
* For previous kernel versions, it may depend upon
|
|
* whether CONFIG_DEBUG_BUGVERBOSE was configured:
|
|
*
|
|
* #ifdef CONFIG_DEBUG_BUGVERBOSE
|
|
* #define BUG() \
|
|
* __asm__ __volatile__( "ud2\n" \
|
|
* "\t.word %c0\n" \
|
|
* "\t.long %c1\n" \
|
|
* : : "i" (__LINE__), "i" (__FILE__))
|
|
* #else
|
|
* #define BUG() __asm__ __volatile__("ud2\n")
|
|
* #endif
|
|
*
|
|
* But that's not necessarily true, since there are
|
|
* pre-2.6.11 versions that force it like so:
|
|
*
|
|
* #if 1 /- Set to zero for a slightly smaller kernel -/
|
|
* #define BUG() \
|
|
* __asm__ __volatile__( "ud2\n" \
|
|
* "\t.word %c0\n" \
|
|
* "\t.long %c1\n" \
|
|
* : : "i" (__LINE__), "i" (__FILE__))
|
|
* #else
|
|
* #define BUG() __asm__ __volatile__("ud2\n")
|
|
* #endif
|
|
*/
|
|
|
|
/*
|
|
* This works if in-kernel config data is available.
|
|
*/
|
|
if ((THIS_KERNEL_VERSION >= LINUX(2,6,11)) &&
|
|
(kt->flags & BUGVERBOSE_OFF))
|
|
return 0;
|
|
|
|
/*
|
|
* At this point, it's a pretty safe bet that it's configured,
|
|
* but to be sure, disassemble a known BUG() caller and
|
|
* verify that the encoding is there.
|
|
*/
|
|
|
|
#define X86_BUG_BYTES (6) /* sizeof(short) + sizeof(pointer) */
|
|
|
|
if (!(sp = symbol_search("do_exit")) ||
|
|
!(spn = next_symbol(NULL, sp)))
|
|
return X86_BUG_BYTES;
|
|
|
|
sprintf(buf1, "x/%ldi 0x%lx", spn->value - sp->value, sp->value);
|
|
|
|
found = FALSE;
|
|
vaddr = 0;
|
|
open_tmpfile();
|
|
gdb_pass_through(buf1, pc->tmpfile, GNU_RETURN_ON_ERROR);
|
|
rewind(pc->tmpfile);
|
|
while (fgets(buf2, BUFSIZE, pc->tmpfile)) {
|
|
if (parse_line(buf2, arglist) < 3)
|
|
continue;
|
|
|
|
if ((vaddr = htol(strip_ending_char(arglist[0], ':'),
|
|
RETURN_ON_ERROR|QUIET, NULL)) >= spn->value)
|
|
continue;
|
|
|
|
if (STREQ(arglist[2], "ud2a")) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
close_tmpfile();
|
|
|
|
if (!found || !readmem(vaddr+4, KVADDR, &fileptr, sizeof(ulong),
|
|
"BUG filename pointer", RETURN_ON_ERROR|QUIET))
|
|
return X86_BUG_BYTES;
|
|
|
|
if (!IS_KVADDR(fileptr)) {
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp,
|
|
"no filename pointer: kt->BUG_bytes: 0\n");
|
|
return 0;
|
|
}
|
|
|
|
if (!read_string(fileptr, buf1, BUFSIZE-1))
|
|
error(WARNING,
|
|
"cannot read BUG (ud2a) encoded filename address: %lx\n",
|
|
fileptr);
|
|
else if (CRASHDEBUG(1))
|
|
fprintf(fp, "BUG bytes filename encoding: [%s]\n", buf1);
|
|
|
|
return X86_BUG_BYTES;
|
|
}
|
|
|
|
static int
|
|
BUG_x86_64(void)
|
|
{
|
|
/*
|
|
* 2.6.20 introduced __bug_table support for x86_64,
|
|
* but even if CONFIG_DEBUG_BUGVERBOSE is not configured,
|
|
* the ud2a stands alone.
|
|
*/
|
|
if (THIS_KERNEL_VERSION >= LINUX(2,6,20))
|
|
return 0;
|
|
|
|
/*
|
|
* The original bug_frame structure looks like this, which
|
|
* causes the disassembler to go off into the weeds:
|
|
*
|
|
* struct bug_frame {
|
|
* unsigned char ud2[2];
|
|
* char *filename;
|
|
* unsigned short line;
|
|
* }
|
|
*
|
|
* In 2.6.13, fake push and ret instructions were encoded
|
|
* into the frame so that the disassembly would at least
|
|
* "work", although the two fake instructions show nonsensical
|
|
* arguments:
|
|
*
|
|
* struct bug_frame {
|
|
* unsigned char ud2[2];
|
|
* unsigned char push;
|
|
* signed int filename;
|
|
* unsigned char ret;
|
|
* unsigned short line;
|
|
* }
|
|
*/
|
|
|
|
if (STRUCT_EXISTS("bug_frame"))
|
|
return (int)(STRUCT_SIZE("bug_frame") - 2);
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Callback from gdb disassembly code.
|
|
*/
|
|
int
|
|
kernel_BUG_encoding_bytes(void)
|
|
{
|
|
return kt->BUG_bytes;
|
|
}
|
|
|
|
#ifdef NOT_USED
|
|
/*
|
|
* To avoid premature stoppage/extension of a dis <function> that includes
|
|
* one of the following x86/gcc 3.2 constant declarations, don't allow them
|
|
* to be considered the next text symbol.
|
|
*/
|
|
static struct syment *
|
|
next_text_symbol(struct syment *sp_in)
|
|
{
|
|
return next_symbol(NULL, sp_in);
|
|
struct syment *sp;
|
|
|
|
sp = sp_in;
|
|
while ((sp = next_symbol(NULL, sp))) {
|
|
if (STREQ(sp->name, "__constant_c_and_count_memset") ||
|
|
STREQ(sp->name, "__constant_copy_from_user") ||
|
|
STREQ(sp->name, "__constant_copy_from_user_nocheck") ||
|
|
STREQ(sp->name, "__constant_copy_to_user") ||
|
|
STREQ(sp->name, "__constant_copy_to_user_nocheck") ||
|
|
STREQ(sp->name, "__constant_memcpy") ||
|
|
STREQ(sp->name, "__constant_c_and_count_memset") ||
|
|
STREQ(sp->name, "__constant_c_x_memset") ||
|
|
STREQ(sp->name, "__constant_memcpy")) {
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
return sp;
|
|
}
|
|
#endif /* NOT_USED */
|
|
|
|
/*
|
|
* Nothing to do.
|
|
*/
|
|
int
|
|
generic_dis_filter(ulong value, char *buf, unsigned int output_radix)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
#define FRAMESIZE_DEBUG_MESSAGE \
|
|
"\nx86 usage: bt -D [size|clear|dump|seek|noseek|validate|novalidate] [-I eip]\n If eip: set its associated framesize to size.\n \"validate/novalidate\" will turn on/off V bit for this eip entry.\n If !eip: \"clear\" will clear the framesize cache and RA seek/noseek flags.\n \"dump\" will dump the current framesize cache entries.\n \"seek/noseek\" turns on/off RA seeking.\n \"validate/novalidate\" turns on/off V bit for all current entries.\n\nx86_64 usage: bt -D [clear|dump|validate|framepointer|noframepointer] [-I rip]\n If rip: \"validate\" will verbosely recalculate the framesize without\n framepointers (no stack reference).\n If !rip: \"clear\" will clear the framesize cache.\n \"dump\" will dump the current framesize cache entries.\n \"framepointer/noframepointer\" toggle the FRAMEPOINTER flag and\n clear the framesize cache."
|
|
|
|
|
|
/*
|
|
* Display a kernel stack backtrace. Arguments may be any number pid or task
|
|
* values, or, if no arguments are given, the stack trace of the current
|
|
* context will be displayed. Alternatively:
|
|
*
|
|
* -a displays the stack traces of the active tasks on each CPU.
|
|
* (only applicable to crash dumps)
|
|
* -r display raw stack data, consisting of a memory dump of the two
|
|
* pages of memory containing the task_union structure.
|
|
* -s displays arguments symbolically.
|
|
*/
|
|
|
|
void
|
|
clone_bt_info(struct bt_info *orig, struct bt_info *new,
|
|
struct task_context *tc)
|
|
{
|
|
BCOPY(orig, new, sizeof(*new));
|
|
new->stackbuf = NULL;
|
|
new->tc = tc;
|
|
new->task = tc->task;
|
|
new->stackbase = GET_STACKBASE(tc->task);
|
|
new->stacktop = GET_STACKTOP(tc->task);
|
|
}
|
|
|
|
#define BT_SETUP(TC) \
|
|
clone_bt_info(&bt_setup, bt, (TC)); \
|
|
if (refptr) { \
|
|
BZERO(&reference, sizeof(struct reference)); \
|
|
bt->ref = &reference; \
|
|
bt->ref->str = refptr; \
|
|
}
|
|
|
|
#define DO_TASK_BACKTRACE() \
|
|
{ \
|
|
BT_SETUP(tc); \
|
|
if (!BT_REFERENCE_CHECK(bt)) \
|
|
print_task_header(fp, tc, subsequent++); \
|
|
back_trace(bt); \
|
|
}
|
|
|
|
#define DO_THREAD_GROUP_BACKTRACE() \
|
|
{ \
|
|
tc = pid_to_context(tgid); \
|
|
BT_SETUP(tc); \
|
|
if (!BT_REFERENCE_CHECK(bt)) \
|
|
print_task_header(fp, tc, subsequent++); \
|
|
if (setjmp(pc->foreach_loop_env)) { \
|
|
pc->flags &= ~IN_FOREACH; \
|
|
free_all_bufs(); \
|
|
} else { \
|
|
pc->flags |= IN_FOREACH; \
|
|
back_trace(bt); \
|
|
pc->flags &= ~IN_FOREACH; \
|
|
} \
|
|
tc = FIRST_CONTEXT(); \
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) { \
|
|
if (tc->pid == tgid) \
|
|
continue; \
|
|
if (task_tgid(tc->task) != tgid) \
|
|
continue; \
|
|
BT_SETUP(tc); \
|
|
if (!BT_REFERENCE_CHECK(bt)) \
|
|
print_task_header(fp, tc, subsequent++);\
|
|
if (setjmp(pc->foreach_loop_env)) { \
|
|
pc->flags &= ~IN_FOREACH; \
|
|
free_all_bufs(); \
|
|
} else { \
|
|
pc->flags |= IN_FOREACH; \
|
|
back_trace(bt); \
|
|
pc->flags &= ~IN_FOREACH; \
|
|
} \
|
|
} \
|
|
pc->flags &= ~IN_FOREACH; \
|
|
}
|
|
|
|
void
|
|
cmd_bt(void)
|
|
{
|
|
int i, c;
|
|
ulong value, *cpus;
|
|
struct task_context *tc;
|
|
int subsequent, active;
|
|
struct stack_hook hook;
|
|
struct bt_info bt_info, bt_setup, *bt;
|
|
struct reference reference;
|
|
char *refptr;
|
|
ulong tgid, task;
|
|
char arg_buf[BUFSIZE];
|
|
|
|
tc = NULL;
|
|
cpus = NULL;
|
|
subsequent = active = 0;
|
|
hook.eip = hook.esp = 0;
|
|
refptr = 0;
|
|
bt = &bt_info;
|
|
BZERO(bt, sizeof(struct bt_info));
|
|
|
|
if (kt->flags & USE_OLD_BT)
|
|
bt->flags |= BT_OLD_BACK_TRACE;
|
|
|
|
while ((c = getopt(argcnt, args, "D:fFI:S:c:aAloreEgstTdxR:O")) != EOF) {
|
|
switch (c)
|
|
{
|
|
case 'f':
|
|
bt->flags |= BT_FULL;
|
|
break;
|
|
|
|
case 'F':
|
|
if (bt->flags & BT_FULL_SYM_SLAB)
|
|
bt->flags |= BT_FULL_SYM_SLAB2;
|
|
else
|
|
bt->flags |= (BT_FULL|BT_FULL_SYM_SLAB);
|
|
break;
|
|
|
|
case 'o':
|
|
if (XEN_HYPER_MODE())
|
|
option_not_supported(c);
|
|
bt->flags |= BT_OLD_BACK_TRACE;
|
|
break;
|
|
|
|
case 'O':
|
|
if (!(machine_type("X86") || machine_type("X86_64")) ||
|
|
XEN_HYPER_MODE())
|
|
option_not_supported(c);
|
|
else if (kt->flags & USE_OLD_BT) {
|
|
/*
|
|
* Make this setting idempotent across the use of
|
|
* $HOME/.crashrc, ./.crashrc, and "-i input" files.
|
|
* If we've been here before during initialization,
|
|
* leave it alone.
|
|
*/
|
|
if (pc->flags & INIT_IFILE) {
|
|
error(INFO, "use old bt method by default (already set)\n");
|
|
return;
|
|
}
|
|
kt->flags &= ~USE_OLD_BT;
|
|
error(INFO, "use new bt method by default\n");
|
|
} else {
|
|
kt->flags |= USE_OLD_BT;
|
|
error(INFO, "use old bt method by default\n");
|
|
}
|
|
return;
|
|
|
|
case 'R':
|
|
if (refptr)
|
|
error(INFO, "only one -R option allowed\n");
|
|
else
|
|
refptr = optarg;
|
|
break;
|
|
|
|
case 'l':
|
|
if (NO_LINE_NUMBERS())
|
|
error(INFO, "line numbers are not available\n");
|
|
else
|
|
bt->flags |= BT_LINE_NUMBERS;
|
|
break;
|
|
|
|
case 'E':
|
|
if (XEN_HYPER_MODE())
|
|
option_not_supported(c);
|
|
bt->flags |= BT_EFRAME_SEARCH|BT_EFRAME_SEARCH2;
|
|
bt->hp = &hook;
|
|
break;
|
|
|
|
case 'e':
|
|
if (XEN_HYPER_MODE())
|
|
option_not_supported(c);
|
|
bt->flags |= BT_EFRAME_SEARCH;
|
|
break;
|
|
|
|
case 'g':
|
|
#ifdef GDB_5_3
|
|
bt->flags |= BT_USE_GDB;
|
|
#else
|
|
bt->flags |= BT_THREAD_GROUP;
|
|
#endif
|
|
break;
|
|
|
|
case 'x':
|
|
if (bt->radix == 10)
|
|
error(FATAL,
|
|
"-d and -x are mutually exclusive\n");
|
|
bt->radix = 16;
|
|
break;
|
|
|
|
case 'd':
|
|
if (bt->radix == 16)
|
|
error(FATAL,
|
|
"-d and -x are mutually exclusive\n");
|
|
bt->radix = 10;
|
|
break;
|
|
|
|
case 'I':
|
|
bt->hp = &hook;
|
|
hook.eip = convert(optarg, FAULT_ON_ERROR,
|
|
NULL, NUM_HEX|NUM_EXPR);
|
|
break;
|
|
|
|
case 'D':
|
|
if (STREQ(optarg, "seek")) {
|
|
kt->flags |= RA_SEEK;
|
|
kt->flags &= ~NO_RA_SEEK;
|
|
return;
|
|
} else if (STREQ(optarg, "noseek")) {
|
|
kt->flags |= NO_RA_SEEK;
|
|
kt->flags &= ~RA_SEEK;
|
|
return;
|
|
}
|
|
bt->hp = &hook;
|
|
bt->flags |= BT_FRAMESIZE_DEBUG;
|
|
if (STREQ(optarg, "dump"))
|
|
hook.esp = 1;
|
|
else if (STRNEQ(optarg, "level-"))
|
|
bt->debug = dtol(optarg+6, FAULT_ON_ERROR, NULL);
|
|
else if (STREQ(optarg, "validate"))
|
|
hook.esp = (ulong)-1;
|
|
else if (STREQ(optarg, "novalidate"))
|
|
hook.esp = (ulong)-2;
|
|
else if (STREQ(optarg, "framepointer"))
|
|
hook.esp = (ulong)-3;
|
|
else if (STREQ(optarg, "noframepointer"))
|
|
hook.esp = (ulong)-4;
|
|
else if (STREQ(optarg, "clear")) {
|
|
kt->flags &= ~(RA_SEEK|NO_RA_SEEK);
|
|
hook.esp = 0;
|
|
} else if (*optarg == '-') {
|
|
hook.esp = dtol(optarg+1, FAULT_ON_ERROR, NULL);
|
|
hook.esp = (ulong)(0 - (long)hook.esp);
|
|
} else if (STREQ(optarg, "dwarf") || STREQ(optarg, "cfi")) {
|
|
if (!(kt->flags & DWARF_UNWIND_CAPABLE))
|
|
return;
|
|
} else
|
|
hook.esp = dtol(optarg, FAULT_ON_ERROR, NULL);
|
|
break;
|
|
|
|
case 'S':
|
|
bt->hp = &hook;
|
|
hook.esp = htol(optarg, FAULT_ON_ERROR, NULL);
|
|
if (!hook.esp)
|
|
error(FATAL,
|
|
"invalid stack address for this task: 0\n");
|
|
break;
|
|
|
|
case 'c':
|
|
if (bt->flags & BT_CPUMASK) {
|
|
error(INFO, "only one -c option allowed\n");
|
|
argerrs++;
|
|
} else {
|
|
bt->flags |= BT_CPUMASK;
|
|
BZERO(arg_buf, BUFSIZE);
|
|
strncpy(arg_buf, optarg, strlen(optarg));
|
|
cpus = get_cpumask_buf();
|
|
}
|
|
break;
|
|
|
|
case 'A':
|
|
if (!machine_type("S390X"))
|
|
option_not_supported(c);
|
|
bt->flags |= BT_SHOW_ALL_REGS; /* FALLTHROUGH */
|
|
case 'a':
|
|
active++;
|
|
break;
|
|
|
|
case 'r':
|
|
bt->flags |= BT_RAW;
|
|
break;
|
|
|
|
case 's':
|
|
bt->flags |= BT_SYMBOL_OFFSET;
|
|
break;
|
|
|
|
case 'T':
|
|
bt->flags |= BT_TEXT_SYMBOLS_ALL;
|
|
case 't':
|
|
bt->flags |= BT_TEXT_SYMBOLS;
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
if (optopt == 'D') {
|
|
fprintf(fp, FRAMESIZE_DEBUG_MESSAGE);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (bt->flags & BT_FRAMESIZE_DEBUG) {
|
|
if (machdep->flags & FRAMESIZE_DEBUG) {
|
|
while (args[optind]) {
|
|
if (!hook.eip)
|
|
hook.eip = convert(args[optind],
|
|
FAULT_ON_ERROR, NULL,
|
|
NUM_HEX|NUM_EXPR);
|
|
else {
|
|
fprintf(fp, FRAMESIZE_DEBUG_MESSAGE);
|
|
return;
|
|
}
|
|
optind++;
|
|
}
|
|
machdep->back_trace(bt);
|
|
return;
|
|
}
|
|
error(FATAL, "framesize debug not available\n");
|
|
}
|
|
|
|
BCOPY(bt, &bt_setup, sizeof(struct bt_info));
|
|
|
|
if (bt->flags & BT_EFRAME_SEARCH2) {
|
|
tc = CURRENT_CONTEXT(); /* borrow stack */
|
|
BT_SETUP(tc);
|
|
if (bt->flags & BT_CPUMASK) {
|
|
make_cpumask(arg_buf, cpus, FAULT_ON_ERROR, NULL);
|
|
bt->cpumask = cpus;
|
|
}
|
|
back_trace(bt);
|
|
return;
|
|
}
|
|
|
|
if (XEN_HYPER_MODE()) {
|
|
#ifdef XEN_HYPERVISOR_ARCH
|
|
/* "task" means vcpu for xen hypervisor */
|
|
if (active) {
|
|
for (c = 0; c < XEN_HYPER_MAX_CPUS(); c++) {
|
|
if (!xen_hyper_test_pcpu_id(c))
|
|
continue;
|
|
fake_tc.task = xen_hyper_pcpu_to_active_vcpu(c);
|
|
BT_SETUP(&fake_tc);
|
|
if (!BT_REFERENCE_CHECK(bt))
|
|
xen_hyper_print_bt_header(fp, fake_tc.task,
|
|
subsequent++);
|
|
back_trace(bt);
|
|
}
|
|
} else {
|
|
if (args[optind]) {
|
|
fake_tc.task = xen_hyper_pcpu_to_active_vcpu(
|
|
convert(args[optind], 0, NULL, NUM_DEC | NUM_HEX));
|
|
} else {
|
|
fake_tc.task = XEN_HYPER_VCPU_LAST_CONTEXT()->vcpu;
|
|
}
|
|
BT_SETUP(&fake_tc);
|
|
if (!BT_REFERENCE_CHECK(bt))
|
|
xen_hyper_print_bt_header(fp, fake_tc.task, 0);
|
|
back_trace(bt);
|
|
}
|
|
return;
|
|
#else
|
|
error(FATAL, XEN_HYPERVISOR_NOT_SUPPORTED);
|
|
#endif
|
|
}
|
|
|
|
if (bt->flags & BT_CPUMASK) {
|
|
if (LIVE())
|
|
error(FATAL,
|
|
"-c option not supported on a live system or live dump\n");
|
|
|
|
if (bt->flags & BT_THREAD_GROUP)
|
|
error(FATAL,
|
|
"-c option cannot be used with the -g option\n");
|
|
|
|
make_cpumask(arg_buf, cpus, FAULT_ON_ERROR, NULL);
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (NUM_IN_BITMAP(cpus, i)) {
|
|
if (hide_offline_cpu(i)) {
|
|
error(INFO, "%sCPU %d is OFFLINE.\n",
|
|
subsequent++ ? "\n" : "", i);
|
|
continue;
|
|
}
|
|
|
|
if ((task = get_active_task(i)))
|
|
tc = task_to_context(task);
|
|
else
|
|
error(FATAL, "cannot determine active task on cpu %ld\n", i);
|
|
DO_TASK_BACKTRACE();
|
|
}
|
|
}
|
|
FREEBUF(cpus);
|
|
return;
|
|
}
|
|
|
|
if (active) {
|
|
if (LIVE())
|
|
error(FATAL,
|
|
"-%c option not supported on a live system or live dump\n",
|
|
bt->flags & BT_SHOW_ALL_REGS ? 'A' : 'a');
|
|
|
|
if (bt->flags & BT_THREAD_GROUP)
|
|
error(FATAL,
|
|
"-a option cannot be used with the -g option\n");
|
|
|
|
for (c = 0; c < NR_CPUS; c++) {
|
|
if (setjmp(pc->foreach_loop_env)) {
|
|
pc->flags &= ~IN_FOREACH;
|
|
free_all_bufs();
|
|
continue;
|
|
}
|
|
if ((tc = task_to_context(tt->panic_threads[c]))) {
|
|
pc->flags |= IN_FOREACH;
|
|
DO_TASK_BACKTRACE();
|
|
pc->flags &= ~IN_FOREACH;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if (!args[optind]) {
|
|
if (CURRENT_PID() && (bt->flags & BT_THREAD_GROUP)) {
|
|
tgid = task_tgid(CURRENT_TASK());
|
|
DO_THREAD_GROUP_BACKTRACE();
|
|
} else {
|
|
tc = CURRENT_CONTEXT();
|
|
DO_TASK_BACKTRACE();
|
|
}
|
|
return;
|
|
}
|
|
|
|
while (args[optind]) {
|
|
switch (str_to_context(args[optind], &value, &tc))
|
|
{
|
|
case STR_PID:
|
|
for (tc = pid_to_context(value); tc; tc = tc->tc_next) {
|
|
if (tc->pid && (bt->flags & BT_THREAD_GROUP)) {
|
|
tgid = task_tgid(tc->task);
|
|
DO_THREAD_GROUP_BACKTRACE();
|
|
break;
|
|
} else if (tc->tc_next) {
|
|
if (setjmp(pc->foreach_loop_env)) {
|
|
pc->flags &= ~IN_FOREACH;
|
|
free_all_bufs();
|
|
continue;
|
|
}
|
|
pc->flags |= IN_FOREACH;
|
|
DO_TASK_BACKTRACE();
|
|
pc->flags &= ~IN_FOREACH;
|
|
} else
|
|
DO_TASK_BACKTRACE();
|
|
}
|
|
break;
|
|
|
|
case STR_TASK:
|
|
if (tc->pid && (bt->flags & BT_THREAD_GROUP)) {
|
|
tgid = task_tgid(value);
|
|
DO_THREAD_GROUP_BACKTRACE();
|
|
} else
|
|
DO_TASK_BACKTRACE();
|
|
break;
|
|
|
|
case STR_INVALID:
|
|
error(INFO, "%sinvalid task or pid value: %s\n",
|
|
subsequent++ ? "\n" : "", args[optind]);
|
|
break;
|
|
}
|
|
|
|
optind++;
|
|
}
|
|
}
|
|
|
|
void
|
|
print_stack_text_syms(struct bt_info *bt, ulong esp, ulong eip)
|
|
{
|
|
ulong next_sp, next_pc;
|
|
int i;
|
|
ulong *up;
|
|
struct load_module *lm;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
|
|
if (bt->flags & BT_TEXT_SYMBOLS) {
|
|
if (!(bt->flags & BT_TEXT_SYMBOLS_ALL))
|
|
fprintf(fp, "%sSTART: %s at %lx\n",
|
|
space(VADDR_PRLEN > 8 ? 14 : 6),
|
|
bt->flags & BT_SYMBOL_OFFSET ?
|
|
value_to_symstr(eip, buf2, bt->radix) :
|
|
closest_symbol(eip), eip);
|
|
}
|
|
|
|
if (bt->hp)
|
|
bt->hp->eip = bt->hp->esp = 0;
|
|
next_pc = next_sp = 0;
|
|
|
|
for (i = (esp - bt->stackbase)/sizeof(ulong);
|
|
i < LONGS_PER_STACK; i++) {
|
|
up = (ulong *)(&bt->stackbuf[i*sizeof(ulong)]);
|
|
if (is_kernel_text_offset(*up)) {
|
|
if (!next_pc)
|
|
next_pc = *up;
|
|
else if (!next_sp)
|
|
next_sp = bt->stackbase + (i * sizeof(long));
|
|
}
|
|
if (is_kernel_text(*up) && (bt->flags &
|
|
(BT_TEXT_SYMBOLS|BT_TEXT_SYMBOLS_PRINT))) {
|
|
if (bt->flags & (BT_ERROR_MASK|BT_TEXT_SYMBOLS)) {
|
|
fprintf(fp, " %s[%s] %s at %lx",
|
|
bt->flags & BT_ERROR_MASK ?
|
|
" " : "",
|
|
mkstring(buf1, VADDR_PRLEN,
|
|
RJUST|LONG_HEX,
|
|
MKSTR(bt->stackbase +
|
|
(i * sizeof(long)))),
|
|
bt->flags & BT_SYMBOL_OFFSET ?
|
|
value_to_symstr(*up, buf2, bt->radix) :
|
|
closest_symbol(*up), *up);
|
|
if (module_symbol(*up, NULL, &lm, NULL, 0))
|
|
fprintf(fp, " [%s]", lm->mod_name);
|
|
fprintf(fp, "\n");
|
|
} else
|
|
fprintf(fp, "%lx: %s\n",
|
|
bt->stackbase +
|
|
(i * sizeof(long)),
|
|
value_to_symstr(*up, buf1, 0));
|
|
}
|
|
}
|
|
|
|
if (bt->hp) {
|
|
bt->hp->eip = next_pc;
|
|
bt->hp->esp = next_sp;
|
|
}
|
|
}
|
|
|
|
int
|
|
in_alternate_stack(int cpu, ulong address)
|
|
{
|
|
if (cpu >= NR_CPUS)
|
|
return FALSE;
|
|
|
|
if (machdep->in_alternate_stack)
|
|
if (machdep->in_alternate_stack(cpu, address))
|
|
return TRUE;
|
|
|
|
if (tt->flags & IRQSTACKS) {
|
|
if (in_irq_ctx(BT_SOFTIRQ, cpu, address) ||
|
|
in_irq_ctx(BT_HARDIRQ, cpu, address))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Gather the EIP, ESP and stack address for the target task, and passing
|
|
* them on to the machine-specific back trace command.
|
|
*/
|
|
void
|
|
back_trace(struct bt_info *bt)
|
|
{
|
|
int i;
|
|
ulong *up;
|
|
char buf[BUFSIZE];
|
|
ulong eip, esp;
|
|
struct bt_info btsave = { 0 };
|
|
|
|
if (bt->flags & BT_RAW) {
|
|
if (bt->hp && bt->hp->esp)
|
|
esp = bt->hp->esp;
|
|
else
|
|
esp = GET_STACKBASE(bt->task);
|
|
raw_stack_dump(esp, STACKSIZE());
|
|
return;
|
|
}
|
|
|
|
if (LIVE() && !(bt->flags & BT_EFRAME_SEARCH) &&
|
|
((bt->task == tt->this_task) || is_task_active(bt->task))) {
|
|
|
|
if (BT_REFERENCE_CHECK(bt) ||
|
|
bt->flags & (BT_TEXT_SYMBOLS_PRINT|BT_TEXT_SYMBOLS_NOPRINT))
|
|
return;
|
|
|
|
if (!(bt->flags &
|
|
(BT_KSTACKP|BT_TEXT_SYMBOLS|BT_TEXT_SYMBOLS_ALL)))
|
|
fprintf(fp, "(active)\n");
|
|
|
|
if (!(bt->flags & (BT_TEXT_SYMBOLS|BT_TEXT_SYMBOLS_ALL) || REMOTE_PAUSED()))
|
|
return;
|
|
}
|
|
|
|
fill_stackbuf(bt);
|
|
|
|
if (CRASHDEBUG(4)) {
|
|
for (i = 0, up = (ulong *)bt->stackbuf;
|
|
i < LONGS_PER_STACK; i++, up++) {
|
|
if (is_kernel_text(*up))
|
|
fprintf(fp, "%lx: %s\n",
|
|
tt->flags & THREAD_INFO ?
|
|
bt->tc->thread_info +
|
|
(i * sizeof(long)) :
|
|
bt->task + (i * sizeof(long)),
|
|
value_to_symstr(*up, buf, 0));
|
|
}
|
|
}
|
|
|
|
if (BT_REFERENCE_CHECK(bt)) {
|
|
if (can_eval(bt->ref->str)) {
|
|
bt->ref->hexval = eval(bt->ref->str,
|
|
FAULT_ON_ERROR, NULL);
|
|
bt->ref->cmdflags |= BT_REF_HEXVAL;
|
|
} else if (hexadecimal(bt->ref->str, 0)) {
|
|
bt->ref->hexval = htol(bt->ref->str,
|
|
FAULT_ON_ERROR, NULL);
|
|
bt->ref->cmdflags |= BT_REF_HEXVAL;
|
|
} else
|
|
bt->ref->cmdflags |= BT_REF_SYMBOL;
|
|
}
|
|
|
|
if (bt->flags & BT_EFRAME_SEARCH) {
|
|
machdep->eframe_search(bt);
|
|
return;
|
|
}
|
|
|
|
if (bt->hp) {
|
|
if (bt->hp->esp && !INSTACK(bt->hp->esp, bt) &&
|
|
!in_alternate_stack(bt->tc->processor, bt->hp->esp))
|
|
error(FATAL,
|
|
"non-process stack address for this task: %lx\n"
|
|
" (valid range: %lx - %lx)\n",
|
|
bt->hp->esp, bt->stackbase, bt->stacktop);
|
|
|
|
eip = bt->hp->eip;
|
|
esp = bt->hp->esp;
|
|
|
|
machdep->get_stack_frame(bt, eip ? NULL : &eip,
|
|
esp ? NULL : &esp);
|
|
|
|
if (in_irq_ctx(BT_HARDIRQ, bt->tc->processor, esp)) {
|
|
bt->stackbase = tt->hardirq_ctx[bt->tc->processor];
|
|
bt->stacktop = bt->stackbase + STACKSIZE();
|
|
alter_stackbuf(bt);
|
|
bt->flags |= BT_HARDIRQ;
|
|
} else if (in_irq_ctx(BT_SOFTIRQ, bt->tc->processor, esp)) {
|
|
bt->stackbase = tt->softirq_ctx[bt->tc->processor];
|
|
bt->stacktop = bt->stackbase + STACKSIZE();
|
|
alter_stackbuf(bt);
|
|
bt->flags |= BT_SOFTIRQ;
|
|
}
|
|
} else if (XEN_HYPER_MODE())
|
|
machdep->get_stack_frame(bt, &eip, &esp);
|
|
else 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 (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);
|
|
|
|
if (bt->flags & BT_KSTACKP) {
|
|
bt->stkptr = esp;
|
|
return;
|
|
}
|
|
|
|
if (ACTIVE() && !INSTACK(esp, bt)) {
|
|
sprintf(buf, "/proc/%ld", bt->tc->pid);
|
|
if (!file_exists(buf, NULL))
|
|
error(INFO, "task no longer exists\n");
|
|
else
|
|
error(INFO,
|
|
"invalid/stale stack pointer for this task: %lx\n",
|
|
esp);
|
|
return;
|
|
}
|
|
|
|
if (bt->flags &
|
|
(BT_TEXT_SYMBOLS|BT_TEXT_SYMBOLS_PRINT|BT_TEXT_SYMBOLS_NOPRINT)) {
|
|
|
|
if (bt->flags & BT_TEXT_SYMBOLS_ALL) {
|
|
esp = bt->stackbase +
|
|
((tt->flags & THREAD_INFO) ?
|
|
SIZE(thread_info) : SIZE(task_struct));
|
|
eip = 0;
|
|
}
|
|
|
|
if (machdep->flags & MACHDEP_BT_TEXT) {
|
|
bt->instptr = eip;
|
|
bt->stkptr = esp;
|
|
machdep->back_trace(bt);
|
|
} else
|
|
print_stack_text_syms(bt, esp, eip);
|
|
|
|
if (bt->flags & (BT_HARDIRQ|BT_SOFTIRQ)) {
|
|
struct bt_info btloc;
|
|
struct stack_hook stack_hook;
|
|
|
|
BZERO(&btloc, sizeof(struct bt_info));
|
|
BZERO(&stack_hook, sizeof(struct stack_hook));
|
|
btloc.flags = bt->flags & ~(BT_HARDIRQ|BT_SOFTIRQ);
|
|
btloc.hp = &stack_hook;
|
|
btloc.tc = bt->tc;
|
|
btloc.task = bt->task;
|
|
btloc.stackbase = GET_STACKBASE(bt->task);
|
|
btloc.stacktop = GET_STACKTOP(bt->task);
|
|
|
|
switch (bt->flags & (BT_HARDIRQ|BT_SOFTIRQ))
|
|
{
|
|
case BT_HARDIRQ:
|
|
btloc.hp->eip = symbol_value("do_IRQ");
|
|
if (symbol_exists("__do_IRQ"))
|
|
btloc.hp->esp = ULONG(bt->stackbuf +
|
|
OFFSET(thread_info_previous_esp));
|
|
else
|
|
btloc.hp->esp = ULONG(bt->stackbuf +
|
|
SIZE(irq_ctx) -
|
|
(sizeof(char *)*2));
|
|
fprintf(fp, "--- <hard IRQ> ---\n");
|
|
if (in_irq_ctx(BT_SOFTIRQ, bt->tc->processor, btloc.hp->esp)) {
|
|
btloc.flags |= BT_SOFTIRQ;
|
|
btloc.stackbase = tt->softirq_ctx[bt->tc->processor];
|
|
btloc.stacktop = btloc.stackbase + STACKSIZE();
|
|
}
|
|
break;
|
|
|
|
case BT_SOFTIRQ:
|
|
btloc.hp->eip = symbol_value("do_softirq");
|
|
btloc.hp->esp = ULONG(bt->stackbuf +
|
|
OFFSET(thread_info_previous_esp));
|
|
fprintf(fp, "--- <soft IRQ> ---\n");
|
|
break;
|
|
}
|
|
|
|
back_trace(&btloc);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
bt->instptr = eip;
|
|
bt->stkptr = esp;
|
|
|
|
complete_trace:
|
|
|
|
if (BT_REFERENCE_CHECK(bt))
|
|
BCOPY(bt, &btsave, sizeof(struct bt_info));
|
|
|
|
if (CRASHDEBUG(4))
|
|
dump_bt_info(bt, "back_trace");
|
|
|
|
machdep->back_trace(bt);
|
|
|
|
if ((bt->flags & (BT_HARDIRQ|BT_SOFTIRQ)) && restore_stack(bt))
|
|
goto complete_trace;
|
|
|
|
if (BT_REFERENCE_FOUND(bt)) {
|
|
#ifdef XEN_HYPERVISOR_ARCH
|
|
if (XEN_HYPER_MODE())
|
|
xen_hyper_print_bt_header(fp, bt->task, 0);
|
|
else
|
|
print_task_header(fp, task_to_context(bt->task), 0);
|
|
#else
|
|
print_task_header(fp, task_to_context(bt->task), 0);
|
|
#endif /* XEN_HYPERVISOR_ARCH */
|
|
|
|
BCOPY(&btsave, bt, sizeof(struct bt_info));
|
|
bt->ref = NULL;
|
|
machdep->back_trace(bt);
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Restore a bt_info to make the jump from an IRQ stack to the task's
|
|
* normal stack.
|
|
*/
|
|
static int
|
|
restore_stack(struct bt_info *bt)
|
|
{
|
|
ulonglong type;
|
|
struct syment *sp;
|
|
ulong retvaddr;
|
|
|
|
bt->instptr = bt->stkptr = 0;
|
|
type = 0;
|
|
|
|
switch (bt->flags & (BT_HARDIRQ|BT_SOFTIRQ))
|
|
{
|
|
case BT_HARDIRQ:
|
|
retvaddr = ULONG(bt->stackbuf +
|
|
SIZE(irq_ctx) - sizeof(char *));
|
|
if ((sp = value_search(retvaddr, NULL)) &&
|
|
STREQ(sp->name, "do_IRQ"))
|
|
bt->instptr = retvaddr;
|
|
else
|
|
bt->instptr = symbol_value("do_IRQ");
|
|
if (symbol_exists("__do_IRQ"))
|
|
bt->stkptr = ULONG(bt->stackbuf +
|
|
OFFSET(thread_info_previous_esp));
|
|
else
|
|
bt->stkptr = ULONG(bt->stackbuf +
|
|
SIZE(irq_ctx) - (sizeof(char *)*2));
|
|
type = BT_HARDIRQ;
|
|
break;
|
|
|
|
case BT_SOFTIRQ:
|
|
retvaddr = ULONG(bt->stackbuf +
|
|
SIZE(irq_ctx) - sizeof(char *));
|
|
if ((sp = value_search(retvaddr, NULL)) &&
|
|
STREQ(sp->name, "do_softirq"))
|
|
bt->instptr = retvaddr;
|
|
else
|
|
bt->instptr = symbol_value("do_softirq");
|
|
bt->stkptr = ULONG(bt->stackbuf +
|
|
OFFSET(thread_info_previous_esp));
|
|
type = BT_SOFTIRQ;
|
|
break;
|
|
}
|
|
|
|
if ((type == BT_HARDIRQ) && bt->instptr &&
|
|
in_irq_ctx(BT_SOFTIRQ, bt->tc->processor, bt->stkptr)) {
|
|
bt->flags &= ~BT_HARDIRQ;
|
|
bt->flags |= BT_SOFTIRQ;
|
|
bt->stackbase = tt->softirq_ctx[bt->tc->processor];
|
|
bt->stacktop = bt->stackbase + STACKSIZE();
|
|
if (!readmem(bt->stackbase, KVADDR, bt->stackbuf,
|
|
bt->stacktop - bt->stackbase,
|
|
"restore softirq_ctx stack", RETURN_ON_ERROR)) {
|
|
error(INFO,
|
|
"read of softirq stack at %lx failed\n",
|
|
bt->stackbase);
|
|
type = 0;
|
|
}
|
|
} else {
|
|
bt->flags &= ~(BT_HARDIRQ|BT_SOFTIRQ);
|
|
bt->stackbase = GET_STACKBASE(bt->tc->task);
|
|
bt->stacktop = GET_STACKTOP(bt->tc->task);
|
|
|
|
if (!readmem(bt->stackbase, KVADDR, bt->stackbuf,
|
|
bt->stacktop - bt->stackbase,
|
|
"restore_stack contents", RETURN_ON_ERROR)) {
|
|
error(INFO, "restore_stack of stack at %lx failed\n",
|
|
bt->stackbase);
|
|
type = 0;
|
|
}
|
|
|
|
if (!(bt->instptr && INSTACK(bt->stkptr, bt)))
|
|
type = 0;
|
|
}
|
|
|
|
if (type) {
|
|
if (!BT_REFERENCE_CHECK(bt))
|
|
fprintf(fp, "--- %s ---\n", type == BT_HARDIRQ ?
|
|
"<hard IRQ>" : "<soft IRQ>");
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
#define MAXHOOKS (100)
|
|
|
|
struct stack_hook *
|
|
gather_text_list(struct bt_info *bt)
|
|
{
|
|
int cnt;
|
|
struct bt_info btloc;
|
|
char buf[BUFSIZE], *p1;
|
|
struct stack_hook *hooks;
|
|
ulong esp, eip;
|
|
FILE *savedfp;
|
|
|
|
BCOPY(bt, &btloc, sizeof(struct bt_info));
|
|
hooks = (struct stack_hook *)GETBUF(sizeof(struct stack_hook)*MAXHOOKS);
|
|
cnt = 0;
|
|
|
|
savedfp = fp;
|
|
open_tmpfile2();
|
|
fp = pc->tmpfile2;
|
|
btloc.flags = BT_TEXT_SYMBOLS_PRINT;
|
|
back_trace(&btloc);
|
|
rewind(pc->tmpfile2);
|
|
while (fgets(buf, BUFSIZE, pc->tmpfile2)) {
|
|
if ((p1 = strstr(buf, ":"))) {
|
|
esp = eip = 0;
|
|
*p1 = NULLCHAR;
|
|
if (((esp = htol(buf, RETURN_ON_ERROR, NULL)) != BADADDR)
|
|
&& INSTACK(esp, bt))
|
|
eip = GET_STACK_ULONG(esp);
|
|
if (esp && eip) {
|
|
hooks[cnt].esp = esp;
|
|
hooks[cnt].eip = eip;
|
|
if (++cnt == MAXHOOKS)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
close_tmpfile2();
|
|
fp = savedfp;
|
|
|
|
if (cnt)
|
|
return (bt->textlist = hooks);
|
|
else {
|
|
FREEBUF(hooks);
|
|
return (bt->textlist = NULL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Debug routine most likely useful from above in back_trace()
|
|
*/
|
|
void
|
|
dump_bt_info(struct bt_info *bt, char *where)
|
|
{
|
|
fprintf(fp, "[%lx] %s:\n", (ulong)bt, where);
|
|
fprintf(fp, " task: %lx\n", bt->task);
|
|
fprintf(fp, " flags: %llx\n", bt->flags);
|
|
fprintf(fp, " instptr: %lx\n", bt->instptr);
|
|
fprintf(fp, " stkptr: %lx\n", bt->stkptr);
|
|
fprintf(fp, " bptr: %lx\n", bt->bptr);
|
|
fprintf(fp, " stackbase: %lx\n", bt->stackbase);
|
|
fprintf(fp, " stacktop: %lx\n", bt->stacktop);
|
|
fprintf(fp, " tc: %lx ", (ulong)bt->tc);
|
|
if (bt->tc)
|
|
fprintf(fp, "(%ld, %lx)\n", bt->tc->pid, bt->tc->task);
|
|
else
|
|
fprintf(fp, "(unknown context)\n");
|
|
fprintf(fp, " hp: %lx\n", (ulong)bt->hp);
|
|
fprintf(fp, " ref: %lx\n", (ulong)bt->ref);
|
|
fprintf(fp, " stackbuf: %lx\n", (ulong)bt->stackbuf);
|
|
fprintf(fp, " textlist: %lx\n", (ulong)bt->textlist);
|
|
fprintf(fp, " frameptr: %lx\n", (ulong)bt->frameptr);
|
|
fprintf(fp, " call_target: %s\n", bt->call_target ?
|
|
bt->call_target : "none");
|
|
fprintf(fp, " eframe_ip: %lx\n", bt->eframe_ip);
|
|
fprintf(fp, " debug: %lx\n", bt->debug);
|
|
fprintf(fp, " radix: %ld\n", bt->radix);
|
|
fprintf(fp, " cpumask: %lx\n", (ulong)bt->cpumask);
|
|
}
|
|
|
|
/*
|
|
* LKCD doesn't save state of the active tasks in the TSS, so poke around
|
|
* the raw stack for some reasonable hooks.
|
|
*/
|
|
static void
|
|
get_lkcd_regs(struct bt_info *bt, ulong *eip, ulong *esp)
|
|
{
|
|
int i;
|
|
char *sym;
|
|
ulong *up;
|
|
ulong sysrq_eip, sysrq_esp;
|
|
|
|
if (!is_task_active(bt->task)) {
|
|
machdep->get_stack_frame(bt, eip, esp);
|
|
return;
|
|
}
|
|
|
|
/* try to get it from the header */
|
|
if (get_lkcd_regs_for_cpu(bt, eip, esp) == 0)
|
|
return;
|
|
|
|
/* if that fails: do guessing */
|
|
sysrq_eip = sysrq_esp = 0;
|
|
|
|
for (i = 0, up = (ulong *)bt->stackbuf; i < LONGS_PER_STACK; i++, up++){
|
|
sym = closest_symbol(*up);
|
|
if (STREQ(sym, "dump_execute") && INSTACK(*(up-1), bt)) {
|
|
*eip = *up;
|
|
*esp = *(up-1);
|
|
return;
|
|
}
|
|
/* Begin 3PAR change -- required for our panic path */
|
|
if (STREQ(sym, "dump_ipi") && INSTACK(*(up-1), bt)) {
|
|
*eip = *up;
|
|
*esp = *(up-1);
|
|
return;
|
|
}
|
|
/* End 3PAR change */
|
|
if (STREQ(sym, "panic") && INSTACK(*(up-1), bt)) {
|
|
*eip = *up;
|
|
*esp = *(up-1);
|
|
return;
|
|
}
|
|
/* Egenera */
|
|
if (STREQ(sym, "netdump_ipi")) {
|
|
*eip = *up;
|
|
*esp = bt->task +
|
|
((char *)(up-1) - bt->stackbuf);
|
|
return;
|
|
}
|
|
if (STREQ(sym, "dump_execute")) {
|
|
*eip = *up;
|
|
*esp = bt->stackbase +
|
|
((char *)(up) - bt->stackbuf);
|
|
return;
|
|
}
|
|
if (STREQ(sym, "vmdump_nmi_callback")) {
|
|
*eip = *up;
|
|
*esp = bt->stackbase +
|
|
((char *)(up) - bt->stackbuf);
|
|
return;
|
|
}
|
|
if (STREQ(sym, "smp_stop_cpu_interrupt")) {
|
|
*eip = *up;
|
|
*esp = bt->task +
|
|
((char *)(up-1) - bt->stackbuf);
|
|
return;
|
|
}
|
|
if (STREQ(sym, "stop_this_cpu")) {
|
|
*eip = *up;
|
|
*esp = bt->task +
|
|
((char *)(up-1) - bt->stackbuf);
|
|
return;
|
|
}
|
|
if (SYSRQ_TASK(bt->task) &&
|
|
STREQ(sym, "smp_call_function_interrupt")) {
|
|
sysrq_eip = *up;
|
|
sysrq_esp = bt->task +
|
|
((char *)(up-1) - bt->stackbuf);
|
|
}
|
|
}
|
|
|
|
if (sysrq_eip) {
|
|
*eip = sysrq_eip;
|
|
*esp = sysrq_esp;
|
|
return;
|
|
}
|
|
|
|
machdep->get_stack_frame(bt, eip, esp);
|
|
}
|
|
|
|
|
|
/*
|
|
* Store the head of the kernel module list for future use.
|
|
* Count the number of symbols defined by all modules in the system,
|
|
* and pass it on to store_module_symbols() to deal with.
|
|
*/
|
|
void
|
|
module_init(void)
|
|
{
|
|
int i, c;
|
|
ulong size, mod, mod_next;
|
|
uint nsyms;
|
|
ulong total, numksyms;
|
|
char *modbuf, *kallsymsbuf;
|
|
ulong kallsyms_header;
|
|
struct syment *sp, *sp_array[10];
|
|
struct kernel_list_head list;
|
|
int modules_found;
|
|
|
|
if (kernel_symbol_exists("module_list"))
|
|
kt->flags |= KMOD_V1;
|
|
else if (kernel_symbol_exists("modules"))
|
|
kt->flags |= KMOD_V2;
|
|
else
|
|
error(WARNING, "cannot determine how modules are linked\n");
|
|
|
|
if (kt->flags & NO_MODULE_ACCESS || !(kt->flags & (KMOD_V1|KMOD_V2))) {
|
|
error(WARNING, "no kernel module access\n\n");
|
|
kt->module_list = 0;
|
|
kt->mods_installed = 0;
|
|
return;
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(module, "module");
|
|
MEMBER_OFFSET_INIT(module_name, "module", "name");
|
|
MEMBER_OFFSET_INIT(module_syms, "module", "syms");
|
|
mod_next = nsyms = 0;
|
|
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V1:
|
|
MEMBER_OFFSET_INIT(module_size_of_struct, "module",
|
|
"size_of_struct");
|
|
MEMBER_OFFSET_INIT(module_next, "module", "next");
|
|
MEMBER_OFFSET_INIT(module_nsyms, "module", "nsyms");
|
|
MEMBER_OFFSET_INIT(module_size, "module", "size");
|
|
MEMBER_OFFSET_INIT(module_flags, "module", "flags");
|
|
|
|
get_symbol_data("module_list", sizeof(ulong), &kt->module_list);
|
|
kt->kernel_module = symbol_value("kernel_module");
|
|
break;
|
|
|
|
case KMOD_V2:
|
|
MEMBER_OFFSET_INIT(module_num_syms, "module", "num_syms");
|
|
MEMBER_OFFSET_INIT(module_list, "module", "list");
|
|
MEMBER_OFFSET_INIT(module_gpl_syms, "module", "gpl_syms");
|
|
MEMBER_OFFSET_INIT(module_num_gpl_syms, "module",
|
|
"num_gpl_syms");
|
|
MEMBER_OFFSET_INIT(module_module_core, "module",
|
|
"module_core");
|
|
MEMBER_OFFSET_INIT(module_core_size, "module",
|
|
"core_size");
|
|
MEMBER_OFFSET_INIT(module_core_text_size, "module",
|
|
"core_text_size");
|
|
MEMBER_OFFSET_INIT(module_module_init, "module", "module_init");
|
|
MEMBER_OFFSET_INIT(module_init_size, "module", "init_size");
|
|
MEMBER_OFFSET_INIT(module_init_text_size, "module",
|
|
"init_text_size");
|
|
MEMBER_OFFSET_INIT(module_percpu, "module", "percpu");
|
|
|
|
/*
|
|
* Make sure to pick the kernel "modules" list_head symbol,
|
|
* not to be confused with the ia64/sn "modules[]" array.
|
|
* The kernel modules list_head will either point to itself
|
|
* (empty) or contain vmalloc'd module addresses; the ia64/sn
|
|
* modules array contains a list of kmalloc'd addresses.
|
|
*/
|
|
if ((c = get_syment_array("modules", sp_array, 10)) > 1) {
|
|
modules_found = FALSE;
|
|
for (i = 0; i < c; i++) {
|
|
sp = sp_array[i];
|
|
|
|
if (!readmem(sp->value, KVADDR,
|
|
&list, sizeof(struct kernel_list_head),
|
|
"modules list_head test",
|
|
RETURN_ON_ERROR|QUIET))
|
|
continue;
|
|
|
|
if ((ulong)list.next == symbol_value("modules")) {
|
|
kt->mods_installed = 0;
|
|
return;
|
|
}
|
|
|
|
if (IS_VMALLOC_ADDR((ulong)list.next) &&
|
|
IS_VMALLOC_ADDR((ulong)list.prev)) {
|
|
kt->kernel_module = sp->value;
|
|
kt->module_list = (ulong)list.next;
|
|
modules_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!modules_found) {
|
|
error(WARNING,
|
|
"cannot determine which of %d \"modules\" symbols is appropriate\n\n",
|
|
c);
|
|
kt->mods_installed = 0;
|
|
kt->flags |= NO_MODULE_ACCESS;
|
|
return;
|
|
}
|
|
} else {
|
|
get_symbol_data("modules", sizeof(ulong),
|
|
&kt->module_list);
|
|
if (kt->module_list == symbol_value("modules")) {
|
|
kt->mods_installed = 0;
|
|
return;
|
|
}
|
|
kt->kernel_module = symbol_value("modules");
|
|
}
|
|
kt->module_list -= OFFSET(module_list);
|
|
break;
|
|
}
|
|
|
|
total = kt->mods_installed = 0;
|
|
|
|
modbuf = GETBUF(SIZE(module));
|
|
kallsymsbuf = kt->flags & KALLSYMS_V1 ?
|
|
GETBUF(SIZE(kallsyms_header)) : NULL;
|
|
|
|
please_wait("gathering module symbol data");
|
|
|
|
for (mod = kt->module_list; mod != kt->kernel_module; mod = mod_next) {
|
|
if (CRASHDEBUG(3))
|
|
fprintf(fp, "module: %lx\n", mod);
|
|
|
|
if (!readmem(mod, KVADDR, modbuf, SIZE(module),
|
|
"module struct", RETURN_ON_ERROR|QUIET)) {
|
|
error(WARNING,
|
|
"%scannot access vmalloc'd module memory\n\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
kt->mods_installed = 0;
|
|
kt->flags |= NO_MODULE_ACCESS;
|
|
FREEBUF(modbuf);
|
|
return;
|
|
}
|
|
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V1:
|
|
nsyms = UINT(modbuf + OFFSET(module_nsyms));
|
|
break;
|
|
case KMOD_V2:
|
|
nsyms = UINT(modbuf + OFFSET(module_num_syms)) +
|
|
UINT(modbuf + OFFSET(module_num_gpl_syms));
|
|
break;
|
|
}
|
|
|
|
total += nsyms;
|
|
total += 2; /* store the module's start/ending addresses */
|
|
|
|
/*
|
|
* If the module has kallsyms, set up to grab them as well.
|
|
*/
|
|
switch (kt->flags & (KALLSYMS_V1|KALLSYMS_V2))
|
|
{
|
|
case KALLSYMS_V1:
|
|
kallsyms_header = ULONG(modbuf +
|
|
OFFSET(module_kallsyms_start));
|
|
if (kallsyms_header) {
|
|
if (!readmem(kallsyms_header, KVADDR,
|
|
kallsymsbuf, SIZE(kallsyms_header),
|
|
"kallsyms_header", RETURN_ON_ERROR|QUIET)) {
|
|
error(WARNING,
|
|
"%scannot access module kallsyms_header\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
} else {
|
|
nsyms = UINT(kallsymsbuf +
|
|
OFFSET(kallsyms_header_symbols));
|
|
total += nsyms;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case KALLSYMS_V2:
|
|
if (THIS_KERNEL_VERSION >= LINUX(2,6,27)) {
|
|
numksyms = UINT(modbuf + OFFSET(module_num_symtab));
|
|
size = UINT(modbuf + OFFSET(module_core_size));
|
|
} else {
|
|
numksyms = ULONG(modbuf + OFFSET(module_num_symtab));
|
|
size = ULONG(modbuf + OFFSET(module_core_size));
|
|
}
|
|
|
|
if (!size) {
|
|
/*
|
|
* Bail out here instead of a crashing with a
|
|
* getbuf(0) failure during storage later on.
|
|
*/
|
|
error(WARNING,
|
|
"invalid kernel module size: 0\n");
|
|
kt->mods_installed = 0;
|
|
kt->flags |= NO_MODULE_ACCESS;
|
|
FREEBUF(modbuf);
|
|
return;
|
|
}
|
|
|
|
total += numksyms;
|
|
break;
|
|
}
|
|
|
|
kt->mods_installed++;
|
|
|
|
NEXT_MODULE(mod_next, modbuf);
|
|
}
|
|
|
|
FREEBUF(modbuf);
|
|
if (kallsymsbuf)
|
|
FREEBUF(kallsymsbuf);
|
|
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V1:
|
|
store_module_symbols_v1(total, kt->mods_installed);
|
|
break;
|
|
case KMOD_V2:
|
|
store_module_symbols_v2(total, kt->mods_installed);
|
|
break;
|
|
}
|
|
|
|
please_wait_done();
|
|
}
|
|
|
|
|
|
/*
|
|
* Verify that the current set of modules jives with what's stored.
|
|
*/
|
|
static int
|
|
verify_modules(void)
|
|
{
|
|
int i;
|
|
int found, irregularities;
|
|
ulong mod, mod_next, mod_base;
|
|
long mod_size;
|
|
char *modbuf, *module_name;
|
|
ulong module_list, mod_name;
|
|
physaddr_t paddr;
|
|
int mods_installed;
|
|
struct load_module *lm;
|
|
char buf[BUFSIZE];
|
|
|
|
if (DUMPFILE() || !kt->module_list || (kt->flags & NO_MODULE_ACCESS))
|
|
return TRUE;
|
|
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V1:
|
|
get_symbol_data("module_list", sizeof(ulong), &module_list);
|
|
break;
|
|
case KMOD_V2:
|
|
if (kt->module_list == symbol_value("modules")) {
|
|
if (!kt->mods_installed)
|
|
return TRUE;
|
|
}
|
|
get_symbol_data("modules", sizeof(ulong), &module_list);
|
|
module_list -= OFFSET(module_list);
|
|
break;
|
|
}
|
|
|
|
mods_installed = irregularities = 0;
|
|
mod_base = mod_next = 0;
|
|
modbuf = GETBUF(SIZE(module));
|
|
|
|
for (mod = module_list; mod != kt->kernel_module; mod = mod_next) {
|
|
|
|
if (!readmem(mod, KVADDR, modbuf, SIZE(module),
|
|
"module struct", RETURN_ON_ERROR|QUIET)) {
|
|
error(WARNING,
|
|
"cannot access vmalloc'd module memory\n");
|
|
FREEBUF(modbuf);
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
for (i = 0, found = FALSE; i < kt->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
if (!kvtop(NULL, lm->mod_base, &paddr, 0)) {
|
|
irregularities++;
|
|
break;
|
|
}
|
|
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V1:
|
|
mod_base = mod;
|
|
break;
|
|
case KMOD_V2:
|
|
mod_base = ULONG(modbuf +
|
|
OFFSET(module_module_core));
|
|
break;
|
|
}
|
|
|
|
if (lm->mod_base == mod_base) {
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V1:
|
|
mod_name = ULONG(modbuf +
|
|
OFFSET(module_name));
|
|
mod_size = LONG(modbuf +
|
|
OFFSET(module_size));
|
|
if (!read_string(mod_name, buf,
|
|
BUFSIZE-1) || !STREQ(lm->mod_name,
|
|
buf) || (mod_size != lm->mod_size)){
|
|
irregularities++;
|
|
goto irregularity;
|
|
}
|
|
break;
|
|
case KMOD_V2:
|
|
module_name = modbuf +
|
|
OFFSET(module_name);
|
|
if (THIS_KERNEL_VERSION >= LINUX(2,6,27))
|
|
mod_size = UINT(modbuf +
|
|
OFFSET(module_core_size));
|
|
else
|
|
mod_size = ULONG(modbuf +
|
|
OFFSET(module_core_size));
|
|
if (strlen(module_name) < MAX_MOD_NAME)
|
|
strcpy(buf, module_name);
|
|
else
|
|
strncpy(buf, module_name,
|
|
MAX_MOD_NAME-1);
|
|
if (!STREQ(lm->mod_name, buf) ||
|
|
(mod_size != lm->mod_size)) {
|
|
irregularities++;
|
|
goto irregularity;
|
|
}
|
|
break;
|
|
}
|
|
found = TRUE;
|
|
irregularity:
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found || irregularities)
|
|
return FALSE;
|
|
|
|
mods_installed++;
|
|
|
|
NEXT_MODULE(mod_next, modbuf);
|
|
}
|
|
|
|
FREEBUF(modbuf);
|
|
|
|
if (mods_installed != kt->mods_installed)
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/*
|
|
* With no arguments, just dump basic data concerning each of the
|
|
* currently-loaded modules. The -s and -S arguments dynamically
|
|
* loads module symbols from its object file.
|
|
*/
|
|
#define LIST_MODULE_HDR (0)
|
|
#define LIST_MODULE (1)
|
|
#define LOAD_ALL_MODULE_SYMBOLS (2)
|
|
#define LOAD_SPECIFIED_MODULE_SYMBOLS (3)
|
|
#define DELETE_MODULE_SYMBOLS (4)
|
|
#define DELETE_ALL_MODULE_SYMBOLS (5)
|
|
#define REMOTE_MODULE_SAVE_MSG (6)
|
|
#define REINIT_MODULES (7)
|
|
#define LIST_ALL_MODULE_TAINT (8)
|
|
|
|
void
|
|
cmd_mod(void)
|
|
{
|
|
int c, ctmp;
|
|
char *p, *objfile, *modref, *tree, *symlink;
|
|
ulong flag, address;
|
|
char buf[BUFSIZE];
|
|
|
|
if (kt->flags & NO_MODULE_ACCESS)
|
|
error(FATAL, "cannot access vmalloc'd module memory\n");
|
|
|
|
if (!verify_modules()) {
|
|
error(NOTE,
|
|
"modules have changed on this system -- reinitializing\n");
|
|
reinit_modules();
|
|
}
|
|
|
|
if (!kt->mods_installed) {
|
|
fprintf(fp, "no modules installed\n");
|
|
return;
|
|
}
|
|
|
|
for (c = 1, p = NULL; c < argcnt; c++) {
|
|
if (args[c][0] != '-')
|
|
continue;
|
|
|
|
if (STREQ(args[c], "-g")) {
|
|
ctmp = c;
|
|
pc->curcmd_flags |= MOD_SECTIONS;
|
|
while (ctmp < argcnt) {
|
|
args[ctmp] = args[ctmp+1];
|
|
ctmp++;
|
|
}
|
|
argcnt--;
|
|
c--;
|
|
} else if (STREQ(args[c], "-r")) {
|
|
ctmp = c;
|
|
pc->curcmd_flags |= MOD_READNOW;
|
|
while (ctmp < argcnt) {
|
|
args[ctmp] = args[ctmp+1];
|
|
ctmp++;
|
|
}
|
|
argcnt--;
|
|
c--;
|
|
} else {
|
|
if ((p = strstr(args[c], "g"))) {
|
|
pc->curcmd_flags |= MOD_SECTIONS;
|
|
shift_string_left(p, 1);
|
|
}
|
|
if ((p = strstr(args[c], "r"))) {
|
|
pc->curcmd_flags |= MOD_READNOW;
|
|
shift_string_left(p, 1);
|
|
}
|
|
/* if I've removed everything but the '-', toss it */
|
|
if (STREQ(args[c], "-")) {
|
|
ctmp = c;
|
|
while (ctmp < argcnt) {
|
|
args[ctmp] = args[ctmp+1];
|
|
ctmp++;
|
|
}
|
|
argcnt--;
|
|
c--;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pc->flags & READNOW)
|
|
pc->curcmd_flags |= MOD_READNOW;
|
|
|
|
modref = objfile = tree = symlink = NULL;
|
|
address = 0;
|
|
flag = LIST_MODULE_HDR;
|
|
|
|
while ((c = getopt(argcnt, args, "Rd:Ds:Sot")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'R':
|
|
if (flag)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
flag = REINIT_MODULES;
|
|
break;
|
|
|
|
case 'D':
|
|
if (flag)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
flag = DELETE_ALL_MODULE_SYMBOLS;
|
|
break;
|
|
|
|
case 'd':
|
|
if (flag)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
else
|
|
flag = DELETE_MODULE_SYMBOLS;
|
|
|
|
if (hexadecimal(optarg, 0) &&
|
|
(strlen(optarg) == VADDR_PRLEN)) {
|
|
address = htol(optarg, FAULT_ON_ERROR, NULL);
|
|
if (!is_module_address(address, buf))
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
modref = buf;
|
|
} else if (is_module_name(optarg, &address, NULL))
|
|
modref = optarg;
|
|
else
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
break;
|
|
|
|
/*
|
|
* Revert to using old-style add-symbol-file command
|
|
* for KMOD_V2 kernels.
|
|
*/
|
|
case 'o':
|
|
if (flag)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
if (kt->flags & KMOD_V1)
|
|
error(INFO,
|
|
"-o option is not applicable to this kernel version\n");
|
|
st->flags |= USE_OLD_ADD_SYM;
|
|
return;
|
|
|
|
case 'S':
|
|
if (flag)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
else
|
|
flag = LOAD_ALL_MODULE_SYMBOLS;
|
|
break;
|
|
|
|
case 's':
|
|
if (flag)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
else
|
|
flag = LOAD_SPECIFIED_MODULE_SYMBOLS;
|
|
|
|
if (hexadecimal(optarg, 0) &&
|
|
(strlen(optarg) == VADDR_PRLEN)) {
|
|
address = htol(optarg, FAULT_ON_ERROR, NULL);
|
|
if (!is_module_address(address, buf))
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
modref = buf;
|
|
} else if (is_module_name(optarg, &address, NULL))
|
|
modref = optarg;
|
|
else
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
break;
|
|
|
|
case 't':
|
|
if (flag)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
else
|
|
flag = LIST_ALL_MODULE_TAINT;
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tree && (flag != LOAD_ALL_MODULE_SYMBOLS))
|
|
argerrs++;
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (NO_MODULES()) {
|
|
error(INFO, "no modules loaded in this kernel\n");
|
|
if (flag != LIST_MODULE_HDR)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
return;
|
|
}
|
|
|
|
switch (flag)
|
|
{
|
|
case LOAD_ALL_MODULE_SYMBOLS:
|
|
switch (argcnt)
|
|
{
|
|
case 3:
|
|
if (is_directory(args[2]))
|
|
tree = args[2];
|
|
else {
|
|
error(INFO, "%s is not a directory\n", args[2]);
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
}
|
|
break;
|
|
|
|
case 2:
|
|
break;
|
|
|
|
default:
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
}
|
|
break;
|
|
|
|
case LOAD_SPECIFIED_MODULE_SYMBOLS:
|
|
switch (argcnt)
|
|
{
|
|
case 4:
|
|
objfile = args[3];
|
|
if (!file_exists(objfile, NULL)) {
|
|
if (!(objfile =
|
|
find_module_objfile(modref, objfile, tree)))
|
|
error(FATAL,
|
|
"%s: cannot find or load object file: %s\n",
|
|
modref, args[3]);
|
|
}
|
|
break;
|
|
|
|
case 3:
|
|
if (!(objfile = find_module_objfile(modref,NULL,tree)))
|
|
error(FATAL,
|
|
"cannot find or load object file for %s module\n",
|
|
modref);
|
|
break;
|
|
|
|
default:
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
}
|
|
|
|
if (!is_elf_file(objfile)) {
|
|
error(INFO, "%s: not an ELF format object file\n",
|
|
objfile);
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if ((flag == LOAD_ALL_MODULE_SYMBOLS) &&
|
|
(tree || kt->module_tree)) {
|
|
if (!tree)
|
|
tree = kt->module_tree;
|
|
}
|
|
|
|
do_module_cmd(flag, modref, address, objfile, tree);
|
|
|
|
if (symlink)
|
|
FREEBUF(symlink);
|
|
}
|
|
|
|
int
|
|
check_specified_module_tree(char *module, char *gdb_buffer)
|
|
{
|
|
char *p1, *treebuf;
|
|
int retval;
|
|
|
|
retval = FALSE;
|
|
|
|
/*
|
|
* Search for "/lib/modules" in the module name string
|
|
* and insert "/usr/lib/debug" there.
|
|
*/
|
|
if (strstr(module, "/lib/modules")) {
|
|
treebuf = GETBUF(strlen(module) + strlen("/usr/lib/debug") +
|
|
strlen(".debug") + 1);
|
|
strcpy(treebuf, module);
|
|
p1 = strstr(treebuf, "/lib/modules");
|
|
shift_string_right(p1, strlen("/usr/lib/debug"));
|
|
BCOPY("/usr/lib/debug", p1, strlen("/usr/lib/debug"));
|
|
strcat(treebuf, ".debug");
|
|
if (file_exists(treebuf, NULL)) {
|
|
strcpy(gdb_buffer, treebuf);
|
|
retval = TRUE;
|
|
}
|
|
FREEBUF(treebuf);
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
static void
|
|
show_module_taint(void)
|
|
{
|
|
int i, j, bx;
|
|
struct load_module *lm;
|
|
int maxnamelen;
|
|
int found;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
int gpgsig_ok, license_gplok;
|
|
struct syment *sp;
|
|
uint *taintsp, taints;
|
|
uint8_t tnt_bit;
|
|
char tnt_true, tnt_false;
|
|
int tnts_exists, tnts_len;
|
|
ulong tnts_addr;
|
|
char *modbuf;
|
|
|
|
if (INVALID_MEMBER(module_taints) &&
|
|
INVALID_MEMBER(module_license_gplok)) {
|
|
MEMBER_OFFSET_INIT(module_taints, "module", "taints");
|
|
MEMBER_OFFSET_INIT(module_license_gplok,
|
|
"module", "license_gplok");
|
|
MEMBER_OFFSET_INIT(module_gpgsig_ok, "module", "gpgsig_ok");
|
|
STRUCT_SIZE_INIT(tnt, "tnt");
|
|
MEMBER_OFFSET_INIT(tnt_bit, "tnt", "bit");
|
|
MEMBER_OFFSET_INIT(tnt_true, "tnt", "true");
|
|
MEMBER_OFFSET_INIT(tnt_false, "tnt", "false");
|
|
}
|
|
|
|
if (INVALID_MEMBER(module_taints) &&
|
|
INVALID_MEMBER(module_license_gplok))
|
|
option_not_supported('t');
|
|
|
|
modbuf = GETBUF(SIZE(module));
|
|
|
|
for (i = found = maxnamelen = 0; i < kt->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
|
|
readmem(lm->module_struct, KVADDR, modbuf, SIZE(module),
|
|
"module struct", FAULT_ON_ERROR);
|
|
|
|
taints = VALID_MEMBER(module_taints) ?
|
|
UINT(modbuf + OFFSET(module_taints)) : 0;
|
|
license_gplok = VALID_MEMBER(module_license_gplok) ?
|
|
INT(modbuf + OFFSET(module_license_gplok)) : 0;
|
|
gpgsig_ok = VALID_MEMBER(module_gpgsig_ok) ?
|
|
INT(modbuf + OFFSET(module_gpgsig_ok)) : 1;
|
|
|
|
if (VALID_MEMBER(module_license_gplok) || taints || !gpgsig_ok) {
|
|
found++;
|
|
maxnamelen = strlen(lm->mod_name) > maxnamelen ?
|
|
strlen(lm->mod_name) : maxnamelen;
|
|
}
|
|
|
|
}
|
|
|
|
if (!found) {
|
|
fprintf(fp, "no tainted modules\n");
|
|
FREEBUF(modbuf);
|
|
return;
|
|
}
|
|
|
|
if (VALID_STRUCT(tnt) && (sp = symbol_search("tnts"))) {
|
|
tnts_exists = TRUE;
|
|
tnts_len = get_array_length("tnts", NULL, 0);
|
|
tnts_addr = sp->value;
|
|
} else {
|
|
tnts_exists = FALSE;
|
|
tnts_len = 0;
|
|
tnts_addr = 0;
|
|
}
|
|
|
|
fprintf(fp, "%s %s\n",
|
|
mkstring(buf2, maxnamelen, LJUST, "NAME"),
|
|
VALID_MEMBER(module_taints) ? "TAINTS" : "LICENSE_GPLOK");
|
|
|
|
for (i = 0; i < st->mods_installed; i++) {
|
|
|
|
lm = &st->load_modules[i];
|
|
bx = 0;
|
|
buf1[0] = '\0';
|
|
|
|
readmem(lm->module_struct, KVADDR, modbuf, SIZE(module),
|
|
"module struct", FAULT_ON_ERROR);
|
|
|
|
taints = VALID_MEMBER(module_taints) ?
|
|
UINT(modbuf + OFFSET(module_taints)) : 0;
|
|
license_gplok = VALID_MEMBER(module_license_gplok) ?
|
|
INT(modbuf + OFFSET(module_license_gplok)) : 0;
|
|
gpgsig_ok = VALID_MEMBER(module_gpgsig_ok) ?
|
|
INT(modbuf + OFFSET(module_gpgsig_ok)) : 1;
|
|
|
|
if (INVALID_MEMBER(module_license_gplok)) {
|
|
if (!taints && gpgsig_ok)
|
|
continue;
|
|
}
|
|
|
|
if (tnts_exists && taints) {
|
|
taintsp = &taints;
|
|
for (j = 0; j < (tnts_len * SIZE(tnt)); j += SIZE(tnt)) {
|
|
readmem((tnts_addr + j) + OFFSET(tnt_bit),
|
|
KVADDR, &tnt_bit, sizeof(uint8_t),
|
|
"tnt bit", FAULT_ON_ERROR);
|
|
|
|
if (NUM_IN_BITMAP(taintsp, tnt_bit)) {
|
|
readmem((tnts_addr + j) + OFFSET(tnt_true),
|
|
KVADDR, &tnt_true, sizeof(char),
|
|
"tnt true", FAULT_ON_ERROR);
|
|
buf1[bx++] = tnt_true;
|
|
} else {
|
|
readmem((tnts_addr + j) + OFFSET(tnt_false),
|
|
KVADDR, &tnt_false, sizeof(char),
|
|
"tnt false", FAULT_ON_ERROR);
|
|
if (tnt_false != ' ' && tnt_false != '-' &&
|
|
tnt_false != 'G')
|
|
buf1[bx++] = tnt_false;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (VALID_MEMBER(module_gpgsig_ok) && !gpgsig_ok) {
|
|
buf1[bx++] = '(';
|
|
buf1[bx++] = 'U';
|
|
buf1[bx++] = ')';
|
|
}
|
|
|
|
buf1[bx++] = '\0';
|
|
|
|
if (tnts_exists)
|
|
fprintf(fp, "%s %s\n", mkstring(buf2, maxnamelen,
|
|
LJUST, lm->mod_name), buf1);
|
|
else
|
|
fprintf(fp, "%s %x%s\n", mkstring(buf2, maxnamelen,
|
|
LJUST, lm->mod_name),
|
|
VALID_MEMBER(module_taints) ?
|
|
taints : license_gplok, buf1);
|
|
}
|
|
|
|
FREEBUF(modbuf);
|
|
}
|
|
|
|
/*
|
|
* Do the simple list work for cmd_mod().
|
|
*/
|
|
|
|
static void
|
|
do_module_cmd(ulong flag, char *modref, ulong address,
|
|
char *objfile, char *tree)
|
|
{
|
|
int i, j;
|
|
struct load_module *lm, *lmp;
|
|
int maxnamelen;
|
|
int maxsizelen;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
|
|
if (NO_MODULES())
|
|
return;
|
|
|
|
switch (flag)
|
|
{
|
|
case LIST_MODULE:
|
|
case LIST_MODULE_HDR:
|
|
maxnamelen = maxsizelen = 0;
|
|
|
|
for (i = 0; i < kt->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
maxnamelen = strlen(lm->mod_name) > maxnamelen ?
|
|
strlen(lm->mod_name) : maxnamelen;
|
|
|
|
sprintf(buf1, "%ld", lm->mod_size);
|
|
maxsizelen = strlen(buf1) > maxsizelen ?
|
|
strlen(buf1) : maxsizelen;
|
|
}
|
|
|
|
if (flag == LIST_MODULE_HDR) {
|
|
fprintf(fp, "%s %s %s OBJECT FILE\n",
|
|
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST,
|
|
"MODULE"),
|
|
mkstring(buf2, maxnamelen, LJUST, "NAME"),
|
|
mkstring(buf3, maxsizelen, RJUST, "SIZE"));
|
|
}
|
|
|
|
for (i = 0; i < kt->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
if (!address || (lm->module_struct == address) ||
|
|
(lm->mod_base == address)) {
|
|
fprintf(fp, "%s ", mkstring(buf1, VADDR_PRLEN,
|
|
LONG_HEX|RJUST, MKSTR(lm->module_struct)));
|
|
fprintf(fp, "%s ", mkstring(buf2, maxnamelen,
|
|
LJUST, lm->mod_name));
|
|
fprintf(fp, "%s ", mkstring(buf3, maxsizelen,
|
|
RJUST|LONG_DEC, MKSTR(lm->mod_size)));
|
|
// fprintf(fp, "%6ld ", lm->mod_size);
|
|
|
|
if (strlen(lm->mod_namelist))
|
|
fprintf(fp, "%s %s",
|
|
lm->mod_namelist,
|
|
lm->mod_flags & MOD_REMOTE ?
|
|
" (temporary)" : "");
|
|
else {
|
|
fprintf(fp, "(not loaded)");
|
|
if (lm->mod_flags & MOD_KALLSYMS)
|
|
fprintf(fp,
|
|
" [CONFIG_KALLSYMS]");
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
break;
|
|
|
|
case REMOTE_MODULE_SAVE_MSG:
|
|
if (!REMOTE())
|
|
return;
|
|
|
|
for (i = j = 0, lmp = NULL; i < kt->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
if (lm->mod_flags & MOD_REMOTE) {
|
|
j++;
|
|
lmp = lm;
|
|
}
|
|
}
|
|
|
|
switch (j)
|
|
{
|
|
case 0:
|
|
return;
|
|
|
|
case 1:
|
|
error(NOTE,
|
|
"\nTo save the %s module object locally,\n enter: \"save %s\"\n",
|
|
lmp->mod_name, lmp->mod_name);
|
|
break;
|
|
|
|
default:
|
|
error(NOTE,
|
|
"\nTo save all temporary remote module objects locally,\n enter: \"save modules\"\n");
|
|
fprintf(fp,
|
|
" To save a single remote module object locally,\n enter: \"save NAME\",\n"
|
|
" where \"NAME\" is one of the module names shown in the list above.\n");
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case LOAD_SPECIFIED_MODULE_SYMBOLS:
|
|
if (!load_module_symbols(modref, objfile, address))
|
|
error(FATAL, "cannot load symbols from: %s\n", objfile);
|
|
do_module_cmd(LIST_MODULE_HDR, 0, address, 0, NULL);
|
|
do_module_cmd(REMOTE_MODULE_SAVE_MSG, 0, 0, 0, NULL);
|
|
break;
|
|
|
|
case LOAD_ALL_MODULE_SYMBOLS:
|
|
for (i = j = 0; i < kt->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
|
|
if (STREQ(lm->mod_name, "(unknown module)")) {
|
|
error(INFO,
|
|
"cannot find object file for unknown module at %lx\n",
|
|
lm->mod_base);
|
|
continue;
|
|
}
|
|
|
|
modref = lm->mod_name;
|
|
address = lm->mod_base;
|
|
|
|
if ((objfile = find_module_objfile(modref,NULL,tree))) {
|
|
if (!is_elf_file(objfile)) {
|
|
error(INFO,
|
|
"%s: not an ELF format object file\n",
|
|
objfile);
|
|
} else if (!load_module_symbols(modref,
|
|
objfile, address))
|
|
error(INFO,
|
|
"cannot load symbols from: %s\n",
|
|
objfile);
|
|
do_module_cmd(j++ ?
|
|
LIST_MODULE : LIST_MODULE_HDR,
|
|
0, address, 0, tree);
|
|
FREEBUF(objfile);
|
|
} else if ((lm->mod_flags & MOD_LOAD_SYMS) ||
|
|
strlen(lm->mod_namelist)) {
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp,
|
|
"%s: module symbols are already loaded\n",
|
|
modref);
|
|
do_module_cmd(j++ ?
|
|
LIST_MODULE : LIST_MODULE_HDR,
|
|
0, address, 0, tree);
|
|
} else
|
|
error(INFO,
|
|
"cannot find or load object file for %s module\n",
|
|
modref);
|
|
}
|
|
do_module_cmd(REMOTE_MODULE_SAVE_MSG, 0, 0, 0, tree);
|
|
break;
|
|
|
|
case DELETE_ALL_MODULE_SYMBOLS:
|
|
delete_load_module(ALL_MODULES);
|
|
break;
|
|
|
|
case DELETE_MODULE_SYMBOLS:
|
|
delete_load_module(address);
|
|
break;
|
|
|
|
case REINIT_MODULES:
|
|
reinit_modules();
|
|
do_module_cmd(LIST_MODULE_HDR, NULL, 0, NULL, NULL);
|
|
break;
|
|
|
|
case LIST_ALL_MODULE_TAINT:
|
|
show_module_taint();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Reinitialize the current set of modules:
|
|
*
|
|
* 1. first clear out all references to the current set.
|
|
* 2. call module_init() again.
|
|
* 3. display the new set.
|
|
*/
|
|
static void
|
|
reinit_modules(void)
|
|
{
|
|
delete_load_module(ALL_MODULES);
|
|
st->mods_installed = 0;
|
|
st->flags &= ~MODULE_SYMS;
|
|
free(st->ext_module_symtable);
|
|
free(st->load_modules);
|
|
st->ext_module_symtable = NULL;
|
|
st->load_modules = NULL;
|
|
kt->mods_installed = 0;
|
|
clear_text_value_cache();
|
|
|
|
module_init();
|
|
}
|
|
|
|
|
|
static char *
|
|
module_objfile_search(char *modref, char *filename, char *tree)
|
|
{
|
|
char buf[BUFSIZE];
|
|
char file[BUFSIZE];
|
|
char dir[BUFSIZE];
|
|
struct load_module *lm;
|
|
char *retbuf;
|
|
int initrd;
|
|
struct syment *sp;
|
|
char *p1, *p2;
|
|
char *env;
|
|
char *namelist;
|
|
|
|
retbuf = NULL;
|
|
initrd = FALSE;
|
|
|
|
if (filename)
|
|
strcpy(file, filename);
|
|
#ifdef MODULES_IN_CWD
|
|
else {
|
|
char *fileext[] = { "ko", "o"};
|
|
int i;
|
|
for (i = 0; i < 2; i++) {
|
|
sprintf(file, "%s.%s", modref, fileext[i]);
|
|
if (access(file, R_OK) == 0) {
|
|
retbuf = GETBUF(strlen(file)+1);
|
|
strcpy(retbuf, file);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp,
|
|
"find_module_objfile: [%s] file in cwd\n",
|
|
retbuf);
|
|
return retbuf;
|
|
}
|
|
}
|
|
}
|
|
#else
|
|
else
|
|
sprintf(file, "%s.o", modref);
|
|
#endif
|
|
|
|
/*
|
|
* Later versions of insmod create a symbol at the module's base
|
|
* address. Examples:
|
|
*
|
|
* __insmod_sunrpc_O/lib/modules/2.2.17/misc/sunrpc.o_M3A7EE300_V131601
|
|
* __insmod_lockd_O/lib/modules/2.2.17/fs/lockd.o_M3A7EE300_V131601
|
|
* __insmod_nfsd_O/lib/modules/2.2.17/fs/nfsd.o_M3A7EE300_V131601
|
|
* __insmod_nfs_O/lib/modules/2.2.17/fs/nfs.o_M3A7EE300_V131601
|
|
*/
|
|
if ((st->flags & INSMOD_BUILTIN) && !filename) {
|
|
sprintf(buf, "__insmod_%s_O/", modref);
|
|
if (symbol_query(buf, NULL, &sp) == 1) {
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "search: INSMOD_BUILTIN %s\n", sp->name);
|
|
BZERO(buf, BUFSIZE);
|
|
p1 = strstr(sp->name, "/");
|
|
if ((p2 = strstr(sp->name, file)))
|
|
p2 += strlen(file);
|
|
if (p2) {
|
|
strncpy(buf, p1, p2-p1);
|
|
if (!strstr(buf, "/lib/modules/")) {
|
|
sprintf(dir, "/lib/%s.o", modref);
|
|
if (STREQ(dir, buf))
|
|
initrd = TRUE;
|
|
} else if (REMOTE())
|
|
strcpy(file, buf);
|
|
else {
|
|
retbuf = GETBUF(strlen(buf)+1);
|
|
strcpy(retbuf, buf);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp,
|
|
"find_module_objfile: [%s]\n",
|
|
retbuf);
|
|
return retbuf;
|
|
}
|
|
}
|
|
}
|
|
if (is_module_name(modref, NULL, &lm) &&
|
|
(lm->mod_flags & MOD_INITRD)) {
|
|
sprintf(dir, "/lib/%s.o", modref);
|
|
initrd = TRUE;
|
|
}
|
|
}
|
|
|
|
if (initrd)
|
|
error(NOTE, "%s: installed from initrd image\n", dir);
|
|
|
|
if (REMOTE()) {
|
|
retbuf = GETBUF(MAX_MOD_NAMELIST*2);
|
|
|
|
if (!is_module_name(modref, NULL, &lm)) {
|
|
error(INFO, "%s is not a module reference\n", modref);
|
|
return NULL;
|
|
}
|
|
|
|
if ((lm->mod_flags & MOD_LOAD_SYMS) &&
|
|
strlen(lm->mod_namelist)) {
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "redundant mod call: %s\n",
|
|
lm->mod_namelist);
|
|
strcpy(retbuf, lm->mod_namelist);
|
|
return retbuf;
|
|
}
|
|
|
|
if (find_remote_module_objfile(lm, file, retbuf))
|
|
return retbuf;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
if (tree) {
|
|
if (!(retbuf = search_directory_tree(tree, file, 1))) {
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V2:
|
|
sprintf(file, "%s.ko", modref);
|
|
retbuf = search_directory_tree(tree, file, 1);
|
|
if (!retbuf) {
|
|
sprintf(file, "%s.ko.debug", modref);
|
|
retbuf = search_directory_tree(tree, file, 1);
|
|
}
|
|
}
|
|
}
|
|
return retbuf;
|
|
}
|
|
|
|
sprintf(dir, "%s/%s", DEFAULT_REDHAT_DEBUG_LOCATION,
|
|
kt->utsname.release);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
|
|
if (!retbuf && (env = getenv("CRASH_MODULE_PATH"))) {
|
|
sprintf(dir, "%s", env);
|
|
if (!(retbuf = search_directory_tree(dir, file, 0))) {
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V2:
|
|
sprintf(file, "%s.ko", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
if (!retbuf) {
|
|
sprintf(file, "%s.ko.debug", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!retbuf) {
|
|
sprintf(dir, "/lib/modules/%s/updates", kt->utsname.release);
|
|
if (!(retbuf = search_directory_tree(dir, file, 0))) {
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V2:
|
|
sprintf(file, "%s.ko", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!retbuf) {
|
|
sprintf(dir, "/lib/modules/%s", kt->utsname.release);
|
|
if (!(retbuf = search_directory_tree(dir, file, 0))) {
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V2:
|
|
sprintf(file, "%s.ko", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!retbuf && !filename && !tree && kt->module_tree) {
|
|
sprintf(dir, "%s", kt->module_tree);
|
|
if (!(retbuf = search_directory_tree(dir, file, 0))) {
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V2:
|
|
sprintf(file, "%s.ko", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
if (!retbuf) {
|
|
sprintf(file, "%s.ko.debug", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check the directory tree where the vmlinux file is located.
|
|
*/
|
|
if (!retbuf &&
|
|
(namelist = realpath(pc->namelist_orig ?
|
|
pc->namelist_orig : pc->namelist, NULL))) {
|
|
sprintf(dir, "%s", dirname(namelist));
|
|
if (!(retbuf = search_directory_tree(dir, file, 0))) {
|
|
switch (kt->flags & (KMOD_V1|KMOD_V2))
|
|
{
|
|
case KMOD_V2:
|
|
sprintf(file, "%s.ko", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
if (!retbuf) {
|
|
sprintf(file, "%s.ko.debug", modref);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
}
|
|
}
|
|
}
|
|
free(namelist);
|
|
}
|
|
|
|
if (!retbuf && is_livepatch()) {
|
|
sprintf(file, "%s.ko", modref);
|
|
sprintf(dir, "/usr/lib/kpatch/%s", kt->utsname.release);
|
|
if (!(retbuf = search_directory_tree(dir, file, 0))) {
|
|
sprintf(file, "%s.ko.debug", modref);
|
|
sprintf(dir, "/usr/lib/debug/usr/lib/kpatch/%s",
|
|
kt->utsname.release);
|
|
retbuf = search_directory_tree(dir, file, 0);
|
|
}
|
|
}
|
|
|
|
return retbuf;
|
|
}
|
|
|
|
/*
|
|
* First look for a module based upon its reference name.
|
|
* If that fails, try replacing any underscores in the
|
|
* reference name with a dash.
|
|
* If that fails, because of intermingled dashes and underscores,
|
|
* try a regex expression.
|
|
*
|
|
* Example: module name "dm_mod" comes from "dm-mod.ko" objfile
|
|
* module name "dm_region_hash" comes from "dm-region_hash.ko" objfile
|
|
*/
|
|
static char *
|
|
find_module_objfile(char *modref, char *filename, char *tree)
|
|
{
|
|
char * retbuf;
|
|
char tmpref[BUFSIZE];
|
|
int i, c;
|
|
|
|
retbuf = module_objfile_search(modref, filename, tree);
|
|
|
|
if (!retbuf) {
|
|
strncpy(tmpref, modref, BUFSIZE);
|
|
for (c = 0; c < BUFSIZE && tmpref[c]; c++)
|
|
if (tmpref[c] == '_')
|
|
tmpref[c] = '-';
|
|
retbuf = module_objfile_search(tmpref, filename, tree);
|
|
}
|
|
|
|
if (!retbuf && (count_chars(modref, '_') > 1)) {
|
|
for (i = c = 0; modref[i]; i++) {
|
|
if (modref[i] == '_') {
|
|
tmpref[c++] = '[';
|
|
tmpref[c++] = '_';
|
|
tmpref[c++] = '-';
|
|
tmpref[c++] = ']';
|
|
} else
|
|
tmpref[c++] = modref[i];
|
|
}
|
|
tmpref[c] = NULLCHAR;
|
|
retbuf = module_objfile_search(tmpref, filename, tree);
|
|
}
|
|
|
|
return retbuf;
|
|
}
|
|
|
|
/*
|
|
* Try to load module symbols with name.
|
|
*/
|
|
int
|
|
load_module_symbols_helper(char *name)
|
|
{
|
|
char *objfile;
|
|
ulong address;
|
|
|
|
if (is_module_name(name, &address, NULL) &&
|
|
(objfile = find_module_objfile(name, NULL, NULL))) {
|
|
do_module_cmd(LOAD_SPECIFIED_MODULE_SYMBOLS, name, address,
|
|
objfile, NULL);
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Unlink any temporary remote module object files.
|
|
*/
|
|
void
|
|
unlink_module(struct load_module *load_module)
|
|
{
|
|
int i;
|
|
struct load_module *lm;
|
|
|
|
if (load_module) {
|
|
if (load_module->mod_flags & MOD_REMOTE)
|
|
unlink(load_module->mod_namelist);
|
|
return;
|
|
}
|
|
|
|
for (i = 0; i < kt->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
if (lm->mod_flags & MOD_REMOTE)
|
|
unlink(lm->mod_namelist);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Dump the kernel log_buf in chronological order.
|
|
*/
|
|
|
|
void
|
|
cmd_log(void)
|
|
{
|
|
int c;
|
|
int msg_flags;
|
|
|
|
msg_flags = 0;
|
|
|
|
while ((c = getopt(argcnt, args, "tdm")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 't':
|
|
msg_flags |= SHOW_LOG_TEXT;
|
|
break;
|
|
case 'd':
|
|
msg_flags |= SHOW_LOG_DICT;
|
|
break;
|
|
case 'm':
|
|
msg_flags |= SHOW_LOG_LEVEL;
|
|
break;
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
dump_log(msg_flags);
|
|
}
|
|
|
|
|
|
void
|
|
dump_log(int msg_flags)
|
|
{
|
|
int i, len, tmp, show_level;
|
|
ulong log_buf, log_end;
|
|
char *buf;
|
|
char last;
|
|
ulong index;
|
|
struct syment *nsp;
|
|
int log_wrap, loglevel, log_buf_len;
|
|
|
|
if (kernel_symbol_exists("log_first_idx") &&
|
|
kernel_symbol_exists("log_next_idx")) {
|
|
dump_variable_length_record_log(msg_flags);
|
|
return;
|
|
}
|
|
|
|
if (msg_flags & SHOW_LOG_DICT)
|
|
option_not_supported('d');
|
|
if ((msg_flags & SHOW_LOG_TEXT) && STREQ(pc->curcmd, "log"))
|
|
option_not_supported('t');
|
|
|
|
show_level = msg_flags & SHOW_LOG_LEVEL ? TRUE : FALSE;
|
|
|
|
if (symbol_exists("log_buf_len")) {
|
|
get_symbol_data("log_buf_len", sizeof(int), &log_buf_len);
|
|
get_symbol_data("log_buf", sizeof(ulong), &log_buf);
|
|
} else {
|
|
if ((ARRAY_LENGTH(log_buf) == 0) &&
|
|
(get_array_length("log_buf", NULL, 0) == 0)) {
|
|
if ((nsp = next_symbol("log_buf", NULL)) == NULL)
|
|
error(FATAL,
|
|
"cannot determine length of log_buf\n");
|
|
builtin_array_length("log_buf",
|
|
(int)(nsp->value - symbol_value("log_buf")),
|
|
NULL);
|
|
}
|
|
|
|
log_buf_len = ARRAY_LENGTH(log_buf);
|
|
log_buf = symbol_value("log_buf");
|
|
}
|
|
|
|
buf = GETBUF(log_buf_len);
|
|
log_wrap = FALSE;
|
|
last = 0;
|
|
if ((len = get_symbol_length("log_end")) == sizeof(int)) {
|
|
get_symbol_data("log_end", len, &tmp);
|
|
log_end = (ulong)tmp;
|
|
} else
|
|
get_symbol_data("log_end", len, &log_end);
|
|
|
|
if (!readmem(log_buf, KVADDR, buf,
|
|
log_buf_len, "log_buf contents", RETURN_ON_ERROR|QUIET)) {
|
|
error(WARNING, "\ncannot read log_buf contents\n");
|
|
return;
|
|
}
|
|
|
|
if (log_end < log_buf_len)
|
|
index = 0;
|
|
else
|
|
index = log_end & (log_buf_len - 1);
|
|
|
|
if ((log_end < log_buf_len) && (index == 0) && (buf[index] == '<'))
|
|
loglevel = TRUE;
|
|
else
|
|
loglevel = FALSE;
|
|
|
|
if (index != 0)
|
|
log_wrap = TRUE;
|
|
|
|
wrap_around:
|
|
|
|
for (i = index; i < log_buf_len; i++) {
|
|
if (loglevel && !show_level) {
|
|
switch (buf[i])
|
|
{
|
|
case '>':
|
|
loglevel = FALSE;
|
|
/* FALLTHROUGH */
|
|
case '<':
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
continue;
|
|
|
|
default:
|
|
loglevel = FALSE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (buf[i]) {
|
|
fputc(ascii(buf[i]) ? buf[i] : '.', fp);
|
|
loglevel = buf[i] == '\n' ? TRUE : FALSE;
|
|
last = buf[i];
|
|
}
|
|
}
|
|
|
|
if (log_wrap) {
|
|
log_buf_len = index;
|
|
index = 0;
|
|
log_wrap = FALSE;
|
|
goto wrap_around;
|
|
}
|
|
|
|
if (last != '\n')
|
|
fprintf(fp, "\n");
|
|
|
|
FREEBUF(buf);
|
|
}
|
|
|
|
/*
|
|
* get log record by index; idx must point to valid message.
|
|
*/
|
|
static char *
|
|
log_from_idx(uint32_t idx, char *logbuf)
|
|
{
|
|
char *logptr;
|
|
uint16_t msglen;
|
|
|
|
logptr = logbuf + idx;
|
|
|
|
/*
|
|
* A length == 0 record is the end of buffer marker.
|
|
* Wrap around and return the message at the start of
|
|
* the buffer.
|
|
*/
|
|
|
|
msglen = USHORT(logptr + OFFSET(log_len));
|
|
if (!msglen)
|
|
logptr = logbuf;
|
|
|
|
return logptr;
|
|
}
|
|
|
|
/*
|
|
* get next record index; idx must point to valid message.
|
|
*/
|
|
static uint32_t
|
|
log_next(uint32_t idx, char *logbuf)
|
|
{
|
|
char *logptr;
|
|
uint16_t msglen;
|
|
|
|
logptr = logbuf + idx;
|
|
|
|
/*
|
|
* A length == 0 record is the end of buffer marker. Wrap around and
|
|
* read the message at the start of the buffer as *this* one, and
|
|
* return the one after that.
|
|
*/
|
|
|
|
msglen = USHORT(logptr + OFFSET(log_len));
|
|
if (!msglen) {
|
|
msglen = USHORT(logbuf + OFFSET(log_len));
|
|
return msglen;
|
|
}
|
|
|
|
return idx + msglen;
|
|
}
|
|
|
|
static void
|
|
dump_log_entry(char *logptr, int msg_flags)
|
|
{
|
|
int indent;
|
|
char *msg, *p;
|
|
uint16_t i, text_len, dict_len, level;
|
|
uint64_t ts_nsec;
|
|
ulonglong nanos;
|
|
ulong rem;
|
|
char buf[BUFSIZE];
|
|
int ilen;
|
|
|
|
ilen = level = 0;
|
|
text_len = USHORT(logptr + OFFSET(log_text_len));
|
|
dict_len = USHORT(logptr + OFFSET(log_dict_len));
|
|
if (VALID_MEMBER(log_level)) {
|
|
/*
|
|
* Initially a "u16 level", then a "u8 level:3"
|
|
*/
|
|
if (SIZE(log_level) == sizeof(short))
|
|
level = USHORT(logptr + OFFSET(log_level));
|
|
else
|
|
level = UCHAR(logptr + OFFSET(log_level));
|
|
} else {
|
|
if (VALID_MEMBER(log_flags_level))
|
|
level = UCHAR(logptr + OFFSET(log_flags_level));
|
|
else if (msg_flags & SHOW_LOG_LEVEL)
|
|
msg_flags &= ~SHOW_LOG_LEVEL;
|
|
}
|
|
ts_nsec = ULONGLONG(logptr + OFFSET(log_ts_nsec));
|
|
|
|
msg = logptr + SIZE(log);
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp,
|
|
"\nlog %lx -> msg: %lx ts_nsec: %lld flags/level: %x"
|
|
" text_len: %d dict_len: %d\n",
|
|
(ulong)logptr, (ulong)msg, (ulonglong)ts_nsec,
|
|
level, text_len, dict_len);
|
|
|
|
if ((msg_flags & SHOW_LOG_TEXT) == 0) {
|
|
nanos = (ulonglong)ts_nsec / (ulonglong)1000000000;
|
|
rem = (ulonglong)ts_nsec % (ulonglong)1000000000;
|
|
sprintf(buf, "[%5lld.%06ld] ", nanos, rem/1000);
|
|
ilen = strlen(buf);
|
|
fprintf(fp, "%s", buf);
|
|
}
|
|
|
|
if (msg_flags & SHOW_LOG_LEVEL) {
|
|
sprintf(buf, "<%x>", level);
|
|
ilen += strlen(buf);
|
|
fprintf(fp, "%s", buf);
|
|
}
|
|
|
|
for (i = 0, p = msg; i < text_len; i++, p++) {
|
|
if (*p == '\n')
|
|
fprintf(fp, "\n%s", space(ilen));
|
|
else if (isprint(*p) || isspace(*p))
|
|
fputc(*p, fp);
|
|
else
|
|
fputc('.', fp);
|
|
}
|
|
|
|
if (dict_len & (msg_flags & SHOW_LOG_DICT)) {
|
|
fprintf(fp, "\n");
|
|
indent = TRUE;
|
|
|
|
for (i = 0; i < dict_len; i++, p++) {
|
|
if (indent) {
|
|
fprintf(fp, "%s", space(ilen));
|
|
indent = FALSE;
|
|
}
|
|
if (isprint(*p))
|
|
fputc(*p, fp);
|
|
else if (*p == NULLCHAR) {
|
|
fputc('\n', fp);
|
|
indent = TRUE;
|
|
} else
|
|
fputc('.', fp);
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* Handle the new variable-length-record log_buf.
|
|
*/
|
|
static void
|
|
dump_variable_length_record_log(int msg_flags)
|
|
{
|
|
uint32_t idx, log_first_idx, log_next_idx, log_buf_len;
|
|
ulong log_buf;
|
|
char *logptr, *logbuf, *log_struct_name;
|
|
|
|
if (INVALID_SIZE(log)) {
|
|
if (STRUCT_EXISTS("printk_log")) {
|
|
/*
|
|
* In kernel 3.11 the log structure name was renamed
|
|
* from log to printk_log. See 62e32ac3505a0cab.
|
|
*/
|
|
log_struct_name = "printk_log";
|
|
} else
|
|
log_struct_name = "log";
|
|
|
|
STRUCT_SIZE_INIT(log, log_struct_name);
|
|
MEMBER_OFFSET_INIT(log_ts_nsec, log_struct_name, "ts_nsec");
|
|
MEMBER_OFFSET_INIT(log_len, log_struct_name, "len");
|
|
MEMBER_OFFSET_INIT(log_text_len, log_struct_name, "text_len");
|
|
MEMBER_OFFSET_INIT(log_dict_len, log_struct_name, "dict_len");
|
|
MEMBER_OFFSET_INIT(log_level, log_struct_name, "level");
|
|
MEMBER_SIZE_INIT(log_level, log_struct_name, "level");
|
|
MEMBER_OFFSET_INIT(log_flags_level, log_struct_name, "flags_level");
|
|
|
|
/*
|
|
* If things change, don't kill a dumpfile session
|
|
* searching for a panic message.
|
|
*/
|
|
if (INVALID_SIZE(log) ||
|
|
INVALID_MEMBER(log_ts_nsec) ||
|
|
INVALID_MEMBER(log_len) ||
|
|
INVALID_MEMBER(log_text_len) ||
|
|
INVALID_MEMBER(log_dict_len) ||
|
|
(INVALID_MEMBER(log_level) && INVALID_MEMBER(log_flags_level)) ||
|
|
!kernel_symbol_exists("log_buf_len") ||
|
|
!kernel_symbol_exists("log_buf")) {
|
|
error(WARNING, "\nlog buf data structure(s) have changed\n");
|
|
return;
|
|
}
|
|
}
|
|
|
|
get_symbol_data("log_first_idx", sizeof(uint32_t), &log_first_idx);
|
|
get_symbol_data("log_next_idx", sizeof(uint32_t), &log_next_idx);
|
|
get_symbol_data("log_buf_len", sizeof(uint32_t), &log_buf_len);
|
|
get_symbol_data("log_buf", sizeof(char *), &log_buf);
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "log_buf: %lx\n", (ulong)log_buf);
|
|
fprintf(fp, "log_buf_len: %d\n", log_buf_len);
|
|
fprintf(fp, "log_first_idx: %d\n", log_first_idx);
|
|
fprintf(fp, "log_next_idx: %d\n", log_next_idx);
|
|
}
|
|
|
|
logbuf = GETBUF(log_buf_len);
|
|
|
|
if (!readmem(log_buf, KVADDR, logbuf,
|
|
log_buf_len, "log_buf contents", RETURN_ON_ERROR|QUIET)) {
|
|
error(WARNING, "\ncannot read log_buf contents\n");
|
|
FREEBUF(logbuf);
|
|
return;
|
|
}
|
|
|
|
hq_open();
|
|
|
|
idx = log_first_idx;
|
|
while (idx != log_next_idx) {
|
|
logptr = log_from_idx(idx, logbuf);
|
|
|
|
dump_log_entry(logptr, msg_flags);
|
|
|
|
if (!hq_enter((ulong)logptr)) {
|
|
error(INFO, "\nduplicate log_buf message pointer\n");
|
|
break;
|
|
}
|
|
|
|
idx = log_next(idx, logbuf);
|
|
|
|
if (idx >= log_buf_len) {
|
|
error(INFO, "\ninvalid log_buf entry encountered\n");
|
|
break;
|
|
}
|
|
|
|
if (CRASHDEBUG(1) && (idx == log_next_idx))
|
|
fprintf(fp, "\nfound log_next_idx OK\n");
|
|
}
|
|
|
|
hq_close();
|
|
|
|
FREEBUF(logbuf);
|
|
}
|
|
|
|
|
|
/*
|
|
* Display general system info.
|
|
*/
|
|
void
|
|
cmd_sys(void)
|
|
{
|
|
int c, cnt;
|
|
ulong sflag;
|
|
char buf[BUFSIZE];
|
|
|
|
sflag = FALSE;
|
|
|
|
while ((c = getopt(argcnt, args, "ctp:")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'p':
|
|
if (STREQ(optarg, "anic"))
|
|
panic_this_kernel();
|
|
else
|
|
argerrs++;
|
|
break;
|
|
|
|
case 'c':
|
|
sflag = TRUE;
|
|
break;
|
|
|
|
case 't':
|
|
show_kernel_taints(buf, VERBOSE);
|
|
return;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (!args[optind]) {
|
|
if (sflag)
|
|
dump_sys_call_table(NULL, 0);
|
|
else
|
|
display_sys_stats();
|
|
return;
|
|
}
|
|
|
|
cnt = 0;
|
|
do {
|
|
if (sflag)
|
|
dump_sys_call_table(args[optind], cnt++);
|
|
else if (STREQ(args[optind], "config"))
|
|
read_in_kernel_config(IKCFG_READ);
|
|
else
|
|
cmd_usage(args[optind], COMPLETE_HELP);
|
|
optind++;
|
|
} while (args[optind]);
|
|
}
|
|
|
|
static int
|
|
is_livepatch(void)
|
|
{
|
|
int i;
|
|
struct load_module *lm;
|
|
char buf[BUFSIZE];
|
|
|
|
show_kernel_taints(buf, !VERBOSE);
|
|
if (strstr(buf, "K")) /* TAINT_LIVEPATCH */
|
|
return TRUE;
|
|
|
|
for (i = 0; i < st->mods_installed; i++) {
|
|
lm = &st->load_modules[i];
|
|
if (STREQ("kpatch", lm->mod_name))
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Display system stats at init-time or for the sys command.
|
|
*/
|
|
void
|
|
display_sys_stats(void)
|
|
{
|
|
struct new_utsname *uts;
|
|
char buf[BUFSIZE];
|
|
ulong mhz;
|
|
|
|
uts = &kt->utsname;
|
|
|
|
// if (!(pc->flags & RUNTIME) && !DUMPFILE() && !GDB_PATCHED())
|
|
// fprintf(fp, "\n");
|
|
|
|
/*
|
|
* It's now safe to unlink the remote namelist.
|
|
*/
|
|
if (pc->flags & UNLINK_NAMELIST) {
|
|
unlink(pc->namelist);
|
|
pc->flags &= ~UNLINK_NAMELIST;
|
|
pc->flags |= NAMELIST_UNLINKED;
|
|
}
|
|
|
|
if (REMOTE()) {
|
|
switch (pc->flags &
|
|
(NAMELIST_LOCAL|NAMELIST_UNLINKED|NAMELIST_SAVED))
|
|
{
|
|
case NAMELIST_UNLINKED:
|
|
fprintf(fp, " KERNEL: %s (temporary)\n",
|
|
pc->namelist);
|
|
break;
|
|
|
|
case (NAMELIST_UNLINKED|NAMELIST_SAVED):
|
|
fprintf(fp, " KERNEL: %s\n", pc->namelist);
|
|
break;
|
|
|
|
case NAMELIST_LOCAL:
|
|
fprintf(fp, " KERNEL: %s\n", pc->namelist);
|
|
break;
|
|
}
|
|
} else {
|
|
if (pc->system_map) {
|
|
fprintf(fp, " SYSTEM MAP: %s%s\n", pc->system_map,
|
|
is_livepatch() ? " [LIVEPATCH]" : "");
|
|
fprintf(fp, "DEBUG KERNEL: %s %s\n",
|
|
pc->namelist_orig ?
|
|
pc->namelist_orig : pc->namelist,
|
|
debug_kernel_version(pc->namelist));
|
|
} else
|
|
fprintf(fp, " KERNEL: %s%s\n", pc->namelist_orig ?
|
|
pc->namelist_orig : pc->namelist,
|
|
is_livepatch() ? " [LIVEPATCH]" : "");
|
|
}
|
|
|
|
if (pc->debuginfo_file) {
|
|
if (STREQ(pc->debuginfo_file, pc->namelist_debug) &&
|
|
pc->namelist_debug_orig)
|
|
fprintf(fp, " DEBUGINFO: %s\n",
|
|
pc->namelist_debug_orig);
|
|
else
|
|
fprintf(fp, " DEBUGINFO: %s\n", pc->debuginfo_file);
|
|
} else if (pc->namelist_debug)
|
|
fprintf(fp, "DEBUG KERNEL: %s %s\n", pc->namelist_debug_orig ?
|
|
pc->namelist_debug_orig : pc->namelist_debug,
|
|
debug_kernel_version(pc->namelist_debug));
|
|
|
|
/*
|
|
* After the initial banner display, we no longer need the
|
|
* temporary namelist file(s).
|
|
*/
|
|
if (!(pc->flags & RUNTIME)) {
|
|
if (pc->namelist_orig)
|
|
unlink(pc->namelist);
|
|
if (pc->namelist_debug_orig)
|
|
unlink(pc->namelist_debug);
|
|
}
|
|
|
|
if (dumpfile_is_split() || sadump_is_diskset() || is_ramdump_image())
|
|
fprintf(fp, " DUMPFILES: ");
|
|
else
|
|
fprintf(fp, " DUMPFILE: ");
|
|
if (ACTIVE()) {
|
|
if (REMOTE_ACTIVE())
|
|
fprintf(fp, "%s@%s (remote live system)\n",
|
|
pc->server_memsrc, pc->server);
|
|
else
|
|
fprintf(fp, "%s\n", pc->live_memsrc);
|
|
} else {
|
|
if (REMOTE_DUMPFILE())
|
|
fprintf(fp, "%s@%s (remote dumpfile)",
|
|
pc->server_memsrc, pc->server);
|
|
else if (REMOTE_PAUSED())
|
|
fprintf(fp, "%s %s (remote paused system)\n",
|
|
pc->server_memsrc, pc->server);
|
|
else {
|
|
if (dumpfile_is_split())
|
|
show_split_dumpfiles();
|
|
else if (sadump_is_diskset())
|
|
sadump_show_diskset();
|
|
else if (is_ramdump_image())
|
|
show_ramdump_files();
|
|
else
|
|
fprintf(fp, "%s", pc->dumpfile);
|
|
}
|
|
|
|
if (LIVE())
|
|
fprintf(fp, " [LIVE DUMP]");
|
|
|
|
if (NETDUMP_DUMPFILE() && is_partial_netdump())
|
|
fprintf(fp, " [PARTIAL DUMP]");
|
|
|
|
if (KDUMP_DUMPFILE() && is_incomplete_dump())
|
|
fprintf(fp, " [INCOMPLETE]");
|
|
|
|
if (DISKDUMP_DUMPFILE() && !dumpfile_is_split() &&
|
|
(is_partial_diskdump() || is_incomplete_dump())) {
|
|
fprintf(fp, " %s%s",
|
|
is_partial_diskdump() ?
|
|
" [PARTIAL DUMP]" : "",
|
|
is_incomplete_dump() ?
|
|
" [INCOMPLETE]" : "");
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
if (KVMDUMP_DUMPFILE() && pc->kvmdump_mapfile)
|
|
fprintf(fp, " MAPFILE: %s\n",
|
|
pc->kvmdump_mapfile);
|
|
}
|
|
|
|
if (machine_type("PPC64"))
|
|
fprintf(fp, " CPUS: %d\n", get_cpus_to_display());
|
|
else {
|
|
fprintf(fp, " CPUS: %d", kt->cpus);
|
|
if (kt->cpus - get_cpus_to_display())
|
|
fprintf(fp, " [OFFLINE: %d]",
|
|
kt->cpus - get_cpus_to_display());
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (ACTIVE())
|
|
get_xtime(&kt->date);
|
|
fprintf(fp, " DATE: %s\n",
|
|
strip_linefeeds(ctime(&kt->date.tv_sec)));
|
|
fprintf(fp, " UPTIME: %s\n", get_uptime(buf, NULL));
|
|
fprintf(fp, "LOAD AVERAGE: %s\n", get_loadavg(buf));
|
|
fprintf(fp, " TASKS: %ld\n", RUNNING_TASKS());
|
|
fprintf(fp, " NODENAME: %s\n", uts->nodename);
|
|
fprintf(fp, " RELEASE: %s\n", uts->release);
|
|
fprintf(fp, " VERSION: %s\n", uts->version);
|
|
fprintf(fp, " MACHINE: %s ", uts->machine);
|
|
if ((mhz = machdep->processor_speed()))
|
|
fprintf(fp, "(%ld Mhz)\n", mhz);
|
|
else
|
|
fprintf(fp, "(unknown Mhz)\n");
|
|
fprintf(fp, " MEMORY: %s\n", get_memory_size(buf));
|
|
#ifdef WHO_CARES
|
|
fprintf(fp, " DOMAINNAME: %s\n", uts->domainname);
|
|
#endif
|
|
if (XENDUMP_DUMPFILE() && (kt->xen_flags & XEN_SUSPEND))
|
|
return;
|
|
|
|
if (DUMPFILE()) {
|
|
fprintf(fp, " PANIC: ");
|
|
if (machdep->flags & HWRESET)
|
|
fprintf(fp, "(HARDWARE RESET)\n");
|
|
else if (machdep->flags & INIT)
|
|
fprintf(fp, "(INIT)\n");
|
|
else if (machdep->flags & MCA)
|
|
fprintf(fp, "(MCA)\n");
|
|
else {
|
|
strip_linefeeds(get_panicmsg(buf));
|
|
fprintf(fp, "\"%s\"%s\n", buf,
|
|
strstr(buf, "Oops: ") ?
|
|
" (check log for details)" : "");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the kernel version from the debug kernel and store it here.
|
|
*/
|
|
static char *debug_kernel_version_string = NULL;
|
|
|
|
static char *
|
|
debug_kernel_version(char *namelist)
|
|
{
|
|
FILE *pipe;
|
|
int argc;
|
|
char buf[BUFSIZE];
|
|
char command[BUFSIZE];
|
|
char *arglist[MAXARGS];
|
|
|
|
if (debug_kernel_version_string)
|
|
return debug_kernel_version_string;
|
|
|
|
sprintf(command, "/usr/bin/strings %s", namelist);
|
|
|
|
if ((pipe = popen(command, "r")) == NULL) {
|
|
debug_kernel_version_string = " ";
|
|
return debug_kernel_version_string;
|
|
}
|
|
|
|
argc = 0;
|
|
while (fgets(buf, BUFSIZE-1, pipe)) {
|
|
if (!strstr(buf, "Linux version 2.") &&
|
|
!strstr(buf, "Linux version 3.") &&
|
|
!strstr(buf, "Linux version 4.") &&
|
|
!strstr(buf, "Linux version 5."))
|
|
continue;
|
|
|
|
argc = parse_line(buf, arglist);
|
|
break;
|
|
}
|
|
pclose(pipe);
|
|
|
|
if ((argc >= 3) && (debug_kernel_version_string = (char *)
|
|
malloc(strlen(arglist[2])+3)))
|
|
sprintf(debug_kernel_version_string, "(%s)", arglist[2]);
|
|
else
|
|
debug_kernel_version_string = " ";
|
|
|
|
return debug_kernel_version_string;
|
|
}
|
|
|
|
/*
|
|
* Calculate and return the uptime.
|
|
*/
|
|
char *
|
|
get_uptime(char *buf, ulonglong *j64p)
|
|
{
|
|
ulong jiffies, tmp1, tmp2;
|
|
ulonglong jiffies_64, wrapped;
|
|
|
|
if (symbol_exists("jiffies_64")) {
|
|
get_symbol_data("jiffies_64", sizeof(ulonglong), &jiffies_64);
|
|
if (THIS_KERNEL_VERSION >= LINUX(2,6,0)) {
|
|
wrapped = (jiffies_64 & 0xffffffff00000000ULL);
|
|
if (wrapped) {
|
|
wrapped -= 0x100000000ULL;
|
|
jiffies_64 &= 0x00000000ffffffffULL;
|
|
jiffies_64 |= wrapped;
|
|
jiffies_64 += (ulonglong)(300*machdep->hz);
|
|
} else {
|
|
tmp1 = (ulong)(uint)(-300*machdep->hz);
|
|
tmp2 = (ulong)jiffies_64;
|
|
jiffies_64 = (ulonglong)(tmp2 - tmp1);
|
|
}
|
|
}
|
|
if (buf)
|
|
convert_time(jiffies_64, buf);
|
|
if (j64p)
|
|
*j64p = jiffies_64;
|
|
} else {
|
|
get_symbol_data("jiffies", sizeof(long), &jiffies);
|
|
if (buf)
|
|
convert_time((ulonglong)jiffies, buf);
|
|
if (j64p)
|
|
*j64p = (ulonglong)jiffies;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
#define FSHIFT 11 /* nr of bits of precision */
|
|
#define FIXED_1 (1<<FSHIFT)
|
|
#define LOAD_INT(x) ((x) >> FSHIFT)
|
|
#define LOAD_FRAC(x) LOAD_INT(((x) & (FIXED_1-1)) * 100)
|
|
|
|
static char *
|
|
get_loadavg(char *buf)
|
|
{
|
|
int a, b, c;
|
|
long avenrun[3];
|
|
|
|
readmem(symbol_value("avenrun"), KVADDR, &avenrun[0],
|
|
sizeof(long)*3, "avenrun array", FAULT_ON_ERROR);
|
|
|
|
a = avenrun[0] + (FIXED_1/200);
|
|
b = avenrun[1] + (FIXED_1/200);
|
|
c = avenrun[2] + (FIXED_1/200);
|
|
sprintf(buf, "%d.%02d, %d.%02d, %d.%02d",
|
|
LOAD_INT(a), LOAD_FRAC(a),
|
|
LOAD_INT(b), LOAD_FRAC(b),
|
|
LOAD_INT(c), LOAD_FRAC(c));
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Determine whether a string or value equates to a system call name or value.
|
|
*/
|
|
int
|
|
is_system_call(char *name, ulong value)
|
|
{
|
|
int i;
|
|
ulong *sys_call_table, *sct;
|
|
char *sp;
|
|
long size;
|
|
int NR_syscalls;
|
|
|
|
NR_syscalls = get_NR_syscalls(NULL);
|
|
size = sizeof(void *) * NR_syscalls;
|
|
sys_call_table = (ulong *)GETBUF(size);
|
|
|
|
readmem(symbol_value("sys_call_table"), KVADDR, sys_call_table,
|
|
size, "sys_call_table", FAULT_ON_ERROR);
|
|
|
|
for (i = 0, sct = sys_call_table; i < NR_syscalls; i++, sct++) {
|
|
if (name && (sp = value_symbol(*sct))) {
|
|
if (STREQ(name, sp))
|
|
return TRUE;
|
|
} else if (value) {
|
|
if (value == *sct)
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
char *sys_call_hdr = "NUM SYSTEM CALL FILE AND LINE NUMBER\n";
|
|
|
|
static void
|
|
dump_sys_call_table(char *spec, int cnt)
|
|
{
|
|
int i, confirmed;
|
|
char buf1[BUFSIZE], *scp;
|
|
char buf2[BUFSIZE], *p;
|
|
char buf3[BUFSIZE];
|
|
char *arglist[MAXARGS];
|
|
int argc, NR_syscalls;
|
|
int number, printit, hdr_printed;
|
|
struct syment *sp, *spn;
|
|
long size;
|
|
#ifdef S390X
|
|
unsigned int *sct, *sys_call_table, sys_ni_syscall, addr;
|
|
#else
|
|
ulong *sys_call_table, *sct, sys_ni_syscall, addr;
|
|
#endif
|
|
if (NO_LINE_NUMBERS())
|
|
error(INFO, "line numbers are not available\n");
|
|
|
|
NR_syscalls = get_NR_syscalls(&confirmed);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "NR_syscalls: %d (%sconfirmed)\n",
|
|
NR_syscalls, confirmed ? "" : "not ");
|
|
size = sizeof(addr) * NR_syscalls;
|
|
#ifdef S390X
|
|
sys_call_table = (unsigned int *)GETBUF(size);
|
|
#else
|
|
sys_call_table = (ulong *)GETBUF(size);
|
|
#endif
|
|
|
|
readmem(symbol_value("sys_call_table"), KVADDR, sys_call_table,
|
|
size, "sys_call_table", FAULT_ON_ERROR);
|
|
|
|
sys_ni_syscall = symbol_value("sys_ni_syscall");
|
|
|
|
if (spec)
|
|
open_tmpfile();
|
|
|
|
fprintf(fp, "%s", sys_call_hdr);
|
|
|
|
for (i = 0, sct = sys_call_table; i < NR_syscalls; i++, sct++) {
|
|
if (!(scp = value_symbol(*sct))) {
|
|
if (confirmed || CRASHDEBUG(1)) {
|
|
fprintf(fp, (*gdb_output_radix == 16) ?
|
|
"%3x " : "%3d ", i);
|
|
fprintf(fp,
|
|
"invalid sys_call_table entry: %lx ",
|
|
(unsigned long)*sct);
|
|
if (strlen(value_to_symstr(*sct, buf1, 0)))
|
|
fprintf(fp, "(%s)\n", buf1);
|
|
else
|
|
fprintf(fp, "\n");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
fprintf(fp, (*gdb_output_radix == 16) ? "%3x " : "%3d ", i);
|
|
if (sys_ni_syscall && *sct == sys_ni_syscall)
|
|
fprintf(fp, "%-26s ", "sys_ni_syscall");
|
|
else
|
|
fprintf(fp, "%-26s ", scp);
|
|
|
|
/*
|
|
* For system call symbols whose first instruction is
|
|
* an inline from a header file, the file/line-number is
|
|
* confusing. For this command only, look for the first
|
|
* instruction address in the system call that shows the
|
|
* the actual source file containing the system call.
|
|
*/
|
|
sp = value_search(*sct, NULL);
|
|
spn = next_symbol(NULL, sp);
|
|
get_build_directory(buf2);
|
|
|
|
for (addr = *sct; sp && spn && (addr < spn->value); addr++) {
|
|
BZERO(buf1, BUFSIZE);
|
|
get_line_number(addr, buf1, FALSE);
|
|
|
|
if (strstr(buf1, ".h: ") && strstr(buf1, "include/"))
|
|
continue;
|
|
|
|
if (strstr(buf1, buf2)) {
|
|
p = buf1 + strlen(buf2);
|
|
fprintf(fp, "%s%s",
|
|
strlen(buf1) ? ".." : "", p);
|
|
break;
|
|
}
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (spec) {
|
|
rewind(pc->tmpfile);
|
|
|
|
hdr_printed = cnt;
|
|
if ((number = IS_A_NUMBER(spec)))
|
|
sprintf(buf3, (*gdb_output_radix == 16) ? "%lx" : "%ld",
|
|
stol(spec, FAULT_ON_ERROR, NULL));
|
|
|
|
while (fgets(buf1, BUFSIZE, pc->tmpfile)) {
|
|
printit = FALSE;
|
|
strcpy(buf2, buf1);
|
|
argc = parse_line(buf2, arglist);
|
|
if (argc < 2)
|
|
continue;
|
|
|
|
if (number && STREQ(arglist[0], buf3))
|
|
printit = TRUE;
|
|
else if (!number && strstr(arglist[1], spec))
|
|
printit = TRUE;
|
|
|
|
if (printit) {
|
|
fprintf(pc->saved_fp, "%s%s", hdr_printed++ ?
|
|
"" : sys_call_hdr, buf1);
|
|
if (number)
|
|
break;
|
|
}
|
|
}
|
|
|
|
close_tmpfile();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the number of system calls in the sys_call_table, confirming
|
|
* the number only if the debuginfo data shows sys_call_table as an
|
|
* array. Otherwise base it upon next symbol after it.
|
|
*/
|
|
static int
|
|
get_NR_syscalls(int *confirmed)
|
|
{
|
|
ulong sys_call_table;
|
|
struct syment *sp;
|
|
int type, cnt;
|
|
|
|
type = get_symbol_type("sys_call_table", NULL, NULL);
|
|
if ((type == TYPE_CODE_ARRAY) &&
|
|
(cnt = get_array_length("sys_call_table", NULL, 0))) {
|
|
*confirmed = TRUE;
|
|
return cnt;
|
|
}
|
|
|
|
*confirmed = FALSE;
|
|
|
|
sys_call_table = symbol_value("sys_call_table");
|
|
if (!(sp = next_symbol("sys_call_table", NULL)))
|
|
return 256;
|
|
|
|
while (sp->value == sys_call_table) {
|
|
if (!(sp = next_symbol(sp->name, NULL)))
|
|
return 256;
|
|
}
|
|
|
|
if (machine_type("S390X"))
|
|
cnt = (sp->value - sys_call_table)/sizeof(int);
|
|
else
|
|
cnt = (sp->value - sys_call_table)/sizeof(void *);
|
|
|
|
return cnt;
|
|
}
|
|
|
|
/*
|
|
* "help -k" output
|
|
*/
|
|
void
|
|
dump_kernel_table(int verbose)
|
|
{
|
|
int i, j, more, nr_cpus;
|
|
struct new_utsname *uts;
|
|
int others;
|
|
|
|
others = 0;
|
|
more = FALSE;
|
|
uts = &kt->utsname;
|
|
|
|
fprintf(fp, " flags: %lx\n (", kt->flags);
|
|
if (kt->flags & NO_MODULE_ACCESS)
|
|
fprintf(fp, "%sNO_MODULE_ACCESS", others++ ? "|" : "");
|
|
if (kt->flags & TVEC_BASES_V1)
|
|
fprintf(fp, "%sTVEC_BASES_V1", others++ ? "|" : "");
|
|
if (kt->flags & TVEC_BASES_V2)
|
|
fprintf(fp, "%sTVEC_BASES_V2", others++ ? "|" : "");
|
|
if (kt->flags & GCC_2_96)
|
|
fprintf(fp, "%sGCC_2_96", others++ ? "|" : "");
|
|
if (kt->flags & GCC_3_2)
|
|
fprintf(fp, "%sGCC_3_2", others++ ? "|" : "");
|
|
if (kt->flags & GCC_3_2_3)
|
|
fprintf(fp, "%sGCC_3_2_3", others++ ? "|" : "");
|
|
if (kt->flags & GCC_3_3_2)
|
|
fprintf(fp, "%sGCC_3_3_2", others++ ? "|" : "");
|
|
if (kt->flags & GCC_3_3_3)
|
|
fprintf(fp, "%sGCC_3_3_3", others++ ? "|" : "");
|
|
if (kt->flags & RA_SEEK)
|
|
fprintf(fp, "%sRA_SEEK", others++ ? "|" : "");
|
|
if (kt->flags & NO_RA_SEEK)
|
|
fprintf(fp, "%sNO_RA_SEEK", others++ ? "|" : "");
|
|
if (kt->flags & KALLSYMS_V1)
|
|
fprintf(fp, "%sKALLSYMS_V1", others++ ? "|" : "");
|
|
if (kt->flags & NO_KALLSYMS)
|
|
fprintf(fp, "%sNO_KALLSYMS", others++ ? "|" : "");
|
|
if (kt->flags & PER_CPU_OFF)
|
|
fprintf(fp, "%sPER_CPU_OFF", others++ ? "|" : "");
|
|
if (kt->flags & SMP)
|
|
fprintf(fp, "%sSMP", others++ ? "|" : "");
|
|
if (kt->flags & KMOD_V1)
|
|
fprintf(fp, "%sKMOD_V1", others++ ? "|" : "");
|
|
if (kt->flags & KMOD_V2)
|
|
fprintf(fp, "%sKMOD_V2", others++ ? "|" : "");
|
|
if (kt->flags & KALLSYMS_V2)
|
|
fprintf(fp, "%sKALLSYMS_V2", others++ ? "|" : "");
|
|
if (kt->flags & USE_OLD_BT)
|
|
fprintf(fp, "%sUSE_OLD_BT", others++ ? "|" : "");
|
|
if (kt->flags & ARCH_XEN)
|
|
fprintf(fp, "%sARCH_XEN", others++ ? "|" : "");
|
|
if (kt->flags & ARCH_PVOPS_XEN)
|
|
fprintf(fp, "%sARCH_PVOPS_XEN", others++ ? "|" : "");
|
|
if (kt->flags & ARCH_OPENVZ)
|
|
fprintf(fp, "%sARCH_OPENVZ", others++ ? "|" : "");
|
|
if (kt->flags & ARCH_PVOPS)
|
|
fprintf(fp, "%sARCH_PVOPS", others++ ? "|" : "");
|
|
if (kt->flags & NO_IKCONFIG)
|
|
fprintf(fp, "%sNO_IKCONFIG", others++ ? "|" : "");
|
|
if (kt->flags & DWARF_UNWIND)
|
|
fprintf(fp, "%sDWARF_UNWIND", others++ ? "|" : "");
|
|
if (kt->flags & NO_DWARF_UNWIND)
|
|
fprintf(fp, "%sNO_DWARF_UNWIND", others++ ? "|" : "");
|
|
if (kt->flags & DWARF_UNWIND_MEMORY)
|
|
fprintf(fp, "%sDWARF_UNWIND_MEMORY", others++ ? "|" : "");
|
|
if (kt->flags & DWARF_UNWIND_EH_FRAME)
|
|
fprintf(fp, "%sDWARF_UNWIND_EH_FRAME", others++ ? "|" : "");
|
|
if (kt->flags & DWARF_UNWIND_MODULES)
|
|
fprintf(fp, "%sDWARF_UNWIND_MODULES", others++ ? "|" : "");
|
|
if (kt->flags & BUGVERBOSE_OFF)
|
|
fprintf(fp, "%sBUGVERBOSE_OFF", others++ ? "|" : "");
|
|
if (kt->flags & RELOC_SET)
|
|
fprintf(fp, "%sRELOC_SET", others++ ? "|" : "");
|
|
if (kt->flags & RELOC_FORCE)
|
|
fprintf(fp, "%sRELOC_FORCE", others++ ? "|" : "");
|
|
if (kt->flags & PRE_KERNEL_INIT)
|
|
fprintf(fp, "%sPRE_KERNEL_INIT", others++ ? "|" : "");
|
|
if (kt->flags & IRQ_DESC_TREE)
|
|
fprintf(fp, "%sIRQ_DESC_TREE", others++ ? "|" : "");
|
|
fprintf(fp, ")\n");
|
|
|
|
others = 0;
|
|
fprintf(fp, " flags2: %llx %s", kt->flags2,
|
|
kt->flags2 ? " \n (" : " (unused");
|
|
if (kt->flags2 & RELOC_AUTO)
|
|
fprintf(fp, "%sRELOC_AUTO", others++ ? "|" : "");
|
|
if (kt->flags2 & KASLR)
|
|
fprintf(fp, "%sKASLR", others++ ? "|" : "");
|
|
if (kt->flags2 & KASLR_CHECK)
|
|
fprintf(fp, "%sKASLR_CHECK", others++ ? "|" : "");
|
|
fprintf(fp, ")\n");
|
|
|
|
fprintf(fp, " stext: %lx\n", kt->stext);
|
|
fprintf(fp, " etext: %lx\n", kt->etext);
|
|
fprintf(fp, " stext_init: %lx\n", kt->stext_init);
|
|
fprintf(fp, " etext_init: %lx\n", kt->etext_init);
|
|
fprintf(fp, " init_begin: %lx\n", kt->init_begin);
|
|
fprintf(fp, " init_end: %lx\n", kt->init_end);
|
|
fprintf(fp, " end: %lx\n", kt->end);
|
|
fprintf(fp, " cpus: %d\n", kt->cpus);
|
|
fprintf(fp, " cpus_override: %s\n", kt->cpus_override);
|
|
fprintf(fp, " NR_CPUS: %d (compiled-in to this version of %s)\n",
|
|
NR_CPUS, pc->program_name);
|
|
fprintf(fp, "kernel_NR_CPUS: %d\n", kt->kernel_NR_CPUS);
|
|
others = 0;
|
|
fprintf(fp, "ikconfig_flags: %x (", kt->ikconfig_flags);
|
|
if (kt->ikconfig_flags & IKCONFIG_AVAIL)
|
|
fprintf(fp, "%sIKCONFIG_AVAIL", others++ ? "|" : "");
|
|
if (kt->ikconfig_flags & IKCONFIG_LOADED)
|
|
fprintf(fp, "%sIKCONFIG_LOADED", others++ ? "|" : "");
|
|
if (!kt->ikconfig_flags)
|
|
fprintf(fp, "unavailable");
|
|
fprintf(fp, ")\n");
|
|
fprintf(fp, " ikconfig_ents: %d\n", kt->ikconfig_ents);
|
|
if (kt->display_bh == display_bh_1)
|
|
fprintf(fp, " display_bh: display_bh_1()\n");
|
|
else if (kt->display_bh == display_bh_2)
|
|
fprintf(fp, " display_bh: display_bh_2()\n");
|
|
else if (kt->display_bh == display_bh_3)
|
|
fprintf(fp, " display_bh: display_bh_3()\n");
|
|
else
|
|
fprintf(fp, " display_bh: %lx\n", (ulong)kt->display_bh);
|
|
fprintf(fp, " highest_irq: ");
|
|
if (kt->highest_irq)
|
|
fprintf(fp, "%d\n", kt->highest_irq);
|
|
else
|
|
fprintf(fp, "(unused/undetermined)\n");
|
|
fprintf(fp, " module_list: %lx\n", kt->module_list);
|
|
fprintf(fp, " kernel_module: %lx\n", kt->kernel_module);
|
|
fprintf(fp, "mods_installed: %d\n", kt->mods_installed);
|
|
fprintf(fp, " module_tree: %s\n", kt->module_tree ?
|
|
kt->module_tree : "(not used)");
|
|
if (!(pc->flags & KERNEL_DEBUG_QUERY) && ACTIVE())
|
|
get_xtime(&kt->date);
|
|
fprintf(fp, " date: %s\n",
|
|
strip_linefeeds(ctime(&kt->date.tv_sec)));
|
|
fprintf(fp, " proc_version: %s\n", strip_linefeeds(kt->proc_version));
|
|
fprintf(fp, " new_utsname: \n");
|
|
fprintf(fp, " .sysname: %s\n", uts->sysname);
|
|
fprintf(fp, " .nodename: %s\n", uts->nodename);
|
|
fprintf(fp, " .release: %s\n", uts->release);
|
|
fprintf(fp, " .version: %s\n", uts->version);
|
|
fprintf(fp, " .machine: %s\n", uts->machine);
|
|
fprintf(fp, " .domainname: %s\n", uts->domainname);
|
|
fprintf(fp, "kernel_version: %d.%d.%d\n", kt->kernel_version[0],
|
|
kt->kernel_version[1], kt->kernel_version[2]);
|
|
fprintf(fp, " gcc_version: %d.%d.%d\n", kt->gcc_version[0],
|
|
kt->gcc_version[1], kt->gcc_version[2]);
|
|
fprintf(fp, " BUG_bytes: %d\n", kt->BUG_bytes);
|
|
fprintf(fp, " relocate: %lx", kt->relocate);
|
|
if (kt->flags2 & KASLR)
|
|
fprintf(fp, " (KASLR offset: %lx / %ldMB)",
|
|
kt->relocate * -1,
|
|
(kt->relocate * -1) >> 20);
|
|
fprintf(fp, "\n runq_siblings: %d\n", kt->runq_siblings);
|
|
fprintf(fp, " __rq_idx[NR_CPUS]: ");
|
|
nr_cpus = kt->kernel_NR_CPUS ? kt->kernel_NR_CPUS : NR_CPUS;
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!(kt->__rq_idx)) {
|
|
fprintf(fp, "(unused)");
|
|
break;
|
|
}
|
|
fprintf(fp, "%ld ", kt->__rq_idx[i]);
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (kt->__rq_idx[j])
|
|
more = TRUE;
|
|
}
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n __cpu_idx[NR_CPUS]: ");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!(kt->__cpu_idx)) {
|
|
fprintf(fp, "(unused)");
|
|
break;
|
|
}
|
|
fprintf(fp, "%ld ", kt->__cpu_idx[i]);
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (kt->__cpu_idx[j])
|
|
more = TRUE;
|
|
}
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n __per_cpu_offset[NR_CPUS]:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
fprintf(fp, "%s%.*lx ", (i % 4) == 0 ? "\n " : "",
|
|
LONG_PRLEN, kt->__per_cpu_offset[i]);
|
|
if ((i % 4) == 0) {
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (kt->__per_cpu_offset[j] &&
|
|
(kt->__per_cpu_offset[j] != kt->__per_cpu_offset[i]))
|
|
more = TRUE;
|
|
}
|
|
}
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
|
|
}
|
|
fprintf(fp, "\n cpu_flags[NR_CPUS]: ");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!(kt->cpu_flags)) {
|
|
fprintf(fp, "(unused)\n");
|
|
goto no_cpu_flags;
|
|
}
|
|
fprintf(fp, "%lx ", kt->cpu_flags[i]);
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (kt->cpu_flags[j])
|
|
more = TRUE;
|
|
}
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
fprintf(fp, " cpu_possible_map: ");
|
|
if (cpu_map_addr("possible")) {
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (kt->cpu_flags[i] & POSSIBLE_MAP)
|
|
fprintf(fp, "%d ", i);
|
|
}
|
|
fprintf(fp, "\n");
|
|
} else
|
|
fprintf(fp, "(does not exist)\n");
|
|
fprintf(fp, " cpu_present_map: ");
|
|
if (cpu_map_addr("present")) {
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (kt->cpu_flags[i] & PRESENT_MAP)
|
|
fprintf(fp, "%d ", i);
|
|
}
|
|
fprintf(fp, "\n");
|
|
} else
|
|
fprintf(fp, "(does not exist)\n");
|
|
fprintf(fp, " cpu_online_map: ");
|
|
if (cpu_map_addr("online")) {
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (kt->cpu_flags[i] & ONLINE_MAP)
|
|
fprintf(fp, "%d ", i);
|
|
}
|
|
fprintf(fp, "\n");
|
|
} else
|
|
fprintf(fp, "(does not exist)\n");
|
|
fprintf(fp, " cpu_active_map: ");
|
|
if (cpu_map_addr("active")) {
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (kt->cpu_flags[i] & ACTIVE_MAP)
|
|
fprintf(fp, "%d ", i);
|
|
}
|
|
fprintf(fp, "\n");
|
|
} else
|
|
fprintf(fp, "(does not exist)\n");
|
|
|
|
no_cpu_flags:
|
|
fprintf(fp, " vmcoreinfo: \n");
|
|
fprintf(fp, " log_buf_SYMBOL: %lx\n", kt->vmcoreinfo.log_buf_SYMBOL);
|
|
fprintf(fp, " log_end_SYMBOL: %ld\n", kt->vmcoreinfo.log_end_SYMBOL);
|
|
fprintf(fp, " log_buf_len_SYMBOL: %ld\n", kt->vmcoreinfo.log_buf_len_SYMBOL);
|
|
fprintf(fp, " logged_chars_SYMBOL: %ld\n", kt->vmcoreinfo.logged_chars_SYMBOL);
|
|
fprintf(fp, "log_first_idx_SYMBOL: %ld\n", kt->vmcoreinfo.log_first_idx_SYMBOL);
|
|
fprintf(fp, " log_next_idx_SYMBOL: %ld\n", kt->vmcoreinfo.log_next_idx_SYMBOL);
|
|
fprintf(fp, " log_SIZE: %ld\n", kt->vmcoreinfo.log_SIZE);
|
|
fprintf(fp, " log_ts_nsec_OFFSET: %ld\n", kt->vmcoreinfo.log_ts_nsec_OFFSET);
|
|
fprintf(fp, " log_len_OFFSET: %ld\n", kt->vmcoreinfo.log_len_OFFSET);
|
|
fprintf(fp, " log_text_len_OFFSET: %ld\n", kt->vmcoreinfo.log_text_len_OFFSET);
|
|
fprintf(fp, " log_dict_len_OFFSET: %ld\n", kt->vmcoreinfo.log_dict_len_OFFSET);
|
|
fprintf(fp, " phys_base_SYMBOL: %lx\n", kt->vmcoreinfo.phys_base_SYMBOL);
|
|
fprintf(fp, " _stext_SYMBOL: %lx\n", kt->vmcoreinfo._stext_SYMBOL);
|
|
fprintf(fp, " hypervisor: %s\n", kt->hypervisor);
|
|
|
|
others = 0;
|
|
fprintf(fp, " xen_flags: %lx (", kt->xen_flags);
|
|
if (kt->xen_flags & WRITABLE_PAGE_TABLES)
|
|
fprintf(fp, "%sWRITABLE_PAGE_TABLES", others++ ? "|" : "");
|
|
if (kt->xen_flags & SHADOW_PAGE_TABLES)
|
|
fprintf(fp, "%sSHADOW_PAGE_TABLES", others++ ? "|" : "");
|
|
if (kt->xen_flags & CANONICAL_PAGE_TABLES)
|
|
fprintf(fp, "%sCANONICAL_PAGE_TABLES", others++ ? "|" : "");
|
|
if (kt->xen_flags & XEN_SUSPEND)
|
|
fprintf(fp, "%sXEN_SUSPEND", others++ ? "|" : "");
|
|
fprintf(fp, ")\n");
|
|
fprintf(fp, " m2p_page: %lx\n", (ulong)kt->m2p_page);
|
|
fprintf(fp, "phys_to_machine_mapping: %lx\n", kt->phys_to_machine_mapping);
|
|
fprintf(fp, " p2m_table_size: %ld\n", kt->p2m_table_size);
|
|
fprintf(fp, " p2m_mapping_cache[%d]: %s\n", P2M_MAPPING_CACHE,
|
|
verbose ? "" : "(use \"help -K\" to view cache contents)");
|
|
for (i = 0; verbose && (i < P2M_MAPPING_CACHE); i++) {
|
|
if (!kt->p2m_mapping_cache[i].mapping)
|
|
continue;
|
|
fprintf(fp, " [%d] mapping: %lx pfn: ", i, kt->p2m_mapping_cache[i].mapping);
|
|
if (PVOPS_XEN())
|
|
fprintf(fp, "%lx ", kt->p2m_mapping_cache[i].pfn);
|
|
else
|
|
fprintf(fp, "n/a ");
|
|
fprintf(fp, "start: %lx end: %lx (%ld mfns)\n",
|
|
kt->p2m_mapping_cache[i].start,
|
|
kt->p2m_mapping_cache[i].end,
|
|
kt->p2m_mapping_cache[i].end - kt->p2m_mapping_cache[i].start + 1);
|
|
}
|
|
fprintf(fp, " last_mapping_read: %lx\n", kt->last_mapping_read);
|
|
fprintf(fp, " p2m_cache_index: %ld\n", kt->p2m_cache_index);
|
|
fprintf(fp, " p2m_pages_searched: %ld\n", kt->p2m_pages_searched);
|
|
fprintf(fp, " p2m_mfn_cache_hits: %ld ", kt->p2m_mfn_cache_hits);
|
|
if (kt->p2m_pages_searched)
|
|
fprintf(fp, "(%ld%%)\n", kt->p2m_mfn_cache_hits * 100 / kt->p2m_pages_searched);
|
|
else
|
|
fprintf(fp, "\n");
|
|
fprintf(fp, " p2m_page_cache_hits: %ld ", kt->p2m_page_cache_hits);
|
|
if (kt->p2m_pages_searched)
|
|
fprintf(fp, "(%ld%%)\n", kt->p2m_page_cache_hits * 100 / kt->p2m_pages_searched);
|
|
else
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " pvops_xen:\n");
|
|
fprintf(fp, " p2m_top: %lx\n", kt->pvops_xen.p2m_top);
|
|
fprintf(fp, " p2m_top_entries: %d\n", kt->pvops_xen.p2m_top_entries);
|
|
if (symbol_exists("p2m_mid_missing"))
|
|
fprintf(fp, " p2m_mid_missing: %lx\n", kt->pvops_xen.p2m_mid_missing);
|
|
fprintf(fp, " p2m_missing: %lx\n", kt->pvops_xen.p2m_missing);
|
|
}
|
|
|
|
/*
|
|
* Set the context to the active task on a given cpu -- dumpfiles only.
|
|
*/
|
|
void
|
|
set_cpu(int cpu)
|
|
{
|
|
ulong task;
|
|
|
|
if (cpu >= kt->cpus)
|
|
error(FATAL, "invalid cpu number: system has only %d cpu%s\n",
|
|
kt->cpus, kt->cpus > 1 ? "s" : "");
|
|
|
|
if (hide_offline_cpu(cpu))
|
|
error(FATAL, "invalid cpu number: cpu %d is OFFLINE\n", cpu);
|
|
|
|
if ((task = get_active_task(cpu)))
|
|
set_context(task, NO_PID);
|
|
else
|
|
error(FATAL, "cannot determine active task on cpu %ld\n", cpu);
|
|
|
|
show_context(CURRENT_CONTEXT());
|
|
}
|
|
|
|
|
|
/*
|
|
* Collect the irq_desc[] entry along with its associated handler and
|
|
* action structures.
|
|
*/
|
|
|
|
void
|
|
cmd_irq(void)
|
|
{
|
|
int i, c;
|
|
int nr_irqs;
|
|
ulong *cpus;
|
|
int show_intr, choose_cpu;
|
|
char buf[10];
|
|
char arg_buf[BUFSIZE];
|
|
|
|
cpus = NULL;
|
|
show_intr = 0;
|
|
choose_cpu = 0;
|
|
|
|
while ((c = getopt(argcnt, args, "dbuasc:")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'd':
|
|
display_idt_table();
|
|
return;
|
|
|
|
case 'b':
|
|
if (!kt->display_bh) {
|
|
if (symbol_exists("bh_base") &&
|
|
symbol_exists("bh_mask") &&
|
|
symbol_exists("bh_active"))
|
|
kt->display_bh = display_bh_1;
|
|
else if (symbol_exists("bh_base") &&
|
|
symbol_exists("softirq_state") &&
|
|
symbol_exists("softirq_vec"))
|
|
kt->display_bh = display_bh_2;
|
|
else if (symbol_exists("bh_base") &&
|
|
symbol_exists("irq_stat") &&
|
|
symbol_exists("softirq_vec") &&
|
|
VALID_MEMBER(irq_cpustat_t___softirq_active)
|
|
&& VALID_MEMBER(irq_cpustat_t___softirq_mask))
|
|
kt->display_bh = display_bh_3;
|
|
else if (get_symbol_type("softirq_vec", NULL, NULL) ==
|
|
TYPE_CODE_ARRAY)
|
|
kt->display_bh = display_bh_4;
|
|
else
|
|
error(FATAL,
|
|
"bottom-half option not supported\n");
|
|
}
|
|
kt->display_bh();
|
|
return;
|
|
|
|
case 'u':
|
|
pc->curcmd_flags |= IRQ_IN_USE;
|
|
if (kernel_symbol_exists("no_irq_chip"))
|
|
pc->curcmd_private = (ulonglong)symbol_value("no_irq_chip");
|
|
else if (kernel_symbol_exists("no_irq_type"))
|
|
pc->curcmd_private = (ulonglong)symbol_value("no_irq_type");
|
|
else
|
|
error(WARNING,
|
|
"irq: -u option ignored: \"no_irq_chip\" or \"no_irq_type\" symbols do not exist\n");
|
|
break;
|
|
|
|
case 'a':
|
|
if (!machdep->get_irq_affinity)
|
|
option_not_supported(c);
|
|
|
|
if (VALID_STRUCT(irq_data)) {
|
|
if (INVALID_MEMBER(irq_data_affinity))
|
|
option_not_supported(c);
|
|
} else if (INVALID_MEMBER(irq_desc_t_affinity))
|
|
option_not_supported(c);
|
|
|
|
if ((nr_irqs = machdep->nr_irqs) == 0)
|
|
error(FATAL, "cannot determine number of IRQs\n");
|
|
|
|
fprintf(fp, "IRQ NAME AFFINITY\n");
|
|
for (i = 0; i < nr_irqs; i++)
|
|
machdep->get_irq_affinity(i);
|
|
|
|
return;
|
|
|
|
case 's':
|
|
if (!machdep->show_interrupts)
|
|
option_not_supported(c);
|
|
show_intr = 1;
|
|
break;
|
|
|
|
case 'c':
|
|
if (choose_cpu) {
|
|
error(INFO, "only one -c option allowed\n");
|
|
argerrs++;
|
|
} else {
|
|
choose_cpu = 1;
|
|
BZERO(arg_buf, BUFSIZE);
|
|
strncpy(arg_buf, optarg, strlen(optarg));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if ((nr_irqs = machdep->nr_irqs) == 0)
|
|
error(FATAL, "cannot determine number of IRQs\n");
|
|
|
|
if (show_intr) {
|
|
cpus = get_cpumask_buf();
|
|
|
|
if (choose_cpu) {
|
|
make_cpumask(arg_buf, cpus, FAULT_ON_ERROR, NULL);
|
|
} else {
|
|
for (i = 0; i < kt->cpus; i++)
|
|
SET_BIT(cpus, i);
|
|
}
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (NUM_IN_BITMAP(cpus, i) && hide_offline_cpu(i))
|
|
error(INFO, "CPU%d is OFFLINE.\n", i);
|
|
}
|
|
|
|
fprintf(fp, " ");
|
|
BZERO(buf, 10);
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (hide_offline_cpu(i))
|
|
continue;
|
|
|
|
if (NUM_IN_BITMAP(cpus, i)) {
|
|
sprintf(buf, "CPU%d", i);
|
|
fprintf(fp, "%10s ", buf);
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
for (i = 0; i < nr_irqs; i++)
|
|
machdep->show_interrupts(i, cpus);
|
|
|
|
if (choose_cpu)
|
|
FREEBUF(cpus);
|
|
return;
|
|
}
|
|
|
|
pc->curcmd_flags &= ~HEADER_PRINTED;
|
|
|
|
if (!args[optind]) {
|
|
for (i = 0; i < nr_irqs; i++)
|
|
machdep->dump_irq(i);
|
|
return;
|
|
}
|
|
|
|
pc->curcmd_flags &= ~IRQ_IN_USE;
|
|
|
|
while (args[optind]) {
|
|
i = dtoi(args[optind], FAULT_ON_ERROR, NULL);
|
|
if (i >= nr_irqs)
|
|
error(FATAL, "invalid IRQ value: %d (%d max)\n",
|
|
i, nr_irqs-1);
|
|
machdep->dump_irq(i);
|
|
optind++;
|
|
}
|
|
}
|
|
|
|
static ulong
|
|
get_irq_desc_addr(int irq)
|
|
{
|
|
int c;
|
|
ulong cnt, addr, ptr;
|
|
long len;
|
|
struct radix_tree_pair *rtp;
|
|
|
|
addr = 0;
|
|
rtp = NULL;
|
|
|
|
if (!VALID_STRUCT(irq_desc_t))
|
|
error(FATAL, "cannot determine size of irq_desc_t\n");
|
|
len = SIZE(irq_desc_t);
|
|
|
|
if (symbol_exists("irq_desc"))
|
|
addr = symbol_value("irq_desc") + (len * irq);
|
|
else if (symbol_exists("_irq_desc"))
|
|
addr = symbol_value("_irq_desc") + (len * irq);
|
|
else if (symbol_exists("irq_desc_ptrs")) {
|
|
if (get_symbol_type("irq_desc_ptrs", NULL, NULL) == TYPE_CODE_PTR)
|
|
get_symbol_data("irq_desc_ptrs", sizeof(void *), &ptr);
|
|
else
|
|
ptr = symbol_value("irq_desc_ptrs");
|
|
ptr += (irq * sizeof(void *));
|
|
readmem(ptr, KVADDR, &addr,
|
|
sizeof(void *), "irq_desc_ptrs entry",
|
|
FAULT_ON_ERROR);
|
|
} else if (kt->flags & IRQ_DESC_TREE) {
|
|
if (kt->highest_irq && (irq > kt->highest_irq))
|
|
return addr;
|
|
|
|
cnt = do_radix_tree(symbol_value("irq_desc_tree"),
|
|
RADIX_TREE_COUNT, NULL);
|
|
len = sizeof(struct radix_tree_pair) * (cnt+1);
|
|
rtp = (struct radix_tree_pair *)GETBUF(len);
|
|
rtp[0].index = cnt;
|
|
cnt = do_radix_tree(symbol_value("irq_desc_tree"),
|
|
RADIX_TREE_GATHER, rtp);
|
|
|
|
if (kt->highest_irq == 0)
|
|
kt->highest_irq = rtp[cnt-1].index;
|
|
|
|
for (c = 0; c < cnt; c++) {
|
|
if (rtp[c].index == irq) {
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "index: %ld value: %lx\n",
|
|
rtp[c].index, (ulong)rtp[c].value);
|
|
addr = (ulong)rtp[c].value;
|
|
break;
|
|
}
|
|
}
|
|
|
|
FREEBUF(rtp);
|
|
} else {
|
|
error(FATAL,
|
|
"neither irq_desc, _irq_desc, irq_desc_ptrs "
|
|
"or irq_desc_tree symbols exist\n");
|
|
}
|
|
|
|
return addr;
|
|
}
|
|
|
|
static void
|
|
display_cpu_affinity(ulong *mask)
|
|
{
|
|
int cpu, seq, start, count;
|
|
|
|
seq = FALSE;
|
|
start = 0;
|
|
count = 0;
|
|
|
|
for (cpu = 0; cpu < kt->cpus; ++cpu) {
|
|
if (NUM_IN_BITMAP(mask, cpu)) {
|
|
if (seq)
|
|
continue;
|
|
start = cpu;
|
|
seq = TRUE;
|
|
} else if (seq) {
|
|
if (count)
|
|
fprintf(fp, ",");
|
|
if (start == cpu - 1)
|
|
fprintf(fp, "%d", cpu - 1);
|
|
else
|
|
fprintf(fp, "%d-%d", start, cpu - 1);
|
|
count++;
|
|
seq = FALSE;
|
|
}
|
|
}
|
|
|
|
if (seq) {
|
|
if (count)
|
|
fprintf(fp, ",");
|
|
if (start == kt->cpus - 1)
|
|
fprintf(fp, "%d", kt->cpus - 1);
|
|
else
|
|
fprintf(fp, "%d-%d", start, kt->cpus - 1);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Do the work for cmd_irq().
|
|
*/
|
|
void
|
|
generic_dump_irq(int irq)
|
|
{
|
|
ulong irq_desc_addr;
|
|
char buf[BUFSIZE];
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
int status, depth, others;
|
|
ulong handler, action, value;
|
|
ulong tmp1, tmp2;
|
|
|
|
handler = UNINITIALIZED;
|
|
action = 0;
|
|
|
|
irq_desc_addr = get_irq_desc_addr(irq);
|
|
if (!irq_desc_addr && symbol_exists("irq_desc_ptrs")) {
|
|
if (!(pc->curcmd_flags & IRQ_IN_USE))
|
|
fprintf(fp, " IRQ: %d (unused)\n\n", irq);
|
|
return;
|
|
}
|
|
|
|
if (irq_desc_addr) {
|
|
if (VALID_MEMBER(irq_desc_t_status))
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_status),
|
|
KVADDR, &status, sizeof(int), "irq_desc status",
|
|
FAULT_ON_ERROR);
|
|
if (VALID_MEMBER(irq_desc_t_handler))
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_handler),
|
|
KVADDR, &handler, sizeof(long), "irq_desc handler",
|
|
FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_desc_t_chip))
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_chip), KVADDR,
|
|
&handler, sizeof(long), "irq_desc chip",
|
|
FAULT_ON_ERROR);
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_action), KVADDR,
|
|
&action, sizeof(long), "irq_desc action", FAULT_ON_ERROR);
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_depth), KVADDR, &depth,
|
|
sizeof(int), "irq_desc depth", FAULT_ON_ERROR);
|
|
}
|
|
|
|
if (!action && (handler == (ulong)pc->curcmd_private))
|
|
return;
|
|
|
|
if ((handler == UNINITIALIZED) && VALID_STRUCT(irq_data))
|
|
goto irq_desc_format_v2;
|
|
|
|
if (!irq_desc_addr) {
|
|
if (!(pc->curcmd_flags & IRQ_IN_USE))
|
|
fprintf(fp, " IRQ: %d (unused)\n\n", irq);
|
|
return;
|
|
}
|
|
|
|
fprintf(fp, " IRQ: %d\n", irq);
|
|
fprintf(fp, " STATUS: %x %s", status, status ? "(" : "");
|
|
others = 0;
|
|
if (status & IRQ_INPROGRESS) {
|
|
fprintf(fp, "IRQ_INPROGRESS");
|
|
others++;
|
|
}
|
|
if (status & IRQ_DISABLED)
|
|
fprintf(fp, "%sIRQ_DISABLED", others++ ? "|" : "");
|
|
if (status & IRQ_PENDING)
|
|
fprintf(fp, "%sIRQ_PENDING", others++ ? "|" : "");
|
|
if (status & IRQ_REPLAY)
|
|
fprintf(fp, "%sIRQ_REPLAY", others++ ? "|" : "");
|
|
if (status & IRQ_AUTODETECT)
|
|
fprintf(fp, "%sIRQ_AUTODETECT", others++ ? "|" : "");
|
|
if (status & IRQ_WAITING)
|
|
fprintf(fp, "%sIRQ_WAITING", others++ ? "|" : "");
|
|
if (status & IRQ_LEVEL)
|
|
fprintf(fp, "%sIRQ_LEVEL", others++ ? "|" : "");
|
|
if (status & IRQ_MASKED)
|
|
fprintf(fp, "%sIRQ_MASKED", others++ ? "|" : "");
|
|
fprintf(fp, "%s\n", status ? ")" : "");
|
|
|
|
fprintf(fp, "HANDLER: ");
|
|
if (value_symbol(handler)) {
|
|
fprintf(fp, "%lx ", handler);
|
|
pad_line(fp, VADDR_PRLEN == 8 ?
|
|
VADDR_PRLEN+2 : VADDR_PRLEN-6, ' ');
|
|
fprintf(fp, "<%s>\n", value_symbol(handler));
|
|
} else
|
|
fprintf(fp, "%lx\n", handler);
|
|
|
|
if (handler) {
|
|
if (VALID_MEMBER(hw_interrupt_type_typename))
|
|
readmem(handler+OFFSET(hw_interrupt_type_typename),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type typename", FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_chip_typename))
|
|
readmem(handler+OFFSET(irq_chip_typename),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type typename", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, " typename: %lx ", tmp1);
|
|
BZERO(buf, BUFSIZE);
|
|
if (read_string(tmp1, buf, BUFSIZE-1))
|
|
fprintf(fp, "\"%s\"", buf);
|
|
fprintf(fp, "\n");
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_startup))
|
|
readmem(handler+OFFSET(hw_interrupt_type_startup),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type startup", FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_chip_startup))
|
|
readmem(handler+OFFSET(irq_chip_startup),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type startup", FAULT_ON_ERROR);
|
|
fprintf(fp, " startup: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>", value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "startup indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_shutdown))
|
|
readmem(handler+OFFSET(hw_interrupt_type_shutdown),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type shutdown", FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_chip_shutdown))
|
|
readmem(handler+OFFSET(irq_chip_shutdown),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type shutdown", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, " shutdown: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>", value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "shutdown indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_handle)) {
|
|
readmem(handler+OFFSET(hw_interrupt_type_handle),
|
|
KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"hw_interrupt_type handle", FAULT_ON_ERROR);
|
|
fprintf(fp, " handle: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "handle indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_enable))
|
|
readmem(handler+OFFSET(hw_interrupt_type_enable),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type enable", FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_chip_enable))
|
|
readmem(handler+OFFSET(irq_chip_enable),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type enable", FAULT_ON_ERROR);
|
|
fprintf(fp, " enable: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>", value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "enable indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_disable))
|
|
readmem(handler+OFFSET(hw_interrupt_type_disable),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type disable", FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_chip_disable))
|
|
readmem(handler+OFFSET(irq_chip_disable),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type disable", FAULT_ON_ERROR);
|
|
fprintf(fp, " disable: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>", value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "disable indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_ack)) {
|
|
readmem(handler+OFFSET(hw_interrupt_type_ack), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"hw_interrupt_type ack", FAULT_ON_ERROR);
|
|
fprintf(fp, " ack: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "ack indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
} else if (VALID_MEMBER(irq_chip_ack)) {
|
|
readmem(handler+OFFSET(irq_chip_ack), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip ack", FAULT_ON_ERROR);
|
|
fprintf(fp, " ack: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "ack indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (VALID_MEMBER(irq_chip_mask)) {
|
|
readmem(handler+OFFSET(irq_chip_mask), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip mask", FAULT_ON_ERROR);
|
|
fprintf(fp, " mask: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "mask indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (VALID_MEMBER(irq_chip_mask_ack)) {
|
|
readmem(handler+OFFSET(irq_chip_mask_ack), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip mask_ack", FAULT_ON_ERROR);
|
|
fprintf(fp, " mask_ack: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "mask_ack indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (VALID_MEMBER(irq_chip_unmask)) {
|
|
readmem(handler+OFFSET(irq_chip_unmask), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip unmask", FAULT_ON_ERROR);
|
|
fprintf(fp, " unmask: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "unmask indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (VALID_MEMBER(irq_chip_eoi)) {
|
|
readmem(handler+OFFSET(irq_chip_eoi), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip eoi", FAULT_ON_ERROR);
|
|
fprintf(fp, " eoi: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "eoi indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_end)) {
|
|
readmem(handler+OFFSET(hw_interrupt_type_end), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"hw_interrupt_type end", FAULT_ON_ERROR);
|
|
fprintf(fp, " end: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "end indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
} else if (VALID_MEMBER(irq_chip_end)) {
|
|
readmem(handler+OFFSET(irq_chip_end), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip end", FAULT_ON_ERROR);
|
|
fprintf(fp, " end: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "end indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (VALID_MEMBER(hw_interrupt_type_set_affinity)) {
|
|
readmem(handler+OFFSET(hw_interrupt_type_set_affinity),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"hw_interrupt_type set_affinity",
|
|
FAULT_ON_ERROR);
|
|
fprintf(fp, " set_affinity: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "set_affinity indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
} else if (VALID_MEMBER(irq_chip_set_affinity)) {
|
|
readmem(handler+OFFSET(irq_chip_set_affinity),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"irq_chip set_affinity",
|
|
FAULT_ON_ERROR);
|
|
fprintf(fp, " set_affinity: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "set_affinity indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
if (VALID_MEMBER(irq_chip_retrigger)) {
|
|
readmem(handler+OFFSET(irq_chip_retrigger), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip retrigger", FAULT_ON_ERROR);
|
|
fprintf(fp, " retrigger: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "retrigger indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
if (VALID_MEMBER(irq_chip_set_type)) {
|
|
readmem(handler+OFFSET(irq_chip_set_type), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip set_type", FAULT_ON_ERROR);
|
|
fprintf(fp, " set_type: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "set_type indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
if (VALID_MEMBER(irq_chip_set_wake)) {
|
|
readmem(handler+OFFSET(irq_chip_set_wake), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irq_chip set wake", FAULT_ON_ERROR);
|
|
fprintf(fp, " set_wake: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "set_wake indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
do_linked_action:
|
|
|
|
fprintf(fp, " ACTION: ");
|
|
if (value_symbol(action)) {
|
|
fprintf(fp, "%lx ", action);
|
|
pad_line(fp, VADDR_PRLEN == 8 ?
|
|
VADDR_PRLEN+2 : VADDR_PRLEN-6, ' ');
|
|
fprintf(fp, "<%s>\n", value_symbol(action));
|
|
} else if (action)
|
|
fprintf(fp, "%lx\n", action);
|
|
else
|
|
fprintf(fp, "(none)\n");
|
|
|
|
|
|
if (action) {
|
|
readmem(action+OFFSET(irqaction_handler), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irqaction handler", FAULT_ON_ERROR);
|
|
fprintf(fp, " handler: %lx ", tmp1);
|
|
if (is_kernel_text(tmp1))
|
|
fprintf(fp, "<%s>", value_to_symstr(tmp1, buf, 0));
|
|
else if (readmem(tmp1, KVADDR, &tmp2,
|
|
sizeof(ulong), "handler indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(tmp2))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(tmp2, buf, 0));
|
|
fprintf(fp, "\n");
|
|
|
|
readmem(action+OFFSET(irqaction_flags), KVADDR,
|
|
&value, sizeof(void *),
|
|
"irqaction flags", FAULT_ON_ERROR);
|
|
fprintf(fp, " flags: %lx\n", value);
|
|
|
|
if (VALID_MEMBER(irqaction_mask)) {
|
|
readmem(action+OFFSET(irqaction_mask), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irqaction mask", FAULT_ON_ERROR);
|
|
fprintf(fp, " mask: %lx\n", tmp1);
|
|
}
|
|
|
|
readmem(action+OFFSET(irqaction_name), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irqaction name", FAULT_ON_ERROR);
|
|
fprintf(fp, " name: %lx ", tmp1);
|
|
BZERO(buf, BUFSIZE);
|
|
if (read_string(tmp1, buf, BUFSIZE-1))
|
|
fprintf(fp, "\"%s\"", buf);
|
|
fprintf(fp, "\n");
|
|
|
|
readmem(action+OFFSET(irqaction_dev_id), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irqaction dev_id", FAULT_ON_ERROR);
|
|
fprintf(fp, " dev_id: %lx\n", tmp1);
|
|
|
|
readmem(action+OFFSET(irqaction_next), KVADDR,
|
|
&action, sizeof(void *),
|
|
"irqaction dev_id", FAULT_ON_ERROR);
|
|
fprintf(fp, " next: %lx\n", action);
|
|
}
|
|
|
|
if (action)
|
|
goto do_linked_action;
|
|
|
|
fprintf(fp, " DEPTH: %d\n\n", depth);
|
|
|
|
return;
|
|
|
|
irq_desc_format_v2:
|
|
if (!(pc->curcmd_flags & HEADER_PRINTED)) {
|
|
fprintf(fp, " IRQ %s %s NAME\n",
|
|
mkstring(buf1, VADDR_PRLEN, CENTER,
|
|
"IRQ_DESC/_DATA"),
|
|
mkstring(buf2, VADDR_PRLEN, CENTER,
|
|
"IRQACTION"));
|
|
|
|
pc->curcmd_flags |= HEADER_PRINTED;
|
|
}
|
|
if (!irq_desc_addr) {
|
|
if (pc->curcmd_flags & IRQ_IN_USE)
|
|
return;
|
|
}
|
|
fprintf(fp, "%s %s ",
|
|
mkstring(buf1, 4, CENTER|RJUST|INT_DEC, MKSTR((ulong)irq)),
|
|
irq_desc_addr ?
|
|
mkstring(buf2, MAX(VADDR_PRLEN, strlen("IRQ_DESC/_DATA")),
|
|
CENTER|LONG_HEX, MKSTR(irq_desc_addr)) :
|
|
mkstring(buf3,
|
|
MAX(VADDR_PRLEN, strlen("IRQ_DESC/_DATA")),
|
|
CENTER, "(unused)"));
|
|
|
|
do_linked_action_v2:
|
|
|
|
fprintf(fp, "%s ", action ?
|
|
mkstring(buf1, MAX(VADDR_PRLEN, strlen("IRQACTION")),
|
|
CENTER|LONG_HEX, MKSTR(action)) :
|
|
mkstring(buf2, MAX(VADDR_PRLEN, strlen("IRQACTION")),
|
|
CENTER, "(unused)"));
|
|
|
|
if (action) {
|
|
readmem(action+OFFSET(irqaction_name), KVADDR,
|
|
&tmp1, sizeof(void *),
|
|
"irqaction name", FAULT_ON_ERROR);
|
|
if (read_string(tmp1, buf, BUFSIZE-1))
|
|
fprintf(fp, "\"%s\"", buf);
|
|
|
|
readmem(action+OFFSET(irqaction_next), KVADDR,
|
|
&action, sizeof(void *),
|
|
"irqaction next", FAULT_ON_ERROR);
|
|
if (action) {
|
|
fprintf(fp, "\n%s",
|
|
space(4 + 2 + MAX(VADDR_PRLEN,
|
|
strlen("IRQ_DESC/_DATA")) + 2));
|
|
goto do_linked_action_v2;
|
|
}
|
|
}
|
|
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
void
|
|
generic_get_irq_affinity(int irq)
|
|
{
|
|
ulong irq_desc_addr;
|
|
long len;
|
|
ulong affinity_ptr;
|
|
ulong *affinity;
|
|
ulong tmp_addr;
|
|
ulong action, name;
|
|
char buf[BUFSIZE];
|
|
char name_buf[BUFSIZE];
|
|
|
|
affinity = NULL;
|
|
|
|
irq_desc_addr = get_irq_desc_addr(irq);
|
|
if (!irq_desc_addr)
|
|
return;
|
|
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_action), KVADDR,
|
|
&action, sizeof(long), "irq_desc action", FAULT_ON_ERROR);
|
|
|
|
if (!action)
|
|
return;
|
|
|
|
if ((len = STRUCT_SIZE("cpumask_t")) < 0)
|
|
len = DIV_ROUND_UP(kt->cpus, BITS_PER_LONG) * sizeof(ulong);
|
|
|
|
affinity = (ulong *)GETBUF(len);
|
|
if (VALID_STRUCT(irq_data))
|
|
tmp_addr = irq_desc_addr + \
|
|
OFFSET(irq_data_affinity);
|
|
else
|
|
tmp_addr = irq_desc_addr + \
|
|
OFFSET(irq_desc_t_affinity);
|
|
|
|
if (symbol_exists("alloc_cpumask_var")) /* pointer member */
|
|
readmem(tmp_addr,KVADDR, &affinity_ptr, sizeof(ulong),
|
|
"irq_desc affinity", FAULT_ON_ERROR);
|
|
else /* array member */
|
|
affinity_ptr = tmp_addr;
|
|
|
|
readmem(affinity_ptr, KVADDR, affinity, len,
|
|
"irq_desc affinity", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, "%3d ", irq);
|
|
|
|
BZERO(name_buf, BUFSIZE);
|
|
|
|
while (action) {
|
|
readmem(action+OFFSET(irqaction_name), KVADDR,
|
|
&name, sizeof(void *),
|
|
"irqaction name", FAULT_ON_ERROR);
|
|
BZERO(buf, BUFSIZE);
|
|
if (read_string(name, buf, BUFSIZE-1)) {
|
|
if (strlen(name_buf) != 0)
|
|
strncat(name_buf, ",", 2);
|
|
strncat(name_buf, buf, strlen(buf));
|
|
}
|
|
|
|
readmem(action+OFFSET(irqaction_next), KVADDR,
|
|
&action, sizeof(void *),
|
|
"irqaction dev_id", FAULT_ON_ERROR);
|
|
}
|
|
|
|
fprintf(fp, "%-20s ", name_buf);
|
|
display_cpu_affinity(affinity);
|
|
fprintf(fp, "\n");
|
|
|
|
FREEBUF(affinity);
|
|
}
|
|
|
|
void
|
|
generic_show_interrupts(int irq, ulong *cpus)
|
|
{
|
|
int i;
|
|
ulong irq_desc_addr;
|
|
ulong handler, action, name;
|
|
uint kstat_irq;
|
|
uint kstat_irqs[kt->cpus];
|
|
ulong kstat_irqs_ptr;
|
|
struct syment *percpu_sp;
|
|
ulong tmp, tmp1;
|
|
char buf[BUFSIZE];
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char name_buf[BUFSIZE];
|
|
|
|
handler = UNINITIALIZED;
|
|
|
|
irq_desc_addr = get_irq_desc_addr(irq);
|
|
if (!irq_desc_addr)
|
|
return;
|
|
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_action), KVADDR,
|
|
&action, sizeof(long), "irq_desc action", FAULT_ON_ERROR);
|
|
|
|
if (!action)
|
|
return;
|
|
|
|
if (!symbol_exists("kstat_irqs_cpu")) { /* for RHEL5 or earlier */
|
|
if (!(percpu_sp = per_cpu_symbol_search("kstat")))
|
|
return;
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (!(NUM_IN_BITMAP(cpus, i)))
|
|
continue;
|
|
|
|
tmp = percpu_sp->value + kt->__per_cpu_offset[i];
|
|
readmem(tmp + OFFSET(kernel_stat_irqs) + sizeof(uint) * irq,
|
|
KVADDR, &kstat_irq, sizeof(uint),
|
|
"kernel_stat irqs", FAULT_ON_ERROR);
|
|
kstat_irqs[i] = kstat_irq;
|
|
}
|
|
} else {
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_kstat_irqs),
|
|
KVADDR, &kstat_irqs_ptr, sizeof(long),
|
|
"irq_desc kstat_irqs", FAULT_ON_ERROR);
|
|
if (THIS_KERNEL_VERSION > LINUX(2,6,37)) {
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (!(NUM_IN_BITMAP(cpus, i)))
|
|
continue;
|
|
|
|
tmp = kstat_irqs_ptr + kt->__per_cpu_offset[i];
|
|
readmem(tmp, KVADDR, &kstat_irq, sizeof(uint),
|
|
"kernel_stat irqs", FAULT_ON_ERROR);
|
|
kstat_irqs[i] = kstat_irq;
|
|
}
|
|
} else
|
|
readmem(kstat_irqs_ptr, KVADDR, kstat_irqs,
|
|
sizeof(kstat_irqs), "kstat_irqs",
|
|
FAULT_ON_ERROR);
|
|
}
|
|
if (VALID_MEMBER(irq_desc_t_handler))
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_handler),
|
|
KVADDR, &handler, sizeof(long), "irq_desc handler",
|
|
FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_desc_t_chip))
|
|
readmem(irq_desc_addr + OFFSET(irq_desc_t_chip), KVADDR,
|
|
&handler, sizeof(long), "irq_desc chip",
|
|
FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(irq_data_chip))
|
|
readmem(irq_desc_addr + OFFSET(irq_data_chip), KVADDR,
|
|
&handler, sizeof(long), "irq_data chip",
|
|
FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, "%3d: ", irq);
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (hide_offline_cpu(i))
|
|
continue;
|
|
|
|
if (NUM_IN_BITMAP(cpus, i))
|
|
fprintf(fp, "%10u ", kstat_irqs[i]);
|
|
}
|
|
|
|
if (handler != UNINITIALIZED) {
|
|
if (VALID_MEMBER(hw_interrupt_type_typename)) {
|
|
readmem(handler+OFFSET(hw_interrupt_type_typename),
|
|
KVADDR, &tmp, sizeof(void *),
|
|
"hw_interrupt_type typename", FAULT_ON_ERROR);
|
|
|
|
BZERO(buf, BUFSIZE);
|
|
if (read_string(tmp, buf, BUFSIZE-1))
|
|
fprintf(fp, "%14s", buf);
|
|
}
|
|
else if (VALID_MEMBER(irq_chip_typename)) {
|
|
readmem(handler+OFFSET(irq_chip_typename),
|
|
KVADDR, &tmp, sizeof(void *),
|
|
"hw_interrupt_type typename", FAULT_ON_ERROR);
|
|
|
|
BZERO(buf, BUFSIZE);
|
|
if (read_string(tmp, buf, BUFSIZE-1))
|
|
fprintf(fp, "%8s", buf);
|
|
BZERO(buf1, BUFSIZE);
|
|
if (VALID_MEMBER(irq_desc_t_name))
|
|
readmem(irq_desc_addr+OFFSET(irq_desc_t_name),
|
|
KVADDR, &tmp1, sizeof(void *),
|
|
"irq_desc name", FAULT_ON_ERROR);
|
|
if (read_string(tmp1, buf1, BUFSIZE-1))
|
|
fprintf(fp, "-%-8s", buf1);
|
|
}
|
|
}
|
|
|
|
BZERO(name_buf, BUFSIZE);
|
|
|
|
while (action) {
|
|
readmem(action+OFFSET(irqaction_name), KVADDR,
|
|
&name, sizeof(void *),
|
|
"irqaction name", FAULT_ON_ERROR);
|
|
BZERO(buf2, BUFSIZE);
|
|
if (read_string(name, buf2, BUFSIZE-1)) {
|
|
if (strlen(name_buf) != 0)
|
|
strncat(name_buf, ",", 2);
|
|
strncat(name_buf, buf2, strlen(buf2));
|
|
}
|
|
|
|
readmem(action+OFFSET(irqaction_next), KVADDR,
|
|
&action, sizeof(void *),
|
|
"irqaction dev_id", FAULT_ON_ERROR);
|
|
}
|
|
|
|
fprintf(fp, " %s\n", name_buf);
|
|
}
|
|
|
|
/*
|
|
* Dump the earlier 2.2 Linux version's bottom-half essentials.
|
|
*/
|
|
static void
|
|
display_bh_1(void)
|
|
{
|
|
int i;
|
|
ulong bh_mask, bh_active;
|
|
ulong bh_base[32];
|
|
char buf[BUFSIZE];
|
|
|
|
get_symbol_data("bh_mask", sizeof(ulong), &bh_mask);
|
|
get_symbol_data("bh_active", sizeof(ulong), &bh_active);
|
|
readmem(symbol_value("bh_base"), KVADDR, bh_base, sizeof(void *) * 32,
|
|
"bh_base[32]", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, "BH_MASK BH_ACTIVE\n");
|
|
fprintf(fp, "%08lx %08lx\n", bh_mask, bh_active);
|
|
fprintf(fp, "\nBH_BASE %s\n",
|
|
mkstring(buf, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
|
|
for (i = 0; i < 32; i++) {
|
|
if (!bh_base[i])
|
|
continue;
|
|
fprintf(fp, " %2d %lx <%s>\n", i, bh_base[i],
|
|
value_to_symstr(bh_base[i], buf, 0));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Dump the 2.3-ish Linux version's bottom half essentials.
|
|
*/
|
|
static void
|
|
display_bh_2(void)
|
|
{
|
|
int i;
|
|
ulong bh_base[32];
|
|
struct softirq_state {
|
|
uint32_t active;
|
|
uint32_t mask;
|
|
} softirq_state;
|
|
struct softirq_action {
|
|
void *action;
|
|
void *data;
|
|
} softirq_vec[32];
|
|
char buf[BUFSIZE];
|
|
|
|
readmem(symbol_value("bh_base"), KVADDR, bh_base, sizeof(void *) * 32,
|
|
"bh_base[32]", FAULT_ON_ERROR);
|
|
|
|
readmem(symbol_value("softirq_vec"), KVADDR, softirq_vec,
|
|
sizeof(struct softirq_action) * 32,
|
|
"softirq_vec[32]", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, "CPU MASK ACTIVE\n");
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
readmem(symbol_value("softirq_state") +
|
|
(i * SIZE(softirq_state)), KVADDR,
|
|
&softirq_state, sizeof(struct softirq_state),
|
|
"softirq_state", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, " %-2d %08x %08x\n",
|
|
i, softirq_state.mask,
|
|
softirq_state.active);
|
|
}
|
|
|
|
fprintf(fp, "\nVEC %s\n",
|
|
mkstring(buf, VADDR_PRLEN, CENTER|LJUST, "ACTION"));
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (!softirq_vec[i].action)
|
|
continue;
|
|
|
|
fprintf(fp, " %-2d %lx <%s>\n", i,
|
|
(ulong)softirq_vec[i].action,
|
|
value_to_symstr((ulong)softirq_vec[i].action, buf, 0));
|
|
}
|
|
|
|
fprintf(fp, "\nBH_BASE %s\n",
|
|
mkstring(buf, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
|
|
for (i = 0; i < 32; i++) {
|
|
if (!bh_base[i])
|
|
continue;
|
|
fprintf(fp, " %2d %lx <%s>\n", i, bh_base[i],
|
|
value_to_symstr(bh_base[i], buf, 0));
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Dump the 2.4 Linux version's bottom half essentials.
|
|
*/
|
|
static void
|
|
display_bh_3(void)
|
|
{
|
|
int i;
|
|
ulong bh_base[32];
|
|
struct softirq_action {
|
|
void *action;
|
|
void *data;
|
|
} softirq_vec[32];
|
|
char buf[BUFSIZE];
|
|
uint active, mask;
|
|
ulong function;
|
|
|
|
readmem(symbol_value("bh_base"), KVADDR, bh_base, sizeof(void *) * 32,
|
|
"bh_base[32]", FAULT_ON_ERROR);
|
|
|
|
readmem(symbol_value("softirq_vec"), KVADDR, softirq_vec,
|
|
sizeof(struct softirq_action) * 32,
|
|
"softirq_vec[32]", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, "CPU MASK ACTIVE\n");
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
readmem(symbol_value("irq_stat") +
|
|
(i * SIZE(irq_cpustat_t)) +
|
|
OFFSET(irq_cpustat_t___softirq_active), KVADDR,
|
|
&active, sizeof(uint),
|
|
"__softirq_active", FAULT_ON_ERROR);
|
|
|
|
readmem(symbol_value("irq_stat") +
|
|
(i * SIZE(irq_cpustat_t)) +
|
|
OFFSET(irq_cpustat_t___softirq_mask), KVADDR,
|
|
&mask, sizeof(uint),
|
|
"__softirq_mask", FAULT_ON_ERROR);
|
|
|
|
fprintf(fp, " %-2d %08x %08x\n", i, mask, active);
|
|
}
|
|
|
|
fprintf(fp, "\nVEC %s\n",
|
|
mkstring(buf, VADDR_PRLEN, CENTER|LJUST, "ACTION"));
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
if (!softirq_vec[i].action)
|
|
continue;
|
|
|
|
fprintf(fp, " %-2d %lx ", i, (ulong)softirq_vec[i].action);
|
|
if (is_kernel_text((ulong)softirq_vec[i].action))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr((ulong)softirq_vec[i].action,
|
|
buf, 0));
|
|
else if (readmem((ulong)softirq_vec[i].action, KVADDR,
|
|
&function, sizeof(ulong), "action indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(function))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(function, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
fprintf(fp, "\nBH_BASE %s\n",
|
|
mkstring(buf, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
|
|
for (i = 0; i < 32; i++) {
|
|
if (!bh_base[i])
|
|
continue;
|
|
fprintf(fp, " %2d %lx ", i, bh_base[i]);
|
|
if (is_kernel_text(bh_base[i]))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(bh_base[i], buf, 0));
|
|
else if (readmem(bh_base[i], KVADDR, &function,
|
|
sizeof(ulong), "bh_base indirection",
|
|
RETURN_ON_ERROR|QUIET) && is_kernel_text(function))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(function, buf, 0));
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Dump the 2.6 Linux version's bottom half essentials.
|
|
*/
|
|
static void
|
|
display_bh_4(void)
|
|
{
|
|
int i, len;
|
|
char buf[BUFSIZE];
|
|
char *array;
|
|
ulong *p;
|
|
struct load_module *lm;
|
|
|
|
if (!(len = get_array_length("softirq_vec", NULL, 0)))
|
|
error(FATAL, "cannot determine softirq_vec array length\n");
|
|
|
|
fprintf(fp, "SOFTIRQ_VEC %s\n",
|
|
mkstring(buf, VADDR_PRLEN, CENTER|RJUST, "ACTION"));
|
|
|
|
array = GETBUF(SIZE(softirq_action) * (len+1));
|
|
|
|
readmem(symbol_value("softirq_vec"), KVADDR,
|
|
array, SIZE(softirq_action) * len,
|
|
"softirq_vec", FAULT_ON_ERROR);
|
|
|
|
for (i = 0, p = (ulong *)array; i < len; i++, p++) {
|
|
if (*p) {
|
|
fprintf(fp, " [%d]%s %s <%s>",
|
|
i, i < 10 ? space(4) : space(3),
|
|
mkstring(buf, VADDR_PRLEN,
|
|
LONG_HEX|CENTER|RJUST, MKSTR(*p)),
|
|
value_symbol(*p));
|
|
if (module_symbol(*p, NULL, &lm, NULL, 0))
|
|
fprintf(fp, " [%s]", lm->mod_name);
|
|
fprintf(fp, "\n");
|
|
}
|
|
if (SIZE(softirq_action) == (sizeof(void *)*2))
|
|
p++;
|
|
}
|
|
|
|
FREEBUF(array);
|
|
}
|
|
|
|
/*
|
|
* Dump the entries in the old- and new-style timer queues in
|
|
* chronological order.
|
|
*/
|
|
void
|
|
cmd_timer(void)
|
|
{
|
|
int c;
|
|
int rflag;
|
|
|
|
rflag = 0;
|
|
|
|
while ((c = getopt(argcnt, args, "r")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'r':
|
|
rflag = 1;
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (rflag)
|
|
dump_hrtimer_data();
|
|
else
|
|
dump_timer_data();
|
|
}
|
|
|
|
static void
|
|
dump_hrtimer_data(void)
|
|
{
|
|
int i, j;
|
|
int hrtimer_max_clock_bases, max_hrtimer_bases;
|
|
struct syment * hrtimer_bases;
|
|
|
|
hrtimer_max_clock_bases = 0;
|
|
max_hrtimer_bases = 0;
|
|
|
|
/*
|
|
* deside whether hrtimer is available and
|
|
* set hrtimer_max_clock_bases or max_hrtimer_bases.
|
|
* if both are not available, hrtimer is not available.
|
|
*/
|
|
if (VALID_STRUCT(hrtimer_clock_base)) {
|
|
hrtimer_max_clock_bases = 2;
|
|
if (symbol_exists("ktime_get_boottime"))
|
|
hrtimer_max_clock_bases = 3;
|
|
} else if (VALID_STRUCT(hrtimer_base)) {
|
|
max_hrtimer_bases = 2;
|
|
} else
|
|
option_not_supported('r');
|
|
|
|
hrtimer_bases = per_cpu_symbol_search("hrtimer_bases");
|
|
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (i)
|
|
fprintf(fp, "\n");
|
|
|
|
if (hide_offline_cpu(i)) {
|
|
fprintf(fp, "CPU: %d [OFFLINE]\n", i);
|
|
continue;
|
|
}
|
|
|
|
fprintf(fp, "CPU: %d ", i);
|
|
if (VALID_STRUCT(hrtimer_clock_base)) {
|
|
fprintf(fp, "HRTIMER_CPU_BASE: %lx\n",
|
|
(ulong)(hrtimer_bases->value +
|
|
kt->__per_cpu_offset[i]));
|
|
|
|
for (j = 0; j < hrtimer_max_clock_bases; j++) {
|
|
if (j)
|
|
fprintf(fp, "\n");
|
|
dump_hrtimer_clock_base(
|
|
(void *)(hrtimer_bases->value) +
|
|
kt->__per_cpu_offset[i], j);
|
|
}
|
|
} else {
|
|
fprintf(fp, "\n");
|
|
for (j = 0; j < max_hrtimer_bases; j++) {
|
|
if (j)
|
|
fprintf(fp, "\n");
|
|
dump_hrtimer_base(
|
|
(void *)(hrtimer_bases->value) +
|
|
kt->__per_cpu_offset[i], j);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int expires_len = -1;
|
|
static int softexpires_len = -1;
|
|
|
|
static void
|
|
dump_hrtimer_clock_base(const void *hrtimer_bases, const int num)
|
|
{
|
|
void *base;
|
|
ulonglong current_time, now;
|
|
ulonglong offset;
|
|
ulong get_time;
|
|
char buf[BUFSIZE];
|
|
|
|
base = (void *)hrtimer_bases + OFFSET(hrtimer_cpu_base_clock_base) +
|
|
SIZE(hrtimer_clock_base) * num;
|
|
readmem((ulong)(base + OFFSET(hrtimer_clock_base_get_time)), KVADDR,
|
|
&get_time, sizeof(get_time), "hrtimer_clock_base get_time",
|
|
FAULT_ON_ERROR);
|
|
fprintf(fp, " CLOCK: %d HRTIMER_CLOCK_BASE: %lx [%s]\n", num,
|
|
(ulong)base, value_to_symstr(get_time, buf, 0));
|
|
|
|
/* get current time(uptime) */
|
|
get_uptime(NULL, ¤t_time);
|
|
|
|
offset = 0;
|
|
if (VALID_MEMBER(hrtimer_clock_base_offset))
|
|
offset = ktime_to_ns(base + OFFSET(hrtimer_clock_base_offset));
|
|
now = current_time * 1000000000LL / machdep->hz + offset;
|
|
|
|
dump_active_timers(base, now);
|
|
}
|
|
|
|
static void
|
|
dump_hrtimer_base(const void *hrtimer_bases, const int num)
|
|
{
|
|
void *base;
|
|
ulonglong current_time, now;
|
|
ulong get_time;
|
|
char buf[BUFSIZE];
|
|
|
|
base = (void *)hrtimer_bases + SIZE(hrtimer_base) * num;
|
|
readmem((ulong)(base + OFFSET(hrtimer_base_get_time)), KVADDR,
|
|
&get_time, sizeof(get_time), "hrtimer_base get_time",
|
|
FAULT_ON_ERROR);
|
|
fprintf(fp, " CLOCK: %d HRTIMER_BASE: %lx [%s]\n", num,
|
|
(ulong)base, value_to_symstr(get_time, buf, 0));
|
|
|
|
/* get current time(uptime) */
|
|
get_uptime(NULL, ¤t_time);
|
|
now = current_time * 1000000000LL / machdep->hz;
|
|
|
|
dump_active_timers(base, now);
|
|
}
|
|
|
|
static void
|
|
dump_active_timers(const void *base, ulonglong now)
|
|
{
|
|
int next, i, t;
|
|
struct rb_node *curr;
|
|
int timer_cnt;
|
|
ulong *timer_list;
|
|
void *timer;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
char buf4[BUFSIZE];
|
|
|
|
next = 0;
|
|
timer_list = 0;
|
|
|
|
/* search hrtimers */
|
|
hq_open();
|
|
timer_cnt = 0;
|
|
next_one:
|
|
i = 0;
|
|
|
|
/* get the first node */
|
|
if (VALID_MEMBER(hrtimer_base_pending))
|
|
readmem((ulong)(base + OFFSET(hrtimer_base_pending) -
|
|
OFFSET(hrtimer_list) + OFFSET(hrtimer_node)),
|
|
KVADDR, &curr, sizeof(curr), "hrtimer_base pending",
|
|
FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(hrtimer_base_first))
|
|
readmem((ulong)(base + OFFSET(hrtimer_base_first)),
|
|
KVADDR, &curr, sizeof(curr), "hrtimer_base first",
|
|
FAULT_ON_ERROR);
|
|
else if (VALID_MEMBER(hrtimer_clock_base_first))
|
|
readmem((ulong)(base + OFFSET(hrtimer_clock_base_first)),
|
|
KVADDR, &curr, sizeof(curr), "hrtimer_clock_base first",
|
|
FAULT_ON_ERROR);
|
|
else
|
|
readmem((ulong)(base + OFFSET(hrtimer_clock_base_active) +
|
|
OFFSET(timerqueue_head_next)),
|
|
KVADDR, &curr, sizeof(curr), "hrtimer_clock base",
|
|
FAULT_ON_ERROR);
|
|
|
|
while (curr && i < next) {
|
|
curr = rb_next(curr);
|
|
i++;
|
|
}
|
|
|
|
if (curr) {
|
|
if (!hq_enter((ulong)curr)) {
|
|
error(INFO, "duplicate rb_node: %lx\n", curr);
|
|
return;
|
|
}
|
|
|
|
timer_cnt++;
|
|
next++;
|
|
goto next_one;
|
|
}
|
|
|
|
if (timer_cnt) {
|
|
timer_list = (ulong *)GETBUF(timer_cnt * sizeof(long));
|
|
timer_cnt = retrieve_list(timer_list, timer_cnt);
|
|
}
|
|
hq_close();
|
|
|
|
if (!timer_cnt) {
|
|
fprintf(fp, " (empty)\n");
|
|
return;
|
|
}
|
|
|
|
/* dump hrtimers */
|
|
/* print header */
|
|
expires_len = get_expires_len(timer_cnt, timer_list, 0);
|
|
if (expires_len < 7)
|
|
expires_len = 7;
|
|
softexpires_len = get_expires_len(timer_cnt, timer_list, 1);
|
|
|
|
if (softexpires_len > -1) {
|
|
if (softexpires_len < 11)
|
|
softexpires_len = 11;
|
|
fprintf(fp, " %s\n", mkstring(buf1, softexpires_len, CENTER|RJUST,
|
|
"CURRENT"));
|
|
sprintf(buf1, "%lld", now);
|
|
fprintf(fp, " %s\n", mkstring(buf1, softexpires_len,
|
|
CENTER|RJUST, NULL));
|
|
fprintf(fp, " %s %s %s %s\n",
|
|
mkstring(buf1, softexpires_len, CENTER|RJUST, "SOFTEXPIRES"),
|
|
mkstring(buf2, expires_len, CENTER|RJUST, "EXPIRES"),
|
|
mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "HRTIMER"),
|
|
mkstring(buf4, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
|
|
} else {
|
|
fprintf(fp, " %s\n", mkstring(buf1, expires_len, CENTER|RJUST,
|
|
"CURRENT"));
|
|
sprintf(buf1, "%lld", now);
|
|
fprintf(fp, " %s\n", mkstring(buf1, expires_len, CENTER|RJUST, NULL));
|
|
fprintf(fp, " %s %s %s\n",
|
|
mkstring(buf1, expires_len, CENTER|RJUST, "EXPIRES"),
|
|
mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "HRTIMER"),
|
|
mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
|
|
}
|
|
|
|
/* print timers */
|
|
for (t = 0; t < timer_cnt; t++) {
|
|
if (VALID_MEMBER(timerqueue_node_node))
|
|
timer = (void *)(timer_list[t] -
|
|
OFFSET(timerqueue_node_node) -
|
|
OFFSET(hrtimer_node));
|
|
else
|
|
timer = (void *)(timer_list[t] - OFFSET(hrtimer_node));
|
|
|
|
print_timer(timer);
|
|
}
|
|
}
|
|
|
|
static int
|
|
get_expires_len(const int timer_cnt, const ulong *timer_list, const int getsoft)
|
|
{
|
|
void *last_timer;
|
|
char buf[BUFSIZE];
|
|
ulonglong softexpires, expires;
|
|
int len;
|
|
|
|
len = -1;
|
|
|
|
if (!timer_cnt)
|
|
return len;
|
|
|
|
if (VALID_MEMBER(timerqueue_node_node))
|
|
last_timer = (void *)(timer_list[timer_cnt - 1] -
|
|
OFFSET(timerqueue_node_node) -
|
|
OFFSET(hrtimer_node));
|
|
else
|
|
last_timer = (void *)(timer_list[timer_cnt -1] -
|
|
OFFSET(hrtimer_node));
|
|
|
|
if (getsoft) {
|
|
/* soft expires exist*/
|
|
if (VALID_MEMBER(hrtimer_softexpires)) {
|
|
softexpires = ktime_to_ns(last_timer +
|
|
OFFSET(hrtimer_softexpires));
|
|
sprintf(buf, "%lld", softexpires);
|
|
len = strlen(buf);
|
|
}
|
|
} else {
|
|
if (VALID_MEMBER(hrtimer_expires))
|
|
expires = ktime_to_ns(last_timer + OFFSET(hrtimer_expires));
|
|
else
|
|
expires = ktime_to_ns(last_timer + OFFSET(hrtimer_node) +
|
|
OFFSET(timerqueue_node_expires));
|
|
|
|
sprintf(buf, "%lld", expires);
|
|
len = strlen(buf);
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
/*
|
|
* print hrtimer and its related information
|
|
*/
|
|
static void
|
|
print_timer(const void *timer)
|
|
{
|
|
ulonglong softexpires, expires;
|
|
|
|
ulong function;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
|
|
/* align information */
|
|
fprintf(fp, " ");
|
|
|
|
if (!accessible((ulong)timer)) {
|
|
fprintf(fp, "(destroyed timer)\n");
|
|
return;
|
|
}
|
|
|
|
if (VALID_MEMBER(hrtimer_expires))
|
|
expires = ktime_to_ns(timer + OFFSET(hrtimer_expires));
|
|
else
|
|
expires = ktime_to_ns(timer + OFFSET(hrtimer_node) +
|
|
OFFSET(timerqueue_node_expires));
|
|
|
|
if (VALID_MEMBER(hrtimer_softexpires)) {
|
|
softexpires = ktime_to_ns(timer + OFFSET(hrtimer_softexpires));
|
|
sprintf(buf1, "%lld-%lld", softexpires, expires);
|
|
}
|
|
|
|
if (VALID_MEMBER(hrtimer_softexpires)) {
|
|
softexpires = ktime_to_ns(timer + OFFSET(hrtimer_softexpires));
|
|
sprintf(buf1, "%lld", softexpires);
|
|
fprintf(fp, "%s ",
|
|
mkstring(buf2, softexpires_len, CENTER|RJUST, buf1));
|
|
}
|
|
|
|
sprintf(buf1, "%lld", expires);
|
|
fprintf(fp, "%s ", mkstring(buf2, expires_len, CENTER|RJUST, buf1));
|
|
|
|
fprintf(fp, "%lx ", (ulong)timer);
|
|
|
|
if (readmem((ulong)(timer + OFFSET(hrtimer_function)), KVADDR, &function,
|
|
sizeof(function), "hrtimer function", QUIET|RETURN_ON_ERROR)) {
|
|
fprintf(fp, "%lx ", function);
|
|
fprintf(fp ,"<%s>", value_to_symstr(function, buf3, 0));
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* convert ktime to ns, only need the address of ktime
|
|
*/
|
|
static ulonglong
|
|
ktime_to_ns(const void *ktime)
|
|
{
|
|
ulonglong ns;
|
|
|
|
ns = 0;
|
|
|
|
if (!accessible((ulong)ktime))
|
|
return ns;
|
|
|
|
if (VALID_MEMBER(ktime_t_tv64)) {
|
|
readmem((ulong)ktime + OFFSET(ktime_t_tv64), KVADDR, &ns,
|
|
sizeof(ns), "ktime_t tv64", QUIET|RETURN_ON_ERROR);
|
|
} else {
|
|
uint32_t sec, nsec;
|
|
|
|
sec = 0;
|
|
nsec = 0;
|
|
|
|
readmem((ulong)ktime + OFFSET(ktime_t_sec), KVADDR, &sec,
|
|
sizeof(sec), "ktime_t sec", QUIET|RETURN_ON_ERROR);
|
|
|
|
readmem((ulong)ktime + OFFSET(ktime_t_nsec), KVADDR, &nsec,
|
|
sizeof(nsec), "ktime_t nsec", QUIET|RETURN_ON_ERROR);
|
|
|
|
ns = sec * 1000000000L + nsec;
|
|
}
|
|
|
|
return ns;
|
|
}
|
|
|
|
/*
|
|
* Display the pending timer queue entries, both the old and new-style.
|
|
*/
|
|
struct timer_data {
|
|
ulong address;
|
|
ulong expires;
|
|
ulong function;
|
|
};
|
|
|
|
struct tv_range {
|
|
ulong base;
|
|
ulong end;
|
|
};
|
|
|
|
#define TVN (6)
|
|
|
|
static void
|
|
dump_timer_data(void)
|
|
{
|
|
int i;
|
|
ulong timer_active;
|
|
struct timer_struct {
|
|
unsigned long expires;
|
|
void *fn;
|
|
} timer_table[32];
|
|
char buf[BUFSIZE];
|
|
char buf1[BUFSIZE];
|
|
struct timer_struct *tp;
|
|
ulong mask, highest, function;
|
|
ulong jiffies, timer_jiffies;
|
|
ulong *vec;
|
|
long count;
|
|
int vec_root_size, vec_size;
|
|
struct timer_data *td;
|
|
int flen, tdx, old_timers_exist;
|
|
struct tv_range tv[TVN];
|
|
|
|
if (per_cpu_symbol_search("per_cpu__tvec_bases")) {
|
|
dump_timer_data_tvec_bases_v2();
|
|
return;
|
|
} else if (symbol_exists("tvec_bases")) {
|
|
dump_timer_data_tvec_bases_v1();
|
|
return;
|
|
}
|
|
|
|
BZERO(tv, sizeof(struct tv_range) * TVN);
|
|
|
|
vec_root_size = (i = ARRAY_LENGTH(timer_vec_root_vec)) ?
|
|
i : get_array_length("timer_vec_root.vec",
|
|
NULL, SIZE(list_head));
|
|
vec_size = (i = ARRAY_LENGTH(timer_vec_vec)) ?
|
|
i : get_array_length("timer_vec.vec", NULL, SIZE(list_head));
|
|
|
|
vec = (ulong *)GETBUF(SIZE(list_head) * MAX(vec_root_size, vec_size));
|
|
|
|
if (symbol_exists("timer_active") && symbol_exists("timer_table")) {
|
|
get_symbol_data("timer_active", sizeof(ulong), &timer_active);
|
|
readmem(symbol_value("timer_table"), KVADDR, &timer_table,
|
|
sizeof(struct timer_struct) * 32, "timer_table[32]",
|
|
FAULT_ON_ERROR);
|
|
old_timers_exist = TRUE;
|
|
} else
|
|
old_timers_exist = FALSE;
|
|
|
|
/*
|
|
* Get rough count first, and then gather a bunch of timer_data
|
|
* structs to stuff in a sortable array.
|
|
*/
|
|
|
|
count = 0;
|
|
for (mask = 1, tp = timer_table+0; old_timers_exist && mask;
|
|
tp++, mask += mask) {
|
|
if (mask > timer_active)
|
|
break;
|
|
if (!(mask & timer_active))
|
|
continue;
|
|
count++;
|
|
}
|
|
|
|
init_tv_ranges(tv, vec_root_size, vec_size, 0);
|
|
|
|
count += do_timer_list(symbol_value("tv1") + OFFSET(timer_vec_root_vec),
|
|
vec_root_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(symbol_value("tv2") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(symbol_value("tv3") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(symbol_value("tv4") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(symbol_value("tv4") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
|
|
td = (struct timer_data *)
|
|
GETBUF((count*2) * sizeof(struct timer_data));
|
|
tdx = 0;
|
|
|
|
get_symbol_data("jiffies", sizeof(ulong), &jiffies);
|
|
get_symbol_data("timer_jiffies", sizeof(ulong), &timer_jiffies);
|
|
if (old_timers_exist)
|
|
get_symbol_data("timer_active", sizeof(ulong), &timer_active);
|
|
|
|
highest = 0;
|
|
for (i = 0, mask = 1, tp = timer_table+0; old_timers_exist && mask;
|
|
i++, tp++, mask += mask) {
|
|
if (mask > timer_active)
|
|
break;
|
|
|
|
if (!(mask & timer_active))
|
|
continue;
|
|
|
|
td[tdx].address = i;
|
|
td[tdx].expires = tp->expires;
|
|
td[tdx].function = (ulong)tp->fn;
|
|
if (td[tdx].expires > highest)
|
|
highest = td[tdx].expires;
|
|
tdx++;
|
|
}
|
|
|
|
do_timer_list(symbol_value("tv1") + OFFSET(timer_vec_root_vec),
|
|
vec_root_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(symbol_value("tv2") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(symbol_value("tv3") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(symbol_value("tv4") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
tdx = do_timer_list(symbol_value("tv5") + OFFSET(timer_vec_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
|
|
qsort(td, tdx, sizeof(struct timer_data), compare_timer_data);
|
|
|
|
/*
|
|
* Because the jiffies values can fluctuate wildly from dump to
|
|
* dump, try to use the appropriate amount of space...
|
|
*/
|
|
sprintf(buf, "%ld", highest);
|
|
flen = MAX(strlen(buf), strlen("JIFFIES"));
|
|
fprintf(fp, "%s\n", mkstring(buf, flen, CENTER|LJUST, "JIFFIES"));
|
|
fprintf(fp, "%s\n", mkstring(buf, flen, RJUST|LONG_DEC,MKSTR(jiffies)));
|
|
|
|
fprintf(fp, "%s TIMER_LIST/TABLE FUNCTION\n",
|
|
mkstring(buf, flen, CENTER|LJUST, "EXPIRES"));
|
|
|
|
for (i = 0; i < tdx; i++) {
|
|
fprintf(fp, "%s",
|
|
mkstring(buf, flen, RJUST|LONG_DEC, MKSTR(td[i].expires)));
|
|
|
|
if (td[i].address < 32) {
|
|
sprintf(buf, "timer_table[%ld]", td[i].address);
|
|
fprintf(fp, " %s ",
|
|
mkstring(buf, 16, CENTER|LJUST, NULL));
|
|
} else {
|
|
mkstring(buf1, VADDR_PRLEN, RJUST|LONG_HEX,
|
|
MKSTR(td[i].address));
|
|
fprintf(fp, " %s ", mkstring(buf, 16, CENTER, buf1));
|
|
}
|
|
|
|
if (is_kernel_text(td[i].function))
|
|
fprintf(fp, "%s <%s>\n",
|
|
mkstring(buf1, VADDR_PRLEN, RJUST|LONG_HEX,
|
|
MKSTR(td[i].function)),
|
|
value_to_symstr(td[i].function, buf, 0));
|
|
else {
|
|
fprintf(fp, "%s ",
|
|
mkstring(buf1, VADDR_PRLEN, RJUST|LONG_HEX,
|
|
MKSTR(td[i].function)));
|
|
if (readmem(td[i].function, KVADDR, &function,
|
|
sizeof(ulong), "timer function",
|
|
RETURN_ON_ERROR|QUIET)) {
|
|
if (is_kernel_text(function))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(function, buf, 0));
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Newer per-cpu timers, using "tvec_bases".
|
|
*/
|
|
|
|
static void
|
|
dump_timer_data_tvec_bases_v1(void)
|
|
{
|
|
int i, cpu, tdx, flen;
|
|
struct timer_data *td;
|
|
int vec_root_size, vec_size;
|
|
struct tv_range tv[TVN];
|
|
ulong *vec, jiffies, highest, function;
|
|
long count;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
|
|
kt->flags |= TVEC_BASES_V1;
|
|
|
|
/*
|
|
*/
|
|
vec_root_size = (i = ARRAY_LENGTH(tvec_root_s_vec)) ?
|
|
i : get_array_length("tvec_root_s.vec", NULL, SIZE(list_head));
|
|
vec_size = (i = ARRAY_LENGTH(tvec_s_vec)) ?
|
|
i : get_array_length("tvec_s.vec", NULL, SIZE(list_head));
|
|
vec = (ulong *)GETBUF(SIZE(list_head) * MAX(vec_root_size, vec_size));
|
|
|
|
cpu = 0;
|
|
|
|
next_cpu:
|
|
|
|
count = 0;
|
|
td = (struct timer_data *)NULL;
|
|
|
|
BZERO(tv, sizeof(struct tv_range) * TVN);
|
|
|
|
init_tv_ranges(tv, vec_root_size, vec_size, cpu);
|
|
|
|
count += do_timer_list(tv[1].base + OFFSET(tvec_root_s_vec),
|
|
vec_root_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[2].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[3].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[4].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[5].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
|
|
if (count)
|
|
td = (struct timer_data *)
|
|
GETBUF((count*2) * sizeof(struct timer_data));
|
|
tdx = 0;
|
|
highest = 0;
|
|
get_symbol_data("jiffies", sizeof(ulong), &jiffies);
|
|
|
|
do_timer_list(tv[1].base + OFFSET(tvec_root_s_vec),
|
|
vec_root_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(tv[2].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(tv[3].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(tv[4].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
tdx = do_timer_list(tv[5].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
|
|
qsort(td, tdx, sizeof(struct timer_data), compare_timer_data);
|
|
|
|
fprintf(fp, "TVEC_BASES[%d]: %lx\n", cpu,
|
|
symbol_value("tvec_bases") + (SIZE(tvec_t_base_s) * cpu));
|
|
|
|
sprintf(buf1, "%ld", highest);
|
|
flen = MAX(strlen(buf1), strlen("JIFFIES"));
|
|
fprintf(fp, "%s\n", mkstring(buf1,flen, CENTER|RJUST, "JIFFIES"));
|
|
fprintf(fp, "%s\n", mkstring(buf1,flen,
|
|
RJUST|LONG_DEC,MKSTR(jiffies)));
|
|
|
|
fprintf(fp, "%s %s %s\n",
|
|
mkstring(buf1, flen, CENTER|RJUST, "EXPIRES"),
|
|
mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "TIMER_LIST"),
|
|
mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
|
|
|
|
for (i = 0; i < tdx; i++) {
|
|
fprintf(fp, "%s",
|
|
mkstring(buf1, flen, RJUST|LONG_DEC, MKSTR(td[i].expires)));
|
|
|
|
fprintf(fp, " %s ", mkstring(buf1,
|
|
MAX(VADDR_PRLEN, strlen("TIMER_LIST")),
|
|
RJUST|CENTER|LONG_HEX, MKSTR(td[i].address)));
|
|
|
|
if (is_kernel_text(td[i].function)) {
|
|
fprintf(fp, "%s <%s>\n",
|
|
mkstring(buf2, VADDR_PRLEN, RJUST|LONG_HEX,
|
|
MKSTR(td[i].function)),
|
|
value_to_symstr(td[i].function, buf1, 0));
|
|
} else {
|
|
fprintf(fp, "%s ", mkstring(buf1, VADDR_PRLEN,
|
|
RJUST|LONG_HEX, MKSTR(td[i].function)));
|
|
if (readmem(td[i].function, KVADDR, &function,
|
|
sizeof(ulong), "timer function",
|
|
RETURN_ON_ERROR|QUIET)) {
|
|
if (is_kernel_text(function))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(function, buf1, 0));
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
if (td)
|
|
FREEBUF(td);
|
|
|
|
if (++cpu < kt->cpus)
|
|
goto next_cpu;
|
|
}
|
|
|
|
/*
|
|
* 2.6 per-cpu timers, using "per_cpu__tvec_bases".
|
|
*/
|
|
|
|
static void
|
|
dump_timer_data_tvec_bases_v2(void)
|
|
{
|
|
int i, cpu, tdx, flen;
|
|
struct timer_data *td;
|
|
int vec_root_size, vec_size;
|
|
struct tv_range tv[TVN];
|
|
ulong *vec, jiffies, highest, function;
|
|
ulong tvec_bases;
|
|
long count;
|
|
struct syment *sp;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
|
|
kt->flags |= TVEC_BASES_V2;
|
|
|
|
/*
|
|
*/
|
|
vec_root_size = (i = ARRAY_LENGTH(tvec_root_s_vec)) ?
|
|
i : get_array_length("tvec_root_s.vec", NULL, SIZE(list_head));
|
|
if (!vec_root_size &&
|
|
(i = get_array_length("tvec_root.vec", NULL, SIZE(list_head))))
|
|
vec_root_size = i;
|
|
if (!vec_root_size)
|
|
error(FATAL, "cannot determine tvec_root.vec[] array size\n");
|
|
|
|
vec_size = (i = ARRAY_LENGTH(tvec_s_vec)) ?
|
|
i : get_array_length("tvec_s.vec", NULL, SIZE(list_head));
|
|
if (!vec_size &&
|
|
(i = get_array_length("tvec.vec", NULL, SIZE(list_head))))
|
|
vec_size = i;
|
|
if (!vec_size)
|
|
error(FATAL, "cannot determine tvec.vec[] array size\n");
|
|
|
|
vec = (ulong *)GETBUF(SIZE(list_head) * MAX(vec_root_size, vec_size));
|
|
cpu = 0;
|
|
|
|
next_cpu:
|
|
/*
|
|
* hide data of offline cpu and goto next cpu
|
|
*/
|
|
|
|
if (hide_offline_cpu(cpu)) {
|
|
fprintf(fp, "TVEC_BASES[%d]: [OFFLINE]\n", cpu);
|
|
if (++cpu < kt->cpus)
|
|
goto next_cpu;
|
|
}
|
|
|
|
|
|
count = 0;
|
|
td = (struct timer_data *)NULL;
|
|
|
|
BZERO(tv, sizeof(struct tv_range) * TVN);
|
|
|
|
init_tv_ranges(tv, vec_root_size, vec_size, cpu);
|
|
|
|
count += do_timer_list(tv[1].base + OFFSET(tvec_root_s_vec),
|
|
vec_root_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[2].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[3].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[4].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
count += do_timer_list(tv[5].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, NULL, NULL, tv);
|
|
|
|
if (count)
|
|
td = (struct timer_data *)
|
|
GETBUF((count*2) * sizeof(struct timer_data));
|
|
tdx = 0;
|
|
highest = 0;
|
|
get_symbol_data("jiffies", sizeof(ulong), &jiffies);
|
|
|
|
do_timer_list(tv[1].base + OFFSET(tvec_root_s_vec),
|
|
vec_root_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(tv[2].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(tv[3].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
do_timer_list(tv[4].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
tdx = do_timer_list(tv[5].base + OFFSET(tvec_s_vec),
|
|
vec_size, vec, (void *)td, &highest, tv);
|
|
|
|
qsort(td, tdx, sizeof(struct timer_data), compare_timer_data);
|
|
|
|
sp = per_cpu_symbol_search("per_cpu__tvec_bases");
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
tvec_bases = sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
tvec_bases = sp->value;
|
|
|
|
if (symbol_exists("boot_tvec_bases")) {
|
|
readmem(tvec_bases, KVADDR, &tvec_bases, sizeof(void *),
|
|
"per-cpu tvec_bases", FAULT_ON_ERROR);
|
|
}
|
|
|
|
fprintf(fp, "TVEC_BASES[%d]: %lx\n", cpu, tvec_bases);
|
|
|
|
sprintf(buf1, "%ld", highest);
|
|
flen = MAX(strlen(buf1), strlen("JIFFIES"));
|
|
fprintf(fp, "%s\n", mkstring(buf1,flen, CENTER|RJUST, "JIFFIES"));
|
|
fprintf(fp, "%s\n", mkstring(buf1,flen,
|
|
RJUST|LONG_DEC,MKSTR(jiffies)));
|
|
|
|
fprintf(fp, "%s %s %s\n",
|
|
mkstring(buf1, flen, CENTER|RJUST, "EXPIRES"),
|
|
mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "TIMER_LIST"),
|
|
mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
|
|
|
|
for (i = 0; i < tdx; i++) {
|
|
fprintf(fp, "%s",
|
|
mkstring(buf1, flen, RJUST|LONG_DEC, MKSTR(td[i].expires)));
|
|
|
|
fprintf(fp, " %s ", mkstring(buf1,
|
|
MAX(VADDR_PRLEN, strlen("TIMER_LIST")),
|
|
RJUST|CENTER|LONG_HEX, MKSTR(td[i].address)));
|
|
|
|
if (is_kernel_text(td[i].function)) {
|
|
fprintf(fp, "%s <%s>\n",
|
|
mkstring(buf2, VADDR_PRLEN, RJUST|LONG_HEX,
|
|
MKSTR(td[i].function)),
|
|
value_to_symstr(td[i].function, buf1, 0));
|
|
} else {
|
|
fprintf(fp, "%s ", mkstring(buf1, VADDR_PRLEN,
|
|
RJUST|LONG_HEX, MKSTR(td[i].function)));
|
|
if (readmem(td[i].function, KVADDR, &function,
|
|
sizeof(ulong), "timer function",
|
|
RETURN_ON_ERROR|QUIET)) {
|
|
if (is_kernel_text(function))
|
|
fprintf(fp, "<%s>",
|
|
value_to_symstr(function, buf1, 0));
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
if (td)
|
|
FREEBUF(td);
|
|
|
|
if (++cpu < kt->cpus)
|
|
goto next_cpu;
|
|
}
|
|
|
|
/*
|
|
* The comparison function must return an integer less than,
|
|
* equal to, or greater than zero if the first argument is
|
|
* considered to be respectively less than, equal to, or
|
|
* greater than the second. If two members compare as equal,
|
|
* their order in the sorted array is undefined.
|
|
*/
|
|
|
|
static int
|
|
compare_timer_data(const void *v1, const void *v2)
|
|
{
|
|
struct timer_data *t1, *t2;
|
|
|
|
t1 = (struct timer_data *)v1;
|
|
t2 = (struct timer_data *)v2;
|
|
|
|
return (t1->expires < t2->expires ? -1 :
|
|
t1->expires == t2->expires ? 0 : 1);
|
|
}
|
|
|
|
/*
|
|
* Create the address range for each of the timer vectors.
|
|
*/
|
|
static void
|
|
init_tv_ranges(struct tv_range *tv, int vec_root_size, int vec_size, int cpu)
|
|
{
|
|
ulong tvec_bases;
|
|
struct syment *sp;
|
|
|
|
if (kt->flags & TVEC_BASES_V1) {
|
|
tv[1].base = symbol_value("tvec_bases") +
|
|
(SIZE(tvec_t_base_s) * cpu) +
|
|
OFFSET(tvec_t_base_s_tv1);
|
|
tv[1].end = tv[1].base + SIZE(tvec_root_s);
|
|
|
|
tv[2].base = tv[1].end;
|
|
tv[2].end = tv[2].base + SIZE(tvec_s);
|
|
|
|
tv[3].base = tv[2].end;
|
|
tv[3].end = tv[3].base + SIZE(tvec_s);
|
|
|
|
tv[4].base = tv[3].end;
|
|
tv[4].end = tv[4].base + SIZE(tvec_s);
|
|
|
|
tv[5].base = tv[4].end;
|
|
tv[5].end = tv[5].base + SIZE(tvec_s);
|
|
} else if (kt->flags & TVEC_BASES_V2) {
|
|
sp = per_cpu_symbol_search("per_cpu__tvec_bases");
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
tvec_bases = sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
tvec_bases = sp->value;
|
|
|
|
if (symbol_exists("boot_tvec_bases")) {
|
|
readmem(tvec_bases, KVADDR, &tvec_bases, sizeof(void *),
|
|
"per-cpu tvec_bases", FAULT_ON_ERROR);
|
|
}
|
|
|
|
tv[1].base = tvec_bases +
|
|
OFFSET(tvec_t_base_s_tv1);
|
|
tv[1].end = tv[1].base + SIZE(tvec_root_s);
|
|
|
|
tv[2].base = tv[1].end;
|
|
tv[2].end = tv[2].base + SIZE(tvec_s);
|
|
|
|
tv[3].base = tv[2].end;
|
|
tv[3].end = tv[3].base + SIZE(tvec_s);
|
|
|
|
tv[4].base = tv[3].end;
|
|
tv[4].end = tv[4].base + SIZE(tvec_s);
|
|
|
|
tv[5].base = tv[4].end;
|
|
tv[5].end = tv[5].base + SIZE(tvec_s);
|
|
} else {
|
|
tv[1].base = symbol_value("tv1");
|
|
tv[1].end = tv[1].base + SIZE(timer_vec_root);
|
|
|
|
tv[2].base = symbol_value("tv2");
|
|
tv[2].end = tv[2].base + SIZE(timer_vec);
|
|
|
|
tv[3].base = symbol_value("tv3");
|
|
tv[3].end = tv[3].base + SIZE(timer_vec);
|
|
|
|
tv[4].base = symbol_value("tv4");
|
|
tv[4].end = tv[4].base + SIZE(timer_vec);
|
|
|
|
tv[5].base = symbol_value("tv5");
|
|
tv[5].end = tv[5].base + SIZE(timer_vec);
|
|
}
|
|
}
|
|
|
|
#define IN_TV_RANGE(vaddr) \
|
|
((((vaddr) >= tv[1].base) && ((vaddr) < tv[1].end)) || \
|
|
(((vaddr) >= tv[2].base) && ((vaddr) < tv[2].end)) || \
|
|
(((vaddr) >= tv[3].base) && ((vaddr) < tv[3].end)) || \
|
|
(((vaddr) >= tv[4].base) && ((vaddr) < tv[4].end)) || \
|
|
(((vaddr) >= tv[5].base) && ((vaddr) < tv[5].end)))
|
|
|
|
/*
|
|
* Count, or stash, the entries of a linked timer_list -- depending
|
|
* upon the option value.
|
|
*/
|
|
static int
|
|
do_timer_list(ulong vec_kvaddr,
|
|
int size,
|
|
ulong *vec,
|
|
void *option,
|
|
ulong *highest,
|
|
struct tv_range *tv)
|
|
{
|
|
int i, t;
|
|
int count, tdx;
|
|
ulong expires, function;
|
|
struct timer_data *td;
|
|
char *timer_list_buf;
|
|
ulong *timer_list;
|
|
int timer_cnt;
|
|
struct list_data list_data, *ld;
|
|
long sz;
|
|
ulong offset;
|
|
|
|
tdx = 0;
|
|
td = option ? (struct timer_data *)option : NULL;
|
|
if (td) {
|
|
while (td[tdx].function)
|
|
tdx++;
|
|
}
|
|
|
|
if (VALID_MEMBER(timer_list_list))
|
|
sz = SIZE(list_head) * size;
|
|
else if (VALID_MEMBER(timer_list_entry))
|
|
sz = SIZE(list_head) * size;
|
|
else
|
|
sz = sizeof(ulong) * size;
|
|
|
|
readmem(vec_kvaddr, KVADDR, vec, sz, "timer_list vec array",
|
|
FAULT_ON_ERROR);
|
|
|
|
if (VALID_MEMBER(timer_list_list)) {
|
|
offset = OFFSET(timer_list_list);
|
|
goto new_timer_list_format;
|
|
}
|
|
|
|
if (VALID_MEMBER(timer_list_entry)) {
|
|
offset = OFFSET(timer_list_entry);
|
|
goto new_timer_list_format;
|
|
}
|
|
|
|
if (VALID_MEMBER(timer_list_next) >= 0)
|
|
offset = OFFSET(timer_list_next);
|
|
else
|
|
error(FATAL, "no timer_list next, list, or entry members?\n");
|
|
|
|
ld = &list_data;
|
|
timer_list_buf = GETBUF(SIZE(timer_list));
|
|
|
|
for (i = count = 0; i < size; i++) {
|
|
if (vec[i]) {
|
|
BZERO(ld, sizeof(struct list_data));
|
|
ld->start = vec[i];
|
|
ld->member_offset = offset;
|
|
|
|
hq_open();
|
|
timer_cnt = do_list(ld);
|
|
if (!timer_cnt)
|
|
continue;
|
|
timer_list = (ulong *)GETBUF(timer_cnt * sizeof(ulong));
|
|
timer_cnt = retrieve_list(timer_list, timer_cnt);
|
|
hq_close();
|
|
|
|
for (t = 0; t < timer_cnt; t++) {
|
|
readmem(timer_list[t], KVADDR, timer_list_buf,
|
|
SIZE(timer_list), "timer_list buffer",
|
|
FAULT_ON_ERROR);
|
|
|
|
expires = ULONG(timer_list_buf +
|
|
OFFSET(timer_list_expires));
|
|
function = ULONG(timer_list_buf +
|
|
OFFSET(timer_list_function));
|
|
|
|
if (td) {
|
|
td[tdx].address = timer_list[t];
|
|
td[tdx].expires = expires;
|
|
td[tdx].function = function;
|
|
if (highest && (expires > *highest))
|
|
*highest = expires;
|
|
tdx++;
|
|
}
|
|
}
|
|
FREEBUF(timer_list);
|
|
count += timer_cnt;
|
|
}
|
|
}
|
|
|
|
FREEBUF(timer_list_buf);
|
|
|
|
return(td ? tdx : count);
|
|
|
|
new_timer_list_format:
|
|
|
|
ld = &list_data;
|
|
timer_list_buf = GETBUF(SIZE(timer_list));
|
|
|
|
for (i = count = 0; i < (size*2); i += 2,
|
|
vec_kvaddr += SIZE(list_head)) {
|
|
|
|
if (vec[i] == vec_kvaddr)
|
|
continue;
|
|
|
|
BZERO(ld, sizeof(struct list_data));
|
|
ld->start = vec[i];
|
|
ld->list_head_offset = offset;
|
|
ld->end = vec_kvaddr;
|
|
ld->flags = RETURN_ON_LIST_ERROR;
|
|
|
|
hq_open();
|
|
if ((timer_cnt = do_list(ld)) == -1) {
|
|
/* Ignore chains with errors */
|
|
error(INFO,
|
|
"ignoring faulty timer list at index %d of timer array\n",
|
|
i/2);
|
|
continue;
|
|
}
|
|
if (!timer_cnt)
|
|
continue;
|
|
timer_list = (ulong *)GETBUF(timer_cnt * sizeof(ulong));
|
|
timer_cnt = retrieve_list(timer_list, timer_cnt);
|
|
hq_close();
|
|
|
|
for (t = 0; t < timer_cnt; t++) {
|
|
if (IN_TV_RANGE(timer_list[t]))
|
|
break;
|
|
|
|
count++;
|
|
|
|
readmem(timer_list[t], KVADDR, timer_list_buf,
|
|
SIZE(timer_list), "timer_list buffer",
|
|
FAULT_ON_ERROR);
|
|
|
|
expires = ULONG(timer_list_buf +
|
|
OFFSET(timer_list_expires));
|
|
function = ULONG(timer_list_buf +
|
|
OFFSET(timer_list_function));
|
|
|
|
if (td) {
|
|
td[tdx].address = timer_list[t];
|
|
td[tdx].expires = expires;
|
|
td[tdx].function = function;
|
|
if (highest && (expires > *highest))
|
|
*highest = expires;
|
|
tdx++;
|
|
}
|
|
}
|
|
}
|
|
|
|
FREEBUF(timer_list_buf);
|
|
|
|
return(td ? tdx : count);
|
|
}
|
|
|
|
/*
|
|
* Panic a live system by exploiting this code in do_exit():
|
|
*
|
|
* if (!tsk->pid)
|
|
* panic("Attempted to kill the idle task!");
|
|
*
|
|
* by writing a zero to this task's pid number. If the write
|
|
* succeeds, the subsequent exit() call will invoke the panic.
|
|
*/
|
|
static void
|
|
panic_this_kernel(void)
|
|
{
|
|
pid_t zero_pid = 0;
|
|
|
|
if (DUMPFILE())
|
|
error(FATAL, "cannot panic a dumpfile!\n");
|
|
|
|
if (!(pc->flags & MFD_RDWR) || (pc->flags & MEMMOD))
|
|
error(FATAL, "cannot write to %s\n", pc->live_memsrc);
|
|
|
|
writemem(pid_to_task(pc->program_pid) + OFFSET(task_struct_pid), KVADDR,
|
|
&zero_pid, sizeof(pid_t), "zero pid", FAULT_ON_ERROR);
|
|
|
|
clean_exit(0);
|
|
}
|
|
|
|
/*
|
|
* Dump the list of entries on a wait queue, taking into account the two
|
|
* different definitions: wait_queue vs. __wait_queue (wait_queue_t).
|
|
*/
|
|
void
|
|
cmd_waitq(void)
|
|
{
|
|
ulong q = 0;
|
|
char *wq_name = NULL; /* name of symbol which is a waitq */
|
|
char *wq_struct = NULL; /* struct containing the waitq */
|
|
char *wq_member = NULL; /* member of struct which is a waitq */
|
|
int recd_address = 0;
|
|
|
|
if (argcnt < 2 || argcnt > 3) {
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
}
|
|
|
|
if (IS_A_NUMBER(args[1])) {
|
|
q = htol(args[1], FAULT_ON_ERROR, NULL);
|
|
recd_address = 1;
|
|
} else {
|
|
/*
|
|
* We weren't given a number... see if it is the name of
|
|
* a symbol or and struct.member format.
|
|
*/
|
|
char *dot;
|
|
|
|
dot = strstr(args[1], ".");
|
|
if (dot == NULL) {
|
|
wq_name = args[1];
|
|
q = symbol_value(wq_name);
|
|
} else {
|
|
|
|
wq_struct = args[1];
|
|
wq_member = dot+1;
|
|
*dot = '\0';
|
|
if (argcnt != 3) {
|
|
fprintf(fp, "must supply an address for %s\n",
|
|
wq_struct);
|
|
return;
|
|
}
|
|
q = htol(args[2], FAULT_ON_ERROR, NULL);
|
|
if (MEMBER_OFFSET(wq_struct, wq_member) == -1) {
|
|
fprintf(fp, "%s is not a member of %s\n",
|
|
wq_member, wq_struct);
|
|
return;
|
|
}
|
|
q += MEMBER_OFFSET(wq_struct, wq_member);
|
|
}
|
|
}
|
|
|
|
if (q != 0 && IS_KVADDR(q)) {
|
|
/*
|
|
* If we weren't passed in an address and we're dealing
|
|
* with old style wait_queue, we must dereference the pointer
|
|
* and pass in the addr of the first elem on the queue.
|
|
* If we were supplied an address, assume the user knows
|
|
* what should be provided.
|
|
*/
|
|
if (!recd_address && VALID_STRUCT(wait_queue)) {
|
|
ulong first_elem;
|
|
readmem(q, KVADDR, &first_elem, sizeof(q),
|
|
"wait queue pointer", FAULT_ON_ERROR);
|
|
if (first_elem == 0) {
|
|
fprintf(fp, "wait queue %lx is empty\n", q);
|
|
return;
|
|
} else {
|
|
q = first_elem;
|
|
}
|
|
}
|
|
dump_waitq(q, wq_name);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_waitq(ulong wq, char *wq_name)
|
|
{
|
|
struct list_data list_data, *ld;
|
|
ulong *wq_list; /* addr of wait queue element */
|
|
ulong next_offset; /* next pointer of wq element */
|
|
ulong task_offset; /* offset of task in wq element */
|
|
int cnt; /* # elems on Queue */
|
|
int start_index; /* where to start in wq array */
|
|
int i;
|
|
|
|
ld = &list_data;
|
|
BZERO(ld, sizeof(*ld));
|
|
|
|
/*
|
|
* setup list depending on how the wait queues are organized.
|
|
*/
|
|
if (VALID_STRUCT(wait_queue)) {
|
|
task_offset = OFFSET(wait_queue_task);
|
|
next_offset = OFFSET(wait_queue_next);
|
|
ld->end = wq;
|
|
ld->start = wq;
|
|
ld->member_offset = next_offset;
|
|
ld->list_head_offset = task_offset;
|
|
|
|
start_index = 0;
|
|
} else if (VALID_STRUCT(__wait_queue)) {
|
|
ulong task_list_offset;
|
|
|
|
next_offset = OFFSET(list_head_next);
|
|
task_offset = OFFSET(__wait_queue_task);
|
|
task_list_offset = OFFSET(__wait_queue_head_task_list);
|
|
ld->end = ld->start = wq + task_list_offset + next_offset;
|
|
ld->list_head_offset = OFFSET(__wait_queue_task_list);
|
|
ld->member_offset = next_offset;
|
|
|
|
start_index = 1;
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
hq_open();
|
|
|
|
cnt = do_list(ld);
|
|
if (cnt <= 1) {
|
|
/*
|
|
* Due to the queueing of wait queues, list count returns
|
|
* an extra number of list entries:
|
|
* - in the case of a wait_queue_head_t, there is the
|
|
* the list_entry in that structure;
|
|
* - in the case of a simple wait_queue, we have the
|
|
* pointer back to the wait_queue head (see the
|
|
* WAIT_QUEUE_HEAD macro in 2.2 systems).
|
|
*/
|
|
if (wq_name)
|
|
fprintf(fp, "wait queue \"%s\" (%lx) is empty\n",
|
|
wq_name, wq);
|
|
else
|
|
fprintf(fp, "wait queue %lx is empty\n", wq);
|
|
hq_close();
|
|
return;
|
|
}
|
|
|
|
wq_list = (ulong *) GETBUF(cnt * sizeof(ulong));
|
|
cnt = retrieve_list(wq_list, cnt);
|
|
|
|
for (i = start_index; i < cnt; i++) {
|
|
struct task_context *tc;
|
|
ulong task;
|
|
|
|
readmem(wq_list[i] + task_offset, KVADDR, &task,
|
|
sizeof(void *), "wait_queue_t.task", FAULT_ON_ERROR);
|
|
|
|
if ((tc = task_to_context(task)) ||
|
|
(tc = task_to_context(stkptr_to_task(task)))) {
|
|
print_task_header(fp, tc, 0);
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
hq_close();
|
|
}
|
|
|
|
/*
|
|
* If active, clear the references to the last page tables read.
|
|
*/
|
|
void
|
|
clear_machdep_cache(void)
|
|
{
|
|
if (ACTIVE()) {
|
|
machdep->last_pgd_read = 0;
|
|
machdep->last_pmd_read = 0;
|
|
machdep->last_ptbl_read = 0;
|
|
if (machdep->clear_machdep_cache)
|
|
machdep->clear_machdep_cache();
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If it exists, return the number of cpus in the cpu_online_map.
|
|
*/
|
|
int
|
|
get_cpus_online()
|
|
{
|
|
int i, len, online;
|
|
char *buf;
|
|
ulong *maskptr, addr;
|
|
|
|
if (!(addr = cpu_map_addr("online")))
|
|
return 0;
|
|
|
|
len = cpu_map_size("online");
|
|
buf = GETBUF(len);
|
|
|
|
online = 0;
|
|
|
|
if (readmem(addr, KVADDR, buf, len,
|
|
"cpu_online_map", RETURN_ON_ERROR)) {
|
|
|
|
maskptr = (ulong *)buf;
|
|
for (i = 0; i < (len/sizeof(ulong)); i++, maskptr++)
|
|
online += count_bits_long(*maskptr);
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "get_cpus_online: online: %d\n", online);
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
|
|
return online;
|
|
}
|
|
|
|
/*
|
|
* Check whether a cpu is offline
|
|
*/
|
|
int
|
|
check_offline_cpu(int cpu)
|
|
{
|
|
if (!cpu_map_addr("online"))
|
|
return FALSE;
|
|
|
|
if (in_cpu_map(ONLINE_MAP, cpu))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* Check whether the data related to the specified cpu should be hidden.
|
|
*/
|
|
int
|
|
hide_offline_cpu(int cpu)
|
|
{
|
|
if (!(pc->flags2 & OFFLINE_HIDE))
|
|
return FALSE;
|
|
|
|
return check_offline_cpu(cpu);
|
|
}
|
|
|
|
/*
|
|
* If it exists, return the highest cpu number in the cpu_online_map.
|
|
*/
|
|
int
|
|
get_highest_cpu_online()
|
|
{
|
|
int i, len;
|
|
char *buf;
|
|
ulong *maskptr, addr;
|
|
int high, highest;
|
|
|
|
if (!(addr = cpu_map_addr("online")))
|
|
return -1;
|
|
|
|
len = cpu_map_size("online");
|
|
buf = GETBUF(len);
|
|
highest = -1;
|
|
|
|
if (readmem(addr, KVADDR, buf, len,
|
|
"cpu_online_map", RETURN_ON_ERROR)) {
|
|
|
|
maskptr = (ulong *)buf;
|
|
for (i = 0; i < (len/sizeof(ulong)); i++, maskptr++) {
|
|
if ((high = highest_bit_long(*maskptr)) < 0)
|
|
continue;
|
|
highest = high + (i * (sizeof(ulong)*8));
|
|
}
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "get_highest_cpu_online: %d\n", highest);
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
|
|
return highest;
|
|
}
|
|
|
|
/*
|
|
* If it exists, return the number of cpus in the cpu_active_map.
|
|
*/
|
|
int
|
|
get_cpus_active()
|
|
{
|
|
int i, len, active;
|
|
char *buf;
|
|
ulong *maskptr, addr;
|
|
|
|
if (!(addr = cpu_map_addr("active")))
|
|
return 0;
|
|
|
|
len = cpu_map_size("active");
|
|
buf = GETBUF(len);
|
|
|
|
active = 0;
|
|
|
|
if (readmem(addr, KVADDR, buf, len,
|
|
"cpu_active_map", RETURN_ON_ERROR)) {
|
|
|
|
maskptr = (ulong *)buf;
|
|
for (i = 0; i < (len/sizeof(ulong)); i++, maskptr++)
|
|
active += count_bits_long(*maskptr);
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "get_cpus_active: active: %d\n", active);
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
|
|
return active;
|
|
}
|
|
|
|
/*
|
|
* If it exists, return the number of cpus in the cpu_present_map.
|
|
*/
|
|
int
|
|
get_cpus_present()
|
|
{
|
|
int i, len, present;
|
|
char *buf;
|
|
ulong *maskptr, addr;
|
|
|
|
if (!(addr = cpu_map_addr("present")))
|
|
return 0;
|
|
|
|
len = cpu_map_size("present");
|
|
buf = GETBUF(len);
|
|
|
|
present = 0;
|
|
|
|
if (readmem(addr, KVADDR, buf, len,
|
|
"cpu_present_map", RETURN_ON_ERROR)) {
|
|
|
|
maskptr = (ulong *)buf;
|
|
for (i = 0; i < (len/sizeof(ulong)); i++, maskptr++)
|
|
present += count_bits_long(*maskptr);
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "get_cpus_present: present: %d\n", present);
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
|
|
return present;
|
|
}
|
|
|
|
/*
|
|
* If it exists, return the highest cpu number in the cpu_present_map.
|
|
*/
|
|
int
|
|
get_highest_cpu_present()
|
|
{
|
|
int i, len;
|
|
char *buf;
|
|
ulong *maskptr, addr;
|
|
int high, highest;
|
|
|
|
if (!(addr = cpu_map_addr("present")))
|
|
return -1;
|
|
|
|
len = cpu_map_size("present");
|
|
buf = GETBUF(len);
|
|
highest = -1;
|
|
|
|
if (readmem(addr, KVADDR, buf, len,
|
|
"cpu_present_map", RETURN_ON_ERROR)) {
|
|
|
|
maskptr = (ulong *)buf;
|
|
for (i = 0; i < (len/sizeof(ulong)); i++, maskptr++) {
|
|
if ((high = highest_bit_long(*maskptr)) < 0)
|
|
continue;
|
|
highest = high + (i * (sizeof(ulong)*8));
|
|
}
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "get_highest_cpu_present: %d\n", highest);
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
|
|
return highest;
|
|
}
|
|
|
|
/*
|
|
* If it exists, return the number of cpus in the cpu_possible_map.
|
|
*/
|
|
int
|
|
get_cpus_possible()
|
|
{
|
|
int i, len, possible;
|
|
char *buf;
|
|
ulong *maskptr, addr;
|
|
|
|
if (!(addr = cpu_map_addr("possible")))
|
|
return 0;
|
|
|
|
len = cpu_map_size("possible");
|
|
buf = GETBUF(len);
|
|
|
|
possible = 0;
|
|
|
|
if (readmem(addr, KVADDR, buf, len,
|
|
"cpu_possible_map", RETURN_ON_ERROR)) {
|
|
|
|
maskptr = (ulong *)buf;
|
|
for (i = 0; i < (len/sizeof(ulong)); i++, maskptr++)
|
|
possible += count_bits_long(*maskptr);
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "get_cpus_possible: possible: %d\n",
|
|
possible);
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
|
|
return possible;
|
|
}
|
|
|
|
/*
|
|
* When displaying cpus, return the number of cpus online if possible,
|
|
* otherwise kt->cpus.
|
|
*/
|
|
int
|
|
get_cpus_to_display(void)
|
|
{
|
|
int online = get_cpus_online();
|
|
|
|
return (online ? online : kt->cpus);
|
|
}
|
|
|
|
/*
|
|
* Xen machine-address to pseudo-physical-page translator.
|
|
*/
|
|
ulonglong
|
|
xen_m2p(ulonglong machine)
|
|
{
|
|
ulong mfn, pfn;
|
|
|
|
mfn = XEN_MACHINE_TO_MFN(machine);
|
|
pfn = __xen_m2p(machine, mfn);
|
|
|
|
if (pfn == XEN_MFN_NOT_FOUND) {
|
|
if (CRASHDEBUG(1) && !STREQ(pc->curcmd, "search"))
|
|
error(INFO,
|
|
"xen_m2p: machine address %lx not found\n",
|
|
machine);
|
|
return XEN_MACHADDR_NOT_FOUND;
|
|
}
|
|
|
|
return XEN_PFN_TO_PSEUDO(pfn);
|
|
}
|
|
|
|
static ulong
|
|
__xen_m2p(ulonglong machine, ulong mfn)
|
|
{
|
|
ulong c, i, kmfn, mapping, p, pfn;
|
|
ulong start, end;
|
|
ulong *mp = (ulong *)kt->m2p_page;
|
|
|
|
/*
|
|
* Check the FIFO cache first.
|
|
*/
|
|
for (c = 0; c < P2M_MAPPING_CACHE; c++) {
|
|
if (kt->p2m_mapping_cache[c].mapping &&
|
|
((mfn >= kt->p2m_mapping_cache[c].start) &&
|
|
(mfn <= kt->p2m_mapping_cache[c].end))) {
|
|
|
|
if (kt->p2m_mapping_cache[c].mapping != kt->last_mapping_read) {
|
|
if (!readmem(kt->p2m_mapping_cache[c].mapping, KVADDR,
|
|
mp, PAGESIZE(), "phys_to_machine_mapping page (cached)",
|
|
RETURN_ON_ERROR))
|
|
error(FATAL, "cannot access "
|
|
"phys_to_machine_mapping page\n");
|
|
else
|
|
kt->last_mapping_read = kt->p2m_mapping_cache[c].mapping;
|
|
} else
|
|
kt->p2m_page_cache_hits++;
|
|
|
|
for (i = 0; i < XEN_PFNS_PER_PAGE; i++) {
|
|
kmfn = (*(mp+i)) & ~XEN_FOREIGN_FRAME;
|
|
if (kmfn == mfn) {
|
|
p = P2M_MAPPING_PAGE_PFN(c);
|
|
pfn = p + i;
|
|
|
|
if (CRASHDEBUG(1))
|
|
console("(cached) mfn: %lx (%llx) p: %ld"
|
|
" i: %ld pfn: %lx (%llx)\n",
|
|
mfn, machine, p,
|
|
i, pfn, XEN_PFN_TO_PSEUDO(pfn));
|
|
kt->p2m_mfn_cache_hits++;
|
|
|
|
return pfn;
|
|
}
|
|
}
|
|
/*
|
|
* Stale entry -- clear it out.
|
|
*/
|
|
kt->p2m_mapping_cache[c].mapping = 0;
|
|
}
|
|
}
|
|
|
|
if (PVOPS_XEN()) {
|
|
/*
|
|
* The machine address was not cached, so search from the
|
|
* beginning of the p2m_top array, caching the contiguous
|
|
* range containing the found machine address.
|
|
*/
|
|
if (symbol_exists("p2m_mid_missing"))
|
|
pfn = __xen_pvops_m2p_l3(machine, mfn);
|
|
else
|
|
pfn = __xen_pvops_m2p_l2(machine, mfn);
|
|
|
|
if (pfn != XEN_MFN_NOT_FOUND)
|
|
return pfn;
|
|
} else {
|
|
/*
|
|
* The machine address was not cached, so search from the
|
|
* beginning of the phys_to_machine_mapping array, caching
|
|
* the contiguous range containing the found machine address.
|
|
*/
|
|
mapping = kt->phys_to_machine_mapping;
|
|
|
|
for (p = 0; p < kt->p2m_table_size; p += XEN_PFNS_PER_PAGE)
|
|
{
|
|
if (mapping != kt->last_mapping_read) {
|
|
if (!readmem(mapping, KVADDR, mp, PAGESIZE(),
|
|
"phys_to_machine_mapping page",
|
|
RETURN_ON_ERROR))
|
|
error(FATAL,
|
|
"cannot access"
|
|
" phys_to_machine_mapping page\n");
|
|
else
|
|
kt->last_mapping_read = mapping;
|
|
}
|
|
|
|
kt->p2m_pages_searched++;
|
|
|
|
if (search_mapping_page(mfn, &i, &start, &end)) {
|
|
pfn = p + i;
|
|
if (CRASHDEBUG(1))
|
|
console("pages: %d mfn: %lx (%llx) p: %ld"
|
|
" i: %ld pfn: %lx (%llx)\n",
|
|
(p/XEN_PFNS_PER_PAGE)+1, mfn, machine,
|
|
p, i, pfn, XEN_PFN_TO_PSEUDO(pfn));
|
|
|
|
c = kt->p2m_cache_index;
|
|
kt->p2m_mapping_cache[c].start = start;
|
|
kt->p2m_mapping_cache[c].end = end;
|
|
kt->p2m_mapping_cache[c].mapping = mapping;
|
|
kt->p2m_cache_index = (c+1) % P2M_MAPPING_CACHE;
|
|
|
|
return pfn;
|
|
}
|
|
|
|
mapping += PAGESIZE();
|
|
}
|
|
}
|
|
|
|
if (CRASHDEBUG(1))
|
|
console("machine address %llx not found\n", machine);
|
|
|
|
return (XEN_MFN_NOT_FOUND);
|
|
}
|
|
|
|
static ulong
|
|
__xen_pvops_m2p_l2(ulonglong machine, ulong mfn)
|
|
{
|
|
ulong c, e, end, i, mapping, p, p2m, pfn, start;
|
|
|
|
for (e = p = 0, p2m = kt->pvops_xen.p2m_top;
|
|
e < kt->pvops_xen.p2m_top_entries;
|
|
e++, p += XEN_PFNS_PER_PAGE, p2m += sizeof(void *)) {
|
|
|
|
if (!readmem(p2m, KVADDR, &mapping, sizeof(void *),
|
|
"p2m_top", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot access p2m_top[] entry\n");
|
|
|
|
if (mapping == kt->pvops_xen.p2m_missing)
|
|
continue;
|
|
|
|
if (mapping != kt->last_mapping_read) {
|
|
if (!readmem(mapping, KVADDR, (void *)kt->m2p_page,
|
|
PAGESIZE(), "p2m_top page", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot access p2m_top[] page\n");
|
|
|
|
kt->last_mapping_read = mapping;
|
|
}
|
|
|
|
kt->p2m_pages_searched++;
|
|
|
|
if (search_mapping_page(mfn, &i, &start, &end)) {
|
|
pfn = p + i;
|
|
if (CRASHDEBUG(1))
|
|
console("pages: %d mfn: %lx (%llx) p: %ld"
|
|
" i: %ld pfn: %lx (%llx)\n",
|
|
(p/XEN_PFNS_PER_PAGE)+1, mfn, machine,
|
|
p, i, pfn, XEN_PFN_TO_PSEUDO(pfn));
|
|
|
|
c = kt->p2m_cache_index;
|
|
kt->p2m_mapping_cache[c].start = start;
|
|
kt->p2m_mapping_cache[c].end = end;
|
|
kt->p2m_mapping_cache[c].mapping = mapping;
|
|
kt->p2m_mapping_cache[c].pfn = p;
|
|
kt->p2m_cache_index = (c+1) % P2M_MAPPING_CACHE;
|
|
|
|
return pfn;
|
|
}
|
|
}
|
|
|
|
return XEN_MFN_NOT_FOUND;
|
|
}
|
|
|
|
static ulong
|
|
__xen_pvops_m2p_l3(ulonglong machine, ulong mfn)
|
|
{
|
|
ulong c, end, i, j, k, mapping, p;
|
|
ulong p2m_mid, p2m_top, pfn, start;
|
|
|
|
p2m_top = kt->pvops_xen.p2m_top;
|
|
|
|
for (i = 0; i < XEN_P2M_TOP_PER_PAGE; ++i, p2m_top += sizeof(void *)) {
|
|
if (!readmem(p2m_top, KVADDR, &mapping,
|
|
sizeof(void *), "p2m_top", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot access p2m_top[] entry\n");
|
|
|
|
if (mapping == kt->pvops_xen.p2m_mid_missing)
|
|
continue;
|
|
|
|
p2m_mid = mapping;
|
|
|
|
for (j = 0; j < XEN_P2M_MID_PER_PAGE; ++j, p2m_mid += sizeof(void *)) {
|
|
if (!readmem(p2m_mid, KVADDR, &mapping,
|
|
sizeof(void *), "p2m_mid", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot access p2m_mid[] entry\n");
|
|
|
|
if (mapping == kt->pvops_xen.p2m_missing)
|
|
continue;
|
|
|
|
if (mapping != kt->last_mapping_read) {
|
|
if (!readmem(mapping, KVADDR, (void *)kt->m2p_page,
|
|
PAGESIZE(), "p2m_mid page", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot access p2m_mid[] page\n");
|
|
|
|
kt->last_mapping_read = mapping;
|
|
}
|
|
|
|
if (!search_mapping_page(mfn, &k, &start, &end))
|
|
continue;
|
|
|
|
p = i * XEN_P2M_MID_PER_PAGE * XEN_P2M_PER_PAGE;
|
|
p += j * XEN_P2M_PER_PAGE;
|
|
pfn = p + k;
|
|
|
|
if (CRASHDEBUG(1))
|
|
console("pages: %d mfn: %lx (%llx) p: %ld"
|
|
" i: %ld j: %ld k: %ld pfn: %lx (%llx)\n",
|
|
(p / XEN_P2M_PER_PAGE) + 1, mfn, machine,
|
|
p, i, j, k, pfn, XEN_PFN_TO_PSEUDO(pfn));
|
|
|
|
c = kt->p2m_cache_index;
|
|
kt->p2m_mapping_cache[c].start = start;
|
|
kt->p2m_mapping_cache[c].end = end;
|
|
kt->p2m_mapping_cache[c].mapping = mapping;
|
|
kt->p2m_mapping_cache[c].pfn = p;
|
|
kt->p2m_cache_index = (c + 1) % P2M_MAPPING_CACHE;
|
|
|
|
return pfn;
|
|
}
|
|
}
|
|
|
|
return XEN_MFN_NOT_FOUND;
|
|
}
|
|
|
|
/*
|
|
* Search for an mfn in the current mapping page, and if found,
|
|
* determine the range of contiguous mfns that it's contained
|
|
* within (if any).
|
|
*/
|
|
#define PREV_UP 0x1
|
|
#define NEXT_UP 0x2
|
|
#define PREV_DOWN 0x4
|
|
#define NEXT_DOWN 0x8
|
|
|
|
static int
|
|
search_mapping_page(ulong mfn, ulong *index, ulong *startptr, ulong *endptr)
|
|
{
|
|
int n, found;
|
|
ulong i, kmfn;
|
|
ulong flags, start, end, next, prev, curr;
|
|
ulong *mp;
|
|
|
|
mp = (ulong *)kt->m2p_page;
|
|
|
|
for (i = 0, found = FALSE; i < XEN_PFNS_PER_PAGE; i++) {
|
|
kmfn = (*(mp+i)) & ~XEN_FOREIGN_FRAME;
|
|
|
|
if (kmfn == mfn) {
|
|
found = TRUE;
|
|
*index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
flags = 0;
|
|
next = prev = XEN_MFN_NOT_FOUND;
|
|
start = end = kmfn;
|
|
|
|
if (i)
|
|
prev = (*(mp+(i-1))) & ~XEN_FOREIGN_FRAME;
|
|
if ((i+1) != XEN_PFNS_PER_PAGE)
|
|
next = (*(mp+(i+1))) & ~XEN_FOREIGN_FRAME;
|
|
|
|
if (prev == (kmfn-1))
|
|
flags |= PREV_UP;
|
|
else if (prev == (kmfn+1))
|
|
flags |= PREV_DOWN;
|
|
|
|
if (next == (kmfn+1))
|
|
flags |= NEXT_UP;
|
|
else if (next == (kmfn-1))
|
|
flags |= NEXT_DOWN;
|
|
|
|
/* Should be impossible, but just in case... */
|
|
if ((flags & PREV_UP) && (flags & NEXT_DOWN))
|
|
flags &= ~NEXT_DOWN;
|
|
else if ((flags & PREV_DOWN) && (flags & NEXT_UP))
|
|
flags &= ~NEXT_UP;
|
|
|
|
if (flags & (PREV_UP|PREV_DOWN)) {
|
|
start = prev;
|
|
|
|
for (n = (i-2); n >= 0; n--) {
|
|
curr = (*(mp+n)) & ~XEN_FOREIGN_FRAME;
|
|
if (flags & PREV_UP) {
|
|
if (curr == (start-1))
|
|
start = curr;
|
|
} else {
|
|
if (curr == (start+1))
|
|
start = curr;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if (flags & (NEXT_UP|NEXT_DOWN)) {
|
|
end = next;
|
|
|
|
for (n = (i+2); n < XEN_PFNS_PER_PAGE; n++) {
|
|
curr = (*(mp+n)) & ~XEN_FOREIGN_FRAME;
|
|
if (flags & NEXT_UP) {
|
|
if (curr == (end+1))
|
|
end = curr;
|
|
} else {
|
|
if (curr == (end-1))
|
|
end = curr;
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
if (start > end) {
|
|
curr = start;
|
|
start = end;
|
|
end = curr;
|
|
}
|
|
|
|
*startptr = start;
|
|
*endptr = end;
|
|
|
|
if (CRASHDEBUG(2))
|
|
fprintf(fp, "mfn: %lx -> start: %lx end: %lx (%ld mfns)\n",
|
|
mfn, start, end, end - start);
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
/*
|
|
* IKCONFIG management.
|
|
*/
|
|
#define IKCONFIG_MAX 5000
|
|
static struct ikconfig_list {
|
|
char *name;
|
|
char *val;
|
|
} *ikconfig_all;
|
|
|
|
static void add_ikconfig_entry(char *line, struct ikconfig_list *ent)
|
|
{
|
|
char *tokptr, *name, *val;
|
|
|
|
name = strtok_r(line, "=", &tokptr);
|
|
sscanf(name, "CONFIG_%s", name);
|
|
val = strtok_r(NULL, "", &tokptr);
|
|
|
|
ent->name = strdup(name);
|
|
ent->val = strdup(val);
|
|
}
|
|
|
|
static int setup_ikconfig(char *config)
|
|
{
|
|
char *ent, *tokptr;
|
|
struct ikconfig_list *new;
|
|
|
|
ikconfig_all = calloc(1, sizeof(struct ikconfig_list) * IKCONFIG_MAX);
|
|
if (!ikconfig_all) {
|
|
error(WARNING, "cannot calloc for ikconfig entries.\n");
|
|
return 0;
|
|
}
|
|
|
|
ent = strtok_r(config, "\n", &tokptr);
|
|
while (ent) {
|
|
while (whitespace(*ent))
|
|
ent++;
|
|
|
|
if (ent[0] != '#') {
|
|
add_ikconfig_entry(ent,
|
|
&ikconfig_all[kt->ikconfig_ents++]);
|
|
if (kt->ikconfig_ents == IKCONFIG_MAX) {
|
|
error(WARNING, "ikconfig overflow.\n");
|
|
return 1;
|
|
}
|
|
}
|
|
ent = strtok_r(NULL, "\n", &tokptr);
|
|
}
|
|
if (kt->ikconfig_ents == 0) {
|
|
free(ikconfig_all);
|
|
return 0;
|
|
}
|
|
if ((new = realloc(ikconfig_all,
|
|
sizeof(struct ikconfig_list) * kt->ikconfig_ents)))
|
|
ikconfig_all = new;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static void free_ikconfig(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < kt->ikconfig_ents; i++) {
|
|
free(ikconfig_all[i].name);
|
|
free(ikconfig_all[i].val);
|
|
}
|
|
free(ikconfig_all);
|
|
}
|
|
|
|
int get_kernel_config(char *conf_name, char **str)
|
|
{
|
|
int i;
|
|
int ret = IKCONFIG_N;
|
|
char *name;
|
|
|
|
if (!(kt->ikconfig_flags & IKCONFIG_AVAIL)) {
|
|
error(WARNING, "CONFIG_IKCONFIG is not set\n");
|
|
return ret;
|
|
} else if (!(kt->ikconfig_flags & IKCONFIG_LOADED)) {
|
|
read_in_kernel_config(IKCFG_SETUP);
|
|
if (!(kt->ikconfig_flags & IKCONFIG_LOADED)) {
|
|
error(WARNING, "IKCFG_SETUP failed\n");
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
name = strdup(conf_name);
|
|
if (!strncmp(name, "CONFIG_", strlen("CONFIG_")))
|
|
sscanf(name, "CONFIG_%s", name);
|
|
|
|
for (i = 0; i < kt->ikconfig_ents; i++) {
|
|
if (STREQ(name, ikconfig_all[i].name)) {
|
|
if (str)
|
|
*str = ikconfig_all[i].val;
|
|
if (STREQ(ikconfig_all[i].val, "y"))
|
|
ret = IKCONFIG_Y;
|
|
else if (STREQ(ikconfig_all[i].val, "m"))
|
|
ret = IKCONFIG_M;
|
|
else
|
|
ret = IKCONFIG_STR;
|
|
|
|
break;
|
|
}
|
|
}
|
|
free(name);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Read the relevant IKCONFIG (In Kernel Config) data if available.
|
|
*/
|
|
|
|
static char *ikconfig[] = {
|
|
"CONFIG_NR_CPUS",
|
|
"CONFIG_PGTABLE_4",
|
|
"CONFIG_HZ",
|
|
"CONFIG_DEBUG_BUGVERBOSE",
|
|
"CONFIG_DEBUG_INFO_REDUCED",
|
|
NULL,
|
|
};
|
|
|
|
void
|
|
read_in_kernel_config(int command)
|
|
{
|
|
struct syment *sp;
|
|
int ii, jj, ret, end, found=0;
|
|
unsigned long size, bufsz;
|
|
char *pos, *ln, *buf, *head, *tail, *val, *uncomp;
|
|
char line[512];
|
|
z_stream stream;
|
|
|
|
if ((kt->flags & NO_IKCONFIG) && !(pc->flags & RUNTIME))
|
|
return;
|
|
|
|
if ((sp = symbol_search("kernel_config_data")) == NULL) {
|
|
if (command == IKCFG_READ)
|
|
error(FATAL,
|
|
"kernel_config_data does not exist in this kernel\n");
|
|
else if (command == IKCFG_SETUP || command == IKCFG_FREE)
|
|
error(WARNING,
|
|
"kernel_config_data does not exist in this kernel\n");
|
|
return;
|
|
}
|
|
|
|
/* We don't know how large IKCONFIG is, so we start with
|
|
* 32k, if we can't find MAGIC_END assume we didn't read
|
|
* enough, double it and try again.
|
|
*/
|
|
ii = 32;
|
|
|
|
again:
|
|
size = ii * 1024;
|
|
|
|
if ((buf = (char *)malloc(size)) == NULL) {
|
|
error(WARNING, "cannot malloc IKCONFIG input buffer\n");
|
|
return;
|
|
}
|
|
|
|
if (!readmem(sp->value, KVADDR, buf, size,
|
|
"kernel_config_data", RETURN_ON_ERROR)) {
|
|
error(WARNING, "cannot read kernel_config_data\n");
|
|
goto out2;
|
|
}
|
|
|
|
/* Find the start */
|
|
if (strstr(buf, MAGIC_START))
|
|
head = buf + MAGIC_SIZE + 10; /* skip past MAGIC_START and gzip header */
|
|
else {
|
|
error(WARNING, "could not find MAGIC_START!\n");
|
|
goto out2;
|
|
}
|
|
|
|
tail = head;
|
|
|
|
end = strlen(MAGIC_END);
|
|
|
|
/* Find the end*/
|
|
while (tail < (buf + (size - 1))) {
|
|
|
|
if (strncmp(tail, MAGIC_END, end)==0) {
|
|
found = 1;
|
|
break;
|
|
}
|
|
tail++;
|
|
}
|
|
|
|
if (found) {
|
|
bufsz = tail - head;
|
|
size = 10 * bufsz;
|
|
if ((uncomp = (char *)malloc(size)) == NULL) {
|
|
error(WARNING, "cannot malloc IKCONFIG output buffer\n");
|
|
goto out2;
|
|
}
|
|
} else {
|
|
if (ii > 512) {
|
|
error(WARNING, "could not find MAGIC_END!\n");
|
|
goto out2;
|
|
} else {
|
|
free(buf);
|
|
ii *= 2;
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
|
|
/* initialize zlib */
|
|
stream.next_in = (Bytef *)head;
|
|
stream.avail_in = (uInt)bufsz;
|
|
|
|
stream.next_out = (Bytef *)uncomp;
|
|
stream.avail_out = (uInt)size;
|
|
|
|
stream.zalloc = NULL;
|
|
stream.zfree = NULL;
|
|
stream.opaque = NULL;
|
|
|
|
ret = inflateInit2(&stream, -MAX_WBITS);
|
|
if (ret != Z_OK) {
|
|
read_in_kernel_config_err(ret, "initialize");
|
|
goto out1;
|
|
}
|
|
|
|
ret = inflate(&stream, Z_FINISH);
|
|
|
|
if (ret != Z_STREAM_END) {
|
|
inflateEnd(&stream);
|
|
if (ret == Z_NEED_DICT ||
|
|
(ret == Z_BUF_ERROR && stream.avail_in == 0)) {
|
|
read_in_kernel_config_err(Z_DATA_ERROR, "uncompress");
|
|
goto out1;
|
|
}
|
|
read_in_kernel_config_err(ret, "uncompress");
|
|
goto out1;
|
|
}
|
|
size = stream.total_out;
|
|
|
|
ret = inflateEnd(&stream);
|
|
|
|
pos = uncomp;
|
|
|
|
if (command == IKCFG_INIT)
|
|
kt->ikconfig_flags |= IKCONFIG_AVAIL;
|
|
else if (command == IKCFG_SETUP) {
|
|
if (!(kt->ikconfig_flags & IKCONFIG_LOADED)) {
|
|
if (setup_ikconfig(pos)) {
|
|
kt->ikconfig_flags |= IKCONFIG_LOADED;
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp,
|
|
"ikconfig: %d valid configs.\n",
|
|
kt->ikconfig_ents);
|
|
} else
|
|
error(WARNING, "IKCFG_SETUP failed\n\n");
|
|
} else
|
|
error(WARNING,
|
|
"IKCFG_SETUP: ikconfig data already loaded\n");
|
|
goto out1;
|
|
} else if (command == IKCFG_FREE) {
|
|
if (kt->ikconfig_flags & IKCONFIG_LOADED) {
|
|
free_ikconfig();
|
|
kt->ikconfig_ents = 0;
|
|
kt->ikconfig_flags &= ~IKCONFIG_LOADED;
|
|
} else
|
|
error(WARNING, "IKCFG_FREE: ikconfig data not loaded\n");
|
|
goto out1;
|
|
}
|
|
|
|
do {
|
|
ret = sscanf(pos, "%511[^\n]\n%n", line, &ii);
|
|
if (ret > 0) {
|
|
if ((command == IKCFG_READ) || CRASHDEBUG(8))
|
|
fprintf(fp, "%s\n", line);
|
|
|
|
pos += ii;
|
|
|
|
ln = line;
|
|
|
|
/* skip leading whitespace */
|
|
while (whitespace(*ln))
|
|
ln++;
|
|
|
|
/* skip comments -- except when looking for "not set" */
|
|
if (*ln == '#') {
|
|
if (strstr(ln, "CONFIG_DEBUG_BUGVERBOSE") &&
|
|
strstr(ln, "not set"))
|
|
kt->flags |= BUGVERBOSE_OFF;
|
|
if (strstr(ln, "CONFIG_DEBUG_INFO_REDUCED"))
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "%s\n", ln);
|
|
continue;
|
|
}
|
|
|
|
/* Find '=' */
|
|
if ((head = strchr(ln, '=')) != NULL) {
|
|
*head = '\0';
|
|
val = head + 1;
|
|
|
|
head--;
|
|
|
|
/* skip trailing whitespace */
|
|
while (whitespace(*head)) {
|
|
*head = '\0';
|
|
head--;
|
|
}
|
|
|
|
/* skip whitespace */
|
|
while (whitespace(*val))
|
|
val++;
|
|
|
|
} else /* Bad line, skip it */
|
|
continue;
|
|
|
|
if (command != IKCFG_INIT)
|
|
continue;
|
|
|
|
for (jj = 0; ikconfig[jj]; jj++) {
|
|
if (STREQ(ln, ikconfig[jj])) {
|
|
|
|
if (STREQ(ln, "CONFIG_NR_CPUS")) {
|
|
kt->kernel_NR_CPUS = atoi(val);
|
|
if (CRASHDEBUG(1))
|
|
error(INFO,
|
|
"CONFIG_NR_CPUS: %d\n",
|
|
kt->kernel_NR_CPUS);
|
|
|
|
} else if (STREQ(ln, "CONFIG_PGTABLE_4")) {
|
|
machdep->flags |= VM_4_LEVEL;
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "CONFIG_PGTABLE_4\n");
|
|
|
|
} else if (STREQ(ln, "CONFIG_HZ")) {
|
|
machdep->hz = atoi(val);
|
|
if (CRASHDEBUG(1))
|
|
error(INFO,
|
|
"CONFIG_HZ: %d\n",
|
|
machdep->hz);
|
|
|
|
} else if (STREQ(ln, "CONFIG_DEBUG_INFO_REDUCED")) {
|
|
if (STREQ(val, "y")) {
|
|
error(WARNING,
|
|
"CONFIG_DEBUG_INFO_REDUCED=y\n");
|
|
no_debugging_data(INFO);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} while (ret > 0);
|
|
|
|
out1:
|
|
free(uncomp);
|
|
out2:
|
|
free(buf);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
read_in_kernel_config_err(int e, char *msg)
|
|
{
|
|
error(WARNING, "zlib could not %s\n", msg);
|
|
switch (e) {
|
|
case Z_OK:
|
|
fprintf(fp, "Z_OK\n");
|
|
break;
|
|
|
|
case Z_STREAM_END:
|
|
fprintf(fp, "Z_STREAM_END\n");
|
|
break;
|
|
|
|
case Z_NEED_DICT:
|
|
fprintf(fp, "Z_NEED_DICT\n");
|
|
break;
|
|
|
|
case Z_ERRNO:
|
|
fprintf(fp, "Z_ERNO\n");
|
|
break;
|
|
|
|
case Z_STREAM_ERROR:
|
|
fprintf(fp, "Z_STREAM\n");
|
|
break;
|
|
|
|
case Z_DATA_ERROR:
|
|
fprintf(fp, "Z_DATA_ERROR\n");
|
|
break;
|
|
|
|
case Z_MEM_ERROR: /* out of memory */
|
|
fprintf(fp, "Z_MEM_ERROR\n");
|
|
break;
|
|
|
|
case Z_BUF_ERROR: /* not enough room in output buf */
|
|
fprintf(fp, "Z_BUF_ERROR\n");
|
|
break;
|
|
|
|
case Z_VERSION_ERROR:
|
|
fprintf(fp, "Z_VERSION_ERROR\n");
|
|
break;
|
|
|
|
default:
|
|
fprintf(fp, "UNKNOWN ERROR: %d\n", e);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* With the evidence available, attempt to pre-determine whether
|
|
* this is a paravirt-capable kernel running as bare-metal, xen,
|
|
* kvm, etc.
|
|
*
|
|
* NOTE: Only bare-metal pv_ops kernels are supported so far.
|
|
*/
|
|
void
|
|
paravirt_init(void)
|
|
{
|
|
/*
|
|
* pv_init_ops appears to be (as of 2.6.27) an arch-common
|
|
* symbol. This may have to change.
|
|
*/
|
|
if (kernel_symbol_exists("pv_init_ops")) {
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "pv_init_ops exists: ARCH_PVOPS\n");
|
|
kt->flags |= ARCH_PVOPS;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Get the kernel's xtime timespec from its relevant location.
|
|
*/
|
|
static void
|
|
get_xtime(struct timespec *date)
|
|
{
|
|
struct syment *sp;
|
|
uint64_t xtime_sec;
|
|
|
|
if (VALID_MEMBER(timekeeper_xtime) &&
|
|
(sp = kernel_symbol_search("timekeeper"))) {
|
|
readmem(sp->value + OFFSET(timekeeper_xtime), KVADDR,
|
|
date, sizeof(struct timespec),
|
|
"timekeeper xtime", RETURN_ON_ERROR);
|
|
} else if (VALID_MEMBER(timekeeper_xtime_sec) &&
|
|
(sp = kernel_symbol_search("timekeeper"))) {
|
|
readmem(sp->value + OFFSET(timekeeper_xtime_sec), KVADDR,
|
|
&xtime_sec, sizeof(uint64_t),
|
|
"timekeeper xtime_sec", RETURN_ON_ERROR);
|
|
date->tv_sec = (__time_t)xtime_sec;
|
|
} else if (VALID_MEMBER(timekeeper_xtime_sec) &&
|
|
(sp = kernel_symbol_search("shadow_timekeeper"))) {
|
|
readmem(sp->value + OFFSET(timekeeper_xtime_sec), KVADDR,
|
|
&xtime_sec, sizeof(uint64_t),
|
|
"shadow_timekeeper xtime_sec", RETURN_ON_ERROR);
|
|
date->tv_sec = (__time_t)xtime_sec;
|
|
} else if (kernel_symbol_exists("xtime"))
|
|
get_symbol_data("xtime", sizeof(struct timespec), date);
|
|
}
|
|
|
|
|
|
static void
|
|
hypervisor_init(void)
|
|
{
|
|
ulong x86_hyper, name, pv_init_ops;
|
|
char buf[BUFSIZE], *p1;
|
|
|
|
kt->hypervisor = "(undetermined)";
|
|
BZERO(buf, BUFSIZE);
|
|
|
|
if (kernel_symbol_exists("pv_info") &&
|
|
MEMBER_EXISTS("pv_info", "name") &&
|
|
readmem(symbol_value("pv_info") + MEMBER_OFFSET("pv_info", "name"),
|
|
KVADDR, &name, sizeof(char *), "pv_info.name",
|
|
QUIET|RETURN_ON_ERROR) && read_string(name, buf, BUFSIZE-1))
|
|
kt->hypervisor = strdup(buf);
|
|
else if (try_get_symbol_data("x86_hyper", sizeof(void *), &x86_hyper)) {
|
|
if (!x86_hyper)
|
|
kt->hypervisor = "bare hardware";
|
|
else if (MEMBER_EXISTS("hypervisor_x86", "name") &&
|
|
readmem(x86_hyper + MEMBER_OFFSET("hypervisor_x86", "name"),
|
|
KVADDR, &name, sizeof(char *), "x86_hyper->name",
|
|
QUIET|RETURN_ON_ERROR) && read_string(name, buf, BUFSIZE-1))
|
|
kt->hypervisor = strdup(buf);
|
|
} else if (XENDUMP_DUMPFILE() || XEN())
|
|
kt->hypervisor = "Xen";
|
|
else if (KVMDUMP_DUMPFILE())
|
|
kt->hypervisor = "KVM";
|
|
else if (PVOPS() && readmem(symbol_value("pv_init_ops"), KVADDR,
|
|
&pv_init_ops, sizeof(void *), "pv_init_ops", RETURN_ON_ERROR) &&
|
|
(p1 = value_symbol(pv_init_ops)) &&
|
|
STREQ(p1, "native_patch"))
|
|
kt->hypervisor = "bare hardware";
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "hypervisor: %s\n", kt->hypervisor);
|
|
}
|
|
|
|
/*
|
|
* Get and display the kernel log buffer using the vmcoreinfo
|
|
* data alone without the vmlinux file.
|
|
*/
|
|
void
|
|
get_log_from_vmcoreinfo(char *file)
|
|
{
|
|
char *string;
|
|
char buf[BUFSIZE];
|
|
char *p1, *p2;
|
|
struct vmcoreinfo_data *vmc = &kt->vmcoreinfo;
|
|
|
|
if (!(pc->flags2 & VMCOREINFO))
|
|
error(FATAL, "%s: no VMCOREINFO section\n", file);
|
|
|
|
vmc->log_SIZE = vmc->log_ts_nsec_OFFSET = vmc->log_len_OFFSET =
|
|
vmc->log_text_len_OFFSET = vmc->log_dict_len_OFFSET = -1;
|
|
|
|
if ((string = pc->read_vmcoreinfo("OSRELEASE"))) {
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OSRELEASE: %s\n", string);
|
|
strcpy(buf, string);
|
|
p1 = p2 = buf;
|
|
while (*p2 != '.')
|
|
p2++;
|
|
*p2 = NULLCHAR;
|
|
kt->kernel_version[0] = atoi(p1);
|
|
p1 = ++p2;
|
|
while (*p2 != '.')
|
|
p2++;
|
|
*p2 = NULLCHAR;
|
|
kt->kernel_version[1] = atoi(p1);
|
|
p1 = ++p2;
|
|
while ((*p2 >= '0') && (*p2 <= '9'))
|
|
p2++;
|
|
*p2 = NULLCHAR;
|
|
kt->kernel_version[2] = atoi(p1);
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "base kernel version: %d.%d.%d\n",
|
|
kt->kernel_version[0],
|
|
kt->kernel_version[1],
|
|
kt->kernel_version[2]);
|
|
free(string);
|
|
} else
|
|
error(FATAL, "VMCOREINFO: cannot determine kernel version\n");
|
|
|
|
if ((string = pc->read_vmcoreinfo("PAGESIZE"))) {
|
|
machdep->pagesize = atoi(string);
|
|
machdep->pageoffset = machdep->pagesize - 1;
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "PAGESIZE: %d\n", machdep->pagesize);
|
|
free(string);
|
|
} else
|
|
error(FATAL, "VMCOREINFO: cannot determine page size\n");
|
|
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(log_buf)"))) {
|
|
vmc->log_buf_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(log_buf): %lx\n",
|
|
vmc->log_buf_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(log_end)"))) {
|
|
vmc->log_end_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(log_end): %lx\n",
|
|
vmc->log_end_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(log_buf_len)"))) {
|
|
vmc->log_buf_len_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(log_buf_len): %lx\n",
|
|
vmc->log_buf_len_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(logged_chars)"))) {
|
|
vmc->logged_chars_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(logged_chars): %lx\n",
|
|
vmc->logged_chars_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(log_first_idx)"))) {
|
|
vmc->log_first_idx_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(log_first_idx): %lx\n",
|
|
vmc->log_first_idx_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(log_next_idx)"))) {
|
|
vmc->log_next_idx_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(log_next_idx): %lx\n",
|
|
vmc->log_next_idx_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(phys_base)"))) {
|
|
vmc->phys_base_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(phys_base): %lx\n",
|
|
vmc->phys_base_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SYMBOL(_stext)"))) {
|
|
vmc->_stext_SYMBOL = htol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SYMBOL(_stext): %lx\n",
|
|
vmc->_stext_SYMBOL);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("OFFSET(log.ts_nsec)"))) {
|
|
vmc->log_ts_nsec_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(log.ts_nsec): %ld\n",
|
|
vmc->log_ts_nsec_OFFSET);
|
|
free(string);
|
|
} else if ((string = pc->read_vmcoreinfo("OFFSET(printk_log.ts_nsec)"))) {
|
|
vmc->log_ts_nsec_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(printk_log.ts_nsec): %ld\n",
|
|
vmc->log_ts_nsec_OFFSET);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("OFFSET(log.len)"))) {
|
|
vmc->log_len_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(log.len): %ld\n",
|
|
vmc->log_len_OFFSET);
|
|
free(string);
|
|
} else if ((string = pc->read_vmcoreinfo("OFFSET(printk_log.len)"))) {
|
|
vmc->log_len_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(printk_log.len): %ld\n",
|
|
vmc->log_len_OFFSET);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("OFFSET(log.text_len)"))) {
|
|
vmc->log_text_len_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(log.text_len): %ld\n",
|
|
vmc->log_text_len_OFFSET);
|
|
free(string);
|
|
} else if ((string = pc->read_vmcoreinfo("OFFSET(printk_log.text_len)"))) {
|
|
vmc->log_text_len_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(printk_log.text_len): %ld\n",
|
|
vmc->log_text_len_OFFSET);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("OFFSET(log.dict_len)"))) {
|
|
vmc->log_dict_len_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(log.dict_len): %ld\n",
|
|
vmc->log_dict_len_OFFSET);
|
|
free(string);
|
|
} else if ((string = pc->read_vmcoreinfo("OFFSET(printk_log.dict_len)"))) {
|
|
vmc->log_dict_len_OFFSET = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "OFFSET(printk_log.dict_len): %ld\n",
|
|
vmc->log_dict_len_OFFSET);
|
|
free(string);
|
|
}
|
|
if ((string = pc->read_vmcoreinfo("SIZE(log)"))) {
|
|
vmc->log_SIZE = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SIZE(log): %ld\n", vmc->log_SIZE);
|
|
free(string);
|
|
} else if ((string = pc->read_vmcoreinfo("SIZE(printk_log)"))) {
|
|
vmc->log_SIZE = dtol(string, RETURN_ON_ERROR, NULL);
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "SIZE(printk_log): %ld\n", vmc->log_SIZE);
|
|
free(string);
|
|
}
|
|
|
|
/*
|
|
* The per-arch VTOP() macro must be functional.
|
|
*/
|
|
machdep_init(LOG_ONLY);
|
|
|
|
if (vmc->log_buf_SYMBOL && vmc->log_buf_len_SYMBOL &&
|
|
vmc->log_first_idx_SYMBOL && vmc->log_next_idx_SYMBOL &&
|
|
(vmc->log_SIZE > 0) &&
|
|
(vmc->log_ts_nsec_OFFSET >= 0) &&
|
|
(vmc->log_len_OFFSET >= 0) &&
|
|
(vmc->log_text_len_OFFSET >= 0) &&
|
|
(vmc->log_dict_len_OFFSET >= 0))
|
|
dump_variable_length_record();
|
|
else if (vmc->log_buf_SYMBOL && vmc->log_end_SYMBOL &&
|
|
vmc->log_buf_len_SYMBOL && vmc->logged_chars_SYMBOL)
|
|
dump_log_legacy();
|
|
else
|
|
error(FATAL, "VMCOREINFO: no log buffer data\n");
|
|
}
|
|
|
|
static void
|
|
dump_log_legacy(void)
|
|
{
|
|
int i;
|
|
physaddr_t paddr;
|
|
ulong long_value;
|
|
uint int_value;
|
|
ulong log_buf;
|
|
uint log_end, log_buf_len, logged_chars, total;
|
|
char *buf, *p;
|
|
ulong index, bytes;
|
|
struct vmcoreinfo_data *vmc;
|
|
|
|
vmc = &kt->vmcoreinfo;
|
|
log_buf = log_end = log_buf_len = logged_chars = 0;
|
|
|
|
paddr = VTOP(vmc->log_buf_SYMBOL);
|
|
if (readmem(paddr, PHYSADDR, &long_value, sizeof(ulong),
|
|
"log_buf pointer", RETURN_ON_ERROR))
|
|
log_buf = long_value;
|
|
else
|
|
error(FATAL, "cannot read log_buf value\n");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "log_buf vaddr: %lx paddr: %llx => %lx\n",
|
|
vmc->log_buf_SYMBOL, (ulonglong)paddr, log_buf);
|
|
|
|
paddr = VTOP(vmc->log_end_SYMBOL);
|
|
if (THIS_KERNEL_VERSION < LINUX(2,6,25)) {
|
|
if (readmem(paddr, PHYSADDR, &long_value, sizeof(ulong),
|
|
"log_end (long)", RETURN_ON_ERROR))
|
|
log_end = (uint)long_value;
|
|
else
|
|
error(FATAL, "cannot read log_end value\n");
|
|
} else {
|
|
if (readmem(paddr, PHYSADDR, &int_value, sizeof(uint),
|
|
"log_end (int)", RETURN_ON_ERROR))
|
|
log_end = int_value;
|
|
else
|
|
error(FATAL, "cannot read log_end value\n");
|
|
}
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "log_end vaddr: %lx paddr: %llx => %d\n",
|
|
vmc->log_end_SYMBOL, (ulonglong)paddr, log_end);
|
|
|
|
paddr = VTOP(vmc->log_buf_len_SYMBOL);
|
|
if (readmem(paddr, PHYSADDR, &int_value, sizeof(uint),
|
|
"log_buf_len", RETURN_ON_ERROR))
|
|
log_buf_len = int_value;
|
|
else
|
|
error(FATAL, "cannot read log_buf_len value\n");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "log_buf_len vaddr: %lx paddr: %llx => %d\n",
|
|
vmc->log_buf_len_SYMBOL, (ulonglong)paddr, log_buf_len);
|
|
|
|
paddr = VTOP(vmc->logged_chars_SYMBOL);
|
|
if (readmem(paddr, PHYSADDR, &int_value, sizeof(uint),
|
|
"logged_chars", RETURN_ON_ERROR))
|
|
logged_chars = int_value;
|
|
else
|
|
error(FATAL, "cannot read logged_chars value\n");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "logged_chars vaddr: %lx paddr: %llx => %d\n",
|
|
vmc->logged_chars_SYMBOL, (ulonglong)paddr, logged_chars);
|
|
|
|
if ((buf = calloc(sizeof(char), log_buf_len)) == NULL)
|
|
error(FATAL, "cannot calloc log_buf_len (%d) bytes\n",
|
|
log_buf_len);
|
|
|
|
paddr = VTOP(log_buf);
|
|
|
|
if (log_end < log_buf_len) {
|
|
bytes = log_end;
|
|
if (!readmem(paddr, PHYSADDR, buf, bytes,
|
|
"log_buf", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot read log_buf\n");
|
|
total = bytes;
|
|
} else {
|
|
index = log_end & (log_buf_len - 1);
|
|
bytes = log_buf_len - index;
|
|
if (!readmem(paddr + index, PHYSADDR, buf, bytes,
|
|
"log_buf + index", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot read log_buf\n");
|
|
if (!readmem(paddr, PHYSADDR, buf + bytes, index,
|
|
"log_buf", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot read log_buf\n");
|
|
total = log_buf_len;
|
|
}
|
|
|
|
for (i = 0, p = buf; i < total; i++, p++) {
|
|
if (*p == NULLCHAR)
|
|
fputc('\n', fp);
|
|
else if (ascii(*p))
|
|
fputc(*p, fp);
|
|
else
|
|
fputc('.', fp);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_variable_length_record(void)
|
|
{
|
|
physaddr_t paddr;
|
|
ulong long_value;
|
|
uint32_t int_value;
|
|
struct vmcoreinfo_data *vmc;
|
|
ulong log_buf;
|
|
uint32_t idx, log_buf_len, log_first_idx, log_next_idx;
|
|
char *buf, *logptr;
|
|
|
|
vmc = &kt->vmcoreinfo;
|
|
log_buf = log_buf_len = log_first_idx = log_next_idx = 0;
|
|
|
|
paddr = VTOP(vmc->log_buf_SYMBOL);
|
|
if (readmem(paddr, PHYSADDR, &long_value, sizeof(ulong),
|
|
"log_buf pointer", RETURN_ON_ERROR))
|
|
log_buf = long_value;
|
|
else
|
|
error(FATAL, "cannot read log_buf value\n");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "log_buf vaddr: %lx paddr: %llx => %lx\n",
|
|
vmc->log_buf_SYMBOL, (ulonglong)paddr, log_buf);
|
|
|
|
paddr = VTOP(vmc->log_buf_len_SYMBOL);
|
|
if (readmem(paddr, PHYSADDR, &int_value, sizeof(uint),
|
|
"log_buf_len", RETURN_ON_ERROR))
|
|
log_buf_len = int_value;
|
|
else
|
|
error(FATAL, "cannot read log_buf_len value\n");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "log_buf_len vaddr: %lx paddr: %llx => %d\n",
|
|
vmc->log_buf_len_SYMBOL, (ulonglong)paddr, log_buf_len);
|
|
|
|
paddr = VTOP(vmc->log_first_idx_SYMBOL);
|
|
if (readmem(paddr, PHYSADDR, &int_value, sizeof(uint),
|
|
"log_first_idx", RETURN_ON_ERROR))
|
|
log_first_idx = int_value;
|
|
else
|
|
error(FATAL, "cannot read log_first_idx value\n");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "log_first_idx vaddr: %lx paddr: %llx => %d\n",
|
|
vmc->log_first_idx_SYMBOL, (ulonglong)paddr, log_first_idx);
|
|
|
|
paddr = VTOP(vmc->log_next_idx_SYMBOL);
|
|
if (readmem(paddr, PHYSADDR, &int_value, sizeof(uint),
|
|
"log_next_idx", RETURN_ON_ERROR))
|
|
log_next_idx = int_value;
|
|
else
|
|
error(FATAL, "cannot read log_next_idx value\n");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "log_next_idx vaddr: %lx paddr: %llx => %d\n",
|
|
vmc->log_next_idx_SYMBOL, (ulonglong)paddr, log_next_idx);
|
|
|
|
ASSIGN_SIZE(log)= vmc->log_SIZE;
|
|
ASSIGN_OFFSET(log_ts_nsec) = vmc->log_ts_nsec_OFFSET;
|
|
ASSIGN_OFFSET(log_len) = vmc->log_len_OFFSET;
|
|
ASSIGN_OFFSET(log_text_len) = vmc->log_text_len_OFFSET;
|
|
ASSIGN_OFFSET(log_dict_len) = vmc->log_dict_len_OFFSET;
|
|
|
|
if ((buf = calloc(sizeof(char), log_buf_len)) == NULL)
|
|
error(FATAL, "cannot calloc log_buf_len (%d) bytes\n",
|
|
log_buf_len);
|
|
|
|
paddr = VTOP(log_buf);
|
|
|
|
if (!readmem(paddr, PHYSADDR, buf, log_buf_len,
|
|
"log_buf", RETURN_ON_ERROR))
|
|
error(FATAL, "cannot read log_buf\n");
|
|
|
|
hq_init();
|
|
hq_open();
|
|
|
|
idx = log_first_idx;
|
|
while (idx != log_next_idx) {
|
|
logptr = log_from_idx(idx, buf);
|
|
|
|
dump_log_entry(logptr, 0);
|
|
|
|
if (!hq_enter((ulong)logptr)) {
|
|
error(INFO, "\nduplicate log_buf message pointer\n");
|
|
break;
|
|
}
|
|
|
|
idx = log_next(idx, buf);
|
|
|
|
if (idx >= log_buf_len) {
|
|
error(INFO, "\ninvalid log_buf entry encountered\n");
|
|
break;
|
|
}
|
|
|
|
if (CRASHDEBUG(1) && (idx == log_next_idx))
|
|
fprintf(fp, "\nfound log_next_idx OK\n");
|
|
}
|
|
|
|
hq_close();
|
|
}
|
|
|
|
static void
|
|
show_kernel_taints(char *buf, int verbose)
|
|
{
|
|
int i, bx;
|
|
uint8_t tnt_bit;
|
|
char tnt_true, tnt_false;
|
|
int tnts_len;
|
|
ulong tnts_addr;
|
|
ulong tainted_mask, *tainted_mask_ptr;
|
|
int tainted;
|
|
struct syment *sp;
|
|
|
|
if (!VALID_STRUCT(tnt)) {
|
|
STRUCT_SIZE_INIT(tnt, "tnt");
|
|
MEMBER_OFFSET_INIT(tnt_bit, "tnt", "bit");
|
|
MEMBER_OFFSET_INIT(tnt_true, "tnt", "true");
|
|
MEMBER_OFFSET_INIT(tnt_false, "tnt", "false");
|
|
}
|
|
|
|
if (VALID_STRUCT(tnt) && (sp = symbol_search("tnts"))) {
|
|
tnts_len = get_array_length("tnts", NULL, 0);
|
|
tnts_addr = sp->value;
|
|
} else
|
|
tnts_addr = tnts_len = 0;
|
|
|
|
bx = 0;
|
|
buf[0] = '\0';
|
|
|
|
tainted_mask = tainted = 0;
|
|
|
|
if (kernel_symbol_exists("tainted_mask")) {
|
|
get_symbol_data("tainted_mask", sizeof(ulong), &tainted_mask);
|
|
tainted_mask_ptr = &tainted_mask;
|
|
} else if (kernel_symbol_exists("tainted")) {
|
|
get_symbol_data("tainted", sizeof(int), &tainted);
|
|
if (verbose)
|
|
fprintf(fp, "TAINTED: %x\n", tainted);
|
|
return;
|
|
} else if (verbose)
|
|
option_not_supported('t');
|
|
|
|
for (i = 0; i < (tnts_len * SIZE(tnt)); i += SIZE(tnt)) {
|
|
readmem((tnts_addr + i) + OFFSET(tnt_bit),
|
|
KVADDR, &tnt_bit, sizeof(uint8_t),
|
|
"tnt bit", FAULT_ON_ERROR);
|
|
|
|
if (NUM_IN_BITMAP(tainted_mask_ptr, tnt_bit)) {
|
|
readmem((tnts_addr + i) + OFFSET(tnt_true),
|
|
KVADDR, &tnt_true, sizeof(char),
|
|
"tnt true", FAULT_ON_ERROR);
|
|
buf[bx++] = tnt_true;
|
|
} else {
|
|
readmem((tnts_addr + i) + OFFSET(tnt_false),
|
|
KVADDR, &tnt_false, sizeof(char),
|
|
"tnt false", FAULT_ON_ERROR);
|
|
if (tnt_false != ' ' && tnt_false != '-' &&
|
|
tnt_false != 'G')
|
|
buf[bx++] = tnt_false;
|
|
}
|
|
}
|
|
|
|
buf[bx++] = '\0';
|
|
|
|
if (verbose)
|
|
fprintf(fp, "TAINTED_MASK: %lx %s\n", tainted_mask, buf);
|
|
}
|
|
|