《一个操作系统的实现》笔记(1)--NASM汇编语法和环境搭建

2018-06-08 11:37:31 浏览数 (1)


概述

实现一个基于Intel x86的32位操作系统。


环境搭建

Ubuntu虚拟机。

Ubuntu - 汇编编译器NASM - C编译器GCC - 软盘绝对扇区读写工具dd - qemu虚拟机 - Bochs模拟器 - 磁盘映像工具bximage

代码语言:javascript复制
$ sudo apt-get install build-essential nasm

这里的build-essential软件包中包含GCC和GNU Make。

一些常用指令

汇编命令

代码语言:javascript复制
$ nasm boot.asm -o boot.bin

反汇编命令

代码语言:javascript复制
$ ndisasmw -o 0x7c00 boot.bin >> disboot.asm

创建一个虚拟软盘或者硬盘

代码语言:javascript复制
$ bximage
//...

将引导扇区写进软盘

代码语言:javascript复制
$ dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc

运行一个系统镜像 用qemu虚拟机来启动之前做好的虚拟软盘

代码语言:javascript复制
$ qemu-system-x86_64 -fda a.img

配置Bochs模拟器 Bochs很强大,可以用来调试操作系统。 把内存、硬盘映像、软盘映像等信息写到bochsrc配置文件中 具体配置方法参考:Configuring Bochs for Debugging

代码语言:javascript复制
###############################################################
# Configuration file for Bochs
###############################################################

# how much memory the emulated machine will have
megs: 32

# filename of ROM images
romimage: file=$BXSHARE/BIOS-bochs-latest
vgaromimage: file=$BXSHARE/VGABIOS-lgpl-latest

# what disk images will be used
floppya: 1_44=a.img, status=inserted

# choose the boot disk.
boot: floppy

display_library:     x
#log: /dev/null
log: bochsout.txt

mouse: enabled=0
# enable key mapping, using US layout as default.
keyboard_mapping: enabled=1, map=$BXSHARE/keymaps/x11-pc-us.map

启动Bochs虚拟机

代码语言:javascript复制
$ bochs -f bochsrc

之后会出来一个交互界面,按c继续执行。

Bochs虚拟机调试方法

也可以在输入b 0x7c00之后继续执行,这样当引导扇区执行到这里时,我们就可以单步调试了,使用dump_cpu可以查看CPU寄存器,x /64xb [addr]查看某个内存地址处的内容,trace-reg on让Bochs每走一步都显示主要寄存器的值,n让代码向下走一步。 调试的指令跟GDB类似。 使用Bochs调试Linux kernel,在赵炯的《linux内核完全剖析》中也有介绍。


计算机的启动过程

当计算机电源被打开时,它会先进行加电自检(POST), 然后寻找启动盘,如果是选择从软盘启动,计算机就会检查软盘的0面0磁道1扇区,如果发现它以0xAA55结束(二进制的数据经常这样搞一个特殊标记,比如jpeg文件格式以0xFFD8作为图像数据的开始标记),则BIOS认为它是一个引导扇区。 一旦BIOS发现了引导扇区,就会将这512字节的内容装载到内存地址0000:7c00处,然后跳转到0000:7c00处将控制权彻底交给这段引导代码。控制权的意思就是ip指针移到这个地方,CPU开始执行这里的代码逻辑。到此为止,计算机不再由BIOS中固有的程序来控制,而变成操作系统的一部分来控制。


NASM汇编指令简介

每种类型的CPU都能理解它们自己的机器语言。机器语言里的指令是以字节形式在内存中储存的数字。 NASM汇编器帮我们完成了由汇编程序到机器指令的转换。

寄存器

