Delphi 10.3与11内存管理差异及旧组件访问违例问题咨询
Delphi 10.3与Delphi 11 32位平台内存管理核心差异
- 内存管理器版本迭代:Delphi 10.3及更早版本32位Windows平台默认搭载FastMM4内存管理器,Delphi 11开始32位平台默认切换为重构后的FastMM5,两者的小内存块分配策略、内存对齐规则、堆块元数据存储结构完全不同。
- 默认安全标记变更:Delphi 11编译32位程序时默认在PE头添加
IMAGE_DLLCHARACTERISTICS_NX_COMPAT(数据执行保护DEP兼容)、IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE(地址空间布局随机化ASLR支持)标记,系统会对这类程序强制开启数据执行保护:普通PAGE_READWRITE权限的内存页不允许执行机器码,一旦跳转执行直接触发访问违例。Delphi 10.3默认不添加这两个标记,系统会对程序做DEP兼容兜底,普通内存页的代码可以正常执行,这也是升级后原有代码直接报错的核心原因。 - 内存对齐规则调整:Delphi 11 32位平台默认全局对齐规则从4字节调整为8字节,包含函数指针、内嵌汇编的结构体大小、成员偏移和Delphi 10.3下的计算结果存在差异,硬编码偏移的老旧代码会直接出现内存越界。
直接替换为VirtualAlloc分配内存后仍出问题的原因
- 绕过了Delphi内存管理器的初始化逻辑:
New()分配结构体时会自动将所有内存初始化为0,对结构体内部的字符串、接口、动态数组等引用计数类型完成初始化标记;VirtualAlloc直接返回的是未初始化的裸内存,残留的随机值会被后续逻辑当成有效指针、引用计数操作,触发随机内存错误。 - 分配释放逻辑不匹配:如果后续代码仍调用
Dispose()释放VirtualAlloc返回的内存,会直接破坏FastMM的堆结构,导致后续所有内存操作出现不可预期的错误;如果不调用Dispose又会出现内存泄漏。 - 不必要的权限放大:直接分配全生命周期可执行的内存页,既不符合系统安全规范,也容易被其他恶意代码利用篡改执行逻辑,间接引发内存异常。
可落地的修复方案
- 方案1:快速兼容旧代码(改动最小)
在项目dpr文件的最开头添加如下编译器指令,完全对齐Delphi 10.3的编译行为:
重新编译后原有代码不需要任何修改即可正常运行。uses Winapi.Windows; {$IFDEF WIN32} // 关闭默认的DEP、ASLR标记,对齐10.3的PE头配置 {$SetPEOptFlags 0} // 全局恢复4字节内存对齐 {$ALIGN 4} // 如需100%匹配内存管理器行为,可将Delphi 10.3自带的System.FastMM4.pas加入项目编译,自动替换11自带的FastMM5 {$ENDIF} - 方案2:规范化修改可执行桩逻辑(推荐,符合新版本安全规则)
保留原有New()分配逻辑,不要直接用VirtualAlloc分配裸内存,仅在需要写入机器码时临时修改对应内存页的权限,既保留内存管理器的初始化、回收逻辑,又满足DEP的权限要求:
后续释放时正常调用var OldProtect: DWORD; begin New(Stub); // 修改Stub对应内存页的权限为可执行读写 if not VirtualProtect(Stub, SizeOf(TStub), PAGE_EXECUTE_READWRITE, OldProtect) then RaiseLastOSError; try // 原有Stub初始化、机器码写入、函数地址绑定逻辑保持不变 finally // 初始化完成后如果不需要再修改内存,可以改回只读执行权限,降低安全风险 VirtualProtect(Stub, SizeOf(TStub), PAGE_EXECUTE_READ, OldProtect); end; end;Dispose(Stub)即可,完全匹配内存管理器的分配释放逻辑,不会出现堆损坏。 - 额外检查项
对老旧SDK中所有和TStub相关的结构体声明,统一用{$ALIGN 4}包裹,强制结构体成员按4字节对齐,避免编译器自动调整成员偏移导致的硬编码逻辑失效。
内容的提问来源于stack exchange,提问作者Levent Üncü
相关产品推荐
相关产品推荐

