x86 汇编语言
从实模式到保护模式
李 忠 王晓波 余 洁 著
Publishing House of Electronics Industry
BEIJING
x86 汇编语言:从实模式到保护模式
内 容 简 介
每一种处理器都有它自己的机器指令集,而汇编语言的发明则是为了方便这些机器指令的记忆和书写。
尽管汇编语言已经较少用于大型软件程序的开发,但从学习者的角度来看,要想真正理解计算机的工作原理,
掌握它内部的运行机制,学习汇编语言是必不可少的。
这套图书分为两册,采用开源的 NASM 汇编语言编译器和 VirtualBox 虚拟机软件,以个人计算机广泛采
用的 Intel 处理器为基础,详细讲解了 Intel 处理器的指令系统和工作模式,以大量的代码演示了 16/32/64
位软件的开发方法。上册集中介绍处理器的 16 位实模式和 32 位保护模式,以及基本的指令系统;下册侧重于
介绍 64 位工作模式、多处理器管理、高速缓存控制、温度和电源管理、高级可编程中断控制器、多媒体支持
等。
这是一本有趣的书,它没有把篇幅花在计算一些枯燥的数学题上。相反,它教你如何直接控制硬件,在
不借助于 BIOS、DOS、Windows、Linux 或者任何其他软件支持的情况下来显示字符、读取硬盘数据、控制其
他硬件等。本书可作为大专院校相关专业学生和计算机编程爱好者的教程。
未经许可,不得以任何方式复制或抄袭本书之部分或全部内容。
版权所有,侵权必究。
图书在版编目(CIP)数据
/主编. —北京: 电子工业出版社,2012.9
ISBN 978-7-121-0-0
Ⅰ. ①汇… Ⅱ. ①… Ⅲ. ① Ⅳ. ①
中国版本图书馆 CIP 数据核字(2012)第 号
责任编辑:董亚峰
印
刷:
订:
装
出版发行:电子工业出版社
北京市海淀区万寿路 173 信箱 邮编 100036
开 本:787×1 092 1/16 印张: 字数: 千字
印 次:2012 年 9 月第 1 次印刷
定 价:00.00 元
凡所购买电子工业出版社图书有缺损问题,请向购买书店调换。若书店售缺,请与本社发行部联系,联系
及邮购电话:(010)88254888。
质量投诉请发邮件至 zlts@phei.com.cn,盗版侵权举报请发邮件至 dbqq@phei.com.cn。
服务热线:(010)88258888。
4
第 1 章 十六进制计数法
前
言
尽管汇编语言也是一种计算机语言,但却是与众不同的,与它的同类们格格不入。一方面,
处理器的工作是执行指令,用它所做的一切都是执行指令并获得结果;另一方面,汇编语言为
每一种指令提供了简单好记、易于书写的符号化表示形式。
一直以来,人们对于汇编语言的认识和评价可以分为两种,一种是觉得它非常简单,另一
种是觉得它学习起来非常困难。
你认为我会赞同哪一种?说汇编语言难学,这没有道理。学习任何一门计算机语言,都需
要一些数制和数制转换的知识,也需要大体上懂得计算机是怎么运作的。在这个前提下,汇编
语言是最贴近硬件实体的,也是最自然和最朴素的。最朴素的东西反而最难掌握,这实在说不
过去。因此,原因很可能出在我们的教科书上,那些一上来就搞一大堆寻址方式的书,往往以
最快的速度打败了本来激情高昂的初学者。
但是,说汇编语言好学,也同样有些荒谬。据我的观察,很多人掌握了若干计算机指令,
会编写一个从键盘输入数据,然后进行加减乘除或者归类排序的程序后,就认为自己掌握了汇
编语言。还有,直到现在,我还经常在网上看到学生们使用 DOS 中断编写程序,他们讨论的
也大多是实模式,而非 32 位或者 64 位保护模式。他们知道如何编译源程序,也知道在命令行
输入文件名,程序就能运行了,使用一个中断,就能显示字符。至于这期间发生了什么,程序
是如何加载到内存中的,又是怎么重定位的,似乎从来不关汇编语言的事。这样做的结果,就
是让人以为汇编语言不过如此,没有大用,而且非常枯燥。
很难说我已经掌握了汇编语言的要义。但至少我知道,尽管汇编语言不适合用来编写大型
程序,但它对于理解计算机原理很有帮助,特别是处理器的工作原理和运行机制。就算是为了
这个目的,也应该让汇编语言回归它的本位,那就是访问和控制硬件(包括处理器),而不仅
仅是编写程序,输入几个数字,找出正数有几个、负数有几个,大于 30 的有几个。
事实上,汇编语言对学习和理解高级语言,比如 C 语言,也有极大的帮助。老教授琢磨了
好几天,终于想到一个好的比喻来帮助学生理解什么是指针,实际上,这对于懂得汇编语言的
学生来说,根本就不算个事儿,并因此能够使老教授省下时间来喝茶。
对于一个国家来说,不能没有人来研究基础学科,尽管它们不能直接产生效益;而对于一
个人来说,也不能没有常识。尽管常识不能直接挣钱吃饭,但它影响谈吐,影响你的判断力和
决断力,决定着你接受新事物和新知识的程度。相应地,汇编语言就是计算机语言里的常识和
基础。
这是继《穿越计算机的迷雾》之后,我写的第二本书。这本书与上本书有两点不同,第一,
上一本花了 4 年才完成,而这本只用了一年,速度之快,令我自己咂舌;第二,上本书属于科
普性质,漫谈计算机原理,这本书就相对专业了。那些还想把我的书当小说看的人,这回要失
望了。
很多人可能会问我,为什么要写这样一本书。我只能说,我第一次学汇编的经历实在是太
深刻了。我第一次学汇编语言是在 1993 年,手中的教材不能说不好,但学习起来实在很吃力。
要知道,在那个年代,没有网络,要买到好书,还得到大武汉。就这样,我抱着两本书,反反
复复地看,直到半年之后才懂得汇编语言是个什么东西。后来,虽然有心写一本汇编语言的书,
一本不一样的汇编语言书,但始终没有时间和精力。
时间过得真快,转眼 20 年过去了。猛回头,我发现同学们依然在走我的老路,他们所用
5
x86 汇编语言:从实模式到保护模式
的教材,都还是我那个年代的,至少区别不大,都还在讲 8086 处理器的实模式。保护模式是
从哪个处理器开始引入的?当然是 80286。它是哪个年代的产品?1982 年!可是,直到现在,
市面上也找不到太多能够把保护模式讲得比较清楚的图书。
也许我应该做点什么。不,事实上,我已经做了,那就是你手中的这本图书。王晓波和湖
北经济学院的余洁共同参与了本书的创作。
在计划写这本书的时候,我就给自己画了几条线。首先不能走老路,一上来就讲指令、寻
址方式,采用任务驱动方式来写,每一章都要做点事情,最好是比较有趣,足够引起读者的事
情。在解决问题的过程中,引入一个个的指令,并进行讲解。一句话,我希望是润物细无声式
的。
其次,汇编语言和硬件并举,完全抛弃 BIOS 中断和 DOS 中断,直接访问硬件,发挥汇
编语言的长处。这样,读者才会深刻体会到汇编的妙处。
这套图书主要讲述 16 位实模式、32 位保护模式和 Intel-64 架构。引入虚拟 8086 模式是为
了兼容传统的 8086 程序,现在看来已经完全过时,不再进行讲述。至于增强的 32 位模式 IA-32e,
读者可以在读完这本书之后自学,也予以省略。
书中配套的程序清单和源代码以及可能用到的程序软件,感兴趣的读者可到电子工业出版
社华信教育资源网下载(待定)。
本书原来有 18 章,后来,考虑到实模式的内容过多,而去掉了一章。这一章的标题是《聆
听数字的声音》,讲述如何通过直接访问和控制 Sound Blaster 16 声卡来播放声音,对此感兴趣
的朋友可以在配书光盘中找到它。
特别感谢长春电视台的王志强台长和台长助理周武军,上本书《穿越计算机的迷雾》出版
后,台长王志强亲自过问出版情况,并给予我特别的奖励,希望大家同样能从这本书中读到他
们对我的关怀和鼓励;同时也要感谢我的母亲、我爱人和我的女儿,她们是我的精神支柱。好
友王南洋、桑国伟、刘维钊、蒋胜友、邱海龙、万利等负责了本书的一部分校对工作;好友周
卫平帮我验证配书代码是否能够在他的机器上正常工作;如果想调试本书中的程序,可以使用
bochs 软件,它的视频教程是由王南洋制作的,在这里向他们表示感谢。在阅读本书的过程中,
如果有任何问题,可以按以下电子邮件地址给我写信:leechung@126.com;或者进入我的博客
参与讨论。博客地址是
http://blog.163.com/leechung@126
6
第 1 章 十六进制计数法
目 录
第 1 部分 预备知识
第 1 章 十六进制计数法 ··························································································· 3
1.1 二进制计数法回顾 ·················································································· 3
1.1.1 关于二进制计数法 ··········································································· 3
1.1.2 二进制到十进制的转换 ····································································· 3
1.1.3 十进制到二进制的转换 ····································································· 4
1.2 十六进制计数法 ····················································································· 4
1.2.1 十六进制计数法的原理 ····································································· 4
1.2.2 十六进制到十进制的转换 ·································································· 5
1.2.3 十进制到十六进制的转换 ·································································· 6
1.3 为什么需要十六进制 ··············································································· 6
本章习题 ···································································································· 7
第 2 章 处理器、内存和指令 ···················································································· 8
2.1 最早的处理器 ························································································ 8
2.2 寄存器和算术逻辑部件 ············································································ 8
2.3 内存储器···························································································· 10
2.4 指令和指令集 ······················································································ 11
2.5 古老的 Intel 8086 处理器 ········································································ 13
2.5.1 8086 的通用寄存器 ········································································· 13
2.5.2 程序的重定位难题 ········································································· 14
2.5.3 内存分段机制 ··············································································· 17
2.5.4 8086 的内存分段机制 ······································································ 18
本章习题 ·································································································· 21
第 3 章 汇编语言和汇编软件 ···················································································22
3.1 汇编语言简介 ······················································································ 22
3.2 NASM 编译器 ····················································································· 24
3.2.1 从网上下载 NASM 安装程序 ···························································· 24
3.2.2 安装 NASM 编译器 ········································································ 25
3.2.3 下载配书源码和工具 ······································································ 26
3.2.4 用 Nasmide 体验代码的书写和编译过程 ·············································· 28
3.2.5 用 HexView 观察编译后的机器代码 ···················································· 29
本章习题 ·································································································· 30
7
x86 汇编语言:从实模式到保护模式
第 4 章 虚拟机的安装和使用 ········································································· 31
4.1 计算机的启动过程 ················································································ 31
4.1.1 如何将编译好的程序提交给处理器 ···················································· 31
4.1.2 计算机的加电和复位 ······································································ 31
4.1.3 基本输入输出系统 ········································································· 32
4.1.4 硬盘及其工作原理 ········································································· 33
4.1.5 一切从主引导扇区开始 ··································································· 35
4.2 创建和使用虚拟机 ················································································ 35
4.2.1 别害怕,虚拟机是软件 ··································································· 35
4.2.2 下载 Oracle VM VirtualBox ······························································· 36
4.2.3 安装 Oracle VM VirtualBox ······························································· 36
4.2.4 创建一台虚拟 PC ··········································································· 37
4.2.5 虚拟硬盘简介 ··············································································· 42
4.2.6 练习使用 FixVhdWr 工具向虚拟硬盘写数据 ········································· 43
第 2 部分 16 位处理器下的实模式
第 5 章 编写主引导扇区代码 ···················································································49
5.1 欢迎来到主引导扇区 ············································································· 49
5.2 注释 ································································································· 49
5.3 在屏幕上显示文字 ················································································ 50
5.3.1 显卡和显存 ·················································································· 50
5.3.2 初始化段寄存器 ············································································ 52
5.3.3 显存的访问和 ASCII 代码 ································································ 53
5.3.4 显示字符 ····················································································· 55
5.4 显示标号的汇编地址 ············································································· 56
5.4.1 标号 ··························································································· 56
5.4.2 如何显示十进制数字 ······································································ 60
5.4.3 在程序中声明并初始化数据 ····························································· 61
5.4.4 分解数的各个数位 ········································································· 61
5.4.5 显示分解出来的各个数位 ································································ 65
5.5 使程序进入无限循环状态 ······································································· 66
5.6 完成并编译主引导扇区代码 ···································································· 67
5.6.1 主引导扇区有效标志 ······································································ 67
5.6.2 代码的保存和编译 ········································································· 68
5.7 加载和运行主引导扇区代码 ···································································· 68
5.7.1 把编译后的指令写入主引导扇区 ······················································· 68
5.7.2 启动虚拟机观察运行结果 ································································ 70
5.7.3 程序的调试 ·················································································· 70
8
第 1 章 十六进制计数法
本章习题 ·································································································· 71
第 6 章 相同的功能,不同的代码 ············································································· 72
6.1 代码清单 6-1 ······················································································· 72
6.2 跳过非指令的数据区 ············································································· 72
6.3 在数据声明中使用字面值 ······································································· 72
6.4 段地址的初始化 ··················································································· 73
6.5 段之间的批量数据传送 ·········································································· 74
6.6 使用循环分解数位 ················································································ 75
6.7 计算机中的负数 ··················································································· 76
6.7.1 无符号数和有符号数 ······································································ 76
6.7.2 处理器视角中的数据类型 ································································ 80
6.8 数位的显示 ························································································· 82
6.9 其他标志位和条件转移指令 ···································································· 83
6.9.1 奇偶标志位 PF ·············································································· 83
6.9.2 进位标志 CF ················································································· 83
6.9.3 溢出标志 OF ················································································· 84
6.9.4 现有指令对标志位的影响 ································································ 84
6.9.5 条件转移指令 ··············································································· 85
6.10 NASM 编译器的$和$$标记 ··································································· 87
6.11 观察运行结果 ···················································································· 87
本章习题 ·································································································· 88
第 7 章 比高斯更快的计算 ·······················································································89
7.1 从 1 加到 100 的故事 ············································································· 89
7.2 代码清单 7-1 ······················································································· 89
7.3 显示字符串 ························································································· 89
7.4 计算 1 到 100 的累加和 ·········································································· 90
7.5 累加和各个数位的分解与显示 ································································· 90
7.5.1 堆栈和堆栈段的初始化 ··································································· 90
7.5.2 分解各个数位并压栈 ······································································ 92
7.5.3 出栈并显示各个数位 ······································································ 94
7.5.4 进一步认识堆栈 ············································································ 95
7.6 程序的编译和运行 ················································································ 96
7.7 8086 处理器的寻址方式 ········································································· 96
7.7.1 寄存器寻址 ·················································································· 96
7.7.2 立即寻址 ····················································································· 97
7.7.3 内存寻址 ····················································································· 97
本章习题 ································································································· 101
9