SEH的强大功能之二

180306 逆向-反调试技术(1)BeingDebugged 1625-5 王子昂 总结《2018年3月6日》 【连续第521天总结】 A. 反调试技术(1) B. BeingDebugged Win32API为程序提供了IsDebuggerPresent来判断自己是否处于调试状态,懒惰的程序员使用这个API来自欺欺人 实现源码为: IsDebuggerPresent(VOID) { return NtCurrentPeb(... 阅读详情

转载自-http://blog.programfan.com/article.asp?id=9839

上一篇文章讲述了SEH的异常处理机制,也即try-except模型的使用规则。本篇文章继续探讨SEH另外一项很重要的机制,那就是“有效保证资源的清除”,其实这才是SEH设计上最为精华的一个东东,对于C程序而言,它贡献简直是太大了。

  SEH的这项机制被称为结束处理(Termination Handling),它是通过try-finally语句来实现的,下面开始讨论吧!

try-finally的作用


  对于try-finally的作用,还是先看看MSDN中怎么说的吧!摘略如下:

The try-finally statement is a Microsoft extension to the C and C++ languages that enables 32-bit target applications to guarantee execution of cleanup code when execution of a block of code is interrupted. Cleanup consists of such tasks as deallocating memory, closing files, and releasing file handles. The try-finally statement is especially useful for routines that have several places where a check is made for an error that could cause premature return from the routine.

  上面的这段话的内容翻译如下:

  try-finally语句是Microsoft对C和C++语言的扩展,它能使32位的目标程序在异常出现时,有效保证一些资源能够被及时清除,这些资源的清除任务可以包括例如内存的释放,文件的关闭,文件句柄的释放等等。try-finally语句特别适合这样的情况下使用,例如一个例程(函数)中,有几个地方需要检测一个错误,并且在错误出现时,函数可能提前返回。

try-finally的语法规则

   上面描述try-finally机制的有关作用时,也许一时我们还难以全面理解,不过没关系,这里还是先看一下try-finally的语法规则吧!其实它很简单,示例代码如下:

//seh-test.c
#include <windows.h>
#include <stdio.h>

void main()
{
puts("hello");
__try
{
puts("__try块中");
}
// 注意,这里不是__except块,而是__finally取代
__finally
{
puts("__finally块中");
}

puts("world");
}

上面的程序运行结果如下:
hello
__try块中
__finally块中
world
Press any key to continue

  try-finally语句的语法与try-except很类似,稍有不同的是,__finally后面没有一个表达式,这是因为try-finally语句的作用不是用于异常处理,所以它不需要一个表达式来判断当前异常错误的种类。另外,与try-except语句类似,try-finally也可以是多层嵌套的,并且一个函数内可以有多个try-finally语句,不管它是嵌套的,或是平行的。当然,try-finally多层嵌套也可以是跨函数的。这里不一一列出示例,大家可以自己测试一番。
另外,对于上面示例程序的运行结果,是不是觉得有点意料之外呢?因为__finally块中的put(“__finally块中”)语句也被执行了。是的,没错!这就是try-finally语句最具有魔幻能力的地方,即“不管在何种情况下,在离开当前的作用域时,finally块区域内的代码都将会被执行到”。呵呵!这的确是很厉害吧!为了验证这条规则,下面来看一个更典型示例,代码如下:

#include <stdio.h>

void main()
{
puts("hello");
__try
{
puts("__try块中");

// 注意,下面return语句直接让函数返回了
return;
}
__finally
{
puts("__finally块中");
}

puts("world");
}

上面的程序运行结果如下:
hello
__try块中
__finally块中
Press any key to continue

  上面的程序运行结果是不是有点意思。在__try块区域中,有一条return语句让函数直接返回了,所以后面的put(“world”)语句没有被执行到,这是很容易被理解的。但是请注意,__finally块区域中的代码也将会被予以执行过了,这是不是进一步验证了上面了那条规则,呵呵!阿愚深有感触的想:“__finally的特性真的很像对象的析构函数”,朋友们觉得如何呢?

   另外,大家也许还特别关心的是,goto语句是不是有可能破坏上面这条规则呢?因为在C语言中,goto语句一般直接对应一条jmp跳转指令,所以如果真的如此的话,那么goto语句很容易破坏上面这条规则。还是看一个具体的例子吧!

#include <stdio.h>

void main()
{
puts("hello");
__try
{
puts("__try块中");

// 跳转指令
goto RETURN;
}
__finally
{
puts("__finally块中");
}

RETURN:
puts("world");
}

上面的程序运行结果如下:
hello
__try块中
__finally块中
world
Press any key to continue

  呵呵!即便上面的示例程序中,goto语句跳过了__finally块,但是__finally块区域中的代码还是被予以执行了。当然,大家也许很关心这到底是为什么?为什么try-finally语句具有如此神奇的功能?这里不打算深入阐述,在后面阐述SEH实现的时候会详细分析到。这里朋友们只牢记一点,“不管是顺序的线性执行,还是return语句或goto语句无条件跳转等情
况下,一旦执行流在离开当前的作用域时,finally块区域内的代码必将会被执行”

try-finally块中的异常


  上面只列举了return语句和goto语句的情况下,但是如果程序中出现异常的话,那么finally块区域内的代码还会被执行吗?上面所讲到的那条规则仍然正确吗?还是看看示例,代码如下:

#include <stdio.h>

void test()
{
puts("hello");
__try
{
int* p;
puts("__try块中");

// 下面抛出一个异常
p = 0;
*p = 25;
}
__finally
{
// 这里会被执行吗
puts("__finally块中");
}

puts("world");
}

void main()
{
__try
{
test();
}
__except(1)
{
puts("__except块中");
}
}

上面的程序运行结果如下:
hello
__try块中
__finally块中
__except块中
Press any key to continue

  从上面示例程序的运行结果来看,它是和“不管在何种情况下,在离开当前的作用域时,finally块区域内的代码都将会被执行到”这条规则相一致的。

__leave关键字的作用

  其实,总结上面的__finally块被执行的流程时,无外乎三种情况。第一种就是顺序执行到__finally块区域内的代码,这种情况很简单,容易理解;第二种就是goto语句或return语句引发的程序控制流离开当前__try块作用域时,系统自动完成对__finally块代码的调用;第三种就是由于在__try块中出现异常时,导致程序控制流离开当前__try块作用域,这种情况下也是由系统自动完成对__finally块的调用。无论是第2种,还是第3种情况,毫无疑问,它们都会引起很大的系统开销,编译器在编译此类程序代码时,它会为这两种情况准备很多的额外代码。一般第2种情况,被称为“局部展开(LocalUnwinding)”;第3种情况,被称为“全局展开(GlobalUnwinding)”。在后面阐述SEH实现的时候会详细分析到这一点。
第3种情况,也即由于出现异常而导致的“全局展开”,对于程序员而言,这也许是无法避免的,因为你在利用异常处理机制提高程序可靠健壮性的同时,不可避免的会引起性能上其它的一些开销。呵呵!这世界其实也算瞒公平的,有得必有失。

  但是,对于第2种情况,程序员完全可以有效地避免它,避免“局部展开”引起的不必要的额外开销。实际这也是与结构化程序设计思想相一致的,也即一个程序模块应该只有一个入口和一个出口,程序模块内尽量避免使用goto语句等。但是,话虽如此,有时为了提高程序的可读性,程序员在编写代码时,有时可能不得不采用一些与结构化程序设计思想相悖的做法,例如,在一个函数中,可能有多处的return语句。针对这种情况,SEH提供了一种非常有效的折衷方案,那就是__leave关键字所起的作用,它既具有像goto语句和return语句那样类似的作用(由于检测到某个程序运行中的错误,需要马上离开当前的__try块作用域),但是又避免了“局部展开” 的额外开销。还是看个例子吧!代码如下:

#include <stdio.h>

void test()
{
puts("hello");
__try
{
int* p;
puts("__try块中");

// 直接跳出当前的__try作用域
__leave;
p = 0;
*p = 25;
}
__finally
{
// 这里会被执行吗?当然
puts("__finally块中");
}

puts("world");
}

void main()
{
__try
{
test();
}
__except(1)
{
puts("__except块中");
}
}

上面的程序运行结果如下:
hello
__try块中
__finally块中
world
Press any key to continue

  这就是__leave关键字的作用,也许大家在编程时很少使用它。但是请注意,如果你的程序中,尤其在那些业务特别复杂的函数模块中,既采用了SEH机制来保证程序的可靠性,同时代码中又拥有大量的goto语句和return语句的话,那么你的源代码编译出来的二进制程序将是十分糟糕的,不仅十分庞大,而且效率也受很大影响。此时,建议不妨多用__leave关键字来提高程序的性能。

try-finally深入


  现在,相信我们已经对try-finally机制有了非常全面的了解,为了更进一步认识try-finally机制的好处(当然,主人公阿愚认为,那些写过Windows平台下设备驱动程序的朋友一定深刻认识到try-finally机制的重要性),这里给出一个具体的例子。还记得,在《第21集 Windows系列操作系统平台中所提供的异常处理机制》中,所讲述到的采用setjmp和longjmp异常处理机制实现的那个简单例程吗?现在如果有了try-finally机制,将能够很容易地来避免内存资源的泄漏,而且还极大地提高了程序模块的可读性,减少程序员由于不小心造成的程序bug等隐患。采用SEH重新实现的代码如下:

#include <stdio.h>
#include <stdlib.h>

void test1()
{
char* p1, *p2, *p3, *p4;

__try
{
p1 = malloc(10);
p2 = malloc(10);
p3 = malloc(10);
p4 = malloc(10);

// do other job
// 期间可能抛出异常
}
__finally
{
// 这里保证所有资源被及时释放
if(p1) free(p1);
if(p2) free(p2);
if(p3) free(p3);
if(p4) free(p4);
}
}

void test()
{
char* p;

__try
{
p = malloc(10);

// do other job
// 期间可能抛出异常

test1();

// do other job
}
__finally
{
// 这里保证资源被释放
if(p) free(p);
}
}

void main( void )
{
__try
{
char* p;

__try
{
p = malloc(10);

// do other job

// 期间可能抛出异常
test();

// do other job
}
__finally
{
// 这里保证资源被释放
if(p) free(p);
}
}
__except(1)
{
printf("捕获到一个异常/n");
}
}

  呵呵!上面的代码与采用setjmp和longjmp机制实现的代码相比,是不是更简洁,更美观。这就是try-finally语句的贡献所在。

总结

   (1) “不管在何种情况下,在离开当前的作用域时,finally块区域内的代码都将会被执行到”,这是核心法则。

   (2) try-finally语句的作用相当于面向对象中的析构函数。

   (3) goto语句和return语句,在其它少数情况下,break语句以及continue语句等,它们都可能会导致程序的控制流非正常顺序地离开__try作用域,此时会发生SEH的“局部展开”。记住,“局部展开”会带来较大的开销,因此,程序员应该尽可能采用__leave关键字来减少一些不必要的额外开销。

  通过这几篇文章中对SEH异常处理机制的深入阐述,相信大家已经能够非常熟悉使用SEH来进行编程了。下一篇文章把try-except和try-finally机制结合起来,进行一个全面而综合的评述,继续吧!

ASP.NET MVC登陆后跳转到原页面 ASP.NET MVC登陆后跳转到原页面的方法 一、功能描述: 当打开一个web页面,该页面需要登陆时,跳转到登陆页面; 登陆页面验证成功之后,再自动跳转到将要打开的web页面; 图示: 二、解决方案 思路: 1)、构造一个抽象过滤控制器,该控制器用于判断用户是否已登陆和登陆后跳转到原页面,所有需要登陆的页面均继承该控制器, 该控制器跳转登陆页面时,会把原页面的地址值作为参数传入到登陆页... 阅读详情

相关推荐

VC Spyglass调试秘籍:从Lint报错到代码修复的完整工作流

本文详细介绍了VC Spyglass调试的完整工作流,重点解析了如何高效定位和修复Lint检查中常见的XMRE-L跨模块引用错误。通过对比GUI与命令行(vc_static_shell)双模式调试技巧,并结合TCL脚本实现批量处理与规则豁免,帮助数字设计工程师系统化地提升代码调试效率与质量。

weixin_29246195的博客 519

【ASP.NET MVC4】第一课:初识ASP.NET MVC,创建第一个ASP.NET MVC项目

知识点:理解ASP.NET MVC

王迪 1661

DAP-Link DIY复刻指南

DAP-Link是ARM开源的一个项目,主要用于代码下载以及调试,支持SWD和JTAG调试接口,对于开发嵌入式的同学相信都或多或少有所耳闻。除了DAP-Link之外还有很多同类型的调试器,如ST-LinkJ-Link,但是这些都是不开源的,无法复刻或进行二次开发。但DAP-Link不同,完全开源,不用担心侵权问题,且随着开源社区大家的共同努力,DAP-Link经过了不断的迭代,先如今已取代ARM之前推出的CMSIS-DAP开源项目。

爱出名的狗腿子 1万+

windows SEH读书笔记

结构化异常SEH包括两个部分:终止处理和异常处理1.异常终止处理程序语法:__try/__finally1)从try块到finally的正常代码控制流 2)局部展开:从try块中的提前退出(由goto、longjump、continue、break、return等语句引起)将呈现控制流转入finally块。 3)全局展开:找try块对应except,从最里层找,没有则着上层,找到except进入

