写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


华丽的分割线


概述

  当用户异常产生后,内核函数KiDispatchException并不是像处理内核异常那样在0环直接进行处理 ,而是修正3环EIP为KiUserExceptionDispatcher函数后就结束了。这样,当线程再次回到3环时,将会从KiUserExceptionDispatcher函数开始执行,这个函数就是我们重点关注对象,我们先看一下它的流程:

  1. 调用RtlDispatchException,查找并执行异常处理函数。
  2. 如果RtlDispatchException返回真,调用ZwContinue再次进入0环,但线程再次返回3环时,会从修正后的位置开始执行。
  3. 如果RtlDispatchException返回假,调用ZwRaiseException进行第二轮异常分发。

  看完上面的流程之后,我们看看其反汇编:

; void __stdcall __noreturn KiUserExceptionDispatcher(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextFrame)
public _KiUserExceptionDispatcher@8
_KiUserExceptionDispatcher@8 proc near ; DATA XREF: .text:off_7C923428↑o var_C = dword ptr -0Ch
var_8 = dword ptr -8
var_4 = dword ptr -4
ExceptionRecord = dword ptr 4
ContextFrame = dword ptr 8 mov ecx, [esp+ExceptionRecord]
mov ebx, [esp]
push ecx ; ContextRecord
push ebx ; ExceptionRecord
call _RtlDispatchException@8 ; RtlDispatchException(x,x)
or al, al
jz short loc_7C92E47A
pop ebx
pop ecx
push 0
push ecx
call _ZwContinue@8 ; ZwContinue(x,x)
jmp short loc_7C92E485
; --------------------------------------------------------------------------- loc_7C92E47A: ; CODE XREF: KiUserExceptionDispatcher(x,x)+10↑j
pop ebx
pop ecx
push 0 ; FirstChance
push ecx ; ContextRecord
push ebx ; ExceptionRecord
call _ZwRaiseException@12 ; ZwRaiseException(x,x,x) loc_7C92E485: ; CODE XREF: KiUserExceptionDispatcher(x,x)+1C↑j
add esp, -14h
mov [esp+EXCEPTION_RECORD.ExceptionCode], eax
mov [esp+EXCEPTION_RECORD.ExceptionFlags], 1
mov [esp+EXCEPTION_RECORD.ExceptionRecord], ebx
mov [esp+EXCEPTION_RECORD.NumberParameters], 0
push esp ; ExceptionRecord
call _RtlRaiseException@4 ; RtlRaiseException(x)
_KiUserExceptionDispatcher@8 endp ; sp-analysis failed

  可以看出该函数会调用RtlDispatchException,为了节省篇幅用伪代码如下:

BOOLEAN __stdcall RtlDispatchException(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND] result = 0;
if ( RtlCallVectoredExceptionHandlers(ExceptionRecord, ContextRecord) )
return 1;
RtlpGetStackLimits(&LowLimit, &HighLimit);
ExceptionRecorda = 0;
exRecord = RtlpGetRegistrationHead(); // ExceptionList
if ( exRecord != -1 )
{
while ( 1 )
{
if ( exRecord < LowLimit
|| &exRecord[1] > HighLimit
|| (exRecord & 3) != 0
|| (handler = exRecord->Handler, handler >= LowLimit) && handler < HighLimit
|| !RtlIsValidHandler(exRecord->Handler) )
{
ExceptionRecord->ExceptionFlags |= EXCEPTION_STACK_INVALID;
return result;
}
if ( byte_7C99B3FA < 0 )
v11 = RtlpLogExceptionHandler(ExceptionRecord, ContextRecord, 0, exRecord, 0x10u);
RtlpExecuteHandlerForException(ExceptionRecord, exRecord, ContextRecord, &a4, exRecord->Handler);
v6 = v5;
if ( byte_7C99B3FA < 0 )
RtlpLogLastExceptionDisposition(v11, v5);
if ( ExceptionRecorda == exRecord )
{
ExceptionRecord->ExceptionFlags &= 0xFFFFFFEF;
ExceptionRecorda = 0;
}
if ( !v6 )
break;
if ( v6 == 1 )
{
if ( (ExceptionRecord->ExceptionFlags & 8) != 0 )
return result;
}
else
{
if ( v6 != 2 )
{
e.ExceptionCode = EXCEPTION_INVALID_DISPOSITION;
e.ExceptionFlags = 1;
e.ExceptionRecord = ExceptionRecord;
e.NumberParameters = 0;
RtlRaiseException(&e);
}
v8 = a4;
ExceptionRecord->ExceptionFlags |= EXCEPTION_NESTED_CALL;
if ( v8 > ExceptionRecorda )
ExceptionRecorda = v8;
}
exRecord = exRecord->Next;
if ( exRecord == -1 )
return result;
}
if ( (ExceptionRecord->ExceptionFlags & 1) != 0 )
{
e.ExceptionCode = EXCEPTION_NONCONTINUABLE_EXCEPTION;
e.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
e.ExceptionRecord = ExceptionRecord;
e.NumberParameters = 0;
RtlRaiseException(&e);
}
result = 1;
}
return result;
}

  RtlCallVectoredExceptionHandlers这个函数就是用来执行VEH的。如果返回假,则说明没有,后面的RtlpGetRegistrationHead就会获取SEH,如果有就执行,它是在堆栈中的。

  有了这些铺垫后,我们来介绍VEHSEH

VEH

  对于VEH,这个是XP及其之后才有的,中文为向量化异常结构处理。我们先看看它的处理流程:

  1. CPU捕获异常信息;
  2. 通过KiDispatchException进行分发;
  3. KiUserExceptionDispatcher调用RtlDispatchException
  4. RtlDispatchException查找VEH处理函数链表 并调用相关处理函数;
  5. 代码返回到KiUserExceptionDispatcher
  6. 调用ZwContinue再次进入0环(ZwContinue调用NtContinue,主要作用就是恢复_TRAP_FRAME然后通过KiServiceExit返回到3环);
  7. 线程再次返回3环后,从修正后的位置开始执行;

  如下是执行VEH的伪代码:

BOOLEAN __stdcall RtlCallVectoredExceptionHandlers(PEXCEPTION_RECORD ExceptionRecord, PCONTEXT ContextRecord)
{
PRTL_VECTORED_HANDLER_ENTRY p; // esi
int (__stdcall *VectoredHandler)(EXCEPTION_POINTERS *); // eax
EXCEPTION_POINTERS ExceptionInfo; // [esp+4h] [ebp-8h] BYREF
BOOLEAN v6; // [esp+17h] [ebp+Bh] if ( IsListEmpty(&RtlpCalloutEntryList) )
return 0;
ExceptionInfo.ExceptionRecord = ExceptionRecord;
ExceptionInfo.ContextRecord = ContextRecord;
RtlEnterCriticalSection(&RtlpCalloutEntryLock);
for ( p = RtlpCalloutEntryList.Flink; ; p = p->ListEntry.Flink )
{
if ( p == &RtlpCalloutEntryList )
{
v6 = 0;
goto EndProc;
}
VectoredHandler = RtlDecodePointer(p->VectoredHandler);
if ( VectoredHandler(&ExceptionInfo) == -1 )
break;
}
v6 = 1;
EndProc:
RtlLeaveCriticalSection(&RtlpCalloutEntryLock);
return v6;
}

  剩余的细节将会在总结与提升进行讲解,下面我们来看看如何使用VEH,如下是实验代码:

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h> typedef PVOID (NTAPI *VectoredExceptionHandler)(ULONG,_EXCEPTION_POINTERS*); LONG NTAPI MyVectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo)
{
puts("进入异常处理函数……");
if (pExceptionInfo->ExceptionRecord->ExceptionCode==0xC0000094)
{
puts("异常函数处理了……");
pExceptionInfo->ContextRecord->Ecx = 1;
return EXCEPTION_CONTINUE_EXECUTION;
} return EXCEPTION_CONTINUE_SEARCH;
} int main(int argc, char* argv[])
{
HMODULE lib = LoadLibrary("kernel32.dll");
VectoredExceptionHandler AddVectoredExceptionHandler = (VectoredExceptionHandler)GetProcAddress(lib,"AddVectoredExceptionHandler");
AddVectoredExceptionHandler(1,(_EXCEPTION_POINTERS*)&MyVectoredExceptionHandler); _asm
{
xor edx,edx;
xor ecx,ecx;
mov eax,0x10;
idiv ecx;
}
puts("继续执行……");
system("pause");
return 0;
}

  执行后会正常执行,并显示异常处理信息。

