已合并
add ios backtrace #691
add ios backtrace #691
已合并
liwenzhen3创建于 7月9日
共 4 个文件变更+503-0
@@ -45,6 +45,7 @@ template("ace_osal_ios_source_set") {
45 "advance/text_share_adapter.cpp",45 "advance/text_share_adapter.cpp",
46 "advance/text_translation_adapter.cpp",46 "advance/text_translation_adapter.cpp",
47 "app_bar_helper_impl.cpp",47 "app_bar_helper_impl.cpp",
48+ "backtrace_handler.cpp",
48 "cpu_boost.cpp",49 "cpu_boost.cpp",
49 "display_info_utils.cpp",50 "display_info_utils.cpp",
50 "display_manager_ios.cpp",51 "display_manager_ios.cpp",
@@ -0,0 +1,441 @@
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 <cerrno>
19+#include <csignal>
20+#include <cstdint>
21+#include <sys/ucontext.h>
22+#include <unistd.h>
23+ 
24+#include <atomic>
25+ 
26+#include <mach-o/dyld.h>
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/os_log route through IPC/locks/malloc and are not async-signal-safe.
31+#include "base/log/log.h"
32+ 
33+// iOS port note: /proc/self/maps does not exist on Darwin; the image table is
34+// pre-cached via the dyld API at init time.
35+ 
36+namespace OHOS::Ace::Platform {
37+namespace {
38+ 
39+// ---------------------------------------------------------------------------
40+// Tunables
41+// ---------------------------------------------------------------------------
42+constexpr uint32_t MAX_STACK_DEPTH = 64;
43+constexpr uintptr_t INSTRUCTION_LENGTH = 4; // aarch64 fixed-width instruction
44+constexpr size_t MAX_IMAGES = 1024;
45+constexpr size_t MAX_NAME_LEN = 128;
46+constexpr size_t DUMP_BUF_SIZE = 8192;
47+constexpr size_t ALT_STACK_SIZE = 64 * 1024;
48+constexpr int DECIMAL_BASE = 10; // base for decimal formatting
49+constexpr int HEX_SHIFT = 4; // bits per hex digit
50+ 
51+// ---------------------------------------------------------------------------
52+// Loaded-image table (pre-cached from dyld at init; read-only in the handler)
53+// ---------------------------------------------------------------------------
54+struct ImageRecord {
55+ uintptr_t loadAddr; // runtime load address = vmaddr + slide
56+ intptr_t slide; // ASLR slide
57+ char name[MAX_NAME_LEN];
58+};
59+ 
60+ImageRecord g_images[MAX_IMAGES];
61+size_t g_imageCount = 0;
62+ 
63+void LoadImages()
64+{
65+ g_imageCount = 0;
66+ const uint32_t count = _dyld_image_count();
67+ for (uint32_t i = 0; i < count && g_imageCount < MAX_IMAGES; ++i) {
68+ const char* name = _dyld_get_image_name(i);
69+ // The Mach-O header sits at the image's runtime load address; the slide
70+ // is its ASLR offset. (dyld has no _dyld_get_image_vmaddr/_slide; the
71+ // real APIs are _dyld_get_image_header and _dyld_get_image_vmaddr_slide.)
72+ const struct mach_header* header = _dyld_get_image_header(i);
73+ if (name == nullptr || header == nullptr) {
74+ continue;
75+ }
76+ ImageRecord& img = g_images[g_imageCount];
77+ img.loadAddr = reinterpret_cast<uintptr_t>(header);
78+ img.slide = _dyld_get_image_vmaddr_slide(i);
79+ size_t nameIdx = 0;
80+ for (; nameIdx + 1 < MAX_NAME_LEN && name[nameIdx] != '\0'; ++nameIdx) {
81+ img.name[nameIdx] = name[nameIdx];
82+ }
83+ img.name[nameIdx] = '\0';
84+ ++g_imageCount;
85+ }
86+}
87+ 
88+// Returns the image whose load address is the largest one <= addr (images do
89+// not overlap, so that is the image containing addr). Heuristic: a PC in a gap
90+// between images would be attributed to the lower image; rare for code PCs.
91+const ImageRecord* FindImage(uintptr_t addr)
92+{
93+ const ImageRecord* bestImg = nullptr;
94+ for (size_t i = 0; i < g_imageCount; ++i) {
95+ const ImageRecord& img = g_images[i];
96+ if (img.loadAddr <= addr && (bestImg == nullptr || img.loadAddr > bestImg->loadAddr)) {
97+ bestImg = &img;
98+ }
99+ }
100+ return bestImg;
101+}
102+ 
103+// ---------------------------------------------------------------------------
104+// Output buffer + async-signal-safe append helpers (zero allocation)
105+// ---------------------------------------------------------------------------
106+struct DumpBuffer {
107+ char data[DUMP_BUF_SIZE];
108+ size_t len;
109+};
110+ 
111+void AppendCh(DumpBuffer& buf, char ch)
112+{
113+ if (buf.len < DUMP_BUF_SIZE - 1) {
114+ buf.data[buf.len++] = ch;
115+ }
116+}
117+ 
118+void AppendLit(DumpBuffer& buf, const char* text)
119+{
120+ while (*text != '\0') {
121+ AppendCh(buf, *text++);
122+ }
123+}
124+ 
125+void AppendStrN(DumpBuffer& buf, const char* text, size_t len)
126+{
127+ for (size_t i = 0; i < len; ++i) {
128+ if (text[i] == '\0') {
129+ break;
130+ }
131+ AppendCh(buf, text[i]);
132+ }
133+}
134+ 
135+void AppendHex(DumpBuffer& buf, uintptr_t value)
136+{
137+ static const char* digits = "0123456789abcdef";
138+ char tmp[2 * sizeof(uintptr_t)];
139+ int i = 0;
140+ if (value == 0) {
141+ AppendCh(buf, '0');
142+ return;
143+ }
144+ while (value != 0) {
145+ tmp[i++] = digits[value & 0xf];
146+ value >>= HEX_SHIFT;
147+ }
148+ while (i > 0) {
149+ AppendCh(buf, tmp[--i]);
150+ }
151+}
152+ 
153+void AppendHex0x(DumpBuffer& buf, uintptr_t value)
154+{
155+ AppendLit(buf, "0x");
156+ AppendHex(buf, value);
157+}
158+ 
159+void AppendDec(DumpBuffer& buf, uint32_t value)
160+{
161+ char tmp[10];
162+ int i = 0;
163+ if (value == 0) {
164+ AppendCh(buf, '0');
165+ return;
166+ }
167+ while (value != 0) {
168+ tmp[i++] = '0' + (value % DECIMAL_BASE);
169+ value /= DECIMAL_BASE;
170+ }
171+ while (i > 0) {
172+ AppendCh(buf, tmp[--i]);
173+ }
174+}
175+ 
176+// Signed-decimal variant for values that may legitimately be negative on Darwin,
177+// e.g. siginfo_t::si_code (SI_USER / SI_TKILL for SIGABRT raised by
178+// abort()/raise()/pthread_kill()). AppendDec only formats the unsigned magnitude.
179+void AppendDecSigned(DumpBuffer& buf, int32_t value)
180+{
181+ uint32_t magnitude;
182+ if (value < 0) {
183+ AppendCh(buf, '-');
184+ // Negate without UB: -(value + 1) + 1 is safe even for INT32_MIN.
185+ magnitude = static_cast<uint32_t>(-(value + 1)) + 1U;
186+ } else {
187+ magnitude = static_cast<uint32_t>(value);
188+ }
189+ AppendDec(buf, magnitude);
190+}
191+ 
192+// Writes the whole buffer to stderr (async-signal-safe). Resets buf.len to 0 so
193+// DumpBacktrace can flush frame-by-frame (a later re-fault must not discard the
194+// already-collected frames held in this buffer).
195+void FlushDump(DumpBuffer& buf)
196+{
197+ // write(2) is async-signal-safe; on iOS fd 2 reaches syslog/Console.app.
198+ ssize_t written = 0;
199+ while (written < static_cast<ssize_t>(buf.len)) {
200+ ssize_t bytes = write(STDERR_FILENO, buf.data + written, buf.len - written);
201+ if (bytes > 0) {
202+ written += bytes;
203+ } else if (bytes < 0 && errno == EINTR) {
204+ continue;
205+ } else {
206+ break;
207+ }
208+ }
209+ buf.len = 0;
210+}
211+ 
212+// ---------------------------------------------------------------------------
213+// Frame printing: "<image>+0x<vaddr>" where vaddr = pc - slide (static/unslid
214+// address consumable by `atos`).
215+// ---------------------------------------------------------------------------
216+void PrintFrame(DumpBuffer& buf, uint32_t idx, uintptr_t pc)
217+{
218+ AppendLit(buf, " #");
219+ AppendDec(buf, idx);
220+ AppendLit(buf, " pc=");
221+ AppendHex0x(buf, pc);
222+ const ImageRecord* img = FindImage(pc);
223+ if (img != nullptr) {
224+ AppendLit(buf, " ");
225+ AppendStrN(buf, img->name, MAX_NAME_LEN);
226+ AppendLit(buf, "+0x");
227+ AppendHex(buf, pc - static_cast<uintptr_t>(img->slide));
228+ }
229+ AppendCh(buf, '\n');
230+}
231+ 
232+// ---------------------------------------------------------------------------
233+// Frame-pointer chain walk + dump
234+// ---------------------------------------------------------------------------
235+void DumpSignalHeader(DumpBuffer& buf, int sig, siginfo_t* info)
236+{
237+ AppendLit(buf, "\n================ ArkUI-x native crash ================\n");
238+ AppendLit(buf, "Fatal signal ");
239+ AppendDec(buf, static_cast<uint32_t>(sig));
240+ AppendLit(buf, " (");
241+ switch (sig) {
242+ case SIGSEGV: AppendLit(buf, "SIGSEGV"); break;
243+ case SIGABRT: AppendLit(buf, "SIGABRT"); break;
244+ case SIGBUS: AppendLit(buf, "SIGBUS"); break;
245+ case SIGILL: AppendLit(buf, "SIGILL"); break;
246+ case SIGFPE: AppendLit(buf, "SIGFPE"); break;
247+ default: AppendLit(buf, "?"); break;
248+ }
249+ AppendLit(buf, "), si_code ");
250+ AppendDecSigned(buf, info ? info->si_code : 0);
251+ if (info != nullptr && info->si_addr != nullptr) {
252+ AppendLit(buf, ", fault addr ");
253+ AppendHex0x(buf, reinterpret_cast<uintptr_t>(info->si_addr));
254+ }
255+ AppendCh(buf, '\n');
256+}
257+ 
258+void DumpBacktrace(DumpBuffer& buf, uintptr_t fp, uintptr_t faultPc)
259+{
260+ AppendLit(buf, "Backtrace (frame-pointer chain):\n");
261+ 
262+#if defined(__arm64__)
263+ uint32_t idx = 0;
264+ 
265+ // #0 = the faulting instruction itself (the crashing function).
266+ if (faultPc != 0) {
267+ PrintFrame(buf, idx++, faultPc);
268+ }
269+ 
270+ if (fp == 0) {
271+ AppendLit(buf, " fp == 0, cannot walk the chain\n");
272+ return;
273+ }
274+ 
275+ // No stack-range bound on iOS (no /proc/self/maps); rely on the 16-byte
276+ // alignment check, the monotonic-up check, and the depth cap to stop a
277+ // corrupted chain.
278+ for (; idx < MAX_STACK_DEPTH; ++idx) {
279+ // arm64 stack frames are 16-byte aligned; a corrupted fp or one that
280+ // came from a frame-pointer-omitting function (JIT/JS, some libs) is
281+ // usually mis-aligned. Stop the walk instead of dereferencing it,
282+ // which would re-fault inside the handler.
283+ if (fp == 0 || (fp & 0xf) != 0) {
284+ break;
285+ }
286+ // [fp + 0] = saved x29 (previous frame), [fp + 8] = saved x30 (LR).
287+ uintptr_t lr = *reinterpret_cast<uintptr_t*>(fp + 8);
288+ uintptr_t next = *reinterpret_cast<uintptr_t*>(fp);
此地不宜久刘同学

fp 链解引用未做可读性校验,递归崩溃会丢失已生成的转储

问题:DumpBacktrace 在 FP 链遍历循环中直接对 fp 解引用,未对待读地址做任何可读性校验。证据代码行:uintptr_t lr = *reinterpret_cast<uintptr_t*>(fp + 8); 与 uintptr_t next = *reinterpret_cast<uintptr_t*>(fp);(osal/backtrace_handler.cpp:257-258)。影响:当 fp 链被破坏或指向未映射内存(栈溢出、堆损坏、JIT/JS 帧缺少帧指针等场景)时,该解引用会在信号处理函数内部再次触发 SIGSEGV;由于 CrashHandler 入口已用 g_inHandler.compare_exchange_strong 设置重入保护,递归进入的处理函数会直接走 ForwardToPrevious 返回,局部变量 DumpBuffer dump 中已收集的部分转储永远不会经 FlushDump 输出,已生成的回栈信息全部丢失。建议:在每次解引用前先 FlushDump 将已缓冲内容落盘(或改为逐帧 write),确保后续解引用崩溃时已打印帧仍保留;并增加对齐校验(arm64 上 fp 应 16 字节对齐)以提前过滤明显非法的 fp 值。

Severity: warning

likedislike
liwenzhen3
liwenzhen3
7月13日 评论:
289+ uintptr_t callSite = (lr != 0) ? lr - INSTRUCTION_LENGTH : 0;
290+ if (callSite == 0) {
291+ break; // bottom-of-stack frame: saved LR is 0 (no caller), stop.
292+ }
293+ PrintFrame(buf, idx, callSite);
294+ // Flush each frame as soon as it is printed. If a later dereference
295+ // re-faults, g_inHandler's re-entrancy guard skips the final FlushDump,
296+ // so buffering frames until the end would lose them all; per-frame
297+ // flushing preserves everything already walked.
298+ FlushDump(buf);
299+ if (next <= fp) {
300+ break; // stack grows down: previous fp must be higher
301+ }
302+ fp = next;
303+ }
304+#else
305+ (void)fp;
306+ (void)faultPc;
307+ AppendLit(buf, " FP-chain walk is only implemented on arm64\n");
308+#endif
309+}
310+ 
311+// ---------------------------------------------------------------------------
312+// Signal handler
313+// ---------------------------------------------------------------------------
314+std::atomic<bool> g_installed { false };
315+std::atomic<bool> g_inHandler { false };
316+struct sigaction g_savedHandlers[NSIG];
317+ 
318+void ForwardToPrevious(int sig, siginfo_t* info, void* context)
319+{
320+ const struct sigaction& prev = g_savedHandlers[sig];
321+ if (prev.sa_flags & SA_SIGINFO) {
322+ if (prev.sa_sigaction != nullptr) {
323+ prev.sa_sigaction(sig, info, context);
324+ return;
325+ }
326+ } else if (prev.sa_handler != SIG_DFL && prev.sa_handler != SIG_IGN &&
327+ prev.sa_handler != SIG_ERR) {
328+ prev.sa_handler(sig);
329+ return;
330+ }
331+ 
332+ // SIG_DFL / SIG_IGN: restore prior disposition and re-raise so iOS still
333+ // produces its own crash log.
334+ sigaction(sig, &prev, nullptr);
335+ if (prev.sa_handler != SIG_IGN) {
336+ sigset_t mask;
337+ sigemptyset(&mask);
338+ sigaddset(&mask, sig);
339+ sigprocmask(SIG_UNBLOCK, &mask, nullptr);
340+ raise(sig);
341+ }
342+}
343+ 
344+void CrashHandler(int sig, siginfo_t* info, void* context)
345+{
346+ bool expected = false;
347+ if (!g_inHandler.compare_exchange_strong(expected, true)) {
348+ ForwardToPrevious(sig, info, context);
349+ return;
350+ }
351+ 
352+ uintptr_t fp = 0;
353+ uintptr_t faultPc = 0;
354+#if defined(__arm64__)
355+ if (context != nullptr) {
356+ // Darwin arm64 ucontext. The thread-state member is `__ss` on current
357+ // SDKs (older ones used `ss`); toggle __ss <-> ss if this errors.
358+ auto* uc = static_cast<ucontext_t*>(context);
359+ fp = uc->uc_mcontext->__ss.__fp; // saved x29 of the interrupted frame
360+ faultPc = uc->uc_mcontext->__ss.__pc;
361+ }
362+#endif
363+ 
364+ DumpBuffer dump {};
365+ DumpSignalHeader(dump, sig, info);
366+ DumpBacktrace(dump, fp, faultPc);
367+ AppendLit(dump, "======================================================\n");
368+ FlushDump(dump);
369+ 
370+ g_inHandler.store(false);
371+ 
372+ ForwardToPrevious(sig, info, context);
373+}
374+ 
375+// ---------------------------------------------------------------------------
376+// Installation (runs in normal context)
377+// ---------------------------------------------------------------------------
378+void InstallAltStack()
379+{
380+ // sigaltstack is per-thread on Darwin. This protects ONLY the thread that
381+ // calls InitBacktraceHandler (the init/main thread). Other threads have no
382+ // alternate stack: a stack-overflow crash on them runs CrashHandler on the
383+ // already-exhausted stack, which faults again when allocating DumpBuffer
384+ // and the kernel terminates with no dump. Registering sigaltstack for every
385+ // framework-spawned thread requires a creation-time hook and is left as a
386+ // follow-up. (Ordinary SEGV/ABRT on any thread still works - only the
387+ // stack-overflow case is affected.)
388+ static char altStack[ALT_STACK_SIZE];
389+ stack_t sigStack {};
390+ sigStack.ss_sp = altStack;
391+ sigStack.ss_size = sizeof(altStack);
392+ sigStack.ss_flags = 0;
393+ if (sigaltstack(&sigStack, nullptr) != 0) {
394+ // Registration failed; the init thread also falls back to its normal
395+ // stack for stack-overflow crashes. Ordinary SEGV/ABRT are unaffected.
396+ }
397+}
C
CChaoZZ7月13日

sigaltstack 仅在 init 线程生效,其余线程栈溢出时无法安全转储

Darwin 上 sigaltstack 是 per-thread 的,InstallAltStack 只为调用 InitBacktraceHandler 的那一个线程注册了备用栈。配合 SA_SIGINFO|SA_ONSTACK,只有该线程的栈溢出类 SIGSEGV 能在备用栈上运行 CrashHandler;其它线程发生栈溢出时,处理函数仍在本线程已近耗尽的栈上运行,进入函数即因分配 DumpBuffer 等局部变量再次触发 SIGSEGV,而该信号在处理期间被自动屏蔽,内核直接执行默认动作终止进程,不产生任何转储。此外 sigaltstack 的返回值未检查,注册失败时备用栈同样不生效。影响:多线程 iOS 应用中绝大多数线程的栈溢出崩溃将完全无输出。建议:在框架派生线程的创建处统一调用 sigaltstack 为每个线程注册备用栈,并检查返回值;若确实不做,应在注释中明确仅 init 线程的栈溢出可被捕获。

严重程度:一般

likedislike
liwenzhen3
liwenzhen3
7月14日 评论:
398+ 
399+} // namespace
400+ 
401+void InitBacktraceHandler()
402+{
403+ bool expected = false;
404+ if (!g_installed.compare_exchange_strong(expected, true)) {
405+ return;
406+ }
407+ 
408+ LoadImages(); // pre-cache dyld image table (dyld is not async-signal-safe)
C
CChaoZZ7月13日

初始化后动态加载的镜像不会进入缓存,FindImage 无法归属这些帧

g_images 仅在 LoadImages 中快照一次 dyld 镜像表,此后通过 lazy load / dlopen 加载的系统库与动态库不会写入缓存。处理函数中 FindImage 对这些 PC 返回 nullptr,对应帧只输出裸 pc 而无法给出 +0x,无法用 atos 符号化。iOS 应用普遍存在大量懒加载/运行时加载的框架,这部分帧将长期缺失归属,显著削弱回栈可用性。建议:在 init 时额外调用 _dyld_register_func_for_add_image 注册回调,镜像加载时增量更新 g_images,并妥善处理与处理函数读侧的并发。

严重程度:一般

likedislike
liwenzhen3
liwenzhen3
7月14日 评论:
409+ 
410+ InstallAltStack();
411+ 
412+ struct sigaction sa {};
413+ sa.sa_sigaction = CrashHandler;
414+ sa.sa_flags = SA_SIGINFO | SA_ONSTACK;
415+ sigemptyset(&sa.sa_mask);
416+ 
417+ const int handledSignals[] = { SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE };
418+ for (int sig : handledSignals) {
419+ struct sigaction old {};
420+ if (sigaction(sig, &sa, &old) == 0) {
421+ g_savedHandlers[sig] = old;
422+ }
423+ }
424+ 
425+ LOGI("ArkUI-x backtrace handler installed.");
426+}
427+ 
428+void DumpBacktraceFromFp(uintptr_t fp)
429+{
430+ bool expected = false;
431+ if (!g_inHandler.compare_exchange_strong(expected, true)) {
432+ return;
433+ }
434+ DumpBuffer dump {};
435+ DumpBacktrace(dump, fp, 0);
436+ AppendLit(dump, "(end of manual backtrace)\n");
437+ FlushDump(dump);
438+ g_inHandler.store(false);
439+}
440+ 
441+} // namespace OHOS::Ace::Platform
@@ -0,0 +1,55 @@
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_IOS_OSAL_BACKTRACE_HANDLER_H
17+#define FOUNDATION_ACE_ADAPTER_IOS_OSAL_BACKTRACE_HANDLER_H
18+ 
19+#include <cstdint>
20+ 
21+namespace OHOS::Ace::Platform {
22+ 
23+/**
24+ * Installs async-signal-safe crash handlers (SIGSEGV/SIGABRT/SIGBUS/SIGILL/
25+ * SIGFPE) that dump a frame-pointer based native backtrace. Idempotent. Invoke
26+ * once from the iOS engine init (StageApplication +initApplication:).
27+ *
28+ * This is the iOS counterpart of the FP-chain crash handler. Key differences
29+ * vs the other-platform build:
30+ * - No /proc/self/maps on Darwin; the loaded-image table is pre-cached at init
31+ * via the dyld API (_dyld_image_count / _dyld_get_image_*). dyld is NOT
32+ * async-signal-safe, so caching happens in normal context, not in the handler.
33+ * - Each frame prints "<image>+0x<vaddr>" where vaddr = runtime_pc - the
34+ * image's ASLR slide (the static/unslid address). Feed it to atos:
35+ * atos -arch arm64 -o <image_binary_or_dsym> 0x<vaddr>
36+ * - Output goes to write(STDERR_FILENO) (fd 2 -> syslog/Console.app); os_log is
37+ * not async-signal-safe and is avoided inside the handler.
38+ *
39+ * The FP chain (x29) is walked only on arm64; on other ABIs only the signal is
40+ * reported. Frames that omit the frame pointer (JIT/JS, some libs) terminate
41+ * the chain. The pre-existing (system) handler is saved and chained to, so iOS
42+ * still produces its own crash log.
43+ */
44+void InitBacktraceHandler();
45+ 
46+/**
47+ * Dumps the FP chain from the given frame pointer, using the same
48+ * async-signal-safe path as the crash handler. Intended for diagnostics/tests.
49+ * @param fp frame pointer to start walking from. No-op when fp == 0.
50+ */
51+void DumpBacktraceFromFp(uintptr_t fp);
52+ 
53+} // namespace OHOS::Ace::Platform
54+ 
55+#endif // FOUNDATION_ACE_ADAPTER_IOS_OSAL_BACKTRACE_HANDLER_H
@@ -22,6 +22,7 @@
22#import "Logger.h"22#import "Logger.h"
23#include <string>23#include <string>
24#include "app_main.h"24#include "app_main.h"
25+#include "foundation/arkui/ace_engine/adapter/ios/osal/backtrace_handler.h"
25#include "foundation/arkui/ace_engine/adapter/ios/osal/high_contrast_observer.h"26#include "foundation/arkui/ace_engine/adapter/ios/osal/high_contrast_observer.h"
26#include "stage_application_info_adapter.h"27#include "stage_application_info_adapter.h"
27 28 
@@ -49,6 +50,11 @@ static bool g_isOnBackground = false;
49}50}
50 51 
51+ (void)initApplication:(BOOL)isLoadArkUI {52+ (void)initApplication:(BOOL)isLoadArkUI {
53+ // Install the async-signal-safe FP-chain crash handler early (caches the
54+ // dyld image table, then registers sigaction handlers). No-op if already
55+ // installed. Must run before any native code that might crash.
56+ OHOS::Ace::Platform::InitBacktraceHandler();
57+ 
52 [self setPidAndUid];58 [self setPidAndUid];
53 [self setLocale];59 [self setLocale];
54 [self setupNotificationCenterObservers];60 [self setupNotificationCenterObservers];