C语言 如何使用libdw找到函数DIE(指令信息条目),给定eip(指令指针)?

gojuced7  于 2023-10-16  发布在  其他
关注(0)|答案(1)|浏览(111)

Libdw是elfutils的一部分,比libdwarf更新。不幸的是,几乎没有可用的文档,也没有例子。
我刚写了一个程序,给定一个正在运行的程序的指令指针,它会找到并打印出该函数的名称(通过调用dladdr并不总是可用的,它只能显示动态(又名公共/可见)符号)。我将把这篇文章作为对以下问题的回答:
当只有一个指令指针时,如何使用libdw从ELF可执行文件的DWARF信息中提取函数名(具有隐藏的可见性)?

dy1byipe

dy1byipe1#

下面的代码片段就是这样做的。main函数调用get_test_address()以在该函数中获取Dwarf_Addr。我使用了libunwind函数,但这有点不相关。
我使用dladdr查找并获取ELF对象文件及其加载地址。

#define _GNU_SOURCE
#include <dlfcn.h>
#define UNW_LOCAL_ONLY
#include <dwarf.h>
#include <elfutils/libdw.h>

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <string.h>
#include <libunwind.h>

void find_function_name_for_ip(char const* elf_path, Dwarf_Addr rel_ip)
{
  // Get file descriptor to ELF object.
  int fd = open(elf_path, O_RDONLY);
  if (fd < 0)
  {
    perror("open");
    return;
  }

  // Initialize libdw.
  Dwarf *dw = dwarf_begin(fd, DWARF_C_READ);
  if (dw == NULL)
  {
    fprintf(stderr, "dwarf_begin failed\n");
    close(fd);
    return;
  }

  // Get address ranges (of compilation units in this object).
  Dwarf_Aranges *aranges;
  size_t cnt;
  if (dwarf_getaranges(dw, &aranges, &cnt) != 0)
  {
    fprintf(stderr, "dwarf_getaranges failed\n");
    dwarf_end(dw);
    close(fd);
    return;
  }

  printf("There are %lu address ranges.\n", cnt);

  // Get the (address range of the) compilation unit containing rel_ip.
  Dwarf_Arange *arange = dwarf_getarange_addr(aranges, rel_ip);
  if (arange)
  {
    // Extract the offset into .debug_info.
    Dwarf_Addr start;
    Dwarf_Word length;
    Dwarf_Off offset;
    if (dwarf_getarangeinfo(arange, &start, &length, &offset) == 0)
    {
      printf("start = %lu, length = %u, offset = %ld\n", start, length, offset);

      // Obtain the DIE of the compilation unit.
      Dwarf_Die cu_die;
      Dwarf_Die* cu_die_ptr = dwarf_offdie(dw, offset, &cu_die);
      if (cu_die_ptr)
      {
        assert(cu_die_ptr == &cu_die);
        char const* name = dwarf_diename(cu_die_ptr);
        if (name)
          printf("Found compilation unit: %s\n", name);

        // Get the first child of this DIE (some type; it was 'size_t' for me).
        Dwarf_Die child_die;
        if (dwarf_child(cu_die_ptr, &child_die) != 0)
        {
          fprintf(stderr, "dwarf_child failed\n");
          return;
        }

        // Iterate over all children of the compilation unit.
        do
        {
          // We are only interested in DIE that represents a function.
          if (dwarf_tag(&child_die) == DW_TAG_subprogram)
          {
            Dwarf_Attribute decl_attr;
            bool is_declaration = 0;

            // Declarations are DW_TAG_subprogram too; skip all declarations.
            // We are only interested in definitions.
            if (dwarf_attr(&child_die, DW_AT_declaration, &decl_attr) &&
                dwarf_formflag(&decl_attr, &is_declaration) == 0 &&
                is_declaration)
              continue;

            // Check if this is the function that contains rel_ip (pc = program counter - same thing as ip).
            if (dwarf_haspc(&child_die, rel_ip))
            {
              // Get the name of the function.
              char const* func_name = dwarf_diename(&child_die);
              if (func_name)
              {
                printf("Found DIE for function: %s\n", func_name);
                // You can do more with the DIE here.
                break;
              }
            }
          }
        }
        while (dwarf_siblingof(&child_die, &child_die) == 0);
      }
    }
  }
  else
  {
    printf("DWARF arange not found\n");
  }

  dwarf_end(dw);
  close(fd);
}

Dwarf_Addr get_test_address()
{
  unw_cursor_t cursor;
  unw_context_t context;

  // Initialize libunwind.
  unw_getcontext(&context);

  unw_init_local(&cursor, &context);

  unw_word_t ip;
  unw_get_reg(&cursor, UNW_REG_IP, &ip);        // Now ip points to the return address of the above unw_getcontext call (aka, to unw_init_local(&cursor, &context) in this case).

  return ip;
}

int main()
{
  Dwarf_Addr ip = get_test_address();

  Dl_info info;
  if (dladdr((void*)ip, &info))
  {
    char const* elf_path = info.dli_fname;
    Dwarf_Addr rel_ip = ip - (Dwarf_Addr)info.dli_fbase;
    printf("ip = 0x%lx --> %ld\n", ip, rel_ip);

    // Compile with -rdynamic (for example) to allow dladdr to print something too, here.
    printf("Function name returned by dladdr: %s\n", (info.dli_sname ? info.dli_sname : "<NULL>"));

    find_function_name_for_ip(elf_path, rel_ip);
  }

  return 0;
}

编译并链接此测试程序如下:

daniel:~/projects/libcwd/libcwd>gcc -g -o find_function find_function.c -lunwind -ldw -lelf
daniel:~/projects/libcwd/libcwd>./find_function
ip = 0x55b24edbd690 --> 5776
Function name returned by dladdr: <NULL>
There are 1 address ranges.
start = 4793, length = 1272, offset = 12
Found compilation unit: find_function.c
Found DIE for function: get_test_address

相关问题