JetsamEvent 日志解读

JetsamEvent 是 iOS 唯一由系统官方生成、且专门记录"因内存原因终止进程"的日志。它不记录线程堆栈,却完整刻画了发生强杀那一刻整机的内存分布——这使它成为 OOM 归因、阈值建模、后台生命周期治理最权威的数据源。 本文聚焦 JetsamEvent 日志本身:存放位置、文件结构、字段含义、reason 类型、案例解读、自动化批量解析脚本,以及机型内存上限/iOS 版本差异等实战要点。OOM 监控治理的整体方法论见 崩溃-治理。 1. Jetsam 与 JetsamEvent 是什么 iOS 通过 XNU 内核的 memorystatus 子系统(俗称 Jetsam)做整机内存管理: 前台/后台进程按 Jetsam 优先级排序; 当系统整体可用物理页(vm_page_free_count)或单个进程 phys_footprint 触发阈值时,按优先级杀掉进程; 被杀时,内核在 /var/mobile/Library/Logs/CrashReporter/ 生成 JetsamEvent-yyyy-MM-dd-HHmmss.ips 文件,并通过 DiagnosticsReporting 暴露给 设置 → 隐私与安全性 → 分析与改进 → 分析数据。 需要理解两个关键结论: JetsamEvent 不是单个进程的崩溃日志,而是整机一次 Jetsam 事件的快照。 一个文件里往往包含几十个进程条目,只有其中一个(或少数)被真正杀掉;其余进程是作为"当时整机内存画像"被附带记录。 JetsamEvent 不含线程堆栈。 它的价值在于告诉你"谁占用了多少内存"和"为什么被杀",而不是"在哪一行代码被杀"。堆栈需要由 App 自己的 Memory Dump / MetricKit 补齐。 2. 获取方式 场景 路径 真机设置页查看 设置 → 隐私与安全性 → 分析与改进 → 分析数据,文件名 JetsamEvent-*.ips Xcode 连接真机 Window → Devices and Simulators → 选中设备 → View Device Logs sysdiagnose 包 解压后位于 crashes_and_spins/*.ips 越狱设备 /var/mobile/Library/Logs/CrashReporter/JetsamEvent-*.ips 自动上报 App 下次启动时扫描沙盒外的 Apple 日志目录不可行,只能依赖 MetricKit 的 MXCrashDiagnostic/MXAppExitMetric(iOS 14+) ⚠️ iOS 沙盒下 App 无法直接读取 /var/mobile/Library/Logs/ 的原始 ips 文件,线上规模化采集必须走 MetricKit。JetsamEvent 原始日志主要用于研发期抓真机、或用户反馈场景的手工分析。 ...

May 3, 2026

崩溃-Mach异常

本文深入介绍Mach异常的底层实现细节,包括端口操作、消息机制以及如何实现自定义的Mach异常处理器。 关于Mach异常的基础概念(Task、Thread、Port、Message)和异常端口的查找顺序,请参考 崩溃-原理。 Mach端口详解 端口权限 端口权限类型: ┌─────────────────────────────────────────────────────────────┐ │ 端口权限 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ MACH_PORT_RIGHT_SEND │ │ ├── 发送权限 │ │ ├── 可以向端口发送消息 │ │ └── 可以复制给其他任务 │ │ │ │ MACH_PORT_RIGHT_RECEIVE │ │ ├── 接收权限 │ │ ├── 可以从端口接收消息 │ │ ├── 只能有一个持有者 │ │ └── 持有者销毁时端口销毁 │ │ │ │ MACH_PORT_RIGHT_SEND_ONCE │ │ ├── 一次性发送权限 │ │ └── 发送一次后自动失效 │ │ │ │ MACH_PORT_RIGHT_PORT_SET │ │ ├── 端口集合权限 │ │ └── 可以同时监听多个端口 │ │ │ │ MACH_PORT_RIGHT_DEAD_NAME │ │ ├── 死名权限 │ │ └── 端口已销毁但名字仍存在 │ │ │ └─────────────────────────────────────────────────────────────┘ 端口操作 #include <mach/mach.h> // 分配新端口 mach_port_t port; kern_return_t kr = mach_port_allocate( mach_task_self(), // 当前任务 MACH_PORT_RIGHT_RECEIVE, // 接收权限 &port // 输出端口名 ); // 添加发送权限 kr = mach_port_insert_right( mach_task_self(), port, port, MACH_MSG_TYPE_MAKE_SEND // 创建发送权限 ); // 销毁端口 kr = mach_port_destroy(mach_task_self(), port); // 释放端口权限 kr = mach_port_deallocate(mach_task_self(), port); Mach消息 消息结构 // 消息头部 typedef struct { mach_msg_bits_t msgh_bits; // 消息类型和权限 mach_msg_size_t msgh_size; // 消息大小 mach_port_t msgh_remote_port; // 目标端口 mach_port_t msgh_local_port; // 本地端口(用于回复) mach_port_name_t msgh_voucher_port;// 凭证端口 mach_msg_id_t msgh_id; // 消息ID } mach_msg_header_t; // 完整消息示例 typedef struct { mach_msg_header_t header; // 可选的消息体 mach_msg_body_t body; // 可选的端口描述符 mach_msg_port_descriptor_t port; // 可选的数据 char data[256]; } MyMessage; 消息发送与接收 // 发送消息 kern_return_t send_message(mach_port_t port, const char *data, size_t len) { struct { mach_msg_header_t header; char data[256]; } message; message.header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0); message.header.msgh_size = sizeof(message); message.header.msgh_remote_port = port; message.header.msgh_local_port = MACH_PORT_NULL; message.header.msgh_id = 0; size_t copy_len = len < sizeof(message.data) ? len : sizeof(message.data); memcpy(message.data, data, copy_len); return mach_msg( &message.header, MACH_SEND_MSG, // 发送 sizeof(message), // 发送大小 0, // 接收大小 MACH_PORT_NULL, // 接收端口 MACH_MSG_TIMEOUT_NONE, // 超时 MACH_PORT_NULL // 通知端口 ); } // 接收消息 kern_return_t receive_message(mach_port_t port) { struct { mach_msg_header_t header; char data[256]; } message; kern_return_t kr = mach_msg( &message.header, MACH_RCV_MSG, // 接收 0, // 发送大小 sizeof(message), // 接收大小 port, // 接收端口 MACH_MSG_TIMEOUT_NONE, // 超时 MACH_PORT_NULL // 通知端口 ); if (kr == MACH_MSG_SUCCESS) { // 处理消息 printf("Received: %s\n", message.data); } return kr; } 异常端口操作 设置异常端口 #include <mach/mach.h> // 设置任务级异常端口 kern_return_t setup_task_exception_port(mach_port_t exception_port) { // 异常类型掩码 exception_mask_t mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT | EXC_MASK_SOFTWARE; return task_set_exception_ports( mach_task_self(), mask, exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE ); } // 获取当前异常端口 kern_return_t get_exception_ports(void) { exception_mask_t masks[EXC_TYPES_COUNT]; mach_msg_type_number_t count = EXC_TYPES_COUNT; mach_port_t ports[EXC_TYPES_COUNT]; exception_behavior_t behaviors[EXC_TYPES_COUNT]; thread_state_flavor_t flavors[EXC_TYPES_COUNT]; return task_get_exception_ports( mach_task_self(), EXC_MASK_ALL, masks, &count, ports, behaviors, flavors ); } 异常处理流程 完整的异常处理流程 flowchart TD A["1. 硬件检测到异常CPU执行非法操作,触发硬件中断"] --> B["2. 陷入内核CPU切换到内核态,执行异常处理程序"] B --> C["3. 内核构造异常消息创建包含异常信息的Mach消息"] C --> D["4. 发送到异常端口按照 Thread → Task → Host 顺序查找端口"] D --> E["5. 等待回复内核暂停线程,等待异常处理程序的回复"] E --> F{"6. 处理回复"} F -->|KERN_SUCCESS| G["恢复线程执行"] F -->|其他| H["转换为Unix信号或终止进程"] 异常消息格式 // 异常消息结构 typedef struct { mach_msg_header_t Head; // 消息体 mach_msg_body_t msgh_body; mach_msg_port_descriptor_t thread; // 发生异常的线程 mach_msg_port_descriptor_t task; // 发生异常的任务 // 异常信息 NDR_record_t NDR; exception_type_t exception; // 异常类型 mach_msg_type_number_t codeCnt; // 代码数量 int64_t code[2]; // 异常代码 } __Request__mach_exception_raise_t; // 异常回复消息 typedef struct { mach_msg_header_t Head; NDR_record_t NDR; kern_return_t RetCode; // 返回码 } __Reply__mach_exception_raise_t; 实现Mach异常处理器 完整示例 #include <mach/mach.h> #include <pthread.h> // 异常处理线程 static pthread_t exception_thread; static mach_port_t exception_port; // 保存原始异常端口 static mach_port_t original_exception_port; static exception_behavior_t original_behavior; static thread_state_flavor_t original_flavor; // 异常处理函数 static kern_return_t handle_exception( mach_port_t exception_port, mach_port_t thread, mach_port_t task, exception_type_t exception, mach_exception_data_t code, mach_msg_type_number_t code_count ) { // 获取线程状态(ARM64架构) // 注意:如需支持x86_64模拟器,需要使用x86_thread_state64_t和X86_THREAD_STATE64 #if defined(__arm64__) arm_thread_state64_t state; mach_msg_type_number_t state_count = ARM_THREAD_STATE64_COUNT; thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, &state_count); // 记录崩溃信息 printf("Exception: %d\n", exception); printf("Code: 0x%llx, 0x%llx\n", code[0], code[1]); printf("PC: 0x%llx\n", (unsigned long long)state.__pc); printf("LR: 0x%llx\n", (unsigned long long)state.__lr); #endif // 采集堆栈(需要自行实现) // collect_backtrace(thread); // 保存崩溃日志(需要自行实现) // save_crash_log(); // 返回失败,让系统继续处理(转换为信号) return KERN_FAILURE; } // 异常处理线程主函数 static void *exception_handler_thread(void *arg) { kern_return_t kr; while (1) { // 接收异常消息 struct { mach_msg_header_t head; mach_msg_body_t body; mach_msg_port_descriptor_t thread; mach_msg_port_descriptor_t task; NDR_record_t NDR; exception_type_t exception; mach_msg_type_number_t code_count; int64_t code[2]; int flavor; mach_msg_type_number_t state_count; natural_t state[224]; } request; struct { mach_msg_header_t head; NDR_record_t NDR; kern_return_t ret_code; } reply; // 接收消息 kr = mach_msg( &request.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(request), exception_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL ); if (kr != MACH_MSG_SUCCESS) { continue; } // 处理异常 kr = handle_exception( exception_port, request.thread.name, request.task.name, request.exception, request.code, request.code_count ); // 发送回复 reply.head.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0); reply.head.msgh_size = sizeof(reply); reply.head.msgh_remote_port = request.head.msgh_remote_port; reply.head.msgh_local_port = MACH_PORT_NULL; // Mach IPC惯例:回复消息ID = 请求消息ID + 100 reply.head.msgh_id = request.head.msgh_id + 100; reply.NDR = NDR_record; reply.ret_code = kr; mach_msg( &reply.head, MACH_SEND_MSG, sizeof(reply), 0, MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL ); } return NULL; } // 安装异常处理器 kern_return_t install_exception_handler(void) { kern_return_t kr; // 创建异常端口 kr = mach_port_allocate( mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port ); if (kr != KERN_SUCCESS) return kr; // 添加发送权限 kr = mach_port_insert_right( mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND ); if (kr != KERN_SUCCESS) return kr; // 保存原始异常端口 exception_mask_t mask = EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT; // 用于接收查询结果的数组 exception_mask_t masks_out[EXC_TYPES_COUNT]; mach_port_t ports_out[EXC_TYPES_COUNT]; exception_behavior_t behaviors_out[EXC_TYPES_COUNT]; thread_state_flavor_t flavors_out[EXC_TYPES_COUNT]; mach_msg_type_number_t count = EXC_TYPES_COUNT; kr = task_get_exception_ports( mach_task_self(), mask, // 输入:要查询的异常类型掩码 masks_out, // 输出:实际的掩码数组 &count, // 输入输出:数组大小 ports_out, // 输出:端口数组 behaviors_out, // 输出:行为数组 flavors_out // 输出:flavor数组 ); // 保存第一个有效的原始端口(简化处理) if (count > 0) { original_exception_port = ports_out[0]; original_behavior = behaviors_out[0]; original_flavor = flavors_out[0]; } // 设置新的异常端口 kr = task_set_exception_ports( mach_task_self(), mask, exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE ); if (kr != KERN_SUCCESS) return kr; // 创建异常处理线程 pthread_create(&exception_thread, NULL, exception_handler_thread, NULL); return KERN_SUCCESS; } 调试器检测 调试器(如LLDB)通过在Task(任务/进程)级别注册异常端口来优先拦截异常。了解这一机制有助于理解为什么调试时某些崩溃行为会有所不同。 ...

May 3, 2026

崩溃-信号处理

Unix信号是进程间通信和异常通知的重要机制。本文详细介绍Unix信号的基础知识、常见崩溃信号以及Signal Handler的正确实现方式。 Unix信号基础 什么是信号 信号的本质: ┌─────────────────────────────────────────────────────────────┐ │ Unix信号 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 信号是一种软件中断机制: │ │ • 用于通知进程发生了某个事件 │ │ • 可以由内核、其他进程或进程自身发送 │ │ • 进程可以选择处理、忽略或使用默认行为 │ │ │ │ 信号的来源: │ │ • 硬件异常(如除零、非法内存访问) │ │ • 用户输入(如Ctrl+C) │ │ • 系统调用(如kill) │ │ • 软件条件(如定时器到期) │ │ │ └─────────────────────────────────────────────────────────────┘ 信号的生命周期 flowchart TD A["1. 信号产生(Generation)某个事件触发信号的产生"] --> B["2. 信号投递(Delivery)信号被投递到目标进程此时信号处于pending状态"] --> C["3. 信号处理(Handling)进程响应信号,执行以下之一"] C --> D["执行默认动作"] C --> E["执行自定义处理函数"] C --> F["忽略信号"] 常见信号类型 崩溃相关信号 信号 编号(macOS/iOS) 默认动作 说明 SIGABRT 6 终止+core 调用abort()产生 SIGBUS 10 终止+core 总线错误 SIGFPE 8 终止+core 算术异常 SIGILL 4 终止+core 非法指令 SIGSEGV 11 终止+core 段错误 SIGTRAP 5 终止+core 断点陷阱 SIGSYS 12 终止+core 非法系统调用 其他重要信号 信号 编号 默认动作 说明 SIGKILL 9 终止 强制终止(不可捕获) SIGTERM 15 终止 请求终止 SIGINT 2 终止 中断(Ctrl+C) SIGQUIT 3 终止+core 退出(Ctrl+\) SIGPIPE 13 终止 管道破裂 SIGALRM 14 终止 定时器到期 信号详解 SIGABRT (6) ──────────────────────────────────────── 触发条件: • 调用abort()函数 • 未捕获的NSException • C++ exception未处理 • assert失败(某些情况) 特点: • 可以捕获和处理 • 通常表示程序主动终止 SIGSEGV (11) ──────────────────────────────────────── 触发条件: • 访问无效内存地址 • 空指针解引用 • 栈溢出 • 访问已释放的内存 对应Mach异常: • EXC_BAD_ACCESS (KERN_INVALID_ADDRESS) • EXC_BAD_ACCESS (KERN_PROTECTION_FAILURE) SIGBUS (10) ──────────────────────────────────────── 触发条件: • 内存对齐错误 • 访问不存在的物理地址 • mmap映射失效 与SIGSEGV的区别: • SIGSEGV: 虚拟地址层面的问题(地址未映射或权限不足) • SIGBUS: 物理地址层面的问题(对齐错误、硬件问题等) SIGILL (4) ──────────────────────────────────────── 触发条件: • 执行非法指令 • Swift强制解包nil • 代码段损坏 对应Mach异常: • EXC_BAD_INSTRUCTION SIGFPE (8) ──────────────────────────────────────── 触发条件: • 整数除零 • 浮点异常 • 算术溢出 对应Mach异常: • EXC_ARITHMETIC SIGTRAP (5) ──────────────────────────────────────── 触发条件: • 断点指令 • Swift的fatalError • precondition失败 • 调试器断点 对应Mach异常: • EXC_BREAKPOINT Signal Handler 基本用法 #include <signal.h> // 简单的信号处理函数 void simple_handler(int sig) { // 处理信号 // 注意:这里只能调用异步信号安全的函数 } // 注册信号处理函数 void setup_simple_handler(void) { signal(SIGSEGV, simple_handler); signal(SIGABRT, simple_handler); } 使用sigaction(推荐) #include <signal.h> // 带详细信息的信号处理函数 void detailed_handler(int sig, siginfo_t *info, void *context) { // sig: 信号编号 // info: 信号详细信息 // context: 上下文(包含寄存器状态) } // 使用sigaction注册 void setup_sigaction_handler(void) { struct sigaction action; // 清零结构体 memset(&action, 0, sizeof(action)); // 设置处理函数 action.sa_sigaction = detailed_handler; // 设置标志 action.sa_flags = SA_SIGINFO // 使用sa_sigaction而非sa_handler | SA_ONSTACK; // 使用备用栈 // 清空信号掩码 sigemptyset(&action.sa_mask); // 注册信号处理器 sigaction(SIGSEGV, &action, NULL); sigaction(SIGABRT, &action, NULL); sigaction(SIGBUS, &action, NULL); sigaction(SIGFPE, &action, NULL); sigaction(SIGILL, &action, NULL); sigaction(SIGTRAP, &action, NULL); } siginfo_t结构 typedef struct { int si_signo; // 信号编号 int si_errno; // 错误码 int si_code; // 信号代码(更详细的原因) pid_t si_pid; // 发送信号的进程ID uid_t si_uid; // 发送信号的用户ID void *si_addr; // 故障地址(SIGSEGV/SIGBUS) int si_status; // 退出状态 // ... 其他字段 } siginfo_t; // 解析siginfo void parse_siginfo(int sig, siginfo_t *info) { printf("Signal: %d (%s)\n", sig, strsignal(sig)); printf("Code: %d\n", info->si_code); if (sig == SIGSEGV || sig == SIGBUS) { printf("Fault address: %p\n", info->si_addr); } // 解析信号代码 switch (sig) { case SIGSEGV: switch (info->si_code) { case SEGV_MAPERR: printf("Address not mapped\n"); break; case SEGV_ACCERR: printf("Invalid permissions\n"); break; } break; case SIGBUS: switch (info->si_code) { case BUS_ADRALN: printf("Invalid address alignment\n"); break; case BUS_ADRERR: printf("Nonexistent physical address\n"); break; } break; // ... 其他信号 } } 备用信号栈 为什么需要备用栈 问题场景: 当栈溢出时: 1. 栈空间已耗尽 2. 系统发送SIGSEGV信号 3. 信号处理函数需要栈空间来执行 4. 但是栈已经溢出了! 5. 信号处理函数无法执行 解决方案: 使用备用信号栈(Alternate Signal Stack) 设置备用栈 #include <signal.h> #include <stdlib.h> // 设置备用信号栈 void setup_alternate_stack(void) { // 分配栈空间 void *stack = malloc(SIGSTKSZ); if (stack == NULL) { return; } // 配置备用栈 stack_t ss; ss.ss_sp = stack; ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; // 设置备用栈 if (sigaltstack(&ss, NULL) != 0) { free(stack); return; } // 注册信号处理器时使用SA_ONSTACK标志 struct sigaction action; action.sa_sigaction = signal_handler; action.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&action.sa_mask); sigaction(SIGSEGV, &action, NULL); } 异步信号安全 什么是异步信号安全 异步信号安全(Async-Signal-Safe): 信号可能在任何时刻打断正常执行: • 可能在malloc中间被打断 • 可能在printf中间被打断 • 可能持有某个锁时被打断 如果信号处理函数调用了相同的函数: • 可能导致死锁(重复获取锁) • 可能导致数据损坏(数据结构不一致) • 可能导致未定义行为 因此,信号处理函数只能调用"异步信号安全"的函数 异步信号安全函数列表 // 可以在信号处理函数中安全调用的函数(部分): // 文件操作 write() read() open() close() fsync() // 进程控制 _exit() _Exit() abort() raise() kill() getpid() // 信号相关 signal() sigaction() sigprocmask() sigemptyset() sigfillset() sigaddset() sigdelset() // 其他(注意:以下函数不在POSIX异步信号安全列表中,但大多数实现是安全的) strlen() // 大多数实现安全,但非POSIX标准 memcpy() // 大多数实现安全,但非POSIX标准 不安全的函数 // 不能在信号处理函数中调用的函数: // 内存分配 malloc() free() realloc() new / delete // 标准I/O printf() fprintf() fopen() fclose() // Objective-C objc_msgSend() NSLog() 任何OC方法调用 // C++ 大多数C++标准库函数 异常处理 // 其他 syslog() strtok() localtime() 完整的崩溃信号处理器 实现示例 #include <signal.h> #include <execinfo.h> #include <unistd.h> #include <fcntl.h> #include <string.h> // 需要捕获的信号 static const int kFatalSignals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, SIGSYS, }; static const int kFatalSignalsCount = sizeof(kFatalSignals) / sizeof(kFatalSignals[0]); // 原始信号处理器 static struct sigaction g_originalActions[32]; // 崩溃日志文件路径(预先设置) static char g_crashLogPath[1024]; // 防止重入的标志 static volatile sig_atomic_t g_handling = 0; // 异步安全的字符串写入 static void safe_write_string(int fd, const char *str) { if (str) { write(fd, str, strlen(str)); } } // 异步安全的整数转字符串 static void safe_write_int(int fd, long value) { char buffer[32]; char *ptr = buffer + sizeof(buffer) - 1; *ptr = '\0'; int negative = value < 0; if (negative) value = -value; do { *--ptr = '0' + (value % 10); value /= 10; } while (value > 0); if (negative) *--ptr = '-'; write(fd, ptr, buffer + sizeof(buffer) - 1 - ptr); } // 异步安全的十六进制写入 static void safe_write_hex(int fd, unsigned long value) { char buffer[32]; char *ptr = buffer + sizeof(buffer) - 1; *ptr = '\0'; static const char hex[] = "0123456789abcdef"; do { *--ptr = hex[value & 0xf]; value >>= 4; } while (value > 0); *--ptr = 'x'; *--ptr = '0'; write(fd, ptr, buffer + sizeof(buffer) - 1 - ptr); } // 信号处理函数 static void crash_signal_handler(int sig, siginfo_t *info, void *context) { // 防止重入 if (g_handling) { return; } g_handling = 1; // 打开崩溃日志文件 int fd = open(g_crashLogPath, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd < 0) { // 无法打开文件,尝试写入stderr fd = STDERR_FILENO; } // 写入基本信息 safe_write_string(fd, "=== Crash Report ===\n"); safe_write_string(fd, "Signal: "); safe_write_int(fd, sig); safe_write_string(fd, " ("); // 信号名称 switch (sig) { case SIGABRT: safe_write_string(fd, "SIGABRT"); break; case SIGBUS: safe_write_string(fd, "SIGBUS"); break; case SIGFPE: safe_write_string(fd, "SIGFPE"); break; case SIGILL: safe_write_string(fd, "SIGILL"); break; case SIGSEGV: safe_write_string(fd, "SIGSEGV"); break; case SIGTRAP: safe_write_string(fd, "SIGTRAP"); break; default: safe_write_string(fd, "UNKNOWN"); break; } safe_write_string(fd, ")\n"); // 信号代码 safe_write_string(fd, "Code: "); safe_write_int(fd, info->si_code); safe_write_string(fd, "\n"); // 故障地址 if (sig == SIGSEGV || sig == SIGBUS) { safe_write_string(fd, "Fault Address: "); safe_write_hex(fd, (unsigned long)info->si_addr); safe_write_string(fd, "\n"); } // 获取堆栈 // 注意:backtrace()严格来说也不是异步信号安全的函数 // 但为了获取崩溃堆栈,这是一个常见的权衡取舍 safe_write_string(fd, "\nBacktrace:\n"); void *callstack[128]; int frames = backtrace(callstack, 128); // 注意:backtrace_symbols使用malloc,不是异步安全的 // 这里我们只写入地址,符号化在后续处理 for (int i = 0; i < frames; i++) { safe_write_int(fd, i); safe_write_string(fd, ": "); safe_write_hex(fd, (unsigned long)callstack[i]); safe_write_string(fd, "\n"); } // 同步写入 if (fd != STDERR_FILENO) { fsync(fd); close(fd); } // 恢复原始处理器并重新发送信号 sigaction(sig, &g_originalActions[sig], NULL); raise(sig); } // 安装信号处理器 void install_crash_signal_handlers(const char *logPath) { // 保存日志路径 strncpy(g_crashLogPath, logPath, sizeof(g_crashLogPath) - 1); // 设置备用栈 static char alternate_stack[SIGSTKSZ]; stack_t ss; ss.ss_sp = alternate_stack; ss.ss_size = SIGSTKSZ; ss.ss_flags = 0; sigaltstack(&ss, NULL); // 配置信号处理器 struct sigaction action; memset(&action, 0, sizeof(action)); action.sa_sigaction = crash_signal_handler; action.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&action.sa_mask); // 注册所有致命信号 for (int i = 0; i < kFatalSignalsCount; i++) { int sig = kFatalSignals[i]; sigaction(sig, &action, &g_originalActions[sig]); } } // 卸载信号处理器 void uninstall_crash_signal_handlers(void) { for (int i = 0; i < kFatalSignalsCount; i++) { int sig = kFatalSignals[i]; sigaction(sig, &g_originalActions[sig], NULL); } } 获取上下文信息 从ucontext获取寄存器 #include <signal.h> #include <sys/ucontext.h> void signal_handler(int sig, siginfo_t *info, void *context) { ucontext_t *uc = (ucontext_t *)context; #if defined(__arm64__) || defined(__aarch64__) // ARM64架构 mcontext_t mc = uc->uc_mcontext; // 通用寄存器 for (int i = 0; i < 29; i++) { printf("x%d: 0x%llx\n", i, mc->__ss.__x[i]); } // 特殊寄存器 printf("fp: 0x%llx\n", mc->__ss.__fp); // 帧指针 printf("lr: 0x%llx\n", mc->__ss.__lr); // 链接寄存器 printf("sp: 0x%llx\n", mc->__ss.__sp); // 栈指针 printf("pc: 0x%llx\n", mc->__ss.__pc); // 程序计数器 printf("cpsr: 0x%x\n", mc->__ss.__cpsr); // 状态寄存器 #elif defined(__x86_64__) // x86_64架构 mcontext_t mc = uc->uc_mcontext; printf("rax: 0x%llx\n", mc->__ss.__rax); printf("rbx: 0x%llx\n", mc->__ss.__rbx); printf("rcx: 0x%llx\n", mc->__ss.__rcx); printf("rdx: 0x%llx\n", mc->__ss.__rdx); printf("rsi: 0x%llx\n", mc->__ss.__rsi); printf("rdi: 0x%llx\n", mc->__ss.__rdi); printf("rbp: 0x%llx\n", mc->__ss.__rbp); printf("rsp: 0x%llx\n", mc->__ss.__rsp); printf("rip: 0x%llx\n", mc->__ss.__rip); #endif } 从寄存器回溯堆栈 // 基于帧指针的堆栈回溯 void backtrace_from_context(ucontext_t *context) { mcontext_t mc = context->uc_mcontext; #if defined(__arm64__) uintptr_t pc = mc->__ss.__pc; uintptr_t fp = mc->__ss.__fp; printf("Backtrace:\n"); printf("0: 0x%lx (PC)\n", (unsigned long)pc); int frame = 1; while (fp != 0 && frame < 128) { // ARM64栈帧布局: // [fp+0]: 上一帧的fp // [fp+8]: 返回地址 uintptr_t *frame_ptr = (uintptr_t *)fp; // 安全检查 if ((uintptr_t)frame_ptr < 0x1000) break; uintptr_t return_addr = frame_ptr[1]; printf("%d: 0x%lx\n", frame, (unsigned long)return_addr); // 移动到上一帧 fp = frame_ptr[0]; frame++; } #endif } 信号掩码 阻塞信号 #include <signal.h> // 阻塞特定信号 void block_signals(void) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); // 阻塞信号 sigprocmask(SIG_BLOCK, &mask, NULL); // 执行关键代码... // 解除阻塞 sigprocmask(SIG_UNBLOCK, &mask, NULL); } // 在信号处理期间阻塞其他信号 void setup_handler_with_mask(void) { struct sigaction action; action.sa_sigaction = signal_handler; action.sa_flags = SA_SIGINFO; // 在处理SIGSEGV时阻塞其他信号 sigfillset(&action.sa_mask); sigaction(SIGSEGV, &action, NULL); } 检查待处理信号 // 检查是否有信号待处理 void check_pending_signals(void) { sigset_t pending; sigpending(&pending); if (sigismember(&pending, SIGINT)) { printf("SIGINT is pending\n"); } } 信号与多线程 线程与信号 多线程环境下的信号处理: 1. 信号处理器是进程级的 • 所有线程共享信号处理器设置 • 一个线程设置的处理器影响整个进程 2. 信号掩码是线程级的 • 每个线程有自己的信号掩码 • pthread_sigmask()用于设置线程的信号掩码 3. 信号投递 • 同步信号(如SIGSEGV)投递给触发它的线程 • 异步信号投递给任意一个不阻塞该信号的线程 线程信号掩码 #include <pthread.h> #include <signal.h> // 设置线程信号掩码 void setup_thread_signal_mask(void) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGPIPE); // 阻塞SIGPIPE pthread_sigmask(SIG_BLOCK, &mask, NULL); } // 专门的信号处理线程 void *signal_handler_thread(void *arg) { sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGINT); int sig; while (1) { // 同步等待信号 sigwait(&mask, &sig); printf("Received signal: %d\n", sig); if (sig == SIGTERM) { // 处理终止信号 break; } } return NULL; } // 主线程设置 void setup_signal_thread(void) { // 在主线程中阻塞这些信号 sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGINT); pthread_sigmask(SIG_BLOCK, &mask, NULL); // 创建信号处理线程 pthread_t thread; pthread_create(&thread, NULL, signal_handler_thread, NULL); } 常见问题与解决方案 信号处理器中的死锁 问题:信号处理器中调用了非异步安全函数导致死锁 场景: 1. 主线程调用malloc(),获取了内存分配锁 2. 此时收到SIGSEGV信号 3. 信号处理器中调用malloc() 4. 尝试获取同一把锁 -> 死锁 解决方案: • 只使用异步信号安全的函数 • 使用预分配的内存 • 将复杂处理延迟到信号处理器外部 信号处理器重入 // 问题:信号处理器被再次调用 // 解决:使用标志位防止重入 static volatile sig_atomic_t g_handling = 0; void signal_handler(int sig, siginfo_t *info, void *context) { // 检查是否正在处理 if (g_handling) { // 已经在处理中,直接返回 return; } // 设置标志 g_handling = 1; // 处理信号... // 注意:不要在这里清除标志 // 因为我们会重新发送信号 } 与其他SDK的冲突 // 问题:多个SDK都注册了信号处理器 // 解决方案:保存并调用原始处理器 static struct sigaction g_original_action; void my_signal_handler(int sig, siginfo_t *info, void *context) { // 我的处理逻辑 handle_crash(sig, info, context); // 调用原始处理器 if (g_original_action.sa_flags & SA_SIGINFO) { g_original_action.sa_sigaction(sig, info, context); } else if (g_original_action.sa_handler != SIG_DFL && g_original_action.sa_handler != SIG_IGN) { g_original_action.sa_handler(sig); } else { // 恢复默认行为 signal(sig, SIG_DFL); raise(sig); } } void install_handler(void) { struct sigaction action; action.sa_sigaction = my_signal_handler; action.sa_flags = SA_SIGINFO | SA_ONSTACK; sigemptyset(&action.sa_mask); // 保存原始处理器 sigaction(SIGSEGV, &action, &g_original_action); } 信号与Mach异常的关系 转换关系 flowchart LR subgraph Mach["Mach异常"] A1[EXC_BAD_ACCESS] A2[EXC_BAD_INSTRUCTION] A3[EXC_ARITHMETIC] A4[EXC_BREAKPOINT] A5[EXC_CRASH] A6[EXC_SOFTWARE] end subgraph Signal["Unix信号"] B1[SIGSEGV / SIGBUS] B2[SIGILL] B3[SIGFPE] B4[SIGTRAP] B5[SIGABRT] B6[取决于具体代码] end A1 --> B1 A2 --> B2 A3 --> B3 A4 --> B4 A5 --> B5 A6 --> B6 转换时机: ...

May 2, 2026

崩溃-原理

理解崩溃的底层原理是有效治理的基础。本文详细介绍iOS的异常处理机制、崩溃的传递链路以及各类崩溃的本质原因。 iOS异常处理架构 iOS的异常处理采用分层架构,从底层到上层依次为: flowchart BT subgraph ARCH["异常处理架构"] direction BT HW["硬件层 (Hardware)CPU异常、内存访问违规、非法指令"] MACH["Mach层 (Mach Exception)EXC_BAD_ACCESS、EXC_BAD_INSTRUCTION、EXC_CRASH"] BSD["BSD层 (Unix Signal)SIGABRT、SIGSEGV、SIGBUS、SIGFPE、SIGILL"] APP["应用层 (Application Layer)NSException、@try-@catch、Swift Error"] HW --> MACH MACH --> BSD BSD --> APP end 需要注意:这张图表示崩溃治理中常见的异常/终止机制层次,并不表示所有异常都一定从硬件层逐级向上传递。NSException 属于 Objective-C 运行时/框架层的语言异常机制,不是内核产生的 Mach 异常。只有当 NSException 未被捕获时,运行时最终通常会调用 abort() 主动终止进程,随后表现为 SIGABRT / EXC_CRASH。 崩溃的传递链路 当异常发生时,会按照特定的链路传递: flowchart TD A["1. 硬件触发异常CPU检测到非法操作(如访问无效内存地址)触发硬件中断,陷入内核态"] --> B["2. Mach层处理内核将硬件异常转换为Mach异常通过异常端口发送给用户态的异常处理程序如果没有处理,转换为Unix信号"] B --> C["3. BSD层处理Mach异常被转换为对应的Unix信号调用进程注册的Signal Handler如果没有处理或处理后继续,执行默认行为"] C --> D["4. 进程终止默认行为通常是终止进程生成崩溃日志"] Mach异常与Unix信号的对应关系 Mach异常 Unix信号 触发原因 EXC_BAD_ACCESS SIGSEGV/SIGBUS 访问无效内存 EXC_BAD_INSTRUCTION SIGILL 非法指令 EXC_ARITHMETIC SIGFPE 算术异常(如除零) EXC_BREAKPOINT SIGTRAP 断点/调试陷阱 EXC_CRASH SIGABRT 程序主动abort Mach异常机制 Mach内核简介 Mach是macOS/iOS的微内核,提供了最基础的系统服务: ...

May 10, 2026

崩溃-治理

本文详细介绍常见崩溃类型的修复方法、防崩溃保护机制以及线上监控体系。 崩溃治理是 APM 稳定性子系的核心模块。完整的监控建设、多类型稳定性问题(Watchdog/OOM/CPU/IO)的统一治理与 Zombie/Coredump/MemoryGraph 等高级归因能力,请参考 APM 系列:指标体系、业界方案。 崩溃治理流程 flowchart TB subgraph S1[1. 崩溃发现] A1[线上监控报警] A2[用户反馈] A3[测试发现] end subgraph S2[2. 崩溃分析] B1[符号化堆栈] B2[问题定位] B3[根因分析] end subgraph S3[3. 问题修复] C1[代码修复] C2[防护措施] C3[回归测试] end subgraph S4[4. 效果验证] D1[灰度发布] D2[监控崩溃率] D3[持续跟踪] end A1 --> S2 A2 --> S2 A3 --> S2 S2 --> S3 S3 --> S4 常见崩溃类型及修复 1. 数组越界 // 崩溃示例 NSArray *array = @[@1, @2, @3]; id obj = array[10]; // NSRangeException // 修复方案1:边界检查 - (id)safeObjectAtIndex:(NSUInteger)index fromArray:(NSArray *)array { if (index < array.count) { return array[index]; } return nil; } // 修复方案2:分类安全方法 @implementation NSArray (Safe) - (id)safeObjectAtIndex:(NSUInteger)index { if (index < self.count) { return [self objectAtIndex:index]; } return nil; } @end // 修复方案3:Swift可选绑定 extension Array { subscript(safe index: Index) -> Element? { return indices.contains(index) ? self[index] : nil } } // 使用 let value = array[safe: 10] // 返回nil而不是崩溃 2. 字典插入nil // 崩溃示例 NSMutableDictionary *dict = [NSMutableDictionary dictionary]; NSString *value = nil; [dict setObject:value forKey:@"key"]; // NSInvalidArgumentException // 修复方案1:nil检查 - (void)setObject:(id)object forKey:(id)key inDict:(NSMutableDictionary *)dict { if (object && key) { dict[key] = object; } } // 修复方案2:分类安全方法 @implementation NSMutableDictionary (Safe) - (void)safeSetObject:(id)object forKey:(id)key { if (object && key) { [self setObject:object forKey:key]; } } @end // 修复方案3:使用NSNull dict[@"key"] = value ?: [NSNull null]; 3. Unrecognized Selector // 崩溃示例 NSString *str = @"hello"; [str performSelector:@selector(count)]; // unrecognized selector // 修复方案1:respondsToSelector检查 if ([obj respondsToSelector:@selector(someMethod)]) { [obj performSelector:@selector(someMethod)]; } // 修复方案2:消息转发防护 @implementation NSObject (CrashProtection) + (void)load { // Hook forwardingTargetForSelector: Method original = class_getInstanceMethod(self, @selector(forwardingTargetForSelector:)); Method swizzled = class_getInstanceMethod(self, @selector(safe_forwardingTargetForSelector:)); method_exchangeImplementations(original, swizzled); } - (id)safe_forwardingTargetForSelector:(SEL)aSelector { // 如果找不到方法,返回一个空实现的对象 id target = [self safe_forwardingTargetForSelector:aSelector]; if (target) return target; // 记录错误 NSLog(@"Unrecognized selector: %@ on %@", NSStringFromSelector(aSelector), self); // 返回一个能处理任何消息的对象 return [CrashStub shared]; } @end 4. KVO崩溃 // 崩溃场景1:重复移除观察者 [obj removeObserver:self forKeyPath:@"value"]; [obj removeObserver:self forKeyPath:@"value"]; // 崩溃 // 崩溃场景2:未移除观察者就释放 - (void)dealloc { // 忘记移除观察者 } // 修复方案:安全的KVO封装 @interface SafeKVOProxy : NSObject @property (nonatomic, weak) id observer; @property (nonatomic, weak) id target; @property (nonatomic, copy) NSString *keyPath; @property (nonatomic, copy) void (^callback)(id newValue); @end @implementation SafeKVOProxy - (instancetype)initWithTarget:(id)target keyPath:(NSString *)keyPath observer:(id)observer callback:(void (^)(id))callback { self = [super init]; if (self) { _target = target; _keyPath = keyPath; _observer = observer; _callback = callback; [target addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:NULL]; } return self; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (self.callback) { self.callback(change[NSKeyValueChangeNewKey]); } } - (void)dealloc { [_target removeObserver:self forKeyPath:_keyPath]; } @end 5. 多线程崩溃 // 崩溃场景:非线程安全的集合操作 // 线程1 [mutableArray addObject:obj1]; // 线程2 [mutableArray removeObjectAtIndex:0]; // 可能崩溃 // 修复方案1:加锁 @property (nonatomic, strong) NSLock *arrayLock; - (void)safeAddObject:(id)object { [self.arrayLock lock]; [self.mutableArray addObject:object]; [self.arrayLock unlock]; } // 修复方案2:使用GCD串行队列 @property (nonatomic, strong) dispatch_queue_t arrayQueue; - (void)safeAddObject:(id)object { dispatch_sync(self.arrayQueue, ^{ [self.mutableArray addObject:object]; }); } // 修复方案3:使用Actor(Swift Concurrency) actor SafeStorage { private var items: [Any] = [] func append(_ item: Any) { items.append(item) } func remove(at index: Int) { guard index < items.count else { return } items.remove(at: index) } func getAll() -> [Any] { return items } } let storage = SafeStorage() await storage.append(obj1) 6. 野指针崩溃 // 崩溃场景 __unsafe_unretained id unsafeRef = obj; obj = nil; [unsafeRef description]; // EXC_BAD_ACCESS // 修复方案1:使用weak而非unsafe_unretained __weak id weakRef = obj; obj = nil; [weakRef description]; // 安全,weakRef为nil // 修复方案2:Zombie Objects检测(调试用) // 在Scheme中启用Zombie Objects // 修复方案3:野指针检测工具 // 使用Address Sanitizer // Product -> Scheme -> Edit Scheme -> Diagnostics -> Address Sanitizer 7. Swift强制解包崩溃 // 崩溃示例 let value: String? = nil print(value!) // Fatal error: Unexpectedly found nil // 修复方案1:可选绑定 if let value = optionalValue { print(value) } // 修复方案2:空合运算符 let value = optionalValue ?? "default" // 修复方案3:guard语句 guard let value = optionalValue else { return } print(value) // 修复方案4:可选链 let length = optionalString?.count // 返回Int? 8. OOM(Out Of Memory)崩溃 OOM 是 iOS 最难治理的崩溃类型之一:系统因进程占用内存超过限制而将其强杀,不会产生标准崩溃日志,App 也没有机会在崩溃现场同步写下详细堆栈。因此 OOM 治理必须在事故前就把现场数据预埋下来,分为三个时段: ...

May 2, 2026

崩溃-采集

准确采集崩溃信息是分析和修复问题的前提。本文详细介绍iOS崩溃的捕获方案、堆栈回溯技术以及符号化原理。 崩溃采集架构 flowchart TB subgraph capture["崩溃捕获层"] direction LR mach["Mach Exception Handler"] signal["Unix Signal Handler"] exception["NSException Handler"] end subgraph collect["信息采集层"] direction LR backtrace["堆栈回溯(Backtrace)"] register["寄存器状态"] thread["线程信息"] device["设备/应用信息"] end subgraph storage["本地存储层"] direction LR async_write["异步安全写入"] file_manage["崩溃文件管理"] end subgraph report["上报处理层"] direction LR symbolicate["符号化解析"] aggregate["崩溃聚合"] analyze["数据分析"] end capture --> collect collect --> storage storage --> report Mach异常捕获 Mach是macOS/iOS的内核,提供了底层的异常处理机制。Mach异常是最底层的异常类型,发生在内核态,比Unix信号更早被触发。通过注册Mach异常处理器,可以在信号处理之前捕获到崩溃信息。 核心概念: Mach Port(端口):Mach内核中进程间通信(IPC)的基本单元,类似于文件描述符 Exception Port(异常端口):专门用于接收异常消息的端口 Task:Mach中的任务概念,对应一个进程,包含地址空间和资源 异常端口注册 #include <mach/mach.h> // 异常处理线程函数 static void *exception_handler_thread(void *arg) { mach_port_t exception_port = (mach_port_t)(uintptr_t)arg; while (1) { // 接收异常消息 struct { mach_msg_header_t head; mach_msg_body_t body; // ... 其他字段 } request; mach_msg_return_t result = mach_msg( &request.head, MACH_RCV_MSG | MACH_RCV_LARGE, 0, sizeof(request), exception_port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL ); if (result == MACH_MSG_SUCCESS) { // 处理异常 handle_exception(&request); } } return NULL; } // 注册异常端口 kern_return_t register_exception_handler(void) { kern_return_t kr; mach_port_t exception_port; // 创建异常端口 kr = mach_port_allocate( mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &exception_port ); if (kr != KERN_SUCCESS) return kr; // 添加发送权限 kr = mach_port_insert_right( mach_task_self(), exception_port, exception_port, MACH_MSG_TYPE_MAKE_SEND ); if (kr != KERN_SUCCESS) return kr; // 设置任务异常端口 kr = task_set_exception_ports( mach_task_self(), EXC_MASK_BAD_ACCESS | EXC_MASK_BAD_INSTRUCTION | EXC_MASK_ARITHMETIC | EXC_MASK_BREAKPOINT, exception_port, EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES, THREAD_STATE_NONE ); if (kr != KERN_SUCCESS) return kr; // 创建异常处理线程 pthread_t thread; pthread_create(&thread, NULL, exception_handler_thread, (void *)(uintptr_t)exception_port); return KERN_SUCCESS; } Mach异常处理流程 flowchart TD A["1. 异常发生"] --> B["2. 内核发送异常消息到异常端口"] B --> C["3. 异常处理线程接收消息"] C --> D["4. 解析异常信息"] D --> D1["exception type"] D --> D2["exception code"] D --> D3["thread state"] D1 & D2 & D3 --> E["5. 采集崩溃信息"] E --> E1["堆栈回溯"] E --> E2["寄存器状态"] E --> E3["其他上下文"] E1 & E2 & E3 --> F["6. 保存崩溃日志"] F --> G["7. 决定后续处理"] G --> G1["转发给原异常处理器"] G --> G2["让进程终止"] Unix信号捕获 Unix信号(Signal)是操作系统向进程发送的异步通知机制。当发生特定事件(如非法内存访问、算术错误等)时,内核会向进程发送相应的信号。iOS中,Mach异常通常会被转换为对应的Unix信号。 ...

June 21, 2026

崩溃

崩溃是影响App稳定性的最严重问题。当应用发生崩溃时,用户会被强制退出应用,严重影响用户体验和产品口碑。 本系列文章系统性地介绍iOS崩溃的原理、采集方案以及治理策略。 什么是崩溃 崩溃(Crash)是指应用程序因为异常或错误而被强制终止的现象。从技术角度看,崩溃可以分为以下几类: 崩溃类型 触发原因 典型场景 Mach异常 底层硬件/内核异常 访问无效内存、除零错误 Unix信号 系统信号导致进程终止 SIGABRT、SIGSEGV、SIGBUS NSException OC层未捕获的异常 数组越界、unrecognized selector 内存问题 OOM或内存损坏 内存不足、野指针 Watchdog 系统监控超时 启动超时、后台任务超时 崩溃率指标 崩溃率是衡量App稳定性的核心指标: 崩溃率计算方式: 1. 崩溃用户率(推荐) 崩溃用户率 = 发生崩溃的用户数 / 总活跃用户数 × 100% 2. 崩溃次数率 崩溃次数率 = 崩溃次数 / 启动次数 × 100% 3. 崩溃Session率 崩溃Session率 = 崩溃Session数 / 总Session数 × 100% 崩溃处理的整体架构 ┌─────────────────────────────────────────────────────────────┐ │ 崩溃处理架构 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 崩溃发生 │ │ │ │ Mach异常 / Unix信号 / NSException / OOM │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 崩溃捕获 │ │ │ │ • Mach异常处理 │ │ │ │ • Signal Handler │ │ │ │ • NSSetUncaughtExceptionHandler │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 信息采集 │ │ │ │ • 堆栈信息 │ │ │ │ • 设备信息 │ │ │ │ • 用户上下文 │ │ │ │ • 崩溃现场 │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 本地存储 │ │ │ │ • 写入文件(需要异步安全) │ │ │ │ • 避免使用OC/Swift API │ │ │ └─────────────────────────────────────────────────────┘ │ │ ↓ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 上报分析 │ │ │ │ • 下次启动时上报 │ │ │ │ • 符号化解析 │ │ │ │ • 聚合分析 │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────┘ 文章导航 本系列包含以下文章,建议按顺序阅读: ...

May 4, 2026

崩溃日志解读

iOS 崩溃日志(.ips / 老版 .crash)是排查线上问题的一手证据。它不仅记录了"谁崩了",更隐藏着异常类型、终止命名空间、寄存器现场、线程堆栈、二进制映射、资源限额等多维度事实。看懂每一个字段,才能把"不能复现"的崩溃拆成"寄存器 x0 是 nil 的后果"这种可复现假设。 本文聚焦系统生成的崩溃日志本身:格式演变、bug_type 全景、Header/Body 逐字段、Exception Type / Termination Namespace 深度解读、寄存器视角、符号化工具链、实战案例库、自动化脚本。OOM 专用的 JetsamEvent 日志见 JetsamEvent 日志解读;崩溃采集与治理方法论见 崩溃-采集、崩溃-治理。 1. .ips / .crash 格式演变 iOS 的崩溃日志格式几经迭代: 时代 文件扩展名 格式 说明 iOS 13 及以前 .crash / .ips 类 plist 纯文本 Key-Value 混排,人眼可读但难解析 iOS 14+ .ips 双段 JSON(Header JSON + Body JSON,以换行分隔) 机器可解析,字段更标准化 Xcode Organizer 导出 .crash 纯文本渲染视图 由 .ips 通过 CrashReporter.framework 渲染而来 解析规则和 JetsamEvent 完全一致——第一行是 Header JSON,剩余部分是 Body JSON,不能整体 parse。iOS 14+ .ips 内容可用 log show --archive 或 Xcode Organizer 转为旧式可读文本,但字段原始数据始终在 JSON 里。 ...

May 2, 2026