diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4088fe8..97fece42 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,11 +41,13 @@ jobs: - uses: actions/checkout@v4 # The keyframe and variants tests decode with ffmpeg, as the service does - # in its image. They fail rather than skip when CI is set and it is missing. - - name: Install ffmpeg + # in its image; the crash symbolizer's unwind with gdb-multiarch and + # unpack firmware with unsquashfs. They fail rather than skip when CI is + # set and one is missing. + - name: Install ffmpeg, gdb-multiarch and squashfs-tools run: | sudo apt-get update -qq - sudo apt-get install -y --no-install-recommends ffmpeg + sudo apt-get install -y --no-install-recommends ffmpeg gdb-multiarch squashfs-tools # --- the Go service (#287) --------------------------------------------- - uses: actions/setup-go@v5 diff --git a/CLAUDE.md b/CLAUDE.md index f328b0ec..b3f34e8f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -234,6 +234,21 @@ restores the image but never the schema, so keep migrations additive. merges: `bogus` takes back what a bug and the signatures merged into it paid, and undoing it lets the settlement pay again. A crash with a MAC is the camera's linked owner's, whoever sent it. + **majestic's own crashes** come the same way, as `/etc/crash/majestic.dump` + (class `user`, kind `signal`, migration 032), and are the maintainers' + only: never on `/crashes`, and a member is shown the signal, not where. + One arrives filed under a provisional signature (module and offset of the + faulting instruction) with its dump in `crash_dumps`; the firmware role's + `internal/crashes/symbolize` (woken by `NOTIFY crash_symbolize`) builds an + ELF core from it and unwinds it with `gdb-multiarch` against the + executable and debuginfo majestic's CI publishes by build-id + (`MAJESTIC_SYMBOLS_BASE`), naming library frames from the firmware build's + own rootfs (`unsquashfs` of its release tarball), and scanning the stack + for probable callers where gdb stops. Then the event is refiled under its + backtrace's signature (the crashes package's one change to an event) and + the dump -- majestic's memory -- is deleted. Retried for days when the + debuginfo is not there, then given up on. Cache under + `RELEASE_CACHE_ROOT/symbols`, swept after 30 days unused. - `internal/tools`, `internal/nfsro` — **ipctool for stock firmware**, which has no curl and no TLS. ipctool's release job pushes each build to `PUT /api/v1/tools/{name}` (OIDC, `internal/tools/PUSH.md`); nginx serves diff --git a/data/locales/boards.en.yml b/data/locales/boards.en.yml index 8ac9ea28..5ec6c8f5 100644 --- a/data/locales/boards.en.yml +++ b/data/locales/boards.en.yml @@ -49,7 +49,7 @@ en: club_crashes: eyebrow: 'OpenIPC Club' title: 'Crash triage' - lede: 'Kernel crashes cameras sent, worst first, with what it takes to reproduce each one.' + lede: 'Crashes cameras sent, the kernel''s and majestic''s, worst first, with what it takes to reproduce each one.' crashes: eyebrow: 'OpenIPC firmware' title: 'Kernel crashes from the field' @@ -632,6 +632,7 @@ en: kind_oops: 'Oops' kind_bug: 'BUG' kind_warning: 'Warning' + kind_signal: 'majestic crashed' in_irq: 'in an interrupt' status_open: 'Not yet looked at' status_confirmed: 'Confirmed' @@ -691,3 +692,7 @@ en: d_cmdline: 'Command line' d_anomalies: 'Before it' d_before: 'Warnings' + d_backtrace: 'Backtrace' + sym_pending: 'Waiting for its backtrace (tried {n} times)' + sym_failed: 'No backtrace' + probable: 'probable' diff --git a/data/locales/boards.ru.yml b/data/locales/boards.ru.yml index e9d6fc33..c1b4fc0a 100644 --- a/data/locales/boards.ru.yml +++ b/data/locales/boards.ru.yml @@ -49,7 +49,7 @@ ru: club_crashes: eyebrow: 'Клуб OpenIPC' title: 'Разбор падений' - lede: 'Падения ядра, присланные камерами, — сначала самые тяжёлые, с тем, что нужно, чтобы воспроизвести каждое.' + lede: 'Падения ядра и majestic, присланные камерами, — сначала самые тяжёлые, с тем, что нужно, чтобы воспроизвести каждое.' crashes: eyebrow: 'Прошивка OpenIPC' title: 'Падения ядра с реальных камер' @@ -673,6 +673,7 @@ ru: kind_oops: 'Oops' kind_bug: 'BUG' kind_warning: 'Предупреждение' + kind_signal: 'Сбой majestic' in_irq: 'в прерывании' status_open: 'Ещё не разобрано' status_confirmed: 'Подтверждено' @@ -736,3 +737,7 @@ ru: d_cmdline: 'Командная строка' d_anomalies: 'Перед этим' d_before: 'Предупреждения' + d_backtrace: 'Стек вызовов' + sym_pending: 'Ждёт стека вызовов (попыток: {n})' + sym_failed: 'Стека вызовов нет' + probable: 'вероятно' diff --git a/frontend/apps/site/src/components/club/CrashTriage.tsx b/frontend/apps/site/src/components/club/CrashTriage.tsx index aa444a75..6eb9ed3e 100644 --- a/frontend/apps/site/src/components/club/CrashTriage.tsx +++ b/frontend/apps/site/src/components/club/CrashTriage.tsx @@ -1,8 +1,9 @@ /** - * /club/crashes: the maintainers' triage of kernel crashes. Every signature, - * worst first; one opens to what it takes to reproduce it -- the chips, - * sensors and builds it was seen on, the firmware builds whose time matches - * its kernel, and each crash's redacted log, warnings and lead-up. + * /club/crashes: the maintainers' triage of kernel crashes and majestic's. + * Every signature, worst first; one opens to what it takes to reproduce it -- + * the chips, sensors and builds it was seen on, the firmware builds whose time + * matches its kernel, and each crash's redacted log, warnings and lead-up, or + * for majestic's, its backtrace from the debuginfo of its build. * * Confirming a bug pays its first reporter, fixing it pays them again; * bogus takes back every star it paid. The same is `openipc crashes status`. @@ -12,7 +13,8 @@ import { useBoardsTranslations, type BoardsT } from '../../lib/boards-i18n'; import { pathFor, type Locale } from '../../lib/i18n'; import { ClubError, fetchMe } from '../../lib/club'; import { - decideCrash, fetchCrashDetail, fetchTriage, frame, KIND_TONE, STATUS_TONE, type Combo, type Detail, type Signature, type Status, + decideCrash, fetchCrashDetail, fetchTriage, frame, frameAt, KIND_TONE, STATUS_TONE, type Combo, type Detail, type Frame, type Signature, + type Status, } from '../../lib/crashes'; type Load = { state: 'loading' } | { state: 'ok'; list: Signature[] } | { state: 'forbidden' } | { state: 'error' }; @@ -81,9 +83,7 @@ function Open({ g, t, onDone }: { g: Signature; t: BoardsT; onDone: () => void } const field = 'min-w-0 rounded-md border border-hairline px-2 py-1 text-sm'; return (
-
-        {g.frames.slice(0, 10).map((f, i) => `${i === 0 ? '' : '← '}${frame(f)}`).join('\n')}
-      
+