1 //===-- CommandObjectDisassemble.cpp ----------------------------*- C++ -*-===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "CommandObjectDisassemble.h" 10 #include "lldb/Core/AddressRange.h" 11 #include "lldb/Core/Disassembler.h" 12 #include "lldb/Core/Module.h" 13 #include "lldb/Core/SourceManager.h" 14 #include "lldb/Host/OptionParser.h" 15 #include "lldb/Interpreter/CommandCompletions.h" 16 #include "lldb/Interpreter/CommandInterpreter.h" 17 #include "lldb/Interpreter/CommandReturnObject.h" 18 #include "lldb/Interpreter/OptionArgParser.h" 19 #include "lldb/Interpreter/Options.h" 20 #include "lldb/Symbol/Function.h" 21 #include "lldb/Symbol/Symbol.h" 22 #include "lldb/Target/Process.h" 23 #include "lldb/Target/SectionLoadList.h" 24 #include "lldb/Target/StackFrame.h" 25 #include "lldb/Target/Target.h" 26 27 #define DEFAULT_DISASM_BYTE_SIZE 32 28 #define DEFAULT_DISASM_NUM_INS 4 29 30 using namespace lldb; 31 using namespace lldb_private; 32 33 static constexpr OptionDefinition g_disassemble_options[] = { 34 // clang-format off 35 { LLDB_OPT_SET_ALL, false, "bytes", 'b', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Show opcode bytes when disassembling." }, 36 { LLDB_OPT_SET_ALL, false, "context", 'C', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeNumLines, "Number of context lines of source to show." }, 37 { LLDB_OPT_SET_ALL, false, "mixed", 'm', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Enable mixed source and assembly display." }, 38 { LLDB_OPT_SET_ALL, false, "raw", 'r', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Print raw disassembly with no symbol information." }, 39 { LLDB_OPT_SET_ALL, false, "plugin", 'P', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypePlugin, "Name of the disassembler plugin you want to use." }, 40 { LLDB_OPT_SET_ALL, false, "flavor", 'F', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeDisassemblyFlavor, "Name of the disassembly flavor you want to use. " 41 "Currently the only valid options are default, and for Intel " 42 "architectures, att and intel." }, 43 { LLDB_OPT_SET_ALL, false, "arch", 'A', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeArchitecture, "Specify the architecture to use from cross disassembly." }, 44 { LLDB_OPT_SET_1 | 45 LLDB_OPT_SET_2, true, "start-address", 's', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeAddressOrExpression, "Address at which to start disassembling." }, 46 { LLDB_OPT_SET_1, false, "end-address", 'e', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeAddressOrExpression, "Address at which to end disassembling." }, 47 { LLDB_OPT_SET_2 | 48 LLDB_OPT_SET_3 | 49 LLDB_OPT_SET_4 | 50 LLDB_OPT_SET_5, false, "count", 'c', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeNumLines, "Number of instructions to display." }, 51 { LLDB_OPT_SET_3, false, "name", 'n', OptionParser::eRequiredArgument, nullptr, {}, CommandCompletions::eSymbolCompletion, eArgTypeFunctionName, "Disassemble entire contents of the given function name." }, 52 { LLDB_OPT_SET_4, false, "frame", 'f', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Disassemble from the start of the current frame's function." }, 53 { LLDB_OPT_SET_5, false, "pc", 'p', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Disassemble around the current pc." }, 54 { LLDB_OPT_SET_6, false, "line", 'l', OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone, "Disassemble the current frame's current source line instructions if there is debug line " 55 "table information, else disassemble around the pc." }, 56 { LLDB_OPT_SET_7, false, "address", 'a', OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeAddressOrExpression, "Disassemble function containing this address." }, 57 // clang-format on 58 }; 59 60 CommandObjectDisassemble::CommandOptions::CommandOptions() 61 : Options(), num_lines_context(0), num_instructions(0), func_name(), 62 current_function(false), start_addr(), end_addr(), at_pc(false), 63 frame_line(false), plugin_name(), flavor_string(), arch(), 64 some_location_specified(false), symbol_containing_addr() { 65 OptionParsingStarting(nullptr); 66 } 67 68 CommandObjectDisassemble::CommandOptions::~CommandOptions() = default; 69 70 Status CommandObjectDisassemble::CommandOptions::SetOptionValue( 71 uint32_t option_idx, llvm::StringRef option_arg, 72 ExecutionContext *execution_context) { 73 Status error; 74 75 const int short_option = m_getopt_table[option_idx].val; 76 77 switch (short_option) { 78 case 'm': 79 show_mixed = true; 80 break; 81 82 case 'C': 83 if (option_arg.getAsInteger(0, num_lines_context)) 84 error.SetErrorStringWithFormat("invalid num context lines string: \"%s\"", 85 option_arg.str().c_str()); 86 break; 87 88 case 'c': 89 if (option_arg.getAsInteger(0, num_instructions)) 90 error.SetErrorStringWithFormat( 91 "invalid num of instructions string: \"%s\"", 92 option_arg.str().c_str()); 93 break; 94 95 case 'b': 96 show_bytes = true; 97 break; 98 99 case 's': { 100 start_addr = OptionArgParser::ToAddress(execution_context, option_arg, 101 LLDB_INVALID_ADDRESS, &error); 102 if (start_addr != LLDB_INVALID_ADDRESS) 103 some_location_specified = true; 104 } break; 105 case 'e': { 106 end_addr = OptionArgParser::ToAddress(execution_context, option_arg, 107 LLDB_INVALID_ADDRESS, &error); 108 if (end_addr != LLDB_INVALID_ADDRESS) 109 some_location_specified = true; 110 } break; 111 112 case 'n': 113 func_name.assign(option_arg); 114 some_location_specified = true; 115 break; 116 117 case 'p': 118 at_pc = true; 119 some_location_specified = true; 120 break; 121 122 case 'l': 123 frame_line = true; 124 // Disassemble the current source line kind of implies showing mixed source 125 // code context. 126 show_mixed = true; 127 some_location_specified = true; 128 break; 129 130 case 'P': 131 plugin_name.assign(option_arg); 132 break; 133 134 case 'F': { 135 TargetSP target_sp = 136 execution_context ? execution_context->GetTargetSP() : TargetSP(); 137 if (target_sp && (target_sp->GetArchitecture().GetTriple().getArch() == 138 llvm::Triple::x86 || 139 target_sp->GetArchitecture().GetTriple().getArch() == 140 llvm::Triple::x86_64)) { 141 flavor_string.assign(option_arg); 142 } else 143 error.SetErrorStringWithFormat("Disassembler flavors are currently only " 144 "supported for x86 and x86_64 targets."); 145 break; 146 } 147 148 case 'r': 149 raw = true; 150 break; 151 152 case 'f': 153 current_function = true; 154 some_location_specified = true; 155 break; 156 157 case 'A': 158 if (execution_context) { 159 const auto &target_sp = execution_context->GetTargetSP(); 160 auto platform_ptr = target_sp ? target_sp->GetPlatform().get() : nullptr; 161 arch = Platform::GetAugmentedArchSpec(platform_ptr, option_arg); 162 } 163 break; 164 165 case 'a': { 166 symbol_containing_addr = OptionArgParser::ToAddress( 167 execution_context, option_arg, LLDB_INVALID_ADDRESS, &error); 168 if (symbol_containing_addr != LLDB_INVALID_ADDRESS) { 169 some_location_specified = true; 170 } 171 } break; 172 173 default: 174 error.SetErrorStringWithFormat("unrecognized short option '%c'", 175 short_option); 176 break; 177 } 178 179 return error; 180 } 181 182 void CommandObjectDisassemble::CommandOptions::OptionParsingStarting( 183 ExecutionContext *execution_context) { 184 show_mixed = false; 185 show_bytes = false; 186 num_lines_context = 0; 187 num_instructions = 0; 188 func_name.clear(); 189 current_function = false; 190 at_pc = false; 191 frame_line = false; 192 start_addr = LLDB_INVALID_ADDRESS; 193 end_addr = LLDB_INVALID_ADDRESS; 194 symbol_containing_addr = LLDB_INVALID_ADDRESS; 195 raw = false; 196 plugin_name.clear(); 197 198 Target *target = 199 execution_context ? execution_context->GetTargetPtr() : nullptr; 200 201 // This is a hack till we get the ability to specify features based on 202 // architecture. For now GetDisassemblyFlavor is really only valid for x86 203 // (and for the llvm assembler plugin, but I'm papering over that since that 204 // is the only disassembler plugin we have... 205 if (target) { 206 if (target->GetArchitecture().GetTriple().getArch() == llvm::Triple::x86 || 207 target->GetArchitecture().GetTriple().getArch() == 208 llvm::Triple::x86_64) { 209 flavor_string.assign(target->GetDisassemblyFlavor()); 210 } else 211 flavor_string.assign("default"); 212 213 } else 214 flavor_string.assign("default"); 215 216 arch.Clear(); 217 some_location_specified = false; 218 } 219 220 Status CommandObjectDisassemble::CommandOptions::OptionParsingFinished( 221 ExecutionContext *execution_context) { 222 if (!some_location_specified) 223 current_function = true; 224 return Status(); 225 } 226 227 llvm::ArrayRef<OptionDefinition> 228 CommandObjectDisassemble::CommandOptions::GetDefinitions() { 229 return llvm::makeArrayRef(g_disassemble_options); 230 } 231 232 // CommandObjectDisassemble 233 234 CommandObjectDisassemble::CommandObjectDisassemble( 235 CommandInterpreter &interpreter) 236 : CommandObjectParsed( 237 interpreter, "disassemble", 238 "Disassemble specified instructions in the current target. " 239 "Defaults to the current function for the current thread and " 240 "stack frame.", 241 "disassemble [<cmd-options>]"), 242 m_options() {} 243 244 CommandObjectDisassemble::~CommandObjectDisassemble() = default; 245 246 bool CommandObjectDisassemble::DoExecute(Args &command, 247 CommandReturnObject &result) { 248 Target *target = GetDebugger().GetSelectedTarget().get(); 249 if (target == nullptr) { 250 result.AppendError("invalid target, create a debug target using the " 251 "'target create' command"); 252 result.SetStatus(eReturnStatusFailed); 253 return false; 254 } 255 if (!m_options.arch.IsValid()) 256 m_options.arch = target->GetArchitecture(); 257 258 if (!m_options.arch.IsValid()) { 259 result.AppendError( 260 "use the --arch option or set the target architecture to disassemble"); 261 result.SetStatus(eReturnStatusFailed); 262 return false; 263 } 264 265 const char *plugin_name = m_options.GetPluginName(); 266 const char *flavor_string = m_options.GetFlavorString(); 267 268 DisassemblerSP disassembler = 269 Disassembler::FindPlugin(m_options.arch, flavor_string, plugin_name); 270 271 if (!disassembler) { 272 if (plugin_name) { 273 result.AppendErrorWithFormat( 274 "Unable to find Disassembler plug-in named '%s' that supports the " 275 "'%s' architecture.\n", 276 plugin_name, m_options.arch.GetArchitectureName()); 277 } else 278 result.AppendErrorWithFormat( 279 "Unable to find Disassembler plug-in for the '%s' architecture.\n", 280 m_options.arch.GetArchitectureName()); 281 result.SetStatus(eReturnStatusFailed); 282 return false; 283 } else if (flavor_string != nullptr && 284 !disassembler->FlavorValidForArchSpec(m_options.arch, 285 flavor_string)) 286 result.AppendWarningWithFormat( 287 "invalid disassembler flavor \"%s\", using default.\n", flavor_string); 288 289 result.SetStatus(eReturnStatusSuccessFinishResult); 290 291 if (!command.empty()) { 292 result.AppendErrorWithFormat( 293 "\"disassemble\" arguments are specified as options.\n"); 294 const int terminal_width = 295 GetCommandInterpreter().GetDebugger().GetTerminalWidth(); 296 GetOptions()->GenerateOptionUsage(result.GetErrorStream(), this, 297 terminal_width); 298 result.SetStatus(eReturnStatusFailed); 299 return false; 300 } 301 302 if (m_options.show_mixed && m_options.num_lines_context == 0) 303 m_options.num_lines_context = 2; 304 305 // Always show the PC in the disassembly 306 uint32_t options = Disassembler::eOptionMarkPCAddress; 307 308 // Mark the source line for the current PC only if we are doing mixed source 309 // and assembly 310 if (m_options.show_mixed) 311 options |= Disassembler::eOptionMarkPCSourceLine; 312 313 if (m_options.show_bytes) 314 options |= Disassembler::eOptionShowBytes; 315 316 if (m_options.raw) 317 options |= Disassembler::eOptionRawOuput; 318 319 if (!m_options.func_name.empty()) { 320 ConstString name(m_options.func_name.c_str()); 321 322 if (Disassembler::Disassemble( 323 GetDebugger(), m_options.arch, plugin_name, flavor_string, 324 m_exe_ctx, name, 325 nullptr, // Module * 326 m_options.num_instructions, m_options.show_mixed, 327 m_options.show_mixed ? m_options.num_lines_context : 0, options, 328 result.GetOutputStream())) { 329 result.SetStatus(eReturnStatusSuccessFinishResult); 330 } else { 331 result.AppendErrorWithFormat("Unable to find symbol with name '%s'.\n", 332 name.GetCString()); 333 result.SetStatus(eReturnStatusFailed); 334 } 335 } else { 336 std::vector<AddressRange> ranges; 337 AddressRange range; 338 StackFrame *frame = m_exe_ctx.GetFramePtr(); 339 if (m_options.frame_line) { 340 if (frame == nullptr) { 341 result.AppendError("Cannot disassemble around the current line without " 342 "a selected frame.\n"); 343 result.SetStatus(eReturnStatusFailed); 344 return false; 345 } 346 LineEntry pc_line_entry( 347 frame->GetSymbolContext(eSymbolContextLineEntry).line_entry); 348 if (pc_line_entry.IsValid()) { 349 range = pc_line_entry.range; 350 } else { 351 m_options.at_pc = 352 true; // No line entry, so just disassemble around the current pc 353 m_options.show_mixed = false; 354 } 355 } else if (m_options.current_function) { 356 if (frame == nullptr) { 357 result.AppendError("Cannot disassemble around the current function " 358 "without a selected frame.\n"); 359 result.SetStatus(eReturnStatusFailed); 360 return false; 361 } 362 Symbol *symbol = frame->GetSymbolContext(eSymbolContextSymbol).symbol; 363 if (symbol) { 364 range.GetBaseAddress() = symbol->GetAddress(); 365 range.SetByteSize(symbol->GetByteSize()); 366 } 367 } 368 369 // Did the "m_options.frame_line" find a valid range already? If so skip 370 // the rest... 371 if (range.GetByteSize() == 0) { 372 if (m_options.at_pc) { 373 if (frame == nullptr) { 374 result.AppendError("Cannot disassemble around the current PC without " 375 "a selected frame.\n"); 376 result.SetStatus(eReturnStatusFailed); 377 return false; 378 } 379 range.GetBaseAddress() = frame->GetFrameCodeAddress(); 380 if (m_options.num_instructions == 0) { 381 // Disassembling at the PC always disassembles some number of 382 // instructions (not the whole function). 383 m_options.num_instructions = DEFAULT_DISASM_NUM_INS; 384 } 385 ranges.push_back(range); 386 } else { 387 range.GetBaseAddress().SetOffset(m_options.start_addr); 388 if (range.GetBaseAddress().IsValid()) { 389 if (m_options.end_addr != LLDB_INVALID_ADDRESS) { 390 if (m_options.end_addr <= m_options.start_addr) { 391 result.AppendErrorWithFormat( 392 "End address before start address.\n"); 393 result.SetStatus(eReturnStatusFailed); 394 return false; 395 } 396 range.SetByteSize(m_options.end_addr - m_options.start_addr); 397 } 398 ranges.push_back(range); 399 } else { 400 if (m_options.symbol_containing_addr != LLDB_INVALID_ADDRESS && 401 target) { 402 if (!target->GetSectionLoadList().IsEmpty()) { 403 bool failed = false; 404 Address symbol_containing_address; 405 if (target->GetSectionLoadList().ResolveLoadAddress( 406 m_options.symbol_containing_addr, 407 symbol_containing_address)) { 408 ModuleSP module_sp(symbol_containing_address.GetModule()); 409 SymbolContext sc; 410 bool resolve_tail_call_address = true; // PC can be one past the 411 // address range of the 412 // function. 413 module_sp->ResolveSymbolContextForAddress( 414 symbol_containing_address, eSymbolContextEverything, sc, 415 resolve_tail_call_address); 416 if (sc.function || sc.symbol) { 417 sc.GetAddressRange(eSymbolContextFunction | 418 eSymbolContextSymbol, 419 0, false, range); 420 } else { 421 failed = true; 422 } 423 } else { 424 failed = true; 425 } 426 if (failed) { 427 result.AppendErrorWithFormat( 428 "Could not find function bounds for address 0x%" PRIx64 429 "\n", 430 m_options.symbol_containing_addr); 431 result.SetStatus(eReturnStatusFailed); 432 return false; 433 } 434 ranges.push_back(range); 435 } else { 436 for (lldb::ModuleSP module_sp : target->GetImages().Modules()) { 437 lldb::addr_t file_addr = m_options.symbol_containing_addr; 438 Address file_address; 439 if (module_sp->ResolveFileAddress(file_addr, file_address)) { 440 SymbolContext sc; 441 bool resolve_tail_call_address = true; // PC can be one past 442 // the address range of 443 // the function. 444 module_sp->ResolveSymbolContextForAddress( 445 file_address, eSymbolContextEverything, sc, 446 resolve_tail_call_address); 447 if (sc.function || sc.symbol) { 448 sc.GetAddressRange(eSymbolContextFunction | 449 eSymbolContextSymbol, 450 0, false, range); 451 ranges.push_back(range); 452 } 453 } 454 } 455 } 456 } 457 } 458 } 459 } else 460 ranges.push_back(range); 461 462 if (m_options.num_instructions != 0) { 463 if (ranges.empty()) { 464 // The default action is to disassemble the current frame function. 465 if (frame) { 466 SymbolContext sc(frame->GetSymbolContext(eSymbolContextFunction | 467 eSymbolContextSymbol)); 468 if (sc.function) 469 range.GetBaseAddress() = 470 sc.function->GetAddressRange().GetBaseAddress(); 471 else if (sc.symbol && sc.symbol->ValueIsAddress()) 472 range.GetBaseAddress() = sc.symbol->GetAddress(); 473 else 474 range.GetBaseAddress() = frame->GetFrameCodeAddress(); 475 } 476 477 if (!range.GetBaseAddress().IsValid()) { 478 result.AppendError("invalid frame"); 479 result.SetStatus(eReturnStatusFailed); 480 return false; 481 } 482 } 483 484 bool print_sc_header = ranges.size() > 1; 485 for (AddressRange cur_range : ranges) { 486 if (Disassembler::Disassemble( 487 GetDebugger(), m_options.arch, plugin_name, flavor_string, 488 m_exe_ctx, cur_range.GetBaseAddress(), 489 m_options.num_instructions, m_options.show_mixed, 490 m_options.show_mixed ? m_options.num_lines_context : 0, options, 491 result.GetOutputStream())) { 492 result.SetStatus(eReturnStatusSuccessFinishResult); 493 } else { 494 if (m_options.start_addr != LLDB_INVALID_ADDRESS) 495 result.AppendErrorWithFormat( 496 "Failed to disassemble memory at 0x%8.8" PRIx64 ".\n", 497 m_options.start_addr); 498 else if (m_options.symbol_containing_addr != LLDB_INVALID_ADDRESS) 499 result.AppendErrorWithFormat( 500 "Failed to disassemble memory in function at 0x%8.8" PRIx64 501 ".\n", 502 m_options.symbol_containing_addr); 503 result.SetStatus(eReturnStatusFailed); 504 } 505 } 506 if (print_sc_header) 507 result.AppendMessage("\n"); 508 } else { 509 if (ranges.empty()) { 510 // The default action is to disassemble the current frame function. 511 if (frame) { 512 SymbolContext sc(frame->GetSymbolContext(eSymbolContextFunction | 513 eSymbolContextSymbol)); 514 if (sc.function) 515 range = sc.function->GetAddressRange(); 516 else if (sc.symbol && sc.symbol->ValueIsAddress()) { 517 range.GetBaseAddress() = sc.symbol->GetAddress(); 518 range.SetByteSize(sc.symbol->GetByteSize()); 519 } else 520 range.GetBaseAddress() = frame->GetFrameCodeAddress(); 521 } else { 522 result.AppendError("invalid frame"); 523 result.SetStatus(eReturnStatusFailed); 524 return false; 525 } 526 ranges.push_back(range); 527 } 528 529 bool print_sc_header = ranges.size() > 1; 530 for (AddressRange cur_range : ranges) { 531 if (cur_range.GetByteSize() == 0) 532 cur_range.SetByteSize(DEFAULT_DISASM_BYTE_SIZE); 533 534 if (Disassembler::Disassemble( 535 GetDebugger(), m_options.arch, plugin_name, flavor_string, 536 m_exe_ctx, cur_range, m_options.num_instructions, 537 m_options.show_mixed, 538 m_options.show_mixed ? m_options.num_lines_context : 0, options, 539 result.GetOutputStream())) { 540 result.SetStatus(eReturnStatusSuccessFinishResult); 541 } else { 542 result.AppendErrorWithFormat( 543 "Failed to disassemble memory at 0x%8.8" PRIx64 ".\n", 544 m_options.start_addr); 545 result.SetStatus(eReturnStatusFailed); 546 } 547 if (print_sc_header) 548 result.AppendMessage("\n"); 549 } 550 } 551 } 552 553 return result.Succeeded(); 554 } 555