Skip to content

[fix](asan) Restore __asan_handle_no_return on the throw path bypassed by the static link#65366

Open
morningman wants to merge 1 commit into
apache:masterfrom
morningman:asan-throw-wrap-fix
Open

[fix](asan) Restore __asan_handle_no_return on the throw path bypassed by the static link#65366
morningman wants to merge 1 commit into
apache:masterfrom
morningman:asan-throw-wrap-fix

Conversation

@morningman

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #xxx

Related PR: #65358 (reproduction & verification branch, do-not-merge), workaround history: #34813 #34994 #36808 #37134 #48347 #49516 #49878

Problem Summary:

For years the BE has crashed only in ASAN builds, on the thrift RPC retry/reopen path (and, before the #ifndef ADDRESS_SANITIZER log guards were added, inside glog LOG(WARNING)), always with the same signature:

AddressSanitizer: CHECK failed: asan_thread.cpp:36x
    "((ptr[0] == kCurrentStackFrameMagic)) != (0)" (0x0, 0x0)

Same failure family as google/glog#978 and google/sanitizers#1010. It was never root-caused, only worked around with ASAN-only log guards and code-path forks — and #49516 showed those forks hatch real bugs of their own.

Root cause. libthrift and the other third-party static libs are built without ASAN instrumentation. ASAN cleans the stack shadow on exception unwinding via __asan_handle_no_return(): the compiler emits it before instrumented throws, and the runtime provides weak __cxa_throw / _Unwind_RaiseException interceptors to cover un-instrumented throws. Under the BE's fully static link (-static-libstdc++ / -static-libgcc plus a static sanitizer runtime) those weak interceptors lose to libstdc++.a's / libgcc_eh.a's strong definitions at link time, so the cleanup never runs when un-instrumented code throws (e.g. TSocket raising TTransportException when the FE restarts). The unwound instrumented frames keep their stack-shadow poison; the retry path immediately re-descends over the same stack range, innocent locals (thrift's getSocketInfo() strings, glog's struct tm, ...) land on the stale poison, the first intercepted write reports a false positive, and ASAN aborts while walking the already-overwritten frame descriptor — before it can even print a normal report. RELEASE/DEBUG builds have no shadow memory, which is why this never happened outside ASAN.

The fix. ASAN/ASAN_UT Linux links now pass

-Wl,--wrap=__cxa_throw -Wl,--wrap=__cxa_rethrow -Wl,--wrap=_Unwind_RaiseException

and be/src/common/asan_cxa_throw_wrap.cpp provides wrappers that call __asan_handle_no_return() before forwarding to the real implementations — exactly what the bypassed official interceptors do. --wrap rewrites every reference from every input object, including the un-instrumented third-party archives and libstdc++.a itself. The flags are attached to DORIS_LINK_LIBS, so any executable receiving them also links Common, which carries the wrapper definitions — flags and definitions cannot drift apart. _Unwind_RaiseException is wrapped too because std::rethrow_exception and foreign-language unwinders raise through it without touching __cxa_throw; its wrapper forwards the return value (it returns when no handler is found). Production (RELEASE/DEBUG) builds are byte-identical.

Cleanup. With the throw path fixed, the ASAN-only forks are removed — 15 #ifndef ADDRESS_SANITIZER sites in thrift_rpc_helper.cpp (6), agent/utils.cpp (6), runtime_query_statistics_mgr.cpp (2, one of which skipped the whole reopen+retry under ASAN, the failure pattern #49516 exposed), pipeline_fragment_context.cpp (1), plus the leftover std::cerr LOG substitutes. ASAN CI now exercises the exact production retry/reopen/logging code.

Verification. Done on the ASAN pipeline via the dedicated reproduction branch #65358, which carries a deterministic startup reproducer (not part of this PR):

unfixed (baseline) with this fix
interceptor check __cxa_throw ... NOT intercepted (bypassed by static link) WRAPPED by __wrap___cxa_throw
stale shadow poison after an un-instrumented throw 58/160 probed frames poisoned 0/160
driving the historical crash site (report() fail → close()flush()getSocketInfo(), swept over 257 stack offsets) deterministic startup crash with the historical stack survives the full sweep, BE starts and runs the regression

Release note

None

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes.
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

🤖 Generated with Claude Code

…d by the static link

For years the BE has crashed only in ASAN builds, on the thrift RPC
retry/reopen path (and, before the log guards were added, inside glog
LOG(WARNING)), always with the same signature:

    AddressSanitizer: CHECK failed: asan_thread.cpp:36x
        "((ptr[0] == kCurrentStackFrameMagic)) != (0)" (0x0, 0x0)

Same failure family as google/glog#978 and google/sanitizers#1010.
It was never root-caused, only worked around (apache#34813 apache#34994 apache#36808
apache#37134 apache#48347 apache#49516 apache#49878).

Root cause: libthrift and the other third-party static libs are built
without ASAN instrumentation. ASAN cleans the stack shadow on exception
unwinding via __asan_handle_no_return(), emitted by the compiler before
instrumented throws and provided as weak __cxa_throw /
_Unwind_RaiseException interceptors for un-instrumented throws. Under
the BE's fully static link (-static-libstdc++/-static-libgcc plus a
static sanitizer runtime) those weak interceptors lose to libstdc++.a's
/ libgcc_eh.a's strong definitions at link time, so the cleanup never
runs when un-instrumented code throws (e.g. TSocket raising
TTransportException on a FE restart). The unwound instrumented frames
keep their stack-shadow poison; the retry path immediately re-descends,
innocent locals land on the stale poison, the first intercepted write
reports a false positive, and ASAN aborts while walking the
already-overwritten frame descriptor. RELEASE/DEBUG builds have no
shadow memory, which is why this never happened outside ASAN.

The fix: ASAN/ASAN_UT Linux links now pass

    -Wl,--wrap=__cxa_throw -Wl,--wrap=__cxa_rethrow
    -Wl,--wrap=_Unwind_RaiseException

and be/src/common/asan_cxa_throw_wrap.cpp provides wrappers that call
__asan_handle_no_return() before forwarding to the real
implementations -- exactly what the bypassed official interceptors do.
--wrap rewrites every reference from every input object, including the
un-instrumented third-party archives and libstdc++.a itself. The flags
are attached to DORIS_LINK_LIBS so any executable receiving them also
links Common, which carries the wrapper definitions.
_Unwind_RaiseException is wrapped too because std::rethrow_exception
and foreign-language unwinders raise through it without touching
__cxa_throw; its wrapper forwards the return value (it returns when no
handler is found). Production builds are byte-identical.

With the throw path fixed, the ASAN-only forks are removed: 15
"#ifndef ADDRESS_SANITIZER" sites in thrift_rpc_helper.cpp (6),
agent/utils.cpp (6), runtime_query_statistics_mgr.cpp (2, one of which
skipped the whole reopen+retry under ASAN -- the failure pattern
apache#49516 exposed), pipeline_fragment_context.cpp (1), plus the leftover
std::cerr LOG substitutes. ASAN CI now runs the exact production
retry/reopen/logging code.

Verified on the ASAN pipeline via apache#65358 with a deterministic
reproducer: unfixed builds show the interceptor bypass at the link
level, measurable stale stack-shadow poison after an un-instrumented
throw (58/160 probed frames), and a deterministic startup crash with
the historical stack; with this fix the same reproducer measures 0/160
poisoned frames, survives the full crash-site sweep, and the BE runs
the regression normally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@shuke987

shuke987 commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

/review

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Codex automated review failed and did not complete.

Error: You've hit your usage limit. Visit https://chatgpt.com/codex/settings/usage to purchase more credits or try again at Jul 10th, 2026 6:44 AM.
Workflow run: https://github.com/apache/doris/actions/runs/28930012948

Please inspect the workflow logs and rerun the review after the underlying issue is resolved.

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 29219 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 91389825f009e2e4b3a347fac8a64a223a408f2a, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17674	4159	4108	4108
q2	2014	320	202	202
q3	10316	1430	802	802
q4	4692	468	349	349
q5	7517	878	569	569
q6	185	177	140	140
q7	776	842	627	627
q8	9318	1500	1410	1410
q9	5693	4384	4355	4355
q10	6764	1790	1520	1520
q11	492	352	308	308
q12	703	548	427	427
q13	18129	3459	2778	2778
q14	265	257	246	246
q15	q16	797	779	720	720
q17	1026	1144	876	876
q18	7037	5772	5516	5516
q19	1387	1293	995	995
q20	750	631	566	566
q21	5859	2608	2408	2408
q22	435	351	297	297
Total cold run time: 101829 ms
Total hot run time: 29219 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4391	4340	4381	4340
q2	296	319	234	234
q3	4533	4974	4388	4388
q4	2043	2129	1368	1368
q5	4427	4314	4309	4309
q6	223	173	128	128
q7	1751	1911	1878	1878
q8	2540	2188	2221	2188
q9	8051	8224	7774	7774
q10	4752	4798	4269	4269
q11	578	412	375	375
q12	779	786	536	536
q13	3328	3525	2990	2990
q14	290	291	281	281
q15	q16	755	720	639	639
q17	1349	1337	1320	1320
q18	7906	7358	7173	7173
q19	1133	1104	1071	1071
q20	2200	2220	1937	1937
q21	5195	4593	4398	4398
q22	516	441	403	403
Total cold run time: 57036 ms
Total hot run time: 51999 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 181501 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 91389825f009e2e4b3a347fac8a64a223a408f2a, data reload: false

query5	4328	665	487	487
query6	452	226	206	206
query7	4853	586	337	337
query8	338	192	174	174
query9	8824	4082	4072	4072
query10	470	337	283	283
query11	5936	2385	2124	2124
query12	156	105	99	99
query13	1269	609	454	454
query14	6212	5299	4961	4961
query14_1	4301	4280	4288	4280
query15	224	211	199	199
query16	1018	489	423	423
query17	1125	733	604	604
query18	2519	490	351	351
query19	215	192	152	152
query20	117	109	106	106
query21	235	159	133	133
query22	13578	13628	13376	13376
query23	17383	16617	16095	16095
query23_1	16277	16280	16262	16262
query24	7586	1778	1297	1297
query24_1	1320	1314	1345	1314
query25	568	473	404	404
query26	1339	363	202	202
query27	2578	590	375	375
query28	4446	2013	2018	2013
query29	1097	632	512	512
query30	334	261	229	229
query31	1119	1096	995	995
query32	118	64	62	62
query33	539	328	268	268
query34	1172	1140	654	654
query35	775	797	676	676
query36	1452	1395	1229	1229
query37	174	113	96	96
query38	1874	1677	1627	1627
query39	930	915	892	892
query39_1	865	905	865	865
query40	247	163	142	142
query41	117	65	62	62
query42	94	89	98	89
query43	323	335	281	281
query44	1444	786	762	762
query45	197	189	174	174
query46	1049	1175	749	749
query47	2356	2362	2282	2282
query48	409	422	298	298
query49	583	426	321	321
query50	1008	439	336	336
query51	10616	10714	10532	10532
query52	93	88	75	75
query53	261	287	197	197
query54	278	251	213	213
query55	85	76	75	75
query56	295	289	280	280
query57	1426	1412	1313	1313
query58	289	278	255	255
query59	1599	1662	1438	1438
query60	310	263	256	256
query61	152	153	166	153
query62	698	649	559	559
query63	242	212	207	207
query64	2855	1037	848	848
query65	4843	4719	4750	4719
query66	1840	512	395	395
query67	29441	29313	29299	29299
query68	3113	1542	1045	1045
query69	418	304	267	267
query70	1050	951	975	951
query71	332	322	314	314
query72	3081	2768	2416	2416
query73	821	805	438	438
query74	5103	4977	4742	4742
query75	2622	2580	2227	2227
query76	2290	1194	815	815
query77	358	386	288	288
query78	12399	12300	11836	11836
query79	1418	1168	741	741
query80	1305	543	478	478
query81	523	324	277	277
query82	641	158	121	121
query83	368	319	298	298
query84	281	164	129	129
query85	1029	645	511	511
query86	453	296	285	285
query87	1840	1811	1733	1733
query88	3766	2791	2809	2791
query89	449	401	364	364
query90	1967	216	198	198
query91	201	187	157	157
query92	65	63	57	57
query93	1690	1608	1035	1035
query94	766	376	355	355
query95	811	513	458	458
query96	1060	813	360	360
query97	2716	2717	2593	2593
query98	207	209	198	198
query99	1170	1175	1007	1007
Total cold run time: 265958 ms
Total hot run time: 181501 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 24.91 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 91389825f009e2e4b3a347fac8a64a223a408f2a, data reload: false

query1	0.01	0.00	0.00
query2	0.09	0.05	0.05
query3	0.25	0.14	0.13
query4	1.61	0.13	0.13
query5	0.24	0.23	0.22
query6	1.25	1.11	1.06
query7	0.04	0.00	0.00
query8	0.05	0.04	0.04
query9	0.39	0.31	0.31
query10	0.56	0.55	0.52
query11	0.20	0.14	0.13
query12	0.19	0.15	0.15
query13	0.47	0.47	0.48
query14	1.02	1.00	1.00
query15	0.62	0.59	0.60
query16	0.36	0.31	0.33
query17	1.10	1.08	1.11
query18	0.22	0.21	0.21
query19	2.00	1.95	1.92
query20	0.02	0.01	0.01
query21	15.42	0.21	0.13
query22	4.84	0.06	0.05
query23	16.14	0.32	0.11
query24	2.93	0.42	0.33
query25	0.12	0.04	0.04
query26	0.72	0.20	0.14
query27	0.04	0.05	0.04
query28	3.60	0.95	0.52
query29	12.50	4.08	3.26
query30	0.27	0.15	0.15
query31	2.77	0.59	0.31
query32	3.22	0.58	0.49
query33	3.24	3.13	3.26
query34	15.58	4.19	3.56
query35	3.58	3.54	3.51
query36	0.55	0.43	0.44
query37	0.08	0.07	0.07
query38	0.05	0.04	0.04
query39	0.05	0.02	0.02
query40	0.17	0.17	0.16
query41	0.09	0.04	0.03
query42	0.04	0.03	0.03
query43	0.04	0.04	0.03
Total cold run time: 96.73 s
Total hot run time: 24.91 s

@morningman

Copy link
Copy Markdown
Contributor Author

/review

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review completed for PR 65366.

Review opinion: no blocking PR-specific issue found, and I am not submitting inline comments.

Critical checkpoint conclusions:

  • Task goal: The PR targets ASAN-only BE crashes caused by uninstrumented/static exception paths bypassing __asan_handle_no_return(). The added ASAN/ASAN_UT Linux wrap flags and be/src/common/asan_cxa_throw_wrap.cpp address that path, and the removed ASAN guards restore the same thrift retry/logging behavior already used by non-ASAN builds.
  • Scope/focus: The change is focused on ASAN link/runtime exception handling and removal of ASAN-only RPC forks. No unrelated FE/Nereids, storage, transaction, or protocol surface is changed.
  • Concurrency/lifecycle: No new threads, locks, shared mutable state, cache ownership, or object lifecycle paths were introduced. The wrapper is process-level link interposition with no persistent state.
  • Configuration/compatibility: No new config, session variable, thrift field, persisted format, or rolling-upgrade compatibility concern was introduced. The link flags are ASAN/ASAN_UT plus Linux only; release/debug builds do not receive the new wrap flags.
  • Parallel paths: ASAN now follows the same thrift RPC reconnect/retry/report code paths as non-ASAN. Common includes the new wrapper source and BE unit-test links inherit the updated DORIS_LINK_LIBS.
  • Error handling and observability: The removed guards restore LOG calls and retry/reopen behavior under ASAN. Existing best-effort discarded reopen() cleanup statuses were checked and are pre-existing, unchanged lines rather than new PR behavior.
  • Tests/validation: I could not run a full local Doris build because this Actions checkout is missing .worktree_initialized and thirdparty/installed. I did run syntax checks for the wrapper with GCC and Clang, with and without ADDRESS_SANITIZER, plus clang-format dry-run and git show --check. Current PR statuses show Linux compile, BE UT, style, P0, non-concurrent, cloud, vault, and performance checks passing. The macOS BE UT failure stops before build due to JDK 25 vs required JDK 17. The external regression failures are external Paimon/Iceberg/Hive/HDFS tests; the detailed failure inspected is ASAN MEM_LIMIT_EXCEEDED in scanner/table-reader materialization, not in the touched wrapper or thrift RPC paths.

Subagent conclusions:

  • optimizer-rewrite found no optimizer/rewrite, semantic-equivalence, or parallel join/aggregate candidate.
  • tests-session-config found no regression-output, session/config propagation, compatibility, or basic CI/style candidate.
  • Convergence round 1 ended with both live subagents replying NO_NEW_VALUABLE_FINDINGS for the same ledger/comment set with no inline comments proposed.

User focus: no additional user-provided focus points were present.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 100.00% (14/14) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 72.97% (29240/40074)
Line Coverage 56.58% (315984/558515)
Region Coverage 53.00% (262784/495788)
Branch Coverage 54.09% (115388/213342)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants