Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion .github/workflows/build-check.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,33 @@
name: Build Check

on:
pull_request:
push:
branches: [main]
pull_request:
workflow_dispatch:

jobs:
verify:
name: Verify (journey replays)
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Install cross toolchain & emulator
run: sudo apt-get update && sudo apt-get install -y gcc-arm-none-eabi qemu-system-arm gdb gdb-multiarch

- name: Lint (commands vs scripts)
shell: bash
env:
LINT_STRICT: '1'
run: bash scripts/lint.sh

- name: Replay all journey scripts
shell: bash
run: bash scripts/run-all.sh

build:
runs-on: ubuntu-latest

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ site/.vitepress/.build-tmp/
*.tgz
.pnpm-store/

build/
Binary file added assets/brand/embedbox-mark-green.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/brand/embedbox-mark-inverse.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 0 additions & 16 deletions examples/instructions.md

This file was deleted.

2 changes: 1 addition & 1 deletion project.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ export default defineProject({
mermaid: true,
},

favicon: '/EmbedBox/Awesome-Embedded.ico',
favicon: '/EmbedBox/embedbox-mark.svg',
})
63 changes: 63 additions & 0 deletions scripts/journey/00-env-check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#!/usr/bin/env bash
# ── 第 1 个历程 · 环境体检 ────────────────────────────────────────────
# 本历程比较特殊:验证脚本本身就是教具——教程教你读懂它输出的体检报告。
# 必需工具(git/gcc/make)缺失 → 红;建议工具缺失只提示,不算失败。
# tier: ci-matrix
#
# 本脚本的运行方式:./scripts/journey/00-env-check.sh
#
# 正文示例命令(逐字收录,供 lint 对账;脚本自己不会去装):
# sudo apt install git
#
# 装齐工具的手工指引(不同平台各取所需;脚本自己不会去装):
# Ubuntu/Debian: sudo apt install build-essential gdb cmake
# Arch: sudo pacman -S base-devel gdb cmake
# Windows 先装 WSL2(PowerShell 管理员):
# wsl --install -d Ubuntu
# 交叉工具链与模拟器(第 6/7 个历程 才需要,现在可以先不装):
# Ubuntu/Debian: sudo apt install gcc-arm-none-eabi qemu-system-arm
# Arch: sudo pacman -S arm-none-eabi-gcc qemu-arm
set -euo pipefail

banner() { printf '\n──────── %s ────────\n' "$*"; }

banner "机器与系统"
uname -a
printf 'bash %s\n' "${BASH_VERSION:-?}"

banner "必需工具(缺任何一个,体检就是红)"
missing=0
for tool in git gcc make; do
if command -v "$tool" >/dev/null 2>&1; then
printf ' [ok] %-8s -> %s\n' "$tool" "$(command -v "$tool")"
else
printf ' [缺失] %s\n' "$tool"
missing=1
fi
done
[ "$missing" -eq 0 ] || { echo "FATAL: 必需工具缺失,安装指引见本文件头部注释" >&2; exit 1; }

banner "工具版本"
git --version
gcc --version | head -1
make --version | head -1

banner "建议工具(现在缺不要紧,后面的历程会用到再装)"
for tool in gdb cmake arm-none-eabi-gcc qemu-system-arm; do
if command -v "$tool" >/dev/null 2>&1; then
printf ' [ok] %s\n' "$tool"
else
printf ' [未装] %s —— 后面的历程会用到,到时候再装也来得及\n' "$tool"
fi
done

banner "PATH —— shell 找命令的地方"
echo "$PATH" | tr ':' '\n' | sed 's/^/ /'

banner "command not found 的三板斧"
echo " 1) 它装了吗: which gcc"
echo " 2) 它在哪: echo \"\$PATH\" 里有没有那个目录"
echo " 3) 名字对吗: 拼写、大小写、别名"

