La corruzione della memoria era un problema comune nei programmi di grandi dimensioni scritti in linguaggio assembly?

Jan 21 2021

I bug di danneggiamento della memoria sono sempre stati un problema comune nei grandi programmi e progetti C. Allora era un problema in 4.3BSD, ed è ancora un problema oggi. Non importa con quanta attenzione il programma sia scritto, se è sufficientemente grande, è spesso possibile scoprire un altro bug di lettura o scrittura fuori limite nel codice.

Ma c'è stato un tempo in cui i programmi di grandi dimensioni, inclusi i sistemi operativi, venivano scritti in assembly, non in C. I bug di danneggiamento della memoria erano un problema comune nei programmi in assembly di grandi dimensioni? E come si è confrontato con i programmi C?

Risposte

53 Jean-FrançoisFabre Jan 21 2021 at 17:23

La codifica in assembly è brutale.

Puntatori canaglia

I linguaggi di assembly si basano ancora di più sui puntatori (tramite i registri degli indirizzi), quindi non puoi nemmeno fare affidamento sul compilatore o sugli strumenti di analisi statica per avvisarti di tali corruzioni della memoria / sovraccarichi del buffer rispetto a C.

Ad esempio in C, un buon compilatore potrebbe emettere un avviso lì:

 char x[10];
 x[20] = 'c';

È limitato. Non appena l'array decade in un puntatore, tali controlli non possono essere eseguiti, ma questo è un inizio.

Nell'assemblaggio, senza un runtime appropriato o strumenti binari di esecuzione formale non è possibile rilevare tali errori.

Registri canaglia (principalmente indirizzi)

Un altro fattore aggravante per l'assemblaggio è che la conservazione del registro e la convenzione di chiamata di routine non sono standard / garantiti.

