Android Logの原理分析

82473 ワード

android開発の過程で、log情報を表示して分析を助ける必要があります.ではlogの原理を知ることが重要です.
Frameworkのログ
Frameworkのログは比較的簡単で、主にインタフェースをカプセル化し、インタフェースでprintln_を呼び出す.native関数.以下、そのうちの1つだけを分析する.
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のログ
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;

上に対応してそのレベルの印刷情報を入力します.次の対応でどのロゴを印刷しますか.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関数を呼び出します.
ログ駆動分析
ファイルパス: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を分析するのは比較的簡単です.ログを通過する.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   }
···
}

生成されたファイルから見ると、関数も自動的に生成されます.では、これらの関数のパラメータはどのように生成されますか.よく観察すると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                    。

コメントに基づいて関数のパラメータを自動的に生成できます.
終わり~~~