In depth analysis of Qianlima android binder - native layer connecting the preceding and the following (client part)

Posted by samsbox on Tue, 30 Nov 2021 16:44:08 +0100

csdn online learning course, course consultation and Q & A and new course information: QQ communication group: 422901085 for course discussion

android cross process communication practice video course (add group to get preferential)

I believe you should be familiar with the above diagram of binder cross process communication. There are four main roles in the diagram:
1. Client (A process)
2,SerivceManager
3. Server (B process)
4. binder driver
How do binders communicate across processes? Then the students answered most of the answers: binder finally transferred to the bottom or through binder driver to achieve cross process communication. So here's a question: How did the java layer binder call of android app call the underlying binder driver step by step?

This middle question mark is also the part to be shared in this section.
##1.ServiceManager method call is also a cross process communication of binder
Cross process communication will inevitably have two ends, that is, the client and server. The problem is how does the C end know the S end? Then this will lead to an important role, ServiceManager. ServiceManager itself is relatively simple, and its function is to query and register services. After the client queries the registration service through the ServiceManager, cross process communication can be carried out. Here, it is often said that the ServiceManager is like a DNS server. However, the problem is that ServiceManager itself is an independent process. Therefore, when the client queries the service in SerivceManager, it must be cross process communication. Therefore, when analyzing cross process communication in this section, the cross process call ServiceManager.getService is used for analysis.

##2.ServiceManager interface acquisition and analysis
Usually, ServiceManager.getService is the last way to obtain a service in the system. Let's look at its source code:

//In ServiceManager.java
    public static IBinder getService(String name) {
        try {
        	//Get from cache, the first cache does not
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
            	//Binder.allowBlocking is only used to prompt whether it is blocking the line interface, without actual work
                return Binder.allowBlocking(getIServiceManager().getService(name));
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

Here you can see that the real work is getIServiceManager().getService(name). Let's first look at the getIServiceManager() method:

//In ServiceManager.java
   private static IServiceManager getIServiceManager() {
      //There is a sServiceManager to cache, but it must be null for the first time
        if (sServiceManager != null) {
            return sServiceManager;
        }
        // Here, the asInterface method and BinderInternal.getContextObject() method of ServiceManagerNative class are called
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }

Here, the asInterface method of the ServiceManagerNative class is called. This method itself should be often seen in the java file generated by the application writing aidl:

//ServiceManagerNative.java
  static public IServiceManager asInterface(IBinder obj)
    {
        if (obj == null) {
            return null;
        }
        IServiceManager in =
            (IServiceManager)obj.queryLocalInterface(descriptor);
        if (in != null) {
            return in;
        }
        //In fact, it is a new ServiceManagerProxy proxy object. The parameter is an object of type IBinder
        return new ServiceManagerProxy(obj);
    }

Its core is the new ServiceManagerProxy, but what really works is the IBinder parameter. It can be seen from the parameter getIServiceManager method that the core of obtaining is the BinderInternal.getContextObject() method, but getContextObject is a native method, so you have to look at the corresponding jni method:

//android_util_Binder.cpp
static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
   //Call getContextObject to construct an IBinder type object
    sp<IBinder> b = ProcessState::self()->getContextObject(NULL);
    //Convert the C + + level IBinder type object into a java IBinder object
    return javaObjectForIBinder(env, b);
}

There are only two lines of code here. Let's go through a line by line analysis. First, let's look at processstate:: self() - > getcontextobject:

//ProcessState.cpp
//Here, the passed parameter is not used directly, and the passed parameter itself is NULL
sp<IBinder> ProcessState::getContextObject(const sp<IBinder>& /*caller*/)
{
    return getStrongProxyForHandle(0);
}

ProcessState called getStrongProxyForHandle method again, and passed a parameter 0:

//ProcessState.cpp
sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
    sp<IBinder> result;

    AutoMutex _l(mLock);
   //Query handle according to handle_ If the entry object has not been initialized, a handle is constructed_ Entry shell out
    handle_entry* e = lookupHandleLocked(handle);
  //Because handle_ The entry already exists. It's just an empty shell with empty contents
    if (e != NULL) {
        IBinder* b = e->binder;
      //Here e - > binder must be empty for the first time
        if (b == NULL || !e->refs->attemptIncWeak(this)) {
          //If the incoming handle is 0
            if (handle == 0) {
                Parcel data;
               //The ipcthreadstate:: self() - > transact method will be called to pass a PING_TRANSACTION, main purpose
               //To test whether ServiceManager is still alive
                status_t status = IPCThreadState::self()->transact(
                        0, IBinder::PING_TRANSACTION, data, NULL, 0);
                if (status == DEAD_OBJECT)
                   return NULL;
            }
            //Use the handle value to construct a BpBinder object, and finally return the same BpBinder object
            b = new BpBinder(handle); 
            e->binder = b;
            if (b) e->refs = b->getWeakRefs();
            result = b;
        } else {
            result.force_set(b);
            e->refs->decWeak(this);
        }
    }
    return result;
}

