第一个Hello,OS World操作系统
清泛原创
首先阐述下程序运行的基本原理:计算机CPU只执行二进制指令,我们使用的开发语言开发出的程序最终由相应的编译器编译为二进制指令,二进制中包含程序相关的数据、代码指令(用我们最常见的公式描述就是:程序=数据+算法)。CPU读取相应的指令、数据后开始执行,执行后的结果输出到外部设备,如屏幕、磁盘等。整个过程中,CPU发挥最为核心的作用,与其他设备一起完成程序的执行、输出。
OS本身也是程序,它的运行也是如此,开机后从指定地址处(0x7c00),开始执行指令。先看看本节例子最终运行效果:
代码如下:
;--------------------------------------------------------------
; 平凡OS(Pf OS) 一个最简单的OS
; Author : tsingfun.com
;--------------------------------------------------------------
; FAT12引导扇区
ORG 0x7c00 ;引导开始地址,固定的,主板BIOS决定,本条指令只是申明,不占字节(有兴趣的可以单独编译这条指令,然后查看二进制,文件0k)
JMP _START ;CPU执行的第一条指令,就是跳转到_START地址处(这里是标签,实际编译后_START是有一个相对地址的)
TIMES 3-($-$$) NOP ;NOP:一个机器周期。$:当前地址,$$:首地址。因为以上信息必须占3个字节,所以不足的部分用nop指令填充,
;具体nop占用几个字节请读者使用二进制查看工具自行验证。
DB "PFOSBEST" ; 标识(公司、品牌等)8个字节
DW 512 ; 每扇区字节数
DB 1 ; 每簇扇区数
DW 1 ; Boot内容占几个扇区
DB 2 ; 共有多少FAT表
DW 224 ; 根目录文件数最大值
DW 2880 ; 扇区总数
DB 0xf0 ; 介质描述符
DW 9 ; 每FAT扇区数
DW 18 ; 每磁道扇区数
DW 2 ; 磁头数(面数)
DD 0 ; 隐藏扇区数
DD 2880 ; 若上面“扇区总数”为0,则这个值记录扇区总数
DB 0,0,0x29 ; 中断13的驱动器号;未使用;拓展引导标记
DD 0xffffffff ; 卷序列号
DB "PFOS v1.0.0" ; 卷标(11个字节)
DB "FAT12 " ; 文件系统类型(8个字节)
;---------------------------------------------------------------------
; 448个字节,引导代码、数据及其他填充字符
TIMES 18 DB 0
_START:
MOV AX, 0 ;AX:累加寄存器,CPU内置的16位寄存器,最为常用,可以用于存储运行的中间结果,此处清零
MOV SI, MSG ;SI:(source index)源变址寄存器,常用来存储待操作的数据的首地址,此处指向数据区的字符串
_LOOP: ;循环指令开始
MOV AL, [SI] ;[]取地址中的内容,AL是AX的低8位,所以取8bit,即1字节,字符串的ASCII。
ADD SI, 1 ;字符串往后移动一个字节
CMP AL, 0 ;判断当前取出的ASCII是否是0,
JE _END ;JE:Equal则Jmp,等于零表示字符串显示完了,结束。
MOV AH, 0x0e ;调用系统10h中断显示ASCII字母,AH,BX指定显示模式及参数(详见:https://www.tsingfun.com/it/cpp/int_10h_instructions.html)
MOV BX, 15
INT 0x10
JMP _LOOP ;继续下一个字符的显示
_END:
JMP $ ;跳到当前的地址,当然就陷入无限循环啦,此处为了让程序停在此处。
;数据区,就是待输出的字符串信息
MSG DB 0x0a, "----------------------------------------------", 0x0d, 0x0a, \
"| Hello, OS World! |", 0x0d, 0x0a, \
"----------------------------------------------", 0x0d, 0x0a, 0x0
TIMES 510-($-$$) DB 0
DW 0xaa55 ; 结束标志
;----------------------------------------------------------------------
; FAT数据区
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
TIMES 4600 DB 0
DB 0xf0, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00
TIMES 1469432 DB 0
其中,主要的步骤代码中都有详尽的注释,如有任何问题,请移步至论坛《深入OS》板块发帖讨论。
编译执行过程:
打开dos窗口,进入源码所在目录,执行命令nasm boot.asm -o pfos.img:
同目录下生成一个"pfos.img"软盘映像文件,接下来只需要把它装载到虚拟机运行即可,当然有条件的话可以实际写入老式的软盘用真机运行,结果是一样的。
同目录下新建一个“pfos.bxrc” Bochs配置文件,内容如下:
#how much memory the emulated machine will have
megs:4
#filename of ROM images
romimage:file=$BXSHARE\BIOS-bochs-latest,address=Oxf0000
vgaromimage: file=$BXSHARE\VGABIOS-elpin-2.40
#what disk images will be used
floppya:1_44="pfos.IMG",status=inserted
#Choose the boot disk
boot:a
#where do we send log messages?
#log:bochsout.txt
双击“pfos.bxrc”启动Bochs运行即可启动我们自己写的os了。源码下载:hello os world.zip。
接下来解释一下运行原理:
首先,软盘大小是1.44M(这个是固定的),所以我们在程序中指定它为1,474,560 字节,除了程序本身的指令、数据外,不足的部分全部补零。
TIMES 1469432 DB 0 就是此处开始写1469432个字节的0。
软盘采用的是FAT12文件格式,我们现在的常见的文件格式有FAT32、NTFS、EXT3等,FAT12是早期的一种文件格式。文件格式是文件格式化存储的一种算法,比如我们要将一个文件存储到软盘(磁盘)上,有些人可能会想我直接从地址0开始存储,直到结束,那么文件名、文件大小、创建时间等其他信息怎么存?紧接着后面继续存储么?那该给各部分分配多少字节空间?先不说后续查找文件的效率,这种存储方法无章可循会完全失控,是不行的方案。
文件格式化算法就解决了此类问题,而且兼顾文件的高效率查找。基本原理就是给软盘(磁盘)分区:FAT区、目录区、数据区,存储文件时先存储文件基本信息到目录区,然后文件的数据按照一定格式存储到数据区,目录区中有数据区中文件数据的地址。
这里只简单介绍一下FAT12格式,后续篇章会深入解析每个字节代表的含义。
我们来看看我们生成的映像里面到底有什么东西?这时我们需要用到二进制查看工具WinHex,点此下载。
以上看到的是二进制静态代码,实际运行中各指令的地址都是动态变化的,下来一起借助Bochs的debug功能来一探究竟。
我们双击“pfos.bxrc”默认是以运行模式启动Bochs,实际上我们应该启动bochsdbg.exe,因此写个简单的批处理脚本启动它吧,如下:
@echo off
SET BXSHARE=C:\Program Files (x86)\Bochs-2.5
if %PROCESSOR_ARCHITECTURE% == x86 (
SET BXSHARE=C:\Program Files\Bochs-2.4.6
)
"%BXSHARE%"\bochsdbg -q -f "pfos.bxrc"
双击脚本,启动debug模式,如下:
Bochs常用的debug命令如下:
b 0x... 断点命令,指定地址处调试
info break 显示当前断点信息
c 继续执行
s 步入执行
n 单步执行
info cpu 查看cpu寄存器(可分别执行 r/fp/sreg/creg)
print-stack 打印堆栈
xp /长度 地址 显示地址处内容(xp:物理地址,x:线性地址)
u 起始地址 结束地址 反汇编一段代码
trace on 反汇编执行的每条指令
trace-reg on 每执行一条指令都打印一下cpu信息
exit 退出调试
大家有兴趣的话可以调试下,然后看看每步骤寄存器值的变化。
总结:本篇主要是让大家对操作系统有个整体概念上的认识,揭开os神秘的面纱,从底层调试到运行,每个过程都真真切切展现在大家面前。至于汇编指令、地址寻址暂时不懂的话,也不要紧,后续章节会继续作详细阐述,力求使大家在不断的运行、调试过程中逐渐熟悉并掌握汇编及计算机底层技术。
大家有任何疑问的,欢迎回复评论提问或
上一篇:Linux Glibc幽灵漏洞允许黑客远程获取系统权
下一篇:逆向工程——二进制炸弹(CSAPP Project)