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

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

<style type="text/css"> <!-- pre.western {font-family:"DejaVu Sans Mono",monospace} pre.cjk {font-family:"DejaVu Sans",monospace} pre.ctl {font-family:"DejaVu Sans Mono",monospace} p {margin-bottom:0.21cm} --> </style>

////// system/core/logwrapper/logwrapper.c -> main函数 ////// 
int main(int argc, char* argv[]) { 
    pid_t pid; 
    // 在子进程退出时,手动让父进程产生segmentation fault,这个比较无语,
    // 不知其有何深意,具体下面再分析。
    int seg_fault_on_exit = 0; 
    int parent_ptty; 
    int child_ptty; 
    char *child_devname = NULL; 

    if (argc < 2) { 
        // 参数传递错误
        // Usage: logwrapper [-x] BINARY [ARGS ...]
        usage(); 
    } 
    // 显示的帮助信息中所加的参数为-x,但在代码中却变为了-d -_-!
    //> modify "-d" to "-x" ,下面已经改正了过来
    if (strncmp(argv[1], "-x", 2) == 0) { 
        seg_fault_on_exit = 1; 
        argc--; 
        argv++; 
    } 
   // 再次进行参数检测
    if (argc < 2) { 
        usage(); 
    } 

    // 下面这句说的很清楚,不用socketpair的原因
    /* Use ptty instead of socketpair so that STDOUT is not buffered */ 
    parent_ptty = open("/dev/ptmx", O_RDWR); 
    if (parent_ptty < 0) { 
        // 打开失败时用fatal函数打印信息,具体就是同时用fprintf(stderr, ...)和LOG进行打印
        // 然后exit退出此程序
        fatal("Cannot create parent ptty\n"); 
    } 
   // 获得与主设备相对应的节点名称
    if (grantpt(parent_ptty) || unlockpt(parent_ptty) || 
            ((child_devname = (char*)ptsname(parent_ptty)) == 0)) { 
        fatal("Problem with /dev/ptmx\n"); 
    } 

    // fork一个进程供子进程运行
    pid = fork(); 
    if (pid < 0) { 
        fatal("Failed to fork\n"); 
    } else if (pid == 0) { 
        // 子进程
        LOG(LOG_INFO, "logwrapper", "In child process."); 
        // 子进程打开对应的节点
        child_ptty = open(child_devname, O_RDWR); 
        if (child_ptty < 0) { 
            fatal("Problem with child ptty\n"); 
        } 
      // 关闭无用的fd,用dup2函数进行重定向(核心之处)
        // redirect stdout and stderr 
        close(parent_ptty); 
        dup2(child_ptty, 1); 
        dup2(child_ptty, 2); 
        close(child_ptty); 
      // 运行具体的程序
        child(argc - 1, &argv[1]); 
    } else { 
        // 设置一下gid和uid
        // switch user and group to "log" 
        // this may fail if we are not root, 
        // but in that case switching user/group is unnecessary 
        setgid(AID_LOG); 
        setuid(AID_LOG); 
      // 父进程处理
        parent(argv[1], seg_fault_on_exit, parent_ptty); 
    } 

    return 0; 
}
<style type="text/css"> <!-- pre.western {font-family:"DejaVu Sans Mono",monospace} pre.cjk {font-family:"DejaVu Sans",monospace} pre.ctl {font-family:"DejaVu Sans Mono",monospace} p {margin-bottom:0.21cm} --> </style>
////// system/core/logwrapper/logwrapper.c --> child函数 //////
void child(int argc, char* argv[]) { 
    // create null terminated argv_child array 
    // 配置一下传递给execvp函数的参数(参数数组必需以NULL结尾)
    char* argv_child[argc + 1]; 
    memcpy(argv_child, argv, argc * sizeof(char *)); 
    argv_child[argc] = NULL; 

    if (execvp(argv_child[0], argv_child)) { 
        // 调用execvp执行失败
        LOG(LOG_ERROR, "logwrapper", 
            "executing %s failed: %s\n", argv_child[0], strerror(errno)); 
        exit(-1); 
    } 
}
<style type="text/css"> <!-- pre.western {font-family:"DejaVu Sans Mono",monospace} pre.cjk {font-family:"DejaVu Sans",monospace} pre.ctl {font-family:"DejaVu Sans Mono",monospace} p {margin-bottom:0.21cm} --> </style>
////// system/core/logwrapper/logwrapper.c --> parent函数 //////
void parent(const char *tag, int seg_fault_on_exit, int parent_read) { 
    int status; 
    char buffer[4096]; 

    int a = 0;  // start index of unprocessed data 
    int b = 0;  // end index of unprocessed data 
    int sz; 
    // 下面这个负责处理打印逻辑
    while ((sz = read(parent_read, &buffer[b], sizeof(buffer)-1-b)) > 0) { 
        sz += b; 
        // Log one line at a time 
        for (b = 0; b < sz; b++) { 
            if (buffer[b] == '\r') { 
                buffer[b] = '\0'; 
            } else if (buffer[b] == '\n') { 
                buffer[b] = '\0'; 
                // 注意到所有的log都是INFO级别(至于为什么没有输出的问题,下面分析)
                LOG(LOG_INFO, tag, "%s", &buffer[a]); 
                a = b + 1; 
            } 
        } 

        if (a == 0 && b == sizeof(buffer) - 1) { 
            // buffer is full, flush 
            buffer[b] = '\0'; 
            LOG(LOG_INFO, tag, "%s", &buffer[a]); 
            b = 0; 
        } else if (a != b) { 
            // Keep left-overs 
            b -= a; 
            memmove(buffer, &buffer[a], b); 
            a = 0; 
        } else { 
            a = 0; 
            b = 0; 
        } 

    } 
    // Flush remaining data 
    if (a != b) { 
        buffer[b] = '\0'; 
        LOG(LOG_INFO, tag, "%s", &buffer[a]); 
    } 
    status = 0xAAAA; 
    if (wait(&status) != -1) {  // Wait for child 
        if (WIFEXITED(status)) // 子进程用exit退出
            LOG(LOG_INFO, "logwrapper", "%s terminated by exit(%d)", tag, 
                    WEXITSTATUS(status)); 
        else if (WIFSIGNALED(status)) // 子进程被信号中断
            LOG(LOG_INFO, "logwrapper", "%s terminated by signal %d", tag, 
                    WTERMSIG(status)); 
        else if (WIFSTOPPED(status)) // 子进程被停止
            LOG(LOG_INFO, "logwrapper", "%s stopped by signal %d", tag, 
                    WSTOPSIG(status)); 
    } else 
        LOG(LOG_INFO, "logwrapper", "%s wait() failed: %s (%d)", tag, 
                strerror(errno), errno); 
    // 手动段错误,这样会导致我们的log中会有一大堆无用的信息,反而有用的就被冲掉了!!
    // 把它注释掉,或者是执行logwrapper时不要加-x选项
    if (seg_fault_on_exit) 
        *(int *)status = 0;  // causes SIGSEGV with fault_address = status 
}
一般是通过在init.rc中配置启动,稍后来看;还有一种是在终端下手动使用。
1)先写一个简单的可执行文件,例如hello
<style type="text/css"> <!-- pre.western {font-family:"DejaVu Sans Mono",monospace} pre.cjk {font-family:"DejaVu Sans",monospace} pre.ctl {font-family:"DejaVu Sans Mono",monospace} p {margin-bottom:0.21cm} --> </style>
////// packages/apps/Hello/hello.c //////
#include <stdio.h> 
#define PRINT_TIMES	5 
int main(int argc, char **argv) 
{ 
	int i; 
	for (i = 0; i < PRINT_TIMES; ++i) { 
		printf("The quick brown fox jumps over a lazy dog.\n"); 
	} 
	return 0; 
} 
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这
个头文件中定义,具体如下:
#ifndef LOG 
#define LOG(priority, tag, ...) \ 
    LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__) 