SEH

  SEH意为结构化异常处理,它的结构如下图所示:

  也就是说包装的异常处理项目是以单向链表的形式管理的。必须具有两个如上图所示的成员,也就是说,这个结构是可以扩展的,有关扩展的将会在后续介绍,下面我们来看实验代码:

#include "stdafx.h"
#include <windows.h>
#include <stdlib.h> struct MyException
{
MyException* prev;
DWORD handle;
}; EXCEPTION_DISPOSITION MyExceptionHandler(_EXCEPTION_RECORD* ExceptionRecord,void* Establisherframe,CONTEXT* context,void* DispatcherContext)
{
puts("进入异常处理……");
if (ExceptionRecord->ExceptionCode==0xC0000094)
{
puts("开始处理异常……");
context->Eip+=2;
return ExceptionContinueExecution;
}
return ExceptionContinueSearch;
} int main(int argc, char* argv[])
{ DWORD tmp;
//初始化异常结构
MyException ex={(MyException*)tmp,(DWORD)MyExceptionHandler}; //加入 SEH
_asm
{
mov eax,fs:[0];
mov tmp,eax;
lea ecx,ex;
mov fs:[0],ecx;
} //制造异常
_asm
{
xor edx,edx;
xor ecx,ecx;
mov eax,0x10;
idiv ecx;
} //撤掉 SEH
_asm
{
mov eax,tmp;
mov fs:[0],eax;
} puts("正常运行……");
system("pause");
return 0;
}

  该程序正常执行,并打印异常处理结果。

编译器扩展 SEH

初识

  前面我们用自己的方式实现了SEH的使用。异常处理很重要,但是,这个对于开发者很不友好。每次都要构造SEH,退出函数要撤掉。编译器提供了关键字,并对SEH进行了扩充,使用如下图所示:

_try    // 挂入 SEH 链表
{ }
_except(/*过滤表达式*/) //异常过滤
{
//异常处理程序
}

  对于过滤表达式的结果值,只能是-101,它们表示的含义如下:

  1. EXCEPTION_EXECUTE_HANDLER (1) 执行except里面的代码
  2. EXCEPTION_CONTINUE_SEARCH (0) 寻找下一个异常处理函数
  3. EXCEPTION_CONTINUE_EXECUTION (-1) 返回出错位置重新执行

  我说只能是这三值,并没有说只能写这三个数字,你可以写入表达式或者函数,使其得到的结果或者返回值是这仨值其中之一就可以,如下是我们的实验程序:

#include "stdafx.h"
#include <stdlib.h> int main(int argc, char* argv[])
{
_try
{
_asm
{
xor edx,edx;
xor ecx,ecx;
mov eax,0x10;
idiv ecx;
}
puts("继续跑……");
}_except(1)
{
puts("异常处理……");
}
system("pause");
return 0;
}

  运行该程序,只打印了except里面的,得到正确结果。

初步深入

  我们接下来在汇编层面查看它是如何实现的,首先我们查看一下编译器为我们扩展的结构,否则看代码是看不懂的。

struct _EXCEPTION_REGISTRATION
{
struct _EXCEPTION_REGISTRATION *prev;
void (*handler)(PEXCEPTION_RECORD, PEXCEPTION_REGISTRATION, PCONTEXT, PEXCEPTION_RECORD);
struct scopetable_entry *scopetable;
int trylevel;
int _ebp;
};

  然后我们所谓的结构就成立这样子:

  图中的_except_handler3是啥我们看它的反汇编是什么就知道了:

