使用logwrapper工具将可执行程序的输出写入日志系统

在init进程执行动作(action)或启动服务(service)时,默认已将标准输入、标准输出、标准错误从定向到
/dev/__null__这个“无底洞”节点,所以任何的输出都会被忽略,但有时我们确实是想把一些执行文件的输出记录下来
以便我们进行分析,这里logwrapper这个工具可以派上用场了。该工具的大致实现思路是:
logwrapper程序会开辟一个子进程来执行我们的可执行文件,父子进程通过devpts文件系统为伪终端提供的标准接口,
它的挂载点是/dev/pts来进行通信。只要pty的主复合设备/dev/ptmx被打开,就会在/dev/pts下动态创建一个新
的pty设备文件。挂接时,UID、GID及其工作模式会指定给devpts文件系统的所有pty文件。这可 以保证伪终端的安
全性。当然父子进程进行通信的方法还有很多,例如管道、系统调用popen、socketpair(这个带有缓冲,不是很好)
等。下面结合代码来进行简要分析:

// system/core/logwrapper/logwrapper.c -> main函数 // 

 
 
  1. int main(int argc, char* argv[]) {   
  2.     pid_t pid;   
  3.     // 在子进程退出时,手动让父进程产生segmentation fault,这个比较无语,  
  4.     // 不知其有何深意,具体下面再分析。  
  5.     int seg_fault_on_exit = 0;   
  6.     int parent_ptty;   
  7.     int child_ptty;   
  8.     char *child_devname = NULL;   
  9.   
  10.     if (argc < 2) {   
  11.         // 参数传递错误  
  12.         // Usage: logwrapper [-x] BINARY [ARGS ...]  
  13.         usage();   
  14.     }   
  15.     // 显示的帮助信息中所加的参数为-x,但在代码中却变为了-d -_-!  
  16.     //> modify "-d" to "-x" ,下面已经改正了过来  
  17.     if (strncmp(argv[1], "-x", 2) == 0) {   
  18.         seg_fault_on_exit = 1;   
  19.         argc--;   
  20.         argv++;   
  21.     }   
  22.    // 再次进行参数检测  
  23.     if (argc < 2) {   
  24.         usage();   
  25.     }   
  26.   
  27.     // 下面这句说的很清楚,不用socketpair的原因  
  28.     /* Use ptty instead of socketpair so that STDOUT is not buffered */   
  29.     parent_ptty = open("/dev/ptmx", O_RDWR);   
  30.     if (parent_ptty < 0) {   
  31.         // 打开失败时用fatal函数打印信息,具体就是同时用fprintf(stderr, ...)和LOG进行打印  
  32.         // 然后exit退出此程序  
  33.         fatal("Cannot create parent ptty\n");   
  34.     }   
  35.    // 获得与主设备相对应的节点名称  
  36.     if (grantpt(parent_ptty) || unlockpt(parent_ptty) ||   
  37.             ((child_devname = (char*)ptsname(parent_ptty)) == 0)) {   
  38.         fatal("Problem with /dev/ptmx\n");   
  39.     }   
  40.   
  41.     // fork一个进程供子进程运行  
  42.     pid = fork();   
  43.     if (pid < 0) {   
  44.         fatal("Failed to fork\n");   
  45.     } else if (pid == 0) {   
  46.         // 子进程  
  47.         LOG(LOG_INFO, "logwrapper""In child process.");   
  48.         // 子进程打开对应的节点  
  49.         child_ptty = open(child_devname, O_RDWR);   
  50.         if (child_ptty < 0) {   
  51.             fatal("Problem with child ptty\n");   
  52.         }   
  53.       // 关闭无用的fd,用dup2函数进行重定向(核心之处)  
  54.         // redirect stdout and stderr   
  55.         close(parent_ptty);   
  56.         dup2(child_ptty, 1);   
  57.         dup2(child_ptty, 2);   
  58.         close(child_ptty);   
  59.       // 运行具体的程序  
  60.         child(argc - 1, &argv[1]);   
  61.     } else {   
  62.         // 设置一下gid和uid  
  63.         // switch user and group to "log"   
  64.         // this may fail if we are not root,   
  65.         // but in that case switching user/group is unnecessary   
  66.         setgid(AID_LOG);   
  67.         setuid(AID_LOG);   
  68.       // 父进程处理  
  69.         parent(argv[1], seg_fault_on_exit, parent_ptty);   
  70.     }   
  71.   
  72.     return 0;   
  73. }  