undeadyql的专栏 356

Windows SEH中的局部展开与全局展开

<br />经过阅读windows核心编程一书,对SEH中的局部展开和全局展开终于有了一个比较清晰的概念。<br />先说说__finally块的语义,__finally块语义就是只要是指令流将要流出其对应的__try块时,则__finally块中的代码将得到执行。那么所谓展开就是保证__finally块中的代码得到执行。下面详细解释下局部展开和全局展开<br />。<br /> <br />局部展开就是指某个函数中的__try块中的代码因为执行了return,break,goto,continue等指令使

2459

!teb 调试

Sometimes there is no private PDB file available for a module in a crash dump although we know they exist for different versions of the same module. The typical example is when we have a public PDB fi

ilzx53xx0的博客 1409

SEH链和展开操作

每次我们定义了一个新的SEH异常处理回调函数,EXCEPTION_REGISTRATION结构的prev字段都被要求填写上一个EXCEPTION_REGISTRATION结构的地址,随着应用程序对模块的调用一层层深入下去的时候,那么最后回调函数会形成一个SEH链   当程序中有多个线程在运行的时候,每个线程中都会存在各自的SEH链,这些SEH链中指定了多个回调函数,除他们以外,系统中可能还会存

bingghost 3831

第二篇:基于ASP.NET MVC2 创建第一个简单页面

