C 如何使dwarf部分加载到elf文件的内存中?

C 如何使dwarf部分加载到elf文件的内存中?,c,linker,elf,dwarf,C,Linker,Elf,Dwarf,我正在编写一个没有标准库的C程序,它由elf加载程序加载到内存中,然后执行。我希望这个C程序也能够将其dwarf调试部分加载到内存中,这样它就可以在运行时打印回溯 为了实现这一点,我在我的C程序中设置了: extern char __my_old_debug_abbrev_start[]; extern char __my_old_debug_abbrev_end[]; extern char __my_old_debug_info_start[]; extern char __my_old_d

我正在编写一个没有标准库的C程序,它由elf加载程序加载到内存中,然后执行。我希望这个C程序也能够将其dwarf调试部分加载到内存中,这样它就可以在运行时打印回溯

为了实现这一点,我在我的C程序中设置了:

extern char __my_old_debug_abbrev_start[];
extern char __my_old_debug_abbrev_end[];
extern char __my_old_debug_info_start[];
extern char __my_old_debug_info_end[];
extern char __my_old_debug_str_start[];
extern char __my_old_debug_str_end[];
这样它就能知道这些部分在哪里。然后,为了实际提供位置,我有一个链接器脚本,如下所示:

SECTIONS
{
  .debug_abbrev : {
    __my_old_debug_abbrev_start = .;
    KEEP (*(.debug_abbrev)) *(.debug_abbrev)
    __my_old_debug_abbrev_end = .;
  }
  .debug_info : {
    __my_old_debug_info_start = .;
    KEEP (*(.debug_info .gnu.linkonce.wi.*)) *(.debug_info .gnu.linkonce.wi.*)
    __my_old_debug_info_end = .;
  }
  .debug_str : {
    __my_old_debug_str_start = .;
    KEEP (*(.debug_str)) *(.debug_str)
    __my_old_debug_str_end = .;
  }
}
INSERT AFTER .rodata;
首先,我将C程序编译成一个
libtest.a
,然后使用
objcopy
将节设置为
alloc
load

objcopy --set-section-flags '.debug_abbrev=alloc,load' libtest.a
objcopy --set-section-flags '.debug_info=alloc,load' libtest.a
objcopy --set-section-flags '.debug_str=alloc,load' libtest.a
objcopy --set-section-flags '.gnu.linkonce.wi.*=alloc,load' libtest.a
然后,我在归档文件上运行gcc,将其编译成可执行文件,大致如下:

gcc libtest.a -o test -T test.lds -static
这会产生错误:

/usr/bin/x86_64-linux-gnu-ld: section .debug_info LMA [0000000000000000,0000000000066291] overlaps section .debug_abbrev LMA [0000000000000000,0000000000007cce]
/usr/bin/x86_64-linux-gnu-ld: section .debug_str LMA [0000000000000000,000000000009d264] overlaps section .debug_info LMA [0000000000000000,0000000000066291]
我不确定如何解决这个问题,因为这些部分只有在链接(?)之后才真正存在,然后我可能可以使用
objcopy
(?)调整lma,但我不确定将它们放在哪里


我已经看到了,但我不确定如何在链接之前创建“洞”,以便我可以使用
objcopy
来调整内容。

根据user2162550的建议,代码成功编译,但我不得不打印出调试信息中的函数名,但没有打印出任何内容。然后,我在gcc使用的默认链接器脚本中看到了一条注释(在链接可执行文件时将
-Wl,--verbose
传递给它):

这让我确信调试符号在最终二进制文件中的位置并不重要。因此,我尝试使用“洞”技巧(来自),但在链接可执行文件之前,我不确定如何复制调试信息(一旦链接可执行文件,我认为
objcopy
不再有效)。因此,我决定在二进制文件中保留一些加载和分配的空间,然后在链接之后,将所需的部分复制到该空间中

为此,我使用链接器脚本留下了一个漏洞,并提供了一些符号来确定调试部分的位置。我使用的方法是使用链接器脚本首先测量每个调试部分的大小,然后为其分配足够的空间。这看起来像(在
test.lds
中):

/* This finds the start and end of each section so we know its size */
SECTIONS
{
  .debug_info 0 : {
    __my_old_debug_info_start = .;
    KEEP (*(.debug_info .gnu.linkonce.wi.*)) *(.debug_info .gnu.linkonce.wi.*)
    __my_old_debug_info_end = .;
  }
  .debug_abbrev 0 : {
    __my_old_debug_abbrev_start = .;
    KEEP (*(.debug_abbrev)) *(.debug_abbrev)
    __my_old_debug_abbrev_end = .;
  }
  .debug_str 0 : {
    __my_old_debug_str_start = .;
    KEEP (*(.debug_str)) *(.debug_str)
    __my_old_debug_str_end = .;
  }
}
INSERT AFTER .rodata;

