Как разобрать 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-битный код?
Смотрите также:
- в 2007 году разработчик GDB ответил "использовать
objdump
" https://www.sourceware.org/ml/gdb/2007-03/msg00308.html как описано в разделе Как разобрать необработанный код x86? Может быть, он был реализован тем временем? - суперсет: использование GDB в 16-битном режиме
- похоже, но у ОП там была ошибка, так может это что то другое? Как мне разобрать win16 с GDB
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), вам нужно переопределить ее:
- Загрузить файл описания - target.xml (gist)
- Запустите GDB и подключитесь к вашей цели (
target remote ...
) - Установите целевую архитектуру, используя файл описания -
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
примечание:
странно, что в целевой спецификации нужно и то, и другое
установить архитектуру i8086
и
установить имя файла tdesc target.xml
для обработки инструкции INT используйте stepo перед выполнением или добавьте точку останова после кода INT.
Работает с:
set architecture i8086
как упомянул Шут.
set architecture
задокументировано по адресу: https://sourceware.org/gdb/onlinedocs/gdb/Targets.html и мы можем получить список целей с помощью:
set architecture
(без аргументов) или завершение табуляции в приглашении GDB.