Here we can see that the last thing is actually new BpBinder(handle);, Here, handle is often referred to as "reference", that is, reference to remote objects. Because of the particularity of ServiceManager, the android system sets the reference handle value of any process to ServiceManager to a fixed 0 by default. Then, we have finished analyzing processstate:: self() - > getcontextobject (null). In fact, the returned is a new BpBinder(0), but here is also the BpBinder object of C + + layer, and we want to obtain the IBinder type object of Java layer. Next, we need to analyze the remaining javaObjectForIBinder(env, b):

//android_util_Binder.cpp
jobject javaObjectForIBinder(JNIEnv* env, const sp<IBinder>& val)
{
        //Omitted part
	//The java object of gBinderProxyOffsets is constructed here
    object = env->NewObject(gBinderProxyOffsets.mClass, gBinderProxyOffsets.mConstructor);
    if (object != NULL) {
        // The proxy holds a reference to the native object.
      //Here, set the object pointer of val c + + to the java property gBinderProxyOffsets.mObject
        env->SetLongField(object, gBinderProxyOffsets.mObject, (jlong)val.get());
        val->incStrong((void*)javaObjectForIBinder);

        // The native object needs to hold a weak reference back to the
        // proxy, so we can retrieve the same proxy if it is still active.
        jobject refObject = env->NewGlobalRef(
                env->GetObjectField(object, gBinderProxyOffsets.mSelf));
        //The previous step is to establish the corresponding java object based on val, and the java object can directly get the val pointer
       //But our process also needs a val pointer to get java objects
        val->attachObject(&gBinderProxyOffsets, refObject,
                jnienv_to_javavm(env), proxy_cleanup);

     //Omitted part
    }

    return object;
}

What is related to gbinder proxyoffsets? Here's the corresponding initialization code:

//android_util_Binder.cpp
const char* const kBinderProxyPathName = "android/os/BinderProxy";

static int int_register_android_os_BinderProxy(JNIEnv* env)
{
    jclass clazz = FindClassOrDie(env, "java/lang/Error");
    gErrorOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
  //Find the java class android/os/BinderProxy through jni
    clazz = FindClassOrDie(env, kBinderProxyPathName);
    gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
  //Find out the construction method of android/os/BinderProxy java class through jni
    gBinderProxyOffsets.mConstructor = GetMethodIDOrDie(env, clazz, "<init>", "()V");
    gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
            "(Landroid/os/IBinder$DeathRecipient;)V");
///Find the mbobject attribute of the java class android/os/BinderProxy through jni
    gBinderProxyOffsets.mObject = GetFieldIDOrDie(env, clazz, "mObject", "J");
//Omitted part
}

It can be seen that the jni related methods are used to bind the related variables of the native layer with the BinderProxy of the java layer. From here, we can see that the BinderProxy is constructed at the native level. If its mbobject value is set as the BpBinder pointer of the c + + layer, we have analyzed the getIServiceManager method, At this time, there is the Proxy proxy of ServiceManager. The following core parts are summarized:
1. ServiceManager is special. Its handle is fixed to 0. The native layer directly performs new BpBinder (0)
2. The BpBinder of the native layer needs to be converted into the BinderProxy of the Java layer. This requires the native to construct the BinderProxy corresponding to the Java layer through jni, and the mObject value of the BinderProxy is the pointer to BpBinder(0). mObject is also the most key link for the interworking between the Java layer and the native layer

##3.getService obtains the source code analysis of remote service agent
First, let's look at the getService in the ServiceManagerProxy class in ServiceManagerNative.java

//ServiceManagerNative.java
    public IBinder getService(String name) throws RemoteException {
         //Preparing to import and return Parcel objects
        Parcel data = Parcel.obtain();
        Parcel reply = Parcel.obtain();
        data.writeInterfaceToken(IServiceManager.descriptor);
        data.writeString(name);
      //It is known here that mRemote is actually the BinderProxy object constructed from the previous analysis
        mRemote.transact(GET_SERVICE_TRANSACTION, data, reply, 0);
      //Read the returned IBinder data
        IBinder binder = reply.readStrongBinder();
        reply.recycle();
        data.recycle();
        return binder;
    }

