We have a problem where initializing the Google Breakpad exception handler errors out when the program is run under lldb, but not when run normally from the shell.
The system is MacOS 13 (Ventura) and the IDE is Visual Studio Code.
The code below fails on a call to init()
:
namespace crashhandler {
static std::unique_ptr<google_breakpad::ExceptionHandler> pExceptionHandler;
namespace {
bool DumpCallback(const char* dump_dir, const char* minidump_id, void*, bool success) {
if (success)
printf("Application crashed. Breakpad Crash Handler created a dump at location %s/%s.dmp\n",
dump_dir, minidump_id);
else
printf("Application crashed. Breakpad Crash Handler failed to create a dump");
fflush(stdout);
return success;
}
} // namespace
void init(const std::string& reportPath) // <-- crash happens when calling this function
{
if (pExceptionHandler)
return;
pExceptionHandler.reset(
new google_breakpad::ExceptionHandler(reportPath, nullptr, DumpCallback, nullptr, true, nullptr));
}
} // namespace crashhandler
The debug console shows:
=================================================================
==5060==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x00016fdfee00 at pc 0x000100ac9030 bp 0x00016ff11540 sp 0x00016ff10d08
READ of size 4608 at 0x00016fdfee00 thread T2
#0 0x100ac902c in wrap_write 0x15c (libclang_rt.asan_osx_dynamic.dylib:arm64e 0x1902c)
#1 0x100109f08 in google_breakpad::UntypedMDRVA::Copy(unsigned int, void const*, unsigned long) 0x54 (my_server:arm64 0x100109f08)
#2 0x10010ce14 in google_breakpad::MinidumpGenerator::WriteStackFromStartAddress(unsigned long long, MDMemoryDescriptor*) 0xf8 (my_server:arm64 0x10010ce14)
#3 0x10010d244 in google_breakpad::MinidumpGenerator::WriteThreadStream(unsigned int, MDRawThread*) 0x100 (my_server:arm64 0x10010d244)
#4 0x10010c04c in google_breakpad::MinidumpGenerator::WriteThreadListStream(MDRawDirectory*) 0xfc (my_server:arm64 0x10010c04c)
#5 0x10010bd20 in google_breakpad::MinidumpGenerator::Write(char const*) 0xc8 (my_server:arm64 0x10010bd20)
#6 0x10010adc0 in google_breakpad::ExceptionHandler::WriteMinidumpWithException(int, int, int, __darwin_ucontext64*, unsigned int, bool, bool) 0x160 (my_server:arm64 0x10010adc0)
#7 0x10010af1c in google_breakpad::ExceptionHandler::WaitForMessage(void*) 0x104 (my_server:arm64 0x10010af1c)
#8 0x1a330a068 in _pthread_start 0x90 (libsystem_pthread.dylib:arm64e 0x7068)
#9 0x1a3304e28 in thread_start 0x4 (libsystem_pthread.dylib:arm64e 0x1e28)
Address 0x00016fdfee00 is located in stack of thread T0 at offset 0 in frame
#0 0x1000034cc in main main.cpp:36
This frame has 10 object(s):
[32, 56) 'reportPath' (line 39) <== Memory access at offset 0 partially underflows this variable
[96, 120) 'ref.tmp' (line 40) <== Memory access at offset 0 partially underflows this variable
[160, 208) 'parser' (line 43) <== Memory access at offset 0 partially underflows this variable
[240, 264) 'configPath' (line 45) <== Memory access at offset 0 partially underflows this variable
[304, 320) 'ref.tmp12' (line 46) <== Memory access at offset 0 partially underflows this variable
[336, 360) 'agg.tmp' <== Memory access at offset 0 partially underflows this variable
[400, 416) 'ref.tmp30' (line 57) <== Memory access at offset 0 partially underflows this variable
[432, 456) 'agg.tmp42' <== Memory access at offset 0 partially underflows this variable
[496, 520) 'agg.tmp80' <== Memory access at offset 0 partially underflows this variable
[560, 568) 'ref.tmp86' (line 74) <== Memory access at offset 0 partially underflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-underflow (libclang_rt.asan_osx_dynamic.dylib:arm64e 0x1902c) in wrap_write 0x15c
Shadow bytes around the buggy address:
0x00702dfdfd70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00702dfdfd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00702dfdfd90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00702dfdfda0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x00702dfdfdb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x00702dfdfdc0:[f1]f1 f1 f1 00 00 00 f2 f2 f2 f2 f2 f8 f8 f8 f2
0x00702dfdfdd0: f2 f2 f2 f2 f8 f8 f8 f8 f8 f8 f2 f2 f2 f2 f8 f8
0x00702dfdfde0: f8 f2 f2 f2 f2 f2 f8 f8 f2 f2 00 00 00 f2 f2 f2
0x00702dfdfdf0: f2 f2 f8 f8 f2 f2 00 00 00 f2 f2 f2 f2 f2 00 00
0x00702dfdfe00: 00 f2 f2 f2 f2 f2 f8 f3 f3 f3 f3 f3 00 00 00 00
0x00702dfdfe10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Thread T2 created by T0 here:
#0 0x100ae8c5c in wrap_pthread_create 0x54 (libclang_rt.asan_osx_dynamic.dylib:arm64e 0x38c5c)
#1 0x10010a360 in google_breakpad::ExceptionHandler::Setup(bool) 0xd0 (my_server:arm64 0x10010a360)
#2 0x10010a1c4 in google_breakpad::ExceptionHandler::ExceptionHandler(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, bool (*)(void*), bool (*)(char const*, char const*, void*, bool), void*, bool, char const*) 0x110 (my_server:arm64 0x10010a1c4)
#3 0x1001132b4 in crashhandler::init(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&) 0x58 (my_server:arm64 0x1001132b4)
#4 0x10000367c in main main.cpp:41
#5 0x1a2fdfe4c (<unknown module>)
==5060==ABORTING
To reiterate, if I run the program outside of the debugger, it proceeds normally.
What can cause this?
CodePudding user response:
Breakpad is inserting a right into the process's "task exception port" - which is where you listen for crashes and the like either from within the process or externally - i.e. when you are a debugger. But in Mach the exception ports only have a single owner. So when you run under the debugger, Breakpad and the debugger fight for control of the exception port. For example, if you got your port right set up before the debugger attached, you will end up with a bad port right after the attach, because lldb now owns the port.
Debugging programs that use task exception port handlers is not well supported, because (a) it would be tricky to get that right and (b) there aren't enough programs that need to do this to motivate the effort (at least on the debugger side). Most people turn off their exception handling for their debug builds since their exception catcher and the debugger are pretty much doing the same job, and it's more convenient to trap in the debugger than the internal exception handler. And the core part of the exception handler is usually simple enough that you can do printf debugging if you really need to debug that part.