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

  • Vue

  • C语法

  • C++语法

  • 汇编语言

    • Day1-前言
    • Day2-语言发展
    • intel-32基础语法(上)
    • intel-32基础语法(中)
      • 算术指令
        • INC 指令
        • DEC指令
        • ADD和SUB指令
        • MUL 乘法指令
        • IMUL 乘法指令
        • DIV / IDIV指令
        • DIV / IDIV (x86 - 32位)
      • 逻辑
        • 逻辑指令
        • AND 指令
        • OR 指令
        • XOR 指令
        • TEST 指令
        • NOT 指令
      • 条件
        • 条件
        • 无条件跳转
        • 条件跳转
        • 示例
      • 过程
      • 循环
        • 循环 loop
        • 例子
    • intel-32基础语法(下)
  • 软件编程及算法
  • 汇编语言
2023-08-16
目录

intel-32基础语法(中)

# 算术指令

# INC 指令

INC指令用于将操作数加1。它对可以在寄存器或内存中的单个操作数起作用。

INC指令具有以下语法-

INC destination

目标操作数可以是8位,16位或32位操作数。

例

INC EBX      ;  32-bit 寄存器 自增1
INC DL       ;  8-bit 寄存器 自增1
INC [count]  ;  变量count  自增1

# DEC指令

DEC指令用于将操作数减1。它对可以在寄存器或内存中的单个操作数起作用。

DEC指令具有以下语法-

DEC destination

操作数目的地可以是8位,16位或32位操作数。

segment .data
   count dw  0
   value db  15
        
segment .text
   inc [count]
   dec [value]
        
   mov ebx, count
   inc word [ebx]
        
   mov esi, value
   dec byte [esi]

# ADD和SUB指令

ADD和SUB指令用于对字节,字和双字大小的二进制数据进行简单的加/减,即分别用于添加或减去8位,16位或32位操作数。

ADD和SUB指令具有以下语法-

ADD/SUB destination, source

ADD / SUB指令可以发生在-

  • 寄存器 to 寄存器
  • 内存 to 寄存器
  • 寄存器 to 内存
  • 寄存器 to 常量数据
  • 内存 to 常量数据

但是,像其他指令一样,使用ADD / SUB指令也无法进行存储器到存储器的操作。ADD或SUB操作设置或清除溢出和进位标志。

下面的示例将要求用户输入两位数字,分别将这些数字存储在EAX和EBX寄存器中,将这些值相加,将结果存储在“ res ”存储位置中,最后显示结果。

SYS_EXIT  equ 1
SYS_READ  equ 3
SYS_WRITE equ 4
STDIN     equ 0
STDOUT    equ 1

segment .data 

   msg1 db "Enter a digit ", 0xA,0xD 
   len1 equ $- msg1 

   msg2 db "Please enter a second digit", 0xA,0xD 
   len2 equ $- msg2 

   msg3 db "The sum is: "
   len3 equ $- msg3

segment .bss

   num1 resb 2 
   num2 resb 2 
   res resb 1    

section .text
   global _start    ;must be declared for using gcc
        
_start:             ;tell linker entry point
   mov eax, SYS_WRITE         
   mov ebx, STDOUT         
   mov ecx, msg1         
   mov edx, len1 
   int 0x80                

   mov eax, SYS_READ 
   mov ebx, STDIN  
   mov ecx, num1 
   mov edx, 2
   int 0x80            

   mov eax, SYS_WRITE        
   mov ebx, STDOUT         
   mov ecx, msg2          
   mov edx, len2         
   int 0x80

   mov eax, SYS_READ  
   mov ebx, STDIN  
   mov ecx, num2 
   mov edx, 2
   int 0x80        

   mov eax, SYS_WRITE         
   mov ebx, STDOUT         
   mov ecx, msg3          
   mov edx, len3         
   int 0x80

   ; moving the first number to eax register and second number to ebx
   ; and subtracting ascii '0' to convert it into a decimal number
        
   mov eax, [num1]
   sub eax, '0'
        
   mov ebx, [num2]
   sub ebx, '0'

   ; add eax and ebx
   add eax, ebx
   ; add '0' to to convert the sum from decimal to ASCII
   add eax, '0'

   ; storing the sum in memory location res
   mov [res], eax

   ; print the sum 
   mov eax, SYS_WRITE        
   mov ebx, STDOUT
   mov ecx, res         
   mov edx, 1        
   int 0x80

exit:    
   
   mov eax, SYS_EXIT   
   xor ebx, ebx 
   int 0x80

尝试一下 (opens new window)

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

Enter a digit:
3
Please enter a second digit:
4
The sum is:
7

具有硬编码变量的程序-

section .text
   global _start    ;must be declared for using gcc
        
_start:             ;tell linker entry point
   mov  eax,'3'
   sub     eax, '0'
        
   mov  ebx, '4'
   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

尝试一下 (opens new window)

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

The sum is:
7

⚠️ 认真阅读发现,前面我们示例都是个位数的运算,如果直接换成个位以上的数运算就会得到错误的结果,原因就是溢出,下面在 乘法 中介绍 “进位” 相关的处理

# MUL 乘法指令

1、mul乘法指令是无符号计算的,也就是不支持负数(imul支持负数)

2、mul是单指令,也就是后面只能跟一个参数(imul支持多参)

32 位模式下,MUL(无符号数乘法)指令有三种类型:

1、执行 8 位操作数与 AL 寄存器的乘法; 2、执行 16 位操作数与 AX 寄存器的乘法; 3、执行 32 位操作数与 EAX 寄存器的乘法。

乘积溢出

MUL 指令中的单操作数是乘数。乘积结果存放的空间是被乘数和乘数大小的两倍,因此不会发生溢岀

比如,两个8位二进制数的乘积不会超过16位。两个16位乘积不会超过32位等等(除法会溢出)

MUL无符号、单指令乘法表:

MUL: 无符号乘 ;影响 OF、CF 标志位 ;

指令格式: ;MUL r/m ;参数是乘数 ;

如果参数是 r8/m8, 将把 AL 做乘数, 结果放在 AX ;(8位)

如果参数是 r16/m16, 将把 AX 做乘数, 结果放在 EAX ;(16位)

如果参数是 r32/m32, 将把 EAX 做乘数, 结果放在 EDX:EAX (32位)

被乘数 乘数 乘积存放位置 解析 标记位
AL reg8/mem8 AX MUL操作数是8位寄存器,自动将AL当作被乘数。结果存放AX
AX reg16/mem16 DX:AX MUL操作数是16位寄存器,自动将AX当作被乘数。结果的低位放在AX,高位在DX CF = 1 标记位1表示DX有高位数据。CF = 0 表示DX没有高位数据,AX中已经显示全
EAX reg16/mem32 EDX:EAX MUL操作数是32位寄存器,自动将EAX当作被乘数。结果的低位在EAX,高位在EDX

# 1、8位汇编

8086芯片 intel-8 位汇编个位乘法

MUL(乘法)指令,在C语法中,使用 int a = 2 * 3; 能很方便的计算乘法,而在汇编中却没有这么方便,而只有学习汇编,你才能更深入理解 2 * 3 底层到底是如何实现的

示例:乘积小于10处理

;调用并输出
%macro  run_out 0                      
        mov eax, 4         
        mov ebx, 1 
        int 0x80               
%endmacro

;退出
%macro  return 0                      
        mov eax,1   
        int 0x80              
%endmacro


;乘法:乘积小于10处理 ( num1,num2)
%macro  mul_oper 2                      
         mov  al, %1
         sub  al, '0'

         mov  bl, %2
         sub  bl, '0'
         
         mul  bl  ;这里乘法指令是一种【隐含寻址】方式,下面备注介绍
         add  al, '0'

         mov  [res], al
         mov  ecx,res 
         mov  edx,1                  
%endmacro

section  .text
   global _start    
        
_start:      
   mul_oper '3','3' ;计算3*3结果
   run_out      
   return     

segment .bss
res resb 1

隐含寻址 有些指令的操作码不仅包含了操作的性质,还隐含了部分操作数的地址,如乘法指令MUL,在这条指令中只需指明乘数的地址,而被乘数以及乘积的地址是隐含且固定的。这种将一个操作数隐含在指令码中的寻址方式称为隐含寻址。

例如:

mul  bl ;mul bl的功能把AL中的内容与BL的内容相乘,乘积送到AX寄存器中

;因此上面示例中我们将另一个送入al,然后会自动隐式从 al 取值

# IMUL 乘法指令

