Android dumpsys 实现

文中所有代码基于Android8.0

1.dumpsys在哪,如何实现的?

android开发经常使用 adb shell dumpsys xoxo,xoxo在SystemServer.java里有很多,像activity,window之类的,比如:

ServiceManager.addService(Context.WINDOW_SERVICE, wm);
ServiceManager.addService(Context.INPUT_SERVICE, inputManager);

native里也有,比如:

void MediaPlayerService::instantiate() {
    defaultServiceManager()->addService(
            String16("media.player"), new MediaPlayerService());
}

总之,是我们平时所见的service。

dumpsys能执行,无疑是一个可执行程序,在这里哟:

framework/native/cmds/dumpsys

从它的Android.bp文件中可以看到,会编出一个二进制文件:

cc_binary {
    name: "dumpsys",

    defaults: ["dumpsys_defaults"],

    srcs: [
        "main.cpp",
    ],
}

首先,看下它的main.cpp:

int main(int argc, char* const argv[]) {
    signal(SIGPIPE, SIG_IGN);
    sp sm = defaultServiceManager();
    fflush(stdout);
    if (sm == nullptr) {
        ALOGE("Unable to get default service manager!");
        aerr << "dumpsys: Unable to get default service manager!" << endl;
        return 20;
    }

    Dumpsys dumpsys(sm.get());
    return dumpsys.main(argc, argv);
}

用signal函数捕获了SIGPIPE信号并忽略,防止收到信号后程序退出。接着
拿到了ServiceManager后,用sm生成了一个Dumpsys的实例,然后执行了他的main方法。

接下来看Dumpsys的main方法(其实它也就这一个方法):

int Dumpsys::main(int argc, char* const argv[]) {
    Vector services;
    Vector args;
    ...
    while (1) {
    ...balabala....
       
    for (size_t i = 0; i < N; i++) {
        String16 service_name = std::move(services[i]);

        // 1. 获取service
        sp service = sm_->checkService(service_name);
        if (service != nullptr) {
            int sfd[2];
            
            //2.打开管道,用来读写数据
            if (pipe(sfd) != 0) {
                continue;
            }
            
            //3.生成fd用 unique_fd封装一下
            unique_fd local_end(sfd[0]);
            unique_fd remote_end(sfd[1]);
            sfd[0] = sfd[1] = -1;

            //4.开个线程,准备dump
            std::thread dump_thread([=, remote_end { std::move(remote_end) }]() mutable {
                //5.dump数据
                int err = service->dump(remote_end.get(), args);
                remote_end.reset();
            });

            auto timeout = std::chrono::seconds(timeoutArg);
            auto start = std::chrono::steady_clock::now();
            auto end = start + timeout;

            struct pollfd pfd = {
                .fd = local_end.get(),
                .events = POLLIN
            };

            bool timed_out = false;
            bool error = false;
            while (true) {
     
                auto time_left_ms = [end]() {
                    auto now = std::chrono::steady_clock::now();
                    auto diff = std::chrono::duration_cast(end - now);
                    return std::max(diff.count(), 0ll);
                };
                //6.等待有数据写入
                int rc = TEMP_FAILURE_RETRY(poll(&pfd, 1, time_left_ms()));
                ...
                char buf[4096];
                //7.读出数据
                rc = TEMP_FAILURE_RETRY(read(local_end.get(), buf, sizeof(buf)));
                ...
                //8.写入到标准输出
                if (!WriteFully(STDOUT_FILENO, buf, rc)) {
                    break;
                }
            }
            
        } else {
            aerr << "Can't find service: " << service_name << endl;
        }
    }

    return 0;
}

代码挺长,精简掉一些balabala的其他代码,比如解析参数,错误判断,是否skipservice之类的,主要流程有以下8点,代码中已经标记:
1.1 从ServiceManager中按名字拿出服务,名字是我们所谓的各种xoxo。
1.2 打开一个管道,linux下的管道想必多少有些了解,就是一头写一头读,现在写入端传给service那边去写,dumpsys这边来读。模拟了一个类似场景,见第二小节。
1.3 刚才生成的读写端都是fd,用android的类封装了一下,好处是能用它的析构方法将fd关掉,避免资源泄漏,类似一些智能指针。同时还提供了一个移动构造函数,用于下面创建线程时用 std::move()移动到lambda表达式里面。
1.4 用一个lambda表达式创建线程。
1.5 调用service的dump方法,并把管道的写入端fd传给service,service里会将数据写入这个管道中。
1.6 用 poll 系统调用来监测刚才的管道 读 这一端有没有数据来,也就是service那边是不是写进去了。TEMP_FAILURE_RETRY这个宏在失败了会自动重试。
1.7 当service将数据写入管道后,poll系统调用会返回,接着就可以读取数据了。
1.8 将所有数据全部写到 STDOUT_FILENO ,也就是标准输出,即我们执行命令的terminal,这样就service的将数据显示出来了。

2.pip poll 的模拟demo

上面提到创建pipe,然后写入,然后从另一端读出,这两个方法的基本用法,参考下面的小demo。
编译运行(mac下需要加上-std=c++11,linux不需要):

$ g++ main.cpp -std=c++11 -lpthread
$ ./a.out
oh baby, data is coming ... 
data received : Oh, yes !


#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

constexpr int kTimeOut = 5;

int main(int argc, char** argv) {
  
  //1.创建管道
  int pids[2];
  int ret = pipe(pids);
  
  //2.新线程中延时写入
  std::string src{"Oh, yes !"};
  std::thread t([&]() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    write(pids[1], src.c_str(), src.length());
    close(pids[1]);
  });
  t.detach();

  nfds_t size = 1;
  struct pollfd fds[size];

  fds[0].fd = pids[0];
  fds[0].events = POLLIN;
  //3.等待数据写入
  ret = poll(fds, size, kTimeOut * 1000);

  if (ret == -1) {
    std::cerr << "failed, baby ~" << std::endl;
    return -1;
  }

  if (ret == 0) {
      std::cout << "time out , baby ~" << std::endl;
      return -1;
  }
  //4.读出数据
  if (fds[0].revents & POLLIN) {
    std::cout << "oh baby, data is coming ... " << std::endl;
    char dst[src.length() + 1];
    read(pids[0], dst, src.length());
    dst[src.length()] = '\0';
    close(pids[0]);
    std::cout << "data received : " << dst << std::endl;
  }

}

