问题描述
我最近编写了一个x86'bootloader'程序,该程序显示BIOS跳转到我的程序后硬件寄存器的值。为了进行测试,我将AX
寄存器设置为已知值,以确保程序正确运行。
BITS 16
%macro pad 1-2 0
times %1 - ($ - $$) db %2
%endmacro
[org 0x7C00]
CLD ; clear direction flag (forward direction)
CLI ; clear interrupt flag (disable interrupts,opposite of 65xx)
MOV [0x8000],AX ; display all registers,MOV [0x8004],BX ; including stack,MOV [0x8008],CX ; segment,& extra
MOV [0x800C],DX ; registers
MOV [0x8010],SP
MOV [0x8014],BP
MOV [0x8018],SI
MOV [0x801C],DI
MOV [0x8020],CS
MOV [0x8024],SS ; we also display DS register,MOV [0x8028],ES ; so we can't modify it or
MOV [0x802C],DS ; we'll loose our data
MOV [0x8030],FS
MOV [0x8034],GS
MOV AX,0x0123 ; write 0x0123 to [0x8000]
MOV [0x8000],AX ; for debugging
MOV DI,0x804C ; DI is pointer to address 0x804C
; (temporary data)
MOV AH,0x02
MOV BH,0x00 ; video page 0?
MOV DX,0x0401
INT 0x10 ; move cursor to XY:($01,$04)
; display register data
MOV AL,'A'
CALL printXl ; print 'AX:'
MOV DX,[0x8000] ; recall value of AX register
; (set to 0x0123 for test)
CALL printascii ; print 16-bit value @ [0x8000]
;... ; omitted code: display other registers
MOV AH,0x00 ; wait for keyboard press
INT 0x16
INT 0x18 ; boot Windows
printXl:
MOV AH,0x0E
XOR BX,BX
INT 0x10 ; display character in 'AL'
MOV AL,'X'
; falls through
prnt: ; referenced in omitted code
MOV AH,0x0E
INT 0x10 ; display character 'X'/'S'
MOV AL,':'
INT 0x10 ; display character ':'
RET
printascii:
MOV [DI],DX ; store value for later recall
MOV AH,0x0E ; INT 10,E
MOV SI,hexascii ; load address of 'hexascii'
AND DX,0xF000
SHR DX,0x0C ; shift high nibble to lowest 4 bits
ADD SI,DX
CS LODSB ; AL = CS:[0x1EE + DX >> 12];
INT 0x10 ; display high nibble of character value
MOV SI,hexascii
MOV DX,[DI]
AND DX,0x0F00
SHR DX,0x08
ADD SI,DX
CS LODSB
INT 0x10 ; display low nibble of character value
MOV SI,0x00F0
SHR DX,0x04
ADD SI,DX
CS LODSB
INT 0x10 ; display high nibble of character value
MOV SI,hexascii ;
MOV DX,0x000F
ADD SI,DX
CS LODSB
INT 0x10 ; display low nibble of character value
RET
pad 0x01EE
hexascii:
db "0123456789ABCDEF" ;
pad 0x01FE ; pad to end of bootsector
dw 0xAA55 ; bootsector signature
从DOSBox运行时,我可以正确看到AX:0123
,但是从软盘启动时,我会得到AX:FFFF
。我不知道我在做什么错。作为参考,我的PC是Intel Core 2 Quad
。
解决方法
在保证100%安全的同时做您想做的事是不可能的。
问题是要存储数据到任何地方,您都必须知道要将其存储在安全的地方(不要覆盖堆栈,不被堆栈覆盖,不写入ROM或不是RAM且不是RAM的其他东西)破坏RAM中的任何其他内容,例如BIOS数据或您的代码);并且您必须先修改寄存器(通常是段寄存器),然后才能知道将数据存储在安全的地方,因此您无法在任何地方安全地存储这些寄存器的原始值。请注意,这是造成(至少其中一个)原始问题的原因-不想更改DS(因为要打印其原始值),最终却不知道使用DS是否安全。
“最不安全”的选择是(暂时)使用BIOS留下的堆栈。 BIOS可能会将堆栈留在一个具有足够空间的地方,以确保在BIOS跳转到您的代码之后但在您可以执行一条指令(或自行设置安全的堆栈)之前发生IRQ不会导致问题,因此很可能可以在该堆栈上存储少量数据。然而;不能保证任何中断(包括IRQ和BIOS函数)不会消耗比使用完一些中断后剩余的堆栈更多的内存(因此,您不想在堆栈上存储大量数据);最好在启用IRQ或调用任何BIOS函数之前先将存储在堆栈中的数据转移到其他地方。
这会导致类似以下内容(NASM语法,未经测试):
org 0x7C00
start:
cli
push ds
push ax
xor ax,ax
mov ds,ax
call far [.fixCSIP] ;Push CS and IP then set CS and IP to known values
.fixCSIP:
dw 0x0000,.here ;Values to load into CS and IP
.here:
pop word [0x8020] ;Move original value of CS
pop ax ;ax = original value of "IP + (.fixCSIP - start)"
sub ax,.fixCSIP-start ;ax = original value of IP
mov [0x8038],ax ;Store original value of IP
pop word [0x8000] ;Move original value of AX
pop word [0x802C] ;Move original value of DS
;SP is now back to the value it originally had
mov [0x8010],sp
mov [0x8024],ss
xor ax,ax
mov ss,ax
mov sp,0x7C00
;Now CS:IP,DS and SS:SP are all "known safe" values,so we can start being normal
sti
mov [0x8004],bx
mov [0x8008],cx
mov [0x800C],dx
...
,
引用布伦丹的答案:
问题是要存储数据到任何地方,您都必须知道要将其存储在安全的地方(不要覆盖堆栈,不被堆栈覆盖,不写入ROM或不是RAM且不是RAM的其他东西)破坏RAM中的任何其他内容,例如BIOS数据或您的代码);并且您必须先修改寄存器(通常是段寄存器),然后才能知道您将数据存储在安全的地方,这样就不能将这些寄存器的原始值安全地存储在任何地方。
此问题的解决方案是使用由ROM-BIOS设置的初始堆栈,该初始堆栈应至少几十个字节是安全的,然后至关重要的是将前几个寄存器的值存储到已占用的空间中由我们自己的引导扇区加载程序。此空间为我们保留,并且不能由ROM-BIOS设置的初始堆栈覆盖。将堆栈切换到已知良好的区域后,我们也可以使用其他内存,尽管在本示例中我们不需要这样做。这是NASM来源(test.asm):
%if 0
Boot sector loader which displays register values
by C. Masloch,2020
Usage of the works is permitted provided that this
instrument is retained with the works,so that any entity
that uses the works is notified of this instrument.
DISCLAIMER: THE WORKS ARE WITHOUT WARRANTY.
%endif
struc BS
bsJump: resb 3
bsOEM: resb 8
bsBPB:
endstruc
struc EBPB ; BPB sec
bpbBytesPerSector: resw 1 ; offset 00h 0Bh
bpbSectorsPerCluster: resb 1 ; offset 02h 0Dh
bpbReservedSectors: resw 1 ; offset 03h 0Eh
bpbNumFATs: resb 1 ; offset 05h 10h
bpbNumRootDirEnts: resw 1 ; offset 06h 11h -- 0 for FAT32
bpbTotalSectors: resw 1 ; offset 08h 13h
bpbMediaID: resb 1 ; offset 0Ah 15h
bpbSectorsPerFAT: resw 1 ; offset 0Bh 16h -- 0 for FAT32
bpbCHSSectors: resw 1 ; offset 0Dh 18h
bpbCHSHeads: resw 1 ; offset 0Fh 1Ah
bpbHiddenSectors: resd 1 ; offset 11h 1Ch
bpbTotalSectorsLarge: resd 1 ; offset 15h 20h
bpbNew: ; offset 19h 24h
ebpbSectorsPerFATLarge: resd 1 ; offset 19h 24h
ebpbFSFlags: resw 1 ; offset 1Dh 28h
ebpbFSVersion: resw 1 ; offset 1Fh 2Ah
ebpbRootCluster: resd 1 ; offset 21h 2Ch
ebpbFSINFOSector: resw 1 ; offset 25h 30h
ebpbBackupSector: resw 1 ; offset 27h 32h
ebpbReserved: resb 12 ; offset 29h 34h
ebpbNew: ; offset 35h 40h
endstruc
struc BPBN ; ofs B16 S16 B32 S32
bpbnBootUnit: resb 1 ; 00h 19h 24h 35h 40h
resb 1 ; 01h 1Ah 25h 36h 41h
bpbnExtBPBSignature: resb 1 ; 02h 1Bh 26h 37h 42h -- 29h for valid BPBN
bpbnSerialNumber: resd 1 ; 03h 1Ch 27h 38h 43h
bpbnVolumeLabel: resb 11 ; 07h 20h 2Bh 3Ch 47h
bpbnFilesystemID: resb 8 ; 12h 2Bh 36h 47h 52h
endstruc ; 1Ah 33h 3Eh 4Fh 5Ah
cpu 8086
org 7C00h
start:
jmp short entrypoint
nop
times (bsBPB + EBPB_size + BPBN_size) - ($ - $$) db 0
entrypoint:
pushf
cli ; An interrupt could use too much more stack space
cld
push bx
push ds
call 0:.next ; Set CS:IP to match ORG
.next:
pop bx ; BX = IP of return address pushed by call
sub bx,.next - start ; calculate original IP on entry to start
push bx
push cs
pop ds ; DS=0 to match ORG
mov bx,start
pop word [bx + reg_ip] ; store into start + BPB space
pop word [bx + reg_cs]
pop word [bx + reg_ds]
pop word [bx + reg_bx]
pop word [bx + reg_fl]
mov word [bx + reg_sp],sp
mov word [bx + reg_ss],ss
mov word [bx + reg_ax],ax
xor ax,ax
mov ss,ax
mov sp,bx ; set sp immediately after ss
sti
mov word [bx + reg_cx],cx
mov word [bx + reg_dx],dx
mov word [bx + reg_es],es
mov word [bx + reg_si],si
mov word [bx + reg_di],di
mov word [bx + reg_bp],bp
mov si,table
; bx -> start
loop_table:
mov al,32
call disp_al
lodsw
call disp_al
xchg al,ah
call disp_al
cmp al,32
jbe .next
mov al,'='
call disp_al
mov ax,[bx]
inc bx
inc bx
call disp_ax_hex
.next:
cmp si,table.end
jb loop_table
exit:
xor ax,ax
int 16h
int 19h
disp_al:
push ax
push bx
push bp
mov ah,0Eh
mov bx,7
int 10h
pop bp
pop bx
pop ax
retn
disp_ax_hex: ; ax
xchg al,ah
call disp_al_hex ; display former ah
xchg al,ah ; and fall through for al
disp_al_hex: ; al
push cx
mov cl,4 ; ror al,4 would require 186
ror al,cl
call disp_al_lownibble_hex ; display former high-nibble
rol al,cl
pop cx
; and fall through for low-nibble
disp_al_lownibble_hex:
push ax ; save ax for call return
and al,00001111b ; high nibble must be zero
add al,'0' ; if number is 0-9,now it's the correct character
cmp al,'9'
jna .decimalnum ; if we get decimal number with this,ok -->
add al,7 ; otherwise,add 7 and we are inside our alphabet
.decimalnum:
call disp_al
pop ax
retn
struc registerstorage
reg_ss: resw 1
reg_bp: resw 1
reg_sp: resw 1
reg_cs: resw 1
reg_ip: resw 1
reg_fl: resw 1
reg_ds: resw 1
reg_si: resw 1
reg_es: resw 1
reg_di: resw 1
reg_ax: resw 1
reg_bx: resw 1
reg_cx: resw 1
reg_dx: resw 1
endstruc
%if registerstorage_size + start > entrypoint
%error Entrypoint is not safe
%endif
align 2
table:
dw "SS"
dw "BP"
dw "SP"
dw "CS"
dw "IP"
dw "FL"
db 13,10
dw "DS"
dw "SI"
dw "ES"
dw "DI"
db 13,10
dw "AX"
dw "BX"
dw "CX"
dw "DX"
db 13,10
.end:
times 510 - ($ - $$) db 0
dw 0AA55h
与nasm test.asm -f bin -o test.bin
组装,然后作为引导扇区加载。示例:
-boot protocol chain test.bin
-r
AX=0000 BX=0000 CX=F000 DX=0000 SP=7BF0 BP=07BE SI=07BE DI=0000
DS=0000 ES=0060 SS=0000 CS=0000 IP=7C00 NV UP DI PL ZR NA PE NC
0000:7C00 EB58 jmp 7C5A
-g
SS=0000 BP=07BE SP=7BF0 CS=0000 IP=7C00 FL=0046
DS=0000 SI=07BE ES=0060 DI=0000
AX=0000 BX=0000 CX=F000 DX=0000
Boot load called
-
(-g
和Boot load called
之间的部分是引导扇区加载程序的输出。)