摘要       本文首先一步一步完成Demo 的第一个页面—— 首页。然后根据实现过程,说明一下其中用到的与ASP.NET MVC 相关的概念与原理。让第一个页面跑起来       现在,我们来实现公告系统中的第一个页面—— 首页。它非常简单,只包括所有公告分类的列表,并且每个列表项是一个超链接。其中分类数据是用我们的Mock 组件得到的。实现后界面如下:         在开始之前...

dzg6200968的博客 249

第二篇:基于ASP.NET MVC2 创建第一个简单页面

  摘要       本文首先一步一步完成Demo 的第一个页面—— 首页。然后根据实现过程,说明一下其中用到的与ASP.NET MVC 相关的概念与原理。 让第一个页面跑起来       现在,我们来实现公告系统中的第一个页面—— 首页。它非常简单,只包括所有公告分类的列表,并且每个列表项是一个超链接。其中分类数据是用我们的Mock 组件得到的。...

ljl_xyf的专栏 247

【ASP.NET】手把手创建一个ASP.NET项目以及MVC模式的介绍

本文演示如何在 Visual Studio 工具上创建一个 ASP.NET 项目以及 MVC 模式下的项目文件的介绍。相信大家看完就会对 ASP.NET 框架有一个初步的认识。这部分介绍 ASP.NET 项目 MVC 模式下的文件信息 以上就是 ASP.NET项目的创建演示和MVC模式的介绍,希望对大家有所帮助。

好学若饥,谦卑若愚。 1万+

【ASP.NET学习】ASP.NET MVC基本编程

在 Web.config 文件中,可以定义与应用程序有关的各种设置,包括连接字符串、验证模式、HTTP 模块、错误处理、会话状态、缓存设置等等。在这个文件夹里存放的是用于呈现数据模型的视图页面,可以包含 HTML、CSS、JavaScript等内容,用于呈现和渲染用户界面。由于常用字体可能会在用户的系统中缺少,因此,网站通常会将自定义字体文件包括在 Fonts 文件夹中,以确保字体在用户设备中正确地显示。它是轻量级的、可测试性高的框架,同时整合了所有已有的 ASP.NET 特性,比如母版页、安全性和认证。

杰西笔记 1819

ASP.NET Core Razor页面 vs MVC

作为.NET Core 2.0发行版的一部分,还有一些ASP.NET的更新。其中之一是添加了一个新的Web框架来创建“页面”,而不需要复杂的ASP.NET MVC。新的Razor页面是一个比较简单的MVC框架版本,在某些方面是老的“.aspx” WebForms的演变。 在本文中,我们将介绍使用ASP.NET Razor页面和MVC的一些细节。 Razor页面基础知识 ASP.NET MV

kof2019的博客 2350

ASP.NET——MVC编程

