Android日志系统分析

您所在的位置:网站首页 utillog Android日志系统分析

Android日志系统分析

#Android日志系统分析| 来源: 网络整理| 查看: 265

以前做项目的时候,使用xposed对使用过爱加密加固过的应用进行hook时,出现过完全打印不出日志的情况,当时完全不知道是什么原因导致的。看到看雪的这篇文章《解决爱加密加固之后使用xposed hook的时候log打印不出来的问题》,加上最近又在看一些系统内核的东西,所以有了这篇文章。· 本文篇幅较长,可以先马再阅读哦~

Android提供的Logger日志系统是基于内核的Logger日志驱动程序实现的,它将日志记录保存在内核空间内。为了内存空间能够被合理地利用,Logger日志驱动程序会在内部使用一个环形的缓冲区来对日志进行保存。所以当缓冲区满了之后,新的日志就会覆盖掉旧的日志。

由于旧的日志会被新的日志覆盖,所以Logger日志驱动程序会根据日志的类型和日志的输出量来对日志记录进行分类。主要分成四类:main, system, radio以及events

这四种类型的日志分别通过/dev/log/main, /dev/log/system, /dev/log/radio, /dev/log/events 四个设备文件来进行访问。

main的日志是应用程序级别;system的日志是系统级别的,这个类型的日志相较于程序级别的日志会更重要,所以与main类型的日志记录分开;events类型是用来诊断系统问题的记录;radio的日志记录是无线设备相关的,日志记录量比较大,所以单独记录在一块,这样可以防止覆盖掉其他类型的日志记录。

在Android系统的框架层中提供了android.util.log, android.util.Slog, android.util.Eventlog三个Java接口来向Logger日志驱动程序中写入日志

他们写入的日志类型分别是main, system和events,如果使用android.util.log和android.util.Slog接口写入日志的标签值是以”RIL”开头或是等于"HTC_RIL", "AT", "GSM", "STK", "CDMA", "PHONE"和"SMS"时,会转换成radio类型的日志写入到logger日志驱动程序中。

Android系统在运行时库层中也提供了三组C/C++宏来向Logger日志驱动程序中写入日志。其中宏LOGV, LOGD, LOGI, LOGW和LOGE用来写入main类型的日志,宏SLOGV, SLOGD, SLOGI, SLOGW和SLOGE用来写入system类型的日志,宏LOG_EVENT_INT, LOG_EVENT_LONG, LOG_EVENT_STRING用来写入events类型的日志。

不管Java接口还是C/C++日志的写入接口最终都会通过运行库层的日志库liblog来向Logger日志驱动程序中写入日志。系统还提供了一个名为Logcat的工具来读取和显示Logger日志驱动程序中的日志。

Android系统日志系统整体框架如下图所示:

01 - Logger日志格式

上面提到了Logger驱动程序的日志一共有四种类型,其中main、system和radio三种类型的日志格式是相同的。日志格式如下所示:

Priority:是一个整数,代表的是日志的优先级;

tag:是一个字符串,代表的是日志的标签;

message:是一个字符串,代表的是日志的内容。

日志的优先级按重要程度不同划分为:VERBOSE、DEBUG、INFO、WARN、ERROR和FATAL六种。

events类型的日志格式和前三种有一些差别,它并没有优先级。它的格式如下所示:

Events类型日志中,

tag:也表示日志的标签,但是它是一个整数,由于它是个整型,所以在显示时它的可读性很差,所以通过系统中的日志标签文件/system/etc/event-log-tags来描述tag数值的含义。

message:代表的是日志的内容,message一块二进制的数据,具体格式由日志写入者决定。

一般情况日志内容是由一个或是多个值组成的,每个值的前面都有一个字段描述它的类型。具体如下所示:

类型使用1-4的数值来描述,分别是1(int),2(long),3(string),4(list),日志标签文件/system/etc/event-log-tags还用来描述events类型的日志内容的格式,日志标签文件中每一条内容格式如下所示:

Tag number :表示的是日志标签的值,它的取值范围是0~2149483648;

Tagname :表示的是标签的值对应的字符串描述,他是由大小写字母,下划线和数字组成的;

Format for tag value :是用来描述日志内容的值格式,具体如下所示:

name:日志内容值的名称;

data type:日志内容的数据类型;

data unit:日志内容值的数据单位,它是个整型,取值范围为1~6,不同的数值代表的含义:1(Number ofobjects), 2(Number of bytes), 3(Number of milliseconds), 4(Number of allocations), 4(ID)和6(Percent)。

如下是从我设备文件系统中截的一张even—log-tags文件中部分内容的图,结果如下所示:

截图中1005表示的是日志标签值(tag),tag_def是代表tag为1005时的含义。日志内容由三个值组成分别为tag(整型),name(字符串)和format(字符串)。

02 - Logger日志驱动程序

Logger日志驱动程序是在系统内核空间中实现的,它的目录结构如下所示:

Logger日志驱动程序中主要使用到了logger_entry, logger_log, long_reader三个结构体,三个结构体的定义如下所示:

logger_entry结构体:用来描述一个日志记录,一个日志记录最大长度为4KB,日志记录的有效负载长度最大等于4KB减去结构体logger_entry的大小。

len:表示实际日志记录的有效负载长度。

