Z80Golf , 53 36 34 byte
-16 byte grazie a @Lynn
-2 byte grazie a @Neil
Dato che questo è solo il codice macchina Z80, ci sono molti non stampabili in questo, quindi ha un xxd -r
hexdump reversibile:
00000000: ddb6 2120 10dd b615 280c 003e 62ff 3e65 ..! ....(..>b.>e
00000010: ffff 3e70 ff76 003e 62ff 3e65 ffff 3e70 ..>p.v.>b.>e..>p
00000020: ff76 .v
Provalo online! (tester esaustivo in Python)
Spiegazione
z80golf è l'ipotetica macchina Z80 di Anarchy Golf, dove call $8000
è un putchar, call $8003
è un getchar, halt
fa uscire l'interprete, il programma è posto in $0000
, e tutta la memoria è piena di zero. Rendere i programmi a prova di radiazione nell'assemblaggio è piuttosto difficile, ma una tecnica genericamente utile sta usando istruzioni idempotenti a un byte. Per esempio,
or c ; b1 ; a = a | c
è solo un byte e a | c | c == a | c
, quindi, può essere reso a prova di radiazione semplicemente ripetendo le istruzioni. Sulla Z80, un carico immediato a 8 bit è di due byte (dove l'immediato si trova nel secondo byte), quindi è possibile caricare in modo affidabile anche alcuni valori nei registri. Questo è quello che ho fatto all'inizio del programma, quindi puoi analizzare le varianti più lunghe che ho archiviato in fondo alla risposta, ma poi ho capito che esiste un modo più semplice.
Il programma consiste in due carichi utili indipendenti, in cui uno di essi potrebbe essere stato danneggiato dalle radiazioni. Controllo se un byte è stato rimosso e se il byte rimosso era prima della seconda copia del payload, controllando i valori di alcuni indirizzi di memoria assoluti.
Innanzitutto, dobbiamo uscire se non si osservano radiazioni:
or a, (ix+endbyte) ; dd b6 21 ; a |= memory[ix+0x0021]
jr nz, midbyte ; 20 10 ; jump to a halt instruction if not zero
Se un byte è stato rimosso, tutti i byte si sposteranno e $0020
conterranno l'ultimo 76
, quindi $0021
sarà uno zero. Possiamo permetterci di irradiare l'inizio del programma, anche se praticamente non c'è ridondanza:
- Se l'offset del salto
$10
viene rimosso, la radiazione verrà rilevata correttamente, il salto non verrà eseguito e l'offset non avrà importanza. Il primo byte dell'istruzione successiva verrà consumato, ma poiché è progettato per resistere alla rimozione dei byte, questo non ha importanza.
- Se il codice operativo del salto
$20
viene rimosso, l'offset del salto $10
verrà decodificato come djnz $ffe4
(consumando il byte di istruzione successivo come offset - vedere sopra), che è un'istruzione di loop - decrementa B, e salta se il risultato non è zero. Poiché ffe4-ffff
è pieno di zero ( nop
s) e il contatore del programma si avvolge, questo eseguirà l'inizio del programma 256 volte, e infine continuerà. Sono stupito che funzioni.
- La rimozione
$dd
di decodificherà il resto del frammento come or (hl) / ld ($1020), hl
, quindi scorrerà nella parte successiva del programma. Questo or
non cambierà alcun registro importante e poiché HL è zero a questo punto, anche la scrittura verrà annullata.
- Rimuovendo
$b6
, il resto verrà decodificato come ld ($1020), ix
e procederà come sopra.
- La rimozione di
$21
farà mangiare il decodificatore $20
, innescando il djnz
comportamento.
Si noti che l'utilizzo or a, (ix+*)
consente di risparmiare due byte ld a, (**) / and a / and a
grazie al controllo integrato per zero.
Ora dobbiamo decidere quale delle due copie del payload eseguire:
or (ix+midbyte) ; dd b6 15
jr z, otherimpl ; 28 0c
nop ; 00
; first payload
ld a, 'b' ; 3e 62
rst $0038 ; ff
ld a, 'e' ; 3e 65
rst $0038 ; ff
rst $0038 ; ff
ld a, 'p' ; 3e 70
rst $0038 ; ff
midbyte:
halt ; 76
otherimpl:
nop ; 00
ld a, 'b' ; 3e 62
; ... ; ...
rst $0038 ; ff
endbyte:
halt ; 76
Le due copie sono separate da un nop, poiché un salto relativo viene utilizzato per scegliere tra di esse e la radiazione potrebbe aver spostato il programma in modo tale da far saltare il primo byte dopo la destinazione. Inoltre, il nop è codificato come zero, il che semplifica il rilevamento di byte spostati. Si noti che non importa quale payload viene scelto se lo switch stesso è danneggiato, perché entrambe le copie sono sicure. Assicuriamoci che non salti nella memoria non inizializzata, però:
- L'eliminazione
$dd
farà decodificare i prossimi due byte come or (hl) / dec d
. Clobbers D. Nessun grosso problema.
- L'eliminazione
$b6
creerà una codifica più lunga non documentata per dec d
. Come sopra.
- L'eliminazione
$15
leggerà $28
invece come offset e l'esecuzione procederà in $0c
, come di seguito.
- Quando
$28
scompare, $0c
viene decodificato come inc c
. Al payload non interessa c
.
- Eliminazione
$0c
: ecco a cosa serve il nop. Altrimenti, il primo byte del payload sarebbe stato letto come offset di salto e il programma sarebbe saltato nella memoria non inizializzata.
Il payload stesso è piuttosto semplice. Penso che le dimensioni ridotte della stringa rendano questo approccio più piccolo di un loop, ed è più facile rendere indipendente dalla posizione in questo modo. L' e
a beep
ripete, così posso radere uno ld a
. Inoltre, poiché tutta la memoria tra $0038
ed $8000
è azzerato, posso cadere attraverso di essa e utilizzare una minore rst
variante della call
istruzione, che funziona solo per $0
, $8
, $10
e così via, fino a $38
.
Approcci precedenti
64 byte
00000000: 2e3f 3f2e 3f3f 7e7e a7a7 201f 1e2b 2b1e .??.??~~.. ..++.
00000010: 2b2b 6b00 7ea7 2814 003e 62cd 0080 3e65 ++k.~.(..>b...>e
00000020: cd00 80cd 0080 3e70 cd00 8076 003e 62cd ......>p...v.>b.
00000030: 0080 3e65 cd00 80cd 0080 3e70 cd00 8076 ..>e......>p...v
58 byte
00000000: 2e39 392e 3939 7e7e a7a7 2019 3a25 00a7 .99.99~~.. .:%..
00000010: 2814 003e 62cd 0080 3e65 cd00 80cd 0080 (..>b...>e......
00000020: 3e70 cd00 8076 003e 62cd 0080 3e65 cd00 >p...v.>b...>e..
00000030: 80cd 0080 3e70 cd00 8076 ....>p...v
53 byte
Questo ha una spiegazione nella cronologia delle modifiche, ma non è troppo diverso.
00000000: 3a34 00a7 a720 193a 2000 a728 1400 3e62 :4... .: ..(..>b
00000010: cd00 803e 65cd 0080 cd00 803e 70cd 0080 ...>e......>p...
00000020: 7600 3e62 cd00 803e 65cd 0080 cd00 803e v.>b...>e......>
00000030: 70cd 0080 76 p...v
Cosa succede se: qualsiasi output non vuoto andava bene invece del segnale acustico
1 byte
v
halt
s normalmente il programma, ma se la radiazione lo rimuove, allora la memoria sarà piena di zero, facendo $8000
eseguire un numero infinito di volte, stampando molti byte nulli.