Skip to content

everettjf/AppleTrace

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

44 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

AppleTrace 🍎

GitHub Stars GitHub Forks License Last Commit Contributors Platform

A lightweight, embeddable Objective-C tracer that produces shareable Perfetto traces

English | δΈ­ζ–‡

πŸš€ Maintained as needed. AppleTrace captures your app's execution timeline β€” manual sections and/or every objc_msgSend β€” and renders it in Perfetto, right in the browser. With the arrival of the AI era, there is even more that can be explored and improved when the need arises.

AppleTrace Demo


Table of Contents


🎯 What is AppleTrace?

AppleTrace is an iOS/macOS tracing toolkit. You instrument your app β€” either by adding manual APTBeginSection / APTEndSection markers, or by hooking every objc_msgSend automatically β€” and AppleTrace records a timeline of events into sandbox trace fragments. A small Python pipeline merges those fragments into a single trace.json that you open directly in Perfetto to explore the call timeline, durations, threads, and counters.

Demo Preview

The trace visualization shows the method execution timeline and call relationships.


✨ Key Features

  • πŸ“Š Automatic method tracing β€” direct objc_msgSend / objc_msgSendSuper2 rebinding on arm64 captures Objective-C activity with no source changes.
  • 🎯 Manual sections β€” APTBeginSection / APTEndSection (and the APTBegin / APTEnd / APTScopeSection helpers) mark exactly the regions you care about β€” the lowest-risk option, works on every OS version.
  • πŸ“ˆ Rich event types β€” instant markers (APTInstant), counter series (APTCounter for memory, FPS, queue depth, …), and async/flow events (APTAsyncBegin / APTAsyncEnd) that cross threads and dispatch queues.
  • ⚑ Built for the hot path β€” (Class, SEL) name interning, a zero-allocation per-thread call stack, and per-thread batched writing keep malloc / snprintf / dispatch off the per-message path. An opt-in binary fragment format (APPLETRACE_BINARY=1) keeps string formatting off it entirely.
  • 🧡 Thread names β€” Perfetto shows real thread names instead of bare ids.
  • πŸ” Runtime filtering β€” scope automatic tracing with class-prefix allow/deny lists (APPLETRACE_TRACE_CLASS_ALLOW / APPLETRACE_TRACE_CLASS_DENY).
  • 🌐 Perfetto-first β€” open trace.json at ui.perfetto.dev; nothing to install, runs in the browser, scales to large traces. Begin/end pairs export as X complete events by default to halve trace size.
  • 🐍 Python 3 tooling β€” a unified CLI (scripts/appletrace_cli.py), streaming merge for large captures, automated tests, and GitHub Actions CI.

Use Cases

  • πŸ” Performance analysis β€” find hotspots and long methods on a real timeline.
  • πŸ› Debugging β€” follow method execution flow across threads.
  • πŸ“š Learning β€” see how iOS/macOS frameworks actually dispatch.
  • πŸ›‘οΈ Security research β€” analyze third-party app behavior.

⚑ Quick Start

# 1. Prerequisites (macOS)
brew install python git ldid          # ldid is only needed for re-signing loader builds

# 2. Clone
git clone https://github.com/everettjf/AppleTrace.git
cd AppleTrace

# 3. Optional: Python tooling for merging/tests
python3 -m pip install -r requirements.txt

Mode A β€” Manual Instrumentation (recommended baseline)

#import <appletrace/appletrace.h>

- (void)yourMethod {
    APTBegin;            // section named "[ClassName yourMethod]"
    // ... your code ...
    APTEnd;
}

Mode B β€” Automatic objc_msgSend Hook (arm64)

// From your app, after launch:
APTInstallObjcMsgSendHook();
# …or without code changes, via environment variable:
export APPLETRACE_AUTO_HOOK_OBJC_MSGSEND=1

Capture & Visualize

# Run the app; fragments land in <app sandbox>/Library/appletracedata.
# Pull that folder from the simulator/device, then:

python3 merge.py -d /path/to/appletracedata     # β†’ trace.json
# or merge AND open Perfetto in one step:
sh go.sh /path/to/appletracedata

Open ui.perfetto.dev and drag in trace.json (or use Open trace file).


