问题:
在 iOS 的开发过程中总是离不开 Debug,调试的时候都是依靠 XCode log 输出来追踪确定问题。
但如果离开了 XCode 的时候仍然想看到日志的输出,比如在调试 App 与硬件的交互的时候,这时候应该怎么办?
解决思路:
方法一:
第一反应是,写个类似于 NSLog 的输出函数,把想要的查看的信息输出到 TextView 上就好了。但很快就否决了,原因有2个:
- 每一个你想要测试的地方都要添加自定打印输出,代码侵入性大,而且 bug 的出现往往在你意想不到的地方
- 内核代码一般都是 C/C++ 写的,用不了 Cocoa 的框架,想要在 TextView 上打印出日志,意味着你要把 printf 的 log 一层一层往上传递到 Cocoa 层,麻烦!
方法二:
Hook NSLog 和 print 等输出函数,考虑到 Method Swizzing 方式只能动态替换 OC 方法,对于 C 函数却无能为力。Google 了一下, 发现 facebook 开源的 fishhook 可以 hook C API 。那么这样一来的话就只需要 Hook 常用的几个输出就可以了。试了一下确实可以实现,使用fishhook hook NSLog 函数。
参考链接:Log message send
方法三:
深究 NSLog 的调用关系
NSlog->_CFLogvEx->0x32262f20
_CFLogvEX 调用
CFStringCreateWithFormatAndArgumentsAux
CFStringGetLength
CFStringGetMaximumSizeForEncoding
进行字符串格式化,然后 malloc 一块内存通过 CFStringGetCString 写入函数 0x32262f20 , 再调用 CFAbsoluteTimeGetCurrent 获得系统时间,CFGetProgname 获得 mach-o 文件名,getpid 获得进程 pid
然后,重点来了:
0x32263250 <<redacted>+816>: movs r0, #2
0x32263252 <<redacted>+818>: blx 0x322b541c <dyld_stub_writev>
没错,写入文件句柄 2,也就是 stderr 。(0 是标准输入,1 是标准输出,2 是错误输出)
writev 执行完后,可以在 stderr 上看到格式化好的字符串。
结论:要获得 NSLog 输出的信息只需要重定向标准输出 stdout 和错误输出 stderr 即可。其他的输出函数也是一样,这样一来,只需要重定向 stdout 和 stderr 就可以做到把所有的打印日志输出到任意地方了,bingo !
解决办法:
以上 3 中方法中,最有意思的最精简的为第三种方法,那么现在就来实现第三种方法。
关于重定向,需要用到 dup2 函数。
int dup2(int oldfd, int newfd);
现在,只需要创建一个文件,然后将其句柄通过函数 dup2 传入,替换到默认的句柄就好了。
int substrateInit()
{
int fd;
char pathBuffer[1000];
char timeBuff[20];
time_t now = time(NULL);
strftime(timeBuff, 20, "%Y-%m-%d %H:%M:%S", localtime(&now));
fflush(stdout);
fflush(stderr);
snprintf(pathBuffer, sizeof(pathBuffer), "%s/Library/stdout-%s.log", getenv("HOME"), timeBuff);
setvbuf(stdout,NULL,_IONBF,0);
fd = open(pathBuffer, (O_RDWR | O_CREAT), 0644);
if(-1 == fd)
{
printf("\n open() failed with error [%s]\n",strerror(errno));
return -1;
} else {
dup2(fd, STDOUT_FILENO);
dup2(fd, STDERR_FILENO);
printf("Here is dll init, redirect stdout and stderr to logfile\n");
}
return 0;
}
打开 Library/ 目录下的日志文件,日志全都写到里面去了,好好耍去吧~
参考链接:ios NSLog 日志重定向到文件中保存 ,这个只能重定向 NSLog 的,printf 的输出是捕获不到的。