汇编学习总结
好久没看汇编了,怕忘。复习一下顺便写一下总结
简介
汇编语言(Assembly Language)是任何一种用于电子计算机、微处理器、微控制器或其他可编程器件的低级语言,亦称为符号语言。在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。在不同的设备中,汇编语言对应着不同的机器语言指令集,通过汇编过程转换成机器指令。特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植
一个CPU的构成,简单的来说:
- 汇编架构
- CPU厂家自身的指令
常见的汇编架构:
- X86
- ARM
- MIPS
- AVR
……
每一个汇编架构的指令都有所不同,这里主要是x86框架的指令。
x86的历史
1978年6月8日,Intel发布了新款16位微处理器“8086”,也同时开创了一个新时代:x86架构诞生了。x86指的是特定微处理器执行的一些计算机语言指令集,定义了芯片的基本使用规则,一如今天的x64、IA64等。
86指令集是美国Intel公司为其第一块16位CPU(i8086)专门开发的,美国IBM公司1981年推出的世界第一台PC机中的CPU–i8088(i8086简化版)使用的也是X86指令,同时电脑中为提高浮点数据处理能力而增加的X87芯片系列数学协处理器则另外使用X87指令,以后就将X86指令集和X87指令集统称为X86指令集。虽然随着CPU技术的不断发展,Intel陆续研制出更新型的i80386、i80486直到今天的Pentium 4(以下简为P4)系列,但为了保证电脑能继续运行以往开发的各类应用程序以保护和继承丰富的软件资源,所以Intel公司所生产的所有CPU仍然继续使用X86指令集,所以它的CPU仍属于X86系列。
x64-x86
x86-64( 又称x64,即英文词64-bit extended,64位拓展 的简写)是x86架构的64位拓展,向后兼容于16位及32位的x86架构。x64于1999年由AMD设计,AMD首次公开64位集以扩展给x86,称为“AMD64”。其后也为英特尔所采用,现时英特尔称之为“Intel 64”,在之前曾使用过“Clackamas Technology” (CT)、“IA-32e”及“EM64T”。
苹果公司和RPM包管理员以“x86-64”或“x86_64”称呼此64位架构。甲骨文公司及Microsoft称之为“x64”。BSD家族及其他Linux发行版则使用“x64-64”,32位版本则称为“i386”(或 i486/586/686),Arch Linux用x86_64称呼此64位架构。
高等语言的执行过程
C:
编译好的程序->汇编->指令
越高级的语言执行过程越慢,但是处理好的另说
汇编语言的组成
汇编指令: 机器码的助记符,有对应的机器码
伪指令:没有对应的机器码,由编译器执行,计算机并不执行
其他符号:+、-、*
、/等,由编译器识别,没有对应的机器码
基础
CPU:
CPU是计算机的核心部件,控制整个计算机的工作。让一个CPU工作 必须向CPU提供指令和数据,指令和数据在存储器中存放(平时说的内存) 磁盘不同内存,磁盘上的数据或程序不读到内存中就无法被CPU使用。
控制器:控制器控制各种器件进行工作
运算器:运算器进行信息处理
(以16位CPU举例)
CPU的总线:
地址总线:用于寻找地址,然后给出内存地址
- 地址总线寻址能力的计算公式:2x地址总线数量根=数据总线宽度
数据总线:用于传输数据
- CPU的读写根据总线进行操作
8根数据总线一次可以传送一个8位二进制数据 16位数据总线一次可以传送两个8位二进制数据 32位数据总线一次可以传送三个8位二进制数
- CPU的读写根据总线进行操作
控制总线:用于控制CPU之外的物理设备
用于控制CPU之外的物理设备,所谓的内存读写都是由控制线操控并发送 其中一根负责:读信号输出,控制CPU向外传送读信号 另外一根负责:写信号输出,控制CPU向外传送写信号
PCIE、PCIEX、PCIEX6之类接口外部设备的数据传输
几位的CPU一次可以传输对应位数的数据 16位CPU一次性可以处理16位的数据 寄存器的最大宽度为16位 * CPU和寄存器之间通路是16位
寄存器
寄存器是干啥的:寄存器信息存储
不同的CPU、寄存器的数量和结构都不同
通用寄存器:
- 16位CPU:AX、BX、CX、DX
- x86:EAX、EBX、ECX、EDX
- x64:RAX、RBX、RCX、RDX
寄存器的高低位:
示例->
16位:AX 高:AH 低:AL
32位:EAX 高:AX 低:AH
64位:RAX 高:EAX 低:AX
高低位的存储:
mov ah,64H
此时AX:6400
AH:64 AL:00
兼容性:
高位寄存器会兼容低位寄存器,例如一个16位寄存器的由两个 8位寄存器组成,则32位寄存器由一个16位寄存器和一个高8位寄存器组成(EAX由:AX和AH组成)。
寄存器的大小和计算
一个16位寄存器只能存放4个字节 一个32位寄存器只能存放8个字节 一个64位寄存器只能存放16个字节
传输问题:
字单位:word
一个字等于两个字节:1 word=2B
8位寄存器传输一个字要传输两次,16位寄存器只需要传输一次
一个字在16位寄存器中,自然就存在这个寄存器中的高8位寄存器和低8位寄存器
寄存器的范围:
16位寄存器的范围:0000~FFFF
32位寄存器的范围:00000000~FFFFFFFF
64位寄存器的范围:0000000000000000~FFFFFFFFFFFFFFFF
常见的计算:
mov eax, 4C0H
mov ebx, 26CH
add eax, ebx
mov data,
结果位:0000072C
如果计算超过最大范围:
8位寄存器计算
MOV AL,96h
MOV BH,99h
add AL,BH
结果为:12F,由于8位寄存器只能放两个字节。所以舍弃到前一位字节,最后AL=2F
如果计算范围超过寄存器接受最大范围从左往右舍弃,直到可以接受的范围
寄存器分类
寄存器分为:
- 段寄存器
- 常用寄存器
- 特殊寄存器
通用寄存器说明:
- AX累加器
- CX循环次数
- DX除法计算存储器
- BX乘法计算存储器
段寄存器:
CS:IP
- 通过CS:IP寻找存放的指令
- CS为段地址,IP为偏移地址
- 每次执行一条指令IP+3
- 计算物理地址公式: 物理地址=段地址x10(十六进制的10即为十进制的16)+偏移地址
DS:IP
- 通过DS:IP寻找存放的数据
- DS为段地址,IP为偏移地址
SS:SP
- SS:SP放着栈段的内存地址
- SS为段地址,SP为偏移地址
DS:SI
- 相对基址变址寻址
DSI:DI
- 相对基址变址寻址
ES:IP
- 扩展寄存器,附加段寄存器ES:存放当前执行程序中一个辅助数据段的段地址。 段寄存器 偏移地址寄存器
物理地址
CPU访问内存单元时,要给出内存单元的地址。所以的内存单元构成的存储空间是一个以维的线性空间
每一个内存单元在这个空间都有唯一的地址,这个唯一的地址叫物理地址
CPU通过地址总线收入存储器的,必须是一个内存单元的物理地址
16寻找物理地址过程如下:
1.CPU中的相关部件提供两个对应CPU位数的地址时,一个称为段地址,另一个称为偏移地址
2.段地址和偏移地址通过内部总线收送入一个称为地址加法器的部件
3.地址加法器将两个地址合成一个物理地址
4.地址加法器通过内部总线将物理地址送入输入输出控制电路
5.输入输出控制电路将物理地址送上地址总线
6.物理地址被地址总线传到存储器
寻址
8086CPU寻址如下:
8086CPU有20位地址总线,可以传送20位地址,达到1MB寻址能力。8086CPU又是16位结构,
在内部一次性处理、传输、暂存的地址都是16位。16位CPU的内部结构来看,如果将地址从内部
简单地发出,那么它只能送出16位的地址,寻址能力只有64KB
只有16位CPU要寻址,原因:16 cpu总线20位最大1mb,但是寄存器是16位的,所以需要段寄存器来寻址,要不然只能访问640kb
寻址大小计算公式:2的x次方/1024
32位找物理地址的方法:
32位CPU有32根地址线百,物理地址=2**32=4294967296=4GB
,但是在实际应用中,PCI内存范围度占用了大量的地址范围——接近750MB,导致最后系统物理地问址只有3.25GB左右。CPU过渡到32位以后,寻址时就不答用段地址来寻址了,32位寄存器可以访问所有版的4G地址,所以你那个每个段大小的问题是没有意义权的。
(这里的**32,代表2的32次方)
其实32位cpu照样可以使用超过4gb的内存,某种技术
指令和数据
指令和数据没有区别,CPU有时候把内容当做指令有时候把内容当做数据 他们都存储在内存或磁盘中。两者都是二进制
内存和单元
存储器被划分成若干个存储单元,每个存储单元从0开始编号。例如一个存储器有128个存储单元 编号:0到127
常见的容量单位
常见的容量单位: 1KB=1024B 1MB=1024KB 1GB=1024MB 1TB=1024GB 比特:bit 字节:byte 字:word 1byte=8bit 1word=4byte
内存地址空间
内存地址分段
- CPU在这段地址空间中读写数据,实际上就是在相对应的物理存储器中读写数据
- 每个物理存储器在这个逻辑存储器中占有一个地址段,即一段地址空间 (某段的地址空间代表着不同的物理设备,假设0~7FFFH空间的代表的是内存的,地址8000H~9FFFH代表的是显存的空间)(内存地址空间是根据不同系统的不同规定)
- (在DOS里)比较安全的一个内存段是:0:200~0:300这个内存地址是无任何数据的,相对安全
所有的物理存储器被看作一个由若干存储单元组成的逻辑存储器
物理地址:
CPU访问内存单元时,要给出内存单元的地址。所以的内存单元构成的存储空间是一个以维的线性空间 每一个内存单元在这个空间都有唯一的地址,这个唯一的地址叫物理地址 CPU通过地址总线收入存储器的,必须是一个内存单元的物理地址
编写汇编的工具
- Masm for Windows 集成实验环境
- RadASM
- Vs
常见的汇编格式
DATAS SEGMENT
;此处输入数据段代码
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
MOV AL,96h
MOV BH,99h
add AL,BH
;此处输入代码段代码
MOV AH,4CH
INT 21H
CODES ENDS
END START
定义一个段
<name> SEGMENT
.....
<name> ENDS
每个编辑器的命名都有所不同,单都大致相同
DATAS SEGMENT
;此处输入数据段代码
DATAS ENDS
STACKS SEGMENT
;此处输入堆栈段代码
STACKS ENDS
CODES SEGMENT
ASSUME CS:CODES,DS:DATAS,SS:STACKS
START:
;汇编指令
CODES ENDS
END START
汇编程序编译
编译可以直接通过工具自带的编译,直接编译好exe。或者用masm进行编译
(工具直接编译好的结果如下)
- obj文件是编译时产生的生文件(交叉引用的文件,又可以称为中间文件)
- lst是链接文件
- exe是最后编译成功产生的文件
常见的汇编指令
指令 | 功能 | 例子 |
mov | 赋值给寄存器 | mov ax,60 #将ax赋给ax |
add | 加法计算 | add ax,ax #将ax的值加给ax=ax+ax |
jmp | 跳转到指定位置 | jmp 03C82 #跳转到03C82内存地址 |
int | 用于中断某些东西,例如int 0中断程序返回0。int 3常用于DEBUG程序,触发为3的时候会调用debug服务 | int 3 |
inc | 用于将目标操作数+1 | inc ax inc eas |
sub | 不带借位的减法指令 | sub ax,bx #ax=ax-bx,ax减去bx的值 |
push | 入栈 | push AX #将ax入栈 |
pop | 出栈 | pop ax #将栈顶的数据出栈到ax |
int | 程序返回 | int <code>,例如int 21H,从CPU将控制权返回给程序 |
dec | 减一指令,用于将目标操作数减一 | dec ax |
dw | 定义字型数据 | dw 4657H,3214H,0123H |
or | 或指令 | or al,423CH |
db | 定义字节型数据 | db ‘unIX’相当于db 75H |
ptr | 类型临时转换,格式:[類型] ptr [寄存器或地址單元] ,指令調用的其他寄存器也要復合類型才行 | mov word ptr [BX],AX |
div、idiv(帶符號的除數) | 除法(需要定义类型) | div byte ptr [BX] |
dd | 定义double word类型数据 | dd 1001H |
dup | 定义一个范围的值 | dd 200 dup (0) |
lea | 從一個有效地址取值給寄存器,只能是地址取值,不能是寄存器取值 | mov bx,0002H mov ax,4C20H mov DS:[BX],4C20H lea CX,DS:[BX] ;CX=4C20H |
call | 無條件跳轉到指令處並開始執行和jmp很像 | mov eax,esp call eax #執行eax放的棧地址 |
dec | 寄存器或值減一 | mov eax,4C00178H dec eax |
mul、imul(帶符號的陳數) | 乘法 | mov ax,1234H mov bx,2H mul bx |
xor | 異或運算 | mov ax,4C21H xor ax,0 |
not | 翻轉操作數中的所有位 | mov ax,2B3CH not al ;AX=3C2BH |
neg | 求補運算 | mov ax,2B3CH neg ax |
shl | 將一個寄存器或內存單元中的數據左移一位 | mov ax,2B3CH shl al,1 ;將al中數據左移一位(1是移幾位) |
shr | 將一個寄存器或內存單元中的數據右移一位 | mov ax,2B3CH shr ah,1 ;將al中數據右移一位(1是移幾位) |
cmp | 對比指令 计算 操作对象1 - 操作对象2 但不保存结果,只是根据结果修改相应的标志位 (p是比较指令,cmp的功能相当于减法指令。它不保存结果,只是影响相应的标志位。其他的指令通过识别这些被影响的标志位来得知比较结果。) | ;死循環例 @test:xor ax,ax mov ax,100H mov bx,50H cmp ax,bx jne @test ;不等於則跳轉到test標準 |
je | 相等則跳轉 | @test:xor ax,ax mov ax,100H mov bx,50H cmp ax,ax je @test ;等於則跳轉到test標準 |
jmp | 無條件跳轉 | mov ax,100H mov word ptr bx,ax jmp @test mov cx,04H @test: mov ax,0H |
jz | 為0則跳轉 | mov ax,0H cmp ax,ax jz @test mov bx,6C43H @test: mov ax,6644H |
jg jng #無無符號數字判斷 | 有符號大於則跳轉 | mov ax,400H mov bx,200H cmp ax,bx jg @test xor ax,ax @test:mov bx,600H |
jl jnl #無符號小於則跳轉 | 有符號小於的則跳轉 | mov ax,400H mov bx,200H cmp bx,ax jl @test xor ax,ax @test:mov bx,600H |
loop | 循环,cx存放循环次数 | mov ax,3 mov cx,2 ;循环两次 s:add ax,ax loop s |
ret | 用栈中所保存的数据赋值给IP的,(CS:IP)jump 跳转回来(注意:跳转到指定的地址后,跳转后地址的那条指令是不会执行的) IP = ss10(16进制的10) + sp (保存的值出栈, 复制给IP) sp = sp +2 这写shellcode的时候,找到函数地址用于返回函数地址 比如栈顶最上面有12345678h的值,那么ret之后。就是把12345678h pop到IP,在x86为EIP 堆栈 12345678h ret前 EAX = 12345678 EBX = 7EFDE000 ECX = 00000000 EDX = 00F21005 ESI = 00000000 EDI = 00000000 EIP = 00F21018 ESP = 002AFC90 EBP = 002AFC94 EFL = 00000244 ret后 EAX = 12345678 EBX = 7EFDE000 ECX = 00000000 EDX = 00F21005 ESI = 00000000 EDI = 00000000 EIP = 12345678 ESP = 002AFC94 EBP = 002AFC94 EFL = 00010244 堆栈 <空> | 假设栈顶最后4个字节(8086汇编)将赋给IP,跳转到对应的物理地址往下执行 MOV AX,264CH MOV BX,3682H PUSH AX PUSH BX MOV CX,2 MOV AX,SS MOV DS,AX MOV BX,SP MOV AX,DS:[BX] XH: ADD AX,AX LOOP XH POP BX POP DX PUSH AX XOR AX,AX RET |
call | 将当前的IP 或者 CS:IP 压入栈中,跳转到指定位置 简单描述: sp = sp -2 ss 10(16进制) + sp = ip (将IP的值压栈) IP = IP +16位位移 (就是jmp了) | MOV AX,264CH CALL FAN MOV BX,142CH FAN: MOV AH,4CH INT 21H 搭配RET一起使用: MOV AX,264CH CALL FAN MOV BX,248CH JMP T ;此处输入代码段代码 FAN: MOV AH,4CH MOV AX,0006H PUSH AX RET ;跳回到MOV BX执行 T: INT 21H |
movsx | 符号扩展的意思是,当计算机存储某一个有符号数时,符号位位于该数的第一位,所以,当扩展一个负数的时候需要将扩展的高位全赋为1.对于正数而言,符号扩展和零扩展MOVZX是一样的,将扩展的高位全赋为0 (X86汇编或以上才有) | 例子:MOV BL,80H MOVSX AX,BL AX == 0FF80H 可能初学者奇怪80H不是正数吗?FF怎么来的?看下面, 80h = 1000 0000 最高位为符号位, 即符号位为1 则MOVSX AX, BL后, AX = 1111 1111 1000 0000 = FF80h |
movzx | movzx是把高位全部用0填充,被进行赋值操作的寄存器会把得到的值放在地位寄存器,然后高位寄存器全0补充 (X86汇编或以上才有) | mov BX, 12C5H movzx EAX,BX |
pusha pushad popa popad | pusha/pushad两条指令都是将通用寄存器进行入栈 popa/popad两条指令都是将栈段内的数据全部出栈到通用寄存器 | |
bswap | 寄存器高位和地位数据字节次序变反 Example: EAX=12345678H 变反后: 78563412H | |
lea | (x86汇编或以上才有) load effective address, 加载有效地址,可以将有效地址传送到指定的的寄存器。指令形式是从存储器读数据到寄存器, 效果是将存储器的有效地址写入到目的操作数, 简单说, 就是C语言中的”&”.(把DS:[寄存器]地址所指向的值基于给某给寄存器) lea对变量没有影响是取地址,对寄存器来说加[]时取值,第二操作数不加[]非法 | mov eax,0x4C2 lea ebx,dword ptr DS:[EAX] ;将EAX的地址给ebx |
LODSD | LODSB、LODSW 和 LODSD 指令分别从 ESI 指向的内存地址加载一个字节或一个字到 AL/AX/EAX。ESI 按照方向标志位的状态递增或递减。 LODS 很少与 REP 前缀一起使用,原因是,加载到累加器的新值会覆盖其原来的内容。相对而言,LODS常常被用于加载单个 数值。在后面的例子中,LODSB代替了如下两条指令(假设方向标志位清零): mov al, [esi] ;将字节送入AL inc esi ;指向下一个字节 默认将DS:[esi]地址所指向的值赋予EAX | |
xchg | 两个寄存器的值互相交换 xchg <被交换的寄存器>,<要交换的寄存器> 例如:xchg eax,esi EAX=ESI的值 ESI=EAX的值 | 执行前: 执行xchg指令后: |
test | 对两个寄存器进行逻辑计算,根据返回的结果修改标志位,自己测试的时候是如果寄存器其中一方为0,Z、P标准位设置位1,否则Z、P标准位设置为0。下面是文章说的 ( 影响标志: C,O,P,Z,S(其中C与O两个标志会被设为0)) 该指令主要用来判断其中一方的寄存器是否为0 顺便说一下标志位,所有标志位在念的时候都加一个F,比如:Z=ZF标准位,C=CF标志位 | 其中一方寄存器为0 两边的寄存器都不为0 |
题外话
内存读写的大小端
主流的汇编架构,内存里都是小端
例如:mov ax,4C3B -> 内存里:3B4C
大端:mov ax,4C3B -> 内存里:4C3B
大端只有在很少见的IOT设备可能有
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。
文章标题:汇编学习总结
本文作者:九世
发布时间:2020-09-21, 19:20:18
最后更新:2020-09-21, 23:22:34
原始链接:http://jiushill.github.io/posts/7a38fa87.html版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。