echo
echo "✅ 第 1 个历程 · 环境体检 —— 必需项全部就绪"
79 changes: 79 additions & 0 deletions scripts/journey/01-elf.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env bash
# ── 第 2 个历程 · 源码→程序 ───────────────────────────────────────────
# 重放 tutorial/journey/01-elf.md 的全部命令:把一行 hello.c
# 逐步变成可执行文件,并把每一段的中间产物拆开看。
# tier: ci-matrix
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SRC="$REPO_ROOT/src/journey/01-elf"

command -v gcc >/dev/null 2>&1 || { echo "FATAL: 没有 gcc,请先完成第 1 个历程 体检" >&2; exit 1; }

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
cd "$WORK"
cp "$SRC/hello.c" .

banner() { printf '\n──────── %s ────────\n' "$*"; }

# ═══ 开场:一行命令,先跑起来 ═══
banner "一行命令,先跑起来"
gcc hello.c -o hello && ./hello
./hello | grep -q 'hello, EmbedBox!' || { echo "FATAL: 输出不符合预期" >&2; exit 1; }

# ═══ 第一步:只跑预处理器 ═══
banner "gcc -E:只跑预处理器"
gcc -E hello.c -o hello.i
wc -l hello.i
head -12 hello.i

# 断言:stdio.h 展开后体积暴涨(几百行起步)
lines="$(wc -l < hello.i)"
[ "$lines" -gt 100 ] || { echo "FATAL: 预处理产物行数异常: $lines" >&2; exit 1; }

# ═══ 第二步:到汇编为止 ═══
banner "gcc -S:到汇编为止"
gcc -S hello.c
grep -n 'main:' hello.s
sed -n '/^main:/,/ret/p' hello.s

# ═══ 第三步:到目标文件为止 ═══
banner "gcc -c:到目标文件为止"
gcc -c hello.c
objdump -f hello.o

# 断言:目标文件带着「待重定位」标记,还不是可执行文件
objdump -f hello.o | grep -q 'HAS_RELOC' \
|| { echo "FATAL: hello.o 应当是可重定位目标文件" >&2; exit 1; }

nm hello.o

# 断言:符号表里有已定义的 main 和未决的标准库函数
# (gcc 会把单参数带换行的 printf 优化成 puts,两者都合法)
nm hello.o | grep -q ' T main' || { echo "FATAL: 符号表缺 main" >&2; exit 1; }
nm hello.o | grep -Eq ' U (printf|puts)' || { echo "FATAL: 符号表缺未决的 printf/puts" >&2; exit 1; }

# ═══ 第四步:链接成可执行文件 ═══
banner "链接:hello.o → hello"
gcc hello.o -o hello
objdump -f hello
nm hello | grep -q ' T main' || { echo "FATAL: 可执行文件缺 main" >&2; exit 1; }
objdump -f hello | grep -q 'x86-64' || { echo "FATAL: 架构字段异常" >&2; exit 1; }
./hello
./hello | grep -q 'hello, EmbedBox!' || { echo "FATAL: 输出不符合预期" >&2; exit 1; }

# ═══ 拆开看:段、反汇编、体积 ═══
banner "readelf -S:可执行文件里的段"
readelf -S hello | grep -E '(\.text|\.data|\.bss|\.rodata)' || true
readelf -S hello | grep -q '\.text' || { echo "FATAL: 缺 .text 段" >&2; exit 1; }
readelf -S hello | grep -q '\.bss' || { echo "FATAL: 缺 .bss 段" >&2; exit 1; }

banner "objdump -d:看自己程序的汇编"
objdump -d hello | sed -n '/<main>:/,/^$/p'

banner "size:三个段各占多少"
size hello

echo
echo "✅ 第 2 个历程 · 源码→程序 —— 全部断言通过"
85 changes: 85 additions & 0 deletions scripts/journey/02-gdb.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env bash
# ── 第 3 个历程 · 程序病了 ────────────────────────────────────────────
# 重放 tutorial/journey/02-gdb.md 的全部命令:用 gdb 脚本化会话
# 揪出 buggy.c 里故意埋的越界读。
# tier: ci-matrix
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
SRC="$REPO_ROOT/src/journey/02-gdb"

