【问题标题】:Delphi classes, shared memory, and varying DLL loading addressesDelphi 类、共享内存和不同的 DLL 加载地址
【发布时间】:2022-11-29 04:39:40
【问题描述】:

我正在使用一个古老而复杂的系统,该系统在数十个(有时数百个)Win32 进程之间共享内存。代码主要是几年前移植到 Delphi 的非常古老的 Pascal。

(几乎)所有代码都在一个 DLL 中,所有进程都加载它。目前,我们已强制该 DLL 的固定加载地址。 Image base 已定义,并且在链接器设置中禁用了 ASLR。每个进程在启动时都会检查 DLL 加载地址,如果 DLL 不能在所有进程中加载​​到完全相同的地址,则整个系统将拒绝工作。这当然是一个有问题的解决方案。有时,客户拥有各种影响地址空间并阻止我们的产品获得 DLL 所需地址的第 3 方小工具。

固定DLL加载地址的原因如下。我想知道是否有办法解决这个问题。

我一直在尝试介绍面向对象的编程。问题是,如果我在共享内存中实例化一个 Delphi 类,该实例现在似乎依赖于 DLL 加载地址。例如,如果另一个进程试图销毁那个对象,它将崩溃,除非这两个进程恰好具有相同的 DLL 地址。 Delphi 运行时似乎将函数地址保存在对象实例中,假设它们在对象的生命周期内保持不变。

一种可能的解决方案可能是将 DLL 内容复制到共享内存,然后对 DLL_PROCESS_ATTACH 进行某种魔术操作,使进程运行该代码副本而不是加载的 DLL 地址。我们拥有的共享内存总是映射到相同的地址。 (是的,这有时也是一个问题,但很少见,因为共享内存可以映射到容易获得的高地址(2 GB 以上)。)

或者有没有办法告诉 Delphi 编译器“请不要假设与此类相关的函数的地址是固定的”?我正在使用 Delphi 11.1。

【问题讨论】:

  • 回答你的最后一个问题:Delphi 编译器没有这样的选项。不过,我不确定如何解决您的一般问题,可能是因为我仍然不太了解该程序中发生的事情。
  • 您的解决方案问题DCOM。您将不得不评估恢复多年前做出的错误决定的难度。
  • 你不能分享对象跨进程边界,只有数据.这是一个非常糟糕的设计,需要重写。话虽这么说,而不是为 DLL 假设任何特定的加载地址,只需让 DLL 分配一个块共享内存在运行时,多个 DLL 实例可以共享,然后根据需要委托出该内存块的一部分。如果需要,创建一个自定义内存管理器来处理它。
  • 第二种解决方案是使用像 Microsoft Detours 这样的工具来拦截对 DLL 的调用,并将它们重定向到另一个进程中的正确地址。这是一个更复杂的解决方案,但它可以让您保留现有代码。
  • 附带说明一下,这种架构有几个历史原因: 1. 该软件已有将近 40 年的历史,最初构建它的操作系统没有线程。并行运行事物的唯一方法是运行多个进程。 2.代码是32位的,不能轻易翻译成64位。通过运行一堆进程,您可以使用大约 2 GB 的共享内存,外加每个进程的健康本地内存块 - 总共有效使用远远超过 4 GB。 3、DCOM自带的开销很大,不能在系统内部使用。

标签: delphi winapi dll shared-memory


【解决方案1】:

我能够找出一个似乎运作良好的解决方案,所以让我回答我自己的问题。

问题在于,为了使动态分派起作用,对象实例必须使用类型信息进行“标记”。对于 Win32 中的 Delphi,此标记位于对象实例的前 32 位中,它是 DLL 中的内存地址,其中包含相关类的代码。

如果您移动此地址以匹配 DLL 的变量(特定于进程的)地址,则动态分派的方法可以正常工作。为此,您需要在创建对象时将此地址与 DLL 的加载地址(或 DLL 内的任何其他引用地址)进行比较并保存偏移量。

然后,在另一个进程中调用该对象的方法之前,通过获取 DLL 的实际地址、添加偏移量并将此和写入该对象的前 32 位来“本地化”该对象。

现在您可以在任何进程中使用该对象,只要您先对其进行本地化:

Obj.Localize;
Obj.Do_Something;

这可以整齐地包装在一个类中。 Offset 只是一个私有的 32 位 UInt32。

constructor Global_Object.Create;

begin
   Self.Offset := PUInt32(Self)^ - Self.Reference_Address;
end;


procedure Global_Object.Localize;

begin
   PUInt32(Self)^ := Self.Reference_Address + Self.Offset;
end;


destructor Global_Object.Destroy;

begin
   inherited Destroy;
end;


function Global_Object.Reference_Address
                           : Cardinal;

begin
   // Anything in the DLL can be used as a reference,
   // such as the address of this function.

   Result := Cardinal(@Global_Object.Reference_Address);
end;

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-12-04
    • 2016-02-27
    • 1970-01-01
    相关资源
    最近更新 更多