共有ライブラリを差し替える方法
Mavericksになって、AirMac ユーティリティ 5.6が起動しなくなってしまった...。AirMac ユーティリティ 6.3はちゃんと起動するのだけど、5.6でないと、できない設定がある。非常に困る...。
- 例えば、「時刻を自動的に設定」や「送信出力」「ログと統計情報」などの設定が、AirMac ユーティリティ 6.3には存在しないのだ。
なぜ起動しないのか?
- TimeMachineのバックアップから、Mavericks環境にAirMac ユーティリティ5.6.1を復元してみる。
- それをダブルクリックして起動してみると「このバージョンのAirMac ユーティリティ.appは、このバージョンのOSXでは使用できません。」と警告される。起動しない...。
- どうして起動しないのだろう?この警告だけでは何の手掛かりもない。
- パッケージの内容を表示して、Contents/MacOS/AirPort Utilityという実行ファイルをダブルクリックしてみた。
- すると、もう少し丁寧な言い回しで似たような意味の警告が表示され、ターミナルが起動した。
- ターミナルの出力内容を見ると、一抹の手掛かりが見つかる。
$ /Applications/Utilities/AirMac ユーティリティ5.6.1/AirPort\ Utility.app/Contents/MacOS/AirPort\ Utility ; exit; dyld: Symbol not found: _wlScanAuthenticationModesArray Referenced from: /Applications/Utilities/AirMac ユーティリティ5.6.1/AirPort Utility.app/Contents/MacOS/AirPort Utility Expected in: /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Apple80211 in /Applications/Utilities/AirMac ユーティリティ5.6.1/AirPort Utility.app/Contents/MacOS/AirPort Utility Trace/BPT trap: 5 logout [プロセスが完了しました]
- どうやら、Mavericksの/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Apple80211というファイルに、何らかの問題があるようだ。
- そこで、Mountain Lionから上記ファイルApple80211を復元して、差し替えてみた。
- その後、もう一度AirMac ユーティリティ 5.6.1を実行してみると、見事に起動した!
- Apple80211が何かと言えば、dynamically linked shared library(ダイナミック リンク ライブラリ)なのである。
$ cd /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A $ file Apple80211 Apple80211: Mach-O universal binary with 2 architectures Apple80211 (for architecture x86_64): Mach-O 64-bit dynamically linked shared library x86_64 Apple80211 (for architecture i386): Mach-O dynamically linked shared library i386
- ライブラリとは、複数のアプリから利用されるサブルーチン(関数)の集合である。
- ダイナミックリンクとは、アプリ実行時に必要なライブラリと接続する機能のこと。
- そのダイナミック リンク ライブラリがどのように動作するのかについては、以下の記事がとっても詳しい。
つまり、
- AirMac ユーティリティ5.6.1をダブルクリックすると、/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Apple80211が物理メモリ空間にロードされる。
- 物理メモリにロードされたApple80211を、AirMac ユーティリティ5.6.1の仮想メモリ空間に割り当て、Apple80211を呼び出す時のアドレスを保持しておく。
- AirMac ユーティリティ5.6.1の処理が進んで、Apple80211が呼び出される。
- この時、Mavericks版のApple80211は「_wlScanAuthenticationModesArray」という機能(シンボル)が存在しないため、実行できないようだ。
- 一方、Mountain Lion版のApple80211には、それがちゃんと含まれている。だから実行できる。
AirMac ユーティリティ5.6.1の実行には、「_wlScanAuthenticationModesArray」が必須なのだ。
もっと賢くダイナミック リンク ライブラリを差し替える方法
- 上記では、Mountain Lion版の/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Apple80211に置き換えてしまったが、これは一番乱暴な方法である。
あまり良くない...。いや、とっても良くない!
- /System/以下はOSXを構成するシステムファイルなのに、安易に書き換えるとOSに不具合を招く可能性がある。
- 今回、たまたまAirMac ユーティリティ5.6.1や6.3.2のどちらも起動したが、
- 場合によっては、新しい6.3.2が起動しなくなったり、Apple80211を利用するその他のアプリが使えなくなる可能性もあった。
- また、その後さらにOSのバージョンが上がってもMountain Lion版のApple80211を使っていると、いずれどこかで何らかの機能に悪影響が出ると予想される。
- AirMac ユーティリティ5.6.1だけのために、システムファイイルをいつまでもMountain Lion版のApple80211に差し替えておきたくないのだ。
- システムファイルをMountain Lion版のApple80211に書き換えたくはないけど、それを必要とするAirMac ユーティリティ5.6.1を起動したい。
この相反する欲求を、一体、どうすべきか?
- 実は、システムファイルを書き換えずにMountain Lion版のApple80211を利用して、AirMac ユーティリティ5.6.1を起動する、という欲張りな方法があるのだ。
論より証拠、とにかくやってみる。
- まず、AirMac ユーティリティ5.6.1とMountain Lion版のApple80211を適当にどこかに置く。(ちなみに、同じディレクトリである必要はない)
- 例:~/Downloads/AirMac ユーティリティ5.6.1
- 例:~/Downloads/Apple80211
- DYLD_INSERT_LIBRARIESに~/Downloads/Apple80211を設定して、AirMac ユーティリティ5.6.1(Contents/MacOS/AirPort Utility)を実行。
$ DYLD_INSERT_LIBRARIES=~/Downloads/Apple80211 ~/Downloads/'AirPort Utility.app'/Contents/MacOS/'AirPort Utility'
- すると、AirMac ユーティリティ5.6.1は見事に起動する!こんな感じ。
- バージョンアップをお勧めされるけど、キャンセルを押して起動完了。
何が起こっているのか?
- あまり馴染みがなかったのだけど、スペース区切りによって、いくつか変数定義して、続けてコマンドを指定することで、
- 指定したコマンドを実行する時だけ有効な、環境変数の設定と認識される。(envコマンドが省略された書式?)
$ DYLD_INSERT_LIBRARIES=~/Downloads/Apple80211 ~/Downloads/'AirPort Utility.app'/Contents/MacOS/'AirPort Utility'
- よって上記書式は、AirPort Utility.appの実行環境だけで有効な、export DYLD_INSERT_LIBRARIES=~/Downloads/Apple80211しているのと同義である。
- printenvで環境変数を確認してみると、その挙動を体感できる。
# 「変数定義 コマンド」の書式では、DYLD_INSERT_LIBRARIESが設定されている。 $ DYLD_INSERT_LIBRARIES=~/Downloads/Apple80211 printenv DYLD_INSERT_LIBRARIES=/Users/zari/Downloads/Apple80211 TERM_PROGRAM=Apple_Terminal SHELL=/bin/bash ...中略... # コマンドが終了すると、DYLD_INSERT_LIBRARIESという環境変数は存在しない。 $ printenv TERM_PROGRAM=Apple_Terminal SHELL=/bin/bash ...中略...
- 環境変数DYLD_INSERT_LIBRARIESにライブラリへのパスが設定されると、OSX標準のライブラリより優先してロードされる。
- この素晴らしい仕組みによって、いちいちOSXのシステムファイルを書き換えずに、アプリごとに独自の処理を追加できるのだ。
すごくワクワクする仕組みである!
AirMac ユーティリティ5.6.1を起動するAppleScript
- 毎回DYLD_INSERT_LIBRARIESを設定して、コマンドラインからAirMac ユーティリティ5.6.1を起動するのは非常に面倒である。
- いつものお決まりのAppleScriptを使って、素早くAirMac ユーティリティ5.6.1を起動するスクリプトに仕上げることにした。
--すでにAirMac ユーティリティが起動している時は、アクティブにするだけ
tell application "System Events"
if processes's name contains "AirPort Utility" then
tell process "AirPort Utility"
set frontmost to true
return
end tell
end if
end tell
--AirMac ユーティリティ 5.6.1対応のApple80211ライブラリを使って、起動する
set dylib to quoted form of (path to resource "Apple80211")'s POSIX path
set old_app to quoted form of (path to resource "AirPort Utility.app")'s POSIX path
do shell script "DYLD_INSERT_LIBRARIES=" & dylib & space & old_app & "Contents/MacOS/'AirPort Utility' >& /dev/null &"
--AirMac ユーティリティをアクティブにして、escキーを押す
delay 1
tell application "System Events"
tell process "AirPort Utility"
set frontmost to true
--delay 2
--key code 53 --esc
repeat 10 times
if exists (window 1's sheet 1) then
key code 53 --esc
return
end if
delay 0.5
end repeat
end tell
end tell
- 上記AppleScriptをアプリケーション形式で保存して、Contents/Resources/以下に二つのファイルを追加して出来上がり。
- AirMac ユーティリティ5.6.1.app
- Mountain Lion版の/System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Apple80211
- GUIスクリプティングを許可するため、以下の設定をオンにしておく必要がある。
- システム環境設定 >> セキュリティとプライバシー >> プライバシー >> アクセシビリティ >> AirMac ユーティリティ5.6.1enabler.app(このアプレットの名前)
ダブルクリックでAirMac ユーティリティ5.6.1が起動するようになった!
参考ページ
とってもワクワクする情報に感謝です!
- LD_PRELOAD を Mac で - EAGLE 雑記
- Overriding library functions in Mac OS X, the easy way: DYLD_INSERT_LIBRARIES | tlrobinson.net blog
- Mac OSで関数をフックする方法
ミニマムなC言語のサンプルを作って、実際にやってみると面白い!(Mac OSで関数をフックする方法)
main.c
#include <stdio.h> int main() { char * s = "hello world!!"; printf(s, ""); return 0; }
printf.c
#include <dlfcn.h> #include <stdio.h> int printf(const char * _format, ...) { typedef int (*ftype)(const char *, ...); return ((ftype)dlsym(RTLD_NEXT, "printf"))("%s %s", "override printf", _format); }
手順
$ gcc main.c $ ./a.out hello world!! $ gcc -ldl -dynamiclib printf.c -o printf.dylib $ DYLD_INSERT_LIBRARIES=printf.dylib DYLD_FORCE_FLAT_NAMESPACE=YES ./a.out override printf hello world!!
printfがオーバーライドできた!
DYLD_FORCE_FLAT_NAMESPACEについて
- DYLD_FORCE_FLAT_NAMESPACEについては、YESでも、NOでも、変数定義さえできていればOKらしい。
$ DYLD_INSERT_LIBRARIES=printf.dylib DYLD_FORCE_FLAT_NAMESPACE=NO ./a.out override printf hello world!! $ DYLD_INSERT_LIBRARIES=printf.dylib DYLD_FORCE_FLAT_NAMESPACE= ./a.out override printf hello world!!
- DYLD_FORCE_FLAT_NAMESPACEが変数定義されてないと、オーバーライドできない...。
$ DYLD_INSERT_LIBRARIES=printf.dylib ./a.out hello world!!
$ gcc -flat_namespace main.c $ gcc -ldl -dynamiclib printf.c -o printf.dylib $ DYLD_INSERT_LIBRARIES=printf.dylib ./a.out override printf hello world!!
余談(ワイヤレスネットワーク拡張した時のネットワーク切断問題のその後)
- ちなみにTime Capsule側の設定で、AirMac >> Time Capsule >> 時刻を自動的に設定=オフにして、
- AirMac Express側の設定で、AirMac >> ベースステーション >> 時刻を自動的に設定=オンにすることで、
- ワイヤレスネットワーク拡張した時の、ネットワークが不定期かつ頻繁に切断する問題が解消したような気がする。
- 以前との設定の違いは、「時刻を自動的に設定」ぐらいしかないので。
- しかし、いろいろ設定を弄っているうちに、いつの間にか切断しない状態になっていたので、その他の要因も考えられる。
- とにかく、AirMac Expressを再起動しないと接続できなくなる症状は、この3ヶ月以上発生していない、というのは事実である。
- 以前は日々、ワイヤレスネットワークが切断して、不便な思いをしていたのに、
- 今は電子レンジを盛大に使っても、ちゃんと接続しているし、通信もできている。