5.2 KiB
# Summary: Dynamic Interposing, Linking, and Build Configuration (macOS / Xcode / SwiftPM)
## Main Topic
Techniques and configuration patterns for:
- **Intercepting and shadowing C/C++ functions** using `dyld` interposing
- **Hooking low-level functions** like `__cxa_throw` to capture stack traces
- **Controlling Xcode’s preview dylib linking**
- **Propagating defines and optional frameworks** across Xcode projects and Swift packages
- **Detecting sandbox vs. DMG runtime environments**
---
## Key Decisions & Solutions
### 1. Shadowing System C Functions via `dyld` Interposing
- Use `__DATA,__interpose` section with dyld to redirect system calls.
- Example interpose tuple:
```c
__attribute__((used))
static struct {
void *new_func;
void *orig_func;
} _interposers[] __attribute__((section("__DATA, __interpose"))) = {
{ (void*)my_open, (void*)open }
};
-
Optionally use
dyld_dynamic_interpose()for runtime registration. -
Reference: HackTricks macOS Function Hooking
2. Interposing __cxa_throw to Capture Stack Traces
-
Replace C++ ABI’s
__cxa_throwto log stack traces at throw time. -
Example implementation:
extern "C" void __cxa_throw(void*, std::type_info*, void (*)(void*)) __attribute__((noreturn)); extern "C" void my___cxa_throw(void* thrown_exception, std::type_info* tinfo, void (*dest)(void*)) __attribute__((noreturn)) { void* frames[128]; int n = backtrace(frames, 128); // Store stack trace... __real___cxa_throw(thrown_exception, tinfo, dest); } __attribute__((used)) static struct { const void* replacement; const void* replacee; } _interposers[] __attribute__((section("__DATA,__interpose"))) = { { (const void*)my___cxa_throw, (const void*)__cxa_throw }, }; -
Hook is used for debugging; not recommended in production.
3. Disable __preview.dylib Linking in Xcode
-
Build setting:
ENABLE_DEBUG_DYLIB = NO -
Steps:
-
Select target → Build Settings
-
Search for
ENABLE_DEBUG_DYLIB -
Set to NO (for Debug or all configurations)
-
Clean and rebuild
-
-
Prevents automatic SwiftUI preview dylib injection.
4. Propagating #define Flags into Swift Packages
-
Not supported directly.
-
Packages build in isolation; app-level defines (
SWIFT_ACTIVE_COMPILATION_CONDITIONS) don’t propagate. -
Solutions:
-
Define flags in the package:
.target( name: "MyLib", swiftSettings: [.define("USE_FOO")] ) -
Pass global flags:
swift build -Xswiftc -D USE_FOO -
Expose feature variants via separate targets or products.
-
5. Detecting Sandbox vs. DMG App Runtime
Detect App Sandbox
import Security
func isSandboxed() -> Bool {
let task = SecTaskCreateFromSelf(nil)!
if let v = SecTaskCopyValueForEntitlement(task, "com.apple.security.app-sandbox" as CFString, nil) as? NSNumber {
return v.boolValue
}
return false
}
Detect App Store vs. DMG
-
Modern (macOS 14+):
import StoreKit func installOrigin() async -> Bool { if case .verified(_) = try? await AppTransaction.shared { return true // App Store } return false } -
Legacy:
if let url = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: url.path) { // App Store receipt found }
6. Using Optional Frameworks from a Swift Package
-
App’s optional linkage does not propagate to packages.
-
Alternatives:
-
Add
linkerSettingsorunsafeFlagsin the package:.target( name: "Child", linkerSettings: [ .unsafeFlags(["-weak_framework", "Foo"]) ] ) -
Use
#if canImport(Foo)inside the package to guard code. -
Offer multiple package products (e.g.,
CoreandWithFoo). -
As last resort,
dlopen()the framework dynamically.
-
Important Snippets Collected
-
Dyld Interpose Example
-
__cxa_throwhook for backtrace -
Disabling SwiftUI
__preview.dylib -
Sandbox/App Store detection functions
-
SwiftPM
swiftSettingsand weak framework linking
Unresolved / Future Considerations
-
Testing cross-target define propagation via unified SwiftPM workspace.
-
Verifying
dyld_dynamic_interpose()availability on latest macOS. -
Providing a reusable utility to report sandbox + install origin combined.