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_killBackgroundProcesses: {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    int _arg1;
                    _arg1 = data.readInt();
                    this.killBackgroundProcesses(_arg0, _arg1);
                    reply.writeNoException();
                    return true;
                }

这里的this是ActivityManagerService对象,所以会调用到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);
        long callingId = Binder.clearCallingIdentity();

那么这个异常是怎样处理的呢?我们会到android.os.BinderexecTransact方法,可以看到,它在调onTransact的时候,使用了try-catch:

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 {

ActivityManagerServicekillBackgroundProcesses方法中抛出的SecurityException,正好被这里的catch捕获,所以并不会导致ActivityManagerService崩溃,然后又把这个异常通过reply.writeException写入到reply中:

public final void writeException(Exception e) {
        int code = 0;
        if (e instanceof Parcelable
                && (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
            // We only send Parcelable exceptions that are in the
            // BootClassLoader to ensure that the receiver can unpack them
            code = EX_PARCELABLE;
        } else if (e instanceof SecurityException) {
            code = EX_SECURITY;
        } else if (e instanceof BadParcelableException) {
            code = EX_BAD_PARCELABLE;
        } else if (e instanceof IllegalArgumentException) {
            code = EX_ILLEGAL_ARGUMENT;
        } else if (e instanceof NullPointerException) {
            code = EX_NULL_POINTER;
        } else if (e instanceof IllegalStateException) {
            code = EX_ILLEGAL_STATE;
        } else if (e instanceof NetworkOnMainThreadException) {
            code = EX_NETWORK_MAIN_THREAD;
        } else if (e instanceof UnsupportedOperationException) {
            code = EX_UNSUPPORTED_OPERATION;
        } else if (e instanceof ServiceSpecificException) {
            code = EX_SERVICE_SPECIFIC;
        }
        writeInt(code);
        StrictMode.clearGatheredViolations();
        if (code == 0) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            }
            throw new RuntimeException(e);
        }
        writeString(e.getMessage());
        switch (code) {
            case EX_SERVICE_SPECIFIC:
                writeInt(((ServiceSpecificException) e).errorCode);
                break;
            case EX_PARCELABLE:
                // Write parceled exception prefixed by length
                final int sizePosition = dataPosition();
                writeInt(0);
                writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                final int payloadPosition = dataPosition();
                setDataPosition(sizePosition);
                writeInt(payloadPosition - sizePosition);
                setDataPosition(payloadPosition);
                break;
        }
    }

可以看到,往reply中写异常的时候,写入的是一个code和异常的msg字符串。
这个reply会通过binder返回给app端,所以导致了app端崩溃。



总结


1 Android中Java层的Binder机制,是支持传递异常的。从Parcel中有readExceptionwriteException方法,就可以看出来。
2 传递异常的方式是将异常的code和msg放到Parcel中进行传递
3 调用端会检查返回的Parcel中有没有异常code和msg,如果有的话,就读出这些信息,创建并抛出异常
4 虽然本文分析Binder传递异常的流程,但还是会涉及到很多关于Binder的其他知识,这些知识点没有深入分析,感兴趣的同学自行分析源码。

猜你喜欢

转载自blog.csdn.net/brave2211/article/details/83420068
今日推荐