Skip to content
Open
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
10 changes: 10 additions & 0 deletions .github/workflows/targets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
83 changes: 83 additions & 0 deletions cmd/llgo/debugtest/ACCEPTANCE.md
Original file line number Diff line number Diff line change
@@ -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=<debug|deployment|debug+deployment> format=<format> size=<bytes> path=<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.
3 changes: 3 additions & 0 deletions cmd/llgo/debugtest/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions cmd/llgo/debugtest/embedded/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
29 changes: 29 additions & 0 deletions cmd/llgo/debugtest/hardware/README.md
Original file line number Diff line number Diff line change
@@ -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.
105 changes: 105 additions & 0 deletions cmd/llgo/debugtest/hardware/runtest.sh
Original file line number Diff line number Diff line change
@@ -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"
Loading