原文使用有道云筆記創作, 看這個獲取更好閱讀體驗: 有道云筆記原創連接
引言
> Android 的log,從操作系統分層上來講,可以分為“Kernel Log”和“User Log”(這是我個人引入的術語)。
> 所謂“Kernel Log”就是操作系統內核打印的log。內核里調用printk等接口請求輸出kernel log。
kernel log最后會被打印到/dev/kmsg文件上。可以通過dmesg查看到
> 所謂“User Log”分為2部分。
一類是Linux的標準輸出設備中打印的log(stderr/stdout).
另一類是android特有的log流程。如通過android.util.Log類打印的log,eventslog, ALOG() native層log打印.
他們都可以通過logcat看到.
本文基于Android N源碼, 對Android的log機制做介紹.
先給出一張Android Log系統的總圖
1 Android特有Log流程
// java
import android.util.Log;
Log.d("cwj", "test log");
==android.util.Log== 是在做Android開發中最常用的log輸出手段.這里輸出的log, 我們通過"adb logcat"或"adb shell logcat"命令獲取.
那么從"Log.d("cwj", "test log");" 到"logcat"之間到底發生了什么呢?
// C/C++
#define LOG_TAG "fingerprintd"
ALOG(LOG_VERBOSE, LOG_TAG, "lockout\n");
ALOGE("Invalid callback object");
ALOGD("onAcquired(%d), duplicatedFingerId(%d)", 1, 2);
對于Native Code中的 ALOG 等的log打印 到logcat之間, 又發生了什么呢?
1.1 android.util.Log 和 android.util.writeEvent
1.1.1 android.util.Log
java類Log的源碼在 "frameworks/base/core/java/android/util/Log.java"
Lod中的 Log.d() / Log.v() 等打印不級別的函數, 最終都走到 ==println_native()== native函數.
public static int v(String tag, String msg) {
return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}
public static int d(String tag, String msg) {
return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
}
/** @hide */ public static native int println_native(int bufID,
int priority, String tag, String msg);
println_native 的實現在 "/frameworks/base/core/jni/android_util_Log.cpp"
/*
* JNI registration.
*/
static const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
...
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
...
};
/*
* In class android.util.Log:
* public static native int println_native(int buffer, int priority, String tag, String msg)
*/
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;
if (msgObj == NULL) {
jniThrowNullPointerException(env, "println needs a message");
return -1;
}
if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID");
return -1;
}
if (tagObj != NULL)
tag = env->GetStringUTFChars(tagObj, NULL);
msg = env->GetStringUTFChars(msgObj, NULL);
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
if (tag != NULL)
env->ReleaseStringUTFChars(tagObj, tag);
env->ReleaseStringUTFChars(msgObj, msg);
return res;
}
println_native() 對應的jni方法是 ==android_util_Log_println_native()==.
android_util_Log_println_native()很簡單,就是簡單的Log級別檢查后, 就調用 ==__android_log_buf_write()== 做進一步處理.
__android_log_buf_write() 的聲明在 "/system/core/include/log/log.h":
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *text);
__android_log_buf_write() 的定義(實現)在 "/system/core/liblog".
發現liblog中有多個 __android_log_buf_write() 的實現:
logd_write.c
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
...
logd_write_kern.c
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
...
logger_write.c
LIBLOG_ABI_PUBLIC int __android_log_buf_write(int bufID, int prio,
...
那么我們用到的到底是哪個呢? 具體分析在[1.3 /system/core/liblog]介紹.
1.1.2 android.util.writeEvent
分析 writeEvent 到 liblog 的流程高度相似, 細節就不展開了, 這里直接貼下最終的結論:
/frameworks/base/core/java/android/util/EventLog.java writeEvent()
--> /frameworks/base/core/jni/android_util_EventLog.cp android_btWriteLog_xx()
--> /system/core/include/log/logger.h --> log.h android_btWriteLog()
--> /system/core/liblog/logger_write.c __android_log_btwrite() -> write_to_log()
==__android_log_btwrite()== 后面調用到 ==write_to_log()==. 前面提到的 __android_log_buf_write() 也是直接調用到 write_to_log() .
即結論是: android.util.EvnentLog 跟 Android.util.Log 打印日志的流程相同, 都是轉到 /system/core/liblog/logger_write.c 去處理.
android.util.writeEvent.writeEvent() --> android_btWriteLog_xx() (/frameworks/base/core/jni/android_util_EventLog.cpp)
--> android_btWriteLog() (/system/core/include/log/log.h) --> __android_log_btwrite() (/system/core/liblog/logger_write.c) --> write_to_log()
1.2 Native Code : ALOG / ALOGE / ALOGD ..
我們還會看到一些native code(主要是C/C++)也有打印log:
// xxx.c / xxx.cpp
#define LOG_TAG "fingerprintd"
ALOG(LOG_VERBOSE, LOG_TAG, "lockout\n");
ALOGE("Invalid callback object");
ALOGD("onAcquired(%d), duplicatedFingerId(%d)", 1, 2);
這些形如 "[ASR]LOG[VDIWE]" 的函數的聲明在 "/system/core/include/log/log.h":
// ALOGD/ALOGE ---> ALOG ---> LOG_PRI ---> android_printLog ---> __android_log_print()
/*
* Basic log message macro.
*
* Example:
* ALOG(LOG_WARN, NULL, "Failed with error %d", errno);
*
* The second argument may be NULL or "" to indicate the "global" tag.
*/
#ifndef ALOG
#define ALOG(priority, tag, ...) \
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif
/*
* Simplified macro to send an error log message using the current LOG_TAG.
*/
#ifndef ALOGE
#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif
/*
* Simplified macro to send a debug log message using the current LOG_TAG.
*/
#ifndef ALOGD
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
/////////////////////////////////////////////
/*
* Log macro that allows you to specify a number for the priority.
*/
#ifndef LOG_PRI
#define LOG_PRI(priority, tag, ...) \
android_printLog(priority, tag, __VA_ARGS__)
#endif
#define android_printLog(prio, tag, fmt...) \
__android_log_print(prio, tag, fmt)
在宏里兜兜轉轉,最后走到 __android_log_print() 函數:
ALOGD/ALOGE ---> ALOG ---> LOG_PRI ---> android_printLog ---> __android_log_print()
而 ==__android_log_print()== 函數的實現在 "/system/core/liblog":
// 只列出其中一個實現.
// /system/core/liblog/logger_write.c
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);
}
__android_log_print() 函數只是 __android_log_buf_write() 的簡單包裝.
所以, 形如 "[ASR]LOG[VDIWE]" 的的log輸出, 最終跟 android.util.Log 一樣, 也是走到 "/system/core/liblog"的 __android_log_buf_write() 處理.
1.3 /system/core/liblog
上面2節都提到, 關鍵函數 __android_log_buf_write() . 以及遺留了一個問題 "__android_log_buf_write() 的多個實現, 到底哪個才是最終的實現?
結論是 "有 "LIBLOG_ABI_PUBLIC" 宏修飾的, 是最終被使用的實現."
// /system/core/liblog/logger_write.c
LIBLOG_ABI_PUBLIC int __android_log_buf_write(int bufID, int prio,
const char *tag, const char *msg)
{
// ....
return write_to_log(bufID, vec, 3);
}
那么怎么得道的這個結論呢?
__android_log_buf_write() 分別在 "logd_write.c" / "logd_write_kern.c" / "logger_write.c" 三個文件都有實現.
logd_write.c
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
...
logd_write_kern.c
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
...
logger_write.c
LIBLOG_ABI_PUBLIC int __android_log_buf_write(int bufID, int prio,
...
根據liblog模塊的mk文件 "/system/core/liblog/Android.mk", 知道前2個都沒有參與編譯. 只有第三個文件 "logger_write.c" 參與了編譯.
另外結合閱讀 liblog 模塊的代碼, 也可以確認, logd_write.c 和 logd_write_kern.c 都是歷史殘留代碼.
在 [1.6 Android 4.4 liblog] 對舊的log邏輯做一些分析.
回到 logger_write.c 的 __android_log_buf_write() 繼續跟進.
// /system/core/liblog/logger_write.c
LIBLOG_ABI_PUBLIC int __android_log_buf_write(int bufID, int prio,
const char *tag, const char *msg)
{
struct iovec vec[3];
char tmp_tag[32];
if (!tag)
tag = "";
/* XXX: This needs to go! */
if ((bufID != LOG_ID_RADIO) &&
(!strcmp(tag, "HTC_RIL") ||
!strncmp(tag, "RIL", 3) || /* Any log tag with "RIL" as the prefix */
!strncmp(tag, "IMS", 3) || /* Any log tag with "IMS" as the prefix */
!strcmp(tag, "AT") ||
!strcmp(tag, "GSM") ||
!strcmp(tag, "STK") ||
!strcmp(tag, "CDMA") ||
!strcmp(tag, "PHONE") ||
!strcmp(tag, "SMS"))) {
bufID = LOG_ID_RADIO;
/* Inform third party apps/ril/radio.. to use Rlog or RLOG */
snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
tag = tmp_tag;
}
#if __BIONIC__
if (prio == ANDROID_LOG_FATAL) {
android_set_abort_message(msg);
}
#endif
vec[0].iov_base = (unsigned char *)&prio;
vec[0].iov_len = 1;
vec[1].iov_base = (void *)tag;
vec[1].iov_len = strlen(tag) + 1;
vec[2].iov_base = (void *)msg;
vec[2].iov_len = strlen(msg) + 1;
return write_to_log(bufID, vec, 3); // [1.1.2 android.util.writeEvent] 提到, eventlog會走到這個函數.
}
__android_log_buf_write() 調用一次就是一條log.
它先處理了log標簽"tag"如果為NULL, 則將其置為空字符"", 然后處理了radio log的特殊情況.
都ok以后,將出bufID以外的所有參數都封裝到一個 iovec struct的數組中. 熟悉Linux C/C編程的小伙伴應該對 iovec 不陌生.
#include <sys/uio.h>
struct iovec {
ptr_t iov_base; // Starting address / iov_base指向一個緩沖區
size_t iov_len; // Length in bytes / 確定了接收的最大長度 or 實際寫入的長度
};
指針 iov_base 指向一個緩沖區,這個緩沖區是存放的是 writev() 將要發送的數據, 或 readv() 所接收的數據.
成員 iov_len 在各種情況下分別確定了 實際寫入的長度 or 接收的最大長度.
所以我們知道 write_to_log() 內部肯定要調用到 writev() 了.
以為后面很容易就可以看到調用 writev() ? 其實還是有一段曲折的.
// /media/moasm/Samsung_SSD_1T/M1871_NF7_base/system/core/liblog/logger_write.c
static int __write_to_log_init(log_id_t, struct iovec *vec, size_t nr);
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) { // 第一次調用到 write_to_log() , if條件肯定成立
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; // write_to_log 函數指針,重新指向到 __write_to_log_daemon 函數
}
__android_log_unlock();
return write_to_log(log_id, vec, nr);
}
函數指針 write_to_log 先初始化指向 __write_to_log_init()函數.
當第一次調用 write_to_log 就等于調用 __write_to_log_init().
然后后續的調用 write_to_log :
要么指向 __write_to_log_daemon() 函數, 以后調用 write_to_log 就直接調到 __write_to_log_daemon();
要么繼續指向 __write_to_log_init() 函數, 但是依然會走到 __write_to_log_daemon() 函數.
總之, 要到 __write_to_log_daemon() 函數繼續跟進.
static int __write_to_log_daemon(log_id_t log_id, struct iovec *vec, size_t nr)
{
struct android_log_transport_write *node;
int ret;
struct timespec ts;
size_t len, i;
for (len = i = 0; i < nr; ++i) { // 檢查 vec 參數是否合法
len += vec[i].iov_len;
}
if (!len) {
return -EINVAL;
}
#if defined(__BIONIC__)
if (log_id == LOG_ID_SECURITY) {
// ...
// check_log_uid_permissions() ...
// __android_log_security() ...
// ...
} else if (log_id == LOG_ID_EVENTS) {
// ...
// tag = android_lookupEventTag(m, get4LE(vec[0].iov_base));
// ret = __android_log_is_loggable(ANDROID_LOG_INFO,
// tag,
// ANDROID_LOG_VERBOSE);
// if (!ret) {
// return -EPERM;
// }
// ...
} else {
// ...
if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
return -EPERM;
}
}
clock_gettime(android_log_clockid(), &ts);
#else
/* 下面小段代碼同 clock_gettime(CLOCK_REALTIME, &ts); */
{
struct timeval tv;
gettimeofday(&tv, NULL);
ts.tv_sec = tv.tv_sec;
ts.tv_nsec = tv.tv_usec * 1000;
}
#endif
ret = 0;
i = 1 << log_id;
write_transport_for_each(node, &__android_log_transport_write) {
if (node->logMask & i) {
ssize_t retval;
retval = (*node->write)(log_id, &ts, vec, nr);
if (ret >= 0) {
ret = retval;
}
}
}
write_transport_for_each(node, &__android_log_persist_write) {
if (node->logMask & i) {
(void)(*node->write)(log_id, &ts, vec, nr);
}
}
return ret;
}
總的來說, 這個函數就是從 __android_log_transport_write 和 __android_log_persist_write 這兩個結構中取出節點(node) 執行 write 操作:
(*node->write)(log_id, &ts, vec, nr);
其定義在 "/system/core/liblog/config_write.c":
// /system/core/liblog/config_write.c
LIBLOG_HIDDEN struct listnode __android_log_transport_write =
{ &__android_log_transport_write, &__android_log_transport_write };
LIBLOG_HIDDEN struct listnode __android_log_persist_write =
{ &__android_log_persist_write, &__android_log_persist_write};
static void __android_log_add_transport(
struct listnode *list, struct android_log_transport_write *transport) {
// ...
}
LIBLOG_HIDDEN void __android_log_config_write() {
#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
}
實際上是對 "struct android_log_transport_write logdLoggerWrite" 和 "struct android_log_transport_write pmsgLoggerWrite" 的包裝. 他們分別在 "/system/core/liblog/logd_writer.c" 和 "/system/core/liblog/pmsg_writer.c" 中實現:
// /system/core/liblog/logd_writer.c
static int logdAvailable(log_id_t LogId);
static int logdOpen();
static void logdClose();
static int logdWrite(log_id_t logId, struct timespec *ts,
struct iovec *vec, size_t nr);
LIBLOG_HIDDEN struct android_log_transport_write logdLoggerWrite = {
.node = { &logdLoggerWrite.node, &logdLoggerWrite.node },
.context.sock = -1,
.name = "logd",
.available = logdAvailable,
.open = logdOpen,
.close = logdClose,
.write = logdWrite,
// chenwenjun add for uplevel log >> kernel log
.fd = -1,
//
};
// .write = logdWrite, --> 實際的 write 操作是由 logdWrite() 函數執行
// /system/core/liblog/pmsg_writer.c
static int pmsgOpen();
static void pmsgClose();
static int pmsgAvailable(log_id_t logId);
static int pmsgWrite(log_id_t logId, struct timespec *ts,
struct iovec *vec, size_t nr);
LIBLOG_HIDDEN struct android_log_transport_write pmsgLoggerWrite = {
.node = { &pmsgLoggerWrite.node, &pmsgLoggerWrite.node },
.context.fd = -1,
.name = "pmsg",
.available = pmsgAvailable,
.open = pmsgOpen,
.close = pmsgClose,
.write = pmsgWrite,
};
// .write = pmsgWrite, --> 實際的 write 操作是由 pmsgWrite() 函數執行
總的來說, "/system/core/liblog/logd_writer.c"的實現, 就是把log寫到名為 "/dev/socket/logdw" 的socket :
// /system/core/liblog/logd_writer.c
// logdOpen() 連接"/dev/socket/logdw" socket,記錄socket句柄到 logdLoggerWrite.context.sock
static int logdOpen()
{
int i, ret = 0;
if (logdLoggerWrite.context.sock < 0) {
i = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0));
if (i < 0) {
ret = -errno;
} else if (TEMP_FAILURE_RETRY(fcntl(i, F_SETFL, O_NONBLOCK)) < 0) {
ret = -errno;
close(i);
} else {
struct sockaddr_un un;
memset(&un, 0, sizeof(struct sockaddr_un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, "/dev/socket/logdw");
if (TEMP_FAILURE_RETRY(connect(i, (struct sockaddr *)&un,
sizeof(struct sockaddr_un))) < 0) {
ret = -errno;
close(i);
} else {
logdLoggerWrite.context.sock = i;
}
}
}
return ret;
}
// 關閉socket
static void logdClose()
{
if (logdLoggerWrite.context.sock >= 0) {
close(logdLoggerWrite.context.sock);
logdLoggerWrite.context.sock = -1;
}
}
// logdWrite() 把log非阻塞地寫出到socket .
static int logdWrite(log_id_t logId, struct timespec *ts,
struct iovec *vec, size_t nr)
{
ssize_t ret;
static const unsigned headerLength = 1;
struct iovec newVec[nr + headerLength];
android_log_header_t header;
size_t i, payloadSize;
static atomic_int_fast32_t dropped;
static atomic_int_fast32_t droppedSecurity;
if (logdLoggerWrite.context.sock < 0) {
return -EBADF;
}
/* logd, after initialization and priv drop */
if (__android_log_uid() == AID_LOGD) {
/*
* ignore log messages we send to ourself (logd).
* Such log messages are often generated by libraries we depend on
* which use standard Android logging.
*/
return 0;
}
/*
* struct {
* // what we provide to socket
* android_log_header_t header;
* // caller provides
* union {
* struct {
* char prio;
* char payload[];
* } string;
* struct {
* uint32_t tag
* char payload[];
* } binary;
* };
* };
*/
header.tid = gettid();
header.realtime.tv_sec = ts->tv_sec;
header.realtime.tv_nsec = ts->tv_nsec;
newVec[0].iov_base = (unsigned char *)&header;
newVec[0].iov_len = sizeof(header);
if (logdLoggerWrite.context.sock > 0) {
int32_t snapshot = atomic_exchange_explicit(&droppedSecurity, 0,
memory_order_relaxed);
if (snapshot) {
android_log_event_int_t buffer;
header.id = LOG_ID_SECURITY;
buffer.header.tag = htole32(LIBLOG_LOG_TAG);
buffer.payload.type = EVENT_TYPE_INT;
buffer.payload.data = htole32(snapshot);
newVec[headerLength].iov_base = &buffer;
newVec[headerLength].iov_len = sizeof(buffer);
ret = TEMP_FAILURE_RETRY(writev(logdLoggerWrite.context.sock, newVec, 2));
if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
atomic_fetch_add_explicit(&droppedSecurity, snapshot,
memory_order_relaxed);
}
}
snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
if (snapshot && __android_log_is_loggable(ANDROID_LOG_INFO,
"liblog",
ANDROID_LOG_VERBOSE)) {
android_log_event_int_t buffer;
header.id = LOG_ID_EVENTS;
buffer.header.tag = htole32(LIBLOG_LOG_TAG);
buffer.payload.type = EVENT_TYPE_INT;
buffer.payload.data = htole32(snapshot);
newVec[headerLength].iov_base = &buffer;
newVec[headerLength].iov_len = sizeof(buffer);
ret = TEMP_FAILURE_RETRY(writev(logdLoggerWrite.context.sock, newVec, 2));
if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
atomic_fetch_add_explicit(&dropped, snapshot,
memory_order_relaxed);
}
}
}
header.id = logId;
for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
newVec[i].iov_base = vec[i - headerLength].iov_base;
payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;
if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
if (newVec[i].iov_len) {
++i;
}
break;
}
}
/*
* The write below could be lost, but will never block.
*
* ENOTCONN occurs if logd dies.
* EAGAIN occurs if logd is overloaded.
*/
ret = TEMP_FAILURE_RETRY(writev(logdLoggerWrite.context.sock, newVec, i));
if (ret < 0) {
ret = -errno;
if (ret == -ENOTCONN) {
__android_log_lock();
logdClose();
ret = logdOpen();
__android_log_unlock();
if (ret < 0) {
return ret;
}
ret = TEMP_FAILURE_RETRY(writev(logdLoggerWrite.context.sock, newVec, i));
if (ret < 0) {
ret = -errno;
}
}
}
if (ret > (ssize_t)sizeof(header)) {
ret -= sizeof(header);
} else if (ret == -EAGAIN) {
atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
if (logId == LOG_ID_SECURITY) {
atomic_fetch_add_explicit(&droppedSecurity, 1,
memory_order_relaxed);
}
}
return ret;
}
socket "/dev/socket/logdw" 是由 logd (/system/core/logd) 創建的socket. 將在 [1.3 logd ] 中介紹.
而 "/system/core/liblog/pmsg_writer.c" 的實現, 總的來說是把日志寫到 "/dev/pmsg0" 文件去:
// /system/core/liblog/pmsg_writer.c
static int pmsgOpen()
{
if (pmsgLoggerWrite.context.fd < 0) {
pmsgLoggerWrite.context.fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY | O_CLOEXEC));
}
return pmsgLoggerWrite.context.fd;
}
static int pmsgWrite(log_id_t logId, struct timespec *ts,
struct iovec *vec, size_t nr)
{
static const unsigned headerLength = 2;
struct iovec newVec[nr + headerLength];
android_log_header_t header;
android_pmsg_log_header_t pmsgHeader;
size_t i, payloadSize;
ssize_t ret;
if ((logId == LOG_ID_EVENTS) && !__android_log_is_debuggable()) {
if (vec[0].iov_len < 4) {
return -EINVAL;
}
if (SNET_EVENT_LOG_TAG != get4LE(vec[0].iov_base)) {
return -EPERM;
}
}
if (pmsgLoggerWrite.context.fd < 0) {
return -EBADF;
}
/*
* struct {
* // what we provide to pstore
* android_pmsg_log_header_t pmsgHeader;
* // what we provide to file
* android_log_header_t header;
* // caller provides
* union {
* struct {
* char prio;
* char payload[];
* } string;
* struct {
* uint32_t tag
* char payload[];
* } binary;
* };
* };
*/
pmsgHeader.magic = LOGGER_MAGIC;
pmsgHeader.len = sizeof(pmsgHeader) + sizeof(header);
pmsgHeader.uid = __android_log_uid();
pmsgHeader.pid = getpid();
header.id = logId;
header.tid = gettid();
header.realtime.tv_sec = ts->tv_sec;
header.realtime.tv_nsec = ts->tv_nsec;
newVec[0].iov_base = (unsigned char *)&pmsgHeader;
newVec[0].iov_len = sizeof(pmsgHeader);
newVec[1].iov_base = (unsigned char *)&header;
newVec[1].iov_len = sizeof(header);
for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
newVec[i].iov_base = vec[i - headerLength].iov_base;
payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;
if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
if (newVec[i].iov_len) {
++i;
}
payloadSize = LOGGER_ENTRY_MAX_PAYLOAD;
break;
}
}
pmsgHeader.len += payloadSize;
ret = TEMP_FAILURE_RETRY(writev(pmsgLoggerWrite.context.fd, newVec, i));
if (ret < 0) {
ret = errno ? -errno : -ENOTCONN;
}
if (ret > (ssize_t)(sizeof(header) + sizeof(pmsgHeader))) {
ret -= sizeof(header) - sizeof(pmsgHeader);
}
return ret;
}
ps: 目前我們手頭的機型, "/dev/pmsg0" 是未被創建出來的. 所以這段代碼是無效的.
我們在創建 "/dev/pmsg0" 并配置合適的權限 和selinux規則后, 是可以同步的bug日志也導到這個設備來的.
(因為liblog.so是運行在每個打印日志的進程中的, "/dev/pmsg0"要創建成能被所有進程讀寫)
本節有點長(主要是貼了2個較長的函數), 這里小結下:
"/system/core/liblog" 模塊編譯生成 "/system/lib(64)/liblog.so".
liblog.so 被所有需要打印日志的進程加載使用, 負責處理打印日志流程.
目前(Android N)上, 它會做嘗試將日志輸出到2個地方:
1> 通過socket "/dev/socket/logdw" 將日志輸送到 logd (/system/bin/logd)進程.
2> 將log直接寫入 "/dev/pmsg0" 文件 (如果存在,且可訪問的話).
"/system/core/liblog"的內部有一些無用的舊版代碼. 關于進程寫出日志,有用的主要源碼文件是:
/system/core/include/log/log.h // liblog 模塊給別的模塊使用的頭文件
/system/core/liblog/logger_write.c // 入口
/system/core/liblog/config_write.h // config_write 可以理解為配置管理
/system/core/liblog/config_write.c
// logd_writer.c 和 pmsg_writer.c 是日志如何寫出的實際實現者
/system/core/liblog/logd_writer.c // !!! 注意有個 "logd_write.c" 文件容易搞混淆 !!!
/system/core/liblog/pmsg_writer.c
ps: 一個用戶進程要讀取日志, 也要用到liblog.so庫. 這里就不展開了.
1.3 logd
上幾節提到, 無論用戶是從Log.java類,還是在 native層調用形如 ALOG 這樣的函數(宏函數)打印log, 最后都是走到 liblog 的 __android_log_buf_write() 函數, 即"/system/lib(64)/liblog.so".
liblog 總的來說就2件事, 其中一件事就是寫日志到socket "/dev/socket/logdw".
而這個socket "/dev/socket/logdw"就是logd負責創建的:
// /system/core/logd/logd.rc
service logd /system/bin/logd
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
socket logdw dgram 0222 logd logd
group root system readproc
writepid /dev/cpuset/system-background/tasks
另外, 我們也可以看到 "socket logdr" 的創建. logcat 連接 socket logdr 獲取日志.
tips 1 : "socket logdr" 最后創建出來的文件是 "/dev/socket/logdr"
tips 2 : "socket logd" 最后創建出來的文件是 "/dev/socket/logd"
tips 3 : 我們說的"文件"是linux世界里的文件. linux的設計是"一切皆是文件"
通過ls命令查看 logd / logdr / logdw三個文件
$ adb shell ls -alZ /dev/socket | grep logd
srw-rw-rw- 1 logd logd u:object_r:logd_socket:s0 0 2018-05-29 15:49 logd
srw-rw-rw- 1 logd logd u:object_r:logdr_socket:s0 0 2018-05-29 15:49 logdr
s-w--w--w- 1 logd logd u:object_r:logdw_socket:s0 0 2018-05-29 15:49 logdw
注意到他們的權限字段都是 "srw-rw-rw-", 最前面的 "s" 即表示該文件是 socket.
logd 模塊源碼在 /system/core/logd/, 編譯的目標是linux可執行文件 "/system/bin/logd".
為常住進程:
$ adb shell ps | grep logd
logd 470 1 18088 2096 sigsuspend 0000000000 S /system/bin/logd
logd中有循環緩沖區 來存儲接收來的log.
屬性字段 "persist.logd.size" 控制log緩沖區的大小:
// 查看logd log緩沖區大小:
$ adb shell getprop persist.logd.size
256KB
// 修改logd log緩沖區大小:
$ adb shell setprop persist.logd.size 512KB
logd中有30個左右C/C++源文件. 大部分是C++文件,可見整個模塊是面向對象編程的.
這里大概畫出一下UML圖(偽).
1> 接收寫到logdw socket的日志:
2> 響應通過logdr socket獲取日志:
3> logd抓取 Kernel Log (簡介見 [1.5 logd 捕獲 kernel log] ):
1.4 logcat
上一節提到, logcat是通過連接 socket "/dev/socket/logdr" 來獲取日志的.
logcat 就是負責從 logd 獲取日志, 然后展示給使用者,或轉存到文件.
logcat也可以清除 logd 中的日志緩存, 命令是"adb shell logcat -c" 或 "adb logcat -c".
logcat 可以按照進程, 模塊, 標簽 過濾查看log. 也可以定制log查看的格式.
具體用法 使用 "$ adb shell logcat -h" 查看.
默認情況下, 一條log的格式形如下:
05-29 15:30:58.325 2943 2943 I Strategy: xxxxxxxxxx
日期 時間 pid tid log級別 log標簽 log正文
logcat模塊源碼在 "/system/core/logcat", 編譯目標為 "/system/bin/logcat" .
以下簡要介紹下, logcat 從 logd 獲取日志的流程
// /system/core/logcat/logcat.cpp
struct log_device_t {
const char* device;
bool binary;
struct logger *logger;
struct logger_list *logger_list;
bool printed;
log_device_t* next;
log_device_t(const char* d, bool b) {
device = d;
binary = b;
next = NULL;
printed = false;
logger = NULL;
logger_list = NULL;
}
};
namespace android {
// ...
static int g_devCount; // >1 means multiple
// ...
int main(int argc, char **argv)
{
log_device_t* devices = NULL;
log_device_t* dev;
bool printDividers = false;
struct logger_list *logger_list;
// ... 參數解析 ...
// 獲取 "main" / "system" / "crash" 三個log緩沖區
if (!devices) {
dev = devices = new log_device_t("main", false);
g_devCount = 1;
if (android_name_to_log_id("system") == LOG_ID_SYSTEM) { // android_name_to_log_id() 見 system/core/liblog/logger_name.c
dev = dev->next = new log_device_t("system", false);
g_devCount++;
}
if (android_name_to_log_id("crash") == LOG_ID_CRASH) {
dev = dev->next = new log_device_t("crash", false);
g_devCount++;
}
}
setupOutput(); // 設置log輸出格式
if (hasSetLogFormat == 0) {
const char* logFormat = getenv("ANDROID_PRINTF_LOG");
if (logFormat != NULL) {
err = setLogFormat(logFormat);
if (err < 0) {
fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n",
logFormat);
}
} else {
setLogFormat("threadtime");
}
}
// ... 設置 log過濾. 調用 android_log_addFilterString() , 源碼在 system/core/liblog/logprint.c
// 一系列繁復代碼, 打醬油, 初始化
/*********************************************************************************
其核心邏輯就是 走 "/system/core/liblog/logger_read.c", 獲取到 struct android_log_transport_read.
目前, 實際上有2個這樣的結構:
// /system/core/liblog/config_read.c
extern struct android_log_transport_read logdLoggerRead;
extern struct android_log_transport_read pmsgLoggerRead;
這個結構記錄了要去讀日志所需的基本參數 和函數. 如果 logdLoggerRead 的 ".read = logdRead,"就表示記錄 loadRead()函數為讀取日志的方法函數.
// /system/core/liblog/logd_reader.c
LIBLOG_HIDDEN struct android_log_transport_read logdLoggerRead = {
.node = { &logdLoggerRead.node, &logdLoggerRead.node },
.name = "logd",
.available = logdAvailable,
.version = logdVersion,
.read = logdRead,
.poll = logdPoll,
.close = logdClose,
.clear = logdClear,
.getSize = logdGetSize,
.setSize = logdSetSize,
.getReadableSize = logdGetReadableSize,
.getPrune = logdGetPrune,
.setPrune = logdSetPrune,
.getStats = logdGetStats,
};
// /system/core/liblog/pmsg_reader.c
LIBLOG_HIDDEN struct android_log_transport_read pmsgLoggerRead = {
.node = { &pmsgLoggerRead.node, &pmsgLoggerRead.node },
.name = "pmsg",
.available = pmsgAvailable,
.version = pmsgVersion,
.read = pmsgRead,
.poll = NULL,
.close = pmsgClose,
.clear = pmsgClear,
.setSize = NULL,
.getSize = NULL,
.getReadableSize = NULL,
.getPrune = NULL,
.setPrune = NULL,
.getStats = NULL,
};
前者(logdLoggerRead) 是負責讀取 logd中的日志(通過讀socket "/dev/socket/logdw");
后者(pmsgLoggerRead) 是負責讀取 "/dev/pmsg0" 文件中的日志. 直接文件讀取. (目前該文件沒有被創建, 所有沒有日志輸出到這里)
即, 想要了解 logcat 如何讀取日志的, 關鍵看 "/system/core/liblog/logd_reader.c" 和 "/system/core/liblog/pmsg_reader.c" 兩個源文件.
可讀性強, 邏輯清晰.
**********************************************************************************/
}
}
如上分析. 我們發現logcat 自身代碼目錄 "/system/core/logcat" 中基本都是打醬油的代碼,
真正的操作 都交給 "/system/core/liblog" 中的方法了.
經過了若干 可讀性很差的代碼跟進后, 我們發現 :
logcat 如何讀取 logd 緩存的日志的, 關鍵看 "/system/core/liblog/logd_reader.c" 源文件.
可讀性強, 邏輯清晰.
"/system/core/liblog/logd_reader.c"顯示, logcat 是通過 連接 socket "logr" 來去讀 logd中緩存的日志的:
// /system/core/liblog/logd_reader.c
// 初始化 socket "logdr"
static int logdOpen(struct android_log_logger_list *logger_list,
struct android_log_transport_context *transp)
{
// ...
int sock = transp->context.sock; // 如果已經初始化過了socket, 則直接返回.
if (sock > 0) {
return sock;
}
// ...
// 創建 socket_local_client 客戶端
sock = socket_local_client("logdr",
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_SEQPACKET);
if (sock == 0) {
/* Guarantee not file descriptor zero */
int newsock = socket_local_client("logdr",
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_SEQPACKET);
close(sock);
sock = newsock;
}
if (sock <= 0) {
if ((sock == -1) && errno) {
return -errno;
}
return sock;
}
// 操作 socket_local_client 結構, 連接socket
return transp->context.sock = sock;
}
// 從 socket "logdr" 讀取日志:
static int logdRead(struct android_log_logger_list *logger_list,
struct android_log_transport_context *transp,
struct log_msg *log_msg)
{
int ret, e;
// ...
ret = logdOpen(logger_list, transp); // 確保socket 打開
if (ret < 0) {
return ret;
}
memset(log_msg, 0, sizeof(*log_msg)); // 初始化接收緩沖區
// ...
/* NOTE: SOCK_SEQPACKET guarantees we read exactly one full entry */
ret = recv(ret, log_msg, LOGGER_ENTRY_MAX_LEN, 0); // 從socket中接收日志
e = errno;
// ...
if ((ret == -1) && e) {
return -e;
}
return ret;
}
其他關于 "logcat -c " 發生了什么的, 不再贅述.可自行閱讀 "/system/core/liblog/logd_reader.c"
tips :
logcat 調用了很多 liblog 模塊的函數. 代碼跟起來有點麻煩.
例如有一個有意思的細節: struct android_log_logger_list 和 struct logger_list 的轉換.
"struct logger_list"的定義在 "/system/core/include/log/logger.h" :
struct logger_list; // 定義了一個空結構體
android_log_logger_list的定義在 "/system/core/liblog/logger.h" :
struct android_log_logger_list {
struct listnode logger;
struct listnode transport;
int mode;
unsigned int tail;
log_time start;
pid_t pid;
};
發現使用中, struct android_log_logger_list 和 struct logger_list 會相互轉換, 如:
// /system/core/liblog/logger_read.c
LIBLOG_ABI_PUBLIC struct logger_list *android_logger_list_alloc_time( // cwj
int mode,
log_time start,
pid_t pid)
{
struct android_log_logger_list *logger_list; // 實際數據結構是 android_log_logger_list
// ...
return (struct logger_list *)logger_list; // 但是給外界使用 是強轉為 logger_list
}
LIBLOG_ABI_PUBLIC struct logger *android_logger_open(
struct logger_list *logger_list,
log_id_t logId)
{
struct android_log_logger_list *logger_list_internal =
(struct android_log_logger_list *)logger_list; // struct logger_list 強轉 struct android_log_logger_list .
// ...
}
為什么要"畫蛇添足"多定義一 個"struct logger_list; " 空結構體呢?
猜測 /system/core/include/log/logger.h" 是對外的. 而 "/system/core/liblog/logger.h" 是對 liblog模塊內的.
liblog設計者想對外隱藏內部數據結構細節.
在能夠隱藏內部細節的情況下, 開發者盡量選擇了隱藏內部細節, 因為外部不需要關心 struct android_log_logger_list 的內部實現,
所以只需要給出 struct logger_list * 去存儲 struct android_log_logger_list 的內存地址, 上層后續有什么操作就把這個結構的地址傳入liblog模塊操作.
即外界根本不需要知道 struct android_log_logger_list 的實現細節.
就像調用 open() API 打開文件, 我們只需拿到文件句柄 FD. 而不需要關系文件系統內部的數據結構. 后續要操作此文件, FD 就是我們的 token(令牌, 資源的身份識別碼).
1.5 logd 捕獲 kernel log
通過設置" adb shell setprop logd.kernel true" 可以讓logd去抓取 Kernel Log.(一直監聽并抓取)
實現原理其實是把 "/proc/kmsg" 和 "/dev/kmsg" 文件當作socket 文件來使用.(詳情追蹤 LogKlog.h )
實測發現, 需要root權限才能才能設置 "setprop logd.kernel true".
代碼如下:
// /system/core/logd/main.cpp
int main(int argc, char *argv[]) {
int fdPmesg = -1;
bool klogd = property_get_bool("logd.kernel", // 獲取 "logd.kernel" property, 確定是否開啟了 kernel log 重定向到 User Log .
BOOL_DEFAULT_TRUE |
BOOL_DEFAULT_FLAG_PERSIST |
BOOL_DEFAULT_FLAG_ENG |
BOOL_DEFAULT_FLAG_SVELTE);
if (klogd) {
fdPmesg = open("/proc/kmsg", O_RDONLY | O_NDELAY); // "/proc/kmsg" 用來讀kernel log
}
fdDmesg = open("/dev/kmsg", O_WRONLY); // "/dev/kmsg" 用來寫 kernel log
//....
bool auditd = property_get_bool("logd.auditd",
BOOL_DEFAULT_TRUE |
BOOL_DEFAULT_FLAG_PERSIST);
LogAudit *al = NULL;
if (auditd) {
al = new LogAudit(logBuf, reader,
property_get_bool("logd.auditd.dmesg",
BOOL_DEFAULT_TRUE |
BOOL_DEFAULT_FLAG_PERSIST)
? fdDmesg
: -1);
}
LogKlog *kl = NULL;
if (klogd) {
kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != NULL); // 核心邏輯在 LogKlog
}
readDmesg(al, kl);
// failure is an option ... messages are in dmesg (required by standard)
if (kl && kl->startListener()) { // startListener成功時返回 0, 失敗返回非0(多數情況是-1)
delete kl;
}
if (al && al->startListener()) {
delete al;
}
TEMP_FAILURE_RETRY(pause());
exit(0);
}
1.6 Android 4.4 liblog
在Android 4.4 及更早的系統中, liblog并像現在這樣通過socket把log傳到logd, 而是直接寫到
"/dev/log/main",
"/dev/log/events",
"/dev/log/system",
"dev/log/radio"
四個文件文件去.
liblog中殘留的 "/system/core/liblog/logd_write_kern.c"就是殘留的舊代碼.
他們現在不生效的原因, 此源文件沒有被包好在模塊的Android.mk文件中, 不會參與編譯.
往上有介紹如何打補丁,讓高版本Android會到這種舊版日志的方法:
https://blog.csdn.net/kc58236582/article/details/72276041
其核心就是2點:
1> 讓 logd_write_kern.c 參與編譯.
2> 修改內核, 使其創建"/dev/log/*",并配置selinux. (目前上文中提到的這一點的連接已經失效)
理論上是可行的. 我們可以在logd莫名其妙掛掉的時候(最近于到這樣的案例, 才產生研究log流程的需求)
, 將日志輸出到這里來.
但是這并沒有意思, 前面我們提到, 現有的代碼中預留了"/dev/pmsg0"設備接管可存儲log.
但是目前此文件也是沒被創建, 所以不會有日志輸出到這里.
目前看, logd掛掉非常罕見. 且logd掛了要找去掛了的原因, kernel log已經足夠詳細, User Log不會對定為logd掛的原因有幫助.
故"liblog庫直接存log到其他文件"的需求暫定沒有開發的意義.
2.0 stdout / stderr 與 logwrapper
據"網傳", Android把系統標準輸出設備 stdout / stderr 重定向到了 "/dev/null" (黑洞文件,有進無出) 網傳屬實.
Android把所有Java虛擬機進程的"系統標準輸出設備" stdout / stderr 重定向到了 "/dev/null" (黑洞文件,有進無出)
, 在java虛擬機初始化較早的時候就操作了:
http://androidxref.com/7.1.2_r36/xref/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java#343
而其他非java虛擬機進程是再哪里關閉的, 有興趣可以自己跟下. (可以找到)
發現logwrapper 可以將 輸出到 stdout / stderr 的信息重定向到"User Log".
這里只對 logwrapper 用法做簡單介紹:
$ adb shell logwrapper
Usage: logwrapper [-a] [-d] [-k] BINARY [ARGS ...]
Forks and executes BINARY ARGS, redirecting stdout and stderr to
the Android logging system. Tag is set to BINARY, priority is
always LOG_INFO.
-a: Causes logwrapper to do abbreviated logging.
This logs up to the first 4K and last 4K of the command
being run, and logs the output when the command exits
-d: Causes logwrapper to SIGSEGV when BINARY terminates
fault address is set to the status of wait()
-k: Causes logwrapper to log to the kernel log instead of
the Android system log
參數說明:
-a 僅拿最前面 和最后面 4K (一共最多8K)的日志
-d 被操作進程如果發生內存錯誤而終結, 就給它發 SIGSEGV 信號.
-k 重定向到 "Kernel log" (默認是重定向到 User Log)
BINARY 是目標, 如"ps"命令.
[ARGS ...] 是給目標可執行文件傳的參數.
用法示范:
// 執行 "ps" 命令, 并將其的輸出信息 重定向"User Log"
$ adb shell logwrapper ps
// 執行 "ps" 命令, 并將其的輸出信息 重定向"Kernel Log"
$ adb shell logwrapper -k ps
以上兩個分身執行后, 可以通過 logcat 或 dmesg 查看拿到 ps的輸出信息了.
3.0 kernel log
在內核模塊的代碼, 通常通過 "printk" 這個"宏函數"輸出 Kernel log.
用法簡單. 不做介紹.
最終 Kernel log會輸出到 "/proc/kmsg", "/dev/kmsg" 這兩個文件.
"/dev/kmsg"是循環緩沖區, 存儲所有 kernel log(超出緩沖區的舊log除外).
"adb shell dmesg" 命令是取的這個文件的內容.
"adb shell cat /proc/kmsg" 則可以只拿這條命令執行之后才輸出的 kernel log, 且是持續監聽更新.
這點比dmesg好,(dmesg是取一次 kernel log, 然后就退出了) , 在開發中, 方便我們實時的看最新的 kernel log .
ps:就是需要root權限. 限制了使用.
3.1 libcutils user space print kernel log
"/system/core/init/log.cpp" 中的 init_klog_write() 函數 可以讓應用也能寫 Kernel log.
代碼很簡單, 就是在寫 "/dev/kmsg" 文件.
也就是說, 應用得有訪問 "/dev/kmsg" 的權限 (尤其是 selinux限制), 才能寫 kernel log.
4.0 貝海拾遺
4.1 局部編譯問題
試驗發現, 對liblog的修改, 局部編譯該模塊 push到手機, 修改不能生效.
(使用 "adb sync system" 命令, 已經確保編譯出爭取的 liblog.so, 且成功push到手機, 且有重啟手機)
全編才生效.
猜測:其他so庫靜態依賴這個so庫.別人用的是其他二次包裝的庫?
4.2 System.out.println("test-system.out");
而據"網傳", Android 輸出到標準出設備的數據重定向到"/dev/null", 是正確的的, 觀察任意一個安卓應用程序進程, fd=0,1的兩個, 都重定向到/dev/null了.
屬實:
而原始的JDK的實現中" System.out.println("xx") "實際上就是輸出到FD=1的文件去.
而根據實際經驗, " System.out.println("xx") " 輸出的信息, 以在 logcat 中看到呢.
二者相悖, 這是為何呢?
這是因為Andorid在將fd=0,1 重定向到 "/dev/null"后,
又將System.out的fd設置到'別處', 如下代碼:
// /media/moasm/Samsung_500G/android-9.0.0_r42/frameworks/base/core/java/com/android/internal/os/RuntimeInit.java
public static void redirectLogStreams() {
System.out.close();
System.setOut(new AndroidPrintStream(Log.INFO, "System.out"));
System.err.close();
System.setErr(new AndroidPrintStream(Log.WARN, "System.err"));
}
// /home/moasm/Android/Sdk/platforms/android-28/android.jar!/android/util/Log.class
public final class Log {
/**
* Priority constant for the println method; use Log.v.
*/
public static final int VERBOSE = 2;
/**
* Priority constant for the println method; use Log.d.
*/
public static final int DEBUG = 3;
/**
* Priority constant for the println method; use Log.i.
*/
public static final int INFO = 4;
...
// /media/moasm/Samsung_500G/android-9.0.0_r42/frameworks/base/core/java/com/android/internal/os/AndroidPrintStream.java
protected void log(String line) {
Log.println(priority, tag, line);
}
調用路徑為:
System.out.print --> AndroidPrintStream as PrintStream .write() --> AndroidPrintStream.log() --> andorid.util.Log.println()
這樣,System.out.print最終還是走 andorid.util.Log.println()