assembly x86 gdb qemu 16-bit

assembly - ¿Cómo desmontar el código del sector de arranque x86 de 16 bits en GDB con "x/i $ pc"? Se trata como 32 bits.



qemu 16-bit (3)

Como Jester señaló correctamente en un comentario, solo necesita usar la set architecture i8086 cuando usa gdb para que sepa asumir el formato de instrucción 8086 de 16 bits. Puedes aprender sobre los objetivos de gdb here .

Estoy agregando esto como respuesta porque era demasiado difícil de explicar en un comentario. Si ensambla y vincula las cosas por separado, puede generar información de depuración que luego puede ser utilizada por gdb para proporcionar una depuración a nivel de origen, incluso cuando se realiza de forma remota con código de 16 bits. Para hacer esto, modificamos un poco su archivo de ensamblaje:

;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

He agregado algunos comentarios para identificar los cambios triviales realizados. Ahora podemos usar comandos como estos para ensamblar nuestro archivo de modo que contenga resultados de depuración en formato enano. Lo vinculamos a una imagen final de elfo. Esta imagen de elfo se puede utilizar para la depuración simbólica de gdb . Luego podemos convertir el formato elfo a un binario plano con 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''

He hecho algunos cambios menores. Yo uso el archivo main.elf (con información simbólica) cuando inicio gdb .

También agrego algunos layouts más útiles para el código de ensamblaje y los registros que pueden facilitar la depuración en la línea de comandos. También rompo en main (no en la dirección). El código fuente de nuestro archivo de ensamblaje también debería aparecer debido a la información de depuración. Puede usar layout asm lugar de layout src si prefiere ver el ensamblaje sin formato.

Este concepto general puede funcionar en otros formatos compatibles con NASM y LD en otras plataformas. elf32 y elf_i386 , así como el tipo de depuración, deberán modificarse para el entorno específico. Mi muestra apunta a sistemas que entienden los binarios de Linux Elf32.

Depuración del gestor de arranque en modo real de 16 bits con GDB / QEMU

Desafortunadamente, por defecto, gdb no hace cálculos de segmento: desplazamiento y utilizará el valor en EIP para puntos de interrupción. Debe especificar puntos de interrupción como direcciones de 32 bits ( EIP ).

Cuando se trata de recorrer el código en modo real, puede ser engorroso porque gdb no maneja la segmentación en modo real. Si ingresa a un controlador de interrupciones, descubrirá que gdb mostrará el código de ensamblaje relativo a EIP . Efectivamente, gdb le mostrará el desensamblaje de la ubicación de memoria incorrecta ya que no contaba con CS . Afortunadamente, alguien ha creado un script GDB para ayudar. Descargue el script en su directorio de desarrollo y luego ejecute QEMU con algo como:

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''

El script se encarga de establecer la arquitectura en i8086 y luego se conecta a gdb . Proporciona una serie de nuevas macros que pueden facilitar el paso por el código de 16 bits.

break_int: agrega un punto de interrupción en un vector de interrupción de software (la forma en que el viejo MS DOS y BIOS exponen sus API)

break_int_if_ah: agrega un punto de interrupción condicional en una interrupción de software. AH tiene que ser igual al parámetro dado. Esto se utiliza para filtrar llamadas de servicio de interrupciones. Por ejemplo, a veces solo desea interrumpir cuando se llama a la función AH = 0h de la interrupción 10h (cambiar el modo de pantalla).

stepo: esta es una macro kabalística utilizada para ''pasar'' la función e interrumpir llamadas. Como funciona ? Se extrae el código de operación de la instrucción actual y, si es una función o una llamada de interrupción, se calcula la dirección de la instrucción "siguiente", se agrega un punto de interrupción temporal en esa dirección y se llama a la función ''continuar''.

step_until_ret: se usa para dar un solo paso hasta que encontremos una instrucción ''RET''.

step_until_iret: se usa para dar un solo paso hasta encontrar una instrucción ''IRET''.

step_until_int: se usa para dar un solo paso hasta encontrar una instrucción ''INT''.

Este script también imprime direcciones y registros con la segmentación calculada. La salida después de cada ejecución de instrucción se ve así:

---------------------------[ 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

Por ejemplo, con un sector de arranque que BIOS imprime en la pantalla main.asm :

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

Entonces:

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''

Yo obtengo:

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

Por lo tanto, se parece al mov ax, 0x0E61 se interpretó como un mov %eax 32 bits y consumió la siguiente instrucción int 0x10 como datos.

¿Cómo puedo decirle a GDB que este es un código de 16 bits?

Ver también:


Funciona con:

set architecture i8086

como lo mencionó Jester .

set architecture se documenta en: here y podemos obtener una lista de objetivos con:

set architecture

(sin argumentos) o finalización de tabulación en el indicador de GDB.


Las respuestas ya proporcionadas aquí son correctas porque parecen comportarse mal con versiones recientes de gdb y / o qemu .

El es un problema abierto en el software fuente con los detalles actuales.

TL; DR

Cuando en modo real, qemu negociará la arquitectura incorrecta (i386), debe anularla:

  1. Descargue el archivo de descripción - target.xml ( target.xml )
  2. Inicie gdb y conéctese a su objetivo ( target remote ... )
  3. Establezca la arquitectura de destino utilizando el archivo de descripción: set tdesc filename target.xml

Configuración de arquitectura en gdb

Normalmente, cuando depura un ELF , PE o cualquier otro archivo de objeto, gdb puede inferir la arquitectura de los encabezados de los archivos. Cuando depura un cargador de arranque, no hay ningún archivo de objeto para leer, por lo que puede decirle a gdb la arquitectura usted mismo (en el caso de un arco de cargador de arranque será i8086 ):

set architecture <arch>

Nota: cuando se conecta a una máquina virtual qemu, en realidad no es necesario decirle a gdb la arquitectura deseada, qemu negociará esta información por usted a través del protocolo qXfer .

Anular la arquitectura de destino

Como se mencionó anteriormente al depurar qemu virtuales de qemu , qemu realmente negociará su arquitectura a gdb , cuando apunta a 32 bits x86, la arquitectura es probablemente i386 , que no es la arquitectura que queremos para el modo real.

Actualmente parece haber un problema en gdb que hace que elija la "arquitectura compatible más característica" entre la arquitectura del objetivo (i386) y la arquitectura proporcionada por el usuario (i8086). Debido a que gdb ve a i386 como un superconjunto adecuado de i8086 lo usa en su lugar. Elegir i386 hace que todos los operandos tengan 32 bits por defecto (en lugar de 16), esto es lo que causa los errores de desensamblador.

Puede anular la arquitectura de destino especificando un archivo de descripción target.xml :

set tdesc filename <file>

Hice target.xml de las fuentes qemu y cambié la arquitectura a i8086.