首页 > 解决方案 > gdb 错误地解析堆栈变量位置

问题描述

在调试我正在编写的有趣/学习经验的小内核时,我遇到了一个有点令人费解的 gdb 问题,它显然没有正确解析堆栈上的局部变量地址。到目前为止,我的调查表明调试符号是正确的,但不知何故 gdb 在显示该变量的内容时仍然从错误的内存位置读取。

有问题的相关C代码是:

typedef union
{
    uint16_t packed;
    struct __attribute__((packed))
    {
        uint8_t PhysicalLimit;
        uint8_t LinearLimit;
    } limits;
} MemAddrLimits;

void KernelMain32()
{
    ClearScreen();
    SimplePrint("kernelMain32");

    MemAddrLimits memAddr;
    memAddr.packed = GetMemoryAddressLimits();
    for (;;) {}
}

where返回指令以 2 字节整数形式GetMemoryAddressLimits()提供的内存地址宽度(目前用于我的测试)。但是,当使用 gdb 单步执行此函数以打印 的值时,不会显示正确的结果:cpuid0x3028memAddr

gdb> p memAddr
$1 = {packed = 0, limits = {PhysicalLimit = 0 '\000', LinearLimit = 0 '\000'}}

gdb> info locals
memAddr = {packed = 0, limits = {PhysicalLimit = 0 '\000', LinearLimit = 0 '\000'}}

gdb> info addr memAddr打印Symbol "memAddr" is a variable at frame base reg $ebp offset 8+-18.ie,memAddr位于ebp-10并且实际上,检查该地址会显示预期的内容:

gdb> x/hx $ebp-10
0x8ffee:        0x3028

相比之下gdb> p &memAddr,给出了(MemAddrLimits *) 0x7f6内存在哪个位置归零的值。

当声明memAddr为 auint16_t而不是我的联合类型时,不会发生这些问题。在这种情况下,我们得到

gdb> info addr memAddr
Symbol "memAddr" is multi-location:
  Range 0x8b95-0x8b97: a variable in $eax
.

但是,结果仍然(也)写入ebp-10,即函数的反汇编是相同的 - 唯一的区别在于调试符号。

我是否在这里遗漏了什么,或者有人对这种情况下可能出现的问题有一个好主意?

更多细节

程序版本和构建标志

使用gcc (Ubuntu 9.3.0-10ubuntu2) 9.3.0GNU gdb (Ubuntu 9.1-0ubuntu1) 9.1

使用标志编译

-ffreestanding -m32 -fcf-protection=none -fno-pie -fno-pic -O0 -gdwarf-2 -fvar-tracking -fvar-tracking-assignments

并与 -m elf_i386 -nodefaultlibs -nostartfiles -Ttext 0x7c00 -e start -g

链接阶段生成kernel.elf我后处理以提取原始可执行二进制文件以及要加载到 gdb 中的符号文件。到目前为止,这对我来说效果很好。

二进制文件中涉及的代码显然比我展示的要多,其中大部分是用汇编编写的,在这里不应该相关。

编译文件

gcc 生成以下代码(片段来自objdump -d kernel.elf):

    00008b74 <KernelMain32>:
    8b74:       55                      push   ebp
    8b75:       89 e5                   mov    ebp,esp
    8b77:       83 ec 18                sub    esp,0x18
    8b7a:       e8 f0 fe ff ff          call   8a6f <ClearScreen>
    8b7f:       68 41 8c 00 00          push   0x8c41
    8b84:       e8 7a ff ff ff          call   8b03 <SimplePrint>
    8b89:       83 c4 04                add    esp,0x4
    8b8c:       e8 0f 00 00 00          call   8ba0 <GetMemoryAddressLimits>
    8b91:       66 89 45 f6             mov    WORD PTR [ebp-0xa],ax
    8b95:       eb fe                   jmp    8b95 <KernelMain32+0x21>

从那里我们可以看到它memAddr确实位于ebp-10堆栈上,与我们gdb> info addr memAddr告诉我们的一致。

矮人信息(objdump --dwarf kernel.elf):

<1><4ff>: Abbrev Number: 20 (DW_TAG_subprogram)
    <500>   DW_AT_external    : 1
    <501>   DW_AT_name        : (indirect string, offset: 0x23c): KernelMain32
    <505>   DW_AT_decl_file   : 2
    <506>   DW_AT_decl_line   : 79
    <507>   DW_AT_decl_column : 6
    <508>   DW_AT_low_pc      : 0x8b74
    <50c>   DW_AT_high_pc     : 0x8b97
    <510>   DW_AT_frame_base  : 0x20 (location list)
    <514>   DW_AT_GNU_all_call_sites: 1
    <515>   DW_AT_sibling     : <0x544>
 <2><519>: Abbrev Number: 21 (DW_TAG_variable)
    <51a>   DW_AT_name        : (indirect string, offset: 0x2d6): memAddr
    <51e>   DW_AT_decl_file   : 2
    <51f>   DW_AT_decl_line   : 86
    <520>   DW_AT_decl_column : 19
    <521>   DW_AT_type        : <0x4f3>
    <525>   DW_AT_location    : 2 byte block: 91 6e     (DW_OP_fbreg: -18)

和相关片段来自objdump --dwarf=loc kernel.elf

    Offset   Begin            End              Expression
    00000000 <End of list>
objdump: Warning: There is an overlap [0x8 - 0x0] in .debug_loc section.
    00000000 <End of list>
objdump: Warning: There is a hole [0x8 - 0x20] in .debug_loc section.
    00000020 00008b74 00008b75 (DW_OP_breg4 (esp): 4)
    0000002c 00008b75 00008b77 (DW_OP_breg4 (esp): 8)
    00000038 00008b77 00008b97 (DW_OP_breg5 (ebp): 8)
    00000044 <End of list>
    [...]

这些似乎都是我所期望的。(不过,我不确定最后一个警告是否有意义)。

附加说明

如果我将编译标志更改-gdwarf-2-g我得到

gdb> p &memAddr
$1 = (MemAddrLimits *) 0x8ffde

gdb> info addr memAddr
Symbol "memAddr" is a complex DWARF expression:
     0: DW_OP_fbreg -18
.

gdb> p memAddr
$2 = {packed = 0, limits = {PhysicalLimit = 0 '\000', LinearLimit = 0 '\000'}}
gdb> p/x $ebp-10
$3 = 0x8ffee

所以memAddr仍然没有正确解决,但p &memAddr至少在堆栈框架中,而不是完全不同的地方。不过,info addr memAddr现在好像有问题了……

标签: cdebugginggccgdbdwarf

解决方案


经过进一步调查,我发现这是由于在 x86-64 qemu 仿真系统上远程调试 32 位代码(我的内核尚未切换到长模式)所致。如果我调试相同的代码,qemu-system-i386一切正常。


推荐阅读