前言
为了更深刻的理解c/c++,学习一下汇编(注:本文汇编讲解只是为了更好的使用c/c++,并不是严格的学习汇编)
我们所有的高级语言除了解释型的,比如javascript,都是被编译器编译成二进制代码。这些二进制代码术语叫COFF文件。
COFF文件在windows平台上被叫做PE文件,linux平台上叫elf文件。
windows上的PE文件常见的有exe dll ocx sys,编译器将我们的程序编译成PE文件,这些PE文件 本质上都是二进制,就是很多01组成的,稍微合并一下就能看到汇编指令,
比如 010110011 可能对应 mov eax, 1
因为指令数量是一定的,所以这些都是固定的
寄存器
根据冯·诺伊曼原理 计算机的的组成是这样的:cpu + 存储设备
而cpu是由运算器,控制器和寄存器组成 ,所有的指令都是在cpu里面运算的,寄存器英文单词叫register,先简单理解为就是一个存储设备。
常见寄存器有: AX BX CX DX ES DS GS SP BP IP
不常见寄存器:GT GS
寄存器简介:http://blog.youkuaiyun.com/rankun1/article/details/70880239
80386以前的cpu,也就是16位操作系统下,所有的寄存器都是16位的,等到了32位时代大多数的寄存器被intel扩展成32位的了,也就是4个字节 32位
注意是大多数,不是所有。比如AX变成EAX,BX变EBX, CX变ECX,DX变EDX, E是extend,扩展的意思。
所以,一个EAX的低16位是AX,AX低8位是AL,高八位是AH。EAX高16位没有名字,因为一般要么用低16位,要么用32位 ,没有单独用高16位。就像数字12(十二)一样,没有低位2,高位1没有意思。
到64位系统再次做了一个扩展,变成RAX RBX RCX RDX,这里不在多说。
EAX
EAX一般有一个很重要的用途,存储函数返回值!(这是编译器的建议规则,大多数编译器会遵循,但也有的不遵循,详情学习编译原理)
比如你调用一个函数,返回一个值,等函数调用完了 栈内存被销毁了,你去哪里找这个返回值呢?
比如这条语句:
int i = func();
假如func()返回一个值,实际上在函数销毁前
mov eax, 函数返回值
这样函数调用完了再从eax中将返回值取出来给i,也就是
mov ptr dword [i], eax
EAX一般用来放置函数的返回值,这在逆向很有用的。如果EAX放不下 就用EDX,如果还放不下就存储指针这样就够了。
ECX
这个C是count,计数用的,像for while等循环里面的计数,就是放在这个寄存器里面(这也是编译器的一条规则,当然也是建议规则)。
举例说明:
char sz[128] = {0};
它对应的机器指令如何呢?,首先就是给这段内存清0,假如现在eax里面就是sz的地址,那实际上就是一个循环 (下面代码只是大概,目的是为了说明ecx作为计数器的用法)
mov ecx 32
zzzz:mov eax, 0
dec ecx
test ecx
jnz zzzz
先看下编译器的思路,首先eax里面存的是数组的首地址,eax是四个字节,那么ecx表示计数的话,要清零这段内存ecx应该是多少?
每次搬动四个字节,总共128个字节,所以应该是128/4 = 32,(这个过程是编译器计算出32)
所以上面指令依次为:
ecx设置为32
zzzz:将eax清0
ecx减一
如果ecx不为0
跳转到zzzz
这样32次循环之后,整个数组就清零了。(疑问:eax增加没看到)
byte word dword
下面几行代码,有什么区别?
char c1 = 1;
shor c2 = 1;
int c3 = 1;
long c4 =1;
不同类型占用的内存长度不同,分别为1字节,2字节,4字节,4字节,那执行的时候cpu如何知道不同的长度?如何知道第一句就是1个字节 第二个是两个字节 第三个和第四个是四个字节?(byte,word,dword)
编译器会把他们编译成不同的机器指令,
mov byte ptr [c1的地址], 1
mov word ptr [c2的地址], 1
mov dword ptr [c3的地址], 1
mov dword ptr [c4的地址], 1
byte是一个字节,word是两个字节,dword是四个字节,这样来区分的。更多的字节,都是通过这些基础的组合。
但是实际的指令不是这样的,因为cpu不能直接将立即数直接放到内存中,必须通过寄存器。所以,先把1放到寄存器里面,再从寄存器转到对应的内存,
比如第一句
mov AL, 1
mov byte ptr [c1的地址], AL
先将1移入寄存器AL,然后将AL移入到c1的地址,大小为1字节
ptr byte [0xFF00EEFF]
意思是0xFF00EEFF地址1字节大小
空指针异常汇编解释
char* p = NULL;
*p = 9;
首先是把 内存地址p出的值赋值为0,
mov dword ptr [p], 0
补充
第一,数据只能从寄存器到寄存器,或者从寄存器到内存,或者从内存到寄存器,不能内存到内存,
比如 你就不能写这样的指令:
mov ptr dword [a1], ptr dword [a2]
这还是现在的冯·诺伊曼原理决定的,因为内存和cpu是不同的组件,cpu通过地址总线连接内存,所以数据必须先从内存中运到cpu中(存在寄存器),
然后再从寄存器搬回内存。
第二,看两种写法,有什么区别
mov eax, ebx
mov eax, [ebx]
第一个是把ebx寄存器里面存的值放到eax中 ,加了中括号表示里面值对应的内存地址中的值
比如ebx 现在等于8,那么mov eax, ebx这样eax就等于8了,如果是mov eax, [ebx]那么相当于把内存地址是8处的内存中的值放到eax中
实例讲解
待续。。。