Как разобрать 16-битный код загрузочного сектора x86 в GDB с помощью "x/i $pc"? Это рассматривается как 32-разрядный

Например, с загрузочным сектором, который печатает BIOS a на экран main.asm:

org 0x7c00
bits 16
cli
mov ax, 0x0E61
int 0x10
hlt
times 510 - ($-$$) db 0
dw 0xaa55

Затем:

nasm -o main.img main.asm
qemu-system-i386 -hda main.img -S -s &
gdb -ex 'target remote localhost:1234' \
    -ex 'break *0x7c00' \
    -ex 'continue' \
    -ex 'x/3i $pc'

Я получил:

0x7c00:      cli    
0x7c01:      mov    $0x10cd0e61,%eax
0x7c06:      hlt 

Так выглядит mov ax, 0x0E61 был интерпретирован как 32-разрядный mov %eax и съел следующую инструкцию int 0x10 как данные.

Как я могу сказать GDB, что это 16-битный код?

Смотрите также:

5 ответов

Как правильно заметил Шестер в комментарии, вам просто нужно использовать set architecture i8086 когда используешь gdb так что он знает, чтобы принять 16-битный формат инструкции 8086. Вы можете узнать о целях GDB здесь.

Я добавляю это как ответ, потому что это было слишком сложно объяснить в комментарии. Если вы собираете и связываете вещи отдельно, вы можете генерировать отладочную информацию, которая затем может быть использована gdb обеспечить отладку на уровне исходного кода даже при удаленном выполнении с 16-битным кодом. Для этого мы немного изменим ваш сборочный файл:

;org 0x7c00    - remove as it may be rejected when assembling
;                with elf format. We can specify it on command
;                line or via a linker script.
bits 16

; Use a label for our main entry point so we can break on it
; by name in the debugger
main:
    cli
    mov ax, 0x0E61
    int 0x10
    hlt
    times 510 - ($-$$) db 0
    dw 0xaa55

Я добавил несколько комментариев, чтобы определить тривиальные изменения. Теперь мы можем использовать такие команды, чтобы собрать наш файл так, чтобы он содержал отладочный вывод в формате dwarf. Мы связываем это с окончательным изображением эльфа. Это изображение эльфа может быть использовано для символической отладки gdb, Затем мы можем преобразовать формат elf в плоский двоичный файл с objcopy

nasm -f elf32 -g3 -F dwarf main.asm -o main.o
ld -Ttext=0x7c00 -melf_i386 main.o -o main.elf
objcopy -O binary main.elf main.img

qemu-system-i386 -hda main.img -S -s &
gdb main.elf \
        -ex 'target remote localhost:1234' \
        -ex 'set architecture i8086' \
        -ex 'layout src' \
        -ex 'layout regs' \
        -ex 'break main' \
        -ex 'continue'

Я сделал несколько небольших изменений. Я использую main.elf файл (с символьной информацией) при запуске gdb,

Я также добавил еще несколько полезных макетов для кода сборки и регистров, которые могут упростить отладку в командной строке. Я тоже ломаю main (не адрес). Исходный код из нашего файла сборки также должен появиться из-за отладочной информации. Ты можешь использовать layout asm вместо layout src если вы предпочитаете видеть необработанную сборку.

Эта общая концепция может работать в других форматах, поддерживаемых NASM и LD на других платформах. elf32 а также elf_i386 а также тип отладки должен быть изменен для конкретной среды. Мой пример предназначен для систем, которые понимают двоичные файлы Linux Elf32.


Отладка 16-битного реального режима загрузчика с помощью GDB/QEMU

К сожалению по умолчанию gdb не выполняет вычисления сегмента: смещение и будет использовать значение в EIP для точек останова. Вы должны указать точки останова как 32-битные адреса (EIP).

Когда дело доходит до перехода по коду реального режима, это может быть громоздким, потому что gdb не обрабатывает сегментацию в реальном режиме. Если вы войдете в обработчик прерываний, вы обнаружите, gdb отобразит код сборки относительно EIP. фактически gdb будет показывать вам разборку неправильной ячейки памяти, поскольку она не учитывает CS. К счастью, кто-то создал скриптGDB, чтобы помочь. Загрузите скрипт в каталог разработки и запустите QEMU с чем-то вроде:

