/** * * Copyright (C) 2018 AVM GmbH * * 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. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "avm_sammel.h" #if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0) static inline void *try_get_task_stack(struct task_struct *tsk) { return task_stack_page(tsk); } static inline void put_task_stack(struct task_struct *tsk) {} #endif #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0) #define task_stack_end_corrupted(task) \ (*(end_of_stack(task)) != STACK_END_MAGIC) #endif /** * @brief get the task where consumed most of stack * stackusage only valid if CONFIG_DEBUG_STACK_USAGE set * stack-corruption independend * @param comm task-name (reserve TASK_COMM_LEN) * @return > 0 minimal stack is free * < 0 -EBUSY can't get lock * -EINVAL stack-corruption */ static int get_taskstack_minfree(char *comm, int *pid, struct seq_file *seq __maybe_unused) { unsigned long flags = 0; int stack_free __maybe_unused, min_stack_free = THREAD_SIZE, min_pid = 0; struct task_struct *g, *task, *min_task = NULL; if (comm) { comm[0] = 0; } __BUILD_AVM_CONTEXT_FUNC(local_irq_save) (flags); if (!read_trylock(&tasklist_lock)) { __BUILD_AVM_CONTEXT_FUNC(local_irq_restore) (flags); return -EBUSY; } do_each_thread(g, task) { if (!try_get_task_stack(task)) continue; if (task != &init_task && task_stack_end_corrupted(task)) { /* for overflow detection */ min_task = task; min_stack_free = -EBUSY; min_pid = task_pid_nr(task); put_task_stack(task); goto stackfree_return; } #if defined(CONFIG_DEBUG_STACK_USAGE) stack_free = stack_not_used(task); if (seq) { seq_printf(seq, "%-16s (%5d) stack depth: %5lu (%5u bytes left)\n", task->comm, task_pid_nr(task), THREAD_SIZE - stack_free, stack_free); } if (stack_free < min_stack_free) { min_task = task; min_stack_free = stack_free; min_pid = task_pid_nr(task); } put_task_stack(task); #endif /*--- #if defined(CONFIG_DEBUG_STACK_USAGE) ---*/ } while_each_thread(g, task); stackfree_return: if (min_task && comm) { strlcpy(comm, min_task->comm, TASK_COMM_LEN); } read_unlock(&tasklist_lock); __BUILD_AVM_CONTEXT_FUNC(local_irq_restore(flags)); if (pid) *pid = min_pid; return min_stack_free; } /** * @brief stack checking * @param task == NULL over all threads */ void avm_stack_check(struct task_struct *task) { char comm[TASK_COMM_LEN]; int min_stack_free, pid; if (virt_addr_valid(task)) { if (task != &init_task && try_get_task_stack(task)) { if (task_stack_end_corrupted(task)) pr_emerg("ERROR: corrupt stack on %s (%d)\n", task->comm, task_pid_nr(task)); put_task_stack(task); } return; } min_stack_free = get_taskstack_minfree(comm, &pid, NULL); switch (min_stack_free) { case -EBUSY: break; case -EINVAL: pr_emerg("ERROR: corrupt stack on %s (%d)\n", comm, pid); break; default: if (comm[0]) { pr_emerg("%s (%d) used greatest stack depth: %6u bytes left\n", comm, pid, min_stack_free); } break; } } /** */ static unsigned long check_task_area(struct task_struct *task, unsigned long addr, char *comm, unsigned int commlen) { if ((addr >= (unsigned long)task) && (addr < (unsigned long)(task + 1))) { if (comm) { snprintf(comm, commlen, "task_struct(%s)", task->comm); } return (unsigned long)task; } return 0; } /** */ static unsigned long check_stack_area(struct task_struct *task, unsigned long addr, char *comm, unsigned int commlen) { unsigned long stack = (unsigned long)task_stack_page(task); /*--- pr_err("%s: %u: %s: %lx %lx\n", __func__, __LINE__, task->comm, stack, addr); ---*/ if ((addr >= stack) && (addr < (stack + THREAD_SIZE))) { if (comm) { snprintf(comm, commlen, "%s(%s)", addr < (unsigned long)(task_thread_info(task) + 1) ? "threadinfo" : "stack", task->comm); } return stack; } return 0; } /** * @brief check if pointer inside a task_page * @param comm task-name * commlen len of comm (please reserve 10 bytes more than TASK_COMM_LEN) * @return = 0 outside */ unsigned long get_taskstack_area(unsigned long addr, char *comm, unsigned int commlen, int only_current) { unsigned long flags = 0; unsigned long start = 0; struct task_struct *g, *task; if (only_current) { start = check_task_area(current, addr, comm, commlen); if (start) { return start; } return check_stack_area(current, addr, comm, commlen); } __BUILD_AVM_CONTEXT_FUNC(local_irq_save) (flags); if (!read_trylock(&tasklist_lock)) { __BUILD_AVM_CONTEXT_FUNC(local_irq_restore) (flags); return 0; } do_each_thread(g, task) { if (virt_addr_valid(task)) { start = check_task_area(task, addr, comm, commlen); if (start) goto end_task; start = check_stack_area(task, addr, comm, commlen); if (start) goto end_task; } } while_each_thread(g, task); end_task: read_unlock(&tasklist_lock); __BUILD_AVM_CONTEXT_FUNC(local_irq_restore(flags)); return start; } #if defined(CONFIG_DEBUG_STACK_USAGE) && defined(CONFIG_PROC_FS) /** * @brief show tack-usage over all tasks * @param seq for proc-device */ static void avm_stack_all_tasks(struct seq_file *seq, void *priv __maybe_unused) { char comm[TASK_COMM_LEN]; int min_stack_free, pid; if (!seq) { return; } min_stack_free = get_taskstack_minfree(comm, &pid, seq); switch (min_stack_free) { case -EBUSY: break; case -EINVAL: seq_printf(seq, "ERROR: corrupt stack on %s\n", comm); break; default: if (comm[0]) { seq_printf(seq, "%-16s (%5d) used greatest stack depth: %5u bytes left\n", comm, pid, min_stack_free); } break; } } /** */ static int __init avm_stack_usage_init(void) { add_simple_proc_file("avm/stack_usage", NULL, avm_stack_all_tasks, NULL); return 0; } late_initcall(avm_stack_usage_init); #endif/*--- #if defined(CONFIG_DEBUG_STACK_USAGE) && defined(CONFIG_PROC_FS) ---*/