Android 12 system source code analysis: native tombstone Manager

Author: Qiucheng

summary

Android 12 new system_ The server process (LocalService) is a local service used to manage native tombstones.

The service is started after the system server initialization process is started, added to the LocalService, and then a ServiceThread thread (mHandler.post) is started to process the business of the service.
The main functions of NativeTombstoneManager are:

  • Listen for changes in the / data/tombstones directory file, parse it into a TombstoneFile object, save it, and notify dropbox
  • Specific tombstones file deletion
  • Specific tombstones file retrieval

It is noteworthy that AMS uses this service, which is also a new API in Android 11: activitymanager java#getHistoricalProcessExitReasons()
The software architecture is shown in the figure below:

Figure: NativeTombstoneManager class diagram

Start process

Figure: NativeTombstoneManager service startup sequence diagram

The service is relatively simple, just like other services started by system server,

frameworks/base/services/core/java/com/android/server/os/NativeTombstoneManagerService.java

public class NativeTombstoneManagerService extends SystemService {
    private NativeTombstoneManager mManager;

    @Override
    public void onStart() {
        mManager = new NativeTombstoneManager(getContext());
        //Only local services are added, and there is no binder service
        LocalServices.addService(NativeTombstoneManager.class, mManager);
    }

    @Override
    public void onBootPhase(int phase) {
        if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
            mManager.onSystemReady();
        }
    }
}

This service is also a subclass of the SystemService tool class. The code flow is obtained by rewriting onStart and onBootPhase

Initialize the real service implementation NativeTombstoneManager in onStart, and add it to local services after instantiation

frameworks/base/services/core/java/com/android/server/os/NativeTombstoneManager.java

public final class NativeTombstoneManager {
        NativeTombstoneManager(Context context) {
    //Start the handler thread for subsequent business processing of this service
        final ServiceThread thread = new ServiceThread(TAG + ":tombstoneWatcher",
                THREAD_PRIORITY_BACKGROUND, true /* allowIo */);
        thread.start();
        mHandler = thread.getThreadHandler();
    //Start file listening / data/tombstones
        mWatcher = new TombstoneWatcher();
        mWatcher.startWatching();
    }

    void onSystemReady() {
        registerForUserRemoval();
        registerForPackageRemoval();
        // During startup, scan the / data/tombstones directory first
        mHandler.post(() -> {
            final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
            for (int i = 0; tombstoneFiles != null && i < tombstoneFiles.length; i++) {
                if (tombstoneFiles[i].isFile()) {
                    handleTombstone(tombstoneFiles[i]);

There are three actions in the startup process

  • Start the handler thread for subsequent business processing of this service
  • TombstoneWatcher starts file listening / data/tombstones
  • During startup, scan the / data/tombstones directory first

Take a look at handleTombstone

frameworks/base/services/core/java/com/android/server/os/NativeTombstoneManager.java

    private void handleTombstone(File path) {
        final String filename = path.getName();
        if (!filename.startsWith("tombstone_")) {
            return;
        }

        if (filename.endsWith(".pb")) {
            handleProtoTombstone(path);
            BootReceiver.addTombstoneToDropBox(mContext, path, true);
        } else {
            BootReceiver.addTombstoneToDropBox(mContext, path, false);

If it is a prototype file ending in pb, a TombstoneFile object is generated for the file in the handleProtoTombstone method and added to the data structure

private final SparseArray<TombstoneFile> mTombstones;

Moreover, each newly generated tombstone file will be synchronized to dropbox

Listening for new files

frameworks/base/services/core/java/com/android/server/os/NativeTombstoneManager.java

    class TombstoneWatcher extends FileObserver {
        TombstoneWatcher() {
            super(TOMBSTONE_DIR, FileObserver.CREATE | FileObserver.MOVED_TO);
        }

        @Override
        public void onEvent(int event, @Nullable String path) {
            mHandler.post(() -> {
                handleTombstone(new File(TOMBSTONE_DIR, path));
            });

The internal class, tombstone watcher, calls back to onEvent when a file is generated in the directory / data/tombstones, and then handles it through the handleTombstone method

AciivtyManager#getHistoricalProcessExitReasons


Figure: sequence diagram of getHistoricalProcessExitReasons method

Pay attention to the processing of the returned data structure ApplicationExitInfo.

frameworks/base/services/core/java/com/android/server/os/NativeTombstoneManager.java

    public void collectTombstones(ArrayList<ApplicationExitInfo> output, int callingUid, int pid,
            int maxNum) {
        CompletableFuture<Object> future = new CompletableFuture<>();

        if (!UserHandle.isApp(callingUid)) {
            return;
        }

        final int userId = UserHandle.getUserId(callingUid);
        final int appId = UserHandle.getAppId(callingUid);

        mHandler.post(() -> {
            boolean appendedTombstones = false;

            synchronized (mLock) {
                final int tombstonesSize = mTombstones.size();

            tombstoneIter:
            //Traverse all known tombstoe,
            //If userid and appid match reason
            //Returns the requested number of tombstone s
                for (int i = 0; i < tombstonesSize; ++i) {
                    TombstoneFile tombstone = mTombstones.valueAt(i);
                    if (tombstone.matches(Optional.of(userId), Optional.of(appId))) {
                        if (pid != 0 && tombstone.mPid != pid) {
                            continue;
                        }
            //reason judgment
                        // Try to attach to an existing REASON_CRASH_NATIVE.
                        final int outputSize = output.size();
                        for (int j = 0; j < outputSize; ++j) {
                            ApplicationExitInfo exitInfo = output.get(j);
                            if (tombstone.matches(exitInfo)) {
                                exitInfo.setNativeTombstoneRetriever(tombstone.getPfdRetriever());
                                continue tombstoneIter;
                            }
                        }
            //Request quantity judgment
                        if (output.size() < maxNum) {
                            appendedTombstones = true;
                            output.add(tombstone.toAppExitInfo());
                        }
                    }
                }
            }
        //If the number of requests exceeds one, they are sorted by timestamp
            if (appendedTombstones) {
                Collections.sort(output, (lhs, rhs) -> {
                    // Reports should be ordered with newest reports first.
                    long diff = rhs.getTimestamp() - lhs.getTimestamp();
                    if (diff < 0) {
                        return -1;
                    } else if (diff == 0) {
                        return 0;
                    } else {
                        return 1;
                    }
                });
            }
            future.complete(null);
        });

        try {
            future.get();

Traverse all known tombstones, and return the requested number of tombstones if userid and appid match reason

If the quantity exceeds one, it is sorted by timestamp

It is worth noting that completable future, functional programming, can refer to: basic usage of completable future

Cleanup of tombstone files

There are currently two scenarios that clean up files

  • Active call interface deletion, AppExitInfoTracker – > purge()
  • When the app is uninstalled, registerForPackageRemoval – > purgepackage() – > purge()

clearHistoryProcessExitInfo

frameworks/base/services/core/java/com/android/server/am/AppExitInfoTracker.java

    void clearHistoryProcessExitInfo(String packageName, int userId) {
        NativeTombstoneManager tombstoneService = LocalServices.getService(
                NativeTombstoneManager.class);
        Optional<Integer> appId = Optional.empty();

        if (TextUtils.isEmpty(packageName)) {
            synchronized (mLock) {
                removeByUserIdLocked(userId);
            }
        } else {
            final int uid = mService.mPackageManagerInt.getPackageUid(packageName,
                    PackageManager.MATCH_ALL, userId);
            appId = Optional.of(UserHandle.getAppId(uid));
            synchronized (mLock) {
                removePackageLocked(packageName, uid, true, userId);
            }
        }

        tombstoneService.purge(Optional.of(userId), appId);
        schedulePersistProcessExitInfo(true);
    }

frameworks/base/services/core/java/com/android/server/os/NativeTombstoneManager.java

     * Remove native tombstones matching a user and/or app.
    public void purge(Optional<Integer> userId, Optional<Integer> appId) {
        mHandler.post(() -> {
            synchronized (mLock) {
                for (int i = mTombstones.size() - 1; i >= 0; --i) {
                    TombstoneFile tombstone = mTombstones.valueAt(i);
                    if (tombstone.matches(userId, appId)) {
                        tombstone.purge();
                        mTombstones.removeAt(i);
----------------------------------------------------------------------
    static class TombstoneFile {
        public void purge() {
            if (!mPurged) {
                try {
                    Os.ftruncate(mPfd.getFileDescriptor(), 0);
                } catch (ErrnoException ex) {
                    Slog.e(TAG, "Failed to truncate tombstone", ex);
                }
                mPurged = true;

purgePackage

frameworks/base/services/core/java/com/android/server/os/NativeTombstoneManager.java

    private void registerForPackageRemoval() {
        final IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
        filter.addDataScheme("package");
        mContext.registerReceiverForAllUsers(new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                final int uid = intent.getIntExtra(Intent.EXTRA_UID, UserHandle.USER_NULL);
                if (uid == UserHandle.USER_NULL) return;

                final boolean allUsers = intent.getBooleanExtra(
                        Intent.EXTRA_REMOVED_FOR_ALL_USERS, false);

                purgePackage(uid, allUsers);
            }
        }, filter, null, mHandler);
    }
---------------------------------------------
    private void purgePackage(int uid, boolean allUsers) {
        final int appId = UserHandle.getAppId(uid);
        Optional<Integer> userId;
        if (allUsers) {
            userId = Optional.empty();
        } else {
            userId = Optional.of(UserHandle.getUserId(uid));
        }
        purge(userId, Optional.of(appId));
    }

    private void purgeUser(int uid) {
        purge(Optional.of(uid), Optional.empty());
    }

When the service is started, call registerForPackageRemoval to start listening to the broadcast: ACTION_PACKAGE_FULLY_REMOVED

When the app is uninstalled, the tombstone file of its corresponding uid is also deleted here

Similar to this package deletion, the corresponding files will also be deleted when the user deletes:

    private void registerForUserRemoval() {
        filter.addAction(Intent.ACTION_USER_REMOVED);

Keywords: Java Android Design Pattern

Added by Sweets287 on Sat, 08 Jan 2022 13:57:52 +0200