linux0.11/bootsect.s/jmpi go, INITSEG巧妙之处

/ 默认分类 / 0 条评论 / 481浏览

首先说下为什么要单独记录下这块知识点,原因是jmpi go, INITSEG实现的逻辑很巧妙,使得程序在复制之后可以继续执行。

之前我们介绍了bios的启动中断程序从约定的指定位置加载了bootsect汇编程序,bootsect做的第一件事就是进行内存规划。这一步会将自身的程序(bootsect.s)从内存的0x07C00位置复制到0x90000位置一份。也就是下面这段汇编指令:

entry _start
_start:
mov	ax,#BOOTSEG
	mov	ds,ax           ; 设置数据段寄存器为 BOOTSEG
	mov	ax,#INITSEG
	mov	es,ax           ; 设置附加段寄存器为 INITSEG
	mov	cx,#256
	sub	si,si
	sub	di,di
	rep
	movw                ; 将256个字(word)从 DS:SI 复制到 ES:DI
	jmpi	go,INITSEG    ; 跳转到 INITSEG 段的 go 标签
go:	mov	ax,cs
	mov	ds,ax
	mov	es,ax

在jmpi指令之前的操作,其实就是在进行代码复制,linus的设计本意是希望,在执行完bootsect的复制之后,继续执行后续的代码(也就是执行go位置的代码),而不是执行复制到的新位置的所有的bootsect的代码,这样的话就出现死循环了。所以jmpi go,INITSEG的作用就是调整cs/ip使得cpu继续从指定的位置执行。 下面我们来详细解释下这个指令的作用,是如何实现程序继续运行的。 实际上**,jmpi go, INITSEG** 是一个跳转指令,用于改变程序的执行流程。为了详细解释这个指令的含义,我们需要逐步拆解它。 jmpi go, INITSEG 可以拆分为两个部分:

  1. jmpi
  2. go, INITSEG

1. jmpi

jmpi 是一种非标准的汇编语法,通常在 AT&T 汇编语法中使用。在英特尔的 x86 架构中,标准的等价指令是 jmpfjmp,用于进行远跳转(far jump)。远跳转指令不仅改变指令指针(IP),还会改变代码段寄存器(CS)的值。

2. go, INITSEG

这个部分指定了跳转的目标:

汇总这两个部分,jmpi go, INITSEG 的含义是:将程序的控制权转移到 INITSEG 段中的 go 标签位置。具体步骤如下:

  1. 改变 CS 寄存器:将代码段寄存器(CS)设置为 INITSEG 的值。
  2. 改变 IP 寄存器:将指令指针(IP)设置为 go 标签的地址。

所以,通过直接设置CS/IP的值就可以跳转到希望继续执行的位置了。

汇编小知识,上面的代码复制的汇编详细解释如下

上面的代码主要作用是将引导扇区中的数据复制到内存的另一个位置,然后跳转到新的位置开始执行代码。具体来说,这段代码执行以下步骤:

  1. 设置段寄存器
    • mov ax, #BOOTSEG: 将引导段地址加载到 AX 寄存器。
    • mov ds, ax: 将引导段地址加载到 DS 段寄存器,DS 段寄存器现在指向引导段。
    • mov ax, #INITSEG: 将初始化段地址加载到 AX 寄存器。
    • mov es, ax: 将初始化段地址加载到 ES 段寄存器,ES 段寄存器现在指向初始化段。
  2. 复制数据
    • mov cx, #256: 将 CX 寄存器设置为 256,这是需要复制的字数(一个字是两个字节,共512字节)。
    • sub si, si: 将 SI 寄存器清零,指向源地址的偏移量为0。
    • sub di, di: 将 DI 寄存器清零,指向目标地址的偏移量为0。
    • rep movw: 重复执行 movw 指令,直到 CX 寄存器变为0。movw 指令将 DS:[SI] 中的字(2字节)复制到 ES:[DI] 中,然后 SI 和 DI 各加2,CX 减1。
  3. 跳转到新位置执行代码
    • jmpi go, INITSEG: 无条件跳转到 INITSEG 段中的 go 标签处继续执行。这意味着从新的位置开始执行代码。

下面是这段代码的详细解释:

_start:
	mov	ax,#BOOTSEG		// 将引导段地址加载到AX寄存器
	mov	ds,ax		// 将AX寄存器的值(即引导段地址)加载到DS段寄存器
	mov	ax,#INITSEG		// 将初始化段地址加载到AX寄存器
	mov	es,ax		// 将AX寄存器的值(即初始化段地址)加载到ES段寄存器
	mov	cx,#256		// 将CX寄存器设置为256,表示需要复制的字数(256个字,共512字节)
	sub	si,si		// 将SI寄存器清零,指向源地址的偏移量为0
	sub	di,di		// 将DI寄存器清零,指向目标地址的偏移量为0
	rep			// 重复以下指令,直到CX寄存器减到0
	movw			// 将DS:[SI]中的字(2字节)复制到ES:[DI]中,然后SI和DI各加2,CX减1
	jmpi	go,INITSEG	// 无条件跳转到INITSEG段中的go标签处继续执行

所以这段代码的主要目的是将引导扇区(通常加载到内存的0x7C00地址处)的数据复制到另一个内存位置(通常是0x9000或0x10000地址处),然后从新的位置开始执行代码。这样做的目的是为了给操作系统内核或其他引导代码腾出空间,并按照新的内存布局进行执行。