| @@ -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); | ||
| 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 sigaltstack 仅在 init 线程生效,其余线程栈溢出时无法安全转储 Darwin 上 sigaltstack 是 per-thread 的,InstallAltStack 只为调用 InitBacktraceHandler 的那一个线程注册了备用栈。配合 SA_SIGINFO|SA_ONSTACK,只有该线程的栈溢出类 SIGSEGV 能在备用栈上运行 CrashHandler;其它线程发生栈溢出时,处理函数仍在本线程已近耗尽的栈上运行,进入函数即因分配 DumpBuffer 等局部变量再次触发 SIGSEGV,而该信号在处理期间被自动屏蔽,内核直接执行默认动作终止进程,不产生任何转储。此外 sigaltstack 的返回值未检查,注册失败时备用栈同样不生效。影响:多线程 iOS 应用中绝大多数线程的栈溢出崩溃将完全无输出。建议:在框架派生线程的创建处统一调用 sigaltstack 为每个线程注册备用栈,并检查返回值;若确实不做,应在注释中明确仅 init 线程的栈溢出可被捕获。 严重程度:一般 ![]() ![]() | |||
| 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 初始化后动态加载的镜像不会进入缓存,FindImage 无法归属这些帧 g_images 仅在 LoadImages 中快照一次 dyld 镜像表,此后通过 lazy load / dlopen 加载的系统库与动态库不会写入缓存。处理函数中 FindImage 对这些 PC 返回 nullptr,对应帧只输出裸 pc 而无法给出 严重程度:一般 ![]() ![]() | |||
| 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]; |


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