521 lines
19 KiB
Python
Executable File
521 lines
19 KiB
Python
Executable File
# Copyright (c) 2013-2021, The Linux Foundation. All rights reserved.
|
|
# Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. 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
|
|
# only version 2 as published by the Free Software Foundation.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
|
|
import bitops
|
|
from ctypes import *
|
|
import math
|
|
|
|
|
|
def page_buddy(ramdump, page):
|
|
if ramdump.kernel_version >= (4, 19, 0):
|
|
# Check if the PG_buddy bit is unset in the page->page_type field
|
|
# Initial value of page_type is -1, hence cleared bit indicates type
|
|
page_type = ramdump.read_structure_field(page, 'struct page', 'page_type')
|
|
return (page_type & 0xf0000080) == 0xf0000000
|
|
|
|
else:
|
|
mapcount_offset = ramdump.field_offset('struct page', '_mapcount')
|
|
val = ramdump.read_int(page + mapcount_offset)
|
|
# -128 is the magic for in the buddy allocator
|
|
return val == 0xffffff80
|
|
|
|
def page_count(ramdump, page):
|
|
"""Commit: 0139aa7b7fa12ceef095d99dc36606a5b10ab83a
|
|
mm: rename _count, field of the struct page, to _refcount"""
|
|
if (ramdump.kernel_version < (4, 6, 0)):
|
|
count = ramdump.read_structure_field(page, 'struct page',
|
|
'_count.counter')
|
|
else:
|
|
count = ramdump.read_structure_field(page, 'struct page',
|
|
'_refcount.counter')
|
|
return count
|
|
|
|
|
|
def page_ref_count(ramdump, page):
|
|
return page_count(ramdump, page)
|
|
|
|
def get_debug_flags(ramdump, page):
|
|
debug_flag_offset = ramdump.field_offset('struct page', 'debug_flags')
|
|
flagval = ramdump.read_word(page + debug_flag_offset)
|
|
return flagval
|
|
|
|
|
|
def page_zonenum(page_flags):
|
|
# save this in a variable somewhere...
|
|
return (page_flags >> 26) & 3
|
|
|
|
|
|
def page_to_nid(page_flags):
|
|
return 0
|
|
|
|
|
|
def page_zone(ramdump, page):
|
|
contig_page_data = ramdump.address_of('contig_page_data')
|
|
node_zones_offset = ramdump.field_offset(
|
|
'struct pglist_data', 'node_zones')
|
|
page_flags_offset = ramdump.field_offset('struct page', 'flags')
|
|
zone_size = ramdump.sizeof('struct zone')
|
|
page_flags = ramdump.read_word(page + page_flags_offset)
|
|
if page_flags is None:
|
|
return None
|
|
zone = contig_page_data + node_zones_offset + \
|
|
(page_zonenum(page_flags) * zone_size)
|
|
return zone
|
|
|
|
|
|
def zone_is_highmem(ramdump, zone):
|
|
if not ramdump.is_config_defined('CONFIG_HIGHMEM'):
|
|
return False
|
|
|
|
if zone is None:
|
|
return False
|
|
# not at all how linux does it but it works for our purposes...
|
|
zone_name_offset = ramdump.field_offset('struct zone', 'name')
|
|
zone_name_addr = ramdump.read_word(zone + zone_name_offset)
|
|
if zone_name_addr is None:
|
|
return False
|
|
zone_name = ramdump.read_cstring(zone_name_addr, 48)
|
|
if zone_name is None:
|
|
# XXX do something?
|
|
return False
|
|
if zone_name == 'HighMem':
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
|
|
def hash32(val, bits):
|
|
chash = c_uint(val * 0x9e370001).value
|
|
return chash >> (32 - bits)
|
|
|
|
|
|
def page_slot(ramdump, page):
|
|
hashed = hash32(page, 7)
|
|
htable = ramdump.address_of('page_address_htable')
|
|
htable_size = ramdump.sizeof('page_address_htable[0]')
|
|
return htable + htable_size * hashed
|
|
|
|
|
|
def page_to_section(page_flags):
|
|
# again savefn8n variable
|
|
return (page_flags >> 28) & 0xF
|
|
|
|
|
|
def section_mem_map_addr(ramdump, section):
|
|
map_offset = ramdump.field_offset('struct mem_section', 'section_mem_map')
|
|
result = ramdump.read_word(section + map_offset)
|
|
return result & ~((1 << 2) - 1)
|
|
|
|
|
|
def pfn_to_section(ramdump, pfn):
|
|
return ramdump.mm.sparsemem.pfn_to_section(pfn)
|
|
|
|
|
|
def pfn_to_page_sparse(ramdump, pfn):
|
|
sec = pfn_to_section(ramdump, pfn)
|
|
sizeof_page = ramdump.sizeof('struct page')
|
|
return section_mem_map_addr(ramdump, sec) + pfn * sizeof_page
|
|
|
|
|
|
def page_to_pfn_sparse(ramdump, page):
|
|
page_flags_offset = ramdump.field_offset('struct page', 'flags')
|
|
sizeof_page = ramdump.sizeof('struct page')
|
|
flags = ramdump.read_word(page + page_flags_offset)
|
|
if flags is None:
|
|
return 0
|
|
section = page_to_section(flags)
|
|
nr = nr_to_section(ramdump, section)
|
|
addr = section_mem_map_addr(ramdump, nr)
|
|
# divide by struct page size for division fun
|
|
return (page - addr) // sizeof_page
|
|
|
|
|
|
def get_vmemmap(ramdump):
|
|
if ramdump.vmemmap is not None:
|
|
return ramdump.vmemmap
|
|
# See: include/asm-generic/pgtable-nopud.h,
|
|
# arch/arm64/include/asm/pgtable-hwdef.h,
|
|
# arch/arm64/include/asm/pgtable.h
|
|
# kernel/arch/arm64/include/asm/memory.h
|
|
nlevels = ramdump.pgtable_levels
|
|
page_shift = ramdump.page_shift
|
|
va_bits = ramdump.va_bits
|
|
pgdir_shift = ramdump.mmu.pgdir_shift
|
|
pud_shift = pgdir_shift
|
|
pud_size = 1 << pud_shift
|
|
spsize = ramdump.sizeof('struct page')
|
|
vmemmap_size = bitops.align((1 << (va_bits - page_shift)) * spsize,
|
|
pud_size)
|
|
|
|
memstart_addr = ramdump.read_s64('memstart_addr')
|
|
page_section_mask = ~((1 << 18) - 1)
|
|
memstart_offset = (memstart_addr >> page_shift) & page_section_mask
|
|
memstart_offset *= spsize
|
|
|
|
if (ramdump.kernel_version < (3, 18, 31)):
|
|
# vmalloc_end = 0xFFFFFFBC00000000
|
|
vmemmap = ramdump.page_offset - pud_size - vmemmap_size
|
|
elif (ramdump.kernel_version < (4, 9, 0)):
|
|
# for version >= 3.18.31,
|
|
# vmemmap is shifted to base addr (0x80000000) pfn.
|
|
vmemmap = (ramdump.page_offset - pud_size - vmemmap_size -
|
|
memstart_offset)
|
|
|
|
elif ramdump.kernel_version >= (5, 15):
|
|
struct_page_max_shift = int(math.log2(spsize))
|
|
vmemmap_shift = page_shift - struct_page_max_shift
|
|
vmemstart = -(1 << (va_bits - vmemmap_shift)) % (1 << 64)
|
|
vmemmap = vmemstart - (memstart_addr >> page_shift)*spsize
|
|
|
|
elif ramdump.kernel_version >= (5, 10):
|
|
struct_page_max_shift = int(math.log2(spsize))
|
|
SZ_2M = 0x00200000
|
|
page_end = -(1 << (va_bits - 1)) % (1 << 64)
|
|
vmemsize = ((page_end - ramdump.page_offset) >> (page_shift - struct_page_max_shift))
|
|
vmemstart = ((-vmemsize) % (1 << 64)) - SZ_2M
|
|
vmemmap = vmemstart - (memstart_addr >> page_shift)*spsize
|
|
elif ramdump.kernel_version >= (5, 4, 0):
|
|
vmemmap = ramdump.read_u64('vmemmap')
|
|
else:
|
|
# for version >= 4.9.0,
|
|
# vmemmap_size = ( 1 << (39 - 12 - 1 + 6))
|
|
struct_page_max_shift = int(math.log2(spsize))
|
|
vmemmap_size = ( 1 << (va_bits - page_shift - 1 + struct_page_max_shift))
|
|
vmemmap = ramdump.page_offset - vmemmap_size - memstart_offset
|
|
|
|
ramdump.vmemmap = vmemmap
|
|
return vmemmap
|
|
|
|
|
|
def page_to_pfn_vmemmap(ramdump, page, vmemmap=None):
|
|
if vmemmap is None:
|
|
vmemmap = get_vmemmap(ramdump)
|
|
page_size = ramdump.sizeof('struct page')
|
|
return ((page - vmemmap) // page_size)
|
|
|
|
|
|
def pfn_to_page_vmemmap(ramdump, pfn, vmemmap=None):
|
|
if vmemmap is None:
|
|
vmemmap = get_vmemmap(ramdump)
|
|
page_size = ramdump.sizeof('struct page')
|
|
return vmemmap + (pfn * page_size)
|
|
|
|
|
|
def page_to_pfn_flat(ramdump, page):
|
|
mem_map_addr = ramdump.address_of('mem_map')
|
|
mem_map = ramdump.read_word(mem_map_addr)
|
|
page_size = ramdump.sizeof('struct page')
|
|
# XXX Needs to change for LPAE
|
|
pfn_offset = ramdump.phys_offset >> ramdump.page_shift
|
|
return ((page - mem_map) // page_size) + pfn_offset
|
|
|
|
|
|
def pfn_to_page_flat(ramdump, pfn):
|
|
mem_map_addr = ramdump.address_of('mem_map')
|
|
mem_map = ramdump.read_word(mem_map_addr)
|
|
page_size = ramdump.sizeof('struct page')
|
|
# XXX Needs to change for LPAE
|
|
pfn_offset = ramdump.phys_offset >> ramdump.page_shift
|
|
return mem_map + ((pfn - pfn_offset) * page_size)
|
|
|
|
|
|
def page_to_pfn(ramdump, page, vmemmap=None):
|
|
if ramdump.arm64:
|
|
return page_to_pfn_vmemmap(ramdump, page, vmemmap)
|
|
if ramdump.is_config_defined('CONFIG_SPARSEMEM'):
|
|
return page_to_pfn_sparse(ramdump, page)
|
|
else:
|
|
return page_to_pfn_flat(ramdump, page)
|
|
|
|
|
|
def pfn_to_page(ramdump, pfn, vmemmap=None):
|
|
if ramdump.arm64:
|
|
return pfn_to_page_vmemmap(ramdump, pfn, vmemmap)
|
|
if ramdump.is_config_defined('CONFIG_SPARSEMEM'):
|
|
return pfn_to_page_sparse(ramdump, pfn)
|
|
else:
|
|
return pfn_to_page_flat(ramdump, pfn)
|
|
|
|
|
|
def sparsemem_lowmem_page_address(ramdump, page):
|
|
membank1_start = ramdump.read_word(ramdump.address_of('membank1_start'))
|
|
membank0_size = ramdump.read_word(ramdump.address_of('membank0_size'))
|
|
# XXX currently magic
|
|
membank0_phys_offset = ramdump.phys_offset
|
|
membank0_page_offset = ramdump.page_offset
|
|
membank1_phys_offset = membank1_start
|
|
membank1_page_offset = membank0_page_offset + membank0_size
|
|
phys = page_to_pfn(ramdump, page) << ramdump.page_shift
|
|
if phys >= membank1_start:
|
|
return phys - membank1_phys_offset + membank1_page_offset
|
|
else:
|
|
return phys - membank0_phys_offset + membank0_page_offset
|
|
|
|
|
|
def dont_map_hole_lowmem_page_address(ramdump, page):
|
|
phys = page_to_pfn(ramdump, page) << ramdump.page_shift
|
|
hole_end_addr = ramdump.address_of('memory_hole_end')
|
|
if hole_end_addr is None:
|
|
hole_end_addr = ramdump.address_of('membank1_start')
|
|
hole_offset_addr = ramdump.address_of('memory_hole_offset')
|
|
if hole_offset_addr is None:
|
|
hole_offset_addr = ramdump.address_of('membank0_size')
|
|
hole_end = ramdump.read_word(hole_end_addr)
|
|
hole_offset = ramdump.read_word(hole_offset_addr)
|
|
if hole_end != 0 and phys >= hole_end:
|
|
return phys - hole_end + hole_offset + ramdump.page_offset
|
|
else:
|
|
return phys - ramdump.phys_offset + ramdump.page_offset
|
|
|
|
|
|
def normal_lowmem_page_address(ramdump, page, vmemmap=None):
|
|
phys = page_to_pfn(ramdump, page, vmemmap) << ramdump.page_shift
|
|
if ramdump.arm64:
|
|
if ramdump.kernel_version >= (5, 10):
|
|
memstart_addr = ramdump.read_s64('memstart_addr')
|
|
return phys - memstart_addr + ramdump.page_offset
|
|
if ramdump.kernel_version >= (5, 4, 0):
|
|
phys_addr = phys - ramdump.read_s64('physvirt_offset')
|
|
if phys_addr < 0:
|
|
phys_addr = phys_addr + (1 << 64)
|
|
return phys_addr
|
|
else:
|
|
memstart_addr = ramdump.read_s64('memstart_addr')
|
|
return phys - memstart_addr + ramdump.page_offset
|
|
else:
|
|
return phys - ramdump.phys_offset + ramdump.page_offset
|
|
|
|
|
|
def lowmem_page_address(ramdump, page, vmemmap=None):
|
|
if ramdump.is_config_defined('CONFIG_SPARSEMEM') and not ramdump.arm64:
|
|
return sparsemem_lowmem_page_address(ramdump, page)
|
|
elif ramdump.is_config_defined('CONFIG_DONT_MAP_HOLE_AFTER_MEMBANK0'):
|
|
return dont_map_hole_lowmem_page_address(ramdump, page)
|
|
else:
|
|
return normal_lowmem_page_address(ramdump, page, vmemmap)
|
|
|
|
|
|
def page_address(ramdump, page, vmemmap=None):
|
|
global lh_offset, pam_page_offset, pam_virtual_offset
|
|
if not zone_is_highmem(ramdump, page_zone(ramdump, page)):
|
|
return lowmem_page_address(ramdump, page, vmemmap)
|
|
|
|
pas = page_slot(ramdump, page)
|
|
lh_offset = ramdump.field_offset('struct page_address_slot', 'lh')
|
|
start = pas + lh_offset
|
|
pam = start
|
|
while True:
|
|
pam = pam - lh_offset
|
|
pam_page_offset = ramdump.field_offset(
|
|
'struct page_address_map', 'page')
|
|
pam_virtual_offset = ramdump.field_offset(
|
|
'struct page_address_map', 'virtual')
|
|
pam_page = ramdump.read_word(pam + pam_page_offset)
|
|
if pam_page == page:
|
|
ret = ramdump.read_word(pam + pam_virtual_offset)
|
|
return ret
|
|
pam = ramdump.read_word(pam + lh_offset)
|
|
if pam == start:
|
|
return None
|
|
|
|
def phys_to_virt(ramdump, phys):
|
|
if not ramdump.arm64:
|
|
return phys - ramdump.phys_offset + ramdump.page_offset
|
|
|
|
if ramdump.kernel_version >= (5, 10, 0):
|
|
memstart_addr = ramdump.read_s64('memstart_addr')
|
|
val = (phys - memstart_addr) | ramdump.page_offset
|
|
return val
|
|
elif ramdump.kernel_version >= (5, 4, 0):
|
|
#as the page_offset value got changed in 5.4 kernel. It is upstream change to support 52 bit
|
|
return phys - ramdump.read_s64('physvirt_offset ')
|
|
|
|
if ramdump.kernel_version < (4, 4, 0):
|
|
return None
|
|
memstart_addr = ramdump.read_s64('memstart_addr')
|
|
val = (phys - memstart_addr) | ramdump.page_offset
|
|
return val
|
|
|
|
|
|
def is_zram_page(ramdump, page):
|
|
try:
|
|
mapping = ramdump.read_structure_field(page, 'struct page', 'mapping')
|
|
if mapping & 0x2:
|
|
# PAGE_MAPPING_MOVABLE bit is set (ZRAM Page)
|
|
return True
|
|
except Exception as err:
|
|
print(err)
|
|
|
|
return False
|
|
|
|
|
|
def for_each_pfn(ramdump):
|
|
""" creates a generator for looping through valid pfn
|
|
Example:
|
|
for i in for_each_pfn(ramdump):
|
|
page = pfn_to_page(i)
|
|
"""
|
|
page_size = ramdump.get_page_size()
|
|
cnt = ramdump.read_structure_field('memblock', 'struct memblock',
|
|
'memory.cnt')
|
|
region = ramdump.read_structure_field('memblock', 'struct memblock',
|
|
'memory.regions')
|
|
memblock_region_size = ramdump.sizeof('struct memblock_region')
|
|
for i in range(cnt):
|
|
start = ramdump.read_structure_field(region, 'struct memblock_region',
|
|
'base')
|
|
end = start + ramdump.read_structure_field(
|
|
region, 'struct memblock_region', 'size')
|
|
|
|
pfn = start // page_size
|
|
end //= page_size
|
|
while pfn < end:
|
|
yield pfn
|
|
pfn += 1
|
|
|
|
region += memblock_region_size
|
|
|
|
|
|
"""
|
|
All fields should be declared and documented in constructor.
|
|
"""
|
|
class MemoryManagementSubsystem:
|
|
def __init__(self, ramdump):
|
|
self.rd = ramdump
|
|
self.SECTION_SIZE_BITS = 0
|
|
|
|
def lookup_page_ext(self, pfn):
|
|
if not self.rd.is_config_defined('CONFIG_PAGE_EXTENSION'):
|
|
return None
|
|
|
|
if not self.rd.is_config_defined('CONFIG_SPARSEMEM'):
|
|
contig_page_data = self.rd.address_of('contig_page_data')
|
|
offset = self.rd.field_offset('struct pglist_data', 'node_page_ext')
|
|
page_ext = self.rd.read_word(contig_page_data + offset)
|
|
else:
|
|
mem_section = pfn_to_section(self.rd, pfn)
|
|
offset = self.rd.field_offset('struct mem_section', 'page_ext')
|
|
page_ext = self.rd.read_word(mem_section + offset)
|
|
|
|
return page_ext
|
|
|
|
|
|
class Sparsemem:
|
|
def __init__(self, ramdump):
|
|
self.rd = ramdump
|
|
""" Cache section_nr to mem_section_ptr mapping for fast lookup """
|
|
self.memsection_cache = dict()
|
|
|
|
def pfn_to_section(self, pfn):
|
|
ramdump = self.rd
|
|
section_nr = pfn >> (self.rd.mm.SECTION_SIZE_BITS - self.rd.page_shift)
|
|
if section_nr in self.memsection_cache:
|
|
return self.memsection_cache[section_nr]
|
|
|
|
memsection_struct_size = ramdump.sizeof('struct mem_section')
|
|
pointer_size = ramdump.sizeof('struct mem_section *')
|
|
if ramdump.is_config_defined('CONFIG_SPARSEMEM_EXTREME'):
|
|
sections_per_root = ramdump.get_page_size() // memsection_struct_size
|
|
else:
|
|
sections_per_root = 1
|
|
root_nr = section_nr // sections_per_root
|
|
section_nr = section_nr % sections_per_root
|
|
|
|
if ramdump.is_config_defined('CONFIG_SPARSEMEM_EXTREME') and \
|
|
ramdump.kernel_version >= (4, 14):
|
|
#struct mem_section **mem_section
|
|
mem_section_base = ramdump.read_word('mem_section')
|
|
offset = pointer_size * root_nr
|
|
ptr = ramdump.read_word(mem_section_base + offset)
|
|
offset = memsection_struct_size * section_nr
|
|
mem_section_ptr = ptr + offset
|
|
elif ramdump.is_config_defined('CONFIG_SPARSEMEM_EXTREME') and \
|
|
ramdump.kernel_version < (4, 14):
|
|
#struct mem_section *mem_section[NR_SECTION_ROOTS]
|
|
mem_section_base = ramdump.address_of('mem_section')
|
|
offset = pointer_size * root_nr
|
|
ptr = ramdump.read_word(mem_section_base + offset)
|
|
offset = memsection_struct_size * section_nr
|
|
mem_section_ptr = ptr + offset
|
|
else:
|
|
#struct mem_section mem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT];
|
|
mem_section_base = ramdump.address_of('mem_section')
|
|
offset = memsection_struct_size * (section_nr + root_nr * sections_per_root)
|
|
mem_section_ptr = mem_section_base + offset
|
|
|
|
self.memsection_cache[section_nr + root_nr * sections_per_root] = mem_section_ptr
|
|
return mem_section_ptr
|
|
|
|
def sparse_init(mm):
|
|
mm.sparsemem = None
|
|
if not mm.rd.is_config_defined('CONFIG_SPARSEMEM'):
|
|
return True
|
|
|
|
mm.sparsemem = Sparsemem(mm.rd)
|
|
return True
|
|
|
|
def section_size_init(mm):
|
|
SECTION_SIZE_BITS = 0
|
|
ramdump = mm.rd
|
|
if ramdump.arm64:
|
|
if ramdump.kernel_version >= (5, 10, 19):
|
|
""" Modified by upstream """
|
|
if (ramdump.is_config_defined('CONFIG_ARM64_4K_PAGES') \
|
|
or ramdump.is_config_defined('CONFIG_ARM64_16K_PAGES')) \
|
|
and not ramdump.is_config_defined('CONFIG_MEMORY_HOTPLUG'):
|
|
SECTION_SIZE_BITS = 30
|
|
elif ramdump.is_config_defined('CONFIG_ARM64_4K_PAGES') \
|
|
or ramdump.is_config_defined('CONFIG_ARM64_16K_PAGES'):
|
|
SECTION_SIZE_BITS = 27
|
|
else:
|
|
SECTION_SIZE_BITS = 29
|
|
else:
|
|
""" CONFIG_HOTPLUG_SIZE_BITS added on 4.4 by us """
|
|
if not ramdump.is_config_defined('CONFIG_MEMORY_HOTPLUG'):
|
|
SECTION_SIZE_BITS = 30
|
|
else:
|
|
SECTION_SIZE_BITS = int(ramdump.get_config_val("CONFIG_HOTPLUG_SIZE_BITS"))
|
|
else:
|
|
SECTION_SIZE_BITS = 28
|
|
mm.SECTION_SIZE_BITS = SECTION_SIZE_BITS
|
|
return True
|
|
|
|
"""
|
|
Invoked functions should return True/False on success/Failure
|
|
"""
|
|
def mm_init(ramdump):
|
|
mm = MemoryManagementSubsystem(ramdump)
|
|
|
|
if not section_size_init(mm):
|
|
return False
|
|
|
|
if not sparse_init(mm):
|
|
return False
|
|
|
|
ramdump.mm = mm
|
|
return True
|
|
|
|
def get_pfn_range(ramdump):
|
|
if ramdump.pfn_range is None:
|
|
ramdump.pfn_range = {}
|
|
memblock = ramdump.read_datatype('memblock')
|
|
cnt = memblock.memory.cnt - 1
|
|
regions = memblock.memory.regions
|
|
first_region = ramdump.read_datatype(regions, 'struct memblock_region')
|
|
if cnt > 0:
|
|
region_addr = regions + (cnt * ramdump.sizeof('struct memblock_region'))
|
|
last_region = ramdump.read_datatype(region_addr, 'struct memblock_region')
|
|
else:
|
|
last_region = first_region
|
|
ramdump.pfn_range['min'] = first_region.base >> ramdump.page_shift
|
|
ramdump.pfn_range['max'] = (last_region.base + last_region.size) >> ramdump.page_shift
|
|
return ramdump.pfn_range |