程序杂谈

标签: 编程语言

来自一个菜鸡的感想,大佬勿喷
刚刚听完老师讲指针,感觉颇多(蟹黄汤包 ),对于指针有了清晰的认识,就决定写篇博客记录下感受。
-------------------------------------------------------------------------------------------------------
这里还是从程序开始谈谈(这里只涉及PE文件)。
一个程序,XX.exe,我们将他从cpp变成了exe,这中间的过程很复杂。但是为什么是exe文件?为什么一定是exe文件?它又是怎么运行的?
我想,肯定有人试过把它用notepad打开(记事本),于是乎见到了传说中的锟斤拷 (乱码),但是这么一堆乱七八糟的东西肯定有它的规律,不然windows怎么把他运行的。
对,所有的程序都是windows运行的,而不是你想的那样,一个个自己就可以运行,换句话说,没得window系统,你的exe就像一块砖,啥用都没有,而在windows系统下,当我们双击我们的hello_world.exe的时候,windows会立马让explorer.exe运行它,并创建进程,explorer其实就是所谓的资源管理器,你可以试试打开任务管理器把它结束进程,我不建议这么做,因为结束后你会发现你的桌面除了壁纸以外空空如也,除非你喜欢。。
你也可以用cmd敲入以上指令,然后你的桌面就炸裂了,你可以用于整整你的好友,start explorer.exe就可以解除.
所以说,你运行的程序是由explorer.exe负责运行的,但其实说到这啥也没说,那到底是怎么运行的,你的exe都是存在磁盘里的,以数据的形式,但是运行确是在内存中的,所以系统会将数据映射到内存中去(Mapping),当然这里的内存是虚拟内存,我们知道正常情况下每个程序进程都是互不干扰的(修改器除外,那玩意用了windows api),这是由于window有一套虚拟内存机制,每个程序表面上有4G内存,但是实际上根本用不到4G,而且你的电脑能装几个4G呢?于是windows做了一件事,欺骗了这些程序,告诉你有4G,但实际上并没有,你真正用了多少,系统就为你在物理内存上分配多少,你访问0x12345678的地址,系统就帮你把你认为的地址转化为实际的物理地址或磁盘位置。
这就是一种映射关系,不难想象,程序在虚拟内存中的占用是稀疏的。就算内存不够用了,磁盘也是一种储存器(比内存慢得多),也可将磁盘当作虚拟内存一部分。


现在exe已经映射到了内存中了,这时候就会开始解析这个文件,exe表面上是一堆乱码,人无法理解,其实并不是这样,在聊exe文件格式之前必须得说说结构体与数据。
我们编程时经常用到数据类型,比如int,char,short,long long。我们知道他们分别占用4,1,2,8字节,每个字节是8位,也就是2的8次方,实际上还要考虑符号的占位。
我们也许认为这四种数据是分开的,完全的割裂的,实际上并不是这样,int就是四个unsigned char,short就是两个unsigned char。
可以这样讲吧,int相当于只是告诉编译器我声明了四个字节的空间(a,b,c,d),我要以一种固定的形式来访问,比如你申明了int a=0x11111111(0x表示十六进制),在内存里就变成了0x11,0x11,0x11,0x11,四个char(很显然不是吗),但是记住这是逆序储存的,顺序还是逆序其实取决于操作系统,可以试着测试一下,可以去百度小(大)端模式。

所以说你完全可以用int来存四个字符长度的字符串,亦可以将四个字符长度的字符串转化为int.

而结构体也是如此,相当于是一串数据(unsigned char)的自定义的访问方式。还有数组,一维数组就想你想的那样,在内存中也是一串数据,二维数组你可能觉得是方块,其实也是一串数据,比如a[10][20]的数组,它其实是10*20长度的数据,相当于每行数据首尾相连,由此可知,二维数组在内存中的存储也是连续的,在C/C++中多维数组都是以行的形式连续地分布在内存中的。

比如,0xaa,0xbb,0xcc,0x11,0x22,0x33就相当于int a[2][3]={{0xaa,0xbb},{0xcc,0x11},{0x22,0x33}}或a[3][2]={{0xaa,0xbb,0xcc},{0x11,0x22,0x33}},他们都在内存中连续储存,只不过访问方式不同罢了。

其实,二维数组就是一维数组的进阶版本,我们可以将它视为多个一维数组的集合,将行号和数组名当做是每一个一维数组的数组名,这样就容易理解多了。

三维数组也是如此。

好,我们大致知道了所有数据类型的本质都是几个byte(unsigned char),于是乎回到exe的解析,exe内容为什么看不懂,因为exe里的数据有其独特的意义,而非ascii上的意义,比如exe文件前两个字节都是MZ,这只是个标志,exe有文件头,当程序被映射到内存中去了后,就开始将这一堆数据套在了一个特定的结构体里。比如有个结构体叫做IMAGE_DOS_HEADER,在windows.h里定义了,它其实就是告诉你了exe数据怎么访问,比如第几个字节是什么变量,有什么意义。
下面来看看这个结构体

