Add a debugger to VIXL simulator (#81)
Add a basic debugger to the VIXL simulator. Once enabled (by default
the debugger is disabled) any brk instruction encountered while
simulating will cause the interactive debugger to be launched.
The debugger supports the following features:
- Break
- Step
- Continue
- Printing registers
- Toggling tracing
- Switching to GDB
diff --git a/doc/aarch64/topics/index.md b/doc/aarch64/topics/index.md
index 0e11450..90c1791 100644
--- a/doc/aarch64/topics/index.md
+++ b/doc/aarch64/topics/index.md
@@ -6,3 +6,4 @@
* [Extending and customizing the disassembler](extending-the-disassembler.md)
* [Using VIM YouCompleteMe with VIXL](ycm.md)
+* [Debugging with the VIXL Simulator](simulator-debugger.md)
diff --git a/doc/aarch64/topics/simulator-debugger.md b/doc/aarch64/topics/simulator-debugger.md
new file mode 100644
index 0000000..7d92b00
--- /dev/null
+++ b/doc/aarch64/topics/simulator-debugger.md
@@ -0,0 +1,114 @@
+Debugging with the VIXL Simulator
+=================================
+
+The VIXL AArch64 simulator contains a basic debugger which can be used to debug
+simulated applications. The debugger supports basic debugging features such as
+setting breakpoints, stepping through simulated instructions and printing
+simulator specific information, for example: printing the values of a register
+or printing instructions at specified addresses.
+
+Using the Debugger
+------------------
+
+In order to use the debugger it first needs to be enabled in the simulator.
+
+```C++
+ Decoder decoder;
+ Simulator simulator(&decoder);
+ simulator.SetDebuggerEnabled(true);
+```
+
+Once enabled, the debugger will be activated whenever a breakpoint (brk) is
+encountered by the simulator. For example:
+
+```asm
+ add x1, x0, #5
+ mov x2, #2
+
+ brk 0 // Debugger activated here.
+
+ sub x3, x1, x2
+```
+
+Further breakpoints can be set either programmatically or interactively in the
+debugger itself. For example, to set breakpoints programmatically:
+
+```C++
+ // 'func' is an AARCH64 assembly function.
+ extern "C" void func();
+
+ Debugger* debugger = simulator.GetDebugger();
+
+ // Register a breakpoint at a fixed (absolute) address.
+ debugger->RegisterBreakpoint(0x00007ffbc6d38000);
+
+ // Register a breakpoint to an already existing assembly function.
+ debugger->RegisterBreakpoint(reinterpret_cast<uint64_t>(&func));
+```
+
+Or to set breakpoints interactively once the debugger has been activated:
+
+```sh
+ sim> break 0x00007ffbc6d38000
+```
+
+The debugger has a variety of useful commands to control program flow (e.g:
+step, next, continue) and inspect features of the running simulator (e.g:
+print, trace). To view a list of all supported commands
+use "help" at the debugger prompt.
+
+```sh
+ sim> help
+```
+
+Extending the Debugger
+----------------------
+
+The debugger can be extended with custom commands to allow for greater
+flexibility in debugging individual applications. This could be used for a
+variety of applications, for example printing out object specific information
+from an address.
+
+To create a custom debugger command, extend the DebuggerCmd class located in
+debugger-aarch64.h and implement its methods.
+
+```C++
+ class PrintObjectCmd : public DebuggerCmd {
+ public:
+ PrintObjectCmd(Simulator* sim)
+ : DebuggerCmd(sim,
+ "printobject",
+ "po",
+ "<address>",
+ "Print a custom object located at the given address.")
+ {}
+
+ // Called when the command word is given to the interactive debugger.
+ DebugReturn Action(const std::vector<std::string>& args) override {
+ // We want exactly 1 argument (an address) given to the printobject
+ // command.
+ if (args.size() != 1) {
+ fprintf(ostream_, "Error: incorrect command format.");
+ return DebugContinue;
+ }
+
+ auto addr = Debugger::ParseUint64String(args.front());
+ if (addr) {
+ fprintf(ostream_, "Error: could not get address from string.");
+ return DebugContinue;
+ }
+
+ // Convert the address given to a custom object and then print it.
+ CustomObject object = reinterpret_cast<CustomObject>(*addr);
+ object.print();
+ }
+ };
+```
+
+Then simply register the new command with the debugger.
+
+```C++
+ Debugger* debugger = simulator.GetDebugger();
+
+ debugger->RegisterCmd<PrintObjectCmd>();
+```
diff --git a/examples/aarch64/debugging.cc b/examples/aarch64/debugging.cc
new file mode 100644
index 0000000..de167f3
--- /dev/null
+++ b/examples/aarch64/debugging.cc
@@ -0,0 +1,87 @@
+// 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.
+
+#include "examples.h"
+
+#include "aarch64/disasm-aarch64.h"
+#include "aarch64/macro-assembler-aarch64.h"
+#include "aarch64/simulator-aarch64.h"
+
+using namespace vixl;
+using namespace vixl::aarch64;
+
+#define __ masm->
+
+void GenerateDebugExample(MacroAssembler* masm) {
+ // Create a breakpoint here to break into the debugger.
+ __ Brk(0);
+
+ // Do some arithmetic.
+ __ Add(x1, x0, 5);
+ __ Mov(x2, 2);
+ __ Sub(x3, x1, x2);
+
+ __ Ret();
+}
+
+#ifndef TEST_EXAMPLES
+#ifdef VIXL_INCLUDE_SIMULATOR_AARCH64
+
+int main(void) {
+ MacroAssembler masm;
+
+ // Generate the code for the example function.
+ Label debug_example;
+ masm.Bind(&debug_example);
+ GenerateDebugExample(&masm);
+ masm.FinalizeCode();
+
+ Instruction* start = masm.GetLabelAddress<Instruction*>(&debug_example);
+
+ // Disassemble the generated code.
+ PrintDisassembler disassembler(stdout);
+ disassembler.DisassembleBuffer(start, masm.GetSizeOfCodeGenerated());
+
+ Decoder decoder;
+ Simulator simulator(&decoder);
+
+ simulator.SetColouredTrace(true);
+ simulator.SetDebuggerEnabled(true);
+
+ int32_t input_a = 1;
+ int32_t input_b = 2;
+ simulator.WriteWRegister(0, input_a);
+ simulator.WriteWRegister(1, input_b);
+ simulator.RunFrom(start);
+ printf("The final result is %ld\n", simulator.ReadXRegister(3));
+
+ return 0;
+}
+
+#else
+int main(void) { return 0; }
+#endif // VIXL_INCLUDE_SIMULATOR_AARCH64
+#endif // TEST_EXAMPLES
diff --git a/src/aarch64/debugger-aarch64.cc b/src/aarch64/debugger-aarch64.cc
new file mode 100644
index 0000000..39db2a0
--- /dev/null
+++ b/src/aarch64/debugger-aarch64.cc
@@ -0,0 +1,499 @@
+// 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
diff --git a/src/aarch64/debugger-aarch64.h b/src/aarch64/debugger-aarch64.h
new file mode 100644
index 0000000..8866094
--- /dev/null
+++ b/src/aarch64/debugger-aarch64.h
@@ -0,0 +1,275 @@
+// 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.
+
+#ifndef VIXL_AARCH64_DEBUGGER_AARCH64_H_
+#define VIXL_AARCH64_DEBUGGER_AARCH64_H_
+
+#include <unordered_set>
+#include <vector>
+
+#include "../globals-vixl.h"
+#include "../utils-vixl.h"
+#include "cpu-features.h"
+
+#include "abi-aarch64.h"
+#include "cpu-features-auditor-aarch64.h"
+#include "disasm-aarch64.h"
+#include "instructions-aarch64.h"
+#include "simulator-aarch64.h"
+#include "simulator-constants-aarch64.h"
+
+#ifdef VIXL_INCLUDE_SIMULATOR_AARCH64
+
+namespace vixl {
+namespace aarch64 {
+
+class Simulator;
+
+enum DebugReturn { DebugContinue, DebugExit };
+
+
+// A debugger command that performs some action when used by the simulator
+// debugger.
+class DebuggerCmd {
+ public:
+ DebuggerCmd(Simulator* sim,
+ std::string cmd_word,
+ std::string cmd_alias,
+ std::string usage,
+ std::string description);
+ virtual ~DebuggerCmd() {}
+
+ // Perform some action based on the arguments passed in. Returns true if the
+ // debugger should exit after the action, false otherwise.
+ virtual DebugReturn Action(const std::vector<std::string>& args) = 0;
+
+ // Return the command word.
+ std::string_view GetCommandWord() { return command_word_; }
+ // Return the alias for this command. Returns an empty string if this command
+ // has no alias.
+ std::string_view GetCommandAlias() { return command_alias_; }
+ // Return this commands usage.
+ std::string_view GetArgsString() { return args_str_; }
+ // Return this commands description.
+ std::string_view GetDescription() { return description_; }
+
+ protected:
+ // Simulator which this command will be performed on.
+ Simulator* sim_;
+ // Stream to output the result of the command to.
+ FILE* ostream_;
+ // Command word that, when given to the interactive debugger, calls Action.
+ std::string command_word_;
+ // Optional alias for the command_word.
+ std::string command_alias_;
+ // Optional string showing the arguments that can be passed to the command.
+ std::string args_str_;
+ // Optional description of the command.
+ std::string description_;
+};
+
+
+//
+// Base debugger command handlers:
+//
+
+
+class HelpCmd : public DebuggerCmd {
+ public:
+ HelpCmd(Simulator* sim)
+ : DebuggerCmd(sim, "help", "h", "", "Display this help message.") {}
+
+ DebugReturn Action(const std::vector<std::string>& args) override;
+};
+
+
+class BreakCmd : public DebuggerCmd {
+ public:
+ BreakCmd(Simulator* sim)
+ : DebuggerCmd(sim,
+ "break",
+ "b",
+ "<address>",
+ "Set or remove a breakpoint.") {}
+
+ DebugReturn Action(const std::vector<std::string>& args) override;
+};
+
+
+class StepCmd : public DebuggerCmd {
+ public:
+ StepCmd(Simulator* sim)
+ : DebuggerCmd(sim,
+ "step",
+ "s",
+ "[<n>]",
+ "Step n instructions, default step 1 instruction.") {}
+
+ DebugReturn Action(const std::vector<std::string>& args) override;
+};
+
+
+class ContinueCmd : public DebuggerCmd {
+ public:
+ ContinueCmd(Simulator* sim)
+ : DebuggerCmd(sim,
+ "continue",
+ "c",
+ "",
+ "Exit the debugger and continue executing instructions.") {}
+
+ DebugReturn Action(const std::vector<std::string>& args) override;
+};
+
+
+class PrintCmd : public DebuggerCmd {
+ public:
+ PrintCmd(Simulator* sim)
+ : DebuggerCmd(sim,
+ "print",
+ "p",
+ "<register|all|system>",
+ "Print the contents of a register, all registers or all"
+ " system registers.") {}
+
+ DebugReturn Action(const std::vector<std::string>& args) override;
+};
+
+
+class TraceCmd : public DebuggerCmd {
+ public:
+ TraceCmd(Simulator* sim)
+ : DebuggerCmd(sim,
+ "trace",
+ "t",
+ "",
+ "Start/stop memory and register tracing.") {}
+
+ DebugReturn Action(const std::vector<std::string>& args) override;
+};
+
+
+class GdbCmd : public DebuggerCmd {
+ public:
+ GdbCmd(Simulator* sim)
+ : DebuggerCmd(sim,
+ "gdb",
+ "g",
+ "",
+ "Enter an already running instance of gdb.") {}
+
+ DebugReturn Action(const std::vector<std::string>& args) override;
+};
+
+
+// A debugger for the Simulator which takes input from the user in order to
+// control the running of the Simulator.
+class Debugger {
+ public:
+ // A pair consisting of a register character (e.g: W, X, V) and a register
+ // code (e.g: 0, 1 ...31) which represents a single parsed register.
+ //
+ // Note: the register character is guaranteed to be upper case.
+ using RegisterParsedFormat = std::pair<char, unsigned>;
+
+ Debugger(Simulator* sim);
+
+ // Set the input stream, from which commands are read, to a custom stream.
+ void SetInputStream(std::istream* stream) { input_stream_ = stream; }
+
+ // Register a new command for the debugger.
+ template <class T>
+ void RegisterCmd();
+
+ // Set a breakpoint at the given address.
+ void RegisterBreakpoint(uint64_t addr) { breakpoints_.insert(addr); }
+ // Remove a breakpoint at the given address.
+ void RemoveBreakpoint(uint64_t addr) { breakpoints_.erase(addr); }
+ // Return true if the address is the location of a breakpoint.
+ bool IsBreakpoint(uint64_t addr) const {
+ return (breakpoints_.find(addr) != breakpoints_.end());
+ }
+ // Return true if the simulator pc is a breakpoint.
+ bool IsAtBreakpoint() const;
+
+ // Main loop for the debugger. Keep prompting for user inputted debugger
+ // commands and try to execute them until a command is given that exits the
+ // interactive debugger.
+ void Debug();
+
+ // Get an unsigned integer value from a string and return it in 'value'.
+ // Base is used to determine the numeric base of the number to be read,
+ // i.e: 8 for octal, 10 for decimal, 16 for hexadecimal and 0 for
+ // auto-detect. Return true if an integer value was found, false otherwise.
+ static std::optional<uint64_t> ParseUint64String(std::string_view uint64_str,
+ int base = 0);
+
+ // Get a register from a string and return it in 'reg'. Return true if a
+ // valid register character and code (e.g: W0, X29, V31) was found, false
+ // otherwise.
+ static std::optional<RegisterParsedFormat> ParseRegString(
+ std::string_view reg_str);
+
+ // Print the usage of each debugger command.
+ void PrintUsage();
+
+ private:
+ // Split a string based on the separator given (a single space character by
+ // default) and return as a std::vector of strings.
+ static std::vector<std::string> Tokenize(std::string_view input_line,
+ char separator = ' ');
+
+ // Try to execute a single debugger command.
+ DebugReturn ExecDebugCommand(const std::vector<std::string>& tokenized_cmd);
+
+ // Return true if the string is zero, i.e: all characters in the string
+ // (other than prefixes) are zero.
+ static bool IsZeroUint64String(std::string_view uint64_str, int base);
+
+ // The simulator that this debugger acts on.
+ Simulator* sim_;
+
+ // A vector of all commands recognised by the debugger.
+ std::vector<std::unique_ptr<DebuggerCmd>> debugger_cmds_;
+
+ // Input stream from which commands are read. Default is std::cin.
+ std::istream* input_stream_;
+
+ // Output stream from the simulator.
+ FILE* ostream_;
+
+ // A list of all instruction addresses that, when executed by the
+ // simulator, will start the interactive debugger if it hasn't already.
+ std::unordered_set<uint64_t> breakpoints_;
+};
+
+
+} // namespace aarch64
+} // namespace vixl
+
+#endif // VIXL_INCLUDE_SIMULATOR_AARCH64
+
+#endif // VIXL_AARCH64_DEBUGGER_AARCH64_H_
diff --git a/src/aarch64/simulator-aarch64.cc b/src/aarch64/simulator-aarch64.cc
index edf5c3a..a02a296 100644
--- a/src/aarch64/simulator-aarch64.cc
+++ b/src/aarch64/simulator-aarch64.cc
@@ -548,6 +548,10 @@
// Initialize all bits of pseudo predicate register to true.
LogicPRegister ones(pregister_all_true_);
ones.SetAllBits();
+
+ // Initialize the debugger but disable it by default.
+ SetDebuggerEnabled(false);
+ debugger_ = std::make_unique<Debugger>(this);
}
void Simulator::ResetSystemRegisters() {
@@ -658,8 +662,21 @@
// manually-set registers are logged _before_ the first instruction.
LogAllWrittenRegisters();
- while (pc_ != kEndOfSimAddress) {
- ExecuteInstruction();
+ if (debugger_enabled_) {
+ // Slow path to check for breakpoints only if the debugger is enabled.
+ Debugger* debugger = GetDebugger();
+ while (!IsSimulationFinished()) {
+ if (debugger->IsAtBreakpoint()) {
+ fprintf(stream_, "Debugger hit breakpoint, breaking...\n");
+ debugger->Debug();
+ } else {
+ ExecuteInstruction();
+ }
+ }
+ } else {
+ while (!IsSimulationFinished()) {
+ ExecuteInstruction();
+ }
}
}
@@ -6874,7 +6891,15 @@
return;
}
case BRK:
- HostBreakpoint();
+ if (debugger_enabled_) {
+ uint64_t next_instr =
+ reinterpret_cast<uint64_t>(pc_->GetNextInstruction());
+ if (!debugger_->IsBreakpoint(next_instr)) {
+ debugger_->RegisterBreakpoint(next_instr);
+ }
+ } else {
+ HostBreakpoint();
+ }
return;
default:
VIXL_UNIMPLEMENTED();
diff --git a/src/aarch64/simulator-aarch64.h b/src/aarch64/simulator-aarch64.h
index b3226b7..e739af0 100644
--- a/src/aarch64/simulator-aarch64.h
+++ b/src/aarch64/simulator-aarch64.h
@@ -37,6 +37,7 @@
#include "cpu-features.h"
#include "abi-aarch64.h"
#include "cpu-features-auditor-aarch64.h"
+#include "debugger-aarch64.h"
#include "disasm-aarch64.h"
#include "instructions-aarch64.h"
#include "simulator-constants-aarch64.h"
@@ -1233,6 +1234,10 @@
uint32_t seed_;
};
+
+class Debugger;
+
+
class Simulator : public DecoderVisitor {
public:
explicit Simulator(Decoder* decoder,
@@ -1307,6 +1312,8 @@
static const Instruction* kEndOfSimAddress;
// Simulation helpers.
+ bool IsSimulationFinished() const { return pc_ == kEndOfSimAddress; }
+
const Instruction* ReadPc() const { return pc_; }
VIXL_DEPRECATED("ReadPc", const Instruction* pc() const) { return ReadPc(); }
@@ -3115,6 +3122,15 @@
meta_data_.RegisterBranchInterception(*function, callback);
}
+ // Return the current output stream in use by the simulator.
+ FILE* GetOutputStream() const { return stream_; }
+
+ bool IsDebuggerEnabled() const { return debugger_enabled_; }
+
+ void SetDebuggerEnabled(bool enabled) { debugger_enabled_ = enabled; }
+
+ Debugger* GetDebugger() const { return debugger_.get(); }
+
protected:
const char* clr_normal;
const char* clr_flag_name;
@@ -5232,6 +5248,12 @@
// Representation of memory attributes such as MTE tagging and BTI page
// protection in addition to branch interceptions.
MetaDataDepot meta_data_;
+
+ // True if the debugger is enabled and might get entered.
+ bool debugger_enabled_;
+
+ // Debugger for the simulator.
+ std::unique_ptr<Debugger> debugger_;
};
#if defined(VIXL_HAS_SIMULATED_RUNTIME_CALL_SUPPORT) && __cplusplus < 201402L
diff --git a/test/aarch64/test-debugger-aarch64.cc b/test/aarch64/test-debugger-aarch64.cc
new file mode 100644
index 0000000..f967335
--- /dev/null
+++ b/test/aarch64/test-debugger-aarch64.cc
@@ -0,0 +1,364 @@
+// 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.
+
+#include "test-debugger-aarch64.h"
+
+namespace vixl {
+namespace aarch64 {
+
+TEST(breakpoints_invalid) {
+ SETUP();
+
+ // Test invalid strings instead of numbers.
+ SETUP_CMD("break a", "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("break abcdef", "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("break A", "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("break ABCDEF", "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("break 0x", "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("break 0xg", "Error: Use `break <address>` to set a breakpoint");
+
+ // Test different amounts of parameters.
+ SETUP_CMD("break", "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("break 42 52", "Error: Use `break <address>` to set a breakpoint");
+
+ // Test out of range addresses.
+ SETUP_CMD("break 0xFFFFFFFFFFFFFFFF1",
+ "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("break 18446744073709551616",
+ "Error: Use `break <address>` to set a breakpoint");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(breakpoints_valid) {
+ SETUP();
+
+ // Test bottom boundary of addresses.
+ SETUP_CMD("break 0x0", "Breakpoint successfully added at: 0x0");
+ SETUP_CMD("break 0", "Breakpoint successfully removed at: 0x0");
+ SETUP_CMD("break 0x1", "Breakpoint successfully added at: 0x1");
+ SETUP_CMD("break 1", "Breakpoint successfully removed at: 0x1");
+
+ // Test top boundary of addresses.
+ SETUP_CMD("break 0xFFFFFFFFFFFFFFFF",
+ "Breakpoint successfully added at: 0xffffffffffffffff");
+ SETUP_CMD("break 18446744073709551615",
+ "Breakpoint successfully removed at: 0xffffffffffffffff");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(breakpoints_hit) {
+ SETUP();
+
+ // Test hitting a breakpoint.
+ std::string mov_addr = GET_INSTRUCTION_ADDRESS("mov x2, #0x2");
+ std::string break_cmd = "break ";
+ break_cmd += mov_addr;
+ std::string expected_trace = "Breakpoint successfully added at: ";
+ expected_trace += mov_addr;
+ SETUP_CMD(break_cmd, expected_trace);
+ SETUP_CMD("continue",
+ "Continuing...\n"
+ "Debugger hit breakpoint, breaking...");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(cmd_aliases) {
+ SETUP();
+
+ // Test all short form commands, to ensure they correctly run their long form
+ // counterparts.
+ SETUP_CMD("b", "Error: Use `break <address>` to set a breakpoint");
+ SETUP_CMD("s x",
+ "Error: use `step \\[number\\]` to step an optional number of"
+ " instructions");
+ SETUP_CMD("p",
+ "Error: use `print <register|all>` to print the contents of a"
+ " specific register or all registers.");
+ SETUP_CMD("t 1", "Error: use `trace` to toggle tracing of registers.");
+ SETUP_CMD("g 1",
+ "Error: use `gdb` to enter GDB from the simulator debugger.");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("c", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_single) {
+ SETUP();
+
+ // Test single stepping through the whole program.
+ SETUP_CMD("step", ".*mov x2, #0x2");
+ SETUP_CMD("step", ".*sub x3, x1, x2");
+ SETUP_CMD("step", ".*ret");
+ SETUP_CMD("step",
+ ".*Debugger at the end of simulation, leaving simulator...");
+
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_single_and_continue) {
+ SETUP();
+
+ // Test single stepping and then continuing.
+ SETUP_CMD("step", ".*mov x2, #0x2");
+ SETUP_CMD("continue", "Continuing...");
+
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_multi_1) {
+ SETUP();
+
+ // Test multi stepping a single instruction.
+ SETUP_CMD("step 1", ".*mov x2, #0x2");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_multi_2) {
+ SETUP();
+
+ // Test multi stepping two instructions.
+ SETUP_CMD("step 2",
+ ".*mov x2, #0x2\n"
+ ".*sub x3, x1, x2");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_multi_3) {
+ SETUP();
+
+ // Test multi stepping three instructions.
+ SETUP_CMD("step 3",
+ ".*mov x2, #0x2\n"
+ ".*sub x3, x1, x2\n"
+ ".*ret");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_multi_4) {
+ SETUP();
+
+ // Test stepping through the whole program in one go.
+ SETUP_CMD("step 4",
+ ".*mov x2, #0x2\n"
+ ".*sub x3, x1, x2\n"
+ ".*ret\n"
+ "Debugger at the end of simulation, leaving simulator...");
+
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_multi_5) {
+ SETUP();
+
+ // Test multi stepping past the end of the program.
+ SETUP_CMD("step 5",
+ ".*mov x2, #0x2\n"
+ ".*sub x3, x1, x2\n"
+ ".*ret\n"
+ "Debugger at the end of simulation, leaving simulator...");
+
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(stepping_invalid) {
+ SETUP();
+
+ // Test invalid arguments to step command.
+ SETUP_CMD("step 1 2",
+ "Error: use `step \\[number\\]` to step an optional number of"
+ " instructions");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(print_invalid) {
+ SETUP();
+
+ // Test invalid amounts of arguments to the print command.
+ SETUP_CMD("print",
+ "Error: use `print <register|all>` to print the contents of a"
+ " specific register or all registers.");
+ SETUP_CMD("print all all",
+ "Error: use `print <register|all>` to print the contents of a"
+ " specific register or all registers.");
+
+ // Test invalid types of registers.
+ SETUP_CMD("print alls",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print a",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print x",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print 0",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+
+ // Test registers that don't exist on AARCH64.
+ SETUP_CMD("print w32",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print W32",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print x32",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print X32",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print v32",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+ SETUP_CMD("print V32",
+ "Error: incorrect register format, use e.g: X0, x0, etc...");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(trace_invalid) {
+ SETUP();
+
+ // Test invalid arguments to trace command.
+ SETUP_CMD("trace 1 2", "Error: use `trace` to toggle tracing of registers.");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(trace_toggling) {
+ SETUP();
+
+ // Test toggling tracing.
+ SETUP_CMD("trace",
+ "Enabling disassembly, registers and memory write tracing");
+ SETUP_CMD("trace",
+ "Disabling disassembly, registers and memory write tracing");
+ SETUP_CMD("trace",
+ "Enabling disassembly, registers and memory write tracing");
+ SETUP_CMD("trace",
+ "Disabling disassembly, registers and memory write tracing");
+
+ // Continue to exit the debugger.
+ SETUP_CMD("continue", "Continuing...");
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(trace_full) {
+ SETUP();
+
+ // Test tracing the whole program.
+ SETUP_CMD("trace",
+ "Enabling disassembly, registers and memory write tracing");
+
+ std::string expected_trace = "Continuing...\n";
+ expected_trace += ".*add x1, x0, #0x5 \\(5\\)\n";
+ expected_trace += "(" + x_register_trace + "\\n){32}";
+ expected_trace += "(" + v_register_trace + "\\n){32}";
+ expected_trace += ".*mov x2, #0x2\n";
+ expected_trace += x_register_trace + "\n";
+ expected_trace += ".*sub x3, x1, x2\n";
+ expected_trace += x_register_trace + "\n";
+ expected_trace += ".*ret\n";
+ expected_trace += "# Branch to 0x0000000000000000.";
+ SETUP_CMD("continue", expected_trace);
+
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+TEST(trace_partial) {
+ SETUP();
+
+ // Test tracing a single line.
+ SETUP_CMD("trace",
+ "Enabling disassembly, registers and memory write tracing");
+
+ std::string expected_trace = ".*add x1, x0, #0x5 \\(5\\)\n";
+ expected_trace += "(" + x_register_trace + "\\n){32}";
+ expected_trace += "(" + v_register_trace + "\\n){32}";
+ expected_trace += ".*mov x2, #0x2\n";
+ SETUP_CMD("step", expected_trace);
+ SETUP_CMD("trace",
+ "Disabling disassembly, registers and memory write tracing");
+ SETUP_CMD("continue", "Continuing...\n");
+
+ RUN();
+
+ CHECK_OUTPUT();
+}
+
+
+} // namespace aarch64
+} // namespace vixl
diff --git a/test/aarch64/test-debugger-aarch64.h b/test/aarch64/test-debugger-aarch64.h
new file mode 100644
index 0000000..b02d380
--- /dev/null
+++ b/test/aarch64/test-debugger-aarch64.h
@@ -0,0 +1,164 @@
+// 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.
+
+// Tests for the simulator debugger.
+
+#include <fstream>
+#include <regex>
+
+#include "test-runner.h"
+#include "test-utils.h"
+
+#include "aarch64/macro-assembler-aarch64.h"
+#include "aarch64/simulator-aarch64.h"
+#include "aarch64/test-utils-aarch64.h"
+
+namespace vixl {
+namespace aarch64 {
+
+#define __ masm->
+#define TEST(name) TEST_(AARCH64_DBG_##name)
+
+//
+// Regex for various types of printing/tracing output.
+//
+
+// Matches traced/printed general purpose register output from the simulator,
+// e.g:
+// "# x0: 0x000000000badbeef"
+const std::string x_register_trace = "#[\\s]+(x\\d{1,2}|lr|sp): 0x[0-9a-f]+";
+// Matches traced/printed vector register output from the simulator, e.g:
+// "# v0: 0x7ff0f0007f80f0017ff0f0007f80f000"
+const std::string v_register_trace = "#[\\s]+(v\\d{1,2}): 0x[0-9a-f]+";
+
+#ifdef VIXL_INCLUDE_SIMULATOR_AARCH64
+// Run tests with the simulator.
+
+// Generate some basic code which immediately breaks into the debugger.
+// This serves as a sandbox for all debugger tests to run in.
+void GenerateDebuggerAsm(MacroAssembler* masm) {
+ // Create a breakpoint here to break into the debugger.
+ __ Brk(0);
+
+ // Do some arithmetic.
+ __ Add(x1, x0, 5);
+ __ Mov(x2, 2);
+ __ Sub(x3, x1, x2);
+
+ __ Ret();
+}
+
+// Setup the test environment with the debugger assembler and simulator.
+#define SETUP() \
+ MacroAssembler masm; \
+ masm.SetCPUFeatures(CPUFeatures::None()); \
+ masm.SetGenerateSimulatorCode(true); \
+ GenerateDebuggerAsm(&masm); \
+ masm.FinalizeCode(); \
+ Instruction* start = masm.GetBuffer()->GetStartAddress<Instruction*>(); \
+ Decoder decoder; \
+ std::istringstream input_stream; \
+ char ostream_filename[] = "/tmp/vixl-test-debugger-XXXXXX"; \
+ FILE* output_stream = fdopen(mkstemp(ostream_filename), "w"); \
+ /* Disassemble the generated code so we can use the addresses later. */ \
+ PrintDisassembler disassembler(output_stream); \
+ disassembler.DisassembleBuffer(start, masm.GetSizeOfCodeGenerated()); \
+ fflush(output_stream); \
+ Simulator simulator(&decoder, output_stream); \
+ simulator.GetDebugger()->SetInputStream(&input_stream); \
+ simulator.SetColouredTrace(Test::coloured_trace()); \
+ simulator.SetCPUFeatures(CPUFeatures::None()); \
+ simulator.SetDebuggerEnabled(true); \
+ /* Setup a map so that commands and their output can be checked. */ \
+ std::unordered_map<std::string, std::string> command_map
+
+// Add a command to the input stream queue and map its expected output so that
+// it can be checked at the end of simulation.
+#define SETUP_CMD(cmd, expected_output) \
+ { \
+ std::string cmd_str(cmd); \
+ cmd_str += "\n"; \
+ std::string exp_out(expected_output); \
+ input_stream.str(input_stream.str() + cmd_str); \
+ command_map.insert({cmd_str, exp_out}); \
+ }
+
+// Run the simulator.
+#define RUN() \
+ simulator.RunFrom(start); \
+ fclose(output_stream)
+
+// Read the output file stream and check that the expected output from each
+// command is found directly following it.
+#define CHECK_OUTPUT() \
+ std::ifstream file_stream(ostream_filename); \
+ std::ostringstream ostream; \
+ ostream << file_stream.rdbuf(); \
+ for (const auto& iter : command_map) { \
+ std::string cmd = iter.first; \
+ std::string expected = iter.second; \
+ /* We assume the expected output follows the command that was issued. */ \
+ std::regex regex(cmd + expected); \
+ if (!std::regex_search(ostream.str(), regex)) { \
+ printf("output = \n\"%s\"\n", ostream.str().c_str()); \
+ /* Remove the newlines. */ \
+ cmd.erase(cmd.size() - 1, 1); \
+ std::string err = \
+ cmd + " - failed: \"" + expected + "\" not found in output "; \
+ VIXL_ABORT_WITH_MSG(err.c_str()); \
+ } \
+ } \
+ std::remove(ostream_filename)
+
+#define GET_INSTRUCTION_ADDRESS(instruction) \
+ GetInstructionAddress(ostream_filename, instruction)
+
+// Get the address of an instruction from the given filename.
+std::string GetInstructionAddress(std::string filename,
+ std::string instruction) {
+ std::ifstream file_stream(filename);
+ std::ostringstream ostream;
+ ostream << file_stream.rdbuf();
+
+ // Match the instruction string and capture the address of that instruction.
+ // Note: leading 0's are matched but not captured.
+ std::smatch sub_matches;
+ std::string str = ostream.str();
+ std::string regex_str = "(0x)0*([0-9a-f]+) [0-9a-f]+\t\t";
+ regex_str += instruction;
+ std::regex regex(regex_str);
+ if (std::regex_search(str, sub_matches, regex) && sub_matches.size() == 3) {
+ return sub_matches[1].str() + sub_matches[2].str();
+ } else {
+ std::string err = regex_str + " not found in output ";
+ VIXL_ABORT_WITH_MSG(err.c_str());
+ }
+}
+
+#endif // VIXL_INCLUDE_SIMULATOR_AARCH64
+
+} // namespace aarch64
+} // namespace vixl