【滴水基础】1.汇编语言(中)
十、内存
寄存器位于cpu之中,如8个32/64位的通用寄存器,存储数据较少。为此,引入内存。
—每个程序都会有自己独立的4G内存空间(os分配)
—这个4G内存是假的(不是真正的内存,而是映射),是空头支票
—1Byte=8bit;1KB=1024Byte;1M=1024kb;1GB=1024MB
内存地址:在内存中存储/读取数据,需要用到内存地址,是一种32位编号。
—八个十六进制,一块内存对应8bit,即1Byte==2个十六进制字符
—min:0000 0000,对应十进制:0
—max:FFFF FFFF,对应十进制:16^8
—对应的GB大小:16^8Byte=16^8/1024KB=16^8/1024/1024MB=4GB
(1)立即数写入内存(MOV指令)
—如:将32位内存写满,需要使用dword
(2)寄存器写入内存
—但是之前1块内存的大小为1Byte=8bit,要存储32bit的立即数,需要4块内存(注意:是连续分配的内存)
—在堆栈中编号间隔4位
—在内存中,编号间隔8*4*4位
—在CPU的进程中,间隔编号的大小不同
—猜测是每个指令占用的内存不同,所以每行程序的执行地址间隔也不同
—将寄存器的内容存入内存(32位)
—换成16位
—换成8位
—注意:AH和AL都存储在堆栈中的低位
(3)内存写入寄存器
—如果内存中存在寄存器ESP/EBP,就使用SS:[]
—否则使用ds:[]
(4)内存写入内存
—在汇编中,一般不允许内存到内存(可以使用MOVS指令)
—但是可以内存到寄存器,再从寄存器到内存
十一:内存地址的五种形式
C语言在汇编中的显示
#include <stdlib.h>
int x=1;//全局变量
int main(int argc,char* argv[])
{
int x=2;
int y=3; //局部变量
return 0;
}
—先在关键函数处按F9下断点,再按F5开始调试。
—接着,Alt+8出现反汇编窗口,或点击查看–>提示窗口->Disassembly:
—可以看到这里将堆栈中EBP的前32位空间,存储立即数2
—return 0:eax xor eax,为0,并且将0存储入eax
—一般eax存储的都是函数调用的结果(在这之前应该将原来eax的值存储入堆栈,在调用函数结束后,将堆栈中的原eax的值赋值给eax寄存器)
—这里缺少了ss:[],是不标准的写法
—内存地址的表示形式:不一定是内存编号,可以是寄存器,也可以是寄存器的四则运算等等
(1)[立即数]
—读取内存的值/向内存中写入数据
(2)[reg]: reg为寄存器,可以是八个通用寄存器的1个
—读取内存的值
—向内存中写入数据
(3)[reg+立即数]
—读取内存的值
—向内存中写入
(4)[reg+reg*[1,2,4,8]]
—注意,只能为1,2,4,8,因为堆栈中的地址之间间隔4
—读取内存的值
—反正EAX的移动距离只能是4的倍数,如这里2*4=8(下移2个堆栈)
—向内存中写入数据
(5)[reg+reg*[1,2,4,8]+立即数]
—读取内存
—向内存中写入数据
十二:数据的存储模式
—1个内存块(编号)是8bit,但是在堆栈内存中,一个堆栈编号是32bit
—所以1个堆栈内存=4个连续的内存块
—那么,数据的存储在内存中是从高位开始存储还是低位存储
Mov word ptr ds:[0000 0000],0x1A2C
—这里从0000 0000开始,但是word有16位,1个内存地址不够存
—就从0000 0000开始占2个内存地址(Dword就是四个)
—0x1A2C中,1A是数据高位,2C是数据低位
—那么,数据的高位、低位在内存中是如何存储的?
—可以发现,上图是小端模式存储
—arm的cpu(手机)的存储模式多为大端存储
—X86的cpu(电脑) 的存储模式多为小端存储,但是不是绝对的
—如:mov dword ptr ds:[0000 0000],1A2C3E4F
—大端模式
—小端模式
#程序内存地址的查看
—内存地址的查看,只能看堆栈中已经分配的地址
—查看19D008的内存
—查看一个字节,用db
—发现数据的低位是21
—后面的43、65、87分别是08、09、0A、0B的1byte的内存低位
—以2byte的形式查看
—以4byte的形式查看,发现和堆栈的表现形式一样
—向内存中写入4byte数据,观察存储形式
—以byte查看,发现内存的低位44=数据的低位
—此电脑的存储方式是小端存储
#总结
—数据的存放:寄存器、内存
—数据的存储方式:小端存储
—数据的存储:以补码的形式存储
—无符号数:原码=反码=补码;有符号数:补码=反码+1
十三:常用的汇编指令
#相关符号
—r:通用寄存器,如r8为8位通用寄存器
—imm:立即数,如imm8位8位立即数
—m:内存,如m8位8位内存
#简单指令
(1):MOV指令(不能内存到内存)
(2):ADD指令
(3):SUB指令
—前面的寄存器减去后面的
—将结果存储到前面的寄存器
(4):AND指令
—两者都为1时才为1
(5)OR指令
—只有存在1个1,就为1
(6)XOR指令
—两个不一样(一公一母)才为1
(7)NOT指令
—对EDX取反
#较复杂的指令
—EDI(destination index):目的地址寄存器,存储目的地址的内存编号
—ESI(source index):源地址寄存器,存储源地址的内存编号
(1)MOVS指令(从内存到内存移动数据)
—简写:MOVSB
—注意:执行完后,EDI、ESI的值+或者-1/2/4(根据复制的数据大小判断)
—同理:
—MOVS word ptr ES:[EDI],word ptr DS:[ESI] //简写:MOVSW
—MOVS dword ptr ES:[EDI],dword ptr DS:[ESI] //简写:MOVSD
—注意:EDI、ESI的值+或者-根据EFL(32标志寄存器)的DF来判断
—DF(方向位),在EFL寄存器中的第十位
—根据byte、word、dword来判断加减的数据的大小
—将19FF78的值复制到19FF7C中去
—单步步过,ESI\EDI的值为
—执行MOVS指令
—发现ESI/EDI+4,且DF=0
(2)STOS指令(将AL/AX/EAX的值存储到EDI指定的内存单元)
—EDI所指向的内存要用ES
—根据DF的值,来判断指向STOS命令后EDI的加减
—STOS byte ptr es:[EDI] //STOSB
—STOS word ptr es:[EDI] //STOSW
—STOS dword ptr es:[EDI] //STOSD
—执行后,只将EAX中的最低位赋值给了EDI指向的内存地址
—EDI的值+1
—缺点:只能1次将EAX复制到内存(1次最多4byte)
(3)REP指令:计数寄存器
—根据ECX(计数寄存器),遍历执行,每次ECX-1
—执行rep后,ecx循环3次,应该是将[esi,esi+12]的数据复制到[edi,edi+12]的堆栈中去
—ecx变为0
—edi指向的堆栈发现增加了3个dword的地址
—这里是根据DF来判断,如果DF=1,则就是向上增加堆栈
—将EAX的值连续赋值给堆栈
—这里将EAX赋值给[edi,edi+4*4]
十四:堆栈相关指令
什么是堆栈?
—操作系统在程序启动时分配,和数据结构的堆栈无关
—查看程度的核心:查看堆栈
—查看exe程序被分配的内存
—查看FS,可以看出OS分配了多少的内存
—这里是336000
—查看这块内存,336000为指向SEH的指针(SHE是啥啊)
—从19D000开始,从1A000结束
—在堆栈中查看(堆栈在使用是从后往前使用,我的理解是:将分配的内存压入堆栈,最后面的内存就在最上面,就最先使用)
—如果程序超出了堆栈,会导致堆栈溢出
—栈底是:19FFFC,19FFFC+4=19A000
—栈顶是19D000
#如何查看当前进程的堆栈使用情况?
—ESP(stack pointer):栈顶指针寄存器,记录当前程序使用的堆栈
—[19FFFC,19FF78]是当前程序使用的堆栈
—[19FF74, 19D000]是后面程序使用的堆栈
—ESP向上移动8位
—向下移动8位
—注意:单纯的移动数据是无法改变ESP
—SUB/ADD的缺点:在修改堆栈之后,还要进行ESP的修改
作者:沙漠里的鲸 https://www.bilibili.com/read/cv19289027/?spm_id_from=333.999.0.0 出处:bilibili