当前位置: 首页 > 工具软件 > anylog > 使用案例 >

Android Log原理分析

黄靖
2023-12-01

在android开发的过程中,需要查看log信息来帮助分析。那么知晓log的原理就是比较重要的了。

Framework 中的Log

Framework中的Log比较简单,主要就是封装接口,在接口中调用println_native函数。下面只以其中的一个进行分析。

public static int v(String tag, String msg) {
   if (tag == null) {
		tag = "NullTag";
	 }
	 if (msg == null) {
		msg = "NullMsg";
	 }
	return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
}

默认情况下,log id为main,这个ID的作用后面会提到。这个函数会检查tag & msg。之后就是调用println_native。

JNI中的Log

在JNI中的对应文件为:android_util_Log.cpp

 static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
         jint bufID, jint priority, jstring tagObj, jstring msgObj)
 {
	int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
 }

在这里需要说明一下BufID & priority

  typedef enum android_LogPriority {
      ANDROID_LOG_UNKNOWN = 0,
      ANDROID_LOG_DEFAULT,    // only for SetMinPriority()
      ANDROID_LOG_VERBOSE,
      ANDROID_LOG_DEBUG,
      ANDROID_LOG_INFO,
      ANDROID_LOG_WARN,
      ANDROID_LOG_ERROR,
      ANDROID_LOG_FATAL,
      ANDROID_LOG_SILENT,     // only for SetMinPriority(); must be last
 } android_LogPriority;
 
typedef enum {
     LOG_ID_MAIN = 0,
     LOG_ID_RADIO = 1,
     LOG_ID_EVENTS = 2,
     LOG_ID_SYSTEM = 3, 
     LOG_ID_MAX
} log_id_t;

上面对应的输入那种等级的打印信息。下面的对应打印哪种log。用过 logcat -b system/main/events/main来进行log输出。

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 = "";
	//特殊处理radio的log
    /* 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;
    }

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

这部分是使用linux中的iov的数据格式进行传递的。最后调用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)
{
#ifdef HAVE_PTHREADS
    pthread_mutex_lock(&log_init_lock);
#endif

    if (write_to_log == __write_to_log_init) {
        log_fds[LOG_ID_MAIN] = log_open("/dev/"LOGGER_LOG_MAIN, O_WRONLY);
        log_fds[LOG_ID_RADIO] = log_open("/dev/"LOGGER_LOG_RADIO, O_WRONLY);
        log_fds[LOG_ID_EVENTS] = log_open("/dev/"LOGGER_LOG_EVENTS, O_WRONLY);
        log_fds[LOG_ID_SYSTEM] = log_open("/dev/"LOGGER_LOG_SYSTEM, O_WRONLY);

        write_to_log = __write_to_log_kernel;
	···
    return write_to_log(log_id, vec, nr);
}

在java层加载这个库时write_to_log函数指针指向的是__write_to_log_init。这时候就会打开对应的设备。这些设备有

#define LOGGER_LOG_RADIO	"log_radio"	/* radio-related messages */
#define LOGGER_LOG_EVENTS	"log_events"	/* system/hardware events */
#define LOGGER_LOG_SYSTEM	"log_system"	/* system/framework messages */
#define LOGGER_LOG_MAIN		"log_main"	/* everything else */

设备打开之后,这个函数指针指向__write_to_log_kernel。

static int __write_to_log_kernel(log_id_t log_id, struct iovec *vec, size_t nr)
{
    ssize_t ret;
    int log_fd;
	//查找上面打开的文件描述符
    if (/*(int)log_id >= 0 &&*/ (int)log_id < (int)LOG_ID_MAX) {
        log_fd = log_fds[(int)log_id];
    } else {
        return EBADF;
    }
	//进行写入动作
    do {
        ret = log_writev(log_fd, vec, nr);
    } while (ret < 0 && errno == EINTR);

    return ret;
}

#define log_writev(filedes, vector, count) writev(filedes, vector, count)

最终调用linux中的writev函数。

Log驱动分析

文件路径:common/drivers/staging/android/logger.c

初始化函数分析

驱动初始化函数

#define LOGGER_LOG_RADIO	"log_radio"	/* radio-related messages */
#define LOGGER_LOG_EVENTS	"log_events"	/* system/hardware events */
#define LOGGER_LOG_SYSTEM	"log_system"	/* system/framework messages */
#define LOGGER_LOG_MAIN		"log_main"	/* everything else */
static int __init logger_init(void)
{
	int ret;

	ret = create_log(LOGGER_LOG_MAIN, 256*1024);
	if (unlikely(ret))
		goto out;

	ret = create_log(LOGGER_LOG_EVENTS, 256*1024);
	if (unlikely(ret))
		goto out;

	ret = create_log(LOGGER_LOG_RADIO, 256*1024);
	if (unlikely(ret))
		goto out;

	ret = create_log(LOGGER_LOG_SYSTEM, 256*1024);
	if (unlikely(ret))
		goto out;

out:
	return ret;
}
device_initcall(logger_init);