#include "stdafx.h"
#include <stdlib.h> int main(int argc, char* argv[])
{
00401010 push ebp
00401011 mov ebp,esp
00401013 push 0FFh
00401015 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed"+0Ch (00424030)
0040101A push offset __except_handler3 (00401400)
0040101F mov eax,fs:[00000000]
00401025 push eax
00401026 mov dword ptr fs:[0],esp
0040102D add esp,0B8h
00401030 push ebx
00401031 push esi
00401032 push edi
00401033 mov dword ptr [ebp-18h],esp
00401036 lea edi,[ebp-58h]
00401039 mov ecx,10h
0040103E mov eax,0CCCCCCCCh
00401043 rep stos dword ptr [edi]
_try
00401045 mov dword ptr [ebp-4],0
{
_asm
{
xor edx,edx;
0040104C xor edx,edx
xor ecx,ecx;
0040104E xor ecx,ecx
mov eax,0x10;
00401050 mov eax,10h
idiv ecx;
00401055 idiv eax,ecx
}
puts("继续跑……");
00401057 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed" (00424024)
0040105C call puts (004011e0)
00401061 add esp,4
}_except(1)
00401064 mov dword ptr [ebp-4],0FFFFFFFFh
0040106B jmp $L865+17h (0040108a)
$L864:
0040106D mov eax,1
$L866:
00401072 ret
$L865:
00401073 mov esp,dword ptr [ebp-18h]
{
puts("异常处理……");
00401076 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00425140)
0040107B call puts (004011e0)
00401080 add esp,4
}
00401083 mov dword ptr [ebp-4],0FFFFFFFFh
system("pause");
0040108A push offset string "pause" (0042401c)
0040108F call system (004010d0)
00401094 add esp,4
return 0;
00401097 xor eax,eax
}
00401099 mov ecx,dword ptr [ebp-10h]
0040109C mov dword ptr fs:[0],ecx
004010A3 pop edi
004010A4 pop esi
004010A5 pop ebx
004010A6 add esp,58h
004010A9 cmp ebp,esp
004010AB call __chkesp (004012d0)
004010B0 mov esp,ebp
004010B2 pop ebp
004010B3 ret

  看不懂吗?我们来画个堆栈图,如下所示:

  标注*的表示原来的值,是不是和结构体的成员对应起来了?注意不要以为只有黄色的区域,由于通常的函数采用ebp寻址,所以我没有把ebp*打上黄色底色。

  下面我们来看看scopetable成员,它的结构如下:

struct scopetable_entry
{
DWORD previousTryLevel; //上一个try{}结构编号
PDWRD lpfnFilter; //过滤函数的起始地址
PDWRD lpfnHandler; //异常处理程序的地址
}

  我们来看看这个结构的内容是啥,最终它的成员如下:

scopetable.previousTryLevel = -1;
scopetable.lpfnFilter = 0x40106D;
scopetable.lpfnHandler = 0x401073;

  正好把代码指令和地址逐个对应起来了。

继续深入

  如果异常处理有嵌套调用的情况会是怎么样呢?如下是测试代码:

#include "stdafx.h"
#include <stdlib.h> int main(int argc, char* argv[])
{
_try
{
_try
{
_asm
{
xor edx,edx;
xor ecx,ecx;
mov eax,0x10;
idiv ecx;
}
}_except(1)
{
puts("测试");
}
puts("继续跑……");
}_except(1)
{
puts("异常处理……");
}
system("pause");
return 0;
}

  然后查看反汇编结果:

#include "stdafx.h"
#include <stdlib.h> int main(int argc, char* argv[])
{
00401010 push ebp
00401011 mov ebp,esp
00401013 push 0FFh
00401015 push offset string "\xb2\xe2\xca\xd4"+0Ch (00424050)
0040101A push offset __except_handler3 (00401450)
0040101F mov eax,fs:[00000000]
00401025 push eax
00401026 mov dword ptr fs:[0],esp
0040102D add esp,0B8h
00401030 push ebx
00401031 push esi
00401032 push edi
00401033 mov dword ptr [ebp-18h],esp
00401036 lea edi,[ebp-58h]
00401039 mov ecx,10h
0040103E mov eax,0CCCCCCCCh
00401043 rep stos dword ptr [edi]
_try
00401045 mov dword ptr [ebp-4],0
{
_try
0040104C mov dword ptr [ebp-4],1
{
_asm
{
xor edx,edx;
00401053 xor edx,edx
xor ecx,ecx;
00401055 xor ecx,ecx
mov eax,0x10;
00401057 mov eax,10h
idiv ecx;
0040105C idiv eax,ecx
}
}_except(1)
0040105E mov dword ptr [ebp-4],0
00401065 jmp $L872+17h (0040f5d4)
$L871:
00401067 mov eax,1
$L873:
0040106C ret
$L872:
0040106D mov esp,dword ptr [ebp-18h]
{
puts("测试");
00401070 push offset string "\xb2\xe2\xca\xd4" (00424044)
00401075 call puts (00401230)
0040107A add esp,4
}
0040107D mov dword ptr [ebp-4],0
puts("继续跑……");
00401084 push offset string "\xbc\xcc\xd0\xf8\xc5\xdc\xa1\xad\xa1\xad" (00424034)
00401089 call puts (00401230)
0040108E add esp,4
}_except(1)
00401091 mov dword ptr [ebp-4],0FFFFFFFFh
00401098 jmp $L868+17h (004010b7)
$L867:
0040109A mov eax,1
$L869:
0040109F ret
$L868:
004010A0 mov esp,dword ptr [ebp-18h]
{
puts("异常处理……");
004010A3 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00424024)
004010A8 call puts (00401230)
004010AD add esp,4
}
004010B0 mov dword ptr [ebp-4],0FFFFFFFFh
system("pause");
004010B7 push offset string "pause" (0042401c)
004010BC call system (00401120)
004010C1 add esp,4
return 0;
004010C4 xor eax,eax
}
004010C6 mov ecx,dword ptr [ebp-10h]
004010C9 mov dword ptr fs:[0],ecx
004010D0 pop edi
004010D1 pop esi
004010D2 pop ebx
004010D3 add esp,58h
004010D6 cmp ebp,esp
004010D8 call __chkesp (00401320)
004010DD mov esp,ebp
004010DF pop ebp
004010E0 ret

  看代码发现还是只是挂了一次,我们得看看scopetable的内容是啥了:

00425168  FFFFFFFF  0040109A  004010A0
00425174 00000000 00401067 0040106D
00425180 00000000 00000000 00000000
0042518C 00000000 00000000 00000000

  可以看到,这里有两个成员了。

finally 关键字

  当然不仅仅有try_except,还可以使用finally,该关键字的作用就是只要退出try就执行里面的函数,无论通过那种方式,如下是我们的实验代码:

#include "stdafx.h"
#include <stdlib.h> int main(int argc, char* argv[])
{
_try
{
return 0;
}__finally
{
puts("异常处理……");
system("pause");
}
return 0;
}

  执行结果如下:

异常处理……
请按任意键继续. . .

  然后我们看看它在汇编层面是如何实现的,其反汇编如下:

#include "stdafx.h"
#include <stdlib.h> int main(int argc, char* argv[])
{
00401010 push ebp
00401011 mov ebp,esp
00401013 push 0FFh
00401015 push offset string "stream != NULL"+10h (00425168)
0040101A push offset __except_handler3 (00401450)
0040101F mov eax,fs:[00000000]
00401025 push eax
00401026 mov dword ptr fs:[0],esp
0040102D add esp,0B4h
00401030 push ebx
00401031 push esi
00401032 push edi
00401033 lea edi,[ebp-5Ch]
00401036 mov ecx,11h
0040103B mov eax,0CCCCCCCCh
00401040 rep stos dword ptr [edi]
_try
00401042 mov dword ptr [ebp-4],0
00401049 push 0FFh
0040104B mov dword ptr [ebp-1Ch],0
{
00401052 lea eax,[ebp-10h]
00401055 push eax
00401056 call __local_unwind2 (0040139a)
0040105B add esp,8
return 0;
0040105E mov eax,dword ptr [ebp-1Ch]
00401061 jmp $L865+2 (00401080)
}__finally
{
puts("异常处理……");
00401063 push offset string "\xd2\xec\xb3\xa3\xb4\xa6\xc0\xed\xa1\xad\xa1\xad" (00424024)
00401068 call puts (00401230)
0040106D add esp,4
system("pause");
00401070 push offset string "pause" (0042401c)
00401075 call system (00401120)
0040107A add esp,4
$L863:
0040107D ret
}
17: return 0;
0040107E xor eax,eax
}
00401080 mov ecx,dword ptr [ebp-10h]
00401083 mov dword ptr fs:[0],ecx
0040108A pop edi
0040108B pop esi
0040108C pop ebx
0040108D add esp,5Ch
00401090 cmp ebp,esp
00401092 call __chkesp (00401320)
00401097 mov esp,ebp
00401099 pop ebp
0040109A ret

  可以看到在调用return 0;之前,被插入了调用__local_unwind2函数,正是这个函数能够调用finally里面的代码的:

__local_unwind2:
0040139A push ebx
0040139B push esi
0040139C push edi
0040139D mov eax,dword ptr [esp+10h]
004013A1 push eax
004013A2 push 0FEh
004013A4 push offset __global_unwind2+20h (00401378)
004013A9 push dword ptr fs:[0]
004013B0 mov dword ptr fs:[0],esp
004013B7 mov eax,dword ptr [esp+20h]
004013BB mov ebx,dword ptr [eax+8]
004013BE mov esi,dword ptr [eax+0Ch]
004013C1 cmp esi,0FFh
004013C4 je __NLG_Return2+2 (004013f4)
004013C6 cmp esi,dword ptr [esp+24h]
004013CA je __NLG_Return2+2 (004013f4)
004013CC lea esi,[esi+esi*2]
004013CF mov ecx,dword ptr [ebx+esi*4]
004013D2 mov dword ptr [esp+8],ecx
004013D6 mov dword ptr [eax+0Ch],ecx
004013D9 cmp dword ptr [ebx+esi*4+4],0
004013DE jne __NLG_Return2 (004013f2)
004013E0 push 101h
004013E5 mov eax,dword ptr [ebx+esi*4+8]
004013E9 call __NLG_Notify (0040142e)
004013EE call dword ptr [ebx+esi*4+8]
__NLG_Return2:
004013F2 jmp __local_unwind2+1Dh (004013b7)
004013F4 pop dword ptr fs:[0]
004013FB add esp,0Ch
004013FE pop edi
004013FF pop esi
00401400 pop ebx
00401401 ret

  关键调用在call dword ptr [ebx+esi*4+8],执行这个就会调用finally里的代码。具体详细的其他细节将会在总结与提升进行介绍。

下一篇

  异常篇——总结与提升

异常篇—— VEH 与 SEH的更多相关文章

  1. 羽夏看Win系统内核——异常篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  2. 关于C++异常机制的笔记(SEH, try-catch)

    昨天晚上加班解决了一个问题,是由于无法正确的捕获到异常导致的.刚开始用try-catch,但是没法捕获到异常:后面改成SEH异常才解决.因此今天将这个问题重新梳理了一遍,关于try-catch, SE ...

  3. linux上安装fastdfs+nginx+ngin-module实践并解决多个异常篇

    为什么选择Nginx Nginx 是一个很牛的高性能Web和反向代理服务器, 它具有有很多非常优越的特性: 在高连接并发的情况下,Nginx是Apache服务器不错的替代品:Nginx在美国是做虚拟主 ...

  4. Python之异常篇 [待更新]

    简介 当你的程序中出现某些 异常的 状况的时候,异常就发生了.例如,当你想要读某个文件的时候,而那个文件不存在.或者在程序运行的时候,你不小心把它删除了.上述这些情况可以使用异常来处理. 假如你的程序 ...

  5. Java面试题(异常篇)

    异常 74.throw 和 throws 的区别? throws是用来声明一个方法可能抛出的所有异常信息,throws是将异常声明但是不处理,而是将异常往上传,谁调用我就交给谁处理.而throw则是指 ...

  6. hive 学习之异常篇

    一.刚装上hive在执行hive启动的过程中出现 [hadoop@localhost hive-0.6.0]$ hive Invalid maximum heap size: -Xmx4096m Th ...

  7. hadoop 学习之异常篇

    本文旨在给予自己在学习hadoop过程中遇到的问题的一个记录和解决方法. 一. copyFromLocal: java.io.IOException: File /user/hadoop/slaves ...

  8. [异常篇]001.MySQL数据库忘记root密码解决办法[转载]

    MySQL数据库忘记root密码解决办法 1.在运行输入services.msc打开服务窗体,找到MYSQL服务.右键停止将其关闭.如图: 2.在运行输入cmd打开终端. 3.找到MYSQL的安装目录 ...

  9. java代码异常篇

    总结:掌握流.缓冲区类的方法 package com.b; import java.io.BufferedReader; import java.io.File; import java.io.Fil ...

随机推荐

  1. [学习分享] 在Windows操作系统下如何安装RMySQL包

    最近在做股票的高频交易数据分析,需要用到数据库,而我只对MySQL比较熟悉,于是就安装了MySQL.当我安装好了MySQL后,正兴冲冲地准备安装RMySQL包时,问题来了:RMySQL包不支持wind ...

  2. 《剑指offer》面试题14- I. 剪绳子

    问题描述 给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m.n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m] .请问 k[0]*k[1]* ...

  3. Sentry 开发者贡献指南 - 测试技巧

    作为 CI 流程的一部分,我们在 Sentry 运行了多种测试. 本节旨在记录一些 sentry 特定的帮助程序, 并提供有关在构建新功能时应考虑包括哪些类型的测试的指南. 获取设置 验收和 pyth ...

  4. 开源办公套件DzzOffice安装教程

    DzzOffice开源办公套件 DzzOffice是一套开源办公套件,适用于企业.团队搭建自己的 类似"Google企业应用套件"."微软Office365"的 ...

  5. 学习Java第17天

    今天差不多搞清了Javaweb的基本学习路线及网站开发的基本流程,以前好像走了不少弯路,不过还好,明天开始学习Javaweb sql 总结了许多 新的学习方法,记笔记很重要,学多少忘多少. 练习也很重 ...

  6. 微服务架构 | 5.4 Sentinel 流控、统计和熔断的源码分析

    目录 前言 1. Sentinel 的自动装配 1.2 依赖引入 1.3 SentinelWebAutoConfiguration 配置类 1.4 CommonFilter 过滤器 1.5 小结 2. ...

  7. Flink源码学习笔记(3)了解Flink HA功能的实现

    使用Flink HA功能维护JobManager中组件的生命周期,可以有效的避免因为JobManager 进程失败导致任务无法恢复的情况. 接下来分享下 Flink HA功能的实现 大纲 基于Zook ...

  8. Iceberg学习日记(1) 定位两个线上Iceberg查不到文件的问题

    前言 Iceberg是我们去年年底(2020)开始调研,目前上线了130多张表.主要用于流量日志清洗,数据报表,推荐特征基础数据.至今为也算是积累了一些使用及定位问题经验. 这篇文章会介绍两个线上Ic ...

  9. python3调用js的库之execjs

    执行JS的类库:execjs,PyV8,selenium,node execjs是一个比较好用且容易上手的类库(支持py2,与py3),支持 JS runtime. 1.安装: pip install ...

  10. SQL语句 order by 升序和 降序查询

    原文 https://blog.csdn.net/u010649766/article/details/76180523?utm_medium=distribute.pc_relevant_t0.no ...