【问题标题】:Getting StackOverflow exception while reading stringlist in newly hooked LoadResString method在新挂钩的 LoadResString 方法中读取字符串列表时出现 StackOverflow 异常
【发布时间】:2015-08-02 19:37:12
【问题描述】:

供参考 - 以下是我的代码,我在 NewLoadResString 函数中得到 StackOverflow 异常。这种情况就像我创建了两个字符串列表,即 RecStrNameIdMap 和 NewStringValueList。 这里的 RecStrNameIdMap 是哈希字符串列表,用于存储名称和字符串标识符的映射。这样我就可以为其标识符(即 ID)引用资源字符串名称。

NewStringValueList 是一个字符串列表,其中包含少数资源字符串的新值。

我已经在 system.LoadResString 方法上连接了 NewLoadResString 方法。在新方法中,我正在检查 NewStringValueList 中给定资源字符串是否有新值,然后获取该值并返回新的而不是旧的声明值。

在线*发生堆栈溢出异常

如果 RecStrNameIdMap.IndexOfName(IntToStr(ResStringRec^.Identifier)) > -1 然后

* 谁能检查我为什么会收到此错误。

unit UnitTest;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IniFiles, StdCtrls;

type
  TForm2 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;


type
  TMethodHook = class
  private
    aOriginal : packed array[ 0..4 ] of byte;
    pOldProc, pNewProc : pointer;
    pPosition : PByteArray;
  public
    constructor Create( pOldProc, pNewProc : pointer );
    destructor Destroy; override;
  end;


var
  Form2: TForm2;

implementation

{$R *.dfm}

ResourceString
  RS_1 = 'ABC';
  RS_2 = 'XYZ';

procedure TForm2.Button1Click(Sender: TObject);
var
  aMethodHook: TMethodHook;
  RecStrNameIdMap: THashedStringList;
  NewStringValueList: TStringList;

  {Hookup aNewProcedure on aOriginalProcedure}
  procedure RegisterProcedures(aOriginalProcedure, aNewProcedure: pointer);
  begin
    if Assigned(aOriginalProcedure) and Assigned(aNewProcedure) then
      aMethodHook := TMethodHook.Create( aOriginalProcedure, aNewProcedure);
  end;

  {Replacement for System.LoadResString}
  function NewLoadResString(ResStringRec: PResStringRec): String;
  var
    Buffer: array [0..4095] of char;
  begin
    if ResStringRec = nil then Exit;
    if ResStringRec.Identifier >= 64 * 1024 then
    begin
      Result := PChar(ResStringRec.Identifier);
    end
    else
    begin
      if RecStrNameIdMap.IndexOfName(IntToStr(ResStringRec^.Identifier)) > -1 then
      begin
        Result := NewStringValueList.Values[
          RecStrNameIdMap.Values[IntToStr(ResStringRec^.Identifier)]];
      end
      else
      begin
        SetString(Result, Buffer,
          LoadString(FindResourceHInstance(ResStringRec.Module^),
            ResStringRec.Identifier, Buffer, SizeOf(Buffer)));
      end;
    end;
  end;

  procedure CreateNameIdMapping;
  begin
    {This is done to get string name from ID}
    RecStrNameIdMap.CaseSensitive := False;
    RecStrNameIdMap.Add(Inttostr(PResStringRec(RS_2)^.Identifier)+'='+'XYZ');
  end;

begin
  aMethodHook := nil;
  try
    RecStrNameIdMap := THashedStringList.Create;
    NewStringValueList := TStringList.Create;

    CreateNameIdMapping;

    {Create new value list for ResourceStrings}
    NewStringValueList.Add('XYZ'+'='+'new value for ResourceString RS_2');
    RegisterProcedures(@System.LoadResString, @NewLoadResString);

    {This should return 'new value for ResourceString RS_2' instead of 'XYZ'}
    ShowMessage(RS_2);

    {This should return 'ABC' - no change in value}
    ShowMessage(RS_1);
  finally
    aMethodHook.Free;
    RecStrNameIdMap.Free;
    NewStringValueList.Free;
  end;
end;

{ TMethodHook }

constructor TMethodHook.Create(pOldProc, pNewProc: pointer);
var
  iOffset : integer;
  iMemProtect : cardinal;
  i : integer;
begin
  Self.pOldProc := pOldProc;
  Self.pNewProc := pNewProc;

  pPosition := pOldProc;
  iOffset := integer( pNewProc ) - integer( pointer( pPosition ) ) - 5;

  for i := 0 to 4 do aOriginal[ i ] := pPosition^[ i ];

  VirtualProtect( pointer( pPosition ), 5, PAGE_EXECUTE_READWRITE,
    @iMemProtect );

  pPosition^[ 0 ] := $E9;
  pPosition^[ 1 ] := byte( iOffset );
  pPosition^[ 2 ] := byte( iOffset shr 8 );
  pPosition^[ 3 ] := byte( iOffset shr 16 );
  pPosition^[ 4 ] := byte( iOffset shr 24 );
end;

destructor TMethodHook.Destroy;
var
  i : integer;
begin
  for i := 0 to 4 do pPosition^[ i ] := aOriginal[ i ];
  inherited;
end;

end.

