bootloader实现

上篇文章我们完成了一个简单的bootloader,与其说是bootloader,不如说是boot,本篇我们完成loader部分功能loader部分是在boot部分基础上,通过到约定好的启动盘位置上读数据载入内存,达到loader的目的。到启动盘读数据是bios提供的功能调用.

1. 铺垫

(1)我们这次的程序分两个部分,一个部分是bootloader,boot和loader功能;一个是head程序,这个程序什么也不做,简单的几条指令,我们只是要加载它执行它而已.

(2)bootloader是as86+ld86的产物,语法遵从as86语法;head是gnu汇编器语法,使用gcc编译ld链接,是32位的程序.

(3)默认bootloader程序会被放在软盘的引导扇区,就是虚拟软盘的前512Byte;head程序则放在从第二个512Byte开始处和以后的地方.

(4)bootloader的任务是加载head,执行head;注意,我们如果还是简单的加载as86汇编程序,实模式下跳转,哪有什么意思呢?我们需要更近一步,进入保护模式,同时跳转到head程序,为以后的AB任务切换做准备.

BOOTSEG=0x07c0
SYSSEG=0x1000
SYSLEN=4

entry start
start:
	jmpi go,#BOOTSEG
go:
	mov ax,cs
	mov ds,ax
	mov es,ax
	mov ss,ax
	mov sp,#0x400

	mov ax,#0x0600
	mov cx,#0x0000
	mov dx,#0xFFFF
	int 0x10

	mov cx,#10
	mov dx,#0x0000
	mov bx,#0x000c
	mov bp,#msg
	mov ax,#0x1301
	int 0x10

load_system:
	mov dx,#0x0000
	mov cx,#0x0002
	mov ax,#SYSSEG
	mov es,ax
	xor bx,bx
	mov ax,#0x200+SYSLEN
	int 0x13
	jnc ok_load
	mov dx,#0x0000
	mov ax,#0x0000
	int 0x13
	jmp load_system

ok_load:
	cli
	mov ax,#SYSSEG
	mov ds,ax
	xor ax,ax
	mov es,ax
	mov cx,#0x1000
	sub si,si
	sub di,di
	rep
	movw

	mov ax,cs
	mov ds,ax

	lidt idt_48
	lgdt gdt_48

	mov ax,#0x0001
	lmsw ax
	jmpi 0,8

msg:
	.ascii "Loading..."
	.byte 13,10

gdt:
	.word 0,0,0,0

	.word 0x07FF
	.word 0x0000
	.word 0x9A00
	.word 0x00C0

	.word 0x07FF
	.word 0x0000
	.word 0x9200
	.word 0x00C0

idt_48:
	.word 0
	.word 0,0

gdt_48:
	.word 0x7FF
	.word 0x7c00+gdt,0
.org 510
	.word 0xAA55

2.代码分析

BOOTSEG = 0x07c0

SYSSEG = 0x1000

SYSLEN = 4

entry start

start:

jmpi go,#BOOTSEG

go:

mov ax,cs

mov ds,ax

mov es,ax

mov ss,ax

mov sp,#0x400

只有mov sp,#0x400需要说一下,大家都知道sp是堆栈指针,对堆栈的操作会引起sp的变化,这里简单留出一段空间即可.

! clear screen

mov ax,#0x0600

mov cx,#0x0000

mov dx,#0xFFFF

int 0x10

! show "Loading..."

mov cx,#10

mov dx,#0x0000

mov bx,#0x000c

mov bp,#msg

mov ax,#0x1301

int 0x10

bios程序0x10的两段程序,分别是清屏幕和写字符串.

load_system:

mov dx,#0x0000

mov cx,#0x0002

mov ax,#SYSSEG

mov es,ax

xor bx,bx

mov ax,#0x200+SYSLEN

int 0x13

jnc ok_load

mov    dx,#0x0000

mov    ax,#0x0000

int    0x13

jmp    load_system

这段程序是真正的loader部分了,也是对bios功能的调用,准备参数如下:

