Full Code of EmergeTools/ETTrace for AI

main ea8a0ec37872 cached
81 files
232.8 KB
61.8k tokens
75 symbols
1 requests
Download .txt
Showing preview only (255K chars total). Download the full file or copy to clipboard to get everything.
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 <Foundation/Foundation.h>

// 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 <Foundation/Foundation.h>
#include <stdint.h>

//! 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 <CommunicationFrame/PublicHeader.h>


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: "<root>", 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 <Foundation/Foundation.h>

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 <Peertalk.h>
#import <CommunicationFrame.h>

@interface EMGChannelListener () <PTChannelDelegate>
@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 <Foundation/Foundation.h>
#import <QuartzCore/QuartzCore.h>
#import <Tracer.h>
#import <vector>
#import <mutex>
#import <mach/mach.h>
#import <sys/sysctl.h>
#import <mach-o/arch.h>
#import <sys/utsname.h>
#import "EMGChannelListener.h"
#import <QuartzCore/QuartzCore.h>
#import "PerfAnalysis.h"
#include <map>

NSString *const kEMGSpanStarted = @"EmergeMetricStarted";
NSString *const kEMGSpanEnded = @"EmergeMetricEnded";

@implementation EMGPerfAnalysis

static dispatch_queue_t fileEventsQueue;

static EMGChannelListener *channelListener;
static NSMutableArray <NSDictionary *> *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 <Foundation/Foundation.h>

