gini-toolkit 6.0.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gini/__init__.py +12 -0
- gini/__main__.py +107 -0
- gini/_version.py +24 -0
- gini/agent/__init__.py +17 -0
- gini/agent/agent_gamemaster.py +140 -0
- gini/agent/api.py +291 -0
- gini/agent/ask.py +123 -0
- gini/agent/authoring.py +72 -0
- gini/agent/blackboard.py +114 -0
- gini/agent/contracts.py +142 -0
- gini/agent/domains.py +91 -0
- gini/agent/embed.py +123 -0
- gini/agent/gamemaster.py +256 -0
- gini/agent/kb.py +148 -0
- gini/agent/lesson_resolver.py +261 -0
- gini/agent/llm/__init__.py +5 -0
- gini/agent/llm/backend.py +43 -0
- gini/agent/llm/fake.py +25 -0
- gini/agent/llm/ollama.py +206 -0
- gini/agent/loop.py +258 -0
- gini/agent/mcp_server.py +86 -0
- gini/agent/meaning.py +225 -0
- gini/agent/mission.py +210 -0
- gini/agent/mission_controller.py +208 -0
- gini/agent/narration.py +116 -0
- gini/agent/notifier.py +86 -0
- gini/agent/personas.py +79 -0
- gini/agent/reasoning.py +172 -0
- gini/agent/recall.py +248 -0
- gini/agent/session.py +79 -0
- gini/agent/teaching_center.py +482 -0
- gini/agent/tools/__init__.py +3 -0
- gini/agent/tools/registry.py +193 -0
- gini/agent/twin/__init__.py +28 -0
- gini/agent/twin/authoring.py +71 -0
- gini/agent/twin/contracts.py +54 -0
- gini/agent/twin/dialectic.py +189 -0
- gini/agent/twin/harness.py +93 -0
- gini/agent/twin/justify.py +156 -0
- gini/agent/twin/learner.py +64 -0
- gini/agent/twin/mission.py +60 -0
- gini/agent/twin/os_coach.py +79 -0
- gini/agent/twin/salience.py +30 -0
- gini/agent/understand.py +250 -0
- gini/agent/verifiers.py +106 -0
- gini/agent/wizard.py +178 -0
- gini/agent/xv6_pack.py +74 -0
- gini/app/__init__.py +3 -0
- gini/app/context.py +368 -0
- gini/app/paths.py +121 -0
- gini/data/README.md +21 -0
- gini/domain/__init__.py +9 -0
- gini/domain/assembly.py +209 -0
- gini/domain/authoring.py +353 -0
- gini/domain/blueprints.py +5 -0
- gini/domain/capabilities.py +177 -0
- gini/domain/catalog.py +85 -0
- gini/domain/certify.py +201 -0
- gini/domain/compose.py +413 -0
- gini/domain/composition.py +88 -0
- gini/domain/concepts.py +383 -0
- gini/domain/connection_rules.py +269 -0
- gini/domain/constraints.py +153 -0
- gini/domain/content.py +59 -0
- gini/domain/cpu_journey.py +89 -0
- gini/domain/devices.py +747 -0
- gini/domain/diagnose.py +201 -0
- gini/domain/element_guide.py +327 -0
- gini/domain/explain.py +90 -0
- gini/domain/fingerprint.py +201 -0
- gini/domain/firewall.py +34 -0
- gini/domain/flowlog.py +61 -0
- gini/domain/flowtable.py +179 -0
- gini/domain/fragment_yaml.py +230 -0
- gini/domain/fragments.py +169 -0
- gini/domain/games/__init__.py +2 -0
- gini/domain/games/paging_games.py +119 -0
- gini/domain/games/policy_game.py +86 -0
- gini/domain/games/process_game.py +48 -0
- gini/domain/games/thrash_game.py +75 -0
- gini/domain/games/translate_game.py +60 -0
- gini/domain/games/trap_game.py +86 -0
- gini/domain/grader.py +155 -0
- gini/domain/grouping.py +67 -0
- gini/domain/legality.py +103 -0
- gini/domain/lesson.py +241 -0
- gini/domain/lexicon.py +150 -0
- gini/domain/machine_state.py +410 -0
- gini/domain/missions/networking/basic-lan.yaml +32 -0
- gini/domain/missions/networking/cache-in-front.yaml +23 -0
- gini/domain/missions/networking/decouple-with-queue.yaml +31 -0
- gini/domain/missions/networking/drive-load.yaml +20 -0
- gini/domain/missions/networking/fix-the-address.yaml +75 -0
- gini/domain/missions/networking/fix-the-lan.yaml +43 -0
- gini/domain/missions/networking/inspect-flows.yaml +16 -0
- gini/domain/missions/networking/k8s-autoscale.yaml +27 -0
- gini/domain/missions/networking/least-privilege.yaml +21 -0
- gini/domain/missions/networking/load-balanced-web.yaml +29 -0
- gini/domain/missions/networking/observe-it.yaml +24 -0
- gini/domain/missions/networking/put-in-vpc.yaml +30 -0
- gini/domain/missions/networking/reachability-boundary.yaml +56 -0
- gini/domain/missions/networking/sdn-reactive.yaml +35 -0
- gini/domain/missions/networking/send-request.yaml +19 -0
- gini/domain/missions/networking/serverless-api.yaml +25 -0
- gini/domain/missions/networking/service-chain.yaml +33 -0
- gini/domain/missions/os/lottery-fix.yaml +19 -0
- gini/domain/missions/os/priority-fix.yaml +24 -0
- gini/domain/missions.py +111 -0
- gini/domain/modulechain.py +36 -0
- gini/domain/objectives.py +488 -0
- gini/domain/os_zoo.py +79 -0
- gini/domain/paging_sim.py +141 -0
- gini/domain/pricing.py +199 -0
- gini/domain/probes.py +226 -0
- gini/domain/profile.py +142 -0
- gini/domain/recipes.py +738 -0
- gini/domain/riders.py +309 -0
- gini/domain/router_modules.py +224 -0
- gini/domain/routetable.py +67 -0
- gini/domain/scoring.py +76 -0
- gini/domain/staging.py +122 -0
- gini/domain/syscall_builder.py +144 -0
- gini/domain/topic_cloud.py +62 -0
- gini/domain/topology.py +213 -0
- gini/domain/vocabulary.py +51 -0
- gini/domain/xv6.py +808 -0
- gini/domain/xv6_fs.py +250 -0
- gini/domain/xv6_runner.py +113 -0
- gini/domain/xv6_vm.py +385 -0
- gini/gloader.py +17 -0
- gini/runtime/__init__.py +18 -0
- gini/runtime/cloudfabric_agent.py +370 -0
- gini/runtime/console.py +68 -0
- gini/runtime/control.py +70 -0
- gini/runtime/frame.py +138 -0
- gini/runtime/gbridge.py +638 -0
- gini/runtime/grouter.py +223 -0
- gini/runtime/hostsim.py +90 -0
- gini/runtime/shuttle.py +348 -0
- gini/runtime/switch.py +109 -0
- gini/runtime/transport.py +77 -0
- gini/runtime/xv6_bridge.py +312 -0
- gini/server/__init__.py +22 -0
- gini/server/__main__.py +74 -0
- gini/server/app.py +140 -0
- gini/server/auth.py +82 -0
- gini/server/policy.py +57 -0
- gini/server/session.py +23 -0
- gini/services/__init__.py +15 -0
- gini/services/boardflash.py +248 -0
- gini/services/boardsetup.py +374 -0
- gini/services/cloud_catalog.py +143 -0
- gini/services/compiler.py +1858 -0
- gini/services/discovery.py +324 -0
- gini/services/gloader.py +183 -0
- gini/services/orchestrator.py +1460 -0
- gini/services/persistence.py +28 -0
- gini/services/probe_runner.py +149 -0
- gini/services/project.py +217 -0
- gini/services/remote.py +93 -0
- gini/services/rider_runner.py +96 -0
- gini/services/rider_session.py +171 -0
- gini/services/shadow_store.py +52 -0
- gini/services/terminal.py +45 -0
- gini/setup/__init__.py +17 -0
- gini/setup/cli.py +109 -0
- gini/setup/images.py +33 -0
- gini/setup/marker.py +43 -0
- gini/setup/runtime.py +69 -0
- gini/ui/__init__.py +3 -0
- gini/ui/assets/app_icon.icns +0 -0
- gini/ui/assets/app_icon.ico +0 -0
- gini/ui/assets/app_icon.png +0 -0
- gini/ui/assets/app_icon_1024.png +0 -0
- gini/ui/assets/cue/_w.txt +1 -0
- gini/ui/assets/cue/ai.png +0 -0
- gini/ui/assets/cue/canvas.png +0 -0
- gini/ui/assets/cue/cloud.png +0 -0
- gini/ui/assets/cue/cost.png +0 -0
- gini/ui/assets/cue/dark/ai.png +0 -0
- gini/ui/assets/cue/dark/canvas.png +0 -0
- gini/ui/assets/cue/dark/cloud.png +0 -0
- gini/ui/assets/cue/dark/cost.png +0 -0
- gini/ui/assets/cue/dark/metrics.png +0 -0
- gini/ui/assets/cue/dark/router.png +0 -0
- gini/ui/assets/cue/dark/run.png +0 -0
- gini/ui/assets/cue/dark/serverless.png +0 -0
- gini/ui/assets/cue/dark/settings.png +0 -0
- gini/ui/assets/cue/dark/welcome.png +0 -0
- gini/ui/assets/cue/dark/wizard.png +0 -0
- gini/ui/assets/cue/ginibrand/ai.png +0 -0
- gini/ui/assets/cue/ginibrand/canvas.png +0 -0
- gini/ui/assets/cue/ginibrand/cloud.png +0 -0
- gini/ui/assets/cue/ginibrand/cost.png +0 -0
- gini/ui/assets/cue/ginibrand/metrics.png +0 -0
- gini/ui/assets/cue/ginibrand/router.png +0 -0
- gini/ui/assets/cue/ginibrand/run.png +0 -0
- gini/ui/assets/cue/ginibrand/serverless.png +0 -0
- gini/ui/assets/cue/ginibrand/settings.png +0 -0
- gini/ui/assets/cue/ginibrand/welcome.png +0 -0
- gini/ui/assets/cue/ginibrand/wizard.png +0 -0
- gini/ui/assets/cue/highcontrast/ai.png +0 -0
- gini/ui/assets/cue/highcontrast/canvas.png +0 -0
- gini/ui/assets/cue/highcontrast/cloud.png +0 -0
- gini/ui/assets/cue/highcontrast/cost.png +0 -0
- gini/ui/assets/cue/highcontrast/metrics.png +0 -0
- gini/ui/assets/cue/highcontrast/router.png +0 -0
- gini/ui/assets/cue/highcontrast/run.png +0 -0
- gini/ui/assets/cue/highcontrast/serverless.png +0 -0
- gini/ui/assets/cue/highcontrast/settings.png +0 -0
- gini/ui/assets/cue/highcontrast/welcome.png +0 -0
- gini/ui/assets/cue/highcontrast/wizard.png +0 -0
- gini/ui/assets/cue/light/ai.png +0 -0
- gini/ui/assets/cue/light/canvas.png +0 -0
- gini/ui/assets/cue/light/cloud.png +0 -0
- gini/ui/assets/cue/light/cost.png +0 -0
- gini/ui/assets/cue/light/metrics.png +0 -0
- gini/ui/assets/cue/light/router.png +0 -0
- gini/ui/assets/cue/light/run.png +0 -0
- gini/ui/assets/cue/light/serverless.png +0 -0
- gini/ui/assets/cue/light/settings.png +0 -0
- gini/ui/assets/cue/light/welcome.png +0 -0
- gini/ui/assets/cue/light/wizard.png +0 -0
- gini/ui/assets/cue/metrics.png +0 -0
- gini/ui/assets/cue/router.png +0 -0
- gini/ui/assets/cue/run.png +0 -0
- gini/ui/assets/cue/serverless.png +0 -0
- gini/ui/assets/cue/settings.png +0 -0
- gini/ui/assets/cue/welcome.png +0 -0
- gini/ui/assets/cue/wizard.png +0 -0
- gini/ui/assistant.py +2111 -0
- gini/ui/author_dialog.py +184 -0
- gini/ui/board_dialog.py +247 -0
- gini/ui/branding.py +21 -0
- gini/ui/canvas.py +2007 -0
- gini/ui/chat_panel.py +7 -0
- gini/ui/cpu_journey.py +212 -0
- gini/ui/cpu_lab.py +306 -0
- gini/ui/cue_cards.py +214 -0
- gini/ui/dashboard.py +222 -0
- gini/ui/diagnose_game.py +336 -0
- gini/ui/fingerprint_lab.py +219 -0
- gini/ui/flash_dialog.py +244 -0
- gini/ui/flow_layout.py +63 -0
- gini/ui/fragment_manager.py +1415 -0
- gini/ui/game_catalog.py +184 -0
- gini/ui/game_renderers.py +340 -0
- gini/ui/games_lab.py +90 -0
- gini/ui/inspector.py +1055 -0
- gini/ui/live_metrics.py +130 -0
- gini/ui/machine_lab.py +1412 -0
- gini/ui/main_window.py +3153 -0
- gini/ui/memory_lab.py +371 -0
- gini/ui/mission_panel.py +302 -0
- gini/ui/mode_indicator.py +227 -0
- gini/ui/palette.py +112 -0
- gini/ui/peripherals.py +218 -0
- gini/ui/process_tree.py +130 -0
- gini/ui/reset_dialog.py +179 -0
- gini/ui/router_lab.py +776 -0
- gini/ui/run_button.py +183 -0
- gini/ui/settings_dialog.py +234 -0
- gini/ui/signin_dialog.py +111 -0
- gini/ui/storage_lab.py +219 -0
- gini/ui/syscall_builder.py +235 -0
- gini/ui/syscall_lab.py +152 -0
- gini/ui/theme/__init__.py +5 -0
- gini/ui/theme/icons.py +145 -0
- gini/ui/theme/manager.py +291 -0
- gini/ui/theme/tokens.py +194 -0
- gini/ui/trap_lab.py +270 -0
- gini/ui/worker_host.py +102 -0
- gini/ui/zoo_lab.py +112 -0
- gini_toolkit-6.0.1.dev0.dist-info/METADATA +77 -0
- gini_toolkit-6.0.1.dev0.dist-info/RECORD +278 -0
- gini_toolkit-6.0.1.dev0.dist-info/WHEEL +5 -0
- gini_toolkit-6.0.1.dev0.dist-info/entry_points.txt +3 -0
- gini_toolkit-6.0.1.dev0.dist-info/top_level.txt +1 -0
gini/domain/xv6_vm.py
ADDED
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
"""xv6 virtual-memory state model — the Memory face's read side.
|
|
2
|
+
|
|
3
|
+
xv6 on RISC-V uses Sv39 three-level page tables: `satp` points at the root, and a 39-bit virtual
|
|
4
|
+
address is split into three 9-bit indices (levels 2/1/0) plus a 12-bit offset. A leaf PTE carries
|
|
5
|
+
a physical page number and permission flags V/R/W/X/U. This module models the flattened set of
|
|
6
|
+
leaf mappings, the process address-space regions (text/data/heap/guard/stack/trapframe/trampoline),
|
|
7
|
+
the physical page allocator, and page faults — with a parser for xv6's own `vmprint()` output, so
|
|
8
|
+
the VA reconstruction, the flag decoding, and the DemoVm feed are all unit-tested without QEMU.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import re
|
|
13
|
+
from dataclasses import dataclass, field
|
|
14
|
+
|
|
15
|
+
PGSIZE = 4096
|
|
16
|
+
MAXVA = 1 << 38
|
|
17
|
+
TRAMPOLINE = MAXVA - PGSIZE # 0x3FFFFFF000
|
|
18
|
+
TRAPFRAME = TRAMPOLINE - PGSIZE # 0x3FFFFFE000
|
|
19
|
+
|
|
20
|
+
# PTE flag bits (RISC-V)
|
|
21
|
+
_FLAGS = (("V", 1), ("R", 2), ("W", 4), ("X", 8), ("U", 16), ("G", 32), ("A", 64), ("D", 128))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def perms(pte: int) -> str:
|
|
25
|
+
"""The R/W/X/U permission string of a PTE value (e.g. 'rwx-' , 'r-x u')."""
|
|
26
|
+
out = ""
|
|
27
|
+
for ch, bit in (("r", 2), ("w", 4), ("x", 8), ("u", 16)):
|
|
28
|
+
out += ch if (pte & bit) else "-"
|
|
29
|
+
return out
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def rsw_bits(flags: int) -> int:
|
|
33
|
+
"""The two reserved-for-software bits (8,9) of a PTE. xv6's COW lab parks its COW flag here,
|
|
34
|
+
so a non-zero RSW on a user page is our (design-agnostic) 'this page is marked by the student'
|
|
35
|
+
signal — we badge it without needing to know which bit they chose."""
|
|
36
|
+
return (flags >> 8) & 0x3
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class Pte:
|
|
41
|
+
va: int
|
|
42
|
+
pa: int
|
|
43
|
+
perms: str = "----"
|
|
44
|
+
valid: bool = True
|
|
45
|
+
flags: int = 0 # raw low-10 PTE bits (V R W X U G A D + RSW8/9); 0 if unknown
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def writable(self) -> bool:
|
|
49
|
+
return "w" in self.perms
|
|
50
|
+
|
|
51
|
+
@property
|
|
52
|
+
def user(self) -> bool:
|
|
53
|
+
return "u" in self.perms
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def rsw(self) -> int:
|
|
57
|
+
return rsw_bits(self.flags)
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def cow(self) -> bool:
|
|
61
|
+
"""A COW candidate: a user page that is present, NOT writable, and carries an RSW mark —
|
|
62
|
+
the exact shape xv6's COW fork leaves behind (shared, write-protected, COW-tagged)."""
|
|
63
|
+
return self.user and not self.writable and self.rsw != 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class Region:
|
|
68
|
+
name: str
|
|
69
|
+
start: int
|
|
70
|
+
end: int
|
|
71
|
+
perms: str = ""
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def pages(self) -> int:
|
|
75
|
+
return max((self.end - self.start) // PGSIZE, 1)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class PhysMem:
|
|
80
|
+
total_pages: int = 0
|
|
81
|
+
free_pages: int = 0
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def used_pages(self) -> int:
|
|
85
|
+
return max(self.total_pages - self.free_pages, 0)
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def used_frac(self) -> float:
|
|
89
|
+
return (self.used_pages / self.total_pages) if self.total_pages else 0.0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@dataclass
|
|
93
|
+
class Fault:
|
|
94
|
+
va: int
|
|
95
|
+
cause: str
|
|
96
|
+
pid: int | None = None
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# scause codes for the three page-fault kinds (RISC-V privileged spec)
|
|
100
|
+
SCAUSE_NAMES = {12: "instruction page fault", 13: "load page fault", 15: "store page fault"}
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@dataclass
|
|
104
|
+
class PageFault:
|
|
105
|
+
"""One captured page fault from the kernel's live fault ring (gini_faultdump)."""
|
|
106
|
+
pid: int
|
|
107
|
+
scause: int
|
|
108
|
+
va: int
|
|
109
|
+
epc: int
|
|
110
|
+
kind: str = "" # filled by classify_faults: lazy / cow-write / stack / illegal
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def cause(self) -> str:
|
|
114
|
+
return SCAUSE_NAMES.get(self.scause, f"scause {self.scause}")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@dataclass
|
|
118
|
+
class ProcVm:
|
|
119
|
+
"""One process's address space from the all-procs dump (gini_vmdump_all)."""
|
|
120
|
+
pid: int
|
|
121
|
+
name: str = ""
|
|
122
|
+
sz: int = 0 # p->sz — the *virtual* size (top of the heap/brk)
|
|
123
|
+
leaves: list = field(default_factory=list) # [Pte]
|
|
124
|
+
|
|
125
|
+
@property
|
|
126
|
+
def resident_pages(self) -> int:
|
|
127
|
+
"""User pages actually backed by physical frames (excludes kernel trampoline/trapframe)."""
|
|
128
|
+
return sum(1 for p in self.leaves if p.user)
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def virtual_pages(self) -> int:
|
|
132
|
+
return max((self.sz + PGSIZE - 1) // PGSIZE, 0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
@dataclass
|
|
136
|
+
class VmSnapshot:
|
|
137
|
+
satp: int = 0
|
|
138
|
+
leaves: list = field(default_factory=list) # [Pte]
|
|
139
|
+
regions: list = field(default_factory=list) # [Region]
|
|
140
|
+
phys: PhysMem = field(default_factory=PhysMem)
|
|
141
|
+
faults: list = field(default_factory=list) # [Fault]
|
|
142
|
+
# Provenance (see FsSnapshot): "real" from the kernel vs "demo" stand-in; ok=False means a
|
|
143
|
+
# real read yielded nothing (show an error, don't fake). Page-table leaves + faults are
|
|
144
|
+
# dumped for real; the region map + physical allocator bar are not yet (so not in `have`).
|
|
145
|
+
source: str = "real"
|
|
146
|
+
ok: bool = True
|
|
147
|
+
have: tuple = ("pagetable", "faults")
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# -- parser: xv6 vmprint() -------------------------------------------------- #
|
|
151
|
+
_ROOT = re.compile(r"page table\s+(0x[0-9a-fA-F]+)")
|
|
152
|
+
_LINE = re.compile(r"^((?:\.\.\s*)+)(\d+):\s*pte\s+(0x[0-9a-fA-F]+)\s+pa\s+(0x[0-9a-fA-F]+)")
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def parse_vmprint(text: str) -> VmSnapshot:
|
|
156
|
+
"""Parse xv6's `vmprint()` tree into leaf mappings. Level depth = number of `..` groups
|
|
157
|
+
(1,2,3 for Sv39 levels 2,1,0); a leaf is depth 3, and its VA is rebuilt from the index path."""
|
|
158
|
+
satp = 0
|
|
159
|
+
path: dict[int, int] = {}
|
|
160
|
+
leaves: list[Pte] = []
|
|
161
|
+
for line in (text or "").splitlines():
|
|
162
|
+
s = line.strip()
|
|
163
|
+
mr = _ROOT.search(s)
|
|
164
|
+
if mr and not leaves and satp == 0:
|
|
165
|
+
satp = int(mr.group(1), 16)
|
|
166
|
+
continue
|
|
167
|
+
m = _LINE.match(s)
|
|
168
|
+
if not m:
|
|
169
|
+
continue
|
|
170
|
+
depth = m.group(1).count("..")
|
|
171
|
+
idx = int(m.group(2))
|
|
172
|
+
pte = int(m.group(3), 16)
|
|
173
|
+
pa = int(m.group(4), 16)
|
|
174
|
+
path[depth] = idx
|
|
175
|
+
if depth == 3: # leaf (level 0)
|
|
176
|
+
va = (path.get(1, 0) << 30) | (path.get(2, 0) << 21) | (idx << 12)
|
|
177
|
+
leaves.append(Pte(va=va, pa=pa, perms=perms(pte), valid=bool(pte & 1)))
|
|
178
|
+
return VmSnapshot(satp=satp, leaves=leaves)
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
# -- parser: live fault ring (gini_faultdump) ------------------------------- #
|
|
182
|
+
# `FLT <pid> <scause> <va_hex> <epc_hex>` — one per captured page fault, oldest first.
|
|
183
|
+
_FLT = re.compile(r"^FLT\s+(-?\d+)\s+(\d+)\s+(0x[0-9a-fA-F]+)\s+(0x[0-9a-fA-F]+)")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def parse_faults(text: str) -> list:
|
|
187
|
+
out: list = []
|
|
188
|
+
for line in (text or "").splitlines():
|
|
189
|
+
m = _FLT.match(line.strip())
|
|
190
|
+
if m:
|
|
191
|
+
out.append(PageFault(pid=int(m.group(1)), scause=int(m.group(2)),
|
|
192
|
+
va=int(m.group(3), 16), epc=int(m.group(4), 16)))
|
|
193
|
+
return out
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# -- parser: all-procs page tables (gini_vmdump_all) ------------------------ #
|
|
197
|
+
# `VP <pid> <name> <sz_hex>` header, then `VL <pid> <va_hex> <pa_hex> <flags_int>` per leaf.
|
|
198
|
+
_VP = re.compile(r"^VP\s+(\d+)\s+(\S+)\s+(0x[0-9a-fA-F]+)")
|
|
199
|
+
_VL = re.compile(r"^VL\s+(\d+)\s+(0x[0-9a-fA-F]+)\s+(0x[0-9a-fA-F]+)\s+(\d+)")
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def parse_vmall(text: str) -> dict:
|
|
203
|
+
"""Parse every user process's leaf mappings into {pid: ProcVm}. Leaves carry the raw PTE
|
|
204
|
+
flag bits, so permissions AND the student's RSW/COW mark survive to the UI."""
|
|
205
|
+
procs: dict = {}
|
|
206
|
+
for line in (text or "").splitlines():
|
|
207
|
+
s = line.strip()
|
|
208
|
+
mp = _VP.match(s)
|
|
209
|
+
if mp:
|
|
210
|
+
pid = int(mp.group(1))
|
|
211
|
+
procs[pid] = ProcVm(pid=pid, name=mp.group(2), sz=int(mp.group(3), 16))
|
|
212
|
+
continue
|
|
213
|
+
ml = _VL.match(s)
|
|
214
|
+
if ml:
|
|
215
|
+
pid = int(ml.group(1))
|
|
216
|
+
flags = int(ml.group(4))
|
|
217
|
+
pte = Pte(va=int(ml.group(2), 16), pa=int(ml.group(3), 16),
|
|
218
|
+
perms=perms(flags), valid=bool(flags & 1), flags=flags)
|
|
219
|
+
procs.setdefault(pid, ProcVm(pid=pid)).leaves.append(pte)
|
|
220
|
+
return procs
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def shared_frames(vmall: dict) -> dict:
|
|
224
|
+
"""Physical pages mapped by MORE THAN ONE process — i.e. sharing, derived directly from the
|
|
225
|
+
page tables (no kernel refcount needed). Restricted to USER pages so the trampoline/trapframe
|
|
226
|
+
kernel mappings (shared by everyone by construction) don't drown out the COW signal.
|
|
227
|
+
|
|
228
|
+
Returns {pa: sorted [pids]} for every physically-shared user frame — this is what makes
|
|
229
|
+
'parent and child share pages until one writes' visible: watch a pa's pid-list shrink from
|
|
230
|
+
[parent, child] to [parent] on the first write."""
|
|
231
|
+
owners: dict = {}
|
|
232
|
+
for pid, pv in vmall.items():
|
|
233
|
+
for pte in pv.leaves:
|
|
234
|
+
if pte.user and pte.valid:
|
|
235
|
+
owners.setdefault(pte.pa, set()).add(pid)
|
|
236
|
+
return {pa: sorted(pids) for pa, pids in owners.items() if len(pids) > 1}
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def classify_faults(faults: list, vmall: dict | None = None,
|
|
240
|
+
regions: list | None = None) -> list:
|
|
241
|
+
"""Best-effort label for each fault, using whatever address-space picture we have at read
|
|
242
|
+
time. Heuristic (the kernel stays dumb, the classification is improvable pure Python):
|
|
243
|
+
|
|
244
|
+
• store to a present, non-writable user page -> cow-write (the write that triggers a copy)
|
|
245
|
+
• fault on an unmapped VA below p->sz -> lazy-alloc (demand paging the heap)
|
|
246
|
+
• fault on an unmapped VA inside a valid region -> lazy-alloc / stack-growth
|
|
247
|
+
• otherwise -> illegal (out of any region -> segfault)
|
|
248
|
+
"""
|
|
249
|
+
for f in faults:
|
|
250
|
+
pv = (vmall or {}).get(f.pid)
|
|
251
|
+
page_va = f.va & ~(PGSIZE - 1)
|
|
252
|
+
leaf = next((p for p in pv.leaves if p.va == page_va), None) if pv else None
|
|
253
|
+
if leaf is not None and not leaf.writable and f.scause == 15:
|
|
254
|
+
f.kind = "cow-write"
|
|
255
|
+
elif leaf is None and pv is not None and page_va < pv.sz:
|
|
256
|
+
f.kind = "lazy-alloc"
|
|
257
|
+
elif leaf is None and regions and region_for(f.va, regions) not in ("?",):
|
|
258
|
+
f.kind = "stack-growth" if region_for(f.va, regions) == "stack" else "lazy-alloc"
|
|
259
|
+
elif leaf is None:
|
|
260
|
+
f.kind = "illegal"
|
|
261
|
+
else:
|
|
262
|
+
f.kind = f.cause
|
|
263
|
+
return faults
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def memory_summary(vm: VmSnapshot) -> str:
|
|
267
|
+
"""Compact, LLM-facing summary of the virtual-memory state (for the Ask GINI card, L2)."""
|
|
268
|
+
if vm is None:
|
|
269
|
+
return ""
|
|
270
|
+
lines = [f"memory (virtual, Sv39): satp={hex(vm.satp)} · {len(vm.leaves)} leaf mappings"]
|
|
271
|
+
if vm.regions:
|
|
272
|
+
lines.append(" regions (low→high VA): "
|
|
273
|
+
+ ", ".join(f"{r.name}[{r.perms}]" for r in vm.regions))
|
|
274
|
+
ph = vm.phys
|
|
275
|
+
if ph and ph.total_pages:
|
|
276
|
+
lines.append(f" physical pages: {ph.used_pages} used / {ph.free_pages} free of "
|
|
277
|
+
f"{ph.total_pages} ({ph.used_frac * 100:.0f}% used)")
|
|
278
|
+
if vm.faults:
|
|
279
|
+
lines.append(" recent page faults: "
|
|
280
|
+
+ "; ".join(f"{hex(f.va)} {f.cause}" for f in vm.faults[-3:]))
|
|
281
|
+
return "\n".join(lines)
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
def region_for(va: int, regions) -> str:
|
|
285
|
+
for r in regions:
|
|
286
|
+
if r.start <= va <= r.end: # end is the region's last byte (inclusive)
|
|
287
|
+
return r.name
|
|
288
|
+
if va == TRAMPOLINE:
|
|
289
|
+
return "trampoline"
|
|
290
|
+
if va == TRAPFRAME:
|
|
291
|
+
return "trapframe"
|
|
292
|
+
return "?"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# -- offline demo ----------------------------------------------------------- #
|
|
296
|
+
class DemoVm:
|
|
297
|
+
"""A deterministic small user address space + simulated growth/faults, so the Memory face is
|
|
298
|
+
explorable and testable without QEMU. Faithful-shaped, not a real MMU."""
|
|
299
|
+
|
|
300
|
+
def __init__(self) -> None:
|
|
301
|
+
self.satp = 0x8000000000087f6e
|
|
302
|
+
self._sp = 0x4000 # top of a one-page user stack region
|
|
303
|
+
self.phys = PhysMem(total_pages=32768, free_pages=32510)
|
|
304
|
+
self._fault_log: list = [] # simulated demand-fault log (NOT the faults() ring)
|
|
305
|
+
self._leaves = [
|
|
306
|
+
Pte(0x0000, 0x87f6b000, "r-x-"), # text
|
|
307
|
+
Pte(0x1000, 0x87f6a000, "rw--"), # data
|
|
308
|
+
Pte(0x2000, 0x87f69000, "rw--"), # bss/heap
|
|
309
|
+
Pte(0x3000, 0x87f68000, "----"), # guard page (no access)
|
|
310
|
+
Pte(0x4000, 0x87f67000, "rw-u"), # user stack
|
|
311
|
+
Pte(TRAPFRAME, 0x87f66000, "rw--"), # trapframe
|
|
312
|
+
Pte(TRAMPOLINE, 0x87f6f000, "r-x-"), # trampoline (shared)
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
def _regions(self):
|
|
316
|
+
return [
|
|
317
|
+
Region("text", 0x0000, 0x0FFF, "r-x-"),
|
|
318
|
+
Region("data", 0x1000, 0x1FFF, "rw--"),
|
|
319
|
+
Region("heap", 0x2000, 0x2FFF, "rw--"),
|
|
320
|
+
Region("guard", 0x3000, 0x3FFF, "----"),
|
|
321
|
+
Region("stack", 0x4000, self._sp - 1, "rw-u") if self._sp > 0x4000
|
|
322
|
+
else Region("stack", 0x4000, 0x4FFF, "rw-u"),
|
|
323
|
+
Region("trapframe", TRAPFRAME, TRAPFRAME, "rw--"),
|
|
324
|
+
Region("trampoline", TRAMPOLINE, TRAMPOLINE, "r-x-"),
|
|
325
|
+
]
|
|
326
|
+
|
|
327
|
+
def snapshot(self) -> VmSnapshot:
|
|
328
|
+
return VmSnapshot(satp=self.satp, leaves=list(self._leaves), regions=self._regions(),
|
|
329
|
+
phys=self.phys, faults=list(self._fault_log), source="demo",
|
|
330
|
+
have=("pagetable", "regions", "phys", "faults"))
|
|
331
|
+
|
|
332
|
+
def simulate_fault(self) -> VmSnapshot:
|
|
333
|
+
"""Grow the stack by one page on a fault (lazy/demand allocation): record the fault,
|
|
334
|
+
allocate a physical page, and add the new leaf mapping."""
|
|
335
|
+
new_va = 0x4000 + len(self._fault_log) * PGSIZE + PGSIZE
|
|
336
|
+
self._fault_log.append(Fault(new_va, "store page fault (stack growth)", pid=3))
|
|
337
|
+
if self.phys.free_pages > 0:
|
|
338
|
+
self.phys.free_pages -= 1
|
|
339
|
+
self._sp = new_va + PGSIZE
|
|
340
|
+
self._leaves.insert(5, Pte(new_va, 0x87f60000 - len(self._fault_log) * PGSIZE, "rw-u"))
|
|
341
|
+
return self.snapshot()
|
|
342
|
+
|
|
343
|
+
# -- COW / all-procs demo (offline) ------------------------------------- #
|
|
344
|
+
_COW = 1 | 2 | 16 | 0x100 # V R U + RSW8 : present, read-only, user, COW-tagged
|
|
345
|
+
_RW = 1 | 2 | 4 | 16 # V R W U : present, writable, user (private after copy)
|
|
346
|
+
_RX = 1 | 2 | 8 | 16 # V R X U : text
|
|
347
|
+
|
|
348
|
+
def __post_cow(self):
|
|
349
|
+
if not hasattr(self, "_cow_written"):
|
|
350
|
+
self._cow_written = False
|
|
351
|
+
|
|
352
|
+
def all_procs(self) -> dict:
|
|
353
|
+
"""A parent+child pair right after fork(): they SHARE the data/stack physical pages,
|
|
354
|
+
write-protected and COW-tagged, until `simulate_cow_write()` copies the child's data page.
|
|
355
|
+
This is the observable heart of the COW assignment."""
|
|
356
|
+
self.__post_cow()
|
|
357
|
+
text = Pte(0x0000, 0x87001000, perms(self._RX), True, self._RX)
|
|
358
|
+
# shared, COW-marked pages (same PA in both procs)
|
|
359
|
+
data_shared = Pte(0x1000, 0x87002000, perms(self._COW), True, self._COW)
|
|
360
|
+
stack_shared = Pte(0x3000, 0x87003000, perms(self._COW), True, self._COW)
|
|
361
|
+
parent = ProcVm(3, "forktest", 0x6000, [text, data_shared, stack_shared,
|
|
362
|
+
Pte(TRAPFRAME, 0x87f10000, perms(self._RW & ~16), True, self._RW & ~16)])
|
|
363
|
+
child_data = (Pte(0x1000, 0x87005000, perms(self._RW), True, self._RW) # copied -> private
|
|
364
|
+
if self._cow_written else data_shared)
|
|
365
|
+
child = ProcVm(4, "forktest", 0x6000, [text, child_data, stack_shared,
|
|
366
|
+
Pte(TRAPFRAME, 0x87f11000, perms(self._RW & ~16), True, self._RW & ~16)])
|
|
367
|
+
return {3: parent, 4: child}
|
|
368
|
+
|
|
369
|
+
def simulate_cow_write(self) -> dict:
|
|
370
|
+
"""The child writes its shared data page -> the store page-fault copies it: a fresh
|
|
371
|
+
physical frame, now writable and no longer COW-tagged. The shared PA's owner list shrinks
|
|
372
|
+
from [parent, child] to [parent]."""
|
|
373
|
+
self.__post_cow()
|
|
374
|
+
self._cow_written = True
|
|
375
|
+
if self.phys.free_pages > 0:
|
|
376
|
+
self.phys.free_pages -= 1
|
|
377
|
+
return self.all_procs()
|
|
378
|
+
|
|
379
|
+
def faults(self) -> list:
|
|
380
|
+
"""A representative fault stream: a COW write, a lazy heap fault, and an illegal access."""
|
|
381
|
+
return [
|
|
382
|
+
PageFault(4, 15, 0x1000, 0x0000000000001abc), # store to the shared data page -> cow
|
|
383
|
+
PageFault(3, 15, 0x5000, 0x0000000000001de0), # unmapped, below sz -> lazy-alloc
|
|
384
|
+
PageFault(3, 13, 0x40000000, 0x0000000000002100), # way out of range -> illegal
|
|
385
|
+
]
|
gini/gloader.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""gLoader -- the GINI topology loader (top-level entry point).
|
|
2
|
+
|
|
3
|
+
The implementation lives in :mod:`gini.services.gloader`; this module exposes it as a
|
|
4
|
+
headline GINI component (alongside gBuilder and gRouter) and provides the CLI::
|
|
5
|
+
|
|
6
|
+
python -m gini.gloader topology.gini # compile the spec + launch on Docker
|
|
7
|
+
python -m gini.gloader topology.gini --sim # run the in-process simulator
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from .services.gloader import GLoader, main
|
|
12
|
+
|
|
13
|
+
__all__ = ["GLoader", "main"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if __name__ == "__main__":
|
|
17
|
+
raise SystemExit(main())
|
gini/runtime/__init__.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""GINI portable runtime — user-space data plane (Ethernet-in-UDP).
|
|
2
|
+
|
|
3
|
+
Productized from the proven R0 spike: machines (TAP+shuttle) and a fabric of
|
|
4
|
+
user-space switches and gRouters, wired by UDP. The compiler lowers a Topology
|
|
5
|
+
onto these; the orchestrator launches them (in-process for tests, Docker for real).
|
|
6
|
+
"""
|
|
7
|
+
from .frame import (
|
|
8
|
+
BROADCAST, ETH_ARP, ETH_IP, PROTO_ICMP, build_eth, parse_eth,
|
|
9
|
+
)
|
|
10
|
+
from .grouter import Router
|
|
11
|
+
from .hostsim import HostSim
|
|
12
|
+
from .switch import Hub, LearningSwitch, make_switch
|
|
13
|
+
from .transport import Port, run_loop
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"Router", "HostSim", "LearningSwitch", "Hub", "make_switch", "Port", "run_loop",
|
|
17
|
+
"BROADCAST", "ETH_ARP", "ETH_IP", "PROTO_ICMP", "build_eth", "parse_eth",
|
|
18
|
+
]
|