はじめに
Xcodeが正式に対応していない新しいiOSバージョンの実機に対して、古いXcodeからアプリをインストール・デバッグしたい場合があります。
以前は、新しいXcodeから古いXcodeへ DeviceSupport ディレクトリをコピーすることで、対応できるケースがありました。
Xcode.app/
Contents/
Developer/
Platforms/
iPhoneOS.platform/
DeviceSupport/
しかし、従来のDeviceSupportのコピーでは対応できないパターンが出てきています。
今回、Xcode 26.3からiOS 27.1搭載のiPhone Duoにアプリをインストールし、LLDBによるデバッグまで成功したため、その方法をまとめます。
なお、Appleが公式にサポートしている方法ではありません。何が起きても自己責任です。
Xcode内部のプラグインキャッシュやバイナリを変更するため、開発・検証用途に限定してください。
検証環境
| 項目 | バージョン・情報 |
|---|---|
| パッチ対象 | Xcode 26.3(17C529) |
| 新しいXcode | Xcode 27.1 |
| デバイス | iPhone Duo |
| モデル識別子 | iPhone19,4 |
| iOS | 27.1 |
| iOSビルド | 24A94232 |
| ホストOS | macOS(バージョン未記録) |
| デフォルトXcode | Xcode 26.2 |
Xcodeは以下の場所にインストールしています。
/Applications/Xcode-26.3.0.app
/Applications/Xcode-27.1.0.app
発生した問題
Xcode 26.3からiPhone Duoへデバッグ実行しようとすると、以下のエラーが発生しました。
Device doesn't have sufficient metadata to manage its symbols
Model: iPhone19,4
Platform: iphoneos
OS Version: 27.1
Build Version: 24A94232
Architecture: undefined_arch
特に注目したのは以下の部分です。
Architecture: undefined_arch
Xcodeがデバイスのアーキテクチャを正常に識別できていないことが分かります。
調査したところ、Xcode 27系列には、古いXcodeには存在しない次のアーキテクチャ定義が追加されていました。
Xcode.Architecture.arm64e.x1
この定義を旧Xcodeのプラグインキャッシュに追加することで、デバイスの準備処理を進められる可能性があります。
参考にした記事
今回の方法は、以下の記事を参考にしています。
Xcode 26.3 调试 iOS 27:从设备元数据报错到 GUI Run
参考記事では、Xcode 26.3からiOS 27.0.1の実機デバッグを行い、メタデータ追加、シンボル抽出器の置換、LLDB設定の変更によって問題を解決しています。
今回の検証ではiOS 27.1を使用し、最終的にはLLDB設定の変更を行わずにデバッグまで成功しました。
パッチの仕組み
今回必要だった修正は大きく2つです。
1. アーキテクチャ定義の追加
Xcodeのプラグインキャッシュ PlugInCache.xcplugincache に arm64e.x1 の定義を追加します。
追加した定義は以下です。
{
"CPUType": "12",
"id": "Xcode.Architecture.arm64e.x1",
"sortPriority": "8",
"point": "Xcode.Architecture",
"canonicalName": "arm64e.x1",
"analyticsEnum": "8",
"CPUSubType": "12",
"is64Bit": "true"
}
このキャッシュはバイナリplist形式であり、内部には DVTScanRecords などのデータが格納されています。
パッチでは既存の Xcode.Architecture.arm64e を持つレコードを検索し、同じ extensions 辞書に上記の定義を追加します。
DVTScanRecords の構造が想定と異なる場合は、変更せずにエラー終了する設計としています。
2. シンボル抽出器の置換
アーキテクチャ定義を追加した後、デバイスのシンボル準備処理が進むようになりましたが、次のエラーが発生しました。
dyld_shared_cache_extract_dylibs failed
Domain: DVTDeviceSymbolsCoordinatorErrorDomain
Code: 908
これは、デバイスのDyld Shared Cacheからシンボルを抽出する処理が失敗している状態です。
Xcode 26.3に含まれる dsc_extractor.bundle を、Xcode 27.1のものに置き換えることで解決しました。
対象ファイル:
Xcode.app/
Contents/
Developer/
Platforms/
iPhoneOS.platform/
usr/
lib/
dsc_extractor.bundle
今回はこの2つの修正で、アプリのインストールからLLDBデバッグまで成功しました。
パッチスクリプトの利用方法
手作業による変更を避けるため、作業を自動化した xcode26-ios27-patch.sh を使用します。
このスクリプトは、元のXcodeを直接変更せず、ホームディレクトリ内に独立コピーを作成してパッチを適用します。
主なコマンドは以下のとおりです。
| コマンド | 内容 |
|---|---|
doctor |
Xcodeのバージョンとコード署名の確認 |
init |
Xcode 26.3の独立コピーを作成 |
launch |
専用プラグインキャッシュを使用して起動 |
patch-metadata |
arm64e.x1 のメタデータを追加 |
patch-extractor |
Xcode 27.1のシンボル抽出器に置換 |
install-lldb |
LLDBの互換性設定を追加 |
status |
パッチ適用状態を確認 |
restore |
パッチ済みXcodeと専用キャッシュを削除 |
スクリプトは参照記事の処理を自動化したものです。以下では、手元に xcode26-ios27-patch.sh が配置されている前提で説明します。
事前準備:macOSの「アプリ管理」権限
この設定は重要です。
今回の検証では、当初Visual Studio Codeの統合ターミナルからパッチを実行していましたが、後述する dsc_extractor.bundle の置換時に書き込みが拒否されました。
cp: .../dsc_extractor.bundle: Operation not permitted
ファイルの所有者やパーミッションに問題はありませんでした。
-rwxr-xr-x@ 1 develop staff compressed ... dsc_extractor.bundle
com.apple.provenance
最終的に、「アプリ管理」が許可されているターミナルアプリから実行したところ、置換に成功しました。
パッチ適用前に、以下を確認することを推奨します。
- macOSの「システム設定」を開く。
- 「プライバシーとセキュリティ」→「アプリ管理」を開く。
- スクリプトを実行するターミナルアプリの権限を有効にする。
- ターミナルアプリを再起動する。
VS Codeの統合ターミナルを使用する場合は、Visual Studio Code自体のアプリ管理権限を確認してください。
なお、今回の事例ではこの権限不足が原因だったと考えられますが、あらゆる Operation not permitted が同じ原因とは限りません。
Step 1. Xcodeのバージョンを確認
まず、コピー元のXcodeが正しいか確認します。
./xcode26-ios27-patch.sh doctor
成功時:
==> Versions and source signature verified
ここではXcode 26.3のビルド番号 17C529 も検証しています。
Step 2. Xcode 26.3の独立コピーを作成
./xcode26-ios27-patch.sh init
Xcode 26.3をコピーします。
コピー先は以下です。
~/Library/Developer/Xcode-iOS27-Debug/
Xcode-26.3.0.app
この構成により、通常利用しているXcode 26.3を変更せずに済みます。
Step 3. 一度Xcodeを起動する
./xcode26-ios27-patch.sh launch
このコマンドは、概ね次の処理を行っています。
export DEVELOPER_DIR="$HOME/Library/Developer/Xcode-iOS27-Debug/Xcode-26.3.0.app/Contents/Developer"
"$HOME/Library/Developer/Xcode-iOS27-Debug/Xcode-26.3.0.app/Contents/MacOS/Xcode" \
-IDECacheRootDirectory \
"$HOME/Library/Developer/Xcode-iOS27-Debug/Caches"
重要なのは以下の起動オプションです。
-IDECacheRootDirectory
これによって、パッチ済みXcode専用のGUIプラグインキャッシュを使用します。
Xcodeが起動したら、一度完全に終了してください。
Step 4. アーキテクチャ定義を追加
./xcode26-ios27-patch.sh patch-metadata
このコマンドでは以下の処理が実行されます。
- GUIプラグインキャッシュの存在と構造を検証
- 元のキャッシュをバックアップ
- Foundationを使用したplist修正プログラムをコンパイル
-
Xcode.Architecture.arm64e.x1を追加 - plistの整合性を検証してキャッシュを置換
対象キャッシュは次の場所です。
~/Library/Developer/Xcode-iOS27-Debug/
Caches/
26.3-17C529/
Xcode/
PlugInCache.xcplugincache
適用後、再びXcodeを起動します。
./xcode26-ios27-patch.sh launch
適用後の変化
当初の以下のエラーは発生しなくなりました。
Device doesn't have sufficient metadata to manage its symbols
代わりに次の処理が開始されました。
Copying shared cache symbols from iPhone Duo (50% completed)
Xcode will continue when the operation completes.
これにより、少なくともアーキテクチャメタデータの不足による初期エラーを突破できたことが確認できました。
ただし、この状態ではシンボルの抽出に失敗しました。
Step 5. シンボル抽出器を置換
発生したエラーは以下です。
dyld_shared_cache_extract_dylibs failed
詳細ログ:
[DVTDeviceOperation:standard]
Error reported for dscExtractor:
dyld_shared_cache_extract_dylibs failed
Domain: DVTDeviceSymbolsCoordinatorErrorDomain
Code: 908
User Info: {
DVTRadarComponentKey = 487927;
"com.apple.dt.DVTCoreDevice.operationName" = dscExtractor;
}
そこで、新しいXcodeの抽出器をコピーします。
パッチ済みXcodeを終了した状態で実行します。
./xcode26-ios27-patch.sh patch-extractor
内部では次のファイルを差し替えます。
Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle
置換時には元のファイルをバックアップし、置換後にコード署名とSHA-256を確認します。
今回成功した際の出力:
==> Checking copied extractor signature and hashes
bdfa4c2da9f272c9b0ae9ae61aad5121524c91e5a12ffa346d52600a26243f3c /Applications/Xcode-27.1.0.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle
bdfa4c2da9f272c9b0ae9ae61aad5121524c91e5a12ffa346d52600a26243f3c /Users/.../Xcode-26.3.0.app/Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle
==> Done.
両方のSHA-256が一致しているため、抽出器がコピーされたことを確認できます。
なお、バンドル内部のファイルを変更すると、Xcode.app全体のリソース署名は破損する可能性があります。抽出器単体の署名が有効でも、アプリバンドル全体の署名が有効であることは保証されません。
注意:App Management権限がない場合
今回、アプリ管理権限が許可されていないVS Code統合ターミナルでは、次のエラーが発生しました。
cp: .../dsc_extractor.bundle: Operation not permitted
mv で既存ファイルをリネームしようとしても、同様に拒否されました。
mv: rename .../dsc_extractor.bundle to .../dsc_extractor.bundle.original: Operation not permitted
通常のファイル権限は問題なく、uchg や restricted といった変更禁止フラグも付いていませんでした。
アプリ管理権限を許可したターミナルから再実行することで、正常に置換できました。
このため、単に sudo を追加するのではなく、まずターミナルアプリの権限を確認することを推奨します。
Step 6. iPhone Duoでデバッグ実行
再度Xcodeを起動します。
./xcode26-ios27-patch.sh launch
iPhone Duoを接続し、Run Destinationとして選択してアプリを実行します。
今回の環境では、これまで失敗していた次の処理が100%まで完了しました。
Copying shared cache symbols from iPhone Duo (100% completed)
その後、アプリのインストール・起動にも成功しました。
検証結果
最終的な結果は以下のとおりです。
| 検証項目 | 結果 |
|---|---|
| iPhone Duoの認識 | 成功 |
undefined_arch エラーの解消 |
成功 |
arm64e.x1 メタデータの追加 |
成功 |
dsc_extractor.bundle の置換 |
成功 |
| Shared Cache Symbolsの抽出 | 成功 |
| アプリのインストール | 成功 |
| アプリの起動 | 成功 |
| LLDBのアタッチ | 成功 |
| Swiftブレークポイントで停止 | 成功 |
| 変数の値の確認 | 成功 |
| ステップ実行 | 成功 |
つまり、Xcode 26.3からiPhone Duo(iOS 27.1)へのGUIデバッグを実現できました。
LLDBの追加パッチは不要だった
参考記事では、デバッグ時のLLDBクラッシュを避けるため、以下の設定も行っています。
settings set target.preload-symbols false
settings set target.load-script-from-symbol-file false
スクリプトには、この設定を追加する install-lldb コマンドも用意しています。
./xcode26-ios27-patch.sh install-lldb
しかし、今回の環境ではこの処理を実行せずに、ブレークポイント・変数参照・ステップ実行がすべて成功しました。
したがって、今回必要だったのは以下の2つだけでした。
./xcode26-ios27-patch.sh patch-metadata
./xcode26-ios27-patch.sh patch-extractor
install-lldb は必要に応じて使用するオプションという位置付けです。
毎回パッチスクリプトから起動する必要がある?
パッチ適用後、毎回 patch-metadata や patch-extractor を実行する必要はありません。
ただし、Xcodeの起動時に専用プラグインキャッシュを指定する必要があります。
通常のFinderやDockから Xcode-26.3.0.app を直接起動すると、パッチを適用していない別のキャッシュが使われる可能性があります。
そのため、以下のコマンドで起動します。
./xcode26-ios27-patch.sh launch
スクリプトの init では、次の起動用ファイルも生成されます。
~/Library/Developer/Xcode-iOS27-Debug/start-xcode-debug.command
この .command ファイルをFinderから実行することもできます。
なお、専用キャッシュが再生成されると、arm64e.x1 の定義が失われる可能性があります。その場合はパッチを再適用する必要があります。
アンインストール・後片付け
今回のスクリプトは、オリジナルのXcode 26.3を変更しない構成です。
パッチ環境が不要になった場合は、パッチ済みXcodeを終了してから以下を実行します。
./xcode26-ios27-patch.sh restore
このコマンドでは次のものが削除されます。
- パッチ済みXcode 26.3の独立コピー
- 専用GUIプラグインキャッシュ
- パッチ用ツール
- 起動用
.commandファイル - スクリプトで追加したLLDB設定(存在する場合)
ただし、バックアップディレクトリは残ります。
バックアップも不要な場合は、内容を確認したうえで以下を削除できます。
rm -rf "$HOME/Library/Developer/Xcode-iOS27-Debug"
また、iPhone Duoから抽出したシンボルキャッシュは、通常以下の場所に保存されます。
~/Library/Developer/Xcode/iOS DeviceSupport/
このデータは他のXcodeでも利用される可能性があるため、通常は削除する必要はありません。
最後に、デフォルトのXcodeが変更されていないか確認します。
xcode-select -p
注意事項・制約
今回の方法は、あくまでXcode内部の非公開実装を利用した回避策です。
以下の点に注意してください。
- Appleによる正式なサポート対象ではありません。
- Xcode 26.3(17C529)を前提としています。
- 異なるXcodeビルドではプラグインキャッシュの構造が異なる可能性があります。
-
dsc_extractor.bundleの置換によってアプリ全体のコード署名が破損する可能性があります。 - Xcodeのアップデートやキャッシュ再生成によってパッチが無効になる可能性があります。
- iOS 27用SDKが利用可能になるわけではありません。
- 全機能・全端末での互換性を保証するものではありません。
また、今回の成功は特定のiPhone DuoとiOS 27.1ビルドでの実機検証結果です。同じXcode構成でも、別の端末やiOSビルドで同じ結果になるとは限りません。
まとめ
Xcode 26.3でiOS 27.1搭載のiPhone Duoをデバッグするために、次の2つのパッチが有効でした。
1. patch-metadata
旧Xcodeに存在しない arm64e.x1 アーキテクチャ定義をプラグインキャッシュへ追加し、デバイスメタデータ不足のエラーを回避。
2. patch-extractor
Xcode 27.1から dsc_extractor.bundle をコピーし、Dyld Shared Cacheのシンボル抽出エラー(Code 908)を解消。
その結果、以下の操作に成功しました。
- アプリのインストールと起動
- LLDBによるデバッグアタッチ
- Swiftのブレークポイント
- 変数参照
- ステップ実行
参考記事ではLLDBの追加設定も必要でしたが、今回の環境では不要でした。
従来の DeviceSupport コピー方式に代わる、CoreDevice世代のXcodeで新しいiOS実機をデバッグするための回避策として参考になれば幸いです。
参考リンク
- Xcode 26.3 调试 iOS 27:从设备元数据报错到 GUI Run
- Apple Developer Forums - CoreDeviceとDeviceSupportについて
- Apple - Xcodeのシステム要件
付録
xcode26-ios27-patch.sh
#!/bin/bash
# This script intentionally does NOT bypass macOS Gatekeeper or modify the source Xcode.
set -euo pipefail
SOURCE_XCODE="${SOURCE_XCODE:-/Applications/Xcode-26.3.0.app}"
NEW_XCODE="${NEW_XCODE:-/Applications/Xcode-27.1.0.app}"
DEBUG_ROOT="${DEBUG_ROOT:-$HOME/Library/Developer/Xcode-iOS27-Debug}"
DEBUG_XCODE="$DEBUG_ROOT/Xcode-26.3.0.app"
CACHE_ROOT="$DEBUG_ROOT/Caches"
LLDB_INIT="$HOME/.lldbinit-Xcode"
EXPECTED_BUILD=17C529
EXPECTED_LLDB=lldb-1703.0.236.103
EXTRACTOR_REL=Contents/Developer/Platforms/iPhoneOS.platform/usr/lib/dsc_extractor.bundle
CACHE_REL=26.3-17C529/Xcode/PlugInCache.xcplugincache
SOURCE_DIR="$(cd "$(dirname "$0")" && pwd -P)"
say() { printf '\n==> %s\n' "$*"; }
fail() { printf 'ERROR: %s\n' "$*" >&2; exit 1; }
require_file() { [[ -f "$1" ]] || fail "File not found: $1"; }
require_dir() { [[ -d "$1" ]] || fail "Directory not found: $1"; }
build_id() { /usr/libexec/PlistBuddy -c 'Print ProductBuildVersion' "$1/Contents/version.plist"; }
version_id() { /usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' "$1/Contents/Info.plist"; }
check_source() {
require_dir "$SOURCE_XCODE"
[[ "$(version_id "$SOURCE_XCODE")" == 26.3* ]] || fail "Source must be Xcode 26.3: $SOURCE_XCODE"
[[ "$(build_id "$SOURCE_XCODE")" == "$EXPECTED_BUILD" ]] || fail "Expected Xcode build $EXPECTED_BUILD, got $(build_id "$SOURCE_XCODE")"
codesign --verify --strict "$SOURCE_XCODE" || fail 'Source code signature verification failed. Use a clean original installation.'
}
check_new() {
require_dir "$NEW_XCODE"
[[ "$(version_id "$NEW_XCODE")" == 27.1* ]] || fail "New Xcode must be 27.1: $NEW_XCODE"
}
ensure_stopped() {
if pgrep -f "$DEBUG_XCODE/Contents/MacOS/Xcode" >/dev/null 2>&1; then
fail 'Debug Xcode appears to be running. Quit it first.'
fi
}
make_launch() {
cat > "$DEBUG_ROOT/start-xcode-debug.command" <<'LAUNCH'
#!/bin/zsh
set -eu
debug_root="$(cd -- "$(dirname -- "$0")" && pwd -P)"
debug_app="$debug_root/Xcode-26.3.0.app"
if [[ ! -x "$debug_app/Contents/MacOS/Xcode" ]]; then
print -u2 -- "Debug Xcode not found: $debug_app"
exit 1
fi
export DEVELOPER_DIR="$debug_app/Contents/Developer"
exec "$debug_app/Contents/MacOS/Xcode" -IDECacheRootDirectory "$debug_root/Caches" "$@"
LAUNCH
chmod +x "$DEBUG_ROOT/start-xcode-debug.command"
}
init() {
check_source
[[ ! -e "$DEBUG_XCODE" ]] || fail "Copy already exists: $DEBUG_XCODE"
[[ ! -e "$DEBUG_ROOT/Xcode-26.3.0.preparing" ]] || fail 'Staging copy already exists'
mkdir -p "$DEBUG_ROOT"
say 'Cloning pristine Xcode 26.3 (APFS clone, original untouched)'
cp -cRp "$SOURCE_XCODE" "$DEBUG_ROOT/Xcode-26.3.0.preparing"
mv "$DEBUG_ROOT/Xcode-26.3.0.preparing" "$DEBUG_XCODE"
make_launch
say 'Created independent copy and launcher.'
say "NEXT: $DEBUG_ROOT/start-xcode-debug.command"
say 'Launch once, then quit Xcode fully, then run: patch-metadata'
}
make_helper() {
mkdir -p "$DEBUG_ROOT/Tools"
cat > "$DEBUG_ROOT/Tools/cache_patch.m" <<'OBJC'
#import <Foundation/Foundation.h>
int main(int argc, const char *argv[]) {
@autoreleasepool {
if (argc != 3) { fprintf(stderr,"Usage: cache_patch INPUT OUTPUT\n"); return 2; }
NSError *error = nil;
NSData *input = [NSData dataWithContentsOfFile:@(argv[1]) options:0 error:&error];
if (!input) { fprintf(stderr,"Read failed: %s\n",error.description.UTF8String); return 3; }
id parsed = [NSPropertyListSerialization propertyListWithData:input
options:NSPropertyListMutableContainersAndLeaves format:NULL error:&error];
if (![parsed isKindOfClass:NSMutableDictionary.class]) { fprintf(stderr,"Unexpected plist root\n"); return 3; }
NSMutableDictionary *root = parsed;
id records = root[@"DVTScanRecords"];
if (![records isKindOfClass:NSArray.class]) { fprintf(stderr,"DVTScanRecords missing\n"); return 4; }
NSMutableDictionary *target = nil;
NSUInteger matches = 0;
for (id record in records) {
if (![record isKindOfClass:NSDictionary.class]) return 4;
id plist = record[@"plugInPlist"];
if (![plist isKindOfClass:NSDictionary.class]) continue;
id plugin = plist[@"plug-in"];
if (![plugin isKindOfClass:NSDictionary.class]) continue;
id extensions = plugin[@"extensions"];
if (![extensions isKindOfClass:NSMutableDictionary.class]) continue;
if (extensions[@"Xcode.Architecture.arm64e"]) { target=extensions; matches++; }
}
if (matches != 1) { fprintf(stderr,"Expected exactly 1 arm64e record, got %lu\n",(unsigned long)matches); return 4; }
if (target[@"Xcode.Architecture.arm64e.x1"]) { fprintf(stderr,"arm64e.x1 already present\n"); return 5; }
target[@"Xcode.Architecture.arm64e.x1"] = @{
@"CPUType":@"12", @"id":@"Xcode.Architecture.arm64e.x1",
@"sortPriority":@"8", @"point":@"Xcode.Architecture",
@"canonicalName":@"arm64e.x1", @"analyticsEnum":@"8",
@"CPUSubType":@"12", @"is64Bit":@"true"
};
NSData *output = [NSPropertyListSerialization dataWithPropertyList:root
format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
if (!output || ![output writeToFile:@(argv[2]) options:NSDataWritingAtomic error:&error]) {
fprintf(stderr,"Write failed: %s\n",error.description.UTF8String); return 6;
}
puts("Architecture metadata patched");
}
return 0;
}
OBJC
local sdk
sdk="$(DEVELOPER_DIR="$SOURCE_XCODE/Contents/Developer" xcrun --sdk macosx --show-sdk-path)"
DEVELOPER_DIR="$SOURCE_XCODE/Contents/Developer" xcrun --sdk macosx clang \
-isysroot "$sdk" -fobjc-arc -framework Foundation \
"$DEBUG_ROOT/Tools/cache_patch.m" -o "$DEBUG_ROOT/Tools/cache_patch"
}
patch_metadata() {
check_source
require_dir "$DEBUG_XCODE"
ensure_stopped
local cache="$CACHE_ROOT/$CACHE_REL"
require_file "$cache"
[[ ! -e "$cache.patched" ]] || fail "Leftover output exists: $cache.patched"
say 'Compiling article Foundation patch helper'
make_helper
local backup_dir="$DEBUG_ROOT/Backups/metadata-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir"
cp -p "$cache" "$backup_dir/PlugInCache.xcplugincache.original"
say 'Adding arm64e.x1 to GUI plugin cache (not xcodebuild cache)'
"$DEBUG_ROOT/Tools/cache_patch" "$cache" "$cache.patched"
plutil -lint "$cache.patched"
mv "$cache.patched" "$cache"
say "Done. Backup: $backup_dir"
say 'NEXT: reopen debug Xcode and test. If Code 908 appears, run patch-extractor.'
}
patch_extractor() {
require_dir "$DEBUG_XCODE"
check_new
ensure_stopped
require_file "$NEW_XCODE/$EXTRACTOR_REL"
require_file "$DEBUG_XCODE/$EXTRACTOR_REL"
local backup_dir="$DEBUG_ROOT/Backups/extractor-$(date +%Y%m%d-%H%M%S)"
mkdir -p "$backup_dir"
cp -p "$DEBUG_XCODE/$EXTRACTOR_REL" "$backup_dir/dsc_extractor.bundle.original"
cp -p "$NEW_XCODE/$EXTRACTOR_REL" "$DEBUG_XCODE/$EXTRACTOR_REL"
say 'Checking copied extractor signature and hashes'
codesign --verify --strict "$DEBUG_XCODE/$EXTRACTOR_REL"
shasum -a 256 "$NEW_XCODE/$EXTRACTOR_REL" "$DEBUG_XCODE/$EXTRACTOR_REL"
say "Done. Backup: $backup_dir"
say 'Note: modifying this component breaks Xcode.app overall resource seal.'
}
install_lldb() {
require_dir "$DEBUG_XCODE"
local marker_start='# BEGIN XCODE26_IOS27_DEBUG_PATCH'
local marker_end='# END XCODE26_IOS27_DEBUG_PATCH'
if [[ -f "$LLDB_INIT" ]] && grep -qF "$marker_start" "$LLDB_INIT"; then
fail "LLDB block already exists: $LLDB_INIT"
fi
if [[ -e "$LLDB_INIT" ]]; then
cp -p "$LLDB_INIT" "$LLDB_INIT.backup-$(date +%Y%m%d-%H%M%S)"
fi
cat >> "$LLDB_INIT" <<'LLDB'
# BEGIN XCODE26_IOS27_DEBUG_PATCH
# Applied only to the independent Xcode 26.3 copy and its exact LLDB version.
script import lldb, os; _ios27_root = os.path.realpath(os.path.expanduser("~/Library/Developer/Xcode-iOS27-Debug/Xcode-26.3.0.app/Contents")) + os.sep
script lldb.debugger.HandleCommand("settings set target.preload-symbols false") if lldb.SBDebugger.GetVersionString().splitlines()[0] == "lldb-1703.0.236.103" and os.path.realpath(lldb.__file__).startswith(_ios27_root) else None
script lldb.debugger.HandleCommand("settings set target.load-script-from-symbol-file false") if lldb.SBDebugger.GetVersionString().splitlines()[0] == "lldb-1703.0.236.103" and os.path.realpath(lldb.__file__).startswith(_ios27_root) else None
# END XCODE26_IOS27_DEBUG_PATCH
LLDB
say "Added conditional LLDB settings to $LLDB_INIT"
if [[ "$DEBUG_ROOT" != "$HOME/Library/Developer/Xcode-iOS27-Debug" ]]; then
say 'WARNING: LLDB guard uses the article default path. Customize it manually for your DEBUG_ROOT.'
fi
}
remove_lldb() {
[[ -f "$LLDB_INIT" ]] || { say 'No LLDB configuration'; return; }
if ! grep -qF '# BEGIN XCODE26_IOS27_DEBUG_PATCH' "$LLDB_INIT"; then
say 'No managed LLDB block found'; return
fi
cp -p "$LLDB_INIT" "$LLDB_INIT.backup-$(date +%Y%m%d-%H%M%S)"
awk '/^# BEGIN XCODE26_IOS27_DEBUG_PATCH$/{skip=1;next} /^# END XCODE26_IOS27_DEBUG_PATCH$/{skip=0;next} !skip{print}' "$LLDB_INIT" > "$LLDB_INIT.tmp.$$"
mv "$LLDB_INIT.tmp.$$" "$LLDB_INIT"
say 'Removed managed LLDB settings; unrelated settings preserved.'
}
status() {
say "Source: $SOURCE_XCODE"
[[ -d "$SOURCE_XCODE" ]] && { echo "Version: $(version_id "$SOURCE_XCODE") / $(build_id "$SOURCE_XCODE")"; } || true
say "Debug copy: $DEBUG_XCODE"
[[ -d "$DEBUG_XCODE" ]] && echo 'Exists' || echo 'Not yet created'
say "GUI cache: $CACHE_ROOT/$CACHE_REL"
[[ -f "$CACHE_ROOT/$CACHE_REL" ]] && plutil -p "$CACHE_ROOT/$CACHE_REL" | grep -F 'Xcode.Architecture.arm64e.x1' | head -n 2 || true
say 'Extractor hashes (if present)'
[[ -f "$DEBUG_XCODE/$EXTRACTOR_REL" ]] && shasum -a 256 "$DEBUG_XCODE/$EXTRACTOR_REL" || true
[[ -f "$NEW_XCODE/$EXTRACTOR_REL" ]] && shasum -a 256 "$NEW_XCODE/$EXTRACTOR_REL" || true
say 'LLDB override marker'
[[ -f "$LLDB_INIT" ]] && grep -nF '# BEGIN XCODE26_IOS27_DEBUG_PATCH' "$LLDB_INIT" || true
}
restore() {
ensure_stopped
remove_lldb
if [[ -d "$DEBUG_XCODE" ]]; then
say 'Deleting ONLY independent debug Xcode and its private cache.'
rm -rf "$DEBUG_XCODE" "$CACHE_ROOT" "$DEBUG_ROOT/Tools" "$DEBUG_ROOT/start-xcode-debug.command"
fi
say 'Kept Backups directory. Original Xcode was never modified.'
}
usage() {
cat <<'HELP'
Usage: ./xcode26-ios27-patch.sh COMMAND [Xcode project/workspace for launch]
doctor Validate paths, versions and source code signature
init Clone clean Xcode 26.3 and create private-cache launcher
launch [path] Open patched debug Xcode (first launch generates its cache)
patch-metadata Patch arm64e.x1 in the GUI plugin cache (Xcode must be closed)
patch-extractor Copy dsc_extractor.bundle from Xcode 27.1 RC (only for Code 908)
install-lldb Add LLDB compatibility settings scoped to the debug copy
status Inspect patch state
restore Remove isolated copy/cache and managed LLDB config (keeps backups)
Defaults:
SOURCE_XCODE=/Applications/Xcode-26.3.0.app
NEW_XCODE=/Applications/Xcode-27.1.0.app
DEBUG_ROOT=~/Library/Developer/Xcode-iOS27-Debug
Override via environment variables where needed.
Steps:
doctor -> init -> launch -> quit Xcode -> patch-metadata -> launch/test
If Code 908: quit -> patch-extractor -> launch/test
If LLDB crashes: quit -> install-lldb -> launch/test
HELP
}
command="${1:-help}"
case "$command" in
doctor) check_source; check_new; say 'Versions and source signature verified';;
init) init;;
launch) require_file "$DEBUG_ROOT/start-xcode-debug.command"; shift; exec "$DEBUG_ROOT/start-xcode-debug.command" "$@";;
patch-metadata) patch_metadata;;
patch-extractor) patch_extractor;;
install-lldb) install_lldb;;
status) status;;
restore) restore;;
help|-h|--help) usage;;
*) usage; fail "Unknown command: $command";;
esac