这是另一个答案的延续,因为超过了职位限制。
演示在v8086模式下生成的GP和UD的示例
以下代码不是进入v8086模式或编写正确的v8086监视器(#GP handler)的入门代码。进入v8086模式的信息可以在我的另一个Stackoverflow中找到
answers
. 这个答案讨论了进入v8086模式的机制。下面的代码基于这个答案,但是包括
TSS
,以及只处理#UD(异常6)和#GP(异常13)的中断描述符表。我选择UD是因为它是一个没有错误代码的异常,我选择GP是因为它是一个有错误代码的异常。
大部分代码都是支持以真实和受保护模式打印到显示器上的代码。这个例子背后的思想只是执行指令
UD2
在v8086模式下并发出特权
HLT
指示。我正在以0的IOPL进入v8086模式
HLT公司
导致由受保护模式GPF处理程序处理的GP异常。#GP有错误代码,而UD没有。要查明是否推送了错误代码,异常处理程序只需要从堆栈底部的地址中减去当前ESP。我使用一个32位的门,因此在有错误代码的情况下,异常堆栈帧应该是40字节(0x28),没有它的情况下应该是36字节(0x24)。
-
使用错误代码GS、FS、DS、ES、USER_SS、USER_ESP、EFLAGS、CS、EIP推送错误代码。每个是32位宽(4字节)。10*4=40。
-
没有错误代码GS、FS、DS、ES、USER_SS、USER_ESP、EFLAGS、CS、EIP。每个是32位宽(4字节)。9*4=36。
v8086模式下的代码执行以下测试:
; v8086 code entry point
v86_mode_entry:
ud2 ; Cause a #UD exception (no error code pushed)
mov dword [vidmem_ptr], 0xb8000+80*2
; Advance current video ptr to second line
hlt ; Cause a #GP exception (error code pushed)
; End of the test - enter infinite loop sice we didn't provide a way for
; the v8086 process to be terminated. We can't do a HLT at ring 3.
.endloop:
jmp $
有两个保护模式异常处理程序通过32位中断门到达。尽管很长一段时间,他们最终只做了一件事——打印出(十六进制)异常堆栈帧的大小,就像控件到达异常处理程序后显示的那样。因为异常处理程序使用
pusha
为了保存所有的通用寄存器,从总数中减去32字节(8*4)。
; #UD Invalid Opcode v8086 exception handler
exc_invopcode:
pusha ; Save all general purpose registers
mov eax, DATA32_SEL ; Setup the segment registers with kernel data selector
mov ds, eax
mov es, eax
cld ; DF=0 forward string movement
test dword [esp+efrm_noerr.user_flags], 1<<EFLAGS_VM_BIT
; Is the VM (v8086) set in the EFLAGS of the code
; that was interrupted?
jnz .isvm ; If set then proceed with processing the exception
mov esi, exc_not_vm ; Otherwise print msg we weren't interrupting v8086 code
mov ah, ATTR_BWHITE_ON_RED
call print_string_pm ; Print message to console
.endloop:
hlt
jmp .endloop ; Infinite HLT loop
.isvm:
mov esi, exc_msg_ud
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print that we are a #UD exception
; The difference between the bottom of the kernel stack and the ESP
; value (accounting for the extra 8 pushes by PUSHA) is the original
; exception stack frame size. Without an error code this should print 0x24.
mov eax, EXC_STACK-8*4
sub eax, esp ; EAX = size of exception stack frame without
; registers pushed by PUSHA
mov edi, tmp_hex_str ; EDI = address of buffer to store converted integer
mov esi, edi ; ESI = copy of address for call to print_string_pm
call dword_to_hex_pm ; Convert EAX to HEX string
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print size of frame in HEX
add word [esp+efrm_noerr.user_eip], 2
; A UD2 instruction is encoded as 2 bytes so update
; the real mode instruction pointer to point to
; next instruction so that the test can continue
; rather than repeatedly throwing #UD exceptions
popa ; Restore all general purpose registers
iret
; #GP v8086 General Protection Fault handler
exc_gpf:
pusha ; Save all general purpose registers
mov eax, DATA32_SEL ; Setup the segment registers with kernel data selector
mov ds, eax
mov es, eax
cld ; DF=0 forward string movement
test dword [esp+efrm_err.user_flags], 1<<EFLAGS_VM_BIT
; Is the VM (v8086) set in the EFLAGS of the code
; that was interrupted?
jnz .isvm ; If set then proceed with processing the exception
mov esi, exc_not_vm ; Otherwise print msg we weren't interrupting v8086 code
mov ah, ATTR_BWHITE_ON_RED
call print_string_pm ; Print message to console
.endloop:
hlt
jmp .endloop ; Infinite HLT loop
.isvm:
mov esi, exc_msg_gp
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print that we are a #UD exception
; The difference between the bottom of the kernel stack and the ESP
; value (accounting for the extra 8 pushes by PUSHA) is the original
; exception stack frame size. With an error code this should print 0x28.
mov eax, EXC_STACK-8*4
sub eax, esp ; EAX = size of exception stack frame without
; registers pushed by PUSHA
mov edi, tmp_hex_str ; EDI = address of buffer to store converted integer
mov esi, edi ; ESI = copy of address for call to print_string_pm
call dword_to_hex_pm ; Convert EAX to HEX string
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print size of frame in HEX
inc word [esp+efrm_err.user_eip]
; A HLT instruction is encoded as 1 bytes so update
; the real mode instruction pointer to point to
; next instruction so that the test can continue
; rather than repeatedly throwing #GP exceptions
popa ; Restore all general purpose registers
add esp, 4 ; Remove the error code
iret
当返回到v8086模式时,有一些硬编码的技巧来调整CS:IP,这样我们就不会在同一个异常上重复出现无限循环错误。一个
UD2
指令是2个字节,所以我们加上2个字节。在这种情况下
HLT
在返回之前,我们将1添加到v8086 CS:IP。这些异常处理程序仅在来自v8086模式时才执行有用的操作,否则,如果异常是从v8086模式以外的其他地方发生的,则它们会打印错误。不要将此代码视为创建自己的异常和中断处理程序的方法,它们是专门为此测试编写的,不是通用的。
以下代码可以在模拟器中运行,也可以使用此Stackoverflow中的bootloader测试工具在实际硬件上引导
answer
:
阶段2.asm
:
VIDEO_TEXT_ADDR EQU 0xb8000 ; Hard code beginning of text video memory
ATTR_BWHITE_ON_MAGENTA EQU 0x5f ; Bright White on magenta attribute
ATTR_BWHITE_ON_RED EQU 0x4f ; Bright White on red attribute
PM_MODE_STACK EQU 0x80000 ; Protected mode stack below EBDA
EXC_STACK EQU 0x70000 ; Kernel Stack for interrupt/exception handling
V86_STACK_SEG EQU 0x0000 ; v8086 stack SS
V86_STACK_OFS EQU 0x7c00 ; v8086 stack SP
V86_CS_SEG EQU 0x0000 ; v8086 code segment CS
EFLAGS_VM_BIT EQU 17 ; EFLAGS VM bit
EFLAGS_BIT1 EQU 1 ; EFLAGS bit 1 (reserved, always 1)
EFLAGS_IF_BIT EQU 6 ; EFLAGS IF bit
TSS_IO_BITMAP_SIZE EQU 0x400/8 ; IO Bitmap for 0x400 IO ports
; Size 0 disables IO port bitmap (no permission)
ORG_ADDR EQU 0x7e00 ; Origin point of stage2 (test code)
; Macro to build a GDT descriptor entry
%define MAKE_GDT_DESC(base, limit, access, flags) \
(((base & 0x00FFFFFF) << 16) | \
((base & 0xFF000000) << 32) | \
(limit & 0x0000FFFF) | \
((limit & 0x000F0000) << 32) | \
((access & 0xFF) << 40) | \
((flags & 0x0F) << 52))
; Macro to build a IDT descriptor entry
%define MAKE_IDT_DESC(offset, selector, access) \
((offset & 0x0000FFFF) | \
((offset & 0xFFFF0000) << 32) | \
((selector & 0x0000FFFF) << 16) | \
((access & 0xFF) << 40))
; Macro to convert an address to an absolute offset
%define ABS_ADDR(label) \
(ORG_ADDR + (label - $$))
; Structure representing exception frame WITH an error code
; including registers pushed by a PUSHA
struc efrm_err
; General purpose registers pushed by PUSHA
.edi: resd 1
.esi: resd 1
.ebp: resd 1
.esp: resd 1
.ebx: resd 1
.edx: resd 1
.ecx: resd 1
.eax: resd 1
; Items pushed by the CPU when an exception occurred
.errno: resd 1
.user_eip: resd 1
.user_cs: resd 1
.user_flags: resd 1
.user_esp: resd 1
.user_ss: resd 1
.vm_es: resd 1
.vm_ds: resd 1
.vm_fs: resd 1
.vm_gs: resd 1
EFRAME_ERROR_SIZE equ $-$$
endstruc
; Structure representing exception frame WITHOUT an error code
; including registers pushed by a PUSHA
struc efrm_noerr
; General purpose registers pushed by PUSHA
.edi: resd 1
.esi: resd 1
.ebp: resd 1
.esp: resd 1
.ebx: resd 1
.edx: resd 1
.ecx: resd 1
.eax: resd 1
; Items pushed by the CPU when an exception occurred
.user_eip: resd 1
.user_cs: resd 1
.user_flags: resd 1
.user_esp: resd 1
.user_ss: resd 1
.vm_es: resd 1
.vm_ds: resd 1
.vm_fs: resd 1
.vm_gs: resd 1
EFRAME_NOERROR_SIZE equ $-$$
endstruc
bits 16
ORG ORG_ADDR
start:
xor ax, ax ; DS=SS=ES=0
mov ds, ax
mov ss, ax ; Stack at 0x0000:0x7c00
mov sp, 0x7c00
cld ; Set string instructions to use forward movement
; No enabling A20 as we don't require it
lgdt [gdtr] ; Load our GDT
lidt [idtr] ; Install interrupt table
mov eax, cr0
or eax, 1
mov cr0, eax ; Set protected mode flag
jmp CODE32_SEL:start32 ; FAR JMP to set CS
; v8086 code entry point
v86_mode_entry:
ud2 ; Cause a #UD exception (no error code pushed)
mov dword [vidmem_ptr], 0xb8000+80*2
; Advance current video ptr to second line
hlt ; Cause a #GP exception (error code pushed)
; End of the test - enter infinite loop sice we didn't provide a way for
; the v8086 process to be terminated. We can't do a HLT at ring 3.
.endloop:
jmp $
; 32-bit protected mode entry point
bits 32
start32:
mov ax, DATA32_SEL ; Setup the segment registers with data selector
mov ds, ax
mov es, ax
mov ss, ax
mov esp, PM_MODE_STACK ; Set protected mode stack pointer
mov fs, ax ; Not currently using FS and GS
mov gs, ax
mov ecx, BSS_SIZE_D ; Zero out BSS section a DWORD at a time
mov edi, bss_start
xor eax, eax
rep stosd
; Set iomap_base in tss with the offset of the iomap relative to beginning of the tss
mov word [tss_entry.iomap_base], tss_entry.iomap-tss_entry
mov dword [tss_entry.esp0], EXC_STACK
mov dword [tss_entry.ss0], DATA32_SEL
mov eax, TSS32_SEL
ltr ax ; Load default TSS (used for exceptions, interrupts, etc)
xor ebx, ebx ; EBX=0
push ebx ; Real mode GS=0
push ebx ; Real mode FS=0
push ebx ; Real mode DS=0
push ebx ; Real mode ES=0
push V86_STACK_SEG
push V86_STACK_OFS ; v8086 stack SS:SP (grows down from SS:SP)
push dword 1<<EFLAGS_VM_BIT | 1<<EFLAGS_BIT1
; Set VM Bit, IF bit is off, DF=0(forward direction),
; IOPL=0, Reserved bit (bit 1) always 1. Everything
; else 0. These flags will be loaded in the v8086 mode
; during the IRET. We don't want interrupts enabled
; because we don't have a proper v86 monitor
; GPF handler to process them.
push V86_CS_SEG ; Real Mode CS (segment)
push v86_mode_entry ; Entry point (offset)
iret ; Transfer control to v8086 mode and our real mode code
; Function: print_string_pm
; Display a string to the console on display page 0 in protected mode.
; Very basic. Doesn't update hardware cursor, doesn't handle scrolling,
; LF, CR, TAB.
;
; Inputs: ESI = Offset of address to print
; AH = Attribute of string to print
; Clobbers: None
; Returns: None
print_string_pm:
push edi
push esi
push eax
mov edi, [vidmem_ptr] ; Start from video address stored at vidmem_ptr
jmp .getchar
.outchar:
stosw ; Output character to video display
.getchar:
lodsb ; Load next character from string
test al, al ; Is character NUL?
jne .outchar ; If not, go back and output character
mov [vidmem_ptr], edi ; Update global video pointer
pop eax
pop esi
pop edi
ret
; Function: dword_to_hex_pm
; Convert a 32-bit value to its equivalent HEXadecimal string
;
; Inputs: EDI = Offset of buffer for converted string (at least 8 bytes)
; EAX = 32-bit value to convert to HEX
; Clobbers: None
; Returns: None
dword_to_hex_pm:
push edx ; Save all registers we use
push ecx
push edi
mov ecx, 8 ; Process 8 nibbles (4 bits each)
.nibble_loop:
rol eax, 4 ; Rotate the high nibble to the low nibble of EAX
mov edx, eax ; Save copy of rotated value to continue conversion
and edx, 0x0f ; Mask off eveything but the lower nibble
movzx edx, byte [.hex_lookup_tbl+edx]
mov [edi], dl ; Convert nibble to HEX character using lookup table
inc edi ; Continue with the next nibble
dec ecx
jnz .nibble_loop ; Continue with next nibble if we haven't processed all
pop edi ; Retsore all the registers we clobbered
pop ecx
pop edx
ret
.hex_lookup_tbl: db "0123456789abcdef"
; #UD Invalid Opcode v8086 exception handler
exc_invopcode:
pusha ; Save all general purpose registers
mov eax, DATA32_SEL ; Setup the segment registers with kernel data selector
mov ds, eax
mov es, eax
cld ; DF=0 forward string movement
test dword [esp+efrm_noerr.user_flags], 1<<EFLAGS_VM_BIT
; Is the VM (v8086) set in the EFLAGS of the code
; that was interrupted?
jnz .isvm ; If set then proceed with processing the exception
mov esi, exc_not_vm ; Otherwise print msg we weren't interrupting v8086 code
mov ah, ATTR_BWHITE_ON_RED
call print_string_pm ; Print message to console
.endloop:
hlt
jmp .endloop ; Infinite HLT loop
.isvm:
mov esi, exc_msg_ud
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print that we are a #UD exception
; The difference between the bottom of the kernel stack and the ESP
; value (accounting for the extra 8 pushes by PUSHA) is the original
; exception stack frame size. Without an error code this should print 0x24.
mov eax, EXC_STACK-8*4
sub eax, esp ; EAX = size of exception stack frame without
; registers pushed by PUSHA
mov edi, tmp_hex_str ; EDI = address of buffer to store converted integer
mov esi, edi ; ESI = copy of address for call to print_string_pm
call dword_to_hex_pm ; Convert EAX to HEX string
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print size of frame in HEX
add word [esp+efrm_noerr.user_eip], 2
; A UD2 instruction is encoded as 2 bytes so update
; the real mode instruction pointer to point to
; next instruction so that the test can continue
; rather than repeatedly throwing #UD exceptions
popa ; Restore all general purpose registers
iret
; #GP v8086 General Protection Fault handler
exc_gpf:
pusha ; Save all general purpose registers
mov eax, DATA32_SEL ; Setup the segment registers with kernel data selector
mov ds, eax
mov es, eax
cld ; DF=0 forward string movement
test dword [esp+efrm_err.user_flags], 1<<EFLAGS_VM_BIT
; Is the VM (v8086) set in the EFLAGS of the code
; that was interrupted?
jnz .isvm ; If set then proceed with processing the exception
mov esi, exc_not_vm ; Otherwise print msg we weren't interrupting v8086 code
mov ah, ATTR_BWHITE_ON_RED
call print_string_pm ; Print message to console
.endloop:
hlt
jmp .endloop ; Infinite HLT loop
.isvm:
mov esi, exc_msg_gp
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print that we are a #UD exception
; The difference between the bottom of the kernel stack and the ESP
; value (accounting for the extra 8 pushes by PUSHA) is the original
; exception stack frame size. With an error code this should print 0x28.
mov eax, EXC_STACK-8*4
sub eax, esp ; EAX = size of exception stack frame without
; registers pushed by PUSHA
mov edi, tmp_hex_str ; EDI = address of buffer to store converted integer
mov esi, edi ; ESI = copy of address for call to print_string_pm
call dword_to_hex_pm ; Convert EAX to HEX string
mov ah, ATTR_BWHITE_ON_MAGENTA
call print_string_pm ; Print size of frame in HEX
inc word [esp+efrm_err.user_eip]
; A HLT instruction is encoded as 1 bytes so update
; the real mode instruction pointer to point to
; next instruction so that the test can continue
; rather than repeatedly throwing #GP exceptions
popa ; Restore all general purpose registers
add esp, 4 ; Remove the error code
iret
; Data section
align 4
vidmem_ptr: dd VIDEO_TEXT_ADDR ; Start console output in upper left of display
tmp_hex_str: TIMES 9 db 0 ; String to store 32-bit value converted HEX + NUL byte
exc_msg_ud:
db "#UD frame size: 0x", 0
exc_msg_gp:
db "#GP frame size: 0x", 0
exc_not_vm:
db "Not a v8086 exception", 0
align 4
gdt_start:
dq MAKE_GDT_DESC(0, 0, 0, 0) ; null descriptor
gdt32_code:
dq MAKE_GDT_DESC(0, 0x000fffff, 10011010b, 1100b)
; 32-bit code, 4kb gran, limit 0xffffffff bytes, base=0
gdt32_data:
dq MAKE_GDT_DESC(0, 0x000fffff, 10010010b, 1100b)
; 32-bit data, 4kb gran, limit 0xffffffff bytes, base=0
gdt32_tss:
dq MAKE_GDT_DESC(tss_entry, TSS_SIZE-1, 10001001b, 0000b)
; 32-bit TSS, 1b gran, available, IOPL=0
end_of_gdt:
CODE32_SEL equ gdt32_code - gdt_start
DATA32_SEL equ gdt32_data - gdt_start
TSS32_SEL equ gdt32_tss - gdt_start
gdtr:
dw end_of_gdt - gdt_start - 1
; limit (Size of GDT - 1)
dd gdt_start ; base of GDT
align 4
; Create an IDT which handles #UD and #GPF. All other exceptions set to 0
; so that they triple fault. No external interrupts supported.
idt_start:
TIMES 6 dq 0
dq MAKE_IDT_DESC(ABS_ADDR(exc_invopcode), CODE32_SEL, 10001110b) ; 6
TIMES 6 dq 0
dq MAKE_IDT_DESC(ABS_ADDR(exc_gpf), CODE32_SEL, 10001110b) ; D
TIMES 18 dq 0
end_of_idt:
align 4
idtr:
dw end_of_idt - idt_start - 1
; limit (Size of IDT - 1)
dd idt_start ; base of IDT
; Data section above bootloader acts like a BSS section
align 4
ABSOLUTE ABS_ADDR($) ; Convert location counter to absolute address
bss_start:
; Task State Structure (TSS)
tss_entry:
.back_link: resd 1
.esp0: resd 1 ; Kernel stack pointer used on ring transitions
.ss0: resd 1 ; Kernel stack segment used on ring transitions
.esp1: resd 1
.ss1: resd 1
.esp2: resd 1
.ss2: resd 1
.cr3: resd 1
.eip: resd 1
.eflags: resd 1
.eax: resd 1
.ecx: resd 1
.edx: resd 1
.ebx: resd 1
.esp: resd 1
.ebp: resd 1
.esi: resd 1
.edi: resd 1
.es: resd 1
.cs: resd 1
.ss: resd 1
.ds: resd 1
.fs: resd 1
.gs: resd 1
.ldt: resd 1
.trap: resw 1
.iomap_base:resw 1 ; IOPB offset
.iomap: resb TSS_IO_BITMAP_SIZE ; IO bitmap (IOPB) size 8192 (8*8192=65536) representing
; all ports. An IO bitmap size of 0 would fault all IO
; port access if IOPL < CPL (CPL=3 with v8086)
%if TSS_IO_BITMAP_SIZE > 0
.iomap_pad: resb 1 ; Padding byte that has to be filled with 0xff
; To deal with issues on some CPUs when using an IOPB
%endif
TSS_SIZE EQU $-tss_entry
bss_end:
BSS_SIZE_B EQU bss_end-bss_start; BSS size in bytes
BSS_SIZE_D EQU (BSS_SIZE_B+3)/4 ; BSS size in dwords
获得
bpb.inc
文件和
boot.asm
从
test harness
. 组装到磁盘映像:
nasm -f bin stage2.asm -o stage2.bin
nasm -f bin boot.asm -o disk.img
stage2.bin
必须先组装,因为它是以二进制形式嵌入的
启动.asm
. 结果应该是一个名为
disk.img
. 如果在QEMU中运行:
qemu-system-i386 -fda disk.img
结果应类似于:
-
UD帧大小应为0x00000024(36=无错误代码的异常帧大小)
-
GP帧大小应为0x00000028(40=带错误代码的异常帧大小)