MTK中 使用logwrapper进行守护进程log的重定向

1.基础知识

在Android有一些应用程序的日志输出是通过printf之类的标准函数输出的,这类log是无法记录到的。主要是由于init进程会把0,1,2三个fd指向到/dev/null,而其他进程都是由init fork出来的,所以标准输出和标准错误输出都会继承自父进程,所以默认也都是不打印出来的。

android init的实现在system/core/init/init.c中:


int main(int argc, char** argv) {

......


 // At this point we're in the second stage of init.

 InitKernelLogging(argv);

 LOG(INFO) << "init second stage started!";

......
}

init会执行log初始化动作,也就是把所有的标准输入标准输出和标准错误输出都指向/dev/null:


void InitKernelLogging(char* argv[]) {

    // Make stdin/stdout/stderr all point to /dev/null.

    int fd = open("/sys/fs/selinux/null", O_RDWR);

    if (fd == -1) {

        int saved_errno = errno;

        android::base::InitLogging(argv, &android::base::KernelLogger);

        errno = saved_errno;

        PLOG(FATAL) << "Couldn't open /sys/fs/selinux/null";

    }

    dup2(fd, 0);

    dup2(fd, 1);

    dup2(fd, 2);

    if (fd > 2) close(fd);



    android::base::InitLogging(argv, &android::base::KernelLogger);

}

android中提供了logwrapper程序用来重定向log的输出,重定向的log可以使用logcat查看,我们来看下他的实现机制又是怎样的呢?

logwrapper的源代码实现在system/core/logwrapper中,原理如下:

通过logwrapper的封装来执行一个command,logwrapper会fork一个子进程,并在子进程中exec执行cmd,父进程负责和子进程通讯,并记录子进程输出的log,父进程会根据logwrapper的参数来选择对应的log输出方式:


/* Log directly to the specified log */

static void do_log_line(struct log_info *log_info, char *line) {

    if (log_info->log_target & LOG_KLOG) {

        klog_write(6, log_info->klog_fmt, line);

    }

    if (log_info->log_target & LOG_ALOG) {

        ALOG(LOG_INFO, log_info->btag, "%s", line);

    }

    if (log_info->log_target & LOG_FILE) {

        fprintf(log_info->fp, "%s\n", line);

    }

}

父进程起到一个log重定向的作用,它收取子进程输出的log,并通过其他方式输出出来,从上面的一段实现上可以看出,父进程可以通过do_log_line来按照三种方式来输出:

  1. LOG_KLOG的方式输出,调用的是klog_write来输出kernel log
  2. LOG_ALOG,这种是输出给logcat使用的log,默认是LOG INFO级别
  3. LOG_FILE,输出log到指定file文件,在我看得这个版本代码中,此方法暂时还不可用

父进程和子进程采用的是ptty机制来做进程通讯的,具体实现在system/core/logwrapper/logwrap.c中:


int android_fork_execvp_ext(int argc, char* argv[], int *status, bool ignore_int_quit,

        int log_target, bool abbreviated, char *file_path,

        void *unused_opts, int unused_opts_len) {

    pid_t pid;

    int parent_ptty;

    int child_ptty;

    struct sigaction intact;

    struct sigaction quitact;

    sigset_t blockset;

    sigset_t oldset;

    int rc = 0;



    LOG_ALWAYS_FATAL_IF(unused_opts != NULL);

    LOG_ALWAYS_FATAL_IF(unused_opts_len != 0);



    rc = pthread_mutex_lock(&fd_mutex);

    if (rc) {

        ERROR("failed to lock signal_fd mutex\n");

        goto err_lock;

    }



    /* Use ptty instead of socketpair so that STDOUT is not buffered */          //使用ptty代替socket,因为socket是有缓冲的

    parent_ptty = TEMP_FAILURE_RETRY(open("/dev/ptmx", O_RDWR));

    if (parent_ptty < 0) {

        ERROR("Cannot create parent ptty\n");

        rc = -1;

        goto err_open;

    }



    char child_devname[64];

    if (grantpt(parent_ptty) || unlockpt(parent_ptty) ||

            ptsname_r(parent_ptty, child_devname, sizeof(child_devname)) != 0) {

        ERROR("Problem with /dev/ptmx\n");

        rc = -1;

        goto err_ptty;

    }



    child_ptty = TEMP_FAILURE_RETRY(open(child_devname, O_RDWR));

    if (child_ptty < 0) {

        ERROR("Cannot open child_ptty\n");

        rc = -1;

        goto err_child_ptty;

    }



    sigemptyset(&blockset);

    sigaddset(&blockset, SIGINT);

    sigaddset(&blockset, SIGQUIT);

    pthread_sigmask(SIG_BLOCK, &blockset, &oldset);



    pid = fork();

    if (pid < 0) {

        close(child_ptty);

        ERROR("Failed to fork\n");

        rc = -1;

        goto err_fork;

    } else if (pid == 0) {

        pthread_mutex_unlock(&fd_mutex);

        pthread_sigmask(SIG_SETMASK, &oldset, NULL);

        close(parent_ptty);                                       //创建的子进程会继承父进程的fd,所以要关闭不需要的fd



        dup2(child_ptty, 1);

        dup2(child_ptty, 2);

        close(child_ptty);



        child(argc, argv);

    } else {

        close(child_ptty);                                            //父进程也要关闭不需要的fd,剩余的fd就是用于进程交互的fd了

        if (ignore_int_quit) {

            struct sigaction ignact;



            memset(&ignact, 0, sizeof(ignact));

            ignact.sa_handler = SIG_IGN;

            sigaction(SIGINT, &ignact, &intact);

            sigaction(SIGQUIT, &ignact, &quitact);

        }



        rc = parent(argv[0], parent_ptty, pid, status, log_target,

                    abbreviated, file_path);

    }



    if (ignore_int_quit) {

        sigaction(SIGINT, &intact, NULL);

        sigaction(SIGQUIT, &quitact, NULL);

    }

err_fork:

    pthread_sigmask(SIG_SETMASK, &oldset, NULL);

err_child_ptty:

err_ptty:

    close(parent_ptty);

err_open:

    pthread_mutex_unlock(&fd_mutex);

err_lock:

    return rc;

}

 

