首页 > temp > 简明python教程 >
-
汇编语言程序设计学习笔记(第一遍学习)第3节:HLA高级汇编语言基础尝鲜
一20日学习汇编语言的感受
学习汇编已经20天啦!我已经学习完了王爽的汇编语言书籍,并做了全部的练习与实验,觉得这是本非常好的书籍,在学习的过程中因为记录笔记非常繁琐,因为每一步都要截图加说明,当时对汇编也有一定的畏惧感,所以我停下了笔记,重点去实践去啦!我现在开始第二次学习汇编语言了,现在对汇编有了一定的认识,所以要自信点了,现在笔记继续啦!我预计汇编是学7次,大家一起加油啊!
二 HLA高级汇编语言环境的搭建与设置
我的操作系统:WINDOWS7
需要下载的东西:MASM32:http://www.masm32.com/masmdl.htm
HLA:http://webster.cs.ucr.edu/
MASM32和HLA你可以随意安装在任何目录,最好不要安装在带空格的目录下.然后复制MASM32\bin目录下的ml.exe, ml.err, link.exe, mspdb50dll到hla目录下。然后把hla目录加入到系统的环境变量中就OK啦!然后编辑环境我们就随便选择1款,或者记事本就可以了。
三 HELLOWORLD的运行
以前我们学PYTHON啊,C啊第一个程序都是用的输出helloworld了!现在我们也用这个,嘿嘿,只要输出了它,总是感觉有一种淡淡的兴奋感!如果没有正确输出它,总是感觉有一种淡淡的哀伤!所以我们一定正确输出它了!
但是呢,我们现在要正确输出HELLOWORLD首先我们必须要熟悉HLA高级汇编语言的程序结构,不然我们就不知道我们怎么去编写啊。首先我们来看1下:
program pgmID; //这里的三处pgmID标识符是定义的程序名,它们必须要相同啊!
声明 //声明就是和C语言的差不多,就是声明一些常量,类型,变量,过程啊,和一些对象
begin pgmID;
语句 //语句就是那个语句了啊!
end pgmID;
//program,begin,end都是保留字啊,还要注意那个分号啊!
下面我们就可以写hellowrold了!
#include("stdlib.hhf")
begin helloworld;
stdout.put("Hello,world!!!", nl);
end helloworld;
我先来解释下这个程序啊!#include和以前我们学的C的#include一样的,就是复制粘贴stdlib.hhf中的内容,为什么要这样做啊,因为下面的stdout.put在那个stdlib.hhf里面啊!stdout.put就是高级汇编语言的格式化输出到标准输出设备的语句。后面的nl是一个常量,表示换行转义序列。
现在我们把这个程序代码保存为helloworld.hla然后我们在cmd中执行hla helloworld看看效果如何:
很棒吧!有1个警告,不知道怎么回事,以后应该会知道的了!
四 高级汇编的数据声明
我们先来看1种类型的声明:带符号整数类型,int8,int16,int32就分别对应8位,16位,32位的带符号证书类型,简单吧!我们来看看它的一般申明格式:
static i8: int8 := 8; i16: int16 := 1600; i32: int32 := -3200000; |
i8,i16,i32就是声明的变量的名字了,static代表,我们声明的是静态的,:=后面的表示初始值,也就是操作系统把程序载入存储器的时候会把这个初始值给变量。
高级汇编变量声明的实例与新的概念
现在呢!我知道了如何声明带符号的整型类型啦!那么高级汇编的stdout.put是不是和C语言的printf一样可以有多个参数呢,另外stdout.put会如何处理输出整型数值呢,还有如果我们要想输入怎么办呢有没有类C的scanf呢!还有关于高级汇编的注释怎么做的呢!我们来看看下面这个程序:
Program DemoVars; #include("stdlib.hhf") static InitDemo: int32 :=5; NotInitialized: int32; begin DemoVars; //输出带符号整型变量的值 stdout.put( "InitDemo's value is" , InitDemo, nl); //输入1个数值到变量中 stdout.put( "Enter an integer value:" ); stdin. get (NotInitialized); stdout.put( "You entered:" , NotInitialized, nl); end DemoVars; |
从这个例子我们得出结论并验证了我们刚才的猜测啦!首先,stdout.put是允许有多个参数的,如果制定了一个整数数值,那么输出的时候stdout.put会把这个值转换为它的字符串形式,stdin.get语句是从标准输入设备读入一个数值,把它转换为整型数,然后再把该数存储到为初始化的变量中。再则在高级汇编语言中和C C++一样用//表示注释。
五 高级汇编中的布尔值
在高级汇编中标准库为布尔对象提供了支持,我记得C中标准库都没有布尔的支持,嘿嘿!是不是突然感觉到高级汇编好高好高级啊!所以,我们就可以在高级汇编的程序中声明布尔变量,也可以使用布尔字面常量,所以在布尔表达式中可以使用布尔变量,并输出布尔变量的值啦!
聪明的我们一下子就知道布尔常量肯定是由true和false标识符表达的。true当然就是1来代表,false就是0来代表。HLA中是用的一个字节来表示布尔值的。我们来看下它的典型声明:
static BoolVar:boolean; HasClass:boolean := flase; IsClear:boolean := ture; |
嘿嘿!我突然发觉高级汇编的语法好酷啊!都没有括号!
六 高级汇编中的字符值
HLA高级汇编语言允许使用char数据类型来声明单字节的ASII字符对象,好棒啊!也可以使用单引号括起来的字面字符值来初始化字符变量,而且可以使用stdout.put来输出字符变量,还可以使用stdin.get过程调用读取字符变量,这里我忘记了说明下,在HLA标准库中不允许通过stdin.get来读取一个布尔值哦!下面我们来看看HLA中声明和初始化字符变量的方法:
static c: char ; LetterA: char := 'A' ; |
七 Intel 80X86处理器简介
到这里,我不得不告诉大家一个很残酷的现实啊!虽然我们之前已经写了几个小程序了,但是你发现没有,我们写的那些小程序要么是数据声明,要么是对HLA标准库的调用,一点汇编语言的影子都没有啊。唉!沮丧啊!不过也别急了!我们学习HLA高级汇编的目的就是让学习真正汇编语言更轻松啊,这个HLA的意义所在啊,那么我们现在开始了解下Intel 80x86处理器,因为不理解处理器的基本结果,机器指令将没有任何意义。我们之前不是了解过了嘛,在前几节笔记中,但是我这里要说明的是,现在我们所学习的方向是32位操作系统中的汇编语言。它对寄存器啊进行了扩展(Extended).
Intel系统的CPU啊一般都归为风诺依曼式机器,这种计算机系统主要包括3个主要模块:CPU 存储器和输入输出(I/O)。这3部分通过系统总线相连,其实我们之前已经知道啦!
Intel系列CPU提供了几个通用寄存器,其中包含八个32位寄存器:
EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP
哇!竟然有ESP,ESP不是日本著名的吉他制造商吗?!大家是不是觉得和我们第一,第二节笔记的寄存器名字不太一样啊,前面怎么多了1个E啊。其实这个E代表扩展的意思,该前缀将32位寄存器和八个16位的寄存器和8个八位的寄存器区分开来,但是遗憾的是啊,这些并非都是分离的寄存器,也就是说,80X86并未提供24个独立的寄存器,那么它是怎么办的呢,它是将16位寄存器重叠于32位寄存器之上的,同理,也将8位寄存器重叠于16位寄存器之上。我们来看个图:
大家一定要理解这一个图的意义啊,不然经常会犯错的,这个错误就是寄存器值的破坏。因为它们并不独立啊,修改一个寄存器可能会影响到其他三个寄存器的。
EFLAGS是一个32位寄存器,它是由几个1位的布尔值组成的。EFLAGS寄存器中的大多数要么是为核心模式函数所保留的,要么就是程序员所不感兴趣的。其中有8位是程序员编写汇编程序所必须要用到的,所以我们重点是关注这8位了啊!它们分别是溢出标志,方向比啊在,中断进制标志,符号标志,零标志,进位标志,奇偶标志还有辅助近卫标志,其中要特别注意的是:溢出标志,进位标志,符号标志和零标志这4个哦,为什么要重点注意这几个呢,说一个例子,比如根据这些标志的状态就可以测试出前一次计算的结果。在对于两个值进行比较以后,条件标志就会告诉你其中1个值是小于,等于还是大于另一个值的。我们现在通过一个图来理解EFLAGS的布局:
为什么寄存器如此重要呢?!因为在80X86CPU中所以的计算都和寄存器有关的。比如,将两个变量相加,再把它们的和存入第3个变量中,首先,必须先将其中一个变量装入一个寄存器中,并将第2个操作数和寄存器的值相加,然后将寄存器中的值存入目的的变量。在这个过程中,寄存器在每次计算机中都充当媒体。所以,汇编语言程序中寄存器是非常重要的。
为什么通过寄存器的这个通用是相对的?!因为有些寄存器是有特殊的目的的,比如ESP它就不能用于任何其他的目的,它只能是用于栈指针。EBP也是特殊目的的。但是你通过静心构造,也是可以运用的,但是通常不会这么做。还要记住,余下的寄存器在程序中不可以完全互换。
存储子系统
我们知道在32位系统中,可以寻址2的32次方个不同的存储单元,也就是4G。如果我们要问,什么是一个存储单元呢,80X86中最基础的存储单元就是1个字节,所以1个字节就是1个存储单元。内存是看不着也摸不着的,那么我们怎么去理解内存呢,怎么去理解内存才更合适呢,最好的理解方式就是把内存看作是字节线性数组。首字节的地址为0,最后1字节的地址为2的32次方-1。比如以下声明:
Memory: array [0..4294967295] of byte;
这样一来,我们为了执行Memory[125] = 0;就是CPU将数值0放在数据总线上,将地址125放在地址总线上,并设置写信号。我们如下图来表示:
如果是执行CPU:=Memory[125],则CPU会将地址125放到地址总线上,并设置读信号,然后从数据总线上读取数据的结果,这个过程可以用下图表示:
哦原来是这样做的,但是上面我们只是处理的单字节,要是遇到更大的东西,需要字或者双字怎么办啊,其实这个很简单了,使用连续的存储单元序列就可以了啊!是啊,我们就把这个对象的首地址做为这个对象的内存地址!而且大家发现没有,用4的整数倍的地址来对准四字节对象是最好的注意啊。因为如果该对象的地址为对象长度的整数倍,那么访问内存中的数据对象的效率最高啊。
理解内存与HLA变量之间的相关性
既然电脑是这样工作的,那么我们声明变量当然就必须自己去做入帮比那里和内存地址所关联的步骤啦!但是我们现在用的是HLA高级汇编语言哦!这些过程HLA已经帮我们做了,以后要用这些变量就通过变量的名称来引用它们啦!但是,我们一定要明白HLA其实后台帮我们做了很多工作哦。这样的话,我们以后写真正的汇编就知道具体该如何做了。HLA可以帮我们更容易的去学汇编的!
八 基本的机器指令
学到机器指令啦!大家肯定都觉得很难了,其实大多数汇编语言程序中可能用到的机器指令就30来个了。我先来看1条C语句:
destination_operand = source_operand;
这个语句在汇编中如何用指令去直线的啊,我们从C的角度知道这个是1个赋值语句啊,也就是说,把数据从1个位置移动到另一个位置(应该说成复制好点,因为并不会破坏原数据),该指令的HLA语法如下:
mov ( source_operand, destination_operand );
大家要注意:source_operand可以是一个寄存器,内存变量或者一个常量。destination_operand可以是一个寄存器或者内存变量。从技术角度上说,80X86是不允许两个操作数都为内存变量的哦!一定要记住哦,但是HLA会自动将一条两字或者双字的内存操作数的MOV指令翻译为一对指令的。所以在HLA中,对mov指令操作数的主要限制是它们必须具有相同的长度。也就是说,你可以在两个8位,16位,32位对象之间移动数据。但是2个操作数的长度必须相同。而且HLA中的mov指令允许的将两个存储器操作数指定为源操作数和目的操作数的这种特殊的翻译方式只适用于mov,不能推广到其他的指令哦!
然后我们知道还应该有2个最基础的指令就是相加和相减,80X86中用add和sub指令表达它们。语法和mov指令是一样的,比如分别将两个操作数相加或者相减代码实例:
add ( source_operand, destination_operand );
sub ( source_operand, destination_operand );
add指令完成了以下操作:
destination_operand = destination_operand + source_operand;
destination_operand += source_operand;
sub指令完成了以下运算:
destination_operand = destination_operand - source_operand;
destination_operand -= source_operand;