代码之家  ›  专栏  ›  技术社区  ›  itarato Randy Sugianto 'Yuku'

GDB报告静态C变量的意外内存

gdb c
  •  1
  • itarato Randy Sugianto 'Yuku'  · 技术社区  · 5 年前

    os-dev tutorial .
    here

    代码如下所示:

    #define BUFSIZE 255
    static char buf[BUFSIZE];
    
    void foo() {
      // Making sure it's all zero.
      for (int i = 0; i < BUFSIZE; i++) buf[i] = 0;
    
      // Setting first char:
      buf[0] = 'a';
    
      // >> insert breakpoint right after setting the char <<
    
      // Prints 'a'.
      printf("%s", buf);
    }
    

    如果我在标记点处放置断点并用 p buf p &buf 我得到了一些看起来不正确的东西,有两个原因:

    1. 如果我做一个 char* p_buf = buf 我和你核对了地址 p p_buf 它给了我一个完全不同的地址,在执行过程中是稳定的(另一个不是)。然后我用 x /255b 0x____ a

    2. 下一个命令( printf("%s", buf); .

    我应该从哪里开始调试这个?


    有关编译条件的详细信息:

    • -g -Wall -Wextra -pedantic -nostdlib -nostdinc -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs -m32
    • qemu-系统-i386

    GDB输出示例:

    (gdb) p buf
    $1 = "dfghjkl;'`\000\\zxcvbnm,./\000*\000 ", '\000' <repeats 198 times>...
    (gdb) p p_buf
    $2 = 0x40c0 <buf+224> "a"
    (gdb) p &buf
    $3 = (char (*)[255]) 0x3fe0 <buf>
    (gdb) info address buf
    Symbol "buf" is static storage at address 0x3fe0.
    

    更新2:

    反汇编显示差异的代码版本:

    ; void foo
    0x19f1 <foo>            push   %ebp
    0x19f2 <foo+1>          mov    %esp,%ebp
    0x19f4 <foo+3>          sub    $0x10,%esp
    
    ; char* p_buf = char_buf; --> `p &char_buf` is 0x4040 (incorrect) but `p p_buf` is 0x4100
    0x19f7 <foo+6>          movl   $0x4100,-0x4(%ebp)
    
    ; void* p_p_buf = (void*)p_buf; --> `p p_p_buf` gives 0x4100
    0x19fe <foo+13>         mov    -0x4(%ebp),%eax
    0x1a01 <foo+16>         mov    %eax,-0x8(%ebp)
    
    ; void* p_char_buf = (void*)&char_buf; --> `p p_char_buf` gives 0x4100
    0x1a04 <foo+19>         movl   $0x4100,-0xc(%ebp)
    
    ; char_buf[0] = 'a'; --> correct address
    0x1a0b <foo+26>         movb   $0x61,0x4100
    
    ; char_buf[1] = 'b'; --> correct address (asking `p &char_buf` here is still incorrectly 0x4040)
    0x1a12 <foo+33>         movb   $0x62,0x4101
    
    ; void foo return
    0x1a19 <foo+40>         nop
    0x1a1a <foo+41>         leave
    0x1a1b <foo+42>         ret
    

    我的 Makefile 对于构建项目,如下所示:

    C_SOURCES = $(wildcard kernel/*.c drivers/*.c)
    C_HEADERS = $(wildcard kernel/*.h drivers/*.h)
    OBJ = ${C_SOURCES:.c=.o kernel/interrupt_table.o}
    CC = /home/itarato/code/os/i386elfgcc/bin/i386-elf-gcc
    # GDB = /home/itarato/code/os/i386elfgcc/bin/i386-elf-gdb
    GDB = /usr/bin/gdb
    CFLAGS = -g -Wall -Wextra -ffreestanding -fno-exceptions -pedantic -fno-builtin -fno-stack-protector -nostartfiles -nodefaultlibs -m32
    QEMU = qemu-system-i386
    
    os-image.bin: boot/boot.bin kernel.bin
        cat $^ > $@
    
    kernel.bin: boot/kernel_entry.o ${OBJ}
        i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
    
    kernel.elf: boot/kernel_entry.o ${OBJ}
        i386-elf-ld -o $@ -Ttext 0x1000 $^
    
    kernel.dis: kernel.bin
        ndisasm -b 32 $< > $@
    
    run: os-image.bin
        ${QEMU} -drive format=raw,media=disk,file=$<,index=0,if=floppy
    
    debug: os-image.bin kernel.elf
        ${QEMU} -s -S -drive format=raw,media=disk,file=$<,index=0,if=floppy &
        ${GDB} -ex "target remote localhost:1234" -ex "symbol-file kernel.elf" -ex "tui enable" -ex "layout split" -ex "focus cmd"
    
    %.o: %.c ${C_HEADERS}
        ${CC} ${CFLAGS} -c $< -o $@
    
    %.o: %.asm
        nasm $< -f elf -o $@
    
    %.bin: %.asm
        nasm $< -f bin -o $@
    
    build: os-image.bin
        echo Pass
    
    clean:
        rm -rf *.bin *.o *.dis *.elf
        rm -rf kernel/*.o boot/*.bin boot/*.o
    
    1 回复  |  直到 5 年前
        1
  •  3
  •   Michael Petch    5 年前

    这是一个有趣的问题。归根结底,LD(linker)为ELF可执行文件生成的代码 kernel.elf kernel.bin 当使用 --oformat binary 选项。虽然人们期望这些是相同的,但事实并非如此。

    更简单的说 Makefile 规则生成的代码与预期的不同:

    kernel.elf: boot/kernel_entry.o ${OBJ}
            i386-elf-ld -o $@ -Ttext 0x1000 $^
    

    kernel.bin: boot/kernel_entry.o ${OBJ}
            i386-elf-ld -o $@ -Ttext 0x1000 $^ --oformat binary
    

    不同之处似乎在于链接器在有和无链接时如何对齐节 --格式二进制 . ELF文件(以及用于调试的符号)显示在一个位置,而实际在QEMU中运行的二进制文件以不同的偏移量生成代码和数据。

    生成文件 规则可以修改如下:

    kernel.bin: kernel.elf
            i386-elf-objcopy -O binary $^ $@
    
    kernel.elf: boot/kernel_entry.o ${OBJ}
            i386-elf-ld -o $@ -Ttext 0x1000 $^
    

    这样做将确保生成的二进制文件与为ELF可执行文件生成的二进制文件匹配。

        2
  •  4
  •   jpa    5 年前

    对我来说,这似乎没有发生:

    Breakpoint 1, main () at test65.c:16
    16    printf("%s", buf);
    (gdb) p buf
    $2 = "a", '\000' <repeats 253 times>
    

    我应该从哪里开始调试这个?

    似乎有两件事可能会出错:

    我不确定是什么原因造成的,但很容易核实。检查什么地址 p &buf 给你的。然后把它和你从中得到的比较一下 p_buf 还有什么 info address buf 给你看。

    注意,由于 address space layout randomization 静态变量的地址将在启动进程时发生更改。那之前呢 run 0x4040 然后换成 0x555555558040

    (gdb) info address buf
    Symbol "buf" is static storage at address 0x4040.
    (gdb) run
    ....
    Breakpoint 1, main () at test65.c:16
    16    printf("%s", buf);
    (gdb) p &buf
    $1 = (char (*)[255]) 0x555555558040 <buf>
    (gdb) info address buf
    Symbol "buf" is static storage at address 0x555555558040.
    

    buf[0] = a 在断点到达的点之后,尽管它必须在 printf() -O0 看看有没有什么变化。

    您也可以使用 disas

    (gdb) disas
    Dump of assembler code for function main:
       0x000055555555517b <+50>:    movb   $0x61,0x2ebe(%rip)        # 0x555555558040 <buf>
    => 0x0000555555555182 <+57>:    lea    0x2eb7(%rip),%rsi        # 0x555555558040 <buf>
       0x0000555555555189 <+64>:    lea    0xe74(%rip),%rdi        # 0x555555556004
       0x0000555555555190 <+71>:    mov    $0x0,%eax
       0x0000555555555195 <+76>:    callq  0x555555555050 <printf@plt>
    

    对我来说,断点正好落在 movb 0x61 a )至 buf

    如果你使用 stepi callq printf 指令,您可以确保看到的缓冲区与 printf

    推荐文章