mirror of
https://github.com/WinampDesktop/winamp.git
synced 2025-05-30 00:01:09 -04:00
500 lines
14 KiB
C++
500 lines
14 KiB
C++
// Copyright 2023, VIXL authors
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of ARM Limited nor the names of its contributors may be
|
|
// used to endorse or promote products derived from this software without
|
|
// specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
|
|
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
|
|
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
#ifdef VIXL_INCLUDE_SIMULATOR_AARCH64
|
|
|
|
#include "debugger-aarch64.h"
|
|
|
|
#include <cerrno>
|
|
#include <cmath>
|
|
#include <cstring>
|
|
#include <errno.h>
|
|
#include <limits>
|
|
#include <unistd.h>
|
|
|
|
namespace vixl {
|
|
namespace aarch64 {
|
|
|
|
|
|
Debugger::Debugger(Simulator* sim)
|
|
: sim_(sim), input_stream_(&std::cin), ostream_(sim->GetOutputStream()) {
|
|
// Register all basic debugger commands.
|
|
RegisterCmd<HelpCmd>();
|
|
RegisterCmd<BreakCmd>();
|
|
RegisterCmd<StepCmd>();
|
|
RegisterCmd<ContinueCmd>();
|
|
RegisterCmd<PrintCmd>();
|
|
RegisterCmd<TraceCmd>();
|
|
RegisterCmd<GdbCmd>();
|
|
}
|
|
|
|
|
|
template <class T>
|
|
void Debugger::RegisterCmd() {
|
|
auto new_command = std::make_unique<T>(sim_);
|
|
|
|
// Check that the new command word and alias, don't already exist.
|
|
std::string_view new_cmd_word = new_command->GetCommandWord();
|
|
std::string_view new_cmd_alias = new_command->GetCommandAlias();
|
|
for (const auto& cmd : debugger_cmds_) {
|
|
std::string_view cmd_word = cmd->GetCommandWord();
|
|
std::string_view cmd_alias = cmd->GetCommandAlias();
|
|
|
|
if (new_cmd_word == cmd_word) {
|
|
VIXL_ABORT_WITH_MSG("Command word matches an existing command word.");
|
|
} else if (new_cmd_word == cmd_alias) {
|
|
VIXL_ABORT_WITH_MSG("Command word matches an existing command alias.");
|
|
}
|
|
|
|
if (new_cmd_alias != "") {
|
|
if (new_cmd_alias == cmd_word) {
|
|
VIXL_ABORT_WITH_MSG("Command alias matches an existing command word.");
|
|
} else if (new_cmd_alias == cmd_alias) {
|
|
VIXL_ABORT_WITH_MSG("Command alias matches an existing command alias.");
|
|
}
|
|
}
|
|
}
|
|
|
|
debugger_cmds_.push_back(std::move(new_command));
|
|
}
|
|
|
|
|
|
bool Debugger::IsAtBreakpoint() const {
|
|
return IsBreakpoint(reinterpret_cast<uint64_t>(sim_->ReadPc()));
|
|
}
|
|
|
|
|
|
void Debugger::Debug() {
|
|
DebugReturn done = DebugContinue;
|
|
while (done == DebugContinue) {
|
|
// Disassemble the next instruction to execute.
|
|
PrintDisassembler print_disasm = PrintDisassembler(ostream_);
|
|
print_disasm.Disassemble(sim_->ReadPc());
|
|
|
|
// Read the command line.
|
|
fprintf(ostream_, "sim> ");
|
|
std::string line;
|
|
std::getline(*input_stream_, line);
|
|
|
|
// Remove all control characters from the command string.
|
|
line.erase(std::remove_if(line.begin(),
|
|
line.end(),
|
|
[](char c) { return std::iscntrl(c); }),
|
|
line.end());
|
|
|
|
// Assume input from std::cin has already been output (e.g: by a terminal)
|
|
// but input from elsewhere (e.g: from a testing input stream) has not.
|
|
if (input_stream_ != &std::cin) {
|
|
fprintf(ostream_, "%s\n", line.c_str());
|
|
}
|
|
|
|
// Parse the command into tokens.
|
|
std::vector<std::string> tokenized_cmd = Tokenize(line);
|
|
if (!tokenized_cmd.empty()) {
|
|
done = ExecDebugCommand(tokenized_cmd);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
std::optional<uint64_t> Debugger::ParseUint64String(std::string_view uint64_str,
|
|
int base) {
|
|
// Clear any previous errors.
|
|
errno = 0;
|
|
|
|
// strtoull uses 0 to indicate that no conversion was possible so first
|
|
// check that the string isn't zero.
|
|
if (IsZeroUint64String(uint64_str, base)) {
|
|
return 0;
|
|
}
|
|
|
|
// Cannot use stoi as it might not be possible to use exceptions.
|
|
char* end;
|
|
uint64_t value = std::strtoull(uint64_str.data(), &end, base);
|
|
if (value == 0 || *end != '\0' || errno == ERANGE) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
|
|
std::optional<Debugger::RegisterParsedFormat> Debugger::ParseRegString(
|
|
std::string_view reg_str) {
|
|
// A register should only have 2 (e.g: X0) or 3 (e.g: X31) characters.
|
|
if (reg_str.size() < 2 || reg_str.size() > 3) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Check for aliases of registers.
|
|
if (reg_str == "lr") {
|
|
return {{'X', kLinkRegCode}};
|
|
} else if (reg_str == "sp") {
|
|
return {{'X', kSpRegCode}};
|
|
}
|
|
|
|
unsigned max_reg_num;
|
|
char reg_prefix = std::toupper(reg_str.front());
|
|
switch (reg_prefix) {
|
|
case 'W':
|
|
VIXL_FALLTHROUGH();
|
|
case 'X':
|
|
max_reg_num = kNumberOfRegisters - 1;
|
|
break;
|
|
case 'V':
|
|
max_reg_num = kNumberOfVRegisters - 1;
|
|
break;
|
|
case 'Z':
|
|
max_reg_num = kNumberOfZRegisters - 1;
|
|
break;
|
|
case 'P':
|
|
max_reg_num = kNumberOfPRegisters - 1;
|
|
break;
|
|
default:
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::string_view str_code = reg_str.substr(1, reg_str.size());
|
|
auto reg_code = ParseUint64String(str_code, 10);
|
|
if (!reg_code) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
if (*reg_code > max_reg_num) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
return {{reg_prefix, *reg_code}};
|
|
}
|
|
|
|
|
|
void Debugger::PrintUsage() {
|
|
for (const auto& cmd : debugger_cmds_) {
|
|
// Print commands in the following format:
|
|
// foo / f
|
|
// foo <arg>
|
|
// A description of the foo command.
|
|
//
|
|
|
|
std::string_view cmd_word = cmd->GetCommandWord();
|
|
std::string_view cmd_alias = cmd->GetCommandAlias();
|
|
if (cmd_alias != "") {
|
|
fprintf(ostream_, "%s / %s\n", cmd_word.data(), cmd_alias.data());
|
|
} else {
|
|
fprintf(ostream_, "%s\n", cmd_word.data());
|
|
}
|
|
|
|
std::string_view args_str = cmd->GetArgsString();
|
|
if (args_str != "") {
|
|
fprintf(ostream_, "\t%s %s\n", cmd_word.data(), args_str.data());
|
|
}
|
|
|
|
std::string_view description = cmd->GetDescription();
|
|
if (description != "") {
|
|
fprintf(ostream_, "\t%s\n", description.data());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
std::vector<std::string> Debugger::Tokenize(std::string_view input_line,
|
|
char separator) {
|
|
std::vector<std::string> words;
|
|
|
|
if (input_line.empty()) {
|
|
return words;
|
|
}
|
|
|
|
for (auto separator_pos = input_line.find(separator);
|
|
separator_pos != input_line.npos;
|
|
separator_pos = input_line.find(separator)) {
|
|
// Skip consecutive, repeated separators.
|
|
if (separator_pos != 0) {
|
|
words.push_back(std::string{input_line.substr(0, separator_pos)});
|
|
}
|
|
|
|
// Remove characters up to and including the separator.
|
|
input_line.remove_prefix(separator_pos + 1);
|
|
}
|
|
|
|
// Add the rest of the string to the vector.
|
|
words.push_back(std::string{input_line});
|
|
|
|
return words;
|
|
}
|
|
|
|
|
|
DebugReturn Debugger::ExecDebugCommand(
|
|
const std::vector<std::string>& tokenized_cmd) {
|
|
std::string cmd_word = tokenized_cmd.front();
|
|
for (const auto& cmd : debugger_cmds_) {
|
|
if (cmd_word == cmd->GetCommandWord() ||
|
|
cmd_word == cmd->GetCommandAlias()) {
|
|
const std::vector<std::string> args(tokenized_cmd.begin() + 1,
|
|
tokenized_cmd.end());
|
|
|
|
// Call the handler for the command and pass the arguments.
|
|
return cmd->Action(args);
|
|
}
|
|
}
|
|
|
|
fprintf(ostream_, "Error: command '%s' not found\n", cmd_word.c_str());
|
|
return DebugContinue;
|
|
}
|
|
|
|
|
|
bool Debugger::IsZeroUint64String(std::string_view uint64_str, int base) {
|
|
// Remove any hex prefixes.
|
|
if (base == 0 || base == 16) {
|
|
std::string_view prefix = uint64_str.substr(0, 2);
|
|
if (prefix == "0x" || prefix == "0X") {
|
|
uint64_str.remove_prefix(2);
|
|
}
|
|
}
|
|
|
|
if (uint64_str.empty()) {
|
|
return false;
|
|
}
|
|
|
|
// Check all remaining digits in the string for anything other than zero.
|
|
for (char c : uint64_str) {
|
|
if (c != '0') {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
DebuggerCmd::DebuggerCmd(Simulator* sim,
|
|
std::string cmd_word,
|
|
std::string cmd_alias,
|
|
std::string args_str,
|
|
std::string description)
|
|
: sim_(sim),
|
|
ostream_(sim->GetOutputStream()),
|
|
command_word_(cmd_word),
|
|
command_alias_(cmd_alias),
|
|
args_str_(args_str),
|
|
description_(description) {}
|
|
|
|
|
|
DebugReturn HelpCmd::Action(const std::vector<std::string>& args) {
|
|
USE(args);
|
|
sim_->GetDebugger()->PrintUsage();
|
|
return DebugContinue;
|
|
}
|
|
|
|
|
|
DebugReturn BreakCmd::Action(const std::vector<std::string>& args) {
|
|
if (args.size() != 1) {
|
|
fprintf(ostream_, "Error: Use `break <address>` to set a breakpoint\n");
|
|
return DebugContinue;
|
|
}
|
|
|
|
std::string arg = args.front();
|
|
auto break_addr = Debugger::ParseUint64String(arg);
|
|
if (!break_addr) {
|
|
fprintf(ostream_, "Error: Use `break <address>` to set a breakpoint\n");
|
|
return DebugContinue;
|
|
}
|
|
|
|
if (sim_->GetDebugger()->IsBreakpoint(*break_addr)) {
|
|
sim_->GetDebugger()->RemoveBreakpoint(*break_addr);
|
|
fprintf(ostream_,
|
|
"Breakpoint successfully removed at: 0x%" PRIx64 "\n",
|
|
*break_addr);
|
|
} else {
|
|
sim_->GetDebugger()->RegisterBreakpoint(*break_addr);
|
|
fprintf(ostream_,
|
|
"Breakpoint successfully added at: 0x%" PRIx64 "\n",
|
|
*break_addr);
|
|
}
|
|
|
|
return DebugContinue;
|
|
}
|
|
|
|
|
|
DebugReturn StepCmd::Action(const std::vector<std::string>& args) {
|
|
if (args.size() > 1) {
|
|
fprintf(ostream_,
|
|
"Error: use `step [number]` to step an optional number of"
|
|
" instructions\n");
|
|
return DebugContinue;
|
|
}
|
|
|
|
// Step 1 instruction by default.
|
|
std::optional<uint64_t> number_of_instructions_to_execute{1};
|
|
|
|
if (args.size() == 1) {
|
|
// Parse the argument to step that number of instructions.
|
|
std::string arg = args.front();
|
|
number_of_instructions_to_execute = Debugger::ParseUint64String(arg);
|
|
if (!number_of_instructions_to_execute) {
|
|
fprintf(ostream_,
|
|
"Error: use `step [number]` to step an optional number of"
|
|
" instructions\n");
|
|
return DebugContinue;
|
|
}
|
|
}
|
|
|
|
while (!sim_->IsSimulationFinished() &&
|
|
*number_of_instructions_to_execute > 0) {
|
|
sim_->ExecuteInstruction();
|
|
(*number_of_instructions_to_execute)--;
|
|
|
|
// The first instruction has already been printed by Debug() so only
|
|
// enable instruction tracing after the first instruction has been
|
|
// executed.
|
|
sim_->SetTraceParameters(sim_->GetTraceParameters() | LOG_DISASM);
|
|
}
|
|
|
|
// Disable instruction tracing after all instructions have been executed.
|
|
sim_->SetTraceParameters(sim_->GetTraceParameters() & ~LOG_DISASM);
|
|
|
|
if (sim_->IsSimulationFinished()) {
|
|
fprintf(ostream_,
|
|
"Debugger at the end of simulation, leaving simulator...\n");
|
|
return DebugExit;
|
|
}
|
|
|
|
return DebugContinue;
|
|
}
|
|
|
|
|
|
DebugReturn ContinueCmd::Action(const std::vector<std::string>& args) {
|
|
USE(args);
|
|
|
|
fprintf(ostream_, "Continuing...\n");
|
|
|
|
if (sim_->GetDebugger()->IsAtBreakpoint()) {
|
|
// This breakpoint has already been hit, so execute it before continuing.
|
|
sim_->ExecuteInstruction();
|
|
}
|
|
|
|
return DebugExit;
|
|
}
|
|
|
|
|
|
DebugReturn PrintCmd::Action(const std::vector<std::string>& args) {
|
|
if (args.size() != 1) {
|
|
fprintf(ostream_,
|
|
"Error: use `print <register|all>` to print the contents of a"
|
|
" specific register or all registers.\n");
|
|
return DebugContinue;
|
|
}
|
|
|
|
if (args.front() == "all") {
|
|
sim_->PrintRegisters();
|
|
sim_->PrintZRegisters();
|
|
} else if (args.front() == "system") {
|
|
sim_->PrintSystemRegisters();
|
|
} else if (args.front() == "ffr") {
|
|
sim_->PrintFFR();
|
|
} else {
|
|
auto reg = Debugger::ParseRegString(args.front());
|
|
if (!reg) {
|
|
fprintf(ostream_,
|
|
"Error: incorrect register format, use e.g: X0, x0, etc...\n");
|
|
return DebugContinue;
|
|
}
|
|
|
|
// Ensure the stack pointer is printed instead of the zero register.
|
|
if ((*reg).second == kSpRegCode) {
|
|
(*reg).second = kSPRegInternalCode;
|
|
}
|
|
|
|
// Registers are printed in different ways depending on their type.
|
|
switch ((*reg).first) {
|
|
case 'W':
|
|
sim_->PrintRegister(
|
|
(*reg).second,
|
|
static_cast<Simulator::PrintRegisterFormat>(
|
|
Simulator::PrintRegisterFormat::kPrintWReg |
|
|
Simulator::PrintRegisterFormat::kPrintRegPartial));
|
|
break;
|
|
case 'X':
|
|
sim_->PrintRegister((*reg).second,
|
|
Simulator::PrintRegisterFormat::kPrintXReg);
|
|
break;
|
|
case 'V':
|
|
sim_->PrintVRegister((*reg).second);
|
|
break;
|
|
case 'Z':
|
|
sim_->PrintZRegister((*reg).second);
|
|
break;
|
|
case 'P':
|
|
sim_->PrintPRegister((*reg).second);
|
|
break;
|
|
default:
|
|
// ParseRegString should only allow valid register characters.
|
|
VIXL_UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
return DebugContinue;
|
|
}
|
|
|
|
|
|
DebugReturn TraceCmd::Action(const std::vector<std::string>& args) {
|
|
if (args.size() != 0) {
|
|
fprintf(ostream_, "Error: use `trace` to toggle tracing of registers.\n");
|
|
return DebugContinue;
|
|
}
|
|
|
|
int trace_params = sim_->GetTraceParameters();
|
|
if ((trace_params & LOG_ALL) != LOG_ALL) {
|
|
fprintf(ostream_,
|
|
"Enabling disassembly, registers and memory write tracing\n");
|
|
sim_->SetTraceParameters(trace_params | LOG_ALL);
|
|
} else {
|
|
fprintf(ostream_,
|
|
"Disabling disassembly, registers and memory write tracing\n");
|
|
sim_->SetTraceParameters(trace_params & ~LOG_ALL);
|
|
}
|
|
|
|
return DebugContinue;
|
|
}
|
|
|
|
|
|
DebugReturn GdbCmd::Action(const std::vector<std::string>& args) {
|
|
if (args.size() != 0) {
|
|
fprintf(ostream_,
|
|
"Error: use `gdb` to enter GDB from the simulator debugger.\n");
|
|
return DebugContinue;
|
|
}
|
|
|
|
HostBreakpoint();
|
|
return DebugContinue;
|
|
}
|
|
|
|
|
|
} // namespace aarch64
|
|
} // namespace vixl
|
|
|
|
#endif // VIXL_INCLUDE_SIMULATOR_AARCH64
|