关于seh的那些事二(易语言的try..except模块化封装)

    之前我们已经说过了seh的一些事情,包括如何用seh构建易语言的try..except,这个是之前的代码

.版本 2

.子程序 子程序_模拟try

' _asm{
' pushad
' call my

' my:
' pop eax ;自定位
' lea ecx, dword [eax-6+safe]
' push ecx
' lea ecx,dword [eax-6+handle]
' push ecx
' push dword  [fs:0]
' mov  dword  [fs:0], esp

' xor eax,eax   ;这里随便写被保护的代码
' mov [eax],eax

' jmp safe

' handle:
' pushad

' mov edi,[ebp+10h]
' mov eax,[ebp+0ch]

' push dword [eax+8h]
' pop dword [edi+0B8h]

' popad
' mov eax,0
' ret 10h

' safe:
' mov esp, dword  [fs:0]
' pop dword  [fs:0]
' add esp,08h
' popad
' }

      这个代码是易语言内联汇编构造的try..except模型,但是我们看到了,如果要用这个模型,写要被保护的代码,必须用内联汇编代码.这样很不方便,我们学易语言,就是为了简单.在try..except模型保护下的代码还必须用汇编,那么显然,这个模型是还不够完善的,今天我们就来完善这个模型,让这个try..except模型能保护易语言的原生态代码.

      下面我还是给出易语言代码,然后再讲解一下.

.版本 2
.支持库 spec

.子程序 子程序_模拟try
.参数 参数一
.参数 参数二
.参数 参数三
.局部变量 局_变量一, 整数型

参数一 = 1
参数二 = 2
参数三 = 3

' _asm{
' pushad
' call my

' my:
' pop eax ;自定位
' lea ecx, dword [eax-6+safe]
' push ecx
' lea ecx,dword [eax-6+handle]
' push ecx
' push dword  [fs:0]
' mov  dword  [fs:0], esp


' jmp bottom

' handle:
' pushad

' mov edi,[ebp+10h]
' mov eax,[ebp+0ch]

' push dword [eax+8h]
' pop dword [edi+0B8h]

' popad
' mov eax,0
' ret 10h

' safe:
' mov esp, dword  [fs:0]
' pop dword  [fs:0]
' add esp,08h
' popad
' mov esp,ebp
' pop ebp
' ret 0ch

' bottom:
' }

调试输出 (“参数一是” + 到文本 (参数一))
调试输出 (“参数二是” + 到文本 (参数二))
调试输出 (“参数三是” + 到文本 (参数三))
局_变量一 = 局_变量一 + 4
调试输出 (“局_变量一是” + 到文本 (局_变量一))
写到内存 (4, 0, 1)

大部分跟昨天一样,只有少量代码做了一些改变,我们重点说下改变的位置.

昨天在' jmp bottom的位置,我们是内联汇编写入的需要保护的代码,昨天在这个位置我们内联汇编构造了一个异常,今天,我们就不直接内联汇编写异常代码了,直接跳到内联汇编的尾部,我们在内联汇编尾部写易语言原生态代码,这样jmp bottom 就跳到了原生态代码处,我们就保护了原生态代码.

另一个位置在safe标号位置,我们在内联代码处增加了三行代码

' mov esp,ebp
' pop ebp
' ret 0ch

这里模拟的就是易语言的函数尾

为什么要模拟易语言的函数尾呢,这里我们假设,如果易语言原生态代码出现异常,比如今天我们就用写到内存(4,0,1)构造了一个异常,那么抛出异常来到了safe安全位置,safe安全位置首先卸载seh,然后平衡堆栈,如果不模拟易语言函数尾,那么他下面继续走,会执行什么?当然是会执行下面的代码.也就是继续执行原生态代码这一段

调试输出 (“参数一是” + 到文本 (参数一))
调试输出 (“参数二是” + 到文本 (参数二))
调试输出 (“参数三是” + 到文本 (参数三))
局_变量一 = 局_变量一 + 4
调试输出 (“局_变量一是” + 到文本 (局_变量一))
写到内存 (4, 0, 1)

那么很显然,写到内存(4,0,1)又是这个异常,这次不好运了,我们连seh都卸了,就会弹出错误,所以我们要执行到safe安全位置后,平衡堆栈,然后模拟易语言函数的尾部,返回,不再执行下面代码.这里要重点说明下,易语言函数的调用约定是stdcall调用方式.也就是函数尾部自己平衡堆栈,我这里写了个简单的测试程序

.版本 2

.子程序 A

