信息提示
您的同一ip请求过于频繁,如果希望继续访问,请输入验证码:
Copyright 1998 – 2010 Tencent. All Rights Reserved.ChinaUnix.net's Archiver
请问[kdb_get_virtual_memory] no real storage
是什么意思
azsxdcfv22
发表于 2010-12-14 10:10
请问[kdb_get_virtual_memory] no real storage
是什么意思
碰到一问题,现场在跑的程序有个线程没起作用,查看发现是这个线程挂死,现在不知道怎么找这个问题,希望有人能帮我一把~~
-bash-3.2# kdb
The ecified kernel file is a 64-bit kernel
Preserving 1414852 bytes of symbol table
First symbol __mulh START END < ame
0000000000001000 0000000003E0F050 start+000FD8
F00000002FF47600 F00000002FFDC940 __ublock+000000
000000002FF22FF4 000000002FF22FF8 environ+000000
000000002FF22FF8 000000002FF22FFC errno+000000
F100070F00000000 F100070F10000000 pvproc+000000
F100070F10000000 F100070F18000000 pvthread+000000
id....................0002
raddr.....0000000002000000 eaddr.....F200800040000000
size..............00080000 align.............00001000
valid..1 ros....0 fixlmb.1 seg....0 wimg...2
(0) dcal 401906
Value decimal: 401906 Value hexa: 000621F2
(0) tpid 000621F2 SLOT NAME STATE TID PRI RQ CPUID CL WCHAN
pvthread+80C800 32968 filter SLEEP 0C81F5 03C 6 0 pvthread+80FD00 33021 filter SLEEP 0FD117 03C 7 0 vthread+80FD40
pvthread+00C000 192 filter SLEEP 0C007B 03C 6 0 F1000110115D0630
pvthread+80E700 32999!filter RUN 0E715F 05E 7 0 ----------挂死的线程
pvthread+00B900 185 filter SLEEP 0B90E1 03C 4 0 F10001101BCEE930
pvthread+80DC00 32988 filter SLEEP 0DC1C3 03C 4 0 pvthread+010000 256 filter SLEEP 1000C3 03C 0 0 vthread+010040
pvthread+805900 32857 filter SLEEP 059105 03C 6 0 F10001101321AA30
pvthread+00D800 216 filter SLEEP 0D80DB 03C 2 0 F10001003EC04FB0
pvthread+805E00 32862 filter SLEEP 05E1CF 03C 6 0 F100011013041AD8
pvthread+005900 89 filter SLEEP 05901D 03C 6 0 F100011013041C58
pvthread+805A00 32858 filter SLEEP 05A1CB 03C 4 0 F100011013041A18
pvthread+809A00 32922 filter SLEEP 09A1A7 03C 6 0 pvthread+811900 33049 filter SLEEP 1191FD 03C 4 0 vthread+811940
pvthread+00DC00 220 filter SLEEP 0DC06D 03C 2 0 vthread+00DC40
pvthread+010A00 266 filter SLEEP 10A00F 03C 0 0 F10001003A677930
(0) f 32968
pvthread+80C800 STACK:
[000551C8]et_wait+0002B0 (00000000D0431F3C, 000000000000D0B2, 00000000D012F230 [??])
[00459AD8]poll_wait+00003C (??, ??)
[00472064]_select+000BD0 (??, ??, ??, ??, ??, ??)
[00003810].svc_i tr+000110 ()
[D0431DBC]__fd_select+000098 (??, ??, ??, ??, ??)
[100153A0]select+00003C (00000004, 2FF20BD4, 00000000, 00000000, 2FF22BE4)
[10015C6C]Select+00003C (00000004, 2FF20BD4, 00000000, 00000000, 2FF22BE4)
[10002230]ListenPorts+000540 (00000000)
[10001CAC]main+000508 (00000001, 2FF22D64)
[100001E8]__start+000098 ()
(0) f 33021
pvthread+80FD00 STACK:
[0005EC1C]ep_block_thread+000520 (00000000D0120F04 [??])
[0005D298]_thread_tsleep+000678 (??, ??, ??, ??, ??)
[00061070]thread_tsleep+000014 (??, ??, ??, ??)
[00003810].svc_i tr+000110 ()
[D0120300]_vp_sleep+000384 (??, ??)
[D011E140]_usched_di atch+000280 (??, ??, ??)
[00000000]00000000 ()
[kdb_get_virtual_memory] no real storage @ 3835383142002234
(0) f 192
pvthread+00C000 STACK:
[000542F8]e_block_thread+000290 ()
[00145504] leep_com+0000BC (??)
[001460A8] leep+00006C (??, ??)
[00003810].svc_i tr+000110 ()
[D0124410]_p_ leep+00000C (??, ??)
[D037D51C] leep+0000A8 (??, ??)
[D02765FC]sleep_4_1+000028 (??)
[10006428]ShExec+000078 (00000000)
[D010D780]_pthread_body+000118 (??)
(0) f 32999 ------------挂死线程的堆栈显示
pvthread+80E700 STACK:
Use current context [F00000003011F780] of cpu 7
[100060FC]ReadFtpFile+000090 (00000000)
[kdb_get_virtual_memory] no real storage @ 307C0D08 ----------不明白是什么意思
[100060F4]ReadFtpFile+000088 (00000000) -------------+000088 怎么理解???
(0) f 185
pvthread+00B900 STACK:
[000542F8]e_block_thread+000290 ()
[00145504] leep_com+0000BC (??)
[001460A8] leep+00006C (??, ??)
[00003810].svc_i tr+000110 ()
[D0124410]_p_ leep+00000C (??, ??)
[D037D51C] leep+0000A8 (??, ??)
[D02765FC]sleep_4_1+000028 (??)
[1002E034]Msleep+00006C (00000BB8)
[100049CC]AlarmMsgSender+000114 (00000000)
[D010D780]_pthread_body+000118 (??)
(0) f 32988
pvthread+80DC00 STACK:
[000551C8]et_wait+0002B0 (000000000000E088, 000000000000D032, 00000000000000FF [??])
[00459AD8]poll_wait+00003C (??, ??)
[00472064]_select+000BD0 (??, ??, ??, ??, ??, ??)
[00003810].svc_i tr+000110 ()
[D0431DBC]__fd_select+000098 (??, ??, ??, ??, ??)
[100153A0]select+00003C (00000001, 294E219C, 00000000, 00000000, 294E419C)
[10015C6C]Select+00003C (00000001, 294E219C, 00000000, 00000000, 294E419C)
[100124AC]WaitSelect+0000CC (00000000, 0000000A, 00000000, 00000008)
[10014A30]RecvFromTcp2+000074 (00000000, 294F4684, 00000001)
[10012F20]RecvIcpData2+00016C (00000000, 294F56E0, 00000001)
[10004114]A IcpRecvMsg+000290 (241D0E54)
[D010D780]_pthread_body+000118 (??)
(0) f 256
pvthread+010000 STACK:
[0005EC1C]ep_block_thread+000520 (00000000D0116AB8 [??])
[0005D298]_thread_tsleep+000678 (??, ??, ??, ??, ??)
[00061070]thread_tsleep+000014 (??, ??, ??, ??)
[00003810].svc_i tr+000110 ()
[D0120300]_vp_sleep+000384 (??, ??)
[D011E140]_usched_di atch+000280 (??, ??, ??)
[00000000]00000000 ()
[kdb_get_virtual_memory] no real storage @ 42002234
(0) f 32857
pvthread+805900 STACK:
[000542F8]e_block_thread+000290 ()
[00145504] leep_com+0000BC (??)
[001460A8] leep+00006C (??, ??)
[00003810].svc_i tr+000110 ()
[D0124410]_p_ leep+00000C (??, ??)
[D037D51C] leep+0000A8 (??, ??)
[D02765FC]sleep_4_1+000028 (??)
[1002E034]Msleep+00006C (00001388)
[100038B0]HandSwitch+000094 (00000005)
[D010D780]_pthread_body+000118 (??)
(0) f 216
pvthread+00D800 STACK:
[000542F8]e_block_thread+000290 ()
[00145504] leep_com+0000BC (??)
[001460A8] leep+00006C (??, ??)
[00003810].svc_i tr+000110 ()
[D0124410]_p_ leep+00000C (??, ??)
[D037D51C] leep+0000A8 (??, ??)
[D02765FC]sleep_4_1+000028 (??)
[1002E034]Msleep+00006C (00001388)
[10003CA0]SysGuardian+000078 (00000000)
[D010D780]_pthread_body+000118 (??)
(0) f 32862
pvthread+805E00 STACK:
[000542F8]e_block_thread+000290 ()
[0044EAF4]rtipc_tsleep_block+000154 (??, ??, ??)
[0044EF4C]rtipc_tsleep_thread+000074 (??, ??, ??, ??, ??, ??, ??)
[004558A0]semsleep+000124 (??, ??, ??, ??, ??)
[00456000]atomic+000654 (??, ??, ??, ??, ??, ??, ??)
[00456B78]rsemop+0004EC (0080003100800031, F00000003008F698, 0000000000000001, 0000000100000001, 0000000000000000, 0000000400000004)
[00452CE0]_sem_wait+0001E0 (??, ??, ??)
[00003810].svc_i tr+000110 ()
[D04E427C]sem_wait+000014 (??)
[100104F8]Sem_wait+00001C (241D0C60)
[10028E54]ReadRecvBuff+00001C (2947C6F0)
[100034D0]MsgProce or+000074 (00000000)
[D010D780]_pthread_body+000118 (??)
(0) f 89
pvthread+005900 STACK:
[000542F8]e_block_thread+000290 ()
[0044EAF4]rtipc_tsleep_block+000154 (??, ??, ??)
[0044EF4C]rtipc_tsleep_thread+000074 (??, ??, ??, ??, ??, ??, ??)
[004558A0]semsleep+000124 (??, ??, ??, ??, ??)
[00456000]atomic+000654 (??, ??, ??, ??, ??, ??, ??)
[00456B78]rsemop+0004EC (0080002F0080002F, F000000030077698, 0000000000000001, 0000000100000001, 0000000000000000, 0000000400000004)
[00452CE0]_sem_wait+0001E0 (??, ??, ??)
[00003810].svc_i tr+000110 ()
[D04E427C]sem_wait+000014 (??)
[100104F8]Sem_wait+00001C (241D0CC0)
[10029290]ReadSendBuff+00001C (2945D6E8)
[10002E38]MsgSender+00004C (00000000)
[D010D780]_pthread_body+000118 (??)
(0) f 32858
pvthread+805A00 STACK:
[000542F8]e_block_thread+000290 ()
[0044EAF4]rtipc_tsleep_block+000154 (??, ??, ??)
[0044EF4C]rtipc_tsleep_thread+000074 (??, ??, ??, ??, ??, ??, ??)
[004558A0]semsleep+000124 (??, ??, ??, ??, ??)
[00456000]atomic+000654 (??, ??, ??, ??, ??, ??, ??)
[00456B78]rsemop+0004EC (0080002C0080002C, F00000003005F698, 0000000000000001, 0000000100000001, 0000000000000000, 0000000400000004)
[00452CE0]_sem_wait+0001E0 (??, ??, ??)
[00003810].svc_i tr+000110 ()
[D04E427C]sem_wait+000014 (??)
[100104F8]Sem_wait+00001C (28681A54)
[1002E8C8]GetRecvBuffer+00001C (291218E8)
[10040168]MMLProce Thread+000038 (00000000)
[D010D780]_pthread_body+000118 (??)
(0) f 32922
pvthread+809A00 STACK:
[0005F3E0]thread_waitact+000228 (00000000D011B7B4 [??])
[00003810].svc_i tr+000110 ()
[D011B7B0]_usched_func+00008C ()
[D010D780]_pthread_body+000118 (??)
(0) f 33049
pvthread+811900 STACK:
[0005EC1C]ep_block_thread+000520 (00000000100EFBF0 [??])
[0005D298]_thread_tsleep+000678 (??, ??, ??, ??, ??)
[00061070]thread_tsleep+000014 (??, ??, ??, ??)
[00003810].svc_i tr+000110 ()
[D0120300]_vp_sleep+000384 (??, ??)
[D011E140]_usched_di atch+000280 (??, ??, ??)
[00000000]00000000 ()
[kdb_get_virtual_memory] no real storage @ 3132303542002234
(0) f 220
pvthread+00DC00 STACK:
[0005EC1C]ep_block_thread+000520 (00000000F028C988 [??])
[0005D298]_thread_tsleep+000678 (??, ??, ??, ??, ??)
[00061070]thread_tsleep+000014 (??, ??, ??, ??)
[00003810].svc_i tr+000110 ()
[D0120300]_vp_sleep+000384 (??, ??)
[D011E140]_usched_di atch+000280 (??, ??, ??)
[00000000]00000000 ()
[kdb_get_virtual_memory] no real storage @ 42002234
(0) f 266
pvthread+010A00 STACK:
[000542F8]e_block_thread+000290 ()
[00145504] leep_com+0000BC (??)
[001460A8] leep+00006C (??, ??)
[00003810].svc_i tr+000110 ()
[D0124410]_p_ leep+00000C (??, ??)
[D037D51C] leep+0000A8 (??, ??)
[D037D14C]usleep+000080 (??)
[1002E040]Msleep+000078 (0000000A)
[1002D464]scan_timer+000424 (00000000)
[D010D780]_pthread_body+000118 (??)
除了挂死的线程,还有好几个线程都有[kdb_get_virtual_memory] no real storage。
azsxdcfv22
发表于 2010-12-15 09:12
有没人知道阿???
azsxdcfv22
发表于 2010-12-15 14:03
:sleepy:
azsxdcfv22
发表于 2010-12-16 10:35
:sleepy:
azsxdcfv22
发表于 2010-12-16 10:40
:sleepy:
azsxdcfv22
发表于 2010-12-23 14:27
有没人知道?
查看完整版本:
Powered by
7.2 2001-2009--如果执行了下面这段,会报buffer overflow limit of 2000 bytes:declarebegin for i in 0..1000 loop dbms_output.put_line('不设定的话,输入太长会报错ORA-20000: ORU-10027:'); end loo end;/-- 不过可以自己来设定,如下:declarebegin dbms_output.enable(99999999999999); for i in 0..1000 loop dbms_output.put_line('设了dbms_output.enable(99999999999999)就不报错了'); end loo end;/
2008-01-16 回复 (0)
相关讨论
觉得应该好好总结一下,ORACLE之PL/SQL学习
begin
if ... then
...
elsif ...then
...
end if;
end;
实例:
DECLARE
a number;
b varchar2(10);
if a=1 then
b:='a';
elsif a=2 then
b:='b';
b:='c';
2008-06-08 回复 (1)
--[3]// Oracle PL/SQL 编程
-------------------------------------------------------------------------------------//
--创建错误信息表
CREATE TABLE ErrInfo
ErrCode NUMBER(4) NOT NULL,
ErrWord VARCHAR2( ...
2007-05-08 回复 (0)
一条查询语句中包括有多个函数,函数的作用主要是主组合字符串.两个数据查询出来花了快一分多钟.
select distinct br.repeal_id,
to_char(br.create_date, 'yyyy-MM-dd hh24:mi') as create_date, o.object_alias_name ob ...
2008-04-16 回复 (8)
这是第三章的学习笔记,学习完第二章的编程基础之后,从现在开始要学习Oracle编程了……,希望大家能多给俺一些支持啊!
这周六总算是不用加班,可以好好出去玩一下了!
今天去武大看樱花了,哈哈,不错!
编程时使用的工具是PLSQL Developer 7.1.4
select * from employee;
select * from dba_tab_ ...
2010-03-20 回复 (0)
关于ORACLE中的数组:记录同集合
集合可以有三种实现方式:
1 自定义一个TYPE使用VARRAY来得到一个数组但只能对基本类型定义如:
CREATE TYPE 类型名 AS VARRAY OF VARCHAR2(20);
1 自定义一个TYPE使用VARRAY来得到一个数组但只能对基本类型定义如:
CREATE TYPE 类型名 AS VARRAY(52) OF VARCHAR2(20);
2007-08-03 回复 (0)
相关新闻
Google 很重视代码风格的一致性,而且还公开过一份 JavaScript 代码风格指南: Google JavaScript Style Guide,现在它们又发布了一个工具来帮助你检查 JavaScript 代码是否严格遵循了
Google JavaScript Style Guide :Closure Linter。
假设你有以下代码:
var x = 10
2010-09-03 回复 (20)
JRuby 社区高兴的宣布: 1.3.0 RC1 发布鸟~
The JRuby community is pleased to a ounce the release of JRuby 1.3.0RC1
主页:
http://www.jruby.org/
下载:
http://dist.codehaus.org/jruby/
版本库:
http://kenai.com/projects/jru ...
2009-05-04 回复 (11)
大家好,
我很自豪的宣布TestNG 5.9 发布了。(TestNG是一个设计用来简化广泛的测试需求的测试框架,从单元测试(隔离测试一个类)到集成测试(测试由有多个类多个包甚至多个外部框架组成的整个系统,例如运用服务器)。) 从变更列表中你可以看到,这次发布版本包含一些bug修复,同时也包含一些新特性。非常感谢你们,提交patch及修复的每个人。我将你人名字加入到了变更列表中,我希望铭记每个人。
2009-04-05 回复 (5)
Ext 小组高兴的宣布Ext GWT 2.0提供下载,Ext GWT 2.0 M2 包含了新的组件和功能,扩大了Ext GWT 1.0的功能集,有几项性能的改进,明显改善组件初始化渲染和布局执行的时间!
新增了下列UI组件
Charting
HtmlEditor (with ColorPalette)
RowEditor
Widget Renderer Grid
ButtonGroup
Status ...
2009-05-21 回复 (6)
相关博客
--如果执行了下面这段,会报buffer overflow limit of 2000 bytes:declarebegin for i in 0..1000 loop dbms_output.put_line('不设定的话,输入太长会报错ORA-20000: ORU-10027:'); end loo end;/-- 不过可以自己来设定,如下:declarebeg ...
2008-01-16 回复 (0)
ORA-20000: ORU-10027: buffer overflow, limit of 2000 bytes问题的解决:
方法1:setserveroutputonsize10000000 //设置大点,默认为2000 bytes
方法2:execdbms_output.enable(999999999999999999999); //设置大点,默认为2000 bytes
出 ...
2010-10-08 回复 (0)
ORA-20000: ORU-10027: buffer overflow, limit of 2000 bytes问题的解决:
方法1:setserveroutputonsize10000000 //设置大点,默认为2000 bytes
方法2:execdbms_output.enable(999999999999999999999); //设置大点,默认为2000 bytes
2011-01-07 回复 (0)
在存储过程中里面使用如下代码:
for i in 1..500 loop
dbms_output.put_line('Hello World.................................................................................');
end loo
执行 ...
2010-09-26 回复 (0)
相关问答
各位精英,小弟有个很基础的问题,希望不要笑话。
假设我创建了一个对象表为create or replace type enroll_obj_type as object
( student_id number(8),
first_name varchar2(25),
last_name varchar2(25)
course_no number(8),
section_no numb ...
2010-09-22 回复 (1)
create or replace procedure pro_loop(userid in varchar2)
is
departid number(4);
begin
begin
declare
cursor list is select departid from departmment ;
rs list%rowtyp ...
2009-01-15 回复 (1)
--这是我的存储过程:
create or replace procedure pro_drop is sql_str varchar2(1000):='' egin for v_cat in (select table_name from cat where table_type='TABLE')loop dbms_output.put_line(v_cat.table ...
2008-12-02 回复 (1)
CREATE OR REPLACE PROCEDURE INSERTSINGLETABLE(HEXID IN VARCHAR2) IS
-- maxrecords co tant int :=1000;
i int :=1;
STARTTIME DATE;
CROSSID CROSSCONFIG.CROSSID%TYPE;
...
2010-08-16 回复 (7)
相关群组讨论
求教一个类似行列互换的sql语句!
我现在有张表cityorder,有如下几个列:id,amount,city,date。表中有以下一些记录:
id amount city date
1 12 001 08-11-16
2 14 001 08-11-17
3 25 ...
2008-11-22 回复 (7)
我现在的数据库服务器,在每天早上数据处理的时候。每次做checkpoint的时间都太长了,有时候甚至达到300s的长度。请大家能给点建议。
服务器为一HP小型机,4CPU,8G内存。OS为Linux系统。数据库为Informix10下面贴出我的onconfig内容
#******************************************************************* ...
2008-07-02 回复 (3)
Eliminate a ert and lazy from D(老A觉得放到库里去更好)
Array literals' default type
Eliminate cla allocators and deallocators?析构后不回收内存(在讨论clear()方法)
空引用编译时检查 -O (2.033)
Changeset [197]:implement contract inh ...
2009-10-13 回复 (51)
CREATE PROC P_public_ViewPage
适用于联合主键/单主键/存在能确定唯一行列/存在能确定唯一行的多列 (用英文,隔开)
调用:
第一页查询时返回总记录和总页数及第一页记录:
EXECUTE P_public_ViewPage_per 'TableName','col1 ...
2010-05-17 回复 (3)
相关专栏文章
关键字
概念
类型
异常处理
一 概念
游标是SQL的一个内存工作区,由系统或用户以变量的形式定义。游标的作用就是用于临时存储从数据库中提取的数据块。在某些情况下,需要把数据从存放在磁盘的表中调到计算机内存中进行处理,最后将处理结果显示出来或最终写回数据库。这样数据处理的速度才会提高,否则频繁的磁盘数据交换会降低效率。
二 类型
Cursor类型包含三种: 隐式Cursor,显式Cu ...
2010-05-03 回复 (1)
关键字
嵌套事务和自治事务的概念
嵌套事务的使用
自治事务的使用
一. 概念
1. 嵌套事务(Nested Tra action):
指在一个Parent事务中嵌套的一个或多个Sub Tra action.并且主事务与其相互影响,这种事务就称为嵌套事务。以Commit作为事务的结束。
2. 自治事务(Autonomous Tra action):
指在function,proced ...
2010-05-26 回复 (1)
在Erlang里面,Binary支持强大的模式匹配,这为编写网络通讯程序提供了便利。
比如一个协议串,格式如下
HEADER(2 Bytes) ID (1 Byte) MESSAGE(10 Bytes)
可以这样匹配
Header:16, Id:8, Me age:10/binary-unit:8
有一些协议,头部是接下来数据的长度,这样就更简单了
...
2009-02-03 回复 (1)
我们知道 j2me 中没有 j2se 里边的 Properties 类,要自己实现才能像 j2se 那样读取文件的,现在 j2mepolish 里边的 de.enough.polish.util.Properties 就实现了类似 j2se 的 Properties, 加上de.enough.polish.util.ResourceStreamUtil(旧版本polish 没有这个类,要自己实现相应 ...
2009-04-08 回复 (0)
2003-2011 ITeye.com.
阅读,行走,思考--我们这样生活。
倾听,记录,讲述--我们这样blog。
发表于
2010-08-16 16:04:02
原文题目: A Crash Course on the Depths of Win32 Structured Exception Handling
原文地址: 翻译
http://www.c logs.com/awpatp/archive/2010/06/15/1758763.html
原作者: Matt Pietrek 在Win32的核心, 结构化异常处理(Structured Exception Handling)(SEH) 是操作系统提供的一种服务. 你能找到的所有关于SEH的文档都会描述某一种编译器的运行时库(runtime library)对操作系统实现的某种包装. 我会层层剥析SEH一直到它的最基本的概念. 这篇文章假设你熟悉Win32,C++ 文章示例代码:
Matt Pietrek 是
Windows 95 System Programming Secrets
(IDG Books, 1995)的作者. 他在NuMega Technologies Inc.工作, 可以通过
联系他. 在所有由Win32操作系统提供的基础设施中, 可能被最广泛应用却没有文档说明的就是结构化异常处理了. 可能一想到Win32结构化异常处理, 大家就会想到诸如_try, _finally, 和_except这样的术语. 你可以在任何合格的讲Win32的书中找到关于SEH的还不错的描述. 甚至Win32SDK也对也对使用_try, _finally, 和_except来进行结构化异常处理有还不错的概述. 有了这么多的文档, 为什么我还要说SEH没有文档说明呢? 在核心里, 结构化异常处理是一种操作系统提供的服务. 你能找到的所有关于SEH的文档都会描述某一种编译器的运行时库(runtime library)对操作系统实现的某种包装. 关键字_try, _finally, 和_except并没有什么神秘的. 微软的操作系统和编译器团队定义了这些关键字还有这些关键字的行为. 其他的C++编译器供应商就只是简单地顺从这些关键字的语义而已. 当编译器的SEH层驯服了原始操作系统SEH的琐碎混乱之处之后, 编译器就把原始操作系统的关于SEH的细节隐藏起来了. 我收到过很多很多的邮件, 需要实现编译器层的SEH的人根本找不到关于操作系统基础设施提供的关于SEH的细节. 在一个合理的世界中, 我将能够拿出来Visual C++ 或 Borland C++ 的运行库源码来分析他们是如何做到的. 可惜的是, 由于某种不知道的原因, 编译器层次的SEH好像是一个巨大的秘密. 微软和Borlandboundary不愿意拿出来源代码来为最底层的SEH提供支持. 在这篇文章里, 我会剖析异常处理一直到它的最基本的概念. 为了这么做, 我会通过生成代码和运行时库的支持, 把操作系统提供的东西从编译器提供的东西中拆分出来. 当我深入到操作系统的关键例程的代码的时候, 我ihui使用intel版本的Windows NT 4.0作为我的基础. 不过, 我所描述的绝大多数东西也适用于其他处理器. 我会避免真实的C++异常处理, 在C++的异常处理中会使用cache()而不是_except. 在幕后, 真实的C++异常处理跟我在这里描述的非常相似. 然而真实的C++异常处理会有一些额外的复杂之处, 我不会涉及他们, 因为他们会混淆我在这片文章中真正想要讲到的概念. 在挖掘组成Win32的结构化处理的晦涩的.H 和 .INC文件片段的时候, 最好的信息来源是IBM OS/2的头文件(尤其是BSEXCPT.H). 如果你在这个行业混过一段时间的话, 那你就不会觉得吃惊了. 这里描述的SEH机制是微软还在OS/2上工作的时候所定义的. 基于这个原因, 你会发现在Win32下的SEH跟OS/2异常类似. SEH in the Buff
============
如果要一次性把SEH的细节都照顾到的话, 那么任务量有点太大了, 我会从简单的地方开始, 逐层向上剖析. 如果你从来没有使用过结构化异常处理, 那你的状态还算不错, 不需要什么知识预备. 如果你以前使用过SEH, 你需要从你的脑子里把_try, GetExceptionCode, 和 EXCEPTION_EXECUTE_HANDLER这些词汇清理掉. 假设这些概念对你来说是新的. 深呼吸, 准备好了么? 很好. 设想一下我告诉你当一个线程出错的时候, 操作系统会给你一个机会, 让你得到这个错误的通知. 更具体地, 当一个线程出错的时候, 操作系统会调用一个用户定义的callback函数. 这个callback函数能够做它想做的任何事. 比如说, 它可以修复引发错误的地方, 或者播放一个搞笑的声音文件. 不管这个callback函数做什么, 它最后的动作时返回一个值, 用来告诉系统下一步该做什么的值(严格来说, 不是这样的, 但是对于现在来说已经足够接近了). 当你的代码把事情搞糟的时候让操作系统来调用你的函数, 那么这个callback函数应该像什么样子呢? 换句话说, 关于这个异常, 你想知道什么信息呢? 不必过多操心, Win32已经替你想好了. 一个异常callback函数看起来像这样:
EXCEPTION_DISPOSITION
__cdecl
_except_handler(
_EXCEPTION_RECORD *ExceptionRecord,
* EstablisherFrame,
_CONTEXT *ContextRecord,
* Di atcherContext
); 这个原型来自于标准的Win32头文件EXCPT.H, 初次看起来有点吓人. 如果你慢慢来看的话, 其实并不是那么难. 对于初学者来说,应该 忽略返回值(EXCEPTION_DISPOSITION). 基本上, 你知道的事实是: 这是一个带有四个参数的叫做_except_handler的函数. 第一个参数是一个指向EXCEPTION_RECORD结构的指针. 这个结构体是在WINNT.***件中定义的, 如下:
typedef struct
_EXCEPTION_RECORD
DWORD ExceptionCode;
DWORD ExceptionFlag struct
_EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddre DWORD NumberParameter DWORD ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
EXCEPTION_RECORD; 这里的ExceptionCode参数是操作系统赋予这个异常的一个号码. 你可以在WINNT.H中看到很多exception code的列表, 搜索以STATUS_开头的#define语句就可以了. 比如说, 熟悉的不能再熟悉的STATUS_ACCESS_VIOLATION的代码是0xC0000005. 一个更详细更全面的exception code的集合可以在Windows NT DDK中的NTSTATUS.H中找到. EXCEPTION_RECORD结构的第四个元素是exception发生的地址. 其他的EXCEPTION_RECORD中的元素目前可以忽略. _except_handler函数的第二个参数是一个指向establisher frame结构的指针. 这是结构化异常处理中的一个至关重要的参数, 但是现在你暂时可以忽略它. _except_handler函数的第三个参数是个指向CONTEXT结构的指针. CONTEXT结构是在WINNT.H中定义的, 它代表着某个线程的寄存器的值. Figure1展示了CONTEXT结构的定义. 当在SEH中使用的时候, CONTEXT结构代表着在异常发生时刻寄存器的值. 意外地是, 在GetThreadContext和SetThreadContext这两个API中, 这个结构是一样的. 第四个参数, 也是最后一个参数叫做Di atcherContext. 现在它也可以被忽略. CONTEXT Structure
typedef struct _CONTEXT
DWORD ContextFlag DWORD
FLOATING_S***E_AREA FloatSave;
SegG DWORD
SegF DWORD
SegE DWORD
SegD DWORD
E DWORD
Ei DWORD
SegC DWORD
EFlag DWORD
E DWORD
SegS } CONTEXT; 到目前为止简单地概括一下, 你有一个callback函数, 在异常发生的时候会被调用. 这个callback函数有四个参数, 其中的三个是指向结构的指针. 在这三个结构之中, 有些field很重要, 其他的却不是. 关键点是_except_handler 函数会收到丰富的信息, 比如发生的是什么类型的异常, 在哪里发生的这个异常. 通过这些信息, 异常callback函数可以决定下一步要做些什么. 看来是时候允许我丢出一个简单的小程序来展示_except_handler函数了, 但是还有一点东西需要补充. 特别地, 在异常发生的时候, 操作系统是如何知道到哪里去调用我们的callback函数呢? ***是另一个叫做EXCEPTION_REGISTRATION的结构体. 在这片文章中你将会看到这个结构体, 别把这一部分跳过了. 唯一一个我能找到的比较正式的对于EXCEPTION_REGISTRATION的定义的地方在EXSUP.INC文件, 它存在于Visual C++运行时库的源文件中:
_EXCEPTION_REGISTRATION
handler dd
_EXCEPTION_REGISTRATION ends 你将会看到在WINNT.H中NT_TIB定义中, 这个结构被引用为一个_EXCEPTION_REGISTRATION_RECORD. 再往下, 就没有定义_EXCEPTION_REGISTRATION_RECORD 的地方了, 所以我即将开始的地方是EXSUP.INC里的汇编语言结构定义. 这只是我早先时候提到的SEH没有被文档记录的几个部分的例子之一. 在任何情况下, 都让我们先回过头来处理一下手头的问题. 操作系统是如何得知异常发生的时候到哪里去调用函数呢?
EXCEPTION_REGISTRATION 结构由两个fields组成, 其中的第一个你现在可以忽略. 第二个field, 就是handler, 包含一个指向_except_ handler 回调函数的指针. 这让你离***更近了一步, 但是问题来了, OS到哪里去找EXCEPTION_REGISTRATION结构呢? 为了回答这个问题, 回忆一下结构化异常处理在单线程基础上的工作机制是有帮助的. 每个线程都有自己的exception handler回调函数. 在我1996年的专栏中, 我描述了一个关键的Win32结构, 线程信息块(TEB或TIB). 这个结构体当中的某些field在Windows NT, Window reg; 95, Win32s, 和OS/2是一样的. TIB的第一个DWORD是一个指向线程的EXCEPTION_REGISTRATION的指针. 在Intel的Win32平台上, FS寄存器永远指向当前的TIB. 即, 在FS:[0]的位置, 你可以找到一个指向EXCEPTION_REGISTRATION结构的指针. 现在我们已经比较深入了. 当一个exception发生的时候, 系统会查看出错线程的TIB结构, 取回一个指向一个EXCEPTION_REGISTRATION结构的指针. 在这个结构中, 有一个指向_except_handler回调函数的指针. 操作系统现在知道了足够的信息来调用_except_handler回调函数, 如
Figure 2
所示: Figure 2 _except_handler_function 小的知识点一块块的拼起来了之后, 我写了一个小程序来示范这个操作系统级的结构化异常处理的描述.
展现了MYSEH.CPP, 其中仅有两个函数. Main函数使用三个内联的ASM块. 第一块通过两个PUSH指令(PUSH handler 和PUSH FS:[0])在栈上构建了EXCEPTION_REGISTRATION结构. PUSH FS:[0]保存了之前的 FS:[0]的值作为这个结构的一部分, 但是目前来说这还不重要. 重要的是粘上有了一个8-byte大小的EXCEPTION_REGISTRATION 结构. 紧跟着的下一条指令(MOV FS:[0],ESP)使得TIB中的第一个DWORD指向了新的EXCEPTION_REGISTRATION结构. MYSEH.CPP
//==================================================
// MYSEH - Matt Pietrek 1997
// Microsoft Systems Journal, January 1997
// FILE: MYSEH.CPP
// To compile: CL MYSEH.CPP
//==================================================
#define
WIN32_LEAN_AND_MEAN
#include
windows.h
#include
< tdio.h
scratch;
EXCEPTION_DISPOSITION
__cdecl
_except_handler(
_EXCEPTION_RECORD *ExceptionRecord,
* EstablisherFrame,
_CONTEXT *ContextRecord,
* Di atcherContext )
u igned
// Indicate that we made it to our exception handler
printf(
Hello from an exception handler\ quot;
// Change EAX in the context record so that it points to someplace
// where we can succe fully write
ContextRecord-Eax = (DWORD)
// Tell the OS to restart the faulting i truction
ExceptionContinueExecutio }
DWORD handler = (DWORD)_except_handler;
// Build EXCEPTION_REGISTRATION record:
handler
// Addre of handler function
// Addre of previous handler
FS:[0],ESP
// I tall new EXECEPTION_REGISTRATION
// Zero out EAX
[eax], 1
// Write to EAX to deliberately cause a fault
printf(
After writing!\ quot;
// Remove our EXECEPTION_REGISTRATION record
eax,[ESP]
// Get pointer to previous record
FS:[0], EAX
// I tall previous record
// Clean our EXECEPTION_REGISTRATION off stack
} 如果你好奇为什么我在栈上创建了一个EXCEPTION_REGISTRATION结构而不是使用一个全局变量, 我有一个很好的理由. 当你使用编译器的_try/_except 语法的时候, 编译器也会在栈上创建EXCEPTION_REGISTRATION结构的. 我只是展现给你了编译器用来处理_try/_except的方式的一个简化版本.
回到Main函数, 下一个_asm块的目的是引发一个错误, 先将EAX寄存器清0,然后使用它(EAX)的值作为下一条指令用来写入的内存地址(MOV [EAX],1).
最后的__asm块移除了这个简单的异常处理器: 首先它恢复之前的FS:[0]的内容, 然后它将EXCEPTION_REGISTRATION记录从栈中弹出(ADD ESP,8). 现在, 假设你正在运行MYSEH.EXE, 那么你会看见发生的一切. 当指令MOV [EAX],1执行的时候, 它会引发一个非法访问的异常. 操作系统会查询TIB中的FS:[0], 找到指向EXCEPTION_REGISTRATION结构的指针. 在这个结构中有一个指针指向在MYSEH.CPP中的_except_handler 函数. 系统然后将四个所需的参数压栈, 然后调用_except_handler函数. 在_except_handler中, 代码首先通过一个printf语句说明嗨, 我搞糟的地方在这里!. 然后_except_handler修复了引发错误的问题. 也就是EAX寄存器指向一个不能写入的内存地址(地址0). 修复的方法是修改CONTEXT结构体中EAX的值, 让它指向一个可写的地址. 在这个简单的程序里, 一个DWORD变量(scratch)就是被设计来完成这个目的的. _except_handler函数的最后的动作时返回值ExceptionContinueExecution, ExceptionContinueExecution是在EXCPT.***件中定义的. 当操作系统发现返回的值是ExceptionContinueExecution 的时候, 它会理解成这意味着你已经修复了问题, 错误的语句可以被再次执行. 因为我的_except_handler函数修改了EAX寄存器的值, 让它指向了合法的内存地址, MOV EAX, 1指令第二次就成功地执行了, main函数可以正常地继续了. 你看到了, 不是那么复杂的, 对不对? 再深入一点 - Moving In a Little Deeper
======================
研究了这个最简单的情形后, 让我们回过头来填补一些当时留下的空隙吧. 虽然异常回调完成得很棒, 它却不是一个完美的解决方案. 在任何大小的应用程序中, 书写一个简单的函数来处理程序中任何地方都可能会出现的异常, 会非常麻烦. 一个更加可行的方式是拥有多重处理异常的路径, 每一个都针对应用程序的某部分而特别订制. 难道你不知道么, 操作系统提供的就是这个功能. 还记得操作系统用来查找异常回调函数地址的EXCEPTION_REGISTRATION 结构么? 这个结构的第一个参数, 我们稍早时忽略的那个, 被叫做prev. 它实际上是一个指向另一个EXCEPTION_REGISTRATION 结构的指针. 这第二个EXCEPTION_REGISTRATION 结构能够拥有完全不同的处理函数. 还有, 它的prev域可以指向第三个EXCEPTION_REGISTRATION 结构, 以此类推. 简单点说, 它们形成了一个EXCEPTION_REGISTRATION 的链表. 这个链表的头永远是被线程信息块(TIB)中的第一个DWORD(intel平台机器里的FS:[0])所指向的. 操作系统是如何处理这个EXCEPTION_REGISTRATION 的链表的呢? 当异常发生的时候, 系统会先遍历该结构的链表, 寻找包含愿意处理这个异常的回调函数的EXCEPTION_REGISTRATION结构. 在MYSEH.CPP中, 回调函数通过返回值ExceptionContinueExecution来表示同意处理这个异常. 异常回调函数也可以拒绝处理异常. 在这个情况下, 系统会继续走到链表中的下一个EXCEPTION_REGISTRATION结构上, 询问异常回调函数是否愿意处理这个异常.
展现了这个过程. 一旦操作系统找到了一个能够处理异常的callback函数, 它就停止遍历链表了. Finding a Structure to Handle the Exception
我展现了一个异常回调函数的例子, 看看
里的MYSEH2.CPP吧. 为了保持代码的简洁, 我使用编译器层的异常处理玩了个小花样. main函数只是设立了一个_try/_except块. 在__try块中, 有一个对HomeGrownFrame函数的调用. 这个函数与更早的那个MYSEH程序非常类似. 它在栈上创建了一个EXCEPTION_REGISTRATION 结构, 让FS:[0]指向这个结构.在创建了新的处理函数之后, 这个函数通过向NULL指针写数据故意地引发了一个错误:
*(PDWORD)0 = 0; MYSEH2.CPP
//==================================================
// MYSEH2 - Matt Pietrek 1997
// Microsoft Systems Journal, January 1997
// FILE: MYSEH2.CPP
// To compile: CL MYSEH2.CPP
//==================================================
#define
WIN32_LEAN_AND_MEAN
#include
windows.h
#include
< tdio.h
EXCEPTION_DISPOSITION
__cdecl
_except_handler(
_EXCEPTION_RECORD *ExceptionRecord,
* EstablisherFrame,
_CONTEXT *ContextRecord,
* Di atcherContext )
printf(
Home Grown handler: Exception Code: %08X Exception Flags %X
ExceptionRecord-ExceptionCode, ExceptionRecord-ExceptionFlags );
( ExceptionRecord-ExceptionFlags &am 1 )
printf(
EH_NONCONTINUABLE
( ExceptionRecord-ExceptionFlags &am 2 )
printf(
EH_UNWINDING
( ExceptionRecord-ExceptionFlags &am 4 )
printf(
EH_EXIT_UNWIND
( ExceptionRecord-ExceptionFlags &am 8 )
printf(
EH_STACK_INVALID
( ExceptionRecord-ExceptionFlags &am 0x10 )
printf(
EH_NESTED_CALL
printf(
\ quot;
// Punt... We don't want to handle this... Let somebody else handle it
ExceptionContinueSearch;
HomeGrownFrame(
DWORD handler = (DWORD)_except_handler;
// Build EXCEPTION_REGISTRATION record:
handler
// Addre of handler function
// Addre of previous handler
FS:[0],ESP
// I tall new EXECEPTION_REGISTRATION
*(PDWORD)0 = 0;
// Write to addre 0 to cause a fault
printf(
I should never get here!\ quot;
// Remove our EXECEPTION_REGISTRATION record
eax,[ESP]
// Get pointer to previous record
FS:[0], EAX
// I tall previous record
// Clean our EXECEPTION_REGISTRATION off stack
HomeGrownFrame();
_except( EXCEPTION_EXECUTE_HANDLER )
printf(
Caught the exception in main()\ quot;
} 异常回调函数, 又一次被命名为_except_ handler, 这一次跟上个版本有很大不同. 代码首先打印了ExceptionRecord 结构中的异常代码(exception code)和异常标志(exception flag), ExceptionRecord 结构的指针被作为一个参数传递给了我们的_except_ handler函数. 打印出exception flag的原因会在晚些时候变的更清楚. 因为这个_except_ handler函数并没有打算修复违例的代码, 该函数返回了ExceptionContinueSearch. 这会引发操作系统继续搜索链表中的下一个EXCEPTION_REGISTRATION 结构. 现在, 相信我的话, 下一个异常回调函数就是为main函数中的 _try/_except而被设立的了. _except 块简单地打印出了信息Caught the exception in main(). 在这个例子里, 对异常的处理就跟忽略它的发生一样的简单. 这里需要提及的一个关键点是执行控制. 当一个handler拒绝处理一个exception的时候, 它会有效地拒绝去判断控制将最终在何处被恢复. 接受异常的handler就是那个决定了控制在所有异常处理代码结束之后最终将在哪个地址上继续的那个handler. 这里有一个重要的隐含含义, 它目前还不明显. 当使用结构化异常处理的时候, 如果一个函数的异常处理函数并没有处理掉异常的话, 它也许会以一种不正常的方式退出. 比如说, 在MYSEH2中, HomeGrownFrame 中的最小的handler就没有处理掉异常. 既然链表中的某个部分的处理函数处理了异常(main函数), 出错指令后面的printf就再没有被执行了. 从某种程度上说, 使用结构化异常处理跟使用运行时的setjmp和longjmp函数是一样的. 如果你运行MYSEH2, 你会在输出中发现一些令人吃惊的东西. 看起来对_except_handler 函数的调用有两次! 根据你了解了的知识, 其中的第一次是不难理解的. 但是第二次调用时怎么回事呢?
Home Grown handler: Exception Code: C0000005 Exception Flags 0
Home Grown handler: Exception Code: C0000027 Exception Flags 2
EH_UNWINDING
Caught the Exception in main() 这里有一处明显的不同: 比较两行以Home Grown Handler 开头的输出. 注意, 第一次exception flag是0, 而第二次是2。这把我们带到议题unwinding上来了. 提前一点, 当异常回调拒绝处理一个异常的时候, 它会再被调用一次. 但是这次回调并不会立即发生. 事实比较复杂, 我需要最后细化异常的secnario一下了. 当一个异常发生的时候, 系统会遍历EXCEPTION_REGISTRATION结构的链表, 直到他找到一个能够处理异常的handler为止. 一旦找到了一个handler, 系统会再次遍历列表, 遍历会停在这个能够处理异常的节点上. 在这第二次的遍历中, 系统会第二次的调用每一个异常处理函数. 关键的不同在于, 在第二次调用中, 2这个值会被赋予exception flag. 这个值也就是EH_UNWINDING. (EH_UNWINDING的定义在EXCEPT.INC中, 该文件在Virtual C++运行时库的源代码中, 但是跟Win32SDK中的没啥关系). 那么EH_UNWINDING是什么意思呢? 当一个异常回调函数在第二次被执行的时候(flag的值是EH_UNWINDING), 操作系统会给handler function一个机会来执行一些它需要做的清理工作. 那种清理工作(cleanup)呢? 最好的例子是C++类的析构函数. 当一个函数的exception handler拒绝处理一个异常的时候, 典型地, 控制并不会以正常的方式从函数中离开的. 现在, 假设有一个函数, 其中声明了一个C++的类对象作为一个局部变量. C++标准说, 析构函数是一定会被调用的. 带着EH_UNWINDING标志的exception handler的第二次调用就是个供函数来执行诸如调用析构函数和_finally块的机会. 当一个异常被处理了, 所有前面的exception frame被依次展开了之后, 执行会在任何处理回调函数确定的一个地方继续下去. 记住, 仅仅设置指令指针到需要的代码地址是不够的. 代码继续执行的地方还需要栈顶指针和栈框架指针被设置为合适的值. 所以, 处理异常的handler有责任设置栈顶指针和栈框架指针的值, 设置之后, 栈框架中包含有处理异常的SEH代码. Unwinding from an Exception 用更概括的术语, 从一个exception展开的动作在栈上引发了栈的handling frame之下的部分都被移除了. 这几乎跟那些函数从没被调用过一样. 另一个unwind的效果是处理异常的节点之前的所有EXCEPTION_REGISTRATION都被从列表中移除了. 这是合理的, 因为这些EXCEPTION_REGISTRATION都是建立在栈上的. 在异常被处理了之后, 栈顶和栈框架指针都会比从列表被移出的EXCEPTION_REGISTRATION的地址要高.
Figure 6
说明了我的观点. 救命呀! 没有人处理这个异常! (Help! Nobody Handled It!)
=======================
到目前为止, 我一直隐含地假设操作系统总是会在EXCEPTION_REGISTRATION结构的链表的某处找到一个handler. 那么如果没有一个结构愿意站出来处理这个异常怎么办? 事实上, 这中情况从来就不会发生. 原因是操作系统偷偷地为每一个线程配置了一个默认的异常处理的handler. 默认的handler永远是链表的最后一个节点, 并且总是会处理掉异常. 它的行为与一般的异常处理回调函数有某种程度的不同, 我会在稍后展现出来. 让我们看一下操作系统插入默认的, 最终的异常handler的地方吧. 显然, 这个动作会在线程执行的非常早期的时候发生, 所谓早期, 是指在任何用户代码执行之前.
展现了我为BaseProce Start方法写的一些伪代码, BaseProce Start是一个Windows NT的KERNEL32.DLL的内部函数. 它带一个参数, 即线程的入口地址. BaseProce Start在新进程的context下运行, 并且调用入口地址来开动进程中的首个线程的执行. BaseProce Start Pseudocode
BaseProce Start( PVOID lpfnEntryPoint )
DWORD retValue
DWORD currentESP;
DWORD exceptionCode;
currentESP = ESP;
NtSetInformationThread( GetCurrentThread(),
ThreadQuerySetWin32StartAddre ,
&am lpfnEntryPoint,
(lpfnEntryPoint) );
retValue = lpfnEntryPoint();
ExitThread( retValue );
_except(
// filter-expre ion code
exceptionCode = GetExceptionInformation(),
UnhandledExceptionFilter( GetExceptionInformation() ) )
ESP = currentESP;
( !_BaseRu ingInServerProce )
// Regular proce ExitProce ( exceptionCode );
// Service
ExitThread( exceptionCode );
} 在伪代码中, 注意对lpfnEntryPoint 的调用是包装在一个_try 和_except 的构造中的. 这个_try块就是那个***默认的, 最终的exception handler的地方. 所有后续注册的异常处理handlers都会被插入到链表中的这个节点的前面. 如果lpfnEntryPoint 函数返回了, 那么线程就成功地运行结束了, 没有引发任何的异常. 如果没有异常, BaseProce Start 会调用ExitThread 来结束线程. 另一方面, 如果线程出错了, 又没有其他的exception handler处理呢? 在这种情况下, 控制会进入到_except关键字后面的括号中. 在BaseProce Start, 这段代码调用了UnhandledExceptionFilter 这个API函数, 我稍后会介绍这个函数. 现在, 关键点是UnhandledExceptionFilter API包含默认exception handler的重要成分. 如果UnhandledExceptionFilter 返回了EXCEPTION_EXECUTE_HANDLER, 那么BaseProce Start 重的_except块中的代码会被执行. _except块内的代码所作的工作就是通过调用ExitProce 来结束掉当前的进程. 花一秒中的时间在这里思考一下, 其实这是合理的: 如果一个程序遇到了错误, 并且没有任何人处理这个错误的话, 操作系统应该终止这个进程, 这应该算是常识. 只不过你在伪代码中看到的是这个常识的精确的发生地和发生方式. 对我刚才描述的要点还有一个最后的补充. 如果出错的线程是作为一个服务运行着的, 并且是一个基于线程的服务的话, 那么_except块的代码并不会调用ExitProce , 取而代之的是调用ExitThread . 你并不需要仅仅因为一个线程出了点毛病, 就把整个进程干掉. 那么, 默认的exception handler中的UnhandledExceptionFilter里的代码都做了些什么呢? 当我在研讨会上问起这个问题的时候, 很少有人能猜到在一个没有被处理的异常发生的时候, 操作系统的默认行为. 如果我们来一个非常简单的对于default handler的行为的demo的话, 事情就简单多了, 大家也更容易理解. 我简单地云新各一个程序, 故意地引发一个错误, 并且指出错误的结果(
Figure 8
). Figure 8 Unhandled Exception Dialog 在较高的层次上看, UnhandledExceptionFilter 会显示一个对话框, 告诉你发生了一个错误. 在那个时间点上, 你被给予一个机会, 要么终止进程, 要么debug出错了的进程. 在幕后还有更多的事情发生, 我会在本文即将结束的时候描述这些事情. 正如我已经演示了的, 当一个异常发生的时候, 用户写的代码能够被执行. 一样地, 在unwind操作的时候, 用户写的代码也能够被执行. 这用户写的代码可能会有bug, 并引发另一个异常. 基于这个原因, exception的回调函数有另外两个值可以返回:
ExceptionNestedException
ExceptionCollidedUnwind.
很明显这很重要, 而且这很明显是非常高级的话题了, 我并不打算在这里停下来描述它, 因为光是理解基本概念就已经够难了. 编译器水平的结构化异常处理(Compiler-level SEH
==================
我已经时不时地引用_try 和_except这两个关键字了, 到现在我写的东西还都是由操作系统实现的. 然而, 在我的两个小程序挑逗性地使用着原始的系统结构化异常处理的时候, 编译器所包装的这个功能肯定早为你准备好了. 让我们看看Virtual C++是如何在操作系统级的SEH基础架构上构建自己的对结构化异常处理的支持的吧. 在继续下去之前, 回忆下能够使用操作系统级的SEH基础设施来完成完全不同的事情的另一个编译器是有必要的. 没有人说编译器一定要实现由Win32 SDK文档描述的_try/_except模型. 比如说, 即将发布的Visual Basic 5.0就在它的运行时代码中使用了结构化异常处理, 但是数据结构和算法都与我在这里描述的完全不同. 如果你通读Win32 SDK文档关于结构化异常处理的部分, 你会遇到下面的叫做frame-based的语法的exception handler:
// guarded body of code
except (filter-expre ion) {
// exception-handler block
} 简单地说, 所有在try块中的代码都被一个EXCEPTION_REGISTRATION 保护着, 这个EXCEPTION_REGISTRATION 是构建在函数的栈帧上的(stack frame). 在入口处, 这个新的EXCEPTION_REGISTRATION 会被放在exception handler的链表的头部. 在_try块的结尾, 它的EXCEPTION_REGISTRATION 会被从链表的头部移除. 正如我早些时候提到的, exception handler链表的头式存储在FS:[0]当中的. 所以, 如果你步入debugger的汇编语言语句的话, 你会看到如下的指令:
MOV DWORD PTR FS:[00000000],ESP
MOV DWORD PTR FS:[00000000],ECX
你可以确定, 这就是在配置和拆除一个_try/_except 块了. 现在, 你已经知道一个_try 块根栈上的EXCEPTION_REGISTRATION 结构的关系, 那在EXCEPTION_ REGISTRATION里的回调函数又是怎么回事儿呢? 用Win32的术语来说, 异常回调函数对应着一个过滤表达式(filter-expre ion)代码. 清理一下你的记忆, 过滤表达式就是_except关键字后面跟着的括号里的代码. 就是这段过滤表达式能够决定紧随其后的{}里的代码是否会执行. 既然你写了filter-expre ion代码, 那么就由你来决定是否某个特定的exception应该在你代码的某个特定的位置来处理. 你的filter-exper ion代码既可以知识简单地打印一句我处理了这个异常, 也可以在返回系统, 告诉系统下一步该做什么之前触发一个极其复杂的函数. 你说了算. 重点是, 你的filter-expre ion代码就是我早先描述的异常回调(exception callback) 我刚刚描述的东西尽管简单的非常合理, 但它确只不过是真实世界的一种乐观抽象. 事实更加复杂这一点无疑是丑陋的现实. 对于初学者来说, 你的filter-expre ion代码并不是直接由操作系统调用的. 其实, 每一个EXCEPTION_REGISTRATION的exception handler域都指向一个相同的函数. 这个函数存在于Visual C++ runtime library里, 并且被叫做__except_handler3. 实际上是你__except_handler3调用的你的filter-expre ion code, 晚些时候我会再解释这一点的. 另一个对与简单试图的扭曲之处是: EXCEPTION_REGISTRATION们并不是在每一次进入或离开_try block的时候被构造和拆解的. 取而代之的是, 你可以在一个函数中添加多个_try/_except结构, 但是只能有一个EXCEPTION_REGISTRATION被创建在栈上. 同理, 你或许有一层_try block内嵌在另一个_try block中, 但是, Visual C++值创建一个EXCEPTION_REGISTRATION. 如果一个单独的exception handler(比如__except_handler3)足以处理整个exe或dll, 并且如果一个EXCEPTION_REGISTRATION 处理过个_try block的话, 很显然这里发生的事情会比眼睛看到的多好多. 这些神奇的事情是通过在你一般看不到的表里的数据来完成的. 然而, 因为这篇文章的目的是解剖异常处理, 看不到这些数据表也不能阻挡我们的, 让我们来一起看一下这些数据结构吧. 扩展了的Exception Handling Frame- (The Extended Exception Handling Frame
====================
Visual C++ SEH的实现并没有使用原始的EXCEPTION_REGISTRATION. 取而代之的是, 它在这个结构的末尾添加了一些额外的数据域. 这些额外的数据对于允许函数(__except_handler3)处理所有的异常, 还有让控制路由到合适的filter-expre ion和_except块都是至关重要的. Visual C++对于这个结构扩展的格式可以在EXSUP.INC中找到, 该文件存在于Visual C++ runtime library的源代码中. 在这个文件中, 你可以找到如下的(已注释的)定义:
_EXCEPTION_REGISTRATION{
_EXCEPTION_REGISTRATION *prev;
(*handler)(PEXCEPTION_RECORD,
PEXCEPTION_REGISTRATION,
PCONTEXT,
PEXCEPTION_RECORD);
scopetable_entry *scopetable;
trylevel;
PEXCEPTION_POINTERS xpointer ;}; 你已经见过头两个fields了, 一个是prev, 另一个是handler. 它们组成了基本的EXCEPTION_REGISTRATION 结构. 最后的三个field是新加上去的, scopetable, trylevel, 和_e . 域scopetable 指向一个元素类型为scopetable_entries的数组, 而域trylevel就是这个数组的索引值. 随后的域_edp, 是在EXCEPTION_REGISTRATION 创建之前的栈框架指针(EBP)的值. 域_e 称为扩展的EXCEPTION_REGISTRATION结构的一部分并不是巧合. 它通过PUSH EBP指令被放置在结构中, push e 指令是绝大多数函数开始的指令. 它的效果是使得所有其他的EXCEPTION_REGISTRATION的field都变成可以访问的了, 原因是框架指针的负位移. 比如说, trylevel域在[EBP-04]的位置, 所以scopetable指针的位置就在 [EBP-08], 以此类推. 紧挨着扩展的EXCEPTION_REGISTRATION结构的下面, Visual C++还添加了两个额外的值. 紧接着的一个DWORD里, 它保留了一个指向EXCEPTION_POINTERS结构的指针(标准Win32的结构). 这个指针在你调用GetExceptionInformation API的时候会被返回. SDK文档暗示GetExceptionInformation是一个标准Win32API, 事实上, GetExceptionInformation是一个编译器固有的函数. 当你调用这个函数的时候, Visual C++生成下面的指令:
MOV EAX,DWORD PTR [EBP-14] 正如GetExceptionInformation 是一个编译器固有函数一样, 与之相关联的GetExceptionCode函数也是一个编译器固有函数. GetExceptionCode 只是寻找并返回GetExceptionInformation 所返回的结构中的一个数据域(field). 我将会把这个留给读者做一个练习, 练习弄清楚在Visual C++为GetExceptionCode产生如下指令的时候, 究竟都发生了什么:
MOV EAX,DWORD PTR [EBP-14]
MOV EAX,DWORD PTR [EAX]
MOV EAX,DWORD PTR [EAX] 返回到扩展了的EXCEPTION_REGISTRATION 结构, 在结构开始前的8个字节, Visual C++会保留一个DWORD来保存所有已经执行了的开场代码的最终的栈指针(ESP). 这个DWORD就是函数正常执行时ESP寄存器的一个普通值(除非当参数正在压栈, 并准备调用下一个函数). 看起来我已经丢给了你一大堆信息, 事实上我的确是这样做的. 在继续下去之前, 让我们稍微暂停并回顾一下Vistal C++为一个使用结构化异常处理而生成的标准的栈内的情况吧.
EBP-00 _e EBP-04 trylevel
EBP-08 scopetable pointer
EBP-0C handler function addre EBP-10 previous EXCEPTION_REGISTRATION
EBP-14 GetExceptionPointers
EBP-18 Standard ESP in frame 从操作系统的角度来看, 只有两个fields组成了原始的EXCEPTION_REGISTRATION结构: 在[EBP-10]位置上的prev指针, 还有在位置[EBP-0Ch]上的handler函数指针. 其他的东西都是具体针对Visual C++的实现的. 了解了这些之后, 让我们来看看体现了编译器等级的结构化异常处理的Visual C++运行时库的函数__except_handler3吧. __except_handler3 and the scopetable
============================
While I'd dearly love to point you to the Visual C++ runtime library sources and have you check out the __except_handler3 function yourself, I can't. It's co icuously a ent. In its place, you'll have to make due with some eudocode for __except_handler3 that I co led together (see
). __except_handler3 Pseudocode
__except_handler3(
_EXCEPTION_RECORD * pExceptionRecord,
EXCEPTION_REGISTRATION * pRegistrationFrame,
_CONTEXT *pContextRecord,
* pDi atcherContext )
LONG filterFuncRet
LONG trylevel
EXCEPTION_POINTERS exceptPtrs
PSCOPETABLE pScopeTable
// Clear the direction flag (make no a umptio !)
// if neither the EXCEPTION_UNWINDING nor EXCEPTION_EXIT_UNWIND bit
// is set...
This is true the first time through the handler (the
// non-unwinding case)
( ! (pExceptionRecord-ExceptionFlags
&am (EXCEPTION_UNWINDING | EXCEPTION_EXIT_UNWIND)) )
// Build the EXCEPTION_POINTERS structure on the stack
exceptPtrs.ExceptionRecord = pExceptionRecord;
exceptPtrs.ContextRecord = pContextRecord;
// Put the pointer to the EXCEPTION_POINTERS 4 bytes below the
// establisher frame.
See ASM code for GetExceptionInformation
*(PDWORD)((PBYTE)pRegistrationFrame - 4) = &am exceptPtr // Get initial trylevel value
trylevel = pRegistrationFrame-trylevel
// Get a pointer to the scopetable array
scopeTable = pRegistrationFrame-
search_for_handler:
( pRegistrationFrame-trylevel != TRYLEVEL_NONE )
( pRegistrationFrame-> copetable[trylevel].lpfnFilter )
PUSH EBP
// Save this frame EBP
// !!!Very Important!!!
Switch to original EBP.
This is
// what allows all locals in the frame to have the same
// value as before the exception occurred.
EBP = &am RegistrationFrame-_e // Call the filter function
filterFuncRet = scopetable[trylevel].lpfnFilter();
POP EBP
// Restore handler frame EBP
( filterFuncRet != EXCEPTION_CONTINUE_SEARCH )
( filterFuncRet 0 )
// EXCEPTION_CONTINUE_EXECUTION
ExceptionContinueExecutio // If we get here, EXCEPTION_EXECUTE_HANDLER was ecified
scopetable == pRegistrationFrame-> copetable
// Does the actual OS cleanup of registration frames
// Causes this function to recurse
__global_unwind2( pRegistrationFrame );
// Once we get here, everything is all cleaned up, except
// for the last frame, where we'll continue execution
EBP = &am RegistrationFrame-_e __local_unwind2( pRegistrationFrame, trylevel );
// NLG == " on-local-goto (setjmp/longjmp stuff)
__NLG_Notify( 1 );
// EAX == scopetable-lpfnHandler
// Set the current trylevel to whatever SCOPETABLE entry
// was being used when a handler was found
pRegistrationFrame-trylevel = scopetable-> reviousTryLevel;
// Call the _except {} block.
Never retur .
pRegistrationFrame-> copetable[trylevel].lpfnHandler();
scopeTable = pRegistrationFrame-
trylevel = scopeTable-> reviousTryLevel
search_for_handler;
// trylevel == TRYLEVEL_NONE
retvalue == DISPOSITION_CONTINUE_SEARCH;
// EXCEPTION_UNWINDING or EXCEPTION_EXIT_UNWIND flags are set
PUSH EBP
// Save EBP
EBP = pRegistrationFrame-_e // Set EBP for __local_unwind2
__local_unwind2( pRegistrationFrame, TRYLEVEL_NONE )
POP EBP
// Restore EBP
retvalue == DISPOSITION_CONTINUE_SEARCH;
While __except_handler3 looks like a lot of code, remember that it's just an exception callback like I described at the begi ing of this article. It takes the identical four parameters as my homegrown exception callbacks in MYSEH.EXE and MYSEH2.EXE. At the topmost level, __except_handler3 is lit into two parts by an if statement. This is because the function can be called twice, once normally and once during the unwind phase. The larger portion of the function is devoted to the non-unwinding callback.
The begi ing of this code first creates an EXCEPTION_POINTERS structure on the stack, initializing it with two of the __except_handler3 parameters. The addre of this structure, which I've called exceptPtrs in the eudocode, is placed at [EBP-14]. This initializes the pointer that the GetExceptionInformation and GetExceptionCode functio use.
Next, __except_handler3 retrieves the current trylevel from the EXCEPTION_REGISTRATION frame (at [EBP-04]). The trylevel variable acts as an index into the scopetable array, which allows a single EXCEPTION_REGISTRATION to be used for multiple _try blocks within a function, as well as nested _try blocks. Each scopetable entry looks like this:
typedef struct
_SCOPETABLE
previousTryLevel;
lpfnFilter
lpfnHandler
} SCOPETABLE, *PSCOPETABLE; The second and third parameters in a SCOPETABLE are easy to understand. They're the addre es of your filter-expre ion and the corre onding _except block code. The previous tryLevel field is bit trickier. In a nutshell, it's for nested try blocks. The important point here is that there's one SCOPETABLE entry for each _try block in a function.
As I mentioned earlier, the current trylevel ecifies the scopetable array entry to be used. This, in turn, ecifies the filter-expre ion and _except block addre es. Now, co ider a scenario with a _try block nested within another _try block. If the i er _try block's filter-expre ion doe 't handle the exception, the outer _try block's filter-expre ion must get a crack at it. How does __except_handler3 know which SCOPETABLE entry corre onds to the outer _try block? Its index is given by the previousTryLevel field in a SCOPETABLE entry. Using this scheme, you can create arbitrarily nested _try blocks. The previousTryLevel field acts as a node in a linked list of po ible exception handlers within the function. The end of the list is indicated by a trylevel of 0xFFFFFFFF.
Returning to the __except_handler3 code, after it retrieves the current trylevel the code points to the corre onding SCOPETABLE entry and calls the filter- expre ion code. If the filter-expre ion retur EXCEPTION_CONTINUE_SEARCH, __except_handler3 moves on to the next SCOPETABLE entry, which is ecified in the previousTryLevel field. If no handler is found by traversing the list, __except_handler3 retur DISPOSITION_CONTINUE_SEARCH, which causes the system to move on to the next EXCEPTION_REGISTRATION frame.
If the filter-expre ion retur EXCEPTION_EXECUTE_HANDLER, it mea that the exception should be handled by the corre onding _except block code. This mea that any previous EXCEPTION_REGISTRATION frames have to be removed from the list and the _except block needs to be executed. The first of these chores is handled by calling __global_unwind2, which I'll describe later on. After some other intervening cleanup code that I'll ignore for the moment, execution leaves __except_handler3 and goes to the _except block. What's strange is that control never retur from the _except block, even though __except_handler3 makes a CALL to it.
How is the current trylevel set? This is handled implicitly by the compiler, which performs on-the-fly modificatio of the trylevel field in the extended EXCEPTION_REGISTRATION structure. If you examine the a embler code generated for a function that uses SEH, you'll see code that modifies the current trylevel at [EBP-04] at different points in the function's code.
How does __except_handler3 make a CALL to the _except block code, yet control never retur ? Since a CALL i truction pushes a return addre onto the stack, you'd think this would me up the stack. If you examine the generated code for an _except block, you'll find that the first thing it does is load the ESP register from the DWORD that's 8 bytes below the EXCEPTION_REGISTRATION structure. As part of its prologue code, the function saves the ESP value away so that an _except block can retrieve it later. The ShowSEHFrames Program
=======================
If you're feeling a bit overwhelmed at this point by things like EXCEPTION_REGISTRATIONs, scopetables, trylevels, filter-expre io , and unwinding, so was I at first. The subject of compiler-level structured exception handling does not lend itself to learning incrementally. Much of it doe 't make se e unle you understand the whole ball of wax. When confronted with a lot of theory, my natural inclination is to write code that a lies the concepts I'm learning. If the program works, I know that my understanding is (usually) correct.
is the source code for ShowSEHFrames.EXE. It uses _try/_except blocks to set up a list of several Visual C++ SEH frames. Afterwards, it di lays information about each frame, as well as the scopetables that Visual C++ builds for each frame. The program doe 't generate or expect any exceptio . Rather, I included all the _try blocks to force Visual C++ to generate multiple EXCEPTION_ REGISTRATION frames, with multiple scopetable entries per frame. ShowSEHFrames.CPP
//==================================================
// ShowSEHFrames - Matt Pietrek 1997
// Microsoft Systems Journal, February 1997
// FILE: ShowSEHFrames.CPP
// To compile: CL ShowSehFrames.CPP
//==================================================
#define
WIN32_LEAN_AND_MEAN
#include
windows.h
#include
< tdio.h
#pragma hdrstop
//----------------------------------------------------------------------------
// !!! WARNING !!!
This program only works with Visual C++, as the data
// structures being shown are ecific to Visual C++.
//----------------------------------------------------------------------------
#ifndef
_MSC_VER
Visual C++ Required (Visual C++ ecific information is di layed)
//----------------------------------------------------------------------------
// Structure Definitio //----------------------------------------------------------------------------
// The basic, OS defined exception frame
EXCEPTION_REGISTRATION
EXCEPTION_REGISTRATION* prev;
FARPROC
handler;
// Data structure(s) pointed to by Visual C++ extended exception frame
scopetable_entry
previousTryLevel;
FARPROC
lpfnFilter;
FARPROC
lpfnHandler;
// The extended exception frame used by Visual C++
VC_EXCEPTION_REGISTRATION : EXCEPTION_REGISTRATION
scopetable_entry *
scopetable;
trylevel;
//----------------------------------------------------------------------------
// Prototypes
//----------------------------------------------------------------------------
// __except_handler3 is a Visual C++ RTL function.
We want to refer to
// it in order to print it's addre .
However, we need to prototype it since
// it doe 't a ear in any header file.
_except_handler3(PEXCEPTION_RECORD, EXCEPTION_REGISTRATION *,
PCONTEXT, PEXCEPTION_RECORD);
//----------------------------------------------------------------------------
// Code
//----------------------------------------------------------------------------
// Di lay the information in one exception frame, along with its scopetable
ShowSEHFrame( VC_EXCEPTION_REGISTRATION * pVCExcRec )
printf(
Frame: %08X
Handler: %08X
Prev: %08X
Scopetable: %08X\ quot;
pVCExcRec, pVCExcRec-handler, pVCExcRec-> rev,
pVCExcRec-> copetable );
scopetable_entry * pScopeTableEntry = pVCExcRec-
u igned
i = 0; i = pVCExcRec-trylevel; i++ )
printf(
scopetable[%u] PrevTryLevel: %08X
filter: %08X
__except: %08X\ quot;
pScopeTableEntry-> reviousTryLevel,
pScopeTableEntry-lpfnFilter,
pScopeTableEntry-lpfnHandler );
pScopeTableEntry++;
printf(
\ quot;
// Walk the linked list of frames, di laying each in turn
WalkSEHFrames(
VC_EXCEPTION_REGISTRATION * pVCExcRec;
// Print out the location of the __except_handler3 function
printf(
_except_handler3 is at addre : %08X\ quot;
, _except_handler3 );
printf(
\ quot;
// Get a pointer to the head of the chain at FS:[0]
mov eax, FS:[0]
mov [pVCExcRec], EAX
// Walk the linked list of frames.
0xFFFFFFFF indicates the end of list
0xFFFFFFFF != (
u igned
)pVCExcRec )
ShowSEHFrame( pVCExcRec );
pVCExcRec = (VC_EXCEPTION_REGISTRATION *)(pVCExcRec-> rev);
Function1(
// Set up 3 nested _try levels (thereby forcing 3 scopetable entries)
WalkSEHFrames();
// Now show all the exception frames
_except( EXCEPTION_CONTINUE_SEARCH )
_except( EXCEPTION_CONTINUE_SEARCH )
_except( EXCEPTION_CONTINUE_SEARCH )
// Use two (non-nested) _try blocks.
This causes two scopetable entries
// to be generated for the function.
i = 0x1234;
// Do nothing in particular
_except( EXCEPTION_CONTINUE_SEARCH )
i = 0x4321;
// Do nothing (in reverse)
Function1();
// Call a function that sets up more exception frames
_except( EXCEPTION_EXECUTE_HANDLER )
// Should never get here, since we aren't expecting an exception
printf(
Caught Exception in main\ quot;
The important functio in ShowSEHFrames are WalkSEHFrames and ShowSEHFrame. WalkSEHFrames first prints out the addre of __except_handler3, the reason for which will be clear in a moment. Next, the function obtai a pointer to the head of the exception list from FS:[0] and walks each node in the list. Each node is of type VC_EXCEPTION_REGISTRATION, which is a structure that I defined to describe a Visual C++ exception handling frame. For each node in the list, WalkSEHFrames pa es a pointer to the node to the ShowSEHFrame function.
ShowSEHFrame starts by printing the addre of the exception frame, the handler callback addre , the addre of the previous exception frame, and a pointer to the scopetable. Next, for each scopetable entry, the code prints out the previous trylevel, the filter-expre ion addre , and the _except block addre . How do I know how many entries are in a scopetable? I don't really. Rather, I a ume that the current trylevel in the VC_EXCEPTION_REGISTRATION structure is one le than the total number of scopetable entries.
shows the results of ru ing ShowSEHFrames. First, examine every line that begi with Frame:. Notice how each succe ive i tance shows an exception frame that's at a higher addre on the stack. Next, on the first three Frame: lines, notice that the Handler value is the same (004012A8). Looking at the begi ing of the output, you'll see that this 004012A8 is none other than the addre of the __except_handler3 function in the Visual C++ runtime library. This proves my earlier a ertion that a single entry point handles all exceptio . Ru ing ShowSEHFrames You may be wondering why there are three exception frames using __except_handler3 as their callback since ShowSEHFrames plainly has only two functio that use SEH. The third frame comes from the Visual C++ runtime library. The code in CRT0.C from the Visual C++ runtime library sources shows that the call to main or WinMain is wra ed in an _try/_except block. The filter-expre ion code for this _try block is found in the WINXFLTR.C file.
Returning to ShowSEHFrames, the Handler for the last Frame: line contai a different addre , 77F3AB6C. Poking around a bit, you'll find that this addre is in KERNEL32.DLL. This particular frame is i talled by KERNEL32.DLL in the BaseProce Start function that I described earlier. Unwinding
=======================
Let's briefly recap what unwinding mea before digging into the code that implements it. Earlier, I described how potential exception handlers are kept in a linked list, pointed to by the first DWORD of the thread information block (FS:[0]). Since the handler for a particular exception may not be at the head of the list, there needs to be an orderly way of removing all exception handlers in the list that are ahead of the handler that actually deals with the exception.
As you saw in the Visual C++ __except_handler3 function, unwinding is performed by the __global_unwind2 RTL function. This function is just a very thin wra er around the undocumented RtlUnwind API:
__global_unwind2(
* pRegistFrame)
_RtlUnwind( pRegistFrame,
&am __ret_label,
0, 0 );
__ret_label:
} While RtlUnwind is a critical API for implementing compiler-level SEH, it's not documented anywhere. While technically a KERNEL32 function, the Windows NT KERNEL32.DLL forwards the call to NTDLL.DLL, which also has an RtlUnwind function. I was able to piece together some eudocode for it, which a ears in
. RtlUnwind Pseudocode
_RtlUnwind( PEXCEPTION_REGISTRATION pRegistrationFrame,
PVOID returnAddr,
// Not used! (At least on i386)
PEXCEPTION_RECORD pExcptRec,
DWORD _eax_value )
stackUserBase;
stackUserTo PEXCEPTION_RECORD pExcptRec;
EXCEPTION_RECORD
exceptRec;
CONTEXT context;
// Get stack boundaries from FS:[4] and FS:[8]
RtlpGetStackLimits( &am tackUserBase, &am tackUserTop );
( 0 == pExcptRec )
// The normal case
pExcptRec = &am excptRec;
pExcptRec-ExceptionFlags = 0;
pExcptRec-ExceptionCode = STATUS_UNWIND;
pExcptRec-ExceptionRecord = 0;
// Get return addre off the stack
pExcptRec-ExceptionAddre = RtlpGetReturnAddre ();
pExcptRec-ExceptionInformation[0] = 0;
( pRegistrationFrame )
pExcptRec-ExceptionFlags |= EXCEPTION_UNWINDING;
pExcptRec-ExceptionFlags|=(EXCEPTION_UNWINDING|EXCEPTION_EXIT_UNWIND);
context.ContextFlags =
(CONTEXT_i486 | CONTEXT_CO***OL | CONTEXT_INTEGER | CONTEXT_SEGMENTS);
RtlpCaptureContext( &am context );
context.E += 0x10;
context.Eax = _eax_value;
PEXCEPTION_REGISTRATION pExcptRegHead;
pExcptRegHead = RtlpGetRegistrationHead();
// Retrieve FS:[0]
// Begin traversing the list of EXCEPTION_REGISTRATION
( -1 != pExcptRegHead )
EXCEPTION_RECORD excptRec2;
( pExcptRegHead == pRegistrationFrame )
_NtContinue( &am context, 0 );
// If there's an exception frame, but it's lower on the stack
// then the head of the exception list, something's wrong!
( pRegistrationFrame &am am (pRegistrationFrame = pExcptRegHead) )
// Generate an exception to bail out
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
excptRec2.ExceptionCode = STATUS_INVALID_UNWIND_TARGET;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
_RtlRaiseException( &am exceptRec2 );
PVOID pStack = pExcptRegHead + 8;
// 8==sizeof(EXCEPTION_REGISTRATION)
(stackUserBase = pExcptRegHead )
// Make sure that
&am am (stackUserTop = pStack )
// pExcptRegHead is in
&am am (0 == (pExcptRegHead &am 3)) )
// range, and a multiple
// of 4 (i.e., sane)
DWORD pNewRegistHead;
DWORD retValue;
retValue = RtlpExecutehandlerForUnwind(
pExcptRec, pExcptRegHead, &am context,
&am NewRegistHead, pExceptRegHead-handler );
( retValue != DISPOSITION_CONTINUE_SEARCH )
( retValue != DISPOSITION_COLLIDED_UNWIND )
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
excptRec2.ExceptionCode = STATUS_INVALID_DISPOSITION;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
RtlRaiseException( &am excptRec2 );
pExcptRegHead = pNewRegistHead;
PEXCEPTION_REGISTRATION pCurrExcptReg = pExcptRegHead;
pExcptRegHead = pExcptRegHead-
RtlpUnlinkHandler( pCurrExcptReg );
// The stack looks goofy!
Raise an exception to bail out
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
excptRec2.ExceptionCode = STATUS_BAD_STACK;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
RtlRaiseException( &am excptRec2 );
// If we get here, we reached the end of the EXCEPTION_REGISTRATION list.
// This shouldn't ha en normally.
( -1 == pRegistrationFrame )
NtContinue( &am context, 0 );
NtRaiseException( pExcptRec, &am context, 0 );
PEXCEPTION_REGISTRATION
RtlpGetRegistrationHead(
FS:[0];
_RtlpUnlinkHandler( PEXCEPTION_REGISTRATION pRegistrationFrame )
FS:[0] = pRegistrationFrame-
_RtlpCaptureContext( CONTEXT * pContext )
pContext-Eax = 0;
pContext-Ecx = 0;
pContext-Edx = 0;
pContext-Ebx = 0;
pContext-Esi = 0;
pContext-Edi = 0;
pContext-SegCs = CS;
pContext-SegDs = DS;
pContext-SegEs = ES;
pContext-SegFs = FS;
pContext-SegGs = GS;
pContext-SegSs = SS;
pContext-EFlags = flag // __asm{ PUSHFD / pop [xxxxxxxx] }
pContext-Eip =
addre of the caller of the caller of
function
pContext-E = EBP of the caller of the caller of
function
pContext-E = Context.E + 8
While RtlUnwind looks imposing, it's not hard to understand if you methodically break it down. The API begi by retrieving the current top and bottom of the thread's stack from FS:[4] and FS:[8]. These values are important later as sanity checks to e ure that all of the exception frames being unwound fall within the stack region.
RtlUnwind next builds a dummy EXCEPTION_RECORD on the stack and sets the ExceptionCode field to STATUS_UNWIND. Also, the EXCEPTION_UNWINDING flag is set in the ExceptionFlags field of the EXCEPTION_RECORD. A pointer to this structure will later be pa ed as a parameter to each exception callback. Afterwards, the code calls the _RtlpCaptureContext function to create a dummy CONTEXT structure that also becomes a parameter for the unwind call of the exception callback.
The remainder of RtlUnwind traverses the linked list of EXCEPTION_REGISTRATION structures. For each frame, the code calls the RtlpExecuteHandlerForUnwind function, which I'll cover later. It's this function that calls the exception callback with the EXCEPTION_UNWINDING flag set. After each callback, the corre onding exception frame is removed by calling RtlpUnlinkHandler.
RtlUnwind sto unwinding frames when it gets to the frame with the addre that was pa ed in as the first parameter. Inter ersed with the code I've described is sanity-checking code to e ure that everything looks okay. If some sort of problem cro up, RtlUnwind raises an exception to indicate what the problem was, and this exception has the EXCEPTION_NONCONTINUABLE flag set. A proce i 't allowed to continue execution when this flag is set, so it must terminate. Unhandled Exceptio ====================
Earlier in the article, I put off a full description of the UnhandledExceptionFilter API. You normally don't call this API directly (although you can). Most of the time, it's invoked by the filter-expre ion code for KERNEL32's default exception callback. I showed this earlier in the eudocode for BaseProce Start.
shows my eudocode for UnhandledExceptionFilter. The API starts out a bit strangely (at least in my opinion). If the fault is an EXCEPTION_ACCESS_ VIOLATION, the code calls _BasepCheckForReadOnlyResource. While I haven't provided eudocode for this function, I can summarize it. If the exception occurred because a resource section (.rsrc) of an EXE or DLL was written to, _BasepCurrentTopLevelFilter changes the faulting page's attributes from its normal read-only state, thereby allowing the write to occur. If this particular scenario occurs, UnhandledExceptionFilter retur EXCEPTION_ CONTINUE_EXECUTION and execution restarts at the faulting i truction. UnHandledExceptionFilter Pseudocode
UnhandledExceptionFilter( STRUCT _EXCEPTION_POINTERS *pExceptionPtrs )
PEXCEPTION_RECORD pExcptRec;
DWORD currentESP;
DWORD retValue;
DWORD DEBUGPORT;
dwTemp2;
dwUseJustInTimeDebugger;
szDbgCmdFmt[256];
// Template string retrieved from AeDebug key
szDbgCmdLine[256];
// Actual debugger string after filling in
STARTUPINFO startupinfo;
PROCESS_INFORMATION pi;
HARDERR_STRUCT
harderr;
BOOL fAeDebugAuto;
TIB * pTi // Thread information block
pExcptRec = pExceptionPtrs-ExceptionRecord;
(pExcptRec-ExceptionCode == EXCEPTION_ACCESS_VIOLATION)
&am am (pExcptRec-ExceptionInformation[0]) )
retValue =
_BasepCheckForReadOnlyResource(pExcptRec-ExceptionInformation[1]);
( EXCEPTION_CONTINUE_EXECUTION == retValue )
EXCEPTION_CONTINUE_EXECUTION;
// See if this proce is being run under a debugger...
retValue = NtQueryInformationProce (GetCurrentProce (), Proce DebugPort,
&am debugPort,
(debugPort), 0 );
( (retValue = 0) &am am debugPort )
// Let debugger have it
EXCEPTION_CONTINUE_SEARCH;
// Did the user call SetUnhandledExceptionFilter?
If so, call their
// i talled proc now.
( _BasepCurrentTopLevelFilter )
retValue = _BasepCurrentTopLevelFilter( pExceptionPtrs );
( EXCEPTION_EXECUTE_HANDLER == retValue )
EXCEPTION_EXECUTE_HANDLER;
( EXCEPTION_CONTINUE_EXECUTION == retValue )
EXCEPTION_CONTINUE_EXECUTION;
// Only EXCEPTION_CONTINUE_SEARCH goes on from here
// Has SetErrorMode(SEM_NOGPFAULTERRORBOX) been called?
( 0 == (GetErrorMode() &am SEM_NOGPFAULTERRORBOX) )
harderr.elem0 = pExcptRec-ExceptionCode;
harderr.elem1 = pExcptRec-ExceptionAddre if
( EXCEPTION_IN_PAGE_ERROR == pExcptRec-ExceptionCode )
harderr.elem2 = pExcptRec-ExceptionInformation[2];
harderr.elem2 = pExcptRec-ExceptionInformation[0];
dwTemp2 = 1;
fAeDebugAuto = FALSE;
harderr.elem3 = pExcptRec-ExceptionInformation[1];
pTib = FS:[18h];
DWORD someVal = pTib-> Proce -0xC;
( pTib-threadID != someVal )
szDbgCmdFmt[256]
retValue = _GetProfileStringA(
AeDebug
Debugger
szDbgCmdFmt,
(szDbgCmdFmt)-1 );
( retValue )
dwTemp2 = 2;
szAuto[8]
retValue = GetProfileStringA(
AeDebug
szAuto,
(szAuto)-1 );
( retValue )
( 0 == strcmp( szAuto,
( 2 == dwTemp2 )
fAeDebugAuto = TRUE;
__except
( EXCEPTION_EXECUTE_HANDLER )
ESP = currentESP;
dwTemp2 = 1
fAeDebugAuto = FALSE;
( FALSE == fAeDebugAuto )
retValue =
NtRaiseHardError(
STATUS_UNHANDLED_EXCEPTION | 0x10000000,
4, 0, &am harderr,
_BasepAlreadyHadHardError ? 1 : dwTemp2,
&am dwUseJustInTimeDebugger );
dwUseJustInTimeDebugger = 3;
retValue = 0;
retValue = 0
&am am ( dwUseJustInTimeDebugger == 3)
&am am ( !_BasepAlreadyHadHardError )
&am am ( !_BaseRu ingInServerProce ) )
_BasepAlreadyHadHardError = 1;
SECURITY_ATTRIBUTES secAttr = {
(secAttr), 0, TRUE };
HANDLE hEvent = CreateEventA( &am ecAttr, TRUE, 0, 0 );
memset( &am tartupinfo, 0,
(startupinfo) ); rintf(szDbgCmdLine, szDbgCmdFmt, GetCurrentProce Id(), hEvent);
startupinfo.cb =
(startupinfo);
startupinfo.lpDesktop =
Wi ta0\Default
CsrIdentifyAlertableThread();
retValue = CreateProce A(
// lpA licationName
szDbgCmdLine,
// Command line
// proce , thread security attrs
// bInheritHandles
// creation flags, environment
// current directory.
&am tatupinfo,
// STARTUPINFO
&am i );
// PROCESS_INFORMATION
( retValue &am am hEvent )
NtWaitForSingleObject( hEvent, 1, 0 );
EXCEPTION_CONTINUE_SEARCH;
( _BasepAlreadyHadHardError )
NtTerminateProce (GetCurrentProce (), pExcptRec-ExceptionCode);
EXCEPTION_EXECUTE_HANDLER;
LPTOP_LEVEL_EXCEPTION_FILTER
SetUnhandledExceptionFilter(
LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter );
// _BasepCurrentTopLevelFilter is a KERNEL32.DLL global var
LPTOP_LEVEL_EXCEPTION_FILTER previous= _BasepCurrentTopLevelFilter;
// Set the new value
_BasepCurrentTopLevelFilter = lpTopLevelExceptionFilter;
previou // return the old value
The next task of UnhandledExceptionFilter is to determine if the proce is being run under a Win32 debugger. That is, the proce was created with the DEBUG_PROCESS or DEBUG_ONLY_THIS_PROCESS flag. UnhandledExceptionFilter uses the NtQueryInformationProce Function that I describe in this month's Under the Hood column to tell if the proce is being debugged. If so, the API retur EXCEPTION_CONTINUE_SEARCH, which tells some other part of the system to wake up the debugger proce and tell it that an exception occurred in the debuggee.
Next on UnhandledExceptionFilter's plate is a call to the user-i talled unhandled exception filter, if present. Normally, there i 't a user-i talled callback, but one can be i talled via the SetUnhandledExceptionFilter API. I've also provided eudocode for this API. The API simply bashes a global variable with the new user callback addre , and retur the value of the old callback.
With the preliminaries out of the way, UnhandledExceptionFilter can get down to its primary job: informing you of your ignominious programming blunder with the ever- stylish A lication Error dialog. There are two ways that this dialog can be avoided. The first is if the proce has called SetErrorMode and ecified the SEM_NOGPFAULTERRORBOX flag. The other method is to have the Auto value under the AeDebug registry key set to 1. In this case, UnhandledExceptionFilter ski the A lication Error dialog and automatically fires up whatever debugger is ecified in the Debugger value of the AeDebug key. If you're familiar with just in time debugging, this is where the operating system su orts it. More on this later.
In most cases, neither of these dialog avoidance conditio are true and UnhandledExceptionFilter calls the NtRaiseHardError function in NTDLL.DLL. It's this function that brings up the A lication Error dialog. This dialog waits for you to hit the OK button to terminate the proce , or Cancel to debug it. (Maybe it's just me, but hitting Cancel to launch a debugger seems a little backward.)
If you hit OK in the A lication Error dialog box, UnhandledExceptionFilter retur EXCEPTION_EXECUTE_HANDLER. The code that called UnhandledExceptionFilter usually re onds by terminating itself (as you saw in the BaseProce Start code). This brings up an interesting point. Most people a ume that the system terminates a proce with an unhandled exception. It's actually more correct to say that the system sets up things so that an unhandled exception causes the proce to terminate itself.
The truly interesting code in UnhandledExceptionFilter executes if you select Cancel in the A lication Error dialog, thereby bringing up a debugger on the faulting proce . The code first calls CreateEvent to make an event that the debugger will signal after it has attached to the faulting proce . This event handle, along with the current proce ID, is pa ed to rintf, which formats the command line used to start the debugger. Once everything is prepared, UnhandledExceptionFilter calls CreateProce to start the debugger. If CreateProce succeeds, the code calls NtWaitForSingleObject on the event created earlier. This call blocks until the debugger proce signals the event, indicating that it has attached to the faulting proce succe fully. There are other little bits and pieces to the UnhandledExceptionFilter code, but I've covered the important highlights here. Into the Inferno
===============
If you've made it this far, it wouldn't be fair to finish without completing the entire circuit. I've shown how the operating system calls a user-defined function when an exception occurs. I've shown what typically goes on i ide of those callbacks, and how compilers use them to implement _try and _catch. I've even shown what ha e when nobody handles the exception and the system has to do the mo ing up. All that remai is to show where the exception callbacks originate from in the first place. Yes, let's plunge into the bowels of the system and see the begi ing stages of the structured exception handling sequence.
shows some eudocode I whi ed up for KiUserExceptionDi atcher and some related functio . KiUserExceptionDi atcher is in NTDLL.DLL and is where execution begi after an exception occurs. To be 100 percent accurate, what I just said i 't exactly true. For i tance, in the Intel architecture an exception causes control to vector to a ring 0 (kernel mode) handler. The handler is defined by the interrupt descriptor table entry that corre onds to an exception. I'm going to skip all that kernel mode code and pretend that the CPU goes straight to KiUserExceptionDi atcher upon an exception KiUserExceptionDi atcher Pseudocode
KiUserExceptionDi atcher( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )
DWORD retValue;
// Note: If the exception is handled, RtlDi atchException() never retur if
( RtlDi atchException( pExceptRec, pContext ) )
retValue = NtContinue( pContext, 0 );
retValue = NtRaiseException( pExceptRec, pContext, 0 );
EXCEPTION_RECORD excptRec2;
excptRec2.ExceptionCode = retValue;
excptRec2.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.NumberParameters = 0;
RtlRaiseException( &am excptRec2 );
RtlDi atchException( PEXCEPTION_RECORD pExcptRec, CONTEXT * pContext )
stackUserBase;
stackUserTo PEXCEPTION_REGISTRATION pRegistrationFrame;
DWORD hLog;
// Get stack boundaries from FS:[4] and FS:[8]
RtlpGetStackLimits( &am tackUserBase, &am tackUserTop );
pRegistrationFrame = RtlpGetRegistrationHead();
( -1 != pRegistrationFrame )
PVOID justPastRegistrationFrame = &am RegistrationFrame + 8;
( stackUserBase justPastRegistrationFrame )
pExcptRec-ExceptionFlags |= EH_STACK_INVALID;
DISPOSITION_DISMISS;
( stackUsertop justPastRegistrationFrame )
pExcptRec-ExceptionFlags |= EH_STACK_INVALID;
DISPOSITION_DISMISS;
( pRegistrationFrame &am 3 )
// Make sure stack is DWORD aligned
pExcptRec-ExceptionFlags |= EH_STACK_INVALID;
DISPOSITION_DISMISS;
( someProce Flag )
// Doe 't seem to do a whole heck of a lot.
hLog = RtlpLogExceptionHandler( pExcptRec, pContext, 0,
pRegistrationFrame, 0x10 );
DWORD retValue, di atcherContext;
retValue= RtlpExecuteHandlerForException(pExcptRec, pRegistrationFrame,
pContext, &am di atcherContext,
pRegistrationFrame-handler );
// Doe 't seem to do a whole heck of a lot.
( someProce Flag )
RtlpLogLastExceptionDi osition( hLog, retValue );
( 0 == pRegistrationFrame )
pExcptRec-ExceptionFlags &am = ~EH_NESTED_CALL;
// Turn off flag
EXCEPTION_RECORD excptRec2;
DWORD yetAnotherValue = 0;
( DISPOSITION_DISMISS == retValue )
( pExcptRec-ExceptionFlags &am EH_NONCONTINUABLE )
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.ExceptionNumber = STATUS_NONCONTINUABLE_EXCEPTION;
excptRec2.ExceptionFlags = EH_NONCONTINUABLE;
excptRec2.NumberParameters = 0
RtlRaiseException( &am excptRec2 );
DISPOSITION_CONTINUE_SEARCH;
else if
( DISPOSITION_CONTINUE_SEARCH == retValue )
else if
( DISPOSITION_NESTED_EXCEPTION == retValue )
pExcptRec-ExceptionFlags |= EH_EXIT_UNWIND;
( di atcherContext yetAnotherValue )
yetAnotherValue = di atcherContext;
// DISPOSITION_COLLIDED_UNWIND
excptRec2.ExceptionRecord = pExcptRec;
excptRec2.ExceptionNumber = STATUS_INVALID_DISPOSITION;
excptRec2.ExceptionFlags = EH_NONCONTINUABLE;
excptRec2.NumberParameters = 0
RtlRaiseException( &am excptRec2 );
pRegistrationFrame = pRegistrationFrame-
// Go to previous frame
DISPOSITION_DISMISS;
_RtlpExecuteHandlerForException:
// Handles exception (first time through)
EDX,XXXXXXXX
ExecuteHandler
RtlpExecutehandlerForUnwind:
// Handles unwind (second time through)
EDX,XXXXXXXX
ExecuteHandler( PEXCEPTION_RECORD pExcptRec
PEXCEPTION_REGISTRATION pExcptReg
CONTEXT * pContext
PVOID pDi atcherContext,
FARPROC handler )
// Really a ptr to an _except_handler()
// Set up an EXCEPTION_REGISTRATION, where EDX points to the
// a ropriate handler code shown below
FS:[0],ESP
// Invoke the exception callback function
EAX = handler( pExcptRec, pExcptReg, pContext, pDi atcherContext );
// Remove the minimal EXCEPTION_REGISTRATION frame
ESP,DWORD PTR FS:[00000000]
DWORD PTR FS:[00000000]
Exception handler used
_RtlpExecuteHandlerForException:
// If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else
// a ign pDi atcher context and return DISPOSITION_NESTED_EXCEPTION
pExcptRec-ExceptionFlags &am EXCEPTION_UNWIND_CONTEXT
? DISPOSITION_CONTINUE_SEARCH
: *pDi atcherContext = pRegistrationFrame-> copetable,
DISPOSITION_NESTED_EXCEPTION;
Exception handler used
_RtlpExecuteHandlerForUnwind:
// If unwind flag set, return DISPOSITION_CONTINUE_SEARCH, else
// a ign pDi atcher context and return DISPOSITION_COLLIDED_UNWIND
pExcptRec-ExceptionFlags &am EXCEPTION_UNWIND_CONTEXT
? DISPOSITION_CONTINUE_SEARCH
: *pDi atcherContext = pRegistrationFrame-> copetable,
DISPOSITION_COLLIDED_UNWIND;
The heart of KiUserExceptionDi atcher is its call to RtlDi atchException. This kicks off the search for any registered exception handlers. If a handler handles the exception and continues execution, the call to RtlDi atchException never retur . If RtlDi atchException retur , there are two po ible paths: either NtContinue is called, which lets the proce continues, or another exception is raised. This time, the exception i 't continuable, and the proce must terminate.
Moving on to the RtlDi atchExceptionCode, this is where you'll find the exception frame walking code that I've referred to throughout this article. The function gra a pointer to the linked list of EXCEPTION_REGISTRATIONs and iterates over every node, looking for a handler. Because of the po ibility of stack corruption, the routine is very paranoid. Before calling the handler ecified in each EXCEPTION_REGISTRATION, the code e ures that the EXCEPTION_REGISTRATION is DWORD-aligned, within the thread's stack, and higher on the stack than the previous EXCEPTION_REGISTRATION.
RtlDi atchException doe 't directly call the addre ecified in the EXCEPTION_REGISTRATION structure. I tead, it calls RtlpExecuteHandlerForException to do the dirty work. Depending on what ha e i ide RtlpExecuteHandlerForException, RtlDi atchException either continues walking the exception frames or raises another exception. This secondary exception indicates that something went wrong i ide the exception callback and that execution can't continue.
The code for RtlpExecuteHandlerForException is closely related to another function, RtlpExecutehandlerForUnwind. You may recall that I mentioned this function earlier when I described unwinding. Both of these functio quot; simply load the EDX register with different values before sending control to the ExecuteHandler function. Put another way, RtlpExecuteHandlerForException and RtlpExecutehandlerForUnwind are separate front ends to a common function, ExecuteHandler.
ExecuteHandler is where the handler field from the EXCEPTION_REGISTRATION is extracted and called. Strange as it may seem, the call to the exception callback is itself wra ed by a structured exception handler. Using SEH within itself seems a bit funky but it makes se e if you ponder it for a moment. If an exception callback causes another exception, the operating system needs to know about it. Depending on whether the exception occurred during the initial callback or during the unwind callback, ExecuteHandler retur either DISPOSITION_NESTED_ EXCEPTION or DISPOSITION_COLLIDED_UNWIND. Both are basically Red Alert! Shut everything down now! kind of codes.
If you're like me, it's hard to keep all of the functio a ociated with SEH straight. Likewise, it's hard to remember who calls who. To help myself, I came up with the diagram shown in
. Who Calls Who in SEH
KiUserExceptionDi atcher()
RtlDi atchException()
RtlpExecuteHandlerForException()
ExecuteHandler()
// Normally goes to __except_handler3
---------
__except_handler3()
scopetable filter-expre ion()
__global_unwind2()
RtlUnwind()
RtlpExecuteHandlerForUnwind()
scopetable
__except
block()
Now, what's the deal with setting EDX before getting to the ExecuteHandler code? It's simple, really. ExecuteHandler uses whatever's in EDX as the raw exception handler if something goes wrong while calling the user-i talled handler. It pushes the EDX register onto the st