0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【自己責任】Xcode 26.3 で iOS 27.1 の実機デバッグを可能にする【開発・検証専用】

0
Posted at

はじめに

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

最終的に、「アプリ管理」が許可されているターミナルアプリから実行したところ、置換に成功しました。

パッチ適用前に、以下を確認することを推奨します。

  1. macOSの「システム設定」を開く。
  2. 「プライバシーとセキュリティ」→「アプリ管理」を開く。
  3. スクリプトを実行するターミナルアプリの権限を有効にする。
  4. ターミナルアプリを再起動する。

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実機をデバッグするための回避策として参考になれば幸いです。

参考リンク

付録

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
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?