chromium/tools/memory/partition_allocator/palloc_viewer/addrlookup.cpp

// 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