| // Copyright 2020 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/debug_agent/zircon_process_handle.h" | |
| #include "src/developer/debug/debug_agent/elf_utils.h" | |
| #include "src/developer/debug/debug_agent/process_handle_observer.h" | |
| #include "src/developer/debug/debug_agent/zircon_exception_handle.h" | |
| #include "src/developer/debug/debug_agent/zircon_thread_handle.h" | |
| #include "src/developer/debug/debug_agent/zircon_utils.h" | |
| #include "src/developer/debug/ipc/records.h" | |
| #include "src/developer/debug/shared/message_loop_target.h" | |
| namespace debug_agent { | |
| ZirconProcessHandle::ZirconProcessHandle(zx::process p) | |
| : process_koid_(zircon::KoidForObject(p)), process_(std::move(p)) {} | |
| std::string ZirconProcessHandle::GetName() const { return zircon::NameForObject(process_); } | |
| std::vector<std::unique_ptr<ThreadHandle>> ZirconProcessHandle::GetChildThreads() const { | |
| std::vector<std::unique_ptr<ThreadHandle>> result; | |
| for (auto& child : zircon::GetChildThreads(process_)) | |
| result.push_back(std::make_unique<ZirconThreadHandle>(std::move(child))); | |
| return result; | |
| } | |
| zx_status_t ZirconProcessHandle::Kill() { return process_.kill(); } | |
| int64_t ZirconProcessHandle::GetReturnCode() const { | |
| zx_info_process info = {}; | |
| if (process_.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr) == ZX_OK) | |
| return info.return_code; | |
| return 0; | |
| } | |
| zx_status_t ZirconProcessHandle::Attach(ProcessHandleObserver* observer) { | |
| FX_DCHECK(observer); | |
| observer_ = observer; | |
| if (!process_watch_handle_.watching()) { | |
| // Start watching. | |
| debug_ipc::MessageLoopTarget* loop = debug_ipc::MessageLoopTarget::Current(); | |
| FX_DCHECK(loop); // Loop must be created on this thread first. | |
| // Register for debug exceptions. | |
| debug_ipc::MessageLoopTarget::WatchProcessConfig config; | |
| config.process_name = GetName(); | |
| config.process_handle = process_.get(); | |
| config.process_koid = GetKoid(); | |
| config.watcher = this; | |
| return loop->WatchProcessExceptions(std::move(config), &process_watch_handle_); | |
| } | |
| return ZX_OK; | |
| } | |
| void ZirconProcessHandle::Detach() { | |
| observer_ = nullptr; | |
| // Unbind from the exception port. | |
| process_watch_handle_.StopWatching(); | |
| } | |
| std::vector<debug_ipc::AddressRegion> ZirconProcessHandle::GetAddressSpace(uint64_t address) const { | |
| std::vector<debug_ipc::AddressRegion> regions; | |
| std::vector<zx_info_maps_t> map = GetMaps(); | |
| if (address) { | |
| // Get a specific region. | |
| for (const auto& entry : map) { | |
| if (address < entry.base) | |
| continue; | |
| if (address <= (entry.base + entry.size)) | |
| regions.push_back({entry.name, entry.base, entry.size, entry.depth}); | |
| } | |
| } else { | |
| // Get all regions. | |
| size_t ix = 0; | |
| regions.resize(map.size()); | |
| for (const auto& entry : map) { | |
| regions[ix].name = entry.name; | |
| regions[ix].base = entry.base; | |
| regions[ix].size = entry.size; | |
| regions[ix].depth = entry.depth; | |
| ++ix; | |
| } | |
| } | |
| return regions; | |
| } | |
| std::vector<debug_ipc::Module> ZirconProcessHandle::GetModules(uint64_t dl_debug_addr) const { | |
| return GetElfModulesForProcess(*this, dl_debug_addr); | |
| } | |
| fitx::result<zx_status_t, std::vector<debug_ipc::InfoHandleExtended>> | |
| ZirconProcessHandle::GetHandles() const { | |
| // Query the table size. | |
| size_t actual = 0; | |
| size_t avail = 0; | |
| if (zx_status_t status = process_.get_info(ZX_INFO_HANDLE_TABLE, nullptr, 0, &actual, &avail); | |
| status != ZX_OK) | |
| return fitx::error(status); | |
| // We're technically racing with the program, so add some extra buffer in case the process has | |
| // opened more handles since the above query. | |
| avail += 64; | |
| std::vector<zx_info_handle_extended_t> handles(avail); | |
| if (zx_status_t status = | |
| process_.get_info(ZX_INFO_HANDLE_TABLE, handles.data(), | |
| avail * sizeof(zx_info_handle_extended_t), &actual, &avail); | |
| status != ZX_OK) | |
| return fitx::error(status); | |
| std::vector<debug_ipc::InfoHandleExtended> result(actual); | |
| for (size_t i = 0; i < actual; ++i) { | |
| result[i].type = handles[i].type; | |
| result[i].handle_value = handles[i].handle_value; | |
| result[i].rights = handles[i].rights; | |
| result[i].koid = handles[i].koid; | |
| result[i].related_koid = handles[i].related_koid; | |
| result[i].peer_owner_koid = handles[i].peer_owner_koid; | |
| } | |
| return fitx::success(std::move(result)); | |
| } | |
| zx_status_t ZirconProcessHandle::ReadMemory(uintptr_t address, void* buffer, size_t len, | |
| size_t* actual) const { | |
| return process_.read_memory(address, buffer, len, actual); | |
| } | |
| zx_status_t ZirconProcessHandle::WriteMemory(uintptr_t address, const void* buffer, size_t len, | |
| size_t* actual) { | |
| return process_.write_memory(address, buffer, len, actual); | |
| } | |
| std::vector<debug_ipc::MemoryBlock> ZirconProcessHandle::ReadMemoryBlocks(uint64_t address, | |
| uint32_t size) const { | |
| // Optimistically assume the read will work which will be faster in the common case. | |
| if (debug_ipc::MemoryBlock block = ReadOneMemoryBlock(address, size); block.valid) | |
| return {std::move(block)}; | |
| // Failure reading, this memory is either not mapped or it may cross mapping boundaries. To solve | |
| // the multiple boundary problem, get the memory mapping and compute all mapping boundaries in the | |
| // requested region. Then try to read each of the resulting blocks (which may be valid or | |
| // invalid). | |
| // | |
| // This computed boundaries array will contain all boundaries (including the end address and some | |
| // duplicates) except the begin address (this will be implicit in the later computation). | |
| std::vector<uint64_t> boundaries; | |
| for (const zx_info_maps_t& map : GetMaps()) { | |
| // The returned maps should be sorted so any mapping region starting past our region means all | |
| // relevant boundaries have been found. | |
| if (map.base > address + size) | |
| break; | |
| if (map.base > address) | |
| boundaries.push_back(map.base); | |
| uint64_t end = map.base + map.size; | |
| if (end > address && end < address + size) | |
| boundaries.push_back(end); | |
| } | |
| boundaries.push_back(address + size); | |
| std::sort(boundaries.begin(), boundaries.end()); | |
| std::vector<debug_ipc::MemoryBlock> blocks; | |
| uint64_t begin = address; | |
| for (uint64_t end : boundaries) { | |
| // There will be some duplicates in the boundaries array so skip anything that's empty. These | |
| // duplicates are caused by a range which a child inside it that is coincident with one of the | |
| // parent boundaries, or two regions that abut each other. | |
| if (end == begin) | |
| continue; | |
| blocks.push_back(ReadOneMemoryBlock(begin, static_cast<uint32_t>(end - begin))); | |
| begin = end; | |
| } | |
| return blocks; | |
| } | |
| debug_ipc::MemoryBlock ZirconProcessHandle::ReadOneMemoryBlock(uint64_t address, | |
| uint32_t size) const { | |
| debug_ipc::MemoryBlock block; | |
| block.address = address; | |
| block.size = size; | |
| block.data.resize(size); | |
| size_t bytes_read = 0; | |
| if (process_.read_memory(address, &block.data[0], block.size, &bytes_read) == ZX_OK && | |
| bytes_read == size) { | |
| block.valid = true; | |
| } else { | |
| block.valid = false; | |
| block.data.resize(0); | |
| } | |
| return block; | |
| } | |
| std::vector<zx_info_maps_t> ZirconProcessHandle::GetMaps() const { | |
| const size_t kRegionsCountGuess = 64u; | |
| const size_t kNewRegionsCountGuess = 4u; | |
| size_t count_guess = kRegionsCountGuess; | |
| std::vector<zx_info_maps_t> map; | |
| size_t actual; | |
| size_t avail; | |
| while (true) { | |
| map.resize(count_guess); | |
| zx_status_t status = process_.get_info(ZX_INFO_PROCESS_MAPS, &map[0], | |
| sizeof(zx_info_maps) * map.size(), &actual, &avail); | |
| if (status != ZX_OK) { | |
| actual = 0; | |
| break; | |
| } else if (actual == avail) { | |
| break; | |
| } | |
| count_guess = avail + kNewRegionsCountGuess; | |
| } | |
| map.resize(actual); | |
| return map; | |
| } | |
| void ZirconProcessHandle::OnProcessTerminated(zx_koid_t process_koid) { | |
| FX_DCHECK(observer_); | |
| FX_DCHECK(process_koid == GetKoid()); | |
| observer_->OnProcessTerminated(); | |
| } | |
| void ZirconProcessHandle::OnThreadStarting(zx::exception exception, zx_exception_info_t info) { | |
| FX_DCHECK(observer_); | |
| observer_->OnThreadStarting(std::make_unique<ZirconExceptionHandle>(std::move(exception), info)); | |
| } | |
| void ZirconProcessHandle::OnThreadExiting(zx::exception exception, zx_exception_info_t info) { | |
| FX_DCHECK(observer_); | |
| observer_->OnThreadExiting(std::make_unique<ZirconExceptionHandle>(std::move(exception), info)); | |
| } | |
| void ZirconProcessHandle::OnException(zx::exception exception, zx_exception_info_t info) { | |
| FX_DCHECK(observer_); | |
| observer_->OnException(std::make_unique<ZirconExceptionHandle>(std::move(exception), info)); | |
| } | |
| } // namespace debug_agent |