VC++ 崩潰處理以及打印調(diào)用堆棧

我們?cè)诔绦虬l(fā)布后總會(huì)面臨崩潰的情況杨蛋,這個(gè)時(shí)候一般很難重現(xiàn)或者很難定位到程序崩潰的位置,之前有方法在程序崩潰的時(shí)候記錄dump文件然后通過(guò)windbg來(lái)分析映砖。那種方法對(duì)開(kāi)發(fā)人員的要求較高忍法,它需要程序員理解內(nèi)存、寄存器等等一系列概念還需要手動(dòng)加載對(duì)應(yīng)的符號(hào)表毅臊。Java理茎、Python等等語(yǔ)言在崩潰的時(shí)候都會(huì)打印一條異常的堆棧信息并告訴用戶那塊出錯(cuò)了,根據(jù)這個(gè)信息程序員可以很容易找到對(duì)應(yīng)的代碼位置并進(jìn)行處理管嬉,而C/C++則會(huì)彈出一個(gè)框告訴用戶程序崩潰了皂林,二者對(duì)比來(lái)看,C++似乎對(duì)用戶太不友好了蚯撩,而且根據(jù)它的彈框很難找到對(duì)應(yīng)的問(wèn)題础倍,那么有沒(méi)有可能使c++像Java那樣打印異常的堆棧呢?這個(gè)自然是可能的胎挎,本文就是要討論如何在Windows上實(shí)現(xiàn)類(lèi)似的功能

異常處理

一般當(dāng)程序發(fā)生異常時(shí)沟启,用戶代碼停止執(zhí)行,并將CPU的控制權(quán)轉(zhuǎn)交給操作系統(tǒng)犹菇,操作系統(tǒng)接到控制權(quán)后德迹,將當(dāng)前線程的環(huán)境保存到結(jié)構(gòu)體CONTEXT中,然后查找針對(duì)此異常的處理函數(shù)项栏。系統(tǒng)利用結(jié)構(gòu)EXCEPTION_RECORD保存了異常描述信息浦辨,它與CONTEXT一同構(gòu)成了結(jié)構(gòu)體EXCEPTION_POINTERS,一般在異常處理中經(jīng)常使用這個(gè)結(jié)構(gòu)體。
異常信息EXCEPTION_RECORD的定義如下:

typedef struct _EXCEPTION_RECORD
{
   DWORD ExceptionCode;  //異常碼
   DWORD ExceptionFlags;  //標(biāo)志異常是否繼續(xù)流酬,標(biāo)志異常處理完成后是否接著之前有問(wèn)題的代碼
   struct _EXCEPTION_RECORD* ExceptionRecord; //指向下一個(gè)異常節(jié)點(diǎn)的指針币厕,這是一個(gè)鏈表結(jié)構(gòu)
   PVOID ExceptionAddress; //異常發(fā)生的地址
   DWORD NumberParameters; //異常附加信息
   ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; //異常的字符串
} EXCEPTION_RECORD,  *PEXCEPTION_RECORD;

Windows平臺(tái)提供的這一套異常處理的機(jī)制,我們叫它結(jié)構(gòu)化異常處理(SEH)芽腾,它的處理過(guò)程一般如下:

  1. 如果程序是被調(diào)試運(yùn)行的(比如我們?cè)赩S編譯器中調(diào)試運(yùn)行程序)旦装,當(dāng)異常發(fā)生時(shí),系統(tǒng)首先將異常信息交給調(diào)試程序摊滔,如果調(diào)試程序處理了那么程序繼續(xù)運(yùn)行阴绢,否則系統(tǒng)便在發(fā)生異常的線程棧中查找可能的處理代碼。若找到則處理異常艰躺,并繼續(xù)運(yùn)行程序
  2. 如果在線程棧中沒(méi)有找到呻袭,則再次通知調(diào)試程序,如果這個(gè)時(shí)候仍然不能處理這個(gè)異常腺兴,那么操作系統(tǒng)會(huì)對(duì)異常進(jìn)程默認(rèn)處理左电,這個(gè)時(shí)候一般都是直接彈出一個(gè)錯(cuò)誤的對(duì)話框然后終止程序。

系統(tǒng)在每個(gè)線程的堆棧環(huán)境中都維護(hù)了一個(gè)SEH表页响,表中是用戶注冊(cè)的異常類(lèi)型以及它對(duì)應(yīng)的處理函數(shù)篓足,每當(dāng)用戶在函數(shù)中注冊(cè)新的異常處理函數(shù),那么這個(gè)信息會(huì)被保存在鏈表的頭部闰蚕,也就是說(shuō)它是采用頭插法來(lái)插入新的處理函數(shù)栈拖,從這個(gè)角度上來(lái)說(shuō),我們可以很容易理解為什么在一般的高級(jí)語(yǔ)言中一般會(huì)先找與try塊最近的catch塊没陡,然后在找它的上層catch涩哟,由里到外依次查找。與try塊最近的catch是最后注冊(cè)的诗鸭,由于采用的是頭插法染簇,自然它會(huì)被首先處理。

在Windows中針對(duì)異常處理强岸,擴(kuò)展了__try__except 兩個(gè)操作符,這兩個(gè)操作符與c++中的try和catch非常相似,作用也基本類(lèi)似砾赔,它的一般的語(yǔ)法結(jié)構(gòu)如下:

__try
{
  //do something
}
__except(filter)
{
  //handle
}

使用 __try__except 的時(shí)候它主要分為3個(gè)部分蝌箍,分別為:保護(hù)代碼體、過(guò)濾表達(dá)式暴心、異常處理塊

  1. 保護(hù)代碼體一般是try中的語(yǔ)句妓盲,它值被保護(hù)的代碼,也就是說(shuō)我們希望處理那個(gè)代碼塊產(chǎn)生的異常
  2. 過(guò)濾表達(dá)式是 except后面擴(kuò)號(hào)中的值专普,它只能是3個(gè)值中的一個(gè)悯衬,EXCEPTION_CONTINUE_SEARCH繼續(xù)向下查找異常處理,也就是說(shuō)這里的異常處理塊不處理這種異常檀夹,EXCEPTION_CONTINUE_EXECUTION表示異常已被處理筋粗,這個(gè)時(shí)候可以繼續(xù)執(zhí)行直線產(chǎn)生異常的代碼策橘,EXCEPTION_EXECUTE_HANDLER表示異常已被處理,此時(shí)直接跳轉(zhuǎn)到except里面的代碼塊中娜亿,這種方式下它的執(zhí)行流程與一般的異常處理的流程類(lèi)似.
  3. 異常處理塊丽已,指的是except下面的擴(kuò)號(hào)中的代碼塊.

注意:我們說(shuō)過(guò)濾表達(dá)式只能是這三個(gè)值中的一個(gè),但是沒(méi)有說(shuō)這里一定得填這三個(gè)值买决,它還支持函數(shù)或者其他的表達(dá)式類(lèi)型沛婴,只要函數(shù)或者表達(dá)式的返回值是這三個(gè)值中的一個(gè)即可。

上述的方式也有他的局限性督赤,也就是說(shuō)它只能保護(hù)我們指定的代碼嘁灯,如果是在 __try 塊之外的代碼發(fā)生了崩潰,可能還是會(huì)造成程序被kill掉躲舌,而且每個(gè)位置都需要寫(xiě)上這么些代碼實(shí)在是太麻煩了丑婿。其實(shí)處理異常還有一種方式,那就是采用 SetUnhandledExceptionFilter來(lái)注冊(cè)一個(gè)全局的異常處理函數(shù)來(lái)處理所有未被處理的異常孽糖,其實(shí)它的主要工作原理就是往異常處理的鏈表頭上添加一個(gè)處理函數(shù)枯冈,函數(shù)的原型如下:

LPTOP_LEVEL_EXCEPTION_FILTER WINAPI SetUnhandledExceptionFilter(__in  LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter);

它需要傳入一個(gè)函數(shù),以便發(fā)生異常的時(shí)候調(diào)用這個(gè)函數(shù)办悟,這個(gè)回調(diào)函數(shù)的原型如下:

LONG WINAPI UnhandledExceptionFilter(
  __in  struct _EXCEPTION_POINTERS* ExceptionInfo
);

回調(diào)函數(shù)會(huì)傳入一個(gè)表示當(dāng)前堆棧和異常信息的結(jié)構(gòu)體的指針尘奏,結(jié)構(gòu)的具體信息請(qǐng)參考MSDN, 函數(shù)會(huì)返回一個(gè)long型的數(shù)值,這個(gè)數(shù)值為上述3個(gè)值中的一個(gè)病蛉,表示當(dāng)系統(tǒng)調(diào)用了這個(gè)異常處理函數(shù)處理異常之后該如何繼續(xù)執(zhí)行用戶代碼炫加。

SetUnhandledExceptionFilter 函數(shù)返回一個(gè)函數(shù)指針,這個(gè)指針指向鏈表的頭部铺然,如果插入處理函數(shù)失敗那么它將指向原來(lái)的鏈表頭俗孝,否則指向新的鏈表頭(也就是注冊(cè)的這個(gè)回調(diào)函數(shù)的地址)

而這次要實(shí)現(xiàn)這么一個(gè)能打印異常信息和調(diào)用堆棧的功能就是要使用這個(gè)方法。

打印函數(shù)調(diào)用堆棧

關(guān)于打印堆棧的內(nèi)容魄健,這里不再多說(shuō)了赋铝,請(qǐng)參考本人之前寫(xiě)的博客
windows平臺(tái)調(diào)用函數(shù)堆棧的追蹤方法
這里的主要思路是使用StackWalker來(lái)根據(jù)當(dāng)前的堆棧環(huán)境來(lái)獲取對(duì)應(yīng)的函數(shù)信息,這個(gè)信息需要根據(jù)符號(hào)表來(lái)生成沽瘦,因此我們需要首先加載符號(hào)表革骨,而獲取當(dāng)前線程的環(huán)境,我們可以像我博客中寫(xiě)的那樣使用GetThreadContext來(lái)獲取析恋,但是在異常中就簡(jiǎn)單的多了良哲,還記得異常處理函數(shù)的原型嗎?異常處理函數(shù)本身會(huì)帶入一個(gè)EXCEPTION_POINTERS結(jié)構(gòu)的指針助隧,而這個(gè)結(jié)構(gòu)中就包含了異常堆棧的信息筑凫。

還有一些需要注意的問(wèn)題,我把它放到實(shí)現(xiàn)那塊了,請(qǐng)小心的往下看_

實(shí)現(xiàn)

實(shí)現(xiàn)部分的源碼我放到了github上巍实,地址

這個(gè)項(xiàng)目中主要分為兩個(gè)類(lèi)CBaseException滓技,主要是對(duì)異常的一個(gè)簡(jiǎn)單的封裝,提供了我們需要的一些功能蔫浆,比如獲取加載的模塊的信息殖属,獲取調(diào)用的堆棧,以及解析發(fā)生異常時(shí)的相關(guān)信息瓦盛。而這些的基礎(chǔ)都在CStackWalker中洗显。
使用上,我把CBaseException中的大部分函數(shù)都定義成了virtual 允許進(jìn)行重寫(xiě)原环。因?yàn)榫唧w我還沒(méi)想好這塊后續(xù)會(huì)需要進(jìn)行哪些擴(kuò)展挠唆。但是里面最主要的功能是OutputString函數(shù),這個(gè)函數(shù)是用來(lái)進(jìn)行信息輸出的嘱吗,默認(rèn)CBaseException是將信息輸出到控制臺(tái)上玄组,后續(xù)可以重載這個(gè)函數(shù)把數(shù)據(jù)輸出到日志中。

CBaseException 類(lèi)

CBaseException 主要是用來(lái)處理異常谒麦,在代碼里面我提供了兩種方式來(lái)進(jìn)行異常處理俄讹,第一種是通過(guò) SetUnhandledExceptionFilter 來(lái)注冊(cè)一個(gè)全局的處理函數(shù),這個(gè)函數(shù)是類(lèi)中的靜態(tài)函數(shù)UnhandledExceptionFilter绕德,在這個(gè)函數(shù)中我主要根據(jù)異常的堆棧環(huán)境來(lái)初始化了一個(gè)CBaseException類(lèi)患膛,然后簡(jiǎn)單的調(diào)用類(lèi)的方法顯示異常與堆棧的相關(guān)信息。第二種是通過(guò) _set_se_translator 來(lái)注冊(cè)一個(gè)將SEH轉(zhuǎn)化為C++異常的方法耻蛇,在對(duì)應(yīng)的回調(diào)中我簡(jiǎn)單的拋出了一個(gè)CBaseException的異常踪蹬,在具體的代碼中只要簡(jiǎn)單的用c++的異常處理捕獲這么一個(gè)異常即可

CBaseException 類(lèi)中主要用來(lái)解析異常的信息,里面提供這樣功能的函數(shù)主要有3個(gè)

  1. ShowExceptionResoult: 這個(gè)函數(shù)主要是根據(jù)異常碼來(lái)獲取到異常的具體字符串信息臣咖,比如非法內(nèi)存訪問(wèn)跃捣、除0異常等等
  2. GetLogicalAddress:根據(jù)發(fā)生異常的代碼的地址來(lái)獲取對(duì)應(yīng)的模塊信息,比如它在PE文件中屬于第幾個(gè)節(jié)夺蛇,節(jié)的地址范圍等等疚漆,它在實(shí)現(xiàn)上首先使用 VirtualQuery來(lái)獲取對(duì)應(yīng)的虛擬內(nèi)存信息,主要是這個(gè)模塊的首地址信息刁赦,然后解析PE文件獲取節(jié)表的信息愿卸,我們循環(huán)節(jié)表中的每一項(xiàng),根據(jù)節(jié)表中的地址范圍來(lái)判斷它屬于第幾個(gè)節(jié)截型,注意這里我們根據(jù)它在內(nèi)存中的偏移計(jì)算了它在PE文件中的偏移,具體的計(jì)算方式請(qǐng)參考PE文件的相關(guān)內(nèi)容.
    3.ShowRegistorInformation:獲取各個(gè)寄存器的值儒溉,這個(gè)值保存在CONTEXT結(jié)構(gòu)中宦焦,我們只需要簡(jiǎn)單打印它就好

CStackWalker類(lèi)

這個(gè)類(lèi)主要實(shí)現(xiàn)一些基礎(chǔ)的功能,它主要提供了初始化符號(hào)表環(huán)境、獲取對(duì)應(yīng)的調(diào)用堆棧信息波闹、獲取加載的模塊信息
在初始化符號(hào)表的時(shí)候盡可以多的遍歷了常見(jiàn)的幾種符號(hào)表的位置并將這些位置中的符號(hào)表加載進(jìn)來(lái)酝豪,以便能更好的獲取到堆棧調(diào)用的情況。在獲取到對(duì)應(yīng)的符號(hào)表位置后有這樣的代碼

if (NULL != m_lpszSymbolPath)
{
        m_bSymbolLoaded = SymInitialize(m_hProcess, T2A(m_lpszSymbolPath), TRUE); //這里設(shè)置為T(mén)RUE精堕,讓它在初始化符號(hào)表的同時(shí)加載符號(hào)表
}