imul乘法指令支持符号运算,支持多参数

1、支持正负符号

2、支持2,3个参数,但是三个参数的最后一个必须是立即数

类似的:

; a 16 bit multiplication:
  mov ax, [factor1]
  mov bx, [factor2]
  imul bx              ; 32-bit result in DX:AX
  ; or  imul  word [factor2]

  ; a 32 bit multiplication:
  mov eax, [factor1]
  mov ebx, [factor2] 
  imul ebx             ; 64-bit result in EDX:EAX

在386或更高版本上

	mov   ecx, [factor1]
  imul  ecx, [factor2]    ; result in ecx, no other registers affected
  imul  ecx, ecx          ; and square the result
  
----------------------------  
  
  movsx   ecx, word [factor1]
  movsx   eax, word [factor2]  ; sign-extend inputs to 32-bit
  imul    eax, ecx             ; 32-bit multiply, result in EAX
  imul    eax, eax             ; and square the result
  
----------------------------
  imul  cx, bx, 123        ; requires 186
----------------------------
	imul  ecx, ebx, -123      ; requires 386

# DIV / IDIV指令

# 示例:6/3=2

section .text
   global _start    ;must be declared for using gcc
        
_start:             ;tell linker entry point
   mov  ax,'6'
   sub     ax, '0'
        
   mov  bl, '3'
   sub     bl, '0'
   div  bl
   add  ax, '0'
        
   mov  [res], ax
   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,res
   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 result is:", 0xA,0xD 
len equ $- msg   
segment .bss
res resb 1

在线运行上面示例: (opens new window)

# DIV / IDIV (x86 - 32位)

# DIV 无符号除法

DIV 寄存器或内存(8位) DIV 寄存器或内存(16位) DIV 寄存器或内存(32位)

被除数 除数 商 余数
AX reg/mem8 AL AH
DX:AX reg/mem16 AX DX
EDX:EAX reg/mem32 EAX EDX

比例

mov ax, 0043h      ; 被除数
mov bl, 5          ; 除数
div bl             ; AL = 08h, AH = O3h

⚠️

1、除数不能为0,需要判断

2、商或余数存储超过对应空间大小就会抛出异常

3、不占用的寄存器需要清空

DX 包含的是被除数的高位部分,因此在执行 DIV 指令之前,必须将其清零:

mov dx, 0             ; 清除被除数高16位
mov ax, 8003h         ; 被除数的低16位
mov ex, 100h          ; 除数
div ex                ; AX = 0080h, DX = 0003h

# IDIV 有符号数除法

有符号除法与无符号几乎相同,但是在计算前必须对被除数进行符号扩展。

指令 全称 说明
cbw convert byte to word 将AL的符号位扩展到AH
cwd convert word to doubleword 将AX的符号位扩展到DX
cdq convert doubleword to quadword 将EAX的符号位扩展到EDX
.data
wordval sword -101
 
.code
main PROC
    nop
    mov dx, 0
    mov ax, wordval
    cwd;   DX:AX=FFFF:FF9B,这样的才能保证被除数是负101,否则会被解释为正65435。
    mov bx, 2
    idiv bx
    invoke ExitProcess,0
main ENDP

# 逻辑

常见逻辑指令 and、or、xor、not、test(测试计算数据)等等

# 逻辑指令

格式:

指令 格式
AND AND 操作数1,操作数2
OR OR 操作数1,操作数2
XOR XOR 操作数1,操作数2
TEST TEST 操作数1,操作数2
NOT NOT 操作数1

# AND 指令

实现按位与运算

1、将寄存器清空

AND     AH, 00H     ; 高位AH清空

2、检查奇偶数

AND     AL, 01H     ; 与上 0000 0001

格式示例:

and 【寄存器or内存】【寄存器or内存or立即数】 ✅
and 【内存】【内存】 ❌
运算结果会修改对应的标志:
CF,OF,PF,SF和ZF标志

以下程序说明了这一点-

section .text
   global _start            
        
_start:                     
   mov   ax,   6h           ;设置数据6(二进制 0110 ),下面开始判断它的奇偶
   and   ax, 1              ;通过 0001 & 0110 = 0,此时标记位 ZF=0
   jz    even_f             ; jz: ZF 等于 0 成立,跳转 偶数过程 even_f
   ;jnz   odd_f               jnz: ZF 不等于 0 成立,跳转 奇数过程 odd_f
   jmp   exit               ; jmp 无条件跳转到 exit

