知识导图 知识导图
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
首页
iOS知识
计算机软件
  • 即时通讯网 (opens new window)
  • 开发常用网站 (opens new window)
  • MD

  • Vue

  • C语法

  • C++语法

  • 汇编语言

    • Day1-前言
    • Day2-语言发展
    • intel-32基础语法(上)
    • intel-32基础语法(中)
    • intel-32基础语法(下)
      • ASCII数值
        • ASCII数值操作
        • 1、ASCII 可打印字符对照表
        • 2、ASCII 非打印控制字符
        • 3、扩展 ASCII 可打印字符
        • 数值数据
        • ASCII表示
        • 压缩
      • 标志位
        • 状态字(PSW)
        • 标志寄存器(flag)
        • OF标志
        • DF标志
        • IF标志
        • TF标志
        • SF标志
        • ZF标志
        • AF标志
        • PF标志
        • CF标志
        • 1.1 abc指令
        • 1.2 sbb指令
        • 1.3 cmp指令
        • 1.4 条件转移指令
        • 1.5 串传送指令
        • 1.6 pushf和popf指令
        • 1.7 Debug
      • 字符串
        • 字符串
        • 字符串指令
        • 标志位重置指令
        • REP重复执行指令
        • MOVS指令
        • LODS指令
        • STOS指令
        • REP、REPE 重复指令
        • CMPS指令
        • SCAS指令
      • 数组
        • 数组
      • 堆栈
      • 递归
        • 递归
  • 软件编程及算法
  • 汇编语言
2023-08-16
目录

intel-32基础语法(下)

# ASCII数值

当数字显示在屏幕上或从键盘输入时,它们是ASCII形式存储的,也就是 输入‘1’ 实际是 49

比如:

mov  eax,'0'

此时,eax 真实数据并非十进制数据0,而是 0的 ASCII 值 48 或者 30 .

所以计算的时候,ASCII 十进制 的话 要先 - 48 得到真实的0

下面观察一组0-9数据表:

Bin(二进制) Oct(八进制) Dec(ASCII 十进制) Hex(ASCII 十六进制) Dec(十进制数值) 解释
0011 0000 060 48 0x30 0 字符0
0011 0001 061 49 0x31 1 字符1
0011 0010 062 50 0x32 2 字符2
0011 0011 063 51 0x33 3 字符3
0011 0100 064 52 0x34 4 字符4
0011 0101 065 53 0x35 5 字符5
0011 0110 066 54 0x36 6 字符6
0011 0111 067 55 0x37 7 字符7
0011 1000 070 56 0x38 8 字符8
0011 1001 071 57 0x39 9 字符9

# ASCII数值操作

假设输入的数值16进制结果 以ASCII 十进制 数理解如下操作:

1、最终输出eax结果:d

mov  eax,'2'	;eax = 50
mov  ebx,'2'	;ebx = 50
add  eax, ebx	;eax = 50 + 50 = 100 

;此时打印eax的值不是4,打印的是 ASCII 100 对应的值d

2、最终输出eax结果:菱形符号控制符号,不能正常输出

mov  eax,2	;eax = 2
mov  ebx,2	;ebx = 2
add  eax, ebx	;eax = 2 + 2 = 4 

;此时打印eax的值不是4,打印的是 ASCII 4 对应的值菱形符号控制符号

3、最终输出eax结果:4

mov  eax,'2'	;eax = 50
mov  ebx,2	;ebx = 2
add  eax, ebx	;eax = 50 + 2 = 52 

;此时打印eax的值是 ASCII 52 对应的值4

4、最终输出eax结果:4

mov  eax,'2'	;eax = 50
sub  eax,'0'	;eax = 50 - 48 = 2

mov  ebx,'2'	;ebx = 50
sub  ebx,'0'	;ebx = 50 - 48 = 2

add  eax, ebx	;eax = 2 + 2 = 4
add  eax, '0' ;eax = 48 + 4 = 52 

;此时打印eax的值是 ASCII 52 对应的值4

下面从网络上收集了ASCII 0 -159 对应的数据

ASCII码大致由三部分组成:

# 1、ASCII 可打印字符对照表

数字 32–126 分配给了能在键盘上找到的字符,当您查看或打印文档时就会出现。注:十进制32代表空格 ,十进制数字 127 代表 DELETE 命令。下面是ASCII码和相应数字的对照表