2.参考patch

1. 首先在  init.rc 需要调试的 service中  添加 如下:

service wpa_supplicant /system/bin/logwrapper /vendor/bin/hw/wpa_supplicant \

 2. 修改 相关 selinux

下面 XXX 代表  自己添加的 service 名称
也就是 logwrapper 和 自己调试的  守护进程 都在 同一个 XXX_exec  域


diff --git a/prebuilts/api/28.0/private/file_contexts b/prebuilts/api/28.0/private/file_contexts
index f195dc762..783f1d49e 100644
--- a/prebuilts/api/28.0/private/file_contexts
+++ b/prebuilts/api/28.0/private/file_contexts
@@ -256,7 +256,7 @@
 /system/bin/traced_probes        u:object_r:traced_probes_exec:s0
 /system/bin/uncrypt     u:object_r:uncrypt_exec:s0
 /system/bin/update_verifier u:object_r:update_verifier_exec:s0
-/system/bin/logwrapper  u:object_r:system_file:s0
+/system/bin/logwrapper  u:object_r:XXX_exec:s0
 /system/bin/vdc         u:object_r:vdc_exec:s0
 /system/bin/cppreopts.sh   u:object_r:cppreopts_exec:s0
 /system/bin/preopt2cachename u:object_r:preopt2cachename_exec:s0
diff --git a/private/file_contexts b/private/file_contexts
index f195dc762..783f1d49e 100644
--- a/private/file_contexts
+++ b/private/file_contexts
@@ -256,7 +256,7 @@
 /system/bin/traced_probes        u:object_r:traced_probes_exec:s0
 /system/bin/uncrypt     u:object_r:uncrypt_exec:s0
 /system/bin/update_verifier u:object_r:update_verifier_exec:s0
-/system/bin/logwrapper  u:object_r:system_file:s0
+/system/bin/logwrapper  u:object_r:XXX_exec:s0
 /system/bin/vdc         u:object_r:vdc_exec:s0
 /system/bin/cppreopts.sh   u:object_r:cppreopts_exec:s0
 /system/bin/preopt2cachename u:object_r:preopt2cachename_exec:s0

 

diff --git a/prebuilts/api/28.0/private/XXX.te b/prebuilts/api/28.0/private/XXX.te
index 6fcaaa3a4..4dc63023f 100644
--- a/prebuilts/api/28.0/private/XXX.te
+++ b/prebuilts/api/28.0/private/XXX.te
@@ -5,3 +5,4 @@ typeattribute XXX coredomain;
 
 init_daemon_domain(XXX)
+allow XXX XXX_exec:file { execute_no_trans };

diff --git a/private/XXX.te b/private/XXX.te
index 6fcaaa3a4..4dc63023f 100644
--- a/private/XXX.te
+++ b/private/XXX.te
@@ -5,3 +5,4 @@ typeattribute XXX coredomain;
 
 init_daemon_domain(XXX)

+allow XXX XXX_exec:file { execute_no_trans };

3.关闭 selinux 调试。通过logcat |grep XXX 关键词 查看调试信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值