| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <assert.h> |
| #include <dwarf.h> |
| #include <elfutils/libdwfl.h> |
| #include <err.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/ptrace.h> |
| #include <sys/user.h> |
| #include <sys/wait.h> |
| #include "common.h" |
| |
| static int lookup_namespaced_name_(Dwarf_Die* scope, |
| const char** namespaces, |
| size_t namespaces_len, |
| const char* expected_name, |
| unsigned int expected_tag, |
| Dwarf_Die* result) { |
| Dwarf_Die child; |
| int res = dwarf_child(scope, &child); |
| if (res) |
| return res; |
| while (1) { |
| unsigned int tag = dwarf_tag(&child); |
| const char* name = dwarf_diename(&child); |
| if (namespaces_len) { |
| const char* ns = namespaces[0]; |
| if (tag == DW_TAG_namespace && |
| (ns ? (name && strcmp(name, ns) == 0) : (name == NULL))) { |
| res = |
| lookup_namespaced_name_(&child, namespaces + 1, namespaces_len - 1, |
| expected_name, expected_tag, result); |
| if (res <= 0) |
| return res; |
| } |
| } else { |
| if (expected_name) { |
| if ((tag == expected_tag || (expected_tag == DW_TAG_structure_type && |
| tag == DW_TAG_class_type)) && |
| name && strcmp(expected_name, name) == 0) { |
| *result = child; |
| return 0; |
| } |
| } else { |
| // for debugging |
| printf("got child '%s', tag 0x%x\n", name, tag); |
| } |
| } |
| res = dwarf_siblingof(&child, &child); |
| if (res) |
| return res; |
| } |
| return 1; |
| } |
| |
| static void lookup_namespaced_name(Dwarf_Die* scope, |
| const char** namespaces, |
| size_t namespaces_len, |
| const char* expected_name, |
| unsigned int expected_tag, |
| Dwarf_Die* result) { |
| if (lookup_namespaced_name_(scope, namespaces, namespaces_len, expected_name, |
| expected_tag, result) != 0) |
| errx(1, "lookup of '%s' failed", expected_name); |
| } |
| |
| static void lookup_name(Dwarf_Die* scope, |
| const char* expected_name, |
| unsigned int tag, |
| Dwarf_Die* result) { |
| lookup_namespaced_name(scope, NULL, 0, expected_name, tag, result); |
| } |
| |
| void* lookup_cu(Dwfl* dwfl, |
| Dwfl_Module* mod, |
| const char* expected_name, |
| unsigned long* bias_out) { |
| fprintf(stderr, "looking up CU '%s'...\n", expected_name); |
| Dwarf_Die* cu = NULL; |
| Dwarf_Addr bias; |
| Dwarf_Die* result = NULL; |
| while ((cu = (mod ? dwfl_module_nextcu(mod, cu, &bias) |
| : dwfl_nextcu(dwfl, cu, &bias))) != NULL) { |
| const char* name = dwarf_diename(cu); |
| if (expected_name == NULL) { |
| // for debugging |
| printf("CU: %s\n", name); |
| continue; |
| } |
| if (strcmp(name, expected_name) == 0) { |
| if (result) |
| errx(1, "duplicate CU '%s'", expected_name); |
| result = cu; |
| *bias_out = bias; |
| } |
| } |
| if (!result) |
| errx(1, "unable to find CU '%s'", expected_name); |
| fprintf(stderr, "CU lookup complete\n"); |
| return (void*)result; |
| } |
| |
| static unsigned long get_die_address(Dwarf_Die* die, unsigned long cu_bias) { |
| Dwarf_Attribute loc_attr; |
| if (dwarf_attr(die, DW_AT_location, &loc_attr) == NULL) |
| return 0; |
| Dwarf_Op* loc_expr; |
| size_t loc_expr_len; |
| if (dwarf_getlocation(&loc_attr, &loc_expr, &loc_expr_len) != 0) |
| return 0; |
| if (loc_expr_len == 1 && loc_expr[0].atom == DW_OP_addr) { |
| return cu_bias + loc_expr[0].number; |
| } else { |
| return 0; |
| } |
| } |
| |
| #if 0 |
| // for debug |
| static int print_attrs_cb(Dwarf_Attribute *attr, void *arg) { |
| printf("attribute: 0x%x\n", attr->code); |
| if (attr->code == DW_AT_data_member_location) |
| printf(" DW_AT_data_member_location\n"); |
| Dwarf_Word uval; |
| if (dwarf_formudata(attr, &uval) == 0) |
| printf(" unsigned value: 0x%lx\n", uval); |
| return 0; |
| } |
| |
| // for debug |
| static int print_modules_cb(Dwfl_Module *mod, void **mod_userdata, const char *name, Dwarf_Addr low_addr, void *arg) { |
| printf("module: %s\n", name); |
| return 0; |
| } |
| #endif |
| |
| struct find_lib_data { |
| Dwfl_Module* res; |
| const char* name; |
| }; |
| static int find_lib_cb(Dwfl_Module* mod, |
| void** mod_userdata, |
| const char* name, |
| Dwarf_Addr low_addr, |
| void* arg) { |
| struct find_lib_data* data = (struct find_lib_data*)arg; |
| if ((strncmp(name, "/lib/", 5) == 0 || strncmp(name, "/usr/", 5) == 0) && |
| strstr(name, data->name)) { |
| if (data->res) |
| errx(1, "two %s mappings?", data->name); |
| data->res = mod; |
| } |
| return 0; |
| } |
| Dwfl_Module* addrlookup_find_lib(Dwfl* dwfl, const char* name) { |
| struct find_lib_data data = {.res = NULL, .name = name}; |
| if (dwfl_getmodules(dwfl, find_lib_cb, &data, 0)) |
| return NULL; |
| if (!data.res) |
| errx(1, "no %s found", name); |
| return data.res; |
| } |
| |
| static unsigned long read_udata_dwarf_attr(Dwarf_Die* die, unsigned int name) { |
| Dwarf_Attribute attr; |
| if (dwarf_attr(die, name, &attr) == NULL) |
| err(1, "unable to find requested attr 0x%x", name); |
| Dwarf_Word value; |
| if (dwarf_formudata(&attr, &value)) |
| err(1, "requested attr 0x%x is not a constant?", name); |
| return value; |
| } |
| |
| unsigned long addrlookup_get_struct_offset(void* scope, |
| const char** namespaces, |
| size_t namespaces_len, |
| const char* struct_name, |
| const char* member_name) { |
| Dwarf_Die struct_die; |
| lookup_namespaced_name((Dwarf_Die*)scope, namespaces, namespaces_len, |
| struct_name, DW_TAG_structure_type, &struct_die); |
| Dwarf_Die member_die; |
| lookup_name(&struct_die, member_name, DW_TAG_member, &member_die); |
| return read_udata_dwarf_attr(&member_die, DW_AT_data_member_location); |
| } |
| |
| unsigned long addrlookup_get_variable_address(void* scope, |
| unsigned long cu_bias, |
| const char** namespaces, |
| size_t namespaces_len, |
| const char* name) { |
| Dwarf_Die var_die; |
| lookup_namespaced_name((Dwarf_Die*)scope, namespaces, namespaces_len, name, |
| DW_TAG_variable, &var_die); |
| return get_die_address(&var_die, cu_bias); |
| } |
| |
| Dwfl* addrlookup_init(pid_t pid) { |
| fprintf(stderr, "initializing DWFL for pid %d\n", pid); |
| static const Dwfl_Callbacks proc_callbacks = { |
| .find_elf = dwfl_linux_proc_find_elf, |
| .find_debuginfo = dwfl_standard_find_debuginfo}; |
| Dwfl* dwfl = dwfl_begin(&proc_callbacks); |
| if (!dwfl) |
| err(1, "dwfl_begin"); |
| |
| if (dwfl_linux_proc_report(dwfl, pid)) |
| errx(1, "proc_report"); |
| if (dwfl_report_end(dwfl, NULL, NULL)) |
| errx(1, "report_end"); |
| fprintf(stderr, "DWFL init complete\n"); |
| return dwfl; |
| } |
| |
| void addrlookup_finish(Dwfl* dwfl) { |
| dwfl_end(dwfl); |
| } |
| |
| #if 0 |
| int main(int argc, char **argv) { |
| if (argc != 2) |
| errx(1, "args"); |
| int pid = atoi(argv[1]); |
| |
| addrlookup_init(pid); |
| |
| Dwfl_Module *libpthread_module = addrlookup_find_lib("/libpthread-"); |
| Dwarf_Addr pthread_bias; |
| Dwarf_Die *pthread_cu = lookup_cu(libpthread_module, "pthread_getspecific.c", &pthread_bias); |
| |
| unsigned long pthread_block_offset = addrlookup_get_struct_offset(pthread_cu, NULL, 0, "pthread", "specific_1stblock"); |
| printf("pthread_block_offset=0x%lx\n", pthread_block_offset); |
| |
| Dwarf_Addr bias; |
| Dwarf_Die *cu = lookup_cu(NULL, "../../base/allocator/partition_allocator/src/partition_alloc/thread_cache.cc", &bias); |
| |
| const char *nspath[] = { "base", "internal", NULL }; |
| unsigned long g_instance_addr = addrlookup_get_variable_address(cu, bias, nspath, 3, "g_instance"); |
| printf("g_instance at 0x%lx\n", g_instance_addr); |
| |
| printf("end\n"); |
| return 0; |
| } |
| #endif |