command -v gcc >/dev/null 2>&1 || { echo "FATAL: 没有 gcc" >&2; exit 1; }
command -v gdb >/dev/null 2>&1 || { echo "FATAL: 没有 gdb,请回第 1 个历程 补装" >&2; exit 1; }

WORK="$(mktemp -d)"
trap 'rm -rf "$WORK"' EXIT
cd "$WORK"
cp "$SRC/buggy.c" .

banner() { printf '\n──────── %s ────────\n' "$*"; }

# ═══ 编译带调试信息的版本,先看症状 ═══
banner "编译(-g 留下调试信息)与症状"
gcc -g -O0 -o buggy buggy.c
./buggy
./buggy | grep -Eq 'total = -?[0-9]+' || { echo "FATAL: 输出不符合预期" >&2; exit 1; }

# ═══ 会话一:断点 + 参数 + 回溯 ═══
banner "gdb 会话一:断点、info args、bt"
gdb -q -batch -iex 'set debuginfod enabled off' ./buggy \
-ex 'break scale' \
-ex 'run' \
-ex 'info args' \
-ex 'bt' \
| tee gdb1.log

grep -q 'Breakpoint 1, ' gdb1.log || { echo "FATAL: 断点未命中" >&2; exit 1; }
grep -q 'factor = 2' gdb1.log || { echo "FATAL: info args 输出异常" >&2; exit 1; }
grep -Eq '#0 +scale' gdb1.log || { echo "FATAL: 回溯缺 scale 帧" >&2; exit 1; }
grep -Eq '#1 .*main' gdb1.log || { echo "FATAL: 回溯缺 main 帧" >&2; exit 1; }

# ═══ 会话二:一路 continue 到第五次调用,看越界值 ═══
banner "gdb 会话二:continue 到 data[4] 那一步"
gdb -q -batch -iex 'set debuginfod enabled off' ./buggy \
-ex 'break scale' \
-ex 'run' \
-ex 'info args' \
-ex 'continue' \
-ex 'info args' \
-ex 'continue' \
-ex 'continue' \
-ex 'continue' \
-ex 'continue' \
| tee gdb2.log

# 第五次调用一定发生(循环 i=0..4 共五次),且 v 是越界读到的值
stops="$(grep -c 'Breakpoint 1, ' gdb2.log)"
[ "$stops" -ge 5 ] || { echo "FATAL: 断点应命中 5 次,实际 $stops 次" >&2; exit 1; }
grep -q 'v = ' gdb2.log || { echo "FATAL: 缺 v 的打印" >&2; exit 1; }

# ═══ 会话三:watch —— 让数据变化自己举手 ═══
banner "gdb 会话三:watch total"
gdb -q -batch -iex 'set debuginfod enabled off' ./buggy \
-ex 'break main' \
-ex 'run' \
-ex 'watch total' \
-ex 'continue' \
| tee gdb3.log

grep -q 'Old value = 0' gdb3.log || { echo "FATAL: watch 未捕获首次写入" >&2; exit 1; }
grep -q 'New value = 2' gdb3.log || { echo "FATAL: watch 首次写入值异常" >&2; exit 1; }

# ═══ 会话四:-O2 下,变量被优化掉 ═══
banner "-O2:优化和调试器打架"
gcc -O2 -g -o buggy-o2 buggy.c
gdb -q -batch -iex 'set debuginfod enabled off' ./buggy-o2 \
-ex 'break main' \
-ex 'run' \
-ex 'print total' \
-ex 'print data' \
| tee gdb4.log

grep -q 'optimized out' gdb4.log || { echo "FATAL: 应观察到 optimized out" >&2; exit 1; }

echo
echo "✅ 第 3 个历程 · 程序病了 —— 全部断言通过"
Loading
Loading