mov dx,#0x0000 - dh磁头是0,dl是0表示软盘

mov cx,#0x0002 - ch柱面是0,cl开始扇区为2

mov ax,#SYSSEG

mov es,ax - es:bx = 0x1000:0x0 表示目的地址,0x13中断把扇区读到此位置.

xor bx,bx - 清零bx.

mov ax,#0x200+SYSLEN - ah对应int 0x13调用功能号02表示读扇区,al对应扇区个数.

int 0x13

整体的意思是把0柱面,0磁头,从2扇区开始的4个扇区读到内存0x1000:0x0处.

jnc ok_load

mov    dx,#0x0000

mov    ax,#0x0000

int    0x13

jmp    load_system

这段代码意思是如果出错了,就反复读,直到读出来正确为止,正确后跳转到ok_load标号处.

ok_load:

cli

mov ax,#SYSSEG

mov ds,ax

xor ax,ax

mov es,ax

mov cx,#0x1000

sub si,si

sub di,di

rep

movw

上边的代码目标是把读出来的代码移动到0x0处,为什么要移动到0x0处呢,就在0x1000:0x0处执行不成?实际上是可以的,只是需要和gdt描述符配合使用.

mov ax,cs

mov ds,ax

lidt idt_48

lgdt gdt_48

上边代码看似复杂,其实没什么,lidt是指令,idt_48是操作数;lgdt是指令,gdt_48是操作数;意思是加载中断描述符表和全局描述符表,为啥要加载这两个表呢?

因为保护模式下,cpu取指令和数据不再是0x7c00:0x0这样的方式了,而是根据你给定的0x1:0x0来找一个表,之后通过这个表找到具体的物理地址.这个过程中,

可以检查点权限什么的,起到保护作用.关于保护模式的问题慢慢理解即可,无需急于求成.mov ds,ax这句实际是提供数据段位置以便找到正确的变量地址.

mov ax,#0x0001

lmsw ax

jmpi 0,8

以上几句看似神奇,其实也很简单,通过设置寄存器的值,让cpu进入保护模式,保护模式无非就是寻址方式变了而已,理解就好.寻址方式变了之后,注意jmpi 0,8不再是跳转到0x8段:0x0偏移处了,而8是gdt表的选择符,0是偏移,8是选择符在gdt表中的偏移,这个时候cs就会被赋值8,但是不会从0x8:0x0处取地址,而是从gdt+8这个描述符定义的物理基地址+0x0物理地址处取指令噢!

至此,跳转到了head程序里了。head程序被我们加载到0x1000:0x0处,后被移动到0x0:0x0处,我们判断8对应的gdt里的描述符定义的物理基地址是0x0,下面重点分析gdt表定义。

gdt:

.word 0,0,0,0 - .word定义了一个字,就是两个字节,此处首先定义了0,0,0,0 8个空的字节,系统规定保留.一个段描述符就是8个字节哦.

.word 0x07FF - 可以想到前边的jmpi 0,8中的8(前边有了8个字节,偏移分别是0^7)指的就是接下来的8个字节定义的这个段. 0x07FF表示段限制长度,就是说这个段有多长,0x07FF十进制是2047,这里还不能确定是2047*1B还是2047*4KB,要看后边的定义.后边定了颗粒度为4KB,表示段限长度为8M

.word 0x0000 - 表示段基地址的0-15位

.word 0x9A00 - 00表示基地址的16-23位,9A为0x10011010分别表示代码段可读、执行

.word 0x00C0 - 00表示基地址的24-31为,C为0x1100表示颗粒度为4KB等.

.word 0x07FF - 此段为数据段描述符,意义基本同上.

.word 0x0000

.word 0x9200 - 00表示基地址的16-23位,92为0x10010010分别表示数据段可读写

.word 0x00C0

综上分析,我们实现了加载head程序,进入保护模式,同时最后跳转到了0x00000000物理地址处开始执行,我们知道head程序的代码就在那.

3. head程序