可以看到驱动中注册的名字与Jni中打开的文件描述符是一一对应的。所以当jni中调用open函数时将会调用驱动中对应的open函数。
再driver中注册的函数有

static const struct file_operations logger_fops = {
	.owner = THIS_MODULE,
	.read = logger_read,
	.aio_write = logger_aio_write,
	.poll = logger_poll,
	.unlocked_ioctl = logger_ioctl,
	.compat_ioctl = logger_ioctl,
	.open = logger_open,
	.release = logger_release,
};

所以jni中调用vwrite函数时就会调用到logger_aio_write函数。
下面继续分析初始化函数中涉及到的create_log

static int __init create_log(char *log_name, int size)
{
	int ret = 0;
	struct logger_log *log;
	unsigned char *buffer;
	//分配size大小的空间,用来存储log信息
	buffer = vmalloc(size);


	log = kzalloc(sizeof(struct logger_log), GFP_KERNEL);
	log->buffer = buffer;

	log->misc.minor = MISC_DYNAMIC_MINOR;
	log->misc.name = kstrdup(log_name, GFP_KERNEL);
	if (log->misc.name == NULL) {
		ret = -ENOMEM;
		goto out_free_log;
	}

	log->misc.fops = &logger_fops;
	log->misc.parent = NULL;

	init_waitqueue_head(&log->wq);
	INIT_LIST_HEAD(&log->readers);
	mutex_init(&log->mutex);
	log->w_off = 0;
	log->head = 0;
	log->size = size;

	INIT_LIST_HEAD(&log->logs);
	list_add_tail(&log->logs, &log_list);//将log->logs放入到log_list中
	//注册misc类型的设备
	/* finally, initialize the misc device for this log */
	ret = misc_register(&log->misc);
	···
	return ret;
}

至此,驱动已经初始化完成。可以接受来自应用的调用。

logger_aio_write函数分析
static ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,
			 unsigned long nr_segs, loff_t ppos)
{
	struct logger_log *log = file_get_log(iocb->ki_filp);
	size_t orig;
	struct logger_entry header;
	struct timespec now;
	ssize_t ret = 0;

	now = current_kernel_time();
	//保存写用户的相关信息
	header.pid = current->tgid;
	header.tid = current->pid;
	header.sec = now.tv_sec;
	header.nsec = now.tv_nsec;
	header.euid = current_euid();
	header.len = min_t(size_t, iocb->ki_nbytes, LOGGER_ENTRY_MAX_PAYLOAD);
	header.hdr_size = sizeof(struct logger_entry);
	···
	orig = log->w_off;

	/*
	 * Fix up any readers, pulling them forward to the first readable
	 * entry after (what will be) the new write offset. We do this now
	 * because if we partially fail, we can end up with clobbered log
	 * entries that encroach on readable buffer.
	 */
	 //在写入log信息之前需要调整readers的一些信息,因为写入的信息有可能会覆盖掉readers正在读入位置的信息
	fix_up_readers(log, sizeof(struct logger_entry) + header.len);

	do_write_log(log, &header, sizeof(struct logger_entry));//写入头内容
	//写入 body
	while (nr_segs-- > 0) {
		size_t len;
		ssize_t nr;

		/* figure out how much of this vector we can keep */
		len = min_t(size_t, iov->iov_len, header.len - ret);

		/* write out this segment's payload */
		nr = do_write_log_from_user(log, iov->iov_base, len);
		if (unlikely(nr < 0)) {//如果写入payload的话,就恢复之前的位置
			log->w_off = orig;
			mutex_unlock(&log->mutex);
			return nr;
		}

		iov++;
		ret += nr;
	}

	mutex_unlock(&log->mutex);
	//写完数据之后,唤醒readers继续进行读取
	/* wake up any blocked readers */
	wake_up_interruptible(&log->wq);

	return ret;
}

其他

在这部分分析的时候,如何重新定位reader的位置比较模糊。下面将分析时的笔记给出

 /* 如果head/readers指向的位置即将被覆盖的话,调整指针,指向/读取下一个entry
 */
 //log, sizeof(struct logger_entry) + header.len
