diff --git a/.github/workflows/targets.yml b/.github/workflows/targets.yml index c82ed1d79c..41d06d193b 100644 --- a/.github/workflows/targets.yml +++ b/.github/workflows/targets.yml @@ -51,5 +51,15 @@ jobs: - name: Test embedded debugger transports run: bash cmd/llgo/debugtest/embedded/runtest.sh + - name: Test physical-probe opt-in guard + shell: bash + run: | + set +e + output="$(bash cmd/llgo/debugtest/hardware/runtest.sh 2>&1)" + result=$? + set -e + test "${result}" -eq 2 + grep -F 'LLGO_HARDWARE_CONFIRM=flash' <<<"${output}" + - name: Test LLGo GDB adapter run: LLGO_GDB_INTEGRATION=1 go test ./cmd/internal/gdb -run '^TestGDBIntegration$' -count=1 diff --git a/cmd/llgo/debugtest/ACCEPTANCE.md b/cmd/llgo/debugtest/ACCEPTANCE.md new file mode 100644 index 0000000000..73fc3ef702 --- /dev/null +++ b/cmd/llgo/debugtest/ACCEPTANCE.md @@ -0,0 +1,83 @@ +# Cross-platform debugger acceptance + +This document is the completion gate for +[xgo-dev/llgo#2164](https://github.com/xgo-dev/llgo/issues/2164). A frontend is +not considered covered merely because it can open an artifact: its checked +lane must exercise the final linked artifact, execution transport, and the +common LLGo debugger ABI independently. + +## Supported tool matrix + +| Frontend | Minimum | Automated lane | +| --- | --- | --- | +| Native LLDB | LLDB 18 with Python | LLVM/LLDB 19 on Darwin and Linux | +| Embedded GDB Remote | GDB 12 with Python; target probe or emulator | `gdb-multiarch`, LLDB 19, and QEMU Cortex-M | +| WASI guest debug | Wasmtime 44; LLDB 22 with `process/wasm` | Wasmtime 47.0.3 and wasi-sdk 33 LLDB | +| Browser Wasm | Chromium 123 | Chrome for Testing 151.0.7922.71 | + +LLVM 19 is the oldest producer/validator lane. Older LLVM versions are not a +compatibility target. Exact commands live in these focused fixtures: + +- native: `cmd/llgo/lldbtest` and `cmd/internal/gdb`; +- emulated embedded: `cmd/llgo/debugtest/embedded`; +- opt-in physical probe: `cmd/llgo/debugtest/hardware`; +- WASI: `cmd/llgo/debugtest/wasi`; +- browser: `cmd/internal/browser` and `internal/browserdebug`. + +## CI baseline + +| Gate | Workflow | What must be observed | +| --- | --- | --- | +| Native Darwin/Linux | `llgo.yml` | source breakpoints, scopes, locals/globals, runtime values, goroutine ownership/stacks, and raw non-LLGo fallback | +| Emulated embedded | `targets.yml` | host ELF, GDB Remote and LLDB `gdb-remote`, source values/backtrace, identical flashed bytes with/without host DWARF, and verified retained DWARF | +| WASI | `wasi-debug.yml` | final Wasm DWARF, Wasmtime guest stub, source breakpoint, parameter/local, call stack, and clean exit | +| Browser | `browser-debug.yml` | embedded and external DWARF, standard build identity, escaped sidecar URL, source remapping, all common runtime-layout categories, real LLGo Wasm registration, and no-extension fallback | + +Physical hardware is deliberately outside required PR CI. Its guarded script +must be run for a representative probe/target before declaring a release's +hardware path validated. + +## Acceptance coverage and limits + +The checked lanes cover source/line mapping, breakpoints, call stacks, +parameters, locals, globals, lexical shadowing, aggregate and recursive DWARF +types, marker/schema rejection, and the common string, slice, interface, +function, map, channel, and goroutine layout contract where the transport can +read memory. + +Platform-specific limits remain explicit: + +- `llgo debug` defaults to `-O0`. A variable with no valid DWARF location at a + stop is omitted as optimized out; adapters never manufacture a zero value. +- Wasmtime 47 cannot expose LLGo's shared guest memory through its current + guest-debug RSP map. Source breakpoints, Wasm locals, and stacks work, while + memory-backed runtime summaries remain gated by Wasmtime issue 14062. +- Browser expression evaluation supports variable names, qualified global + names, field selection, pointers, arrays, aggregates, and schema-backed Go + runtime values. It is not a general Go expression interpreter. +- A missing external Wasm sidecar and a mismatched `build_id` fail before the + browser launches. A consumer without the LLGo extension keeps raw/name + section symbolication but does not get Go runtime presentation. +- The current Asyncify-transformed browser runtime is accepted by Go's DWARF + reader and Chrome, but LLVM's final `llvm-dwarfdump --verify` still reports + overlapping/range-containment diagnostics. This is a final-artifact blocker, + not a condition that the acceptance lane may suppress. + +Panic/trap source stops, optimized inline stepping, Go/host boundary frames, +and clean final DWARF after every LTO/post-link transform remain required +before the umbrella issue itself can be closed. Keep those failures visible in +focused fixtures rather than weakening the validators. + +## Artifact-size accounting + +Every build reports artifacts independently as: + +```text +llgo: artifact role= format= size= path= +``` + +For embedded targets, the host debug ELF and derived flash image are separate +roles, and CI verifies that host DWARF does not change loadable bytes. For +external Wasm, the deployable module and `.debug.wasm` sidecar have separate +sizes and share one `build_id`. Size regressions must compare like roles; the +combined checkout size is not a deployment-size metric. diff --git a/cmd/llgo/debugtest/README.md b/cmd/llgo/debugtest/README.md index 3fae9b34a5..765269fb78 100644 --- a/cmd/llgo/debugtest/README.md +++ b/cmd/llgo/debugtest/README.md @@ -32,6 +32,9 @@ OpenOCD interface/transport/target fields need no additional command. `llgo lldb` remains the explicit compatibility command for opening an existing artifact without building it. +The cross-platform tool pins, CI gates, artifact-size policy, and known +completion blockers are maintained in [ACCEPTANCE.md](ACCEPTANCE.md). + ## WASI tool matrix WASI guest debugging uses Wasmtime's built-in gdbstub and LLDB's WebAssembly diff --git a/cmd/llgo/debugtest/embedded/README.md b/cmd/llgo/debugtest/embedded/README.md index 1987fc5a55..1fac26ad15 100644 --- a/cmd/llgo/debugtest/embedded/README.md +++ b/cmd/llgo/debugtest/embedded/README.md @@ -27,3 +27,7 @@ and connect to its GDB port: ```sh llgo debug -target=rp2040 -remote=:3333 . ``` + +The guarded, repeatable probe acceptance and its explicit flash confirmation +are documented in [`../hardware`](../hardware/README.md). Ordinary CI never +claims ownership of a physical probe. diff --git a/cmd/llgo/debugtest/hardware/README.md b/cmd/llgo/debugtest/hardware/README.md new file mode 100644 index 0000000000..163ae5cf32 --- /dev/null +++ b/cmd/llgo/debugtest/hardware/README.md @@ -0,0 +1,29 @@ +# Opt-in physical probe acceptance + +This test runs the shared embedded source-debug fixture on a real target. It is +never part of ordinary CI and refuses to start unless the operator explicitly +confirms that the selected device may be halted and flashed. + +The normal path uses the target's checked-in OpenOCD and GDB configuration: + +```sh +LLGO_HARDWARE_CONFIRM=flash \ +LLGO_HARDWARE_TARGET=rp2040 \ +LLGO_HARDWARE_GDB=arm-none-eabi-gdb \ +bash cmd/llgo/debugtest/hardware/runtest.sh +``` + +The test builds a host-side ELF with DWARF, starts the configured probe server, +loads the ELF, stops at the fixture's source breakpoint, checks parameters, +locals, aggregates, globals, and the backtrace, then detaches. Set +`LLGO_HARDWARE_SERVER` to override the target's server command. + +An already running GDB Remote server can be selected with +`LLGO_HARDWARE_REMOTE=host:port`. Remote and custom-server modes do not alter +target memory by default, so the exact generated ELF must already be present on +the device. Set `LLGO_HARDWARE_LOAD=1` only when that server is allowed to reset +and load the new ELF. + +Only connect one test process to a probe. The command intentionally has no +automatic retry: loss of probe ownership, power, reset, or transport should +remain visible to the operator. diff --git a/cmd/llgo/debugtest/hardware/runtest.sh b/cmd/llgo/debugtest/hardware/runtest.sh new file mode 100755 index 0000000000..47e28fd81a --- /dev/null +++ b/cmd/llgo/debugtest/hardware/runtest.sh @@ -0,0 +1,105 @@ +#!/bin/bash + +set -euo pipefail + +if [[ "${LLGO_HARDWARE_CONFIRM:-}" != "flash" ]]; then + echo "physical debugger test disabled; set LLGO_HARDWARE_CONFIRM=flash to allow halt/load" >&2 + exit 2 +fi + +target=${LLGO_HARDWARE_TARGET:?set LLGO_HARDWARE_TARGET to the checked-in target name} +gdb=${LLGO_HARDWARE_GDB:?set LLGO_HARDWARE_GDB to the target-aware GDB executable} +if ! command -v "$gdb" >/dev/null 2>&1; then + echo "GDB executable not found: $gdb" >&2 + exit 1 +fi +gdb=$(command -v "$gdb") + +script_dir=$(cd "$(dirname "$0")" && pwd) +repo_root=$(cd "$script_dir/../../../.." && pwd) +fixture_dir="$script_dir/../embedded" +source_file="$fixture_dir/C/c.go" +break_line=$(awk '/LLGO_EMBEDDED_DEBUG_BREAK/ { print NR; exit }' "$source_file") +if [[ -z "$break_line" ]]; then + echo "embedded hardware breakpoint marker not found" >&2 + exit 1 +fi + +tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/llgo-hardware-debug.XXXXXX") +cleanup() { + rm -rf "$tmp_dir" +} +trap cleanup EXIT + +llgo=${LLGO:-llgo} +if ! command -v "$llgo" >/dev/null 2>&1; then + echo "llgo executable not found: $llgo" >&2 + exit 1 +fi +llgo=$(command -v "$llgo") + +remote=${LLGO_HARDWARE_REMOTE:-} +server=${LLGO_HARDWARE_SERVER:-} +if [[ -n "$remote" && -n "$server" ]]; then + echo "LLGO_HARDWARE_REMOTE and LLGO_HARDWARE_SERVER are mutually exclusive" >&2 + exit 2 +fi + +debug_flags=( + -backend=gdb + -gdb "$gdb" + -target "$target" + -o "$tmp_dir/hardware-debug.elf" +) +if [[ -n "$remote" ]]; then + debug_flags+=(-remote "$remote") +elif [[ -n "$server" ]]; then + debug_flags+=(-server "$server") +fi + +gdb_commands=( + --nx --batch + -ex "set pagination off" + -ex "set confirm off" +) +if [[ ( -n "$remote" || -n "$server" ) && "${LLGO_HARDWARE_LOAD:-0}" == "1" ]]; then + gdb_commands+=( + -ex "monitor reset halt" + -ex "load" + -ex "monitor reset halt" + ) +fi +gdb_commands+=( + -ex "break $source_file:$break_line" + -ex "continue" + -ex 'printf "LLGO_HARDWARE_SEED=%d\n", seed' + -ex 'printf "LLGO_HARDWARE_PAIR=%d,%d\n", pair.Left, pair.Right' + -ex 'printf "LLGO_HARDWARE_VALUES=%d,%d,%d\n", values[0], values[1], values[2]' + -ex 'printf "LLGO_HARDWARE_RESULT=%d\n", result' + -ex 'printf "LLGO_HARDWARE_SINK=%d\n", DebugSink' + -ex "backtrace" + -ex "detach" +) + +if ! output=$(cd "$fixture_dir" && LLGO_ROOT="$repo_root" "$llgo" debug \ + "${debug_flags[@]}" . -- "${gdb_commands[@]}" 2>&1); then + printf '%s\n' "$output" >&2 + exit 1 +fi +printf '%s\n' "$output" + +for expected in \ + "LLGO_HARDWARE_SEED=7" \ + "LLGO_HARDWARE_PAIR=7,8" \ + "LLGO_HARDWARE_VALUES=9,10,11" \ + "LLGO_HARDWARE_RESULT=33" \ + "LLGO_HARDWARE_SINK=33" \ + "C/c.go:$break_line" +do + if [[ "$output" != *"$expected"* ]]; then + echo "missing expected hardware debugger output: $expected" >&2 + exit 1 + fi +done + +echo "LLGo physical probe source-debug acceptance passed for $target"