Monkey source code analysis of Android (Chapter 7: logic analysis of how monkey programs know that an application crashes and then stop running)

Posted by brauchi on Wed, 12 Jan 2022 22:06:26 +0100

preface

The Monkey program is used to perform the stability test. If the tested App crashes and ANR, the Monkey program will know the Crash at the first time, and then write the stack information to the standard error. How does the Monkey program know the Crash? After learning the ActivityController, you will get the answer

ActivityController class structure

    private class ActivityController extends IActivityController.Stub {
  
           ............Omit a lot of code

    }

The ActivityController is located in Monkey In the Java class, it is created as an ordinary internal class. The Monkey program relies on the ActivityController object to maintain communication with AMS system services. The ActivityManagerService tells the Monkey program what happened to the Android system through the API provided by the ActivityController object?  

What services does the ActivityController provide as a Binder object across processes? These methods implemented in the ActivityController have been standardized in an interface called IActivityController in advance. These methods represent what services the ActivityController can provide!

IActivityController is an interface class. A static internal class is defined inside the interface class. Its name is Stub, so there is IActivityController Syntax like Stub.

Parent class of ActivityController iactivitycontroller Stub is located in the iactivitycontroller interface class. The ActivityController as Binder object inherits iactivitycontroller Stub,IActivityController.Stub class inherits Binder class, and Binder class inherits iactivitycontroller!

What methods are specified in the IActivityController interface class? (as a service) that is, what functions can the Binder service specified by IActivityController provide?

Methods specified by IActivityController (services provided externally)

activityStarting() AMS will call this method across processes when starting an Activity through Intent
activityResuming() AMS will call this method across processes when the Activity needs to be restored
appCrashed() AMS will call this method across processes when any App crashes
appEarlyNotResponding() AMS will call this method across processes when App sends ANR very early
systemNotResponding() AMS will call this method across processes when the Android system does not respond

The IActivityController interface specifies five methods, indicating that the Binder can provide five services.

AMS holds the reference of the remote Binder object of the subclass corresponding to IActivityController, so AMS system service adds cross process calls to these methods in its own business logic to achieve inter process communication. (each process no longer runs in isolation, but indirectly changes the data in the memory of the other process to complete the communication and form a more complex business logic). Here, the two processes communicating are Monkey process and SystemServer process. The Monkey process is informed by AMS system service (thread form) in SystemServer, To complete their own business logic. (in words)

1. Monkey process

2. AMS system service process (SystemServer process)

Note: all methods in the Binder object that implements the IActivityController interface run in a thread in the Binder thread pool in the local App process............ for example, the appCrashed() method will run in a thread in the Binder thread pool where the Monkey process is located

In the Monkey program, the ActivityController implements some business logic according to the system conditions, such as the business logic of how the Monkey program does when the system informs that App crashes, ANR crashes and Native crashes occur

Before that, let's continue to learn with a few questions... They are:

1. When does Monkey program register the ActivityController object (Binder object) with AMS?

2. When did the Monkey program tell AMS to terminate the registered ActivityController object (Binder object)?

3. How do the methods provided in the ActivityContoller implement their own business logic?

4. What does Monkey do when App crashes in the system?

5. When an Anr appears in the system, what does Monkey do?

Register the ActivityController object with AMS

   private boolean getSystemInterfaces() {
        mAm = ActivityManager.getService();
        
        ......Omit code

        try {
            mAm.setActivityController(new ActivityController(), true);
            mNetworkMonitor.register(mAm);
        } catch (RemoteException e) {
            Logger.err.println("** Failed talking with activity manager!");
            return false;
        }

        ......Omit code

    }

Monkey program obtains all system services to be used in the getSystemInterfaces () method, and uses the mAm representing AMS system services held by monkey object. In the ActivityManager object, there is a setActivityController () method to actively register a Binder object implementing IActivityControler interface with AMS, that is, the binding object in the code

            mAm.setActivityController(new ActivityController(), true);

setActivityController() method, the first parameter passed in is the newly created ActivityController object, and the second parameter is true, which tells AMS that the Monkey program is registered

This is also a cross process Binder call. The last actual call is the setactivitycontroller () method in the ActivityManagerService. Let's see the implementation of this method