B (1)
' _asm{
' mov eax,eax
' mov eax,eax
' mov eax,eax
' mov eax,eax
' mov eax,eax
' }


.子程序 B, 整数型, , 参看B的调用约定
.参数 C

返回 (C)

反汇编工具查看了下

00AD04D6 - 55                         - push ebp
00AD04D7 - 8b ec                      - mov ebp,esp
00AD04D9 - 68 01 00 00 00             - push 00000001 参数=1
00AD04DE - e8 0e 00 00 00             - call 00ad04f1     B程序CALL
00AD04E3 - 89 c0                      - mov eax,eax
00AD04E5 - 89 c0                      - mov eax,eax
00AD04E7 - 89 c0                      - mov eax,eax
00AD04E9 - 89 c0                      - mov eax,eax
00AD04EB - 89 c0                      - mov eax,eax
00AD04ED - 8b e5                      - mov esp,ebp
00AD04EF - 5d                         - pop ebp
00AD04F0 - c3                         - ret
00AD04F1 - 55                         - push ebp
00AD04F2 - 8b ec                      - mov ebp,esp
00AD04F4 - 8b 45 08                   - mov eax,[ebp+08]
00AD04F7 - e9 00 00 00 00             - jmp 00ad04fc
00AD04FC - 8b e5                      - mov esp,ebp
00AD04FE - 5d                         - pop ebp
00AD04FF - c2 04 00                   - ret 0004     自己平衡了堆栈

这里证明了 确实易语言函数的调用约定确实是stdcall方式,所以我们模拟易语言函数尾就要自己平衡堆栈,我们模拟程序是三个参数,所以自己平衡堆栈是ret 0ch,如果大家用这个try..except模型保护代码,一定要注意被保护函数的参数个数.

下面有同学要问了,假如我的被保护的原生态易代码,没有异常,也就是顺利执行,那么他的流程大概是这样的:

' _asm{
' pushad
' call my

' my:
' pop eax ;自定位
' lea ecx, dword [eax-6+safe]
' push ecx
' lea ecx,dword [eax-6+handle]
' push ecx
' push dword  [fs:0]
' mov  dword  [fs:0], esp


' jmp bottom

bottom:

调试输出 (“参数一是” + 到文本 (参数一))
调试输出 (“参数二是” + 到文本 (参数二))
调试输出 (“参数三是” + 到文本 (参数三))
局_变量一 = 局_变量一 + 4
调试输出 (“局_变量一是” + 到文本 (局_变量一))

你昨天都说过,注册seh,用了三个push,如果发生异常,被异常处理函数处理,来到安全位置,用pop和add esp,08平衡了堆栈,但是现在你的原生态易语言代码是安全的,那你怎么平衡堆栈?

我的回答是,用易语言自己的函数尾平衡堆栈.下面我来详细说明下,为什么可以用易语言自己的函数尾平衡堆栈:

易语言函数头是

push ebp

mov ebp,esp

这个是易语言的函数头,通过函数头进入函数后,保存了调用函数方的ebp,然后esp=ebp,也就是经过函数头以后,[ebp]等于上一个ebp,[ebp+4]等于返回地址[ebp+08h]等于参数一,如果还有局部变量的话,比如,如果有个一个局部变量,就是sub,esp 4.有二个局部变量,就是sub,esp 8,经过易语言函数头初始化以后,才进入到自己写代码中,所以在易语言函数里,是用ebp寻址,寻参的话是[ebp+8]开始,寻局部变量是[ebp-4]开始,所以用ebp不能被破坏,esp在经过函数头以后,做的别的事情是不会破坏堆栈的,只要在返回的时候,把esp地址找回来就行了,这里我要说的是,一定要经过易语言函数头后,esp才能做点别的事情.

那么我们看下易语言函数尾:一般是这样二句

mov esp,ebp

pop ebp

这里mov esp,ebp把ebp=esp,对应的是之前函数头的mov ebp,esp,终于,拿了我的给我还回来,吃了我的给我吐出来了.

然后现在[esp]=调用函数方的ebp值,[esp+4]=等于返回地址

下一个pop ebp

把调用函数方的ebp值放入ebp,然后现在[esp]等于返回地址了.

     这样就完美的走完了整个调用过程,长路漫漫,他的使命终于完成了,不管他是完美的走过,还是出现了那么小丁点的错误,但总归,找到了来时的路.我们表示祝贺已及最真挚的慰问.

   

源代码:http://files.cnblogs.com/qq32175822/2.rar

原文地址:https://www.cnblogs.com/qq32175822/p/3499478.html