美文网首页
Android 日志系统--04:selinux、kernel日

Android 日志系统--04:selinux、kernel日

作者: DarcyZhou | 来源:发表于2023-12-16 07:27 被阅读0次

    本文转载自:Android10.0 日志系统分析(四)-selinux、kernel日志在logd中的实现

    本文基于Android 10.0源码分析

    1.概述

      上一节我们看了Android日志系统的架构分析以及logd、logcat的初始化操作,这一节我们来看看日志系统的读写操作。

    2.LogAudit的写入

    日志系统4-1.PNG

    从logd初始化时,我们可以看到,如果配置了属性“ro.logd.auditd”,则会创建LogAudit,LogAudit在NETLINK_AUDIT的socket上侦听selinux启动的日志消息。可以在手机root后,进行属性设置:setprop ro.logd.auditd true。

    // system/core/logd/main.cpp
    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
    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
    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
    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;
    }
    

    3.kernel日志的写入

    日志系统4-2.png

    从logd初始化时,我们可以看到,如果配置了属性“ro.logd.kernel”,则会创建LogKlog,用来抓取Kernel日志。可以在手机root后,进行属性设置:setprop ro.logd.kernel true。实现原理其实是把 "/proc/kmsg" 和 "/dev/kmsg" 文件当作socket 文件来使用。

    // system/core/logd/main.cpp
    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
    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
    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;
    }
    

    4.总结

      至此,Android 10.0的日志系统全部总结完成。 Android日志系统现在主要由Logd守护进行进行管理,liblog提供读写日志的接口,logcat提供读取日志的参数命令。

      在日常调试或者CTS测试时,会遇到日志丢失或者不全的情况,主要原因是日志量很大,但是日志缓冲区很小,此时只要把日志的缓冲区调大即可。

    • 方法1:setprop ro.logd.size 5120;即把日志缓冲区都调整为5M;
    • 方法2:开发者模式->日志记录缓冲区大小-> 选择相应的缓冲区大小,可以选择64K -16M等5个大小。

    相关文章

      网友评论

          本文标题:Android 日志系统--04:selinux、kernel日

          本文链接:https://www.haomeiwen.com/subject/hynfidtx.html