已合并
add android FP backtrace #770
add android FP backtrace #770
已合并
liwenzhen3创建于 7月8日
4 个文件变更+713-1
@@ -61,6 +61,7 @@ template("ace_common_jni_source_set") {
61 "$ace_root/adapter/android/entrance/java/jni/ace_env_jni.cpp",61 "$ace_root/adapter/android/entrance/java/jni/ace_env_jni.cpp",
62 "$ace_root/adapter/android/entrance/java/jni/ace_resource_register.cpp",62 "$ace_root/adapter/android/entrance/java/jni/ace_resource_register.cpp",
63 "$ace_root/adapter/android/entrance/java/jni/ace_translate_manager.cpp",63 "$ace_root/adapter/android/entrance/java/jni/ace_translate_manager.cpp",
64+ "$ace_root/adapter/android/entrance/java/jni/backtrace_handler.cpp",
64 "$ace_root/adapter/android/entrance/java/jni/display_info.cpp",65 "$ace_root/adapter/android/entrance/java/jni/display_info.cpp",
65 "$ace_root/adapter/android/entrance/java/jni/display_info_jni.cpp",66 "$ace_root/adapter/android/entrance/java/jni/display_info_jni.cpp",
66 "$ace_root/adapter/android/entrance/java/jni/display_manager_agent.cpp",67 "$ace_root/adapter/android/entrance/java/jni/display_manager_agent.cpp",
@@ -130,7 +131,10 @@ template("ace_common_jni_source_set") {
130 131 
131 if (is_arkui_x) {132 if (is_arkui_x) {
132 defines += [ "CROSS_PLATFORM" ]133 defines += [ "CROSS_PLATFORM" ]
133- libs = [ "mediandk" ]134+ libs = [
135+ "mediandk",
136+ "log",
137+ ]
134 }138 }
135 139 
136 defines += ["ARKUI_X_VERSION=\"$arkuix_sdk_version\""]140 defines += ["ARKUI_X_VERSION=\"$arkuix_sdk_version\""]
@@ -0,0 +1,644 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#include "backtrace_handler.h"
17+ 
18+#include <android/log.h>
19+#include <cerrno>
20+#include <csignal>
21+#include <cstdint>
22+#include <fcntl.h>
23+#include <ucontext.h>
24+#include <unistd.h>
25+ 
26+#include <atomic>
27+ 
28+// Engine log. Only used from the normal (non-signal) context, e.g. inside
29+// InitBacktraceHandler(). NEVER call LOG* from within the signal handler:
30+// HILOG routes through IPC/locks/malloc and is not async-signal-safe.
31+#include "base/log/log.h"
32+ 
33+namespace OHOS::Ace::Platform {
34+namespace {
35+ 
36+constexpr const char* LOG_TAG = "ArkUI-x";
37+ 
38+// ---------------------------------------------------------------------------
39+// Tunables
40+// ---------------------------------------------------------------------------
41+constexpr uint32_t MAX_STACK_DEPTH = 64; // hard cap on walked frames
42+constexpr uintptr_t INSTRUCTION_LENGTH = 4; // aarch64 fixed-width instruction
43+constexpr int HEX_SHIFT = 4; // bits per hex digit (AppendHex/ParseHex)
44+constexpr int DECIMAL_BASE = 10; // base for decimal formatting (AppendDec/ParseHex)
45+constexpr size_t FRAME_POINTER_SLOTS = 2; // [fp]=x29, [fp+8]=x30: one frame spans 2 pointers
46+// An ArkUI-X process loads dozens of .so plus JIT/scudo regions and per-thread
47+// stacks, so /proc/self/maps reaches several thousand entries. The original 768
48+// (and even 2048) truncated the stack/so mappings and lost frames / module
49+// names; 4096 covers the measured ~3900 entries with headroom.
50+constexpr size_t MAX_MAP_RECORDS = 4096;
51+// 1MB; the original 256KB cannot hold the full maps text such a process emits.
52+constexpr size_t MAPS_FILE_BUF_SIZE = 1024 * 1024;
53+constexpr size_t DUMP_BUF_SIZE = 8192;
C
CChaoZZ7月10日

DUMP_BUF_SIZE 过小,深层栈回溯会被静默截断

DumpBuffer 容量仅 8192 字节,而 MAX_STACK_DEPTH=64。单帧输出形如 " #NN pc=0x<16hex> /data/app/.../lib/arm64-v8a/libxxx.so+0x\n",真实 Android .so 路径常超过 100 字节,整帧约 130 字节;64 帧 × 130 ≈ 8320,尚未计入 DumpSignalHeader、标签行与结尾分隔行(合计约 300+ 字节)。即对深层原生栈(恰恰是本处理器的目标场景),缓冲区必然在遍历中途被填满,而 AppendCh(line 80 if (b.len < DUMP_BUF_SIZE - 1))一旦满即静默丢弃,最深处的帧被无声截断且无截断标记。建议在 DumpBacktrace 帧循环中当 dump.len 接近上限时调用 FlushDump 后将 dump.len 置 0 分批输出(保持全异步信号安全),或增大 DUMP_BUF_SIZE(需注意 DumpBuffer 作为局部变量落在信号栈上,勿超过栈容量)。

Severity: warning

likedislike
liwenzhen3
liwenzhen3
7月13日 评论:
54+constexpr size_t FLUSH_THRESHOLD = 256; // flush when fewer than this many bytes remain
55+constexpr size_t ALT_STACK_SIZE = 64 * 1024;
56+constexpr size_t LOG_LINE_SIZE = 512; // per-line buffer used when emitting to the system log
57+ 
58+#if defined(__aarch64__)
59+constexpr int REG_FP = 29; // x29 is the frame pointer on AArch64
60+#endif
61+ 
62+// ---------------------------------------------------------------------------
63+// /proc/self/maps model (plain data, no heap, safe to touch in a handler)
64+// ---------------------------------------------------------------------------
65+struct MapRecord {
66+ uintptr_t start;
67+ uintptr_t end;
68+ uintptr_t offset;
69+ bool executable; // permission has 'x' (i.e. may hold return addresses)
70+ const char* name; // points into g_mapsFileBuf; full length, never truncated
71+};
72+ 
73+// File-scope statics: kept off the (possibly tiny alternate) signal stack and
74+// reused across crashes. Reuse is serialized by g_inHandler below.
75+MapRecord g_maps[MAX_MAP_RECORDS];
76+size_t g_mapCount = 0;
77+char g_mapsFileBuf[MAPS_FILE_BUF_SIZE];
78+ 
79+// ---------------------------------------------------------------------------
80+// Output buffer + async-signal-safe append helpers (zero allocation)
81+// ---------------------------------------------------------------------------
82+struct DumpBuffer {
83+ char data[DUMP_BUF_SIZE];
84+ size_t len;
85+};
86+ 
87+void AppendCh(DumpBuffer& buf, char ch)
88+{
89+ if (buf.len < DUMP_BUF_SIZE - 1) {
90+ buf.data[buf.len++] = ch;
91+ }
92+}
93+ 
94+void AppendLit(DumpBuffer& buf, const char* text)
95+{
96+ while (*text != '\0') {
97+ AppendCh(buf, *text++);
98+ }
99+}
100+ 
101+void AppendStr(DumpBuffer& buf, const char* text)
102+{
103+ if (text == nullptr) {
104+ return;
105+ }
106+ while (*text != '\0') {
107+ AppendCh(buf, *text++);
108+ }
109+}
110+ 
111+void AppendHex(DumpBuffer& buf, uintptr_t value)
112+{
113+ static const char* digits = "0123456789abcdef";
114+ char tmp[2 * sizeof(uintptr_t)];
115+ int i = 0;
116+ if (value == 0) {
117+ AppendCh(buf, '0');
118+ return;
119+ }
120+ while (value != 0) {
121+ tmp[i++] = digits[value & 0xf];
122+ value >>= HEX_SHIFT;
123+ }
124+ while (i > 0) {
125+ AppendCh(buf, tmp[--i]);
126+ }
127+}
128+ 
129+void AppendHex0x(DumpBuffer& buf, uintptr_t value)
130+{
131+ AppendLit(buf, "0x");
132+ AppendHex(buf, value);
133+}
134+ 
135+void AppendDec(DumpBuffer& buf, uint32_t value)
136+{
137+ char tmp[10];
138+ int i = 0;
139+ if (value == 0) {
140+ AppendCh(buf, '0');
141+ return;
142+ }
143+ while (value != 0) {
144+ tmp[i++] = '0' + (value % DECIMAL_BASE);
145+ value /= DECIMAL_BASE;
146+ }
147+ while (i > 0) {
148+ AppendCh(buf, tmp[--i]);
149+ }
150+}
151+ 
152+// Signed-decimal variant for values that may legitimately be negative on Linux,
153+// e.g. siginfo_t::si_code (SI_TKILL == -6 for the SIGABRT raised by
154+// abort()/raise()/pthread_kill()). AppendDec only formats the unsigned magnitude.
155+void AppendDecSigned(DumpBuffer& buf, int32_t value)
156+{
157+ uint32_t magnitude;
158+ if (value < 0) {
159+ AppendCh(buf, '-');
160+ // Negate without UB: -(value + 1) + 1 is safe even for INT32_MIN.
161+ magnitude = static_cast<uint32_t>(-(value + 1)) + 1U;
162+ } else {
163+ magnitude = static_cast<uint32_t>(value);
164+ }
165+ AppendDec(buf, magnitude);
166+}
167+ 
168+// Writes the whole buffer to stderr (async-signal-safe) and additionally routes
169+// each line to the system log via __android_log_write, the lightweight (no
170+// varargs formatting) logging call. Resets buf.len to 0 afterwards, so deep
171+// backtraces can batch-flush without overflowing the fixed-size buffer.
172+void FlushDump(DumpBuffer& buf)
173+{
174+ ssize_t written = 0;
175+ while (written < static_cast<ssize_t>(buf.len)) {
176+ ssize_t bytes = write(STDERR_FILENO, buf.data + written, buf.len - written);
177+ if (bytes > 0) {
178+ written += bytes;
179+ } else if (bytes < 0 && errno == EINTR) {
180+ continue;
181+ } else {
182+ break; // give up silently; we are crashing anyway
183+ }
184+ }
185+ 
186+#ifdef __ANDROID__
187+ // Emit line-by-line so the system log shows one readable entry per frame.
188+ size_t lineStart = 0;
189+ char line[LOG_LINE_SIZE];
190+ for (size_t i = 0; i <= buf.len; ++i) {
191+ if (i == buf.len || buf.data[i] == '\n') {
192+ size_t lineLen = i - lineStart;
193+ size_t cap = sizeof(line) - 1;
194+ size_t copyLen = lineLen < cap ? lineLen : cap;
195+ for (size_t k = 0; k < copyLen; ++k) {
196+ line[k] = buf.data[lineStart + k];
197+ }
198+ line[copyLen] = '\0';
199+ if (copyLen > 0) {
200+ __android_log_write(ANDROID_LOG_FATAL, LOG_TAG, line);
201+ }
202+ lineStart = i + 1;
203+ }
204+ }
205+#endif
206+ buf.len = 0; // allow batch flushing from DumpBacktrace
207+}
208+ 
209+// ---------------------------------------------------------------------------
210+// /proc/self/maps parsing (async-signal-safe: open/read/close only)
211+// ---------------------------------------------------------------------------
212+bool ParseHex(const char*& cursor, const char* end, uintptr_t& out)
213+{
214+ uintptr_t value = 0;
215+ bool found = false;
216+ while (cursor < end) {
217+ char ch = *cursor;
218+ int digit;
219+ if (ch >= '0' && ch <= '9') {
220+ digit = ch - '0';
221+ } else if (ch >= 'a' && ch <= 'f') {
222+ digit = DECIMAL_BASE + (ch - 'a');
223+ } else if (ch >= 'A' && ch <= 'F') {
224+ digit = DECIMAL_BASE + (ch - 'A');
225+ } else {
226+ break;
227+ }
228+ value = (value << HEX_SHIFT) | static_cast<uintptr_t>(digit);
229+ found = true;
230+ ++cursor;
231+ }
232+ out = value;
233+ return found;
234+}
235+ 
236+const char* SkipSpaces(const char* cursor, const char* end)
237+{
238+ while (cursor < end && (*cursor == ' ' || *cursor == '\t')) {
239+ ++cursor;
240+ }
241+ return cursor;
242+}
243+ 
244+// Advances past one whitespace-delimited token, returning its end pointer.
245+const char* SkipToken(const char* cursor, const char* end)
246+{
247+ while (cursor < end && *cursor != ' ' && *cursor != '\t' && *cursor != '\n') {
248+ ++cursor;
249+ }
250+ return cursor;
251+}
252+ 
253+// Parses one maps line in [line, end). Returns false for malformed lines.
254+// `line` is mutable so the caller's buffer can be null-terminated in place.
255+bool ParseMapLine(char* line, const char* end, MapRecord& rec)
256+{
257+ const char* cursor = line;
258+ 
259+ uintptr_t start = 0;
260+ if (!ParseHex(cursor, end, start)) {
261+ return false;
262+ }
263+ if (cursor < end && *cursor == '-') {
264+ ++cursor;
265+ }
266+ 
267+ uintptr_t mapEnd = 0;
268+ if (!ParseHex(cursor, end, mapEnd)) {
269+ return false;
270+ }
271+ if (mapEnd <= start) {
272+ return false;
273+ }
274+ 
275+ cursor = SkipSpaces(cursor, end);
276+ 
277+ // Permissions token, e.g. "r-xp".
278+ const char* perms = cursor;
279+ cursor = SkipToken(cursor, end);
280+ bool isExec = false;
281+ for (const char* ch = perms; ch < cursor; ++ch) {
282+ if (*ch == 'x') {
283+ isExec = true;
284+ break;
285+ }
286+ }
287+ cursor = SkipSpaces(cursor, end);
288+ 
289+ uintptr_t offset = 0;
290+ ParseHex(cursor, end, offset); // file offset (may be absent for some entries)
291+ 
292+ // Skip dev token and inode token.
293+ cursor = SkipSpaces(cursor, end);
294+ cursor = SkipToken(cursor, end);
295+ cursor = SkipSpaces(cursor, end);
296+ cursor = SkipToken(cursor, end);
297+ cursor = SkipSpaces(cursor, end);
298+ 
299+ // Remainder of the line is the pathname. Point directly into g_mapsFileBuf
300+ // (no fixed-size copy, so the path is never truncated) and null-terminate it
301+ // in place by overwriting the trailing '\n' (the buffer is ours/mutable).
302+ rec.name = cursor;
303+ while (cursor < end && *cursor != '\n') {
304+ ++cursor;
305+ }
306+ line[cursor - line] = '\0'; // null-terminate the name in place (buffer is mutable)
307+ 
308+ rec.start = start;
309+ rec.end = mapEnd;
310+ rec.offset = offset;
311+ rec.executable = isExec;
312+ return true;
313+}
314+ 
315+void LoadMaps()
316+{
317+ g_mapCount = 0;
318+ int fd = open("/proc/self/maps", O_RDONLY | O_CLOEXEC);
319+ if (fd < 0) {
320+ return;
321+ }
322+ 
323+ size_t total = 0;
324+ // Leave one byte so ParseMapLine can always null-terminate a name in place.
325+ while (total < MAPS_FILE_BUF_SIZE - 1) {
326+ ssize_t bytes = read(fd, g_mapsFileBuf + total, MAPS_FILE_BUF_SIZE - 1 - total);
327+ if (bytes > 0) {
328+ total += static_cast<size_t>(bytes);
329+ } else if (bytes == 0) {
330+ break;
331+ } else if (errno == EINTR) {
332+ continue;
333+ } else {
334+ break;
335+ }
336+ }
337+ close(fd);
338+ 
339+ char* cursor = g_mapsFileBuf;
340+ char* fileEnd = g_mapsFileBuf + total;
341+ while (cursor < fileEnd && g_mapCount < MAX_MAP_RECORDS) {
342+ char* lineEnd = cursor;
343+ while (lineEnd < fileEnd && *lineEnd != '\n') {
344+ ++lineEnd;
345+ }
346+ if (ParseMapLine(cursor, lineEnd, g_maps[g_mapCount])) {
347+ ++g_mapCount;
348+ }
349+ cursor = (lineEnd < fileEnd) ? lineEnd + 1 : fileEnd;
350+ }
351+}
352+ 
353+const MapRecord* FindRecord(uintptr_t addr)
354+{
355+ for (size_t i = 0; i < g_mapCount; ++i) {
356+ const MapRecord& rec = g_maps[i];
357+ if (addr >= rec.start && addr < rec.end) {
358+ return &rec;
359+ }
360+ }
361+ return nullptr;
362+}
363+ 
364+bool NamesMatch(const char* lhs, const char* rhs)
365+{
366+ if (lhs == nullptr || rhs == nullptr) {
367+ return false;
368+ }
369+ while (*lhs != '\0' && *rhs != '\0') {
370+ if (*lhs != *rhs) {
371+ return false;
372+ }
373+ ++lhs;
374+ ++rhs;
375+ }
376+ return *lhs == *rhs; // equal only if both ended together
377+}
378+ 
379+// Returns the load base (runtime address of vaddr 0) of the shared object that
380+// owns `mod`: the mapping with file offset 0 and the SAME name. We match by
381+// name (MapRecord.name points into the raw maps text, so it is never truncated)
382+// because an address-only heuristic fails: /proc/self/maps also contains huge
383+// anonymous regions (scudo reserves, heap) whose offset is 0, and those would
384+// be picked instead of the library's own base.
385+uintptr_t FindLoadBase(const MapRecord* mod)
386+{
387+ if (mod == nullptr || mod->name[0] == '\0') {
388+ return 0;
389+ }
390+ uintptr_t best = 0;
391+ for (size_t i = 0; i < g_mapCount; ++i) {
392+ const MapRecord& rec = g_maps[i];
393+ if (rec.offset == 0 && rec.start <= mod->start && rec.start > best && NamesMatch(rec.name, mod->name)) {
394+ best = rec.start;
395+ }
396+ }
397+ return best;
398+}
399+ 
400+// ---------------------------------------------------------------------------
401+// Frame-pointer chain walk + dump
402+// ---------------------------------------------------------------------------
403+void DumpSignalHeader(DumpBuffer& buf, int sig, siginfo_t* info)
404+{
405+ AppendLit(buf, "\n================ ArkUI-x native crash ================\n");
406+ AppendLit(buf, "Fatal signal ");
407+ AppendDec(buf, static_cast<uint32_t>(sig));
408+ AppendLit(buf, " (");
409+ switch (sig) {
410+ case SIGSEGV: AppendLit(buf, "SIGSEGV"); break;
411+ case SIGABRT: AppendLit(buf, "SIGABRT"); break;
412+ case SIGBUS: AppendLit(buf, "SIGBUS"); break;
413+ case SIGILL: AppendLit(buf, "SIGILL"); break;
414+ case SIGFPE: AppendLit(buf, "SIGFPE"); break;
415+ default: AppendLit(buf, "?"); break;
416+ }
417+ AppendLit(buf, "), si_code ");
418+ AppendDecSigned(buf, info ? info->si_code : 0);
419+ if (info != nullptr && info->si_addr != nullptr) {
420+ AppendLit(buf, ", fault addr ");
421+ AppendHex0x(buf, reinterpret_cast<uintptr_t>(info->si_addr));
422+ }
423+ AppendCh(buf, '\n');
424+}
425+ 
426+void DumpBacktrace(DumpBuffer& buf, uintptr_t fp)
427+{
428+ AppendLit(buf, "Backtrace (frame-pointer chain):\n");
429+ 
430+#if defined(__aarch64__)
431+ if (fp == 0) {
432+ AppendLit(buf, " fp == 0, cannot walk the chain\n");
433+ return;
434+ }
435+ 
436+ // The mapping holding the initial fp is (one of) the stack mapping(s); use it
437+ // to bound the walk so a corrupted chain cannot run away. If unknown, fall
438+ // back to the monotonic-up check plus the depth cap.
439+ const MapRecord* stack = FindRecord(fp);
440+ const uintptr_t stackLo = stack ? stack->start : 0;
441+ const uintptr_t stackHi = stack ? stack->end : 0;
442+ 
443+ for (uint32_t idx = 0; idx < MAX_STACK_DEPTH; ++idx) {
444+ // The whole frame [fp, fp + 2*sizeof(uintptr_t)) must lie inside the
445+ // stack mapping before we dereference it. Checking only `fp < stackHi`
446+ // is not enough: the two 8-byte reads at fp/fp+8 would still run past
447+ // the mapping edge when fp sits in the last 16 bytes and hit a guard
448+ // page, re-faulting inside the handler. When fp does not fall in any
449+ // known stack mapping (nullptr) there is no safe bound to rely on, so
450+ // stop the walk rather than dereference blindly.
451+ if (stack != nullptr) {
452+ if (fp < stackLo || fp + FRAME_POINTER_SLOTS * sizeof(uintptr_t) > stackHi) {
453+ break;
454+ }
455+ } else {
456+ // fp did not land in any parsed mapping (e.g. the stack mapping was
457+ // truncated by MAX_MAP_RECORDS). Rather than give up the whole
458+ // backtrace, do a minimal sanity check on the AArch64 16-byte frame
459+ // alignment and attempt the read. A second fault here is still caught
460+ // by the g_inHandler re-entrancy guard.
461+ if (fp == 0 || (fp & 0xf) != 0) {
462+ break;
463+ }
464+ }
465+ 
466+ // Frame layout produced by the AArch64 prologue (stp x29, x30):
467+ // [fp + 0] = saved x29 (link to the previous frame)
468+ // [fp + 8] = saved x30 (return address / LR)
469+ uintptr_t lr = *reinterpret_cast<uintptr_t*>(fp + 8);
470+ uintptr_t next = *reinterpret_cast<uintptr_t*>(fp);
471+ uintptr_t callSite = (lr != 0) ? lr - INSTRUCTION_LENGTH : 0;
472+ 
473+ // A deep native stack can hold more frames than DUMP_BUF_SIZE; flush
474+ // and reuse the buffer before appending this frame so frames are never
475+ // silently dropped. FlushDump is async-signal-safe and resets buf.len.
476+ if (buf.len > DUMP_BUF_SIZE - FLUSH_THRESHOLD) {
477+ FlushDump(buf);
478+ }
479+ 
480+ AppendLit(buf, " #");
481+ AppendDec(buf, idx);
482+ AppendLit(buf, " pc=");
483+ AppendHex0x(buf, callSite);
484+ 
485+ const MapRecord* mod = FindRecord(callSite);
486+ if (mod != nullptr) {
487+ AppendLit(buf, " ");
488+ AppendStr(buf, mod->name);
489+ AppendLit(buf, " +0x");
490+ // Print the vaddr = callSite - load base, which addr2line /
491+ // llvm-symbolizer consume directly (no manual +base correction).
492+ // Fall back to the mapping-relative offset if the load base is unknown.
493+ uintptr_t loadBase = FindLoadBase(mod);
494+ uintptr_t relOffset = (loadBase != 0) ? (callSite - loadBase) : (callSite - mod->start);
495+ AppendHex(buf, relOffset);
496+ }
497+ AppendCh(buf, '\n');
498+ 
499+ // The saved fp must point upward (stack grows down); otherwise the
500+ // chain has ended or is corrupted. The full [next, next+16) range is
501+ // re-checked against the stack mapping at the top of the next iteration.
502+ if (next <= fp) {
503+ break;
504+ }
505+ fp = next;
506+ }
507+#else
508+ (void)fp; // FP-chain walk is aarch64-only; nothing to traverse on this ABI.
509+ AppendLit(buf, " FP-chain walk is only implemented on aarch64 (arch skipped)\n");
510+#endif
511+}
512+ 
513+// ---------------------------------------------------------------------------
514+// Signal handler
515+// ---------------------------------------------------------------------------
516+std::atomic<bool> g_installed { false };
517+// Process-wide (NOT thread_local) on purpose. While one thread is dumping
518+// (reading /proc/self/maps, walking the FP chain, writing the system log), any other
519+// thread that crashes skips its own dump and just forwards to the previous
520+// handler. This is a deliberate trade-off: the maps model (g_maps /
521+// g_mapsFileBuf) is process-global, so letting two threads dump concurrently
522+// would race and corrupt it (and risk a second fault). Consequence: in a
523+// multi-threaded crash, only the first crashing thread emits a backtrace.
524+std::atomic<bool> g_inHandler { false };
525+struct sigaction g_savedHandlers[NSIG];
526+ 
527+// Hands control back to whatever handler (if any) was installed before us, and
528+// ensures a SIG_DFL disposition still terminates with a system crash report.
529+void ForwardToPrevious(int sig, siginfo_t* info, void* context)
530+{
531+ const struct sigaction& prev = g_savedHandlers[sig];
532+ if (prev.sa_flags & SA_SIGINFO) {
533+ if (prev.sa_sigaction != nullptr) {
534+ prev.sa_sigaction(sig, info, context);
535+ return;
536+ }
537+ } else if (prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN && prev.sa_handler != SIG_ERR) {
538+ prev.sa_handler(sig);
539+ return;
540+ }
541+ 
542+ // SIG_DFL / SIG_IGN / nothing usable: restore the prior disposition and
543+ // re-raise so the default action (crash report/coredump) runs.
544+ sigaction(sig, &prev, nullptr);
545+ if (prev.sa_handler != SIG_IGN) {
546+ sigset_t mask;
547+ sigemptyset(&mask);
548+ sigaddset(&mask, sig);
549+ sigprocmask(SIG_UNBLOCK, &mask, nullptr);
550+ raise(sig);
551+ }
552+}
553+ 
554+void CrashHandler(int sig, siginfo_t* info, void* context)
555+{
556+ // Guard against re-entrancy (a fault while dumping). If we are already inside
C
CChaoZZ7月10日

g_inHandler 为进程级全局,转储期间其他线程崩溃不会输出回栈

注释将其描述为防"re-entrancy(重入)",但 g_inHandler 是单个 std::atomic,为所有线程共享,而非按线程隔离。后果:当线程 A 正在转储(读取 /proc/self/maps、走 FP 链、写 stderr/logcat,耗时数毫秒)期间,若线程 B 发生崩溃,B 的 CrashHandler 在 CAS 处发现标志已为 true,直接 ForwardToPrevious 返回,B 的回栈完全不输出。对于多线程并发崩溃(如共享数据损坏导致多个线程相继 fault)这类最需要现场的场景,会丢失其中一线程的上下文。建议改用 thread_local 守卫以允许各线程独立转储,同时仍能捕获同线程内的二次 fault;若确属可接受的折中,请在注释中明确说明"同一时刻仅输出首个崩溃线程的回栈"。

Severity: info

likedislike
liwenzhen3
liwenzhen3
7月13日 评论:
557+ // the handler, skip the dump and just forward.
558+ bool expected = false;
559+ if (!g_inHandler.compare_exchange_strong(expected, true)) {
560+ ForwardToPrevious(sig, info, context);
561+ return;
562+ }
563+ 
564+ uintptr_t fp = 0;
565+#if defined(__aarch64__)
566+ if (context != nullptr) {
567+ auto* uc = static_cast<ucontext_t*>(context);
568+ fp = uc->uc_mcontext.regs[REG_FP]; // saved x29 of the interrupted frame
569+ }
570+#endif
571+ 
572+ DumpBuffer dump {};
573+ DumpSignalHeader(dump, sig, info);
574+ LoadMaps();
575+ DumpBacktrace(dump, fp);
576+ AppendLit(dump, "======================================================\n");
577+ FlushDump(dump);
578+ 
579+ g_inHandler.store(false);
580+ 
581+ ForwardToPrevious(sig, info, context);
582+}
583+ 
584+// ---------------------------------------------------------------------------
585+// Installation (runs in normal context)
586+// ---------------------------------------------------------------------------
587+void InstallAltStack()
588+{
589+ // Static so the buffer outlives the call. sigaltstack is per-thread on the
590+ // system libc, so this protects the thread that runs JNI_OnLoad; other
591+ // threads fall back to their own stack (stack-overflow on them may not be
592+ // caught, ordinary SEGV/ABRT still are).
593+ static char altStack[ALT_STACK_SIZE];
594+ stack_t sigStack {};
595+ sigStack.ss_sp = altStack;
596+ sigStack.ss_size = sizeof(altStack);
597+ sigStack.ss_flags = 0;
598+ sigaltstack(&sigStack, nullptr);
599+}
600+ 
601+} // namespace
602+ 
603+void InitBacktraceHandler()
604+{
605+ bool expected = false;
606+ if (!g_installed.compare_exchange_strong(expected, true)) {
607+ return; // already installed
608+ }
609+ 
610+ InstallAltStack();
611+ 
612+ struct sigaction sa {};
613+ sa.sa_sigaction = CrashHandler;
614+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK; // SA_ONSTACK -> run on the alternate stack
615+ sigemptyset(&sa.sa_mask);
616+ 
617+ const int handledSignals[] = { SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE };
618+ for (int sig : handledSignals) {
619+ struct sigaction old {};
620+ if (sigaction(sig, &sa, &old) == 0) {
621+ g_savedHandlers[sig] = old;
622+ }
623+ }
624+ 
625+ LOGI("ArkUI-x backtrace handler installed.");
626+}
627+ 
628+void DumpBacktraceFromFp(uintptr_t fp)
629+{
630+ bool expected = false;
631+ if (!g_inHandler.compare_exchange_strong(expected, true)) {
632+ return; // a crash dump is in progress on this thread
633+ }
634+ 
635+ DumpBuffer dump {};
636+ LoadMaps();
637+ DumpBacktrace(dump, fp);
638+ AppendLit(dump, "(end of manual backtrace)\n");
639+ FlushDump(dump);
640+ 
641+ g_inHandler.store(false);
642+}
643+ 
644+} // namespace OHOS::Ace::Platform
@@ -0,0 +1,57 @@
1+/*
2+ * Copyright (c) 2026 Huawei Device Co., Ltd.
3+ * Licensed under the Apache License, Version 2.0 (the "License");
4+ * you may not use this file except in compliance with the License.
5+ * You may obtain a copy of the License at
6+ *
7+ * http://www.apache.org/licenses/LICENSE-2.0
8+ *
9+ * Unless required by applicable law or agreed to in writing, software
10+ * distributed under the License is distributed on an "AS IS" BASIS,
11+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+ * See the License for the specific language governing permissions and
13+ * limitations under the License.
14+ */
15+ 
16+#ifndef FOUNDATION_ACE_ADAPTER_ANDROID_ENTRANCE_JAVA_JNI_BACKTRACE_HANDLER_H
17+#define FOUNDATION_ACE_ADAPTER_ANDROID_ENTRANCE_JAVA_JNI_BACKTRACE_HANDLER_H
18+ 
19+#include <cstdint>
20+ 
21+namespace OHOS::Ace::Platform {
22+ 
23+// Installs async-signal-safe crash handlers (SIGSEGV/SIGABRT/SIGBUS/SIGILL/SIGFPE)
24+// that dump a frame-pointer based native backtrace. Idempotent; safe to call more
25+// than once. Designed to be invoked once from JNI_OnLoad of libarkui_android.so.
26+//
27+// The whole dump path is async-signal-safe: no malloc, no stdio, no locks, no
28+// HILOG. It reads /proc/self/maps through open()/read() and walks the FP chain
29+// (x29) with hand-rolled integer formatting, emitting via write(2) and
30+// __android_log_write. Any pre-existing handler (the runtime / signal chain /
31+// crash reporter) is saved and chained to, so a system crash report is still
32+// produced.
33+//
34+// The FP chain is only walked on aarch64; on other ABIs only the signal is
35+// reported. Stack frames that omit the frame pointer (JIT/JS, some third-party
36+// libs) terminate the chain, so the dump covers the native C++ caller frames
37+// around the crash rather than the full stack.
38+//
39+// Each printed frame is "<module>+0x<vaddr>", where vaddr = runtime_pc - the
40+// module's load base. That value is consumable by addr2line / llvm-symbolizer
41+// directly (point them at the matching unstripped .so), with no manual base
42+// correction needed.
43+// Multi-threading: the re-entrancy guard is process-wide, so if several threads
44+// crash concurrently only the first one dumps a backtrace; the others forward
45+// straight to the previous handler. This avoids racing the process-global maps
46+// model.
47+void InitBacktraceHandler();
48+ 
49+// Dumps the frame-pointer chain starting at the given frame pointer, using the
50+// same async-signal-safe code path as the crash handler. Exposed so the walk can
51+// be triggered from a normal context (e.g. diagnostics or a forced-crash test).
52+// No-op when fp == 0 or on non-aarch64 ABIs.
53+void DumpBacktraceFromFp(uintptr_t fp);
54+ 
55+} // namespace OHOS::Ace::Platform
56+ 
57+#endif // FOUNDATION_ACE_ADAPTER_ANDROID_ENTRANCE_JAVA_JNI_BACKTRACE_HANDLER_H
@@ -15,6 +15,7 @@
15 15 
16#include "jni.h"16#include "jni.h"
17 17 
18+#include "adapter/android/entrance/java/jni/backtrace_handler.h"
18#include "adapter/android/entrance/java/jni/jni_environment.h"19#include "adapter/android/entrance/java/jni/jni_environment.h"
19#include "base/log/log.h"20#include "base/log/log.h"
20#include "base/log/ace_trace.h"21#include "base/log/ace_trace.h"
@@ -55,6 +56,12 @@ jint JNI_OnLoad(JavaVM* vm, void*)
55 return RET_FAIL;56 return RET_FAIL;
56 }57 }
57 58 
59+ // Install async-signal-safe FP-chain crash handlers so native crashes in
60+ // libarkui_android.so still produce a backtrace even when unwind tables are
61+ // stripped for size. Must run after the JVM/JS runtime is up so the saved
62+ // (previous) handlers it chains to are already in place.
63+ OHOS::Ace::Platform::InitBacktraceHandler();
64+ 
58 LOGI("JNI Onload: sharedlibrary has been loaded successsfully!");65 LOGI("JNI Onload: sharedlibrary has been loaded successsfully!");
59 return OHOS::Ace::Platform::JniEnvironment::GetInstance().Version();66 return OHOS::Ace::Platform::JniEnvironment::GetInstance().Version();
60}67}