#endif
<style type="text/css"> <!-- pre.western {font-family:"DejaVu Sans Mono",monospace} pre.cjk {font-family:"DejaVu Sans",monospace} pre.ctl {font-family:"DejaVu Sans Mono",monospace} p {margin-bottom:0.21cm} --> </style>其中LOG_PRI这个宏如下:
#ifndef LOG_PRI 
#define LOG_PRI(priority, tag, ...)                                   \ 
    ({                                                                 \ 
       if (((priority == ANDROID_LOG_VERBOSE) && (LOG_NDEBUG == 0)) ||  \ 
           ((priority == ANDROID_LOG_DEBUG) && (LOG_NDDEBUG == 0))  ||  \ 
           ((priority == ANDROID_LOG_INFO) && (LOG_NIDEBUG == 0))   ||  \ 
            (priority == ANDROID_LOG_WARN)                          ||  \ 
            (priority == ANDROID_LOG_ERROR)                         ||  \ 
            (priority == ANDROID_LOG_FATAL))                            \ 
                (void)android_printLog(priority, tag, __VA_ARGS__);     \ 
    }) 
#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中配置启动,这个相对来说比较简单,具体如下:
service logwrapper /system/bin/logwrapper /system/bin/hello
    user root 
    group root
    oneshot

你可能感兴趣的:(日志系统)