From: Mahesh Salgaonkar <mahesh@xxxxxxxxxxxxxxxxxx> 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 <id> in {<ArrayVar> | <StructVar> via <NextMember> | <ListHeadVar> within <StructName>:<ListHeadMember>} erase <id>[.MemberExpression] [size <SizeExpression>|nullify] [erase <id> ...] [...] endfor Updated filter.conf(8) man page that describes the syntax for loop construct. Signed-off-by: Mahesh Salgaonkar <mahesh at linux.vnet.ibm.com> Signed-off-by: Prerna Saxena <prerna at linux.vnet.ibm.com> --- makedumpfile.c | 533 ++++++++++++++++++++++++++++++++++++++++++++++++++- makedumpfile.conf | 67 ++++++ makedumpfile.conf.8 | 212 ++++++++++++++++++++ makedumpfile.h | 19 ++ 4 files changed, 814 insertions(+), 17 deletions(-) diff --git a/makedumpfile.c b/makedumpfile.c index 1c7237a..a616bae 100644 --- a/makedumpfile.c +++ b/makedumpfile.c @@ -7788,13 +7788,20 @@ free_config_entry(struct config_entry *ce) 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); } } @@ -7851,7 +7858,16 @@ create_config_entry(const char *token, unsigned short flag, int line) /* 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) { @@ -8050,6 +8066,7 @@ get_config_token(char *expected_token, unsigned char flag, int *line, 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)) { @@ -8057,12 +8074,19 @@ read_size_entry(struct config *config, int line) " '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; } @@ -8076,6 +8100,7 @@ read_size_entry(struct config *config, int line) 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)) { @@ -8083,15 +8108,41 @@ read_filter_entry(struct config *config, int line) " '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)) @@ -8100,6 +8151,150 @@ read_filter_entry(struct config *config, int line) 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 = 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 @@ -8125,6 +8320,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, @@ -8193,6 +8395,24 @@ resolve_config_entry(struct config_entry *ce, unsigned long long base_addr, 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, @@ -8216,9 +8436,58 @@ resolve_config_entry(struct config_entry *ce, unsigned long long base_addr, 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. */ @@ -8227,7 +8496,7 @@ resolve_config_entry(struct config_entry *ce, unsigned long long base_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. + * by 'void *' pointer or nullify option. */ if (!strcmp(ce->type_name, "void")) ce->size = 0; @@ -8286,6 +8555,8 @@ resolve_config_entry(struct config_entry *ce, unsigned long long base_addr, free(val); } ce->flag |= ENTRY_RESOLVED; + if (ce->next) + ce->next->flag &= ~ENTRY_RESOLVED; return TRUE; } @@ -8294,18 +8565,87 @@ get_config_symbol_addr(struct config_entry *ce, 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; @@ -8340,6 +8680,48 @@ get_config_symbol_size(struct config_entry *ce, } } +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 @@ -8420,6 +8802,107 @@ update_filter_info(struct config_entry *filter_symbol, 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 @@ -8427,7 +8910,35 @@ update_filter_info(struct config_entry *filter_symbol, 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 --git a/makedumpfile.conf b/makedumpfile.conf index 3d47e25..03e604a 100644 --- a/makedumpfile.conf +++ b/makedumpfile.conf @@ -80,3 +80,70 @@ ## erase cred_jar.array ## erase vmlist.addr nullify ## +## +## - To filter kernel data referred through Array/list_head Symbol +## ================================================================= +## Syntax: +## for <id> in { <ArrayVar> | +## <StructVar> via <NextMember> | +## <ListHeadVar> within <StructName>:<ListHeadMember> } +## erase <id>[.MemberExpression] [size <SizeExpression>|nullify] +## [erase <id> ...] +## [...] +## endfor +## +## where +## <id> +## Arbitrary name used to temporarily point to elements of the +## list. Referred as iteration variable. +## <ArrayVar> +## A simple expression in the form of <Symbol>[.member[...]] that +## results into an array variable. +## <StructVar> +## A simple expression in the form of <Symbol>[.member[...]] that +## results into a variable that points to a structure. +## <NextMember> +## Member within <StructVar> that points to an object of same +## type that of <StructVar>. +## <ListHeadVar> +## A simple expression in the form of <Symbol>[.member[...]] that +## results into a variable of type struct list_head. +## <StructName> +## Name of the structure type that can be traversed using +## HEAD variable <ListHeadVar> and contains a member named +## <ListHeadMember>. +## <ListHeadMember> +## Name of a member in <StructName>, of type struct list_head. +## <MemberExpression> +## A simple expression in the form of [.member[...]] to specify a +## member or component of a member in <ArrayVar>, <StructVar> or +## <StructName>. +## <SizeExpression> +## One of the following: +## - An integer value. +## - <Symbol>[.member[...]] +## - <id>[.MemberExpresion] +## +## The <ArrayVar>, <StructVar> and <ListHeadVar> is also referred as LIST +## entry +## +## Filter out the specified size of the data accessible through LIST entries. +## e.g. +## [vmlinux] +## # Traversing <ListHeadVar> +## for m in modules.next within module:list +## erase m.holders_dir.name +## endfor +## # Traversing <ArrayVar> +## 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 --git a/makedumpfile.conf.8 b/makedumpfile.conf.8 index 0a7d22a..ef0e86a 100644 --- a/makedumpfile.conf.8 +++ b/makedumpfile.conf.8 @@ -48,17 +48,21 @@ will skip the section with a warning message. .SH FILTER COMMANDS .SS filter command .PP -A filter command is an erase command. Each erase command must start with a new -line. Each filter command describes data in the dump to be erased. +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> +<\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 @@ -207,6 +211,208 @@ 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 --git a/makedumpfile.h b/makedumpfile.h index 94f3521..1c61edb 100644 --- a/makedumpfile.h +++ b/makedumpfile.h @@ -1275,6 +1275,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; @@ -1297,18 +1298,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 /* @@ -1329,13 +1337,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);