setActivityController() method in ActivityManagerService

    @Override
    public void setActivityController(IActivityController controller, boolean imAMonkey) {
        enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                "setActivityController()");
        synchronized (this) {
            mController = controller;
            mControllerIsAMonkey = imAMonkey;
            Watchdog.getInstance().setActivityController(controller);
        }
    }

The ActivityController object (Binder object) created in the Monkey program is finally saved by the mController held by the ActivityManagerService object. A series of remote callbacks are completed through the mController, which completes the communication between AMS system services and Monkey program processes... Click here for the time being, I can't say it's over (Note: there's another Watchdog at the end)

Note the following line of code: it means to forcibly check whether the caller App is set with Android Manifest. permission. SET_ ACTIVITY_ Watch permission. It seems that when we write an App, we must set this permission to register an IActivityController (Binder object)

        enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER,
                "setActivityController()");

Unregister ActivityController

        try {

            mAm.setActivityController(null, true);

            ............Omit code

        } catch (RemoteException e) {
            // just in case this was latent (after mCount cycles), make sure
            // we report it
            
            ............Omit code

        }

Monkey program will call setActivityController () method again before the end. This time, the first parameter passed in will be assigned null. At this time, the mController held by the ActivityManagerService object will point to null, indicating that the activitycontroller object has been successfully unregistered. The second parameter is still true to inform AMS, This is a call initiated by the monkey program (this specific implementation can be seen in the setActivityController () method of AMS)

Binder thread pool

All methods implemented in the ActivityController in Monkey program and those in AMS are executed in a worker thread in the Binder thread pool belonging to their own process... This concept ends!

Concrete implementation of ActivityController

Monkey program uses the created ActivityController object to realize inter process communication with AMS system services, which of course depends on Android Binder mechanism!

System service AMS will call ActivityController's method () across processes through the services provided by ActivityController in case of crash, ANR and system unavailability. At this time, Monkey program will add its own business logic in these remotely called methods according to the situation of Android system, Next, let's look at how the Monkey program implements these business logic!

    private class ActivityController extends IActivityController.Stub {
        public boolean activityStarting(Intent intent, String pkg) {
             
            ......Omit a lot of code                

            return allow;
        }

        private boolean isActivityStartingAllowed(Intent intent, String pkg) {
           
            ......Omit a lot of code

            return false;
        }

        public boolean activityResuming(String pkg) {

            ......Omit a lot of code

            return allow;
        }

        public boolean appCrashed(String processName, int pid,
                String shortMsg, String longMsg,
                long timeMillis, String stackTrace) {
            
            ......Omit a lot of code            

            return false;
        }

        public int appEarlyNotResponding(String processName, int pid, String annotation) {
            return 0;
        }

        public int appNotResponding(String processName, int pid, String processStats) {
            
            ......Omit a lot of code

            return (mKillProcessAfterError) ? -1 : 1;
        }

        public int systemNotResponding(String message) {

            ......Omit a lot of code            

            return (mKillProcessAfterError) ? -1 : 1;
        }
    }

The above code is the overall picture of ActivityController. Each method will be executed in a worker thread in the Binder thread pool belonging to Monkey program. After execution, the results will be returned to ActivityManagerService. AMS system service will do some specific business logic according to the returned results, Next, let's learn how each method implements some business logic?

1. Learn that an App crashes

2. Learn that an App sends an Anr

3. Learn that there are problems in the system

4. Start AMS Activity

5. AMS restore Activity

What does Monkey program do?

Activity starting (Intent, String) method analysis

       public boolean activityStarting(Intent intent, String pkg) {
            final boolean allow = isActivityStartingAllowed(intent, pkg);
            if (mVerbose > 0) {
                // StrictMode's disk checks end up catching this on
                // userdebug/eng builds due to PrintStream going to a
                // FileOutputStream in the end (perhaps only when
                // redirected to a file?)  So we allow disk writes
                // around this region for the monkey to minimize
                // harmless dropbox uploads from monkeys.
                StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
                Logger.out.println("    // " + (allow ? "Allowing" : "Rejecting") + " start of "
                        + intent + " in package " + pkg);
                StrictMode.setThreadPolicy(savedPolicy);
            }
            currentPackage = pkg;
            currentIntent = intent;
            return allow;
        }

When an Activity is started through the Intent method, AMS system service will call back this method. It will pass the Intent object and package name of the Activity as parameters across processes (both objects support serialization). Monkey program writes its own business logic in the activityStarting () method: monkey program can tell AMS which apps can be started, Which apps cannot start the business logic!

