文章

16位汇编程序设计

16位汇编程序设计

为什么要学习汇编语言?

  • 嵌入式系统:在特定硬件下,对程序的大小和运行速度需要高度优化的情况。
  • 驱动程序的设计者;操作系统内核的设计者;编译程序的设计者。
  • 有助于对计算机硬件、操作系统、应用程序之间交互的整体理解。
  • 突破高级语言的局限:高级语言中嵌入汇编。

汇编语言通常在编程语言中排名第10左右

Intel处理器体系结构的发展

处理器微体系结构决定了处理器的性能、成本等指标,故其体系结构在不断地发展变化。

年份微处理器型号体系结构
19788086Intel IA-32架构先导,第1个16位处理器
198580386Intel IA-32架构,第1个32位处理器
1995-1999P6家族Intel IA-32架构(超标量微体系结构)
2000-2006Pentium 4Intel IA-32架构(NetBurst微体系结构)
 Pentium 4(6xx、5xx)Intel 64架构
2003Pentium M增强的Intel IA-32移动架构
2005-2007Pentium ExtremeIntel 64架构(NetBurst微体系结构)
2006-2007Core Duo增强的Pentium M微体系结构
2006Pentium双核,Core 2 DuoIntel 64架构(Core微体系结构,65nm)
2007Core 2 Duo E8000,Quad Q9000Intel 64架构(增强Core微体系结构,45nm)
2008AtomIntel 64架构(Atom微体系结构,45nm)
2008Core i7 900Intel 64架构(Nehalem微体系结构,45nm)
2010Core i7、i5、i3Intel 64架构(Westmere微体系结构,32nm)
2011第二代Core i7、i5、i3Intel 64架构(Sandy Bridge微体系结构,32nm)
2012第三代Core i7、i5、i3Intel 64架构(Ivy Bridge微体系结构,22nm)
2013第四代Core i7、i5、i3Intel 64架构(Haswell微体系结构,22nm)
2015第五代Core i7、i5、i3Intel 64架构(Broadwell微体系结构,14nm)

其变化内容大致如下:

  • 主存分段
  • 寄存器结构
  • 主存寻址空间
  • 外部总线宽度
  • 更宽的内部数据通路
  • 保护模式
    • 分段存储模型
    • 平坦存储模型
    • 分页虚存管理
  • 流水线技术
    • 超标量
    • 分支预测
    • 动态执行/推测执行/乱序
  • 高速缓存技术;Smart Cache
  • 扩展指令集
  • 超线程技术;多核技术
  • 硬件虚拟化技术
  • 集成的多通道存储控制器
  • QPI
  • 集成的图形处理单元
  • Turbo Boost
  • 环形内部总线(第二代Core i7、i5、i3)
  • 16位→32位→64位
  • 制造工艺:65nm→45nm→32nm→22nm→14nm

单核处理器 8086

为什么选择 8086

  • 单核处理器是多核处理器的基础。
  • 从8086 CPU开始,Intel CPU设计采用了向后兼容(backward compatibility,也称作向下兼容Downward Compatibility)的特性。
  • 单核的8086 CPU成为其后Intel CPU的基石。

8086 简介

  • 8086 CPU有三个版本:8086、8086-2、8086-1,仅时钟频率不同,依次为5Mhz、8Mhz、10Mhz。
  • 8086 CPU具有如下功能特性:
    • 直接主存寻址能力1MB;
    • 体系结构是针对强大的汇编语言和有效的高级语言设计的;
    • 14个16位寄存器;
    • 24种操作数寻址方式;
    • 操作数类型:位、字节、字和块;
    • 8、16位无符号和带符号二进制或十进制运算,包括乘法和除法。
  • 特点:
    • 实地址模式
    • 小端存储(反常识),如 5678H 表式为 7856H

体系结构

8086处理器体系结构.png

  • 总线接口单元BIU(Bus Interface Unit): 负责与存储器、I/O接口传递数据,具体完成:
    • 从内存取指令,送到指令队列
    • 配合EU从指定的内存单元或IO端口取数据
    • 将EU的操作结果送到指定的内存单元或IO端口
  • 执行单元EU(Execute Unit):负责指令的执行(算术、逻辑、移位运算,有效地址计算,控制命令、……)

由于有指令队列,BIU和EU可并行工作

寄存器结构

寄存器结构.png 其中各寄存器说明如下: 寄存器结构说明.png

关于寄存器的更多信息可以参考这篇文章 x86 Assembly Registers[All Types Explained]

对于有保护模式的32位的 CPU (如80386),其寄存器有如下特点:

  • 每个寄存器可作32位或16位使用。
  • 一些16位的寄存器也可以作为两个单独的8位使用。
  • 其余通用寄存器的低16位有独立的名字,但不能进一步细分。下面列出的16位寄存器通常只在编写运行于实地址模式(8086只有实地址模式,保护模式首次出现在80386中)下的程序时才使用。

    32位16位高8位低8位
    EAXAXAHAL
    EBXBXBHBL
    ECXCXCHCL
    EDXDXDHDL
    32位16位
    ESISI
    EDIDI
    EBPBP
    ESPSP

