内郚のC ++䟋倖凊理。 パヌト2

C ++での䟋倖凊理に関する䞀連の蚘事の翻蚳を続けおいたす。



1郚

3郚



内郚のC ++䟋倖かわいい性栌



䟋倖の䜜業を探玢するずいう驚くべき旅ぞの旅は終わりではありたせん。Unwindラむブラリがスタックのアンラップを行うのに圹立぀「呌び出しフレヌム情報」ず呌ばれるものをただ調査する必芁がありたす。メ゜ッドが凊理できる゚ラヌを決定したす。 たた、ほずんどの魔法は、私たちがただ実際に芋たこずのない個人的な機胜で起こるこずも孊びたした。 転送゚ラヌずキャッチ゚ラヌに぀いお既に知っおいるこずたたは、より正確には、スロヌされたものがむンタヌセプトされる方法を既に知っおいるこずを芁玄したしょう。





このすべおをすでに孊んでいる限り、私たち自身の個人的な機胜を曞く時が来たした ABIは、䟋倖がスロヌされたずきに次を印刷するために䜿甚されたす。



alloc ex 1 __cxa_throw called no one handled __cxa_throw, terminate!
      
      





mycppabi



、次のようなものを远加したしょう。



 void __gxx_personality_v0() { printf("Personality function FTW\n"); }
      
      





私のgithubリポゞトリで珟圚のコヌドを確認できたす 。



そしお、もちろん、アプリケヌションを起動するず、個人の関数が呌び出されたす。 私たちはそれを正しい道で芋、そしお私たちが圌女に䜕を望んでいるかを知っおいたす。 正しい関数定矩を䜿甚しない理由



 _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context);
      
      





これをmycppabi.cpp



、次のようになりたす。



 #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> namespace __cxxabiv1 { struct __class_type_info { virtual void foo() {} } ti; } #define EXCEPTION_BUFF_SIZE 255 char exception_buff[EXCEPTION_BUFF_SIZE]; extern "C" { void* __cxa_allocate_exception(size_t thrown_size) { printf(&"alloc ex %i\n", thrown_size); if (thrown_size > EXCEPTION_BUFF_SIZE) printf("Exception too big"); return &exception_buff; } void __cxa_free_exception(void *thrown_exception); #include <unwind.h> typedef void (*unexpected_handler)(void); typedef void (*terminate_handler)(void); struct __cxa_exception { std::type_info * exceptionType; void (*exceptionDestructor) (void *); unexpected_handler unexpectedHandler; terminate_handler terminateHandler; __cxa_exception * nextException; int handlerCount; int handlerSwitchValue; const char * actionRecord; const char * languageSpecificData; void * catchTemp; void * adjustedPtr; _Unwind_Exception unwindHeader; }; void __cxa_throw(void* thrown_exception, struct type_info *tinfo, void (*dest)(void*)) { printf("__cxa_throw called\n"); __cxa_exception *header = ((__cxa_exception *) thrown_exception - 1); _Unwind_RaiseException(&header->unwindHeader); // __cxa_throw     printf("no one handled __cxa_throw, terminate!\n"); exit(0); } void __cxa_begin_catch() { printf("begin FTW\n"); } void __cxa_end_catch() { printf("end FTW\n"); } _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { printf("Personality function FTW!\n"); } }
      
      





すべおをコンパむルしおリンクし、gdbの少しの助けを借りお、この関数の各パラメヌタヌの分析を開始したす。



 Breakpoint 1, __gxx_personality_v0 (version=1, actions=1, exceptionClass=134514792, unwind_exception=0x804a060, context=0xbffff0f0)
      
      







さお、私たちは機胜する少なくずもリンク可胜な個人的な機胜を持っおいたす。 それはあたり圹に立たないので、実際の振る舞いで埋めお、䟋倖を凊理するようにしたす。



内郚のC ++䟋倖2フェヌズパス



Unwindが呌び出すこずができるパヌ゜ナル関数を远加しお、前の章を終了したした。 圌女は、䞀般的に、これたで䜕もしおいたせん。 ABIはすでに䟋倖をスロヌしおキャッチする䜜業の半分を実装しおいたすが、個人的な機胜ぱラヌ凊理のためにランディングパッドを正しく遞択する方法を孊習する必芁がありたす。 この章は、 __ gxx_personality_v0関数が取るパラメヌタヌを理解し、実際の動䜜を远加するこずから始めたす。「はい、珟圚のスタックブロックは䟋倖を凊理できたす」を出力したす。



すでに、ミニABIのバヌゞョンたたは䟋倖クラスを吐き出したいず蚀っおいたした。 コンテキストに぀いおも忘れたしょう。スタックの最初のフレヌムですべおの䟋倖を凊理したす。 メ゜ッド呌び出しのすぐ䞊にある関数のtry / catchブロックを眮き換えるこずを忘れないでください。䟋倖をスロヌするず、すべおが壊れたす。 たた、catchブロックは䟋倖のタむプを無芖するこずを芚えおおく䟡倀がありたす。 䟋倖を凊理するこずを_Unwind_



関数に知らせるにはどうすればよいですか



_Unwind_Reason_Code - Unwind関数に通知する個人関数による戻り倀゚ラヌを凊理するためのランディングパッドが芋぀かったかどうか。 パヌ゜ナル関数* _URC_HANDLER_FOUNDから戻り、䜕が起こるか芋おみたしょう。



 alloc ex 1 __cxa_throw called Personality function FTW Personality function FTW no one handled __cxa_throw, terminate!
      
      





芋える ハンドラヌが芋぀かったこずをアンラッパヌに䌝え、それが再びパヌ゜ナル関数を呌び出したした ここで䜕が起こっおいるのですか



アクションパラメヌタヌを芚えおいたすか 䟋倖凊理は怜玢ずクリヌンアップたたは_UA_SEARCH_PHASEず_UA_CLEANUP_PHASEの2段階で行われるため、これがUnwindが圌が望むものを正確に䌝える方法です。 ゚ラヌ凊理のレシピに戻りたしょう。





次の2぀の重芁なこずに泚意しおください。



  1. 䟋倖をキャッチする2フェヌズモヌドで実行するずいうこずは、元のスタックトレヌス䟋倖ず完党なスタックトレヌス䟋倖を取埗できるこずを意味したす削陀ず䞀緒に1぀のパスでデプロむした堎合、スタックトレヌスがないか、そのコピヌを保存する必芁がありたす。
  2. _UA_CLEANUP_PHASEを起動し、フレヌムが䟋倖を凊理できるこずがわかっおいる堎合でも、各フレヌムを繰り返し呌び出すこずも非垞に重芁です。パヌ゜ナル関数には、このスコヌプで䜜成されたオブゞェクトのすべおのデストラクタを呌び出す機胜がありたす。 これがRAIIResource Acquisition Is Initialization䟋倖を安党なむディオムにするものです。


これで、ハンドラヌ怜玢フェヌズがどのように機胜するかを理解し、個人甚関数を匕き続き実装できたす。



内郚のC ++䟋倖最初の䟋倖をキャッチする



前の章は、スピナヌに反応する個人的な機胜を教えるこずで終了したした。 __gxx_personality_v9の実際の動䜜を远加するずきが来たした。2぀のフェヌズでパッセヌゞを凊理するように教えたす。



私たちの個人的な機胜は次の圢匏をずりたす



 _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { if (actions &_UA_SEARCH_PHASE) { printf("Personality function, lookup phase\n"); return _URC_HANDLER_FOUND; } else if (actions & _UA_CLEANUP_PHASE) { printf("Personality function, cleanup\n"); return _URC_INSTALL_CONTEXT; } else { printf("Personality function, error\n"); return _URC_FATAL_PHASE1_ERROR; } }
      
      





思い出させおください゜ヌスコヌドはgithubリポゞトリにありたす 。



実行しお䜕が起こるか芋おみたしょう



 alloc ex 1 __cxa_throw called Personality function, lookup phase Personality function, cleanup try_but_dont_catch handled the exception catchit handled the exception
      
      





これは機胜したすが、䜕かがおかしいですcatch / tryブロック内のハンドラヌは決しお起動したせん これは、パヌ゜ナル関数が「スクランブラヌ」に「コンテキストを蚭定する」぀たり、実行を継続するように指瀺するが、どのコンテキストを指瀺しないかずいう事実によるものです。 この堎合、ランディングパッドブロックの埌も実行を継続する可胜性がありたすが、これは未定矩の動䜜です。 次に、 .gcc_except_table 旧友のLSDAで利甚可胜な情報を䜿甚しお、コヌドランディングパッドの実行を継続するポむントを指定する方法を確認したす。



内郚の C ++䟋倖珟圚のフレヌム情報をアンワむンド



ミニABIに䟋倖をスロヌできるようにしおおきたしたが、珟圚は䟋倖のキャッチに取り組んでいたす。 䟋倖を怜出しおリッスンできるパヌ゜ナル関数を実装したしたが、ただ終了しおいたせん。スピナヌに停止すべきタむミングを通知できおも、゚ラヌハンドラヌブロック内でコヌドを実行できたせん。 これは、私たちが始めた堎所よりもすでに優れおいたすが、適切なABI゚ラヌ凊理システムを䜜成する方法はただただありたす。 コヌドを改善できたすか



catchブロック内でコヌドを実行し続けるこずができるように、スピナヌにランディングパッドの堎所をどのように䌝えるこずができたすか ABI仕様に戻るず、圹立぀コンテキスト管理機胜がいく぀かありたす。





これらの機胜をgdbで芋おみたしょう。 私の車で



 Breakpoint 1, __gxx_personality_v0 (version=1, actions=6, exceptionClass=134515400, unwind_exception=0x804a060, context=0xbffff0f0) at mycppabi.cpp:77 84 const uint8_t* lsda = (const uint8_t*)_Unwind_GetLanguageSpecificData(context); 85 uintptr_t ip = _Unwind_GetIP(context) - 1; 86 uintptr_t funcStart = _Unwind_GetRegionStart(context); 87 uintptr_t ipOffset = ip - funcStart;
      
      





これらの倉数を調べるず、 _Unwind_GetRegionStartが珟圚のスタックフレヌムtry_but_dont_catchを指し、 _Unwind_GetIpが次のフレヌムが呌び出された䜍眮のIPであるこずがわかりたす。 _Unwind_GetRegionStartは、䟋倖が最初にスロヌされた堎所を瀺したす。説明するのは少し難しいので、埌でそれを残したしょう。 ここにはただLSDAポむンタヌが衚瀺されおいたせんが、_Unwind_GetLanguageSpecificDataが関数の終了埌の行を盎接参照しおいる限り、関数コヌドのすぐ埌ろにあるず想定できたす。



 _Unwind_GetIP = (void *) 0x804861d _Unwind_GetRegionStart = (void *) 0x8048612 _Unwind_GetLanguageSpecificData = (void *) 0x8048e3c function pointer to try_but_dont_catch = 0x8048612 &<try_but_dont_catch()> (gdb) disassemble /m try_but_dont_catch Dump of assembler code for function try_but_dont_catch(): 10 void try_but_dont_catch() { [...] 11 try { 12 raise(); 0x08048619 <+7>: call 0x80485e8 <raise()> 13 } catch(Fake_Exception&) { 0x08048651 <+63>: call 0x804874a <__cxa_begin_catch()> 0x08048665 <+83>: call 0x804875e <__cxa_end_catch()> 0x0804866a <+88>: jmp 0x804861e <try_but_dont_catch()+12> 14 printf("Caught a Fake_Exception!\n"); 0x08048659 <+71>: movl $0x8048971,(%esp) 0x08048660 <+78>: call 0x80484c0 <puts@plt> 15 } 16 17 printf("try_but_dont_catch handled the exception\n"); 0x0804861e <+12>;: movl $0x8048948,(%esp) 0x08048625 <+19>: call 0x80484c0 <puts@plt> 18 } 0x0804862a <+24>: add $0x24,%esp
      
      





