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

时间:2011-06-28 09:05:40

标签: delphi linker vcl

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

TDBLookupControl是VCL& amp;的一部分。有一些不良行为,这是由使用内部变量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;

但是,SearchTickCount从未在VCL中包含PACKAGEd,如下例所示。

extern PACKAGE int SearchTickCount;

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

namespace Dbctrls
{
  extern int SearchTickCount;
}
// later on, inside a function
Dbctrls::SearchTickCount = 0;

是否有任何方式/解决方法可以链接到此变量?

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

3 个答案:

答案 0 :(得分:6)

问题

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

理智的解决方案

  • TDBLookupControl进行子类化,覆盖ProcessSearchKey()并确保它使用您自己的SearchTickCount,一个易于访问的ProcessSearchKey()。令人高兴的是FListField是虚拟的,理论上这应该有用,但实际上代码依赖于TDBLookupControl,这是一个私有字段,所以我们将回到1。
  • 将整个TMyDBLookupControl复制到您自己的SearchTickCount,并确保您可以访问SearchTickCount。这肯定会有效。

HACKY解决方案

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

评估SearchTickCount方法的代码,它只使用一个全局变量(if TickCount - SearchTickCount > 2000 then ),并在两个地方使用它。首先在这个测试中:

SearchTickCount := TickCount;

然后在这条指令中:

if

如果查看该例程的反汇编列表,很容易发现全局变量访问,因为它在方括号中给出了变量的地址,没有其他限定符。要使SUB EAX, [$000000] 起作用,编译器会执行以下操作:

MOV [$000000], EAX // or ESI on Delphi 7 with debug enabled

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

2B0500000000

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

TDBLookupControl.ProcessSearchKey

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

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

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

以下是代码:

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

你使用像这样的hacky版本:

{{1}}

答案 1 :(得分:0)

另外两个选项:

哈克

  • 剪掉我自己的VCL

修复有问题的实现并确保所有包都使用我自己的VCL版本

神志正常

  • 避免TDBLookupControl s
  • 中的fkData和fkInternalCalc字段

之前我错过了这个实现细节。仅当字段类型为fkData或fkInternalCalc时才会检查SearchTickCount。计算字段(fkCalculated)应该完全避免这个问题。

答案 2 :(得分:0)

static int* s_TimerMemoryAddress;

union VTableHelper
{
    char* pointer;
    char** deref;
    unsigned int adjustment;
};

#pragma pack(1)
struct TInstructionHack
{
    WORD OpCodePrefix;
    int* OpCodeAddresss;
};

union FuncPtr
{
    TInstructionHack* Checker;
    char* Increment;
};
#pragma pack()

由于TDBLookupControl :: ProcessSearchKey是虚拟的,因此指向此函数的指针 不返回实际的非静态成员函数指针地址。 相反,它返回一个vtable地址,指向thunk (一小段代码重定向虚函数以纠正派生 对象非静态成员函数)。下面的代码计算出最终(非虚拟) 成员函数TDBLookupControl :: * ProcessSearchKey的地址基于thunk

try
{
    std::auto_ptr<TDBLookupControlHelper> hack(new TDBLookupControlHelper);
    TDBLookupControlHelper* ptrptr = hack.get();

    VTableHelper thunk;
    thunk.pointer = reinterpret_cast<char*>(ptrptr);
    thunk.pointer = *thunk.deref;       //get virtual table pointer
    //adjust for specific function pointer (TDBLookupControl::* ProcessSearchKey)
      as specified by thunk
    thunk.adjustment += 0xF4;       
    thunk.pointer = *thunk.deref;
    thunk.adjustment += 0x02;       //adjust for long jump instruction
    thunk.pointer = *thunk.deref;
    //get actual location of TDBLookupControl::ProcessSearchKey
    thunk.pointer = *thunk.deref;

    FuncPtr ptr;
    ptr.Increment = thunk.pointer;

    //2000 is completely arbitrary, only to prevent an infinite loop
    for(int counter = 0; counter < 2000 && s_TimerMemoryAddress == NULL; ++counter)
    {
         // Looking for SUB EAX, [SearchTickCount]
        if(ptr.Checker->OpCodePrefix == 0x052B)
            s_TimerMemoryAddress = ptr.Checker->OpCodeAddresss;
        else
            ptr.Increment++;
        counter++;
    }
}
catch(...) // catch any illegal dereferences of VTableHelper
{
}