πŸ”§ How It Works

   Your app (instrumented)                Host tooling                 Browser
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ APTBeginSection / APTEnd…  β”‚      β”‚  merge.py /           β”‚      β”‚                β”‚
β”‚ APTInstant / APTCounter    β”‚ ───► β”‚  appletrace_cli.py    β”‚ ───► β”‚ ui.perfetto.devβ”‚
β”‚ APTAsyncBegin / …          β”‚      β”‚                       β”‚      β”‚                β”‚
β”‚ objc_msgSend auto-hook     β”‚      β”‚  fragments β†’ trace.jsonβ”‚      β”‚  drag & drop   β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
   per-thread batched writes              X-complete collapse
   β†’ <sandbox>/Library/appletracedata     β†’ single JSON array
  1. Instrument β€” add manual markers, or install the objc_msgSend hook.
  2. Capture β€” events accumulate in per-thread buffers and flush in bulk to trace fragments under <app sandbox>/Library/appletracedata.
  3. Merge β€” pull the folder and run merge.py; begin/end pairs collapse into Perfetto X complete events.
  4. Visualize β€” drag trace.json into Perfetto.

πŸ“¦ Installation

Requirements

Requirement Version Used for
macOS 10.15+ Build environment
Xcode 12+ iOS/macOS builds (arm64)
Python 3.9+ Trace merging, CLI, and tests
Perfetto Web Visualization at ui.perfetto.dev
ldid Optional Re-signing the loader's embedded framework
LLDB Optional Driving the dynamic hook mode

Build the framework

From the repository root:

# iOS device (arm64)
xcodebuild -project appletrace/appletrace.xcodeproj -scheme appletrace \
  -configuration Release -sdk iphoneos build

AppleTrace targets arm64 only. arm64e is out of scope: the auto-hook would have to rebind pointer-authenticated GOT entries, so the hook source deliberately fails to compile for arm64e. Build a plain arm64 slice.

Embed the resulting appletrace.framework into your target (see sample/ManualSectionDemo for manual mode and sample/TraceAllMsgDemo for the auto-hook). For injecting into third-party apps, see the loader/ project and run loader/resign.sh after swapping in a rebuilt framework.


πŸ› οΈ Usage

Manual Instrumentation

Objective-C

#import <appletrace/appletrace.h>

- (void)viewDidLoad {
    APTBegin;                 // auto-named "[ClassName viewDidLoad]"
    [super viewDidLoad];
    APTEnd;
}

- (void)networkRequest {
    APTBeginSection("network");   // explicit section name
    // ... network code ...
    APTEndSection("network");
}

C / C++

#include <appletrace/appletrace.h>

void complexFunction() {
    APTBeginSection("processing");
    // ... C++ code ...
    APTEndSection("processing");
}

void saferCppFunction() {
    APTScopeSection("processing");   // RAII: ends automatically at scope exit
    // ... C++ code ...
}

Instant Markers, Counters & Async Events

// Mark a point in time on the current thread's timeline
APTInstant("cache_miss");

// Plot a value over time (memory, FPS, queue depth, ...)
APTCounter("resident_mb", 142.5);
APTCounter("fps", 60);

// Track work that crosses threads / dispatch queues (matched by name + id)
uint64_t requestID = 42;
APTAsyncBegin("image_load", requestID);
dispatch_async(queue, ^{
    // ... work on another thread ...
    APTAsyncEnd("image_load", requestID);
});

Runtime Controls

APTSetEnabled(NO);                       // Temporarily pause recording
APTSetEnabled(YES);                      // Resume
BOOL on = APTIsEnabled();                // Query state
APTFlush();                              // Force buffered writes to disk
APTSyncWait();                           // Block until pending writes complete
NSLog(@"trace dir = %s", APTGetTraceDirectory());
BOOL hooked = APTIsObjcMsgSendHookInstalled();

Environment Variables

export APPLETRACE_ENABLED=1
export APPLETRACE_DATA_DIR="$HOME/tmp/appletracedata"
export APPLETRACE_BLOCK_SIZE_MB=32
export APPLETRACE_KEEP_EXISTING=1

# Automatic objc_msgSend hook (arm64)
export APPLETRACE_AUTO_HOOK_OBJC_MSGSEND=1
# Only trace classes whose names start with these comma-separated prefixes
export APPLETRACE_TRACE_CLASS_ALLOW="MyApp,UI"
# Never trace classes with these prefixes (takes precedence over allow)
export APPLETRACE_TRACE_CLASS_DENY="NSKVO,_"

# Opt-in binary fragment format (keeps string formatting off the hot path)
export APPLETRACE_BINARY=1

πŸ“Š Processing & Visualizing Traces

# Merge all fragments into trace.json (X complete events by default)
python3 merge.py -d /path/to/appletracedata

# Keep raw begin/end events instead of collapsing them
python3 merge.py -d /path/to/appletracedata --raw

# Unified CLI
python3 scripts/appletrace_cli.py merge /path/to/appletracedata
python3 scripts/appletrace_cli.py open  /path/to/appletracedata   # merge + open Perfetto