如何使用 C# 中的 MVC 模式去开发一个简单的 Web 应用程序,将业务逻辑、用户界面和请求处理清晰地分离,提高开发效率和代码的可维护性。根据需要,可以进一步扩展和优化该示例,添加更多的功能和页面。

2401_84642440的博客 2337

C#、ASP.NET、ASP.NET MVC、ASP.NET web form、asp.net core mvc的区别。哦我的上帝啊!这可真糟糕

微软的命名很糟糕。技术上有两个框架:ASP.NET和ASP.NET Core,它们分别基于.NET Framework和.NET Core构建。 当Microsoft首次尝试创建一个遵循MVC模式的“现代”Web应用程序平台时,它将这个新平台称为“ASP.NET MVC”,以区别于以前的ASP.NET应用程序(主要是Web Forms)。然后,为了更加混淆事情,他们后来添加了“ASP.NET We...

菜鸟变凤凰 2431

ASP.NET MVC框架学习#1:创建第一个MVC项目

前言 因为工作需要,开始接触 ASP.NET MVC 框架,对于我来说是一个全新的东西,需要从最基本的概念开始学习,特此记录学习过程中的各种心得体会,方便以后回忆。 什么是MVC? MVC由三个部分组成,即Model,View和Controller。 Model:负责整个系统的逻辑运算,数据库定义,数据处理等 View:负责所有和视图有关的部分。 Controller:从model取得数据,并决定...

founderznd 1131

一文讲懂C#、ASP.NET、ASP.NET MVC、ASP.NET web form、asp.net core mvc的区别

一文讲懂C#、ASP.NET、ASP.NET MVC、ASP.NET web form、asp.net core mvc的区别

Blue 4481

创建ASP.NET MVC5 应用程序

创建第一个ASP.NET MVC项目。使用Visual Studio 2017/2019创建ASP.NET MVC5应用程序,要求如下: (1)选择“MVC”模板,创建ASP.NET MVC应用程序。 (2)分别修改主页、关于我们及联系方式页面内容。 页面效果如图所示: 首页效果 关于我们效果 联系方式效果 实践思路: 选择菜单“文件”→“新建”→“项目”,在弹出的“新建项目”对话框选择“ASP.NET Web应用程序(.NET Framework)”。 在“新建ASP.NET Web应用程序”对话框选择

m0_47675090的博客 5944

VSCode创建第一个ASP.NET Core MVC程序

根据微软教程,在VSCode中创建第一个ASP.NET Core MVC项目

gc_2299的博客 1922

创建一个ASP.NET MVC 5项目

创建第一个MVC项目 选择] ASP.NET Web应用程序(.NET Framework) 选择一下创建的路径 选择MVC 到这里恭喜你成功的创建了你的MVC项目

Ares0的博客 3518

芝柯CC3通用版刷机包

芝柯CC3通用版刷机包

上一篇: SEH的强大功能之一
下一篇: SEH的综合
hbrr224
博客等级 码龄21年 9粉丝 17原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值