| // Copyright 2018 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/developer/debug/zxdb/client/step_thread_controller.h" |
| |
| #include <inttypes.h> |
| |
| #include "src/developer/debug/zxdb/client/finish_thread_controller.h" |
| #include "src/developer/debug/zxdb/client/frame.h" |
| #include "src/developer/debug/zxdb/client/process.h" |
| #include "src/developer/debug/zxdb/client/thread.h" |
| #include "src/developer/debug/zxdb/common/err.h" |
| #include "src/developer/debug/zxdb/symbols/line_details.h" |
| #include "src/developer/debug/zxdb/symbols/process_symbols.h" |
| |
| namespace zxdb { |
| |
| StepThreadController::StepThreadController(StepMode mode) : step_mode_(mode) {} |
| StepThreadController::StepThreadController(AddressRanges ranges) |
| : step_mode_(StepMode::kAddressRange), current_ranges_(ranges) {} |
| StepThreadController::~StepThreadController() = default; |
| |
| void StepThreadController::InitWithThread(Thread* thread, |
| std::function<void(const Err&)> cb) { |
| set_thread(thread); |
| |
| const Stack& stack = thread->GetStack(); |
| if (stack.empty()) { |
| cb(Err("Can't step, no frames.")); |
| return; |
| } |
| const Frame* top_frame = stack[0]; |
| uint64_t ip = top_frame->GetAddress(); |
| |
| if (step_mode_ == StepMode::kSourceLine) { |
| // Always take the file/line from the stack rather than the line table. |
| // The stack will have been fixed up and may reference the calling line |
| // for an inline routine, while the line table will reference the inlined |
| // source that generated the instructions. |
| file_line_ = top_frame->GetLocation().file_line(); |
| |
| LineDetails line_details = |
| thread->GetProcess()->GetSymbols()->LineDetailsForAddress(ip); |
| if (line_details.file_line() == file_line_) { |
| // When the stack and the line details match up, the range from the line |
| // table is usable. |
| current_ranges_ = AddressRanges(line_details.GetExtent()); |
| Log("Stepping in %s:%d %s", file_line_.file().c_str(), file_line_.line(), |
| current_ranges_.ToString().c_str()); |
| } else { |
| // Otherwise keep the current range empty to cause a |
| // step into inline routine or potentially a single step. |
| current_ranges_ = AddressRanges(); |
| Log("Stepping in empty range, likely to step into an inline routine."); |
| } |
| |
| original_frame_fingerprint_ = *thread->GetStack().GetFrameFingerprint(0); |
| } else { |
| // In the "else" cases, the range will already have been set up. |
| Log("Stepping in %s", current_ranges_.ToString().c_str()); |
| } |
| |
| cb(Err()); |
| } |
| |
| ThreadController::ContinueOp StepThreadController::GetContinueOp() { |
| if (finish_unsymolized_function_) |
| return finish_unsymolized_function_->GetContinueOp(); |
| |
| // The stack shouldn't be empty when stepping in a range, but in case it is, |
| // fall back to single-step. |
| const auto& stack = thread()->GetStack(); |
| if (stack.empty()) |
| return ContinueOp::StepInstruction(); |
| |
| // Check for inlines. This case will likely have an empty address range so |
| // the inline check needs to be done before checking for empty ranges below. |
| // |
| // GetContinueOp() should not modify thread state, so we need to return |
| // whether we want to modify the inline stack. Returning SyntheticStop here |
| // will schedule a call OnThreadStop with a synthetic exception. The inline |
| // stack should actually be modified at that point. |
| if (TrySteppingIntoInline(StepIntoInline::kQuery)) |
| return ContinueOp::SyntheticStop(); |
| |
| // An empty range means to step by instruction. |
| if (current_ranges_.empty()) |
| return ContinueOp::StepInstruction(); |
| |
| // Use the IP from the top of the stack to figure out which range to send |
| // to the agent (it only accepts one, while we can have a set). |
| if (auto inside = current_ranges_.GetRangeContaining(stack[0]->GetAddress())) |
| return ContinueOp::StepInRange(*inside); |
| |
| // Don't generally expect to be continuing in a range that we're not |
| // currently inside of. But it could be the caller is expecting the next |
| // instruction to be in that range, so fall back to single-step mode. |
| return ContinueOp::StepInstruction(); |
| } |
| |
| ThreadController::StopOp StepThreadController::OnThreadStop( |
| debug_ipc::NotifyException::Type stop_type, |
| const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) { |
| if (finish_unsymolized_function_) { |
| Log("Trying to step out of unsymbolized function."); |
| if (finish_unsymolized_function_->OnThreadStop( |
| stop_type, hit_breakpoints) == kContinue) { |
| finish_unsymolized_function_->Log("Reported continue."); |
| return kContinue; |
| } |
| |
| finish_unsymolized_function_->Log("Reported stop, continuing with step."); |
| finish_unsymolized_function_.reset(); |
| } else { |
| // The only real exception type we care about (as opposed to synthetic and |
| // "none" -- see below) are the single step exceptions. We wouldn't want to |
| // try to resume from a crash just because it's in our range, or if there |
| // was a hardcoded debug instruction in the range, for example. |
| // |
| // This must happen only when there's no "finish" controller since a |
| // successful "finish" hit will have a software breakpoint. |
| // |
| // A "none" type means to ignore the exception type and evaluate the |
| // current code location. It is used when this controller is nested. A |
| // synthetic exception is used to step into inline functions. |
| if (stop_type != debug_ipc::NotifyException::Type::kNone && |
| stop_type != debug_ipc::NotifyException::Type::kSynthetic && |
| stop_type != debug_ipc::NotifyException::Type::kSingleStep) { |
| Log("Not our exception type, stop is somebody else's."); |
| return kUnexpected; |
| } |
| } |
| |
| Stack& stack = thread()->GetStack(); |
| if (stack.empty()) |
| return kUnexpected; // Agent sent bad state, give up trying to step. |
| |
| const Frame* top_frame = stack[0]; |
| uint64_t ip = top_frame->GetAddress(); |
| if (current_ranges_.InRange(ip)) { |
| Log("In existing range: %s", current_ranges_.ToString().c_str()); |
| return kContinue; |
| } |
| |
| Log("Left range: %s", current_ranges_.ToString().c_str()); |
| |
| if (step_mode_ == StepMode::kSourceLine) { |
| ProcessSymbols* process_symbols = thread()->GetProcess()->GetSymbols(); |
| LineDetails line_details = process_symbols->LineDetailsForAddress(ip); |
| |
| if (!line_details.is_valid()) { |
| // Stepping by line but we ended up in a place where there's no line |
| // information. |
| if (stop_on_no_symbols_) { |
| Log("Stopping because there are no symbols."); |
| return kStopDone; |
| } |
| return OnThreadStopOnUnsymbolizedCode(); |
| } |
| |
| // When stepping by source line the current_ranges_ will be the entry for |
| // the current line in the line table. But we could have a line table |
| // like this: |
| // line 10 <= current_ranges_ |
| // line 11 |
| // line 10 |
| // Initially we were stepping in the range of the first "line 10" entry. |
| // But when we leave that, we could have skipped over the "line 11" entry |
| // (say for a short-circuited if statement) and could still be on line 10! |
| // |
| // We could also have "line 0" entries which represent code without any |
| // corresponding source line (usually bookkeeping by the compiler). |
| // |
| // This checks if we're in another entry representing the same source line |
| // or line 0, and continues stepping in that range. |
| // |
| // Note: don't check the original file_line_ variable for line 0 since if |
| // the source of the step was in one of these weird locations, all |
| // subsequent lines will compare for equality and we'll never stop |
| // stepping! |
| // |
| // As in InitWithThread(), always use the stack's file/line over the result |
| // from the line table. |
| const Location& top_location = top_frame->GetLocation(); |
| if (top_location.file_line().line() == 0 || |
| top_location.file_line() == file_line_) { |
| // Still on the same line. |
| if (top_location.file_line() == line_details.file_line()) { |
| // Can use the range from the line table. |
| current_ranges_ = AddressRanges(line_details.GetExtent()); |
| Log("Got new range for line: %s", current_ranges_.ToString().c_str()); |
| return kContinue; |
| } else { |
| // Line table and stack don't match due to inlined calls. Clearing the |
| // range will make the next operation will either single-step or step |
| // into an inline function. |
| current_ranges_ = AddressRanges(); |
| // Fall-through to trying to fixup inline frames or stopping. |
| } |
| } |
| } |
| |
| if (stop_type == debug_ipc::NotifyException::Type::kSynthetic || |
| stop_type == debug_ipc::NotifyException::Type::kNone) { |
| // Handle virtually stepping into inline functions by modifying the hidden |
| // ambiguous inline frame count. |
| // |
| // This should only happen for synthetic stops because modifying the hide |
| // count is an alternative to actually stepping the CPU. Doing this after a |
| // real step will modify the stack for the *next* instruction (like doing |
| // "step into" twice in the case of ambiguous inline frames). |
| // |
| // On the other hand, this check should happen after the other types of |
| // range checking in case the thread is still in range. |
| if (TrySteppingIntoInline(StepIntoInline::kCommit)) |
| return kStopDone; |
| } else { |
| // When an actual step (not synthetic) has resulted in landing at an |
| // ambiguous inline location, always consider the location to be the oldest |
| // frame to allow the user to step into the inline frames if desired. |
| // |
| // We don't want to select the same frame here that we were originally |
| // stepping in because we could have just stepped out of a frame to an |
| // inline function starting immediately after the call. We always want to at |
| // the oldest possible inline call. |
| stack.SetHideAmbiguousInlineFrameCount( |
| stack.GetAmbiguousInlineFrameCount()); |
| } |
| return kStopDone; |
| } |
| |
| bool StepThreadController::TrySteppingIntoInline(StepIntoInline command) { |
| if (step_mode_ != StepMode::kSourceLine) { |
| // Only do inline frame handling when stepping by line. |
| // |
| // When the user is doing a single-instruction step, ignore special inline |
| // frames and always do a real step. The other mode is "address range" |
| // which isn't exposed to the user directly so we probably won't encounter |
| // it here, but assume that it's also a low-level operation that doesn't |
| // need inline handling. |
| return false; |
| } |
| |
| Stack& stack = thread()->GetStack(); |
| |
| size_t hidden_frame_count = stack.hide_ambiguous_inline_frame_count(); |
| if (hidden_frame_count == 0) { |
| // The Stack object always contains all inline functions nested at the |
| // current address. When it's not logically in one or more of them, they |
| // will be hidden. Not having any hidden inline frames means there's |
| // nothing to a synthetic inline step into. |
| return false; |
| } |
| |
| // Examine the closest hidden frame. |
| const Frame* frame = |
| stack.FrameAtIndexIncludingHiddenInline(hidden_frame_count - 1); |
| if (!frame->IsAmbiguousInlineLocation()) |
| return false; // No inline or not ambiguous. |
| |
| // Do the synthetic step into by unhiding an inline frame. |
| if (command == StepIntoInline::kCommit) { |
| size_t new_hide_count = hidden_frame_count - 1; |
| stack.SetHideAmbiguousInlineFrameCount(new_hide_count); |
| Log("Synthetically stepping into inline frame %s, new hide count = %zu.", |
| FrameFunctionNameForLog(stack[0]).c_str(), new_hide_count); |
| } |
| return true; |
| } |
| |
| ThreadController::StopOp |
| StepThreadController::OnThreadStopOnUnsymbolizedCode() { |
| Log("Stepped into code with no symbols."); |
| |
| const Stack& stack = thread()->GetStack(); |
| const Frame* top_frame = stack[0]; |
| |
| ProcessSymbols* process_symbols = thread()->GetProcess()->GetSymbols(); |
| if (process_symbols->HaveSymbolsLoadedForModuleAt(top_frame->GetAddress())) { |
| // We ended up in code with no symbols inside a module where we expect |
| // to have symbols. The common cause of this is a shared library thunk: |
| // When there is an imported symbol, all code in a module will jump to |
| // some generated code (no symbols) that in turn does an indirect jump |
| // to the destination. The destination of the indirect jump is what's |
| // filled in by the dynamic loader when imports are resolved. |
| // |
| // LLDB indexes ELF imports in the symbol database (type |
| // eSymbolTypeTrampoline) and can then compare to see if the current |
| // code is a trampoline. See |
| // DynamicLoaderPOSIXDYLD::GetStepThroughTrampolinePlan. |
| // |
| // We should do something similar which will be less prone to errors. |
| // GDB does something similar but also checks that the instruction is |
| // the right type of jump. This involves two memory lookups which make |
| // it difficult for us to implement since they require async calls. |
| // We might be able to just check that the address is inside the |
| // procedure linkage table (see below). |
| // |
| // ELF imports |
| // ----------- |
| // ELF imports go through the "procedure linkage table" (see the ELF |
| // spec) which allows lazy resolution. These trampolines have a default |
| // jump address is to the next instruction which then pushes the item |
| // index on the stack and does a dance to jump to the dynamic linker to |
| // resolve this import. Once resolved, the first jump takes the code |
| // directly to the destination. |
| // |
| // Our loader seems to resolve these up-front. In the future we might |
| // need to add logic to step over the dynamic loader when its resolving |
| // the import. |
| Log("In function with no symbols, single-stepping."); |
| current_ranges_ = AddressRanges(); // No range: step by instruction. |
| return kContinue; |
| } |
| |
| if (FrameFingerprint::Newer(*thread()->GetStack().GetFrameFingerprint(0), |
| original_frame_fingerprint_)) { |
| // Called a new stack frame that has no symbols. We need to "finish" to |
| // step over the unsymbolized code to automatically step over the |
| // unsymbolized code. |
| Log("Called unsymbolized function, stepping out."); |
| FXL_DCHECK(original_frame_fingerprint_.is_valid()); |
| finish_unsymolized_function_ = |
| std::make_unique<FinishThreadController>(thread()->GetStack(), 0); |
| finish_unsymolized_function_->InitWithThread(thread(), [](const Err&) {}); |
| return kContinue; |
| } |
| |
| // Here we jumped (not called, we checked the frames above) to some |
| // unsymbolized code. Don't know what this is so stop. |
| Log("Jumped to unsymbolized code, giving up and stopping."); |
| return kStopDone; |
| } |
| |
| } // namespace zxdb |