xCrashAnr

总结

API level < 21

new FileObserver("/data/anr/", CLOSE_WRITE)

API level >= 21

SIGQUIT

ELFFormat

图解

sequenceDiagram

SystemServer->>xcc_signal_trace_register:SIGQUIT

xcc_signal_trace_register->>xcc_signal_trace_register:xc_trace_handler

xcc_signal_trace_register->>xc_trace_dumper:write(xc_trace_notifier, &data, sizeof(data)

Note over xcc_signal_trace_register,xc_trace_dumper:和xc_trace_dumper线程通过阻塞式io通信


xc_trace_dumper->>xc_trace_dumper: xc_trace_load_symbols
activate xc_trace_dumper

xc_trace_dumper->>xc_trace_dumper: xc_dl_create

Note right of xc_trace_dumper: xc_dl_find_map_start,/proc/self/maps中找到指定so,设置self->map_start
Note right of xc_trace_dumper: xc_dl_file_open(open and mmap so)
Note right of xc_trace_dumper: xc_dl_parse_elf

xc_trace_dumper->>xc_trace_dumper: xc_dl_sym

Note right of xc_trace_dumper: read each .symtab/.dynsym, 需要str_offset作为index指向的str等于当前目标str,则找到Sym
Note right of xc_trace_dumper: (void *)(self->map_start + sym->st_value - self->load_bias);

deactivate xc_trace_dumper

xc_trace_dumper->>xc_trace_dumper: xc_trace_libart_runtime_dump
activate xc_trace_dumper
deactivate xc_trace_dumper
Note right of xc_trace_dumper: 转调动态链接的系统so方法处理
Note right of xc_trace_dumper: Runtime::DumpForSigQuit(std::ostream& os),dump本进程anr信息

xc_trace_init

//xc_trace.c
int xc_trace_init(JNIEnv *env,
                  int rethrow,
                  unsigned int logcat_system_lines,
                  unsigned int logcat_events_lines,
                  unsigned int logcat_main_lines,
                  int dump_fds,
                  int dump_network_info)
{
    //capture SIGQUIT only for ART
    if(xc_common_api_level < 21) return 0;

    //is Android Lollipop (5.x)?
    xc_trace_is_lollipop = ((21 == xc_common_api_level || 22 == xc_common_api_level) ? 1 : 0);

    //init for JNI callback
    xc_trace_init_callback(env);

    //create event FD
    if(0 > (xc_trace_notifier = eventfd(0, EFD_CLOEXEC))) return XCC_ERRNO_SYS;

    //register signal handler
    if(0 != (r = xcc_signal_trace_register(xc_trace_handler))) goto err2;//main

    //create thread for dump trace
    if(0 != (r = pthread_create(&thd, NULL, xc_trace_dumper, NULL))) goto err1;//main

    return 0;

xcc_signal_trace_register

//注册SIGQUIT信号处理函数
int xcc_signal_trace_register(void (*handler)(int, siginfo_t *, void *))
{
}

xc_trace_handler

static void xc_trace_handler(int sig, siginfo_t *si, void *uc)
{
    uint64_t data;

    if(xc_trace_notifier >= 0)
    {
        data = 1;
        XCC_UTIL_TEMP_FAILURE_RETRY(write(xc_trace_notifier, &data, sizeof(data)));//和xc_trace_dumper线程通过阻塞式io通信
    }
}

xc_trace_dumper

static void *xc_trace_dumper(void *arg)
{

    while(1)
    {
        //block here, waiting for sigquit
        XCC_UTIL_TEMP_FAILURE_RETRY(read(xc_trace_notifier, &data, sizeof(data)));

......
        //write header info
        if(0 != xc_trace_write_header(fd, trace_time)) goto end;

xc_trace_load_symbols()//main
xc_trace_check_address_valid()

dup2(fd, STDERR_FILENO) < 0//接收下面的stderr输出

xc_trace_libart_runtime_dump(*xc_trace_libart_runtime_instance, xc_trace_libcpp_cerr);//转调动态链接的系统so方法处理,是系统在data/anr中trace.txt文件中当前pid的部分,main

  skip:
        if(0 != xcc_util_write_str(fd, "\n"XCC_UTIL_THREAD_END"\n")) goto end;

        //write other info
        if(0 != xcc_util_record_logcat(fd, xc_common_process_id, xc_common_api_level, xc_trace_logcat_system_lines, xc_trace_logcat_events_lines, xc_trace_logcat_main_lines)) goto end;
        if(xc_trace_dump_fds)
            if(0 != xcc_util_record_fds(fd, xc_common_process_id)) goto end;
        if(xc_trace_dump_network_info)
            if(0 != xcc_util_record_network_info(fd, xc_common_process_id, xc_common_api_level)) goto end;
        if(0 != xcc_meminfo_record(fd, xc_common_process_id)) goto end;

    end:
        //close log file
        xc_common_close_trace_log(fd);

        //rethrow SIGQUIT to ART Signal Catcher
        if(xc_trace_rethrow) xc_trace_send_sigquit();

        //JNI callback
        //Do we need to implement an emergency buffer for disk exhausted?
        if(NULL == xc_trace_cb_method) continue;
        if(NULL == (j_pathname = (*env)->NewStringUTF(env, pathname))) continue;
        (*env)->CallStaticVoidMethod(env, xc_common_cb_class, xc_trace_cb_method, j_pathname, NULL);
        XC_JNI_IGNORE_PENDING_EXCEPTION();
        (*env)->DeleteLocalRef(env, j_pathname);

xc_trace_load_symbols

static int xc_trace_load_symbols()
{//动态链接系统so执行任务
    if(xc_common_api_level >= 29) libcpp = xc_dl_create(XCC_UTIL_LIBCPP_APEX);
    if(NULL == libcpp && NULL == (libcpp = xc_dl_create(XCC_UTIL_LIBCPP))) goto end;
    if(NULL == (xc_trace_libcpp_cerr = xc_dl_sym(libcpp, XCC_UTIL_LIBCPP_CERR))) goto end;//main

    if(xc_common_api_level >= 29) libart = xc_dl_create(XCC_UTIL_LIBART_APEX);
    if(NULL == libart && NULL == (libart = xc_dl_create(XCC_UTIL_LIBART))) goto end;
    if(NULL == (xc_trace_libart_runtime_instance = (void **)xc_dl_sym(libart, XCC_UTIL_LIBART_RUNTIME_INSTANCE))) goto end;
    if(NULL == (xc_trace_libart_runtime_dump = (xcc_util_libart_runtime_dump_t)xc_dl_sym(libart, XCC_UTIL_LIBART_RUNTIME_DUMP))) goto end;//main

xc_dl_create

xc_dl_t *xc_dl_create(const char *pathname)
{
    xc_dl_t *self;

    if(NULL == (self = calloc(1, sizeof(xc_dl_t)))) return NULL;
    self->fd = -1;
    self->data = MAP_FAILED;
    TAILQ_INIT(&(self->symbolsq));

    //main
    if(0 != xc_dl_find_map_start(self, pathname)) goto err;//确保/proc/self/maps中包含指定的so,设置self->map_start
    if(0 != xc_dl_file_open(self, pathname)) goto err;//mmap so
    if(0 != xc_dl_parse_elf(self)) goto err;
    
    return self;

 err:
    xc_dl_destroy(&self);
    return NULL;
}
xc_dl_find_map_start
static int xc_dl_find_map_start(xc_dl_t *self, const char *pathname)
{
    FILE      *f = NULL;
    char       line[512];
    uintptr_t  offset;
    int        pos;
    char      *p;
    int        r = XCC_ERRNO_NOTFND;

    if(NULL == (f = fopen("/proc/self/maps", "r"))) return XCC_ERRNO_SYS;
    while(fgets(line, sizeof(line), f))
    {
        //main
        if(2 != sscanf(line, "%"SCNxPTR"-%*"SCNxPTR" %*4s %"SCNxPTR" %*x:%*x %*d%n", &(self->map_start), &offset, &pos)) continue;
        if(0 != offset) continue;
        p = xcc_util_trim(line + pos);
        if(0 != strcmp(p, pathname)) continue;

        r = 0; //found
        break;
    }

    fclose(f);
    return r;
}
xc_dl_file_open(open and mmap so)
static int xc_dl_file_open(xc_dl_t *self, const char *pathname)
{
    //open file,main
    if(0 > (self->fd = XCC_UTIL_TEMP_FAILURE_RETRY(open(pathname, O_RDONLY | O_CLOEXEC)))) return XCC_ERRNO_SYS;
    //get file size
    //mmap the file,main
    if(MAP_FAILED == (self->data = (uint8_t *)mmap(NULL, self->size, PROT_READ, MAP_PRIVATE, self->fd, 0))) return XCC_ERRNO_SYS;
    return 0;
}
xc_dl_parse_elf
static int xc_dl_parse_elf(xc_dl_t *self)
{
    ElfW(Ehdr)      *ehdr;
    ElfW(Phdr)      *phdr;
    ElfW(Shdr)      *shdr, *str_shdr;
    xc_dl_symbols_t *symbols;
    size_t           i, cnt = 0;
    
    //get ELF header
    if(NULL == (ehdr = xc_dl_file_get(self, 0, sizeof(ElfW(Ehdr))))) return XCC_ERRNO_FORMAT;

    //find load_bias in program headers
    for(i = 0; i < ehdr->e_phnum * ehdr->e_phentsize; i += ehdr->e_phentsize)
    {
        if(NULL == (phdr = xc_dl_file_get(self, ehdr->e_phoff + i, sizeof(ElfW(Phdr))))) return XCC_ERRNO_FORMAT;

        //PT_LOAD     The array element specifies a loadable segment
        //p_offset: This member holds the offset from the beginning of the file at which the first byte of the segment resides
        if((PT_LOAD == phdr->p_type) && (phdr->p_flags & PF_X) && (0 == phdr->p_offset))//main
        {
            //p_vaddr: This member holds the virtual address at which the first byte of the segment resides in memory.
            self->load_bias = phdr->p_vaddr;
            break;
        }
    }

    //find symbol tables in section headers
    for(i = ehdr->e_shentsize; i < ehdr->e_shnum * ehdr->e_shentsize; i += ehdr->e_shentsize)
    {
        if(NULL == (shdr = xc_dl_file_get(self, ehdr->e_shoff + i, sizeof(ElfW(Shdr))))) return XCC_ERRNO_FORMAT;

        //SHT_SYMTAB     This section holds a symbol table.
        //SHT_DYNSYM     This section holds a minimal set of dynamic linking symbols.
        if(SHT_SYMTAB == shdr->sh_type || SHT_DYNSYM == shdr->sh_type)//main
        {
            //sh_link   This member holds a section header table index link
            //e_shnum   This member holds the number of entries in the section header table.
            if(shdr->sh_link >= ehdr->e_shnum) continue;
            //e_shoff   This member holds the section header table's file offset in bytes
            //e_shentsize This member holds a sections header's size in bytes.  A section header is one entry in the section header table;  all entries are the same size.
            //  sh_type   This member categorizes the section's contents and semantics.
            //  SHT_STRTAB     This section holds a string table.  An  object file may have multiple string table  sections.
            if(NULL == (str_shdr = xc_dl_file_get(self, ehdr->e_shoff + shdr->sh_link * ehdr->e_shentsize, sizeof(ElfW(Shdr))))) return XCC_ERRNO_FORMAT;
            if(SHT_STRTAB != str_shdr->sh_type) continue;//main
            
            if(NULL == (symbols = malloc(sizeof(xc_dl_symbols_t)))) return XCC_ERRNO_NOMEM;
            //sh_offset This member's value holds the byte offset from the beginning of the file to the first byte in the section
            symbols->sym_offset = shdr->sh_offset;
            //sh_size   This member holds the section's size in bytes.
            symbols->sym_end = shdr->sh_offset + shdr->sh_size;
           //sh_entsize Some sections hold a table of fixed-sized entries, such as a symbol table.  
           //For such a section, this member gives the size in bytes for each entry.
            symbols->sym_entry_size = shdr->sh_entsize;
            symbols->str_offset = str_shdr->sh_offset;
            symbols->str_end = str_shdr->sh_offset + str_shdr->sh_size;
            TAILQ_INSERT_TAIL(&(self->symbolsq), symbols, link);//main
            cnt++;
        }
    }
    if(0 == cnt) return XCC_ERRNO_FORMAT;
    return 0;
}

xc_dl_sym

void *xc_dl_sym(xc_dl_t *self, const char *symbol)
{
    xc_dl_symbols_t *symbols;
    ElfW(Sym)       *sym;//main
    size_t           offset, str_offset;
    char            *str;

    TAILQ_FOREACH(symbols, &(self->symbolsq), link)//main
    {
        for(offset = symbols->sym_offset; offset < symbols->sym_end; offset += symbols->sym_entry_size)
        {
            //read .symtab / .dynsym
            if(NULL == (sym = xc_dl_file_get(self, offset, sizeof(ElfW(Sym))))) break;//main
            // st_shndx  Every symbol table entry is "defined" in relation to somesection.  
            // This member holds the relevant section header table index.
            if(SHN_UNDEF == sym->st_shndx) continue;

            //read .strtab / .dynstr
            //st_name   This member holds an index into the object file's symbol string table, 
            //which holds character representations of the symbol names.  
            //If the value is nonzero, it represents a string table index that gives the symbol name. 
            //Otherwise,  the symbol has no name.
            str_offset = symbols->str_offset + sym->st_name;//main
            if(str_offset >= symbols->str_end) continue;
            if(NULL == (str = xc_dl_file_get_string(self, str_offset))) continue;

            //compare symbol name
            if(0 != strcmp(symbol, str)) continue;

            //found
            //st_value  This member gives the value of the associated symbol.是地址
            return (void *)(self->map_start + sym->st_value - self->load_bias);//main
        }
    }
    return NULL;
}

其他

XCC_UTIL_LIBART_RUNTIME_DUMP:DumpForSigQuit

xcc_util_libart_runtime_dump_t

//对应libart.so中art/runtime/runtime.cc的方法Runtime::DumpForSigQuit(std::ostream& os),dump本进程的anr信息
#define XCC_UTIL_LIBART_RUNTIME_DUMP     "_ZN3art7Runtime14DumpForSigQuitERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE" 

//symbol address in libc++.so and libart.so
static void                            *xc_trace_libcpp_cerr = NULL;
typedef void  (*xcc_util_libart_runtime_dump_t)(void *runtime, void *ostream);