主存结构

双体

既可以实现16位存储,也可以实现8位存储。 主存的双体结构.png

分段

1
物理地址 = 段地址*16+段内偏移地址

例如:形式地址6832H:1280H → 物理地址?(68320H + 1280H = 695A0H)

优点:

  • 代码、数据量不大 → 使其处于同一段内(64KB范围内)→ 可减少指令长度、提高运行速度。
  • 内存分段为程序的浮动分配创造了条件。
  • 各个分段之间可以重叠

8086 CPU 执行程序时,当前指令的存储地址为(CS)×16+(IP)。当CS不变、IP单独改变时,会发生段内的程序转移;当CS改变时,会发生段间的程序转移。

段寄存器的使用约定如下: 主存的分段结构之段寄存器的使用.png

主存结构如下: 主存结构.png

工作过程

应用程序->OS函数->BIOS功能->硬件

例如,在显示器上显示一个特定颜色字符串:

  1. 应用程序调用一个库函数向标准输出上写字符串。
  2. 库函数(层次3)调用一个操作系统函数,传递一个字符串指针。
  3. 操作系统函数(层次2)重复调用BIOS的某个功能,向它传递ASCII码及每个字符的颜色,操作系统调用另外一个BIOS功能将光标前进到屏幕的下一个字符位置。
  4. BIOS功能(层次1)接收每个字符,将其映射为特定的系统字体,然后把字符送至与视频控制卡相连的硬件端口。
  5. 视频控制卡(层次0)定时产生硬件信号给显示器以控制光栅扫描和象素显示。

8086 汇编程序设计

Hello World 程序

完整段定义的程序结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
stack	segment stack
		db 100 dup (?)
stack	ends
data	segment
		message db 'Hello, world',0dh,0ah,'$'
data	ends
code	segment
		assume cs:code,ds:data,ss:stack
start:
		mov  ax,data
		mov  ds,ax

		mov  ah,9
		mov  dx,offset message
		int  21h

		mov  ah,4ch
		int  21h
code	ends
		end start

其中:

  • 完整段定义:
    1
    2
    3
    
    段名	SEGMENT [对齐方式] [组合方式] ['类']
        	语句
    段名 	ENDS
    

    段名:标识段的名字,惟一的或已存在的。 对齐方式:可以是 BYTE、WORD、DWORD、PARA、PAGE。 组合方式:可以是 PRIVATE、PUBLIC、STACK、COMMON、MEMORY、AT地址。 类:用于标识特定类型段的名字,如CODE、STACK等。

  • ASSUME伪指令:告诉汇编程序,哪一个段和哪一个段寄存器相对应,即某一段地址应放入哪一个段寄存器。
  • 操作系统的装入程序在装入执行时,把CS初始化成正确的代码段地址,把SS初始化为正确的堆栈段地址,因此源程序中无再需初始化CS、SS。
  • 装入程序已将DS寄存器留作它用,故在源程序中应有以下两条指令: MOV AX,DATA MOV DS,AX
  • DOS环境下,汇编语言返回DOS: MOV AH,4CH INT 21H

简化段定义的程序结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.model small,stdcall			            ;存储模型:小型
.stack 100          		              ;定义堆栈段及其大小
.data               	                ;定义数据段
message db 'Hello, world',0dh,0ah,'$' ;数据声明
.code                                 ;定义代码段
start:                                ;起始执行地址标号
		mov  ax,data                      ;数据段地址
		mov  ds,ax                        ;存入数据段寄存器
                                      ;具体程序代码
		mov  ah,9                                           
		mov  dx,offset message                              
		int  21h                          

		mov  ah,4ch
		int  21h                          ;程序结束

		end start

