diff --git a/linux-ramdump-parser-v2/parsers/slabinfo.py b/linux-ramdump-parser-v2/parsers/slabinfo.py old mode 100755 new mode 100644 index 0f9a95a47ef65284798cb3c6568a742bbf580738..460c08ea5cc108e9dd060dc098c8a1d028180707 --- a/linux-ramdump-parser-v2/parsers/slabinfo.py +++ b/linux-ramdump-parser-v2/parsers/slabinfo.py @@ -1,4 +1,4 @@ -# Copyright (c) 2012-2015, The Linux Foundation. All rights reserved. +# Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License version 2 and @@ -9,11 +9,12 @@ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. -import re +import sys from mm import page_address, pfn_to_page from print_out import print_out_str from parser_util import register_parser, RamParser +import operator SLAB_RED_ZONE = 0x400 SLAB_POISON = 0x800 @@ -25,52 +26,80 @@ SLUB_RED_ACTIVE = 0xcc POISON_INUSE = 0x5a POISON_FREE = 0x6b POISON_END = 0xa5 +g_printfreeobjStack = False + class kmem_cache(object): def __init__(self, ramdump, addr): self.valid = False - offset = ramdump.field_offset( 'struct kmem_cache', 'flags') self.flags = ramdump.read_word(addr + offset) if self.flags is None: return - offset = ramdump.field_offset( 'struct kmem_cache', 'size') self.size = ramdump.read_int(addr + offset) if self.size is None: return - offset = ramdump.field_offset( 'struct kmem_cache', 'object_size') self.object_size = ramdump.read_int(addr + offset) if self.object_size is None: return - offset = ramdump.field_offset( 'struct kmem_cache', 'offset') self.offset = ramdump.read_int(addr + offset) if self.offset is None: return - - offset = ramdump.field_offset( - 'struct kmem_cache', 'max') - self.max = ramdump.read_word(addr + offset) - if self.max is None: - return - offset = ramdump.field_offset( 'struct kmem_cache', 'inuse') self.inuse = ramdump.read_int(addr + offset) if self.inuse is None: return - self.addr = addr self.valid = True + +class struct_member_offset(object): + def __init__(self, ramdump): + self.kmemcache_list = ramdump.field_offset( + 'struct kmem_cache', 'list') + self.kmemcache_name = ramdump.field_offset( + 'struct kmem_cache', 'name') + self.kmemcache_node = ramdump.field_offset( + 'struct kmem_cache', 'node') + self.kmemcache_cpu_page = ramdump.field_offset( + 'struct kmem_cache_cpu', 'page') + self.kmemcpucache_cpu_slab = ramdump.field_offset( + 'struct kmem_cache', 'cpu_slab') + self.kmemcachenode_partial = ramdump.field_offset( + 'struct kmem_cache_node', 'partial') + self.kmemcachenode_full = ramdump.field_offset( + 'struct kmem_cache_node', 'full') + self.page_lru = ramdump.field_offset( + 'struct page', 'lru') + self.page_flags = ramdump.field_offset( + 'struct page', 'flags') + self.page_mapcount = ramdump.field_offset( + 'struct page', '_mapcount') + self.track_addrs = ramdump.field_offset( + 'struct track', 'addrs') + self.page_freelist = ramdump.field_offset( + 'struct page', 'freelist') + self.sizeof_struct_track = ramdump.sizeof( + 'struct track') + self.sizeof_void_pointer = ramdump.sizeof( + "void *") + self.sizeof_unsignedlong = ramdump.sizeof( + "unsigned long") + + @register_parser('--slabinfo', 'print information about slabs', optional=True) class Slabinfo(RamParser): + g_allstacks = {} # hold callstack stack + g_index = 0 + g_offsetof = None def get_free_pointer(self, ramdump, s, obj): # just like validate_slab_slab! @@ -80,72 +109,278 @@ class Slabinfo(RamParser): return (p - addr) / slab.size def get_map(self, ramdump, slab, page, bitarray): - freelist_offset = self.ramdump.field_offset('struct page', 'freelist') - freelist = self.ramdump.read_word(page + freelist_offset) + freelist = self.ramdump.read_word(page + g_offsetof.page_freelist) p = freelist addr = page_address(self.ramdump, page) seen = [] if addr is None: return while p != 0 and p is not None and p not in seen: - idx = self.slab_index(self.ramdump, p, addr, slab) - if idx >= len(bitarray) or idx < 0: + index = self.slab_index(self.ramdump, p, addr, slab) + if index >= len(bitarray) or index < 0: return - bitarray[idx] = 1 + bitarray[index] = 1 seen.append(p) p = self.get_free_pointer(self.ramdump, slab, p) - def get_track(self, ramdump, slab, obj, track_type): - track_size = self.ramdump.sizeof('struct track') - slab_offset_offset = self.ramdump.field_offset( - 'struct kmem_cache', 'offset') - slab_inuse_offset = self.ramdump.field_offset( - 'struct kmem_cache', 'inuse') - slab_offset = self.ramdump.read_int(slab + slab_offset_offset) - slab_inuse = self.ramdump.read_int(slab + slab_inuse_offset) - if slab_offset != 0: - p = obj + slab_offset + self.ramdump.sizeof("void *") + def get_track(self, ramdump, slab, obj, track_type): + track_size = g_offsetof.sizeof_struct_track + if slab.offset != 0: + p = obj + slab.offset + g_offsetof.sizeof_void_pointer else: - p = obj + slab_inuse + p = obj + slab.inuse return p + track_type * track_size + def extract_callstack(self, ramdump, a, stack, out_file): + for a in stack: + look = ramdump.unwind_lookup(a) + if look is None: + out_file.write("look is None") + continue + symname, offset = look + out_file.write( + ' [<{0:x}>] {1}+0x{2:x}\n'.format(a, symname, offset)) + return + def print_track(self, ramdump, slab, obj, track_type, out_file): - p = self.get_track(self.ramdump, slab, obj, track_type) - track_addrs_offset = self.ramdump.field_offset('struct track', 'addrs') + stack = [] + stackstr = "" + p = self.get_track(ramdump, slab, obj, track_type) + track_addrs_offset = g_offsetof.track_addrs start = p + track_addrs_offset - pointer_size = self.ramdump.sizeof("unsigned long") - if track_type == 0: - out_file.write(' ALLOC STACK\n') - else: - out_file.write(' FREE STACK\n') + pointer_size = g_offsetof.sizeof_unsignedlong for i in range(0, 16): a = self.ramdump.read_word(start + pointer_size * i) if a == 0: - break - look = self.ramdump.unwind_lookup(a) - if look is None: + continue + stack += [a] + stackstr += str(a) + stackstr_len = len(stackstr) + if stackstr_len == 0: + return + try: + self.g_allstacks[stackstr][0] += 1 + if self.g_allstacks[stackstr][0] > 1: return - symname, offset = look - out_file.write( - ' [<{0:x}>] {1}+0x{2:x}\n'.format(a, symname, offset)) - out_file.write('\n') - - def get_nobjects(self, ramdump, page): - if re.search('3\.0\.\d', self.ramdump.version) is not None: - n_objects_offset = self.ramdump.field_offset( - 'struct page', 'objects') - n_objects = self.ramdump.read_halfword(page + n_objects_offset) - return n_objects + self.extract_callstack(self.ramdump, a, stack, out_file) + except KeyError: + if g_printfreeobjStack is False: + if track_type != 0: + # if free object and g_printfreeobjStack is False, + # ignore it for printing its call stack + return + if track_type == 1: + out_file.write( + "FREE Call stack index:{0}".format( + self.g_index)) + else: + out_file.write( + "ALLOCATED Call stack index:{0}".format( + self.g_index)) + self.extract_callstack(self.ramdump, a, stack, out_file) + self.g_allstacks[stackstr] = [1, self.g_index] + self.g_index += 1 + out_file.write('\n') + + def print_slab( + self, ramdump, slab, page, + out_file, map_fn, out_slabs_addrs): + + page_addr = page_address(ramdump, page) + p = page_addr + if page is None: + return + n_objects = self.ramdump.read_word(page + g_offsetof.page_mapcount) + n_objects = (n_objects >> 16) & 0x00007FFF + if n_objects is None: + return + bitarray = [0] * n_objects + addr = page_address(self.ramdump, page) + self.get_map(self.ramdump, slab, page, bitarray) + while p < page_addr + (n_objects * slab.size): + bitidx = self.slab_index(self.ramdump, p, addr, slab) + if bitidx >= n_objects or bitidx < 0: + return + map_fn( + ramdump, p, bitarray[bitidx], slab, + page, out_file, out_slabs_addrs) + p = p + slab.size + + def printsummary(self, slabs_output_summary): + sorted_val = sorted( + self.g_allstacks.items(), + key=operator.itemgetter(1), reverse=True) + for key, value in sorted_val: + slabs_output_summary.write( + " stack index:{0} frequency:{1}\n".format(value[1], value[0])) + + def print_slab_page_info( + self, ramdump, slab_obj, slab_node, start, + out_file, map_fn, out_slabs_addrs): + page = self.ramdump.read_word(start) + if page == 0: + return + seen = [] + max_pfn_addr = self.ramdump.address_of('max_pfn') + max_pfn = self.ramdump.read_word(max_pfn_addr) + max_page = pfn_to_page(ramdump, max_pfn) + while page != start: + if page is None: + return + if page in seen: + return + if page > max_page: + return + seen.append(page) + page = page - g_offsetof.page_lru + self.print_slab( + self.ramdump, slab_obj, page, out_file, map_fn, + out_slabs_addrs) + page = self.ramdump.read_word(page + g_offsetof.page_lru) + + def print_per_cpu_slab_info( + self, ramdump, slab, slab_node, start, out_file, map_fn): + page = self.ramdump.read_word(start) + if page == 0: + return + if page is None: + return + page_addr = page_address(self.ramdump, page) + self.print_slab( + self.ramdump, page_addr, slab, page, out_file, map_fn) + + def print_all_objects( + self, ramdump, p, free, slab, page, out_file, out_slabs_addrs): + + if free: + out_slabs_addrs.write( + '\n Object {0:x}-{1:x} FREE'.format( + p, p + slab.size)) + else: + out_slabs_addrs.write( + '\n Object {0:x}-{1:x} ALLOCATED'.format( + p, p + slab.size)) + if self.ramdump.is_config_defined('CONFIG_SLUB_DEBUG_ON'): + if g_printfreeobjStack is True: + self.print_track(ramdump, slab, p, 0, out_file) + self.print_track(ramdump, slab, p, 1, out_file) + else: + self.print_track(ramdump, slab, p, free, out_file) + + def print_check_poison(self, p, free, slab, page, out_file): + if free: + self.check_object(slab, page, p, SLUB_RED_INACTIVE, out_file) else: - # The objects field is now a bit field. This confuses GDB as it thinks the - # offset is always 0. Work around this for now - map_count_offset = self.ramdump.field_offset( - 'struct page', '_mapcount') - count = self.ramdump.read_int(page + map_count_offset) - if count is None: - return None - n_objects = (count >> 16) & 0xFFFF - return n_objects + self.check_object(slab, page, p, SLUB_RED_ACTIVE, out_file) + + def initializeOffset(self): + global g_offsetof + g_offsetof = struct_member_offset(self.ramdump) + + # based on validate_slab_cache. Currently assuming there + # is only one numa node in the system because the code to + # do that correctly is a big pain. This will + # need to be changed if we ever do NUMA properly. + def validate_slab_cache(self, slab_out, input_slabname, map_fn): + slab_name_found = False + original_slab = self.ramdump.address_of('slab_caches') + cpu_present_bits_addr = self.ramdump.address_of('cpu_present_bits') + cpu_present_bits = self.ramdump.read_word(cpu_present_bits_addr) + cpus = bin(cpu_present_bits).count('1') + offsetof = struct_member_offset(self.ramdump) + self.initializeOffset() + slab_list_offset = g_offsetof.kmemcache_list + slab_name_offset = g_offsetof.kmemcache_name + slab_node_offset = g_offsetof.kmemcache_node + cpu_slab_offset = g_offsetof.kmemcpucache_cpu_slab + slab_partial_offset = g_offsetof.kmemcachenode_partial + slab_full_offset = g_offsetof.kmemcachenode_full + slab = self.ramdump.read_word(original_slab) + slabs_output_summary = self.ramdump.open_file('slabs_output.txt') + out_slabs_addrs = self.ramdump.open_file('out_slabs_addrs.txt') + while slab != original_slab: + slab = slab - slab_list_offset + slab_obj = kmem_cache(self.ramdump, slab) + if not slab_obj.valid: + slab_out.write( + 'Invalid slab object {0:x}'.format(slab)) + slab = self.ramdump.read_word(slab + slab_list_offset) + continue + slab_name_addr = self.ramdump.read_word( + slab + slab_name_offset) + slab_name = self.ramdump.read_cstring( + slab_name_addr, 48) + if input_slabname is not None: + if input_slabname != slab_name: + slab = self.ramdump.read_word(slab + slab_list_offset) + continue + else: + slab_name_found = True + # actually an array but again, no numa + slab_node_addr = self.ramdump.read_word( + slab + slab_node_offset) + slab_node = self.ramdump.read_word( + slab_node_addr) + print_out_str( + '\nExtracting slab details of : {0}'.format( + slab_name)) + cpu_slab_addr = self.ramdump.read_word( + slab + cpu_slab_offset) + nr_total_objects = self.ramdump.read_structure_field( + slab_node_addr, + 'struct kmem_cache_node', 'total_objects') + slab_out.write( + '\n {0:x} slab {1} {2:x} total objects: {3}\n'.format( + slab, slab_name, slab_node_addr, nr_total_objects)) + + self.print_slab_page_info( + self.ramdump, slab_obj, slab_node, + slab_node_addr + slab_partial_offset, + slab_out, map_fn, out_slabs_addrs) + + if self.ramdump.is_config_defined('CONFIG_SLUB_DEBUG'): + self.print_slab_page_info( + self.ramdump, slab_obj, slab_node, + slab_node_addr + slab_full_offset, + slab_out, map_fn, out_slabs_addrs) + + # per cpu slab + for i in range(0, cpus): + cpu_slabn_addr = self.ramdump.read_word( + cpu_slab_addr, cpu=i) + if cpu_slabn_addr == 0 or None: + break + self.print_per_cpu_slab_info( + self.ramdump, slab_obj, + slab_node, cpu_slabn_addr + offsetof.cpu_cache_page_offset, + slab_out, map_fn) + + self.printsummary(slabs_output_summary) + self.g_allstacks.clear() + if slab_name_found is True: + break + slab = self.ramdump.read_word(slab + slab_list_offset) + out_slabs_addrs.close() + slabs_output_summary.close() + + def parse(self): + global g_printfreeobjStack + slabname = None + for arg in sys.argv: + if 'slabname=' in arg: + k, slabname = arg.split('=') + if 'freeobj' in arg: + g_printfreeobjStack = True + slab_out = self.ramdump.open_file('slabs.txt') + self.validate_slab_cache(slab_out, slabname, self.print_all_objects) + slab_out.close() + + +@register_parser('--slabpoison', 'check slab poison', optional=True) +class Slabpoison(Slabinfo): + """Note that this will NOT find any slab errors which are printed out by the + kernel, because the slab object is removed from the freelist while being + processed""" def print_section(self, text, addr, length, out_file): out_file.write('{}\n'.format(text)) @@ -250,138 +485,6 @@ class Slabinfo(RamParser): # check_pad_bytes cleans up on its own. self.check_pad_bytes(s, page, p, out_file) - - def print_slab(self, ramdump, slab_start, slab, page, out_file, map_fn): - p = slab_start - if page is None: - return - n_objects = self.get_nobjects(self.ramdump, page) - if n_objects is None: - return - bitarray = [0] * slab.max - addr = page_address(self.ramdump, page) - self.get_map(self.ramdump, slab, page, bitarray) - while p < slab_start + n_objects * slab.size: - idx = self.slab_index(self.ramdump, p, addr, slab) - bitidx = self.slab_index(self.ramdump, p, addr, slab) - if bitidx >= len(bitarray) or bitidx < 0: - return - map_fn(p, bitarray[bitidx], slab, page, out_file) - p = p + slab.size - - def print_slab_page_info(self, ramdump, slab, slab_node, start, out_file, map_fn): - page = self.ramdump.read_word(start) - seen = [] - if page == 0: - return - slab_lru_offset = self.ramdump.field_offset('struct page', 'lru') - page_flags_offset = self.ramdump.field_offset('struct page', 'flags') - max_pfn_addr = self.ramdump.address_of('max_pfn') - max_pfn = self.ramdump.read_word(max_pfn_addr) - max_page = pfn_to_page(ramdump, max_pfn) - while page != start: - if page is None: - return - if page in seen: - return - if page > max_page: - return - seen.append(page) - page = page - slab_lru_offset - page_flags = self.ramdump.read_word(page + page_flags_offset) - page_addr = page_address(self.ramdump, page) - self.print_slab(self.ramdump, page_addr, slab, page, out_file, map_fn) - page = self.ramdump.read_word(page + slab_lru_offset) - - def print_per_cpu_slab_info(self, ramdump, slab, slab_node, start, out_file, map_fn): - page = self.ramdump.read_word(start) - if page == 0: - return - page_flags_offset = self.ramdump.field_offset('struct page', 'flags') - if page is None: - return - page_flags = self.ramdump.read_word(page + page_flags_offset) - page_addr = page_address(self.ramdump, page) - self.print_slab(self.ramdump, page_addr, slab, page, out_file, map_fn) - - def print_all_objects(self, p, free, slab, page, out_file): - if free: - out_file.write( - ' Object {0:x}-{1:x} FREE\n'.format(p, p + slab.size)) - else: - out_file.write( - ' Object {0:x}-{1:x} ALLOCATED\n'.format(p, p + slab.size)) - if self.ramdump.is_config_defined('CONFIG_SLUB_DEBUG_ON'): - self.print_track(self.ramdump, slab, p, 0, out_file) - self.print_track(self.ramdump, slab, p, 1, out_file) - - def print_check_poison(self, p, free, slab, page, out_file): - if free: - self.check_object(slab, page, p, SLUB_RED_INACTIVE, out_file) - else: - self.check_object(slab, page, p, SLUB_RED_ACTIVE, out_file) - - # based on validate_slab_cache. Currently assuming there is only one numa node - # in the system because the code to do that correctly is a big pain. This will - # need to be changed if we ever do NUMA properly. - def validate_slab_cache(self, slab_out, map_fn): - original_slab = self.ramdump.address_of('slab_caches') - cpu_present_bits_addr = self.ramdump.address_of('cpu_present_bits') - cpu_present_bits = self.ramdump.read_word(cpu_present_bits_addr) - cpus = bin(cpu_present_bits).count('1') - slab_list_offset = self.ramdump.field_offset( - 'struct kmem_cache', 'list') - slab_name_offset = self.ramdump.field_offset( - 'struct kmem_cache', 'name') - slab_node_offset = self.ramdump.field_offset( - 'struct kmem_cache', 'node') - cpu_cache_page_offset = self.ramdump.field_offset( - 'struct kmem_cache_cpu', 'page') - cpu_slab_offset = self.ramdump.field_offset( - 'struct kmem_cache', 'cpu_slab') - slab_partial_offset = self.ramdump.field_offset( - 'struct kmem_cache_node', 'partial') - slab = self.ramdump.read_word(original_slab) - while slab != original_slab: - slab = slab - slab_list_offset - slab_obj = kmem_cache(self.ramdump, slab) - if not slab_obj.valid: - continue - slab_name_addr = self.ramdump.read_word(slab + slab_name_offset) - # actually an array but again, no numa - slab_node_addr = self.ramdump.read_word(slab + slab_node_offset) - slab_node = self.ramdump.read_word(slab_node_addr) - slab_name = self.ramdump.read_cstring(slab_name_addr, 48) - cpu_slab_addr = self.ramdump.read_word(slab + cpu_slab_offset) - print_out_str('Parsing slab {0}'.format(slab_name)) - slab_out.write( - '{0:x} slab {1} {2:x}\n'.format(slab, slab_name, slab_node_addr)) - self.print_slab_page_info( - self.ramdump, slab_obj, slab_node, slab_node_addr + slab_partial_offset, slab_out, map_fn) - if self.ramdump.is_config_defined('CONFIG_SLUB_DEBUG'): - slab_full_offset = self.ramdump.field_offset( - 'struct kmem_cache_node', 'full') - self.print_slab_page_info( - self.ramdump, slab_obj, slab_node, slab_node_addr + slab_full_offset, slab_out, map_fn) - - for i in range(0, cpus): - cpu_slabn_addr = self.ramdump.read_word(cpu_slab_addr, cpu=i) - self.print_per_cpu_slab_info( - self.ramdump, slab_obj, slab_node, cpu_slabn_addr + cpu_cache_page_offset, slab_out, map_fn) - - slab = self.ramdump.read_word(slab + slab_list_offset) - - def parse(self): - slab_out = self.ramdump.open_file('slabs.txt') - self.validate_slab_cache(slab_out, self.print_all_objects) - print_out_str('---wrote slab information to slabs.txt') - -@register_parser('--slabpoison', 'check slab poison', optional=True) -class Slabpoison(Slabinfo): - """Note that this will NOT find any slab errors which are printed out by the - kernel, because the slab object is removed from the freelist while being - processed""" - # since slabs are relatively "packed", caching has a large # performance benefit def read_byte_array(self, addr, size): @@ -406,5 +509,5 @@ class Slabpoison(Slabinfo): self.cache = None self.cache_addr = None slab_out = self.ramdump.open_file('slabpoison.txt') - self.validate_slab_cache(slab_out, self.print_check_poison) + self.validate_slab_cache(slab_out, None, self.print_check_poison) print_out_str('---wrote slab information to slabpoison.txt')