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是否有許可權。所以,在ActivityManagerService
的killBackgroundProcesses
方法中,找到了拋異常的地方。
@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)
可以看到在Activity
的onCreate
中,呼叫了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$Proxy
的killBackgroundProcesses
方法:
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
物件,專門用來呼叫遠端程序的。這個方法中把所有的引數收集到一個名為_data
的Parcel
物件中,並且建立一個叫_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)
我們看一下Parcel
的readException
實現:
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.Binder
的execTransact
方法
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層的ActivityManagerService
的execTransact
方法。
因為ActivityManagerService
中沒有定義這個方法,所以會調到父類android.os.Binder
的execTransact
方法:
// 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