# One-liner helper
sh go.sh /path/to/appletracedata

merge.py auto-discovers both text (trace[_N].appletrace) and binary (trace[_N].appletracebin) fragments and decodes each by its magic header. Then drag the resulting trace.json into ui.perfetto.dev.

Want to try it without building anything? Drag the prebuilt sampledata/trace.json into Perfetto.


🧩 Platform & Hook Support

AppleTrace targets arm64 only.

Mode arm64
Manual sections & explicit events (APTBeginSection, APTInstant, …) βœ…
Automatic objc_msgSend / objc_msgSendSuper2 hook βœ…
  • Manual sections are the lowest-risk baseline and work on every iOS/macOS version.
  • The arm64 auto-hook is validated end-to-end on the iOS Simulator and a host stress test β€” nested sends, super dispatch, cross-thread events, a 10-argument call, and floating-point / small-aggregate ABI cases all survive the tracing wrapper.
  • arm64e is not supported. Its callers reach objc_msgSend through authenticated GOT entries (__DATA_CONST.__auth_got), which would require re-signing rebound pointers with the correct pointer-authentication context. Rather than ship an unvalidated hook, the hook source hard-errors when built for arm64e β€” build a plain arm64 slice instead.

βœ… Testing

# Python tooling (merge pipeline + binary fragment format)
python3 -m pytest tests

# objc_msgSend hook smoke tests (builds + runs the sample on a simulator)
./scripts/test_objc_msgsend_hook.sh
./scripts/test_objc_msgsend_hook_experimental.sh

# Batched-writer concurrency stress test (host build, text + binary modes)
./scripts/test_batching_stress.sh

The experimental hook script additionally validates super dispatch, cross-thread events, stack-passed and floating-point Objective-C arguments, and small aggregate returns. The stress test asserts no events are lost or duplicated across threads, flushes, and thread exits.


πŸ“ Project Structure

AppleTrace/
β”œβ”€β”€ appletrace/              # Core tracing framework (appletrace.xcodeproj)
β”‚   └── appletrace/src/      # Framework source + objc_msgSend hook
β”œβ”€β”€ loader/                  # Dynamic library loader + resign.sh
β”œβ”€β”€ sample/
β”‚   β”œβ”€β”€ ManualSectionDemo/   # Manual instrumentation demo
β”‚   └── TraceAllMsgDemo/     # Automatic objc_msgSend hook demo
β”œβ”€β”€ scripts/                 # CLI + smoke/stress test scripts
β”‚   └── appletrace_cli.py    # Merge + open-in-Perfetto CLI
β”œβ”€β”€ docs/                    # Binary format & batching design notes
β”œβ”€β”€ tests/                   # Python regression tests + stress harness
β”œβ”€β”€ sampledata/              # Demo trace.json for Perfetto
β”œβ”€β”€ merge.py                 # Merge fragments β†’ trace.json
β”œβ”€β”€ appletrace_binary.py     # Binary fragment encoder/decoder
β”œβ”€β”€ go.sh                    # Merge and open Perfetto
└── requirements.txt         # Python dev/test dependencies

❓ FAQ

Is AppleTrace still maintained? Yes β€” maintained as needed. AppleTrace is no longer under active day-to-day development, but the arrival of the AI era opens up more possibilities for future improvements and targeted maintenance when useful.

Does AppleTrace work on recent iOS versions? Manual instrumentation works on all iOS versions. The automatic hook mode targets arm64 only (arm64e is out of scope β€” see Platform & Hook Support).

Can I trace third-party apps? Yes β€” see the loader project and this Chinese guide: 搭载 MonkeyDev 可 trace 第三方 App.

Why is Python 3 required? Python 2 reached end-of-life in 2020. The tooling requires Python 3.9+.

Can I use this on macOS apps? Yes β€” AppleTrace works for both iOS and macOS applications.


🀝 Contributing

Contributions are welcome! Please read the Contributing Guide and the Agent Guide for repository conventions.

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feature/amazing-feature
  3. Commit your changes (run the test suite first).
  4. Push and open a Pull Request.

Code style: Google Objective-C Style Guide Β· PEP 8 Β· Google Shell Style Guide


πŸ› οΈ Tech Stack

Objective-C C Python Xcode Perfetto LLDB


πŸ™ Acknowledgements

Inspired by Facebook's fbtrace, and built around Google's Perfetto and the Trace Event Format.


πŸ“œ License

AppleTrace is released under the MIT License. See LICENSE for details.


πŸ“ž Support

Made with ❀️ by Everett


πŸ“ˆ Star History

Star History Chart

About

🍎Objective C Method Tracing Call Chart

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors