#!/usr/bin/python3
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
from typing import Dict, Union, TypeVar, Iterable, Callable
from math import log, ceil
import crash
from crash.util import find_member_variant
from crash.util.symbols import Types, Symvals, TypeCallbacks
from crash.util.symbols import SymbolCallbacks, MinimalSymbolCallbacks
from crash.cache.syscache import config
from crash.exceptions import DelayedAttributeError
import gdb
#TODO debuginfo won't tell us, depends on version?
PAGE_MAPPING_ANON = 1
types = Types(['unsigned long', 'struct page', 'enum pageflags',
'enum zone_type', 'struct mem_section'])
symvals = Symvals(['mem_section', 'max_pfn'])
PageType = TypeVar('PageType', bound='Page')
[docs]class Page:
slab_cache_name = None
slab_page_name = None
compound_head_name = None
vmemmap_base = 0xffffea0000000000
vmemmap: gdb.Value
directmap_base = 0xffff880000000000
pageflags: Dict[str, int] = dict()
PG_tail = -1
PG_slab = -1
PG_lru = -1
setup_page_type_done = False
setup_pageflags_done = False
setup_pageflags_finish_done = False
ZONES_WIDTH = -1
NODES_WIDTH = -1
# TODO have arch provide this?
BITS_PER_LONG = -1
PAGE_SIZE = 4096
PAGE_SHIFT = 12
sparsemem = False
SECTION_SIZE_BITS = -1 # Depends on sparsemem=True
SECTIONS_PER_ROOT = -1 # Depends on SPARSEMEM_EXTREME
_is_tail: Callable[['Page'], bool]
_compound_head: Callable[['Page'], int]
[docs] @classmethod
def setup_page_type(cls, gdbtype: gdb.Type) -> None:
# TODO: should check config, but that failed to work on ppc64, hardcode
# 64k for now
if crash.current_target().arch.name() == "powerpc:common64":
cls.PAGE_SHIFT = 16
# also a config
cls.directmap_base = 0xc000000000000000
cls.sparsemem = True
cls.SECTION_SIZE_BITS = 24
cls.PAGE_SIZE = 1 << cls.PAGE_SHIFT
cls.slab_cache_name = find_member_variant(gdbtype, ['slab_cache', 'lru'])
cls.slab_page_name = find_member_variant(gdbtype, ['slab_page', 'lru'])
cls.compound_head_name = find_member_variant(gdbtype, ['compound_head', 'first_page'])
if not hasattr(cls, 'vmemmap'):
cls.vmemmap = gdb.Value(cls.vmemmap_base).cast(gdbtype.pointer())
cls.setup_page_type_done = True
if cls.setup_pageflags_done and not cls.setup_pageflags_finish_done:
cls.setup_pageflags_finish()
[docs] @classmethod
def setup_mem_section(cls, gdbtype: gdb.Type) -> None:
# TODO assumes SPARSEMEM_EXTREME
cls.SECTIONS_PER_ROOT = cls.PAGE_SIZE // gdbtype.sizeof
[docs] @classmethod
def pfn_to_page(cls, pfn: int) -> gdb.Value:
if cls.sparsemem:
section_nr = pfn >> (cls.SECTION_SIZE_BITS - cls.PAGE_SHIFT)
root_idx = section_nr // cls.SECTIONS_PER_ROOT
offset = section_nr & (cls.SECTIONS_PER_ROOT - 1)
section = symvals.mem_section[root_idx][offset]
pagemap = section["section_mem_map"] & ~3
return (pagemap.cast(types.page_type.pointer()) + pfn).dereference()
# pylint doesn't have the visibility it needs to evaluate this
# pylint: disable=unsubscriptable-object
return cls.vmemmap[pfn]
[docs] @classmethod
def setup_pageflags(cls, gdbtype: gdb.Type) -> None:
for field in gdbtype.fields():
cls.pageflags[field.name] = field.enumval
cls.setup_pageflags_done = True
if cls.setup_page_type_done and not cls.setup_pageflags_finish_done:
cls.setup_pageflags_finish()
cls.PG_slab = 1 << cls.pageflags['PG_slab']
cls.PG_lru = 1 << cls.pageflags['PG_lru']
[docs] @classmethod
def setup_vmemmap_base(cls, symbol: gdb.Symbol) -> None:
cls.vmemmap_base = int(symbol.value())
# setup_page_type() was first and used the hardcoded initial value,
# we have to update
cls.vmemmap = gdb.Value(cls.vmemmap_base).cast(types.page_type.pointer())
[docs] @classmethod
def setup_directmap_base(cls, symbol: gdb.Symbol) -> None:
cls.directmap_base = int(symbol.value())
[docs] @classmethod
def setup_zone_type(cls, gdbtype: gdb.Type) -> None:
max_nr_zones = gdbtype['__MAX_NR_ZONES'].enumval
cls.ZONES_WIDTH = int(ceil(log(max_nr_zones)))
[docs] @classmethod
# pylint: disable=unused-argument
def setup_nodes_width(cls, symbol: Union[gdb.Symbol, gdb.MinSymbol]) -> None:
"""
Detect NODES_WITH from the in-kernel config table
Args:
symbol: The ``kernel_config_data`` symbol or minimal symbol.
It is not used directly. It is used to determine whether
the config data should be available.
"""
# TODO: handle kernels with no space for nodes in page flags
try:
cls.NODES_WIDTH = int(config['NODES_SHIFT'])
except (KeyError, DelayedAttributeError):
# XXX
print("Unable to determine NODES_SHIFT from config, trying 8")
cls.NODES_WIDTH = 8
# piggyback on this callback because type callback doesn't seem to work
# for unsigned long
cls.BITS_PER_LONG = types.unsigned_long_type.sizeof * 8
[docs] @classmethod
def setup_pageflags_finish(cls) -> None:
cls.setup_pageflags_finish_done = True
cls._is_tail = cls.__is_tail_compound_head_bit
cls._compound_head = cls.__compound_head_uses_low_bit
if 'PG_tail' in cls.pageflags.keys():
cls.PG_tail = 1 << cls.pageflags['PG_tail']
cls._is_tail = cls.__is_tail_flag
if cls.compound_head_name == 'first_page':
cls._compound_head = cls.__compound_head_first_page
if cls.PG_tail == -1:
cls.PG_tail = 1 << cls.pageflags['PG_compound'] | 1 << cls.pageflags['PG_reclaim']
cls._is_tail = cls.__is_tail_flagcombo
[docs] @classmethod
def from_obj(cls, page: gdb.Value) -> 'Page':
pfn = (int(page.address) - Page.vmemmap_base) // types.page_type.sizeof
return Page(page, pfn)
[docs] @classmethod
def from_page_addr(cls, addr: int) -> 'Page':
page_ptr = gdb.Value(addr).cast(types.page_type.pointer())
return cls.from_obj(page_ptr.dereference())
def __init__(self, obj: gdb.Value, pfn: int) -> None:
self.gdb_obj = obj
self.pfn = pfn
self.flags = int(obj["flags"])
def __is_tail_flagcombo(self) -> bool:
return bool((self.flags & self.PG_tail) == self.PG_tail)
def __is_tail_flag(self) -> bool:
return bool(self.flags & self.PG_tail)
def __is_tail_compound_head_bit(self) -> bool:
return bool(self.gdb_obj['compound_head'] & 1)
[docs] def is_tail(self) -> bool:
return self._is_tail()
[docs] def is_slab(self) -> bool:
return bool(self.flags & self.PG_slab)
[docs] def is_lru(self) -> bool:
return bool(self.flags & self.PG_lru)
[docs] def is_anon(self) -> bool:
mapping = int(self.gdb_obj["mapping"])
return (mapping & PAGE_MAPPING_ANON) != 0
[docs] def get_slab_cache(self) -> gdb.Value:
if Page.slab_cache_name == "lru":
return self.gdb_obj["lru"]["next"]
return self.gdb_obj[Page.slab_cache_name]
[docs] def get_slab_page(self) -> gdb.Value:
if Page.slab_page_name == "lru":
return self.gdb_obj["lru"]["prev"]
return self.gdb_obj[Page.slab_page_name]
[docs] def get_nid(self) -> int:
return self.flags >> (self.BITS_PER_LONG - self.NODES_WIDTH)
[docs] def get_zid(self) -> int:
shift = self.BITS_PER_LONG - self.NODES_WIDTH - self.ZONES_WIDTH
zid = self.flags >> shift & ((1 << self.ZONES_WIDTH) - 1)
return zid
def __compound_head_first_page(self) -> int:
return int(self.gdb_obj['first_page'])
def __compound_head_uses_low_bit(self) -> int:
return int(self.gdb_obj['compound_head']) - 1
def __compound_head(self) -> int:
return self._compound_head()
[docs] def compound_head(self) -> 'Page':
if not self.is_tail():
return self
return self.__class__.from_page_addr(self.__compound_head())
type_cbs = TypeCallbacks([('struct page', Page.setup_page_type),
('enum pageflags', Page.setup_pageflags),
('enum zone_type', Page.setup_zone_type),
('struct mem_section', Page.setup_mem_section)])
msymbol_cbs = MinimalSymbolCallbacks([('kernel_config_data',
Page.setup_nodes_width)])
# TODO: this should better be generalized to some callback for
# "config is available" without refering to the symbol name here
symbol_cbs = SymbolCallbacks([('vmemmap_base', Page.setup_vmemmap_base),
('page_offset_base',
Page.setup_directmap_base)])
[docs]def pfn_to_page(pfn: int) -> 'Page':
return Page(Page.pfn_to_page(pfn), pfn)
[docs]def page_from_addr(addr: int) -> 'Page':
pfn = (addr - Page.directmap_base) // Page.PAGE_SIZE
return pfn_to_page(pfn)
[docs]def page_from_gdb_obj(gdb_obj: gdb.Value) -> 'Page':
pfn = (int(gdb_obj.address) - Page.vmemmap_base) // types.page_type.sizeof
return Page(gdb_obj, pfn)
[docs]def for_each_page() -> Iterable[gdb.Value]:
# TODO works only on x86?
max_pfn = int(symvals.max_pfn)
for pfn in range(max_pfn):
try:
yield Page.pfn_to_page(pfn)
except gdb.error:
# TODO: distinguish pfn_valid() and report failures for those?
pass