如何从第一阶段加载第二阶段引导加载程序?

发布于 2024-08-18 01:18:56 字数 1557 浏览 3 评论 0原文

我编写了简单的第一阶段引导加载程序,它使用 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

I have written simple first stage bootloader which displays "Hello world" using interrupt to bios. Now as a next obvious step to write a second stage, but where code for that should exist and how to load it from first stage ?

Here is a program for first stage

[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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

儭儭莪哋寶赑 2024-08-25 01:18:56

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

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

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

希望有帮助 ^^

On x86 you would do the following (simplified):

  • Have the bootloader load the n-th sector of the disk/floppy (wherever you're booting from) into memory and execute it (i.e. load segment/offset and do retf). A better alternative is to search the filesystem for a certain filename (e.g. KERNEL.BIN) -- but you'd need to know the file system type (e.g. FAT12 if you're testing from a floppy image).
  • The kernel would then start in real mode. It sets up code descriptors, GDT, and so on, activates 32-bit addressing (you should have heard of "A20") and finally enters protected mode. Then you need a far jump to a 32-bit code segment (kernel file must be linked together in a way that the 32-bit code is at an absolute position, e.g. at offset 512, right after the 16-bit real mode stuff).
  • The 32-bit kernel assembly, then, just defines EXTERN _mykernel (for example) and calls that symbol.
  • Then you can begin writing your kernel as C function mykernel.

Okay that was a short overview of what I did a few years ago (with lots of copy&paste from the Internet ;). If that isn't helpful, here are some good web resources on OS development:

Hope that helps ^^

眼睛会笑 2024-08-25 01:18:56

查看此处的 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(阶段2_段),%es
第351章
第352章
第353章
354 * stage2 使用它们而不初始化它们。
第355章
第356章
第357章
第358章
第 359 章
第 360 章
第361章
第362章
第363章
第364章
365
366 代表
第367章
第368章
第369章
第370章
第371章
第372章
第373章
第374章
第375章
第376章

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

Look at the GRUB implementation here (stage 1):

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

First noticed the starting point at 0x7c00 and the end signature of 0xaa55 for this first sector. From within the disassembly, u can see this:

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

Essentially the logic is to copy the stage 2 code into another part of memory, and after that jump directly there, and that is "boot stage2". In other words, "boot stage1" is effectively triggered from BIOS after it has loaded the sector into memory, whereas stage2 is where you jump there - it can be anywhere.

2024-08-25 01:18:56

加载第 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 上

Minimal runnable NASM BIOS example that loads stage 2 and jumps to it

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

Compile and run:

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

Expected outcome: a gets printed to the screen, and then the program halts.

Tested on Ubuntu 14.04.

Saner GAS example using a linker script and more correct initialization (segment registers, stack) on my GitHub.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文