| // Copyright 2016 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 <fcntl.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <link.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <elf-search.h> |
| #include <zircon/assert.h> |
| #include <zircon/syscalls.h> |
| #include <zircon/types.h> |
| |
| #include "inspector/inspector.h" |
| #include "dso-list-impl.h" |
| #include "utils-impl.h" |
| |
| #define rdebug_off_lmap offsetof(struct r_debug, r_map) |
| |
| #define lmap_off_next offsetof(struct link_map, l_next) |
| #define lmap_off_name offsetof(struct link_map, l_name) |
| #define lmap_off_addr offsetof(struct link_map, l_addr) |
| |
| using inspector::fetch_build_id; |
| using inspector::fetch_string; |
| using inspector::read_mem; |
| |
| const char kDebugDirectory[] = "/boot/debug"; |
| const char kDebugSuffix[] = ".debug"; |
| |
| static inspector_dsoinfo_t* dsolist_add(inspector_dsoinfo_t** list, const char* name, |
| uintptr_t base) { |
| if (!strncmp(name, "app:devhost:", 12)) { |
| // devhost processes use their name field to describe |
| // the root of their device sub-tree. |
| name = "app:/boot/bin/devhost"; |
| } |
| size_t len = strlen(name); |
| auto dso = |
| reinterpret_cast<inspector_dsoinfo_t*>(calloc(1, sizeof(inspector_dsoinfo_t) + len + 1)); |
| if (dso == nullptr) { |
| return nullptr; |
| } |
| memcpy(dso->name, name, len + 1); |
| memset(dso->buildid, 'x', sizeof(dso->buildid) - 1); |
| dso->base = base; |
| dso->debug_file_tried = false; |
| dso->debug_file_status = ZX_ERR_BAD_STATE; |
| while (*list != nullptr) { |
| if ((*list)->base < dso->base) { |
| dso->next = *list; |
| *list = dso; |
| return dso; |
| } |
| list = &((*list)->next); |
| } |
| *list = dso; |
| dso->next = nullptr; |
| return dso; |
| } |
| |
| __EXPORT inspector_dsoinfo_t* inspector_dso_fetch_list(zx_handle_t h) { |
| // Prepend "app:" to the name we print for the process binary to tell the |
| // reader (and the symbolize script!) that the name is the process's. |
| // The name property is only 32 characters which may be insufficient. |
| // N.B. The symbolize script looks for "app" and "app:". |
| #define PROCESS_NAME_PREFIX "app:" |
| #define PROCESS_NAME_PREFIX_LEN (sizeof(PROCESS_NAME_PREFIX) - 1) |
| char name[ZX_MAX_NAME_LEN + PROCESS_NAME_PREFIX_LEN]; |
| strcpy(name, PROCESS_NAME_PREFIX); |
| auto status = zx_object_get_property(h, ZX_PROP_NAME, name + PROCESS_NAME_PREFIX_LEN, |
| sizeof(name) - PROCESS_NAME_PREFIX_LEN); |
| if (status != ZX_OK) { |
| print_zx_error("zx_object_get_property, falling back to \"app\" for program name", status); |
| strlcpy(name, "app", sizeof(name)); |
| } |
| |
| uintptr_t lmap, debug_addr; |
| status = zx_object_get_property(h, ZX_PROP_PROCESS_DEBUG_ADDR, &debug_addr, sizeof(debug_addr)); |
| if (status != ZX_OK) { |
| print_zx_error("zx_object_get_property(ZX_PROP_PROCESS_DEBUG_ADDR), unable to fetch dso list", |
| status); |
| return nullptr; |
| } |
| if (read_mem(h, debug_addr + rdebug_off_lmap, &lmap, sizeof(lmap))) { |
| return nullptr; |
| } |
| inspector_dsoinfo_t* dsolist = nullptr; |
| int iter = 0; |
| while (lmap != 0) { |
| if (iter++ > 500) { |
| print_error("dso_fetch_list detected too many entries, possible infinite loop"); |
| return nullptr; |
| } |
| char dsoname[64]; |
| zx_vaddr_t base; |
| uintptr_t next; |
| uintptr_t str; |
| if (read_mem(h, lmap + lmap_off_addr, &base, sizeof(base))) { |
| break; |
| } |
| if (read_mem(h, lmap + lmap_off_next, &next, sizeof(next))) { |
| break; |
| } |
| if (read_mem(h, lmap + lmap_off_name, &str, sizeof(str))) { |
| break; |
| } |
| if (fetch_string(h, str, dsoname, sizeof(dsoname))) { |
| break; |
| } |
| inspector_dsoinfo_t* dso = dsolist_add(&dsolist, dsoname[0] ? dsoname : name, base); |
| if (dso != nullptr) { |
| fetch_build_id(h, dso->base, dso->buildid, sizeof(dso->buildid)); |
| } |
| lmap = next; |
| } |
| |
| return dsolist; |
| } |
| |
| __EXPORT void inspector_dso_free_list(inspector_dsoinfo_t* list) { |
| while (list != NULL) { |
| inspector_dsoinfo_t* next = list->next; |
| free(list->debug_file); |
| free(list); |
| list = next; |
| } |
| } |
| |
| __EXPORT inspector_dsoinfo_t* inspector_dso_lookup(inspector_dsoinfo_t* dso_list, zx_vaddr_t pc) { |
| for (inspector_dsoinfo_t* dso = dso_list; dso != NULL; dso = dso->next) { |
| if (pc >= dso->base) { |
| return dso; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| __EXPORT void inspector_print_markup_context(FILE* f, zx_handle_t process) { |
| fprintf(f, "{{{reset}}}\n"); |
| ForEachModule(*zx::unowned_process{process}, [f, count = 0u](const ModuleInfo& info) mutable { |
| unsigned int module_id = count++; |
| // Print out the module first. |
| fprintf(f, "{{{module:%#x:%s:elf:", module_id, info.name.begin()); |
| for (uint8_t byte : info.build_id) { |
| fprintf(f, "%02x", byte); |
| } |
| fprintf(f, "}}}\n"); |
| // Now print out the various segments. |
| for (const auto& phdr : info.phdrs) { |
| if (phdr.p_type != PT_LOAD) { |
| continue; |
| } |
| uintptr_t start = phdr.p_vaddr & -PAGE_SIZE; |
| uintptr_t end = (phdr.p_vaddr + phdr.p_memsz + PAGE_SIZE - 1) & -PAGE_SIZE; |
| fprintf(f, "{{{mmap:%#" PRIxPTR ":%#" PRIxPTR ":load:%#x:", info.vaddr + start, end - start, |
| module_id); |
| if (phdr.p_flags & PF_R) { |
| fprintf(f, "%c", 'r'); |
| } |
| if (phdr.p_flags & PF_W) { |
| fprintf(f, "%c", 'w'); |
| } |
| if (phdr.p_flags & PF_X) { |
| fprintf(f, "%c", 'x'); |
| } |
| fprintf(f, ":%#" PRIxPTR "}}}\n", start); |
| } |
| }); |
| } |
| |
| __EXPORT void inspector_dso_print_list(FILE* f, inspector_dsoinfo_t* dso_list) { |
| for (inspector_dsoinfo_t* dso = dso_list; dso != nullptr; dso = dso->next) { |
| fprintf(f, "dso: id=%s base=%p name=%s\n", dso->buildid, (void*)dso->base, dso->name); |
| } |
| } |
| |
| __EXPORT zx_status_t inspector_dso_find_debug_file(inspector_dsoinfo_t* dso, |
| const char** out_debug_file) { |
| // Have we already tried? |
| // Yeah, if we OOM it's possible it'll succeed next time, but |
| // it's not worth the extra complexity to avoid printing the debugging |
| // messages twice. |
| if (dso->debug_file_tried) { |
| switch (dso->debug_file_status) { |
| case ZX_OK: |
| ZX_DEBUG_ASSERT(dso->debug_file != nullptr); |
| *out_debug_file = dso->debug_file; |
| __FALLTHROUGH; |
| default: |
| debugf(2, "returning %d, already tried to find debug file for %s\n", dso->debug_file_status, |
| dso->name); |
| return dso->debug_file_status; |
| } |
| } |
| |
| dso->debug_file_tried = true; |
| |
| char* path; |
| if (asprintf(&path, "%s/%s%s", kDebugDirectory, dso->buildid, kDebugSuffix) < 0) { |
| debugf(1, "OOM building debug file path for dso %s\n", dso->name); |
| dso->debug_file_status = ZX_ERR_NO_MEMORY; |
| return dso->debug_file_status; |
| } |
| |
| debugf(1, "looking for debug file %s\n", path); |
| |
| int fd = open(path, O_RDONLY); |
| if (fd < 0) { |
| debugf(1, "debug file for dso %s not found: %s\n", dso->name, path); |
| free(path); |
| dso->debug_file_status = ZX_ERR_NOT_FOUND; |
| } else { |
| debugf(1, "found debug file for dso %s: %s\n", dso->name, path); |
| close(fd); |
| dso->debug_file = path; |
| *out_debug_file = path; |
| dso->debug_file_status = ZX_OK; |
| } |
| |
| return dso->debug_file_status; |
| } |