查看原文
其他

Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现​-[Android取经之路]

IngresGe IngresGe 2021-11-05

阅读本文大约需要花费15分钟。

系列文章:

Android取经之路——启动篇

Android系统架构-[Android取经之路]

Android是怎么启动的-[Android取经之路]

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个大小


: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存