The activityStarting () method runs in a worker thread in the Binder thread pool in the Monkey program, and its return result will be used by AMS system services (AMS business logic is also executed in a thread in the Binder thread pool). The return value will determine whether AMS will start the Activity

1. The return value is true

Indicates that AMS can start an Activity

2. The return value is false

Indicates that AMS cannot start an Activity

Analyze the method body of activityStrating() together

Note: the activityStrating () method will be called back by AMS only when the Activity is started through the Intent object

The passed in parameter intent represents the intent object when the Activity is started, and the passed in parameter pkg represents the package name of the Activity to be started

1. Get startup results

By executing the isActivityStartingAllowed() method, you can get the result of whether the Activity can be started. The result will be saved in the local variable allow

2. Log output

Determine whether to output the log according to the mVerbose value held by the Monkey object

3. Save the currently started package name

The currentPackage held by Monkey class saves the package name of the Activity to be started recently

4. Saves the Intent object at current startup

The currentIntent held by the Monkey class is responsible for saving the Intent object of the recently started Activity

5. Return results to AMS system service

Threads in Binder thread pool will return a boolean. AMS system service receives this value and decides whether to start Activity

Activity resuming (String) method analysis

        public boolean activityResuming(String pkg) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); //Related to strict mode
            Logger.out.println("    // Activityresuming ("+ PKG +"); / / output log
            boolean allow = MonkeyUtils.getPackageFilter().checkEnteringPackage(pkg)
                    || (DEBUG_ALLOW_ANY_RESTARTS != 0); //Gets whether it is an app that is allowed to start
            if (!allow) { //If it is an application that does not agree to start
                if (mVerbose > 0) { //Output only one line of log
                    Logger.out.println("    // " + (allow ? "Allowing" : "Rejecting")
                            + " resume of package " + pkg);
                }
            }
            currentPackage = pkg;//Record the package name of the application launched in the current screen
            StrictMode.setThreadPolicy(savedPolicy); //Or strict mode
            return allow; //Returns whether the Activity is allowed to start
        }

AMS will call this method across processes before resuming an Activity (before onResume() method is called). The return value of this method indicates whether the Activity can be recovered, and the passed in parameter pkg indicates the package name

1. Get strictmode Threadpolicy object

This should be used in strict mode, right?

2. Write log to standard output

A log representing the package name is written to standard output

3. Get package name whether to start

The results are saved in the local variable allow

4. Starting an Activity is not allowed

If the log specification is greater than 0, write a line of log to the standard output

                    Logger.out.println("    // " + (allow ? "Allowing" : "Rejecting")
                            + " resume of package " + pkg);

5. Update the current package name held by Monkey class

currentPackage = pkg

6. The created strict mode object is passed into the static method setThreadPolicy of StrictMode

7. Returns a result to the caller (AMS will receive the result)