2.1 打开一个管道,结果保存在pids[2]这个数组中,其中pids[0]用于读取,pids[1]用于写入。
2.2 新线程等待两秒后将数据写入。
2.3 poll在这等待有数据可读(POLLIN)。
2.4 将数据读出来打印。

这样就可以理解dumpsys中这个用法。

3. service 来源 和 dump

从代码上看,service是从IServiceManager这个类里取出来的:

sp service = sm_->checkService(service_name);

这是一个c++的类,那我们ServiceManager.java里添加的不都是java的对象吗?

3.1 native层的ServiceManager
### IServiceManager.cpp

sp defaultServiceManager()
{
    if (gDefaultServiceManager != NULL) return gDefaultServiceManager;

    {
        AutoMutex _l(gDefaultServiceManagerLock);
        while (gDefaultServiceManager == NULL) {
            gDefaultServiceManager = interface_cast(
                ProcessState::self()->getContextObject(NULL));
            if (gDefaultServiceManager == NULL)
                sleep(1);
        }
    }

    return gDefaultServiceManager;
}

可以看出,native层的servicemanager是这样拿到的:

gDefaultServiceManager = interface_cast(
                ProcessState::self()->getContextObject(NULL));

先通过ProcessState::self()->getContextObject(NULL) 生成了一个BpBinder(0)的对象,然后又通过interface_cast这个宏生成了一个BpManagerService(BpBinder(0))的实例。
关于Binder,后面文章专门分解。

3.2 java层的ServiceManager
### ServiceManager.java
   private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative
                .asInterface(Binder.allowBlocking(BinderInternal.getContextObject()));
        return sServiceManager;
    }

首先BinderInternal.getContextObject()拿到一个对象,contextObject跟上面有点像,继续往下看,一个native方法:

   /**
     * Return the global "context object" of the system.  This is usually
     * an implementation of IServiceManager, which you can use to find
     * other services.
     */
    public static final native IBinder getContextObject();

意思是,你能通过这个 context object 去find 其他service,这不就是servicemanager吗?

继续来到jni层:

static jobject android_os_BinderInternal_getContextObject(JNIEnv* env, jobject clazz)
{
    sp b = ProcessState::self()->getContextObject(NULL);
    return javaObjectForIBinder(env, b);
}

同样是ProcessState::self()->getContextObject(NULL); 然后转成一个java对象返回了。也就证明无论java还是native,用的都是一个servicemanager,所以service都在一个native对象里存着。
当然servicemanager从8.0开始不止一个,vndservicemanager,hwservicemanager也同时存在,现在我们先关心这一个。后面文章再分解。

3.3 dump方法

作为系统服务,肯定要与binder通信,下面看native和java 系统服务的实现,超精简,主要看dump方法。后面单独写如何实现native和java的系统服务。

3.3.1 java层一般用aidl生成接口,比如PMS:

public class PackageManagerService extends IPackageManager.Stub
        implements PackageSender {
        @Override
    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
        if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
        .....
    }
}

IPackageManager.Stub 继承Binder类,实现dump方法, 第一个参数FileDescriptor 就是我们在dumpsys用管道创建的写入端。
第一句checkpermission一定要有,否则google cts过不了,自己写服务要注意。

3.3.2 native层的服务,比如MediaPlayerService:

//继承BnMediaPlayerService
class MediaPlayerService : public BnMediaPlayerService {
  virtual status_t        dump(int fd, const Vector& args) const;
}
//继承BnInterface
class BnMediaPlayerService: public BnInterface {}
//继承BBinder,BBinder继承IBinder,从而拥有dump方法
template
class BnInterface : public INTERFACE, public BBinder {}

这样两种service都有相同的接口,都存储在一个servicemanager中,所以dumpsys可以统一执行命令,dump不同的service。

欢迎一起讨论。

你可能感兴趣的:(Android dumpsys 实现)