8086 16位寄存器 通用寄存器(AX、BX、CX、DX,可以分成H和L两个8位的寄存器使用):多数使用在数据移动和算术指令中。 指针寄存器:SI和DI,也可以像通用寄存器一样使用,但不能分割使用。 BP和SP寄存器用来指向机器语言堆栈里的数据,被各自成为基址寄存器和堆栈指针寄存器。 CS、DS、SS、ES寄存器是段寄存器。它们指出程序不同部分所使用的内存。分别表示代码段、数据段、堆栈段和附加段。 指令指针段寄存器(IP)与CS寄存器一起使用来跟踪CPU下一条执行指令的地址。 FLAGS寄存器储存了前面指令执行结果的重要信息。 80386 32位寄存器 80386及以后的处理器扩展了寄存器。例如:16位AX寄存器扩展成 了32位。为了向后兼容,AX依然表示16位寄存器而EAX 用来表示扩展 的32位寄存器。AX是EAX 的低16位就像AL是AX(EAX)的低8位一样。但 是没有直接访问EAX 高16位的方法。其它的扩展寄存器是EBX,ECX,EDX, ESI 和EDI 。 许多其它类型的寄存器同样也扩展了。BP变成了EBP;SP 变成了ESP;FLAGS变 成了EFLAGSEFLAGS 而IP变成了EIP。 在80386里,段寄存器依然是16位的。这儿有两个新的段寄存器:FS和GS。 它们名字并不代表什么。它们是附加段寄存器(像ES一样)。

语法

类似于tag:这种方式表示对后面的地址做一个别名。 在NASM中,任何不被方括号括起来的标签或变量名都被认为是地址,访问标签中的内容必须使用[ ]。 一个简单的boot程序,开机后显示红色的”Hello,OS world!”

代码语言:javascript复制
    org 07c00h          ; 告诉编译器程序加载到7c00处
    mov ax, cs
    mov ds, ax
    mov es, ax
    call    DispStr         ; 调用显示字符串例程
    jmp $          ; 无限循环
DispStr:
    mov ax, BootMessage
    mov bp, ax          ; ES:BP = 串地址
    mov cx, 16          ; CX = 串长度
    mov ax, 01301h      ; AH = 13,  AL = 01h
    mov bx, 000ch       ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
    mov dl, 0
    int 10h         ; 10h 号中断
    ret
BootMessage:        db  "Hello, OS world!"
times   510-($-$$)   db  0   ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw  0xaa55              ; 结束标志

$表示当前行被汇编后的地址。 $$表示一个节(section)的开始处被汇编后的地址。 ($$-$)表示本行距离程序开始处的相对距离。

指示符

指示符是由汇编程序产生的而不是由CPU产生。它们通常用来要么指示 汇编程序做什么要么提示汇编程序什么。它们并不翻译成机器代码。 - 1、equ 指示符: 定义符号 symbol equ value - 2、�fine 指示符:定义宏常量 - 3、数据指示符:用在数据段中用来定义内存空间。 如L8 db "A" ;字节变量初始化成ASCII值A(65),使用变量L8来标记内存位置。

Big和Little Endian 表示法

不同的处理器在内存 里以不同的顺序储存多字节整形:big endian和little endian。 Big endian是 一种看起来更自然的方法。最大(也就是: 最高有效位)的字节首先被储 存,然后才是第二大的,依此类推。例如:双字00000004将被储存为四个字节00 00 00 04。IBM处理器都使用这 种big endian方法。 然而,基于Intel的处理器使用little endian方法,首先被储存是最小的有效字节。所以00000004在内存中储存为04 00 00 00。这种格式强制连入CPU而且不可能更改。 我们需要在下面这种情况下, 考虑这两种格式的区别: 1. 当二进制数据在不同的电脑上传输时(不管来自文件还是网络)。 2. 当二进制数据作为一个多字节整形写入到内存中然后当作单个单个字 节读出,反之亦然。 所有的内部的TCP/IP消息头都以big endian的格式来储存整形。(称为 网络字节续). TCP/IP 库提供了可移植处理Endian格式问题的方法的C函数。例如:htonl() 函数把一个双字(或长整形)从主机格式转换成了网络格 式。ntohl()函数执行一个相反的交换。对于一个big endian系统,这两个函数仅仅是无修改地返回它们的输入。这就允许你写出的网络程序可以在任何的Endian格式系统上成功编译和运行。


参考

《汇编语言–王爽著》 《PC汇编语言》

0 人点赞