汇编语言语法-2

汇编语言语法-2

数据传送,寻址和算数运算

写在前面的补充(32位x86处理器):

基本程序执行寄存器

  • 通用寄存器:主要用于算数运算和数据传输
  • 一些寄存器的组成部分可以处理8位的值,如AX寄存器的高8位被称为AH,低8位被称为AL
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位
ESI SI EBP BP
EDI DI ESP SP
  • 特殊用法:

    • 乘除指令默认使用EAX(extended accumulator)
    • CPU默认使用ECX为循环计数器
    • ESP(extended stack accumulator)用于寻址堆栈数据,极少用于数据传输和算数运算
    • ESI(extended source index)和EDI(extended destination index)用于高速存储传输指令
    • 高级语言通过EBP(extended frame pointer)来引用堆栈中的函数参数和局部变量
  • 段寄存器
    实地址模式中,16位段寄存器表示的是预先分配的内存区域的基址,这个内存区域称为段。一些段中存放程序指令(代码),其他段中存放变量(数据),还有一个堆栈段存放的局部变量和函数参数

  • 指令指针
    EIP寄存器中包含下一条要执行指令的地址。某些机器指令可以控制EIP,使程序分支转向另一个新位置

数据传送指令

Q: 操作数的三种类型是什么
A:操作数有3种基本形式:

  • 立即数——————使用数字文本表达式
  • 寄存器操作数————————使用CPU内已经命名的寄存器
  • 内存操作数————————引用内存位置

Q:Intel使用的操作数符号中reg/mem32的含义是什么?
K:不用死记:

  • reg表示通用寄存器(register):reg8,8位的(AH,AL,BH,BL,CH,CL,DH,DL);reg16,16位的
  • sreg表示16位段寄存器(stack reg):CS,DS,SS,ES,GS,FS
  • imm立即数(immediate):imm8,8位立即数;imm32,32位立即数(DWORD型)
  • reg/mem8:8位操作数,可以是8位通用寄存器或者内存字节
  • reg/mem16:16位立即数,可以是16位通用寄存器或者内存字
  • reg/mem32:32位立即数,可以是32位通用寄存器或者内存双字
  • mem 内存操作数(8位,16位,32位)
    A:略…

Q:(判断)
1.MOV指令的目的操作数不能为段寄存器
2.MOV指令中的第二个操作数是目的操作数
3.EIP寄存器不能作为MOV指令的目的操作数
K:

  • MOV用于数据传送

MOV destination,source

  • MOV操作要满足如下规则:
    • 两个操作数必须是同样的大小
    • 两个操作数不能同时为内存操作数
    • 指令指针寄存器(IP,EIP,RIP)不能作为目标操作数
  • MOV不能内存到内存,要先入寄存器
  • 由于MOV不能直接将较小的操作数复制到较大的操作数中便有了:
    • MOVZX指令将一个较小的操作数(无符号)0扩展为较大的操作数再移动
    • MOVSX指令将一个较小的操作数(有符号)符号扩展为较大的操作数再移动

A:F F T

实例

  • XCHG指令交换两个操作数的内容,指令中至少有一个操作数是寄存器
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
43
44
45
46
47
48
.386
.model flat,stdcall
.stack 4096
ExitProcess proto,dwExitCode:dword


.data
val1 WORD 1000h
val2 WORD 2000h
arrayB BYTE 10h,20h,30h,50h
arrayW WORD 100h, 200h, 300h
arrayD DWORD 10000h, 20000h

.code
main proc
;演示MOVZS指令
mov bx, 0A69Bh
movzx eax, bx ; EAX = 0000A69Bh
movzx edx, bl ; EDX = 0000009Bh
movzx cx, bl ; CX = 009Bh

;演示MOVSX指令
mov bx, 0A69Bh
movsx eax, bx ; EAX = FFFFA69Bh
movsx edx, bl ; EDX = FFFFFF9Bh
mov bl, 7Bh
movsx cx, bl ; CX = 007Bh

;内存-内存的交换
mov ax, val1 ; AX = 1000h
xchg ax, val2 ; AX = 2000h, val2 = 1000h
mov val1, ax ; val1= 2000h

;直接-偏移量寻址
mov al, arrayB ; AL = 10h
mov al, [arrayB+1] ; AL = 20h
mov al, [arrayB+2] ; AL = 30h

mov ax, arrayW ; AX = 100h
mov ax, [arrayW+2] ; AX = 200h

mov eax, arrayD ; EAX = 10000h
mov eax, [arrayD+4] ; EAX = 20000h

invoke ExitProcess,0

main endp
end main

加减法

算数指令

  • INC指令实现操作数加1
  • DEC指令实现操作数减1
  • ADD指令实现源操作数和目的操作数相加
  • SUB指令实现目的操作数减去源操作数
  • NEG指令实现操作数符号翻转

状态标志

表示受算数影响的CPU状态标志:

  • 算数结果为负,符号标志位(SF)为1
  • 与目标操作数相比,无符号算术运算操作结果太大,进位标志位(CF)为1
  • 执行算术或布尔指令后,奇偶标志位(PF)能反映出目标操作数最低有效字节中1的个数为奇数还是偶数
  • 目的操作数位3有进位或借位,辅助进位标志位(AC)为1,主要用于二进制编码的十进制数(BCD)运算,如1+0Fh,和数在位4上为1,这是位3的进位,AC=1
  • 算术操作结果为0,零标志位(ZF)为1
  • 有符号算术操作结果超过目标操作数范围时,溢出标志位(OF)为1,典型的就是两个正数相加得到了一个负数,两个负数相加得到了一个正数

ps:若NEG求反后原寄存器无法装下,则OF置1,寄存器内结果不变,如:

1
2
mov al,-128 ;AL=10000000b
neg al ;AL=10000000b, OF=1

Q:写一个汇编程序实现a=R = -X + (Y - Z),其中abcd均为变量,为结果,26, 30, 40
A:

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
.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
; 执行运算Rval = -Xval + (Yval - Zval)
mov eax, Xval
neg eax
mov ebx, Yval
sub ebx, Zval
add eax, ebx
mov Rval, eax

INVOKE ExitProcess,0

main ENDP
END main

与数据相关的运算符和伪指令

  • OFFSET运算符返回的是一个变量与其所在段起始地址之间的距离
  • PTR可以重写操作数的默认大小类型
  • TYPE返回的是一个操作数或者数组中每个元素的大小
  • LENGHOF返回数组中元素的个数
  • SIZEOF返回数组初始化时使用的字节数

Q:假如有如下定义:

1
2
3
4
5
6
7
8
9
10
11
;Q1
.data
myArray BYTE 10,20,30,40,50
BYTE 60,70,80,90,100

;Q2
.data
myDouble DWORD 12345678h
.code
mov ax,WORD PTR myDouble

Q1: TYPE,LENGHOF,SIZEOF结果为多少?
Q2:AX=?

K:

  • 如果数组定义占多行,LENGHOF只针对第一行,故为5,而SIZEOF返回值等于TYPE与LENGHOF的乘积,但如果该定义多行数组第一行结尾加上一个逗号,LENGHOF就为10
  • 操作数大小不匹配,则我们无法直接让myDouble的后四位5678h移动到AX中,但使用PTR修改大小即可。为啥是后四位?想想x86处理器的小端存储

A1:1,5,5
A2:5678h

间接寻址

直接寻址很少用于数组的处理,反之,会用寄存器作为指针(称为间接寻址)并控制该寄存器的值。如果一个操作数使用的是间接寻址,就称之为间接操作数
Q:(判断)
1.任何一个32位寄存器都可以用作间接操作数
2.指令inc[esi]非法
3.array[esi]是变址操作数

A:T T T

K:

保护模式

  • 任何一个32位通用寄存器(EAX,EBX,ECX,EDX,ESI,EDI,EBP,ESP)加上括号就是一个间接操作数。寄存器中存放的是数据的地址。
1
2
3
4
5
6
.data
byteVal BYTE 10h
.code
mov esi,OFFSET byteVal
mov al,[esi]

上面代码就是常见用法,MOV指令使用间接操作数作为源操作数,解析ESI中的偏移量,并将该内存的值送入AL,当然也可以mov [esi],bl 即将BL内容复制到ESI寻址的内存地址中

使用PTR

inc [esi]会产生operand must have size的错误,汇编器不知道ESI指针的类型是啥,我们需要使用PTR

1
inc BYTE PTR [esi]

变址操作数

变址操作数是指,在寄存器上加上常数产生一个有效地址。每个32位通用寄存器都可以作为变址寄存器。

constant[reg]
[constant + reg]
变址操作非常适合数组处理,在访问第一个元素前,变址寄存器需要初始化为0

1
2
3
4
5
.data
arrayB BYTE 10h,20h,30h
.code
mov esi,0
mov al,arrayB[esi] ;AL=10h

需要注意,arrayB为BYTE型时,下一个为[esi+1],arrayB为WORD型时,下一个为[esi+2],这同样和数组元素大小有关,简化这一步骤,可以使用比例因子

1
2
3
4
5
6
7
8
9
.data 
arrayB DWORD 1,2,3,4
.code
mov esi,3
mov eax,array[esi*4]

;使用TYPE使程序更加灵活
mov esi,3
mov eax,array[esi*TYPE arrayB]

Q:分析下面程序填结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.data
myBytes BYTE 10h,20h,30h,40h
myWords WORD 8Ah,3Bh,72h,44h,66h,
myDoubles DWORD 1,2,3,4
myPointer DWORD myDoubles

.code
mov esi,OFFSET myBytes
mov ax, [esi] ;a. AX =
mov eax, DOWRD PTR myWord ;b. EAX =
mov esi, myPointer
mov ax, [esi+2] ;c.AX =
mov ax, [esi+6] ;d.AX =
mov ax, [esi-4] ;e.AX =

K:

  • myPointer DWORD myDoubles为定义一个指向双字的指针,值为myDoubles(地址),可以联想求数组大小时x = $ - array 中的array就是首地址
  • TYPEDEF可以创建用户定义类型,如:PBYTE TYPEDEF PTR BYTE创建了自定义的PBYTE,一个字节的指针

A:a:0010h; b:003B008Ah; c:0; d:0; e:0044h

  • 将字类型重定义为双字,则接下来的2字节的内存和该2字节的内存的值一起组成一个双字(小端顺序),自然b就是003B008Ah
  • 双字在内存里4个字节存一个数,则该部分排列为00h,00h,00h,01h,00h,00h,00h…,自然[esi+2]得到0,[esi+4]也为0
  • .data的数据都是连续存储的,减4得到向前4字节,是44h

JMP&LOOP

Q(判断):1.JMP指令只能跳转到当前过程中的标号。2.JMP是条件跳转指令
A:T F
K:JMP为无条件跳转,目标地址的偏移量直接送入指令指针寄存器,从而从新的地址开始

Q:循环开始时,如果ECX初始化为0,则LOOP指令要循环多少次(假设循环中没有其他指令修改ECX)
A:FFFFFFFFh次
K:LOOP使用ECX计数器循环。LOOP指令为条件跳转,每次执行到LOOP语句时,先使ECX-1,然后判断是否为0,0就结束LOOP,非0跳转到目标给出标号。若循环前初始化ECX=0,则减1变成FFFFFFFFh,真就跑死CPU了。

Q:实地址模式中LOOP和LOOPD分别使用哪个寄存器作为计数器?
A:LOOP–CX; LOOPD–ECX

  • 另外还需注意一点,LOOP指令运行跳转目标的范围为-128~127字节内,内存中地址超出跳转范围会报错

Q:分析下面程序:求EAX的最后值

1
2
3
4
5
6
7
8
9
    mov eax,0
mov ecx,10 ;外层循环计数器
L1:
mov eax,3
mov ecx,5 ;内层循环计数器
L2:
add eax,5
loop L2
loop L1

A:你以为是28(3+5*5)?,是死循环哒!L2循环结束时ecx为0,接着执行loop L1,ecx-1,每次循环结束时ecx都会被置成FFFFFFFFh,gg
K:更正方法:循环嵌套时最好使用变量保存进入内层时ecx的值,内层结束后再恢复,模板如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.data
count DWORD ?
.code
mov ecx,100 ;设置外层循环计数值
L1:
mov count,ecx
mov ecx,20 ;设置内层循环计数值
L2:
.
.
.
loop L2
mov ecx, count ;恢复外层循环计数值
loop L1

写在后面

  • 写loop时一定要注意:
    我就是学习汇编语言时长1周的个人练习生,喜欢top:jmp top,loop死循环
  • 练习题会随后更
Author

Ctwo

Posted on

2019-07-19

Updated on

2020-10-25

Licensed under

Comments