10

我编写了简单的第一阶段引导加载程序,它使用 BIOS 中断显示“Hello world”。现在作为编写第二阶段的下一个明显步骤,但是应该存在哪里的代码以及如何从第一阶段加载它?

这是第一阶段的程序

[BITS 16]   ;Tells the assembler that its a 16 bit code
[ORG 0x7C00]    ;Origin, tell the assembler that where the code will
                ;be in memory after it is been loaded

MOV SI, HelloString ;Store string pointer to SI
CALL PrintString    ;Call print string procedure
JMP $       ;Infinite loop, hang it here.


PrintCharacter: ;Procedure to print character on screen
    ;Assume that ASCII value is in register AL
MOV AH, 0x0E    ;Tell BIOS that we need to print one charater on screen.
MOV BH, 0x00    ;Page no.
MOV BL, 0x07    ;Text attribute 0x07 is lightgrey font on black background

INT 0x10    ;Call video interrupt
RET     ;Return to calling procedure



PrintString:    ;Procedure to print string on screen
    ;Assume that string starting pointer is in register SI

next_character: ;Lable to fetch next character from string
MOV AL, [SI]    ;Get a byte from string and store in AL register
INC SI      ;Increment SI pointer
OR AL, AL   ;Check if value in AL is zero (end of string)
JZ exit_function ;If end then return
CALL PrintCharacter ;Else print the character which is in AL register
JMP next_character  ;Fetch next character from string
exit_function:  ;End label
RET     ;Return from procedure


;Data
HelloString db 'Hello World', 0 ;HelloWorld string ending with 0

TIMES 510 - ($ - $$) db 0   ;Fill the rest of sector with 0
DW 0xAA55           ;Add boot signature at the end of bootloader
4

3 回答 3

8

在 x86 上,您将执行以下操作(简化):

  • 让引导加载程序将磁盘/软盘的第 n​​ 个扇区(无论您从哪里启动)加载到内存中并执行它(即加载段/偏移量和 do retf)。更好的选择是在文件系统中搜索某个文件名(例如 KERNEL.BIN)——但您需要知道文件系统类型(例如,如果您从软盘映像进行测试,则为 FAT12)。
  • 然后内核将以实模式启动。它设置代码描述符、GDT 等,激活 32 位寻址(你应该听说过“A20”),最后进入保护模式。然后你需要一个远跳转到一个 32 位代码段(内核文件必须以 32 位代码处于绝对位置的方式链接在一起,例如在偏移 512 处,就在 16 位实模式内容之后) .
  • 然后,32 位内核程序集只定义EXTERN _mykernel(例如)并调用该符号。
  • 然后您可以开始将内核编写为 C 函数mykernel

好的,这是我几年前所做工作的简短概述(有很多来自 Internet 的复制和粘贴;)。如果这没有帮助,这里有一些关于操作系统开发的优秀网络资源:

希望有帮助^^

于 2010-01-15T22:55:30.467 回答
2

加载第 2 阶段并跳转到它的最小可运行 NASM BIOS 示例

use16
org 0x7C00

    ; You should do further initializations here
    ; like setup the stack and segment registers.

    ; Load stage 2 to memory.
    mov ah, 0x02
    ; Number of sectors to read.
    mov al, 1
    ; This may not be necessary as many BIOS set it up as an initial state.
    mov dl, 0x80
    ; Cylinder number.
    mov ch, 0
    ; Head number.
    mov dh, 0
    ; Starting sector number. 2 because 1 was already loaded.
    mov cl, 2
    ; Where to load to.
    mov bx, stage2
    int 0x13

    jmp stage2

    ; Magic bytes.    
    times ((0x200 - 2) - ($ - $$)) db 0x00
    dw 0xAA55

stage2:

    ; Print 'a'.
    mov ax, 0x0E61
    int 0x10

    cli
    hlt

    ; Pad image to multiple of 512 bytes.
    times ((0x400) - ($ - $$)) db 0x00

编译并运行:

nasm -f bin -o main.img main.asm
qemu-system-i386 main.img

预期结果:a打印到屏幕上,然后程序停止。

在 Ubuntu 14.04 上测试。

Saner GAS 示例在我的 GitHub 上使用链接器脚本和更正确的初始化(段寄存器、堆栈)。

于 2015-10-04T21:05:35.173 回答
1

看看这里的 GRUB 实现(第 1 阶段):

http://src.illumos.org/source/xref/illumos-gate/usr/src/grub/grub-0.97/stage1/stage1.S

首先注意到第一个扇区的起始点 0x7c00 和 0xaa55 的结束签名。从反汇编中,您可以看到:

349 copy_buffer:
350   movw    ABS(stage2_segment), %es
351 
352   /*
353    * We need to save %cx and %si because the startup code in
354    * stage2 uses them without initializing them.
355    */
356   pusha
357   pushw   %ds
358 
359   movw    $0x100, %cx
360   movw    %bx, %ds
361   xorw    %si, %si
362   xorw    %di, %di
363 
364   cld
365 
366   rep
367   movsw
368 
369   popw    %ds
370   popa
371 
372   /* boot stage2 */
373   jmp *(stage2_address)
374 
375 /* END OF MAIN LOOP */
376

本质上,逻辑是将阶段 2 的代码复制到内存的另一部分,然后直接跳转到那里,这就是“boot stage2”。换句话说,“boot stage1”在将扇区加载到内存后从 BIOS 有效地触发,而 stage2 是您跳转到那里的地方 - 它可以在任何地方。

于 2012-02-18T12:59:17.407 回答