一、/charpter5/i中代码树状图如下:
| -- Makefile
| -- a.img
| -- bochsrc
| -- boot
| | -- boot.asm
| | -- include
| | | -- fat12hdr.inc
| | | -- load.inc
| | | -- pm.inc
| | -- loader.asm
| -- include
| | -- const.h
| | -- global.h
| | -- protect.h
| | -- proto.h
| | -- string.h
| | -- type.h
| -- kernel
| | -- global.c
| | -- i8259.c
| | -- kernel.asm
| | -- protect.c
| | -- start.c
| -- lib
| | -- klib.c
| | -- kliba.asm
| | -- string.asm
二、代码整体架构
此代码分析涉及到FAT12文件系统,ELF文件格式,X86/WIN32函数调用规范,int 10h,int 13h,int 15h。请参考以下博客:
FAT12文件系统:http://blog.csdn.net/jltxgcy/article/details/8665475
ELF文件格式:http://blog.csdn.net/jltxgcy/article/details/8687737
X86/WIN32函数调用规范:http://blog.csdn.net/jltxgcy/article/details/8668666
int 10h,int 13h,int 15h:http://blog.csdn.net/jltxgcy/article/details/8687881
系统内存分配如图:
程序整体流程如下:
boot从软盘存入7c00h,开始执行把loader加载到内存90100h处,跳转到此处,loader把kernel加载到内存80000h处,跳入保护模式,在保护模式下,重新放置kernel的位置到30000h处,并跳转到3040D处开始执行代码。然后用C语言和汇编共同扩展kernel。首先把gdt和堆栈移到了kernel中,添加中断和异常处理。
核心思路,把用于C语言引用的.c和汇编中的global的,还有常量,变量(用extern)都放在.h文件夹中,这样在C语言调用只需#include就能用,汇编里面用C语言,只需要extern就可以。
汇编语言传递参数有两种方式,一、push 参数 因为call还要传入eip,所以在子函数中如果要取到,需要[esp+8] ,调用者返回后要恢复堆栈,传递了几个参数就要让esp+4*参数的个数。二、mov cl , 1 此种方法如果想要暂时保持数据还要sub esp-2。 push在其他处的用法均为下面的语句会改变当前寄存器的值,并且这些寄存器的值后面会用到。movebp, esp 因为后面的push 语言要改变esp,那为什么不push esp呢?因为我们还要用到ebp来取得传递过来的参数。
这里面C语言的参数都是二进制的。
三、代码详细分析
boot/boot.asm
根目录读取一个扇区到内存09000h:0100位置,遍历此扇区的16个根目录,看是否有LOADER BIN,如果没找到,再读取一个扇区到内存09000h:0100位置,循环刚才的动作,直到14个扇区全部查找完毕;如果找到了,那么取该目录的开始簇号,①根据开始簇号取得他在数据区的扇区号然后读入内存09000h:0100处;然后根据开始簇号取得它在FAT1中的扇区号,然后读入内存08F00:0000处,一般读两个扇区;根据偏移计算FAT项的值,如果为FFF则结束,如果为008,转到①继续执行。
;%define _BOOT_DEBUG_ ; 做 Boot Sector 时一定将此行注释掉!将此行打开后用 nasm Boot.asm -o Boot.com 做成一个.COM文件易于调试 %ifdef _BOOT_DEBUG_ org 0100h ; 调试状态, 做成 .COM 文件, 可调试 %else org 07c00h ; Boot 状态, Bios 将把 Boot Sector 加载到 0:7C00 处并开始执行 %endif ;================================================================================================ %ifdef _BOOT_DEBUG_ BaseOfStack equ 0100h ; 调试状态下堆栈基地址(栈底, 从这个位置向低地址生长) %else BaseOfStack equ 07c00h ; Boot状态下堆栈基地址(栈底, 从这个位置向低地址生长) %endif %include "load.inc" ;================================================================================================ jmp short LABEL_START ; Start to boot. nop ; 这个 nop 不可少 ; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 %include "fat12hdr.inc" LABEL_START: mov ax, cs ;cs=0000h mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack;上面的地方没有代码,可以自由存数据 ; 清屏 mov ax, 0600h ; AH = 6, AL = 0h mov bx, 0700h ; 黑底白字(BL = 07h) mov cx, 0 ; 左上角: (0, 0) mov dx, 0184fh ; 右下角: (80, 50) int 10h ; int 10h mov dh, 0 ; "Booting " call DispStr ;int 10号暂时不考虑,功能显示字符串,因为有很多种方式 xor ah, ah ; ┓ xor dl, dl ; ┣ 软驱复位 int 13h ; ┛ ; 下面在 A 盘的根目录寻找 LOADER.BIN mov word [wSectorNo], SectorNoOfRootDirectory ;19 LABEL_SEARCH_IN_ROOT_DIR_BEGIN: cmp word [wRootDirSizeForLoop], 0 ; ┓ 14 jz LABEL_NO_LOADERBIN ; ┣ 判断根目录区是不是已经读完 dec word [wRootDirSizeForLoop] ; ┛ 如果读完表示没有找到 LOADER.BIN mov ax, BaseOfLoader ;09000h mov es, ax ; es <- BaseOfLoader mov bx, OffsetOfLoader ; 0100h bx <- OffsetOfLoader 于是, es:bx = BaseOfLoader:OffsetOfLoader mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号 mov cl, 1 call ReadSector mov si, LoaderFileName ; ds:si -> "LOADER BIN" mov di, OffsetOfLoader ; es:di -> BaseOfLoader:0100,,正好指向根目录项的文件名属性 cld mov dx, 10h ;因为一个扇区最多有512/32=16个根目录 LABEL_SEARCH_FOR_LOADERBIN: cmp dx, 0 ; 循环次数控制, jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ;如果已经读完这个扇区的所有根目录,就跳到下一个扇区。 dec dx ; 就跳到这个扇区的下一个根目录 mov cx, 11 ;因为根目录的DIR_Name有11个字节 LABEL_CMP_FILENAME: cmp cx, 0 jz LABEL_FILENAME_FOUND ; 如果比较了 11 个字符都相等, 表示找到 dec cx lodsb ; ds:si -> al cmp al, byte [es:di] jz LABEL_GO_ON jmp LABEL_DIFFERENT ; 只要发现不一样的字符就表明本 DirectoryEntry 不是我们要找的 LOADER.BIN LABEL_GO_ON: inc di jmp LABEL_CMP_FILENAME ; 继续循环 LABEL_DIFFERENT: and di, 0FFE0h ; di &= E0 为了让它指向本条目开头 add di, 20h ; di += 20h 下一个目录条目 20h=32个字节 mov si, LoaderFileName jmp LABEL_SEARCH_FOR_LOADERBIN; LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: add word [wSectorNo], 1 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN LABEL_NO_LOADERBIN: mov dh, 2 ; "No LOADER." call DispStr ; 显示字符串 %ifdef _BOOT_DEBUG_ mov ax, 4c00h ; ┓ int 21h ; ┛没有找到 LOADER.BIN, 回到 DOS %else jmp $ ; 没有找到 LOADER.BIN, 死循环在这里 %endif LABEL_FILENAME_FOUND: ; 找到 LOADER.BIN 后便来到这里继续 mov ax, RootDirSectors and di, 0FFE0h ; di -> 当前条目的开始 add di, 01Ah ; 取得此条目对应的开始簇号的偏移 mov cx, word [es:di] ;2 push cx ; 保存此 Sector 在 FAT 中的序号 add cx, ax ;2+14 add cx, DeltaSectorNo ; 19+14+簇号-2 mov ax, BaseOfLoader mov es, ax ; mov bx, OffsetOfLoader ; mov ax, cx ; ax=34 LABEL_GOON_LOADING_FILE: push ax ; `. push bx ; | mov ah, 0Eh ; | 每读一个扇区就在 "Booting " 后面 mov al, '.' ; | 打一个点, 形成这样的效果: mov bl, 0Fh ; | Booting ...... int 10h ; | pop bx ; | pop ax ; / mov cl, 1 ;扇区号为34,读一个扇区,到09000h:0100h call ReadSector pop ax ; 取出此 Sector 在 FAT 中的序号 call GetFATEntry ;或者FAT项的值 cmp ax, 0FFFh jz LABEL_FILE_LOADED push ax ; 保存 Sector 在 FAT 中的序号 mov dx, RootDirSectors add ax, dx ;ax+14 add ax, DeltaSectorNo ;ax+14+17 add bx, [BPB_BytsPerSec];0100h+200h,又读取一个扇区 jmp LABEL_GOON_LOADING_FILE LABEL_FILE_LOADED: mov dh, 1 ; "Ready." call DispStr ; 显示字符串 ; ***************************************************************************************************** jmp BaseOfLoader:OffsetOfLoader ; 这一句正式跳转到已加载到内 ; 存中的 LOADER.BIN 的开始处, ; 开始执行 LOADER.BIN 的代码。 ; Boot Sector 的使命到此结束 ; ***************************************************************************************************** ;============================================================================ ;变量 ;---------------------------------------------------------------------------- wRootDirSizeForLoop: dw RootDirSectors ; 因为要用到这个存储单元,不是单一的用14这个数字,Root Directory 占用的扇区数, 在循环中会递减至零. wSectorNo: dw 0 ; 要读取的扇区号 bOdd: db 0 ; 奇数还是偶数 ;============================================================================ ;字符串 ;---------------------------------------------------------------------------- LoaderFileName: db "LOADER BIN", 0 ; LOADER.BIN 之文件名 ; 为简化代码, 下面每个字符串的长度均为 MessageLength MessageLength equ 9 BootMessage: db "Booting "; 9字节, 不够则用空格补齐. 序号 0 Message1: db "Ready. "; 9字节, 不够则用空格补齐. 序号 1 Message2: db "No LOADER"; 9字节, 不够则用空格补齐. 序号 2 ;============================================================================ ;---------------------------------------------------------------------------- ; 函数名: DispStr ;---------------------------------------------------------------------------- ; 作用: ; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) DispStr: mov ax, MessageLength mul dh add ax, BootMessage mov bp, ax ; ┓ mov ax, ds ; ┣ ES:BP = 串地址 mov es, ax ; ┛ mov cx, MessageLength ; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) mov dl, 0 ;起始行列 int 10h ; int 10h ret ;---------------------------------------------------------------------------- ; 函数名: ReadSector ;---------------------------------------------------------------------------- ; 作用: ; 从第 ax 个 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 ReadSector: ; ----------------------------------------------------------------------- ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号) ; ----------------------------------------------------------------------- ; 设扇区号为 x ; ┌ 柱面号 = y >> 1 ; x ┌ 商 y ┤ ; -------------- => ┤ └ 磁头号 = y & 1 ; 每磁道扇区数 │ ; └ 余 z => 起始扇区号 = z + 1 push bp ;因为要用到bp mov bp, sp ; sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] mov byte [bp-2], cl ;大家奇怪为什么不直接push呢,因为如果直接push,那么则不能直接取出来数据 push bx ; 保存 bx,因为要做除数 mov bl, [BPB_SecPerTrk] ; bl: 除数为18 div bl ; y 在 al 中, z 在 ah 中 inc ah ; z ++ mov cl, ah ; cl <- 起始扇区号 mov dh, al ; dh <- y shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) mov ch, al ; ch <- 柱面号 and dh, 1 ; dh & 1 = 磁头号 pop bx ; 恢复 bx ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^ mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) .GoOnReading: mov ah, 2 ; 读 mov al, byte [bp-2] ; 读 al 个扇区 int 13h jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 add esp, 2 ;由于前面辟出了两个字节,保存要读的扇区数 pop bp ret ;---------------------------------------------------------------------------- ; 函数名: GetFATEntry ;---------------------------------------------------------------------------- ; 作用: ; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中 ; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx GetFATEntry: push es push bx push ax mov ax, BaseOfLoader; `. sub ax, 0100h ; | 在 BaseOfLoader 后面留出 4K 空间用于存放 FAT;9000-0100=8F00,90000和8F000之前差4K mov es, ax ; / pop ax ;ax=2 mov byte [bOdd], 0 mov bx, 3 mul bx ; ax=6; mov bx, 2 div bx ;ax=3(商);dx=0(余数),本质是ax*1.5,为了求偏移 cmp dx, 0 jz LABEL_EVEN mov byte [bOdd], 1 ;如果为1,说明是奇数 LABEL_EVEN:;偶数 ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量,下面来 ; 计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区) xor dx, dx mov bx, [BPB_BytsPerSec] :512 div bx ; dx:ax / BPB_BytsPerSec ; ax <- 商 (FATEntry 所在的扇区相对于 FAT 的扇区号) 结果是0号扇区 ; dx <- 余数 (FATEntry 在扇区内的偏移) 结果是3号偏移 push dx mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfLoader - 100):00 add ax, SectorNoOfFAT1 ; 引导扇区占一个扇区,此句之后的 ax 就是 FATEntry 所在的扇区号 mov cl, 2 call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界 ; 发生错误, 因为一个 FATEntry 可能跨越两个扇区 pop dx add bx, dx ;偏移为3 mov ax, [es:bx] ; 如前面的图所说,ax=8FFFh cmp byte [bOdd], 1 jnz LABEL_EVEN_2 shr ax, 4 ;如果偏移为4,上步得到ax=008Fh,右移后ax=0008h,与后还为0008h LABEL_EVEN_2: and ax, 0FFFh ;不等于跳到此处,ax=0FFFh LABEL_GET_FAT_ENRY_OK: pop bx pop es ret ;---------------------------------------------------------------------------- times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 dw 0xaa55 ; 结束标志
; FAT12 磁盘的头 ; ---------------------------------------------------------------------- ;一个磁道有18个扇区,一个扇区有512字节 ; 下面是 FAT12 磁盘的头 BS_OEMName DB 'ForrestY' ; OEM String, 必须 8 个字节 BPB_BytsPerSec DW 512 ; 每扇区字节数 ;重要 BPB_SecPerClus DB 1 ; 每簇多少扇区 BPB_RsvdSecCnt DW 1 ; Boot 记录占用多少扇区 ;所以FAT的第一个扇区为1 BPB_NumFATs DB 2 ; 共有多少 FAT 表 BPB_RootEntCnt DW 224 ; 根目录文件数最大值 ;计算扇区个数时候用到 BPB_TotSec16 DW 2880 ; 逻辑扇区总数 BPB_Media DB 0xF0 ; 媒体描述符 BPB_FATSz16 DW 9 ; 每FAT扇区数 BPB_SecPerTrk DW 18 ; 每磁道扇区数 ;重要 BPB_NumHeads DW 2 ; 磁头数(面数) ;由扇区求柱面,磁头,扇区时候用到 BPB_HiddSec DD 0 ; 隐藏扇区数 BPB_TotSec32 DD 0 ; 如果 wTotalSectorCount 是 0 由这个值记录扇区数 BS_DrvNum DB 0 ; 中断 13 的驱动器号 ;后来赋值给dl BS_Reserved1 DB 0 ; 未使用 BS_BootSig DB 29h ; 扩展引导标记 (29h) BS_VolID DD 0 ; 卷序列号 BS_VolLab DB 'OrangeS0.02'; 卷标, 必须 11 个字节 BS_FileSysType DB 'FAT12 ' ; 文件系统类型, 必须 8个字节 ;------------------------------------------------------------------------ ; ------------------------------------------------------------------------- ; 基于 FAT12 头的一些常量定义,如果头信息改变,下面的常量可能也要做相应改变 ; ------------------------------------------------------------------------- FATSz equ 9 ; BPB_FATSz16 ;equ为伪指令,编译时候就变成对应的代码,本身不占用空间 RootDirSectors equ 14 ; 根目录占用14个扇区 根据BPB_RootEntCnt计算出来的 SectorNoOfRootDirectory equ 19 ; 根目录的第一个扇区号,每个根目录项占32个字节 SectorNoOfFAT1 equ 1 ; FAT1 的第一个扇区号 = BPB_RsvdSecCnt DeltaSectorNo equ 17 ; DeltaSectorNo = BPB_RsvdSecCnt + (BPB_NumFATs * FATSz) - 2 ; 文件的开始Sector号 = DirEntry中的开始Sector号 + 根目录占用Sector数目 + DeltaSectorNo
BaseOfLoader equ 09000h ; LOADER.BIN 被加载到的位置 ---- 段地址 OffsetOfLoader equ 0100h ; LOADER.BIN 被加载到的位置 ---- 偏移地址 BaseOfLoaderPhyAddr equ BaseOfLoader * 10h ; LOADER.BIN 被加载到的位置 ---- 物理地址 (= BaseOfLoader * 10h) BaseOfKernelFile equ 08000h ; KERNEL.BIN 被加载到的位置 ---- 段地址 OffsetOfKernelFile equ 0h ; KERNEL.BIN 被加载到的位置 ---- 偏移地址 BaseOfKernelFilePhyAddr equ BaseOfKernelFile * 10h KernelEntryPointPhyAddr equ 030400h ; 注意:1、必须与 MAKEFILE 中参数 -Ttext 的值相等!! ; 2、这是个地址而非仅仅是个偏移,如果 -Ttext 的值为 0x400400,则它的值也应该是 0x400400。 PageDirBase equ 200000h ; 页目录开始地址: 2M PageTblBase equ 201000h ; 页表开始地址: 2M + 4K
参考http://blog.csdn.net/jltxgcy/article/details/8656101
/boot/loader.asm
org 0100h jmp LABEL_START ; Start ; 下面是 FAT12 磁盘的头, 之所以包含它是因为下面用到了磁盘的一些信息 %include "fat12hdr.inc" %include "load.inc" %include "pm.inc" ; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ ; 段基址 段界限 , 属性 LABEL_GDT: Descriptor 0, 0, 0 ; 空描述符 LABEL_DESC_FLAT_C: Descriptor 0, 0fffffh, DA_CR | DA_32 | DA_LIMIT_4K ; 0 ~ 4G LABEL_DESC_FLAT_RW: Descriptor 0, 0fffffh, DA_DRW | DA_32 | DA_LIMIT_4K ; 0 ~ 4G LABEL_DESC_VIDEO: Descriptor 0B8000h, 0ffffh, DA_DRW | DA_DPL3 ; 显存首地址 ; GDT ------------------------------------------------------------------------------------------------------------------------------------------------------------ GdtLen equ $ - LABEL_GDT GdtPtr dw GdtLen - 1 ; 段界限 dd BaseOfLoaderPhyAddr + LABEL_GDT ; 基地址 ; GDT 选择子 ---------------------------------------------------------------------------------- SelectorFlatC equ LABEL_DESC_FLAT_C - LABEL_GDT SelectorFlatRW equ LABEL_DESC_FLAT_RW - LABEL_GDT SelectorVideo equ LABEL_DESC_VIDEO - LABEL_GDT + SA_RPL3 ; GDT 选择子 ---------------------------------------------------------------------------------- BaseOfStack equ 0100h LABEL_START: ; <--- 从这里开始 ************* mov ax, cs ;cs:09000h mov ds, ax mov es, ax mov ss, ax mov sp, BaseOfStack mov dh, 0 ; "Loading " call DispStrRealMode ; 显示字符串 ; 得到内存数 mov ebx, 0 ; ebx = 后续值, 开始时需为 0 mov di, _MemChkBuf ; es:di 指向一个地址范围描述符结构(Address Range Descriptor Structure) .MemChkLoop: mov eax, 0E820h ; eax = 0000E820h mov ecx, 20 ; ecx = 地址范围描述符结构的大小 mov edx, 0534D4150h ; edx = 'SMAP' int 15h ; int 15h jc .MemChkFail add di, 20 inc dword [_dwMCRNumber] ; dwMCRNumber = ARDS 的个数 cmp ebx, 0 jne .MemChkLoop jmp .MemChkOK .MemChkFail: mov dword [_dwMCRNumber], 0 .MemChkOK: ; 下面在 A 盘的根目录寻找 KERNEL.BIN mov word [wSectorNo], SectorNoOfRootDirectory xor ah, ah ; ┓ xor dl, dl ; ┣ 软驱复位 int 13h ; ┛ LABEL_SEARCH_IN_ROOT_DIR_BEGIN: cmp word [wRootDirSizeForLoop], 0 ; ┓ jz LABEL_NO_KERNELBIN ; ┣ 判断根目录区是不是已经读完, 如果读完表示没有找到 KERNEL.BIN dec word [wRootDirSizeForLoop] ; ┛ mov ax, BaseOfKernelFile mov es, ax ; es <- BaseOfKernelFile mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile mov ax, [wSectorNo] ; ax <- Root Directory 中的某 Sector 号 mov cl, 1 call ReadSector mov si, KernelFileName ; ds:si -> "KERNEL BIN" mov di, OffsetOfKernelFile ; es:di -> BaseOfKernelFile:???? = BaseOfKernelFile*10h+???? cld mov dx, 10h LABEL_SEARCH_FOR_KERNELBIN: cmp dx, 0 ; ┓ jz LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR ; ┣ 循环次数控制, 如果已经读完了一个 Sector, 就跳到下一个 Sector dec dx ; ┛ mov cx, 11 LABEL_CMP_FILENAME: cmp cx, 0 ; ┓ jz LABEL_FILENAME_FOUND ; ┣ 循环次数控制, 如果比较了 11 个字符都相等, 表示找到 dec cx ; ┛ lodsb ; ds:si -> al cmp al, byte [es:di] ; if al == es:di jz LABEL_GO_ON jmp LABEL_DIFFERENT LABEL_GO_ON: inc di jmp LABEL_CMP_FILENAME ; 继续循环 LABEL_DIFFERENT: and di, 0FFE0h ; else┓ 这时di的值不知道是什么, di &= e0 为了让它是 20h 的倍数 add di, 20h ; ┃ mov si, KernelFileName ; ┣ di += 20h 下一个目录条目 jmp LABEL_SEARCH_FOR_KERNELBIN; ┛ LABEL_GOTO_NEXT_SECTOR_IN_ROOT_DIR: add word [wSectorNo], 1 jmp LABEL_SEARCH_IN_ROOT_DIR_BEGIN LABEL_NO_KERNELBIN: mov dh, 2 ; "No KERNEL." call DispStrRealMode ; 显示字符串 jmp $ ; 没有找到 KERNEL.BIN, 死循环在这里 LABEL_FILENAME_FOUND: ; 找到 KERNEL.BIN 后便来到这里继续 mov ax, RootDirSectors and di, 0FFF0h ; di -> 当前条目的开始 push eax mov eax, [es : di + 01Ch] ; ┓ mov dword [dwKernelSize], eax ; ┛保存 KERNEL.BIN 文件大小 pop eax add di, 01Ah ; di -> 首 Sector mov cx, word [es:di] push cx ; 保存此 Sector 在 FAT 中的序号 add cx, ax add cx, DeltaSectorNo ; 这时 cl 里面是 LOADER.BIN 的起始扇区号 (从 0 开始数的序号) mov ax, BaseOfKernelFile mov es, ax ; es <- BaseOfKernelFile mov bx, OffsetOfKernelFile ; bx <- OffsetOfKernelFile 于是, es:bx = BaseOfKernelFile:OffsetOfKernelFile = BaseOfKernelFile * 10h + OffsetOfKernelFile mov ax, cx ; ax <- Sector 号 LABEL_GOON_LOADING_FILE: push ax ; ┓ push bx ; ┃ mov ah, 0Eh ; ┃ 每读一个扇区就在 "Loading " 后面打一个点, 形成这样的效果: mov al, '.' ; ┃ mov bl, 0Fh ; ┃ Loading ...... int 10h ; ┃ pop bx ; ┃ pop ax ; ┛ mov cl, 1;传递参数 call ReadSector pop ax ; 取出此 Sector 在 FAT 中的序号 call GetFATEntry cmp ax, 0FFFh jz LABEL_FILE_LOADED push ax ; 保存 Sector 在 FAT 中的序号 mov dx, RootDirSectors add ax, dx add ax, DeltaSectorNo add bx, [BPB_BytsPerSec] jmp LABEL_GOON_LOADING_FILE LABEL_FILE_LOADED: call KillMotor ; 关闭软驱马达 mov dh, 1 ; "Ready." call DispStrRealMode ; 显示字符串 ; 下面准备跳入保护模式 ------------------------------------------- ; 加载 GDTR lgdt [GdtPtr] ; 关中断 cli ; 打开地址线A20 in al, 92h or al, 00000010b out 92h, al ; 准备切换到保护模式 mov eax, cr0 or eax, 1 mov cr0, eax ; 真正进入保护模式 jmp dword SelectorFlatC:(BaseOfLoaderPhyAddr+LABEL_PM_START) ;============================================================================ ;变量 ;---------------------------------------------------------------------------- wRootDirSizeForLoop: dw RootDirSectors ; Root Directory 占用的扇区数 wSectorNo: dw 0 ; 要读取的扇区号 bOdd: db 0 ; 奇数还是偶数 dwKernelSize: dd 0 ; KERNEL.BIN 文件大小 ;============================================================================ ;字符串 ;---------------------------------------------------------------------------- KernelFileName db "KERNEL BIN", 0 ; KERNEL.BIN 之文件名 ; 为简化代码, 下面每个字符串的长度均为 MessageLength MessageLength equ 9 LoadMessage: db "Loading " Message1: db "Ready. " Message2: db "No KERNEL" ;============================================================================ ;---------------------------------------------------------------------------- ; 函数名: DispStrRealMode ;---------------------------------------------------------------------------- ; 运行环境: ; 实模式(保护模式下显示字符串由函数 DispStr 完成) ; 作用: ; 显示一个字符串, 函数开始时 dh 中应该是字符串序号(0-based) DispStrRealMode: mov ax, MessageLength mul dh add ax, LoadMessage mov bp, ax ; ┓ mov ax, ds ; ┣ ES:BP = 串地址 mov es, ax ; ┛ mov cx, MessageLength ; CX = 串长度 mov ax, 01301h ; AH = 13, AL = 01h mov bx, 0007h ; 页号为0(BH = 0) 黑底白字(BL = 07h) mov dl, 0 add dh, 3 ; 从第 3 行往下显示 int 10h ; int 10h ret ;---------------------------------------------------------------------------- ; 函数名: ReadSector ;---------------------------------------------------------------------------- ; 作用: ; 从序号(Directory Entry 中的 Sector 号)为 ax 的的 Sector 开始, 将 cl 个 Sector 读入 es:bx 中 ReadSector: ; ----------------------------------------------------------------------- ; 怎样由扇区号求扇区在磁盘中的位置 (扇区号 -> 柱面号, 起始扇区, 磁头号) ; ----------------------------------------------------------------------- ; 设扇区号为 x ; ┌ 柱面号 = y >> 1 ; x ┌ 商 y ┤ ; -------------- => ┤ └ 磁头号 = y & 1 ; 每磁道扇区数 │ ; └ 余 z => 起始扇区号 = z + 1 push bp mov bp, sp sub esp, 2 ; 辟出两个字节的堆栈区域保存要读的扇区数: byte [bp-2] mov byte [bp-2], cl ;暂时存放参数,如果是push进来的就不存在此问题 push bx ; 保存 bx mov bl, [BPB_SecPerTrk] ; bl: 除数 div bl ; y 在 al 中, z 在 ah 中 inc ah ; z ++ mov cl, ah ; cl <- 起始扇区号 mov dh, al ; dh <- y shr al, 1 ; y >> 1 (其实是 y/BPB_NumHeads, 这里BPB_NumHeads=2) mov ch, al ; ch <- 柱面号 and dh, 1 ; dh & 1 = 磁头号 pop bx ; 恢复 bx ; 至此, "柱面号, 起始扇区, 磁头号" 全部得到 ^^^^^^^^^^^^^^^^^^^^^^^^ mov dl, [BS_DrvNum] ; 驱动器号 (0 表示 A 盘) .GoOnReading: mov ah, 2 ; 读 mov al, byte [bp-2] ; 读 al 个扇区 int 13h jc .GoOnReading ; 如果读取错误 CF 会被置为 1, 这时就不停地读, 直到正确为止 add esp, 2 pop bp ret ;---------------------------------------------------------------------------- ; 函数名: GetFATEntry ;---------------------------------------------------------------------------- ; 作用: ; 找到序号为 ax 的 Sector 在 FAT 中的条目, 结果放在 ax 中 ; 需要注意的是, 中间需要读 FAT 的扇区到 es:bx 处, 所以函数一开始保存了 es 和 bx GetFATEntry: push es push bx push ax mov ax, BaseOfKernelFile ; ┓ sub ax, 0100h ; ┣ 在 BaseOfKernelFile 后面留出 4K 空间用于存放 FAT mov es, ax ; ┛ pop ax mov byte [bOdd], 0 mov bx, 3 mul bx ; dx:ax = ax * 3 mov bx, 2 div bx ; dx:ax / 2 ==> ax <- 商, dx <- 余数 cmp dx, 0 jz LABEL_EVEN mov byte [bOdd], 1 LABEL_EVEN:;偶数 xor dx, dx ; 现在 ax 中是 FATEntry 在 FAT 中的偏移量. 下面来计算 FATEntry 在哪个扇区中(FAT占用不止一个扇区) mov bx, [BPB_BytsPerSec] div bx ; dx:ax / BPB_BytsPerSec ==> ax <- 商 (FATEntry 所在的扇区相对于 FAT 来说的扇区号) ; dx <- 余数 (FATEntry 在扇区内的偏移)。 push dx mov bx, 0 ; bx <- 0 于是, es:bx = (BaseOfKernelFile - 100):00 = (BaseOfKernelFile - 100) * 10h add ax, SectorNoOfFAT1 ; 此句执行之后的 ax 就是 FATEntry 所在的扇区号 mov cl, 2 call ReadSector ; 读取 FATEntry 所在的扇区, 一次读两个, 避免在边界发生错误, 因为一个 FATEntry 可能跨越两个扇区 pop dx add bx, dx mov ax, [es:bx] cmp byte [bOdd], 1 jnz LABEL_EVEN_2 shr ax, 4 LABEL_EVEN_2: and ax, 0FFFh LABEL_GET_FAT_ENRY_OK: pop bx pop es ret ;---------------------------------------------------------------------------- ;---------------------------------------------------------------------------- ; 函数名: KillMotor ;---------------------------------------------------------------------------- ; 作用: ; 关闭软驱马达 KillMotor: push dx mov dx, 03F2h mov al, 0 out dx, al pop dx ret ;---------------------------------------------------------------------------- ; 从此以后的代码在保护模式下执行 ---------------------------------------------------- ; 32 位代码段. 由实模式跳入 --------------------------------------------------------- [SECTION .s32] ALIGN 32 [BITS 32] LABEL_PM_START: mov ax, SelectorVideo mov gs, ax mov ax, SelectorFlatRW mov ds, ax mov es, ax mov fs, ax mov ss, ax mov esp, TopOfStack push szMemChkTitle call DispStr add esp, 4 call DispMemInfo call SetupPaging ;mov ah, 0Fh ; 0000: 黑底 1111: 白字 ;mov al, 'P' ;mov [gs:((80 * 0 + 39) * 2)], ax ; 屏幕第 0 行, 第 39 列。 call InitKernel ;jmp $ ;*************************************************************** jmp SelectorFlatC:KernelEntryPointPhyAddr ; 正式进入内核 * ;*************************************************************** ; 内存看上去是这样的: ; ┃ ┃ ; ┃ . ┃ ; ┃ . ┃ ; ┃ . ┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; ┃■■■■■■Page Tables■■■■■■┃ ; ┃■■■■■(大小由LOADER决定)■■■■┃ ; 00101000h ┃■■■■■■■■■■■■■■■■■■┃ PageTblBase ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 00100000h ┃■■■■Page Directory Table■■■■┃ PageDirBase <- 1M ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; F0000h ┃□□□□□□□System ROM□□□□□□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; E0000h ┃□□□□Expansion of system ROM □□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; C0000h ┃□□□Reserved for ROM expansion□□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ B8000h ← gs ; A0000h ┃□□□Display adapter reserved□□□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; 9FC00h ┃□□extended BIOS data area (EBDA)□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 90000h ┃■■■■■■■LOADER.BIN■■■■■■┃ somewhere in LOADER ← esp ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 80000h ┃■■■■■■■KERNEL.BIN■■■■■■┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 30000h ┃■■■■■■■■KERNEL■■■■■■■┃ 30400h ← KERNEL 入口 (KernelEntryPointPhyAddr) ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃ ┃ ; 7E00h ┃ F R E E ┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃■■■■■■■■■■■■■■■■■■┃ ; 7C00h ┃■■■■■■BOOT SECTOR■■■■■■┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃ ┃ ; 500h ┃ F R E E ┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃□□□□□□□□□□□□□□□□□□┃ ; 400h ┃□□□□ROM BIOS parameter area □□┃ ; ┣━━━━━━━━━━━━━━━━━━┫ ; ┃◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇◇┃ ; 0h ┃◇◇◇◇◇◇Int Vectors◇◇◇◇◇◇┃ ; ┗━━━━━━━━━━━━━━━━━━┛ ← cs, ds, es, fs, ss ; ; ; ┏━━━┓ ┏━━━┓ ; ┃■■■┃ 我们使用 ┃□□□┃ 不能使用的内存 ; ┗━━━┛ ┗━━━┛ ; ┏━━━┓ ┏━━━┓ ; ┃ ┃ 未使用空间 ┃◇◇◇┃ 可以覆盖的内存 ; ┗━━━┛ ┗━━━┛ ; ; 注:KERNEL 的位置实际上是很灵活的,可以通过同时改变 LOAD.INC 中的 KernelEntryPointPhyAddr 和 MAKEFILE 中参数 -Ttext 的值来改变。 ; 比如,如果把 KernelEntryPointPhyAddr 和 -Ttext 的值都改为 0x400400,则 KERNEL 就会被加载到内存 0x400000(4M) 处,入口在 0x400400。 ; ; ------------------------------------------------------------------------ ; 显示 AL 中的数字 ; ------------------------------------------------------------------------ DispAL:;主要就是一个2进制到16进制转换的过程 push ecx push edx push edi mov edi, [dwDispPos] mov ah, 0Fh ; 0000b: 黑底 1111b: 白字 mov dl, al ;al 赋给dl,先把al的值保存起来 shr al, 4 ;al右移4位,原来al中的高四位成为低四位 mov ecx, 2 .begin: and al, 01111b ;处理al的高4位 cmp al, 9 ;前者大于后者跳转 ,即al大于9跳转,al小于9不跳转。 ja .1 add al, '0' ;al小于9还时用数字表示 jmp .2 ;显示出来 .1: sub al, 0Ah ;al大于9就要转换为16进制,用字母的方式表示 add al, 'A' .2: mov [gs:edi], ax ;al中要显示的数值已经转化为16进制了,就显示出来 add edi, 2 mov al, dl ;这时al中低四位就是存放的原来al中的低四位 loop .begin ;跳转到begin继续执行,这时就是跳转上去处理原来al中的低四位 ;add edi, 2 mov [dwDispPos], edi ;每次都要修改,以便下一个演示,可以接着显示 pop edi pop edx pop ecx ret ; DispAL 结束------------------------------------------------------------- ; ------------------------------------------------------------------------ ; 显示一个整形数 ; ------------------------------------------------------------------------ DispInt: mov eax, [esp + 4] ;ss:(esp+4)注意这里esp+4是因为调入之前的call指令,因为call指令会自动把一些参数入栈,esp要减4,esp+4正好指向了第一次push进去的 shr eax, 24 call DispAL mov eax, [esp + 4] shr eax, 16 call DispAL mov eax, [esp + 4] shr eax, 8 call DispAL mov eax, [esp + 4] call DispAL mov ah, 07h ; 0000b: 黑底 0111b: 灰字 mov al, 'h' push edi mov edi, [dwDispPos] mov [gs:edi], ax ;显示h add edi, 4;本来ax应该是edi+2,但是为了再空一格,所以又加了2 ,一共加4,从而得到新的位置 mov [dwDispPos], edi ;每次都要修改,以便下一个演示,可以接着显示 pop edi ret ; DispInt 结束------------------------------------------------------------ ; ------------------------------------------------------------------------ ; 显示一个字符串 ; ------------------------------------------------------------------------ DispStr: push ebp ;因为还压入了eip mov ebp, esp push ebx push esi push edi mov esi, [ebp + 8] ; ss:ebp+8 pszInfo mov edi, [dwDispPos] mov ah, 0Fh .1: lodsb test al, al jz .2 ;结束了么? cmp al, 0Ah ; 是回车吗? jnz .3 push eax mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop eax ;如果是回车,那么光标另起一行 jmp .1 ;跳到1 .3: mov [gs:edi], ax add edi, 2 jmp .1 .2: mov [dwDispPos], edi; 每次都要修改,以便下一个演示,可以接着显示 pop edi pop esi pop ebx pop ebp ret ; DispStr 结束------------------------------------------------------------ ; ------------------------------------------------------------------------ ; 换行 ; ------------------------------------------------------------------------ DispReturn: push szReturn call DispStr ;printf("\n"); add esp, 4 ret ; DispReturn 结束--------------------------------------------------------- ; ------------------------------------------------------------------------ ; 内存拷贝,仿 memcpy ; ------------------------------------------------------------------------ ; void* MemCpy(void* es:pDest, void* ds:pSrc, int iSize); ; ------------------------------------------------------------------------ MemCpy: push ebp mov ebp, esp push esi push edi push ecx mov edi, [ebp + 8] ; Destination mov esi, [ebp + 12] ; Source mov ecx, [ebp + 16] ; Counter .1: cmp ecx, 0 ; 判断计数器 jz .2 ; 计数器为零时跳出 mov al, [ds:esi] ; ┓ inc esi ; ┃ ; ┣ 逐字节移动 mov byte [es:edi], al ; ┃ inc edi ; ┛ dec ecx ; 计数器减一 jmp .1 ; 循环 .2: mov eax, [ebp + 8] ; 返回值 pop ecx pop edi pop esi mov esp, ebp pop ebp ret ; 函数结束,返回 ; MemCpy 结束------------------------------------------------------------- ; 显示内存信息 -------------------------------------------------------------- DispMemInfo: push esi push edi push ecx mov esi, MemChkBuf mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构 .loop: ;{ mov edx, 5 ; for(int j=0;j<5;j++) // 每次得到一个ARDS中的成员,共5个成员 mov edi, ARDStruct ; { // 依次显示:BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type .1: ; push dword [esi] ; BaseAddrLow,BaseAddrHigh,LengthLow,LengthHigh,Type 每个对应4个字节,每次压入4个字节 call DispInt ; DispInt(MemChkBuf[j*4]); // 显示一个成员 pop eax ; stosd ; ARDStruct[j*4] = MemChkBuf[j*4]; add esi, 4 ; dec edx ; cmp edx, 0 ; jnz .1 ; } call DispReturn ; printf("\n"); cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2 jne .2 ; { mov eax, [dwBaseAddrLow] ; add eax, [dwLengthLow] ; cmp eax, [dwMemSize] ; if(BaseAddrLow + LengthLow > MemSize) jb .2 ; mov [dwMemSize], eax ; MemSize = BaseAddrLow + LengthLow; .2: ; } loop .loop ;} ; call DispReturn ;printf("\n"); push szRAMSize ; call DispStr ;printf("RAM size:"); add esp, 4 ; ; push dword [dwMemSize] ; call DispInt ;DispInt(MemSize); add esp, 4 ; pop ecx pop edi pop esi ret ; --------------------------------------------------------------------------- ; 启动分页机制 -------------------------------------------------------------- SetupPaging: ; 根据内存大小计算应初始化多少PDE以及多少页表 xor edx, edx mov eax, [dwMemSize] mov ebx, 400000h ; 400000h = 4M = 4096 * 1024, 一个页表对应的内存大小 div ebx mov ecx, eax ; 此时 ecx 为页表的个数,也即 PDE 应该的个数 test edx, edx jz .no_remainder inc ecx ; 如果余数不为 0 就需增加一个页表 .no_remainder: push ecx ; 暂存页表个数 ; 为简化处理, 所有线性地址对应相等的物理地址. 并且不考虑内存空洞. ; 首先初始化页目录 mov ax, SelectorFlatRW mov es, ax mov edi, PageDirBase ; 此段首地址为 PageDirBase xor eax, eax mov eax, PageTblBase | PG_P | PG_USU | PG_RWW .1: stosd add eax, 4096 ; 为了简化, 所有页表在内存中是连续的. loop .1 ; 再初始化所有页表 pop eax ; 页表个数 mov ebx, 1024 ; 每个页表 1024 个 PTE mul ebx mov ecx, eax ; PTE个数 = 页表个数 * 1024 mov edi, PageTblBase ; 此段首地址为 PageTblBase xor eax, eax mov eax, PG_P | PG_USU | PG_RWW .2: stosd add eax, 4096 ; 每一页指向 4K 的空间 loop .2 mov eax, PageDirBase mov cr3, eax mov eax, cr0 or eax, 80000000h mov cr0, eax jmp short .3 .3: nop ret ; 分页机制启动完毕 ---------------------------------------------------------- ; InitKernel --------------------------------------------------------------------------------- ; 将 KERNEL.BIN 的内容经过整理对齐后放到新的位置 ; -------------------------------------------------------------------------------------------- InitKernel: ; 遍历每一个 Program Header,根据 Program Header 中的信息来确定把什么放进内存,放到什么位置,以及放多少。 xor esi, esi mov cx, word [BaseOfKernelFilePhyAddr + 2Ch]; 一共有几个Program Header放入cx movzx ecx, cx ; mov esi, [BaseOfKernelFilePhyAddr + 1Ch] ; Program Header相对ELF文件的偏移地址 add esi, BaseOfKernelFilePhyAddr ; 0x00080000+0x34,Program Header在内存中的偏移 .Begin: mov eax, [esi + 0] ;如果为0,那么直接换另一个Program Header cmp eax, 0 ; PT_NULL jz .NoAction push dword [esi + 010h] ; 把文件大小压入栈,作为第三个参数 mov eax, [esi + 04h] ; eax=0x00 add eax, BaseOfKernelFilePhyAddr ; add eax,00080000h push eax ; 源地址为00080000h压入栈,作为第二个参数 push dword [esi + 08h] ;把段的第一个字节在内存中的地址30000h压入栈,作为第一个参数 call MemCpy ; add esp, 12 ; .NoAction: add esi, 020h ; esi指向下一个Program Header Entry程序头目录 dec ecx jnz .Begin ret ; InitKernel ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ; SECTION .data1 之开始 --------------------------------------------------------------------------------------------- [SECTION .data1] ALIGN 32 LABEL_DATA: ; 实模式下使用这些符号 ; 字符串 _szMemChkTitle: db "BaseAddrL BaseAddrH LengthLow LengthHigh Type", 0Ah, 0 _szRAMSize: db "RAM size:", 0 _szReturn: db 0Ah, 0 ;0的意义是结束 ;; 变量 _dwMCRNumber: dd 0 ; Memory Check Result _dwDispPos: dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。 _dwMemSize: dd 0 _ARDStruct: ; Address Range Descriptor Structure _dwBaseAddrLow: dd 0 _dwBaseAddrHigh: dd 0 _dwLengthLow: dd 0 _dwLengthHigh: dd 0 _dwType: dd 0 _MemChkBuf: times 256 db 0 ; ;; 保护模式下使用这些符号 szMemChkTitle equ BaseOfLoaderPhyAddr + _szMemChkTitle szRAMSize equ BaseOfLoaderPhyAddr + _szRAMSize szReturn equ BaseOfLoaderPhyAddr + _szReturn dwDispPos equ BaseOfLoaderPhyAddr + _dwDispPos dwMemSize equ BaseOfLoaderPhyAddr + _dwMemSize dwMCRNumber equ BaseOfLoaderPhyAddr + _dwMCRNumber ARDStruct equ BaseOfLoaderPhyAddr + _ARDStruct dwBaseAddrLow equ BaseOfLoaderPhyAddr + _dwBaseAddrLow dwBaseAddrHigh equ BaseOfLoaderPhyAddr + _dwBaseAddrHigh dwLengthLow equ BaseOfLoaderPhyAddr + _dwLengthLow dwLengthHigh equ BaseOfLoaderPhyAddr + _dwLengthHigh dwType equ BaseOfLoaderPhyAddr + _dwType MemChkBuf equ BaseOfLoaderPhyAddr + _MemChkBuf ; 堆栈就在数据段的末尾 StackSpace: times 1000h db 0 TopOfStack equ BaseOfLoaderPhyAddr + $ ; 栈顶 ; SECTION .data1 之结束
/include/const.h
#ifndef _ORANGES_CONST_H_ #define _ORANGES_CONST_H_ /* EXTERN is defined as extern except in global.c */ #define EXTERN extern /* 函数类型 */ #define PUBLIC /* PUBLIC is the opposite of PRIVATE */ #define PRIVATE static /* PRIVATE x limits the scope of x */ /* GDT 和 IDT 中描述符的个数 */ #define GDT_SIZE 128 #define IDT_SIZE 256 /* 权限 */ #define PRIVILEGE_KRNL 0 #define PRIVILEGE_TASK 1 #define PRIVILEGE_USER 3 /* 8259A interrupt controller ports. */ #define INT_M_CTL 0x20 控制8259A的端口 #define INT_M_CTLMASK 0x21 #define INT_S_CTL 0xA0 #define INT_S_CTLMASK 0xA1 #endif /* _ORANGES_CONST_H_ */
#ifdef GLOBAL_VARIABLES_HERE #undef EXTERN #define EXTERN #endif EXTERN int disp_pos; //此处没有extern,是引用global.h中的。这个变量记录了显存gs:edi中的edi,为了连续显示打下基础 EXTERN u8 gdt_ptr[6]; /* 0~15:Limit 16~47:Base */ EXTERN DESCRIPTOR gdt[GDT_SIZE]; EXTERN u8 idt_ptr[6]; /* 0~15:Limit 16~47:Base */ EXTERN GATE idt[IDT_SIZE];
#ifndef _ORANGES_PROTECT_H_ #define _ORANGES_PROTECT_H_ /* 存储段描述符/系统段描述符 */ typedef struct s_descriptor /* 共 8 个字节 */ { u16 limit_low; /* Limit */ u16 base_low; /* Base */ u8 base_mid; /* Base */ u8 attr1; /* P(1) DPL(2) DT(1) TYPE(4) */ u8 limit_high_attr2; /* G(1) D(1) 0(1) AVL(1) LimitHigh(4) */ u8 base_high; /* Base */ }DESCRIPTOR; /* 门描述符 */ typedef struct s_gate //按字节排放顺序写的 { u16 offset_low; /* Offset Low */ u16 selector; /* Selector */ u8 dcount; /* 该字段只在调用门描述符中有效。如果在利用 调用门调用子程序时引起特权级的转换和堆栈 的改变,需要将外层堆栈中的参数复制到内层 堆栈。该双字计数字段就是用于说明这种情况 发生时,要复制的双字参数的数量。*/ u8 attr; /* P(1) DPL(2) DT(1) TYPE(4) */ u16 offset_high; /* Offset High */ }GATE; /* GDT */ /* 描述符索引 */ #define INDEX_DUMMY 0 // ┓ #define INDEX_FLAT_C 1 // ┣ LOADER 里面已经确定了的. #define INDEX_FLAT_RW 2 // ┃ #define INDEX_VIDEO 3 // ┛ /* 选择子 */ #define SELECTOR_DUMMY 0 // ┓ #define SELECTOR_FLAT_C 0x08 // ┣ LOADER 里面已经确定了的. #define SELECTOR_FLAT_RW 0x10 // ┃ #define SELECTOR_VIDEO (0x18+3) // ┛<-- RPL=3 #define SELECTOR_KERNEL_CS SELECTOR_FLAT_C #define SELECTOR_KERNEL_DS SELECTOR_FLAT_RW /* 描述符类型值说明 */ #define DA_32 0x4000 /* 32 位段 */ #define DA_LIMIT_4K 0x8000 /* 段界限粒度为 4K 字节 */ #define DA_DPL0 0x00 /* DPL = 0 */ #define DA_DPL1 0x20 /* DPL = 1 */ #define DA_DPL2 0x40 /* DPL = 2 */ #define DA_DPL3 0x60 /* DPL = 3 */ /* 存储段描述符类型值说明 */ #define DA_DR 0x90 /* 存在的只读数据段类型值 */ #define DA_DRW 0x92 /* 存在的可读写数据段属性值 */ #define DA_DRWA 0x93 /* 存在的已访问可读写数据段类型值 */ #define DA_C 0x98 /* 存在的只执行代码段属性值 */ #define DA_CR 0x9A /* 存在的可执行可读代码段属性值 */ #define DA_CCO 0x9C /* 存在的只执行一致代码段属性值 */ #define DA_CCOR 0x9E /* 存在的可执行可读一致代码段属性值 */ /* 系统段描述符类型值说明 */ #define DA_LDT 0x82 /* 局部描述符表段类型值 */ #define DA_TaskGate 0x85 /* 任务门类型值 */ #define DA_386TSS 0x89 /* 可用 386 任务状态段类型值 */ #define DA_386CGate 0x8C /* 386 调用门类型值 */ #define DA_386IGate 0x8E /* 386 中断门类型值 */ #define DA_386TGate 0x8F /* 386 陷阱门类型值 */ /* 中断向量 */ #define INT_VECTOR_DIVIDE 0x0 #define INT_VECTOR_DEBUG 0x1 #define INT_VECTOR_NMI 0x2 #define INT_VECTOR_BREAKPOINT 0x3 #define INT_VECTOR_OVERFLOW 0x4 #define INT_VECTOR_BOUNDS 0x5 #define INT_VECTOR_INVAL_OP 0x6 #define INT_VECTOR_COPROC_NOT 0x7 #define INT_VECTOR_DOUBLE_FAULT 0x8 #define INT_VECTOR_COPROC_SEG 0x9 #define INT_VECTOR_INVAL_TSS 0xA #define INT_VECTOR_SEG_NOT 0xB #define INT_VECTOR_STACK_FAULT 0xC #define INT_VECTOR_PROTECTION 0xD #define INT_VECTOR_PAGE_FAULT 0xE #define INT_VECTOR_COPROC_ERR 0x10 /* 中断向量 */ #define INT_VECTOR_IRQ0 0x20 #define INT_VECTOR_IRQ8 0x28 #endif /* _ORANGES_PROTECT_H_ */
PUBLIC void out_byte(u16 port, u8 value); //把用于C语言引用的.c和汇编中的global的,还有常量,变量(用extern)都放在.h文件夹中 PUBLIC u8 in_byte(u16 port); PUBLIC void disp_str(char * info); PUBLIC void disp_color_str(char * info, int color); PUBLIC void init_prot(); PUBLIC void init_8259A();
PUBLIC void* memcpy(void* p_dst, void* p_src, int size); ////把用于C语言引用的.c和汇编中的global的,还有常量,变量(用extern)都放在.h文件夹中
/include/type.h
#ifndef _ORANGES_TYPE_H_ #define _ORANGES_TYPE_H_ typedef unsigned int u32;32位的数据 typedef unsigned short u16;16位的数据 typedef unsigned char u8;一个字节的数据 typedef void (*int_handler) ();//定义了一个函数指针,具体细节不追究 #endif /* _ORANGES_TYPE_H_ */
/kernel/global.c
#define GLOBAL_VARIABLES_HERE #include "type.h" #include "const.h" #include "protect.h" #include "proto.h" #include "global.h" //此处定义了gdt ldt那些变量
#include "type.h" #include "const.h" #include "protect.h" #include "proto.h" /*======================================================================* init_8259A *======================================================================*/ PUBLIC void init_8259A() //初始化8259A,并打开键盘中断 { /* Master 8259, ICW1. */ out_byte(INT_M_CTL, 0x11); /* Slave 8259, ICW1. */ out_byte(INT_S_CTL, 0x11); /* Master 8259, ICW2. 设置 '主8259' 的中断入口地址为 0x20. */ out_byte(INT_M_CTLMASK, INT_VECTOR_IRQ0); /* Slave 8259, ICW2. 设置 '从8259' 的中断入口地址为 0x28 */ out_byte(INT_S_CTLMASK, INT_VECTOR_IRQ8); /* Master 8259, ICW3. IR2 对应 '从8259'. */ out_byte(INT_M_CTLMASK, 0x4); /* Slave 8259, ICW3. 对应 '主8259' 的 IR2. */ out_byte(INT_S_CTLMASK, 0x2); /* Master 8259, ICW4. */ out_byte(INT_M_CTLMASK, 0x1); /* Slave 8259, ICW4. */ out_byte(INT_S_CTLMASK, 0x1); /* Master 8259, OCW1. */ out_byte(INT_M_CTLMASK, 0xFD);//打开键盘中断 /* Slave 8259, OCW1. */ out_byte(INT_S_CTLMASK, 0xFF); } /*======================================================================* spurious_irq *======================================================================*/ PUBLIC void spurious_irq(int irq)//键盘中断处理函数 { disp_str("spurious_irq: "); disp_int(irq); disp_str("\n"); }
; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ; kernel.asm ; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ; Forrest Yu, 2005 ; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ SELECTOR_KERNEL_CS equ 8 ; 导入函数 extern cstart extern exception_handler;硬件中断处理函数 extern spurious_irq;异常处理函数 ; 导入全局变量 extern gdt_ptr extern idt_ptr extern disp_pos [SECTION .bss] StackSpace resb 2 * 1024 StackTop: ; 栈顶 30400+StackTop [section .text] ; 代码在此 global _start ; 导出 _start global divide_error global single_step_exception global nmi global breakpoint_exception global overflow global bounds_check global inval_opcode global copr_not_available global double_fault global copr_seg_overrun global inval_tss global segment_not_present global stack_exception global general_protection global page_fault global copr_error global hwint00 global hwint01 global hwint02 global hwint03 global hwint04 global hwint05 global hwint06 global hwint07 global hwint08 global hwint09 global hwint10 global hwint11 global hwint12 global hwint13 global hwint14 global hwint15 _start: ; 把 esp 从 LOADER 挪到 KERNEL mov esp, StackTop ; 堆栈在 bss 段中 mov dword [disp_pos], 0 ;刚开始gs:edi为0 sgdt [gdt_ptr] ; 把当前gdt的基地址和界限放入gdt_ptr中 call cstart ; 在此函数中改变了gdt_ptr,让它指向新的GDT lgdt [gdt_ptr] ; 把新的gdt的基地址和界限放到gdtr寄存器中 lidt [idt_ptr] ;把新的ldt的基地址和界限放到ldtr寄存器中 jmp SELECTOR_KERNEL_CS:csinit ;csinit地址为30400+偏移,SELECTOR_KERNEL_CS=8,选择子为8,对应的描述符基地址是0 csinit: sti hlt ; 中断和异常 -- 硬件中断 ; --------------------------------- %macro hwint_master 1 push %1 call spurious_irq add esp, 4 hlt %endmacro ; --------------------------------- ALIGN 16 hwint00: ; Interrupt routine for irq 0 (the clock). hwint_master 0 ALIGN 16 hwint01: ; Interrupt routine for irq 1 (keyboard) hwint_master 1 ALIGN 16 hwint02: ; Interrupt routine for irq 2 (cascade!) hwint_master 2 ALIGN 16 hwint03: ; Interrupt routine for irq 3 (second serial) hwint_master 3 ALIGN 16 hwint04: ; Interrupt routine for irq 4 (first serial) hwint_master 4 ALIGN 16 hwint05: ; Interrupt routine for irq 5 (XT winchester) hwint_master 5 ALIGN 16 hwint06: ; Interrupt routine for irq 6 (floppy) hwint_master 6 ALIGN 16 hwint07: ; Interrupt routine for irq 7 (printer) hwint_master 7 ; --------------------------------- %macro hwint_slave 1 ;外部中断没有错误码 push %1 call spurious_irq add esp, 4 hlt %endmacro ; --------------------------------- ALIGN 16 hwint08: ; Interrupt routine for irq 8 (realtime clock). hwint_slave 8 ALIGN 16 hwint09: ; Interrupt routine for irq 9 (irq 2 redirected) hwint_slave 9 ALIGN 16 hwint10: ; Interrupt routine for irq 10 hwint_slave 10 ALIGN 16 hwint11: ; Interrupt routine for irq 11 hwint_slave 11 ALIGN 16 hwint12: ; Interrupt routine for irq 12 hwint_slave 12 ALIGN 16 hwint13: ; Interrupt routine for irq 13 (FPU exception) hwint_slave 13 ALIGN 16 hwint14: ; Interrupt routine for irq 14 (AT winchester) hwint_slave 14 ALIGN 16 hwint15: ; Interrupt routine for irq 15 hwint_slave 15 ;中断或异常发生时eflags,cs,eip已经被压入堆栈,如果有错误码的话,错误码已经被压栈, ;所以我们的整体思想是,如果有错误码,直接把向量号压栈,然后再执行一个函数, ;如果没有错误码,则先压入一个0xFFFFFFFF,再把向量号压栈,再执行函数。 ; 中断和异常 -- 异常 divide_error: push 0xFFFFFFFF ; no err code push 0 ; vector_no = 0 jmp exception single_step_exception: push 0xFFFFFFFF ; no err code push 1 ; vector_no = 1 jmp exception nmi: push 0xFFFFFFFF ; no err code push 2 ; vector_no = 2 jmp exception breakpoint_exception: push 0xFFFFFFFF ; no err code push 3 ; vector_no = 3 jmp exception overflow: push 0xFFFFFFFF ; no err code push 4 ; vector_no = 4 jmp exception bounds_check: push 0xFFFFFFFF ; no err code push 5 ; vector_no = 5 jmp exception inval_opcode: push 0xFFFFFFFF ; no err code push 6 ; vector_no = 6 jmp exception copr_not_available: push 0xFFFFFFFF ; no err code push 7 ; vector_no = 7 jmp exception double_fault: push 8 ; vector_no = 8 jmp exception copr_seg_overrun: push 0xFFFFFFFF ; no err code push 9 ; vector_no = 9 jmp exception inval_tss: push 10 ; vector_no = A jmp exception segment_not_present: push 11 ; vector_no = B jmp exception stack_exception: push 12 ; vector_no = C jmp exception general_protection: push 13 ; vector_no = D jmp exception page_fault: push 14 ; vector_no = E jmp exception copr_error: push 0xFFFFFFFF ; no err code push 16 ; vector_no = 10h jmp exception exception: call exception_handler add esp, 4*2 ; 让栈顶指向 EIP,堆栈中从顶向下依次是:EIP、CS、EFLAGS ;iretd返回前应该是这个样子 hlt
#include "type.h" #include "const.h" #include "protect.h" #include "global.h" #include "proto.h" /* 本文件内函数声明 */ PRIVATE void init_idt_desc(unsigned char vector, u8 desc_type, int_handler handler, unsigned char privilege); /* 中断处理函数 */ void divide_error(); void single_step_exception(); void nmi(); void breakpoint_exception(); void overflow(); void bounds_check(); void inval_opcode(); void copr_not_available(); void double_fault(); void copr_seg_overrun(); void inval_tss(); void segment_not_present(); void stack_exception(); void general_protection(); void page_fault(); void copr_error(); void hwint00(); void hwint01(); void hwint02(); void hwint03(); void hwint04(); void hwint05(); void hwint06(); void hwint07(); void hwint08(); void hwint09(); void hwint10(); void hwint11(); void hwint12(); void hwint13(); void hwint14(); void hwint15(); /*======================================================================* init_prot *======================================================================*/ PUBLIC void init_prot() { init_8259A(); // 全部初始化成中断门(没有陷阱门) init_idt_desc(INT_VECTOR_DIVIDE, DA_386IGate, divide_error, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_DEBUG, DA_386IGate, single_step_exception, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_NMI, DA_386IGate, nmi, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_BREAKPOINT, DA_386IGate, breakpoint_exception, PRIVILEGE_USER); init_idt_desc(INT_VECTOR_OVERFLOW, DA_386IGate, overflow, PRIVILEGE_USER); init_idt_desc(INT_VECTOR_BOUNDS, DA_386IGate, bounds_check, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_INVAL_OP, DA_386IGate, inval_opcode, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_COPROC_NOT, DA_386IGate, copr_not_available, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_DOUBLE_FAULT, DA_386IGate, double_fault, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_COPROC_SEG, DA_386IGate, copr_seg_overrun, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_INVAL_TSS, DA_386IGate, inval_tss, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_SEG_NOT, DA_386IGate, segment_not_present, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_STACK_FAULT, DA_386IGate, stack_exception, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_PROTECTION, DA_386IGate, general_protection, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_PAGE_FAULT, DA_386IGate, page_fault, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_COPROC_ERR, DA_386IGate, copr_error, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 0, DA_386IGate, hwint00, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 1, DA_386IGate, hwint01, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 2, DA_386IGate, hwint02, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 3, DA_386IGate, hwint03, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 4, DA_386IGate, hwint04, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 5, DA_386IGate, hwint05, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 6, DA_386IGate, hwint06, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ0 + 7, DA_386IGate, hwint07, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 0, DA_386IGate, hwint08, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 1, DA_386IGate, hwint09, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 2, DA_386IGate, hwint10, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 3, DA_386IGate, hwint11, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 4, DA_386IGate, hwint12, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 5, DA_386IGate, hwint13, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 6, DA_386IGate, hwint14, PRIVILEGE_KRNL); init_idt_desc(INT_VECTOR_IRQ8 + 7, DA_386IGate, hwint15, PRIVILEGE_KRNL); } /*======================================================================* init_idt_desc *----------------------------------------------------------------------* 初始化 386 中断门 *======================================================================*/ PRIVATE void init_idt_desc(unsigned char vector, u8 desc_type, int_handler handler, unsigned char privilege) // { GATE * p_gate = &idt[vector]; u32 base = (u32)handler; p_gate->offset_low = base & 0xFFFF; p_gate->selector = SELECTOR_KERNEL_CS; p_gate->dcount = 0; p_gate->attr = desc_type | (privilege << 5); p_gate->offset_high = (base >> 16) & 0xFFFF; } /*======================================================================* exception_handler *----------------------------------------------------------------------* 异常处理 *======================================================================*/ PUBLIC void exception_handler(int vec_no,int err_code,int eip,int cs,int eflags)//先入栈的参数放后面 { int i; int text_color = 0x74; /* 灰底红字 */ char * err_msg[] = {"#DE Divide Error", "#DB RESERVED", "— NMI Interrupt", "#BP Breakpoint", "#OF Overflow", "#BR BOUND Range Exceeded", "#UD Invalid Opcode (Undefined Opcode)", "#NM Device Not Available (No Math Coprocessor)", "#DF Double Fault", " Coprocessor Segment Overrun (reserved)", "#TS Invalid TSS", "#NP Segment Not Present", "#SS Stack-Segment Fault", "#GP General Protection", "#PF Page Fault", "— (Intel reserved. Do not use.)", "#MF x87 FPU Floating-Point Error (Math Fault)", "#AC Alignment Check", "#MC Machine Check", "#XF SIMD Floating-Point Exception" }; /* 通过打印空格的方式清空屏幕的前五行,并把 disp_pos 清零 */ disp_pos = 0; for(i=0;i<80*5;i++){ disp_str(" "); } disp_pos = 0; disp_color_str("Exception! --> ", text_color); disp_color_str(err_msg[vec_no], text_color); disp_color_str("\n\n", text_color); disp_color_str("EFLAGS:", text_color); disp_int(eflags); disp_color_str("CS:", text_color); disp_int(cs); disp_color_str("EIP:", text_color); disp_int(eip); if(err_code != 0xFFFFFFFF){ disp_color_str("Error code:", text_color); disp_int(err_code); } }
/kernel/start.c
#include "type.h" #include "const.h" #include "protect.h" #include "proto.h" #include "string.h" #include "global.h" /*======================================================================* cstart *======================================================================*/ PUBLIC void cstart() //gdt ldt 都是extern引用global.c中 { disp_str("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n" "-----\"cstart\" begins-----\n"); /* 将 LOADER 中的 GDT的内容 复制到新的 GDT的内容 中 */ memcpy(&gdt, /* New GDT */ (void*)(*((u32*)(&gdt_ptr[2]))), //目的是取出32位的数据 /* Base of Old GDT */ *((u16*)(&gdt_ptr[0])) + 1 /*目的是取出32位的数据 Limit of Old GDT */ ); /* gdt_ptr[6] 共 6 个字节:0~15:Limit 16~47:Base。用作 sgdt/lgdt 的参数。*/ //把新gdt的基地址和界限赋给gdt_ptr中 u16* p_gdt_limit = (u16*)(&gdt_ptr[0]);//目的是取出16位的数据 u32* p_gdt_base = (u32*)(&gdt_ptr[2]);//目的是取出32位的数据 *p_gdt_limit = GDT_SIZE * sizeof(DESCRIPTOR) - 1; *p_gdt_base = &gdt; /* idt_ptr[6] 共 6 个字节:0~15:Limit 16~47:Base。用作 sidt/lidt 的参数。*/ //把新ldt的基地址和界限赋给ldt_ptr中,此时ldt还为空 u16* p_idt_limit = (u16*)(&idt_ptr[0]); u32* p_idt_base = (u32*)(&idt_ptr[2]); *p_idt_limit = IDT_SIZE * sizeof(GATE) - 1; *p_idt_base = &idt; init_prot(); disp_str("-----\"cstart\" ends-----\n"); }
#include "type.h" #include "const.h" #include "protect.h" #include "proto.h" #include "string.h" #include "global.h" /*======================================================================* itoa *======================================================================*/ /* 数字前面的 0 不被显示出来, 比如 0000B800 被显示成 B800 */ PUBLIC char * itoa(char * str, int num) { char * p = str; char ch; int i; int flag = 0; *p++ = '0'; *p++ = 'x';//0000B800 被显示成 0xB800 if(num == 0){ //如果要显示的数字为0 *p++ = '0'; } else{ for(i=28;i>=0;i-=4){ //一共8次循环,每次检查4位 ch = (num >> i) & 0xF; if(flag || (ch > 0)){ //防止第一个非0数字前面的0被显示出来 flag = 1; ch += '0'; if(ch > '9'){ ch += 7;//此处看ascii码 } *p++ = ch; } } } *p = 0; return str; } /*======================================================================* disp_int *======================================================================*/ PUBLIC void disp_int(int input) { char output[16];//16个字节 itoa(output, input); disp_str(output); }
; 导入全局变量 extern disp_pos ;此变量为而来gs:di中的di 为了显存的连续显示 [SECTION .text] ; 导出函数 global disp_str global disp_color_str global out_byte global in_byte ; ======================================================================== ; void disp_str(char * pszInfo); ; ======================================================================== disp_str: push ebp mov ebp, esp mov esi, [ebp + 8] ; pszInfo mov edi, [disp_pos] mov ah, 0Fh .1: lodsb test al, al jz .2 cmp al, 0Ah ; 是回车吗? jnz .3 push eax mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop eax jmp .1 .3: mov [gs:edi], ax add edi, 2 jmp .1 .2: mov [disp_pos], edi pop ebp ret ; ======================================================================== ; void disp_color_str(char * info, int color); ; ======================================================================== disp_color_str: push ebp mov ebp, esp mov esi, [ebp + 8] ; pszInfo mov edi, [disp_pos] mov ah, [ebp + 12] ; color .1: lodsb test al, al jz .2 cmp al, 0Ah ; 是回车吗? jnz .3 push eax mov eax, edi mov bl, 160 div bl and eax, 0FFh inc eax mov bl, 160 mul bl mov edi, eax pop eax jmp .1 .3: mov [gs:edi], ax add edi, 2 jmp .1 .2: mov [disp_pos], edi pop ebp ret ; ======================================================================== ; void out_byte(u16 port, u8 value); ; ======================================================================== out_byte: ;对于256~65535的端口进行读写,端口号放在dx中 mov edx, [esp + 4] ; port mov al, [esp + 4 + 4] ; value out dx, al nop ; 一点延迟 nop ret ; ======================================================================== ; u8 in_byte(u16 port); ; ======================================================================== in_byte: mov edx, [esp + 4] ; port xor eax, eax in al, dx nop ; 一点延迟 nop ret
[SECTION .text] ; 导出函数 global memcpy ; ------------------------------------------------------------------------ ; void* memcpy(void* es:p_dst, void* ds:p_src, int size); ; ------------------------------------------------------------------------ memcpy: push ebp mov ebp, esp push esi push edi push ecx mov edi, [ebp + 8] ; Destination mov esi, [ebp + 12] ; Source mov ecx, [ebp + 16] ; Counter .1: cmp ecx, 0 ; 判断计数器 jz .2 ; 计数器为零时跳出 mov al, [ds:esi] ; ds和es对应的描述符基地址都是0 inc esi ; ┃ ; ┣ 逐字节移动 mov byte [es:edi], al ; ┃ inc edi ; ┛ dec ecx ; 计数器减一 jmp .1 ; 循环 .2: mov eax, [ebp + 8] ; 返回值 pop ecx pop edi pop esi mov esp, ebp pop ebp ret ; 函数结束,返回 ; memcpy 结束-------------------------------------------------------------