// system/core/logwrapper/logwrapper.c --> child函数 //
  1. void child(int argc, char* argv[]) {   
  2.     // create null terminated argv_child array   
  3.     // 配置一下传递给execvp函数的参数(参数数组必需以NULL结尾)  
  4.     char* argv_child[argc + 1];   
  5.     memcpy(argv_child, argv, argc * sizeof(char *));   
  6.     argv_child[argc] = NULL;   
  7.   
  8.     if (execvp(argv_child[0], argv_child)) {   
  9.         // 调用execvp执行失败  
  10.         LOG(LOG_ERROR, "logwrapper",   
  11.             "executing %s failed: %s\n", argv_child[0], strerror(errno));   
  12.         exit(-1);   
  13.     }   
  14. }  
// system/core/logwrapper/logwrapper.c --> parent函数 //
  1. void parent(const char *tag, int seg_fault_on_exit, int parent_read) {   
  2.     int status;   
  3.     char buffer[4096];   
  4.   
  5.     int a = 0;  // start index of unprocessed data   
  6.     int b = 0;  // end index of unprocessed data   
  7.     int sz;   
  8.     // 下面这个负责处理打印逻辑  
  9.     while ((sz = read(parent_read, &buffer[b], sizeof(buffer)-1-b)) > 0) {   
  10.         sz += b;   
  11.         // Log one line at a time   
  12.         for (b = 0; b < sz; b++) {   
  13.             if (buffer[b] == '\r') {   
  14.                 buffer[b] = '\0';   
  15.             } else if (buffer[b] == '\n') {   
  16.                 buffer[b] = '\0';   
  17.                 // 注意到所有的log都是INFO级别(至于为什么没有输出的问题,下面分析)  
  18.                 LOG(LOG_INFO, tag, "%s", &buffer[a]);   
  19.                 a = b + 1;   
  20.             }   
  21.         }   
  22.   
  23.         if (a == 0 && b == sizeof(buffer) - 1) {   
  24.             // buffer is full, flush   
  25.             buffer[b] = '\0';   
  26.             LOG(LOG_INFO, tag, "%s", &buffer[a]);   
  27.             b = 0;   
  28.         } else if (a != b) {   
  29.             // Keep left-overs   
  30.             b -= a;   
  31.             memmove(buffer, &buffer[a], b);   
  32.             a = 0;   
  33.         } else {   
  34.             a = 0;   
  35.             b = 0;   
  36.         }   
  37.   
  38.     }   
  39.     // Flush remaining data   
  40.     if (a != b) {   
  41.         buffer[b] = '\0';   
  42.         LOG(LOG_INFO, tag, "%s", &buffer[a]);   
  43.     }   
  44.     status = 0xAAAA;   
  45.     if (wait(&status) != -1) {  // Wait for child   
  46.         if (WIFEXITED(status)) // 子进程用exit退出  
  47.             LOG(LOG_INFO, "logwrapper""%s terminated by exit(%d)", tag,   
  48.                     WEXITSTATUS(status));   
  49.         else if (WIFSIGNALED(status)) // 子进程被信号中断  
  50.             LOG(LOG_INFO, "logwrapper""%s terminated by signal %d", tag,   
  51.                     WTERMSIG(status));   
  52.         else if (WIFSTOPPED(status)) // 子进程被停止  
  53.             LOG(LOG_INFO, "logwrapper""%s stopped by signal %d", tag,   
  54.                     WSTOPSIG(status));   
  55.     } else   
  56.         LOG(LOG_INFO, "logwrapper""%s wait() failed: %s (%d)", tag,   
  57.                 strerror(errno), errno);   
  58.     // 手动段错误,这样会导致我们的log中会有一大堆无用的信息,反而有用的就被冲掉了!!  
  59.     // 把它注释掉,或者是执行logwrapper时不要加-x选项  
  60.     if (seg_fault_on_exit)   
  61.         *(int *)status = 0;  // causes SIGSEGV with fault_address = status   
  62. }  
