《一个操作系统的实现》笔记(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汇编语言》

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开发与安全

linux系统编程之文件与I/O(三):目录的操作

一、目录的访问 功能说明:打开一个目录 原型:DIR*  opendir(char *pathname); 返回值: 打开成功,返回一个目录指针 打开...

1665
来自专栏CaiRui

Shell-2-命令之乐

1.cat (1)基本用法 [root@cai tmp]# cat 1.txt 2.txt this is a test1 this is a test 2...

2305
来自专栏Kevin-ZhangCG

[ Java面试题 ]并发篇

1282
来自专栏醒者呆

Debug EOS:nodeos + mongo_db_plugin

nodeos开始运行前,要先使用项目的总CmakeList.txt配置,这里我配置了boost库的位置,如果你配置了boost的环境变量可以跳过这里。

3991
来自专栏菩提树下的杨过

struts2使用Convention Plugin在weblogic上以war包部署时,找不到Action的解决办法

环境: struts 2.3.16.3 + Convention Plugin 2.3.16.3 实现零配置 现象:以文件夹方式部署在weblogic(10.3...

23610
来自专栏大闲人柴毛毛

程序员必知的并发编程注意事项

获取单例对象需要保证线程安全,其中的方法也要保证线程安全。 单例对象会被多线程共享,因此要保证它是线程安全的,它其中的方法都要保证是线程安全的。 工具类、资源...

4035
来自专栏Linux驱动

2.制作第一个驱动程序

先讲解驱动框架,然后写出first_drv驱动程序,来打印一些信息 写出first_drv驱动程序需要以下几步: (1)写出驱动程序first_drv_open...

2005
来自专栏安恒网络空间安全讲武堂

writeup分享 | 近期做的比较好的web

0x01猫头鹰嘤嘤嘤 http://124.128.55.5:30829/index.php 首先分析一下功能,随便上传一张jpg图片上传,跳转到 http...

5818
来自专栏JAVA烂猪皮

JAVA多线程与并发学习总结

使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行;当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存...

731
来自专栏人工智能LeadAI

用Python使用C语言程序(Windows平台)

前言 在机器学习中,很多时候我们需要Python和C的混合编程,最重要的原因是为了性能效率的提升: 解释型语言一般比编译型语言慢,一般提高性能的有效做法是,先做...

5664

扫码关注云+社区

领取腾讯云代金券