diff options
Diffstat (limited to 'erase_info.c')
-rw-r--r-- | erase_info.c | 2470 |
1 files changed, 2470 insertions, 0 deletions
diff --git a/erase_info.c b/erase_info.c new file mode 100644 index 0000000..60abfa1 --- /dev/null +++ b/erase_info.c @@ -0,0 +1,2470 @@ +/* + * erase_info.c + * + * Created by: Mahesh J Salgaonkar <mahesh@linux.vnet.ibm.com> + * + * 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 <dlfcn.h> + +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 <Symbol>[.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 <Symbol>[.member[...]] [size <SizeValue>[K|M]] + * erase <Symbol>[.member[...]] [size <SizeSymbol>] + * erase <Symbol>[.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 <id> in {<ArrayVar> | + * <StructVar> via <NextMember> | + * <ListHeadVar> within <StructName>:<ListHeadMember>} + * erase <id>[.MemberExpression] [size <SizeExpression>|nullify] + * [erase <id>...] + * [...] + * 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; +} + |