程序地址重定位和模块绑定

1.程序的构建

在构建程序的时候,链接器都会给程序设置一个默认的加载地址,即首选基地址,它表示该模块被映射到进程地址空间时最佳的内存地址。默认情况下,对于EXE程序而言,windows链接器会将它的首选基地址设置为0X400000(四十万),而DLL程序的首选基地址则被设置为0X10000000(1千万),然后链接器将该地址以及一些相关数据和代码的地址写入到PE文件中。首选基地址的是为了系统程序加载器设计的,作用是告诉加载器把程序优选加载到该首选基地址,然后就可以直接将其他的数据和代码加载到内存中。

由于程序中会有代码和数据,而这些代码所引用的数据地址在程序构建完成后就已经确定了,但是这些数据地址是相对地址,是相对于设置好的首选基地址。对于某个EXE文件,假如代码中有一条指令:

                   MOV    EAX, [0X52050]

假如该EXE被加载到首选基地址(0X400000,一般而言,EXE都会加载的首选基地址的,因为EXE都有自己的独立地址空间),那么该条指定实际上是把该进程空间内的地址为0X52050+0X400000=0X452050处的内容移到寄存器EAX中,但是如果该EXE不是加载到首选基地址,那么该条指令的数据地址就不再是0X452050了,而是EXE实际加载的基地址+0X52050,这个就涉及到程序的加载和地址重定位的问题了。


2.程序的加载

系统程序加载程序的时候是根据程序的PE文件格式加载的,一般而言,由于EXE文件都是有自己独立的地址空间的,所以EXE一般都可以加载到EXE的首选基地址空间上。而对于DLL程序而言,由于DLL一般是加载到其他的进程的地址空间内,而对应的进程往往需要加载多个DLL模块,所以大多时候的DLL都不会加载到首选基地址上,这个时候程序模块的重定位段的作用就凸现出来了。
所谓重定位就是当某模块未被载入到首选基地址时,加载器会计算模块实际载入的地所以DLL一定要有重定位段,除非在链接时使用/FIXED 开关。此时链接器会去掉重定位段。如果此后模块未被载入首选基地址,由于无法重定位,模块不会被载入,导致程序无法运行。址跟首选基地址的差值,将这个差值加到机器指令所引用的原来的地址,得到的就是模块中各指令所引用的数据在本进程地址空间的正确地址,随后加载器会修正模块中对每个内存地址的引用。为了便于系统有能力对各数据的地址进行修正,windows提供了重定位段,它包含很多基址重定位信息,这个段是有很多项组成,每一项表示一个要重定位的地址,它包含一个字节偏移量列。该偏移量表示一条机器指令所使用的内存地址,这便于系统在确认该模块需要重定位时对需要重定位的数据进行定位。
由于程序有时候并不能加载到首选基地址,所以需要在加载的时候做重定位,根据程序实际加载的基地址和程序的重定位段对重定位段中的每一项做重定位。由于DLL的特性导致DLL一定要有重定位段,除非在链接时使用/FIXED 开关,此时链接器会去掉重定位段。如果此后DLL模块未被载入首选基地址,由于无法重定位,模块不会被载入,导致程序无法运行。如果加载程序将模块加载到它的首选基地址,那么系统就不会访问模块的重定位段。否则系统会打开模块的重定位段并遍历其中的条目,对每个条目,加载程序先找到包含机器指令的那个存储页面,算出差值,将差值加到机器指令当前正在使用的内存地址上。
对于重定位而言,其对系统的开销还是蛮大的:
a) 由于每次重定位需要对重定位段中的每一项做重定位操作,需要修改对应的代码,影响程序的初始化效率;
b) 当加载程序写入模块的代码页面时,由于它们具有写时复制属性,写时复制机制会导致系统从页交换文件中分配空间来容纳这些修改后的页面。

