feat: add lab-rv32i-c-smalloc-sbrk-libc card

This commit is contained in:
user
2026-07-21 19:14:20 +02:00
commit d522c15f75
33 changed files with 5965 additions and 0 deletions
+10
View File
@@ -0,0 +1,10 @@
/build/
/host-build/
__pycache__/
*.pyc
/doc/generated/*.aux
/doc/generated/*.fdb_latexmk
/doc/generated/*.fls
/doc/generated/*.log
/doc/generated/*.out
/doc/generated/*.upa
+161
View File
@@ -0,0 +1,161 @@
RISCV_PREFIX ?= riscv64-unknown-elf-
CC := $(RISCV_PREFIX)gcc
OBJCOPY := $(RISCV_PREFIX)objcopy
OBJDUMP := $(RISCV_PREFIX)objdump
READELF := $(RISCV_PREFIX)readelf
NM := $(RISCV_PREFIX)nm
SIZE := $(RISCV_PREFIX)size
HOST_CC ?= cc
LOCAL_RV_ENV := $(abspath ../../../../../edu/rv32i-hazard3-student-env)
ifneq ($(wildcard /opt/rv-env/vendor/Hazard3/test/sim/common/init.S),)
RV_ENV_ROOT ?= /opt/rv-env
else
RV_ENV_ROOT ?= $(LOCAL_RV_ENV)
endif
H3_ROOT ?= $(RV_ENV_ROOT)/vendor/Hazard3
H3_COMMON ?= $(H3_ROOT)/test/sim/common
H3_INIT ?= $(H3_COMMON)/init.S
LOCAL_H3_TB := $(H3_ROOT)/test/sim/tb_verilator/tb
PREBUILT_H3_TB := $(abspath ../../freertos/lab-rv32i-freertos-c-scheduler/vendor/Hazard3/test/sim/tb_verilator/tb)
H3_TB ?= $(if $(wildcard $(LOCAL_H3_TB)),$(LOCAL_H3_TB),$(if $(wildcard $(PREBUILT_H3_TB)),$(PREBUILT_H3_TB),$(LOCAL_H3_TB)))
H3_TB_DIR ?= $(dir $(H3_TB))
PROFILE ?= observe
ifeq ($(PROFILE),observe)
OPTFLAGS := -O0 -fno-omit-frame-pointer -fno-inline -fno-optimize-sibling-calls
DBGFLAGS := -g3 -ggdb
else ifeq ($(PROFILE),debug)
OPTFLAGS := -Og -fno-omit-frame-pointer -fno-optimize-sibling-calls
DBGFLAGS := -g3 -ggdb
else ifeq ($(PROFILE),release)
OPTFLAGS := -Os
DBGFLAGS :=
else
$(error PROFILE must be observe, debug or release)
endif
ARCH ?= rv32i_zicsr_zifencei
ABI ?= ilp32
BUILD_ROOT ?= build
HOST_BUILD_ROOT ?= host-build
LINKER_SCRIPT := link/hazard3_c09.ld
TASK01 := task01_libc_without_heap
TASK02 := task02_malloc_requires_sbrk
TASK03 := task03_bounded_sbrk
TASK04 := task04_smalloc
TASK05 := task05_picolibc_malloc
SUCCESS_TASKS := $(TASK01) $(TASK03) $(TASK04) $(TASK05)
RV_CFLAGS := -std=c11 -march=$(ARCH) -mabi=$(ABI) -nostdlib -nostartfiles \
-ffreestanding -fno-builtin -fno-stack-protector \
-ffunction-sections -fdata-sections $(OPTFLAGS) $(DBGFLAGS) \
-Wall -Wextra -Wpedantic -Werror -Isrc/runtime
RV_LDFLAGS := -Wl,--no-relax -Wl,--gc-sections -T$(LINKER_SCRIPT)
RV_LIBS := -Wl,--start-group -lc -lgcc -Wl,--end-group
HOST_CFLAGS := -std=c11 -DC09_HOST=1 -DHOST_PRINTF=1 -fno-builtin \
$(OPTFLAGS) $(DBGFLAGS) -Wall -Wextra -Wpedantic -Werror -Isrc/runtime
.PHONY: all tasks host check full-check clean check-support \
task1 task2 task3 task4 task5 t1 t2 t3 t4 t5 \
host-test expected-fail archive-map check-elf testbench hazard3-test card card-check
all: tasks
tasks: $(SUCCESS_TASKS)
host: $(addprefix $(HOST_BUILD_ROOT)/,$(addsuffix /prog,$(SUCCESS_TASKS)))
task1 t1: $(TASK01)
task2 t2: expected-fail
task3 t3: $(TASK03)
task4 t4: $(TASK04)
task5 t5: $(TASK05)
check-support:
@test -f "$(H3_INIT)" || { echo "Missing Hazard3 init.S: $(H3_INIT)" >&2; exit 1; }
@test -f "$(LINKER_SCRIPT)" || { echo "Missing $(LINKER_SCRIPT)" >&2; exit 1; }
define rv_task_rules
$(1): $(BUILD_ROOT)/$(1)/prog.bin $(BUILD_ROOT)/$(1)/prog.lst
$(BUILD_ROOT)/$(1)/prog.elf: crt0.S $(H3_INIT) $(LINKER_SCRIPT) $(2) | check-support
mkdir -p $$(@D)
$(CC) $(RV_CFLAGS) $(RV_LDFLAGS) -Wl,-Map,$(BUILD_ROOT)/$(1)/prog.map \
-o $$@ $(H3_INIT) crt0.S $(2) $(RV_LIBS)
$(SIZE) -A -x $$@
$(BUILD_ROOT)/$(1)/prog.bin: $(BUILD_ROOT)/$(1)/prog.elf
$(OBJCOPY) -O binary $$< $$@
$(BUILD_ROOT)/$(1)/prog.lst: $(BUILD_ROOT)/$(1)/prog.elf
$(OBJDUMP) -d -S -M no-aliases,numeric $$< > $$@
$(HOST_BUILD_ROOT)/$(1)/prog: $(2)
mkdir -p $$(@D)
$(HOST_CC) $(HOST_CFLAGS) -o $$@ $(2)
endef
$(eval $(call rv_task_rules,$(TASK01),src/tasks/$(TASK01).c))
$(eval $(call rv_task_rules,$(TASK03),src/tasks/$(TASK03).c src/runtime/c09_bounded_sbrk.c))
$(eval $(call rv_task_rules,$(TASK04),src/tasks/$(TASK04).c src/runtime/c09_bounded_sbrk.c src/runtime/c09_smalloc.c))
$(eval $(call rv_task_rules,$(TASK05),src/tasks/$(TASK05).c))
$(BUILD_ROOT)/$(TASK02)/task.o: src/tasks/$(TASK02).c
mkdir -p $(@D)
$(CC) $(RV_CFLAGS) -c -o $@ $<
expected-fail: $(BUILD_ROOT)/$(TASK02)/task.o | check-support
@set -eu; \
log="$(BUILD_ROOT)/$(TASK02)/expected-link-failure.log"; \
elf="$(BUILD_ROOT)/$(TASK02)/must-not-exist.elf"; \
rm -f "$$elf" "$$log"; \
set +e; \
$(CC) $(RV_CFLAGS) $(RV_LDFLAGS) -Wl,--wrap=sbrk \
-Wl,-Map,$(BUILD_ROOT)/$(TASK02)/expected-link-failure.map \
-o "$$elf" $(H3_INIT) crt0.S $(BUILD_ROOT)/$(TASK02)/task.o \
$(RV_LIBS) >"$$log" 2>&1; \
status=$$?; \
set -e; \
if [ "$$status" -eq 0 ]; then \
echo "FAIL: Task02 unexpectedly linked" >&2; rm -f "$$elf"; exit 1; \
fi; \
test ! -e "$$elf"; \
grep -q "undefined reference to.*__wrap_sbrk" "$$log"; \
grep -q "libc_stdlib_malloc.c.o" "$$log"; \
echo "PASS Task02: controlled link failure (__wrap_sbrk missing)"
host-test: host
./tests/test_host.sh --already-built
archive-map: $(BUILD_ROOT)/$(TASK01)/prog.elf $(BUILD_ROOT)/$(TASK05)/prog.elf
./tests/check_archive_maps.sh
check-elf: tasks
./tests/check_elf.sh
testbench:
@if [ ! -x "$(H3_TB)" ]; then $(MAKE) -C "$(H3_TB_DIR)"; fi
@test -x "$(H3_TB)"
hazard3-test: tasks testbench
@set -eu; \
for task in $(SUCCESS_TASKS); do \
echo "RUN Hazard3 $$task"; \
"$(H3_TB)" --bin "$(abspath $(BUILD_ROOT))/$$task/prog.bin" \
--cycles 1000000 --cpuret; \
done
card:
./scripts/build_new_card.sh
GITEA_SHA=$${GITEA_SHA:-0000000000000000000000000000000000000000} \
./scripts/render_new_pdf.sh
card-check: card
./tests/check_card.sh
check: host-test expected-fail archive-map check-elf
full-check: check hazard3-test card-check
clean:
rm -rf $(BUILD_ROOT) $(HOST_BUILD_ROOT)
+59
View File
@@ -0,0 +1,59 @@
# C09 — `smalloc`, `sbrk` i minimalne libc
Pięć krótkich eksperymentów rozdziela trzy warstwy, które łatwo pomylić:
funkcję biblioteczną niewymagającą sterty, kontrakt `malloc``sbrk` oraz
politykę prostego alokatora przyrostowego. Karta używa rzeczywistego toolchaina
GCC 15.2 z picolibc dla RV32I/Hazard3.
## Przykłady
| Task | Pytanie rozstrzygane przez program | Wynik |
|---|---|---|
| 01 | Czy każda funkcja libc wymaga sterty? | `strlen.c.o` trafia do mapy, człony `malloc`/`sbrk` nie. |
| 02 | Co się stanie bez dostawcy `sbrk`? | Kontrolowany błąd linkera: brak `__wrap_sbrk`. |
| 03 | Jaki jest minimalny bezpieczny kontrakt przesuwania break? | `heap_start <= brk <= heap_end`; błąd nie przesuwa break. |
| 04 | Co naprawdę gwarantuje `smalloc`? | Nagłówek przed payloadem, 16 B alignment, append-only, tylko reset całej areny. |
| 05 | Jak zachowuje się prawdziwe `malloc/free` picolibc? | Dwa rozłączne bloki, zapis/odczyt, `free`; implementacja pozostaje czarną skrzynką. |
Każdy przykład na karcie ma dokładnie widoki A1A8. A3 jest oznaczony
`N/D`, bo w żadnym przykładzie nie ma callbacku, ISR ani dynamicznego wyboru
wykonawcy. Task02 nie ma A6 runtime, ponieważ zgodnie z celem nie powstaje ELF.
## Ważne nazwy symboli
`vendor/Hazard3/test/sim/common/init.S` definiuje `_sbrk` z podkreśleniem.
Picolibc w tym toolchainie definiuje i wywołuje `sbrk` bez podkreślenia.
Są to różne symbole; Task05 celowo zachowuje oba w jednym ELF-ie. Task03 i
Task04 używają nazwy `c09_sbrk`, aby nie podszywać się pod libc.
Własny skrypt `link/hazard3_c09.ld` udostępnia picolibc symbole
`__heap_start` i `__heap_end`, rezerwuje 64 KiB na stos przy końcu 16 MiB RAM
i zatrzymuje linkowanie przez `ASSERT`, jeżeli sterta nachodzi na stos.
## Budowanie i dowody
```bash
make host-test
make expected-fail
make tasks archive-map check-elf
make hazard3-test
make card-check
```
`make task2` ma zakończyć się sukcesem jako test, ale zapisany w logu link
programu ma zakończyć się porażką. Nie jest to zepsuty build: flaga
`--wrap=sbrk` przekierowuje odwołanie picolibc na celowo niezdefiniowany
`__wrap_sbrk`, dzięki czemu zależność jest dowodzona deterministycznie.
Udane obrazy RV32I znajdują się w `build/<task>/` jako ELF, BIN, listing i
mapa linkera. Task02 ma tylko obiekt, mapę częściowego linku i
`expected-link-failure.log`; plik `must-not-exist.elf` jest zabroniony przez
test.
## Zakres modelu `smalloc`
`c09_smalloc` jest celowo mały: wyrównuje całkowity blok do 16 B, zapisuje
16-bajtowy nagłówek bezpośrednio przed payloadem i dopisuje kolejny blok na
końcu. Nie ma `c09_sfree`, listy wolnych bloków, split ani coalescing.
`c09_smalloc_reset` unieważnia całą arenę naraz i służy tylko do kontrolowanego
ćwiczenia. Task05 nie wyciąga z tego modelu wniosków o wnętrzu picolibc.
+36
View File
@@ -0,0 +1,36 @@
.section .text.startup,"ax",@progbits
.global _start
.type _start, @function
_start:
.option push
.option norelax
la gp, __global_pointer$
.option pop
/* One static TLS block is enough for this single-hart lab. */
la tp, __tls_start
la a0, __tbss_start
la a1, __tbss_end
clear_tbss:
bgeu a0, a1, finish_tbss
sb zero, 0(a0)
addi a0, a0, 1
j clear_tbss
finish_tbss:
la a0, __bss_start
la a1, __bss_end
clear_bss:
bgeu a0, a1, finish_bss
sb zero, 0(a0)
addi a0, a0, 1
j clear_bss
finish_bss:
call main
tail _exit
.size _start, .-_start
Binary file not shown.
+593
View File
@@ -0,0 +1,593 @@
% Generated from json/card_source.json by tools/render_card.py.
% Do not edit manually; update the JSON and regenerate.
\documentclass[12pt,a4paper]{article}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage[utf8]{inputenc}
\usepackage[polish]{babel}
\usepackage{csquotes}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage{xcolor}
\usepackage[a4paper,left=2.15cm,right=2.15cm,top=0.5cm,bottom=0.5cm,headheight=24mm,headsep=3mm,includehead]{geometry}
\usepackage{eso-pic}
\usepackage{marginnote}
\usepackage{array}
\usepackage{tabularx}
\usepackage{graphicx}
\usepackage{pdflscape}
\usepackage{float}
\usepackage{silence}
\WarningFilter{soulutf8}{This package is obsolete}
\usepackage{pdfcomment}
\usepackage{enumitem}
\usepackage{needspace}
\usepackage{fancyhdr}
\usepackage{lastpage}
\usepackage{microtype}
\usepackage[most]{tcolorbox}
\usepackage{qrcode}
\IfFileExists{references.bib}{%
\usepackage[backend=biber,style=numeric,sorting=none]{biblatex}%
\addbibresource{references.bib}%
}{}
\hypersetup{hidelinks}
\IfFileExists{build-meta.tex}{%
\input{build-meta.tex}%
}{%
\newcommand{\BuildCommit}{lokalna}%
}
\newcommand{\DocumentAuthor}{M. Pabiszczak}
\newcommand{\DocumentYear}{2026}
\newcommand{\CardSeries}{c}
\newcommand{\CardNumber}{09}
\newcommand{\CardCount}{13}
\newcommand{\CardSlug}{smalloc-sbrk-libc}
\newcommand{\CardVersion}{v00.01}
\newcommand{\DocumentUUID}{47826ae5-6370-5ac2-bd91-9acb89116d40}
\newcommand{\EmptyCheck}{\(\square\)}
\newcommand{\EscAnswerLines}[1][3]{\par\noindent\dotfill\par\noindent\dotfill\par\noindent\dotfill}
\newcommand{\EscWriteRow}[1][2.8em]{\rule{0pt}{#1}}
\setlength{\parindent}{0pt}
\setlength{\parskip}{0.45em}
\setlength{\headheight}{24mm}
\setlength{\headsep}{3mm}
\setlength{\footskip}{8mm}
\setlength{\marginparwidth}{1.5cm}
\setlength{\marginparsep}{0.25cm}
\renewcommand{\arraystretch}{1.22}
% Margin separator lines disabled for layout trial.
\newcommand{\ESCPageResourceHeader}{%
\begingroup%
\setlength{\parskip}{0pt}%
\setlength{\fboxsep}{0pt}%
\setlength{\fboxrule}{0.35pt}%
\setlength{\arrayrulewidth}{0.35pt}%
\noindent\fbox{%
\begin{minipage}[c][23.5mm][c]{\dimexpr\textwidth-2\fboxrule\relax}%
\begin{minipage}[c][23mm][c]{24mm}\centering%
\href{https://zsl-gitea.mpabi.pl/edu-inf/lab-rv32i-c-smalloc-sbrk-libc}{\qrcode[level=L,height=22mm]{https://zsl-gitea.mpabi.pl/edu-inf/lab-rv32i-c-smalloc-sbrk-libc}}%
\end{minipage}%
\vrule width0.35pt%
\begin{minipage}[c][23mm][c]{\dimexpr\linewidth-48mm-0.7pt\relax}%
\setlength{\tabcolsep}{0pt}%
\renewcommand{\arraystretch}{0}%
\begin{tabularx}{\linewidth}{@{}p{\dimexpr0.50000000\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.15126050\linewidth-\arrayrulewidth\relax}|X@{}}%
\parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries TITLE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{7.7}{7.9}\selectfont\ttfamily\bfseries smalloc, sbrk i minimalne libc}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries VER.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.6}{6.8}\selectfont\ttfamily\bfseries v00.01}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries DATETIME}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries 2026-07-20T00:00:00+02:00}\hspace{0.45mm}}\par} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}p{\dimexpr0.50000000\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.25210084\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.14285714\linewidth-\arrayrulewidth\relax}|X@{}}%
\parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries PROJECT}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{7.7}{7.9}\selectfont\ttfamily\bfseries Freestanding C na RV32I}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries SERIES}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries C · Freestanding RV32I and K\&R}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries CARD}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries 09/13}\hspace{0.45mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{3.2}{3.4}\selectfont\ttfamily\bfseries SHEET}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{6.4}{6.6}\selectfont\ttfamily\bfseries \thepage/\pageref{LastPage}}\hspace{0.45mm}}\par} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}p{\dimexpr0.05882353\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.05042017\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.14705882\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.09243697\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.07563025\linewidth-\arrayrulewidth\relax}|p{\dimexpr0.07563025\linewidth-\arrayrulewidth\relax}|X@{}}%
\parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries SUBJ.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries Inf.}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries PROG.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries CORE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries SCOPE}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries LEVEL}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hspace{0.15mm}{\fontsize{2.85}{3}\selectfont\ttfamily\bfseries POS.}\par\nointerlineskip\vspace{0.12mm}\noindent\makebox[\linewidth][r]{{\fontsize{4.45}{4.65}\selectfont\ttfamily\bfseries}\hspace{0.15mm}}\par} & \parbox[c][5.19414mm][c]{\linewidth}{\hbox to\linewidth{\hspace{0.35mm}{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries GITEA UUID}\hfill{\fontsize{3.4}{3.75}\selectfont\ttfamily 47826ae5-6370-5ac2-bd91-9acb89116d40}\hspace{0.35mm}}\par\nointerlineskip\hbox to\linewidth{\hspace{0.35mm}{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries CARD UUID} {\fontsize{3.4}{3.75}\selectfont\ttfamily 8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0}\hfill{\fontsize{3.4}{3.75}\selectfont\ttfamily\bfseries AUTHOR M. Pabiszczak}\hspace{0.35mm}}} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}X@{}}%
\parbox[c][3.46276mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{4.5}{4.85}\selectfont\ttfamily\bfseries GITEA} {\fontsize{4.5}{4.85}\selectfont\ttfamily\href{https://zsl-gitea.mpabi.pl/edu-inf/lab-rv32i-c-smalloc-sbrk-libc}{\nolinkurl{https://zsl-gitea.mpabi.pl/edu-inf/lab-rv32i-c-smalloc-sbrk-libc}}}} \\%
\end{tabularx}%
\par\nointerlineskip%
\hrule height0.35pt%
\nointerlineskip%
\begin{tabularx}{\linewidth}{@{}X@{}}%
\parbox[c][3.46276mm][c]{\linewidth}{\hspace{0.45mm}{\fontsize{4.5}{4.85}\selectfont\ttfamily\bfseries HTML} {\fontsize{4.5}{4.85}\selectfont\ttfamily\href{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0}{\nolinkurl{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0}}}} \\%
\end{tabularx}%
\end{minipage}%
\vrule width0.35pt%
\begin{minipage}[c][23mm][c]{24mm}\centering%
\href{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0}{\qrcode[level=L,height=22mm]{https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0}}%
\end{minipage}%
\end{minipage}%
}%
\endgroup%
}
\pagestyle{fancy}
\fancyhf{}
\fancyhead[C]{\ESCPageResourceHeader}
\renewcommand{\headrulewidth}{0pt}
\renewcommand{\footrulewidth}{0pt}
\newcommand{\ESCMarginTag}[4]{%
\hspace*{#1}\textcolor{#2}{#3~#4}%
}
\newcommand{\ESCSectionBlockStart}{%
\Needspace{8\baselineskip}%
\par\vspace{0.35em}%
\noindent\textcolor{black!28}{\rule{\textwidth}{0.35pt}}%
\par\vspace{0.15em}%
}
\newcommand{\ESCSectionBlockEnd}{%
\par\vspace{0.45em}%
}
\newcommand{\ESCTinyStepSeparator}{%
\par\vspace{0.10em}%
\noindent{\color{black!18}\leaders\hbox{\rule{0.60em}{0.22pt}\hspace{0.38em}}\hfill\kern0pt}%
\par\vspace{0.10em}%
}
\newtcolorbox{ESCTaskFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black,boxsep=0pt,left=1.4mm,right=1.4mm,top=0.8mm,bottom=0.8mm,colbacktitle=black!7,coltitle=black,title={\ttfamily\bfseries TASK\quad #1}}
\newtcolorbox{ESCBlockFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!55,boxsep=0pt,left=1.0mm,right=1.0mm,top=0.6mm,bottom=0.6mm,colbacktitle=black!4,coltitle=black,title={\ttfamily\bfseries BLOCK\quad #1}}
\newtcolorbox{ESCStepFrame}[1]{enhanced,breakable,arc=0pt,boxrule=0.35pt,colback=white,colframe=black!28,boxsep=0pt,left=0.8mm,right=0.8mm,top=0.45mm,bottom=0.45mm,colbacktitle=black!2,coltitle=black,title={\ttfamily STEP\quad #1}}
\newtcolorbox{ESCConclusionFrame}{enhanced,breakable,arc=0pt,boxrule=0.45pt,colback=blue!2,colframe=blue!45!black,boxsep=0pt,left=1.2mm,right=1.2mm,top=0.7mm,bottom=0.7mm,colbacktitle=blue!7,coltitle=black,title={\ttfamily\bfseries WNIOSEK}}
\newcommand{\ESCLearningTreeWidget}{%
\reversemarginpar
\marginnote[%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\begin{minipage}[t]{\marginparwidth}%
\raggedright
{\bfseries\textcolor{black!65}{TECH}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\hspace*{0.28em}\textcolor{orange!85!black}{EK~LOCAL~C}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Uczeń porównuje mapę linkera, kontrolowany błąd symbolu, globalne checkpointy, wynik hosta i\textCR kod wyjścia Hazard3, a wnioski ogranicza do naprawdę zmierzonych własności.}}\par%
\hspace*{0.54em}\textcolor{blue!70!black}{KW~LOCAL~C}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Kod, przewidywanie i pomiar tworzą jeden dowód.}}\par%
\end{minipage}%
}%
\end{minipage}%
]{}
\normalmarginpar
\marginnote{%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\hspace*{0.06cm}%
\begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}%
\raggedright
{\bfseries\textcolor{black!65}{OG}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\hspace*{0.28em}\textcolor{green!50!black}{EN~LOCAL~EN~C}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Uczeń wyprowadza osobno: wybór członu archiwum, wiązanie symbolu dostawcy pamięci oraz\textCR inwarianty polityki alokacji, zamiast nazywać wszystkie trzy mechanizmy stertą.}}\par%
\hspace*{0.54em}\textcolor{blue!70!black}{KW~LOCAL~KW~C}\textcolor{black!48}{\pdftooltip[width=\textwidth]{.01}{Kod, przewidywanie i pomiar tworzą jeden dowód.}}\par%
\end{minipage}%
}%
\end{minipage}%
}
}
\begin{document}
\sloppy
\vspace{1.0em}
\noindent{\Large\bfseries Cel karty}\par
\vspace{0.35em}
Uczeń rozdziela wybór funkcji z archiwum libc, kontrakt dostawcy pamięci sbrk i politykę alokatora. Najpierw dowodzi, że strlen nie potrzebuje sterty, potem kontrolowanie zrywa zależność malloc do sbrk, buduje ograniczony model break i wyrównany smalloc, a na końcu traktuje prawdziwe malloc/free picolibc jako czarną skrzynkę sprawdzaną przez kontrakt.
\vspace{0.8em}
\noindent\textcolor{black!25}{\rule{\textwidth}{0.35pt}}
\vspace{0.7em}
\noindent{\Large\bfseries Zakres i zachowane przykłady}\par
\vspace{0.35em}
Pięć zachowanych przykładów działa na hoście i RV32I tam, gdzie istnieje program uruchamialny. Task02 jest zamierzonym expected-fail linkera i nie ma obrazu runtime. W aktualnym GCC 15.2 biblioteka to picolibc: jej malloc odwołuje się do sbrk, podczas gdy vendorowy init.S Hazard3 definiuje inny symbol \_sbrk. Własny linker ogranicza stertę symbolami \_\_heap\_start i \_\_heap\_end oraz rezerwuje 64 KiB stosu.\par\textbf{Układ każdego przykładu:} pełny profil A1--A8; widok N/D ma jawny powód, a diagram nie jest wymagany, gdy tekst daje lepszy dowód.
\vspace{0.55em}
\small\begin{tabularx}{\textwidth}{@{}>{\ttfamily\raggedright\arraybackslash}p{1.10cm}>{\ttfamily\raggedright\arraybackslash}p{1.45cm}X>{\raggedright\arraybackslash}p{2.50cm}@{}}
\textbf{K\&R} & \textbf{Numer tasku} & \textbf{Najważniejsza idea} & \textbf{Priorytet} \\ \hline
\hline
\textbf{\texttt{7.8}} & \textbf{\texttt{Task01}} & \textbf{libc.a: wybór strlen.c.o} & \textbf{kluczowe} \\ \hline
\hline
\textbf{\texttt{5.4 + ABI}} & \textbf{\texttt{Task02}} & \textbf{expected-fail: malloc -> sbrk} & \textbf{kluczowe} \\ \hline
\hline
\textbf{\texttt{5.4}} & \textbf{\texttt{Task03}} & \textbf{ograniczony break i atomowy błąd} & \textbf{kluczowe} \\ \hline
\hline
\textbf{\texttt{5.4}} & \textbf{\texttt{Task04}} & \textbf{nagłówek, alignment i reset areny} & \textbf{kluczowe} \\ \hline
\hline
\textbf{\texttt{7.8 + ABI}} & \textbf{\texttt{Task05}} & \textbf{malloc/free picolibc jako black box} & \textbf{kluczowe} \\ \hline
\end{tabularx}
\normalsize
\vspace{0.8em}
\noindent\textcolor{black!25}{\rule{\textwidth}{0.35pt}}
\clearpage
\ESCSectionBlockStart
\section{1 — Funkcja z libc nie oznacza automatycznie sterty}
\reversemarginpar
\marginnote[%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\begin{minipage}[t]{\marginparwidth}%
\raggedright
{\bfseries\textcolor{black!65}{TECH}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
]{}[-3.1em]
\normalmarginpar
\marginnote{%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\hspace*{0.06cm}%
\begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}%
\raggedright
{\bfseries\textcolor{black!65}{OG}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
}[-3.1em]
Archiwum \texttt{libc.a} nie jest włączane do programu w całości. Linker pobiera człon, który rozwiązuje potrzebny symbol, a następne człony tylko wtedy, gdy pojawią się kolejne nierozwiązane odwołania. Dlatego bezpośrednie wywołanie \texttt{strlen} z opcją \texttt{-fno-builtin} wybiera \texttt{strlen.c.o}, lecz nie wybiera \texttt{libc\_stdlib\_malloc.c.o} ani \texttt{libc\_misc\_picosbrk.c.o}. Dowodem jest mapa linkera, a nie samo dołączenie nagłówka \texttt{string.h}.
Task01 rozstrzyga wyłącznie tę zależność. Nie dowodzi, że każda funkcja tekstowa jest bezkosztowa ani że całe libc nie używa pamięci dynamicznej.
\ESCSectionBlockEnd
\ESCSectionBlockStart
\section{2 — malloc potrzebuje sbrk, lecz \_sbrk to inna nazwa}
\reversemarginpar
\marginnote[%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\begin{minipage}[t]{\marginparwidth}%
\raggedright
{\bfseries\textcolor{black!65}{TECH}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
]{}[-3.1em]
\normalmarginpar
\marginnote{%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\hspace*{0.06cm}%
\begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}%
\raggedright
{\bfseries\textcolor{black!65}{OG}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
}[-3.1em]
W badanym GCC 15.2 używane jest \textbf{picolibc}, nie newlib. Człon \texttt{libc\_stdlib\_malloc.c.o} ma niezdefiniowane odwołanie do \texttt{sbrk}. Picolibc może dostarczyć je z \texttt{libc\_misc\_picosbrk.c.o}; ten kod opiera granice na symbolach linkera \texttt{\_\_heap\_start} i \texttt{\_\_heap\_end}. Z kolei \texttt{vendor/Hazard3/.../init.S} definiuje \texttt{\_sbrk} z jednym podkreśleniem. Dla linkera są to dwa różne symbole, więc mogą współistnieć w Task05.
Task02 używa \texttt{--wrap=sbrk}: każde odwołanie do \texttt{sbrk} zostaje skierowane do celowo brakującego \texttt{\_\_wrap\_sbrk}. Oczekiwany błąd jest kontrolowanym testem krawędzi \texttt{malloc -> sbrk}, a nie awarią całego projektu.
\ESCSectionBlockEnd
\ESCSectionBlockStart
\section{3 — Od alloc z K\&R 5.4 do bounded sbrk i smalloc}
\reversemarginpar
\marginnote[%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\begin{minipage}[t]{\marginparwidth}%
\raggedright
{\bfseries\textcolor{black!65}{TECH}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
]{}[-3.1em]
\normalmarginpar
\marginnote{%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\hspace*{0.06cm}%
\begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}%
\raggedright
{\bfseries\textcolor{black!65}{OG}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
}[-3.1em]
Liniowy \texttt{alloc} z K\&R 5.4 przesuwa wskaźnik w stałej tablicy. Jest prosty, ale nie odzyskuje dowolnego bloku, nie wybiera najlepszego dopasowania, nie dzieli i nie scala wolnych obszarów. \texttt{afree} może cofnąć wskaźnik jedynie do adresu należącego do areny, więc bez dyscypliny stosowej łatwo unieważnić późniejsze przydziały. Historyczna tablica bajtów nie jest też ogólną deklaracją wyrównania dla każdego typu.
Task03 zachowuje istotę modelu, lecz jawnie wymusza \texttt{heap\_start <= brk <= heap\_end}; odrzucona operacja nie zmienia break. Task04 dodaje 16-bajtowe wyrównanie i 16-bajtowy nagłówek bezpośrednio przed payloadem. Nadal jest to alokator append-only: nie ma \texttt{sfree}, listy wolnych bloków, split ani coalescing. Jedyną operacją odzyskania jest reset całej areny, który unieważnia wszystkie wcześniejsze wskaźniki.
\ESCSectionBlockEnd
\ESCSectionBlockStart
\section{4 — Prawdziwe malloc/free jako czarna skrzynka}
\reversemarginpar
\marginnote[%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\begin{minipage}[t]{\marginparwidth}%
\raggedright
{\bfseries\textcolor{black!65}{TECH}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
]{}[-3.1em]
\normalmarginpar
\marginnote{%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\hspace*{0.06cm}%
\begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}%
\raggedright
{\bfseries\textcolor{black!65}{OG}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
}[-3.1em]
Task05 nie zakłada, że picolibc ma taki sam nagłówek lub strategię jak \texttt{c09\_smalloc}. Program sprawdza tylko publiczny kontrakt potrzebny klientowi: dwa udane przydziały dają używalne, rozłączne obszary; zapisane bajty można odczytać; adresy spełniają mierzone wyrównanie; oba bloki są przekazane do \texttt{free}. Nie odczytuje pamięci po \texttt{free} i nie wymaga ponownego użycia tego samego adresu.
Minimalny startup ustawia \texttt{gp}, pojedynczą bazę TLS \texttt{tp}, zeruje \texttt{.tbss} i \texttt{.bss}, a następnie wywołuje \texttt{main}. Skrypt linkera pozostawia \texttt{\_\_heap\_end} 64 KiB przed \texttt{\_\_stack\_top} i ma \texttt{ASSERT} zatrzymujący obraz, gdy sekcje zajmą zarezerwowany obszar.
\ESCSectionBlockEnd
\clearpage
\ESCSectionBlockStart
\section{Zadania — zachowane przykłady w profilu A1A8}
\reversemarginpar
\marginnote[%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\begin{minipage}[t]{\marginparwidth}%
\raggedright
{\bfseries\textcolor{black!65}{TECH}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
]{}[-3.1em]
\normalmarginpar
\marginnote{%
\begin{minipage}{\marginparwidth}%
\raggedright
{\fontsize{3.55}{3.95}\selectfont\ttfamily
\hspace*{0.06cm}%
\begin{minipage}[t]{\dimexpr\marginparwidth-0.06cm\relax}%
\raggedright
{\bfseries\textcolor{black!65}{OG}\par}%
\vspace{0.08em}%
\hspace*{0.00em}\textcolor{red}{WE~01}\textcolor{black!48}{}\par%
\end{minipage}%
}%
\end{minipage}%
}[-3.1em]
\begin{ESCTaskFrame}{TASK01 · strlen z libc bez sterty}
{\scriptsize\ttfamily 839b8401-9f15-5edc-a214-176c06ba5cb1}\par
\begin{ESCBlockFrame}{A1 CONTEXT · AKTYWNE — klient, archiwum i brak dostawcy pamięci}
\textbf{Zachowany przykład:} \texttt{src/tasks/task01\_libc\_without\_heap.c}.\par Klient wywołuje publiczne \texttt{strlen}. Granicą systemu jest linker wybierający potrzebny człon \texttt{libc.a}; Task01 nie deklaruje ani nie wywołuje \texttt{malloc}, \texttt{free} lub \texttt{sbrk}.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A2 STRUCTURE · AKTYWNE — jedna krawędź symboliczna}
Przy \texttt{-fno-builtin} obiekt tasku zachowuje niezdefiniowany symbol \texttt{strlen}. Linker rozwiązuje go przez \texttt{strlen.c.o}. Ten człon nie tworzy następnej krawędzi do alokatora, dlatego graf zależności kończy się bez warstwy sterty.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A3 DISPATCH · N/D}
\textbf{N/D.} strlen i checkpoint są wywołaniami bezpośrednimi; program nie ma callbacku, ISR, wskaźnika funkcji ani dynamicznego wyboru implementacji.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A4 APPLICATION · AKTYWNE — dwa niezależne warunki sukcesu}
Warunek wykonania to długość 16 dla napisu \texttt{picolibc-no-heap}. Warunek obrazu to obecność wyłącznie potrzebnego członu tekstowego i brak trzech nazw członów związanych z przydziałem. Sam kod wyjścia nie zastępuje kontroli mapy.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A5 FLOW · AKTYWNE — kompilacja, selekcja archiwum, wywołanie}
Kompilator pozostawia call do \texttt{strlen}; linker przegląda archiwum i pobiera \texttt{strlen.c.o}; startup uruchamia \texttt{main}; funkcja oblicza 16; checkpoint publikuje wynik; \texttt{main} zwraca 0. Każdy etap ma inny artefakt dowodowy.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A6 STATE · N/D}
\textbf{N/D.} Program wykonuje jednorazowy, czysty pomiar długości i nie ma domenowego obiektu przechodzącego przez istotne stany ani zasobu o cyklu życia.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A7 RUNTIME · AKTYWNE — global i mapa odpowiadają na inne pytania}
W \texttt{task01\_debug\_checkpoint} global \texttt{g\_task01\_length} ma wartość 16, a \texttt{g\_task01\_pass} wartość 1. Osobno \texttt{prog.map} wskazuje \texttt{libc.a(strlen.c.o)}; test mapy odrzuca każdą nazwę członu heap.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A8 PATTERNS · AKTYWNE — płać za osiągalne symbole, nie za nagłówek}
Dla archiwum statycznego właściwym wzorcem audytu jest śledzenie nierozwiązanych symboli i pobranych członów. \texttt{\#include <string.h>} dostarcza deklarację; nie jest poleceniem dołączenia całej implementacji libc.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{Ćwiczenie · Przewidywanie → wykonanie → wniosek}
Przed linkowaniem przewidź, które człony \texttt{libc.a} są potrzebne przez bezpośrednie \texttt{strlen}. Uruchom program, a potem w mapie udowodnij obecność \texttt{strlen.c.o} i nieobecność członów malloc, free oraz picosbrk.
\par\textbf{Evidence:} Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.
\par\textbf{Acceptance:} Host i Hazard3: length=16, pass=1, kod 0. Mapa RV32I zawiera libc.a(strlen.c.o), ale nie zawiera libc\_stdlib\_malloc.c.o, libc\_stdlib\_free.c.o ani libc\_misc\_picosbrk.c.o.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{TASK01 · Zapis dowodu ucznia}
\textbf{1. Przewidywanie przed uruchomieniem}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{2. Wynik programu hostowego}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{3. Wynik RV32I i kod wyjścia}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\par\noindent\dotfill\par\noindent\dotfill
\end{ESCBlockFrame}
\end{ESCTaskFrame}
\clearpage
\begin{ESCTaskFrame}{TASK02 · Kontrolowany brak dostawcy sbrk}
{\scriptsize\ttfamily 9c55597f-3145-5744-b13e-f1c0ee83abc8}\par
\begin{ESCBlockFrame}{A1 CONTEXT · AKTYWNE — kontrolowane przecięcie granicy platformy}
\textbf{Zachowany przykład:} \texttt{src/tasks/task02\_malloc\_requires\_sbrk.c}.\par Kod klienta żąda 16 B przez \texttt{malloc}. Opcja linkera przechwytuje wyłącznie symbol dostawcy pamięci, dzięki czemu testuje granicę libc--platforma bez usuwania lub modyfikowania systemowego archiwum.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A2 STRUCTURE · AKTYWNE — caller, człon malloc i brakujący wrapper}
Obiekt tasku wnosi odwołanie do \texttt{malloc}; linker pobiera \texttt{libc\_stdlib\_malloc.c.o}. Jego odwołania do \texttt{sbrk} są przez \texttt{--wrap} przepisywane na \texttt{\_\_wrap\_sbrk}. Żaden obiekt celowo nie definiuje tej ostatniej nazwy.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A3 DISPATCH · N/D}
\textbf{N/D.} Wywołanie malloc jest bezpośrednie, a --wrap jest transformacją nazw podczas linkowania, nie mechanizmem callbacku, ISR ani dynamicznego dispatchu w programie.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A4 APPLICATION · AKTYWNE — porażka ma precyzyjną sygnaturę}
Sukces testu oznacza jednocześnie: obiekt C powstał, link zwrócił status różny od zera, diagnostyka zawiera \texttt{\_\_wrap\_sbrk} oraz nazwę członu malloc, a końcowy ELF nie istnieje. Inny błąd linkera nie spełnia kryterium.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A5 FLOW · AKTYWNE — expected-fail jako kontrolowany przebieg}
Skrypt usuwa stary artefakt, kompiluje task, uruchamia link w trybie dopuszczającym błąd, zapisuje status i log, po czym przywraca rygor \texttt{set -e}. Dopiero cztery asercje zamieniają oczekiwaną porażkę programu w sukces testu.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A6 STATE · N/D}
\textbf{N/D.} Linkowanie kończy się przed utworzeniem obrazu wykonywalnego, więc nie istnieje runtime, breakpoint ani obiekt domenowy zmieniający stan w czasie wykonania.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A7 RUNTIME · AKTYWNE — dowodem jest diagnostyka przed runtime}
Odczytaj \texttt{expected-link-failure.log}: cztery relokacje z \texttt{libc\_stdlib\_malloc.c.o} wskazują brak \texttt{\_\_wrap\_sbrk}. Następnie sprawdź nieobecność \texttt{must-not-exist.elf}; nie wolno wymyślać wartości runtime dla nieistniejącego programu.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A8 PATTERNS · AKTYWNE — test negatywny musi odrzucać zły powód}
Wzorzec expected-fail wymaga konkretnej sygnatury błędu i braku produktu końcowego. Sam warunek „polecenie nie przeszło” byłby za słaby, bo zaliczyłby literówkę, brak kompilatora lub błędny skrypt linkera.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{Ćwiczenie · Przewidywanie → wykonanie → wniosek}
Narysuj łańcuch \texttt{main -> malloc -> sbrk}. Następnie uruchom \texttt{make expected-fail} i wyjaśnij, dlaczego \texttt{--wrap=sbrk} zmienia ostatnią krawędź na \texttt{\_\_wrap\_sbrk}, dlaczego brak ELF jest wynikiem poprawnym oraz czego ten test nie mówi o runtime.
\par\textbf{Evidence:} Przewidywana brakująca nazwa, status kompilacji obiektu, niezerowy status linkera, cytat dwóch sygnatur z logu oraz sprawdzenie, że końcowy ELF nie istnieje.
\par\textbf{Acceptance:} Kompilacja task.o kończy się sukcesem. Link ma niezerowy status, log wymienia libc\_stdlib\_malloc.c.o i undefined reference to \_\_wrap\_sbrk, a must-not-exist.elf nie istnieje. Nie wykonuje się testu host ani Hazard3.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{TASK02 · Zapis dowodu ucznia}
\textbf{1. Przewidywanie przed uruchomieniem}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{2. Wynik kompilacji task.o}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{3. Status i sygnatury błędu linkera}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\par\noindent\dotfill\par\noindent\dotfill
\end{ESCBlockFrame}
\end{ESCTaskFrame}
\clearpage
\begin{ESCTaskFrame}{TASK03 · Bounded sbrk i atomowy błąd}
{\scriptsize\ttfamily 5a69ff6d-bda9-5b20-a18c-a8da2f88c64a}\par
\begin{ESCBlockFrame}{A1 CONTEXT · AKTYWNE — model oddzielony od libc}
\textbf{Zachowane materiały:} \texttt{src/tasks/task03\_bounded\_sbrk.c}, \texttt{src/runtime/c09\_bounded\_sbrk.c}, \texttt{src/runtime/c09\_bounded\_sbrk.h}, \texttt{link/hazard3\_c09.ld}.\par Task03 eksportuje nazwę \texttt{c09\_sbrk}, aby nie przejąć symbolu \texttt{sbrk} picolibc. Na hoście granice obejmują statyczną arenę 512 B; na RV32I pochodzą z linkera. Ten sam algorytm widzi tylko trzy adresy.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A2 STRUCTURE · AKTYWNE — start, break i end}
Stan ma jedną zmienną \texttt{g\_c09\_break} oraz niezmienne granice \texttt{start} i \texttt{end}. Każdy poprawny stan spełnia \texttt{start <= break <= end}; wolny obszar ma rozmiar \texttt{end-break}, a zajęty prefiks \texttt{break-start}.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A3 DISPATCH · N/D}
\textbf{N/D.} Wszystkie operacje c09\_sbrk i gettery są wywoływane bezpośrednio; nie ma callbacku, ISR, tablicy funkcji ani wyboru implementacji w runtime.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A4 APPLICATION · AKTYWNE — tabela pięciu przejść}
Od offsetu 0 operacje dają kolejno: \texttt{+16 -> 16}, \texttt{+24 -> 40}, zbyt duży wzrost \texttt{-> 40}, \texttt{-8 -> 32}, zejście o 33 \texttt{-> 32}. Udany call zwraca stary break; porażka zwraca wszystkie bity ustawione.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A5 FLOW · AKTYWNE — najpierw zakres, potem commit}
Dla wzrostu kod porównuje wielkość z \texttt{end-current}; dla spadku oblicza bezpiecznie moduł także dla \texttt{PTRDIFF\_MIN} i porównuje go z \texttt{current-start}. Dopiero po zaliczeniu warunku zapisuje \texttt{g\_c09\_break=next}.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A6 STATE · AKTYWNE — VALID pozostaje VALID także po odrzuceniu}
Break przechodzi przez offsety \texttt{0 -> 16 -> 40 -> 40 -> 32 -> 32}. Obie porażki są samopętlą stanu: zmienia się wynik operacji, ale nie break. Nie ma przejścia do wartości poniżej start ani powyżej end.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A7 RUNTIME · AKTYWNE — offsety zamiast niestabilnych adresów}
Checkpoint publikuje cztery offsety \texttt{40,40,32,32} oraz flagi \texttt{preserved=1} i \texttt{bounds=1}. Adresy bezwzględne hosta mogą się zmieniać przez ASLR, dlatego dowód porównuje różnice względem startu.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A8 PATTERNS · AKTYWNE — validate then commit}
Wzorzec silnej gwarancji błędu brzmi: najpierw policz bez przepełnienia, następnie sprawdź granice, a dopiero potem zmień jedyny stan. Wskaźnik break pozostaje obserwowalnie niezmieniony po odrzuconej operacji.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{Ćwiczenie · Przewidywanie → wykonanie → wniosek}
Zapisz stan break po sekwencji \texttt{+16,+24,fail,-8,fail}. Dla obu porażek wskaż sprawdzaną granicę i udowodnij, że zwrot \texttt{(void *)-1} nie zmienia break. Porównaj stałe offsety hosta i RV32I.
\par\textbf{Evidence:} Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.
\par\textbf{Acceptance:} Host i Hazard3: grow=40, rejected\_grow=40, shrink=32, rejected\_shrink=32, preserved=1, bounds=1, pass=1 i kod 0. Linker: \_\_heap\_start oraz \_\_heap\_end są wyrównane do 16 B, \_\_heap\_end == \_\_stack\_bottom.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{TASK03 · Zapis dowodu ucznia}
\textbf{1. Przewidywanie przed uruchomieniem}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{2. Wynik programu hostowego}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{3. Wynik RV32I i kod wyjścia}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\par\noindent\dotfill\par\noindent\dotfill
\end{ESCBlockFrame}
\end{ESCTaskFrame}
\clearpage
\begin{ESCTaskFrame}{TASK04 · Wyrównany smalloc append-only}
{\scriptsize\ttfamily e745e1ea-9494-5b75-9596-816022153237}\par
\begin{ESCBlockFrame}{A1 CONTEXT · AKTYWNE — API ma celowo małą władzę}
\textbf{Zachowane materiały:} \texttt{src/tasks/task04\_smalloc.c}, \texttt{src/runtime/c09\_smalloc.c}, \texttt{src/runtime/c09\_smalloc.h}, \texttt{src/runtime/c09\_bounded\_sbrk.c}, \texttt{src/runtime/c09\_bounded\_sbrk.h}.\par Klient może przydzielić payload i zresetować całą arenę. Nie może zwolnić jednego bloku ani przekazać modelowi wskaźnika do picolibc. \texttt{c09\_smalloc} pobiera surowy fragment wyłącznie przez ograniczone \texttt{c09\_sbrk}.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A2 STRUCTURE · AKTYWNE — 16 B metadanych bezpośrednio przed payloadem}
Nagłówek ma cztery pola \texttt{uint32\_t}: magic, requested, reserved i sequence. Jego rozmiar wynosi 16 B. Zwrócony adres to \texttt{header+1}; inspekcja kontrolnie cofa się o dokładnie jeden rzeczywisty nagłówek.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A3 DISPATCH · N/D}
\textbf{N/D.} smalloc, inspect, reset i c09\_sbrk są zwykłymi wywołaniami bezpośrednimi; przykład nie zawiera callbacku, ISR ani dynamicznie wybieranej strategii.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A4 APPLICATION · AKTYWNE — geometria dwóch bloków}
Żądanie 1 B daje \texttt{align16(16+1)=32}; żądanie 17 B daje \texttt{align16(16+17)=48}. Pierwszy payload leży 16 B od startu, drugi 48 B od startu, więc ich delta wynosi 32 B. Oba adresy są wielokrotnością 16.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A5 FLOW · AKTYWNE — size, align, reserve, header, payload}
Kod odrzuca zero i przepełnienie, zaokrągla sumę nagłówka i żądania, sprawdza reprezentowalność jako \texttt{ptrdiff\_t}, rezerwuje cały blok jednym \texttt{c09\_sbrk}, zapisuje cztery pola nagłówka i dopiero wtedy zwraca payload.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A6 STATE · AKTYWNE — EMPTY, TWO BLOCKS, RESET}
Arena przechodzi \texttt{EMPTY -> seq1 -> seq1+seq2}. Brak indywidualnego free oznacza, że break tylko rośnie. \texttt{c09\_smalloc\_reset} wykonuje jedno przejście z dowolnej zawartości do EMPTY i unieważnia oba wskaźniki naraz.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A7 RUNTIME · AKTYWNE — pamięć potwierdza header przed payloadem}
W checkpointcie odczytaj \texttt{first-16}: słowo magic to \texttt{0x534D414C}, requested=1, reserved=32, sequence=1. Pod \texttt{second-16} odpowiednie wartości to 17, 48 i 2. Globale potwierdzają deltę 32 i reset.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A8 PATTERNS · AKTYWNE — bump allocator z jawną granicą użycia}
Append-only arena jest wartościowa dla fazowego scratch storage, gdy wszystkie obiekty mają wspólny czas życia. Nie zastępuje ogólnego malloc: brak selekcji, ponownego użycia pojedynczych luk, split, coalescing i niezależnego free.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{Ćwiczenie · Przewidywanie → wykonanie → wniosek}
Dla żądań 1 B i 17 B wyprowadź rozmiar nagłówka, rezerwację po align-up, adres obu payloadów i zmianę break. Wskaż, dlaczego brak \texttt{c09\_sfree} jest częścią kontraktu, a reset areny nie jest odpowiednikiem \texttt{free(pointer)}.
\par\textbf{Evidence:} Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.
\par\textbf{Acceptance:} Host i Hazard3: header=16, first=32, second=48, delta=32, aligned=1, metadata=1, append=1, reset=1, pass=1 i kod 0. Nagłówki mają sequence 1 i 2; po resecie pierwszy payload wraca pod poprzedni pierwszy adres.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{TASK04 · Zapis dowodu ucznia}
\textbf{1. Przewidywanie przed uruchomieniem}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{2. Wynik programu hostowego}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{3. Wynik RV32I i kod wyjścia}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\par\noindent\dotfill\par\noindent\dotfill
\end{ESCBlockFrame}
\end{ESCTaskFrame}
\clearpage
\begin{ESCTaskFrame}{TASK05 · malloc/free picolibc jako czarna skrzynka}
{\scriptsize\ttfamily 9a7174f1-efb7-5c18-afbe-1a34ee6da0b7}\par
\begin{ESCBlockFrame}{A1 CONTEXT · AKTYWNE — klient nie zna wnętrza picolibc}
\textbf{Zachowane materiały:} \texttt{src/tasks/task05\_picolibc\_malloc.c}, \texttt{crt0.S}, \texttt{link/hazard3\_c09.ld}.\par Kod tasku używa tylko \texttt{malloc} i \texttt{free}. Linker łączy picolibc z platformą przez \texttt{sbrk} oraz symbole granic. Vendorowe \texttt{\_sbrk} pozostaje osobnym, nieużywanym przez malloc symbolem startu Hazard3.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A2 STRUCTURE · AKTYWNE — obraz składa się z czterech warstw}
ELF zawiera klienta, człony \texttt{malloc/free}, człon \texttt{picosbrk} i platformowy startup. Skrypt linkera dostarcza \texttt{\_\_heap\_start/\_\_heap\_end}; crt0 ustawia \texttt{gp/tp} i zeruje BSS/TBSS. Mapa pokazuje, który obiekt wniósł każdy publiczny symbol.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A3 DISPATCH · N/D}
\textbf{N/D.} Klient wywołuje malloc i free bezpośrednio; badany przebieg nie przekazuje callbacku, nie obsługuje ISR i nie wybiera alokatora przez tablicę funkcji.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A4 APPLICATION · AKTYWNE — kontrakt dwóch niezależnych payloadów}
Program żąda 13 B i 29 B, zapisuje dwie różne sekwencje, sumuje odczytane bajty do 4612 i sprawdza rozłączność półotwartych zakresów adresów. Mierzy co najmniej 8-bajtowe wyrównanie, ale nie narzuca odległości między blokami.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A5 FLOW · AKTYWNE — allocate, use, verify, release}
Po dwóch udanych przydziałach kod zapisuje i natychmiast odczytuje oba zakresy, oblicza checksum i warunki adresowe, zwalnia najpierw drugi, potem pierwszy blok, ustawia \texttt{freed=1} i zatrzymuje się w checkpointcie.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A6 STATE · AKTYWNE — własność payloadu kończy się w free}
Każdy wskaźnik przechodzi od braku zasobu do stanu OWNED po udanym malloc, następnie USED po zapisie i RELEASED po free. Program nie dereferencjuje wskaźnika w RELEASED i nie bada, czy następny malloc odzyska ten sam adres.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A7 RUNTIME · AKTYWNE — wykonanie plus audyt ELF}
Hazard3 kończy program kodem 0; globale pokazują checksum 4612, rozłączność, alignment i oba free. \texttt{nm} musi równocześnie pokazać \texttt{\_sbrk} oraz \texttt{sbrk}, a mapa trzy człony picolibc związane z malloc, free i dostawcą pamięci.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{A8 PATTERNS · AKTYWNE — test kontraktu bez zgadywania implementacji}
Poprawny test czarnej skrzynki wymusza własności API, ale nie zależy od prywatnego nagłówka, kierunku wzrostu sterty, dokładnej delty adresów ani ponownego użycia po free. Dzięki temu pozostaje wartościowy po zmianie implementacji libc.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{Ćwiczenie · Przewidywanie → wykonanie → wniosek}
Nie zakładając formatu nagłówka libc, wypisz własności możliwe do sprawdzenia przez klienta dwóch przydziałów. Uruchom host i Hazard3, sprawdź checksum oraz mapę, a w ELF wykaż współistnienie vendorowego \texttt{\_sbrk} i picolibc \texttt{sbrk}.
\par\textbf{Evidence:} Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.
\par\textbf{Acceptance:} Host i Hazard3: nonnull=1, delta\_nonzero=1, checksum=4612, nonoverlap=1, aligned=1, freed=1, pass=1 i kod 0. Mapa RV32I zawiera libc\_stdlib\_malloc.c.o, libc\_stdlib\_free.c.o i libc\_misc\_picosbrk.c.o. ELF definiuje \_sbrk, sbrk, malloc, free, \_\_heap\_start i \_\_heap\_end.
\end{ESCBlockFrame}
\begin{ESCBlockFrame}{TASK05 · Zapis dowodu ucznia}
\textbf{1. Przewidywanie przed uruchomieniem}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{2. Wynik programu hostowego}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{3. Wynik RV32I i kod wyjścia}\par\noindent\dotfill\par\noindent\dotfill\par\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\par\noindent\dotfill\par\noindent\dotfill
\end{ESCBlockFrame}
\end{ESCTaskFrame}
\ESCSectionBlockEnd
\end{document}
View File
+962
View File
@@ -0,0 +1,962 @@
{
"$schema": "../../../tools/card-layouts/schemas/card-source.schema.json",
"schema": "esc-card-source.v1",
"card": {
"id": "mpabi-inf-c-09-smalloc-sbrk-libc",
"series": "c",
"series_title": "C · Freestanding RV32I and K&R",
"number": "09",
"count": "13",
"slug": "smalloc-sbrk-libc",
"title": "smalloc, sbrk i minimalne libc",
"topic": "K&R 5.4 i 7.8: granica libc, bounded sbrk, prosty smalloc oraz malloc/free picolibc",
"project": "Freestanding C na RV32I",
"subject": "Informatyka",
"level": "Rok 1 · C09 · RV32I/Hazard3",
"revision_date": "2026-07-20T00:00:00+02:00",
"status": "Gotowa",
"version": "v00.01",
"uuid": "47826ae5-6370-5ac2-bd91-9acb89116d40",
"author": "M. Pabiszczak",
"year": "2026"
},
"generated": {
"tex": "doc/generated/main.tex",
"html": "web/index.html",
"html_css": "web/style.css",
"html_tree_inspector": false,
"react_app": true
},
"render_dictionary": false,
"template": "templates/karta-klasyczna.json",
"title_block": {
"category": "KARTA PRACY · INFORMATYKA",
"prepared_by": "M. Pabiszczak",
"prepared_on": "2026-07-20T00:00:00+02:00",
"title": "smalloc, sbrk i minimalne libc",
"url": "https://dce7fb9d-7b2f-5d49-96a2-3a30d3070b84.mpabi.pl/8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0",
"repository_url": "https://zsl-gitea.mpabi.pl/edu-inf/lab-rv32i-c-smalloc-sbrk-libc",
"url_host_uuid": "dce7fb9d-7b2f-5d49-96a2-3a30d3070b84",
"url_domain": "mpabi.pl",
"doc_uuid": "8e4f8bee-41f0-523b-8ea2-8926bd3d7ab0",
"revision": "v00.01",
"issued_on": "2026-07-20T00:00:00+02:00",
"series": "C-09",
"document_type": "karta pracy",
"tool": "card-layouts",
"show_qr": true,
"show_repository_qr": true,
"height_cm": 2.6,
"repeat_on_every_page": true,
"replace_front_matter": true
},
"front_page_break": true,
"front_page_scope": {
"title": "Cel karty",
"content_tex": "Uczeń rozdziela wybór funkcji z archiwum libc, kontrakt dostawcy pamięci sbrk i politykę alokatora. Najpierw dowodzi, że strlen nie potrzebuje sterty, potem kontrolowanie zrywa zależność malloc do sbrk, buduje ograniczony model break i wyrównany smalloc, a na końcu traktuje prawdziwe malloc/free picolibc jako czarną skrzynkę sprawdzaną przez kontrakt.",
"scope_title": "Zakres i zachowane przykłady",
"scope_content_tex": "Pięć zachowanych przykładów działa na hoście i RV32I tam, gdzie istnieje program uruchamialny. Task02 jest zamierzonym expected-fail linkera i nie ma obrazu runtime. W aktualnym GCC 15.2 biblioteka to picolibc: jej malloc odwołuje się do sbrk, podczas gdy vendorowy init.S Hazard3 definiuje inny symbol \\_sbrk. Własny linker ogranicza stertę symbolami \\_\\_heap\\_start i \\_\\_heap\\_end oraz rezerwuje 64 KiB stosu.\\par\\textbf{Układ każdego przykładu:} pełny profil A1--A8; widok N/D ma jawny powód, a diagram nie jest wymagany, gdy tekst daje lepszy dowód.",
"scope_table": {
"headers": [
"K&R",
"Numer tasku",
"Najważniejsza idea",
"Priorytet"
],
"rows": [
{
"chapter": "7.8",
"task": "Task01",
"idea_tex": "libc.a: wybór strlen.c.o",
"priority": "kluczowe",
"key": true
},
{
"chapter": "5.4 + ABI",
"task": "Task02",
"idea_tex": "expected-fail: malloc -> sbrk",
"priority": "kluczowe",
"key": true
},
{
"chapter": "5.4",
"task": "Task03",
"idea_tex": "ograniczony break i atomowy błąd",
"priority": "kluczowe",
"key": true
},
{
"chapter": "5.4",
"task": "Task04",
"idea_tex": "nagłówek, alignment i reset areny",
"priority": "kluczowe",
"key": true
},
{
"chapter": "7.8 + ABI",
"task": "Task05",
"idea_tex": "malloc/free picolibc jako black box",
"priority": "kluczowe",
"key": true
}
]
}
},
"side_margin_tree_layout": {
"columns": [
{
"id": "zawodowe",
"label": "TECH",
"side": "left",
"tree": "WE -> EK -> KW",
"description": "Kod C, ABI i obserwacja RV32I."
},
{
"id": "ogolne",
"label": "OG",
"side": "right",
"tree": "WE -> EN -> KW",
"description": "Przewidywanie, pomiar i wniosek."
}
]
},
"learning_effects": {
"C09.EN01": {
"bloom_level": "Analiza",
"label": "Trzy warstwy jednego przydziału",
"text": "Uczeń wyprowadza osobno: wybór członu archiwum, wiązanie symbolu dostawcy pamięci oraz inwarianty polityki alokacji, zamiast nazywać wszystkie trzy mechanizmy stertą.",
"assessment_criteria": [
"C09.KW01"
]
},
"C09.EK01": {
"bloom_level": "Zastosowanie",
"label": "Dowód z mapy, stanu i wykonania",
"text": "Uczeń porównuje mapę linkera, kontrolowany błąd symbolu, globalne checkpointy, wynik hosta i kod wyjścia Hazard3, a wnioski ogranicza do naprawdę zmierzonych własności.",
"assessment_criteria": [
"C09.KW01"
]
}
},
"assessment_criteria": {
"C09.KW01": {
"text": "Task01: length=16, strlen.c.o obecny, człony malloc/free/picosbrk nieobecne. Task02: link kończy się niezerowo przez brak __wrap_sbrk, powstaje log, ale nie ELF. Task03: offset break 40 po wzroście, 40 po odrzuconym wzroście, 32 po skurczeniu i 32 po odrzuconym zejściu poniżej początku; granice są zachowane. Task04: header=16, rezerwacje 32 i 48, delta payloadów 32, alignment=16, kolejność append-only i reset całej areny. Task05: dwa niepuste, rozłączne i co najmniej 8-bajtowo wyrównane bloki, checksum=4612, oba free wykonane; mapa zawiera malloc/free/picosbrk, a ELF równocześnie _sbrk i sbrk. Wszystkie uruchamialne programy host i Hazard3 zwracają 0.",
"learning_effects": [
"C09.EN01",
"C09.EK01"
]
}
},
"educational_requirements": {
"C09.WE01": {
"text": "Czytanie mapy linkera i symboli ELF, bezpieczna arytmetyka granic, wyrównanie bloków oraz rozróżnienie modelu dydaktycznego od kontraktu prawdziwej biblioteki.",
"label": "K&R 5.4 i 7.8: granica libc, bounded sbrk, prosty smalloc oraz malloc/free picolibc",
"learning_effects": [
"C09.EN01",
"C09.EK01"
],
"learning_tree": {
"schema": "we-learning-tree.v1",
"policy": "Najpierw przewidywanie, następnie wykonanie i odczyt dowodu.",
"ogolne": [
{
"effect_ref": "C09.EN01",
"display": "EN C09 01",
"source": "LOCAL",
"official": "C",
"local": "01",
"kind": "EN",
"tree_id": "C09.WE01.OG.LOCAL.C.01",
"text": "Uczeń wyprowadza osobno: wybór członu archiwum, wiązanie symbolu dostawcy pamięci oraz inwarianty polityki alokacji, zamiast nazywać wszystkie trzy mechanizmy stertą.",
"kw": [
{
"criterion_ref": "C09.KW01",
"display": "KW C09 01",
"source": "LOCAL",
"kind": "KW",
"official": "C",
"local": "01",
"text": "Kod, przewidywanie i pomiar tworzą jeden dowód."
}
]
}
],
"zawodowe": [
{
"effect_ref": "C09.EK01",
"display": "EK C09 01",
"source": "LOCAL",
"official": "C",
"local": "01",
"kind": "EK",
"tree_id": "C09.WE01.TECH.LOCAL.C.01",
"text": "Uczeń porównuje mapę linkera, kontrolowany błąd symbolu, globalne checkpointy, wynik hosta i kod wyjścia Hazard3, a wnioski ogranicza do naprawdę zmierzonych własności.",
"kw": [
{
"criterion_ref": "C09.KW01",
"display": "KW C09 01",
"source": "LOCAL",
"kind": "KW",
"official": "C",
"local": "01",
"text": "Kod, przewidywanie i pomiar tworzą jeden dowód."
}
]
}
]
}
}
},
"sections": [
{
"title": "1 — Funkcja z libc nie oznacza automatycznie sterty",
"order": 10,
"content_kind": "prose",
"page_orientation": "portrait",
"content_tex": "Archiwum \\texttt{libc.a} nie jest włączane do programu w całości. Linker pobiera człon, który rozwiązuje potrzebny symbol, a następne człony tylko wtedy, gdy pojawią się kolejne nierozwiązane odwołania. Dlatego bezpośrednie wywołanie \\texttt{strlen} z opcją \\texttt{-fno-builtin} wybiera \\texttt{strlen.c.o}, lecz nie wybiera \\texttt{libc\\_stdlib\\_malloc.c.o} ani \\texttt{libc\\_misc\\_picosbrk.c.o}. Dowodem jest mapa linkera, a nie samo dołączenie nagłówka \\texttt{string.h}.\n\nTask01 rozstrzyga wyłącznie tę zależność. Nie dowodzi, że każda funkcja tekstowa jest bezkosztowa ani że całe libc nie używa pamięci dynamicznej.",
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_refs": [
"C09.KW01"
]
},
{
"title": "2 — malloc potrzebuje sbrk, lecz _sbrk to inna nazwa",
"order": 20,
"content_kind": "prose",
"page_orientation": "portrait",
"content_tex": "W badanym GCC 15.2 używane jest \\textbf{picolibc}, nie newlib. Człon \\texttt{libc\\_stdlib\\_malloc.c.o} ma niezdefiniowane odwołanie do \\texttt{sbrk}. Picolibc może dostarczyć je z \\texttt{libc\\_misc\\_picosbrk.c.o}; ten kod opiera granice na symbolach linkera \\texttt{\\_\\_heap\\_start} i \\texttt{\\_\\_heap\\_end}. Z kolei \\texttt{vendor/Hazard3/.../init.S} definiuje \\texttt{\\_sbrk} z jednym podkreśleniem. Dla linkera są to dwa różne symbole, więc mogą współistnieć w Task05.\n\nTask02 używa \\texttt{--wrap=sbrk}: każde odwołanie do \\texttt{sbrk} zostaje skierowane do celowo brakującego \\texttt{\\_\\_wrap\\_sbrk}. Oczekiwany błąd jest kontrolowanym testem krawędzi \\texttt{malloc -> sbrk}, a nie awarią całego projektu.",
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_refs": [
"C09.KW01"
]
},
{
"title": "3 — Od alloc z K&R 5.4 do bounded sbrk i smalloc",
"order": 30,
"content_kind": "prose",
"page_orientation": "portrait",
"content_tex": "Liniowy \\texttt{alloc} z K\\&R 5.4 przesuwa wskaźnik w stałej tablicy. Jest prosty, ale nie odzyskuje dowolnego bloku, nie wybiera najlepszego dopasowania, nie dzieli i nie scala wolnych obszarów. \\texttt{afree} może cofnąć wskaźnik jedynie do adresu należącego do areny, więc bez dyscypliny stosowej łatwo unieważnić późniejsze przydziały. Historyczna tablica bajtów nie jest też ogólną deklaracją wyrównania dla każdego typu.\n\nTask03 zachowuje istotę modelu, lecz jawnie wymusza \\texttt{heap\\_start <= brk <= heap\\_end}; odrzucona operacja nie zmienia break. Task04 dodaje 16-bajtowe wyrównanie i 16-bajtowy nagłówek bezpośrednio przed payloadem. Nadal jest to alokator append-only: nie ma \\texttt{sfree}, listy wolnych bloków, split ani coalescing. Jedyną operacją odzyskania jest reset całej areny, który unieważnia wszystkie wcześniejsze wskaźniki.",
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_refs": [
"C09.KW01"
]
},
{
"title": "4 — Prawdziwe malloc/free jako czarna skrzynka",
"order": 40,
"content_kind": "prose",
"page_orientation": "portrait",
"content_tex": "Task05 nie zakłada, że picolibc ma taki sam nagłówek lub strategię jak \\texttt{c09\\_smalloc}. Program sprawdza tylko publiczny kontrakt potrzebny klientowi: dwa udane przydziały dają używalne, rozłączne obszary; zapisane bajty można odczytać; adresy spełniają mierzone wyrównanie; oba bloki są przekazane do \\texttt{free}. Nie odczytuje pamięci po \\texttt{free} i nie wymaga ponownego użycia tego samego adresu.\n\nMinimalny startup ustawia \\texttt{gp}, pojedynczą bazę TLS \\texttt{tp}, zeruje \\texttt{.tbss} i \\texttt{.bss}, a następnie wywołuje \\texttt{main}. Skrypt linkera pozostawia \\texttt{\\_\\_heap\\_end} 64 KiB przed \\texttt{\\_\\_stack\\_top} i ma \\texttt{ASSERT} zatrzymujący obraz, gdy sekcje zajmą zarezerwowany obszar.",
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_refs": [
"C09.KW01"
]
},
{
"title": "Zadania — zachowane przykłady w profilu A1A8",
"order": 50,
"content_kind": "tasks",
"page_orientation": "portrait",
"task_refs": [
"task01",
"task02",
"task03",
"task04",
"task05"
],
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_refs": [
"C09.KW01"
]
}
],
"tasks": {
"task01": {
"title": "strlen z libc bez sterty",
"uuid": "839b8401-9f15-5edc-a214-176c06ba5cb1",
"prompt_tex": "Przed linkowaniem przewidź, które człony \\texttt{libc.a} są potrzebne przez bezpośrednie \\texttt{strlen}. Uruchom program, a potem w mapie udowodnij obecność \\texttt{strlen.c.o} i nieobecność członów malloc, free oraz picosbrk.",
"criterion": "Host i Hazard3: length=16, pass=1, kod 0. Mapa RV32I zawiera libc.a(strlen.c.o), ale nie zawiera libc_stdlib_malloc.c.o, libc_stdlib_free.c.o ani libc_misc_picosbrk.c.o.",
"conclusion_tex": "",
"render_task_acceptance": false,
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — klient, archiwum i brak dostawcy pamięci",
"content_tex": "Klient wywołuje publiczne \\texttt{strlen}. Granicą systemu jest linker wybierający potrzebny człon \\texttt{libc.a}; Task01 nie deklaruje ani nie wywołuje \\texttt{malloc}, \\texttt{free} lub \\texttt{sbrk}."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — jedna krawędź symboliczna",
"content_tex": "Przy \\texttt{-fno-builtin} obiekt tasku zachowuje niezdefiniowany symbol \\texttt{strlen}. Linker rozwiązuje go przez \\texttt{strlen.c.o}. Ten człon nie tworzy następnej krawędzi do alokatora, dlatego graf zależności kończy się bez warstwy sterty."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "strlen i checkpoint są wywołaniami bezpośrednimi; program nie ma callbacku, ISR, wskaźnika funkcji ani dynamicznego wyboru implementacji."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — dwa niezależne warunki sukcesu",
"content_tex": "Warunek wykonania to długość 16 dla napisu \\texttt{picolibc-no-heap}. Warunek obrazu to obecność wyłącznie potrzebnego członu tekstowego i brak trzech nazw członów związanych z przydziałem. Sam kod wyjścia nie zastępuje kontroli mapy."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — kompilacja, selekcja archiwum, wywołanie",
"content_tex": "Kompilator pozostawia call do \\texttt{strlen}; linker przegląda archiwum i pobiera \\texttt{strlen.c.o}; startup uruchamia \\texttt{main}; funkcja oblicza 16; checkpoint publikuje wynik; \\texttt{main} zwraca 0. Każdy etap ma inny artefakt dowodowy."
},
{
"id": "A6",
"status": "unavailable",
"title": "STATE",
"reason": "Program wykonuje jednorazowy, czysty pomiar długości i nie ma domenowego obiektu przechodzącego przez istotne stany ani zasobu o cyklu życia."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — global i mapa odpowiadają na inne pytania",
"content_tex": "W \\texttt{task01\\_debug\\_checkpoint} global \\texttt{g\\_task01\\_length} ma wartość 16, a \\texttt{g\\_task01\\_pass} wartość 1. Osobno \\texttt{prog.map} wskazuje \\texttt{libc.a(strlen.c.o)}; test mapy odrzuca każdą nazwę członu heap."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — płać za osiągalne symbole, nie za nagłówek",
"content_tex": "Dla archiwum statycznego właściwym wzorcem audytu jest śledzenie nierozwiązanych symboli i pobranych członów. \\texttt{\\#include <string.h>} dostarcza deklarację; nie jest poleceniem dołączenia całej implementacji libc."
}
],
"flow": [
{
"kind": "block",
"id": "task01.a1",
"title": "A1 CONTEXT · AKTYWNE — klient, archiwum i brak dostawcy pamięci",
"content_tex": "\\textbf{Zachowany przykład:} \\texttt{src/tasks/task01\\_libc\\_without\\_heap.c}.\\par Klient wywołuje publiczne \\texttt{strlen}. Granicą systemu jest linker wybierający potrzebny człon \\texttt{libc.a}; Task01 nie deklaruje ani nie wywołuje \\texttt{malloc}, \\texttt{free} lub \\texttt{sbrk}."
},
{
"kind": "block",
"id": "task01.a2",
"title": "A2 STRUCTURE · AKTYWNE — jedna krawędź symboliczna",
"content_tex": "Przy \\texttt{-fno-builtin} obiekt tasku zachowuje niezdefiniowany symbol \\texttt{strlen}. Linker rozwiązuje go przez \\texttt{strlen.c.o}. Ten człon nie tworzy następnej krawędzi do alokatora, dlatego graf zależności kończy się bez warstwy sterty."
},
{
"kind": "block",
"id": "task01.a3",
"title": "A3 DISPATCH · N/D",
"content_tex": "\\textbf{N/D.} strlen i checkpoint są wywołaniami bezpośrednimi; program nie ma callbacku, ISR, wskaźnika funkcji ani dynamicznego wyboru implementacji."
},
{
"kind": "block",
"id": "task01.a4",
"title": "A4 APPLICATION · AKTYWNE — dwa niezależne warunki sukcesu",
"content_tex": "Warunek wykonania to długość 16 dla napisu \\texttt{picolibc-no-heap}. Warunek obrazu to obecność wyłącznie potrzebnego członu tekstowego i brak trzech nazw członów związanych z przydziałem. Sam kod wyjścia nie zastępuje kontroli mapy."
},
{
"kind": "block",
"id": "task01.a5",
"title": "A5 FLOW · AKTYWNE — kompilacja, selekcja archiwum, wywołanie",
"content_tex": "Kompilator pozostawia call do \\texttt{strlen}; linker przegląda archiwum i pobiera \\texttt{strlen.c.o}; startup uruchamia \\texttt{main}; funkcja oblicza 16; checkpoint publikuje wynik; \\texttt{main} zwraca 0. Każdy etap ma inny artefakt dowodowy."
},
{
"kind": "block",
"id": "task01.a6",
"title": "A6 STATE · N/D",
"content_tex": "\\textbf{N/D.} Program wykonuje jednorazowy, czysty pomiar długości i nie ma domenowego obiektu przechodzącego przez istotne stany ani zasobu o cyklu życia."
},
{
"kind": "block",
"id": "task01.a7",
"title": "A7 RUNTIME · AKTYWNE — global i mapa odpowiadają na inne pytania",
"content_tex": "W \\texttt{task01\\_debug\\_checkpoint} global \\texttt{g\\_task01\\_length} ma wartość 16, a \\texttt{g\\_task01\\_pass} wartość 1. Osobno \\texttt{prog.map} wskazuje \\texttt{libc.a(strlen.c.o)}; test mapy odrzuca każdą nazwę członu heap."
},
{
"kind": "block",
"id": "task01.a8",
"title": "A8 PATTERNS · AKTYWNE — płać za osiągalne symbole, nie za nagłówek",
"content_tex": "Dla archiwum statycznego właściwym wzorcem audytu jest śledzenie nierozwiązanych symboli i pobranych członów. \\texttt{\\#include <string.h>} dostarcza deklarację; nie jest poleceniem dołączenia całej implementacji libc."
},
{
"kind": "exercise",
"id": "task01.proof",
"title": "Przewidywanie → wykonanie → wniosek",
"prompt_tex": "Przed linkowaniem przewidź, które człony \\texttt{libc.a} są potrzebne przez bezpośrednie \\texttt{strlen}. Uruchom program, a potem w mapie udowodnij obecność \\texttt{strlen.c.o} i nieobecność członów malloc, free oraz picosbrk.",
"evidence_tex": "Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.",
"criterion": "Host i Hazard3: length=16, pass=1, kod 0. Mapa RV32I zawiera libc.a(strlen.c.o), ale nie zawiera libc_stdlib_malloc.c.o, libc_stdlib_free.c.o ani libc_misc_picosbrk.c.o."
},
{
"kind": "block",
"id": "task01.worksheet",
"title": "TASK01 · Zapis dowodu ucznia",
"content_tex": "\\textbf{1. Przewidywanie przed uruchomieniem}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{2. Wynik programu hostowego}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{3. Wynik RV32I i kod wyjścia}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\\par\\noindent\\dotfill\\par\\noindent\\dotfill"
}
],
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_ref": "C09.KW01"
},
"task02": {
"title": "Kontrolowany brak dostawcy sbrk",
"uuid": "9c55597f-3145-5744-b13e-f1c0ee83abc8",
"prompt_tex": "Narysuj łańcuch \\texttt{main -> malloc -> sbrk}. Następnie uruchom \\texttt{make expected-fail} i wyjaśnij, dlaczego \\texttt{--wrap=sbrk} zmienia ostatnią krawędź na \\texttt{\\_\\_wrap\\_sbrk}, dlaczego brak ELF jest wynikiem poprawnym oraz czego ten test nie mówi o runtime.",
"criterion": "Kompilacja task.o kończy się sukcesem. Link ma niezerowy status, log wymienia libc_stdlib_malloc.c.o i undefined reference to __wrap_sbrk, a must-not-exist.elf nie istnieje. Nie wykonuje się testu host ani Hazard3.",
"conclusion_tex": "",
"render_task_acceptance": false,
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — kontrolowane przecięcie granicy platformy",
"content_tex": "Kod klienta żąda 16 B przez \\texttt{malloc}. Opcja linkera przechwytuje wyłącznie symbol dostawcy pamięci, dzięki czemu testuje granicę libc--platforma bez usuwania lub modyfikowania systemowego archiwum."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — caller, człon malloc i brakujący wrapper",
"content_tex": "Obiekt tasku wnosi odwołanie do \\texttt{malloc}; linker pobiera \\texttt{libc\\_stdlib\\_malloc.c.o}. Jego odwołania do \\texttt{sbrk} są przez \\texttt{--wrap} przepisywane na \\texttt{\\_\\_wrap\\_sbrk}. Żaden obiekt celowo nie definiuje tej ostatniej nazwy."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "Wywołanie malloc jest bezpośrednie, a --wrap jest transformacją nazw podczas linkowania, nie mechanizmem callbacku, ISR ani dynamicznego dispatchu w programie."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — porażka ma precyzyjną sygnaturę",
"content_tex": "Sukces testu oznacza jednocześnie: obiekt C powstał, link zwrócił status różny od zera, diagnostyka zawiera \\texttt{\\_\\_wrap\\_sbrk} oraz nazwę członu malloc, a końcowy ELF nie istnieje. Inny błąd linkera nie spełnia kryterium."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — expected-fail jako kontrolowany przebieg",
"content_tex": "Skrypt usuwa stary artefakt, kompiluje task, uruchamia link w trybie dopuszczającym błąd, zapisuje status i log, po czym przywraca rygor \\texttt{set -e}. Dopiero cztery asercje zamieniają oczekiwaną porażkę programu w sukces testu."
},
{
"id": "A6",
"status": "unavailable",
"title": "STATE",
"reason": "Linkowanie kończy się przed utworzeniem obrazu wykonywalnego, więc nie istnieje runtime, breakpoint ani obiekt domenowy zmieniający stan w czasie wykonania."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — dowodem jest diagnostyka przed runtime",
"content_tex": "Odczytaj \\texttt{expected-link-failure.log}: cztery relokacje z \\texttt{libc\\_stdlib\\_malloc.c.o} wskazują brak \\texttt{\\_\\_wrap\\_sbrk}. Następnie sprawdź nieobecność \\texttt{must-not-exist.elf}; nie wolno wymyślać wartości runtime dla nieistniejącego programu."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — test negatywny musi odrzucać zły powód",
"content_tex": "Wzorzec expected-fail wymaga konkretnej sygnatury błędu i braku produktu końcowego. Sam warunek „polecenie nie przeszło” byłby za słaby, bo zaliczyłby literówkę, brak kompilatora lub błędny skrypt linkera."
}
],
"flow": [
{
"kind": "block",
"id": "task02.a1",
"title": "A1 CONTEXT · AKTYWNE — kontrolowane przecięcie granicy platformy",
"content_tex": "\\textbf{Zachowany przykład:} \\texttt{src/tasks/task02\\_malloc\\_requires\\_sbrk.c}.\\par Kod klienta żąda 16 B przez \\texttt{malloc}. Opcja linkera przechwytuje wyłącznie symbol dostawcy pamięci, dzięki czemu testuje granicę libc--platforma bez usuwania lub modyfikowania systemowego archiwum."
},
{
"kind": "block",
"id": "task02.a2",
"title": "A2 STRUCTURE · AKTYWNE — caller, człon malloc i brakujący wrapper",
"content_tex": "Obiekt tasku wnosi odwołanie do \\texttt{malloc}; linker pobiera \\texttt{libc\\_stdlib\\_malloc.c.o}. Jego odwołania do \\texttt{sbrk} są przez \\texttt{--wrap} przepisywane na \\texttt{\\_\\_wrap\\_sbrk}. Żaden obiekt celowo nie definiuje tej ostatniej nazwy."
},
{
"kind": "block",
"id": "task02.a3",
"title": "A3 DISPATCH · N/D",
"content_tex": "\\textbf{N/D.} Wywołanie malloc jest bezpośrednie, a --wrap jest transformacją nazw podczas linkowania, nie mechanizmem callbacku, ISR ani dynamicznego dispatchu w programie."
},
{
"kind": "block",
"id": "task02.a4",
"title": "A4 APPLICATION · AKTYWNE — porażka ma precyzyjną sygnaturę",
"content_tex": "Sukces testu oznacza jednocześnie: obiekt C powstał, link zwrócił status różny od zera, diagnostyka zawiera \\texttt{\\_\\_wrap\\_sbrk} oraz nazwę członu malloc, a końcowy ELF nie istnieje. Inny błąd linkera nie spełnia kryterium."
},
{
"kind": "block",
"id": "task02.a5",
"title": "A5 FLOW · AKTYWNE — expected-fail jako kontrolowany przebieg",
"content_tex": "Skrypt usuwa stary artefakt, kompiluje task, uruchamia link w trybie dopuszczającym błąd, zapisuje status i log, po czym przywraca rygor \\texttt{set -e}. Dopiero cztery asercje zamieniają oczekiwaną porażkę programu w sukces testu."
},
{
"kind": "block",
"id": "task02.a6",
"title": "A6 STATE · N/D",
"content_tex": "\\textbf{N/D.} Linkowanie kończy się przed utworzeniem obrazu wykonywalnego, więc nie istnieje runtime, breakpoint ani obiekt domenowy zmieniający stan w czasie wykonania."
},
{
"kind": "block",
"id": "task02.a7",
"title": "A7 RUNTIME · AKTYWNE — dowodem jest diagnostyka przed runtime",
"content_tex": "Odczytaj \\texttt{expected-link-failure.log}: cztery relokacje z \\texttt{libc\\_stdlib\\_malloc.c.o} wskazują brak \\texttt{\\_\\_wrap\\_sbrk}. Następnie sprawdź nieobecność \\texttt{must-not-exist.elf}; nie wolno wymyślać wartości runtime dla nieistniejącego programu."
},
{
"kind": "block",
"id": "task02.a8",
"title": "A8 PATTERNS · AKTYWNE — test negatywny musi odrzucać zły powód",
"content_tex": "Wzorzec expected-fail wymaga konkretnej sygnatury błędu i braku produktu końcowego. Sam warunek „polecenie nie przeszło” byłby za słaby, bo zaliczyłby literówkę, brak kompilatora lub błędny skrypt linkera."
},
{
"kind": "exercise",
"id": "task02.proof",
"title": "Przewidywanie → wykonanie → wniosek",
"prompt_tex": "Narysuj łańcuch \\texttt{main -> malloc -> sbrk}. Następnie uruchom \\texttt{make expected-fail} i wyjaśnij, dlaczego \\texttt{--wrap=sbrk} zmienia ostatnią krawędź na \\texttt{\\_\\_wrap\\_sbrk}, dlaczego brak ELF jest wynikiem poprawnym oraz czego ten test nie mówi o runtime.",
"evidence_tex": "Przewidywana brakująca nazwa, status kompilacji obiektu, niezerowy status linkera, cytat dwóch sygnatur z logu oraz sprawdzenie, że końcowy ELF nie istnieje.",
"criterion": "Kompilacja task.o kończy się sukcesem. Link ma niezerowy status, log wymienia libc_stdlib_malloc.c.o i undefined reference to __wrap_sbrk, a must-not-exist.elf nie istnieje. Nie wykonuje się testu host ani Hazard3."
},
{
"kind": "block",
"id": "task02.worksheet",
"title": "TASK02 · Zapis dowodu ucznia",
"content_tex": "\\textbf{1. Przewidywanie przed uruchomieniem}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{2. Wynik kompilacji task.o}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{3. Status i sygnatury błędu linkera}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\\par\\noindent\\dotfill\\par\\noindent\\dotfill"
}
],
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_ref": "C09.KW01"
},
"task03": {
"title": "Bounded sbrk i atomowy błąd",
"uuid": "5a69ff6d-bda9-5b20-a18c-a8da2f88c64a",
"prompt_tex": "Zapisz stan break po sekwencji \\texttt{+16,+24,fail,-8,fail}. Dla obu porażek wskaż sprawdzaną granicę i udowodnij, że zwrot \\texttt{(void *)-1} nie zmienia break. Porównaj stałe offsety hosta i RV32I.",
"criterion": "Host i Hazard3: grow=40, rejected_grow=40, shrink=32, rejected_shrink=32, preserved=1, bounds=1, pass=1 i kod 0. Linker: __heap_start oraz __heap_end są wyrównane do 16 B, __heap_end == __stack_bottom.",
"conclusion_tex": "",
"render_task_acceptance": false,
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — model oddzielony od libc",
"content_tex": "Task03 eksportuje nazwę \\texttt{c09\\_sbrk}, aby nie przejąć symbolu \\texttt{sbrk} picolibc. Na hoście granice obejmują statyczną arenę 512 B; na RV32I pochodzą z linkera. Ten sam algorytm widzi tylko trzy adresy."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — start, break i end",
"content_tex": "Stan ma jedną zmienną \\texttt{g\\_c09\\_break} oraz niezmienne granice \\texttt{start} i \\texttt{end}. Każdy poprawny stan spełnia \\texttt{start <= break <= end}; wolny obszar ma rozmiar \\texttt{end-break}, a zajęty prefiks \\texttt{break-start}."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "Wszystkie operacje c09_sbrk i gettery są wywoływane bezpośrednio; nie ma callbacku, ISR, tablicy funkcji ani wyboru implementacji w runtime."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — tabela pięciu przejść",
"content_tex": "Od offsetu 0 operacje dają kolejno: \\texttt{+16 -> 16}, \\texttt{+24 -> 40}, zbyt duży wzrost \\texttt{-> 40}, \\texttt{-8 -> 32}, zejście o 33 \\texttt{-> 32}. Udany call zwraca stary break; porażka zwraca wszystkie bity ustawione."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — najpierw zakres, potem commit",
"content_tex": "Dla wzrostu kod porównuje wielkość z \\texttt{end-current}; dla spadku oblicza bezpiecznie moduł także dla \\texttt{PTRDIFF\\_MIN} i porównuje go z \\texttt{current-start}. Dopiero po zaliczeniu warunku zapisuje \\texttt{g\\_c09\\_break=next}."
},
{
"id": "A6",
"status": "enabled",
"title": "STATE — VALID pozostaje VALID także po odrzuceniu",
"content_tex": "Break przechodzi przez offsety \\texttt{0 -> 16 -> 40 -> 40 -> 32 -> 32}. Obie porażki są samopętlą stanu: zmienia się wynik operacji, ale nie break. Nie ma przejścia do wartości poniżej start ani powyżej end."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — offsety zamiast niestabilnych adresów",
"content_tex": "Checkpoint publikuje cztery offsety \\texttt{40,40,32,32} oraz flagi \\texttt{preserved=1} i \\texttt{bounds=1}. Adresy bezwzględne hosta mogą się zmieniać przez ASLR, dlatego dowód porównuje różnice względem startu."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — validate then commit",
"content_tex": "Wzorzec silnej gwarancji błędu brzmi: najpierw policz bez przepełnienia, następnie sprawdź granice, a dopiero potem zmień jedyny stan. Wskaźnik break pozostaje obserwowalnie niezmieniony po odrzuconej operacji."
}
],
"flow": [
{
"kind": "block",
"id": "task03.a1",
"title": "A1 CONTEXT · AKTYWNE — model oddzielony od libc",
"content_tex": "\\textbf{Zachowane materiały:} \\texttt{src/tasks/task03\\_bounded\\_sbrk.c}, \\texttt{src/runtime/c09\\_bounded\\_sbrk.c}, \\texttt{src/runtime/c09\\_bounded\\_sbrk.h}, \\texttt{link/hazard3\\_c09.ld}.\\par Task03 eksportuje nazwę \\texttt{c09\\_sbrk}, aby nie przejąć symbolu \\texttt{sbrk} picolibc. Na hoście granice obejmują statyczną arenę 512 B; na RV32I pochodzą z linkera. Ten sam algorytm widzi tylko trzy adresy."
},
{
"kind": "block",
"id": "task03.a2",
"title": "A2 STRUCTURE · AKTYWNE — start, break i end",
"content_tex": "Stan ma jedną zmienną \\texttt{g\\_c09\\_break} oraz niezmienne granice \\texttt{start} i \\texttt{end}. Każdy poprawny stan spełnia \\texttt{start <= break <= end}; wolny obszar ma rozmiar \\texttt{end-break}, a zajęty prefiks \\texttt{break-start}."
},
{
"kind": "block",
"id": "task03.a3",
"title": "A3 DISPATCH · N/D",
"content_tex": "\\textbf{N/D.} Wszystkie operacje c09\\_sbrk i gettery są wywoływane bezpośrednio; nie ma callbacku, ISR, tablicy funkcji ani wyboru implementacji w runtime."
},
{
"kind": "block",
"id": "task03.a4",
"title": "A4 APPLICATION · AKTYWNE — tabela pięciu przejść",
"content_tex": "Od offsetu 0 operacje dają kolejno: \\texttt{+16 -> 16}, \\texttt{+24 -> 40}, zbyt duży wzrost \\texttt{-> 40}, \\texttt{-8 -> 32}, zejście o 33 \\texttt{-> 32}. Udany call zwraca stary break; porażka zwraca wszystkie bity ustawione."
},
{
"kind": "block",
"id": "task03.a5",
"title": "A5 FLOW · AKTYWNE — najpierw zakres, potem commit",
"content_tex": "Dla wzrostu kod porównuje wielkość z \\texttt{end-current}; dla spadku oblicza bezpiecznie moduł także dla \\texttt{PTRDIFF\\_MIN} i porównuje go z \\texttt{current-start}. Dopiero po zaliczeniu warunku zapisuje \\texttt{g\\_c09\\_break=next}."
},
{
"kind": "block",
"id": "task03.a6",
"title": "A6 STATE · AKTYWNE — VALID pozostaje VALID także po odrzuceniu",
"content_tex": "Break przechodzi przez offsety \\texttt{0 -> 16 -> 40 -> 40 -> 32 -> 32}. Obie porażki są samopętlą stanu: zmienia się wynik operacji, ale nie break. Nie ma przejścia do wartości poniżej start ani powyżej end."
},
{
"kind": "block",
"id": "task03.a7",
"title": "A7 RUNTIME · AKTYWNE — offsety zamiast niestabilnych adresów",
"content_tex": "Checkpoint publikuje cztery offsety \\texttt{40,40,32,32} oraz flagi \\texttt{preserved=1} i \\texttt{bounds=1}. Adresy bezwzględne hosta mogą się zmieniać przez ASLR, dlatego dowód porównuje różnice względem startu."
},
{
"kind": "block",
"id": "task03.a8",
"title": "A8 PATTERNS · AKTYWNE — validate then commit",
"content_tex": "Wzorzec silnej gwarancji błędu brzmi: najpierw policz bez przepełnienia, następnie sprawdź granice, a dopiero potem zmień jedyny stan. Wskaźnik break pozostaje obserwowalnie niezmieniony po odrzuconej operacji."
},
{
"kind": "exercise",
"id": "task03.proof",
"title": "Przewidywanie → wykonanie → wniosek",
"prompt_tex": "Zapisz stan break po sekwencji \\texttt{+16,+24,fail,-8,fail}. Dla obu porażek wskaż sprawdzaną granicę i udowodnij, że zwrot \\texttt{(void *)-1} nie zmienia break. Porównaj stałe offsety hosta i RV32I.",
"evidence_tex": "Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.",
"criterion": "Host i Hazard3: grow=40, rejected_grow=40, shrink=32, rejected_shrink=32, preserved=1, bounds=1, pass=1 i kod 0. Linker: __heap_start oraz __heap_end są wyrównane do 16 B, __heap_end == __stack_bottom."
},
{
"kind": "block",
"id": "task03.worksheet",
"title": "TASK03 · Zapis dowodu ucznia",
"content_tex": "\\textbf{1. Przewidywanie przed uruchomieniem}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{2. Wynik programu hostowego}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{3. Wynik RV32I i kod wyjścia}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\\par\\noindent\\dotfill\\par\\noindent\\dotfill"
}
],
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_ref": "C09.KW01"
},
"task04": {
"title": "Wyrównany smalloc append-only",
"uuid": "e745e1ea-9494-5b75-9596-816022153237",
"prompt_tex": "Dla żądań 1 B i 17 B wyprowadź rozmiar nagłówka, rezerwację po align-up, adres obu payloadów i zmianę break. Wskaż, dlaczego brak \\texttt{c09\\_sfree} jest częścią kontraktu, a reset areny nie jest odpowiednikiem \\texttt{free(pointer)}.",
"criterion": "Host i Hazard3: header=16, first=32, second=48, delta=32, aligned=1, metadata=1, append=1, reset=1, pass=1 i kod 0. Nagłówki mają sequence 1 i 2; po resecie pierwszy payload wraca pod poprzedni pierwszy adres.",
"conclusion_tex": "",
"render_task_acceptance": false,
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — API ma celowo małą władzę",
"content_tex": "Klient może przydzielić payload i zresetować całą arenę. Nie może zwolnić jednego bloku ani przekazać modelowi wskaźnika do picolibc. \\texttt{c09\\_smalloc} pobiera surowy fragment wyłącznie przez ograniczone \\texttt{c09\\_sbrk}."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — 16 B metadanych bezpośrednio przed payloadem",
"content_tex": "Nagłówek ma cztery pola \\texttt{uint32\\_t}: magic, requested, reserved i sequence. Jego rozmiar wynosi 16 B. Zwrócony adres to \\texttt{header+1}; inspekcja kontrolnie cofa się o dokładnie jeden rzeczywisty nagłówek."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "smalloc, inspect, reset i c09_sbrk są zwykłymi wywołaniami bezpośrednimi; przykład nie zawiera callbacku, ISR ani dynamicznie wybieranej strategii."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — geometria dwóch bloków",
"content_tex": "Żądanie 1 B daje \\texttt{align16(16+1)=32}; żądanie 17 B daje \\texttt{align16(16+17)=48}. Pierwszy payload leży 16 B od startu, drugi 48 B od startu, więc ich delta wynosi 32 B. Oba adresy są wielokrotnością 16."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — size, align, reserve, header, payload",
"content_tex": "Kod odrzuca zero i przepełnienie, zaokrągla sumę nagłówka i żądania, sprawdza reprezentowalność jako \\texttt{ptrdiff\\_t}, rezerwuje cały blok jednym \\texttt{c09\\_sbrk}, zapisuje cztery pola nagłówka i dopiero wtedy zwraca payload."
},
{
"id": "A6",
"status": "enabled",
"title": "STATE — EMPTY, TWO BLOCKS, RESET",
"content_tex": "Arena przechodzi \\texttt{EMPTY -> seq1 -> seq1+seq2}. Brak indywidualnego free oznacza, że break tylko rośnie. \\texttt{c09\\_smalloc\\_reset} wykonuje jedno przejście z dowolnej zawartości do EMPTY i unieważnia oba wskaźniki naraz."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — pamięć potwierdza header przed payloadem",
"content_tex": "W checkpointcie odczytaj \\texttt{first-16}: słowo magic to \\texttt{0x534D414C}, requested=1, reserved=32, sequence=1. Pod \\texttt{second-16} odpowiednie wartości to 17, 48 i 2. Globale potwierdzają deltę 32 i reset."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — bump allocator z jawną granicą użycia",
"content_tex": "Append-only arena jest wartościowa dla fazowego scratch storage, gdy wszystkie obiekty mają wspólny czas życia. Nie zastępuje ogólnego malloc: brak selekcji, ponownego użycia pojedynczych luk, split, coalescing i niezależnego free."
}
],
"flow": [
{
"kind": "block",
"id": "task04.a1",
"title": "A1 CONTEXT · AKTYWNE — API ma celowo małą władzę",
"content_tex": "\\textbf{Zachowane materiały:} \\texttt{src/tasks/task04\\_smalloc.c}, \\texttt{src/runtime/c09\\_smalloc.c}, \\texttt{src/runtime/c09\\_smalloc.h}, \\texttt{src/runtime/c09\\_bounded\\_sbrk.c}, \\texttt{src/runtime/c09\\_bounded\\_sbrk.h}.\\par Klient może przydzielić payload i zresetować całą arenę. Nie może zwolnić jednego bloku ani przekazać modelowi wskaźnika do picolibc. \\texttt{c09\\_smalloc} pobiera surowy fragment wyłącznie przez ograniczone \\texttt{c09\\_sbrk}."
},
{
"kind": "block",
"id": "task04.a2",
"title": "A2 STRUCTURE · AKTYWNE — 16 B metadanych bezpośrednio przed payloadem",
"content_tex": "Nagłówek ma cztery pola \\texttt{uint32\\_t}: magic, requested, reserved i sequence. Jego rozmiar wynosi 16 B. Zwrócony adres to \\texttt{header+1}; inspekcja kontrolnie cofa się o dokładnie jeden rzeczywisty nagłówek."
},
{
"kind": "block",
"id": "task04.a3",
"title": "A3 DISPATCH · N/D",
"content_tex": "\\textbf{N/D.} smalloc, inspect, reset i c09\\_sbrk są zwykłymi wywołaniami bezpośrednimi; przykład nie zawiera callbacku, ISR ani dynamicznie wybieranej strategii."
},
{
"kind": "block",
"id": "task04.a4",
"title": "A4 APPLICATION · AKTYWNE — geometria dwóch bloków",
"content_tex": "Żądanie 1 B daje \\texttt{align16(16+1)=32}; żądanie 17 B daje \\texttt{align16(16+17)=48}. Pierwszy payload leży 16 B od startu, drugi 48 B od startu, więc ich delta wynosi 32 B. Oba adresy są wielokrotnością 16."
},
{
"kind": "block",
"id": "task04.a5",
"title": "A5 FLOW · AKTYWNE — size, align, reserve, header, payload",
"content_tex": "Kod odrzuca zero i przepełnienie, zaokrągla sumę nagłówka i żądania, sprawdza reprezentowalność jako \\texttt{ptrdiff\\_t}, rezerwuje cały blok jednym \\texttt{c09\\_sbrk}, zapisuje cztery pola nagłówka i dopiero wtedy zwraca payload."
},
{
"kind": "block",
"id": "task04.a6",
"title": "A6 STATE · AKTYWNE — EMPTY, TWO BLOCKS, RESET",
"content_tex": "Arena przechodzi \\texttt{EMPTY -> seq1 -> seq1+seq2}. Brak indywidualnego free oznacza, że break tylko rośnie. \\texttt{c09\\_smalloc\\_reset} wykonuje jedno przejście z dowolnej zawartości do EMPTY i unieważnia oba wskaźniki naraz."
},
{
"kind": "block",
"id": "task04.a7",
"title": "A7 RUNTIME · AKTYWNE — pamięć potwierdza header przed payloadem",
"content_tex": "W checkpointcie odczytaj \\texttt{first-16}: słowo magic to \\texttt{0x534D414C}, requested=1, reserved=32, sequence=1. Pod \\texttt{second-16} odpowiednie wartości to 17, 48 i 2. Globale potwierdzają deltę 32 i reset."
},
{
"kind": "block",
"id": "task04.a8",
"title": "A8 PATTERNS · AKTYWNE — bump allocator z jawną granicą użycia",
"content_tex": "Append-only arena jest wartościowa dla fazowego scratch storage, gdy wszystkie obiekty mają wspólny czas życia. Nie zastępuje ogólnego malloc: brak selekcji, ponownego użycia pojedynczych luk, split, coalescing i niezależnego free."
},
{
"kind": "exercise",
"id": "task04.proof",
"title": "Przewidywanie → wykonanie → wniosek",
"prompt_tex": "Dla żądań 1 B i 17 B wyprowadź rozmiar nagłówka, rezerwację po align-up, adres obu payloadów i zmianę break. Wskaż, dlaczego brak \\texttt{c09\\_sfree} jest częścią kontraktu, a reset areny nie jest odpowiednikiem \\texttt{free(pointer)}.",
"evidence_tex": "Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.",
"criterion": "Host i Hazard3: header=16, first=32, second=48, delta=32, aligned=1, metadata=1, append=1, reset=1, pass=1 i kod 0. Nagłówki mają sequence 1 i 2; po resecie pierwszy payload wraca pod poprzedni pierwszy adres."
},
{
"kind": "block",
"id": "task04.worksheet",
"title": "TASK04 · Zapis dowodu ucznia",
"content_tex": "\\textbf{1. Przewidywanie przed uruchomieniem}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{2. Wynik programu hostowego}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{3. Wynik RV32I i kod wyjścia}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\\par\\noindent\\dotfill\\par\\noindent\\dotfill"
}
],
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_ref": "C09.KW01"
},
"task05": {
"title": "malloc/free picolibc jako czarna skrzynka",
"uuid": "9a7174f1-efb7-5c18-afbe-1a34ee6da0b7",
"prompt_tex": "Nie zakładając formatu nagłówka libc, wypisz własności możliwe do sprawdzenia przez klienta dwóch przydziałów. Uruchom host i Hazard3, sprawdź checksum oraz mapę, a w ELF wykaż współistnienie vendorowego \\texttt{\\_sbrk} i picolibc \\texttt{sbrk}.",
"criterion": "Host i Hazard3: nonnull=1, delta_nonzero=1, checksum=4612, nonoverlap=1, aligned=1, freed=1, pass=1 i kod 0. Mapa RV32I zawiera libc_stdlib_malloc.c.o, libc_stdlib_free.c.o i libc_misc_picosbrk.c.o. ELF definiuje _sbrk, sbrk, malloc, free, __heap_start i __heap_end.",
"conclusion_tex": "",
"render_task_acceptance": false,
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — klient nie zna wnętrza picolibc",
"content_tex": "Kod tasku używa tylko \\texttt{malloc} i \\texttt{free}. Linker łączy picolibc z platformą przez \\texttt{sbrk} oraz symbole granic. Vendorowe \\texttt{\\_sbrk} pozostaje osobnym, nieużywanym przez malloc symbolem startu Hazard3."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — obraz składa się z czterech warstw",
"content_tex": "ELF zawiera klienta, człony \\texttt{malloc/free}, człon \\texttt{picosbrk} i platformowy startup. Skrypt linkera dostarcza \\texttt{\\_\\_heap\\_start/\\_\\_heap\\_end}; crt0 ustawia \\texttt{gp/tp} i zeruje BSS/TBSS. Mapa pokazuje, który obiekt wniósł każdy publiczny symbol."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "Klient wywołuje malloc i free bezpośrednio; badany przebieg nie przekazuje callbacku, nie obsługuje ISR i nie wybiera alokatora przez tablicę funkcji."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — kontrakt dwóch niezależnych payloadów",
"content_tex": "Program żąda 13 B i 29 B, zapisuje dwie różne sekwencje, sumuje odczytane bajty do 4612 i sprawdza rozłączność półotwartych zakresów adresów. Mierzy co najmniej 8-bajtowe wyrównanie, ale nie narzuca odległości między blokami."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — allocate, use, verify, release",
"content_tex": "Po dwóch udanych przydziałach kod zapisuje i natychmiast odczytuje oba zakresy, oblicza checksum i warunki adresowe, zwalnia najpierw drugi, potem pierwszy blok, ustawia \\texttt{freed=1} i zatrzymuje się w checkpointcie."
},
{
"id": "A6",
"status": "enabled",
"title": "STATE — własność payloadu kończy się w free",
"content_tex": "Każdy wskaźnik przechodzi od braku zasobu do stanu OWNED po udanym malloc, następnie USED po zapisie i RELEASED po free. Program nie dereferencjuje wskaźnika w RELEASED i nie bada, czy następny malloc odzyska ten sam adres."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — wykonanie plus audyt ELF",
"content_tex": "Hazard3 kończy program kodem 0; globale pokazują checksum 4612, rozłączność, alignment i oba free. \\texttt{nm} musi równocześnie pokazać \\texttt{\\_sbrk} oraz \\texttt{sbrk}, a mapa trzy człony picolibc związane z malloc, free i dostawcą pamięci."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — test kontraktu bez zgadywania implementacji",
"content_tex": "Poprawny test czarnej skrzynki wymusza własności API, ale nie zależy od prywatnego nagłówka, kierunku wzrostu sterty, dokładnej delty adresów ani ponownego użycia po free. Dzięki temu pozostaje wartościowy po zmianie implementacji libc."
}
],
"flow": [
{
"kind": "block",
"id": "task05.a1",
"title": "A1 CONTEXT · AKTYWNE — klient nie zna wnętrza picolibc",
"content_tex": "\\textbf{Zachowane materiały:} \\texttt{src/tasks/task05\\_picolibc\\_malloc.c}, \\texttt{crt0.S}, \\texttt{link/hazard3\\_c09.ld}.\\par Kod tasku używa tylko \\texttt{malloc} i \\texttt{free}. Linker łączy picolibc z platformą przez \\texttt{sbrk} oraz symbole granic. Vendorowe \\texttt{\\_sbrk} pozostaje osobnym, nieużywanym przez malloc symbolem startu Hazard3."
},
{
"kind": "block",
"id": "task05.a2",
"title": "A2 STRUCTURE · AKTYWNE — obraz składa się z czterech warstw",
"content_tex": "ELF zawiera klienta, człony \\texttt{malloc/free}, człon \\texttt{picosbrk} i platformowy startup. Skrypt linkera dostarcza \\texttt{\\_\\_heap\\_start/\\_\\_heap\\_end}; crt0 ustawia \\texttt{gp/tp} i zeruje BSS/TBSS. Mapa pokazuje, który obiekt wniósł każdy publiczny symbol."
},
{
"kind": "block",
"id": "task05.a3",
"title": "A3 DISPATCH · N/D",
"content_tex": "\\textbf{N/D.} Klient wywołuje malloc i free bezpośrednio; badany przebieg nie przekazuje callbacku, nie obsługuje ISR i nie wybiera alokatora przez tablicę funkcji."
},
{
"kind": "block",
"id": "task05.a4",
"title": "A4 APPLICATION · AKTYWNE — kontrakt dwóch niezależnych payloadów",
"content_tex": "Program żąda 13 B i 29 B, zapisuje dwie różne sekwencje, sumuje odczytane bajty do 4612 i sprawdza rozłączność półotwartych zakresów adresów. Mierzy co najmniej 8-bajtowe wyrównanie, ale nie narzuca odległości między blokami."
},
{
"kind": "block",
"id": "task05.a5",
"title": "A5 FLOW · AKTYWNE — allocate, use, verify, release",
"content_tex": "Po dwóch udanych przydziałach kod zapisuje i natychmiast odczytuje oba zakresy, oblicza checksum i warunki adresowe, zwalnia najpierw drugi, potem pierwszy blok, ustawia \\texttt{freed=1} i zatrzymuje się w checkpointcie."
},
{
"kind": "block",
"id": "task05.a6",
"title": "A6 STATE · AKTYWNE — własność payloadu kończy się w free",
"content_tex": "Każdy wskaźnik przechodzi od braku zasobu do stanu OWNED po udanym malloc, następnie USED po zapisie i RELEASED po free. Program nie dereferencjuje wskaźnika w RELEASED i nie bada, czy następny malloc odzyska ten sam adres."
},
{
"kind": "block",
"id": "task05.a7",
"title": "A7 RUNTIME · AKTYWNE — wykonanie plus audyt ELF",
"content_tex": "Hazard3 kończy program kodem 0; globale pokazują checksum 4612, rozłączność, alignment i oba free. \\texttt{nm} musi równocześnie pokazać \\texttt{\\_sbrk} oraz \\texttt{sbrk}, a mapa trzy człony picolibc związane z malloc, free i dostawcą pamięci."
},
{
"kind": "block",
"id": "task05.a8",
"title": "A8 PATTERNS · AKTYWNE — test kontraktu bez zgadywania implementacji",
"content_tex": "Poprawny test czarnej skrzynki wymusza własności API, ale nie zależy od prywatnego nagłówka, kierunku wzrostu sterty, dokładnej delty adresów ani ponownego użycia po free. Dzięki temu pozostaje wartościowy po zmianie implementacji libc."
},
{
"kind": "exercise",
"id": "task05.proof",
"title": "Przewidywanie → wykonanie → wniosek",
"prompt_tex": "Nie zakładając formatu nagłówka libc, wypisz własności możliwe do sprawdzenia przez klienta dwóch przydziałów. Uruchom host i Hazard3, sprawdź checksum oraz mapę, a w ELF wykaż współistnienie vendorowego \\texttt{\\_sbrk} i picolibc \\texttt{sbrk}.",
"evidence_tex": "Przewidywany wynik, odczyt programu hostowego wraz z kodem powrotu, wartość obiektu wynikowego w obrazie RV32I oraz krótki wniosek wyjaśniający zgodność albo różnicę.",
"criterion": "Host i Hazard3: nonnull=1, delta_nonzero=1, checksum=4612, nonoverlap=1, aligned=1, freed=1, pass=1 i kod 0. Mapa RV32I zawiera libc_stdlib_malloc.c.o, libc_stdlib_free.c.o i libc_misc_picosbrk.c.o. ELF definiuje _sbrk, sbrk, malloc, free, __heap_start i __heap_end."
},
{
"kind": "block",
"id": "task05.worksheet",
"title": "TASK05 · Zapis dowodu ucznia",
"content_tex": "\\textbf{1. Przewidywanie przed uruchomieniem}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{2. Wynik programu hostowego}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{3. Wynik RV32I i kod wyjścia}\\par\\noindent\\dotfill\\par\\noindent\\dotfill\\par\\textbf{4. Wniosek: reguła języka lub kontrakt targetu potwierdzony przez pomiar}\\par\\noindent\\dotfill\\par\\noindent\\dotfill"
}
],
"educational_requirement_refs": [
"C09.WE01"
],
"learning_effect_refs": [
"C09.EN01",
"C09.EK01"
],
"assessment_criterion_ref": "C09.KW01"
}
},
"tasks_order": [
"task01",
"task02",
"task03",
"task04",
"task05"
]
}
+366
View File
@@ -0,0 +1,366 @@
{
"card": {
"number": "09",
"slug": "smalloc-sbrk-libc",
"title": "smalloc, sbrk i minimalne libc",
"topic": "K&R 5.4 i 7.8: granica libc, bounded sbrk, prosty smalloc oraz malloc/free picolibc",
"status": "Gotowa",
"version": "v00.01",
"revision_date": "2026-07-20T00:00:00+02:00"
},
"front": {
"goal": "Uczeń rozdziela wybór funkcji z archiwum libc, kontrakt dostawcy pamięci sbrk i politykę alokatora. Najpierw dowodzi, że strlen nie potrzebuje sterty, potem kontrolowanie zrywa zależność malloc do sbrk, buduje ograniczony model break i wyrównany smalloc, a na końcu traktuje prawdziwe malloc/free picolibc jako czarną skrzynkę sprawdzaną przez kontrakt.",
"scope": "Pięć zachowanych przykładów działa na hoście i RV32I tam, gdzie istnieje program uruchamialny. Task02 jest zamierzonym expected-fail linkera i nie ma obrazu runtime. W aktualnym GCC 15.2 biblioteka to picolibc: jej malloc odwołuje się do sbrk, podczas gdy vendorowy init.S Hazard3 definiuje inny symbol \\_sbrk. Własny linker ogranicza stertę symbolami \\_\\_heap\\_start i \\_\\_heap\\_end oraz rezerwuje 64 KiB stosu."
},
"learning": {
"reasoning_label": "Trzy warstwy jednego przydziału",
"reasoning": "Uczeń wyprowadza osobno: wybór członu archiwum, wiązanie symbolu dostawcy pamięci oraz inwarianty polityki alokacji, zamiast nazywać wszystkie trzy mechanizmy stertą.",
"practice_label": "Dowód z mapy, stanu i wykonania",
"practice": "Uczeń porównuje mapę linkera, kontrolowany błąd symbolu, globalne checkpointy, wynik hosta i kod wyjścia Hazard3, a wnioski ogranicza do naprawdę zmierzonych własności.",
"criterion": "Task01: length=16, strlen.c.o obecny, człony malloc/free/picosbrk nieobecne. Task02: link kończy się niezerowo przez brak __wrap_sbrk, powstaje log, ale nie ELF. Task03: offset break 40 po wzroście, 40 po odrzuconym wzroście, 32 po skurczeniu i 32 po odrzuconym zejściu poniżej początku; granice są zachowane. Task04: header=16, rezerwacje 32 i 48, delta payloadów 32, alignment=16, kolejność append-only i reset całej areny. Task05: dwa niepuste, rozłączne i co najmniej 8-bajtowo wyrównane bloki, checksum=4612, oba free wykonane; mapa zawiera malloc/free/picosbrk, a ELF równocześnie _sbrk i sbrk. Wszystkie uruchamialne programy host i Hazard3 zwracają 0.",
"requirement": "Czytanie mapy linkera i symboli ELF, bezpieczna arytmetyka granic, wyrównanie bloków oraz rozróżnienie modelu dydaktycznego od kontraktu prawdziwej biblioteki."
},
"sections": [
{
"title": "1 — Funkcja z libc nie oznacza automatycznie sterty",
"content_tex": "Archiwum \\texttt{libc.a} nie jest włączane do programu w całości. Linker pobiera człon, który rozwiązuje potrzebny symbol, a następne człony tylko wtedy, gdy pojawią się kolejne nierozwiązane odwołania. Dlatego bezpośrednie wywołanie \\texttt{strlen} z opcją \\texttt{-fno-builtin} wybiera \\texttt{strlen.c.o}, lecz nie wybiera \\texttt{libc\\_stdlib\\_malloc.c.o} ani \\texttt{libc\\_misc\\_picosbrk.c.o}. Dowodem jest mapa linkera, a nie samo dołączenie nagłówka \\texttt{string.h}.\n\nTask01 rozstrzyga wyłącznie tę zależność. Nie dowodzi, że każda funkcja tekstowa jest bezkosztowa ani że całe libc nie używa pamięci dynamicznej."
},
{
"title": "2 — malloc potrzebuje sbrk, lecz _sbrk to inna nazwa",
"content_tex": "W badanym GCC 15.2 używane jest \\textbf{picolibc}, nie newlib. Człon \\texttt{libc\\_stdlib\\_malloc.c.o} ma niezdefiniowane odwołanie do \\texttt{sbrk}. Picolibc może dostarczyć je z \\texttt{libc\\_misc\\_picosbrk.c.o}; ten kod opiera granice na symbolach linkera \\texttt{\\_\\_heap\\_start} i \\texttt{\\_\\_heap\\_end}. Z kolei \\texttt{vendor/Hazard3/.../init.S} definiuje \\texttt{\\_sbrk} z jednym podkreśleniem. Dla linkera są to dwa różne symbole, więc mogą współistnieć w Task05.\n\nTask02 używa \\texttt{--wrap=sbrk}: każde odwołanie do \\texttt{sbrk} zostaje skierowane do celowo brakującego \\texttt{\\_\\_wrap\\_sbrk}. Oczekiwany błąd jest kontrolowanym testem krawędzi \\texttt{malloc -> sbrk}, a nie awarią całego projektu."
},
{
"title": "3 — Od alloc z K&R 5.4 do bounded sbrk i smalloc",
"content_tex": "Liniowy \\texttt{alloc} z K\\&R 5.4 przesuwa wskaźnik w stałej tablicy. Jest prosty, ale nie odzyskuje dowolnego bloku, nie wybiera najlepszego dopasowania, nie dzieli i nie scala wolnych obszarów. \\texttt{afree} może cofnąć wskaźnik jedynie do adresu należącego do areny, więc bez dyscypliny stosowej łatwo unieważnić późniejsze przydziały. Historyczna tablica bajtów nie jest też ogólną deklaracją wyrównania dla każdego typu.\n\nTask03 zachowuje istotę modelu, lecz jawnie wymusza \\texttt{heap\\_start <= brk <= heap\\_end}; odrzucona operacja nie zmienia break. Task04 dodaje 16-bajtowe wyrównanie i 16-bajtowy nagłówek bezpośrednio przed payloadem. Nadal jest to alokator append-only: nie ma \\texttt{sfree}, listy wolnych bloków, split ani coalescing. Jedyną operacją odzyskania jest reset całej areny, który unieważnia wszystkie wcześniejsze wskaźniki."
},
{
"title": "4 — Prawdziwe malloc/free jako czarna skrzynka",
"content_tex": "Task05 nie zakłada, że picolibc ma taki sam nagłówek lub strategię jak \\texttt{c09\\_smalloc}. Program sprawdza tylko publiczny kontrakt potrzebny klientowi: dwa udane przydziały dają używalne, rozłączne obszary; zapisane bajty można odczytać; adresy spełniają mierzone wyrównanie; oba bloki są przekazane do \\texttt{free}. Nie odczytuje pamięci po \\texttt{free} i nie wymaga ponownego użycia tego samego adresu.\n\nMinimalny startup ustawia \\texttt{gp}, pojedynczą bazę TLS \\texttt{tp}, zeruje \\texttt{.tbss} i \\texttt{.bss}, a następnie wywołuje \\texttt{main}. Skrypt linkera pozostawia \\texttt{\\_\\_heap\\_end} 64 KiB przed \\texttt{\\_\\_stack\\_top} i ma \\texttt{ASSERT} zatrzymujący obraz, gdy sekcje zajmą zarezerwowany obszar."
}
],
"tasks": [
{
"id": "task01",
"chapter": "7.8",
"title": "strlen z libc bez sterty",
"idea_tex": "libc.a: wybór strlen.c.o",
"priority": "kluczowe",
"key": true,
"source": "src/tasks/task01_libc_without_heap.c",
"prompt_tex": "Przed linkowaniem przewidź, które człony \\texttt{libc.a} są potrzebne przez bezpośrednie \\texttt{strlen}. Uruchom program, a potem w mapie udowodnij obecność \\texttt{strlen.c.o} i nieobecność członów malloc, free oraz picosbrk.",
"criterion": "Host i Hazard3: length=16, pass=1, kod 0. Mapa RV32I zawiera libc.a(strlen.c.o), ale nie zawiera libc_stdlib_malloc.c.o, libc_stdlib_free.c.o ani libc_misc_picosbrk.c.o.",
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — klient, archiwum i brak dostawcy pamięci",
"content_tex": "Klient wywołuje publiczne \\texttt{strlen}. Granicą systemu jest linker wybierający potrzebny człon \\texttt{libc.a}; Task01 nie deklaruje ani nie wywołuje \\texttt{malloc}, \\texttt{free} lub \\texttt{sbrk}."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — jedna krawędź symboliczna",
"content_tex": "Przy \\texttt{-fno-builtin} obiekt tasku zachowuje niezdefiniowany symbol \\texttt{strlen}. Linker rozwiązuje go przez \\texttt{strlen.c.o}. Ten człon nie tworzy następnej krawędzi do alokatora, dlatego graf zależności kończy się bez warstwy sterty."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "strlen i checkpoint są wywołaniami bezpośrednimi; program nie ma callbacku, ISR, wskaźnika funkcji ani dynamicznego wyboru implementacji."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — dwa niezależne warunki sukcesu",
"content_tex": "Warunek wykonania to długość 16 dla napisu \\texttt{picolibc-no-heap}. Warunek obrazu to obecność wyłącznie potrzebnego członu tekstowego i brak trzech nazw członów związanych z przydziałem. Sam kod wyjścia nie zastępuje kontroli mapy."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — kompilacja, selekcja archiwum, wywołanie",
"content_tex": "Kompilator pozostawia call do \\texttt{strlen}; linker przegląda archiwum i pobiera \\texttt{strlen.c.o}; startup uruchamia \\texttt{main}; funkcja oblicza 16; checkpoint publikuje wynik; \\texttt{main} zwraca 0. Każdy etap ma inny artefakt dowodowy."
},
{
"id": "A6",
"status": "unavailable",
"title": "STATE",
"reason": "Program wykonuje jednorazowy, czysty pomiar długości i nie ma domenowego obiektu przechodzącego przez istotne stany ani zasobu o cyklu życia."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — global i mapa odpowiadają na inne pytania",
"content_tex": "W \\texttt{task01\\_debug\\_checkpoint} global \\texttt{g\\_task01\\_length} ma wartość 16, a \\texttt{g\\_task01\\_pass} wartość 1. Osobno \\texttt{prog.map} wskazuje \\texttt{libc.a(strlen.c.o)}; test mapy odrzuca każdą nazwę członu heap."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — płać za osiągalne symbole, nie za nagłówek",
"content_tex": "Dla archiwum statycznego właściwym wzorcem audytu jest śledzenie nierozwiązanych symboli i pobranych członów. \\texttt{\\#include <string.h>} dostarcza deklarację; nie jest poleceniem dołączenia całej implementacji libc."
}
]
},
{
"id": "task02",
"chapter": "5.4 + ABI",
"title": "Kontrolowany brak dostawcy sbrk",
"idea_tex": "expected-fail: malloc -> sbrk",
"priority": "kluczowe",
"key": true,
"source": "src/tasks/task02_malloc_requires_sbrk.c",
"prompt_tex": "Narysuj łańcuch \\texttt{main -> malloc -> sbrk}. Następnie uruchom \\texttt{make expected-fail} i wyjaśnij, dlaczego \\texttt{--wrap=sbrk} zmienia ostatnią krawędź na \\texttt{\\_\\_wrap\\_sbrk}, dlaczego brak ELF jest wynikiem poprawnym oraz czego ten test nie mówi o runtime.",
"evidence_tex": "Przewidywana brakująca nazwa, status kompilacji obiektu, niezerowy status linkera, cytat dwóch sygnatur z logu oraz sprawdzenie, że końcowy ELF nie istnieje.",
"worksheet_step2": "Wynik kompilacji task.o",
"worksheet_step3": "Status i sygnatury błędu linkera",
"criterion": "Kompilacja task.o kończy się sukcesem. Link ma niezerowy status, log wymienia libc_stdlib_malloc.c.o i undefined reference to __wrap_sbrk, a must-not-exist.elf nie istnieje. Nie wykonuje się testu host ani Hazard3.",
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — kontrolowane przecięcie granicy platformy",
"content_tex": "Kod klienta żąda 16 B przez \\texttt{malloc}. Opcja linkera przechwytuje wyłącznie symbol dostawcy pamięci, dzięki czemu testuje granicę libc--platforma bez usuwania lub modyfikowania systemowego archiwum."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — caller, człon malloc i brakujący wrapper",
"content_tex": "Obiekt tasku wnosi odwołanie do \\texttt{malloc}; linker pobiera \\texttt{libc\\_stdlib\\_malloc.c.o}. Jego odwołania do \\texttt{sbrk} są przez \\texttt{--wrap} przepisywane na \\texttt{\\_\\_wrap\\_sbrk}. Żaden obiekt celowo nie definiuje tej ostatniej nazwy."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "Wywołanie malloc jest bezpośrednie, a --wrap jest transformacją nazw podczas linkowania, nie mechanizmem callbacku, ISR ani dynamicznego dispatchu w programie."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — porażka ma precyzyjną sygnaturę",
"content_tex": "Sukces testu oznacza jednocześnie: obiekt C powstał, link zwrócił status różny od zera, diagnostyka zawiera \\texttt{\\_\\_wrap\\_sbrk} oraz nazwę członu malloc, a końcowy ELF nie istnieje. Inny błąd linkera nie spełnia kryterium."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — expected-fail jako kontrolowany przebieg",
"content_tex": "Skrypt usuwa stary artefakt, kompiluje task, uruchamia link w trybie dopuszczającym błąd, zapisuje status i log, po czym przywraca rygor \\texttt{set -e}. Dopiero cztery asercje zamieniają oczekiwaną porażkę programu w sukces testu."
},
{
"id": "A6",
"status": "unavailable",
"title": "STATE",
"reason": "Linkowanie kończy się przed utworzeniem obrazu wykonywalnego, więc nie istnieje runtime, breakpoint ani obiekt domenowy zmieniający stan w czasie wykonania."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — dowodem jest diagnostyka przed runtime",
"content_tex": "Odczytaj \\texttt{expected-link-failure.log}: cztery relokacje z \\texttt{libc\\_stdlib\\_malloc.c.o} wskazują brak \\texttt{\\_\\_wrap\\_sbrk}. Następnie sprawdź nieobecność \\texttt{must-not-exist.elf}; nie wolno wymyślać wartości runtime dla nieistniejącego programu."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — test negatywny musi odrzucać zły powód",
"content_tex": "Wzorzec expected-fail wymaga konkretnej sygnatury błędu i braku produktu końcowego. Sam warunek „polecenie nie przeszło” byłby za słaby, bo zaliczyłby literówkę, brak kompilatora lub błędny skrypt linkera."
}
]
},
{
"id": "task03",
"chapter": "5.4",
"title": "Bounded sbrk i atomowy błąd",
"idea_tex": "ograniczony break i atomowy błąd",
"priority": "kluczowe",
"key": true,
"sources": [
"src/tasks/task03_bounded_sbrk.c",
"src/runtime/c09_bounded_sbrk.c",
"src/runtime/c09_bounded_sbrk.h",
"link/hazard3_c09.ld"
],
"prompt_tex": "Zapisz stan break po sekwencji \\texttt{+16,+24,fail,-8,fail}. Dla obu porażek wskaż sprawdzaną granicę i udowodnij, że zwrot \\texttt{(void *)-1} nie zmienia break. Porównaj stałe offsety hosta i RV32I.",
"criterion": "Host i Hazard3: grow=40, rejected_grow=40, shrink=32, rejected_shrink=32, preserved=1, bounds=1, pass=1 i kod 0. Linker: __heap_start oraz __heap_end są wyrównane do 16 B, __heap_end == __stack_bottom.",
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — model oddzielony od libc",
"content_tex": "Task03 eksportuje nazwę \\texttt{c09\\_sbrk}, aby nie przejąć symbolu \\texttt{sbrk} picolibc. Na hoście granice obejmują statyczną arenę 512 B; na RV32I pochodzą z linkera. Ten sam algorytm widzi tylko trzy adresy."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — start, break i end",
"content_tex": "Stan ma jedną zmienną \\texttt{g\\_c09\\_break} oraz niezmienne granice \\texttt{start} i \\texttt{end}. Każdy poprawny stan spełnia \\texttt{start <= break <= end}; wolny obszar ma rozmiar \\texttt{end-break}, a zajęty prefiks \\texttt{break-start}."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "Wszystkie operacje c09_sbrk i gettery są wywoływane bezpośrednio; nie ma callbacku, ISR, tablicy funkcji ani wyboru implementacji w runtime."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — tabela pięciu przejść",
"content_tex": "Od offsetu 0 operacje dają kolejno: \\texttt{+16 -> 16}, \\texttt{+24 -> 40}, zbyt duży wzrost \\texttt{-> 40}, \\texttt{-8 -> 32}, zejście o 33 \\texttt{-> 32}. Udany call zwraca stary break; porażka zwraca wszystkie bity ustawione."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — najpierw zakres, potem commit",
"content_tex": "Dla wzrostu kod porównuje wielkość z \\texttt{end-current}; dla spadku oblicza bezpiecznie moduł także dla \\texttt{PTRDIFF\\_MIN} i porównuje go z \\texttt{current-start}. Dopiero po zaliczeniu warunku zapisuje \\texttt{g\\_c09\\_break=next}."
},
{
"id": "A6",
"status": "enabled",
"title": "STATE — VALID pozostaje VALID także po odrzuceniu",
"content_tex": "Break przechodzi przez offsety \\texttt{0 -> 16 -> 40 -> 40 -> 32 -> 32}. Obie porażki są samopętlą stanu: zmienia się wynik operacji, ale nie break. Nie ma przejścia do wartości poniżej start ani powyżej end."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — offsety zamiast niestabilnych adresów",
"content_tex": "Checkpoint publikuje cztery offsety \\texttt{40,40,32,32} oraz flagi \\texttt{preserved=1} i \\texttt{bounds=1}. Adresy bezwzględne hosta mogą się zmieniać przez ASLR, dlatego dowód porównuje różnice względem startu."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — validate then commit",
"content_tex": "Wzorzec silnej gwarancji błędu brzmi: najpierw policz bez przepełnienia, następnie sprawdź granice, a dopiero potem zmień jedyny stan. Wskaźnik break pozostaje obserwowalnie niezmieniony po odrzuconej operacji."
}
]
},
{
"id": "task04",
"chapter": "5.4",
"title": "Wyrównany smalloc append-only",
"idea_tex": "nagłówek, alignment i reset areny",
"priority": "kluczowe",
"key": true,
"sources": [
"src/tasks/task04_smalloc.c",
"src/runtime/c09_smalloc.c",
"src/runtime/c09_smalloc.h",
"src/runtime/c09_bounded_sbrk.c",
"src/runtime/c09_bounded_sbrk.h"
],
"prompt_tex": "Dla żądań 1 B i 17 B wyprowadź rozmiar nagłówka, rezerwację po align-up, adres obu payloadów i zmianę break. Wskaż, dlaczego brak \\texttt{c09\\_sfree} jest częścią kontraktu, a reset areny nie jest odpowiednikiem \\texttt{free(pointer)}.",
"criterion": "Host i Hazard3: header=16, first=32, second=48, delta=32, aligned=1, metadata=1, append=1, reset=1, pass=1 i kod 0. Nagłówki mają sequence 1 i 2; po resecie pierwszy payload wraca pod poprzedni pierwszy adres.",
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — API ma celowo małą władzę",
"content_tex": "Klient może przydzielić payload i zresetować całą arenę. Nie może zwolnić jednego bloku ani przekazać modelowi wskaźnika do picolibc. \\texttt{c09\\_smalloc} pobiera surowy fragment wyłącznie przez ograniczone \\texttt{c09\\_sbrk}."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — 16 B metadanych bezpośrednio przed payloadem",
"content_tex": "Nagłówek ma cztery pola \\texttt{uint32\\_t}: magic, requested, reserved i sequence. Jego rozmiar wynosi 16 B. Zwrócony adres to \\texttt{header+1}; inspekcja kontrolnie cofa się o dokładnie jeden rzeczywisty nagłówek."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "smalloc, inspect, reset i c09_sbrk są zwykłymi wywołaniami bezpośrednimi; przykład nie zawiera callbacku, ISR ani dynamicznie wybieranej strategii."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — geometria dwóch bloków",
"content_tex": "Żądanie 1 B daje \\texttt{align16(16+1)=32}; żądanie 17 B daje \\texttt{align16(16+17)=48}. Pierwszy payload leży 16 B od startu, drugi 48 B od startu, więc ich delta wynosi 32 B. Oba adresy są wielokrotnością 16."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — size, align, reserve, header, payload",
"content_tex": "Kod odrzuca zero i przepełnienie, zaokrągla sumę nagłówka i żądania, sprawdza reprezentowalność jako \\texttt{ptrdiff\\_t}, rezerwuje cały blok jednym \\texttt{c09\\_sbrk}, zapisuje cztery pola nagłówka i dopiero wtedy zwraca payload."
},
{
"id": "A6",
"status": "enabled",
"title": "STATE — EMPTY, TWO BLOCKS, RESET",
"content_tex": "Arena przechodzi \\texttt{EMPTY -> seq1 -> seq1+seq2}. Brak indywidualnego free oznacza, że break tylko rośnie. \\texttt{c09\\_smalloc\\_reset} wykonuje jedno przejście z dowolnej zawartości do EMPTY i unieważnia oba wskaźniki naraz."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — pamięć potwierdza header przed payloadem",
"content_tex": "W checkpointcie odczytaj \\texttt{first-16}: słowo magic to \\texttt{0x534D414C}, requested=1, reserved=32, sequence=1. Pod \\texttt{second-16} odpowiednie wartości to 17, 48 i 2. Globale potwierdzają deltę 32 i reset."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — bump allocator z jawną granicą użycia",
"content_tex": "Append-only arena jest wartościowa dla fazowego scratch storage, gdy wszystkie obiekty mają wspólny czas życia. Nie zastępuje ogólnego malloc: brak selekcji, ponownego użycia pojedynczych luk, split, coalescing i niezależnego free."
}
]
},
{
"id": "task05",
"chapter": "7.8 + ABI",
"title": "malloc/free picolibc jako czarna skrzynka",
"idea_tex": "malloc/free picolibc jako black box",
"priority": "kluczowe",
"key": true,
"sources": [
"src/tasks/task05_picolibc_malloc.c",
"crt0.S",
"link/hazard3_c09.ld"
],
"prompt_tex": "Nie zakładając formatu nagłówka libc, wypisz własności możliwe do sprawdzenia przez klienta dwóch przydziałów. Uruchom host i Hazard3, sprawdź checksum oraz mapę, a w ELF wykaż współistnienie vendorowego \\texttt{\\_sbrk} i picolibc \\texttt{sbrk}.",
"criterion": "Host i Hazard3: nonnull=1, delta_nonzero=1, checksum=4612, nonoverlap=1, aligned=1, freed=1, pass=1 i kod 0. Mapa RV32I zawiera libc_stdlib_malloc.c.o, libc_stdlib_free.c.o i libc_misc_picosbrk.c.o. ELF definiuje _sbrk, sbrk, malloc, free, __heap_start i __heap_end.",
"viewpoints": [
{
"id": "A1",
"status": "enabled",
"title": "CONTEXT — klient nie zna wnętrza picolibc",
"content_tex": "Kod tasku używa tylko \\texttt{malloc} i \\texttt{free}. Linker łączy picolibc z platformą przez \\texttt{sbrk} oraz symbole granic. Vendorowe \\texttt{\\_sbrk} pozostaje osobnym, nieużywanym przez malloc symbolem startu Hazard3."
},
{
"id": "A2",
"status": "enabled",
"title": "STRUCTURE — obraz składa się z czterech warstw",
"content_tex": "ELF zawiera klienta, człony \\texttt{malloc/free}, człon \\texttt{picosbrk} i platformowy startup. Skrypt linkera dostarcza \\texttt{\\_\\_heap\\_start/\\_\\_heap\\_end}; crt0 ustawia \\texttt{gp/tp} i zeruje BSS/TBSS. Mapa pokazuje, który obiekt wniósł każdy publiczny symbol."
},
{
"id": "A3",
"status": "unavailable",
"title": "DISPATCH",
"reason": "Klient wywołuje malloc i free bezpośrednio; badany przebieg nie przekazuje callbacku, nie obsługuje ISR i nie wybiera alokatora przez tablicę funkcji."
},
{
"id": "A4",
"status": "enabled",
"title": "APPLICATION — kontrakt dwóch niezależnych payloadów",
"content_tex": "Program żąda 13 B i 29 B, zapisuje dwie różne sekwencje, sumuje odczytane bajty do 4612 i sprawdza rozłączność półotwartych zakresów adresów. Mierzy co najmniej 8-bajtowe wyrównanie, ale nie narzuca odległości między blokami."
},
{
"id": "A5",
"status": "enabled",
"title": "FLOW — allocate, use, verify, release",
"content_tex": "Po dwóch udanych przydziałach kod zapisuje i natychmiast odczytuje oba zakresy, oblicza checksum i warunki adresowe, zwalnia najpierw drugi, potem pierwszy blok, ustawia \\texttt{freed=1} i zatrzymuje się w checkpointcie."
},
{
"id": "A6",
"status": "enabled",
"title": "STATE — własność payloadu kończy się w free",
"content_tex": "Każdy wskaźnik przechodzi od braku zasobu do stanu OWNED po udanym malloc, następnie USED po zapisie i RELEASED po free. Program nie dereferencjuje wskaźnika w RELEASED i nie bada, czy następny malloc odzyska ten sam adres."
},
{
"id": "A7",
"status": "enabled",
"title": "RUNTIME — wykonanie plus audyt ELF",
"content_tex": "Hazard3 kończy program kodem 0; globale pokazują checksum 4612, rozłączność, alignment i oba free. \\texttt{nm} musi równocześnie pokazać \\texttt{\\_sbrk} oraz \\texttt{sbrk}, a mapa trzy człony picolibc związane z malloc, free i dostawcą pamięci."
},
{
"id": "A8",
"status": "enabled",
"title": "PATTERNS — test kontraktu bez zgadywania implementacji",
"content_tex": "Poprawny test czarnej skrzynki wymusza własności API, ale nie zależy od prywatnego nagłówka, kierunku wzrostu sterty, dokładnej delty adresów ani ponownego użycia po free. Dzięki temu pozostaje wartościowy po zmianie implementacji libc."
}
]
}
]
}
+93
View File
@@ -0,0 +1,93 @@
OUTPUT_FORMAT("elf32-littleriscv")
OUTPUT_ARCH(riscv)
ENTRY(_start)
MEMORY
{
RAM (rwx) : ORIGIN = 0x80000000, LENGTH = 16M
}
PHDRS
{
text PT_LOAD FLAGS(5); /* read + execute */
data PT_LOAD FLAGS(6); /* read + write */
tls PT_TLS FLAGS(4); /* TLS template */
}
SECTIONS
{
. = ORIGIN(RAM);
.text : ALIGN(4)
{
KEEP(*(.vectors))
*(.text.startup .text.startup.*)
*(.text .text.*)
} > RAM :text
.rodata : ALIGN(4)
{
*(.rodata .rodata.*)
} > RAM :text
.data : ALIGN(16)
{
__data_start = .;
*(.data .data.*)
*(.sdata .sdata.*)
__data_end = .;
} > RAM :data
.tdata : ALIGN(16)
{
__tls_start = .;
*(.tdata .tdata.* .gnu.linkonce.td.*)
__tdata_end = .;
} > RAM :data :tls
.tbss (NOLOAD) : ALIGN(16)
{
__tbss_start = .;
*(.tbss .tbss.* .gnu.linkonce.tb.*)
*(.tcommon)
__tbss_end = .;
} > RAM :data :tls
/* GNU ld does not advance the normal location counter for TLS NOBITS. */
.tbss_space (NOLOAD) : ALIGN(16)
{
. = ADDR(.tbss) + SIZEOF(.tbss);
} > RAM :data
.bss (NOLOAD) : ALIGN(16)
{
__bss_start = .;
*(.bss .bss.* COMMON)
*(.sbss .sbss.*)
__bss_end = .;
} > RAM :data
. = ALIGN(16);
__heap_start = .;
_end = __heap_start;
PROVIDE(end = __heap_start);
__stack_top = ORIGIN(RAM) + LENGTH(RAM);
__stack_reserve = 64K;
__stack_bottom = __stack_top - __stack_reserve;
__heap_end = __stack_bottom;
__global_pointer$ = __data_start + 0x800;
/DISCARD/ :
{
*(.comment)
*(.note .note.*)
}
}
ASSERT(__heap_start <= __heap_end,
"C09: heap overlaps the reserved 64 KiB stack")
ASSERT((__heap_start & 15) == 0,
"C09: __heap_start must be 16-byte aligned")
ASSERT((__heap_end & 15) == 0,
"C09: __heap_end must be 16-byte aligned")
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env sh
set -eu
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
repo_root=$(CDPATH= cd "$script_dir/.." && pwd)
layouts_root=$repo_root/../../../tools/card-layouts
python3 "$layouts_root/tools/build_kr_c_card.py" "$repo_root"
exec "$script_dir/render_card_layouts.sh"
+9
View File
@@ -0,0 +1,9 @@
#!/usr/bin/env sh
set -eu
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
repo_root=$(CDPATH= cd "$script_dir/.." && pwd)
layouts_root=$repo_root/../../../tools/card-layouts
exec python3 "$layouts_root/tools/render_card.py" "$repo_root" "$@"
+11
View File
@@ -0,0 +1,11 @@
#!/usr/bin/env sh
set -eu
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
repo_root=$(CDPATH= cd "$script_dir/.." && pwd)
layouts_root=$repo_root/../../../tools/card-layouts
: "${GITEA_SHA:=0000000000000000000000000000000000000000}"
export GITEA_SHA
exec "$layouts_root/tools/render_card_pdf_preserving.sh" "$repo_root"
+8
View File
@@ -0,0 +1,8 @@
#!/usr/bin/env sh
set -eu
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
repo_root=$(CDPATH= cd "$script_dir/.." && pwd)
exec make -C "$repo_root" full-check
+81
View File
@@ -0,0 +1,81 @@
#include "c09_bounded_sbrk.h"
#if defined(C09_HOST)
enum { C09_HOST_ARENA_BYTES = 512 };
static _Alignas(16) unsigned char g_c09_host_arena[C09_HOST_ARENA_BYTES];
#else
extern unsigned char __heap_start[];
extern unsigned char __heap_end[];
#endif
static uintptr_t g_c09_break;
static uintptr_t arena_start(void)
{
#if defined(C09_HOST)
return (uintptr_t)&g_c09_host_arena[0];
#else
return (uintptr_t)__heap_start;
#endif
}
static uintptr_t arena_end(void)
{
#if defined(C09_HOST)
return (uintptr_t)&g_c09_host_arena[C09_HOST_ARENA_BYTES];
#else
return (uintptr_t)__heap_end;
#endif
}
void c09_sbrk_reset(void)
{
g_c09_break = arena_start();
}
uintptr_t c09_sbrk_start_address(void)
{
return arena_start();
}
uintptr_t c09_sbrk_end_address(void)
{
return arena_end();
}
uintptr_t c09_sbrk_current_address(void)
{
return g_c09_break == 0U ? arena_start() : g_c09_break;
}
void *c09_sbrk(ptrdiff_t increment)
{
const uintptr_t start = arena_start();
const uintptr_t end = arena_end();
const uintptr_t current = c09_sbrk_current_address();
uintptr_t magnitude;
uintptr_t next;
if (current < start || current > end) {
return (void *)(uintptr_t)-1;
}
if (increment >= 0) {
magnitude = (uintptr_t)increment;
if (magnitude > end - current) {
return (void *)(uintptr_t)-1;
}
next = current + magnitude;
} else {
/* This form is defined even for PTRDIFF_MIN. */
magnitude = (uintptr_t)(-(increment + 1)) + 1U;
if (magnitude > current - start) {
return (void *)(uintptr_t)-1;
}
next = current - magnitude;
}
g_c09_break = next;
return (void *)current;
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef C09_BOUNDED_SBRK_H
#define C09_BOUNDED_SBRK_H
#include <stddef.h>
#include <stdint.h>
/*
* Model used by Task03 and Task04. Its name is deliberately not `sbrk`:
* Task05 must resolve the real picolibc symbol from libc.a.
*/
void c09_sbrk_reset(void);
void *c09_sbrk(ptrdiff_t increment);
uintptr_t c09_sbrk_start_address(void);
uintptr_t c09_sbrk_end_address(void);
uintptr_t c09_sbrk_current_address(void);
#endif
+83
View File
@@ -0,0 +1,83 @@
#include "c09_smalloc.h"
#include "c09_bounded_sbrk.h"
typedef struct {
uint32_t magic;
uint32_t requested_bytes;
uint32_t reserved_bytes;
uint32_t sequence;
} C09SmallocHeader;
typedef char c09_header_must_be_sixteen_bytes[
sizeof(C09SmallocHeader) == C09_SMALLOC_HEADER_BYTES ? 1 : -1
];
static uint32_t g_c09_sequence;
static int aligned_total(size_t requested_bytes, size_t *result)
{
const size_t header_bytes = sizeof(C09SmallocHeader);
const size_t mask = C09_SMALLOC_ALIGNMENT - 1U;
size_t unaligned;
if (requested_bytes == 0U || requested_bytes > UINT32_MAX ||
requested_bytes > (size_t)-1 - header_bytes) {
return 0;
}
unaligned = header_bytes + requested_bytes;
if (unaligned > (size_t)-1 - mask) {
return 0;
}
*result = (unaligned + mask) & ~mask;
return 1;
}
void c09_smalloc_reset(void)
{
c09_sbrk_reset();
g_c09_sequence = 0U;
}
void *c09_smalloc(size_t requested_bytes)
{
C09SmallocHeader *header;
size_t reserved_bytes;
if (!aligned_total(requested_bytes, &reserved_bytes) ||
reserved_bytes > UINT32_MAX || reserved_bytes > (size_t)PTRDIFF_MAX) {
return (void *)0;
}
header = (C09SmallocHeader *)c09_sbrk((ptrdiff_t)reserved_bytes);
if (header == (void *)(uintptr_t)-1) {
return (void *)0;
}
header->magic = C09_SMALLOC_MAGIC;
header->requested_bytes = (uint32_t)requested_bytes;
header->reserved_bytes = (uint32_t)reserved_bytes;
header->sequence = ++g_c09_sequence;
return (void *)(header + 1);
}
int c09_smalloc_inspect(const void *payload, C09SmallocInfo *result)
{
const C09SmallocHeader *header;
if (payload == (const void *)0 || result == (C09SmallocInfo *)0) {
return 0;
}
header = ((const C09SmallocHeader *)payload) - 1;
if (header->magic != C09_SMALLOC_MAGIC ||
header->reserved_bytes < sizeof(C09SmallocHeader) ||
(header->reserved_bytes % C09_SMALLOC_ALIGNMENT) != 0U) {
return 0;
}
result->magic = header->magic;
result->requested_bytes = header->requested_bytes;
result->reserved_bytes = header->reserved_bytes;
result->sequence = header->sequence;
return 1;
}
+24
View File
@@ -0,0 +1,24 @@
#ifndef C09_SMALLOC_H
#define C09_SMALLOC_H
#include <stddef.h>
#include <stdint.h>
enum {
C09_SMALLOC_ALIGNMENT = 16,
C09_SMALLOC_HEADER_BYTES = 16,
C09_SMALLOC_MAGIC = 0x534D414CU
};
typedef struct {
uint32_t magic;
uint32_t requested_bytes;
uint32_t reserved_bytes;
uint32_t sequence;
} C09SmallocInfo;
void c09_smalloc_reset(void);
void *c09_smalloc(size_t requested_bytes);
int c09_smalloc_inspect(const void *payload, C09SmallocInfo *result);
#endif
+31
View File
@@ -0,0 +1,31 @@
#include <stddef.h>
#include <string.h>
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
char g_task01_text[] = "picolibc-no-heap";
volatile size_t g_task01_length;
volatile int g_task01_pass;
__attribute__((noinline)) void task01_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
/* -fno-builtin makes this a real archive call, visible in prog.map. */
g_task01_length = strlen(g_task01_text);
g_task01_pass = g_task01_length == 16U;
task01_debug_checkpoint();
#if defined(HOST_PRINTF)
printf("length=%zu pass=%d\n", g_task01_length, g_task01_pass);
#endif
return g_task01_pass ? 0 : 1;
}
+12
View File
@@ -0,0 +1,12 @@
#include <stddef.h>
#include <stdlib.h>
volatile void *g_task02_result;
int main(void)
{
/* This source is compiled, then deliberately linked with --wrap=sbrk. */
g_task02_result = malloc(16U);
return g_task02_result == (void *)0;
}
+93
View File
@@ -0,0 +1,93 @@
#include <stddef.h>
#include <stdint.h>
#include "c09_bounded_sbrk.h"
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
volatile size_t g_task03_after_grow;
volatile size_t g_task03_after_failed_grow;
volatile size_t g_task03_after_shrink;
volatile size_t g_task03_after_failed_shrink;
volatile int g_task03_failures_preserve_break;
volatile int g_task03_bounds_hold;
volatile int g_task03_pass;
__attribute__((noinline)) void task03_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
const uintptr_t failed = (uintptr_t)-1;
uintptr_t start;
uintptr_t end;
uintptr_t before_failure;
uintptr_t before_underflow;
uintptr_t current;
size_t remaining;
size_t used;
void *first;
void *second;
void *rejected_grow;
void *old_break;
void *rejected_shrink;
c09_sbrk_reset();
start = c09_sbrk_start_address();
end = c09_sbrk_end_address();
first = c09_sbrk(16);
second = c09_sbrk(24);
current = c09_sbrk_current_address();
g_task03_after_grow = (size_t)(current - start);
remaining = (size_t)(end - current);
before_failure = current;
rejected_grow = c09_sbrk((ptrdiff_t)(remaining + 1U));
current = c09_sbrk_current_address();
g_task03_after_failed_grow = (size_t)(current - start);
old_break = c09_sbrk(-8);
current = c09_sbrk_current_address();
g_task03_after_shrink = (size_t)(current - start);
used = (size_t)(current - start);
before_underflow = current;
rejected_shrink = c09_sbrk(-(ptrdiff_t)(used + 1U));
current = c09_sbrk_current_address();
g_task03_after_failed_shrink = (size_t)(current - start);
g_task03_failures_preserve_break =
(uintptr_t)rejected_grow == failed &&
g_task03_after_failed_grow == (size_t)(before_failure - start) &&
(uintptr_t)rejected_shrink == failed &&
g_task03_after_failed_shrink == (size_t)(before_underflow - start);
g_task03_bounds_hold = start <= current && current <= end;
g_task03_pass =
(uintptr_t)first == start &&
(uintptr_t)second == start + 16U &&
(uintptr_t)old_break == start + 40U &&
g_task03_after_grow == 40U &&
g_task03_after_shrink == 32U &&
g_task03_failures_preserve_break &&
g_task03_bounds_hold;
task03_debug_checkpoint();
#if defined(HOST_PRINTF)
printf(
"grow=%zu rejected_grow=%zu shrink=%zu rejected_shrink=%zu "
"preserved=%d bounds=%d pass=%d\n",
g_task03_after_grow, g_task03_after_failed_grow,
g_task03_after_shrink, g_task03_after_failed_shrink,
g_task03_failures_preserve_break, g_task03_bounds_hold,
g_task03_pass);
#endif
return g_task03_pass ? 0 : 1;
}
+110
View File
@@ -0,0 +1,110 @@
#include <stddef.h>
#include <stdint.h>
#include "c09_bounded_sbrk.h"
#include "c09_smalloc.h"
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
volatile size_t g_task04_header_bytes;
volatile size_t g_task04_first_reserved;
volatile size_t g_task04_second_reserved;
volatile size_t g_task04_payload_delta;
volatile int g_task04_aligned;
volatile int g_task04_header_before_payload;
volatile int g_task04_reset_rewinds_arena;
volatile int g_task04_append_only;
volatile int g_task04_pass;
__attribute__((noinline)) void task04_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
C09SmallocInfo first_info;
C09SmallocInfo second_info;
unsigned char *first;
unsigned char *second;
unsigned char *after_reset;
uintptr_t break_before_reset;
uintptr_t break_after_reset;
size_t index;
int bytes_hold = 1;
c09_smalloc_reset();
first = (unsigned char *)c09_smalloc(1U);
second = (unsigned char *)c09_smalloc(17U);
if (first == (unsigned char *)0 || second == (unsigned char *)0) {
return 1;
}
first[0] = 0x5AU;
for (index = 0U; index < 17U; ++index) {
second[index] = (unsigned char)(0x80U + index);
}
bytes_hold = first[0] == 0x5AU;
for (index = 0U; index < 17U; ++index) {
if (second[index] != (unsigned char)(0x80U + index)) {
bytes_hold = 0;
}
}
if (!c09_smalloc_inspect(first, &first_info) ||
!c09_smalloc_inspect(second, &second_info)) {
return 2;
}
g_task04_header_bytes = C09_SMALLOC_HEADER_BYTES;
g_task04_first_reserved = first_info.reserved_bytes;
g_task04_second_reserved = second_info.reserved_bytes;
g_task04_payload_delta = (size_t)((uintptr_t)second - (uintptr_t)first);
g_task04_aligned =
((uintptr_t)first % C09_SMALLOC_ALIGNMENT) == 0U &&
((uintptr_t)second % C09_SMALLOC_ALIGNMENT) == 0U;
g_task04_header_before_payload =
first_info.magic == C09_SMALLOC_MAGIC &&
first_info.requested_bytes == 1U &&
first_info.sequence == 1U &&
second_info.magic == C09_SMALLOC_MAGIC &&
second_info.requested_bytes == 17U &&
second_info.sequence == 2U;
g_task04_append_only =
(uintptr_t)second > (uintptr_t)first &&
g_task04_payload_delta == first_info.reserved_bytes;
break_before_reset = c09_sbrk_current_address();
c09_smalloc_reset();
break_after_reset = c09_sbrk_current_address();
after_reset = (unsigned char *)c09_smalloc(1U);
g_task04_reset_rewinds_arena =
break_before_reset > break_after_reset && after_reset == first;
g_task04_pass =
g_task04_header_bytes == 16U &&
g_task04_first_reserved == 32U &&
g_task04_second_reserved == 48U &&
g_task04_payload_delta == 32U &&
g_task04_aligned && g_task04_header_before_payload &&
g_task04_append_only && g_task04_reset_rewinds_arena &&
bytes_hold;
task04_debug_checkpoint();
#if defined(HOST_PRINTF)
printf(
"header=%zu first=%zu second=%zu delta=%zu aligned=%d "
"metadata=%d append=%d reset=%d pass=%d\n",
g_task04_header_bytes, g_task04_first_reserved,
g_task04_second_reserved, g_task04_payload_delta,
g_task04_aligned, g_task04_header_before_payload,
g_task04_append_only, g_task04_reset_rewinds_arena,
g_task04_pass);
#endif
return g_task04_pass ? 0 : 1;
}
+88
View File
@@ -0,0 +1,88 @@
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#if defined(HOST_PRINTF)
#include <stdio.h>
#endif
volatile uintptr_t g_task05_first_address;
volatile uintptr_t g_task05_second_address;
volatile size_t g_task05_address_delta;
volatile uint32_t g_task05_checksum;
volatile int g_task05_nonoverlap;
volatile int g_task05_aligned;
volatile int g_task05_freed;
volatile int g_task05_pass;
__attribute__((noinline)) void task05_debug_checkpoint(void)
{
#if defined(__GNUC__)
__asm__ volatile("" ::: "memory");
#endif
}
int main(void)
{
unsigned char *first = (unsigned char *)malloc(13U);
unsigned char *second = (unsigned char *)malloc(29U);
uintptr_t first_end;
uintptr_t second_end;
uint32_t checksum = 0U;
uint32_t expected = 0U;
size_t index;
if (first == (unsigned char *)0 || second == (unsigned char *)0) {
free(second);
free(first);
return 1;
}
for (index = 0U; index < 13U; ++index) {
first[index] = (unsigned char)(0x20U + index);
}
for (index = 0U; index < 29U; ++index) {
second[index] = (unsigned char)(0x80U + index);
}
for (index = 0U; index < 13U; ++index) {
checksum += first[index];
expected += (uint32_t)(0x20U + index);
}
for (index = 0U; index < 29U; ++index) {
checksum += second[index];
expected += (uint32_t)(0x80U + index);
}
g_task05_first_address = (uintptr_t)first;
g_task05_second_address = (uintptr_t)second;
g_task05_address_delta = g_task05_first_address < g_task05_second_address
? (size_t)(g_task05_second_address - g_task05_first_address)
: (size_t)(g_task05_first_address - g_task05_second_address);
first_end = g_task05_first_address + 13U;
second_end = g_task05_second_address + 29U;
g_task05_nonoverlap =
first_end <= g_task05_second_address ||
second_end <= g_task05_first_address;
g_task05_aligned =
(g_task05_first_address % 8U) == 0U &&
(g_task05_second_address % 8U) == 0U;
g_task05_checksum = checksum;
g_task05_pass = checksum == expected &&
g_task05_nonoverlap && g_task05_aligned;
free(second);
free(first);
g_task05_freed = 1;
task05_debug_checkpoint();
#if defined(HOST_PRINTF)
printf(
"nonnull=1 delta_nonzero=%d checksum=%u nonoverlap=%d "
"aligned=%d freed=%d pass=%d\n",
g_task05_address_delta != 0U, g_task05_checksum,
g_task05_nonoverlap, g_task05_aligned, g_task05_freed,
g_task05_pass);
#endif
return g_task05_pass && g_task05_freed ? 0 : 2;
}
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
task01_map="$root/build/task01_libc_without_heap/prog.map"
task05_map="$root/build/task05_picolibc_malloc/prog.map"
grep -q 'libc.a(strlen.c.o)' "$task01_map"
if grep -Eq 'libc_stdlib_malloc.c.o|libc_stdlib_free.c.o|libc_misc_picosbrk.c.o' "$task01_map"; then
echo 'FAIL Task01: heap-related archive member entered the image' >&2
exit 1
fi
for member in libc_stdlib_malloc.c.o libc_stdlib_free.c.o libc_misc_picosbrk.c.o; do
grep -q "$member" "$task05_map"
done
printf '%s\n' \
'PASS Task01 map: strlen.c.o present, malloc/free/picosbrk absent' \
'PASS Task05 map: malloc/free/picosbrk archive members present'
+80
View File
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
pdf_count="$(find "$root/doc/pdf" -maxdepth 1 -type f -name '*.pdf' | wc -l)"
[[ "$pdf_count" -eq 1 ]]
python3 - "$root/json/card_source.json" "$root/web/card-data.json" <<'PY'
import json
import sys
with open(sys.argv[1], encoding="utf-8") as stream:
card = json.load(stream)
with open(sys.argv[2], encoding="utf-8") as stream:
web = json.load(stream)
assert card["tasks_order"] == [f"task{number:02d}" for number in range(1, 6)]
blocks = []
flow_ids = []
for task_id in card["tasks_order"]:
task = card["tasks"][task_id]
viewpoints = task["viewpoints"]
assert [item["id"] for item in viewpoints] == [f"A{number}" for number in range(1, 9)]
assert len(viewpoints) == 8
assert all(item["status"] in {"enabled", "unavailable"} for item in viewpoints)
assert all(item.get("id") for item in task["flow"])
flow_ids.extend(item["id"] for item in task["flow"])
blocks.extend(item for item in task["flow"] if item["id"].startswith(task_id + ".a"))
assert len(blocks) == 40
assert len(flow_ids) == len(set(flow_ids))
task02_proof = next(
item for item in card["tasks"]["task02"]["flow"]
if item["id"] == "task02.proof"
)
assert "programu hostowego" not in task02_proof["evidence_tex"]
assert "brakująca nazwa" in task02_proof["evidence_tex"]
def viewpoint_ids(value):
if isinstance(value, dict):
identifier = value.get("id")
if isinstance(identifier, str) and identifier.rsplit(".", 1)[-1] in {
f"a{number}" for number in range(1, 9)
}:
yield identifier
for child in value.values():
yield from viewpoint_ids(child)
elif isinstance(value, list):
for child in value:
yield from viewpoint_ids(child)
web_ids = list(viewpoint_ids(web))
assert len(web_ids) == 40
assert len(set(web_ids)) == 40
print("PASS card JSON: 5 tasks x exact A1-A8 = 40 viewpoint blocks")
PY
tex_viewpoints="$(rg -o '\\begin\{ESCBlockFrame\}\{A[1-8] ' \
"$root/doc/generated/main.tex" | wc -l)"
[[ "$tex_viewpoints" -eq 40 ]]
for viewpoint in {1..8}; do
count="$(rg -o "\\\\begin\\{ESCBlockFrame\\}\\{A${viewpoint} " \
"$root/doc/generated/main.tex" | wc -l)"
[[ "$count" -eq 5 ]]
done
tex_count="$(rg -o '\\textbf\{N/D\.\}' "$root/doc/generated/main.tex" | wc -l)"
[[ "$tex_count" -ge 6 ]]
qpdf --check "$root"/doc/pdf/*.pdf
rg -q 'task02\.a6' "$root/json/card_source.json"
pdf_text="$(mktemp)"
trap 'rm -f -- "$pdf_text"' EXIT
pdftotext "$root"/doc/pdf/*.pdf "$pdf_text"
pdf_viewpoints="$(rg -o 'BLOCK A[1-8]' "$pdf_text" | wc -l)"
[[ "$pdf_viewpoints" -eq 40 ]]
if rg -qi 'anonymous|anonimowy|bez nazwy' "$pdf_text"; then
echo 'FAIL card: anonymous block marker found' >&2
exit 1
fi
printf 'PASS card artifacts: TeX, web and one valid PDF\n'
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
nm_tool="${RISCV_PREFIX:-riscv64-unknown-elf-}nm"
readelf_tool="${RISCV_PREFIX:-riscv64-unknown-elf-}readelf"
symbol_value() {
"$nm_tool" -n "$1" | awk -v wanted="$2" '$3 == wanted { print $1; exit }'
}
for task in \
task01_libc_without_heap \
task03_bounded_sbrk \
task04_smalloc \
task05_picolibc_malloc; do
elf="$root/build/$task/prog.elf"
start_hex="$(symbol_value "$elf" __heap_start)"
end_hex="$(symbol_value "$elf" __heap_end)"
stack_bottom_hex="$(symbol_value "$elf" __stack_bottom)"
reset_hex="$(symbol_value "$elf" .reset_handler)"
[[ -n "$start_hex" && -n "$end_hex" && -n "$stack_bottom_hex" ]]
[[ "$reset_hex" == "80000040" ]]
(( 16#$start_hex <= 16#$end_hex ))
(( 16#$end_hex == 16#$stack_bottom_hex ))
(( (16#$start_hex & 15) == 0 ))
(( (16#$end_hex & 15) == 0 ))
"$readelf_tool" -h "$elf" | grep -q 'Class:.*ELF32'
if "$readelf_tool" -lW "$elf" | grep -Eq 'LOAD.*RWE'; then
echo "FAIL $task: writable and executable LOAD segment" >&2
exit 1
fi
done
task05="$root/build/task05_picolibc_malloc/prog.elf"
for symbol in _sbrk sbrk malloc free __heap_start __heap_end; do
[[ -n "$(symbol_value "$task05" "$symbol")" ]]
done
printf '%s\n' \
'PASS linker: reset=0x80000040; bounded aligned heap; no RWX LOAD segment' \
'PASS Task05 ELF: vendor _sbrk and picolibc sbrk coexist'
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
if [[ "${1:-}" != "--already-built" ]]; then
make -C "$root" PROFILE=observe host >/dev/null
fi
check_output() {
local program="$1" expected="$2" actual
actual="$($program)"
if [[ "$actual" != "$expected" ]]; then
printf 'FAIL %s\nexpected: %s\nactual: %s\n' \
"$program" "$expected" "$actual" >&2
return 1
fi
printf 'PASS %s: %s\n' "$(basename -- "$(dirname -- "$program")")" "$actual"
}
check_output "$root/host-build/task01_libc_without_heap/prog" \
'length=16 pass=1'
check_output "$root/host-build/task03_bounded_sbrk/prog" \
'grow=40 rejected_grow=40 shrink=32 rejected_shrink=32 preserved=1 bounds=1 pass=1'
check_output "$root/host-build/task04_smalloc/prog" \
'header=16 first=32 second=48 delta=32 aligned=1 metadata=1 append=1 reset=1 pass=1'
check_output "$root/host-build/task05_picolibc_malloc/prog" \
'nonnull=1 delta_nonzero=1 checksum=4612 nonoverlap=1 aligned=1 freed=1 pass=1'
+1
View File
File diff suppressed because one or more lines are too long
+61
View File
File diff suppressed because one or more lines are too long
+2821
View File
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
<!doctype html>
<html lang="pl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="card-renderer" content="react">
<title>Karta pracy</title>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="app.css">
</head>
<body class="resource-header-enabled">
<div id="root"></div>
<script type="module" src="app.js"></script>
</body>
</html>
+39
View File
File diff suppressed because one or more lines are too long