汇编学习笔记(一)

来源:互联网 发布:移动3g卡能用4g网络 编辑:程序博客网 时间:2024/05/16 05:48

这只是我学习汇编时候为了防止自己忘记的笔记系列,所以可能没什么条理性,基本上算是0基础开始吧,如果你也要学习,希望对你有帮助(目前会极其没有条理,后来会整理发出来)。
之前写kiwi系列(现在还没有写好,自己的惰性真是可怕……)的时候有想过要学习一下汇编,后来觉得没什么必要搁置了。今天乱逛的时候在知乎上看到的,顿时汗颜。
我不相信,一个人用纯手工一条条指令去雕琢他的程序,用手指感受计算机的呼吸时会无动于衷……以前觉得汇编就是一门编程语言,要用的时候再去学习学习,看完这句话以后觉得这是一门像数据结构一样的基础课。

话不多说,进入正题。
首先是从cpu开始,cpu(本系列讲的全部都是基于X86架构,以后不在赘述)是由运算器、控制器和寄存器组成的,这些器件靠内部总线(总线分为内部总线和外部总线:外部总线是连接cpu和主板上的器件,主要分为数据总线,指令总线和控制总线;内部总线)相连接。
原来寄存器可以分为高位低位来使用,是为了向下兼容性。

本文涉及的汇编代码使用的编译器是MASM编译器。

汇编指令不区分大小写。
当把ax分成al和ah,单独拿出al作为一个寄存器,然后发生运算结果溢出时候,溢出的数字也不会存放在ah中,因为al单独作为一个寄存器来使用。

我们描述一个cpu是32位具体是:
1、ALU一次可以处理32位的数据
2、通用寄存器最大的宽度是32位
3、寄存器和运算器之间的通路是32位

X86系列cpu内部专门集成了地址加法器。
在汇编程序中,可以通过段地址:偏移量的形式来表示一个内存单元的逻辑地址。

寄存器部分
来说一下几个很重要的段寄存器,
CS:代码段寄存器
SS:堆栈段寄存器
ES:附加段寄存器
IP:指令指针寄存器

CPU读取指令的过程:
把CS寄存器和IP寄存器的内容送入地址加法器单元,得到一个20位地址,通过输入输出电路,读取内存的指定单元内容并放入指令缓冲器,最后执行。 执行完了以后,修改IP寄存器的内容(IP=IP+所读取指令的长度),然后执行读取下一条指令。
注:CPU加点启动或复位以后,CS被设置位FFFFH,IP被设置为0000H,所以说FFFF0H单元中的内容是CPU加电以后执行的第一条指令。
CS寄存器和IP寄存器的值是可以被程序员改变的,这里说点别的,这个特点可以做一些很有意思的事,黑客做的就是修改这两个寄存器值 。当然不能像用mov指令改变通用寄存器的值一样,而是通过转移指令来改变。
例如jmp指令:
jmp 032ah:8246h
上一行代码实现的功能是跳转到逻辑地址是032ah:8246h 的内存单元,这时候CS的内容被修改成032ah,IP被修改成8246h。当然也可以用jmp指令仅仅修改IP寄存器的内容。
jmp ax(或者别的寄存器) 表示用ax(别的寄存器)的值填充IP寄存器。

DS寄存器:常常用来存放数据段的段地址。 X86CPU不支持将数据直接存入DS,必须通过一个通用寄存器。例如:mov ax 1000H mov ds,ax。

栈部分

PUSH(入栈)指令和POP(出栈)指令操作都是以字为单位执行的,并且可以实现内存和寄存器之间的数据传递。

段寄存器SS用来存放栈顶的段地址,SP寄存器用来存放栈顶的偏移地址。因为这两个寄存器,CPU可以知道内存中的哪一块单元、多大的单元被当做栈使用,而且SS:SP都是栈顶单元的地址(当初始状态栈是空的,SP指向的是栈底下面的一个空间)。当每一次执行了push或者pop命令的时候,SP被修改一次(push指令先修改SP然后填入数据,pop指令则与之相反)。

这样就有一个问题。假设ss和sp寄存器都是16位,如果栈的空间是从10000H~1FFFFH,这时候SS=1000H,sp值是0。按照之前的原理ss:sp应该指向栈底元素的下一个元素,也就是20000H单元。不能用sp指针来表示10000H。如果要把10000H存在16位的寄存器,就是0.这里比较不好理解,要注意。

现在就有一个问题,CPU是怎么解决栈顶越界的问题呢?当栈满的时候在执行push指令或者栈空的时候执行了pop指令,栈一定会发生溢出。这也就是我们常常听到的溢出攻击,这也是很容易理解的,如果我想要得到你程序的信息,我可以写一段汇编代码在你内存旁边开辟一段空间作为栈,然后一直pop,这样就能很容易读到你内存的信息(前段时间Ubuntu用户密码输入28个空格就死机,应该也是发生了溢出了但是系统没有处理这个问题,众所周知C语言不会检查越界)。或者我一直push覆盖你的内容,你的程序也就不能运行。说的远了,回到我们的主题上来,CPU怎么检查栈越界呢?答案是CPU根本不管这个问题,CPU没有提供越界检查的机制。

push和pop指令的格式:
push 寄存器名(可以是通用寄存器也可以是段寄存器)/内存单元,
pop和push类似

汇编语言源程序结构

由指令和伪指令构成,伪指令不能被编译位相关的机器码。伪指令是给编译器看的。下面解释一下针对masm编译器的伪指令:

1.程序有数据段、代码段和堆栈段,定义一个段,segment和ends是定义段的一对伪指令。使用格式是:

 XXX segment XXX ends

2.汇编程序结束标记是end
3.assume伪指令:假设某一个代码段和某一个寄存器有关联(不明白这个假设是什么意思)

0 0