【问题标题】:How to make the dwarf sections get loaded into memory in an elf file?如何使矮小的部分在 elf 文件中加载到内存中?
【发布时间】:2018-09-17 10:52:27
【问题描述】:

我正在编写一个没有标准库的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将section设置为allocload

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 但我不确定我会将它们放在哪里.

我见过https://stackoverflow.com/a/31126336/3492895,但我不确定如何在链接之前创建“洞”,因此我可以使用objcopy 进行调整。

【问题讨论】:

  • 尝试将三个输出部分合并为一个,将其命名为.debug_all,然后发布结果

标签: c linker elf dwarf


【解决方案1】:

使用 user2162550 的建议,代码成功编译,但我必须打印出调试信息中的函数名称的一些代码什么也没打印出来。然后我在 gcc 使用的默认链接器脚本中看到了一条注释(通过在链接可执行文件时将 -Wl,--verbose 传递给它):

/* DWARF debug sections.
  Symbols in the DWARF debugging sections are relative to the beginning
  of the section so we begin them at 0.  */
...
.debug_info     0 : { *(.debug_info .gnu.linkonce.wi.*) }
.debug_abbrev   0 : { *(.debug_abbrev) }
...

这让我确信,调试符号在最终二进制文件中的位置并不重要。所以然后我尝试使用漏洞技巧(来自here),但我不确定如何在链接可执行文件之前复制调试信息(一旦链接了可执行文件,我认为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;

我认为为INSERT AFTER 选择.rodata 是任意的。

然后,我编译并链接到:

gcc libtest.a -g -o test -T test.lds -static

this 获得灵感,我有一个 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

但我不明白这是什么意思,而且它似乎不会造成问题。

请告诉我是否可以做些什么来改进这个问题或答案。

【讨论】:

  • 您可以通过声明(并获取地址)extern void __start_debug_info, __stop_debug_info; 之类的内容来获取调试部分的开始和结束,以避免符号放置恶作剧。请参阅另一个问题:stackoverflow.com/questions/16552710/…
  • @zneak 我试过了,将链接描述文件中的 . += __my_old_debug_info_end - __my_old_debug_info_start; 替换为 . += __stop_debug_info - __start_debug_info; 等,并删除第一个 SECTIONS 块。链接器吐出:` undefined symbol __stop_debug_info' referenced in expression,所以它似乎没有工作。 D=
  • 如果您在 C 代码中使用它们而不是将它们集成到链接器脚本中会怎样?那么问题是你的部分没有被复制到可执行文件中吗?
  • 我认为问题将变成弄清楚自定义.debug_all 部分应该有多大才能包含调试部分的副本。我尝试使用SIZEOF,但这似乎不起作用。如果.debug_allINSERT AFTER .rodata,它似乎返回 0,但如果我们稍后插入它,在最后一个调试部分之后,它返回一个非零值。但是随后readelf 报告Warning: DIE at offset 0xb refers to abbreviation number 1 which does not exist 并且调试部分似乎没有正确解析。
猜你喜欢
  • 2012-05-12
  • 1970-01-01
  • 2017-01-22
  • 2010-11-08
  • 2017-07-03
  • 1970-01-01
  • 1970-01-01
  • 2019-12-12
  • 1970-01-01
相关资源
最近更新 更多