コマンドラインツールの本質
日々の開発現場では、cdやClutchなど、ターミナル上で実行するコマンドラインツールが多く使われています。そして、これらのツールの本質は、実はMach-O型の実行ファイルです。
コマンドラインツールの開発
Xcodeでプロジェクトをコンパイルし、そのプロジェクトの実行ファイルを.appパッケージで見つけることができます。
- iosプロジェクトの作成と冗長ファイルの削除
- プロジェクトをコンパイルすると、Products/.appに生成された実行ファイルがあります。
- 実行ファイルをiFunBox経由でiPhoneの/usr/binディレクトリにコピーします。
- iPhoneを接続し、コマンドラインにツール名を入力してコマンドを実行したところ、実行権限がないことが判明。
5s:~ root# CommandToolDemo
-sh: /usr/bin/CommandToolDemo: Permission denied
- コマンドラインツールへの実行可能パーミッションの追加
5s:~ root# chmod +x /usr/bin/CommandToolDemo
- コマンドを実行すると、次のようなエラーが報告されます。
5s:~ root# CommandToolDemo
-sh: /usr/bin/CommandToolDemo: Bad CPU type in executable
- 理由は、コンパイラがコンパイルする実機を選択し、iPhoneの/usr/bin/ディレクトリに再ドラッグし、実行可能な権限を割り当てていないため、コマンドラインツールは、端末で実行することができます
実行ファイルのアーキテクチャの取得
iPhone上の実行ファイルへのパスが与えられた場合、あなたが開発したコマンドラインツールを使用して、実行ファイルを読み、実行ファイルが含むアーキテクチャの種類を決定し、それを表示します。
要件の完了では、最初のステップは、MachOViewツールを使用して、Mach-Oファイルの内部構造を表示し、Mach-Oファイルの内部構造を理解することです。
Mach-Oファイルのヘッダの1行目は、現在のMach-Oファイルがどのようなバイナリファイルであるかを示しています。そして2行目は、現在のMach-Oファイルがどの種類のアーキテクチャを含んでいるかを示しています。つまり、マッハOファイルの種類を判断したい場合、マッハOファイルの最初の4バイトを読むだけで判断できるのです。
どのような条件に基づいて、Mach-Oファイルがどのアーキテクチャ・タイプに属するかを判断するのですか?xnuのソースコードを見ると、EXTERNAL_HEADERS/mach-o/fat.hとEXTERNAL_HEADERS/mach-o/loader.hにいくつかの一般的なアーキテクチャタイプに対応する定義があります。
- 上の図では、FAT_MAGICとFAT_CIGAMは一般的なバイナリ・ファイル・タイプを表しており、FAT_MAGICとFAT_CIGAMは以下の構造体によってuint32_t型であることがわかります。
- 上図はloader.hで定義されているファイルタイプで、MH_MAGICとMH_CIGAMは64ビット以外のアーキテクチャタイプ、つまりarmv7またはarmv7sタイプのアーキテクチャを表し、MH_MAGIC_64とMH_CIGAM_64は64ビットタイプのアーキテクチャ、つまりarm64アーキテクチャを表します。
ソースコードを見ると問題が見つかります。上記の型定義には2種類あり、対応する値は全く逆なのです。実はこれがビッグエンドモードとスモールエンドモードです。
ビッグエンドモード:データの上位バイトがメモリの下位アドレスに保存され、データの下位バイトがメモリの上位アドレスに保存されます。
スモール・エンド・モード:データのハイ・バイトがメモリのハイ・アドレスに、データのロー・バイトがメモリのロー・アドレスに保存されます。
このことから、データを読み出す場合、ハイ・アドレスからロー・アドレスに読み出す場合と、ロー・アドレスからハイ・アドレスに読み出す場合があり、上記のように2つの定義があります。
- まず、コマンドライン・プロジェクトを作成し、余分なファイルを削除した後、main.mに以下のコードを追加します。
#import <UIKit/UIKit.h>
#import <mach-o/loader.h>
#import <mach-o/fat.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
NSString *pathStr = @"/var/containers/Bundle/Application/19EDB123-E540-4570-90FC-50768A917A43/.app/";
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:pathStr];
NSUInteger length = sizeof(uint32_t);
//読み取り uint32_tバイト長
NSData *magicData = [handle readDataOfLength:length];
//magicNumberにデータを保存する。
uint32_t magicNumber;
[magicData getBytes:&magicNumber length:length];
if (magicNumber == FAT_MAGIC || magicNumber == FAT_CIGAM) { //汎用バイナリ
printf("Universal Binary
");
}else if (magicNumber == MH_MAGIC_64 || magicNumber == MH_CIGAM_64){//64
printf("64 bit Binary
");
}else if (magicNumber == MH_MAGIC || magicNumber == MH_CIGAM){//非64ビットアーキテクチャ
printf("32 bit Binary
");
}else{
printf("読み込みに失敗した
");
}
printf("magicNumber = 0x%x
", magicNumber);
[handle closeFile];
return 0;
}
}
- iPhoneの/usr/bin/ディレクトリに実行ファイルを置き、コマンドを実行します:
5s:~ root# CommandToolDemo
64 bit Binary
magicNumber = 0xfeedfacf
コマンドラインツールにパラメータを追加
int main(int argc, char * argv[])
argcパラメータの数を示す
argv[]ストレージパラメータ(デフォルトでは、現在のコマンドラインツールのパス"/var/containers/Bundle/Application/615972A2-4B62-4B89-A572-590B4FA2485E/CommandToolDemo "というパラメータがある).app/CommandToolDemo")
コマンドラインツールにおけるパーミッションの問題
- デフォルトでは、開発されたコマンドラインツールは、他のアプリのファイルにアクセスできません。
- 署名 -> 他のアプリの実行ファイルにアクセスできるように、特定のパーミッションで実行ファイルに署名します。
- よく使われる署名ツール:ldibとcodesign
エンタイトルメント
- パーミッション: entitlements
uhoodeMacBook-Pro:コマンドラインツール pinba$ ldid -e CommandToolDemo > CommandTool.entitlements
- パーミッションの追加とは、対応するkey-valueをplistに追加することです。
- 追加すべきパーミッションがわからない場合、システムのSpringBoardアプリケーションのパーミッションを使用することができます。
ldid -SSpringBoard.entitlements CommandToolDemo
-S コマンドの後にパーミッションファイルをスペースなしで記述する。