ASCII 码 字符 ASCII 码 字符 ASCII 码 字符 ASCII 码 字符
十进位 十六进位 十进位 十六进位 十进位 十六进位 十进位 十六进位
032 20 056 38 8 080 50 P 104 68 h
033 21 ! 057 39 9 081 51 Q 105 69 i
034 22 " 058 3A : 082 52 R 106 6A j
035 23 # 059 3B ; 083 53 S 107 6B k
036 24 $ 060 3C < 084 54 T 108 6C l
037 25 % 061 3D = 085 55 U 109 6D m
038 26 & 062 3E > 086 56 V 110 6E n
039 27 ' 063 3F ? 087 57 W 111 6F o
040 28 ( 064 40 @ 088 58 X 112 70 p
041 29 ) 065 41 A 089 59 Y 113 71 q
042 2A * 066 42 B 090 5A Z 114 72 r
043 2B + 067 43 C 091 5B [ 115 73 s
044 2C , 068 44 D 092 5C \ 116 74 t
045 2D - 069 45 E 093 5D ] 117 75 u
046 2E . 070 46 F 094 5E ^ 118 76 v
047 2F / 071 47 G 095 5F _ 119 77 w
048 30 0 072 48 H 096 60 ` 120 78 x
049 31 1 073 49 I 097 61 a 121 79 y
050 32 2 074 4A J 098 62 b 122 7A z
051 33 3 075 4B K 099 63 c 123 7B {
052 34 4 076 4C L 100 64 d 124 7C |
053 35 5 077 4D M 101 65 e 125 7D }
054 36 6 078 4E N 102 66 f 126 7E ~
055 37 7 079 4F O 103 67 g 127 7F DEL

# 2、ASCII 非打印控制字符

ASCII 表上的数字 0–31 分配给了控制字符,用于控制像打印机等一些外围设备。例如,12 代表换页/新页功能。此命令指示打印机跳到下一页的开头。

进制 十六进制 字符 十进制 十六进制 字符
0 00 空 16 10 数据链路转意
1 01 头标开始 17 11 设备控制 1
2 02 正文开始 18 12 设备控制 2
3 03 正文结束 19 13 设备控制 3
4 04 传输结束 20 14 设备控制 4
5 05 查询 21 15 反确认
6 06 确认 22 16 同步空闲
7 07 震铃 23 17 传输块结束
8 08 backspace 24 18 取消
9 09 水平制表符 25 19 媒体结束
10 0A 换行/新行 26 1A 替换
11 0B 竖直制表符 27 1B 转意
12 0C 换页/新页 28 1C 文件分隔符
13 0D 回车 29 1D 组分隔符
14 0E 移出 30 1E 记录分隔符
15 0F 移入 31 1F 单元分隔符

# 3、扩展 ASCII 可打印字符

十进制 十六进制 字符 十进制 十六进制 字符
32 20 space 80 50 P
33 21 ! 81 51 Q
34 22 " 82 52 R
35 23 # 83 53 S
36 24 $ 84 54 T
37 25 % 85 55 U
38 26 & 86 56 V
39 27 ' 87 57 w
40 28 ( 88 58 X
41 29 ) 89 59 Y
42 2A * 90 5A Z
43 2B + 91 5B [
44 2C , 92 5C /
45 2D - 93 5D ]
46 2E . 94 5E ^
47 2F / 95 5F _
48 30 0 96 60 `
49 31 1 97 61 a
50 32 2 98 62 b
51 33 3 99 63 c
52 34 4 100 64 d
53 35 5 101 65 e
54 36 6 102 66 f
55 37 7 103 67 g
56 38 8 104 68 h
57 39 9 105 69 i
58 3A : 106 6A j
59 3B ; 107 6B k
60 3C < 108 6C l
61 3D = 109 6D m
62 3E > 110 6E n
63 3F ? 111 6F o
64 40 @ 112 70 p
65 41 A 113 71 q
66 42 B 114 72 r
67 43 C 115 73 s
68 44 D 116 74 t
69 45 E 117 75 u
70 46 F 118 76 v
71 47 G 119 77 w
72 48 H 120 78 x
73 49 I 121 79 y
74 4A J 122 7A z
75 4B K 123 7B {
76 4C L 124 7C
77 4D M 125 7D }
78 4E N 126 7E ~
79 4F O 127 7F DEL

# 数值数据

section .text
   global _start        ;must be declared for using gcc
        
_start:                 ;tell linker entry point
   mov  eax,'5'
   sub     eax, '0'
        
   mov  ebx, '6'
   sub     ebx, '0'
   add  eax, ebx
   add  eax, '0'
        
   mov  [sum], eax
   mov  ecx,msg 
   mov  edx, len
   mov  ebx,1            ;file descriptor (stdout)
   mov  eax,4            ;system call number (sys_write)
   int  0x80             ;call kernel
        
   mov  ecx,sum
   mov  edx, 1
   mov  ebx,1            ;file descriptor (stdout)
   mov  eax,4            ;system call number (sys_write)
   int  0x80             ;call kernel
        
   mov  eax,1            ;system call number (sys_exit)
   int  0x80             ;call kernel
        
section .data
msg db "The sum is:", 0xA,0xD 
len equ $ - msg   
segment .bss
sum resb 1

结果:(显然结果不是我们想看到的)

The sum is:
;

TODO:

1、每次运算都要对每个寄存器处理有一定消耗

2、能计算个位,但是没法进行进位,比如不能正确表示出11

# ASCII表示

在ASCII表示中,十进制数字存储为ASCII字符字符串,运算需要使用正确的汇编指令进行处理

输入‘0’,结果其实是

30H

输入十进制值0123存储为:

30 		31     32      33H

其中,30H是0的ASCII值,31H是1的ASCII值,依此类推。

所以要输出一个11需要这样:

section .text
   global _start        
        
_start:   
   mov     ax, 3131H	 ;设置 ax值 11
   
   mov     [res], ax

   mov  edx,2           ;长度2
   mov  ecx,res         ;赋值
   mov  ebx,1           
   mov  eax,4           
   int  0x80            
        
   mov  eax,1           
   int  0x80            
                      
section .bss
res resb 2  

所以在本篇汇编中是不能完全直接使用 add 、sub直接加减的

1、处理ASCII运算的指令

ASCII运算的指令 (未压缩) 解释
AAA(⚠️ 都是单指令,后不接操作数) 加法后ASCII调整
AAS 减法后的ASCII调整
AAM 乘法后ASCII调整
AAD 除法前ASCII调整

2、处理对象

这些指令处理的是 ax(eax 等),不需要指定,类似 ‘隐含寻址’

3、使用方式

;加法运算
add     ax, '6'
aaa

;减法运算
sub     ax, '6'
aas

示例:

计算 5+6=11

section .text
   global _start        
        
