| // 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/function.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(const FileLine& line) | |
| : step_mode_(StepMode::kSourceLine), file_line_(line) {} | |
| StepThreadController::StepThreadController(AddressRanges ranges) | |
| : step_mode_(StepMode::kAddressRange), current_ranges_(ranges) {} | |
| StepThreadController::~StepThreadController() = default; | |
| void StepThreadController::InitWithThread(Thread* thread, fit::callback<void(const Err&)> cb) { | |
| SetThread(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) { | |
| if (!file_line_) { | |
| // 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."); | |
| } | |
| 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, give up if it is. | |
| const auto& stack = thread()->GetStack(); | |
| if (stack.empty()) { | |
| Log("Declaring synthetic stop due to empty stack."); | |
| return ContinueOp::SyntheticStop(); | |
| } | |
| // 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)) { | |
| Log("Declaring synthetic stop due to inline."); | |
| 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::ExceptionType stop_type, | |
| const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) { | |
| Log("StepThreadController::OnThreadStop"); | |
| Stack& stack = thread()->GetStack(); | |
| if (stack.empty()) { | |
| Log("StepThreadController unexpected"); | |
| return kUnexpected; // Agent sent bad state, give up trying to step. | |
| } | |
| 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::ExceptionType::kNone && | |
| stop_type != debug_ipc::ExceptionType::kSynthetic && | |
| stop_type != debug_ipc::ExceptionType::kSingleStep) || | |
| !hit_breakpoints.empty()) { | |
| Log("Not our exception type, stop is somebody else's."); | |
| return kUnexpected; | |
| } | |
| } | |
| if (stop_type == debug_ipc::ExceptionType::kSynthetic || | |
| stop_type == debug_ipc::ExceptionType::kNone) { | |
| // Handle virtually stepping into inline functions by modifying the hidden ambiguous inline | |
| // frame count. | |
| // | |
| // This should 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). | |
| if (TrySteppingIntoInline(StepIntoInline::kCommit)) | |
| return kStopDone; | |
| if (stop_type == debug_ipc::ExceptionType::kSynthetic) { | |
| // In every case where GetContinueOp() returns SyntheticStop, this controller should do | |
| // something. Otherwise there will be an infinite loop since GetContinueOp() will presumably | |
| // return the same thing given the same conditions. | |
| // | |
| // This condition prevents the loop if such a case were to occur. If this assertion hits, | |
| // GetContinueOp() needs to agree with this function on what to do in the synthetic case. | |
| FX_NOTREACHED(); | |
| return kStopDone; | |
| } | |
| // In the ExceptionType::kNone case, it's normal we didn't do anything if there are no inline | |
| // routines. This will happen when this controller is used as a sub controller for e.g. the | |
| // "step over" controller. GetContinueOp() has not been called to classify. | |
| } | |
| 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(); | |
| // Normally you'll want to use the line information from line_details instead of from the Stack. | |
| // See big comment below. | |
| 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). We always want to step over "line 0" code ranges. | |
| // | |
| // To make things more complicated, the stack will try to fix up "line 0" locations to use the | |
| // next real file/line in order to avoid showing "no line information" errors in the stack | |
| // trace. This means we can't trust the stack frame's location for making stepping decisions and | |
| // should always use the line_details. | |
| // | |
| // This case is a little different than the code in InitWithThread() which always wants to use | |
| // the stack frame's location if there is ambiguity. This is because when the user starts | |
| // stepping, they think they're at the location identified by the Stack frame. But once we're in | |
| // the middle of stepping, there is no more expectation about ambiguous stack frames. | |
| // | |
| // 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! | |
| if (stack.hide_ambiguous_inline_frame_count() > 0) { | |
| // There are ambiguous locations to step into at this location, the next "step" operation will | |
| // be to go into that. Clear the range and fall through to the inline stepping code at the | |
| // bottom of this function. | |
| // | |
| // Note in this case the current line_details will normally identify the first line of the | |
| // most deeply nested inline function, while the current stack frame's location will be the | |
| // call location of the current inline. This code needs to happen before the line_details are | |
| // checked because the line_details don't represent the thing we're trying to step. | |
| current_ranges_ = AddressRanges(); | |
| Log("Stepping hit inline boundary"); | |
| } else if (line_details.file_line().line() == 0 || line_details.file_line() == *file_line_) { | |
| // The current code's file/line matches what we're stepping over. Continue stepping inside the | |
| // current range. | |
| current_ranges_ = AddressRanges(line_details.GetExtent()); | |
| Log("Still on the same line, continuing with new range: %s", | |
| current_ranges_.ToString().c_str()); | |
| return kContinue; | |
| } else { | |
| // This "else" case is just that the line information is different than the one we're trying | |
| // to step over, so we fall through to the "done" code at the end of the function. | |
| Log("Got to a different line."); | |
| } | |
| } | |
| // Just completed a true step. It may have landed at an ambiguous inline location. When | |
| // line stepping from an outer frame into a newer inline, always go into exactly one frame. This | |
| // corresponds to executing instructions on the line before the inline call, and then stopping | |
| // at the first instruction of the inline call. | |
| // | |
| // Need to reset the hide count before doing this because we just stepped *to* the ambiguous | |
| // location and want to have our default to be to stay in the same (outermost) frame. | |
| stack.SetHideAmbiguousInlineFrameCount(stack.GetAmbiguousInlineFrameCount()); | |
| TrySteppingIntoInline(StepIntoInline::kCommit); | |
| 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 inline frame to potentially unhide. | |
| const Frame* frame = stack.FrameAtIndexIncludingHiddenInline(hidden_frame_count - 1); | |
| if (!frame->IsAmbiguousInlineLocation()) | |
| return false; // No inline or not ambiguous. | |
| // For "step" to go into an inline function, the line of the inline call must be the same as the | |
| // line the user was stepping from. This disambiguates these two cases: | |
| // 1) Stepping on some code followed by an inline call on the same line (should step in). | |
| // 2) Stepping on a line with no function calls, immediately followed by a different inline | |
| // function call on a subsequent line (don't step in). | |
| // We could get the inline function definition and ask for its file/line. The previous stack | |
| // frame's file/line will have the same location (the Stack fills this in based on the inline | |
| // call source). Use the latter to help keep things in sync. This also makes testing easier since | |
| // the tests don't have to fill in the inline call locations, on the stack. | |
| const Frame* before_inline_frame = stack.FrameAtIndexIncludingHiddenInline(hidden_frame_count); | |
| if (before_inline_frame->GetLocation().file_line() != file_line_) | |
| return false; // Different lines. | |
| // Require that the the frame we might step into is newer than the frame we started stepping at. | |
| // This handles the "step into inline" case. | |
| // | |
| // We don't want to do anything when the newer frame is the same level or older than the source. | |
| // These states indicate that we stepped out of one or more inline frames, and immediately to the | |
| // beginning of another (or else the location wouldn't be ambiguous). Stepping should leave us | |
| // at the lower leve of the stack in that case. | |
| // | |
| // The Stack object can only get fingerprints for unhidden frames, so unhide it and put it | |
| // back. Hiding/unhiding is inexpensive so don't worry about it. | |
| size_t new_hide_count = hidden_frame_count - 1; | |
| stack.SetHideAmbiguousInlineFrameCount(new_hide_count); | |
| FrameFingerprint new_inline_fingerprint = stack.GetFrameFingerprint(0); | |
| stack.SetHideAmbiguousInlineFrameCount(hidden_frame_count); | |
| // Either the original_frame_fingerprint_ or the new_inline_fingerprint could be null at this | |
| // point if the CFA for the current frame is 0. This can occur in unsymbolized code and I've also | |
| // seen it in Go code where it seems the unwinder doesn't completely work. | |
| // | |
| // In this case, two null fingerprints will compare equal, and the frame will be considered the | |
| // same (what we want for this case). | |
| if (!FrameFingerprint::Newer(new_inline_fingerprint, original_frame_fingerprint_)) | |
| return false; // Not newer. | |
| // Inline frame should be stepped into. | |
| if (command == StepIntoInline::kCommit) { | |
| 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."); | |
| FX_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 |