blob: 4b3b7912bdf1cd9c6b1b8683781e946f2437556b [file] [log] [blame]
// Copyright 2021 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_through_plt_thread_controller.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/function_step.h"
#include "src/developer/debug/zxdb/client/process.h"
#include "src/developer/debug/zxdb/client/stack.h"
#include "src/developer/debug/zxdb/client/thread.h"
#include "src/developer/debug/zxdb/symbols/elf_symbol.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/symbol.h"
namespace zxdb {
void StepThroughPltThreadController::InitWithThread(Thread* thread,
fit::callback<void(const Err&)> cb) {
SetThread(thread);
const Stack& stack = thread->GetStack();
if (stack.empty())
return cb(Err("Can't step, no frames."));
const Frame* top_frame = stack[0];
// Extract the ELF PLT symbol for the current location (the thread should be stopped at a PLT
// symbol when InitWithThread() is called).
const Location& cur_loc = top_frame->GetLocation();
if (!cur_loc.symbol()) {
FX_NOTREACHED();
return cb(Err("Expecting a PLT symbol to step through."));
}
const ElfSymbol* elf_sym = cur_loc.symbol().Get()->As<ElfSymbol>();
if (!elf_sym || elf_sym->elf_type() != ElfSymbolType::kPlt) {
FX_NOTREACHED();
return cb(Err("Expecting a PLT symbol to step through."));
}
const std::string linkage_name = elf_sym->linkage_name();
plt_address_ = cur_loc.address();
// The PLT trampoline will have the same name as the destinaion symbols, they'll all be called,
// for example, "open" and they'll all be a PLT type (so "$plt(open)" in zxdb naming).
// Currently ELF symbol lookup only takes mangled names, so we need to construct an identifier
// based on the linkage name.
Identifier plt_name(IdentifierComponent(SpecialIdentifier::kPlt, linkage_name));
FX_DCHECK(plt_name.components().size() == 1); // Expect one component for all ELF symbols.
// Get the elf symbol name because we don't want to just match PLT entries. Querying for
// $elf(open) will also match $plt(open) because PLT symbols are a subset of ELF symbols. These
// extra matches should be harmless: we'll filter out our current PLT symbols and other modules'
// PLT entries for the same symbol just won't be hit.
Identifier elf_name(IdentifierComponent(SpecialIdentifier::kElf, linkage_name));
// We expect the function name to resolve to two locations: the current one (the calling PLT
// entry) and the destination one. There might be additional ones if there are duplicate symbols
// (yikes) or other modules importing the same function (normal) but if there is only one it's our
// calling location and the destination is unresolved.
//
// We could pass the function name directly to the "Until" controller but it will also match
// our current location and will hit when we try to continue.
//
// There is some extra logic in the breakpoint that the "until" controller makes about dynamically
// loaded libraries (like if this PLT thunk actually causes a module to be loaded) that we may
// want in the future. If that's the case, we may want to just pass the function name to the
// "until" controller and reach into its breakpoint and disable the current location.
auto found = thread->GetProcess()->GetSymbols()->ResolveInputLocation(InputLocation(elf_name));
// Filter out the current IP.
found.erase(
std::remove_if(found.begin(), found.end(),
[ip = plt_address_](const Location& cur) { return cur.address() == ip; }),
found.end());
Log("Got %zu matches for ELF symbol %s, running 'until' there.", found.size(),
plt_name.components()[0].name().c_str());
// When no matches were found, the destination can never be hit. Using the "until" controller at
// this point will be like continuing the program which will lose the current location. In this
// case, give up and stop the program so the user can figure out what they want to do.
if (found.empty()) {
cb(Err("Could not find destination of PLT trampoline."));
return;
}
// Make the "until" controller run until the resulting address(s). It's important that this
// forward asynchronous failures back to our callback parameter because the breakpoint set could
// fail (for example, the code could be in the read-only vDSO) and we don't want execution to just
// continue in that case.
std::vector<InputLocation> input_locations;
for (const auto& loc : found) {
dest_addrs_.push_back(loc.address());
input_locations.push_back(InputLocation(loc.address()));
}
until_ = std::make_unique<UntilThreadController>(std::move(input_locations));
until_->InitWithThread(
thread, [weak_this = weak_factory_.GetWeakPtr(), cb = std::move(cb)](const Err& err) mutable {
if (err.has_error() && weak_this)
weak_this->OnUntilControllerInitializationFailed();
cb(err);
});
}
ThreadController::ContinueOp StepThroughPltThreadController::GetContinueOp() {
if (until_)
return until_->GetContinueOp();
// Fall back to single-stepping instructions if the until controller failed.
return ContinueOp::StepInstruction();
}
ThreadController::StopOp StepThroughPltThreadController::OnThreadStop(
debug_ipc::ExceptionType stop_type,
const std::vector<fxl::WeakPtr<Breakpoint>>& hit_breakpoints) {
if (until_) {
// Delegate to thread controller.
Log("Checking with until controller to see if PLT stepping is complete.");
return until_->OnThreadStop(stop_type, hit_breakpoints);
}
// We're single-stepping through the PLT, check against the addresses.
if (!dest_addrs_.empty()) {
Stack& stack = thread()->GetStack();
if (stack.empty()) {
Log("Unexpected empty stack");
return kUnexpected; // Agent sent bad state, give up trying to step.
}
const Frame* top_frame = stack[0];
uint64_t ip = top_frame->GetAddress();
for (auto addr : dest_addrs_) {
if (addr == ip) {
Log("Matched PLT destination for stepping.");
return ThreadController::StopOp::kStopDone;
}
}
Log("Continuing to single-step through PLT.");
return ThreadController::StopOp::kContinue;
}
Log("No destination for PLT step, stopping execution.");
return ThreadController::StopOp::kStopDone;
}
void StepThroughPltThreadController::OnUntilControllerInitializationFailed() {
// The "until" controller failed to initialize. Most commonly this is because the breakpoint could
// not be set because the destination memory is read-only (this will happen for syscalls which are
// in the vDSO). Fall back to single-stepping through the trampoline.
Log("Until controller failed, falling back to single-stepping through PLT.");
until_ = nullptr;
}
} // namespace zxdb