VirtualProtect - AV: попытка чтения или записи в защищенную память

1

У меня есть DLL Delphi 6, которая внутренне использует стороннюю библиотеку для перевода автоматической строки ресурса.

В исходном коде библиотеки 2 функции RTL (ShortCutToText и LoadResString) заменяются многоязычными аналогами (trShortCutToText и trLoadResString). Код показан ниже:

VirtualProtect(@ShortCutToText, SIZE_C, PAGE_READWRITE, @protectType);
Move((@ShortCutToText)^, shortCutBuffer, SIZE_C);
Move((@trShortCutToText)^, (@ShortCutToText)^, SIZE_C);
VirtualProtect(@ShortCutToText, SIZE_C, protectType, @protectType);
FlushInstructionCache(GetCurrentProcess, @ShortCutToText, SIZE_C);

а также

VirtualProtect(@LoadResString, SIZE_C, PAGE_READWRITE, @protectType);
Move((@LoadResString)^, loadResStringBuffer, SIZE_C);
Move((@trLoadResString)^, (@LoadResString)^, SIZE_C);
VirtualProtect(@LoadResString, SIZE_C, protectType, @protectType); //<--Exception thrown at this line
FlushInstructionCache(GetCurrentProcess, @LoadResString, SIZE_C);

Теперь, когда я вызываю функции этой DLL из другого приложения Delphi 6, все работает нормально.

Однако, когда я вызываю DLL из приложения С# (скомпилированного как 32-битное приложение на 64-битной машине), мое приложение С# просто сбой.

При отладке DLL я обнаружил, что первый блок вышеупомянутого стороннего кода выполняется без каких-либо проблем, однако второй блок выходит из строя во второй последней строке.

Сообщение об ошибке показано ниже:

System.AccessViolationException was unhandled
  Message="Attempted to read or write protected memory. This is often an indication that other memory is corrupt."
  Source="TestWinform"
  StackTrace:
       at TestWinform.TestSwitching()
       at TestWinform.Form1.button1_Click(Object sender, EventArgs e)
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.Run(Form mainForm)
       at TestWinform.Program.Main()
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

РЕДАКТИРОВАТЬ:

Я создал образец DLL-проекта в Delphi 6 для репликации проблемы.

Исходный код из файла DelphiDllTest.dpr приведен ниже:

library DelphiDllTest;
uses
  SysUtils,
  Classes,
  MyUnit in 'MyUnit.pas';

{$R *.res}

procedure TestSwitching; export;
var
  myDict : TMyDictionary;
begin
  myDict := TMyDictionary.Create(nil);
  try        
    myDict.Open;
  finally
    myDict.Close;
    FreeAndNil(myDict);
  end;
end;

exports
TestSwitching;
begin
end.