IMAGE_DOS_HEADER STRUCT 
{ 
+0h WORD e_magic   // Magic DOS signature MZ(4Dh 5Ah)     DOS可执行文件标记 
+2h   WORD  e_cblp  // Bytes on last page of file    
+4h WORD  e_cp   // Pages in file 
+6h WORD  e_crlc   // Relocations 
+8h WORD  e_cparhdr   // Size of header in paragraphs 
+0ah WORD  e_minalloc  // Minimun extra paragraphs needs 
+0ch WORD  e_maxalloc  // Maximun extra paragraphs needs 
+0eh WORD  e_ss    // intial(relative)SS value      DOS代码的初始化堆栈SS 
+10h WORD  e_sp    // intial SP value                 DOS代码的初始化堆栈指针SP 
+12h WORD  e_csum    // Checksum 
+14h WORD  e_ip    //    intial IP value                     DOS代码的初始化指令入口[指针IP] 
+16h WORD  e_cs    // intial(relative)CS value                    DOS代码的初始堆栈入口 
+18h WORD  e_lfarlc    // File Address of relocation table 
+1ah WORD  e_ovno        //    Overlay number 
+1ch WORD  e_res[4]    // Reserved words 
+24h WORD  e_oemid    //    OEM identifier(for e_oeminfo) 
+26h WORD      e_oeminfo   //    OEM information;e_oemid specific  
+29h WORD  e_res2[10]   //    Reserved words 
+3ch DWORD   e_lfanew     // Offset to start of PE header             指向PE文件头 
} IMAGE_DOS_HEADER ENDS

这里的+?h代表的是从这个文件开始偏移多少个字节,WORD其实就是short(两个字节),这里只是exe文件格式中最基础的结构体,还有PE文件头,区段表,导入导出表,各种各样的结构以数据的形式存在了文件里,windows都会读取这些信息(你也可以读取,直接拿指针指向exe文件的开头地址,就可以读取),用于初始化程序,比如入口点地址,这个也存在了IMAGE_DOS_HEADER中(那个e_ip)里。

在读入了这些信息后,操作系统就会将各种寄存器的值置位,然后设置程序从哪里开始,于是会操作系统就将程序真正运行起来,运行的代码其实也是一些数据,你可以将其反汇编,当然妄想直接看c代码的话,只能请出那位女人了。(IDA PRO)

所以说现在看来,指针就是一种访问方式,指针本质也是一个数,这个数就是地址,你这个指针的类型并不会影响这个指针变量的大小,但它确实会告诉编译器,你将怎么去访问指针指向地址的那一坨数据。

由于虚拟内存,你在你自己的进程里访问0x12345678处的地址和别的进程访问的结果显然是不一样的。

但是window提供了一个READ/WriteProcessMemory的函数,就可以骚扰 别的程序。

BOOL WriteProcessMemory(
HANDLE hProcess,
LPVOID lpBaseAddress,
LPVOID lpBuffer,
DWORD nSize,
LPDWORD lpNumberOfBytesWritten
);

还有,我还想谈谈堆和栈有什么区别,其实无论是linux还是windows下的ELF或PE,其中都有栈机制,栈是一种先入后出的数据结构,每个函数都有栈帧,而堆却是任何函数都可访问的区域,是不是很像全局变量,栈是连续的,(相比之下)有大小限制的,你在调用一个函数,都会将参数,这个函数执行完后该返回的地址,压入栈,同时还会抬高栈顶,用于这个函数内的临时变量,所以说你在调用函数的时候其实是由空间花销的,如果你写个递归层数太多了,占用的栈内存超过了OJ给定的,自然程序也会MLE FOR OJ。

或者是你输入的数据个数远大于分配的,你甚至可以修改相邻的数据,比如函数的返回地址,从而可以操纵这个程序的执行逻辑。(典型的栈溢出漏洞)

而当函数执行完毕后,程序会将这个函数内的东西都取出来,最后取出的函数返回地址,然后跳到这个地址去执行,于是就可以执行这个函数调用后的正确的代码。
而堆区是程序里动态分配的内容,堆区的内存容量大,使用灵活,分别后要自行回收容易产生内存碎片(malloc,free)。
所以,我们要是定义了很大的数组,那就请将它放在main函数外面(堆区)。
/
在windows下,我们只需要用malloc或free,但是具体他是怎么来管理这些内存的,我们并不知道,windows并没有开源,毕竟windows都提供好了,我们用就完事了。
还有一点想说的就是,好多库函数你自己并没有写,其实在链接的时候,都把那些写好的库函数代码塞进你的程序里去了,这种叫做静态链接
还有一种文件是dll,在程序运行前,系统就会检测程序的导入表,然后将这些dll也映射到内存中,这样程序也可以调用这些函数。
你可以自己写个dll,让后用LoadLibrary来玩玩(位于windows.h)。
回到开头,为什么是exe,你难道没发现windows都给你固定好了吗,IMAGE_DOS_HEADER已经限制了程序的结构,你要是不以这种结构来,windows怎么知道他是可执行文件呢?
这是菜鸡的一点点见解,有错误请大佬指出23333

版权声明:本文为Zarcs_233原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Zarcs_233/article/details/103586372