前沿拓展:
ieframe.dll
如果沒有權(quán)限,在系統(tǒng)中是不能刪除的,可以在u盤PE系統(tǒng)中刪除,就么有權(quán)限問題了。
還可以在雙系統(tǒng)中,刪除另一個系統(tǒng)中的動態(tài)鏈文件(dll擴(kuò)展名)這也也沒權(quán)限問題的。
一:背景1. 講故事
在項目中摸爬滾打幾年,應(yīng)該或多或少的見過有人把異常當(dāng)做業(yè)務(wù)邏輯處理的情況(┬_┬),比如說判斷一個數(shù)字是否為整數(shù),就想當(dāng)然的用try catch包起來,再進(jìn)行 int.Parse,如果拋異常就說明不是整數(shù),簡單粗暴,也不需要寫正則或者其他邏輯,再比如一個字符串強(qiáng)制轉(zhuǎn)化為Enum,直接用Enum.Parse,可能是因為對異常的開銷不是特別了解,這種不好的使用習(xí)慣也許被官方發(fā)現(xiàn)了,后續(xù)給我們補(bǔ)了很多的Try前綴的方法,比如:int.TryParse , Enum.TryParse, dict.TryGetValue ,用代碼展示如下:
//原始寫法
var num = int.Parse("1");
//使用try方式
var result = 0;
var b = int.TryParse("1", out result);
用Try系列方法沒毛病,但這寫法讓人吐槽,還要單獨定義result變量,沒撤,官方還得靠我們這些開發(fā)者給他們發(fā)揚(yáng)光大,終于在C# 7.0 中新增了一個 out variables 語法糖。
//try out 變量模式
var c = int.TryParse("1", out int result2);
這種 out 變量 模式就了,一個方法獲取兩個值,還沒有拋異常的風(fēng)險。
二:為什么要用tryxxx方法
有了tryxxx方法之后,你就應(yīng)該明白微軟已經(jīng)在提醒我們開發(fā)人員不要濫用異常,尤其在可預(yù)知可預(yù)見的場景下,畢竟他們知道異常的開銷真的是太大了,不知者不怪哈。
1. 肉眼看得見的低性能
為了讓大家肉眼能看見,我們就用異常方法和tryxxx方法做一個性能比較,迭代50w次,看看各自的性能如何?
for (int i = 0; i < 3; i++)
{
var watch = Stopwatch.StartNew();
for (int k = 0; k < 50000; k++)
{
try
{
var num = int.Parse("xxx");
}
catch (Exception ex) { }
}
watch.Stop();
Console.WriteLine(#34;i={i + 1},耗費:{watch.ElapsedMilliseconds}");
}
Console.WriteLine("———————————————");
for (int i = 0; i < 3; i++)
{
var watch = Stopwatch.StartNew();
for (int k = 0; k < 50000; k++)
{
var num = int.TryParse("xxx", out int reuslt);
}
watch.Stop();
Console.WriteLine(#34;i={i + 1},耗費:{watch.ElapsedMilliseconds}");
}
Console.ReadLine();
看結(jié)果還挺嚇人的,相差480倍, 好熟悉的一個數(shù)字。。。 南朝四百八十寺,多少樓臺煙雨中
三: 異常的超強(qiáng)開銷
為什么異常有那么大的開銷? 只有知己知彼才能心中有數(shù),看過我多線程視頻的朋友應(yīng)該知道,線程的創(chuàng)建和銷毀代價都是非常大的,其中有一項就是需要代碼從用戶態(tài)切換到了內(nèi)核態(tài),畢竟線程是**作系統(tǒng)層面的事情,和你CLR無關(guān),CLR只是做了一層系統(tǒng)包裝而已,其實很多人都想不到,我們用的 try catch finally 底層也是封裝了**作系統(tǒng)層面的(Windows 結(jié)構(gòu)化異常處理),也叫做SEH,什么意思? 就是當(dāng)你throw之后,代碼需要從用戶態(tài)切換到內(nèi)核態(tài),這個開銷是不會小的,還有一個開銷來自于Exception中的StackTrace,這里面的值需要從當(dāng)前異常的線程棧中去抓取調(diào)用堆棧,棧越深,開銷就越大。
1. 從用戶態(tài)到內(nèi)核態(tài)
大家肯定會說,甭那么玄乎,凡事都要講個證據(jù), Do more,Talk less, 這里我準(zhǔn)備分兩種情況講解。
<1> 有catch情況
準(zhǔn)備在catch的時候阻塞住,第二抓它的dump文件。
public static void Main(string[] args)
{
try
{
var num = int.Parse("xxx");
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Console.ReadLine();
}
}
使用 !dumpstack 把當(dāng)前 0號線程 的所有托管和非托管堆棧全部打出來,簡化后如下:
0:000> ~0s
ntdll!NtReadFile+0x14:
00007fff`f805aa64 c3 ret
0:000> !dumpstack
OS Thread Id: 0x2bf0 (0)
Current frame: ntdll!NtReadFile+0x14
Caller, Callee
(MethodDesc 00007fffde3a40b8 +0x18 System.Console.ReadLine())
(MethodDesc 00007fff810d59f8 +0xa5 ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3a40b8 +0 System.Console.ReadLine())
00000044433fc700 00007fffe07a29e0 clr!ExceptionTracker::CallCatchHandler+0x9c, calling clr!ExceptionTracker::CallHandler
clr!ClrUnwindEx+0x40, calling ntdll!RtlUnwindEx
ntdll!RtlRaiseException+0x4e, calling ntdll!RtlpCaptureContext
clr!IL_Throw+0x114, calling clr!RaiseTheExceptionInternalOnly
(MethodDesc 00007fffde4f95c0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)), calling mscorlib_ni+0x53976a
(MethodDesc 00007fffde3b5330 +0xae System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)), calling (MethodDesc 00007fffde4f95c0 +0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean))
(MethodDesc 00007fffde1ebfa8 +0x2eb System.Globalization.NumberFormatInfo..ctor(System.Globalization.CultureData)), calling (MethodDesc 00007fffde1eba68 +0 System.Globalization.CultureData.GetNFIValues(System.Globalization.NumberFormatInfo))
(MethodDesc 00007fff810d59f8 +0x49 ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3b1708 +0 System.Int32.Parse(System.String))
因為是堆棧,所以執(zhí)行流就要從后往前看,你會發(fā)現(xiàn)流程大概是這個樣子 int.Parse -> CLR -> ntdll -> CLR -> Console.ReadLine ,很顯然 ntdll.dll 是**作系統(tǒng)層級的一個核心文件,這就從用戶態(tài)切入到了內(nèi)核態(tài),如果不是很明白,我畫一張簡圖吧。。。
<2>. 無catch處理
大家肯定很好奇,如果無catch會是怎么樣,大家也可以用windbg去挖一下。
public static void Main(string[] args)
{
var num = int.Parse("xxx");
}
0:000> !dumpstack
OS Thread Id: 0xd68 (0)
Current frame: ntdll!NtTerminateProcess+0x14
Caller, Callee
mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes+0x285, calling KERNEL32!ExitProcessImplementation
mscoreei!CLRRuntimeHostInternalImpl::ShutdownAllRuntimesThenExit+0x14, calling mscoreei!RuntimeDesc::ShutdownAllActiveRuntimes
clr!EEPolicy::ExitProcessViaShim+0x9c
clr!SafeExitProcess+0x9d, calling clr!EEPolicy::ExitProcessViaShim
ntdll!KiUserExceptionDispatch+0x53, calling ntdll!NtRaiseException
clr!RaiseTheExceptionInternalOnly+0x188426, calling clr!EEPolicy::HandleFatalError
clr!IL_Throw+0x45, calling clr!LazyMachStateCaptureState
(MethodDesc 00007fffde4f95c0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean)), calling mscorlib_ni+0x53976a
(MethodDesc 00007fffde3b5330 +0xae System.Number.ParseInt32(System.String, System.Globalization.NumberStyles, System.Globalization.NumberFormatInfo)), calling (MethodDesc 00007fffde4f95c0 +0 System.Number.StringToNumber(System.String, System.Globalization.NumberStyles, NumberBuffer ByRef, System.Globalization.NumberFormatInfo, Boolean))
(MethodDesc 00007fffde1ebfa8 +0x2eb System.Globalization.NumberFormatInfo..ctor(System.Globalization.CultureData)), calling (MethodDesc 00007fffde1eba68 +0 System.Globalization.CultureData.GetNFIValues(System.Globalization.NumberFormatInfo))
(MethodDesc 00007fff810e59f8 +0x37 ConsoleApp4.Program.Main(System.String[])), calling (MethodDesc 00007fffde3b1708 +0 System.Int32.Parse(System.String))
可以看到進(jìn)程的退出邏輯給了托管程序入口 mscoreei.dll 而再也沒有進(jìn)入Main函數(shù)了, 為此我也補(bǔ)一張圖給大家看看
2. 抓取線程調(diào)用棧
當(dāng)大家慌慌張張的看到異常的時候,第一眼會去看異常信息是什么? 第二眼會去看異常出在了哪一行代碼,這就是線程的調(diào)用棧,這個信息非常重要,可以快捷的幫助我們找到問題解決問題,放在Exception的StackTrace中,先上一段代碼。
public static void Main(string[] args)
{
Run();
Console.ReadLine();
}
public static void Run()
{
var ex = new FormatException("你的格式錯誤啦!?。?#034;);
throw ex;
}
<1> StackTrace何時塞入的
到目前為止還沒看到哪本書說到StackTrace是何時被塞入的? 由于水平有限,我也試著探測一下下。
從代碼中可以看到不是在new的時候塞入的,那會是哪里呢?
<2> 從CLR中尋找**
既然不在用戶代碼,那就到CLR中去看看,在windbg中用 dumpstack 去查看非托管堆棧。
0:000> !dumpstack
OS Thread Id: 0x4090 (0)
Current frame: ntdll!NtTerminateProcess+0x14
Caller, Callee
clr!EETypeHashTable::FindItem+0x532, calling clr!NgenHashTable<EEClassHashTable,EEClassHashEntry,4>::PersistedBucketList::GetBucket
clr!JIT_StrCns+0xd0, calling clr!HelperMethodFrameRestoreState
(MethodDesc 00007fff810f5a08 +0x70 ConsoleApp4.Program.Run()), calling clr!IL_Throw
clr!IL_Throw+0x45, calling clr!LazyMachStateCaptureState
(MethodDesc 00007fff810f5a08 +0x70 ConsoleApp4.Program.Run()), calling clr!IL_Throw
(MethodDesc 00007fff810f59f8 +0x28 ConsoleApp4.Program.Main(System.String[])), calling 00007fff81200488 (stub for ConsoleApp4.Program.Run())
從簡化后的流程看,懷疑是由 clr!HelperMethodFrameRestoreState 處理的,為什么這么說呢? 因為我們定義的 FormatException ex 會傳給CLR的,不信可以用 kb 看一看。
0:000> kb
# RetAddr : Args to Child : Call Site
00 00007fff`e07a3181 : 00000000`e0434352 0000006d`4a7fe938 0000017b`30ad2d48 0000017b`2f081690 : KERNELBASE!RaiseException+0x68
01 00007fff`e07a45f4 : ffffffff`fffffffe 0000017b`2ef02542 00000000`0000000a 0000017b`2f040910 : clr!RaiseTheExceptionInternalOnly+0x31f
02 00007fff`811d0950 : 00000000`70000001 00007fff`810c4140 0000006d`4a7fedb8 0000006d`4a7fec78 : clr!IL_Throw+0x114
03 00007fff`811d08b8 : 0000017b`30ad2d30 00007fff`810c4140 00000000`00000000 00007fff`00000000 : 0x00007fff`811d0950
04 00007fff`e0736c93 : 0000017b`30ad2d30 00007fff`810c4140 00000000`00000000 00007fff`00000000 : 0x00007fff`811d08b8
05 00007fff`e0736b79 : 00000000`00000000 00007fff`e0737aae 0000006d`4a7fefb8 00000000`00000000 : clr!CallDescrWorkerInternal+0x83
06 00007fff`e0737410 : 0000006d`4a7fefb8 0000006d`4a7ff048 0000006d`4a7feeb8 00000000`00000001 : clr!CallDescrWorkerWithHandler+0x4e
07 00007fff`e08dcaf2 : 0000006d`4a7fee00 00000000`00000001 00000000`00000001 0000017b`2efcecf0 : clr!MethodDescCallSite::CallTargetWorker+0x102
08 00007fff`e08dd4b3 : 00000000`00000001 00000000`00000000 0000017b`30ad2d30 0000017b`30ad2d30 : clr!RunMain+0x25f
09 00007fff`e08dd367 : 0000017b`2f040910 0000006d`4a7ff420 0000017b`2f040910 0000017b`2f082770 : clr!Assembly::ExecuteMainMethod+0xb7
0a 00007fff`e08dccb3 : 00000000`00000000 0000017b`2ef00000 00000000`00000000 00000000`00000000 : clr!SystemDomain::ExecuteMainMethod+0x643
0b 00007fff`e08dcc31 : 0000017b`2ef00000 00007fff`e08de090 00000000`00000000 00000000`00000000 : clr!ExecuteEXE+0x3f
0c 00007fff`e08de0a4 : ffffffff`ffffffff 00007fff`e08de090 00000000`00000000 00000000`00000000 : clr!_CorExeMainInternal+0xb2
0d 00007fff`e1208a61 : 00000000`00000000 00007fff`00000091 00000000`00000000 0000006d`4a7ff9f8 : clr!CorExeMain+0x14
0e 00007fff`e133a4cc : 00000000`00000000 00007fff`e08de090 00000000`00000000 00000000`00000000 : mscoreei!CorExeMain+0x112
0f 00007fff`f5cc4034 : 00007fff`e1200000 00000000`00000000 00000000`00000000 00000000`00000000 : MSCOREE!CorExeMain_Exported+0x6c
10 00007fff`f8033691 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : KERNEL32!BaseThreadInitThunk+0x14
11 00000000`00000000 : 00000000`00000000 00000000`00000000 00000000`00000000 00000000`00000000 : ntdll!RtlUserThreadStart+0x21
其中第一行的 00 00007fffe07a3181 : 00000000e0434352 0000006d4a7fe938 0000017b30ad2d48 0000017b2f081690 : KERNELBASE!RaiseException+0x68中的第三個參數(shù)地址0000017b30ad2d48` 就是我們的異常類,打印出來看一下。
0:000> !do 0000017b30ad2d48
Name: System.FormatException
MethodTable: 00007fffde285c38
EEClass: 00007fffde3930e0
Size: 160(0xa0) bytes
File: C:WINDOW**icrosoft.NetassemblyGAC_64mscorlibv4.0_4.0.0.0__b77a5c561934e089mscorlib.dll
Fields:
MT Field Offset Type VT Attr Value Name
00007fffde2059c0 40002a2 8 System.String 0 instance 0000017b30ad4c80 _className
00007fffde282a50 40002a3 10 …ection.MethodBase 0 instance 0000000000000000 _exceptionMethod
00007fffde2059c0 40002a4 18 System.String 0 instance 0000000000000000 _exceptionMethodString
00007fffde2059c0 40002a5 20 System.String 0 instance 0000017b30ad2de8 _message
00007fffde2883d8 40002a6 28 …tions.IDictionary 0 instance 0000000000000000 _data
00007fffde205b70 40002a7 30 System.Exception 0 instance 0000000000000000 _innerException
00007fffde2059c0 40002a8 38 System.String 0 instance 0000000000000000 _helpURL
00007fffde205dd8 40002a9 40 System.Object 0 instance 0000017b30ad2e98 _stackTrace
00007fffde205dd8 40002aa 48 System.Object 0 instance 0000017b30ad2f28 _watsonBuckets
00007fffde2059c0 40002ab 50 System.String 0 instance 0000000000000000 _stackTraceString
00007fffde2059c0 40002ac 58 System.String 0 instance 0000000000000000 _remoteStackTraceString
00007fffde2085a0 40002ad 88 System.Int32 1 instance 0 _remoteStackIndex
00007fffde205dd8 40002ae 60 System.Object 0 instance 0000000000000000 _dynamicMethods
00007fffde2085a0 40002af 8c System.Int32 1 instance -2146233033 _HResult
00007fffde2059c0 40002b0 68 System.String 0 instance 0000000000000000 _source
00007fffde2831f8 40002b1 78 System.IntPtr 1 instance 0 _xptrs
00007fffde2085a0 40002b2 90 System.Int32 1 instance -532462766 _xcode
00007fffde21e720 40002b3 80 System.UIntPtr 1 instance 0 _ipForWatsonBuckets
00007fffde1f5080 40002b4 70 …ializationManager 0 instance 0000017b30ad2e18 _safeSerializationManager
00007fffde205dd8 40002a1 100 System.Object 0 shared static s_EDILock
>> Domain:Value 0000017b2efe0af0:NotInit <<
0:000> !do 0000017b30ad2e98
Name: System.**yte[]
MethodTable: 00007fffde20dde8
EEClass: 00007fffde390920
Size: 120(0x78) bytes
Array: Rank 1, Number of elements 96, Type **yte (Print Array)
Content: ………../{…P…….@..Jm….Z…………………….Jm….Y…………………………
Fields:
None
此時 _stackTrace 已經(jīng)有值了,畢竟Console上已經(jīng)打印出來了。
最后補(bǔ)充一下大家也可以通過 !threads 去找異常的線程,如下圖的中 System.FormatException 0000017b30ad2d48,第二通過 !printexception 去打印這個地址 0000017b30ad2d48 上異常對象。
0:000> !threads
ThreadCount: 2
UnstartedThread: 0
BackgroundThread: 1
PendingThread: 0
DeadThread: 0
Hosted Runtime: no
Lock
ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception
0 1 80c 0000016816f508f0 2a020 Preemptive 0000016818CCE3B8:0000016818CCFFD0 0000016816ef0b10 0 MTA System.FormatException 0000017b30ad2d48
6 2 12d8 0000016816f7b0e0 2b220 Preemptive 0000000000000000:0000000000000000 0000016816ef0b10 0 MTA (Finalizer)
0:000> !printexception 0000017b30ad2d48
Exception object: 0000017b30ad2d48
Exception type: System.FormatException
Message: 你的格式錯誤啦!??!
InnerException: <none>
StackTrace (generated):
SP IP Function
0000001F8F7FEE90 00007FFF811E0951 ConsoleApp4!ConsoleApp4.Program.Run()+0x71
0000001F8F7FEEE0 00007FFF811E08B9 ConsoleApp4!ConsoleApp4.Program.Main(System.String[])+0x29
StackTraceString: <none>
HResult: 80131537
三:小編綜合來說
不要把異常當(dāng)做業(yè)務(wù)邏輯處理,這開銷有可能你承受不起,把那些真正不可期的情況留給異常吧,如: TimeoutException。。。
拓展知識:
原創(chuàng)文章,作者:九賢生活小編,如若轉(zhuǎn)載,請注明出處:http://m.xiesong.cn/21954.html