_start:                 ;tell linker entry point
   ;初始化操作,清空ax中的 高地位数据
   sub     al, ah
   sub     ah, ah
   
   ;运算
   mov     ax, '5'			;ax = 00 35H
   
   add     ax, '6'			;ax = 00 35H + 00 36H = 00 6BH
   aaa		;调整ax中值(不是其它寄存器)
   ;⚠️ 使用add后接着使用aaa表示格式化,将上面加法后ASCII值进行调整
   ;调整前 00 6BH ,处理后 ax = 01 01H,高低位各一个1表示 11的值
   
   or      al, 30h			;ax = 01 31H
   or      ah, 30h			;ax = 31 31H
   
   mov     [res], ax		;res = 11

   mov  edx,2           ;11占两个字节,如果设置1个字节的长度的话返回的是1不是11
   mov  ecx,res         ;写入结果
   mov  ebx,1           
   mov  eax,4           
   int  0x80            
        
   mov  eax,1           
   int  0x80           
                      
section .bss
res resb 2  

结果:

11

# 压缩

对于一个十进制数1234

1、1234 的 ASCII十六进制表示:

31 32 33 34 H

对应的未压缩十进制表示:

01 02 03 04

2、所以前面上一个示例中 aaa 的结果就是未压缩十进制表示

01 01

3、对应压缩后十进制表示:

12 34

从上可以看到,压缩后可以减少一半的存储空间。

压缩后数据运算指令:(⚠️ 不支持 )

ASCII运算的指令 (压缩) 解释
DAA 加法后的十进制调整
DAS 减后的十进制调整
乘法 不支持
除法 不支持

对比加法运算后 aaa(未压缩) 和 daa (压缩)结果

1、aaa

mov     ax, '5'			;ax = 00 35H
add     ax, '6'			;ax = 00 6BH
aaa 								;ax = 01 01H

2、daa

mov     ax, '5'			;ax = 00 35H
add     ax, '6'			;ax = 00 6BH
daa 								;ax = 00 11H

示例:

section .text
   global _start        ;must be declared for using gcc

_start:                 ;tell linker entry point

   mov     esi, 4       ;pointing to the rightmost digit
   mov     ecx, 5       ;num of digits
   clc				; 清除标志位 CF的值,即CF=0
add_num:  
   mov  al, [num1 + esi]
   adc  al, [num2 + esi]
   aaa
   pushf
   or   al, 30h
   popf
        
   mov  [sum + esi], al
   dec  esi
   num  add_num
        
   mov  edx,len         ;message length
   mov  ecx,msg         ;message to write
   mov  ebx,1           ;file descriptor (stdout)
   mov  eax,4           ;system call number (sys_write)
   int  0x80            ;call kernel
        
   mov  edx,5           ;message length
   mov  ecx,sum         ;message to write
   mov  ebx,1           ;file descriptor (stdout)
   mov  eax,4           ;system call number (sys_write)
   int  0x80            ;call kernel
        
   mov  eax,1           ;system call number (sys_exit)
   int  0x80            ;call kernel

section .data
msg db 'The Sum is:',0xa        
len equ $ - msg                 
num1 db '12345'
num2 db '23456'
sum db '     '

尝试一下 (opens new window)

编译并执行上述代码后,将产生以下结果-

The Sum is:
35801

# 标志位

前面在介绍逻辑章节的时候使用到了标志位,此节详细介绍每一个标志位(intel 16、32位)。

CPU内部的寄存器中有一种特殊的寄存器,被称为标志寄存器,具有以下三种作用,:

(1)用来存储相关指令的某些执行结果;

(2)用来为CPU执行相关指令提供行为依据;

(3)用来控制CPU的相关工作方式。

# 状态字(PSW)

程序运行中一些记录信息,根据CPU不同,存储状态信息的寄存器也不同,有16位,32位,64位等等

# 标志寄存器(flag)

标志,顾名思义就是记录一些状态当前的信息,方便做判断,

flag寄存器是按位起作用的,每一位都有专门的含义,8086 CPU的flag寄存器的结构:

位(高-低) flag(计算结果标志位) 含义(计算后的标记)
15
14
13
12
11 OF(有符号运算是否有溢出) if (结果溢出) { of=1} else { of=0 }
10 DF(递增递减方向) si、di递增 df=0,si、di递减df=1
9 IF(中断允许) 允许并响应外部中断时if=1,反之屏蔽
8 TF(调试) tf=1表示处理器每次只执行一条指令,调试模式
7 SF(非负) if (结果负数) { sf=1} else { sf=0 }
6 ZF(非0) if (计算结果为0) { zf=1} else { zf=0 }
5
4 AF(辅助进位) af=1表示运算过程中最后四位有进位
3
2 PF(奇偶) if (计算结果为偶数) { pf=1} else { pf=0 }
1
0 CF(无符号运算是否有借位) 结果显示不下产生了借位,cf=1

其中1、3、5、12、13、14、15位在8086 CPU中没有使用

# OF标志

有符号运算溢出标志位。

记录了有符号运算的结果是否发生了溢出,如果发生溢出OF=1,如果没有OF=0;

# DF标志

方向标志位。

在串处理指令中,每次操作后,如果DF=0,si、di递增,如果DF=1,si、di递减;

DF的值是由程序员进行设定的 cld命令是将DF设置为0,std命令是将DF设置为1;

# IF标志

中断允许标志位。

它用来控制是否允许接收外部中断请求。

若IF=1,能响应外部中断,反之则屏蔽外部中断;

# TF标志

调试标志位。

当TF=1时,处理器每次只执行一条指令,即单步执行;

# SF标志

flag的第7位是SF,符号标志位。

它记录相关指令执行后,其结果是否为负。如果结果为负,sf=1;如果非负,sf=0。

计算机中通常用补码来表示有符号数据(负数)。

运算后可以通过它来得知结果的正负。

mov ax,1
sub ax,2
;1-2<0,此时sf=1

# ZF标志

flag的第6位是ZF,零标志位