【问题讨论】:

    标签: delphi winapi hook resourcestring


    【解决方案1】:

    似乎替换过程不能是嵌套例程。
    documentation中所述:

    过程类型允许您将过程和函数视为可以分配给变量或传递给其他过程和函数的值。

    ...

    嵌套过程和函数(在其他例程中声明的例程)不能用作过程值,预定义过程和函数也不能。

    过程类型是指针。虽然嵌套例程不能用作过程类型,但我假设指向嵌套例程的指针不能用作过程参数,否则此操作可能会产生不可预知的结果,例如在这种情况下。
    该过程已正确挂钩(您做到了);我提取了NewLoadResString 过程,stackoverflow 错误不再发生。
    弹出的resourcestring 始终是旧的,但我没有对NewLoadResString 过程进行任何更改。
    整个编辑单元如下。

    unit UnitTest;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
      Dialogs, IniFiles, StdCtrls;
    
    type
      TForm2 = class(TForm)
        Button1: TButton;
        procedure Button1Click(Sender: TObject);
      private
        { Private declarations }
        RecStrNameIdMap: THashedStringList;
        NewStringValueList: TStringList;
      public
        { Public declarations }
      end;
    
    
    type
      TMethodHook = class
      private
        aOriginal : packed array[ 0..4 ] of byte;
        pOldProc, pNewProc : pointer;
        pPosition : PByteArray;
      public
        constructor Create( pOldProc, pNewProc : pointer );
        destructor Destroy; override;
      end;
    
    
    var
      Form2: TForm2;
    
    implementation
    
    {$R *.dfm}
    
    ResourceString
      RS_1 = 'ABC';
      RS_2 = 'XYZ';
    
    
    {Replacement for System.LoadResString}
    function NewLoadResString(ResStringRec: PResStringRec): String;
    var
      Buffer: array [0..4095] of char;
    begin
      if ResStringRec = nil then Exit;
      if ResStringRec.Identifier >= 64 * 1024 then
      begin
        Result := PChar(ResStringRec.Identifier);
      end
      else
      begin
        if RecStrNameIdMap.IndexOfName(IntToStr(ResStringRec^.Identifier)) > -1 then
        begin
          Result := NewStringValueList.Values[
            RecStrNameIdMap.Values[IntToStr(ResStringRec^.Identifier)]];
        end
        else
        begin
          SetString(Result, Buffer,
            LoadString(FindResourceHInstance(ResStringRec.Module^),
              ResStringRec.Identifier, Buffer, SizeOf(Buffer)));
        end;
      end;
    end;
    
    procedure TForm2.Button1Click(Sender: TObject);
    var
      aMethodHook: TMethodHook;
    
      {Hookup aNewProcedure on aOriginalProcedure}
      procedure RegisterProcedures(aOriginalProcedure, aNewProcedure: pointer);
      begin
        if Assigned(aOriginalProcedure) and Assigned(aNewProcedure) then
          aMethodHook := TMethodHook.Create( aOriginalProcedure, aNewProcedure);
      end;
    
      procedure CreateNameIdMapping;
      begin
        {This is done to get string name from ID}
        RecStrNameIdMap.CaseSensitive := False;
        RecStrNameIdMap.Add(Inttostr(PResStringRec(RS_2)^.Identifier)+'='+'XYZ');
      end;
    
    begin
      aMethodHook := nil;
      RecStrNameIdMap := THashedStringList.Create;
      NewStringValueList := TStringList.Create;
      try
        CreateNameIdMapping;
    
        {Create new value list for ResourceStrings}
        NewStringValueList.Add('XYZ'+'='+'new value for ResourceString RS_2');
        RegisterProcedures(@System.LoadResString, @NewLoadResString);
    
        {This should return 'new value for ResourceString RS_2' instead of 'XYZ'}
        ShowMessage(RS_2);
    
        {This should return 'ABC' - no change in value}
        ShowMessage(RS_1);
      finally
        aMethodHook.Free;
        RecStrNameIdMap.Free;
        NewStringValueList.Free;
      end;
    end;
    
    { TMethodHook }
    
    constructor TMethodHook.Create(pOldProc, pNewProc: pointer);
    var
      iOffset : integer;
      iMemProtect : cardinal;
      i : integer;
    begin
      Self.pOldProc := pOldProc;
      Self.pNewProc := pNewProc;
    
      pPosition := pOldProc;
      iOffset := integer( pNewProc ) - integer( pointer( pPosition ) ) - 5;
    
      for i := 0 to 4 do aOriginal[ i ] := pPosition^[ i ];
    
      VirtualProtect( pointer( pPosition ), 5, PAGE_EXECUTE_READWRITE,
        @iMemProtect );
    
      pPosition^[ 0 ] := $E9;
      pPosition^[ 1 ] := byte( iOffset );
      pPosition^[ 2 ] := byte( iOffset shr 8 );
      pPosition^[ 3 ] := byte( iOffset shr 16 );
      pPosition^[ 4 ] := byte( iOffset shr 24 );
    end;
    
    destructor TMethodHook.Destroy;
    var
      i : integer;
    begin
      for i := 0 to 4 do pPosition^[ i ] := aOriginal[ i ];
      inherited;
    end;
    
    end.
    

    【讨论】:

    • 我尝试了上面建议的代码,现在它没有在 system.LoadResString 上连接新程序。
    • @user4916191 抱歉...在 XE4 上对我来说很好用。也许您对上述问题中的代码进行了其他更改?
    • @user4916191 你是对的:我误读了你的代码{This should return 'new value for ResourceString RS_2' instead of 'XYZ'} 中的一条评论,所以我认为错误的输出XYZ 是正确的。我删除了答案,因为它是错误的
    • @user4916191 stackoverflow 已解决;请检查我的编辑
    • @ fantaghirocco - 感谢您的评论,找到了正确的方向。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-03
    • 2021-04-17
    • 2013-08-11
    • 2014-11-28
    • 1970-01-01
    • 2020-05-18
    相关资源
    最近更新 更多