静的オブジェクトの破棄時にクラッシュする例
とある市販ソフトが macOS がシャットダウン(ログアウト?)するときに異常終了する。実害は見当たらない。
異常終了するのは、ログインから起動しっぱなしのプロセス。
クラッシュ ログの抜粋 (\*\*\*は伏せた箇所)
Crashed Thread: 0 Dispatch queue: com.apple.main-thread
Exception Type: EXC_BAD_ACCESS (SIGSEGV)
Exception Codes: KERN_INVALID_ADDRESS at 0x00000000000000b0
Exception Note: EXC_CORPSE_NOTIFY
Termination Signal: Segmentation fault: 11
Termination Reason: Namespace SIGNAL, Code 0xb
Terminating Process: exc handler [2226]
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
(略)
3 libsystem_platform.dylib 0x00007fff7031142d _sigtramp + 29
4 ??? 000000000000000000 0 + 0
5 libsystem_c.dylib 0x00007fff701e6a1c abort + 120
6 libc++abi.dylib 0x00007fff6d284be8 abort_message + 231
7 libc++abi.dylib 0x00007fff6d284cc6 demangling_terminate_handler() + 48
8 libc++abi.dylib 0x00007fff6d291dc7 std::__terminate(void (*)()) + 8
9 libc++abi.dylib 0x00007fff6d291d88 std::terminate() + 56
10 *** 0x0000000104a844c4 std::__1::unique_ptr<***Thread, std::__1::default_delete<***Thread> >::~unique_ptr() + 68
11 libsystem_c.dylib 0x00007fff701c1446 __cxa_finalize_ranges + 319
12 libsystem_c.dylib 0x00007fff701c171c exit + 55
13 com.apple.AppKit 0x00007fff35f53c03 -[NSApplication terminate:] + 1759
14 com.apple.AppKit 0x00007fff360dac50 -[NSApplication _terminateFromSender:askIfShouldTerminate:saveWindows:] + 126
15 com.apple.AppKit 0x00007fff360dab5b __52-[NSApplication(NSAppleEventHandling) _handleAEQuit]_block_invoke + 46
16 com.apple.AppKit 0x00007fff36122c31 ___NSMainRunLoopPerformBlockInModes_block_invoke + 25
17 com.apple.CoreFoundation 0x00007fff38abd7ab __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ + 12
18 com.apple.CoreFoundation 0x00007fff38abd6ed __CFRunLoopDoBlocks + 379
19 com.apple.CoreFoundation 0x00007fff38abc731 __CFRunLoopRun + 1257
20 com.apple.CoreFoundation 0x00007fff38abbbd3 CFRunLoopRunSpecific + 499
21 com.apple.HIToolbox 0x00007fff3761265d RunCurrentEventLoopInMode + 292
22 com.apple.HIToolbox 0x00007fff376122a9 ReceiveNextEventCommon + 356
23 com.apple.HIToolbox 0x00007fff37612127 _BlockUntilNextEventMatchingListInModeWithFilter + 64
24 com.apple.AppKit 0x00007fff35c83eb4 _DPSNextEvent + 990
25 com.apple.AppKit 0x00007fff35c82690 -[NSApplication(NSEvent) _nextEventMatchingEventMask:untilDate:inMode:dequeue:] + 1352
26 com.apple.AppKit 0x00007fff35c743ae -[NSApplication run] + 658
27 com.*** 0x000000010405b49a -[*** run] + 138
28 com.*** 0x0000000104049d9b main + 1163
29 libdyld.dylib 0x00007fff701187fd start + 1
Thread 1:
0 libsystem_kernel.dylib 0x00007fff7025bce6 __psynch_cvwait + 10
1 libsystem_pthread.dylib 0x00007fff7031d185 _pthread_cond_wait + 701
(略: ***Thread 関連)
7 libsystem_pthread.dylib 0x00007fff7031ce65 _pthread_start + 148
8 libsystem_pthread.dylib 0x00007fff7031883b thread_start + 15
Thread 2:
0 libsystem_kernel.dylib 0x00007fff7025bce6 __psynch_cvwait + 10
1 libsystem_pthread.dylib 0x00007fff7031d185 _pthread_cond_wait + 701
(略: ***Thread 関連)
5 libsystem_pthread.dylib 0x00007fff7031ce65 _pthread_start + 148
6 libsystem_pthread.dylib 0x00007fff7031883b thread_start + 15
Thread 3:: default
(略)
Thread 4:
(略)
Thread 5:
(略)
Thread 6:
(略)
Thread 7:
(略)
Thread 8:
(略)
Thread 9:
(略)
Thread 10:
(略)
Thread 11:
(略)
Thread 12:
(略)
Thread 13:
(略)
Thread 14:
(略)
Thread 15:
(略)
Thread 16:
(略)
Thread 17:
(略)
Thread 18:: com.apple.NSEventThread
0 libsystem_kernel.dylib 0x00007fff7025925a mach_msg_trap + 10
1 libsystem_kernel.dylib 0x00007fff702595d0 mach_msg + 60
2 com.apple.CoreFoundation 0x00007fff38abdd0b __CFRunLoopServiceMachPort + 322
3 com.apple.CoreFoundation 0x00007fff38abc8e7 __CFRunLoopRun + 1695
4 com.apple.CoreFoundation 0x00007fff38abbbd3 CFRunLoopRunSpecific + 499
5 com.apple.AppKit 0x00007fff35e26a72 _NSEventThread + 132
6 libsystem_pthread.dylib 0x00007fff7031ce65 _pthread_start + 148
7 libsystem_pthread.dylib 0x00007fff7031883b thread_start + 15
Thread 19:
(略)
Thread 20:
0 libsystem_pthread.dylib 0x00007fff70318818 start_wqthread + 0
Thread 21:
0 libsystem_pthread.dylib 0x00007fff70318818 start_wqthread + 0
注目する部分は
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
5 libsystem_c.dylib 0x00007fff701e6a1c abort + 120
(略)
9 libc++abi.dylib 0x00007fff6d291d88 std::terminate() + 56
10 *** 0x0000000104a844c4 std::__1::unique_ptr<***Thread, std::__1::default_delete<***Thread> >::~unique_ptr() + 68
11 libsystem_c.dylib 0x00007fff701c1446 __cxa_finalize_ranges + 319
12 libsystem_c.dylib 0x00007fff701c171c exit + 55
Thread 1:
Thread 2:
で、exit は静的オブジェクトを破棄するため __cxa_finalize_ranges を呼んでいる。
静的オブジェクトの1つが unique_ptr<\*\*\*Thread,...> の型で存在するらしい。
ここでは、スレッド管理クラス (***Thread) と思われるオブジェクトを破棄しようとして失敗していることが覗える。
***Thread のデストラクタでスレッドを終了させる仕様ならば、デストラクタで必要とするオブジェクトが既に破棄されている可能性を考えないといけない。
スレッドを終了させてから、***Thread のデストラクタを呼ぶ仕様ならば exit 時の対応を忘れていたのだろう。
どちらにしろ 100% 再現するので、終了処理のテストをやっていない。
静的オブジェクト 初期化・破棄処理順
静的オブジェクトが破棄される順番は、初期化の順逆になっている。その順番は、翻訳単位(コンパイルで生成される1つのオブジェクト ファイル)の中での順番と、リンク時の順番などで決まる。
g++ の __attribute__((init_priority(N))) を使って、処理順を確認してみる。テストした環境は
Apple clang version 11.0.0 (clang-1100.0.33.17)
Target: x86_64-apple-darwin19.2.0
#include <cstdio>
#ifdef NO_ATTRIBUTE
#define __attribute__(x)
#endif
struct ObjA
{
const char *message;
ObjA(const char *msg) : message(msg) { printf(" ObjA: %s\n", message); }
~ObjA() { printf("~ObjA: %s\n", message); }
};
ObjA __attribute__((init_priority(1002))) a2("a2");
ObjA __attribute__((init_priority(1001))) a1("a1");
ObjA __attribute__((init_priority(1000))) a0("a0");
ObjA &foo()
{
static ObjA obj_foo("foo");
return obj_foo;
}
ObjA &bar()
{
static ObjA obj_bar("bar");
return obj_bar;
}
int main(int argc, char **argv)
{
foo();
bar();
return 0;
}
#include <cstdio>
#ifdef NO_ATTRIBUTE
#define __attribute__(x)
#endif
struct ObjB
{
const char *message;
ObjB(const char *msg) : message(msg) { printf(" ObjB: %s\n", message); }
~ObjB() { printf("~ObjB: %s\n", message); }
};
ObjB __attribute__((init_priority(1002))) b2("b2");
ObjB __attribute__((init_priority(1001))) b1("b1");
ObjB __attribute__((init_priority(1000))) b0("b0");
実行結果は
$ g++ -o sample sample1.cpp sample2.cpp
$ ./sample
ObjA: a0
ObjA: a1
ObjA: a2
ObjB: b0
ObjB: b1
ObjB: b2
ObjA: foo
ObjA: bar
~ObjA: bar
~ObjA: foo
~ObjB: b2
~ObjB: b1
~ObjB: b0
~ObjA: a2
~ObjA: a1
~ObjA: a0
となって、翻訳単位の中では順番の制御ができるようですが、翻訳単位を跨いだ順番の制御はできていません。
init_priority なしでは
$ g++ -DNO_ATTRIBUTE -o sample sample1.cpp sample2.cpp
$ ./sample
ObjA: a2
ObjA: a1
ObjA: a0
ObjB: b2
ObjB: b1
ObjB: b0
ObjA: foo
ObjA: bar
~ObjA: bar
~ObjA: foo
~ObjB: b0
~ObjB: b1
~ObjB: b2
~ObjA: a0
~ObjA: a1
~ObjA: a2
ソース ファイルの順番を入れ替えると
$ g++ -DNO_ATTRIBUTE -o sample sample2.cpp sample1.cpp
$ ./sample
ObjB: b2
ObjB: b1
ObjB: b0
ObjA: a2
ObjA: a1
ObjA: a0
ObjA: foo
ObjA: bar
~ObjA: bar
~ObjA: foo
~ObjA: a0
~ObjA: a1
~ObjA: a2
~ObjB: b0
~ObjB: b1
~ObjB: b2
順番が入れ替わりました。
静的オブジェクトを使うときは、デストラクタで必要とするオブジェクトが先に破棄されないように気をつけなくてはなりません。
しかし、静的オブジェクトの配置場所やリンク順を追って制御するのは、大きなプログラムほど困難です。
デバッガで「実行→中断」の繰り返しで開発していると、初期化順問題は正常に動作しないので直ぐに分かりますが、破棄順問題は気付きません。
たまには終了処理のテストをしましょう。