关键概念

  • 汇编语言:用指令的助记符、符号地址、标号、伪指令等符号书写程序的语言。
  • 汇编语言源程序:用汇编语言书写的程序。
  • 汇编:把汇编语言源程序翻译成机器语言程序的过程。
  • 汇编程序:完成汇编过程的系统程序。
  • 语句格式:汇编语言语句的格式
    • 中的参数必需,即默认必需
    • […]中的参数可选
    • {…|…}多选一(由|分割)
  • 常数: 整数常量和字符串常量
  • 伪指令:用来对汇编程序进行控制,以使程序中的数据实现条件转移、列表、存储空间分配等处理。其格式与汇编指令一样,但一般不产生目的代码。
  • 运算符: 算术运算符等
  • 源程序的结构: 代码结构
  • 实地址模式/保护模式:实(地址)模式将整个物理内存看成分段的区域,程序代码和数据位于不同区域,系统程序和用户程序没有区别对待,而且每一个指针都是指向真实的物理地址;保护模式中物理内存地址不能直接被程序访问,程序内部的地址(虚拟地址)要由操作系统转化为物理地址去访问,程序对此一无所知。
  • 大端/小端存储: 大端存储(常识)用于网络,有利于人看,如 5678H 表式存储为 5678H;小端存储(反常识)用于 intel 处理器,有利于电脑看,如 5678H 表式存储为 7856H
  • 32位和16位:32位最先出现在80386处理器,16位最先出现在8086处理器(8086只支持实模式);从80386开始,cpu有三种工作方式:实模式,保护模式和虚拟8086模式。只有在刚刚启动的时候是实模式,等到操作系统运行起来以后就运行在保护模式。虚拟8086模式是运行在保护模式中的实模式,为了在32位保护模式下执行纯16位程序。它不是一个真正的CPU模式,还属于保护模式。CPU启动环境为16位实模式,之后可以切换到保护模式。但从保护模式无法切换回实模式
  • ECX 和 CX:前者是后者的拓展,主要是在32位CPU中使用的(当然,由于 Intel 向下兼容,所以 64 位应该也可以)

MASM

  • MASM 4.00: 这是最先广泛使用的一个MASM版本,适用于DOS 下的汇编编程。它很精巧,但使用起来不是很智能化,需要用户自己一板一眼地写出所有的东西。很多教科书上讲的8086 汇编语法都是针对这个版本的,对程序员来说,它只比用Debug 方便一点点。
  • MASM 5.00: MASM 5.00 比4.00 在速度上快了很多,并将段定义的伪指令简化为类似 .code 与.data 之类的定义方式,同时增加了对80386 处理器指令的支持,对4.00 版本的兼容性很好。
  • MASM 5.10: 对程序员来说,这个版本最大的进步是增加了对@@标号的支持。这样,程序员可以不再为标号的起名花掉很多时间。另外,MASM 5.10 增加了对OS/2 1.x 的支持。
  • MASM 5.10B: 1989 年推出,比上一个版本更稳定、更快,它是传统的DOS 汇编编译器中最完善的版本。
  • MASM 6.00: 1992 年发布,有了很多的改进。编译器可以使用扩展内存,这样可以编译更大的文件,可执行文件名相应从Masm.exe 改为ML.exe。从这个版本开始可以在命令行上用*.asm同时编译多个源文件,源程序中数据结构的使用和命令行参数的语法也更像C 的风格。最大的改进之一是开始支持 .if/.endif 这样的高级语法,这样,使用复杂的条件分支时和用高级语言书写一样简单,可以做到几千行的代码中不定义一个标号;另外增加了invoke 伪指令来简化带参数的子程序调用。这两个改进使汇编代码的风格越来越像C,可读性和可维护性提高了很多。
  • MASM 6.00A: 未发售的版本。
  • MASM 6.00B: 最后一个支持OS/2 的MASM 版本,修正了上一版本中的一些错误。
  • MASM 6.10: 修正了一些错误,同时增加了/Sc 选项,可以在产生的list 文件中列出每条指令使用的时钟周期数。
  • MASM 6.10A: 1992 年发布,修正了一些内存管理方面的问题。
  • MASM 6.11: 1993 年11 月发布,支持Windows NT,可以编写Win32 程序,同时支持Pentium 指令,但不支持MMX指令集。
  • MASM 6.11C: 1994 年发布,增加了对Windows 95 VxD 的支持。
  • MASM 6.12: 1997 年8 月发布,增加 .686,.686P,.MMX 声明和对相应指令的支持。
  • MASM 6.13: 1997 年12 月发布,增加了 .K3D 声明,开始支持AMD 处理器的3D 指令。
  • MASM 6.14: 这是一个很完善的版本,它在 .XMM中增加了对Pentium III的SIMD指令集的支持,相应增加了OWORD(16 字节)的变量类型。
  • MASM 6.15: 2000 年4 月发布。

编译、链接和运行程序

过程

编译链接和运行程序.png

汇编程序做了什么?

汇编程序做了什么.png 其中EXE的文件头MZMark Zbikowski的缩写,Mark Zbikowski 曾是Microsoft 高级构架师和最早的计算机黑客之一,DOS 可执行文件格式的设计者。 汇编程序做了什么2.png

基本元素

  • 中的参数必需,即默认必需
  • […]中的参数可选
  • {…|…}多选一(由|分割)

整数常量

1
[{+|-}]digits[radix]
  • digits是一个整数
  • radix(基数后缀)可以是以下之一(大小写均可):
    • h: 十六进制
    • b: 二进制
    • q/o: 八进制
    • r: 编码实数
    • d: 十进制

    例如:26 26d 11010011b 42q 42o 1Ah 0A3h

字符、字符串常量