.globl startup_32
.text
startup_32:
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
die:
	jmp die

head程序没什么功能,只是测试是否能够进入保护模式,并跳转到head程序过来.

4. 编译组合

(1)编译bootloader

as86 -0 -a -o boot.o boot.s

ld86 -0 -s -o boot boot.o

(2)编译head

gcc -m32 -g -Wall -O2 -fomit-frame-pointer -fno-stack-protector -traditional -c head.s

此句用gcc编译head.s生成head.o,实际上gcc会调用as来汇编head.s

ld head.o -m elf_i386 -Ttext 0 -e startup_32 -o system

ld把head.o链接成system,启动代码段偏移从0开始,且把startup_32作为第一条指令.

objcopy -O binary -R .note -R .comment system kernel

ld生成的实际上是有文件头的文件,使用objcopy -O binary可以去掉文件头,同时-R去掉了文件中的指定段,生成kernel文件

至此,kernel是head.s生成的纯二进制代码.

(3)组合bootloader和head

dd if=boot of=boot.img bs=32 skip=1

生成boot到boot.img中,读写Block大小为32Byte,跳过输入文件的1个Block,也就是跳过了文件头.

dd if=kernel of=boot.img bs=512 seek=1

生成kernel到boot.img中,读写Block大小为512Byte,跳过输出文件的1个Block,也就是保留了boot.img中boot程序的512Byte,从512Byte后写入head程序kernel.

至此,boot.img就是集合了bootloader和head的启动盘了,其中bootloader在前512Byte,head紧挨着bootloader.

(4)执行

bochs即可

疑问解答:

1.我在首次看到这段的代码的时候,对mov ax,cs;mov ds,ax这两句的功能不了解,以为可以省略,但是经过代码调试发现,并不能省略。

lidt idt_48,在bochs调试对应的形式:

我们知道idt_48是一个偏移值,所以0x9a就是这个标号在代码段的偏移量(这个程序代码和数据放在同一个段中),所以lidt指令对应了一种寻址方式,段寄存器默认是ds,要知道现在还没进入保护模式,也就是在实模式下面,所以设置ds是非常重要的。

2.gdt_48:

.word 0x7FF

.word 0x7c00+gdt,0

根据调试,gdt是82,所以第二行相当于:.word 0x7c82,0,而lgdt执行完毕,GDTR中base(基址)的值是:0x00007c82,段长度是0x7FF。根据我的估计,我的计算机小段模式,所以要颠倒顺序。

参考:

http://www.cnblogs.com/linucos/archive/2012/04/01/2428402.html

bootloader实现

时间: 2024-10-17 17:24:40

bootloader实现的相关文章

How to acquire an Android phone with locked bootloader?

As we know that some devices come with locked bootloaders like Sony, HUAWEI, hTC...If you try to unlock bootloader, the data would disappear!!! Take hTC d826Y for example, our goal is to do physical acquisition so as to carve deleted data. But hTC D8

自己用C语言写PIC32单片机的serial bootloader

从15年12月份以来我的空余时间都在折腾15年底买的PIC32MZ EC Starter kit.并陆续写了十多篇随笔记录我折腾的历程.最近新完成的是用C语言实现了PIC32的UART bootloader, 采用串口通信,适用于Microchip的PIC32MZ EC,稍作对应的修改也可适用于PIC32MX, PIC32MZ EF等.Uart bootloader是用XC32编译的,电脑端的通信软件是用超级终端--HyperTerminal (也可以使用SecureCRT). 和之前我写的Hy

关于自制 STM8 Bootloader

由于本人项目需要,要做STM8L052R8的bootloader,用于远程程序升级功能,为了安全考虑,不使用ST自带的bootloader,而是自制bootloader. 基本的功能是这样的,首先程序运行在一个V1.0的版本上,且带了BOOT,当程序收到一条命令后,程序跑入死循环,等待硬件看门狗复位:程序复位后进入bootload区,等待第二条命令的接收,接收到正确的数据帧后,bootloader开始擦除FLASH,并接收数据包,直到最后一个数据包接收完毕,通过指示灯以2HZ的频率闪烁,指示升级

