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<IServiceManager> 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<String16> services;
    Vector<String16> args;
    ...
    while (1) {
    ...balabala....
       
    for (size_t i = 0; i < N; i++) {
        String16 service_name = std::move(services[i]);

        // 1. 获取service
        sp<IBinder> 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<std::chrono::milliseconds>(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 <cstdio>
#include <unistd.h>
#include <fcntl.h>
#include <sys/poll.h>

#include <unistd.h>
#include <iostream>
#include <thread>
#include <string>

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<IBinder> service = sm_->checkService(service_name);

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

3.1 native层的ServiceManager
### IServiceManager.cpp

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

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

    return gDefaultServiceManager;
}

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

gDefaultServiceManager = interface_cast<IServiceManager>(
                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<IBinder> 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<String16>& args) const;
}
//继承BnInterface
class BnMediaPlayerService: public BnInterface<IMediaPlayerService> {}
//继承BBinder,BBinder继承IBinder,从而拥有dump方法
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder {}

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

欢迎一起讨论。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容

  • 1:InputChannel提供函数创建底层的Pipe对象 2: 1)客户端需要新建窗口 2)new ViewRo...
    自由人是工程师阅读 5,308评论 0 18
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,138评论 25 707
  • 昨天下午,听了果语市代俐俐关于自己微商心路历程的分享。以前对于这种分享,我本能地是抗拒的,觉得就是打鸡血吧,成功学...
    绽蕊向阳阅读 272评论 1 1
  • 小月亮9月11日出生,距离现在已经有34天了,我也终于有时间有精力来记录下月亮出现到生产的过程。 猴宝宝降临 20...
    余小头阅读 1,158评论 3 0
  • 我就是在和时光谈恋爱,没有永远,只有一瞬。——若雪 迷迷糊糊中,似乎有一束阳光给她挠着痒痒,不舒服,于是睁开了眼睛...
    一心小记阅读 289评论 0 4