even_f:  ;偶数打印
  
   mov   eax, 4             
   mov   ebx, 1             
   mov   ecx, even_msg      
   mov   edx, len1          
   int   0x80               

odd_f:  ;奇数打印
   
   mov   eax, 4             
   mov   ebx, 1             
   mov   ecx, odd_msg       
   mov   edx, len2          
   int   0x80

exit: ;退出
   
   mov   eax,1             
   int   0x80              

section   .data
even_msg  db  '是偶数!' 
len1  equ  $ - even_msg 
   
odd_msg db  '是奇数!'    
len2  equ  $ - odd_msg

跳转相关的标志位: 【⚠️ 关于标志位详细介绍后面篇章有介绍】

11 10 9 8 7 6 5 4 3 2 1 0
OF DF IF TF SF ZF AF PF CF
溢 出 方向 中断 调试 符 号 非零 未 用 辅 助 未 用 奇 偶 未 用 进 位

部分跳转指令和标记位关系:

程序控制类 无条件转移 JMP 不影响标志位
条 件 转 移 单个 标志 位 JS/JNS SF=1/0,则转移到目的地址
JZ/ JNZ ZF=1/0,则转移到目的地址
JP/JNP PF=1/0,则转移到目的地址
JB/JNB CF=1/0,则转移到目的地址
JO/JNO OF=1/0,则转移到目的地址
若干 标志 位的 逻辑 组合 JA 两个无符号数比较,A>B

# OR 指令

OR 指令用于通过执行按位或运算来支持逻辑表达式。

例如

4 or 3 结果 = 7

 mov    al, 4             ;0100
 mov    bl, 3             ;0011
 or     al, bl            ;0100 | 0011 = 0111 
 add    al, byte '0'      ;格式化

# XOR 指令

异或运算:位不同为1,相同为0

例如,

4 xor 3 结果 = 7

 mov    al, 4             ;0100
 mov    bl, 3             ;0011
 or     al, bl            ;0100 xor 0011 = 0111 
 add    al, byte '0'      ;格式化

清零操作:

将操作数与自身进行XOR会将操作数更改为0。这用于清除寄存器。

XOR     EAX, EAX

# TEST 指令

TEST 指令与AND运算的工作原理相同,但与AND指令不同的是,它不会更改第一个操作数。

因此,可以使用test测试某个逻辑运算的结果:

AND    AL, 01H ;会修改AL的值

TEST    AL, 01H ;不会修改AL的值

# NOT 指令

NOT 指令实现按位NOT运算。NOT操作将操作数中的位取反。操作数可以在寄存器中,也可以在存储器中。

NOT 是单操作,接受一个参数

例如,

 mov    al, 4             ;0100
 mov    bl, 3             ;0011
 or     al, bl            ;al = 0100 | 0011 = 0111 
 not    al 			          ;al = 0000 0111 取反 = 1111 1000

# 条件

前面示例已经介绍过一些条件指令,比如 jmp 无条件跳转

# 条件

条件令可以更改程序中的控制流,分为有条件和无条件:

无条件跳转 - JMP 就是最常见的,类似 go to

有条件跳转 - 通过条件判断是否或者如何处理控制流向

# 无条件跳转

语法格式

JMP     label

JMP指令:直接跳转到指定的过程,不做任何判断

_start:                     
   ...
   jmp   exit               ; jmp 无条件跳转到 exit

exit: ;退出
   
   mov   eax,1             
   int   0x80 

# 条件跳转

如果在条件跳转中满足某些指定条件,则控制流将转移到目标指令。

指令 描述 标志测试
JE/JZ 跳转等于或跳转零 ZF
JNE/JNZ 跳转不等于或跳转不为零 ZF
JG/JNLE 跳转大于或跳转不小于/等于 OF,SF,ZF
JGE/JNL 跳转大于/等于或不小于跳转 OF,SF
JL/JNGE 跳转小于或不大于/等于 OF,SF
JLE/JNG 跳少/等于或跳不大于 OF,SF,ZF

以下是对用于逻辑运算的无符号数据使用的条件跳转指令-

