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://swiftpackageindex.com/EmergeTools/ETTrace)
[](https://swiftpackageindex.com/EmergeTools/ETTrace)
[](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)

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

### 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:

## 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
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
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[;\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.