引导加载程序不会跳转到内核code [英] Boot loader doesn't jump to kernel code

查看:131
本文介绍了引导加载程序不会跳转到内核code的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在写个小手术系统 - 实践。我开始与引导加载器。结果我想创建,在16位实模式下运行(现在)的小指挥系统。结果
我创建启动加载器,复位驱动器,然后引导程序加载后行业。结果
现在的问题是因为在 JMP 函数没有实际发生的情况。

I't尝试下一个扇区的0x7E00加载(我不完全知道如何使用点地址ES:BX所以这可能是一个问题,我相信它的地址:偏移),只是引导程序后

这是code:

 ;
;部门为0x0
;; DL是硬盘的数,其中为引导程序
组织0x7C00
16位;重置硬盘驱动器
XOR啊,啊
INT 0x13
;读取的扇区
CLC
MOV BX,0x7E00
MOV ES,BX
异或BX,BX
MOV啊,0X02;功能
MOV等,为0x1;扇区读
MOV CH,为0x0;轨道
MOV CL,为0x1;部门
MOV DH,为0x0;头
INT 0x13
;如果没有readed JMP错误
JC错误
;跳转到0x7E00 - 只有当加载执行
JMP 0x7E00
错误:
    MOV SI,MSGError
    。循环:
        LODSB
        还是人,人
        JZ .END
        MOV啊,为0x0E
        INT为0x10
        JMP .loop
    。结束:
        HLT
MSGError DB错误,同时启动,为0x0
次0x1FE - ($ - $$)分贝为0x0
将0x55分贝
DB和0xAA;
;部门为0x1
;JMP printtest
;定义
MSGLoaded DB执行成功,为0x0
;
;打印功能
; SI - 消息普林(需要与为0x0完)printtest:
    MOV SI,MSGLoaded
    。循环:
        LODSB
        还是人,人
        JZ .END
        MOV啊,为0x0E
        INT为0x10
        JMP .loop
    。结束:
        HLT次0x400的 - ($ - $$)分贝为0x0

我一直在测试使用VirtualBox此code,但没有实际发生时,读取错误不表演,以及应打印信息。


解决方案

本code的主要问题是:


  1. ES:BX 的被指向错误的段:偏移内核加载到

  2. 错误扇区被加载内核所以没有什么预期

第一次是在这个code:

  MOV BX,0x7E00
MOV ES,BX
异或BX,BX

问题想从磁盘扇区加载到 0×0000:0x7E00 ES:BX 的)。这code设置的 ES:BX 0x7E00:0×0000 解析为一个物理地址 0x7E000 ((0x7E00<< 4)+ 0×0000)。我觉得意图是 0x07E0 装入的 ES 的这将产生 0x7E00 ((0x07E0&下; 4;)+ 0×0000)。您可以了解更多有关16:16内存这里寻址计算。由16乘以该段是相同移它留下4比特。

在code中的第二个问题是在这里:

  MOV啊,0X02;功能
MOV等,为0x1;扇区读
MOV CH,为0x0;轨道
MOV CL,0X2;扇区号
MOV DH,为0x0;头
INT 0x13

在磁盘上的第二块512的扇区数为2,而不是1所以要解决上述code你需要设置的 CL 的相应

  MOV CL,0X2;扇区号

一般提示Bootloader的发展

这可以绊倒在各种模拟器,虚拟机和真实的物理硬件应该解决运行code。其他问题是:


  1. 当BIOS跳转到您的code,你不能依靠的 CS DS ES SS SP 的有有效的或预期值寄存器。他们应该引导程序启动的时候进行适当的设置。您只能保证你的引导程序被加载并从物理地址0x00007c00运行和引导驱动器号被装入的 DL 的注册。

  2. LODSB 使用的方向标志, MOVSB​​ 等可以设置或清除。如果方向标志设置不当的 SI / DI 的寄存器可以在错误的方向调整。使用 STD / CLD 将其设置为你希望的方向(CLD =正向/ STD =向后)。在这种情况下,code假定向前运动因此应该使用 CLD 。更多关于这可以在指令集参考中

  3. 当跳转到内核它通常是一个好主意的 FAR JMP 的它,因此它正确地设置的 CS:IP 的预期值。这可避免与内核code问题,可能会做的绝对附近的JMP 通话的同段内。

  4. 如果您的目标引导装载程序的16位code,关于8086/8088处理器(及以上)的作品避免在组装code 32位寄存器使用。使用的 AX / BX / CX / DX / SI / DI 的/ SP 的/代替的 BP EAX/EBX/ECX/EDX/ESI/EDI/ESP/EBP.虽然这个问题不是一个问题,它一直为他人寻求帮助的问题。 32位处理器可以使用16位实模式32位寄存器,而是一个8086/8088/80286不能因为他们是没有访问扩展32位寄存器的16位处理器。

  5. FS 的和的 GS 的段寄存器被添加到80386+的CPU。避免他们,如果你打算到目标8086/8088/80286。

要解决的第一个和第二个项目这个code能引导装载程序的附近开始使用:

 异斧,斧;我们要的0段为DS此问题