_pad:没有实际的意义,用来将pid的地址值对齐到4字节边界的;

tidpid:分别记录写入该日志记录的进程PID(进程ID)和TGID(线程组ID);

nsecsec:记录写入日志的时间;

msg:代表世界写入的日志内容。

logger_log:用来描述一个日志缓冲区,在logger驱动程序中,每一种类型的日志都对应一个单独的日志缓冲区。

buffer:指向一个内核缓冲区,用来保存日志记录,它是循环使用的,大小由size决定。

wq:是一个等待队列,用来记录那些正在等待读取新的日志记录的进程;

readers:是一个列表,用来记录那些正在读取日志记录的进程,每一个进程都使用一个结构体logger_reader来描述;

mutex:是一个互斥量用来保护日志缓冲区的并发。

Logger_readerv:用来描述一个正在读取某一个日志缓冲区的日志记录的进程。

list:是一个列表项,用来连接所有读取同一类型日志记录的进程,

r_off:表示当前进程要读取的下一条日志记录在日志缓冲区的位置。

03 - 日志设备初始化过程

日志设备初始化过程是在Logger日志驱动程序的入口logger_init中进行的。

init_log函数中,使用misc_register函数将对应的日志设备注册到系统中,系统中所有的misc设备都保存在一个misc_list列表中。检查所有注册的misc设备的从设备是否被注册,部分关键代码如下所示:

当日志驱动程序注册一个日志设备时,内核就会发出一个uevent事件,然后通过init进程中的handle_device_event函数来处理,部分关键代码如下所示:

函数handle_device_event发现新注册的设备类型为misc,并且名称是以log_开头的,他便会在/dev目录创建一个log目录,然后去掉log_子字符串,所以生成的是main,events和radio三个设备文件

添加了设备就会调用logger_opne函数来对文件进行打开操作,然后使用nonseekable_open函数将对应的设备文件设置为不可以随机访问。然后调用get_log_from_minor函数根据从设备号获取要操作的日志缓冲区结构体,部分关键代码如下所示:

打开文件后调用logger_read函数从对应的缓冲区读取日志记录。

当进程往日志设备写入日志记录时,logger日志驱动程序就会执行logger_aio_write函数执行日志记录的写入操作。

04 - 运行时库层日志库

Android系统在运行时库层提供了一个用来和logger日志驱动程序进行交互的日志库liblog。通过liblog库提供的接口,应用可以方便的往logger日志驱动程序中写入日志记录。Liblog提供的接口实现在logd_write.c文件中,它的位置如下:

其中实现了一系列的日志写入函数,如下图所示:

其中_android_log_assert, _android_log_vprint和_android_log_print用来写入类型为main的日志记录;

_android_log_btwrite和_android_log_bwrite用来写入类型为events的日志记录;

_android_log_buf_print可以写入任意类型的日志记录。

上述所有类型的写入方式,最终都会调用write_to_log函数将日志记录写入到logger日志驱动中的。关键代码如下所示:

05 - C/C++日志写入接口

上文提到了,C/C++定义了几组宏来实现不同类型日志的写入,宏的定义在log.h头文件中,在android系统源码中的位置如下所示:

宏的部分定义代码如下所示:

在这个头文件中定义了一个宏LOG_NDEBUG来判定程序是否是调试版本,调试版本中这个宏的数值定义为0。LOGV只有在宏LOG_NDEBUG定义为0时才是有效的,否则它只是一个空定义。

使用LOGW类型方法定义的宏,最终会通过调用_android_log_print函数运行时库中的方法写入日志,部分关键代码如下:

06 - Java日志写入接口

上文介绍了三个Java日志写入接口,这三个Java接口都是通过JNI方法调用liblog日志库提供的函数来实现日志记录的写入功能。其中使用较多的是android.util.Slogandroid.util.Log这两个接口,这两个接口的差异不大,所以在这部分就只介绍一下android.util.Log接口。

android.util.Log:该接口提供的日志记录写入成员比较多,不过在应用开发只关注常用的成员函数v、d、i、w和e。

这些成员函数写入的日志记录的类型都是main,而对应的日志记录的优先级分别为VERBOSE, DEBUG, INFO, WARN和ERROR。代码位置在frameworks\base\core\java\android\util\Log.java类中,打印日志的方法实现以及日志级别的定义都在该类中,部分代码截图如下所示:

使用JNI的println_native方法进行日志的打印,该方法实现的地方在frameworks\base\core\jni\android_util_Log.cpp中,最终使用__android_log_buf_write函数打印日志。

07 - Logcat工具分析

Logcat是内置在Android系统中的一个实用的工具,我们可以通过adb使用logcat工具。连接设备,打开设备的调试功能,使用adblogcat命令来查看目标设备上的日志记录。运行了命令后,发现日志会不断的输出,部分日志截图结果如下所示:

Logcat工具:具有丰富的功能,可以通过adb logcat –help查看该工具的详细用法。

参考资料· 书籍《Android系统源代码情景分析》- 作者:罗升阳·《解决爱加密加固之后使用xposed hook的时候log打印不出来的问题》- 看雪论坛 作者:smartdon

最后声明一下,本人水平有限,如果文中有发现错误或可以改进得更好的地方,欢迎下方留言指出哦!:)

内容来源:骇极安全技术团队原创

骇极 - 让安全简单



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3