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