MOV DS,AX;将AX设为您的具体情况适当段值
MOV ES,AX;在这种情况下,我们将默认为ES = DS
MOV BX,为0x8000;堆栈段可以是任何可用内存CLI;禁止中断规避早期的CPU 8088的bug
MOV SS,BX;这与堆栈@ 0x80000的顶部放置它。
MOV SP,AX;集SP = 0,堆栈底部将是@ 0x8FFFF
STI;重新允许中断CLD;设置的方向标志为正方向

有两件事情要注意。当您更改的值的 SS 的寄存器(经由这种情况下, MOV )的处理器是假设关闭中断在该指令,并保持他们等到之后的 的下一条指令。通常你并不需要担心禁止中断,如果你更新的 SS 的立即的 SP 的的更新紧随其后。有一个在非常早期的8088处理器,这哪里是没有兑现,所以如果你的目标尽可能多的环境中,它是一个安全的赌注,以明确禁用和重新启用他们的错误。如果你不打算在马车8088则 CLI / STI 指令可以在除去以往工作code以上。我知道这个错误第一手我在80年代我家电脑上的中期做的工作。

要注意的第二件事是我如何设置堆栈。对于人们新8088/8086 16位汇编堆栈可设置多种方式。在这种情况下,我在设置堆栈(最低部分内存)的顶部为0x8000 SS 的)。然后,我设置堆栈指针( SP 的),以 0 。当您 16位实模式推栈东西处理器首先通过减2,然后堆栈指针在该位置放置一个16位的 WORD 的。因此,先推到堆栈将处于0x0000-2 = 0xFFFE(-2)。那么你有一个的 SS:SP 的,看起来像为0x8000:0xFFFE 。在这种情况下,堆栈为0x8000上运行:0×0000 为0x8000:0xFFFF的

当与一个8086运行栈处理(不适用于80286,80386+处理器)它是堆栈指针(的 SP 的)设置为偶数个好主意。在最初的8086,如果你的 SP 的设置为奇数,你会产生一个 4个时钟周期惩罚每次访问堆栈空间。由于8088有一个8位数据总线这个点球根本不存在,但装上8086的16位的的花了4个时钟周期,而这发生在8088(两个8位存储8个时钟周期读取)。

最后,如果你要明确设置的 CS:IP 的这样的 CS 的正确的时间设置的 JMP 的完成(以你的内核),则建议做一个 FAR JMP 影响段寄存器 / <请参阅操作EM>远跳转的)。在NASM语法 JMP 是这样的:

  JMP 0x07E0:为0x0000

一些(即MASM / MASM32)汇编不必℃,为en $ C $直接支持的 FAR JMP 的这么一种方式是可以做到的是手动这样的:

 分贝0x0ea;远跳转指令
DW为0x0000;抵消
DW 0x07E0;分割

如果使用GNU汇编它看起来像:

  ljmpw $ 0x07E0,$为0x0000

I'm writing small operation system - for practice. I started with bootloader.
I want to create small command system that runs in 16 bit real mode (for now).
I've created bootloader that resets drive, then loads sector after bootloader.
The problem is because after jmp function nothing actually happening.

I't trying to load next sector at 0x7E00 (I'm not totally sure how to point address using es:bx so that may be a problem, I believe that its Address:offset), just after bootloader.

This is the code:

;
; SECTOR 0x0
;

;dl is number of harddrive where is bootloader
org 0x7C00
bits 16

;reset hard drive
xor ah,ah
int 0x13
;read sectors
clc
mov bx,0x7E00
mov es,bx
xor bx,bx
mov ah,0x02 ;function
mov al,0x1  ;sectors to read
mov ch,0x0  ;tracks
mov cl,0x1  ;sector
mov dh,0x0  ;head
int 0x13
;if not readed jmp to error
jc error
;jump to 0x7E00 - executed only if loaded
jmp 0x7E00
error:
    mov si,MSGError
    .loop:
        lodsb
        or al,al
        jz .end
        mov ah,0x0E
        int 0x10
        jmp .loop
    .end:
        hlt
MSGError db "Error while booting", 0x0
times 0x1FE - ($ - $$) db 0x0
db 0x55
db 0xAA

;
; SECTOR 0x1
;

jmp printtest
;definitions
MSGLoaded db "Execution successful", 0x0
;
; Print function
; si - message to pring (NEED TO BE FINISHED WITH 0x0)

printtest:
    mov si,MSGLoaded
    .loop:
        lodsb
        or al,al
        jz .end
        mov ah,0x0E
        int 0x10
        jmp .loop
    .end:
        hlt

times 0x400 - ($-$$) db 0x0

I've been testing this code using VirtualBox but nothing actually happens, The read error doesn't shows, as well as message that should be printed.

解决方案

The primary problems with this code were:

  1. ES:BX was pointing to the wrong segment:offset to load the kernel into
  2. Wrong sector was being loaded so kernel wasn't what was expected

The first one was in this code:

mov bx,0x7E00
mov es,bx
xor bx,bx

