(Androidシステム)android logメカニズムの概要
13147 ワード
androidの下でdebug、最も主要な方法はlogcatでlogを捕まえて、私達はprintfを使って印刷することを試みたことがあるかもしれなくて、もちろん結果はだめで、ここで時間があってandroidプラットフォームの下でlogのflowを見て、ここでメモを取って記録します
私達は普通ALOGDを使ってlogを印刷して、だからここでALOGDのflowについて
system/core/include/log/log.h
ここで発見されたALOGDは実は一連のマクロであり、最終的には
__android_log_print(prio,tag,fmt)、この関数には3つのパラメータがあります.
1、優先度、これは私たちが普段言っているlog levelで、ALOGDのlog levelはLOG_です.DEBUG;
2、tag、これが私たちがcodeで指定したLOGです.TAG
3,__VA_ARGS__,これが一般的なフォーマット入力です.つまり、ALOGDバンドのパラメータはフォーマットされたフォーマットを使用することができます.
についてandroid_log_printの具体的な実現は以下の通りである.
system/core/liblog/logd_write.c
についてandroid_log_writeの具体的な実現は以下の通りである.
iovecはio vetorであり、readvとwritevに関連するstructであり、readvとreadvは高度なI/Oであり、ここでは深く説明しない.
呼び出し元から渡されたパラメータがiovecに固定されたフォーマットで埋め込まれていることを知っておく必要があります.最後にwrite_を呼び出します.to_log(log_id, vec, 3);
ここでwriteを見てみましょうto_ロゴの具体的な実装:
でもwrite_to_logはinitの時に__に割り当てられますwrite_to_log_kernel
次に_を見てみましょうwrite_to_log_kernelの具体的な実装
1,log_fd、つまり私たちのALOGDが生成したlogに書き込む必要があるfdです.ここでは/dev/log/main、/dev/log/radio/、dev/log/event、/dev/log/systemなどのファイルです.
2,vec,priority,tag,msgなどの情報を含むiovec,readvやwirtev専用struct
3:nr,ここではvec構造体配列の個数であるべきで,ここでは3個でpriority,tag,msgがそれぞれ充填されている
次にlogcatのflowを見てみましょう.なぜ端末がlogcatを入力すると、log情報が端末に印刷されるのでしょうか.
まず、printfが端末に情報を印刷できない理由を説明する必要があります.
system/core/init/init.c
もともとinitプロセスは起動時に0,1,2など3つのfdを/dev/nullに指向していたので、printfがstdout(つまりfd 1)に出力して/dev/nullに捨ててしまったので、情報は得られませんでした
Androidの公式解釈は以下の通りです(system/core/init/readme.txt):
By default, programs executed by init will drop stdout and stderr into
/dev/null. To help with debugging, you can execute your program via the
Andoird program logwrapper. This will redirect stdout/stderr into the
Android logging system (accessed via logcat).
system/core/logcat/logcat.cpp
まずlogcatのmain関数から歩くと、まず重要な場所に遭遇します.
次にoutputのパスを設定します.ここで重要なのは、logの出力に直接関連し、stdOUT_に出力できることです.FILENOは、システムが指定したパラメータであってもよく、これは-fが指定したものであり、-fの役割についてはhelpドキュメントで確認してもよい
最後に重要な点は、selectのメカニズムで/dev/log/mainと/dev/log/systemの2つのパスにlog情報が印刷できるかどうかをクエリーすることです.
あれば端末に印刷し、なければsleep 5 ms、ここの時間はselect関数の最後のパラメータで指定します
result>0の場合、つまりselectが/dev/log/mainと/dev/log/systemの2つのパスのいずれかがreadableであると判断した場合、次の関数を実行することがわかります.
ここでは/dev/log/mainまたは/dev/log/systemで読み取った情報(つまり前にALOGDで格納した情報)をg_に印刷しますoutFD対応のデバイスでは、デフォルトでは端末
ここまでlogcatのプロセス全体が終わりましたので、皆さんの役に立つことを願っています.
私達は普通ALOGDを使ってlogを印刷して、だからここでALOGDのflowについて
system/core/include/log/log.h
system/core/include/log/log.h
#ifndef ALOGD
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif
#ifndef ALOG
#define ALOG(priority, tag, ...) \
LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif
#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)
ここで発見されたALOGDは実は一連のマクロであり、最終的には
__android_log_print(prio,tag,fmt)、この関数には3つのパラメータがあります.
1、優先度、これは私たちが普段言っているlog levelで、ALOGDのlog levelはLOG_です.DEBUG;
2、tag、これが私たちがcodeで指定したLOGです.TAG
3,__VA_ARGS__,これが一般的なフォーマット入力です.つまり、ALOGDバンドのパラメータはフォーマットされたフォーマットを使用することができます.
についてandroid_log_printの具体的な実現は以下の通りである.
system/core/liblog/logd_write.c
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);
}
ここでは、フォーマットされた入力がbufの文字配列に変換され、bufが3番目のパラメータとして呼び出される_android_log_write についてandroid_log_writeの具体的な実現は以下の通りである.
int __android_log_write(int prio, const char *tag, const char *msg)
{
struct iovec vec[3];
log_id_t log_id = LOG_ID_MAIN;
char tmp_tag[32];
if (!tag)
tag = "";
/* XXX: This needs to go! */
if (!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")) {
log_id = 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(log_id, vec, 3);
}
priority,tag,msgの3つのパラメータがiovecのstructにそれぞれ埋め込まれていることがわかります.iovecはio vetorであり、readvとwritevに関連するstructであり、readvとreadvは高度なI/Oであり、ここでは深く説明しない.
呼び出し元から渡されたパラメータがiovecに固定されたフォーマットで埋め込まれていることを知っておく必要があります.最後にwrite_を呼び出します.to_log(log_id, vec, 3);
ここでwriteを見てみましょうto_ロゴの具体的な実装:
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
write_を見つけることができますto_logは関数ポインタで、__に初期化されます.write_to_log_init でもwrite_to_logはinitの時に__に割り当てられますwrite_to_log_kernel
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;
次に_を見てみましょう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;
}
は、log_が呼び出されたことを示します.writev(log_fd,vec,nr)、ここで3つのパラメータはそれぞれ:1,log_fd、つまり私たちのALOGDが生成したlogに書き込む必要があるfdです.ここでは/dev/log/main、/dev/log/radio/、dev/log/event、/dev/log/systemなどのファイルです.
2,vec,priority,tag,msgなどの情報を含むiovec,readvやwirtev専用struct
3:nr,ここではvec構造体配列の個数であるべきで,ここでは3個でpriority,tag,msgがそれぞれ充填されている
#if FAKE_LOG_DEVICE
// This will be defined when building for the host.
#define log_open(pathname, flags) fakeLogOpen(pathname, flags)
#define log_writev(filedes, vector, count) fakeLogWritev(filedes, vector, count)
#define log_close(filedes) fakeLogClose(filedes)
#else
#define log_open(pathname, flags) open(pathname, (flags) | O_CLOEXEC)
#define log_writev(filedes, vector, count) writev(filedes, vector, count)
#define log_close(filedes) close(filedes)
#endif
見たでしょう.ここではwritevでvecに埋め込まれた内容を固定形式でlog_に書きます.fdに対応するパスが作成され、これでALOGDで印刷するmsgが/dev/log/mainなどに書き込まれます次にlogcatのflowを見てみましょう.なぜ端末がlogcatを入力すると、log情報が端末に印刷されるのでしょうか.
まず、printfが端末に情報を印刷できない理由を説明する必要があります.
system/core/init/init.c
static void open_console()
{
int fd;
if ((fd = open(console_name, O_RDWR)) < 0) {
fd = open("/dev/null", O_RDWR);
}
ioctl(fd, TIOCSCTTY, 0);
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
もともとinitプロセスは起動時に0,1,2など3つのfdを/dev/nullに指向していたので、printfがstdout(つまりfd 1)に出力して/dev/nullに捨ててしまったので、情報は得られませんでした
Androidの公式解釈は以下の通りです(system/core/init/readme.txt):
By default, programs executed by init will drop stdout and stderr into
/dev/null. To help with debugging, you can execute your program via the
Andoird program logwrapper. This will redirect stdout/stderr into the
Android logging system (accessed via logcat).
system/core/logcat/logcat.cpp
まずlogcatのmain関数から歩くと、まず重要な場所に遭遇します.
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++;
}
}
ここではlogcatが/dev/log/mainと/dev/log/systemの2つのパスをdeviceとして使用していることがわかりました.ここでg_devCountの値は2です次にoutputのパスを設定します.ここで重要なのは、logの出力に直接関連し、stdOUT_に出力できることです.FILENOは、システムが指定したパラメータであってもよく、これは-fが指定したものであり、-fの役割についてはhelpドキュメントで確認してもよい
static void setupOutput()
{
if (g_outputFileName == NULL) {
g_outFD = STDOUT_FILENO;
} else {
struct stat statbuf;
g_outFD = openLogFile (g_outputFileName);
if (g_outFD < 0) {
perror ("couldn't open output file");
exit(-1);
}
fstat(g_outFD, &statbuf);
g_outByteCount = statbuf.st_size;
}
}
一般的にlogcatを直接使用する場合、g_outFDに対応する出力装置が私たちの端末です.これはlogcatがlogを端末に印刷できる理由です.最後に重要な点は、selectのメカニズムで/dev/log/mainと/dev/log/systemの2つのパスにlog情報が印刷できるかどうかをクエリーすることです.
あれば端末に印刷し、なければsleep 5 ms、ここの時間はselect関数の最後のパラメータで指定します
static void readLogLines(log_device_t* devices)
{
log_device_t* dev;
int max = 0;
int ret;
int queued_lines = 0;
bool sleep = false;
int result;
fd_set readset;
for (dev=devices; dev; dev = dev->next) {
if (dev->fd > max) {
max = dev->fd;
}
}
while (1) {
do {
timeval timeout = { 0, 5000 /* 5ms */ }; // If we oversleep it's ok, i.e. ignore EINTR.
FD_ZERO(&readset);
for (dev=devices; dev; dev = dev->next) {
FD_SET(dev->fd, &readset);
}
result = select(max + 1, &readset, NULL, NULL, sleep ? NULL : &timeout);
} while (result == -1 && errno == EINTR);
if (result >= 0) {
for (dev=devices; dev; dev = dev->next) {
if (FD_ISSET(dev->fd, &readset)) {
queued_entry_t* entry = new queued_entry_t();
/* NOTE: driver guarantees we read exactly one full entry */
ret = read(dev->fd, entry->buf, LOGGER_ENTRY_MAX_LEN);
if (ret < 0) {
if (errno == EINTR) {
delete entry;
goto next;
}
if (errno == EAGAIN) {
delete entry;
break;
}
perror("logcat read");
exit(EXIT_FAILURE);
}
else if (!ret) {
fprintf(stderr, "read: Unexpected EOF!
");
exit(EXIT_FAILURE);
}
else if (entry->entry.len != ret - sizeof(struct logger_entry)) {
fprintf(stderr, "read: unexpected length. Expected %d, got %d
",
entry->entry.len, ret - sizeof(struct logger_entry));
exit(EXIT_FAILURE);
entry->entry.msg[entry->entry.len] = '\0';
dev->enqueue(entry);
++queued_lines;
}
}
if (result == 0) {
// we did our short timeout trick and there's nothing new
// print everything we have and wait for more data
sleep = true;
while (true) {
chooseFirst(devices, &dev);
if (dev == NULL) {
break;
}
if (g_tail_lines == 0 || queued_lines <= g_tail_lines) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
// the caller requested to just dump the log and exit
if (g_nonblock) {
return;
}
} else {
// print all that aren't the last in their list
sleep = false;
while (g_tail_lines == 0 || queued_lines > g_tail_lines) {
chooseFirst(devices, &dev);
if (dev == NULL || dev->queue->next == NULL) {
break;
}
if (g_tail_lines == 0) {
printNextEntry(dev);
} else {
skipNextEntry(dev);
}
--queued_lines;
}
}
}
next:
;
}
}
result>0の場合、つまりselectが/dev/log/mainと/dev/log/systemの2つのパスのいずれかがreadableであると判断した場合、次の関数を実行することがわかります.
static void printNextEntry(log_device_t* dev) {
maybePrintStart(dev);
if (g_printBinary) {
printBinary(&dev->queue->entry);
} else {
processBuffer(dev, &dev->queue->entry);
}
skipNextEntry(dev);
}
static void skipNextEntry(log_device_t* dev) {
maybePrintStart(dev);
queued_entry_t* entry = dev->queue;
dev->queue = entry->next;
delete entry;
}
ここでは/dev/log/mainまたは/dev/log/systemで読み取った情報(つまり前にALOGDで格納した情報)をg_に印刷しますoutFD対応のデバイスでは、デフォルトでは端末
static void maybePrintStart(log_device_t* dev) {
if (!dev->printed) {
dev->printed = true;
if (g_devCount > 1 && !g_printBinary) {
char buf[1024];
snprintf(buf, sizeof(buf), "--------- beginning of %s
", dev->device);
if (write(g_outFD, buf, strlen(buf)) < 0) {
perror("output error");
exit(-1);
}
}
}
}
ここまでlogcatのプロセス全体が終わりましたので、皆さんの役に立つことを願っています.