1 | //===-- CommandObjectDisassemble.cpp --------------------------------------===// |
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/Host/OptionParser.h" |
14 | #include "lldb/Interpreter/CommandInterpreter.h" |
15 | #include "lldb/Interpreter/CommandOptionArgumentTable.h" |
16 | #include "lldb/Interpreter/CommandReturnObject.h" |
17 | #include "lldb/Interpreter/OptionArgParser.h" |
18 | #include "lldb/Interpreter/Options.h" |
19 | #include "lldb/Symbol/Function.h" |
20 | #include "lldb/Symbol/Symbol.h" |
21 | #include "lldb/Target/SectionLoadList.h" |
22 | #include "lldb/Target/StackFrame.h" |
23 | #include "lldb/Target/Target.h" |
24 | |
25 | static constexpr unsigned default_disasm_byte_size = 32; |
26 | static constexpr unsigned default_disasm_num_ins = 4; |
27 | |
28 | using namespace lldb; |
29 | using namespace lldb_private; |
30 | |
31 | #define LLDB_OPTIONS_disassemble |
32 | #include "CommandOptions.inc" |
33 | |
34 | CommandObjectDisassemble::CommandOptions::CommandOptions() { |
35 | OptionParsingStarting(execution_context: nullptr); |
36 | } |
37 | |
38 | CommandObjectDisassemble::CommandOptions::~CommandOptions() = default; |
39 | |
40 | Status CommandObjectDisassemble::CommandOptions::SetOptionValue( |
41 | uint32_t option_idx, llvm::StringRef option_arg, |
42 | ExecutionContext *execution_context) { |
43 | Status error; |
44 | |
45 | const int short_option = m_getopt_table[option_idx].val; |
46 | |
47 | switch (short_option) { |
48 | case 'm': |
49 | show_mixed = true; |
50 | break; |
51 | |
52 | case 'C': |
53 | if (option_arg.getAsInteger(Radix: 0, Result&: num_lines_context)) |
54 | error.SetErrorStringWithFormat("invalid num context lines string: \"%s\"" , |
55 | option_arg.str().c_str()); |
56 | break; |
57 | |
58 | case 'c': |
59 | if (option_arg.getAsInteger(Radix: 0, Result&: num_instructions)) |
60 | error.SetErrorStringWithFormat( |
61 | "invalid num of instructions string: \"%s\"" , |
62 | option_arg.str().c_str()); |
63 | break; |
64 | |
65 | case 'b': |
66 | show_bytes = true; |
67 | break; |
68 | |
69 | case 'k': |
70 | show_control_flow_kind = true; |
71 | break; |
72 | |
73 | case 's': { |
74 | start_addr = OptionArgParser::ToAddress(exe_ctx: execution_context, s: option_arg, |
75 | LLDB_INVALID_ADDRESS, error_ptr: &error); |
76 | if (start_addr != LLDB_INVALID_ADDRESS) |
77 | some_location_specified = true; |
78 | } break; |
79 | case 'e': { |
80 | end_addr = OptionArgParser::ToAddress(exe_ctx: execution_context, s: option_arg, |
81 | LLDB_INVALID_ADDRESS, error_ptr: &error); |
82 | if (end_addr != LLDB_INVALID_ADDRESS) |
83 | some_location_specified = true; |
84 | } break; |
85 | |
86 | case 'n': |
87 | func_name.assign(str: std::string(option_arg)); |
88 | some_location_specified = true; |
89 | break; |
90 | |
91 | case 'p': |
92 | at_pc = true; |
93 | some_location_specified = true; |
94 | break; |
95 | |
96 | case 'l': |
97 | frame_line = true; |
98 | // Disassemble the current source line kind of implies showing mixed source |
99 | // code context. |
100 | show_mixed = true; |
101 | some_location_specified = true; |
102 | break; |
103 | |
104 | case 'P': |
105 | plugin_name.assign(str: std::string(option_arg)); |
106 | break; |
107 | |
108 | case 'F': { |
109 | TargetSP target_sp = |
110 | execution_context ? execution_context->GetTargetSP() : TargetSP(); |
111 | if (target_sp && (target_sp->GetArchitecture().GetTriple().getArch() == |
112 | llvm::Triple::x86 || |
113 | target_sp->GetArchitecture().GetTriple().getArch() == |
114 | llvm::Triple::x86_64)) { |
115 | flavor_string.assign(str: std::string(option_arg)); |
116 | } else |
117 | error.SetErrorStringWithFormat("Disassembler flavors are currently only " |
118 | "supported for x86 and x86_64 targets." ); |
119 | break; |
120 | } |
121 | |
122 | case 'r': |
123 | raw = true; |
124 | break; |
125 | |
126 | case 'f': |
127 | current_function = true; |
128 | some_location_specified = true; |
129 | break; |
130 | |
131 | case 'A': |
132 | if (execution_context) { |
133 | const auto &target_sp = execution_context->GetTargetSP(); |
134 | auto platform_ptr = target_sp ? target_sp->GetPlatform().get() : nullptr; |
135 | arch = Platform::GetAugmentedArchSpec(platform: platform_ptr, triple: option_arg); |
136 | } |
137 | break; |
138 | |
139 | case 'a': { |
140 | symbol_containing_addr = OptionArgParser::ToAddress( |
141 | exe_ctx: execution_context, s: option_arg, LLDB_INVALID_ADDRESS, error_ptr: &error); |
142 | if (symbol_containing_addr != LLDB_INVALID_ADDRESS) { |
143 | some_location_specified = true; |
144 | } |
145 | } break; |
146 | |
147 | case '\x01': |
148 | force = true; |
149 | break; |
150 | |
151 | default: |
152 | llvm_unreachable("Unimplemented option" ); |
153 | } |
154 | |
155 | return error; |
156 | } |
157 | |
158 | void CommandObjectDisassemble::CommandOptions::OptionParsingStarting( |
159 | ExecutionContext *execution_context) { |
160 | show_mixed = false; |
161 | show_bytes = false; |
162 | show_control_flow_kind = false; |
163 | num_lines_context = 0; |
164 | num_instructions = 0; |
165 | func_name.clear(); |
166 | current_function = false; |
167 | at_pc = false; |
168 | frame_line = false; |
169 | start_addr = LLDB_INVALID_ADDRESS; |
170 | end_addr = LLDB_INVALID_ADDRESS; |
171 | symbol_containing_addr = LLDB_INVALID_ADDRESS; |
172 | raw = false; |
173 | plugin_name.clear(); |
174 | |
175 | Target *target = |
176 | execution_context ? execution_context->GetTargetPtr() : nullptr; |
177 | |
178 | // This is a hack till we get the ability to specify features based on |
179 | // architecture. For now GetDisassemblyFlavor is really only valid for x86 |
180 | // (and for the llvm assembler plugin, but I'm papering over that since that |
181 | // is the only disassembler plugin we have... |
182 | if (target) { |
183 | if (target->GetArchitecture().GetTriple().getArch() == llvm::Triple::x86 || |
184 | target->GetArchitecture().GetTriple().getArch() == |
185 | llvm::Triple::x86_64) { |
186 | flavor_string.assign(s: target->GetDisassemblyFlavor()); |
187 | } else |
188 | flavor_string.assign(s: "default" ); |
189 | |
190 | } else |
191 | flavor_string.assign(s: "default" ); |
192 | |
193 | arch.Clear(); |
194 | some_location_specified = false; |
195 | force = false; |
196 | } |
197 | |
198 | Status CommandObjectDisassemble::CommandOptions::OptionParsingFinished( |
199 | ExecutionContext *execution_context) { |
200 | if (!some_location_specified) |
201 | current_function = true; |
202 | return Status(); |
203 | } |
204 | |
205 | llvm::ArrayRef<OptionDefinition> |
206 | CommandObjectDisassemble::CommandOptions::GetDefinitions() { |
207 | return llvm::ArrayRef(g_disassemble_options); |
208 | } |
209 | |
210 | // CommandObjectDisassemble |
211 | |
212 | CommandObjectDisassemble::CommandObjectDisassemble( |
213 | CommandInterpreter &interpreter) |
214 | : CommandObjectParsed( |
215 | interpreter, "disassemble" , |
216 | "Disassemble specified instructions in the current target. " |
217 | "Defaults to the current function for the current thread and " |
218 | "stack frame." , |
219 | "disassemble [<cmd-options>]" , eCommandRequiresTarget) {} |
220 | |
221 | CommandObjectDisassemble::~CommandObjectDisassemble() = default; |
222 | |
223 | llvm::Error CommandObjectDisassemble::CheckRangeSize(const AddressRange &range, |
224 | llvm::StringRef what) { |
225 | if (m_options.num_instructions > 0 || m_options.force || |
226 | range.GetByteSize() < GetDebugger().GetStopDisassemblyMaxSize()) |
227 | return llvm::Error::success(); |
228 | StreamString msg; |
229 | msg << "Not disassembling " << what << " because it is very large " ; |
230 | range.Dump(s: &msg, target: &GetSelectedTarget(), style: Address::DumpStyleLoadAddress, |
231 | fallback_style: Address::DumpStyleFileAddress); |
232 | msg << ". To disassemble specify an instruction count limit, start/stop " |
233 | "addresses or use the --force option." ; |
234 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
235 | S: msg.GetString()); |
236 | } |
237 | |
238 | llvm::Expected<std::vector<AddressRange>> |
239 | CommandObjectDisassemble::GetContainingAddressRanges() { |
240 | std::vector<AddressRange> ranges; |
241 | const auto &get_range = [&](Address addr) { |
242 | ModuleSP module_sp(addr.GetModule()); |
243 | SymbolContext sc; |
244 | bool resolve_tail_call_address = true; |
245 | addr.GetModule()->ResolveSymbolContextForAddress( |
246 | so_addr: addr, resolve_scope: eSymbolContextEverything, sc, resolve_tail_call_address); |
247 | if (sc.function || sc.symbol) { |
248 | AddressRange range; |
249 | sc.GetAddressRange(scope: eSymbolContextFunction | eSymbolContextSymbol, range_idx: 0, |
250 | use_inline_block_range: false, range); |
251 | ranges.push_back(x: range); |
252 | } |
253 | }; |
254 | |
255 | Target &target = GetSelectedTarget(); |
256 | if (!target.GetSectionLoadList().IsEmpty()) { |
257 | Address symbol_containing_address; |
258 | if (target.GetSectionLoadList().ResolveLoadAddress( |
259 | load_addr: m_options.symbol_containing_addr, so_addr&: symbol_containing_address)) { |
260 | get_range(symbol_containing_address); |
261 | } |
262 | } else { |
263 | for (lldb::ModuleSP module_sp : target.GetImages().Modules()) { |
264 | Address file_address; |
265 | if (module_sp->ResolveFileAddress(vm_addr: m_options.symbol_containing_addr, |
266 | so_addr&: file_address)) { |
267 | get_range(file_address); |
268 | } |
269 | } |
270 | } |
271 | |
272 | if (ranges.empty()) { |
273 | return llvm::createStringError( |
274 | EC: llvm::inconvertibleErrorCode(), |
275 | Fmt: "Could not find function bounds for address 0x%" PRIx64, |
276 | Vals: m_options.symbol_containing_addr); |
277 | } |
278 | |
279 | if (llvm::Error err = CheckRangeSize(range: ranges[0], what: "the function" )) |
280 | return std::move(err); |
281 | return ranges; |
282 | } |
283 | |
284 | llvm::Expected<std::vector<AddressRange>> |
285 | CommandObjectDisassemble::GetCurrentFunctionRanges() { |
286 | Process *process = m_exe_ctx.GetProcessPtr(); |
287 | StackFrame *frame = m_exe_ctx.GetFramePtr(); |
288 | if (!frame) { |
289 | if (process) { |
290 | return llvm::createStringError( |
291 | EC: llvm::inconvertibleErrorCode(), |
292 | Msg: "Cannot disassemble around the current " |
293 | "function without the process being stopped.\n" ); |
294 | } else { |
295 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
296 | Msg: "Cannot disassemble around the current " |
297 | "function without a selected frame: " |
298 | "no currently running process.\n" ); |
299 | } |
300 | } |
301 | SymbolContext sc( |
302 | frame->GetSymbolContext(resolve_scope: eSymbolContextFunction | eSymbolContextSymbol)); |
303 | AddressRange range; |
304 | if (sc.function) |
305 | range = sc.function->GetAddressRange(); |
306 | else if (sc.symbol && sc.symbol->ValueIsAddress()) { |
307 | range = {sc.symbol->GetAddress(), sc.symbol->GetByteSize()}; |
308 | } else |
309 | range = {frame->GetFrameCodeAddress(), default_disasm_byte_size}; |
310 | |
311 | if (llvm::Error err = CheckRangeSize(range, what: "the current function" )) |
312 | return std::move(err); |
313 | return std::vector<AddressRange>{range}; |
314 | } |
315 | |
316 | llvm::Expected<std::vector<AddressRange>> |
317 | CommandObjectDisassemble::GetCurrentLineRanges() { |
318 | Process *process = m_exe_ctx.GetProcessPtr(); |
319 | StackFrame *frame = m_exe_ctx.GetFramePtr(); |
320 | if (!frame) { |
321 | if (process) { |
322 | return llvm::createStringError( |
323 | EC: llvm::inconvertibleErrorCode(), |
324 | Msg: "Cannot disassemble around the current " |
325 | "function without the process being stopped.\n" ); |
326 | } else { |
327 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
328 | Msg: "Cannot disassemble around the current " |
329 | "line without a selected frame: " |
330 | "no currently running process.\n" ); |
331 | } |
332 | } |
333 | |
334 | LineEntry pc_line_entry( |
335 | frame->GetSymbolContext(resolve_scope: eSymbolContextLineEntry).line_entry); |
336 | if (pc_line_entry.IsValid()) |
337 | return std::vector<AddressRange>{pc_line_entry.range}; |
338 | |
339 | // No line entry, so just disassemble around the current pc |
340 | m_options.show_mixed = false; |
341 | return GetPCRanges(); |
342 | } |
343 | |
344 | llvm::Expected<std::vector<AddressRange>> |
345 | CommandObjectDisassemble::GetNameRanges(CommandReturnObject &result) { |
346 | ConstString name(m_options.func_name.c_str()); |
347 | |
348 | ModuleFunctionSearchOptions function_options; |
349 | function_options.include_symbols = true; |
350 | function_options.include_inlines = true; |
351 | |
352 | // Find functions matching the given name. |
353 | SymbolContextList sc_list; |
354 | GetSelectedTarget().GetImages().FindFunctions(name, name_type_mask: eFunctionNameTypeAuto, |
355 | options: function_options, sc_list); |
356 | |
357 | std::vector<AddressRange> ranges; |
358 | llvm::Error range_errs = llvm::Error::success(); |
359 | AddressRange range; |
360 | const uint32_t scope = |
361 | eSymbolContextBlock | eSymbolContextFunction | eSymbolContextSymbol; |
362 | const bool use_inline_block_range = true; |
363 | for (SymbolContext sc : sc_list.SymbolContexts()) { |
364 | for (uint32_t range_idx = 0; |
365 | sc.GetAddressRange(scope, range_idx, use_inline_block_range, range); |
366 | ++range_idx) { |
367 | if (llvm::Error err = CheckRangeSize(range, what: "a range" )) |
368 | range_errs = joinErrors(E1: std::move(range_errs), E2: std::move(err)); |
369 | else |
370 | ranges.push_back(x: range); |
371 | } |
372 | } |
373 | if (ranges.empty()) { |
374 | if (range_errs) |
375 | return std::move(range_errs); |
376 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
377 | Fmt: "Unable to find symbol with name '%s'.\n" , |
378 | Vals: name.GetCString()); |
379 | } |
380 | if (range_errs) |
381 | result.AppendWarning(in_string: toString(E: std::move(range_errs))); |
382 | return ranges; |
383 | } |
384 | |
385 | llvm::Expected<std::vector<AddressRange>> |
386 | CommandObjectDisassemble::GetPCRanges() { |
387 | Process *process = m_exe_ctx.GetProcessPtr(); |
388 | StackFrame *frame = m_exe_ctx.GetFramePtr(); |
389 | if (!frame) { |
390 | if (process) { |
391 | return llvm::createStringError( |
392 | EC: llvm::inconvertibleErrorCode(), |
393 | Msg: "Cannot disassemble around the current " |
394 | "function without the process being stopped.\n" ); |
395 | } else { |
396 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
397 | Msg: "Cannot disassemble around the current " |
398 | "PC without a selected frame: " |
399 | "no currently running process.\n" ); |
400 | } |
401 | } |
402 | |
403 | if (m_options.num_instructions == 0) { |
404 | // Disassembling at the PC always disassembles some number of |
405 | // instructions (not the whole function). |
406 | m_options.num_instructions = default_disasm_num_ins; |
407 | } |
408 | return std::vector<AddressRange>{{frame->GetFrameCodeAddress(), 0}}; |
409 | } |
410 | |
411 | llvm::Expected<std::vector<AddressRange>> |
412 | CommandObjectDisassemble::GetStartEndAddressRanges() { |
413 | addr_t size = 0; |
414 | if (m_options.end_addr != LLDB_INVALID_ADDRESS) { |
415 | if (m_options.end_addr <= m_options.start_addr) { |
416 | return llvm::createStringError(EC: llvm::inconvertibleErrorCode(), |
417 | Msg: "End address before start address." ); |
418 | } |
419 | size = m_options.end_addr - m_options.start_addr; |
420 | } |
421 | return std::vector<AddressRange>{{Address(m_options.start_addr), size}}; |
422 | } |
423 | |
424 | llvm::Expected<std::vector<AddressRange>> |
425 | CommandObjectDisassemble::GetRangesForSelectedMode( |
426 | CommandReturnObject &result) { |
427 | if (m_options.symbol_containing_addr != LLDB_INVALID_ADDRESS) |
428 | return CommandObjectDisassemble::GetContainingAddressRanges(); |
429 | if (m_options.current_function) |
430 | return CommandObjectDisassemble::GetCurrentFunctionRanges(); |
431 | if (m_options.frame_line) |
432 | return CommandObjectDisassemble::GetCurrentLineRanges(); |
433 | if (!m_options.func_name.empty()) |
434 | return CommandObjectDisassemble::GetNameRanges(result); |
435 | if (m_options.start_addr != LLDB_INVALID_ADDRESS) |
436 | return CommandObjectDisassemble::GetStartEndAddressRanges(); |
437 | return CommandObjectDisassemble::GetPCRanges(); |
438 | } |
439 | |
440 | void CommandObjectDisassemble::DoExecute(Args &command, |
441 | CommandReturnObject &result) { |
442 | Target *target = &GetSelectedTarget(); |
443 | |
444 | if (!m_options.arch.IsValid()) |
445 | m_options.arch = target->GetArchitecture(); |
446 | |
447 | if (!m_options.arch.IsValid()) { |
448 | result.AppendError( |
449 | in_string: "use the --arch option or set the target architecture to disassemble" ); |
450 | return; |
451 | } |
452 | |
453 | const char *plugin_name = m_options.GetPluginName(); |
454 | const char *flavor_string = m_options.GetFlavorString(); |
455 | |
456 | DisassemblerSP disassembler = |
457 | Disassembler::FindPlugin(arch: m_options.arch, flavor: flavor_string, plugin_name); |
458 | |
459 | if (!disassembler) { |
460 | if (plugin_name) { |
461 | result.AppendErrorWithFormat( |
462 | format: "Unable to find Disassembler plug-in named '%s' that supports the " |
463 | "'%s' architecture.\n" , |
464 | plugin_name, m_options.arch.GetArchitectureName()); |
465 | } else |
466 | result.AppendErrorWithFormat( |
467 | format: "Unable to find Disassembler plug-in for the '%s' architecture.\n" , |
468 | m_options.arch.GetArchitectureName()); |
469 | return; |
470 | } else if (flavor_string != nullptr && !disassembler->FlavorValidForArchSpec( |
471 | arch: m_options.arch, flavor: flavor_string)) |
472 | result.AppendWarningWithFormat( |
473 | format: "invalid disassembler flavor \"%s\", using default.\n" , flavor_string); |
474 | |
475 | result.SetStatus(eReturnStatusSuccessFinishResult); |
476 | |
477 | if (!command.empty()) { |
478 | result.AppendErrorWithFormat( |
479 | format: "\"disassemble\" arguments are specified as options.\n" ); |
480 | const int terminal_width = |
481 | GetCommandInterpreter().GetDebugger().GetTerminalWidth(); |
482 | GetOptions()->GenerateOptionUsage(strm&: result.GetErrorStream(), cmd&: *this, |
483 | screen_width: terminal_width); |
484 | return; |
485 | } |
486 | |
487 | if (m_options.show_mixed && m_options.num_lines_context == 0) |
488 | m_options.num_lines_context = 2; |
489 | |
490 | // Always show the PC in the disassembly |
491 | uint32_t options = Disassembler::eOptionMarkPCAddress; |
492 | |
493 | // Mark the source line for the current PC only if we are doing mixed source |
494 | // and assembly |
495 | if (m_options.show_mixed) |
496 | options |= Disassembler::eOptionMarkPCSourceLine; |
497 | |
498 | if (m_options.show_bytes) |
499 | options |= Disassembler::eOptionShowBytes; |
500 | |
501 | if (m_options.show_control_flow_kind) |
502 | options |= Disassembler::eOptionShowControlFlowKind; |
503 | |
504 | if (m_options.raw) |
505 | options |= Disassembler::eOptionRawOuput; |
506 | |
507 | llvm::Expected<std::vector<AddressRange>> ranges = |
508 | GetRangesForSelectedMode(result); |
509 | if (!ranges) { |
510 | result.AppendError(in_string: toString(E: ranges.takeError())); |
511 | return; |
512 | } |
513 | |
514 | bool = ranges->size() > 1; |
515 | for (AddressRange cur_range : *ranges) { |
516 | Disassembler::Limit limit; |
517 | if (m_options.num_instructions == 0) { |
518 | limit = {.kind: Disassembler::Limit::Bytes, .value: cur_range.GetByteSize()}; |
519 | if (limit.value == 0) |
520 | limit.value = default_disasm_byte_size; |
521 | } else { |
522 | limit = {.kind: Disassembler::Limit::Instructions, .value: m_options.num_instructions}; |
523 | } |
524 | if (Disassembler::Disassemble( |
525 | debugger&: GetDebugger(), arch: m_options.arch, plugin_name, flavor: flavor_string, |
526 | exe_ctx: m_exe_ctx, start: cur_range.GetBaseAddress(), limit, mixed_source_and_assembly: m_options.show_mixed, |
527 | num_mixed_context_lines: m_options.show_mixed ? m_options.num_lines_context : 0, options, |
528 | strm&: result.GetOutputStream())) { |
529 | result.SetStatus(eReturnStatusSuccessFinishResult); |
530 | } else { |
531 | if (m_options.symbol_containing_addr != LLDB_INVALID_ADDRESS) { |
532 | result.AppendErrorWithFormat( |
533 | format: "Failed to disassemble memory in function at 0x%8.8" PRIx64 ".\n" , |
534 | m_options.symbol_containing_addr); |
535 | } else { |
536 | result.AppendErrorWithFormat( |
537 | format: "Failed to disassemble memory at 0x%8.8" PRIx64 ".\n" , |
538 | cur_range.GetBaseAddress().GetLoadAddress(target)); |
539 | } |
540 | } |
541 | if (print_sc_header) |
542 | result.GetOutputStream() << "\n" ; |
543 | } |
544 | } |
545 | |