学习与此
程序员进阶之汇编语言从0到1---up:胖薯code
视频中的讲义
仅用于自我总结
一,寄存器的命名
1.x86
四个通用寄存器**ax bx cx dx ,**可以高版本兼容低版本
================ rax (64 bits)
======== eax (32 bits)
==== ax (16 bits)
== ah (8 bits)
== al (8 bits)
mov ax,2000h ;h结尾表示16进制,不区分大小写;b结尾表示2进制,不区分大小写
;没有字母表示10进制
2.ARM
31个通用寄存器x0--x30
================ xn (64 bits)
======== wn (64 bits)64位寄存器中的低32位
======== rn (32 bits)
mov r0,#0x20202020A ;使用0x开头表示16进制,#是固定写法
3.MIPS
32个通用寄存器**¥0--¥31**
二,内存单元和地址
最大运算表示位数=min{cpu运算能力,地址总线宽度}
物理地址=段地址*16+偏移地址
三,栈和队列
栈和队列都属于数据存储结构,数据结构大致分为一下几种存储结构
1.线性表:顺序表,链表,栈和队列
2.树结构:普通树,二叉树,线索二叉树
3.图存储结构
- 队列结构:先进先出
- 栈存储结构:先进后出
栈作用:用于存储临时数据,对数据进行暂时性保护,不被复写
四,搭建x86汇编环境emu8086
五,x86汇编语法
1.注释
;
2.变量取值和赋值(传送指令)
;赋值
mov ax,2900h
mov bx, 4904h
;取值
mov cx,ax
mov dx,0FFFFh
16进制数据不能以字母开头,需要在前面加上0
3.函数声明
结构
函数名:
结构体
ret ;结束标志
示例:
void:
mov ax,2000h
ret
4.函数调用
call printf
注意把函数写在下面
5.字符串的定义
伪指令
db-->define byte 定义字节
dw-->define word 定义字=2字节
示例,好像单引号或是双引号都可以
db 'hello'
dw 'hello'
dw定义应该是偶数字符,否则会对应添加.在末尾
6.字符串的获取
因为bd是伪指令,不想让程序给转换成指令,所以用"end"表示程序的开始,其中end是标志,start这种是开始的自定义字符
str db 'hello'
start:
mov ax,str
end start
别名中存放的是偏移地址,还需要段地址,**段地址*16+偏移地址=实际物理地址,**别名默认从ds
(data segment)寄存器中读取段地址,但是我们没有给ds赋值过,这就导致我们无法获取正确的数据,因为我们不知道正确的段地址是多少
字符串的段地址的获取:
- 方法一:直接从内存中找(仅限于调试,实际开发不可以)
- 方法二:使用段进行包裹,段能给我们提供一个段地址
data segment ;data可以自定义命名
str db 'hello' ;伪指令
data ends
start2: ;程序入口,提示是指令开始的位置
mov ax,data
mov ds,ax
mov ax,str
end start2
7.对内存中的数据进行读写操作
data segment
str dw 'hello' ;如果定义多个数据,用逗号进行隔开
data ends
start:
mov ax,data
mov ds,ax
mov ax,str ;如果从内存中读取数据,是根据寄存器的大小来读取,16位的寄存器一 次性读取16位数据
end start
注意需要寄存器的位数和地址的位数匹配
ax---dw
al(16位中的低八位)---b.str //db
内存数据的读写是从低往高进行读写
比如:
\
我们写下一个简单的汇编代码
我们看到,在寄存器ax中低段位储存的是34,高段位储存的是12(H和L)
而在内存中也是低段位储存的是34,高段位储存的是12(从左向右)
如果我们想要从指定的内存地址中写入或者读数据的话,需要借助段寄存器进行实现
ds,cs,ss,es
需要通用寄存器进行中转
- 如何从指定内存中读取数据
start:
mov ax,0710h
mov ds,ax
mov ax,ds:[0] ;实际物理地址 段地址+偏移地址==> ds:[xxx] 表示从该地址取数据
end start
- 如何往指定内存中写入数据
start:
mov ax,0710h
mov ds,ax
mov ax,1234h
mov ds:[0],ax
end start
data segmentstr dw 'he'
data ends
start :
mov ax,data
mov ds,ax
mov ax,ds:str ;str-==>[xx] ds:[xxx] 和一开始的mov ax,str是等价的
mov ds:[0],ax
end start
别名数组读写的简便方式
;将第二个字符串的o替换为e
data segment
str dw 'hello '
newstr dw 'wowowo'
data ends
code segment
start:
mov ax,data
mov ds,ax
mov al,b.str+1 ;b. 相当于一个字节,同理,w. 相当于一个字,两个字节
mov b.newstr+5,ax
end start
code ends
data segmentstr dw 'hello '
newstr dw 'wowowo'
data ends
code segment
start:
mov ax,data
mov ds,ax
mov al,b.str[1] ;str 相当于str[0]
mov b.newstr[5],al
end start
code ends
8.字符串的修改和替换
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
data segmentstr dw "hello "
newstr dw "wowowo"
data ends
start:
mov ax,data
mov ds,ax
mov ax,str
mov ds:newstr,ax
mov ax,str+2
mov ds:newstr+2,ax
mov ax,str+4
mov ds:newstr+4,ax
end start
分段写法:
data segment
str dw "hello "
data end
newdata segment
newstr dw "wowowo"
newdata end
code segment
start:
mov ax,data
mov ds,ax
mov ax,newdata
mov es,ax
mov ax,ds:str
mov es:newstr,ax
mov ax,ds:str+2
mov es:newstr+2,ax
mov ax,ds:str+4
mov es:newstr+4,ax
end code
end start
9.Loop循环指令
data segment
dw 'hello '
dw 'wowowo'
data end
start:
mov ax,data
mov ds,ax
mov cx,3 ;对应循环次数
mov bx,0
pp:
mov ax,ds:[bx]
mov ds:[bx+6],ax
add bx,2 ;bx=bx+2
loop pp
end start
进阶:
data segment
①dw 'aaaaaaa' 需要修改bx的初始值,或者当有str dw 'hello' 时,使用offset指令
在赋值的时候把str变成对应的偏移地址,而不是对应地址所存储的数据
dw 'hello '
dw 'wowowo'
data end
start:
mov ax,data
mov ds,ax
mov cx,3
mov bx,0
①mov bx,7
或者 mov bx,offset str
pp:
mov ax,ds:[bx]
mov ds:[bx+6],ax
add bx,2
loop pp
end start
加减指令运算add/sub
注意不能内存之间的修改数据
错误写法: add/sub ds:[0],ds:[6]
10.中断
相当于主线任务和支线任务
mov ah,01h
int 21h
例如: INT21h 系统功能调用 在AH中储存功能号,在AL中储存输入的数据
11.字符串的输出
data segment
str db "hello world!$"
data ends
code segment
start:
mov ax,data
mov ds,ax
mov dx,offset str
mov ah,09h
int 21h
end start
code ends
data segmentstr db "hello world!$"
data ends
code segment
start:
call print ;调用函数
mov al,0h
mov ah,4ch ;结束程序带返回值al
int 21h
print: ;函数定义
mov ax,data
mov ds,ax
mov dx,offset str
mov ah,09h
int 21h
ret
end start
code ends
12.除法指令DIV
被除数/除数==商......余数
- 被除数:高16位放在DX寄存器中,低16位放在AX寄存器中
- 除数 div 除数,一般放在BX或CX寄存器中
- 商存放在AX寄存器中
- 余数存放在DX寄存器中
mov dx,20 ;定义被除数高16位
mov ax,2000 ;定义被除数低16位
mov bx,300 ;定义除数
div bx
mov ds:[0],ax ;在内存中存放商
mov ds:[2],dx ;在内存中存放余数
13.乘法指令MUL
乘数X乘数=积
mul 乘数
- AX存放第一个乘数
- 调用mul指令 第二个乘数
- 积的高16位存放在DX寄存器中
- 积的低16位存放在AX寄存器中
mov ax,20
mov bx,3
mul bx
mov ds:[0],ax
mov ds:[2],dx
六.段寄存器
1.数据和指令
用段寄存器标记是指令还是数据
2.数据DS-->data segment 偏移地址bx
指令CS-->code segment 偏移地址ip
栈空间SS-->stack segment 偏移地址sp
ES-->extra segment 一般用于DS的替代
七.栈空间的操作
正常的排列方式是数据从低地址往高地址进行偏移存储,读取数据也是从低到高
栈是写入数据从高到低进行写入,读取是从低到高进行
栈存储的特点
- 一次读写两个字节的数据
- 数据是从高地址往低地址逆序偏移存放
栈空间的声明
data segment
str db 8,7,6,5,4,3,2,1
data ends
code segment
start:
mov ax,data
mov ss,ax
mov sp,8
code ends
end start
往栈空间中写入数据
push指令
data segment
str db 8,7,6,5,4,3,2,1
data ends
code segment
start:
mov ax,data
mov ss,ax
mov sp,8
push 2000h
code ends
end start
data segment
str db 8,7,6,5,4,3,2,1
data ends
code segment
start:
mov ax,data
mov ss,ax
mov sp,8
push 2000h
push 5522h
code ends
end start
从栈空间读取数据
POP指令
data segment
str db 8,7,6,5,4,3,2,1
data ends
code segment
start:
mov ax,data
mov ss,ax
mov sp,8
push 2000h
push 5522h
pop bx
code ends
end start
八.操控显存输出字符串
在8086的内存地址结构中,B8000h~BFFFFh 这部分的内存区域为显存区域,一旦向这个地址空间写入数据,cpu就会从0号偏移地址读取数据让后显示输出(每写入一次数据就从0号地址开始读取一次)
每个字符站两个字节的空间,前面的字节表示字符,后面的字节表示颜色
0 0 0 0 0 0 0 0 ;用8个2进制位表示字符颜色属性
从高位往地位数:
- 第一位表示是否显示闪烁痕迹
- 第234位代表字符背景颜色RGB
- 第5位显示是否高亮
- 第678位代表字符颜色RGB
字符串打印
data segment
str db 'hello world!'
endstr db ''
data ends
code segment
start:
mov ax,data
mov ds,ax
mov ax,0B800h ;只赋子段地址B800,而且因为是字母开头,前面要加0
mov es,ax
mov cx,offset endstr-str ;一种获取字符串长度的方法
mov bx,0
mov si,0
print:
mov al,ds:[si] ;注意使用al,获取8位一字节的数据
mov es:[bx],al
add bx,2
inc si ;相当于 add si,1
loop print
code ends
end start
借助字符不断刷新显示的特性,可以让字符动画显示
;让字符从左往右显示
code segment
start:
mov ax,0B800h
mov es,ax
mov cx,30
mov bx,2
print:
mov al,' '
mov es:[bx-2],al
mov al,'a'
mov es:[bx],al
add bx,2
loop print
code ends
end start
九.使用键盘控制字符的移动
-
int 16中断,键盘输入字符,
-
cmp 函数,jne和je配合使用
:用键盘控制字符移动,a61代表左移,d64代表右移,w77代表上移,:s73代表下移
code segment
start:
mov ax,0B800h
mov ds,ax
mov bx,0
scanf1:
mov ah,0h
int 16h
cmp al,61h :判断值是否相等
jne scanf2 ;jmp not epual 如果值不相等,跳转ssanf2,否则往下执行
call left
jmp scanf1
scanf2:
cmp al,64h
jne scanf3
call right
jmp scanf1
scanf3:
cmp al,77h
jne scanf4
call up
jmp scanf1
scanf4:
cmp al,73h
jne scanf1
call down
jmp scanf1
left:
mov ds:[bx],' '
mov ds:[bx-2],'a'
sub bx,2
ret
right:
mov ds:[bx],' '
mov ds:[bx+2],'a'
add bx,2
ret
up:
mov ds:[bx],' '
mov ds:[bx-160],'a'
sub bx,160
ret
down:
mov ds:[bx],' '
mov ds:[bx+160],'a'
add bx,160
ret
code ends
end start
十.转移指令
1.jmp
jmp 0100:0008h
mov ax,2222h
mov bx,3333h
code segmentjmp 0005h
mov ax,2222h
mov bx,3333h
code ends
code segmentjmp b
mov ax,2222h
mov ax,2222h
mov ax,2222h
mov ax,2222h
mov ax,2222h
b:
mov bx,3333h
code ends
2.jcxz
当cx的值是0的时候跳转,当非零的时候不跳转
code segment
jcxz b
mov ax,2222h
mov ax,2222h
mov ax,2222h
mov ax,2222h
mov ax,2222h
b:
mov bx,3333h
code ends
3.retf
retf
需要配合栈进行使用,当程序执行到retf
这条指令时,会连续从栈中pop
两次数据,第一次的数据赋值给CS
,第二次的数据赋值给IP,那么如果我们想要跳转到指定的指令,需要将该指令的段地址和偏移地址分别push
进栈中
stack segment
dw 1234h
stack ends
code segment
start:
mov ax,stack
mov ss,ax
mov ax,0710H ;指定段地址
push ax
mov ax,0000H ;指定偏移地址
push ax
retf ;程序跳转到0710:0000这个位置
code ends
end start
十一.call和ret进阶
1.call和ret指令
call在执行时会先将下一条指令所对应的ip地址入栈,让后修改ip的值实现跳转,ret指令执行的时候,将ip地址pop出来进行跳转
call 偏移地址(或者是函数名,标号)
2.call Far ptr和retf指令
call Far ptr 执行时会将下一条指令做对应的cs和ip\都入栈,retf指令执行的时候,将ip和cs的值pop出来进行跳转,进行段之间的跳转
- ret和call配套使用
- retf和call Far ptr 配套使用
call Far ptr 偏移地址
3.call word ptr和call dword ptr
直接从内存中获取ip地址然后跳转
call word ptr ds:[0] ;ds:[0]存放ip值
直接从内存中获取cs和ip地址然后跳转
call dword ptr ds:[0] ;ds:[0]存放的是ip值, ds:[2]存放的是cs值 ;这种方式同样会将cs和ip入栈 可以配合retf使用
call
指令和jmp
指令的区别
jmp
指令仅仅只是修改了cs:ip
的值call
指令除了修改cs:ip
的值之外,还将下一条指令的ip
值入栈,方便ret
指令跳转调用