对于EXE文件而言,系统首先给该EXE创建进程空间,然后将该EXE映射到内存中,接着检查该EXE的导入段,把需要加载的DLL加载过来,在加载DLL的时候检查该DLL的导入段,将DLL对应的附加DLL也加载过来,当所有的DLL加载完毕后需要修复EXE的导入函数表。此时,检查对应DLL的实际记载的基地址和对于函数的相对偏移地址(RVA),计算出函数在进程内的虚拟地址(VA)=基地址+RVA,然后用该地址修复对应的导入符号的引用即可。

所谓的模块绑定就是说在运行之前,所有导入符号在进程地址空间的地址已经获得,不需要加载时在计算出来这节省初始化时间,另外将导入符号的虚拟地址写入exe模块的导入段,也会由于写时复制机制将要修改的页面以系统页交换文件为后备存储器。这会遇到与基址重定位相似的问题。所以模块绑定对提高系统性能的提高是显著的。

对于DLL而言,因为其一般是加载到其他进程空间的,如果加载到目标进程空间内不需要重定位,那么其加载过程和EXE的加载类似,如果需要重定位,则首选需要对模块做重定位,然后再映射到目标进程内,其后续的过程也和EXE的加载类似。


3.VS工具

根据地址重定位的原理和修复导入表的过程可知,如果希望程序能够快速加载,那么就希望程序加载的时候不需要做地址重定位,也不需要修复导入符号引用地址,这样加载程序就会很顺畅,提升系统性能。对于解决上述的两个问题,微软给我们提供了两个工具:ReBase和Bind。顾名思义,ReBase是用来解决地址重定位的问题,而Bind则用来解决模块绑定的问题。ReBase的工作原理是针对某个应用程序所使用的DLL的首选基地址做优化,使得优化后加载DLL时不再需要地址重定位,而Bind则模拟程序加载,获取导入符号的地址,并修复程序导入符号表,同时将修复后的地址写入磁盘文件中。
Rebase.exe的工具,如果在执行它时为它提供一组映像文件名,它会执行以下操作:
a) 它会模拟创建一个地址空间。
b) 它会打开这一组映像文件,并得到它们的大小和首选基地址。
c) 它会在模拟的地址空间对模块重定位的过程进行模拟,以便各模块没有交叉。
d) 对于每个需要重定位的模块,它会解析模块的重定位段,并修改模块在磁盘文件中的代码。
e) 将每个模块新的首选基地址写入各个模块磁盘文件中。
所以通过这种方法后可解除一个程序的地址重定位问题,但是需要注意的是,这种优化只针对某个特定程序有效,当这几个DLL同时加载到其他程序里面的时候就未必不会发生地址重定位的问题了。
Bind.exe工具,如果在执行它的时候传给它一个映像文件名,它会对其执行模块绑定操作。具体过程为:
a) 它会打开模块的导入段。
b) 对导入段列出的每个DLL,它会检查该DLL文件的文件头,来确定该DLL的首选基地址。
c) 它会在DLL的导出段查看每个符号。
d) 取得符号的RAV,并将其与模块的首选基地址相加。得到导入符号的虚拟地址(VA)。
e) 在映像文件的导入段中添加额外信息。这些信息包括映像文件被绑定的各DLL的名称,以及各模块的时间戳。
因为该工具假设DLL会加载到首选基地址,所以如果程序在加载时出现了地址重定位问题,那么模块绑定是无效的,该工具有效的时候需要满足的条件是:
a) 进程初始化时所需的DLL都被映射到了它们的首选基地址。
b) 绑定完成之后,DLL导出段所列出的符号的位置没有发生改变。这可以通过检查每个DLL的时间戳来保证。
如果上述假设有一个不成立。加载程序必须像绑定之前一样,手动修正可执行文件导入段。如果都成立加载程序就可以不用做这些工作了。


参考

1 《Windows核心编程系列》谈谈基址重定位和模块绑定 ithzhang


转载于:https://www.cnblogs.com/arbboter/p/4225204.html

