| // Copyright 2019 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 "zx_channel_params.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "src/developer/debug/ipc/register_desc.h" |
| #include "src/developer/debug/shared/message_loop.h" |
| #include "src/developer/debug/zxdb/client/memory_dump.h" |
| #include "src/developer/debug/zxdb/client/process.h" |
| #include "src/developer/debug/zxdb/client/step_thread_controller.h" |
| #include "src/lib/fxl/logging.h" |
| |
| namespace fidlcat { |
| |
| const std::vector<zxdb::Register>* ZxChannelParamsBuilder::GetGeneralRegisters( |
| fxl::WeakPtr<zxdb::Thread> thread, const zxdb::Err& err, |
| const zxdb::RegisterSet& in_regs) { |
| if (!thread) { |
| Cancel(zxdb::Err(zxdb::ErrType::kGeneral, |
| "Error reading registers: thread went away")); |
| return nullptr; |
| } |
| if (!err.ok()) { |
| Cancel(zxdb::Err(err.type(), "Error reading registers" + err.msg())); |
| return nullptr; |
| } |
| auto regs_it = |
| in_regs.category_map().find(debug_ipc::RegisterCategory::Type::kGeneral); |
| if (regs_it == in_regs.category_map().end()) { |
| Cancel(zxdb::Err(zxdb::ErrType::kGeneral, "Can't read registers")); |
| return nullptr; |
| } |
| return ®s_it->second; |
| } |
| |
| // This is called when we want to abort the current build. Callers should not |
| // continue to try to build after it is called. |
| void ZxChannelParamsBuilder::Cancel(const zxdb::Err& e) { |
| if (e.ok()) { |
| err_ = zxdb::Err(zxdb::ErrType::kGeneral, "Canceled for unknown reason"); |
| } else { |
| err_ = e; |
| } |
| Finalize(); |
| } |
| |
| // The last method to run, which invokes the ZxChannelCallback |
| void ZxChannelParamsBuilder::Finalize() { |
| if (once_) { |
| return; |
| } |
| once_ = true; |
| if (!err_.ok()) { |
| ZxChannelParams p; |
| callback_(err_, p); |
| } else { |
| ZxChannelParams params = |
| ZxChannelParams(handle_, options_, std::move(bytes_), num_bytes_, |
| std::move(handles_), num_handles_); |
| zxdb::Err err; |
| callback_(err, params); |
| } |
| } |
| |
| namespace { |
| |
| // Helper function to convert a vector of bytes to a T. |
| template <typename T> |
| T GetValueFromBytes(const std::vector<uint8_t>& bytes) { |
| T ret = 0; |
| for (uint64_t i = 0; i < sizeof(ret) && i < bytes.size(); i++) { |
| ret |= ((uint64_t)(bytes[i])) << (i * 8); |
| } |
| return ret; |
| } |
| |
| // Helper function to convert an array of bytes to a T. |
| template <typename T> |
| T GetValueFromBytes(const T* bytes_t) { |
| T ret = 0; |
| const uint8_t* bytes = reinterpret_cast<const uint8_t*>(bytes_t); |
| constexpr size_t size = sizeof(T); |
| for (uint64_t i = 0; i < sizeof(ret) && i < size; i++) { |
| ret |= ((uint64_t)(bytes[i])) << (i * 8); |
| } |
| return ret; |
| } |
| |
| // Helper function to convert the value in a register to a T |
| template <typename T> |
| T GetRegisterValue(const std::vector<zxdb::Register>& regs, |
| const debug_ipc::RegisterID id) { |
| for (const zxdb::Register& reg : regs) { |
| if (reg.id() == id) { |
| return GetValueFromBytes<T>(reg.data()); |
| } |
| } |
| return 0; |
| } |
| |
| // Grovels through the |dump| and constructs a local copy of the bytes into an |
| // array of type |T|, starting at |bytes_address| and continuing for |count| |
| // bytes. |
| template <typename T> |
| std::unique_ptr<T> MemoryDumpToArray(uint64_t bytes_address, uint32_t count, |
| const zxdb::MemoryDump& dump) { |
| std::unique_ptr<T> output_buffer = std::make_unique<T>(count); |
| memset(reinterpret_cast<void*>(output_buffer.get()), 0, count); |
| |
| uint8_t* buffer_as_bytes = reinterpret_cast<uint8_t*>(output_buffer.get()); |
| size_t output_offset = 0; |
| |
| for (const debug_ipc::MemoryBlock& block : dump.blocks()) { |
| if (!block.valid) { |
| continue; |
| } |
| size_t block_offset = 0; |
| if (block.address < bytes_address) { |
| if (block.address + block.size < bytes_address) { |
| continue; |
| } |
| block_offset = bytes_address - block.address; |
| } |
| while (block_offset < block.size && |
| output_offset < (sizeof(output_buffer.get()[0]) * count)) { |
| buffer_as_bytes[output_offset] = block.data[block_offset]; |
| output_offset++; |
| block_offset++; |
| } |
| } |
| return output_buffer; |
| } |
| |
| // Schedules an async task that gets the remote memory available via |thread|, |
| // located at |remote_address|, and going for |count| bytes. The asynchronous |
| // task will invoke |callback| with the relevant error and the data retrieved. |
| template <typename T> |
| void GetMemoryAtAndThen( |
| fxl::WeakPtr<zxdb::Thread> thread, uint64_t remote_address, uint64_t count, |
| std::function<void(const zxdb::Err&, std::unique_ptr<T> data)> callback) { |
| if (count == 0 || remote_address == 0) { |
| zxdb::Err err; |
| callback(err, {}); |
| return; |
| } |
| thread->GetProcess()->ReadMemory( |
| remote_address, count, |
| [callback = std::move(callback), remote_address, count]( |
| const zxdb::Err& err, zxdb::MemoryDump dump) mutable { |
| std::unique_ptr<T> data; |
| zxdb::Err e = err; |
| if (err.ok()) { |
| data = MemoryDumpToArray<T>(remote_address, count, dump); |
| } else { |
| std::string msg = "Failed to build parameters for syscall: "; |
| msg.append(err.msg()); |
| e = zxdb::Err(err.type(), msg); |
| } |
| callback(e, std::move(data)); |
| }); |
| } |
| |
| // Abstract class that gets the parameters to a given functions, assuming you |
| // are in a breakpoint at the beginning of the function, and pass in the current |
| // registers. |
| class CallingConventionDecoder { |
| public: |
| explicit CallingConventionDecoder(const std::vector<zxdb::Register>& regs) |
| : regs_(regs) {} |
| |
| // Fills out the arguments so that GetArgument can later be called, and then |
| // runs the and_then function. |
| virtual void PopulateArguments( |
| fxl::WeakPtr<zxdb::Thread> thread, size_t arity, |
| std::function<void(const zxdb::Err&)> and_then) = 0; |
| |
| // Getter: for argument at position i, return the value. |
| template <typename T> |
| T GetArgument(size_t position) { |
| uint64_t tmp = GetUintArgument(position); |
| return static_cast<T>(tmp); |
| } |
| |
| // Gets the return value, per the calling conventions |
| template <typename T> |
| T GetReturnValue() { |
| uint64_t tmp = GetResult(); |
| return static_cast<T>(tmp); |
| } |
| |
| // Gets the link register, if it exists |
| virtual std::optional<uint64_t> GetLinkRegister() const { |
| return std::nullopt; |
| } |
| |
| // Gets the stack pointer from the registers passed into the constructor. |
| virtual uint64_t GetStackPointer() { return stack_pointer_; } |
| |
| CallingConventionDecoder() = delete; |
| CallingConventionDecoder(const CallingConventionDecoder&) = delete; |
| CallingConventionDecoder(CallingConventionDecoder&&) = delete; |
| |
| virtual ~CallingConventionDecoder() = default; |
| |
| protected: |
| uint64_t GetUintArgument(size_t position) { |
| FXL_DCHECK(position < args_.size()) << "Bad parameter to GetUintArgument"; |
| return args_[position]; |
| } |
| |
| virtual uint64_t GetResult() const = 0; |
| |
| std::vector<uint64_t> args_; |
| const std::vector<zxdb::Register>& regs_; |
| uint64_t stack_pointer_; |
| }; |
| |
| // X86 specialization of CallingConventionDecoder above. |
| class CallingConventionDecoderX86 : public CallingConventionDecoder { |
| public: |
| CallingConventionDecoderX86(const std::vector<zxdb::Register>& regs) |
| : CallingConventionDecoder(regs) { |
| stack_pointer_ = |
| GetRegisterValue<uint64_t>(regs_, debug_ipc::RegisterID::kX64_rsp); |
| } |
| virtual void PopulateArguments( |
| fxl::WeakPtr<zxdb::Thread> thread, size_t arity, |
| std::function<void(const zxdb::Err&)> and_then) override { |
| // The order of parameters in the System V AMD64 ABI we use, according to |
| // Wikipedia: |
| static debug_ipc::RegisterID param_regs[] = { |
| debug_ipc::RegisterID::kX64_rdi, debug_ipc::RegisterID::kX64_rsi, |
| debug_ipc::RegisterID::kX64_rdx, debug_ipc::RegisterID::kX64_rcx, |
| debug_ipc::RegisterID::kX64_r8, debug_ipc::RegisterID::kX64_r9}; |
| constexpr size_t param_regs_size = |
| sizeof(param_regs) / sizeof(debug_ipc::RegisterID); |
| args_.reserve(arity); |
| size_t i = 0; |
| for (; i < arity && i < param_regs_size; i++) { |
| args_.push_back(GetRegisterValue<uint64_t>(regs_, param_regs[i])); |
| } |
| if (i == arity) { |
| // No more arguments to resolve. Returning. |
| and_then(zxdb::Err()); |
| return; |
| } |
| |
| // The remaining args are on the stack. The first arg is rsp + 8, the |
| // second is rsp + 16, and so on. |
| size_t memory_amount_to_read = sizeof(uint64_t) * (arity - i); |
| |
| GetMemoryAtAndThen<uint64_t[]>( |
| thread, stack_pointer_ + sizeof(uint64_t), memory_amount_to_read, |
| [this, thread, current = i, arity, and_then = std::move(and_then)]( |
| const zxdb::Err& err, std::unique_ptr<uint64_t[]> data) { |
| if (!thread || !err.ok()) { |
| and_then(err); |
| return; |
| } |
| if (data == nullptr) { |
| and_then(zxdb::Err(zxdb::ErrType::kGeneral, |
| "Unable to read params for syscall")); |
| return; |
| } |
| uint64_t* reg_arr = data.get(); |
| for (size_t i = current; i < arity; i++) { |
| // TODO: This assumes that all of the parameters are 64-bit. That's |
| // broken if they aren't. |
| args_.push_back(GetValueFromBytes<uint64_t>(reg_arr + i - current)); |
| } |
| and_then(zxdb::Err()); |
| }); |
| }; |
| |
| virtual uint64_t GetResult() const override { |
| return GetRegisterValue<uint64_t>(regs_, debug_ipc::RegisterID::kX64_rax); |
| } |
| }; |
| |
| // ARM specialization of CallingConventionDecoder above. |
| class CallingConventionDecoderArm : public CallingConventionDecoder { |
| public: |
| CallingConventionDecoderArm(const std::vector<zxdb::Register>& regs) |
| : CallingConventionDecoder(regs) { |
| stack_pointer_ = |
| GetRegisterValue<uint64_t>(regs_, debug_ipc::RegisterID::kARMv8_sp); |
| } |
| |
| virtual void PopulateArguments( |
| fxl::WeakPtr<zxdb::Thread> thread, size_t arity, |
| std::function<void(const zxdb::Err&)> and_then) override { |
| FXL_CHECK(arity <= 8) << "Too many arguments for ARM call"; |
| // The order of parameters in the System V ARM64 ABI we use, according to |
| // Wikipedia: |
| static debug_ipc::RegisterID param_regs[] = { |
| debug_ipc::RegisterID::kARMv8_x0, debug_ipc::RegisterID::kARMv8_x1, |
| debug_ipc::RegisterID::kARMv8_x2, debug_ipc::RegisterID::kARMv8_x3, |
| debug_ipc::RegisterID::kARMv8_x4, debug_ipc::RegisterID::kARMv8_x5, |
| debug_ipc::RegisterID::kARMv8_x6, debug_ipc::RegisterID::kARMv8_x7}; |
| constexpr size_t param_regs_size = |
| sizeof(param_regs) / sizeof(debug_ipc::RegisterID); |
| args_.reserve(arity); |
| size_t i = 0; |
| for (; i < arity && i < param_regs_size; i++) { |
| args_.push_back(GetRegisterValue<uint64_t>(regs_, param_regs[i])); |
| } |
| if (i == arity) { |
| // No more arguments to resolve. Returning. |
| and_then(zxdb::Err()); |
| return; |
| } |
| FXL_NOTREACHED() << "More than 8 arguments, yet fewer. How strange!"; |
| }; |
| |
| virtual uint64_t GetResult() const override { |
| return GetRegisterValue<uint64_t>(regs_, debug_ipc::RegisterID::kARMv8_x0); |
| } |
| |
| virtual std::optional<uint64_t> GetLinkRegister() const override { |
| return GetRegisterValue<uint64_t>(regs_, debug_ipc::RegisterID::kARMv8_lr); |
| } |
| }; |
| |
| CallingConventionDecoder* GetFetcherFor( |
| debug_ipc::Arch arch, const std::vector<zxdb::Register>& regs) { |
| if (arch == debug_ipc::Arch::kX64) { |
| return new CallingConventionDecoderX86(regs); |
| } else if (arch == debug_ipc::Arch::kArm64) { |
| return new CallingConventionDecoderArm(regs); |
| } |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| void ZxChannelParamsBuilder::BuildZxChannelParamsAndContinue( |
| fxl::WeakPtr<zxdb::Thread> thread, BreakpointRegisterer& registerer, |
| ZxChannelCallback&& fn) { |
| callback_ = std::move(fn); |
| |
| std::vector<debug_ipc::RegisterCategory::Type> register_types = { |
| debug_ipc::RegisterCategory::Type::kGeneral}; |
| |
| thread->ReadRegisters(register_types, [this, thread, ®isterer]( |
| const zxdb::Err& err, |
| const zxdb::RegisterSet& in_regs) { |
| const std::vector<zxdb::Register>* general_registers = |
| GetGeneralRegisters(thread, err, in_regs); |
| if (general_registers == nullptr) { |
| return; |
| } |
| |
| BuildAndContinue(GetFetcherFor(in_regs.arch(), *general_registers), thread, |
| *general_registers, registerer); |
| }); |
| } |
| |
| // Assuming that |thread| is stopped in a zx_channel_write, and that |regs| is |
| // the set of registers for that thread, and that both are on a connected x64 |
| // device, do what is necessary to populate |params| and pass them to the |
| // callback. |
| // |
| // This remains pretty brittle WRT the order of parameters to zx_channel_write |
| // and the calling conventions. The zx_channel_write parameters may change; |
| // we'll update as appropriate. |
| void ZxChannelWriteParamsBuilder::BuildAndContinue( |
| CallingConventionDecoder* fetcher, fxl::WeakPtr<zxdb::Thread> thread, |
| const std::vector<zxdb::Register>& regs, BreakpointRegisterer& registerer) { |
| if (fetcher == nullptr) { |
| Cancel(zxdb::Err(zxdb::ErrType::kCanceled, "Unknown arch")); |
| return; |
| } |
| fetcher->PopulateArguments( |
| thread, 6, [this, fetcher, thread](const zxdb::Err& err) { |
| std::unique_ptr<CallingConventionDecoder> f(fetcher); |
| if (!err.ok()) { |
| Cancel(err); |
| return; |
| } |
| if (!thread) { |
| Cancel(zxdb::Err(zxdb::ErrType::kGeneral, |
| "Error reading params: thread went away")); |
| return; |
| } |
| zx_handle_t handle = fetcher->GetArgument<zx_handle_t>(0); |
| uint32_t options = fetcher->GetArgument<uint32_t>(1); |
| uint64_t bytes_address = fetcher->GetArgument<uint64_t>(2); |
| uint32_t num_bytes = fetcher->GetArgument<uint32_t>(3); |
| uint64_t handles_address = fetcher->GetArgument<uint64_t>(4); |
| uint32_t num_handles = fetcher->GetArgument<uint32_t>(5); |
| |
| handle_ = handle; |
| options_ = options; |
| num_bytes_ = num_bytes; |
| num_handles_ = num_handles; |
| |
| // Note that the lambdas capture |this|. In typical use, |this| will be |
| // deleted by Finalize(). See the docs on |
| // ZxChannelParamsBuilder::BuildZxChannelParamsAndContinue for more |
| // detail. |
| GetMemoryAtAndThen<uint8_t[]>( |
| thread, bytes_address, num_bytes, |
| [this](const zxdb::Err& err, std::unique_ptr<uint8_t[]> data) { |
| bytes_ = std::move(data); |
| if ((num_handles_ == 0 || handles_ != nullptr) || !err.ok()) { |
| Finalize(); |
| } |
| }); |
| |
| GetMemoryAtAndThen<zx_handle_t[]>( |
| thread, handles_address, num_handles * sizeof(zx_handle_t), |
| [this](const zxdb::Err& err, std::unique_ptr<zx_handle_t[]> data) { |
| handles_ = std::move(data); |
| if ((num_bytes_ == 0 || bytes_ != nullptr) || !err.ok()) { |
| Finalize(); |
| } |
| }); |
| |
| if (num_handles == 0 && num_bytes == 0) { |
| Finalize(); |
| } |
| }); |
| } |
| |
| std::map<uint64_t, ZxChannelReadParamsBuilder::PerThreadState>& |
| ZxChannelReadParamsBuilder::GetPerThreadState() { |
| static std::map<uint64_t, ZxChannelReadParamsBuilder::PerThreadState> the_map; |
| return the_map; |
| } |
| |
| ZxChannelReadParamsBuilder::~ZxChannelReadParamsBuilder() { |
| GetPerThreadState().erase(thread_koid_); |
| } |
| |
| void ZxChannelReadParamsBuilder::GetResultAndContinue( |
| fxl::WeakPtr<zxdb::Thread> thread) { |
| PerThreadState state = GetPerThreadState()[thread_koid_]; |
| |
| // Read the filled in values for actual_bytes and actual_handles, then read |
| // the memory at those locations, and then finish. |
| if (state == PerThreadState::READING_ACTUAL_BYTES) { |
| // actual_bytes_ptr_ is allowed to be null. |
| if (actual_bytes_ptr_ == 0) { |
| num_bytes_ = 0; |
| GetPerThreadState()[thread_koid_] = |
| PerThreadState::READING_ACTUAL_HANDLES; |
| GetResultAndContinue(thread); |
| } else { |
| GetMemoryAtAndThen<uint32_t>( |
| thread, actual_bytes_ptr_, sizeof(uint32_t), |
| [this, thread](const zxdb::Err& err, std::unique_ptr<uint32_t> data) { |
| if (!thread || !err.ok()) { |
| Cancel(err); |
| return; |
| } |
| if (data != nullptr) { |
| num_bytes_ = *data; |
| GetPerThreadState()[thread_koid_] = |
| PerThreadState::READING_ACTUAL_HANDLES; |
| } else { |
| Cancel(zxdb::Err(zxdb::ErrType::kGeneral, |
| "Malformed zx_channel_read call")); |
| return; |
| } |
| GetResultAndContinue(thread); |
| }); |
| } |
| } else if (state == PerThreadState::READING_ACTUAL_HANDLES) { |
| // actual_handles_ptr_ is allowed to be null. |
| if (actual_handles_ptr_ == 0) { |
| num_handles_ = 0; |
| GetPerThreadState()[thread_koid_] = PerThreadState::FILLING_IN_BYTES; |
| GetResultAndContinue(thread); |
| } else { |
| GetMemoryAtAndThen<uint32_t>( |
| thread, actual_handles_ptr_, sizeof(uint32_t), |
| [this, thread](const zxdb::Err& err, std::unique_ptr<uint32_t> data) { |
| if (!thread || !err.ok()) { |
| Cancel(err); |
| return; |
| } |
| num_handles_ = *data; |
| GetPerThreadState()[thread_koid_] = |
| PerThreadState::FILLING_IN_BYTES; |
| GetResultAndContinue(thread); |
| }); |
| } |
| } else if (state == PerThreadState::FILLING_IN_BYTES) { |
| if (num_bytes_ == 0) { |
| GetPerThreadState()[thread_koid_] = PerThreadState::FILLING_IN_HANDLES; |
| GetResultAndContinue(thread); |
| return; |
| } |
| GetMemoryAtAndThen<uint8_t[]>( |
| thread, bytes_address_, num_bytes_, |
| [this, thread](const zxdb::Err& err, std::unique_ptr<uint8_t[]> data) { |
| if (!thread || !err.ok()) { |
| Cancel(err); |
| return; |
| } |
| bytes_ = std::move(data); |
| if (num_handles_ == 0 || !err_.ok()) { |
| Finalize(); |
| } else { |
| GetPerThreadState()[thread_koid_] = |
| PerThreadState::FILLING_IN_HANDLES; |
| GetResultAndContinue(thread); |
| } |
| }); |
| } else if (state == PerThreadState::FILLING_IN_HANDLES) { |
| if (num_handles_ == 0) { |
| Finalize(); |
| return; |
| } |
| GetMemoryAtAndThen<zx_handle_t[]>( |
| thread, handles_address_, num_handles_ * sizeof(zx_handle_t), |
| [this, thread](const zxdb::Err& err, |
| std::unique_ptr<zx_handle_t[]> data) { |
| if (!thread || !err.ok()) { |
| Cancel(err); |
| return; |
| } |
| handles_ = std::move(data); |
| Finalize(); |
| }); |
| } |
| } |
| |
| // Assuming that |thread| is stopped in a zx_channel_read, and that |regs| is |
| // the set of registers for that thread, and that both are on a connected |
| // device, do what is necessary to populate |params| and pass them to the |
| // callback. |
| // |
| // This remains pretty brittle WRT the order of parameters to zx_channel_read |
| // and calling conventions. Those things aren't likely to change, but |
| // if they did, we'd have to update this. |
| void ZxChannelReadParamsBuilder::BuildAndContinue( |
| CallingConventionDecoder* fetcher, fxl::WeakPtr<zxdb::Thread> thread, |
| const std::vector<zxdb::Register>& regs, BreakpointRegisterer& registerer) { |
| if (fetcher == nullptr) { |
| Cancel(zxdb::Err(zxdb::ErrType::kCanceled, "Unknown arch")); |
| return; |
| } |
| std::optional<uint64_t> link_register = fetcher->GetLinkRegister(); |
| |
| thread_koid_ = thread->GetKoid(); |
| registerer_ = ®isterer; |
| |
| once_ = false; |
| first_sp_ = fetcher->GetStackPointer(); |
| |
| fetcher->PopulateArguments( |
| thread, 8, [this, thread, fetcher, link_register](const zxdb::Err& err) { |
| std::unique_ptr<CallingConventionDecoder> f(fetcher); |
| if (!err.ok()) { |
| Cancel(err); |
| return; |
| } |
| if (!thread) { |
| Cancel(zxdb::Err(zxdb::ErrType::kGeneral, |
| "Error reading params: thread went away")); |
| return; |
| } |
| handle_ = fetcher->GetArgument<zx_handle_t>(0); |
| options_ = fetcher->GetArgument<uint32_t>(1); |
| bytes_address_ = fetcher->GetArgument<uint64_t>(2); |
| handles_address_ = fetcher->GetArgument<uint64_t>(3); |
| // The num_bytes and num_handles values are not useful, and are only |
| // included here for documentation purposes: |
| // num_bytes_ = fetcher->GetArgument(4); |
| // num_handles_ = fetcher->GetArgument(5); |
| actual_bytes_ptr_ = fetcher->GetArgument<uint64_t>(6); |
| actual_handles_ptr_ = fetcher->GetArgument<uint64_t>(7); |
| GetPerThreadState()[thread_koid_] = PerThreadState::STEPPING; |
| if (link_register) { |
| FinishChannelReadArm(thread, *link_register); |
| } else { |
| FinishChannelReadX86(thread); |
| } |
| }); |
| } |
| |
| // Advance until the stack pointer increases (i.e., the stack frame has popped) |
| void ZxChannelReadParamsBuilder::FinishChannelReadX86( |
| fxl::WeakPtr<zxdb::Thread> thread) { |
| PerThreadState state = GetPerThreadState()[thread_koid_]; |
| |
| if (state == PerThreadState::STEPPING) { |
| // Then we step... |
| auto controller = std::make_unique<zxdb::StepThreadController>( |
| zxdb::StepMode::kSourceLine); |
| controller->set_stop_on_no_symbols(false); |
| GetPerThreadState()[thread_koid_] = PerThreadState::CHECKING_STEP; |
| thread->ContinueWith( |
| std::move(controller), [this, thread](const zxdb::Err& err) { |
| if (!thread || !err.ok()) { |
| Cancel(err); |
| return; |
| } |
| if (thread) { |
| registerer_->Register(thread->GetKoid(), |
| [this, thread](zxdb::Thread* t) { |
| // TODO: I think the post-stepping stack |
| // pointer may be in *thread somewhere. |
| FinishChannelReadX86(thread); |
| }); |
| } |
| }); |
| } else if (state == PerThreadState::CHECKING_STEP) { |
| // ... and we continue to step until the stack pointer increases, indicating |
| // that we've exited the method. |
| std::vector<debug_ipc::RegisterCategory::Type> register_types = { |
| debug_ipc::RegisterCategory::Type::kGeneral}; |
| thread->ReadRegisters( |
| register_types, |
| [this, thread](const zxdb::Err& err, const zxdb::RegisterSet& in_regs) { |
| auto general_registers = GetGeneralRegisters(thread, err, in_regs); |
| if (general_registers == nullptr) { |
| return; |
| } |
| std::unique_ptr<CallingConventionDecoder> fetcher( |
| GetFetcherFor(in_regs.arch(), *general_registers)); |
| FXL_CHECK(fetcher != nullptr) << "No architecture found"; |
| |
| // See if the stack pointer has regressed, if not, step some more. |
| uint64_t stack_pointer = fetcher->GetStackPointer(); |
| if (stack_pointer > first_sp_) { |
| int64_t result = fetcher->GetReturnValue<uint64_t>(); |
| if (result < 0) { |
| std::string message = |
| "aborted zx_channel_read (errno=" + std::to_string(result) + |
| ")"; |
| err_ = zxdb::Err(zxdb::ErrType::kGeneral, message); |
| Finalize(); |
| } else { |
| GetPerThreadState()[thread->GetKoid()] = |
| PerThreadState::READING_ACTUAL_BYTES; |
| GetResultAndContinue(thread); |
| } |
| } else { |
| GetPerThreadState()[thread->GetKoid()] = PerThreadState::STEPPING; |
| FinishChannelReadX86(thread); |
| } |
| }); |
| } |
| } |
| |
| // Advance to wherever the link register says the return location of the |
| // zx_channel_read is. |
| void ZxChannelReadParamsBuilder::FinishChannelReadArm( |
| fxl::WeakPtr<zxdb::Thread> thread, uint64_t link_register_contents) { |
| PerThreadState state = GetPerThreadState()[thread_koid_]; |
| if (state == PerThreadState::STEPPING) { |
| zxdb::BreakpointSettings settings; |
| settings.enabled = true; |
| settings.stop_mode = zxdb::BreakpointSettings::StopMode::kThread; |
| settings.type = debug_ipc::BreakpointType::kSoftware; |
| settings.location.address = link_register_contents; |
| settings.location.type = zxdb::InputLocation::Type::kAddress; |
| settings.scope = zxdb::BreakpointSettings::Scope::kThread; |
| settings.scope_thread = &(*thread); |
| settings.scope_target = thread->GetProcess()->GetTarget(); |
| settings.one_shot = true; |
| registerer_->CreateNewBreakpoint(settings); |
| registerer_->Register(thread->GetKoid(), [this, thread](zxdb::Thread* t) { |
| std::vector<debug_ipc::RegisterCategory::Type> register_types = { |
| debug_ipc::RegisterCategory::Type::kGeneral}; |
| thread->ReadRegisters( |
| register_types, [this, thread](const zxdb::Err& err, |
| const zxdb::RegisterSet& in_regs) { |
| auto general_registers = GetGeneralRegisters(thread, err, in_regs); |
| if (general_registers == nullptr) { |
| return; |
| } |
| std::unique_ptr<CallingConventionDecoder> fetcher( |
| GetFetcherFor(in_regs.arch(), *general_registers)); |
| FXL_CHECK(fetcher != nullptr) << "No architecture found"; |
| |
| int64_t result = fetcher->GetReturnValue<int64_t>(); |
| if (result < 0) { |
| std::string message = |
| "aborted zx_channel_read (errno=" + std::to_string(result) + |
| ")"; |
| err_ = zxdb::Err(zxdb::ErrType::kGeneral, message); |
| Finalize(); |
| } else { |
| GetPerThreadState()[thread_koid_] = |
| PerThreadState::READING_ACTUAL_BYTES; |
| GetResultAndContinue(thread); |
| } |
| }); |
| }); |
| thread->Continue(); |
| } |
| } |
| |
| } // namespace fidlcat |