一般是通过在init.rc中配置启动,稍后来看;还有一种是在终端下手动使用。
1)先写一个简单的可执行文件,例如hello
// packages/apps/Hello/hello.c //
  1. #include <stdio.h>   
  2. #define PRINT_TIMES 5   
  3. int main(int argc, char **argv)   
  4. {   
  5.     int i;   
  6.     for (i = 0; i < PRINT_TIMES; ++i) {   
  7.         printf("The quick brown fox jumps over a lazy dog.\n");   
  8.     }   
  9.     return 0;   
  10. }   
2)编写Android.mk,然后mm编译,然后将可执行文件推入机器中(例如/system/bin/)
3)进入终端,直接执行hello程序
# hello
The quick brown fox jumps over a lazy dog.
The quick brown fox jumps over a lazy dog.
The quick brown fox jumps over a lazy dog.
The quick brown fox jumps over a lazy dog.
The quick brown fox jumps over a lazy dog.
4)使用logwrapper执行hello程序
# logwrapper /system/bin/hello
发现没有任何输出,然后使用logcat查看发现也没有任何输出-_-!这里问题就出在logwrapper中的LOG这个宏上,
具体如下:
当在直接使用LOG(LOG_INFO, tag, "%s", &buffer[a]); 来打印log时,由于系统log的权限限制,默认是
INFO级别的日志是无法打印的,我们可以追一下代码。LOG这个宏在system/core/include/cutils/log.h这
个头文件中定义,具体如下:
  1. #ifndef LOG   
  2. #define LOG(priority, tag, ...) \   
  3.     LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)   
  4. #endif  
其中LOG_PRI这个宏如下:
  1. #ifndef LOG_PRI   
  2. #define LOG_PRI(priority, tag, ...)                                   \   
  3.     ({                                                                 \   
  4.        if (((priority == ANDROID_LOG_VERBOSE) && (LOG_NDEBUG == 0)) ||  \   
  5.            ((priority == ANDROID_LOG_DEBUG) && (LOG_NDDEBUG == 0))  ||  \   
  6.            ((priority == ANDROID_LOG_INFO) && (LOG_NIDEBUG == 0))   ||  \   
  7.             (priority == ANDROID_LOG_WARN)                          ||  \   
  8.             (priority == ANDROID_LOG_ERROR)                         ||  \   
  9.             (priority == ANDROID_LOG_FATAL))                            \   
  10.                 (void)android_printLog(priority, tag, __VA_ARGS__);     \   
  11.     })   
  12. #endif  
5)修改logwrapper源程序,
看到对于WARN、ERROR、FATAL级别的log可以直接输出,而对于VERBOSE、DEBUG、INFO级别的log必需在定
义相应宏的情况下才可以输出,所以我们将logwrapper源程序的开头宏定义一下:
#define LOG_NIDEBUG 0
然后执行步骤4,发现现在logcat可以打印出我们想要的信息了。
I/logwrapper( 373): In parent process.
I/logwrapper( 373): Call parent(arg...).
I/logwrapper( 374): In child process.
I/logwrapper( 374): Call child(argc - 1, &argv[1]).
I/logwrapper( 373): The quick brown fox jumps over a lazy dog.
I/logwrapper( 373): The quick brown fox jumps over a lazy dog.
I/logwrapper( 373): The quick brown fox jumps over a lazy dog.
I/logwrapper( 373): The quick brown fox jumps over a lazy dog.
I/logwrapper( 373): The quick brown fox jumps over a lazy dog.
I/logwrapper( 373): /system/bin/hello terminated by exit(0)

在init.rc中配置启动,这个相对来说比较简单,具体如下:
  1. service logwrapper /system/bin/logwrapper /system/bin/hello  
  2.     user root   
  3.     group root  
  4.     oneshot 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值