android使用logwrapper进行log重定向
16lz
2021-01-25
在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;}
示例:
servcie akmd /system/bin/logwrapper /sbin/akmd
更多相关文章
- GitHub 标星 2.5K+!教你通过玩游戏的方式学习 VIM!
- 一款霸榜 GitHub 的开源 Linux 资源监视器!
- android 定位的4种方式介绍
- Android基于Pull方式解析xml的方法详解
- 安装android studio报错Failed to install Intel HAXM的解决方法
- Android(安卓)IPC 进程间通信实现理解
- Android中使用GPS和NetWork获取定位信息
- android 中组件 service
- Framework笔记 | Android(安卓)Framework用到了哪些IPC方式,分别