Источник MyUnit.pas приведен ниже:

    unit MyUnit;

    interface

    uses
      Classes, Windows;

    type

     TMyDictionary = class(TComponent)
      public
        constructor Create(owner: TComponent); override;
        destructor Destroy; override;
        procedure Open; virtual;
        procedure Close; virtual;
     end;

    function TranslateLoadResString(resStringRec: PResStringRec): String;
    function trLoadResString(resStringRec: PResStringRec): String;

    const
      SIZE_C = 32;

    var
      loadResStringBuffer: array[0..SIZE_C] of Byte;

    implementation

    constructor TMyDictionary.Create(owner: TComponent);
    begin
      inherited Create(owner);
    end;

    destructor TMyDictionary.Destroy;
    begin
      inherited Destroy;
    end;

    procedure TMyDictionary.Open;
    var
      protect: Integer;
    begin
      VirtualProtect(@LoadResString, SIZE_C, PAGE_READWRITE, @protect);
      Move((@LoadResString)^, loadResStringBuffer, SIZE_C);
      Move((@trLoadResString)^, (@LoadResString)^, SIZE_C);
      VirtualProtect(@LoadResString, SIZE_C, protect, @protect);
      FlushInstructionCache(GetCurrentProcess, @LoadResString, SIZE_C);
    end;

    procedure TMyDictionary.Close;
    var
      protect: Integer;
    begin
        VirtualProtect(@LoadResString, SIZE_C, PAGE_READWRITE, @protect);
        Move(loadResStringBuffer, (@LoadResString)^, SIZE_C);
        VirtualProtect(@LoadResString, SIZE_C, protect, @protect);
    end;


    function TranslateLoadResString(resStringRec: PResStringRec): String;
    var
      buffer: array[0..1023] of Char;
    begin
      try
        if resStringRec <> nil then
        begin
          if resStringRec.Identifier < 64*1024 then
          begin
            SetString(
              Result,
              buffer,
              LoadString(
                FindResourceHInstance(resStringRec.Module^),
                resStringRec.Identifier,
                buffer,
                SizeOf(buffer)));
          end
          else
            Result := PChar(resStringRec.Identifier);
        end
        else
          Result := '';

      except
        Result := '';
      end;
    end;

    function trLoadResString(resStringRec: PResStringRec): String;
    asm
      PUSH  EBP
      MOV   EBP, ESP
      ADD   ESP, $-8
      MOV   [EBP-$8], EDX
      MOV   [EBP-$4], EAX
      MOV   EDX, [EBP-$8]
      MOV   EAX, [EBP-$4]
      MOV   ECX, OFFSET Addr(TranslateLoadResString)-$1
      CALL  ECX
      MOV   ESP,EBP
      POP   EBP
    end;

    end.

Теперь, наконец, код С#, который использует эту DLL, приведен ниже:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace TestWinform
{
    public partial class Form1 : Form
    {
        [DllImport("DelphiDllTest.dll")]
        public static extern void TestSwitching();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            TestSwitching();            
        }
    }
}

Надеюсь, этого достаточно, чтобы сгенерировать AV.

  • 0
    Вполне вероятно, что виновато ваше взаимодействие. Мы не можем видеть это. MCVE был бы полезен, как всегда.
  • 0
    @DavidHeffernan: То есть вы думаете, что это как-то связано с функцией экспорта из DLL Delphi? Что вы подразумеваете под "MCVE"?
Показать ещё 4 комментария
Теги:
delphi-6

1 ответ

3
Лучший ответ

Я считаю, что DEP несет ответственность за исключения. Проблема заключается в том, что вы используете опцию защиты. Вместо PAGE_READWRITE вам необходимо использовать PAGE_EXECUTE_READWRITE. В Open вам необходимо защитить @trLoadResString а также @LoadResString, вам не хватает этого вызова VirtualProtect.

Возможно, вот так:

VirtualProtect(@LoadResString, SIZE_C, PAGE_EXECUTE_READWRITE, @protect);
Move((@LoadResString)^, loadResStringBuffer, SIZE_C);
VirtualProtect(@LoadResString, SIZE_C, protect, @protect);
FlushInstructionCache(GetCurrentProcess, @LoadResString, SIZE_C);

VirtualProtect(@trLoadResString, SIZE_C, PAGE_EXECUTE_READWRITE, @protect);
Move((@trLoadResString)^, (@LoadResString)^, SIZE_C);
VirtualProtect(@trLoadResString, SIZE_C, protect, @protect);
FlushInstructionCache(GetCurrentProcess, @trLoadResString, SIZE_C);

И вам нужно сделать что-то подобное в методе Close. Возможно, вам нужно восстановить trLoadResString.

Ваша экспортированная функция использует соглашение о вызове register, но она должна экспортироваться как stdcall. Директива export игнорируется. Это только exports что имеет значение.

  • 0
    Я полностью согласен с тобой. Это кажется чем-то связанным с DEP. Приложение работало нормально на WinXP, но не на Win7. Использование флага PAGE_EXECUTE_READWRITE решило проблему. Я также защитил @trLoadResString сейчас.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню