2012年3月16日金曜日

既存のLinux環境の上でまるごとのAndroidを動かす方法

  1. そのボード上にAndroidをポーティングする。NFSrootにするとよい。
  2. その時使ったAndroid用にpatchをあてたカーネルを使って既存のLinuxを動かす。
  3. 既存のLinuxのユーザーランドに /androidというディレクトリを作って、そこにandroidのユーザーランドのファイルを全て置く。
  4. 既存のLinuxが動いている状態でそのshellからrootで以下のコマンドを実行する。
# ls -l /android total 180 drwxr-xr-x  3 root root   4096 2010-09-08 17:00 acct drwxrwx---  3 user 2001   4096 2010-09-08 17:00 cache dr-x------  2 root root   4096 2010-09-08 17:00 config lrwxrwxrwx  1 root root     17 2010-09-08 17:00 d -> /sys/kernel/debug drwxrwx--x 13 user user   4096 2010-09-08 17:01 data -rw-r--r--  1 root root    118 2010-09-08 15:48 default.prop drwxr-xr-x  9 root root   2360 1970-01-01 09:07 dev lrwxrwxrwx  1 root root     11 2010-09-08 17:00 etc -> /system/etc -rwxr-x---  1 root root 107420 2010-09-08 15:48 init -rwxr-x---  1 root root   1677 2010-09-08 15:48 init.goldfish.rc -rwxr-x---  1 root root  13046 2010-09-08 15:48 init.rc -rwxr-x---  1 root root  13041 2010-09-08 15:48 init.rc.org drwxrwxr-x  5 root user   4096 2010-09-08 17:00 mnt dr-xr-xr-x 79 root root      0 1970-01-01 09:00 proc drwxr-x---  2 root root   4096 2010-09-08 15:48 sbin lrwxrwxrwx  1 root root     11 2010-09-08 17:00 sdcard -> /mnt/sdcard drwxr-xr-x 12 root root      0 1970-01-01 09:00 sys drwxr-xr-x 10 root root   4096 2010-08-27 10:47 system # # chroot /android /init 

既存のLinux環境の代表としてDebianとUbuntuで説明しています。

ARM Debian lennyではこれで問題なくAndoridが起動しました。つまりDebian側もAndroid側も修正無しです。

この状態で、Debian側のsshdは生きていてsshでログインが可能です。それと同時にAndroidのプロセスが動いています。Android側からSDメモリカードに読み書きすることも可能です。

pstreeコマンドでこの時のプロセスの状態を見ると以下のようになりました。


ARM Ubuntu 10.04でやったときには問題が発生して、Android側で回避していました。その詳細と対策を以下で説明します。

Android on Ubuntuで発生していた問題

前回のスライドの通り、UbuntuからchrootでAndroidを起動した場合、voldとnetdがSEGVで落ちていました。

logcatによるログは以下の通り。

I/Vold    ( 3043): Vold 2.1 (the revenge) firing up D/Vold    ( 3043): Volume sdcard state changing -1 (Initializing) -> 0 (No-Media) W/Vold    ( 3043): No UMS switch available I/DEBUG   ( 2999): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** I/DEBUG   ( 2999): Build fingerprint: 'generic/generic/generic/:2.2/FRF91/eng.koba.20100827.102349:eng/test-keys' I/DEBUG   ( 2999): pid: 3043, tid: 3044  >>> /system/bin/vold <<< I/DEBUG   ( 2999): signal 11 (SIGSEGV), fault addr 4001a000 I/DEBUG   ( 2999):  r0 4001a000  r1 ffffeea5  r2 400090ea  r3 00000001 I/DEBUG   ( 2999):  r4 4000902c  r5 aef04178  r6 000162b0  r7 000000be I/DEBUG   ( 2999):  r8 00100000  r9 aef01cad  10 10000000  fp 40009008 I/DEBUG   ( 2999):  ip 00000000  sp 100ffda8  lr aef0250f  pc aef0267c  cpsr 80000030 I/DEBUG   ( 2999): cannot get registers: I/O error I/DEBUG   ( 2999):          #00  pc 0000267c  /system/lib/libsysutils.so I/DEBUG   ( 2999):          #01  pc 0000250a  /system/lib/libsysutils.so I/DEBUG   ( 2999):          #02  pc 00001b16  /system/lib/libsysutils.so I/DEBUG   ( 2999):          #03  pc 00001cae  /system/lib/libsysutils.so I/DEBUG   ( 2999):          #04  pc 00010ec0  /system/lib/libc.so I/DEBUG   ( 2999):          #05  pc 000109b0  /system/lib/libc.so I/DEBUG   ( 2999):  

