Featured image of post x86语法

x86语法

本文简要介绍了x86基本语法。

x86语法

1、注释

1
;我是注释

2、变量取值和赋值(传送指令)

1
2
3
4
5
6
7
8
;赋值
mov ax,2000H ;将十六进制2000赋值给十六位寄存器ax 相当于ax=2000H
mov bx,0FFFFH
;十六进制数不能以字母开头,字母开头需要补0,数字开头不需要
;1000|000 - 0080

;取值
mov bx,ax ;将ax中的值取出赋值给bx 相当于bx=ax

存放的数据大小根据使用的寄存器而定,比如ax是16位寄存器,最大只能存放16位数据,也就是4位十六进制数据

十六进制数据不能以字母开头,前面需要加上0,否则编译报错

3、函数声明

结构如下:

1
2
3
函数名:
	函数体
	ret   ;结尾标记 跳到调用函数的下一条指令

示例:

1
2
3
4
5
6
print:   ;函数名
		mov dx,offset str
		mov ah, 9ch
		int 21h
		
		ret ;函数结尾标记

4、函数调用

x86架构中使用关键指令call

x86架构汇编示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
        call print ;调用print函数

        ;退出程序
        mov ah, 4ch
        int 21h
print: ;函数名
        mov dx,offset str ;获取别名对应数据的偏移地址
        mov ah, 9ch ;9h表示调用显存 从dx总读取偏移地址对应的数据
        int 21h
        ret

运行—查看—内存

process

5、字符串的定义

  • 起因:如果直接将字符串赋值给同样寄存器,会出现以下两个问题:

    • 字符顺序是反着的
    • 最多只能存放两个字符
    • 无法获取到数据地址,不能对字符串进行修改
  • 为了解决这个问题,需要使用另一种方式,定义字符串

    • 首先需要先在内存中申请一块空间,可以使用伪指令db和dw

      1
      2
      
      db-->define byte 定义字节
      dw-->define word 定义字 = 2字节
      
    • 示例

      1
      2
      
      db 'hello' ;占用五个字节的内存空间
      dw 'hello' ;占用六个字节的内存空间
      

    如果定义数字,使用dw每个数字占用两个字节的空间,字符串比较特殊,并不是每个字符占用两个字节,而是==总长度必须是2的倍数==

6、字符串的获取

  • 获取字符串的数据,首先要获取到数据所对应的内存地址
  • 那怎么获取已经定义好的地址呢?
第一步尝试:给数据添加别名
1
2
3
4
5
6
str db 'hello'

start:
mov bx,str ;别名中存放的是偏移地址

end start

别名中存放的是偏移地址,但光有偏移地址还不行,还需要段地址

name

[00000h]是偏移地址 // 0710:0000

段地址+偏移地址=实际物理地址

别名默认从ds寄存器中读取段地址,但是我们并没有给ds寄存器赋过值,这就导致我们无法获取正确的数据,因为我们不知道正确的段地址是多少?

那字符串段地址从哪里获取呢?

  • 方法一:直接从内存中找(仅限于调试,实际开发肯定不行)

  • 方法二:使用段进行包裹,段能给我们提供一个段地址(正解)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    ;段包裹
    data segment
        str db 'hello'    ;伪指令
    data ends
    ;使用段进行包裹,可以借助段名称获取段地址
    
    start:   ;程序入口,指令开始的位置
        mov ax, data     ;段寄存器必须借助通用寄存器才能赋值
        mov ds, ax
        mov ax , str     ;指令
        ; (str)ax存放偏移地址, ds存放段地址
    
    end start
    

7、对内存中的数据进行读写

  • 从内存中一次读取数据的多少,取决于寄存器的容器大小

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    data segment
    	str dw 'hello'  ;如果定义多个数据,使用逗号进行分隔
    data ends
    
    	start:
    		mov ax,data
    		mov ds,ax
    		mov ax, str  ;从内存中读取数据,是根据寄存器大小读取,16位寄存器则一次性读取16位数据,8位al则一次性读取八位数据
    	end start
    
  • 思考:为什么以下写法报错?

     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
    
    ;报错1
    data segment
    	str db 'hello'  ;改成dw则不报错
    data ends
    start:
    	mov ax,data
    	mov ds,ax
    	mov ax,str
    	;ax 16bit str==>ds:str 偏移地址所指向的数据 db:8bit dw:16bit
    	;db每次偏移一个字节(8位),dw每次偏移两个字节(16位)
    end start
    
    ;报错2
    data segment
    	str dw 'hello'
    data ends
    
    start:
    	mov ax,data
    	mov ds,ax;
    	mov al,str
    	;改成mov ax,str或者mov ax,b.str 则不报错(b.str意味从两个字符中选择一个字符进行赋值,'.'表示进行分隔)
    end start
    
    ;报错3
    mov ax,b1 ;宽度不匹配 以上两个也是同样的问题
    
  • 内存数据的读写是从低往高进行读写

    上面使用db或者dw定义数据的方式,定义数据的同时就已经定义好了数据所在的物理地址,如果我们想要从指定的内存地址中写入或者读取数据的话,需要借助段寄存器来实现,在8086中给我们提供了DS SS CS ES四个寄存器,理论上你使用哪一个都行,但由于系统默认读取DS寄存器中的数据当做段地址,所以我们一般使用DS进行数据的段地址管理

    • 从内存中读取数据
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    ;假设我们需要从0710:0000这个物理地址中取数据,然后放到寄存器中
    
    ;错误写法1
    start:
    	mov ax,0710:0000H  ;没有这种语法
    end start
    
    ;错误写法2
    start:
    	mov ds,0710H  ;段寄存器不能直接赋值,必须借助通用寄存器
    	mov ax,ds:[0]
    end start
    
    ;正确写法
    start:
    	mov ax,0710H
    	mov ds,ax
    	mov ax,ds:[0] ;实际物理地址 段地址+偏移地址 ==> ds:[xx] 表示从该地址取数据
    end start
    
    • 往内存中写入数据
     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
    
    ;假设我们需要将数据写入0710:0000这个物理地址中
    
    ;错误写法1
    start:
    	mov ax,3333H
    	mov 0710:0000,ax  ;没有这种语法
    end start
    
    ;错误写法2
    data segment
    	str dw 'he'
    data ends
    
    start:
    	mov ax,data
    	mov ds,ax
    	mov ds:[0],ds:str  ;必须借助通用寄存器进行赋值
    end start
    
    ;正确写法1:
    data segment
    	str dw 'he'
    data ends
    
    start:
    	mov ax,data
    	mov ds,ax
    	mov ax,ds:str ;str ==> [xx] ds:[xx]
    	mov ds:[0],ax
    end start
    
    ;正确写法2
    start:
    	mov ax,0710H
    	mov ds,ax ;指定需要写入数据的段地址
    
    	mov ax,3333H
    	mov ds:[0],ax
    end start
    
    ;正确写法3
    start:
    	mov ax,0710H
    	mov ds,ax
    
    	mov ds:[0],3333H  ;可以直接将数据写入 最多写入十六位的数据
    end start
    

8、字符串修改和替换

 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
;需求1:将内存中的he修改为wo

data segment
	str dw 'he'
	newstr dw 'wo'
data ends

start:
	mov ax,data
	mov ds,ax
	
	mov ax,ds:str
	mov ds:newstr,ax
end start

;需求2:将内存中的hello修改为wowowo

data segment
	str dw 'hello '
	newstr dw 'wowowo'
data ends

start:
	mov ax,data
	mov ds,ax
	
	mov ax,ds:str
	mov ds:newstr,ax
	
	mov ax,ds:str+2
	mov ds:newstr+2,ax
	
	mov ax,ds:str+4
	mov ds:newstr+4,ax
	
end start

分段写法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
data segment
	str dw 'hello '
data ends

newData segment
	newstr dw 'wowowo'
newData ends

code segment   ;如果有多个段包裹,需要对代码段也进行段包裹
start:
	mov ax,data
	mov ds,ax
	mov ax,newData
	mov es,ax
	
	mov ax,ds:str
	mov es:newstr,ax
	
code ends

end start

9、Loop循环指令

  • 在同一个段包裹中,偏移地址从0开始,依次递增

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    ;将内存中的wowowo修改为hello
    
    data segment
    	dw 'hello'
    	dw 'wowowo'
    data ends
    
    start:
    	mov ax,data
    	mov ds,ax
    
    	mov ax,ds:[0]
    	mov ds:[6],ax
    
    	mov ax,ds:[2]
    	mov ds:[8],ax
    
    	mov ax,ds:[4]
    	mov ds:[10],ax
    
    end start
    
  • 类似于高级语言中的while循环,系统默认从cx寄存器中读取数据作为循环的条件,当cx中的值cx-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
    
    ;需求:将内存中的wowowo修改为hello
    
    data segment
    	str dw 'hello'
    	newstr dw 'wowowo'
    data ends
    
    start:
    	mov ax,data
    	mov ds,ax
    
    	mov bx,0   ;一般偏移地址使用bx储存
    	mov cx,3
    replace:
    	mov ax,ds:[bx]
    	mov ds:[bx+6],ax
    	add bx,2
    	loop replace
    
    end start
    
    data segment
    	dw 'aaa'
    	str dw 'hello'
    	newstr dw 'wowowo'
    data ends
    
    start:
    	mov ax,data
    	mov ds,ax
    
    	mov bx,offset str
    	;有offset str表示偏移地址
    	;没有offset str表示偏移地址所指向的数据[](取内容)
    	mov cx,3
    replace:
    	mov ax,ds:[bx]
    	mov ds:[bx+6],ax
    	add bx,2
    	loop replace
    
    end start
    
  • 加减运算指令add和sub

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    add ax,2  ;ax = ax + 2
    sub bx,2  ;bx = bx - 2
    
    
    sub/add 通用寄存器,数值   ;add/sub ax,2
    sub/add 通用寄存器,通用寄存器  ;add/sub ax,bx
    sub/add 内存地址,通用寄存器    ;add/sub ds:[0],bx
    sub/add 通用寄存器,内存地址    ;add/sub ax,ds:[0]
    
    
    ;错误写法
    sub/add 内存地址,内存地址      ;add/sub ds:[0],ds:[3]  不允许这样写
    

10、中断

顾名思义,程序运行到一半时暂时断开,官方一点说就是,由于软件或者硬件信息,使得cpu暂停当前任务,转而执行另一段子程序

可以形象理解为游戏中暂时搁置主线任务临时去完成支线任务

中断的分类:

  • 外中断(硬中断):由外部设备(比如网卡、硬盘、键盘或者鼠标)引发的中断,比如当网卡收到数据包的时候,就会发出一个中断
  • 内中断(软中断):由执行的中断指令产生的,可以通过程序控制触发

我们接下来要学习的时内中断只是,如果我们想要通过代码发出一个中断,那么需要使用中断指令int

1
int 21h  ;执行中断码为21H的中断指令

cpu接收到中断信号后,暂停当前正在执行的指令,临时去执行中断码对应的内容

中断码不止一个。每个码都代表着不同的含义,部分中断码列表如下:

中断 功能 入口参数 出口参数
INT16 键盘输入 AH=0H读键盘 AH=10读扩展键盘 AH=键盘扫描码 AL=字符ascii码
INT20 程序正常退出 CS=PSP段地址
INT21 系统功能调用 AH=功能号
INT22 程序结束处理
INT23 Ctrl-Break处理 AL=0(忽略)
INT24 严重错误处理 AL=驱动器号 AL=1(重试)AL=2(通过INT 23H终止)Cy=1出错
INT25 绝对磁盘读 CX=读入扇区数DX=起始逻辑扇区数DS:BX=缓冲区地址AL=驱动器号 Cy=0正确
INT26 绝对磁盘写 CX=写盘扇区数DX=起始逻辑扇区数DS:BX=缓冲区地址
INT27 驻留退出 CS=PSP段地址DX=程序末地址+1

|

11、字符串的输出

 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
data segment
    
    str db 'hello world!$'   ; '$' 停止读取
    
data ends

code segment  
    start: 
    
    call print  
    mov ax,2233H
    
    mov ah,4cH      ;程序结束,并且带有返回码
    int 21H         ;调用系统功能
    
    print:          ;定义一个函数
    mov ax,data
    mov ds,ax
    
    mov dx,offset str
    
    
     mov ah , 9H
     int 21H  
     
     ret
     
     
code ends
end start
✨ 本站由 Hugo + Stack 主题搭建 | 不忘初心,慢慢成长 ✨
使用 Hugo 构建
主题 StackJimmy 设计