Skip to content

Commit

Permalink
Site updated: 2023-11-27 16:36:26
Browse files Browse the repository at this point in the history
  • Loading branch information
YunZh1Jun committed Nov 27, 2023
1 parent fdff5d9 commit 4434b1b
Show file tree
Hide file tree
Showing 3 changed files with 2 additions and 2 deletions.
4 changes: 2 additions & 2 deletions 2023/11/27/WindowsSEH2/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ <h2 id="再分析"><a href="#再分析" class="headerlink" title="再分析"></a
<h2 id="展开-unwind"><a href="#展开-unwind" class="headerlink" title="展开(unwind)"></a>展开(unwind)</h2><p>如果运行这个程序,会发现输出有些奇怪。看起来_except_handler调用了两次,这是为什么?</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Home Grown handler: Exception Code: C0000005 Exception Flags 0</span><br><span class="line">Home Grown handler: Exception Code: C0000027 Exception Flags 2 EH_UNWINDING</span><br><span class="line">Caught the Exception in main()</span><br></pre></td></tr></table></figure>

<p>两次异常标志不同,第二次的异常标志是2,而这是由于**展开(unwind)**。<br><strong>当一个异常处理回调函数拒绝处理某个异常时,它会被再一次调用。</strong><br><strong>当异常发生时,系统遍历EXCEPTION_REGISTRATION结构链表,直到它找到一个处理这个异常的处理程序。一旦找到,系统就再次遍历这个链表,直到处理这个异常的结点为止。在这第二次遍历中,系统将再次调用每个异常处理函数。关键的区别是,在第二次调用中,异常标志被设置为2。这个值被定义为EH_UNWINDING。</strong><br>为何要这样设置(即调用两次未处理异常的函数)呢?这是为了给这个函数最后一个清理的机会。一个绝好的例子是C++类的析构函数。当一个函数的异常处理程序拒绝处理某个异常时,通常执行流程并不会正常地从那个函数退出。现在,想像一个定义了一个C++类的实例作为局部变量的函数。C++规范规定析构函数必须被调用。这带EH_UNWINDING标志的第二次回调就给这个函数一个机会去做一些类似于调用析构函数和__finally块之类的清理工作。<br>在异常已经被处理完毕,并且所有前面的异常帧都已经被展开之后,流程从处理异常的那个回调函数决定的地方开始继续执行。一定要记住,仅仅把指令指针设置到所需的代码处就开始执行是不行的。流程恢复执行处的代码的堆栈指针和栈帧指针(在Intel CPU上是ESP和EBP)也必须被恢复成它们在处理这个异常的函数的栈帧上的值。因此,这个处理异常的回调函数必须负责把堆栈指针和栈帧指针恢复成它们在包含处理这个异常的SEH代码的函数的堆栈上的值。<br>通常,展开操作导致堆栈上处理异常的帧以下的堆栈区域上的所有内容都被移除了,就好像我们从来没有调用过这些函数一样。展开的另外一个效果就是 EXCEPTION_REGISTRATION结构链表上处理异常的那个结构之前的所有EXCEPTION_REGISTRATION结构都被移除了。这很好理解,因为这些EXCEPTION_REGISTRATION结构通常都被创建在堆栈上。在异常被处理后,堆栈指针和栈帧指针在内存中比那些从 EXCEPTION_REGISTRATION结构链表上移除的EXCEPTION_REGISTRATION结构高。<br>链表的最后一个节点是操作系统提供的默认异常处理函数,它总是会选择处理异常。这个函数是在用户代码执行前插入的。下为BaseProcessStart函数写的伪代码,它是Windows NT KERNEL32.DLL的一个内部例程。这个函数带一个参数——线程入口点函数的地址。BaseProcessStart运行在新进程的环境中,并且它调用这个进程的第一个线程的入口点函数。</p>
<p>两次异常标志不同,第二次的异常标志是2,而这是由于 <strong>展开(unwind)</strong><br><strong>当一个异常处理回调函数拒绝处理某个异常时,它会被再一次调用。</strong><br><strong>当异常发生时,系统遍历EXCEPTION_REGISTRATION结构链表,直到它找到一个处理这个异常的处理程序。一旦找到,系统就再次遍历这个链表,直到处理这个异常的结点为止。在这第二次遍历中,系统将再次调用每个异常处理函数。关键的区别是,在第二次调用中,异常标志被设置为2。这个值被定义为EH_UNWINDING。</strong><br>为何要这样设置(即调用两次未处理异常的函数)呢?这是为了给这个函数最后一个清理的机会。一个绝好的例子是C++类的析构函数。当一个函数的异常处理程序拒绝处理某个异常时,通常执行流程并不会正常地从那个函数退出。现在,想像一个定义了一个C++类的实例作为局部变量的函数。C++规范规定析构函数必须被调用。这带EH_UNWINDING标志的第二次回调就给这个函数一个机会去做一些类似于调用析构函数和__finally块之类的清理工作。<br>在异常已经被处理完毕,并且所有前面的异常帧都已经被展开之后,流程从处理异常的那个回调函数决定的地方开始继续执行。一定要记住,仅仅把指令指针设置到所需的代码处就开始执行是不行的。流程恢复执行处的代码的堆栈指针和栈帧指针(在Intel CPU上是ESP和EBP)也必须被恢复成它们在处理这个异常的函数的栈帧上的值。因此,这个处理异常的回调函数必须负责把堆栈指针和栈帧指针恢复成它们在包含处理这个异常的SEH代码的函数的堆栈上的值。<br>通常,展开操作导致堆栈上处理异常的帧以下的堆栈区域上的所有内容都被移除了,就好像我们从来没有调用过这些函数一样。展开的另外一个效果就是 EXCEPTION_REGISTRATION结构链表上处理异常的那个结构之前的所有EXCEPTION_REGISTRATION结构都被移除了。这很好理解,因为这些EXCEPTION_REGISTRATION结构通常都被创建在堆栈上。在异常被处理后,堆栈指针和栈帧指针在内存中比那些从 EXCEPTION_REGISTRATION结构链表上移除的EXCEPTION_REGISTRATION结构高。<br>链表的最后一个节点是操作系统提供的默认异常处理函数,它总是会选择处理异常。这个函数是在用户代码执行前插入的。下为BaseProcessStart函数写的伪代码,它是Windows NT KERNEL32.DLL的一个内部例程。这个函数带一个参数——线程入口点函数的地址。BaseProcessStart运行在新进程的环境中,并且它调用这个进程的第一个线程的入口点函数。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line">BaseProcessStart(PVOID lpfnEntryPoint)</span><br><span class="line">&#123;</span><br><span class="line"> DWORD retValue;</span><br><span class="line"> DWORD currentESP;</span><br><span class="line"> DWORD exceptionCode;</span><br><span class="line"> currentESP = ESP;</span><br><span class="line"> __try</span><br><span class="line"> &#123;</span><br><span class="line"> NtSetInformationThread(GetCurrentThread(),</span><br><span class="line"> ThreadQuerySetWin32StartAddress,</span><br><span class="line"> &amp;lpfnEntryPoint,</span><br><span class="line"> <span class="keyword">sizeof</span>(lpfnEntryPoint));</span><br><span class="line"> retValue = lpfnEntryPoint();</span><br><span class="line"> ExitThread(retValue);</span><br><span class="line"> &#125;</span><br><span class="line"> __except ( <span class="comment">//过滤器表达式代码</span></span><br><span class="line"> exceptionCode = GetExceptionInformation(),</span><br><span class="line"> UnhandledExceptionFilter(GetExceptionInformation()))</span><br><span class="line"> &#123;</span><br><span class="line"> ESP = currentESP;</span><br><span class="line"> <span class="keyword">if</span> (!_BaseRunningInServerProcess) <span class="comment">// 普通进程</span></span><br><span class="line"> ExitProcess(exceptionCode);</span><br><span class="line"> <span class="keyword">else</span> <span class="comment">// 服务</span></span><br><span class="line"> ExitThread(exceptionCode);</span><br><span class="line"> &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>到这里👴实际上已经有点迷糊了(<br>那展开的意思是_except_handler会被调用两次,那么自己写个正常插入的异常试试:</p>
Expand All @@ -157,7 +157,7 @@ <h2 id="展开-unwind"><a href="#展开-unwind" class="headerlink" title="展开
<p>☁️:❓<br>☁️:怎么会是捏❓</p>
</blockquote>
<p>fine,本来的疑问没解决,结果多了一堆新的疑问………<br>算了,暂且放下这些问题继续往下看</p>
<h2 id="编译器层面的SEH"><a href="#编译器层面的SEH" class="headerlink" title="编译器层面的SEH"></a>编译器层面的SEH</h2><p>关于编译器级的SEH我已经在[第一篇关于SEH的学习笔记]中写过了(<a target="_blank" rel="noopener" href="https://www.yunzh1jun.com/2022/05/27/WindowsSEH/)%EF%BC%8C%E6%AD%A4%E5%A4%84%E6%80%BB%E7%BB%93%E5%87%A0%E4%B8%AA%E8%A6%81%E7%82%B9%E3%80%82">https://www.yunzh1jun.com/2022/05/27/WindowsSEH/),此处总结几个要点。</a></p>
<h2 id="编译器层面的SEH"><a href="#编译器层面的SEH" class="headerlink" title="编译器层面的SEH"></a>编译器层面的SEH</h2><p>关于编译器级的SEH我已经在<a target="_blank" rel="noopener" href="https://www.yunzh1jun.com/2022/05/27/WindowsSEH/">第一篇关于SEH的学习笔记</a>中写过了,此处总结几个要点。</p>
<h2 id="基于帧的异常处理程序模型"><a href="#基于帧的异常处理程序模型" class="headerlink" title="基于帧的异常处理程序模型"></a>基于帧的异常处理程序模型</h2><p>简单地说,在一个函数中,一个__try块中的所有代码就通过创建在这个函数的堆栈帧上的一个EXCEPTION_REGISTRATION结构来保护。在函数的入口处,这个新的EXCEPTION_REGISTRATION结构被放在异常处理程序链表的头部。在__try块结束后,相应的 EXCEPTION_REGISTRATION结构从这个链表的头部被移除。正如前面所说,异常处理程序链表的头部被保存在FS:[0]处。因此,如果你在调试器中单步跟踪时看到类似MOV DWORD PTR FS:[00000000],ESP或者MOV DWORD PTR FS:[00000000],ECX的指令时,就能非常确定这段代码正在进入或退出一个__try&#x2F;__except块。</p>
<figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> _<span class="title">EXCEPTION_REGISTRATION</span></span></span><br><span class="line"><span class="class">&#123;</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> _<span class="title">EXCEPTION_REGISTRATION</span> *<span class="title">prev</span>;</span> <span class="comment">//ebp-0x10</span></span><br><span class="line"><span class="type">void</span> *handler; <span class="comment">//ebp-0x0c 异常处理函数</span></span><br><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">scopetable_entry</span> *<span class="title">scopetable</span>;</span><span class="comment">//ebp-8 类型为 scopetable_entry 的数组</span></span><br><span class="line"> <span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> _<span class="title">SCOPETABLE</span></span></span><br><span class="line"><span class="class"> &#123;</span></span><br><span class="line"> DWORD previousTryLevel; <span class="comment">//定位前一个try块的索引值</span></span><br><span class="line"> DWORD lpfnFilter; <span class="comment">//当前try块的过滤函数</span></span><br><span class="line"> DWORD lpfnHandler; <span class="comment">//当前try块的终止函数</span></span><br><span class="line"> &#125;SCOPETABLE, *PSCOPETABLE;</span><br><span class="line"> </span><br><span class="line"><span class="type">int</span> trylevel; <span class="comment">//ebp-4 数组下标,用来索引 scopetable 中的数组成员</span></span><br><span class="line"><span class="type">int</span> _ebp; <span class="comment">//ebp 包含该 _EXCEPTION_REGISTRATION 结构体创建之前的栈帧指针</span></span><br><span class="line"> PEXCEPTION_POINTERS xpointers;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

Expand Down
File renamed without changes
File renamed without changes

0 comments on commit 4434b1b

Please sign in to comment.