//! 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 <PerfAnalysis/PublicHeader.h>

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<sockaddr_in>.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<sockaddr_in>.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<Data, Never>?

  private var reportGenerated: Bool = false
  private var reportedGeneratedContinuation: CheckedContinuation<Void, Never>?

  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<Void, Error>) {
      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
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>com.apple.security.temporary-exception.sbpl</key>
	<array>
		<string>(allow network-outbound (literal "/private/var/run/usbmuxd"))</string>
	</array>
</dict>
</plist>


================================================
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<pid_t>.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<pid_t>.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 <Foundation/Foundation.h>
#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<FlamegraphEvent *> *)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..<eventIndex {
                eventTimes[i] = eventTime
            }
            eventTime += timeDiff
        }
        timeDiffs.append(sampleInterval) // Assume last stack was the usual amount of time
        var samples = zip(stacks, timeDiffs).map { (stack, timeDiff) -> Sample in
            let stackSyms: [(String?, String, UInt64?)] = stack.stack.map { address in
              guard let sym = syms[address] else {
                return ("<unknown>", "<unknown>", 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?, "<unattributed>", 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<UInt64>] = [:]
        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>) -> [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: " (<compiler-generated>)$", with: "", options: .regularExpression) // static UIApplicationDelegate.main() (in emergeTest) (<compiler-generated>)
            .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 <QuartzCore/QuartzCore.h>
#import <mach-o/arch.h>
#import <mach/mach.h>
#import <pthread.h>
#import <deque>
#import <iostream>
#import <mutex>
#import <unordered_map>

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<ThreadSummary> EMGStackTraceRecorder::collectThreadSummaries() {
    std::lock_guard<std::mutex> lockGuard(threadsLock);
    
    std::vector<ThreadSummary> summaries;
    for (const auto &[threadId, thread] : threadsMap) {
        std::vector<StackSummary> stackSummaries;
        for (const auto &stack : thread.stacks) {
            std::vector<uintptr_t> 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<std::mutex> 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 <deque>
#import <vector>
#import <unordered_map>
#import <mach/mach.h>
#import <QuartzCore/QuartzCore.h>
#import <iostream>

struct StackSummary {
    CFTimeInterval time;
    std::vector<uintptr_t> stack;
    
    StackSummary(CFTimeInterval time, std::vector<uintptr_t> &stack) : time(time), stack(stack) {
    }
};

struct ThreadSummary {
    thread_t threadId;
    std::string name;
    std::vector<StackSummary> stacks;
    
    ThreadSummary(thread_t threadId, const std::string &name, std::vector<StackSummary> &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<Stack> stacks;
    std::string name;
    
    Thread(thread_t threadId, thread_t mainThreadId);
};

class EMGStackTraceRecorder {
    std::unordered_map<unsigned int, Thread> threadsMap;
    std::mutex threadsLock;
    std::deque<uintptr_t> addressStorage;
    
public:
    void recordStackForAllThreads(bool recordAllThreads, thread_t mainMachThread, thread_t etTraceThread);

    std::vector<ThreadSummary> collectThreadSummaries();
};


================================================
FILE: ETTrace/Tracer/EMGTracer+PrintThreads.m
================================================
//
//  EMGTracer+PrintThreads.m
//  
//
//  Created by Itay Brenner on 15/8/24.
//

#import <Foundation/Foundation.h>
#import <Tracer.h>
@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 <Foundation/Foundation.h>
#import <vector>
#import <mutex>
#import <map>
#import <mach/mach.h>
#import <sys/sysctl.h>
#import <mach-o/arch.h>
#import <sys/utsname.h>
#import <QuartzCore/QuartzCore.h>
#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 <NSString *, NSDictionary<NSString *, id> *> *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 <NSDictionary <NSString *, id> *> *) arrayFromStacks: (const std::vector<StackSummary> &)stacks {
    NSMutableArray <NSDictionary <NSString *, id> *> *threadStacks = [NSMutableArray array];
    for (const auto &cStack : stacks) {
        NSMutableArray <NSNumber *> *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 <Foundation/Foundation.h>
#import <dlfcn.h>
#import <mach/mach_init.h>
#import <mach/task.h>
#import <mach-o/dyld_images.h>
#import <mach-o/dyld.h>
#import <pthread.h>
#import <QuartzCore/QuartzCore.h>

#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 <Foundation/Foundation.h>

#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<thread_t>.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..<count {
      let index = Int(i)
      if let p_thread = pthread_from_mach_thread_np((threads[index])) {
        let thread: thread_t = threads[index]
        let pthread = pthread_from_mach_thread_np(thread)!
        if pthread == pthread_self() {
          // Skip our thread
          continue
        }

        let threadName = getThreadName(p_thread) ?? ""

        withUnsafeMutablePointer(to: &frameCount) { frameCountPtr in
          frames.withUnsafeMutableBufferPointer { framesPtr in
            let firstPtr: UnsafeMutablePointer<UInt64> = 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) } ?? "<unknown>"
          
        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 "<unknown>"
  }

  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..<nsyms {
      let symbolStart = symStart.advanced(by: Int(i) * MemoryLayout<nlist_64>.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<UInt64>,
    maxFrames: UInt64,
    frameCount: UnsafeMutablePointer<UInt64>) {
    FIRCLSWriteThreadStack(thread, frames, maxFrames, frameCount)
  }
#else
  static func getStacktrace(
    forThread thread: thread_t,
    frames: UnsafeMutablePointer<UInt64>,
    maxFrames: UInt64,
    frameCount: UnsafeMutablePointer<UInt64>) {
    FIRCLSWriteThreadStack(thread, frames, maxFrames, frameCount)
  }
#endif
}

@_silgen_name("swift_demangle")
public
func _stdlib_demangleImpl(
  mangledName: UnsafePointer<CChar>?,
  mangledNameLength: UInt,
  outputBuffer: UnsafeMutablePointer<CChar>?,
  outputBufferSize: UnsafeMutablePointer<UInt>?,
  flags: UInt32
  ) -> UnsafeMutablePointer<CChar>?

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<UInt64>, _ framesCapacity: UInt64, _ framesWritten: UnsafeMutablePointer<UInt64>)
#else
@_silgen_name("FIRCLSWriteThreadStack")
func FIRCLSWriteThreadStack(_ thread: thread_t, _ frames: UnsafeMutablePointer<UInt64>, _ framesCapacity: UInt64, _ framesWritten: UnsafeMutablePointer<UInt64>)
#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<mach_header_64>.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..<numberOfCommands {
      let command = pointer.load(as: load_command.self)
      if !callback(command, pointer) {
        break
      }
      pointer = pointer.advanced(by: Int(command.cmdsize))
    }
  }
}


================================================
FILE: LICENSE
================================================
Copyright 2023 Emerge Tools

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: Package.resolved
================================================
{
  "pins" : [
    {
      "identity" : "peertalk",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/EmergeTools/peertalk.git",
      "state" : {
        "revision" : "02b8bebff91cd3d7a5d89c0ddb4bbd873c62879d",
        "version" : "1.0.0"
      }
    },
    {
      "identity" : "swift-argument-parser",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-argument-parser.git",
      "state" : {
        "revision" : "fee6933f37fde9a5e12a1e4aeaa93fe60116ff2a",
        "version" : "1.2.2"
      }
    },
    {
      "identity" : "swifter",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/httpswift/swifter.git",
      "state" : {
        "revision" : "9483a5d459b45c3ffd059f7b55f9638e268632fd",
        "version" : "1.5.0"
      }
    }
  ],
  "version" : 2
}


================================================
FILE: Package.swift
================================================
// swift-tools-version: 5.9
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "ETTrace",
    platforms: [.iOS(.v13), .macOS(.v12), .tvOS(.v13), .visionOS(.v1)],
    products: [
        .library(
            name: "ETTrace",
            type: .dynamic,
            targets: ["ETTrace"]
        ),
        .library(name: "Tracer", targets: ["Tracer"]),
        .library(name: "Symbolicator", targets: ["Symbolicator"]),
        .executable(
            name: "ETTraceRunner",
            targets: ["ETTraceRunner"]
        )
    ],
    dependencies: [
        .package(url: "https://github.com/EmergeTools/peertalk.git", from: "1.0.0"),
        .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.2.0"),
        .package(url: "https://github.com/httpswift/swifter.git", from: "1.5.0")
    ],
    targets: [
        .target(
            name: "ETTrace",
            dependencies: [
                "Tracer",
                "CommunicationFrame",
                .product(name: "Peertalk", package: "peertalk")
            ],
            path: "ETTrace/ETTrace",
            publicHeadersPath: "Public"
        ),
        .target(
          name: "Tracer",
          dependencies: [
              "Unwinding",
              "TracerSwift"
          ],
          path: "ETTrace/Tracer",
          publicHeadersPath: "Public"
        ),
        .target(
          name: "TracerSwift",
          dependencies: [
              "Unwinding",
          ],
          path: "ETTrace/TracerSwift"
        ),
        .target(name: "Symbolicator", dependencies: ["ETModels"], path: "ETTrace/Symbolicator"),
        .target(
            name: "CommunicationFrame",
            path: "ETTrace/CommunicationFrame",
            publicHeadersPath: "Public"
        ),
        .target(
            name: "Unwinding",
            dependencies: [],
            path: "Unwinding/Crashlytics",
            exclude: [
                "LICENSE",
                "README.md"
            ],
            publicHeadersPath: "Public"
        ),
        .executableTarget(
            name: "ETTraceRunner",
            dependencies: [
                "CommunicationFrame",
                "JSONWrapper",
                "ETModels",
                "Symbolicator",
                .product(name: "Peertalk", package: "peertalk"),
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
                .product(name: "Swifter", package: "swifter")
            ],
            path: "ETTrace/ETTraceRunner",
            exclude: [
                "ETTraceRunner.entitlements"
            ]
        ),
        .target(
            name: "JSONWrapper",
            dependencies: [
                "ETModels"
            ],
            path: "ETTrace/JSONWrapper",
            publicHeadersPath: "Public"
        ),
        .target(
            name: "ETModels",
            dependencies: [],
            path: "ETTrace/ETModels"
        ),
    ],
    cxxLanguageStandard: .cxx17
)


================================================
FILE: README.md
================================================
# ETTrace 👽

[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FEmergeTools%2FETTrace%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/EmergeTools/ETTrace)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FEmergeTools%2FETTrace%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/EmergeTools/ETTrace)
[![](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fwww.emergetools.com%2Fapi%2Fv2%2Fpublic_new_build%3FexampleId%3Dettrace.ETTrace%26platform%3Dios%26badgeOption%3Dversion_and_max_install_size%26buildType%3Drelease&query=$.badgeMetadata&label=ETTrace&logo=apple)](https://www.emergetools.com/app/example/ios/ettrace.ETTrace/release?utm_campaign=badge-data)

Locally measure performance of your app, without Xcode or Instruments. Read all about it in the [launch blog post](https://www.emergetools.com/blog/posts/ettrace-reliable-ios-profiling-with-flamecharts)

![Example Flamechart](https://raw.githubusercontent.com/EmergeTools/ETTrace/master/images/example_flamechart.png)

## Building and Installing

ETTrace has two components, the "runner" which is a command line tool, and a framework that your app links to.

First, install the runner with `brew install emergetools/homebrew-tap/ettrace`. Then install the framework using one of the following methods:

### Swift Package Manager

Add this repo as a swift package (either in Xcode or your Package.swift file) using the URL `https://github.com/EmergeTools/ETTrace`. Select "ETTrace" as the package product to add to your app.
> [!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 <mach/mach.h>
#include <stdbool.h>
#include <dispatch/dispatch.h>
#include <objc/message.h>
#include <pthread.h>
#include <sys/sysctl.h>

#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 <dispatch/dispatch.h>

__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 <TargetConditionals.h>

// 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(<MetricKit/MetricKit.h>) && 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 <arm/arch.h>
#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 <dispatch/dispatch.h>

#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 <stdio.h>

#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 <Foundation/Foundation.h>

__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 <mach/arm/thread_status.h>
#include <ptrauth.h>
#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 <stdbool.h>
#include <sys/ucontext.h>

#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 <mach/vm_types.h>
#include <stdbool.h>
#include <stdio.h>
#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 <mach/mach.h>

#include <dlfcn.h>

#include "../Components/FIRCLSGlobals.h"
#include "FIRCLSFeatures.h"

#import <CommonCrypto/CommonHMAC.h>

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 <mach/mach.h>
#include <signal.h>
#include <stdio.h>

// 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<A,R>::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 <mach/vm_types.h>
#include <stdbool.h>

#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 <mach-o/compact_unwind_encoding.h>

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
     
Download .txt
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
Download .txt
SYMBOL INDEX (75 symbols across 14 files)

FILE: ETTrace/CommunicationFrame/Public/CommunicationFrame.h
  type PTStartFrame (line 38) | typedef struct _PTStartFrame {
  type PTMetadataFrame (line 44) | typedef struct _PTMetadataFrame {

FILE: ETTrace/Tracer/EMGStackTraceRecorder.cpp
  function kern_return_t (line 18) | kern_return_t checkMachCall(kern_return_t result) {

FILE: ETTrace/Tracer/EMGStackTraceRecorder.h
  type Stack (line 25) | struct Stack {

FILE: Unwinding/Crashlytics/Crashlytics/Components/FIRCLSEmerge.c
  function FIRCLSWriteThreadStack (line 29) | void FIRCLSWriteThreadStack(thread_t thread, uintptr_t *frames, uint64_t...

FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.c
  function FIRCLSSDKFileLog (line 21) | void FIRCLSSDKFileLog(FIRCLSInternalLogLevel level, const char* format, ...

FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h
  type FIRCLSInternalLogLevel (line 25) | typedef enum {
  type FIRCLSInternalLoggingWritableContext (line 33) | typedef struct {

FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.c
  function FIRCLSThreadContextGetPC (line 77) | uintptr_t FIRCLSThreadContextGetPC(FIRCLSThreadContext* registers) {
  function FIRCLSThreadContextGetStackPointer (line 85) | uintptr_t FIRCLSThreadContextGetStackPointer(const FIRCLSThreadContext* ...
  function FIRCLSThreadContextSetStackPointer (line 93) | bool FIRCLSThreadContextSetStackPointer(FIRCLSThreadContext* registers, ...
  function FIRCLSThreadContextGetLinkRegister (line 103) | uintptr_t FIRCLSThreadContextGetLinkRegister(const FIRCLSThreadContext* ...
  function FIRCLSThreadContextSetLinkRegister (line 111) | bool FIRCLSThreadContextSetLinkRegister(FIRCLSThreadContext* registers, ...
  function FIRCLSThreadContextSetPC (line 121) | bool FIRCLSThreadContextSetPC(FIRCLSThreadContext* registers, uintptr_t ...
  function FIRCLSThreadContextGetFramePointer (line 131) | uintptr_t FIRCLSThreadContextGetFramePointer(const FIRCLSThreadContext* ...
  function FIRCLSThreadContextSetFramePointer (line 139) | bool FIRCLSThreadContextSetFramePointer(FIRCLSThreadContext* registers, ...

FILE: Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h
  type _STRUCT_MCONTEXT (line 35) | typedef _STRUCT_MCONTEXT FIRCLSThreadContext;
  type _STRUCT_UCONTEXT (line 40) | typedef _STRUCT_UCONTEXT _STRUCT_UCONTEXT64;

FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.c
  function FIRCLSUnwindInit (line 44) | bool FIRCLSUnwindInit(FIRCLSUnwindContext* context, FIRCLSThreadContext ...
  function FIRCLSUnwindNextFrame (line 56) | bool FIRCLSUnwindNextFrame(FIRCLSUnwindContext* context) {
  function FIRCLSUnwindGetPC (line 110) | uintptr_t FIRCLSUnwindGetPC(FIRCLSUnwindContext* context) {
  function FIRCLSUnwindGetStackPointer (line 118) | uintptr_t FIRCLSUnwindGetStackPointer(FIRCLSUnwindContext* context) {
  function FIRCLSUnwindGetFramePointer (line 126) | static uintptr_t FIRCLSUnwindGetFramePointer(FIRCLSUnwindContext* contex...
  function FIRCLSUnwindGetFrameRepeatCount (line 134) | uint32_t FIRCLSUnwindGetFrameRepeatCount(FIRCLSUnwindContext* context) {
  function FIRCLSUnwindNextFrameUsingAllStrategies (line 143) | static bool FIRCLSUnwindNextFrameUsingAllStrategies(FIRCLSUnwindContext*...
  function FIRCLSUnwindWithCompactUnwindInfo (line 219) | static bool FIRCLSUnwindWithCompactUnwindInfo(FIRCLSUnwindContext* conte...
  function FIRCLSUnwindContextHasValidPCAndSP (line 258) | bool FIRCLSUnwindContextHasValidPCAndSP(FIRCLSUnwindContext* context) {
  function FIRCLSUnwindIsAddressExecutable (line 274) | bool FIRCLSUnwindIsAddressExecutable(vm_address_t address) {
  function FIRCLSUnwindFirstExecutableAddress (line 284) | bool FIRCLSUnwindFirstExecutableAddress(vm_address_t start,

FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h
  type FIRCLSUnwindContext (line 31) | typedef struct {

FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arm.c
  function FIRCLSUnwindWithLRRegister (line 23) | static bool FIRCLSUnwindWithLRRegister(FIRCLSThreadContext* registers) {
  function FIRCLSUnwindWithFramePointer (line 39) | bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext* registers, bool a...
  function FIRCLSUnwindStackPointerFromFramePointer (line 78) | uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr) {
  function FIRCLSCompactUnwindComputeRegisters (line 84) | bool FIRCLSCompactUnwindComputeRegisters(FIRCLSCompactUnwindContext* con...
  function FIRCLSDwarfUnwindGetRegisterValue (line 115) | uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext* r...
  function FIRCLSDwarfUnwindSetRegisterValue (line 190) | bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext* registers,

FILE: Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.c
  function FIRCLSCompactUnwindComputeRegisters (line 33) | bool FIRCLSCompactUnwindComputeRegisters(FIRCLSCompactUnwindContext* con...
  function FIRCLSCompactUnwindBPFrame (line 64) | static bool FIRCLSCompactUnwindBPFrame(compact_unwind_encoding_t encoding,
  function FIRCLSUnwindWithStackScanning (line 103) | bool FIRCLSUnwindWithStackScanning(FIRCLSThreadContext* registers) {
  function FIRCLSUnwindWithFramePointer (line 116) | bool FIRCLSUnwindWithFramePointer(FIRCLSThreadContext* registers, bool a...
  function FIRCLSUnwindStackPointerFromFramePointer (line 139) | uintptr_t FIRCLSUnwindStackPointerFromFramePointer(uintptr_t framePtr) {
  function FIRCLSDwarfUnwindGetRegisterValue (line 145) | uintptr_t FIRCLSDwarfUnwindGetRegisterValue(const FIRCLSThreadContext* r...
  function FIRCLSDwarfUnwindSetRegisterValue (line 211) | bool FIRCLSDwarfUnwindSetRegisterValue(FIRCLSThreadContext* registers,
  function FIRCLSCompactUnwindComputeStackSize (line 307) | bool FIRCLSCompactUnwindComputeStackSize(const compact_unwind_encoding_t...
  function FIRCLSCompactUnwindDecompressPermutation (line 338) | bool FIRCLSCompactUnwindDecompressPermutation(const compact_unwind_encod...
  function FIRCLSCompactUnwindRemapRegisters (line 401) | bool FIRCLSCompactUnwindRemapRegisters(const compact_unwind_encoding_t e...
  function FIRCLSCompactUnwindRestoreRegisters (line 433) | bool FIRCLSCompactUnwindRestoreRegisters(compact_unwind_encoding_t encod...
  function FIRCLSCompactUnwindFrameless (line 478) | static bool FIRCLSCompactUnwindFrameless(compact_unwind_encoding_t encod...

FILE: Unwinding/Crashlytics/ProtoSupport/nanopb_objc_generator.py
  function main (line 39) | def main():
  function use_anonymous_oneof (line 58) | def use_anonymous_oneof(request):
  function use_bytes_for_strings (line 75) | def use_bytes_for_strings(request):
  function iterate_messages (line 101) | def iterate_messages(request):
  function nanopb_parse_options (line 116) | def nanopb_parse_options(request):
  function nanopb_parse_files (line 143) | def nanopb_parse_files(request, options):
  function nanopb_generate (line 163) | def nanopb_generate(request, options, parsed_files):
  function nanopb_write (line 195) | def nanopb_write(results):

FILE: Unwinding/Crashlytics/ProtoSupport/proto_generator.py
  function main (line 59) | def main():
  class NanopbGenerator (line 108) | class NanopbGenerator(object):
    method __init__ (line 111) | def __init__(self, args, proto_files):
    method run (line 115) | def run(self):
    method __run_generator (line 132) | def __run_generator(self, out_dir):
  function protoc_command (line 151) | def protoc_command(args):
  function run_protoc (line 159) | def run_protoc(args, cmd):
  function remove_well_known_protos (line 182) | def remove_well_known_protos(filenames):
  function post_process_files (line 198) | def post_process_files(filenames, *processors):
  function write_file (line 210) | def write_file(filename, lines):
  function add_copyright (line 216) | def add_copyright(lines):
  function nanopb_remove_extern_c (line 223) | def nanopb_remove_extern_c(lines):
  function nanopb_rename_delete (line 248) | def nanopb_rename_delete(lines):
  function nanopb_use_module_import (line 264) | def nanopb_use_module_import(lines):
  function strip_trailing_whitespace (line 269) | def strip_trailing_whitespace(lines):
  function objc_flatten_imports (line 274) | def objc_flatten_imports(lines):
  function objc_strip_extension_registry (line 281) | def objc_strip_extension_registry(lines):
  function collect_files (line 296) | def collect_files(root_dir, *extensions):
  function mkdir (line 317) | def mkdir(dirname):
Condensed preview — 81 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (253K chars).
[
  {
    "path": ".github/workflows/build.yml",
    "chars": 1745,
    "preview": "name: Pull Request Build\n\non:\n  pull_request:\n    branches:\n      - main\n\njobs:\n  build:\n    runs-on: macos-15\n\n    step"
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 1144,
    "preview": "name: Release Workflow\n\non:\n  push:\n    tags:\n      - 'v*'\n\njobs:\n  release:\n    runs-on: macos-15\n\n    steps:\n      - n"
  },
  {
    "path": ".gitignore",
    "chars": 237,
    "preview": ".DS_Store\n**/*.xcodeproj/xcuserdata/\n**/*.xcworkspace/xcuserdata/\n**/*.xcodeproj/xcshareddata/\nETTrace-iphonesimulator.x"
  },
  {
    "path": ".spi.yml",
    "chars": 115,
    "preview": "version: 1\nbuilder:\n  configs:\n  - platform: ios\n    scheme: ETTrace\n  - platform: macos\n    scheme: ETTraceRunner\n"
  },
  {
    "path": "ETTrace/.gitignore",
    "chars": 5,
    "preview": "Pods/"
  },
  {
    "path": "ETTrace/CommunicationFrame/EMGDummyEmptyClass.h",
    "chars": 281,
    "preview": "//\n//  DummyEmptyClass.h\n//  \n//\n//  Created by Itay Brenner on 2/6/23.\n//\n\n#import <Foundation/Foundation.h>\n\n// This c"
  },
  {
    "path": "ETTrace/CommunicationFrame/EMGDummyEmptyClass.m",
    "chars": 224,
    "preview": "//\n//  DummyEmptyClass.m\n//  \n//\n//  Created by Itay Brenner on 2/6/23.\n//\n\n#import \"EMGDummyEmptyClass.h\"\n\n// This clas"
  },
  {
    "path": "ETTrace/CommunicationFrame/Public/CommunicationFrame.h",
    "chars": 1180,
    "preview": "//\n//  CommunicationFrame.h\n//  CommunicationFrame\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\n#import <Foundation/Fou"
  },
  {
    "path": "ETTrace/ETModels/FlameNode.swift",
    "chars": 2444,
    "preview": "//\n//  FlameNode.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 7/3/23.\n//\n\nimport Foundation\n\n@objc\npub"
  },
  {
    "path": "ETTrace/ETModels/Flamegraph.swift",
    "chars": 887,
    "preview": "//\n//  Flamegraph.swift\n//  \n//\n//  Created by Itay Brenner on 27/6/23.\n//\n\nimport Foundation\n\n@objc\npublic class Flameg"
  },
  {
    "path": "ETTrace/ETModels/FlamegraphEvent.swift",
    "chars": 408,
    "preview": "//\n//  File.swift\n//  \n//\n//  Created by Itay Brenner on 28/6/23.\n//\n\nimport Foundation\n\n@objc\npublic class FlamegraphEv"
  },
  {
    "path": "ETTrace/ETModels/Sample.swift",
    "chars": 660,
    "preview": "//\n//  Sample.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 7/3/23.\n//\n\nimport Foundation\n\npublic class"
  },
  {
    "path": "ETTrace/ETModels/ThreadNode.swift",
    "chars": 385,
    "preview": "//\n//  ThreadNode.swift\n//  \n//\n//  Created by Itay Brenner on 18/8/23.\n//\n\nimport Foundation\n\n@objc\npublic class Thread"
  },
  {
    "path": "ETTrace/ETTrace/EMGChannelListener.h",
    "chars": 278,
    "preview": "//\n//  EMGChannelListener.h\n//  PerfAnalysis\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\n#import <Foundation/Foundatio"
  },
  {
    "path": "ETTrace/ETTrace/EMGChannelListener.m",
    "chars": 4959,
    "preview": "//\n//  EMGChannelListener.m\n//  PerfAnalysis\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\n#import \"EMGChannelListener.h"
  },
  {
    "path": "ETTrace/ETTrace/EMGPerfAnalysis.mm",
    "chars": 5547,
    "preview": "//\n//  Constructor.m\n//  PerfAnalysis\n//\n//  Created by Noah Martin on 11/23/22.\n//\n\n#import <Foundation/Foundation.h>\n#"
  },
  {
    "path": "ETTrace/ETTrace/EMGPerfAnalysis_Private.h",
    "chars": 471,
    "preview": "//\n//  EMGPerfAnalysis_Private.h\n//  PerfAnalysis\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\n#ifndef EMGPerfAnalysis_"
  },
  {
    "path": "ETTrace/ETTrace/Public/PerfAnalysis.h",
    "chars": 593,
    "preview": "//\n//  PerfAnalysis.h\n//  PerfAnalysis\n//\n//  Created by Noah Martin on 12/9/22.\n//\n\n#import <Foundation/Foundation.h>\n\n"
  },
  {
    "path": "ETTrace/ETTraceRunner/ConnectivityHelper.swift",
    "chars": 756,
    "preview": "//\n//  ConnectivityHelper.swift\n//  ETTrace\n//\n//  Created by Noah Martin on 2/27/25.\n//\n\nimport Foundation\n\nfunc isPort"
  },
  {
    "path": "ETTrace/ETTraceRunner/Devices/CommunicationChannel.swift",
    "chars": 3055,
    "preview": "//\n//  CommunicationChannel.swift\n//  ETTraceRunner\n//\n//  Created by Noah Martin on 4/12/23.\n//\n\nimport Foundation\nimpo"
  },
  {
    "path": "ETTrace/ETTraceRunner/Devices/DeviceManager.swift",
    "chars": 2510,
    "preview": "//\n//  DeviceManager.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\nimport Foundation\nimport"
  },
  {
    "path": "ETTrace/ETTraceRunner/Devices/PhysicalDeviceManager.swift",
    "chars": 2392,
    "preview": "//\n//  PhysicalDeviceManager.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\nimport Foundatio"
  },
  {
    "path": "ETTrace/ETTraceRunner/Devices/SimulatorDeviceManager.swift",
    "chars": 953,
    "preview": "//\n//  SimulatorDeviceManager.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\nimport Foundati"
  },
  {
    "path": "ETTrace/ETTraceRunner/ETTrace.swift",
    "chars": 1573,
    "preview": "//\n//  ETTrace.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\nimport Foundation\nimport Argum"
  },
  {
    "path": "ETTrace/ETTraceRunner/ETTraceRunner.entitlements",
    "chars": 344,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "ETTrace/ETTraceRunner/ProcessSelector.swift",
    "chars": 1774,
    "preview": "//\n//  ProcessSelector.swift\n//  ETTrace\n//\n//  Created by Noah Martin on 2/27/25.\n//\n\nimport Foundation\n\nstruct Running"
  },
  {
    "path": "ETTrace/ETTraceRunner/ResponseModels/ResponseModel.swift",
    "chars": 620,
    "preview": "//\n//  ResponseModel.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 7/3/23.\n//\n\nimport Foundation\nimport"
  },
  {
    "path": "ETTrace/ETTraceRunner/RunnerHelper.swift",
    "chars": 8234,
    "preview": "//\n//  RunnerHelper.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 8/3/23.\n//\n\nimport AppKit\nimport Foun"
  },
  {
    "path": "ETTrace/ETTraceRunner/main.swift",
    "chars": 84,
    "preview": "//\n//  main.swift\n//  \n//\n//  Created by Itay Brenner on 6/6/23.\n//\n\nETTrace.main()\n"
  },
  {
    "path": "ETTrace/JSONWrapper/JSONWrapper.m",
    "chars": 2263,
    "preview": "//\n//  TestClass.m\n//  ETTraceRunner\n//\n//  Created by Noah Martin on 4/13/23.\n//\n\n#import <Foundation/Foundation.h>\n#im"
  },
  {
    "path": "ETTrace/JSONWrapper/Public/JSONWrapper.h",
    "chars": 360,
    "preview": "//\n//  TestClass.h\n//  ETTrace\n//\n//  Created by Noah Martin on 4/13/23.\n//\n\n#ifndef TestClass_h\n#define TestClass_h\n\n@i"
  },
  {
    "path": "ETTrace/Symbolicator/FlamegraphGenerator.swift",
    "chars": 3451,
    "preview": "//\n//  FlamegraphGenerator.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 7/3/23.\n//\n\nimport Foundation\n"
  },
  {
    "path": "ETTrace/Symbolicator/Models.swift",
    "chars": 522,
    "preview": "//\n//  Models.swift\n//\n//\n//  Created by Noah Martin on 11/7/23.\n//\n\nimport Foundation\n\npublic struct Event: Decodable {"
  },
  {
    "path": "ETTrace/Symbolicator/Symbolicator.swift",
    "chars": 11159,
    "preview": "//\n//  Symbolicator.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 7/3/23.\n//\n\nimport Foundation\nimport "
  },
  {
    "path": "ETTrace/Symbolicator/Utils.swift",
    "chars": 1488,
    "preview": "//\n//  Utils.swift\n//  PerfAnalysisRunner\n//\n//  Created by Itay Brenner on 6/3/23.\n//\n\nimport Foundation\nimport AppKit\n"
  },
  {
    "path": "ETTrace/Tracer/EMGStackTraceRecorder.cpp",
    "chars": 3730,
    "preview": "#include \"EMGStackTraceRecorder.h\"\n\n#import <QuartzCore/QuartzCore.h>\n#import <mach-o/arch.h>\n#import <mach/mach.h>\n#imp"
  },
  {
    "path": "ETTrace/Tracer/EMGStackTraceRecorder.h",
    "chars": 1372,
    "preview": "#import <deque>\n#import <vector>\n#import <unordered_map>\n#import <mach/mach.h>\n#import <QuartzCore/QuartzCore.h>\n#import"
  },
  {
    "path": "ETTrace/Tracer/EMGTracer+PrintThreads.m",
    "chars": 262,
    "preview": "//\n//  EMGTracer+PrintThreads.m\n//  \n//\n//  Created by Itay Brenner on 15/8/24.\n//\n\n#import <Foundation/Foundation.h>\n#i"
  },
  {
    "path": "ETTrace/Tracer/EMGTracer.mm",
    "chars": 5265,
    "preview": "//\n//  Tracer.m\n//  \n//\n//  Created by Noah Martin on 10/27/23.\n//\n\n#import \"Tracer.h\"\n#import <Foundation/Foundation.h>"
  },
  {
    "path": "ETTrace/Tracer/EMGWriteLibraries.m",
    "chars": 2991,
    "preview": "//\n//  EMGWriteLibraries.m\n//  PerfAnalysis\n//\n//  Created by Noah Martin on 12/9/22.\n//\n\n#import <Foundation/Foundation"
  },
  {
    "path": "ETTrace/Tracer/Public/Tracer.h",
    "chars": 745,
    "preview": "//\n//  EMGWriteLibraries.h\n//  PerfAnalysis\n//\n//  Created by Noah Martin on 12/9/22.\n//\n\n#ifndef EMGWriteLibraries_h\n#d"
  },
  {
    "path": "ETTrace/TracerSwift/ThreadHelper.swift",
    "chars": 8054,
    "preview": "//\n//  ThreadHelper.swift\n//  Tracer\n//\n//  Created by Itay Brenner on 23/7/24.\n//\n\nimport Foundation\nimport Darwin\nimpo"
  },
  {
    "path": "ETTrace/TracerSwift/UnsafeRawPointer+Commands.swift",
    "chars": 1030,
    "preview": "//\n//  UnsafeRawPointer+Commands.swift\n//  Tracer\n//\n//  Created by Itay Brenner on 15/8/24.\n//\n\nimport Foundation\nimpor"
  },
  {
    "path": "LICENSE",
    "chars": 1052,
    "preview": "Copyright 2023 Emerge Tools\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this softwa"
  },
  {
    "path": "Package.resolved",
    "chars": 854,
    "preview": "{\n  \"pins\" : [\n    {\n      \"identity\" : \"peertalk\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://gi"
  },
  {
    "path": "Package.swift",
    "chars": 3103,
    "preview": "// swift-tools-version: 5.9\n// The swift-tools-version declares the minimum version of Swift required to build this pack"
  },
  {
    "path": "README.md",
    "chars": 5641,
    "preview": "# ETTrace 👽\n\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2FEmergeTools"
  },
  {
    "path": "Unwinding/.gitignore",
    "chars": 4157,
    "preview": "FirebaseAuth/Tests/Sample/Sample/Application.plist\nFirebaseAuth/Tests/Sample/Sample/AuthCredentials.h\nFirebaseAuth/Tests"
  },
  {
    "path": "Unwinding/Crashlytics/CHANGELOG.md",
    "chars": 6951,
    "preview": "# Unreleased\n- [fixed] Fixed an issue where passing nil as a value for a custom key or user ID did not clear the stored "
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Components/FIRCLSEmerge.c",
    "chars": 2507,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Components/FIRCLSGlobals.h",
    "chars": 690,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSDefines.h",
    "chars": 2389,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSFeatures.h",
    "chars": 1271,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.c",
    "chars": 871,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSInternalLogging.h",
    "chars": 2182,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.h",
    "chars": 829,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSLogger.m",
    "chars": 1149,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.c",
    "chars": 5097,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSThreadState.h",
    "chars": 2243,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.h",
    "chars": 1557,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Helpers/FIRCLSUtility.m",
    "chars": 4528,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.c",
    "chars": 10519,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind.h",
    "chars": 1829,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arch.h",
    "chars": 1332,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_arm.c",
    "chars": 10223,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.c",
    "chars": 18523,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/Crashlytics/Unwind/FIRCLSUnwind_x86.h",
    "chars": 3349,
    "preview": "// Copyright 2019 Google\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/LICENSE",
    "chars": 12775,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "Unwinding/Crashlytics/ProtoSupport/Protos/crashlytics.options",
    "chars": 1070,
    "preview": "# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this f"
  },
  {
    "path": "Unwinding/Crashlytics/ProtoSupport/Protos/crashlytics.proto",
    "chars": 1375,
    "preview": "// Copyright 2020 Google LLC\n//\n// Licensed under the Apache License, Version 2.0 (the \"License\");\n// you may not use th"
  },
  {
    "path": "Unwinding/Crashlytics/ProtoSupport/generate_crashlytics_protos.sh",
    "chars": 1774,
    "preview": "#!/bin/bash\n\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may n"
  },
  {
    "path": "Unwinding/Crashlytics/ProtoSupport/nanopb_objc_generator.py",
    "chars": 6894,
    "preview": "#!/usr/bin/env python\n\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n#"
  },
  {
    "path": "Unwinding/Crashlytics/ProtoSupport/proto_generator.py",
    "chars": 8872,
    "preview": "#! /usr/bin/env python\n\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n"
  },
  {
    "path": "Unwinding/Crashlytics/Public/Unwinding.h",
    "chars": 125,
    "preview": "//\n//  Header.h\n//  \n//\n//  Created by Itay Brenner on 2/6/23.\n//\n\n#ifndef Header_h\n#define Header_h\n\n\n#endif /* Header_"
  },
  {
    "path": "Unwinding/Crashlytics/README.md",
    "chars": 1331,
    "preview": "# Firebase Crashlytics SDK\n\n## Development\n\nFollow the subsequent instructions to develop, debug, unit test, and\nintegra"
  },
  {
    "path": "Unwinding/Crashlytics/third_party/libunwind/LICENSE",
    "chars": 1023,
    "preview": "Permission is hereby granted, free of charge, to any person obtaining\na copy of this software and associated documentati"
  },
  {
    "path": "Unwinding/Crashlytics/third_party/libunwind/dwarf.h",
    "chars": 6537,
    "preview": "/* libunwind - a platform-independent unwind library\n   Copyright (c) 2003-2005 Hewlett-Packard Development Company, L.P"
  },
  {
    "path": "Unwinding/FirebaseCrashlytics.podspec",
    "chars": 3873,
    "preview": "Pod::Spec.new do |s|\n  s.name             = 'FirebaseCrashlytics'\n  s.version          = '8.13.0'\n  s.summary          ="
  },
  {
    "path": "Unwinding/LICENSE",
    "chars": 11358,
    "preview": "\n                                 Apache License\n                           Version 2.0, January 2004\n                  "
  },
  {
    "path": "build.sh",
    "chars": 1599,
    "preview": "xcodebuild archive \\\n -scheme ETTrace \\\n -archivePath ./ETTrace-iphonesimulator.xcarchive \\\n -sdk iphonesimulator \\\n -de"
  },
  {
    "path": "build_runner.sh",
    "chars": 400,
    "preview": "xcodebuild archive \\\n -scheme ETTraceRunner \\\n -archivePath ./ETTraceRunner.xcarchive \\\n -sdk macosx \\\n -destination 'ge"
  }
]

About this extraction

This page contains the full source code of the EmergeTools/ETTrace GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 81 files (232.8 KB), approximately 61.8k tokens, and a symbol index with 75 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!