diff options
Diffstat (limited to 'roms/skiboot/core/stack.c')
-rw-r--r-- | roms/skiboot/core/stack.c | 266 |
1 files changed, 266 insertions, 0 deletions
diff --git a/roms/skiboot/core/stack.c b/roms/skiboot/core/stack.c new file mode 100644 index 000000000..3edf98411 --- /dev/null +++ b/roms/skiboot/core/stack.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Create/Print backtraces, check stack usage etc. + * + * Copyright 2013-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <processor.h> +#include <cpu.h> +#include <stack.h> +#include <mem_region.h> +#include <unistd.h> +#include <lock.h> + +#define STACK_BUF_ENTRIES 60 +static struct bt_entry bt_buf[STACK_BUF_ENTRIES]; + +/* Dumps backtrace to buffer */ +static void __nomcount __backtrace_create(struct bt_entry *entries, + unsigned int max_ents, + struct bt_metadata *metadata, + struct stack_frame *eframe) +{ + unsigned long *fp = (unsigned long *)eframe; + unsigned long top_adj = top_of_ram; + + /* Assume one stack for early backtraces */ + if (top_of_ram == SKIBOOT_BASE + SKIBOOT_SIZE) + top_adj = top_of_ram + STACK_SIZE; + + metadata->ents = 0; + while (max_ents) { + fp = (unsigned long *)fp[0]; + if (!fp || (unsigned long)fp > top_adj) + break; + eframe = (struct stack_frame *)fp; + if (eframe->magic == STACK_INT_MAGIC) { + entries->exception_type = eframe->type; + entries->exception_pc = eframe->pc; + } else { + entries->exception_type = 0; + } + entries->sp = (unsigned long)fp; + entries->pc = fp[2]; + entries++; + metadata->ents++; + max_ents--; + } + + metadata->r1_caller = eframe->gpr[1]; + + if (fp) + metadata->token = eframe->gpr[0]; + else + metadata->token = -1UL; + + metadata->pir = mfspr(SPR_PIR); +} + +void __nomcount backtrace_create(struct bt_entry *entries, + unsigned int max_ents, + struct bt_metadata *metadata) +{ + unsigned long *fp = __builtin_frame_address(0); + struct stack_frame *eframe = (struct stack_frame *)fp; + + __backtrace_create(entries, max_ents, metadata, eframe); +} + +void backtrace_print(struct bt_entry *entries, struct bt_metadata *metadata, + char *out_buf, unsigned int *len, bool symbols) +{ + static char bt_text_buf[4096]; + int i, l = 0, max; + char *buf = out_buf; + unsigned long bottom, top, normal_top, tbot, ttop; + char mark; + + if (!out_buf) { + buf = bt_text_buf; + max = sizeof(bt_text_buf) - 16; + } else + max = *len - 1; + + bottom = cpu_stack_bottom(metadata->pir); + normal_top = cpu_stack_top(metadata->pir); + top = cpu_emergency_stack_top(metadata->pir); + tbot = SKIBOOT_BASE; + ttop = (unsigned long)&_etext; + + l += snprintf(buf, max, "CPU %04lx Backtrace:\n", metadata->pir); + for (i = 0; i < metadata->ents && l < max; i++) { + if (entries->sp < bottom || entries->sp > top) + mark = '!'; + else if (entries->sp > normal_top) + mark = 'E'; + else if (entries->pc < tbot || entries->pc > ttop) + mark = '*'; + else + mark = ' '; + l += snprintf(buf + l, max - l, + " S: %016lx R: %016lx %c ", + entries->sp, entries->pc, mark); + if (symbols) + l += snprintf_symbol(buf + l, max - l, entries->pc); + l += snprintf(buf + l, max - l, "\n"); + if (entries->exception_type) { + l += snprintf(buf + l, max - l, + " --- Interrupt 0x%lx at %016lx ---\n", + entries->exception_type, entries->exception_pc); + } + entries++; + } + if (metadata->token <= OPAL_LAST) + l += snprintf(buf + l, max - l, + " --- OPAL call token: 0x%lx caller R1: 0x%016lx ---\n", + metadata->token, metadata->r1_caller); + else if (metadata->token == -1UL) + l += snprintf(buf + l, max - l, " --- OPAL boot ---\n"); + if (!out_buf) + write(stdout->fd, bt_text_buf, l); + buf[l++] = 0; + if (len) + *len = l; +} + +/* + * To ensure that we always get backtrace output we bypass the usual console + * locking paths. The downside is that when multiple threads need to print + * a backtrace they garble each other. To prevent this we use a seperate + * lock to serialise printing of the dumps. + */ +static struct lock bt_lock = LOCK_UNLOCKED; + +void backtrace(void) +{ + struct bt_metadata metadata; + + lock(&bt_lock); + + backtrace_create(bt_buf, STACK_BUF_ENTRIES, &metadata); + backtrace_print(bt_buf, &metadata, NULL, NULL, true); + + unlock(&bt_lock); +} + +void backtrace_r1(uint64_t r1) +{ + struct bt_metadata metadata; + + lock(&bt_lock); + + __backtrace_create(bt_buf, STACK_BUF_ENTRIES, &metadata, (struct stack_frame *)r1); + backtrace_print(bt_buf, &metadata, NULL, NULL, true); + + unlock(&bt_lock); +} + +void __nomcount __stack_chk_fail(void); +void __nomcount __stack_chk_fail(void) +{ + static bool failed_once; + + if (failed_once) + return; + failed_once = true; + prlog(PR_EMERG, "Stack corruption detected !\n"); + abort(); +} + +#ifdef STACK_CHECK_ENABLED + +static int64_t lowest_stack_mark = LONG_MAX; +static struct lock stack_check_lock = LOCK_UNLOCKED; + +void __nomcount __mcount_stack_check(uint64_t sp, uint64_t lr); +void __nomcount __mcount_stack_check(uint64_t sp, uint64_t lr) +{ + struct cpu_thread *c = this_cpu(); + uint64_t base = (uint64_t)c; + uint64_t bot = base + sizeof(struct cpu_thread); + int64_t mark = sp - bot; + uint64_t top = base + NORMAL_STACK_SIZE; + + /* + * Don't check the emergency stack just yet. + */ + if (c->in_opal_call > 1) + return; + + /* + * Don't re-enter on this CPU or don't enter at all if somebody + * has spotted an overflow + */ + if (c->in_mcount) + return; + c->in_mcount = true; + + /* Capture lowest stack for this thread */ + if (mark < c->stack_bot_mark) { + lock(&stack_check_lock); + c->stack_bot_mark = mark; + c->stack_bot_pc = lr; + c->stack_bot_tok = c->current_token; + backtrace_create(c->stack_bot_bt, CPU_BACKTRACE_SIZE, + &c->stack_bot_bt_metadata); + unlock(&stack_check_lock); + + if (mark < STACK_WARNING_GAP) { + prlog(PR_EMERG, "CPU %04x Stack usage danger !" + " pc=%08llx sp=%08llx (gap=%lld) token=%lld\n", + c->pir, lr, sp, mark, c->current_token); + } + } + + /* Stack is within bounds? */ + if (sp >= (bot + STACK_SAFETY_GAP) && sp < top) { + c->in_mcount = false; + return; + } + + prlog(PR_EMERG, "CPU %04x Stack overflow detected !" + " pc=%08llx sp=%08llx (gap=%lld) token=%lld\n", + c->pir, lr, sp, mark, c->current_token); + abort(); +} + +void check_stacks(void) +{ + struct cpu_thread *c, *lowest = NULL; + + /* We should never call that from mcount */ + assert(!this_cpu()->in_mcount); + + /* Mark ourselves "in_mcount" to avoid deadlock on stack + * check lock + */ + this_cpu()->in_mcount = true; + + for_each_cpu(c) { + if (!c->stack_bot_mark || + c->stack_bot_mark >= lowest_stack_mark) + continue; + lock(&stack_check_lock); + if (c->stack_bot_mark < lowest_stack_mark) { + lowest = c; + lowest_stack_mark = c->stack_bot_mark; + } + unlock(&stack_check_lock); + } + if (lowest) { + lock(&bt_lock); + prlog(PR_NOTICE, "CPU %04x lowest stack mark %lld bytes left" + " pc=%08llx token=%lld\n", + lowest->pir, lowest->stack_bot_mark, lowest->stack_bot_pc, + lowest->stack_bot_tok); + backtrace_print(lowest->stack_bot_bt, + &lowest->stack_bot_bt_metadata, + NULL, NULL, true); + unlock(&bt_lock); + } + + this_cpu()->in_mcount = false; +} +#endif /* STACK_CHECK_ENABLED */ |