在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
分享到:
相关推荐
此示例定义了一个名为`akmd`的服务,该服务将在启动时通过`logwrapper`运行`/sbin/akmd`程序,这样可以将标准输出和标准错误重定向到Android日志系统中。 #### 六、Options **Options**用于控制服务的行为。常见的...
在软件开发中,日志记录是不可或缺的一部分,它能够帮助开发者追踪程序运行状态,定位错误,优化性能。Log4cplus是一个广泛使用的C++日志记录库,它借鉴了Java的log4j理念,为C++程序员提供了强大的日志管理功能。...
`hcid`和`hciattach`的默认日志输出指向`/dev/null`,可通过编辑`init.rc`和`init.PLATFORM.rc`文件,将这些守护进程置于`logwrapper`下运行,以便日志信息能被`logcat`捕获,便于分析和调试。 #### 蓝牙工具集 ...
内核记录器是一个内核模块,可以替代logger或logwrapper 。 它的日志类似于systemd的日志,可以由dmesg或journalctl读取。 用例 您需要记录initramfs的日志,并希望日志记录可以保留这些日志。 您需要一个记录器,...
在`BaseSpringBootTest`类中,我们创建了一个`LogWrapper`实例用于日志记录,并在`@Before`和`@After`方法中定义了测试开始和结束时的日志信息。这样可以方便追踪测试执行的状态。 接着,我们创建了一个名为`...
测试步骤/请眼中注意:打开WIFI之前必须将 USB接口的WIFI:RTL8188CU插入到USB1中去,否则会死机!!!!切记! 1、(如果SDK没有编译过,请跳过本次执行步骤!!!!) rootroot@cm-System-Product-Name:/home/...
全志R16平台的android6.0.1系统下的RTL8188EU的配置V1.0 2017/5/31 10:30 1、R:\wyb\rtl8188eu_r16m_20161208\android\device\softwinner\astar-evb30\overlay\frameworks\base\core\res\res\values\config.xml ...
全志R16平台的Android6系统下调通rtl8189es 2017/8/28 9:41 版本:V1.1 1、干掉BT选项: Q:\r16m\rtl8189es_r16m\Android\device\softwinner\astar-evb30\overlay\frameworks\base\core\res\res\values\...
全志R16平台的Android6系统下调通rtl8189es 2017/8/28 9:41 版本:V1.1 1、干掉BT选项: Q:\r16m\rtl8189es_r16m\Android\device\softwinner\astar-evb30\overlay\frameworks\base\core\res\res\values\...
关闭这里(默认配置为使用正基的WIFI模块了,比如:AP6212A0版本): # 1.2 broadcom wifi support #BOARD_WIFI_VENDOR := broadcom 关闭这里(没有蓝牙): ## 2. Bluetooth Configuration ## make sure BOARD_...