duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

debugger-aarch64.cc (14871B)


      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