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)
This commit is contained in:
Dave Anderson 2015-05-12 15:17:54 -04:00
parent c50fad9be1
commit a8e7fc1e58
3 changed files with 400 additions and 17 deletions

1
defs.h
View File

@ -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
};

105
help.c
View File

@ -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 ",

311
memory.c
View File

@ -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);
}