1. 程式人生 > >Android Binder異常傳遞流程分析

Android Binder異常傳遞流程分析

從一個異常日誌開始


作為Android程式設計師,經常會遇到如下的異常日誌:

AndroidRuntime: java.lang.RuntimeException: Unable to start activity ComponentInfo{com.qiku.activitylifecycletest/com.qiku.activitylifecycletest.MainActivity}: java.lang.SecurityException: Permission Denial: killBackgroundProcesses() from pid=4752, uid=10125 requires android.
permission.KILL_BACKGROUND_PROCESSES AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2808) AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2886) AndroidRuntime: at android.app.ActivityThread.-wrap11(Unknown Source:
0) AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1603) AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:106) AndroidRuntime: at android.os.Looper.loop(Looper.java:164) AndroidRuntime:
at android.app.ActivityThread.main(ActivityThread.java:6538) AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method) AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:453) AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:833) AndroidRuntime: Caused by: java.lang.SecurityException: Permission Denial: killBackgroundProcesses() from pid=4752, uid=10125 requires android.permission.KILL_BACKGROUND_PROCESSES AndroidRuntime: at android.os.Parcel.readException(Parcel.java:2004) AndroidRuntime: at android.os.Parcel.readException(Parcel.java:1950) AndroidRuntime: at android.app.IActivityManager$Stub$Proxy.killBackgroundProcesses(IActivityManager.java:6426) AndroidRuntime: at android.app.ActivityManager.killBackgroundProcesses(ActivityManager.java:3739) AndroidRuntime: at com.qiku.activitylifecycletest.MainActivity.onCreate(MainActivity.java:36) AndroidRuntime: at android.app.Activity.performCreate(Activity.java:7000) AndroidRuntime: at android.app.Activity.performCreate(Activity.java:6991) AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1215) AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2761)

導致該異常的程式碼如下:

    ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
    am.killBackgroundProcesses("com.tencent.mm");

該異常會導致app crash。關鍵資訊如下

Caused by: java.lang.SecurityException: Permission Denial:killBackgroundProcesses() from pid=4752, uid=10125 requires android.permission.KILL_BACKGROUND_PROCESSES

意思就是當前程序呼叫ActivityManager的killBackgroundProcesses,而沒有android.permission.KILL_BACKGROUND_PROCESSES這個許可權,所以導致crash。



到底在哪裡丟擲的異常


我們想找到這個拋異常的地方,然後就在aosp的原始碼中尋找。因為呼叫的是ActivityManager的方法,所以,憑藉經驗,我們應該知道,呼叫killBackgroundProcesses這個方法,會通過binder調到ActivityManagerService中,並且只有系統服務才能鑑定app是否有許可權。所以,在ActivityManagerServicekillBackgroundProcesses方法中,找到了拋異常的地方。

@Override
    public void killBackgroundProcesses(final String packageName, int userId) {
        if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
                != PackageManager.PERMISSION_GRANTED &&
                checkCallingPermission(android.Manifest.permission.RESTART_PACKAGES)
                        != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: killBackgroundProcesses() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }

        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
                userId, true, ALLOW_FULL_ONLY, "killBackgroundProcesses", null);

那麼問題來了:

拋異常的位置位於ActivityManagerService中,是執行在system_server程序中的,
為什麼system_server程序沒有崩潰,反而導致app程序崩潰呢?



App中呼叫棧分析


我們再仔細分析一下app崩潰時的呼叫棧。
AndroidRuntime: Caused by: java.lang.SecurityException: Permission Denial: killBackgroundProcesses() from pid=4752, uid=10125 requires android.permission.KILL_BACKGROUND_PROCESSES
AndroidRuntime:     at android.os.Parcel.readException(Parcel.java:2004)                            
AndroidRuntime:     at android.os.Parcel.readException(Parcel.java:1950)                            
AndroidRuntime:     at android.app.IActivityManager$Stub$Proxy.killBackgroundProcesses(IActivityManager.java:6426)
AndroidRuntime:     at android.app.ActivityManager.killBackgroundProcesses(ActivityManager.java:3739)
AndroidRuntime:     at com.qiku.activitylifecycletest.MainActivity.onCreate(MainActivity.java:36) 

可以看到在ActivityonCreate中,呼叫了ActivityManager.killBackgroundProcesses
ActivityManager.killBackgroundProcesses又呼叫了```killBackgroundProcesses`

根據Android中實現java層的binder呼叫的套路,IActivityManager是一個aidl檔案,該檔案經過aidl處理,生成IAcitivityManager.java檔案,IAcitivityManager.java檔案中存在一個IActivityManager介面,定義了app可以呼叫ams的方法,IActivityManager中有一個抽象類Stub,該Stub類實現IActivityManager介面,並繼承Binder:

public interface IActivityManager extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements android.app.IActivityManager {
        private static final java.lang.String DESCRIPTOR = "android.app.IActivityManager";

並且Stub中又有一個叫做Proxy的內部類,該Proxy實現IActivityManager

       private static class Proxy implements android.app.IActivityManager {
            private android.os.IBinder mRemote;

Proxy用於app端使用,當app端調ActivityManager的方法的時候,就會調到IActivityManager$Stub$Proxy

我們看一下IActivityManager$Stub$ProxykillBackgroundProcesses方法:

            public void killBackgroundProcesses(java.lang.String packageName, int userId) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(packageName);
                    _data.writeInt(userId);
                    mRemote.transact(Stub.TRANSACTION_killBackgroundProcesses, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }

mRemote是一個IBinder物件,專門用來呼叫遠端程序的。這個方法中把所有的引數收集到一個名為_dataParcel物件中,並且建立一個叫_reply的空Parcel物件,然後呼叫mRemote.transact進行binder呼叫。呼叫完這個方法後,app中的當前執行緒就暫停了,然後ActivityManagerService開始執行,等ActivityManagerService執行完之後,app中的當前執行緒繼續執行,這時候_reply裡面存放的就是ActivityManagerService的執行結果。

mRemote.transact之後,繼續呼叫_reply.readException,app就是從這裡崩潰的:

AndroidRuntime: Caused by: java.lang.SecurityException: Permission Denial: killBackgroundProcesses() from pid=4752, uid=10125 requires android.permission.KILL_BACKGROUND_PROCESSES
AndroidRuntime:     at android.os.Parcel.readException(Parcel.java:2004)                            
AndroidRuntime:     at android.os.Parcel.readException(Parcel.java:1950)   

我們看一下ParcelreadException實現:

    public final void readException() {
        int code = readExceptionCode();
        if (code != 0) {
            String msg = readString();
            readException(code, msg);
        }
    }
    public final int readExceptionCode() {
        int code = readInt();
        if (code == EX_HAS_REPLY_HEADER) {
            int headerSize = readInt();
            if (headerSize == 0) {
                Log.e(TAG, "Unexpected zero-sized Parcel reply header.");
            } else {
                // Currently the only thing in the header is StrictMode stacks,
                // but discussions around event/RPC tracing suggest we might
                // put that here too.  If so, switch on sub-header tags here.
                // But for now, just parse out the StrictMode stuff.
                StrictMode.readAndHandleBinderCallViolations(this);
            }
            // And fat response headers are currently only used when
            // there are no exceptions, so return no error:
            return 0;
        }
        return code;
    }

public final void readException(int code, String msg) {
        switch (code) {
            case EX_PARCELABLE:
                if (readInt() > 0) {
                    SneakyThrow.sneakyThrow(
                            (Exception) readParcelable(Parcelable.class.getClassLoader()));
                } else {
                    throw new RuntimeException(msg + " [missing Parcelable]");
                }
            case EX_SECURITY:
                throw new SecurityException(msg);
            case EX_BAD_PARCELABLE:
                throw new BadParcelableException(msg);
            case EX_ILLEGAL_ARGUMENT:
                throw new IllegalArgumentException(msg);
            case EX_NULL_POINTER:
                throw new NullPointerException(msg);
            case EX_ILLEGAL_STATE:
                throw new IllegalStateException(msg);
            case EX_NETWORK_MAIN_THREAD:
                throw new NetworkOnMainThreadException();
            case EX_UNSUPPORTED_OPERATION:
                throw new UnsupportedOperationException(msg);
            case EX_SERVICE_SPECIFIC:
                throw new ServiceSpecificException(readInt(), msg);
        }
        throw new RuntimeException("Unknown exception code: " + code
                + " msg " + msg);
    }

可以看到,其實是從Parcel中讀了一個code,讀了一個msg字串,然後根據code和msg丟擲了對應的異常。

所以我們可以認為,ActivityManagerService在丟擲異常後,把異常的資訊(code和msg)放到了Parcel物件中,傳遞到了app程序中,app程序讀到ActivityManagerService中傳過來的異常資訊後,又構建了對應的Exception物件,然後throw這個Exception物件,導致app程序崩潰。

所以總結一下,異常是可以通過binder來傳遞的,只是不是直接傳遞,而是傳遞異常型別(code)和異常相關的描述資訊(msg)。binder呼叫端會解析這些資訊建立和丟擲異常。



ActivityManagerService中異常處理分析


下面我們看一下ActivityManagerService端是怎樣傳遞這個異常的。我們需要了解一些java服務端Binder的執行流程。

binder是Android中的程序間通訊方式,它是有核心中的binder驅動支援的。客戶端呼叫binder,會從java層一直通過jni呼叫到native層,然後調到kernel層。kernel層的binder驅動在接到binder請求後,會確定要把這個請求分派給哪個程序的哪個執行緒去執行。這個流程會從kernel層調到native層,然後native層通過jni反調到java層。具體過程這裡不深究,感興趣的請自己讀下原始碼。

我們直接給出服務端native通過jni調java層的程式碼,這裡是ActivityManagerService在java層執行的起點,該程式碼實現在frameworks/base/core/jni/android_util_Binder.cpp中:

