aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/core/stack.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/core/stack.c')
-rw-r--r--roms/skiboot/core/stack.c266
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 */