前言
汇编的学习记录,主要理一下学习路线及思路,细节方面可能不会做太详细的介绍,需要的还要查专业文档。开始要了解一下汇编之前,至少有一门高级语言的编程经验,要对操作系统、计算机结构有个大概的认识,这里不做这个方面的介绍。
一、汇编是什么?
汇编是最接近机器语言,可以直接操控硬件设备,处理速度快,体积小,多应用于核心算法,驱动开发,嵌入式设备等,它有大量的指令集构成,不同的CPU架构的指令集不同,所以汇编不具备移植性(指令也可以理解为助记符,针对机器码而言)。汇编主要的两个工具程序:汇编器(把汇编文字转换成机器码)、链接器(把多个汇编器编译的文件合成可执行的汇编程序)。
二、学习步骤
1.搭建汇编环境
硬件:准备32位 或64位 windows 的电脑,
软件:在win10 (64位)系统环境下搭建的汇编环境,下载dosbox,地址:DOSBox0.74-3-win32-installer.exe,masm32为汇编编译器,下载地址:masm32
详细使用过程到:win10汇编环境的搭建
2.语言层级,以及虚拟机
虚拟机就是运作在计算机中的程序,或者用来模拟物理机或虚拟计算机来执行其他的软件的程序。
编译器运行在计算机中,执行某种语言的编译工作,使其成为可执行的程序
编译器自举原理,假设A(初级),B(低级),C(高级)语言,首先使用机器码编写A-A编译器,随后发明了B语言,就用A语言完成A-B的编译器编译程序使用A-A完成编译于是就诞生了A-B编译器,随后用B语言开发出B-B的编译器程序用A-B完成编译,结果诞生B-B编译器,诞生C语言后,用就先编写B-C编译器,之后用C语言开发出C-C编译程序,用B-C完成编译,就诞生了C-C的编译器,这个就实现了编译器的自举。(可以理解为一种语言的诞生就是先诞生可在机器上运行的编译器)
VM0 直接代表物理机,一般有硬件实现。
理解语言层级:机器码>指令集架构(ISA)>汇编语言>高级语言。
与实际机器和语言相对,用 Level 2 表示 VM2,Level 1 表示 VM1,如下图所示。计算机数字逻辑硬件表示为 Level 1 机器。其上是 Level 2,称为指令集架构(ISA, Instruction Set Architecture) 。
3.了解CPU 基本架构图
- 汇编是理解计算机工作原理的最好语言,需要具备一定的计算机硬件结构知识,下面微机的基本设计。中央处理单元(CPU)是进行算术和逻辑操作的部件,包含了有限数量的存储位置——寄存器(register),一个高频时钟、一个控制单元和一个算术逻辑单元。
- 程序的执行及传输主要借助寄存器完成,通用寄存器主要用于算术运算和数据传输 ,标志寄存器EFLAGS主要对一些特殊的状态进行标记,指令寄存器EIP
EAX | EBP |
EBX | ESP |
ECX | ESI |
EDX | EDI |
其中以EAX为例,低16又分为 AX寄存器,AX的16位又分AH 高8位,AL低8位,可以处理8位的值
32位标识 | 16位 | 高8位 | 低8位 |
EAX | AX | AH | AL |
EBX | BX | BH | BL |
ECX | CX | CH | CL |
EDX | DX | DH | DL |
另外一些寄存器处理32位的同时,还可处理16位的值。
32位 | 16位 | 32位 | 16位 |
EBP | BP | ESI | SI |
ESP | SP | EDI | DI |
CS | ES |
SS | FS |
DS | GS |
溢出 | 方向 | 中断 | 符号 | 零置为 | 辅助进位 | 奇偶 | 进位标志位 | 单步 |
OF | DF | IF | SF | ZF | AF | PF | CF | TF |
其他还有8个64位MMX 寄存器 和 8 个 128 位 XMM 寄存器, MMX 寄存器支持称为 SIMD(单指令,多数据,Single-Instruction,Multiple-Data)的特殊指令进行一些多媒体处理和应用通信处理;XMM寄存器被用于 SIMD 流扩展指令集。后面用到时再做名称介绍。
4.汇编语言示例
第一个汇编程序,进行简单的认识。
;进行两个数相加
main PROC ;程序入口
mov eax, 1b ;将数字 1 送入 eax 寄存器
add eax, 10b ;eax 寄存器加 2
INVOKE ExitProcess, 0 ;程序结束
main ENDP ;结束标记符
指令结构解析为:操作指令 目标,源 (注意之间的空格)
目标和源统一为操作数分为三类:
-
立即数——用数字文本表达式
-
寄存器操作数——使用 CPU 内已命名的寄存器
-
内存操作数——引用内存位置
指令本次使用到 mov ,add ,MOV 指令将源操作数复制到目的操作数,add对目标操作数数加上源操作数,源操作数不变 后面我们会进行更多的指令介绍。
main 为程序命名,
PROC 为伪指令,进行一些程序标注作用,程序执行时,进行标注说明,汇编设计支持多种伪指令操作,后面会一一介绍。
INVOKE ExitProcess 调用结束过程返回系统操作权。
5.汇编基础操作符
这里已高级语言的语法为引去理解倒推理并理解汇编语言,程序的编写离不开条件判断,或 、非 逻辑,就是以汇编为基础进行再次封装的结果,或者叫编写,先介绍指令类型。在从简单的指令开始介绍基础逻辑运算。
一条指令有四个组成部分:
-
标号(可选)
-
指令助记符(必需)
-
操作数(通常是必需的)
-
注释(可选)
[标号: ] 指令 [操作数] [;注释]
标号(label)是一种标识符,是指令和数据的位置标记。标号位于指令的前端,表示指令的地址。同样,标号也位于变量的前端,表示变量的地址。标号有两种类型:数据标号和代码标号。
数据标号标识变量的位置,它提供了一种方便的手段在代码中引用该变量。比如,下面定义了一个名为 count 的变量:
汇编器为每个标号分配一个数字地址。可以在一个标号后面定义多个数据项。在下面的例子中,array 定义了第一个数字(1024)的位置,其他数字在内存中的位置紧随其后:
array DWORD 1024, 2048
DWORD 4096, 8192
程序代码区(指令所在区段)的标号必须用冒号(:)结束。代码标号用作跳转和循环指令的目标。例如,下面的 JMP 指令创建一个循环,将程序控制传递给标号 target 标识的位置:
target:
mov ax,bx
...
jmp target
代码标号可以与指令在同一行上,也可以自己独立一行:
L1: mov ax, bx
L2 :
标号命名规则要求,只要每个标号在其封闭子程序页中是唯一的,那么就可以多次使用相同的标号
指令助记符,就是汇编定义的具体某项功能操作的短单词,如上面示例的:add,mov,jmp等。
操作数是输入输出的数值,每个指令后跟操作数为0~3个,操作数可以是寄存器、内存操作数、整数表达式和输入输出端口生成内存操作数有不同的方法,使用变量名、带方括号的寄存器等。变量名代表变量地址,并指示计算机使用给定地址的内存内容
示例 | 操作数类型 | 示例 | 操作数类型 |
22 | 整数常量 | eax | 寄存器 |
1+1 | 整数表达式 | array | 内存 |
注释有两种指定方法:
单行注释,用分号(;)开始。汇编器将忽略在同一行上分号之后的所有字符。
块注释,用 COMMENT 伪指令和一个用户定义的符号开始。汇编器将忽略其后所有的文本行,直到相同的用户定义符号出现为止。
;这是单行注释
COMMENT !
这是多行注释
这是多行注释
!
伪指令 (directive) 是嵌入源代码中的命令,由汇编器识别和执行。伪指令不在运行时执行,但是它们可以定义变量、宏和子程序;为内存段分配名称,执行许多其他与汇编器相关的日常任务。
默认情况下,伪指令不区分大小写。例如,.data,.DATA 和 .Data 是相同的。
下面的例子有助于说明伪指令和指令的区别。DWORD 伪指令告诉汇编器在程序中为一个双字变量保留空间。另一方面,MOV 指令在运行时执行,将 myVar 的内容复制到 EAX 寄存器中:
myVar DWORD 26 ;定义常量26
mov eax,myVar ;把myvar 送到寄存器eax
汇编器伪指令的一个重要功能是定义程序区段,也称为段 (segment)。程序中的段具有不同的作用。如下面的例子,一个段可以用于定义变量,并用 .DATA 伪指令进行标识:
.CODE 伪指令标识的程序区段包含了可执行的指令
.STACK 伪指令标识的程序区段定义了运行时堆栈,并设置了其大小:
示例代码:
;相加操作
.386
.model flat,stdcall
.stack 4096
ExitProcess PROTO, dwExitCode:DWORD
.data
oneval DWORD 10001000h
twodval DWORD 22222222h
threeval DWORD 33333333h
sum DWORD 0
.code
main PROC
mov eax,oneval
add eax,twodval
add eax,threeval
mov sum,eax
INVOKE ExitProcess,0
main ENDP
END main
标识符(identifier)是由程序员选择的名称,它用于标识变量、常数、子程序和代码标签。
标识符的形成有一些规则:
-
可以包含 1 到 247 个字符。
-
不区分大小写。
-
第一个字符必须为字母 (A---Z, a---z) A 下划线 (_)、@、? 或 $。其后的字符也可以是数字。
-
标识符不能与汇编器保留字相同。
保留字(reserved words)有特殊意义并且只能在其正确的上下文中使用。默认保留字是没有大小写之分 。如,MOV 与 mov、Mov 是相同的。
保留字有不同的类型:
-
指令助记符,如 MOV、ADD 和 MUL。
-
寄存器名称。
-
伪指令,告诉汇编器如何汇编程序。
-
属性,提供变量和操作数的大小与使用信息。例如 BYTE 和 WORD。
-
运算符,在常量表达式中使用。
-
预定义符号,比如 @data,它在汇编时返回常量的整数值。
6. X86数据类型以及定义
由上面代码我们发现.adta区所定义的一些变量,变量就要有属于他的数据类型,下面表格展示了汇编器识别的一组内部数据类型,按数据大小、是否有符号,是整数还是实数来描述其类型。
BYTE | 8 位无符号整数,B 代表字节 |
SBYTE | 8 位有符号整数,S 代表有符号 |
WORD | 16 位无符号整数 |
SWORD | 16 位有符号整数 |
DWORD | 32 位无符号整数,D 代表双(字) |
SDWORD | 32 位有符号整数,SD 代表有符号双(字) |
FWORD | 48 位整数(保护模式中的远指针) |
QWORD | 64 位整数,Q 代表四(字) |
TBYTE | 80 位(10 字节)整数,T 代表 10 字节 |
REAL4 | 32 位(4 字节)IEEE 短实数 |
REAL8 | 64 位(8 字节)IEEE 长实数 |
REAL10 | 80 位(10 字节)IEEE 扩展实数 |
自动定义示例:
nume DWORD 1234 ;定义32双字符常量
数据定义中至少要有一个初始值,多个初始值用 “,”逗号分隔。如果不想对变量初始化,可以用?号作为初始值,不论什么进制类似的数据最后都会由编译器转化位二进制
DUP 操作符使用一个整数表作为计数器,位多个数据分配存储空间。多用为字符串和数组分配存储空间
aa DWord 4 DUP (10)
BYTE 20 DUP ( 0 ) ;20 个字节,值都为 0
BYTE 20 DUP ( ? ) ;20 个字节,非初始化
BYTE 4 DUP ( "STACK" ) ; 共20 个字节,byte 字长8等于一个字节,5个字符就是5个字节
word1 WORD 65535 ;最大无符号数
word2 SWORD -32768 ;最小有符号数
word3 WORD ? ;未初始化,无符号
7.汇编语言的一些常量
有整数常量和实数常量,又分有符号和无符号等,字符常量,字符串常量等
整数常量有2.进制(1010b),8进制(755o),10进制(18901d),16进制(1F4Ah)表示方法
字符常量 "A","c"
字符串常量有 "sdfsdfsfdsf"
未完待续.....
总结
这次汇编就记录到里,大概对汇编的代码结构有了初步的了解,后续学习运算符及逻辑判断等指令,本文大多是知识点理解,学习路线说明,以及归纳总结,详细的使用技巧还要查阅资料,这里说明指令集特别的琐碎,毕竟目的就是为了看的懂汇编程序,了解其运行原理,做到认识理解就达到了初级目标。