   virtual status_t onTransact(
        uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags = 0)
    {
        JNIEnv* env = javavm_to_jnienv(mVM);

        ALOGV("onTransact() on %p calling object %p in env %p vm %p\n", this, mObject, env, mVM);

        IPCThreadState* thread_state = IPCThreadState::self();
        const int32_t strict_policy_before = thread_state->getStrictModePolicy();

        //printf("Transact from %p to Java code sending: ", this);
        //data.print();
        //printf("\n");
        jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
            code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);

首先看gBinderOffsets

const char* const kBinderPathName = "android/os/Binder";

static int int_register_android_os_Binder(JNIEnv* env)
{
    jclass clazz = FindClassOrDie(env, kBinderPathName);

    gBinderOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
    gBinderOffsets.mExecTransact = GetMethodIDOrDie(env, clazz, "execTransact", "(IJJI)Z");
    gBinderOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");

    return RegisterMethodsOrDie(
        env, kBinderPathName,
        gBinderMethods, NELEM(gBinderMethods));
}

這裡gBinderOffsets.mClass表示的是java層的android.os.Binder類。
gBinderOffsets.mExecTransact表示的是android.os.BinderexecTransact方法
gBinderOffsets.mObject表示的是當前的Binder服務物件。在這個例子中,就是ActivityManagerService例項,
因為ActivityManagerService繼承自IActivityManager.Stub

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {

IActivityManager.Stub又繼承自android.os.Binder。所以這裡mObject就是ActivityManagerService的例項。

所以

       jboolean res = env->CallBooleanMethod(mObject, gBinderOffsets.mExecTransact,
            code, reinterpret_cast<jlong>(&data), reinterpret_cast<jlong>(reply), flags);

裡面的CallBooleanMethod就是通過jni呼叫java層的ActivityManagerServiceexecTransact方法。

因為ActivityManagerService中沒有定義這個方法,所以會調到父類android.os.BinderexecTransact方法:

// Entry point from android_util_Binder.cpp's onTransact
    private boolean execTransact(int code, long dataObj, long replyObj,
            int flags) {
        Parcel data = Parcel.obtain(dataObj);
        Parcel reply = Parcel.obtain(replyObj);
        // theoretically, we should call transact, which will call onTransact,
        // but all that does is rewind it, and we just got these from an IPC,
        // so we'll just call it directly.
        boolean res;
        // Log any exceptions as warnings, don't silently suppress them.
        // If the call was FLAG_ONEWAY then these exceptions disappear into the ether.
        final boolean tracingEnabled = Binder.isTracingEnabled();
        try {
            if (tracingEnabled) {
                Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, getClass().getName() + ":" + code);
            }
            res = onTransact(code, data, reply, flags);
        } catch (RemoteException|RuntimeException e) {
            if (LOG_RUNTIME_EXCEPTION) {
                Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
            }
            if ((flags & FLAG_ONEWAY) != 0) {
                if (e instanceof RemoteException) {
                    Log.w(TAG, "Binder call failed.", e);
                } else {
                    Log.w(TAG, "Caught a RuntimeException from the binder stub implementation.", e);
                }
            } else {
                reply.setDataPosition(0);
                reply.writeException(e);
            }
            res = true;
        } finally {
            if (tracingEnabled) {
                Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
            }
        }
        checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
        reply.recycle();
        data.recycle();

        // Just in case -- we are done with the IPC, so there should be no more strict
        // mode violations that have gathered for this thread.  Either they have been
        // parceled and are now in transport off to the caller, or we are returning back
        // to the main transaction loop to wait for another incoming transaction.  Either
        // way, strict mode begone!
        StrictMode.clearGatheredViolations();

        return res;
    }

該方法中的data,就是app在調binder的時候傳來的引數,reply就是要返回給app的資料。

首先呼叫的是onTransact方法,onTransact方法會調到子類IActivityManager.Stub中:

     @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_openContentUri: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                ......
                ......

該方法會通過傳入的code,定位到killBackgroundProcesses方法:

                case TRANSACTION_killBackgroundP