VirtualProtect - AV:试图读取或写入受保护的内存
VirtualProtect - AV : Attempted to read or write protected memory
我有 Delphi 6 个 DLL,它在内部使用第三方库来进行自动资源字符串转换。
在库源代码中,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);
现在,当我从另一个 Delphi 6 应用程序中调用此 DLL 的函数时,一切正常。
然而,当我从 C# 应用程序(在 64 位机器上编译为 32 位应用程序)调用 DLL 时,我的 C# 应用程序崩溃了。
在调试 DLL 时,我发现上述第 3 方代码的第一块执行没有任何问题,但第二块在倒数第二行失败。
报错信息如下:
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:
编辑:
我在 Delphi 6 中创建了一个示例 DLL 项目来重现该问题。
来自 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-], EDX
MOV [EBP-], EAX
MOV EDX, [EBP-]
MOV EAX, [EBP-]
MOV ECX, OFFSET Addr(TranslateLoadResString)-
CALL ECX
MOV ESP,EBP
POP EBP
end;
end.
下面终于给出了使用该 DLL 的 C# 代码:
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。
我认为 DEP 对异常负责。问题出在您使用的保护选项上。您需要使用 PAGE_EXECUTE_READWRITE
而不是 PAGE_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
很重要。
我有 Delphi 6 个 DLL,它在内部使用第三方库来进行自动资源字符串转换。
在库源代码中,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);
现在,当我从另一个 Delphi 6 应用程序中调用此 DLL 的函数时,一切正常。
然而,当我从 C# 应用程序(在 64 位机器上编译为 32 位应用程序)调用 DLL 时,我的 C# 应用程序崩溃了。
在调试 DLL 时,我发现上述第 3 方代码的第一块执行没有任何问题,但第二块在倒数第二行失败。
报错信息如下:
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:
编辑:
我在 Delphi 6 中创建了一个示例 DLL 项目来重现该问题。
来自 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-], EDX
MOV [EBP-], EAX
MOV EDX, [EBP-]
MOV EAX, [EBP-]
MOV ECX, OFFSET Addr(TranslateLoadResString)-
CALL ECX
MOV ESP,EBP
POP EBP
end;
end.
下面终于给出了使用该 DLL 的 C# 代码:
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。
我认为 DEP 对异常负责。问题出在您使用的保护选项上。您需要使用 PAGE_EXECUTE_READWRITE
而不是 PAGE_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
很重要。