我正在编写不带标准库的C程序,该库由elf加载程序加载到内存中,然后执行。我希望这个C程序也能够将其矮小的调试部分加载到内存中,以便它可以在运行时打印回溯。
为实现这一目标,我将其放置在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,但是我不确定在哪里放置它们。
我见过https://stackoverflow.com/a/31126336/3492895,但不确定在链接之前如何创建“孔”,以便可以使用objcopy
进行调整。
答案 0 :(得分:0)
使用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),但不确定在链接可执行文件之前如何复制调试信息(一旦链接了可执行文件,我不认为{{1} })。因此,我决定在二进制文件中保留一些已加载和分配的空间,然后在链接后将所需的节复制到该空间中。
为此,我使用了链接描述文件留下了漏洞,并提供了符号来指出调试部分的位置。我正在使用的方法是使用链接器脚本来首先测量每个调试部分的大小,然后为其分配足够的空间。看起来像(在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;
的{{1}}选择是任意的。
然后,我编译并链接了以下内容:
.rodata
从this那里得到灵感,我有一个bash脚本来解析INSERT AFTER
的输出,并计算二进制文件中从何处获取调试信息以及将调试信息复制到何处,以便将其加载。使用gcc libtest.a -g -o test -T test.lds -static
完成复制。
readelf
运行此命令后,将打印出我期望的功能名称。
如果有人想知道我如何打印出函数名,我会使用gimli库在rust中编写一些代码。由于这与问题无关,因此我没有将其包括在内。我用它来确保存在正确的调试信息,因为我没有找到任何神奇的矮人数字来在线查找以确保信息的完整性。
唯一的潜在问题是,在运行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'
但是我不明白这意味着什么,而且似乎也没有问题。
请告诉我是否有什么办法可以改善这个问题或答案。