它记录相关指令执行后,结果为0,ZF=1(记录下是0这样的肯定信息),结果不为0,ZF=0(表示结果非0)。

mov ax,5
sub ax,5
;指令执行后,结果为0,则ZF=1

mov ax,5
and ax,0
;指令执行后,结果为0,则ZF=1

mov ax,5
sub ax,1
;指令执行后,结果为4,不为0,则ZF=0

mov ax,1
or ax,0
;指令执行后,结果为1,不为0,则ZF=0

# AF标志

辅助进位标志位。

运算过程中看最后四位,不论长度为多少。最后四位向前有进位或者借位,AF=1,否则AF=0;

# PF标志

flag的第2位是PF,奇偶标志位。

记录指令执行后结果所有的二进制位中1的个数。为偶数,PF=1;为奇数,PF=0。

mov al,1
add al,2
;执行结果为00000011B,有2个1,偶数,则PF=1

mov al,1
or al,0
;执行后结果为00000001B,有1个1,奇数,则PF=1

# CF标志

flag的第0位是CF,无符号运算进位标志位。

是无符号运算记录了运算结果的最高有效位向更高位的进位值,或从更高位的借位值。

对于位数为N的无符号数,其对应的二进制信息的最高位,即第N-1位,就是它的最高有效位,假想存在第N位是相对于最高有效位的更高位。

mov al,98h
add al,al      ;98H + 98H = 01 30H; al 只有两个字节,所以执行后(al)=30H,cf=1,cf记录了从最高有效位向更高位的进位值
add al,al      ;30H + 30H = 60H; 执行后(al)=60H,cf=0,cf记录了从最高有效位向更高位的进位值

sub al,al      ;执行后(al)=0,cf=0,cf记录了向更高位的借位值

# 1.1 abc指令

adc是带进位加法指令,利用了CF位上记录的进位值。

格式:adc 操作对象1,操作对象2

功能:操作对象1=操作对象1+操作对象2+CF。

mov ax,2
mov bx,1
sub bx,ax
adc ax,1
执行后 (ax)=4,相当于计算(ax)+1+CF=2+1+1+4

mov ax,1
add ax,ax
adc ax,3
执行后(ax)=5,相当于执行(ax)+3+CF=2+3+0=5

mov al,98H
add al,al
adx al,3
执行后 (al)=34H,相当于执行(ax)+3+CF=30H+3+1=34H

由adc指令前面的指令,决定在执行adc指令的时候加上的CF的值的含义。也就是说关键在于所加上的CF值是被什么指令设置的。

1、进位值

如果是被add指令设置的,那么它的含义就是进位值。

加法运算分两步进行:

①低位相加

②高位相加加上低位相加产生的进位值。

CPU提供adc指令的目的,就是来进行加法的第二步运算的。adc指令和add指令相配合就可以对更大的数据进行加法运算。

2、借位值

如果CF的值是被sub指令设置的,那么它的含义就是借位值;

编程:计算1EF000H+201000H,结果存放在AX(高16位)和BX(低16位)中。

mov ax,001EH
mov bx,0F000H
add bx,1000H
adc ax,0020H
;编程:1EF0001000H+2010001EF0H,结果存放在AX(高16位)、BX(次16位)中和cx(低16位)。

计算分三步进行:

(1)先将低16位相加,完成后,CF中记录本次相加的进位值。
(2)再将次高16位和CF(来自低16位的进位值)相加,完成后,CF中记录本次相加的进位值。
(3)最后高16位和CF(来自次高16位的进位值)相加,完成后,CF中记录本次相加的进位值。
mov ax,001EH
mov bx,0F000H
mov cx,1000H
add cx,1EF0H
add bx,1000H
adc ax,0020H

编程:对两个128位数据进行相加

assume cs:code,ds:data
    data segment
            db 16 dup(88H)
            db 16 dup(11H)
    data ends
    code segment
    start:
            mov ax,data
            mov ds,ax
            mov si,0
            mov di,16
            mov cx,8
            call add128
            mov ax,4C00H
            int 21H
    add128:
            push ax
            push cx
            push si
            push di
            sub ax,ax;将CF设置为0
        s:
            mov ax,[si]
            adc ax,[di]
            mov [si],ax
            inc si;不能用add si,2代替
            inc si;因为会影响cf位
            inc di;而loop和inc不会影响
            inc di
            loop s
             pop di
             pop si
             pop cx
             pop ax
             ret
    code ends
end start

# 1.2 sbb指令

sbb是带借位减法指令,利用了CF位上记录的借位值。

格式:sbb 操作对象1,操作对象2

功能:操作对象1=操作对象1-操作对象2-CF

利用sbb指令我们可以对任意大的数据进行减法运算。

sbb和adc是基于同样的思想设计的两条指令,在应用思路上sbb和adc类似。

编程:计算003E1000H-00202000H,结果放在ax,bx中

mov bx,1000H
mov ax,003EH
sub bx,2000H
sbb ax,0020H

# 1.3 cmp指令

cmp是比较指令,功能上相当于减法指令,只是不保存结果。

格式:cmp 操作对象1,操作对象2

功能:计算操作对象1-操作对象2但不保存结果,仅仅是根据计算结果对标志寄存器进行设置。

cmp指令运算执行后通过做减法将对标志寄存器产生影响,其他相关指令通过识别这些被影响的标志寄存器位来得知比较结果。

cmp ax,ax
;执行后结果为0,ZF=1,PF=1,SF=0,CF=0,OF=0

mov ax,8
mov bx,3
cmp ax,bx
;执行后ax、bx的值不变,ZF=0,PF=1,SF=0,CF=0,OF=0
;通过cmp指令执行后,相关标志位的值就可以看出比较的结果。 
;指令"cmp ax,bx”的逻辑含义是比较ax和bx中的值

判断结果大小

