| // Copyright 2022 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. |
| |
| #ifndef SRC_LIB_ZXDUMP_INCLUDE_LIB_ZXDUMP_TASK_H_ |
| #define SRC_LIB_ZXDUMP_INCLUDE_LIB_ZXDUMP_TASK_H_ |
| |
| #include <lib/elfldltl/constants.h> |
| #include <lib/fit/result.h> |
| #include <lib/stdcompat/version.h> |
| #include <zircon/errors.h> |
| #include <zircon/syscalls/debug.h> |
| |
| #include <cstddef> |
| #include <cstring> |
| #include <functional> |
| #include <map> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include <fbl/unique_fd.h> |
| |
| #ifdef __Fuchsia__ |
| #include <lib/zx/handle.h> |
| #endif |
| |
| #include "buffer.h" |
| #include "types.h" |
| |
| namespace zxdump { |
| |
| constexpr size_t kReadMemoryStringLimit = 1024; |
| |
| // This is the type of the optional argument to Process::read_memory, below. |
| enum class ReadMemorySize { |
| // Return exactly the amount requested, never more or less--except that an |
| // emtpy buffer may be returned for memory elided from the dump. |
| kExact, |
| |
| // Return at least the amount requested, and possibly more if convenient. |
| // (Still return an empty buffer for elided memory.) |
| kMore, |
| |
| // Return any amount conveniently available. An empty buffer still means the |
| // requested memory was elided entirely. But for any nonempty buffer less |
| // than what was requested, the caller should make an additional read_memory |
| // call at a higher address to get the remainder piecemeal; a later call will |
| // fail outright if that higher address is not valid in the process. |
| // |
| // This mode is recommended for large reads that don't need to be contiguous |
| // in a single buffer, or that will be copied elsewhere anyway. It allows |
| // Process::read_memory to minimize data copying. |
| kLess, |
| }; |
| |
| // On Fuchsia, live task handles can be used via the lib/zx API. On other |
| // systems, the API parts for live tasks are still available but they use a |
| // stub handle type that is always invalid. |
| #ifdef __Fuchsia__ |
| using LiveHandle = zx::handle; |
| #else |
| // The stub type is move-only and contextually convertible to bool just like |
| // the real one. It supports only a few basic methods, which do nothing and |
| // always report an invalid handle. |
| struct LiveHandle { |
| LiveHandle() = default; |
| LiveHandle(const LiveHandle&) = delete; |
| LiveHandle(LiveHandle&&) = default; |
| LiveHandle& operator=(const LiveHandle&) = delete; |
| LiveHandle& operator=(LiveHandle&&) = default; |
| |
| void reset() {} |
| |
| bool is_valid() const { return false; } |
| |
| explicit operator bool() const { return is_valid(); } |
| |
| zx_status_t get_info(uint32_t topic, void* buffer, size_t buffer_size, size_t* actual_count, |
| size_t* avail_count) const { |
| return ZX_ERR_BAD_HANDLE; |
| } |
| |
| zx_status_t get_property(uint32_t property, void* value, size_t size) const { |
| return ZX_ERR_BAD_HANDLE; |
| } |
| |
| zx_status_t get_child(uint64_t koid, zx_rights_t rights, LiveHandle* result) const { |
| return ZX_ERR_BAD_HANDLE; |
| } |
| }; |
| #endif |
| |
| // This is an opaque type used internally. |
| namespace internal { |
| class DumpFile; |
| } // namespace internal |
| |
| // Forward declarations for below. |
| class Object; |
| class Resource; |
| class Task; |
| class Job; |
| class Process; |
| class Thread; |
| |
| // This is an opaque type used internally. |
| namespace internal { |
| class DumpFile; |
| } // namespace internal |
| |
| // This is the API for reading in dumps, both `ET_CORE` files and job archives. |
| // |
| // The zxdump::TaskHolder object is a container that holds the data from any |
| // number of dump files. It provides access to the data as a Zircon job tree. |
| // The Job, Process, and Thread objects represent the jobs and processes found |
| // in dump files. Each object provides calls analogous to the Zircon get_info, |
| // get_property, read_memory, and read_state calls, as well as get_child for |
| // nagivating a task tree. |
| // |
| // Dumps are inserted into the container by providing the file descriptor. The |
| // type of file will be determined automatically from its contents. Dump files |
| // can be ELF core dump (`ET_CORE`) files, or `ar` archive files. An archive |
| // file can be a job archive or just a plain archive of other dump files. Job |
| // archives can be mere "stub archives", or full hierarchical job archives, or |
| // flattened job archives. |
| // |
| // All the jobs and processes found in the dumps inserted then self-assemble |
| // into a job tree. If the same task (same KOID) appears a second time either |
| // in two dump files or in two members of a job archive, insertion fails but |
| // may have added some of the tasks from the dump anyway. |
| // |
| // If every process and every job but one is a child of another job found in |
| // the dump so they all form a single job tree, then `root_job` returns the |
| // root of that tree. If not, then `root_job` returns a fake "root job" with a |
| // KOID of 0 and no information or properties available except for the children |
| // and process lists. These show all the jobs that don't have parent jobs that |
| // were dumped, i.e. the roots of job trees; and all the processes that aren't |
| // part of any dumped job at all. Hence populating the container with a single |
| // ELF core dump will yield a fake root job whose sole child is that process. |
| // |
| // Methods that can fail use a result type with zxdump::Error. When the |
| // `status_` field is ZX_ERR_IO, that means the failure was in a POSIXish |
| // filesystem access function and `errno` is set to indicate the exact error. |
| // Otherwise the error codes have mostly the same meaning they would have for |
| // the real Zircon calls, with some amendments: |
| // |
| // * ZX_ERR_NOT_SUPPORTED just means the dump didn't include the requested |
| // type of data. It doesn't indicate whether the kernel didn't support it, |
| // or the dump-writer intentionally chose not to dump it, or the dump was |
| // just truncated, etc. |
| // |
| // * Process::read_memory fails with ZX_ERR_NOT_FOUND if the dump indicated |
| // the memory mapping existed but the dump did not include that memory. |
| // ZX_ERR_OUT_OF_RANGE means the memory is absent because the dump was |
| // truncated though this memory was intended to be included in the dump. |
| // ZX_ERR_NO_MEMORY has the kernel's meaning that there was no memory |
| // mapped at that address in the process. ZX_ERR_NOT_SUPPORTED means that |
| // the dump was inserted with the `read_memory=false` flag. |
| // |
| class TaskHolder { |
| public: |
| // Default-constructible, move-only. |
| TaskHolder(); |
| TaskHolder(TaskHolder&&) = default; |
| TaskHolder& operator=(TaskHolder&&) = default; |
| ~TaskHolder(); |
| |
| // Read the dump file from the file descriptor and insert its tasks. If |
| // `read_memory` is false, state will be trimmed after reading in all the |
| // notes so less memory is used and the file descriptor is never kept open; |
| // but read_memory calls will always fail with ZX_ERR_NOT_SUPPORTED. |
| fit::result<Error> Insert(fbl::unique_fd fd, bool read_memory = true); |
| |
| // Insert a live task (job or process) or resource. Live threads cannot be |
| // inserted alone, only their containing process. |
| fit::result<Error, std::reference_wrapper<Object>> Insert(LiveHandle obj); |
| |
| // Insert system data (returned by system_get_*, below) taken from the |
| // currently running system. |
| fit::result<Error> InsertSystem(); |
| |
| // Yields the current root job. If all tasks in the eye of the TaskHolder |
| // form a unified tree, this returns the actual root job in that tree. |
| // Otherwise, this is the fake "root job" that reads as KOID 0 with no data |
| // available except the Job::children and Job::processes lists holding each |
| // orphaned task not claimed by any parent job. It's always safe to hold |
| // onto this reference for the life of the TaskHolder. If more tasks are |
| // added, this will start returning a different reference. An old reference |
| // to the fake root job will read as having no children and no processes if |
| // all the tasks self-assembled into a single tree after more dumps were |
| // inserted, and later start reporting new orphan tasks inserted after that. |
| Job& root_job() const; |
| |
| // Yields the root resource. If a live handle to the root resource was |
| // passed to Insert, this can access its data. If dumps inserted include the |
| // "privileged kernel" information, that is attached to this fake root |
| // resource though it doesn't have information like a KOID itself. If |
| // multiple dumps supply kernel information, conflicting data inserted later |
| // will be ignored. If both live and dump data are available, the dump data |
| // will obscure the live data. |
| Resource& root_resource() const; |
| |
| // These can't fail, but return empty/zero if no corresponding data is in the |
| // dump. If multiple dumps supply system-wide information, only the first |
| // dump's data will be used. There is no checking that the system-wide data |
| // in dumps is valid; malformed data may be treated like no data at all but |
| // still may prevent well-formed data in other dumps from being used. |
| uint32_t system_get_dcache_line_size() const; |
| uint32_t system_get_num_cpus() const; |
| uint64_t system_get_page_size() const; |
| uint64_t system_get_physmem() const; |
| std::string_view system_get_version_string() const; |
| |
| // Get and set the limit (in bytes) of unreferenced Process::read_memory |
| // pages from live processes that may be cached for reuse. |
| size_t memory_cache_limit() const; |
| void set_memory_cache_limit(size_t limit); |
| |
| private: |
| friend Object; |
| friend Task; |
| friend Job; |
| friend Process; |
| friend Thread; |
| friend Resource; |
| class JobTree; |
| class LiveMemoryCache; |
| |
| std::unique_ptr<JobTree> tree_; |
| }; |
| |
| // This is the superclass of all (virtual) kernel object types. |
| // All the methods here correspond to the generic zx::object methods. |
| class Object { |
| public: |
| // None of the Object types can be constructed in any way. |
| // They are only used via references returned by the owning TaskHolder. |
| Object(const Object&) = delete; |
| |
| // The subclass constructors need to be public so that they can be called by |
| // the std::optional and std::map emplace methods, but they are intended as |
| // private so none of these objects can exist outside the TaskHolder. Since |
| // the JobTree& is a required argument in the subclass constructors and the |
| // class is private, outside code cannot construct new objects. |
| explicit Object(TaskHolder::JobTree& tree, LiveHandle live = {}) |
| : tree_{tree}, live_(std::move(live)) {} |
| |
| struct WaitItem { |
| std::reference_wrapper<Object> handle; |
| zx_signals_t waitfor = 0; |
| zx_signals_t pending = 0; |
| }; |
| |
| using WaitItemVector = std::vector<WaitItem>; |
| |
| // Every task has a KOID. This is just shorthand for extracting it from |
| // ZX_INFO_HANDLE_BASIC. The fake root job returns zero (ZX_KOID_INVALID). |
| zx_koid_t koid() const; |
| |
| // This is a shorthand for extracting the type from ZX_INFO_HANDLE_BASIC. |
| // * If it returns ZX_OBJ_TYPE_JOB, `static_cast<Job&>(*this)` is safe. |
| // * If it returns ZX_OBJ_TYPE_PROCESS, `static_cast<Process&>(*this)` is. |
| // * If it returns ZX_OBJ_TYPE_THREAD, `static_cast<Thread&>(*this)` is. |
| // * If it returns ZX_OBJ_TYPE_RESOURCE, `static_cast<Resource&>(*this)` is. |
| |
| // The only objects on which get_info<ZX_INFO_HANDLE_BASIC> can fail are the |
| // fake root job and the fake root resource; type() on those returns zero |
| // (ZX_OBJ_TYPE_NONE). |
| zx_obj_type_t type() const; |
| |
| // This returns the timestamp of the dump, which may be zero. |
| time_t date() const { return date_; } |
| |
| // This is provided for parity with zx::object::get_child, but just using |
| // Process::threads, Job::children, or Job::processes is much more convenient |
| // for iterating through the lists reported by get_info. However, on live |
| // tasks either get_child or find to reach specific known KOIDs can be much |
| // more efficient than accessing the one of the child-list methods, which |
| // will acquire all the child handles implicitly, not just the matching one. |
| // |
| // **Notes for live objects:** Unlike zx::object::get_child, this does not |
| // take a zx_rights_t parameter even when referring to a live object. |
| // Instead, it always requests the full suite of rights that are necessary to |
| // do get_info, get_property, read_memory, read_state, etc.--everything the |
| // dumper usually needs. There is only ever a single Object for each KOID, |
| // so returning live object handle with lesser rights once would mean later |
| // calls couldn't use greater rights on the same KOID later. |
| fit::result<Error, std::reference_wrapper<Object>> get_child(zx_koid_t koid); |
| |
| // Find a task by KOID: this task or a descendent task. |
| fit::result<Error, std::reference_wrapper<Object>> find(zx_koid_t koid); |
| |
| // This gets the full info block for this topic, whatever its size. Note the |
| // data is not necessarily aligned in memory, so it can't be safely accessed |
| // with reinterpret_cast. |
| // |
| // **Notes for live objects:** When called on a live object, this ordinarily |
| // gets each topic just once and then returns the cached value; the optional |
| // refresh_live flag says to ignore any cached data and always get fresh data |
| // now (which will be cached for later calls without the flag). Note that a |
| // failed call with refresh_live will *not* remove old cached data, so it |
| // might be retrieved by calling again without the flag even if something |
| // prevents getting new info. |
| fit::result<Error, ByteView> get_info(zx_object_info_topic_t topic, bool refresh_live = false, |
| size_t record_size = 0); |
| |
| // Get statically-typed info for a topic chosen at a compile time. Some |
| // types return a single `zx_info_*_t` object. Others return a span of const |
| // type that points into storage permanently cached for the lifetime of the |
| // containing TaskHolder. See <lib/zxdump/types.h> for topic->type mappings. |
| template <zx_object_info_topic_t Topic> |
| fit::result<Error, InfoTraitsType<Topic>> get_info(bool refresh_live = false) { |
| using Info = typename InfoTraits<Topic>::type; |
| if constexpr (kIsSpan<Info>) { |
| using Element = typename RemoveSpan<Info>::type; |
| auto result = get_info_aligned(Topic, sizeof(Element), alignof(Element), refresh_live); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| return fit::ok(Info{ |
| reinterpret_cast<Element*>(result.value().data()), |
| result.value().size() / sizeof(Element), |
| }); |
| } else { |
| auto result = get_info(Topic, refresh_live, sizeof(Info)); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| ByteView bytes = result.value(); |
| Info data; |
| if (bytes.size() < sizeof(data)) { |
| return fit::error(Error{"truncated info note", ZX_ERR_NOT_SUPPORTED}); |
| } |
| memcpy(&data, bytes.data(), sizeof(data)); |
| return fit::ok(data); |
| } |
| } |
| |
| // This gets the property, whatever its size. Note the data is not |
| // necessarily aligned in memory, so it can't be safely accessed with |
| // reinterpret_cast. |
| fit::result<Error, ByteView> get_property(uint32_t property); |
| |
| // Get a statically-typed property chosen at compile time. |
| // See <lib/zxdump/types.h> for property->type mappings. |
| template <uint32_t Property> |
| fit::result<Error, PropertyTraitsType<Property>> get_property() { |
| auto result = get_property(Property); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| ByteView bytes = result.value(); |
| PropertyTraitsType<Property> data; |
| if (bytes.size() < sizeof(data)) { |
| return fit::error(Error{"truncated property note", ZX_ERR_NOT_SUPPORTED}); |
| } |
| memcpy(&data, bytes.data(), sizeof(data)); |
| return fit::ok(data); |
| } |
| |
| static fit::result<Error> wait_many(WaitItemVector& wait_items); |
| |
| bool is_live() const { return live_.is_valid(); } |
| |
| // As a convenience, TaskHolder::root_resource() is proxied by every Object. |
| Resource& root_resource(); |
| |
| // A job or process can have dump remarks, stored as a vector of {name, data} |
| // pairs. |
| const auto& remarks() const { return remarks_; } |
| |
| protected: |
| // The class is abstract. Only the subclasses can be created and destroyed. |
| Object() = delete; |
| |
| // Move construction and assignment are only used during initial creation. |
| Object(Object&&) noexcept = default; |
| Object& operator=(Object&&) noexcept = default; |
| |
| ~Object(); |
| |
| LiveHandle& live() { return live_; } |
| |
| TaskHolder::JobTree& tree() { return tree_; } |
| |
| bool empty() const { return info_.empty(); } |
| |
| std::byte* GetBuffer(size_t size); |
| void TakeBuffer(std::unique_ptr<std::byte[]> buffer); |
| |
| private: |
| friend TaskHolder::JobTree; |
| |
| fit::result<Error, ByteView> get_info_aligned(zx_object_info_topic_t topic, size_t record_size, |
| size_t align, bool refresh_live); |
| fit::result<Error, ByteView> GetSuperrootInfo(zx_object_info_topic_t topic); |
| |
| // A plain reference would make the type not movable. |
| std::reference_wrapper<TaskHolder::JobTree> tree_; |
| std::map<zx_object_info_topic_t, ByteView> info_; |
| std::map<uint32_t, ByteView> properties_; |
| std::vector<std::pair<std::string_view, ByteView>> remarks_; |
| time_t date_ = 0; |
| LiveHandle live_; |
| }; |
| |
| // As with zx::task, this is the superclass of Job, Process, and Thread. |
| class Task : public Object { |
| public: |
| using Object::Object; |
| |
| // This returns a suspend token. |
| fit::result<Error, LiveHandle> suspend(); |
| |
| protected: |
| Task(Task&&) noexcept = default; |
| Task& operator=(Task&&) noexcept = default; |
| |
| ~Task(); |
| }; |
| |
| // A Thread is a Task and also has register state. |
| class Thread : public Task { |
| public: |
| using Task::Task; |
| |
| ~Thread(); |
| |
| Process& process() const { return *process_; } |
| |
| fit::result<Error, ByteView> read_state(zx_thread_state_topic_t topic); |
| |
| // Use read_state<zx_thread_state_*_t>() with the machine-specific type for a |
| // particular kind of state, which implies the topic. |
| template <class StateType> |
| fit::result<Error, StateType> read_state() { |
| StateType state; |
| using Traits = ThreadStateTraits<StateType>; |
| auto result = read_state(Traits::kTopic, Traits::kMachine, sizeof(state)); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| memcpy(&state, result->data(), sizeof(state)); |
| return fit::ok(state); |
| } |
| |
| private: |
| friend Process; |
| friend TaskHolder::JobTree; |
| |
| Thread(Thread&&) noexcept = default; |
| Thread& operator=(Thread&&) noexcept = default; |
| |
| fit::result<Error, ByteView> read_state(zx_thread_state_topic_t topic, |
| elfldltl::ElfMachine machine, size_t size); |
| |
| std::map<zx_thread_state_topic_t, ByteView> state_; |
| Process* process_ = nullptr; |
| }; |
| |
| // A Process is a Task and also has threads and memory. |
| class Process : public Task { |
| public: |
| using ThreadMap = std::map<zx_koid_t, Thread>; |
| |
| using Task::Task; |
| |
| ~Process(); |
| |
| // Return the size of memory pages in this process, always a power of two. |
| // For a live process, this returns zx_system_get_page_size(). In a dump, |
| // it's determined from the PT_LOAD segments found (even if the dump elides |
| // the actual memory). If the dump had no memory segments and no system |
| // information to indicate the page size post mortem, this is 1 to maintain |
| // the power-of-two invariant. |
| uint64_t dump_page_size() const { return dump_page_size_; } |
| |
| // Return the machine architecture of this process. |
| // In a dump, it's determined by the ELF file header. |
| elfldltl::ElfMachine dump_machine() const { return dump_machine_; } |
| |
| // This is the same as what you'd get from get_info<ZX_INFO_PROCESS_THREADS> |
| // and then get_child on each KOID, but pre-cached. Note the returned map is |
| // not const so the Thread references can be non-const, but the caller must |
| // not modify the map itself. |
| fit::result<Error, std::reference_wrapper<ThreadMap>> threads(); |
| |
| // Find a task by KOID: this process or one of its threads. |
| fit::result<Error, std::reference_wrapper<Task>> find(zx_koid_t koid); |
| |
| // zxdump::Object::get_child actually just dispatches to this method. |
| fit::result<Error, std::reference_wrapper<Thread>> get_child(zx_koid_t koid); |
| |
| // Read process memory at the given vaddr. This tries to read at least count |
| // contiguous elements of type T, and succeeds if that was valid memory of |
| // type T in the process. The optional size_mode argument can permit it to |
| // return a buffer with more or less data than the exact size requested; see |
| // the enum definition above for details. In all modes it can return an |
| // empty buffer if the memory was elided from the dump. The success return |
| // value is a move-only type; see <zxdump/buffer.h> for full details. |
| template <typename T = std::byte, class View = cpp20::span<const T>> |
| fit::result<Error, Buffer<T, View>> read_memory( |
| uint64_t vaddr, size_t count, ReadMemorySize size_mode = ReadMemorySize::kExact) { |
| static_assert(!std::is_reference_v<T>, |
| "cannot instantiate zxdump::Process::read_memory with a reference type"); |
| static_assert( |
| std::is_same_v<const T*, decltype(std::data(View{}))>, |
| "must instantiate zxdump::Process::read_memory with corresponding T and View types"); |
| static_assert(alignof(T) <= __STDCPP_DEFAULT_NEW_ALIGNMENT__, |
| "alignment too large; must read as bytes and copy data"); |
| if (vaddr % alignof(T) != 0) { |
| return fit::error{Error{ |
| .op_ = "vaddr misaligned for type", |
| .status_ = ZX_ERR_INVALID_ARGS, |
| }}; |
| } |
| |
| Buffer<> buffer; |
| if (auto result = ReadMemoryImpl(vaddr, count * sizeof(T), size_mode, alignof(T)); |
| result.is_ok()) { |
| buffer = *std::move(result); |
| } else { |
| return result.take_error(); |
| } |
| |
| return fit::ok(static_cast<Buffer<T, View>>(std::move(buffer))); |
| } |
| |
| // This just reads a single datum of type T from dumped process memory at |
| // vaddr, and simply returns it by value. |
| template <typename T> |
| fit::result<Error, T> read_memory(uint64_t vaddr) { |
| using namespace std::literals; |
| auto result = read_memory<T>(vaddr, 1); |
| if (result.is_error()) { |
| return result.take_error(); |
| } |
| if (result->empty()) { |
| return fit::error{Error{ |
| .op_ = "memory elided from dump"sv, |
| .status_ = ZX_ERR_NO_MEMORY, |
| }}; |
| } |
| return fit::ok(result->front()); |
| } |
| |
| // Convenience function to read a C-style NUL-terminated string, while |
| // reading no more than limit chars total. No string is returned if no NUL |
| // terminator is found, but the std::string returned does not include the NUL |
| // terminator in its size() value (though of course its c_str() value is |
| // NUL-terminated). If the memory is accessible but no NUL terminator is |
| // found before the size limit is reached, the error result will use |
| // ZX_ERR_OUT_OF_RANGE. If the necessary memory was simply elided from the |
| // dump, the error result will use ZX_ERR_NOT_SUPPORTED. |
| fit::result<Error, std::string> read_memory_string(uint64_t vaddr, |
| size_t limit = kReadMemoryStringLimit) { |
| return read_memory_basic_string<char>(vaddr, limit); |
| } |
| |
| // The same thing is provided in u8string, u16string, u32string, and wstring |
| // variants. |
| |
| #if __cpp_lib_char8_t |
| fit::result<Error, std::u8string> read_memory_u8string(uint64_t vaddr, |
| size_t limit = kReadMemoryStringLimit / |
| sizeof(char8_t)) { |
| return read_memory_basic_string<char8_t>(vaddr, limit); |
| } |
| #endif |
| |
| fit::result<Error, std::u16string> read_memory_u16string(uint64_t vaddr, |
| size_t limit = kReadMemoryStringLimit / |
| sizeof(char16_t)) { |
| return read_memory_basic_string<char16_t>(vaddr, limit); |
| } |
| |
| fit::result<Error, std::u32string> read_memory_u32string(uint64_t vaddr, |
| size_t limit = kReadMemoryStringLimit / |
| sizeof(char32_t)) { |
| return read_memory_basic_string<char32_t>(vaddr, limit); |
| } |
| |
| fit::result<Error, std::wstring> read_memory_wstring(uint64_t vaddr, |
| size_t limit = kReadMemoryStringLimit / |
| sizeof(wchar_t)) { |
| return read_memory_basic_string<wchar_t>(vaddr, limit); |
| } |
| |
| private: |
| friend TaskHolder; |
| class LiveMemory; |
| |
| Process(Process&&) noexcept = default; |
| Process& operator=(Process&&) noexcept = default; |
| |
| struct Segment { |
| uint64_t offset, filesz, memsz; |
| }; |
| |
| fit::result<Error, Buffer<>> ReadMemoryImpl(uint64_t vaddr, size_t size, ReadMemorySize size_mode, |
| size_t alignment); |
| fit::result<Error, Buffer<>> ReadLiveMemory(uint64_t vaddr, size_t size, |
| ReadMemorySize size_mode); |
| |
| template <typename CharT = char> |
| fit::result<Error, std::basic_string<CharT>> read_memory_basic_string( |
| uint64_t vaddr, size_t limit = kReadMemoryStringLimit / sizeof(CharT)); |
| |
| std::map<zx_koid_t, Thread> threads_; |
| std::map<uint64_t, Segment> memory_; |
| uint64_t dump_page_size_ = 1; |
| elfldltl::ElfMachine dump_machine_ = elfldltl::ElfMachine::kNone; |
| internal::DumpFile* dump_ = nullptr; |
| std::unique_ptr<LiveMemory> live_memory_; |
| }; |
| |
| // Only these instantiations are actually defined in the library. |
| extern template fit::result<Error, std::basic_string<char>> Process::read_memory_basic_string<char>( |
| uint64_t vaddr, size_t limit); |
| #if __cpp_lib_char8_t |
| extern template fit::result<Error, std::basic_string<char8_t>> |
| Process::read_memory_basic_string<char8_t>(uint64_t vaddr, size_t limit); |
| #endif |
| extern template fit::result<Error, std::basic_string<char16_t>> |
| Process::read_memory_basic_string<char16_t>(uint64_t vaddr, size_t limit); |
| extern template fit::result<Error, std::basic_string<char32_t>> |
| Process::read_memory_basic_string<char32_t>(uint64_t vaddr, size_t limit); |
| extern template fit::result<Error, std::basic_string<wchar_t>> |
| Process::read_memory_basic_string<wchar_t>(uint64_t vaddr, size_t limit); |
| |
| // A Job is a Task and also has child jobs and processes. |
| class Job : public Task { |
| public: |
| using JobMap = std::map<zx_koid_t, Job>; |
| using ProcessMap = std::map<zx_koid_t, Process>; |
| |
| using Task::Task; |
| |
| ~Job(); |
| |
| // This is the same as what you'd get from get_info<ZX_INFO_JOB_CHILDREN> and |
| // then get_child on each KOID, but pre-cached. Note the returned map is not |
| // const so the Job references can be non-const, but the caller must not |
| // modify the map itself. |
| fit::result<Error, std::reference_wrapper<JobMap>> children(); |
| |
| // This is the same as what you'd get from get_info<ZX_INFO_JOB_PROCESSES> |
| // and then get_child on each KOID, but pre-cached. Note the returned map is |
| // not const so the Process references can be non-const, but the caller must |
| // not modify the map itself. |
| fit::result<Error, std::reference_wrapper<ProcessMap>> processes(); |
| |
| // Find a task by KOID: this task or a descendent task. |
| fit::result<Error, std::reference_wrapper<Task>> find(zx_koid_t koid); |
| |
| // zxdump::Object::get_child actually just dispatches to this method. |
| fit::result<Error, std::reference_wrapper<Task>> get_child(zx_koid_t koid); |
| |
| private: |
| friend TaskHolder::JobTree; |
| |
| Job(Job&&) noexcept = default; |
| Job& operator=(Job&&) noexcept = default; |
| |
| fit::result<Error, ByteView> GetSuperrootInfo(zx_object_info_topic_t topic); |
| |
| JobMap children_; |
| ProcessMap processes_; |
| }; |
| |
| // Resource objects just hold access to information, but are a distinct type. |
| class Resource : public Object { |
| public: |
| using Object::Object; |
| |
| ~Resource(); |
| |
| private: |
| friend TaskHolder::JobTree; |
| |
| Resource& operator=(Resource&&) noexcept = default; |
| }; |
| |
| // Get the live root job of the running system, e.g. for TaskHolder::Insert. |
| fit::result<Error, LiveHandle> GetRootJob(); |
| |
| // Get the live root resource handle of the running system. |
| fit::result<Error, LiveHandle> GetRootResource(); |
| |
| } // namespace zxdump |
| |
| #endif // SRC_LIB_ZXDUMP_INCLUDE_LIB_ZXDUMP_TASK_H_ |