Se una routine viene chiamata e non salva un particolare registro per errore, allora ritorna al chiamante con un registro modificato (accanto ai registri "scratch" che sono noti per essere cestinati all'uscita), e il chiamante non si aspetta it, che porta a leggere / scrivere all'indirizzo errato. Ad esempio nel codice 68k:

    move.b  d0,(a3)+
    bsr  a_routine
    move.b  d0,(a3)+   ; memory corruption, a3 has changed unexpectedly
    ...

a_routine:
    movem.l a0-a2,-(a7)
    ; do stuff
    lea some_table(pc),a3    ; change a3 if some condition is met
    movem.l (a7)+,a0-a2   ; the routine forgot to save a3 !
    rts

Usare una routine scritta da qualcun altro che non usa le stesse convenzioni di salvataggio dei registri può portare allo stesso problema. Di solito salvo tutti i registri prima di utilizzare la routine di qualcun altro.

D'altra parte, un compilatore utilizza lo stack o il passaggio di parametri del registro standard, gestisce le variabili locali usando lo stack / altro dispositivo, conserva i registri se necessario, ed è tutto coerente nell'intero programma, garantito dal compilatore (a meno che non ci siano bug, di corso)

Modalità di indirizzamento canaglia

Ho corretto molte violazioni della memoria negli antichi giochi Amiga. Eseguirli in un ambiente virtuale con MMU attivata a volte innesca errori di lettura / scrittura in indirizzi falsi completi. Il più delle volte quelle letture / scritture non hanno effetto perché le letture restituiscono 0 e le scritture vanno nel bosco, ma a seconda della configurazione della memoria può avere conseguenze spiacevoli.

Si sono verificati anche casi di risoluzione degli errori. Ho visto cose come:

 move.l $40000,a0

invece che immediato

 move.l #$40000,a0

in tal caso, il registro degli indirizzi contiene ciò che è contenuto $40000(probabilmente nel cestino) e non l' $40000indirizzo. Ciò porta in alcuni casi a un danneggiamento catastrofico della memoria. Il gioco di solito finisce per eseguire l'azione che non ha funzionato da qualche altra parte senza risolvere questo problema, quindi il gioco funziona correttamente la maggior parte del tempo. Ma ci sono momenti in cui i giochi dovevano essere riparati correttamente per ripristinare il comportamento corretto.

In C, fuorviare un valore per un puntatore porta a un avviso.

(Abbiamo rinunciato a un gioco come "Wicked" che aveva sempre più corruzione grafica quanto più avanzavi nei livelli, ma anche a seconda del modo in cui superavi i livelli e del loro ordine ...)

Dimensioni dei dati non corrette

Nell'assemblaggio non ci sono tipi. Significa che se lo faccio

move.w #$4000,d0           ; copy only 16 bits
move.l #1,(a0,d0.l)    ; indexed write on d1, long

il d0registro riceve solo la metà dei dati modificati. Può essere quello che volevo, forse no. Quindi se d0contiene zero sui 32-16 bit più significativi, il codice fa quello che ci si aspetta, altrimenti aggiunge a0e d0(gamma completa) e la scrittura risultante è "nel bosco". Una soluzione è:

move.l #1,(a0,d0.w)    ; indexed write on d1, long

Ma poi se d0> $7FFFfa anche qualcosa di sbagliato, perché d0è considerato negativo allora (non il caso di d0.l). Quindi d0necessita di estensione o mascheramento del segno ...

Questi errori di dimensione possono essere visti su un codice C, ad esempio quando si assegna a una shortvariabile (che tronca il risultato) ma anche in questo caso si ottiene un risultato sbagliato la maggior parte delle volte, non problemi fatali come sopra (ovvero: se non si non mentire al compilatore forzando cast di tipi sbagliati)

Gli assemblatori non hanno tipi, ma i buoni assemblatori consentono di utilizzare strutture ( STRUCTparole chiave) che consentono di elevare un po 'il codice calcolando automaticamente gli offset della struttura. Ma una lettura di dimensioni errate può essere catastrofica, indipendentemente dal fatto che tu stia utilizzando o meno struct / offset definiti

move.w  the_offset(a0),d0

invece di

move.l  the_offset(a0),d0

non è selezionato e ti fornisce i dati sbagliati in d0. Assicurati di bere abbastanza caffè durante la codifica, o scrivi semplicemente la documentazione ...

Allineamento dei dati canaglia

L'assemblatore di solito avvisa sul codice non allineato, ma non sui puntatori non allineati (perché i puntatori non hanno tipo), che possono innescare errori di bus.

I linguaggi di alto livello usano i tipi ed evitano la maggior parte di questi errori eseguendo l'allineamento / riempimento (a meno che, ancora una volta, non ci abbia mentito).

È comunque possibile scrivere programmi assembly con successo. Utilizzando una metodologia rigorosa per il passaggio dei parametri / il salvataggio dei registri e cercando di coprire il 100% del codice tramite test e un debugger (simbolico o meno, questo è ancora il codice che hai scritto). Questo non rimuoverà tutti i potenziali bug, specialmente quelli causati da dati di input errati, ma aiuterà.

24 jackbochsler Jan 22 2021 at 05:41

Ho trascorso la maggior parte della mia carriera scrivendo assemblatore, solista, piccoli team e grandi team (Cray, SGI, Sun, Oracle). Ho lavorato su sistemi embedded, OS, VM e bootstrap loader. La corruzione della memoria era raramente, se non mai, un problema. Abbiamo assunto persone intelligenti e quelle che hanno fallito sono state gestite in diversi lavori più appropriati alle loro capacità.

Abbiamo anche testato in modo fanatico, sia a livello di unità che a livello di sistema. Avevamo test automatizzati che venivano eseguiti costantemente sia sui simulatori che sull'hardware reale.

Verso la fine della mia carriera ho fatto un colloquio con un'azienda e ho chiesto come facevano i test automatizzati. La loro risposta di "Cosa?!?" era tutto quello che avevo bisogno di sentire, ho concluso l'intervista.

19 RETRAC Jan 21 2021 at 23:10

Semplici errori idioti abbondano nell'assemblaggio, non importa quanto tu sia attento. Si scopre che anche gli stupidi compilatori per linguaggi di alto livello mal definiti (come il C) vincolano una vasta gamma di possibili errori come semanticamente o sintatticamente non validi. Un errore con una singola sequenza di tasti in più o dimenticata è molto più probabile che si rifiuti di compilare piuttosto che di assemblare. I costrutti che puoi esprimere validamente in assembly che semplicemente non hanno alcun senso perché stai sbagliando tutto hanno meno probabilità di tradursi in qualcosa che è accettato come valido C.E dal momento che stai operando a un livello più alto, stai più propenso a strizzare gli occhi e dire "eh?" e riscrivi il mostro che hai appena scritto.

Quindi lo sviluppo e il debugging dell'assembly è, in effetti, dolorosamente spietato. Ma la maggior parte di questi errori si rompono le cose difficili , e sarebbe presentarsi in fase di sviluppo e il debugging. Azzarderei l'ipotesi plausibile che, se gli sviluppatori stanno seguendo la stessa architettura di base e le stesse buone pratiche di sviluppo, il prodotto finale dovrebbe essere altrettanto robusto. Il tipo di errori che un compilatore rileva può essere individuato con buone pratiche di sviluppo e il tipo di errori che i compilatori non rileva può o non può essere individuato con tali pratiche. Tuttavia, ci vorrà molto più tempo per arrivare allo stesso livello.

14 WalterMitty Jan 23 2021 at 02:48

Ho scritto il garbage collector originale per MDL, un linguaggio simile a Lisp, nel 1971-72. Allora era una vera sfida per me. È stato scritto in MIDAS, un assemblatore per il PDP-10 che esegue ITS.

Evitare il danneggiamento della memoria era il nome del gioco in quel progetto. L'intero team aveva paura che una demo di successo si interrompesse e si bruciasse quando veniva richiamato il garbage collector. E non avevo un buon piano di debug per quel codice. Ho fatto più controlli da scrivania di quanti ne abbia mai fatti prima o dopo. Cose come assicurarsi che non ci siano errori di recinzione. Assicurandosi che quando un gruppo di vettori veniva spostato, il bersaglio non conteneva oggetti non spazzatura. Ancora e ancora, testando le mie supposizioni.

Non ho mai trovato alcun bug in quel codice, ad eccezione di quelli rilevati dal controllo del desk. Dopo che siamo andati in diretta, nessuno è mai emerso durante il mio orologio.

Semplicemente non sono intelligente come cinquant'anni fa. Non ho potuto fare niente del genere oggi. E i sistemi odierni sono migliaia di volte più grandi di MDL.

7 Raffzahn Jan 22 2021 at 00:00

I bug di corruzione della memoria sono sempre stati un problema comune nei programmi C di grandi dimensioni [...] Ma c'è stato un tempo in cui i programmi di grandi dimensioni, inclusi i sistemi operativi, venivano scritti in assembly, non in C.

Sai che ci sono altre lingue che erano abbastanza comuni già all'inizio? Come COBOL, FORTRAN o PL / 1?

I bug di danneggiamento della memoria erano un problema comune nei programmi assembly di grandi dimensioni?

Questo dipende ovviamente da più fattori, come

  • l'Assembler utilizzato, poiché diversi programmi assembler offrono diversi livelli di supporto alla programmazione.
  • struttura del programma, poiché i programmi particolarmente grandi aderiscono alla struttura controllabile
  • modularizzazione e interfacce chiare
  • il tipo di programma scritto, poiché non tutte le attività richiedono manipolazioni del puntatore
  • stile di migliore pratica

Un buon assemblatore non solo si assicura che i dati siano allineati, ma offre anche strumenti per gestire tipi di dati complessi, strutture e simili in modo astratto, riducendo la necessità di calcolare "manualmente" i puntatori.

Un assemblatore utilizzato per qualsiasi progetto serio è come sempre un macro assemblatore (* 1), quindi in grado di racchiudere operazioni primitive in istruzioni macro di livello superiore, consentendo una programmazione più incentrata sull'applicazione evitando molte insidie ​​della gestione del puntatore (* 2).

Anche i tipi di programma sono piuttosto influenti. Le applicazioni di solito sono costituite da vari moduli, molti dei quali possono essere scritti quasi o completi senza (o solo controllati) l'uso del puntatore. Ancora una volta, l'utilizzo degli strumenti forniti dall'assemblatore è la chiave per un codice meno difettoso.

La prossima sarebbe la migliore pratica, che va di pari passo con molte delle precedenti. Semplicemente non scrivere programmi / moduli che richiedono più registri di base, che consegnano grandi blocchi di memoria invece di strutture di richiesta dedicate e così via ...

Ma le migliori pratiche iniziano già presto e con cose apparentemente semplici. Basta prendere l'esempio di una CPU primitiva (mi dispiace) come il 6502 con forse un insieme di tabelle, tutte regolate ai bordi della pagina per le prestazioni. Quando si carica l'indirizzo di una di queste tabelle in un puntatore di pagina zero per l'accesso indicizzato, l'utilizzo degli strumenti che l'assemblatore vorrebbe

     LDA   #<Table
     STA   Pointer

Alcuni programmi che ho visto piuttosto andare

     LDA   #0
     STA   Pointer

(o peggio, se su un 65C02)

     STZ   Pointer

La solita argomentazione è "Ma è comunque allineata". È? Può essere garantito per tutte le future iterazioni? Che ne dici di un giorno in cui lo spazio degli indirizzi si restringe e devono essere spostati in indirizzi non allineati? Ci si aspetta un sacco di grandi errori (ovvero difficili da trovare).

Quindi le migliori pratiche ci riportano di nuovo all'uso dell'Assembler e di tutti gli strumenti che offre.

Non provare a giocare all'Assemblatore invece che all'Assemblatore : lascia che faccia il suo lavoro per te.

E poi c'è il runtime, qualcosa che si applica a tutte le lingue ma spesso viene dimenticato. Oltre a cose come il controllo dello stack o il controllo dei limiti sui parametri, uno dei modi più efficaci per rilevare gli errori del puntatore è semplicemente bloccare la prima e l'ultima pagina di memoria contro la scrittura e la lettura (* 3). Non rileva solo il tanto amato errore del puntatore nullo, ma anche tutti i numeri bassi positivi o negativi che sono spesso il risultato di una precedente indicizzazione sbagliata. Certo, Runtime è sempre l'ultima risorsa, ma questa è facile.

Soprattutto, forse la ragione più rilevante è

  • l'ISA della macchina

nel ridurre le possibilità di danneggiamento della memoria riducendo la necessità di gestire i puntatori.

Alcune strutture della CPU richiedono semplicemente meno operazioni (dirette) del puntatore rispetto ad altre. Esiste un enorme divario tra le architetture che includono operazioni da memoria a memoria rispetto a quelle che non lo fanno, come le architetture di carico / archivio basate su accumulatori. Richiedono intrinsecamente la gestione del puntatore per qualsiasi cosa più grande di un singolo elemento (byte / parola).

Ad esempio per trasferire un campo, diciamo il nome di un cliente da circa in memoria, a / 360 utilizza una singola operazione MVC con indirizzi e lunghezza di trasferimento generati dall'assemblatore dalla definizione dei dati, mentre un'architettura load / store, progettata per gestire ogni byte separato, deve impostare puntatori e lunghezza nei registri e eseguire il ciclo attorno a un singolo elemento in movimento.

Poiché tali operazioni sono abbastanza comuni, anche il potenziale di errore risultante è comune. Oppure, in modo più generalizzato, si può dire che:

I programmi per i processori CISC sono generalmente meno soggetti a errori rispetto a quelli scritti per le macchine RISC.

Ovviamente e come al solito, tutto può essere rovinato da una cattiva programmazione.

E come si è confrontato con i programmi C?

Più o meno lo stesso - o meglio, C è l'equivalente HLL della CPU ISA più primitiva, quindi qualsiasi cosa che offra istruzioni di livello superiore andrà meglio.

C è intrinsecamente un linguaggio RISCy. Le operazioni fornite sono ridotte al minimo, il che va con una minima capacità di controllo contro operazioni involontarie. L'uso di puntatori non controllati non è solo standard, ma è necessario per molte operazioni, aprendo molte possibilità di danneggiamento della memoria.

Al contrario, un HLL come ADA, qui è quasi impossibile creare il caos del puntatore, a meno che non sia inteso ed esplicitamente dichiarato come opzione. Una buona parte di ciò è (come con l'ISA prima) dovuto a tipi di dati più elevati e alla loro gestione in modo sicuro.


Per la parte dell'esperienza, ho svolto la maggior parte della mia vita professionale (> 30 anni) in progetti di assemblaggio, con 80% Mainframe (/ 370) 20% Micros (principalmente 8080 / x86) - più privato molto di più :) fino a 2+ milioni di LOC (solo istruzioni) mentre i micro progetti hanno mantenuto circa 10-20k LOC.


* 1 - No, qualcosa che offre via la sostituzione di passaggi di testo con testo preconfezionato è nella migliore delle ipotesi un preprocessore testuale, ma non un assemblatore di macro. Un macro assemblatore è un meta strumento per creare il linguaggio necessario per un progetto. Offre strumenti per attingere alle informazioni che l'assemblatore raccoglie sulla sorgente (dimensione del campo, tipo di campo e molti altri) così come strutture di controllo per formulare la gestione, utilizzate per generare codice appropriato.

* 2 - È facile lamentarsi che il C non fosse adatto a nessuna seria capacità di macro, non solo avrebbe rimosso la necessità di molti costrutti oscuri, ma avrebbe anche consentito molti progressi estendendo il linguaggio senza la necessità di scriverne uno nuovo.

* 3 - Personalmente preferisco rendere la pagina 0 protetta solo da scrittura e riempire i primi256 byte con zero binario. In questo modo tutte le scritture del puntatore nullo (o basso) risultano comunque in un errore della macchina, ma la lettura da un puntatore nullo restituisce, a seconda del tipo, un byte / mezza parola / parola / doublewort contenente zero - beh, o una stringa nulla :) Lo so, è pigro, ma rende la vita molto di più se è facile non collaborare con il codice di altre persone. Anche la pagina rimanente può essere utilizzata per utili valori costanti come puntatori a varie fonti globali, stringhe ID, contenuto di campi costanti e tabelle di traduzione.

6 waltinator Jan 22 2021 at 09:17

Ho scritto mod OS in assembly su CDC G-21, Univac 1108, DECSystem-10, DECSystem-20, tutti i sistemi a 36 bit, più 2 assemblatori IBM 1401.

Il "danneggiamento della memoria" esisteva, principalmente come voce di un elenco "Cose da non fare".

Su un Univac 1108 ho trovato un errore hardware in cui il primo recupero di mezza parola (l'indirizzo del gestore di interrupt) dopo un interrupt hardware restituiva tutti gli 1, invece del contenuto dell'indirizzo. Tra le erbacce, con interruzioni disabilitate, nessuna protezione della memoria. Va in tondo, dove si ferma nessuno lo sa.

5 Peter-ReinstateMonica Jan 22 2021 at 19:31

Stai confrontando mele e pere. I linguaggi di alto livello furono inventati perché i programmi raggiungevano una dimensione ingestibile con l'assembler. Esempio: "La V1 aveva 4.501 righe di codice assembly per kernel, inizializzazione e shell. Di queste, 3.976 rappresentano il kernel e 374 per la shell." (Da questa risposta .)

Il. V1. Conchiglia. Aveva. 347. Linee. Di. Codice.

La bash di oggi ha forse 100.000 righe di codice (un wc sul repository produce 170k), senza contare le librerie centrali come readline e localization. I linguaggi di alto livello sono utilizzati in parte per la portabilità ma anche perché è praticamente impossibile scrivere programmi delle dimensioni odierne in assembler. Non è solo più soggetto a errori, è quasi impossibile.

4 supercat Jan 22 2021 at 03:45

Non penso che la corruzione della memoria sia generalmente più un problema nel linguaggio assembly che in qualsiasi altro linguaggio che utilizza operazioni di inscrittura di array non controllate, quando si confrontano programmi che eseguono attività simili. Sebbene scrivere un codice assembly corretto possa richiedere attenzione ai dettagli oltre a quelli che sarebbero rilevanti in un linguaggio come C, alcuni aspetti del linguaggio assembly sono effettivamente più sicuri di C.Nel linguaggio assembly, se il codice esegue una sequenza di caricamenti e produrre istruzioni di carico e immagazzinamento nell'ordine dato senza chiedersi se siano tutte necessarie. In C, al contrario, se un compilatore intelligente come clang viene invocato con qualsiasi impostazione di ottimizzazione diversa da -O0e data qualcosa come:

extern char x[],y[];
int test(int index)
{
    y[0] = 1;
    if (x+2 == y+index)
        y[index] = 2;
    return y[0];
}

può determinare che il valore di y[0]quando l' returnistruzione viene eseguita sarà sempre 1, e quindi non è necessario ricaricare il suo valore dopo la scrittura y[index], anche se l'unica circostanza definita in cui potrebbe verificarsi la scrittura nell'indice sarebbe se x[]è di due byte, si y[]verifica per seguirlo immediatamente, ed indexè zero, il che implica che y[0]verrebbe effettivamente lasciato con il numero 2.

3 phyrfox Jan 23 2021 at 23:33

Assembler richiede una conoscenza più approfondita dell'hardware che stai utilizzando rispetto ad altri linguaggi come C o Java. La verità è, tuttavia, l'assemblatore è stato utilizzato in quasi tutto, dalle prime auto computerizzate, ai primi sistemi di videogiochi fino agli anni '90, fino ai dispositivi Internet of Things che usiamo oggi.

Sebbene C offrisse la sicurezza dei tipi, ancora non offriva altre misure di sicurezza come il controllo del puntatore vuoto o gli array limitati (almeno, non senza codice extra). Era abbastanza facile scrivere un programma che andasse in crash e masterizzasse così come qualsiasi programma assembler.

Decine di migliaia di videogiochi sono stati scritti in assembler, compositi per scrivere demo piccole ma impressionanti in solo pochi kilobyte di codice / dati da decenni, migliaia di auto usano ancora una qualche forma di assemblatore oggi, così come alcuni meno conosciuti sistemi operativi (ad esempio MenuetOS ). Potresti avere dozzine o addirittura centinaia di cose in casa che sono state programmate in assembler di cui non sei nemmeno a conoscenza.

Il problema principale con la programmazione in assembly è che è necessario pianificare in modo più vigoroso di quanto non si faccia in un linguaggio come C.È perfettamente possibile scrivere un programma con anche 100k righe di codice in assembler senza un singolo bug, ed è anche possibile scrivere un programma con 20 righe di codice che ha 5 bug.

Non è lo strumento il problema, è il programmatore. Direi che la corruzione della memoria era un problema comune nella programmazione iniziale in generale. Questo non era limitato all'assembler, ma anche C (che era noto per la perdita di memoria e l'accesso a intervalli di memoria non validi), C ++ e altri linguaggi in cui era possibile accedere direttamente alla memoria, anche BASIC (che aveva la capacità di leggere / scrivere specifiche I / O porte sulla CPU).

Anche con linguaggi moderni che hanno protezioni di sicurezza, vedremo errori di programmazione che si bloccano nei giochi. Perché? Perché non c'è abbastanza cura nella progettazione dell'applicazione. La gestione della memoria non è scomparsa, è stata nascosta in un angolo dove è più difficile da visualizzare, causando ogni tipo di caos casuale nel codice moderno.

Praticamente ogni lingua è suscettibile a vari tipi di danneggiamento della memoria se utilizzata in modo errato. Oggi, il problema più comune sono le perdite di memoria, che sono più facili che mai da introdurre accidentalmente a causa di chiusure e astrazioni.

È ingiusto affermare che l'assembler fosse intrinsecamente più o meno dannoso per la memoria rispetto ad altri linguaggi, ha avuto solo una brutta reputazione a causa di quanto fosse difficile scrivere codice corretto.

2 JohnDoty Jan 23 2021 at 02:12

Era un problema molto comune. Il compilatore FORTRAN di IBM per l'1130 ne aveva parecchi: quelli che ricordo riguardavano casi di sintassi errata che non venivano rilevati. Il passaggio a linguaggi di livello più vicino alla macchina ovviamente non ha aiutato: i primi sistemi Multics scritti in PL / I si sono bloccati frequentemente. Penso che la cultura e la tecnica della programmazione abbiano avuto più a che fare con il miglioramento di questa situazione rispetto al linguaggio.

2 JohnDallman Jan 24 2021 at 21:26

Ho fatto alcuni anni di programmazione in assembler, seguiti da decenni di C. I programmi assembler non sembravano avere più cattivi bug dei puntatori di C, ma una ragione significativa era che la programmazione in assembler è un lavoro relativamente lento.

I team in cui facevo parte volevano testare il loro lavoro ogni volta che avevano scritto un incremento di funzionalità, che in genere era ogni 10-20 istruzioni dell'assemblatore. Nei linguaggi di livello superiore, in genere si esegue il test dopo un numero simile di righe di codice, che hanno molte più funzionalità. Ciò equivale alla sicurezza di un HLL.

Assembler smise di essere utilizzato per attività di programmazione su larga scala perché dava una minore produttività e perché di solito non era portabile su altri tipi di computer. Negli ultimi 25 anni ho scritto circa 8 righe di assemblatore, e questo era per generare condizioni di errore per testare un gestore di errori.

1 postasaguest Jan 22 2021 at 23:25

Non quando lavoravo con i computer allora. Abbiamo avuto molti problemi ma non ho mai riscontrato problemi di danneggiamento della memoria.

Ora ho lavorato su diverse macchine IBM 7090,360,370, s / 3, s / 7 e anche su micro basati su 8080 e Z80. Altri computer potrebbero aver avuto problemi di memoria.