Delphi: есть ли способ связать переменную в BPL, которая не была PACKAGEd? - PullRequest
3 голосов
/ 28 июня 2011

Я работаю над проектом в RAD Studio 2007, используя классы VCL в c ++.

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;

Однако, SearchTickCount никогда не упаковывался внутри VCL, как в примере ниже.

extern PACKAGE int SearchTickCount;

Я бы хотел установить SearchTickCount на ноль (по требованию) в моем коде c ++. Превышение этого в моем коде делает компиляцию c ++. Однако компоновщик (очевидно) не может найти переменную.

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

Есть ли способ / способ обойти эту переменную?

EDIT: К сожалению, мы также используем некоторые пользовательские элементы управления, производные от TDBLookupControl, поэтому я старался избегать создания дополнительных пользовательских элементов управления.

Ответы [ 3 ]

6 голосов
/ 28 июня 2011

проблема

SearchTickCount - это глобальная переменная (уровня блока), объявленная в разделе реализации модуля, к которой нельзя обращаться за пределами этого модуля. У вас возникла бы та же проблема, если бы вы работали с Delphi, а не с C ++ Builder.

Нормальные решения

  • Подкласс TDBLookupControl, переопределите ProcessSearchKey() и убедитесь, что он использует ваш SearchTickCount, тот, который легко доступен. К счастью ProcessSearchKey() является виртуальным, теоретически это должно работать, но на практике код зависит от FListField, это частное поле, поэтому мы вернемся к квадрату 1.
  • Скопируйте весь TDBLookupControl на свой TMyDBLookupControl и убедитесь, что вы можете получить доступ к SearchTickCount. Это будет окончательно работать.

HACKY решение

Конечно, хаки гораздо веселее. Процессор не имеет проблем с поиском SearchTickCount, потому что адрес закодирован в инструкции ASM, которые составляют код ProcessSearchKey's. То, что процессор может прочитать, мы можем прочитать.

Оценивая код для метода ProcessSearchKey, он использует только одну глобальную переменную (SearchTickCount) и использует ее в двух местах. Первый в этом тесте:

if TickCount - SearchTickCount > 2000 then

тогда в этой инструкции:

SearchTickCount := TickCount;

Если вы посмотрите на список разборок этой подпрограммы, доступ к глобальной переменной будет легко обнаружен, потому что он дает адрес переменной в квадратных скобках, без другого квалификатора. Чтобы if работал, компилятор делает что-то вроде этого:

SUB EAX, [$000000]

Для присваивания компилятор делает что-то вроде этого:

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

Если вы посмотрите слева от инструкции на ассемблере, вы можете легко увидеть фактический код операции в шестнадцатеричной записи. Например, SUB EAX, [$ 000000] выглядит так:

2B0500000000

Мое хакерское решение использует это. Я получаю адрес самой процедуры (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.

Вы используете хакерскую версию, подобную этой:

var P: PInteger;
begin
  P := GetSearchTickCountPointer;
  if Assigned(P) then
    P^ := 1; // change SearchTickCount value!
end;
0 голосов
/ 26 августа 2011
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
{
}
0 голосов
/ 29 июня 2011

Еще 2 варианта:

Хаки

  • вырезать мой собственный VCL

Исправить проблемную реализацию и убедиться, что все пакеты работают с моей собственной версиейVCL

Sane

  • избегать полей fkData и fkInternalCalc в TDBLookupControl s

Я пропустил эту деталь реализации раньше.SearchTickCount проверяется, только если тип поля fkData или fkInternalCalc.Расчет полей (fkCalculated) должен полностью избежать проблемы.

...