是否可以让 FORTRAN DLL 每次加载到随机地址?
Posted
技术标签:
【中文标题】是否可以让 FORTRAN DLL 每次加载到随机地址?【英文标题】:Is it possible to get a FORTRAN DLL to load at a random address each time? 【发布时间】:2013-01-17 14:19:02 【问题描述】:我一直在搜索,但没有找到一个假定的编译器标志或类似的东西,可以让我构建我的 FORTRAN DLL(使用 Intel Visual Fortran Composer XE 2013 编译器)以便加载它每次随机基址。我在我的 C++ 代码中显式加载我的 FORTRAN DLL 并且它加载/卸载很好,但我只是注意到它每次加载到的地址都是完全相同的位置。我想知道这是否就是为什么我的 FORTRAN DLL 有时会成功加载,而有时当我同时运行我的程序多次时会失败。英特尔 Fortran 编译器是否存在任何随机基地址编译器选项?我已经阅读了它的发行说明,但也没有运气。
【问题讨论】:
你为什么关心它在内存中加载的位置? 因为我在我的 c++ 程序中使用了一些 FORTRAN 通用块来进行计算。恐怕如果每次来自公共块的这些值都会保留并因此导致问题时,它会加载到相同的位置... Windows 的 PE 加载器决定动态加载 DLL 的位置并修补地址。通常它是根据您的代码大小 IIRC 计算的。顺便说一句,不,即使您看到的地址“相同”,GDTR 和 LDTR 也会改变。因此,即使您多次加载可执行文件,公共块代码也会每次重新加载。 一个进程的内存与另一个进程的内存是分开的。 如果 c++ 可执行文件没有被多次加载会发生什么?它只是加载/卸载fortran dll会影响你上面提到的任何东西吗? 【参考方案1】:回答您的直接问题:是的,可以标记一个 DLL,以便最近的 Windows 版本将其加载到一个稍微随机的基地址。这是通过将/DYNAMICBASE 选项传递给链接器(link.exe
)来实现的。阅读链接页面以获取有关如何在 Visual Studio 中启用此功能的信息。如果ifort
在makefile 的命令行中使用,那么/link
选项可用于将标志传递给链接器:
ifort.exe ... /link /DYNAMICBASE
请注意,/link
选项应该是命令行中的最后一个选项,因为后面的所有内容都会传递给链接器。另请注意,/DYNAMICBASE
默认为 ON,您的 ibrary 应以稍微随机的地址加载(您运行的是 Windows XP 吗?)
然而这并不是真正必要的。解释为什么如下。
只是为了清楚地总结 cmets。 Windows 上的每个进程(不仅在 Windows 上,而且在几乎任何现代操作系统上,如 *BSD、Linux、OS X 等)都有自己的虚拟线性地址空间,并且用户空间中的所有内存都使用这些虚拟地址进行操作.虚拟内存分为页面,这些页面由物理内存的帧支持。一个物理内存帧可能映射到多个虚拟内存页面,甚至是来自不同进程的地址空间,从而促进进程之间的内存共享。虚拟内存页和物理内存帧之间的映射被维护在所谓的页表中。这些对于进程来说是本地的,因此映射是本地的,这意味着两个不同进程中的相同虚拟内存地址很可能会映射到完全不同的物理内存地址。一些操作系统(包括 Windows)将每个进程的虚拟地址空间分成两部分 - 下部和上部。下半部分属于进程,上半部分在所有进程中映射到操作系统的内核内存区域。这对应用程序开发人员来说几乎没有兴趣,因为内核内存无法从用户空间访问,因为他们缺乏必要的权限,因此仅表现为可用虚拟内存空间的减少(例如,只能从 2 或 3 GiB 访问在 32 位系统上为 4 GiB)。其他操作系统(其中 OS X 是最流行的桌面操作系统)拥有整个进程私有的虚拟地址空间,内核在其自己独立的虚拟内存空间中运行。
Windows(以及大多数其他实现虚拟内存管理的操作系统)上的可执行文件通常由不同的部分组成,其中部分被分组为段。可执行文件格式被设计成可以通过直接将其部分映射到虚拟地址空间来将其加载到内存中 - 这一过程称为内存映射。通常包含程序指令(通常称为.text
或类似名称)的可执行文件部分是只读的,因此可以在从同一可执行文件创建或已加载相同 DLL 的所有进程之间共享(DLL 也是具有与可执行文件相同的结构,但包含不同的部分集,并且不能单独运行)以节省物理内存。可能有许多其他部分包含不同的数据,例如.data
部分包含已初始化的静态(也是全局)变量,.bss
部分包含未初始化的静态变量、带有调试信息的部分、重定位部分、导入和导出表等。读/写(数据)部分通常是除非采取明确的措施,否则绝不会在不同的进程之间共享。
Fortran COMMON 块通常位于.bss
部分,因为它们只是未初始化的静态数据。如果使用 BLOCK DATA
构造使用数据初始化 COMMON 块,则将其放入 .data
部分。无论哪种方式,COMMON 块都以在加载 DLL 的不同进程之间不可共享的部分结束。最后,当两个进程加载 DLL 时,无论是作为其依赖项的一部分隐式加载还是显式使用LoadLibrary()
,只读部分将在两个进程之间共享,但读写数据部分(包括您的 COMMON 块)在每个进程中会有所不同,并且在一个进程中对数据的修改在另一个进程中是不可见的,即使在两个进程中加载到相同基址的 DLL 也是如此。
Windows DLL 具有称为“首选基地址”的功能。每当操作系统加载此类 DLL 时,它都会尝试将其放置在指定的首选基地址处。如果不能(例如,所需的虚拟地址空间的一部分已经被占用),那么它将库重新定位到不同的基地址。这种行为的原因是 DLL 重定位在 Windows 上代价高昂,因为绝对寻址用于访问全局符号,并且每当必须重定位库时,加载程序必须修补(修复)地址。相比之下,许多 Unix 系统将其动态库作为 PIC(与位置无关的代码),并且可以在任何基本虚拟地址处加载。但是 PIC 代码的执行速度比普通的位置相关代码要慢一些。
较旧的 Windows 版本具有它们最基本的库,例如 USER32.DLL
和 KERNEL32.DLL
始终加载在相同的基地址。它们的加载器是非常可预测的,如果您在启动和可执行时加载相同的库集并以相同的顺序加载,通常库在每次运行时加载到相同的基本虚拟地址。自 Vista 以来,这种情况发生了变化,它引入了地址空间布局的随机化 - 可选 功能,允许在随机虚拟基址加载特殊标记的可执行文件(和 DLL),以使其更难用于远程网络攻击,以找出各种操作系统或用户 API 调用的正确地址。
【讨论】:
以上是关于是否可以让 FORTRAN DLL 每次加载到随机地址?的主要内容,如果未能解决你的问题,请参考以下文章
vs2010下静态加载dll文件,每次都要把.dll .lib 复制到目录下,但书上说可以设置系统环境变量就不要复制