appCrashed() method analysis

        /**
         * In case of App crash, AMS system service will call back this method. This method runs in a thread in the binder thread pool of the current Monkey process. AMS is awesome and monitors which process crashes (see the interpretation of boss yuan Huihui)
         * @param processName AMS Tells the process name of the. The string naturally supports serialization
         * @param pid AMS Tells the pid of the process
         * @param shortMsg Short stack information
         * @param longMsg Long stack information
         * @param timeMillis time stamp
         * @param stackTrace Stack information
         * @return When true is returned, it means that the process can be restarted. When false is returned, it means that it (the process) is killed immediately. AMS will use this return value
         */
        public boolean appCrashed(String processName, int pid,
                String shortMsg, String longMsg,
                long timeMillis, String stackTrace) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); //
            Logger.err.println("// Crash: "+ processname +" (PID "+ PID +") "; / / output the log to the standard error stream (this method is executed in the binder thread of the current process)
            Logger.err.println("// Short MSG: "+ shortmsg); / / output the log to the standard error stream (default to the screen)
            Logger.err.println("// Long Msg: " + longMsg);
            Logger.err.println("// Build Label: " + Build.FINGERPRINT);
            Logger.err.println("// Build Changelist: " + Build.VERSION.INCREMENTAL);
            Logger.err.println("// Build Time: " + Build.TIME);
            Logger.err.println("// "+ stacktrace. Replace (" \ n "," n / / "); / / this is why there are two / / in front of the thread stack
            StrictMode.setThreadPolicy(savedPolicy); //Strict mode

            if (mMatchDescription == null
                    || shortMsg.contains(mMatchDescription)
                    || longMsg.contains(mMatchDescription)
                    || stackTrace.contains(mMatchDescription)) { //When no matching stack information is set, or the short message contains the specified content, or the long message contains the specified content, or the stack information contains the specified content, this will be followed
                if (!mIgnoreCrashes || mRequestBugreport) { //If there is no setting to ignore crashes, or if you have to need a bug report, you will go here
                    synchronized (Monkey.this) { //For inter thread synchronization, the appCrashed () method runs in a thread in the Binder thread pool of the Monkey process, so that the threads in the Binder thread pool will compete with the main thread of Monkey for the same object lock (inter thread synchronization)
                                                 //The Monkey main thread releases the Monkey object lock only once every cycle. If the Monkey main thread keeps holding the Monkey object, the threads in the Binder thread pool will be blocked and wait for the Monkey object lock to be released. In this case, the AMS thread will be affected by the threads in the Binder thread pool
                        if (!mIgnoreCrashes) { //If the option to ignore crashes is not set
                            mAbort = true; //Modify the shared variable mAbort, and the Monkey process (main thread) reads the interrupt flag bit and finds true, which will end the program. This shared variable needs to be protected, so an object lock is used to prevent simultaneous writing
                        }
                        if (mRequestBugreport){ //If the user sets a crash report
                            mRequestAppCrashBugreport = true; //The binder thread modifies this shared variable, which indicates that the flag bit of appcrashbulgeport is required. The monkey main process (main thread) will always read this shared variable in the event loop
                            mReportProcessName = processName; //Set the process name (program name) to be reported. This shared variable needs to be protected (only the shared variables that need to be written are protected)
                        }
                    } //Here, the worker thread in the Binder thread pool will release the object lock, and the Monkey main process (main thread) can continue to execute after obtaining the object lock (inter thread synchronization with object lock)
                    return !mKillProcessAfterError; //This return value is for AMS... The default value must be true. When a Crash occurs, the system is required to restart the app process... No wonder it is set to ignore the automatic restart after app Crash
                }
            }
            return false; //Triggered after an App process crashes, when true is returned, AMS restarts the process (so you can see the restarted application soon). When false is returned, it immediately kills it (the process). The return value indicates how AMS handles the process
        }

When any App crashes, AMS will call this method across processes. When Monkey program learns that an App process crashes, it will execute its own business logic

If ignore crash is not specified on the command line started by the Monkey program, the magnorecrashes held by the Monkey object is false. At this time, the value of the shared variable mAbort will be changed to true, indicating that the Monkey main thread will end. If the loop ends during the running cycle, the Monkey program will end

 

I won't write the specific details first. The article is too long. I want to write it separately

appEarlyNotResponding() method analysis

        /**
         * Triggered when it is identified as ANR (AMS is used to identify ANR, said by Yuan Huihui)
         * What happens when an ANR is identified? What does Early mean?
         * @param processName Process name
         * @param pid Process id
         * @param annotation Description?
         * @return Returns a 0
         */
        public int appEarlyNotResponding(String processName, int pid, String annotation) {
            return 0;
        }

appNotResponding() method analysis

        /**
         * app When an ANR occurs, AMS calls back this method, which is executed in a thread in a binder thread pool of the Monkey process
         * @param processName Process name
         * @param pid Process id
         * @param processStats Process status
         * @return When 0 is returned, it means that the application does not respond. If 1 is returned, it means to continue waiting. If - 1 is returned, it means to kill the process immediately
         * When an ANR occurs in an application process, it will be triggered. I found that this API is prepared for the Setting App. I understand
         */
        public int appNotResponding(String processName, int pid, String processStats) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites(); //Create a strictmode Threadpolicy object, which seems to be a strict mode?
            Logger.err.println("// Not responding: "+ processname +" (pid "+ pid +") "; / / print the process name and pid of ANR in the standard error stream
            Logger.err.println(processStats); //Status of the print process
            StrictMode.setThreadPolicy(savedPolicy); //What does this static method setThreadPolicy () do?

            if (mMatchDescription == null || processStats.contains(mMatchDescription)) {
                synchronized (Monkey.this) { //The threads in the Binder thread pool can continue to run only after obtaining the object lock. Once the Monkey main thread releases the object lock, it starts to continue the execution of the Binder thread. Because the Monkey main thread has no object lock, it causes the program to stay and perfect inter thread synchronization (inter process synchronization)
                    mRequestAnrTraces = true;  //Modify the shared variable (shared memory) to indicate the Trace that needs to request ANR
                    mRequestDumpsysMemInfo = true; //Modify the shared variable to indicate that memory information needs to be output
                    mRequestProcRank = true; //Modify the shared variable to indicate the request
                    if (mRequestBugreport) { //If a bugreport is executed in the command line parameter
                        mRequestAnrBugreport = true; //Modify the shared variable to indicate that you need to request the bug report of anr
                        mReportProcessName = processName; //Modify the shared variable and save the reported process name
                    }
                }
                if (!mIgnoreTimeouts) { //If the ignore timeout is not set in the command line, the monkey program will stop after ANR appears
                    synchronized (Monkey.this) { //Competing with monkey's main thread (main process) for the monkey object lock, the threads in the binder thread pool may be blocked here (in that case, a binder thread in the binder thread pool in the SystemServer where AMS is located will also be blocked. Yes, it seems that monkey's main thread can't be too tired, which will affect the execution of System_Server (see yuan Huihui)
                        mAbort = true; //The Monkey program modifies this shared variable according to the flag bit of whether to interrupt. However, it is interesting to repeatedly obtain the same object lock here
                    }
                }
            }

            return (mKillProcessAfterError) ? -1 : 1;  //The return value of - 1 requires AMS to kill the process immediately. Does 1 mean not to kill the process? And the return value can set the value of mKillProcessAfterError according to the command line
        }

When ANR occurs in any App, AMS informs Monkey program through this method, and Monkey program adds its own processing logic after learning it

systemNotResponding() method analysis

        /**
         * This method will also be triggered when the system watchdog detects that the system is hung
         * AMS will call back this method when the system does not respond
         * @param message Reasons for not responding
         * @return The number returned indicates the exit status code
         * If 1 is returned, it means to continue waiting. If - 1 is returned, it means that the system commits suicide normally (here, the system commits suicide on its own initiative. The saved data is saved first and then committed suicide, which is not caused by other reasons)
         */
        public int systemNotResponding(String message) {
            StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
            Logger.err.println("// Watchlog: "+ message); / / print watchlog to standard error
            StrictMode.setThreadPolicy(savedPolicy);

            synchronized (Monkey.this) { //Competing object lock: a worker thread in the Binder thread pool competes for the object lock first. Only after obtaining the object lock can the method in the code block be executed, otherwise it will be blocked (object lock is used as a way of synchronization between threads)
                if (mMatchDescription == null || message.contains(mMatchDescription)) { //If the matching information is not set or the matching information is set and included
                    if (!mIgnoreCrashes) { //If the ignore crash is not set, the Monkey program will end when the system does not respond
                        mAbort = true; //When the worker thread in the Binder thread pool modifies the shared variable (writes), monkey's main thread will poll the shared variable. When it is found to be true, the monkey program will end the cycle, the main thread will end, and the whole monkey program will end
                    }
                    if (mRequestBugreport) { //If you need a bugreport on the command line
                        mRequestWatchdogBugreport = true; //To modify a shared variable, you need a bugreport
                    }
                }
                mWatchdogWaiting = true; //Modify the shared variable (shared memory), and the flag bit of watchDog is true
            } //Release the monkey object lock. Why release the object lock here? Do you want the monkey program to continue running?
            synchronized (Monkey.this) { //Obtain the object lock again. If it is not obtained, a worker thread in the Binder thread pool will be blocked here
                while (mWatchdogWaiting) { //If watchDog needs to wait
                    try {
                        Monkey.this.wait(); //The thread of Binder thread pool will stay here and wait for the Monkey object lock
                    } catch (InterruptedException e) {
                    }
                }
            }
            return (mKillProcessAfterError) ? -1 : 1; //The return value of - 1 indicates that the process needs to be dried up, and 1 indicates that the process does not need to be dried up
        }
    }

When the system does not respond, AMS will inform Monkey program through this method. See the notes for the specific logic

summary

This article has been written for a long time, spanning a year, and the harvest must be full. It turns out that Monkey program has no magic. It still uses the API of Android Framework layer and also relies on system services to complete its own business logic. Android is broad and profound, which is worth learning

Topics: Android