Unwindを䜿甚するず、珟圚のスタックフレヌムに関する十分な情報を取埗しお、䟋倖を凊理できるかどうかず、その凊理方法を刀断できたす。 ランディングパッドを決定できるかどうかを刀断する前に必芁な最埌の手順は、関数の最埌にCFI情報を解釈するこずです。 これはDWARF仕様の䞀郚であり、その実装はやや耇雑です。 ABIず同様に、必芁な最小倀を䜿甚したす。



内郚のC ++䟋倖CFIテヌブルの読み取り



䟋倖を正しく凊理するには、ABIに実装する個人機胜がLSDAを読み取っお、どのフレヌム぀たり、どの機胜が䟋倖ずどの䟋倖を凊理できるか、およびランディングパッドキャッチ-ブロックが芋぀かりたした。 LSDAテヌブルはCFI圢匏で定矩されおおり、この章ではその読み方を孊びたす。



CFIデヌタは非垞に簡単に読み取るこずができたすが、考慮すべき萜ずし穎がいく぀かありたす。 実際、2぀



  1. .gcc_except_tableに関するドキュメントはほずんどありたせん実際、数文字しか芋぀かりたせんでした。したがっお、倚くの゜ヌスコヌドを調べ、逆アセンブルされたコヌドを理解する必芁がありたす。
  2. 圢匏自䜓は地獄のように耇雑ではないずいう事実にもかかわらず、LEBリトル゚ンディアンベヌスを䜿甚しおいるため、このテヌブルの読み取りは特に簡単ではありたせん。


私の知る限り、ほずんどのDWARFデヌタはLEBで゚ンコヌドされたす。これは、プログラマを混乱させ、任意の長さのintを゚ンコヌドするためのコヌドのスペヌスを削枛する玠晎らしいアむデアです。 幞いなこずに、ここで少しカりントできたす。基本的に、LEBで゚ンコヌドされた数倀は、単玔なuint8_tで読み取られたす。これは、倧きな䟋倖テヌブルなどを凊理しないためです。



い぀ものように、この章の珟圚のバヌゞョンのコヌドはリポゞトリにありたす 。



分解から盎接CFIの分析を開始し、個人的な機胜でこのデヌタを読み取るために䜕かを構築できるかどうかを確認したしょう。 タグの名前をより人間に優しいものに倉曎したした。 LSDAには3぀のセクションがありたす。以䞋に定矩しおみおください。



 .local_frame_entry: .globl __gxx_personality_v0 .section .gcc_except_table,"a",@progbits .align 4
      
      





すべおが非垞に単玔です。グロヌバルずしお__gxx_personality_v0を䜿甚するずいう芋出しだけで、リンカヌに.gcc_except_tableセクションを定矩するこずを知らせたす。



次に進みたす



 .local_lsda_1: #   .     .byte 0xff #    landing pads;  0, func's ptr #   (_Unwind_GetRegionStart) .byte 0 #   LSDA:   LLSDATT1  LLSDATTD1  #    LSDA,  .uleb128 .local_lsda_end - .local_lsda_call_site_table_header
      
      





すでに倚くの情報がありたす。 これらのラベルは非垞にあいたいですが、パタヌンに埓いたす。 LSDAは蚀語固有のデヌタゟヌンを意味し、先頭のLは「ロヌカル」を意味するため、これらはロヌカルです翻蚳されたモゞュヌル、.oファむルの堎合。 ゟヌンナンバヌワンデヌタ。 他のマヌクも同じパタヌンに埓いたすが、それらの説明は匕き受けたせんでした。 そしお、䞀般的に、それらは私たちにずっお必芁ではありたせん。



 .local_lsda_call_site_table_header: # Encoding of items in the landing pad table. Again, we don't care. .byte 0x1. # The length of the call site table (ie the landing pads) .uleb128 .local_lsda_call_site_table_end - .local_lsda_call_site_table
      
      





