Skip to content

netdev: card.resource stubs list every scratch register the call may destroy (#70, A of 3) - #71

Merged
tinic merged 1 commit into
mainfrom
fix/lvo-clobber-pcmcia
Sep 26, 2026
Merged

tinic merged 1 commit into
mainfrom
fix/lvo-clobber-pcmcia

Conversation

@tinic

@tinic tinic commented Sep 26, 2026

Copy link
Copy Markdown
Owner

Part of #70, shipped-site fixes, PR A of 3: src/netdev/netdev_pcmcia.c → anxnet.device.

The five card.resource stubs passed their argument registers (a1, plus a0/d1 where used) as asm inputs only. Any LVO may destroy d0/d1/a0/a1, but GCC was told they survived the call.

REAL today (main 7108650, anxnet.device 7d62fbe4):

68a8 moveq #10,d1
68aa jsr -48(a6)        ; CardMiscControl
68ae movea.l d0,a6
68b0 move.l d1,-(sp)    ; stale d1 = IOMODE value of netdev_diag_note(0x29)

After (anxnet.device 9e8e9011):

68a8 moveq #10,d1
68aa jsr -48(a6)
68ae movea.l d0,a6
68b0 pea 0xa            ; the constant, not the register

The other four stubs (OwnCard -6, ReleaseCard -12, CardResetCard -66, CopyTuple -72) were latent. Before and after they compile to the same instructions, shifted by 2 bytes.

Change: each argument register is now a "+r" operand, and unused scratch registers stay clobbers. There is a comment at the stub header, a CHANGELOG entry, and no other code change.

Proof:

🤖 Generated with Claude Code

…destroy

The five card.resource stubs in netdev_pcmcia.c passed their argument
registers (a1, and a0/d1 where used) as inputs only. The library ABI lets
every LVO destroy d0/d1/a0/a1, but GCC was told those registers still held
their values after the jsr.

This was REAL in anxnet.device at main 7108650. pc_misc_control loads
d1 = 10 for CardMiscControl (jsr -48 at 0x68aa), and GCC reused d1 as the
value argument of netdev_diag_note(ANXDIAG_PC_IOMODE) at 0x68b0, so the
diagnostic IOMODE value was whatever card.resource left in d1. The other
four stubs were latent: today's code rewrites or never reads the register.

Each argument register is now a "+r" operand; unused scratch registers
remain clobbers. After the change, 0x68b0 is `pea 0xa` (the constant), and
the OwnCard / ReleaseCard / CardResetCard / CopyTuple sites compile to the
same instructions, only shifted by 2 bytes. The #70 scanner no longer flags
netdev_pcmcia.c. anxnet.device 7d62fbe4 -> 9e8e9011 (default preset).

Part of #70 (shipped sites, PR A of 3).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@tinic
tinic force-pushed the fix/lvo-clobber-pcmcia branch from 367c60d to 8c300b0 Compare September 26, 2026 02:30
@tinic
tinic merged commit 66e0bf5 into main Sep 26, 2026
4 of 5 checks passed
@tinic
tinic deleted the fix/lvo-clobber-pcmcia branch September 26, 2026 02:31
tinic added a commit that referenced this pull request Sep 26, 2026
…call may destroy (#73)

installnetprobe.c's three card.resource stubs (OwnCard, ReleaseCard,
CopyTuple) passed their argument registers as asm inputs only. The
library ABI lets every LVO destroy d0/d1/a0/a1.

This was REAL at main 66e0bf5. probe_own_card loads a1 = &handle
(0x968) for OwnCard (jsr -6 at 0x96e). On the owned path, the first
CopyTuple(0x20) (jsr -72 at 0x98e) was issued without reloading a1, so it
used whatever OwnCard left there. After the change, 0x986 is
`lea 0x18,a1` before the CopyTuple, which reloads the handle. The other
two stubs were latent.

Each argument register is now a "+r" operand; unused scratch registers
remain clobbers. The #70 scanner no longer flags installnetprobe.c. With
#71 and #72 this closes the 16 shipped sites in #70.

Part of #70 (shipped sites, PR C of 3).

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tinic added a commit that referenced this pull request Sep 26, 2026
…ers; CI gate (#70) (#86)

An LVO may destroy d0, d1, a0 and a1.  An extended asm statement that
lists one of them only as an input lets GCC reuse a value the call has
overwritten, which is how #68 passed a stale recvfrom address.

tools/check-lvo-clobbers.sh (python in check-lvo-clobbers.py) scans
src/ and tests/ for asm statements whose template calls through a6
and reports each one whose outputs and clobbers leave out a scratch
register.  It reads register declarations through object-like macros
(BSD_SCRATCH, NETDEV_REG_A1) from the file and its "..." includes, and
skips top-level asm.  Output is key=value, exit 1 on any site.  It is
wired into stage_host next to check-lvo-matrix.sh, registered in
check-gates-wired.sh, and added to the shellcheck list.

Validation:
  58c5c4f (the tree the issue scanned): 415 statements, 31 flagged.
  All 16 shipped sites match the issue's missing sets exactly, and
  tests/sockopt/sockopt_test.c:332 (pre-#68) is flagged missing=a1.
  src/tools/toolsock.c alone: 22 statements, 0 flagged.
  The issue's 77 included 46 false positives: 43 BSD_SCRATCH macro
  sites, apidrill's TH() macro, and the top-level asm in genet.c and
  netdev_cache.c.

The 14 real test sites on main are fixed: a register that is also an
input becomes an output (dummy "=r", or "+r" as in #71), one that is
not becomes a clobber.  udpdrill's recvfrom and getsockopt helpers had
the #68 shape (a1).

  before (7fa3df3): statements=415 files=50 flagged=14
  after:             statements=415 files=50 flagged=0

Fixes #70

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant