├── .github ├── FUNDING.yml └── workflows │ └── ci.yml ├── .gitignore ├── .spi.yml ├── .swiftpm └── xcode │ └── xcshareddata │ └── xcschemes │ └── Meter.xcscheme ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Package.swift ├── README.md ├── Sources ├── BinaryImage │ ├── BinaryImage.h │ └── BinaryImage.m └── Meter │ ├── CallStackTree.swift │ ├── DiagnosticPayload.swift │ ├── DlfcnSymbolicator.swift │ ├── ExceptionInfo.swift │ └── Symbolicator.swift └── Tests ├── BinaryImageTests └── BinaryImageTests.swift └── MeterTests ├── DiagnosticTests.swift ├── Resources ├── exception.log ├── ios_uncaugh_exception_payload.json ├── real_report.json ├── xcode_simulated.crash ├── xcode_simulated.json ├── xcode_simulated_macOS_12.json └── xcode_simulated_macOS_13.json └── SymbolicationTests.swift /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [mattmassicotte] 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - 'README.md' 9 | - 'CODE_OF_CONDUCT.md' 10 | - '.editorconfig' 11 | - '.spi.yml' 12 | pull_request: 13 | branches: 14 | - main 15 | 16 | env: 17 | DEVELOPER_DIR: /Applications/Xcode_15.2.app/Contents/Developer 18 | 19 | jobs: 20 | test-13: 21 | name: Test macOS 13 22 | runs-on: macOS-13 23 | strategy: 24 | matrix: 25 | destination: 26 | - "platform=macOS" 27 | # the test copies files, and that fails to built for other platforms for some reason... 28 | # - "platform=iOS Simulator,name=iPhone 12" 29 | # - "platform=watchOS Simulator,name=Apple Watch Series 6 (40mm)" 30 | 31 | steps: 32 | - uses: actions/checkout@v4 33 | - name: Test platform ${{ matrix.destination }} 34 | run: set -o pipefail && xcodebuild -scheme Meter -destination "${{ matrix.destination }}" test | xcbeautify 35 | 36 | test-14: 37 | name: Test macOS 14 38 | runs-on: macOS-14 39 | strategy: 40 | matrix: 41 | destination: 42 | - "platform=macOS" 43 | 44 | steps: 45 | - uses: actions/checkout@v4 46 | - name: Test platform ${{ matrix.destination }} 47 | run: set -o pipefail && xcodebuild -scheme Meter -destination "${{ matrix.destination }}" test | xcbeautify 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.build 3 | /Packages 4 | xcuserdata/ 5 | DerivedData/ 6 | .swiftpm/configuration/registries.json 7 | .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata 8 | .netrc 9 | -------------------------------------------------------------------------------- /.spi.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | builder: 3 | configs: 4 | - documentation_targets: [Meter] 5 | -------------------------------------------------------------------------------- /.swiftpm/xcode/xcshareddata/xcschemes/Meter.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 43 | 49 | 50 | 51 | 57 | 63 | 64 | 65 | 71 | 77 | 78 | 79 | 85 | 91 | 92 | 93 | 99 | 105 | 106 | 107 | 113 | 119 | 120 | 121 | 122 | 123 | 130 | 131 | 137 | 138 | 139 | 140 | 142 | 148 | 149 | 150 | 152 | 158 | 159 | 160 | 161 | 162 | 172 | 173 | 179 | 180 | 186 | 187 | 188 | 189 | 191 | 192 | 195 | 196 | 197 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at support@chimehq.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020, Chime 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version: 5.9 2 | 3 | import PackageDescription 4 | 5 | let package = Package( 6 | name: "Meter", 7 | platforms: [ 8 | .macOS(.v10_13), 9 | .iOS(.v12), 10 | .tvOS(.v12), 11 | .watchOS(.v4), 12 | .visionOS(.v1), 13 | ], 14 | products: [ 15 | .library(name: "Meter", targets: ["Meter"]), 16 | ], 17 | dependencies: [], 18 | targets: [ 19 | .target(name: "BinaryImage", publicHeadersPath: "."), 20 | .target(name: "Meter", dependencies: ["BinaryImage"]), 21 | .testTarget( 22 | name: "MeterTests", 23 | dependencies: ["Meter"], 24 | resources: [ 25 | .copy("Resources"), 26 | ] 27 | ), 28 | .testTarget( 29 | name: "BinaryImageTests", 30 | dependencies: ["BinaryImage"] 31 | ), 32 | ] 33 | ) 34 | 35 | let swiftSettings: [SwiftSetting] = [ 36 | .enableExperimentalFeature("StrictConcurrency") 37 | ] 38 | 39 | for target in package.targets { 40 | var settings = target.swiftSettings ?? [] 41 | settings.append(contentsOf: swiftSettings) 42 | target.swiftSettings = settings 43 | } 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | [![Build Status][build status badge]][build status] 4 | [![Platforms][platforms badge]][platforms] 5 | [![Documentation][documentation badge]][documentation] 6 | [![Matrix][matrix badge]][matrix] 7 | 8 |
9 | 10 | # Meter 11 | 12 | Meter is a companion library to [MetricKit](https://developer.apple.com/documentation/metrickit). It aims to provide the following capabilities: 13 | 14 | - API for `MXCallStackTree` 15 | - Types for `MXDiagnostic` emulation and coding 16 | - `MXMetricManager`-like interface for unsupported platforms 17 | - On-device symbolication 18 | - Account for MetricKit inconsistencies across platforms and types 19 | - Support for custom exception reporting 20 | 21 | If you're also looking for a way to transmit MetricKit data to your server, have a look at [MeterReporter](https://github.com/ChimeHQ/MeterReporter). It uses Meter under the hood, and takes care of the details. 22 | 23 | ## Integration 24 | 25 | ```swift 26 | dependencies: [ 27 | .package(url: "https://github.com/ChimeHQ/Meter") 28 | ] 29 | ``` 30 | 31 | ## Expanded API 32 | 33 | The MetricKit API for crash reporting is unwieldy. In particular, `MXCallStackTree` lacks any kind of interface for interacting with its structure. Meter includes some classes that make it easier to work with. In addition to providing an API for `MXCallStackTree`, Meter includes types to emulate and parse MetricKit diagnostics. 34 | 35 | ```swift 36 | let data = mxTree.jsonRepresentation() 37 | let tree = try CallStackTree.from(data: data) 38 | 39 | for frame in tree.callStacks[0].frames { 40 | print("\(frame.address) \(frame.binaryName) \(frame.binaryUUID)") 41 | } 42 | ``` 43 | 44 | ## Custom Exceptions 45 | 46 | As of iOS 17, macOS 14, visionOS 1.0, MetricKit does capture uncaught [NSExceptions](https://developer.apple.com/documentation/metrickit/mxcrashdiagnostic/4172947-exceptionreason). To help support older OSes, and exceptions that do not originate from places that are covered by this feature, custom exceptions are also supported. These can be created from an `NSException` object, which will capture all the needed runtime information to emulate a standard `CallStack`. 47 | 48 | How you actually get access to the `NSException` is not defined by Meter. But, if you have one, the `CrashDiagnostic` type also includes an `exceptionInfo` property that can accept one of these for easy encoding. 49 | 50 | ## On-Device Symbolication 51 | 52 | The stack traces provided by MetricKit, like other types of crash logs, are not symbolicated. There are a bunch of different ways to tackle this problem, but one very convenient option is just to do it as a post-processing step on the device where the crash occurred. This does come, however, with one major drawback. It only works when you still have access to the same binaries. OS updates will almost certainly change all the OS binaries. The same is true for an app update, though in that case, an off-line symbolication step using a dSYM is still doable. 53 | 54 | Meter provides an API for performing symbolication, via the `Symbolicator` protocol. The core of this protocol should be usable to symbolicate any address, and is not tied to MetricKit. But, the protocol also does include a number of convenience methods that can operate on the various MetricKit classes. The result uses the Meter's wrapper classes to return `Frame` instances which include a `symbolInfo` property. This property can be accessed directly or just re-encoded for transport. 55 | 56 | ```swift 57 | let symbolicator = DlfcnSymbolicator() 58 | let symPayload = symbolicator.symbolicate(payload: diagnosticPayload) 59 | ``` 60 | 61 | ### DlfcnSymbolicator 62 | 63 | This class implements the `Symbolicator` protocol, and uses the functions with `dlfcn.h` to determine symbol/offset. This works, but does have some limitations. First, it relies on looking up symbols in the **currently executing** process, so it will only work if the needed binary is currently loaded. 64 | 65 | Second, these functions return `` for some binary's symbols on iOS. I know the symbol information is still accessible from the binary, so it's unclear why this is done. 66 | 67 | This is a relatively inexpensive symbolication pass, and is a first effort. Further work here is definitely necessary. 68 | 69 | ## Contributing and Collaboration 70 | 71 | I would love to hear from you! Issues or pull requests work great. Both a [Matrix space][matrix] and [Discord][discord] are available for live help, but I have a strong bias towards answering in the form of documentation. You can also find me on [mastodon](https://mastodon.social/@mattiem). 72 | 73 | I prefer collaboration, and would love to find ways to work together if you have a similar project. 74 | 75 | I prefer indentation with tabs for improved accessibility. But, I'd rather you use the system you want and make a PR than hesitate because of whitespace. 76 | 77 | By participating in this project you agree to abide by the [Contributor Code of Conduct](CODE_OF_CONDUCT.md). 78 | 79 | [build status]: https://github.com/ChimeHQ/Meter/actions 80 | [build status badge]: https://github.com/ChimeHQ/Meter/workflows/CI/badge.svg 81 | [platforms]: https://swiftpackageindex.com/ChimeHQ/Meter 82 | [platforms badge]: https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FChimeHQ%2FMeter%2Fbadge%3Ftype%3Dplatforms 83 | [documentation]: https://swiftpackageindex.com/ChimeHQ/Meter/main/documentation 84 | [documentation badge]: https://img.shields.io/badge/Documentation-DocC-blue 85 | [matrix]: https://matrix.to/#/%23chimehq%3Amatrix.org 86 | [matrix badge]: https://img.shields.io/matrix/chimehq%3Amatrix.org?label=Matrix 87 | [discord]: https://discord.gg/esFpX6sErJ 88 | -------------------------------------------------------------------------------- /Sources/BinaryImage/BinaryImage.h: -------------------------------------------------------------------------------- 1 | #ifndef BinaryImage_h 2 | #define BinaryImage_h 3 | 4 | #define _Noescape __attribute__((noescape)) 5 | #define ASSUME_NONNULL_BEGIN _Pragma("clang assume_nonnull begin") 6 | #define ASSUME_NONNULL_END _Pragma("clang assume_nonnull end") 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #if __OBJC__ 13 | #import 14 | #endif 15 | 16 | ASSUME_NONNULL_BEGIN 17 | 18 | typedef struct { 19 | uintptr_t address; 20 | intptr_t loadAddress; 21 | uintptr_t length; 22 | } MachODataRegion; 23 | 24 | typedef struct { 25 | const uint8_t* uuid; 26 | intptr_t slide; 27 | MachODataRegion ehFrameRegion; 28 | MachODataRegion unwindInfoRegion; 29 | uintptr_t loadAddress; 30 | uintptr_t textSize; 31 | const char* path; 32 | } MachOData; 33 | 34 | #if __LP64__ 35 | typedef struct mach_header_64 MachOHeader; 36 | typedef struct section_64 MachOSection; 37 | typedef struct segment_command_64 SegmentCommand; 38 | typedef struct section_64 Section; 39 | 40 | const static uint32_t LCSegment = LC_SEGMENT_64; 41 | #else 42 | typedef struct mach_header MachOHeader; 43 | typedef struct section MachOSection; 44 | typedef struct segment_command SegmentCommand; 45 | typedef struct section Section; 46 | 47 | const static uint32_t LCSegment = LC_SEGMENT; 48 | #endif 49 | 50 | typedef struct { 51 | const char* name; 52 | const MachOHeader* header; 53 | } BinaryImage; 54 | 55 | typedef void (^BinaryImageIterator)(BinaryImage image, bool* stop); 56 | 57 | void BinaryImageEnumerateLoadedImages(_Noescape BinaryImageIterator iterator); 58 | 59 | typedef void (^BinaryImageLoadCommandIterator)(const struct load_command* lcmd, uint32_t cmdCode, bool* stop); 60 | 61 | void BinaryImageEnumerateLoadCommands(const MachOHeader* header, _Noescape BinaryImageLoadCommandIterator iterator); 62 | 63 | uint8_t* _Nullable BinaryImageGetUUIDBytesFromLoadCommand(const struct load_command* lcmd, uint32_t cmdCode); 64 | 65 | #if __OBJC__ 66 | NSUUID* _Nullable BinaryuImageUUIDFromLoadCommand(const struct load_command* lcmd, uint32_t cmdCode); 67 | NSUUID* BinaryImageGetUUID(const MachOHeader* header); 68 | 69 | void LogException(NSException* exception, NSString* path); 70 | #endif 71 | 72 | ASSUME_NONNULL_END 73 | 74 | #endif /* BinaryImage_h */ 75 | -------------------------------------------------------------------------------- /Sources/BinaryImage/BinaryImage.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #include "BinaryImage.h" 4 | #include 5 | 6 | void BinaryImageEnumerateLoadedImages(BinaryImageIterator iterator) { 7 | for (uint32_t i = 0; i < _dyld_image_count(); ++i) { 8 | BinaryImage image = {0}; 9 | 10 | image.name = _dyld_get_image_name(i); 11 | image.header = (MachOHeader*)_dyld_get_image_header(i); 12 | 13 | bool stop = false; 14 | 15 | iterator(image, &stop); 16 | 17 | if (stop) { 18 | break; 19 | } 20 | } 21 | } 22 | 23 | void BinaryImageEnumerateLoadCommands(const MachOHeader* header, BinaryImageLoadCommandIterator iterator) { 24 | if (header == NULL) { 25 | return; 26 | } 27 | 28 | const uint8_t *ptr = (uint8_t *)header + sizeof(MachOHeader); 29 | 30 | for (uint32_t i = 0; i < header->ncmds; ++i) { 31 | const struct load_command* const lcmd = (struct load_command*)ptr; 32 | const uint32_t cmdCode = lcmd->cmd & ~LC_REQ_DYLD; 33 | 34 | bool stop = false; 35 | 36 | iterator(lcmd, cmdCode, &stop); 37 | 38 | if (stop) { 39 | break; 40 | } 41 | 42 | ptr += lcmd->cmdsize; 43 | } 44 | } 45 | 46 | uint8_t* BinaryImageGetUUIDBytesFromLoadCommand(const struct load_command* lcmd, uint32_t cmdCode) { 47 | if (lcmd == NULL || cmdCode != LC_UUID) { 48 | return NULL; 49 | } 50 | 51 | return ((struct uuid_command*)lcmd)->uuid; 52 | } 53 | 54 | NSUUID* BinaryuImageUUIDFromLoadCommand(const struct load_command* lcmd, uint32_t cmdCode) { 55 | const uint8_t* bytes = BinaryImageGetUUIDBytesFromLoadCommand(lcmd, cmdCode); 56 | 57 | return [[NSUUID alloc] initWithUUIDBytes:bytes]; 58 | } 59 | 60 | NSUUID* BinaryImageGetUUID(const MachOHeader* header) { 61 | __block NSUUID* uuid = nil; 62 | 63 | BinaryImageEnumerateLoadCommands(header, ^(const struct load_command * _Nonnull lcmd, uint32_t cmdCode, bool * _Nonnull stop) { 64 | switch (cmdCode) { 65 | case LC_UUID: 66 | uuid = BinaryuImageUUIDFromLoadCommand(lcmd, cmdCode); 67 | *stop = true; 68 | break; 69 | } 70 | }); 71 | 72 | return uuid; 73 | } 74 | 75 | bool BinaryImageGetData(const MachOHeader* header, const char* path, MachOData* data) { 76 | if (header == NULL || data == NULL) { 77 | return false; 78 | } 79 | 80 | const uint8_t *ptr = (uint8_t *)header + sizeof(MachOHeader); 81 | 82 | data->loadAddress = (uintptr_t)header; 83 | data->path = path; 84 | 85 | return true; 86 | } 87 | -------------------------------------------------------------------------------- /Sources/Meter/CallStackTree.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(MetricKit) 3 | import MetricKit 4 | #endif 5 | 6 | public struct Binary: Codable, Hashable { 7 | public var uuid: UUID 8 | public var loadAddress: Int 9 | public var approximateSize: Int 10 | public var name: String? 11 | 12 | public init(uuid: UUID, loadAddress: Int, approximateSize: Int, name: String?) { 13 | self.uuid = uuid 14 | self.loadAddress = loadAddress 15 | self.approximateSize = approximateSize 16 | self.name = name 17 | } 18 | 19 | public init?(uuid: String, loadAddress: Int, approximateSize: Int, name: String?) { 20 | guard let value = UUID(uuidString: uuid) else { return nil } 21 | 22 | self.uuid = value 23 | self.loadAddress = loadAddress 24 | self.approximateSize = approximateSize 25 | self.name = name 26 | } 27 | 28 | public var addressRange: Range { 29 | return loadAddress.. Bool { 33 | return addressRange.contains(address) 34 | } 35 | } 36 | 37 | public struct Frame: Codable, Hashable { 38 | public var binaryUUID: UUID? 39 | public var offsetIntoBinaryTextSegment: Int? 40 | public var sampleCount: Int? 41 | public var binaryName: String? 42 | public var address: UInt64 43 | public var subFrames: [Frame]? 44 | public var symbolInfo: [SymbolInfo]? 45 | 46 | public init(binaryUUID: UUID? = nil, offsetIntoBinaryTextSegment: Int? = nil, sampleCount: Int? = nil, binaryName: String? = nil, address: UInt64, subFrames: [Frame]?, symbolInfo: [SymbolInfo]? = nil) { 47 | self.binaryUUID = binaryUUID 48 | self.offsetIntoBinaryTextSegment = offsetIntoBinaryTextSegment 49 | self.sampleCount = sampleCount 50 | self.binaryName = binaryName 51 | self.address = address 52 | self.subFrames = subFrames 53 | self.symbolInfo = symbolInfo 54 | } 55 | 56 | init(frame: Frame, symbolInfo: [SymbolInfo], subFrames: [Frame]?) { 57 | self.binaryUUID = frame.binaryUUID 58 | self.offsetIntoBinaryTextSegment = frame.offsetIntoBinaryTextSegment 59 | self.sampleCount = frame.sampleCount 60 | self.binaryName = frame.binaryName 61 | self.address = frame.address 62 | self.subFrames = subFrames 63 | self.symbolInfo = symbolInfo 64 | } 65 | 66 | public var flattenedFrames: [Frame] { 67 | return subFrames?.flatMap({ [$0] + $0.flattenedFrames }) ?? [] 68 | } 69 | 70 | public func binary(withOffsetAsLoadAddress: Bool) -> Binary? { 71 | guard 72 | let uuid = binaryUUID, 73 | let offset = offsetIntoBinaryTextSegment 74 | else { 75 | return nil 76 | } 77 | 78 | let loadAddress = withOffsetAsLoadAddress ? offset : Int(address) - offset 79 | let size = Int(address) - loadAddress + 1 80 | 81 | return Binary(uuid: uuid, 82 | loadAddress: loadAddress, 83 | approximateSize: size, 84 | name: binaryName) 85 | } 86 | 87 | public var isSimulated: Bool { 88 | return binaryName == "testBinaryName" 89 | } 90 | } 91 | 92 | public class CallStack: NSObject, Codable { 93 | /// Indicates which thread caused the crash 94 | public var threadAttributed: Bool? 95 | public var rootFrames: [Frame] 96 | 97 | enum CodingKeys: String, CodingKey { 98 | case threadAttributed 99 | case rootFrames = "callStackRootFrames" 100 | } 101 | 102 | public init(threadAttributed: Bool, rootFrames: [Frame]) { 103 | self.threadAttributed = threadAttributed 104 | self.rootFrames = rootFrames 105 | } 106 | 107 | /// Returns a single array of Frame objects 108 | public var frames: [Frame] { 109 | return rootFrames.flatMap({ [$0] + $0.flattenedFrames }) 110 | } 111 | 112 | public var isSimulated: Bool { 113 | return rootFrames.first?.isSimulated == true 114 | } 115 | } 116 | 117 | public class CallStackTree: Codable { 118 | public let callStacks: [CallStack] 119 | public let callStackPerThread: Bool 120 | 121 | public static func from(data: Data) throws -> CallStackTree { 122 | return try JSONDecoder().decode(CallStackTree.self, from: data) 123 | } 124 | 125 | #if canImport(MetricKit) 126 | @available(iOS 14.0, macOS 12.0, visionOS 1.0, *) 127 | @available(tvOS, unavailable) 128 | @available(watchOS, unavailable) 129 | public static func from(callStackTree: MXCallStackTree) throws -> CallStackTree { 130 | let data = callStackTree.jsonRepresentation() 131 | 132 | return try from(data: data) 133 | } 134 | #endif 135 | 136 | public init(callStacks: [CallStack], callStackPerThread: Bool) { 137 | self.callStacks = callStacks 138 | self.callStackPerThread = callStackPerThread 139 | } 140 | 141 | public func jsonRepresentation() -> Data { 142 | do { 143 | return try JSONEncoder().encode(self) 144 | } catch { 145 | return Data() 146 | } 147 | } 148 | 149 | public var isSimulated: Bool { 150 | return callStacks.first?.isSimulated == true 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /Sources/Meter/DiagnosticPayload.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | #if canImport(MetricKit) 3 | import MetricKit 4 | #endif 5 | 6 | public class DiagnosticPayload: Codable { 7 | private static let dateFormatter: DateFormatter = { 8 | let formatter = DateFormatter() 9 | 10 | // "2020-10-10 19:35:24 +0000" 11 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z" 12 | 13 | return formatter 14 | }() 15 | 16 | private static let dateWithoutMillisFormatter: DateFormatter = { 17 | let formatter = DateFormatter() 18 | 19 | // "2020-10-10 19:35:24" 20 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 21 | 22 | return formatter 23 | }() 24 | 25 | enum CodingKeys: String, CodingKey { 26 | case timeStampBegin 27 | case timeStampEnd 28 | case crashDiagnostics 29 | case hangDiagnostics 30 | case cpuExceptionDiagnostics 31 | case diskWriteExceptionDiagnostics 32 | } 33 | 34 | public let timeStampBegin: Date 35 | public let timeStampEnd: Date 36 | public let crashDiagnostics: [CrashDiagnostic]? 37 | public let hangDiagnostics: [HangDiagnostic]? 38 | public let cpuExceptionDiagnostics: [CPUExceptionDiagnostic]? 39 | public let diskWriteExceptionDiagnostics: [DiskWriteExceptionDiagnostic]? 40 | 41 | public static func from(data: Data) throws -> DiagnosticPayload { 42 | let decoder = JSONDecoder() 43 | 44 | decoder.dateDecodingStrategy = .custom({ decoder in 45 | let container = try decoder.singleValueContainer() 46 | let string = try container.decode(String.self) 47 | 48 | if let date = DiagnosticPayload.dateFormatter.date(from: string) { 49 | return date 50 | } 51 | 52 | if let date = DiagnosticPayload.dateWithoutMillisFormatter.date(from: string) { 53 | return date 54 | } 55 | 56 | throw DecodingError.dataCorruptedError(in: container, debugDescription: "Invalid date: \(string)") 57 | }) 58 | 59 | return try decoder.decode(DiagnosticPayload.self, from: data) 60 | } 61 | 62 | #if canImport(MetricKit) 63 | @available(iOS 14.0, macOS 12.0, visionOS 1.0, *) 64 | @available(tvOS, unavailable) 65 | @available(watchOS, unavailable) 66 | public static func from(payload: MXDiagnosticPayload) throws -> DiagnosticPayload { 67 | let data = payload.jsonRepresentation() 68 | 69 | return try from(data: data) 70 | } 71 | #endif 72 | 73 | public init(timeStampBegin: Date, timeStampEnd: Date, crashDiagnostics: [CrashDiagnostic]?, hangDiagnostics: [HangDiagnostic]?, cpuExceptionDiagnostics: [CPUExceptionDiagnostic]?, diskWriteExceptionDiagnostics: [DiskWriteExceptionDiagnostic]?) { 74 | self.timeStampBegin = timeStampBegin 75 | self.timeStampEnd = timeStampEnd 76 | self.crashDiagnostics = crashDiagnostics 77 | self.hangDiagnostics = hangDiagnostics 78 | self.cpuExceptionDiagnostics = cpuExceptionDiagnostics 79 | self.diskWriteExceptionDiagnostics = diskWriteExceptionDiagnostics 80 | } 81 | 82 | public func jsonRepresentation() -> Data { 83 | let encoder = JSONEncoder() 84 | 85 | encoder.dateEncodingStrategy = .formatted(DiagnosticPayload.dateFormatter) 86 | 87 | return (try? encoder.encode(self)) ?? Data() 88 | } 89 | 90 | public var isSimulated: Bool { 91 | if crashDiagnostics?.allSatisfy({ $0.isSimulated }) == false { 92 | return false 93 | } 94 | 95 | if hangDiagnostics?.allSatisfy({ $0.isSimulated }) == false { 96 | return false 97 | } 98 | 99 | if cpuExceptionDiagnostics?.allSatisfy({ $0.isSimulated }) == false { 100 | return false 101 | } 102 | 103 | if diskWriteExceptionDiagnostics?.allSatisfy({ $0.isSimulated }) == false { 104 | return false 105 | } 106 | 107 | return true 108 | } 109 | } 110 | 111 | public extension DiagnosticPayload { 112 | var dateRange: Range { 113 | return timeStampBegin.. { 123 | return timeStampBegin.. Data { 242 | return (try? JSONEncoder().encode(self)) ?? Data() 243 | } 244 | } 245 | 246 | public class CrashDiagnostic: Codable { 247 | public let version: String 248 | private let internalMetaData: CrashMetaData 249 | public let callStackTree: CallStackTree 250 | 251 | /// Per-binary crash-related information 252 | /// 253 | /// This is available only for convenience when encoding. The value 254 | /// must be set externally. 255 | public var applicationSpecificInformation: [String: [String]]? 256 | 257 | /// Backtraces that are available only within the crashing process 258 | /// 259 | /// This is available only for convenience when encoding. The value 260 | /// must be set externally. 261 | public var exceptionInfo: ExceptionInfo? 262 | 263 | enum CodingKeys: String, CodingKey { 264 | case version 265 | case internalMetaData = "diagnosticMetaData" 266 | case callStackTree = "callStackTree" 267 | case applicationSpecificInformation 268 | case exceptionInfo 269 | } 270 | 271 | public init(metaData: CrashMetaData, callStackTree: CallStackTree) { 272 | self.version = "1.0.0" 273 | self.internalMetaData = metaData 274 | self.callStackTree = callStackTree 275 | } 276 | 277 | public var applicationVersion: String { 278 | return internalMetaData.applicationVersion 279 | } 280 | 281 | public var virtualMemoryRegionInfo: String? { 282 | return internalMetaData.virtualMemoryRegionInfo 283 | } 284 | 285 | public var metaData: CrashMetaData { 286 | return internalMetaData 287 | } 288 | 289 | public var terminationReason: String? { 290 | return internalMetaData.terminationReason 291 | } 292 | 293 | public var signal: NSNumber? { 294 | return internalMetaData.signal.map({ NSNumber(value: $0) }) 295 | } 296 | 297 | public var exceptionReason: ObjectiveCExceptionReason? { 298 | return internalMetaData.exceptionReason 299 | } 300 | 301 | public var exceptionCode: NSNumber? { 302 | return internalMetaData.exceptionCode.map({ NSNumber(value: $0) }) 303 | } 304 | 305 | public var exceptionType: NSNumber? { 306 | return internalMetaData.exceptionType.map({ NSNumber(value: $0) }) 307 | } 308 | 309 | public func jsonRepresentation() -> Data { 310 | return (try? JSONEncoder().encode(self)) ?? Data() 311 | } 312 | 313 | public var isSimulated: Bool { 314 | return callStackTree.isSimulated 315 | } 316 | 317 | /// Determine how `offsetIntoBinaryTextSegment` data should be intrepreted. 318 | public var usesOffsetAsLoadAddress: Bool { 319 | let osVersion = metaData.osVersion 320 | 321 | if osVersion.hasPrefix("macOS 12") { 322 | return true 323 | } 324 | 325 | if osVersion.hasPrefix("iPhone OS 14") || osVersion.hasPrefix("iPhone OS 15") { 326 | return true 327 | } 328 | 329 | return false 330 | } 331 | } 332 | 333 | public class HangMetaData: Codable { 334 | public let deviceType: String 335 | public let applicationBuildVersion: String 336 | public let applicationVersion: String 337 | public let osVersion: String 338 | public let platformArchitecture: String 339 | public let regionFormat: String 340 | private let hangDuration: String 341 | 342 | enum CodingKeys: String, CodingKey { 343 | case applicationBuildVersion = "appBuildVersion" 344 | case applicationVersion = "appVersion" 345 | case osVersion 346 | case platformArchitecture 347 | case regionFormat 348 | case deviceType 349 | case hangDuration 350 | } 351 | 352 | public init(deviceType: String, applicationBuildVersion: String, applicationVersion: String, osVersion: String, platformArchitecture: String, regionFormat: String) { 353 | self.deviceType = deviceType 354 | self.applicationBuildVersion = applicationBuildVersion 355 | self.applicationVersion = applicationVersion 356 | self.osVersion = osVersion 357 | self.platformArchitecture = platformArchitecture 358 | self.regionFormat = regionFormat 359 | self.hangDuration = "" 360 | } 361 | 362 | public init(diagnostic: HangDiagnostic) { 363 | self.deviceType = diagnostic.metaData.deviceType 364 | self.applicationBuildVersion = diagnostic.metaData.applicationBuildVersion 365 | self.applicationVersion = diagnostic.applicationVersion 366 | self.osVersion = diagnostic.metaData.osVersion 367 | self.platformArchitecture = diagnostic.metaData.platformArchitecture 368 | self.regionFormat = diagnostic.metaData.regionFormat 369 | self.hangDuration = "" 370 | } 371 | 372 | public func jsonRepresentation() -> Data { 373 | return (try? JSONEncoder().encode(self)) ?? Data() 374 | } 375 | } 376 | 377 | public class HangDiagnostic: Codable { 378 | public let version: String 379 | private let internalMetaData: HangMetaData 380 | public let callStackTree: CallStackTree 381 | 382 | enum CodingKeys: String, CodingKey { 383 | case version 384 | case internalMetaData = "diagnosticMetaData" 385 | case callStackTree 386 | } 387 | 388 | public init(metaData: HangMetaData, callStackTree: CallStackTree) { 389 | self.version = "1.0.0" 390 | self.internalMetaData = metaData 391 | self.callStackTree = callStackTree 392 | } 393 | 394 | public func jsonRepresentation() -> Data { 395 | return (try? JSONEncoder().encode(self)) ?? Data() 396 | } 397 | 398 | public var metaData: HangMetaData { 399 | return internalMetaData 400 | } 401 | 402 | public var applicationVersion: String { 403 | return internalMetaData.applicationVersion 404 | } 405 | 406 | public var isSimulated: Bool { 407 | return callStackTree.isSimulated 408 | } 409 | } 410 | 411 | public class CPUExceptionMetaData: Codable { 412 | public let deviceType: String 413 | public let applicationBuildVersion: String 414 | public let applicationVersion: String 415 | public let osVersion: String 416 | public let platformArchitecture: String 417 | public let regionFormat: String 418 | private let totalCPUTime: String 419 | private let totalSampledTime: String 420 | 421 | enum CodingKeys: String, CodingKey { 422 | case applicationBuildVersion = "appBuildVersion" 423 | case applicationVersion = "appVersion" 424 | case osVersion 425 | case platformArchitecture 426 | case regionFormat 427 | case deviceType 428 | case totalCPUTime 429 | case totalSampledTime 430 | } 431 | 432 | public init(deviceType: String, applicationBuildVersion: String, applicationVersion: String, osVersion: String, platformArchitecture: String, regionFormat: String) { 433 | self.deviceType = deviceType 434 | self.applicationBuildVersion = applicationBuildVersion 435 | self.applicationVersion = applicationVersion 436 | self.osVersion = osVersion 437 | self.platformArchitecture = platformArchitecture 438 | self.regionFormat = regionFormat 439 | self.totalCPUTime = "" 440 | self.totalSampledTime = "" 441 | } 442 | 443 | public init(diagnostic: CPUExceptionDiagnostic) { 444 | self.deviceType = diagnostic.metaData.deviceType 445 | self.applicationBuildVersion = diagnostic.metaData.applicationBuildVersion 446 | self.applicationVersion = diagnostic.applicationVersion 447 | self.osVersion = diagnostic.metaData.osVersion 448 | self.platformArchitecture = diagnostic.metaData.platformArchitecture 449 | self.regionFormat = diagnostic.metaData.regionFormat 450 | self.totalCPUTime = "" 451 | self.totalSampledTime = "" 452 | } 453 | 454 | public func jsonRepresentation() -> Data { 455 | return (try? JSONEncoder().encode(self)) ?? Data() 456 | } 457 | } 458 | 459 | public class CPUExceptionDiagnostic: Codable { 460 | public let version: String 461 | private let internalMetaData: CPUExceptionMetaData 462 | public let callStackTree: CallStackTree 463 | 464 | enum CodingKeys: String, CodingKey { 465 | case version 466 | case internalMetaData = "diagnosticMetaData" 467 | case callStackTree 468 | } 469 | 470 | public init(metaData: CPUExceptionMetaData, callStackTree: CallStackTree) { 471 | self.version = "1.0.0" 472 | self.internalMetaData = metaData 473 | self.callStackTree = callStackTree 474 | } 475 | 476 | public func jsonRepresentation() -> Data { 477 | return (try? JSONEncoder().encode(self)) ?? Data() 478 | } 479 | 480 | public var metaData: CPUExceptionMetaData { 481 | return internalMetaData 482 | } 483 | 484 | public var applicationVersion: String { 485 | return internalMetaData.applicationVersion 486 | } 487 | 488 | public var isSimulated: Bool { 489 | return callStackTree.isSimulated 490 | } 491 | } 492 | 493 | public class DiskWriteExceptionMetaData: Codable { 494 | public let deviceType: String 495 | public let applicationBuildVersion: String 496 | public let applicationVersion: String 497 | public let osVersion: String 498 | public let platformArchitecture: String 499 | public let regionFormat: String 500 | private let writesCaused: String 501 | 502 | enum CodingKeys: String, CodingKey { 503 | case applicationBuildVersion = "appBuildVersion" 504 | case applicationVersion = "appVersion" 505 | case osVersion 506 | case platformArchitecture 507 | case regionFormat 508 | case deviceType 509 | case writesCaused 510 | } 511 | 512 | public init(deviceType: String, applicationBuildVersion: String, applicationVersion: String, osVersion: String, platformArchitecture: String, regionFormat: String) { 513 | self.deviceType = deviceType 514 | self.applicationBuildVersion = applicationBuildVersion 515 | self.applicationVersion = applicationVersion 516 | self.osVersion = osVersion 517 | self.platformArchitecture = platformArchitecture 518 | self.regionFormat = regionFormat 519 | self.writesCaused = "" 520 | } 521 | 522 | public init(diagnostic: DiskWriteExceptionDiagnostic) { 523 | self.deviceType = diagnostic.metaData.deviceType 524 | self.applicationBuildVersion = diagnostic.metaData.applicationBuildVersion 525 | self.applicationVersion = diagnostic.applicationVersion 526 | self.osVersion = diagnostic.metaData.osVersion 527 | self.platformArchitecture = diagnostic.metaData.platformArchitecture 528 | self.regionFormat = diagnostic.metaData.regionFormat 529 | self.writesCaused = "" 530 | } 531 | 532 | public func jsonRepresentation() -> Data { 533 | return (try? JSONEncoder().encode(self)) ?? Data() 534 | } 535 | } 536 | 537 | public class DiskWriteExceptionDiagnostic: Codable { 538 | public let version: String 539 | private let internalMetaData: DiskWriteExceptionMetaData 540 | public let callStackTree: CallStackTree 541 | 542 | enum CodingKeys: String, CodingKey { 543 | case version 544 | case internalMetaData = "diagnosticMetaData" 545 | case callStackTree 546 | } 547 | 548 | public init(metaData: DiskWriteExceptionMetaData, callStackTree: CallStackTree) { 549 | self.version = "1.0.0" 550 | self.internalMetaData = metaData 551 | self.callStackTree = callStackTree 552 | } 553 | 554 | public func jsonRepresentation() -> Data { 555 | return (try? JSONEncoder().encode(self)) ?? Data() 556 | } 557 | 558 | public var metaData: DiskWriteExceptionMetaData { 559 | return internalMetaData 560 | } 561 | 562 | public var applicationVersion: String { 563 | return internalMetaData.applicationVersion 564 | } 565 | 566 | public var isSimulated: Bool { 567 | return callStackTree.isSimulated 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /Sources/Meter/DlfcnSymbolicator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BinaryImage 3 | 4 | extension BinaryImage { 5 | static var imageMap: [UUID: BinaryImage] { 6 | var map: [UUID: BinaryImage] = [:] 7 | 8 | BinaryImageEnumerateLoadedImages { image, _ in 9 | BinaryImageEnumerateLoadCommands(image.header) { lcmd, code, stop in 10 | switch code { 11 | case UInt32(LC_UUID): 12 | if let uuid = BinaryuImageUUIDFromLoadCommand(lcmd, code) { 13 | map[uuid] = image 14 | } 15 | 16 | stop.pointee = true 17 | default: 18 | break 19 | } 20 | } 21 | } 22 | 23 | return map 24 | } 25 | } 26 | 27 | public class DlfcnSymbolicator { 28 | private lazy var imageMap: [UUID: BinaryImage] = BinaryImage.imageMap 29 | 30 | public init() { 31 | } 32 | } 33 | 34 | struct DynamicLibraryInfo { 35 | private let info: Dl_info 36 | private let address: Int 37 | 38 | init?(address: Int) { 39 | self.address = address 40 | 41 | let ptr = UnsafeRawPointer(bitPattern: address) 42 | var infoObj = Dl_info() 43 | 44 | guard dladdr(ptr, &infoObj) != 0 else { 45 | return nil 46 | } 47 | 48 | self.info = infoObj 49 | } 50 | 51 | init() { 52 | self.info = Dl_info(dli_fname: nil, dli_fbase: nil, dli_sname: nil, dli_saddr: nil) 53 | self.address = 0 54 | } 55 | 56 | var symbolName: String? { 57 | return info.dli_sname.map(String.init(cString:)) 58 | } 59 | 60 | var symbolOffset: Int { 61 | return address - symbolAddress 62 | } 63 | 64 | var symbolAddress: Int { 65 | return info.dli_saddr.map({ Int(bitPattern: $0) }) ?? 0 66 | } 67 | 68 | var path: String? { 69 | return info.dli_fname.map(String.init(cString:)) 70 | } 71 | 72 | var machOHeader: UnsafePointer? { 73 | return info.dli_fbase 74 | .map({ $0.bindMemory(to: MachOHeader.self, capacity: 1) }) 75 | .map({ UnsafePointer($0) }) 76 | } 77 | 78 | var loadAddress: Int { 79 | return info.dli_fbase.map({ Int(bitPattern: $0) }) ?? 0 80 | } 81 | 82 | func getUUID() -> UUID? { 83 | return machOHeader.map({ BinaryImageGetUUID($0) }) 84 | } 85 | 86 | func makeBinary() -> Binary? { 87 | guard let uuid = getUUID() else { 88 | return nil 89 | } 90 | 91 | let size = address - loadAddress 92 | 93 | let name = path?.components(separatedBy: "/").last 94 | 95 | return Binary(uuid: uuid, loadAddress: loadAddress, approximateSize: size, name: name) 96 | } 97 | 98 | var symbolInfo: SymbolInfo { 99 | return SymbolInfo(symbol: symbolName ?? "", offset: symbolOffset) 100 | } 101 | } 102 | 103 | extension DlfcnSymbolicator: Symbolicator { 104 | public func symbolicate(address: UInt64, in target: SymbolicationTarget) -> [SymbolInfo] { 105 | guard let loadedImage = imageMap[target.uuid] else { 106 | return [] 107 | } 108 | 109 | let loadAddress = Int(bitPattern: loadedImage.header) 110 | let relativeAddress = address - UInt64(target.loadAddress) 111 | let processAddress = UInt64(loadAddress) + relativeAddress 112 | 113 | if processAddress > Int.max { 114 | return [] 115 | } 116 | 117 | guard let info = DynamicLibraryInfo(address: Int(processAddress)) else { 118 | return [] 119 | } 120 | 121 | return [info.symbolInfo] 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Sources/Meter/ExceptionInfo.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BinaryImage 3 | 4 | public struct ExceptionInfo: Codable { 5 | public let name: String 6 | public let reason: String 7 | public let backtrace: [Frame] 8 | 9 | public init(name: String, reason: String, backtrace: [Frame]) { 10 | self.name = name 11 | self.reason = reason 12 | self.backtrace = backtrace 13 | } 14 | 15 | public init(exception: NSException) { 16 | self.name = exception.name.rawValue 17 | self.reason = exception.reason ?? "" 18 | self.backtrace = [exception.frameRepresentation] 19 | } 20 | 21 | public func write(to url: URL) throws { 22 | let data = try JSONEncoder().encode(self) 23 | 24 | try data.write(to: url) 25 | } 26 | 27 | public func matchesCrashDiagnostic(_ diagnostic: CrashDiagnostic) -> Bool { 28 | // ok, yes, this is insufficient 29 | return true 30 | } 31 | } 32 | 33 | extension NSException { 34 | public var frameRepresentation: Frame { 35 | let addresses = callStackReturnAddresses.map({ $0.intValue }) 36 | 37 | var binaryMap = [Int : Binary]() 38 | 39 | var frame: Frame? = nil 40 | 41 | // both creating the DynamicLibraryInfo and calling makeBinary are 42 | // expensive. Use a simple cache to avoid one of those operations. 43 | 44 | for addr in addresses.reversed() { 45 | let info = DynamicLibraryInfo(address: addr) ?? DynamicLibraryInfo() 46 | 47 | let binary = binaryMap[info.loadAddress] ?? info.makeBinary() 48 | let loadAddress = binary?.loadAddress ?? info.loadAddress 49 | 50 | if binary != nil && info.loadAddress > 0 { 51 | binaryMap[info.loadAddress] = binary 52 | } 53 | 54 | let subFrames = [frame].compactMap({ $0 }) 55 | 56 | // have to account for the change in recent platforms to make this an actual offset 57 | let offset: Int 58 | 59 | if #available(macOS 13, iOS 16, *) { 60 | offset = addr - loadAddress 61 | } else { 62 | offset = loadAddress 63 | } 64 | 65 | frame = Frame(binaryUUID: binary?.uuid, 66 | offsetIntoBinaryTextSegment: offset, 67 | binaryName: binary?.name, 68 | address: UInt64(addr), 69 | subFrames: subFrames, 70 | symbolInfo: [info.symbolInfo]) 71 | } 72 | 73 | return frame ?? Frame(address: 0, subFrames: nil) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Sources/Meter/Symbolicator.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import BinaryImage 3 | 4 | public struct SymbolInfo: Codable, Hashable { 5 | public var offset: Int? 6 | public var symbol: String 7 | public var demangledSymbol: String? 8 | public var file: String? 9 | public var lineNumber: Int? 10 | 11 | public init(symbol: String, demangledSymbol: String? = nil, offset: Int? = nil, file: String? = nil, lineNumber: Int? = nil) { 12 | self.symbol = symbol 13 | self.demangledSymbol = demangledSymbol 14 | self.offset = offset 15 | self.file = file 16 | self.lineNumber = lineNumber 17 | } 18 | } 19 | 20 | extension SymbolInfo: CustomDebugStringConvertible { 21 | public var debugDescription: String { 22 | var str = "" 37 | } 38 | } 39 | 40 | public struct SymbolicationTarget: Hashable { 41 | public var uuid: UUID 42 | public var loadAddress: Int 43 | public var path: String? 44 | 45 | public init(uuid: UUID, loadAddress: Int, path: String? = nil) { 46 | self.uuid = uuid 47 | self.loadAddress = loadAddress 48 | self.path = path 49 | } 50 | 51 | public init?(uuid: String, loadAddress: Int, path: String? = nil) { 52 | guard let value = UUID(uuidString: uuid) else { return nil } 53 | 54 | self.uuid = value 55 | self.loadAddress = loadAddress 56 | self.path = path 57 | } 58 | } 59 | 60 | public protocol Symbolicator { 61 | func symbolicate(address: UInt64, in target: SymbolicationTarget) -> [SymbolInfo] 62 | } 63 | 64 | public extension Frame { 65 | func symbolicationTarget(withOffsetAsLoadAddress: Bool) -> SymbolicationTarget? { 66 | let binary = binary(withOffsetAsLoadAddress: withOffsetAsLoadAddress) 67 | 68 | return binary.map({ SymbolicationTarget(uuid: $0.uuid, loadAddress: $0.loadAddress, path: $0.name) }) 69 | } 70 | } 71 | 72 | public extension Symbolicator { 73 | func symbolicate(frame: Frame, withOffsetAsLoadAddress: Bool) -> Frame { 74 | let subframes = frame.subFrames ?? [] 75 | let symSubframes = subframes.map({ symbolicate(frame: $0, withOffsetAsLoadAddress: withOffsetAsLoadAddress) }) 76 | 77 | if frame.address > Int.max { 78 | return Frame(frame: frame, symbolInfo: [], subFrames: symSubframes) 79 | } 80 | 81 | let addr = Int(frame.address) 82 | let target = frame.symbolicationTarget(withOffsetAsLoadAddress: withOffsetAsLoadAddress) 83 | let info = target.map({ symbolicate(address: UInt64(addr), in: $0) }) ?? [] 84 | 85 | return Frame(frame: frame, symbolInfo: info, subFrames: symSubframes) 86 | } 87 | 88 | func symbolicate(callStack: CallStack, withOffsetAsLoadAddress: Bool) -> CallStack { 89 | let symFrames = callStack.rootFrames.map({ symbolicate(frame: $0, withOffsetAsLoadAddress: withOffsetAsLoadAddress) }) 90 | let attributed = callStack.threadAttributed ?? false 91 | 92 | return CallStack(threadAttributed: attributed, rootFrames: symFrames) 93 | } 94 | 95 | func symbolicate(tree: CallStackTree, withOffsetAsLoadAddress: Bool) -> CallStackTree { 96 | let stacks = tree.callStacks.map({ symbolicate(callStack: $0, withOffsetAsLoadAddress: withOffsetAsLoadAddress) }) 97 | 98 | return CallStackTree(callStacks: stacks, 99 | callStackPerThread: tree.callStackPerThread) 100 | } 101 | 102 | func symbolicate(diagnostic: CrashDiagnostic) -> CrashDiagnostic { 103 | let metadata = CrashMetaData(diagnostic: diagnostic) 104 | 105 | let useOffsets = diagnostic.usesOffsetAsLoadAddress 106 | let symTree = symbolicate(tree: diagnostic.callStackTree, withOffsetAsLoadAddress: useOffsets) 107 | 108 | return CrashDiagnostic(metaData: metadata, callStackTree: symTree) 109 | } 110 | 111 | func symbolicate(diagnostic: HangDiagnostic) -> HangDiagnostic { 112 | let metadata = HangMetaData(diagnostic: diagnostic) 113 | 114 | let symTree = symbolicate(tree: diagnostic.callStackTree, withOffsetAsLoadAddress: false) 115 | 116 | return HangDiagnostic(metaData: metadata, callStackTree: symTree) 117 | } 118 | 119 | func symbolicate(diagnostic: DiskWriteExceptionDiagnostic) -> DiskWriteExceptionDiagnostic { 120 | let metadata = DiskWriteExceptionMetaData(diagnostic: diagnostic) 121 | 122 | let symTree = symbolicate(tree: diagnostic.callStackTree, withOffsetAsLoadAddress: false) 123 | 124 | return DiskWriteExceptionDiagnostic(metaData: metadata, callStackTree: symTree) 125 | } 126 | 127 | func symbolicate(diagnostic: CPUExceptionDiagnostic) -> CPUExceptionDiagnostic { 128 | let metadata = CPUExceptionMetaData(diagnostic: diagnostic) 129 | 130 | let symTree = symbolicate(tree: diagnostic.callStackTree, withOffsetAsLoadAddress: false) 131 | 132 | return CPUExceptionDiagnostic(metaData: metadata, callStackTree: symTree) 133 | } 134 | 135 | func symbolicate(payload: DiagnosticPayload) -> DiagnosticPayload { 136 | let symCrashDiagnostics = payload.crashDiagnostics?.map({ symbolicate(diagnostic: $0) }) 137 | let symHangDiagnostics = payload.hangDiagnostics?.map({ symbolicate(diagnostic: $0) }) 138 | let symCPUDiagnostics = payload.cpuExceptionDiagnostics?.map({ symbolicate(diagnostic: $0) }) 139 | let diskWriteDiagnostics = payload.diskWriteExceptionDiagnostics?.map({ symbolicate(diagnostic: $0) }) 140 | 141 | return DiagnosticPayload(timeStampBegin: payload.timeStampBegin, 142 | timeStampEnd: payload.timeStampEnd, 143 | crashDiagnostics: symCrashDiagnostics, 144 | hangDiagnostics: symHangDiagnostics, 145 | cpuExceptionDiagnostics: symCPUDiagnostics, 146 | diskWriteExceptionDiagnostics: diskWriteDiagnostics) 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Tests/BinaryImageTests/BinaryImageTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import BinaryImage 3 | 4 | final class BinaryImageTests: XCTestCase { 5 | func testIteration() { 6 | var images: [BinaryImage] = [] 7 | 8 | BinaryImageEnumerateLoadedImages { image, _ in 9 | images.append(image) 10 | } 11 | 12 | let matchingImage = images.first { image in 13 | let name = String(cString: image.name) 14 | 15 | return name == "/usr/lib/system/libsystem_kernel.dylib" 16 | } 17 | 18 | XCTAssertNotNil(matchingImage) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Tests/MeterTests/DiagnosticTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Meter 3 | 4 | final class DiagnosticTests: XCTestCase { 5 | func testReadingSimulatedCrashDiagnosticsData() throws { 6 | let url = try XCTUnwrap(Bundle.module.url(forResource: "xcode_simulated", withExtension: "json", subdirectory: "Resources")) 7 | let data = try Data(contentsOf: url, options: []) 8 | 9 | let payload = try XCTUnwrap(DiagnosticPayload.from(data: data)) 10 | 11 | XCTAssertEqual(payload.timeStampBegin, Date(timeIntervalSince1970: 1602358524)) 12 | XCTAssertEqual(payload.timeStampEnd, Date(timeIntervalSince1970: 1602358524)) 13 | XCTAssertTrue(payload.isSimulated) 14 | 15 | XCTAssertEqual(payload.crashDiagnostics?.count, 1) 16 | let crashDiagnostic = try XCTUnwrap(payload.crashDiagnostics?[0]) 17 | 18 | XCTAssertEqual(crashDiagnostic.metaData.applicationBuildVersion, "1") 19 | XCTAssertEqual(crashDiagnostic.metaData.osVersion, "iPhone OS 14.0.1 (18A393)") 20 | if #available(iOS 14.0, macOS 12.0, *) { 21 | XCTAssertEqual(crashDiagnostic.metaData.platformArchitecture, "arm64") 22 | } 23 | XCTAssertEqual(crashDiagnostic.metaData.regionFormat, "CA") 24 | XCTAssertEqual(crashDiagnostic.virtualMemoryRegionInfo?.hasPrefix("0 is not in any region"), true) 25 | XCTAssertEqual(crashDiagnostic.applicationVersion, "1.0") 26 | XCTAssertEqual(crashDiagnostic.terminationReason, "Namespace SIGNAL, Code 0xb") 27 | XCTAssertEqual(crashDiagnostic.signal, 11) 28 | XCTAssertEqual(crashDiagnostic.exceptionCode, 0) 29 | XCTAssertEqual(crashDiagnostic.exceptionType, 1) 30 | XCTAssertTrue(crashDiagnostic.isSimulated) 31 | XCTAssertTrue(crashDiagnostic.usesOffsetAsLoadAddress) 32 | 33 | let tree = crashDiagnostic.callStackTree 34 | 35 | XCTAssertTrue(tree.callStackPerThread) 36 | XCTAssertEqual(tree.callStacks.count, 1); 37 | 38 | let callStack = tree.callStacks[0] 39 | 40 | XCTAssertTrue(callStack.threadAttributed == true) 41 | XCTAssertEqual(callStack.rootFrames.count, 1) 42 | 43 | let frame = callStack.rootFrames[0] 44 | 45 | XCTAssertEqual(frame.binaryUUID, UUID(uuidString: "CDB53DDB-2337-4933-B62F-4356E6174AF0")) 46 | XCTAssertEqual(frame.offsetIntoBinaryTextSegment, 123) 47 | XCTAssertEqual(frame.sampleCount, 20) 48 | XCTAssertEqual(frame.binaryName, "testBinaryName") 49 | XCTAssertEqual(frame.address, 74565) 50 | } 51 | 52 | func testReadingSimulatedMacOSCrashDiagnosticsData() throws { 53 | let url = try XCTUnwrap(Bundle.module.url(forResource: "xcode_simulated_macOS_12", withExtension: "json", subdirectory: "Resources")) 54 | let data = try Data(contentsOf: url, options: []) 55 | 56 | let payload = try XCTUnwrap(DiagnosticPayload.from(data: data)) 57 | 58 | XCTAssertTrue(payload.isSimulated) 59 | 60 | XCTAssertEqual(payload.crashDiagnostics?.count, 1) 61 | 62 | let crashDiagnostic = try XCTUnwrap(payload.crashDiagnostics?[0]) 63 | 64 | XCTAssertTrue(crashDiagnostic.isSimulated) 65 | XCTAssertTrue(crashDiagnostic.usesOffsetAsLoadAddress) 66 | 67 | // on macOS, these dates are encoded in local time, which is a real pain 68 | let formatter = DateFormatter() 69 | 70 | formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" 71 | 72 | XCTAssertEqual(payload.timeStampBegin, formatter.date(from: "2022-03-07 07:30:54")) 73 | XCTAssertEqual(payload.timeStampEnd, formatter.date(from: "2022-03-07 07:30:54")) 74 | } 75 | 76 | func testReadingSimulatedMacOS13CrashDiagnosticsData() throws { 77 | let url = try XCTUnwrap(Bundle.module.url(forResource: "xcode_simulated_macOS_13", withExtension: "json", subdirectory: "Resources")) 78 | let data = try Data(contentsOf: url, options: []) 79 | 80 | let payload = try XCTUnwrap(DiagnosticPayload.from(data: data)) 81 | 82 | XCTAssertTrue(payload.isSimulated) 83 | 84 | XCTAssertEqual(payload.crashDiagnostics?.count, 1) 85 | 86 | let crashDiagnostic = try XCTUnwrap(payload.crashDiagnostics?[0]) 87 | 88 | XCTAssertTrue(crashDiagnostic.isSimulated) 89 | XCTAssertFalse(crashDiagnostic.usesOffsetAsLoadAddress) 90 | } 91 | 92 | func testReadingSimulatedHangDiagnosticsData() throws { 93 | let url = try XCTUnwrap(Bundle.module.url(forResource: "xcode_simulated", withExtension: "json", subdirectory: "Resources")) 94 | let data = try Data(contentsOf: url, options: []) 95 | 96 | let payload = try XCTUnwrap(DiagnosticPayload.from(data: data)) 97 | 98 | XCTAssertTrue(payload.isSimulated) 99 | 100 | XCTAssertEqual(payload.hangDiagnostics?.count, 1) 101 | let diagnostic = try XCTUnwrap(payload.hangDiagnostics?[0]) 102 | 103 | XCTAssertEqual(diagnostic.metaData.applicationBuildVersion, "1") 104 | XCTAssertTrue(diagnostic.isSimulated) 105 | 106 | let tree = diagnostic.callStackTree 107 | 108 | XCTAssertTrue(tree.callStackPerThread) 109 | XCTAssertEqual(tree.callStacks.count, 1); 110 | 111 | let callStack = tree.callStacks[0] 112 | 113 | XCTAssertTrue(callStack.threadAttributed == true) 114 | XCTAssertEqual(callStack.rootFrames.count, 1) 115 | 116 | let frame = callStack.rootFrames[0] 117 | 118 | XCTAssertEqual(frame.offsetIntoBinaryTextSegment, 123) 119 | XCTAssertEqual(frame.address, 74565) 120 | } 121 | 122 | func testReadingSimulatedCPUExceptionDiagnosticsData() throws { 123 | let url = try XCTUnwrap(Bundle.module.url(forResource: "xcode_simulated", withExtension: "json", subdirectory: "Resources")) 124 | let data = try Data(contentsOf: url, options: []) 125 | 126 | let payload = try XCTUnwrap(DiagnosticPayload.from(data: data)) 127 | 128 | XCTAssertTrue(payload.isSimulated) 129 | 130 | XCTAssertEqual(payload.cpuExceptionDiagnostics?.count, 1) 131 | let diagnostic = try XCTUnwrap(payload.cpuExceptionDiagnostics?[0]) 132 | 133 | XCTAssertEqual(diagnostic.metaData.applicationBuildVersion, "1") 134 | XCTAssertTrue(diagnostic.isSimulated) 135 | 136 | let tree = diagnostic.callStackTree 137 | 138 | XCTAssertFalse(tree.callStackPerThread) 139 | XCTAssertEqual(tree.callStacks.count, 1); 140 | 141 | let callStack = tree.callStacks[0] 142 | 143 | XCTAssertNil(callStack.threadAttributed) 144 | XCTAssertEqual(callStack.rootFrames.count, 1) 145 | } 146 | 147 | func testRealPayloadWithSubframes() throws { 148 | let url = try XCTUnwrap(Bundle.module.url(forResource: "real_report", withExtension: "json", subdirectory: "Resources")) 149 | let data = try Data(contentsOf: url, options: []) 150 | let payload = try DiagnosticPayload.from(data: data) 151 | 152 | XCTAssertFalse(payload.isSimulated) 153 | 154 | XCTAssertEqual(payload.crashDiagnostics?.count, 2) 155 | 156 | let crashDiagnostic = try XCTUnwrap(payload.crashDiagnostics?[0]) 157 | 158 | XCTAssertFalse(crashDiagnostic.isSimulated) 159 | 160 | let tree = crashDiagnostic.callStackTree 161 | 162 | XCTAssertTrue(crashDiagnostic.usesOffsetAsLoadAddress) 163 | 164 | XCTAssertTrue(tree.callStackPerThread) 165 | XCTAssertEqual(tree.callStacks.count, 10); 166 | 167 | let callStack = tree.callStacks[0] 168 | 169 | XCTAssertTrue(callStack.threadAttributed == true) 170 | XCTAssertEqual(callStack.rootFrames.count, 1) 171 | XCTAssertEqual(callStack.rootFrames[0].subFrames?.count, 1) 172 | 173 | let frames = callStack.frames 174 | 175 | XCTAssertEqual(frames.count, 32) 176 | 177 | // guard against 64bit addresses 178 | #if !os(watchOS) 179 | XCTAssertEqual(frames[0].binaryUUID, UUID(uuidString: "9156BE86-D4B6-3A81-8460-8728FA38C978")) 180 | XCTAssertEqual(frames[0].offsetIntoBinaryTextSegment, 6859616256) 181 | XCTAssertEqual(frames[0].sampleCount, 1) 182 | XCTAssertEqual(frames[0].binaryName, "libswiftCore.dylib") 183 | XCTAssertEqual(frames[0].address, 6859816880) 184 | 185 | XCTAssertEqual(frames[1].binaryUUID, UUID(uuidString: "9156BE86-D4B6-3A81-8460-8728FA38C978")) 186 | XCTAssertEqual(frames[1].offsetIntoBinaryTextSegment, 6859616256) 187 | XCTAssertEqual(frames[1].sampleCount, 1) 188 | XCTAssertEqual(frames[1].binaryName, "libswiftCore.dylib") 189 | XCTAssertEqual(frames[1].address, 6859816880) 190 | 191 | XCTAssertEqual(frames[30].binaryUUID, UUID(uuidString: "444F912B-06E7-395E-9E6E-D947B07401AC")) 192 | XCTAssertEqual(frames[30].offsetIntoBinaryTextSegment, 4303568896) 193 | XCTAssertEqual(frames[30].sampleCount, 1) 194 | XCTAssertEqual(frames[30].binaryName, "MetricKitTest") 195 | XCTAssertEqual(frames[30].address, 4303603064) 196 | 197 | XCTAssertEqual(frames[31].binaryUUID, UUID(uuidString: "77E57314-8A58-3064-90C0-8AF9A4745430")) 198 | XCTAssertEqual(frames[31].offsetIntoBinaryTextSegment, 6795280384) 199 | XCTAssertEqual(frames[31].sampleCount, 1) 200 | XCTAssertEqual(frames[31].binaryName, "libdyld.dylib") 201 | XCTAssertEqual(frames[31].address, 6795285912) 202 | #endif 203 | } 204 | 205 | func testVeryLargeAddress() throws { 206 | let value = """ 207 | { 208 | "timeStampEnd" : "2022-10-07 14:41:00", 209 | "timeStampBegin" : "2022-10-07 14:41:00", 210 | "hangDiagnostics" : [ 211 | { 212 | "version" : "1.0.0", 213 | "callStackTree" : { 214 | "callStacks" : [ 215 | { 216 | "threadAttributed" : true, 217 | "callStackRootFrames" : [ 218 | { 219 | "binaryUUID" : "1A111731-DB37-3138-BB6A-4F8CC7551374", 220 | "offsetIntoBinaryTextSegment" : 239784, 221 | "sampleCount" : 434, 222 | "subFrames" : [], 223 | "binaryName" : "kernel.release.t8103", 224 | "address" : 18446741874824079528 225 | } 226 | ] 227 | } 228 | ], 229 | "callStackPerThread" : true 230 | }, 231 | "diagnosticMetaData" : { 232 | "appBuildVersion" : "1", 233 | "appVersion" : "1.0.0", 234 | "regionFormat" : "US", 235 | "hangDuration" : "7 sec", 236 | "osVersion" : "macOS 13.0 (22A5358e)", 237 | "deviceType" : "Macmini9,1", 238 | "bundleIdentifier" : "x.y.x", 239 | "platformArchitecture" : "arm64e" 240 | } 241 | } 242 | ] 243 | } 244 | """ 245 | let data = try XCTUnwrap(value.data(using: .utf8)) 246 | 247 | XCTAssertNoThrow(try DiagnosticPayload.from(data: data)) 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /Tests/MeterTests/Resources/exception.log: -------------------------------------------------------------------------------- 1 | 1 {"type":"objc","name":"c29tZXRoaW5n","reason":"a2Fib29t","time_ms":1646487651000} 2 | 2 {"pc":7268352440} 3 | 2 {"pc":7265540024} 4 | 2 {"pc":7268527652} 5 | 2 {"pc":4309232304} 6 | 2 {"pc":4309232612} 7 | 2 {"pc":7315379200} 8 | 2 {"pc":7315378688} 9 | 2 {"pc":7315378440} 10 | 2 {"pc":7315378172} 11 | 2 {"pc":7315377960} 12 | 2 {"pc":7315365224} 13 | 2 {"pc":7315363460} 14 | 2 {"pc":7315363064} 15 | 2 {"pc":7315359584} 16 | 2 {"pc":7315352500} 17 | 2 {"pc":7314776068} 18 | 2 {"pc":7314773004} 19 | 2 {"pc":7314768752} 20 | 2 {"pc":7317622800} 21 | 2 {"pc":7313201628} 22 | 2 {"pc":7313010824} 23 | 2 {"pc":4309233164} 24 | 2 {"pc":4309233108} 25 | 2 {"pc":4309233384} 26 | 2 {"pc":4313256180} 27 | 3 {"address":7267287040, "uuid":"16FADF8B-A3AF-3E0C-B5FB-CE9A9CDC8747", "path":"/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"} 28 | 3 {"address":7265443840, "uuid":"2EB32E1D-90CB-3256-8681-9034085A06A6", "path":"/usr/lib/libobjc.A.dylib"} 29 | 3 {"address":7267287040, "uuid":"16FADF8B-A3AF-3E0C-B5FB-CE9A9CDC8747", "path":"/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation"} 30 | 3 {"address":4309204992, "uuid":"EED71F69-9B36-34C0-97D2-ACA3784A7FB8", "path":"/Users/USER/Library/Developer/Xcode/DerivedData/MetricKitTest-cqwuvojqzcbwndfiobiaxddtbmzd/Build/Products/Debug/MetricKitTest.app/Contents/MacOS/MetricKitTest"} 31 | 3 {"address":7312994304, "uuid":"1B65B629-6D3B-3FCD-AD43-E5683A9F94B7", "path":"/System/Library/Frameworks/AppKit.framework/Versions/C/AppKit"} 32 | 3 {"address":4309204992, "uuid":"EED71F69-9B36-34C0-97D2-ACA3784A7FB8", "path":"/Users/USER/Library/Developer/Xcode/DerivedData/MetricKitTest-cqwuvojqzcbwndfiobiaxddtbmzd/Build/Products/Debug/MetricKitTest.app/Contents/MacOS/MetricKitTest"} 33 | 34 | -------------------------------------------------------------------------------- /Tests/MeterTests/Resources/ios_uncaugh_exception_payload.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeStampEnd" : "2020-10-12 03:59:00 +0000", 3 | "timeStampBegin" : "2020-10-11 04:00:00 +0000", 4 | "crashDiagnostics" : [ 5 | { 6 | "version" : "1.0.0", 7 | "callStackTree" : { 8 | "callStacks" : [ 9 | { 10 | "threadAttributed" : true, 11 | "callStackRootFrames" : [ 12 | { 13 | "binaryUUID" : "7C9A24C6-CB84-3053-8481-DA167DA8FB9C", 14 | "offsetIntoBinaryTextSegment" : 4331307008, 15 | "sampleCount" : 1, 16 | "subFrames" : [ 17 | { 18 | "binaryUUID" : "7C9A24C6-CB84-3053-8481-DA167DA8FB9C", 19 | "offsetIntoBinaryTextSegment" : 4331307008, 20 | "sampleCount" : 1, 21 | "subFrames" : [ 22 | { 23 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 24 | "offsetIntoBinaryTextSegment" : 6900813824, 25 | "sampleCount" : 1, 26 | "subFrames" : [ 27 | { 28 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 29 | "offsetIntoBinaryTextSegment" : 6900813824, 30 | "sampleCount" : 1, 31 | "subFrames" : [ 32 | { 33 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 34 | "offsetIntoBinaryTextSegment" : 6900813824, 35 | "sampleCount" : 1, 36 | "subFrames" : [ 37 | { 38 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 39 | "offsetIntoBinaryTextSegment" : 6900813824, 40 | "sampleCount" : 1, 41 | "subFrames" : [ 42 | { 43 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 44 | "offsetIntoBinaryTextSegment" : 6900813824, 45 | "sampleCount" : 1, 46 | "subFrames" : [ 47 | { 48 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 49 | "offsetIntoBinaryTextSegment" : 6900813824, 50 | "sampleCount" : 1, 51 | "subFrames" : [ 52 | { 53 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 54 | "offsetIntoBinaryTextSegment" : 6900813824, 55 | "sampleCount" : 1, 56 | "subFrames" : [ 57 | { 58 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 59 | "offsetIntoBinaryTextSegment" : 6900813824, 60 | "sampleCount" : 1, 61 | "subFrames" : [ 62 | { 63 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 64 | "offsetIntoBinaryTextSegment" : 6900813824, 65 | "sampleCount" : 1, 66 | "subFrames" : [ 67 | { 68 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 69 | "offsetIntoBinaryTextSegment" : 6900813824, 70 | "sampleCount" : 1, 71 | "subFrames" : [ 72 | { 73 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 74 | "offsetIntoBinaryTextSegment" : 6829649920, 75 | "sampleCount" : 1, 76 | "subFrames" : [ 77 | { 78 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 79 | "offsetIntoBinaryTextSegment" : 6829649920, 80 | "sampleCount" : 1, 81 | "subFrames" : [ 82 | { 83 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 84 | "offsetIntoBinaryTextSegment" : 6829649920, 85 | "sampleCount" : 1, 86 | "subFrames" : [ 87 | { 88 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 89 | "offsetIntoBinaryTextSegment" : 6829649920, 90 | "sampleCount" : 1, 91 | "subFrames" : [ 92 | { 93 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 94 | "offsetIntoBinaryTextSegment" : 6829649920, 95 | "sampleCount" : 1, 96 | "subFrames" : [ 97 | { 98 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 99 | "offsetIntoBinaryTextSegment" : 6829649920, 100 | "sampleCount" : 1, 101 | "subFrames" : [ 102 | { 103 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 104 | "offsetIntoBinaryTextSegment" : 6829649920, 105 | "sampleCount" : 1, 106 | "subFrames" : [ 107 | { 108 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 109 | "offsetIntoBinaryTextSegment" : 6829649920, 110 | "sampleCount" : 1, 111 | "subFrames" : [ 112 | { 113 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 114 | "offsetIntoBinaryTextSegment" : 6829649920, 115 | "sampleCount" : 1, 116 | "subFrames" : [ 117 | { 118 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 119 | "offsetIntoBinaryTextSegment" : 6797963264, 120 | "sampleCount" : 1, 121 | "subFrames" : [ 122 | { 123 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 124 | "offsetIntoBinaryTextSegment" : 6797963264, 125 | "sampleCount" : 1, 126 | "subFrames" : [ 127 | { 128 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 129 | "offsetIntoBinaryTextSegment" : 6797963264, 130 | "sampleCount" : 1, 131 | "subFrames" : [ 132 | { 133 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 134 | "offsetIntoBinaryTextSegment" : 6797963264, 135 | "sampleCount" : 1, 136 | "subFrames" : [ 137 | { 138 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 139 | "offsetIntoBinaryTextSegment" : 6797963264, 140 | "sampleCount" : 1, 141 | "subFrames" : [ 142 | { 143 | "binaryUUID" : "7762C161-3C1B-3E56-BA75-9CB873604898", 144 | "offsetIntoBinaryTextSegment" : 7168679936, 145 | "sampleCount" : 1, 146 | "subFrames" : [ 147 | { 148 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 149 | "offsetIntoBinaryTextSegment" : 6829649920, 150 | "sampleCount" : 1, 151 | "subFrames" : [ 152 | { 153 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 154 | "offsetIntoBinaryTextSegment" : 6829649920, 155 | "sampleCount" : 1, 156 | "subFrames" : [ 157 | { 158 | "binaryUUID" : "7C9A24C6-CB84-3053-8481-DA167DA8FB9C", 159 | "offsetIntoBinaryTextSegment" : 4331307008, 160 | "sampleCount" : 1, 161 | "subFrames" : [ 162 | { 163 | "binaryUUID" : "77E57314-8A58-3064-90C0-8AF9A4745430", 164 | "offsetIntoBinaryTextSegment" : 6795280384, 165 | "sampleCount" : 1, 166 | "binaryName" : "libdyld.dylib", 167 | "address" : 6795285912 168 | } 169 | ], 170 | "binaryName" : "MetricKitTest", 171 | "address" : 4331338160 172 | } 173 | ], 174 | "binaryName" : "UIKitCore", 175 | "address" : 6841341400 176 | } 177 | ], 178 | "binaryName" : "UIKitCore", 179 | "address" : 6841319428 180 | } 181 | ], 182 | "binaryName" : "GraphicsServices", 183 | "address" : 7168693656 184 | } 185 | ], 186 | "binaryName" : "CoreFoundation", 187 | "address" : 6798565888 188 | } 189 | ], 190 | "binaryName" : "CoreFoundation", 191 | "address" : 6798568000 192 | } 193 | ], 194 | "binaryName" : "CoreFoundation", 195 | "address" : 6798591112 196 | } 197 | ], 198 | "binaryName" : "CoreFoundation", 199 | "address" : 6798594368 200 | } 201 | ], 202 | "binaryName" : "CoreFoundation", 203 | "address" : 6798594624 204 | } 205 | ], 206 | "binaryName" : "UIKitCore", 207 | "address" : 6841959552 208 | } 209 | ], 210 | "binaryName" : "UIKitCore", 211 | "address" : 6841995712 212 | } 213 | ], 214 | "binaryName" : "UIKitCore", 215 | "address" : 6841977376 216 | } 217 | ], 218 | "binaryName" : "UIKitCore", 219 | "address" : 6841444660 220 | } 221 | ], 222 | "binaryName" : "UIKitCore", 223 | "address" : 6841595188 224 | } 225 | ], 226 | "binaryName" : "UIKitCore", 227 | "address" : 6836611588 228 | } 229 | ], 230 | "binaryName" : "UIKitCore", 231 | "address" : 6836612372 232 | } 233 | ], 234 | "binaryName" : "UIKitCore", 235 | "address" : 6841884856 236 | } 237 | ], 238 | "binaryName" : "UIKitCore", 239 | "address" : 6836652972 240 | } 241 | ], 242 | "binaryName" : "SwiftUI", 243 | "address" : 6909904924 244 | } 245 | ], 246 | "binaryName" : "SwiftUI", 247 | "address" : 6909901984 248 | } 249 | ], 250 | "binaryName" : "SwiftUI", 251 | "address" : 6909909448 252 | } 253 | ], 254 | "binaryName" : "SwiftUI", 255 | "address" : 6905817256 256 | } 257 | ], 258 | "binaryName" : "SwiftUI", 259 | "address" : 6905526492 260 | } 261 | ], 262 | "binaryName" : "SwiftUI", 263 | "address" : 6905585528 264 | } 265 | ], 266 | "binaryName" : "SwiftUI", 267 | "address" : 6905585556 268 | } 269 | ], 270 | "binaryName" : "SwiftUI", 271 | "address" : 6905585528 272 | } 273 | ], 274 | "binaryName" : "SwiftUI", 275 | "address" : 6907555208 276 | } 277 | ], 278 | "binaryName" : "SwiftUI", 279 | "address" : 6902833692 280 | } 281 | ], 282 | "binaryName" : "MetricKitTest", 283 | "address" : 4331345300 284 | } 285 | ], 286 | "binaryName" : "MetricKitTest", 287 | "address" : 4331327932 288 | } 289 | ] 290 | }, 291 | { 292 | "threadAttributed" : false, 293 | "callStackRootFrames" : [ 294 | { 295 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 296 | "offsetIntoBinaryTextSegment" : 7966019584, 297 | "sampleCount" : 1, 298 | "binaryName" : "libsystem_pthread.dylib", 299 | "address" : 7966079084 300 | } 301 | ] 302 | }, 303 | { 304 | "threadAttributed" : false, 305 | "callStackRootFrames" : [ 306 | { 307 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 308 | "offsetIntoBinaryTextSegment" : 7966019584, 309 | "sampleCount" : 1, 310 | "binaryName" : "libsystem_pthread.dylib", 311 | "address" : 7966079084 312 | } 313 | ] 314 | }, 315 | { 316 | "threadAttributed" : false, 317 | "callStackRootFrames" : [ 318 | { 319 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 320 | "offsetIntoBinaryTextSegment" : 7966019584, 321 | "sampleCount" : 1, 322 | "binaryName" : "libsystem_pthread.dylib", 323 | "address" : 7966079084 324 | } 325 | ] 326 | }, 327 | { 328 | "threadAttributed" : false, 329 | "callStackRootFrames" : [ 330 | { 331 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 332 | "offsetIntoBinaryTextSegment" : 7966019584, 333 | "sampleCount" : 1, 334 | "binaryName" : "libsystem_pthread.dylib", 335 | "address" : 7966079084 336 | } 337 | ] 338 | }, 339 | { 340 | "threadAttributed" : false, 341 | "callStackRootFrames" : [ 342 | { 343 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 344 | "offsetIntoBinaryTextSegment" : 7966019584, 345 | "sampleCount" : 1, 346 | "binaryName" : "libsystem_pthread.dylib", 347 | "address" : 7966079084 348 | } 349 | ] 350 | }, 351 | { 352 | "threadAttributed" : false, 353 | "callStackRootFrames" : [ 354 | { 355 | "binaryUUID" : "82DBF088-D587-35CF-9598-15F79C12EAA4", 356 | "offsetIntoBinaryTextSegment" : 7513526272, 357 | "sampleCount" : 1, 358 | "subFrames" : [ 359 | { 360 | "binaryUUID" : "82DBF088-D587-35CF-9598-15F79C12EAA4", 361 | "offsetIntoBinaryTextSegment" : 7513526272, 362 | "sampleCount" : 1, 363 | "subFrames" : [ 364 | { 365 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 366 | "offsetIntoBinaryTextSegment" : 6797963264, 367 | "sampleCount" : 1, 368 | "subFrames" : [ 369 | { 370 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 371 | "offsetIntoBinaryTextSegment" : 6797963264, 372 | "sampleCount" : 1, 373 | "subFrames" : [ 374 | { 375 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 376 | "offsetIntoBinaryTextSegment" : 6797963264, 377 | "sampleCount" : 1, 378 | "subFrames" : [ 379 | { 380 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 381 | "offsetIntoBinaryTextSegment" : 6817447936, 382 | "sampleCount" : 1, 383 | "subFrames" : [ 384 | { 385 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 386 | "offsetIntoBinaryTextSegment" : 6817447936, 387 | "sampleCount" : 1, 388 | "subFrames" : [ 389 | { 390 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 391 | "offsetIntoBinaryTextSegment" : 6829649920, 392 | "sampleCount" : 1, 393 | "subFrames" : [ 394 | { 395 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 396 | "offsetIntoBinaryTextSegment" : 6817447936, 397 | "sampleCount" : 1, 398 | "subFrames" : [ 399 | { 400 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 401 | "offsetIntoBinaryTextSegment" : 7966019584, 402 | "sampleCount" : 1, 403 | "subFrames" : [ 404 | { 405 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 406 | "offsetIntoBinaryTextSegment" : 7966019584, 407 | "sampleCount" : 1, 408 | "binaryName" : "libsystem_pthread.dylib", 409 | "address" : 7966079104 410 | } 411 | ], 412 | "binaryName" : "libsystem_pthread.dylib", 413 | "address" : 7966059376 414 | } 415 | ], 416 | "binaryName" : "Foundation", 417 | "address" : 6818909256 418 | } 419 | ], 420 | "binaryName" : "UIKitCore", 421 | "address" : 6842022396 422 | } 423 | ], 424 | "binaryName" : "Foundation", 425 | "address" : 6817481048 426 | } 427 | ], 428 | "binaryName" : "Foundation", 429 | "address" : 6817481336 430 | } 431 | ], 432 | "binaryName" : "CoreFoundation", 433 | "address" : 6798565888 434 | } 435 | ], 436 | "binaryName" : "CoreFoundation", 437 | "address" : 6798568400 438 | } 439 | ], 440 | "binaryName" : "CoreFoundation", 441 | "address" : 6798591820 442 | } 443 | ], 444 | "binaryName" : "libsystem_kernel.dylib", 445 | "address" : 7513537736 446 | } 447 | ], 448 | "binaryName" : "libsystem_kernel.dylib", 449 | "address" : 7513540804 450 | } 451 | ] 452 | }, 453 | { 454 | "threadAttributed" : false, 455 | "callStackRootFrames" : [ 456 | { 457 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 458 | "offsetIntoBinaryTextSegment" : 7966019584, 459 | "sampleCount" : 1, 460 | "binaryName" : "libsystem_pthread.dylib", 461 | "address" : 7966079084 462 | } 463 | ] 464 | }, 465 | { 466 | "threadAttributed" : false, 467 | "callStackRootFrames" : [ 468 | { 469 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 470 | "offsetIntoBinaryTextSegment" : 7966019584, 471 | "sampleCount" : 1, 472 | "binaryName" : "libsystem_pthread.dylib", 473 | "address" : 7966079084 474 | } 475 | ] 476 | }, 477 | { 478 | "threadAttributed" : false, 479 | "callStackRootFrames" : [ 480 | { 481 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 482 | "offsetIntoBinaryTextSegment" : 7966019584, 483 | "sampleCount" : 1, 484 | "binaryName" : "libsystem_pthread.dylib", 485 | "address" : 7966079084 486 | } 487 | ] 488 | } 489 | ], 490 | "callStackPerThread" : true 491 | }, 492 | "diagnosticMetaData" : { 493 | "appBuildVersion" : "1", 494 | "appVersion" : "1.0", 495 | "regionFormat" : "CA", 496 | "exceptionType" : 1, 497 | "osVersion" : "iPhone OS 14.0.1 (18A393)", 498 | "deviceType" : "iPhone10,2", 499 | "signal" : 11, 500 | "exceptionCode" : 1, 501 | "platformArchitecture" : "arm64", 502 | "terminationReason" : "Namespace SIGNAL, Code 0xb", 503 | "virtualMemoryRegionInfo" : "0 is not in any region. Bytes before following region: 4331307008\n REGION TYPE START - END [ VSIZE] PRT\/MAX SHRMOD REGION DETAIL\n UNUSED SPACE AT START\n---> \n __TEXT 1022a8000-102300000 [ 352K] r-x\/r-x SM=COW ...MetricKitTest" 504 | } 505 | } 506 | ] 507 | } 508 | -------------------------------------------------------------------------------- /Tests/MeterTests/Resources/real_report.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeStampEnd" : "2020-10-11 03:59:00 +0000", 3 | "timeStampBegin" : "2020-10-10 04:00:00 +0000", 4 | "crashDiagnostics" : [ 5 | { 6 | "version" : "1.0.0", 7 | "callStackTree" : { 8 | "callStacks" : [ 9 | { 10 | "threadAttributed" : true, 11 | "callStackRootFrames" : [ 12 | { 13 | "binaryUUID" : "9156BE86-D4B6-3A81-8460-8728FA38C978", 14 | "offsetIntoBinaryTextSegment" : 6859616256, 15 | "sampleCount" : 1, 16 | "subFrames" : [ 17 | { 18 | "binaryUUID" : "9156BE86-D4B6-3A81-8460-8728FA38C978", 19 | "offsetIntoBinaryTextSegment" : 6859616256, 20 | "sampleCount" : 1, 21 | "subFrames" : [ 22 | { 23 | "binaryUUID" : "444F912B-06E7-395E-9E6E-D947B07401AC", 24 | "offsetIntoBinaryTextSegment" : 4303568896, 25 | "sampleCount" : 1, 26 | "subFrames" : [ 27 | { 28 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 29 | "offsetIntoBinaryTextSegment" : 6900813824, 30 | "sampleCount" : 1, 31 | "subFrames" : [ 32 | { 33 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 34 | "offsetIntoBinaryTextSegment" : 6900813824, 35 | "sampleCount" : 1, 36 | "subFrames" : [ 37 | { 38 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 39 | "offsetIntoBinaryTextSegment" : 6900813824, 40 | "sampleCount" : 1, 41 | "subFrames" : [ 42 | { 43 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 44 | "offsetIntoBinaryTextSegment" : 6900813824, 45 | "sampleCount" : 1, 46 | "subFrames" : [ 47 | { 48 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 49 | "offsetIntoBinaryTextSegment" : 6900813824, 50 | "sampleCount" : 1, 51 | "subFrames" : [ 52 | { 53 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 54 | "offsetIntoBinaryTextSegment" : 6900813824, 55 | "sampleCount" : 1, 56 | "subFrames" : [ 57 | { 58 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 59 | "offsetIntoBinaryTextSegment" : 6900813824, 60 | "sampleCount" : 1, 61 | "subFrames" : [ 62 | { 63 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 64 | "offsetIntoBinaryTextSegment" : 6900813824, 65 | "sampleCount" : 1, 66 | "subFrames" : [ 67 | { 68 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 69 | "offsetIntoBinaryTextSegment" : 6900813824, 70 | "sampleCount" : 1, 71 | "subFrames" : [ 72 | { 73 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 74 | "offsetIntoBinaryTextSegment" : 6900813824, 75 | "sampleCount" : 1, 76 | "subFrames" : [ 77 | { 78 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 79 | "offsetIntoBinaryTextSegment" : 6829649920, 80 | "sampleCount" : 1, 81 | "subFrames" : [ 82 | { 83 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 84 | "offsetIntoBinaryTextSegment" : 6829649920, 85 | "sampleCount" : 1, 86 | "subFrames" : [ 87 | { 88 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 89 | "offsetIntoBinaryTextSegment" : 6829649920, 90 | "sampleCount" : 1, 91 | "subFrames" : [ 92 | { 93 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 94 | "offsetIntoBinaryTextSegment" : 6829649920, 95 | "sampleCount" : 1, 96 | "subFrames" : [ 97 | { 98 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 99 | "offsetIntoBinaryTextSegment" : 6829649920, 100 | "sampleCount" : 1, 101 | "subFrames" : [ 102 | { 103 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 104 | "offsetIntoBinaryTextSegment" : 6829649920, 105 | "sampleCount" : 1, 106 | "subFrames" : [ 107 | { 108 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 109 | "offsetIntoBinaryTextSegment" : 6829649920, 110 | "sampleCount" : 1, 111 | "subFrames" : [ 112 | { 113 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 114 | "offsetIntoBinaryTextSegment" : 6829649920, 115 | "sampleCount" : 1, 116 | "subFrames" : [ 117 | { 118 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 119 | "offsetIntoBinaryTextSegment" : 6829649920, 120 | "sampleCount" : 1, 121 | "subFrames" : [ 122 | { 123 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 124 | "offsetIntoBinaryTextSegment" : 6797963264, 125 | "sampleCount" : 1, 126 | "subFrames" : [ 127 | { 128 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 129 | "offsetIntoBinaryTextSegment" : 6797963264, 130 | "sampleCount" : 1, 131 | "subFrames" : [ 132 | { 133 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 134 | "offsetIntoBinaryTextSegment" : 6797963264, 135 | "sampleCount" : 1, 136 | "subFrames" : [ 137 | { 138 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 139 | "offsetIntoBinaryTextSegment" : 6797963264, 140 | "sampleCount" : 1, 141 | "subFrames" : [ 142 | { 143 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 144 | "offsetIntoBinaryTextSegment" : 6797963264, 145 | "sampleCount" : 1, 146 | "subFrames" : [ 147 | { 148 | "binaryUUID" : "7762C161-3C1B-3E56-BA75-9CB873604898", 149 | "offsetIntoBinaryTextSegment" : 7168679936, 150 | "sampleCount" : 1, 151 | "subFrames" : [ 152 | { 153 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 154 | "offsetIntoBinaryTextSegment" : 6829649920, 155 | "sampleCount" : 1, 156 | "subFrames" : [ 157 | { 158 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 159 | "offsetIntoBinaryTextSegment" : 6829649920, 160 | "sampleCount" : 1, 161 | "subFrames" : [ 162 | { 163 | "binaryUUID" : "444F912B-06E7-395E-9E6E-D947B07401AC", 164 | "offsetIntoBinaryTextSegment" : 4303568896, 165 | "sampleCount" : 1, 166 | "subFrames" : [ 167 | { 168 | "binaryUUID" : "77E57314-8A58-3064-90C0-8AF9A4745430", 169 | "offsetIntoBinaryTextSegment" : 6795280384, 170 | "sampleCount" : 1, 171 | "binaryName" : "libdyld.dylib", 172 | "address" : 6795285912 173 | } 174 | ], 175 | "binaryName" : "MetricKitTest", 176 | "address" : 4303603064 177 | } 178 | ], 179 | "binaryName" : "UIKitCore", 180 | "address" : 6841341400 181 | } 182 | ], 183 | "binaryName" : "UIKitCore", 184 | "address" : 6841319428 185 | } 186 | ], 187 | "binaryName" : "GraphicsServices", 188 | "address" : 7168693656 189 | } 190 | ], 191 | "binaryName" : "CoreFoundation", 192 | "address" : 6798565888 193 | } 194 | ], 195 | "binaryName" : "CoreFoundation", 196 | "address" : 6798568000 197 | } 198 | ], 199 | "binaryName" : "CoreFoundation", 200 | "address" : 6798591112 201 | } 202 | ], 203 | "binaryName" : "CoreFoundation", 204 | "address" : 6798594368 205 | } 206 | ], 207 | "binaryName" : "CoreFoundation", 208 | "address" : 6798594624 209 | } 210 | ], 211 | "binaryName" : "UIKitCore", 212 | "address" : 6841959552 213 | } 214 | ], 215 | "binaryName" : "UIKitCore", 216 | "address" : 6841995712 217 | } 218 | ], 219 | "binaryName" : "UIKitCore", 220 | "address" : 6841977376 221 | } 222 | ], 223 | "binaryName" : "UIKitCore", 224 | "address" : 6841444660 225 | } 226 | ], 227 | "binaryName" : "UIKitCore", 228 | "address" : 6841595188 229 | } 230 | ], 231 | "binaryName" : "UIKitCore", 232 | "address" : 6836611588 233 | } 234 | ], 235 | "binaryName" : "UIKitCore", 236 | "address" : 6836612372 237 | } 238 | ], 239 | "binaryName" : "UIKitCore", 240 | "address" : 6841884856 241 | } 242 | ], 243 | "binaryName" : "UIKitCore", 244 | "address" : 6836652972 245 | } 246 | ], 247 | "binaryName" : "SwiftUI", 248 | "address" : 6909904924 249 | } 250 | ], 251 | "binaryName" : "SwiftUI", 252 | "address" : 6909901984 253 | } 254 | ], 255 | "binaryName" : "SwiftUI", 256 | "address" : 6909909448 257 | } 258 | ], 259 | "binaryName" : "SwiftUI", 260 | "address" : 6905817256 261 | } 262 | ], 263 | "binaryName" : "SwiftUI", 264 | "address" : 6905526492 265 | } 266 | ], 267 | "binaryName" : "SwiftUI", 268 | "address" : 6905585528 269 | } 270 | ], 271 | "binaryName" : "SwiftUI", 272 | "address" : 6905585556 273 | } 274 | ], 275 | "binaryName" : "SwiftUI", 276 | "address" : 6905585528 277 | } 278 | ], 279 | "binaryName" : "SwiftUI", 280 | "address" : 6907555208 281 | } 282 | ], 283 | "binaryName" : "SwiftUI", 284 | "address" : 6902833692 285 | } 286 | ], 287 | "binaryName" : "MetricKitTest", 288 | "address" : 4303609936 289 | } 290 | ], 291 | "binaryName" : "libswiftCore.dylib", 292 | "address" : 6859816880 293 | } 294 | ], 295 | "binaryName" : "libswiftCore.dylib", 296 | "address" : 6859816880 297 | } 298 | ] 299 | }, 300 | { 301 | "threadAttributed" : false, 302 | "callStackRootFrames" : [ 303 | { 304 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 305 | "offsetIntoBinaryTextSegment" : 7966019584, 306 | "sampleCount" : 1, 307 | "binaryName" : "libsystem_pthread.dylib", 308 | "address" : 7966079084 309 | } 310 | ] 311 | }, 312 | { 313 | "threadAttributed" : false, 314 | "callStackRootFrames" : [ 315 | { 316 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 317 | "offsetIntoBinaryTextSegment" : 7966019584, 318 | "sampleCount" : 1, 319 | "binaryName" : "libsystem_pthread.dylib", 320 | "address" : 7966079084 321 | } 322 | ] 323 | }, 324 | { 325 | "threadAttributed" : false, 326 | "callStackRootFrames" : [ 327 | { 328 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 329 | "offsetIntoBinaryTextSegment" : 7966019584, 330 | "sampleCount" : 1, 331 | "binaryName" : "libsystem_pthread.dylib", 332 | "address" : 7966079084 333 | } 334 | ] 335 | }, 336 | { 337 | "threadAttributed" : false, 338 | "callStackRootFrames" : [ 339 | { 340 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 341 | "offsetIntoBinaryTextSegment" : 7966019584, 342 | "sampleCount" : 1, 343 | "binaryName" : "libsystem_pthread.dylib", 344 | "address" : 7966079084 345 | } 346 | ] 347 | }, 348 | { 349 | "threadAttributed" : false, 350 | "callStackRootFrames" : [ 351 | { 352 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 353 | "offsetIntoBinaryTextSegment" : 7966019584, 354 | "sampleCount" : 1, 355 | "binaryName" : "libsystem_pthread.dylib", 356 | "address" : 7966079084 357 | } 358 | ] 359 | }, 360 | { 361 | "threadAttributed" : false, 362 | "callStackRootFrames" : [ 363 | { 364 | "binaryUUID" : "82DBF088-D587-35CF-9598-15F79C12EAA4", 365 | "offsetIntoBinaryTextSegment" : 7513526272, 366 | "sampleCount" : 1, 367 | "subFrames" : [ 368 | { 369 | "binaryUUID" : "82DBF088-D587-35CF-9598-15F79C12EAA4", 370 | "offsetIntoBinaryTextSegment" : 7513526272, 371 | "sampleCount" : 1, 372 | "subFrames" : [ 373 | { 374 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 375 | "offsetIntoBinaryTextSegment" : 6797963264, 376 | "sampleCount" : 1, 377 | "subFrames" : [ 378 | { 379 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 380 | "offsetIntoBinaryTextSegment" : 6797963264, 381 | "sampleCount" : 1, 382 | "subFrames" : [ 383 | { 384 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 385 | "offsetIntoBinaryTextSegment" : 6797963264, 386 | "sampleCount" : 1, 387 | "subFrames" : [ 388 | { 389 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 390 | "offsetIntoBinaryTextSegment" : 6817447936, 391 | "sampleCount" : 1, 392 | "subFrames" : [ 393 | { 394 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 395 | "offsetIntoBinaryTextSegment" : 6817447936, 396 | "sampleCount" : 1, 397 | "subFrames" : [ 398 | { 399 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 400 | "offsetIntoBinaryTextSegment" : 6829649920, 401 | "sampleCount" : 1, 402 | "subFrames" : [ 403 | { 404 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 405 | "offsetIntoBinaryTextSegment" : 6817447936, 406 | "sampleCount" : 1, 407 | "subFrames" : [ 408 | { 409 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 410 | "offsetIntoBinaryTextSegment" : 7966019584, 411 | "sampleCount" : 1, 412 | "subFrames" : [ 413 | { 414 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 415 | "offsetIntoBinaryTextSegment" : 7966019584, 416 | "sampleCount" : 1, 417 | "binaryName" : "libsystem_pthread.dylib", 418 | "address" : 7966079104 419 | } 420 | ], 421 | "binaryName" : "libsystem_pthread.dylib", 422 | "address" : 7966059376 423 | } 424 | ], 425 | "binaryName" : "Foundation", 426 | "address" : 6818909256 427 | } 428 | ], 429 | "binaryName" : "UIKitCore", 430 | "address" : 6842022396 431 | } 432 | ], 433 | "binaryName" : "Foundation", 434 | "address" : 6817481048 435 | } 436 | ], 437 | "binaryName" : "Foundation", 438 | "address" : 6817481336 439 | } 440 | ], 441 | "binaryName" : "CoreFoundation", 442 | "address" : 6798565888 443 | } 444 | ], 445 | "binaryName" : "CoreFoundation", 446 | "address" : 6798568400 447 | } 448 | ], 449 | "binaryName" : "CoreFoundation", 450 | "address" : 6798591820 451 | } 452 | ], 453 | "binaryName" : "libsystem_kernel.dylib", 454 | "address" : 7513537736 455 | } 456 | ], 457 | "binaryName" : "libsystem_kernel.dylib", 458 | "address" : 7513540804 459 | } 460 | ] 461 | }, 462 | { 463 | "threadAttributed" : false, 464 | "callStackRootFrames" : [ 465 | { 466 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 467 | "offsetIntoBinaryTextSegment" : 7966019584, 468 | "sampleCount" : 1, 469 | "binaryName" : "libsystem_pthread.dylib", 470 | "address" : 7966079084 471 | } 472 | ] 473 | }, 474 | { 475 | "threadAttributed" : false, 476 | "callStackRootFrames" : [ 477 | { 478 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 479 | "offsetIntoBinaryTextSegment" : 7966019584, 480 | "sampleCount" : 1, 481 | "binaryName" : "libsystem_pthread.dylib", 482 | "address" : 7966079084 483 | } 484 | ] 485 | }, 486 | { 487 | "threadAttributed" : false, 488 | "callStackRootFrames" : [ 489 | { 490 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 491 | "offsetIntoBinaryTextSegment" : 7966019584, 492 | "sampleCount" : 1, 493 | "binaryName" : "libsystem_pthread.dylib", 494 | "address" : 7966079084 495 | } 496 | ] 497 | } 498 | ], 499 | "callStackPerThread" : true 500 | }, 501 | "diagnosticMetaData" : { 502 | "appBuildVersion" : "1", 503 | "appVersion" : "1.0", 504 | "regionFormat" : "CA", 505 | "exceptionType" : 6, 506 | "osVersion" : "iPhone OS 14.0.1 (18A393)", 507 | "deviceType" : "iPhone10,2", 508 | "signal" : 5, 509 | "exceptionCode" : 1, 510 | "platformArchitecture" : "arm64", 511 | "terminationReason" : "Namespace SIGNAL, Code 0x5" 512 | } 513 | }, 514 | { 515 | "version" : "1.0.0", 516 | "callStackTree" : { 517 | "callStacks" : [ 518 | { 519 | "threadAttributed" : true, 520 | "callStackRootFrames" : [ 521 | { 522 | "binaryUUID" : "444F912B-06E7-395E-9E6E-D947B07401AC", 523 | "offsetIntoBinaryTextSegment" : 4303978496, 524 | "sampleCount" : 1, 525 | "subFrames" : [ 526 | { 527 | "binaryUUID" : "444F912B-06E7-395E-9E6E-D947B07401AC", 528 | "offsetIntoBinaryTextSegment" : 4303978496, 529 | "sampleCount" : 1, 530 | "subFrames" : [ 531 | { 532 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 533 | "offsetIntoBinaryTextSegment" : 6900813824, 534 | "sampleCount" : 1, 535 | "subFrames" : [ 536 | { 537 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 538 | "offsetIntoBinaryTextSegment" : 6900813824, 539 | "sampleCount" : 1, 540 | "subFrames" : [ 541 | { 542 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 543 | "offsetIntoBinaryTextSegment" : 6900813824, 544 | "sampleCount" : 1, 545 | "subFrames" : [ 546 | { 547 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 548 | "offsetIntoBinaryTextSegment" : 6900813824, 549 | "sampleCount" : 1, 550 | "subFrames" : [ 551 | { 552 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 553 | "offsetIntoBinaryTextSegment" : 6900813824, 554 | "sampleCount" : 1, 555 | "subFrames" : [ 556 | { 557 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 558 | "offsetIntoBinaryTextSegment" : 6900813824, 559 | "sampleCount" : 1, 560 | "subFrames" : [ 561 | { 562 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 563 | "offsetIntoBinaryTextSegment" : 6900813824, 564 | "sampleCount" : 1, 565 | "subFrames" : [ 566 | { 567 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 568 | "offsetIntoBinaryTextSegment" : 6900813824, 569 | "sampleCount" : 1, 570 | "subFrames" : [ 571 | { 572 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 573 | "offsetIntoBinaryTextSegment" : 6900813824, 574 | "sampleCount" : 1, 575 | "subFrames" : [ 576 | { 577 | "binaryUUID" : "E9BDE7BE-9A61-3A4C-A7D8-F14F9B216DD1", 578 | "offsetIntoBinaryTextSegment" : 6900813824, 579 | "sampleCount" : 1, 580 | "subFrames" : [ 581 | { 582 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 583 | "offsetIntoBinaryTextSegment" : 6829649920, 584 | "sampleCount" : 1, 585 | "subFrames" : [ 586 | { 587 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 588 | "offsetIntoBinaryTextSegment" : 6829649920, 589 | "sampleCount" : 1, 590 | "subFrames" : [ 591 | { 592 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 593 | "offsetIntoBinaryTextSegment" : 6829649920, 594 | "sampleCount" : 1, 595 | "subFrames" : [ 596 | { 597 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 598 | "offsetIntoBinaryTextSegment" : 6829649920, 599 | "sampleCount" : 1, 600 | "subFrames" : [ 601 | { 602 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 603 | "offsetIntoBinaryTextSegment" : 6829649920, 604 | "sampleCount" : 1, 605 | "subFrames" : [ 606 | { 607 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 608 | "offsetIntoBinaryTextSegment" : 6829649920, 609 | "sampleCount" : 1, 610 | "subFrames" : [ 611 | { 612 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 613 | "offsetIntoBinaryTextSegment" : 6829649920, 614 | "sampleCount" : 1, 615 | "subFrames" : [ 616 | { 617 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 618 | "offsetIntoBinaryTextSegment" : 6829649920, 619 | "sampleCount" : 1, 620 | "subFrames" : [ 621 | { 622 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 623 | "offsetIntoBinaryTextSegment" : 6829649920, 624 | "sampleCount" : 1, 625 | "subFrames" : [ 626 | { 627 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 628 | "offsetIntoBinaryTextSegment" : 6797963264, 629 | "sampleCount" : 1, 630 | "subFrames" : [ 631 | { 632 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 633 | "offsetIntoBinaryTextSegment" : 6797963264, 634 | "sampleCount" : 1, 635 | "subFrames" : [ 636 | { 637 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 638 | "offsetIntoBinaryTextSegment" : 6797963264, 639 | "sampleCount" : 1, 640 | "subFrames" : [ 641 | { 642 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 643 | "offsetIntoBinaryTextSegment" : 6797963264, 644 | "sampleCount" : 1, 645 | "subFrames" : [ 646 | { 647 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 648 | "offsetIntoBinaryTextSegment" : 6797963264, 649 | "sampleCount" : 1, 650 | "subFrames" : [ 651 | { 652 | "binaryUUID" : "7762C161-3C1B-3E56-BA75-9CB873604898", 653 | "offsetIntoBinaryTextSegment" : 7168679936, 654 | "sampleCount" : 1, 655 | "subFrames" : [ 656 | { 657 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 658 | "offsetIntoBinaryTextSegment" : 6829649920, 659 | "sampleCount" : 1, 660 | "subFrames" : [ 661 | { 662 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 663 | "offsetIntoBinaryTextSegment" : 6829649920, 664 | "sampleCount" : 1, 665 | "subFrames" : [ 666 | { 667 | "binaryUUID" : "444F912B-06E7-395E-9E6E-D947B07401AC", 668 | "offsetIntoBinaryTextSegment" : 4303978496, 669 | "sampleCount" : 1, 670 | "subFrames" : [ 671 | { 672 | "binaryUUID" : "77E57314-8A58-3064-90C0-8AF9A4745430", 673 | "offsetIntoBinaryTextSegment" : 6795280384, 674 | "sampleCount" : 1, 675 | "binaryName" : "libdyld.dylib", 676 | "address" : 6795285912 677 | } 678 | ], 679 | "binaryName" : "MetricKitTest", 680 | "address" : 4304012664 681 | } 682 | ], 683 | "binaryName" : "UIKitCore", 684 | "address" : 6841341400 685 | } 686 | ], 687 | "binaryName" : "UIKitCore", 688 | "address" : 6841319428 689 | } 690 | ], 691 | "binaryName" : "GraphicsServices", 692 | "address" : 7168693656 693 | } 694 | ], 695 | "binaryName" : "CoreFoundation", 696 | "address" : 6798565888 697 | } 698 | ], 699 | "binaryName" : "CoreFoundation", 700 | "address" : 6798568000 701 | } 702 | ], 703 | "binaryName" : "CoreFoundation", 704 | "address" : 6798591112 705 | } 706 | ], 707 | "binaryName" : "CoreFoundation", 708 | "address" : 6798594368 709 | } 710 | ], 711 | "binaryName" : "CoreFoundation", 712 | "address" : 6798594624 713 | } 714 | ], 715 | "binaryName" : "UIKitCore", 716 | "address" : 6841959552 717 | } 718 | ], 719 | "binaryName" : "UIKitCore", 720 | "address" : 6841995712 721 | } 722 | ], 723 | "binaryName" : "UIKitCore", 724 | "address" : 6841977376 725 | } 726 | ], 727 | "binaryName" : "UIKitCore", 728 | "address" : 6841444660 729 | } 730 | ], 731 | "binaryName" : "UIKitCore", 732 | "address" : 6841595188 733 | } 734 | ], 735 | "binaryName" : "UIKitCore", 736 | "address" : 6836611588 737 | } 738 | ], 739 | "binaryName" : "UIKitCore", 740 | "address" : 6836612372 741 | } 742 | ], 743 | "binaryName" : "UIKitCore", 744 | "address" : 6841884856 745 | } 746 | ], 747 | "binaryName" : "UIKitCore", 748 | "address" : 6836652972 749 | } 750 | ], 751 | "binaryName" : "SwiftUI", 752 | "address" : 6909904924 753 | } 754 | ], 755 | "binaryName" : "SwiftUI", 756 | "address" : 6909901984 757 | } 758 | ], 759 | "binaryName" : "SwiftUI", 760 | "address" : 6909909448 761 | } 762 | ], 763 | "binaryName" : "SwiftUI", 764 | "address" : 6905817256 765 | } 766 | ], 767 | "binaryName" : "SwiftUI", 768 | "address" : 6905526492 769 | } 770 | ], 771 | "binaryName" : "SwiftUI", 772 | "address" : 6905585528 773 | } 774 | ], 775 | "binaryName" : "SwiftUI", 776 | "address" : 6905585556 777 | } 778 | ], 779 | "binaryName" : "SwiftUI", 780 | "address" : 6905585528 781 | } 782 | ], 783 | "binaryName" : "SwiftUI", 784 | "address" : 6907555208 785 | } 786 | ], 787 | "binaryName" : "SwiftUI", 788 | "address" : 6902833692 789 | } 790 | ], 791 | "binaryName" : "MetricKitTest", 792 | "address" : 4304019804 793 | } 794 | ], 795 | "binaryName" : "MetricKitTest", 796 | "address" : 4304003120 797 | } 798 | ] 799 | }, 800 | { 801 | "threadAttributed" : false, 802 | "callStackRootFrames" : [ 803 | { 804 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 805 | "offsetIntoBinaryTextSegment" : 7966019584, 806 | "sampleCount" : 1, 807 | "binaryName" : "libsystem_pthread.dylib", 808 | "address" : 7966079084 809 | } 810 | ] 811 | }, 812 | { 813 | "threadAttributed" : false, 814 | "callStackRootFrames" : [ 815 | { 816 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 817 | "offsetIntoBinaryTextSegment" : 7966019584, 818 | "sampleCount" : 1, 819 | "binaryName" : "libsystem_pthread.dylib", 820 | "address" : 7966079084 821 | } 822 | ] 823 | }, 824 | { 825 | "threadAttributed" : false, 826 | "callStackRootFrames" : [ 827 | { 828 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 829 | "offsetIntoBinaryTextSegment" : 7966019584, 830 | "sampleCount" : 1, 831 | "binaryName" : "libsystem_pthread.dylib", 832 | "address" : 7966079084 833 | } 834 | ] 835 | }, 836 | { 837 | "threadAttributed" : false, 838 | "callStackRootFrames" : [ 839 | { 840 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 841 | "offsetIntoBinaryTextSegment" : 7966019584, 842 | "sampleCount" : 1, 843 | "binaryName" : "libsystem_pthread.dylib", 844 | "address" : 7966079084 845 | } 846 | ] 847 | }, 848 | { 849 | "threadAttributed" : false, 850 | "callStackRootFrames" : [ 851 | { 852 | "binaryUUID" : "82DBF088-D587-35CF-9598-15F79C12EAA4", 853 | "offsetIntoBinaryTextSegment" : 7513526272, 854 | "sampleCount" : 1, 855 | "subFrames" : [ 856 | { 857 | "binaryUUID" : "82DBF088-D587-35CF-9598-15F79C12EAA4", 858 | "offsetIntoBinaryTextSegment" : 7513526272, 859 | "sampleCount" : 1, 860 | "subFrames" : [ 861 | { 862 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 863 | "offsetIntoBinaryTextSegment" : 6797963264, 864 | "sampleCount" : 1, 865 | "subFrames" : [ 866 | { 867 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 868 | "offsetIntoBinaryTextSegment" : 6797963264, 869 | "sampleCount" : 1, 870 | "subFrames" : [ 871 | { 872 | "binaryUUID" : "F80FCA31-BF76-3293-8BC6-1729588AE8B6", 873 | "offsetIntoBinaryTextSegment" : 6797963264, 874 | "sampleCount" : 1, 875 | "subFrames" : [ 876 | { 877 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 878 | "offsetIntoBinaryTextSegment" : 6817447936, 879 | "sampleCount" : 1, 880 | "subFrames" : [ 881 | { 882 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 883 | "offsetIntoBinaryTextSegment" : 6817447936, 884 | "sampleCount" : 1, 885 | "subFrames" : [ 886 | { 887 | "binaryUUID" : "00EA1426-38F7-3FD2-BE01-04EBD44ECA35", 888 | "offsetIntoBinaryTextSegment" : 6829649920, 889 | "sampleCount" : 1, 890 | "subFrames" : [ 891 | { 892 | "binaryUUID" : "4B7B9C0A-BAD3-348D-95A3-94784BFED02E", 893 | "offsetIntoBinaryTextSegment" : 6817447936, 894 | "sampleCount" : 1, 895 | "subFrames" : [ 896 | { 897 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 898 | "offsetIntoBinaryTextSegment" : 7966019584, 899 | "sampleCount" : 1, 900 | "subFrames" : [ 901 | { 902 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 903 | "offsetIntoBinaryTextSegment" : 7966019584, 904 | "sampleCount" : 1, 905 | "binaryName" : "libsystem_pthread.dylib", 906 | "address" : 7966079104 907 | } 908 | ], 909 | "binaryName" : "libsystem_pthread.dylib", 910 | "address" : 7966059376 911 | } 912 | ], 913 | "binaryName" : "Foundation", 914 | "address" : 6818909256 915 | } 916 | ], 917 | "binaryName" : "UIKitCore", 918 | "address" : 6842022396 919 | } 920 | ], 921 | "binaryName" : "Foundation", 922 | "address" : 6817481048 923 | } 924 | ], 925 | "binaryName" : "Foundation", 926 | "address" : 6817481336 927 | } 928 | ], 929 | "binaryName" : "CoreFoundation", 930 | "address" : 6798565888 931 | } 932 | ], 933 | "binaryName" : "CoreFoundation", 934 | "address" : 6798568400 935 | } 936 | ], 937 | "binaryName" : "CoreFoundation", 938 | "address" : 6798591820 939 | } 940 | ], 941 | "binaryName" : "libsystem_kernel.dylib", 942 | "address" : 7513537736 943 | } 944 | ], 945 | "binaryName" : "libsystem_kernel.dylib", 946 | "address" : 7513540804 947 | } 948 | ] 949 | }, 950 | { 951 | "threadAttributed" : false, 952 | "callStackRootFrames" : [ 953 | { 954 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 955 | "offsetIntoBinaryTextSegment" : 7966019584, 956 | "sampleCount" : 1, 957 | "binaryName" : "libsystem_pthread.dylib", 958 | "address" : 7966079084 959 | } 960 | ] 961 | }, 962 | { 963 | "threadAttributed" : false, 964 | "callStackRootFrames" : [ 965 | { 966 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 967 | "offsetIntoBinaryTextSegment" : 7966019584, 968 | "sampleCount" : 1, 969 | "binaryName" : "libsystem_pthread.dylib", 970 | "address" : 7966079084 971 | } 972 | ] 973 | }, 974 | { 975 | "threadAttributed" : false, 976 | "callStackRootFrames" : [ 977 | { 978 | "binaryUUID" : "9A318917-27DB-312E-91D6-81661034FCC6", 979 | "offsetIntoBinaryTextSegment" : 7966019584, 980 | "sampleCount" : 1, 981 | "binaryName" : "libsystem_pthread.dylib", 982 | "address" : 7966079084 983 | } 984 | ] 985 | } 986 | ], 987 | "callStackPerThread" : true 988 | }, 989 | "diagnosticMetaData" : { 990 | "appBuildVersion" : "1", 991 | "appVersion" : "1.0", 992 | "regionFormat" : "CA", 993 | "exceptionType" : 1, 994 | "osVersion" : "iPhone OS 14.0.1 (18A393)", 995 | "deviceType" : "iPhone10,2", 996 | "signal" : 11, 997 | "exceptionCode" : 1, 998 | "platformArchitecture" : "arm64", 999 | "terminationReason" : "Namespace SIGNAL, Code 0xb", 1000 | "virtualMemoryRegionInfo" : "0 is not in any region. Bytes before following region: 4303978496\n REGION TYPE START - END [ VSIZE] PRT\/MAX SHRMOD REGION DETAIL\n UNUSED SPACE AT START\n---> \n __TEXT 100898000-1008e4000 [ 304K] r-x\/r-x SM=COW ...MetricKitTest" 1001 | } 1002 | } 1003 | ] 1004 | } 1005 | -------------------------------------------------------------------------------- /Tests/MeterTests/Resources/xcode_simulated.crash: -------------------------------------------------------------------------------- 1 | Process: testBinaryName [0] 2 | Path: 3 | Identifier: testBinaryName 4 | Version: 1 5 | Code Type: ARM-64 (Native) 6 | Parent Process: ??? [0] 7 | Responsible: testBinaryName [0] 8 | User ID: 0 9 | 10 | -------------------------------------------------------------------------------- /Tests/MeterTests/Resources/xcode_simulated.json: -------------------------------------------------------------------------------- 1 | { 2 | "crashDiagnostics" : [ 3 | { 4 | "version" : "1.0.0", 5 | "callStackTree" : { 6 | "callStacks" : [ 7 | { 8 | "threadAttributed" : true, 9 | "callStackRootFrames" : [ 10 | { 11 | "binaryUUID" : "CDB53DDB-2337-4933-B62F-4356E6174AF0", 12 | "offsetIntoBinaryTextSegment" : 123, 13 | "sampleCount" : 20, 14 | "binaryName" : "testBinaryName", 15 | "address" : 74565 16 | } 17 | ] 18 | } 19 | ], 20 | "callStackPerThread" : true 21 | }, 22 | "diagnosticMetaData" : { 23 | "appBuildVersion" : "1", 24 | "appVersion" : "1.0", 25 | "regionFormat" : "CA", 26 | "exceptionType" : 1, 27 | "osVersion" : "iPhone OS 14.0.1 (18A393)", 28 | "deviceType" : "iPhone10,2", 29 | "signal" : 11, 30 | "exceptionCode" : 0, 31 | "platformArchitecture" : "arm64", 32 | "terminationReason" : "Namespace SIGNAL, Code 0xb", 33 | "virtualMemoryRegionInfo" : "0 is not in any region. Bytes before following region: 4000000000 REGION TYPE START - END [ VSIZE] PRT\/MAX SHRMOD REGION DETAIL UNUSED SPACE AT START ---> __TEXT 0000000000000000-0000000000000000 [ 32K] r-x\/r-x SM=COW ...pp\/Test" 34 | } 35 | } 36 | ], 37 | "timeStampEnd" : "2020-10-10 19:35:24 +0000", 38 | "hangDiagnostics" : [ 39 | { 40 | "version" : "1.0.0", 41 | "callStackTree" : { 42 | "callStacks" : [ 43 | { 44 | "threadAttributed" : true, 45 | "callStackRootFrames" : [ 46 | { 47 | "binaryUUID" : "CDB53DDB-2337-4933-B62F-4356E6174AF0", 48 | "offsetIntoBinaryTextSegment" : 123, 49 | "sampleCount" : 20, 50 | "binaryName" : "testBinaryName", 51 | "address" : 74565 52 | } 53 | ] 54 | } 55 | ], 56 | "callStackPerThread" : true 57 | }, 58 | "diagnosticMetaData" : { 59 | "appBuildVersion" : "1", 60 | "appVersion" : "1.0", 61 | "regionFormat" : "CA", 62 | "hangDuration" : "20 secs.", 63 | "osVersion" : "iPhone OS 14.0.1 (18A393)", 64 | "deviceType" : "iPhone10,2", 65 | "platformArchitecture" : "arm64" 66 | } 67 | } 68 | ], 69 | "cpuExceptionDiagnostics" : [ 70 | { 71 | "version" : "1.0.0", 72 | "callStackTree" : { 73 | "callStacks" : [ 74 | { 75 | "callStackRootFrames" : [ 76 | { 77 | "binaryUUID" : "50E04296-0089-4EA2-9125-9C7E2D6CE8E6", 78 | "offsetIntoBinaryTextSegment" : 123, 79 | "sampleCount" : 20, 80 | "binaryName" : "testBinaryName", 81 | "address" : 74565 82 | } 83 | ] 84 | } 85 | ], 86 | "callStackPerThread" : false 87 | }, 88 | "diagnosticMetaData" : { 89 | "appBuildVersion" : "1", 90 | "appVersion" : "1.0", 91 | "regionFormat" : "CA", 92 | "osVersion" : "iPhone OS 14.0.1 (18A393)", 93 | "deviceType" : "iPhone10,2", 94 | "platformArchitecture" : "arm64", 95 | "totalCPUTime" : "20 secs.", 96 | "totalSampledTime" : "20 secs." 97 | } 98 | } 99 | ], 100 | "timeStampBegin" : "2020-10-10 19:35:24 +0000", 101 | "diskWriteExceptionDiagnostics" : [ 102 | { 103 | "version" : "1.0.0", 104 | "callStackTree" : { 105 | "callStacks" : [ 106 | { 107 | "callStackRootFrames" : [ 108 | { 109 | "binaryUUID" : "50E04296-0089-4EA2-9125-9C7E2D6CE8E6", 110 | "offsetIntoBinaryTextSegment" : 123, 111 | "sampleCount" : 20, 112 | "binaryName" : "testBinaryName", 113 | "address" : 74565 114 | } 115 | ] 116 | } 117 | ], 118 | "callStackPerThread" : false 119 | }, 120 | "diagnosticMetaData" : { 121 | "appBuildVersion" : "1", 122 | "appVersion" : "1.0", 123 | "regionFormat" : "CA", 124 | "writesCaused" : "2,000 byte", 125 | "osVersion" : "iPhone OS 14.0.1 (18A393)", 126 | "deviceType" : "iPhone10,2", 127 | "platformArchitecture" : "arm64" 128 | } 129 | } 130 | ] 131 | } -------------------------------------------------------------------------------- /Tests/MeterTests/Resources/xcode_simulated_macOS_12.json: -------------------------------------------------------------------------------- 1 | { 2 | "crashDiagnostics" : [ 3 | { 4 | "version" : "1.0.0", 5 | "callStackTree" : { 6 | "callStacks" : [ 7 | { 8 | "threadAttributed" : true, 9 | "callStackRootFrames" : [ 10 | { 11 | "binaryUUID" : "3AAA2D7B-FA6A-43EE-BCD3-0CB87D7AD39A", 12 | "offsetIntoBinaryTextSegment" : 123, 13 | "sampleCount" : 20, 14 | "binaryName" : "testBinaryName", 15 | "address" : 74565 16 | } 17 | ] 18 | } 19 | ], 20 | "callStackPerThread" : true 21 | }, 22 | "diagnosticMetaData" : { 23 | "platformArchitecture" : "arm64e", 24 | "terminationReason" : "Namespace SIGNAL, Code 0xb", 25 | "exceptionType" : 1, 26 | "appBuildVersion" : "", 27 | "osVersion" : "macOS 12.2 (21D49)", 28 | "bundleIdentifier" : "com.chimehq.MetricKitTest", 29 | "deviceType" : "MacBookPro18,3", 30 | "exceptionCode" : 0, 31 | "virtualMemoryRegionInfo" : "0 is not in any region. Bytes before following region: 4000000000 REGION TYPE START - END [ VSIZE] PRT\/MAX SHRMOD REGION DETAIL UNUSED SPACE AT START ---> __TEXT 0000000000000000-0000000000000000 [ 32K] r-x\/r-x SM=COW ...pp\/Test", 32 | "signal" : 11, 33 | "appVersion" : "", 34 | "regionFormat" : "CA" 35 | } 36 | } 37 | ], 38 | "timeStampEnd" : "2022-03-07 07:30:54", 39 | "hangDiagnostics" : [ 40 | { 41 | "version" : "1.0.0", 42 | "callStackTree" : { 43 | "callStacks" : [ 44 | { 45 | "threadAttributed" : true, 46 | "callStackRootFrames" : [ 47 | { 48 | "binaryUUID" : "3AAA2D7B-FA6A-43EE-BCD3-0CB87D7AD39A", 49 | "offsetIntoBinaryTextSegment" : 123, 50 | "sampleCount" : 20, 51 | "binaryName" : "testBinaryName", 52 | "address" : 74565 53 | } 54 | ] 55 | } 56 | ], 57 | "callStackPerThread" : true 58 | }, 59 | "diagnosticMetaData" : { 60 | "appBuildVersion" : "", 61 | "appVersion" : "", 62 | "regionFormat" : "CA", 63 | "hangDuration" : "20 sec", 64 | "osVersion" : "macOS 12.2 (21D49)", 65 | "deviceType" : "MacBookPro18,3", 66 | "bundleIdentifier" : "com.chimehq.MetricKitTest", 67 | "platformArchitecture" : "arm64e" 68 | } 69 | } 70 | ], 71 | "cpuExceptionDiagnostics" : [ 72 | { 73 | "version" : "1.0.0", 74 | "callStackTree" : { 75 | "callStacks" : [ 76 | { 77 | "callStackRootFrames" : [ 78 | { 79 | "binaryUUID" : "D80E43C4-AF16-4F1A-8296-C59FC0C054D0", 80 | "offsetIntoBinaryTextSegment" : 123, 81 | "sampleCount" : 20, 82 | "binaryName" : "testBinaryName", 83 | "address" : 74565 84 | } 85 | ] 86 | } 87 | ], 88 | "callStackPerThread" : false 89 | }, 90 | "diagnosticMetaData" : { 91 | "appBuildVersion" : "", 92 | "appVersion" : "", 93 | "regionFormat" : "CA", 94 | "osVersion" : "macOS 12.2 (21D49)", 95 | "deviceType" : "MacBookPro18,3", 96 | "bundleIdentifier" : "com.chimehq.MetricKitTest", 97 | "platformArchitecture" : "arm64e", 98 | "totalCPUTime" : "20 sec", 99 | "totalSampledTime" : "20 sec" 100 | } 101 | } 102 | ], 103 | "timeStampBegin" : "2022-03-07 07:30:54", 104 | "diskWriteExceptionDiagnostics" : [ 105 | { 106 | "version" : "1.0.0", 107 | "callStackTree" : { 108 | "callStacks" : [ 109 | { 110 | "callStackRootFrames" : [ 111 | { 112 | "binaryUUID" : "D80E43C4-AF16-4F1A-8296-C59FC0C054D0", 113 | "offsetIntoBinaryTextSegment" : 123, 114 | "sampleCount" : 20, 115 | "binaryName" : "testBinaryName", 116 | "address" : 74565 117 | } 118 | ] 119 | } 120 | ], 121 | "callStackPerThread" : false 122 | }, 123 | "diagnosticMetaData" : { 124 | "appBuildVersion" : "", 125 | "appVersion" : "", 126 | "regionFormat" : "CA", 127 | "writesCaused" : "2000 byte", 128 | "osVersion" : "macOS 12.2 (21D49)", 129 | "deviceType" : "MacBookPro18,3", 130 | "bundleIdentifier" : "com.chimehq.MetricKitTest", 131 | "platformArchitecture" : "arm64e" 132 | } 133 | } 134 | ] 135 | } 136 | -------------------------------------------------------------------------------- /Tests/MeterTests/Resources/xcode_simulated_macOS_13.json: -------------------------------------------------------------------------------- 1 | { 2 | "timeStampBegin" : "2022-09-28 10:59:30", 3 | "diskWriteExceptionDiagnostics" : [ 4 | { 5 | "version" : "1.0.0", 6 | "callStackTree" : { 7 | "callStacks" : [ 8 | { 9 | "callStackRootFrames" : [ 10 | { 11 | "binaryUUID" : "97D62FA4-1DF8-4CF5-8D0B-13F0AD6E1C45", 12 | "offsetIntoBinaryTextSegment" : 123, 13 | "sampleCount" : 20, 14 | "binaryName" : "testBinaryName", 15 | "address" : 74565 16 | } 17 | ] 18 | } 19 | ], 20 | "callStackPerThread" : false 21 | }, 22 | "diagnosticMetaData" : { 23 | "appBuildVersion" : "", 24 | "appVersion" : "", 25 | "regionFormat" : "CA", 26 | "writesCaused" : "2000 byte", 27 | "osVersion" : "macOS 13.0 (22A5358e)", 28 | "deviceType" : "MacBookPro18,3", 29 | "bundleIdentifier" : "com.chimehq.Edit", 30 | "platformArchitecture" : "arm64e" 31 | } 32 | } 33 | ], 34 | "timeStampEnd" : "2022-09-28 10:59:30", 35 | "hangDiagnostics" : [ 36 | { 37 | "version" : "1.0.0", 38 | "callStackTree" : { 39 | "callStacks" : [ 40 | { 41 | "threadAttributed" : true, 42 | "callStackRootFrames" : [ 43 | { 44 | "binaryUUID" : "2B063BFD-1034-4DF1-A195-30DBC462B747", 45 | "offsetIntoBinaryTextSegment" : 123, 46 | "sampleCount" : 20, 47 | "binaryName" : "testBinaryName", 48 | "address" : 74565 49 | } 50 | ] 51 | } 52 | ], 53 | "callStackPerThread" : true 54 | }, 55 | "diagnosticMetaData" : { 56 | "appBuildVersion" : "", 57 | "appVersion" : "", 58 | "regionFormat" : "CA", 59 | "hangDuration" : "20 sec", 60 | "osVersion" : "macOS 13.0 (22A5358e)", 61 | "deviceType" : "MacBookPro18,3", 62 | "bundleIdentifier" : "com.chimehq.Edit", 63 | "platformArchitecture" : "arm64e" 64 | } 65 | } 66 | ], 67 | "crashDiagnostics" : [ 68 | { 69 | "version" : "1.0.0", 70 | "callStackTree" : { 71 | "callStacks" : [ 72 | { 73 | "threadAttributed" : true, 74 | "callStackRootFrames" : [ 75 | { 76 | "binaryUUID" : "2B063BFD-1034-4DF1-A195-30DBC462B747", 77 | "offsetIntoBinaryTextSegment" : 123, 78 | "sampleCount" : 20, 79 | "binaryName" : "testBinaryName", 80 | "address" : 74565 81 | } 82 | ] 83 | } 84 | ], 85 | "callStackPerThread" : true 86 | }, 87 | "diagnosticMetaData" : { 88 | "platformArchitecture" : "arm64e", 89 | "terminationReason" : "Namespace SIGNAL, Code 0xb", 90 | "exceptionType" : 1, 91 | "appBuildVersion" : "", 92 | "osVersion" : "macOS 13.0 (22A5358e)", 93 | "bundleIdentifier" : "com.chimehq.Edit", 94 | "deviceType" : "MacBookPro18,3", 95 | "exceptionCode" : 0, 96 | "virtualMemoryRegionInfo" : "0 is not in any region. Bytes before following region: 4000000000 REGION TYPE START - END [ VSIZE] PRT\/MAX SHRMOD REGION DETAIL UNUSED SPACE AT START ---> __TEXT 0000000000000000-0000000000000000 [ 32K] r-x\/r-x SM=COW ...pp\/Test", 97 | "signal" : 11, 98 | "appVersion" : "", 99 | "regionFormat" : "CA" 100 | } 101 | } 102 | ], 103 | "cpuExceptionDiagnostics" : [ 104 | { 105 | "version" : "1.0.0", 106 | "callStackTree" : { 107 | "callStacks" : [ 108 | { 109 | "callStackRootFrames" : [ 110 | { 111 | "binaryUUID" : "97D62FA4-1DF8-4CF5-8D0B-13F0AD6E1C45", 112 | "offsetIntoBinaryTextSegment" : 123, 113 | "sampleCount" : 20, 114 | "binaryName" : "testBinaryName", 115 | "address" : 74565 116 | } 117 | ] 118 | } 119 | ], 120 | "callStackPerThread" : false 121 | }, 122 | "diagnosticMetaData" : { 123 | "appBuildVersion" : "", 124 | "appVersion" : "", 125 | "regionFormat" : "CA", 126 | "osVersion" : "macOS 13.0 (22A5358e)", 127 | "deviceType" : "MacBookPro18,3", 128 | "bundleIdentifier" : "com.chimehq.Edit", 129 | "platformArchitecture" : "arm64e", 130 | "totalCPUTime" : "20 sec", 131 | "totalSampledTime" : "20 sec" 132 | } 133 | } 134 | ], 135 | "appLaunchDiagnostics" : [ 136 | { 137 | "version" : "1.0.0", 138 | "callStackTree" : { 139 | "callStacks" : [ 140 | { 141 | "threadAttributed" : true, 142 | "callStackRootFrames" : [ 143 | { 144 | "binaryUUID" : "2B063BFD-1034-4DF1-A195-30DBC462B747", 145 | "offsetIntoBinaryTextSegment" : 123, 146 | "sampleCount" : 20, 147 | "binaryName" : "testBinaryName", 148 | "address" : 74565 149 | } 150 | ] 151 | } 152 | ], 153 | "callStackPerThread" : true 154 | }, 155 | "diagnosticMetaData" : { 156 | "appBuildVersion" : "", 157 | "appVersion" : "", 158 | "regionFormat" : "CA", 159 | "osVersion" : "macOS 13.0 (22A5358e)", 160 | "deviceType" : "MacBookPro18,3", 161 | "bundleIdentifier" : "com.chimehq.Edit", 162 | "launchDuration" : "20000 ms", 163 | "platformArchitecture" : "arm64e" 164 | } 165 | } 166 | ] 167 | } -------------------------------------------------------------------------------- /Tests/MeterTests/SymbolicationTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Meter 3 | import BinaryImage 4 | 5 | struct MockSymbolicator { 6 | typealias SymbolicationHandler = (UInt64, SymbolicationTarget) -> [SymbolInfo] 7 | 8 | var symbolicationHandler: SymbolicationHandler 9 | 10 | init(symbolicationHandler: @escaping SymbolicationHandler) { 11 | self.symbolicationHandler = symbolicationHandler 12 | } 13 | } 14 | 15 | extension MockSymbolicator: Symbolicator { 16 | func symbolicate(address: UInt64, in target: SymbolicationTarget) -> [SymbolInfo] { 17 | return symbolicationHandler(address, target) 18 | } 19 | } 20 | 21 | final class SymbolicationTests: XCTestCase { 22 | lazy var processImages = BinaryImage.imageMap 23 | 24 | // guard against 64bit addresses 25 | #if !os(watchOS) 26 | func testDlfcnSymbolicator() throws { 27 | // This is a fragile test, because it will only work when running 28 | // against 12.0 (21A344) on arm. I can think of a way to build a 29 | // more robust test, but it will take quite a bit of work. 30 | try XCTSkipUnless(ProcessInfo.processInfo.operatingSystemVersionString == "Version 12.0 (Build 21A344)") 31 | 32 | let symbolicator = DlfcnSymbolicator() 33 | let target = try XCTUnwrap(SymbolicationTarget(uuid: "17550b77-d255-389a-b779-906af75314b6", 34 | loadAddress: 0x19d0c4000, 35 | path: "/usr/lib/system/libsystem_kernel.dylib")) 36 | 37 | let infoArray = symbolicator.symbolicate(address: 0x19d0c59b4, in: target) 38 | 39 | XCTAssertEqual(infoArray.count, 1) 40 | XCTAssertEqual(infoArray[0].symbol, "mach_msg_trap") 41 | XCTAssertEqual(infoArray[0].offset, 8) 42 | XCTAssertNil(infoArray[0].lineNumber) 43 | XCTAssertNil(infoArray[0].file) 44 | } 45 | #endif 46 | 47 | func testDlfcnSymbolicatorWithAddressOutsideIntRange() throws { 48 | let randomImage = try XCTUnwrap(processImages.first) 49 | 50 | let symbolicator = DlfcnSymbolicator() 51 | 52 | // the load address and path are both bogus and will not match the UUID 53 | let target = try XCTUnwrap( 54 | SymbolicationTarget( 55 | uuid: randomImage.key, 56 | loadAddress: 0x19d0c4000, 57 | path: "/usr/lib/system/libsystem_kernel.dylib" 58 | ) 59 | ) 60 | 61 | // this will fail, but it should not crash 62 | let infoArray = symbolicator.symbolicate(address: UInt64(Int.max) + 1, in: target) 63 | 64 | XCTAssertEqual(infoArray.count, 0) 65 | } 66 | 67 | func testDlfcnSymbolicatorWithFrameAddressOutsideIntRange() throws { 68 | let symbolicator = DlfcnSymbolicator() 69 | 70 | // this will fail, but it should not crash 71 | let frame = Frame(address: UInt64(Int.max) + 1, subFrames: nil) 72 | 73 | _ = symbolicator.symbolicate(frame: frame, withOffsetAsLoadAddress: true) 74 | } 75 | 76 | func testTargetCalculation() throws { 77 | let uuid = UUID() 78 | let frame = Frame(binaryUUID: uuid, 79 | offsetIntoBinaryTextSegment: 100, 80 | binaryName: "binary", 81 | address: 105, 82 | subFrames: nil) 83 | 84 | let loadTarget = frame.symbolicationTarget(withOffsetAsLoadAddress: true) 85 | 86 | XCTAssertEqual(loadTarget, SymbolicationTarget(uuid: uuid, loadAddress: 100, path: "binary")) 87 | 88 | let offsetTarget = frame.symbolicationTarget(withOffsetAsLoadAddress: false) 89 | 90 | XCTAssertEqual(offsetTarget, SymbolicationTarget(uuid: uuid, loadAddress: 5, path: "binary")) 91 | } 92 | 93 | func testSymbolicateCallStack() throws { 94 | let uuidB = UUID() 95 | let frameB = Frame(binaryUUID: uuidB, 96 | offsetIntoBinaryTextSegment: 2000, 97 | sampleCount: 1, 98 | binaryName: "binaryB", 99 | address: 2020, 100 | subFrames: []) 101 | let uuidA = UUID() 102 | let frameA = Frame(binaryUUID: uuidA, 103 | offsetIntoBinaryTextSegment: 1000, 104 | sampleCount: 1, 105 | binaryName: "binaryA", 106 | address: 1015, subFrames: [frameB]) 107 | let callStack = CallStack(threadAttributed: true, rootFrames: [frameA]) 108 | 109 | let symbolInfoB = SymbolInfo(symbol: "symbolB", offset: 10) 110 | let symbolInfoA = SymbolInfo(symbol: "symbolA", offset: 10) 111 | 112 | let mockResults = [frameB.address: [symbolInfoB], frameA.address: [symbolInfoA]] 113 | 114 | let mockSymbolicator = MockSymbolicator { addr, _ in 115 | return mockResults[addr]! 116 | } 117 | 118 | let symbolicatedStack = mockSymbolicator.symbolicate(callStack: callStack, withOffsetAsLoadAddress: true) 119 | 120 | XCTAssertEqual(symbolicatedStack.threadAttributed, true) 121 | XCTAssertEqual(symbolicatedStack.rootFrames.count, 1) 122 | XCTAssertEqual(symbolicatedStack.rootFrames[0].symbolInfo?.count, 1) 123 | XCTAssertEqual(symbolicatedStack.rootFrames[0].symbolInfo?[0].symbol, "symbolA") 124 | XCTAssertEqual(symbolicatedStack.rootFrames[0].symbolInfo?[0].offset, 10) 125 | 126 | XCTAssertEqual(symbolicatedStack.rootFrames[0].subFrames?.count, 1) 127 | XCTAssertEqual(symbolicatedStack.rootFrames[0].subFrames?[0].symbolInfo?[0].symbol, "symbolB") 128 | XCTAssertEqual(symbolicatedStack.rootFrames[0].subFrames?[0].symbolInfo?[0].offset, 10) 129 | } 130 | 131 | func testUsingCrashOffsets() { 132 | let uuidA = UUID() 133 | let frameA = Frame(binaryUUID: uuidA, 134 | offsetIntoBinaryTextSegment: 15, 135 | sampleCount: 1, 136 | binaryName: "binaryA", 137 | address: 1015, subFrames: []) 138 | let callStack = CallStack(threadAttributed: true, rootFrames: [frameA]) 139 | let tree = CallStackTree(callStacks: [callStack], callStackPerThread: true) 140 | 141 | let offsetCrashMetaData = CrashMetaData(deviceType: "", 142 | applicationBuildVersion: "", 143 | applicationVersion: "", 144 | osVersion: "macOS 13.0 (22A5358e)", 145 | platformArchitecture: "", 146 | regionFormat: "", 147 | isTestFlightApp: nil, 148 | lowPowerModeEnabled: nil, 149 | pid: nil, 150 | virtualMemoryRegionInfo: nil, 151 | exceptionType: nil, 152 | terminationReason: nil, 153 | exceptionCode: nil, 154 | signal: nil, 155 | exceptionReason: nil) 156 | let offsetCrashDiagnotic = CrashDiagnostic(metaData: offsetCrashMetaData, callStackTree: tree) 157 | 158 | let absoluteCrashMetaData = CrashMetaData(deviceType: "", 159 | applicationBuildVersion: "", 160 | applicationVersion: "", 161 | osVersion: "macOS 12.1", 162 | platformArchitecture: "", 163 | regionFormat: "", 164 | isTestFlightApp: nil, 165 | lowPowerModeEnabled: nil, 166 | pid: nil, 167 | virtualMemoryRegionInfo: nil, 168 | exceptionType: nil, 169 | terminationReason: nil, 170 | exceptionCode: nil, 171 | signal: nil, 172 | exceptionReason: nil) 173 | let absoluteCrashDiagnotic = CrashDiagnostic(metaData: absoluteCrashMetaData, callStackTree: tree) 174 | 175 | var symbolicationTarget: SymbolicationTarget? = nil 176 | 177 | let mockSymbolicator = MockSymbolicator { _, target in 178 | symbolicationTarget = target 179 | 180 | return [] 181 | } 182 | 183 | _ = mockSymbolicator.symbolicate(diagnostic: offsetCrashDiagnotic) 184 | 185 | XCTAssertEqual(symbolicationTarget?.loadAddress, 1000) 186 | 187 | _ = mockSymbolicator.symbolicate(diagnostic: absoluteCrashDiagnotic) 188 | 189 | XCTAssertEqual(symbolicationTarget?.loadAddress, 15) 190 | } 191 | 192 | func testSymbolicatesAllDiagnosticTypes() throws { 193 | let url = try XCTUnwrap(Bundle.module.url(forResource: "xcode_simulated", withExtension: "json", subdirectory: "Resources")) 194 | let data = try Data(contentsOf: url, options: []) 195 | let payload = try XCTUnwrap(DiagnosticPayload.from(data: data)) 196 | 197 | let symbolInfo = SymbolInfo(symbol: "symSymbol", offset: 10) 198 | 199 | let mockResults = [UInt64(74565): [symbolInfo]] 200 | 201 | let mockSymbolicator = MockSymbolicator { addr, _ in 202 | return mockResults[addr]! 203 | } 204 | 205 | let symPayload = mockSymbolicator.symbolicate(payload: payload) 206 | 207 | XCTAssertEqual(symPayload.crashDiagnostics?[0].callStackTree.callStacks[0].rootFrames[0].symbolInfo, [symbolInfo]) 208 | XCTAssertEqual(symPayload.hangDiagnostics?[0].callStackTree.callStacks[0].rootFrames[0].symbolInfo, [symbolInfo]) 209 | XCTAssertEqual(symPayload.cpuExceptionDiagnostics?[0].callStackTree.callStacks[0].rootFrames[0].symbolInfo, [symbolInfo]) 210 | XCTAssertEqual(symPayload.diskWriteExceptionDiagnostics?[0].callStackTree.callStacks[0].rootFrames[0].symbolInfo, [symbolInfo]) 211 | } 212 | } 213 | --------------------------------------------------------------------------------