Как разобрать 16-разрядный код загрузочного сектора x86 в GDB с помощью «x / i $ pc»? Он рассматривается как 32-битный
Например, с загрузочным сектором, который BIOS печатает на экране 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'
Я получил:
- Почему нет регистра, который содержит более высокие байты EAX?
- Прямой просмотр программы
- Что такое 0x10 в инструкции по сборке «leal 0x10 (% ebx),% eax» x86?
- Почему этот код SSE в 6 раз медленнее без VZEROUPPER на Skylake?
- Ошибка сегментации сборки после выполнения системного вызова, в конце моего кода
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, как описано вobjdump
« Как разобрать необработанный код x86»? Может быть, это было реализовано? - superset: использование GDB в 16-битном режиме
- похоже, но у ОР там была ошибка, так что, может быть, это что-то еще? Как разобрать win16 с помощью GDB
- Что происходит, когда запускается компьютерная программа?
- clflush для аннулирования строки кэша через функцию C
- Как читать и писать регистры x86 флаги напрямую?
- Использование базового указателя в C ++ inline asm
- Вызов 32-битного кода из 64-битного процесса
- Медленная инструкция jmp
- Как слить скаляр в вектор без компилятора, теряющего инструкцию обнуления верхних элементов? Ограничение дизайна в встроенных средах Intel?
- Какие целые операции с дополнением 2 можно использовать без обнуления высоких бит в входах, если требуется только низкая часть результата?
Как правильно отметил Джесмер в комментарии, вам просто нужно использовать 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
Я добавил несколько комментариев, чтобы определить тривиальные изменения. Теперь мы можем использовать такие команды, чтобы собрать наш файл, чтобы он содержал вывод отладки в формате карлика. Мы связываем его с окончательным изображением эльфа. Это изображение эльфа может использоваться для символической отладки gdb
. Затем мы можем преобразовать формат эльфа в плоский двоичный код с 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
будет показывать вам parsingку неправильной ячейки памяти, поскольку она не учитывала 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 (режим экрана изменения).
stepo: это каббалистический макрос, используемый для функции «переключение» и прерывания вызовов. Как это работает ? Код операции текущей команды извлекается, и если это функция или вызов прерывания, вычисляется адрес «следующей» инструкции, временная точка останова добавляется на этот адрес и вызывается функция «продолжить».
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 : 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
Он работает с:
set architecture i8086
как упоминалось в «Шутере» .
set architecture
документируется по адресу: https://sourceware.org/gdb/onlinedocs/gdb/Targets.html, и мы можем получить список целей с помощью:
set architecture
(без аргументов) или завершение табуляции в приглашении GDB.