22
Dalvikのソースコードを 読んで分かったこと(1) @kishima http://silentworlds.info/ 2011/6/5 横浜AndroidPF部勉強会 (で発表する予定だったもの) 1 2011612日日曜日

Dalvik Source Code Reading

Embed Size (px)

DESCRIPTION

横浜AndroidPF部で発表しようとして、できなかったときの資料です。 http://silentworlds.info/pukiwiki/?%E3%82%BD%E3%83%BC%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89%E3%83%AA%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0

Citation preview

Page 1: Dalvik Source Code Reading

Dalvikのソースコードを読んで分かったこと(1)

@kishimahttp://silentworlds.info/

2011/6/5 横浜AndroidPF部勉強会(で発表する予定だったもの)

12011年6月12日日曜日

Page 2: Dalvik Source Code Reading

今日話すこと(★がついているもの)

★ソースツリー構成★Zygoteの起動  ・プレロード<宿題★アプリの起動  ★ソケット通信  ・fork<宿題★VMの起動  ★dexの読み込み    ★ dexopt の入り口まで★インタプリタの基本的な動き

22011年6月12日日曜日

Page 3: Dalvik Source Code Reading

ソース構成dalvik/ dalvikvm/ DalvikVMのmain()が入っている。 dexdump/ dexファイルの解析 dexlist/ ? dexopt/ dexの最適化 dexロード時に行われる docs/ ドキュメント dvz/ zygoteの制御を行うツール dx/ バイトコードをdexへ変換 hit/ ? libdex/ dex操作関連の処理(vmからコール)

libnativehelper/ ? tests/ テスト tools/ ツール類 vm/ VMの本体

frameworks/base/cmds/app_process/ が参照 Zygote(system-server)プロセスのエントリポイント

system/core/libcutils/zygote.c を参照 zygote操作用のちょっとした関数が入っている

32011年6月12日日曜日

Page 4: Dalvik Source Code Reading

ソース構成2dalvik/ vm/ alloc/ メモリ周り(確保、解放、GC) analysis/ 解析 arch/ アーキテクチャ依存 compiler/ JITコンパイラ hprof/ ? interp/ インタプリタ(実体はmterpにある) jdwp/ ? mterp/ インタプリタの実体     (アーキテクチャ向けに最適化されたアセンブラコードを含む) native/ JNIで呼ばれる関数 oo/ ? reflect/ ? test/ テスト VMの基本的な機能たち

42011年6月12日日曜日

Page 5: Dalvik Source Code Reading

Zygote起動1

init.rc

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server

52011年6月12日日曜日

Page 6: Dalvik Source Code Reading

Zygote起動2

int main(int argc, const char* const argv[]){--- // Next arg is startup classname or "--zygote" if (i < argc) { arg = argv[i++]; if (0 == strcmp("--zygote", arg)) { bool startSystemServer = (i < argc) ? strcmp(argv[i], "--start-system-server") == 0 : false; setArgv0(argv0, "zygote"); set_process_name("zygote"); runtime.start("com.android.internal.os.ZygoteInit", startSystemServer);---

frameworks/base/cmds/app_process/app_main.cpp

initから起動されたZygoteプロセスの入り口

起動処理へ

62011年6月12日日曜日

Page 7: Dalvik Source Code Reading

frameworks/base/core/jni/AndroidRuntime.cpp

/* * Start the Android runtime. This involves starting the virtual machine * and calling the "static void main(String[] args)" method in the class * named by "className". */void AndroidRuntime::start(const char* className, const bool startSystemServer){--- /* start the virtual machine */ if (startVm(&mJavaVM, &env) != 0)--- /* * Start VM. This thread becomes the main thread of the VM, and will * not return until the VM exits. */--- startClass = env->FindClass(slashClassName);--- startMeth = env->GetStaticMethodID(startClass, "main", "([Ljava/lang/String;)V");--- env->CallStaticVoidMethod(startClass, startMeth, strArray);---}

“com.android.internal.os.ZygoteInit”

Zygote起動3

VMの起動処理

JNIでJava側のmainメソッドを呼び出す

72011年6月12日日曜日

Page 8: Dalvik Source Code Reading

frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

public static void main(String argv[]) { try { VMRuntime.getRuntime().setMinimumHeapSize(5 * 1024 * 1024);

// Start profiling the zygote initialization. SamplingProfilerIntegration.start();

registerZygoteSocket(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis()); preloadClasses(); preloadResources(); EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis());

// Finish profiling the zygote initialization. SamplingProfilerIntegration.writeZygoteSnapshot();

// Do an initial gc to clean up after startup gc();

// If requested, start system server directly from Zygote if (argv.length != 2) { throw new RuntimeException(argv[0] + USAGE_STRING); }

if (argv[1].equals("true")) { startSystemServer(); } else if (!argv[1].equals("false")) { throw new RuntimeException(argv[0] + USAGE_STRING); }

Log.i(TAG, "Accepting command socket connections");

if (ZYGOTE_FORK_MODE) { runForkMode(); } else { runSelectLoopMode(); }

closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } }

AndroidRuntime.cppからJNIで呼ばれる

PreLoad

ソケットを作る

Fork?

Zygote起動4

82011年6月12日日曜日

Page 9: Dalvik Source Code Reading

アプリ起動1dvzのソースを参考にしてみる

system/core/libcutils/zygote.c

int zygote_run_wait(int argc, const char **argv, void (*post_run_func)(int)){ fd = socket_local_client(ZYGOTE_SOCKET, ANDROID_SOCKET_NAMESPACE_RESERVED, AF_LOCAL);--- // The command socket is passed to the peer as close-on-exec // and will close when the peer dies newargv[0] = "--peer-wait"; memcpy(newargv + 1, argv, argc * sizeof(*argv));

pid = send_request(fd, 1, argc + 1, newargv);---}

dalvik/dvzint main (int argc, const char **argv) {--- err = zygote_run_wait(argc - 1, argv + 1, post_run_func);

/dec/socket/zygoteのローカルソケットを開くこのソケットを介してzygoteプロセスと通信

次のページ

92011年6月12日日曜日

Page 10: Dalvik Source Code Reading

アプリ起動2static int send_request(int fd, int sendStdio, int argc, const char **argv){--- struct msghdr msg;--- ret = sendmsg(fd, &msg, MSG_NOSIGNAL);--- // replace any newlines with spaces and send the args For (i = 0; i < argc; i++) {--- toprint = argv[i];--- ivs[0].iov_base = (char *)toprint; ivs[0].iov_len = strlen(toprint); ivs[1].iov_base = (char *)newline_string; //中身は”¥n”

ivs[1].iov_len = 1;

msg.msg_iovlen = 2;

do { ret = sendmsg(fd, &msg, MSG_NOSIGNAL); } while (ret < 0 && errno == EINTR);--- // Read the pid, as a 4-byte network-order integer ivs[0].iov_base = &pid;--- ret = recvmsg(fd, &msg, MSG_NOSIGNAL | MSG_WAITALL);

sendmsgでメッセージを送ってる1.stdioディスクリプタとかを送信2.argvの文字列を送信3.PIDが応答で返ってくるのを待つ (新しくforkされたプロセスのPID

と思われる)

system/core/libcutils/zygote.c

102011年6月12日日曜日

Page 11: Dalvik Source Code Reading

int AndroidRuntime::startVm(JavaVM** pJavaVM, JNIEnv** pEnv){--- if (executionMode == kEMIntPortable) { opt.optionString = "-Xint:portable"; mOptions.add(opt); } else if (executionMode == kEMIntFast) { opt.optionString = "-Xint:fast"; mOptions.add(opt);#if defined(WITH_JIT) } else if (executionMode == kEMJitCompiler) { opt.optionString = "-Xint:jit"; mOptions.add(opt);#endif--- /* * Initialize the VM. * * The JavaVM* is essentially per-process, and the JNIEnv* is per-thread. * If this call succeeds, the VM is ready, and we can start issuing * JNI calls. */ if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {---}

frameworks/base/core/jni/AndroidRuntime.cpp

JIT使用の設定他にも色々オプションを設定しているヒープサイズ設定とか。

VMのインスタンス生成

VMの起動1

112011年6月12日日曜日

Page 12: Dalvik Source Code Reading

dalvik/vm/Jni.cjint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args){ /* * Set up structures for JNIEnv and VM. */ pVM = (JavaVMExt*) malloc(sizeof(JavaVMExt));

memset(pVM, 0, sizeof(JavaVMExt)); pVM->funcTable = &gInvokeInterface; pVM->envList = pEnv;

--- /* set this up before initializing VM, so it can create some JNIEnvs */ gDvm.vmList = (JavaVM*) pVM;

/* * Create an env for main thread. We need to have something set up * here because some of the class initialization we do when starting * up the VM will call into native code. */ pEnv = (JNIEnvExt*) dvmCreateJNIEnv(NULL);--- /* initialize VM */ gDvm.initializing = true; if (dvmStartup(argc, argv, args->ignoreUnrecognized, (JNIEnv*)pEnv) != 0) { --- }--- *p_env = (JNIEnv*) pEnv; *p_vm = (JavaVM*) pVM;---}

VMのインスタンス生成

VMがリストで管理されてる※複数も想定してる?現在は想定してないとコメントがどこかにあった

初期化でJNI使うので準備する

VMの起動2

122011年6月12日日曜日

Page 13: Dalvik Source Code Reading

VMの構造体

JavaVMlibnativehelper/include/nativehelper/jni.hstruct JNIInvokeInterface{ jint (*DestroyJavaVM)(JavaVM*); jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); jint (*DetachCurrentThread)(JavaVM*); jint (*GetEnv)(JavaVM*, void**, jint); jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*);}

JNIEnvlibnativehelper/include/nativehelper/jni.h struct JNINativeInterface{} VMへのアクセス関数が大量に

VMの本体

JNIのための関数ポインタが大量に登録されてる

132011年6月12日日曜日

Page 14: Dalvik Source Code Reading

VMの構造体2struct JavaVMExt;

typedef struct JNIEnvExt { const struct JNINativeInterface* funcTable; /* must be first */

const struct JNINativeInterface* baseFuncTable;

/* pointer to the VM we are a part of */ struct JavaVMExt* vm;

u4 envThreadId; Thread* self;

/* if nonzero, we are in a "critical" JNI call */ int critical;

/* keep a copy of this here for speed */ bool forceDataCopy;

struct JNIEnvExt* prev; struct JNIEnvExt* next;} JNIEnvExt;

typedef struct JavaVMExt { const struct JNIInvokeInterface* funcTable; /* must be first */

const struct JNIInvokeInterface* baseFuncTable;

/* if multiple VMs are desired, add doubly-linked list stuff here */

/* per-VM feature flags */ bool useChecked; bool warnError; bool forceDataCopy;

/* head of list of JNIEnvs associated with this VM */ JNIEnvExt* envList; pthread_mutex_t envListLock;} JavaVMExt;

142011年6月12日日曜日

Page 15: Dalvik Source Code Reading

dex読み込み1

/* * Open a Jar file. It's okay if it's just a Zip archive without all of * the Jar trimmings, but we do insist on finding "classes.dex" inside * or an appropriately-named ".odex" file alongside. * * If "isBootstrap" is not set, the optimizer/verifier regards this DEX as * being part of a different class loader. */int dvmJarFileOpen(const char* fileName, const char* odexOutputName, JarFile** ppJarFile, bool isBootstrap){--- /* First, look for a ".odex" alongside the jar file. It will * have the same name/path except for the extension. */ fd = openAlternateSuffix(fileName, "odex", O_RDONLY, &cachedName);--- /* * Pre-created .odex absent or stale. Look inside the jar for a * "classes.dex". */ entry = dexZipFindEntry(&archive, kDexInJarName);

dalvik/vm/JarFile.c

classes.dexまたは.odexのファイルを開こうとする

まずは.odexを開こうとする.odexが開けたら、その先頭ポインタを返却して終了。

もしなかったらclasses.dexを開く

152011年6月12日日曜日

Page 16: Dalvik Source Code Reading

/* * We've found the one we want. See if there's an up-to-date copy * in the cache. * * On return, "fd" will be seeked just past the "opt" header. * * If a stale .odex file is present and classes.dex exists in * the archive, this will *not* return an fd pointing to the * .odex file; the fd will point into dalvik-cache like any * other jar. */ if (odexOutputName == NULL) { cachedName = dexOptGenerateCacheFileName(fileName, kDexInJarName);--- /* * If fd points to a new file (because there was no cached version, * or the cached version was stale), generate the optimized DEX. * The file descriptor returned is still locked, and is positioned * just past the optimization header. */ if (newFile) {--- result = dvmOptimizeDexFile(fd, dexOffset, dexGetZipEntryUncompLen(&archive, entry), fileName, dexGetZipEntryModTime(&archive, entry), dexGetZipEntryCrc32(&archive, entry), isBootstrap);---

dex読み込み2classes.dexが見つかると、キャッシュから最新を探す。

(戻るときには、fdはoptヘッダを超えた位置を指している)

(古くなったodexがあり、かつclasses.dexがある場合は、fdはodexを指さず、他のjarのようにキャッシュを指す)

キャッシュされたものが無い、もしくは古くなってる場合、最適化されたDEXを生成する。(これがodex?)

odex生成。後述。

dalvik/vm/JarFile.c

162011年6月12日日曜日

Page 17: Dalvik Source Code Reading

--- /* * Map the cached version. This immediately rewinds the fd, so it * doesn't have to be seeked anywhere in particular. */ if (dvmDexFileOpenFromFd(fd, &pDvmDex) != 0) {---

*ppJarFile = (JarFile*) calloc(1, sizeof(JarFile)); (*ppJarFile)->archive = archive; (*ppJarFile)->cacheFileName = cachedName; (*ppJarFile)->pDvmDex = pDvmDex; ---(読み込み完了)

}

dex読み込み3

キャッシュされたものをmmapする。seekする必要は特にない

dalvik/libdex/SysUtil.c

fdの現在位置から終端までを書き込み可能な読み込み専用領域(writable read-only)として 、pDvmDexにmmapしてるメモリ節約の意図がある

mmapのオプション: prot=PROT_READ | PROT_WRITE

 flags=MAP_FILE | MAP_PRIVATE (copy-on-write)

mprotectのオプション: PROT_READ

dalvik/DvmDex.c dvmDexFileOpenFromFd(){ ・ファイル展開 ・ヘッダを読み飛ばす ・チェックサムの確認 ・SHA-1署名のチェック(最初だけ) ・クラスやメソッドのリファレンス領域を確保}

dalvik/vm/JarFile.c

172011年6月12日日曜日

Page 18: Dalvik Source Code Reading

dexの最適化1/* * Given a descriptor for a file with DEX data in it, produce an * optimized version. * * The file pointed to by "fd" is expected to be a locked shared resource * (or private); we make no efforts to enforce multi-process correctness * here. * * "fileName" is only used for debug output. "modWhen" and "crc" are stored * in the dependency set. * * The "isBootstrap" flag determines how the optimizer and verifier handle * package-scope access checks. When optimizing, we only load the bootstrap * class DEX files and the target DEX, so the flag determines whether the * target DEX classes are given a (synthetic) non-NULL classLoader pointer. * This only really matters if the target DEX contains classes that claim to * be in the same package as bootstrap classes. * * The optimizer will need to load every class in the target DEX file. * This is generally undesirable, so we start a subprocess to do the * work and wait for it to complete. * * Returns "true" on success. All data will have been written to "fd". */bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength, const char* fileName, u4 modWhen, u4 crc, bool isBootstrap)

dalvik/vm/analysis/DexPrepare.cfdはロックされた共有メモリなんでマルチプロセスのことは気にしなくてOK。

最適化するために、dexファイルの全てのクラスを読み込む。それはあんまり好ましくないので、作業のためにサブプロセスを起こし、完了するまで待つことにする。完了した時点で、全てのデータはfd

に書き込まれている。

※なんでだろう?

bootstrapフラグ ※あんまり理解できてません

182011年6月12日日曜日

Page 19: Dalvik Source Code Reading

dexの最適化2

bool dvmOptimizeDexFile(int fd, off_t dexOffset, long dexLength, const char* fileName, u4 modWhen, u4 crc, bool isBootstrap){--- pid = fork(); if (pid == 0) { static const int kUseValgrind = 0; static const char* kDexOptBin = "/bin/dexopt"; static const char* kValgrinder = "/usr/bin/valgrind";--- strcpy(execFile, androidRoot); strcat(execFile, kDexOptBin);--- if (kUseValgrind) execv(kValgrinder, argv); else execv(execFile, argv);--- } else {--- /* * Wait for the optimization process to finish. We go into VMWAIT * mode here so GC suspension won't have to wait for us. */ oldStatus = dvmChangeStatus(NULL, THREAD_VMWAIT);--- }}

dalvik/vm/analysis/DexPrepare.c

引数の準備をして、dexoptを、execv()する詳しくはdexoptにて

処理が終わるまで待つVMをVMWAITモードにする。※GCを動かせるようにしておくって意味?

※forkされた側

※forkした側

192011年6月12日日曜日

Page 20: Dalvik Source Code Reading

インタプリタの基本的な動き1(ソース構成から)dalvik/ vm/ mterp/ インタプリタの実体  gen-mterp.py ソースコード生成スクリプト  config-xxx アーキテクチャxxxの設定  config-yyy アーキテクチャyyyの設定  ・・・  config-portstd アーキテクチャ非依存の設定ファイル  rebuild.sh ビルドスクリプト  xxx/ アーキテクチャxxxのコード(xxxのアセンブリで書かれている)  yyy/ アーキテクチャyyyのコード(yyyのアセンブリで書かれている)  c/ アーキテクチャ非依存のコード(Cで書かれている)  out/ 生成されたソースコードの出力先  ・・・  

まずは、outに出力されたコードを読んでみます

各アーキテクチャ向けに数百ある大量のOPコード実装を効率よく管理するために、スクリプトによるソースコードの半自動生成を行っている

202011年6月12日日曜日

Page 21: Dalvik Source Code Reading

インタプリタの基本的な動き2dalvik/vm/mterp/out/InterpC-portstd.c

/* File: portable/entry.c */ * Main interpreter loop.bool INTERP_FUNC_NAME(Thread* self, InterpState* interpState){--- /* copy state in */ curMethod = interpState->method; pc = interpState->pc; fp = interpState->fp;--- methodClassDex = curMethod->clazz->pDvmDex;--- while (1) {--- /* fetch the next 16 bits from the instruction stream */ inst = FETCH(0);

switch (INST_INST(inst)) {---/* File: c/OP_NOP.c */HANDLE_OPCODE(OP_NOP) FINISH(1);OP_END---・・・---}

インタプリタの実体(汎用C版の場合)ところどころマクロ魔術が使われているので注意

#define FETCH(_offset) (pc[(_offset)])#define INST_INST(_inst) ((_inst) & 0xff)# define HANDLE_OPCODE(_op) case _op:# define ADJUST_PC(_offset) do { \ pc += _offset; \ EXPORT_EXTRA_PC(); \ } while (false)# define FINISH(_offset) { ADJUST_PC(_offset); break; }

1OPコード:1ファイルで実装されたソースがここに展開されて並ぶ1つのOPコードが1つのcaseに相当する

マクロを駆使してでサブルーチン的な実装を行っていたりする(メソッド呼び出しとか。JNI調査の入り口)

PCをいくつ進めるかを示している

PC(プログラムカウンタ)の位置を起点に、命令(instruction)

を取り出す

例外とか起きるまで無限ループ

212011年6月12日日曜日

Page 22: Dalvik Source Code Reading

(欲張りな) 今後調べること

・プレロードのあたり・Zygoteの実際にforkしているあたり・dexoptの中身・VM起動->fork->dex読み込み->インタプリタで実行  の流れ・インタプリタのもっと細かいところ・JNI

・JIT成果は↓でまとめていきます

http://silentworlds.info/pukiwiki/

222011年6月12日日曜日