指令"cmp ax,bx”的逻辑含义是比较ax和bx中的值,如果执行后:

cmp ax,bx 比较后的标志状态 含义
ZF=1 ax = bx
ZF=0 ax != bx
CF=1 ax < bx
CF=0 ax >= bx
CF=0 & ZF=0 ax > bx
CF=1 & ZF=1 ax <= bx

判断结果正负

⚠️ sf记录的是ah中的8位二进制信息所表示的数据的正负, 而cmp并不修改寄存器的值,单纯地考察SF的值不可能知道结果的正负

OF 0: 没有溢出,1:溢出
SF 1:表示负数,0 正数
OF=0 & SF=1 (ah)<(bh)
OF=0 & SF=0 (ah)≥(bh)
OF=1 & SF=1 (ah)>(bh)
OF=1 & SF=0 (ah)<(bh)

1、无溢出时通过sf标记可判断结果的正负

如果SF=1或SF=0,OF=0,逻辑上真正结果的正负=实际结果的正负。

of=0,说明没有溢出,逻辑上真正结果的正负=实际结果的正负;

若sf=1,实际结果为负,所以逻辑上真正的结果为负,所以(ah)<(bh)

若sf=0,实际结果非负,所以逻辑上真正的结果非负,所以(ah)≥(bh)

2、有溢出时通过sf标记 判断结果的**正负****

如果SF=1或SF=0,OF=1,逻辑上真正结果的负正≠实际结果的正负。

of=1,说明有溢出,逻辑上真正结果的正负≠实际结果的正负;

若sf=1,实际结果为负,而又有溢出,这说明是由于溢出导致了实际结果为负,简单分析一下,就可以看出,如果因为溢出导致了实际结果为负,那么逻辑上真正的结果必然为正,则说明(ah)>(bh)。

若sf=0,实际结果非负,而of=1说明有溢出,则结果非0,所以,实际结果为正。实际结果为正,而又有溢出,这说明是由于溢出导致了导致了实际结果非负,简单分析一下就可以看出,如果因为溢出导致了实际结果为正,那么逻辑上真正的结果必然为负,则说明(ah)<(bh)。

# 1.4 条件转移指令

条件转移指令需根据比较指令、并配合标志综合使用,

比如与cmp相配使用,根据cmp指令的比较结果(cmp指令执行后相关标志位的值)进行工作的指令。

cmp指令可以同时进行两种比较,无符号数比较和有符号数比较,所以根据cmp指令的比较结果进行转移的指令也分为两种:

1、无符号比较

根据无符号数的比较结果进行转移的条件转移指令,它们检测ZF、CF的值;

2、有符号比较

根据有符号数的比较结果进行转移的条件转移指令,它们检测SF、OF、ZF的值。

跳转指令 跳转条件 标志位
JE = ZF=1
JNE != ZF=0
JB < CF=1
JNB >= CF=0
JA > CF=0 & ZF=0
JNA <= CF=1 & ZF=1
 ;编程:如果(ah)=(bh)则(ah)=(ah)+(ah),否则(ah)=(ah)+(bh)。

  cmp ah,bh
  je s                       ;ZF=1则跳转
  add ah,bh
  jmp short ok
    s: add ah,bh
   ok: ret 

 ;je检测的是ZF的位置,不管je前面是什么指令,只要CPU执行je指令时,ZF=1那么就发生转移。
		mov ax,0
    mov ax,0
    je s
    inc ax
s:
    inc ax
    
  ;执行后(ax)=1,add ax,0使得ZF=1,所以je指令将进行转移。 

编程:统计data段中数值为8的字节的个数,用ax保存统计结果。

方案一

assume cs:code
    data segment
            db 8,11,8,1,8,5,63,38
    data ends
    code segment
    start:
            mov ax,data
            mov ds,ax
            mov bx,0;ds:bx指向第一个字节
            mov ax,0;初始化累加器
            mov cx,0
        s:
            cmp byte ptr [bx],8;和8进行比较
            jne next;如果不相等转到next,继续循环
            inc ax;如果相等就计数值加1
        next:
            inc bx
            loop s;执行后:(ax)=3
            mov ax,4c00h
            int 21h
    code ends
end segment

方案二

assume cs:code
    data segment
            db 8,11,8,1,8,5,63,38
    data ends
    code segment
    start:
            mov ax,data
            mov ds,ax
            mov bx,0;ds:bx指向第一个字节
            mov ax,0;初始化累加器
            mov cx,0
        s:
            cmp byte ptr [bx],8;和8进行比较
            je ok;如果不相等转到ok,继续循环
            jmp short next;如果不想等就转到next,继续循环
        ok:
            inc ax;如果相等就计数值加1
        next:
            inc bx
            loop s;执行后:(ax)=3
            mov ax,4c00h
            int 21h
    code ends
end segment 

编程:统计data段中数值大于8的字节的个数,用ax保存统计结果。

assume cs:code
    data segment
            db 8,11,8,1,8,5,63,38
    data ends
    code segment
    start:
            mov ax,data
            mov ds,ax
            mov bx,0;ds:bx指向第一个字节
            mov ax,0;初始化累加器
            mov cx,0
        s:
            cmp byte ptr [bx],8;和8进行比较
            jna next;如果大于8转到next,继续循环
            inc ax;如果大于就计数值加1
        next:
            inc bx
            loop s;执行后:(ax)=3
            mov ax,4c00h
            int 21h
    code ends
end segment

# 1.5 串传送指令

1、DF标志:方向标志位

在串处理指令中,控制每次操作后si(一般指向原始偏移地址)、di(一般指向目标偏移地址)的增减。

DF=0:每次操作后si、di递增;

DF=1:每次操作后so、di递减。

