/*
* 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;
}