DWORD symOptions = SymGetOptions();
symOptions |= SYMOPT_LOAD_LINES;
symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
symOptions |= SYMOPT_DEBUG;
SymSetOptions(symOptions);

return m_bSymbolLoaded;

這里將 SymInitialize的最后一個(gè)函數(shù)置為T(mén)RUE孵淘,這個(gè)參數(shù)的意思是是否枚舉加載的模塊并加載對(duì)應(yīng)的符號(hào)表,直接在開(kāi)始的時(shí)候加載上可能會(huì)比較浪費(fèi)內(nèi)存歹篓,這個(gè)時(shí)候我們可以采用動(dòng)態(tài)加載的方式瘫证,在初始化的時(shí)候先填入FALSE,然后在需要的時(shí)候自己枚舉所有的模塊庄撮,然后手動(dòng)加載所有模塊的符號(hào)表背捌,手動(dòng)加載需要調(diào)用SymLoadModuleEx。這里需要提醒各位的是洞斯,這里如果填的是FALSE的話毡庆,后續(xù)一定得自己加載模塊的符號(hào)表,否則在后續(xù)調(diào)用SymGetSymFromAddr64的時(shí)候會(huì)得到一堆的487錯(cuò)誤(也就是地址無(wú)效)
我之前就是這個(gè)問(wèn)題困擾了我很久的時(shí)間烙如。

在獲取模塊的信息時(shí)主要提供了兩種方式么抗,一種是使用CreateToolhelp32Snapshot 函數(shù)來(lái)獲取進(jìn)程中模塊信息的快照然后調(diào)用Module32Next 和 Module32First來(lái)枚舉模塊信息,還有一種是使用EnumProcessModules來(lái)獲取所有模塊的句柄亚铁,然后根據(jù)句柄來(lái)獲取模塊的信息蝇刀,當(dāng)然還有另外的方式,其他的方式可以參考我的這篇博客 枚舉進(jìn)程中的模塊

在枚舉加載的模塊的同時(shí)還針對(duì)每個(gè)模塊調(diào)用了 GetModuleInformation 函數(shù)刀闷,這個(gè)函數(shù)主要有兩個(gè)功能熊泵,獲取模塊文件的版本號(hào)和獲取加載的符號(hào)表信息。

接下來(lái)就是重頭戲了——獲取調(diào)用堆棧甸昏。獲取調(diào)用堆棧首先得獲取當(dāng)前的環(huán)境顽分,在代碼中進(jìn)行了相應(yīng)的判斷,如果當(dāng)前傳入的CONTEXT為NULL施蜜,則函數(shù)自己獲取當(dāng)前的堆棧信息卒蘸。在獲取堆棧信息的時(shí)候首先判斷是否為當(dāng)前線程,如果不是那么為了結(jié)果準(zhǔn)確翻默,需要先停止目標(biāo)線程缸沃,然后獲取,否則直接使用宏來(lái)獲取修械,對(duì)應(yīng)的宏定義如下:

#define GET_CURRENT_THREAD_CONTEXT(c, contextFlags) \
    do\
    {\
        memset(&c, 0, sizeof(CONTEXT));\
        c.ContextFlags = contextFlags;\
        __asm    call $+5\
        __asm    pop eax\
        __asm    mov c.Eip, eax\
        __asm    mov c.Ebp, ebp\
        __asm    mov c.Esp, esp\
} while (0)

在調(diào)用StackWalker時(shí)只需要關(guān)注esp ebp eip的信息趾牧,所以這里我們也只簡(jiǎn)單的獲取這些寄存器的環(huán)境,而其他的就不管了肯污。這樣有一個(gè)問(wèn)題翘单,就是我們是在CStackWalker類(lèi)中的函數(shù)中獲取的這個(gè)線程環(huán)境吨枉,那么這個(gè)環(huán)境里面會(huì)包含CStackWalker::StackWalker,結(jié)果自然與我們想要的不太一樣(我們想要的是隱藏這個(gè)庫(kù)中的相關(guān)信息哄芜,而只保留調(diào)用者的相關(guān)堆棧信息)貌亭。這個(gè)問(wèn)題我還沒(méi)有什么好的解決方案。

