/* * erase_info.c * * Created by: Mahesh J Salgaonkar * * Copyright (C) 2011 IBM Corporation * Copyright (C) 2011 NEC Corporation * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include "makedumpfile.h" #include "print_info.h" #include "dwarf_info.h" #include "erase_info.h" #include struct erase_info *erase_info = NULL; unsigned long num_erase_info = 1; /* Node 0 is unused. */ struct call_back eppic_cb = { &get_domain_all, &readmem, &get_die_attr_type, &get_die_name, &get_die_offset, &get_die_length, &get_die_member_all, &get_die_nfields_all, &get_symbol_addr_all, &update_filter_info_raw }; /* * flags for config_entry.flag */ #define FILTER_ENTRY 0x0001 #define SIZE_ENTRY 0x0002 #define ITERATION_ENTRY 0x0004 #define LIST_ENTRY 0x0008 #define SYMBOL_ENTRY 0x0010 #define VAR_ENTRY 0x0020 #define TRAVERSAL_ENTRY 0x0040 #define ENTRY_RESOLVED 0x8000 /* * flags for get_config() */ #define CONFIG_SKIP_SECTION 0x01 #define CONFIG_NEW_CMD 0x02 #define IS_KEYWORD(tkn) \ (!strcmp(tkn, "erase") || !strcmp(tkn, "size") || \ !strcmp(tkn, "nullify") || !strcmp(tkn, "for") || \ !strcmp(tkn, "in") || !strcmp(tkn, "within") || \ !strcmp(tkn, "endfor")) struct module_sym_table { unsigned int num_modules; unsigned int current_mod; struct module_info *modules; }; /* * Filtering physical address range. */ struct filter_info { unsigned long long vaddr; /* symbol address for debugging */ unsigned long long paddr; long size; /* direct access to update erase information node */ int erase_info_idx; /* 0= invalid index */ int size_idx; int erase_ch; struct filter_info *next; unsigned short nullify; }; /* * Filter config information */ struct filter_config { char *name_filterconfig; FILE *file_filterconfig; char *cur_module; char *saved_token; char *token; int new_section; int line_count; }; struct config_entry { char *name; char *type_name; char *symbol_expr; /* original symbol expression */ unsigned short flag; unsigned short nullify; unsigned long long sym_addr; /* Symbol address */ unsigned long vaddr; /* Symbol address or value pointed by sym_addr */ unsigned long long cmp_addr; /* for LIST_ENTRY */ unsigned long offset; unsigned long type_flag; long array_length; long index; long size; int line; /* Line number in config file. */ int erase_info_idx; /* 0= invalid index */ struct config_entry *refer_to; struct config_entry *next; }; struct config { char *module_name; struct config_entry *iter_entry; struct config_entry *list_entry; int num_filter_symbols; struct config_entry **filter_symbol; struct config_entry **size_symbol; }; static struct module_sym_table mod_st = { 0 }; static struct filter_info *filter_info = NULL; static struct filter_config filter_config; static char config_buf[BUFSIZE_FGETS]; /* * Internal functions. */ static struct module_info * get_loaded_module(char *mod_name) { unsigned int i; struct module_info *modules; modules = mod_st.modules; if (strcmp(mod_name, modules[mod_st.current_mod].name)) { for (i = 0; i < mod_st.num_modules; i++) { if (!strcmp(mod_name, modules[i].name)) break; } if (i == mod_st.num_modules) return NULL; /* set the current_mod for fast lookup next time */ mod_st.current_mod = i; } return &modules[mod_st.current_mod]; } static unsigned long long find_module_symbol(struct module_info *module_ptr, char *symname) { int i; struct symbol_info *sym_info; sym_info = module_ptr->sym_info; if (!sym_info) return FALSE; for (i = 1; i < module_ptr->num_syms; i++) { if (sym_info[i].name && !strcmp(sym_info[i].name, symname)) return sym_info[i].value; } return NOT_FOUND_SYMBOL; } static int sym_in_module(char *symname, unsigned long long *symbol_addr) { char *module_name; struct module_info *module_ptr; module_name = get_dwarf_module_name(); if (!mod_st.num_modules || !strcmp(module_name, "vmlinux") || !strcmp(module_name, "xen-syms")) return FALSE; module_ptr = get_loaded_module(module_name); if (!module_ptr) return FALSE; *symbol_addr = find_module_symbol(module_ptr, symname); if (*symbol_addr == NOT_FOUND_SYMBOL) return FALSE; else return TRUE; } static unsigned int get_num_modules(unsigned long head, unsigned int *num) { unsigned long cur; unsigned int num_modules = 0; if (!num) return FALSE; if (!readmem(VADDR, head + OFFSET(list_head.next), &cur, sizeof cur)) { ERRMSG("Can't get next list_head.\n"); return FALSE; } while (cur != head) { num_modules++; if (!readmem(VADDR, cur + OFFSET(list_head.next), &cur, sizeof cur)) { ERRMSG("Can't get next list_head.\n"); return FALSE; } } *num = num_modules; return TRUE; } static void free_symbol_info(struct module_info *module) { int i; if (module->num_syms == 0) return; for (i = 1; i < module->num_syms; i++) if (module->sym_info[i].name) free(module->sym_info[i].name); free(module->sym_info); } static void clean_module_symbols(void) { int i; for (i = 0; i < mod_st.num_modules; i++) free_symbol_info(&mod_st.modules[i]); if (mod_st.num_modules) { free(mod_st.modules); mod_st.modules = NULL; mod_st.num_modules = 0; } } static int __load_module_symbol(struct module_info *modules, unsigned long addr_module) { int ret = FALSE; unsigned int nsym; unsigned long symtab, strtab; unsigned long mod_base, mod_init; unsigned int mod_size, mod_init_size; unsigned char *module_struct_mem = NULL; unsigned char *module_core_mem = NULL; unsigned char *module_init_mem = NULL; unsigned char *symtab_mem; char *module_name, *strtab_mem, *nameptr; unsigned int num_symtab; /* Allocate buffer to read struct module data from vmcore. */ if ((module_struct_mem = calloc(1, SIZE(module))) == NULL) { ERRMSG("Failed to allocate buffer for module\n"); return FALSE; } if (!readmem(VADDR, addr_module, module_struct_mem, SIZE(module))) { ERRMSG("Can't get module info.\n"); goto out; } module_name = (char *)(module_struct_mem + OFFSET(module.name)); if (strlen(module_name) < MOD_NAME_LEN) strcpy(modules->name, module_name); else strncpy(modules->name, module_name, MOD_NAME_LEN-1); mod_init = ULONG(module_struct_mem + OFFSET(module.module_init)); mod_init_size = UINT(module_struct_mem + OFFSET(module.init_size)); mod_base = ULONG(module_struct_mem + OFFSET(module.module_core)); mod_size = UINT(module_struct_mem + OFFSET(module.core_size)); DEBUG_MSG("Module: %s, Base: 0x%lx, Size: %u\n", module_name, mod_base, mod_size); if (mod_init_size > 0) { module_init_mem = calloc(1, mod_init_size); if (module_init_mem == NULL) { ERRMSG("Can't allocate memory for module " "init\n"); goto out; } if (!readmem(VADDR, mod_init, module_init_mem, mod_init_size)) { ERRMSG("Can't access module init in memory.\n"); goto out; } } if ((module_core_mem = calloc(1, mod_size)) == NULL) { ERRMSG("Can't allocate memory for module\n"); goto out; } if (!readmem(VADDR, mod_base, module_core_mem, mod_size)) { ERRMSG("Can't access module in memory.\n"); goto out; } num_symtab = UINT(module_struct_mem + OFFSET(module.num_symtab)); if (!num_symtab) { ERRMSG("%s: Symbol info not available\n", module_name); goto out; } modules->num_syms = num_symtab; DEBUG_MSG("num_sym: %d\n", num_symtab); symtab = ULONG(module_struct_mem + OFFSET(module.symtab)); strtab = ULONG(module_struct_mem + OFFSET(module.strtab)); /* check if symtab and strtab are inside the module space. */ if (!IN_RANGE(symtab, mod_base, mod_size) && !IN_RANGE(symtab, mod_init, mod_init_size)) { ERRMSG("%s: module symtab is outside of module " "address space\n", module_name); goto out; } if (IN_RANGE(symtab, mod_base, mod_size)) symtab_mem = module_core_mem + (symtab - mod_base); else symtab_mem = module_init_mem + (symtab - mod_init); if (!IN_RANGE(strtab, mod_base, mod_size) && !IN_RANGE(strtab, mod_init, mod_init_size)) { ERRMSG("%s: module strtab is outside of module " "address space\n", module_name); goto out; } if (IN_RANGE(strtab, mod_base, mod_size)) strtab_mem = (char *)(module_core_mem + (strtab - mod_base)); else strtab_mem = (char *)(module_init_mem + (strtab - mod_init)); modules->sym_info = calloc(num_symtab, sizeof(struct symbol_info)); if (modules->sym_info == NULL) { ERRMSG("Can't allocate memory to store sym info\n"); goto out; } /* symbols starts from 1 */ for (nsym = 1; nsym < num_symtab; nsym++) { Elf32_Sym *sym32; Elf64_Sym *sym64; /* * TODO: * If case of ELF vmcore then the word size can be * determined using flag_elf64_memory flag. * But in case of kdump-compressed dump, kdump header * does not carry word size info. May be in future * this info will be available in kdump header. * Until then, in order to make this logic work on both * situation we depend on pointer_size that is * extracted from vmlinux dwarf information. */ if ((get_pointer_size() * 8) == 64) { sym64 = (Elf64_Sym *) (symtab_mem + (nsym * sizeof(Elf64_Sym))); modules->sym_info[nsym].value = (unsigned long long) sym64->st_value; nameptr = strtab_mem + sym64->st_name; } else { sym32 = (Elf32_Sym *) (symtab_mem + (nsym * sizeof(Elf32_Sym))); modules->sym_info[nsym].value = (unsigned long long) sym32->st_value; nameptr = strtab_mem + sym32->st_name; } if (strlen(nameptr)) modules->sym_info[nsym].name = strdup(nameptr); DEBUG_MSG("\t[%d] %llx %s\n", nsym, modules->sym_info[nsym].value, nameptr); } ret = TRUE; out: free(module_struct_mem); free(module_core_mem); free(module_init_mem); return ret; } static int load_module_symbols(void) { unsigned long head, cur, cur_module; struct module_info *modules = NULL; unsigned int i = 0; head = SYMBOL(modules); if (!get_num_modules(head, &mod_st.num_modules) || !mod_st.num_modules) { ERRMSG("Can't get module count\n"); return FALSE; } mod_st.modules = calloc(mod_st.num_modules, sizeof(struct module_info)); if (!mod_st.modules) { ERRMSG("Can't allocate memory for module info\n"); return FALSE; } modules = mod_st.modules; if (!readmem(VADDR, head + OFFSET(list_head.next), &cur, sizeof cur)) { ERRMSG("Can't get next list_head.\n"); return FALSE; } /* Travese the list and read module symbols */ while (cur != head) { cur_module = cur - OFFSET(module.list); if (!__load_module_symbol(&modules[i], cur_module)) return FALSE; if (!readmem(VADDR, cur + OFFSET(list_head.next), &cur, sizeof cur)) { ERRMSG("Can't get next list_head.\n"); return FALSE; } i++; } return TRUE; } static void free_config_entry(struct config_entry *ce) { struct config_entry *p; while(ce) { p = ce; ce = p->next; if (p->name) free(p->name); if (p->type_name) free(p->type_name); if (p->symbol_expr) free(p->symbol_expr); free(p); } } static void free_config(struct config *config) { int i; if (config == NULL) return; if (config->module_name) free(config->module_name); for (i = 0; i < config->num_filter_symbols; i++) { if (config->filter_symbol[i]) free_config_entry(config->filter_symbol[i]); if (config->size_symbol[i]) free_config_entry(config->size_symbol[i]); } if (config->filter_symbol) free(config->filter_symbol); if (config->size_symbol) free(config->size_symbol); free(config); } static void print_config_entry(struct config_entry *ce) { while (ce) { DEBUG_MSG("Name: %s\n", ce->name); DEBUG_MSG("Type Name: %s, ", ce->type_name); DEBUG_MSG("flag: %x, ", ce->flag); DEBUG_MSG("Type flag: %lx, ", ce->type_flag); DEBUG_MSG("sym_addr: %llx, ", ce->sym_addr); DEBUG_MSG("vaddr: %lx, ", ce->vaddr); DEBUG_MSG("offset: %llx, ", (unsigned long long)ce->offset); DEBUG_MSG("size: %ld\n", ce->size); ce = ce->next; } } /* * Read the non-terminal's which are in the form of [.member[...]] */ static struct config_entry * create_config_entry(const char *token, unsigned short flag, int line) { struct config_entry *ce = NULL, *ptr, *prev_ce; char *str, *cur, *next; long len; int depth = 0; if (!token) return NULL; cur = str = strdup(token); prev_ce = ptr = NULL; while (cur != NULL) { if ((next = strchr(cur, '.')) != NULL) { *next++ = '\0'; } if (!strlen(cur)) { cur = next; continue; } if ((ptr = calloc(1, sizeof(struct config_entry))) == NULL) { ERRMSG("Can't allocate memory for config_entry\n"); goto err_out; } ptr->line = line; ptr->flag |= flag; if (depth == 0) { /* First node is always a symbol name */ ptr->flag |= SYMBOL_ENTRY; } if (flag & ITERATION_ENTRY) { /* Max depth for iteration entry is 1 */ if (depth > 0) { ERRMSG("Config error at %d: Invalid iteration " "variable entry.\n", line); goto err_out; } ptr->name = strdup(cur); } if (flag & (FILTER_ENTRY | LIST_ENTRY)) { ptr->name = strdup(cur); } if (flag & SIZE_ENTRY) { char ch = '\0'; int n = 0; /* See if absolute length is provided */ if ((depth == 0) && ((n = sscanf(cur, "%ld%c", &len, &ch)) > 0)) { if (len < 0) { ERRMSG("Config error at %d: size " "value must be positive.\n", line); goto err_out; } ptr->size = len; ptr->flag |= ENTRY_RESOLVED; if (n == 2) { /* Handle suffix. * K = Kilobytes * M = Megabytes */ switch (ch) { case 'M': case 'm': ptr->size *= 1024; case 'K': case 'k': ptr->size *= 1024; break; } } } else ptr->name = strdup(cur); } if (prev_ce) { prev_ce->next = ptr; prev_ce = ptr; } else ce = prev_ce = ptr; cur = next; ptr = NULL; depth++; } free(str); return ce; err_out: if (ce) free_config_entry(ce); if (ptr) free_config_entry(ptr); free(str); return NULL; } static int is_module_loaded(char *mod_name) { if (!strcmp(mod_name, "vmlinux") || get_loaded_module(mod_name)) return TRUE; return FALSE; } /* * read filter config file and return each string token. If the parameter * expected_token is non-NULL, then return the current token if it matches * with expected_token otherwise save the current token and return NULL. * At start of every module section filter_config.new_section is set to 1 and * subsequent function invocations return NULL untill filter_config.new_section * is reset to 0 by passing @flag = CONFIG_NEW_CMD (0x02). * * Parameters: * @expected_token INPUT * Token string to match with currnet token. * =NULL - return the current available token. * * @flag INPUT * =0x01 - Skip to next module section. * =0x02 - Treat the next token as next filter command and reset. * * @line OUTPUT * Line number of current token in filter config file. * * @cur_mod OUTPUT * Points to current module section name on non-NULL return value. * * @eof OUTPUT * set to -1 when end of file is reached. * set to -2 when end of section is reached. */ #define NOT_REACH_END (0) #define REACH_END_OF_FILE (-1) #define REACH_END_OF_SECTION (-2) static char * get_config_token(char *expected_token, unsigned char flag, int *line, char **cur_mod, int *eof) { char *p; struct filter_config *fc = &filter_config; int skip = flag & CONFIG_SKIP_SECTION; if (!fc->file_filterconfig) return NULL; if (eof) *eof = NOT_REACH_END; /* * set token and saved_token to NULL if skip module section is set * to 1. */ if (skip) { fc->token = NULL; fc->saved_token = NULL; } else if (fc->saved_token) { fc->token = fc->saved_token; fc->saved_token = NULL; } else if (fc->token) fc->token = strtok(NULL, " "); /* Read next line if we are done all tokens from previous line */ while (!fc->token && fgets(config_buf, sizeof(config_buf), fc->file_filterconfig)) { if ((p = strchr(config_buf, '\n'))) { *p = '\0'; fc->line_count++; } if ((p = strchr(config_buf, '#'))) { *p = '\0'; } /* replace all tabs with spaces */ for (p = config_buf; *p != '\0'; p++) if (*p == '\t') *p = ' '; if (config_buf[0] == '[') { /* module section entry */ p = strchr(config_buf, ']'); if (!p) { ERRMSG("Config error at %d: Invalid module " "section entry.\n", fc->line_count); /* skip to next valid module section */ skip = 1; } else { /* * Found the valid module section. Reset the * skip flag. */ *p = '\0'; if (fc->cur_module) free(fc->cur_module); fc->cur_module = strdup(&config_buf[1]); fc->new_section = 1; skip = 0; } continue; } /* * If symbol info for current module is not loaded then * skip to next module section. */ if (skip || (fc->cur_module && !is_module_loaded(fc->cur_module))) continue; fc->token = strtok(config_buf, " "); } if (!fc->token) { if (eof) *eof = REACH_END_OF_FILE; return NULL; } if (fc->new_section && !(flag & CONFIG_NEW_CMD)) { fc->saved_token = fc->token; if (eof) *eof = REACH_END_OF_SECTION; return NULL; } fc->new_section = 0; if (cur_mod) *cur_mod = fc->cur_module; if (line) *line = fc->line_count; if (expected_token && strcmp(fc->token, expected_token)) { fc->saved_token = fc->token; return NULL; } return fc->token; } static int read_size_entry(struct config *config, int line, int idx) { char *token = get_config_token(NULL, 0, &line, NULL, NULL); if (!token || IS_KEYWORD(token)) { ERRMSG("Config error at %d: expected size symbol after" " 'size' keyword.\n", line); return FALSE; } config->size_symbol[idx] = create_config_entry(token, SIZE_ENTRY, line); if (!config->size_symbol[idx]) { ERRMSG("Error at line %d: Failed to read size symbol\n", line); return FALSE; } if (config->iter_entry && config->size_symbol[idx]->name && (!strcmp(config->size_symbol[idx]->name, config->iter_entry->name))) { config->size_symbol[idx]->flag &= ~SYMBOL_ENTRY; config->size_symbol[idx]->flag |= VAR_ENTRY; config->size_symbol[idx]->refer_to = config->iter_entry; } return TRUE; } /* * Read erase command entry. The erase command syntax is: * * erase [.member[...]] [size [K|M]] * erase [.member[...]] [size ] * erase [.member[...]] [nullify] */ static int read_erase_cmd_entry(struct config *config, int line) { int size, idx; char *token = get_config_token(NULL, 0, &line, NULL, NULL); if (!token || IS_KEYWORD(token)) { ERRMSG("Config error at %d: expected kernel symbol after" " 'erase' command.\n", line); return FALSE; } idx = config->num_filter_symbols; config->num_filter_symbols++; size = config->num_filter_symbols * sizeof(struct config_entry *); config->filter_symbol = realloc(config->filter_symbol, size); config->size_symbol = realloc(config->size_symbol, size); if (!config->filter_symbol || !config->size_symbol) { ERRMSG("Can't get memory to read config symbols.\n"); return FALSE; } config->filter_symbol[idx] = NULL; config->size_symbol[idx] = NULL; config->filter_symbol[idx] = create_config_entry(token, FILTER_ENTRY, line); if (!config->filter_symbol[idx]) { ERRMSG("Error at line %d: Failed to read filter symbol\n", line); return FALSE; } /* * Save the symbol expression string for generation of eraseinfo data * later while writing dumpfile. */ config->filter_symbol[idx]->symbol_expr = strdup(token); if (config->iter_entry) { if (strcmp(config->filter_symbol[idx]->name, config->iter_entry->name)) { ERRMSG("Config error at %d: unused iteration" " variable '%s'.\n", line, config->iter_entry->name); return FALSE; } config->filter_symbol[idx]->flag &= ~SYMBOL_ENTRY; config->filter_symbol[idx]->flag |= VAR_ENTRY; config->filter_symbol[idx]->refer_to = config->iter_entry; } if (get_config_token("nullify", 0, &line, NULL, NULL)) { config->filter_symbol[idx]->nullify = 1; } else if (get_config_token("size", 0, &line, NULL, NULL)) { if (!read_size_entry(config, line, idx)) return FALSE; } return TRUE; } static int add_traversal_entry(struct config_entry *ce, char *member, int line) { if (!ce) return FALSE; while (ce->next) ce = ce->next; ce->next = create_config_entry(member, LIST_ENTRY, line); if (ce->next == NULL) { ERRMSG("Error at line %d: Failed to read 'via' member\n", line); return FALSE; } ce->next->flag |= TRAVERSAL_ENTRY; ce->next->flag &= ~SYMBOL_ENTRY; return TRUE; } static int read_list_entry(struct config *config, int line) { char *token = get_config_token(NULL, 0, &line, NULL, NULL); if (!token || IS_KEYWORD(token)) { ERRMSG("Config error at %d: expected list symbol after" " 'in' keyword.\n", line); return FALSE; } config->list_entry = create_config_entry(token, LIST_ENTRY, line); if (!config->list_entry) { ERRMSG("Error at line %d: Failed to read list symbol\n", line); return FALSE; } /* Check if user has provided 'via' or 'within' keyword */ if (get_config_token("via", 0, &line, NULL, NULL)) { /* next token is traversal member NextMember */ token = get_config_token(NULL, 0, &line, NULL, NULL); if (!token) { ERRMSG("Config error at %d: expected member name after" " 'via' keyword.\n", line); return FALSE; } if (!add_traversal_entry(config->list_entry, token, line)) return FALSE; } else if (get_config_token("within", 0, &line, NULL, NULL)) { char *s_name, *lh_member; /* next value is StructName:ListHeadMember */ s_name = get_config_token(NULL, 0, &line, NULL, NULL); if (!s_name || IS_KEYWORD(s_name)) { ERRMSG("Config error at %d: expected struct name after" " 'within' keyword.\n", line); return FALSE; } lh_member = strchr(s_name, ':'); if (lh_member) { *lh_member++ = '\0'; if (!strlen(lh_member)) { ERRMSG("Config error at %d: expected list_head" " member after ':'.\n", line); return FALSE; } config->iter_entry->next = create_config_entry(lh_member, ITERATION_ENTRY, line); if (!config->iter_entry->next) return FALSE; config->iter_entry->next->flag &= ~SYMBOL_ENTRY; } if (!strlen(s_name)) { ERRMSG("Config error at %d: Invalid token found " "after 'within' keyword.\n", line); return FALSE; } config->iter_entry->type_name = strdup(s_name); } return TRUE; } /* * Read the iteration entry (LoopConstruct). The syntax is: * * for in { | * via | * within :} * erase [.MemberExpression] [size |nullify] * [erase ...] * [...] * endfor */ static int read_iteration_entry(struct config *config, int line) { int eof = NOT_REACH_END; char *token = get_config_token(NULL, 0, &line, NULL, NULL); if (!token || IS_KEYWORD(token)) { ERRMSG("Config error at %d: expected iteration VAR entry after" " 'for' keyword.\n", line); return FALSE; } config->iter_entry = create_config_entry(token, ITERATION_ENTRY, line); if (!config->iter_entry) { ERRMSG("Error at line %d: " "Failed to read iteration VAR entry.\n", line); return FALSE; } if (!get_config_token("in", 0, &line, NULL, NULL)) { char *token; token = get_config_token(NULL, 0, &line, NULL, NULL); if (token) ERRMSG("Config error at %d: Invalid token '%s'.\n", line, token); ERRMSG("Config error at %d: expected token 'in'.\n", line); return FALSE; } if (!read_list_entry(config, line)) return FALSE; while (!get_config_token("endfor", 0, &line, NULL, &eof) && !eof) { if (get_config_token("erase", 0, &line, NULL, NULL)) { if (!read_erase_cmd_entry(config, line)) return FALSE; } else { token = get_config_token(NULL, 0, &line, NULL, NULL); ERRMSG("Config error at %d: " "Invalid token '%s'.\n", line, token); return FALSE; } } if (eof != NOT_REACH_END) { ERRMSG("Config error at %d: No matching 'endfor' found.\n", line); return FALSE; } return TRUE; } /* * Configuration file 'makedumpfile.conf' contains filter commands. * Every individual filter command is considered as a config entry. * A config entry can be provided on a single line or multiple lines. */ static struct config * get_config(int skip) { struct config *config; char *token = NULL; static int line_count = 0; char *cur_module = NULL; int eof = NOT_REACH_END; unsigned char flag = CONFIG_NEW_CMD; if (skip) flag |= CONFIG_SKIP_SECTION; if ((config = calloc(1, sizeof(struct config))) == NULL) return NULL; if (get_config_token("erase", flag, &line_count, &cur_module, &eof)) { if (cur_module) config->module_name = strdup(cur_module); if (!read_erase_cmd_entry(config, line_count)) goto err_out; } else if (get_config_token("for", 0, &line_count, &cur_module, &eof)) { if (cur_module) config->module_name = strdup(cur_module); if (!read_iteration_entry(config, line_count)) goto err_out; } else { if (eof == NOT_REACH_END) { token = get_config_token(NULL, 0, &line_count, NULL, NULL); ERRMSG("Config error at %d: Invalid token '%s'.\n", line_count, token); } goto err_out; } return config; err_out: if (config) free_config(config); return NULL; } static unsigned long read_pointer_value(unsigned long long vaddr) { unsigned long val; if (!readmem(VADDR, vaddr, &val, sizeof(val))) { ERRMSG("Can't read pointer value\n"); return 0; } return val; } static long get_strlen(unsigned long long vaddr) { char buf[BUFSIZE + 1]; long len = 0; /* * Determine the string length for 'char' pointer. * BUFSIZE(1024) is the upper limit for string length. */ if (readmem(VADDR, vaddr, buf, BUFSIZE)) { buf[BUFSIZE] = '\0'; len = strlen(buf); } return len; } static int resolve_config_entry(struct config_entry *ce, unsigned long long base_vaddr, char *base_struct_name) { unsigned long long symbol; if (ce->flag & SYMBOL_ENTRY) { /* find the symbol info */ if (!ce->name) return FALSE; /* * If we are looking for module symbol then traverse through * mod_st.modules for symbol lookup */ if (sym_in_module(ce->name, &symbol)) ce->sym_addr = symbol; else ce->sym_addr = get_symbol_addr(ce->name); if (!ce->sym_addr) { ERRMSG("Config error at %d: Can't find symbol '%s'.\n", ce->line, ce->name); return FALSE; } ce->sym_addr += get_kaslr_offset(ce->sym_addr); ce->type_name = get_symbol_type_name(ce->name, DWARF_INFO_GET_SYMBOL_TYPE, &ce->size, &ce->type_flag); if (ce->type_flag & TYPE_ARRAY) { ce->array_length = get_array_length(ce->name, NULL, DWARF_INFO_GET_SYMBOL_ARRAY_LENGTH); if (ce->array_length < 0) ce->array_length = 0; } } else if (ce->flag & VAR_ENTRY) { /* iteration variable. * read the value from ce->refer_to */ ce->vaddr = ce->refer_to->vaddr; ce->sym_addr = ce->refer_to->sym_addr; ce->size = ce->refer_to->size; ce->type_flag = ce->refer_to->type_flag; if (!ce->type_name) ce->type_name = strdup(ce->refer_to->type_name); /* This entry has been changed hence next entry needs to * be resolved accordingly. */ if (ce->next) ce->next->flag &= ~ENTRY_RESOLVED; return TRUE; } else { /* find the member offset */ ce->offset = get_member_offset(base_struct_name, ce->name, DWARF_INFO_GET_MEMBER_OFFSET); ce->sym_addr = base_vaddr + ce->offset; ce->type_name = get_member_type_name(base_struct_name, ce->name, DWARF_INFO_GET_MEMBER_TYPE, &ce->size, &ce->type_flag); if (ce->type_flag & TYPE_ARRAY) { ce->array_length = get_array_length(base_struct_name, ce->name, DWARF_INFO_GET_MEMBER_ARRAY_LENGTH); if (ce->array_length < 0) ce->array_length = 0; } } if (ce->type_name == NULL) { if (!(ce->flag & SYMBOL_ENTRY)) ERRMSG("Config error at %d: struct '%s' has no member" " with name '%s'.\n", ce->line, base_struct_name, ce->name); return FALSE; } if (!strcmp(ce->type_name, "list_head")) { ce->type_flag |= TYPE_LIST_HEAD; /* If this list head expression is a LIST entry then * mark the next entry as TRAVERSAL_ENTRY, if any. * Error out if next entry is not a last node. */ if ((ce->flag & LIST_ENTRY) && ce->next) { if (ce->next->next) { ERRMSG("Config error at %d: Only one traversal" " entry is allowed for list_head type" " LIST entry", ce->line); return FALSE; } ce->next->flag |= TRAVERSAL_ENTRY; } } ce->vaddr = ce->sym_addr; if (ce->size < 0) ce->size = 0; if ((ce->flag & LIST_ENTRY) && !ce->next) { /* This is the last node of LIST entry. * For the list entry symbol, the allowed data types are: * Array, Structure Pointer (with 'next' member) and list_head. * * If this is a struct or list_head data type then * create a leaf node entry with 'next' member. */ if (((ce->type_flag & (TYPE_BASE | TYPE_ARRAY)) == TYPE_BASE) && (strcmp(ce->type_name, "void"))) return FALSE; if ((ce->type_flag & TYPE_LIST_HEAD) || ((ce->type_flag & (TYPE_STRUCT | TYPE_ARRAY)) == TYPE_STRUCT)) { if (!(ce->flag & TRAVERSAL_ENTRY)) { ce->next = create_config_entry("next", LIST_ENTRY, ce->line); if (ce->next == NULL) return FALSE; ce->next->flag |= TRAVERSAL_ENTRY; ce->next->flag &= ~SYMBOL_ENTRY; } } if (ce->flag & TRAVERSAL_ENTRY) { /* type name of traversal entry should match with * that of parent node. */ if (strcmp(base_struct_name, ce->type_name)) return FALSE; } } if ((ce->type_flag & (TYPE_ARRAY | TYPE_PTR)) == TYPE_PTR) { /* If it's a pointer variable (not array) then read the * pointer value. */ ce->vaddr = read_pointer_value(ce->sym_addr); /* * if it is a void pointer then reset the size to 0 * User need to provide a size to filter data referenced * by 'void *' pointer or nullify option. */ if (!strcmp(ce->type_name, "void")) ce->size = 0; } if ((ce->type_flag & TYPE_BASE) && (ce->type_flag & TYPE_PTR) && !(ce->type_flag & TYPE_ARRAY)) { if (!strcmp(ce->type_name, "char")) ce->size = get_strlen(ce->vaddr); } if (!ce->next && (ce->flag & SIZE_ENTRY)) { void *val; /* leaf node of size entry */ /* If it is size argument then update the size with data * value of this symbol/member. * Check if current symbol/member is of base data type. */ if (((ce->type_flag & (TYPE_ARRAY | TYPE_BASE)) != TYPE_BASE) || (ce->size > sizeof(long))) { ERRMSG("Config error at %d: size symbol/member '%s' " "is not of base type.\n", ce->line, ce->name); return FALSE; } if ((val = calloc(1, ce->size)) == NULL) { ERRMSG("Can't get memory for size parameter\n"); return FALSE; } if (!readmem(VADDR, ce->vaddr, val, ce->size)) { ERRMSG("Can't read symbol/member data value\n"); return FALSE; } switch (ce->size) { case 1: ce->size = (long)(*((uint8_t *)val)); break; case 2: ce->size = (long)(*((uint16_t *)val)); break; case 4: ce->size = (long)(*((uint32_t *)val)); break; case 8: ce->size = (long)(*((uint64_t *)val)); break; } free(val); } ce->flag |= ENTRY_RESOLVED; if (ce->next) ce->next->flag &= ~ENTRY_RESOLVED; return TRUE; } static unsigned long long get_config_symbol_addr(struct config_entry *ce, unsigned long long base_vaddr, char *base_struct_name) { if (!(ce->flag & ENTRY_RESOLVED)) { if (!resolve_config_entry(ce, base_vaddr, base_struct_name)) return 0; } if (ce->next && ce->vaddr) { /* Populate nullify flag down the list */ ce->next->nullify = ce->nullify; return get_config_symbol_addr(ce->next, ce->vaddr, ce->type_name); } else if (!ce->next && ce->nullify) { /* nullify is applicable to pointer type */ if (ce->type_flag & TYPE_PTR) return ce->sym_addr; else return 0; } else return ce->vaddr; } static long get_config_symbol_size(struct config_entry *ce, unsigned long long base_vaddr, char *base_struct_name) { if (!(ce->flag & ENTRY_RESOLVED)) { if (!resolve_config_entry(ce, base_vaddr, base_struct_name)) return 0; } if (ce->next && ce->vaddr) return get_config_symbol_size(ce->next, ce->vaddr, ce->type_name); else { if (ce->type_flag & TYPE_ARRAY) { if (ce->type_flag & TYPE_PTR) return ce->array_length * get_pointer_size(); else return ce->array_length * ce->size; } return ce->size; } } static int get_next_list_entry(struct config_entry *ce, unsigned long long base_vaddr, char *base_struct_name, struct config_entry *out_ce) { unsigned long vaddr = 0; /* This function only deals with LIST_ENTRY config entry. */ if (!(ce->flag & LIST_ENTRY)) return FALSE; if (!(ce->flag & ENTRY_RESOLVED)) { if (!resolve_config_entry(ce, base_vaddr, base_struct_name)) return FALSE; } if (!ce->next) { /* leaf node. */ if (ce->type_flag & TYPE_ARRAY) { if (ce->index == ce->array_length) return FALSE; if (ce->type_flag & TYPE_PTR) { /* Array of pointers. * * Array may contain NULL pointers at some * indexes. Hence jump to the next non-null * address value. */ while (ce->index < ce->array_length) { vaddr = read_pointer_value(ce->vaddr + (ce->index * get_pointer_size())); if (vaddr) break; ce->index++; } if (ce->index == ce->array_length) return FALSE; out_ce->sym_addr = ce->vaddr + (ce->index * get_pointer_size()); out_ce->vaddr = vaddr; if (!strcmp(ce->type_name, "char")) out_ce->size = get_strlen(vaddr); else out_ce->size = ce->size; } else { out_ce->sym_addr = ce->vaddr + (ce->index * ce->size); out_ce->vaddr = out_ce->sym_addr; out_ce->size = ce->size; } ce->index++; } else { if (ce->vaddr == ce->cmp_addr) return FALSE; out_ce->vaddr = ce->vaddr; /* Set the leaf node as unresolved, so that * it will be resolved every time when * get_next_list_entry is called untill * it hits the exit condiftion. */ ce->flag &= ~ENTRY_RESOLVED; } return TRUE; } else if ((ce->next->next == NULL) && !(ce->next->type_flag & TYPE_ARRAY)) { /* the next node is leaf node. for non-array element * Set the sym_addr and addr of this node with that of * leaf node. */ if (!(ce->type_flag & TYPE_LIST_HEAD)) { if (!ce->vaddr || ce->vaddr == ce->next->cmp_addr) return FALSE; if (!ce->next->cmp_addr) { /* safeguard against circular * link-list */ ce->next->cmp_addr = ce->vaddr; } out_ce->vaddr = ce->vaddr; out_ce->sym_addr = ce->sym_addr; out_ce->size = ce->size; ce->sym_addr = ce->next->sym_addr; ce->vaddr = ce->next->vaddr; /* Force resolution of traversal node */ if (ce->vaddr && !resolve_config_entry(ce->next, ce->vaddr, ce->type_name)) return FALSE; return TRUE; } else { ce->sym_addr = ce->next->sym_addr; ce->vaddr = ce->next->vaddr; } } if (ce->next && ce->vaddr) return get_next_list_entry(ce->next, ce->vaddr, ce->type_name, out_ce); return FALSE; } static int resolve_list_entry(struct config_entry *ce, unsigned long long base_vaddr, char *base_struct_name, char **out_type_name, unsigned char *out_type_flag) { if (!(ce->flag & ENTRY_RESOLVED)) { if (!resolve_config_entry(ce, base_vaddr, base_struct_name)) return FALSE; } if (ce->next && (ce->next->flag & TRAVERSAL_ENTRY) && (ce->type_flag & TYPE_ARRAY)) { /* * We are here because user has provided * traversal member for ArrayVar using 'via' keyword. * * Print warning and continue. */ ERRMSG("Warning: line %d: 'via' keyword not required " "for ArrayVar.\n", ce->next->line); free_config_entry(ce->next); ce->next = NULL; } if ((ce->type_flag & TYPE_LIST_HEAD) && ce->next && (ce->next->flag & TRAVERSAL_ENTRY)) { /* set cmp_addr for list empty condition. */ ce->next->cmp_addr = ce->sym_addr; } if (ce->next && ce->vaddr) { return resolve_list_entry(ce->next, ce->vaddr, ce->type_name, out_type_name, out_type_flag); } else { ce->index = 0; if (out_type_name) *out_type_name = ce->type_name; if (out_type_flag) *out_type_flag = ce->type_flag; } return TRUE; } /* * Insert the filter info node using insertion sort. * If filter node for a given paddr is aready present then update the size * and delete the fl_info node passed. * * Return 1 on successfull insertion. * Return 0 if filter node with same paddr is found. */ static int insert_filter_info(struct filter_info *fl_info) { struct filter_info *prev = NULL; struct filter_info *ptr = filter_info; if (!ptr) { filter_info = fl_info; return 1; } while (ptr) { if (fl_info->paddr <= ptr->paddr) break; prev = ptr; ptr = ptr->next; } if (ptr && (fl_info->paddr == ptr->paddr)) { if (fl_info->size > ptr->size) ptr->size = fl_info->size; free(fl_info); return 0; } if (prev) { fl_info->next = ptr; prev->next = fl_info; } else { fl_info->next = filter_info; filter_info = fl_info; } return 1; } /* * Create an erase info node for each erase command. One node per erase * command even if it is part of loop construct. * For erase commands that are not part of loop construct, the num_sizes will * always be 1 * For erase commands that are part of loop construct, the num_sizes may be * 1 or >1 depending on number iterations. This function will called multiple * times depending on iterations. At first invokation create a node and * increment num_sizes for subsequent invokations. * * The valid erase info node starts from index value 1. (index 0 is invalid * index). * * Index 0 1 2 3 * +------+--------+--------+--------+ * erase_info->|Unused| | | |...... * +------+--------+--------+--------+ * | . . ..... * V * +---------+ * | char* |----> Original erase command string * +---------+ * |num_sizes| * +---------+ +--+--+--+ * | sizes |----> | | | |... Sizes array of num_sizes * +---------+ +--+--+--+ * * On success, return the index value of erase node for given erase command. * On failure, return 0. */ static int add_erase_info_node(struct config_entry *filter_symbol) { int idx = filter_symbol->erase_info_idx; /* * Check if node is already created, if yes, increment the num_sizes. */ if (idx) { erase_info[idx].num_sizes++; return idx; } /* Allocate a new node. */ DEBUG_MSG("Allocating new erase info node for command \"%s\"\n", filter_symbol->symbol_expr); idx = num_erase_info++; erase_info = realloc(erase_info, sizeof(struct erase_info) * num_erase_info); if (!erase_info) { ERRMSG("Can't get memory to create erase information.\n"); return 0; } memset(&erase_info[idx], 0, sizeof(struct erase_info)); erase_info[idx].symbol_expr = filter_symbol->symbol_expr; erase_info[idx].num_sizes = 1; filter_symbol->symbol_expr = NULL; filter_symbol->erase_info_idx = idx; return idx; } /* Return the index value in sizes array for given erase command index. */ static inline int get_size_index(int ei_idx) { if (ei_idx) return erase_info[ei_idx].num_sizes - 1; return 0; } static int update_filter_info(struct config_entry *filter_symbol, struct config_entry *size_symbol) { unsigned long long sym_addr; long size; struct filter_info *fl_info; sym_addr = get_config_symbol_addr(filter_symbol, 0, NULL); if (message_level & ML_PRINT_DEBUG_MSG) print_config_entry(filter_symbol); if (!sym_addr) return FALSE; if (filter_symbol->nullify) size = get_pointer_size(); else if (size_symbol) { size = get_config_symbol_size(size_symbol, 0, NULL); if (message_level & ML_PRINT_DEBUG_MSG) print_config_entry(size_symbol); } else size = get_config_symbol_size(filter_symbol, 0, NULL); if (size <= 0) return FALSE; if ((fl_info = calloc(1, sizeof(struct filter_info))) == NULL) { ERRMSG("Can't allocate filter info\n"); return FALSE; } fl_info->vaddr = sym_addr; fl_info->paddr = vaddr_to_paddr(sym_addr); fl_info->size = size; fl_info->nullify = filter_symbol->nullify; fl_info->erase_ch = 'X'; if (insert_filter_info(fl_info)) { fl_info->erase_info_idx = add_erase_info_node(filter_symbol); fl_info->size_idx = get_size_index(fl_info->erase_info_idx); } return TRUE; } int update_filter_info_raw(unsigned long long sym_addr, int ch, int len) { struct filter_info *fl_info; fl_info = calloc(1, sizeof(struct filter_info)); if (fl_info == NULL) { ERRMSG("Can't allocate filter info\n"); return FALSE; } fl_info->vaddr = sym_addr; fl_info->paddr = vaddr_to_paddr(sym_addr); fl_info->size = len; fl_info->nullify = 0; fl_info->erase_ch = ch; if (insert_filter_info(fl_info)) { /* TODO * Add support to update erase information to the * resulting dump file */ fl_info->erase_info_idx = 0; fl_info->size_idx = 0; } return TRUE; } static int initialize_iteration_entry(struct config_entry *ie, char *type_name, unsigned char type_flag) { if (!(ie->flag & ITERATION_ENTRY)) return FALSE; if (type_flag & TYPE_LIST_HEAD) { if (!ie->type_name) { ERRMSG("Config error at %d: Use 'within' keyword " "to specify StructName:ListHeadMember.\n", ie->line); return FALSE; } /* * If the LIST entry is of list_head type and user has not * specified the member name where iteration entry is hooked * on to list_head, then we default to member name 'list'. */ if (!ie->next) { ie->next = create_config_entry("list", ITERATION_ENTRY, ie->line); ie->next->flag &= ~SYMBOL_ENTRY; } /* * For list_head find out the size of the StructName and * populate ie->size now. For array and link list we get the * size info from config entry returned by * get_next_list_entry(). */ ie->size = get_structure_size(ie->type_name, 0); if (ie->size == FAILED_DWARFINFO) { ERRMSG("Config error at %d: " "Can't get size for type: %s.\n", ie->line, ie->type_name); return FALSE; } else if (ie->size == NOT_FOUND_STRUCTURE) { ERRMSG("Config error at %d: " "Can't find structure: %s.\n", ie->line, ie->type_name); return FALSE; } if (!resolve_config_entry(ie->next, 0, ie->type_name)) return FALSE; if (strcmp(ie->next->type_name, "list_head")) { ERRMSG("Config error at %d: " "Member '%s' is not of 'list_head' type.\n", ie->next->line, ie->next->name); return FALSE; } ie->type_flag = TYPE_STRUCT; } else { if (ie->type_name) { /* looks like user has used 'within' keyword for * non-list_head VAR. Print the warning and continue. */ ERRMSG("Warning: line %d: 'within' keyword not " "required for ArrayVar/StructVar.\n", ie->line); free(ie->type_name); /* remove the next list_head member from iteration * entry that would have added as part of 'within' * keyword processing. */ if (ie->next) { free_config_entry(ie->next); ie->next = NULL; } } /* * Set type flag for iteration entry. The iteration entry holds * individual element from array/list, hence strip off the * array type flag bit. */ ie->type_name = strdup(type_name); ie->type_flag = type_flag; ie->type_flag &= ~TYPE_ARRAY; } return TRUE; } static int list_entry_empty(struct config_entry *le, struct config_entry *ie) { struct config_entry ce; /* Error out if arguments are not correct */ if (!(le->flag & LIST_ENTRY) || !(ie->flag & ITERATION_ENTRY)) { ERRMSG("Invalid arguments\n"); return TRUE; } memset(&ce, 0, sizeof(struct config_entry)); /* get next available entry from LIST entry. */ if (!get_next_list_entry(le, 0, NULL, &ce)) return TRUE; if (ie->next) { /* we are dealing with list_head */ ie->next->vaddr = ce.vaddr; ie->vaddr = ce.vaddr - ie->next->offset; } else { ie->vaddr = ce.vaddr; ie->sym_addr = ce.sym_addr; ie->size = ce.size; } return FALSE; } /* * Process the config entry that has been read by get_config. * return TRUE on success */ static int process_config(struct config *config) { int i; unsigned char type_flag; char *type_name = NULL; if (config->list_entry) { /* * We are dealing with 'for' command. * - First resolve list entry. * - Initialize iteration entry for iteration. * - Populate iteration entry untill list entry empty. */ if (!resolve_list_entry(config->list_entry, 0, NULL, &type_name, &type_flag)) { return FALSE; } if (!initialize_iteration_entry(config->iter_entry, type_name, type_flag)) { return FALSE; } while (!list_entry_empty(config->list_entry, config->iter_entry)) { for (i = 0; i < config->num_filter_symbols; i++) update_filter_info(config->filter_symbol[i], config->size_symbol[i]); } } else update_filter_info(config->filter_symbol[0], config->size_symbol[0]); return TRUE; } static void print_filter_info() { struct filter_info *fl_info = filter_info; DEBUG_MSG("\n"); while (fl_info) { DEBUG_MSG("filter address: paddr (%llx), sym_addr (%llx)," " Size (%ld)\n", fl_info->paddr, fl_info->vaddr, fl_info->size); fl_info = fl_info->next; } } static void init_filter_config() { filter_config.name_filterconfig = info->name_filterconfig; filter_config.file_filterconfig = info->file_filterconfig; filter_config.saved_token = NULL; filter_config.token = NULL; filter_config.cur_module = NULL; filter_config.new_section = 0; filter_config.line_count = 0; } /* * Read and process each config entry (filter commands) from filter config * file. If no module debuginfo found for specified module section then skip * to next module section. */ static int process_config_file(const char *name_config) { struct config *config; int skip_section = 0; if (!name_config) return FALSE; if ((info->file_filterconfig = fopen(name_config, "r")) == NULL) { ERRMSG("Can't open config file(%s). %s\n", name_config, strerror(errno)); return FALSE; } init_filter_config(); while((config = get_config(skip_section)) != NULL) { skip_section = 0; if (config->module_name && strcmp(config->module_name, "vmlinux")) { /* * if Module debuginfo is not available, then skip to * next module section. */ if (!set_dwarf_debuginfo(config->module_name, info->system_utsname.release, NULL, -1)) { ERRMSG("Skipping to next Module section\n"); skip_section = 1; free_config(config); continue; } } else { set_dwarf_debuginfo("vmlinux", NULL, info->name_vmlinux, info->fd_vmlinux); } process_config(config); free_config(config); } fclose(info->file_filterconfig); print_filter_info(); return TRUE; } /* * Search for symbol in modules as well as vmlinux */ unsigned long long get_symbol_addr_all(char *name) { short vmlinux_searched = 0; unsigned long long symbol_addr = 0; unsigned int i, current_mod; struct module_info *modules; /* Search in vmlinux if debuginfo is set to vmlinux */ if (!strcmp(get_dwarf_module_name(), "vmlinux")) { symbol_addr = get_symbol_addr(name); if (symbol_addr) return symbol_addr; vmlinux_searched = 1; } /* * Proceed the search in modules. Try in the module * which resulted in a hit in the previous search */ modules = mod_st.modules; current_mod = mod_st.current_mod; if (strcmp(get_dwarf_module_name(), modules[current_mod].name)) { if (!set_dwarf_debuginfo(modules[current_mod].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Cannot set to current module %s\n", modules[current_mod].name); return NOT_FOUND_SYMBOL; } } symbol_addr = find_module_symbol(&modules[current_mod], name); if (symbol_addr) return symbol_addr; /* Search in all modules */ for (i = 0; i < mod_st.num_modules; i++) { /* Already searched. Skip */ if (i == current_mod) continue; if (!set_dwarf_debuginfo(modules[i].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Skipping Module section %s\n", modules[i].name); continue; } symbol_addr = find_module_symbol(&modules[i], name); if (!symbol_addr) continue; /* * Symbol found. Set the current_mod to this module index, a * minor optimization for fast lookup next time */ mod_st.current_mod = i; return symbol_addr; } /* Symbol not found in any module. Set debuginfo back to vmlinux */ set_dwarf_debuginfo("vmlinux", NULL, info->name_vmlinux, info->fd_vmlinux); /* * Search vmlinux if not already searched. This can happen when * this function is called with debuginfo set to a particular * kernel module and we are looking for symbol in vmlinux */ if (!vmlinux_searched) return get_symbol_addr(name); else return NOT_FOUND_SYMBOL; } /* * Search for domain in modules as well as vmlinux */ long get_domain_all(char *symname, int cmd, unsigned long long *die) { short vmlinux_searched = 0; long size = 0; unsigned int i, current_mod; struct module_info *modules; /* Search in vmlinux if debuginfo is set to vmlinux */ if (!strcmp(get_dwarf_module_name(), "vmlinux")) { size = get_domain(symname, cmd, die); if (size > 0 && die) return size; vmlinux_searched = 1; } /* * Proceed the search in modules. Try in the module * which resulted in a hit in the previous search */ modules = mod_st.modules; current_mod = mod_st.current_mod; if (strcmp(get_dwarf_module_name(), modules[current_mod].name)) { if (!set_dwarf_debuginfo(modules[current_mod].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Cannot set to current module %s\n", modules[current_mod].name); return NOT_FOUND_STRUCTURE; } } size = get_domain(symname, cmd, die); if (size > 0 && die) return size; /* Search in all modules */ for (i = 0; i < mod_st.num_modules; i++) { /* Already searched. Skip */ if (i == current_mod) continue; if (!set_dwarf_debuginfo(modules[i].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Skipping Module section %s\n", modules[i].name); continue; } size = get_domain(symname, cmd, die); if (size <= 0 || !die) continue; /* * Domain found. Set the current_mod to this module index, a * minor optimization for fast lookup next time */ mod_st.current_mod = i; return size; } /* Domain not found in any module. Set debuginfo back to vmlinux */ set_dwarf_debuginfo("vmlinux", NULL, info->name_vmlinux, info->fd_vmlinux); if (!vmlinux_searched) return get_domain(symname, cmd, die); else return NOT_FOUND_STRUCTURE; } /* * Search for die in modules as well as vmlinux */ int get_die_nfields_all(unsigned long long die_off) { short vmlinux_searched = 0; long nfields = -1; unsigned int i, current_mod; struct module_info *modules; /* Search in vmlinux if debuginfo is set to vmlinux */ if (!strcmp(get_dwarf_module_name(), "vmlinux")) { nfields = get_die_nfields(die_off); if (nfields > 0) return nfields; vmlinux_searched = 1; } /* * Proceed the search in modules. Try in the module * which resulted in a hit in the previous search */ modules = mod_st.modules; current_mod = mod_st.current_mod; if (strcmp(get_dwarf_module_name(), modules[current_mod].name)) { if (!set_dwarf_debuginfo(modules[current_mod].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Cannot set to current module %s\n", modules[current_mod].name); return -1; } } nfields = get_die_nfields(die_off); if (nfields > 0) return nfields; /* Search in all modules */ for (i = 0; i < mod_st.num_modules; i++) { /* Already searched. Skip */ if (i == current_mod) continue; if (!set_dwarf_debuginfo(modules[i].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Skipping Module section %s\n", modules[i].name); continue; } nfields = get_die_nfields(die_off); if (nfields < 0) continue; /* * Die found. Set the current_mod to this module index, * a minor optimization for fast lookup next time */ mod_st.current_mod = i; return nfields; } /* Die not found in any module. Set debuginfo back to vmlinux */ set_dwarf_debuginfo("vmlinux", NULL, info->name_vmlinux, info->fd_vmlinux); if (!vmlinux_searched) return get_die_nfields(die_off); else return -1; } /* * Search for die member in modules as well as vmlinux */ int get_die_member_all(unsigned long long die_off, int index, long *offset, char **name, int *nbits, int *fbits, unsigned long long *m_die) { short vmlinux_searched = 0; long size = -1; unsigned int i, current_mod; struct module_info *modules; /* Search in vmlinux if debuginfo is set to vmlinux */ if (!strcmp(get_dwarf_module_name(), "vmlinux")) { size = get_die_member(die_off, index, offset, name, nbits, fbits, m_die); if (size >= 0) return size; vmlinux_searched = 1; } /* * Proceed the search in modules. Try in the module * which resulted in a hit in the previous search */ modules = mod_st.modules; current_mod = mod_st.current_mod; if (strcmp(get_dwarf_module_name(), modules[current_mod].name)) { if (!set_dwarf_debuginfo(modules[current_mod].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Cannot set to current module %s\n", modules[current_mod].name); return -1; } } size = get_die_member(die_off, index, offset, name, nbits, fbits, m_die); if (size >= 0) return size; /* Search in all modules */ for (i = 0; i < mod_st.num_modules; i++) { /* Already searched. Skip */ if (i == current_mod) continue; if (!set_dwarf_debuginfo(modules[i].name, info->system_utsname.release, NULL, -1)) { ERRMSG("Skipping Module section %s\n", modules[i].name); continue; } size = get_die_member(die_off, index, offset, name, nbits, fbits, m_die); if (size < 0) continue; /* * Die member found. Set the current_mod to this module index, * a minor optimization for fast lookup next time */ mod_st.current_mod = i; return size; } /* Die member not found in any module. Set debuginfo back to vmlinux */ set_dwarf_debuginfo("vmlinux", NULL, info->name_vmlinux, info->fd_vmlinux); if (!vmlinux_searched) return get_die_member(die_off, index, offset, name, nbits, fbits, m_die); else return -1; } /* Process the eppic macro using eppic library */ static int process_eppic_file(char *name_config) { void *handle; void (*eppic_load)(char *), (*eppic_unload)(char *); int (*eppic_init)(); /* * Dynamically load the eppic_makedumpfile.so library. */ handle = dlopen("eppic_makedumpfile.so", RTLD_LAZY); if (!handle) { ERRMSG("dlopen failed: %s\n", dlerror()); return FALSE; } /* TODO * Support specifying eppic macros in makedumpfile.conf file */ eppic_init = dlsym(handle, "eppic_init"); if (!eppic_init) { ERRMSG("Could not find eppic_init function\n"); return FALSE; } eppic_load = dlsym(handle, "eppic_load"); if (!eppic_load) { ERRMSG("Could not find eppic_load function\n"); return FALSE; } eppic_unload = dlsym(handle, "eppic_unload"); if (!eppic_unload) ERRMSG("Could not find eppic_unload function\n"); if (eppic_init(&eppic_cb)) { ERRMSG("Init failed \n"); return FALSE; } /* Load/compile, execute and unload the eppic macro */ eppic_load(name_config); eppic_unload(name_config); if (dlclose(handle)) ERRMSG("dlclose failed: %s\n", dlerror()); return TRUE; } static void split_filter_info(struct filter_info *prev, unsigned long long next_paddr, size_t size) { struct filter_info *new; if ((new = calloc(1, sizeof(struct filter_info))) == NULL) { ERRMSG("Can't allocate memory to split filter info\n"); return; } /* * copy over existing data from prev node and only update fields * that differ. This approach will take care of copying over of any * future member addition to filter_info structure. */ *new = *prev; new->paddr = next_paddr; new->size = size; prev->next = new; } static void update_erase_info(struct filter_info *fi) { struct erase_info *ei; if (!fi->erase_info_idx) return; ei = &erase_info[fi->erase_info_idx]; if (!ei->sizes) { /* First time, allocate sizes array */ ei->sizes = calloc(ei->num_sizes, sizeof(long)); if (!ei->sizes) { ERRMSG("Can't allocate memory for erase info sizes\n"); return; } } ei->erased = 1; if (!fi->nullify) ei->sizes[fi->size_idx] += fi->size; else ei->sizes[fi->size_idx] = -1; } static int extract_filter_info(unsigned long long start_paddr, unsigned long long end_paddr, struct filter_info *fl_info) { struct filter_info *fi = filter_info; struct filter_info *prev = NULL; size_t size1, size2; if (!fl_info) return FALSE; while (fi) { if ((fi->paddr >= start_paddr) && (fi->paddr < end_paddr)) { size1 = end_paddr - fi->paddr; if (fi->size <= size1) break; size2 = fi->size - size1; fi->size = size1; split_filter_info(fi, fi->paddr + size1, size2); break; } prev = fi; fi = fi->next; } if (!fi) return FALSE; *fl_info = *fi; fl_info->next = NULL; /* Delete this node */ if (!prev) filter_info = fi->next; else prev->next = fi->next; update_erase_info(fi); free(fi); return TRUE; } /* * External functions. */ int gather_filter_info(void) { int ret = TRUE; /* * Before processing filter config file, load the symbol data of * loaded modules from vmcore. */ set_dwarf_debuginfo("vmlinux", NULL, info->name_vmlinux, info->fd_vmlinux); if (!load_module_symbols()) return FALSE; /* * XXX: We support specifying both makedumpfile.conf and * eppic macro at the same time. Whether to retain or discard the * functionality provided by makedumpfile.conf is open for * discussion */ if (info->name_filterconfig) ret = process_config_file(info->name_filterconfig); if (info->name_eppic_config) ret &= process_eppic_file(info->name_eppic_config); /* * Remove modules symbol information, we dont need now. * Reset the dwarf debuginfo to vmlinux to close open file * descripter of module debuginfo file, if any. */ clean_module_symbols(); set_dwarf_debuginfo("vmlinux", NULL, info->name_vmlinux, info->fd_vmlinux); return ret; } void clear_filter_info(void) { struct filter_info *prev, *fi = filter_info; int i; /* Delete filter_info nodes that are left out. */ while (fi) { prev = fi; fi = fi->next; free(prev); } filter_info = NULL; if (erase_info == NULL) return; for (i = 1; i < num_erase_info; i++) { free(erase_info[i].symbol_expr); free(erase_info[i].sizes); } free(erase_info); erase_info = NULL; } /* * Filter buffer if the physical address is in filter_info. */ void filter_data_buffer(unsigned char *buf, unsigned long long paddr, size_t size) { struct filter_info fl_info; unsigned char *buf_ptr; while (extract_filter_info(paddr, paddr + size, &fl_info)) { buf_ptr = buf + (fl_info.paddr - paddr); if (fl_info.nullify) memset(buf_ptr, 0, fl_info.size); else memset(buf_ptr, fl_info.erase_ch, fl_info.size); } } /* * Filter buffer if the physical address is in filter_info. */ void filter_data_buffer_parallel(unsigned char *buf, unsigned long long paddr, size_t size, pthread_mutex_t *mutex) { struct filter_info fl_info; unsigned char *buf_ptr; int found = FALSE; while (TRUE) { pthread_mutex_lock(mutex); found = extract_filter_info(paddr, paddr + size, &fl_info); pthread_mutex_unlock(mutex); if (found) { buf_ptr = buf + (fl_info.paddr - paddr); if (fl_info.nullify) memset(buf_ptr, 0, fl_info.size); else memset(buf_ptr, fl_info.erase_ch, fl_info.size); } else { break; } } } unsigned long get_size_eraseinfo(void) { unsigned long size_eraseinfo = 0; char size_str[MAX_SIZE_STR_LEN]; struct erase_info *ei; struct filter_info *fl_info = filter_info; while (fl_info) { if (!fl_info->erase_info_idx) continue; ei = &erase_info[fl_info->erase_info_idx]; if (fl_info->nullify) sprintf(size_str, "nullify\n"); else sprintf(size_str, "size %ld\n", fl_info->size); size_eraseinfo += strlen("erase ") + strlen(ei->symbol_expr) + 1 + strlen(size_str); fl_info = fl_info->next; } return size_eraseinfo; }