實例分析:android.process.media由於調用進程crash而退出


Log:

09-13 11:46:42.093 14778 17309 I dalvikvm: Ljava/lang/RuntimeException;: No memory in memObj
09-13 11:46:42.093 14778 17309 I dalvikvm:     at android.database.CursorWindow.native_init(Native Method)
09-13 11:46:42.093 14778 17309 I dalvikvm:     at android.database.CursorWindow.<init>(CursorWindow.java:569)
09-13 11:46:42.093 14778 17309 I dalvikvm:     at android.database.CursorWindow.<init>(CursorWindow.java:36)
09-13 11:46:42.093 14778 17309 I dalvikvm:     at android.database.CursorWindow$1.createFromParcel(CursorWindow.java:544)
09-13 11:46:42.093 14778 17309 I dalvikvm:     at android.database.CursorWindow$1.createFromParcel(CursorWindow.java:542)
09-13 11:46:42.093 14778 17309 I dalvikvm:     at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:116)
09-13 11:46:42.093 14778 17309 I dalvikvm:     at android.os.Binder.execTransact(Binder.java:336)
09-13 11:46:42.093 14778 17309 I dalvikvm:     at dalvik.system.NativeStart.run(Native Method)
09-13 11:46:42.093 14778 17309 I dalvikvm: "Binder Thread #3" prio=5 tid=10 NATIVE
09-13 11:46:42.093 14778 17309 I dalvikvm:   | group="main" sCount=0 dsCount=0 obj=0x4055e5f8 self=0x361b28
09-13 11:46:42.093 14778 17309 I dalvikvm:   | sysTid=17309 nice=10 sched=0/0 cgrp=bg_non_interactive handle=3356232
09-13 11:46:42.093 14778 17309 I dalvikvm:   | schedstat=( 1261444095 8805053706 2920 )
09-13 11:46:42.093 14778 17309 I dalvikvm:   at dalvik.system.NativeStart.run(Native Method)

09-13 11:46:42.093 14778 17309 E dalvikvm: VM aborting
09-13 11:46:42.093 14778 17309 I dalvikvm: "Binder Thread #3" prio=5 tid=10 NATIVE
09-13 11:46:42.093 14778 17309 I dalvikvm:   | group="main" sCount=0 dsCount=0 obj=0x4055e5f8 self=0x361b28
09-13 11:46:42.093 14778 17309 I dalvikvm:   | sysTid=17309 nice=10 sched=0/0 cgrp=bg_non_interactive handle=3356232
09-13 11:46:42.093 14778 17309 I dalvikvm:   | schedstat=( 1261444095 8805053706 2920 )
09-13 11:46:42.093 14778 17309 I dalvikvm:   at dalvik.system.NativeStart.run(Native Method)

分析:

檢查代碼位置,此Exception出現在MediaProvider Server端響應QUERY_TRANSACTION時,由於傳來的Parcel指向的內存地址爲空引起。


考慮整個調用流程,CursorWindow實例由ContentProviderProxy在Binder調用前時產生,故此對象產生於用戶進程,並傳給Server端,在處理QUERY_TRANSACTION時,由於讀出的CursorWindow實例內存地址爲空拋出異常引起android.process.media退出。

而仔細檢查相關程序代碼,並未發現再出現內存不足時在Log中應出現的那些信息,故排除掉內存不足情形。而且Log中也沒有Leaked Cursor信息。


最終原因剖析:

當MediaProvider收到外部出現的query請求時,此外部程序所在進程退出,導致所傳進來的CursorWindow所擁有的IMememory binder被清空,所以當MediaProvider處理QUERY_TRANSACTION時發現收到的IMemory對象指向的內存地址爲NULL,最終拋出異常致android.media.process退出。


Binder調用前代碼如下:

ContentProviderNative.java

final class ContentProviderProxy implements IContentProvider
{
    public Cursor query(Uri url, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) throws RemoteException {
        CursorWindow window = new CursorWindow(false /* window will be used remotely */); //生成空的CursorWindow實例
        BulkCursorToCursorAdaptor adaptor = new BulkCursorToCursorAdaptor();
        IBulkCursor bulkCursor = bulkQueryInternal(
            url, projection, selection, selectionArgs, sortOrder,
            adaptor.getObserver(), window,
            adaptor);
        if (bulkCursor == null) {
            window.close();
            adaptor.close();
            return null;
        }
        return adaptor;
    }