【NROS-01】核心态程序 Bootloader 装载操作系统

Bootloader Bootloader 是计算机启动之后第一个被执行的程序,负责装载操作系统.硬件能做的事情比较简单,内核结构复杂,硬件只能装载较为简单的 bootloader,由 bootloader 装载 OS. 计算机启动 计算机启动大约流程: 加电自检等,这是硬件厂商的事情: Bios 装载 Bootloader 到内存位置 0x7c00: Bootloader 装载 OS 到特定位置,跳转到 OS 执行. GCC & GNU-ld GCC 和 GNU-ld 是久经检验的工具,虽然也

DSP学习bootloader

DSP bootloader学习笔记1 彭会锋 参考: 1 TMS320F28xx DSP中内部Flash的应用研究 http://wenku.baidu.com/view/83e9837931b765ce050814fd.html 1 关于_c_int00问题 从代码注释可以看到 : _c_int00 is branch to start of boot.asm in RTS libray //翻译为中文就是:_c_int00是rts2800_ml.lib的入口地址: _c_int00是C初始

PIC32MZ Live update bootloader

PIC32MZ 的 flash memory 支持live update, 这是个全新的特性,在之前的所有PIC不管是8位还是16位的单片机上面都没有这个特性.我写过很多PIC 8位和16位单片机的bootloader,但这些bootloader都是传统的bootloader.传统的bootloader在更新程序时,需要先进入BOOTLOADER模式,BOOTLOADER模式完全独立于用户程序,只做更新应用程序的活. 今天要介绍的我最新完成的live update bootloader, 它是用

高通平台MSM8916LCM模块移植(一)-bootloader部分【转】

本文转载自:http://www.mobile-open.com/2016/970947.html 高通平台中的bootloader叫做LK(Little Kernel,对于LCM来说LK部分相当重要,它不仅要负责开机部分的LCD显示任务,还要负责传参给kernel的LCM驱动,指导kernel选择合适的LCM参数. 1.LK中LCM启动流程 注:read_panel_id()和read_panel_id_ddr3()为私有添加,非高通库上代码. 在这个流程图中,需要着重了解的有oem_pane

(待解决)开发板刷系统(一)---破坏bootloader以及从SD卡启动

开发板上默认在板载iNand中烧入了bootloader和Android系统镜像,如果想要从SD卡来启动,就必须先破坏掉iNand中的bootloader,让其校验失败,从而选择第二启动即从SD通道2启动. 破坏板载iNand中的bootloader的方法,可以参考九鼎的文档,其中提到的在Android平台下破坏iNand的bootloader的方法如下如所示: 执行上述指令后,在执行sync命令确保数据有效.解读下上面那条指令的意思: 输入文件是/dev/zero,输出文件是/dev/bloc

斐讯k1路由器刷Breed BootLoader(不死UBoot)教程

刷入Breed BootLoader: 因为这个K1路由器可以开启telnet服务,所以此处刷Breed可以不使用编程器刷Flash芯片的方法进行. 1.打开K1路由器的Telnet服务. 电脑通过有线的方式连接路由器(为了不必要的麻烦,这里建议用有线连接),在浏览器中输入: http://192.168.2.1/goform/Diagnosis?pingAddr=192.168.2.100|echo""|telnetd 2.在电脑上打开Telnet软件,连接路由器. Password

ARM Linux从Bootloader、kernel到filesystem启动流程

转自:http://www.veryarm.com/1491.html ARM Linux启动流程大致为:bootloader ---->kernel---->root filesystem.bootloader 是一上电就拿到cpu 的控制权的,而bootloader实现了硬件的初始化.bootloader俨然就成了Power on 之后”第一个吃螃蟹”的代码. 谈到这就得想到硬件机制是如何满足这个功能的了.CPU内部一般都集成小容量的SRAM (又叫stapping stone,垫脚石),