1
2
3
4
5
6
7
'A'
"d"
'ABC'
"Goodnight, Gracie"
'4096'
"This isn't a test"
'Say "Goodnight," Gracie'

算术运算符

运算符名称优先级
()圆括号1
+, - 单目加、减2
*, / 乘、除3
MOD取模3
+, -加、减4

保留字

保留字:有特殊含义,只能用于正确的上下文环境中

  • 指令助记符,例如:MOVADDMUL
  • 伪指令,告诉MASM如何编译程序,如:ASSUMEOFFSET.data.codename PROC
  • 属性,为变量和操作数提供有关尺寸及使用方式信息,如:BYTEWORD
  • 运算符,用在常量表达式中,如:+-*/
  • 预定义符号,例如@data,在编译时返回整数常量值。

标识符

标识符:程序员选择的名字,用来识别变量、常量、过程或代码标号。

  • 可包含1~247个字符。
  • 大小写不敏感。(汇编器加-Cp参数则大小写敏感)
  • 第一个字符必须是字母(A-Za-z)、下划线(_)、@?$,后续字符可以是数字。
  • 不能与保留字相同。
  • 尽量避免用@_作为第一个字符,因为它们即用于汇编器,也用于高级语言编译器。

指令

指令:程序被加载至内存开始运行后,由处理器执行的语句。

1
[标号:] 指令助记符 [操作数] [;注释]

例子:

  1. 1
    2
    3
    4
    5
    
       ;代码标号
       target:
     mov ax,bx
     ...
     jmp target
    
  2. 1
    2
    
       ;数据标号
       first BYTE 10
    

数据定义

1
[名称] 类型 初始值[,初始值]…

其中类型可以为:

类型用途早期版本
BYTE8位无符号整数DB
SBYTE8位有符号整数 
WORD16位无符号整数(可在实模式下用作近指针)DW
SWORD16位有符号整数 
DWORD32位无符号整数(可在保护模式下用作近指针)DD
SDWORD32位有符号整数 
FWORD48位整数(可在保护模式下用作远指针) 
QWORD64位整数DQ
TBYTE80位整数DT
REAL432位IEEE短实数 
REAL864位IEEE长实数 
REAL1080位IEEE扩展精度实数 

例如:

1
2
3
4
5
6
7
8
9
.data
value1 BYTE 10h
value2 BYTE ?
list1  BYTE 10,20,30,40
       BYTE 50,60,70,80
list2  BYTE 32,41h,00100010b,'a'
greeting BYTE "Good afternoon",0dh,0ah,0
array  WORD 5 DUP(?)	;5个未初始化的值
value3 DWORD 12345678h

符号常量

符号常量:不占用任何实际的存储空间。

  • 等号伪指令,例如:
    1
    2
    3
    4
    5
    
    COUNT = 500
    ESC_key = 27
    array COUNT DUP(0)
    mov cx,COUNT
    mov al,ESC_key
    
  • EQU伪指令
  • TEXTEQU伪指令
  • ($ - array)伪指令,计算数组和字符串的大小,例:
    1
    2
    3
    4
    5
    6
    7
    8
    
    list1 BYTE 10,20,30,40
    List1Size = ($ - list1)
    myString BYTE "This is a long string,"
             BYTE " Containing any number"
             BYTE " of characters",0dh,0ah
    MyString_len = ($ - myString)
    list2 WORD 1000h,2000h,3000h,4000h
    List2Size = ($ - list2)/2
    

数据传送指令—— MOV 类指令

操作数类型

  • 立即操作数(immediate)
    • imm: 8、16或32位立即数
    • imm8: 8位立即数(字节)
    • imm16: 16位立即数(字)
    • imm32: 32位立即数(双字)
  • 寄存器操作数(register)
    • reg: 任意的通用寄存器
    • sreg: 16位段寄存器CS、DS、SS、ES、FS、GS
    • r8: AH、AL、BH、BL、CH、CL、DH、DL
    • r16: AX、BX、CX、DX、SI、DI、SP、BP
    • r32: EAX、EBX、ECX、EDX、ESI、EDI、ESP、EBP
  • 内存操作数(memory)
    • mem: 8、16或32位内存操作数
  • 其它约定
    • r/m8: 8位操作数(8位通用寄存器或内存字节)
    • r/m16: 16位操作数(16位通用寄存器或内存字)
    • r/m32: 32位操作数(32位通用寄存器或内存双字)

直接内存操作数

直接内存操作数.png

MOV

1
2
3
4
5
6
7
mov destination,source

mov reg,reg
mov mem,reg
mov reg,mem
mov mem,imm
mov reg,imm

类似 C语言中destination=source

