Delphi:链接到BPL中没有PACKAGEd的变量的任何方法?

Chr*_*ski 3 delphi linker vcl

我正在研究RAD Studio 2007中的一个项目,使用c ++中的VCL类.

TDBLookupControl是VCL的一部分,并且有一些不良行为,这是由使用内部变量引起的 SearchTickCount

var
   SearchTickCount: Integer = 0; //file scope in DBCtrls.pas

procedure TDBLookupControl.ProcessSearchKey(Key: Char);
var
  TickCount: Integer;
  S: string;
begin
//some code removed for brevity
      TickCount := GetTickCount;
      if TickCount - SearchTickCount > 2000 then SearchText := '';
      SearchTickCount := TickCount;
//some code removed for brevity
end;
Run Code Online (Sandbox Code Playgroud)

但是,SearchTickCount从未在VCL内部进行PACKAGEd,如下例所示.

extern PACKAGE int SearchTickCount;
Run Code Online (Sandbox Code Playgroud)

我想SearchTickCount在我的c ++代码中设置为零(按需).在我的代码中对它进行外部化使得c ++编译.但是,链接器(显然)找不到变量.

namespace Dbctrls
{
  extern int SearchTickCount;
}
// later on, inside a function
Dbctrls::SearchTickCount = 0;
Run Code Online (Sandbox Code Playgroud)

有没有办法/解决方法来链接到这个变量?

编辑:不幸的是,我们还使用了一些源自TDBLookupControl的自定义控件,所以我想避免创建更多的自定义控件.

Cos*_*und 6

问题

SearchTickCount是在单元的实现部分中声明的全局(单元级)变量,它不应该在该单元之外访问.如果你使用Delphi而不是C++ Builder,你会遇到同样的问题.

理智的解决方案

  • 子类化TDBLookupControl,覆盖ProcessSearchKey()并确保它使用您自己的SearchTickCount,易于访问的.幸运的ProcessSearchKey()是虚拟,理论上这应该有效,但在实践中代码依赖于FListField,这是一个私有领域,所以我们将回到正方形1.
  • 将整体复制TDBLookupControl到您自己的TMyDBLookupControl,并确保您可以访问SearchTickCount.这肯定会起作用.

HACKY解决方案

当然,黑客攻击更有趣.CPU没有问题,SearchTickCount因为地址被编码到组成ProcessSearchKey's代码的ASM指令中.CPU可以读取什么,我们可以阅读.

评估ProcessSearchKey方法的代码时,它只使用一个全局变量(SearchTickCount),并在两个地方使用它.首先在这个测试中:

if TickCount - SearchTickCount > 2000 then
Run Code Online (Sandbox Code Playgroud)

那么在这个指令中:

SearchTickCount := TickCount;
Run Code Online (Sandbox Code Playgroud)

如果查看该例程的反汇编列表,则很容易发现全局变量访问,因为它在方括号中给出了变量的地址,没有其他限定符.为了if工作,编译器执行以下操作:

SUB EAX, [$000000]
Run Code Online (Sandbox Code Playgroud)

对于赋值,编译器执行如下操作:

MOV [$000000], EAX // or ESI on Delphi 7 with debug enabled
Run Code Online (Sandbox Code Playgroud)

如果查看汇编程序指令的左侧,可以用HEX表示法轻松查看实际的操作码.例如SUB EAX,[$ 000000]如下所示:

2B0500000000
Run Code Online (Sandbox Code Playgroud)

我的hacky解决方案利用了这一点.我得到实际过程的地址(TDBLookupControl.ProcessSearchKey),扫描代码寻找操作码(2B 05)并获取地址.就是这样,它的确有效.

当然,这有潜在的问题.这取决于使用那些精确寄存器编译的代码(EAX在我的示例中).编译器可以自由选择不同的寄存器.我使用Delphi7和Delphi 2010进行了测试,代码编译为Debug,编译时没有Debug.在所有4种情况下,编译器选择EAX用于SUB指令,并且在3/4情况下选择ESI用作MOV指令的寄存器.因此,我的代码只查找SUB指令.

另一方面,如果代码工作一次,代码每次都有效.代码一旦发布就不会改变,所以如果你可以在开发机器上正确测试,你就不会在客户的机器上得到令人讨厌的AV.但是使用风险自负,这毕竟是黑客攻击!

这是代码:

unit Unit2;

interface

uses DbCtrls;

function GetSearchTickCountPointer: PInteger;

implementation

type
  THackDbLookupControl = class(TDBLookupControl); // Hack to get address of protected member
  TInstructionHack = packed record
    OpCodePrefix: Word;
    OpCodeAddress: PInteger;
  end;
  PInstructionHack = ^TInstructionHack;

function GetSearchTickCountPointer: PInteger;
var P: PInstructionHack;
    N: Integer;
begin
  P := @THackDbLookupControl.ProcessSearchKey;
  N := 0; // Sentinel counter, so we don't look for the opcode for ever
  while N < 2000 do
  begin
    if P.OpCodePrefix = $052B then // Looking for SUB EAX, [SearchTickCount]
    begin
      Result := P.OpCodeAddress;
      Exit;
    end;
    Inc(N);
    P := PInstructionHack(Cardinal(P)+1); // Move pointer 1 byte
  end;
  Result := nil;
end;

end.
Run Code Online (Sandbox Code Playgroud)

你使用像这样的hacky版本:

var P: PInteger;
begin
  P := GetSearchTickCountPointer;
  if Assigned(P) then
    P^ := 1; // change SearchTickCount value!
end;
Run Code Online (Sandbox Code Playgroud)

  • @Cosmin我同意这一点.我只是一个紧张的性格,我可能会选择将所有内容都放在我的可执行文件中.在我的代码中,我使用类似的代码将TButton的默认高度从25更改为23.但我确保StdCtrls.dcu在我的模块而不是包中. (2认同)