Coverage Report

Created: 2026-07-09 20:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
be/src/common/asan_cxa_throw_wrap.cpp
Line
Count
Source
1
// Licensed to the Apache Software Foundation (ASF) under one
2
// or more contributor license agreements.  See the NOTICE file
3
// distributed with this work for additional information
4
// regarding copyright ownership.  The ASF licenses this file
5
// to you under the Apache License, Version 2.0 (the
6
// "License"); you may not use this file except in compliance
7
// with the License.  You may obtain a copy of the License at
8
//
9
//   http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing,
12
// software distributed under the License is distributed on an
13
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14
// KIND, either express or implied.  See the License for the
15
// specific language governing permissions and limitations
16
// under the License.
17
18
// Fix for the long-standing ASAN-only BE crashes on the thrift RPC
19
// retry/reopen path (and, before the LOG guards were added, inside glog's
20
// LOG(WARNING)), which always aborted with:
21
//
22
//     AddressSanitizer: CHECK failed: asan_thread.cpp:...
23
//         "((ptr[0] == kCurrentStackFrameMagic)) != (0)" (0x0, 0x0)
24
//
25
// (Same failure family as google/glog#978 and google/sanitizers#1010.)
26
//
27
// Root cause: libthrift and the other third-party static libs are built
28
// WITHOUT ASAN instrumentation. ASAN cleans up the stack shadow on exception
29
// unwinding via __asan_handle_no_return(), emitted by the compiler before
30
// instrumented throws and provided as weak __cxa_throw/_Unwind_RaiseException
31
// interceptors in the runtime for un-instrumented throws. Under the BE's
32
// fully static link (-static-libstdc++/-static-libgcc plus a static sanitizer
33
// runtime) the linker resolves those symbols to libstdc++.a's/libgcc_eh.a's
34
// STRONG definitions, silently discarding the weak interceptors. So when
35
// un-instrumented code throws (e.g. TSocket raising TTransportException on a
36
// FE restart), the unwound instrumented frames keep their stack-shadow
37
// poison; the retry path immediately re-descends over the same stack range,
38
// innocent locals of thrift/glog/libc land on the stale poison, the first
39
// intercepted write reports a false positive, and ASAN aborts while walking
40
// the already-overwritten frame descriptor -- before it can even print a
41
// normal report. RELEASE/DEBUG builds have no shadow memory, which is why
42
// this never happened outside ASAN.
43
//
44
// ASAN builds therefore link with
45
//     -Wl,--wrap=__cxa_throw -Wl,--wrap=__cxa_rethrow
46
//     -Wl,--wrap=_Unwind_RaiseException
47
// (see the ASAN branch in be/CMakeLists.txt). --wrap redirects every
48
// reference from every input object -- including the members of the
49
// un-instrumented third-party archives and of libstdc++.a itself -- to the
50
// wrappers below, which restore exactly what the official interceptors do:
51
// wipe the stack shadow above the throw point, then forward to the real
52
// implementation (__real___cxa_throw resolves to libstdc++'s definition).
53
//
54
// _Unwind_RaiseException is wrapped as well because not every raise funnels
55
// through __cxa_throw: std::rethrow_exception (libstdc++'s eh_ptr.cc) and
56
// foreign-language unwinders call it directly. This matches the official
57
// ASAN interceptor set for the unwind entry points. Throws via __cxa_throw
58
// run the shadow wipe twice (once per wrapper); that is idempotent and is
59
// also what happens with the stock interceptors.
60
61
#if defined(ADDRESS_SANITIZER) && defined(__linux__)
62
63
extern "C" {
64
65
void __asan_handle_no_return();
66
void __real___cxa_throw(void* thrown_exception, void* tinfo, void (*dest)(void*));
67
void __real___cxa_rethrow();
68
int __real__Unwind_RaiseException(void* exception_object);
69
70
void __wrap___cxa_throw(void* thrown_exception, void* tinfo, void (*dest)(void*));
71
void __wrap___cxa_rethrow();
72
int __wrap__Unwind_RaiseException(void* exception_object);
73
74
16.1k
void __wrap___cxa_throw(void* thrown_exception, void* tinfo, void (*dest)(void*)) {
75
16.1k
    __asan_handle_no_return();
76
16.1k
    __real___cxa_throw(thrown_exception, tinfo, dest);
77
16.1k
}
78
79
75
void __wrap___cxa_rethrow() {
80
75
    __asan_handle_no_return();
81
75
    __real___cxa_rethrow();
82
75
}
83
84
// Unlike the two above this one RETURNS when no handler is found (the caller
85
// then calls std::terminate), so the return value must be forwarded.
86
16.2k
int __wrap__Unwind_RaiseException(void* exception_object) {
87
16.2k
    __asan_handle_no_return();
88
16.2k
    return __real__Unwind_RaiseException(exception_object);
89
16.2k
}
90
91
} // extern "C"
92
93
#endif // ADDRESS_SANITIZER && __linux__