commit dc34687567976a6766f748314c0a7c3a1087660e Author: Mahesh Salgaonkar Date: Fri Aug 19 17:07:56 2011 +0900 [PATCH v2 6/8] Read and process 'for' command from config file. This patch adds support to read and process 'for' command from config file to filter multiple memory locations that are accessible through an array, link list or list_head. The syntax for 'for' (loop construct) filter command is: for in { | via | within :} erase [.MemberExpression] [size |nullify] [erase ...] [...] endfor Updated filter.conf(8) man page that describes the syntax for loop construct. Signed-off-by: Mahesh Salgaonkar Signed-off-by: Prerna Saxena --- diff -Nrup kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.c kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.c --- kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.c 2012-01-02 17:03:57.000000000 +0800 +++ kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.c 2012-01-02 17:06:09.000000000 +0800 @@ -7732,13 +7732,20 @@ free_config_entry(struct config_entry *c void free_config(struct config *config) { + int i; if (config) { 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_entry(config->filter_symbol); + free(config->filter_symbol); if (config->size_symbol) - free_config_entry(config->size_symbol); + free(config->size_symbol); free(config); } } @@ -7795,7 +7802,16 @@ create_config_entry(const char *token, u /* First node is always a symbol name */ ptr->flag |= SYMBOL_ENTRY; } - if (flag & FILTER_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) { @@ -7994,6 +8010,7 @@ get_config_token(char *expected_token, u static int read_size_entry(struct config *config, int line) { + int idx = config->num_filter_symbols - 1; char *token = get_config_token(NULL, 0, &line, NULL, NULL); if (!token || IS_KEYWORD(token)) { @@ -8001,12 +8018,19 @@ read_size_entry(struct config *config, i " 'size' keyword.\n", line); return FALSE; } - config->size_symbol = create_config_entry(token, SIZE_ENTRY, line); - if (!config->size_symbol) { + 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; } @@ -8020,6 +8044,7 @@ read_size_entry(struct config *config, i static int read_filter_entry(struct config *config, int line) { + int size, idx; char *token = get_config_token(NULL, 0, &line, NULL, NULL); if (!token || IS_KEYWORD(token)) { @@ -8027,15 +8052,41 @@ read_filter_entry(struct config *config, " 'erase' command.\n", line); return FALSE; } - config->filter_symbol = + + 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) { + if (!config->filter_symbol[idx]) { ERRMSG("Error at line %d: Failed to read filter symbol\n", line); return FALSE; } + 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->nullify = 1; + config->filter_symbol[idx]->nullify = 1; } else if (get_config_token("size", 0, &line, NULL, NULL)) { if (!read_size_entry(config, line)) @@ -8044,6 +8095,150 @@ read_filter_entry(struct config *config, return TRUE; } +static int +add_traversal_entry(struct config_entry *ce, char *member, int line) +{ + if (!ce) + return FALSE; + + while (ce->next) + ce = ce->next; + + ce->next = create_config_entry(member, LIST_ENTRY, line); + if (ce->next == NULL) { + ERRMSG("Error at line %d: Failed to read 'via' member\n", + line); + return FALSE; + } + + ce->next->flag |= TRAVERSAL_ENTRY; + ce->next->flag &= ~SYMBOL_ENTRY; + return TRUE; +} + +static int +read_list_entry(struct config *config, int line) +{ + char *token = get_config_token(NULL, 0, &line, NULL, NULL); + + if (!token || IS_KEYWORD(token)) { + ERRMSG("Config error at %d: expected list symbol after" + " 'in' keyword.\n", line); + return FALSE; + } + config->list_entry = create_config_entry(token, LIST_ENTRY, line); + if (!config->list_entry) { + ERRMSG("Error at line %d: Failed to read list symbol\n", + line); + return FALSE; + } + /* Check if user has provided 'via' or 'within' keyword */ + if (get_config_token("via", 0, &line, NULL, NULL)) { + /* next token is traversal member NextMember */ + token = get_config_token(NULL, 0, &line, NULL, NULL); + if (!token) { + ERRMSG("Config error at %d: expected member name after" + " 'via' keyword.\n", line); + return FALSE; + } + if (!add_traversal_entry(config->list_entry, token, line)) + return FALSE; + } + else if (get_config_token("within", 0, &line, NULL, NULL)) { + char *s_name, *lh_member; + /* next value is StructName:ListHeadMember */ + s_name = get_config_token(NULL, 0, &line, NULL, NULL); + if (!s_name || IS_KEYWORD(s_name)) { + ERRMSG("Config error at %d: expected struct name after" + " 'within' keyword.\n", line); + return FALSE; + } + lh_member = strchr(s_name, ':'); + if (lh_member) { + *lh_member++ = '\0'; + if (!strlen(lh_member)) { + ERRMSG("Config error at %d: expected list_head" + " member after ':'.\n", line); + return FALSE; + } + config->iter_entry->next = + create_config_entry(lh_member, + ITERATION_ENTRY, line); + if (!config->iter_entry->next) + return FALSE; + config->iter_entry->next->flag &= ~SYMBOL_ENTRY; + } + if (!strlen(s_name)) { + ERRMSG("Config error at %d: Invalid token found " + "after 'within' keyword.\n", line); + return FALSE; + } + config->iter_entry->type_name = strdup(s_name); + } + return TRUE; +} + +/* + * Read the iteration entry (LoopConstruct). The syntax is: + * + * for in { | + * via | + * within :} + * erase [.MemberExpression] [size |nullify] + * [erase ...] + * [...] + * endfor + */ +static int +read_iteration_entry(struct config *config, int line) +{ + int eof = 0; + 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_filter_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) { + 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 @@ -8069,6 +8264,13 @@ get_config(int skip) if (!read_filter_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) { token = get_config_token(NULL, 0, &line_count, @@ -8137,6 +8339,24 @@ resolve_config_entry(struct config_entry ce->array_length = 0; } } + else if (ce->flag & VAR_ENTRY) { + /* iteration variable. + * read the value from ce->refer_to + */ + ce->addr = ce->refer_to->addr; + 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, @@ -8160,9 +8380,58 @@ resolve_config_entry(struct config_entry 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->addr = 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) + && (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. */ @@ -8171,7 +8440,7 @@ resolve_config_entry(struct config_entry /* * 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. + * by 'void *' pointer or nullify option. */ if (!strcmp(ce->type_name, "void")) ce->size = 0; @@ -8230,6 +8499,8 @@ resolve_config_entry(struct config_entry free(val); } ce->flag |= ENTRY_RESOLVED; + if (ce->next) + ce->next->flag &= ~ENTRY_RESOLVED; return TRUE; } @@ -8238,18 +8509,87 @@ get_config_symbol_addr(struct config_ent unsigned long long base_addr, char *base_struct_name) { + unsigned long long addr = 0; + if (!(ce->flag & ENTRY_RESOLVED)) { if (!resolve_config_entry(ce, base_addr, base_struct_name)) return 0; } + if ((ce->flag & LIST_ENTRY)) { + /* handle List entry differently */ + if (!ce->next) { + /* leaf node. */ + if (ce->type_flag & TYPE_ARRAY) { + if (ce->index == ce->array_length) + return 0; + if (!(ce->type_flag & TYPE_PTR)) + return (ce->addr + + (ce->index * ce->size)); + /* Array of pointers. + * + * Array may contain NULL pointers at some + * indexes. Hence return the next non-null + * address value. + */ + while (ce->index < ce->array_length) { + addr = read_pointer_value(ce->addr + + (ce->index * pointer_size)); + ce->index++; + if (addr) + break; + } + return addr; + } + else { + if (ce->addr == ce->cmp_addr) + return 0; + + /* Set the leaf node as unresolved, so that + * it will be resolved every time when + * get_config_symbol_addr is called untill + * it hits the exit condiftion. + */ + ce->flag &= ~ENTRY_RESOLVED; + } + } + 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. + */ + addr = ce->addr; + ce->addr = ce->next->addr; + + if (!(ce->type_flag & TYPE_LIST_HEAD)) { + if (addr == ce->next->cmp_addr) + return 0; + + if (!ce->next->cmp_addr) { + /* safeguard against circular + * link-list + */ + ce->next->cmp_addr = addr; + } + + /* Force resolution of traversal node */ + if (ce->addr && !resolve_config_entry(ce->next, + ce->addr, ce->type_name)) + return 0; + + return addr; + } + } + } + if (ce->next && ce->addr) { /* Populate nullify flag down the list */ ce->next->nullify = ce->nullify; return get_config_symbol_addr(ce->next, ce->addr, ce->type_name); } - else if (ce->nullify) { + else if (!ce->next && ce->nullify) { /* nullify is applicable to pointer type */ if (ce->type_flag & TYPE_PTR) return ce->sym_addr; @@ -8284,6 +8624,48 @@ get_config_symbol_size(struct config_ent } } +static int +resolve_list_entry(struct config_entry *ce, unsigned long long base_addr, + char *base_struct_name, char **out_type_name, + unsigned char *out_type_flag) +{ + if (!(ce->flag & ENTRY_RESOLVED)) { + if (!resolve_config_entry(ce, base_addr, 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->addr) { + return resolve_list_entry(ce->next, ce->addr, + 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 @@ -8364,6 +8746,107 @@ update_filter_info(struct config_entry * return TRUE; } +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; + } + } + 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; + } + } + ie->type_name = strdup(type_name); + } + + if (!ie->size) { + ie->size = get_structure_size(ie->type_name, + DWARF_INFO_GET_STRUCT_SIZE); + 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 (type_flag & TYPE_LIST_HEAD) { + 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; + } + } + return TRUE; +} + +int +list_entry_empty(struct config_entry *le, struct config_entry *ie) +{ + unsigned long long addr; + + /* Error out if arguments are not correct */ + if (!(le->flag & LIST_ENTRY) || !(ie->flag & ITERATION_ENTRY)) { + ERRMSG("Invalid arguments\n"); + return TRUE; + } + addr = get_config_symbol_addr(le, 0, NULL); + if (!addr) + return TRUE; + + if (ie->next) { + /* we are dealing with list_head */ + ie->next->addr = addr; + ie->addr = addr - ie->next->offset; + //resolve_iteration_entry(ie, addr); + } + else + ie->addr = addr; + return FALSE; +} + /* * Process the config entry that has been read by get_config. * return TRUE on success @@ -8371,7 +8854,35 @@ update_filter_info(struct config_entry * int process_config(struct config *config) { - update_filter_info(config->filter_symbol, config->size_symbol); + int i; + if (config->list_entry) { + unsigned char type_flag; + char *type_name = NULL; + /* + * 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; } diff -Nrup kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.conf kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.conf --- kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.conf 1970-01-01 08:00:00.000000000 +0800 +++ kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.conf 2012-01-02 17:06:09.000000000 +0800 @@ -0,0 +1,149 @@ +## Filter config file +## +## Description: +## Configuration file to specify filter commands to filter out desired +## kernel data from vmcore. It supports erasing of symbol data and +## it's members of any data type. In case of filtering of data pointed by +## void * pointer, user needs to provide size to filter out. +## +## Please refer to manpage makedumpfile.conf(8) for more details. +## +## +## - Module section +## ========================================================= +## Syntax: +## [ModuleName] +## +## Define the module section where the symbols specified in subsequent erase +## commands belong to. The section name is a kernel module name (including +## vmlinux). The unnamed section defaults to [vmlinux] section. +## +## NOTE: There should not be any whitespaces before or after the ModuleName. +## +## e.g. +## [vmlinux] # Symbols in erase command belongs to main kernel (vmlinux) +## erase modules +## erase cred_jar.name +## erase cred_jar.name size 10 +## erase cred_jar.array +## erase vmlist.addr nullify +## +## [z90crypt] # Symbols in erase command belongs to kernel module z90crypt +## erase ap_device_list +## +## # erase entire CPRBX structure +## erase static_cprbx +## +## +## - To erase kernel data referred through a kernel Symbol +## ========================================================= +## Syntax: +## erase [.member[...]] [size [K|M]] +## erase [.member[...]] [size ] +## erase [.member[...]] [nullify]] +## +## where +## +## A variable name from the kernel or module, which is part of +## global symbols '/proc/kallsyms'. +## +## Integer value that specifies size of data to be erased. The +## suffixes 'K' and 'M' can be used to specify kilobytes and +## megabytes respectively where, K means 1024 bytes and M means +## 1024 ^ 2 = 1048576 bytes. +## +## A simple axpression of the form [.member[...]] that +## denotes a symbol which contains a positive integer value as a +## size of the data in bytes to be erased. +## +## Filter out the specified size of the data referred by symbol/member. +## If size option is not provided then the size of will be calculated +## according to it's data type. For 'char *' data type, string length will be +## determined with an upper limit of 1024. +## +## If specified is of type 'void *', then user needs to provide +## either 'size' or 'nullify' option. Otherwise erase command will not have +## any effect. +## +## The option 'nullify' will work only if filter symbol/member is a pointer and +## is used to set NULL value to the pointer type symbol/member. +## +## NOTE: Please note that by nullifying pointer values will affect the +## debug ability of created DUMPFILE. Use 'nullify' option only when size of +## data to be filter out is not known e.g. data pointed by 'void *'. +## +## e.g. +## [vmlinux] +## erase modules +## erase cred_jar.name +## erase cred_jar.name size 10 +## erase cred_jar.array +## erase vmlist.addr nullify +## +## +## - To filter kernel data referred through Array/list_head Symbol +## ================================================================= +## Syntax: +## for in { | +## via | +## within : } +## erase [.MemberExpression] [size |nullify] +## [erase ...] +## [...] +## endfor +## +## where +## +## Arbitrary name used to temporarily point to elements of the +## list. Referred as iteration variable. +## +## A simple expression in the form of [.member[...]] that +## results into an array variable. +## +## A simple expression in the form of [.member[...]] that +## results into a variable that points to a structure. +## +## Member within that points to an object of same +## type that of . +## +## A simple expression in the form of [.member[...]] that +## results into a variable of type struct list_head. +## +## Name of the structure type that can be traversed using +## HEAD variable and contains a member named +## . +## +## Name of a member in , of type struct list_head. +## +## A simple expression in the form of [.member[...]] to specify a +## member or component of a member in , or +## . +## +## One of the following: +## - An integer value. +## - [.member[...]] +## - [.MemberExpresion] +## +## The , and is also referred as LIST +## entry +## +## Filter out the specified size of the data accessible through LIST entries. +## e.g. +## [vmlinux] +## # Traversing +## for m in modules.next within module:list +## erase m.holders_dir.name +## endfor +## # Traversing +## for lc in lowcore_ptr +## erase lc +## endfor +## # Traversing link-list +## for cj in cred_jar via slabp_cache +## erase cj.name +## endfor +## [z90crypt] +## for ap_dev in ap_device_list.next within ap_device:list +## erase ap_dev.reply.message size ap_dev.reply.length +## endfor +## diff -Nrup kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.conf.8 kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.conf.8 --- kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.conf.8 1970-01-01 08:00:00.000000000 +0800 +++ kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.conf.8 2012-01-02 17:06:09.000000000 +0800 @@ -0,0 +1,419 @@ +.TH FILTER.CONF 8 "16 November 2010" "makedumpfile v1.3.7" "Linux System Administrator's Manual" +.SH NAME +makedumpfile.conf \- The filter configuration file for makedumpfile(8). +.SH DESCRIPTION +.PP +The makedumpfile.conf is a configuration file for makedumpfile tool. +makedumpfile.conf file contains the erase commands to filter out desired kernel +data from the vmcore while creating \fIDUMPFILE\fR using makedumpfile tool. +makedumpfile reads the filter config and builds the list of memory addresses +and its sizes after processing filter commands. The memory locations that +require to be filtered out are then poisoned with character fXf (58 in Hex). +.SH FILE FORMAT +.PP +The file consists of module sections that contains filter commands. A section +begins with the name of the section in square brackets and continues until the +next section begins. + +.br +"["<\fIModuleName\fR>"]" +.br +<\fIFilterCommands\fR> +.br + +where +.br +"[" is the character \fB[\fR +.br +"]" is the character \fB]\fR +.TP +<\fIModuleName\fR> +is either 'vmlinux' or name of a Linux kernel module. +.TP +<\fIFilterCommands\fR> +is a list of one or more filter commands as described in the section +\fBFILTER COMMANDS\fR of this manual page. +.PP +The section name indicates a kernel module name (including \fBvmlinux\fR) where +the symbols specified in subsequent erase commands belong to. The unnamed +section defaults to \fB[vmlinux]\fR section. However, a user can also explicitly +define \fB[vmlinux]\fR section. The sections help makedumpfile tool to select +appropriate kernel or module debuginfo file before processing the subsequent +erase commands. Before selecting appropriate debuginfo file, the module name +is validated against the loaded modules from the vmcore. If no match is found, +then the section is ignored and makedumpfile skips to the next module section. +If match is found, then makedumpfile will try to load the corresponding +module debuginfo file. If module debuginfo is not available then, makedumpfile +will skip the section with a warning message. +.SH FILTER COMMANDS +.SS filter command +.PP +A filter command is either an erase command or a loop construct. Each erase +command and loop construct must start with a new line. Each filter command +describes data in the dump to be erased. Syntax: + +.br +<\fIEraseCommands\fR>|<\fILoopConstruct\fR> +.br + +where +.TP +<\fIEraseCommands\fR> +Described in the subsection \fBerase command\fR of this manual page. +.TP +<\fILoopConstruct\fR> +Described in the subsection \fBLoop construct\fR of this manual page. +.SS erase command +.PP +Erase specified size of a kernel data referred by specified kernel/module +symbol or its member component. The erase command syntax is: + +.br +\fBerase\fR <\fISymbol\fR>[.\fImember\fR[...]] [\fBsize\fR +<\fISizeValue\fR>[K|M]] +.br +\fBerase\fR <\fISymbol\fR>[.\fImember\fR[...]] [\fBsize\fR <\fISizeSymbol\fR>] +.br +\fBerase\fR <\fISymbol\fR>[.\fImember\fR[...]] [\fBnullify\fR] +.br + +where +.br +.TP +<\fISymbol\fR> +A kernel or module symbol (variable) name that is part of global symbols +\fB/proc/kallsyms\fR. +.TP +<\fISizeValue\fR> +A positive integer value as a size of the data in bytes to be erased. The +suffixes 'K' and 'M' can be used to specify kilobytes and Megabytes +respectively where, K means 1024 bytes and M means 1024 ^ 2 = 1048576 bytes. +The suffixes are not case sensitive. +.TP +<\fISizeSymbol\fR> +A simple expression of the form <\fISymbol\fR>[.\fImember\fR[...]] that denotes +a symbol which contains a positive integer value as a size of the data in bytes +to be erased. +.TP +<\fISymbol\fR>[.\fImember\fR[...]] +A simple expression that results into either a global kernel symbol name or +its member components. The expression always uses '.' operator to specify +the \fImember\fR component of kernel symbol or its member irrespective of +whether it is of pointer type or not. +.TP +\fImember\fR[...] +Member or component of member in <\fISymbol\fR>. +.PP +The \fBerase\fR command takes two arguments 1. kernel symbol name or its +member components and 2. size of the data referred by argument (1) OR +\fBnullify\fR keyword. The second argument \fBsize\fR OR \fBnullify\fR is +optional. The unit for size value is in \fBbytes\fR. If \fBsize\fR option is +not specified then the size of the first argument is determined according to +its data type using dwarf info from debuginfo file. In case of '\fBchar *\fR' +data type, the length of string pointed by '\fBchar *\fR' pointer is determined +with an upper limit of 1024. The \fBsize\fR can be specified in two forms 1. +a integer value as explained above (<\fISizeValue\fR>) and 2. a simple +expression in the form of <\fISymbol\fR>[.\fImember\fR[...]]] that results into +base type (integer) variable. +.PP +If the specified <\fISymbol\fR> is of type '\fBvoid *\fR', then user needs to +provide either \fBsize\fR or \fBnullify\fR option, otherwise the erase command +will not have any effect. +.PP +The \fBnullify\fR option only works if specified <\fISymbol\fR> is a pointer. +Instead of erasing data pointed by the specified pointer \fBnullify\fR erases +the pointer value and set it to '0' (NULL). Please note that by nullifying +the pointer values may affect the debug ability of created \fIDUMPFILE\fR. +Use the \fBnullify\fR option only when the size of data to be erased is not +known. \fBe.g.\fR data pointed by '\fBvoid *\fR'. +.PP +Let us look at the makedumpfile.conf file from the example below which was +configured to erase desired kernel data from the kernel module with name +\fBmymodule\fR. At line 1 and 3, the user has not specified size option while +erasing 'array_var' and 'mystruct1.name' symbols. Instead the user depends on +makedumpfile to automatically determine the sizes to be erased \fBi.e\fR +100 bytes for 'array_var' and 11 bytes for 'mystruct1.name'. At line 2, +while erasing the 'mystruct1.buffer' member the user has specified the size +value 25 against the actual size of 50. In this case the user specified +\fBsize\fR takes the precedence and makedumpfile erases only 25 bytes from +\'mystruct1.buffer'. At line 4, the size of the data pointed by \fBvoid *\fR +pointer 'mystruct1.addr' is unknown. Hence the \fBnullify\fR option has been +specified to reset the pointer value to NULL. At line 5, the +\'mystruct2.addr_size' is specified as \fBsize\fR argument to determine the +size of the data pointed by \fBvoid *\fR pointer 'mystruct2.addr'. +.br + +.B Example: +.PP +Assuming the following piece of code is from kernel module 'mymodule': +.br + +struct s1 { +.br + char *name; +.br + void *addr1; +.br + void *addr2; +.br + char buffer[50]; +.br +}; +.br +struct s2 { +.br + void *addr; +.br + long addr_size; +.br +}; +.br + +/* Global symbols */ +.br +char array_var[100]; +.br +struct s1 mystruct1; +.br +struct s2 *mystruct2; +.br + +int foo() +.br +{ +.br + ... +.br + s1.name = "Hello World"; +.br + ... +.br +} +.br + +\fBmakedumpfile.conf:\fR +.br +[mymodule] +.br +erase array_var +.br +erase mystruct1.buffer size 25 +.br +erase mystruct1.name +.br +erase mystruct1.addr1 nullify +.br +# Assuming addr2 points to 1024 bytes +.br +erase mystruct1.addr2 size 1K +.br +erase mystruct2.addr size mystruct2.addr_size +.br +.B EOF + +.SS Loop construct +.PP +A Loop construct allows the user to traverse the linked list or array elements +and erase the data contents referred by each element. + +.br +\fBfor\fR <\fIid\fR> \fBin\fR {<\fIArrayVar\fR> | +.br + <\fIStructVar\fR> \fBvia\fR <\fINextMember\fR> | +.br + <\fIListHeadVar\fR> \fBwithin\fR +<\fIStructName\fR>\fB:\fR<\fIListHeadMember\fR>} +.br + \fBerase\fR <\fIid\fR>[.\fIMemberExpression\fR] +[\fBsize\fR <\fISizeExpression\fR>|\fBnullify\fR] +.br + [\fBerase\fR <\fIid\fR>...] +.br + [...] +.br +\fBendfor\fR +.PP +where +.PP +.TP +<\fIid\fR> +Arbitrary name used to temporarily point to elements of the list. This is +also called iteration variable. +.TP +<\fIArrayVar\fR> +A simple expression in the form of <\fISymbol\fR>[.\fImember\fR[...]] that +results into an array variable. +.TP +<\fIStructVar\fR> +A simple expression in the form of <\fISymbol\fR>[.\fImember\fR[...]] that +results into a variable that points to a structure. +.TP +<\fINextMember\fR> +Member within <\fIStructVar\fR> that points to an object of same type that of +<\fIStructVar\fR>. +.TP +<\fIListHeadVar\fR> +A simple expression in the form of <\fISymbol\fR>[.\fImember\fR[...]] that +results into a variable of type struct list_head. +.TP +<\fIStructName\fR> +Name of the structure type that can be traversed using HEAD variable +<\fIListHeadVar\fR> and contains a member named <\fIListHeadMember\fR>. +.TP +<\fIListHeadMember\fR> +Name of a member in <\fIStructName\fR>, of type struct list_head. +.TP +<\fIMemberExpression\fR> +A simple expression in the form of [.\fImember\fR[...]] to specify a member +or component of an element in <\fIArrayVar\fR>, <\fIStructVar\fR> +or <\fIStructName\fR>. +.TP +<\fISizeExpression\fR> +Size value in the form of <\fISizeValue\fR>, <\fIid\fR>[.\fIMemberExpression\fR] +or <\fISymbol\fR>[.\fImember\fR[...]]. +.PP +The \fBfor\fR loop construct allows to iterate on list of elements in an array +or linked lists. Each element in the list is assigned to iteration variable +<\fIid\fR>. The type of the iteration variable is determined by that of the +list elements. The entry specified after '\fBin\fR' terminal is called LIST +entry. The LIST entry can be an array variable, structure variable/pointer or a +struct list_head type variable. The set of \fBerase\fR commands specified +between \fBfor\fR and \fBendfor\fR, will be executed for each element in the +LIST entry. +.PP +If the LIST entry specified is an array variable, then the loop will be +executed for each array element. The size of the array will be determined by +using dwarf information. +.PP +If the LIST entry specified is a structure variable/pointer, then a traversal +member (<\fINextMember\fR>) must be specified using '\fBvia\fR' terminal. The +\fBfor\fR loop will continue until the value of traversal member is NULL or +matches with address of the first node <\fIStructVar\fR> if it is a circular +linked list. +.PP +If the LIST entry is specified using a struct list_head type variable, then +\fBwithin\fR terminal must be used to specify the structure name +<\fIStructName\fR> that is surrounding to it along with the struct list_head +type member after '\fB:\fR' which is part of the linked list. In the erase +statement <\fIid\fR> then denotes the structure that the list_head is +contained in (ELEMENT_OF). +.PP +The below example illustrates how to use loop construct for traversing +Array, linked list via next member and list_head. + +.B Example: +.PP +Assuming following piece of code is from kernel module 'mymodule': +.br + +struct s1 { +.br + struct *next; +.br + struct list_head list; +.br + char private[100]; +.br + void *key; +.br + long key_size; +.br +}; +.br + +/* Global symbols */ +.br +struct s1 mystruct1; +.br +static LIST_HEAD(s1_list_head); +.br +struct s1 myarray[100]; +.br + +void foo() +.br +{ +.br + struct s1 *s1_ptr; +.br + ... +.br + ... +.br + s1_ptr = malloc(...); +.br + ... +.br + ... +.br + list_add(&s1_ptr->list, &s1_list_head); +.br + ... +.br +} +.br + +\fBmakedumpfile.conf:\fR +.br +[mymodule] +.br +# erase private fields from list starting with mystruct1 connected via +.br +# 'next' member: +.br +for mys1 in mystruct1 via next +.br + erase mys1.private +.br + erase mys1.key size mys1.key_size +.br +endfor +.br + +# erase private fields from list starting with list_head variable +.br +# s1_list_head. +.br +for mys1 in s1_list_head.next within s1:list +.br + erase mys1.private +.br + erase mys1.key size mys1.key_size +.br +endfor +.br + +# erase private fields from all elements of the array myarray: +.br +for mys1 in myarray +.br + erase mys1.private +.br + erase mys1.key size mys1.key_size +.br +endfor +.br +.B EOF +.PP +In the above example, the first \fBfor\fR construct traverses the linked list +through a specified structure variable \fBmystruct1\fR of type \fBstruct s1\fR. +The linked list can be traversed using '\fBnext\fR' member of \fBmystruct1\fR. +Hence a \fBvia\fR terminal has been used to specify the traversal member +name '\fBnext\fR'. +.PP +The second \fBfor\fR construct traverses the linked list through a specified +struct list_head variable \fBs1_list_head.next\fR. The global symbol +\fBs1_list_head\fR is a start address of the linked list and its \fBnext\fR +member points to the address of struct list_head type member '\fBlist\fR' from +\fBstruct s1\fR. Hence a \fBwithin\fR terminal is used to specify the structure +name '\fBs1\fR' that can be traversed using \fBs1_list_head.next\fR variable +along with the name of struct list_head type member '\fBlist\fR' which is part +of the linked list that starts from \fBs1_list_head\fR global symbol. +.PP +The third \fBfor\fR construct traverses the array elements specified through +a array variable \fBmyarray\fR. +.br +.SH SEE ALSO +.PP +makedumpfile(8) + diff -Nrup kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.h kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.h --- kexec-tools-2.0.0.orig/makedumpfile-1.3.5/makedumpfile.h 2012-01-02 17:03:57.000000000 +0800 +++ kexec-tools-2.0.0/makedumpfile-1.3.5/makedumpfile.h 2012-01-02 17:06:09.000000000 +0800 @@ -1243,6 +1243,7 @@ struct dwarf_info { #define TYPE_ARRAY 0x02 #define TYPE_PTR 0x04 #define TYPE_STRUCT 0x08 +#define TYPE_LIST_HEAD 0x10 extern struct dwarf_info dwarf_info; @@ -1265,18 +1266,25 @@ struct config_entry { unsigned long long sym_addr; /* Symbol address */ unsigned long long addr; /* 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. */ + struct config_entry *refer_to; struct config_entry *next; }; /* 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 /* @@ -1297,13 +1305,18 @@ struct filter_config { struct config { char *module_name; - struct config_entry *filter_symbol; - struct config_entry *size_symbol; + struct config_entry *iter_entry; + struct config_entry *list_entry; + int num_filter_symbols; + struct config_entry **filter_symbol; + struct config_entry **size_symbol; }; #define IS_KEYWORD(tkn) \ (!strcmp(tkn, "erase") || !strcmp(tkn, "size") || \ - !strcmp(tkn, "nullify")) + !strcmp(tkn, "nullify") || !strcmp(tkn, "for") || \ + !strcmp(tkn, "in") || !strcmp(tkn, "within") || \ + !strcmp(tkn, "endfor")) int readmem(int type_addr, unsigned long long addr, void *bufptr, size_t size); off_t paddr_to_offset(unsigned long long paddr);