Repository: EmergeTools/ETTrace Branch: main Commit: ea8a0ec37872 Files: 81 Total size: 232.8 KB Directory structure: gitextract_ymv8c43d/ ├── .github/ │ └── workflows/ │ ├── build.yml │ └── release.yml ├── .gitignore ├── .spi.yml ├── ETTrace/ │ ├── .gitignore │ ├── CommunicationFrame/ │ │ ├── EMGDummyEmptyClass.h │ │ ├── EMGDummyEmptyClass.m │ │ └── Public/ │ │ └── CommunicationFrame.h │ ├── ETModels/ │ │ ├── FlameNode.swift │ │ ├── Flamegraph.swift │ │ ├── FlamegraphEvent.swift │ │ ├── Sample.swift │ │ └── ThreadNode.swift │ ├── ETTrace/ │ │ ├── EMGChannelListener.h │ │ ├── EMGChannelListener.m │ │ ├── EMGPerfAnalysis.mm │ │ ├── EMGPerfAnalysis_Private.h │ │ └── Public/ │ │ └── PerfAnalysis.h │ ├── ETTraceRunner/ │ │ ├── ConnectivityHelper.swift │ │ ├── Devices/ │ │ │ ├── CommunicationChannel.swift │ │ │ ├── DeviceManager.swift │ │ │ ├── PhysicalDeviceManager.swift │ │ │ └── SimulatorDeviceManager.swift │ │ ├── ETTrace.swift │ │ ├── ETTraceRunner.entitlements │ │ ├── ProcessSelector.swift │ │ ├── ResponseModels/ │ │ │ └── ResponseModel.swift │ │ ├── RunnerHelper.swift │ │ └── main.swift │ ├── JSONWrapper/ │ │ ├── JSONWrapper.m │ │ └── Public/ │ │ └── JSONWrapper.h │ ├── Symbolicator/ │ │ ├── FlamegraphGenerator.swift │ │ ├── Models.swift │ │ ├── Symbolicator.swift │ │ └── Utils.swift │ ├── Tracer/ │ │ ├── EMGStackTraceRecorder.cpp │ │ ├── EMGStackTraceRecorder.h │ │ ├── EMGTracer+PrintThreads.m │ │ ├── EMGTracer.mm │ │ ├── EMGWriteLibraries.m │ │ └── Public/ │ │ └── Tracer.h │ └── TracerSwift/ │ ├── ThreadHelper.swift │ └── UnsafeRawPointer+Commands.swift ├── LICENSE ├── Package.resolved ├── Package.swift ├── README.md ├── Unwinding/ │ ├── .gitignore │ ├── Crashlytics/ │ │ ├── CHANGELOG.md │ │ ├── Crashlytics/ │ │ │ ├── Components/ │ │ │ │ ├── FIRCLSEmerge.c │ │ │ │ └── FIRCLSGlobals.h │ │ │ ├── Helpers/ │ │ │ │ ├── FIRCLSDefines.h │ │ │ │ ├── FIRCLSFeatures.h │ │ │ │ ├── FIRCLSInternalLogging.c │ │ │ │ ├── FIRCLSInternalLogging.h │ │ │ │ ├── FIRCLSLogger.h │ │ │ │ ├── FIRCLSLogger.m │ │ │ │ ├── FIRCLSThreadState.c │ │ │ │ ├── FIRCLSThreadState.h │ │ │ │ ├── FIRCLSUtility.h │ │ │ │ └── FIRCLSUtility.m │ │ │ └── Unwind/ │ │ │ ├── FIRCLSUnwind.c │ │ │ ├── FIRCLSUnwind.h │ │ │ ├── FIRCLSUnwind_arch.h │ │ │ ├── FIRCLSUnwind_arm.c │ │ │ ├── FIRCLSUnwind_x86.c │ │ │ └── FIRCLSUnwind_x86.h │ │ ├── LICENSE │ │ ├── ProtoSupport/ │ │ │ ├── Protos/ │ │ │ │ ├── crashlytics.options │ │ │ │ └── crashlytics.proto │ │ │ ├── generate_crashlytics_protos.sh │ │ │ ├── nanopb_objc_generator.py │ │ │ └── proto_generator.py │ │ ├── Public/ │ │ │ └── Unwinding.h │ │ ├── README.md │ │ └── third_party/ │ │ └── libunwind/ │ │ ├── LICENSE │ │ └── dwarf.h │ ├── FirebaseCrashlytics.podspec │ └── LICENSE ├── build.sh └── build_runner.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Pull Request Build on: pull_request: branches: - main jobs: build: runs-on: macos-15 steps: - name: Checkout code uses: actions/checkout@v2 - name: Xcode select run: sudo xcode-select -s '/Applications/Xcode_16.0.app/Contents/Developer' - name: Show destinations run: xcodebuild -scheme ETTrace -showdestinations - name: Build ETTrace for iOS Simulator run: xcodebuild build -scheme ETTrace -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' CLANG_CXX_LANGUAGE_STANDARD=c++17 - name: Build ETTrace for iOS run: xcodebuild build -scheme ETTrace -sdk iphoneos -destination 'generic/platform=iOS' CLANG_CXX_LANGUAGE_STANDARD=c++17 - name: Build ETTrace for macOS run: xcodebuild build -scheme ETTrace -sdk macosx -destination 'generic/platform=macOS' ONLY_ACTIVE_ARCH=NO - name: Build ETTraceRunner for macOS run: xcodebuild build -scheme ETTraceRunner -sdk macosx -destination 'generic/platform=macOS' ONLY_ACTIVE_ARCH=NO - name: Build ETTrace for tvOS run: xcodebuild build -scheme ETTrace -sdk appletvos -destination 'generic/platform=tvOS' ONLY_ACTIVE_ARCH=NO - name: Build ETTrace for tvOS Simulator run: xcodebuild build -scheme ETTrace -sdk appletvsimulator -destination 'generic/platform=tvOS Simulator' ONLY_ACTIVE_ARCH=NO - name: Build ETTrace for visionOS run: xcodebuild build -scheme ETTrace -sdk xros -destination 'generic/platform=visionOS' ONLY_ACTIVE_ARCH=NO - name: Build ETTrace for visionOS Simulator run: xcodebuild build -scheme ETTrace -sdk xrsimulator -destination 'generic/platform=visionOS Simulator' ONLY_ACTIVE_ARCH=NO ================================================ FILE: .github/workflows/release.yml ================================================ name: Release Workflow on: push: tags: - 'v*' jobs: release: runs-on: macos-15 steps: - name: Checkout code uses: actions/checkout@v2 - name: Xcode select run: sudo xcode-select -s '/Applications/Xcode_16.0.app/Contents/Developer' - name: Setup Signing Certificate uses: apple-actions/import-codesign-certs@v3 with: p12-file-base64: ${{ secrets.CERTIFICATES_P12 }} p12-password: ${{ secrets.CERTIFICATES_P12_PASSWORD }} - name: Build ETTrace xcframework run: sh build.sh - name: Zip xcframework run: zip -r ETTrace.xcframework.zip ETTrace.xcframework - name: Build ETTraceRunner run: sh build_runner.sh env: SIGNING_IDENTITY: ${{ secrets.SIGNING_IDENTITY }} - name: Upload Artifact uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: files: | ETTrace.xcframework.zip ETTraceRunner body: Release ${{ github.ref }} Automated release created by GitHub Actions. ================================================ FILE: .gitignore ================================================ .DS_Store **/*.xcodeproj/xcuserdata/ **/*.xcworkspace/xcuserdata/ **/*.xcodeproj/xcshareddata/ ETTrace-iphonesimulator.xcarchive/ ETTrace-iphoneos.xcarchive/ ./ETTrace.xcframework/ output.json output.folded .swiftpm .build output_*.json ================================================ FILE: .spi.yml ================================================ version: 1 builder: configs: - platform: ios scheme: ETTrace - platform: macos scheme: ETTraceRunner ================================================ FILE: ETTrace/.gitignore ================================================ Pods/ ================================================ FILE: ETTrace/CommunicationFrame/EMGDummyEmptyClass.h ================================================ // // DummyEmptyClass.h // // // Created by Itay Brenner on 2/6/23. // #import // This class is required becasue SPM doesn't support header only targets NS_ASSUME_NONNULL_BEGIN @interface EMGDummyEmptyClass : NSObject @end NS_ASSUME_NONNULL_END ================================================ FILE: ETTrace/CommunicationFrame/EMGDummyEmptyClass.m ================================================ // // DummyEmptyClass.m // // // Created by Itay Brenner on 2/6/23. // #import "EMGDummyEmptyClass.h" // This class is required becasue SPM doesn't support header only targets @implementation EMGDummyEmptyClass @end ================================================ FILE: ETTrace/CommunicationFrame/Public/CommunicationFrame.h ================================================ // // CommunicationFrame.h // CommunicationFrame // // Created by Itay Brenner on 6/3/23. // #import #include //! Project version number for CommunicationFrame. FOUNDATION_EXPORT double CommunicationFrameVersionNumber; //! Project version string for CommunicationFrame. FOUNDATION_EXPORT const unsigned char CommunicationFrameVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import static const int PTPortNumber = 3116; static const int PTNoFrameTag = 0; // Use 1MB as max size to transfer static const int PTMaxChunkSize = 1024 * 1024; enum { PTFrameTypeStart = 101, PTFrameTypeStop = 102, PTFrameTypeReportCreated = 103, PTFrameTypeRequestResults = 104, PTFrameTypeResultsMetadata = 105, PTFrameTypeResultsData = 106, PTFrameTypeResultsTransferComplete = 107, PTFrameTypeStartMultiThread = 108, }; typedef struct _PTStartFrame { bool runAtStartup; // Added in v1.6 uint32_t sampleRate; } PTStartFrame; typedef struct _PTMetadataFrame { uint64_t fileSize; } PTMetadataFrame; ================================================ FILE: ETTrace/ETModels/FlameNode.swift ================================================ // // FlameNode.swift // PerfAnalysisRunner // // Created by Itay Brenner on 7/3/23. // import Foundation @objc public class FlameNode: NSObject { @objc public let name: String @objc public let start: Double @objc public var duration: Double @objc public var children: [FlameNode] @objc public let library: String? @objc public let address: NSNumber? public init(name: String, start: Double, duration: Double, library: String?, address: NSNumber?) { self.name = name self.start = start self.duration = duration self.library = library self.children = [] self.address = address } private func stop() -> Double { return start + duration } public func add(stack: [(String?, String, UInt64?)], duration: Double) { // Add a nil element at the end, or else siblings with the same name, separated by a gap, will be merged into each other var newStack: [(String?, String, UInt64?)?] = stack + [nil] var currentNode = self while !newStack.isEmpty { currentNode.duration += duration let s = newStack[0] var lib: String? = nil let name: String var address: NSNumber? = nil if let tuple = s { lib = tuple.0 name = tuple.1 address = tuple.2 != nil ? NSNumber(value: tuple.2!) : nil } else { name = "" } if currentNode.children.count == 0 || (currentNode.children.last!.name != name || currentNode.children.last!.library != lib) { let child = FlameNode(name: name, start: currentNode.children.last?.stop() ?? currentNode.start, duration: 0, library: lib, address: address) currentNode.children.append(child) } newStack = Array(newStack.dropFirst()) currentNode = currentNode.children.last! } } public static func fromSamples(_ samples: [Sample]) -> FlameNode { let root = FlameNode(name: "", start: 0, duration: 0, library: nil, address: nil) for sample in samples { let sampleDuration = sample.time root.add(stack: sample.stack, duration: sampleDuration) } return root } } ================================================ FILE: ETTrace/ETModels/Flamegraph.swift ================================================ // // Flamegraph.swift // // // Created by Itay Brenner on 27/6/23. // import Foundation @objc public class Flamegraph: NSObject { @objc public let osBuild: String @objc public let device: String @objc public let isSimulator: Bool @objc public var events: [FlamegraphEvent] @objc public var libraries: [String:UInt64] @objc public var threadNodes: [ThreadNode] public init(osBuild: String, device: String, isSimulator: Bool, libraries: [String:UInt64], events: [FlamegraphEvent], threadNodes: [ThreadNode]) { self.osBuild = osBuild self.device = device self.isSimulator = isSimulator self.events = events self.libraries = libraries self.threadNodes = threadNodes } } ================================================ FILE: ETTrace/ETModels/FlamegraphEvent.swift ================================================ // // File.swift // // // Created by Itay Brenner on 28/6/23. // import Foundation @objc public class FlamegraphEvent: NSObject { @objc public let name: String @objc public let type: String @objc public let time: Double public init(name: String, type: String, time: Double) { self.name = name self.type = type self.time = time } } ================================================ FILE: ETTrace/ETModels/Sample.swift ================================================ // // Sample.swift // PerfAnalysisRunner // // Created by Itay Brenner on 7/3/23. // import Foundation public class Sample { public let stack: [(String?, String, UInt64?)] public var time: Double public init(time: Double, stack: [(String?, String, UInt64?)]) { self.time = time self.stack = stack } public var description: String { let timeStr = String(format: "%.15f", self.time).replacingOccurrences(of: "0*$", with: "", options: .regularExpression) let stackStr = stack.map { s in return "\(s.1)" }.joined(separator: ";") return "\(stackStr) \(timeStr)" } } ================================================ FILE: ETTrace/ETModels/ThreadNode.swift ================================================ // // ThreadNode.swift // // // Created by Itay Brenner on 18/8/23. // import Foundation @objc public class ThreadNode: NSObject { @objc public let threadName: String? @objc public var nodes: FlameNode public init(nodes: FlameNode, threadName: String? = nil) { self.nodes = nodes self.threadName = threadName } } ================================================ FILE: ETTrace/ETTrace/EMGChannelListener.h ================================================ // // EMGChannelListener.h // PerfAnalysis // // Created by Itay Brenner on 6/3/23. // #import NS_ASSUME_NONNULL_BEGIN @interface EMGChannelListener : NSObject - (instancetype) init; - (void) sendReportCreatedMessage; @end NS_ASSUME_NONNULL_END ================================================ FILE: ETTrace/ETTrace/EMGChannelListener.m ================================================ // // EMGChannelListener.m // PerfAnalysis // // Created by Itay Brenner on 6/3/23. // #import "EMGChannelListener.h" #import "EMGPerfAnalysis_Private.h" #import #import @interface EMGChannelListener () @property (nonatomic, weak) PTChannel *serverChannel; @property (nonatomic, weak) PTChannel *peerChannel; @end @implementation EMGChannelListener - (instancetype) init { self = [super init]; if (self) { [self setupChannel]; } return self; } - (void) setupChannel { dispatch_queue_t peertalk_queue = dispatch_queue_create("emg_queue", DISPATCH_QUEUE_SERIAL); PTProtocol *protocol = [[PTProtocol alloc] initWithDispatchQueue:peertalk_queue]; PTChannel *channel = [[PTChannel alloc] initWithProtocol:protocol delegate:self]; [channel listenOnPort:PTPortNumber IPv4Address:INADDR_LOOPBACK callback:^(NSError *error) { if (error) { NSLog(@"Failed to listen on 127.0.0.1:%d: %@", PTPortNumber, error); } else { NSLog(@"Listening on 127.0.0.1:%d", PTPortNumber); self.serverChannel = channel; } }]; } #pragma mark - PTChannelDelegate - (BOOL)ioFrameChannel:(PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize { if (channel != self.peerChannel) { // A previous channel that has been canceled but not yet ended. Ignore. return NO; } else if (type == PTFrameTypeStart || type == PTFrameTypeStop || type == PTFrameTypeRequestResults || type == PTFrameTypeStartMultiThread){ return YES; } else { NSLog(@"Unexpected frame of type %u", type); [channel close]; return NO; } } - (void)ioFrameChannel:(PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(NSData *)payload { if (type == PTFrameTypeStart || type == PTFrameTypeStartMultiThread) { PTStartFrame *startFrame = (PTStartFrame *)payload.bytes; NSLog(@"Start received, with: %i", startFrame->runAtStartup); BOOL runAtStartup = startFrame->runAtStartup; BOOL recordAllThreads = type == PTFrameTypeStartMultiThread; NSInteger sampleRate = 0; // If the size is smaller it is a message from an older version of the CLI // before sampleRate was added if (payload.length >= sizeof(PTStartFrame)) { sampleRate = startFrame->sampleRate; } if (runAtStartup) { [EMGPerfAnalysis setupRunAtStartup:recordAllThreads rate:sampleRate]; } else { [EMGPerfAnalysis startRecording:recordAllThreads rate:sampleRate]; } } else if (type == PTFrameTypeStop) { [EMGPerfAnalysis stopRecording]; } else if (type == PTFrameTypeRequestResults) { [self sendReportData]; } } - (void)ioFrameChannel:(PTChannel*)channel didEndWithError:(NSError*)error { if (error) { NSLog(@"%@ ended with error: %@", channel, error); } else { NSLog(@"Disconnected from %@", channel.userInfo); } } - (void)ioFrameChannel:(PTChannel*)channel didAcceptConnection:(PTChannel*)otherChannel fromAddress:(PTAddress*)address { if (self.peerChannel) { [self.peerChannel cancel]; } self.peerChannel = otherChannel; self.peerChannel.userInfo = address; NSLog(@"Connected to %@", address); } - (void) sendReportCreatedMessage { NSData *emptyData = [[NSData alloc] init]; [self.peerChannel sendFrameOfType:PTFrameTypeReportCreated tag:PTFrameNoTag withPayload:emptyData callback:^(NSError * _Nullable error) { if (error) { NSLog(@"Could not send message"); } else { NSLog(@"Message sent"); } }]; } - (void) sendReportData { NSURL *outURL = [EMGPerfAnalysis outputPath]; NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:outURL.path]; if (!fileHandle) { NSLog(@"Error opening file"); return; } // Sending metadata PTMetadataFrame *frame = CFAllocatorAllocate(nil, sizeof(PTMetadataFrame), 0); frame->fileSize = fileHandle.availableData.length; dispatch_data_t dataFrame = dispatch_data_create((const void*)frame, sizeof(PTMetadataFrame), nil, ^{ CFAllocatorDeallocate(nil, frame); }); [self.peerChannel sendFrameOfType:PTFrameTypeResultsMetadata tag:PTFrameNoTag withPayload:dataFrame callback:nil]; [fileHandle seekToFileOffset:0]; while (YES) { NSData *chunk = [fileHandle readDataOfLength:PTMaxChunkSize]; if (chunk.length == 0) { break; } [self.peerChannel sendFrameOfType:PTFrameTypeResultsData tag:PTFrameNoTag withPayload:chunk callback:nil]; } // Confirm file completed [self.peerChannel sendFrameOfType:PTFrameTypeResultsTransferComplete tag:PTFrameNoTag withPayload:nil callback:nil]; } @end ================================================ FILE: ETTrace/ETTrace/EMGPerfAnalysis.mm ================================================ // // Constructor.m // PerfAnalysis // // Created by Noah Martin on 11/23/22. // #import #import #import #import #import #import #import #import #import #import "EMGChannelListener.h" #import #import "PerfAnalysis.h" #include NSString *const kEMGSpanStarted = @"EmergeMetricStarted"; NSString *const kEMGSpanEnded = @"EmergeMetricEnded"; @implementation EMGPerfAnalysis static dispatch_queue_t fileEventsQueue; static EMGChannelListener *channelListener; static NSMutableArray *sSpanTimes; + (void)startRecording:(BOOL)recordAllThreads rate:(NSInteger)sampleRate { sSpanTimes = [NSMutableArray array]; [EMGTracer setupStackRecording:recordAllThreads rate:(useconds_t) sampleRate]; } + (void)setupRunAtStartup:(BOOL) recordAllThreads rate:(NSInteger)sampleRate { [[NSUserDefaults standardUserDefaults] setBool:true forKey:@"runAtStartup"]; [[NSUserDefaults standardUserDefaults] setBool:recordAllThreads forKey:@"recordAllThreads"]; [[NSUserDefaults standardUserDefaults] setInteger:sampleRate forKey:@"ETTraceSampleRate"]; exit(0); } + (void)startObserving { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *documentsURL = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0]; NSURL *emergeDirectoryURL = [documentsURL URLByAppendingPathComponent:@"emerge-perf-analysis"]; if (![[NSFileManager defaultManager] fileExistsAtPath:emergeDirectoryURL.path isDirectory:NULL]) { [[NSFileManager defaultManager] createDirectoryAtURL:emergeDirectoryURL withIntermediateDirectories:YES attributes:nil error:nil]; } channelListener = [[EMGChannelListener alloc] init]; }); [[NSNotificationCenter defaultCenter] addObserverForName:kEMGSpanStarted object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { if (![EMGTracer isRecording]) { return; } NSString *span = notification.userInfo[@"metric"]; [sSpanTimes addObject:@{ @"span": span, @"type": @"start", @"time": @(CACurrentMediaTime()) }]; }]; [[NSNotificationCenter defaultCenter] addObserverForName:kEMGSpanEnded object:nil queue:nil usingBlock:^(NSNotification * _Nonnull notification) { if (![EMGTracer isRecording]) { return; } NSString *span = notification.userInfo[@"metric"]; [sSpanTimes addObject:@{ @"span": span, @"type": @"stop", @"time": @(CACurrentMediaTime()) }]; }]; } + (void)stopRecording { [EMGTracer stopRecording:^(NSDictionary *results) { NSMutableDictionary *info = [results mutableCopy]; info[@"events"] = sSpanTimes; NSError *error = nil; NSData *data = [NSJSONSerialization dataWithJSONObject:info options:0 error:&error]; if (error) { @throw error; } NSURL *documentsURL = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0]; NSURL *emergeDirectoryURL = [documentsURL URLByAppendingPathComponent:@"emerge-output"]; if (![[NSFileManager defaultManager] fileExistsAtPath:emergeDirectoryURL.path isDirectory:NULL]) { [[NSFileManager defaultManager] createDirectoryAtURL:emergeDirectoryURL withIntermediateDirectories:YES attributes:nil error:nil]; } NSURL *outputURL = [emergeDirectoryURL URLByAppendingPathComponent:@"output.json"]; BOOL result = [data writeToURL:outputURL options:NSDataWritingAtomic error:&error]; if (!result || error) { NSLog(@"Error writing ETTrace state %@", error); } else { NSLog(@"ETTrace result written"); } [channelListener sendReportCreatedMessage]; }]; } + (void)load { NSLog(@"Starting ETTrace"); [EMGTracer setup]; fileEventsQueue = dispatch_queue_create("com.emerge.file_queue", DISPATCH_QUEUE_SERIAL); BOOL infoPlistRunAtStartup = ((NSNumber *) NSBundle.mainBundle.infoDictionary[@"ETTraceRunAtStartup"]).boolValue; if ([[NSUserDefaults standardUserDefaults] boolForKey:@"runAtStartup"] || infoPlistRunAtStartup) { NSInteger sampleRate = [[NSUserDefaults standardUserDefaults] integerForKey:@"ETTraceSampleRate"]; BOOL recordAllThreads = [[NSUserDefaults standardUserDefaults] boolForKey:@"recordAllThreads"]; [EMGPerfAnalysis startRecording:recordAllThreads rate:sampleRate]; [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"runAtStartup"]; [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"recordAllThreads"]; } [EMGPerfAnalysis startObserving]; } + (NSURL *) outputPath { NSURL *documentsURL = [[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0]; NSURL *emergeDirectoryURL = [documentsURL URLByAppendingPathComponent:@"emerge-output"]; return [emergeDirectoryURL URLByAppendingPathComponent:@"output.json"]; } @end ================================================ FILE: ETTrace/ETTrace/EMGPerfAnalysis_Private.h ================================================ // // EMGPerfAnalysis_Private.h // PerfAnalysis // // Created by Itay Brenner on 6/3/23. // #ifndef EMGPerfAnalysis_Private_h #define EMGPerfAnalysis_Private_h #import "PerfAnalysis.h" @interface EMGPerfAnalysis (Private) + (void)startRecording:(BOOL) recordAllThreads rate:(NSInteger)sampleRate; +(void)stopRecording; + (void)setupRunAtStartup:(BOOL) recordAllThreads rate:(NSInteger)sampleRate; + (NSURL *)outputPath; @end #endif /* EMGPerfAnalysis_Private_h */ ================================================ FILE: ETTrace/ETTrace/Public/PerfAnalysis.h ================================================ // // PerfAnalysis.h // PerfAnalysis // // Created by Noah Martin on 12/9/22. // #import //! Project version number for PerfAnalysis. FOUNDATION_EXPORT double PerfAnalysisVersionNumber; //! Project version string for PerfAnalysis. FOUNDATION_EXPORT const unsigned char PerfAnalysisVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import extern NSString *const kEMGSpanStarted; extern NSString *const kEMGSpanEnded; @interface EMGPerfAnalysis : NSObject @end ================================================ FILE: ETTrace/ETTraceRunner/ConnectivityHelper.swift ================================================ // // ConnectivityHelper.swift // ETTrace // // Created by Noah Martin on 2/27/25. // import Foundation func isPortInUse(port: Int) -> Bool { let sock = socket(AF_INET, SOCK_STREAM, 0) if sock == -1 { print("Failed to create socket") return false } var addr = sockaddr_in() addr.sin_len = UInt8(MemoryLayout.size); addr.sin_family = sa_family_t(AF_INET) addr.sin_port = in_port_t(port).bigEndian addr.sin_addr.s_addr = INADDR_LOOPBACK.bigEndian let result = withUnsafePointer(to: &addr) { $0.withMemoryRebound(to: sockaddr.self, capacity: 1) { connect(sock, $0, socklen_t(MemoryLayout.size)) } } close(sock) return result == 0 } ================================================ FILE: ETTrace/ETTraceRunner/Devices/CommunicationChannel.swift ================================================ // // CommunicationChannel.swift // ETTraceRunner // // Created by Noah Martin on 4/12/23. // import Foundation import CommunicationFrame import Peertalk class CommunicationChannel: NSObject { lazy var channel = PTChannel(protocol: nil, delegate: self) private var expectedDataLength: UInt64 = 0 private var receivedData = Data() private var resultsReceived: Bool = false private var resultsReceivedContinution: CheckedContinuation? private var reportGenerated: Bool = false private var reportedGeneratedContinuation: CheckedContinuation? private let verbose: Bool private var relaunch: Bool init(verbose: Bool, relaunch: Bool) { self.verbose = verbose self.relaunch = relaunch } func waitForReportGenerated() async { return await withCheckedContinuation { continuation in DispatchQueue.main.async { [weak self] in if self?.reportGenerated == true { continuation.resume() } else { self?.reportedGeneratedContinuation = continuation } } } } func waitForResultsReceived() async -> Data { return await withCheckedContinuation{ continuation in DispatchQueue.main.async { [weak self] in guard let self = self else { return } if self.resultsReceived == true { continuation.resume(returning: self.receivedData) } else { self.resultsReceivedContinution = continuation } } } } } extension CommunicationChannel: PTChannelDelegate { @objc func channel(_ channel: PTChannel, didRecieveFrame type: UInt32, tag: UInt32, payload: Data?) { dispatchPrecondition(condition: .onQueue(.main)) if type == PTFrameTypeReportCreated { reportGenerated = true reportedGeneratedContinuation?.resume() reportedGeneratedContinuation = nil } else if type == PTFrameTypeResultsMetadata, let payload = payload { let metadata = payload.withUnsafeBytes { buffer in buffer.load(as: PTMetadataFrame.self) } expectedDataLength = UInt64(metadata.fileSize) } else if type == PTFrameTypeResultsData, let payload = payload { receivedData.append(payload) } else if type == PTFrameTypeResultsTransferComplete { guard receivedData.count == expectedDataLength else { fatalError("Received \(receivedData.count) bytes, expected \(expectedDataLength)") } resultsReceived = true resultsReceivedContinution?.resume(returning: receivedData) resultsReceivedContinution = nil } } @objc func channelDidEnd(_ channel: PTChannel, error: Error?) { dispatchPrecondition(condition: .onQueue(.main)) guard !relaunch else { relaunch = false return } if !resultsReceived { print("Disconnected before results received, exiting early") exit(1) } else if verbose { print("Disconnected") } } } ================================================ FILE: ETTrace/ETTraceRunner/Devices/DeviceManager.swift ================================================ // // DeviceManager.swift // PerfAnalysisRunner // // Created by Itay Brenner on 6/3/23. // import Foundation import CommunicationFrame import Peertalk protocol DeviceManager { var communicationChannel: CommunicationChannel { get } var verbose: Bool { get } func connect() async throws -> Void } extension DeviceManager { func sendStartRecording(_ runAtStartup: Bool, _ multiThread: Bool, _ sampleRate: UInt32) async throws -> Void { return try await withCheckedThrowingContinuation { continuation in var startFrame = _PTStartFrame(runAtStartup: runAtStartup, sampleRate: sampleRate) let data = Data(bytes: &startFrame, count: MemoryLayout<_PTStartFrame>.size) let type = multiThread ? PTFrameTypeStartMultiThread : PTFrameTypeStart communicationChannel.channel.sendFrame(type: UInt32(type), tag: UInt32(PTNoFrameTag), payload: data) { error in if let error = error { continuation.resume(throwing: error) } else { continuation.resume() } } } } private func sendStopRecording() async throws -> Void { return try await withCheckedThrowingContinuation { continuation in communicationChannel.channel.sendFrame(type: UInt32(PTFrameTypeStop), tag: UInt32(PTNoFrameTag), payload: Data()) { error in if let error = error { continuation.resume(throwing: error) } else { continuation.resume() } } } } private func sendRequestResults() async throws { return try await withCheckedThrowingContinuation { continuation in communicationChannel.channel.sendFrame(type: UInt32(PTFrameTypeRequestResults), tag: UInt32(PTNoFrameTag), payload: Data()) { error in if let error = error { continuation.resume(throwing: error) } else { if verbose { print("Extracting results from device...") } continuation.resume() } } } } func getResults() async throws -> Data { try await sendStopRecording() await communicationChannel.waitForReportGenerated() try await sendRequestResults() return await communicationChannel.waitForResultsReceived() } } ================================================ FILE: ETTrace/ETTraceRunner/Devices/PhysicalDeviceManager.swift ================================================ // // PhysicalDeviceManager.swift // PerfAnalysisRunner // // Created by Itay Brenner on 6/3/23. // import Foundation import Peertalk import CommunicationFrame enum ConnectionError: Error { case noUsbHub case connectionFailed } class PhysicalDevicemanager: DeviceManager { var communicationChannel: CommunicationChannel init(verbose: Bool, relaunch: Bool) { communicationChannel = CommunicationChannel(verbose: verbose, relaunch: relaunch) self.verbose = verbose } let verbose: Bool private var observer: NSObjectProtocol? = nil private var deviceID: NSNumber? = nil private func connect(withId deviceID: NSNumber, usbHub: PTUSBHub, continuation: CheckedContinuation) { communicationChannel.channel.connect(to: PTPortNumber, over: usbHub, deviceID: deviceID) { error in if error != nil { print("Connection failed, make sure the app is open on your device") continuation.resume(throwing: ConnectionError.connectionFailed) } else { print("Connected") continuation.resume() } } } func connect() async throws -> Void { return try await withCheckedThrowingContinuation { continuation in if let usbHub = PTUSBHub.shared() { if let deviceID = self.deviceID { connect(withId: deviceID, usbHub: usbHub, continuation: continuation) } else { observer = NotificationCenter.default.addObserver(forName:.deviceDidAttach, object: usbHub, queue: nil) {[weak self] notification in if self?.verbose == true { print("Device did attach notification") } guard let deviceID = notification.userInfo?[PTUSBHubNotificationKey.deviceID] as? NSNumber else { return } self?.deviceID = deviceID NotificationCenter.default.removeObserver(self?.observer as Any, name: .deviceDidAttach, object: usbHub) self?.connect(withId: deviceID, usbHub: usbHub, continuation: continuation) } } } else { continuation.resume(throwing: ConnectionError.noUsbHub) } } } } ================================================ FILE: ETTrace/ETTraceRunner/Devices/SimulatorDeviceManager.swift ================================================ // // SimulatorDeviceManager.swift // PerfAnalysisRunner // // Created by Itay Brenner on 6/3/23. // import Foundation import Peertalk import CommunicationFrame struct SimulatorDeviceManager: DeviceManager { var communicationChannel: CommunicationChannel let verbose: Bool init(verbose: Bool, relaunch: Bool) { communicationChannel = CommunicationChannel(verbose: verbose, relaunch: relaunch) self.verbose = verbose } func connect() async throws -> Void { return try await withCheckedThrowingContinuation { continuation in communicationChannel.channel.connect(to: UInt16(PTPortNumber), IPv4Address: INADDR_LOOPBACK) { error, address in if let error = error { continuation.resume(throwing: error) } else { print("Connected") continuation.resume() } } } } } ================================================ FILE: ETTrace/ETTraceRunner/ETTrace.swift ================================================ // // ETTrace.swift // PerfAnalysisRunner // // Created by Itay Brenner on 6/3/23. // import Foundation import ArgumentParser struct ETTrace: ParsableCommand { @Option(name: .shortAndLong, help: "Directory with dSYMs") var dsyms: String? = nil @Flag(name: .shortAndLong, help: "Relaunch app with profiling from startup.") var launch = false @Flag(name: .shortAndLong, help: "Use simulator") var simulator: Bool = false @Flag(name: .shortAndLong, help: "Verbose logging") var verbose: Bool = false @Flag(name: .long, help: "Save intermediate files directly from the phone before processing them.") var saveIntermediate: Bool = false @Option(name: .shortAndLong, help: "Directory for output files") var output: String? = nil @Flag(name: .shortAndLong, help: "Record all threads") var multiThread: Bool = false @Option(name: .long, help: "Sample rate") var sampleRate: UInt32 = 0 mutating func run() throws { if let dsym = dsyms, dsym.hasSuffix(".dSYM") { ETTrace.exit(withError: ValidationError("The dsym argument should be set to a folder containing your dSYM files, not the dSYM itself")) } let helper = RunnerHelper(dsyms, launch, simulator, verbose, saveIntermediate, output, multiThread, sampleRate) Task { do { try await helper.start() } catch let error { print("ETTrace error: \(error)") } ETTrace.exit() } RunLoop.main.run() } } ================================================ FILE: ETTrace/ETTraceRunner/ETTraceRunner.entitlements ================================================ com.apple.security.temporary-exception.sbpl (allow network-outbound (literal "/private/var/run/usbmuxd")) ================================================ FILE: ETTrace/ETTraceRunner/ProcessSelector.swift ================================================ // // ProcessSelector.swift // ETTrace // // Created by Noah Martin on 2/27/25. // import Foundation struct RunningProcess { let path: String let pid: Int let bundleID: String? } func trimPath(_ path: String) -> String { let pattern = "/([^/]+\\.app)/" if let range = path.range(of: pattern, options: .regularExpression) { return String(path[range.lowerBound...]) } return path } func listRunningProcesses() -> [RunningProcess] { var results: [RunningProcess] = [] let numberOfProcesses = proc_listpids(UInt32(PROC_ALL_PIDS), 0, nil, 0) / Int32(MemoryLayout.size) guard numberOfProcesses > 0 else { return [] } var pids = [pid_t](repeating: 0, count: Int(numberOfProcesses)) let result = proc_listpids(UInt32(PROC_ALL_PIDS), 0, &pids, Int32(pids.count * MemoryLayout.size)) guard result > 0 else { return [] } for pid in pids { if pid == 0 { continue } var pathBuffer = [CChar](repeating: 0, count: 4 * 1024) let pathResult = proc_pidpath(pid, &pathBuffer, UInt32(pathBuffer.count)) if pathResult > 0 { let path = String(cString: pathBuffer) if path.contains("CoreSimulator/Devices/") { let url = URL(fileURLWithPath: path) var bundleID: String? = nil var bundleURL = url while bundleURL.pathComponents.count > 1 { if bundleURL.pathExtension == "app" { break } bundleURL.deleteLastPathComponent() } if bundleURL.pathExtension == "app", let bundle = Bundle(url: bundleURL) { bundleID = bundle.bundleIdentifier } results.append(.init(path: trimPath(path), pid: Int(pid), bundleID: bundleID)) } } } return results } ================================================ FILE: ETTrace/ETTraceRunner/ResponseModels/ResponseModel.swift ================================================ // // ResponseModel.swift // PerfAnalysisRunner // // Created by Itay Brenner on 7/3/23. // import Foundation import Symbolicator struct ResponseModel: Decodable { let osBuild: String let osVersion: String? let isSimulator: Bool let libraryInfo: LibraryInfo let cpuType: String let device: String let events: [Event] let threads: [String: Thread] let sampleRate: UInt32? } struct LibraryInfo: Decodable { let relativeTime: Double let mainThreadId: Int let loadedLibraries: [LoadedLibrary] } struct Thread: Decodable { let name: String let stacks: [Stack] } ================================================ FILE: ETTrace/ETTraceRunner/RunnerHelper.swift ================================================ // // RunnerHelper.swift // PerfAnalysisRunner // // Created by Itay Brenner on 8/3/23. // import AppKit import Foundation import Peertalk import CommunicationFrame import Swifter import JSONWrapper import ETModels import Symbolicator class RunnerHelper { let dsyms: String? let launch: Bool let useSimulator: Bool let verbose: Bool let saveIntermediate: Bool let outputDirectory: String? let multiThread: Bool let sampleRate: UInt32 var server: HttpServer? = nil init(_ dsyms: String?, _ launch: Bool, _ simulator: Bool, _ verbose: Bool, _ saveIntermediate: Bool, _ outputDirectory: String?, _ multiThread: Bool, _ sampleRate: UInt32) { self.dsyms = dsyms self.launch = launch self.useSimulator = simulator self.verbose = verbose self.saveIntermediate = saveIntermediate self.outputDirectory = outputDirectory self.multiThread = multiThread self.sampleRate = sampleRate } private func printMessageAndWait() { print("Please open the app on the \(useSimulator ? "simulator" : "device")") if !useSimulator { print("Re-run with `--simulator` to connect to the simulator.") } print("Press return when ready...") _ = readLine() } func start() async throws { while useSimulator && !isPortInUse(port: Int(PTPortNumber)) { let running = listRunningProcesses() if !running.isEmpty { print(running.count == 1 ? "1 app was found but it is not running" : "\(running.count) apps were found but they are not running") for p in running { if let bundleId = p.bundleID { print("\tBundle Id: \(bundleId) path: \(p.path)") } else { print("\tPath: \(p.path)") } } } else { print("No apps found running on the simulator") } printMessageAndWait() } if !useSimulator { printMessageAndWait() } if verbose { print("Connecting to device.") } let deviceManager: DeviceManager = useSimulator ? SimulatorDeviceManager(verbose: verbose, relaunch: launch) : PhysicalDevicemanager(verbose: verbose, relaunch: launch) try await deviceManager.connect() try await deviceManager.sendStartRecording(launch, multiThread, sampleRate) if launch { print("Re-launch the app to start recording, then press return to exit") } else { print("Started recording, press return to exit") } _ = readLine() if launch { try await deviceManager.connect() } if verbose { print("Waiting for report to be generated..."); } let receivedData = try await deviceManager.getResults() if saveIntermediate { let outFolder = "\(NSTemporaryDirectory())/emerge-output" try FileManager.default.createDirectory(atPath: outFolder, withIntermediateDirectories: true) let outputPath = "\(outFolder)/output.json" if FileManager.default.fileExists(atPath: outputPath) { try FileManager.default.removeItem(atPath: outputPath) } FileManager.default.createFile(atPath: outputPath, contents: receivedData) print("Intermediate file saved to \(outputPath)") } if verbose { print("Stopped recording, symbolicating...") } let responseData = try JSONDecoder().decode(ResponseModel.self, from: receivedData) let isSimulator = responseData.isSimulator var arch = responseData.cpuType.lowercased() if arch == "arm64e" { arch = " arm64e" } else { arch = "" } var osBuild = responseData.osBuild osBuild.removeAll(where: { !$0.isLetter && !$0.isNumber }) let threadIds = responseData.threads.keys let threads = threadIds.map { responseData.threads[$0]!.stacks } let symbolicator = StackSymbolicator(isSimulator: isSimulator, dSymsDir: dsyms, osBuild: osBuild, osVersion: responseData.osVersion, arch: arch, verbose: verbose) let flamegraphs = FlamegraphGenerator.generate( events: responseData.events, threads: threads, sampleRate: responseData.sampleRate, loadedLibraries: responseData.libraryInfo.loadedLibraries, symbolicator: symbolicator) let outputUrl = URL(fileURLWithPath: outputDirectory ?? FileManager.default.currentDirectoryPath) var mainThreadData: Data? for (threadId, symbolicationResult) in zip(threadIds, flamegraphs) { let thread = responseData.threads[threadId]! let flamegraph = createFlamegraphForThread(symbolicationResult.0, symbolicationResult.1, thread, responseData) let outJsonData = JSONWrapper.toData(flamegraph)! if thread.name == "Main Thread" { if verbose { try symbolicationResult.2.write(toFile: "output.folded", atomically: true, encoding: .utf8) } mainThreadData = outJsonData } try saveFlamegraph(outJsonData, outputUrl, threadId) } guard let mainThreadData else { fatalError("No main thread flamegraphs generated") } // Serve Main Thread try startLocalServer(mainThreadData) let url = URL(string: "https://emergetools.com/ettrace")! NSWorkspace.shared.open(url) // Wait 4 seconds for results to be accessed from server, then exit sleep(4) print("Results saved to \(outputUrl)") } private func createFlamegraphForThread(_ flamegraphNodes: FlameNode, _ eventTimes: [Double], _ thread: Thread, _ responseData: ResponseModel) -> Flamegraph { let threadNode = ThreadNode(nodes: flamegraphNodes, threadName: thread.name) let events = zip(responseData.events, eventTimes).map { (event, t) in return FlamegraphEvent(name: event.span, type: event.type.rawValue, time: t) } let libraries = responseData.libraryInfo.loadedLibraries.reduce(into: [String:UInt64]()) { partialResult, library in partialResult[library.path] = library.loadAddress } return Flamegraph(osBuild: responseData.osBuild, device: responseData.device, isSimulator: responseData.isSimulator, libraries: libraries, events: events, threadNodes: [threadNode]) } func startLocalServer(_ data: Data) throws { server = HttpServer() let headers = [ "Content-Type": "application/json", "Access-Control-Allow-Origin": "*", "Content-Length": "\(data.count)", "Access-Control-Allow-Headers": "baggage,sentry-trace" ] server?["/output.json"] = { a in if a.method == "OPTIONS" { return .raw(204, "No Content", [ "Access-Control-Allow-Methods": "GET", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "baggage,sentry-trace" ], nil) } return .raw(200, "OK", headers, { writter in try? writter.write(data) exit(0) }) } try server?.start(37577) } private func saveFlamegraph(_ outJsonData: Data, _ outputUrl: URL, _ threadId: String? = nil) throws { var saveUrl = outputUrl.appendingPathComponent("output.json") if let threadId = threadId { saveUrl = outputUrl.appendingPathComponent("output_\(threadId).json") } let jsonString = String(data: outJsonData, encoding: .utf8)! try jsonString.write(to: saveUrl, atomically: true, encoding: .utf8) } } ================================================ FILE: ETTrace/ETTraceRunner/main.swift ================================================ // // main.swift // // // Created by Itay Brenner on 6/6/23. // ETTrace.main() ================================================ FILE: ETTrace/JSONWrapper/JSONWrapper.m ================================================ // // TestClass.m // ETTraceRunner // // Created by Noah Martin on 4/13/23. // #import #import "JSONWrapper.h" @import ETModels; @implementation JSONWrapper + (NSDictionary *)flameNodeToDictionary:(FlameNode *)node { NSObject *children; if (node.children.count == 1) { children = [JSONWrapper flameNodeToDictionary:node.children[0]]; } else { children = [[NSMutableArray alloc] init]; for (FlameNode * c in node.children) { [(NSMutableArray *) children addObject:[JSONWrapper flameNodeToDictionary:c]]; } } NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithDictionary:@{ @"name": node.name, @"start": @(node.start), @"duration": @(node.duration), @"library": node.library ? node.library : @"", @"children": children, }]; if (node.address != nil) { [result setObject:node.address forKey:@"address"]; } return result; } + (NSDictionary *)flamegraphToDictionary:(Flamegraph *)flamegraph { NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithDictionary:@{ @"osBuild": flamegraph.osBuild, @"isSimulator": @(flamegraph.isSimulator), @"libraries": flamegraph.libraries, @"events": [self eventsToArray:flamegraph.events], @"device": flamegraph.device, }]; ThreadNode *thread = flamegraph.threadNodes.firstObject; [result setObject:[self flameNodeToDictionary:thread.nodes] forKey:@"nodes"]; return result; } + (NSArray *)eventsToArray:(NSArray *)events { NSMutableArray *result = [NSMutableArray array]; for (FlamegraphEvent *event in events) { [result addObject:@{ @"name": event.name, @"type": [event.type uppercaseString], @"time": @(event.time), }]; } return result; } + (NSData *)toData:(NSObject *)anyInput { Flamegraph *input = (Flamegraph *)anyInput; return [NSJSONSerialization dataWithJSONObject:[JSONWrapper flamegraphToDictionary:input] options:NSJSONWritingWithoutEscapingSlashes error:nil]; } @end ================================================ FILE: ETTrace/JSONWrapper/Public/JSONWrapper.h ================================================ // // TestClass.h // ETTrace // // Created by Noah Martin on 4/13/23. // #ifndef TestClass_h #define TestClass_h @import Foundation; @interface JSONWrapper : NSObject // Use NSObject here because we cannot import Swift packages from the public header to avoid circular dependencies + (NSData *)toData:(NSObject *)input; @end #endif /* TestClass_h */ ================================================ FILE: ETTrace/Symbolicator/FlamegraphGenerator.swift ================================================ // // FlamegraphGenerator.swift // PerfAnalysisRunner // // Created by Itay Brenner on 7/3/23. // import Foundation import ETModels public enum FlamegraphGenerator { public static func generate(events: [Event], threads: [[Stack]], sampleRate: UInt32?, loadedLibraries: [LoadedLibrary], symbolicator: StackSymbolicator) -> [(FlameNode, [Double], String)] { let syms = symbolicator.symbolicate(threads.flatMap { $0 }, loadedLibraries) return threads.map { generateFlamegraphs(events: events, stacks: $0, sampleRate: sampleRate, syms: syms) } } private static func generateFlamegraphs( events: [Event], stacks: [Stack], sampleRate: UInt32?, syms: SymbolicationResult) -> (FlameNode, [Double], String) { var eventTimes = [Double](repeating: 0, count: events.count) let times = stacks.map { $0.time } var timeDiffs: [Double] = [] let rate = sampleRate == 0 ? 4500 : (sampleRate ?? 4500) // The default sample rate is 4500 microseconds, add 500 because // the samples have slightly more delay than the rate passed to usleep. let sampleInterval = Double(rate + 500) / 1000000.0 var unattributedTime = 0.0 let partitions = partitions(times, size: 2, step: 1) var eventTime: Double = 0 var eventIndex = 0 for (t1, t2) in partitions { let timeDiff: Double if t2 - t1 > sampleInterval * 2 { unattributedTime += t2 - t1 - sampleInterval * 2 timeDiff = sampleInterval * 2 timeDiffs.append(timeDiff) } else { timeDiff = t2 - t1 timeDiffs.append(timeDiff) } let previousIndex = eventIndex while eventIndex < events.count && events[eventIndex].time < t1 { eventIndex += 1 } for i in previousIndex.. Sample in let stackSyms: [(String?, String, UInt64?)] = stack.stack.map { address in guard let sym = syms[address] else { return ("", "", nil) } if sym.2 { return (sym.0, sym.1, address) } return (sym.0, sym.1, nil) } return Sample(time: timeDiff, stack: stackSyms) } if unattributedTime > 0 { let stack = (nil as String?, "", nil as UInt64?) samples.append(Sample(time: unattributedTime, stack: [stack])) } let folded = samples.map { $0.description }.joined(separator: "\n") let node = FlameNode.fromSamples(samples) return (node, eventTimes, folded) } private static func partitions(_ array: [Double], size: Int, step: Int? = nil) -> [(Double, Double)] { let step = step ?? size var startIdx = 0 var endIdx = size - 1 var partitions: [(Double, Double)] = [] while Int(endIdx) < array.count { partitions.append( (array[startIdx], array[endIdx]) ) startIdx += step endIdx += step } return partitions } } ================================================ FILE: ETTrace/Symbolicator/Models.swift ================================================ // // Models.swift // // // Created by Noah Martin on 11/7/23. // import Foundation public struct Event: Decodable { public let span: String public let type: EventType public let time: Double } public enum EventType: String, Decodable { case start case stop } public struct LoadedLibrary: Decodable, Equatable, Hashable { public let path: String public let loadAddress: UInt64 public let uuid: String } public struct Stack: Decodable { let stack: [UInt64] let time: Double } ================================================ FILE: ETTrace/Symbolicator/Symbolicator.swift ================================================ // // Symbolicator.swift // PerfAnalysisRunner // // Created by Itay Brenner on 7/3/23. // import Foundation import ETModels struct Address { let originalAddress: UInt64 let offset: UInt64? let lib: LoadedLibrary? } typealias SymbolicationResult = [UInt64: (String, String, Bool)] public class StackSymbolicator { var formatSymbolCache: [String: String] = [:] let isSimulator: Bool let dSymsDir: String? let osBuild: String let osVersion: String? let arch: String let verbose: Bool public init(isSimulator: Bool, dSymsDir: String?, osBuild: String, osVersion: String?, arch: String, verbose: Bool) { self.isSimulator = isSimulator self.dSymsDir = dSymsDir self.osBuild = osBuild self.osVersion = osVersion self.arch = arch self.verbose = verbose } // Return value is map of address to (lib, symbol, isMissing) func symbolicate(_ stacks: [Stack], _ loadedLibs: [LoadedLibrary]) -> SymbolicationResult { var libToAddrs: [LoadedLibrary: Set] = [:] let stacks = stacksFromResults(stacks, loadedLibs) stacks.flatMap { $0 }.forEach { addr in if let lib = addr.lib, let offset = addr.offset { libToAddrs[lib, default: []].insert(offset) } } let stateLock = NSLock() var libToCleanedPath = [String: (String, String)]() var libToAddrToSym: [String: [UInt64: String]] = [:] let queue = DispatchQueue(label: "com.emerge.symbolication", qos: .userInitiated, attributes: .concurrent) let group = DispatchGroup() for (lib, addrs) in libToAddrs { let cleanedPath = cleanedUpPath(lib.path) libToCleanedPath[lib.path] = (cleanedPath, URL(string: cleanedPath)?.lastPathComponent ?? "") group.enter() queue.async { if let dSym = self.dsymForLib(lib) { let addrToSym = Self.addrToSymForBinary(dSym, self.archForBinary(dSym), addrs) stateLock.lock() libToAddrToSym[lib.path] = addrToSym stateLock.unlock() } group.leave() } } group.wait() var noLibCount = 0 var noSymMap: [String: UInt64] = [:] var result: SymbolicationResult = [:] stacks.forEach { stack in stack.forEach { addr in if let lib = addr.lib, let offset = addr.offset { let (libPath, lastPathComponent) = libToCleanedPath[lib.path]! guard let addrToSym = libToAddrToSym[lib.path], let sym = addrToSym[offset] else { noSymMap[libPath, default: 0] += 1 result[addr.originalAddress] = (libPath, lastPathComponent, true) return } result[addr.originalAddress] = (libPath, formatSymbol(sym), false) } else { noLibCount += 1 } } } let totalCount = stacks.flatMap { $0 }.count let noLibPercentage = (Double(noLibCount) / Double(totalCount) * 100.0) if verbose { print("\(noLibPercentage)% have no library") for (key, value) in noSymMap { let percentage = (Double(value) / Double(totalCount) * 100.0) print("\(percentage)% from \(key) have library but no symbol") } } return result } private func cleanedUpPath(_ path: String) -> String { if path.contains(".app/") && !path.contains("/Xcode.app/") { return path.split(separator: "/").drop(while: { $0.hasSuffix(".app") }).joined(separator: "/") } else if path.contains("/RuntimeRoot/") { if let index = path.range(of: "/RuntimeRoot/")?.upperBound { return String(path[index...]) } } return path } private func stacksFromResults(_ stacks: [Stack], _ loadedLibs: [LoadedLibrary]) -> [[Address]] { let sortedLibs = loadedLibs.sorted(by: { $0.loadAddress > $1.loadAddress } ) let firstTextSize: UInt64 = 50 * 1024 * 1024 var addrToAddress: [UInt64: Address] = [:] let addrs: [[Address]] = stacks.map { stack in return stack.stack.map { addr in let cachedAddress = addrToAddress[addr] if let cachedAddress = cachedAddress { return cachedAddress } var lib: LoadedLibrary? = sortedLibs.first(where: { $0.loadAddress <= addr }) if lib == sortedLibs.first { if !(addr < sortedLibs.first!.loadAddress + firstTextSize) { // TODO: sometimes there are a few really large addresses that neither us nor instruments can symbolicate. Investigate why lib = nil } } if lib == nil { if verbose { print("\(addr) not contained within any frameworks") } return Address(originalAddress: addr, offset: nil, lib: nil) } let address = Address(originalAddress: addr, offset: addr - lib!.loadAddress, lib: lib) addrToAddress[addr] = address return address } } return addrs } private func archForBinary(_ binary: String) -> String { let archsStr = try? processWithOutput("/usr/bin/lipo", args: ["-archs", binary]) let archs = archsStr?.split(separator: " ").map { $0.trimmingCharacters(in: .whitespacesAndNewlines) } ?? [] let trimmedArch = self.arch.trimmingCharacters(in: .whitespacesAndNewlines) if archs.contains(trimmedArch) { return trimmedArch } if trimmedArch == "arm64e" && archs.contains("arm64") { return "arm64" } return archs.first ?? trimmedArch } private static func addrToSymForBinary(_ binary: String, _ arch: String, _ addrs: Set) -> [UInt64: String] { let addrsArray = Array(addrs) let addrsFile = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)!.path let addition: UInt64 = 0x1000000 // atos can fail when the load address is 0, so add extra let strs = addrsArray.map { String($0 + addition, radix: 16) } try! strs.joined(separator: "\n").write(toFile: addrsFile, atomically: true, encoding: .utf8) let symsStr = try? processWithOutput("/usr/bin/atos", args: ["-l", String(addition, radix: 16), "-o", binary, "-f", addrsFile, "-arch", arch]) let syms = symsStr!.split(separator: "\n").enumerated().map { (idx, sym) -> (UInt64, String?) in let trimmed = sym.trimmingCharacters(in: .whitespacesAndNewlines) if trimmed.count == 0 || trimmed.starts(with: "0x") || trimmed == strs[idx] { return (addrsArray[idx], nil) } else { return (addrsArray[idx], trimmed) } }.filter({ (_, sym) in return sym != nil }) var result: [UInt64: String] = [:] for (addr, sym) in syms { result[addr] = sym } return result } private func formatSymbol(_ sym: String) -> String { if let cachedResult = formatSymbolCache[sym] { return cachedResult } let result = sym.replacingOccurrences(of: ":\\d+\\)", with: ")", options: .regularExpression) // static AppDelegate.$main() (in emergeTest) (AppDelegate.swift:10) .replacingOccurrences(of: " \\+ \\d+$", with: "", options: .regularExpression) // _dyld_start (in dyld) + 0 .replacingOccurrences(of: " ()$", with: "", options: .regularExpression) // static UIApplicationDelegate.main() (in emergeTest) () .replacingOccurrences(of: " \\(\\S+.\\S+\\)$", with: "", options: .regularExpression) // static AppDelegate.$main() (in emergeTest) (AppDelegate.swift) .replacingOccurrences(of: " \\(in (\\S| )+\\)", with: "", options: .regularExpression) // static AppDelegate.$main() (in emergeTest) .replacingOccurrences(of: "^__\\d+\\+", with: "", options: .regularExpression) .replacingOccurrences(of: "^__\\d+\\-", with: "", options: .regularExpression) .trimmingCharacters(in: .whitespacesAndNewlines) formatSymbolCache[sym] = result return result } private static func fileIfExists(_ path: String) -> String? { FileManager.default.fileExists(atPath: path) ? path : nil } private func dsymForLib(_ lib: LoadedLibrary) -> String? { let libPath = lib.path if libPath.contains(".app/") { // Look for matching dsyms if let dsymsDir = dSymsDir { let libName = URL(fileURLWithPath: libPath).lastPathComponent let folderExtension = libPath.contains(".framework") ? "framework" : "app" let dsyms = try? FileManager.default.contentsOfDirectory(atPath: "\(dsymsDir)/\(libName).\(folderExtension).dSYM/Contents/Resources/DWARF/") if let dsym = dsyms?.first { return "\(dsymsDir)/\(libName).\(folderExtension).dSYM/Contents/Resources/DWARF/\(dsym)" } } // Use spotlight to find dsyms let foundDsyms = try? safeShellWithOutput("/usr/bin/mdfind \"com_apple_xcode_dsym_uuids == \(lib.uuid)\"").components(separatedBy: .newlines) if let foundDsym = foundDsyms?.first { let dwarfFiles = try? FileManager.default.contentsOfDirectory(atPath: "\(foundDsym)/Contents/Resources/DWARF/") if let dwarfFile = dwarfFiles?.first { return "\(foundDsym)/Contents/Resources/DWARF/\(dwarfFile)" } } // Try using the binary in the simulator to symbolicate if isSimulator { return libPath } return nil } else { if !isSimulator { // Get symbols from device support dir let searchFolder = "\(FileManager.default.homeDirectoryForCurrentUser.path)/Library/Developer/Xcode/iOS DeviceSupport" let directories = (try? FileManager.default.contentsOfDirectory(atPath: searchFolder)) ?? [] // First look for matching os and arch, then just matching os for folder in directories where folder.contains(osBuild) && folder.hasSuffix(arch) { return Self.fileIfExists("\(searchFolder)/\(folder)/Symbols\(libPath)") } for folder in directories where folder.contains(osBuild) { return Self.fileIfExists("\(searchFolder)/\(folder)/Symbols\(libPath)") } return nil } else { return libPath } } } } ================================================ FILE: ETTrace/Symbolicator/Utils.swift ================================================ // // Utils.swift // PerfAnalysisRunner // // Created by Itay Brenner on 6/3/23. // import Foundation import AppKit func safeShell(_ command: String) throws { let task = Process() task.arguments = ["--login", "-c", command] task.executableURL = URL(fileURLWithPath: "/bin/zsh") task.standardInput = nil try task.run() task.waitUntilExit() } func processWithOutput(_ executable: String, args: [String]) throws -> String { let task = Process() task.arguments = args task.executableURL = URL(fileURLWithPath: executable) task.standardInput = nil return try runTask(task) } func safeShellWithOutput(_ command: String) throws -> String { let task = Process() task.arguments = ["--login", "-c", command] task.executableURL = URL(fileURLWithPath: "/bin/zsh") task.standardInput = nil return try runTask(task) } private func runTask(_ task: Process) throws -> String { let pipe = Pipe() task.standardOutput = pipe let group = DispatchGroup() group.enter() var result = String() pipe.fileHandleForReading.readabilityHandler = { fh in let data = fh.availableData if data.isEmpty { // EOF on the pipe pipe.fileHandleForReading.readabilityHandler = nil group.leave() } else { if let newString = String(data: data, encoding: .utf8) { result.append(newString) } } } try task.run() task.waitUntilExit() group.wait() return result } ================================================ FILE: ETTrace/Tracer/EMGStackTraceRecorder.cpp ================================================ #include "EMGStackTraceRecorder.h" #import #import #import #import #import #import #import #import extern "C" { void FIRCLSWriteThreadStack(thread_t thread, uintptr_t *frames, uint64_t framesCapacity, uint64_t *framesWritten); } static const int kMaxFramesPerStack = 1024; kern_return_t checkMachCall(kern_return_t result) { if (result != KERN_SUCCESS) { std::cerr << "Mach call failed with " << result << std::endl; } return result; } Thread::Thread(thread_t threadId, thread_t mainThreadId) { name = "Failed to get name"; // Error case if(threadId == mainThreadId) { name = "Main Thread"; } else { // Get thread Name char cName[1024]; pthread_t pt = pthread_from_mach_thread_np(threadId); if (pt) { int rc = pthread_getname_np(pt, cName, sizeof(cName)); if (rc == 0) { name = cName; } } } } std::vector EMGStackTraceRecorder::collectThreadSummaries() { std::lock_guard lockGuard(threadsLock); std::vector summaries; for (const auto &[threadId, thread] : threadsMap) { std::vector stackSummaries; for (const auto &stack : thread.stacks) { std::vector addresses; for (auto i = stack.storageStartIndex; i < stack.storageEndIndex; i++) { addresses.emplace_back(addressStorage[i]); } // Reverse the stack addresses to get the correct order std::reverse(addresses.begin(), addresses.end()); stackSummaries.emplace_back(stack.time, addresses); } summaries.emplace_back(threadId, thread.name, stackSummaries); } return summaries; } void EMGStackTraceRecorder::recordStackForAllThreads(bool recordAllThreads, thread_t mainMachThread, thread_t etTraceThread) { std::lock_guard lockGuard(threadsLock); thread_act_array_t threads = nullptr; mach_msg_type_number_t threadCount = 0; if (recordAllThreads) { int result = checkMachCall(task_threads(mach_task_self(), &threads, &threadCount)); if (result != KERN_SUCCESS) { threadCount = 0; } } else { threads = &mainMachThread; threadCount = 1; } // This time gets less accurate for later threads, but still good CFTimeInterval time = CACurrentMediaTime(); for (mach_msg_type_number_t i = 0; i < threadCount; i++) { if (threads[i] == etTraceThread) { continue; } uintptr_t frames[kMaxFramesPerStack]; uint64_t frameCount = 0; if (thread_suspend(threads[i]) != KERN_SUCCESS) { // In theory, the thread may have been destroyed by now, so we exit early if this fails continue; } // BEGIN REENTRANT SECTION FIRCLSWriteThreadStack(threads[i], frames, kMaxFramesPerStack, &frameCount); // END REENTRANT SECTION checkMachCall(thread_resume(threads[i])); auto emplaceResult = threadsMap.try_emplace(threads[i], threads[i], mainMachThread); size_t startIndex = addressStorage.size(); for (int frame_idx = 0; frame_idx < frameCount; frame_idx++) { addressStorage.emplace_back(frames[frame_idx]); } size_t endIndex = addressStorage.size(); emplaceResult.first->second.stacks.emplace_back(time, startIndex, endIndex); } if (recordAllThreads) { vm_deallocate(mach_task_self(), (vm_address_t) threads, sizeof(thread_t) * threadCount); } } ================================================ FILE: ETTrace/Tracer/EMGStackTraceRecorder.h ================================================ #import #import #import #import #import #import struct StackSummary { CFTimeInterval time; std::vector stack; StackSummary(CFTimeInterval time, std::vector &stack) : time(time), stack(stack) { } }; struct ThreadSummary { thread_t threadId; std::string name; std::vector stacks; ThreadSummary(thread_t threadId, const std::string &name, std::vector &stacks) : threadId(threadId), name(name), stacks(stacks) { } }; struct Stack { CFTimeInterval time; size_t storageStartIndex; // Inclusive size_t storageEndIndex; // Exclusive Stack(CFTimeInterval time, size_t storageStartIndex, size_t storageEndIndex) : time(time), storageStartIndex(storageStartIndex), storageEndIndex(storageEndIndex) { } }; struct Thread { std::deque stacks; std::string name; Thread(thread_t threadId, thread_t mainThreadId); }; class EMGStackTraceRecorder { std::unordered_map threadsMap; std::mutex threadsLock; std::deque addressStorage; public: void recordStackForAllThreads(bool recordAllThreads, thread_t mainMachThread, thread_t etTraceThread); std::vector collectThreadSummaries(); }; ================================================ FILE: ETTrace/Tracer/EMGTracer+PrintThreads.m ================================================ // // EMGTracer+PrintThreads.m // // // Created by Itay Brenner on 15/8/24. // #import #import @import TracerSwift; @implementation EMGTracer (PrintThread) + (void)printThreads { [ThreadHelper printThreads]; } @end ================================================ FILE: ETTrace/Tracer/EMGTracer.mm ================================================ // // Tracer.m // // // Created by Noah Martin on 10/27/23. // #import "Tracer.h" #import #import #import #import #import #import #import #import #import #import "EMGStackTraceRecorder.h" static NSThread *sStackRecordingThread = nil; static thread_t sMainMachThread = {0}; static useconds_t sSampleRate = 0; // To avoid static initialization order fiasco, we access it from a function EMGStackTraceRecorder &getRecorder() { static EMGStackTraceRecorder recorder; return recorder; } @implementation EMGTracer + (BOOL)isRecording { return sStackRecordingThread != nil; } + (void)stopRecording:(void (^)(NSDictionary *))stopped { [sStackRecordingThread cancel]; sStackRecordingThread = nil; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ stopped([EMGTracer getResults]); }); } + (NSDictionary *)getResults { NSMutableDictionary *> *threads = [NSMutableDictionary dictionary]; auto threadSummaries = getRecorder().collectThreadSummaries(); for (const auto &thread : threadSummaries) { NSString *threadId = [@(thread.threadId) stringValue]; threads[threadId] = @{ @"name": @(thread.name.c_str()), @"stacks": [self arrayFromStacks:thread.stacks] }; } const NXArchInfo *archInfo = NXGetLocalArchInfo(); NSString *cpuType = [NSString stringWithUTF8String:archInfo->description]; NSOperatingSystemVersion version = [NSProcessInfo processInfo].operatingSystemVersion; return @{ @"libraryInfo": EMGLibrariesData(), @"isSimulator": @([self isRunningOnSimulator]), @"osBuild": [self osBuild], @"osVersion": [NSString stringWithFormat:@"%ld.%ld.%ld", (long)version.majorVersion, (long)version.minorVersion, (long)version.patchVersion], @"cpuType": cpuType, @"device": [self deviceName], @"threads": threads, @"sampleRate": @(sSampleRate), }; } + (NSArray *> *) arrayFromStacks: (const std::vector &)stacks { NSMutableArray *> *threadStacks = [NSMutableArray array]; for (const auto &cStack : stacks) { NSMutableArray *stack = [NSMutableArray array]; for (const auto &address : cStack.stack) { [stack addObject:@((NSUInteger)address)]; } NSDictionary *stackDictionary = @{ @"stack": [stack copy], @"time": @(cStack.time) }; [threadStacks addObject:stackDictionary]; } return threadStacks; } + (BOOL)isRunningOnSimulator { #if TARGET_OS_SIMULATOR return YES; #else return NO; #endif } + (NSString *)osBuild { int mib[2] = {CTL_KERN, KERN_OSVERSION}; u_int namelen = sizeof(mib) / sizeof(mib[0]); size_t bufferSize = 0; NSString *osBuildVersion = nil; // Get the size for the buffer sysctl(mib, namelen, NULL, &bufferSize, NULL, 0); u_char buildBuffer[bufferSize]; int result = sysctl(mib, namelen, buildBuffer, &bufferSize, NULL, 0); if (result >= 0) { osBuildVersion = [[NSString alloc] initWithBytes:buildBuffer length:bufferSize encoding:NSUTF8StringEncoding]; } NSCharacterSet *nonAlphanumericStrings = [[NSCharacterSet alphanumericCharacterSet] invertedSet]; // Remove final NULL character return [osBuildVersion stringByTrimmingCharactersInSet:nonAlphanumericStrings]; } + (NSString *)deviceName { struct utsname systemInfo; uname(&systemInfo); return [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding]; } + (void)setup { sMainMachThread = mach_thread_self(); EMGBeginCollectingLibraries(); } + (void)setupStackRecording:(BOOL)recordAllThreads rate:(useconds_t)sampleRate { if (sStackRecordingThread != nil) { return; } sSampleRate = sampleRate; // Make sure that +recordStack is always called on the same (non-main) thread. // This is because a Process keeps its own "current thread" variable which we need // to keep separate // from the main thread. This is because unwinding itself from the main thread // requires Crashlyics to use a hack, and because the stack recording would show up // in the trace. The current strategy is to sleep for 4.5 ms because // usleep is guaranteed to sleep more than that, in practice ~5ms. We could use a // dispatch_timer, which at least tries to compensate for drift etc., but the // timer's queue could theoretically end up run on the main thread sStackRecordingThread = [[NSThread alloc] initWithBlock:^{ thread_t etTraceThread = mach_thread_self(); NSThread *thread = [NSThread currentThread]; while (!thread.cancelled) { getRecorder().recordStackForAllThreads(recordAllThreads, sMainMachThread, etTraceThread); usleep(sampleRate > 0 ? sampleRate : 4500); } }]; sStackRecordingThread.qualityOfService = NSQualityOfServiceUserInteractive; [sStackRecordingThread start]; } @end ================================================ FILE: ETTrace/Tracer/EMGWriteLibraries.m ================================================ // // EMGWriteLibraries.m // PerfAnalysis // // Created by Noah Martin on 12/9/22. // #import #import #import #import #import #import #import #import #import "Tracer.h" static NSRecursiveLock *sLock; static NSMutableArray *sLoadedLibraries; static uint64_t sMainThreadID; static void addLibrary(const char *path, const void *loadAddress, NSUUID *binaryUUID) { // Note that the slide given is very odd (seems incorrect, and many binaries share the same slide value) // So, just print out the header address [sLoadedLibraries addObject:@{ @"path": @(path), // Although it's undefined if JSON can handle 64-bit integers, Apple's NSJSONSerialization seems to write them // out correctly @"loadAddress": @((uint64_t)loadAddress), @"uuid": binaryUUID.UUIDString }]; } static NSUUID* uuid(const struct mach_header *header) { BOOL is64bit = header->magic == MH_MAGIC_64 || header->magic == MH_CIGAM_64; uintptr_t cursor = (uintptr_t)header + (is64bit ? sizeof(struct mach_header_64) : sizeof(struct mach_header)); const struct segment_command *segmentCommand = NULL; for (uint32_t i = 0; i < header->ncmds; i++, cursor += segmentCommand->cmdsize) { segmentCommand = (struct segment_command *)cursor; if (segmentCommand->cmd == LC_UUID) { const struct uuid_command *uuidCommand = (const struct uuid_command *)segmentCommand; return [[NSUUID alloc] initWithUUIDBytes:uuidCommand->uuid]; } } return NULL; } static void printLibrary(const struct mach_header *header, intptr_t slide) { // Lock just in case this function isn't called in a thread-safe manner [sLock lock]; Dl_info info = {0}; dladdr(header, &info); addLibrary(info.dli_fname, header, uuid(header)); [sLock unlock]; } void EMGBeginCollectingLibraries() { sLoadedLibraries = [NSMutableArray array]; sLock = [NSRecursiveLock new]; struct task_dyld_info dyld_info; mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; task_info(mach_task_self_, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); struct dyld_all_image_infos *infos = (struct dyld_all_image_infos *)dyld_info.all_image_info_addr; void *header = (void *)infos->dyldImageLoadAddress; addLibrary("/usr/lib/dyld", header, uuid(header)); pthread_threadid_np(NULL, &sMainThreadID); _dyld_register_func_for_add_image(printLibrary); } NSDictionary *EMGLibrariesData() { [sLock lock]; NSString *runId = [NSProcessInfo processInfo].environment[@"EMERGE_RUN_ID"]; NSDictionary *result = @{ @"runId": runId ?: [NSNull null], @"relativeTime": @(CACurrentMediaTime()), @"mainThreadId": @(sMainThreadID), @"loadedLibraries": [sLoadedLibraries copy] }; [sLock unlock]; return result; } ================================================ FILE: ETTrace/Tracer/Public/Tracer.h ================================================ // // EMGWriteLibraries.h // PerfAnalysis // // Created by Noah Martin on 12/9/22. // #ifndef EMGWriteLibraries_h #define EMGWriteLibraries_h #import #ifdef __cplusplus extern "C" { #endif NSDictionary *EMGLibrariesData(void); void EMGBeginCollectingLibraries(void); #ifdef __cplusplus } #endif @interface EMGTracer : NSObject + (void)setupStackRecording:(BOOL)recordAllThreads rate:(useconds_t)sampleRate; + (void)stopRecording:(void (^)(NSDictionary *))stopped; // Must be called on the main thread, before setupStackRecording is called + (void)setup; + (NSDictionary *)getResults; + (BOOL)isRecording; @end @interface EMGTracer (PrintThread) + (void)printThreads; @end #endif /* EMGWriteLibraries_h */ ================================================ FILE: ETTrace/TracerSwift/ThreadHelper.swift ================================================ // // ThreadHelper.swift // Tracer // // Created by Itay Brenner on 23/7/24. // import Foundation import Darwin import MachO public struct StackFrame { let symbol: String let file: String let address: UInt var demangledSymbol: String { return _stdlib_demangleName(symbol) } } public struct ThreadInfo: Hashable { let name: String let number: Int } @objc public class ThreadHelper: NSObject { public static let main_thread_t = mach_thread_self() static var symbolsLoaded = false static var symbolAddressTuples = [(UInt, String)]() static let kMaxFramesPerStack = 512 @objc public static func printThreads() { NSLog("Stack trace:") let backtrace = callStackForAllThreads() for (thread, stackframe) in backtrace { NSLog("Thread \(thread.number): \(thread.name)") for (index, frame) in stackframe.enumerated() { NSLog(" \(index) - \(frame.demangledSymbol) [0x\(String(frame.address, radix: 16))] (\(frame.file)") } } } public static func callStackForAllThreads() -> [ThreadInfo: [StackFrame]] { var result: [ThreadInfo: [StackFrame]] = [:] var count: mach_msg_type_number_t = 0 var threads: thread_act_array_t! guard task_threads(mach_task_self_, &(threads), &count) == KERN_SUCCESS else { return result } defer { let size = MemoryLayout.size * Int(count) vm_deallocate(mach_task_self_, vm_address_t(bitPattern: threads), vm_size_t(size)) } var frameCount: UInt64 = 0 var frames = [UInt64](repeating: 0, count: kMaxFramesPerStack) for i in 0.. = framesPtr.baseAddress! thread_suspend(thread) // Ensure any code here does not take locks using @_noLocks getStacktrace(forThread: thread, frames: firstPtr, maxFrames: UInt64(kMaxFramesPerStack), frameCount: frameCountPtr) thread_resume(thread) } } let stacktrace = Array(frames.prefix(Int(frameCount))) let stacks = getCallStack(stacktrace) ?? [] let threadInfo: ThreadInfo = ThreadInfo(name: threadName,number: index) result[threadInfo] = stacks } } return result } static func getCallStack(_ array: [UInt64]) -> [StackFrame]? { var symbols = [StackFrame]() for address in array { var info = Dl_info() if dladdr(UnsafeRawPointer(bitPattern: UInt(address)), &info) != 0 { let functionName = info.dli_sname.map { String(cString: $0) } ?? alternativeSymbolName(UInt(address)) let fileName = info.dli_fname.map { String(cString: $0) } ?? "" symbols.append(StackFrame(symbol: functionName, file: fileName, address: UInt(address))) } } return symbols } static func alternativeSymbolName(_ address: UInt) -> String { NSLog("Using alternate name") if (!symbolsLoaded) { parseImages() } var previous: (UInt, String)? = nil for (addr, str) in symbolAddressTuples { if addr > address { return previous?.1 ?? "Invalid" } previous = (addr, str) } return "" } private static func getThreadName(_ thread: pthread_t) -> String? { var name = [Int8](repeating: 0, count: 256) let result = pthread_getname_np(thread, &name, name.count) if result != 0 { print("Failed to get thread name: \(result)") return nil } return String(cString: name) } private static func parseImages() { for i in 0..<_dyld_image_count() { guard let header = _dyld_get_image_header(i) else { continue } let slide = _dyld_get_image_vmaddr_slide(i) let bytes: UnsafeRawPointer = UnsafeRawPointer(OpaquePointer(header)) var symtabCommand: symtab_command? var linkeditCmd: segment_command_64? bytes.processLoadComands { command, commandPointer in switch command.cmd { case UInt32(LC_SYMTAB): let commandType = commandPointer.load(as: symtab_command.self) symtabCommand = commandType case UInt32(LC_SEGMENT_64): let cmd = commandPointer.load(as: segment_command_64.self) var segname = cmd.segname if strcmp(&segname, SEG_LINKEDIT) == 0 { linkeditCmd = commandPointer.load(as: segment_command_64.self) } default: break } return true } guard let command = symtabCommand, let linkeditCmd = linkeditCmd else { continue } let linkeditBase = slide + Int(linkeditCmd.vmaddr) - Int(linkeditCmd.fileoff) parseTable(command: command, linkeditBase, slide) } symbolAddressTuples.sort { addr1, addr2 in return addr1.0 < addr2.0 } symbolsLoaded = true } private static func parseTable(command: symtab_command, _ linkeditBase: Int, _ slide: Int) { let imageBase = UnsafeRawPointer(bitPattern: linkeditBase)! let nsyms = command.nsyms let symStart = imageBase.advanced(by: Int(command.symoff)) let strStart = imageBase.advanced(by: Int(command.stroff)) for i in 0...size) let nlist = symbolStart.load(as: nlist_64.self) guard (nlist.n_type & UInt8(N_STAB) == 0) && nlist.n_value != 0 else { continue } let stringStart = strStart.advanced(by: Int(nlist.n_un.n_strx)) let string = String(cString: stringStart.assumingMemoryBound(to: UInt8.self)) // Add slide since frame addresses will have it symbolAddressTuples.append((UInt(nlist.n_value) + UInt(slide), string)) } } #if swift(>=5.10) @_noLocks static func getStacktrace( forThread thread: thread_t, frames: UnsafeMutablePointer, maxFrames: UInt64, frameCount: UnsafeMutablePointer) { FIRCLSWriteThreadStack(thread, frames, maxFrames, frameCount) } #else static func getStacktrace( forThread thread: thread_t, frames: UnsafeMutablePointer, maxFrames: UInt64, frameCount: UnsafeMutablePointer) { FIRCLSWriteThreadStack(thread, frames, maxFrames, frameCount) } #endif } @_silgen_name("swift_demangle") public func _stdlib_demangleImpl( mangledName: UnsafePointer?, mangledNameLength: UInt, outputBuffer: UnsafeMutablePointer?, outputBufferSize: UnsafeMutablePointer?, flags: UInt32 ) -> UnsafeMutablePointer? public func _stdlib_demangleName(_ mangledName: String) -> String { return mangledName.utf8CString.withUnsafeBufferPointer { (mangledNameUTF8CStr) in let demangledNamePtr = _stdlib_demangleImpl( mangledName: mangledNameUTF8CStr.baseAddress, mangledNameLength: UInt(mangledNameUTF8CStr.count - 1), outputBuffer: nil, outputBufferSize: nil, flags: 0) if let demangledNamePtr = demangledNamePtr { let demangledName = String(cString: demangledNamePtr) free(demangledNamePtr) return demangledName } return mangledName } } #if swift(>=5.10) @_silgen_name("FIRCLSWriteThreadStack") @_noLocks func FIRCLSWriteThreadStack(_ thread: thread_t, _ frames: UnsafeMutablePointer, _ framesCapacity: UInt64, _ framesWritten: UnsafeMutablePointer) #else @_silgen_name("FIRCLSWriteThreadStack") func FIRCLSWriteThreadStack(_ thread: thread_t, _ frames: UnsafeMutablePointer, _ framesCapacity: UInt64, _ framesWritten: UnsafeMutablePointer) #endif ================================================ FILE: ETTrace/TracerSwift/UnsafeRawPointer+Commands.swift ================================================ // // UnsafeRawPointer+Commands.swift // Tracer // // Created by Itay Brenner on 15/8/24. // import Foundation import MachO extension UnsafeRawPointer { func numberOfCommands() -> (Int, UnsafeRawPointer)? { let headerPointer = load(as: mach_header_64.self) let headerSize: Int if headerPointer.magic == MH_MAGIC_64 { headerSize = MemoryLayout.size } else { return nil } return (Int(headerPointer.ncmds), advanced(by: headerSize)) } func processLoadComands(_ callback: (load_command, UnsafeRawPointer) -> Bool) { var pointer: UnsafeRawPointer guard let (numberOfCommands, headers) = numberOfCommands() else { return } if numberOfCommands > 1000 { print("Too many load commands") return } pointer = headers for _ in 0.. [!WARNING] > Make sure to add the ETTrace package product to your app as shown in the screenshot below. ![SPM Installation](https://raw.githubusercontent.com/EmergeTools/ETTrace/master/images/spm_installation.png) ### Manual Alternatively, run `./build.sh` to build the xcframework `ETTrace.xcframework`. Link the xcframework to your app. > [!NOTE] > Linking the framework to your app is the only installation step, there are no code changes you need to make. If everything is set up correctly "Starting ETTrace" will be printed to the console when you launch the app. ## Testimonials Our users love ETTrace and find it essential in their iOS development process. Here’s what some of them have to say: - **Keith Smiley, Principal Engineer @ Lyft**: "With ETTrace I can pinpoint exactly what is taking time in my app, and quickly find opportunities to improve it. It’s my go-to tool for in-depth performance analysis." - **Bruno Rocha, Software Engineer @ Spotify**: "ETTrace is my favorite tool for diagnosing iOS performance, the flamechart view makes it easy to find bottlenecks and speed up app launch." ## Using Launch your app and run `ettrace` or `ettrace --simulator`. After profiling, the result will be displayed on https://emergetools.com/flamegraph Note: Always launch the app by manually tapping the icon on the iOS homescreen, running the app through Xcode can result in inaccurate results. ## dSYMs You can point `ettrace` to a folder containing your dsyms with the `--dsyms` flag. If the dsyms are indexed by spotlight they will be automatically found and used. ## Run at Launch Use the flag `--launch` to start recording on app launch. When you first connect to the app using this flag, the app will force quit. On the next launch it will start profiling automatically and capture all of your `main` function. In some cases you need to record the first launch after an install. You can't use the `--launch` flag for this because that requires re-launching the app. Instead, add a boolean set to `YES` in your Info.plist with the key `ETTraceRunAtStartup`. You can then run `ettrace` regularly, without the `--launch` flag, and still start profiling at the start of app launch. ## Under the hood ETTrace spawns a new thread which captures a stacktrace of the main thread periodically. This is sampling based profiling. The sampling thread starts either when ettrace.framework is loaded (+load method), or when the CLI sends a message to the application. These control messages and the sampled data are communicated using PeerTalk. ## Adding events to the flamechart ETTrace supports displaying events in the flamechart from version v1.0. You can use them to track user flows in your code, you can easily add them by posting a notification in your code: ```swift NotificationCenter.default.post(name: Notification.Name(rawValue: "EmergeMetricStarted" | "EmergeMetricEnded"), object: nil, userInfo: [ "metric": "EVENT_NAME" ]) ``` Use `EmergeMetricStarted` to register the start of your flow and `EmergeMetricEnded` to track it's end. For example we would post this notification to track the start of an user login flow. ```swift NotificationCenter.default.post(name: Notification.Name(rawValue: "EmergeMetricStarted"), object: nil, userInfo: [ "metric": "USER_LOGIN" ]) ``` And then this one when the user successfuly logins: ```swift NotificationCenter.default.post(name: Notification.Name(rawValue: "EmergeMetricEnded"), object: nil, userInfo: [ "metric": "USER_LOGIN" ]) ``` In the flamechart we would be able to see this flow as follows: ![Events Flamechart](images/events_flamechart.png) ## Profiling background threads Use the flag `--multi-thread` (`-m`) to record all threads. This will provide a output.json file for every thread recorded, which you can drag into ETTrace. ================================================ FILE: Unwinding/.gitignore ================================================ FirebaseAuth/Tests/Sample/Sample/Application.plist FirebaseAuth/Tests/Sample/Sample/AuthCredentials.h FirebaseAuth/Tests/Sample/Sample/GoogleService-Info_multi.plist FirebaseAuth/Tests/Sample/Sample/GoogleService-Info.plist FirebaseAuth/Tests/Sample/Sample/Sample.entitlements FirebaseAuth/Tests/Sample/ApiTests/AuthCredentials.h FirebaseAuth/Tests/Sample/SwiftApiTests/Credentials.swift FirebaseDatabase/Tests/Resources/GoogleService-Info.plist FirebaseRemoteConfig/Tests/Sample/GoogleService-Info.plist # FirebaseStorage integration tests GoogleService-Info.plist FirebaseStorage/Tests/Integration/Resources/GoogleService-Info.plist # FirebaseInstallations integration tests GoogleService-Info.plist FirebaseInstallations/Source/Tests/Resources/GoogleService-Info.plist # FirebaseMessaging integration tests GoogleService-Info.plist FirebaseMessaging/Tests/IntegrationTests/Resources/GoogleService-Info.plist # FirebaseMessaging test app GoogleService-Info.plist FirebaseMessaging/Apps/Shared/GoogleService-Info.plist FirebaseMessaging/Apps/AdvancedSample/SampleWatchWatchKitExtension/GoogleService-Info.plist FirebaseMessaging/Apps/AdvancedSample/AppClips/GoogleService-Info.plist # Credentials for Firebase Storage Integration Tests FirebaseStorage/Tests/Integration/Credentials.h FirebaseStorage/Tests/SwiftIntegration/Credentials.swift FirebaseStorageSwift/Tests/Integration/Credentials.swift # FirebaseMLModelDownloader integration tests GoogleService-Info.plist FirebaseMLModelDownloader/Tests/Integration/Resources/GoogleService-Info.plist FirebaseMLModelDownloader/Apps/Sample/**/GoogleService-Info.plist # FirebasePerformance dev test App and integration tests GoogleService-Info.plist FirebasePerformance/**/GoogleService-Info.plist Secrets.tar # OS X .DS_Store # Xcode build/ *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ *.xccheckout profile *.moved-aside DerivedData *.hmap *.ipa # Swift Package Manager Package.resolved */.build ReleaseTooling/.swiftpm ReleaseTooling/Packages ReleaseTooling/*.xcodeproj ReleaseTooling/Package.resolved scripts/code_coverage_report/*/Package.resolved scripts/code_coverage_report/*/.build # Bad sorts get generated if the package .xcscheme is not regenerated. # Anything committed to xcshareddata gets propagated to clients. (#8167) .swiftpm/xcode/xcshareddata/ # Mint package manager Mint # IntelliJ .idea # Vim *.swo *.swp *~ # Bundler /.bundle /vendor Carthage # Cocoapods recommends against adding the Pods directory to your .gitignore. See # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control # Since Firebase is building libraries, not apps, we should not check in Pods. # Pods are only used in the Examples and tests and doing a 'pod install' better # matches our customers' environments. # # Note: if you ignore the Pods directory, make sure to uncomment # `pod install` in .travis.yml # Pods/ Podfile.lock *.xcworkspace # CMake .downloads Debug Release Ninja # CLion /cmake-build-debug /cmake-build-release # Python *.pyc # Visual Studio /.vs # Visual Studio Code /.vscode # clangd support file compile_commands.json # CocoaPods generate gen/ # b/111916494 default.profraw # Firestore emulator cloud-firestore-emulator.log cloud-firestore-emulator.pid # Let Gemfiles in CocoaPodsIntegrationTest float to catch issues asap CocoaPodsIntegrationTest/**/Gemfile.lock CocoaPodsIntegrationTest/Gemfile CocoaPodsIntegrationTest/Podfile # In-app messaging integration tests FirebaseInAppMessaging/Tests/Integration/FunctionalTestApp/GoogleService-Info.plist FirebaseInAppMessaging/Tests/Integration/FunctionalTestApp/App/InAppMessaging-Example-iOS/AppDelegate.m # FIRAppCheckTestApp FirebaseAppCheck/Apps/FIRAppCheckTestApp/FIRAppCheckTestApp/GoogleService-Info.plist FirebaseAppCheck/Apps/AppCheckCustomProvideApp/AppCheckCustomProvideApp/GoogleService-Info.plist # FirestoreSample /Example/FirestoreSample/FirestoreSample/GoogleService-Info.plist /Example/FirestoreSample/ui-debug.log /Example/FirestoreSample/firestore-debug.log ================================================ FILE: Unwinding/Crashlytics/CHANGELOG.md ================================================ # Unreleased - [fixed] Fixed an issue where passing nil as a value for a custom key or user ID did not clear the stored value as expected. # v8.9.0 - [fixed] Fixed an issue where exceptions with `nil` reasons weren't properly recorded (#8671). # v8.8.0 - [added] Internal SDK updates to test potential future MetricKit support. # v8.4.0 - [fixed] Bump Promises dependency. (#8365) # v8.3.0 - [fixed] Add missing dependency that could cause missing symbol build failures. (#8137) # v8.2.0 - [changed] Incorporated code quality changes around integer overflow, potential race conditions, and reinstalling signal handlers. - [fixed] Fixed an issue where iOS-only apps running on iPads would report iOS as their OS Name. - [fixed] Fixed depcrecation warning for projects with minimum deployment version iOS 13 and up. # v8.0.0 - [changed] Added a warning to upload-symbols when it detects a dSYM with hidden symbols. # v7.10.0 - [changed] Added a warning to upload-symbols when it detects a dSYM without any symbols. # v7.9.0 - [changed] Updated Firebase pod to allow iOS 9 installation via `pod 'Firebase/Crashlytics'` # v7.8.0 - [added] Added a new API checkAndUpdateUnsentReportsWithCompletion for updating the crash report from the previous run of the app if, for example, the developer wants to implement a feedback dialog to ask end-users for more information. Unsent Crashlytics Reports have familiar methods like setting custom keys and logs (#7503). - [changed] Added a limit to the number of unsent reports on disk to prevent disk filling up when automatic data collection is off. Developers can ensure this limit is never reached by calling send/deleteUnsentReports every run (#7619). # v7.7.0 - [added] Added a new API to allow for bulk logging of custom keys and values (#7302). # v7.6.0 - [fixed] Fixed an issue where some developers experienced a race condition involving binary image operations (#7459). # v7.5.0 - [changed] Improve start-up performance by moving some initialization work to a background thread (#7332). - [changed] Updated upload-symbols to a version that is notarized to avoid macOS security alerts (#7323). - [changed] Deleting unsent reports with deleteUnsentReports no longer happens on the main thread (#7298). # v7.4.0 - [changed] Removed obsolete crash reporting mechanism from the SDK (#7076). # v7.3.0 - [added] Added Crashlytics support for x86 apps running on Apple Silicon via Rosetta 2 - [changed] Decreased Crashlytics CocoaPods minimum deployment target from iOS 10 to iOS 9 - [changed] Removed obsolete API calls from upload-symbols - [changed] Removed obsolete onboarding calls from the SDK. # v7.1.0 - [fixed] Fixed an issue where symbol uploads would fail when there are spaces in the project path, particularly in Unity builds (#6789). - [changed] Added additional logging when settings requests fail with a 404 status to help customers debug onboarding issues (#6847). # v4.6.2 - [changed] Improved upload-symbols conversion speed. Customers with large dSYMs should see a significant improvement in the time it takes to upload Crashlytics symbols. - [fixed] Fixed Apple Watch crash related to `sigaction` (#6434). # v4.6.0 - [added] Added stackFrameWithAddress API for recording custom errors that are symbolicated on the backend (#5975). - [fixed] Fixed comment typos (#6363). - [fixed] Remove device information from binary image data crash info entries (#6382). # v4.5.0 - [fixed] Fixed a compiler warning and removed unused networking code (#6210). - [fixed] Fixed a crash that occurred rarely when trying to restart a URL session task without a valid request (#5984). - [added] Introduced watchOS support (#6262). # v4.3.1 - [fixed] Fixed a segmentation fault that could occur when writing crash contexts to disk (#6048). # v4.3.0 - [changed] Add dispatch_once for opening sdk log file. (#5904) - [changed] Functionally neutral updated import references for dependencies. (#5902) # v4.2.0 - [changed] Removed an unnecessary linker rule for embedding the Info.plist. (#5804) # v4.1.1 - [fixed] Fixed a crash that could occur if certain plist fields necessary to create Crashlytics records were missing at runtime. Also added some diagnostic logging to make the issue cause more explicit (#5565). # v4.1.0 - [fixed] Fixed unchecked `malloc`s in Crashlytics (#5428). - [fixed] Fixed an instance of undefined behavior when loading files from disk (#5454). # v4.0.0 - [changed] The Firebase Crashlytics SDK is now generally available. # v4.0.0-beta.7 - [changed] Increased network timeout for symbol uploads to improve reliability on limited internet connections. (#5228) # v4.0.0-beta.6 - [added] Added a new API to record custom exception models and stacktraces to Crashlytics. This is a replacement for the `recordCustomException` API that existed in the Fabric Crashlytics SDK (#5055) - [fixed] Fixed an issue with the `sendUnsentReports` API where reports wouldn't be uploaded until the method was called twice in specific instances (#5060) - [changed] Changed Crashlytics to use GoogleDataTransport to upload crashes (#4989) - [changed] Changed the origin that Crashlytics uses to register Crash events for Crash Free Users. Ensure you have installed Firebase Analytics version 6.3.1 or above (#5030) # v4.0.0-beta.5 - [changed] Changed two endpoints in the Firebase Crashlytics SDK with no expected end-user impact (#4953, #4988). # v4.0.0-beta.4 - [fixed] Fixed symbol collisions with the legacy Fabric Crashlytics SDK and added a warning not to include both (#4753, #4755) - [fixed] Added crash prevention checks (#4661) # v4.0.0-beta.3 - [fixed] Fixed an import declaration for installing Crashlytics. Previously, the declaration caused a compile error when you installed using CocoaPods with the `generate_multiple_pods_project` flag set to true (#4786) # v4.0.0-beta.2 - [fixed] Fixed VeraCode scanner issues for unchecked error conditions (#4669) # v4.0.0-beta.1 This Firebase Crashlytics version includes the initial beta release of the Firebase Crashlytics SDK: - [feature] The SDK is now open-sourced. Take a look in our [GitHub repository](https://github.com/firebase/firebase-ios-sdk/tree/master/Crashlytics). - [feature] Added support for Catalyst (note that Crashlytics still supports tvOS and macOS). - [feature] Added new APIs that are more consistent with other Firebase SDKs and more intuitive to use. The new APIs also give your users more control over how you collect their data. - [removed] Removed the Fabric API Key. Now, Crashlytics uses the GoogleService-Info.plist file to associate your app with your project. If you linked your app from Fabric and want to upgrade to the new SDK, remove the Fabric API key from your `run` and `upload-symbols` scripts. We also recommend removing the Fabric section from your app's Info.plist (when you upgrade, Crashlytics uses the new configuration you set up in Firebase). ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Components/FIRCLSEmerge.c ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "../Helpers/FIRCLSDefines.h" #include "../Helpers/FIRCLSThreadState.h" #include "../Unwind/FIRCLSUnwind.h" #include "../Helpers/FIRCLSUtility.h" #include #include #include #include #include #include #define THREAD_NAME_BUFFER_SIZE (64) void FIRCLSWriteThreadStack(thread_t thread, uintptr_t *frames, uint64_t framesCapacity, uint64_t *framesWritten) { *framesWritten = 0; FIRCLSUnwindContext unwindContext; FIRCLSThreadContext context; // try to get the value by querying the thread state mach_msg_type_number_t stateCount = FIRCLSThreadStateCount; // For unknown reasons, thread_get_state returns this value on Rosetta, // but still succeeds. const int ROSETTA_SUCCESS = 268435459; kern_return_t status = thread_get_state(thread, FIRCLSThreadState, (thread_state_t)(&(context.__ss)), &stateCount); if (status != KERN_SUCCESS && status != ROSETTA_SUCCESS) { FIRCLSSDKLogError("Failed to get thread state via thread_get_state for thread: %i\n", thread); *framesWritten = 0; return; } if (!FIRCLSUnwindInit(&unwindContext, context)) { FIRCLSSDKLog("Unable to init unwind context\n"); return; } uint32_t repeatedPCCount = 0; uint64_t repeatedPC = 0; while (FIRCLSUnwindNextFrame(&unwindContext) && (*framesWritten) < framesCapacity) { const uintptr_t pc = FIRCLSUnwindGetPC(&unwindContext); const uint32_t frameCount = FIRCLSUnwindGetFrameRepeatCount(&unwindContext); if (repeatedPC == pc && repeatedPC != 0) { // actively counting a recursion repeatedPCCount = frameCount; continue; } if (frameCount >= FIRCLSUnwindInfiniteRecursionCountThreshold && repeatedPC == 0) { repeatedPC = pc; continue; } frames[*framesWritten] = pc; (*framesWritten)++; } return; } ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Components/FIRCLSGlobals.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "../Helpers/FIRCLSInternalLogging.h" #include __BEGIN_DECLS __END_DECLS ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include // macro trickiness #define CONCAT_EXPANDED(a, b) a##b #define CONCAT(a, b) CONCAT_EXPANDED(a, b) // These macros generate a function to force a symbol for the containing .o, to work around an issue // where strip will not strip debug information without a symbol to strip. #define DUMMY_FUNCTION_NAME(x) CONCAT(fircls_strip_this_, x) #define INJECT_STRIP_SYMBOL(x) \ void DUMMY_FUNCTION_NAME(x)(void) { \ } // These make some target os types available to previous versions of xcode that do not yet have them // in their SDKs #ifndef TARGET_OS_IOS #define TARGET_OS_IOS TARGET_OS_IPHONE #endif #ifndef TARGET_OS_WATCH #define TARGET_OS_WATCH 0 #endif #ifndef TARGET_OS_TV #define TARGET_OS_TV 0 #endif // Whether MetricKit should be supported #if defined(__IPHONE_15_0) #define CLS_METRICKIT_SUPPORTED (__has_include() && TARGET_OS_IOS) #else #define CLS_METRICKIT_SUPPORTED 0 #endif // These help compile based on availability of technologies/frameworks. #define CLS_TARGET_OS_OSX (TARGET_OS_MAC && !TARGET_OS_IPHONE) #define CLS_TARGET_OS_HAS_UIKIT (TARGET_OS_IOS || TARGET_OS_TV) // arch definitions #if defined(__arm__) || defined(__arm64__) || defined(__arm64e__) #include #endif #if defined(__arm__) #define CLS_CPU_ARM 1 #endif #if defined(__arm64__) || defined(__arm64e__) #define CLS_CPU_ARM64 1 #endif #if defined(__ARM_ARCH_7S__) #define CLS_CPU_ARMV7S 1 #endif #if defined(_ARM_ARCH_7) #define CLS_CPU_ARMV7 1 #endif #if defined(_ARM_ARCH_6) #define CLS_CPU_ARMV6 1 #endif #if defined(__i386__) #define CLS_CPU_I386 1 #endif #if defined(__x86_64__) #define CLS_CPU_X86_64 1 #endif #define CLS_CPU_X86 (CLS_CPU_I386 || CLS_CPU_X86_64) #define CLS_CPU_64BIT (CLS_CPU_X86_64 || CLS_CPU_ARM64) ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include "FIRCLSDefines.h" #define CLS_MEMORY_PROTECTION_ENABLED 1 // #define CLS_COMPACT_UNWINDED_ENABLED 1 #define CLS_DWARF_UNWINDING_ENABLED 1 #define CLS_USE_SIGALTSTACK (!TARGET_OS_WATCH && !TARGET_OS_TV) #define CLS_CAN_SUSPEND_THREADS !TARGET_OS_WATCH #define CLS_MACH_EXCEPTION_SUPPORTED (!TARGET_OS_WATCH && !TARGET_OS_TV) #define CLS_SIGNAL_SUPPORTED !TARGET_OS_WATCH // As of WatchOS 3, Signal crashes are not supported #define CLS_COMPACT_UNWINDING_SUPPORTED \ ((CLS_CPU_I386 || CLS_CPU_X86_64 || CLS_CPU_ARM64) && CLS_COMPACT_UNWINDED_ENABLED) #define CLS_DWARF_UNWINDING_SUPPORTED \ (CLS_COMPACT_UNWINDING_SUPPORTED && CLS_DWARF_UNWINDING_ENABLED) ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.c ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include "FIRCLSInternalLogging.h" #include "../Components/FIRCLSGlobals.h" #include "../Helpers/FIRCLSUtility.h" void FIRCLSSDKFileLog(FIRCLSInternalLogLevel level, const char* format, ...) { va_list args; va_start(args, format); va_end(args); } ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include #if __OBJC__ #import "FIRCLSLogger.h" #define FIRCLSDeveloperLog(label, __FORMAT__, ...) \ FIRCLSDebugLog(@"[" label "] " __FORMAT__, ##__VA_ARGS__); #endif typedef enum { FIRCLSInternalLogLevelUnknown = 0, FIRCLSInternalLogLevelDebug = 1, FIRCLSInternalLogLevelInfo = 2, FIRCLSInternalLogLevelWarn = 3, FIRCLSInternalLogLevelError = 4 } FIRCLSInternalLogLevel; typedef struct { int logFd; FIRCLSInternalLogLevel logLevel; } FIRCLSInternalLoggingWritableContext; #define FIRCLSSDKLogDebug(__FORMAT__, ...) \ FIRCLSSDKFileLog(FIRCLSInternalLogLevelDebug, "DEBUG [%s:%d] " __FORMAT__, __FUNCTION__, \ __LINE__, ##__VA_ARGS__) #define FIRCLSSDKLogInfo(__FORMAT__, ...) \ FIRCLSSDKFileLog(FIRCLSInternalLogLevelInfo, "INFO [%s:%d] " __FORMAT__, __FUNCTION__, \ __LINE__, ##__VA_ARGS__) #define FIRCLSSDKLogWarn(__FORMAT__, ...) \ FIRCLSSDKFileLog(FIRCLSInternalLogLevelWarn, "WARN [%s:%d] " __FORMAT__, __FUNCTION__, \ __LINE__, ##__VA_ARGS__) #define FIRCLSSDKLogError(__FORMAT__, ...) \ FIRCLSSDKFileLog(FIRCLSInternalLogLevelError, "ERROR [%s:%d] " __FORMAT__, __FUNCTION__, \ __LINE__, ##__VA_ARGS__) #define FIRCLSSDKLog FIRCLSSDKLogWarn __BEGIN_DECLS void FIRCLSSDKFileLog(FIRCLSInternalLogLevel level, const char* format, ...) __printflike(2, 3); __END_DECLS ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import __BEGIN_DECLS void FIRCLSDebugLog(NSString *message, ...); void FIRCLSInfoLog(NSString *message, ...); void FIRCLSWarningLog(NSString *message, ...); void FIRCLSErrorLog(NSString *message, ...); __END_DECLS ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.m ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #import "FIRCLSLogger.h" NSString *const CrashlyticsMessageCode = @"I-CLS000000"; void FIRCLSDebugLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); va_end(args_ptr); } void FIRCLSInfoLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); va_end(args_ptr); } void FIRCLSWarningLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); va_end(args_ptr); } void FIRCLSErrorLog(NSString *message, ...) { va_list args_ptr; va_start(args_ptr, message); va_end(args_ptr); } ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.c ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "FIRCLSThreadState.h" #include "FIRCLSDefines.h" #include "FIRCLSUtility.h" #if defined(__arm__) || defined(__arm64__) #include #include #endif #if CLS_CPU_X86_64 #define GET_IP_REGISTER(r) (r->__ss.__rip) #define GET_FP_REGISTER(r) (r->__ss.__rbp) #define GET_SP_REGISTER(r) (r->__ss.__rsp) #define GET_LR_REGISTER(r) 0 #define SET_IP_REGISTER(r, v) (r->__ss.__rip = v) #define SET_FP_REGISTER(r, v) (r->__ss.__rbp = v) #define SET_SP_REGISTER(r, v) (r->__ss.__rsp = v) #define SET_LR_REGISTER(r, v) #elif CLS_CPU_I386 #define GET_IP_REGISTER(r) (r->__ss.__eip) #define GET_FP_REGISTER(r) (r->__ss.__ebp) #define GET_SP_REGISTER(r) (r->__ss.__esp) #define GET_LR_REGISTER(r) 0 #define SET_IP_REGISTER(r, v) (r->__ss.__eip = v) #define SET_FP_REGISTER(r, v) (r->__ss.__ebp = v) #define SET_SP_REGISTER(r, v) (r->__ss.__esp = v) #define SET_LR_REGISTER(r, v) #elif CLS_CPU_ARM64 // The arm_thread_state64_get_* macros translate down to the AUTIA and AUTIB instructions which // authenticate the address, but don't clear the upper bits. From the docs: // "If the authentication passes, the upper bits of the address are restored to enable // subsequent use of the address. the authentication fails, the upper bits are corrupted and // any subsequent use of the address results in a Translation fault." // Since we only want the address (with the metadata in the upper bits masked out), we used the // ptrauth_strip macro to clear the upper bits. // // We found later that ptrauth_strip doesn't seem to do anything. In many cases, the upper bits were // already stripped, so for most non-system-library code, Crashlytics would still symbolicate. But // for system libraries, the upper bits were being left in even when we called ptrauth_strip. // Instead, we're bit masking and only allowing the latter 36 bits. #define CLS_PTRAUTH_STRIP(pointer) ((uintptr_t)pointer & 0x0000000FFFFFFFFF) #define GET_IP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_pc(r->__ss))) #define GET_FP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_fp(r->__ss))) #define GET_SP_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_sp(r->__ss))) #define GET_LR_REGISTER(r) (CLS_PTRAUTH_STRIP(arm_thread_state64_get_lr(r->__ss))) #define SET_IP_REGISTER(r, v) arm_thread_state64_set_pc_fptr(r->__ss, (void*)v) #define SET_FP_REGISTER(r, v) arm_thread_state64_set_fp(r->__ss, v) #define SET_SP_REGISTER(r, v) arm_thread_state64_set_sp(r->__ss, v) #define SET_LR_REGISTER(r, v) arm_thread_state64_set_lr_fptr(r->__ss, (void*)v) #elif CLS_CPU_ARM #define GET_IP_REGISTER(r) (r->__ss.__pc) #define GET_FP_REGISTER(r) (r->__ss.__r[7]) #define GET_SP_REGISTER(r) (r->__ss.__sp) #define GET_LR_REGISTER(r) (r->__ss.__lr) #define SET_IP_REGISTER(r, v) (r->__ss.__pc = v) #define SET_FP_REGISTER(r, v) (r->__ss.__r[7] = v) #define SET_SP_REGISTER(r, v) (r->__ss.__sp = v) #define SET_LR_REGISTER(r, v) (r->__ss.__lr = v) #else #error "Architecture Unsupported" #endif uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers) { if (!registers) { return 0; } return GET_IP_REGISTER(registers); } uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* registers) { if (!registers) { return 0; } return GET_SP_REGISTER(registers); } bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, uintptr_t value) { if (!FIRCLSIsValidPointer(registers)) { return false; } SET_SP_REGISTER(registers, value); return true; } uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* registers) { if (!FIRCLSIsValidPointer(registers)) { return 0; } return GET_LR_REGISTER(registers); } bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, uintptr_t value) { if (!FIRCLSIsValidPointer(registers)) { return false; } SET_LR_REGISTER(registers, value); return true; } bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t value) { if (!registers) { return false; } SET_IP_REGISTER(registers, value); return true; } uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* registers) { if (!FIRCLSIsValidPointer(registers)) { return 0; } return GET_FP_REGISTER(registers); } bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, uintptr_t value) { if (!FIRCLSIsValidPointer(registers)) { return false; } SET_FP_REGISTER(registers, value); return true; } ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include #include #if CLS_CPU_ARM #define FIRCLSThreadStateCount ARM_THREAD_STATE_COUNT #define FIRCLSThreadState ARM_THREAD_STATE #elif CLS_CPU_ARM64 #define FIRCLSThreadStateCount ARM_THREAD_STATE64_COUNT #define FIRCLSThreadState ARM_THREAD_STATE64 #elif CLS_CPU_I386 #define FIRCLSThreadStateCount x86_THREAD_STATE32_COUNT #define FIRCLSThreadState x86_THREAD_STATE32 #elif CLS_CPU_X86_64 #define FIRCLSThreadStateCount x86_THREAD_STATE64_COUNT #define FIRCLSThreadState x86_THREAD_STATE64 #endif // _STRUCT_MCONTEXT was fixed to point to the right thing on ARM in the iOS 7.1 SDK typedef _STRUCT_MCONTEXT FIRCLSThreadContext; // I'm not entirely sure what happened when, but this appears to have disappeared from // the SDKs... #if !defined(_STRUCT_UCONTEXT64) typedef _STRUCT_UCONTEXT _STRUCT_UCONTEXT64; #endif #pragma mark Register Access uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers); uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* registers); uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* registers); bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t value); bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, uintptr_t value); bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, uintptr_t value); // The link register only exists on ARM platforms. #if CLS_CPU_ARM || CLS_CPU_ARM64 uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* registers); bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, uintptr_t value); #endif ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include #include #include #include "../Components/FIRCLSGlobals.h" #define FIRCLSIsValidPointer(x) ((uintptr_t)x >= 4096) #define FIRCLSInvalidCharNybble (255) __BEGIN_DECLS void FIRCLSLookupFunctionPointer(void* ptr, void (^block)(const char* name, const char* lib)); void FIRCLSHexFromByte(uint8_t c, char output[]); uint8_t FIRCLSNybbleFromChar(char c); bool FIRCLSReadMemory(vm_address_t src, void* dest, size_t len); bool FIRCLSReadString(vm_address_t src, char** dest, size_t maxlen); bool FIRCLSUnlinkIfExists(const char* path); void FIRCLSRedactUUID(char* value); #if __OBJC__ void FIRCLSDispatchAfter(float timeInSeconds, dispatch_queue_t queue, dispatch_block_t block); NSString* FIRCLSNormalizeUUID(NSString* value); void FIRCLSAddOperationAfter(float timeInSeconds, NSOperationQueue* queue, void (^block)(void)); #endif #if DEBUG void FIRCLSPrintAUUID(const uint8_t* value); #endif __END_DECLS ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.m ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "FIRCLSUtility.h" #include #include #include "../Components/FIRCLSGlobals.h" #include "FIRCLSFeatures.h" #import void FIRCLSLookupFunctionPointer(void* ptr, void (^block)(const char* name, const char* lib)) { Dl_info info; if (dladdr(ptr, &info) == 0) { block(NULL, NULL); return; } const char* name = "unknown"; const char* lib = "unknown"; if (info.dli_sname) { name = info.dli_sname; } if (info.dli_fname) { lib = info.dli_fname; } block(name, lib); } uint8_t FIRCLSNybbleFromChar(char c) { if (c >= '0' && c <= '9') { return c - '0'; } if (c >= 'a' && c <= 'f') { return c - 'a' + 10; } if (c >= 'A' && c <= 'F') { return c - 'A' + 10; } return FIRCLSInvalidCharNybble; } bool FIRCLSReadMemory(vm_address_t src, void* dest, size_t len) { if (!FIRCLSIsValidPointer(src)) { return false; } vm_size_t readSize = len; // Originally this was a `vm_read_overwrite` to protect against reading invalid memory. // That can happen in the context of a crash reporter, but should not happen during normal // ettrace operation. Replacing it with memcpy makes this about 5x faster // return vm_read_overwrite(mach_task_self(), src, len, (pointer_t)dest, &readSize) == KERN_SUCCESS; memcpy(dest, src, len); return true; } bool FIRCLSReadString(vm_address_t src, char** dest, size_t maxlen) { char c; vm_address_t address; if (!dest) { return false; } // Walk the entire string. Not certain this is perfect... for (address = src; address < src + maxlen; ++address) { if (!FIRCLSReadMemory(address, &c, 1)) { return false; } if (c == 0) { break; } } *dest = (char*)src; return true; } void FIRCLSDispatchAfter(float timeInSeconds, dispatch_queue_t queue, dispatch_block_t block) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInSeconds * NSEC_PER_SEC)), queue, block); } bool FIRCLSUnlinkIfExists(const char* path) { if (unlink(path) != 0) { if (errno != ENOENT) { return false; } } return true; } NSString* FIRCLSNormalizeUUID(NSString* value) { return [[value stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString]; } // Redacts a UUID wrapped in parenthesis from a char* using strchr, which is async safe. // Ex. // "foo (bar) (45D62CC2-CFB5-4E33-AB61-B0684627F1B6) baz" // becomes // "foo (bar) (********-****-****-****-************) baz" void FIRCLSRedactUUID(char* value) { if (value == NULL) { return; } char* openParen = value; // find the index of the first paren while ((openParen = strchr(openParen, '(')) != NULL) { // find index of the matching close paren const char* closeParen = strchr(openParen, ')'); if (closeParen == NULL) { break; } // if the distance between them is 37, traverse the characters // and replace anything that is not a '-' with '*' if (closeParen - openParen == 37) { for (int i = 1; i < 37; ++i) { if (*(openParen + i) != '-') { *(openParen + i) = '*'; } } break; } openParen++; } } void FIRCLSAddOperationAfter(float timeInSeconds, NSOperationQueue* queue, void (^block)(void)) { dispatch_queue_t afterQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); FIRCLSDispatchAfter(timeInSeconds, afterQueue, ^{ [queue addOperationWithBlock:block]; }); } #if DEBUG void FIRCLSPrintAUUID(const uint8_t* value) { CFUUIDRef uuid = CFUUIDCreateFromUUIDBytes(kCFAllocatorDefault, *(CFUUIDBytes*)value); NSString* string = CFBridgingRelease(CFUUIDCreateString(kCFAllocatorDefault, uuid)); CFRelease(uuid); FIRCLSDebugLog(@"%@", [[string stringByReplacingOccurrencesOfString:@"-" withString:@""] lowercaseString]); } #endif ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.c ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "FIRCLSUnwind.h" #include "../Helpers/FIRCLSFeatures.h" #include "../Components/FIRCLSGlobals.h" #include "../Helpers/FIRCLSUtility.h" #include #include #include // Without a limit on the number of frames we unwind, there's a real possibility // we'll get stuck in an infinite loop. But, we still need pretty big limits, // because stacks can get quite big. Also, the stacks are different on the platforms. // These values were empirically determined (~525000 on OS X, ~65000 on iOS). #if TARGET_OS_EMBEDDED const uint32_t FIRCLSUnwindMaxFrames = 100000; #else const uint32_t FIRCLSUnwindMaxFrames = 600000; #endif const uint32_t FIRCLSUnwindInfiniteRecursionCountThreshold = 10; #pragma mark Prototypes static bool FIRCLSUnwindNextFrameUsingAllStrategies(FIRCLSUnwindContext* context); #if CLS_COMPACT_UNWINDING_SUPPORTED static bool FIRCLSUnwindWithCompactUnwindInfo(FIRCLSUnwindContext* context); #endif bool FIRCLSUnwindContextHasValidPCAndSP(FIRCLSUnwindContext* context); #pragma mark - API bool FIRCLSUnwindInit(FIRCLSUnwindContext* context, FIRCLSThreadContext threadContext) { if (!context) { return false; } memset(context, 0, sizeof(FIRCLSUnwindContext)); context->registers = threadContext; return true; } bool FIRCLSUnwindNextFrame(FIRCLSUnwindContext* context) { if (!FIRCLSIsValidPointer(context)) { FIRCLSSDKLog("Error: invalid inputs\n"); return false; } if (!FIRCLSUnwindContextHasValidPCAndSP(context)) { // This is a special-case. It is possible to try to unwind a thread that has no stack (ie, is // executing zero functions. I believe this happens when a thread has exited, but before the // kernel has actually cleaned it up. This situation can only apply to the first frame. So, in // that case, we don't count it as an error. But, if it happens mid-unwind, it's a problem. if (context->frameCount == 0) { FIRCLSSDKLog("Cancelling unwind for thread with invalid PC/SP\n"); } else { FIRCLSSDKLog("Error: thread PC/SP invalid before unwind\n"); } return false; } if (!FIRCLSUnwindNextFrameUsingAllStrategies(context)) { FIRCLSSDKLogError("Failed to advance to the next frame\n"); return false; } uintptr_t pc = FIRCLSUnwindGetPC(context); uintptr_t sp = FIRCLSUnwindGetStackPointer(context); // Unwinding will complete when this is no longer a valid value if (!FIRCLSIsValidPointer(pc)) { return false; } // after unwinding, validate that we have a sane register value if (!FIRCLSIsValidPointer(sp)) { FIRCLSSDKLog("Error: SP (%p) isn't a valid pointer\n", (void*)sp); return false; } // track repeating frames if (context->lastFramePC == pc) { context->repeatCount += 1; } else { context->repeatCount = 0; } context->frameCount += 1; context->lastFramePC = pc; return true; } #pragma mark - Register Accessors uintptr_t FIRCLSUnwindGetPC(FIRCLSUnwindContext* context) { if (!FIRCLSIsValidPointer(context)) { return 0; } return FIRCLSThreadContextGetPC(&context->registers); } uintptr_t FIRCLSUnwindGetStackPointer(FIRCLSUnwindContext* context) { if (!FIRCLSIsValidPointer(context)) { return 0; } return FIRCLSThreadContextGetStackPointer(&context->registers); } static uintptr_t FIRCLSUnwindGetFramePointer(FIRCLSUnwindContext* context) { if (!FIRCLSIsValidPointer(context)) { return 0; } return FIRCLSThreadContextGetFramePointer(&context->registers); } uint32_t FIRCLSUnwindGetFrameRepeatCount(FIRCLSUnwindContext* context) { if (!FIRCLSIsValidPointer(context)) { return 0; } return context->repeatCount; } #pragma mark - Unwind Strategies static bool FIRCLSUnwindNextFrameUsingAllStrategies(FIRCLSUnwindContext* context) { if (!FIRCLSIsValidPointer(context)) { FIRCLSSDKLogError("Arguments invalid\n"); return false; } if (context->frameCount >= FIRCLSUnwindMaxFrames) { FIRCLSSDKLogWarn("Exceeded maximum number of frames\n"); return false; } uintptr_t pc = FIRCLSUnwindGetPC(context); // Ok, what's going on here? libunwind's UnwindCursor::setInfoBasedOnIPRegister has a // parameter that, if true, does this subtraction. Despite the comments in the code // (of 35.1), I found that the parameter was almost always set to true. // // I then ran into a problem when unwinding from _pthread_start -> thread_start. This // is a common transition, which happens in pretty much every report. An extra frame // was being generated, because the PC we get for _pthread_start was mapping to exactly // one greater than the function's last byte, according to the compact unwind info. This // resulted in using the wrong compact encoding, and picking the next function, which // turned out to be dwarf instead of a frame pointer. // So, the moral is - do the subtraction for all frames except the first. I haven't found // a case where it produces an incorrect result. Also note that at first, I thought this would // subtract one from the final addresses too. But, the end of this function will *compute* PC, // so this value is used only to look up unwinding data. if (context->frameCount > 0) { --pc; if (!FIRCLSThreadContextSetPC(&context->registers, pc)) { FIRCLSSDKLogError("Unable to set PC\n"); return false; } } if (!FIRCLSIsValidPointer(pc)) { FIRCLSSDKLogError("PC is invalid\n"); return false; } // the first frame is special - as the registers we need // are already loaded by definition if (context->frameCount == 0) { return true; } #if CLS_COMPACT_UNWINDING_SUPPORTED // attempt to advance to the next frame using compact unwinding, and // only fall back to the frame pointer if that fails if (FIRCLSUnwindWithCompactUnwindInfo(context)) { return true; } #endif // If the frame pointer is zero, we cannot use an FP-based unwind and we can reasonably // assume that we've just gotten to the end of the stack. if (FIRCLSUnwindGetFramePointer(context) == 0) { FIRCLSSDKLogWarn("FP is zero, aborting unwind\n"); // make sure to set the PC to zero, to indicate the unwind is complete return FIRCLSThreadContextSetPC(&context->registers, 0); } // Only allow stack scanning (as a last resort) if we're on the first frame. All others // are too likely to screw up. if (FIRCLSUnwindWithFramePointer(&context->registers, context->frameCount == 1)) { return true; } FIRCLSSDKLogError("Unable to use frame pointer\n"); return false; } #if CLS_COMPACT_UNWINDING_SUPPORTED static bool FIRCLSUnwindWithCompactUnwindInfo(FIRCLSUnwindContext* context) { if (!context) { return false; } // step one - find the image the current pc is within FIRCLSBinaryImageRuntimeNode image; uintptr_t pc = FIRCLSUnwindGetPC(context); if (!FIRCLSBinaryImageSafeFindImageForAddress(pc, &image)) { FIRCLSSDKLogWarn("Unable to find binary for %p\n", (void*)pc); return false; } #if CLS_BINARY_IMAGE_RUNTIME_NODE_RECORD_NAME FIRCLSSDKLogDebug("Binary image for %p at %p => %s\n", (void*)pc, image.baseAddress, image.name); #else FIRCLSSDKLogDebug("Binary image for %p at %p\n", (void*)pc, image.baseAddress); #endif if (!FIRCLSBinaryImageSafeHasUnwindInfo(&image)) { FIRCLSSDKLogInfo("Binary image at %p has no unwind info\n", image.baseAddress); return false; } if (!FIRCLSCompactUnwindInit(&context->compactUnwindState, image.unwindInfo, image.ehFrame, (uintptr_t)image.baseAddress)) { FIRCLSSDKLogError("Unable to read unwind info\n"); return false; } // this function will actually attempt to find compact unwind info for the current PC, // and use it to mutate the context register state return FIRCLSCompactUnwindLookupAndCompute(&context->compactUnwindState, &context->registers); } #endif #pragma mark - Utility Functions bool FIRCLSUnwindContextHasValidPCAndSP(FIRCLSUnwindContext* context) { return FIRCLSIsValidPointer(FIRCLSUnwindGetPC(context)) && FIRCLSIsValidPointer(FIRCLSUnwindGetStackPointer(context)); } #if CLS_CPU_64BIT #define BASIC_INFO_TYPE vm_region_basic_info_64_t #define BASIC_INFO VM_REGION_BASIC_INFO_64 #define BASIC_INFO_COUNT VM_REGION_BASIC_INFO_COUNT_64 #define vm_region_query_fn vm_region_64 #else #define BASIC_INFO_TYPE vm_region_basic_info_t #define BASIC_INFO VM_REGION_BASIC_INFO #define BASIC_INFO_COUNT VM_REGION_BASIC_INFO_COUNT #define vm_region_query_fn vm_region #endif bool FIRCLSUnwindIsAddressExecutable(vm_address_t address) { #if CLS_COMPACT_UNWINDING_SUPPORTED FIRCLSBinaryImageRuntimeNode unusedNode; return FIRCLSBinaryImageSafeFindImageForAddress(address, &unusedNode); #else return true; #endif } bool FIRCLSUnwindFirstExecutableAddress(vm_address_t start, vm_address_t end, vm_address_t* foundAddress) { // This function walks up the data on the stack, looking for the first value that is an address on // an exectuable page. This is a heurestic, and can hit false positives. *foundAddress = 0; // write in a 0 do { vm_address_t address; FIRCLSSDKLogDebug("Checking address %p => %p\n", (void*)start, (void*)*(uintptr_t*)start); // if start isn't a valid pointer, don't even bother trying if (FIRCLSIsValidPointer(start)) { if (!FIRCLSReadMemory(start, &address, sizeof(void*))) { // if we fail to read from the stack, we're done return false; } FIRCLSSDKLogDebug("Checking for executable %p\n", (void*)address); // when we find an exectuable address, we're finished if (FIRCLSUnwindIsAddressExecutable(address)) { *foundAddress = address; return true; } } start += sizeof(void*); // move back up the stack } while (start < end); return false; } ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include "../Helpers/FIRCLSThreadState.h" #include "../Helpers/FIRCLSUtility.h" #if CLS_COMPACT_UNWINDING_SUPPORTED #include "../Unwind/Compact/FIRCLSCompactUnwind.h" #endif #include #include #include "FIRCLSUnwind_arch.h" extern const uint32_t FIRCLSUnwindMaxFrames; extern const uint32_t FIRCLSUnwindInfiniteRecursionCountThreshold; typedef struct { FIRCLSThreadContext registers; uint32_t frameCount; #if CLS_COMPACT_UNWINDING_SUPPORTED FIRCLSCompactUnwindContext compactUnwindState; #endif uintptr_t lastFramePC; uint32_t repeatCount; } FIRCLSUnwindContext; // API bool FIRCLSUnwindInit(FIRCLSUnwindContext* context, FIRCLSThreadContext threadContext); bool FIRCLSUnwindNextFrame(FIRCLSUnwindContext* context); uintptr_t FIRCLSUnwindGetPC(FIRCLSUnwindContext* context); uintptr_t FIRCLSUnwindGetStackPointer(FIRCLSUnwindContext* context); uint32_t FIRCLSUnwindGetFrameRepeatCount(FIRCLSUnwindContext* context); // utility functions bool FIRCLSUnwindIsAddressExecutable(vm_address_t address); bool FIRCLSUnwindFirstExecutableAddress(vm_address_t start, vm_address_t end, vm_address_t* foundAddress); ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arch.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include "../Helpers/FIRCLSFeatures.h" #include "../Helpers/FIRCLSThreadState.h" #if CLS_COMPACT_UNWINDING_SUPPORTED #include "../Unwind/Compact/FIRCLSCompactUnwind.h" #endif bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext *registers, bool allowScanning); uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr); #if CLS_DWARF_UNWINDING_SUPPORTED uintptr_t FIRCLSCompactUnwindDwarfOffset(compact_unwind_encoding_t encoding); bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext *registers, uint64_t num, uintptr_t value); uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext *registers, uint64_t num); #endif ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arm.c ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "../Helpers/FIRCLSDefines.h" #include "../Helpers/FIRCLSFeatures.h" #include "FIRCLSUnwind.h" #include "FIRCLSUnwind_arch.h" #include "../Helpers/FIRCLSUtility.h" #if CLS_CPU_ARM || CLS_CPU_ARM64 static bool FIRCLSUnwindWithLRRegister(FIRCLSThreadContext* registers) { if (!FIRCLSIsValidPointer(registers)) { return false; } // Return address is in LR, SP is pointing to the next frame. uintptr_t value = FIRCLSThreadContextGetLinkRegister(registers); if (!FIRCLSIsValidPointer(value)) { FIRCLSSDKLog("Error: LR value is invalid\n"); return false; } return FIRCLSThreadContextSetPC(registers, value); } bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext* registers, bool allowScanning) { if (allowScanning) { // The LR register does have the return address here, but there are situations where // this can produce false matches. Better backend rules can fix this up in many cases. if (FIRCLSUnwindWithLRRegister(registers)) { return true; } else { // In this case, we're unable to use the LR. We don't want to just stop unwinding, so // proceed with the normal, non-scanning path FIRCLSSDKLog("Unable to use LR, skipping\n"); } } // read the values from the stack const uintptr_t framePointer = FIRCLSThreadContextGetFramePointer(registers); uintptr_t stack[2]; if (!FIRCLSReadMemory((vm_address_t)framePointer, stack, sizeof(stack))) { // unable to read the first stack frame FIRCLSSDKLog("Error: failed to read memory at address %p\n", (void*)framePointer); return false; } if (!FIRCLSThreadContextSetPC(registers, stack[1])) { return false; } if (!FIRCLSThreadContextSetFramePointer(registers, stack[0])) { return false; } if (!FIRCLSThreadContextSetStackPointer(registers, FIRCLSUnwindStackPointerFromFramePointer(framePointer))) { return false; } return true; } uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr) { // the stack pointer is the frame pointer plus the two saved pointers for the frame return framePtr + 2 * sizeof(void*); } #if CLS_COMPACT_UNWINDING_SUPPORTED bool FIRCLSCompactUnwindComputeRegisters(FIRCLSCompactUnwindContext* context, FIRCLSCompactUnwindResult* result, FIRCLSThreadContext* registers) { if (!context || !result || !registers) { return false; } // Note that compact_uwnind_encoding.h has a few bugs in it prior to iOS 8.0. // Only refer to the >= 8.0 header. switch (result->encoding & UNWIND_ARM64_MODE_MASK) { case UNWIND_ARM64_MODE_FRAMELESS: // Interestingly, we also know the size of the stack frame, by // using UNWIND_ARM64_FRAMELESS_STACK_SIZE_MASK. Is that useful? return FIRCLSUnwindWithLRRegister(registers); break; case UNWIND_ARM64_MODE_DWARF: return FIRCLSCompactUnwindDwarfFrame( context, result->encoding & UNWIND_ARM64_DWARF_SECTION_OFFSET, registers); break; case UNWIND_ARM64_MODE_FRAME: return FIRCLSUnwindWithFramePointer(registers, false); default: FIRCLSSDKLog("Invalid encoding 0x%x\n", result->encoding); break; } return false; } #endif #if CLS_DWARF_UNWINDING_SUPPORTED uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext* registers, uint64_t num) { switch (num) { case CLS_DWARF_ARM64_X0: return registers->__ss.__x[0]; case CLS_DWARF_ARM64_X1: return registers->__ss.__x[1]; case CLS_DWARF_ARM64_X2: return registers->__ss.__x[2]; case CLS_DWARF_ARM64_X3: return registers->__ss.__x[3]; case CLS_DWARF_ARM64_X4: return registers->__ss.__x[4]; case CLS_DWARF_ARM64_X5: return registers->__ss.__x[5]; case CLS_DWARF_ARM64_X6: return registers->__ss.__x[6]; case CLS_DWARF_ARM64_X7: return registers->__ss.__x[7]; case CLS_DWARF_ARM64_X8: return registers->__ss.__x[8]; case CLS_DWARF_ARM64_X9: return registers->__ss.__x[9]; case CLS_DWARF_ARM64_X10: return registers->__ss.__x[10]; case CLS_DWARF_ARM64_X11: return registers->__ss.__x[11]; case CLS_DWARF_ARM64_X12: return registers->__ss.__x[12]; case CLS_DWARF_ARM64_X13: return registers->__ss.__x[13]; case CLS_DWARF_ARM64_X14: return registers->__ss.__x[14]; case CLS_DWARF_ARM64_X15: return registers->__ss.__x[15]; case CLS_DWARF_ARM64_X16: return registers->__ss.__x[16]; case CLS_DWARF_ARM64_X17: return registers->__ss.__x[17]; case CLS_DWARF_ARM64_X18: return registers->__ss.__x[18]; case CLS_DWARF_ARM64_X19: return registers->__ss.__x[19]; case CLS_DWARF_ARM64_X20: return registers->__ss.__x[20]; case CLS_DWARF_ARM64_X21: return registers->__ss.__x[21]; case CLS_DWARF_ARM64_X22: return registers->__ss.__x[22]; case CLS_DWARF_ARM64_X23: return registers->__ss.__x[23]; case CLS_DWARF_ARM64_X24: return registers->__ss.__x[24]; case CLS_DWARF_ARM64_X25: return registers->__ss.__x[25]; case CLS_DWARF_ARM64_X26: return registers->__ss.__x[26]; case CLS_DWARF_ARM64_X27: return registers->__ss.__x[27]; case CLS_DWARF_ARM64_X28: return registers->__ss.__x[28]; case CLS_DWARF_ARM64_FP: return FIRCLSThreadContextGetFramePointer(registers); case CLS_DWARF_ARM64_LR: return FIRCLSThreadContextGetLinkRegister(registers); case CLS_DWARF_ARM64_SP: return FIRCLSThreadContextGetStackPointer(registers); default: break; } FIRCLSSDKLog("Error: Unrecognized get register number %llu\n", num); return 0; } bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext* registers, uint64_t num, uintptr_t value) { switch (num) { case CLS_DWARF_ARM64_X0: registers->__ss.__x[0] = value; return true; case CLS_DWARF_ARM64_X1: registers->__ss.__x[1] = value; return true; case CLS_DWARF_ARM64_X2: registers->__ss.__x[2] = value; return true; case CLS_DWARF_ARM64_X3: registers->__ss.__x[3] = value; return true; case CLS_DWARF_ARM64_X4: registers->__ss.__x[4] = value; return true; case CLS_DWARF_ARM64_X5: registers->__ss.__x[5] = value; return true; case CLS_DWARF_ARM64_X6: registers->__ss.__x[6] = value; return true; case CLS_DWARF_ARM64_X7: registers->__ss.__x[7] = value; return true; case CLS_DWARF_ARM64_X8: registers->__ss.__x[8] = value; return true; case CLS_DWARF_ARM64_X9: registers->__ss.__x[9] = value; return true; case CLS_DWARF_ARM64_X10: registers->__ss.__x[10] = value; return true; case CLS_DWARF_ARM64_X11: registers->__ss.__x[11] = value; return true; case CLS_DWARF_ARM64_X12: registers->__ss.__x[12] = value; return true; case CLS_DWARF_ARM64_X13: registers->__ss.__x[13] = value; return true; case CLS_DWARF_ARM64_X14: registers->__ss.__x[14] = value; return true; case CLS_DWARF_ARM64_X15: registers->__ss.__x[15] = value; return true; case CLS_DWARF_ARM64_X16: registers->__ss.__x[16] = value; return true; case CLS_DWARF_ARM64_X17: registers->__ss.__x[17] = value; return true; case CLS_DWARF_ARM64_X18: registers->__ss.__x[18] = value; return true; case CLS_DWARF_ARM64_X19: registers->__ss.__x[19] = value; return true; case CLS_DWARF_ARM64_X20: registers->__ss.__x[20] = value; return true; case CLS_DWARF_ARM64_X21: registers->__ss.__x[21] = value; return true; case CLS_DWARF_ARM64_X22: registers->__ss.__x[22] = value; return true; case CLS_DWARF_ARM64_X23: registers->__ss.__x[23] = value; return true; case CLS_DWARF_ARM64_X24: registers->__ss.__x[24] = value; return true; case CLS_DWARF_ARM64_X25: registers->__ss.__x[25] = value; return true; case CLS_DWARF_ARM64_X26: registers->__ss.__x[26] = value; return true; case CLS_DWARF_ARM64_X27: registers->__ss.__x[27] = value; return true; case CLS_DWARF_ARM64_X28: registers->__ss.__x[28] = value; return true; case CLS_DWARF_ARM64_FP: FIRCLSThreadContextSetFramePointer(registers, value); return true; case CLS_DWARF_ARM64_SP: FIRCLSThreadContextSetStackPointer(registers, value); return true; case CLS_DWARF_ARM64_LR: // Here's what's going on. For x86, the "return register" is virtual. The architecture // doesn't actually have one, but DWARF does have the concept. So, when the system // tries to set the return register, we set the PC. You can see this behavior // in the FIRCLSDwarfUnwindSetRegisterValue implemenation for that architecture. In the // case of ARM64, the register is real. So, we have to be extra careful to make sure // we update the PC here. Otherwise, when a DWARF unwind completes, it won't have // changed the PC to the right value. FIRCLSThreadContextSetLinkRegister(registers, value); FIRCLSThreadContextSetPC(registers, value); return true; default: break; } FIRCLSSDKLog("Unrecognized set register number %llu\n", num); return false; } #endif #else INJECT_STRIP_SYMBOL(unwind_arm) #endif ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.c ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "FIRCLSUnwind_x86.h" #include "../Helpers/FIRCLSDefines.h" #include "../Helpers/FIRCLSFeatures.h" #include "FIRCLSUnwind.h" #include "FIRCLSUnwind_arch.h" #include "../Helpers/FIRCLSUtility.h" #if CLS_CPU_X86 #include static bool FIRCLSCompactUnwindBPFrame(compact_unwind_encoding_t encoding, FIRCLSThreadContext* registers); static bool FIRCLSCompactUnwindFrameless(compact_unwind_encoding_t encoding, FIRCLSThreadContext* registers, uintptr_t functionStart, bool indirect); #if CLS_COMPACT_UNWINDING_SUPPORTED bool FIRCLSCompactUnwindComputeRegisters(FIRCLSCompactUnwindContext* context, FIRCLSCompactUnwindResult* result, FIRCLSThreadContext* registers) { if (!FIRCLSIsValidPointer(context) || !FIRCLSIsValidPointer(result) || !FIRCLSIsValidPointer(registers)) { FIRCLSSDKLogError("invalid inputs\n"); return false; } FIRCLSSDKLogDebug("Computing registers for encoding %x\n", result->encoding); switch (result->encoding & CLS_X86_MODE_MASK) { case CLS_X86_MODE_BP_FRAME: return FIRCLSCompactUnwindBPFrame(result->encoding, registers); case CLS_X86_MODE_STACK_IMMD: return FIRCLSCompactUnwindFrameless(result->encoding, registers, result->functionStart, false); case CLS_X86_MODE_STACK_IND: return FIRCLSCompactUnwindFrameless(result->encoding, registers, result->functionStart, true); case CLS_X86_MODE_DWARF: return FIRCLSCompactUnwindDwarfFrame(context, result->encoding & CLS_X86_DWARF_SECTION_OFFSET, registers); default: FIRCLSSDKLogError("Invalid encoding %x\n", result->encoding); break; } return false; } #endif static bool FIRCLSCompactUnwindBPFrame(compact_unwind_encoding_t encoding, FIRCLSThreadContext* registers) { // this is the plain-vanilla frame pointer process // uint32_t offset = GET_BITS_WITH_MASK(encoding, UNWIND_X86_EBP_FRAME_OFFSET); // uint32_t locations = GET_BITS_WITH_MASK(encoding, UNWIND_X86_64_RBP_FRAME_REGISTERS); // TODO: pretty sure we do need to restore registers here, so that if a subsequent frame needs // these results, they will be correct // Checkout CompactUnwinder.hpp in libunwind for how to do this. Since we don't make use of any of // those registers for a stacktrace only, there's nothing we need do with them. // read the values from the stack const uintptr_t framePointer = FIRCLSThreadContextGetFramePointer(registers); uintptr_t stack[2]; if (!FIRCLSReadMemory((vm_address_t)framePointer, stack, sizeof(stack))) { // unable to read the first stack frame FIRCLSSDKLog("Error: failed to read memory at address %p\n", (void*)framePointer); return false; } if (!FIRCLSThreadContextSetPC(registers, stack[1])) { return false; } if (!FIRCLSThreadContextSetFramePointer(registers, stack[0])) { return false; } if (!FIRCLSThreadContextSetStackPointer(registers, FIRCLSUnwindStackPointerFromFramePointer(framePointer))) { return false; } return true; } bool FIRCLSUnwindWithStackScanning(FIRCLSThreadContext* registers) { vm_address_t start = (vm_address_t)FIRCLSThreadContextGetStackPointer(registers); vm_address_t end = (vm_address_t)FIRCLSThreadContextGetFramePointer(registers); uintptr_t newPC = 0; if (!FIRCLSUnwindFirstExecutableAddress(start, end, (vm_address_t*)&newPC)) { return false; } return FIRCLSThreadContextSetPC(registers, newPC); } bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext* registers, bool allowScanning) { // Here's an interesting case. We've just processed the first frame, and it did // not have any unwind info. If that first function did not allocate // a stack frame, we'll "skip" the caller. This might sound unlikely, but it actually // happens a lot in practice. // Sooo, one thing we can do is try to stack the stack for things that look like return // addresses. Normally, this technique will hit many false positives. But, if we do it // only for the second frame, and only when we don't have other unwind info available. if (allowScanning) { FIRCLSSDKLogInfo("Attempting stack scan\n"); if (FIRCLSUnwindWithStackScanning(registers)) { FIRCLSSDKLogInfo("Stack scan successful\n"); return true; } } // If we ever do anything else with the encoding, we need to be sure // to set it up right. return FIRCLSCompactUnwindBPFrame(CLS_X86_MODE_BP_FRAME, registers); } uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr) { // the stack pointer is the frame pointer plus the two saved pointers for the frame return framePtr + 2 * sizeof(void*); } #if CLS_COMPACT_UNWINDING_SUPPORTED || CLS_DWARF_UNWINDING_SUPPORTED uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext* registers, uint64_t num) { switch (num) { #if CLS_CPU_X86_64 case CLS_DWARF_X86_64_RAX: return registers->__ss.__rax; case CLS_DWARF_X86_64_RDX: return registers->__ss.__rdx; case CLS_DWARF_X86_64_RCX: return registers->__ss.__rcx; case CLS_DWARF_X86_64_RBX: return registers->__ss.__rbx; case CLS_DWARF_X86_64_RSI: return registers->__ss.__rsi; case CLS_DWARF_X86_64_RDI: return registers->__ss.__rdi; case CLS_DWARF_X86_64_RBP: return registers->__ss.__rbp; case CLS_DWARF_X86_64_RSP: return registers->__ss.__rsp; case CLS_DWARF_X86_64_R8: return registers->__ss.__r8; case CLS_DWARF_X86_64_R9: return registers->__ss.__r9; case CLS_DWARF_X86_64_R10: return registers->__ss.__r10; case CLS_DWARF_X86_64_R11: return registers->__ss.__r11; case CLS_DWARF_X86_64_R12: return registers->__ss.__r12; case CLS_DWARF_X86_64_R13: return registers->__ss.__r13; case CLS_DWARF_X86_64_R14: return registers->__ss.__r14; case CLS_DWARF_X86_64_R15: return registers->__ss.__r15; case CLS_DWARF_X86_64_RET_ADDR: return registers->__ss.__rip; #elif CLS_CPU_I386 case CLS_DWARF_X86_EAX: return registers->__ss.__eax; case CLS_DWARF_X86_ECX: return registers->__ss.__ecx; case CLS_DWARF_X86_EDX: return registers->__ss.__edx; case CLS_DWARF_X86_EBX: return registers->__ss.__ebx; case CLS_DWARF_X86_EBP: return registers->__ss.__ebp; case CLS_DWARF_X86_ESP: return registers->__ss.__esp; case CLS_DWARF_X86_ESI: return registers->__ss.__esi; case CLS_DWARF_X86_EDI: return registers->__ss.__edi; case CLS_DWARF_X86_RET_ADDR: return registers->__ss.__eip; #endif default: break; } FIRCLSSDKLog("Error: Unrecognized get register number %llu\n", num); return 0; } bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext* registers, uint64_t num, uintptr_t value) { switch (num) { #if CLS_CPU_X86_64 case CLS_DWARF_X86_64_RAX: registers->__ss.__rax = value; return true; case CLS_DWARF_X86_64_RDX: registers->__ss.__rdx = value; return true; case CLS_DWARF_X86_64_RCX: registers->__ss.__rcx = value; return true; case CLS_DWARF_X86_64_RBX: registers->__ss.__rbx = value; return true; case CLS_DWARF_X86_64_RSI: registers->__ss.__rsi = value; return true; case CLS_DWARF_X86_64_RDI: registers->__ss.__rdi = value; return true; case CLS_DWARF_X86_64_RBP: registers->__ss.__rbp = value; return true; case CLS_DWARF_X86_64_RSP: registers->__ss.__rsp = value; return true; case CLS_DWARF_X86_64_R8: registers->__ss.__r8 = value; return true; case CLS_DWARF_X86_64_R9: registers->__ss.__r9 = value; return true; case CLS_DWARF_X86_64_R10: registers->__ss.__r10 = value; return true; case CLS_DWARF_X86_64_R11: registers->__ss.__r11 = value; return true; case CLS_DWARF_X86_64_R12: registers->__ss.__r12 = value; return true; case CLS_DWARF_X86_64_R13: registers->__ss.__r13 = value; return true; case CLS_DWARF_X86_64_R14: registers->__ss.__r14 = value; return true; case CLS_DWARF_X86_64_R15: registers->__ss.__r15 = value; return true; case CLS_DWARF_X86_64_RET_ADDR: registers->__ss.__rip = value; return true; #elif CLS_CPU_I386 case CLS_DWARF_X86_EAX: registers->__ss.__eax = value; return true; case CLS_DWARF_X86_ECX: registers->__ss.__ecx = value; return true; case CLS_DWARF_X86_EDX: registers->__ss.__edx = value; return true; case CLS_DWARF_X86_EBX: registers->__ss.__ebx = value; return true; case CLS_DWARF_X86_EBP: registers->__ss.__ebp = value; return true; case CLS_DWARF_X86_ESP: registers->__ss.__esp = value; return true; case CLS_DWARF_X86_ESI: registers->__ss.__esi = value; return true; case CLS_DWARF_X86_EDI: registers->__ss.__edi = value; return true; case CLS_DWARF_X86_RET_ADDR: registers->__ss.__eip = value; return true; #endif default: break; } FIRCLSSDKLog("Unrecognized set register number %llu\n", num); return false; } #endif #if CLS_COMPACT_UNWINDING_SUPPORTED bool FIRCLSCompactUnwindComputeStackSize(const compact_unwind_encoding_t encoding, const uintptr_t functionStart, const bool indirect, uint32_t* const stackSize) { if (!FIRCLSIsValidPointer(stackSize)) { FIRCLSSDKLog("Error: invalid inputs\n"); return false; } const uint32_t stackSizeEncoded = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_SIZE); if (!indirect) { *stackSize = stackSizeEncoded * sizeof(void*); return true; } const vm_address_t sublAddress = functionStart + stackSizeEncoded; uint32_t sublValue = 0; if (!FIRCLSReadMemory(sublAddress, &sublValue, sizeof(uint32_t))) { FIRCLSSDKLog("Error: unable to read subl value\n"); return false; } const uint32_t stackAdjust = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_ADJUST); *stackSize = sublValue + stackAdjust * sizeof(void*); return true; } bool FIRCLSCompactUnwindDecompressPermutation(const compact_unwind_encoding_t encoding, uintptr_t permutatedRegisters[const static 6]) { const uint32_t regCount = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_COUNT); uint32_t permutation = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_PERMUTATION); switch (regCount) { case 6: permutatedRegisters[0] = permutation / 120; permutation -= (permutatedRegisters[0] * 120); permutatedRegisters[1] = permutation / 24; permutation -= (permutatedRegisters[1] * 24); permutatedRegisters[2] = permutation / 6; permutation -= (permutatedRegisters[2] * 6); permutatedRegisters[3] = permutation / 2; permutation -= (permutatedRegisters[3] * 2); permutatedRegisters[4] = permutation; permutatedRegisters[5] = 0; break; case 5: permutatedRegisters[0] = permutation / 120; permutation -= (permutatedRegisters[0] * 120); permutatedRegisters[1] = permutation / 24; permutation -= (permutatedRegisters[1] * 24); permutatedRegisters[2] = permutation / 6; permutation -= (permutatedRegisters[2] * 6); permutatedRegisters[3] = permutation / 2; permutation -= (permutatedRegisters[3] * 2); permutatedRegisters[4] = permutation; break; case 4: permutatedRegisters[0] = permutation / 60; permutation -= (permutatedRegisters[0] * 60); permutatedRegisters[1] = permutation / 12; permutation -= (permutatedRegisters[1] * 12); permutatedRegisters[2] = permutation / 3; permutation -= (permutatedRegisters[2] * 3); permutatedRegisters[3] = permutation; break; case 3: permutatedRegisters[0] = permutation / 20; permutation -= (permutatedRegisters[0] * 20); permutatedRegisters[1] = permutation / 4; permutation -= (permutatedRegisters[1] * 4); permutatedRegisters[2] = permutation; break; case 2: permutatedRegisters[0] = permutation / 5; permutation -= (permutatedRegisters[0] * 5); permutatedRegisters[1] = permutation; break; case 1: permutatedRegisters[0] = permutation; break; case 0: break; default: FIRCLSSDKLog("Error: unhandled number of register permutations for encoding %x\n", encoding); return false; } return true; } bool FIRCLSCompactUnwindRemapRegisters(const compact_unwind_encoding_t encoding, uintptr_t permutatedRegisters[const static 6], uintptr_t savedRegisters[const static 6]) { const uint32_t regCount = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_COUNT); if (regCount > 6) { FIRCLSSDKLog("Error: invalid register number count %d\n", regCount); return false; } // Re-number the registers // You are probably wondering, what the hell is this algorithm even doing? It is // taken from libunwind's implemenation that does the same thing. bool used[7] = {false, false, false, false, false, false, false}; for (uint32_t i = 0; i < regCount; ++i) { int renum = 0; for (int u = 1; u < 7; ++u) { if (!used[u]) { if (renum == permutatedRegisters[i]) { savedRegisters[i] = u; used[u] = true; break; } ++renum; } } } return true; } bool FIRCLSCompactUnwindRestoreRegisters(compact_unwind_encoding_t encoding, FIRCLSThreadContext* registers, uint32_t stackSize, const uintptr_t savedRegisters[const static 6], uintptr_t* address) { if (!FIRCLSIsValidPointer(registers) || !FIRCLSIsValidPointer(address)) { FIRCLSSDKLog("Error: invalid inputs\n"); return false; } const uint32_t regCount = GET_BITS_WITH_MASK(encoding, CLS_X86_FRAMELESS_STACK_REG_COUNT); // compute initial address of saved registers *address = FIRCLSThreadContextGetStackPointer(registers) + stackSize - sizeof(void*) - sizeof(void*) * regCount; uintptr_t value = 0; for (uint32_t i = 0; i < regCount; ++i) { value = 0; switch (savedRegisters[i]) { case CLS_X86_REG_RBP: if (!FIRCLSReadMemory((vm_address_t)*address, (void*)&value, sizeof(uintptr_t))) { FIRCLSSDKLog("Error: unable to read memory to set register\n"); return false; } if (!FIRCLSThreadContextSetFramePointer(registers, value)) { FIRCLSSDKLog("Error: unable to set FP\n"); return false; } break; default: // here, we are restoring a register we don't need for unwinding FIRCLSSDKLog("Error: skipping a restore of register %d at %p\n", (int)savedRegisters[i], (void*)*address); break; } *address += sizeof(void*); } return true; } static bool FIRCLSCompactUnwindFrameless(compact_unwind_encoding_t encoding, FIRCLSThreadContext* registers, uintptr_t functionStart, bool indirect) { FIRCLSSDKLog("Frameless unwind encountered with encoding %x\n", encoding); uint32_t stackSize = 0; if (!FIRCLSCompactUnwindComputeStackSize(encoding, functionStart, indirect, &stackSize)) { FIRCLSSDKLog("Error: unable to compute stack size for encoding %x\n", encoding); return false; } uintptr_t permutatedRegisters[6]; memset(permutatedRegisters, 0, sizeof(permutatedRegisters)); if (!FIRCLSCompactUnwindDecompressPermutation(encoding, permutatedRegisters)) { FIRCLSSDKLog("Error: unable to decompress registers %x\n", encoding); return false; } uintptr_t savedRegisters[6]; memset(savedRegisters, 0, sizeof(savedRegisters)); if (!FIRCLSCompactUnwindRemapRegisters(encoding, permutatedRegisters, savedRegisters)) { FIRCLSSDKLog("Error: unable to remap registers %x\n", encoding); return false; } uintptr_t address = 0; if (!FIRCLSCompactUnwindRestoreRegisters(encoding, registers, stackSize, savedRegisters, &address)) { FIRCLSSDKLog("Error: unable to restore registers\n"); return false; } FIRCLSSDKLog("SP is %p and we are reading %p\n", (void*)FIRCLSThreadContextGetStackPointer(registers), (void*)address); // read the value from the stack, now that we know the address to read uintptr_t value = 0; if (!FIRCLSReadMemory((vm_address_t)address, (void*)&value, sizeof(uintptr_t))) { FIRCLSSDKLog("Error: unable to read memory to set register\n"); return false; } FIRCLSSDKLog("Read PC to be %p\n", (void*)value); if (!FIRCLSIsValidPointer(value)) { FIRCLSSDKLog("Error: computed PC is invalid\n"); return false; } return FIRCLSThreadContextSetPC(registers, value) && FIRCLSThreadContextSetStackPointer(registers, address + sizeof(void*)); } #endif #else INJECT_STRIP_SYMBOL(unwind_x86) #endif ================================================ FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.h ================================================ // Copyright 2019 Google // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #pragma once #include "../Helpers/FIRCLSFeatures.h" // Add some abstraction to compact unwinding, because compact // unwinding is nearly identical between 32 and 64 bit #if CLS_CPU_X86_64 #define CLS_X86_MODE_MASK UNWIND_X86_64_MODE_MASK #define CLS_X86_MODE_BP_FRAME UNWIND_X86_64_MODE_RBP_FRAME #define CLS_X86_MODE_STACK_IMMD UNWIND_X86_64_MODE_STACK_IMMD #define CLS_X86_MODE_STACK_IND UNWIND_X86_64_MODE_STACK_IND #define CLS_X86_MODE_DWARF UNWIND_X86_64_MODE_DWARF #define CLS_X86_BP_FRAME_REGISTERS UNWIND_X86_64_RBP_FRAME_REGISTERS #define CLS_X86_BP_FRAME_OFFSET UNWIND_X86_64_RBP_FRAME_OFFSET #define CLS_X86_FRAMELESS_STACK_SIZE UNWIND_X86_64_FRAMELESS_STACK_SIZE #define CLS_X86_FRAMELESS_STACK_ADJUST UNWIND_X86_64_FRAMELESS_STACK_ADJUST #define CLS_X86_FRAMELESS_STACK_REG_COUNT UNWIND_X86_64_FRAMELESS_STACK_REG_COUNT #define CLS_X86_FRAMELESS_STACK_REG_PERMUTATION UNWIND_X86_64_FRAMELESS_STACK_REG_PERMUTATION #define CLS_X86_DWARF_SECTION_OFFSET UNWIND_X86_64_DWARF_SECTION_OFFSET #define CLS_X86_REG_RBP UNWIND_X86_64_REG_RBP #else #define CLS_X86_MODE_MASK UNWIND_X86_MODE_MASK #define CLS_X86_MODE_BP_FRAME UNWIND_X86_MODE_EBP_FRAME #define CLS_X86_MODE_STACK_IMMD UNWIND_X86_MODE_STACK_IMMD #define CLS_X86_MODE_STACK_IND UNWIND_X86_MODE_STACK_IND #define CLS_X86_MODE_DWARF UNWIND_X86_MODE_DWARF #define CLS_X86_BP_FRAME_REGISTERS UNWIND_X86_RBP_FRAME_REGISTERS #define CLS_X86_BP_FRAME_OFFSET UNWIND_X86_RBP_FRAME_OFFSET #define CLS_X86_FRAMELESS_STACK_SIZE UNWIND_X86_FRAMELESS_STACK_SIZE #define CLS_X86_FRAMELESS_STACK_ADJUST UNWIND_X86_FRAMELESS_STACK_ADJUST #define CLS_X86_FRAMELESS_STACK_REG_COUNT UNWIND_X86_FRAMELESS_STACK_REG_COUNT #define CLS_X86_FRAMELESS_STACK_REG_PERMUTATION UNWIND_X86_FRAMELESS_STACK_REG_PERMUTATION #define CLS_X86_DWARF_SECTION_OFFSET UNWIND_X86_DWARF_SECTION_OFFSET #define CLS_X86_REG_RBP UNWIND_X86_REG_EBP #endif #if CLS_COMPACT_UNWINDING_SUPPORTED bool FIRCLSCompactUnwindComputeStackSize(const compact_unwind_encoding_t encoding, const uintptr_t functionStart, const bool indirect, uint32_t* const stackSize); bool FIRCLSCompactUnwindDecompressPermutation(const compact_unwind_encoding_t encoding, uintptr_t permutatedRegisters[const static 6]); bool FIRCLSCompactUnwindRestoreRegisters(compact_unwind_encoding_t encoding, FIRCLSThreadContext* registers, uint32_t stackSize, const uintptr_t savedRegisters[const static 6], uintptr_t* address); #endif ================================================ FILE: Unwinding/Crashlytics/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additions Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================================================ The following copyright from Hewlett-Packard Development Company, L.P. applies to the dwarf.h file in third_party/libunwind libunwind - a platform-independent unwind library Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P. Contributed by David Mosberger-Tang Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Unwinding/Crashlytics/ProtoSupport/Protos/crashlytics.options ================================================ # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Options for crashlytics.proto google_crashlytics.Report.sdk_version type:FT_POINTER google_crashlytics.Report.gmp_app_id type:FT_POINTER google_crashlytics.Report.installation_uuid type:FT_POINTER google_crashlytics.Report.build_version type:FT_POINTER google_crashlytics.Report.display_version type:FT_POINTER google_crashlytics.FilesPayload.File.filename type:FT_POINTER google_crashlytics.FilesPayload.File.contents type:FT_POINTER google_crashlytics.FilesPayload.files type:FT_POINTER ================================================ FILE: Unwinding/Crashlytics/ProtoSupport/Protos/crashlytics.proto ================================================ // Copyright 2020 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. syntax = "proto3"; package google_crashlytics; enum Platforms { UNKNOWN_PLATFORM = 0; IOS = 1; TVOS = 2; MAC_OS_X = 5; } message Report { // SDK version that generated this session string sdk_version = 1; // GMP App ID string gmp_app_id = 3; // General device type which generated the Event Platforms platform = 4; // Unique device generated guid for application install. string installation_uuid = 5; // App build version. string build_version = 6; // Developer-supplied application version. string display_version = 7; FilesPayload apple_payload = 10; } // Session data represented as a set of log and metadata files. message FilesPayload { message File { string filename = 1; bytes contents = 2; } repeated File files = 1; } ================================================ FILE: Unwinding/Crashlytics/ProtoSupport/generate_crashlytics_protos.sh ================================================ #!/bin/bash # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Example usage: # ./build_protos # Dependencies: git, protobuf, python-protobuf, pyinstaller readonly DIR="$( git rev-parse --show-toplevel )" # Current release of nanopb being used to build the CCT protos readonly NANOPB_VERSION="0.3.9.8" readonly NANOPB_TEMPDIR="${DIR}/Crashlytics/nanopb_temp" readonly LIBRARY_DIR="${DIR}/Crashlytics/Crashlytics/" readonly PROTO_DIR="${DIR}/Crashlytics/ProtoSupport/Protos/" readonly PROTOGEN_DIR="${DIR}/Crashlytics/Protogen/" rm -rf "${NANOPB_TEMPDIR}" echo "Downloading nanopb..." git clone --branch "${NANOPB_VERSION}" https://github.com/nanopb/nanopb.git "${NANOPB_TEMPDIR}" echo "Building nanopb..." pushd "${NANOPB_TEMPDIR}" ./tools/make_mac_package.sh GIT_DESCRIPTION=`git describe --always`-macosx-x86 NANOPB_BIN_DIR="dist/${GIT_DESCRIPTION}" popd echo "Removing existing CCT protos..." rm -rf "${PROTOGEN_DIR}/*" echo "Generating CCT protos..." python "${DIR}/Crashlytics/ProtoSupport/proto_generator.py" \ --nanopb \ --protos_dir="${PROTO_DIR}" \ --pythonpath="${NANOPB_TEMPDIR}/${NANOPB_BIN_DIR}/generator" \ --output_dir="${PROTOGEN_DIR}" \ --include="${PROTO_DIR}" rm -rf "${NANOPB_TEMPDIR}" ================================================ FILE: Unwinding/Crashlytics/ProtoSupport/nanopb_objc_generator.py ================================================ #!/usr/bin/env python # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Generates and massages protocol buffer outputs. """ from __future__ import print_function import sys import io import nanopb_generator as nanopb import os import os.path import shlex from google.protobuf.descriptor_pb2 import FieldDescriptorProto # The plugin_pb2 package loads descriptors on import, but doesn't defend # against multiple imports. Reuse the plugin package as loaded by the # nanopb_generator. plugin_pb2 = nanopb.plugin_pb2 nanopb_pb2 = nanopb.nanopb_pb2 def main(): # Parse request data = io.open(sys.stdin.fileno(), 'rb').read() request = plugin_pb2.CodeGeneratorRequest.FromString(data) # Preprocess inputs, changing types and nanopb defaults options = nanopb_parse_options(request) use_anonymous_oneof(request) use_bytes_for_strings(request) # Generate code parsed_files = nanopb_parse_files(request, options) results = nanopb_generate(request, options, parsed_files) response = nanopb_write(results) # Write to stdout io.open(sys.stdout.fileno(), 'wb').write(response.SerializeToString()) def use_anonymous_oneof(request): """Use anonymous unions for oneofs if they're the only one in a message. Equivalent to setting this option on messages where it applies: option (nanopb).anonymous_oneof = true; Args: request: A CodeGeneratorRequest from protoc. The descriptors are modified in place. """ for _, message_type in iterate_messages(request): if len(message_type.oneof_decl) == 1: ext = message_type.options.Extensions[nanopb_pb2.nanopb_msgopt] ext.anonymous_oneof = True def use_bytes_for_strings(request): """Always use the bytes type instead of string. By default, nanopb renders proto strings as having the C type char* and does not include a separate size field, getting the length of the string via strlen(). Unfortunately this prevents using strings with embedded nulls, which is something the wire format supports. Fortunately, string and bytes proto fields are identical on the wire and nanopb's bytes representation does have an explicit length, so this function changes the types of all string fields to bytes. The generated code will now contain pb_bytes_array_t. There's no nanopb or proto option to control this behavior. The equivalent would be to hand edit all the .proto files :-(. Args: request: A CodeGeneratorRequest from protoc. The descriptors are modified in place. """ for names, message_type in iterate_messages(request): for field in message_type.field: if field.type == FieldDescriptorProto.TYPE_STRING: field.type = FieldDescriptorProto.TYPE_BYTES def iterate_messages(request): """Iterates over all messages in all files in the request. Args: request: A CodeGeneratorRequest passed by protoc. Yields: names: a nanopb.Names object giving a qualified name for the message message_type: a DescriptorProto for the message. """ for fdesc in request.proto_file: for names, message_type in nanopb.iterate_messages(fdesc): yield names, message_type def nanopb_parse_options(request): """Parses nanopb_generator command-line options from the given request. Args: request: A CodeGeneratorRequest passed by protoc. Returns: Nanopb's options object, obtained via optparser. """ # Parse options the same as nanopb_generator.main_plugin() does. args = shlex.split(request.parameter) options, _ = nanopb.optparser.parse_args(args) # Force certain options options.extension = '.nanopb' options.verbose = True # Replicate options setup from nanopb_generator.main_plugin. nanopb.Globals.verbose_options = options.verbose # Google's protoc does not currently indicate the full path of proto files. # Instead always add the main file path to the search dirs, that works for # the common case. options.options_path.append(os.path.dirname(request.file_to_generate[0])) return options def nanopb_parse_files(request, options): """Parses the files in the given request into nanopb ProtoFile objects. Args: request: A CodeGeneratorRequest, as passed by protoc. options: The command-line options from nanopb_parse_options. Returns: A dictionary of filename to nanopb.ProtoFile objects, each one representing the parsed form of a FileDescriptor in the request. """ # Process any include files first, in order to have them # available as dependencies parsed_files = {} for fdesc in request.proto_file: parsed_files[fdesc.name] = nanopb.parse_file(fdesc.name, fdesc, options) return parsed_files def nanopb_generate(request, options, parsed_files): """Generates C sources from the given parsed files. Args: request: A CodeGeneratorRequest, as passed by protoc. options: The command-line options from nanopb_parse_options. parsed_files: A dictionary of filename to nanopb.ProtoFile, as returned by nanopb_parse_files(). Returns: A list of nanopb output dictionaries, each one representing the code generation result for each file to generate. The output dictionaries have the following form: { 'headername': Name of header file, ending in .h, 'headerdata': Contents of the header file, 'sourcename': Name of the source code file, ending in .c, 'sourcedata': Contents of the source code file } """ output = [] for filename in request.file_to_generate: for fdesc in request.proto_file: if fdesc.name == filename: results = nanopb.process_file(filename, fdesc, options, parsed_files) output.append(results) return output def nanopb_write(results): """Translates nanopb output dictionaries to a CodeGeneratorResponse. Args: results: A list of generated source dictionaries, as returned by nanopb_generate(). Returns: A CodeGeneratorResponse describing the result of the code generation process to protoc. """ response = plugin_pb2.CodeGeneratorResponse() for result in results: f = response.file.add() f.name = result['headername'] f.content = result['headerdata'] f = response.file.add() f.name = result['sourcename'] f.content = result['sourcedata'] return response if __name__ == '__main__': main() ================================================ FILE: Unwinding/Crashlytics/ProtoSupport/proto_generator.py ================================================ #! /usr/bin/env python # Copyright 2020 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Generates and massages protocol buffer outputs. Example usage: python Crashlytics/ProtoSupport/build_protos.py \ --nanopb \ --protos_dir=Crashlytics/Classes/Protos/ \ --pythonpath=~/Downloads/nanopb-0.3.9.2-macosx-x86/generator/ \ --output_dir=Crashlytics/Protogen/ """ from __future__ import print_function import sys import argparse import os import os.path import re import subprocess OBJC_GENERATOR='nanopb_objc_generator.py' COPYRIGHT_NOTICE = ''' /* * Copyright 2019 Google * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ '''.lstrip() def main(): parser = argparse.ArgumentParser( description='Generates proto messages.') parser.add_argument( '--nanopb', action='store_true', help='Generates nanopb messages.') parser.add_argument( '--objc', action='store_true', help='Generates Objective-C messages.') parser.add_argument( '--protos_dir', help='Source directory containing .proto files.') parser.add_argument( '--output_dir', '-d', help='Directory to write files; subdirectories will be created.') parser.add_argument( '--protoc', default='protoc', help='Location of the protoc executable') parser.add_argument( '--pythonpath', help='Location of the protoc python library.') parser.add_argument( '--include', '-I', action='append', default=[], help='Adds INCLUDE to the proto path.') args = parser.parse_args() if args.nanopb is None and args.objc is None: parser.print_help() sys.exit(1) if args.protos_dir is None: root_dir = os.path.abspath(os.path.dirname(__file__)) args.protos_dir = os.path.join(root_dir, 'protos') if args.output_dir is None: root_dir = os.path.abspath(os.path.dirname(__file__)) args.output_dir = os.path.join(root_dir, 'protogen-please-supply-an-outputdir') all_proto_files = collect_files(args.protos_dir, '.proto') if args.nanopb: NanopbGenerator(args, all_proto_files).run() proto_files = remove_well_known_protos(all_proto_files) if args.objc: ObjcProtobufGenerator(args, proto_files).run() class NanopbGenerator(object): """Builds and runs the nanopb plugin to protoc.""" def __init__(self, args, proto_files): self.args = args self.proto_files = proto_files def run(self): """Performs the action of the generator.""" nanopb_out = os.path.join(self.args.output_dir, 'nanopb') mkdir(nanopb_out) self.__run_generator(nanopb_out) sources = collect_files(nanopb_out, '.nanopb.h', '.nanopb.c') post_process_files( sources, add_copyright, nanopb_remove_extern_c, nanopb_rename_delete, nanopb_use_module_import ) def __run_generator(self, out_dir): """Invokes protoc using the nanopb plugin.""" cmd = protoc_command(self.args) gen = os.path.join(os.path.dirname(__file__), OBJC_GENERATOR) cmd.append('--plugin=protoc-gen-nanopb=%s' % gen) nanopb_flags = [ '--extension=.nanopb', '--source-extension=.c', '--no-timestamp' ] nanopb_flags.extend(['-I%s' % path for path in self.args.include]) cmd.append('--nanopb_out=%s:%s' % (' '.join(nanopb_flags), out_dir)) cmd.extend(self.proto_files) run_protoc(self.args, cmd) def protoc_command(args): """Composes the initial protoc command-line including its include path.""" cmd = [args.protoc] if args.include is not None: cmd.extend(['-I=%s' % path for path in args.include]) return cmd def run_protoc(args, cmd): """Actually runs the given protoc command. Args: args: The command-line args (including pythonpath) cmd: The command to run expressed as a list of strings """ kwargs = {} if args.pythonpath: env = os.environ.copy() old_path = env.get('PYTHONPATH') env['PYTHONPATH'] = os.path.expanduser(args.pythonpath) if old_path is not None: env['PYTHONPATH'] += os.pathsep + old_path kwargs['env'] = env try: print(subprocess.check_output(cmd, stderr=subprocess.STDOUT, **kwargs)) except subprocess.CalledProcessError as error: print('command failed: ', ' '.join(cmd), '\nerror: ', error.output) def remove_well_known_protos(filenames): """Remove "well-known" protos for objc. On those platforms we get these for free as a part of the protobuf runtime. We only need them for nanopb. Args: filenames: A list of filenames, each naming a .proto file. Returns: The filenames with members of google/protobuf removed. """ return [f for f in filenames if 'protos/google/protobuf/' not in f] def post_process_files(filenames, *processors): for filename in filenames: lines = [] with open(filename, 'r') as fd: lines = fd.readlines() for processor in processors: lines = processor(lines) write_file(filename, lines) def write_file(filename, lines): mkdir(os.path.dirname(filename)) with open(filename, 'w') as fd: fd.write(''.join(lines)) def add_copyright(lines): """Adds a copyright notice to the lines.""" result = [COPYRIGHT_NOTICE, '\n'] result.extend(lines) return result def nanopb_remove_extern_c(lines): """Removes extern "C" directives from nanopb code. Args: lines: A nanobp-generated source file, split into lines. Returns: A list of strings, similar to the input but modified to remove extern "C". """ result = [] state = 'initial' for line in lines: if state == 'initial': if '#ifdef __cplusplus' in line: state = 'in-ifdef' continue result.append(line) elif state == 'in-ifdef': if '#endif' in line: state = 'initial' return result def nanopb_rename_delete(lines): """Renames a delete symbol to delete_. If a proto uses a field named 'delete', nanopb happily uses that in the message definition. Works fine for C; not so much for C++. Args: lines: The lines to fix. Returns: The lines, fixed. """ delete_keyword = re.compile(r'\bdelete\b') return [delete_keyword.sub('delete_', line) for line in lines] def nanopb_use_module_import(lines): """Changes #include to include """ # Don't let Copybara alter these lines. return [line.replace('#include ', '{}include '.format("#")) for line in lines] def strip_trailing_whitespace(lines): """Removes trailing whitespace from the given lines.""" return [line.rstrip() + '\n' for line in lines] def objc_flatten_imports(lines): """Flattens the import statements for compatibility with CocoaPods.""" long_import = re.compile(r'#import ".*/') return [long_import.sub('#import "', line) for line in lines] def objc_strip_extension_registry(lines): """Removes extensionRegistry methods from the classes.""" skip = False result = [] for line in lines: if '+ (GPBExtensionRegistry*)extensionRegistry {' in line: skip = True if not skip: result.append(line) elif line == '}\n': skip = False return result def collect_files(root_dir, *extensions): """Finds files with the given extensions in the root_dir. Args: root_dir: The directory from which to start traversing. *extensions: Filename extensions (including the leading dot) to find. Returns: A list of filenames, all starting with root_dir, that have one of the given extensions. """ result = [] for root, _, files in os.walk(root_dir): for basename in files: for ext in extensions: if basename.endswith(ext): filename = os.path.join(root, basename) result.append(filename) return result def mkdir(dirname): if not os.path.isdir(dirname): os.makedirs(dirname) if __name__ == '__main__': main() ================================================ FILE: Unwinding/Crashlytics/Public/Unwinding.h ================================================ // // Header.h // // // Created by Itay Brenner on 2/6/23. // #ifndef Header_h #define Header_h #endif /* Header_h */ ================================================ FILE: Unwinding/Crashlytics/README.md ================================================ # Firebase Crashlytics SDK ## Development Follow the subsequent instructions to develop, debug, unit test, and integration test FirebaseCrashlytics: ### Prereqs - At least CocoaPods 1.6.0 - Install [cocoapods-generate](https://github.com/square/cocoapods-generate) - For nanopb and GDT: - `brew install protobuf nanopb-generator` - `easy_install protobuf python` ### To Develop - Run `Crashlytics/generate_project.sh` - `open gen/FirebaseCrashlytics/FirebaseCrashlytics.xcworkspace` You're now in an Xcode workspace generate for building, debugging and testing the FirebaseCrashlytics CocoaPod. ### Running Unit Tests Open the generated workspace, choose the FirebaseCrashlytics-Unit-unit scheme and press Command-u. ### Changing crash report uploads (using GDT) #### Update report proto If the crash report proto needs to be updated, follow these instructions: - Update `ProtoSupport/Protos/crashlytics.proto` with the new changes - Depending on the type of fields added/removed, also update `ProtoSupport/Protos/crashlytics.options`. `CALLBACK` type fields in crashlytics.nanopb.c needs to be changed to `POINTER` (through the options file). Known field types that require an entry in crashlytics.options are `strings`, `repeated` and `bytes`. - Run `generate_project.sh` to update the nanopb .c/.h files. ================================================ FILE: Unwinding/Crashlytics/third_party/libunwind/LICENSE ================================================ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Unwinding/Crashlytics/third_party/libunwind/dwarf.h ================================================ /* libunwind - a platform-independent unwind library Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P. Contributed by David Mosberger-Tang This file is part of libunwind. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #pragma once // #define DWARF_EXTENDED_LENGTH_FLAG (0xffffffff) #define DWARF_CIE_ID_CIE_FLAG (0) // Exception Handling Pointer Encoding constants #define DW_EH_PE_VALUE_MASK (0x0F) #define DW_EH_PE_RELATIVE_OFFSET_MASK (0x70) // Register Definitions #define DW_EN_MAX_REGISTER_NUMBER (120) enum { DW_EH_PE_ptr = 0x00, DW_EH_PE_uleb128 = 0x01, DW_EH_PE_udata2 = 0x02, DW_EH_PE_udata4 = 0x03, DW_EH_PE_udata8 = 0x04, DW_EH_PE_signed = 0x08, DW_EH_PE_sleb128 = 0x09, DW_EH_PE_sdata2 = 0x0A, DW_EH_PE_sdata4 = 0x0B, DW_EH_PE_sdata8 = 0x0C, DW_EH_PE_absptr = 0x00, DW_EH_PE_pcrel = 0x10, DW_EH_PE_textrel = 0x20, DW_EH_PE_datarel = 0x30, DW_EH_PE_funcrel = 0x40, DW_EH_PE_aligned = 0x50, DW_EH_PE_indirect = 0x80, DW_EH_PE_omit = 0xFF }; // Unwind Instructions #define DW_CFA_OPCODE_MASK (0xC0) #define DW_CFA_OPERAND_MASK (0x3F) enum { DW_CFA_nop = 0x0, DW_CFA_set_loc = 0x1, DW_CFA_advance_loc1 = 0x2, DW_CFA_advance_loc2 = 0x3, DW_CFA_advance_loc4 = 0x4, DW_CFA_offset_extended = 0x5, DW_CFA_restore_extended = 0x6, DW_CFA_undefined = 0x7, DW_CFA_same_value = 0x8, DW_CFA_register = 0x9, DW_CFA_remember_state = 0xA, DW_CFA_restore_state = 0xB, DW_CFA_def_cfa = 0xC, DW_CFA_def_cfa_register = 0xD, DW_CFA_def_cfa_offset = 0xE, DW_CFA_def_cfa_expression = 0xF, DW_CFA_expression = 0x10, DW_CFA_offset_extended_sf = 0x11, DW_CFA_def_cfa_sf = 0x12, DW_CFA_def_cfa_offset_sf = 0x13, DW_CFA_val_offset = 0x14, DW_CFA_val_offset_sf = 0x15, DW_CFA_val_expression = 0x16, // opcode is in high 2 bits, operand in is lower 6 bits DW_CFA_advance_loc = 0x40, // operand is delta DW_CFA_offset = 0x80, // operand is register DW_CFA_restore = 0xC0, // operand is register // GNU extensions DW_CFA_GNU_window_save = 0x2D, DW_CFA_GNU_args_size = 0x2E, DW_CFA_GNU_negative_offset_extended = 0x2F }; // Expression Instructions enum { DW_OP_addr = 0x03, DW_OP_deref = 0x06, DW_OP_const1u = 0x08, DW_OP_const1s = 0x09, DW_OP_const2u = 0x0A, DW_OP_const2s = 0x0B, DW_OP_const4u = 0x0C, DW_OP_const4s = 0x0D, DW_OP_const8u = 0x0E, DW_OP_const8s = 0x0F, DW_OP_constu = 0x10, DW_OP_consts = 0x11, DW_OP_dup = 0x12, DW_OP_drop = 0x13, DW_OP_over = 0x14, DW_OP_pick = 0x15, DW_OP_swap = 0x16, DW_OP_rot = 0x17, DW_OP_xderef = 0x18, DW_OP_abs = 0x19, DW_OP_and = 0x1A, DW_OP_div = 0x1B, DW_OP_minus = 0x1C, DW_OP_mod = 0x1D, DW_OP_mul = 0x1E, DW_OP_neg = 0x1F, DW_OP_not = 0x20, DW_OP_or = 0x21, DW_OP_plus = 0x22, DW_OP_plus_uconst = 0x23, DW_OP_shl = 0x24, DW_OP_shr = 0x25, DW_OP_shra = 0x26, DW_OP_xor = 0x27, DW_OP_skip = 0x2F, DW_OP_bra = 0x28, DW_OP_eq = 0x29, DW_OP_ge = 0x2A, DW_OP_gt = 0x2B, DW_OP_le = 0x2C, DW_OP_lt = 0x2D, DW_OP_ne = 0x2E, DW_OP_lit0 = 0x30, DW_OP_lit1 = 0x31, DW_OP_lit2 = 0x32, DW_OP_lit3 = 0x33, DW_OP_lit4 = 0x34, DW_OP_lit5 = 0x35, DW_OP_lit6 = 0x36, DW_OP_lit7 = 0x37, DW_OP_lit8 = 0x38, DW_OP_lit9 = 0x39, DW_OP_lit10 = 0x3A, DW_OP_lit11 = 0x3B, DW_OP_lit12 = 0x3C, DW_OP_lit13 = 0x3D, DW_OP_lit14 = 0x3E, DW_OP_lit15 = 0x3F, DW_OP_lit16 = 0x40, DW_OP_lit17 = 0x41, DW_OP_lit18 = 0x42, DW_OP_lit19 = 0x43, DW_OP_lit20 = 0x44, DW_OP_lit21 = 0x45, DW_OP_lit22 = 0x46, DW_OP_lit23 = 0x47, DW_OP_lit24 = 0x48, DW_OP_lit25 = 0x49, DW_OP_lit26 = 0x4A, DW_OP_lit27 = 0x4B, DW_OP_lit28 = 0x4C, DW_OP_lit29 = 0x4D, DW_OP_lit30 = 0x4E, DW_OP_lit31 = 0x4F, DW_OP_reg0 = 0x50, DW_OP_reg1 = 0x51, DW_OP_reg2 = 0x52, DW_OP_reg3 = 0x53, DW_OP_reg4 = 0x54, DW_OP_reg5 = 0x55, DW_OP_reg6 = 0x56, DW_OP_reg7 = 0x57, DW_OP_reg8 = 0x58, DW_OP_reg9 = 0x59, DW_OP_reg10 = 0x5A, DW_OP_reg11 = 0x5B, DW_OP_reg12 = 0x5C, DW_OP_reg13 = 0x5D, DW_OP_reg14 = 0x5E, DW_OP_reg15 = 0x5F, DW_OP_reg16 = 0x60, DW_OP_reg17 = 0x61, DW_OP_reg18 = 0x62, DW_OP_reg19 = 0x63, DW_OP_reg20 = 0x64, DW_OP_reg21 = 0x65, DW_OP_reg22 = 0x66, DW_OP_reg23 = 0x67, DW_OP_reg24 = 0x68, DW_OP_reg25 = 0x69, DW_OP_reg26 = 0x6A, DW_OP_reg27 = 0x6B, DW_OP_reg28 = 0x6C, DW_OP_reg29 = 0x6D, DW_OP_reg30 = 0x6E, DW_OP_reg31 = 0x6F, DW_OP_breg0 = 0x70, DW_OP_breg1 = 0x71, DW_OP_breg2 = 0x72, DW_OP_breg3 = 0x73, DW_OP_breg4 = 0x74, DW_OP_breg5 = 0x75, DW_OP_breg6 = 0x76, DW_OP_breg7 = 0x77, DW_OP_breg8 = 0x78, DW_OP_breg9 = 0x79, DW_OP_breg10 = 0x7A, DW_OP_breg11 = 0x7B, DW_OP_breg12 = 0x7C, DW_OP_breg13 = 0x7D, DW_OP_breg14 = 0x7E, DW_OP_breg15 = 0x7F, DW_OP_breg16 = 0x80, DW_OP_breg17 = 0x81, DW_OP_breg18 = 0x82, DW_OP_breg19 = 0x83, DW_OP_breg20 = 0x84, DW_OP_breg21 = 0x85, DW_OP_breg22 = 0x86, DW_OP_breg23 = 0x87, DW_OP_breg24 = 0x88, DW_OP_breg25 = 0x89, DW_OP_breg26 = 0x8A, DW_OP_breg27 = 0x8B, DW_OP_breg28 = 0x8C, DW_OP_breg29 = 0x8D, DW_OP_breg30 = 0x8E, DW_OP_breg31 = 0x8F, DW_OP_regx = 0x90, DW_OP_fbreg = 0x91, DW_OP_bregx = 0x92, DW_OP_piece = 0x93, DW_OP_deref_size = 0x94, DW_OP_xderef_size = 0x95, DW_OP_nop = 0x96, DW_OP_push_object_addres = 0x97, DW_OP_call2 = 0x98, DW_OP_call4 = 0x99, DW_OP_call_ref = 0x9A, DW_OP_lo_user = 0xE0, DW_OP_APPLE_uninit = 0xF0, DW_OP_hi_user = 0xFF }; ================================================ FILE: Unwinding/FirebaseCrashlytics.podspec ================================================ Pod::Spec.new do |s| s.name = 'FirebaseCrashlytics' s.version = '8.13.0' s.summary = 'Best and lightest-weight crash reporting for mobile, desktop and tvOS.' s.description = 'Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality.' s.homepage = 'https://firebase.google.com/' s.license = { :type => 'Apache', :file => 'Crashlytics/LICENSE' } s.authors = 'Google, Inc.' s.source = { :git => 'https://github.com/firebase/firebase-ios-sdk.git', :tag => 'CocoaPods-' + s.version.to_s } ios_deployment_target = '9.0' osx_deployment_target = '10.12' tvos_deployment_target = '10.0' watchos_deployment_target = '6.0' s.ios.deployment_target = ios_deployment_target s.osx.deployment_target = osx_deployment_target s.tvos.deployment_target = tvos_deployment_target s.watchos.deployment_target = watchos_deployment_target s.cocoapods_version = '>= 1.4.0' s.prefix_header_file = false s.source_files = [ 'Crashlytics/Crashlytics/**/*.{c,h,m,mm}', 'Crashlytics/Protogen/**/*.{c,h,m,mm}', 'Crashlytics/Shared/**/*.{c,h,m,mm}', 'Crashlytics/third_party/**/*.{c,h,m,mm}', 'FirebaseCore/Sources/Private/*.h', 'FirebaseInstallations/Source/Library/Private/*.h', 'Interop/Analytics/Public/*.h', ] s.public_header_files = [ 'Crashlytics/Crashlytics/Public/FirebaseCrashlytics/*.h' ] s.preserve_paths = [ 'Crashlytics/README.md', 'run', 'upload-symbols', ] # Ensure the run script and upload-symbols are callable via # ${PODS_ROOT}/FirebaseCrashlytics/ s.prepare_command = <<-PREPARE_COMMAND_END PREPARE_COMMAND_END s.libraries = 'c++', 'z' s.ios.frameworks = 'Security', 'SystemConfiguration' s.macos.frameworks = 'Security', 'SystemConfiguration' s.osx.frameworks = 'Security', 'SystemConfiguration' s.watchos.frameworks = 'Security' s.ios.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => 'CLS_SDK_NAME="Crashlytics iOS SDK" ' + # For nanopb: 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1', 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"', } s.osx.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => 'CLS_SDK_NAME="Crashlytics Mac SDK" ' + # For nanopb: 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1', 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"', } s.tvos.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => 'CLS_SDK_NAME="Crashlytics tvOS SDK" ' + # For nanopb: 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1', 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"', } s.watchos.pod_target_xcconfig = { 'GCC_C_LANGUAGE_STANDARD' => 'c99', 'GCC_PREPROCESSOR_DEFINITIONS' => 'CLS_SDK_NAME="Crashlytics watchOS SDK" ' + # For nanopb: 'PB_FIELD_32BIT=1 PB_NO_PACKED_STRUCTS=1 PB_ENABLE_MALLOC=1', 'OTHER_LD_FLAGS' => '$(inherited) -sectcreate __TEXT __info_plist', 'HEADER_SEARCH_PATHS' => '"${PODS_TARGET_SRCROOT}"', } s.test_spec 'unit' do |unit_tests| unit_tests.scheme = { :code_coverage => true } # Unit tests can't run on watchOS. unit_tests.platforms = { :ios => ios_deployment_target, :osx => osx_deployment_target, :tvos => tvos_deployment_target } unit_tests.source_files = 'Crashlytics/UnitTests/*.[mh]', 'Crashlytics/UnitTests/*/*.[mh]' unit_tests.resources = 'Crashlytics/UnitTests/Data/*', 'Crashlytics/UnitTests/*.clsrecord', 'Crashlytics/UnitTests/FIRCLSMachO/data/*' end end ================================================ FILE: Unwinding/LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: build.sh ================================================ xcodebuild archive \ -scheme ETTrace \ -archivePath ./ETTrace-iphonesimulator.xcarchive \ -sdk iphonesimulator \ -destination 'generic/platform=iOS Simulator' \ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ INSTALL_PATH='Library/Frameworks' \ SKIP_INSTALL=NO \ CLANG_CXX_LANGUAGE_STANDARD=c++17 xcodebuild archive \ -scheme ETTrace \ -archivePath ./ETTrace-iphoneos.xcarchive \ -sdk iphoneos \ -destination 'generic/platform=iOS' \ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ INSTALL_PATH='Library/Frameworks' \ SKIP_INSTALL=NO \ CLANG_CXX_LANGUAGE_STANDARD=c++17 xcodebuild archive \ -scheme ETTrace \ -archivePath ./ETTrace-tvOSSimulator.xcarchive \ -sdk appletvsimulator \ -destination 'generic/platform=tvOS Simulator' \ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ INSTALL_PATH='Library/Frameworks' \ SKIP_INSTALL=NO \ CLANG_CXX_LANGUAGE_STANDARD=c++17 xcodebuild archive \ -scheme ETTrace \ -archivePath ./ETTrace-visionOSSimulator.xcarchive \ -sdk xrsimulator \ -destination 'generic/platform=visionOS Simulator' \ BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ INSTALL_PATH='Library/Frameworks' \ SKIP_INSTALL=NO \ CLANG_CXX_LANGUAGE_STANDARD=c++17 xcodebuild -create-xcframework \ -framework ./ETTrace-iphonesimulator.xcarchive/Products/Library/Frameworks/ETTrace.framework \ -framework ./ETTrace-iphoneos.xcarchive/Products/Library/Frameworks/ETTrace.framework \ -framework ./ETTrace-visionOSSimulator.xcarchive/Products/Library/Frameworks/ETTrace.framework \ -framework ./ETTrace-tvOSSimulator.xcarchive/Products/Library/Frameworks/ETTrace.framework \ -output ./ETTrace.xcframework ================================================ FILE: build_runner.sh ================================================ xcodebuild archive \ -scheme ETTraceRunner \ -archivePath ./ETTraceRunner.xcarchive \ -sdk macosx \ -destination 'generic/platform=macOS' \ SKIP_INSTALL=NO codesign --entitlements ./ETTrace/ETTraceRunner/ETTraceRunner.entitlements -f -s $SIGNING_IDENTITY ETTraceRunner.xcarchive/Products/usr/local/bin/ETTraceRunner cp ETTraceRunner.xcarchive/Products/usr/local/bin/ETTraceRunner ETTraceRunner