2、movsb串传送指令 movsb(mov string byte)串传送指令

以字节为单位传送

格式:movsb

执行movsb相当于以下操作:

(1)((es)*16+(di))=((ds)*16+(si))

(2)如果DF=0,则(si)=(si)+1,(di)=(di)+1;
	
	如果DF=1,则(si)=(si)-1,(di)=(di)-1。
	movsb功能:将ds:si指向的内存单元中的字节送入es:di中,然后根据标志寄存器DF位的值将si和di递增1或递减1。

2、movsw串传送指令

以字为单位传送。

将ds:si指向的内存单元中的字送入es:di中,然后根据标志寄存器DF位的值将si和di递增2或递减2。

movsb和movsw进行的是串传送操作中的一个步骤,一般和rep配合使用,格式:rep movsb,rep的作用是根据cx 的值,重复执行后面的串传送指令。由于每执行一次movsb指令si和di都会递增或递减指向后一个单元或前个单元,则rep movsb就可以循环实现(cx)个字符的传送。

使用串传送指令进行数据的传送,需要提供:

1、传送的原始位置;

2、传送的目的位置;

3、传送的长度;

4、传送的方向。

由于flag的DF位决定着串传送指令执行后,si和di改变的方向,8086CPU提供两条指令对DF位进行设置:

(1)cld指令:将标志寄存器的DF位设置为0;

(2)std指令:将标志寄存器的DF位设置为1。

# 1.6 pushf和popf指令

pushf的功能是将标志寄存器的值压栈,

popf是从栈中弹出数据,送入标志寄存器中。

pushf和popf为直接访问标志寄存器提供了一种方法。

下面的程序执行后ax的值是多少?

mov ax,0
push ax
popf
mov ax,0fff0h
add ax,0010h
pushf
pop ax
and al,11000101b
and ah,00001000b

# 1.7 Debug

标志寄存器在Debug中的表示

标志 值为1的标记 值为。的标记
OF OV NV
SF NG PL
ZF ZR NZ
PF PE PO
CF CY NC
DF DN UP

# 字符串

可以使用 section .data 显式存储字符串,

或者 section .bss 段定义字符串变量再赋值

# 字符串

我们可以使用表示位置计数器当前值的$位置计数器符号来显式存储字符串长度

msg  db  'Hello, world!',0xa ;我们常见的字符
len  equ  $ - msg            ;长度
;$指向字符串变量msg的最后一个字符之后的字节。因此,$ - msg给出字符串的长度

# 字符串指令

对于16位地址,使用SI和DI寄存器,对于32位地址,使用ESI和EDI寄存器。

下表提供了各种版本的字符串指令和假定的操作数空间:

基本指令 操作的寄存器 字节运算 字运算 双字运算 说明
MOVS ES:DI,DS:SI MOVSB MOVSW MOVSD 该指令将1字节,字或双字数据从存储位置移到另一个位置
LODS DS:SI LODSB LODSW LODSD 该指令从存储器加载。如果操作数是一个字节,则将其加载到AL寄存器中;如果操作数是一个字,则将其加载到AX寄存器中,并将双字加载到EAX寄存器中
STOS ES:DI,AX STOSB STOS STOSD 该指令将数据从寄存器(AL,AX或EAX)存储到存储器
CMPS DS:SI,ES:DI CMPSB CMPSW CMPSD 该指令比较内存中的两个数据项。数据可以是字节大小,字或双字
SCAS ES:DI,AX SCASB SCASW SCASD 该指令将寄存器(AL,AX或EAX)的内容与内存中项目的内容进行比较

下面介绍一些会用到的指令

# 标志位重置指令

CLD 将方向标志位DF清零
STD 将方向标志位DF置1

# REP重复执行指令

1、REP(Repeat)汇编指令是一种重复执行指令的控制指令,它通常和其他指令组合使用,用于在处理字符串或数组时进行重复操作。REP指令可以和多个其他指令搭配使用,如MOV、STOS等。其语法如下:

rep instruction ;比如 rep MOVSB

其中,instruction是要重复执行的指令。

2、REP指令会将ECX寄存器中的值作为计数器,循环执行instruction指定的操作。

每次循环都会将ECX减1,直到ECX的值为0为止(类似loop)

3、对于不同的操作码,REP有三种形式:

REPE、REP Repeat while Equal 比较结果相同时继续执行,不相同退出循环
REPNE、REPN Repeat while Not Equal 比较结果不相同时继续执行,相同退出循环
REPZ Repeat while Zero 结果 =0 时继续执行,结果 !=0 退出循环

# MOVS指令

MOVS指令用于将数据项(字节,字或双字)从源字符串SI 到目标字符串DI :

section .text
   global _start 
        
_start:              
   mov  ecx, len       ;s1长度
   mov  esi, s1        ;将s1的字符串送入 si 寄存器中
   mov  edi, s2        ;开辟 s2空间并指定到 di 寄存器
   cld					       ;方向标志位DF清零
   rep  movsb	         ;源字符串SI 送到目标字符串DI
        
   mov  edx,20          ;s2长度
   mov  ecx,s2          ;s2信息
   mov  ebx,1           ;stdout
   mov  eax,4           ;sys_write
   int  0x80            ;call 
        
   mov  eax,1           ;system call number (sys_exit)
   int  0x80            ;call kernel
        
section .data
s1 db 'Hello, world!'
len equ $-s1

section  .bss
s2 resb 20              

结果:

Hello, world!

# LODS指令

该指令从存储器加载。如果操作数是一个字节,则将其加载到AL寄存器中;如果操作数是一个字,则将其加载到AX寄存器中,并将双字加载到EAX寄存器中

示例:

将secret加密显示,加密方式:单字节增加2位

section .text
   global _start       
        
_start:                 
   mov    ecx, 6      ;secret字符串长度,同时也是loop循环的次数
   mov    esi, s1      	;将s1的字符串送入 si 寄存器中
   mov    edi, s2      	;开辟 s2空间并指定到 di 寄存器
      
loop_encrypt: 			;加密(循环6次)
   lodsb				;将si中的'secret'加载到AL(AX或EAX)寄存器
   add al, 02		;al内容偏移2(混淆)
   stosb 				;该指令将数据从寄存器AL(AX或EAX)存储到存储器
   loop    loop_encrypt          
   
   cld					       ;方向标志位DF清零
   rep  movsb	         ;源字符串SI 送到目标字符串DI
        
   mov     edx,20        
   mov     ecx,s2       
   mov     ebx,1        
   mov     eax,4        
   int     0x80         
        
   mov     eax,1        
   int     0x80         
        
section .data
s1 db 'secret key'
len equ $-s1

section .bss
s2 resb 10             

结果:

ugetgv

同样如果想要将密文ugetgv解密,转换片段:

encrypt_code: 			;解密(循环6次)
   lodsb				;将si中的密文'ugetgv'加载到AL(AX或EAX)寄存器
   sub al, 02		;al内容偏移2(还原)
   stosb 				;该指令将数据从寄存器AL(AX或EAX)存储到存储器
   loop    encrypt_code          

结果还原为'secret'

# STOS指令

STOS指令将数据项从AL(对于字节-STOSB),AX(对于字-STOSW)或EAX(对于双字-STOSD)到目标字符串,该目标字符串由内存中的ES:DI指向。

上面示例已经使用了stosb

将大写字符串转换为其小写值转换片段:

   
lower_case:
   lodsb
   or      al, 20h
   stosb
   loop    lower_case    
   
   cld
   rep  movsb
 

# REP、REPE 重复指令

REP前缀在字符串指令(例如-REP MOVSB)之前设置时,会根据放置在CX寄存器中的计数器使该指令重复。REP执行该指令,将CX减1,然后检查CX是否为零。重复指令处理,直到CX为零为止。

方向标志(DF)确定操作的方向。

- 使用CLD(清除方向标志,DF = 0)使操作从左到右。
- 使用STD(设置方向标志,DF = 1)使操作从右到左。

REP前缀也有以下变化:

- REP:这是无条件的重复。重复该操作,直到CX为零为止。
- REPE或REPZ:这是有条件的重复。当零标志指示等于/零时,它将重复操作。当ZF表示不等于零或CX为零时,它将停止。
- REPNE或REPNZ:这也是有条件的重复。当零标志指示不等于/零时,它将重复操作。当ZF指示等于/零或CX减为零时,它将停止。

# CMPS指令

CMPS指令:

比较两个字符串。该指令比较DS:SI和ES:DI寄存器所指向的两个数据项,即一个字节,一个字或一个双字,并相应地设置标志。

REPE:

比较结果相同时继续执行,不相同退出循环,

1、循环次数同loop使用cx寄存器值

2、不同的是,cx递减到0前满足退出循环条件的时候就结束了,不会继续循环,比loop 损耗小

JECXZ:

根据 CX、ECX 寄存器的值跳转: JCXZ(CX 为 0 则跳转)、JECXZ(ECX 为 0 则跳转);

section .text
   global _start           
        
_start: 
   mov esi, s1
   mov edi, s2
   mov ecx, lens2
   
   cld
   repe  cmpsb              ;重复执行cmpsb命令,直到 cx=0或者 比较结果不相同 结束
   jecxz  equal             ;CX 为 0 则跳转
	 jmp nequal

nequal: 
   ;不相同时逻辑
   mov eax, 4
   mov ebx, 1
   mov ecx, msg_neq
   mov edx, len_neq
   int 80h
   jmp exit
        
equal: ;相同时逻辑
   mov eax, 4
   mov ebx, 1
   mov ecx, msg_eq
   mov edx, len_eq
   int 80h
   jmp exit
        
exit: ;退出
   mov eax, 1
   mov ebx, 0
   int 80h
        
section .data
s1 db 'Hello, world!',0      ;源字符
lens1 equ $-s1

s2 db 'Hello, world!', 0     ;比较字符
lens2 equ $-s2

msg_eq db '相同的字符串!', 0xa
len_eq  equ $-msg_eq

msg_neq db '不相同的字符串!'
len_neq equ $-msg_neq

结果

相同的字符串!

# SCAS指令

SCAS指令用于搜索字符串中的特定字符或一组字符。要搜索的数据项应该在AL(对于SCASB),AX(对于SCASW)或EAX(对于SCASD)寄存器中。要搜索的字符串应在内存中,并由ES:DI(或EDI)寄存器指向。查看以下程序以了解概念-

section .text
   global _start        ;must be declared for using gcc
        
_start:                 ;tell linker entry point

   mov ecx,len
   mov edi,my_string
   mov al , 'e'
   cld
   repne scasb
   je found ; when found
   ; If not not then the following code
        
   mov eax,4
   mov ebx,1
   mov ecx,msg_notfound
   mov edx,len_notfound
   int 80h
   jmp exit
        
found:
   mov eax,4
   mov ebx,1
   mov ecx,msg_found
   mov edx,len_found
   int 80h
        
exit:
   mov eax,1
   mov ebx,0
   int 80h
        
section .data
my_string db 'hello world', 0
len equ $-my_string  

msg_found db '包含该字符串!', 0xa
len_found equ $-msg_found

msg_notfound db '不包含该字符串!'
len_notfound equ $-msg_notfound   

结果-:

包含该字符串!

# 数组

C++语法中数组示例:

int marks[6] = {34,  45,  56,  67,  75, 89};

# 数组