/* This creates some space in the binary which is loaded and big enough to store all the debugging info, as well as marking the start and end of each area */
SECTIONS
{
  .debug_all : {
    __my_debug_info_start = .;
    . += __my_old_debug_info_end - __my_old_debug_info_start;
    __my_debug_info_end = .;
    __my_debug_abbrev_start = .;
    . += __my_old_debug_abbrev_end - __my_old_debug_abbrev_start;
    __my_debug_abbrev_end = .;
    __my_debug_str_start = .;
    . += __my_old_debug_str_end - __my_old_debug_str_start;
    __my_debug_str_end = .;
  }
}
INSERT AFTER .rodata;
我认为在
之后插入
时选择
.rodata
是任意的

然后,我编译并链接到:

gcc libtest.a -g -o test -T test.lds -static
受此启发,我让一个bash脚本解析
readelf
的输出,并计算二进制文件中的何处来获取调试信息,以及将其复制到何处以便加载。复制是使用
dd
完成的

function getSymbolValue {
  binary=$1
  symbol=$2

  # Assumes that this will only find one symbol
  truncated_symbol=`echo $symbol | cut -c 1-25`
  readelf -s $binary | grep $truncated_symbol | awk '{print $2}'
}
function getSectionInfo {
  binary=$1
  section=$2

  # returns all but the [Nr] column of data returned by readelf
  # https://stackoverflow.com/a/3795522/3492895
  readelf -S $binary | cut -c7- | grep '\.'"$section"
}
function getSectionAddress {
  binary=$1
  section=$2

  getSectionInfo $binary $section | awk '{print $3}'
}
function getSectionOffset {
  binary=$1
  section=$2

  getSectionInfo $binary $section | awk '{print $4}'
}
function copyData {
  binary=$1
  from_start=$2
  to_start=$3
  len=$4

  dd iflag=skip_bytes,count_bytes if=$binary skip=$from_start count=$len | dd oflag=seek_bytes of=$binary seek=$to_start count=$len conv=notrunc
}
function copyDebugSection {
  binary=$1
  from_section=$2
  to_section=$3

  from_off=`getSectionOffset $binary $from_section`
  to_section_off=`getSectionOffset $binary $to_section`
  to_section_addr=`getSectionAddress $binary $to_section`
  to_start_addr=`getSymbolValue $binary "__my_${from_section}_start"`
  to_end_addr=`getSymbolValue $binary "__my_${from_section}_end"`

  copyData $binary $((0x$from_off)) $((0x$to_start_addr - 0x$to_section_addr + 0x$to_section_off)) $((0x$to_end_addr - 0x$to_start_addr))
}

copyDebugSection ./test 'debug_info' 'debug_all'
copyDebugSection ./test 'debug_abbrev' 'debug_all'
copyDebugSection ./test 'debug_str' 'debug_all'
运行这个之后,我期望的函数名就被打印出来了

如果有人想知道我是如何打印出函数名的,我使用库gimli编写了一些rust代码。因为这与问题无关,所以我没有包含它。我使用它来确保正确的调试信息在那里,因为我没有在网上找到任何魔幻矮人数字来确保信息的完整性离子

唯一的潜在问题是,当运行
readelf
时,它会输出:

  [Nr] Name              Type             Address           Offset
   Size              EntSize          Flags  Link  Info  Align
...
readelf: Warning: [ 3]: Link field (0) should index a symtab section.
  [ 3] .rela.plt         RELA             0000000000400168  00000168
   0000000000000228  0000000000000018  AI       0    25     8
但我不明白这意味着什么,而且似乎也不构成问题


请告诉我是否有什么可以改进此问题或答案。

尝试将三个输出部分合并为一个,称之为
.debug\u all
,然后发布结果您可以通过声明(并获取的地址)获得调试部分的开始和结束类似于
extern void\uu start\u debug\u info,\uu stop\u debug\u info;
以避免符号放置的恶作剧。请看另一个问题:@zneak我试过了,在链接器脚本中替换
+==\uu my\u old\u debug\u info\u end-\uu my\u old\u debug\u info\u start;
+=\uu stop\u debug\u info-\uu start\u debug\u info;
等,然后重新启动移动第一个
块。链接器弹出:`undefined symbol
\uuuu stop\u debug\u info'在表达式
中引用,因此它似乎不起作用。D=如果从C代码中使用它们,而不是将它们集成到链接器脚本中,会发生什么问题?那么,您的节没有复制到可执行文件中吗?我想然后问题将变成计算自定义
.debug\u all
节应该有多大,以包含调试节的副本。我尝试使用
SIZEOF
但似乎不起作用。如果
.debug\u all
在.rodata之后插入,但如果我们在最后一个调试节之后插入它,则返回0启用时,它返回一个非零值。但随后
readelf
报告
警告:偏移量0xb处的DIE指的是不存在的缩写数字1
,调试部分似乎无法正确解析。
  [Nr] Name              Type             Address           Offset
   Size              EntSize          Flags  Link  Info  Align
...
readelf: Warning: [ 3]: Link field (0) should index a symtab section.
  [ 3] .rela.plt         RELA             0000000000400168  00000168
   0000000000000228  0000000000000018  AI       0    25     8