From 63c79e99e1573b2b42aaa036ef56f68500b98ee8 Mon Sep 17 00:00:00 2001 From: zqqf16 Date: Fri, 18 Mar 2022 19:31:23 +0800 Subject: [PATCH] Support iOS 15 json format ips file --- SYM.xcodeproj/project.pbxproj | 96 ++++- .../contents.xcworkspacedata | 2 +- .../xcshareddata/swiftpm/Package.resolved | 14 + SYM/Convertor.swift | 253 +++++++++++ SYM/Crash.swift | 242 +---------- SYM/CrashDocument.swift | 11 +- SYM/Dsym/DsymDownloader.swift | 10 +- SYM/Dsym/DsymManager.swift | 6 +- SYM/Extensions.swift | 11 + SYM/Parser.swift | 397 ++++++++++++++++++ SYM/Regex.swift | 69 +++ SYM/{Symbolicate.swift => Symbolicator.swift} | 50 ++- SYM/UI/ContentViewController.swift | 12 +- SYMTests/AppleDemo.ips | 2 +- SYMTests/AppleJson.ips | 245 +++++++++++ SYMTests/ConvertorTests.swift | 52 +++ SYMTests/ParserTests.swift | 34 +- 17 files changed, 1236 insertions(+), 270 deletions(-) create mode 100644 SYM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved create mode 100644 SYM/Convertor.swift create mode 100644 SYM/Parser.swift rename SYM/{Symbolicate.swift => Symbolicator.swift} (67%) create mode 100644 SYMTests/AppleJson.ips create mode 100644 SYMTests/ConvertorTests.swift diff --git a/SYM.xcodeproj/project.pbxproj b/SYM.xcodeproj/project.pbxproj index 6f5039e..687009e 100644 --- a/SYM.xcodeproj/project.pbxproj +++ b/SYM.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 52; objects = { /* Begin PBXAggregateTarget section */ @@ -58,6 +58,7 @@ B56B9B8D23A3869100049491 /* DsymViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56B9B8C23A3869100049491 /* DsymViewController.swift */; }; B56BAA5023A8E8EB00049491 /* DsymManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56BAA4F23A8E8EB00049491 /* DsymManager.swift */; }; B56BAA5623A8ECDB00049491 /* MdfindWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56BAA5523A8ECDB00049491 /* MdfindWrapper.swift */; }; + B56FBA1C27D72E200065E78F /* Parser.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56FBA1B27D72E200065E78F /* Parser.swift */; }; B5847A8D2569419600B674CA /* DeviceContentViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5847A8C2569419600B674CA /* DeviceContentViewController.swift */; }; B597FE1522DC8A9400BE439A /* FileBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B597FE1422DC8A9400BE439A /* FileBrowserViewController.swift */; }; B59B326B22E0112400013B67 /* MDDeviceMonitor.m in Sources */ = {isa = PBXBuildFile; fileRef = F4EFE90A2125583700E9EDA9 /* MDDeviceMonitor.m */; }; @@ -70,6 +71,8 @@ B5AF3E0327A243AC0037AAAD /* template.sh in Resources */ = {isa = PBXBuildFile; fileRef = B5AF3E0227A243AC0037AAAD /* template.sh */; }; B5B97AEF22DC737900F5DDF2 /* MDInstProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = B5B97AEE22DC737900F5DDF2 /* MDInstProxy.m */; }; B5B97AF022DC737900F5DDF2 /* MDInstProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = B5B97AEE22DC737900F5DDF2 /* MDInstProxy.m */; }; + B5C4EA5827D77C6500D0F8A5 /* Convertor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C4EA5727D77C6500D0F8A5 /* Convertor.swift */; }; + B5D0512327D77335009E95B0 /* Symbolicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D0512227D77335009E95B0 /* Symbolicator.swift */; }; B5EC56D227759AE5007EB567 /* ConfigTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EC56D127759AE5007EB567 /* ConfigTests.swift */; }; B5F2732525691658001BDE21 /* DeviceSidebarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F2732425691658001BDE21 /* DeviceSidebarViewController.swift */; }; B5F3577E22C0878800111FF3 /* PreferencesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F3577D22C0878800111FF3 /* PreferencesViewController.swift */; }; @@ -80,6 +83,10 @@ B5F3579D22C5CB1000111FF3 /* Preferences.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B5F3579F22C5CB1000111FF3 /* Preferences.storyboard */; }; B5F357B522C5E90C00111FF3 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = B5F357B322C5E90C00111FF3 /* Localizable.strings */; }; B5F357BD22C6062400111FF3 /* LineNumberRulerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F357BC22C6062400111FF3 /* LineNumberRulerView.swift */; }; + B5F935F327E46E9800C2D0C6 /* SwiftyJSON in Frameworks */ = {isa = PBXBuildFile; productRef = B5F935F227E46E9800C2D0C6 /* SwiftyJSON */; }; + B5F935F627E4827C00C2D0C6 /* AppleJson.ips in Resources */ = {isa = PBXBuildFile; fileRef = B5F935F427E4827C00C2D0C6 /* AppleJson.ips */; }; + B5F935F727E4827C00C2D0C6 /* AppleConverted.log in Resources */ = {isa = PBXBuildFile; fileRef = B5F935F527E4827C00C2D0C6 /* AppleConverted.log */; }; + B5F935F927E482A000C2D0C6 /* ConvertorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F935F827E482A000C2D0C6 /* ConvertorTests.swift */; }; F400154821539E8500267818 /* Automator.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F400154721539E8500267818 /* Automator.framework */; }; F405436A1D321E37009B2923 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40543691D321E37009B2923 /* Window.swift */; }; F40A369C1D3B5DCA00F3B00C /* AboutViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F40A369A1D3B5DCA00F3B00C /* AboutViewController.swift */; }; @@ -99,7 +106,6 @@ F49E2D47214BA704001A6A88 /* Config.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49E2D46214BA704001A6A88 /* Config.swift */; }; F49E2D4A214BB2D6001A6A88 /* DsymDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F49E2D49214BB2D6001A6A88 /* DsymDownloader.swift */; }; F4A679741E21319D009F93E7 /* BundleDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A679731E21319D009F93E7 /* BundleDocument.swift */; }; - F4A8DC101D525DAC00ACC4E5 /* Symbolicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4A8DC0F1D525DAC00ACC4E5 /* Symbolicate.swift */; }; F4CD14631D39D323006BAB2B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F4CD14621D39D323006BAB2B /* Assets.xcassets */; }; F4CD14651D39D5B7006BAB2B /* file.icns in Resources */ = {isa = PBXBuildFile; fileRef = F4CD14641D39D5B7006BAB2B /* file.icns */; }; F4EFE9022125305300E9EDA9 /* libimobiledevice.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F4EFE8EE2125305200E9EDA9 /* libimobiledevice.a */; }; @@ -170,11 +176,14 @@ B56B9B8C23A3869100049491 /* DsymViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DsymViewController.swift; sourceTree = ""; }; B56BAA4F23A8E8EB00049491 /* DsymManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = DsymManager.swift; path = SYM/dSYM/DsymManager.swift; sourceTree = SOURCE_ROOT; }; B56BAA5523A8ECDB00049491 /* MdfindWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MdfindWrapper.swift; sourceTree = ""; }; + B56FBA1B27D72E200065E78F /* Parser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Parser.swift; sourceTree = ""; }; B5847A8C2569419600B674CA /* DeviceContentViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceContentViewController.swift; sourceTree = ""; }; B597FE1422DC8A9400BE439A /* FileBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileBrowserViewController.swift; sourceTree = ""; }; B5AF3E0227A243AC0037AAAD /* template.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; name = template.sh; path = SYM/dSYM/template.sh; sourceTree = SOURCE_ROOT; }; B5B97AED22DC737900F5DDF2 /* MDInstProxy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MDInstProxy.h; sourceTree = ""; }; B5B97AEE22DC737900F5DDF2 /* MDInstProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MDInstProxy.m; sourceTree = ""; }; + B5C4EA5727D77C6500D0F8A5 /* Convertor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Convertor.swift; sourceTree = ""; }; + B5D0512227D77335009E95B0 /* Symbolicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Symbolicator.swift; sourceTree = ""; }; B5EC56D127759AE5007EB567 /* ConfigTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConfigTests.swift; sourceTree = ""; }; B5F2732425691658001BDE21 /* DeviceSidebarViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceSidebarViewController.swift; sourceTree = ""; }; B5F3577D22C0878800111FF3 /* PreferencesViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesViewController.swift; sourceTree = ""; }; @@ -190,6 +199,9 @@ B5F357B422C5E90C00111FF3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = Base; path = Base.lproj/Localizable.strings; sourceTree = ""; }; B5F357B622C5E92000111FF3 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; B5F357BC22C6062400111FF3 /* LineNumberRulerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineNumberRulerView.swift; sourceTree = ""; }; + B5F935F427E4827C00C2D0C6 /* AppleJson.ips */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AppleJson.ips; sourceTree = ""; }; + B5F935F527E4827C00C2D0C6 /* AppleConverted.log */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = AppleConverted.log; sourceTree = ""; }; + B5F935F827E482A000C2D0C6 /* ConvertorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConvertorTests.swift; sourceTree = ""; }; F400154721539E8500267818 /* Automator.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Automator.framework; path = System/Library/Frameworks/Automator.framework; sourceTree = SDKROOT; }; F40543691D321E37009B2923 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; F40A369A1D3B5DCA00F3B00C /* AboutViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutViewController.swift; sourceTree = ""; }; @@ -210,7 +222,6 @@ F49E2D46214BA704001A6A88 /* Config.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Config.swift; sourceTree = ""; }; F49E2D49214BB2D6001A6A88 /* DsymDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DsymDownloader.swift; sourceTree = ""; }; F4A679731E21319D009F93E7 /* BundleDocument.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BundleDocument.swift; sourceTree = ""; }; - F4A8DC0F1D525DAC00ACC4E5 /* Symbolicate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Symbolicate.swift; sourceTree = ""; }; F4CD14621D39D323006BAB2B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; F4CD14641D39D5B7006BAB2B /* file.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = file.icns; sourceTree = ""; }; F4EFE81421242D9300E9EDA9 /* SYM-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SYM-Bridging-Header.h"; sourceTree = ""; }; @@ -290,6 +301,7 @@ buildActionMask = 2147483647; files = ( F400154821539E8500267818 /* Automator.framework in Frameworks */, + B5F935F327E46E9800C2D0C6 /* SwiftyJSON in Frameworks */, F41CD76321291AA300F7CEFB /* libssl.a in Frameworks */, F41CD76221291AA300F7CEFB /* libcrypto.a in Frameworks */, F4EFE90D212580CB00E9EDA9 /* libplist.a in Frameworks */, @@ -332,9 +344,12 @@ 51BF9C501D6C7A1C00379BFC /* SYMTests */ = { isa = PBXGroup; children = ( + B5F935F527E4827C00C2D0C6 /* AppleConverted.log */, + B5F935F427E4827C00C2D0C6 /* AppleJson.ips */, 51BF9C591D6C7ACC00379BFC /* UmengDemo.crash */, 510777781EFBA4C30055E8BD /* AppleDemo.ips */, 51C9A49F1EEFE98700F21AE4 /* ParserTests.swift */, + B5F935F827E482A000C2D0C6 /* ConvertorTests.swift */, B5EC56D127759AE5007EB567 /* ConfigTests.swift */, 51BF9C531D6C7A1C00379BFC /* Info.plist */, ); @@ -509,8 +524,10 @@ isa = PBXGroup; children = ( F45320FA1D30A33C00639917 /* Crash.swift */, + B56FBA1B27D72E200065E78F /* Parser.swift */, + B5D0512227D77335009E95B0 /* Symbolicator.swift */, + B5C4EA5727D77C6500D0F8A5 /* Convertor.swift */, 51C9A4A11EEFF78500F21AE4 /* Doctor.swift */, - F4A8DC0F1D525DAC00ACC4E5 /* Symbolicate.swift */, ); name = Crash; sourceTree = ""; @@ -651,6 +668,9 @@ dependencies = ( ); name = SYM; + packageProductDependencies = ( + B5F935F227E46E9800C2D0C6 /* SwiftyJSON */, + ); productName = SYM; productReference = F49BD1EC1D2FF46300024AAE /* SYM.app */; productType = "com.apple.product-type.application"; @@ -699,6 +719,9 @@ "zh-Hans", ); mainGroup = F49BD1E31D2FF46300024AAE; + packageReferences = ( + B5F935F127E46E9800C2D0C6 /* XCRemoteSwiftPackageReference "SwiftyJSON" */, + ); productRefGroup = F49BD1ED1D2FF46300024AAE /* Products */; projectDirPath = ""; projectRoot = ""; @@ -718,6 +741,8 @@ files = ( 510777791EFBA4E20055E8BD /* AppleDemo.ips in Resources */, 51BF9C5A1D6C7ACC00379BFC /* UmengDemo.crash in Resources */, + B5F935F727E4827C00C2D0C6 /* AppleConverted.log in Resources */, + B5F935F627E4827C00C2D0C6 /* AppleJson.ips in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -792,6 +817,7 @@ B59B326E22E0112E00013B67 /* MDAfcClient.m in Sources */, 51C9A4A01EEFE98700F21AE4 /* ParserTests.swift in Sources */, B59B326D22E0112B00013B67 /* MDDeviceFile.m in Sources */, + B5F935F927E482A000C2D0C6 /* ConvertorTests.swift in Sources */, B59B326B22E0112400013B67 /* MDDeviceMonitor.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -815,11 +841,11 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B5C4EA5827D77C6500D0F8A5 /* Convertor.swift in Sources */, B535D7362576223800502711 /* MDSBServices.m in Sources */, B53E2F8622D48383003BC45C /* MDHouseArrest.m in Sources */, F40A36A41D3FBB7200F3B00C /* Regex.swift in Sources */, B5F3578922C3121200111FF3 /* Image.swift in Sources */, - F4A8DC101D525DAC00ACC4E5 /* Symbolicate.swift in Sources */, F49BD1F01D2FF46300024AAE /* AppDelegate.swift in Sources */, B53E2F8022D477A3003BC45C /* MDAfcClient.m in Sources */, F422C7C31D36A66800066837 /* FileFinder.swift in Sources */, @@ -847,10 +873,12 @@ F4EFE91A2126D0B600E9EDA9 /* CrashImporterViewController.swift in Sources */, B5B97AEF22DC737900F5DDF2 /* MDInstProxy.m in Sources */, B5089463258C838D0074254E /* MDScreenShotr.m in Sources */, + B56FBA1C27D72E200065E78F /* Parser.swift in Sources */, B53E2F7722D44838003BC45C /* MDLockdown.m in Sources */, F4EFE90B2125583700E9EDA9 /* MDDeviceMonitor.m in Sources */, F4A679741E21319D009F93E7 /* BundleDocument.swift in Sources */, B597FE1522DC8A9400BE439A /* FileBrowserViewController.swift in Sources */, + B5D0512327D77335009E95B0 /* Symbolicator.swift in Sources */, 51B8D7281D4F6A0000F2C24C /* ContentViewController.swift in Sources */, 51C9A4A21EEFF78500F21AE4 /* Doctor.swift in Sources */, B5F3578722C30B7C00111FF3 /* Color.swift in Sources */, @@ -936,7 +964,11 @@ BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = SYMTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = im.zorro.SYMTests; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -954,11 +986,16 @@ BUNDLE_LOADER = "$(TEST_HOST)"; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = SYMTests/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/../Frameworks", + ); MACOSX_DEPLOYMENT_TARGET = 11.0; PRODUCT_BUNDLE_IDENTIFIER = im.zorro.SYMTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYSTEM_FRAMEWORK_SEARCH_PATHS = ""; SYSTEM_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/SYM/Device"; @@ -1023,8 +1060,9 @@ MACOSX_DEPLOYMENT_TARGET = 10.12; MTL_FAST_MATH = YES; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/icp/ICP-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/SYM/Device"; }; @@ -1163,7 +1201,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 91; + CURRENT_PROJECT_VERSION = 92; DEVELOPMENT_TEAM = BPL95996PK; ENABLE_HARDENED_RUNTIME = YES; GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1"; @@ -1172,7 +1210,10 @@ "$(PROJECT_DIR)/SYM/Device/libimobiledevice", ); INFOPLIST_FILE = SYM/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/SYM/Device/libimobiledevice", @@ -1181,7 +1222,7 @@ "$(PROJECT_DIR)/SYM/Device/plist", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 0.8.3; + MARKETING_VERSION = 0.8.4; PRODUCT_BUNDLE_IDENTIFIER = im.zorro.SYM; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; @@ -1202,7 +1243,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = ""; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 91; + CURRENT_PROJECT_VERSION = 92; DEVELOPMENT_TEAM = BPL95996PK; ENABLE_HARDENED_RUNTIME = YES; GCC_PREPROCESSOR_DEFINITIONS = ""; @@ -1211,7 +1252,10 @@ "$(PROJECT_DIR)/SYM/Device/libimobiledevice", ); INFOPLIST_FILE = SYM/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/SYM/Device/libimobiledevice", @@ -1220,13 +1264,14 @@ "$(PROJECT_DIR)/SYM/Device/plist", ); MACOSX_DEPLOYMENT_TARGET = 11.0; - MARKETING_VERSION = 0.8.3; + MARKETING_VERSION = 0.8.4; PRODUCT_BUNDLE_IDENTIFIER = im.zorro.SYM; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE = ""; PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/SYM/SYM-Bridging-Header.h"; - SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = "$(PROJECT_DIR)/SYM/Device"; @@ -1283,6 +1328,25 @@ defaultConfigurationName = Release; }; /* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + B5F935F127E46E9800C2D0C6 /* XCRemoteSwiftPackageReference "SwiftyJSON" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/SwiftyJSON/SwiftyJSON.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 5.0.0; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + B5F935F227E46E9800C2D0C6 /* SwiftyJSON */ = { + isa = XCSwiftPackageProductDependency; + package = B5F935F127E46E9800C2D0C6 /* XCRemoteSwiftPackageReference "SwiftyJSON" */; + productName = SwiftyJSON; + }; +/* End XCSwiftPackageProductDependency section */ }; rootObject = F49BD1E41D2FF46300024AAE /* Project object */; } diff --git a/SYM.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/SYM.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 917255f..919434a 100644 --- a/SYM.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/SYM.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/SYM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/SYM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000..3198c98 --- /dev/null +++ b/SYM.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "swiftyjson", + "kind" : "remoteSourceControl", + "location" : "https://github.com/SwiftyJSON/SwiftyJSON.git", + "state" : { + "revision" : "b3dcd7dbd0d488e1a7077cb33b00f2083e382f07", + "version" : "5.0.1" + } + } + ], + "version" : 2 +} diff --git a/SYM/Convertor.swift b/SYM/Convertor.swift new file mode 100644 index 0000000..c8bd572 --- /dev/null +++ b/SYM/Convertor.swift @@ -0,0 +1,253 @@ +// The MIT License (MIT) +// +// Copyright (c) 2022 zqqf16 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation +import SwiftyJSON + +protocol Convertor { + static func match(_ content: String) -> Bool + func convert(_ content: String) -> String +} + +@resultBuilder +struct CrashContentBuilder { + static func buildBlock(_ components: String...) -> String { + return components.joined(separator: "\n") + } + static func buildArray(_ components: [String]) -> String { + return components.joined(separator: "\n") + } + static func buildEither(first component: String) -> String { + component + } + static func buildEither(second component: String) -> String { + component + } + static func buildOptional(_ component: String?) -> String { + return component ?? "" + } +} + +@resultBuilder +struct CrashInlineBuilder { + static func buildBlock(_ components: String...) -> String { + return components.joined(separator: "") + } + static func buildArray(_ components: [String]) -> String { + return components.joined(separator: "") + } + static func buildEither(first component: String) -> String { + component + } + static func buildEither(second component: String) -> String { + component + } + static func buildOptional(_ component: String?) -> String { + return component ?? "" + } +} + + +struct AppleJsonConvertor: Convertor { + static func match(_ content: String) -> Bool { + let components = self.split(content) + return components.header != nil && components.payload != nil + } + + private static func split(_ content: String) -> (header: JSON?, payload: JSON?) { + var header: JSON? + var payload: JSON? + + var lines = content.components(separatedBy: "\n") + if let headerData = lines.removeFirst().data(using: .utf8) { + header = try? JSON(data: headerData) + } + if let payloadData = lines.joined(separator: "\n").data(using: .utf8) { + payload = try? JSON(data: payloadData) + } + + return (header, payload) + } + + func convert(_ content: String) -> String { + let components = Self.split(content) + guard let header = components.header, + let payload = components.payload + else { + return content + } + + let _P: (String) -> String = { key in + return payload[key].stringValue + } + + let _H: (String) -> String = { key in + return header[key].stringValue + } + + return self.buildCrash { + "Incident Identifier: %@".format(_H("incident_id")) + "CrashReporter Key: %@".format(_P("crashReporterKey")) + "Hardware Model: %@".format(_P("modelCode")) + "Process: %@ [%@]".format(_P("procName"), _P("pid")) + "Path: %@".format(_P("procPath")) + "Identifier: %@".format(_P("coalitionName")) + "Version: %@ (%@)".format(_H("app_version"), _H("build_version")) + "Code Type: %@".format(_P("cpuType")) + "Role: %@".format(_P("procRole")) + "Parent Process: %@ [%@]".format(_P("parentProc"), _P("parentPid")) + "Coalition: %@ [%@]".format(_P("coalitionName"), _P("coalitionID")) + "" + "Date/Time: %@".format(_P("captureTime")) + "Launch Time: %@".format(_P("procLaunch")) + "OS Version: %@".format(_H("os_version")) + "Release Type: %@".format(payload["osVersion"]["releaseType"].stringValue) + "Baseband Version: %@".format(_P("basebandVersion")) + "Report Version: 104" + "" + "Exception Type: %@ (%@)".format(payload["exception"]["type"].stringValue, payload["exception"]["signal"].stringValue) + "Exception Codes: %@".format(payload["exception"]["codes"].stringValue) + //"Exception Note: %@".format() + "Termination Reason: %@ %@".format(payload["termination"]["namespace"].stringValue, payload["termination"]["code"].stringValue) + payload["termination"]["details"][0].stringValue + "" + "Triggered by Thread: %@".format(_P("faultingThread")) + "" + self.buildThreads(payload) + "" + self.buildRegisters(payload) + "" + self.buildImage(payload) + "" + "EOF" + "" + } + } + + private func buildCrash(@CrashContentBuilder builder: ()->String) -> String { + builder() + } + + private func buildLine(@CrashInlineBuilder builder: ()->String) -> String { + builder() + } + + private func buildThreads(_ payload: JSON) -> String { + let binaryImages = payload["usedImages"] + let threads = payload["threads"].arrayValue + return self.buildCrash { + for (index, thread) in threads.enumerated() { + if thread["name"].string != nil { + "Thread %d name: %@".format(index, thread["name"].stringValue) + } else if thread["queue"].string != nil { + "Thread %d name: Dispatch queue: %@".format(index, thread["queue"].stringValue) + } + if thread["triggered"].boolValue { + "Thread \(index) Crashed:" + } else { + "Thread \(index):" + } + for (frameIndex, frame) in thread["frames"].arrayValue.enumerated() { + self.build(frame: frame, index: frameIndex, binaryImages: binaryImages) + } + "" + } + } + } + + private func build(frame: JSON, index: Int, binaryImages: JSON) -> String { + let image = binaryImages[frame["imageIndex"].intValue] + let address = frame["imageOffset"].intValue + image["base"].intValue + //0 Foundation 0x182348144 NSKeyValueWillChangeWithPerThreadPendingNotifications + 200 + return self.buildLine { + String(index).padding(length: 4) + image["name"].stringValue.padding(length: 39) + "0x%llx ".format(address) + if let symbol = frame["symbol"].string, let symbolLocation = frame["symbolLocation"].int { + "\(symbol) + \(symbolLocation)" + } else { + "0x%llx + %d".format(image["base"].int64Value, frame["imageOffset"].intValue) + } + if let sourceFile = frame["sourceFile"].string, let sourceLine = frame["sourceLine"].int { + " (\(sourceFile):\(sourceLine))" + } + } + } + + private func buildRegisters(_ payload: JSON) -> String { + let threads = payload["threads"].arrayValue + let triggeredThread = threads.first { thread in + thread["triggered"].boolValue + } + if triggeredThread == nil { + return "" + } + + let triggeredIndex = payload["faultingThread"].intValue + let cpu = payload["cpuType"].stringValue + var content = "Thread \(triggeredIndex) crashed with ARM Thread State (\(cpu)):\n" + + let threadState = triggeredThread!["threadState"] + let x = threadState["x"].arrayValue + for (index, reg) in x.enumerated() { + let id = "x\(index)".padding(length: 6, atLeft: true) + content.append("\(id): 0x%016X".format(reg["value"].int64Value)) + if index % 4 == 3 { + content.append("\n") + } + } + var index = x.count % 4 + for name in ["fp", "lr", "sp", "pc", "cpsr", "far", "esr"] { + let value = threadState[name]["value"].int64Value + let desc = threadState[name]["description"].stringValue + let id = "\(name)".padding(length: 6, atLeft: true) + content.append("\(id): 0x%016X".format(value)) + if desc.count > 0 { + content.append(" \(desc)") + } + if index % 3 == 2 { + content.append("\n") + } + index += 1 + } + + return content + } + + private func buildImage(_ payload: JSON) -> String { + /* + Binary Images: + 0x18232f000 - 0x182635fff Foundation arm64e <9618b2f2a4c23e07b7eed8d9e1bdeaec> /System/Library/Frameworks/Foundation.framework/Foundation + */ + let binaryImages = payload["usedImages"].arrayValue + return self.buildCrash { + "Binary Images:" + for image in binaryImages { + self.buildLine { + "0x%llx - 0x%llx ".format(image["base"].intValue, image["base"].intValue + image["size"].intValue - 1) + "%@ %@ ".format(image["name"].stringValue, image["arch"].stringValue) + "<%@> %@".format(image["uuid"].stringValue.replacingOccurrences(of: "-", with: ""), image["path"].stringValue) + } + } + } + } +} diff --git a/SYM/Crash.swift b/SYM/Crash.swift index 4be681d..f9fc4ce 100644 --- a/SYM/Crash.swift +++ b/SYM/Crash.swift @@ -84,248 +84,34 @@ class Binary { } } - -class CrashInfo { - let raw: String +class Crash { + let content: String var appName: String? var device: String? var bundleID: String? - var arch: String = "arm64" + var arch: String? = "arm64" var uuid: String? var osVersion: String? var appVersion: String? - var embeddedBinaries: [Binary] = [] var binaryImages: [Binary] = [] - init(_ raw: String) { - self.raw = raw - self.parseCrashInfo() - self.parseBinaries() - } - - func parseOneLineInfo(_ re: RE) -> String? { - return re.findFirst(self.raw)?[0] - } - - func parseCrashInfo() { - self.appName = self.parseOneLineInfo(.process) - self.device = self.parseOneLineInfo(.hardware) - self.bundleID = self.parseOneLineInfo(.identifier) - self.osVersion = self.parseOneLineInfo(.osVersion) - self.appVersion = self.parseOneLineInfo(.version) - - if let binary = self.appName, - let imageRE = RE.image(binary, options:[]), - let imageMatch = imageRE.findFirst(self.raw) { - self.uuid = imageMatch[3].uuidFormat() - self.arch = imageMatch[2] - } - } - - func parseBinaries() { - self.binaryImages = [] - if let groups = RE.image.findAll(self.raw) { - for group in groups { - let binary = Binary(name: group[1], - uuid: group[3].uuidFormat(), - arch: group[2], - loadAddress: group[0], - path: group[4]) - - binary.executable = binary.name == self.appName - self.binaryImages.append(binary) - } - } - - self.embeddedBinaries = self.binaryImages.filter { $0.inApp } - } - - func crashedThreadRange() -> NSRange? { - return RE.threadCrashed.findFirstRange(self.raw) - } - - func backtraceRanges(withBinary binary: String) -> [NSRange] { - var result:[NSRange] = [] - if let frameRE = RE.frame(binary), - let frames = frameRE.findAllRanges(self.raw) { - result = frames - } - return result - } - - func appBacktraceRanges() -> [NSRange] { - var binaryNames: [String] = [] - if self.embeddedBinaries.count > 0 { - self.embeddedBinaries.forEach { (binary) in - binaryNames.append(binary.name) - } - } else if let appName = self.appName { - binaryNames.append(appName) - } - - var ranges: [NSRange] = [] - binaryNames.forEach { (name) in - ranges.append(contentsOf: self.backtraceRanges(withBinary: name)) - } - - return ranges; - } + var crashedThreadRange: NSRange? + var appBacktraceRanges: [NSRange] = [] - func symbolicate(dsyms: [String]? = nil) -> String { - if let content = SubProcess.symbolicatecrash(crash: self.raw, dsyms: dsyms), content.count > 0 { - return content - } - - return self.raw - } -} - -class CPUUsageLog: CrashInfo { - override func parseCrashInfo() { - self.appName = self.parseOneLineInfo(.powerstats) - self.device = self.parseOneLineInfo(.hardware) - self.arch = self.parseOneLineInfo(.architecture) ?? "arm64" - self.osVersion = self.parseOneLineInfo(.osVersion) - self.appVersion = self.parseAppVersion() - if let path = self.parseOneLineInfo(.path), - let imageRE = RE.image(withPath: path), - let imageMatch = imageRE.findFirst(self.raw) { - //self.bundleID = imageMatch[1] - self.uuid = imageMatch[1].uuidFormat() - } + var embeddedBinaries: [Binary] { + // executable, embedded dynamic libraries + return binaryImages.filter { $0.inApp } } - private func parseAppVersion() -> String? { - if let app = self.parseOneLineInfo(.appVersion), let build = self.parseOneLineInfo(.buildVersion) { - return "\(build) (\(app))" - } - - guard let versionString = self.parseOneLineInfo(.version) else { - return nil - } - - // 1.1.1 (123) to 123 (1.1.1) - let components = versionString.components(separatedBy: " ") - if components.count != 2 { - return versionString; - } - - let app = components[0] - let build = components[1].replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "") - return "\(build) (\(app))" + enum SymbolicateMethod { + case atos + case symbolicatecrash // buildin symbolicatecrash } - override func parseBinaries() { - self.binaryImages = [] - if let groups = RE.cpuUsageImage.findAll(self.raw) { - for group in groups { - let binary = Binary(name: group[1], - uuid: group[2].uuidFormat(), - arch: nil, - loadAddress: group[0], - path: group[3]) - - binary.executable = binary.name == self.appName - self.binaryImages.append(binary) - } - } - - self.embeddedBinaries = self.binaryImages.filter { $0.inApp } - } - - override func backtraceRanges(withBinary binary: String) -> [NSRange] { - var result:[NSRange] = [] - if let frameRE = RE.cpuUsageFrame(binary), - let frames = frameRE.findAllRanges(self.raw) { - result = frames - } - return result - } -} - -class FabricCrash: CrashInfo { - override func parseCrashInfo() { - self.device = self.parseOneLineInfo(.hashDevice) - self.osVersion = self.parseOneLineInfo(.hashOSVersion) - self.appVersion = self.parseOneLineInfo(.hashAppVersion) - self.bundleID = self.parseOneLineInfo(.hashBundleID) - if let osVersion = self.osVersion, let platform = self.parseOneLineInfo(.hashPlatform) { - self.osVersion = "\(platform) \(osVersion)" - } - } -} - -class UmengCrash: CrashInfo { - override func parseCrashInfo() { - self.appName = self.parseOneLineInfo(.binaryImage) - self.uuid = self.parseOneLineInfo(.dsymUUID) - self.arch = self.parseOneLineInfo(.cpuType) ?? "arm64" - } - - func executableBinaryImage() -> Binary? { - guard let appName = self.appName else { - return nil - } - - let loadAddress = self.parseOneLineInfo(.slideAddress) - let ranges = self.appBacktraceRanges() - let backtrace = ranges.map { (range) -> Frame in - let line = self.raw.substring(with: range)! - let group = RE.frame.match(line)! - // 0 BinaryName 0x00000001000effdc 0x1000e4000 + 49116 - return Frame(raw: line, - index: group[0], - image: group[1], - address: group[2], - symbol: group[3]) - } - - let binary = Binary(name: appName, - uuid: self.uuid, - arch: self.arch, - loadAddress: loadAddress, - path: "") - binary.backtrace = backtrace - return binary - } + var symbolicateMethod: SymbolicateMethod = .symbolicatecrash - override func symbolicate(dsyms: [String]? = nil) -> String { - guard let image = self.executableBinaryImage(), image.isValid else { - return self.raw - } - - image.fix() - var dsymPath = "" - if let dsymPaths = dsyms { - dsymPath = dsymPaths[0] - } - guard let result = SubProcess.atos(image, dsym: dsymPath) else { - return self.raw - } - - var newContent = self.raw - for frame in image.backtrace! { - if let symbol = result[frame.address] { - var newFrame = frame - newFrame.symbol = symbol - newContent = newContent.replacingOccurrences(of: frame.raw, with: newFrame.description) - } - } - - return newContent - } -} - -extension CrashInfo { - static func parse(_ content: String) -> CrashInfo { - if content.contains("dSYM UUID") && content.contains("Slide Address") { - return UmengCrash(content) - } else if (content.contains("Wakeups limit") || content.contains("CPU limit")) && content.contains("Limit duration:") { - return CPUUsageLog(content) - } else if content.contains("# Crashlytics - plaintext stacktrace") { - return FabricCrash(content) - } - return CrashInfo(content) + init(_ content: String) { + self.content = content } } diff --git a/SYM/CrashDocument.swift b/SYM/CrashDocument.swift index 55fbcbf..c3913bd 100644 --- a/SYM/CrashDocument.swift +++ b/SYM/CrashDocument.swift @@ -32,7 +32,7 @@ class CrashDocument: NSDocument { let textStorage = NSTextStorage() @Published - var crashInfo: CrashInfo? + var crashInfo: Crash? @Published var isSymbolicating: Bool = false @@ -79,7 +79,10 @@ class CrashDocument: NSDocument { } private func readCrash(from data: Data) throws { - let content = String(data: data, encoding: .utf8) ?? "" + var content = String(data: data, encoding: .utf8) ?? "" + if AppleJsonConvertor.match(content) { + content = AppleJsonConvertor().convert(content) + } self.update(content: content) self.parseCrashInfo(content) } @@ -109,7 +112,7 @@ class CrashDocument: NSDocument { extension CrashDocument: NSTextStorageDelegate { func parseCrashInfo(_ content: String) { - let crashInfo = CrashInfo.parse(content) + let crashInfo = Crash.parse(content) DispatchQueue.main.async { self.crashInfo = crashInfo } @@ -131,7 +134,7 @@ extension CrashDocument { DispatchQueue.global().async { self.isSymbolicating = true - let content = crash.symbolicate(dsyms: dsyms) + let content = crash.symbolicate(dsymPaths: dsyms) DispatchQueue.main.async { self.update(content: content) self.undoManager?.removeAllActions() diff --git a/SYM/Dsym/DsymDownloader.swift b/SYM/Dsym/DsymDownloader.swift index 59f149b..01da624 100644 --- a/SYM/Dsym/DsymDownloader.swift +++ b/SYM/Dsym/DsymDownloader.swift @@ -24,7 +24,7 @@ import Cocoa import Combine class DsymDownloadTask { - var crashInfo: CrashInfo + var crashInfo: Crash enum Status { case waiting @@ -104,7 +104,7 @@ class DsymDownloadTask { private var fileURL: URL? private var scriptURL: URL - init(crashInfo: CrashInfo, scriptURL: URL, fileURL: URL?) { + init(crashInfo: Crash, scriptURL: URL, fileURL: URL?) { self.crashInfo = crashInfo self.fileURL = fileURL; self.scriptURL = scriptURL; @@ -122,7 +122,7 @@ class DsymDownloadTask { let crashPath = self.fileURL?.path ?? FileManager.default.temporaryPath() do { - try self.crashInfo.raw.write(toFile: crashPath, atomically: true, encoding: .utf8) + try self.crashInfo.content.write(toFile: crashPath, atomically: true, encoding: .utf8) } catch { self.statusCode = -1001 self.status = .failed(code: self.statusCode, message: "Failed to save file") @@ -156,7 +156,7 @@ class DsymDownloadTask { self.status = .canceled } - private func crashInfoToEnv(_ crashInfo: CrashInfo) -> [String: String] { + private func crashInfoToEnv(_ crashInfo: Crash) -> [String: String] { var env: [String: String] = [:] env["APP_NAME"] = crashInfo.appName ?? "" env["UUID"] = crashInfo.uuid ?? "" @@ -243,7 +243,7 @@ class DsymDownloader { } @discardableResult - func download(crashInfo: CrashInfo, fileURL: URL?) -> DsymDownloadTask? { + func download(crashInfo: Crash, fileURL: URL?) -> DsymDownloadTask? { guard let uuid = crashInfo.uuid, self.canDownload() else { return nil } diff --git a/SYM/Dsym/DsymManager.swift b/SYM/Dsym/DsymManager.swift index 6bd1e1c..6df9549 100644 --- a/SYM/Dsym/DsymManager.swift +++ b/SYM/Dsym/DsymManager.swift @@ -55,7 +55,7 @@ class DsymManager { var dsymFiles: [String: DsymFile] = [:] @Published - var crash: CrashInfo? + var crash: Crash? private var uuids: [String] { return self.binaries.compactMap { $0.uuid } @@ -68,7 +68,7 @@ class DsymManager { return mdfind }() - func update(_ crash: CrashInfo?) { + func update(_ crash: Crash?) { self.crash = crash self.binaries = crash?.embeddedBinaries ?? [] if crash != nil { @@ -78,7 +78,7 @@ class DsymManager { } } - func start(_ crash: CrashInfo) { + func start(_ crash: Crash) { if let condition = self.createCondition(bundleID: crash.bundleID, binaries: crash.embeddedBinaries) { self.monitor.start(withCondition: condition) } diff --git a/SYM/Extensions.swift b/SYM/Extensions.swift index 2b7049d..b8185fb 100644 --- a/SYM/Extensions.swift +++ b/SYM/Extensions.swift @@ -34,6 +34,10 @@ extension NSRange { // MARK: - String extension String { + func format(_ args: CVarArg...) -> String { + return String(format: self, arguments: args) + } + var hexaToDecimal: Int { var hex = self if hex.hasPrefix("0x") { @@ -89,6 +93,13 @@ extension String { } } + func padding(length: Int, atLeft: Bool = false) -> String { + if atLeft { + return String(repeatElement(" ", count: length - self.count)) + self + } + return self.padding(toLength: length, withPad: " ", startingAt: 0) + } + func extendToLength(_ length: Int, withString padString: String=" ", atRight: Bool=true) -> String { return self.padding(toLength: length, withPad: padString, startingAt: 0) } diff --git a/SYM/Parser.swift b/SYM/Parser.swift new file mode 100644 index 0000000..9bc3506 --- /dev/null +++ b/SYM/Parser.swift @@ -0,0 +1,397 @@ +// The MIT License (MIT) +// +// Copyright (c) 2022 zqqf16 +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +import Foundation + +protocol CrashParser { + static func match(_ content: String) -> Bool + func parse(_ content: String) -> Crash +} + +struct RegexHelper { + typealias KeyValueRegexMap = [WritableKeyPath: Regex] + + static func parseBaseInfo(_ content: String, crash: inout Crash, map: KeyValueRegexMap) { + for (keyPath, regex) in map { + if let value = regex.firstMatch(in: content)?.captures?[1] { + crash[keyPath: keyPath] = value + } + } + } + + static func parseBinaries(_ content: String, crash: inout Crash, regex: Regex, convertor:([String])->Binary) { + if let images = regex.matches(in: content) { + for match in images { + guard let captures = match.captures else { + continue + } + + let binary = convertor(captures) + binary.executable = binary.name == crash.appName + crash.binaryImages.append(binary) + } + } + } + + static func parseCrashedThreadRange(_ content: String, crash: inout Crash, regex: Regex) { + if let match = regex.firstMatch(in: content) { + crash.crashedThreadRange = match.range + } + } + + static func parseAppBacktrackRanges(_ content: String, crash: inout Crash, constructor:((String)->Regex?)) { + let embeddedBinaries = crash.embeddedBinaries.map { $0.name } + for binary in embeddedBinaries { + guard let regex = constructor(binary) else { + continue + } + + regex.matches(in: content)?.forEach({ match in + crash.appBacktraceRanges.append(match.range) + }) + } + } +} + +extension Regex { + func value(in string: String) -> String? { + return self.firstMatch(in: string)?.captures?[1] + } +} + +// MARK: - Apple +extension Regex { + // 0 BinaryName 0x00000001000effdc 0x1000e4000 + 49116 + static let frame = try! Regex("^\\s*(\\d{1,3})\\s+([^ ]+)\\s+(0[xX][A-Fa-f0-9]+)\\s+(.*)", options: .anchorsMatchLines) + + // 0x19a8d8000 - 0x19a8f4fff libsystem_m.dylib arm64 /usr/lib/system/libsystem_m.dylib + static let image = try! Regex("\\s*(0[xX][A-Fa-f0-9]+)\\s+-\\s+\\w+\\s+([^\\s]+)\\s*(\\w+)\\s*<(.*)> (.*)") + + // Thread 0: + // Thread 0 Crashed: + // Thread 0 name xxxxxx + static let thread = try! Regex("Thread (\\d{1,3})(?:[: ])(?:(?:(Crashed):)|(?:name:\\s+(.*)))*$") + + // Process demo [1111] + static let process = try! Regex("^Process:\\s*([^\\s]+)\\s*\\[*", options: .anchorsMatchLines) + + // Identifier: im.zorro.demo + static let identifier = try! Regex("^Identifier:\\s*([^\\s]+)", options: .anchorsMatchLines) + + // Hardware Model: iPhone5,2 + static let hardware = try! Regex("Hardware Model:\\s*([^\\s]+)", options: .caseInsensitive) + + // Frame with specified binary + static func frame(_ binary: String, options: NSRegularExpression.Options = .anchorsMatchLines) -> Regex? { + return try? Regex("^\\s*(\\d{1,3})\\s+(\(binary))\\s+(0[xX][A-Fa-f0-9]+)\\s+(.*)", options: options) + } + + // Image with specified binary + static func image(_ binary: String, options: NSRegularExpression.Options = .anchorsMatchLines) -> Regex? { + return try? Regex("\\s*(0[xX][A-Fa-f0-9]+)\\s+-\\s+\\w+\\s+(\(binary))\\s*(\\w+)\\s*<(.*)>", options: options) + } + + // UUID: E5B0A378-6816-3D90-86FD-2AEF15894A85 + static let uuid = try! Regex("[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}", options: [.anchorsMatchLines, .caseInsensitive]) + + // Thread 55 Crashed: + static let threadCrashed = try! Regex("(?:^Thread \\d+.*\n)*^Thread \\d+ Crashed:\\s*\n(?:^\\s*\\d{1,3}.*\n)+", options: .anchorsMatchLines) + + // OS Version: iPhone OS 11.4 (15F79) + static let osVersion = try! Regex("^OS Version:\\s*(.*)", options: .anchorsMatchLines) + + // Version: 521 (5.7.8) + static let version = try! Regex("^Version:\\s*(.*)", options: .anchorsMatchLines) +} + +struct AppleParser: CrashParser { + static func match(_ content: String) -> Bool { + return true //cover all + } + + func parse(_ content: String) -> Crash { + var crash = Crash(content) + + let regexMap: RegexHelper.KeyValueRegexMap = [ + \.appName: .process, + \.device: .hardware, + \.bundleID: .identifier, + \.osVersion: .osVersion, + \.appVersion: .version + ] + RegexHelper.parseBaseInfo(content, crash: &crash, map: regexMap) + + if let binary = crash.appName, + let imageRegex = Regex.image(binary, options:[]), + let captures = imageRegex.firstMatch(in: content)?.captures { + crash.uuid = captures[4].uuidFormat() + crash.arch = captures[3] + } + + RegexHelper.parseBinaries(content, crash: &crash, regex: .image) { captures in + return Binary(name: captures[2], + uuid: captures[4].uuidFormat(), + arch: captures[3], + loadAddress: captures[1], + path: captures[5]) + } + + // backtrace + RegexHelper.parseCrashedThreadRange(content, crash: &crash, regex: .threadCrashed) + RegexHelper.parseAppBacktrackRanges(content, crash: &crash) { binary in + return Regex.frame(binary) + } + + return crash + } +} + +// MARK: - CPU Usage +extension Regex { + // Powerstats for: demo [28286] + static let powerstats = try! Regex("^Powerstats for:\\s*([^\\s]+)\\s*\\[*", options: .anchorsMatchLines) + + // Architecture: arm64 + static let architecture = try! Regex("^Architecture:\\s*([^\\s]+)\\s*", options: .anchorsMatchLines) + + // App version: 5.7.8 + static let appVersion = try! Regex("^App version:\\s*(.*)", options: .anchorsMatchLines) + + // Build version: 521 + static let buildVersion = try! Regex("^Build version:\\s*(.*)", options: .anchorsMatchLines) + + // Path: /private/var/containers/Bundle/Application/xxx + static let path = try! Regex("^Path:\\s*([^\\s]+)\\s*", options: .anchorsMatchLines) + + // 0x100874000 - ??? com.yourapp 5.8.0 (5044) <425D7866-BFF0-3D9C-B354-07057F9A903A> /private/var/containers/Bundle/Application/DACCA9B7-C6CD-4FBF-A2A2-2C78701748AA/demo.app/demo + static func image(withPath path: String, options: NSRegularExpression.Options = .anchorsMatchLines) -> Regex? { + return try? Regex("\\s*(0[xX][A-Fa-f0-9]+)\\s+-.*<(.*)>\\s+\(path)", options: options) + } + + // 0x18876d000 - 0x188790fff libsystem_malloc.dylib <6E321806-C54E-31DB-B4A8-9DEC04A5CA2C> /usr/lib/system/libsystem_malloc.dylib + // 0x18876d000 - ??? libsystem_malloc.dylib <6E321806-C54E-31DB-B4A8-9DEC04A5CA2C> /usr/lib/system/libsystem_malloc.dylib + // 0x18876d000 - ??? libsystem_malloc.dylib 123 (1.1.1) <6E321806-C54E-31DB-B4A8-9DEC04A5CA2C> /usr/lib/system/libsystem_malloc.dylib + static let cpuUsageImage = try! Regex("\\s*(0[xX][A-Fa-f0-9]+)\\s+-\\s+[^\\s]+\\s+([^\\s]+).*\\s*<(.*)> (.*)") + + // 2 -[NSRunLoop run] + 87 (Foundation + 512424) [0x182f721a8] + // 2 -[TheClass function:] (xxx.m:1476 in binary + 12591292) [0x101b720bc] + // 2 ??? (AGXMetalA11 + 553492) [0x1aaa7f214] + static let cpuUsageFrame = try! Regex("^.*[\\( ](.*) \\+ \\d+\\) \\[(0[xX][A-Fa-f0-9]+)\\].*", options: .anchorsMatchLines) + + static func cpuUsageFrame(_ binary: String, options: NSRegularExpression.Options = .anchorsMatchLines) -> Regex? { + //4 function_name (file.name:90 in binary + 869436) [0x1050a843c] + //2 ??? (binary + 41878904) [0x1032d0578] + return try? Regex("^\\s*\\d+.*(\(binary)).*\\[(0[xX][A-Fa-f0-9]+)\\].*", options: options) + } +} + +struct CPUUsageParser: CrashParser { + static func match(_ content: String) -> Bool { + return (content.contains("Wakeups limit") + || content.contains("CPU limit")) + && content.contains("Limit duration:") + } + + func parse(_ content: String) -> Crash { + var crash = Crash(content) + + let regexMap: RegexHelper.KeyValueRegexMap = [ + \.appName: .powerstats, + \.device: .hardware, + \.arch: .architecture, + \.osVersion: .osVersion, + ] + RegexHelper.parseBaseInfo(content, crash: &crash, map: regexMap) + + crash.appVersion = self.parseAppVersion(content) + + if let path = Regex.path.value(in: content), + let regex = Regex.image(withPath: path), + let captures = regex.firstMatch(in: content)?.captures { + crash.uuid = captures[2].uuidFormat() + } + + RegexHelper.parseBinaries(content, crash: &crash, regex: .image) { captures in + return Binary(name: captures[2], + uuid: captures[3].uuidFormat(), + arch: nil, + loadAddress: captures[1], + path: captures[4]) + } + + // backtrace + RegexHelper.parseCrashedThreadRange(content, crash: &crash, regex: .threadCrashed) + RegexHelper.parseAppBacktrackRanges(content, crash: &crash) { binary in + return Regex.cpuUsageFrame(binary) + } + + return crash + } + + private func parseAppVersion(_ content: String) -> String? { + if let app = Regex.appVersion.value(in: content), + let build = Regex.buildVersion.value(in: content) { + return "\(build) (\(app))" + } + + guard let versionString = Regex.version.value(in: content) else { + return nil + } + + // 1.1.1 (123) to 123 (1.1.1) + let components = versionString.components(separatedBy: " ") + if components.count != 2 { + return versionString; + } + + let app = components[0] + let build = components[1].replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "") + return "\(build) (\(app))" + } +} + +// MARK: - Fabric +extension Regex { + static let hashDevice = try! Regex("^# Device:\\s*(.+)", options: .anchorsMatchLines) + static let hashAppVersion = try! Regex("^# Version:\\s*(.+)", options: .anchorsMatchLines) + static let hashPlatform = try! Regex("^# Platform:\\s*(.+)", options: .anchorsMatchLines) + static let hashOSVersion = try! Regex("^# OS Version:\\s*([^\\(]+)", options: .anchorsMatchLines) + static let hashBundleID = try! Regex("^# Bundle Identifier:\\s*(.*)", options: .anchorsMatchLines) +} + +struct FabricParser: CrashParser { + static func match(_ content: String) -> Bool { + return content.contains("# Crashlytics - plaintext stacktrace") + } + + func parse(_ content: String) -> Crash { + var crash = Crash(content) + let regexMap: RegexHelper.KeyValueRegexMap = [ + \.device: .hashDevice, + \.osVersion: .hashOSVersion, + \.appVersion: .hashAppVersion, + \.bundleID: .hashBundleID + ] + RegexHelper.parseBaseInfo(content, crash: &crash, map: regexMap) + + if let osVersion = crash.osVersion, let platform = Regex.hashPlatform.value(in: content) { + crash.osVersion = "\(platform) \(osVersion)" + } + + RegexHelper.parseBinaries(content, crash: &crash, regex: .image) { captures in + return Binary(name: captures[2], + uuid: captures[4].uuidFormat(), + arch: captures[3], + loadAddress: captures[1], + path: captures[5]) + } + + // backtrace + RegexHelper.parseCrashedThreadRange(content, crash: &crash, regex: .threadCrashed) + RegexHelper.parseAppBacktrackRanges(content, crash: &crash) { binary in + return Regex.frame(binary) + } + + return crash + } +} + +// MARK: - Umeng +extension Regex { + // Binary Image: demo + static let binaryImage = try! Regex("Binary Image:\\s*([^\\s]+)") + + // dSYM UUID: 45AF800D-B56A-39D8-AB1C-AD0F3208EC50 + static let dsymUUID = try! Regex("dSYM UUID:\\s*([^\\s]+)") + + // Slide Address: 0x0000000100000000 + static let slideAddress = try! Regex("Slide Address:\\s*([^\\s]+)") + + // CPU Type: arm64 + static let cpuType = try! Regex("CPU Type:\\s*([^\\s]+)") +} + +struct UmengParser: CrashParser { + static func match(_ content: String) -> Bool { + return content.contains("dSYM UUID") && content.contains("Slide Address") + } + + func parse(_ content: String) -> Crash { + var crash = Crash(content) + let regexMap: RegexHelper.KeyValueRegexMap = [ + \.appName: .binaryImage, + \.uuid: .dsymUUID, + \.arch: .cpuType, + ] + RegexHelper.parseBaseInfo(content, crash: &crash, map: regexMap) + + guard let appName = crash.appName, let frameRegex = Regex.frame(appName) else { + return crash + } + + let loadAddress = Regex.slideAddress.value(in: content) + + var backtrace: [Frame] = [] + frameRegex.matches(in: content)?.forEach({ match in + if let captures = match.captures { + crash.appBacktraceRanges.append(match.range) + let frame = Frame(raw: captures[0], + index: captures[1], + image: captures[2], + address: captures[3], + symbol: captures[4]) + backtrace.append(frame) + } + }) + + let binary = Binary(name: appName, + uuid: crash.uuid, + arch: crash.arch, + loadAddress: loadAddress, + path: "") + binary.backtrace = backtrace + crash.binaryImages.append(binary) + + return crash + } +} + + +extension Crash { + static func parser(for content: String) -> CrashParser { + if UmengParser.match(content) { + return UmengParser() + } + if CPUUsageParser.match(content) { + return CPUUsageParser() + } + if FabricParser.match(content) { + return FabricParser() + } + return AppleParser() + } + + static func parse(_ content: String) -> Crash { + let parser = self.parser(for: content) + return parser.parse(content) + } +} diff --git a/SYM/Regex.swift b/SYM/Regex.swift index 15dd924..ac8a9c9 100644 --- a/SYM/Regex.swift +++ b/SYM/Regex.swift @@ -22,6 +22,75 @@ import Foundation +struct Regex { + class Match { + let string: String + fileprivate let result: NSTextCheckingResult + + init(_ string: String, result: NSTextCheckingResult) { + self.string = string + self.result = result + } + + lazy var captures: [String]? = { + let number = result.numberOfRanges + guard number >= 1 else { + return nil + } + + var groups = [String]() + for index in 0.. Match? { + let range = NSRange(string.startIndex..., in: string) + guard let result = _regex.firstMatch(in: string, options: options, range: range) else { + return nil + } + + return Match(string, result: result) + } + + func matches(in string: String, options: MatchingOptions = []) -> [Match]? { + let range = NSRange(string.startIndex..., in: string) + let matches = _regex.matches(in: string, options: options, range: range) + if matches.count == 0 { + return nil + } + + return matches.map { Match(string, result: $0) } + } +} + + + + + + struct RE { let regex: NSRegularExpression diff --git a/SYM/Symbolicate.swift b/SYM/Symbolicator.swift similarity index 67% rename from SYM/Symbolicate.swift rename to SYM/Symbolicator.swift index 02b0dd4..b8705de 100644 --- a/SYM/Symbolicate.swift +++ b/SYM/Symbolicator.swift @@ -1,6 +1,6 @@ // The MIT License (MIT) // -// Copyright (c) 2017 - present zqqf16 +// Copyright (c) 2022 zqqf16 // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal @@ -84,3 +84,51 @@ extension SubProcess { return process.output } } + + +protocol Symbolicator { + func symbolicate(crash: Crash, dsymPaths: [String]?) -> String +} + +struct SymbolicateCrash: Symbolicator { + func symbolicate(crash: Crash, dsymPaths: [String]?) -> String { + if let content = SubProcess.symbolicatecrash(crash: crash.content, dsyms: dsymPaths), content.count > 0 { + return content + } + + return crash.content + } +} + +struct Atos: Symbolicator { + func symbolicate(crash: Crash, dsymPaths: [String]?) -> String { + //TODO: Only support the first binary image now + + guard let image = crash.binaryImages.first, image.isValid else { + return crash.content + } + + image.fix() + guard let result = SubProcess.atos(image, dsym: dsymPaths?.first) else { + return crash.content + } + + var newContent = crash.content + for frame in image.backtrace! { + if let symbol = result[frame.address] { + var newFrame = frame + newFrame.symbol = symbol + newContent = newContent.replacingOccurrences(of: frame.raw, with: newFrame.description) + } + } + + return newContent + } +} + +extension Crash { + func symbolicate(dsymPaths: [String]?) -> String { + let symbolicator: Symbolicator = self.symbolicateMethod == .atos ? Atos() : SymbolicateCrash() + return symbolicator.symbolicate(crash: self, dsymPaths: dsymPaths) + } +} diff --git a/SYM/UI/ContentViewController.swift b/SYM/UI/ContentViewController.swift index 892ecd3..55747a3 100644 --- a/SYM/UI/ContentViewController.swift +++ b/SYM/UI/ContentViewController.swift @@ -80,7 +80,7 @@ class ContentViewController: NSViewController { self.textView.layoutManager?.allowsNonContiguousLayout = false } - private func infoString(fromCrash crash: CrashInfo) -> String { + private func infoString(fromCrash crash: Crash) -> String { var info = "" var divider = "" if let device = crash.device { @@ -98,24 +98,24 @@ class ContentViewController: NSViewController { return info } - func update(crashInfo: CrashInfo?) { + func update(crashInfo: Crash?) { self.updateHighlighting(crashInfo) self.updateSummary(crashInfo) } - private func updateHighlighting(_ crashInfo: CrashInfo?) { + private func updateHighlighting(_ crashInfo: Crash?) { guard let textStorage = self.textView.textStorage else { return } self.textView.font = self.font textStorage.beginEditing() textStorage.font = self.font - let ranges = crashInfo?.appBacktraceRanges() ?? [] + let ranges = crashInfo?.appBacktraceRanges ?? [] textStorage.processHighlighting(ranges) textStorage.endEditing() } - private func updateSummary(_ crashInfo: CrashInfo?) { + private func updateSummary(_ crashInfo: Crash?) { guard let info = crashInfo else { self.toggleBottomBar(false) return @@ -153,7 +153,7 @@ extension ContentViewController: NSSplitViewDelegate { extension ContentViewController { @IBAction func scrollToTarget(_ sender: AnyObject?) { - guard let crashInfo = self.document?.crashInfo, let range = crashInfo.crashedThreadRange() else { + guard let crashInfo = self.document?.crashInfo, let range = crashInfo.crashedThreadRange else { return } diff --git a/SYMTests/AppleDemo.ips b/SYMTests/AppleDemo.ips index 53148a8..346c864 100644 --- a/SYMTests/AppleDemo.ips +++ b/SYMTests/AppleDemo.ips @@ -314,7 +314,7 @@ Thread 0 crashed with ARM Thread State (64-bit): sp: 0x000000016fd8eae0 pc: 0x0000000100125780 cpsr: 0xa0000000 Binary Images: -0x100070000 - 0x101607fff demo arm64 <42fd89f730be3ac5a40a4c1a99438dfb> /var/containers/Bundle/Application/BC57CB8A-281B-4D12-A9F2-73A7463B79BE/demo.app/demo +0x100070000 - 0x101607fff demo arm64 <42fd89f730be3ac5a40a4c1a99438dfb> /var/containers/Bundle/Application/BC57CB8A-281B-4D12-A9F2-73A7463B79BE/demo.app/demo 0x101bb8000 - 0x101bc3fff libswiftAVFoundation.dylib arm64 /var/containers/Bundle/Application/BC57CB8A-281B-4D12-A9F2-73A7463B79BE/demo.app/Frameworks/libswiftAVFoundation.dylib 0x101bd4000 - 0x101bdffff libswiftCoreAudio.dylib arm64 /var/containers/Bundle/Application/BC57CB8A-281B-4D12-A9F2-73A7463B79BE/demo.app/Frameworks/libswiftCoreAudio.dylib 0x101bf4000 - 0x101bfffff libswiftCoreData.dylib arm64 <73e38baa0e40324fa6358cfb2a0b1cc5> /var/containers/Bundle/Application/BC57CB8A-281B-4D12-A9F2-73A7463B79BE/demo.app/Frameworks/libswiftCoreData.dylib diff --git a/SYMTests/AppleJson.ips b/SYMTests/AppleJson.ips new file mode 100644 index 0000000..1439e22 --- /dev/null +++ b/SYMTests/AppleJson.ips @@ -0,0 +1,245 @@ +{"app_name":"Demo","timestamp":"2022-03-16 11:45:14.00 +0800","app_version":"1.0","slice_uuid":"db2fd9ec-8dd1-3021-98df-93db16cf4863","build_version":"4","platform":2,"bundleID":"im.zorro.demo","share_with_app_devs":1,"is_first_party":0,"bug_type":"309","os_version":"iPhone OS 15.2.1 (19C63)","incident_id":"4904A8AF-6173-4A1B-B354-0A9C4FB1F673","name":"Demo"} +{ + "uptime" : 220000, + "procLaunch" : "2022-03-16 11:45:12.3458 +0800", + "procRole" : "Foreground", + "version" : 2, + "userID" : 501, + "deployVersion" : 210, + "modelCode" : "iPhone13,4", + "procStartAbsTime" : 5347525806957, + "coalitionID" : 2211, + "osVersion" : { + "isEmbedded" : true, + "train" : "iPhone OS 15.2.1", + "releaseType" : "User", + "build" : "19C63" + }, + "captureTime" : "2022-03-16 11:45:14.2121 +0800", + "incident" : "4904A8AF-6173-4A1B-B354-0A9C4FB1F673", + "bug_type" : "309", + "pid" : 6209, + "procExitAbsTime" : 5347570556853, + "cpuType" : "ARM-64", + "procName" : "Demo", + "procPath" : "\/private\/var\/containers\/Bundle\/Application\/CA1090A6-A0E0-41FF-A314-9FE69476EEAB\/Demo.app\/Demo", + "bundleInfo" : {"CFBundleShortVersionString":"1.0","CFBundleVersion":"4","CFBundleIdentifier":"im.zorro.demo"}, + "storeInfo" : {"deviceIdentifierForVendor":"DB82FC95-D5E0-4EF5-A07F-A2998544E5C7","thirdParty":true}, + "parentProc" : "launchd", + "parentPid" : 1, + "coalitionName" : "im.zorro.demo", + "crashReporterKey" : "d7abd7c61291b3e610c5095b565fd5fdc5e83b6b", + "basebandVersion" : "2.23.02", + "isCorpse" : 1, + "exception" : {"codes":"0x0000000000000000, 0x0000000000000000","rawCodes":[0,0],"type":"EXC_CRASH","signal":"SIGABRT"}, + "termination" : {"flags":518,"code":0,"namespace":"TCC","details":["This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data."]}, + "faultingThread" : 1, + "threads" : [{"id":1809767,"queue":"com.apple.main-thread","frames":[{"imageOffset":102724,"symbol":"NSKeyValueWillChangeWithPerThreadPendingNotifications","symbolLocation":200,"imageIndex":0},{"imageOffset":102648,"symbol":"NSKeyValueWillChangeWithPerThreadPendingNotifications","symbolLocation":124,"imageIndex":0},{"imageOffset":334592,"symbol":"CA::Layer::begin_change(CA::Transaction*, unsigned int, objc_object*, objc_object*&)","symbolLocation":252,"imageIndex":1},{"imageOffset":347900,"symbol":"CA::Layer::setter(unsigned int, _CAValueType, void const*)","symbolLocation":352,"imageIndex":1},{"imageOffset":408732,"symbol":"-[CALayer setBackgroundColor:]","symbolLocation":56,"imageIndex":1},{"imageOffset":4472144,"symbol":"-[UIView _setBackgroundCGColor:withSystemColorName:]","symbolLocation":1224,"imageIndex":2},{"imageOffset":4783908,"symbol":"-[UIView(Hierarchy) _setBackgroundColor:]","symbolLocation":564,"imageIndex":2},{"imageOffset":7042564,"symbol":"-[_UIParallaxDimmingView initWithFrame:overrideDimmingColor:]","symbolLocation":292,"imageIndex":2},{"imageOffset":1216640,"symbol":"-[_UIParallaxDimmingView initViewWrappingView:withLeftBorder:shouldReverseLayoutDirection:]","symbolLocation":68,"imageIndex":2},{"imageOffset":3663740,"symbol":"__53-[_UINavigationParallaxTransition animateTransition:]_block_invoke_2","symbolLocation":1088,"imageIndex":2},{"imageOffset":1522332,"symbol":"+[UIView(Animation) performWithoutAnimation:]","symbolLocation":104,"imageIndex":2},{"imageOffset":2844536,"symbol":"__53-[_UINavigationParallaxTransition animateTransition:]_block_invoke","symbolLocation":260,"imageIndex":2},{"imageOffset":3711104,"symbol":"+[UIView _performBlockDelayingTriggeringResponderEvents:forScene:]","symbolLocation":252,"imageIndex":2},{"imageOffset":3747372,"symbol":"-[_UINavigationParallaxTransition animateTransition:]","symbolLocation":1092,"imageIndex":2},{"imageOffset":4956688,"symbol":"___UIViewControllerTransitioningRunCustomTransition_block_invoke_2","symbolLocation":76,"imageIndex":2},{"imageOffset":3035412,"symbol":"+[UIKeyboardSceneDelegate _pinInputViewsForKeyboardSceneDelegate:onBehalfOfResponder:duringBlock:]","symbolLocation":116,"imageIndex":2},{"imageOffset":10168092,"symbol":"___UIViewControllerTransitioningRunCustomTransition_block_invoke.663","symbolLocation":204,"imageIndex":2},{"imageOffset":4100828,"symbol":"+[UIView(Animation) _setAlongsideAnimations:toRunByEndOfBlock:]","symbolLocation":204,"imageIndex":2},{"imageOffset":2610440,"symbol":"_UIViewControllerTransitioningRunCustomTransition","symbolLocation":628,"imageIndex":2},{"imageOffset":3653448,"symbol":"-[UINavigationController _startCustomTransition:]","symbolLocation":3600,"imageIndex":2},{"imageOffset":5086804,"symbol":"-[UINavigationController _startDeferredTransitionIfNeeded:]","symbolLocation":696,"imageIndex":2},{"imageOffset":3862180,"symbol":"-[UINavigationController __viewWillLayoutSubviews]","symbolLocation":168,"imageIndex":2},{"imageOffset":2970788,"symbol":"-[UILayoutContainerView layoutSubviews]","symbolLocation":228,"imageIndex":2},{"imageOffset":1628056,"symbol":"-[UIView(CALayerDelegate) layoutSublayersOfLayer:]","symbolLocation":2620,"imageIndex":2},{"imageOffset":262456,"symbol":"CA::Layer::layout_if_needed(CA::Transaction*)","symbolLocation":536,"imageIndex":1},{"imageOffset":207192,"symbol":"CA::Layer::layout_and_display_if_needed(CA::Transaction*)","symbolLocation":144,"imageIndex":1},{"imageOffset":290688,"symbol":"CA::Context::commit_transaction(CA::Transaction*, double, double*)","symbolLocation":524,"imageIndex":1},{"imageOffset":325748,"symbol":"CA::Transaction::commit()","symbolLocation":680,"imageIndex":1},{"imageOffset":204988,"symbol":"CA::Transaction::flush_as_runloop_observer(bool)","symbolLocation":100,"imageIndex":1},{"imageOffset":5520576,"symbol":"_UIApplicationFlushCATransaction","symbolLocation":76,"imageIndex":2},{"imageOffset":8250876,"symbol":"_UIUpdateSequenceRun","symbolLocation":84,"imageIndex":2},{"imageOffset":15050160,"symbol":"schedulerStepScheduledMainSection","symbolLocation":144,"imageIndex":2},{"imageOffset":15047584,"symbol":"runloopSourceCallback","symbolLocation":60,"imageIndex":2},{"imageOffset":766160,"symbol":"__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__","symbolLocation":28,"imageIndex":3},{"imageOffset":834960,"symbol":"__CFRunLoopDoSource0","symbolLocation":208,"imageIndex":3},{"imageOffset":24728,"symbol":"__CFRunLoopDoSources0","symbolLocation":268,"imageIndex":3},{"imageOffset":47268,"symbol":"__CFRunLoopRun","symbolLocation":820,"imageIndex":3},{"imageOffset":128104,"symbol":"CFRunLoopRunSpecific","symbolLocation":600,"imageIndex":3},{"imageOffset":5004,"symbol":"GSEventRunModal","symbolLocation":164,"imageIndex":4},{"imageOffset":5349512,"symbol":"-[UIApplication _run]","symbolLocation":1100,"imageIndex":2},{"imageOffset":2722136,"symbol":"UIApplicationMain","symbolLocation":2092,"imageIndex":2},{"imageOffset":200608,"symbol":"UIApplicationMain(_:_:_:_:)","symbolLocation":104,"imageIndex":5},{"imageOffset":804020,"imageIndex":6},{"imageOffset":803884,"imageIndex":6},{"imageOffset":804172,"imageIndex":6},{"imageOffset":105124,"symbol":"start","symbolLocation":520,"imageIndex":7}]},{"triggered":true,"id":1809769,"threadState":{"x":[{"value":11},{"value":0},{"value":4365255752},{"value":25},{"value":4365255796},{"value":0},{"value":68116944433316130},{"value":62},{"value":32},{"value":16940962263036854446},{"value":72057598332895232},{"value":1809769},{"value":68763556336},{"value":0},{"value":2199023325184},{"value":144115192639262680},{"value":521},{"value":8712424848},{"value":0},{"value":0},{"value":4365255796},{"value":25},{"value":4365255752},{"value":0},{"value":11},{"value":10804300352},{"value":0},{"value":0},{"value":0}],"flavor":"ARM_THREAD_STATE64","lr":{"value":7384495544},"cpsr":{"value":1073745920},"fp":{"value":6157231712},"sp":{"value":6157231648},"esr":{"value":1442840704,"description":" Address size fault"},"pc":{"value":7384485112,"matchesCrashFrame":1},"far":{"value":4343316560}},"queue":"com.apple.root.default-qos","frames":[{"imageOffset":175352,"symbol":"__abort_with_payload","symbolLocation":8,"imageIndex":8},{"imageOffset":185784,"symbol":"abort_with_payload_wrapper_internal","symbolLocation":104,"imageIndex":8},{"imageOffset":185836,"symbol":"abort_with_payload","symbolLocation":16,"imageIndex":8},{"imageOffset":29392,"symbol":"__TCC_CRASHING_DUE_TO_PRIVACY_VIOLATION__","symbolLocation":172,"imageIndex":9},{"imageOffset":31404,"symbol":"__TCCAccessRequest_block_invoke.231","symbolLocation":600,"imageIndex":9},{"imageOffset":18640,"symbol":"__tccd_send_message_block_invoke","symbolLocation":624,"imageIndex":9},{"imageOffset":115500,"symbol":"_xpc_connection_reply_callout","symbolLocation":116,"imageIndex":10},{"imageOffset":63580,"symbol":"_xpc_connection_call_reply_async","symbolLocation":88,"imageIndex":10},{"imageOffset":18160,"symbol":"_dispatch_client_callout3","symbolLocation":20,"imageIndex":11},{"imageOffset":138968,"symbol":"_dispatch_mach_msg_async_reply_invoke","symbolLocation":348,"imageIndex":11},{"imageOffset":93040,"symbol":"_dispatch_kevent_worker_thread","symbolLocation":1316,"imageIndex":11},{"imageOffset":4396,"symbol":"_pthread_wqthread","symbolLocation":344,"imageIndex":12},{"imageOffset":3732,"symbol":"start_wqthread","symbolLocation":8,"imageIndex":12}]},{"id":1809770,"frames":[{"imageOffset":3724,"symbol":"start_wqthread","symbolLocation":0,"imageIndex":12}]},{"id":1809771,"frames":[{"imageOffset":3724,"symbol":"start_wqthread","symbolLocation":0,"imageIndex":12}]},{"id":1809772,"frames":[{"imageOffset":3724,"symbol":"start_wqthread","symbolLocation":0,"imageIndex":12}]},{"id":1809773,"name":"com.apple.uikit.eventfetch-thread","frames":[{"imageOffset":5380,"symbol":"mach_msg_trap","symbolLocation":8,"imageIndex":8},{"imageOffset":7068,"symbol":"mach_msg","symbolLocation":76,"imageIndex":8},{"imageOffset":30520,"symbol":"__CFRunLoopServiceMachPort","symbolLocation":372,"imageIndex":3},{"imageOffset":47660,"symbol":"__CFRunLoopRun","symbolLocation":1212,"imageIndex":3},{"imageOffset":128104,"symbol":"CFRunLoopRunSpecific","symbolLocation":600,"imageIndex":3},{"imageOffset":101524,"symbol":"-[NSRunLoop(NSRunLoop) runMode:beforeDate:]","symbolLocation":236,"imageIndex":0},{"imageOffset":368072,"symbol":"-[NSRunLoop(NSRunLoop) runUntilDate:]","symbolLocation":92,"imageIndex":0},{"imageOffset":4796976,"symbol":"-[UIEventFetcher threadMain]","symbolLocation":524,"imageIndex":2},{"imageOffset":427020,"symbol":"__NSThread__start__","symbolLocation":808,"imageIndex":0},{"imageOffset":6564,"symbol":"_pthread_start","symbolLocation":148,"imageIndex":12},{"imageOffset":3744,"symbol":"thread_start","symbolLocation":8,"imageIndex":12}]},{"id":1809774,"frames":[{"imageOffset":3724,"symbol":"start_wqthread","symbolLocation":0,"imageIndex":12}]},{"id":1809775,"frames":[{"imageOffset":3724,"symbol":"start_wqthread","symbolLocation":0,"imageIndex":12}]}], + "usedImages" : [ + { + "source" : "P", + "arch" : "arm64e", + "base" : 6479343616, + "size" : 3174400, + "uuid" : "9618b2f2-a4c2-3e07-b7ee-d8d9e1bdeaec", + "path" : "\/System\/Library\/Frameworks\/Foundation.framework\/Foundation", + "name" : "Foundation" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 6518353920, + "size" : 3043328, + "uuid" : "0594369d-11dd-39d9-a981-74984f490c0c", + "path" : "\/System\/Library\/Frameworks\/QuartzCore.framework\/QuartzCore", + "name" : "QuartzCore" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 6492467200, + "size" : 25735168, + "uuid" : "cd7f7ba2-a2c6-3727-aff6-9baab60cc6ab", + "path" : "\/System\/Library\/PrivateFrameworks\/UIKitCore.framework\/UIKitCore", + "name" : "UIKitCore" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 6454034432, + "size" : 4538368, + "uuid" : "16faa70c-278c-3561-859e-cec407c2dc7c", + "path" : "\/System\/Library\/Frameworks\/CoreFoundation.framework\/CoreFoundation", + "name" : "CoreFoundation" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 6919245824, + "size" : 36864, + "uuid" : "064a1bb6-0e41-3ad2-a402-fb563fc141f5", + "path" : "\/System\/Library\/PrivateFrameworks\/GraphicsServices.framework\/GraphicsServices", + "name" : "GraphicsServices" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 6849085440, + "size" : 446464, + "uuid" : "101fe096-8d8b-33d5-913b-c31c5d26c5f2", + "path" : "\/usr\/lib\/swift\/libswiftUIKit.dylib", + "name" : "libswiftUIKit.dylib" + }, + { + "source" : "P", + "arch" : "arm64", + "base" : 4310204416, + "size" : 16400384, + "uuid" : "db2fd9ec-8dd1-3021-98df-93db16cf4863", + "path" : "\/private\/var\/containers\/Bundle\/Application\/CA1090A6-A0E0-41FF-A314-9FE69476EEAB\/Demo.app\/Demo", + "name" : "Demo" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 4334862336, + "size" : 360448, + "uuid" : "f94ccc7a-6d1a-33c7-997f-f611a6caa7e0", + "path" : "\/usr\/lib\/dyld", + "name" : "dyld" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 7384309760, + "size" : 212992, + "uuid" : "eb3e47f3-3953-3583-9fee-fb6cff8a8d7a", + "path" : "\/usr\/lib\/system\/libsystem_kernel.dylib", + "name" : "libsystem_kernel.dylib" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 8203038720, + "size" : 73728, + "uuid" : "5b4c4ea9-8763-38ec-b447-9b9169c542df", + "path" : "\/System\/Library\/PrivateFrameworks\/TCC.framework\/TCC", + "name" : "TCC" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 8353550336, + "size" : 229376, + "uuid" : "4a0071bc-1b1f-3cbb-9602-72663b72e875", + "path" : "\/usr\/lib\/system\/libxpc.dylib", + "name" : "libxpc.dylib" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 6450917376, + "size" : 290816, + "uuid" : "edd169e1-d0db-3808-a19e-99c1cd5a1c4c", + "path" : "\/usr\/lib\/system\/libdispatch.dylib", + "name" : "libdispatch.dylib" + }, + { + "source" : "P", + "arch" : "arm64e", + "base" : 8353439744, + "size" : 49152, + "uuid" : "c5c27e9d-9557-39c9-b9c6-5f6e7323ee1c", + "path" : "\/usr\/lib\/system\/libsystem_pthread.dylib", + "name" : "libsystem_pthread.dylib" + } +], + "sharedCache" : { + "base" : 6450593792, + "size" : 2516353024, + "uuid" : "0dfdef8d-9c37-3d78-a984-2560ceae74be" +}, + "vmSummary" : "ReadOnly portion of Libraries: Total=791.6M resident=0K(0%) swapped_out_or_unallocated=791.6M(100%)\nWritable regions: Total=588.5M written=0K(0%) resident=0K(0%) swapped_out=0K(0%) unallocated=588.5M(100%)\n\n VIRTUAL REGION \nREGION TYPE SIZE COUNT (non-coalesced) \n=========== ======= ======= \nActivity Tracing 256K 1 \nColorSync 32K 2 \nCoreAnimation 80K 3 \nFoundation 16K 1 \nKernel Alloc Once 32K 1 \nMALLOC 580.5M 38 \nMALLOC guard page 128K 8 \nSTACK GUARD 128K 8 \nStack 4816K 8 \nVM_ALLOCATE 160K 2 \n__AUTH 4246K 415 \n__AUTH_CONST 22.8M 553 \n__DATA 18.1M 543 \n__DATA_CONST 23.2M 560 \n__DATA_DIRTY 2577K 459 \n__FONT_DATA 4K 1 \n__LINKEDIT 189.2M 4 \n__OBJC_CONST 5728K 374 \n__OBJC_RO 91.1M 1 \n__OBJC_RW 3488K 1 \n__TEXT 602.5M 568 \n__UNICODE 588K 1 \ndyld private memory 1024K 1 \nmapped file 112.8M 15 \nshared memory 48K 3 \n=========== ======= ======= \nTOTAL 1.6G 3571 \n", + "legacyInfo" : { + "threadTriggered" : { + "queue" : "com.apple.root.default-qos" + } +}, + "trialInfo" : { + "rollouts" : [ + { + "rolloutId" : "607844aa04477260f58a8077", + "factorPackIds" : { + "SIRI_MORPHUN_ASSETS" : "6103050cbfe6dc472e1c982a" + }, + "deploymentId" : 240000066 + }, + { + "rolloutId" : "60da5e84ab0ca017dace9abf", + "factorPackIds" : { + + }, + "deploymentId" : 240000008 + }, + { + "rolloutId" : "602ad4dac86151000cf27e46", + "factorPackIds" : { + "SIRI_DICTATION_ASSETS" : "61fb0e59c773c43cde3bb80d" + }, + "deploymentId" : 240000303 + }, + { + "rolloutId" : "60509d56f2766876dee21c11", + "factorPackIds" : { + "SIRI_UNDERSTANDING_ASR_ASSISTANT" : "61fb11b9c773c43cde3bb81d", + "SIRI_UNDERSTANDING_NL" : "619444caea7ed64a7812a483", + "SIRI_UNDERSTANDING_MORPHUN" : "610072c2f6774779a7ced014", + "SIRI_EXPERIENCE_CAM" : "619444d02171a2330e561f65" + }, + "deploymentId" : 240000605 + }, + { + "rolloutId" : "5ffde50ce2aacd000d47a95f", + "factorPackIds" : { + + }, + "deploymentId" : 240000119 + }, + { + "rolloutId" : "60356660bbe37970735c5624", + "factorPackIds" : { + + }, + "deploymentId" : 240000027 + }, + { + "rolloutId" : "601d9415f79519000ccd4b69", + "factorPackIds" : { + "SIRI_TEXT_TO_SPEECH" : "621d4d0f680160486b9e1c98" + }, + "deploymentId" : 240000416 + }, + { + "rolloutId" : "5fc94383418129005b4e9ae0", + "factorPackIds" : { + "SIRI_UNDERSTANDING_ASR_HAMMER" : "62265a330683fd69cdcf0e23" + }, + "deploymentId" : 240000274 + }, + { + "rolloutId" : "602c34a3046661000c9ebdde", + "factorPackIds" : { + "SIRI_UNDERSTANDING_NL_OVERRIDES" : "622f4833d14f83424df6686d" + }, + "deploymentId" : 240000642 + } + ], + "experiments" : [ + { + "treatmentId" : "933be31a-5eb4-4343-ba7a-0c0acffdbcc0", + "experimentId" : "61a93b78bce7b01d4c40654f", + "deploymentId" : 400000007 + } + ] +} +} diff --git a/SYMTests/ConvertorTests.swift b/SYMTests/ConvertorTests.swift new file mode 100644 index 0000000..8ec027a --- /dev/null +++ b/SYMTests/ConvertorTests.swift @@ -0,0 +1,52 @@ +// +// ConvertorTests.swift +// SYMTests +// +// Created by 张全全 on 2022/3/18. +// Copyright © 2022 zqqf16. All rights reserved. +// + +import XCTest +@testable import SYM + +class ConvertorTests: XCTestCase { + + override func setUpWithError() throws { + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDownWithError() throws { + // Put teardown code here. This method is called after the invocation of each test method in the class. + } + + func loadFile() -> String { + let bundle = Bundle(for: type(of: self)) + let path = bundle.path(forResource: "AppleJson", ofType: "ips")! + return try! String(contentsOfFile: path) + } + + func testMatching() throws { + let content = self.loadFile() + XCTAssertTrue(AppleJsonConvertor.match(content)) + } + + func testConvertor() throws { + let content = self.loadFile() + let convertor = AppleJsonConvertor() + let converted = convertor.convert(content) + + let bundle = Bundle(for: type(of: self)) + let path = bundle.path(forResource: "AppleConverted", ofType: "log")! + let target = try! String(contentsOfFile: path) + + //XCTAssertEqual(converted, target) + } + + func testPerformanceExample() throws { + // This is an example of a performance test case. + self.measure { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/SYMTests/ParserTests.swift b/SYMTests/ParserTests.swift index fcd4e06..2311e34 100644 --- a/SYMTests/ParserTests.swift +++ b/SYMTests/ParserTests.swift @@ -58,21 +58,45 @@ class ParserTests: XCTestCase { func testAppleParser() { let content = self.crashContent(fromFile: "AppleDemo", ofType: "ips") - let crash = CrashInfo.parse(content) + let parser = AppleParser() + let crash: Crash! = parser.parse(content) + + XCTAssertNotNil(crash) + + XCTAssertEqual(crash.uuid, "42fd89f730be3ac5a40a4c1a99438dfb".uuidFormat()) XCTAssertEqual(crash.appName, "demo") - XCTAssertEqual(crash.device, "iPhone9,2") - XCTAssertEqual(crash.uuid, "42fd89f730be3ac5a40a4c1a99438dfb".uuidFormat()) + XCTAssertEqual(crash.arch, "arm64") + XCTAssertEqual(crash.osVersion, "iPhone OS 10.1.1 (14B100)") + XCTAssertEqual(crash.appVersion, "3.5.5.2 (3.5.5)") + XCTAssertEqual(crash.bundleID, "im.zorro.demo") + XCTAssertEqual(crash.symbolicateMethod, .symbolicatecrash) + + let embedded = crash.embeddedBinaries + XCTAssertEqual(embedded.count, 16) + + XCTAssertTrue(crash.appBacktraceRanges.count > 0) + XCTAssertNotNil(crash.crashedThreadRange) } func testUmengParser() { let content = self.crashContent(fromFile: "UmengDemo", ofType: "crash") - let crash = CrashInfo.parse(content) + let parser = UmengParser() + let crash: Crash! = parser.parse(content) + XCTAssertNotNil(crash) + XCTAssertEqual(crash.appName, "DemoApp") XCTAssertNil(crash.device) - XCTAssertEqual(crash.uuid, "E5B0A378-6816-3D90-86FD-2AEF15894A85") + + XCTAssertTrue(crash.appBacktraceRanges.count > 0) + + let binary: Binary! = crash.binaryImages.first + XCTAssertNotNil(binary) + XCTAssertEqual(binary.name, "DemoApp") + XCTAssertEqual(binary.loadAddress, "0x0000000100000000") + } }