指令 描述 标志测试
JE/JZ 跳转等于或跳转零 ZF
JNE/JNZ 跳转不等于或跳转不为零 ZF
JA/JNBE 跳转向上或不低于/等于 CF,ZF
JAE/JNB 高于/等于或不低于 CF
JB/JNAE 跳到以下或跳到不高于/等于 CF
JBE/JNA 跳到下面/等于或不跳到上方 AF,CF

以下条件跳转指令有特殊用途,并检查标志的值-

指令 描述 标志测试
JXCZ 如果CX为零则跳转 没有
JC 如果携带则跳 CF
JNC 如果不携带则跳转 CF
JO 溢出时跳转 OF
JNO 如果没有溢出则跳转 OF
JP/JPE 跳校验或偶校验 PF
JNP/JPO 跳转无奇偶校验或跳转奇偶校验 PF
JS 跳跃符号(负值) SF
JNS 跳转无符号(正值) SF

根据 CX、ECX 寄存器的值跳转:

JCXZ(CX 为 0 则跳转)、JECXZ(ECX 为 0 则跳转);

# 示例

CMP 比较是否相同的指令,通过相减等于0表示相同,否则不相同。

CMP     AL, BL  ;比较他们是否相同,相同 ZF=0
JE      equal		;使用je:判断ZF标记等于0跳转

CMP     AL, BH
JNE     not_equal   ;使用jne:判断ZF标记不等于0跳转

not_equal: 
...

equal: 
...

# 过程

可以理解为c 语言的函数

前面示例 jmp、je等条件指令 跳转的就是 过程或子例程

过程的语法:

proc_name:				 ;过程名称
   procedure body  ;过程主体
   ...
   ret							;过程的结束由return语句指示

示例:

section .text
   global _start      
        
_start:    
   jmp name_1
   
 name_2:
   mov  edx,4           
   mov  ecx, names+8     
   mov  ebx,1          
   mov  eax,4          
   int  0x80  
   jmp exit

 name_1:
   mov  edx,4           
   mov  ecx, names+4     
   mov  ebx,1          
   mov  eax,4          
   int  0x80      
   jmp name_2
   
exit:
   mov  eax,1           
   int  0x80            

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

# 循环

c语法中 while 等能很方便实现循环功能

在汇编中可以通过loop 和jmp 等实现

# 循环 loop

loop指令的格式是:loop 标号

通常我们用loop指令来实现循环功能,cx中存放循环次数。

CPU执行loop指令的时候,要进行两步操作:

1、(cx)=(cx)-1; 2、判断CX中的值,不为零则转至标号处执行程序,如果为零则向下执行。

示例:

循环判断5次,每次都对ax 加一,最后cx=0时循环结束,

	mov ax,5
	mov cx,5
loop_1:		
	inc ax
			
	loop loop_1

1、标号:loop_1

在汇编语言中,标号代表一个地址,程序中有一个标号loop_1。它实际上标识了一个 地址,

这个地址处有一条指令:inc ax

2、loop loop_1:

CPU执行loop s的时候,要进行两步操作:

① (cx)=(cx)-1;(loop 自动计算的)

②判断cx中的值,不为0则转至标号s所标识的地址处执行,为零则执行下一条指令。

3、在这段程序中第一次执行loop loop_1前,cx=5,ax=5

第一次执行后,cx=4,ax=6

最后一次 cx=0,ax=10

4、用CX和loop指令相配合实现循环功能的3个要点:

1、在 CX中存放循环次数; 2、loop指令中的标号所标识地址要在前面; 3、要循环执行的程序段,要写在标号和loop指令的中间

# 例子

从9到0打印:

section .text
   global _start      
        
_start:               
   mov ecx,10				;index 起始值
   mov eax, '9'			;起始值
        
loop_dec:
   mov [num], eax
   mov eax, 4
   mov ebx, 1
   push ecx
        
   mov ecx, num        
   mov edx, 1        
   int 0x80
        
   mov eax, [num]
   sub eax, '0'
   dec eax					;递减
   add eax, '0'
   pop ecx
   loop loop_dec		;循环,每次自动对 ecx 自减,直到0结束循环
        
   mov eax,1             
   int 0x80              
section .bss
num resb 1

结果:

9876543210
intel-32基础语法(上)
intel-32基础语法(下)

← intel-32基础语法(上) intel-32基础语法(下)→

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