週末は病院巡りとQt的なお話で少々忙しかったのでお休みしてました。
で、続き行きます。そろそろ真面目にペースを上げないと、資料作成に入れませんし。
ここまでの調査でZygote(app_process)が、socketを待ち受け、sokectに起動パラメータが来るとそれに従って自身のコピーを使ってJavaアプリの起動を行うという流れまで確認しました。
では、誰がソケットを開いて要求を投げてくるのかを探してみたいなと思っています。
まぁ、まずは待ち受けてるソケットを調べて見るのがセオリーでしょうね。
先日は待ち受けているコードは見つかりました。
private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
:
private static void registerZygoteSocket() {
:
String env = System.getenv(ANDROID_SOCKET_ENV);
fileDesc = Integer.parseInt(env);
sServerSocket = new LocalServerSocket(createFileDescriptor(fileDesc));
:
}
でも、環境変数から先がよくわかっていなかったので、とりあえず、実際に動いているエミュレータで当たってみましょう。
とりあえずadb shellでzygoteのプロセス番号をしらべます。
# ps
USER PID PPID VSIZE RSS WCHAN PC NAME
:
:
radio 32 1 5412 484 ffffffff afd0bdac S /system/bin/rild
root 33 1 63964 20588 c009b74c afd0b844 S zygote
media 34 1 17212 1364 ffffffff afd0b6fc S /system/bin/mediaserver
:
:
続いて、openされているfdを調べます。開いているsocketが2つありますね。
# ls -l /proc/33/fd
lrwx------ root root 2011-01-31 22:51 0 -> /dev/null
lrwx------ root root 2011-01-31 22:51 1 -> /dev/null
lrwx------ root root 2011-01-31 22:51 2 -> /dev/null
l-wx------ root root 2011-01-31 22:51 3 -> /dev/log/main
l-wx------ root root 2011-01-31 22:51 4 -> /dev/log/radio
l-wx------ root root 2011-01-31 22:51 5 -> /dev/log/events
lr-x------ root root 2011-01-31 22:51 6 -> /system/framework/core.jar
lr-x------ root root 2011-01-31 22:51 7 -> /system/framework/bouncycastle.jar
lr-x------ root root 2011-01-31 22:51 8 -> /dev/__properties__ (deleted)
lrwx------ root root 2011-01-31 22:51 9 -> socket:[289]
lr-x------ root root 2011-01-31 22:51 10 -> /system/framework/ext.jar
lr-x------ root root 2011-01-31 22:51 11 -> /system/framework/framework.jar
lr-x------ root root 2011-01-31 22:51 12 -> /system/framework/android.policy.jar
lr-x------ root root 2011-01-31 22:51 13 -> /system/framework/services.jar
lr-x------ root root 2011-01-31 22:51 14 -> /system/framework/core-junit.jar
lr-x------ root root 2011-01-31 22:51 15 -> /system/framework/framework.jar
lr-x------ root root 2011-01-31 22:51 16 -> /system/fonts/DroidSans.ttf
lr-x------ root root 2011-01-31 22:51 17 -> /system/framework/core.jar
lr-x------ root root 2011-01-31 22:51 18 -> /dev/urandom
lr-x------ root root 2011-01-31 22:51 19 -> /system/framework/framework-res.apk
lrwx------ root root 2011-01-31 22:51 20 -> socket:[583]
では、開いているsocketを調べてみましょう。LocalServerなんて名前からもunixかなぁと当て推量で・・・・。
# cat /proc/net/unix
Num RefCount Protocol Flags Type St Inode Path
c5924de0: 00000002 00000000 00010000 0001 01 257 /dev/socket/property_service
c5924960: 00000002 00000000 00010000 0001 01 276 /dev/socket/vold
c59247e0: 00000002 00000000 00010000 0001 01 283 /dev/socket/netd
c5924660: 00000002 00000000 00010000 0001 01 285 /dev/socket/rild-debug
c59244e0: 00000002 00000000 00010000 0001 01 287 /dev/socket/rild
c5924360: 00000002 00000000 00010000 0001 01 289 /dev/socket/zygote
c5b34800: 00000002 00000000 00010000 0001 01 319 @jdwp-control
c59241e0: 00000002 00000000 00010000 0001 01 296 /dev/socket/installd
c5924060: 00000002 00000000 00010000 0001 01 298 /dev/socket/keystore
c5b34e00: 00000002 00000000 00010000 0001 01 305 /dev/socket/qemud
c5b34c80: 00000002 00000000 00010000 0001 01 308 @android:debuggerd
:
:
c40fb3c0: 00000003 00000000 00000000 0001 03 613 /dev/socket/qemud
c40fb240: 00000003 00000000 00000000 0001 03 612
c40fb0c0: 00000002 00000000 00000000 0001 03 606
c40fbb40: 00000003 00000000 00000000 0001 03 583 /dev/socket/zygote
c40fbcc0: 00000003 00000000 00000000 0001 03 582
c40fbe40: 00000003 00000000 00000000 0001 03 564
c40716a0: 00000003 00000000 00000000 0001 03 563
c4071520: 00000003 00000000 00000000 0001 03 561
c40713a0: 00000003 00000000 00000000 0001 03 560
c4071220: 00000003 00000000 00000000 0001 03 558 /dev/socket/vold
c40710a0: 00000003 00000000 00000000 0001 03 557
c4071820: 00000003 00000000 00000000 0001 03 550 /dev/socket/netd
c40719a0: 00000003 00000000 00000000 0001 03 549
c4071b20: 00000003 00000000 00000000 0001 03 538 /dev/socket/qemud
c4071ca0: 00000003 00000000 00000000 0001 03 537
c4071e20: 00000003 00000000 00000000 0001 03 317 /dev/socket/installd
c5b34380: 00000003 00000000 00000000 0001 03 523
c5b34200: 00000003 00000000 00000000 0001 03 404 @jdwp-control
c5b34080: 00000003 00000000 00000000 0001 03 402
c5b34500: 00000003 00000000 00000000 0001 03 354 /dev/socket/qemud
c5b34680: 00000003 00000000 00000000 0001 03 353
c5b34980: 00000003 00000000 00000000 0001 03 316
c5b34b00: 00000003 00000000 00000000 0001 03 315
c5924ae0: 00000003 00000000 00000000 0001 03 260
c5924c60: 00000003 00000000 00000000 0001 03 259
どうやら、ビンゴです。
/dev/socket/zygoteでUNIXドメインソケットを開いているようですね。肝心の起動要求はというと、いちいちaccept()して読み込んで閉じるという作業を繰り返しているわけですから、エミュレータでは探れません。というわけで、ソースコードに戻ります。
とりあえず、zygoteであることはわかったので、こういう時は"で囲まれた文字列としてのzygoteをコードで検索します。
gingerbread hermit4$ source build/envsetup.sh
gingerbread hermit4$ sgrep \"zygote\"
./cts/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java:36: "zygote"
./dalvik/vm/Jni.c:4322: * only be called in "zygote" mode, when we have one thread running.
./dalvik/vm/alloc/HeapDebug.c:87: if (strcmp(buf, "zygote") != 0) {
./dalvik/vm/alloc/HeapDebug.c:88: /* If the process is no longer called "zygote",
./dalvik/vm/hprof/HprofHeap.c:264: nameId = hprofLookupStringId("zygote");
./dalvik/vm/native/dalvik_system_Zygote.c:243: dvmDumpLoaderStats("zygote");
./dalvik/vm/native/dalvik_system_Zygote.c:396: dvmDumpLoaderStats("zygote");
./frameworks/base/cmds/app_process/app_main.cpp:156: setArgv0(argv0, "zygote");
./frameworks/base/cmds/app_process/app_main.cpp:157: set_process_name("zygote");
./frameworks/base/cmds/dumpstate/utils.c:394: if (len <= 0 || !memcmp(data, "zygote", 6)) continue;
./frameworks/base/cmds/rawbu/backup.cpp:707: property_set("ctl.stop", "zygote"); ./frameworks/base/cmds/rawbu/backup.cpp:727: property_set("ctl.start", "zygote"); ./frameworks/base/core/java/android/os/Process.java:46: private static final String ZYGOTE_SOCKET = "zygote";
./frameworks/base/core/java/com/android/internal/os/SamplingProfilerIntegration.java:129: writeSnapshot(dir, "zygote");
./frameworks/base/tools/preload/PrintHtmlDiff.java:44: if (proc.name.equals("zygote")) {
./frameworks/base/tools/preload/Proc.java:79: return parent != null && parent.name.equals("zygote")
./frameworks/base/tools/preload/WritePreloadedClassFile.java:118: addAllClassesFrom("zygote", root, toPreload);
./libcore/dalvik/src/main/java/dalvik/system/Zygote.java:20: * Provides access to the Dalvik "zygote" feature, which allows a VM instance to
./system/core/libcutils/zygote.c:34:#define ZYGOTE_SOCKET "zygote"
./system/core/toolbox/start.c:15: property_set("ctl.start", "zygote");
./system/core/toolbox/stop.c:15: property_set("ctl.stop", "zygote");
すばらしく順調です。それっぽい箇所が2カ所ほど見つかりました。
一カ所は、android.os.Process ですね。
frameworks/base/core/java/android/os/Process.java
private static void openZygoteSocketIfNeeded() throws ZygoteStartFailedEx {
:
:
sZygoteSocket = new LocalSocket();
sZygoteSocket.connect(new LocalSocketAddress(ZYGOTE_SOCKET,
LocalSocketAddress.Namespace.RESERVED));
sZygoteInputStream
= new DataInputStream(sZygoteSocket.getInputStream());
sZygoteWriter =
new BufferedWriter(
new OutputStreamWriter(
sZygoteSocket.getOutputStream()),
256);
:
:
}
private static int zygoteSendArgsAndGetPid(ArrayList args) throws ZygoteStartFailedEx {
int pid;
openZygoteSocketIfNeeded();
:
:
sZygoteWriter.write(Integer.toString(args.size()));
sZygoteWriter.newLine();
int sz = args.size();
for (int i = 0; i < sz; i++) {
String arg = args.get(i);
if (arg.indexOf('\n') >= 0) {
throw new ZygoteStartFailedEx("embedded newlines not allowed");
}
sZygoteWriter.write(arg);
sZygoteWriter.newLine();
}
sZygoteWriter.flush();
// Should there be a timeout on this?
pid = sZygoteInputStream.readInt();
:
:
}
private static int startViaZygote(final String processClass,
final String niceName,
final int uid, final int gid,
final int[] gids,
int debugFlags,
String[] extraArgs) throws ZygoteStartFailedEx {
int pid;
:
pid = zygoteSendArgsAndGetPid(argsForZygote);
:
return pid;
}
public static final int start(final String processClass,
final String niceName,
int uid, int gid, int[] gids,
int debugFlags,
String[] zygoteArgs)
{
if (supportsProcesses()) {
try {
return startViaZygote(processClass, niceName, uid, gid, gids,
debugFlags, zygoteArgs);
} catch (ZygoteStartFailedEx ex) {
Log.e(LOG_TAG,
"Starting VM process through Zygote failed");
throw new RuntimeException(
"Starting VM process through Zygote failed", ex);
}
} else {
// Running in single-process mode
Runnable runnable = new Runnable() {
public void run() {
Process.invokeStaticMain(processClass);
}
};
// Thread constructors must not be called with null names (see spec).
if (niceName != null) {
new Thread(runnable, niceName).start();
} else {
new Thread(runnable).start();
}
return 0;
}
}
というわけで、一カ所は
Process.start()から始まるようです。が、適当にソースコードを探して見ましたが、呼び出していそうな箇所をまだ見つけられていません。
もう一カ所は、Nativeになります。
system/core/libcutils/zygote.c
int zygote_run_wait(int argc, const char **argv, void (*post_run_func)(int))
{
int fd;
int pid;
int err;
const char *newargv[argc + 1];
fd = socket_local_client(ZYGOTE_SOCKET,
ANDROID_SOCKET_NAMESPACE_RESERVED, AF_LOCAL);
if (fd < 0) {
return -1;
}
newargv[0] = "--peer-wait";
memcpy(newargv + 1, argv, argc * sizeof(*argv));
pid = send_request(fd, 1, argc + 1, newargv);
if (pid > 0 && post_run_func != NULL) {
post_run_func(pid);
}
// Wait for socket to close
do {
int dummy;
err = read(fd, &dummy, sizeof(dummy));
} while ((err < 0 && errno == EINTR) || err != 0);
do {
err = close(fd);
} while (err < 0 && errno == EINTR);
return 0;
}
int zygote_run_oneshot(int sendStdio, int argc, const char **argv)
{
int fd = -1;
int err;
int i;
int retries;
int pid;
const char **newargv = argv;
const int newargc = argc;
for (retries = 0; (fd < 0) && (retries < ZYGOTE_RETRY_COUNT); retries++) {
if (retries > 0) {
struct timespec ts;
memset(&ts, 0, sizeof(ts));
ts.tv_nsec = ZYGOTE_RETRY_MILLIS * 1000 * 1000;
do {
err = nanosleep (&ts, &ts);
} while (err < 0 && errno == EINTR);
}
fd = socket_local_client(ZYGOTE_SOCKET, AF_LOCAL,
ANDROID_SOCKET_NAMESPACE_RESERVED);
}
if (fd < 0) {
return -1;
}
pid = send_request(fd, 0, newargc, newargv);
do {
err = close(fd);
} while (err < 0 && errno == EINTR);
return pid;
}
というわけで、利用されているのは、zygote_run_waitとzygote_run_oneshotの2カ所でした。
zygote_run_waitが使われている箇所を調べると、
dalvik/dvz/dvz.c
int main (int argc, const char **argv) {
int err;
if (argc > 1 && 0 == strcmp(argv[1], "--help")) {
usage(argv[0]);
exit(0);
}
err = zygote_run_wait(argc - 1, argv + 1, post_run_func);
if (err < 0) {
fprintf(stderr, "%s error: no zygote process found\n", argv[0]);
exit(-1);
}
exit(0);
}
ということで、/system/bin/dvz にたどり着きました。Usageを見てみると
# dvz --help
Usage: dvz [--help] [-classpath ]
[additional zygote args] fully.qualified.java.ClassName [args]
Requests a new Dalvik VM instance to be spawned from the zygote
process. stdin, stdout, and stderr are hooked up. This process remains
while the spawned VM instance is alive and forwards some signals.
The exit code of the spawned VM instance is dropped.
というわけで、コマンドラインからクラスを指定してVMプロセスを起動するコマンドです。
もう一方のoneshotの方を探してみると
frameworks/base/cmds/runtime/main_runtime.cpp
extern "C"
int main(int argc, char* const argv[])
{
:
:
if (proc->supportsProcesses()) {
// If stdio logging is on, system_server should not inherit our stdio
// The dalvikvm instance will copy stdio to the log on its own
char propBuf[PROPERTY_VALUE_MAX];
bool logStdio = false;
property_get("log.redirect-stdio", propBuf, "");
logStdio = (strcmp(propBuf, "true") == 0);
zygote_run_oneshot((int)(!logStdio),
sizeof(ZYGOTE_ARGV) / sizeof(ZYGOTE_ARGV[0]),
ZYGOTE_ARGV);
} else {
#ifndef HAVE_ANDROID_OS
QuickRuntime* runt = new QuickRuntime();
runt->start("com/android/server/SystemServer",
false /* spontaneously fork system server from zygote */);
#endif
}
finish_system_init(proc);
run(proc);
:
:
}
ということですが、このコードをビルドするAndroid.mkを見ると
ifeq ($(TARGET_SIMULATOR),true)ということで、シミュレータ専用のようですから、とりあえず除外で良いでしょう。
というわけで、本日追いかけた限りでは、可能性があるのは
- android.os.Process.start()を呼び出すコード
- /system/bin/dvz
の辺りを攻めると、回答に近づけるかなぁ?といった雰囲気でした。