我们已经讨论过,汇编程序的数据定义指令用于为变量分配存储空间。变量也可以用一些特定的值初始化。初始化值可以以十六进制,十进制或二进制形式指定。例如,我们可以通过以下两种方式之一来定义单词变量“months”-

MONTHS  DW      12			;十进制表示12月
MONTHS  DW      0CH			;十六进制表示12月
MONTHS  DW      0110B		;二进制表示12月

1、连续数据定义

分配连续内存空间,初始化值0

List1   DW  0, 0 , 0 , 0 , 0 , 0 

定义一维数字数组(连续的相同类型)。

List1 DW  34,  45,  56,  67,  75, 89
;分配了2x6 = 12个字节的连续存储空间

定义相同内容时可以使用 times

List1 TIMES 6 DW 0

定义6个8

List1 TIMES 6 DW 8

2、访问数据

第一个数字的符号地址为List1,第二个数字的符号地址为List1 + 2,依此类推。

比如访问 56

[List1 + 4]

访问第二个名称:

section .text
   global _start        ;must be declared for linker (ld)
        
_start:    
   
   mov  edx,4           ;message length
   mov  ecx, names+4      ;message to write
   mov  ebx,1           ;file descriptor (stdout)
   mov  eax,4           ;system call number (sys_write)
   int  0x80      
   
   mov  eax,1           ;system call number (sys_exit)
   int  0x80            ;call kernel

section .data
global names
names:
DW  'Lucy', 'Andy', 'Pete'

结果

Andy

# 堆栈

内存结构:

堆栈是内存中类似数组的连续内存数据结构,通常会固定一段存储空间。

可以在其中存储数据并从称为“堆栈顶部”的位置删除数据。

入栈:

需要存储的数据被“推送”到堆栈中

出栈:

要检索的数据从堆栈中“弹出”。

LIFO:

堆栈是一种LIFO(后进先出)数据结构,即首先存储的数据最后被检索。

PUSH和POP:

汇编语言为堆栈操作提供了两条指令:PUSH和POP。

语法如下:

PUSH    operand
POP     address/register

堆栈段中保留的内存空间用于实现堆栈。寄存器SS和ESP(或SP)用于实现堆栈。

SS栈顶指针寄存器:

指向堆栈段的开头,也就是起始地址,通常是最高位开始

SP(或ESP)偏移量:

SS起始地址上,再偏移SP的偏移量,就是栈顶的位置,也就是最后存储的一条数据位置。

比如:

从1-5将数据存储到堆栈(0x01-0x09)的存储空间上:

堆栈示意地址 插入的数据 位置
0x09 1 SS:存放栈顶的段地址(最高位为起始地址)
0x08 2
0x07 3
0x06 4
0x05 5 SS: SP:sp偏移量结合的位置为栈顶(最后数据5的位置)
0x04
0x03
0x02
0x01

pop的顺序依次是:5,4,3,2,1 ,和push是相反的

- 只能将字或双字保存到堆栈中,而不是字节。
- 堆栈朝反方向增长,即朝着较低的存储器地址增长
- 堆栈的顶部指向插入堆栈中的最后一个项目。它指向插入的最后一个字的低字节。

使用场景,比如:

MOV     AX, 1
MOV     BX, 2

; 将AX和BX寄存器内容保存在堆栈中
PUSH    AX
PUSH    BX

;将寄存器用着其他用途
MOV     AX, 3
MOV     BX, 4
...

;使用完之后恢复寄存器原始值,最后push的BX要最先pop才行。
POP     BX ;BX=2
POP     AX ;AX=1

以下程序显示整个ASCII字符集。主程序调用一个名为display的过程,该过程显示ASCII字符集。

section .text
   global _start        
        
_start: 
	 ;call 指令表示执行一段代码,需要ret 配合返回并继续执行后续代码
   call init_count ;从'5' 开始递增打印20个ASCII字符
   call next  ;循环打印
   call exit
   
init_count:     
   mov    ecx, 20 
	 ret
next:
   push    ecx
   call display  ;调用显示代码块
        
   pop     ecx  
   mov  dx, [achar]
   cmp  byte [achar], 0dh
   inc  byte [achar]
   loop    next
   ret
   
 display:
   mov     eax, 4
   mov     ebx, 1
   mov     ecx, achar
   mov     edx, 1
   int     80h  
   ret
   
exit:
   mov  eax,1           ;system call number (sys_exit)
   int  0x80            ;call kernel

section .data
achar db '5' 

结果:

56789:;<=>?@ABCDEFGH

# 递归

递归过程是一个调用自身的过程。

上一个示例中我们使用到了 call 指令; call 指令表示执行一段代码,需要ret 配合返回并继续执行后续代码

# 递归

示例:直接递归(方法内调用自己)

3阶乘c示意代码:

int a=3,b=3;

fun mul_fun {
	if (b <= 1) return; // 结束
	b--;
	a = a*b;
	mul_fun();
}

汇编示例:

section .text
   global _start      
        
_start:               

   mov bx, 3
	 mov ax, 3
   call  mul_fun

mul_fun: ;阶乘递归
    cmp   bl, 1
    JLE	  p_total ;<= 1时结束,跳转到打印方法
    dec bl
    mul bl ;ax = al * bl
    call mul_fun
        
p_total: ;打印
   add   ax, 30h
   mov  [fact], ax
   
   mov    edx,1            ;message length
   mov    ecx,fact       ;message to write
   mov    ebx,1          ;file descriptor (stdout)
   mov    eax,4          ;system call number (sys_write)
   int    0x80           ;call kernel
    
   mov    eax,1          ;system call number (sys_exit)
   int    0x80           ;call kernel

section .bss
fact resb 1

结果:

6
intel-32基础语法(中)

← intel-32基础语法(中)

Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式