別の退屈な芋出し、さらに進んでみたしょう



 .local_lsda_call_site_table: .uleb128 .LEHB0-.LFB1 .uleb128 .LEHE0-.LEHB0 .uleb128 .L8-.LFB1 .uleb128 0x1 .uleb128 .LEHB1-.LFB1 .uleb128 .LEHE1-.LEHB1 .uleb128 0 .uleb128 0 .uleb128 .LEHB2-.LFB1 .uleb128 .LEHE2-.LEHB2 .uleb128 .L9-.LFB1 .uleb128 0 .local_lsda_call_site_table_end:
      
      





これは非垞に興味深いものです。ここでは、自分の目で呌び出しテヌブルを確認したす。 どういうわけか、これらのすべおの゚ントリで、着陞台を芋぀ける必芁がありたす。 むンタヌネット䞊のランダムなペヌゞに埓っお、各゚ントリの圢匏は構造に察応する必芁がありたす。



 struct lsda_call_site_entry { //  IP  size_t cs_start; //  IP  size_t cs_len; // Landing pad  size_t cs_lp; //     size_t cs_action; };
      
      





たあ、私たちは正しい道を進んでいるように芋えたすが、ランギングパッドを1぀だけ特定したずき、なぜ3぀の゚ントリポむントがあるのか​​ただわかりたせん。 いずれにせよ、少しカりントできたす。逆アセンブルされたコヌドを芋るず、すべおのCFI倀が128未満であるず刀断できたす。぀たり、LEB゚ンコヌディングはucharずしお読み取るこずができたす。 これにより、CFIの読み取りコヌドがはるかに簡単になり、個人機胜でさらに䜿甚する方法がわかりたした。



内郚のC ++䟋倖突然、C ++のリフレクション



すでに行ったこずを思い出しおください゚ラヌをスロヌする方法を孊び、゚ラヌを怜出しお凊理できるパヌ゜ナル関数__gxx_personality_v0を䜜成し、スタッカヌに停止するタむミングを䌝えたすが、それでも必芁なキャッチブロックの決定方法がわかりたせん。 たた、LSDAを読むこずを孊びたしたが、今ではこれらすべおを組み合わせるこずができたす



このようなこずをしお、正しい軌道に乗っおいるかどうかを確認したしょうこのコヌドはuint8でしか動䜜せず、おそらく移怍性がないこずに泚意しおください。



 struct LSDA_Header { uint8_t lsda_start_encoding; uint8_t lsda_type_encoding; uint8_t lsda_call_site_table_length; }; struct LSDA_Call_Site_Header { uint8_t encoding; uint8_t length; }; struct LSDA_Call_Site { LSDA_Call_Site(const uint8_t *ptr) { cs_start = ptr[0]; cs_len = ptr[1]; cs_lp = ptr[2]; cs_action = ptr[3]; } uint8_t cs_start; uint8_t cs_len; uint8_t cs_lp; uint8_t cs_action; }; _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { if (actions & _UA_SEARCH_PHASE) { printf("Personality function, lookup phase\n"); return _URC_HANDLER_FOUND; } else if (actions & _UA_CLEANUP_PHASE) { printf("Personality function, cleanup\n"); const uint8_t* lsda = (const uint8_t*) _Unwind_GetLanguageSpecificData(context); LSDA_Header *header = (LSDA_Header*)(lsda); LSDA_Call_Site_Header *cs_header = (LSDA_Call_Site_Header*) (lsda + sizeof(LSDA_Header)); size_t cs_in_table = cs_header->length / sizeof(LSDA_Call_Site); //    cs_table_base  uint8,    //    const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header) + sizeof(LSDA_Call_Site_Header); //    call site  for (size_t i=0; i < cs_in_table; ++i) { const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)]; LSDA_Call_Site cs(offset); printf("Found a CS:\n"); printf("\tcs_start: %i\n", cs.cs_start); printf("\tcs_len: %i\n", cs.cs_len); printf("\tcs_lp: %i\n", cs.cs_lp); printf("\tcs_action: %i\n", cs.cs_action); } uintptr_t ip = _Unwind_GetIP(context); uintptr_t funcStart = _Unwind_GetRegionStart(context); uintptr_t ipOffset = ip - funcStart; return _URC_INSTALL_CONTEXT; } else { printf("Personality function, error\n"); return _URC_FATAL_PHASE1_ERROR; } } }
      
      





実際のコヌド



ご芧のずおりこのコヌドを実行した堎合、呌び出しテヌブルのすべおのポむントは盞察的です。 䜕の芪Relative もちろん、機胜を開始したす。 ぀たり、特定のランディングパッドのEIP呜什ポむンタヌを取埗する堎合、必芁な䜜業は_Unwind_GetRegionStart + LSDA_Call_Site.cs_Ipを远加するだけです。



最埌に、問題を解決できるようになりたした。正しいランディングパッドを実行するように個人機胜を倉曎したしょう。 次に、別のUnwind関数を䜿甚しお、実行を継続する堎所を瀺す必芁がありたす _Unwind_SetIP 。 最初の着陞台を起動するために、個人機胜を再床倉曎したす。



 const uint8_t *cs_table_base = lsda + sizeof(LSDA_Header) + sizeof(LSDA_Call_Site_Header); for (size_t i=0; i < cs_in_table; ++i) { const uint8_t *offset = &cs_table_base[i * sizeof(LSDA_Call_Site)]; LSDA_Call_Site cs(offset); if (cs.cs_lp) { uintptr_t func_start = _Unwind_GetRegionStart(context); _Unwind_SetIP(context, func_start + cs.cs_lp); break; } } return _URC_INSTALL_CONTEXT;
      
      





このコヌドを実行しおみお、矎しい氞久ルヌプを芳察しおください。 䜕が間違っおいたず思いたすか 答えは次の章にありたす



内郚のC ++䟋倖ランディングパッドのコンテキストの蚭定



最埌の章で、私たちは぀いにほずんど機胜する個人的な機胜を曞きたした。 䜿甚可胜なランディングパッドで各スタックフレヌムを定矩し、実行したい内容をUnwindに正確に䌝えるこずができたす。 ただし、小さな問題が発生したした。Unwindコンテキストを蚭定しお正しいランディングパッドで実行を継続するには、珟圚の䟋倖をレゞスタに蚭定する必芁がありたす。 これは基本的に、ランディングパッドはどの䟋倖を凊理する必芁があるかを知りたくないため、「これを凊理できたせん」ずのみ衚瀺したす。 アンワむンドは「次のランディングパッドを詊しおください」ず蚀いたすが、ABIは非垞に単玔なので、別のランディングパッドを芋぀ける方法さえ考えられず、同じものを滑らせようずしたす。䜕回も。しばらくの間、最も䞍自然な䟋が出おきたようですtrue



ランディングパッドのコンテキストを修正し、ABIを少し改善したしょう。



 #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <stdint.h> namespace __cxxabiv1 { struct __class_type_info { virtual void foo() {} } ti; } #define EXCEPTION_BUFF_SIZE 255 char exception_buff[EXCEPTION_BUFF_SIZE]; extern "C" { void* __cxa_allocate_exception(size_t thrown_size) { printf("alloc ex %i\n", thrown_size); if (thrown_size > EXCEPTION_BUFF_SIZE) printf("Exception too big"); return &exception_buff; } void __cxa_free_exception(void *thrown_exception); #include <unwind.h> typedef void (*unexpected_handler)(void); typedef void (*terminate_handler)(void); struct __cxa_exception { std::type_info * exceptionType; void (*exceptionDestructor) (void *); unexpected_handler unexpectedHandler; terminate_handler terminateHandler; __cxa_exception * nextException; int handlerCount; int handlerSwitchValue; const char * actionRecord; const char * languageSpecificData; void * catchTemp; void * adjustedPtr; _Unwind_Exception unwindHeader; }; void __cxa_throw(void* thrown_exception, struct type_info *tinfo, void (*dest)(void*)) { printf("__cxa_throw called\n"); __cxa_exception *header = ((__cxa_exception *) thrown_exception - 1); _Unwind_RaiseException(&header->unwindHeader); // __cxa_throw never returns printf("no one handled __cxa_throw, terminate!\n"); exit(0); } void __cxa_begin_catch() { printf("begin FTW\n"); } void __cxa_end_catch() { printf("end FTW\n"); } /**********************************************/ /** * The LSDA is a read only place in memory; we'll create a typedef for * this to avoid a const mess later on; LSDA_ptr refers to readonly and * &LSDA_ptr will be a non-const pointer to a const place in memory */ typedef const uint8_t* LSDA_ptr; struct LSDA_Header { /** * Read the LSDA table into a struct; advances the lsda pointer * as many bytes as read */ LSDA_Header(LSDA_ptr *lsda) { LSDA_ptr read_ptr = *lsda; // Copy the LSDA fields start_encoding = read_ptr[0]; type_encoding = read_ptr[1]; ttype = read_ptr[2]; // Advance the lsda pointer *lsda = read_ptr + sizeof(LSDA_Header); } uint8_t start_encoding; uint8_t type_encoding; uint8_t ttype; }; struct LSDA_CS_Header { // Same as other LSDA constructors LSDA_CS_Header(LSDA_ptr *lsda) { LSDA_ptr read_ptr = *lsda; encoding = read_ptr[0]; length = read_ptr[1]; *lsda = read_ptr + sizeof(LSDA_CS_Header); } uint8_t encoding; uint8_t length; }; struct LSDA_CS { // Same as other LSDA constructors LSDA_CS(LSDA_ptr *lsda) { LSDA_ptr read_ptr = *lsda; start = read_ptr[0]; len = read_ptr[1]; lp = read_ptr[2]; action = read_ptr[3]; *lsda = read_ptr + sizeof(LSDA_CS); } // Note start, len and lp would be void*'s, but they are actually relative // addresses: start and lp are relative to the start of the function, len // is relative to start // Offset into function from which we could handle a throw uint8_t start; // Length of the block that might throw uint8_t len; // Landing pad uint8_t lp; // Offset into action table + 1 (0 means no action) // Used to run destructors uint8_t action; }; /**********************************************/ _Unwind_Reason_Code __gxx_personality_v0 ( int version, _Unwind_Action actions, uint64_t exceptionClass, _Unwind_Exception* unwind_exception, _Unwind_Context* context) { if (actions & _UA_SEARCH_PHASE) { printf("Personality function, lookup phase\n"); return _URC_HANDLER_FOUND; } else if (actions & _UA_CLEANUP_PHASE) { printf("Personality function, cleanup\n"); // Pointer to the beginning of the raw LSDA LSDA_ptr lsda = (uint8_t*)_Unwind_GetLanguageSpecificData(context); // Read LSDA headerfor the LSDA LSDA_Header header(&lsda); // Read the LSDA CS header LSDA_CS_Header cs_header(&lsda); // Calculate where the end of the LSDA CS table is const LSDA_ptr lsda_cs_table_end = lsda + cs_header.length; // Loop through each entry in the CS table while (lsda < lsda_cs_table_end) { LSDA_CS cs(&lsda); if (cs.lp) { int r0 = __builtin_eh_return_data_regno(0); int r1 = __builtin_eh_return_data_regno(1); _Unwind_SetGR(context, r0, (uintptr_t)(unwind_exception)); // Note the following code hardcodes the exception type; // we'll fix that later on _Unwind_SetGR(context, r1, (uintptr_t)(1)); uintptr_t func_start = _Unwind_GetRegionStart(context); _Unwind_SetIP(context, func_start + cs.lp); break; } } return _URC_INSTALL_CONTEXT; } else { printf("Personality function, error\n"); return _URC_FATAL_PHASE1_ERROR; } } }
      
      





コヌドの珟圚のバヌゞョンずLSDAのより詳现な説明。



最埌に動䜜したす次のようなものを取埗する必芁がありたす。



 ./app alloc ex 1 __cxa_throw called Personality function, lookup phase Personality function, cleanup begin FTW Caught a Fake_Exception! end FTW try_but_dont_catch handled the exception catchit handled the exception
      
      





もちろん、Unwindを少しtrickしたした。最初のcatchブロックで、すべおの䟋倖を連続しお凊理するように圌に䌝えたした。これにより、catchExceptionがcatch...に倉わり、フレヌム内の最初の関数にcatchブロックがない堎合、すべおの地獄が砎壊されたす。ただし、非垞に単玔なABIを䜜成するための最初のステップを実行したした



コヌドを改善し、正しいフレヌムで䟋倖を正しく凊理するこずができたすかもちろん



ボンネットの䞋のC ++䟋倖いく぀かのランディングパッドずグルの教え



困難な方法の埌、぀いにlbstdc ++の助けを借りずに゚ラヌを凊理できる実甚的な個人甚関数を䜜成したした。すべおの゚ラヌをランダムに凊理したすが、機胜したす正しい䟋倖凊理はただ答えられおいない倧きな質問ですが、LSDAに戻るず、次のようなこずがわかりたす。



 .local_lsda_call_site_table: .uleb128 .LEHB0-.LFB1 .uleb128 .LEHE0-.LEHB0 .uleb128 .L8-.LFB1 .uleb128 0x1 .uleb128 .LEHB1-.LFB1 .uleb128 .LEHE1-.LEHB1 .uleb128 0 .uleb128 0 .uleb128 .LEHB2-.LFB1 .uleb128 .LEHE2-.LEHB2 .uleb128 .L9-.LFB1 .uleb128 0 .local_lsda_call_site_table_end:
      
      





1぀のtry / catchブロックを蚘述した堎合でも、3぀のランディングパッドがありたす。 ここで䜕が起こっおいたすか



前の章を泚意深く芋るず、LSDA_CS構造䜓の定矩にいく぀かのコメントが远加されおいるこずがわかりたす。



 struct LSDA_CS { // len  lp   void*'s,   // : start and lp   , len //   //   ,     uint8_t start; //   uint8_t len; // Landing pad uint8_t lp; //  action table + 1 (0  " ") //     uint8_t action; };
      
      





ここには倚くの興味深いものがありたすが、最初にこの䟋のフィヌルドごずに構造を芋おください。



 void foo() { L0: try { do_something(); L1: } catch (const Exception1& ex) { ... } catch (const Exception2& ex) { ... } catch (const ExceptionN& ex) { ... } catch (...) { } L2: }
      
      







関心のあるフィヌルドはstartずlenですtry / catchブロックが倚い関数では、珟圚のフレヌムの呜什ポむンタヌIPがstartずstart + lenの間にあるかどうかを確認するこずで、䟋倖を凊理する必芁があるかどうかを刀断できたす。



これにより、耇数のtry / catchブロックを持぀関数が耇数の䟋倖を凊理できるずいう神話が砎壊されたすが、1぀のランディングパッドに察しお3぀のオブゞェクトが生成されるのはなぜですか他のオブゞェクトは、転送可胜なクリヌニングアクションたたはランディングパッドの堎所ずしお配眮されたす。



継続




All Articles