Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
stenzek
GitHub Repository: stenzek/duckstation
Path: blob/master/dep/vixl/src/aarch64/debugger-aarch64.cc
4261 views
1
// Copyright 2023, VIXL authors
2
// All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or without
5
// modification, are permitted provided that the following conditions are met:
6
//
7
// * Redistributions of source code must retain the above copyright notice,
8
// this list of conditions and the following disclaimer.
9
// * Redistributions in binary form must reproduce the above copyright notice,
10
// this list of conditions and the following disclaimer in the documentation
11
// and/or other materials provided with the distribution.
12
// * Neither the name of ARM Limited nor the names of its contributors may be
13
// used to endorse or promote products derived from this software without
14
// specific prior written permission.
15
//
16
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
17
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27
#ifdef VIXL_INCLUDE_SIMULATOR_AARCH64
28
29
#include "debugger-aarch64.h"
30
31
#include <cerrno>
32
#include <cmath>
33
#include <cstring>
34
#include <errno.h>
35
#include <limits>
36
#include <unistd.h>
37
38
namespace vixl {
39
namespace aarch64 {
40
41
42
Debugger::Debugger(Simulator* sim)
43
: sim_(sim), input_stream_(&std::cin), ostream_(sim->GetOutputStream()) {
44
// Register all basic debugger commands.
45
RegisterCmd<HelpCmd>();
46
RegisterCmd<BreakCmd>();
47
RegisterCmd<StepCmd>();
48
RegisterCmd<ContinueCmd>();
49
RegisterCmd<PrintCmd>();
50
RegisterCmd<TraceCmd>();
51
RegisterCmd<GdbCmd>();
52
}
53
54
55
template <class T>
56
void Debugger::RegisterCmd() {
57
auto new_command = std::make_unique<T>(sim_);
58
59
// Check that the new command word and alias, don't already exist.
60
std::string_view new_cmd_word = new_command->GetCommandWord();
61
std::string_view new_cmd_alias = new_command->GetCommandAlias();
62
for (const auto& cmd : debugger_cmds_) {
63
std::string_view cmd_word = cmd->GetCommandWord();
64
std::string_view cmd_alias = cmd->GetCommandAlias();
65
66
if (new_cmd_word == cmd_word) {
67
VIXL_ABORT_WITH_MSG("Command word matches an existing command word.");
68
} else if (new_cmd_word == cmd_alias) {
69
VIXL_ABORT_WITH_MSG("Command word matches an existing command alias.");
70
}
71
72
if (new_cmd_alias != "") {
73
if (new_cmd_alias == cmd_word) {
74
VIXL_ABORT_WITH_MSG("Command alias matches an existing command word.");
75
} else if (new_cmd_alias == cmd_alias) {
76
VIXL_ABORT_WITH_MSG("Command alias matches an existing command alias.");
77
}
78
}
79
}
80
81
debugger_cmds_.push_back(std::move(new_command));
82
}
83
84
85
bool Debugger::IsAtBreakpoint() const {
86
return IsBreakpoint(reinterpret_cast<uint64_t>(sim_->ReadPc()));
87
}
88
89
90
void Debugger::Debug() {
91
DebugReturn done = DebugContinue;
92
while (done == DebugContinue) {
93
// Disassemble the next instruction to execute.
94
PrintDisassembler print_disasm = PrintDisassembler(ostream_);
95
print_disasm.Disassemble(sim_->ReadPc());
96
97
// Read the command line.
98
fprintf(ostream_, "sim> ");
99
std::string line;
100
std::getline(*input_stream_, line);
101
102
// Remove all control characters from the command string.
103
line.erase(std::remove_if(line.begin(),
104
line.end(),
105
[](char c) { return std::iscntrl(c); }),
106
line.end());
107
108
// Assume input from std::cin has already been output (e.g: by a terminal)
109
// but input from elsewhere (e.g: from a testing input stream) has not.
110
if (input_stream_ != &std::cin) {
111
fprintf(ostream_, "%s\n", line.c_str());
112
}
113
114
// Parse the command into tokens.
115
std::vector<std::string> tokenized_cmd = Tokenize(line);
116
if (!tokenized_cmd.empty()) {
117
done = ExecDebugCommand(tokenized_cmd);
118
}
119
}
120
}
121
122
123
std::optional<uint64_t> Debugger::ParseUint64String(std::string_view uint64_str,
124
int base) {
125
// Clear any previous errors.
126
errno = 0;
127
128
// strtoull uses 0 to indicate that no conversion was possible so first
129
// check that the string isn't zero.
130
if (IsZeroUint64String(uint64_str, base)) {
131
return 0;
132
}
133
134
// Cannot use stoi as it might not be possible to use exceptions.
135
char* end;
136
uint64_t value = std::strtoull(uint64_str.data(), &end, base);
137
if (value == 0 || *end != '\0' || errno == ERANGE) {
138
return std::nullopt;
139
}
140
141
return value;
142
}
143
144
145
std::optional<Debugger::RegisterParsedFormat> Debugger::ParseRegString(
146
std::string_view reg_str) {
147
// A register should only have 2 (e.g: X0) or 3 (e.g: X31) characters.
148
if (reg_str.size() < 2 || reg_str.size() > 3) {
149
return std::nullopt;
150
}
151
152
// Check for aliases of registers.
153
if (reg_str == "lr") {
154
return {{'X', kLinkRegCode}};
155
} else if (reg_str == "sp") {
156
return {{'X', kSpRegCode}};
157
}
158
159
unsigned max_reg_num;
160
char reg_prefix = std::toupper(reg_str.front());
161
switch (reg_prefix) {
162
case 'W':
163
VIXL_FALLTHROUGH();
164
case 'X':
165
max_reg_num = kNumberOfRegisters - 1;
166
break;
167
case 'V':
168
max_reg_num = kNumberOfVRegisters - 1;
169
break;
170
case 'Z':
171
max_reg_num = kNumberOfZRegisters - 1;
172
break;
173
case 'P':
174
max_reg_num = kNumberOfPRegisters - 1;
175
break;
176
default:
177
return std::nullopt;
178
}
179
180
std::string_view str_code = reg_str.substr(1, reg_str.size());
181
auto reg_code = ParseUint64String(str_code, 10);
182
if (!reg_code) {
183
return std::nullopt;
184
}
185
186
if (*reg_code > max_reg_num) {
187
return std::nullopt;
188
}
189
190
return {{reg_prefix, *reg_code}};
191
}
192
193
194
void Debugger::PrintUsage() {
195
for (const auto& cmd : debugger_cmds_) {
196
// Print commands in the following format:
197
// foo / f
198
// foo <arg>
199
// A description of the foo command.
200
//
201
202
std::string_view cmd_word = cmd->GetCommandWord();
203
std::string_view cmd_alias = cmd->GetCommandAlias();
204
if (cmd_alias != "") {
205
fprintf(ostream_, "%s / %s\n", cmd_word.data(), cmd_alias.data());
206
} else {
207
fprintf(ostream_, "%s\n", cmd_word.data());
208
}
209
210
std::string_view args_str = cmd->GetArgsString();
211
if (args_str != "") {
212
fprintf(ostream_, "\t%s %s\n", cmd_word.data(), args_str.data());
213
}
214
215
std::string_view description = cmd->GetDescription();
216
if (description != "") {
217
fprintf(ostream_, "\t%s\n", description.data());
218
}
219
}
220
}
221
222
223
std::vector<std::string> Debugger::Tokenize(std::string_view input_line,
224
char separator) {
225
std::vector<std::string> words;
226
227
if (input_line.empty()) {
228
return words;
229
}
230
231
for (auto separator_pos = input_line.find(separator);
232
separator_pos != input_line.npos;
233
separator_pos = input_line.find(separator)) {
234
// Skip consecutive, repeated separators.
235
if (separator_pos != 0) {
236
words.push_back(std::string{input_line.substr(0, separator_pos)});
237
}
238
239
// Remove characters up to and including the separator.
240
input_line.remove_prefix(separator_pos + 1);
241
}
242
243
// Add the rest of the string to the vector.
244
words.push_back(std::string{input_line});
245
246
return words;
247
}
248
249
250
DebugReturn Debugger::ExecDebugCommand(
251
const std::vector<std::string>& tokenized_cmd) {
252
std::string cmd_word = tokenized_cmd.front();
253
for (const auto& cmd : debugger_cmds_) {
254
if (cmd_word == cmd->GetCommandWord() ||
255
cmd_word == cmd->GetCommandAlias()) {
256
const std::vector<std::string> args(tokenized_cmd.begin() + 1,
257
tokenized_cmd.end());
258
259
// Call the handler for the command and pass the arguments.
260
return cmd->Action(args);
261
}
262
}
263
264
fprintf(ostream_, "Error: command '%s' not found\n", cmd_word.c_str());
265
return DebugContinue;
266
}
267
268
269
bool Debugger::IsZeroUint64String(std::string_view uint64_str, int base) {
270
// Remove any hex prefixes.
271
if (base == 0 || base == 16) {
272
std::string_view prefix = uint64_str.substr(0, 2);
273
if (prefix == "0x" || prefix == "0X") {
274
uint64_str.remove_prefix(2);
275
}
276
}
277
278
if (uint64_str.empty()) {
279
return false;
280
}
281
282
// Check all remaining digits in the string for anything other than zero.
283
for (char c : uint64_str) {
284
if (c != '0') {
285
return false;
286
}
287
}
288
289
return true;
290
}
291
292
293
DebuggerCmd::DebuggerCmd(Simulator* sim,
294
std::string cmd_word,
295
std::string cmd_alias,
296
std::string args_str,
297
std::string description)
298
: sim_(sim),
299
ostream_(sim->GetOutputStream()),
300
command_word_(cmd_word),
301
command_alias_(cmd_alias),
302
args_str_(args_str),
303
description_(description) {}
304
305
306
DebugReturn HelpCmd::Action(const std::vector<std::string>& args) {
307
USE(args);
308
sim_->GetDebugger()->PrintUsage();
309
return DebugContinue;
310
}
311
312
313
DebugReturn BreakCmd::Action(const std::vector<std::string>& args) {
314
if (args.size() != 1) {
315
fprintf(ostream_, "Error: Use `break <address>` to set a breakpoint\n");
316
return DebugContinue;
317
}
318
319
std::string arg = args.front();
320
auto break_addr = Debugger::ParseUint64String(arg);
321
if (!break_addr) {
322
fprintf(ostream_, "Error: Use `break <address>` to set a breakpoint\n");
323
return DebugContinue;
324
}
325
326
if (sim_->GetDebugger()->IsBreakpoint(*break_addr)) {
327
sim_->GetDebugger()->RemoveBreakpoint(*break_addr);
328
fprintf(ostream_,
329
"Breakpoint successfully removed at: 0x%" PRIx64 "\n",
330
*break_addr);
331
} else {
332
sim_->GetDebugger()->RegisterBreakpoint(*break_addr);
333
fprintf(ostream_,
334
"Breakpoint successfully added at: 0x%" PRIx64 "\n",
335
*break_addr);
336
}
337
338
return DebugContinue;
339
}
340
341
342
DebugReturn StepCmd::Action(const std::vector<std::string>& args) {
343
if (args.size() > 1) {
344
fprintf(ostream_,
345
"Error: use `step [number]` to step an optional number of"
346
" instructions\n");
347
return DebugContinue;
348
}
349
350
// Step 1 instruction by default.
351
std::optional<uint64_t> number_of_instructions_to_execute{1};
352
353
if (args.size() == 1) {
354
// Parse the argument to step that number of instructions.
355
std::string arg = args.front();
356
number_of_instructions_to_execute = Debugger::ParseUint64String(arg);
357
if (!number_of_instructions_to_execute) {
358
fprintf(ostream_,
359
"Error: use `step [number]` to step an optional number of"
360
" instructions\n");
361
return DebugContinue;
362
}
363
}
364
365
while (!sim_->IsSimulationFinished() &&
366
*number_of_instructions_to_execute > 0) {
367
sim_->ExecuteInstruction();
368
(*number_of_instructions_to_execute)--;
369
370
// The first instruction has already been printed by Debug() so only
371
// enable instruction tracing after the first instruction has been
372
// executed.
373
sim_->SetTraceParameters(sim_->GetTraceParameters() | LOG_DISASM);
374
}
375
376
// Disable instruction tracing after all instructions have been executed.
377
sim_->SetTraceParameters(sim_->GetTraceParameters() & ~LOG_DISASM);
378
379
if (sim_->IsSimulationFinished()) {
380
fprintf(ostream_,
381
"Debugger at the end of simulation, leaving simulator...\n");
382
return DebugExit;
383
}
384
385
return DebugContinue;
386
}
387
388
389
DebugReturn ContinueCmd::Action(const std::vector<std::string>& args) {
390
USE(args);
391
392
fprintf(ostream_, "Continuing...\n");
393
394
if (sim_->GetDebugger()->IsAtBreakpoint()) {
395
// This breakpoint has already been hit, so execute it before continuing.
396
sim_->ExecuteInstruction();
397
}
398
399
return DebugExit;
400
}
401
402
403
DebugReturn PrintCmd::Action(const std::vector<std::string>& args) {
404
if (args.size() != 1) {
405
fprintf(ostream_,
406
"Error: use `print <register|all>` to print the contents of a"
407
" specific register or all registers.\n");
408
return DebugContinue;
409
}
410
411
if (args.front() == "all") {
412
sim_->PrintRegisters();
413
sim_->PrintZRegisters();
414
} else if (args.front() == "system") {
415
sim_->PrintSystemRegisters();
416
} else if (args.front() == "ffr") {
417
sim_->PrintFFR();
418
} else {
419
auto reg = Debugger::ParseRegString(args.front());
420
if (!reg) {
421
fprintf(ostream_,
422
"Error: incorrect register format, use e.g: X0, x0, etc...\n");
423
return DebugContinue;
424
}
425
426
// Ensure the stack pointer is printed instead of the zero register.
427
if ((*reg).second == kSpRegCode) {
428
(*reg).second = kSPRegInternalCode;
429
}
430
431
// Registers are printed in different ways depending on their type.
432
switch ((*reg).first) {
433
case 'W':
434
sim_->PrintRegister(
435
(*reg).second,
436
static_cast<Simulator::PrintRegisterFormat>(
437
Simulator::PrintRegisterFormat::kPrintWReg |
438
Simulator::PrintRegisterFormat::kPrintRegPartial));
439
break;
440
case 'X':
441
sim_->PrintRegister((*reg).second,
442
Simulator::PrintRegisterFormat::kPrintXReg);
443
break;
444
case 'V':
445
sim_->PrintVRegister((*reg).second);
446
break;
447
case 'Z':
448
sim_->PrintZRegister((*reg).second);
449
break;
450
case 'P':
451
sim_->PrintPRegister((*reg).second);
452
break;
453
default:
454
// ParseRegString should only allow valid register characters.
455
VIXL_UNREACHABLE();
456
}
457
}
458
459
return DebugContinue;
460
}
461
462
463
DebugReturn TraceCmd::Action(const std::vector<std::string>& args) {
464
if (args.size() != 0) {
465
fprintf(ostream_, "Error: use `trace` to toggle tracing of registers.\n");
466
return DebugContinue;
467
}
468
469
int trace_params = sim_->GetTraceParameters();
470
if ((trace_params & LOG_ALL) != LOG_ALL) {
471
fprintf(ostream_,
472
"Enabling disassembly, registers and memory write tracing\n");
473
sim_->SetTraceParameters(trace_params | LOG_ALL);
474
} else {
475
fprintf(ostream_,
476
"Disabling disassembly, registers and memory write tracing\n");
477
sim_->SetTraceParameters(trace_params & ~LOG_ALL);
478
}
479
480
return DebugContinue;
481
}
482
483
484
DebugReturn GdbCmd::Action(const std::vector<std::string>& args) {
485
if (args.size() != 0) {
486
fprintf(ostream_,
487
"Error: use `gdb` to enter GDB from the simulator debugger.\n");
488
return DebugContinue;
489
}
490
491
HostBreakpoint();
492
return DebugContinue;
493
}
494
495
496
} // namespace aarch64
497
} // namespace vixl
498
499
#endif // VIXL_INCLUDE_SIMULATOR_AARCH64
500
501