MOV指令需要遵循的规则:

  • 两个操作数的尺寸必须一致。
  • 两个操作数不能同时为内存操作数。
  • 目的操作数不能是CS,EIP和IP。
  • 立即数不能直接送至段寄存器。
  • 段寄存器仅用于实地址模式(即80386中的保护模式下不行)下运行的程序。
  • 内存之间的移动通过寄存器暂存。如:
    1
    2
    3
    4
    5
    6
    
    .data
    var1 WORD ?
    var2 WORD ?
    .code
    mov ax,var1
    mov var2,ax
    

MOVZX

move with zero-extend

1
2
3
4
movzx r32,r/m8
movzx r32,r/m16
movzx r16,r/m8

MOVSX

move with sign-extend

1
2
3
4
movsx r32,r/m8
movsx r32,r/m16
movsx r16,r/m8

XCHG

exchange data: 交换两个操作数的内容。

1
2
3
xchg reg,reg
xchg reg,mem
xchg mem,reg

加减法类指令

INCDEC

  • 含义:increment和decrement,加1、减1。
  • 格式:
    1
    2
    
    inc reg/mem
    dec reg/mem
    
  • 例子:
    1
    2
    3
    4
    5
    6
    
    .data
    myWord WORD 1000h
    .code
    inc myWord		; 1001h
    mov bx,myWord
    dec bx		; 1000h
    
  • 影响的标志位:INC和DEC指令不影响进位标志

ADD

  • 含义:将同尺寸的源操作数和目的操作数相加,结果在目的操作数中(不改变源操作数)。
  • 格式:与MOV指令相同。
    1
    
    add 目的操作数,源操作数
    
  • 例:
    1
    2
    3
    4
    5
    6
    
    .data
    var1 DWORD 10000h
    var2 DWORD 20000h
    .code
    mov eax,var1
    add eax,var2	; 30000h
    

SUB

  • 含义:将源操作数从目的操作数中减掉,结果在目的操作数中(不改变源操作数)。
  • 格式:与MOV、ADD指令相同。
    1
    
    sub 目的操作数,源操作数
    
  • 例:
    1
    2
    3
    4
    5
    6
    7
    
    .data
    var1 DWORD 30000h
    var2 DWORD 10000h
    .code
    mov eax,var1
    sub eax,var2	; 20000h
      
    
  • 影响的标志位:CF、ZF、SF、OF、AF、PF

NEG

  • 含义:negate,求负。将操作数按位取反、末位加1。
  • 格式:
    1
    2
    
    neg reg
    neg mem
    
  • 影响的标志位:CF、ZF、SF、OF、AF、PF

标志位

  • 零标志和符号标志
  • 进位标志(无符号算术运算):INC和DEC指令不影响进位标志。
  • 溢出标志(有符号算术运算)
    1
    
    OF=C𝑛⊕C(𝑛−1)
    

    其中,Cn为符号位产生的进位,即标志位CF; Cn-1为最高有效位向符号位产生的进位。

例子程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
.386
.MODEL flat, stdcall
.STACK 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
Rval SDWORD ?
Xval SDWORD 26
Yval SDWORD 30
Zval SDWORD 40
.code
main PROC
	mov ax,1000h   ; INC and DEC
	inc ax	    ; 1001h
	dec ax	    ; 1000h
	; Expression: Rval = -Xval + (Yval - Zval)
	mov  eax,Xval
	neg  eax	    ; -26
	mov  ebx,Yval
	sub  ebx,Zval  ; -10
	add  eax,ebx
	mov  Rval,eax  ; -36
	; Zero flag example:
	mov cx,1
	sub cx,1		; ZF = 1
	mov ax,0FFFFh
	inc ax		; ZF = 1
	; Sign flag example:
	mov cx,0
	sub cx,1		; SF = 1
	mov ax,7FFFh
	add ax,2		; SF = 1
	; Carry flag example:
	mov al,0FFh
	add al,1		; CF = 1, AL = 00
	; Overflow flag example:
	mov  al,+127
	add  al,1		; OF = 1
	mov  al,-128
	sub  al,1		; OF = 1
	INVOKE ExitProcess,0
main ENDP
END main

和数据相关的操作符

OFFSET

  • OFFSET 操作符返回数据标号的偏移地址(标号距数据段开始的距离,以字节为单位)。
  • 保护模式下偏移总是32位的。
  • 实模式下偏移只有16位。
  • 可与直接偏移操作数联合使用

例如(假设bVal位于00303000h处):

1
2
3
4
5
6
7
8
9
10
11
12
13
.data
bVal  BYTE  ?
wVal  WORD  ?
dVal1 DWORD ?
dVal2 DWORD ?
.code
……
mov esi,OFFSET bVal	  ; ESI = 00303000
mov esi,OFFSET wVal	  ; ESI = 00303001
mov esi,OFFSET dVal1	; ESI = 00303003
mov esi,OFFSET dVal2	; ESI = 00303007
mov esi,OFFSET bVal + 1
……

PTR

用来重载操作数的默认尺寸。

