16 位实模式下的位置无关代码,bootloading/floppy 读取
Position independent code in 16-bit real mode, bootloading/floppy read
以下源文件被单独组装(成原始二进制文件)并分别加载到虚拟软盘的扇区 1 和扇区 2。然后这张软盘用作 qemu-system-i386 VM 的引导介质。
"bootloader"从软盘的第2扇区读取"first program",然后跳转到包含刚才读取的代码的内存。以下代码按预期工作(即打印 "first program" 欢迎消息),但我必须在 "first program" 的源代码中指定 ORG 0x001E
(通过检查引导加载程序的十六进制代码获得编辑)。 0x001E 是 temp
缓冲区的偏移量,它保存从软盘读取的代码。
"bootloader":
BITS 16
bootloader_main:
mov bx, 0x07C0 ; Set data segment to bootloader's default segment
mov ds, bx
mov ah, 0x02 ; BIOS int13h "read sector" function
mov al, 1 ; Number of sectors to read
mov cl, 2 ; Sector to read
mov ch, 0 ; Cylinder/track
mov dh, 0 ; Head
mov dl, 0 ; Disk number (here, the floppy disk)
mov bx, 0x07C0 ; Segment containing the destination buffer
mov es, bx
mov bx, temp ; Destination buffer offset
int 0x13
jmp temp
ret
;end bootloader_main
temp: times 60 db 17
times 510-($-$$) db 0 ; Pad rest of sector and add bootloader
dw 0xAA55 signature
"first program":
BITS 16
ORG 0x001E ; Assume that this code will be located 0x001E bytes
after start of bootloader (in RAM)
mov bx, string ; Print a welcome string
mov ah, 0x0E
print_loop:
mov al, byte [bx]
int 0x10
inc bx
cmp byte [bx], 0
jne print_loop
;end print_loop
string: db "This is the first program.", 0
或者,我可以使用 ORG 0x200
和 0x200
作为缓冲区而不是 temp
(即在引导加载程序之后将程序加载到 RAM 中),但是这些 hack 似乎都不是在创建有用的操作系统方面具有可持续性。如何避免这种地址硬编码?
您可以通过使用段来避免地址的硬编码。在 16 的倍数的地址处加载 "first program" 并加载具有相应段(地址 / 16)的 DS,然后远跳转到 segment:0
,其中 segment
是您加载程序的位置。在加载的程序中使用ORG 0
。
例如:
BITS 16
bootloader_main:
mov ax, 0x07C0 ; Set data segment to bootloader's default segment
mov ds, ax
mov ah, 0x02 ; BIOS int13h "read sector" function
mov al, 1 ; Number of sectors to read
mov cl, 2 ; Sector to read
mov ch, 0 ; Cylinder/track
mov dh, 0 ; Head
mov bx, program_seg ; load program at program_seg:0
mov es, bx
xor bx, bx
int 0x13
mov ax, program_seg
mov ds, ax
mov ss, ax ; set stack to end of program_seg
mov sp, 0
jmp program_seg:0
bootloader_end:
program_seg equ (bootloader_end - bootloader_main + 0x7c00 + 15) / 16
times 510-($-$$) db 0 ; Pad rest of sector and add bootloader
dw 0xAA55 ; signature
BITS 16
ORG 0
mov bx, string ; Print a welcome string
mov ah, 0x0E
print_loop:
mov al, byte [bx]
int 0x10
inc bx
cmp byte [bx], 0
jne print_loop
;end print_loop
string: db "This is the first program.", 0
我删除了 mov dl, 0
指令,因为您不应该对该值进行硬编码。 BIOS会在DL中传递启动设备的驱动器号,无需更改。
此示例允许您在之间输入任意数量的代码
jmp Load_Buffer & Load_Buffer,只要它不超过运行 引导扇区的最后 4 个字节。
引导程序
BOOTSEG equ 0x7c0
LOW_MEM equ 18
DISKIO equ 19
确定有多少 4k 页面可用并将其转换为一个段
地址。在我的例子中是 0x8FC0。
xor cx, cx
mov cl, 64
int LOW_MEM ; Get # of 4k segments to top of memory
sub ax, cx
shl ax, 6 ;
修改堆栈指针时始终禁用中断。这将堆栈放在内存顶部的一个非常安全的地方,少了 64k
cli
mov ss, ax
xor sp, sp
sti
此时 CS 可能为 0 或 0x7c0,具体取决于 bios 供应商,但这并不重要,因为此时只有 ES:BX 很重要
mov ax, Load_Buffer
shr ax, 4
add ax, BOOTSEG
mov es, ax
mov ax, 0x201 ; Read one sector
mov cx, 2 ; Starting @ cylinder 0, sector 2
xor dh, dh ; Head 0 and BIOS has already passed device.
这里的另一种选择是用 0x7c0 加载 ES,用 Load_Buffer 加载 BX,但在我们的例子中,ES = BOOTSEG + Load_Buffer / 16.
xor bx, bx
int DISKIO ; Read sector 2
jmp Load_Buffer
页面边界对齐将保证无论在 Load_Buffer 之上添加了多少,加载程序都将始终工作,除非它恰好超过 运行 引导扇区的最后 4 个字节。对齐只需要在页面边界上,所以 16 也可以。
align 32
Load_Buffer:
用 NOP 的 incase 代码填充扇区可能是个好主意 运行s 之后,不会出现段错误。
times 508 - ($-$$) db 144 ; NOP's 0x90
int 25 ; Incase codes runs away we'll reboot
dw 0xAA55 ; Boot sig (no required by some emulators)
第一个节目
打算使用 LODSB,并不是说比你的方法更正确,只是使用的代码更少。
mov si, string
push es
pop ds ; DS:SI now points to string
mov ah, 14 ; TTY output
Loop:
lodsb ; Read from DS:SI
test al, 255 ; Test if these any of these bits are on.
jz .Done
int 16
jmp Loop
标签之前的句号只是NASM声明本地标签的功能
.Done:
hlt
jmp $
string db 'This is the first program', 0
以下源文件被单独组装(成原始二进制文件)并分别加载到虚拟软盘的扇区 1 和扇区 2。然后这张软盘用作 qemu-system-i386 VM 的引导介质。
"bootloader"从软盘的第2扇区读取"first program",然后跳转到包含刚才读取的代码的内存。以下代码按预期工作(即打印 "first program" 欢迎消息),但我必须在 "first program" 的源代码中指定 ORG 0x001E
(通过检查引导加载程序的十六进制代码获得编辑)。 0x001E 是 temp
缓冲区的偏移量,它保存从软盘读取的代码。
"bootloader":
BITS 16
bootloader_main:
mov bx, 0x07C0 ; Set data segment to bootloader's default segment
mov ds, bx
mov ah, 0x02 ; BIOS int13h "read sector" function
mov al, 1 ; Number of sectors to read
mov cl, 2 ; Sector to read
mov ch, 0 ; Cylinder/track
mov dh, 0 ; Head
mov dl, 0 ; Disk number (here, the floppy disk)
mov bx, 0x07C0 ; Segment containing the destination buffer
mov es, bx
mov bx, temp ; Destination buffer offset
int 0x13
jmp temp
ret
;end bootloader_main
temp: times 60 db 17
times 510-($-$$) db 0 ; Pad rest of sector and add bootloader
dw 0xAA55 signature
"first program":
BITS 16
ORG 0x001E ; Assume that this code will be located 0x001E bytes
after start of bootloader (in RAM)
mov bx, string ; Print a welcome string
mov ah, 0x0E
print_loop:
mov al, byte [bx]
int 0x10
inc bx
cmp byte [bx], 0
jne print_loop
;end print_loop
string: db "This is the first program.", 0
或者,我可以使用 ORG 0x200
和 0x200
作为缓冲区而不是 temp
(即在引导加载程序之后将程序加载到 RAM 中),但是这些 hack 似乎都不是在创建有用的操作系统方面具有可持续性。如何避免这种地址硬编码?
您可以通过使用段来避免地址的硬编码。在 16 的倍数的地址处加载 "first program" 并加载具有相应段(地址 / 16)的 DS,然后远跳转到 segment:0
,其中 segment
是您加载程序的位置。在加载的程序中使用ORG 0
。
例如:
BITS 16
bootloader_main:
mov ax, 0x07C0 ; Set data segment to bootloader's default segment
mov ds, ax
mov ah, 0x02 ; BIOS int13h "read sector" function
mov al, 1 ; Number of sectors to read
mov cl, 2 ; Sector to read
mov ch, 0 ; Cylinder/track
mov dh, 0 ; Head
mov bx, program_seg ; load program at program_seg:0
mov es, bx
xor bx, bx
int 0x13
mov ax, program_seg
mov ds, ax
mov ss, ax ; set stack to end of program_seg
mov sp, 0
jmp program_seg:0
bootloader_end:
program_seg equ (bootloader_end - bootloader_main + 0x7c00 + 15) / 16
times 510-($-$$) db 0 ; Pad rest of sector and add bootloader
dw 0xAA55 ; signature
BITS 16
ORG 0
mov bx, string ; Print a welcome string
mov ah, 0x0E
print_loop:
mov al, byte [bx]
int 0x10
inc bx
cmp byte [bx], 0
jne print_loop
;end print_loop
string: db "This is the first program.", 0
我删除了 mov dl, 0
指令,因为您不应该对该值进行硬编码。 BIOS会在DL中传递启动设备的驱动器号,无需更改。
此示例允许您在之间输入任意数量的代码 jmp Load_Buffer & Load_Buffer,只要它不超过运行 引导扇区的最后 4 个字节。
引导程序
BOOTSEG equ 0x7c0
LOW_MEM equ 18
DISKIO equ 19
确定有多少 4k 页面可用并将其转换为一个段 地址。在我的例子中是 0x8FC0。
xor cx, cx
mov cl, 64
int LOW_MEM ; Get # of 4k segments to top of memory
sub ax, cx
shl ax, 6 ;
修改堆栈指针时始终禁用中断。这将堆栈放在内存顶部的一个非常安全的地方,少了 64k
cli
mov ss, ax
xor sp, sp
sti
此时 CS 可能为 0 或 0x7c0,具体取决于 bios 供应商,但这并不重要,因为此时只有 ES:BX 很重要
mov ax, Load_Buffer
shr ax, 4
add ax, BOOTSEG
mov es, ax
mov ax, 0x201 ; Read one sector
mov cx, 2 ; Starting @ cylinder 0, sector 2
xor dh, dh ; Head 0 and BIOS has already passed device.
这里的另一种选择是用 0x7c0 加载 ES,用 Load_Buffer 加载 BX,但在我们的例子中,ES = BOOTSEG + Load_Buffer / 16.
xor bx, bx
int DISKIO ; Read sector 2
jmp Load_Buffer
页面边界对齐将保证无论在 Load_Buffer 之上添加了多少,加载程序都将始终工作,除非它恰好超过 运行 引导扇区的最后 4 个字节。对齐只需要在页面边界上,所以 16 也可以。
align 32
Load_Buffer:
用 NOP 的 incase 代码填充扇区可能是个好主意 运行s 之后,不会出现段错误。
times 508 - ($-$$) db 144 ; NOP's 0x90
int 25 ; Incase codes runs away we'll reboot
dw 0xAA55 ; Boot sig (no required by some emulators)
第一个节目
打算使用 LODSB,并不是说比你的方法更正确,只是使用的代码更少。
mov si, string
push es
pop ds ; DS:SI now points to string
mov ah, 14 ; TTY output
Loop:
lodsb ; Read from DS:SI
test al, 255 ; Test if these any of these bits are on.
jz .Done
int 16
jmp Loop
标签之前的句号只是NASM声明本地标签的功能
.Done:
hlt
jmp $
string db 'This is the first program', 0