共有ライブラリを差し替える方法

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を実行してみると、見事に起動した!
$ 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 ユーティリティ5.6.1を起動した時のいくつかの不便も解消してみた。
    • 複数のAirMac ユーティリティ5.6.1が起動しないようにする処理(コマンドラインから起動すると複数のAirMac ユーティリティ プロセスが起動してしまうので)
    • 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



ダブルクリックでAirMac ユーティリティ5.6.1が起動するようになった!

参考ページ

とってもワクワクする情報に感謝です!

ミニマムな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!!
  • -flat_namespaceオプション付きでコンパイルした実行ファイルは、DYLD_FORCE_FLAT_NAMESPACEの変数定義なしでも、オーバーライドできる。
$ 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ヶ月以上発生していない、というのは事実である。
    • 以前は日々、ワイヤレスネットワークが切断して、不便な思いをしていたのに、
    • 今は電子レンジを盛大に使っても、ちゃんと接続しているし、通信もできている。