13 KiB
Kontenery i debug
Ten dokument opisuje wdrożony kontrakt kontenerów i jawnie wskazuje pozostałe
etapy. Pełne
uzasadnienie, przepływ ucznia i kolejność wdrożenia są w
doc/architecture-plan.md.
Stan obecny:
stemctluruchamia trzy profile przez rootless Podman;- repo środowiska zawiera wieloetapowy
Dockerfilei Compose z dokładnie trzema usługami; gotowych obrazów nie publikujemy; - profil RP2350 zawiera oba toolchainy, Pico SDK, FreeRTOS, OpenOCD i picotool;
rvctl,hostirv32ipozostają aliasami okresu zgodności.
Profile docelowe
| Profil | Alias przejściowy | Service | Target Dockerfile | Targety |
|---|---|---|---|---|
native-amd64 |
host |
native-amd64 |
native-amd64-final |
native |
hazard3-sim |
rv32i |
hazard3-sim |
hazard3-sim-final |
hazard3-baremetal; hazard3-freertos po BSP timer/IRQ |
rp2350 |
brak | rp2350 |
rp2350-final |
rp2350-rv, rp2350-arm |
Wszystkie targety dziedziczą stage dev-ui-base. Nie jest on osobnym
kontenerem roboczym.
Jednolity kontrakt CLI
stemctl env list
stemctl env ensure native-amd64
stemctl env ensure hazard3-sim
stemctl env ensure rp2350
stemctl build native-amd64 inf bss 1
stemctl test native-amd64 inf bss 1
stemctl run native-amd64 inf bss 1
stemctl debug native-amd64 inf bss 1
stemctl build hazard3-sim inf bss 1 --target hazard3-baremetal
stemctl test hazard3-sim inf bss 1 --target hazard3-baremetal
stemctl run hazard3-sim inf bss 1
stemctl debug hazard3-sim inf bss 1
stemctl build rp2350 inf bss 1 --target rp2350-rv
stemctl deploy rp2350 inf bss 1 --target rp2350-rv --device /dev/bus/usb/001/006
stemctl debug rp2350 inf bss 1 --target rp2350-rv --device /dev/bus/usb/001/006
Selektory series card task są opcjonalne, jeśli ustawiono wartości domyślne.
Każda akcja obsługuje:
--instance NAME
--target NAME
--editor nvim|vim
--dry-run
debug dodatkowo może przyjąć --pane TARGET i --device PATH. deploy
przyjmuje --device PATH oraz --backend probe|bootsel. probe list --json
ma stabilny format maszynowy, a akcje zapisują maszynowy result.json.
deploy jest capability wyłącznie targetów sprzętowych RP2350. W
hazard3-sim artefakt ładuje run albo debug; osobne deploy zwraca
unsupported.
Capabilities zamiast wyjątków w kodzie
Launcher nie powinien zawierać warunków typu „jeśli profil rv32i, uruchom ten konkretny skrypt”. Profil publikuje capabilities i adaptery:
{
"profile": "rp2350",
"targets": ["rp2350-rv", "rp2350-arm"],
"actions": {
"build": {"requires": []},
"test": {"requires": []},
"run": {"requires": ["matching-deploy-record"]},
"debug": {"requires": ["probe"]},
"deploy": {"requires": ["probe"]},
"shell": {"requires": []}
},
"debug_backends": ["openocd"],
"devices": ["debug-probe", "target-usb-optional"]
}
Karta deklaruje target, a profil dostarcza implementację. Brak capability jest
normalnym, maszynowo czytelnym wynikiem unsupported, nie awarią launchera.
Wspólny interfejs w kontenerze
Każdy profil zapewnia kanoniczny dispatcher:
/usr/local/bin/stem-entry
/usr/local/bin/stem-card
Dispatcher przyjmuje build/test/run/debug/deploy; manifest może zwrócić
unsupported, jeżeli akcja nie ma sensu dla targetu.
Skrypty korzystają wyłącznie z manifestu karty i zmiennych kontraktowych:
STEM_WORKSPACE=/workspace
STEM_PROFILE=hazard3-sim
STEM_TARGET=hazard3-freertos
STEM_CARD=inf/bss
STEM_TASK=task1
STEM_INSTANCE=hazard3-sim-inf-bss-t1
STEM_STATE_DIR=/workspace/.stem/instances/<instance>
STEM_ARTIFACT_DIR=/workspace/.stem/artifacts/<profile>/<target>/<task>
Stare RV_* są czytane wyłącznie jako fallback w okresie migracji.
Montowania
Minimalny zestaw:
| Źródło hosta | Cel | Tryb |
|---|---|---|
| repo karty | /workspace |
rw |
| cache profilu | /cache |
rw, osobny named volume |
$XDG_RUNTIME_DIR/stem |
ta sama krótka ścieżka | rw |
| konkretne urządzenie probe | urządzenie | tylko profil rp2350 |
Nie montujemy:
$HOME/.ssh;- pliku tokenów;
- całego
/dev; - socketu Docker/Podman;
- katalogu domowego hosta;
- repo innych uczniów.
Repo jest własnością użytkownika hosta. Rootless Podman uruchamia kontener z
mapowaniem UID/GID (keep-id albo równoważnym), żeby artefakty nie powstawały
jako root.
Layout tmuxa i Neovima
W każdym profilu po debug powstaje ten sam układ:
work
├─ pane 0 (góra): jeden Neovim
│ ├─ okno lewe: Termdebug/GDB dashboard
│ └─ okno prawe: edytor źródeł i nvim-dap
└─ pane 1 (dół): bash w /workspace
Lewa i prawa część są oknami Neovima, nie osobnymi pane'ami tmuxa. Hazard3
trzyma symulator w ukrytym oknie tmuxa tb. Prefix tmuxa to Ctrl-s. Neovim
ma identyczne mapowania Termdebug i nvim-dap.
Opis backendu GDB pochodzi z jednego target descriptor, dzięki czemu oba
frontendy nie rozjeżdżają się.
Debugger według profilu
native-amd64
- GDB jest backendem domyślnym;
- LLDB pozostaje dostępny ręcznie w shellu; wspólny interfejs Termdebug używa GDB;
- przed debugowaniem powstaje build
-g3 -O0; testuruchamia co najmniej ASan i UBSan.
hazard3-sim
- obecny codzienny backend:
fast-rsp, jawnie opisany jako uproszczony; - docelowy, planowany backend:
fast-dm, czyli GDB RSP -> APB/DMI -> prawdziwy DM Hazard3; - gate zgodności: OpenOCD + JTAG remote-bitbang;
- planowany simulatorowy BSP FreeRTOS ma używać timera 1 kHz, czyli ticka co 150 000 cykli przy zegarze gościa 150 MHz; nie jest jeszcze wdrożony.
rp2350
- OpenOCD/probe dla targetów RISC-V i ARM;
- picotool dla informacji, resetu i operacji wspieranych przez RP2350;
deployzawsze raportuje probe, target, artefakt i jego SHA-256;- launcher nie wybiera pierwszego przypadkowego probe, gdy widocznych jest kilka urządzeń.
Zewnętrzny debug probe i natywne USB płytki są rozdzielone. Standardowy
debug/flash używa stabilnego probe, który nie re-enumeruje się przy resecie
targetu. BOOTSEL/picotool jest opcjonalną, krótkotrwałą akcją z tego samego
profilu: launcher wykrywa bieżący węzeł USB przed każdą fazą i ponownie po
re-enumeracji. Nie przekazuje całego /dev. Build i test offline nie wymagają
żadnego urządzenia.
run rp2350 wymaga zgodnego rekordu ostatniego deploy dla wybranej płytki i
artefaktu. Bez niego odmawia wykonania, chyba że użytkownik jawnie zaakceptuje
istniejący firmware. Zapobiega to testowaniu starego programu.
Źródła projektu
Źródła kart i projektów pobiera wyłącznie stemctl na hoście, zawsze pod
~/dev/workspace danego konta Unix. Kanoniczny workspace STEM ma postać
~/dev/workspace/stem; nie używamy do pracy katalogów kontenera ani
przypadkowych klonów poza ~/dev/workspace. Pierwszy klon launchera trafia do
~/dev/workspace/stem/tools/stem-launcher i od niego zaczyna się cały
bootstrap workspace. Typowy przepływ to:
stemctl workspace sync
stemctl series cards fetch inf pointers
stemctl debug rp2350 inf pointers 4
Katalog karty pozostaje na hoście, na przykład
~/dev/workspace/stem/series/inf/pointers/, i jest przekazywany kontenerowi
jako bind-mount /workspace. Nie klonujemy repozytoriów źródłowych wewnątrz
kontenera: kontenery są odtwarzalne i mogą zostać usunięte, natomiast host
zachowuje Git, zmiany ucznia, odpowiedzi i historię pracy. W obrazie lub
osobnym cache volume mogą znajdować się wyłącznie narzędzia, zależności i
odtwarzalne cache budowania.
Dla Hazard3 po przygotowaniu przez stemctl env sources SERIES CARD ten sam
bind-mount zawiera również rzeczywiste (niebędące symlinkami) katalogi
vendor/Hazard3 i vendor/lab-runtime. Program, testbench oraz GDB korzystają
wyłącznie z nich. Build testbencha i ELF trafia do
.stem/instances/<instance>/build, a DWARF zachowuje ścieżki
/workspace/vendor/Hazard3/... i /workspace/src/.... /opt pozostaje
miejscem toolchainów i programów obrazu; nie jest źródłem kodu wyświetlanego w
Neovimie ani GDB.
Instancje i sockety MCP
Stabilna nazwa logiczna:
<profile>-<series>-<card>-<task>
Aktualny container ID jest pobierany z podman inspect. Pełne nazwy logiczne
są w labels i registry, a sockety używają krótkich kluczy:
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/t.sock
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/n.sock
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/current
Klucze wątku i instancji mają 12 znaków, cid12 jest prefiksem bieżącego ID.
Registry przechowuje pełne wartości. Host i kontener używają tej samej ścieżki,
a całkowita długość musi być mniejsza lub równa 100 bajtów, pozostawiając
zapas względem linuksowego limitu sun_path.
Wrapper MCP otrzymuje instance, sprawdza label kontenera, aktualny ID i typ
socketu. Dopiero wtedy łączy się z tmuxem albo Neovimem. Pozwala to Codexowi
obsługiwać kilka kontenerów bez przypadkowego wejścia do starej sesji.
Publiczne wejścia launchera to stemctl mcp tmux INSTANCE oraz
stemctl mcp nvim INSTANCE. Hostowy wrapper wykonuje wyłącznie resolver i
podman exec; sam serwer wraz z przypiętymi zależnościami działa wewnątrz
zweryfikowanego kontenera. Neovim MCP publikuje również stan i komendy
Termdebug/nvim-dap.
Dla jednego stale działającego klienta hostowego dostępny jest również jawny, atomowy przełącznik obu socketów:
stemctl mcp list
stemctl mcp select INSTANCE_OR_CONTAINER_ID
stemctl mcp status
mcp select sprawdza registry, pełny ID i label kontenera, aktywnie testuje
serwery Neovima i tmuxa, a następnie jednym rename(2) przełącza symlink
$XDG_RUNTIME_DIR/stem/mcp-selected/current. Jeśli choć jeden serwer nie
odpowiada, poprzedni wybór pozostaje bez zmian.
Katalog socketów jest tworzony przez launcher na hoście i bind-mountowany do
kontenera pod identyczną ścieżką. Dopiero nvim i tmux działające w
kontenerze tworzą odpowiednio n.sock i t.sock; hostowy Codex łączy się z
tym samym plikiem przez widok hosta. Sockety nie są przekazywane przez sieć ani
nie dają kontenerowi dostępu do socketu Podmana.
Połączenia MCP mają profil observe, assist albo full. full jest
przeznaczony dla nauczyciela: może wykonywać dowolne działania dostępne w
Neovimie, tmuxie i terminalu wybranego kontenera jako konto ucznia. Profile
observe i assist muszą używać oddzielnych, ograniczonych providerów;
nie wolno udawać ograniczenia przez samą zmienną środowiskową przy providerze
udostępniającym ogólny Vimscript lub polecenia pane’a.
MCP nie jest czwartym kontenerem. Bridge jest częścią dev-ui-base, a hostowy
resolver jest częścią stem-launcher.
Cykl życia
stemctl start hazard3-sim inf bss 1
stemctl status hazard3-sim --instance hazard3-sim-inf-bss-t1
stemctl attach hazard3-sim --instance hazard3-sim-inf-bss-t1
stemctl stop hazard3-sim --instance hazard3-sim-inf-bss-t1
stemctl rm hazard3-sim --instance hazard3-sim-inf-bss-t1
start jest idempotentne dla zgodnej instancji. Jeżeli zmienił się lokalny ID
wyniku builda albo konfiguracja targetu, launcher odtwarza kontener.
stop/start zachowuje container ID; rm/start tworzy nowy ID i nowy katalog
socketów. Stan karty pozostaje na hoście.
Zdalny komputer
Na komputer ucznia wchodzimy przez SSH z kluczem i uruchamiamy stemctl tam:
ssh uczen-lab
cd ~/dev/workspace/stem/tools/stem-launcher
./stemctl status hazard3-sim --instance hazard3-sim-inf-bss-t1
./stemctl debug hazard3-sim inf bss 1
GDB, UART i MCP nasłuchują na loopback albo Unix socketach. Jeżeli potrzebny
jest dostęp z komputera nauczyciela, launcher generuje jawne polecenie tunelu
SSH. Nie otwiera portów na 0.0.0.0.
Budowanie i cache
dev-ui-base zawiera przypięte binaria/dependencies UI, lecz nie zawiera
często zmienianych configs/ i entrypointów. Trzy toolchainowe stage'e
dziedziczą z niego, instalują ciężkie SDK/kompilatory, a dopiero finalne stage'e
kopiują konfigurację UI i skrypty. Kod karty jest montowany, nie kopiowany.
Zmiana entrypointu, dashboardu albo mapowania klawisza nie może invalidować
warstwy pobierającej toolchain.
Status implementacji
| Element | Status |
|---|---|
native-amd64 |
wdrożony i sprawdzony z ASan/UBSan |
hazard3-sim |
wdrożony dla bare metal; fast-dm i BSP FreeRTOS są następne |
rp2350 |
wdrożony; FreeRTOS 1 kHz buduje ELF/UF2 dla RV i ARM, hardware gate wymaga ACL probe |
stemctl |
wdrożony; rvctl jest wrapperem zgodności |
| pobieranie kart z Git | istnieje w rvctl |
build/test/run/debug/deploy |
wdrożony dispatcher manifestu i result.json |
| sockety tmux/nvim | wdrożone z thread12/instance12/container12 i resolverem |
| zdalny SSH | kontrakt zaprojektowany, smoke test do dodania |