《一个操作系统的实现》笔记(1)--NASM汇编语法和环境搭建
概述
实现一个基于Intel x86的32位操作系统。
环境搭建
Ubuntu虚拟机。
Ubuntu - 汇编编译器NASM - C编译器GCC - 软盘绝对扇区读写工具dd - qemu虚拟机 - Bochs模拟器 - 磁盘映像工具bximage
$ sudo apt-get install build-essential nasm
这里的build-essential软件包中包含GCC和GNU Make。
一些常用指令
汇编命令
$ nasm boot.asm -o boot.bin
反汇编命令
$ ndisasmw -o 0x7c00 boot.bin >> disboot.asm
创建一个虚拟软盘或者硬盘
$ bximage
//...
将引导扇区写进软盘
$ dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
运行一个系统镜像 用qemu虚拟机来启动之前做好的虚拟软盘
$ qemu-system-x86_64 -fda a.img
配置Bochs模拟器 Bochs很强大,可以用来调试操作系统。 把内存、硬盘映像、软盘映像等信息写到bochsrc配置文件中 具体配置方法参考:Configuring Bochs for Debugging
###############################################################
# 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虚拟机
$ 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!”
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、%define 指示符:定义宏常量
- 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汇编语言》
- Scala-2.13.0 安装及配置
- HBase-1.3.1 集群搭建
- CentOs7.3 Hadoop 用户 ssh 免密登录
- 手把手教你用Mysql-Cluster-7.5搭建数据库集群
- 简单的java开源图床
- 调度器Quartz的简述与使用总结
- 使用 RecyclerView 实现 Gallery 画廊效果,并控制 Item 停留位置
- linux chmod,chown命令详解
- Quartz任务调度快速入门
- ElasticSearch 安装报错整理
- Docker Compose 1.16.1 安装
- 教你如何用 RecyclerView 做一个好用的轮播图
- Docker-17.06.2 环境搭建
- 我所理解的Intent 和Intent-filter
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 剑指offer(25-30)题解
- 如何实时迁移MySQL到TcaplusDB
- 如何利用Terraform工具编排管理TcaplusDB
- 如何实时迁移AWS DynamoDB到TcaplusDB
- 腾讯云TcaplusDB基础能力介绍
- 游戏架构上云实战
- 【JUC】CyclicBarrier的了解和使用
- 完美解决-RuntimeError: CUDA error: device-side assert triggered
- springmvc之异常处理SimpleMappingExceptionResolver
- 剑指offer(13-15)题解
- 【leetCode】青蛙跳台问题(这只青蛙会托马斯大旋转)day07
- 【leetCode】斐波那契数列day06
- 剑指offer(61-67)题解
- 宇智波程序笔记8-【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!
- 情感分析数据预处理过程