summaryrefslogtreecommitdiff
path: root/lib/mipsunwinder/backtrace-mips32.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/mipsunwinder/backtrace-mips32.c')
-rw-r--r--lib/mipsunwinder/backtrace-mips32.c236
1 files changed, 236 insertions, 0 deletions
diff --git a/lib/mipsunwinder/backtrace-mips32.c b/lib/mipsunwinder/backtrace-mips32.c
new file mode 100644
index 0000000000..c5ab41e628
--- /dev/null
+++ b/lib/mipsunwinder/backtrace-mips32.c
@@ -0,0 +1,236 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * MIPS32 backtrace implementation
11 * Copyright (C) 2022 Aidan MacDonald
12 *
13 * References:
14 * https://yosefk.com/blog/getting-the-call-stack-without-a-frame-pointer.html
15 * https://elinux.org/images/6/68/ELC2008_-_Back-tracing_in_MIPS-based_Linux_Systems.pdf
16 * System V ABI MIPS Supplement, 3rd edition
17 *
18 * This program is free software; you can redistribute it and/or
19 * modify it under the terms of the GNU General Public License
20 * as published by the Free Software Foundation; either version 2
21 * of the License, or (at your option) any later version.
22 *
23 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
24 * KIND, either express or implied.
25 *
26 ****************************************************************************/
27
28#include "backtrace.h"
29#include "lcd.h"
30#include <stdint.h>
31#include <stdbool.h>
32#include <stdalign.h>
33#include <stdarg.h>
34#include <stdio.h>
35
36#define MIN_ADDR 0x80000000ul
37#define MAX_ADDR (MIN_ADDR + (MEMORYSIZE * 1024 * 1024) - 1)
38
39static bool read_check(const void* addr, size_t alignment)
40{
41 if(addr < (const void*)MIN_ADDR ||
42 addr > (const void*)MAX_ADDR)
43 return false;
44
45 if((uintptr_t)addr & (alignment - 1))
46 return false;
47
48 return true;
49}
50
51static bool read32(const void* addr, uint32_t* val)
52{
53 if(!read_check(addr, alignof(uint32_t)))
54 return false;
55
56 *val = *(const uint32_t*)addr;
57 return true;
58}
59
60#if 0
61static bool read16(const void* addr, uint16_t* val)
62{
63 if(!read_check(addr, alignof(uint16_t)))
64 return false;
65
66 *val = *(const uint16_t*)addr;
67 return true;
68}
69
70static bool read8(const void* addr, uint8_t* val)
71{
72 if(!read_check(addr, alignof(uint8_t)))
73 return false;
74
75 *val = *(const uint8_t*)addr;
76 return true;
77}
78#endif
79
80static long extract_s16(uint32_t val)
81{
82#if 1
83 /* not ISO C, but gets GCC to emit 'seh' which is more compact */
84 return (int32_t)(val << 16) >> 16;
85#else
86 val &= 0xffff;
87
88 if(val > 0x7fff)
89 return (long)val - 0x10000l;
90 else
91 return val;
92#endif
93}
94
95/* TODO - cases not handled by the backtrace algorithm
96 *
97 * 1. functions that save the frame pointer will not be handled correctly
98 * (need to implement the algorithm specified by the System V ABI).
99 *
100 * 2. GCC can generate functions with a "false" stack pointer decrement,
101 * for some examples see read_bmp_fd and walk_path. those functions
102 * seem to be more difficult to deal with and the SysV algorithm will
103 * also get confused by them, but they are not common.
104 */
105int mips_bt_step(struct mips_bt_context* ctx)
106{
107 /* go backward and look for the stack pointer decrement */
108 uint32_t* pc = ctx->pc;
109 uint32_t insn;
110 long sp_off;
111
112 while(true) {
113 if(!read32(pc, &insn))
114 return 0;
115
116 /* addiu sp, sp, sp_off */
117 if((insn >> 16) == 0x27bd) {
118 sp_off = extract_s16(insn);
119 if(sp_off < 0)
120 break;
121 }
122
123 /* jr ra */
124 if(insn == 0x03e00008) {
125 /* end if this is not a leaf or we lack register info */
126 if(ctx->depth > 0 || !(ctx->valid & (1 << MIPSBT_RA))) {
127 mips_bt_debug(ctx, "unexpected leaf function");
128 return 0;
129 }
130
131 /* this is a leaf function - ra contains the return address
132 * and sp is unchanged */
133 ctx->pc = (void*)ctx->reg[MIPSBT_RA] - 8;
134 ctx->depth++;
135 return 1;
136 }
137
138 --pc;
139 }
140
141 mips_bt_debug(ctx, "found sp_off=%ld at %p", sp_off, pc);
142
143 /* now go forward and find the saved return address */
144 while((void*)pc < ctx->pc) {
145 if(!read32(pc, &insn))
146 return 0;
147
148 /* sw ra, ra_off(sp) */
149 if((insn >> 16) == 0xafbf) {
150 long ra_off = extract_s16(insn);
151 uint32_t save_ra;
152
153 /* load the saved return address */
154 mips_bt_debug(ctx, "load ra from %p+%ld", ctx->sp, ra_off);
155 if(!read32(ctx->sp + ra_off, &save_ra))
156 return 0;
157
158 if(save_ra == 0) {
159 mips_bt_debug("hit root");
160 return 0;
161 }
162
163 /* update bt context */
164 ctx->pc = (void*)save_ra - 8;
165 ctx->sp -= sp_off;
166
167 /* update saved register info */
168 ctx->reg[MIPSBT_RA] = save_ra;
169 ctx->valid |= (1 << MIPSBT_RA);
170
171 ctx->depth++;
172 return 1;
173 }
174
175 ++pc;
176 }
177
178 /* sometimes an exception occurs before ra is saved - in this case
179 * the ra register should contain the caller PC, but we still need
180 * to adjust the stack frame. */
181 if(ctx->depth == 0 && (ctx->valid & (1 << MIPSBT_RA))) {
182 ctx->pc = (void*)ctx->reg[MIPSBT_RA] - 8;
183 ctx->sp -= sp_off;
184
185 ctx->depth++;
186 return 1;
187 }
188
189 mips_bt_debug(ctx, "ra not found");
190 return 0;
191}
192
193#ifdef MIPSUNWINDER_DEBUG
194static void rb_backtrace_debugf(void* arg, const char* fmt, ...)
195{
196 static char buf[64];
197 va_list ap;
198 va_start(ap, fmt);
199 vsnprintf(buf, sizeof(buf), fmt, ap);
200 va_end(ap);
201
202 unsigned int* line = arg;
203 lcd_putsf(4, (*line)++, "%s", buf);
204 lcd_update();
205}
206#endif
207
208void rb_backtrace_ctx(void* arg, unsigned* line)
209{
210 struct mips_bt_context* ctx = arg;
211#ifdef MIPSUNWINDER_DEBUG
212 ctx->debugf = rb_backtrace_debugf;
213 ctx->debug_arg = line;
214#endif
215
216 do {
217 lcd_putsf(0, (*line)++, "%02d pc:%08lx sp:%08lx",
218 ctx->depth, (unsigned long)ctx->pc, (unsigned long)ctx->sp);
219 lcd_update();
220 } while(mips_bt_step(ctx));
221
222 lcd_puts(0, (*line)++, "bt end");
223 lcd_update();
224}
225
226void rb_backtrace(int pcAddr, int spAddr, unsigned* line)
227{
228 (void)pcAddr;
229 (void)spAddr;
230
231 struct mips_bt_context ctx;
232 mips_bt_start(&ctx);
233 mips_bt_step(&ctx); /* step over this function */
234
235 rb_backtrace_ctx(&ctx, line);
236}