Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions .github/workflows/swift-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
name: Swift Tests

on:
push:
pull_request:

jobs:
swift-package:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4

- name: Swift version
run: swift --version

- name: Run package tests (macros + runtime)
run: swift test
env:
APPLETRACE_DATA_DIR: ${{ runner.temp }}/appletracedata-tests

- name: Smoke-test the SwiftTrace bridge (executable)
run: swift run AppleTraceAutoExample
env:
APPLETRACE_DATA_DIR: ${{ runner.temp }}/appletracedata-example

- name: Build both routes for the iOS Simulator
run: |
xcodebuild -scheme AppleTrace \
-destination 'generic/platform=iOS Simulator' build
xcodebuild -scheme AppleTraceAuto \
-destination 'generic/platform=iOS Simulator' build

- name: Build the Swift demo app for the iOS Simulator
# Note: -destination only, no -sdk (that would force the macro plugin
# onto the simulator SDK instead of the macOS host).
run: |
xcodebuild \
-project sample/AppleTraceSwiftDemo/AppleTraceSwiftDemo.xcodeproj \
-scheme AppleTraceSwiftDemo \
-destination 'generic/platform=iOS Simulator' \
CODE_SIGNING_ALLOWED=NO build
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,7 @@ build/
__pycache__/
*.pyc
.pytest_cache/

# SwiftPM
.build/
.swiftpm/
3 changes: 2 additions & 1 deletion AGENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ This reference is for AI agents and contributors working inside the AppleTrace r
## Repository Map
- `appletrace/` — Core framework sources (`appletrace.xcodeproj`, Objective-C runtime hooks, exported headers).
- `loader/` — Loader/packaging project plus `resign.sh` for re-signing the embedded `appletrace.framework`.
- `sample/ManualSectionDemo` and `sample/TraceAllMsgDemo` — Xcode samples that show manual instrumentation and automatic `objc_msgSend` tracing.
- `sample/ManualSectionDemo` and `sample/TraceAllMsgDemo` — Objective-C Xcode samples that show manual instrumentation and automatic `objc_msgSend` tracing.
- `sample/AppleTraceSwiftDemo` — Swift sample (SwiftUI) that consumes the local SwiftPM package and demonstrates both Swift routes: the `@Traced`/`@TraceAll`/`withSpan` macros and the `AppleTraceAuto` SwiftTrace bridge. Build with `-destination` only (no `-sdk`, which would force the macro plugin onto the wrong SDK).
- `springboard/AppleTraceSpringBoard` — Additional loader project for SpringBoard-focused experiments.
- `hookzz/` — Legacy embedded HookZz dependency (the current `objc_msgSend` hook uses a direct symbol rebind instead).
- `go.sh`, `merge.py`, `scripts/appletrace_cli.py` — Scripts for merging trace fragments into `trace.json` and opening Perfetto.
Expand Down
24 changes: 24 additions & 0 deletions Package.resolved

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

73 changes: 73 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// swift-tools-version: 6.0
import PackageDescription
import CompilerPluginSupport

let package = Package(
name: "AppleTrace",
platforms: [
.iOS(.v13),
.macOS(.v11),
],
products: [
.library(name: "AppleTrace", targets: ["AppleTrace"]),
.library(name: "AppleTraceAuto", targets: ["AppleTraceAuto"]),
],
dependencies: [
.package(url: "https://github.com/swiftlang/swift-syntax.git", from: "600.0.0"),
.package(url: "https://github.com/johnno1962/SwiftTrace.git", from: "8.6.0"),
],
targets: [
// The existing Objective-C++ trace core, reused from the Xcode tree.
.target(
name: "CAppleTrace"
),

// Swift surface: idiomatic wrappers + the @Traced / @TraceAll macros.
.target(
name: "AppleTrace",
dependencies: ["CAppleTrace", "AppleTraceMacrosPlugin"]
),

// Secondary route: zero-annotation auto-tracing by bridging the proven
// SwiftTrace runtime hook into AppleTrace events.
.target(
name: "AppleTraceAuto",
dependencies: [
"CAppleTrace",
.product(name: "SwiftTrace", package: "SwiftTrace"),
],
// SwiftTrace's API relies on global mutable state (swizzleFactory);
// build this thin bridge in the Swift 5 language mode to match it.
swiftSettings: [.swiftLanguageMode(.v5)]
),

// SwiftSyntax compiler plugin implementing the macros.
.macro(
name: "AppleTraceMacrosPlugin",
dependencies: [
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax"),
]
),

// Runnable demo + smoke check for the SwiftTrace bridge (which an
// XCTest bundle can't exercise — see the file header).
.executableTarget(
name: "AppleTraceAutoExample",
dependencies: ["AppleTrace", "AppleTraceAuto"],
swiftSettings: [.swiftLanguageMode(.v5)]
),

.testTarget(
name: "AppleTraceTests",
dependencies: [
"AppleTrace",
"AppleTraceAuto",
"AppleTraceMacrosPlugin",
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax"),
]
),
]
)
56 changes: 54 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,19 @@ sh go.sh "<trace directory shown in the app>"
On the Simulator the directory is already on your Mac. On a device, pull the
app container first (Xcode ▸ *Window ▸ Devices and Simulators ▸ Download
Container*, or `xcrun devicectl device copy from …`) — the app shows the full
command. `sample/TraceAllMsgDemo` is the companion sample for the automatic
`objc_msgSend` hook.
command.

Three samples are included:

