From 4a6d9c6caeb7dc8ca77a88c3ac45da49a98ca247 Mon Sep 17 00:00:00 2001 From: Rodger-Wang <1367893453@qq.com> Date: Wed, 30 Sep 2026 11:35:29 +0800 Subject: [PATCH] =?UTF-8?q?client(iOS)=EF=BC=9A=E5=8A=A0=E5=8E=9F=E7=94=9F?= =?UTF-8?q?=E5=B4=A9=E6=BA=83=E8=AE=B0=E5=BD=95=E5=86=99=E5=85=A5=20Docume?= =?UTF-8?q?nts/logs/native=5Fcrash.log=EF=BC=9BBES=20FileUtils=20=E6=94=B9?= =?UTF-8?q?=E5=90=8D=20BesFileUtils=20=E9=81=BF=E5=85=8D=E7=B1=BB=E5=90=8D?= =?UTF-8?q?=E5=86=B2=E7=AA=81=EF=BC=9B=E8=A1=A5=E8=BF=90=E5=8A=A8=E6=9D=83?= =?UTF-8?q?=E9=99=90=E8=AF=B4=E6=98=8E=EF=BC=9B=E6=9E=84=E5=BB=BA=E5=8F=B7?= =?UTF-8?q?=201.0.2+25?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Cursor --- .../ios/Runner.xcodeproj/project.pbxproj | 22 ++-- apps/client/ios/Runner/AppDelegate.swift | 119 ++++++++++++++++++ apps/client/ios/Runner/Info.plist | 2 + .../Classes/bessdk/connect/BesBTConnector.m | 2 +- .../service/TotaDemo/BesTotaDemoService.m | 2 +- .../ios/Classes/bessdk/utils/FileUtils.h | 2 +- .../ios/Classes/bessdk/utils/FileUtils.m | 4 +- apps/client/pubspec.yaml | 2 +- 8 files changed, 142 insertions(+), 13 deletions(-) diff --git a/apps/client/ios/Runner.xcodeproj/project.pbxproj b/apps/client/ios/Runner.xcodeproj/project.pbxproj index df80aa2b..59f18b63 100644 --- a/apps/client/ios/Runner.xcodeproj/project.pbxproj +++ b/apps/client/ios/Runner.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ @@ -338,7 +338,7 @@ ); mainGroup = 97C146E51CF9000F007C117D; packageReferences = ( - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */, + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */, ); productRefGroup = 97C146EF1CF9000F007C117D /* Products */; projectDirPath = ""; @@ -389,10 +389,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Copy Pods Resources"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; @@ -437,10 +441,14 @@ inputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); + inputPaths = ( + ); name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); + outputPaths = ( + ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; @@ -641,7 +649,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/RunnerProfile.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 24; DEVELOPMENT_TEAM = B8AJQ7ACU7; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -835,7 +843,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 24; DEVELOPMENT_TEAM = B8AJQ7ACU7; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -869,7 +877,7 @@ CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 20; + CURRENT_PROJECT_VERSION = 24; DEVELOPMENT_TEAM = B8AJQ7ACU7; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; @@ -988,7 +996,7 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */ = { + 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */ = { isa = XCLocalSwiftPackageReference; relativePath = Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; }; @@ -997,7 +1005,7 @@ /* Begin XCSwiftPackageProductDependency section */ 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */ = { isa = XCSwiftPackageProductDependency; - package = 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "FlutterGeneratedPluginSwiftPackage" */; + package = 781AD8BC2B33823900A9FFBB /* XCLocalSwiftPackageReference "Flutter/ephemeral/Packages/FlutterGeneratedPluginSwiftPackage" */; productName = FlutterGeneratedPluginSwiftPackage; }; /* End XCSwiftPackageProductDependency section */ diff --git a/apps/client/ios/Runner/AppDelegate.swift b/apps/client/ios/Runner/AppDelegate.swift index 2348427b..343c8573 100644 --- a/apps/client/ios/Runner/AppDelegate.swift +++ b/apps/client/ios/Runner/AppDelegate.swift @@ -1,4 +1,5 @@ import Flutter +import MachO import UIKit @main @@ -8,7 +9,11 @@ import UIKit didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { application.applicationIconBadgeNumber = 0 + NativeCrashRecorder.install() GeneratedPluginRegistrant.register(with: self) + // 第三方 SDK(高德/杰理等)初始化时可能装自己的信号处理器把我们顶掉, + // 过一会儿再装一次;装的时候会把当时在位的处理器记下来链式调用,两边都不丢。 + DispatchQueue.main.asyncAfter(deadline: .now() + 15) { NativeCrashRecorder.install() } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } @@ -33,3 +38,117 @@ import UIKit return false } } + + +// MARK: - 原生崩溃记录(2026-09-26) +// +// 起因:车载香薰对话里 App 两次 SIGSEGV 退出,但这台 iPhone 上**一份系统崩溃报告都没留下** +// (「分析数据」里最新的还是 9-21),devicectl 只能看到 "terminated due to signal 11", +// 没有调用栈就没法定位。怀疑是某个 SDK 装了信号处理器却没把信号交还系统。 +// +// 这里在崩溃那一刻把调用栈写进 Documents/logs/native_crash.log(和 App 日志同目录, +// devicectl 能直接拉),然后把信号交还给之前在位的处理器 / 系统默认行为,不改变崩溃本身。 +// 调用栈是「镜像 + 偏移」形式,拿同一次构建的 dSYM 用 atos 还原成函数名。 +// +// 信号处理函数里只用 write / backtrace_symbols_fd 这类 async-signal-safe 的调用, +// 不分配内存、不碰 Swift 运行时的锁。 + +private var crashFD: Int32 = -1 +/// 调用栈缓冲:安装时就分配好,信号处理里不再申请内存 +private let crashFrames = UnsafeMutablePointer.allocate(capacity: 128) +private let crashSignals: [Int32] = [SIGSEGV, SIGBUS, SIGABRT, SIGILL, SIGFPE, SIGTRAP] +/// 安装时在位的旧处理器(按信号号索引),崩溃时链式调用 +private let previousHandlers = UnsafeMutablePointer.allocate(capacity: 32) +private var previousExceptionHandler: (@convention(c) (NSException) -> Void)? + +private func crashWrite(_ s: StaticString) { + guard crashFD >= 0 else { return } + _ = s.withUTF8Buffer { write(crashFD, $0.baseAddress, $0.count) } +} + +private func crashWriteInt(_ value: Int) { + guard crashFD >= 0 else { return } + // 用栈上的元组当缓冲,不走堆分配 + var buf: (UInt64, UInt64, UInt64) = (0, 0, 0) + withUnsafeMutableBytes(of: &buf) { raw in + let p = raw.bindMemory(to: UInt8.self) + var v = value < 0 ? -value : value + var i = p.count + repeat { i -= 1; p[i] = UInt8(48 + v % 10); v /= 10 } while v > 0 && i > 0 + if value < 0 && i > 0 { i -= 1; p[i] = 45 } + _ = write(crashFD, p.baseAddress! + i, p.count - i) + } +} + +private let crashSignalHandler: @convention(c) (Int32) -> Void = { sig in + crashWrite("\n=== native crash signal=") + crashWriteInt(Int(sig)) + crashWrite(" time=") + crashWriteInt(Int(time(nil))) + crashWrite(" ===\n") + let n = backtrace(crashFrames, 128) + backtrace_symbols_fd(crashFrames, n, crashFD) + crashWrite("=== images ===\n") + // 各镜像的加载基址:atos 还原时要用(-l <基址>) + let count = _dyld_image_count() + for idx in 0.. Void = { e in + let text = "\n=== uncaught exception \(Date()) \(e.name.rawValue): \(e.reason ?? "")\n" + + e.callStackSymbols.joined(separator: "\n") + "\n" + if crashFD >= 0, let d = text.data(using: .utf8) { + d.withUnsafeBytes { _ = write(crashFD, $0.baseAddress, d.count) } + fsync(crashFD) + } + previousExceptionHandler?(e) + } +} diff --git a/apps/client/ios/Runner/Info.plist b/apps/client/ios/Runner/Info.plist index b9a5caa3..ea18f365 100644 --- a/apps/client/ios/Runner/Info.plist +++ b/apps/client/ios/Runner/Info.plist @@ -140,6 +140,8 @@ Location access enables the app to provide location-based voice translation services, such as translating local place names and providing contextual information relevant to your current location during conversations. NSMicrophoneUsageDescription Our app's voice AI interaction features (such as voice wake-up for AI, issuing commands, and real-time conversations) require microphone access. With this permission, you can interact with AI directly through speech, improving operational efficiency. We strictly protect your voice data—only used for real-time processing of interaction requests, and raw audio will not be stored. + NSMotionUsageDescription + This app uses motion and fitness data to detect when you're walking or moving, which helps optimize voice recognition accuracy and automatically adjust microphone sensitivity for better audio recording quality during different activities. NSPhotoLibraryAddUsageDescription Photo library permission is required to save exported images (mind maps, statistics) to your album NSPhotoLibraryUsageDescription diff --git a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/connect/BesBTConnector.m b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/connect/BesBTConnector.m index d41ac158..a3740444 100644 --- a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/connect/BesBTConnector.m +++ b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/connect/BesBTConnector.m @@ -334,7 +334,7 @@ typedef enum : NSUInteger { [defaults setObject:BES_SAVE_DEFAULT_VALUE forKey:BES_SAVE_LOG_KEY]; } if ([[defaults valueForKey:BES_SAVE_LOG_KEY] isEqual:@"YES"] && [defaults valueForKey:BES_SAVE_LOG_TITLE_KEY]) { - [FileUtils saveStringToDocumentWithName:[NSString stringWithFormat:@"%@.txt", [defaults valueForKey:BES_SAVE_LOG_TITLE_KEY]] text:text]; + [BesFileUtils saveStringToDocumentWithName:[NSString stringWithFormat:@"%@.txt", [defaults valueForKey:BES_SAVE_LOG_TITLE_KEY]] text:text]; } } diff --git a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/service/TotaDemo/BesTotaDemoService.m b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/service/TotaDemo/BesTotaDemoService.m index 0ddae3db..c3d103df 100644 --- a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/service/TotaDemo/BesTotaDemoService.m +++ b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/service/TotaDemo/BesTotaDemoService.m @@ -605,7 +605,7 @@ [defaults setObject:BES_SAVE_DEFAULT_VALUE forKey:BES_SAVE_LOG_KEY]; } if ([[defaults valueForKey:BES_SAVE_LOG_KEY] isEqual:@"YES"] && [defaults valueForKey:BES_SAVE_LOG_TITLE_KEY]) { - [FileUtils saveStringToDocumentWithName:[NSString stringWithFormat:@"%@.txt", [defaults valueForKey:BES_SAVE_LOG_TITLE_KEY]] text:text]; + [BesFileUtils saveStringToDocumentWithName:[NSString stringWithFormat:@"%@.txt", [defaults valueForKey:BES_SAVE_LOG_TITLE_KEY]] text:text]; } } diff --git a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.h b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.h index 208991d9..ca1ff6a9 100644 --- a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.h +++ b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.h @@ -9,7 +9,7 @@ NS_ASSUME_NONNULL_BEGIN -@interface FileUtils : NSObject +@interface BesFileUtils : NSObject + (NSString *)getDocumentPath; diff --git a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.m b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.m index ff17a252..18e7993d 100644 --- a/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.m +++ b/apps/client/local_plugins/bes_ota_manager/ios/Classes/bessdk/utils/FileUtils.m @@ -1,5 +1,5 @@ // -// FileUtils.m +// BesFileUtils.m // BesAll // // Created by 范羽 on 2021/2/25. @@ -7,7 +7,7 @@ #import "FileUtils.h" -@implementation FileUtils +@implementation BesFileUtils + (NSString *)getDocumentPath { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); diff --git a/apps/client/pubspec.yaml b/apps/client/pubspec.yaml index ae14290a..82388bd8 100644 --- a/apps/client/pubspec.yaml +++ b/apps/client/pubspec.yaml @@ -1,7 +1,7 @@ name: eaimar description: "Eaimar - AI Voice Assistant." publish_to: "none" -version: 1.0.2+23 +version: 1.0.2+25 environment: sdk: ">=3.3.0 <4.0.0"