mirror of
https://github.com/crash-utility/crash
synced 2025-02-18 22:46:50 +00:00
Previously, the ps command will iterate over all threads which have the same tgid, to accumulate their rss value, in order to get a thread/process's final rss value as part of the final output. For non-live systems, the rss accumulation values are identical for threads which have the same tgid, so there is no need to do the iteration and accumulation repeatly, thus a lot of readmem calls are skipped. Otherwise it will be the performance bottleneck if the vmcores have a large number of threads. In this patch, the rss accumulation value will be stored in a cache, next time a thread with the same tgid will take it directly without the iteration. For example, we can monitor the performance issue when a vmcore has ~65k processes, most of which are threads for several specific processes. Without the patch, it will take ~7h for ps command to finish. With the patch, ps command will finish in 1min. Signed-off-by: Tao Liu <ltao@redhat.com>
11306 lines
292 KiB
C
11306 lines
292 KiB
C
/* task.c - core analysis suite
|
|
*
|
|
* Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
|
|
* Copyright (C) 2002-2018 David Anderson
|
|
* Copyright (C) 2002-2018 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"
|
|
|
|
static ulong get_panic_context(void);
|
|
static int sort_by_pid(const void *, const void *);
|
|
static void show_ps(ulong, struct psinfo *);
|
|
static struct task_context *panic_search(void);
|
|
static void allocate_task_space(int);
|
|
static void refresh_fixed_task_table(void);
|
|
static void refresh_unlimited_task_table(void);
|
|
static void refresh_pidhash_task_table(void);
|
|
static void refresh_pid_hash_task_table(void);
|
|
static void refresh_hlist_task_table(void);
|
|
static void refresh_hlist_task_table_v2(void);
|
|
static void refresh_hlist_task_table_v3(void);
|
|
static void refresh_active_task_table(void);
|
|
static int radix_tree_task_callback(ulong);
|
|
static void refresh_radix_tree_task_table(void);
|
|
static void refresh_xarray_task_table(void);
|
|
static struct task_context *add_context(ulong, char *);
|
|
static void refresh_context(ulong, ulong);
|
|
static ulong parent_of(ulong);
|
|
static void parent_list(ulong);
|
|
static void child_list(ulong);
|
|
static void initialize_task_state(void);
|
|
static void dump_task_states(void);
|
|
static void show_ps_data(ulong, struct task_context *, struct psinfo *);
|
|
static void show_task_times(struct task_context *, ulong);
|
|
static void show_task_args(struct task_context *);
|
|
static void show_task_rlimit(struct task_context *);
|
|
static void show_tgid_list(ulong);
|
|
static int compare_start_time(const void *, const void *);
|
|
static int start_time_timespec(void);
|
|
static ulonglong convert_start_time(ulonglong, ulonglong);
|
|
static ulong search_panic_task_by_cpu(char *);
|
|
static ulong search_panic_task_by_keywords(char *, int *);
|
|
static ulong get_log_panic_task(void);
|
|
static ulong get_dumpfile_panic_task(void);
|
|
static ulong get_active_set_panic_task(void);
|
|
static void populate_panic_threads(void);
|
|
static int verify_task(struct task_context *, int);
|
|
static ulong get_idle_task(int, char *);
|
|
static ulong get_curr_task(int, char *);
|
|
static long rq_idx(int);
|
|
static long cpu_idx(int);
|
|
static void dump_runq(void);
|
|
static void dump_on_rq_timestamp(void);
|
|
static void dump_on_rq_lag(void);
|
|
static void dump_on_rq_milliseconds(void);
|
|
static void dump_runqueues(void);
|
|
static void dump_prio_array(int, ulong, char *);
|
|
static void dump_task_runq_entry(struct task_context *, int);
|
|
static void print_group_header_fair(int, ulong, void *);
|
|
static void print_parent_task_group_fair(void *, int);
|
|
static int dump_tasks_in_lower_dequeued_cfs_rq(int, ulong, int, struct task_context *);
|
|
static int dump_tasks_in_cfs_rq(ulong);
|
|
static int dump_tasks_in_task_group_cfs_rq(int, ulong, int, struct task_context *);
|
|
static void dump_on_rq_tasks(void);
|
|
static void cfs_rq_offset_init(void);
|
|
static void task_group_offset_init(void);
|
|
static void dump_CFS_runqueues(void);
|
|
static void print_group_header_rt(ulong, void *);
|
|
static void print_parent_task_group_rt(void *, int);
|
|
static int dump_tasks_in_lower_dequeued_rt_rq(int, ulong, int);
|
|
static int dump_RT_prio_array(ulong, char *);
|
|
static void dump_tasks_in_task_group_rt_rq(int, ulong, int);
|
|
static char *get_task_group_name(ulong);
|
|
static void sort_task_group_info_array(void);
|
|
static void print_task_group_info_array(void);
|
|
static void reuse_task_group_info_array(void);
|
|
static void free_task_group_info_array(void);
|
|
static void fill_task_group_info_array(int, ulong, char *, int);
|
|
static void dump_tasks_by_task_group(void);
|
|
static void task_struct_member(struct task_context *,unsigned int, struct reference *);
|
|
static void signal_reference(struct task_context *, ulong, struct reference *);
|
|
static void do_sig_thread_group(ulong);
|
|
static void dump_signal_data(struct task_context *, ulong);
|
|
#define TASK_LEVEL (0x1)
|
|
#define THREAD_GROUP_LEVEL (0x2)
|
|
#define TASK_INDENT (0x4)
|
|
static int sigrt_minmax(int *, int *);
|
|
static void signame_list(void);
|
|
static void sigqueue_list(ulong);
|
|
static ulonglong task_signal(ulong, ulong*);
|
|
static ulonglong task_blocked(ulong);
|
|
static void translate_sigset(ulonglong);
|
|
static ulonglong sigaction_mask(ulong);
|
|
static int task_has_cpu(ulong, char *);
|
|
static int is_foreach_keyword(char *, int *);
|
|
static void foreach_cleanup(void *);
|
|
static void ps_cleanup(void *);
|
|
static char *task_pointer_string(struct task_context *, ulong, char *);
|
|
static int panic_context_adjusted(struct task_context *tc);
|
|
static void show_last_run(struct task_context *, struct psinfo *);
|
|
static void show_milliseconds(struct task_context *, struct psinfo *);
|
|
static char *translate_nanoseconds(ulonglong, char *);
|
|
static int sort_by_last_run(const void *arg1, const void *arg2);
|
|
static void sort_context_array_by_last_run(void);
|
|
static void show_ps_summary(ulong);
|
|
static void irqstacks_init(void);
|
|
static void parse_task_thread(int argcnt, char *arglist[], struct task_context *);
|
|
static void stack_overflow_check_init(void);
|
|
static int has_sched_policy(ulong, ulong);
|
|
static ulong task_policy(ulong);
|
|
static ulong sched_policy_bit_from_str(const char *);
|
|
static ulong make_sched_policy(const char *);
|
|
|
|
static struct sched_policy_info {
|
|
ulong value;
|
|
char *name;
|
|
} sched_policy_info[] = {
|
|
{ SCHED_NORMAL, "NORMAL" },
|
|
{ SCHED_FIFO, "FIFO" },
|
|
{ SCHED_RR, "RR" },
|
|
{ SCHED_BATCH, "BATCH" },
|
|
{ SCHED_ISO, "ISO" },
|
|
{ SCHED_IDLE, "IDLE" },
|
|
{ SCHED_DEADLINE, "DEADLINE" },
|
|
{ ULONG_MAX, NULL }
|
|
};
|
|
|
|
enum PANIC_TASK_FOUND_RESULT {
|
|
FOUND_NO_PANIC_KEYWORD,
|
|
FOUND_PANIC_KEYWORD,
|
|
FOUND_PANIC_TASK
|
|
};
|
|
|
|
const char *panic_keywords[] = {
|
|
"Unable to handle kernel",
|
|
"BUG: unable to handle kernel",
|
|
"Kernel BUG at",
|
|
"kernel BUG at",
|
|
"Bad mode in",
|
|
"Oops",
|
|
"Kernel panic",
|
|
NULL,
|
|
};
|
|
|
|
/*
|
|
* Figure out how much space will be required to hold the task context
|
|
* data, malloc() it, and call refresh_task_table() to fill it up.
|
|
* Gather a few key offset and size values. Lastly, get, and then set,
|
|
* the initial context.
|
|
*/
|
|
void
|
|
task_init(void)
|
|
{
|
|
long len;
|
|
int dim, task_struct_size;
|
|
struct syment *nsp;
|
|
long tss_offset, thread_offset;
|
|
long eip_offset, esp_offset, ksp_offset;
|
|
struct gnu_request req;
|
|
ulong active_pid;
|
|
|
|
if (!(tt->idle_threads = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc idle_threads array");
|
|
if (DUMPFILE() &&
|
|
!(tt->panic_threads = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc panic_threads array");
|
|
|
|
if (kernel_symbol_exists("nr_tasks")) {
|
|
/*
|
|
* Figure out what maximum NR_TASKS would be by getting the
|
|
* address of the next symbol after "task".
|
|
*/
|
|
tt->task_start = symbol_value("task");
|
|
if ((nsp = next_symbol("task", NULL)) == NULL)
|
|
error(FATAL, "cannot determine size of task table\n");
|
|
|
|
tt->flags |= TASK_ARRAY_EXISTS;
|
|
tt->task_end = nsp->value;
|
|
tt->max_tasks = (tt->task_end-tt->task_start) / sizeof(void *);
|
|
allocate_task_space(tt->max_tasks);
|
|
|
|
tss_offset = MEMBER_OFFSET_INIT(task_struct_tss,
|
|
"task_struct", "tss");
|
|
eip_offset = MEMBER_OFFSET_INIT(thread_struct_eip,
|
|
"thread_struct", "eip");
|
|
esp_offset = MEMBER_OFFSET_INIT(thread_struct_esp,
|
|
"thread_struct", "esp");
|
|
ksp_offset = MEMBER_OFFSET_INIT(thread_struct_ksp,
|
|
"thread_struct", "ksp");
|
|
ASSIGN_OFFSET(task_struct_tss_eip) =
|
|
(eip_offset == INVALID_OFFSET) ?
|
|
INVALID_OFFSET : tss_offset + eip_offset;
|
|
ASSIGN_OFFSET(task_struct_tss_esp) =
|
|
(esp_offset == INVALID_OFFSET) ?
|
|
INVALID_OFFSET : tss_offset + esp_offset;
|
|
ASSIGN_OFFSET(task_struct_tss_ksp) =
|
|
(ksp_offset == INVALID_OFFSET) ?
|
|
INVALID_OFFSET : tss_offset + ksp_offset;
|
|
|
|
tt->flags |= TASK_REFRESH;
|
|
tt->refresh_task_table = refresh_fixed_task_table;
|
|
|
|
readmem(tt->task_start, KVADDR, &tt->idle_threads[0],
|
|
kt->cpus * sizeof(void *), "idle threads",
|
|
FAULT_ON_ERROR);
|
|
} else {
|
|
/*
|
|
* Make the task table big enough to hold what's running.
|
|
* It can be realloc'd later if it grows on a live system.
|
|
*/
|
|
get_symbol_data("nr_threads", sizeof(int), &tt->nr_threads);
|
|
tt->max_tasks = tt->nr_threads + NR_CPUS + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
|
|
thread_offset = MEMBER_OFFSET_INIT(task_struct_thread,
|
|
"task_struct", "thread");
|
|
eip_offset = MEMBER_OFFSET_INIT(thread_struct_eip,
|
|
"thread_struct", "eip");
|
|
esp_offset = MEMBER_OFFSET_INIT(thread_struct_esp,
|
|
"thread_struct", "esp");
|
|
/*
|
|
* Handle x86/x86_64 merger.
|
|
*/
|
|
if (eip_offset == INVALID_OFFSET)
|
|
eip_offset = MEMBER_OFFSET_INIT(thread_struct_eip,
|
|
"thread_struct", "ip");
|
|
if (esp_offset == INVALID_OFFSET)
|
|
esp_offset = MEMBER_OFFSET_INIT(thread_struct_esp,
|
|
"thread_struct", "sp");
|
|
ksp_offset = MEMBER_OFFSET_INIT(thread_struct_ksp,
|
|
"thread_struct", "ksp");
|
|
ASSIGN_OFFSET(task_struct_thread_eip) =
|
|
(eip_offset == INVALID_OFFSET) ?
|
|
INVALID_OFFSET : thread_offset + eip_offset;
|
|
ASSIGN_OFFSET(task_struct_thread_esp) =
|
|
(esp_offset == INVALID_OFFSET) ?
|
|
INVALID_OFFSET : thread_offset + esp_offset;
|
|
ASSIGN_OFFSET(task_struct_thread_ksp) =
|
|
(ksp_offset == INVALID_OFFSET) ?
|
|
INVALID_OFFSET : thread_offset + ksp_offset;
|
|
|
|
tt->flags |= TASK_REFRESH;
|
|
tt->refresh_task_table = refresh_unlimited_task_table;
|
|
|
|
get_idle_threads(&tt->idle_threads[0], kt->cpus);
|
|
}
|
|
|
|
/*
|
|
* Handle CONFIG_THREAD_INFO_IN_TASK changes
|
|
*/
|
|
MEMBER_OFFSET_INIT(task_struct_stack, "task_struct", "stack");
|
|
MEMBER_OFFSET_INIT(task_struct_thread_info, "task_struct", "thread_info");
|
|
|
|
if (VALID_MEMBER(task_struct_thread_info)) {
|
|
switch (MEMBER_TYPE("task_struct", "thread_info"))
|
|
{
|
|
case TYPE_CODE_PTR:
|
|
break;
|
|
case TYPE_CODE_STRUCT:
|
|
tt->flags |= THREAD_INFO_IN_TASK;
|
|
break;
|
|
default:
|
|
error(FATAL,
|
|
"unexpected type code for task_struct.thread_info: %ld\n",
|
|
MEMBER_TYPE("task_struct", "thread_info"));
|
|
break;
|
|
}
|
|
} else if (VALID_MEMBER(task_struct_stack))
|
|
MEMBER_OFFSET_INIT(task_struct_thread_info, "task_struct", "stack");
|
|
|
|
MEMBER_OFFSET_INIT(task_struct_cpu, "task_struct", "cpu");
|
|
|
|
if (VALID_MEMBER(task_struct_thread_info)) {
|
|
if (tt->flags & THREAD_INFO_IN_TASK && VALID_MEMBER(task_struct_cpu)) {
|
|
MEMBER_OFFSET_INIT(thread_info_flags, "thread_info", "flags");
|
|
/* (unnecessary) reminders */
|
|
ASSIGN_OFFSET(thread_info_task) = INVALID_OFFSET;
|
|
ASSIGN_OFFSET(thread_info_cpu) = INVALID_OFFSET;
|
|
ASSIGN_OFFSET(thread_info_previous_esp) = INVALID_OFFSET;
|
|
} else {
|
|
MEMBER_OFFSET_INIT(thread_info_task, "thread_info", "task");
|
|
MEMBER_OFFSET_INIT(thread_info_cpu, "thread_info", "cpu");
|
|
MEMBER_OFFSET_INIT(thread_info_flags, "thread_info", "flags");
|
|
MEMBER_OFFSET_INIT(thread_info_previous_esp, "thread_info",
|
|
"previous_esp");
|
|
}
|
|
STRUCT_SIZE_INIT(thread_info, "thread_info");
|
|
tt->flags |= THREAD_INFO;
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(task_struct_state, "task_struct", "state");
|
|
MEMBER_SIZE_INIT(task_struct_state, "task_struct", "state");
|
|
if (INVALID_MEMBER(task_struct_state)) {
|
|
MEMBER_OFFSET_INIT(task_struct_state, "task_struct", "__state");
|
|
MEMBER_SIZE_INIT(task_struct_state, "task_struct", "__state");
|
|
}
|
|
MEMBER_OFFSET_INIT(task_struct_exit_state, "task_struct", "exit_state");
|
|
MEMBER_OFFSET_INIT(task_struct_pid, "task_struct", "pid");
|
|
MEMBER_OFFSET_INIT(task_struct_comm, "task_struct", "comm");
|
|
MEMBER_OFFSET_INIT(task_struct_next_task, "task_struct", "next_task");
|
|
MEMBER_OFFSET_INIT(task_struct_processor, "task_struct", "processor");
|
|
MEMBER_OFFSET_INIT(task_struct_p_pptr, "task_struct", "p_pptr");
|
|
MEMBER_OFFSET_INIT(task_struct_parent, "task_struct", "parent");
|
|
if (INVALID_MEMBER(task_struct_parent))
|
|
MEMBER_OFFSET_INIT(task_struct_parent, "task_struct",
|
|
"real_parent");
|
|
MEMBER_OFFSET_INIT(task_struct_has_cpu, "task_struct", "has_cpu");
|
|
MEMBER_OFFSET_INIT(task_struct_cpus_runnable,
|
|
"task_struct", "cpus_runnable");
|
|
MEMBER_OFFSET_INIT(task_struct_active_mm, "task_struct", "active_mm");
|
|
MEMBER_OFFSET_INIT(task_struct_next_run, "task_struct", "next_run");
|
|
MEMBER_OFFSET_INIT(task_struct_flags, "task_struct", "flags");
|
|
MEMBER_SIZE_INIT(task_struct_flags, "task_struct", "flags");
|
|
MEMBER_OFFSET_INIT(task_struct_policy, "task_struct", "policy");
|
|
MEMBER_SIZE_INIT(task_struct_policy, "task_struct", "policy");
|
|
MEMBER_OFFSET_INIT(task_struct_pidhash_next,
|
|
"task_struct", "pidhash_next");
|
|
MEMBER_OFFSET_INIT(task_struct_pgrp, "task_struct", "pgrp");
|
|
MEMBER_OFFSET_INIT(task_struct_tgid, "task_struct", "tgid");
|
|
MEMBER_OFFSET_INIT(task_struct_pids, "task_struct", "pids");
|
|
MEMBER_OFFSET_INIT(task_struct_last_run, "task_struct", "last_run");
|
|
MEMBER_OFFSET_INIT(task_struct_timestamp, "task_struct", "timestamp");
|
|
MEMBER_OFFSET_INIT(task_struct_sched_info, "task_struct", "sched_info");
|
|
if (VALID_MEMBER(task_struct_sched_info))
|
|
MEMBER_OFFSET_INIT(sched_info_last_arrival,
|
|
"sched_info", "last_arrival");
|
|
if (VALID_MEMBER(task_struct_last_run) ||
|
|
VALID_MEMBER(task_struct_timestamp) ||
|
|
VALID_MEMBER(sched_info_last_arrival)) {
|
|
char buf[BUFSIZE];
|
|
strcpy(buf, "alias last ps -l");
|
|
alias_init(buf);
|
|
}
|
|
MEMBER_OFFSET_INIT(task_struct_pid_links, "task_struct", "pid_links");
|
|
MEMBER_OFFSET_INIT(pid_link_pid, "pid_link", "pid");
|
|
MEMBER_OFFSET_INIT(pid_hash_chain, "pid", "hash_chain");
|
|
|
|
STRUCT_SIZE_INIT(pid_link, "pid_link");
|
|
STRUCT_SIZE_INIT(upid, "upid");
|
|
if (VALID_STRUCT(upid)) {
|
|
MEMBER_OFFSET_INIT(upid_nr, "upid", "nr");
|
|
MEMBER_OFFSET_INIT(upid_ns, "upid", "ns");
|
|
MEMBER_OFFSET_INIT(upid_pid_chain, "upid", "pid_chain");
|
|
MEMBER_OFFSET_INIT(pid_numbers, "pid", "numbers");
|
|
MEMBER_OFFSET_INIT(pid_tasks, "pid", "tasks");
|
|
tt->init_pid_ns = symbol_value("init_pid_ns");
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(pid_pid_chain, "pid", "pid_chain");
|
|
|
|
STRUCT_SIZE_INIT(task_struct, "task_struct");
|
|
|
|
if (kernel_symbol_exists("arch_task_struct_size") &&
|
|
readmem(symbol_value("arch_task_struct_size"), KVADDR,
|
|
&task_struct_size, sizeof(int),
|
|
"arch_task_struct_size", RETURN_ON_ERROR)) {
|
|
ASSIGN_SIZE(task_struct) = task_struct_size;
|
|
if (STRUCT_SIZE("task_struct") != SIZE(task_struct))
|
|
add_to_downsized("task_struct");
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "downsize task_struct: %ld to %ld\n",
|
|
STRUCT_SIZE("task_struct"),
|
|
SIZE(task_struct));
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(task_struct_sig, "task_struct", "sig");
|
|
MEMBER_OFFSET_INIT(task_struct_signal, "task_struct", "signal");
|
|
MEMBER_OFFSET_INIT(task_struct_blocked, "task_struct", "blocked");
|
|
MEMBER_OFFSET_INIT(task_struct_sigpending, "task_struct", "sigpending");
|
|
MEMBER_OFFSET_INIT(task_struct_pending, "task_struct", "pending");
|
|
MEMBER_OFFSET_INIT(task_struct_sigqueue, "task_struct", "sigqueue");
|
|
MEMBER_OFFSET_INIT(task_struct_sighand, "task_struct", "sighand");
|
|
|
|
MEMBER_OFFSET_INIT(signal_struct_count, "signal_struct", "count");
|
|
MEMBER_OFFSET_INIT(signal_struct_nr_threads, "signal_struct", "nr_threads");
|
|
MEMBER_OFFSET_INIT(signal_struct_action, "signal_struct", "action");
|
|
MEMBER_OFFSET_INIT(signal_struct_shared_pending, "signal_struct",
|
|
"shared_pending");
|
|
|
|
MEMBER_OFFSET_INIT(k_sigaction_sa, "k_sigaction", "sa");
|
|
|
|
MEMBER_OFFSET_INIT(sigaction_sa_handler, "sigaction", "sa_handler");
|
|
MEMBER_OFFSET_INIT(sigaction_sa_mask, "sigaction", "sa_mask");
|
|
MEMBER_OFFSET_INIT(sigaction_sa_flags, "sigaction", "sa_flags");
|
|
MEMBER_OFFSET_INIT(sigpending_head, "sigpending", "head");
|
|
if (INVALID_MEMBER(sigpending_head))
|
|
MEMBER_OFFSET_INIT(sigpending_list, "sigpending", "list");
|
|
MEMBER_OFFSET_INIT(sigpending_signal, "sigpending", "signal");
|
|
MEMBER_SIZE_INIT(sigpending_signal, "sigpending", "signal");
|
|
|
|
STRUCT_SIZE_INIT(sigqueue, "sigqueue");
|
|
STRUCT_SIZE_INIT(signal_queue, "signal_queue");
|
|
|
|
STRUCT_SIZE_INIT(sighand_struct, "sighand_struct");
|
|
if (VALID_STRUCT(sighand_struct))
|
|
MEMBER_OFFSET_INIT(sighand_struct_action, "sighand_struct",
|
|
"action");
|
|
|
|
MEMBER_OFFSET_INIT(siginfo_si_signo, "siginfo", "si_signo");
|
|
|
|
STRUCT_SIZE_INIT(signal_struct, "signal_struct");
|
|
STRUCT_SIZE_INIT(k_sigaction, "k_sigaction");
|
|
|
|
MEMBER_OFFSET_INIT(task_struct_start_time, "task_struct", "start_time");
|
|
MEMBER_SIZE_INIT(task_struct_start_time, "task_struct", "start_time");
|
|
MEMBER_SIZE_INIT(task_struct_utime, "task_struct", "utime");
|
|
MEMBER_SIZE_INIT(task_struct_stime, "task_struct", "stime");
|
|
MEMBER_OFFSET_INIT(task_struct_times, "task_struct", "times");
|
|
MEMBER_OFFSET_INIT(tms_tms_utime, "tms", "tms_utime");
|
|
MEMBER_OFFSET_INIT(tms_tms_stime, "tms", "tms_stime");
|
|
MEMBER_OFFSET_INIT(task_struct_utime, "task_struct", "utime");
|
|
MEMBER_OFFSET_INIT(task_struct_stime, "task_struct", "stime");
|
|
|
|
STRUCT_SIZE_INIT(cputime_t, "cputime_t");
|
|
|
|
if ((THIS_KERNEL_VERSION < LINUX(4,8,0)) &&
|
|
symbol_exists("cfq_slice_async")) {
|
|
uint cfq_slice_async;
|
|
|
|
get_symbol_data("cfq_slice_async", sizeof(int),
|
|
&cfq_slice_async);
|
|
|
|
if (cfq_slice_async) {
|
|
machdep->hz = cfq_slice_async * 25;
|
|
|
|
if (CRASHDEBUG(2))
|
|
fprintf(fp,
|
|
"cfq_slice_async exists: setting hz to %d\n",
|
|
machdep->hz);
|
|
}
|
|
} else if ((symbol_exists("dd_init_queue") &&
|
|
gdb_set_crash_scope(symbol_value("dd_init_queue"), "dd_init_queue")) ||
|
|
(symbol_exists("dd_init_sched") &&
|
|
gdb_set_crash_scope(symbol_value("dd_init_sched"), "dd_init_sched")) ||
|
|
(symbol_exists("deadline_init_queue") &&
|
|
gdb_set_crash_scope(symbol_value("deadline_init_queue"), "deadline_init_queue"))) {
|
|
char buf[BUFSIZE];
|
|
uint write_expire = 0;
|
|
|
|
open_tmpfile();
|
|
sprintf(buf, "printf \"%%d\", write_expire");
|
|
if (gdb_pass_through(buf, pc->tmpfile, GNU_RETURN_ON_ERROR)) {
|
|
rewind(pc->tmpfile);
|
|
if (fgets(buf, BUFSIZE, pc->tmpfile))
|
|
sscanf(buf, "%d", &write_expire);
|
|
}
|
|
close_tmpfile();
|
|
|
|
if (write_expire) {
|
|
machdep->hz = write_expire / 5;
|
|
if (CRASHDEBUG(2))
|
|
fprintf(fp, "write_expire exists: setting hz to %d\n",
|
|
machdep->hz);
|
|
}
|
|
gdb_set_crash_scope(0, NULL);
|
|
}
|
|
|
|
if (VALID_MEMBER(runqueue_arrays))
|
|
MEMBER_OFFSET_INIT(task_struct_run_list, "task_struct",
|
|
"run_list");
|
|
|
|
MEMBER_OFFSET_INIT(task_struct_rss_stat, "task_struct",
|
|
"rss_stat");
|
|
MEMBER_OFFSET_INIT(task_rss_stat_count, "task_rss_stat",
|
|
"count");
|
|
|
|
if ((tt->task_struct = (char *)malloc(SIZE(task_struct))) == NULL)
|
|
error(FATAL, "cannot malloc task_struct space.");
|
|
|
|
if ((tt->mm_struct = (char *)malloc(SIZE(mm_struct))) == NULL)
|
|
error(FATAL, "cannot malloc mm_struct space.");
|
|
|
|
if ((tt->flags & THREAD_INFO) &&
|
|
((tt->thread_info = (char *)malloc(SIZE(thread_info))) == NULL))
|
|
error(FATAL, "cannot malloc thread_info space.");
|
|
|
|
STRUCT_SIZE_INIT(task_union, "task_union");
|
|
STRUCT_SIZE_INIT(thread_union, "thread_union");
|
|
|
|
if (VALID_SIZE(task_union) && (SIZE(task_union) != STACKSIZE())) {
|
|
error(WARNING, "\nnon-standard stack size: %ld\n",
|
|
len = SIZE(task_union));
|
|
machdep->stacksize = len;
|
|
} else if (VALID_SIZE(thread_union) &&
|
|
((len = SIZE(thread_union)) != STACKSIZE())) {
|
|
machdep->stacksize = len;
|
|
} else if (!VALID_SIZE(thread_union) && !VALID_SIZE(task_union)) {
|
|
if (kernel_symbol_exists("__start_init_task") &&
|
|
kernel_symbol_exists("__end_init_task")) {
|
|
len = symbol_value("__end_init_task");
|
|
len -= symbol_value("__start_init_task");
|
|
ASSIGN_SIZE(thread_union) = len;
|
|
machdep->stacksize = len;
|
|
}
|
|
}
|
|
|
|
MEMBER_OFFSET_INIT(pid_namespace_idr, "pid_namespace", "idr");
|
|
MEMBER_OFFSET_INIT(idr_idr_rt, "idr", "idr_rt");
|
|
|
|
if (symbol_exists("height_to_maxindex") ||
|
|
symbol_exists("height_to_maxnodes")) {
|
|
int newver = symbol_exists("height_to_maxnodes");
|
|
int tmp ATTRIBUTE_UNUSED;
|
|
if (!newver) {
|
|
if (LKCD_KERNTYPES())
|
|
ARRAY_LENGTH_INIT_ALT(tmp, "height_to_maxindex",
|
|
"radix_tree_preload.nodes", NULL, 0);
|
|
else
|
|
ARRAY_LENGTH_INIT(tmp, height_to_maxindex,
|
|
"height_to_maxindex", NULL, 0);
|
|
} else {
|
|
if (LKCD_KERNTYPES())
|
|
ARRAY_LENGTH_INIT_ALT(tmp, "height_to_maxnodes",
|
|
"radix_tree_preload.nodes", NULL, 0);
|
|
else
|
|
ARRAY_LENGTH_INIT(tmp, height_to_maxnodes,
|
|
"height_to_maxnodes", NULL, 0);
|
|
}
|
|
STRUCT_SIZE_INIT(radix_tree_root, "radix_tree_root");
|
|
STRUCT_SIZE_INIT(radix_tree_node, "radix_tree_node");
|
|
MEMBER_OFFSET_INIT(radix_tree_root_height,
|
|
"radix_tree_root","height");
|
|
MEMBER_OFFSET_INIT(radix_tree_root_rnode,
|
|
"radix_tree_root","rnode");
|
|
MEMBER_OFFSET_INIT(radix_tree_node_slots,
|
|
"radix_tree_node","slots");
|
|
MEMBER_OFFSET_INIT(radix_tree_node_height,
|
|
"radix_tree_node","height");
|
|
MEMBER_OFFSET_INIT(radix_tree_node_shift,
|
|
"radix_tree_node","shift");
|
|
}
|
|
|
|
STRUCT_SIZE_INIT(xarray, "xarray");
|
|
STRUCT_SIZE_INIT(xa_node, "xa_node");
|
|
MEMBER_OFFSET_INIT(xarray_xa_head, "xarray","xa_head");
|
|
MEMBER_OFFSET_INIT(xa_node_slots, "xa_node","slots");
|
|
MEMBER_OFFSET_INIT(xa_node_shift, "xa_node","shift");
|
|
|
|
if (symbol_exists("pidhash") && symbol_exists("pid_hash") &&
|
|
!symbol_exists("pidhash_shift"))
|
|
error(FATAL,
|
|
"pidhash and pid_hash both exist -- cannot distinquish between them\n");
|
|
|
|
if (VALID_MEMBER(pid_namespace_idr)) {
|
|
STRUCT_SIZE_INIT(pid, "pid");
|
|
if (STREQ(MEMBER_TYPE_NAME("idr", "idr_rt"), "xarray")) {
|
|
tt->refresh_task_table = refresh_xarray_task_table;
|
|
tt->pid_xarray = symbol_value("init_pid_ns") +
|
|
OFFSET(pid_namespace_idr) + OFFSET(idr_idr_rt);
|
|
tt->flags |= PID_XARRAY;
|
|
} else if STREQ(MEMBER_TYPE_NAME("idr", "idr_rt"), "radix_tree_root") {
|
|
if (MEMBER_EXISTS("radix_tree_root", "rnode")) {
|
|
tt->refresh_task_table = refresh_radix_tree_task_table;
|
|
tt->pid_radix_tree = symbol_value("init_pid_ns") +
|
|
OFFSET(pid_namespace_idr) + OFFSET(idr_idr_rt);
|
|
tt->flags |= PID_RADIX_TREE;
|
|
} else if (MEMBER_EXISTS("radix_tree_root", "xa_head")) {
|
|
tt->refresh_task_table = refresh_xarray_task_table;
|
|
tt->pid_xarray = symbol_value("init_pid_ns") +
|
|
OFFSET(pid_namespace_idr) + OFFSET(idr_idr_rt);
|
|
tt->flags |= PID_XARRAY;
|
|
}
|
|
} else
|
|
error(FATAL, "unknown pid_namespace.idr type: %s\n",
|
|
MEMBER_TYPE_NAME("idr", "idr_rt"));
|
|
} else if (symbol_exists("pid_hash") && symbol_exists("pidhash_shift")) {
|
|
int pidhash_shift;
|
|
|
|
if (get_symbol_type("PIDTYPE_PID", NULL, &req) !=
|
|
TYPE_CODE_ENUM)
|
|
error(FATAL,
|
|
"cannot determine PIDTYPE_PID pid_hash dimension\n");
|
|
|
|
get_symbol_data("pidhash_shift", sizeof(int), &pidhash_shift);
|
|
tt->pidhash_len = 1 << pidhash_shift;
|
|
get_symbol_data("pid_hash", sizeof(ulong), &tt->pidhash_addr);
|
|
|
|
if (VALID_MEMBER(pid_link_pid) && VALID_MEMBER(pid_hash_chain)) {
|
|
get_symbol_data("pid_hash", sizeof(ulong), &tt->pidhash_addr);
|
|
tt->refresh_task_table = refresh_pid_hash_task_table;
|
|
} else {
|
|
tt->pidhash_addr = symbol_value("pid_hash");
|
|
if (LKCD_KERNTYPES()) {
|
|
if (VALID_STRUCT(pid_link)) {
|
|
if (VALID_STRUCT(upid) && VALID_MEMBER(pid_numbers))
|
|
tt->refresh_task_table =
|
|
refresh_hlist_task_table_v3;
|
|
else
|
|
tt->refresh_task_table =
|
|
refresh_hlist_task_table_v2;
|
|
} else
|
|
tt->refresh_task_table =
|
|
refresh_hlist_task_table;
|
|
builtin_array_length("pid_hash",
|
|
tt->pidhash_len, NULL);
|
|
} else {
|
|
if (!get_array_length("pid_hash", NULL,
|
|
sizeof(void *)) && VALID_STRUCT(pid_link)) {
|
|
if (VALID_STRUCT(upid) && VALID_MEMBER(pid_numbers))
|
|
tt->refresh_task_table =
|
|
refresh_hlist_task_table_v3;
|
|
else
|
|
tt->refresh_task_table =
|
|
refresh_hlist_task_table_v2;
|
|
}
|
|
else
|
|
tt->refresh_task_table =
|
|
refresh_hlist_task_table;
|
|
}
|
|
}
|
|
|
|
tt->flags |= PID_HASH;
|
|
|
|
} else if (symbol_exists("pid_hash")) {
|
|
if (get_symbol_type("PIDTYPE_PGID", NULL, &req) !=
|
|
TYPE_CODE_ENUM)
|
|
error(FATAL,
|
|
"cannot determine PIDTYPE_PID pid_hash dimension\n");
|
|
if (!(tt->pidhash_len = get_array_length("pid_hash",
|
|
&dim, SIZE(list_head))))
|
|
error(FATAL,
|
|
"cannot determine pid_hash array dimensions\n");
|
|
|
|
tt->pidhash_addr = symbol_value("pid_hash");
|
|
tt->refresh_task_table = refresh_pid_hash_task_table;
|
|
tt->flags |= PID_HASH;
|
|
|
|
} else if (symbol_exists("pidhash")) {
|
|
tt->pidhash_addr = symbol_value("pidhash");
|
|
tt->pidhash_len = get_array_length("pidhash", NULL, 0);
|
|
if (tt->pidhash_len == 0) {
|
|
if (!(nsp = next_symbol("pidhash", NULL)))
|
|
error(FATAL,
|
|
"cannot determine pidhash length\n");
|
|
tt->pidhash_len =
|
|
(nsp->value-tt->pidhash_addr) / sizeof(void *);
|
|
}
|
|
if (ACTIVE())
|
|
tt->refresh_task_table = refresh_pidhash_task_table;
|
|
tt->flags |= PIDHASH;
|
|
}
|
|
|
|
tt->pf_kthread = UNINITIALIZED;
|
|
|
|
get_active_set();
|
|
|
|
if (tt->flags & ACTIVE_ONLY)
|
|
tt->refresh_task_table = refresh_active_task_table;
|
|
|
|
tt->refresh_task_table();
|
|
|
|
if (tt->flags & TASK_REFRESH_OFF)
|
|
tt->flags &= ~(TASK_REFRESH|TASK_REFRESH_OFF);
|
|
|
|
/*
|
|
* Get the IRQ stacks info if it's configured.
|
|
*/
|
|
if (VALID_STRUCT(irq_ctx))
|
|
irqstacks_init();
|
|
|
|
if (ACTIVE()) {
|
|
active_pid = REMOTE() ? pc->server_pid :
|
|
LOCAL_ACTIVE() ? pc->program_pid : 1;
|
|
set_context(NO_TASK, active_pid);
|
|
tt->this_task = pid_to_task(active_pid);
|
|
}
|
|
else {
|
|
if (KDUMP_DUMPFILE())
|
|
map_cpus_to_prstatus();
|
|
else if (ELF_NOTES_VALID() && DISKDUMP_DUMPFILE())
|
|
map_cpus_to_prstatus_kdump_cmprs();
|
|
please_wait("determining panic task");
|
|
set_context(get_panic_context(), NO_PID);
|
|
please_wait_done();
|
|
}
|
|
|
|
sort_context_array();
|
|
sort_tgid_array();
|
|
|
|
if (pc->flags & SILENT)
|
|
initialize_task_state();
|
|
|
|
stack_overflow_check_init();
|
|
|
|
if (machdep->hz) {
|
|
ulonglong uptime_jiffies;
|
|
ulong uptime_sec;
|
|
|
|
get_uptime(NULL, &uptime_jiffies);
|
|
uptime_sec = (uptime_jiffies)/(ulonglong)machdep->hz;
|
|
kt->boot_date.tv_sec = kt->date.tv_sec - uptime_sec;
|
|
kt->boot_date.tv_nsec = 0;
|
|
}
|
|
|
|
tt->flags |= TASK_INIT_DONE;
|
|
}
|
|
|
|
/*
|
|
* Store the pointers to the hard and soft irq_ctx arrays as well as
|
|
* the task pointers contained within each of them.
|
|
*/
|
|
static void
|
|
irqstacks_init(void)
|
|
{
|
|
int i;
|
|
char *thread_info_buf;
|
|
struct syment *hard_sp, *soft_sp;
|
|
ulong ptr, hardirq_next_sp = 0;
|
|
|
|
if (!(tt->hardirq_ctx = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc hardirq_ctx space.");
|
|
if (!(tt->hardirq_tasks = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc hardirq_tasks space.");
|
|
if (!(tt->softirq_ctx = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc softirq_ctx space.");
|
|
if (!(tt->softirq_tasks = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc softirq_tasks space.");
|
|
|
|
thread_info_buf = GETBUF(SIZE(irq_ctx));
|
|
|
|
if ((hard_sp = per_cpu_symbol_search("per_cpu__hardirq_ctx")) ||
|
|
(hard_sp = per_cpu_symbol_search("per_cpu__hardirq_stack"))) {
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF)) {
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!kt->__per_cpu_offset[i])
|
|
continue;
|
|
ptr = hard_sp->value + kt->__per_cpu_offset[i];
|
|
|
|
if (!readmem(ptr, KVADDR, &ptr,
|
|
sizeof(void *), "hardirq ctx",
|
|
RETURN_ON_ERROR)) {
|
|
error(INFO, "cannot read hardirq_ctx[%d] at %lx\n",
|
|
i, ptr);
|
|
continue;
|
|
}
|
|
tt->hardirq_ctx[i] = ptr;
|
|
}
|
|
} else
|
|
tt->hardirq_ctx[0] = hard_sp->value;
|
|
} else if (symbol_exists("hardirq_ctx")) {
|
|
i = get_array_length("hardirq_ctx", NULL, 0);
|
|
get_symbol_data("hardirq_ctx",
|
|
sizeof(long)*(i <= NR_CPUS ? i : NR_CPUS),
|
|
&tt->hardirq_ctx[0]);
|
|
} else
|
|
error(WARNING, "cannot determine hardirq_ctx addresses\n");
|
|
|
|
/* TODO: Use multithreading to parallely update irq_tasks. */
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!(tt->hardirq_ctx[i]))
|
|
continue;
|
|
|
|
if (!readmem(tt->hardirq_ctx[i], KVADDR, thread_info_buf,
|
|
SIZE(irq_ctx), "hardirq thread_union",
|
|
RETURN_ON_ERROR)) {
|
|
error(INFO, "cannot read hardirq_ctx[%d] at %lx\n",
|
|
i, tt->hardirq_ctx[i]);
|
|
continue;
|
|
}
|
|
|
|
if (MEMBER_EXISTS("irq_ctx", "tinfo"))
|
|
tt->hardirq_tasks[i] =
|
|
ULONG(thread_info_buf+OFFSET(thread_info_task));
|
|
else {
|
|
hardirq_next_sp = ULONG(thread_info_buf);
|
|
tt->hardirq_tasks[i] = stkptr_to_task(hardirq_next_sp);
|
|
}
|
|
}
|
|
|
|
if ((soft_sp = per_cpu_symbol_search("per_cpu__softirq_ctx")) ||
|
|
(soft_sp = per_cpu_symbol_search("per_cpu__softirq_stack"))) {
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF)) {
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!kt->__per_cpu_offset[i])
|
|
continue;
|
|
ptr = soft_sp->value + kt->__per_cpu_offset[i];
|
|
|
|
if (!readmem(ptr, KVADDR, &ptr,
|
|
sizeof(void *), "softirq ctx",
|
|
RETURN_ON_ERROR)) {
|
|
error(INFO, "cannot read softirq_ctx[%d] at %lx\n",
|
|
i, ptr);
|
|
continue;
|
|
}
|
|
tt->softirq_ctx[i] = ptr;
|
|
}
|
|
} else
|
|
tt->softirq_ctx[0] = soft_sp->value;
|
|
} else if (symbol_exists("softirq_ctx")) {
|
|
i = get_array_length("softirq_ctx", NULL, 0);
|
|
get_symbol_data("softirq_ctx",
|
|
sizeof(long)*(i <= NR_CPUS ? i : NR_CPUS),
|
|
&tt->softirq_ctx[0]);
|
|
} else
|
|
error(WARNING, "cannot determine softirq_ctx addresses\n");
|
|
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!(tt->softirq_ctx[i]))
|
|
continue;
|
|
|
|
if (!readmem(tt->softirq_ctx[i], KVADDR, thread_info_buf,
|
|
SIZE(irq_ctx), "softirq thread_union",
|
|
RETURN_ON_ERROR)) {
|
|
error(INFO, "cannot read softirq_ctx[%d] at %lx\n",
|
|
i, tt->hardirq_ctx[i]);
|
|
continue;
|
|
}
|
|
|
|
if (MEMBER_EXISTS("irq_ctx", "tinfo"))
|
|
tt->softirq_tasks[i] =
|
|
ULONG(thread_info_buf+OFFSET(thread_info_task));
|
|
else {
|
|
tt->softirq_tasks[i] = stkptr_to_task(ULONG(thread_info_buf));
|
|
/* Checking if softirq => hardirq nested stack */
|
|
if ((tt->softirq_tasks[i] != NO_TASK) && hardirq_next_sp) {
|
|
if ((tt->softirq_ctx[i] <= hardirq_next_sp) &&
|
|
(hardirq_next_sp < tt->softirq_ctx[i] + STACKSIZE()))
|
|
tt->hardirq_tasks[i] = tt->softirq_tasks[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
tt->flags |= IRQSTACKS;
|
|
|
|
FREEBUF(thread_info_buf);
|
|
|
|
}
|
|
|
|
int
|
|
in_irq_ctx(ulonglong type, int cpu, ulong addr)
|
|
{
|
|
if (!(tt->flags & IRQSTACKS))
|
|
return FALSE;
|
|
|
|
switch (type)
|
|
{
|
|
case BT_SOFTIRQ:
|
|
if (tt->softirq_ctx[cpu] &&
|
|
(addr >= tt->softirq_ctx[cpu]) &&
|
|
(addr < (tt->softirq_ctx[cpu] + STACKSIZE())))
|
|
return TRUE;
|
|
break;
|
|
|
|
case BT_HARDIRQ:
|
|
if (tt->hardirq_ctx[cpu] &&
|
|
(addr >= tt->hardirq_ctx[cpu]) &&
|
|
(addr < (tt->hardirq_ctx[cpu] + STACKSIZE())))
|
|
return TRUE;
|
|
break;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Allocate or re-allocated space for the task_context array and task list.
|
|
*/
|
|
static void
|
|
allocate_task_space(int cnt)
|
|
{
|
|
if (tt->context_array == NULL) {
|
|
if (!(tt->task_local = (void *)
|
|
malloc(cnt * sizeof(void *))))
|
|
error(FATAL,
|
|
"cannot malloc kernel task array (%d tasks)", cnt);
|
|
|
|
if (!(tt->context_array = (struct task_context *)
|
|
malloc(cnt * sizeof(struct task_context))))
|
|
error(FATAL, "cannot malloc context array (%d tasks)",
|
|
cnt);
|
|
if (!(tt->context_by_task = (struct task_context **)
|
|
malloc(cnt * sizeof(struct task_context*))))
|
|
error(FATAL, "cannot malloc context_by_task array (%d tasks)",
|
|
cnt);
|
|
if (!(tt->tgid_array = (struct tgid_context *)
|
|
malloc(cnt * sizeof(struct tgid_context))))
|
|
error(FATAL, "cannot malloc tgid array (%d tasks)",
|
|
cnt);
|
|
|
|
} else {
|
|
if (!(tt->task_local = (void *)
|
|
realloc(tt->task_local, cnt * sizeof(void *))))
|
|
error(FATAL,
|
|
"%scannot realloc kernel task array (%d tasks)",
|
|
(pc->flags & RUNTIME) ? "" : "\n", cnt);
|
|
|
|
if (!(tt->context_array = (struct task_context *)
|
|
realloc(tt->context_array,
|
|
cnt * sizeof(struct task_context))))
|
|
error(FATAL,
|
|
"%scannot realloc context array (%d tasks)",
|
|
(pc->flags & RUNTIME) ? "" : "\n", cnt);
|
|
|
|
if (!(tt->context_by_task = (struct task_context **)
|
|
realloc(tt->context_by_task,
|
|
cnt * sizeof(struct task_context*))))
|
|
error(FATAL,
|
|
"%scannot realloc context_by_task array (%d tasks)",
|
|
(pc->flags & RUNTIME) ? "" : "\n", cnt);
|
|
|
|
if (!(tt->tgid_array = (struct tgid_context *)
|
|
realloc(tt->tgid_array,
|
|
cnt * sizeof(struct tgid_context))))
|
|
error(FATAL,
|
|
"%scannot realloc tgid array (%d tasks)",
|
|
(pc->flags & RUNTIME) ? "" : "\n", cnt);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* This routine runs one time on dumpfiles, and constantly on live systems.
|
|
* It walks through the kernel task array looking for active tasks, and
|
|
* populates the local task table with their essential data.
|
|
*/
|
|
static void
|
|
refresh_fixed_task_table(void)
|
|
{
|
|
int i;
|
|
ulong *tlp;
|
|
ulong curtask;
|
|
ulong retries;
|
|
ulong curpid;
|
|
char *tp;
|
|
|
|
#define TASK_FREE(x) ((x == 0) || (((ulong)(x) >= tt->task_start) && \
|
|
((ulong)(x) < tt->task_end)))
|
|
#define TASK_IN_USE(x) (!TASK_FREE(x))
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE))
|
|
return;
|
|
|
|
if (DUMPFILE()) {
|
|
fprintf(fp, (pc->flags & SILENT) || !(pc->flags & TTY) ?
|
|
"" : "%splease wait... (gathering task table data)",
|
|
GDB_PATCHED() ? "" : "\n");
|
|
fflush(fp);
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
retries = 0;
|
|
retry:
|
|
if (!readmem(tt->task_start, KVADDR, tt->task_local,
|
|
tt->max_tasks * sizeof(void *), "kernel task array",
|
|
RETURN_ON_ERROR))
|
|
error(FATAL, "cannot read kernel task array");
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (TASK_IN_USE(*tlp)) {
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
}
|
|
|
|
if (DUMPFILE()) {
|
|
fprintf(fp, (pc->flags & SILENT) || !(pc->flags & TTY) ? "" :
|
|
"\r \r");
|
|
fflush(fp);
|
|
}
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
/*
|
|
* Verify that a task_context's data makes sense enough to include
|
|
* in the task_context array.
|
|
*/
|
|
static int
|
|
verify_task(struct task_context *tc, int level)
|
|
{
|
|
int i;
|
|
ulong next_task;
|
|
ulong readflag;
|
|
|
|
readflag = ACTIVE() ? (RETURN_ON_ERROR|QUIET) : (RETURN_ON_ERROR);
|
|
|
|
switch (level)
|
|
{
|
|
case 1:
|
|
if (!readmem(tc->task + OFFSET(task_struct_next_task),
|
|
KVADDR, &next_task, sizeof(void *), "next_task", readflag)) {
|
|
return FALSE;
|
|
}
|
|
if (!IS_TASK_ADDR(next_task))
|
|
return FALSE;
|
|
|
|
if (tc->processor & ~NO_PROC_ID)
|
|
return FALSE;
|
|
|
|
/* fall through */
|
|
case 2:
|
|
if (!IS_TASK_ADDR(tc->ptask))
|
|
return FALSE;
|
|
|
|
if ((tc->processor < 0) || (tc->processor >= NR_CPUS)) {
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (tc->task == tt->active_set[i]) {
|
|
error(WARNING,
|
|
"active task %lx on cpu %d: corrupt cpu value: %u\n\n",
|
|
tc->task, i, tc->processor);
|
|
tc->processor = i;
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO,
|
|
"verify_task: task: %lx invalid processor: %u",
|
|
tc->task, tc->processor);
|
|
return FALSE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* This routine runs one time on dumpfiles, and constantly on live systems.
|
|
* It walks through the kernel task array looking for active tasks, and
|
|
* populates the local task table with their essential data.
|
|
*/
|
|
|
|
#define MAX_UNLIMITED_TASK_RETRIES (500)
|
|
|
|
void
|
|
refresh_unlimited_task_table(void)
|
|
{
|
|
int i;
|
|
ulong *tlp;
|
|
ulong curtask;
|
|
ulong curpid;
|
|
struct list_data list_data, *ld;
|
|
ulong init_tasks[NR_CPUS];
|
|
ulong retries;
|
|
char *tp;
|
|
int cnt;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE))
|
|
return;
|
|
|
|
if (DUMPFILE()) {
|
|
fprintf(fp, (pc->flags & SILENT) || !(pc->flags & TTY) ?
|
|
"" : "%splease wait... (gathering task table data)",
|
|
GDB_PATCHED() ? "" : "\n");
|
|
fflush(fp);
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
tp = NULL;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
retries = 0;
|
|
retry:
|
|
if (retries && DUMPFILE()) {
|
|
if (tt->flags & PIDHASH) {
|
|
error(WARNING,
|
|
"\ncannot gather a stable task list -- trying pidhash\n");
|
|
refresh_pidhash_task_table();
|
|
return;
|
|
}
|
|
error(FATAL, "\ncannot gather a stable task list\n");
|
|
}
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL, "cannot gather a stable task list\n");
|
|
|
|
/*
|
|
* Populate the task_local array with a quick walk-through.
|
|
* If there's not enough room in the local array, realloc() it.
|
|
*/
|
|
ld = &list_data;
|
|
BZERO(ld, sizeof(struct list_data));
|
|
ld->flags |= RETURN_ON_LIST_ERROR;
|
|
ld->start = symbol_value("init_task_union");
|
|
ld->member_offset = OFFSET(task_struct_next_task);
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
FREEBUF(tp);
|
|
return;
|
|
}
|
|
|
|
if ((cnt = do_list(ld)) < 0) {
|
|
retries++;
|
|
goto retry;
|
|
}
|
|
|
|
if ((cnt+NR_CPUS+1) > tt->max_tasks) {
|
|
tt->max_tasks = cnt + NR_CPUS + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
hq_close();
|
|
if (!DUMPFILE())
|
|
retries++;
|
|
goto retry;
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
hq_close();
|
|
|
|
/*
|
|
* If SMP, add in the other idle tasks.
|
|
*/
|
|
if (kt->flags & SMP) {
|
|
/*
|
|
* Now get the rest of the init_task[] entries, starting
|
|
* at offset 1 since we've got the init_task already.
|
|
*/
|
|
BZERO(&init_tasks[0], sizeof(ulong) * NR_CPUS);
|
|
get_idle_threads(&init_tasks[0], kt->cpus);
|
|
|
|
tlp = (ulong *)tt->task_local;
|
|
tlp += cnt;
|
|
|
|
for (i = 1; i < kt->cpus; i++) {
|
|
if (init_tasks[i]) {
|
|
*tlp = init_tasks[i];
|
|
tlp++;
|
|
}
|
|
}
|
|
}
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(INFO,
|
|
"\ninvalid task address in task list: %lx\n", *tlp);
|
|
retries++;
|
|
goto retry;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
if (DUMPFILE()) {
|
|
fprintf(fp, (pc->flags & SILENT) || !(pc->flags & TTY) ? "" :
|
|
"\r \r");
|
|
fflush(fp);
|
|
}
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
|
|
}
|
|
|
|
/*
|
|
* This routine runs one time on dumpfiles, and constantly on live systems.
|
|
* It walks through the kernel pidhash array looking for active tasks, and
|
|
* populates the local task table with their essential data.
|
|
*
|
|
* The following manner of refreshing the task table can be used for all
|
|
* kernels that have a pidhash[] array, whether or not they still
|
|
* have a fixed task[] array or an unlimited list.
|
|
*/
|
|
static void
|
|
refresh_pidhash_task_table(void)
|
|
{
|
|
int i;
|
|
char *pidhash, *tp;
|
|
ulong *pp, next, pnext;
|
|
int len, cnt;
|
|
ulong curtask;
|
|
ulong curpid;
|
|
ulong retries;
|
|
ulong *tlp;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) { /* impossible */
|
|
fprintf(fp, (pc->flags & SILENT) || !(pc->flags & TTY) ?
|
|
"" : "\rplease wait... (gathering task table data)");
|
|
fflush(fp);
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
len = tt->pidhash_len;
|
|
pidhash = GETBUF(len * sizeof(ulong));
|
|
retries = 0;
|
|
|
|
retry_pidhash:
|
|
if (retries && DUMPFILE())
|
|
error(FATAL,"\ncannot gather a stable task list via pidhash\n");
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pidhash (%d retries)\n",
|
|
retries);
|
|
|
|
if (!readmem(tt->pidhash_addr, KVADDR, pidhash,
|
|
len * sizeof(ulong), "pidhash contents", RETURN_ON_ERROR))
|
|
error(FATAL, "\ncannot read pidhash array\n");
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
FREEBUF(pidhash);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the idle threads first.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (hq_enter(tt->idle_threads[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate idle tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
/*
|
|
* Then dump the pidhash contents.
|
|
*/
|
|
for (i = 0, pp = (ulong *)pidhash; i < len; i++, pp++) {
|
|
if (!(*pp) || !IS_KVADDR(*pp))
|
|
continue;
|
|
/*
|
|
* Mininum verification here -- make sure that a task address
|
|
* and its pidhash_next entry (if any) both appear to be
|
|
* properly aligned before accepting the task.
|
|
*/
|
|
next = *pp;
|
|
while (next) {
|
|
if (!IS_TASK_ADDR(next)) {
|
|
error(INFO,
|
|
"%sinvalid task address in pidhash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pidhash;
|
|
|
|
}
|
|
|
|
if (!readmem(next + OFFSET(task_struct_pidhash_next),
|
|
KVADDR, &pnext, sizeof(void *),
|
|
"pidhash_next entry", QUIET|RETURN_ON_ERROR)) {
|
|
error(INFO, "%scannot read from task: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pidhash;
|
|
}
|
|
|
|
if (!hq_enter(next)) {
|
|
error(INFO,
|
|
"%sduplicate task in pidhash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pidhash;
|
|
}
|
|
|
|
next = pnext;
|
|
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
if ((cnt+1) > tt->max_tasks) {
|
|
tt->max_tasks = cnt + NR_CPUS + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
hq_close();
|
|
if (!DUMPFILE())
|
|
retries++;
|
|
goto retry_pidhash;
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pidhash;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pidhash;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
FREEBUF(pidhash);
|
|
|
|
if (DUMPFILE()) {
|
|
fprintf(fp, (pc->flags & SILENT) || !(pc->flags & TTY) ? "" :
|
|
"\r \r");
|
|
fflush(fp);
|
|
}
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
|
|
/*
|
|
* The following manner of refreshing the task table is used for all
|
|
* kernels that have a pid_hash[][] array.
|
|
*
|
|
* This routine runs one time on dumpfiles, and constantly on live systems.
|
|
* It walks through the kernel pid_hash[PIDTYPE_PID] array looking for active
|
|
* tasks, and populates the local task table with their essential data.
|
|
*/
|
|
|
|
#define HASH_TO_TASK(X) ((ulong)(X) - (OFFSET(task_struct_pids) + \
|
|
OFFSET(pid_link_pid) + OFFSET(pid_hash_chain)))
|
|
|
|
#define TASK_TO_HASH(X) ((ulong)(X) + (OFFSET(task_struct_pids) + \
|
|
OFFSET(pid_link_pid) + OFFSET(pid_hash_chain)))
|
|
|
|
static void
|
|
refresh_pid_hash_task_table(void)
|
|
{
|
|
int i;
|
|
struct kernel_list_head *pid_hash, *pp, *kpp;
|
|
char *tp;
|
|
ulong next, pnext;
|
|
int len, cnt;
|
|
ulong curtask;
|
|
ulong curpid;
|
|
ulong retries;
|
|
ulong *tlp;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) { /* impossible */
|
|
please_wait("gathering task table data");
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
len = tt->pidhash_len;
|
|
pid_hash = (struct kernel_list_head *)GETBUF(len * SIZE(list_head));
|
|
retries = 0;
|
|
|
|
retry_pid_hash:
|
|
if (retries && DUMPFILE())
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash\n");
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash (%d retries)\n",
|
|
retries);
|
|
|
|
if (!readmem(tt->pidhash_addr, KVADDR, pid_hash,
|
|
len * SIZE(list_head), "pid_hash contents", RETURN_ON_ERROR))
|
|
error(FATAL, "\ncannot read pid_hash array\n");
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
FREEBUF(pid_hash);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the idle threads first.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (hq_enter(tt->idle_threads[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate idle tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
pp = &pid_hash[i];
|
|
kpp = (struct kernel_list_head *)(tt->pidhash_addr +
|
|
i * SIZE(list_head));
|
|
if (pp->next == kpp)
|
|
continue;
|
|
|
|
if (CRASHDEBUG(7))
|
|
console("%lx: pid_hash[%d]: %lx (%lx) %lx (%lx)\n", kpp, i,
|
|
pp->next, HASH_TO_TASK(pp->next),
|
|
pp->prev, HASH_TO_TASK(pp->prev));
|
|
|
|
next = (ulong)HASH_TO_TASK(pp->next);
|
|
while (next) {
|
|
if (!IS_TASK_ADDR(next)) {
|
|
error(INFO,
|
|
"%sinvalid task address in pid_hash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
|
|
}
|
|
|
|
if (!readmem(TASK_TO_HASH(next),
|
|
KVADDR, &pnext, sizeof(void *),
|
|
"pid_hash entry", QUIET|RETURN_ON_ERROR)) {
|
|
error(INFO, "%scannot read from task: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
if (!is_idle_thread(next) && !hq_enter(next)) {
|
|
error(INFO,
|
|
"%sduplicate task in pid_hash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
cnt++;
|
|
|
|
if (pnext == (ulong)kpp)
|
|
break;
|
|
|
|
next = HASH_TO_TASK(pnext);
|
|
}
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
FREEBUF(pid_hash);
|
|
|
|
please_wait_done();
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
/*
|
|
* Adapt to yet another scheme, using later 2.6 hlist_head and hlist_nodes.
|
|
*/
|
|
|
|
#define HLIST_TO_TASK(X) ((ulong)(X) - (OFFSET(task_struct_pids) + \
|
|
OFFSET(pid_pid_chain)))
|
|
|
|
static void
|
|
refresh_hlist_task_table(void)
|
|
{
|
|
int i;
|
|
ulong *pid_hash;
|
|
struct syment *sp;
|
|
ulong pidhash_array;
|
|
ulong kpp;
|
|
char *tp;
|
|
ulong next, pnext, pprev;
|
|
char *nodebuf;
|
|
int plen, len, cnt;
|
|
long value;
|
|
ulong curtask;
|
|
ulong curpid;
|
|
ulong retries;
|
|
ulong *tlp;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) { /* impossible */
|
|
please_wait("gathering task table data");
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
if (!(plen = get_array_length("pid_hash", NULL, sizeof(void *)))) {
|
|
/*
|
|
* Workaround for gcc omitting debuginfo data for pid_hash.
|
|
*/
|
|
if (enumerator_value("PIDTYPE_MAX", &value)) {
|
|
if ((sp = next_symbol("pid_hash", NULL)) &&
|
|
(((sp->value - tt->pidhash_addr) / sizeof(void *)) < value))
|
|
error(WARNING, "possible pid_hash array mis-handling\n");
|
|
plen = (int)value;
|
|
} else {
|
|
error(WARNING,
|
|
"cannot determine pid_hash array dimensions\n");
|
|
plen = 1;
|
|
}
|
|
}
|
|
|
|
pid_hash = (ulong *)GETBUF(plen * sizeof(void *));
|
|
|
|
if (!readmem(tt->pidhash_addr, KVADDR, pid_hash,
|
|
plen * SIZE(hlist_head), "pid_hash[] contents", RETURN_ON_ERROR))
|
|
error(FATAL, "\ncannot read pid_hash array\n");
|
|
|
|
if (CRASHDEBUG(7))
|
|
for (i = 0; i < plen; i++)
|
|
console("pid_hash[%d]: %lx\n", i, pid_hash[i]);
|
|
|
|
/*
|
|
* The zero'th (PIDTYPE_PID) entry is the hlist_head array
|
|
* that we want.
|
|
*/
|
|
if (CRASHDEBUG(1)) {
|
|
if (!enumerator_value("PIDTYPE_PID", &value))
|
|
error(WARNING,
|
|
"possible pid_hash array mis-handling: PIDTYPE_PID: (unknown)\n");
|
|
else if (value != 0)
|
|
error(WARNING,
|
|
"possible pid_hash array mis-handling: PIDTYPE_PID: %d \n",
|
|
value);
|
|
}
|
|
|
|
pidhash_array = pid_hash[0];
|
|
FREEBUF(pid_hash);
|
|
|
|
len = tt->pidhash_len;
|
|
pid_hash = (ulong *)GETBUF(len * SIZE(hlist_head));
|
|
nodebuf = GETBUF(SIZE(hlist_node));
|
|
retries = 0;
|
|
|
|
retry_pid_hash:
|
|
if (retries && DUMPFILE())
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash\n");
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash (%d retries)\n",
|
|
retries);
|
|
|
|
if (!readmem(pidhash_array, KVADDR, pid_hash,
|
|
len * SIZE(hlist_head), "pid_hash[0] contents", RETURN_ON_ERROR))
|
|
error(FATAL, "\ncannot read pid_hash[0] array\n");
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
FREEBUF(pid_hash);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the idle threads first.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (hq_enter(tt->idle_threads[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate idle tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (!pid_hash[i])
|
|
continue;
|
|
|
|
if (!readmem(pid_hash[i], KVADDR, nodebuf,
|
|
SIZE(hlist_node), "pid_hash node", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\ncannot read pid_hash node\n");
|
|
if (DUMPFILE())
|
|
continue;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
kpp = pid_hash[i];
|
|
next = (ulong)HLIST_TO_TASK(kpp);
|
|
pnext = ULONG(nodebuf + OFFSET(hlist_node_next));
|
|
pprev = ULONG(nodebuf + OFFSET(hlist_node_pprev));
|
|
|
|
if (CRASHDEBUG(1))
|
|
console("pid_hash[%d]: %lx task: %lx (node: %lx) next: %lx pprev: %lx\n",
|
|
i, pid_hash[i], next, kpp, pnext, pprev);
|
|
|
|
while (next) {
|
|
if (!IS_TASK_ADDR(next)) {
|
|
error(INFO,
|
|
"%sinvalid task address in pid_hash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
|
|
}
|
|
|
|
if (!is_idle_thread(next) && !hq_enter(next)) {
|
|
error(INFO,
|
|
"%sduplicate task in pid_hash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
cnt++;
|
|
|
|
if (!pnext)
|
|
break;
|
|
|
|
if (!readmem((ulonglong)pnext, KVADDR, nodebuf,
|
|
SIZE(hlist_node), "task hlist_node", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\ncannot read hlist_node from task\n");
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
kpp = (ulong)pnext;
|
|
next = (ulong)HLIST_TO_TASK(kpp);
|
|
pnext = ULONG(nodebuf + OFFSET(hlist_node_next));
|
|
pprev = ULONG(nodebuf + OFFSET(hlist_node_pprev));
|
|
|
|
if (CRASHDEBUG(1))
|
|
console(" chained task: %lx (node: %lx) next: %lx pprev: %lx\n",
|
|
(ulong)HLIST_TO_TASK(kpp), kpp, pnext, pprev);
|
|
}
|
|
}
|
|
|
|
if (cnt > tt->max_tasks) {
|
|
tt->max_tasks = cnt + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
hq_close();
|
|
if (!DUMPFILE())
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
FREEBUF(pid_hash);
|
|
FREEBUF(nodebuf);
|
|
|
|
please_wait_done();
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
/*
|
|
* 2.6.17 replaced:
|
|
* static struct hlist_head *pid_hash[PIDTYPE_MAX];
|
|
* with
|
|
* static struct hlist_head *pid_hash;
|
|
*/
|
|
static void
|
|
refresh_hlist_task_table_v2(void)
|
|
{
|
|
int i;
|
|
ulong *pid_hash;
|
|
ulong pidhash_array;
|
|
ulong kpp;
|
|
char *tp;
|
|
ulong next, pnext, pprev;
|
|
char *nodebuf;
|
|
int len, cnt;
|
|
ulong curtask;
|
|
ulong curpid;
|
|
ulong retries;
|
|
ulong *tlp;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) { /* impossible */
|
|
please_wait("gathering task table data");
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
get_symbol_data("pid_hash", sizeof(void *), &pidhash_array);
|
|
|
|
len = tt->pidhash_len;
|
|
pid_hash = (ulong *)GETBUF(len * SIZE(hlist_head));
|
|
nodebuf = GETBUF(SIZE(pid_link));
|
|
retries = 0;
|
|
|
|
retry_pid_hash:
|
|
if (retries && DUMPFILE())
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash\n");
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash (%d retries)\n",
|
|
retries);
|
|
|
|
if (!readmem(pidhash_array, KVADDR, pid_hash,
|
|
len * SIZE(hlist_head), "pid_hash contents", RETURN_ON_ERROR))
|
|
error(FATAL, "\ncannot read pid_hash array\n");
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
FREEBUF(pid_hash);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the idle threads first.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (hq_enter(tt->idle_threads[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate idle tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (!pid_hash[i])
|
|
continue;
|
|
|
|
if (!readmem(pid_hash[i], KVADDR, nodebuf,
|
|
SIZE(pid_link), "pid_hash node pid_link", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\ncannot read pid_hash node pid_link\n");
|
|
if (DUMPFILE())
|
|
continue;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
kpp = pid_hash[i];
|
|
next = ULONG(nodebuf + OFFSET(pid_link_pid));
|
|
if (next)
|
|
next -= OFFSET(task_struct_pids);
|
|
pnext = ULONG(nodebuf + OFFSET(hlist_node_next));
|
|
pprev = ULONG(nodebuf + OFFSET(hlist_node_pprev));
|
|
|
|
if (CRASHDEBUG(1))
|
|
console("pid_hash[%d]: %lx task: %lx (node: %lx) next: %lx pprev: %lx\n",
|
|
i, pid_hash[i], next, kpp, pnext, pprev);
|
|
|
|
while (1) {
|
|
if (next) {
|
|
if (!IS_TASK_ADDR(next)) {
|
|
error(INFO,
|
|
"%sinvalid task address in pid_hash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
|
|
}
|
|
|
|
if (!is_idle_thread(next) && !hq_enter(next)) {
|
|
error(INFO,
|
|
"%sduplicate task in pid_hash: %lx\n",
|
|
DUMPFILE() ? "\n" : "", next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
}
|
|
cnt++;
|
|
|
|
if (!pnext)
|
|
break;
|
|
|
|
if (!readmem((ulonglong)pnext, KVADDR, nodebuf,
|
|
SIZE(pid_link), "task hlist_node pid_link", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\ncannot read hlist_node pid_link from node next\n");
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
kpp = (ulong)pnext;
|
|
next = ULONG(nodebuf + OFFSET(pid_link_pid));
|
|
if (next)
|
|
next -= OFFSET(task_struct_pids);
|
|
pnext = ULONG(nodebuf + OFFSET(hlist_node_next));
|
|
pprev = ULONG(nodebuf + OFFSET(hlist_node_pprev));
|
|
|
|
if (CRASHDEBUG(1))
|
|
console(" chained task: %lx (node: %lx) next: %lx pprev: %lx\n",
|
|
next, kpp, pnext, pprev);
|
|
}
|
|
}
|
|
|
|
if (cnt > tt->max_tasks) {
|
|
tt->max_tasks = cnt + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
hq_close();
|
|
if (!DUMPFILE())
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
FREEBUF(pid_hash);
|
|
FREEBUF(nodebuf);
|
|
|
|
please_wait_done();
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
|
|
/*
|
|
* 2.6.24: The pid_hash[] hlist_head entries were changed to point
|
|
* to the hlist_node structure embedded in a upid structure.
|
|
*/
|
|
static void
|
|
refresh_hlist_task_table_v3(void)
|
|
{
|
|
int i;
|
|
ulong *pid_hash;
|
|
ulong pidhash_array;
|
|
ulong kpp;
|
|
char *tp;
|
|
ulong next, pnext, pprev;
|
|
ulong upid;
|
|
char *nodebuf;
|
|
int len, cnt;
|
|
ulong curtask;
|
|
ulong curpid;
|
|
ulong retries;
|
|
ulong *tlp;
|
|
uint upid_nr;
|
|
ulong upid_ns;
|
|
int chained;
|
|
ulong pid;
|
|
ulong pid_tasks_0;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) { /* impossible */
|
|
please_wait("gathering task table data");
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
get_symbol_data("pid_hash", sizeof(void *), &pidhash_array);
|
|
|
|
len = tt->pidhash_len;
|
|
pid_hash = (ulong *)GETBUF(len * SIZE(hlist_head));
|
|
nodebuf = GETBUF(SIZE(upid));
|
|
retries = 0;
|
|
|
|
retry_pid_hash:
|
|
if (retries && DUMPFILE())
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash\n");
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via pid_hash (%d retries)\n",
|
|
retries);
|
|
|
|
if (!readmem(pidhash_array, KVADDR, pid_hash,
|
|
len * SIZE(hlist_head), "pid_hash contents", RETURN_ON_ERROR))
|
|
error(FATAL, "\ncannot read pid_hash array\n");
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
FREEBUF(pid_hash);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the idle threads first.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (!tt->idle_threads[i])
|
|
continue;
|
|
if (hq_enter(tt->idle_threads[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate idle tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
for (i = 0; i < len; i++) {
|
|
if (!pid_hash[i])
|
|
continue;
|
|
|
|
kpp = pid_hash[i];
|
|
upid = pid_hash[i] - OFFSET(upid_pid_chain);
|
|
chained = 0;
|
|
do_chained:
|
|
if (!readmem(upid, KVADDR, nodebuf, SIZE(upid),
|
|
"pid_hash upid", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\npid_hash[%d]: cannot read pid_hash upid\n", i);
|
|
if (DUMPFILE())
|
|
continue;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
pnext = ULONG(nodebuf + OFFSET(upid_pid_chain) + OFFSET(hlist_node_next));
|
|
pprev = ULONG(nodebuf + OFFSET(upid_pid_chain) + OFFSET(hlist_node_pprev));
|
|
upid_nr = UINT(nodebuf + OFFSET(upid_nr));
|
|
upid_ns = ULONG(nodebuf + OFFSET(upid_ns));
|
|
/*
|
|
* Use init_pid_ns level 0 (PIDTYPE_PID).
|
|
*/
|
|
if (upid_ns != tt->init_pid_ns) {
|
|
if (!accessible(upid_ns)) {
|
|
error(INFO,
|
|
"%spid_hash[%d]: invalid upid.ns: %lx\n",
|
|
DUMPFILE() ? "\n" : "",
|
|
i, upid_ns);
|
|
continue;
|
|
}
|
|
goto chain_next;
|
|
}
|
|
|
|
pid = upid - OFFSET(pid_numbers);
|
|
|
|
if (!readmem(pid + OFFSET(pid_tasks), KVADDR, &pid_tasks_0,
|
|
sizeof(void *), "pid tasks", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\npid_hash[%d]: cannot read pid.tasks[0]\n", i);
|
|
if (DUMPFILE())
|
|
continue;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
if (pid_tasks_0 == 0)
|
|
goto chain_next;
|
|
|
|
next = pid_tasks_0 - OFFSET(task_struct_pids);
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
if (chained)
|
|
console(" %lx upid: %lx nr: %d pid: %lx\n"
|
|
" pnext/pprev: %.*lx/%lx task: %lx\n",
|
|
kpp, upid, upid_nr, pid, VADDR_PRLEN, pnext, pprev, next);
|
|
else
|
|
console("pid_hash[%4d]: %lx upid: %lx nr: %d pid: %lx\n"
|
|
" pnext/pprev: %.*lx/%lx task: %lx\n",
|
|
i, kpp, upid, upid_nr, pid, VADDR_PRLEN, pnext, pprev, next);
|
|
}
|
|
|
|
if (!IS_TASK_ADDR(next)) {
|
|
error(INFO, "%spid_hash[%d]: invalid task address: %lx\n",
|
|
DUMPFILE() ? "\n" : "", i, next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
if (!is_idle_thread(next) && !hq_enter(next)) {
|
|
error(INFO, "%spid_hash[%d]: duplicate task: %lx\n",
|
|
DUMPFILE() ? "\n" : "", i, next);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
cnt++;
|
|
chain_next:
|
|
if (pnext) {
|
|
if (chained >= tt->max_tasks) {
|
|
error(INFO,
|
|
"%spid_hash[%d]: corrupt/invalid upid chain\n",
|
|
DUMPFILE() ? "\n" : "", i);
|
|
continue;
|
|
}
|
|
kpp = pnext;
|
|
upid = pnext - OFFSET(upid_pid_chain);
|
|
chained++;
|
|
goto do_chained;
|
|
}
|
|
}
|
|
|
|
if (cnt > tt->max_tasks) {
|
|
tt->max_tasks = cnt + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
hq_close();
|
|
if (!DUMPFILE())
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_pid_hash;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
FREEBUF(pid_hash);
|
|
FREEBUF(nodebuf);
|
|
|
|
please_wait_done();
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
/*
|
|
* Linux 4.15: pid_hash[] replaced by IDR/radix_tree
|
|
*/
|
|
static int
|
|
radix_tree_task_callback(ulong task)
|
|
{
|
|
ulong *tlp;
|
|
|
|
if (tt->callbacks < tt->max_tasks) {
|
|
tlp = (ulong *)tt->task_local;
|
|
tlp += tt->callbacks++;
|
|
*tlp = task;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
refresh_radix_tree_task_table(void)
|
|
{
|
|
int i, cnt;
|
|
ulong count, retries, next, curtask, curpid, upid_ns, pid_tasks_0, task;
|
|
ulong *tlp;
|
|
char *tp;
|
|
struct list_pair rtp;
|
|
char *pidbuf;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) { /* impossible */
|
|
please_wait("gathering task table data");
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
count = do_radix_tree(tt->pid_radix_tree, RADIX_TREE_COUNT, NULL);
|
|
if (CRASHDEBUG(1))
|
|
console("do_radix_tree: count: %ld\n", count);
|
|
|
|
retries = 0;
|
|
pidbuf = GETBUF(SIZE(pid));
|
|
|
|
retry_radix_tree:
|
|
if (retries && DUMPFILE())
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via radix tree\n");
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via radix tree (%d retries)\n",
|
|
retries);
|
|
|
|
if (count > tt->max_tasks) {
|
|
tt->max_tasks = count + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
tt->callbacks = 0;
|
|
rtp.index = 0;
|
|
rtp.value = (void *)&radix_tree_task_callback;
|
|
count = do_radix_tree(tt->pid_radix_tree, RADIX_TREE_DUMP_CB, &rtp);
|
|
if (CRASHDEBUG(1))
|
|
console("do_radix_tree: count: %ld tt->callbacks: %d\n", count, tt->callbacks);
|
|
|
|
if (count > tt->max_tasks) {
|
|
retries++;
|
|
goto retry_radix_tree;
|
|
}
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the idle threads first.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (!tt->idle_threads[i])
|
|
continue;
|
|
if (hq_enter(tt->idle_threads[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate idle tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
for (i = 0; i < tt->max_tasks; i++) {
|
|
tlp = (ulong *)tt->task_local;
|
|
tlp += i;
|
|
if ((next = *tlp) == 0)
|
|
break;
|
|
|
|
/*
|
|
* Translate radix tree contents to PIDTYPE_PID task.
|
|
* - the radix tree contents are struct pid pointers
|
|
* - upid is contained in pid.numbers[0]
|
|
* - upid.ns should point to init->init_pid_ns
|
|
* - pid->tasks[0] is first hlist_node in task->pids[3]
|
|
* - get task from address of task->pids[0]
|
|
*/
|
|
if (!readmem(next, KVADDR, pidbuf,
|
|
SIZE(pid), "pid", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\ncannot read pid struct from radix tree\n");
|
|
if (DUMPFILE())
|
|
continue;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_radix_tree;
|
|
}
|
|
|
|
upid_ns = ULONG(pidbuf + OFFSET(pid_numbers) + OFFSET(upid_ns));
|
|
if (upid_ns != tt->init_pid_ns)
|
|
continue;
|
|
pid_tasks_0 = ULONG(pidbuf + OFFSET(pid_tasks));
|
|
if (!pid_tasks_0)
|
|
continue;
|
|
if (VALID_MEMBER(task_struct_pids))
|
|
task = pid_tasks_0 - OFFSET(task_struct_pids);
|
|
else
|
|
task = pid_tasks_0 - OFFSET(task_struct_pid_links);
|
|
|
|
if (CRASHDEBUG(1))
|
|
console("pid: %lx ns: %lx tasks[0]: %lx task: %lx\n",
|
|
next, upid_ns, pid_tasks_0, task);
|
|
|
|
if (is_idle_thread(task))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(task)) {
|
|
error(INFO, "%s: IDR radix tree: invalid task address: %lx\n",
|
|
DUMPFILE() ? "\n" : "", task);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_radix_tree;
|
|
}
|
|
|
|
if (!hq_enter(task)) {
|
|
error(INFO, "%s: IDR radix tree: duplicate task: %lx\n",
|
|
DUMPFILE() ? "\n" : "", task);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_radix_tree;
|
|
}
|
|
|
|
cnt++;
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_radix_tree;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_radix_tree;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
FREEBUF(pidbuf);
|
|
|
|
please_wait_done();
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
|
|
/*
|
|
* Linux 4.20: pid_hash[] IDR changed from radix tree to xarray
|
|
*/
|
|
static int
|
|
xarray_task_callback(ulong task)
|
|
{
|
|
ulong *tlp;
|
|
|
|
if (tt->callbacks < tt->max_tasks) {
|
|
tlp = (ulong *)tt->task_local;
|
|
tlp += tt->callbacks++;
|
|
*tlp = task;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
refresh_xarray_task_table(void)
|
|
{
|
|
int i, cnt;
|
|
ulong count, retries, next, curtask, curpid, upid_ns, pid_tasks_0, task;
|
|
ulong *tlp;
|
|
char *tp;
|
|
struct list_pair xp;
|
|
char *pidbuf;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) { /* impossible */
|
|
please_wait("gathering task table data");
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curpid = NO_PID;
|
|
curtask = NO_TASK;
|
|
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
count = do_xarray(tt->pid_xarray, XARRAY_COUNT, NULL);
|
|
if (CRASHDEBUG(1))
|
|
console("xarray: count: %ld\n", count);
|
|
|
|
retries = 0;
|
|
pidbuf = GETBUF(SIZE(pid));
|
|
|
|
retry_xarray:
|
|
if (retries && DUMPFILE())
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via xarray\n");
|
|
|
|
if ((retries == MAX_UNLIMITED_TASK_RETRIES) &&
|
|
!(tt->flags & TASK_INIT_DONE))
|
|
error(FATAL,
|
|
"\ncannot gather a stable task list via xarray (%d retries)\n",
|
|
retries);
|
|
|
|
if (count > tt->max_tasks) {
|
|
tt->max_tasks = count + TASK_SLUSH;
|
|
allocate_task_space(tt->max_tasks);
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
tt->callbacks = 0;
|
|
xp.index = 0;
|
|
xp.value = (void *)&xarray_task_callback;
|
|
count = do_xarray(tt->pid_xarray, XARRAY_DUMP_CB, &xp);
|
|
if (CRASHDEBUG(1))
|
|
console("do_xarray: count: %ld tt->callbacks: %d\n", count, tt->callbacks);
|
|
|
|
if (count > tt->max_tasks) {
|
|
retries++;
|
|
goto retry_xarray;
|
|
}
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the idle threads first.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (!tt->idle_threads[i])
|
|
continue;
|
|
if (hq_enter(tt->idle_threads[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate idle tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
for (i = 0; i < tt->max_tasks; i++) {
|
|
tlp = (ulong *)tt->task_local;
|
|
tlp += i;
|
|
if ((next = *tlp) == 0)
|
|
break;
|
|
|
|
/*
|
|
* Translate xarray contents to PIDTYPE_PID task.
|
|
* - the xarray contents are struct pid pointers
|
|
* - upid is contained in pid.numbers[0]
|
|
* - upid.ns should point to init->init_pid_ns
|
|
* - pid->tasks[0] is first hlist_node in task->pids[3]
|
|
* - get task from address of task->pids[0]
|
|
*/
|
|
if (!readmem(next, KVADDR, pidbuf,
|
|
SIZE(pid), "pid", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "\ncannot read pid struct from xarray\n");
|
|
if (DUMPFILE())
|
|
continue;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_xarray;
|
|
}
|
|
|
|
upid_ns = ULONG(pidbuf + OFFSET(pid_numbers) + OFFSET(upid_ns));
|
|
if (upid_ns != tt->init_pid_ns)
|
|
continue;
|
|
pid_tasks_0 = ULONG(pidbuf + OFFSET(pid_tasks));
|
|
if (!pid_tasks_0)
|
|
continue;
|
|
if (VALID_MEMBER(task_struct_pids))
|
|
task = pid_tasks_0 - OFFSET(task_struct_pids);
|
|
else
|
|
task = pid_tasks_0 - OFFSET(task_struct_pid_links);
|
|
|
|
if (CRASHDEBUG(1))
|
|
console("pid: %lx ns: %lx tasks[0]: %lx task: %lx\n",
|
|
next, upid_ns, pid_tasks_0, task);
|
|
|
|
if (is_idle_thread(task))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(task)) {
|
|
error(INFO, "%s: IDR xarray: invalid task address: %lx\n",
|
|
DUMPFILE() ? "\n" : "", task);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_xarray;
|
|
}
|
|
|
|
if (!hq_enter(task)) {
|
|
error(INFO, "%s: IDR xarray: duplicate task: %lx\n",
|
|
DUMPFILE() ? "\n" : "", task);
|
|
if (DUMPFILE())
|
|
break;
|
|
hq_close();
|
|
retries++;
|
|
goto retry_xarray;
|
|
}
|
|
|
|
cnt++;
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_xarray;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_xarray;
|
|
}
|
|
|
|
add_context(*tlp, tp);
|
|
}
|
|
|
|
FREEBUF(pidbuf);
|
|
|
|
please_wait_done();
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
static void
|
|
refresh_active_task_table(void)
|
|
{
|
|
int i;
|
|
char *tp;
|
|
int cnt;
|
|
ulong curtask;
|
|
ulong curpid;
|
|
ulong retries;
|
|
ulong *tlp;
|
|
|
|
if (DUMPFILE() && (tt->flags & TASK_INIT_DONE)) /* impossible */
|
|
return;
|
|
|
|
if (DUMPFILE()) {
|
|
please_wait("gathering task table data");
|
|
if (!symbol_exists("panic_threads"))
|
|
tt->flags |= POPULATE_PANIC;
|
|
}
|
|
|
|
if (ACTIVE() && !(tt->flags & TASK_REFRESH))
|
|
return;
|
|
|
|
curtask = NO_TASK;
|
|
curpid = NO_PID;
|
|
retries = 0;
|
|
|
|
get_active_set();
|
|
/*
|
|
* The current task's task_context entry may change,
|
|
* or the task may not even exist anymore.
|
|
*/
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE)) {
|
|
curtask = CURRENT_TASK();
|
|
curpid = CURRENT_PID();
|
|
}
|
|
|
|
retry_active:
|
|
|
|
if (!hq_open()) {
|
|
error(INFO, "cannot hash task_struct entries\n");
|
|
if (!(tt->flags & TASK_INIT_DONE))
|
|
clean_exit(1);
|
|
error(INFO, "using stale task_structs\n");
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Get the active tasks.
|
|
*/
|
|
cnt = 0;
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (hq_enter(tt->active_set[i]))
|
|
cnt++;
|
|
else
|
|
error(WARNING, "%sduplicate active tasks?\n",
|
|
DUMPFILE() ? "\n" : "");
|
|
}
|
|
|
|
BZERO(tt->task_local, tt->max_tasks * sizeof(void *));
|
|
cnt = retrieve_list((ulong *)tt->task_local, cnt);
|
|
|
|
hq_close();
|
|
|
|
clear_task_cache();
|
|
|
|
for (i = 0, tlp = (ulong *)tt->task_local, tt->running_tasks = 0;
|
|
i < tt->max_tasks; i++, tlp++) {
|
|
if (!(*tlp))
|
|
continue;
|
|
|
|
if (!IS_TASK_ADDR(*tlp)) {
|
|
error(WARNING,
|
|
"%sinvalid task address found in task list: %lx\n",
|
|
DUMPFILE() ? "\n" : "", *tlp);
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_active;
|
|
}
|
|
|
|
if (!(tp = fill_task_struct(*tlp))) {
|
|
if (DUMPFILE())
|
|
continue;
|
|
retries++;
|
|
goto retry_active;
|
|
}
|
|
|
|
if (!add_context(*tlp, tp) && DUMPFILE())
|
|
error(WARNING, "corrupt/invalid active task: %lx\n",
|
|
*tlp);
|
|
}
|
|
|
|
if (!tt->running_tasks) {
|
|
if (DUMPFILE())
|
|
error(FATAL, "cannot determine any active tasks!\n");
|
|
retries++;
|
|
goto retry_active;
|
|
}
|
|
|
|
please_wait_done();
|
|
|
|
if (ACTIVE() && (tt->flags & TASK_INIT_DONE))
|
|
refresh_context(curtask, curpid);
|
|
|
|
tt->retries = MAX(tt->retries, retries);
|
|
}
|
|
|
|
/*
|
|
* Initialize and return a new task_context structure with data from a task.
|
|
* NULL is returned on error.
|
|
*/
|
|
static struct task_context *
|
|
add_context(ulong task, char *tp)
|
|
{
|
|
pid_t *pid_addr, *tgid_addr;
|
|
char *comm_addr;
|
|
int *processor_addr;
|
|
ulong *parent_addr;
|
|
ulong *mm_addr;
|
|
int has_cpu;
|
|
int do_verify;
|
|
struct task_context *tc;
|
|
struct tgid_context *tg;
|
|
|
|
processor_addr = NULL;
|
|
|
|
if (tt->refresh_task_table == refresh_fixed_task_table)
|
|
do_verify = 1;
|
|
else if (tt->refresh_task_table == refresh_pid_hash_task_table)
|
|
do_verify = 2;
|
|
else if (tt->refresh_task_table == refresh_hlist_task_table)
|
|
do_verify = 2;
|
|
else if (tt->refresh_task_table == refresh_hlist_task_table_v2)
|
|
do_verify = 2;
|
|
else if (tt->refresh_task_table == refresh_hlist_task_table_v3)
|
|
do_verify = 2;
|
|
else if (tt->refresh_task_table == refresh_active_task_table)
|
|
do_verify = 2;
|
|
else
|
|
do_verify = 0;
|
|
|
|
tc = tt->context_array + tt->running_tasks;
|
|
|
|
pid_addr = (pid_t *)(tp + OFFSET(task_struct_pid));
|
|
tgid_addr = (pid_t *)(tp + OFFSET(task_struct_tgid));
|
|
comm_addr = (char *)(tp + OFFSET(task_struct_comm));
|
|
if (tt->flags & THREAD_INFO) {
|
|
if (tt->flags & THREAD_INFO_IN_TASK)
|
|
tc->thread_info = task + OFFSET(task_struct_thread_info);
|
|
else
|
|
tc->thread_info = ULONG(tp + OFFSET(task_struct_thread_info));
|
|
fill_thread_info(tc->thread_info);
|
|
if (tt->flags & THREAD_INFO_IN_TASK && VALID_MEMBER(task_struct_cpu))
|
|
processor_addr = (int *) (tp + OFFSET(task_struct_cpu));
|
|
else
|
|
processor_addr = (int *) (tt->thread_info +
|
|
OFFSET(thread_info_cpu));
|
|
} else if (VALID_MEMBER(task_struct_processor))
|
|
processor_addr = (int *) (tp + OFFSET(task_struct_processor));
|
|
else if (VALID_MEMBER(task_struct_cpu))
|
|
processor_addr = (int *) (tp + OFFSET(task_struct_cpu));
|
|
if (VALID_MEMBER(task_struct_p_pptr))
|
|
parent_addr = (ulong *)(tp + OFFSET(task_struct_p_pptr));
|
|
else
|
|
parent_addr = (ulong *)(tp + OFFSET(task_struct_parent));
|
|
mm_addr = (ulong *)(tp + OFFSET(task_struct_mm));
|
|
has_cpu = task_has_cpu(task, tp);
|
|
|
|
tc->pid = (ulong)(*pid_addr);
|
|
strlcpy(tc->comm, comm_addr, TASK_COMM_LEN);
|
|
if (machine_type("SPARC64"))
|
|
tc->processor = *(unsigned short *)processor_addr;
|
|
else
|
|
tc->processor = *processor_addr;
|
|
tc->ptask = *parent_addr;
|
|
tc->mm_struct = *mm_addr;
|
|
tc->task = task;
|
|
tc->tc_next = NULL;
|
|
|
|
/*
|
|
* Fill a tgid_context structure with the data from
|
|
* the incoming task.
|
|
*/
|
|
tg = tt->tgid_array + tt->running_tasks;
|
|
tg->tgid = *tgid_addr;
|
|
tg->task = task;
|
|
tg->rss_cache = UNINITIALIZED;
|
|
|
|
if (do_verify && !verify_task(tc, do_verify)) {
|
|
error(INFO, "invalid task address: %lx\n", tc->task);
|
|
BZERO(tc, sizeof(struct task_context));
|
|
return NULL;
|
|
}
|
|
|
|
if (has_cpu && (tt->flags & POPULATE_PANIC))
|
|
tt->panic_threads[tc->processor] = tc->task;
|
|
|
|
tt->flags &= ~INDEXED_CONTEXTS;
|
|
tt->running_tasks++;
|
|
return tc;
|
|
}
|
|
|
|
/*
|
|
* The current context may have moved to a new spot in the task table
|
|
* or have exited since the last command. If it still exists, reset its
|
|
* new position. If it doesn't exist, set the context back to the initial
|
|
* crash context. If necessary, complain and show the restored context.
|
|
*/
|
|
static void
|
|
refresh_context(ulong curtask, ulong curpid)
|
|
{
|
|
ulong value, complain;
|
|
struct task_context *tc;
|
|
|
|
if (task_exists(curtask) && pid_exists(curpid)) {
|
|
set_context(curtask, NO_PID);
|
|
} else {
|
|
set_context(tt->this_task, NO_PID);
|
|
|
|
complain = TRUE;
|
|
if (STREQ(args[0], "set") && (argcnt == 2) &&
|
|
IS_A_NUMBER(args[1])) {
|
|
|
|
switch (str_to_context(args[optind], &value, &tc))
|
|
{
|
|
case STR_PID:
|
|
case STR_TASK:
|
|
complain = FALSE;
|
|
break;
|
|
case STR_INVALID:
|
|
complain = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (complain) {
|
|
error(INFO, "current context no longer exists -- "
|
|
"restoring \"%s\" context:\n\n",
|
|
pc->program_name);
|
|
show_context(CURRENT_CONTEXT());
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
sort_by_task(const void *arg1, const void *arg2)
|
|
{
|
|
const struct task_context *t1, *t2;
|
|
|
|
t1 = *(const struct task_context **)arg1;
|
|
t2 = *(const struct task_context **)arg2;
|
|
|
|
if (t1->task == t2->task)
|
|
return 0;
|
|
|
|
return (t1->task < t2->task) ? -1 : 1;
|
|
}
|
|
|
|
/* sort context_by_task by task address */
|
|
static void
|
|
sort_context_by_task(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < tt->running_tasks; i++)
|
|
tt->context_by_task[i] = &tt->context_array[i];
|
|
qsort(tt->context_by_task, tt->running_tasks,
|
|
sizeof(*tt->context_by_task), sort_by_task);
|
|
tt->flags |= INDEXED_CONTEXTS;
|
|
}
|
|
|
|
/*
|
|
* Sort the task_context array by PID number; for PID 0, sort by processor.
|
|
*/
|
|
void
|
|
sort_context_array(void)
|
|
{
|
|
ulong curtask;
|
|
|
|
curtask = CURRENT_TASK();
|
|
qsort((void *)tt->context_array, (size_t)tt->running_tasks,
|
|
sizeof(struct task_context), sort_by_pid);
|
|
set_context(curtask, NO_PID);
|
|
|
|
sort_context_by_task();
|
|
}
|
|
|
|
static int
|
|
sort_by_pid(const void *arg1, const void *arg2)
|
|
{
|
|
struct task_context *t1, *t2;
|
|
|
|
t1 = (struct task_context *)arg1;
|
|
t2 = (struct task_context *)arg2;
|
|
|
|
if ((t1->pid == 0) && (t2->pid == 0))
|
|
return (t1->processor < t2->processor ? -1 :
|
|
t1->processor == t2->processor ? 0 : 1);
|
|
else
|
|
return (t1->pid < t2->pid ? -1 :
|
|
t1->pid == t2->pid ? 0 : 1);
|
|
}
|
|
|
|
|
|
static int
|
|
sort_by_last_run(const void *arg1, const void *arg2)
|
|
{
|
|
ulong task_last_run_stamp(ulong);
|
|
struct task_context *t1, *t2;
|
|
ulonglong lr1, lr2;
|
|
|
|
t1 = (struct task_context *)arg1;
|
|
t2 = (struct task_context *)arg2;
|
|
|
|
lr1 = task_last_run(t1->task);
|
|
lr2 = task_last_run(t2->task);
|
|
|
|
return (lr2 < lr1 ? -1 :
|
|
lr2 == lr1 ? 0 : 1);
|
|
}
|
|
|
|
static void
|
|
sort_context_array_by_last_run(void)
|
|
{
|
|
ulong curtask;
|
|
|
|
curtask = CURRENT_TASK();
|
|
qsort((void *)tt->context_array, (size_t)tt->running_tasks,
|
|
sizeof(struct task_context), sort_by_last_run);
|
|
set_context(curtask, NO_PID);
|
|
|
|
sort_context_by_task();
|
|
}
|
|
|
|
/*
|
|
* Set the tgid_context array by tgid number.
|
|
*/
|
|
void
|
|
sort_tgid_array(void)
|
|
{
|
|
if (VALID_MEMBER(mm_struct_rss) || (!VALID_MEMBER(task_struct_rss_stat)))
|
|
return;
|
|
|
|
qsort((void *)tt->tgid_array, (size_t)tt->running_tasks,
|
|
sizeof(struct tgid_context), sort_by_tgid);
|
|
|
|
tt->last_tgid = tt->tgid_array;
|
|
}
|
|
|
|
int
|
|
sort_by_tgid(const void *arg1, const void *arg2)
|
|
{
|
|
struct tgid_context *t1, *t2;
|
|
|
|
t1 = (struct tgid_context *)arg1;
|
|
t2 = (struct tgid_context *)arg2;
|
|
|
|
return (t1->tgid < t2->tgid ? -1 :
|
|
t1->tgid == t2->tgid ? 0 : 1);
|
|
}
|
|
|
|
/*
|
|
* Keep a stash of the last task_struct accessed. Chances are it will
|
|
* be hit several times before the next task is accessed.
|
|
*/
|
|
|
|
char *
|
|
fill_task_struct(ulong task)
|
|
{
|
|
if (XEN_HYPER_MODE())
|
|
return NULL;
|
|
|
|
if (!IS_LAST_TASK_READ(task)) {
|
|
if (!readmem(task, KVADDR, tt->task_struct,
|
|
SIZE(task_struct), "fill_task_struct",
|
|
ACTIVE() ? (RETURN_ON_ERROR|QUIET) : RETURN_ON_ERROR)) {
|
|
tt->last_task_read = 0;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
tt->last_task_read = task;
|
|
return(tt->task_struct);
|
|
}
|
|
|
|
/*
|
|
* Keep a stash of the last thread_info struct accessed. Chances are it will
|
|
* be hit several times before the next task is accessed.
|
|
*/
|
|
|
|
char *
|
|
fill_thread_info(ulong thread_info)
|
|
{
|
|
if (!IS_LAST_THREAD_INFO_READ(thread_info)) {
|
|
if (!readmem(thread_info, KVADDR, tt->thread_info,
|
|
SIZE(thread_info), "fill_thread_info",
|
|
ACTIVE() ? (RETURN_ON_ERROR|QUIET) : RETURN_ON_ERROR)) {
|
|
tt->last_thread_info_read = 0;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
tt->last_thread_info_read = thread_info;
|
|
return(tt->thread_info);
|
|
}
|
|
/*
|
|
* Used by back_trace(), copy the complete kernel stack into a local buffer
|
|
* and fill the task_struct buffer, dealing with possible future separation
|
|
* of task_struct and stack and/or cache coloring of stack top.
|
|
*/
|
|
void
|
|
fill_stackbuf(struct bt_info *bt)
|
|
{
|
|
if (!bt->stackbuf) {
|
|
bt->stackbuf = GETBUF(bt->stacktop - bt->stackbase);
|
|
|
|
if (!readmem(bt->stackbase, KVADDR, bt->stackbuf,
|
|
bt->stacktop - bt->stackbase,
|
|
"stack contents", RETURN_ON_ERROR))
|
|
error(FATAL, "read of stack at %lx failed\n",
|
|
bt->stackbase);
|
|
}
|
|
|
|
if (XEN_HYPER_MODE())
|
|
return;
|
|
|
|
if (!IS_LAST_TASK_READ(bt->task)) {
|
|
if (bt->stackbase == bt->task) {
|
|
BCOPY(bt->stackbuf, tt->task_struct, SIZE(task_struct));
|
|
tt->last_task_read = bt->task;
|
|
} else
|
|
fill_task_struct(bt->task);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Keeping the task_struct info intact, alter the contents of the already
|
|
* allocated local copy of a kernel stack, for things like IRQ stacks or
|
|
* non-standard eframe searches. The caller must change the stackbase
|
|
* and stacktop values.
|
|
*/
|
|
void
|
|
alter_stackbuf(struct bt_info *bt)
|
|
{
|
|
if (!readmem(bt->stackbase, KVADDR, bt->stackbuf,
|
|
bt->stacktop - bt->stackbase, "stack contents", RETURN_ON_ERROR))
|
|
error(FATAL, "read of stack at %lx failed\n", bt->stackbase);
|
|
}
|
|
|
|
/*
|
|
* In the same vein as fill_task_struct(), keep a stash of the mm_struct
|
|
* of a task.
|
|
*/
|
|
|
|
char *fill_mm_struct(ulong mm)
|
|
{
|
|
if (!IS_LAST_MM_READ(mm)) {
|
|
if (!readmem(mm, KVADDR, tt->mm_struct,
|
|
SIZE(mm_struct), "fill_mm_struct",
|
|
ACTIVE() ? (RETURN_ON_ERROR|QUIET) : RETURN_ON_ERROR)) {
|
|
tt->last_mm_read = 0;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
tt->last_mm_read = mm;
|
|
return(tt->mm_struct);
|
|
}
|
|
|
|
/*
|
|
* If active, clear out references to the last task and mm_struct read.
|
|
*/
|
|
void
|
|
clear_task_cache(void)
|
|
{
|
|
if (ACTIVE())
|
|
tt->last_task_read = tt->last_mm_read = 0;
|
|
}
|
|
|
|
/*
|
|
* Shorthand command to dump the current context's task_struct, or if
|
|
* pid or task arguments are entered, the task_structs of the targets.
|
|
* References to structure members can be given to pare down the output,
|
|
* which are put in a comma-separated list.
|
|
*/
|
|
void
|
|
cmd_task(void)
|
|
{
|
|
int c, tcnt, bogus;
|
|
unsigned int radix;
|
|
ulong value;
|
|
struct reference *ref;
|
|
struct task_context *tc;
|
|
ulong *tasklist;
|
|
char *memberlist;
|
|
|
|
tasklist = (ulong *)GETBUF((MAXARGS+NR_CPUS)*sizeof(ulong));
|
|
ref = (struct reference *)GETBUF(sizeof(struct reference));
|
|
memberlist = GETBUF(BUFSIZE);
|
|
ref->str = memberlist;
|
|
radix = 0;
|
|
|
|
while ((c = getopt(argcnt, args, "xdhR:")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'h':
|
|
case 'x':
|
|
if (radix == 10)
|
|
error(FATAL,
|
|
"-d and -x are mutually exclusive\n");
|
|
radix = 16;
|
|
break;
|
|
|
|
case 'd':
|
|
if (radix == 16)
|
|
error(FATAL,
|
|
"-d and -x are mutually exclusive\n");
|
|
radix = 10;
|
|
break;
|
|
|
|
case 'R':
|
|
if (strlen(ref->str))
|
|
strcat(ref->str, ",");
|
|
strcat(ref->str, optarg);
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
tcnt = bogus = 0;
|
|
|
|
while (args[optind]) {
|
|
if (IS_A_NUMBER(args[optind])) {
|
|
switch (str_to_context(args[optind], &value, &tc))
|
|
{
|
|
case STR_PID:
|
|
for (tc = pid_to_context(value); tc;
|
|
tc = tc->tc_next)
|
|
tasklist[tcnt++] = tc->task;
|
|
break;
|
|
|
|
case STR_TASK:
|
|
tasklist[tcnt++] = value;
|
|
break;
|
|
|
|
case STR_INVALID:
|
|
bogus++;
|
|
error(INFO, "invalid task or pid value: %s\n\n",
|
|
args[optind]);
|
|
break;
|
|
}
|
|
} else if (strstr(args[optind], ",") ||
|
|
MEMBER_EXISTS("task_struct", args[optind])) {
|
|
if (strlen(ref->str))
|
|
strcat(ref->str, ",");
|
|
strcat(ref->str, args[optind]);
|
|
} else if (strstr(args[optind], ".") || strstr(args[optind], "[")) {
|
|
if (strlen(ref->str))
|
|
strcat(ref->str, ",");
|
|
strcat(ref->str, args[optind]);
|
|
} else
|
|
error(INFO,
|
|
"invalid task, pid, or task_struct member: %s\n\n",
|
|
args[optind]);
|
|
optind++;
|
|
}
|
|
|
|
if (!tcnt && !bogus)
|
|
tasklist[tcnt++] = CURRENT_TASK();
|
|
|
|
for (c = 0; c < tcnt; c++)
|
|
do_task(tasklist[c], 0, strlen(ref->str) ? ref : NULL, radix);
|
|
|
|
}
|
|
|
|
/*
|
|
* Do the work for the task command.
|
|
*/
|
|
void
|
|
do_task(ulong task, ulong flags, struct reference *ref, unsigned int radix)
|
|
{
|
|
struct task_context *tc;
|
|
|
|
tc = task_to_context(task);
|
|
|
|
if (ref) {
|
|
print_task_header(fp, tc, 0);
|
|
task_struct_member(tc, radix, ref);
|
|
} else {
|
|
if (!(flags & FOREACH_TASK))
|
|
print_task_header(fp, tc, 0);
|
|
dump_struct("task_struct", task, radix);
|
|
if (tt->flags & THREAD_INFO) {
|
|
fprintf(fp, "\n");
|
|
dump_struct("thread_info", tc->thread_info, radix);
|
|
}
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* Search the task_struct for the referenced field.
|
|
*/
|
|
static void
|
|
task_struct_member(struct task_context *tc, unsigned int radix, struct reference *ref)
|
|
{
|
|
int i;
|
|
int argcnt;
|
|
char *arglist[MAXARGS];
|
|
char *refcopy;
|
|
struct datatype_member dm;
|
|
|
|
if ((count_chars(ref->str, ',')+1) > MAXARGS) {
|
|
error(INFO,
|
|
"too many -R arguments in comma-separated list!\n");
|
|
return;
|
|
}
|
|
|
|
refcopy = GETBUF(strlen(ref->str)+1);
|
|
strcpy(refcopy, ref->str);
|
|
replace_string(refcopy, ",", ' ');
|
|
|
|
argcnt = parse_line(refcopy, arglist);
|
|
|
|
open_tmpfile();
|
|
dump_struct("task_struct", tc->task, radix);
|
|
if (tt->flags & THREAD_INFO)
|
|
dump_struct("thread_info", tc->thread_info, radix);
|
|
|
|
for (i = 0; i < argcnt; i++) {
|
|
if (count_chars(arglist[i], '.') || count_chars(arglist[i], '[')) {
|
|
dm.member = arglist[i];
|
|
parse_for_member_extended(&dm, 0);
|
|
} else {
|
|
if (!MEMBER_EXISTS("task_struct", arglist[i]) &&
|
|
!MEMBER_EXISTS("thread_info", arglist[i]))
|
|
error(INFO, "%s: not a task_struct or "
|
|
"thread_info member\n", arglist[i]);
|
|
|
|
parse_task_thread(1, &arglist[i], tc);
|
|
}
|
|
}
|
|
|
|
close_tmpfile();
|
|
|
|
FREEBUF(refcopy);
|
|
|
|
}
|
|
|
|
static void
|
|
parse_task_thread(int argcnt, char *arglist[], struct task_context *tc) {
|
|
char buf[BUFSIZE];
|
|
char lookfor1[BUFSIZE];
|
|
char lookfor2[BUFSIZE];
|
|
char lookfor3[BUFSIZE];
|
|
int i, cnt, randomized;
|
|
|
|
rewind(pc->tmpfile);
|
|
|
|
BZERO(lookfor1, BUFSIZE);
|
|
BZERO(lookfor2, BUFSIZE);
|
|
BZERO(lookfor3, BUFSIZE);
|
|
randomized = FALSE;
|
|
|
|
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (STREQ(buf, " {\n"))
|
|
randomized = TRUE;
|
|
else if (randomized && STREQ(buf, " }, \n"))
|
|
randomized = FALSE;
|
|
|
|
if (strlen(lookfor2)) {
|
|
fprintf(pc->saved_fp, "%s", buf);
|
|
if (STRNEQ(buf, lookfor2))
|
|
BZERO(lookfor2, BUFSIZE);
|
|
continue;
|
|
}
|
|
|
|
if (strlen(lookfor3)) {
|
|
fprintf(pc->saved_fp, "%s", buf);
|
|
if (strstr(buf, lookfor3))
|
|
BZERO(lookfor3, BUFSIZE);
|
|
continue;
|
|
}
|
|
|
|
for (i = 0; i < argcnt; i++) {
|
|
BZERO(lookfor1, BUFSIZE);
|
|
BZERO(lookfor2, BUFSIZE);
|
|
BZERO(lookfor3, BUFSIZE);
|
|
sprintf(lookfor1, "%s %s = ",
|
|
randomized ? " " : "", arglist[i]);
|
|
if (STRNEQ(buf, lookfor1)) {
|
|
fprintf(pc->saved_fp, "%s", buf);
|
|
if (strstr(buf, "{{\n"))
|
|
sprintf(lookfor2, "%s }},",
|
|
randomized ? " " : "");
|
|
else if (strstr(buf, " = {\n")) {
|
|
cnt = count_leading_spaces(buf);
|
|
sprintf(lookfor2, "%s}", space(cnt));
|
|
} else if (strstr(buf, "{"))
|
|
sprintf(lookfor3, "},");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static char *ps_exclusive =
|
|
"-a, -t, -c, -p, -g, -l, -m, -S, -r and -A flags are all mutually-exclusive\n";
|
|
|
|
static void
|
|
check_ps_exclusive(ulong flag, ulong thisflag)
|
|
{
|
|
if (flag & (PS_EXCLUSIVE & ~thisflag))
|
|
error(FATAL, ps_exclusive);
|
|
}
|
|
|
|
/*
|
|
* Display ps-like data for all tasks, or as specified by pid, task, or
|
|
* command-name arguments.
|
|
*/
|
|
void
|
|
cmd_ps(void)
|
|
{
|
|
int c, ac;
|
|
ulong flag;
|
|
ulong value;
|
|
static struct psinfo psinfo;
|
|
struct task_context *tc;
|
|
char *cpuspec, *p;
|
|
|
|
BZERO(&psinfo, sizeof(struct psinfo));
|
|
cpuspec = NULL;
|
|
flag = 0;
|
|
|
|
while ((c = getopt(argcnt, args, "ASgstcpkuGlmarC:y:")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'k':
|
|
if (flag & PS_USER)
|
|
error(FATAL,
|
|
"-u and -k are mutually exclusive\n");
|
|
flag |= PS_KERNEL;
|
|
break;
|
|
|
|
case 'u':
|
|
if (flag & PS_KERNEL)
|
|
error(FATAL,
|
|
"-u and -k are mutually exclusive\n");
|
|
flag |= PS_USER;
|
|
break;
|
|
|
|
case 'G':
|
|
if (flag & PS_GROUP)
|
|
break;
|
|
else if (hq_open())
|
|
flag |= PS_GROUP;
|
|
else
|
|
error(INFO, "cannot hash thread group tasks\n");
|
|
break;
|
|
/*
|
|
* The a, t, c, p, g, l and r flags are all mutually-exclusive.
|
|
*/
|
|
case 'g':
|
|
check_ps_exclusive(flag, PS_TGID_LIST);
|
|
flag |= PS_TGID_LIST;
|
|
break;
|
|
|
|
case 'a':
|
|
check_ps_exclusive(flag, PS_ARGV_ENVP);
|
|
flag |= PS_ARGV_ENVP;
|
|
break;
|
|
|
|
case 't':
|
|
check_ps_exclusive(flag, PS_TIMES);
|
|
flag |= PS_TIMES;
|
|
break;
|
|
|
|
case 'c':
|
|
check_ps_exclusive(flag, PS_CHILD_LIST);
|
|
flag |= PS_CHILD_LIST;
|
|
break;
|
|
|
|
case 'p':
|
|
check_ps_exclusive(flag, PS_PPID_LIST);
|
|
flag |= PS_PPID_LIST;
|
|
break;
|
|
|
|
case 'm':
|
|
if (INVALID_MEMBER(task_struct_last_run) &&
|
|
INVALID_MEMBER(task_struct_timestamp) &&
|
|
INVALID_MEMBER(sched_info_last_arrival)) {
|
|
error(INFO,
|
|
"last-run timestamps do not exist in this kernel\n");
|
|
argerrs++;
|
|
break;
|
|
}
|
|
if (INVALID_MEMBER(rq_timestamp))
|
|
option_not_supported(c);
|
|
check_ps_exclusive(flag, PS_MSECS);
|
|
flag |= PS_MSECS;
|
|
break;
|
|
|
|
case 'l':
|
|
if (INVALID_MEMBER(task_struct_last_run) &&
|
|
INVALID_MEMBER(task_struct_timestamp) &&
|
|
INVALID_MEMBER(sched_info_last_arrival)) {
|
|
error(INFO,
|
|
"last-run timestamps do not exist in this kernel\n");
|
|
argerrs++;
|
|
break;
|
|
}
|
|
check_ps_exclusive(flag, PS_LAST_RUN);
|
|
flag |= PS_LAST_RUN;
|
|
break;
|
|
|
|
case 's':
|
|
flag |= PS_KSTACKP;
|
|
break;
|
|
|
|
case 'r':
|
|
check_ps_exclusive(flag, PS_RLIMIT);
|
|
flag |= PS_RLIMIT;
|
|
break;
|
|
|
|
case 'S':
|
|
check_ps_exclusive(flag, PS_SUMMARY);
|
|
flag |= PS_SUMMARY;
|
|
break;
|
|
|
|
case 'C':
|
|
cpuspec = optarg;
|
|
psinfo.cpus = get_cpumask_buf();
|
|
make_cpumask(cpuspec, psinfo.cpus, FAULT_ON_ERROR, NULL);
|
|
break;
|
|
|
|
case 'y':
|
|
flag |= PS_POLICY;
|
|
psinfo.policy = make_sched_policy(optarg);
|
|
break;
|
|
|
|
case 'A':
|
|
check_ps_exclusive(flag, PS_ACTIVE);
|
|
flag |= PS_ACTIVE;
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (flag & (PS_LAST_RUN|PS_MSECS))
|
|
sort_context_array_by_last_run();
|
|
else if (psinfo.cpus) {
|
|
error(INFO, "-C option is only applicable with -l and -m\n");
|
|
goto bailout;
|
|
}
|
|
|
|
if (!args[optind]) {
|
|
show_ps(PS_SHOW_ALL|flag, &psinfo);
|
|
return;
|
|
}
|
|
|
|
if (flag & PS_SUMMARY)
|
|
error(FATAL, "-S option takes no arguments\n");
|
|
|
|
if (psinfo.cpus)
|
|
error(INFO,
|
|
"-C option is not applicable with specified tasks\n");
|
|
ac = 0;
|
|
while (args[optind]) {
|
|
if (IS_A_NUMBER(args[optind])) {
|
|
switch (str_to_context(args[optind], &value, &tc))
|
|
{
|
|
case STR_PID:
|
|
psinfo.pid[ac] = value;
|
|
psinfo.task[ac] = NO_TASK;
|
|
psinfo.type[ac] = PS_BY_PID;
|
|
flag |= PS_BY_PID;
|
|
break;
|
|
|
|
case STR_TASK:
|
|
psinfo.task[ac] = value;
|
|
psinfo.pid[ac] = NO_PID;
|
|
psinfo.type[ac] = PS_BY_TASK;
|
|
flag |= PS_BY_TASK;
|
|
break;
|
|
|
|
case STR_INVALID:
|
|
error(INFO, "invalid task or pid value: %s\n\n",
|
|
args[optind]);
|
|
break;
|
|
}
|
|
ac++;
|
|
} else if (SINGLE_QUOTED_STRING(args[optind])) {
|
|
/*
|
|
* Regular expression is exclosed within "'" character.
|
|
* The args[optind] string may not be modified, so a copy
|
|
* is duplicated.
|
|
*/
|
|
if (psinfo.regexs == MAX_PS_ARGS)
|
|
error(INFO, "too many expressions specified!\n");
|
|
else {
|
|
p = strdup(&args[optind][1]);
|
|
LASTCHAR(p) = NULLCHAR;
|
|
|
|
if (regcomp(&psinfo.regex_data[psinfo.regexs].regex,
|
|
p, REG_EXTENDED|REG_NOSUB)) {
|
|
error(INFO,
|
|
"invalid regular expression: %s\n", p);
|
|
free(p);
|
|
goto bailout;
|
|
}
|
|
|
|
psinfo.regex_data[psinfo.regexs].pattern = p;
|
|
if (psinfo.regexs++ == 0) {
|
|
pc->cmd_cleanup_arg = (void *)&psinfo;
|
|
pc->cmd_cleanup = ps_cleanup;
|
|
}
|
|
psinfo.type[ac] = PS_BY_REGEX;
|
|
flag |= PS_BY_REGEX;
|
|
ac++;
|
|
}
|
|
optind++;
|
|
continue;
|
|
} else {
|
|
psinfo.pid[ac] = NO_PID;
|
|
psinfo.task[ac] = NO_TASK;
|
|
p = args[optind][0] == '\\' ?
|
|
&args[optind][1] : args[optind];
|
|
strlcpy(psinfo.comm[ac], p, TASK_COMM_LEN);
|
|
psinfo.type[ac] = PS_BY_CMD;
|
|
flag |= PS_BY_CMD;
|
|
ac++;
|
|
}
|
|
optind++;
|
|
}
|
|
|
|
psinfo.argc = ac;
|
|
show_ps(flag, &psinfo);
|
|
|
|
bailout:
|
|
ps_cleanup((void *)&psinfo);
|
|
}
|
|
|
|
/*
|
|
* Clean up regex buffers and pattern strings.
|
|
*/
|
|
static void
|
|
ps_cleanup(void *arg)
|
|
{
|
|
int i;
|
|
struct psinfo *ps;
|
|
|
|
pc->cmd_cleanup = NULL;
|
|
pc->cmd_cleanup_arg = NULL;
|
|
|
|
ps = (struct psinfo *)arg;
|
|
|
|
for (i = 0; i < ps->regexs; i++) {
|
|
regfree(&ps->regex_data[i].regex);
|
|
free(ps->regex_data[i].pattern);
|
|
}
|
|
|
|
if (ps->cpus)
|
|
FREEBUF(ps->cpus);
|
|
}
|
|
|
|
/*
|
|
* Do the work requested by cmd_ps().
|
|
*/
|
|
static void
|
|
show_ps_data(ulong flag, struct task_context *tc, struct psinfo *psi)
|
|
{
|
|
struct task_mem_usage task_mem_usage, *tm;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
ulong tgid;
|
|
int task_active;
|
|
|
|
if ((flag & PS_USER) && is_kernel_thread(tc->task))
|
|
return;
|
|
if ((flag & PS_KERNEL) && !is_kernel_thread(tc->task))
|
|
return;
|
|
if ((flag & PS_POLICY) && !has_sched_policy(tc->task, psi->policy))
|
|
return;
|
|
if (flag & PS_GROUP) {
|
|
if (flag & (PS_LAST_RUN|PS_MSECS))
|
|
error(FATAL, "-G not supported with -%c option\n",
|
|
flag & PS_LAST_RUN ? 'l' : 'm');
|
|
|
|
tgid = task_tgid(tc->task);
|
|
if (tc->pid != tgid) {
|
|
if (pc->curcmd_flags & TASK_SPECIFIED) {
|
|
if (!(tc = tgid_to_context(tgid)))
|
|
return;
|
|
if (hq_entry_exists((ulong)tc))
|
|
return;
|
|
hq_enter((ulong)tc);
|
|
} else
|
|
return;
|
|
} else {
|
|
if (hq_entry_exists((ulong)tc))
|
|
return;
|
|
hq_enter((ulong)tc);
|
|
}
|
|
}
|
|
|
|
if (flag & PS_PPID_LIST) {
|
|
parent_list(tc->task);
|
|
fprintf(fp, "\n");
|
|
return;
|
|
}
|
|
if (flag & PS_CHILD_LIST) {
|
|
child_list(tc->task);
|
|
fprintf(fp, "\n");
|
|
return;
|
|
}
|
|
if (flag & (PS_LAST_RUN)) {
|
|
show_last_run(tc, psi);
|
|
return;
|
|
}
|
|
if (flag & (PS_MSECS)) {
|
|
show_milliseconds(tc, psi);
|
|
return;
|
|
}
|
|
if (flag & PS_ARGV_ENVP) {
|
|
show_task_args(tc);
|
|
return;
|
|
}
|
|
if (flag & PS_RLIMIT) {
|
|
show_task_rlimit(tc);
|
|
return;
|
|
}
|
|
if (flag & PS_TGID_LIST) {
|
|
show_tgid_list(tc->task);
|
|
return;
|
|
}
|
|
|
|
tm = &task_mem_usage;
|
|
get_task_mem_usage(tc->task, tm);
|
|
|
|
task_active = is_task_active(tc->task);
|
|
|
|
if ((flag & PS_ACTIVE) && (flag & PS_SHOW_ALL) && !task_active)
|
|
return;
|
|
|
|
if (task_active) {
|
|
if (hide_offline_cpu(tc->processor))
|
|
fprintf(fp, "- ");
|
|
else
|
|
fprintf(fp, "> ");
|
|
} else
|
|
fprintf(fp, " ");
|
|
|
|
fprintf(fp, "%5ld %5ld %2s %s %3s",
|
|
tc->pid, task_to_pid(tc->ptask),
|
|
task_cpu(tc->processor, buf2, !VERBOSE),
|
|
task_pointer_string(tc, flag & PS_KSTACKP, buf3),
|
|
task_state_string(tc->task, buf1, !VERBOSE));
|
|
pad_line(fp, strlen(buf1) > 3 ? 1 : 2, ' ');
|
|
sprintf(buf1, "%.1f", tm->pct_physmem);
|
|
if (strlen(buf1) == 3)
|
|
mkstring(buf1, 4, CENTER|RJUST, NULL);
|
|
fprintf(fp, "%s ", buf1);
|
|
fprintf(fp, "%7ld ", (tm->total_vm * PAGESIZE())/1024);
|
|
fprintf(fp, "%6ld ", (tm->rss * PAGESIZE())/1024);
|
|
if (is_kernel_thread(tc->task))
|
|
fprintf(fp, "[%s]\n", tc->comm);
|
|
else
|
|
fprintf(fp, "%s\n", tc->comm);
|
|
}
|
|
|
|
static void
|
|
show_ps(ulong flag, struct psinfo *psi)
|
|
{
|
|
int i, ac;
|
|
struct task_context *tc;
|
|
int print;
|
|
char buf[BUFSIZE];
|
|
|
|
if (!(flag & ((PS_EXCLUSIVE & ~PS_ACTIVE)|PS_NO_HEADER)))
|
|
fprintf(fp,
|
|
" PID PPID CPU %s ST %%MEM VSZ RSS COMM\n",
|
|
flag & PS_KSTACKP ?
|
|
mkstring(buf, VADDR_PRLEN, CENTER|RJUST, "KSTACKP") :
|
|
mkstring(buf, VADDR_PRLEN, CENTER, "TASK"));
|
|
|
|
if (flag & PS_SHOW_ALL) {
|
|
|
|
if (flag & PS_TIMES) {
|
|
show_task_times(NULL, flag);
|
|
return;
|
|
}
|
|
|
|
if (flag & PS_SUMMARY) {
|
|
show_ps_summary(flag);
|
|
return;
|
|
}
|
|
|
|
if (psi->cpus) {
|
|
show_ps_data(flag, NULL, psi);
|
|
return;
|
|
}
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++)
|
|
show_ps_data(flag, tc, psi);
|
|
|
|
return;
|
|
}
|
|
|
|
pc->curcmd_flags |= TASK_SPECIFIED;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
for (ac = 0; ac < psi->argc; ac++) {
|
|
|
|
print = FALSE;
|
|
|
|
switch(psi->type[ac])
|
|
{
|
|
case PS_BY_PID:
|
|
if (tc->pid == psi->pid[ac])
|
|
print = TRUE;
|
|
break;
|
|
|
|
case PS_BY_TASK:
|
|
if ((tc->task == psi->task[ac]))
|
|
print = TRUE;
|
|
break;
|
|
|
|
case PS_BY_CMD:
|
|
if (STREQ(tc->comm, psi->comm[ac])) {
|
|
if (flag & (PS_TGID_LIST|PS_GROUP)) {
|
|
if (tc->pid == task_tgid(tc->task))
|
|
print = TRUE;
|
|
else
|
|
print = FALSE;
|
|
} else
|
|
print = TRUE;
|
|
}
|
|
break;
|
|
|
|
case PS_BY_REGEX:
|
|
if (regexec(&psi->regex_data[ac].regex,
|
|
tc->comm, 0, NULL, 0) == 0) {
|
|
if (flag & (PS_TGID_LIST|PS_GROUP)) {
|
|
if (tc->pid == task_tgid(tc->task))
|
|
print = TRUE;
|
|
else
|
|
print = FALSE;
|
|
} else
|
|
print = TRUE;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (print) {
|
|
if (flag & PS_TIMES)
|
|
show_task_times(tc, flag);
|
|
else
|
|
show_ps_data(flag, tc, psi);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
show_ps_summary(ulong flag)
|
|
{
|
|
int i, s;
|
|
struct task_context *tc;
|
|
char buf[BUFSIZE];
|
|
#define MAX_STATES 20
|
|
struct ps_state {
|
|
long cnt;
|
|
char string[3];
|
|
} ps_state[MAX_STATES];
|
|
|
|
if (flag & (PS_USER|PS_KERNEL|PS_GROUP))
|
|
error(FATAL, "-S option cannot be used with other options\n");
|
|
|
|
for (s = 0; s < MAX_STATES; s++)
|
|
ps_state[s].cnt = 0;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
task_state_string(tc->task, buf, !VERBOSE);
|
|
for (s = 0; s < MAX_STATES; s++) {
|
|
if (ps_state[s].cnt &&
|
|
STREQ(ps_state[s].string, buf)) {
|
|
ps_state[s].cnt++;
|
|
break;
|
|
}
|
|
if (ps_state[s].cnt == 0) {
|
|
strcpy(ps_state[s].string, buf);
|
|
ps_state[s].cnt++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
for (s = 0; s < MAX_STATES; s++) {
|
|
if (ps_state[s].cnt)
|
|
fprintf(fp,
|
|
" %s: %ld\n", ps_state[s].string, ps_state[s].cnt);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* Display the task preceded by the last_run stamp and its
|
|
* current state.
|
|
*/
|
|
static void
|
|
show_last_run(struct task_context *tc, struct psinfo *psi)
|
|
{
|
|
int i, c, others;
|
|
struct task_context *tcp;
|
|
char format[15];
|
|
char buf[BUFSIZE];
|
|
|
|
tcp = FIRST_CONTEXT();
|
|
sprintf(buf, pc->output_radix == 10 ? "%lld" : "%llx",
|
|
task_last_run(tcp->task));
|
|
c = strlen(buf);
|
|
sprintf(format, "[%c%dll%c] ", '%', c,
|
|
pc->output_radix == 10 ? 'u' : 'x');
|
|
|
|
if (psi && psi->cpus) {
|
|
for (c = others = 0; c < kt->cpus; c++) {
|
|
if (!NUM_IN_BITMAP(psi->cpus, c))
|
|
continue;
|
|
fprintf(fp, "%sCPU: %d",
|
|
others++ ? "\n" : "", c);
|
|
if (hide_offline_cpu(c)) {
|
|
fprintf(fp, " [OFFLINE]\n");
|
|
continue;
|
|
} else
|
|
fprintf(fp, "\n");
|
|
|
|
tcp = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tcp++) {
|
|
if (tcp->processor != c)
|
|
continue;
|
|
fprintf(fp, format, task_last_run(tcp->task));
|
|
fprintf(fp, "[%s] ",
|
|
task_state_string(tcp->task, buf, !VERBOSE));
|
|
print_task_header(fp, tcp, FALSE);
|
|
}
|
|
}
|
|
} else if (tc) {
|
|
fprintf(fp, format, task_last_run(tc->task));
|
|
fprintf(fp, "[%s] ", task_state_string(tc->task, buf, !VERBOSE));
|
|
print_task_header(fp, tc, FALSE);
|
|
} else {
|
|
tcp = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tcp++) {
|
|
fprintf(fp, format, task_last_run(tcp->task));
|
|
fprintf(fp, "[%s] ", task_state_string(tcp->task, buf, !VERBOSE));
|
|
print_task_header(fp, tcp, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Translate a value in nanoseconds into a string showing days,
|
|
* hours, minutes, seconds and milliseconds.
|
|
*/
|
|
static char *
|
|
translate_nanoseconds(ulonglong value, char *buf)
|
|
{
|
|
ulong days, hours, mins, secs, ms;
|
|
|
|
value = value / 1000000L;
|
|
ms = value % 1000L;
|
|
value = value / 1000L;
|
|
secs = value % 60L;
|
|
value = value / 60L;
|
|
mins = value % 60L;
|
|
value = value / 60L;
|
|
hours = value % 24L;
|
|
value = value / 24L;
|
|
days = value;
|
|
|
|
sprintf(buf, "%ld %02ld:%02ld:%02ld.%03ld",
|
|
days, hours, mins, secs, ms);
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Display the task preceded by a per-rq translation of the
|
|
* sched_info.last_arrival and its current state.
|
|
*/
|
|
static void
|
|
show_milliseconds(struct task_context *tc, struct psinfo *psi)
|
|
{
|
|
int i, c, others, days, max_days;
|
|
struct task_context *tcp;
|
|
char format[15];
|
|
char buf[BUFSIZE];
|
|
struct syment *rq_sp;
|
|
ulong runq;
|
|
ulonglong rq_clock;
|
|
long long delta;
|
|
|
|
if (!(rq_sp = per_cpu_symbol_search("per_cpu__runqueues")))
|
|
error(FATAL, "cannot determine per-cpu runqueue address\n");
|
|
|
|
tcp = FIRST_CONTEXT();
|
|
sprintf(buf, pc->output_radix == 10 ? "%lld" : "%llx",
|
|
task_last_run(tcp->task));
|
|
c = strlen(buf);
|
|
sprintf(format, "[%c%dll%c] ", '%', c,
|
|
pc->output_radix == 10 ? 'u' : 'x');
|
|
|
|
if (psi && psi->cpus) {
|
|
for (c = others = 0; c < kt->cpus; c++) {
|
|
if (!NUM_IN_BITMAP(psi->cpus, c))
|
|
continue;
|
|
|
|
fprintf(fp, "%sCPU: %d",
|
|
others++ ? "\n" : "", c);
|
|
|
|
if (hide_offline_cpu(c)) {
|
|
fprintf(fp, " [OFFLINE]\n");
|
|
continue;
|
|
} else
|
|
fprintf(fp, "\n");
|
|
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[c];
|
|
else
|
|
runq = rq_sp->value;
|
|
readmem(runq + OFFSET(rq_timestamp), KVADDR, &rq_clock,
|
|
sizeof(ulonglong), "per-cpu rq clock",
|
|
FAULT_ON_ERROR);
|
|
|
|
translate_nanoseconds(rq_clock, buf);
|
|
max_days = first_space(buf) - buf;
|
|
|
|
tcp = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tcp++) {
|
|
if (tcp->processor != c)
|
|
continue;
|
|
delta = rq_clock - task_last_run(tcp->task);
|
|
if (delta < 0)
|
|
delta = 0;
|
|
translate_nanoseconds(delta, buf);
|
|
days = first_space(buf) - buf;
|
|
fprintf(fp, "[%s%s] ", space(max_days - days),
|
|
buf);
|
|
fprintf(fp, "[%s] ",
|
|
task_state_string(tcp->task,
|
|
buf, !VERBOSE));
|
|
print_task_header(fp, tcp, FALSE);
|
|
}
|
|
}
|
|
} else if (tc) {
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[tc->processor];
|
|
else
|
|
runq = rq_sp->value;
|
|
readmem(runq + OFFSET(rq_timestamp), KVADDR, &rq_clock,
|
|
sizeof(ulonglong), "per-cpu rq clock",
|
|
FAULT_ON_ERROR);
|
|
translate_nanoseconds(rq_clock, buf);
|
|
max_days = first_space(buf) - buf;
|
|
delta = rq_clock - task_last_run(tc->task);
|
|
if (delta < 0)
|
|
delta = 0;
|
|
translate_nanoseconds(delta, buf);
|
|
days = first_space(buf) - buf;
|
|
fprintf(fp, "[%s%s] ", space(max_days - days), buf);
|
|
fprintf(fp, "[%s] ", task_state_string(tc->task, buf, !VERBOSE));
|
|
print_task_header(fp, tc, FALSE);
|
|
} else {
|
|
tcp = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tcp++) {
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
runq = rq_sp->value +
|
|
kt->__per_cpu_offset[tcp->processor];
|
|
else
|
|
runq = rq_sp->value;
|
|
readmem(runq + OFFSET(rq_timestamp), KVADDR, &rq_clock,
|
|
sizeof(ulonglong), "per-cpu rq clock",
|
|
FAULT_ON_ERROR);
|
|
delta = rq_clock - task_last_run(tcp->task);
|
|
if (delta < 0)
|
|
delta = 0;
|
|
fprintf(fp, "[%s] ", translate_nanoseconds(delta, buf));
|
|
fprintf(fp, "[%s] ", task_state_string(tcp->task, buf, !VERBOSE));
|
|
print_task_header(fp, tcp, FALSE);
|
|
}
|
|
}
|
|
}
|
|
|
|
static char *
|
|
read_arg_string(struct task_context *tc, char *buf, ulong start, ulong end)
|
|
{
|
|
physaddr_t paddr;
|
|
ulong uvaddr, size, cnt;
|
|
char *bufptr;
|
|
|
|
uvaddr = start;
|
|
size = end - start;
|
|
bufptr = buf;
|
|
|
|
while (size > 0) {
|
|
if (!uvtop(tc, uvaddr, &paddr, 0)) {
|
|
error(INFO, "cannot access user stack address: %lx\n\n",
|
|
uvaddr);
|
|
return NULL;
|
|
}
|
|
|
|
cnt = PAGESIZE() - PAGEOFFSET(uvaddr);
|
|
|
|
if (cnt > size)
|
|
cnt = size;
|
|
|
|
if (!readmem(paddr, PHYSADDR, bufptr, cnt,
|
|
"user stack contents", RETURN_ON_ERROR|QUIET)) {
|
|
error(INFO, "cannot access user stack address: %lx\n\n",
|
|
uvaddr);
|
|
return NULL;
|
|
}
|
|
|
|
uvaddr += cnt;
|
|
bufptr += cnt;
|
|
size -= cnt;
|
|
}
|
|
|
|
return bufptr;
|
|
}
|
|
|
|
/*
|
|
* Show the argv and envp strings pointed to by mm_struct->arg_start
|
|
* and mm_struct->env_start. The user addresses need to broken up
|
|
* into physical on a page-per-page basis because we typically are
|
|
* not going to be working in the context of the target task.
|
|
*/
|
|
static void
|
|
show_task_args(struct task_context *tc)
|
|
{
|
|
ulong arg_start, arg_end, env_start, env_end;
|
|
char *buf, *p1, *end;
|
|
int c, d;
|
|
|
|
print_task_header(fp, tc, 0);
|
|
|
|
if (!tc || !tc->mm_struct) { /* probably a kernel thread */
|
|
error(INFO, "no user stack\n\n");
|
|
return;
|
|
}
|
|
|
|
if (!task_mm(tc->task, TRUE))
|
|
return;
|
|
|
|
if (INVALID_MEMBER(mm_struct_arg_start)) {
|
|
MEMBER_OFFSET_INIT(mm_struct_arg_start, "mm_struct", "arg_start");
|
|
MEMBER_OFFSET_INIT(mm_struct_arg_end, "mm_struct", "arg_end");
|
|
MEMBER_OFFSET_INIT(mm_struct_env_start, "mm_struct", "env_start");
|
|
MEMBER_OFFSET_INIT(mm_struct_env_end, "mm_struct", "env_end");
|
|
}
|
|
|
|
arg_start = ULONG(tt->mm_struct + OFFSET(mm_struct_arg_start));
|
|
arg_end = ULONG(tt->mm_struct + OFFSET(mm_struct_arg_end));
|
|
env_start = ULONG(tt->mm_struct + OFFSET(mm_struct_env_start));
|
|
env_end = ULONG(tt->mm_struct + OFFSET(mm_struct_env_end));
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, "arg_start: %lx arg_end: %lx (%ld)\n",
|
|
arg_start, arg_end, arg_end - arg_start);
|
|
fprintf(fp, "env_start: %lx env_end: %lx (%ld)\n",
|
|
env_start, env_end, env_end - env_start);
|
|
}
|
|
|
|
buf = GETBUF(arg_end - arg_start + 1);
|
|
end = read_arg_string(tc, buf, arg_start, arg_end);
|
|
if (!end)
|
|
goto bailout;
|
|
|
|
fprintf(fp, "ARG: ");
|
|
for (p1 = buf, c = 0; p1 < end; p1++) {
|
|
if (*p1 == NULLCHAR) {
|
|
if (c)
|
|
fprintf(fp, " ");
|
|
c = 0;
|
|
} else {
|
|
fprintf(fp, "%c", *p1);
|
|
c++;
|
|
}
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
|
|
buf = GETBUF(env_end - env_start + 1);
|
|
end = read_arg_string(tc, buf, env_start, env_end);
|
|
if (!end)
|
|
goto bailout;
|
|
|
|
fprintf(fp, "\nENV: ");
|
|
for (p1 = buf, c = d = 0; p1 < end; p1++) {
|
|
if (*p1 == NULLCHAR) {
|
|
if (c)
|
|
fprintf(fp, "\n");
|
|
c = 0;
|
|
} else {
|
|
fprintf(fp, "%s%c", !c && (p1 != buf) ? " " : "", *p1);
|
|
c++, d++;
|
|
}
|
|
}
|
|
fprintf(fp, "\n%s", d ? "" : "\n");
|
|
|
|
bailout:
|
|
FREEBUF(buf);
|
|
}
|
|
|
|
char *rlim_names[] = {
|
|
/* 0 */ "CPU",
|
|
/* 1 */ "FSIZE",
|
|
/* 2 */ "DATA",
|
|
/* 3 */ "STACK",
|
|
/* 4 */ "CORE",
|
|
/* 5 */ "RSS",
|
|
/* 6 */ "NPROC",
|
|
/* 7 */ "NOFILE",
|
|
/* 8 */ "MEMLOCK",
|
|
/* 9 */ "AS",
|
|
/* 10 */ "LOCKS",
|
|
/* 11 */ "SIGPENDING",
|
|
/* 12 */ "MSGQUEUE",
|
|
/* 13 */ "NICE",
|
|
/* 14 */ "RTPRIO",
|
|
/* 15 */ "RTTIME",
|
|
NULL,
|
|
};
|
|
|
|
#ifndef RLIM_INFINITY
|
|
#define RLIM_INFINITY (~0UL)
|
|
#endif
|
|
|
|
/*
|
|
* Show the current and maximum rlimit values.
|
|
*/
|
|
static void
|
|
show_task_rlimit(struct task_context *tc)
|
|
{
|
|
int i, j, len1, len2, rlimit_index;
|
|
int in_task_struct, in_signal_struct;
|
|
char *rlimit_buffer;
|
|
ulong *p1, rlim_addr;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
|
|
rlimit_index = 0;
|
|
|
|
if (!VALID_MEMBER(task_struct_rlim) && !VALID_MEMBER(signal_struct_rlim)) {
|
|
MEMBER_OFFSET_INIT(task_struct_rlim, "task_struct", "rlim");
|
|
MEMBER_OFFSET_INIT(signal_struct_rlim, "signal_struct", "rlim");
|
|
STRUCT_SIZE_INIT(rlimit, "rlimit");
|
|
if (!VALID_MEMBER(task_struct_rlim) &&
|
|
!VALID_MEMBER(signal_struct_rlim))
|
|
error(FATAL, "cannot determine rlimit array location\n");
|
|
} else if (!VALID_STRUCT(rlimit))
|
|
error(FATAL, "cannot determine rlimit structure definition\n");
|
|
|
|
in_task_struct = in_signal_struct = FALSE;
|
|
|
|
if (VALID_MEMBER(task_struct_rlim)) {
|
|
rlimit_index = (i = ARRAY_LENGTH(task_struct_rlim)) ?
|
|
i : get_array_length("task_struct.rlim", NULL, 0);
|
|
in_task_struct = TRUE;
|
|
} else if (VALID_MEMBER(signal_struct_rlim)) {
|
|
if (!VALID_MEMBER(task_struct_signal))
|
|
error(FATAL, "cannot determine rlimit array location\n");
|
|
rlimit_index = (i = ARRAY_LENGTH(signal_struct_rlim)) ?
|
|
i : get_array_length("signal_struct.rlim", NULL, 0);
|
|
in_signal_struct = TRUE;
|
|
}
|
|
|
|
if (!rlimit_index)
|
|
error(FATAL, "cannot determine rlimit array size\n");
|
|
|
|
for (i = len1 = 0; i < rlimit_index; i++) {
|
|
if (rlim_names[i] == NULL)
|
|
continue;
|
|
if ((j = strlen(rlim_names[i])) > len1)
|
|
len1 = j;
|
|
}
|
|
len2 = strlen("(unlimited)");
|
|
|
|
rlimit_buffer = GETBUF(rlimit_index * SIZE(rlimit));
|
|
|
|
print_task_header(fp, tc, 0);
|
|
|
|
fill_task_struct(tc->task);
|
|
|
|
if (in_task_struct) {
|
|
BCOPY(tt->task_struct + OFFSET(task_struct_rlim),
|
|
rlimit_buffer, rlimit_index * SIZE(rlimit));
|
|
} else if (in_signal_struct) {
|
|
rlim_addr = ULONG(tt->task_struct + OFFSET(task_struct_signal));
|
|
if (!readmem(rlim_addr + OFFSET(signal_struct_rlim),
|
|
KVADDR, rlimit_buffer, rlimit_index * SIZE(rlimit),
|
|
"signal_struct rlimit array", RETURN_ON_ERROR)) {
|
|
FREEBUF(rlimit_buffer);
|
|
return;
|
|
}
|
|
}
|
|
|
|
fprintf(fp, " %s %s %s\n",
|
|
mkstring(buf1, len1, RJUST, "RLIMIT"),
|
|
mkstring(buf2, len2, CENTER|RJUST, "CURRENT"),
|
|
mkstring(buf3, len2, CENTER|RJUST, "MAXIMUM"));
|
|
|
|
for (p1 = (ulong *)rlimit_buffer, i = 0; i < rlimit_index; i++) {
|
|
fprintf(fp, " %s ", mkstring(buf1, len1, RJUST,
|
|
rlim_names[i] ? rlim_names[i] : "(unknown)"));
|
|
if (*p1 == (ulong)RLIM_INFINITY)
|
|
fprintf(fp, "(unlimited) ");
|
|
else
|
|
fprintf(fp, "%s ", mkstring(buf1, len2,
|
|
CENTER|LJUST|LONG_DEC, MKSTR(*p1)));
|
|
p1++;
|
|
if (*p1 == (ulong)RLIM_INFINITY)
|
|
fprintf(fp, "(unlimited)\n");
|
|
else
|
|
fprintf(fp, "%s\n", mkstring(buf1, len2,
|
|
CENTER|LJUST|LONG_DEC, MKSTR(*p1)));
|
|
p1++;
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
FREEBUF(rlimit_buffer);
|
|
}
|
|
|
|
/*
|
|
* Put either the task_struct address or kernel stack pointer into a string.
|
|
* If the kernel stack pointer is requested, piggy-back on top of the
|
|
* back trace code to avoid having to deal with machine dependencies,
|
|
* live active tasks, and dumpfile panic tasks.
|
|
*/
|
|
static char *
|
|
task_pointer_string(struct task_context *tc, ulong do_kstackp, char *buf)
|
|
{
|
|
struct bt_info bt_info, *bt;
|
|
char buf1[BUFSIZE];
|
|
|
|
if (do_kstackp) {
|
|
bt = &bt_info;
|
|
BZERO(bt, sizeof(struct bt_info));;
|
|
|
|
if (is_task_active(tc->task)) {
|
|
bt->stkptr = 0;
|
|
} else if (VALID_MEMBER(task_struct_thread_esp)) {
|
|
readmem(tc->task + OFFSET(task_struct_thread_esp),
|
|
KVADDR, &bt->stkptr, sizeof(void *),
|
|
"thread_struct esp", FAULT_ON_ERROR);
|
|
} else if (VALID_MEMBER(task_struct_thread_ksp)) {
|
|
readmem(tc->task + OFFSET(task_struct_thread_ksp),
|
|
KVADDR, &bt->stkptr, sizeof(void *),
|
|
"thread_struct ksp", FAULT_ON_ERROR);
|
|
} else if (VALID_MEMBER(task_struct_thread_context_sp)) {
|
|
readmem(tc->task + OFFSET(task_struct_thread_context_sp),
|
|
KVADDR, &bt->stkptr, sizeof(void *),
|
|
"cpu_context sp", FAULT_ON_ERROR);
|
|
} else {
|
|
if ((bt->stackbase = GET_STACKBASE(tc->task))) {
|
|
bt->stacktop = GET_STACKTOP(tc->task);
|
|
bt->task = tc->task;
|
|
bt->tc = tc;
|
|
bt->flags |= BT_KSTACKP;
|
|
back_trace(bt);
|
|
if (bt->stackbuf)
|
|
FREEBUF(bt->stackbuf);
|
|
} else
|
|
bt->stkptr = 0;
|
|
}
|
|
|
|
if (bt->stkptr)
|
|
sprintf(buf, "%s",
|
|
mkstring(buf1, VADDR_PRLEN,
|
|
CENTER|RJUST|LONG_HEX,
|
|
MKSTR(bt->stkptr)));
|
|
else
|
|
sprintf(buf, "%s",
|
|
mkstring(buf1, VADDR_PRLEN, CENTER|RJUST, "--"));
|
|
} else
|
|
sprintf(buf, "%s",
|
|
mkstring(buf1, VADDR_PRLEN,
|
|
CENTER|RJUST|LONG_HEX,
|
|
MKSTR(tc->task)));
|
|
|
|
return buf;
|
|
}
|
|
|
|
|
|
/*
|
|
* Dump the task list ordered by start_time.
|
|
*/
|
|
struct kernel_timeval {
|
|
unsigned int tv_sec;
|
|
unsigned int tv_usec;
|
|
};
|
|
|
|
struct task_start_time {
|
|
struct task_context *tc;
|
|
ulonglong start_time;
|
|
ulong tms_utime;
|
|
ulong tms_stime;
|
|
struct timeval old_utime;
|
|
struct timeval old_stime;
|
|
struct kernel_timeval kutime;
|
|
struct kernel_timeval kstime;
|
|
ulonglong utime;
|
|
ulonglong stime;
|
|
};
|
|
|
|
static void
|
|
show_task_times(struct task_context *tcp, ulong flags)
|
|
{
|
|
int i, tasks, use_kernel_timeval, use_utime_stime;
|
|
struct task_context *tc;
|
|
struct task_start_time *task_start_times, *tsp;
|
|
ulong jiffies, tgid;
|
|
ulonglong jiffies_64;
|
|
char buf1[BUFSIZE];
|
|
|
|
task_start_times = (struct task_start_time *)
|
|
GETBUF(RUNNING_TASKS() * sizeof(struct task_start_time));
|
|
|
|
use_kernel_timeval = STRUCT_EXISTS("kernel_timeval");
|
|
if (VALID_MEMBER(task_struct_utime) &&
|
|
(SIZE(task_struct_utime) ==
|
|
(BITS32() ? sizeof(uint32_t) : sizeof(uint64_t))))
|
|
use_utime_stime = TRUE;
|
|
else
|
|
use_utime_stime = FALSE;
|
|
get_symbol_data("jiffies", sizeof(long), &jiffies);
|
|
if (symbol_exists("jiffies_64"))
|
|
get_uptime(NULL, &jiffies_64);
|
|
tsp = task_start_times;
|
|
tc = tcp ? tcp : FIRST_CONTEXT();
|
|
|
|
for (i = tasks = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
|
|
if ((flags & PS_USER) && is_kernel_thread(tc->task))
|
|
continue;
|
|
if ((flags & PS_KERNEL) && !is_kernel_thread(tc->task))
|
|
continue;
|
|
if (flags & PS_GROUP) {
|
|
tgid = task_tgid(tc->task);
|
|
if (tc->pid != tgid) {
|
|
if (tcp) {
|
|
if (!(tc = tgid_to_context(tgid)))
|
|
return;
|
|
} else
|
|
continue;
|
|
}
|
|
if (hq_entry_exists((ulong)tc))
|
|
return;
|
|
hq_enter((ulong)tc);
|
|
}
|
|
|
|
fill_task_struct(tc->task);
|
|
if (!tt->last_task_read) {
|
|
if (tcp)
|
|
return;
|
|
continue;
|
|
}
|
|
|
|
tsp->tc = tc;
|
|
|
|
if (BITS32() && (SIZE(task_struct_start_time) == 8)) {
|
|
if (start_time_timespec())
|
|
tsp->start_time =
|
|
ULONG(tt->task_struct +
|
|
OFFSET(task_struct_start_time));
|
|
else
|
|
tsp->start_time =
|
|
ULONGLONG(tt->task_struct +
|
|
OFFSET(task_struct_start_time));
|
|
} else {
|
|
start_time_timespec();
|
|
tsp->start_time = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_start_time));
|
|
}
|
|
|
|
if (VALID_MEMBER(task_struct_times)) {
|
|
tsp->tms_utime = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_times) +
|
|
OFFSET(tms_tms_utime));
|
|
tsp->tms_stime = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_times) +
|
|
OFFSET(tms_tms_stime));
|
|
} else if (VALID_MEMBER(task_struct_utime)) {
|
|
if (use_utime_stime) {
|
|
tsp->utime = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_utime));
|
|
tsp->stime = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_stime));
|
|
} else if (use_kernel_timeval) {
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_utime), &tsp->kutime,
|
|
sizeof(struct kernel_timeval));
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_stime), &tsp->kstime,
|
|
sizeof(struct kernel_timeval));
|
|
} else if (VALID_STRUCT(cputime_t)) {
|
|
/* since linux 2.6.11 */
|
|
if (SIZE(cputime_t) == 8) {
|
|
uint64_t utime_64, stime_64;
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_utime),
|
|
&utime_64, 8);
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_stime),
|
|
&stime_64, 8);
|
|
/* convert from micro-sec. to sec. */
|
|
tsp->old_utime.tv_sec = utime_64 / 1000000;
|
|
tsp->old_stime.tv_sec = stime_64 / 1000000;
|
|
} else {
|
|
uint32_t utime_32, stime_32;
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_utime),
|
|
&utime_32, 4);
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_stime),
|
|
&stime_32, 4);
|
|
tsp->old_utime.tv_sec = utime_32;
|
|
tsp->old_stime.tv_sec = stime_32;
|
|
}
|
|
} else {
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_utime),
|
|
&tsp->utime, sizeof(struct timeval));
|
|
BCOPY(tt->task_struct +
|
|
OFFSET(task_struct_stime),
|
|
&tsp->stime, sizeof(struct timeval));
|
|
}
|
|
}
|
|
|
|
tasks++;
|
|
tsp++;
|
|
|
|
if (tcp)
|
|
break;
|
|
}
|
|
|
|
qsort((void *)task_start_times, (size_t)tasks,
|
|
sizeof(struct task_start_time), compare_start_time);
|
|
|
|
for (i = 0, tsp = task_start_times; i < tasks; i++, tsp++) {
|
|
print_task_header(fp, tsp->tc, 0);
|
|
fprintf(fp, " RUN TIME: %s\n", symbol_exists("jiffies_64") ?
|
|
convert_time(convert_start_time(tsp->start_time, jiffies_64), buf1) :
|
|
convert_time(jiffies - tsp->start_time, buf1));
|
|
fprintf(fp, " START TIME: %llu\n", tsp->start_time);
|
|
if (VALID_MEMBER(task_struct_times)) {
|
|
fprintf(fp, " USER TIME: %ld\n", tsp->tms_utime);
|
|
fprintf(fp, " SYSTEM TIME: %ld\n\n", tsp->tms_stime);
|
|
} else if (VALID_MEMBER(task_struct_utime)) {
|
|
if (use_utime_stime) {
|
|
fprintf(fp, " UTIME: %lld\n",
|
|
(ulonglong)tsp->utime);
|
|
fprintf(fp, " STIME: %lld\n\n",
|
|
(ulonglong)tsp->stime);
|
|
} else if (use_kernel_timeval) {
|
|
fprintf(fp, " USER TIME: %d\n",
|
|
tsp->kutime.tv_sec);
|
|
fprintf(fp, " SYSTEM TIME: %d\n\n",
|
|
tsp->kstime.tv_sec);
|
|
} else {
|
|
fprintf(fp, " USER TIME: %ld\n",
|
|
tsp->old_utime.tv_sec);
|
|
fprintf(fp, " SYSTEM TIME: %ld\n\n",
|
|
tsp->old_stime.tv_sec);
|
|
}
|
|
}
|
|
}
|
|
FREEBUF(task_start_times);
|
|
}
|
|
|
|
static int
|
|
start_time_timespec(void)
|
|
{
|
|
switch(tt->flags & (TIMESPEC | NO_TIMESPEC | START_TIME_NSECS))
|
|
{
|
|
case TIMESPEC:
|
|
return TRUE;
|
|
case NO_TIMESPEC:
|
|
case START_TIME_NSECS:
|
|
return FALSE;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
tt->flags |= NO_TIMESPEC;
|
|
|
|
if (VALID_MEMBER(task_struct_start_time) &&
|
|
STREQ(MEMBER_TYPE_NAME("task_struct", "start_time"), "timespec")) {
|
|
tt->flags &= ~NO_TIMESPEC;
|
|
tt->flags |= TIMESPEC;
|
|
}
|
|
|
|
if ((tt->flags & NO_TIMESPEC) && (SIZE(task_struct_start_time) == 8)) {
|
|
tt->flags &= ~NO_TIMESPEC;
|
|
tt->flags |= START_TIME_NSECS;
|
|
}
|
|
|
|
return (tt->flags & TIMESPEC ? TRUE : FALSE);
|
|
}
|
|
|
|
static ulonglong
|
|
convert_start_time(ulonglong start_time, ulonglong current)
|
|
{
|
|
ulong tmp1, tmp2;
|
|
ulonglong wrapped;
|
|
|
|
switch(tt->flags & (TIMESPEC | NO_TIMESPEC | START_TIME_NSECS))
|
|
{
|
|
case START_TIME_NSECS:
|
|
start_time /= 1000000000ULL; /* FALLTHROUGH */
|
|
case TIMESPEC:
|
|
if ((start_time * (ulonglong)machdep->hz) > current)
|
|
return 0;
|
|
else
|
|
return current - (start_time * (ulonglong)machdep->hz);
|
|
|
|
case NO_TIMESPEC:
|
|
if (THIS_KERNEL_VERSION >= LINUX(2,6,0)) {
|
|
wrapped = (start_time & 0xffffffff00000000ULL);
|
|
if (wrapped) {
|
|
wrapped -= 0x100000000ULL;
|
|
start_time &= 0x00000000ffffffffULL;
|
|
start_time |= wrapped;
|
|
start_time += (ulonglong)(300*machdep->hz);
|
|
} else {
|
|
tmp1 = (ulong)(uint)(-300*machdep->hz);
|
|
tmp2 = (ulong)start_time;
|
|
start_time = (ulonglong)(tmp2 - tmp1);
|
|
}
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return start_time;
|
|
}
|
|
|
|
/*
|
|
* 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_start_time(const void *v1, const void *v2)
|
|
{
|
|
struct task_start_time *t1, *t2;
|
|
|
|
t1 = (struct task_start_time *)v1;
|
|
t2 = (struct task_start_time *)v2;
|
|
|
|
return (t1->start_time < t2->start_time ? -1 :
|
|
t1->start_time == t2->start_time ? 0 : 1);
|
|
}
|
|
|
|
static ulong
|
|
parent_of(ulong task)
|
|
{
|
|
long offset;
|
|
ulong parent;
|
|
|
|
if (VALID_MEMBER(task_struct_parent))
|
|
offset = OFFSET(task_struct_parent);
|
|
else
|
|
offset = OFFSET(task_struct_p_pptr);
|
|
|
|
readmem(task+offset, KVADDR, &parent,
|
|
sizeof(void *), "task parent", FAULT_ON_ERROR);
|
|
|
|
return parent;
|
|
}
|
|
|
|
/*
|
|
* Dump the parental hierarchy of a task.
|
|
*/
|
|
static void
|
|
parent_list(ulong task)
|
|
{
|
|
int i, j, cnt;
|
|
struct task_context *tc;
|
|
char *buffer;
|
|
long reserved;
|
|
ulong *task_list, child, parent;
|
|
|
|
reserved = 100 * sizeof(ulong);
|
|
buffer = GETBUF(reserved);
|
|
task_list = (ulong *)buffer;
|
|
child = task_list[0] = task;
|
|
parent = parent_of(child);
|
|
cnt = 1;
|
|
|
|
while (child != parent) {
|
|
child = task_list[cnt++] = parent;
|
|
parent = parent_of(child);
|
|
if ((cnt * sizeof(ulong)) == reserved) {
|
|
RESIZEBUF(buffer, reserved, reserved * 2);
|
|
reserved *= 2;
|
|
task_list = (ulong *)buffer;
|
|
}
|
|
}
|
|
|
|
for (i = cnt-1, j = 0; i >= 0; i--, j++) {
|
|
INDENT(j);
|
|
tc = task_to_context(task_list[i]);
|
|
if (tc)
|
|
print_task_header(fp, tc, 0);
|
|
}
|
|
|
|
FREEBUF(task_list);
|
|
}
|
|
|
|
/*
|
|
* Dump the children of a task.
|
|
*/
|
|
static void
|
|
child_list(ulong task)
|
|
{
|
|
int i;
|
|
int cnt;
|
|
struct task_context *tc;
|
|
|
|
tc = task_to_context(task);
|
|
print_task_header(fp, tc, 0);
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = cnt = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (tc->ptask == task) {
|
|
INDENT(2);
|
|
print_task_header(fp, tc, 0);
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
if (!cnt)
|
|
fprintf(fp, " (no children)\n");
|
|
}
|
|
|
|
/*
|
|
* Dump the children of a task.
|
|
*/
|
|
static void
|
|
show_tgid_list(ulong task)
|
|
{
|
|
int i;
|
|
int cnt;
|
|
struct task_context *tc;
|
|
ulong tgid;
|
|
|
|
tc = task_to_context(task);
|
|
tgid = task_tgid(task);
|
|
|
|
if (tc->pid != tgid) {
|
|
if (pc->curcmd_flags & TASK_SPECIFIED) {
|
|
if (!(tc = tgid_to_context(tgid)))
|
|
return;
|
|
task = tc->task;
|
|
} else
|
|
return;
|
|
}
|
|
|
|
if ((tc->pid == 0) && (pc->curcmd_flags & IDLE_TASK_SHOWN))
|
|
return;
|
|
|
|
print_task_header(fp, tc, 0);
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = cnt = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (tc->task == task)
|
|
continue;
|
|
|
|
if (task_tgid(tc->task) == tgid) {
|
|
INDENT(2);
|
|
print_task_header(fp, tc, 0);
|
|
cnt++;
|
|
if (tc->pid == 0)
|
|
pc->curcmd_flags |= IDLE_TASK_SHOWN;
|
|
}
|
|
}
|
|
|
|
if (!cnt)
|
|
fprintf(fp, " (no threads)\n");
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* Return the first task found that belongs to a pid.
|
|
*/
|
|
ulong
|
|
pid_to_task(ulong pid)
|
|
{
|
|
int i;
|
|
struct task_context *tc;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++)
|
|
if (tc->pid == pid)
|
|
return(tc->task);
|
|
|
|
return((ulong)NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the pid of a task.
|
|
*/
|
|
ulong
|
|
task_to_pid(ulong task)
|
|
{
|
|
struct task_context *tc;
|
|
|
|
tc = task_to_context(task);
|
|
if (tc != NULL)
|
|
return tc->pid;
|
|
|
|
return(NO_PID);
|
|
}
|
|
|
|
/*
|
|
* Verify whether a task exists.
|
|
*/
|
|
int
|
|
task_exists(ulong task)
|
|
{
|
|
int i;
|
|
struct task_context *tc;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++)
|
|
if (tc->task == task)
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Return the task_context structure of a task.
|
|
*/
|
|
struct task_context *
|
|
task_to_context(ulong task)
|
|
{
|
|
struct task_context key, *tc, **found;
|
|
int i;
|
|
|
|
/* Binary search the context_by_task array. */
|
|
if (tt->flags & INDEXED_CONTEXTS) {
|
|
key.task = task;
|
|
tc = &key;
|
|
found = bsearch(&tc, tt->context_by_task, tt->running_tasks,
|
|
sizeof(*tt->context_by_task), sort_by_task);
|
|
return found ? *found : NULL;
|
|
}
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++)
|
|
if (tc->task == task)
|
|
return tc;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Return a tgid's parent task_context structure.
|
|
*/
|
|
struct task_context *
|
|
tgid_to_context(ulong parent_tgid)
|
|
{
|
|
int i;
|
|
struct task_context *tc;
|
|
ulong tgid;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
tgid = task_tgid(tc->task);
|
|
if ((tgid == parent_tgid) && (tgid == tc->pid))
|
|
return tc;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the task_context structure of the first task found with a pid,
|
|
* while linking all tasks that have that pid.
|
|
*/
|
|
struct task_context *
|
|
pid_to_context(ulong pid)
|
|
{
|
|
int i;
|
|
struct task_context *tc, *firsttc, *lasttc;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
firsttc = lasttc = NULL;
|
|
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (tc->pid == pid) {
|
|
if (!firsttc)
|
|
firsttc = tc;
|
|
if (lasttc)
|
|
lasttc->tc_next = tc;
|
|
tc->tc_next = NULL;
|
|
lasttc = tc;
|
|
}
|
|
}
|
|
|
|
return firsttc;
|
|
}
|
|
|
|
|
|
/*
|
|
* Verify whether a pid exists, and if found, linking all tasks having the pid.
|
|
*/
|
|
int
|
|
pid_exists(ulong pid)
|
|
{
|
|
int i;
|
|
struct task_context *tc, *lasttc;
|
|
int count;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
count = 0;
|
|
lasttc = NULL;
|
|
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (tc->pid == pid) {
|
|
count++;
|
|
if (lasttc)
|
|
lasttc->tc_next = tc;
|
|
tc->tc_next = NULL;
|
|
lasttc = tc;
|
|
}
|
|
}
|
|
|
|
return(count);
|
|
}
|
|
|
|
/*
|
|
* Translate a stack pointer to a task, dealing with possible split.
|
|
* If that doesn't work, check the hardirq_stack and softirq_stack.
|
|
*
|
|
* TODO: This function can be optimized by getting min & max of the
|
|
* stack range in first pass and use these values against the
|
|
* given SP to decide whether or not to proceed with stack lookup.
|
|
*/
|
|
ulong
|
|
stkptr_to_task(ulong sp)
|
|
{
|
|
int i, c;
|
|
struct task_context *tc;
|
|
struct bt_info bt_info, *bt;
|
|
|
|
if (!sp)
|
|
return NO_TASK;
|
|
|
|
bt = &bt_info;
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
bt->stackbase = GET_STACKBASE(tc->task);
|
|
bt->stacktop = GET_STACKTOP(tc->task);
|
|
if (INSTACK(sp, bt))
|
|
return tc->task;
|
|
}
|
|
|
|
if (!(tt->flags & IRQSTACKS))
|
|
return NO_TASK;
|
|
|
|
bt = &bt_info;
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
for (c = 0; c < NR_CPUS; c++) {
|
|
if (tt->hardirq_ctx[c]) {
|
|
bt->stackbase = tt->hardirq_ctx[c];
|
|
bt->stacktop = bt->stackbase +
|
|
SIZE(irq_ctx);
|
|
if (INSTACK(sp, bt) &&
|
|
(tt->hardirq_tasks[c] == tc->task))
|
|
return tc->task;
|
|
}
|
|
if (tt->softirq_ctx[c]) {
|
|
bt->stackbase = tt->softirq_ctx[c];
|
|
bt->stacktop = bt->stackbase +
|
|
SIZE(irq_ctx);
|
|
if (INSTACK(sp, bt) &&
|
|
(tt->softirq_tasks[c] == tc->task))
|
|
return tc->task;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NO_TASK;
|
|
}
|
|
|
|
/*
|
|
* Translate a task pointer to its thread_info.
|
|
*/
|
|
ulong
|
|
task_to_thread_info(ulong task)
|
|
{
|
|
int i;
|
|
struct task_context *tc;
|
|
|
|
if (!(tt->flags & THREAD_INFO))
|
|
error(FATAL,
|
|
"task_to_thread_info: thread_info struct does not exist!\n");
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (tc->task == task)
|
|
return tc->thread_info;
|
|
}
|
|
return(error(FATAL, "task does not exist: %lx\n", task));
|
|
}
|
|
|
|
/*
|
|
* Translate a task address to its stack base, dealing with potential split.
|
|
*/
|
|
ulong
|
|
task_to_stackbase(ulong task)
|
|
{
|
|
ulong stackbase;
|
|
|
|
if (tt->flags & THREAD_INFO_IN_TASK) {
|
|
readmem(task + OFFSET(task_struct_stack), KVADDR, &stackbase,
|
|
sizeof(void *), "task_struct.stack", FAULT_ON_ERROR);
|
|
return stackbase;
|
|
} else if (tt->flags & THREAD_INFO)
|
|
return task_to_thread_info(task);
|
|
else
|
|
return (task & ~(STACKSIZE()-1));
|
|
}
|
|
|
|
/*
|
|
* Try to translate a decimal or hexadecimal string into a task or pid,
|
|
* failing if no task or pid exists, or if there is ambiguity between
|
|
* the decimal and hexadecimal translations. However, if the value could
|
|
* be a decimal PID and a hexadecimal PID of two different processes, then
|
|
* default to the decimal value.
|
|
*
|
|
* This was added in preparation for overlapping, zero-based, user and kernel
|
|
* virtual addresses on s390 and s390x, allowing for the entry of ambiguous
|
|
* decimal/hexadecimal task address values without the leading "0x".
|
|
* It should be used in lieu of "stol" when parsing for task/pid arguments.
|
|
*/
|
|
int
|
|
str_to_context(char *string, ulong *value, struct task_context **tcp)
|
|
{
|
|
ulong dvalue, hvalue;
|
|
int found, type;
|
|
char *s;
|
|
struct task_context *tc_dp, *tc_dt, *tc_hp, *tc_ht;
|
|
|
|
if (string == NULL) {
|
|
error(INFO, "received NULL string\n");
|
|
return STR_INVALID;
|
|
}
|
|
|
|
s = string;
|
|
dvalue = hvalue = BADADDR;
|
|
|
|
if (decimal(s, 0))
|
|
dvalue = dtol(s, RETURN_ON_ERROR, NULL);
|
|
|
|
if (hexadecimal(s, 0)) {
|
|
if (STRNEQ(s, "0x") || STRNEQ(s, "0X"))
|
|
s += 2;
|
|
if (strlen(s) <= MAX_HEXADDR_STRLEN)
|
|
hvalue = htol(s, RETURN_ON_ERROR, NULL);
|
|
}
|
|
|
|
found = 0;
|
|
tc_dp = tc_dt = tc_hp = tc_ht = NULL;
|
|
type = STR_INVALID;
|
|
|
|
if (dvalue != BADADDR) {
|
|
if ((tc_dp = pid_to_context(dvalue)))
|
|
found++;
|
|
if ((tc_dt = task_to_context(dvalue)))
|
|
found++;
|
|
}
|
|
|
|
if ((hvalue != BADADDR) && (dvalue != hvalue)) {
|
|
if ((tc_hp = pid_to_context(hvalue)))
|
|
found++;
|
|
if ((tc_ht = task_to_context(hvalue)))
|
|
found++;
|
|
}
|
|
|
|
switch (found)
|
|
{
|
|
case 2:
|
|
if (tc_dp && tc_hp) {
|
|
*tcp = tc_dp;
|
|
*value = dvalue;
|
|
type = STR_PID;
|
|
}
|
|
break;
|
|
|
|
case 1:
|
|
if (tc_dp) {
|
|
*tcp = tc_dp;
|
|
*value = dvalue;
|
|
type = STR_PID;
|
|
}
|
|
|
|
if (tc_dt) {
|
|
*tcp = tc_dt;
|
|
*value = dvalue;
|
|
type = STR_TASK;
|
|
}
|
|
|
|
if (tc_hp) {
|
|
*tcp = tc_hp;
|
|
*value = hvalue;
|
|
type = STR_PID;
|
|
}
|
|
|
|
if (tc_ht) {
|
|
*tcp = tc_ht;
|
|
*value = hvalue;
|
|
type = STR_TASK;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return type;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return the task if the vaddr is part of a task's task_struct.
|
|
*/
|
|
ulong
|
|
vaddr_in_task_struct(ulong vaddr)
|
|
{
|
|
int i;
|
|
struct task_context *tc;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if ((vaddr >= tc->task) &&
|
|
(vaddr < (tc->task + SIZE(task_struct))))
|
|
return tc->task;
|
|
}
|
|
|
|
return NO_TASK;
|
|
}
|
|
|
|
/*
|
|
* Verify whether any task is running a command.
|
|
*/
|
|
int
|
|
comm_exists(char *s)
|
|
{
|
|
int i, cnt;
|
|
struct task_context *tc;
|
|
char buf[TASK_COMM_LEN];
|
|
|
|
strlcpy(buf, s, TASK_COMM_LEN);
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = cnt = 0; i < RUNNING_TASKS(); i++, tc++)
|
|
if (STREQ(tc->comm, buf))
|
|
cnt++;
|
|
|
|
return cnt;
|
|
}
|
|
|
|
/*
|
|
* Set a new context. If only a pid is passed, the first task found with
|
|
* that pid is selected.
|
|
*/
|
|
int
|
|
set_context(ulong task, ulong pid)
|
|
{
|
|
int i;
|
|
struct task_context *tc;
|
|
int found;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
|
|
for (i = 0, found = FALSE; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (task && (tc->task == task)) {
|
|
found = TRUE;
|
|
break;
|
|
} else if (pid == tc->pid) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found) {
|
|
CURRENT_CONTEXT() = tc;
|
|
return TRUE;
|
|
} else {
|
|
if (task)
|
|
error(INFO, "cannot set context for task: %lx\n", task);
|
|
else
|
|
error(INFO, "cannot set context for pid: %d\n", pid);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check whether the panic was determined to be caused by a "sys -panic"
|
|
* command. If so, fix the task_context's pid despite what the task_struct
|
|
* says.
|
|
*/
|
|
#define CONTEXT_ADJUSTED (1)
|
|
#define CONTEXT_ERRONEOUS (2)
|
|
|
|
static int
|
|
panic_context_adjusted(struct task_context *tc)
|
|
{
|
|
pid_t pgrp, tgid;
|
|
char buf[BUFSIZE];
|
|
|
|
if (!(DUMPFILE() && (tc == task_to_context(tt->panic_task)) &&
|
|
(tc->pid == 0) && STRNEQ(tc->comm, pc->program_name) &&
|
|
strstr(get_panicmsg(buf), "Attempted to kill the idle task")))
|
|
return 0;
|
|
|
|
if (INVALID_MEMBER(task_struct_pgrp) ||
|
|
INVALID_MEMBER(task_struct_tgid))
|
|
return CONTEXT_ERRONEOUS;
|
|
|
|
fill_task_struct(tc->task);
|
|
|
|
pgrp = tt->last_task_read ?
|
|
UINT(tt->task_struct + OFFSET(task_struct_pgrp)) : 0;
|
|
tgid = tt->last_task_read ?
|
|
UINT(tt->task_struct + OFFSET(task_struct_tgid)) : 0;
|
|
|
|
if (pgrp && tgid && (pgrp == tgid) && !pid_exists((ulong)pgrp)) {
|
|
tc->pid = (ulong)pgrp;
|
|
return CONTEXT_ADJUSTED;
|
|
}
|
|
|
|
return CONTEXT_ERRONEOUS;
|
|
}
|
|
|
|
/*
|
|
* Display a task context.
|
|
*/
|
|
|
|
void
|
|
show_context(struct task_context *tc)
|
|
{
|
|
char buf[BUFSIZE];
|
|
char *p1;
|
|
int adjusted, cnt, indent;
|
|
|
|
adjusted = pc->flags & RUNTIME ? 0 : panic_context_adjusted(tc);
|
|
indent = pc->flags & RUNTIME ? 0 : 5;
|
|
|
|
INDENT(indent);
|
|
fprintf(fp, " PID: %ld\n", tc->pid);
|
|
INDENT(indent);
|
|
fprintf(fp, "COMMAND: \"%s\"\n", tc->comm);
|
|
INDENT(indent);
|
|
fprintf(fp, " TASK: %lx ", tc->task);
|
|
if ((machdep->flags & (INIT|MCA)) && (tc->pid == 0))
|
|
cnt = comm_exists(tc->comm);
|
|
else
|
|
cnt = TASKS_PER_PID(tc->pid);
|
|
if (cnt > 1)
|
|
fprintf(fp, "(1 of %d) ", cnt);
|
|
if (tt->flags & THREAD_INFO)
|
|
fprintf(fp, "[THREAD_INFO: %lx]", tc->thread_info);
|
|
fprintf(fp, "\n");
|
|
INDENT(indent);
|
|
fprintf(fp, " CPU: %s\n", task_cpu(tc->processor, buf, VERBOSE));
|
|
INDENT(indent);
|
|
fprintf(fp, " STATE: %s ",
|
|
task_state_string(tc->task, buf, VERBOSE));
|
|
if (is_task_active(tc->task)) {
|
|
if (machdep->flags & HWRESET)
|
|
fprintf(fp, "(HARDWARE RESET)");
|
|
else if ((pc->flags & SYSRQ) && (tc->task == tt->panic_task))
|
|
fprintf(fp, "(SYSRQ)");
|
|
else if (machdep->flags & INIT)
|
|
fprintf(fp, "(INIT)");
|
|
else if ((machdep->flags & MCA) && (tc->task == tt->panic_task))
|
|
fprintf(fp, "(MCA)");
|
|
else if ((tc->processor >= 0) &&
|
|
(tc->processor < NR_CPUS) &&
|
|
(kt->cpu_flags[tc->processor] & NMI))
|
|
fprintf(fp, "(NMI)");
|
|
else if ((tc->task == tt->panic_task) &&
|
|
XENDUMP_DUMPFILE() && (kt->xen_flags & XEN_SUSPEND))
|
|
fprintf(fp, "(SUSPEND)");
|
|
else if ((tc->task == tt->panic_task) && !(pc->flags2 & SNAP))
|
|
fprintf(fp, "(PANIC)");
|
|
else
|
|
fprintf(fp, "(ACTIVE)");
|
|
}
|
|
|
|
if (!(pc->flags & RUNTIME) && !ACTIVE() &&
|
|
(tt->flags & PANIC_TASK_NOT_FOUND) &&
|
|
!SYSRQ_TASK(tc->task)) {
|
|
fprintf(fp, "\n"); INDENT(indent);
|
|
if (machine_type("S390") || machine_type("S390X"))
|
|
fprintf(fp, " INFO: no panic task found");
|
|
else if (tt->panic_processor >= 0)
|
|
fprintf(fp,
|
|
"WARNING: reported panic task %lx not found",
|
|
tt->panic_threads[tt->panic_processor]);
|
|
else
|
|
fprintf(fp, "WARNING: panic task not found");
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
|
|
if (pc->flags & RUNTIME)
|
|
return;
|
|
|
|
/*
|
|
* Dump any pre-first-prompt messages here.
|
|
*/
|
|
cnt = 0;
|
|
|
|
if (pc->flags & NAMELIST_UNLINKED) {
|
|
strcpy(buf, pc->namelist);
|
|
if ((p1 = strstr(buf, "@")))
|
|
*p1 = NULLCHAR;
|
|
fprintf(fp,
|
|
"%sNOTE: To save the remote \"%s\" locally,\n enter: \"save kernel\"\n",
|
|
cnt++ ? "" : "\n", buf);
|
|
}
|
|
|
|
if (REMOTE_DUMPFILE())
|
|
fprintf(fp,
|
|
"%sNOTE: To save the remote \"%s\" locally,\n enter: \"save dumpfile\"\n",
|
|
cnt++ ? "" : "\n",
|
|
basename(pc->server_memsrc));
|
|
|
|
/*
|
|
* If this panic was caused by a "sys -panic" command, issue the
|
|
* proper warning message.
|
|
*/
|
|
switch (adjusted)
|
|
{
|
|
case CONTEXT_ADJUSTED:
|
|
fprintf(fp,
|
|
"%sNOTE: The \"%s\" task_struct will erroneously show a p_pid of 0\n",
|
|
cnt++ ? "" : "\n", tc->comm);
|
|
break;
|
|
|
|
case CONTEXT_ERRONEOUS:
|
|
fprintf(fp,
|
|
"%sWARNING: The \"%s\" context will erroneously show a PID of 0\n",
|
|
cnt++ ? "" : "\n", tc->comm);
|
|
break;
|
|
}
|
|
|
|
if (!(pc->flags & RUNTIME) && (tt->flags & ACTIVE_ONLY))
|
|
error(WARNING,
|
|
"\nonly the active tasks on each cpu are being tracked\n");
|
|
}
|
|
|
|
|
|
/*
|
|
* Translate a task_struct state value into a long (verbose), or short string,
|
|
* or if requested, just pass back the state value.
|
|
*/
|
|
|
|
#define TASK_STATE_UNINITIALIZED (-1)
|
|
|
|
static long _RUNNING_ = TASK_STATE_UNINITIALIZED;
|
|
static long _INTERRUPTIBLE_ = TASK_STATE_UNINITIALIZED;
|
|
static long _UNINTERRUPTIBLE_ = TASK_STATE_UNINITIALIZED;
|
|
static long _STOPPED_ = TASK_STATE_UNINITIALIZED;
|
|
static long _TRACING_STOPPED_ = TASK_STATE_UNINITIALIZED;
|
|
long _ZOMBIE_ = TASK_STATE_UNINITIALIZED; /* also used by IS_ZOMBIE() */
|
|
static long _DEAD_ = TASK_STATE_UNINITIALIZED;
|
|
static long _SWAPPING_ = TASK_STATE_UNINITIALIZED;
|
|
static long _EXCLUSIVE_ = TASK_STATE_UNINITIALIZED;
|
|
static long _WAKEKILL_ = TASK_STATE_UNINITIALIZED;
|
|
static long _WAKING_ = TASK_STATE_UNINITIALIZED;
|
|
static long _NONINTERACTIVE_ = TASK_STATE_UNINITIALIZED;
|
|
static long _PARKED_ = TASK_STATE_UNINITIALIZED;
|
|
static long _NOLOAD_ = TASK_STATE_UNINITIALIZED;
|
|
static long _NEW_ = TASK_STATE_UNINITIALIZED;
|
|
|
|
#define valid_task_state(X) ((X) != TASK_STATE_UNINITIALIZED)
|
|
|
|
static void
|
|
dump_task_states(void)
|
|
{
|
|
int hi, lo;
|
|
|
|
fprintf(fp, " RUNNING: %3ld (0x%lx)\n",
|
|
_RUNNING_, _RUNNING_);
|
|
|
|
fprintf(fp, " INTERRUPTIBLE: %3ld (0x%lx)\n",
|
|
_INTERRUPTIBLE_, _INTERRUPTIBLE_);
|
|
|
|
fprintf(fp, " UNINTERRUPTIBLE: %3ld (0x%lx)\n",
|
|
_UNINTERRUPTIBLE_, _UNINTERRUPTIBLE_);
|
|
|
|
fprintf(fp, " STOPPED: %3ld (0x%lx)\n",
|
|
_STOPPED_, _STOPPED_);
|
|
|
|
if (valid_task_state(_TRACING_STOPPED_)) {
|
|
if (count_bits_long(_TRACING_STOPPED_) > 1) {
|
|
lo = lowest_bit_long(_TRACING_STOPPED_);
|
|
hi = highest_bit_long(_TRACING_STOPPED_);
|
|
fprintf(fp,
|
|
" TRACING_STOPPED: %3d and %d (0x%x and 0x%x)\n",
|
|
1<<lo, 1<<hi, 1<<lo, 1<<hi);
|
|
} else
|
|
fprintf(fp, " TRACING_STOPPED: %3ld (0x%lx)\n",
|
|
_TRACING_STOPPED_, _TRACING_STOPPED_);
|
|
}
|
|
|
|
fprintf(fp, " ZOMBIE: %3ld (0x%lx)\n",
|
|
_ZOMBIE_, _ZOMBIE_);
|
|
|
|
if (count_bits_long(_DEAD_) > 1) {
|
|
lo = lowest_bit_long(_DEAD_);
|
|
hi = highest_bit_long(_DEAD_);
|
|
fprintf(fp, " DEAD: %3d and %d (0x%x and 0x%x)\n",
|
|
1<<lo, 1<<hi, 1<<lo, 1<<hi);
|
|
} else
|
|
fprintf(fp, " DEAD: %3ld (0x%lx)\n",
|
|
_DEAD_, _DEAD_);
|
|
|
|
if (valid_task_state(_NONINTERACTIVE_))
|
|
fprintf(fp, " NONINTERACTIVE: %3ld (0x%lx)\n",
|
|
_NONINTERACTIVE_, _NONINTERACTIVE_);
|
|
|
|
if (valid_task_state(_SWAPPING_))
|
|
fprintf(fp, " SWAPPING: %3ld (0x%lx)\n",
|
|
_SWAPPING_, _SWAPPING_);
|
|
|
|
if (valid_task_state(_EXCLUSIVE_))
|
|
fprintf(fp, " EXCLUSIVE: %3ld (0x%lx)\n",
|
|
_EXCLUSIVE_, _EXCLUSIVE_);
|
|
|
|
if (valid_task_state(_WAKEKILL_) && valid_task_state(_WAKING_)) {
|
|
if (_WAKEKILL_ < _WAKING_) {
|
|
fprintf(fp, " WAKEKILL: %3ld (0x%lx)\n",
|
|
_WAKEKILL_, _WAKEKILL_);
|
|
fprintf(fp, " WAKING: %3ld (0x%lx)\n",
|
|
_WAKING_, _WAKING_);
|
|
} else {
|
|
fprintf(fp, " WAKING: %3ld (0x%lx)\n",
|
|
_WAKING_, _WAKING_);
|
|
fprintf(fp, " WAKEKILL: %3ld (0x%lx)\n",
|
|
_WAKEKILL_, _WAKEKILL_);
|
|
}
|
|
}
|
|
|
|
if (valid_task_state(_PARKED_))
|
|
fprintf(fp, " PARKED: %3ld (0x%lx)\n",
|
|
_PARKED_, _PARKED_);
|
|
|
|
if (valid_task_state(_NOLOAD_))
|
|
fprintf(fp, " NOLOAD: %3ld (0x%lx)\n",
|
|
_NOLOAD_, _NOLOAD_);
|
|
|
|
if (valid_task_state(_NEW_))
|
|
fprintf(fp, " NEW: %3ld (0x%lx)\n",
|
|
_NEW_, _NEW_);
|
|
}
|
|
|
|
|
|
/*
|
|
* Initialize the task state fields based upon the kernel's task_state_array
|
|
* string table.
|
|
*/
|
|
static void
|
|
initialize_task_state(void)
|
|
{
|
|
int i, len;
|
|
ulong bitpos;
|
|
ulong str, task_state_array;
|
|
char buf[BUFSIZE];
|
|
|
|
if (!symbol_exists("task_state_array") ||
|
|
!readmem(task_state_array = symbol_value("task_state_array"),
|
|
KVADDR, &str, sizeof(void *),
|
|
"task_state_array", RETURN_ON_ERROR)) {
|
|
old_defaults:
|
|
_RUNNING_ = 0;
|
|
_INTERRUPTIBLE_ = 1;
|
|
_UNINTERRUPTIBLE_ = 2;
|
|
_ZOMBIE_ = 4;
|
|
_STOPPED_ = 8;
|
|
_SWAPPING_ = 16;
|
|
_EXCLUSIVE_ = 32;
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* If the later version of stat_nam[] array exists that contains
|
|
* WAKING, WAKEKILL and PARKED, use it instead of task_state_array[].
|
|
* Available since kernel version 2.6.33 to 4.13.
|
|
*/
|
|
if (((len = get_array_length("stat_nam", NULL, 0)) > 0) &&
|
|
read_string(symbol_value("stat_nam"), buf, BUFSIZE-1) &&
|
|
ascii_string(buf) && (strlen(buf) > strlen("RSDTtZX"))) {
|
|
for (i = 0; i < strlen(buf); i++) {
|
|
switch (buf[i])
|
|
{
|
|
case 'R':
|
|
_RUNNING_ = i;
|
|
break;
|
|
case 'S':
|
|
_INTERRUPTIBLE_ = i;
|
|
break;
|
|
case 'D':
|
|
_UNINTERRUPTIBLE_ = (1 << (i-1));
|
|
break;
|
|
case 'T':
|
|
_STOPPED_ = (1 << (i-1));
|
|
break;
|
|
case 't':
|
|
_TRACING_STOPPED_ = (1 << (i-1));
|
|
break;
|
|
case 'X':
|
|
if (_DEAD_ == UNINITIALIZED)
|
|
_DEAD_ = (1 << (i-1));
|
|
else
|
|
_DEAD_ |= (1 << (i-1));
|
|
break;
|
|
case 'Z':
|
|
_ZOMBIE_ = (1 << (i-1));
|
|
break;
|
|
case 'x':
|
|
if (_DEAD_ == UNINITIALIZED)
|
|
_DEAD_ = (1 << (i-1));
|
|
else
|
|
_DEAD_ |= (1 << (i-1));
|
|
break;
|
|
case 'K':
|
|
_WAKEKILL_ = (1 << (i-1));
|
|
break;
|
|
case 'W':
|
|
_WAKING_ = (1 << (i-1));
|
|
break;
|
|
case 'P':
|
|
_PARKED_ = (1 << (i-1));
|
|
break;
|
|
case 'N':
|
|
_NOLOAD_ = (1 << (i-1));
|
|
break;
|
|
case 'n':
|
|
_NEW_ = (1 << (i-1));
|
|
break;
|
|
}
|
|
}
|
|
|
|
goto done_states;
|
|
}
|
|
|
|
if ((len = get_array_length("task_state_array", NULL, 0)) <= 0)
|
|
goto old_defaults;
|
|
bitpos = 0;
|
|
for (i = 0; i < len; i++) {
|
|
if (!read_string(str, buf, BUFSIZE-1))
|
|
break;
|
|
|
|
if (CRASHDEBUG(3))
|
|
fprintf(fp, "%s%s[%d][%s]\n", bitpos ? "" : "\n",
|
|
i < 10 ? " " : "", i, buf);
|
|
|
|
if (strstr(buf, "(running)"))
|
|
_RUNNING_ = bitpos;
|
|
else if (strstr(buf, "(sleeping)"))
|
|
_INTERRUPTIBLE_ = bitpos;
|
|
else if (strstr(buf, "(disk sleep)"))
|
|
_UNINTERRUPTIBLE_ = bitpos;
|
|
else if (strstr(buf, "(stopped)"))
|
|
_STOPPED_ = bitpos;
|
|
else if (strstr(buf, "(zombie)"))
|
|
_ZOMBIE_ = bitpos;
|
|
else if (strstr(buf, "(dead)")) {
|
|
if (_DEAD_ == TASK_STATE_UNINITIALIZED)
|
|
_DEAD_ = bitpos;
|
|
else
|
|
_DEAD_ |= bitpos;
|
|
} else if (strstr(buf, "(swapping)")) /* non-existent? */
|
|
_SWAPPING_ = bitpos;
|
|
else if (strstr(buf, "(tracing stop)")) {
|
|
if (_TRACING_STOPPED_ == TASK_STATE_UNINITIALIZED)
|
|
_TRACING_STOPPED_ = bitpos;
|
|
else
|
|
_TRACING_STOPPED_ |= bitpos;
|
|
} else if (strstr(buf, "(wakekill)"))
|
|
_WAKEKILL_ = bitpos;
|
|
else if (strstr(buf, "(waking)"))
|
|
_WAKING_ = bitpos;
|
|
else if (strstr(buf, "(parked)"))
|
|
_PARKED_ = bitpos;
|
|
|
|
if (!bitpos)
|
|
bitpos = 1;
|
|
else
|
|
bitpos = bitpos << 1;
|
|
|
|
task_state_array += sizeof(void *);
|
|
if (!readmem(task_state_array, KVADDR, &str, sizeof(void *),
|
|
"task_state_array", RETURN_ON_ERROR))
|
|
break;
|
|
}
|
|
|
|
if ((THIS_KERNEL_VERSION >= LINUX(2,6,16)) &&
|
|
(THIS_KERNEL_VERSION < LINUX(2,6,24))) {
|
|
_NONINTERACTIVE_ = 64;
|
|
}
|
|
|
|
if (THIS_KERNEL_VERSION >= LINUX(4,14,0)) {
|
|
if (valid_task_state(_PARKED_)) {
|
|
bitpos = _PARKED_;
|
|
_DEAD_ |= (bitpos << 1); /* TASK_DEAD */
|
|
_WAKEKILL_ = (bitpos << 2); /* TASK_WAKEKILL */
|
|
_WAKING_ = (bitpos << 3); /* TASK_WAKING */
|
|
_NOLOAD_ = (bitpos << 4); /* TASK_NOLOAD */
|
|
_NEW_ = (bitpos << 5); /* TASK_NEW */
|
|
}
|
|
} else if (THIS_KERNEL_VERSION >= LINUX(2,6,32)) {
|
|
/*
|
|
* Account for states not listed in task_state_array[]
|
|
*/
|
|
if (count_bits_long(_DEAD_) == 1) {
|
|
bitpos = 1<< lowest_bit_long(_DEAD_);
|
|
_DEAD_ |= (bitpos<<1); /* TASK_DEAD */
|
|
_WAKEKILL_ = (bitpos<<2); /* TASK_WAKEKILL */
|
|
_WAKING_ = (bitpos<<3); /* TASK_WAKING */
|
|
}
|
|
}
|
|
|
|
done_states:
|
|
if (CRASHDEBUG(3))
|
|
dump_task_states();
|
|
|
|
if (!valid_task_state(_RUNNING_) ||
|
|
!valid_task_state(_INTERRUPTIBLE_) ||
|
|
!valid_task_state(_UNINTERRUPTIBLE_) ||
|
|
!valid_task_state(_ZOMBIE_) ||
|
|
!valid_task_state(_STOPPED_)) {
|
|
if (CRASHDEBUG(3))
|
|
fprintf(fp,
|
|
"initialize_task_state: using old defaults\n");
|
|
goto old_defaults;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Print multiple state strings if appropriate.
|
|
*/
|
|
static char *
|
|
task_state_string_verbose(ulong task, char *buf)
|
|
{
|
|
long state, both;
|
|
int count;
|
|
|
|
state = task_state(task);
|
|
|
|
buf[0] = NULLCHAR;
|
|
count = 0;
|
|
|
|
if (state == _RUNNING_) {
|
|
sprintf(buf, "TASK_RUNNING");
|
|
return buf;
|
|
}
|
|
|
|
if (state & _INTERRUPTIBLE_)
|
|
sprintf(&buf[strlen(buf)], "%sTASK_INTERRUPTIBLE",
|
|
count++ ? "|" : "");
|
|
|
|
if (state & _UNINTERRUPTIBLE_)
|
|
sprintf(&buf[strlen(buf)], "%sTASK_UNINTERRUPTIBLE",
|
|
count++ ? "|" : "");
|
|
|
|
if (state & _STOPPED_)
|
|
sprintf(&buf[strlen(buf)], "%sTASK_STOPPED",
|
|
count++ ? "|" : "");
|
|
|
|
if (state & _TRACING_STOPPED_)
|
|
sprintf(&buf[strlen(buf)], "%sTASK_TRACED",
|
|
count++ ? "|" : "");
|
|
|
|
if ((both = (state & _DEAD_))) {
|
|
if (count_bits_long(both) > 1)
|
|
sprintf(&buf[strlen(buf)], "%sEXIT_DEAD|TASK_DEAD",
|
|
count++ ? "|" : "");
|
|
else
|
|
sprintf(&buf[strlen(buf)], "%sEXIT_DEAD",
|
|
count++ ? "|" : "");
|
|
}
|
|
|
|
if (state & _ZOMBIE_)
|
|
sprintf(&buf[strlen(buf)], "%sEXIT_ZOMBIE",
|
|
count++ ? "|" : "");
|
|
|
|
if (valid_task_state(_WAKING_) && (state & _WAKING_))
|
|
sprintf(&buf[strlen(buf)], "%sTASK_WAKING",
|
|
count++ ? "|" : "");
|
|
|
|
if (valid_task_state(_WAKEKILL_) && (state & _WAKEKILL_))
|
|
sprintf(&buf[strlen(buf)], "%sTASK_WAKEKILL",
|
|
count++ ? "|" : "");
|
|
|
|
if (valid_task_state(_NOLOAD_) && (state & _NOLOAD_))
|
|
sprintf(&buf[strlen(buf)], "%sTASK_NOLOAD",
|
|
count++ ? "|" : "");
|
|
|
|
if (valid_task_state(_NEW_) && (state & _NEW_))
|
|
sprintf(&buf[strlen(buf)], "%sTASK_NEW",
|
|
count++ ? "|" : "");
|
|
|
|
if (valid_task_state(_NONINTERACTIVE_) &&
|
|
(state & _NONINTERACTIVE_))
|
|
sprintf(&buf[strlen(buf)], "%sTASK_NONINTERACTIVE",
|
|
count++ ? "|" : "");
|
|
|
|
if (state == _PARKED_) {
|
|
sprintf(buf, "TASK_PARKED");
|
|
return buf;
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
char *
|
|
task_state_string(ulong task, char *buf, int verbose)
|
|
{
|
|
long state;
|
|
int exclusive;
|
|
int valid, set;
|
|
|
|
if (_RUNNING_ == TASK_STATE_UNINITIALIZED)
|
|
initialize_task_state();
|
|
|
|
if (verbose)
|
|
return task_state_string_verbose(task, buf);
|
|
|
|
if (buf)
|
|
sprintf(buf, verbose ? "(unknown)" : "??");
|
|
|
|
state = task_state(task);
|
|
|
|
set = valid = exclusive = 0;
|
|
if (valid_task_state(_EXCLUSIVE_)) {
|
|
exclusive = state & _EXCLUSIVE_;
|
|
state &= ~(_EXCLUSIVE_);
|
|
}
|
|
|
|
if (state == _RUNNING_) {
|
|
sprintf(buf, "RU");
|
|
valid++;
|
|
}
|
|
|
|
if (state & _INTERRUPTIBLE_) {
|
|
sprintf(buf, "IN");
|
|
valid++;
|
|
set++;
|
|
}
|
|
|
|
if (state & _UNINTERRUPTIBLE_) {
|
|
if (valid_task_state(_NOLOAD_) &&
|
|
(state & _NOLOAD_))
|
|
sprintf(buf, "ID");
|
|
else
|
|
sprintf(buf, "UN");
|
|
valid++;
|
|
set++;
|
|
}
|
|
|
|
if (state & _ZOMBIE_) {
|
|
sprintf(buf, "ZO");
|
|
valid++;
|
|
set++;
|
|
}
|
|
|
|
if (state & _STOPPED_) {
|
|
sprintf(buf, "ST");
|
|
valid++;
|
|
set++;
|
|
}
|
|
|
|
if (valid_task_state(_TRACING_STOPPED_) &&
|
|
(state & _TRACING_STOPPED_)) {
|
|
sprintf(buf, "TR");
|
|
valid++;
|
|
set++;
|
|
}
|
|
|
|
if (state == _SWAPPING_) {
|
|
sprintf(buf, "SW");
|
|
valid++;
|
|
set++;
|
|
}
|
|
|
|
if ((state & _DEAD_) && !set) {
|
|
sprintf(buf, "DE");
|
|
valid++;
|
|
set++;
|
|
}
|
|
|
|
if (state == _PARKED_) {
|
|
sprintf(buf, "PA");
|
|
valid++;
|
|
}
|
|
|
|
if (state == _WAKING_) {
|
|
sprintf(buf, "WA");
|
|
valid++;
|
|
}
|
|
|
|
if (state == _NEW_) {
|
|
sprintf(buf, "NE");
|
|
valid++;
|
|
}
|
|
|
|
if (valid && exclusive)
|
|
strcat(buf, "EX");
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Return a task's state and exit_state together.
|
|
*/
|
|
ulong
|
|
task_state(ulong task)
|
|
{
|
|
ulong state, exit_state;
|
|
|
|
fill_task_struct(task);
|
|
|
|
if (!tt->last_task_read)
|
|
return 0;
|
|
|
|
if (SIZE(task_struct_state) == sizeof(ulong))
|
|
state = ULONG(tt->task_struct + OFFSET(task_struct_state));
|
|
else
|
|
state = UINT(tt->task_struct + OFFSET(task_struct_state));
|
|
exit_state = VALID_MEMBER(task_struct_exit_state) ?
|
|
ULONG(tt->task_struct + OFFSET(task_struct_exit_state)) : 0;
|
|
|
|
return (state | exit_state);
|
|
}
|
|
|
|
/*
|
|
* Return a task's flags.
|
|
*/
|
|
ulong
|
|
task_flags(ulong task)
|
|
{
|
|
ulong flags;
|
|
|
|
fill_task_struct(task);
|
|
|
|
if (tt->last_task_read) {
|
|
if (SIZE(task_struct_flags) == sizeof(unsigned int))
|
|
flags = UINT(tt->task_struct +
|
|
OFFSET(task_struct_flags));
|
|
else
|
|
flags = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_flags));
|
|
} else
|
|
flags = 0;
|
|
|
|
return flags;
|
|
}
|
|
|
|
/*
|
|
* Return task's policy as bitmask bit.
|
|
*/
|
|
static ulong
|
|
task_policy(ulong task)
|
|
{
|
|
ulong policy = 0;
|
|
|
|
fill_task_struct(task);
|
|
|
|
if (!tt->last_task_read)
|
|
return policy;
|
|
|
|
if (SIZE(task_struct_policy) == sizeof(unsigned int))
|
|
policy = 1 << UINT(tt->task_struct + OFFSET(task_struct_policy));
|
|
else
|
|
policy = 1 << ULONG(tt->task_struct + OFFSET(task_struct_policy));
|
|
|
|
return policy;
|
|
}
|
|
|
|
/*
|
|
* Return a task's tgid.
|
|
*/
|
|
ulong
|
|
task_tgid(ulong task)
|
|
{
|
|
uint tgid;
|
|
|
|
fill_task_struct(task);
|
|
|
|
tgid = tt->last_task_read ?
|
|
UINT(tt->task_struct + OFFSET(task_struct_tgid)) : 0;
|
|
|
|
return (ulong)tgid;
|
|
}
|
|
|
|
ulonglong
|
|
task_last_run(ulong task)
|
|
{
|
|
ulong last_run;
|
|
ulonglong timestamp;
|
|
|
|
timestamp = 0;
|
|
fill_task_struct(task);
|
|
|
|
if (VALID_MEMBER(task_struct_last_run)) {
|
|
last_run = tt->last_task_read ? ULONG(tt->task_struct +
|
|
OFFSET(task_struct_last_run)) : 0;
|
|
timestamp = (ulonglong)last_run;
|
|
} else if (VALID_MEMBER(task_struct_timestamp))
|
|
timestamp = tt->last_task_read ? ULONGLONG(tt->task_struct +
|
|
OFFSET(task_struct_timestamp)) : 0;
|
|
else if (VALID_MEMBER(sched_info_last_arrival))
|
|
timestamp = tt->last_task_read ? ULONGLONG(tt->task_struct +
|
|
OFFSET(task_struct_sched_info) +
|
|
OFFSET(sched_info_last_arrival)) : 0;
|
|
|
|
return timestamp;
|
|
}
|
|
|
|
/*
|
|
* Return a task's mm_struct address. If "fill" is set, the mm_struct
|
|
* cache is loaded.
|
|
*/
|
|
ulong
|
|
task_mm(ulong task, int fill)
|
|
{
|
|
ulong mm_struct;
|
|
|
|
fill_task_struct(task);
|
|
|
|
if (!tt->last_task_read)
|
|
return 0;
|
|
|
|
mm_struct = ULONG(tt->task_struct + OFFSET(task_struct_mm));
|
|
|
|
if (fill && mm_struct)
|
|
fill_mm_struct(mm_struct);
|
|
|
|
return mm_struct;
|
|
}
|
|
|
|
/*
|
|
* Translate a processor number into a string, taking NO_PROC_ID into account.
|
|
*/
|
|
char *
|
|
task_cpu(int processor, char *buf, int verbose)
|
|
{
|
|
if (processor < NR_CPUS)
|
|
sprintf(buf, "%d", processor);
|
|
else
|
|
sprintf(buf, verbose ? "(unknown)" : "?");
|
|
|
|
return buf;
|
|
}
|
|
|
|
/*
|
|
* Check either the panic_threads[] array on a dump, or the has_cpu flag
|
|
* of a task_struct on a live system. Also account for deprecation of
|
|
* usage of has_cpu on non-SMP systems.
|
|
*/
|
|
int
|
|
is_task_active(ulong task)
|
|
{
|
|
int has_cpu;
|
|
|
|
if (LOCAL_ACTIVE() && (task == tt->this_task))
|
|
return TRUE;
|
|
if (DUMPFILE() && is_panic_thread(task))
|
|
return TRUE;
|
|
|
|
fill_task_struct(task);
|
|
|
|
has_cpu = tt->last_task_read ?
|
|
task_has_cpu(task, tt->task_struct) : 0;
|
|
|
|
return(has_cpu);
|
|
}
|
|
|
|
/*
|
|
* Return true if a task is the panic_task or is contained within the
|
|
* panic_threads[] array.
|
|
*/
|
|
int
|
|
is_panic_thread(ulong task)
|
|
{
|
|
int i;
|
|
|
|
if (DUMPFILE()) {
|
|
if (tt->panic_task == task)
|
|
return TRUE;
|
|
|
|
for (i = 0; i < NR_CPUS; i++)
|
|
if (tt->panic_threads[i] == task)
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Depending upon the kernel, check the task_struct's has_cpu or cpus_runnable
|
|
* field if either exist, or the global runqueues[].curr via get_active_set()
|
|
* to determine whether a task is running on a cpu.
|
|
*/
|
|
static int
|
|
task_has_cpu(ulong task, char *local_task)
|
|
{
|
|
int i, has_cpu;
|
|
ulong cpus_runnable;
|
|
|
|
if (DUMPFILE() && (task == tt->panic_task)) /* no need to continue */
|
|
return TRUE;
|
|
|
|
if (VALID_MEMBER(task_struct_has_cpu)) {
|
|
if (local_task)
|
|
has_cpu = INT(local_task+OFFSET(task_struct_has_cpu));
|
|
else if (!readmem((ulong)(task+OFFSET(task_struct_has_cpu)),
|
|
KVADDR, &has_cpu, sizeof(int),
|
|
"task_struct has_cpu", RETURN_ON_ERROR))
|
|
has_cpu = FALSE;
|
|
} else if (VALID_MEMBER(task_struct_cpus_runnable)) {
|
|
if (local_task)
|
|
cpus_runnable = ULONG(local_task +
|
|
OFFSET(task_struct_cpus_runnable));
|
|
else if (!readmem((ulong)(task +
|
|
OFFSET(task_struct_cpus_runnable)),
|
|
KVADDR, &cpus_runnable, sizeof(ulong),
|
|
"task_struct cpus_runnable", RETURN_ON_ERROR))
|
|
cpus_runnable = ~0UL;
|
|
has_cpu = (cpus_runnable != ~0UL);
|
|
} else if (get_active_set()) {
|
|
for (i = 0, has_cpu = FALSE; i < NR_CPUS; i++) {
|
|
if (task == tt->active_set[i]) {
|
|
has_cpu = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} else
|
|
error(FATAL,
|
|
"task_struct has no has_cpu, or cpus_runnable; runqueues[] not defined?\n");
|
|
|
|
return has_cpu;
|
|
}
|
|
|
|
|
|
/*
|
|
* If a task is in the panic_threads array and has an associated panic_ksp
|
|
* array entry, return it.
|
|
*/
|
|
int
|
|
get_panic_ksp(struct bt_info *bt, ulong *ksp)
|
|
{
|
|
int i;
|
|
|
|
if (tt->flags & PANIC_KSP) {
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if ((tt->panic_threads[i] == bt->task) &&
|
|
tt->panic_ksp[i] &&
|
|
INSTACK(tt->panic_ksp[i], bt)) {
|
|
*ksp = tt->panic_ksp[i];
|
|
return TRUE;
|
|
}
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Look for kcore's storage information for the system's panic state.
|
|
* If it's not there (somebody else's dump format?), look through all
|
|
* the stack traces or the log buffer for evidence of panic.
|
|
*/
|
|
static ulong
|
|
get_panic_context(void)
|
|
{
|
|
int i;
|
|
struct task_context *tc;
|
|
ulong panic_threads_addr;
|
|
ulong task;
|
|
char *tp;
|
|
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!(task = tt->active_set[i]))
|
|
continue;
|
|
|
|
if (!task_exists(task)) {
|
|
error(WARNING,
|
|
"active task %lx on cpu %d not found in PID hash\n\n",
|
|
task, i);
|
|
if ((tp = fill_task_struct(task)))
|
|
add_context(task, tp);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* --no_panic command line option
|
|
*/
|
|
if (tt->flags & PANIC_TASK_NOT_FOUND)
|
|
goto use_task_0;
|
|
|
|
tt->panic_processor = -1;
|
|
task = NO_TASK;
|
|
tc = FIRST_CONTEXT();
|
|
|
|
if (symbol_exists("panic_threads") &&
|
|
symbol_exists("panicmsg") &&
|
|
symbol_exists("panic_processor")) {
|
|
panic_threads_addr = symbol_value("panic_threads");
|
|
get_symbol_data("panic_processor", sizeof(int),
|
|
&tt->panic_processor);
|
|
get_symbol_data("panicmsg", sizeof(char *), &tt->panicmsg);
|
|
|
|
if (!readmem(panic_threads_addr, KVADDR, tt->panic_threads,
|
|
sizeof(void *)*NR_CPUS, "panic_processor array",
|
|
RETURN_ON_ERROR))
|
|
goto use_task_0;
|
|
|
|
task = tt->panic_threads[tt->panic_processor];
|
|
|
|
if (symbol_exists("panic_ksp")) {
|
|
if (!(tt->panic_ksp = (ulong *)
|
|
calloc(NR_CPUS, sizeof(void *))))
|
|
error(FATAL,
|
|
"cannot malloc panic_ksp array.\n");
|
|
readmem(symbol_value("panic_ksp"), KVADDR,
|
|
tt->panic_ksp,
|
|
sizeof(void *)*NR_CPUS, "panic_ksp array",
|
|
RETURN_ON_ERROR);
|
|
tt->flags |= PANIC_KSP;
|
|
}
|
|
|
|
if (machdep->flags & HWRESET) {
|
|
populate_panic_threads();
|
|
task = tt->panic_threads[0];
|
|
}
|
|
}
|
|
|
|
if (task && task_exists(task))
|
|
return(tt->panic_task = task);
|
|
|
|
if (task)
|
|
error(INFO, "reported panic task %lx does not exist!\n\n",
|
|
task);
|
|
|
|
if ((tc = panic_search())) {
|
|
tt->panic_processor = tc->processor;
|
|
return(tt->panic_task = tc->task);
|
|
}
|
|
|
|
use_task_0:
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "get_panic_context: panic task not found\n");
|
|
|
|
tt->flags |= PANIC_TASK_NOT_FOUND;
|
|
tc = FIRST_CONTEXT();
|
|
return(tc->task);
|
|
}
|
|
|
|
/*
|
|
* Get the active task on a cpu -- from a dumpfile only.
|
|
*/
|
|
ulong
|
|
get_active_task(int cpu)
|
|
{
|
|
int i;
|
|
ulong task;
|
|
struct task_context *tc;
|
|
|
|
if (DUMPFILE() && (task = tt->panic_threads[cpu]))
|
|
return task;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if ((tc->processor == cpu) && is_task_active(tc->task))
|
|
return(tc->task);
|
|
}
|
|
|
|
return NO_TASK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read the panic string.
|
|
*/
|
|
char *
|
|
get_panicmsg(char *buf)
|
|
{
|
|
int msg_found;
|
|
|
|
BZERO(buf, BUFSIZE);
|
|
msg_found = FALSE;
|
|
|
|
if (tt->panicmsg) {
|
|
read_string(tt->panicmsg, buf, BUFSIZE-1);
|
|
msg_found = TRUE;
|
|
} else if (LKCD_DUMPFILE()) {
|
|
get_lkcd_panicmsg(buf);
|
|
msg_found = TRUE;
|
|
}
|
|
|
|
if (msg_found == TRUE)
|
|
return(buf);
|
|
|
|
open_tmpfile();
|
|
dump_log(SHOW_LOG_TEXT);
|
|
|
|
/*
|
|
* First check for a SYSRQ-generated crash, and set the
|
|
* active-task flag appropriately. The message may or
|
|
* may not be used as the panic message.
|
|
*/
|
|
rewind(pc->tmpfile);
|
|
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "SysRq : Crash") ||
|
|
strstr(buf, "SysRq : Trigger a crash")) {
|
|
pc->flags |= SYSRQ;
|
|
break;
|
|
}
|
|
}
|
|
rewind(pc->tmpfile);
|
|
while (!msg_found && fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "general protection fault: ") ||
|
|
strstr(buf, "double fault: ") ||
|
|
strstr(buf, "divide error: ") ||
|
|
strstr(buf, "stack segment: ")) {
|
|
msg_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
rewind(pc->tmpfile);
|
|
while (!msg_found && fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "SysRq : Netdump") ||
|
|
strstr(buf, "SysRq : Crash") ||
|
|
strstr(buf, "SysRq : Trigger a crash")) {
|
|
pc->flags |= SYSRQ;
|
|
msg_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
rewind(pc->tmpfile);
|
|
while (!msg_found && fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "Oops: ") ||
|
|
strstr(buf, "Kernel BUG at") ||
|
|
strstr(buf, "kernel BUG at") ||
|
|
strstr(buf, "Unable to handle kernel paging request") ||
|
|
strstr(buf, "Unable to handle kernel NULL pointer dereference") ||
|
|
strstr(buf, "BUG: unable to handle kernel "))
|
|
msg_found = TRUE;
|
|
}
|
|
rewind(pc->tmpfile);
|
|
while (!msg_found && fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "sysrq") &&
|
|
symbol_exists("sysrq_pressed")) {
|
|
get_symbol_data("sysrq_pressed", sizeof(int),
|
|
&msg_found);
|
|
break;
|
|
}
|
|
}
|
|
rewind(pc->tmpfile);
|
|
while (!msg_found && fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "Kernel panic: ") ||
|
|
strstr(buf, "Kernel panic - ")) {
|
|
msg_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
rewind(pc->tmpfile);
|
|
while (!msg_found && fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "[Hardware Error]: ")) {
|
|
msg_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
rewind(pc->tmpfile);
|
|
while (!msg_found && fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (strstr(buf, "Bad mode in ")) {
|
|
msg_found = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
close_tmpfile();
|
|
|
|
if (!msg_found)
|
|
BZERO(buf, BUFSIZE);
|
|
|
|
return(buf);
|
|
}
|
|
|
|
/*
|
|
* This command allows the running of a set of commands on any or all
|
|
* tasks running on a system. The target tasks may be designated by
|
|
* pid, task or command name. The available command set is designated by
|
|
* the FOREACH_xxx definitions below. If a running command name string
|
|
* conflicts with a foreach command, the command name string may be
|
|
* prefixed with a \ character.
|
|
*/
|
|
|
|
void
|
|
cmd_foreach(void)
|
|
{
|
|
int a, c, k, t, p;
|
|
ulong value;
|
|
static struct foreach_data foreach_data;
|
|
struct foreach_data *fd;
|
|
struct task_context *tc;
|
|
char *p1;
|
|
int key;
|
|
|
|
BZERO(&foreach_data, sizeof(struct foreach_data));
|
|
fd = &foreach_data;
|
|
|
|
while ((c = getopt(argcnt, args, "R:vomlgersStTpukcfFxhdaGy:")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'R':
|
|
fd->reference = optarg;
|
|
break;
|
|
|
|
case 'h':
|
|
case 'x':
|
|
fd->flags |= FOREACH_x_FLAG;
|
|
break;
|
|
|
|
case 'd':
|
|
fd->flags |= FOREACH_d_FLAG;
|
|
break;
|
|
|
|
case 'v':
|
|
fd->flags |= FOREACH_v_FLAG;
|
|
break;
|
|
|
|
case 'm':
|
|
fd->flags |= FOREACH_m_FLAG;
|
|
break;
|
|
|
|
case 'l':
|
|
fd->flags |= FOREACH_l_FLAG;
|
|
break;
|
|
|
|
case 'o':
|
|
fd->flags |= FOREACH_o_FLAG;
|
|
break;
|
|
|
|
case 'g':
|
|
fd->flags |= FOREACH_g_FLAG;
|
|
break;
|
|
|
|
case 'e':
|
|
fd->flags |= FOREACH_e_FLAG;
|
|
break;
|
|
|
|
case 's':
|
|
fd->flags |= FOREACH_s_FLAG;
|
|
break;
|
|
|
|
case 'S':
|
|
fd->flags |= FOREACH_S_FLAG;
|
|
break;
|
|
|
|
case 'r':
|
|
fd->flags |= FOREACH_r_FLAG;
|
|
break;
|
|
|
|
case 'T':
|
|
fd->flags |= FOREACH_T_FLAG;
|
|
break;
|
|
|
|
case 't':
|
|
fd->flags |= FOREACH_t_FLAG;
|
|
break;
|
|
|
|
case 'p':
|
|
fd->flags |= FOREACH_p_FLAG;
|
|
break;
|
|
|
|
case 'u':
|
|
fd->flags |= FOREACH_u_FLAG;
|
|
break;
|
|
|
|
case 'k':
|
|
fd->flags |= FOREACH_k_FLAG;
|
|
break;
|
|
|
|
case 'c':
|
|
fd->flags |= FOREACH_c_FLAG;
|
|
break;
|
|
|
|
case 'f':
|
|
fd->flags |= FOREACH_f_FLAG;
|
|
break;
|
|
|
|
case 'F':
|
|
if (fd->flags & FOREACH_F_FLAG)
|
|
fd->flags |= FOREACH_F_FLAG2;
|
|
else
|
|
fd->flags |= FOREACH_F_FLAG;
|
|
break;
|
|
|
|
case 'a':
|
|
fd->flags |= FOREACH_a_FLAG;
|
|
break;
|
|
|
|
case 'G':
|
|
fd->flags |= FOREACH_G_FLAG;
|
|
break;
|
|
|
|
case 'y':
|
|
fd->flags |= FOREACH_y_FLAG;
|
|
fd->policy = make_sched_policy(optarg);
|
|
break;
|
|
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs || !args[optind])
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
a = c = k = t = p = 0;
|
|
|
|
while (args[optind]) {
|
|
/*
|
|
* Once a keyword has been entered, then only accept
|
|
* command arguments.
|
|
*/
|
|
if (k) {
|
|
p1 = args[optind];
|
|
goto command_argument;
|
|
}
|
|
|
|
/*
|
|
* If it's a keyword, grab it and check no further.
|
|
*/
|
|
if (is_foreach_keyword(args[optind], &key)) {
|
|
if (k == MAX_FOREACH_KEYWORDS)
|
|
error(INFO, "too many keywords!\n");
|
|
else
|
|
fd->keyword_array[k++] = key;
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* If it's a task pointer or pid, take it.
|
|
*/
|
|
if (IS_A_NUMBER(args[optind])) {
|
|
if (STREQ(args[optind], "DE") && pid_exists(0xde)) {
|
|
error(INFO, "ambiguous task-identifying argument: %s\n", args[optind]);
|
|
error(CONT, "for a \"state\" argument, use: \\DE\n");
|
|
error(CONT, "for a \"pid\" argument, use: 0xDE, 0xde, de or 222\n\n");
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
return;
|
|
}
|
|
|
|
switch (str_to_context(args[optind], &value, &tc))
|
|
{
|
|
case STR_PID:
|
|
if (p == MAX_FOREACH_PIDS)
|
|
error(INFO,
|
|
"too many pids specified!\n");
|
|
else {
|
|
fd->pid_array[p++] = value;
|
|
fd->flags |= FOREACH_SPECIFIED;
|
|
}
|
|
optind++;
|
|
continue;
|
|
|
|
case STR_TASK:
|
|
if (t == MAX_FOREACH_TASKS)
|
|
error(INFO,
|
|
"too many tasks specified!\n");
|
|
else {
|
|
fd->task_array[t++] = value;
|
|
fd->flags |= FOREACH_SPECIFIED;
|
|
}
|
|
optind++;
|
|
continue;
|
|
|
|
case STR_INVALID:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Select all kernel threads.
|
|
*/
|
|
if (STREQ(args[optind], "kernel")) {
|
|
if (fd->flags & FOREACH_USER)
|
|
error(FATAL,
|
|
"user and kernel are mutually exclusive!\n");
|
|
fd->flags |= FOREACH_KERNEL;
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
if ((args[optind][0] == '\\') &&
|
|
STREQ(&args[optind][1], "DE"))
|
|
shift_string_left(args[optind], 1);
|
|
|
|
if (STREQ(args[optind], "RU") ||
|
|
STREQ(args[optind], "IN") ||
|
|
STREQ(args[optind], "UN") ||
|
|
STREQ(args[optind], "ST") ||
|
|
STREQ(args[optind], "TR") ||
|
|
STREQ(args[optind], "ZO") ||
|
|
STREQ(args[optind], "DE") ||
|
|
STREQ(args[optind], "PA") ||
|
|
STREQ(args[optind], "WA") ||
|
|
STREQ(args[optind], "ID") ||
|
|
STREQ(args[optind], "NE") ||
|
|
STREQ(args[optind], "SW")) {
|
|
|
|
if (fd->flags & FOREACH_STATE)
|
|
error(FATAL, "only one task state allowed\n");
|
|
|
|
if (STREQ(args[optind], "RU"))
|
|
fd->state = _RUNNING_;
|
|
else if (STREQ(args[optind], "IN"))
|
|
fd->state = _INTERRUPTIBLE_;
|
|
else if (STREQ(args[optind], "UN"))
|
|
fd->state = _UNINTERRUPTIBLE_;
|
|
else if (STREQ(args[optind], "ST"))
|
|
fd->state = _STOPPED_;
|
|
else if (STREQ(args[optind], "TR"))
|
|
fd->state = _TRACING_STOPPED_;
|
|
else if (STREQ(args[optind], "ZO"))
|
|
fd->state = _ZOMBIE_;
|
|
else if (STREQ(args[optind], "DE"))
|
|
fd->state = _DEAD_;
|
|
else if (STREQ(args[optind], "SW"))
|
|
fd->state = _SWAPPING_;
|
|
else if (STREQ(args[optind], "PA"))
|
|
fd->state = _PARKED_;
|
|
else if (STREQ(args[optind], "WA"))
|
|
fd->state = _WAKING_;
|
|
else if (STREQ(args[optind], "ID"))
|
|
fd->state = _UNINTERRUPTIBLE_|_NOLOAD_;
|
|
else if (STREQ(args[optind], "NE"))
|
|
fd->state = _NEW_;
|
|
|
|
if (fd->state == TASK_STATE_UNINITIALIZED)
|
|
error(FATAL,
|
|
"invalid task state for this kernel: %s\n",
|
|
args[optind]);
|
|
|
|
fd->flags |= FOREACH_STATE;
|
|
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Select only user threads.
|
|
*/
|
|
if (STREQ(args[optind], "user")) {
|
|
if (fd->flags & FOREACH_KERNEL)
|
|
error(FATAL,
|
|
"user and kernel are mutually exclusive!\n");
|
|
fd->flags |= FOREACH_USER;
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Select only user-space thread group leaders
|
|
*/
|
|
if (STREQ(args[optind], "gleader")) {
|
|
if (fd->flags & FOREACH_KERNEL)
|
|
error(FATAL,
|
|
"gleader and kernel are mutually exclusive!\n");
|
|
fd->flags |= (FOREACH_USER|FOREACH_GLEADER);
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Select only active tasks (dumpfile only)
|
|
*/
|
|
if (STREQ(args[optind], "active")) {
|
|
if (!DUMPFILE())
|
|
error(FATAL,
|
|
"active option not allowed on live systems\n");
|
|
fd->flags |= FOREACH_ACTIVE;
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Regular expression is exclosed within "'" character.
|
|
* The args[optind] string may not be modified, so a copy
|
|
* is duplicated.
|
|
*/
|
|
if (SINGLE_QUOTED_STRING(args[optind])) {
|
|
if (fd->regexs == MAX_REGEX_ARGS)
|
|
error(INFO, "too many expressions specified!\n");
|
|
else {
|
|
p1 = strdup(&args[optind][1]);
|
|
LASTCHAR(p1) = NULLCHAR;
|
|
|
|
if (regcomp(&fd->regex_info[fd->regexs].regex, p1,
|
|
REG_EXTENDED|REG_NOSUB)) {
|
|
error(INFO,
|
|
"invalid regular expression: %s\n",
|
|
p1);
|
|
free(p1);
|
|
goto bailout;
|
|
}
|
|
|
|
fd->regex_info[fd->regexs].pattern = p1;
|
|
if (fd->regexs++ == 0) {
|
|
pc->cmd_cleanup_arg = (void *)fd;
|
|
pc->cmd_cleanup = foreach_cleanup;
|
|
}
|
|
}
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* If it's a command name, prefixed or otherwise, take it.
|
|
*/
|
|
p1 = (args[optind][0] == '\\') ?
|
|
&args[optind][1] : args[optind];
|
|
|
|
if (comm_exists(p1)) {
|
|
if (c == MAX_FOREACH_COMMS)
|
|
error(INFO, "too many commands specified!\n");
|
|
else {
|
|
fd->comm_array[c++] = p1;
|
|
fd->flags |= FOREACH_SPECIFIED;
|
|
}
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
command_argument:
|
|
/*
|
|
* If no keyword has been entered, we don't know what this
|
|
* is -- most likely it's a bogus command specifier. We set
|
|
* FOREACH_SPECIFIED in case it was a bad specifier and no
|
|
* other task selectors exist -- which in turn would causes
|
|
* the command to be erroneously run on all tasks.
|
|
*/
|
|
if (!k) {
|
|
fd->flags |= FOREACH_SPECIFIED;
|
|
error(INFO, "unknown argument: \"%s\"\n",
|
|
args[optind]);
|
|
optind++;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* Must be an command argument -- so store it and let
|
|
* the command deal with it...
|
|
*/
|
|
if (a == MAX_FOREACH_ARGS)
|
|
error(INFO, "too many arguments specified!\n");
|
|
else
|
|
fd->arg_array[a++] = (ulong)p1;
|
|
|
|
optind++;
|
|
}
|
|
|
|
fd->flags |= FOREACH_CMD;
|
|
fd->pids = p;
|
|
fd->keys = k;
|
|
fd->comms = c;
|
|
fd->tasks = t;
|
|
fd->args = a;
|
|
|
|
if (fd->keys)
|
|
foreach(fd);
|
|
else
|
|
error(INFO, "no keywords specified\n");
|
|
bailout:
|
|
foreach_cleanup((void *)fd);
|
|
}
|
|
|
|
/*
|
|
* Do the work for cmd_foreach().
|
|
*/
|
|
void
|
|
foreach(struct foreach_data *fd)
|
|
{
|
|
int i, j, k, a;
|
|
struct task_context *tc, *tgc;
|
|
int specified;
|
|
int doit;
|
|
int subsequent;
|
|
unsigned int radix;
|
|
ulong cmdflags;
|
|
ulong tgid;
|
|
struct reference reference, *ref;
|
|
int print_header;
|
|
struct bt_info bt_info, *bt;
|
|
char buf[TASK_COMM_LEN];
|
|
struct psinfo psinfo;
|
|
|
|
/*
|
|
* Filter out any command/option issues.
|
|
*/
|
|
if (CRASHDEBUG(1)) {
|
|
fprintf(fp, " flags: %lx\n", fd->flags);
|
|
fprintf(fp, " task_array: %s", fd->tasks ? "" : "(none)");
|
|
for (j = 0; j < fd->tasks; j++)
|
|
fprintf(fp, "[%lx] ", fd->task_array[j]);
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " pid_array: %s", fd->pids ? "" : "(none)");
|
|
for (j = 0; j < fd->pids; j++)
|
|
fprintf(fp, "[%ld] ", fd->pid_array[j]);
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " comm_array: %s", fd->comms ? "" : "(none)");
|
|
for (j = 0; j < fd->comms; j++)
|
|
fprintf(fp, "[%s] ", fd->comm_array[j]);
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " regex_info: %s", fd->regexs ? "" : "(none)\n");
|
|
for (j = 0; j < fd->regexs; j++) {
|
|
fprintf(fp, "%s[%d] pattern: [%s] ",
|
|
j ? " " : "",
|
|
j, fd->regex_info[j].pattern);
|
|
fprintf(fp, "regex: [%lx]\n",
|
|
(ulong)&fd->regex_info[j].regex);
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, "keyword_array: %s", fd->keys ? "" : "(none)");
|
|
for (k = 0; k < fd->keys; k++)
|
|
fprintf(fp, "[%d] ", fd->keyword_array[k]);
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " arg_array: %s", fd->args ? "" : "(none)");
|
|
for (a = 0; a < fd->args; a++)
|
|
fprintf(fp, "[%lx (%s)] ",
|
|
fd->arg_array[a],
|
|
(char *)fd->arg_array[a]);
|
|
fprintf(fp, "\n");
|
|
fprintf(fp, " reference: \"%s\"\n",
|
|
fd->reference ? fd->reference : "");
|
|
}
|
|
|
|
print_header = TRUE;
|
|
bt = NULL;
|
|
|
|
for (k = 0; k < fd->keys; k++) {
|
|
switch(fd->keyword_array[k])
|
|
{
|
|
case FOREACH_NET:
|
|
switch (fd->flags & (FOREACH_s_FLAG|FOREACH_S_FLAG))
|
|
{
|
|
case (FOREACH_s_FLAG|FOREACH_S_FLAG):
|
|
error(WARNING,
|
|
"net -s and -S options are mutually exclusive!\n");
|
|
fd->flags = FOREACH_s_FLAG;
|
|
break;
|
|
|
|
case 0:
|
|
error(WARNING,
|
|
"net command requires -s or -S option\n\n");
|
|
fd->flags |= FOREACH_s_FLAG;
|
|
break;
|
|
}
|
|
if ((fd->flags & (FOREACH_x_FLAG|FOREACH_d_FLAG)) ==
|
|
(FOREACH_x_FLAG|FOREACH_d_FLAG))
|
|
error(FATAL,
|
|
"net: -x and -d options are mutually exclusive\n");
|
|
break;
|
|
|
|
case FOREACH_VTOP:
|
|
if (!fd->args)
|
|
error(FATAL,
|
|
"foreach command requires address argument\n");
|
|
if (fd->reference)
|
|
error(FATAL,
|
|
"vtop command does not support -R option\n");
|
|
if ((fd->flags & (FOREACH_u_FLAG|FOREACH_k_FLAG)) ==
|
|
(FOREACH_u_FLAG|FOREACH_k_FLAG))
|
|
error(FATAL,
|
|
"vtop: -u and -k options are mutually exclusive\n");
|
|
break;
|
|
|
|
case FOREACH_VM:
|
|
if ((fd->flags & (FOREACH_x_FLAG|FOREACH_d_FLAG)) ==
|
|
(FOREACH_x_FLAG|FOREACH_d_FLAG))
|
|
error(FATAL,
|
|
"vm: -x and -d options are mutually exclusive\n");
|
|
if (count_bits_long(fd->flags &
|
|
(FOREACH_i_FLAG|FOREACH_p_FLAG|
|
|
FOREACH_m_FLAG|FOREACH_v_FLAG)) > 1)
|
|
error(FATAL,
|
|
"vm command accepts only one of -p, -m or -v flags\n");
|
|
if (fd->reference) {
|
|
if (fd->flags & FOREACH_i_FLAG)
|
|
error(FATAL,
|
|
"vm: -i is not applicable to the -R option\n");
|
|
if (fd->flags & FOREACH_m_FLAG)
|
|
error(FATAL,
|
|
"vm: -m is not applicable to the -R option\n");
|
|
if (fd->flags & FOREACH_v_FLAG)
|
|
error(FATAL,
|
|
"vm: -v is not applicable to the -R option\n");
|
|
}
|
|
break;
|
|
|
|
case FOREACH_BT:
|
|
if ((fd->flags & (FOREACH_x_FLAG|FOREACH_d_FLAG)) ==
|
|
(FOREACH_x_FLAG|FOREACH_d_FLAG))
|
|
error(FATAL,
|
|
"bt: -x and -d options are mutually exclusive\n");
|
|
|
|
if ((fd->flags & FOREACH_l_FLAG) && NO_LINE_NUMBERS()) {
|
|
error(INFO, "line numbers are not available\n");
|
|
fd->flags &= ~FOREACH_l_FLAG;
|
|
}
|
|
#ifndef GDB_5_3
|
|
if ((fd->flags & FOREACH_g_FLAG))
|
|
error(FATAL,
|
|
"bt -g option is not supported when issued from foreach\n");
|
|
#endif
|
|
bt = &bt_info;
|
|
break;
|
|
|
|
case FOREACH_TASK:
|
|
if ((fd->flags & (FOREACH_x_FLAG|FOREACH_d_FLAG)) ==
|
|
(FOREACH_x_FLAG|FOREACH_d_FLAG))
|
|
error(FATAL,
|
|
"task: -x and -d options are mutually exclusive\n");
|
|
if (count_bits_long(fd->flags &
|
|
(FOREACH_x_FLAG|FOREACH_d_FLAG)) > 1)
|
|
error(FATAL,
|
|
"task command accepts -R member[,member],"
|
|
" and either -x or -d flags\n");
|
|
break;
|
|
|
|
case FOREACH_SET:
|
|
if (fd->reference)
|
|
error(FATAL,
|
|
"set command does not support -R option\n");
|
|
break;
|
|
|
|
case FOREACH_SIG:
|
|
if (fd->flags & (FOREACH_l_FLAG|FOREACH_s_FLAG))
|
|
error(FATAL,
|
|
"sig: -l and -s options are not applicable\n");
|
|
if (fd->flags & FOREACH_g_FLAG) {
|
|
if (!hq_open()) {
|
|
error(INFO,
|
|
"cannot hash thread group tasks\n");
|
|
fd->flags &= ~FOREACH_g_FLAG;
|
|
} else
|
|
print_header = FALSE;
|
|
}
|
|
break;
|
|
|
|
case FOREACH_PS:
|
|
if (count_bits_long(fd->flags & FOREACH_PS_EXCLUSIVE) > 1)
|
|
error(FATAL, ps_exclusive);
|
|
if ((fd->flags & (FOREACH_l_FLAG|FOREACH_m_FLAG)) &&
|
|
(fd->flags & FOREACH_G_FLAG))
|
|
error(FATAL, "-G not supported with -%c option\n",
|
|
fd->flags & FOREACH_l_FLAG ? 'l' : 'm');
|
|
|
|
BZERO(&psinfo, sizeof(struct psinfo));
|
|
if (fd->flags & FOREACH_G_FLAG) {
|
|
if (!hq_open()) {
|
|
error(INFO,
|
|
"cannot hash thread group tasks\n");
|
|
fd->flags &= ~FOREACH_G_FLAG;
|
|
}
|
|
}
|
|
if (fd->flags & (FOREACH_l_FLAG|FOREACH_m_FLAG))
|
|
sort_context_array_by_last_run();
|
|
if ((fd->flags & FOREACH_m_FLAG) &&
|
|
INVALID_MEMBER(rq_timestamp))
|
|
option_not_supported('m');
|
|
|
|
print_header = FALSE;
|
|
break;
|
|
|
|
case FOREACH_FILES:
|
|
if (fd->flags & FOREACH_p_FLAG)
|
|
error(FATAL,
|
|
"files command does not support -p option\n");
|
|
break;
|
|
|
|
case FOREACH_TEST:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
subsequent = FALSE;
|
|
specified = (fd->tasks || fd->pids || fd->comms || fd->regexs ||
|
|
(fd->flags & FOREACH_SPECIFIED));
|
|
ref = &reference;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
doit = FALSE;
|
|
|
|
if ((fd->flags & FOREACH_ACTIVE) && !is_task_active(tc->task))
|
|
continue;
|
|
|
|
if ((fd->flags & FOREACH_USER) && is_kernel_thread(tc->task))
|
|
continue;
|
|
|
|
if ((fd->flags & FOREACH_GLEADER) && tc->pid != task_tgid(tc->task))
|
|
continue;
|
|
|
|
if ((fd->flags & FOREACH_KERNEL) && !is_kernel_thread(tc->task))
|
|
continue;
|
|
|
|
if (fd->flags & FOREACH_STATE) {
|
|
if (fd->state == _RUNNING_) {
|
|
if (task_state(tc->task) != _RUNNING_)
|
|
continue;
|
|
} else if (fd->state & _UNINTERRUPTIBLE_) {
|
|
if (!(task_state(tc->task) & _UNINTERRUPTIBLE_))
|
|
continue;
|
|
|
|
if (valid_task_state(_NOLOAD_)) {
|
|
if (fd->state & _NOLOAD_) {
|
|
if (!(task_state(tc->task) & _NOLOAD_))
|
|
continue;
|
|
} else {
|
|
if ((task_state(tc->task) & _NOLOAD_))
|
|
continue;
|
|
}
|
|
}
|
|
} else if (!(task_state(tc->task) & fd->state))
|
|
continue;
|
|
}
|
|
|
|
if (specified) {
|
|
for (j = 0; j < fd->tasks; j++) {
|
|
if (fd->task_array[j] == tc->task) {
|
|
doit = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (j = 0; !doit && (j < fd->pids); j++) {
|
|
if (fd->pid_array[j] == tc->pid) {
|
|
doit = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (j = 0; !doit && (j < fd->comms); j++) {
|
|
strlcpy(buf, fd->comm_array[j], TASK_COMM_LEN);
|
|
if (STREQ(buf, tc->comm)) {
|
|
doit = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (j = 0; !doit && (j < fd->regexs); j++) {
|
|
if (regexec(&fd->regex_info[j].regex,
|
|
tc->comm, 0, NULL, 0) == 0) {
|
|
doit = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
} else
|
|
doit = TRUE;
|
|
|
|
if (!doit)
|
|
continue;
|
|
|
|
if (output_closed() || received_SIGINT()) {
|
|
free_all_bufs();
|
|
goto foreach_bailout;
|
|
}
|
|
|
|
if (setjmp(pc->foreach_loop_env)) {
|
|
free_all_bufs();
|
|
continue;
|
|
}
|
|
pc->flags |= IN_FOREACH;
|
|
|
|
if (fd->reference) {
|
|
BZERO(ref, sizeof(struct reference));
|
|
ref->str = fd->reference;
|
|
} else if (print_header)
|
|
print_task_header(fp, tc, subsequent++);
|
|
|
|
for (k = 0; k < fd->keys; k++) {
|
|
free_all_bufs();
|
|
|
|
switch(fd->keyword_array[k])
|
|
{
|
|
case FOREACH_BT:
|
|
pc->curcmd = "bt";
|
|
BZERO(bt, sizeof(struct bt_info));;
|
|
bt->task = tc->task;
|
|
bt->tc = tc;
|
|
bt->stackbase = GET_STACKBASE(tc->task);
|
|
bt->stacktop = GET_STACKTOP(tc->task);
|
|
if (fd->flags & FOREACH_r_FLAG)
|
|
bt->flags |= BT_RAW;
|
|
if (fd->flags & FOREACH_s_FLAG)
|
|
bt->flags |= BT_SYMBOL_OFFSET;
|
|
if (fd->flags & FOREACH_t_FLAG)
|
|
bt->flags |= BT_TEXT_SYMBOLS;
|
|
if (fd->flags & FOREACH_T_FLAG) {
|
|
bt->flags |= BT_TEXT_SYMBOLS;
|
|
bt->flags |= BT_TEXT_SYMBOLS_ALL;
|
|
}
|
|
if ((fd->flags & FOREACH_o_FLAG) ||
|
|
(kt->flags & USE_OPT_BT))
|
|
bt->flags |= BT_OPT_BACK_TRACE;
|
|
if (fd->flags & FOREACH_e_FLAG)
|
|
bt->flags |= BT_EFRAME_SEARCH;
|
|
#ifdef GDB_5_3
|
|
if (fd->flags & FOREACH_g_FLAG)
|
|
bt->flags |= BT_USE_GDB;
|
|
#endif
|
|
if (fd->flags & FOREACH_l_FLAG)
|
|
bt->flags |= BT_LINE_NUMBERS;
|
|
if (fd->flags & FOREACH_f_FLAG)
|
|
bt->flags |= BT_FULL;
|
|
if (fd->flags & FOREACH_F_FLAG)
|
|
bt->flags |= (BT_FULL|BT_FULL_SYM_SLAB);
|
|
if (fd->flags & FOREACH_F_FLAG2)
|
|
bt->flags |= BT_FULL_SYM_SLAB2;
|
|
if (fd->flags & FOREACH_x_FLAG)
|
|
bt->radix = 16;
|
|
if (fd->flags & FOREACH_d_FLAG)
|
|
bt->radix = 10;
|
|
if (fd->reference)
|
|
bt->ref = ref;
|
|
back_trace(bt);
|
|
break;
|
|
|
|
case FOREACH_VM:
|
|
pc->curcmd = "vm";
|
|
cmdflags = 0;
|
|
if (fd->flags & FOREACH_x_FLAG)
|
|
cmdflags = PRINT_RADIX_16;
|
|
else if (fd->flags & FOREACH_d_FLAG)
|
|
cmdflags = PRINT_RADIX_10;
|
|
if (fd->flags & FOREACH_i_FLAG)
|
|
vm_area_dump(tc->task,
|
|
PRINT_INODES, 0, NULL);
|
|
else if (fd->flags & FOREACH_p_FLAG)
|
|
vm_area_dump(tc->task,
|
|
PHYSADDR, 0,
|
|
fd->reference ? ref : NULL);
|
|
else if (fd->flags & FOREACH_m_FLAG)
|
|
vm_area_dump(tc->task,
|
|
PRINT_MM_STRUCT|cmdflags, 0, NULL);
|
|
else if (fd->flags & FOREACH_v_FLAG)
|
|
vm_area_dump(tc->task,
|
|
PRINT_VMA_STRUCTS|cmdflags, 0, NULL);
|
|
else
|
|
vm_area_dump(tc->task, 0, 0,
|
|
fd->reference ? ref : NULL);
|
|
break;
|
|
|
|
case FOREACH_TASK:
|
|
pc->curcmd = "task";
|
|
if (fd->flags & FOREACH_x_FLAG)
|
|
radix = 16;
|
|
else if (fd->flags & FOREACH_d_FLAG)
|
|
radix = 10;
|
|
else
|
|
radix = pc->output_radix;
|
|
do_task(tc->task, FOREACH_TASK,
|
|
fd->reference ? ref : NULL,
|
|
radix);
|
|
break;
|
|
|
|
case FOREACH_SIG:
|
|
pc->curcmd = "sig";
|
|
if (fd->flags & FOREACH_g_FLAG) {
|
|
tgid = task_tgid(tc->task);
|
|
tgc = tgid_to_context(tgid);
|
|
if (hq_enter(tgc->task))
|
|
do_sig_thread_group(tgc->task);
|
|
} else
|
|
do_sig(tc->task, FOREACH_SIG,
|
|
fd->reference ? ref : NULL);
|
|
break;
|
|
|
|
case FOREACH_SET:
|
|
pc->curcmd = "set";
|
|
show_context(tc);
|
|
break;
|
|
|
|
case FOREACH_PS:
|
|
pc->curcmd = "ps";
|
|
psinfo.task[0] = tc->task;
|
|
psinfo.pid[0] = NO_PID;
|
|
psinfo.type[0] = PS_BY_TASK;
|
|
psinfo.argc = 1;
|
|
cmdflags = PS_BY_TASK;
|
|
if (subsequent++)
|
|
cmdflags |= PS_NO_HEADER;
|
|
if (fd->flags & FOREACH_G_FLAG)
|
|
cmdflags |= PS_GROUP;
|
|
if (fd->flags & FOREACH_s_FLAG)
|
|
cmdflags |= PS_KSTACKP;
|
|
if (fd->flags & FOREACH_y_FLAG) {
|
|
cmdflags |= PS_POLICY;
|
|
psinfo.policy = fd->policy;
|
|
}
|
|
/*
|
|
* mutually exclusive flags
|
|
*/
|
|
if (fd->flags & FOREACH_a_FLAG)
|
|
cmdflags |= PS_ARGV_ENVP;
|
|
else if (fd->flags & FOREACH_c_FLAG)
|
|
cmdflags |= PS_CHILD_LIST;
|
|
else if (fd->flags & FOREACH_p_FLAG)
|
|
cmdflags |= PS_PPID_LIST;
|
|
else if (fd->flags & FOREACH_t_FLAG)
|
|
cmdflags |= PS_TIMES;
|
|
else if (fd->flags & FOREACH_l_FLAG)
|
|
cmdflags |= PS_LAST_RUN;
|
|
else if (fd->flags & FOREACH_m_FLAG)
|
|
cmdflags |= PS_MSECS;
|
|
else if (fd->flags & FOREACH_r_FLAG)
|
|
cmdflags |= PS_RLIMIT;
|
|
else if (fd->flags & FOREACH_g_FLAG)
|
|
cmdflags |= PS_TGID_LIST;
|
|
show_ps(cmdflags, &psinfo);
|
|
break;
|
|
|
|
case FOREACH_FILES:
|
|
pc->curcmd = "files";
|
|
cmdflags = 0;
|
|
|
|
if (fd->flags & FOREACH_i_FLAG)
|
|
cmdflags |= PRINT_INODES;
|
|
if (fd->flags & FOREACH_c_FLAG)
|
|
cmdflags |= PRINT_NRPAGES;
|
|
|
|
open_files_dump(tc->task,
|
|
cmdflags,
|
|
fd->reference ? ref : NULL);
|
|
break;
|
|
|
|
case FOREACH_NET:
|
|
pc->curcmd = "net";
|
|
if (fd->flags & (FOREACH_s_FLAG|FOREACH_S_FLAG))
|
|
dump_sockets_workhorse(tc->task,
|
|
fd->flags,
|
|
fd->reference ? ref : NULL);
|
|
break;
|
|
|
|
case FOREACH_VTOP:
|
|
pc->curcmd = "vtop";
|
|
cmdflags = 0;
|
|
if (fd->flags & FOREACH_c_FLAG)
|
|
cmdflags |= USE_USER_PGD;
|
|
if (fd->flags & FOREACH_u_FLAG)
|
|
cmdflags |= UVADDR;
|
|
if (fd->flags & FOREACH_k_FLAG)
|
|
cmdflags |= KVADDR;
|
|
|
|
for (a = 0; a < fd->args; a++) {
|
|
do_vtop(htol((char *)fd->arg_array[a],
|
|
FAULT_ON_ERROR, NULL), tc,
|
|
cmdflags);
|
|
}
|
|
break;
|
|
|
|
case FOREACH_TEST:
|
|
pc->curcmd = "test";
|
|
foreach_test(tc->task, 0);
|
|
break;
|
|
}
|
|
|
|
pc->curcmd = "foreach";
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Post-process any commands requiring it.
|
|
*/
|
|
for (k = 0; k < fd->keys; k++) {
|
|
switch(fd->keyword_array[k])
|
|
{
|
|
case FOREACH_SIG:
|
|
if (fd->flags & FOREACH_g_FLAG)
|
|
hq_close();
|
|
break;
|
|
}
|
|
}
|
|
|
|
foreach_bailout:
|
|
|
|
pc->flags &= ~IN_FOREACH;
|
|
}
|
|
|
|
/*
|
|
* Clean up regex buffers and pattern strings.
|
|
*/
|
|
static void
|
|
foreach_cleanup(void *arg)
|
|
{
|
|
int i;
|
|
struct foreach_data *fd;
|
|
|
|
pc->cmd_cleanup = NULL;
|
|
pc->cmd_cleanup_arg = NULL;
|
|
|
|
fd = (struct foreach_data *)arg;
|
|
|
|
for (i = 0; i < fd->regexs; i++) {
|
|
regfree(&fd->regex_info[i].regex);
|
|
free(fd->regex_info[i].pattern);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The currently available set of foreach commands.
|
|
*/
|
|
static int
|
|
is_foreach_keyword(char *s, int *key)
|
|
{
|
|
if (STREQ(args[optind], "bt")) {
|
|
*key = FOREACH_BT;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "vm")) {
|
|
*key = FOREACH_VM;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "task")) {
|
|
*key = FOREACH_TASK;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "set")) {
|
|
*key = FOREACH_SET;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "files")) {
|
|
*key = FOREACH_FILES;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "net")) {
|
|
*key = FOREACH_NET;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "vtop")) {
|
|
*key = FOREACH_VTOP;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "sig")) {
|
|
*key = FOREACH_SIG;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "test")) {
|
|
*key = FOREACH_TEST;
|
|
return TRUE;
|
|
}
|
|
|
|
if (STREQ(args[optind], "ps")) {
|
|
*key = FOREACH_PS;
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Try the dumpfile-specific manner of finding the panic task first. If
|
|
* that fails, find the panic task the hard way -- do a "foreach bt" in the
|
|
* background, and look for the only one that has "panic" embedded in it.
|
|
*/
|
|
static struct task_context *
|
|
panic_search(void)
|
|
{
|
|
struct foreach_data foreach_data, *fd;
|
|
char *p1, *p2, *tp;
|
|
ulong lasttask, dietask, found;
|
|
char buf[BUFSIZE];
|
|
struct task_context *tc;
|
|
|
|
if ((lasttask = get_dumpfile_panic_task())) {
|
|
found = TRUE;
|
|
goto found_panic_task;
|
|
}
|
|
|
|
if (pc->flags2 & LIVE_DUMP)
|
|
return NULL;
|
|
|
|
BZERO(&foreach_data, sizeof(struct foreach_data));
|
|
fd = &foreach_data;
|
|
fd->keys = 1;
|
|
fd->keyword_array[0] = FOREACH_BT;
|
|
if (machine_type("S390X"))
|
|
fd->flags |= FOREACH_o_FLAG;
|
|
else if (machine_type("ARM64"))
|
|
fd->flags |= FOREACH_t_FLAG;
|
|
else
|
|
fd->flags |= (FOREACH_t_FLAG|FOREACH_o_FLAG);
|
|
|
|
dietask = lasttask = NO_TASK;
|
|
|
|
found = FALSE;
|
|
|
|
open_tmpfile();
|
|
|
|
foreach(fd);
|
|
|
|
rewind(pc->tmpfile);
|
|
|
|
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if ((p1 = strstr(buf, " TASK: "))) {
|
|
p1 += strlen(" TASK: ");
|
|
p2 = p1;
|
|
while (!whitespace(*p2))
|
|
p2++;
|
|
*p2 = NULLCHAR;
|
|
lasttask = htol(p1, RETURN_ON_ERROR, NULL);
|
|
}
|
|
|
|
if (strstr(buf, " panic at ")) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (strstr(buf, " crash_kexec at ") ||
|
|
strstr(buf, " .crash_kexec at ")) {
|
|
found = TRUE;
|
|
break;
|
|
}
|
|
|
|
if (strstr(buf, " die at ")) {
|
|
switch (dietask)
|
|
{
|
|
case NO_TASK:
|
|
dietask = lasttask;
|
|
break;
|
|
default:
|
|
if (dietask != lasttask)
|
|
dietask = NO_TASK+1;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
close_tmpfile();
|
|
|
|
pc->curcmd = pc->program_name;
|
|
|
|
if (!found && (dietask > (NO_TASK+1)) && task_has_cpu(dietask, NULL)) {
|
|
lasttask = dietask;
|
|
found = TRUE;
|
|
}
|
|
|
|
if (dietask == (NO_TASK+1))
|
|
error(WARNING, "multiple active tasks have called die\n\n");
|
|
|
|
if (CRASHDEBUG(1) && found)
|
|
error(INFO, "panic_search: %lx (via foreach bt)\n",
|
|
lasttask);
|
|
|
|
if (!found) {
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "panic_search: failed (via foreach bt)\n");
|
|
if ((lasttask = get_log_panic_task()))
|
|
found = TRUE;
|
|
}
|
|
|
|
found_panic_task:
|
|
populate_panic_threads();
|
|
|
|
if (found) {
|
|
if ((tc = task_to_context(lasttask)))
|
|
return tc;
|
|
|
|
/*
|
|
* If the task list was corrupted, add this one in.
|
|
*/
|
|
if ((tp = fill_task_struct(lasttask))) {
|
|
if ((tc = add_context(lasttask, tp)))
|
|
return tc;
|
|
}
|
|
}
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO, "panic_search: failed\n");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ulong
|
|
search_panic_task_by_cpu(char *buf)
|
|
{
|
|
int crashing_cpu;
|
|
char *p1, *p2;
|
|
ulong task = NO_TASK;
|
|
|
|
p1 = NULL;
|
|
|
|
if ((p1 = strstr(buf, "CPU: ")))
|
|
p1 += strlen("CPU: ");
|
|
else if (STRNEQ(buf, "CPU "))
|
|
p1 = buf + strlen("CPU ");
|
|
|
|
if (p1) {
|
|
p2 = p1;
|
|
while (!whitespace(*p2) && (*p2 != '\n'))
|
|
p2++;
|
|
*p2 = NULLCHAR;
|
|
crashing_cpu = dtol(p1, RETURN_ON_ERROR, NULL);
|
|
if ((crashing_cpu >= 0) && in_cpu_map(ONLINE_MAP, crashing_cpu)) {
|
|
task = tt->active_set[crashing_cpu];
|
|
if (CRASHDEBUG(1))
|
|
error(WARNING,
|
|
"get_log_panic_task: active_set[%d]: %lx\n",
|
|
crashing_cpu, tt->active_set[crashing_cpu]);
|
|
}
|
|
}
|
|
return task;
|
|
}
|
|
|
|
static ulong
|
|
search_panic_task_by_keywords(char *buf, int *found_flag)
|
|
{
|
|
char *p;
|
|
int i = 0;
|
|
ulong task;
|
|
|
|
while (panic_keywords[i]) {
|
|
if ((p = strstr(buf, panic_keywords[i]))) {
|
|
if ((task = search_panic_task_by_cpu(p))) {
|
|
*found_flag = FOUND_PANIC_TASK;
|
|
return task;
|
|
} else {
|
|
*found_flag = FOUND_PANIC_KEYWORD;
|
|
return NO_TASK;
|
|
}
|
|
}
|
|
i++;
|
|
}
|
|
*found_flag = FOUND_NO_PANIC_KEYWORD;
|
|
return NO_TASK;
|
|
}
|
|
|
|
/*
|
|
* Search for the panic task by seeking panic keywords from kernel log buffer.
|
|
* The panic keyword is generally followed by printing out the stack trace info
|
|
* of the panicking task. We can determine the panic task by finding the first
|
|
* instance of "CPU: " or "CPU " following the panic keywords.
|
|
*/
|
|
static ulong
|
|
get_log_panic_task(void)
|
|
{
|
|
int found_flag = FOUND_NO_PANIC_KEYWORD;
|
|
int found_panic_keyword = FALSE;
|
|
ulong task = NO_TASK;
|
|
char buf[BUFSIZE];
|
|
|
|
if (!get_active_set())
|
|
goto fail;
|
|
|
|
BZERO(buf, BUFSIZE);
|
|
open_tmpfile();
|
|
dump_log(SHOW_LOG_TEXT);
|
|
rewind(pc->tmpfile);
|
|
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
|
|
if (!found_panic_keyword) {
|
|
task = search_panic_task_by_keywords(buf, &found_flag);
|
|
switch (found_flag) {
|
|
case FOUND_PANIC_TASK:
|
|
goto found_panic_task;
|
|
case FOUND_PANIC_KEYWORD:
|
|
found_panic_keyword = TRUE;
|
|
continue;
|
|
default:
|
|
continue;
|
|
}
|
|
} else {
|
|
task = search_panic_task_by_cpu(buf);
|
|
if (task)
|
|
goto found_panic_task;
|
|
}
|
|
}
|
|
|
|
found_panic_task:
|
|
close_tmpfile();
|
|
fail:
|
|
if (CRASHDEBUG(1) && !task)
|
|
error(WARNING, "cannot determine the panic task from kernel log buffer\n");
|
|
|
|
return task;
|
|
}
|
|
|
|
/*
|
|
* Get the panic task from the appropriate dumpfile handler.
|
|
*/
|
|
static ulong
|
|
get_dumpfile_panic_task(void)
|
|
{
|
|
ulong task;
|
|
|
|
if (NETDUMP_DUMPFILE()) {
|
|
task = pc->flags & REM_NETDUMP ?
|
|
tt->panic_task : get_netdump_panic_task();
|
|
if (task)
|
|
return task;
|
|
} else if (KDUMP_DUMPFILE()) {
|
|
task = get_kdump_panic_task();
|
|
if (task)
|
|
return task;
|
|
} else if (DISKDUMP_DUMPFILE()) {
|
|
task = get_diskdump_panic_task();
|
|
if (task)
|
|
return task;
|
|
} else if (KVMDUMP_DUMPFILE()) {
|
|
task = get_kvmdump_panic_task();
|
|
if (task)
|
|
return task;
|
|
} else if (XENDUMP_DUMPFILE()) {
|
|
task = get_xendump_panic_task();
|
|
if (task)
|
|
return task;
|
|
} else if (LKCD_DUMPFILE())
|
|
return(get_lkcd_panic_task());
|
|
|
|
if (pc->flags2 & LIVE_DUMP)
|
|
return NO_TASK;
|
|
|
|
if (get_active_set())
|
|
return(get_active_set_panic_task());
|
|
|
|
return NO_TASK;
|
|
}
|
|
|
|
/*
|
|
* If runqueues is defined in the kernel, get the panic threads from the
|
|
* active set.
|
|
*
|
|
* If it's an LKCD dump, or for some other reason the active threads cannot
|
|
* be determined, do it the hard way.
|
|
*
|
|
* NOTE: this function should be deprecated -- the work should have been
|
|
* done in the initial task table refresh.
|
|
*/
|
|
static void
|
|
populate_panic_threads(void)
|
|
{
|
|
int i;
|
|
int found;
|
|
struct task_context *tc;
|
|
|
|
if (get_active_set()) {
|
|
for (i = 0; i < NR_CPUS; i++)
|
|
tt->panic_threads[i] = tt->active_set[i];
|
|
return;
|
|
}
|
|
|
|
found = 0;
|
|
if (!(machdep->flags & HWRESET)) {
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if (tt->panic_threads[i]) {
|
|
if (++found == kt->cpus)
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (task_has_cpu(tc->task, NULL) &&
|
|
(tc->processor >= 0) &&
|
|
(tc->processor < NR_CPUS)) {
|
|
tt->panic_threads[tc->processor] = tc->task;
|
|
found++;
|
|
}
|
|
}
|
|
|
|
if (!found && !(kt->flags & SMP) &&
|
|
(LKCD_DUMPFILE() || NETDUMP_DUMPFILE() ||
|
|
KDUMP_DUMPFILE() || DISKDUMP_DUMPFILE() || KVMDUMP_DUMPFILE()))
|
|
tt->panic_threads[0] = get_dumpfile_panic_task();
|
|
}
|
|
|
|
/*
|
|
* Separate the foreach command's output on a task-by-task basis by
|
|
* displaying this header string.
|
|
*/
|
|
void
|
|
print_task_header(FILE *out, struct task_context *tc, int newline)
|
|
{
|
|
char buf[BUFSIZE];
|
|
char buf1[BUFSIZE];
|
|
|
|
fprintf(out, "%sPID: %-5ld TASK: %s CPU: %-2s COMMAND: \"%s\"\n",
|
|
newline ? "\n" : "", tc->pid,
|
|
mkstring(buf1, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(tc->task)),
|
|
task_cpu(tc->processor, buf, !VERBOSE), tc->comm);
|
|
}
|
|
|
|
/*
|
|
* "help -t" output
|
|
*/
|
|
void
|
|
dump_task_table(int verbose)
|
|
{
|
|
int i, j, more, nr_cpus;
|
|
struct task_context *tc;
|
|
struct tgid_context *tg;
|
|
char buf[BUFSIZE];
|
|
int others, wrap, flen;
|
|
|
|
tc = tt->current;
|
|
others = 0;
|
|
more = FALSE;
|
|
|
|
fprintf(fp, " current: %lx [%ld]\n", (ulong)tt->current,
|
|
(ulong)(tt->current - tt->context_array));
|
|
if (tt->current) {
|
|
fprintf(fp, " .pid: %ld\n", tc->pid);
|
|
fprintf(fp, " .comm: \"%s\"\n", tc->comm);
|
|
fprintf(fp, " .task: %lx\n", tc->task);
|
|
fprintf(fp, " .thread_info: %lx\n", tc->thread_info);
|
|
fprintf(fp, " .processor: %d\n", tc->processor);
|
|
fprintf(fp, " .ptask: %lx\n", tc->ptask);
|
|
fprintf(fp, " .mm_struct: %lx\n", tc->mm_struct);
|
|
fprintf(fp, " .tc_next: %lx\n", (ulong)tc->tc_next);
|
|
}
|
|
fprintf(fp, " context_array: %lx\n", (ulong)tt->context_array);
|
|
fprintf(fp, " context_by_task: %lx\n", (ulong)tt->context_by_task);
|
|
fprintf(fp, " tgid_array: %lx\n", (ulong)tt->tgid_array);
|
|
fprintf(fp, " tgid_searches: %ld\n", tt->tgid_searches);
|
|
fprintf(fp, " tgid_cache_hits: %ld (%ld%%)\n", tt->tgid_cache_hits,
|
|
tt->tgid_searches ?
|
|
tt->tgid_cache_hits * 100 / tt->tgid_searches : 0);
|
|
fprintf(fp, " last_tgid: %lx\n", (ulong)tt->last_tgid);
|
|
fprintf(fp, "refresh_task_table: ");
|
|
if (tt->refresh_task_table == refresh_fixed_task_table)
|
|
fprintf(fp, "refresh_fixed_task_table()\n");
|
|
else if (tt->refresh_task_table == refresh_unlimited_task_table)
|
|
fprintf(fp, "refresh_unlimited_task_table()\n");
|
|
else if (tt->refresh_task_table == refresh_pidhash_task_table)
|
|
fprintf(fp, "refresh_pidhash_task_table()\n");
|
|
else if (tt->refresh_task_table == refresh_pid_hash_task_table)
|
|
fprintf(fp, "refresh_pid_hash_task_table()\n");
|
|
else if (tt->refresh_task_table == refresh_hlist_task_table)
|
|
fprintf(fp, "refresh_hlist_task_table()\n");
|
|
else if (tt->refresh_task_table == refresh_hlist_task_table_v2)
|
|
fprintf(fp, "refresh_hlist_task_table_v2()\n");
|
|
else if (tt->refresh_task_table == refresh_hlist_task_table_v3)
|
|
fprintf(fp, "refresh_hlist_task_table_v3()\n");
|
|
else if (tt->refresh_task_table == refresh_active_task_table)
|
|
fprintf(fp, "refresh_active_task_table()\n");
|
|
else if (tt->refresh_task_table == refresh_radix_tree_task_table)
|
|
fprintf(fp, "refresh_radix_tree_task_table()\n");
|
|
else if (tt->refresh_task_table == refresh_xarray_task_table)
|
|
fprintf(fp, "refresh_xarray_task_table()\n");
|
|
else
|
|
fprintf(fp, "%lx\n", (ulong)tt->refresh_task_table);
|
|
|
|
buf[0] = NULLCHAR;
|
|
fprintf(fp, " flags: %lx ", tt->flags);
|
|
sprintf(buf, "(");
|
|
if (tt->flags & TASK_INIT_DONE)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sTASK_INIT_DONE", others++ ? "|" : "");
|
|
if (tt->flags & TASK_ARRAY_EXISTS)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sTASK_ARRAY_EXISTS", others++ ? "|" : "");
|
|
if (tt->flags & PANIC_TASK_NOT_FOUND)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sPANIC_TASK_NOT_FOUND", others++ ? "|" : "");
|
|
if (tt->flags & TASK_REFRESH)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sTASK_REFRESH", others++ ? "|" : "");
|
|
if (tt->flags & TASK_REFRESH_OFF)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sTASK_REFRESH_OFF", others++ ? "|" : "");
|
|
if (tt->flags & PANIC_KSP)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sPANIC_KSP", others++ ? "|" : "");
|
|
if (tt->flags & POPULATE_PANIC)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sPOPULATE_PANIC", others++ ? "|" : "");
|
|
if (tt->flags & ACTIVE_SET)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sACTIVE_SET", others++ ? "|" : "");
|
|
if (tt->flags & PIDHASH)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sPIDHASH", others++ ? "|" : "");
|
|
if (tt->flags & PID_HASH)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sPID_HASH", others++ ? "|" : "");
|
|
if (tt->flags & PID_RADIX_TREE)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sPID_RADIX_TREE", others++ ? "|" : "");
|
|
if (tt->flags & PID_XARRAY)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sPID_XARRAY", others++ ? "|" : "");
|
|
if (tt->flags & THREAD_INFO)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sTHREAD_INFO", others++ ? "|" : "");
|
|
if (tt->flags & THREAD_INFO_IN_TASK)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sTHREAD_INFO_IN_TASK", others++ ? "|" : "");
|
|
if (tt->flags & IRQSTACKS)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sIRQSTACKS", others++ ? "|" : "");
|
|
if (tt->flags & TIMESPEC)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sTIMESPEC", others++ ? "|" : "");
|
|
if (tt->flags & NO_TIMESPEC)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sNO_TIMESPEC", others++ ? "|" : "");
|
|
if (tt->flags & START_TIME_NSECS)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sSTART_TIME_NSECS", others++ ? "|" : "");
|
|
if (tt->flags & ACTIVE_ONLY)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sACTIVE_ONLY", others++ ? "|" : "");
|
|
if (tt->flags & INDEXED_CONTEXTS)
|
|
sprintf(&buf[strlen(buf)],
|
|
"%sINDEXED_CONTEXTS", others++ ? "|" : "");
|
|
sprintf(&buf[strlen(buf)], ")");
|
|
|
|
if (strlen(buf) > 54)
|
|
fprintf(fp, "\n%s\n", mkstring(buf, 80, CENTER|LJUST, NULL));
|
|
else
|
|
fprintf(fp, "%s\n", buf);
|
|
|
|
fprintf(fp, " task_start: %lx\n", tt->task_start);
|
|
fprintf(fp, " task_end: %lx\n", tt->task_end);
|
|
fprintf(fp, " task_local: %lx\n", (ulong)tt->task_local);
|
|
fprintf(fp, " max_tasks: %d\n", tt->max_tasks);
|
|
fprintf(fp, " pid_radix_tree: %lx\n", tt->pid_radix_tree);
|
|
fprintf(fp, " pid_xarray: %lx\n", tt->pid_xarray);
|
|
fprintf(fp, " callbacks: %d\n", tt->callbacks);
|
|
fprintf(fp, " nr_threads: %d\n", tt->nr_threads);
|
|
fprintf(fp, " running_tasks: %ld\n", tt->running_tasks);
|
|
fprintf(fp, " retries: %ld\n", tt->retries);
|
|
fprintf(fp, " panicmsg: \"%s\"\n",
|
|
strip_linefeeds(get_panicmsg(buf)));
|
|
fprintf(fp, " panic_processor: %d\n", tt->panic_processor);
|
|
fprintf(fp, " panic_task: %lx\n", tt->panic_task);
|
|
fprintf(fp, " this_task: %lx\n", tt->this_task);
|
|
fprintf(fp, " pidhash_len: %d\n", tt->pidhash_len);
|
|
fprintf(fp, " pidhash_addr: %lx\n", tt->pidhash_addr);
|
|
fprintf(fp, " last_task_read: %lx\n", tt->last_task_read);
|
|
fprintf(fp, " last_mm_read: %lx\n", tt->last_mm_read);
|
|
fprintf(fp, " task_struct: %lx\n", (ulong)tt->task_struct);
|
|
fprintf(fp, " mm_struct: %lx\n", (ulong)tt->mm_struct);
|
|
fprintf(fp, " init_pid_ns: %lx\n", tt->init_pid_ns);
|
|
fprintf(fp, " filepages: %ld\n", tt->filepages);
|
|
fprintf(fp, " anonpages: %ld\n", tt->anonpages);
|
|
fprintf(fp, " stack_end_magic: %lx\n", tt->stack_end_magic);
|
|
fprintf(fp, " pf_kthread: %lx ", tt->pf_kthread);
|
|
switch (tt->pf_kthread)
|
|
{
|
|
case UNINITIALIZED:
|
|
fprintf(fp, "(UNINITIALIZED)\n");
|
|
break;
|
|
case 0:
|
|
fprintf(fp, "(n/a)\n");
|
|
break;
|
|
default:
|
|
fprintf(fp, "(PF_KTHREAD)\n");
|
|
break;
|
|
}
|
|
|
|
wrap = sizeof(void *) == SIZEOF_32BIT ? 8 : 4;
|
|
flen = sizeof(void *) == SIZEOF_32BIT ? 8 : 16;
|
|
|
|
nr_cpus = kt->kernel_NR_CPUS ? kt->kernel_NR_CPUS : NR_CPUS;
|
|
|
|
|
|
fprintf(fp, " idle_threads:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->idle_threads) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->idle_threads[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->idle_threads[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " active_set:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->active_set) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->active_set[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->active_set[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " panic_threads:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->panic_threads) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->panic_threads[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->panic_threads[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " panic_ksp:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->panic_ksp) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->panic_ksp[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->panic_ksp[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " hardirq_ctx:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->hardirq_ctx) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->hardirq_ctx[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->hardirq_ctx[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " hardirq_tasks:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->hardirq_tasks) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->hardirq_tasks[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->hardirq_tasks[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " softirq_ctx:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->softirq_ctx) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->softirq_ctx[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->softirq_ctx[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " softirq_tasks:");
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (!tt->softirq_tasks) {
|
|
fprintf(fp, " (unused)");
|
|
break;
|
|
}
|
|
if ((i % wrap) == 0) {
|
|
fprintf(fp, "\n ");
|
|
for (j = i, more = FALSE; j < nr_cpus; j++) {
|
|
if (tt->softirq_tasks[j]) {
|
|
more = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fprintf(fp, "%.*lx ", flen, tt->softirq_tasks[i]);
|
|
if (!more) {
|
|
fprintf(fp, "...");
|
|
break;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
dump_task_states();
|
|
|
|
if (!verbose)
|
|
return;
|
|
|
|
if (tt->flags & THREAD_INFO)
|
|
fprintf(fp,
|
|
"\nINDEX TASK/THREAD_INFO PID CPU PTASK MM_STRUCT COMM\n");
|
|
else
|
|
fprintf(fp,
|
|
"\nINDEX TASK PID CPU PTASK MM_STRUCT COMM\n");
|
|
tc = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (tt->flags & THREAD_INFO)
|
|
fprintf(fp,
|
|
"[%3d] %08lx/%08lx %5ld %d %08lx %016lx %s\n",
|
|
i, tc->task, tc->thread_info, tc->pid,
|
|
tc->processor, tc->ptask, (ulong)tc->mm_struct,
|
|
tc->comm);
|
|
else
|
|
fprintf(fp, "[%3d] %08lx %5ld %d %08lx %08lx %s\n",
|
|
i, tc->task, tc->pid, tc->processor, tc->ptask,
|
|
(ulong)tc->mm_struct, tc->comm);
|
|
}
|
|
|
|
fprintf(fp, "\nINDEX TASK TGID (COMM)\n");
|
|
for (i = 0; i < RUNNING_TASKS(); i++) {
|
|
tg = &tt->tgid_array[i];
|
|
tc = task_to_context(tg->task);
|
|
fprintf(fp, "[%3d] %lx %ld (%s)\n", i, tg->task, tg->tgid, tc->comm);
|
|
}
|
|
|
|
fprintf(fp, "\nINDEX TASK (COMM)\n");
|
|
for (i = 0; i < RUNNING_TASKS(); i++) {
|
|
tc = tt->context_by_task[i];
|
|
fprintf(fp, "[%3d] %lx (%s)\n", i, tc->task, tc->comm);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Determine whether a task is a kernel thread. This would seem easier than
|
|
* it looks, but on live systems it's easy to get faked out.
|
|
*/
|
|
int
|
|
is_kernel_thread(ulong task)
|
|
{
|
|
struct task_context *tc;
|
|
ulong mm;
|
|
|
|
if (tt->pf_kthread == UNINITIALIZED) {
|
|
if (THIS_KERNEL_VERSION >= LINUX(2,6,27)) {
|
|
tt->pf_kthread = PF_KTHREAD;
|
|
|
|
if ((tc = pid_to_context(0)) &&
|
|
!(task_flags(tc->task) & PF_KTHREAD)) {
|
|
error(WARNING, "pid 0: PF_KTHREAD not set?\n");
|
|
tt->pf_kthread = 0;
|
|
}
|
|
if ((tc = pid_to_context(1)) &&
|
|
task_mm(tc->task, FALSE) &&
|
|
(task_flags(tc->task) & PF_KTHREAD)) {
|
|
error(WARNING, "pid 1: PF_KTHREAD set?\n");
|
|
tt->pf_kthread = 0;
|
|
}
|
|
} else
|
|
tt->pf_kthread = 0;
|
|
}
|
|
|
|
if (tt->pf_kthread)
|
|
return (task_flags(task) & tt->pf_kthread ? TRUE : FALSE);
|
|
|
|
tc = task_to_context(task);
|
|
|
|
if ((tc->pid == 0) && !STREQ(tc->comm, pc->program_name))
|
|
return TRUE;
|
|
|
|
if (_ZOMBIE_ == TASK_STATE_UNINITIALIZED)
|
|
initialize_task_state();
|
|
|
|
if (IS_ZOMBIE(task) || IS_EXITING(task))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Check for shifting sands on a live system.
|
|
*/
|
|
mm = task_mm(task, TRUE);
|
|
|
|
if (ACTIVE() && (mm != tc->mm_struct))
|
|
return FALSE;
|
|
|
|
/*
|
|
* Later version Linux kernel threads have no mm_struct at all.
|
|
* Earlier version kernel threads point to common init_mm.
|
|
*/
|
|
if (!tc->mm_struct) {
|
|
if (IS_EXITING(task))
|
|
return FALSE;
|
|
|
|
if (!task_state(task) && !task_flags(task))
|
|
return FALSE;
|
|
|
|
return TRUE;
|
|
|
|
} else if (tc->mm_struct == symbol_value("init_mm"))
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/*
|
|
* Checks if task policy corresponds to given mask.
|
|
*/
|
|
static int
|
|
has_sched_policy(ulong task, ulong policy)
|
|
{
|
|
return !!(task_policy(task) & policy);
|
|
}
|
|
|
|
/*
|
|
* Converts sched policy name into mask bit.
|
|
*/
|
|
static ulong
|
|
sched_policy_bit_from_str(const char *policy_str)
|
|
{
|
|
struct sched_policy_info *info = NULL;
|
|
ulong policy = 0;
|
|
int found = 0;
|
|
char *upper = NULL;
|
|
/*
|
|
* Once kernel gets more than 10 scheduling policies,
|
|
* sizes of these arrays should be adjusted
|
|
*/
|
|
char digit[2] = { 0, 0 };
|
|
char hex[4] = { 0, 0, 0, 0 };
|
|
|
|
upper = GETBUF(strlen(policy_str) + 1);
|
|
upper_case(policy_str, upper);
|
|
|
|
for (info = sched_policy_info; info->name; info++) {
|
|
snprintf(digit, sizeof digit, "%lu", info->value);
|
|
/*
|
|
* Not using %#lX format here since "0X" prefix
|
|
* is not prepended if 0 value is given
|
|
*/
|
|
snprintf(hex, sizeof hex, "0X%lX", info->value);
|
|
if (strncmp(upper, info->name, strlen(info->name)) == 0 ||
|
|
strncmp(upper, digit, sizeof digit) == 0 ||
|
|
strncmp(upper, hex, sizeof hex) == 0) {
|
|
policy = 1 << info->value;
|
|
found = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
FREEBUF(upper);
|
|
|
|
if (!found)
|
|
error(FATAL,
|
|
"%s: invalid scheduling policy\n", policy_str);
|
|
|
|
return policy;
|
|
}
|
|
|
|
/*
|
|
* Converts sched policy string set into bitmask.
|
|
*/
|
|
static ulong
|
|
make_sched_policy(const char *policy_str)
|
|
{
|
|
ulong policy = 0;
|
|
char *iter = NULL;
|
|
char *orig = NULL;
|
|
char *cur = NULL;
|
|
|
|
iter = STRDUPBUF(policy_str);
|
|
orig = iter;
|
|
|
|
while ((cur = strsep(&iter, ",")))
|
|
policy |= sched_policy_bit_from_str(cur);
|
|
|
|
FREEBUF(orig);
|
|
|
|
return policy;
|
|
}
|
|
|
|
/*
|
|
* Gather an arry of pointers to the per-cpu idle tasks. The tasklist
|
|
* argument must be at least the size of ulong[NR_CPUS]. There may be
|
|
* junk in everything after the first entry on a single CPU box, so the
|
|
* data gathered may be throttled by kt->cpus.
|
|
*/
|
|
void
|
|
get_idle_threads(ulong *tasklist, int nr_cpus)
|
|
{
|
|
int i, cnt;
|
|
ulong runq, runqaddr;
|
|
char *runqbuf;
|
|
struct syment *rq_sp;
|
|
|
|
BZERO(tasklist, sizeof(ulong) * NR_CPUS);
|
|
runqbuf = NULL;
|
|
cnt = 0;
|
|
|
|
if ((rq_sp = per_cpu_symbol_search("per_cpu__runqueues")) &&
|
|
VALID_MEMBER(runqueue_idle)) {
|
|
runqbuf = GETBUF(SIZE(runqueue));
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
if (machine_type("SPARC64") &&
|
|
cpu_map_addr("possible") &&
|
|
!(in_cpu_map(POSSIBLE, i)))
|
|
continue;
|
|
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[i];
|
|
else
|
|
runq = rq_sp->value;
|
|
|
|
readmem(runq, KVADDR, runqbuf,
|
|
SIZE(runqueue), "runqueues entry (per_cpu)",
|
|
FAULT_ON_ERROR);
|
|
tasklist[i] = ULONG(runqbuf + OFFSET(runqueue_idle));
|
|
if (IS_KVADDR(tasklist[i]))
|
|
cnt++;
|
|
}
|
|
} else if (symbol_exists("runqueues") && VALID_MEMBER(runqueue_idle)) {
|
|
runq = symbol_value("runqueues");
|
|
runqbuf = GETBUF(SIZE(runqueue));
|
|
for (i = 0; i < nr_cpus; i++, runq += SIZE(runqueue)) {
|
|
readmem(runq, KVADDR, runqbuf,
|
|
SIZE(runqueue), "runqueues entry (old)",
|
|
FAULT_ON_ERROR);
|
|
tasklist[i] = ULONG(runqbuf + OFFSET(runqueue_idle));
|
|
if (IS_KVADDR(tasklist[i]))
|
|
cnt++;
|
|
}
|
|
} else if (symbol_exists("runqueues") && VALID_MEMBER(runqueue_cpu)) {
|
|
runq = symbol_value("runqueues");
|
|
runqbuf = GETBUF(SIZE(runqueue));
|
|
|
|
for (i = 0; i < nr_cpus; i++) {
|
|
runqaddr = runq + (SIZE(runqueue) * rq_idx(i));
|
|
readmem(runqaddr, KVADDR, runqbuf,
|
|
SIZE(runqueue), "runqueues entry",
|
|
FAULT_ON_ERROR);
|
|
if ((tasklist[i] = get_idle_task(i, runqbuf)))
|
|
cnt++;
|
|
}
|
|
} else if (symbol_exists("init_tasks")) {
|
|
readmem(symbol_value("init_tasks"), KVADDR, tasklist,
|
|
sizeof(void *) * nr_cpus, "init_tasks array",
|
|
FAULT_ON_ERROR);
|
|
if (IS_KVADDR(tasklist[0]))
|
|
cnt++;
|
|
else
|
|
BZERO(tasklist, sizeof(ulong) * NR_CPUS);
|
|
} else if (OPENVZ()) {
|
|
runq = symbol_value("pcpu_info");
|
|
runqbuf = GETBUF(SIZE(pcpu_info));
|
|
for (i = 0; i < nr_cpus; i++, runq += SIZE(pcpu_info)) {
|
|
readmem(runq, KVADDR, runqbuf, SIZE(pcpu_info),
|
|
"pcpu info", FAULT_ON_ERROR);
|
|
tasklist[i] = ULONG(runqbuf + OFFSET(pcpu_info_idle));
|
|
if (IS_KVADDR(tasklist[i]))
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
if (runqbuf)
|
|
FREEBUF(runqbuf);
|
|
|
|
if (!cnt) {
|
|
error(INFO,
|
|
"cannot determine idle task addresses from init_tasks[] or runqueues[]\n");
|
|
tasklist[0] = symbol_value("init_task_union");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Emulate the kernel rq_idx() macro.
|
|
*/
|
|
static long
|
|
rq_idx(int cpu)
|
|
{
|
|
if (kt->runq_siblings == 1)
|
|
return cpu;
|
|
else if (!(kt->__rq_idx))
|
|
return 0;
|
|
else
|
|
return kt->__rq_idx[cpu];
|
|
}
|
|
|
|
/*
|
|
* Emulate the kernel cpu_idx() macro.
|
|
*/
|
|
static long
|
|
cpu_idx(int cpu)
|
|
{
|
|
if (kt->runq_siblings == 1)
|
|
return 0;
|
|
else if (!(kt->__cpu_idx))
|
|
return 0;
|
|
else
|
|
return kt->__cpu_idx[cpu];
|
|
}
|
|
|
|
/*
|
|
* Dig out the idle task data from a runqueue structure.
|
|
*/
|
|
static ulong
|
|
get_idle_task(int cpu, char *runqbuf)
|
|
{
|
|
ulong idle_task;
|
|
|
|
idle_task = ULONG(runqbuf + OFFSET(runqueue_cpu) +
|
|
(SIZE(cpu_s) * cpu_idx(cpu)) + OFFSET(cpu_s_idle));
|
|
|
|
if (IS_KVADDR(idle_task))
|
|
return idle_task;
|
|
else {
|
|
if (cpu < kt->cpus)
|
|
error(INFO,
|
|
"cannot determine idle task for cpu %d\n", cpu);
|
|
return NO_TASK;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Dig out the current task data from a runqueue structure.
|
|
*/
|
|
static ulong
|
|
get_curr_task(int cpu, char *runqbuf)
|
|
{
|
|
ulong curr_task;
|
|
|
|
curr_task = ULONG(runqbuf + OFFSET(runqueue_cpu) +
|
|
(SIZE(cpu_s) * cpu_idx(cpu)) + OFFSET(cpu_s_curr));
|
|
|
|
if (IS_KVADDR(curr_task))
|
|
return curr_task;
|
|
else
|
|
return NO_TASK;
|
|
}
|
|
|
|
/*
|
|
* On kernels with runqueue[] array, store the active set of tasks.
|
|
*/
|
|
int
|
|
get_active_set(void)
|
|
{
|
|
int i, cnt;
|
|
ulong runq, runqaddr;
|
|
char *runqbuf;
|
|
struct syment *rq_sp;
|
|
|
|
if (tt->flags & ACTIVE_SET)
|
|
return TRUE;
|
|
|
|
runq = 0;
|
|
rq_sp = per_cpu_symbol_search("per_cpu__runqueues");
|
|
|
|
if (!rq_sp) {
|
|
if (symbol_exists("runqueues"))
|
|
runq = symbol_value("runqueues");
|
|
else if (OPENVZ())
|
|
runq = symbol_value("pcpu_info");
|
|
else
|
|
return FALSE;
|
|
} else
|
|
runq = rq_sp->value;
|
|
|
|
if (!tt->active_set &&
|
|
!(tt->active_set = (ulong *)calloc(NR_CPUS, sizeof(ulong))))
|
|
error(FATAL, "cannot malloc active_set array");
|
|
|
|
runqbuf = GETBUF(SIZE(runqueue));
|
|
cnt = 0;
|
|
|
|
if (OPENVZ()) {
|
|
ulong vcpu_struct;
|
|
char *pcpu_info_buf, *vcpu_struct_buf;
|
|
|
|
pcpu_info_buf = GETBUF(SIZE(pcpu_info));
|
|
vcpu_struct_buf = GETBUF(SIZE(vcpu_struct));
|
|
|
|
for (i = 0; i < kt->cpus; i++, runq += SIZE(pcpu_info)) {
|
|
readmem(runq, KVADDR, pcpu_info_buf,
|
|
SIZE(pcpu_info), "pcpu_info", FAULT_ON_ERROR);
|
|
vcpu_struct= ULONG(pcpu_info_buf +
|
|
OFFSET(pcpu_info_vcpu));
|
|
readmem(vcpu_struct, KVADDR, vcpu_struct_buf,
|
|
SIZE(vcpu_struct), "pcpu_info->vcpu",
|
|
FAULT_ON_ERROR);
|
|
tt->active_set[i] = ULONG(vcpu_struct_buf +
|
|
OFFSET(vcpu_struct_rq) + OFFSET(runqueue_curr));
|
|
if (IS_KVADDR(tt->active_set[i]))
|
|
cnt++;
|
|
}
|
|
FREEBUF(pcpu_info_buf);
|
|
FREEBUF(vcpu_struct_buf);
|
|
} else if (VALID_MEMBER(runqueue_curr) && rq_sp) {
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[i];
|
|
else
|
|
runq = rq_sp->value;
|
|
|
|
readmem(runq, KVADDR, runqbuf, SIZE(runqueue),
|
|
"active runqueues entry (per_cpu)",
|
|
FAULT_ON_ERROR);
|
|
|
|
tt->active_set[i] = ULONG(runqbuf +
|
|
OFFSET(runqueue_curr));
|
|
if (IS_KVADDR(tt->active_set[i]))
|
|
cnt++;
|
|
}
|
|
} else if (VALID_MEMBER(runqueue_curr)) {
|
|
for (i = 0; i < MAX(kt->cpus, kt->kernel_NR_CPUS); i++,
|
|
runq += SIZE(runqueue)) {
|
|
readmem(runq, KVADDR, runqbuf,
|
|
SIZE(runqueue), "(old) runqueues curr",
|
|
FAULT_ON_ERROR);
|
|
tt->active_set[i] = ULONG(runqbuf +
|
|
OFFSET(runqueue_curr));
|
|
if (IS_KVADDR(tt->active_set[i]))
|
|
cnt++;
|
|
}
|
|
} else if (VALID_MEMBER(runqueue_cpu)) {
|
|
for (i = 0; i < kt->cpus; i++) {
|
|
runqaddr = runq + (SIZE(runqueue) * rq_idx(i));
|
|
readmem(runqaddr, KVADDR, runqbuf,
|
|
SIZE(runqueue), "runqueues curr",
|
|
FAULT_ON_ERROR);
|
|
if ((tt->active_set[i] = get_curr_task(i, runqbuf)))
|
|
cnt++;
|
|
}
|
|
}
|
|
|
|
if (cnt) {
|
|
tt->flags |= ACTIVE_SET;
|
|
return TRUE;
|
|
} else {
|
|
error(INFO, "get_active_set: no tasks found?\n");
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Clear the ACTIVE_SET flag on a live system, forcing a re-read of the
|
|
* runqueues[] array the next time get_active_set() is called above.
|
|
*/
|
|
void
|
|
clear_active_set(void)
|
|
{
|
|
if (ACTIVE() && (tt->flags & TASK_REFRESH))
|
|
tt->flags &= ~ACTIVE_SET;
|
|
}
|
|
|
|
#define RESOLVE_PANIC_AND_DIE_CALLERS() \
|
|
if (xen_panic_task) { \
|
|
if (CRASHDEBUG(1)) \
|
|
error(INFO, \
|
|
"get_active_set_panic_task: %lx (xen_panic_event)\n", \
|
|
xen_panic_task); \
|
|
return xen_panic_task; \
|
|
} \
|
|
if (crash_kexec_task) { \
|
|
if (CRASHDEBUG(1)) \
|
|
error(INFO, \
|
|
"get_active_set_panic_task: %lx (crash_kexec)\n", \
|
|
crash_kexec_task); \
|
|
return crash_kexec_task; \
|
|
} \
|
|
if (crash_fadump_task) { \
|
|
if (CRASHDEBUG(1)) \
|
|
error(INFO, \
|
|
"get_active_set_panic_task: %lx (crash_fadump)\n", \
|
|
crash_fadump_task); \
|
|
return crash_fadump_task; \
|
|
} \
|
|
if ((panic_task > (NO_TASK+1)) && !die_task) { \
|
|
if (CRASHDEBUG(1)) \
|
|
fprintf(fp, \
|
|
"get_active_set_panic_task: %lx (panic)\n", \
|
|
panic_task); \
|
|
return panic_task; \
|
|
} \
|
|
\
|
|
if (panic_task && die_task) { \
|
|
if ((panic_task > (NO_TASK+1)) && \
|
|
(panic_task == die_task)) { \
|
|
if (CRASHDEBUG(1)) \
|
|
fprintf(fp, \
|
|
"get_active_set_panic_task: %lx (panic)\n", \
|
|
panic_task); \
|
|
return panic_task; \
|
|
} \
|
|
error(WARNING, \
|
|
"multiple active tasks have called die and/or panic\n\n"); \
|
|
goto no_panic_task_found; \
|
|
} \
|
|
\
|
|
if (die_task > (NO_TASK+1)) { \
|
|
if (CRASHDEBUG(1)) \
|
|
fprintf(fp, \
|
|
"get_active_set_panic_task: %lx (die)\n", \
|
|
die_task); \
|
|
return die_task; \
|
|
} \
|
|
else if (die_task == (NO_TASK+1)) \
|
|
error(WARNING, \
|
|
"multiple active tasks have called die\n\n");
|
|
|
|
#define SEARCH_STACK_FOR_PANIC_DIE_AND_KEXEC_CALLERS() \
|
|
while (fgets(buf, BUFSIZE, pc->tmpfile)) { \
|
|
if (strstr(buf, " die+")) { \
|
|
switch (die_task) \
|
|
{ \
|
|
case NO_TASK: \
|
|
die_task = task; \
|
|
break; \
|
|
default: \
|
|
if (die_task != task) \
|
|
die_task = NO_TASK+1; \
|
|
break; \
|
|
} \
|
|
} \
|
|
if (strstr(buf, " panic+")) { \
|
|
switch (panic_task) \
|
|
{ \
|
|
case NO_TASK: \
|
|
panic_task = task; \
|
|
if (XENDUMP_DUMPFILE()) \
|
|
xendump_panic_hook(buf); \
|
|
break; \
|
|
default: \
|
|
if (panic_task != task) \
|
|
panic_task = NO_TASK+1; \
|
|
break; \
|
|
} \
|
|
} \
|
|
if (strstr(buf, " crash_kexec+") || \
|
|
strstr(buf, " .crash_kexec+")) { \
|
|
crash_kexec_task = task; \
|
|
} \
|
|
if (strstr(buf, " .crash_fadump+")) \
|
|
crash_fadump_task = task; \
|
|
if (strstr(buf, " machine_kexec+") || \
|
|
strstr(buf, " .machine_kexec+")) { \
|
|
crash_kexec_task = task; \
|
|
} \
|
|
if (strstr(buf, " xen_panic_event+") || \
|
|
strstr(buf, " .xen_panic_event+")){ \
|
|
xen_panic_task = task; \
|
|
xendump_panic_hook(buf); \
|
|
} \
|
|
if (machine_type("IA64") && XENDUMP_DUMPFILE() && !xen_panic_task && \
|
|
strstr(buf, " sysrq_handle_crashdump+")) \
|
|
xen_sysrq_task = task; \
|
|
}
|
|
|
|
/*
|
|
* Search the active set tasks for instances of die or panic calls.
|
|
*/
|
|
static ulong
|
|
get_active_set_panic_task()
|
|
{
|
|
int i, j, found;
|
|
ulong task;
|
|
char buf[BUFSIZE];
|
|
ulong panic_task, die_task, crash_kexec_task, crash_fadump_task;
|
|
ulong xen_panic_task;
|
|
ulong xen_sysrq_task;
|
|
|
|
panic_task = die_task = crash_kexec_task = xen_panic_task = NO_TASK;
|
|
xen_sysrq_task = NO_TASK;
|
|
crash_fadump_task = NO_TASK;
|
|
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!(task = tt->active_set[i]) || !task_exists(task))
|
|
continue;
|
|
|
|
open_tmpfile();
|
|
raw_stack_dump(GET_STACKBASE(task), STACKSIZE());
|
|
rewind(pc->tmpfile);
|
|
|
|
SEARCH_STACK_FOR_PANIC_DIE_AND_KEXEC_CALLERS();
|
|
|
|
close_tmpfile();
|
|
}
|
|
|
|
RESOLVE_PANIC_AND_DIE_CALLERS();
|
|
|
|
if (tt->flags & IRQSTACKS) {
|
|
panic_task = die_task = NO_TASK;
|
|
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!(task = tt->hardirq_tasks[i]))
|
|
continue;
|
|
|
|
for (j = found = 0; j < NR_CPUS; j++) {
|
|
if (task == tt->active_set[j]) {
|
|
found++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
continue;
|
|
|
|
open_tmpfile();
|
|
raw_stack_dump(tt->hardirq_ctx[i], SIZE(thread_union));
|
|
rewind(pc->tmpfile);
|
|
|
|
SEARCH_STACK_FOR_PANIC_DIE_AND_KEXEC_CALLERS();
|
|
|
|
close_tmpfile();
|
|
}
|
|
|
|
RESOLVE_PANIC_AND_DIE_CALLERS();
|
|
|
|
panic_task = die_task = NO_TASK;
|
|
|
|
for (i = 0; i < NR_CPUS; i++) {
|
|
if (!(task = tt->softirq_tasks[i]))
|
|
continue;
|
|
|
|
for (j = found = 0; j < NR_CPUS; j++) {
|
|
if (task == tt->active_set[j]) {
|
|
found++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
continue;
|
|
|
|
open_tmpfile();
|
|
raw_stack_dump(tt->softirq_ctx[i], SIZE(thread_union));
|
|
rewind(pc->tmpfile);
|
|
|
|
SEARCH_STACK_FOR_PANIC_DIE_AND_KEXEC_CALLERS();
|
|
|
|
close_tmpfile();
|
|
}
|
|
|
|
RESOLVE_PANIC_AND_DIE_CALLERS();
|
|
}
|
|
|
|
if (crash_kexec_task) {
|
|
if (CRASHDEBUG(1))
|
|
error(INFO,
|
|
"get_active_set_panic_task: %lx (crash_kexec)\n",
|
|
crash_kexec_task);
|
|
return crash_kexec_task;
|
|
}
|
|
if (crash_fadump_task) {
|
|
if (CRASHDEBUG(1))
|
|
error(INFO,
|
|
"get_active_set_panic_task: %lx (crash_fadump)\n",
|
|
crash_fadump_task);
|
|
return crash_fadump_task;
|
|
}
|
|
|
|
if (xen_sysrq_task) {
|
|
if (CRASHDEBUG(1))
|
|
error(INFO,
|
|
"get_active_set_panic_task: %lx (sysrq_handle_crashdump)\n",
|
|
xen_sysrq_task);
|
|
return xen_sysrq_task;
|
|
}
|
|
|
|
no_panic_task_found:
|
|
|
|
if (CRASHDEBUG(1))
|
|
error(INFO,
|
|
"get_active_set_panic_task: failed\n");
|
|
|
|
return NO_TASK;
|
|
}
|
|
|
|
|
|
/*
|
|
* Determine whether a task is one of the idle threads.
|
|
*/
|
|
int
|
|
is_idle_thread(ulong task)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < NR_CPUS; i++)
|
|
if (task == tt->idle_threads[i])
|
|
return TRUE;
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Dump the current run queue task list. This command should be expanded
|
|
* to deal with timer queues, bottom halves, etc...
|
|
*/
|
|
void
|
|
cmd_runq(void)
|
|
{
|
|
int c;
|
|
char arg_buf[BUFSIZE];
|
|
ulong *cpus = NULL;
|
|
int sched_debug = 0;
|
|
int dump_timestamp_flag = 0;
|
|
int dump_lag_flag = 0;
|
|
int dump_task_group_flag = 0;
|
|
int dump_milliseconds_flag = 0;
|
|
|
|
while ((c = getopt(argcnt, args, "dtTgmc:")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 'd':
|
|
sched_debug = 1;
|
|
break;
|
|
case 't':
|
|
dump_timestamp_flag = 1;
|
|
break;
|
|
case 'T':
|
|
dump_lag_flag = 1;
|
|
break;
|
|
case 'm':
|
|
dump_milliseconds_flag = 1;
|
|
break;
|
|
case 'g':
|
|
if ((INVALID_MEMBER(task_group_cfs_rq) &&
|
|
INVALID_MEMBER(task_group_rt_rq)) ||
|
|
INVALID_MEMBER(task_group_parent))
|
|
option_not_supported(c);
|
|
dump_task_group_flag = 1;
|
|
break;
|
|
case 'c':
|
|
if (pc->curcmd_flags & CPUMASK) {
|
|
error(INFO, "only one -c option allowed\n");
|
|
argerrs++;
|
|
} else {
|
|
pc->curcmd_flags |= CPUMASK;
|
|
BZERO(arg_buf, BUFSIZE);
|
|
strcpy(arg_buf, optarg);
|
|
cpus = get_cpumask_buf();
|
|
make_cpumask(arg_buf, cpus, FAULT_ON_ERROR, NULL);
|
|
pc->curcmd_private = (ulong)cpus;
|
|
}
|
|
break;
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
if (dump_timestamp_flag)
|
|
dump_on_rq_timestamp();
|
|
else if (dump_lag_flag)
|
|
dump_on_rq_lag();
|
|
else if (dump_milliseconds_flag)
|
|
dump_on_rq_milliseconds();
|
|
else if (sched_debug)
|
|
dump_on_rq_tasks();
|
|
else if (dump_task_group_flag)
|
|
dump_tasks_by_task_group();
|
|
else
|
|
dump_runq();
|
|
|
|
if (cpus)
|
|
FREEBUF(cpus);
|
|
}
|
|
|
|
/*
|
|
* Displays the runqueue and active task timestamps of each cpu.
|
|
*/
|
|
static void
|
|
dump_on_rq_timestamp(void)
|
|
{
|
|
ulong runq;
|
|
char buf[BUFSIZE];
|
|
char format[15];
|
|
struct syment *rq_sp;
|
|
struct task_context *tc;
|
|
int cpu, len, indent;
|
|
ulonglong timestamp;
|
|
ulong *cpus;
|
|
|
|
indent = runq = 0;
|
|
cpus = pc->curcmd_flags & CPUMASK ?
|
|
(ulong *)(ulong)pc->curcmd_private : NULL;
|
|
|
|
if (!(rq_sp = per_cpu_symbol_search("per_cpu__runqueues")))
|
|
error(FATAL, "per-cpu runqueues do not exist\n");
|
|
if (INVALID_MEMBER(rq_timestamp))
|
|
option_not_supported('t');
|
|
|
|
for (cpu = 0; cpu < kt->cpus; cpu++) {
|
|
if (cpus && !NUM_IN_BITMAP(cpus, cpu))
|
|
continue;
|
|
|
|
if ((kt->flags & SMP) && (kt->flags &PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
runq = rq_sp->value;
|
|
|
|
readmem(runq + OFFSET(rq_timestamp), KVADDR, ×tamp,
|
|
sizeof(ulonglong), "per-cpu rq timestamp",
|
|
FAULT_ON_ERROR);
|
|
|
|
sprintf(buf, pc->output_radix == 10 ? "%llu" : "%llx",
|
|
timestamp);
|
|
fprintf(fp, "%sCPU %d: ", cpu < 10 ? " " : "", cpu);
|
|
|
|
if (hide_offline_cpu(cpu)) {
|
|
fprintf(fp, "[OFFLINE]\n");
|
|
continue;
|
|
} else
|
|
fprintf(fp, "%s\n", buf);
|
|
|
|
len = strlen(buf);
|
|
|
|
if ((tc = task_to_context(tt->active_set[cpu]))){
|
|
if (cpu < 10)
|
|
indent = 7;
|
|
else if (cpu < 100)
|
|
indent = 8;
|
|
else if (cpu < 1000)
|
|
indent = 9;
|
|
if (cpu < 10)
|
|
indent++;
|
|
|
|
timestamp = task_last_run(tc->task);
|
|
sprintf(format, "%c0%dll%c", '%', len,
|
|
pc->output_radix == 10 ? 'u' : 'x');
|
|
sprintf(buf, format, timestamp);
|
|
fprintf(fp, "%s%s PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
space(indent), buf, tc->pid, tc->task, tc->comm);
|
|
} else
|
|
fprintf(fp, "\n");
|
|
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Runqueue timestamp struct for dump_on_rq_lag().
|
|
*/
|
|
struct runq_ts_info {
|
|
int cpu;
|
|
ulonglong ts;
|
|
};
|
|
|
|
/*
|
|
* Comparison function for dump_on_rq_lag().
|
|
* Sorts runqueue timestamps in a descending order.
|
|
*/
|
|
static int
|
|
compare_runq_ts(const void *p1, const void *p2)
|
|
{
|
|
const struct runq_ts_info *ts1 = p1;
|
|
const struct runq_ts_info *ts2 = p2;
|
|
|
|
if (ts1->ts > ts2->ts)
|
|
return -1;
|
|
|
|
if (ts1->ts < ts2->ts)
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Calculates integer log10
|
|
*/
|
|
static ulong
|
|
__log10ul(ulong x)
|
|
{
|
|
ulong ret = 1;
|
|
|
|
while (x > 9) {
|
|
ret++;
|
|
x /= 10;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
* Displays relative CPU lag.
|
|
*/
|
|
static void
|
|
dump_on_rq_lag(void)
|
|
{
|
|
struct syment *rq_sp;
|
|
int cpu;
|
|
ulong runq;
|
|
ulonglong timestamp;
|
|
struct runq_ts_info runq_ts[kt->cpus];
|
|
|
|
if (!(rq_sp = per_cpu_symbol_search("per_cpu__runqueues")))
|
|
error(FATAL, "per-cpu runqueues do not exist\n");
|
|
if (INVALID_MEMBER(rq_timestamp))
|
|
option_not_supported('T');
|
|
|
|
for (cpu = 0; cpu < kt->cpus; cpu++) {
|
|
if ((kt->flags & SMP) && (kt->flags &PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
runq = rq_sp->value;
|
|
|
|
readmem(runq + OFFSET(rq_timestamp), KVADDR, ×tamp,
|
|
sizeof(ulonglong), "per-cpu rq timestamp",
|
|
FAULT_ON_ERROR);
|
|
|
|
runq_ts[cpu].cpu = cpu;
|
|
runq_ts[cpu].ts = timestamp;
|
|
}
|
|
|
|
qsort(runq_ts, (size_t)kt->cpus, sizeof(struct runq_ts_info), compare_runq_ts);
|
|
|
|
for (cpu = 0; cpu < kt->cpus; cpu++) {
|
|
fprintf(fp, "%sCPU %d: %.2lf secs\n",
|
|
space(2 + __log10ul(kt->cpus) - __log10ul(runq_ts[cpu].cpu)),
|
|
runq_ts[cpu].cpu,
|
|
((double)runq_ts[0].ts - (double)runq_ts[cpu].ts) / 1000000000.0);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Displays the runqueue and active task timestamps of each cpu.
|
|
*/
|
|
static void
|
|
dump_on_rq_milliseconds(void)
|
|
{
|
|
ulong runq;
|
|
char buf[BUFSIZE];
|
|
struct syment *rq_sp;
|
|
struct task_context *tc;
|
|
int cpu, max_indent, indent, max_days, days;
|
|
long long delta;
|
|
ulonglong task_timestamp, rq_timestamp;
|
|
ulong *cpus;
|
|
|
|
if (!(rq_sp = per_cpu_symbol_search("per_cpu__runqueues")))
|
|
error(FATAL, "per-cpu runqueues do not exist\n");
|
|
if (INVALID_MEMBER(rq_timestamp))
|
|
option_not_supported('m');
|
|
|
|
if (kt->cpus < 10)
|
|
max_indent = 1;
|
|
else if (kt->cpus < 100)
|
|
max_indent = 2;
|
|
else if (kt->cpus < 1000)
|
|
max_indent = 3;
|
|
else
|
|
max_indent = 4;
|
|
|
|
max_days = days = 0;
|
|
cpus = pc->curcmd_flags & CPUMASK ?
|
|
(ulong *)(ulong)pc->curcmd_private : NULL;
|
|
|
|
for (cpu = 0; cpu < kt->cpus; cpu++) {
|
|
if (cpus && !NUM_IN_BITMAP(cpus, cpu))
|
|
continue;
|
|
|
|
if ((kt->flags & SMP) && (kt->flags &PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
runq = rq_sp->value;
|
|
|
|
readmem(runq + OFFSET(rq_timestamp), KVADDR, &rq_timestamp,
|
|
sizeof(ulonglong), "per-cpu rq timestamp",
|
|
FAULT_ON_ERROR);
|
|
|
|
if (!max_days) {
|
|
translate_nanoseconds(rq_timestamp, buf);
|
|
max_days = first_space(buf) - buf;
|
|
}
|
|
|
|
if (cpu < 10)
|
|
indent = max_indent;
|
|
else if (cpu < 100)
|
|
indent = max_indent - 1;
|
|
else if (cpu < 1000)
|
|
indent = max_indent - 2;
|
|
else
|
|
indent = max_indent - 4;
|
|
|
|
if (hide_offline_cpu(cpu)) {
|
|
fprintf(fp, "%sCPU %d: [OFFLINE]\n", space(indent), cpu);
|
|
continue;
|
|
}
|
|
|
|
if ((tc = task_to_context(tt->active_set[cpu])))
|
|
task_timestamp = task_last_run(tc->task);
|
|
else {
|
|
fprintf(fp, "%sCPU %d: [unknown]\n", space(indent), cpu);
|
|
continue;
|
|
}
|
|
|
|
delta = rq_timestamp - task_timestamp;
|
|
if (delta < 0)
|
|
delta = 0;
|
|
translate_nanoseconds(delta, buf);
|
|
days = first_space(buf) - buf;
|
|
|
|
fprintf(fp,
|
|
"%sCPU %d: [%s%s] PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
space(indent), cpu, space(max_days - days), buf, tc->pid,
|
|
tc->task, tc->comm);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Dump the task run queue on behalf cmd_runq().
|
|
*/
|
|
|
|
static void
|
|
dump_runq(void)
|
|
{
|
|
int i;
|
|
ulong next, runqueue_head;
|
|
long offs;
|
|
int qlen, cnt;
|
|
ulong *tlist;
|
|
struct task_context *tc;
|
|
|
|
if (VALID_MEMBER(rq_cfs)) {
|
|
dump_CFS_runqueues();
|
|
return;
|
|
}
|
|
|
|
if (VALID_MEMBER(runqueue_arrays)) {
|
|
dump_runqueues();
|
|
return;
|
|
}
|
|
|
|
offs = runqueue_head = 0;
|
|
qlen = 1000;
|
|
|
|
start_again:
|
|
tlist = (ulong *)GETBUF(qlen * sizeof(void *));
|
|
|
|
if (symbol_exists("runqueue_head")) {
|
|
next = runqueue_head = symbol_value("runqueue_head");
|
|
offs = 0;
|
|
} else if (VALID_MEMBER(task_struct_next_run)) {
|
|
offs = OFFSET(task_struct_next_run);
|
|
next = runqueue_head = symbol_value("init_task_union");
|
|
} else
|
|
error(FATAL,
|
|
"cannot determine run queue structures\n");
|
|
|
|
cnt = 0;
|
|
do {
|
|
if (cnt == qlen) {
|
|
FREEBUF(tlist);
|
|
qlen += 1000;
|
|
goto start_again;
|
|
}
|
|
|
|
tlist[cnt++] = next;
|
|
|
|
readmem(next+offs, KVADDR, &next, sizeof(void *),
|
|
"run queue entry", FAULT_ON_ERROR);
|
|
|
|
if (next == runqueue_head)
|
|
break;
|
|
} while (next);
|
|
|
|
for (i = 0; i < cnt; i++) {
|
|
if (tlist[i] == runqueue_head)
|
|
continue;
|
|
|
|
if (!(tc = task_to_context(VIRTPAGEBASE(tlist[i])))) {
|
|
fprintf(fp,
|
|
"PID: ? TASK: %lx CPU: ? COMMAND: ?\n",
|
|
tlist[i]);
|
|
continue;
|
|
}
|
|
|
|
if (!is_idle_thread(tc->task))
|
|
print_task_header(fp, tc, 0);
|
|
}
|
|
}
|
|
|
|
#define RUNQ_ACTIVE (1)
|
|
#define RUNQ_EXPIRED (2)
|
|
|
|
static void
|
|
dump_runqueues(void)
|
|
{
|
|
int cpu, displayed;
|
|
ulong runq, offset;
|
|
char *runqbuf;
|
|
ulong active, expired, arrays;
|
|
struct task_context *tc;
|
|
struct syment *rq_sp;
|
|
ulong *cpus;
|
|
|
|
runq = 0;
|
|
|
|
rq_sp = per_cpu_symbol_search("per_cpu__runqueues");
|
|
if (!rq_sp) {
|
|
if (symbol_exists("runqueues"))
|
|
runq = symbol_value("runqueues");
|
|
else
|
|
error(FATAL, "cannot determine run queue structures\n");
|
|
}
|
|
|
|
get_active_set();
|
|
runqbuf = GETBUF(SIZE(runqueue));
|
|
cpus = pc->curcmd_flags & CPUMASK ?
|
|
(ulong *)(ulong)pc->curcmd_private : NULL;
|
|
|
|
for (cpu = displayed = 0; cpu < kt->cpus; cpu++, runq += SIZE(runqueue)) {
|
|
if (cpus && !NUM_IN_BITMAP(cpus, cpu))
|
|
continue;
|
|
|
|
if (rq_sp) {
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
runq = rq_sp->value;
|
|
}
|
|
|
|
fprintf(fp, "%sCPU %d ", displayed++ ? "\n" : "", cpu);
|
|
|
|
if (hide_offline_cpu(cpu)) {
|
|
fprintf(fp, "[OFFLINE]\n");
|
|
continue;
|
|
} else
|
|
fprintf(fp, "RUNQUEUE: %lx\n", runq);
|
|
|
|
fprintf(fp, " CURRENT: ");
|
|
if ((tc = task_to_context(tt->active_set[cpu])))
|
|
fprintf(fp, "PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
tc->pid, tc->task, tc->comm);
|
|
else
|
|
fprintf(fp, "%lx\n", tt->active_set[cpu]);
|
|
|
|
readmem(runq, KVADDR, runqbuf, SIZE(runqueue),
|
|
"runqueues array entry", FAULT_ON_ERROR);
|
|
active = ULONG(runqbuf + OFFSET(runqueue_active));
|
|
expired = ULONG(runqbuf + OFFSET(runqueue_expired));
|
|
arrays = runq + OFFSET(runqueue_arrays);
|
|
|
|
console("active: %lx\n", active);
|
|
console("expired: %lx\n", expired);
|
|
console("arrays: %lx\n", arrays);
|
|
|
|
offset = active == arrays ? OFFSET(runqueue_arrays) :
|
|
OFFSET(runqueue_arrays) + SIZE(prio_array);
|
|
offset = active - runq;
|
|
dump_prio_array(RUNQ_ACTIVE, active, &runqbuf[offset]);
|
|
|
|
offset = expired == arrays ? OFFSET(runqueue_arrays) :
|
|
OFFSET(runqueue_arrays) + SIZE(prio_array);
|
|
offset = expired - runq;
|
|
dump_prio_array(RUNQ_EXPIRED, expired, &runqbuf[offset]);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_prio_array(int which, ulong k_prio_array, char *u_prio_array)
|
|
{
|
|
int i, c, cnt, tot, nr_active;
|
|
int qheads ATTRIBUTE_UNUSED;
|
|
ulong offset, kvaddr, uvaddr;
|
|
ulong list_head[2];
|
|
struct list_data list_data, *ld;
|
|
struct task_context *tc;
|
|
ulong *tlist;
|
|
|
|
qheads = (i = ARRAY_LENGTH(prio_array_queue)) ?
|
|
i : get_array_length("prio_array.queue", NULL, SIZE(list_head));
|
|
|
|
console("dump_prio_array[%d]: %lx %lx\n",
|
|
which, k_prio_array, (ulong)u_prio_array);
|
|
|
|
nr_active = INT(u_prio_array + OFFSET(prio_array_nr_active));
|
|
console("nr_active: %d\n", nr_active);
|
|
|
|
fprintf(fp, " %s PRIO_ARRAY: %lx\n",
|
|
which == RUNQ_ACTIVE ? "ACTIVE" : "EXPIRED", k_prio_array);
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "nr_active: %d\n", nr_active);
|
|
|
|
ld = &list_data;
|
|
|
|
for (i = tot = 0; i < 140; i++) {
|
|
offset = OFFSET(prio_array_queue) + (i * SIZE(list_head));
|
|
kvaddr = k_prio_array + offset;
|
|
uvaddr = (ulong)u_prio_array + offset;
|
|
BCOPY((char *)uvaddr, (char *)&list_head[0], sizeof(ulong)*2);
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "prio_array[%d] @ %lx => %lx/%lx %s\n",
|
|
i, kvaddr, list_head[0], list_head[1],
|
|
(list_head[0] == list_head[1]) &&
|
|
(list_head[0] == kvaddr) ? "(empty)" : "");
|
|
|
|
if ((list_head[0] == kvaddr) && (list_head[1] == kvaddr))
|
|
continue;
|
|
|
|
console("[%d] %lx => %lx-%lx ", i, kvaddr, list_head[0],
|
|
list_head[1]);
|
|
|
|
fprintf(fp, " [%3d] ", i);
|
|
|
|
BZERO(ld, sizeof(struct list_data));
|
|
ld->start = list_head[0];
|
|
ld->list_head_offset = OFFSET(task_struct_run_list);
|
|
ld->end = kvaddr;
|
|
hq_open();
|
|
cnt = do_list(ld);
|
|
hq_close();
|
|
console("%d entries\n", cnt);
|
|
tlist = (ulong *)GETBUF((cnt) * sizeof(ulong));
|
|
cnt = retrieve_list(tlist, cnt);
|
|
for (c = 0; c < cnt; c++) {
|
|
if (!(tc = task_to_context(tlist[c])))
|
|
continue;
|
|
if (c)
|
|
INDENT(11);
|
|
fprintf(fp, "PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
tc->pid, tc->task, tc->comm);
|
|
}
|
|
tot += cnt;
|
|
FREEBUF(tlist);
|
|
}
|
|
|
|
if (!tot) {
|
|
INDENT(5);
|
|
fprintf(fp, "[no tasks queued]\n");
|
|
}
|
|
}
|
|
|
|
#define MAX_GROUP_NUM 200
|
|
struct task_group_info {
|
|
int use;
|
|
int depth;
|
|
char *name;
|
|
ulong task_group;
|
|
struct task_group_info *parent;
|
|
};
|
|
|
|
static struct task_group_info **tgi_array;
|
|
static int tgi_p = 0;
|
|
static int tgi_p_max = 0;
|
|
|
|
static void
|
|
sort_task_group_info_array(void)
|
|
{
|
|
int i, j;
|
|
struct task_group_info *tmp;
|
|
|
|
for (i = 0; i < tgi_p - 1; i++) {
|
|
for (j = 0; j < tgi_p - i - 1; j++) {
|
|
if (tgi_array[j]->depth > tgi_array[j+1]->depth) {
|
|
tmp = tgi_array[j+1];
|
|
tgi_array[j+1] = tgi_array[j];
|
|
tgi_array[j] = tmp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
print_task_group_info_array(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < tgi_p; i++) {
|
|
fprintf(fp, "%d : use=%d, depth=%d, group=%lx, ", i,
|
|
tgi_array[i]->use, tgi_array[i]->depth,
|
|
tgi_array[i]->task_group);
|
|
fprintf(fp, "name=%s, ",
|
|
tgi_array[i]->name ? tgi_array[i]->name : "NULL");
|
|
if (tgi_array[i]->parent)
|
|
fprintf(fp, "parent=%lx",
|
|
tgi_array[i]->parent->task_group);
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
static void
|
|
free_task_group_info_array(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < tgi_p; i++) {
|
|
if (tgi_array[i]->name)
|
|
FREEBUF(tgi_array[i]->name);
|
|
FREEBUF(tgi_array[i]);
|
|
}
|
|
tgi_p = 0;
|
|
FREEBUF(tgi_array);
|
|
}
|
|
|
|
static void
|
|
reuse_task_group_info_array(void)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < tgi_p; i++) {
|
|
if (tgi_array[i]->depth == 0)
|
|
tgi_array[i]->use = 0;
|
|
else
|
|
tgi_array[i]->use = 1;
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_task_runq_entry(struct task_context *tc, int current)
|
|
{
|
|
int prio;
|
|
|
|
readmem(tc->task + OFFSET(task_struct_prio), KVADDR,
|
|
&prio, sizeof(int), "task prio", FAULT_ON_ERROR);
|
|
fprintf(fp, "[%3d] ", prio);
|
|
fprintf(fp, "PID: %-5ld TASK: %lx COMMAND: \"%s\"",
|
|
tc->pid, tc->task, tc->comm);
|
|
if (current)
|
|
fprintf(fp, " [CURRENT]\n");
|
|
else
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
static void
|
|
print_group_header_fair(int depth, ulong cfs_rq, void *t)
|
|
{
|
|
int throttled;
|
|
struct task_group_info *tgi = (struct task_group_info *)t;
|
|
|
|
INDENT(2 + 3 * depth);
|
|
fprintf(fp, "TASK_GROUP: %lx CFS_RQ: %lx ",
|
|
tgi->task_group, cfs_rq);
|
|
if (tgi->name)
|
|
fprintf(fp, " <%s>", tgi->name);
|
|
|
|
if (VALID_MEMBER(cfs_rq_throttled)) {
|
|
readmem(cfs_rq + OFFSET(cfs_rq_throttled), KVADDR,
|
|
&throttled, sizeof(int), "cfs_rq throttled",
|
|
FAULT_ON_ERROR);
|
|
if (throttled)
|
|
fprintf(fp, " (THROTTLED)");
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
static void
|
|
print_parent_task_group_fair(void *t, int cpu)
|
|
{
|
|
struct task_group_info *tgi;
|
|
ulong cfs_rq_c, cfs_rq_p;
|
|
|
|
tgi = ((struct task_group_info *)t)->parent;
|
|
if (tgi && tgi->use)
|
|
print_parent_task_group_fair(tgi, cpu);
|
|
else
|
|
return;
|
|
|
|
readmem(tgi->task_group + OFFSET(task_group_cfs_rq),
|
|
KVADDR, &cfs_rq_c, sizeof(ulong),
|
|
"task_group cfs_rq", FAULT_ON_ERROR);
|
|
readmem(cfs_rq_c + cpu * sizeof(ulong), KVADDR, &cfs_rq_p,
|
|
sizeof(ulong), "task_group cfs_rq", FAULT_ON_ERROR);
|
|
|
|
print_group_header_fair(tgi->depth, cfs_rq_p, tgi);
|
|
tgi->use = 0;
|
|
}
|
|
|
|
static int
|
|
dump_tasks_in_lower_dequeued_cfs_rq(int depth, ulong cfs_rq, int cpu,
|
|
struct task_context *ctc)
|
|
{
|
|
int i, total, nr_running;
|
|
ulong group, cfs_rq_c, cfs_rq_p;
|
|
|
|
total = 0;
|
|
for (i = 0; i < tgi_p; i++) {
|
|
if (tgi_array[i]->use == 0 || tgi_array[i]->depth - depth != 1)
|
|
continue;
|
|
|
|
readmem(cfs_rq + OFFSET(cfs_rq_tg), KVADDR, &group,
|
|
sizeof(ulong), "cfs_rq tg", FAULT_ON_ERROR);
|
|
if (group != tgi_array[i]->parent->task_group)
|
|
continue;
|
|
|
|
readmem(tgi_array[i]->task_group + OFFSET(task_group_cfs_rq),
|
|
KVADDR, &cfs_rq_c, sizeof(ulong), "task_group cfs_rq",
|
|
FAULT_ON_ERROR);
|
|
readmem(cfs_rq_c + cpu * sizeof(ulong), KVADDR, &cfs_rq_p,
|
|
sizeof(ulong), "task_group cfs_rq", FAULT_ON_ERROR);
|
|
if (cfs_rq == cfs_rq_p)
|
|
continue;
|
|
|
|
readmem(cfs_rq_p + OFFSET(cfs_rq_nr_running), KVADDR,
|
|
&nr_running, sizeof(int), "cfs_rq nr_running",
|
|
FAULT_ON_ERROR);
|
|
if (nr_running == 0) {
|
|
total += dump_tasks_in_lower_dequeued_cfs_rq(depth + 1,
|
|
cfs_rq_p, cpu, ctc);
|
|
continue;
|
|
}
|
|
|
|
print_parent_task_group_fair(tgi_array[i], cpu);
|
|
|
|
total++;
|
|
total += dump_tasks_in_task_group_cfs_rq(depth + 1, cfs_rq_p, cpu, ctc);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
static int
|
|
dump_tasks_in_cfs_rq(ulong cfs_rq)
|
|
{
|
|
struct task_context *tc;
|
|
struct rb_root *root;
|
|
struct rb_node *node;
|
|
ulong my_q, leftmost, curr, curr_my_q;
|
|
int total;
|
|
|
|
total = 0;
|
|
|
|
if (VALID_MEMBER(sched_entity_my_q)) {
|
|
readmem(cfs_rq + OFFSET(cfs_rq_curr), KVADDR, &curr,
|
|
sizeof(ulong), "curr", FAULT_ON_ERROR);
|
|
if (curr) {
|
|
readmem(curr + OFFSET(sched_entity_my_q), KVADDR,
|
|
&curr_my_q, sizeof(ulong), "curr->my_q",
|
|
FAULT_ON_ERROR);
|
|
if (curr_my_q)
|
|
total += dump_tasks_in_cfs_rq(curr_my_q);
|
|
}
|
|
}
|
|
|
|
readmem(cfs_rq + OFFSET(cfs_rq_rb_leftmost), KVADDR, &leftmost,
|
|
sizeof(ulong), "rb_leftmost", FAULT_ON_ERROR);
|
|
root = (struct rb_root *)(cfs_rq + OFFSET(cfs_rq_tasks_timeline));
|
|
|
|
for (node = rb_first(root); leftmost && node; node = rb_next(node)) {
|
|
if (VALID_MEMBER(sched_entity_my_q)) {
|
|
readmem((ulong)node - OFFSET(sched_entity_run_node)
|
|
+ OFFSET(sched_entity_my_q), KVADDR, &my_q,
|
|
sizeof(ulong), "my_q", FAULT_ON_ERROR);
|
|
if (my_q) {
|
|
total += dump_tasks_in_cfs_rq(my_q);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
tc = task_to_context((ulong)node - OFFSET(task_struct_se) -
|
|
OFFSET(sched_entity_run_node));
|
|
if (!tc)
|
|
continue;
|
|
if (hq_enter((ulong)tc)) {
|
|
INDENT(5);
|
|
dump_task_runq_entry(tc, 0);
|
|
} else {
|
|
error(WARNING, "duplicate CFS runqueue node: task %lx\n",
|
|
tc->task);
|
|
return total;
|
|
}
|
|
total++;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
static int
|
|
dump_tasks_in_task_group_cfs_rq(int depth, ulong cfs_rq, int cpu,
|
|
struct task_context *ctc)
|
|
{
|
|
struct task_context *tc;
|
|
struct rb_root *root;
|
|
struct rb_node *node;
|
|
ulong my_q, leftmost, curr, curr_my_q, tg;
|
|
int total, i;
|
|
|
|
total = 0;
|
|
curr_my_q = curr = 0;
|
|
|
|
if (depth) {
|
|
readmem(cfs_rq + OFFSET(cfs_rq_tg), KVADDR,
|
|
&tg, sizeof(ulong), "cfs_rq tg",
|
|
FAULT_ON_ERROR);
|
|
for (i = 0; i < tgi_p; i++) {
|
|
if (tgi_array[i]->task_group == tg) {
|
|
print_group_header_fair(depth,
|
|
cfs_rq, tgi_array[i]);
|
|
tgi_array[i]->use = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (VALID_MEMBER(sched_entity_my_q)) {
|
|
readmem(cfs_rq + OFFSET(cfs_rq_curr), KVADDR, &curr,
|
|
sizeof(ulong), "curr", FAULT_ON_ERROR);
|
|
if (curr) {
|
|
readmem(curr + OFFSET(sched_entity_my_q), KVADDR,
|
|
&curr_my_q, sizeof(ulong), "curr->my_q",
|
|
FAULT_ON_ERROR);
|
|
if (curr_my_q) {
|
|
total++;
|
|
total += dump_tasks_in_task_group_cfs_rq(depth + 1,
|
|
curr_my_q, cpu, ctc);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* check if "curr" is the task that is current running task
|
|
*/
|
|
if (!curr_my_q && ctc && (curr - OFFSET(task_struct_se)) == ctc->task) {
|
|
/* curr is not in the rb tree, so let's print it here */
|
|
total++;
|
|
INDENT(5 + 3 * depth);
|
|
dump_task_runq_entry(ctc, 1);
|
|
}
|
|
|
|
readmem(cfs_rq + OFFSET(cfs_rq_rb_leftmost), KVADDR, &leftmost,
|
|
sizeof(ulong), "rb_leftmost", FAULT_ON_ERROR);
|
|
root = (struct rb_root *)(cfs_rq + OFFSET(cfs_rq_tasks_timeline));
|
|
|
|
for (node = rb_first(root); leftmost && node; node = rb_next(node)) {
|
|
if (VALID_MEMBER(sched_entity_my_q)) {
|
|
readmem((ulong)node - OFFSET(sched_entity_run_node)
|
|
+ OFFSET(sched_entity_my_q), KVADDR, &my_q,
|
|
sizeof(ulong), "my_q", FAULT_ON_ERROR);
|
|
if (my_q) {
|
|
total++;
|
|
total += dump_tasks_in_task_group_cfs_rq(depth + 1,
|
|
my_q, cpu, ctc);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
tc = task_to_context((ulong)node - OFFSET(task_struct_se) -
|
|
OFFSET(sched_entity_run_node));
|
|
if (!tc)
|
|
continue;
|
|
if (hq_enter((ulong)tc)) {
|
|
INDENT(5 + 3 * depth);
|
|
dump_task_runq_entry(tc, 0);
|
|
} else {
|
|
error(WARNING, "duplicate CFS runqueue node: task %lx\n",
|
|
tc->task);
|
|
return total;
|
|
}
|
|
total++;
|
|
}
|
|
|
|
total += dump_tasks_in_lower_dequeued_cfs_rq(depth, cfs_rq, cpu, ctc);
|
|
|
|
if (!total) {
|
|
INDENT(5 + 3 * depth);
|
|
fprintf(fp, "[no tasks queued]\n");
|
|
}
|
|
return total;
|
|
}
|
|
|
|
static void
|
|
dump_on_rq_tasks(void)
|
|
{
|
|
char buf[BUFSIZE];
|
|
struct task_context *tc;
|
|
int i, cpu, on_rq, tot;
|
|
ulong *cpus;
|
|
|
|
if (!VALID_MEMBER(task_struct_on_rq)) {
|
|
MEMBER_OFFSET_INIT(task_struct_se, "task_struct", "se");
|
|
STRUCT_SIZE_INIT(sched_entity, "sched_entity");
|
|
MEMBER_OFFSET_INIT(sched_entity_on_rq, "sched_entity", "on_rq");
|
|
MEMBER_OFFSET_INIT(task_struct_on_rq, "task_struct", "on_rq");
|
|
MEMBER_OFFSET_INIT(task_struct_prio, "task_struct", "prio");
|
|
if (INVALID_MEMBER(task_struct_on_rq)) {
|
|
if (INVALID_MEMBER(task_struct_se) ||
|
|
INVALID_SIZE(sched_entity))
|
|
option_not_supported('d');
|
|
}
|
|
}
|
|
|
|
cpus = pc->curcmd_flags & CPUMASK ?
|
|
(ulong *)(ulong)pc->curcmd_private : NULL;
|
|
|
|
for (cpu = 0; cpu < kt->cpus; cpu++) {
|
|
if (cpus && !NUM_IN_BITMAP(cpus, cpu))
|
|
continue;
|
|
|
|
fprintf(fp, "%sCPU %d", cpu ? "\n" : "", cpu);
|
|
|
|
if (hide_offline_cpu(cpu)) {
|
|
fprintf(fp, " [OFFLINE]\n");
|
|
continue;
|
|
} else
|
|
fprintf(fp, "\n");
|
|
|
|
tc = FIRST_CONTEXT();
|
|
tot = 0;
|
|
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
|
|
if (VALID_MEMBER(task_struct_on_rq)) {
|
|
readmem(tc->task + OFFSET(task_struct_on_rq),
|
|
KVADDR, &on_rq, sizeof(int),
|
|
"task on_rq", FAULT_ON_ERROR);
|
|
} else {
|
|
readmem(tc->task + OFFSET(task_struct_se), KVADDR,
|
|
buf, SIZE(sched_entity), "task se",
|
|
FAULT_ON_ERROR);
|
|
on_rq = INT(buf + OFFSET(sched_entity_on_rq));
|
|
}
|
|
|
|
if (!on_rq || tc->processor != cpu)
|
|
continue;
|
|
|
|
INDENT(5);
|
|
dump_task_runq_entry(tc, 0);
|
|
tot++;
|
|
}
|
|
|
|
if (!tot) {
|
|
INDENT(5);
|
|
fprintf(fp, "[no tasks queued]\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
cfs_rq_offset_init(void)
|
|
{
|
|
if (!VALID_STRUCT(cfs_rq)) {
|
|
STRUCT_SIZE_INIT(cfs_rq, "cfs_rq");
|
|
STRUCT_SIZE_INIT(rt_rq, "rt_rq");
|
|
MEMBER_OFFSET_INIT(rq_rt, "rq", "rt");
|
|
MEMBER_OFFSET_INIT(rq_nr_running, "rq", "nr_running");
|
|
MEMBER_OFFSET_INIT(task_struct_se, "task_struct", "se");
|
|
STRUCT_SIZE_INIT(sched_entity, "sched_entity");
|
|
MEMBER_OFFSET_INIT(sched_entity_run_node, "sched_entity",
|
|
"run_node");
|
|
MEMBER_OFFSET_INIT(sched_entity_cfs_rq, "sched_entity",
|
|
"cfs_rq");
|
|
MEMBER_OFFSET_INIT(sched_entity_my_q, "sched_entity",
|
|
"my_q");
|
|
MEMBER_OFFSET_INIT(sched_rt_entity_my_q, "sched_rt_entity",
|
|
"my_q");
|
|
MEMBER_OFFSET_INIT(sched_entity_on_rq, "sched_entity", "on_rq");
|
|
MEMBER_OFFSET_INIT(cfs_rq_tasks_timeline, "cfs_rq",
|
|
"tasks_timeline");
|
|
MEMBER_OFFSET_INIT(cfs_rq_rb_leftmost, "cfs_rq", "rb_leftmost");
|
|
if (INVALID_MEMBER(cfs_rq_rb_leftmost) &&
|
|
VALID_MEMBER(cfs_rq_tasks_timeline) &&
|
|
MEMBER_EXISTS("rb_root_cached", "rb_leftmost"))
|
|
ASSIGN_OFFSET(cfs_rq_rb_leftmost) = OFFSET(cfs_rq_tasks_timeline) +
|
|
MEMBER_OFFSET("rb_root_cached", "rb_leftmost");
|
|
MEMBER_OFFSET_INIT(cfs_rq_nr_running, "cfs_rq", "nr_running");
|
|
MEMBER_OFFSET_INIT(cfs_rq_curr, "cfs_rq", "curr");
|
|
MEMBER_OFFSET_INIT(rt_rq_active, "rt_rq", "active");
|
|
MEMBER_OFFSET_INIT(task_struct_run_list, "task_struct",
|
|
"run_list");
|
|
MEMBER_OFFSET_INIT(task_struct_on_rq, "task_struct", "on_rq");
|
|
MEMBER_OFFSET_INIT(task_struct_prio, "task_struct",
|
|
"prio");
|
|
MEMBER_OFFSET_INIT(task_struct_rt, "task_struct", "rt");
|
|
MEMBER_OFFSET_INIT(sched_rt_entity_run_list, "sched_rt_entity",
|
|
"run_list");
|
|
MEMBER_OFFSET_INIT(rt_prio_array_queue, "rt_prio_array", "queue");
|
|
}
|
|
}
|
|
|
|
static void
|
|
task_group_offset_init(void)
|
|
{
|
|
if (!VALID_STRUCT(task_group)) {
|
|
STRUCT_SIZE_INIT(task_group, "task_group");
|
|
MEMBER_OFFSET_INIT(rt_rq_rt_nr_running, "rt_rq", "rt_nr_running");
|
|
MEMBER_OFFSET_INIT(cfs_rq_tg, "cfs_rq", "tg");
|
|
MEMBER_OFFSET_INIT(rt_rq_tg, "rt_rq", "tg");
|
|
MEMBER_OFFSET_INIT(rt_rq_highest_prio, "rt_rq", "highest_prio");
|
|
MEMBER_OFFSET_INIT(task_group_css, "task_group", "css");
|
|
MEMBER_OFFSET_INIT(cgroup_subsys_state_cgroup,
|
|
"cgroup_subsys_state", "cgroup");
|
|
|
|
MEMBER_OFFSET_INIT(cgroup_dentry, "cgroup", "dentry");
|
|
MEMBER_OFFSET_INIT(cgroup_kn, "cgroup", "kn");
|
|
MEMBER_OFFSET_INIT(kernfs_node_name, "kernfs_node", "name");
|
|
MEMBER_OFFSET_INIT(kernfs_node_parent, "kernfs_node", "parent");
|
|
|
|
MEMBER_OFFSET_INIT(task_group_siblings, "task_group", "siblings");
|
|
MEMBER_OFFSET_INIT(task_group_children, "task_group", "children");
|
|
|
|
MEMBER_OFFSET_INIT(task_group_cfs_bandwidth,
|
|
"task_group", "cfs_bandwidth");
|
|
MEMBER_OFFSET_INIT(cfs_rq_throttled, "cfs_rq",
|
|
"throttled");
|
|
|
|
MEMBER_OFFSET_INIT(task_group_rt_bandwidth,
|
|
"task_group", "rt_bandwidth");
|
|
MEMBER_OFFSET_INIT(rt_rq_rt_throttled, "rt_rq",
|
|
"rt_throttled");
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_CFS_runqueues(void)
|
|
{
|
|
int cpu, tot, displayed;
|
|
ulong runq, cfs_rq, prio_array;
|
|
char *runqbuf, *cfs_rq_buf;
|
|
ulong tasks_timeline ATTRIBUTE_UNUSED;
|
|
struct task_context *tc;
|
|
struct rb_root *root;
|
|
struct syment *rq_sp, *init_sp;
|
|
ulong *cpus;
|
|
|
|
cfs_rq_offset_init();
|
|
|
|
if (!(rq_sp = per_cpu_symbol_search("per_cpu__runqueues")))
|
|
error(FATAL, "per-cpu runqueues do not exist\n");
|
|
|
|
runqbuf = GETBUF(SIZE(runqueue));
|
|
if ((init_sp = per_cpu_symbol_search("per_cpu__init_cfs_rq")))
|
|
cfs_rq_buf = GETBUF(SIZE(cfs_rq));
|
|
else
|
|
cfs_rq_buf = NULL;
|
|
|
|
get_active_set();
|
|
cpus = pc->curcmd_flags & CPUMASK ?
|
|
(ulong *)(ulong)pc->curcmd_private : NULL;
|
|
|
|
for (cpu = displayed = 0; cpu < kt->cpus; cpu++) {
|
|
if (cpus && !NUM_IN_BITMAP(cpus, cpu))
|
|
continue;
|
|
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
runq = rq_sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
runq = rq_sp->value;
|
|
|
|
fprintf(fp, "%sCPU %d ", displayed++ ? "\n" : "", cpu);
|
|
|
|
if (hide_offline_cpu(cpu)) {
|
|
fprintf(fp, "[OFFLINE]\n");
|
|
continue;
|
|
} else
|
|
fprintf(fp, "RUNQUEUE: %lx\n", runq);
|
|
|
|
fprintf(fp, " CURRENT: ");
|
|
if ((tc = task_to_context(tt->active_set[cpu])))
|
|
fprintf(fp, "PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
tc->pid, tc->task, tc->comm);
|
|
else
|
|
fprintf(fp, "%lx\n", tt->active_set[cpu]);
|
|
|
|
readmem(runq, KVADDR, runqbuf, SIZE(runqueue),
|
|
"per-cpu rq", FAULT_ON_ERROR);
|
|
|
|
if (cfs_rq_buf) {
|
|
/*
|
|
* Use default task group's cfs_rq on each cpu.
|
|
*/
|
|
if ((kt->flags & SMP) && (kt->flags & PER_CPU_OFF))
|
|
cfs_rq = init_sp->value + kt->__per_cpu_offset[cpu];
|
|
else
|
|
cfs_rq = init_sp->value;
|
|
|
|
readmem(cfs_rq, KVADDR, cfs_rq_buf, SIZE(cfs_rq),
|
|
"per-cpu cfs_rq", FAULT_ON_ERROR);
|
|
root = (struct rb_root *)(cfs_rq +
|
|
OFFSET(cfs_rq_tasks_timeline));
|
|
} else {
|
|
cfs_rq = runq + OFFSET(rq_cfs);
|
|
root = (struct rb_root *)(runq + OFFSET(rq_cfs) +
|
|
OFFSET(cfs_rq_tasks_timeline));
|
|
}
|
|
|
|
prio_array = runq + OFFSET(rq_rt) + OFFSET(rt_rq_active);
|
|
fprintf(fp, " RT PRIO_ARRAY: %lx\n", prio_array);
|
|
|
|
tot = dump_RT_prio_array(prio_array,
|
|
&runqbuf[OFFSET(rq_rt) + OFFSET(rt_rq_active)]);
|
|
if (!tot) {
|
|
INDENT(5);
|
|
fprintf(fp, "[no tasks queued]\n");
|
|
}
|
|
|
|
fprintf(fp, " CFS RB_ROOT: %lx\n", (ulong)root);
|
|
|
|
hq_open();
|
|
tot = dump_tasks_in_cfs_rq(cfs_rq);
|
|
hq_close();
|
|
|
|
if (!tot) {
|
|
INDENT(5);
|
|
fprintf(fp, "[no tasks queued]\n");
|
|
}
|
|
}
|
|
|
|
FREEBUF(runqbuf);
|
|
if (cfs_rq_buf)
|
|
FREEBUF(cfs_rq_buf);
|
|
}
|
|
|
|
static void
|
|
print_group_header_rt(ulong rt_rq, void *t)
|
|
{
|
|
int throttled;
|
|
struct task_group_info *tgi = (struct task_group_info *)t;
|
|
|
|
fprintf(fp, "TASK_GROUP: %lx RT_RQ: %lx", tgi->task_group, rt_rq);
|
|
if (tgi->name)
|
|
fprintf(fp, " <%s>", tgi->name);
|
|
|
|
if (VALID_MEMBER(task_group_rt_bandwidth)) {
|
|
readmem(rt_rq + OFFSET(rt_rq_rt_throttled), KVADDR,
|
|
&throttled, sizeof(int), "rt_rq rt_throttled",
|
|
FAULT_ON_ERROR);
|
|
if (throttled)
|
|
fprintf(fp, " (THROTTLED)");
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
static void
|
|
print_parent_task_group_rt(void *t, int cpu)
|
|
{
|
|
int prio;
|
|
struct task_group_info *tgi;
|
|
ulong rt_rq_c, rt_rq_p;
|
|
|
|
|
|
tgi = ((struct task_group_info *)t)->parent;
|
|
if (tgi && tgi->use)
|
|
print_parent_task_group_fair(tgi, cpu);
|
|
else
|
|
return;
|
|
|
|
readmem(tgi->task_group + OFFSET(task_group_rt_rq),
|
|
KVADDR, &rt_rq_c, sizeof(ulong),
|
|
"task_group rt_rq", FAULT_ON_ERROR);
|
|
readmem(rt_rq_c + cpu * sizeof(ulong), KVADDR, &rt_rq_p,
|
|
sizeof(ulong), "task_group rt_rq", FAULT_ON_ERROR);
|
|
|
|
readmem(rt_rq_p + OFFSET(rt_rq_highest_prio), KVADDR, &prio,
|
|
sizeof(int), "rt_rq highest prio", FAULT_ON_ERROR);
|
|
|
|
INDENT(-1 + 6 * tgi->depth);
|
|
fprintf(fp, "[%3d] ", prio);
|
|
print_group_header_rt(rt_rq_p, tgi);
|
|
tgi->use = 0;
|
|
}
|
|
|
|
static int
|
|
dump_tasks_in_lower_dequeued_rt_rq(int depth, ulong rt_rq, int cpu)
|
|
{
|
|
int i, prio, tot, delta, nr_running;
|
|
ulong rt_rq_c, rt_rq_p, group;
|
|
|
|
tot = 0;
|
|
for (i = 0; i < tgi_p; i++) {
|
|
delta = tgi_array[i]->depth - depth;
|
|
if (delta > 1)
|
|
break;
|
|
|
|
if (tgi_array[i]->use == 0 || delta < 1)
|
|
continue;
|
|
|
|
readmem(rt_rq + OFFSET(rt_rq_tg), KVADDR, &group,
|
|
sizeof(ulong), "rt_rq tg", FAULT_ON_ERROR);
|
|
if (group != tgi_array[i]->parent->task_group)
|
|
continue;
|
|
|
|
readmem(tgi_array[i]->task_group + OFFSET(task_group_rt_rq),
|
|
KVADDR, &rt_rq_c, sizeof(ulong), "task_group rt_rq",
|
|
FAULT_ON_ERROR);
|
|
readmem(rt_rq_c + cpu * sizeof(ulong), KVADDR, &rt_rq_p,
|
|
sizeof(ulong), "task_group rt_rq", FAULT_ON_ERROR);
|
|
if (rt_rq == rt_rq_p)
|
|
continue;
|
|
|
|
readmem(rt_rq_p + OFFSET(rt_rq_rt_nr_running), KVADDR,
|
|
&nr_running, sizeof(int), "rt_rq rt_nr_running",
|
|
FAULT_ON_ERROR);
|
|
if (nr_running == 0) {
|
|
tot += dump_tasks_in_lower_dequeued_rt_rq(depth + 1,
|
|
rt_rq_p, cpu);
|
|
continue;
|
|
}
|
|
|
|
print_parent_task_group_rt(tgi_array[i], cpu);
|
|
|
|
readmem(rt_rq_p + OFFSET(rt_rq_highest_prio), KVADDR,
|
|
&prio, sizeof(int), "rt_rq highest_prio",
|
|
FAULT_ON_ERROR);
|
|
INDENT(5 + 6 * depth);
|
|
fprintf(fp, "[%3d] ", prio);
|
|
tot++;
|
|
dump_tasks_in_task_group_rt_rq(depth + 1, rt_rq_p, cpu);
|
|
}
|
|
|
|
return tot;
|
|
}
|
|
|
|
static int
|
|
dump_RT_prio_array(ulong k_prio_array, char *u_prio_array)
|
|
{
|
|
int i, c, tot, cnt, qheads;
|
|
ulong offset, kvaddr, uvaddr;
|
|
ulong list_head[2];
|
|
struct list_data list_data, *ld;
|
|
struct task_context *tc;
|
|
ulong my_q, task_addr;
|
|
char *rt_rq_buf;
|
|
|
|
qheads = (i = ARRAY_LENGTH(rt_prio_array_queue)) ?
|
|
i : get_array_length("rt_prio_array.queue", NULL, SIZE(list_head));
|
|
|
|
ld = &list_data;
|
|
|
|
for (i = tot = 0; i < qheads; i++) {
|
|
offset = OFFSET(rt_prio_array_queue) + (i * SIZE(list_head));
|
|
kvaddr = k_prio_array + offset;
|
|
uvaddr = (ulong)u_prio_array + offset;
|
|
BCOPY((char *)uvaddr, (char *)&list_head[0], sizeof(ulong)*2);
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "rt_prio_array[%d] @ %lx => %lx/%lx\n",
|
|
i, kvaddr, list_head[0], list_head[1]);
|
|
|
|
if ((list_head[0] == kvaddr) && (list_head[1] == kvaddr))
|
|
continue;
|
|
|
|
BZERO(ld, sizeof(struct list_data));
|
|
ld->start = list_head[0];
|
|
ld->flags |= LIST_ALLOCATE;
|
|
if (VALID_MEMBER(task_struct_rt) &&
|
|
VALID_MEMBER(sched_rt_entity_run_list))
|
|
ld->list_head_offset = OFFSET(sched_rt_entity_run_list);
|
|
else
|
|
ld->list_head_offset = OFFSET(task_struct_run_list);
|
|
ld->end = kvaddr;
|
|
cnt = do_list(ld);
|
|
for (c = 0; c < cnt; c++) {
|
|
task_addr = ld->list_ptr[c];
|
|
if (VALID_MEMBER(sched_rt_entity_my_q)) {
|
|
readmem(ld->list_ptr[c] + OFFSET(sched_rt_entity_my_q),
|
|
KVADDR, &my_q, sizeof(ulong), "my_q",
|
|
FAULT_ON_ERROR);
|
|
if (my_q) {
|
|
rt_rq_buf = GETBUF(SIZE(rt_rq));
|
|
readmem(my_q, KVADDR, rt_rq_buf,
|
|
SIZE(rt_rq), "rt_rq",
|
|
FAULT_ON_ERROR);
|
|
|
|
tot += dump_RT_prio_array(
|
|
my_q + OFFSET(rt_rq_active),
|
|
&rt_rq_buf[OFFSET(rt_rq_active)]);
|
|
FREEBUF(rt_rq_buf);
|
|
continue;
|
|
}
|
|
}
|
|
if (VALID_MEMBER(task_struct_rt))
|
|
task_addr -= OFFSET(task_struct_rt);
|
|
else
|
|
task_addr -= OFFSET(task_struct_run_list);
|
|
|
|
if (!(tc = task_to_context(task_addr)))
|
|
continue;
|
|
|
|
INDENT(5);
|
|
fprintf(fp, "[%3d] ", i);
|
|
fprintf(fp, "PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
tc->pid, tc->task, tc->comm);
|
|
tot++;
|
|
}
|
|
FREEBUF(ld->list_ptr);
|
|
}
|
|
|
|
return tot;
|
|
}
|
|
|
|
static void
|
|
dump_tasks_in_task_group_rt_rq(int depth, ulong rt_rq, int cpu)
|
|
{
|
|
int i, c, tot, cnt, qheads;
|
|
ulong offset, kvaddr, uvaddr;
|
|
ulong list_head[2];
|
|
struct list_data list_data, *ld;
|
|
struct task_context *tc;
|
|
ulong my_q, task_addr, tg, k_prio_array;
|
|
char *rt_rq_buf, *u_prio_array;
|
|
|
|
k_prio_array = rt_rq + OFFSET(rt_rq_active);
|
|
rt_rq_buf = GETBUF(SIZE(rt_rq));
|
|
readmem(rt_rq, KVADDR, rt_rq_buf, SIZE(rt_rq), "rt_rq", FAULT_ON_ERROR);
|
|
u_prio_array = &rt_rq_buf[OFFSET(rt_rq_active)];
|
|
|
|
if (depth) {
|
|
readmem(rt_rq + OFFSET(rt_rq_tg), KVADDR,
|
|
&tg, sizeof(ulong), "rt_rq tg",
|
|
FAULT_ON_ERROR);
|
|
for (i = 0; i < tgi_p; i++) {
|
|
if (tgi_array[i]->task_group == tg) {
|
|
print_group_header_rt(rt_rq, tgi_array[i]);
|
|
tgi_array[i]->use = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
qheads = (i = ARRAY_LENGTH(rt_prio_array_queue)) ?
|
|
i : get_array_length("rt_prio_array.queue", NULL, SIZE(list_head));
|
|
|
|
ld = &list_data;
|
|
|
|
for (i = tot = 0; i < qheads; i++) {
|
|
offset = OFFSET(rt_prio_array_queue) + (i * SIZE(list_head));
|
|
kvaddr = k_prio_array + offset;
|
|
uvaddr = (ulong)u_prio_array + offset;
|
|
BCOPY((char *)uvaddr, (char *)&list_head[0], sizeof(ulong)*2);
|
|
|
|
if (CRASHDEBUG(1))
|
|
fprintf(fp, "rt_prio_array[%d] @ %lx => %lx/%lx\n",
|
|
i, kvaddr, list_head[0], list_head[1]);
|
|
|
|
if ((list_head[0] == kvaddr) && (list_head[1] == kvaddr))
|
|
continue;
|
|
|
|
BZERO(ld, sizeof(struct list_data));
|
|
ld->start = list_head[0];
|
|
ld->flags |= LIST_ALLOCATE;
|
|
if (VALID_MEMBER(task_struct_rt) &&
|
|
VALID_MEMBER(sched_rt_entity_run_list))
|
|
ld->list_head_offset = OFFSET(sched_rt_entity_run_list);
|
|
else
|
|
ld->list_head_offset = OFFSET(task_struct_run_list);
|
|
ld->end = kvaddr;
|
|
cnt = do_list(ld);
|
|
for (c = 0; c < cnt; c++) {
|
|
task_addr = ld->list_ptr[c];
|
|
if (INVALID_MEMBER(sched_rt_entity_my_q))
|
|
goto is_task;
|
|
|
|
readmem(ld->list_ptr[c] + OFFSET(sched_rt_entity_my_q),
|
|
KVADDR, &my_q, sizeof(ulong), "my_q",
|
|
FAULT_ON_ERROR);
|
|
if (!my_q) {
|
|
task_addr -= OFFSET(task_struct_rt);
|
|
goto is_task;
|
|
}
|
|
|
|
INDENT(5 + 6 * depth);
|
|
fprintf(fp, "[%3d] ", i);
|
|
tot++;
|
|
dump_tasks_in_task_group_rt_rq(depth + 1, my_q, cpu);
|
|
continue;
|
|
|
|
is_task:
|
|
if (!(tc = task_to_context(task_addr)))
|
|
continue;
|
|
|
|
INDENT(5 + 6 * depth);
|
|
fprintf(fp, "[%3d] ", i);
|
|
fprintf(fp, "PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
tc->pid, tc->task, tc->comm);
|
|
tot++;
|
|
}
|
|
FREEBUF(ld->list_ptr);
|
|
}
|
|
|
|
tot += dump_tasks_in_lower_dequeued_rt_rq(depth, rt_rq, cpu);
|
|
|
|
if (!tot) {
|
|
INDENT(5 + 6 * depth);
|
|
fprintf(fp, "[no tasks queued]\n");
|
|
}
|
|
FREEBUF(rt_rq_buf);
|
|
}
|
|
|
|
static char *
|
|
get_task_group_name(ulong group)
|
|
{
|
|
ulong cgroup, dentry, kernfs_node, parent, name;
|
|
char *dentry_buf, *tmp;
|
|
char buf[BUFSIZE];
|
|
int len;
|
|
|
|
tmp = NULL;
|
|
readmem(group + OFFSET(task_group_css) + OFFSET(cgroup_subsys_state_cgroup),
|
|
KVADDR, &cgroup, sizeof(ulong),
|
|
"task_group css cgroup", FAULT_ON_ERROR);
|
|
if (cgroup == 0)
|
|
return NULL;
|
|
|
|
if (VALID_MEMBER(cgroup_dentry)) {
|
|
readmem(cgroup + OFFSET(cgroup_dentry), KVADDR, &dentry, sizeof(ulong),
|
|
"cgroup dentry", FAULT_ON_ERROR);
|
|
if (dentry == 0)
|
|
return NULL;
|
|
|
|
dentry_buf = GETBUF(SIZE(dentry));
|
|
readmem(dentry, KVADDR, dentry_buf, SIZE(dentry),
|
|
"dentry", FAULT_ON_ERROR);
|
|
len = UINT(dentry_buf + OFFSET(dentry_d_name) + OFFSET(qstr_len));
|
|
tmp = GETBUF(len + 1);
|
|
name = ULONG(dentry_buf + OFFSET(dentry_d_name) + OFFSET(qstr_name));
|
|
readmem(name, KVADDR, tmp, len, "qstr name", FAULT_ON_ERROR);
|
|
|
|
FREEBUF(dentry_buf);
|
|
return tmp;
|
|
}
|
|
|
|
/*
|
|
* Emulate kernfs_name() and kernfs_name_locked()
|
|
*/
|
|
if (INVALID_MEMBER(cgroup_kn) || INVALID_MEMBER(kernfs_node_name) ||
|
|
INVALID_MEMBER(kernfs_node_parent))
|
|
return NULL;
|
|
|
|
readmem(cgroup + OFFSET(cgroup_kn), KVADDR, &kernfs_node, sizeof(ulong),
|
|
"cgroup kn", FAULT_ON_ERROR);
|
|
if (kernfs_node == 0)
|
|
return NULL;
|
|
|
|
readmem(kernfs_node + OFFSET(kernfs_node_parent), KVADDR, &parent,
|
|
sizeof(ulong), "kernfs_node parent", FAULT_ON_ERROR);
|
|
if (!parent) {
|
|
tmp = GETBUF(2);
|
|
strcpy(tmp, "/");
|
|
return tmp;
|
|
}
|
|
|
|
readmem(kernfs_node + OFFSET(kernfs_node_name), KVADDR, &name,
|
|
sizeof(ulong), "kernfs_node name", FAULT_ON_ERROR);
|
|
if (!name || !read_string(name, buf, BUFSIZE-1))
|
|
return NULL;
|
|
|
|
tmp = GETBUF(strlen(buf)+1);
|
|
strcpy(tmp, buf);
|
|
|
|
return tmp;
|
|
}
|
|
|
|
static void
|
|
fill_task_group_info_array(int depth, ulong group, char *group_buf, int i)
|
|
{
|
|
int d;
|
|
ulong kvaddr, uvaddr, offset;
|
|
ulong list_head[2], next;
|
|
struct task_group_info **tgi_array_new;
|
|
|
|
d = tgi_p;
|
|
tgi_array[tgi_p] = (struct task_group_info *)
|
|
GETBUF(sizeof(struct task_group_info));
|
|
if (depth)
|
|
tgi_array[tgi_p]->use = 1;
|
|
else
|
|
tgi_array[tgi_p]->use = 0;
|
|
|
|
tgi_array[tgi_p]->depth = depth;
|
|
tgi_array[tgi_p]->name = get_task_group_name(group);
|
|
tgi_array[tgi_p]->task_group = group;
|
|
if (i >= 0)
|
|
tgi_array[tgi_p]->parent = tgi_array[i];
|
|
else
|
|
tgi_array[tgi_p]->parent = NULL;
|
|
tgi_p++;
|
|
|
|
if (tgi_p == tgi_p_max) {
|
|
tgi_p_max += MAX_GROUP_NUM;
|
|
tgi_array_new = (struct task_group_info **)
|
|
GETBUF(sizeof(void *) * tgi_p_max);
|
|
BCOPY(tgi_array, tgi_array_new, sizeof(void *) * tgi_p);
|
|
FREEBUF(tgi_array);
|
|
tgi_array = tgi_array_new;
|
|
}
|
|
|
|
offset = OFFSET(task_group_children);
|
|
kvaddr = group + offset;
|
|
uvaddr = (ulong)(group_buf + offset);
|
|
BCOPY((char *)uvaddr, (char *)&list_head[0], sizeof(ulong)*2);
|
|
|
|
if ((list_head[0] == kvaddr) && (list_head[1] == kvaddr))
|
|
return;
|
|
|
|
next = list_head[0];
|
|
while (next != kvaddr) {
|
|
group = next - OFFSET(task_group_siblings);
|
|
readmem(group, KVADDR, group_buf, SIZE(task_group),
|
|
"task_group", FAULT_ON_ERROR);
|
|
next = ULONG(group_buf + OFFSET(task_group_siblings) +
|
|
OFFSET(list_head_next));
|
|
fill_task_group_info_array(depth + 1, group, group_buf, d);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dump_tasks_by_task_group(void)
|
|
{
|
|
int cpu, displayed;
|
|
ulong root_task_group, cfs_rq = 0, cfs_rq_p;
|
|
ulong rt_rq = 0, rt_rq_p;
|
|
char *buf;
|
|
struct task_context *tc;
|
|
char *task_group_name;
|
|
ulong *cpus;
|
|
|
|
cfs_rq_offset_init();
|
|
task_group_offset_init();
|
|
|
|
root_task_group = 0;
|
|
task_group_name = NULL;
|
|
if (symbol_exists("init_task_group")) {
|
|
root_task_group = symbol_value("init_task_group");
|
|
task_group_name = "INIT";
|
|
} else if (symbol_exists("root_task_group")) {
|
|
root_task_group = symbol_value("root_task_group");
|
|
task_group_name = "ROOT";
|
|
} else
|
|
error(FATAL, "cannot determine root task_group\n");
|
|
|
|
tgi_p_max = MAX_GROUP_NUM;
|
|
tgi_array = (struct task_group_info **)GETBUF(sizeof(void *)
|
|
* tgi_p_max);
|
|
buf = GETBUF(SIZE(task_group));
|
|
readmem(root_task_group, KVADDR, buf, SIZE(task_group),
|
|
"task_group", FAULT_ON_ERROR);
|
|
if (VALID_MEMBER(task_group_rt_rq))
|
|
rt_rq = ULONG(buf + OFFSET(task_group_rt_rq));
|
|
if (VALID_MEMBER(task_group_cfs_rq))
|
|
cfs_rq = ULONG(buf + OFFSET(task_group_cfs_rq));
|
|
|
|
fill_task_group_info_array(0, root_task_group, buf, -1);
|
|
sort_task_group_info_array();
|
|
if (CRASHDEBUG(1))
|
|
print_task_group_info_array();
|
|
|
|
get_active_set();
|
|
|
|
cpus = pc->curcmd_flags & CPUMASK ?
|
|
(ulong *)(ulong)pc->curcmd_private : NULL;
|
|
|
|
for (cpu = displayed = 0; cpu < kt->cpus; cpu++) {
|
|
if (cpus && !NUM_IN_BITMAP(cpus, cpu))
|
|
continue;
|
|
|
|
if (rt_rq)
|
|
readmem(rt_rq + cpu * sizeof(ulong), KVADDR,
|
|
&rt_rq_p, sizeof(ulong), "task_group rt_rq",
|
|
FAULT_ON_ERROR);
|
|
if (cfs_rq)
|
|
readmem(cfs_rq + cpu * sizeof(ulong), KVADDR,
|
|
&cfs_rq_p, sizeof(ulong), "task_group cfs_rq",
|
|
FAULT_ON_ERROR);
|
|
fprintf(fp, "%sCPU %d", displayed++ ? "\n" : "", cpu);
|
|
|
|
if (hide_offline_cpu(cpu)) {
|
|
fprintf(fp, " [OFFLINE]\n");
|
|
continue;
|
|
} else
|
|
fprintf(fp, "\n");
|
|
|
|
fprintf(fp, " CURRENT: ");
|
|
if ((tc = task_to_context(tt->active_set[cpu])))
|
|
fprintf(fp, "PID: %-5ld TASK: %lx COMMAND: \"%s\"\n",
|
|
tc->pid, tc->task, tc->comm);
|
|
else
|
|
fprintf(fp, "%lx\n", tt->active_set[cpu]);
|
|
|
|
if (rt_rq) {
|
|
fprintf(fp, " %s_TASK_GROUP: %lx RT_RQ: %lx\n",
|
|
task_group_name, root_task_group, rt_rq_p);
|
|
reuse_task_group_info_array();
|
|
dump_tasks_in_task_group_rt_rq(0, rt_rq_p, cpu);
|
|
}
|
|
|
|
if (cfs_rq) {
|
|
fprintf(fp, " %s_TASK_GROUP: %lx CFS_RQ: %lx\n",
|
|
task_group_name, root_task_group, cfs_rq_p);
|
|
reuse_task_group_info_array();
|
|
dump_tasks_in_task_group_cfs_rq(0, cfs_rq_p, cpu, tc);
|
|
}
|
|
}
|
|
|
|
FREEBUF(buf);
|
|
free_task_group_info_array();
|
|
}
|
|
|
|
#undef _NSIG
|
|
#define _NSIG 64
|
|
#define _NSIG_BPW machdep->bits
|
|
#define _NSIG_WORDS (_NSIG / _NSIG_BPW)
|
|
|
|
#undef SIGRTMIN
|
|
#define SIGRTMIN 32
|
|
|
|
static struct signame {
|
|
char *name;
|
|
char *altname;
|
|
} signame[_NSIG] = {
|
|
/* 0 */ {NULL, NULL},
|
|
/* 1 */ {"SIGHUP", NULL},
|
|
/* 2 */ {"SIGINT", NULL},
|
|
/* 3 */ {"SIGQUIT", NULL},
|
|
/* 4 */ {"SIGILL", NULL},
|
|
/* 5 */ {"SIGTRAP", NULL},
|
|
/* 6 */ {"SIGABRT", "SIGIOT"},
|
|
/* 7 */ {"SIGBUS", NULL},
|
|
/* 8 */ {"SIGFPE", NULL},
|
|
/* 9 */ {"SIGKILL", NULL},
|
|
/* 10 */ {"SIGUSR1", NULL},
|
|
/* 11 */ {"SIGSEGV", NULL},
|
|
/* 12 */ {"SIGUSR2", NULL},
|
|
/* 13 */ {"SIGPIPE", NULL},
|
|
/* 14 */ {"SIGALRM", NULL},
|
|
/* 15 */ {"SIGTERM", NULL},
|
|
/* 16 */ {"SIGSTKFLT", NULL},
|
|
/* 17 */ {"SIGCHLD", "SIGCLD"},
|
|
/* 18 */ {"SIGCONT", NULL},
|
|
/* 19 */ {"SIGSTOP", NULL},
|
|
/* 20 */ {"SIGTSTP", NULL},
|
|
/* 21 */ {"SIGTTIN", NULL},
|
|
/* 22 */ {"SIGTTOU", NULL},
|
|
/* 23 */ {"SIGURG", NULL},
|
|
/* 24 */ {"SIGXCPU", NULL},
|
|
/* 25 */ {"SIGXFSZ", NULL},
|
|
/* 26 */ {"SIGVTALRM", NULL},
|
|
/* 27 */ {"SIGPROF", NULL},
|
|
/* 28 */ {"SIGWINCH", NULL},
|
|
/* 29 */ {"SIGIO", "SIGPOLL"},
|
|
/* 30 */ {"SIGPWR", NULL},
|
|
/* 31 */ {"SIGSYS", "SIGUNUSED"},
|
|
{NULL, NULL}, /* Real time signals start here. */
|
|
};
|
|
|
|
static int
|
|
sigrt_minmax(int *min, int *max)
|
|
{
|
|
int sigrtmax, j;
|
|
|
|
sigrtmax = THIS_KERNEL_VERSION < LINUX(2,5,0) ?
|
|
_NSIG - 1 : _NSIG;
|
|
|
|
if (min && max) {
|
|
j = sigrtmax-SIGRTMIN-1;
|
|
*max = j / 2;
|
|
*min = j - *max;
|
|
}
|
|
|
|
return sigrtmax;
|
|
}
|
|
|
|
static void
|
|
signame_list(void)
|
|
{
|
|
int i, sigrtmax, j, min, max;
|
|
|
|
sigrtmax = sigrt_minmax(&min, &max);
|
|
j = 1;
|
|
|
|
for (i = 1; i <= sigrtmax; i++) {
|
|
if ((i == SIGRTMIN) || (i == sigrtmax)) {
|
|
fprintf(fp, "[%d] %s", i,
|
|
(i== SIGRTMIN) ? "SIGRTMIN" : "SIGRTMAX");
|
|
} else if (i > SIGRTMIN) {
|
|
if (j <= min){
|
|
fprintf(fp, "[%d] %s%d", i , "SIGRTMIN+", j);
|
|
j++;
|
|
} else if (max >= 1) {
|
|
fprintf(fp, "[%d] %s%d", i , "SIGRTMAX-",max);
|
|
max--;
|
|
}
|
|
} else {
|
|
if (!signame[i].name)
|
|
continue;
|
|
|
|
fprintf(fp, "%s[%d] %s", i < 10 ? " " : "",
|
|
i, signame[i].name);
|
|
if (signame[i].altname)
|
|
fprintf(fp, "/%s", signame[i].altname);
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Translate the bits in a signal set into their name strings.
|
|
*/
|
|
static void
|
|
translate_sigset(ulonglong sigset)
|
|
{
|
|
int sigrtmax, min, max, i, j, c, len;
|
|
char buf[BUFSIZE];
|
|
|
|
if (!sigset) {
|
|
fprintf(fp, "(none)\n");
|
|
return;
|
|
}
|
|
|
|
len = 0;
|
|
sigrtmax= sigrt_minmax(&min, &max);
|
|
j = 1;
|
|
|
|
for (i = 1, c = 0; i <= sigrtmax; i++) {
|
|
if (sigset & (ulonglong)1) {
|
|
if (i == SIGRTMIN || i == sigrtmax)
|
|
sprintf(buf, "%s%s", c++ ? " " : "",
|
|
(i==SIGRTMIN) ? "SIGRTMIN" : "SIGRTMAX");
|
|
else if (i > SIGRTMIN) {
|
|
if (j <= min)
|
|
sprintf(buf, "%s%s%d",
|
|
c++ ? " " : "", "SIGRTMIN+", j);
|
|
else if (max >= 1)
|
|
sprintf(buf, "%s%s%d",
|
|
c++ ? " " : "", "SIGRTMAX-", max);
|
|
} else
|
|
sprintf(buf, "%s%s", c++ ? " " : "",
|
|
signame[i].name);
|
|
|
|
if ((len + strlen(buf)) > 80) {
|
|
shift_string_left(buf, 1);
|
|
fprintf(fp, "\n");
|
|
len = 0;
|
|
}
|
|
|
|
len += strlen(buf);
|
|
fprintf(fp, "%s", buf);
|
|
}
|
|
|
|
sigset >>= 1;
|
|
if (i > SIGRTMIN) {
|
|
if (j <= min)
|
|
j++;
|
|
else if (max >= 1)
|
|
max--;
|
|
}
|
|
}
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* Machine dependent interface to modify signame struct contents.
|
|
*/
|
|
void modify_signame(int sig, char *name, char *altname)
|
|
{
|
|
signame[sig].name = name;
|
|
signame[sig].altname = altname;
|
|
}
|
|
|
|
/*
|
|
* Display all signal-handling data for a task.
|
|
*
|
|
* Reference handling framework is here, but not used as of yet.
|
|
*/
|
|
|
|
void
|
|
cmd_sig(void)
|
|
{
|
|
int c, tcnt, bogus;
|
|
ulong value;
|
|
ulonglong sigset;
|
|
struct reference *ref;
|
|
struct task_context *tc;
|
|
ulong *tasklist;
|
|
char *siglist;
|
|
int thread_group = FALSE;
|
|
|
|
tasklist = (ulong *)GETBUF((MAXARGS+NR_CPUS)*sizeof(ulong));
|
|
ref = (struct reference *)GETBUF(sizeof(struct reference));
|
|
siglist = GETBUF(BUFSIZE);
|
|
ref->str = siglist;
|
|
|
|
while ((c = getopt(argcnt, args, "lR:s:g")) != EOF) {
|
|
switch(c)
|
|
{
|
|
case 's':
|
|
sigset = htoll(optarg, FAULT_ON_ERROR, NULL);
|
|
translate_sigset(sigset);
|
|
return;
|
|
|
|
case 'R':
|
|
if (strlen(ref->str))
|
|
strcat(ref->str, ",");
|
|
strcat(ref->str, optarg);
|
|
break;
|
|
|
|
case 'l':
|
|
signame_list();
|
|
return;
|
|
|
|
case 'g':
|
|
pc->curcmd_flags |= TASK_SPECIFIED;
|
|
thread_group = TRUE;
|
|
break;
|
|
default:
|
|
argerrs++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (argerrs)
|
|
cmd_usage(pc->curcmd, SYNOPSIS);
|
|
|
|
tcnt = bogus = 0;
|
|
|
|
while (args[optind]) {
|
|
if (IS_A_NUMBER(args[optind])) {
|
|
switch (str_to_context(args[optind], &value, &tc))
|
|
{
|
|
case STR_PID:
|
|
for (tc = pid_to_context(value); tc;
|
|
tc = tc->tc_next)
|
|
tasklist[tcnt++] = tc->task;
|
|
break;
|
|
|
|
case STR_TASK:
|
|
tasklist[tcnt++] = value;
|
|
break;
|
|
|
|
case STR_INVALID:
|
|
bogus++;
|
|
error(INFO, "invalid task or pid value: %s\n\n",
|
|
args[optind]);
|
|
break;
|
|
}
|
|
} else if (strstr(args[optind], ",") ||
|
|
MEMBER_EXISTS("task_struct", args[optind])) {
|
|
if (strlen(ref->str))
|
|
strcat(ref->str, ",");
|
|
strcat(ref->str, args[optind]);
|
|
} else
|
|
error(INFO, "invalid task or pid value: %s\n\n",
|
|
args[optind]);
|
|
optind++;
|
|
}
|
|
|
|
if (!tcnt && !bogus)
|
|
tasklist[tcnt++] = CURRENT_TASK();
|
|
|
|
for (c = 0; c < tcnt; c++) {
|
|
if (thread_group)
|
|
do_sig_thread_group(tasklist[c]);
|
|
else {
|
|
do_sig(tasklist[c], 0, strlen(ref->str) ? ref : NULL);
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/*
|
|
* Do the work for the "sig -g" command option, coming from sig or foreach.
|
|
*/
|
|
static void
|
|
do_sig_thread_group(ulong task)
|
|
{
|
|
int i;
|
|
int cnt;
|
|
struct task_context *tc;
|
|
ulong tgid;
|
|
|
|
tc = task_to_context(task);
|
|
tgid = task_tgid(task);
|
|
|
|
if (tc->pid != tgid) {
|
|
if (pc->curcmd_flags & TASK_SPECIFIED) {
|
|
if (!(tc = tgid_to_context(tgid)))
|
|
return;
|
|
task = tc->task;
|
|
} else
|
|
return;
|
|
}
|
|
|
|
if ((tc->pid == 0) && (pc->curcmd_flags & IDLE_TASK_SHOWN))
|
|
return;
|
|
|
|
print_task_header(fp, tc, 0);
|
|
dump_signal_data(tc, THREAD_GROUP_LEVEL);
|
|
fprintf(fp, "\n ");
|
|
print_task_header(fp, tc, 0);
|
|
dump_signal_data(tc, TASK_LEVEL|TASK_INDENT);
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = cnt = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
if (tc->task == task)
|
|
continue;
|
|
|
|
if (task_tgid(tc->task) == tgid) {
|
|
fprintf(fp, "\n ");
|
|
print_task_header(fp, tc, 0);
|
|
dump_signal_data(tc, TASK_LEVEL|TASK_INDENT);
|
|
cnt++;
|
|
if (tc->pid == 0)
|
|
pc->curcmd_flags |= IDLE_TASK_SHOWN;
|
|
}
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
/*
|
|
* Do the work for the sig command, coming from sig or foreach.
|
|
*/
|
|
void
|
|
do_sig(ulong task, ulong flags, struct reference *ref)
|
|
{
|
|
struct task_context *tc;
|
|
|
|
tc = task_to_context(task);
|
|
|
|
if (ref)
|
|
signal_reference(tc, flags, ref);
|
|
else {
|
|
if (!(flags & FOREACH_SIG))
|
|
print_task_header(fp, tc, 0);
|
|
dump_signal_data(tc, TASK_LEVEL|THREAD_GROUP_LEVEL);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Implementation for -R reference for the sig command.
|
|
*/
|
|
static void
|
|
signal_reference(struct task_context *tc, ulong flags, struct reference *ref)
|
|
{
|
|
if (flags & FOREACH_SIG)
|
|
error(FATAL, "sig: -R not supported yet\n");
|
|
else
|
|
error(FATAL, "-R not supported yet\n");
|
|
}
|
|
|
|
/*
|
|
* Dump all signal-handling data for a task.
|
|
*/
|
|
static void
|
|
dump_signal_data(struct task_context *tc, ulong flags)
|
|
{
|
|
int i, sigrtmax, others, use_sighand;
|
|
int translate, sigpending;
|
|
uint ti_flags;
|
|
ulonglong sigset, blocked, mask;
|
|
ulong signal_struct, kaddr, handler, sa_flags, sigqueue;
|
|
ulong sighand_struct;
|
|
long size;
|
|
char *signal_buf, *uaddr;
|
|
ulong shared_pending, signal;
|
|
char buf1[BUFSIZE];
|
|
char buf2[BUFSIZE];
|
|
char buf3[BUFSIZE];
|
|
char buf4[BUFSIZE];
|
|
|
|
sigpending = sigqueue = 0;
|
|
sighand_struct = signal_struct = 0;
|
|
|
|
if (VALID_STRUCT(sigqueue) && !VALID_MEMBER(sigqueue_next)) {
|
|
MEMBER_OFFSET_INIT(sigqueue_next, "sigqueue", "next");
|
|
MEMBER_OFFSET_INIT(sigqueue_list, "sigqueue", "list");
|
|
MEMBER_OFFSET_INIT(sigqueue_info, "sigqueue", "info");
|
|
} else if (!VALID_MEMBER(signal_queue_next)) {
|
|
MEMBER_OFFSET_INIT(signal_queue_next, "signal_queue", "next");
|
|
MEMBER_OFFSET_INIT(signal_queue_info, "signal_queue", "info");
|
|
}
|
|
|
|
sigset = task_signal(tc->task, 0);
|
|
if (!tt->last_task_read)
|
|
return;
|
|
|
|
if (VALID_MEMBER(task_struct_sig))
|
|
signal_struct = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_sig));
|
|
else if (VALID_MEMBER(task_struct_signal))
|
|
signal_struct = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_signal));
|
|
|
|
size = MAX(SIZE(signal_struct), VALID_SIZE(signal_queue) ?
|
|
SIZE(signal_queue) : SIZE(sigqueue));
|
|
if (VALID_SIZE(sighand_struct))
|
|
size = MAX(size, SIZE(sighand_struct));
|
|
signal_buf = GETBUF(size);
|
|
|
|
if (signal_struct)
|
|
readmem(signal_struct, KVADDR, signal_buf,
|
|
SIZE(signal_struct), "signal_struct buffer",
|
|
FAULT_ON_ERROR);
|
|
|
|
/*
|
|
* Signal dispositions (thread group level).
|
|
*/
|
|
if (flags & THREAD_GROUP_LEVEL) {
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
fprintf(fp, "SIGNAL_STRUCT: %lx ", signal_struct);
|
|
if (!signal_struct) {
|
|
fprintf(fp, "\n");
|
|
return;
|
|
}
|
|
if (VALID_MEMBER(signal_struct_count))
|
|
fprintf(fp, "COUNT: %d\n",
|
|
INT(signal_buf + OFFSET(signal_struct_count)));
|
|
else if (VALID_MEMBER(signal_struct_nr_threads))
|
|
fprintf(fp, "NR_THREADS: %d\n",
|
|
INT(signal_buf + OFFSET(signal_struct_nr_threads)));
|
|
else
|
|
fprintf(fp, "\n");
|
|
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
fprintf(fp, " SIG %s %s %s %s\n",
|
|
mkstring(buf1, VADDR_PRLEN == 8 ? 9 : VADDR_PRLEN,
|
|
CENTER, "SIGACTION"),
|
|
mkstring(buf2, UVADDR_PRLEN, RJUST, "HANDLER"),
|
|
mkstring(buf3, 16, CENTER, "MASK"),
|
|
mkstring(buf4, VADDR_PRLEN, LJUST, "FLAGS"));
|
|
|
|
if (VALID_MEMBER(task_struct_sighand)) {
|
|
sighand_struct = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_sighand));
|
|
readmem(sighand_struct, KVADDR, signal_buf,
|
|
SIZE(sighand_struct), "sighand_struct buffer",
|
|
FAULT_ON_ERROR);
|
|
use_sighand = TRUE;
|
|
} else
|
|
use_sighand = FALSE;
|
|
|
|
sigrtmax = sigrt_minmax(NULL, NULL);
|
|
|
|
for (i = 1; i <= sigrtmax; i++) {
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
|
|
fprintf(fp, "%s[%d] ", i < 10 ? " " : "", i);
|
|
|
|
if (use_sighand) {
|
|
kaddr = sighand_struct +
|
|
OFFSET(sighand_struct_action) +
|
|
((i-1) * SIZE(k_sigaction));
|
|
uaddr = signal_buf +
|
|
OFFSET(sighand_struct_action) +
|
|
((i-1) * SIZE(k_sigaction));
|
|
} else {
|
|
kaddr = signal_struct +
|
|
OFFSET(signal_struct_action) +
|
|
((i-1) * SIZE(k_sigaction));
|
|
uaddr = signal_buf +
|
|
OFFSET(signal_struct_action) +
|
|
((i-1) * SIZE(k_sigaction));
|
|
}
|
|
|
|
handler = ULONG(uaddr + OFFSET(sigaction_sa_handler));
|
|
switch ((long)handler)
|
|
{
|
|
case -1:
|
|
mkstring(buf1, UVADDR_PRLEN, RJUST, "SIG_ERR");
|
|
break;
|
|
case 0:
|
|
mkstring(buf1, UVADDR_PRLEN, RJUST, "SIG_DFL");
|
|
break;
|
|
case 1:
|
|
mkstring(buf1, UVADDR_PRLEN, RJUST, "SIG_IGN");
|
|
break;
|
|
default:
|
|
mkstring(buf1, UVADDR_PRLEN, RJUST|LONG_HEX,
|
|
MKSTR(handler));
|
|
break;
|
|
}
|
|
|
|
mask = sigaction_mask((ulong)uaddr);
|
|
sa_flags = ULONG(uaddr + OFFSET(sigaction_sa_flags));
|
|
|
|
fprintf(fp, "%s%s %s %016llx %lx ",
|
|
space(MINSPACE-1),
|
|
mkstring(buf2,
|
|
UVADDR_PRLEN,LJUST|LONG_HEX,MKSTR(kaddr)),
|
|
buf1,
|
|
mask,
|
|
sa_flags);
|
|
|
|
if (sa_flags) {
|
|
others = 0; translate = 1;
|
|
if (sa_flags & SA_NOCLDSTOP)
|
|
fprintf(fp, "%s%sSA_NOCLDSTOP",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
#ifdef SA_RESTORER
|
|
if (sa_flags & SA_RESTORER)
|
|
fprintf(fp, "%s%sSA_RESTORER",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
#endif
|
|
#ifdef SA_NOCLDWAIT
|
|
if (sa_flags & SA_NOCLDWAIT)
|
|
fprintf(fp, "%s%sSA_NOCLDWAIT",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
#endif
|
|
if (sa_flags & SA_SIGINFO)
|
|
fprintf(fp, "%s%sSA_SIGINFO",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
if (sa_flags & SA_ONSTACK)
|
|
fprintf(fp, "%s%sSA_ONSTACK",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
if (sa_flags & SA_RESTART)
|
|
fprintf(fp, "%s%sSA_RESTART",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
if (sa_flags & SA_NODEFER)
|
|
fprintf(fp, "%s%sSA_NODEFER",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
if (sa_flags & SA_RESETHAND)
|
|
fprintf(fp, "%s%sSA_RESETHAND",
|
|
translate-- > 0 ? "(" : "",
|
|
others++ ? "|" : "");
|
|
if (translate < 1)
|
|
fprintf(fp, ")");
|
|
}
|
|
|
|
fprintf(fp, "\n");
|
|
}
|
|
}
|
|
|
|
if (flags & TASK_LEVEL) {
|
|
/*
|
|
* Pending signals (task level).
|
|
*/
|
|
if (VALID_MEMBER(task_struct_sigpending))
|
|
sigpending = INT(tt->task_struct +
|
|
OFFSET(task_struct_sigpending));
|
|
else if (VALID_MEMBER(thread_info_flags)) {
|
|
fill_thread_info(tc->thread_info);
|
|
ti_flags = UINT(tt->thread_info + OFFSET(thread_info_flags));
|
|
sigpending = ti_flags & (1<<TIF_SIGPENDING);
|
|
}
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
fprintf(fp, "SIGPENDING: %s\n", sigpending ? "yes" : "no");
|
|
|
|
/*
|
|
* Blocked signals (task level).
|
|
*/
|
|
|
|
blocked = task_blocked(tc->task);
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
fprintf(fp, " BLOCKED: %016llx\n", blocked);
|
|
|
|
/*
|
|
* Pending queue (task level).
|
|
*/
|
|
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
if (VALID_MEMBER(signal_struct_shared_pending)) {
|
|
fprintf(fp, "PRIVATE_PENDING\n");
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
}
|
|
fprintf(fp, " SIGNAL: %016llx\n", sigset);
|
|
|
|
if (VALID_MEMBER(task_struct_sigqueue))
|
|
sigqueue = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_sigqueue));
|
|
|
|
else if (VALID_MEMBER(task_struct_pending))
|
|
sigqueue = ULONG(tt->task_struct +
|
|
OFFSET(task_struct_pending) +
|
|
OFFSET_OPTION(sigpending_head,
|
|
sigpending_list));
|
|
|
|
if (VALID_MEMBER(sigqueue_list) && empty_list(sigqueue))
|
|
sigqueue = 0;
|
|
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
if (sigqueue) {
|
|
fprintf(fp, " SIGQUEUE: SIG %s\n",
|
|
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "SIGINFO"));
|
|
sigqueue_list(sigqueue);
|
|
} else
|
|
fprintf(fp, " SIGQUEUE: (empty)\n");
|
|
}
|
|
|
|
/*
|
|
* Pending queue (thread group level).
|
|
*/
|
|
if ((flags & THREAD_GROUP_LEVEL) &&
|
|
VALID_MEMBER(signal_struct_shared_pending)) {
|
|
|
|
fprintf(fp, "SHARED_PENDING\n");
|
|
shared_pending = signal_struct + OFFSET(signal_struct_shared_pending);
|
|
signal = shared_pending + OFFSET(sigpending_signal);
|
|
readmem(signal, KVADDR, signal_buf,SIZE(sigpending_signal),
|
|
"signal", FAULT_ON_ERROR);
|
|
sigset = task_signal(0, (ulong*)signal_buf);
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
fprintf(fp, " SIGNAL: %016llx\n", sigset);
|
|
sigqueue = (shared_pending +
|
|
OFFSET_OPTION(sigpending_head, sigpending_list) +
|
|
OFFSET(list_head_next));
|
|
readmem(sigqueue,KVADDR, signal_buf,
|
|
SIZE(sigqueue), "sigqueue", FAULT_ON_ERROR);
|
|
sigqueue = ULONG(signal_buf);
|
|
|
|
if (VALID_MEMBER(sigqueue_list) && empty_list(sigqueue))
|
|
sigqueue = 0;
|
|
if (flags & TASK_INDENT)
|
|
INDENT(2);
|
|
if (sigqueue) {
|
|
fprintf(fp, " SIGQUEUE: SIG %s\n",
|
|
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "SIGINFO"));
|
|
sigqueue_list(sigqueue);
|
|
} else
|
|
fprintf(fp, " SIGQUEUE: (empty)\n");
|
|
}
|
|
FREEBUF(signal_buf);
|
|
}
|
|
|
|
/*
|
|
* Dump a pending signal queue (private/shared).
|
|
*/
|
|
|
|
static void sigqueue_list(ulong sigqueue) {
|
|
ulong sigqueue_save, next;
|
|
int sig;
|
|
char *signal_buf;
|
|
long size;
|
|
size = VALID_SIZE(signal_queue) ? SIZE(signal_queue) : SIZE(sigqueue);
|
|
signal_buf = GETBUF(size);
|
|
|
|
sigqueue_save = sigqueue;
|
|
while (sigqueue) {
|
|
readmem(sigqueue, KVADDR, signal_buf,
|
|
SIZE_OPTION(signal_queue, sigqueue),
|
|
"signal_queue/sigqueue", FAULT_ON_ERROR);
|
|
|
|
if (VALID_MEMBER(signal_queue_next) &&
|
|
VALID_MEMBER(signal_queue_info)) {
|
|
next = ULONG(signal_buf + OFFSET(signal_queue_next));
|
|
sig = INT(signal_buf + OFFSET(signal_queue_info) +
|
|
OFFSET(siginfo_si_signo));
|
|
} else {
|
|
next = ULONG(signal_buf +
|
|
OFFSET_OPTION(sigqueue_next, sigqueue_list));
|
|
sig = INT(signal_buf + OFFSET(sigqueue_info) +
|
|
OFFSET(siginfo_si_signo));
|
|
}
|
|
|
|
if (sigqueue_save == next)
|
|
break;
|
|
|
|
fprintf(fp, " %3d %lx\n",
|
|
sig, sigqueue +
|
|
OFFSET_OPTION(signal_queue_info, sigqueue_info));
|
|
|
|
sigqueue = next;
|
|
}
|
|
FREEBUF(signal_buf);
|
|
|
|
}
|
|
|
|
/*
|
|
* Return the current set of signals sent to a task, in the form of
|
|
* a long long data type form that can be easily masked regardless
|
|
* of its size.
|
|
*/
|
|
|
|
static ulonglong
|
|
task_signal(ulong task, ulong *signal)
|
|
{
|
|
ulong *sigset_ptr;
|
|
ulonglong sigset = 0;
|
|
|
|
if (task) {
|
|
fill_task_struct(task);
|
|
|
|
if (!tt->last_task_read)
|
|
return 0;
|
|
|
|
if (VALID_MEMBER(sigpending_signal)) {
|
|
sigset_ptr = (ulong *)(tt->task_struct +
|
|
OFFSET(task_struct_pending) +
|
|
OFFSET(sigpending_signal));
|
|
} else if (VALID_MEMBER(task_struct_signal)) {
|
|
sigset_ptr = (ulong *)(tt->task_struct +
|
|
OFFSET(task_struct_signal));
|
|
} else
|
|
return 0;
|
|
} else if (signal) {
|
|
sigset_ptr = signal;
|
|
} else
|
|
return 0;
|
|
|
|
switch (_NSIG_WORDS)
|
|
{
|
|
case 1:
|
|
sigset = (ulonglong)sigset_ptr[0];
|
|
break;
|
|
|
|
case 2:
|
|
sigset = (ulonglong)(sigset_ptr[1]) << 32;
|
|
sigset |= (ulonglong)(sigset_ptr[0]);
|
|
break;
|
|
}
|
|
|
|
return sigset;
|
|
}
|
|
|
|
/*
|
|
* Return the current set of signals that a task has blocked, in the form
|
|
* of a long long data type form that can be easily masked regardless
|
|
* of its size.
|
|
*/
|
|
|
|
static ulonglong
|
|
task_blocked(ulong task)
|
|
{
|
|
ulonglong sigset;
|
|
ulong *sigset_ptr;
|
|
|
|
fill_task_struct(task);
|
|
|
|
if (!tt->last_task_read)
|
|
return 0;
|
|
|
|
sigset_ptr = (ulong *)(tt->task_struct + OFFSET(task_struct_blocked));
|
|
|
|
sigset = (ulonglong)(sigset_ptr[1]) << 32;
|
|
sigset |= (ulonglong)(sigset_ptr[0]);
|
|
|
|
return sigset;
|
|
}
|
|
|
|
static ulonglong
|
|
sigaction_mask(ulong sigaction)
|
|
{
|
|
ulonglong sigset;
|
|
ulong *sigset_ptr;
|
|
|
|
sigset = 0;
|
|
sigset_ptr = (ulong *)(sigaction + OFFSET(sigaction_sa_mask));
|
|
|
|
switch (_NSIG_WORDS)
|
|
{
|
|
case 1:
|
|
sigset = (ulonglong)sigset_ptr[0];
|
|
break;
|
|
|
|
case 2:
|
|
sigset = (ulonglong)(sigset_ptr[1]) << 32;
|
|
sigset |= (ulonglong)(sigset_ptr[0]);
|
|
break;
|
|
}
|
|
|
|
return sigset;
|
|
}
|
|
|
|
/*
|
|
* Deal with potential separation of task_struct and kernel stack.
|
|
*/
|
|
ulong
|
|
generic_get_stackbase(ulong task)
|
|
{
|
|
return task_to_stackbase(task);
|
|
}
|
|
|
|
ulong
|
|
generic_get_stacktop(ulong task)
|
|
{
|
|
return task_to_stackbase(task) + STACKSIZE();
|
|
}
|
|
|
|
#define STACK_END_MAGIC 0x57AC6E9D
|
|
|
|
static void
|
|
stack_overflow_check_init(void)
|
|
{
|
|
int pid;
|
|
struct task_context *tc;
|
|
ulong location, magic;
|
|
|
|
if (!(tt->flags & THREAD_INFO))
|
|
return;
|
|
|
|
for (pid = 1; pid < 10; pid++) {
|
|
if (!(tc = pid_to_context(pid)))
|
|
continue;
|
|
|
|
if (tt->flags & THREAD_INFO_IN_TASK)
|
|
location = task_to_stackbase(tc->task);
|
|
else
|
|
location = tc->thread_info + SIZE(thread_info);
|
|
|
|
if (!readmem(location, KVADDR, &magic, sizeof(long),
|
|
"stack magic", RETURN_ON_ERROR|QUIET))
|
|
continue;
|
|
|
|
if (magic == STACK_END_MAGIC) {
|
|
tt->stack_end_magic = STACK_END_MAGIC;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Check thread_info.task and thread_info.cpu members,
|
|
* and the STACK_END_MAGIC location.
|
|
*/
|
|
void
|
|
check_stack_overflow(void)
|
|
{
|
|
int i, overflow, cpu_size, cpu, total;
|
|
char buf[BUFSIZE];
|
|
ulong magic, task, stackbase, location;
|
|
struct task_context *tc;
|
|
|
|
if (!tt->stack_end_magic &&
|
|
INVALID_MEMBER(thread_info_task) &&
|
|
INVALID_MEMBER(thread_info_cpu))
|
|
option_not_supported('v');
|
|
|
|
cpu_size = VALID_MEMBER(thread_info_cpu) ?
|
|
MEMBER_SIZE("thread_info", "cpu") : 0;
|
|
|
|
tc = FIRST_CONTEXT();
|
|
for (i = total = 0; i < RUNNING_TASKS(); i++, tc++) {
|
|
overflow = 0;
|
|
|
|
if (tt->flags & THREAD_INFO_IN_TASK) {
|
|
if (!readmem(task_to_stackbase(tc->task), KVADDR, &stackbase,
|
|
sizeof(ulong), "stack overflow check", RETURN_ON_ERROR))
|
|
continue;
|
|
goto check_stack_end_magic;
|
|
} else {
|
|
if (!readmem(tc->thread_info, KVADDR, buf,
|
|
SIZE(thread_info) + sizeof(ulong),
|
|
"stack overflow check", RETURN_ON_ERROR))
|
|
continue;
|
|
}
|
|
|
|
if (VALID_MEMBER(thread_info_task)) {
|
|
task = ULONG(buf + OFFSET(thread_info_task));
|
|
if (task != tc->task) {
|
|
print_task_header(fp, tc, 0);
|
|
fprintf(fp,
|
|
" possible stack overflow: thread_info.task: %lx != %lx\n",
|
|
task, tc->task);
|
|
overflow++; total++;
|
|
}
|
|
}
|
|
|
|
if (VALID_MEMBER(thread_info_cpu)) {
|
|
switch (cpu_size)
|
|
{
|
|
case 1:
|
|
cpu = UCHAR(buf + OFFSET(thread_info_cpu));
|
|
break;
|
|
case 2:
|
|
cpu = USHORT(buf + OFFSET(thread_info_cpu));
|
|
break;
|
|
case 4:
|
|
cpu = UINT(buf + OFFSET(thread_info_cpu));
|
|
break;
|
|
default:
|
|
cpu = 0;
|
|
break;
|
|
}
|
|
if (cpu >= kt->cpus) {
|
|
if (!overflow)
|
|
print_task_header(fp, tc, 0);
|
|
fprintf(fp,
|
|
" possible stack overflow: thread_info.cpu: %d >= %d\n",
|
|
cpu, kt->cpus);
|
|
overflow++; total++;
|
|
}
|
|
}
|
|
|
|
check_stack_end_magic:
|
|
if (!tt->stack_end_magic)
|
|
continue;
|
|
|
|
if (tt->flags & THREAD_INFO_IN_TASK)
|
|
magic = stackbase;
|
|
else
|
|
magic = ULONG(buf + SIZE(thread_info));
|
|
|
|
if (tc->pid == 0) {
|
|
if (kernel_symbol_exists("init_task")) {
|
|
if (tc->task == symbol_value("init_task"))
|
|
continue;
|
|
} else
|
|
continue;
|
|
}
|
|
|
|
if (magic != STACK_END_MAGIC) {
|
|
if (!overflow)
|
|
print_task_header(fp, tc, 0);
|
|
|
|
if (tt->flags & THREAD_INFO_IN_TASK)
|
|
location = task_to_stackbase(tc->task);
|
|
else
|
|
location = tc->thread_info + SIZE(thread_info);
|
|
|
|
fprintf(fp,
|
|
" possible stack overflow: %lx: %lx != STACK_END_MAGIC\n",
|
|
location, magic);
|
|
overflow++, total++;
|
|
}
|
|
|
|
if (overflow)
|
|
fprintf(fp, "\n");
|
|
}
|
|
|
|
if (!total)
|
|
fprintf(fp, "No stack overflows detected\n");
|
|
}
|