乐趣区

关于android:Logcat-读取-Kernel-Log

这个话题真的是非常简单的一个问题,以至于网上很难找到正确的解读。

最近做 Android 零碎开发,零碎开发通常要建设本人的 Log 零碎,抓取 Android Log、Kernel Log 和其余一些特定的 Log。对于 Kernel Log 的需要也很简略,

  • 抓取 Kernel 打印的 Log,存储到文件中。
  • Log 应用墙上时钟(wall clock)显示工夫戳,这样不便问题剖析。

看似简略的需要却失去形形色色的实现,可见码农的创造力有限。一个计划是抓取 "/proc/kmsg" 信息,但它不是以墙上时钟显示的,又在 Kernel 中定时打印墙上工夫用于对照。另一个计划是应用 “dmesg -T -c” 来抓取信息,这样是能失去墙上工夫的 Log,但它清空了 Kernel Log buffer,等于是造成了毁坏。还有网络上形容的各种解决方案。看到这些办法时,我就蛊惑了,一个简略正确的获取 Kernel Log 的办法难道不是通过 Logcat 获取吗?产生了什么导致很多人并不知道这个办法,并且网上也很少提及。

正确的读取 Kernel Log

先说论断,偷懒的就省得看前面了。从 Android 6.0 开始,收集 Kernel Log 只需两步,任何需要都满足了。

  1. 设置”ro.logd.kernel=true“
  2. 通过”logcat -b kernel“抓取。

接下来说一下具体的影响因素。原本在 debuggable 版本的零碎,能够上面的命令读取 Kernel Log,

# logcat -b kernel

然而~~~,你会发现在许多零碎中下面的命令并不能读到信息,因为它还受一些 property 的限度。在源码中的一个文档记录了与 logd 相干的 property,摘取 Kernel Log 相干的形容如下。

file: /system/core/logd/README.property

The properties that logd and friends react to are:

name                       type default  description

ro.logd.kernel             bool+ svelte+ Enable klogd daemon
......
ro.debuggable              number        if not "1", logd.statistics &
                                         ro.logd.kernel default false.
......
ro.config.low_ram          bool   false  if true, logd.statistics,
                                         ro.logd.kernel default false,
                                         logd.size 64K instead of 256K.
......
NB:
- auto - managed by /init
- bool+ - "true", "false" and comma separated list of "eng" (forced false if
  ro.debuggable is not "1") or "svelte" (forced false if ro.config.low_ram is
  true).
- svelte - see ro.config.low_ram for details.
- svelte+ - see ro.config.low_ram and ro.debuggable for details.
......

下面形容表明,ro.logd.kernel 用于管制是否使能 klogd 模块,klogd 就是用来采集 Kernel Log 的过程。ro.logd.kernel 的值为 bool+,能够设置为 “true”、”false” 或者通过逗号分隔符连贯 “eng” 或 “svelte”。设置为 “eng” 时,如果 ro.debuggable 不为 “1”,则 ro.logd.kernel 为 false。设置为 “svelte” 时,如果 ro.config.low_ram 为 “true”,则 ro.logd.kernel 为 false。

下面这段真的很乱,我看了半天 + 撸代码总算略微看懂了,咱们用上面的表格来阐明一下。

Value svelte+ Return
ro.logd.kernel=true,xxx N/A true
ro.logd.kernel=false,xxx N/A false
ro.logd.kernel=xxx,eng (xxx != true or false) ro.debuggable=1 true
ro.logd.kernel=xxx,eng (xxx != true or false) ro.debuggable!=1 false
ro.logd.kernel=xxx,svelte (xxx != true or false) ro.config.low_ram=true false
ro.logd.kernel=xxx,svelte (xxx != true or false) ro.config.low_ram=false true

默认 ro.logd.kernel 是没有被设置的,这时会依据 ro.debuggablero.config.low_ram来决定缺省值。

svelte+ ro.logd.kernel default value
ro.config.low_ram=true false
ro.config.low_ram!=true && ro.debuggable!=1 false
others true

所以,当 logcat 无奈抓取 Kernel Log 时,须要查看三个 property:ro.logd.kernelro.config.low_ramro.debuggable 的状态。而须要强制关上 Kernel Log 时,只有设置”ro.logd.kernel=true“。

为什么无人提及

首先,这个办法切实太简略了,简略的都不值得去认真剖析它。但为何还有很多人不晓得呢?我遇到的状况少数都是因为历史继承性引起的。在 Android 6.0 之前,抓取 Kernel Log 只能本人想方法,于是码农们实现了各种各样的形式来获取 Log,并加上工夫戳。然而当零碎版本升级时,这部分抓取 Log 日志的零碎做为可贵的自研代码保留了下来,之后始终继承着。局部零碎素来没从新扫视这部分代码的合理性,因为它统一能用,就接着用。相似的问题在许多代码中都存在,很难有人跳进去重新整理。

代码剖析

简略剖析一下 Kernel Log 相干的代码,要不这个文档就更没有价值了。Android 的 Log 都由 logd 来治理,Kernel Log 当然也时一样,只是多开拓了一个 kernel buffer。

file: /system/core/logd/main.cpp
    