static void fix_up_readers(struct logger_log *log, size_t len)
{
	size_t old = log->w_off;
	size_t new = logger_offset(log, old + len);//new代表的是写入之后的w_off位置
	struct logger_reader *reader;

	if (is_between(old, new, log->head))//也就是当前read的位置将要被覆盖
		log->head = get_next_entry(log, log->head, len);//这种情况对应的应该是head正读取的数据将要被覆盖,所以将head指向下个数据。
	//上面这条执行完成之后,log->head应该是指向len之后的第一个有效的entry
	//如果有reading的reader的话,也要一起调整。
	list_for_each_entry(reader, &log->readers, list)
		if (is_between(old, new, reader->r_off))
			reader->r_off = get_next_entry(log, reader->r_off, len);
}

//获取下一个entry的起始位置
static size_t logger_offset(struct logger_log *log, size_t n)
{
	return n & (log->size - 1);
}

/*
 * do_write_log - writes 'len' bytes from 'buf' to 'log'
 * 将buf中的内容拷贝到log中
 * 这个里面size没有变,所以这个size应该是之的所有buffer的size
 * The caller needs to hold log->mutex.
 */
static void do_write_log(struct logger_log *log, const void *buf, size_t count)
{
	size_t len;

	len = min(count, log->size - log->w_off);//查看还能写多少
	memcpy(log->buffer + log->w_off, buf, len);//将最小的写入
	/*
	* 1,如果count < size - w_off 也就是说buffer中能够放得下。这种情况上面执行完之后 count == len
	* 2。如果count > size - w_off 也就是说buffer剩余空间放不下,那么应该是要覆盖之前的
	*/
	if (count != len)
		memcpy(log->buffer, buf + len, count - len);

	log->w_off = logger_offset(log, log->w_off + count);

}

/*
 * get_next_entry - return the offset of the first valid entry at least 'len'
 * bytes after 'off'.
 * 返回off len bytes之后的第一个有效的 off
 * Caller must hold log->mutex.
 *///log, log->head, len // len应该是将要写入数据的length
static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)
{
	size_t count = 0;
	//这个就是要保证有足够的空间保存len长度的数据
	do {
		//off位置处对应的entry size
		size_t nr = sizeof(struct logger_entry) +
			get_entry_msg_len(log, off);//log, log->head
		off = logger_offset(log, off + nr);
		count += nr;
	} while (count < len);

	return off;
}

 //获取off位置的entry header
 //只是获取entry的头部分
static struct logger_entry *get_entry_header(struct logger_log *log,
		size_t off, struct logger_entry *scratch)
{
	size_t len = min(sizeof(struct logger_entry), log->size - off);
	if (len != sizeof(struct logger_entry)) {
		memcpy(((void *) scratch), log->buffer + off, len);
		memcpy(((void *) scratch) + len, log->buffer,
			sizeof(struct logger_entry) - len);
		return scratch;
	}

	return (struct logger_entry *) (log->buffer + off);
}

比较难懂的部分,在代码中已经添加注释,此处就不在赘述。
上面就是log驱动的分析。

Logcat命令分析

logcat命令的实现方式比较简单,与logd的实现方式基本相同。下面主要看一下他的main函数:

int main(int argc, char **argv)
{
	····
	for (;;) {
		····
		ret = getopt(argc, argv, "cdt:gsQf:r::n:v:b:B");
		···
		switch(ret) {
			//这里面处理命令行里面的参数
		}
		····
		
	}
	//如果没有指定查看的log的话,就打开main/system。
	if (!devices) {
        devices = new log_device_t(strdup("/dev/"LOGGER_LOG_MAIN), false, 'm');
        android::g_devCount = 1;
        int accessmode =
                  (mode & O_RDONLY) ? R_OK : 0
                | (mode & O_WRONLY) ? W_OK : 0;
        // only add this if it's available
        if (0 == access("/dev/"LOGGER_LOG_SYSTEM, accessmode)) {
            devices->next = new log_device_t(strdup("/dev/"LOGGER_LOG_SYSTEM), false, 's');
            android::g_devCount++;
        }
    }
	····
	android::setupOutput();//设置log输出到什么位置。一般为STDOUT
	···
	dev = devices;
    while (dev) {
        dev->fd = open(dev->device, mode);
		···
		readable = android::getLogReadableSize(dev->fd);
		···
		dev = dev->next;
		···
	}
	···
	if (needBinary)//logcat -b events时为真
        android::g_eventTagMap = android_openEventTagMap(EVENT_TAG_MAP_FILE);
	//读取log信息。
    android::readLogLines(devices);
}

总体来说步骤如下:
1.打开文件节点。
2.将节点中的信息输出到stdout或者文件中(通过-f指定输出文件)

EventLogTags.logtags

在分析main/system的log时比较容易。通过Log.d/e/w/i等方式就可以将log写入到驱动的缓存中。但是events要比较特殊一些。下面就介绍一下。
在android sdk中存在着EventLogTags.logtags文件,基本格式如下:

frameworks/base/services/java/com/android/server/EventLogTags.logtags
  1 # See system/core/logcat/event.logtags for a description of the format of this file.
  2 
  3 option java_package com.android.server
  4 
  5 # ---------------------------
  6 # BatteryService.java
  7 # ---------------------------
  8 2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)
  9 2723 battery_status (status|1|5),(health|1|5),(present|1|5),(plugged|1|5),(technology|3)
 10 # This is logged when battery goes from discharging to charging.
 11 # It lets us count the total amount of time between charges and the discharge level
 12 2730 battery_discharge (duration|2|3),(minLevel|1|6),(maxLevel|1|6)
	···

在系统编译时,将会根据这个文件自动生成java文件。以这个为例,生成的java文件路径为:out/target/common/obj/JAVA_LIBRARIES/services_intermediates/src/com/android/server/EventLogTags.java


  1 /* This file is auto-generated.  DO NOT MODIFY.
  2  * Source file: frameworks/base/services/java/com/android/server/EventLogTags.logtags
  3  */
  4 
  5 package com.android.server;
  6 
  7 /**
  8  * @hide
  9  */
 10 public class EventLogTags {
 11   private EventLogTags() { }  // don't instantiate
 12 
 13   /** 2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1) */
 14   public static final int BATTERY_LEVEL = 2722;
 15 
 16   /** 2723 battery_status (status|1|5),(health|1|5),(present|1|5),(plugged|1|5),(technology|3) */
 17   public static final int BATTERY_STATUS = 2723;
 18 
 19   /** 2730 battery_discharge (duration|2|3),(minLevel|1|6),(maxLevel|1|6) */
 20   public static final int BATTERY_DISCHARGE = 2730;
···
211   public static void writeBatteryLevel(int level, int voltage, int temperature) {
212     android.util.EventLog.writeEvent(BATTERY_LEVEL, level, voltage, temperature);
213   }
214 
215   public static void writeBatteryStatus(int status, int health, int present, int plugged, String technology) {
216     android.util.EventLog.writeEvent(BATTERY_STATUS, status, health, present, plugged, technology);
217   }
218 
219   public static void writeBatteryDischarge(long duration, int minlevel, int maxlevel) {
220     android.util.EventLog.writeEvent(BATTERY_DISCHARGE, duration, minlevel, maxlevel);
221   }
···
}

从生成的文件看,还自动生成了函数。那么这些函数的参数是如何生成的呢。
细心观察可以发现,在EventLogTags.logtags里面定义了很多参数,例如:
2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)
会根据2722 battery_level生成宏定义/函数名。函数的参数就是根据(level|1|6),(voltage|1|1),(temperature|1|1)生成的。
下面看一下这个是如何定义的

1 # The entries in this file map a sparse set of log tag numbers to tag names.
  2 # This is installed on the device, in /system/etc, and parsed by logcat.
  3 #
  4 # Tag numbers are decimal integers, from 0 to 2^31.  (Let's leave the
  5 # negative values alone for now.)
  6 # Tag numbers是十进制的整数,取值从0到2^31
  7 # Tag names are one or more ASCII letters and numbers or underscores, i.e.
  8 # "[A-Z][a-z][0-9]_".  Do not include spaces or punctuation (the former
  9 # impacts log readability, the latter makes regex searches more annoying).
10 # Tag names由1到多个ASCII码的字母和下划线组成,为了方便在log中搜索,name中避免使用空格和标点
11 # Tag numbers and names are separated by whitespace.  Blank lines and lines
12 # starting with '#' are ignored.
13 # Tag numbers和names之间用空格隔开,空白和#开头的行会被忽略
14 # Optionally, after the tag names can be put a description for the value(s)
15 # of the tag. Description are in the format
16 #    (<name>|data type[|data unit]) 
    #  根据需要,tag names后面可以加上这个tag的values来描述这个tag的打印格式。
17 # Multiple values are separated by commas.
18 # values之间用逗号隔开,每个value的格式如下
19 # The data type is a number from the following values:
20 # 1: int
21 # 2: long
22 # 3: string
23 # 4: list
24 # "data unit"表示数据格式,相当于data的单位:
25 # The data unit is a number taken from the following list:
26 # 1: Number of objects
27 # 2: Number of bytes
28 # 3: Number of milliseconds
29 # 4: Number of allocations
30 # 5: Id
31 # 6: Percent
32 # Default value for data of type int/long is 2 (bytes).
33 #
34 # TODO: generate ".java" and ".h" files with integer constants from this file.
         ... ...
137 # NOTE - the range 1000000-2000000 is reserved for partners and others who
138 # want to define their own log tags without conflicting with the core platform.
# 1000000-2000000的tag number是留给合作厂商或者其他开发者用户扩展用的。

根据注释就能自动生成函数的参数了。

完结~~~

 类似资料: