《汇编语言:基于linux环境(第3版)》是风靡美国的经典汇编语言畅销书籍的最新版,美国计算机领域著名作者jeffduntemann的力作。作者以其渊博的专业知识,丰富的实战经验,结合生动详尽的实例,全面系统地介绍了linux环境下如何使用汇编语言进行程序设计以及与之有关的背景知识和相关工具的使用。本书写作风格独特,全书采用作者最有特色的对话式风格,结合大量源于生活的暗喻,将晦涩难懂的知识点条分缕析地呈现出来,以便读者能以轻松愉快的心情学习。
《汇编语言:基于linux环境(第3版)》适合刚涉足linux环境下汇编语言的读者,也可作为相关技术人员的参考书。
“你为什么要做那件事情?”
那是1985年,我和其他几个焦躁不安的媒体大腕一起,乘坐一辆出租车去纽约参加记者招待会。那时我刚刚开始媒体生涯(作为PC Tech杂志的技术编辑),我的第一本书还有几个月就要出版。我碰巧坐在一个已经成名的编程栏目的作者/大师旁边。我们谈论了很多这样那样的事情,他给我留下了深刻的印象。我不会说出他的名字,因为他已经在这个领域做了很多事情,如果他不把戒烟当成第一要务的话,肯定会作出更多的贡献。
但是,碰巧我是一个Turbo Pascal的狂热爱好者,我真正想做的事情是要学习如何编写能够使用全新的Microsoft Windows用户界面的Turbo Pascal程序。在谈到下面这个臭名昭著的问题之前,他皱了皱鼻子,做了个鬼脸苦笑道:
“你为什么想做那件事情呢?”
我以前从来没有听到过这个问题(尽管后来不止一次地听到它),所以有点猝不及防。为什么?因为,嗯,因为……我想知道它的工作原理。
“嗨! 那是C语言要做的事情。”
更深一层的讨论使我彻底对Pascal迷失了方向。但是通过一番寻根究底之后,我了解到,不能用Turbo Pascal编写Windows应用程序。这样做是不可能的。或者这个编程栏目作者/大师根本不知道怎么做,或者二者兼而有之。我不知道真相属于哪一种,但是我的确知道了那个臭名昭著的问题的含义。
各位看官,注意:当某些人问你“为什么你想做那件事情?”,它的真实含义是:“你在问我如何做一件或者通过使用我喜欢的工具无法实现,或者彻底位于我的经验之外的事情,但是我又不想承认这一点,以免丢脸。所以……如何和那些黑鹰队较量?”
多年以来,我一遍又一遍地听到这句话:
问:如何创建一个不用扫描就能读出它的长度的C字符串?
答:你为什么想做那件事情?
问:如何用汇编语言编写一个能够在Turbo Pascal中调用的子程序?
答:你为什么想做那件事情?
问:如何用汇编语言编写Windows应用程序?
答:你为什么想做那件事情?
你明白了吧。对于这个臭名昭著的问题的答案通常是一样的,如果哪个狡猾的人这样问你,你应该立即以最快的速度反驳道:因为我想知道它的工作原理。
这是一个很不充分的回答。我每次回答这个问题都用这个答案,但是有一次例外,那是很多年以前,我宣布要写一本教那些初次接触编程的人们如何用汇编语言来编写程序的书。
问:天哪,你为什么想做那件事情?
答:因为要想具备理解所有其他编程体系工作原理的本事,这是现有的最佳方法。
对于程序员而言,有一件事情高于一切,那就是理解事情的工作原理。而且,学习如何成为一名程序员的过程几乎就是一个掌握事情工作原理的过程。这可以在各个级别上完成,取决于你所使用的工具。如果你正在用Visual Basic编程,你必须理解某些事情的工作原理,但是这些事情总体来说局限于Visual Basic本身。Visual Basic在程序员和计算机之间放置了一个“层”,这个层隐藏了很多机制(同样的情况适用于Delphi、Java、Python以及很多其他的高级语言编程环境)。如果你在使用C编译器,那么你距离计算机更近了一些,你将看到更多的机制——而且必须看到,只有了解了这些机制的工作原理才能够正确地使用它。然而,即便如此,还有一些内容是隐藏的,哪怕是对于那些老练的C程序员而言。
反过来,如果你在使用汇编语言,那么你与计算机的距离就最大限度地缩短了。汇编语言什么都没有隐藏,而且也没扣留任何权利。当然,从另一方面来讲,由于在你和机器之间没有任何神奇的“层”来替你“照顾”某些事情,所以再无法为自己的无知找到任何托词。如果不知道事情的工作原理,那么你死定了——除非你拥有的知识够多,能自己把它弄明白。
我创作本书的目的不完全是为了教你汇编语言本身,这是关键所在。如果说本书有一个根本的指导思想,那就是:引入一种受过训练的对于机器的好奇心,以及一些基本的上下文,根据这些上下文,你能够从最底层开始探索计算机,并具备竭尽所能掌握它的信心。这是一件困难的事情,但是只要专注些,耐心些,没有什么你掌握不了的,而且只要有时间(我得提醒一下),可能相当长。
事实上,这里我真正想教给你的是如何学习。
你需要准备些什么
要想按照我教的方式编写程序,你需要一台运行Linux的基于Intel x86的计算机。本书的正文和示例都假设至少运行在386上,但是因为Linux本身至少需要386环境,所以你已经满足硬件要求了。
你需要熟悉使用Linux。我不能在本书中教你如何安装和运行Linux,不过在某些不太直观易懂的情况下,我会提供一些暗示。如果你对Linux环境还没有熟悉,我建议你找本教程,借助它来进行工作。这样的书很多,但是我最喜欢的是William von Hagen撰写的Ubuntu 8.10 Linux Bible(Linux for Dummies虽然不错,但是内容不够全面)。
到底使用哪个Linux版本并不是很重要,只要至少基于2.4版的内核即可,当然,2.6版更好一些。我用来编写本书示例程序的版本是Ubuntu 8.10。哪一种图形用户界面并不重要,因为所有的程序都被编写为运行在纯文本Linux控制台下。汇编程序本身,即NASM,也是纯文本。
我在讲解编程逻辑的时候以Kate编辑器为模型,它需要用到GUI。实际上你可以使用任何你想用的编辑器。并不是程序本身需要Kate编辑器,而是如果你刚刚开始接触编程,或者经常使用高级语言的特定编辑环境的话,Kate编辑器是一个很好的选择。
我在正文中引用的调试器是Gdb,但是大多数情况下是通过它的内置GUI前端——Insight来使用它。Insight需要一个有效的X Window子系统,但是并不绑定到某一特定的GUI系统上,如GNOME或KDE。
你不必事先知道如何安装和配置这些工具,因为我会在合适的时候,在相应的章节里涵盖所有必要工具的安装及配置。
注意,对于其他不是基于Linux内核的Unix实现而言,其功能的执行方式可能与基于Linux内核的不完全相同。例如在调用内核时,BSD Unix就使用不同的规则。其他一些Unix版本(如Solaris)我并不擅长。
学习计划
本书从基础开始讲起,我的意思是“零起点”。也许你已经入门,或者已略有心得也没关系。我对待起点问题非常慎重。我仍然相信,依次阅读本书各章并非浪费时间。复习很管用,嘿!你会发现,你了解的知识并不像想象中的那么多(我经常这样)。
但是,如果你的时间很不充裕,下面这个学习进度安排表可以让你偷点儿懒。
1.如果你已经理解了计算机编程的基本概念,可跳过第1章。
2.如果你已经理解了除了十进制之外的其他数制(尤其是十六进制和二进制)背后的思想,可跳过第2章。
3.如果你已经对计算机内部原理(内存、CPU结构等)有了一些了解,可跳过第3章。
4.如果你已经理解了x86内存寻址机制,可跳过第4章。
5.不。停!勾掉它(第4点)。即使你已经理解了x86内存寻址机制,也请阅读第4章。
强调一下,这里的第5点是出于这样的原因:汇编语言编程是关于内存寻址的编程。如果不理解内存寻址机制,学习所有其他关于汇编的知识将毫无用处。所以,不要跳过第4章,无论你了解了或者认为了解了其他知识与否。从这一章开始,一直通读到本书的结尾。加载每一个示例程序,汇编每一个示例程序,运行每一个示例程序。力求理解每一个程序中的每一行代码。对什么内容都不要无条件地相信。
而且,不仅限于此。当你理解了知识之后,修改那些示例程序。试着采用不同的方式完成它们。勇于尝试我没有提到的那些内容。大胆些。这样还不够,最好更疯狂一些——这些二进制位是没有感情的,可能发生的最糟糕的事情无非就是Linux抛出一个分段错误,它将会破坏你的程序(也许会伤害你的自尊),但是并不会破坏Linux(“保护模式”不是白叫的!)。唯一可能存在的困难是:当你在尝试某些知识的时候,要了解程序不工作的原因,而且要像你了解所有它做的其他事情一样的清晰。然后将其记录下来。
最后是我追求的目标:教给你一种方法,通过这个方法,你可以理解计算机的每一个组成部分正在做的事情,以及它们是如何协同工作的。这并不意味着我本人会讲解计算机的每一个组成部分——即使我活得再长,也做不到。计算过程已经不再像以前那样简单,但是如果具备了耐心研究和实验的品质,你很有可能自己把它完成。最后,学习的唯一方法是:一切要靠自己。不能否认,你能够找到一些指导性内容——来自朋友的,来自互联网的,来自像本书一样的书籍的,但那仅仅是指导,车轴上的润滑剂而已。你必须搞清楚谁才是真正的主人,是你还是机器,然后使其成为主人。只有汇编语言程序员才真正有资格声称是主人,这是一个值得思考的事实。
留意大写习惯
汇编语言是所有编程语言中最为独特的一种,它对于大小写的区分没有统一的标准。在C语言中,所有的标识符都是区分大小写的,但是,在汇编语言中,存在根本不区分大小写的汇编编译器。我在本书中用到的汇编编译器NASM,只对程序员定义的标识符区分大小写。但是,对于指令助记符以及寄存器名却不区分大小写。
在有关汇编语言的书籍中常有一些书写习惯,其中之一就是将CPU指令助记符和寄存器名在正文中大写,而在散布于正文之中的源代码文件和代码片段中小写。这里,我也遵循这个习惯。在讨论部分的正文里,我会说MOV、EAX寄存器、EFLAGS等。在示例代码中,我会用mov、eax及eflags表示它们。
这样做出于两个原因。
* 在正文中,助记符和寄存器需要醒目。因为在众多的一般文字之中,非常容易失去它们的线索。
* 为了阅读和学习本书之外的其他已有文档和源代码,你需要能够轻松地阅读汇编语言,无论它是大写的,小写的,还是大小写混合的。适应同一内容的不同表达方式,这一点非常重要。
这可能会激怒某些Unix界的人们,因为他们盲目崇拜小写字符。我事先为激怒他们这件事情而道歉,但是我仍然坚定地认为,那样做就是一种盲目崇拜,而且相当孩子气。
为什么我又到这了
无论你选择从什么地方开始学习本书,现在都是时间开始行动了。你只需记住,无论你面对的是什么,管它是什么黄鼠狼、机器,还是你自己的经验不足,脑海中首先要记住一点:你学习的目的是为了搞清楚它的工作原理。
让我们出发吧。
致谢
首先,我要感谢Wiley的Carol Long和Brain Herrmann,是他们允许这本书再版,并确保了再版的实现,而且比上次出版耗时更短。
对于所有三个版本,我都欠Michael Abrash 太多人情债,是他始终如一地在诸多方面为我提供非常明智的建议,尤其是那些Intel微体系结构之间的晦涩难解的区别。
尽管Randy Hyde、Frank Kotler、Beth和alt.lang.asm上的其余所有人没有意识到,但是他们其实已经通过各种方式为我提供了帮助,正是由于他们听取和回答来自汇编语言初学者的要求,才更有助于我决定哪些内容必须涵盖在像本书一样的书籍中,哪些不应该包含在里面。
最后,一如既往,向Carol致敬!是她40年如一日给予我绝对的支持和神圣的友谊,使我能够从事诸如本书之类的项目,并且将其进行到底。
作者简介
Jeff Duntemann,技术作家、编辑和讲师,同时也是一个出版业分析师。在他涉足技术领域的30年中,他曾经担任过施乐公司的程序员和系统分析员,Ziff-Davis出版公司的技术期刊编辑,Coriolis Group Books及后来的Paraglyph 杂志社的编辑部主任。他目前是一名技术出版顾问,同时拥有Copperwood出版社(lulu.com的按需印刷出版商)。Jeff与妻子Carol住在科罗拉多州斯普林斯市。
第1章 又一个令人愉快的星期六
1.1 一切尽在计划之中
1.2 如果这是真实情况
1.3 此路不通,请绕行
1.4 像棋盘游戏一样的汇编语言编程
第2章 外星基数
2.1 新数学怪物归来
2.2 在火星上计数
2.3 八进制:绿色精怪怎样偷走8和9的
2.4 十六进制:解决数字的短缺
2.5 从十六进制到十进制,从十进制到十六进制
2.6 十六进制运算
2.7 进制
2.8 进制简写方式:十六进制
第3章 摘下面具 第1章 又一个令人愉快的星期六
1.1 一切尽在计划之中
1.2 如果这是真实情况
1.3 此路不通,请绕行
1.4 像棋盘游戏一样的汇编语言编程
第2章 外星基数
2.1 新数学怪物归来
2.2 在火星上计数
2.3 八进制:绿色精怪怎样偷走8和9的
2.4 十六进制:解决数字的短缺
2.5 从十六进制到十进制,从十进制到十六进制
2.6 十六进制运算
2.7 进制
2.8 进制简写方式:十六进制
第3章 摘下面具
3.1 raxie,我们怎么不认识你
3.2 开关、晶体管和存储器
3.3 遵循计划行事的盒子
3.4 是什么vs.怎么做:体系结构和微体系结构
.3.5 工厂经理
第4章 位置,位置,位置
4.1 内存模式的乐趣
4.2 段的本质
4.3 16位和32位寄存器
4.4 三种主要的汇编编程模型
4.5 保护模式下不再允许我们做的事情
4.6 展望未来:64位“长模式”
第5章 汇编的权利
5.1 文件及其包含的内容
5.2 文本进去,代码出来
5.3 汇编语言开发过程
5.4 沿着汇编小路旅行
第6章 有地儿,有工具
6.1 kate编辑器
6.2 linux和终端
6.3 使用linux make
6.4 insight调试器
第7章 跟踪指令
7.1 为自己建立一个沙盒
7.2 cpu的标志位
7.3 有符号值和无符号值
7.4 隐式操作数和mul
7.5 阅读和使用汇编语言参考资料
7.6 neg:求补(求补码;即,与-1相乘)
第8章 我们的崇高目标
8.1 汇编语言程序的基本框架
8.2 通过堆栈实现后进先出
8.3 通过int80使用linux内核服务
8.4 设计一个有价值的程序
第9章 位、标志、分支和表
9.1 位就是二进制位(字节也是二进制位)
9.2 移位操作
9.3 位操作
9.4 标志、测试和分支
9.5 保护模式下内存寻址详解
9.6 字符表转换
9.7 用表来代替计算
第10章 分治
10.1 盒子里面的盒子
10.2 调用和返回
10.3 局部标号和跳转的长度
10.4 生成外部过程库
10.5 自定义过程的艺术
10.6 linux控制台下的简单光标控制
10.7 创建和使用宏
第11章 字符串奏鸣曲
11.1 汇编语言字符串的概念
11.2 rep stosb,软件机枪
11.3 半自动武器:不带rep的stosb
11.4 movsb:快速块拷贝
11.5 将数据存储到不连续的字符串中
11.6 命令行参数和堆栈检查
11.7 使用scasb进行字符串搜索
第12章 c语言
12.1 什么是gnu
12.2 连接到标准的c函数库
12.3 使用printf()格式化文本输出
12.4 使用fgets()和scanf()进行数据输入
12.5 驾驭时间
12.6 理解at&t指令助记符
12.7 产生随机数
12.8 c如何看待命令行参数
12.9 简单文件输入/输出
结论:不是结束,而是刚刚开始
附录a 部分x86指令集
附录b 字符集图