int main(int argc, char* argv[]) {
    ......
    static const char dev_kmsg[] = "/dev/kmsg"; // 应用这个节点来输入 logd 中的打印
    fdDmesg = android_get_control_file(dev_kmsg);
    ......
    bool klogd = __android_logger_property_get_bool( // 获取 klogd 状态
        "ro.logd.kernel",
        BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
    if (klogd) {static const char proc_kmsg[] = "/proc/kmsg"; // 用来读取 Kernel Log 的节点
        fdPmesg = android_get_control_file(proc_kmsg);
        ......
    }
    ......
    logBuf = new LogBuffer(times); // 创立 logBuffer,用于保留日志
    ......
    //LogReader 监听 /dev/socket/logdr,logcat 通过它来获取日志
    LogReader* reader = new LogReader(logBuf); 
    if (reader->startListener()) {exit(1);
    }
    ......
    //LogListener 监听 /dev/socket/logdw,用于写入日志
    LogListener* swl = new LogListener(logBuf, reader);
    if (swl->startListener(600)) {exit(1);
    }
    // CommandListener 监听 /dev/socket/logd,用于命令传输
    CommandListener* cl = new CommandListener(logBuf, reader, swl);
    if (cl->startListener()) {exit(1);
    }
    ......
    LogKlog* kl = nullptr; //LogKlog 用于存储 Kernel Log
    if (klogd) {kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
    }

    readDmesg(al, kl); // 启动时读取 kernel buffer 中缓存的日志

    if (kl && kl->startListener()) { // 启动 Kernel log 监听
        delete kl;
    }
    ......
}

重点的几个中央:

  • 关上 “/dev/kmsg” 用来输入 logd 中的一些日志。这个设计挺奇妙的,在 logd 筹备好之前,日志是无奈写入 Android Log buffer 中的。这里将日志写入 “/dev/kmsg” 中,等于写入了 Kernel Log buffer 中,通过 dmesg 或 “/proc/kmsg” 就能够获取相干日志。
  • 应用 “/proc/kmsg” 来获取 Kernel Log。”/proc/kmsg” 会记录上次读取的地位,与 dmesg 不同,读取时不会输入整个 buffer 内容。只有监听这个节点就能够实时获取 Kernel Log。
  • LogKlog 是一个 socket listener,用来监听 “/proc/kmsg”,而后将 Kernel Log 写入到 Android Log buffer 中。

接下来简略看一下 Kernel Log 真正写入的代码,

file: /system/core/logd/LogKlog.cpp
    
// 当监听到有 Kernel Log 写入时会调用该办法    
bool LogKlog::onDataAvailable(SocketClient* cli) {
    ......
    char buffer[LOGGER_ENTRY_MAX_PAYLOAD]; // 每次最多读取 4068 字节的日志
    ssize_t len = 0;

    for (;;) {
        ssize_t retval = 0;
        if (len < (ssize_t)(sizeof(buffer) - 1)) {
            retval = // 读取 "/proc/kmsg" 中的日志
                read(cli->getSocket(), buffer + len, sizeof(buffer) - 1 - len);
        }
        ......
        for (char *ptr = nullptr, *tok = buffer;
             !!(tok = android::log_strntok_r(tok, len, ptr, sublen));
             tok = nullptr) {
            ......
            if ((sublen > 0) && *tok) {log(tok, sublen); // 一行行的将日志写入 buffer
            }
        }
    }

    return true;
}
......
// 写入日志,对 Kernel Log 做了大量解析,转换为 Android Log 格局。// 可能 Kernel Log 太随便了,这段代码看起来都晕,大抵了解一下。int LogKlog::log(const char* buf, ssize_t len) {
    // 如果 auditd 在运行,它会记录相干日志,所以在 LogKlog 不会重复记录该日志。if (auditd && android::strnstr(buf, len, auditStr)) {return 0;}
    
    // 解析 Kernel Log 中的优先级信息
    const char* p = buf;
    int pri = parseKernelPrio(p, len);
    
    // 解析工夫信息
    log_time now;
    sniffTime(now, p, len - (p - buf), false);
    ......
    // 解析 pid、tid、uid
    const pid_t pid = sniffPid(p, len - (p - buf));
    const pid_t tid = pid;
    uid_t uid = AID_ROOT;
    if (pid) {logbuf->wrlock();
        uid = logbuf->pidToUid(pid);
        logbuf->unlock();}
    
    // 解析 Kernel Log 中的 tag 信息
    while ((p < &buf[len]) && (isspace(*p) || !*p)) {++p;}
    ......
    // 依据解析的信息创立 Android Log
    char newstr[n];
    char* np = newstr;
    
    // 将优先级转换为 Android Log 的优先级格局
    *np = convertKernelPrioToAndroidPrio(pri);
    ++np;
    
    // 复制解析的 tag 信息
    memcpy(np, tag, taglen);
    np += taglen;
    *np = '\0';
    ++np;

    // 复制主信息
    memcpy(np, p, b);
    np[b] = '\0';
    
    // 工夫修改
    if (!isMonotonic()) {......}

    // 将日志写入 LOG_ID_KERNEL buffer 中
    int rc = logbuf->log(LOG_ID_KERNEL, now, uid, pid, tid, newstr,
                         (unsigned short)n);

    // 告诉 readers
    if (rc > 0) {reader->notifyNewLog(static_cast<log_mask_t>(1 << LOG_ID_KERNEL));
    }

    return rc;
}
退出移动版