必须和以下标准数据类型联合使用:BYTE,SBYTE,WORD,SWORD,DWORD,SDWORD,FWORD,QWORD,TBYTE

例如:

1
2
3
4
5
6
7
.data
myDouble DWORD 12345678h
.code
mov ax, myDouble			; 错误
mov ax, WORD PTR myDouble		; ax = 5678h
mov ax, WORD PTR [myDouble+2]	; ax = 1234h
mov bl, BYTE PTR myDouble		; bl = 78h

TYPE

返回按字节计算的变量的单个元素的大小。 例如如下代码:

1
2
3
4
5
.data
var1 BYTE ?
var2 WORD ?
var3 DWORD ?
var4 QWORD ?

则有:

表达式
TYPE var11
TYPE var22
TYPE var34
TYPE var48

LENGTHOF

计算数组中元素的个数。例如:

1
2
3
4
5
6
.data
byte1    BYTE 10,20,30
array1   WORD 30 DUP(?),0,0
array2   WORD 5 DUP(3 DUP(?))
arrar3   DWORD 1,2,3,4
digitStr BYTE "12345678",0

则有:

表达式
LENGTHOF byte13
LENGTHOF array130+2
LENGTHOF array25×3
LENGTHOF array34
LENGTHOF digitStr9

注意如下代码:

1
2
3
4
5
6
7
8
pitches WORD 0960H,0960H,0960H,0960H,0960H,0960H,0BB8H,0960H,0834H,0960H
        WORD 0BB8H,0CE4H,0BB8H,0960H,0960H,0BB8H,0960H,0BB8H,0CE4H,0E10H
        WORD 0BB8H,0CE4H,0BB8H,0BB8H,0CE4H,1068H,0E10H,0CE4H,0834H,0834H
        WORD 0960H,0BB8H,0834H,0960H,0960H,0BB8H,0CE4H,0BB8H,0960H,0960H
        WORD 0BB8H,0CE4H,0BB8H,0960H,0BB8H,0CE4H,0BB8H,1068H,0E10H,0CE4H
        WORD 0BB8H,0E10H
pitches_len=( $ - pitches)/2 ; =52,即正确的
pitches_len=LENGTHOF pitches ; =10,即错误的

SIZEOF

SIZEOF返回值=LENGTHOF返回值×TYPE返回值。例如下代码:

1
2
.data
intArray WORD 32 DUP(0)

则有:

表达式
TYPE intArray2
LENGTHOF intArray32
SIZEOF intArray64

间接寻址

间接操作数(寄存器间接寻址)

用寄存器作为指针,通过改变指针寄存器的值来访问数组中的不同元素,这种寻址方式称为寄存器间接寻址,存放地址的寄存器称为间接操作数

