异常处理 (4)
发布时间:2010-04-11 |
1楼: 关于异常处理使用的SEH链实现异常嵌套.
|------------|
线程信息块 |-------------| | ... |
|-------------| | 异常处理1 |--->| pop fs:[0] |
|----->|ExceptionList|---->| |-------------| | pop eax |
| |-------------| | |--->| FFFFFFFF | | ret |
| | ... | | | |-------------| |------------|
| |-------------| | | | ... |
| | | | | |-------------|
| |-------------| | | | | |------------|
|| pop fs:[0] |
|-------------| | | | |-------------| | pop eax |
| ... | | | ||-------------| |------------|
|------>| 段描述符 |->| | | | ... |
| |-------------| | | |-------------|
| | | | | | | |------------|
| |-------------| | | |-------------| | ... |
| (LDT) | | | 异常处理 |--->| pop fs:[0] |
| | | |-------------| | pop eax |
| | |----|-------------| |------------|
| | | | ... |
| | | |-------------|
|-----------| | | | | |------------|
| fs 寄存器| | | |-------------| | ... |
|-----------| | | | 异常处理 |--->| pop fs:[0] |
| | |-------------| | pop eax |
| ||-------------| |------------|
| |
|-------------|
字串6 2楼: 大侠给解释一下啦! 字串8 3楼: 最简单的例子, 来自 http://www.microsoft.com/msj/0197/exception/exception.aspx
//==================================================
// MYSEH - Matt Pietrek 1997
// Microsoft Systems Journal, January 1997
// File: MySEH.dpr
// To compile: Delphi 7.0
//==================================================
program MySEH;
{$AppType Console}
uses
Windows, SysUtils;
type
TExceptionDisposition = (
ExceptionContinueExecution = 0,
ExceptionContinueSearch = 1,
ExceptionNestedException = 2,
ExceptionCollidedUnwind = 3 );
var
scratch: DWORD;
// 异常回调
function FilterFunc(ExceptionRecord: PExceptionRecord; EstablisherFrame: Pointer;
ContextRecord: PContext; DispatcherContext: Pointer): TExceptionDisposition; Cdecl;
begin
// 看到这句说明进入了异常回调
Writeln(‘Hello from an exception handler‘);
// 修改线程的EAX为一个合法地址
ContextRecord.Eax := DWORD(@scratch);
// 要求重新执行导致异常的指令
Result := ExceptionContinueExecution;
end;
// 程序入口
var
Handler: DWORD;
begin
Handler := DWORD(@FilterFunc);
asm
PUSH Handler // 异常回调地址
PUSH FS:[0] // 上一节点地址
MOV FS:[0], ESP // 插入链表表头
end;
asm
MOV EAX , 0 // 清空EAX寄存器
MOV [EAX], 1 // 访问[EAX]处,这将引发一个异常
end;
Writeln(‘After writing!‘); // 写入成功
asm
MOV EAX, [ESP] // 上一节点地址
MOV FS:[0], EAX // 设为链表表头
ADD ESP, 8 // 恢复堆栈指针
end;
Readln;
end.
字串1 4楼: 下面是《Windows 核心编程》中的一个例子,利用异常机制恢复线程堆栈,我按照SEH的原理用PASCAL写了一遍,测试通过~~
// 1.工程文件 Summation.dpr :
program Summation;
{$R ‘Summation.res‘ ‘Summation.rc‘}
uses
Windows, Messages;
const
IDD_SUMMATION = 101; // 资源ID
IDI_SUMMATION = 102;
IDC_SUMNUM = 1000; // 控件ID
IDC_CALC = 1001;
IDC_ANSWER = 1002;
UINT_MAX = DWORD(-1); // 非法标识
type
// 异常回调返回值
TExceptionDisposition = (
ExceptionContinueExecution = 0, // 继续执行遇到异常的线程(回调已经作了修复工作)
ExceptionContinueSearch = 1, // 回调未作处理, 请在链表上继续寻找其他回调
ExceptionNestedException = 2,
ExceptionCollidedUnwind = 3
);
// 标准异常结构体
PExceptionRegistration = ^TExceptionRegistration;
TExceptionRegistration = record
PrevStruct: PExceptionRegistration; // 上一节点位置
ExceptionHandler: Pointer; // 异常回调地址
end;
// 扩展异常结构体
PExcFrame = ^TExcFrame;
TExcFrame = record
PStruct: PExcFrame; // 上一节点位置
Handler: Pointer; // 异常回调地址
SafeEip: Pointer; // 安全指令地址
end;
// 递归求和
function Sum(uNum: UINT): UINT;
begin
if (uNum = 0) then Result := 0 else Result := uNum + Sum(uNum - 1);
end;
// 异常回调
function ExceptHandler(var ExceptionRecord: TExceptionRecord; var EstablisherFrame: TExcFrame;
var ContextRecord: TContext; DispatcherContext: Pointer): TExceptionDisposition; Cdecl;
begin
if (ExceptionRecord.ExceptionCode = STATUS_STACK_OVERFLOW) then
begin
ContextRecord.Eax := UINT_MAX; // 线程函数返回‘错误标识‘
ContextRecord.Eip := DWORD(EstablisherFrame.SafeEip); // 跳到‘Call Sum‘指令之后执行
ContextRecord.Esp := DWORD(@EstablisherFrame); // 恢复栈顶为执行‘Call Sum‘前的位置
Result := ExceptionContinueExecution;
end else
Result := ExceptionContinueSearch;
end;
// 线程函数
function SumThreadFunc(uSumNum: UINT): DWORD;
asm
// 在堆栈中构建异常结构
PUSH OFFSET @@SafeEip // TExcFrame.SafeEip
PUSH OFFSET ExceptHandler // TExcFrame.Handler
PUSH FS:[0] // TExcFrame.PStruct
// 将该结构插入链表首部
MOV FS:[0], ESP // FS:[0]乃TIB.ExceptionList
// 参数/返回值均在EAX中
CALL Sum
// 遇到异常时的跳转位置
@@SafeEip:
// 从链表摘除我们的结构
MOV EDX , [ESP] // 上个节点地址
MOV FS:[0], EDX // 设其为首节点
// 清除异常结构占用堆栈
ADD ESP , 12 // 修改栈顶指针
end;
// 对话框WM_INITDIALOG消息处理
function Dlg_OnInitDialog(hWnd, hWndFocus: HWND; lParam: LPARAM): BOOL;
begin
// 设置窗口图标
SendMessage(hWnd, WM_SETICON, ICON_BIG, LoadIcon(HInstance, MakeIntResource(IDI_SUMMATION)));
SendMessage(hWnd, WM_SETICON, ICON_SMALL, LoadIcon(HInstance, MakeIntResource(IDI_SUMMATION)));
// 限制输入长度
SendMessage(GetDlgItem(hWnd, IDC_SUMNUM), EM_LIMITTEXT, 9, 0);
// 接受默认焦点
Result := TRUE;
end;
// 对话框WM_COMMAND消息处理
procedure Dlg_OnCommand(hWnd: HWND; id: Integer; hWndCtl: HWND; codeNotify: UINT);
var
dwThreadId: DWORD;
uSum: UINT;
hThread: THandle;
begin
case (id) of
IDCANCEL:
begin
EndDialog(hWnd, id);
end;
IDC_CALC:
begin
// 取得输入数值
uSum := GetDlgItemInt(hWnd, IDC_SUMNUM, PBOOL(nil)^, FALSE);
// 建立计算线程
hThread := BeginThread(nil, 0, @SumThreadFunc, Pointer(uSum), 0, dwThreadId);
// 等待线程结束
WaitForSingleObject(hThread, INFINITE);
// 线程退出代码 (等于线程函数返回值)
GetExitCodeThread(hThread, uSum);
// 关闭内核对象
CloseHandle(hThread);
// 堆栈是否溢出
if (uSum = UINT_MAX) then
begin
SetDlgItemText(hWnd, IDC_ANSWER, ‘Error‘);
MessageBox(0, ‘The number is too big, please enter a smaller number‘, ‘Summation‘, MB_OK);
end else
SetDlgItemInt(hWnd, IDC_ANSWER, uSum, FALSE);
end;
end;
end;
// 对话框回调
function Dlg_Proc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL; stdcall;
begin
case (uMsg) of
WM_INITDIALOG:
begin
Result := BOOL(SetWindowLong(hWnd,
DWL_MSGRESULT, Longint(Dlg_OnInitDialog(hWnd, wParam, lParam))));
end;
WM_COMMAND:
begin
Dlg_OnCommand(hWnd, LOWORD(wParam), lParam, HIWORD(wParam));
Result := TRUE;
end;
else
Result := FALSE; // 未曾处理
end;
end;
// 主线程入口
begin
DialogBox(HInstance, MakeIntResource(IDD_SUMMATION), 0, @Dlg_Proc);
end.
// 2.资源脚本 Summation.rc :
// 定义
#define IDD_SUMMATION 101
#define IDI_SUMMATION 102
#define IDC_SUMNUM 1000
#define IDC_CALC 1001
#define IDC_ANSWER 1002
#define IDC_STATIC -1
// 语言
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
// 图标
IDI_SUMMATION ICON DISCARDABLE “Summation.ico“
// 模板
IDD_SUMMATION DIALOG DISCARDABLE 18, 18, 162, 41
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU
CAPTION “Summation“
FONT 8, “MS Sans Serif“
BEGIN
LTEXT “Calculate the sum of the numbers from 0 through &x, where x is: “, IDC_STATIC, 4, 4, 112, 20
EDITTEXT IDC_SUMNUM, 120, 8, 40, 13, ES_AUTOHSCROLL | ES_NUMBER
DEFPUSHBUTTON “&Calculate“, IDC_CALC, 4, 28, 56, 12
LTEXT “Answer:“, IDC_STATIC, 68, 30, 30, 8
LTEXT “?“, IDC_ANSWER, 104, 30, 56, 8
END
// 3.图标文件 Summation.ico , 自备. [:D][:D] 字串4 5楼: 再来贴一个,仍然是《Windows核心编程》上的,一个“结束处理”的简单例子,并且在Finally部分判断之前代码是否出现异常,
不过呢,他是利用关键字让C编译器自动加入SEH代码的,而我这个则是手工编码,我想,可能这更有助于我们了解事情的真相吧。
主要的参考文章就是《A Crash Course on the Depths of Win32™ Structured Exception Handling》,另外还有aimingoo大师
的《Delphi源代码分析》中相关章节,给我不少启发,下面的代码可能有不当之处,欢迎指正,非常感谢。[:D][:D]
program SEHTerm;
{$R ‘SEHTerm.res‘ ‘SEHTerm.rc‘}
uses Windows;
type
TExceptionDisposition = DWORD; // 异常回调返回值类型(C语言中的枚举型占4字节, 而PASCAL只占1字节)
PExceptionFrame = ^TExceptionFrame; // 扩展的异常帧结构(增加了FinallyAddress字段来实现Finally)
TExceptionFrame = record
PrevStruct: PExceptionFrame; // 上一节点位置(线程异常帧链表)
ExceptionHandler: Pointer; // 异常回调地址(由操作系统调用)
FinallyAddress: procedure; // 结束执行地址(由我们自己调用)
end;
// 异常回调
function ExceptHandler(var ExceptionRecord: TExceptionRecord; var EstablisherFrame: TExceptionFrame;
var ContextRecord: TContext; DispatcherContext: Pointer): TExceptionDisposition; Cdecl;
const
ExceptionContinueSearch = $00000001;
StatusUnwind = $C0000027;
ExceptionUnwinding = $00000002;
ExceptionUnwindingForExit = $00000004;
begin
if (ExceptionRecord.ExceptionCode = StatusUnwind) or
(ExceptionRecord.ExceptionFlags = ExceptionUnwinding) or
(ExceptionRecord.ExceptionFlags = ExceptionUnwindingForExit) then
begin
EstablisherFrame.FinallyAddress();
end;
Result := ExceptionContinueSearch;
end;
// 程序入口
const
lpText1: PChar = ‘Perform invalid memory access?‘;
lpCaption1: PChar = ‘SEHTerm: In try block‘;
lpText2: PChar = ‘Abnormal termination‘;
lpText3: PChar = ‘Normal termination‘;
lpCaption2: PChar = ‘SEHTerm: In finally block‘;
lpText4: PChar = ‘Normal process termination‘;
lpCaption4: PChar = ‘SEHTerm: After finally block‘;
asm
// 构造TExceptionFrame并将其插入链表
PUSH OFFSET @@Finally // TExceptionFrame.FinallyAddress
PUSH OFFSET ExceptHandler // TExceptionFrame.ExceptionHandler
PUSH FS:[0] // TExceptionFrame.PrevStruct
MOV FS:[0], ESP // FS:[0]乃TIB.ExceptionList(链表头)
// MessageBox(0, ‘Perform invalid memory access?‘, ‘SEHTerm: In try block‘, MB_YESNO);
PUSH $00000004 // MB_YESNO
PUSH lpCaption1 // ‘SEHTerm: In try block‘
PUSH lpText1 // ‘Perform invalid memory access?‘
PUSH $00000000
CALL MessageBox
// if (n = IDYES) then PByte(0)^ := 5;
CMP EAX, $00000006 // (n = ID_YES) ???
JNZ @@NoAccess // 返回值不是IDYES
MOV [0], $00000005 // 故意访问非法地址
@@NoAccess:
// 如果能执行到这里说明没有出错, ^.^
POP FS:[0] // 将我们节点从异常帧链表摘除
ADD ESP, $00000008 // 修改栈顶指针(出栈简化动作)
PUSH OFFSET @@Other // 准备后面RET指令的跳转地址
@@Finally:
// 1.如果没有出错, 则会顺序执行到这里, :-)
// 2.如果出错, 也会从ExceptHandler()转过来
// if AbnormalTermination() then .. ;
CMP [ESP], OFFSET @@Other // 根据栈顶内容是否等于@@Other, 判断之前是否出错
JNZ @@Abnormal // (从而使得MessageBox()显示不同内容)
// MessageBox(0, ‘Normal termination‘, ‘SEHTerm: In finally block‘, MB_OK)
PUSH $00000000 // MB_OK
PUSH lpCaption2 // ‘SEHTerm: In finally block‘
PUSH lpText3 // ‘Normal termination‘
PUSH $00000000
CALL MessageBox
JMP @@Return
@@Abnormal:
// MessageBox(0, ‘Abnormal termination‘, ‘SEHTerm: In finally block‘, MB_OK)
PUSH $00000000 // MB_OK
PUSH lpCaption2 // ‘SEHTerm: In finally block‘
PUSH lpText2 // ‘Abnormal termination‘
PUSH $00000000
CALL MessageBox
@@Return:
RET // 没发生异常则顺序执行, 否则返回ExceptHandler()
@@Other:
// MessageBox(0, ‘Normal process termination‘, ‘SEHTerm: After finally block‘, MB_OK);
PUSH $00000000 // MB_OK
PUSH lpCaption4 // ‘SEHTerm: After finally block‘
PUSH lpText4 // ‘Normal process termination‘
PUSH $00000000
CALL MessageBox
end. 字串4 6楼: 麻子利害
字串9 7楼: 惭愧惭愧
字串6 8楼: 最后一个, 仍然是《Windows核心编程》上的, 利用异常保护, 实现稀疏提交的数组.
// ------------------ 1. VMArray.pas ------------------
unit VMArray;
interface
uses Windows;
// TopLevelExceptionFilter返回值含义
const
EXCEPTION_EXECUTE_HANDLER = 1; // 已处理异常, 请结束程序, (不要显示出错对话框)
EXCEPTION_CONTINUE_SEARCH = 0; // 未处理异常, 作默认处理, (通常显示出错对话框)
EXCEPTION_CONTINUE_EXECUTION = -1; // 已修复问题, 线程按ContextRecord继续执行
// 扩展了的异常帧(系统只使用前两个域)
type
PExceptionRegistration = ^TExceptionRegistration;
TExceptionRegistration = record
PrevStruct: PExceptionRegistration; // 上一节点位置
ExceptionHandler: Pointer; // 异常回调地址
ExceptionAddress: Pointer; // except块地址
end;
// (虚拟内存+异常保护实现的)稀疏数组
type
TVMArray = class(TObject)
private
m_pNext: TVMArray; // 下一个对象
m_pArray: Pointer; // 数组首地址
m_cbReserve: DWORD; // 数组尺寸(in bytes)
m_dwTypeSize: DWORD; // 组员尺寸(in bytes)
protected
function OnAccessViolation(pvAddrTouched: Pointer; fAttemptedRead: BOOL; // 负责修复内存访问错误,可覆盖
var pep: TExceptionPointers; fRetryUntilSuccessful: BOOL): LongInt; virtual;
public
constructor Create(dwTypeSize, dwReserveElements: DWORD);
destructor Destroy(); override;
class procedure RemoveCurrentThreadOtherSEH(); // 用作摘除SetExceptionHandler()增加的SEH回调
function VMPointer(): Pointer; // 返回数组首地址
function ExceptionFilter(var pep: TExceptionPointers; // 可在except块中作为“过滤器“函数调用,
fRetryUntilSuccessful: BOOL = FALSE): LongInt; // 若没有except块,将由顶层异常回调调用
end;
implementation
type
PfnTopLevelExceptionFilter = function (var pep: TExceptionPointers): LongInt; stdcall;
var
sm_pHead: TVMArray = nil; // 第一个TVMArray对象地址
sm_pfnUnhandledExceptionFilterPrev: PfnTopLevelExceptionFilter; // 以前的顶层异常回调
// 顶层异常回调
function TopLevelExceptionFilter(var pep: TExceptionPointers): LongInt; stdcall;
var
p: TVMArray;
begin
Result := EXCEPTION_CONTINUE_SEARCH;
// 我们只修复内存访问错误
if (pep.ExceptionRecord.ExceptionCode = EXCEPTION_ACCESS_VIOLATION) then
begin
// 遍历TVMArray对象链表, 一一调用
p := sm_pHead;
while (p <> nil) do
begin
// 询问当前节点对象是否修复错误
Result := p.ExceptionFilter(pep, TRUE);
// 这个对象修复了问题, 停止循环
if (Result <> EXCEPTION_CONTINUE_SEARCH) then Break;
// 单链表上的下一个TVMArray对象
p := p.m_pNext;
// 注: 若均不处理则程序就会退出
end;
end;
// 调用以前的顶层异常回调
if (Result = EXCEPTION_CONTINUE_SEARCH) then
Result := sm_pfnUnhandledExceptionFilterPrev(pep);
end;
// 负责修复内存访问错误(被ExceptionFilter调用)
function TVMArray.OnAccessViolation(pvAddrTouched: Pointer; fAttemptedRead: BOOL;
var pep: TExceptionPointers; fRetryUntilSuccessful: BOOL): LongInt;
var
fCommittedStorage: BOOL;
begin
repeat
// 为出错地址提交物理内存
fCommittedStorage := (VirtualAlloc(pvAddrTouched, m_dwTypeSize, MEM_COMMIT, PAGE_READWRITE) <> nil);
// 分配内存失败, 通知用户
if (not fCommittedStorage) and (fRetryUntilSuccessful) then
begin
MessageBox(0, ‘Please close some other applications and Press OK.‘,
‘Insufficient Memory Available‘, MB_ICONWARNING or MB_OK);
end;
until (fCommittedStorage) or (not fRetryUntilSuccessful);
// 1.分配成功, 从出错指令处继续执行
// 2.分配失败, 让系统安静地关闭程序(or 跳至except块执行)
if fCommittedStorage then
Result := EXCEPTION_CONTINUE_EXECUTION
else
Result := EXCEPTION_EXECUTE_HANDLER;
end;
// TVMArray对象初始化
constructor TVMArray.Create(dwTypeSize, dwReserveElements: DWORD);
begin
// 第一个类建立时安装顶层异常回调
if (sm_pHead = nil) then
sm_pfnUnhandledExceptionFilterPrev := SetUnhandledExceptionFilter(@TopLevelExceptionFilter);
// 将自己接到TVMArray对象链表表头
m_pNext := sm_pHead;
sm_pHead := Self;
// 需要为数组保留的虚拟内存的长度
m_dwTypeSize := dwTypeSize;
m_cbReserve := m_dwTypeSize * dwReserveElements;
// 为数组保留虚拟内存空间(不提交)
m_pArray := VirtualAlloc(nil, m_cbReserve, MEM_RESERVE or MEM_TOP_DOWN, PAGE_READWRITE);
// chASSERT(m_pArray <> NULL);
end;
// TVMArray对象结束化
destructor TVMArray.Destroy();
var
p: TVMArray;
begin
// 释放数组对应的虚拟内存空间
if (m_pArray <> nil) then VirtualFree(m_pArray, 0, MEM_RELEASE);
// 从TVMArray对象链表摘除自己
if (sm_pHead = Self) then
sm_pHead := sm_pHead.m_pNext
else begin
p := sm_pHead;
while (p.m_pNext <> nil) do
begin
if (p.m_pNext = Self) then
begin
p.m_pNext := p.m_pNext.m_pNext;
Break;
end;
p := p.m_pNext;
end;
end; // END: else
end;
// 返回数组首地址
function TVMArray.VMPointer(): Pointer;
begin
Result := m_pArray;
end;
// 摘除当前线程所有SEH节点, 慎用!
class procedure TVMArray.RemoveCurrentThreadOtherSEH();
asm
// p := TEB.ExceptionList;
MOV EDX, FS:[0]
// if (p = $FFFFFFFF) then Exit;
CMP EDX, -1
JZ @@QUIT
// while (p.PrevStruct <> $FFFFFFFF) do p := p.PrevStruct;
@@LOOP:
MOV EAX, EDX
MOV EDX, [EAX].TExceptionRegistration.PrevStruct
CMP EDX, -1
JNZ @@LOOP
// TEB.ExceptionList := p;
MOV FS:[0], EAX
@@QUIT:
end;
// 异常过滤函数(被TopLevelExceptionFilter或except块调用)
function TVMArray.ExceptionFilter(var pep: TExceptionPointers; fRetryUntilSuccessful: BOOL = FALSE): LongInt;
var
pvAddrTouched: DWORD;
fAttemptedRead: BOOL;
begin
Result := EXCEPTION_CONTINUE_SEARCH;
// 我们只修复内存访问错误
if (pep.ExceptionRecord.ExceptionCode <> EXCEPTION_ACCESS_VIOLATION) then Exit;
// 发生读写异常的内存地址
pvAddrTouched := pep.ExceptionRecord.ExceptionInformation[1];
// 导致异常的动作(读or写)
fAttemptedRead := (pep.ExceptionRecord.ExceptionInformation[0] = 0);
// 如果该地址在数组范围内
if (DWORD(m_pArray) <= pvAddrTouched) and (DWORD(pvAddrTouched) < (DWORD(m_pArray) + m_cbReserve)) then
Result := OnAccessViolation(Pointer(pvAddrTouched), fAttemptedRead, pep, fRetryUntilSuccessful);
end;
end.
// ------------------- 2. Spreadsheet.dpr -------------------
program Spreadsheet;
{$R ‘Spreadsheet.res‘ ‘Spreadsheet.rc‘}
uses Windows, Messages, VMArray in ‘VMArray.pas‘;
const
IDD_SPREADSHEET = 1;
IDI_SPREADSHEET = 102;
IDC_LOG = 101;
IDC_ROW = 1001;
IDC_COLUMN = 1002;
IDC_VALUE = 1003;
IDC_READCELL = 1004;
IDC_WRITECELL = 1005;
g_nNumRows = 256;
g_nNumCols = 1024;
// 界面窗口句柄
var g_hWnd: HWND;
// 电子表格单元
type
PCell = ^TCell;
TCell = packed record
dwValue: DWORD;
bDummy: array[1..1020] of Byte;
end;
// 电子表格数组
type
PSpreadSheet = ^TSpreadSheet;
TSpreadSheet = array[0..g_nNumRows-1] of array[0..g_nNumCols-1] of TCell;
// 电子表格类
type
TVMSpreadsheet = class(TVMArray)
public
constructor Create();
protected
function OnAccessViolation(pvAddrTouched: Pointer; fAttemptedRead: BOOL; // 新的修复动作
var pep: TExceptionPointers; fRetryUntilSuccessful: BOOL): LongInt; override;
end;
constructor TVMSpreadsheet.Create();
begin
inherited Create(SizeOf(TCell), g_nNumRows * g_nNumCols);
end;
function TVMSpreadsheet.OnAccessViolation(pvAddrTouched: Pointer; fAttemptedRead: BOOL;
var pep: TExceptionPointers; fRetryUntilSuccessful: BOOL): LongInt;
begin
if fAttemptedRead then
begin
SetDlgItemText(g_hWnd, IDC_LOG, ‘Violation: Attempting to Read‘);
Result := EXCEPTION_EXECUTE_HANDLER;
end else
begin
SetDlgItemText(g_hWnd, IDC_LOG, ‘Violation: Attempting to Write‘);
Result := inherited OnAccessViolation(pvAddrTouched, fAttemptedRead, pep, fRetryUntilSuccessful);
end;
end;
var
g_ssObject: TVMSpreadsheet; // 电子表格对象
g_ss: PSpreadSheet = nil; // 表格数组首地址
// WM_INITDIALOG
function Dlg_OnInitDialog(hWnd, hWndFocus: HWND; lParam: LPARAM): BOOL;
begin
SendMessage(hWnd, WM_SETICON, ICON_BIG, LoadIcon(HInstance, MakeIntResource(IDI_SPREADSHEET)));
SendMessage(hWnd, WM_SETICON, ICON_SMALL, LoadIcon(HInstance, MakeIntResource(IDI_SPREADSHEET)));
g_hWnd := hWnd;
SendMessage(GetDlgItem(hWnd, IDC_ROW), EM_LIMITTEXT, 3, 0);
SendMessage(GetDlgItem(hWnd, IDC_COLUMN), EM_LIMITTEXT, 4, 0);
SendMessage(GetDlgItem(hWnd, IDC_VALUE), EM_LIMITTEXT, 7, 0);
SetDlgItemInt(hWnd, IDC_ROW, 100, FALSE);
SetDlgItemInt(hWnd, IDC_COLUMN, 100, FALSE);
SetDlgItemInt(hWnd, IDC_VALUE, 12345, FALSE);
Result := TRUE;
end;
// 数值范围判断
function chInRange(const AMin, AValue, AMax: Integer): Boolean;
begin
Result := (AValue >= AMin) and (AValue <= AMax);
end;
// 线程异常回调
function ThreadExceptHandler(var ExceptionRecord: TExceptionRecord; var EstablisherFrame: TExceptionRegistration;
var ContextRecord: TContext; DispatcherContext: Pointer): DWORD; Cdecl;
const
ExceptionContinueExecution = 0;
ExceptionContinueSearch = 1;
var
ExceptionPointers: TExceptionPointers;
FilterResult: LongInt;
begin
ExceptionPointers.ExceptionRecord := @ExceptionRecord;
ExceptionPointers.ContextRecord := @ContextRecord;
FilterResult := g_ssObject.ExceptionFilter(ExceptionPointers, FALSE);
case FilterResult of
EXCEPTION_EXECUTE_HANDLER: // 跳至except块后执行
begin
ContextRecord.Eip := DWORD(EstablisherFrame.ExceptionAddress);
ContextRecord.Esp := DWORD(@EstablisherFrame);
Result := ExceptionContinueExecution;
end;
EXCEPTION_CONTINUE_EXECUTION: // 重新执行出错指令
begin
Result := ExceptionContinueExecution;
end;
EXCEPTION_CONTINUE_SEARCH: // 未作处理
begin
Result := ExceptionContinueSearch;
end;
else Result := ExceptionContinueSearch;
end;
end;
// WM_COMMAND
procedure Dlg_OnCommand(hWnd: HWND; id: Integer; hWndCtl: HWND; codeNotify: UINT);
label
On_Except, No_Except;
var
nRow, nCol: Integer;
begin
case (id) of
IDCANCEL: // 要求关闭
begin
EndDialog(hWnd, id);
end;
IDC_ROW: // 行号(改变通知)
begin
nRow := GetDlgItemInt(hWnd, IDC_ROW, PBOOL(nil)^, FALSE);
EnableWindow(GetDlgItem(hWnd, IDC_READCELL), chInRange(0, nRow, g_nNumRows - 1));
EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL), chInRange(0, nRow, g_nNumRows - 1));
end;
IDC_COLUMN: // 列号(改变通知)
begin
nCol := GetDlgItemInt(hWnd, IDC_COLUMN, PBOOL(nil)^, FALSE);
EnableWindow(GetDlgItem(hWnd, IDC_READCELL), chInRange(0, nCol, g_nNumCols - 1));
EnableWindow(GetDlgItem(hWnd, IDC_WRITECELL), chInRange(0, nCol, g_nNumCols - 1));
end;
IDC_READCELL: // 读数组
begin
SetDlgItemText(g_hWnd, IDC_LOG, ‘No violation raised‘);
nRow := GetDlgItemInt(hWnd, IDC_ROW, PBOOL(nil)^, FALSE);
nCol := GetDlgItemInt(hWnd, IDC_COLUMN, PBOOL(nil)^, FALSE);
// 异常保护
asm
PUSH OFFSET On_Except // TExceptionRegistration.ExceptionAddress := On_Except;
PUSH OFFSET ThreadExceptHandler // TExceptionRegistration.ExceptionHandler := @ThreadExceptHandler;
PUSH FS:[0] // TExceptionRegistration.PrevStruct := TEB.ExceptionList;
MOV FS:[0], ESP // TEB.ExceptionList := @TExceptionRegistration;
end;
// 可能出错
SetDlgItemInt(hWnd, IDC_VALUE, g_ss[nRow][nCol].dwValue, FALSE);
// 没有出错
asm
JMP No_Except
end;
// 异常处理
On_Except:
SetDlgItemText(hWnd, IDC_VALUE, ‘‘); // 清空Edit, 以示此处还未分配物理内存
// 收尾工作
No_Except:
asm
POP FS:[0] // TEB.ExceptionList := TExceptionRegistration.PrevStruct;
ADD ESP, TYPE Pointer * 2 // 恢复栈顶(与前面的PUSH对应)
end;
end;
IDC_WRITECELL: // 写数组
begin
SetDlgItemText(g_hWnd, IDC_LOG, ‘No violation raised‘);
nRow := GetDlgItemInt(hWnd, IDC_ROW, PBOOL(nil)^, FALSE);
nCol := GetDlgItemInt(hWnd, IDC_COLUMN, PBOOL(nil)^, FALSE);
// 若该地址所处页还未分配物理内存, 执行写入指令将导致异常,
// 我们的顶层异常处理回调将: 1.提交 2.从出错指令处重新执行
g_ss[nRow][nCol].dwValue := GetDlgItemInt(hWnd, IDC_VALUE, PBOOL(nil)^, FALSE);
end;
end;
end;
// 对话框回调
function Dlg_Proc(hWnd: HWND; uMsg: UINT; wParam: WPARAM; lParam: LPARAM): BOOL; stdcall;
begin
case (uMsg) of
WM_INITDIALOG:
begin
Result :=
SetWindowLong(hWnd, DWL_MSGRESULT, Longint(Dlg_OnInitDialog(hWnd, wParam, lParam))) <> 0;
end;
WM_COMMAND:
begin
Dlg_OnCommand(hWnd, LOWORD(wParam), lParam, HIWORD(wParam));
Result := TRUE;
end;
else
Result := FALSE;
end;
end;
// 程序入口
begin
g_ssObject := TVMSpreadsheet.Create();
g_ss := g_ssObject.VMPointer;
if (g_ss = nil) then
MessageBox(0, ‘Reserves a range failure.‘, ‘Spreadsheet‘, MB_OK)
else begin
TVMArray.RemoveCurrentThreadOtherSEH();
DialogBox(HInstance, MakeIntResource(IDD_SPREADSHEET), 0, @Dlg_Proc);
end;
g_ssObject.Free;
end.
// 3. ------------------ Spreadsheet.rc -------------------
// Language
LANGUAGE LANG_CHINESE, SUBLANG_CHINESE_SIMPLIFIED
// Define
#define IDD_SPREADSHEET 1
#define IDC_LOG 101
#define IDI_SPREADSHEET 102
#define IDC_ROW 1001
#define IDC_COLUMN 1002
#define IDC_COLUMN2 1003
#define IDC_VALUE 1003
#define IDC_READCELL 1004
#define IDC_WRITECELL 1005
// Dialog
IDD_SPREADSHEET DIALOG DISCARDABLE 18, 18, 164, 165
STYLE DS_CENTER | WS_MINIMIZEBOX | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION “Spreadsheet“
FONT 8, “MS Sans Serif“
BEGIN
LTEXT “Cell size:\nRows:\nColumns:\nTotal size:“, IDC_STATIC, 4, 4, 36, 36
LTEXT “1024 bytes\n256\n1024\n256 MB (268,435,456 bytes)“, IDC_STATIC, 44, 4, 104, 36
LTEXT “R&ow (0-255):“, IDC_STATIC, 4, 56, 42, 8
EDITTEXT IDC_ROW, 60, 52, 40, 14, ES_AUTOHSCROLL | ES_NUMBER
LTEXT “&Column (0-1023):“, IDC_STATIC, 4, 76, 54, 8
EDITTEXT IDC_COLUMN, 60, 72, 40, 14, ES_AUTOHSCROLL | ES_NUMBER
PUSHBUTTON “&Read Cell“, IDC_READCELL, 108, 72, 50, 14
LTEXT “&Value:“, IDC_STATIC, 4, 96, 21, 8
EDITTEXT IDC_VALUE, 60, 92, 40, 14, ES_AUTOHSCROLL | ES_NUMBER
PUSHBUTTON “&Write Cell“, IDC_WRITECELL, 108, 92, 50, 14
LTEXT “Execution lo&g:“, IDC_STATIC, 4, 118, 48, 8
EDITTEXT IDC_LOG, 4, 132, 156, 28, ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY
END 字串3 9楼: ft!麻子厉害,什么时候也能像他那样..哈哈.. 字串4 10楼: 接受答案了. 字串8