x86语法
1、注释
|
|
2、变量取值和赋值(传送指令)
|
|
存放的数据大小根据使用的寄存器而定,比如ax是16位寄存器,最大只能存放16位数据,也就是4位十六进制数据
十六进制数据不能以字母开头,前面需要加上0,否则编译报错
3、函数声明
结构如下:
|
|
示例:
|
|
4、函数调用
x86架构中使用关键指令call
x86架构汇编示例:
|
|
运行—查看—内存

5、字符串的定义
-
起因:如果直接将字符串赋值给同样寄存器,会出现以下两个问题:
- 字符顺序是反着的
- 最多只能存放两个字符
- 无法获取到数据地址,不能对字符串进行修改
-
为了解决这个问题,需要使用另一种方式,定义字符串
-
首先需要先在内存中申请一块空间,可以使用伪指令db和dw
1 2db-->define byte 定义字节 dw-->define word 定义字 = 2字节 -
示例
1 2db 'hello' ;占用五个字节的内存空间 dw 'hello' ;占用六个字节的内存空间
如果定义数字,使用dw每个数字占用两个字节的空间,字符串比较特殊,并不是每个字符占用两个字节,而是==总长度必须是2的倍数==
-
6、字符串的获取
- 获取字符串的数据,首先要获取到数据所对应的内存地址
- 那怎么获取已经定义好的地址呢?
第一步尝试:给数据添加别名
|
|
别名中存放的是偏移地址,但光有偏移地址还不行,还需要段地址

[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 9data 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、字符串修改和替换
|
|
分段写法
|
|
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 12add 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
|
|
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、字符串的输出
|
|