qemu-system-i386 -hda main.img -S -s &
gdb -ix gdbinit_real_mode.txt main.elf \
        -ex 'target remote localhost:1234' \
        -ex 'break main' \
        -ex 'continue'

Сценарий заботится о настройке архитектуры на i8086, а затем подключается к gdb, Он предоставляет ряд новых макросов, которые могут упростить пошаговое выполнение 16-битного кода.

break_int: добавляет точку останова на программный вектор прерывания (так, как старые добрые MS DOS и BIOS выставляют свои API)

break_int_if_ah: добавляет условную точку останова на программном прерывании. AH должен быть равен данному параметру. Это используется для фильтрации сервисных вызовов прерываний. Например, иногда вы хотите прерывать работу только тогда, когда вызывается функция AH=0h прерывания 10h (изменить режим экрана).

Степо: это каббалистический макрос, используемый для "перешагивания" функции и прерывания вызовов. Как это работает? Извлекается код операции текущей инструкции, и, если это вызов функции или прерывания, вычисляется адрес "следующей" инструкции, на этот адрес добавляется временная точка останова и вызывается функция "продолжить".

step_until_ret: это используется для одиночного шага, пока мы не встретим команду "RET".

step_until_iret: это используется для одиночного шага, пока мы не встретим инструкцию "IRET".

step_until_int: это используется для одиночного шага, пока мы не встретим инструкцию INT.

Этот сценарий также распечатывает адреса и регистры с сегментацией, рассчитанной в. Вывод после каждого выполнения инструкции выглядит следующим образом:

---------------------------[ STACK ]---
D2EA F000 0000 0000 6F62 0000 0000 0000
7784 0000 7C00 0000 0080 0000 0000 0000
---------------------------[ DS:SI ]---
00000000: 53 FF 00 F0 53 FF 00 F0 C3 E2 00 F0 53 FF 00 F0  S...S.......S...
00000010: 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0  S...S...S...S...
00000020: A5 FE 00 F0 87 E9 00 F0 76 D6 00 F0 76 D6 00 F0  ........v...v...
00000030: 76 D6 00 F0 76 D6 00 F0 57 EF 00 F0 76 D6 00 F0  v...v...W...v...
---------------------------[ ES:DI ]---
00000000: 53 FF 00 F0 53 FF 00 F0 C3 E2 00 F0 53 FF 00 F0  S...S.......S...
00000010: 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0 53 FF 00 F0  S...S...S...S...
00000020: A5 FE 00 F0 87 E9 00 F0 76 D6 00 F0 76 D6 00 F0  ........v...v...
00000030: 76 D6 00 F0 76 D6 00 F0 57 EF 00 F0 76 D6 00 F0  v...v...W...v...
----------------------------[ CPU ]----
AX: AA55 BX: 0000 CX: 0000 DX: 0080
SI: 0000 DI: 0000 SP: 6F2C BP: 0000
CS: 0000 DS: 0000 ES: 0000 SS: 0000

IP: 7C00 EIP:00007C00
CS:IP: 0000:7C00 (0x07C00)
SS:SP: 0000:6F2C (0x06F2C)
SS:BP: 0000:0000 (0x00000)
OF <0>  DF <0>  IF <1>  TF <0>  SF <0>  ZF <0>  AF <0>  PF <0>  CF <0>
ID <0>  VIP <0> VIF <0> AC <0>  VM <0>  RF <0>  NT <0>  IOPL <0>
---------------------------[ CODE ]----
=> 0x7c00 <main>:       cli
   0x7c01:      mov    ax,0xe61
   0x7c04:      int    0x10
   0x7c06:      hlt
   0x7c07:      add    BYTE PTR [bx+si],al
   0x7c09:      add    BYTE PTR [bx+si],al
   0x7c0b:      add    BYTE PTR [bx+si],al
   0x7c0d:      add    BYTE PTR [bx+si],al
   0x7c0f:      add    BYTE PTR [bx+si],al
   0x7c11:      add    BYTE PTR [bx+si],al

