当前位置:   article > 正文

Android_8.1 Log 系统源码分析_liblog.so 源码包

liblog.so 源码包

0x01 Android Log框架推荐

1、 logger

提供了一些格式化输出、美观

// 添加依赖
implementation 'com.orhanobut:logger:2.2.0'

// 初始化
Logger.addLogAdapter(new AndroidLogAdapter());
// 使用
Logger.d("hello,Android");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
2、timber

基于原生Log类的小型可扩展的log框架

3、Hugo

使用注解形式的调试版本log框架

4、xLog

可扩展,支持多种数据格式,支持线程和调用栈信息

5、 LogUtils

支持多种数据结构,支持系统对象,支持高性能写入文件(mmap)

6、Log4a

基于mmap内存映射,最大化保证日志完整性

0x02 Android 原生 Log 系统

1、Java 层逻辑

Android 系统 Java 层 Log 定义在 /frameworks/base/core/java/android/util/Log.java

此外还提供了 EventLogSLog【todo】

Log 提供了六种日志级别,并定义了一系列静态方法:

public static int v/d/i/w/e/wtf(String tag, String msg) {
  return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}

public static int v/d/i/w/e/wtf(String tag, String msg, Throwable tr) {
  return printlns(LOG_ID_MAIN, VERBOSE, tag, msg, tr);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

下面这种调用带异常记录,会调用 Log 类内部类 ImediateLogWriter 来写入日志消息,最终也会调用 println_native

说明:Android 中不同的 log 会指定不同的缓冲区然后被写入到不同的设备中,包括 system(系统相关)、radio(无线/电话相关)、event(事件相关)、main(主缓冲区,默认)

本地实现定义在 /frameworks/base/core/jni/android_util_Log.cpp

static const JNINativeMethod gMethods[] = {
   
  {
    "isLoggable", "(Ljava/lang/String;I)Z", (void*) android_util_Log_isLoggable },
  {
    "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
  {
    "logger_entry_max_payload_native", "()I", (void*) android_util_Log_logger_entry_max_payload_native },
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

android_util_Log_isLoggable

  • 检查 tag,取tag,调用 isLoggable 判断

android_util_Log_println_native

2、Native 层逻辑

Native 层通过定义一系列宏的方式提供 log 功能,全部是调用了 __android_log_print

LIBLOG_ABI_PUBLIC int __android_log_print(int prio, const char* tag,
                                          const char* fmt, ...) {
   
  va_list ap;
  char buf[LOG_BUF_SIZE];
  va_start(ap, fmt);
  vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
  va_end(ap);

  return __android_log_write(prio, tag, buf);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

这个过程使用了可变参数,va 就是 variable-argument,相关宏定义在 stdarg.h

  • 首先定义 va_list 类型变量,是一个指向参数的指针
  • va_start 初始化变量,此函数的第一个参数是指针,第二个参数是固定参数
  • vsnprintf 将可变参数格式化输出到一个字符数组
  • 另外还有 va_arg 获取可变的参数,此函数的第一个参数是指针,第二个参数是可变参数类型
  • va_end 结束可变参数的获取

最后函数会调用 __android_log_write

LIBLOG_ABI_PUBLIC int __android_log_write(int prio, const char* tag,
                                          const char* msg) {
   
  return __android_log_buf_write(LOG_ID_MAIN, prio, tag, msg);
}
  • 1
  • 2
  • 3
  • 4
  • 5

这里 Java/C++ 层就走到同一个函数,在这个函数中会实现写设备文件

3、liblog.so 逻辑

liblog.so 会被所有需要日志操作的进程加载,负责处理打印和读取日志的流程

主要代码及逻辑都在 /system/core/liblog/logger_write.c 中,__android_log_buf_write 里面做了下面这几件事:

  1. 定义 iovec 结构数组 vec[3],三个元素分别存放 prio、tag、msg

    struct iovec {
         
      void* iov_base;
      size_t iov_len;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5

    iovec 结构体包含一个指向缓冲区的指针和读/写的长度

  2. 判断 bufID 修改 tag(bufID 这个变量表示不同的 log 缓冲区,或者理解成写到不同的文件里)

  3. 最后调用 write_to_log

write_to_log 是一个函数指针,初始设置指向 __write_to_log_init,进入 __write_to_log_init 后,首先会去调用 __write_to_log_initialize,然后将 write_to_log 设置为指向 __write_to_log_daemon,然后又调用一次 write_to_log

static int (*write_to_log)(log_id_t, struct iovec* vec, size_t nr) = __write_to_log_init;


static int __write_to_log_init(log_id_t log_id, struct iovec* vec, size_t nr) {
   
  __android_log_lock();
  if (write_to_log == __write_to_log_init) {
   
    int ret;
    ret = __write_to_log_initialize();
    if (ret < 0) {
   
      __android_log_unlock();
      if (!list_empty(&__android_log_persist_write)) {
   
        __write_to_log_daemon(log_id, vec, nr);
      }
      return ret;
    }
    write_to_log = __write_to_log_daemon;
  }
  __android_log_unlock();
  return write_to_log(log_id, vec, nr);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

__write_to_log_initialize__write_to_log_daemon 实现太复杂了,总结下来就是 initialize 基于默认配置构造结构体链表,daemon 从链表中取出节点,就是 android_log_transport_write,节点的结构体的定义在liblog/logger.h

struct android_log_transport_write {
   
  struct listnode node;
  const char* name;                  /* human name to describe the transport */
  unsigned logMask;                  /* mask cache of available() success */
  union android_log_context context; /* Initialized by static allocation */

  int (*available)(log_id_t logId); /* Does not cause resources to be taken */
  int (*open)();   /* can be called multiple times, reusing current resources */
  void (*close)(); /* free up resources */
  int (*write)(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr); /* write log to transport, returns number of bytes propagated, or -errno */
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

初始化 的时候会调用 __android_log_config_write 会基于不同的场景定义不同的结构去写日志,包括 localLoggerWritelogdLoggerWritepmsgLoggerWritefakeLoggerWritestderrLoggerWrite,调用 retval = (*node->write)(log_id, &ts, vec, nr) 进行 write 操作

LIBLOG_HIDDEN void __android_log_config_write() {
   
  if (__android_log_transport & LOGGER_LOCAL) {
   
    extern struct android_log_transport_write localLoggerWrite;
    __android_log_add_transport(&__android_log_transport_write,
                                &localLoggerWrite);
  }

  if ((__android_log_transport == LOGGER_DEFAULT) || (__android_log_transport & LOGGER_LOGD)) {
   
#if (FAKE_LOG_DEVICE == 0)
    extern struct android_log_transport_write logdLoggerWrite;
    extern struct android_log_transport_write pmsgLoggerWrite;
    __android_log_add_transport(&__android_log_transport_write, &logdLoggerWrite);
    __android_log_add_transport(&__android_log_persist_write, &pmsgLoggerWrite);
#else
    extern struct android_log_transport_write fakeLoggerWrite;
    __android_log_add_transport(&__android_log_transport_write, &fakeLoggerWrite);
#endif
  }

  if (__android_log_transport & LOGGER_STDERR) {
   
    extern struct android_log_transport_write stderrLoggerWrite;
    if (list_empty(&__android_log_transport_write)) {
   
      __android_log_add_transport(&__android_log_transport_write, &stderrLoggerWrite);
    } else {
   
      struct android_log_transport_write* transp;
      write_transport_for_each(transp, &__android_log_transport_write) {
   
        if (transp == &stderrLoggerWrite) {
   
          return;
        }
      }
      __android_log_add_transport(&__android_log_persist_write, &stderrLoggerWrite);
    }
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

看其中比较重要的几个,logdLoggerWrite 的定义在 /system/core/liblog/logd_writer.c

LIBLOG_HIDDEN struct android_log_transport_write logdLoggerWrite = {
   
  .node = {
    &logdLoggerWrite.node, &logdLoggerWrite.node },
  .context.sock = -EBADF,
  .name = "logd",
  .available = logdAvailable,
  .open = logdOpen,
  .close = logdClose,
  .write = logdWrite,
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

其中,logdOpen 方法创建 sockaddr_un 结构体并将 “/dev/socket/logdw” 写入 sun_path 成员变量中,然后调用 connect 去建立连接,并将套接字标识放到 logdLoggerWrite.context.sock 中;write 函数指针指向 logdWrite 方法,会调用 writev 非阻塞地写入日志信息

同理,pmsgLoggerWrite 打开的是 “/dev/pmsg0” 的 socket

所以,liblog.so 的一个主要工作就是写入日志

4、logd 逻辑

使用 file 命令查看 /dev/socket/logdw 发现是一个 socket,这个 socket 是由 logd 创建的,见 /system/corelogd/logd.rc

service logd /system/bin/logd
    socket logd stream 0666 logd logd
    socket logdr seqpacket 0666 logd logd
    socket logdw dgram+passcred 0222 logd logd
    file /proc/kmsg r
    file /dev/kmsg w
    user logd
    group logd system package_info readproc
    writepid /dev/cpuset/system-background/tasks
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

logd 是 C 层的守护进程,由 init 进程创建(创建 servicemanager 时同时创建),可以看到启动 logd 后会创建三个 socket,分别为 logd 用来监听命令、logdr 用于读日志、logdw 用于写日志,还打开了两个文件,修改了 user id 和 group id,并把 pid 写文件

logd 启动后会从 main 函数开始执行,见 /system/core/logd/main.cpp

第一步、打开 /dev/kmsg 用于写内核 log 的,在 logd 还未启动或出错时,只能写到内核日志中

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));
    }

    ......

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

android_get_control_file 里面先调用 __android_get_control_from_env 拼接 Android 文件前缀 ANDROID_FILE_ 和路径 /dev/kmsg,然后做符号转换得到 ANDROID_FILE__dev_kmsg,接下来通过 getenv 获取这个环境变量的值,最后通过 strtol 将这个值转换成 long 类型就是文件描述符,还要通过 fcntl 去验证下文件是不是开着


这里用了三种方法去验证:

  1. 通过访问文件表获取 flag,最快;
  2. 通过文件表去拿 file 对象,然后获取 flag,其次;
  3. 通过文件表拿到 file 对象,然后进一步拿到 inode 节点数据,再获取 stat,最慢。

__android_get_control_from_env 返回文件描述符后,还会调用 /proc/self/fd/fd_num 验证一次

第二步、打开 /proc/kmsg 用于读内核日志

int main(int argc, char* argv[]) {
   

  	......
  	
  	bool klogd = __android_logger_property_get_bool(
        "logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
                           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);
    }
   
    ......

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

第三步、启动 reinit 线程,处理 --reinit 命令,此外这个线程还会完成 uid 转 name

第四步、设置运行时优先级和权限【略】

第五步、启动 log 监听

  • 创建 LogBuffer 对象,负责管理所有的 log entries,可以理解为存放 log,对应线程“logd.auditd”
  • 创建 LogReader 对象,监听 /dev/socket/logdr,有客户端接入时就把 logBuf 中的 log entry 给客户端,即管理客户端读取 log
  • 创建 LogListener 对象,监听 /dev/socket/logdw,负责将 log entry 写入 logBuf,即写入 log
  • 创建 CommandListener 对象,监听 /dev/socket/logd,接收各种命令
  • LogAudit 读取 selinux 的日志
  • LogKlog 读取 kernel 的日志,写到 logBuf 中

看一下 logd 的线程,可以看到 logd 进程 pid = 574,ppid = 1,即 init 进程孵化,reinit_thread_start 函数启动线程“logd.daemon”,LogReader 启动线程“logd.reader”监听 /dev/socket/logdr,LogListener 启动线程“logd.writer”监听 /dev/socket/logdw,CommandListener 启动线程“logd.control”监听 /dev/socket/logd,LogAudit 启动线程“logd.auditd”,LogKlog 启动线程“logd.klogd”,LogTimeEntry 启动线程“logd.reader.per”

logd	574   574   1   32428   4912 SyS_rt_sigsuspend 753d7b1634 S logd
logd	574   577   1   32428   4912 futex_wait_queue_me 753d7644b0 S logd.daemon
logd	574   578   1   32428   4912 do_select  753d7b15a4 S logd.reader
logd	574   579   1   32428   4912 do_select  753d7b15a4 S logd.writer
logd	574   580   1   32428   4912 do_select  753d7b15a4 S logd.control
logd	574   582   1   32428   4912 do_select  753d7b15a4 S logd.auditd
logd	574 14402   1   32428   4912 futex_wait_queue_me 753d7644b0 S logd.reader.per
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
4.1、LogBuffer 逻辑

LogBuffer 继承自 LogBufferInterface,类内部定义了很多成员变量和一些函数,包括:

  • LogBufferElementCollection
  • LogBufferElement 指针变量:lastLoggedElements 和 droppedElements
  • LastLogTimes
  • 构造和析构方法,init/log 方法

LogBufferElementCollection 是一个 LogBufferElement 指针类型的 list

LogBufferElement 中存放 LogBuffer 元素的相关信息,包括 uint32_t 类型的 uid/pid/tid,还有 realTime、消息内容 msg,logId 等;

LastLogTimes 是一个 LogTimeEntry 指针类型的 list,两者都定义在 LogTime 中,LogTimeEntry 里面包

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/609800
推荐阅读
相关标签
  

闽ICP备14008679号