In fact, the core here is mRemote.transact and reply.readStrongBinder. Let's analyze the transact part first,
This mRemote object is a BinderProxy object, so look at the transact method of BinderProxy class. Note that BinderProxy does not have a separate java file here. It is in the Binder.java file:

//Binder.java
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//ellipsis
        try {
            return transactNative(code, data, reply, flags);
        } finally {
           //ellipsis
        }
    }

In fact, a native method transactNative is called here. Note that transactNative is connected to android_os_BinderProxy_transact has a conversion relationship, which is very common when analyzing the android system source code:

//android_util_Binder.cpp
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
        jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
   //Omitted part
//Here, the mbobject attribute is obtained from the BinderProxy type java object, and its value analyzed earlier is the BpBinder object pointer
    IBinder* target = (IBinder*)env->GetLongField(obj, gBinderProxyOffsets.mObject);
   //Omitted part
//Call the transact method of the BpBinder object
    status_t err = target->transact(code, *data, reply, flags);
//Omitted part
}

Here we can see why the mbobject mentioned above is an "important link". Next, let's take a look at the transact method of BpBinder:

//BpBinder.cpp
status_t BpBinder::transact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    if (mAlive) {
       //The essence is to call the transact method of IPCThreadState
        status_t status = IPCThreadState::self()->transact(mHandle, code, data, reply, flags);
        if (status == DEAD_OBJECT) mAlive = 0;
        return status;
    }
    return DEAD_OBJECT;
}

Next, let's look at the transact method of IPCThreadState:

//IPCThreadState.cpp
status_t IPCThreadState::transact(int32_t handle,
                                  uint32_t code, const Parcel& data,
                                  Parcel* reply, uint32_t flags)
{
 //Omitted part
    if (err == NO_ERROR) {
      //Prepare the data format structure for communication with the driver
        err = writeTransactionData(BC_TRANSACTION, flags, handle, code, data, NULL);
    }
//If not TF_ONE_WAY is a call of oneway type
    if ((flags & TF_ONE_WAY) == 0) {
        if (reply) {
          //We need to wait for a reply here
            err = waitForResponse(reply);
        } else {
            Parcel fakeReply;
            err = waitForResponse(&fakeReply);
        }
    } else {
//That is, the call of oneway type
        err = waitForResponse(NULL, NULL);
    }
    return err;
}

It is divided into two blocks: writeTransactionData and waitForResponse. The main purpose of writeTransactionData is to assemble the data into a binder consistent with the driver_ transaction_ Data structure, followed by waitForResponse:

