From a8e7fc1e580122fac032c80f789026dfe3f1fc4d Mon Sep 17 00:00:00 2001 From: Dave Anderson Date: Tue, 12 May 2015 15:17:54 -0400 Subject: [PATCH] Implemented a new "kmem -m" option that is similar to "kmem -p", but it allows the user to specify the page struct members to be displayed. The option takes a comma-separated list of one or more page struct members, which will be displayed following the page structure address. The "flags" member will always be expressed in hexadecimal format, and the "_count" and "_mapcount" members will always be expressed in decimal format. Otherwise, all other members will be displayed in hexadecimal format unless the current output radix is 10 and the member is a signed/unsigned integer. Members that are data structures may be specified by the data structure's member name, or expanded to specify a member of that data structure. For example, "-m lru" refers to a list_head data structure, in which case both the list_head.next and list_head.prev pointer values will be displayed; if "-m lru.next" is specified, just the list_head.next value will be displayed. (atomlin@redhat.com, anderson@redhat.com) --- defs.h | 1 + help.c | 105 ++++++++++++++++--- memory.c | 311 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 400 insertions(+), 17 deletions(-) diff --git a/defs.h b/defs.h index d2a8215..f21137d 100644 --- a/defs.h +++ b/defs.h @@ -4201,6 +4201,7 @@ enum type_code { /* * NOTE: the remainder of the type codes are not list or used here... */ + TYPE_CODE_BOOL = 20, #endif }; diff --git a/help.c b/help.c index 39ccbc9..43a3bf6 100644 --- a/help.c +++ b/help.c @@ -5532,23 +5532,15 @@ NULL char *help_kmem[] = { "kmem", "kernel memory", -"[-f|-F|-p|-c|-C|-i|-s|-S|-v|-V|-n|-z|-o|-h] [slab] [[-P] address]\n" -" [-g [flags]] [-I slab[,slab]]", +"[-f|-F|-c|-C|-i|-v|-V|-n|-z|-o|-h] [-p | -m member[,member]]\n" +" [[-s|-S] [slab] [-I slab[,slab]]] [-g [flags]] [[-P] address]]", " This command displays information about the use of kernel memory.\n", " -f displays the contents of the system free memory headers.", " also verifies that the page count equals nr_free_pages.", " -F same as -f, but also dumps all pages linked to that header.", -" -p displays basic information about each page in the system ", -" mem_map[] array.", " -c walks through the page_hash_table and verifies page_cache_size.", " -C same as -c, but also dumps all pages in the page_hash_table.", " -i displays general memory usage information", -" -s displays basic kmalloc() slab data.", -" -S displays all kmalloc() slab data, including all slab objects,", -" and whether each object is in use or is free. If CONFIG_SLUB,", -" slab data for each per-cpu slab is displayed, along with the", -" address of each kmem_cache_node, its count of full and partial", -" slabs, and a list of all tracked slabs.", " -v displays the mapped virtual memory regions allocated by vmalloc().", " -V displays the kernel vm_stat table if it exists, the cumulative", " page_states counter values if they exist, and/or the cumulative", @@ -5557,17 +5549,41 @@ char *help_kmem[] = { " -z displays per-zone memory statistics.", " -o displays each cpu's offset value that is added to per-cpu symbol", " values to translate them into kernel virtual addresses.", -" -g displays the enumerator value of all bits in the page structure's", -" \"flags\" field.", " -h display the address of hugepage hstate array entries, along with", " their hugepage size, total and free counts, and name.", -" flags when used with -g, translates all bits in this hexadecimal page", -" structure flags value into its enumerator values.", +" -p displays basic information about each page structure in the system", +" mem_map[] array, made up of the page struct address, its associated", +" physical address, the page.mapping, page.index, page._count and", +" page.flags fields.", +" -m member similar to -p, but displays page structure contents specified by", +" a comma-separated list of one or more struct page members. The", +" \"flags\" member will always be expressed in hexadecimal format, and", +" the \"_count\" and \"_mapcount\" members will always be expressed", +" in decimal format. Otherwise, all other members will be displayed", +" in hexadecimal format unless the output radix is 10 and the member", +" is a signed/unsigned integer. Members that are data structures may", +" be specified either by the data structure's member name, or expanded", +" to specify a member of the data structure. For example, \"-m lru\"", +" refers to a list_head data structure, and both the list_head.next", +" and list_head.prev pointer values will be displayed, whereas if", +" \"-m lru.next\" is specified, just the list_head.next value will", +" be displayed.", +" -s displays basic kmalloc() slab data.", +" -S displays all kmalloc() slab data, including all slab objects,", +" and whether each object is in use or is free. If CONFIG_SLUB,", +" slab data for each per-cpu slab is displayed, along with the", +" address of each kmem_cache_node, its count of full and partial", +" slabs, and a list of all tracked slabs.", " slab when used with -s or -S, limits the command to only the slab cache", " of name \"slab\". If the slab argument is \"list\", then", " all slab cache names and addresses are listed.", " -I slab when used with -s or -S, one or more slab cache names in a", " comma-separated list may be specified as slab caches to ignore.", +" their hugepage size, total and free counts, and name.", +" -g displays the enumerator value of all bits in the page structure's", +" \"flags\" field.", +" flags when used with -g, translates all bits in this hexadecimal page", +" structure flags value into its enumerator values.", " -P declares that the following address argument is a physical address.", " address when used without any flag, the address can be a kernel virtual,", " or physical address; a search is made through the symbol table,", @@ -5587,6 +5603,9 @@ char *help_kmem[] = { " address when used with -p, the address can be either a page pointer, a", " physical address, or a kernel virtual address; its basic mem_map", " page information is displayed.", +" address when used with -m, the address can be either a page pointer, a", +" physical address, or a kernel virtual address; the specified", +" members of the associated page struct are displayed.", " address when used with -c, the address must be a page pointer address;", " the page_hash_table entry containing the page is displayed.", " address when used with -l, the address must be a page pointer address;", @@ -5720,6 +5739,64 @@ char *help_kmem[] = { " f5c51440 22000 0 0 1 80 slab", " ...", " ", +" Display the \"page.lru\" list_head structure member in each page:\n", +" %s> kmem -m lru", +" PAGE lru ", +" ffffea0000000000 0000000000000000,0000000000000000 ", +" ffffea0000000040 ffffea0000000060,ffffea0000000060 ", +" ffffea0000000080 ffffea00000000a0,ffffea00000000a0 ", +" ffffea00000000c0 ffffea00000000e0,ffffea00000000e0 ", +" ffffea0000000100 ffffea0000000120,ffffea0000000120 ", +" ffffea0000000140 ffffea0000000160,ffffea0000000160 ", +" ffffea0000000180 ffffea00000001a0,ffffea00000001a0 ", +" ffffea00000001c0 ffffea00000001e0,ffffea00000001e0 ", +" ffffea0000000200 ffffea0000000220,ffffea0000000220 ", +" ffffea0000000240 ffffea0000000260,ffffea0000000260 ", +" ffffea0000000280 ffffea00000002a0,ffffea00000002a0 ", +" ffffea00000002c0 ffffea00000002e0,ffffea00000002e0 ", +" ffffea0000000300 ffffea0000000320,ffffea0000000320 ", +" ffffea0000000340 ffffea0000000360,ffffea0000000360 ", +" ffffea0000000380 ffffea00000003a0,ffffea00000003a0 ", +" ffffea00000003c0 ffffea00000003e0,ffffea00000003e0 ", +" ffffea0000000400 ffff88021e5e41e8,ffffea0000002020 ", +" ffffea0000000440 dead000000100100,dead000000200200 ", +" ffffea0000000480 dead000000100100,dead000000200200 ", +" ffffea00000004c0 dead000000100100,dead000000200200 ", +" ...", +" ", +" Find the two pages that link to the page at ffffea0001dafb20 ", +" via their page.lru list_head's next and prev pointers:\n", +" %s> kmem -m lru | grep ffffea0001dafb20", +" ffffea000006b500 ffffea0001dafb20,ffffea0001eb4520 ", +" ffffea0000127d80 ffffea000152b620,ffffea0001dafb20 ", +" ", +" Find all of the combined slab/page structures that are used by", +" the kmalloc-8192 slab cache:\n", +" %s> kmem -s kmalloc-8192", +" CACHE NAME OBJSIZE ALLOCATED TOTAL SLABS SSIZE", +" ffff880215802e00 kmalloc-8192 8192 65 80 20 32k", +" %s> kmem -m slab_cache | grep ffff880215802e00", +" ffffea0004117800 ffff880215802e00 ", +" ffffea00041ca600 ffff880215802e00 ", +" ffffea00044ab200 ffff880215802e00 ", +" ffffea0004524000 ffff880215802e00 ", +" ffffea0004591600 ffff880215802e00 ", +" ffffea00047eac00 ffff880215802e00 ", +" ffffea0004875800 ffff880215802e00 ", +" ffffea0008357a00 ffff880215802e00 ", +" ffffea0008362a00 ffff880215802e00 ", +" ffffea00083b9400 ffff880215802e00 ", +" ffffea00083c1000 ffff880215802e00 ", +" ffffea00083c1e00 ffff880215802e00 ", +" ffffea00083c2000 ffff880215802e00 ", +" ffffea00083c2a00 ffff880215802e00 ", +" ffffea00083d2000 ffff880215802e00 ", +" ffffea00083d3e00 ffff880215802e00 ", +" ffffea0008407c00 ffff880215802e00 ", +" ffffea000848ce00 ffff880215802e00 ", +" ffffea0008491800 ffff880215802e00 ", +" ffffea00084bf800 ffff880215802e00 ", +" ", " Use the commands above with a page pointer or a physical address argument:\n", " %s> kmem -f c40425b0", " NODE ", diff --git a/memory.c b/memory.c index 700cbf4..e815c9a 100644 --- a/memory.c +++ b/memory.c @@ -54,6 +54,8 @@ struct meminfo { /* general purpose memory information structure */ int current_cache_index; ulong found; ulong retval; + struct struct_member_data *page_member_cache; + ulong nr_members; char *ignore; int errors; int calls; @@ -134,6 +136,14 @@ struct searchinfo { static char *memtype_string(int, int); static char *error_handle_string(ulong); +static void collect_page_member_data(char *, struct meminfo *); +struct integer_data { + ulong value; + ulong bitfield_value; + struct struct_member_data *pmd; +}; +static int get_bitfield_data(struct integer_data *); +static int show_page_member_data(char *, ulong, struct meminfo *, char *); static void dump_mem_map(struct meminfo *); static void dump_mem_map_SPARSEMEM(struct meminfo *); static void fill_mem_map_cache(ulong, ulong, char *); @@ -4214,6 +4224,268 @@ tgid_quick_search(ulong tgid) return NULL; } +static void +collect_page_member_data(char *optlist, struct meminfo *mi) +{ + int i; + int members; + char buf[BUFSIZE]; + char *memberlist[MAXARGS]; + struct struct_member_data *page_member_cache, *pmd; + + if ((count_chars(optlist, ',')+1) > MAXARGS) + error(FATAL, "too many members in comma-separated list\n"); + + if ((LASTCHAR(optlist) == ',') || (LASTCHAR(optlist) == '.')) + error(FATAL, "invalid format: %s\n", optlist); + + strcpy(buf, optlist); + replace_string(optlist, ",", ' '); + + if (!(members = parse_line(optlist, memberlist))) + error(FATAL, "invalid page struct member list format: %s\n", buf); + + page_member_cache = (struct struct_member_data *) + GETBUF(sizeof(struct struct_member_data) * members); + + for (i = 0, pmd = page_member_cache; i < members; i++, pmd++) { + pmd->structure = "page"; + pmd->member = memberlist[i]; + + if (!fill_struct_member_data(pmd)) + error(FATAL, "invalid %s struct member: %s\n", + pmd->structure, pmd->member); + + if (CRASHDEBUG(1)) { + fprintf(fp, " structure: %s\n", pmd->structure); + fprintf(fp, " member: %s\n", pmd->member); + fprintf(fp, " type: %ld\n", pmd->type); + fprintf(fp, " unsigned_type: %ld\n", pmd->unsigned_type); + fprintf(fp, " length: %ld\n", pmd->length); + fprintf(fp, " offset: %ld\n", pmd->offset); + fprintf(fp, " bitpos: %ld\n", pmd->bitpos); + fprintf(fp, " bitsize: %ld%s", pmd->bitsize, + members > 1 ? "\n\n" : "\n"); + } + } + + mi->nr_members = members; + mi->page_member_cache = page_member_cache; +} + +static int +get_bitfield_data(struct integer_data *bd) +{ + int pos, size; + uint32_t tmpvalue32; + uint64_t tmpvalue64; + uint32_t mask32; + uint64_t mask64; + struct struct_member_data *pmd; + + pmd = bd->pmd; + pos = bd->pmd->bitpos; + size = bd->pmd->bitsize; + + if (pos == 0 && size == 0) { + bd->bitfield_value = bd->value; + return TRUE; + } + + switch (__BYTE_ORDER) + { + case __LITTLE_ENDIAN: + switch (pmd->length) + { + case 4: + tmpvalue32 = (uint32_t)bd->value; + tmpvalue32 >>= pos; + mask32 = (1 << size) - 1; + tmpvalue32 &= mask32; + bd->bitfield_value = (ulong)tmpvalue32; + break; + case 8: + tmpvalue64 = (uint64_t)bd->value; + tmpvalue64 >>= pos; + mask64 = (1UL << size) - 1; + tmpvalue64 &= mask64; + bd->bitfield_value = tmpvalue64; + break; + default: + return FALSE; + } + break; + + case __BIG_ENDIAN: + switch (pmd->length) + { + case 4: + tmpvalue32 = (uint32_t)bd->value; + tmpvalue32 <<= pos; + tmpvalue32 >>= (32-size); + mask32 = (1 << size) - 1; + tmpvalue32 &= mask32; + bd->bitfield_value = (ulong)tmpvalue32; + break; + case 8: + tmpvalue64 = (uint64_t)bd->value; + tmpvalue64 <<= pos; + tmpvalue64 >>= (64-size); + mask64 = (1UL << size) - 1; + tmpvalue64 &= mask64; + bd->bitfield_value = tmpvalue64; + break; + default: + return FALSE; + } + break; + } + + return TRUE; +} + +static int +show_page_member_data(char *pcache, ulong pp, struct meminfo *mi, char *outputbuffer) +{ + int bufferindex, i, c, cnt, radix, struct_intbuf[10]; + ulong longbuf, struct_longbuf[10]; + unsigned char boolbuf; + void *voidptr; + ushort shortbuf; + struct struct_member_data *pmd; + struct integer_data integer_data; + + bufferindex = 0; + pmd = mi->page_member_cache; + + bufferindex += sprintf(outputbuffer + bufferindex, "%lx ", pp); + + for (i = 0; i < mi->nr_members; pmd++, i++) { + + switch (pmd->type) + { + case TYPE_CODE_PTR: + voidptr = VOID_PTR(pcache + pmd->offset); + bufferindex += sprintf(outputbuffer + bufferindex, + VADDR_PRLEN == 8 ? "%08lx " : "%016lx ", (ulong)voidptr); + break; + + case TYPE_CODE_INT: + switch (pmd->length) + { + case 1: + integer_data.value = UCHAR(pcache + pmd->offset); + break; + case 2: + integer_data.value = USHORT(pcache + pmd->offset); + break; + case 4: + integer_data.value = UINT(pcache + pmd->offset); + break; + case 8: + if (BITS32()) + goto unsupported; + integer_data.value = ULONG(pcache + pmd->offset); + break; + default: + goto unsupported; + } + + integer_data.pmd = pmd; + if (get_bitfield_data(&integer_data)) + longbuf = integer_data.bitfield_value; + else + goto unsupported; + + if (STREQ(pmd->member, "flags")) + radix = 16; + else if (STRNEQ(pmd->member, "_count") || STRNEQ(pmd->member, "_mapcount")) + radix = 10; + else + radix = *gdb_output_radix; + + if (pmd->unsigned_type) { + if (pmd->length == sizeof(ulonglong)) + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%lu " : "%016lx ", longbuf); + else if (pmd->length == sizeof(int)) + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%u " : "%08x ", (uint)longbuf); + else if (pmd->length == sizeof(short)) { + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%u " : "%04x ", (ushort)longbuf); + } + else if (pmd->length == sizeof(char)) + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%u " : "%02x ", (unsigned char)longbuf); + } else { + if (pmd->length == sizeof(ulonglong)) + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%ld " : "%016lx", longbuf); + else if (pmd->length == sizeof(int)) + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%d " : "%08x ", (int)longbuf); + else if (pmd->length == sizeof(short)) + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%d " : "%04x ", (short)longbuf); + else if (pmd->length == sizeof(char)) + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%d " : "%02x ", (char)longbuf); + } + break; + + case TYPE_CODE_STRUCT: + if (STRNEQ(pmd->member, "_count") || STRNEQ(pmd->member, "_mapcount")) { + BCOPY(pcache+pmd->offset, (char *)&struct_intbuf[0], pmd->length); + bufferindex += sprintf(outputbuffer + bufferindex, + "%d ", struct_intbuf[0]); + } else if ((pmd->length % sizeof(long)) == 0) { + BCOPY(pcache+pmd->offset, (char *)&struct_longbuf[0], pmd->length); + cnt = pmd->length / sizeof(long); + for (c = 0; c < cnt; c++) { + bufferindex += sprintf(outputbuffer + bufferindex, + BITS32() ? "%08lx%s" : "%016lx%s", + struct_longbuf[c], (c+1) < cnt ? "," : ""); + } + bufferindex += sprintf(outputbuffer + bufferindex, " "); + } else if ((pmd->length % sizeof(int)) == 0) { + BCOPY(pcache+pmd->offset, (char *)&struct_intbuf[0], pmd->length); + cnt = pmd->length / sizeof(int); + for (c = 0; c < cnt; c++) { + bufferindex += sprintf(outputbuffer + bufferindex, + "%08x%s", struct_intbuf[c], + (c+1) < cnt ? "," : ""); + } + } else if (pmd->length == sizeof(short)) { + BCOPY(pcache+pmd->offset, (char *)&shortbuf, pmd->length); + bufferindex += sprintf(outputbuffer + bufferindex, + "%04x ", shortbuf); + } else + goto unsupported; + break; + + case TYPE_CODE_BOOL: + radix = *gdb_output_radix; + boolbuf = UCHAR(pcache + pmd->offset); + if (boolbuf <= 1) + bufferindex += sprintf(outputbuffer + bufferindex, "%s ", + boolbuf ? "true" : "false"); + else + bufferindex += sprintf(outputbuffer + bufferindex, + radix == 10 ? "%d" : "%x ", boolbuf); + break; + + default: +unsupported: + error(FATAL, "unsupported page member reference: %s.%s\n", + pmd->structure, pmd->member); + break; + } + } + + return bufferindex += sprintf(outputbuffer+bufferindex, "\n"); +} + /* * Fill in the task_mem_usage structure with the RSS, virtual memory size, * percent of physical memory being used, and the mm_struct address. @@ -4426,7 +4698,7 @@ cmd_kmem(void) BZERO(&value[0], sizeof(ulonglong)*MAXARGS); pc->curcmd_flags &= ~HEADER_PRINTED; - while ((c = getopt(argcnt, args, "gI:sSFfpvczCinl:L:PVoh")) != EOF) { + while ((c = getopt(argcnt, args, "gI:sSFfm:pvczCinl:L:PVoh")) != EOF) { switch(c) { case 'V': @@ -4481,6 +4753,11 @@ cmd_kmem(void) pflag = 1; break; + case 'm': + pflag = 1; + collect_page_member_data(optarg, &meminfo); + break; + case 'I': meminfo.ignore = optarg; break; @@ -5026,7 +5303,13 @@ dump_mem_map_SPARSEMEM(struct meminfo *mi) space(MINSPACE), mkstring(buf4, 8, CENTER|LJUST, "OFFSET"), space(MINSPACE-1)); - } else { + } else if (mi->nr_members) { + sprintf(hdr, "%s", mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE")); + for (i = 0; i < mi->nr_members; i++) + sprintf(&hdr[strlen(hdr)], " %s", + mi->page_member_cache[i].member); + strcat(hdr, "\n"); + } else { sprintf(hdr, "%s%s%s%s%s%s%sCNT FLAGS\n", mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE"), space(MINSPACE), @@ -5189,6 +5472,11 @@ dump_mem_map_SPARSEMEM(struct meminfo *mi) if (!done && (pg_spec || phys_spec)) continue; + + if (mi->nr_members) { + bufferindex += show_page_member_data(pcache, pp, mi, outputbuffer+bufferindex); + goto display_members; + } flags = ULONG(pcache + OFFSET(page_flags)); if (SIZE(page_flags) == 4) @@ -5365,6 +5653,7 @@ dump_mem_map_SPARSEMEM(struct meminfo *mi) bufferindex += sprintf(outputbuffer+bufferindex, "\n"); } +display_members: if (bufferindex > buffersize) { fprintf(fp, "%s", outputbuffer); bufferindex = 0; @@ -5412,6 +5701,8 @@ dump_mem_map_SPARSEMEM(struct meminfo *mi) break; } + if (mi->nr_members) + FREEBUF(mi->page_member_cache); FREEBUF(outputbuffer); FREEBUF(page_cache); } @@ -5502,7 +5793,13 @@ dump_mem_map(struct meminfo *mi) space(MINSPACE), mkstring(buf4, 8, CENTER|LJUST, "OFFSET"), space(MINSPACE-1)); - } else { + } else if (mi->nr_members) { + sprintf(hdr, "%s", mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE")); + for (i = 0; i < mi->nr_members; i++) + sprintf(&hdr[strlen(hdr)], " %s", + mi->page_member_cache[i].member); + strcat(hdr, "\n"); + } else { sprintf(hdr, "%s%s%s%s%s%s%sCNT FLAGS\n", mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE"), space(MINSPACE), @@ -5627,6 +5924,11 @@ dump_mem_map(struct meminfo *mi) if (!done && (pg_spec || phys_spec)) continue; + if (mi->nr_members) { + bufferindex += show_page_member_data(pcache, pp, mi, outputbuffer+bufferindex); + goto display_members; + } + flags = ULONG(pcache + OFFSET(page_flags)); if (SIZE(page_flags) == 4) flags &= 0xffffffff; @@ -5803,6 +6105,7 @@ dump_mem_map(struct meminfo *mi) bufferindex += sprintf(outputbuffer+bufferindex, "\n"); } +display_members: if (bufferindex > buffersize) { fprintf(fp, "%s", outputbuffer); bufferindex = 0; @@ -5850,6 +6153,8 @@ dump_mem_map(struct meminfo *mi) break; } + if (mi->nr_members) + FREEBUF(mi->page_member_cache); FREEBUF(outputbuffer); FREEBUF(page_cache); }