在獲取到線程環(huán)境后就是簡(jiǎn)單的調(diào)用StackWalker以及那堆Sym開(kāi)頭的函數(shù)來(lái)獲取各種信息了认臊,這里就不再詳細(xì)說(shuō)明了圃庭。

至此這個(gè)功能已經(jīng)實(shí)現(xiàn)的差不多了。庫(kù)的具體使用請(qǐng)參考main.cpp這個(gè)文件失晴,相信有這篇博文以及源碼各位應(yīng)該很容易就能夠使用它剧腻。

據(jù)說(shuō)這些函數(shù)不是多線程安全的,我自己沒(méi)有在多線程環(huán)境下進(jìn)行測(cè)試师坎,所以具體它在多線程環(huán)境下表現(xiàn)如何還是個(gè)未知數(shù)恕酸,如果后續(xù)我有興趣繼續(xù)完善它的話,可能會(huì)加入多線程的支持胯陋。
<hr />

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蕊温,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子遏乔,更是在濱河造成了極大的恐慌义矛,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盟萨,死亡現(xiàn)場(chǎng)離奇詭異凉翻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)捻激,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)制轰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人胞谭,你說(shuō)我怎么就攤上這事垃杖。” “怎么了丈屹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵调俘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我旺垒,道長(zhǎng)彩库,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任先蒋,我火速辦了婚禮骇钦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘竞漾。我一直安慰自己司忱,他們只是感情好皇忿,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著坦仍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叨襟。 梳的紋絲不亂的頭發(fā)上汇陆,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天炼蹦,我揣著相機(jī)與錄音,去河邊找鬼。 笑死单料,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丹擎。 我是一名探鬼主播砌滞,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼念链!你這毒婦竟也來(lái)了盼忌?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤掂墓,失蹤者是張志新(化名)和其女友劉穎谦纱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體君编,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡跨嘉,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了吃嘿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片祠乃。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖兑燥,靈堂內(nèi)的尸體忽然破棺而出亮瓷,到底是詐尸還是另有隱情,我是刑警寧澤贪嫂,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布寺庄,位于F島的核電站,受9級(jí)特大地震影響力崇,放射性物質(zhì)發(fā)生泄漏斗塘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一亮靴、第九天 我趴在偏房一處隱蔽的房頂上張望馍盟。 院中可真熱鬧,春花似錦茧吊、人聲如沸贞岭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瞄桨。三九已至话速,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間芯侥,已是汗流浹背泊交。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留柱查,地道東北人廓俭。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像唉工,于是被迫代替她去往敵國(guó)和親研乒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 29,321評(píng)論 8 265
  • 看完這封信不知道你們的心情是怎樣的淋硝!我為作者感到開(kāi)心雹熬!他還在奮斗、還在堅(jiān)持著奖地,并沒(méi)有被壓力擊倒橄唬! 曾幾何時(shí),高...
    小豬_笨笨閱讀 221評(píng)論 0 0
  • 也算是結(jié)束了一段感情参歹,傷感對(duì)于我而言貌似絲毫不存在仰楚,不,是有一點(diǎn)點(diǎn)難過(guò)的犬庇,也許是覺(jué)得僧界,從此以后再也遇不到像他那樣對(duì)...
    908dc7f54129閱讀 399評(píng)論 0 0
  • wei小海閱讀 330評(píng)論 0 0
  • __call() 方法用于監(jiān)視錯(cuò)誤的方法調(diào)用。 __call()(Method overloading) 為了避免...
    雪碧spirit閱讀 608評(píng)論 0 1