Ответы, уже предоставленные здесь, являются правильными, кажется, неправильно ведут себя с последними версиями gdb и / или qemu,

Это открытый вопрос о программном обеспечении с текущими подробностями.

TL; DR

Когда в реальном режиме qemu будет согласовывать неправильную архитектуру (i386), вам нужно переопределить ее:

  1. Загрузить файл описания - target.xml (gist)
  2. Запустите GDB и подключитесь к вашей цели (target remote ...)
  3. Установите целевую архитектуру, используя файл описания - set tdesc filename target.xml

Настройка архитектуры на GDB

Обычно, когда вы отлаживаете ELF, PE или любой другой объектный файл GDB может выводить архитектуру из заголовков файлов. При отладке загрузчика нет объектного файла для чтения, так что вы можете сказать gdb архитектура сама (в случае загрузчика арка будет i8086):

set architecture <arch>

Примечание: при подключении к виртуальной машине qemu фактически нет необходимости сообщать gdb желаемую архитектуру, qemu будет согласовывать эту информацию для вас через qXfer протокол.

Переопределение целевой архитектуры

Как упоминалось выше при отладке qemu виртуальные машины qemu будет на самом деле договориться о своей архитектуре gdbпри 32-битной архитектуре x86 архитектура, вероятно, i386Это не та архитектура, которую мы хотим для реального режима.

В настоящее время, похоже, существует проблема в gdb, которая заставляет его выбирать наиболее "многофункциональную совместимую архитектуру" между архитектурой цели (i386) и предоставленной пользователем архитектурой (i8086). Так как gdb видит i386 как правильный супер набор i8086 он использует это вместо этого. Выбор i386 приводит к тому, что все операнды по умолчанию устанавливаются в 32 бита (вместо 16), что вызывает ошибки дизассемблера.

Вы можете переопределить целевую архитектуру, указав target.xml файл описания:

set tdesc filename <file>

Я сделал этот файл описания из qemu Исходники и изменили архитектуру на i8086.

Файл описания цели, похоже, изменился в qemu, поэтому связанный xml из ответа Матана Шахара больше не работает. Но можно сделать примерно так:

$ echo '<?xml version="1.0"?><!DOCTYPE target SYSTEM "gdb-target.dtd"><target><architecture>i8086</architecture><xi:include href="i386-32bit.xml"/></target>' > target.xml
$ wget https://raw.githubusercontent.com/qemu/qemu/master/gdb-xml/i386-32bit.xml

А потом:

(gdb) target remote localhost:26000
[...]
Breakpoint 1, 0x00007c00 in ?? ()
(gdb) x /5i 0x7c24
   0x7c24:      cli    
   0x7c25:      cld    
   0x7c26:      mov    eax,0xc08e0050
   0x7c2b:      xor    ebx,ebx
   0x7c2d:      mov    al,0x2
(gdb) set tdesc filename target.xml 
warning: A handler for the OS ABI "GNU/Linux" is not built into this configuration
of GDB.  Attempting to continue with the default i8086 settings.

(gdb) x /5i 0x7c24
   0x7c24:      cli    
   0x7c25:      cld    
   0x7c26:      mov    ax,0x50
   0x7c29:      mov    es,ax
   0x7c2b:      xor    bx,bx

Примечание: set debug remote-packet-max-chars 2000 а также set debug remote 1 Команды gdb были полезны для отладки этого.

2020 12 24 Обновить

проверено на gdb-9.2.0 Ubuntu20.04

следующий target.xml от @Kirill Spitsyn

      $ echo '<?xml version="1.0"?><!DOCTYPE target SYSTEM "gdb-target.dtd"><target><architecture>i8086</architecture><xi:include href="i386-32bit.xml"/></target>' > target.xml
$ wget https://raw.githubusercontent.com/qemu/qemu/master/gdb-xml/i386-32bit.xml

следующий gdbinit основан на gdb_init_real_mode.txt и переименован в gdb.txt

ДОБАВЬТЕ следующую строку в gdb.txt в разделе # Real Mode

      set tdesc filename target.xml

после редактирования gdb.txt

      # Real mode
set architecture i8086
set tdesc filename target.xml

затем соберите и протестируйте:

основной.asm:

      bits 16

; Use a label for our main entry point so we can break on it
; by name in the debugger
main:
    cli
    mov ax, 0x0E61
    int 0x10
    hlt
    times 510 - ($-$$) db 0
    dw 0xaa55

скомпилировать.fish:

      nasm -f elf32 -g3 -F dwarf main.asm -o main.o
ld -Ttext=0x7c00 -melf_i386 main.o -o main.elf
objcopy -O binary main.elf main.img
qemu-system-i386 -hda main.img -S -s &
gdb --nx -ix gdb.txt main.elf \
        -ex 'target remote localhost:1234'

Результат:

      real-mode-gdb$ b main  
Breakpoint 1 at 0x7c00: file main.asm, line 6.  
real-mode-gdb$ c   
Continuing.  
---------------------------[ STACK ]---  
D002 F000 0000 0000 6F5E 0000 8016 0000   
8057 0000 0000 0000 0000 0000 8016 0000   
---------------------------[ DS:SI ]---  
00000000: 53 FF 00 F0 53 FF 00 F0 C3 E2 00 F0 53 FF 00 F0  S...S.......S...  
00000010: 53 FF 00 F0 54 FF 00 F0 53 FF 00 F0 53 FF 00 F0  S...T...S...S...  
00000020: A5 FE 00 F0 87 E9 00 F0 42 D4 00 F0 42 D4 00 F0  ........B...B...  
00000030: 42 D4 00 F0 42 D4 00 F0 57 EF 00 F0 42 D4 00 F0  B...B...W...B...  
---------------------------[ ES:DI ]---  
00000000: 53 FF 00 F0 53 FF 00 F0 C3 E2 00 F0 53 FF 00 F0  S...S.......S...  
00000010: 53 FF 00 F0 54 FF 00 F0 53 FF 00 F0 53 FF 00 F0  S...T...S...S...  
00000020: A5 FE 00 F0 87 E9 00 F0 42 D4 00 F0 42 D4 00 F0  ........B...B...  
00000030: 42 D4 00 F0 42 D4 00 F0 57 EF 00 F0 42 D4 00 F0  B...B...W...B...  
----------------------------[ CPU ]----  
AX: AA55 BX: 0000 CX: 0000 DX: 0080  
SI: 0000 DI: 0000 SP: 6F00 BP: 0000  
CS: 0000 DS: 0000 ES: 0000 SS: 0000  
  
IP: 7C00 EIP:00007C00  
CS:IP: 0000:7C00 (0x07C00)  
SS:SP: 0000:6F00 (0x06F00)  
SS:BP: 0000:0000 (0x00000)  
OF <0>  DF <0>  IF <1>  TF <0>  SF <0>  ZF <0>  AF <0>  PF <0>  CF <0>  
ID <0>  VIP <0> VIF <0> AC <0>  VM <0>  RF <0>  NT <0>  IOPL <0>  
---------------------------[ CODE ]----  
=> 0x7c00 <main>:       cli  
   0x7c01:      mov    ax,0xe61  
   0x7c04:      int    0x10  
   0x7c06:      hlt  
   0x7c07:      add    BYTE PTR [bx+si],al  
   0x7c09:      add    BYTE PTR [bx+si],al  
   0x7c0b:      add    BYTE PTR [bx+si],al  
   0x7c0d:      add    BYTE PTR [bx+si],al  
   0x7c0f:      add    BYTE PTR [bx+si],al  
   0x7c11:      add    BYTE PTR [bx+si],al  
  
Breakpoint 1, main () at main.asm:6  
6           cli  

примечание:

  1. странно, что в целевой спецификации нужно и то, и другое

    установить архитектуру i8086

    и

    установить имя файла tdesc target.xml

  2. для обработки инструкции INT используйте stepo перед выполнением или добавьте точку останова после кода INT.

Работает с:

set architecture i8086

как упомянул Шут.

set architecture задокументировано по адресу: https://sourceware.org/gdb/onlinedocs/gdb/Targets.html и мы можем получить список целей с помощью:

set architecture

(без аргументов) или завершение табуляции в приглашении GDB.

Другие вопросы по тегам