Skip to content

Commit

Permalink
GDB: Improve debugging options for VM subsystem
Browse files Browse the repository at this point in the history
  • Loading branch information
franciscozdo committed Jul 1, 2023
1 parent 6383814 commit 2110cbc
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 50 deletions.
2 changes: 2 additions & 0 deletions sys/debug/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -32,6 +33,7 @@ def addPrettyPrinters():
Kthread()
Ktrace()
Kgmon()
VmInfo()

# Functions
CurrentThread()
Expand Down
8 changes: 4 additions & 4 deletions sys/debug/kdump.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from .virtmem import VmPhysSeg, VmFreePages, VmMapSeg, PhysMap
from .memory import Vmem, MallocStats, PoolStats
from .cmd import CommandDispatcher

Expand All @@ -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(),
])
10 changes: 9 additions & 1 deletion sys/debug/proc.py
Original file line number Diff line number Diff line change
@@ -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


Expand All @@ -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
Expand All @@ -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

Expand Down
27 changes: 27 additions & 0 deletions sys/debug/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand All @@ -21,6 +25,29 @@ def relpath(path):
return path.rsplit('sys/')[-1]


def get_arch():
try:
_ = gdb.parse_and_eval('aarch64_init')
return 'aarch64'
except gdb.error:
pass

try:
_ = gdb.parse_and_eval('riscv_init')
return 'riscv'
except gdb.error:
pass

try:
_ = gdb.parse_and_eval('mips_init')
return 'mips'
except gdb.error:
pass

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)
Expand Down
205 changes: 160 additions & 45 deletions sys/debug/virtmem.py
Original file line number Diff line number Diff line change
@@ -1,43 +1,179 @@
import gdb

from .struct import TailQueue
from .cmd import UserCommand
from .cmd import UserCommand, CommandDispatcher
from .cpu import TLBLo
from .utils import TextTable, global_var, cast
from .utils import TextTable, global_var, cast_ptr, get_arch
from .proc import Process


PM_NQUEUES = 16


class PhysMap(UserCommand):
"""List active page entries in kernel pmap"""
class VmInfo(CommandDispatcher):
"""Examine virtual memory data structures."""

def __init__(self):
super().__init__('pmap')
super().__init__('vm', [DumpPmap('kernel'),
DumpPmap('user'),
VmMapDump(),
SegmentInfo(''),
SegmentInfo('proc'),
VmPhysSeg(),
VmFreePages(),
])


def _print_mips_pmap(pmap):
pdp = cast_ptr(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(['{:8x}'.format((i << 22) + (j << 12)),
pte4[0], pte4[1], pte4[2], pte4[3]])
print(table)


class DumpPmap(UserCommand):
"""List active page entries in user pmap"""

def __init__(self, typ):
command = 'pmap_' + typ
if command not in ('pmap_user', 'pmap_kernel'):
print(f'{command} command not supported')
return
self.command = command
super().__init__(command)

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]])
if self.command == 'pmap_kernel':
pmap = global_var('kernel_pmap')
else:
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 = proc.p_uspace.pmap

arch = get_arch()
if arch == 'mips':
_print_mips_pmap(pmap)
else:
print(f"Can't print {arch} pmap")


class VmMapDump(UserCommand):
"""List segments describing virtual address space"""

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)


class SegmentInfo(UserCommand):
"""Show info about i-th segment in proc vm_map"""

def __init__(self, typ):
command = f"segment{'_' if typ != '' else ''}{typ}"
if command not in ['segment', 'segment_proc']:
print(f'{command} command not supported')
return
self.command = command
super().__init__(command)

def _print_segment(self, seg, pid, id):
print('Segment {} in proc {}'.format(id, pid))
print('Range: {:#08x}-{:#08x} ({:d} pages)'.format(seg.start,
seg.end,
seg.pages))
print('Prot: {}'.format(seg.prot))
print('Flags: {}'.format(seg.flags))
amap = seg.amap
if amap:
print('Amap: {}'.format(seg.amap_ptr))
print('Amap offset: {}'.format(seg.amap_offset))
print('Amap slots: {}'.format(amap.slots))
print('Amap refs: {}'.format(amap.ref_cnt))

# TODO: show used pages/anons
else:
print('Amap: NULL')

def __call__(self, args):
args = args.split()
if self.command == 'segment':
if len(args) < 1:
print('require argument (segment)')
return
proc = Process.from_current()
else:
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
args = args[1:]

entries = proc.p_uspace.get_entries()

segment = int(args[0], 0)

if segment < 4096:
# Lookup by id
if segment > len(entries):
print(f'Segment {segment} does not exist!')
return
self._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:
self._print_segment(e, proc.p_pid, idx)
return
print(f'Segment with address {addr} not found')


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')
Expand All @@ -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')
Expand All @@ -71,24 +207,3 @@ def __call__(self, args):
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)
55 changes: 55 additions & 0 deletions sys/debug/vm_map.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
from .struct import GdbStructMeta
from .struct import TailQueue


class VmMap(metaclass=GdbStructMeta):
__ctype__ = 'struct vm_map'

def __repr__(self):
return 'vm_map[entries=[{} {}], pmap={}]'.format(
self.entries, self.nentries, 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 'vm_map_entry[{:#08x}-{:#08x}]'.format(self.start, self.end)

@property
def amap_ptr(self):
return self.aref['amap']

@property
def pages(self):
size = self.end - self.start
return int(size / 4096)

@property
def amap(self):
if int(self.aref['amap']) == 0:
return None
else:
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 'vm_amap[slots={}, refs={}]'.format(self.slots, self.ref_cnt)

0 comments on commit 2110cbc

Please sign in to comment.