Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现-[Android取经之路]
阅读本文大约需要花费15分钟。
系列文章:
Android系统启动之init进程(一)-「Android取经之路」
Android系统启动之init进程(二)-「Android取经之路」
Android 10.0系统启动之init进程(三)-「Android取经之路」
Android 10.0系统启动之init进程(四)-「Android取经之路」
Android 10.0系统启动之Zygote进程(一)-「Android取经之路」
Android 10.0系统启动之Zygote进程(二)-「Android取经之路」
Android 10.0系统启动之Zygote进程(三)-「Android取经之路」
Android 10.0系统启动之Zygote进程(四)-「Android取经之路」
Android 10.0系统启动之SystemServer进程(一)-「Android取经之路」
Android 10.0系统启动之SystemServer进程(二)-「Android取经之路」
Android 10.0系统服务之AMS启动流程-「Android取经之路」
Android10.0系统启动之Launcher(桌面)启动流程-[Android取经之路]
Android10.0应用进程创建过程以及Zygote的fork流程-[Android取经之路]
Android 10.0 PackageManagerService(一)工作原理及启动流程-[Android取经之路]
Android 10.0 PackageManagerService(二)权限扫描-[Android取经之路]
Android 10.0 PackageManagerService(三)APK扫描-[Android取经之路]
Android 10.0 PackageManagerService(四)APK安装流程-[Android取经之路]
Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性-[Android取经之路]
上一节我们看了Android日志系统的读写操作,这一节我来一起看看selinux、kernel日志在logd中的实现
日志系统系列文章:
Android10.0 日志系统分析(一)-logd、logcat 指令说明、分类和属性-[Android取经之路]
Android10.0 日志系统分析(二)-logd、logcat架构分析及日志系统初始化-[Android取经之路]
Android10.0 日志系统分析(三)-logd、logcat读写日志源码分析-[Android取经之路]
7.5 LogAudit的写入
从logd初始化时,我们可以看到,如果配置了属性“ro.logd.auditd”,则会创建LogAudit,LogAudit 在NETLINK_AUDIT的socket上侦听selinux启动的日志消息。
可以在手机root后,进行属性设置:setprop ro.logd.auditd true
[/system/core/logd/main.cpp] main()
int main(int argc, char* argv[]) {
...
bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
if (drop_privs(klogd, auditd) != 0) {
return EXIT_FAILURE;
}
...
LogAudit* al = nullptr;
if (auditd) {
al = new LogAudit(logBuf, reader,
__android_logger_property_get_bool(
"ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
? fdDmesg
: -1);
}
readDmesg(al, kl);
...
}
在LogAudit()被创建时,会去调用getLogSocket(),创建socket PF_NETLINK,并与内核进行连接,把pid发给内核,告诉内核,用来获取selinux日志。
[/system/core/logd/LogAudit.cpp] getLogSocket()
int LogAudit::getLogSocket() {
//创建socket PF_NETLINK
int fd = audit_open();
if (fd < 0) {
return fd;
}
//与内核建立连接,让内核知道这个pid用来获取selinux信息
if (audit_setup(fd, getpid()) < 0) {
audit_close(fd);
fd = -1;
}
return fd;
}
启动socket监听后,调用onDataAvailable(),与logd.auditd建立连接,调用recvfrom()接收socket传来的数据,最终调用logPrint把/dev/kmsg 内容写入“log to events”\"log to main",并通知LogReader有日志写入。
[/system/core/logd/LogAudit.cpp] onDataAvailable()
bool LogAudit::onDataAvailable(SocketClient* cli) {
if (!initialized) {
prctl(PR_SET_NAME, "logd.auditd");
initialized = true;
}
struct audit_message rep;
rep.nlh.nlmsg_type = 0;
rep.nlh.nlmsg_len = 0;
rep.data[0] = '\0';
if (audit_get_reply(cli->getSocket(), &rep, GET_REPLY_BLOCKING, 0) < 0) {
SLOGE("Failed on audit_get_reply with error: %s", strerror(errno));
return false;
}
logPrint("type=%d %.*s", rep.nlh.nlmsg_type, rep.nlh.nlmsg_len, rep.data);
return true;
}
把日志内容写入LogBuffer 中的events和main的日志中,并通知LogReader有日志写入,供其他客户端进行读取。
[/system/core/logd/LogAudit.cpp] logPrint()
int LogAudit::logPrint(const char* fmt, ...) {
...
//把selinux写入Log buffer中的events id
if (events) { // begin scope for event buffer
...
rc = logbuf->log(
LOG_ID_EVENTS, now, uid, pid, tid, reinterpret_cast<char*>(event),
(message_len <= UINT16_MAX) ? (uint16_t)message_len : UINT16_MAX);
if (rc >= 0) {
notify |= 1 << LOG_ID_EVENTS;
}
// end scope for event buffer
}
...
//把selinux写入Log buffer中的main id
if (main) { // begin scope for main buffer
...
rc = logbuf->log(
LOG_ID_MAIN, now, uid, pid, tid, newstr,
(message_len <= UINT16_MAX) ? (uint16_t)message_len : UINT16_MAX);
if (rc >= 0) {
notify |= 1 << LOG_ID_MAIN;
}
// end scope for main buffer
}
free(const_cast<char*>(commfree));
free(str);
if (notify) {
//通知LogReader有日志写入
reader->notifyNewLog(notify);
if (rc < 0) {
rc = message_len;
}
}
return rc;
}
7.6 kernel日志的写入
从logd初始化时,我们可以看到,如果配置了属性“ro.logd.kernel”,则会创建LogKlog,用来抓取Kernel日志。
可以在手机root后,进行属性设置:setprop ro.logd.kernel true
实现原理其实是把 "/proc/kmsg" 和 "/dev/kmsg" 文件当作socket 文件来使用。
[/system/core/logd/main.cpp] main()
int main(int argc, char* argv[]) {
...
static const char dev_kmsg[] = "/dev/kmsg";
fdDmesg = android_get_control_file(dev_kmsg);
if (fdDmesg < 0) {
fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
}
bool klogd = __android_logger_property_get_bool(
"ro.logd.kernel",
BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
if (klogd) {
static const char proc_kmsg[] = "/proc/kmsg";
fdPmesg = android_get_control_file(proc_kmsg);
if (fdPmesg < 0) {
fdPmesg = TEMP_FAILURE_RETRY(
open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
}
if (fdPmesg < 0) android::prdebug("Failed to open %s\n", proc_kmsg);
}
...
LogKlog* kl = nullptr;
if (klogd) {
kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
}
readDmesg(al, kl);
...
}
LogKlog 创建后,启动listener,调用onDataAvailable(),与logd.klog建立连接,最终调用log()把日志存入kernel buffer。
[/system/core/logd/LogKlog.cpp] onDataAvailable()
bool LogKlog::onDataAvailable(SocketClient* cli) {
if (!initialized) {
prctl(PR_SET_NAME, "logd.klogd");
initialized = true;
enableLogging = false;
}
char buffer[LOGGER_ENTRY_MAX_PAYLOAD];
ssize_t len = 0;
for (;;) {
ssize_t retval = 0;
if (len < (ssize_t)(sizeof(buffer) - 1)) {
retval =
read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);
}
if ((retval == 0) && (len <= 0)) {
break;
}
if (retval < 0) {
return false;
}
len += retval;
bool full = len == (sizeof(buffer) - 1);
char* ep = buffer + len;
*ep = '\0';
ssize_t sublen;
for (char *ptr = nullptr, *tok = buffer;
!!(tok = android::log_strntok_r(tok, len, ptr, sublen));
tok = nullptr) {
if (((tok + sublen) >= ep) && (retval != 0) && full) {
if (sublen > 0) memmove(buffer, tok, sublen);
len = sublen;
break;
}
if ((sublen > 0) && *tok) {
//调用log(),把日志写入kernel buffer
log(tok, sublen);
}
}
}
return true;
}
log()主要通过LogBuffer把日志写入kernel的buffer,再通知LogReader有日志写入。
[/system/core/logd/LogKlog.cpp] log()
int LogKlog::log(const char* buf, ssize_t len) {
...
// 把日志写入logbuffer
int rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr, (uint16_t)n);
// 通知LogReader,有日志写入。
if (rc > 0) {
reader->notifyNewLog(static_cast<log_mask_t>(1 << LOG_ID_KERNEL));
}
return rc;
}
8.总结
至此,Android10.0的日志系统全部总结完成。
Android日志系统现在主要由Logd守护进行进行管理,liblog提供读写日志的接口,logcat提供读取日志的参数命令。
我们在日常调试或者CTS测试时,会遇到日志丢失或者不全的情况,主要原因是日志量很大,但是日志缓冲区很小,此时只要把日志的缓冲区调大即可。
方法1: setprop ro.logd.size 5120 即把日志缓冲区都调整为5M
方法2:开发者模式->日志记录缓冲区大小-> 选择相应的缓冲区大小,可以选择64K -16M等5个大小