不知道有木有骚年和我一样曾经疑惑过,我们说的分段机制转换最开始的逻辑地址哪里来的,比如程序的指令地址?要明白这个,我们首先需要知道代码编译成二进制后在磁盘上是怎么组织的(以windows下的exe文件为例)
举个栗子:
.386
.model flat,stdcall
Option Casemap:none
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
.data
szText db 'fwddsg',0
.code
Start:
invoke MessageBoxA,NULL,offset szText,offset szText,MB_OK
invoke ExitProcess,0
end Start
这段汇编使用MASMPlus编译后会生成一个exe文件,功能很简单,就是弹出个提示框。这段代码启动后第一条指令的地址是多少?
这边是OD加载程序后的截图,这边可以看到,Start处的地址为0x401000,’fwddsg’字符串的地址是0x403000,这些逻辑地址怎么来的呢?我们随便用款PE文件解析器打开程序:
PE头信息:
这边很容易看出,加载基址是0x400000,入口地址是0x1000,按这么算第一条指令的地址恰好是0x401000,我们再看下代码段和数据段在磁盘上的内容(想看文件在磁盘的内容,用UE打开exe文件即可)
代码段:
这边RVA虚地址0x1000在磁盘0x400(节数据地址)上对应的内容和OD上的内容一模一样,也就是说此节保存的就是代码编译后的指令(当然.text不一定保存的全是指令,只要节属性相同的内容,数据也可以放这边),所以第一条指令的起始地址就是0x40000+0x1000=0x401000,这边由于程序的main地址就是代码节起始地址,所以可以这样计算
数据段:
这边的RVA虚地址0x3000在磁盘0x800(节数据地址)上对应的内容是’fwddsg’,加上起始地址0x400000=0x403000,这下知道常量字符串的地址怎么算出来的吗?(这边要说下,dsg的含义据说语文好的认为是大帅哥,语文不好的就认为是大骚狗,很奇怪的是我身边的人的语文好像都不怎么好~)
上面PE文件的相关信息是在程序编译连接的时候由编译器和连接器完成的(注意这个时候保存的是RVA),所以最初的逻辑地址在程序编译连接完成后基本就可以计算出来了,细心的同学会发现大部分程序的加载基址都是0x400000(高10位为1),这个表示使用页目录的第一项,所有程序都用这一个基址没问题吗?别忘了,程序每次启动,操作系统都会为其分配一个独一无二的页目录基址,所以程序线性地址的索引相同也无所谓
(这边所有描述基本基于EXE文件,其他格式的PE文件可能有点不同,PE文件结构和加载详细过程可参考《琢石成器》相关章节)