このログで以下の情報がわかります。

  • SEGVを起こしたときのPCは0xaef0267c
  • アクセスしたアドレスは 0x4001a000, 同じ値がr0に入っている。
  • (おそらく r0をベースアドレスにしたロード命令かストア命令)
  • PCのアドレスは /system/lib/libsysutil.soの中。
  • このライブラリは固定アドレスにprelinkされているので、libsysutil.soの中のオフセット 0x0000267c を調べればどの関数かわかる。
$ cd out/target/product/generic/symbols/system/lib $ nm -n -C libsysutils.so |less 
  ... 00002611 T NetlinkEvent::dump() 00002640 t $d 0000264c t $t 0000264d T NetlinkEvent::decode(char*, int) 00002794 t $d 000027bc t $t 000027bd T NetlinkEvent::~NetlinkEvent()   ... 

PCはNetlinkEvent::decode(char*, int)の中のどこかであることまでわかりました。

この関数を見ただけで怪しいところが臭ってきました。

bool NetlinkEvent::decode(char *buffer, int size) {     char *s = buffer;     char *end;     int param_idx = 0;     int i;     int first = 1;      end = s + size;     while (s < end) {         if (first) {             char *p;             for (p = s; *p != '@'; p++); // <== あれ?! これはマズいでしょう             p++;             mPath = strdup(p);             first = 0;         } else {             if (!strncmp(s, "ACTION=", strlen("ACTION="))) {                 char *a = s + strlen("ACTION=");                 if (!strcmp(a, "add"))                     mAction = NlActionAdd;                 else if (!strcmp(a, "remove"))                     mAction = NlActionRemove;                 else if (!strcmp(a, "change"))                     mAction = NlActionChange;             } else if (!strncmp(s, "SEQNUM=", strlen("SEQNUM=")))                 mSeq = atoi(s + strlen("SEQNUM="));             else if (!strncmp(s, "SUBSYSTEM=", strlen("SUBSYSTEM=")))                 mSubsystem = strdup(s + strlen("SUBSYSTEM="));             else                 mParams[param_idx++] = strdup(s);         }         s+= strlen(s) + 1;     }     return true; } 

どうやら、メッセージの先頭から'@'までを読み飛ばそうとしているようですが、その時にデータのサイズをチェックしていません! そのため、もしメッセージに'@'が含まれていない場合にはバッファを突き抜けてSEGVを引き起こします。

2010.12.13 修正

chrootでAndroidを起動するとvoldは元のLinuxのudevdと通信することになるようです。Ubuntu 10.04ではudevdのバージョンが上がっているらしく、Androidでも解釈できる形式のメッセージの後に、同じデバイスに関する情報が異なる(新しい)形式でも送られてきます。

新しい形式のメッセージには'@'が含まれていません。

新しい形式が理解できないならば、単にそれを無視すればいいように新旧両方の形式のメッセージが来るようですが、Androidではこのバグにより新しいメッセージを受けたとたんにvoldが落ちてしまいます。そしてinitがvoldを再起動して、また落ちるというのを繰り返してしまいます。

initとvoldとnetdはカーネルからのメッセージを受け取るためNETLINK_KOBJECT_UEVENTのソケットの受信を行います。通常のLinuxではudevdもこのソケットの受信を行いますが、udevdからのメッセージも同じソケットが使われます。voldとnetdはカーネルからのメッセージのみを処理してudevdからのメッセージは無視すればよいのですが、メッセージ内に必ず'@'が含まれるということを前提にしてコーディングしてしまっていたために、'@'を含まないudevdからのメッセージを受けたときにSEGVを起こしていました。

修正パッチ

system/core/libsysutils/src/NetlinkEvent.cpp

--- NetlinkEvent.cpp.org	2010-09-08 14:25:21.000000000 +0900 +++ NetlinkEvent.cpp	2010-09-08 17:49:30.000000000 +0900 @@ -67,7 +67,9 @@      while (s < end) {          if (first) {              char *p; -            for (p = s; *p != '@'; p++); +            for (p = s; *p != '@'; p++) { +                if (p >= end) return false; +            }              p++;              mPath = strdup(p);              first = 0; 

このように修正することで、voldもnetdも落ちなくなりました。両方ともこの関数を呼んでいました。

前回のスライドでは回避のためにinit.rc, MountService.java, NetworkManagementService.javaを修正しましたが、それを全部元に戻しても大丈夫です。つまり修正が必要なのはこのNetlinkEvent.cppだけです。

voldが正常に機能するようになったので、SDメモリカードの挿入を検出してmountが自動で行われます。これでAndroid側からSDメモリカードの読み書きができるようになりました。

PARTNER-Jetによるデバッグ

このようにDebian/Ubuntuからchrootで起動されたAndroidも、Android単体起動の時と同様にPARTNER-Jetでソースレベルデバッグが可能です。また、同時にDebian/Ubuntu側のプロセスもソースレベルデバッグが可能です。

(ただし製品としての提供の方法はまだ確定していません。ご興味のある方は問い合わせてください。)

0 件のコメント:

コメントを投稿