    private IBulkCursor bulkQueryInternal(
        Uri url, String[] projection,
        String selection, String[] selectionArgs, String sortOrder,
        IContentObserver observer, CursorWindow window,
        BulkCursorToCursorAdaptor adaptor) throws RemoteException {
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();

        data.writeInterfaceToken(IContentProvider.descriptor);
...
        data.writeString(sortOrder);
        data.writeStrongBinder(observer.asBinder());
        window.writeToParcel(data, 0); //把CursorWindow對象寫入到Parcel
...
        mRemote.transact(IContentProvider.QUERY_TRANSACTION, data, reply, 0); //調用到server端

        DatabaseUtils.readExceptionFromParcel(reply);
...


在query方法中,調用newCursorWindow(false) -> initBuffer(false) 生成空的CursorWindow。window.writeToParcel將把IMemory所在的Binder對象寫入Parcel.


CursorWindow.java

    public void writeToParcel(Parcel dest, int flags) {
        dest.writeStrongBinder(native_getBinder()); //把IMemory所在的Binder對象寫入Parcel
        dest.writeInt(mStartPos);
    }

bool CursorWindow::initBuffer(bool localOnly)
{
    sp<MemoryHeapBase> heap;
    heap = new MemoryHeapBase(mMaxSize, 0, "CursorWindow");
    if (heap != NULL) {
        mMemory = new MemoryBase(heap, 0, mMaxSize);
        if (mMemory != NULL) {
            mData = (uint8_t *) mMemory->pointer();
            if (mData) {
                mHeader = (window_header_t *) mData;
                mSize = mMaxSize;

                // Put the window into a clean state
                clear();
            LOG_WINDOW("Created CursorWindow with new MemoryDealer: mFreeOffset = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mSize, mMaxSize, mData);
                return true;                
            }
        } 
        LOGE("CursorWindow heap allocation failed"); //如果mData爲空,由於此Log未出現,因此其必不爲空
        return false;
    } else { //如果分配堆內存失敗
        LOGE("failed to create the CursorWindow heap");
        return false;
    }
}
由於Log中上述Log均未出現,因此內存分配成功。


這裏調用到Server端:

abstract public class ContentProviderNative extends Binder implements IContentProvider {
    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
...
            switch (code) {
                case QUERY_TRANSACTION:
                {
                    data.enforceInterface(IContentProvider.descriptor);

                    Uri url = Uri.CREATOR.createFromParcel(data);

                    // String[] projection
                    int num = data.readInt();
                    String[] projection = null;   
                    if (num > 0) {
                        projection = new String[num];
                        for (int i = 0; i < num; i++) {
                            projection[i] = data.readString();
                        }
                    }

                    // String selection, String[] selectionArgs...
                    String selection = data.readString();
                    num = data.readInt();
                    String[] selectionArgs = null;
                    if (num > 0) {
                        selectionArgs = new String[num];
                        for (int i = 0; i < num; i++) {
                            selectionArgs[i] = data.readString();
                        }
                    }

                    String sortOrder = data.readString();
                    IContentObserver observer = IContentObserver.Stub.
                        asInterface(data.readStrongBinder());
                    CursorWindow window = CursorWindow.CREATOR.createFromParcel(data); //這裏調用產生Exception,即從此Parcel中讀取CursorWindow對象時,由於該對象的內存指針爲空引起異常

 


以下爲從Parcel中重建CursorWindow的代碼:


CursorWindow.java

public class CursorWindow extends SQLiteClosable implements Parcelable {
    private CursorWindow(Parcel source) { //從Parcel中重建
        IBinder nativeBinder = source.readStrongBinder();
        mStartPos = source.readInt();

        native_init(nativeBinder); //Exception here ----
    }

    // Creates a new empty window.
    public CursorWindow(boolean localWindow) {
        mStartPos = 0;
        native_init(localWindow);
    }
    public static final Parcelable.Creator<CursorWindow> CREATOR
            = new Parcelable.Creator<CursorWindow>() {        //從Parcel中重建CursorWindow實例
        public CursorWindow createFromParcel(Parcel source) {
            return new CursorWindow(source);
        }

        public CursorWindow[] newArray(int size) {
            return new CursorWindow[size];
        }
    };


android_database_CursorWindow.cpp

static void native_init_memory(JNIEnv * env, jobject object, jobject memObj)
{
    sp<IMemory> memory = interface_cast<IMemory>(ibinderForJavaObject(env, memObj)); //將Java對象轉化爲IMemory實例
    if (memory == NULL) {
        jniThrowException(env, "java/lang/IllegalStateException", "Couldn't get native binder");
        return;
    }

    CursorWindow * window = new CursorWindow();
    if (!window) {
        jniThrowException(env, "java/lang/RuntimeException", "No memory for native window object");
        return;
    }
    if (!window->setMemory(memory)) {  //異常拋出點
        jniThrowException(env, "java/lang/RuntimeException", "No memory in memObj");
        delete window;
        return;
    }

LOG_WINDOW("native_init_memory: numRows = %d, numColumns = %d, window = %p", window->getNumRows(), window->getNumColumns(), window);
    SET_WINDOW(env, object, window);
}


CursorWindow.cpp

bool CursorWindow::setMemory(const sp<IMemory>& memory)
{
    mMemory = memory;
    mData = (uint8_t *) memory->pointer();
    if (mData == NULL) {       //顯然此處爲NULL導致Exception
        return false;
    }
    mHeader = (window_header_t *) mData;

    // Make the window read-only
    ssize_t size = memory->size();
    mSize = size;
    mMaxSize = size;
    mFreeOffset = size;
LOG_WINDOW("Created CursorWindow from existing IMemory: mFreeOffset = %d, numRows = %d, numColumns = %d, mSize = %d, mMaxSize = %d, mData = %p", mFreeOffset, mHeader->numRows, mHeader->numColumns, mSize, mMaxSize, mData);
    return true;
}


由此可見,當Binder設備喚醒Server端處理Query請求時,發送的CursorWindow實例所擁有的IMemory對象已經無效,極可能由於調用者進程crash導致該對象被清除。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章