<think>首先,用户的问题是:“我想理解基于软件的动态重定位的工作原理及其在计算机系统中的应用 请问基于软件的动态重定位原理及实现方式”。我需要基于系统级指令用户可见层的要求来回答。 系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 用户可见层: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用: - 引用[1]:关于重定位的修复,涉及修改重定位地址。 - 引用[2]:关于信息技术发展,不直接相关。 用户的问题是关于基于软件的动态重定位的原理实现方式。引用[1]提到了重定位表、修改地址等,这与动态重定位相关。我应该基于这个来构建回答。 关键点: - 动态重定位:在程序加载时或运行时修改内存地址,以适应实际加载地址。 - 基于软件:这意味着重定位由软件(如操作系统、加载器)处理,而不是硬件。 - 原理:程序编译时使用相对地址或偏移量;加载时,加载器计算实际地址并修改代码或数据中的引用。 - 实现方式:涉及重定位表(Relocation Table),其中存储需要修改的地址位置。 从引用[1]: - “修复重定位,通过重定位表找到能存储着需要重定位地址地址,然后我们把这个地址存储的数据给改了” - “修改重定位信息,简而言之就是把需要重定位地址改成基于新基址的虚拟地址” - 例如,计算新地址:基于新基址的虚拟地址。 应用:在操作系统中加载可执行文件、动态链接库(DLL)等。 回答结构: 1. **介绍动态重定位**:解释概念为什么需要它。 2. **工作原理**:详细步骤,包括编译时、加载时。 3. **实现方式**:描述重定位表、加载器如何修改地址。 4. **应用**:在计算机系统中的应用,如程序加载、共享库。 5. **生成相关问题**:在末尾添加相关问题。 使用中文回答。 数学表达式:如果有地址计算,使用行内数学格式,如$base + offset$。 引用:在引用段落末尾添加引用标识,如[^1]。引用[1]是相关的,引用[2]不直接相关,所以我主要参考引用[1]。 逐步回答: - 步骤1:解释动态重定位的基本概念。 - 步骤2:描述原理,包括地址绑定。 - 步骤3:实现方式,基于重定位表。 - 步骤4:应用场景。 - 步骤5:相关问题。 确保真实可靠:基于标准计算机科学知识。</think>### 基于软件的动态重定位原理及实现方式 基于软件的动态重定位是计算机系统中一种关键的内存管理技术,主要用于程序加载运行时地址调整。它允许程序在编译时使用相对地址(例如,基于假设的基地址),而在实际加载到内存时,由软件(如操作系统加载器)动态修改这些地址,以适应程序的实际加载位置。这在多任务系统共享库环境中至关重要,因为它提高了内存利用率程序的可移植性。下面我将逐步解释其工作原理、实现方式,以及应用场景,确保回答清晰可靠。 #### 1. **动态重定位的工作原理** 动态重定位的核心在于“地址绑定延迟”:程序在编译链接阶段不绑定绝对内存地址,而是使用相对偏移量;加载或运行时,软件加载器根据实际基地址调整这些地址。其工作流程如下: - **编译阶段**:编译器生成目标代码时,使用相对地址(relative addressing)。例如,变量或函数的地址表示为相对于一个假设基地址(如$0x0000$)的偏移量。例如,一个变量地址可能写为$offset$,而不是绝对地址。 - **链接阶段**:链接器生成可执行文件时,添加重定位表(Relocation Table)。这个表记录了所有需要修改的地址位置(如指令或数据引用),以及它们如何基于新基地址调整。例如,重定位表条目可能包含: - 需要修改的内存位置(relative virtual address, RVA)。 - 偏移量类型(如绝对地址或相对跳转)。 - **加载/运行时阶段**:当程序被加载到内存时,加载器执行以下步骤: 1. 确定程序的实际加载基地址($base_{actual}$)。例如,如果程序加载到$0x400000$。 2. 遍历重定位表,找到每个需要修改的地址位置。 3. 计算新地址:将原偏移量加上新基地址,得到绝对虚拟地址。公式为: $$ address_{new} = base_{actual} + offset $$ 其中,$offset$ 是编译时确定的相对偏移量。 4. 修改内存中的值:加载器直接覆盖原地址位置的数据,将其更新为$address_{new}$。这确保了所有内存引用(如函数调用或数据访问)都指向正确位置。 这个过程的关键是“软件介入”:加载器(软件组件)动态修改地址,而不是依赖硬件(如MMU)。这使程序能适应不同的内存布局,例如在操作系统调度时,程序可能被加载到任意空闲地址[^1]。 #### 2. **实现方式** 基于软件的实现主要依赖重定位加载器逻辑。以下是详细步骤,参考了标准计算机系统设计(如Windows PE格式或ELF格式): - **重定位表结构**:重定位表是文件头的一部分(例如,在PE文件中是`.reloc` section)。每个条目包含: - RVA(相对虚拟地址):需要修改的内存位置相对于文件基址的偏移。 - 类型标志:指示如何修改(如32位绝对地址或相对跳转)。 例如,一个条目可能表示为$(RVA, type)$。 - **加载器算法**:加载器执行重定位的伪代码如下(简化版): ```c void apply_relocations(uintptr_t base_actual, RelocTable* table) { for (each entry in table) { uintptr_t* target_address = (uintptr_t*)(base_actual + entry.RVA); // 计算需要修改的地址位置 *target_address = base_actual + (*target_address - base_default); // 修改为基于新基址的地址 } } ``` 这里: - $base_{default}$ 是编译时假设的基地址(如$0x400000$)。 - $base_{actual}$ 是实际加载基地址。 - 修改过程:加载器读取重定位表,找到目标地址位置(如引用[1]所述,“找到能存储着需要重定位地址地址”),然后覆盖其值。 实现细节: - **效率优化**:重定位表通常使用区块压缩(block-based compression),只存储变化部分,减少加载时间。 - **错误处理**:如果基地址冲突(如地址已被占用),加载器可能触发异常或重试加载。 - **示例计算**:假设程序编译时基地址为$0x400000$,一个变量偏移为$0x1000$;实际加载到$0x500000$。则新地址为$0x500000 + 0x1000 = 0x501000$。加载器修改内存中该位置的值[^1]。 - **软件依赖性**:这完全由软件实现,无需硬件支持。操作系统(如Linux的ld.so或Windows的Loader)提供加载器组件。引用[1]强调了这一过程:“把需要重定位地址改成基于新基址的虚拟地址”,这正是软件动态重定位的本质。 #### 3. **在计算机系统中的应用** 基于软件的动态重定位广泛应用于以下场景,提升系统灵活性效率: - **程序加载**:在多任务操作系统中,程序被加载到随机空闲地址以避免冲突。例如,Windows的ASLR(地址空间布局随机化)技术使用动态重定位增强安全性。 - **共享库(DLL/SO)**:动态链接库在运行时加载到不同进程的地址空间。重定位确保库代码能适应进程的基地址,实现内存共享。 - **虚拟内存管理**:在分页系统中,重定位支持程序在物理内存中的移动,而无需重新编译。 - **软件加壳与安全**:在反病毒或软件保护中,加壳工具(如UPX)使用重定位修改宿主程序地址,以隐藏原始代码(引用[1]提到“壳加到PE之后的作为新区段”的应用)。 - **嵌入式系统**:在资源受限设备中,动态重定位减少内存碎片,支持动态加载模块。 优势包括:减少内存浪费(多个程序共享代码)、提高可移植性(程序不依赖固定地址)、支持动态扩展。缺点:轻微性能开销(加载时间增加),但通常可忽略。 #### 总结 基于软件的动态重定位通过软件加载器动态修改内存地址,实现程序地址空间的灵活绑定。核心原理是延迟地址绑定,依赖重定位表指导修改过程;实现方式涉及文件格式加载器算法;应用覆盖操作系统、安全等领域。理解此技术有助于深入计算机系统底层机制。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值