status_t IPCThreadState::waitForResponse(Parcel *reply, status_t *acquireResult)
{
    uint32_t cmd;
    int32_t err;

    while (1) {
        //Communicate with the driver, mainly through the relevant ioctl methods
        if ((err=talkWithDriver()) < NO_ERROR) break;
        if (mIn.dataAvail() == 0) continue;
        //Get driver return cmd
        cmd = (uint32_t)mIn.readInt32();
        switch (cmd) {
        case BR_REPLY:
            {
                binder_transaction_data tr;
              //Get driver return data
                err = mIn.read(&tr, sizeof(tr));
                //Omitted part
         }
//Omitted part
}

It can be seen that the main reason is that talkWithDriver communicates with the driver and has been waiting for the corresponding results of the driver to be returned. The part involving the driver will not be explained here. We can treat the driver as a black box for the time being.
Here, let's take a brief look at the service side of ServiceManager. What do you do with the getService of the client side?
In the servicemanager code, if a client request is received, svcmgr will be called in the end_ Handler to handle:

int svcmgr_handler(struct binder_state *bs,
                   struct binder_transaction_data *txn,
                   struct binder_io *msg,
                   struct binder_io *reply)
{
    struct svcinfo *si;
    uint16_t *s;
    size_t len;
    uint32_t handle;
    uint32_t strict_policy;
    int allow_isolated;
//.. Omitted
    switch(txn->code) {
    case SVC_MGR_GET_SERVICE:
    case SVC_MGR_CHECK_SERVICE:
     //Get the name of the service according to the
        s = bio_get_string16(msg, &len);
        if (s == NULL) {
            return -1;
        }
      //Find the corresponding handle, i.e. index, from the existing collection according to name
        handle = do_find_service(s, len, txn->sender_euid, txn->sender_pid);
        if (!handle)
            break;
      //Get the handle and write it to reply
        bio_put_ref(reply, handle);
        return 0;
//.. Omitted
    default:
        ALOGE("unknown code %d\n", txn->code);
        return -1;
    }

    bio_put_uint32(reply, 0);
    return 0;
}

Here, the service handle is found from the collection according to the name. Note that only an int handle is found here, and then bio is called_ put_ Ref writes the reply, and then passes it to the client through the binder driver. Here's a look at bio_put_ref method:

void bio_put_ref(struct binder_io *bio, uint32_t handle)
{
//Construct a binder structure
    struct flat_binder_object *obj;

    if (handle)
        obj = bio_alloc_obj(bio);
    else
        obj = bio_alloc(bio, sizeof(*obj));

    if (!obj)
        return;
//To flat_ binder_ Attribute assignment corresponding to object structure
    obj->flags = 0x7f | FLAT_BINDER_FLAG_ACCEPTS_FDS;
 //Set the type to BINDER_TYPE_HANDLE
    obj->type = BINDER_TYPE_HANDLE;
   //Assign handle to flat_ binder_ Handle attribute of object
    obj->handle = handle;
    obj->cookie = 0;
}

Here, we probably cleared the original servicemanager side, which is to find the handle corresponding to the name according to the name, and then wrap the handle value into flat_ binder_ In the object structure, the structure is transferred to the driver, and the driver is transferred to the client. The specific transfer process is not described in detail in this section. After the client calls the transact method, the data of the service manager on the server is returned through the binder driver. But how does the returned data become an ibinder object step by step? Here we look at the IBinder binder = reply.readStrongBinder() method of IBinder getService(String name), which is called readStrongBinder method from Parcel type reply and returns the IBinder object. Notice that Parcel is java level, but call it at last and call it in Parcel.cpp, first look at Parcel.java:

//Parcel.java
  public final IBinder readStrongBinder() {
        return nativeReadStrongBinder(mNativePtr);
    }

In fact, only the nativereaderstrongbinder method is called here, which is also a jni method, so this method will be called to the native layer. See which method this method is mapped to in the native layer:

//android_os_Parcel.cpp
    {"nativeReadStrongBinder",    "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},

This is mapped to android_os_Parcel_readStrongBinder method:

//android_os_Parcel.cpp
static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, jclass clazz, jlong nativePtr)
{
    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
    if (parcel != NULL) {
        return javaObjectForIBinder(env, parcel->readStrongBinder());
    }
    return NULL;
}

Here, you will call parcel - > readstrongbinder() to obtain the BpBinder object of the native layer as a parameter, and then call javaObjectForIBinder to convert it into a java object. This has been mentioned earlier.

//Parcel.cpp
sp<IBinder> Parcel::readStrongBinder() const
{
    sp<IBinder> val;
//readNullableStrongBinder is called here again
    readNullableStrongBinder(&val);
    return val;
}

Next, look at readNullableStrongBinder:

status_t Parcel::readNullableStrongBinder(sp<IBinder>* val) const
{
    return unflatten_binder(ProcessState::self(), *this, val);
}

Unflatten is called here again_ binder:

status_t unflatten_binder(const sp<ProcessState>& proc,
    const Parcel& in, sp<IBinder>* out)
{
    const flat_binder_object* flat = in.readObject(false);

    if (flat) {
        switch (flat->type) {
            case BINDER_TYPE_BINDER:
                *out = reinterpret_cast<IBinder*>(flat->cookie);
                return finish_unflatten_binder(NULL, *flat, in);
            case BINDER_TYPE_HANDLE:
              //The core here is to call getStrongProxyForHandle after getting handle.
                *out = proc->getStrongProxyForHandle(flat->handle);
                return finish_unflatten_binder(
                    static_cast<BpBinder*>(out->get()), *flat, in);
        }
    }
    return BAD_TYPE;
}

When we analyzed the servicemanager earlier, we knew that the type was BINDER_TYPE_HANDLE, so call getStrongProxyForHandle according to the handle value of flat - > handle. The getStrongProxyForHandle method has been analyzed earlier. It is new BpBinder (handle), so it is clear here that the getService method is finally based on the handle returned by the servicemanager (note here that the handle value of the client is not necessarily equal to the handle value written in the servicemanager, but various processes are independent, but the binder driver can be associated. The specific differences will be analyzed in the binder driver).

Summary of the getService section:
1. Obtaining the remote service as a whole is still the process of changing the new BpBinder (handle) - > javaobjectforibinder into BinderProxy
2. However, the handle here is no longer as simple as the ServiceManager. Instead, it needs to pass the name to the ServiceManager to query the corresponding service. The ServiceManager passes the corresponding service flat_binder_object object object to the client through the binder driver, and the client obtains the handle from the flat_binder_object

So far, we have analyzed the whole process from the client initiating a call to obtaining the driver data return, so the next thing to be analyzed is as a server, that is, Binder implementation.

Topics: Java C++ Android Framework