// 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 #include #include #include #include #include namespace vixl { namespace aarch64 { Debugger::Debugger(Simulator* sim) : sim_(sim), input_stream_(&std::cin), ostream_(sim->GetOutputStream()) { // Register all basic debugger commands. RegisterCmd(); RegisterCmd(); RegisterCmd(); RegisterCmd(); RegisterCmd(); RegisterCmd(); RegisterCmd(); } template void Debugger::RegisterCmd() { auto new_command = std::make_unique(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(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 tokenized_cmd = Tokenize(line); if (!tokenized_cmd.empty()) { done = ExecDebugCommand(tokenized_cmd); } } } std::optional 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::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 // 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 Debugger::Tokenize(std::string_view input_line, char separator) { std::vector 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& 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 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& args) { USE(args); sim_->GetDebugger()->PrintUsage(); return DebugContinue; } DebugReturn BreakCmd::Action(const std::vector& args) { if (args.size() != 1) { fprintf(ostream_, "Error: Use `break
` 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
` 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& 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 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& 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& args) { if (args.size() != 1) { fprintf(ostream_, "Error: use `print ` 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::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& 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& 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