The question wants to load the sector from disk to 0x0000:0x7E00(ES:BX). This code sets the ES:BX to 0x7E00:0x0000 which resolves to a physical address of 0x7E000 ((0x7E00<<4)+0x0000). I think the intention was to load 0x07E0 into ES which would yield a physical address of 0x7E00 ((0x07E0<<4)+0x0000). You can learn more about 16:16 memory addressing calculations here. Multiplying the segment by 16 is the same as shifting it left 4 bits.

The second problem in the code is here:

mov ah,0x02 ;function
mov al,0x1  ;sectors to read
mov ch,0x0  ;tracks
mov cl,0x2  ;sector number
mov dh,0x0  ;head
int 0x13

The number for the second 512 block sector on the disk is 2, not 1. So to fix the above code you need to set CL accordingly:

mov cl,0x2  ;sector number

General Tips for Bootloader Development

Other issues that can trip up running code on various emulators, virtual machines and real physical hardware that should be addressed are:

  1. When the BIOS jumps to your code you can't rely on CS,DS,ES,SS,SP registers having valid or expected values. They should be set up appropriately when your bootloader starts. You can only be guaranteed that your bootloader will be loaded and run from physical address 0x00007c00 and that the boot drive number is loaded into the DL register.
  2. The direction flag used by lodsb, movsb etc could be either set or cleared. If the direction flag is set improperly SI/DI registers may be adjusted in the wrong direction. Use STD/CLD to set it to the direction you wish (CLD=forward/STD=backwards). In this case the code assumes forward movement so one should use CLD. More on this can be found in an instruction set reference
  3. When jumping to a kernel it is generally a good idea to FAR JMP to it so that it properly sets CS:IP to expected values. This can avoid problems with kernel code that may do absolute near JMPs and CALLs within the same segment.
  4. If targeting your boot loader for 16-bit code that works on 8086/8088 processors (AND higher) avoid usage of 32 bit registers in assembly code. Use AX/BX/CX/DX/SI/DI/SP/BP instead of EAX/EBX/ECX/EDX/ESI/EDI/ESP/EBP. Although not an issue in this question, it has been an issue for others seeking help. A 32 bit processor can utilizes 32 bit registers in 16-bit real mode, but an 8086/8088/80286 can't since they were 16 bit processors without access to extended 32 bit registers.
  5. FS and GS segment registers were added to 80386+ CPUs. Avoid them if you intend to target 8086/8088/80286.

To resolve the first and second item this code can be used near the start of the boot loader:

xor ax,ax      ; We want a segment of 0 for DS for this question
mov ds,ax      ;     Set AX to appropriate segment value for your situation
mov es,ax      ; In this case we'll default to ES=DS
mov bx,0x8000  ; Stack segment can be any usable memory

cli            ; Disable interrupts to circumvent bug on early 8088 CPUs
mov ss,bx      ; This places it with the top of the stack @ 0x80000.
mov sp,ax      ; Set SP=0 so the bottom of stack will be @ 0x8FFFF
sti            ; Re-enable interrupts

cld            ; Set the direction flag to be positive direction

A couple things to note. When you change the value of the SS register (in this case via a MOV) the processor is suppose to turn off interrupts for that instruction and keep them off until after the following instruction. Normally you don't need to worry about disabling interrupts if you update SS followed immediately by an update of SP. There is a bug in very early 8088 processors where this wasn't honored so if you are targeting the widest possible environments it is a safe bet to explicitly disable and re-enable them. If you don't intend to ever work on a buggy 8088 then the CLI/STI instructions can be removed in the code above. I know about this bug first hand with work I did in the mid 80s on my home PC.

The second thing to note is how I set up the stack. For people new to 8088/8086 16-bit assembly the stack can be set a multitude of ways. In this case I set the top of the stack (lowest part in memory) at 0x8000(SS). I then set the stack pointer (SP) to 0. When you push something on the stack in 16-bit real mode the processor first decrements the stack pointer by 2 and then places a 16-bit WORD at that location. Thus the first push to the stack would be at 0x0000-2 = 0xFFFE (-2). You'd then have an SS:SP that looks like 0x8000:0xFFFE . In this case the stack runs from 0x8000:0x0000 to 0x8000:0xFFFF.

When dealing with the stack running on an 8086(doesn't apply to 80286,80386+ processors) it is a good idea to set the stack pointer (SP) to an even number. On the original 8086 if you set SP to an odd number you would incur a 4 clock cycle penalty for every access to stack space. Since the 8088 had an 8 bit data bus this penalty didn't exist, but loading a 16-bit word on 8086 took 4 clock cycles whereas it took 8 clock cycles on the 8088 (two 8 bit memory reads).

Lastly, If you want to explicitly set CS:IP so that CS is properly set by the time the JMP is complete (to your kernel) then it is recommended to do a FAR JMP (See Operations that affect segment registers/FAR Jump). In NASM syntax the JMP would look like this:

jmp 0x07E0:0x0000

Some (ie MASM/MASM32) assemblers don't have direct support to encode a FAR Jmp so one way it can be done is manually like this:

db 0x0ea     ; Far Jump instruction
dw 0x0000    ; Offset
dw 0x07E0    ; Segment

If using GNU assembler it would look like:

ljmpw $0x07E0,$0x0000

这篇关于引导加载程序不会跳转到内核code的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