Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/Core/MIPS/MIPSStackWalk.cpp
3186 views
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "Common/Log.h"
19
#include "Core/MemMap.h"
20
#include "Core/Debugger/SymbolMap.h"
21
#include "Core/MIPS/MIPSCodeUtils.h"
22
#include "Core/MIPS/MIPSStackWalk.h"
23
24
#define _RS ((op >> 21) & 0x1F)
25
#define _RT ((op >> 16) & 0x1F)
26
#define _RD ((op >> 11) & 0x1F)
27
#define _IMM16 ((signed short)(op & 0xFFFF))
28
29
#define MIPSTABLE_IMM_MASK 0xFC000000
30
#define MIPSTABLE_SPECIAL_MASK 0xFC00003F
31
32
namespace MIPSStackWalk {
33
using namespace MIPSCodeUtils;
34
35
// In the worst case, we scan this far above the pc for an entry.
36
const int MAX_FUNC_SIZE = 32768 * 4;
37
// After this we assume we're stuck.
38
const size_t MAX_DEPTH = 1024;
39
40
static u32 GuessEntry(u32 pc) {
41
SymbolInfo info;
42
if (g_symbolMap->GetSymbolInfo(&info, pc)) {
43
return info.address;
44
}
45
return INVALIDTARGET;
46
}
47
48
bool IsSWInstr(MIPSOpcode op) {
49
return (op & MIPSTABLE_IMM_MASK) == 0xAC000000;
50
}
51
52
bool IsAddImmInstr(MIPSOpcode op) {
53
return (op & MIPSTABLE_IMM_MASK) == 0x20000000 || (op & MIPSTABLE_IMM_MASK) == 0x24000000;
54
}
55
56
bool IsMovRegsInstr(MIPSOpcode op) {
57
// TODO: There are more options here. Let's assume addu for now.
58
if ((op & MIPSTABLE_SPECIAL_MASK) == 0x00000021) {
59
return _RS == 0 || _RT == 0;
60
}
61
return false;
62
}
63
64
bool ScanForAllocaSignature(u32 pc) {
65
// In God Eater Burst, for example, after 0880E750, there's what looks like an alloca().
66
// It's surrounded by "mov fp, sp" and "mov sp, fp", which is unlikely to be used for other reasons.
67
68
// It ought to be pretty close.
69
u32 stop = pc - 32 * 4;
70
for (; Memory::IsValidAddress(pc) && pc >= stop; pc -= 4) {
71
MIPSOpcode op = Memory::Read_Instruction(pc, true);
72
73
// We're looking for a "mov fp, sp" close by a "addiu sp, sp, -N".
74
if (IsMovRegsInstr(op) && _RD == MIPS_REG_FP && (_RS == MIPS_REG_SP || _RT == MIPS_REG_SP)) {
75
return true;
76
}
77
}
78
return false;
79
}
80
81
bool ScanForEntry(StackFrame &frame, u32 entry, u32 &ra) {
82
// Let's hope there are no > 0.5MB functions on the PSP, for the sake of humanity...
83
const u32 LONGEST_FUNCTION = 1024 * 512;
84
// TODO: Check if found entry is in the same symbol? Might be wrong sometimes...
85
86
if (entry != INVALIDTARGET && frame.pc == entry) {
87
// This happens when we're at the start of a function. Our ra is already correct.
88
frame.entry = entry;
89
// This function may consume stack, but the frame hasn't used it yet.
90
frame.stackSize = 0;
91
return true;
92
}
93
94
int ra_offset = -1;
95
// Start one instruction before the current frame pc, as that hasn't run yet.
96
const u32 start = frame.pc - 4;
97
u32 stop = entry;
98
if (entry == INVALIDTARGET) {
99
if (start >= PSP_GetUserMemoryBase()) {
100
stop = PSP_GetUserMemoryBase();
101
} else if (start >= PSP_GetKernelMemoryBase()) {
102
stop = PSP_GetKernelMemoryBase();
103
} else if (start >= PSP_GetScratchpadMemoryBase()) {
104
stop = PSP_GetScratchpadMemoryBase();
105
}
106
}
107
108
if (!Memory::IsValidAddress(start)) {
109
return false;
110
}
111
112
if (stop < start - LONGEST_FUNCTION) {
113
stop = start - LONGEST_FUNCTION;
114
}
115
for (u32 pc = start; Memory::IsValidAddress(pc) && pc >= stop; pc -= 4) {
116
_dbg_assert_(Memory::IsValidAddress(pc));
117
MIPSOpcode op = Memory::Read_Instruction(pc, true);
118
119
// Here's where they store the ra address.
120
if (IsSWInstr(op) && _RT == MIPS_REG_RA && _RS == MIPS_REG_SP) {
121
ra_offset = _IMM16;
122
}
123
124
if (IsAddImmInstr(op) && _RT == MIPS_REG_SP && _RS == MIPS_REG_SP) {
125
// A positive imm either means alloca() or we went too far.
126
if (_IMM16 > 0) {
127
// TODO: Maybe check for any alloca() signature and bail?
128
continue;
129
}
130
if (ScanForAllocaSignature(pc)) {
131
continue;
132
}
133
134
frame.entry = pc;
135
frame.stackSize = -_IMM16;
136
if (ra_offset != -1 && Memory::IsValidAddress(frame.sp + ra_offset)) {
137
ra = Memory::Read_U32(frame.sp + ra_offset);
138
}
139
return true;
140
}
141
}
142
return false;
143
}
144
145
bool DetermineFrameInfo(StackFrame &frame, u32 possibleEntry, u32 threadEntry, u32 &ra) {
146
if (ScanForEntry(frame, possibleEntry, ra)) {
147
// Awesome, found one that looks right.
148
return true;
149
} else if (ra != INVALIDTARGET && possibleEntry != INVALIDTARGET) {
150
// Let's just assume it's a leaf.
151
frame.entry = possibleEntry;
152
frame.stackSize = 0;
153
return true;
154
}
155
156
// Okay, we failed to get one. Our possibleEntry could be wrong, it often is.
157
// Let's just scan upward.
158
u32 newPossibleEntry = frame.pc > threadEntry ? threadEntry : frame.pc - MAX_FUNC_SIZE;
159
if (ScanForEntry(frame, newPossibleEntry, ra)) {
160
return true;
161
} else {
162
return false;
163
}
164
}
165
166
std::vector<StackFrame> Walk(const u32 pc, u32 ra, u32 sp, const u32 threadEntry, u32 threadStackTop) {
167
std::vector<StackFrame> frames;
168
169
if (!Memory::IsValidAddress(pc) || !Memory::IsValidAddress(sp) || !Memory::IsValidAddress(ra)) {
170
return frames;
171
}
172
173
StackFrame current;
174
current.pc = pc;
175
current.sp = sp;
176
current.entry = INVALIDTARGET;
177
current.stackSize = -1;
178
179
u32 prevEntry = INVALIDTARGET;
180
while (current.pc != threadEntry) {
181
if (!Memory::IsValidAddress(current.pc)) {
182
break;
183
}
184
185
u32 possibleEntry = GuessEntry(current.pc);
186
if (DetermineFrameInfo(current, possibleEntry, threadEntry, ra)) {
187
frames.push_back(current);
188
if (current.entry == threadEntry || GuessEntry(current.entry) == threadEntry) {
189
break;
190
}
191
if (current.entry == prevEntry || frames.size() >= MAX_DEPTH) {
192
// Recursion, means we're screwed. Let's just give up.
193
break;
194
}
195
prevEntry = current.entry;
196
197
current.pc = ra;
198
current.sp += current.stackSize;
199
ra = INVALIDTARGET;
200
current.entry = INVALIDTARGET;
201
current.stackSize = -1;
202
} else {
203
// Well, we got as far as we could.
204
current.entry = possibleEntry;
205
current.stackSize = 0;
206
frames.push_back(current);
207
break;
208
}
209
}
210
211
return frames;
212
}
213
};
214
215