间接操作数只能用SI,DI,BX,BP。通常尽量避免使用BP(BP常用来寻址堆栈而不是数据段)(32位汇编,即保护模式中可以是任何用方括号括起来的32位通用寄存器(EAX,EBX,ECX,EDX,ESI,EDI,EBP、ESP)。例如:

1
2
3
4
5
6
.data
val1 BYTE 10h
.code
main proc
  mov si,OFFSET val1
  mov AL,[si] ;AL=10h

PTR与间接操作数的联合使用例:

1
2
inc [esi]	; error: operand must have size
inc BYTE PTR [esi]

变址操作数(寄存器相对寻址)

例(32位):

1
2
3
4
5
6
7
8
9
10
.data
arrayB BYTE 10h,20h,30h
.code
mov esi,0
mov al,[arrayB + esi]	; AL = 10h
mov al,arrayB[esi]	; 同上,另一种格式
mov esi,OFFSET arrayB
mov al,[esi]			; AL = 10h
mov al,[esi+1]		; AL = 20h
mov al,[esi+2]		; AL = 30h

实模式(即16位汇编)下只能使用SI,DI,BX,BP寄存器。(尽量避免使用BP寄存器)

JMP和LOOP指令

控制转移(跳转)或分支是一种改变程序执行顺序的方法。 控制转移可分为两种: 无条件转移:以JMP指令为例 条件转移:以LOOP指令为例

JMP

例:创建一个循环

1
2
3
4
5
top:
		.
		.
		.
		jmp top		; repeat the endless loop

LOOP

格式:

1
LOOP 目的地址

LOOP指令的执行过程:ECX-=1; ECX=0?退出循环:跳转到目的地址; ECX-=1; ....

  • 在实地址模式下,用做默认循环计数器的是CX而不是ECX。如:
    1
    2
    3
    4
    
        mov  ax,0
        mov  cx,5
    L1: inc  ax
        loop L1
    

    循环结束时,AX=5 ECX=0

  • 在 32 位 CPU 中的任何模式下,LOOPD指令都使用ECX作为循环计数器;LOOPW都使用CX作为循环计数器。
  • 循环的目的地址与当前地址只能在相距-128到+127字节的范围之内。机器指令平均3字节左右,因此一个循环平均最多只能包含大约42条指令。
  • 循环可以嵌套。如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    .data
    count DWORD ?
    .code
        mov ecx,100
    L1: mov count,ecx
        mov ecx,20
    L2: .
        .
        loop L2
        mov ecx,count
        loop L1
    

过程—— PROC 相关指令

与外部库链接

链接库:包含已经编译成机器码的过程的文件。

编译源文件->产生目标文件->插入到库中

链接库如何工作?假设程序要调用Irvine32.inc文件中的名为WriteString的过程在控制台上显示字符串,则:

  1. 程序中要用 PROTO 伪指令声明要调用的程序。Irvine32.inc中包含语句WriteString PROTO
  2. 用一条 CALL 指令执行 WriteString 过程: call WriteString
  3. 当程序被编译时,编译器为 CALL 指令的目标地址留出空白,该空白将由链接器填充。
  4. 链接器在链接库中查找WriteString这个名字,从库中把合适的机器指令拷贝到程序的可执行文件中,并把WriteString的地址插入到CALL指令中。

链接器的命令行选项 例:

1
Link32 /subsystem:windows HelloWin.obj user32.lib kernel32.lib

链接时引入user32.libkernel32.lib文件->运行时就会引用user32.dllkernel32.dll

kernel32.lib可以在Microsoft Windows平台软件开发包(platform SDK)中找到,它包含了存储在另一个文件 —— kernel32.dll 中的操作系统函数的链接信息。

存在的问题:

  1. .inc.obj.dll文件的作用与联系?

经典例子

整数数组求和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; This program sums an array of words. (SumArray.asm)
.386
.MODEL flat, stdcall
.STACK 4096
ExitProcess PROTO,dwExitCode:DWORD
.data
intarray WORD 100h,200h,300h,400h
.code
main PROC
	mov  edi,OFFSET intarray	; address of intarray
	mov  ecx,LENGTHOF intarray	; loop counter
	mov  ax,0				; zero the accumulator
L1:
	add  ax,[edi]			; add an integer
	add  edi,TYPE intarray   	; point to next integer
	loop L1				; repeat until ECX = 0
	INVOKE ExitProcess,0
main ENDP
END main

拷贝字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
TITLE Copying a String                  (CopyStr.asm)
; This program copies a string.
INCLUDE Irvine32.inc
.data
source  BYTE  "This is the source string",0
target  BYTE  SIZEOF source DUP(0),0
.code
main PROC
	mov  esi,0			; index register
	mov  ecx,SIZEOF source	; loop counter
L1:
	mov  al,source[esi]	; get a character from source
	mov  target[esi],al	; store it in the target
	inc  esi			; move to next character
	loop L1			; repeat for entire string
	INVOKE ExitProcess,0
main ENDP
END main

链接

缩略语

  • AF: Auxiliary carry Flag(辅助进位标志)
  • AH: Accumulator register High(寄存器 A 高位)
  • AL: Accumulator register Low(寄存器 A 低位)
  • AMD: Advanced Micro Devices
  • ASCII: American Standard Code for Information Interchange
  • AT: Advanced Technology
  • AX: Accumulator register (寄存器 A)
  • BH: Base register High(寄存器 B 高位)
  • BIOS: Basic Input Output System
  • BIU: Bus Interface Unit(总线接口单元)
  • BL: Base register Low(寄存器 B 低位)
  • BP: Base Pointer
  • BX: Base register(寄存器 B)
  • CF: Carry Flag(进位标志)
  • CH: Count register High(寄存器 C 高位)
  • CL: Count register Low(寄存器 C 低位)
  • CPU: Central Processing Unit
  • CS: Code Segment(代码段)
  • CX: Count register(寄存器 C)
  • DD: Define Dword
  • DEC: DECrement(递减)
  • DH: Data register High(寄存器 D 高位)
  • DI: Destination Index
  • DL: Data register Low(寄存器 D 低位)
  • DOS: Disk Operating System
  • DQ: Define QWORD
  • DS: Data Segment
  • DT: Define TBYTE
  • DUP: DUPlicate
  • DW: Define WORD
  • DWORD: Double WORD
  • DX: Data register(寄存器 D)
  • EAX: Extented AX
  • EBP: Extented BP
  • EBX: Extented BX
  • ECX: Extented CX
  • EDI: Extented DI
  • EDX: Extented DX
  • EIP: Extented IP
  • ENDP: END Process
  • ENDS: END Segment
  • EQU: EQUal
  • ES: Extra Segment
  • ESI: Extented SI
  • ESP: Extented SP
  • EU: Execute Unit
  • EXE: EXEcute
  • FS: File System
  • FWORD: Far WORD
  • IA: Intel Architecture
  • IEEE: Institute of Electrical and Electronics Engineers
  • INC: INCrement
  • INT: INTerrupt
  • IO: Input Output
  • IP: Instruction Pointer
  • JMP: JuMP
  • KB: Kilobyte
  • LOOPD: LOOP DWORD
  • LOOPW: LOOP WORD
  • MB: Megabyte
  • MMX: Multi-Media Extensions
  • MOV: MOVe
  • MOVSX: MOVe with Sign-eXtend
  • MOVZX: MOVe with Zero-eXtend
  • MUL: MULtiplication
  • MZ: Mark Zbikowski
  • NEG: NEGative
  • NT: New Technology
  • NT: Windows New Technology (Windows NT)
  • OF: Overflow Flag
  • OOP: Object-Oriented Programming
  • OS: Operating System
  • OV: OVerflow
  • PARA: PARAgraph
  • PF: Parity Flag
  • PROC: PROCess
  • PROTO: PROTOtype
  • PTR: PoinTeR
  • QWORD: Quadruple WORD
  • SBYTE: Sign BYTE
  • SDK: Software Development Kit
  • SDWORD: Sign DWORD
  • SF: Sign Flag
  • SI: Source Index
  • SIMD: Single Instruction, Multiple Data
  • SP: Stack Pointer
  • SS: Stack Segment
  • SUB: SUBtract
  • SWORD: Sign WORD
  • TBYTE: TB
  • XCHG: eXCHanGe data
  • ZF: Zero Flag *[AF]: Auxiliary carry Flag(辅助进位标志) *[AH]: Accumulator register High(寄存器 A 高位) *[AL]: Accumulator register Low(寄存器 A 低位) *[AMD]: Advanced Micro Devices *[ASCII]: American Standard Code for Information Interchange *[AT]: Advanced Technology *[AX]: Accumulator register (寄存器 A) *[BH]: Base register High(寄存器 B 高位) *[BIOS]: Basic Input Output System *[BIU]: Bus Interface Unit(总线接口单元) *[BL]: Base register Low(寄存器 B 低位) *[BP]: Base Pointer *[BX]: Base register(寄存器 B) *[CF]: Carry Flag(进位标志) *[CH]: Count register High(寄存器 C 高位) *[CL]: Count register Low(寄存器 C 低位) *[CPU]: Central Processing Unit *[CS]: Code Segment(代码段) *[CX]: Count register(寄存器 C) *[DD]: Define Dword *[DEC]: DECrement(递减) *[DH]: Data register High(寄存器 D 高位) *[DI]: Destination Index *[DL]: Data register Low(寄存器 D 低位) *[DOS]: Disk Operating System *[DQ]: Define QWORD *[DS]: Data Segment *[DT]: Define TBYTE *[DUP]: DUPlicate *[DW]: Define WORD *[DWORD]: Double WORD *[DX]: Data register(寄存器 D) *[EAX]: Extented AX *[EBP]: Extented BP *[EBX]: Extented BX *[ECX]: Extented CX *[EDI]: Extented DI *[EDX]: Extented DX *[EIP]: Extented IP *[ENDP]: END Process *[ENDS]: END Segment *[EQU]: EQUal *[ES]: Extra Segment *[ESI]: Extented SI *[ESP]: Extented SP *[EU]: Execute Unit *[EXE]: EXEcute *[FS]: File System *[FWORD]: Far WORD *[IA]: Intel Architecture *[IEEE]: Institute of Electrical and Electronics Engineers *[INC]: INCrement *[INT]: INTerrupt *[IO]: Input Output *[IP]: Instruction Pointer *[JMP]: JuMP *[KB]: Kilobyte *[LOOPD]: LOOP DWORD *[LOOPW]: LOOP WORD *[MB]: Megabyte *[MMX]: Multi-Media Extensions *[MOV]: MOVe *[MOVSX]: MOVe with Sign-eXtend *[MOVZX]: MOVe with Zero-eXtend *[MUL]: MULtiplication *[MZ]: Mark Zbikowski *[NEG]: NEGative *[NT]: New Technology *[NT]: Windows New Technology (Windows NT) *[OF]: Overflow Flag *[OOP]: Object-Oriented Programming *[OS]: Operating System *[OV]: OVerflow *[PARA]: PARAgraph *[PF]: Parity Flag *[PROC]: PROCess *[PROTO]: PROTOtype *[PTR]: PoinTeR *[QWORD]: Quadruple WORD *[SBYTE]: Sign BYTE *[SDK]: Software Development Kit *[SDWORD]: Sign DWORD *[SF]: Sign Flag *[SI]: Source Index *[SIMD]: Single Instruction, Multiple Data *[SP]: Stack Pointer *[SS]: Stack Segment *[SUB]: SUBtract *[SWORD]: Sign WORD *[TBYTE]: TB *[XCHG]: eXCHanGe data *[ZF]: Zero Flag
本文由作者按照 CC BY 4.0 进行授权