diff --git a/sys/debug/__init__.py b/sys/debug/__init__.py index 545f1b696..7d4a0f002 100644 --- a/sys/debug/__init__.py +++ b/sys/debug/__init__.py @@ -10,6 +10,7 @@ from .sync import CondVar, Mutex from .thread import Kthread, Thread, CurrentThread from .events import stop_handler +from .virtmem import VmInfo def addPrettyPrinters(): @@ -32,6 +33,7 @@ def addPrettyPrinters(): Kthread() Ktrace() Kgmon() +VmInfo() # Functions CurrentThread() diff --git a/sys/debug/cpu.py b/sys/debug/cpu.py index 05d09e6f9..ce8555d5e 100644 --- a/sys/debug/cpu.py +++ b/sys/debug/cpu.py @@ -1,7 +1,7 @@ import gdb from .struct import GdbStructMeta -from .utils import TextTable, cast +from .utils import TextTable, cast, cast_ptr, get_arch from .cmd import UserCommand, CommandDispatcher @@ -110,3 +110,54 @@ class Cpu(CommandDispatcher): def __init__(self): super().__init__('cpu', [TLB()]) + + +class PageTableMips(): + def __init__(self, pmap): + self._pmap = pmap + + def print(self): + pdp = cast_ptr(self._pmap['pde'], 'pde_t') + table = TextTable(types='ttttt', align='rrrrr') + table.header(['vpn', 'pte0', 'pte1', 'pte2', 'pte3']) + for i in range(1024): + pde = TLBLo(pdp[i]) + if not pde.valid: + continue + ptp = cast_ptr(pde.ppn, 'pte_t') + pte = [TLBLo(ptp[j]) for j in range(1024)] + for j in range(0, 1024, 4): + if not any(pte.valid for pte in pte[j:j+4]): + continue + pte4 = [str(pte) if pte.valid else '-' for pte in pte[j:j+4]] + table.add_row([f'{(i << 22) + (j << 12):8x}', pte4[0], pte4[1], + pte4[2], pte4[3]]) + print(table) + + +class PageTableAArch64(): + def __init__(self, pmap): + self._pmap = pmap + print("Page table not implemented for AArch64") + + def print(self): + pass + + +class PageTableRiscv(): + def __init__(self, pmap): + self._pmap = pmap + print("Page table not implemented for RISC-V") + + def print(self): + pass + + +if get_arch() == 'mips': + PageTable = PageTableMips +elif get_arch() == 'aarch64': + PageTable = PageTableAArch64 +elif get_arch() == 'riscv': + PageTable = PageTableRiscv +else: + print(f'Arch {get_arch()} not supported') diff --git a/sys/debug/kdump.py b/sys/debug/kdump.py index a0f5e7028..47bb7a3fd 100644 --- a/sys/debug/kdump.py +++ b/sys/debug/kdump.py @@ -1,4 +1,3 @@ -from .virtmem import VmPhysSeg, VmFreePages, VmMapSeg, PhysMap from .memory import Vmem, MallocStats, PoolStats from .cmd import CommandDispatcher @@ -7,6 +6,7 @@ class Kdump(CommandDispatcher): """Examine kernel data structures.""" def __init__(self): - super().__init__('kdump', [VmPhysSeg(), VmFreePages(), VmMapSeg(), - PhysMap(), Vmem(), MallocStats(), - PoolStats()]) + super().__init__('kdump', [Vmem(), + MallocStats(), + PoolStats(), + ]) diff --git a/sys/debug/proc.py b/sys/debug/proc.py index c91d5ce5d..822924780 100644 --- a/sys/debug/proc.py +++ b/sys/debug/proc.py @@ -1,9 +1,10 @@ import gdb -from .cmd import SimpleCommand, AutoCompleteMixin +from .cmd import SimpleCommand from .utils import TextTable, global_var from .struct import GdbStructMeta, TailQueue, enum from .thread import Thread +from .vm_map import VmMap from .sync import Mutex @@ -12,6 +13,7 @@ class Process(metaclass=GdbStructMeta): __cast__ = {'p_pid': int, 'p_lock': Mutex, 'p_thread': Thread, + 'p_uspace': VmMap, 'p_state': enum} @staticmethod @@ -32,6 +34,12 @@ def list_all(cls): dead = TailQueue(global_var('zombie_list'), 'p_all') return map(cls, list(alive) + list(dead)) + @classmethod + def find_by_pid(cls, pid): + for p in cls.list_all(): + if p.p_pid == pid: + return p + def __repr__(self): return 'proc{pid=%d}' % self.p_pid diff --git a/sys/debug/utils.py b/sys/debug/utils.py index 622ab1763..61977c14a 100644 --- a/sys/debug/utils.py +++ b/sys/debug/utils.py @@ -9,6 +9,10 @@ def cast(value, typename): return value.cast(gdb.lookup_type(typename)) +def cast_ptr(value, typename): + return value.cast(gdb.lookup_type(typename).pointer()) + + def local_var(name): return gdb.newest_frame().read_var(name) @@ -21,6 +25,14 @@ def relpath(path): return path.rsplit('sys/')[-1] +def get_arch(): + for arch in ['mips', 'aarch64', 'riscv']: + if arch in gdb.architecture_names(): + return arch + print('Current architecture is not supported') + raise KeyError + + # calculates address of ret instruction within function body (MIPS specific) def func_ret_addr(name): s = gdb.execute('disass thread_create', to_string=True) diff --git a/sys/debug/virtmem.py b/sys/debug/virtmem.py index 69fb77cef..a2f4c0675 100644 --- a/sys/debug/virtmem.py +++ b/sys/debug/virtmem.py @@ -1,43 +1,179 @@ -import gdb - from .struct import TailQueue -from .cmd import UserCommand -from .cpu import TLBLo -from .utils import TextTable, global_var, cast +from .cmd import UserCommand, CommandDispatcher +from .cpu import PageTable +from .utils import TextTable, global_var +from .proc import Process PM_NQUEUES = 16 -class PhysMap(UserCommand): +class VmInfo(CommandDispatcher): + """Examine virtual memory data structures.""" + + def __init__(self): + super().__init__('vm', [KernelPmap(), + UserPmap(), + VmMapDump(), + CurrentSegmentInfo(), + ProcSegmentInfo(), + VmPhysSeg(), + VmFreePages(), + ]) + + +class KernelPmap(UserCommand): """List active page entries in kernel pmap""" + + def __init__(self): + super().__init__('pmap_kernel') + + def __call__(self, args): + pmap = PageTable(global_var('kernel_pmap')) + pmap.print() + + +class UserPmap(UserCommand): + """List active page entries in user pmap + + vm pmap_user [pid] + + Arguments: + pid pid of process to show pmap for + + If argument is not specifed the pmap of current process is dumped. + """ + def __init__(self): - super().__init__('pmap') + super().__init__('pmap_user') def __call__(self, args): - pdp = global_var('kernel_pmap')['pde'] - table = TextTable(types='ttttt', align='rrrrr') - table.header(['vpn', 'pte0', 'pte1', 'pte2', 'pte3']) - for i in range(1024): - pde = TLBLo(pdp[i]) - if not pde.valid: - continue - ptp = pde.ppn.cast(gdb.lookup_type('pte_t').pointer()) - pte = [TLBLo(ptp[j]) for j in range(1024)] - for j in range(0, 1024, 4): - if not any(pte.valid for pte in pte[j:j+4]): - continue - pte4 = [str(pte) if pte.valid else '-' for pte in pte[j:j+4]] - table.add_row(['{:8x}'.format((i << 22) + (j << 12)), - pte4[0], pte4[1], pte4[2], pte4[3]]) + args = args.split() + if len(args) == 0: + proc = Process.from_current() + else: + pid = int(args[0]) + proc = Process.find_by_pid(pid) + if proc is None: + print(f'Process {pid} not found') + return + pmap = PageTable(proc.p_uspace.pmap) + pmap.print() + + +class VmMapDump(UserCommand): + """List segments describing virtual address space + + vm map [pid] + + Arguments: + pid pid of process to list vm_map for + + If argument is not specifed the vm_map of current process is listed. + """ + + def __init__(self): + super().__init__('map') + + def __call__(self, args): + args = args.split() + if len(args) == 0: + proc = Process.from_current() + else: + pid = int(args[0]) + proc = Process.find_by_pid(pid) + if proc is None: + print(f'Process {pid} not found') + return + + entries = proc.p_uspace.get_entries() + + table = TextTable(types='ittttt', align='rrrrrr') + table.header(['segment', 'start', 'end', 'prot', 'flags', 'amap']) + for idx, seg in enumerate(entries): + table.add_row([idx, hex(seg.start), hex(seg.end), seg.prot, + seg.flags, seg.aref]) print(table) +def _print_segment(seg, pid, id): + print(f'Segment {id} in proc {pid}') + print(f'Range: {seg.start:#08x}-{seg.end:#08x} ({seg.pages:d} pages)') + print(f'Prot: {seg.prot}') + print(f'Flags: {seg.flags}') + amap = seg.amap + if amap: + print(f'Amap: {seg.amap_ptr}') + print(f'Amap offset: {seg.amap_offset}') + print(f'Amap slots: {amap.slots}') + print(f'Amap refs: {amap.refcnt}') + + amap.print_pages(seg.amap_offset, seg.pages) + else: + print('Amap: NULL') + + +def _segment_info(proc, segment): + MAX_SEGMENT_ID = 4096 + entries = proc.p_uspace.get_entries() + if segment < MAX_SEGMENT_ID: + # Lookup by id + if segment > len(entries): + print(f'Segment {segment} does not exist!') + return + _print_segment(entries[segment], proc.p_pid, segment) + else: + # Lookup by address + addr = segment + for idx, e in enumerate(entries): + if e.start <= addr and addr < e.end: + _print_segment(e, proc.p_pid, idx) + return + print(f'Segment with address {addr} not found') + + +class CurrentSegmentInfo(UserCommand): + """Show info about i-th segment in curent proc vm_map""" + + def __init__(self): + super().__init__('segment') + + def __call__(self, args): + args = args.split() + if len(args) < 1: + print('require argument (segment)') + return + proc = Process.from_current() + segment = int(args[0], 0) + _segment_info(proc, segment) + + +class ProcSegmentInfo(UserCommand): + """Show info about i-th segment in proc vm_map""" + + def __init__(self): + super().__init__('segment_proc') + + def __call__(self, args): + args = args.split() + if len(args) < 2: + print('require 2 arguments (pid and segment)') + return + + pid = int(args[0]) + proc = Process.find_by_pid(pid) + if proc is None: + print(f'Process {pid} not found') + return + segment = int(args[1], 0) + _segment_info(proc, segment) + + class VmPhysSeg(UserCommand): """List physical memory segments managed by vm subsystem""" def __init__(self): - super().__init__('vm_physseg') + super().__init__('physseg') def __call__(self, args): table = TextTable(types='ittit', align='rrrrr') @@ -53,7 +189,7 @@ class VmFreePages(UserCommand): """List free pages known to vm subsystem""" def __init__(self): - super().__init__('vm_freepages') + super().__init__('freepages') def __call__(self, args): table = TextTable(align='rrl', types='iit') @@ -62,33 +198,12 @@ def __call__(self, args): count = int(global_var('pagecount')[q]) pages = [] for page in TailQueue(global_var('freelist')[q], 'freeq'): - pages.append('{:8x}'.format(int(page['paddr']))) + pages.append(f'{int(page["paddr"]):8x}') free_pages += int(page['size']) table.add_row([count, 2**q, ' '.join(pages)]) table.header(['#pages', 'size', 'addresses']) print(table) - print('Free pages count: {}'.format(free_pages)) + print(f'Free pages count: {free_pages}') segments = TailQueue(global_var('seglist'), 'seglink') pages = int(sum(seg['npages'] for seg in segments if not seg['used'])) - print('Used pages count: {}'.format(pages - free_pages)) - - -class VmMapSeg(UserCommand): - """List segments describing virtual address space""" - - def __init__(self): - super().__init__('vm_map') - - def __call__(self, args): - vm_map = gdb.parse_and_eval('vm_map_user()') - if vm_map == 0: - print('No active user vm_map!') - return - entries = vm_map['entries'] - table = TextTable(types='ittttt', align='rrrrrr') - table.header(['segment', 'start', 'end', 'prot', 'flags', 'amap']) - segments = TailQueue(entries, 'link') - for idx, seg in enumerate(segments): - table.add_row([idx, seg['start'], seg['end'], seg['prot'], - seg['flags'], seg['aref']]) - print(table) + print(f'Used pages count: {pages - free_pages}') diff --git a/sys/debug/vm_map.py b/sys/debug/vm_map.py new file mode 100644 index 000000000..7b4647fc9 --- /dev/null +++ b/sys/debug/vm_map.py @@ -0,0 +1,81 @@ +from .struct import GdbStructMeta +from .struct import TailQueue + + +class VmMap(metaclass=GdbStructMeta): + __ctype__ = 'struct vm_map' + + def __repr__(self): + return (f'vm_map[entries=[{self.entries} {self.nentries}],' + f'pmap={self.pmap}]') + + def get_entries(self): + entries = TailQueue(self.entries, 'link') + return [VmMapEntry(e) for e in entries] + + +class VmMapEntry(metaclass=GdbStructMeta): + __ctype__ = 'struct vm_map_entry' + __cast__ = {'start': int, + 'end': int} + + def __repr__(self): + return f'vm_map_entry[{self.start:#08x}-{self.end:#08x}]' + + @property + def amap_ptr(self): + return self.aref['amap'] + + @property + def pages(self): + size = self.end - self.start + return size // 4096 + + @property + def amap(self): + if not self.aref['amap']: + return None + return Amap(self.aref['amap']) + + @property + def amap_offset(self): + return self.aref['offset'] + + def amap_bitmap_str(self): + return self.amap.str_bitmap(self.amap_offset, self.pages) + + +class Amap(metaclass=GdbStructMeta): + __ctype__ = 'struct vm_amap' + __cast__ = {'slots': int, + 'ref_cnt': int} + + def __repr__(self): + return f'vm_amap[slots={self.slots}, refs={self.ref_cnt}]' + + def _get_bitmap(self): + bitmap = [] + bitssize = (self.slots + 7) >> 3 + for i in range(bitssize): + val = self.pg_bitmap[i] + for _ in range(8): + bitmap.append(val & 1 == 1) + val >>= 1 + return bitmap + + def print_pages(self, offset, n): + bm = self._get_bitmap() + slots = 'Page num:' + values = 'Ref cnt: ' + used = 0 + for i in range(n): + slots += f'{i:^5}' + if bm[offset + i]: + values += f'{1:^5}' + used += 1 + else: + values += f'{"-": ^5}' + + print(f'Pages used by segment ({used} pages)') + print(slots) + print(values)