【问题标题】:Assembly code different from gdb display of code汇编代码不同于gdb显示代码
【发布时间】:2020-12-10 12:28:41
【问题描述】:

我正在从《从 0 到 1 的操作系统》一书中学习操作系统,我正在尝试在我的内核中显示名为 main 的代码,但是即使我跳转到 GDB 中显示的代码也不相同作为入口点的地址。

bootloader.asm

;*************************************************
; bootloader.asm 
; A Simple Bootloader
;*************************************************
bits 16
start: jmp  boot

;; constants and variable definitions
msg db "Welcome to My Operating System!", 0ah, 0dh, 0h

boot:

    cli ; no interrupts 
    cld ; all that we need to init
    
    mov ax, 0x0000

    ;; set buffer
    mov es, ax  
    mov bx, 0x0600

    mov al, 1   ; read one sector
    mov ch, 0   ; track 0
    mov cl, 2       ; sector to read
    mov dh, 0   ; head number
    mov dl, 0   ; drive number
        
    mov ah, 0x02    ; read sectors from disk    
    int 0x13      ; call the BIOS routine
    jmp 0x0000:0x0600   ; jump and execute the sector!
    
    hlt ; halt the system 

; We have to be 512 bytes. Clear the rest of the bytes with 0

times 510 - ($-$$) db 0
dw 0xAA55 ; Boot Signature

readelf -l 主要

ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x600
  Start of program headers:          52 (bytes into file)
  Start of section headers:          12888 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         3
  Size of section headers:           40 (bytes)
  Number of section headers:         12
  Section header string table index: 11

readelf -l 主要


Elf file type is EXEC (Executable file)
Entry point 0x600
There are 3 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000000 0x00000000 0x00000000 0x00094 0x00094 R   0x4
  LOAD           0x000000 0x00000000 0x00000000 0x00094 0x00094 R   0x4
  LOAD           0x000100 0x00000600 0x00000600 0x00006 0x00006 R E 0x100

 Section to Segment mapping:
  Segment Sections...
   00     
   01     
   02     .text 

main.c

void main(){}

objdump -z -M intel -S -D build/os/main

Disassembly of section .text:

00000600 <main>:
void main(){}
 600:   55                      push   ebp
 601:   89 e5                   mov    ebp,esp
 603:   90                      nop
 604:   5d                      pop    ebp
 605:   c3                      ret    

但这是 GDB 在 main 0x600 处设置断点的输出

0x600 <main>    jg     0x647                                               │
│   0x602 <main+2>  dec    esp                                                 │
│   0x603 <main+3>  inc    esi                                                 │
│   0x604 <main+4>  add    DWORD PTR [ecx],eax                                 │

为什么会这样?我在错误的地址加载吗?如何找到正确的加载地址?

编辑: 这是编译的代码;

nasm -f elf bootloader.asm -F dwarf -g -o ../build/bootloader/bootloader.o
ld -m elf_i386 -T bootloader.lds ../build/bootloader/bootloader.o -o ../build/bootloader/bootloader.o.elf
objcopy -O binary ../build/bootloader/bootloader.o.elf ../build/bootloader/bootloader.o
gcc -ffreestanding -nostdlib -fno-pic -gdwarf-4 -m16 -ggdb3 -c main.c -o ../build/os/main.o
ld -m elf_i386 -nmagic -T os.lds  ../build/os/main.o -o ../build/os/main
dd if=/dev/zero of=disk.img bs=512 count=2880
2880+0 records in
2880+0 records out
1474560 bytes (1.5 MB, 1.4 MiB) copied, 0.0150958 s, 97.7 MB/s
dd conv=notrunc if=build/bootloader/bootloader.o of=disk.img bs=512 count=1 seek=0
1+0 records in
1+0 records out
512 bytes copied, 0.000127745 s, 4.0 MB/s
dd conv=notrunc if=build/os/main.o of=disk.img bs=512 count=$((8504/512))
seek=1
16+0 records in
16+0 records out
8192 bytes (8.2 kB, 8.0 KiB) copied, 0.000184251 s, 44.5 MB/s
qemu-system-i386 -machine q35 -fda disk.img -gdb tcp::26000 -S

和显示主要代码的gdb代码;

set architecture i8086
target remote localhost:26000
b *0x7c00
set disassembly-flavor intel
layout asm
layout reg
symbol-file build/os/main
b main

【问题讨论】:

  • 您使用什么命令将您的源代码构建为可执行文件,以及您究竟是如何在任何东西上运行 GDB?在 QEMU 遥控器中?您是否可能用 ASCII 文本覆盖了代码?检查“代码”的十六进制转储以查看数据是否看起来像其他东西。 (虽然这不太可能,the opcode 对应于 add r/m32, r3201。)
  • 是的,我在 qemu 环境中运行它。我通过本地主机将程序与 gdb 连接起来。我不确定用 ASCII 文本覆盖代码是什么意思。
  • 您的命令(特别是 gcc -m16)与您的 objdump 输出不匹配。 -m16 会使用 66 55 push ebp 等等:16 位模式下的 32 位操作数大小,使用 66 前缀。 (哪个 objdump 反汇编为 push bp,因为它位于 32 位 ELF 可执行文件中)。喜欢godbolt.org/z/x7eTnx。您的 objdump 输出看起来像是使用 -m32 编译的。并不是说它改变了答案,除了需要-m16的部分,但这意味着你的问题不是自洽的。

标签: c assembly gdb kernel bootloader


【解决方案1】:

jg / dec esp / inc esi 是 ELF 幻数,而不是机器码!从ndisasm -b32 /bin/ls 的输出开始,您会看到相同的内容。 (ndisasm 始终将其输入视为平面二进制;它不查找任何元数据。)

7F 45 4C 46 是 0x7F 字节后的字符串 "ELF",将文件格式标识为 ELF 的 ELF 幻数。在main 的实际机器代码之前,它后面跟着更多的 ELF 标头字节。 objdump -D 反汇编所有 ELF 部分,但它仍然解析 ELF 标头,而不是像 ndisasm 那样反汇编 them。所以你最终还是会看到 .text 部分的代码,因为其他部分是空的(因为你链接了这个可执行文件而没有 libc 或 CRT 启动文件,并且使用 C main 作为 ELF 入口点?!?)

您正在跳转到 ELF 文件的开头,就好像它是一个平面二进制文件一样。不是,编写一个 ELF 程序加载器并不是那么简单。 ELF 程序标头(readelf 可以解析)告诉您哪个文件偏移量位于哪个地址。 .text 部分的开头将在文件中的某个偏移处,由于明显的原因不与 ELF 幻数重叠。 (虽然它可以与 ELF 标头重叠,如果您能找到使其适合的方法:http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html

然后,一旦将文件映射到程序头中指定的内存中,就跳转到 ELF 入口点地址(在您的情况下为 0x600)。 (这通常不是一个函数;在像 Linux 这样的真实操作系统下,你不能从入口点ret。相反,你需要进行退出系统调用。)你不能在这里,或者,因为你jmp而不是call

这就是_startmain 分开的原因;用编译器生成的main 作为入口点来构建程序是行不通的。

当然,大部分努力都注定要失败,因为您在 CPU 仍处于 16 位实模式的情况下跳转到主线程。但是您的 main 是针对 32 位模式编译/汇编的。你可以通过gcc -m16 来解决这个问题,为 16 位模式组装 gcc 输出,必要时使用操作数大小 + 地址大小前缀。

那个无所事事的主程序的机器代码实际上可以在 16 位和 32 位模式下工作。如果您在没有优化的情况下使用return 0,则情况并非如此:mov eax, imm32 的操作码(不带前缀)意味着不同的指令长度,具体取决于 CPU 解码的模式,因此解码为 16-位模式会写入 AX 并留下 2 个字节的零。


很可能最简单的做法是将“内核”转换为平面二进制文件,而不是在引导加载程序中编写 ELF 程序加载程序。遵循 osdev 教程,因为可能会出现很多问题,例如,您必须小心静态数据。

或参阅How to make the kernel for my bootloader? 以获取在切换到 32 位保护模式后调用 C 函数的示例引导加载程序

https://stackoverflow.com/tags/x86/info中查看更多链接。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 2014-02-03
    • 1970-01-01
    • 2017-12-25
    • 2012-06-26
    相关资源
    最近更新 更多