| Sample | Language | Shows |
|--------|----------|-------|
| `sample/ManualSectionDemo` | Objective-C | Manual `APTBeginSection` sections, counters, async, threads |
| `sample/AppleTraceSwiftDemo` | Swift | `@Traced` / `@TraceAll` / `withSpan` macros **and** the `AppleTraceAuto` SwiftTrace auto-hook |
| `sample/TraceAllMsgDemo` | Objective-C | Automatic `objc_msgSend` hook |

The Swift demo consumes the local SwiftPM package, so open it from the repo
root (`open sample/AppleTraceSwiftDemo/AppleTraceSwiftDemo.xcodeproj`) and Xcode
resolves the `AppleTrace` / `AppleTraceAuto` products automatically.

### Mode A — Manual Instrumentation (recommended baseline)

Expand Down Expand Up @@ -265,6 +276,47 @@ void saferCppFunction() {
}
```

### Tracing Swift (SwiftPM)

The `objc_msgSend` hook can't see Swift's static / vtable / witness dispatch, so
Swift is traced at the **source level**. Add the package
(`https://github.com/everettjf/AppleTrace.git`) and `import AppleTrace`:

```swift
import AppleTrace

// Scoped span (closes even on throw / early return):
withSpan("loadFeed") { try? loadFeed() }

// Or annotate — works for final classes, structs, and protocol methods alike,
// because the begin/end is inserted into the body at compile time:
@Traced
func decodeImage() { /* ... */ }

@TraceAll // stamps @Traced on every method with a body
final class FeedViewModel {
func reload() { /* traced */ }
func render() { /* traced */ }
}

APTFlush() // (or AppleTrace.flush()) before pulling the trace
```

Want zero-annotation auto-tracing of a class hierarchy? The optional
`AppleTraceAuto` product bridges [SwiftTrace](https://github.com/johnno1962/SwiftTrace):

```swift
import AppleTraceAuto
AppleTraceAuto.trace(aClass: FeedViewModel.self) // entry/exit → AppleTrace
```

`AppleTraceAuto` is **Simulator / macOS only** (SwiftTrace patches
pointer-authenticated vtable slots, unsafe on real devices — gate it with
`#if targetEnvironment(simulator)`), and it can't see `final` /
statically-dispatched methods. The macros have neither limitation and are the
on-device path. See `docs/swift-tracing.md` and the runnable
`AppleTraceAutoExample` (`swift run AppleTraceAutoExample`).

### Instant Markers, Counters & Async Events

```objc
Expand Down
54 changes: 52 additions & 2 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,19 @@ sh go.sh "<App 中显示的 trace 目录>"

模拟器上该目录就在你的 Mac 本地。真机上需先拉取 App 容器(Xcode ▸ *Window ▸
Devices and Simulators ▸ Download Container*,或 `xcrun devicectl device
copy from …`)——App 内会显示完整命令。`sample/TraceAllMsgDemo` 则是自动
`objc_msgSend` hook 的配套示例。
copy from …`)——App 内会显示完整命令。

仓库内含三个示例:

| 示例 | 语言 | 演示内容 |
|------|------|---------|
| `sample/ManualSectionDemo` | Objective-C | 手动 `APTBeginSection`、counter、async、多线程 |
| `sample/AppleTraceSwiftDemo` | Swift | `@Traced` / `@TraceAll` / `withSpan` 宏,**以及** `AppleTraceAuto` 的 SwiftTrace 自动 hook |
| `sample/TraceAllMsgDemo` | Objective-C | 自动 `objc_msgSend` hook |

Swift demo 依赖本地 SwiftPM 包,从仓库根目录打开
(`open sample/AppleTraceSwiftDemo/AppleTraceSwiftDemo.xcodeproj`),Xcode 会自动
解析 `AppleTrace` / `AppleTraceAuto` 两个 product。

### 模式 A — 手动埋点(推荐基线)

Expand Down Expand Up @@ -256,6 +267,45 @@ void saferCppFunction() {
}
```

### 追踪 Swift 代码(SwiftPM)

`objc_msgSend` hook 看不到 Swift 的静态 / vtable / witness 派发,所以 Swift 走
**源码级埋点**。把本仓库作为 SwiftPM 依赖添加
(`https://github.com/everettjf/AppleTrace.git`),然后 `import AppleTrace`:

```swift
import AppleTrace

// 作用域 span(即使 throw / 提前返回也会闭合):
withSpan("loadFeed") { try? loadFeed() }

// 或用宏标注——对 final 类、struct、protocol 方法都生效,
// 因为 begin/end 是在编译期插入函数体的:
@Traced
func decodeImage() { /* ... */ }

@TraceAll // 给每个有函数体的方法都自动加 @Traced
final class FeedViewModel {
func reload() { /* 已追踪 */ }
func render() { /* 已追踪 */ }
}

APTFlush() // (或 AppleTrace.flush())读取 trace 前先 flush
```

想要零标注地自动追踪整个类层级?可选的 `AppleTraceAuto` product 桥接了
[SwiftTrace](https://github.com/johnno1962/SwiftTrace):

```swift
import AppleTraceAuto
AppleTraceAuto.trace(aClass: FeedViewModel.self) // 进入/退出 → AppleTrace
```

`AppleTraceAuto` **仅限模拟器 / macOS**(SwiftTrace 会改写经过指针认证的 vtable 槽,
在真机上不安全——请用 `#if targetEnvironment(simulator)` 包起来),且看不到 `final` /
静态派发的方法。宏没有这些限制,是真机上的首选路径。详见 `docs/swift-tracing.md`
与可运行的 `AppleTraceAutoExample`(`swift run AppleTraceAutoExample`)。

### 瞬时标记、计数器与异步事件

```objc
Expand Down
Loading
Loading