Free Book: DEBUG / ASSEMBLY TUTORIAL\nDEBUG / ASSEMBLY TUTORIAL NEXT chapters\nDebug Tutorial by Fran Golden\nDocumento originale: http://www.armory.com/~rstevew/Public/Tutor/Debug/debug1.htm\nTraduzione: agosto 2009\nAll'interno del testo originale sono state inserite note aggiuntive al solo scopo di rendere più chiaro il testo. Tali note si distinguono dal testo originale perche' sono stampate a video con un carattere di testo più piccolo e sono delimitate dalle tag NOTA / FINE NOTA.\nI comandi di debug.exe non sono stati pubblicati in ordine alfabetico, ma in un ordine logico, sequenziale, in modo da facilitarne la comprensione anche a chi è a digiuno di questo genere di argomenti.\nRicorda che l'utilizzo delle informazioni contenute in questo articolo è sotto la tua responsabilità e a tuo rischio.\nDebug è un comando DOS, MS-DOS, OS/2 e Microsoft Windows ( solo le versioni x86, no x64 ) che lancia il programma debug.exe ( o DEBUG.COM nelle vecchie versioni di DOS ). Questo è un tutorial su Debug per assistere gli studenti che devono comprendere il funzionamento interno dei computer con tecnologia Intel. L'obiettivo di questo manuale è istruire lo studente su come osservare il contenuto del microprocessore e di tutte le locazioni di memoria che il processore può raggiungere. Dopo aver familiarizzato con Debug ed aver compreso come Debug vede gli indirizzi di memoria, sarete introdotti alla programmazione a livello macchina, utilizzando Debug come un assembler. Al termine di questo tutorial ( 11 lezioni ) dovreste aver raggiunto una buona comprensione dei sistemi IBM e della programmazione di basso livello, essendo in grado di: guardare nell'area dati di DOS allocata nella memoria e determinare che tipo di periferiche sono installate nella scheda madre; guardare il funzionamento interno di DOS, in particolare a cose del tipo: il buffer della tastiera, il clock real time, la tabella degli interrupt, il chip rom bios e i chip della ram video; recuperare file cancellati da un floppy o un hard disk; recuperare da un hard disk dati illeggibili per DOS che, in caso contrario, andrebbero perduti; testare l'hardware, come la scheda video, stampanti, dischi e chip; formattare a basso livello un hard disk; infine, assemblare piccoli programmi .COM utilizzando il set di istruzioni per la famiglia 8088.\nNOTA: Il programma DEBUG incluso nelle versioni Windows® NT/2000/XP/2003 sembra funzionare come su DOS 5.0, ma con due fondamentali differenze: DEBUG non ha più l'accesso all'hard disk, ma solo a file esistenti o ai dischi esterni A:\_ o B:\_ ( comandi L e W ), ma solo se Windows è in grado di riconoscere i relativi filesystem! Inoltre, i comandi di Input e Output non hanno alcuna utilità, visto che l'interfaccia con il resto del sistema è solo una emulazione, non prevedendo alcun accesso diretto all'hardware ( A Guide to DEBUG, di Daniel B. Sedory ). FINE NOTA\nIl debugger viene fornito in tutte le versioni di Windows, con il file:\nPer lanciarlo, è sufficiente aprire una finestra DOS. Al prompt di DOS scrivere:\nDebug risponderà con un trattino al prompt:\nDa questo momento, Debug resta in attesa di uno dei suoi tanti comandi composti da una sola lettera. Al prompt, è possibile anche specificare un file da caricare:\ndebug \_path\_filespec (return)\nIn questo caso, debug carica in memoria sè stesso e il file specificato, immettendo il primo byte del file nella locazione di memoria posta all'indirzzo ( offset ) 100H dell'area di lavoro. Debug si riserva un'area di lavoro di 65.536 locazioni di memoria ad un byte. Le prime 256 locazioni ( 000 - 100 in esadecimale ) di quest'area di lavoro sono riservate al Program Segment Prefix ( PSP ) del programma stesso ( debug ) e non devono essere modificate in alcun modo. Qualsiasi cosa dovessimo caricare con debug, deve essere allocata in locazioni con offset superiore a 100H.\nNOTA - Debug accetta solo locazioni espresse in notazione esadecimale. Quindi, in tutto l'articolo, verranno utilizzate le notazioni numeriche esadecimali, facendo seguire il numero dalla lettera H ( esadecimale ): negli esempi pratici di riga di comando, la lettera H non apparirà, per evitare di generare confusione nel lettore, ma resta inteso che i numeri saranno sempre espressi in notazione esadecimale. Inoltre, ritengo utile ricordare cosa si intende con i termini: locazione di memoria, segmento, offset. Per segmento, si intende un'area di memoria ( RAM ) di 65536 ( 64k ) locazioni consecutive, utilizzata dal sistema per allocare i programmi. Una locazione di memoria ( RAM ) è una sequenza di bit ( generalmente 8 ) che può essere utilizzata per conservare un dato o una istruzione. Un altra coppia di concetti chiave è quella di indirizzo logico e indirizzo fisico. Il problema è sorto fin dall'inizio: i progettisti dell'8086 si sono trovati a gestire un bus d'indirizzi di 20 linee con registri CPU a 16 bit; in pratica era evidente la necessità di porre un indirizzo valido sul rispettivo bus ma non era possibile farlo perchè gli strumenti a disposizione ( registri ) erano troppo piccoli. Da questa esigenza è nata la tecnica della segmentazione della memoria, un abile sotterfugio per poter puntare ogni indirizzo con 2 registri a 16 bit: il primo ( detto appunto registro di segmento ) individua un'area di 64kBytes consecutivi, dentro lo spazio indirizzabile del processore. Il secondo ( detto registro di offset ) è in grado di scorrere ciascuna delle 65536 locazioni contenute nel segmento, in virtù dei diversi possibili valori che può assumere. Dunque con un indirizzo logico, cioè con una coppia di registri di tipo:\nè possibile ottenere un indirizzo fisico. L'indirizzo fisico ( cioè il numero binario a 20 bit che sarà posto sul bus address ) si ottiene sommando il valore di un registro di segmento moltiplicato per 16 ( moltiplicare per 16 significa aggiungere a destra del valore esadecimale del registro di segmento 4 bit, un nibble, di valore nullo, uguale a 0 ) con il valore di un registro di offset. Se:\nSegment register CS =1234H\nCS = 0001001000110100\nOffset register IP = 0100H\nIP = 0000000100000000\nmoltiplicando 1234H x 10H abbiamo:\n00010010001101000000 ( 12340H )\nSommando, infine, 12340H e 0100H, abbiamo:\n00010010010001000000 ( 12440H )\nche è l'indirizzo fisico. Quante locazioni di memoria ( indirizzi fisici ) è possibile raggiungere con il metodo degli indirizzi logici? L'ultimo indirizzo raggiungibile è esprimibile nella coppia segmento/offset seguente:\n11111111111111110000\t# FFFF * 10H\n00001111111111111111\t# + FFFF\n100001111111111101111\t# = 10FFEF ( 1.114.095 in decimale )\nL'ultimo indirizzo raggiungibile, quindi, è un indirizzo a 21 bit. Al tempo in cui il bus degli indirizzi era di soli 20 bit, di fronte ad un indirizzo composto da più di 20 bit, la CPU troncava i bit più significativi. Il nostro indirizzo fisico 10FFEF sarebbe, quindi, diventato FFEF. Un motivo di confusione nell'utilizzo del formato Segmento:Offset è che uno stesso indirizzo fisico può essere rappresentato da più coppie di valori: ci possono essere fino a 4096 coppie di valori che esprimono lo stesso indirizzo fisico, o indirizzo assoluto.\n0007:7B90\t# indirizzo fisico 7C00 ( 31744 in decimale )\n0047:7790\t# indirizzo fisico 7C00 ( 31744 in decimale )\nIn realtà, il primo segmento di memoria contiene solo i primi 16 byte ( o paragrafi: sequenza di 16 byte consecutivi di memoria ) unici. C'è solo una coppia di valori che esprime ciascuno di quei sedici indirizzi: segmento 0000 e uno dei sedici valori che può assumere l'offset: 0000 fino a 000F.\n0000:0000\t# indirizzo fisico 0000 ( 0 in decimale )\n0000:0001\t# indirizzo fisico 0001 ( 1 in decimale )\n0000:000F\t# indirizzo fisico 000F ( 15 in decimale )\n0000:0010\t# indirizzo fisico 0010 ( 16 in decimale )\n0001:0000\t# indirizzo fisico 0010 ( 16 in decimale )\nPer ogni paragrafo ( 16 byte ) superiore della memoria, il numero di segmenti sovrapponibili cresce di uno, fino ad arrivare al termine del primo segmento. A questo punto, ogni paragrafo successivo di memoria avrà un numero di segmenti sovrapponibili pari a 4,096!\nlocazioni di memoria 00 ... 0FFFF ( 00 ... 65535 )\nlocazioni di memoria 10 ... 1000F ( 16 ... 65551 )\nlocazioni di memoria 20 ... 1001F ( 32 ... 65567 )\nlocazioni di memoria 30 ... 1002F ( 48 ... 65583 )\nlocazioni di memoria 90000 ... 9FFFF ( 589824 ... 655359 )\nlocazioni di memoria A0000 ... AFFFF ( 655360 ... 720895 )\nlocazioni di memoria F0000 ... FFFFF ( 983040 ... 1048575 )\nlocazioni di memoria FFFF0 ... 10FFEF ( 1048560 ... 1114095 )\nCome è possibile verificare, il segmento 9000 è l'ultimo segmento ( 64kb ) interamente compreso nei limiti di quell'area di memoria chiamata "Memoria Convenzionale" ( i primi 640kb o 655.360 byte). Il primo paragrafo del segmento A000 è l'inizio di quell'area di memoria chiamata Upper Memory Area ( UMA ), che contiene un totale di 384kb o 393.216 byte. Il segmento F000 è l'ultimo segmento dell'area UMA.\n640 KB + 384 KB = 1024 KB = 1.048.576 byte = 1 Mbyte\nGli indirizzi compresi nel segmento FFFF venivano, una volta, mappati nel segmento 0000. Più tardi, la memoria superiore al MB ( 1.048.576 byte ) che ancora poteva essere raggiunta utilizzando le coppie di valori segment:offset venne chiamata High Memory Area ( HMA ). ( A Guide to DEBUG ) FINE NOTA\nProgrammi EXE e programmi COM\nMS-DOS permette solo due tipi di programmi da verificare con il debugger: i programmi con estensione EXE ed i programmi con estensione COM. La differenza tra le due estensioni è nel modo in cui DOS gestisce le porzioni del programma, porzioni chiamate spesso Program Segment Prefix ( PSP ): si tratta di un blocco di memoria di 256 byte riservato al programma ( debug.exe ) e necessario a DOS per riconsegnare il controllo al sistema operativo una volta che il programma ( debug.exe ) termina. Senza addentrarci troppo nei dettagli, almeno in questo momento, vediamo quali sono le principali differenze tra le estensioni EXE e COM.\nI programmi .COM sono piccoli e compatti e non possono superare i 65 Kbyte.\nL'area PSP di un programma .COM è compresa nelle prime 100H ( 256 decimale ) locazioni di memoria riservate al programma.\nLa prima istruzione di un programma .COM deve iniziare alla locazione di memoria con offset 100H.\nE' DOS che crea l'area PSP per i programmi .COM, non il programmatore.\nTutti i dati, il codice e l'area dello stack risiedono all'interno dello stesso segmento di memoria ( 1 segmento è di 65 Kb ).\nI programmi .EXE possono avere qualsiasi dimensione ( 200 byte - 640 Kbyte ).\nL'area PSP deve essere creata dal programmatore.\nLa prima istruzione di un programma .EXE deve essere allocata in una locazione di memoria stabilita dal programmatore.\nI programmi .EXE utilizzano segmenti differenti di memoria per i dati, il codice e l'area dello stack.\nDalla comparazione, è evidente che è molto più difficoltoso assemblare programmi .EXE, piuttosto che programmi .COM. La utility debug era stata creata per funzionare solo con programmi .COM, impostando l'area PSP ad ogni apertura di debug.exe. Una volta aperto il debugger, possiamo lanciare qualsiasi programma che inizi dalla locazione con offset 100H, senza preoccuparci dell'area PSP o di dove siano allocati i dati, il codice e l'area dello stack.\nI set di istruzioni\nE' importante ora comprendere che:\nDebug ha il suo set di comandi:\nA C D E F G H I L M N O P Q R S T U W ?\nIl microprocessore ha il suo set di istruzioni. Il microprocessore è un circuito digitale che risponde solo ad una serie di 1 e 0 ( zero ). I dati entrano nella CPU attraverso i PIN dei dati, marcati da D0 a D7. Ciò equivale a un byte o 8 bit. Se il primo byte di dati che raggiunge la CPU è una istruzione, il processore risponderà eseguendo l'operazione appropriata. In altre parole, la CPU ha un suo set di codici a 8 bit o un byte ai quali risponde eseguendo una operazione all'interno dei registri o nei bus della scheda madre di sistema. Questi codici sono chiamati istruzioni e l'insieme dei codici viene definito set delle istruzioni. Quelle che seguono sono alcune istruzioni di esempio reali, che svolgono operazioni specifiche, relative ai processori 8088.\n10111000 sposta i prossimi due byte in AX\n00000101 LSB byte meno significativo del numero ( in AL )\n00000000 MSB byte più significativo del numero ( in AH )\n00000001 somma BX e AX, risultato in BX\n10001001 sposta il contenuto di BX in\n00011110 locazione di memoria puntata da\n00100000 i prossimi due byte\nQuesto tipo di programmazione è chiamato codice macchina, perchè le istruzioni vengono inserite utilizzando i dati binari o esadecimali che sono i soli dati ai quali la CPU risponde. Il set completo delle istruzioni macchina non viene riportato qui, perchè non servirebbe a nulla, ora. E' possibile, comunque, reperire queste informazioni nei manuali tecnici INTEL 8088. Ai primordi dell'informatica, la programmazione doveva essere scritta usando il codice macchina proprio del microprocessore su cui si lavorava e questo richiedeva la presenza di professionisti dotati di tale abilità. L'utilizzo dei computer era quindi riservato alle aziende e organizzazioni in grado di supportare finanziariamente operazioni tanto specialistiche e specializzate.\nNon ci volle molto tempo perchè i programmatori scrivessero un codice che svolgesse la gran parte del lavoro in binario. Uno dei programmi più importanti scritti fu Assembler che era un programma di conversione che permetteva l'utilizzo della lingua inglese per rappresentare il set delle istruzioni della CPU. Una volta scritto, il programma veniva compilato in codice macchina. Un programma simile è debug.exe, fornito da Microsoft in molte versioni di DOS e ( tutte ) di Windows. Un'istruzione Assembler è composta da una parola-istruzione facile da ricordare e un numero di operandi variante da 0 a 2:\nMNEMONIC ( op-code ) destinazione, sorgente\nop-code ( MOV nel nostro esempio ) dice al processore cosa fare. Il primo operando è la destinazione del dato, mentre il secondo operando è la sorgente. In altre parole, il dato viene spostato dal secondo operando al primo. L'esempio appena proposto sposta il contenuto del registro BX nel registro AX.\ncarica il numero 45 ( esadecimale ) nel registro AX.\ncarica il contenuto della locazione di memoria 120 ( esadecimale ) nel registro AX.\nsomma il contenuto della locazione di memoria 500 ( esadecimale ) ad AX , salvando il risultato in AX.\nI comandi di debug.exe: Q\nQ ( quit ). Chiude debug.exe e restituisce il controllo a DOS. Non accetta parametri. Assicurarsi di aver scritto su disco il programma o i dati prima di lanciare Q, altrimenti verranno persi.\nI comandi di debug.exe: D\nD ( dump ). Stampa a video il contenuto delle locazioni di memoria, suddiviso in due schermate: una in esadecimale e l'altra in ASCII.\nIl parametro range specifica la locazione di partenza e l'ultima locazione da leggere oppure la lunghezza della sequenza da leggere ( in esadecimale ), preceduta dalla lettera L.\n-D 100 110\t# Prima ed ultima locazione ( offset )\n-D 100 L11\t# espresso in lunghezza della sequenza\nSe le locazioni vengono espresse con il solo offset, debug.exe mostrerà le locazioni dello stesso segmento in cui si trova. Per ricevere, invece, il contenuto di un segmento diverso da quello in cui è allocato debug.exe, occorre che il primo valore sia espresso con la coppia: segmento:offset:\n-D 0d27:100 110\t# Prima ed ultima locazione ( segment:offset )\n-D 0d27:100 L11\t# espresso in lunghezza della sequenza\nSe non viene specificato alcun range ( intervallo ) debug.exe stamperà 128 byte a partire dall'ultimo indirizzo stampato. Per esempio:\n0D28:0100  4E 4C 53 46 55 4E 43 20-6E 6F 6E 20 69 6E 73 74\n0D28:0110  61 6C 6C 61 74 6F 0D 0A-21 4C 27 75 34 00 17 0D\n0D28:0120  20 63 6F 72 72 65 6E 74-65 20 6E 6F 6E 20 8A 20\n0D28:0130  70 69 97 20 76 61 6C 69-64 61 17 45 74 69 63 68\n0D28:0140  65 74 74 61 20 6E 6F 6E-20 74 72 6F 76 61 74 61\n0D28:0150  0D 0A 14 45 72 72 6F 72-65 20 64 69 20 73 69 6E\n0D28:0160  74 61 73 73 69 0D 0A 1A-4C 61 20 64 61 74 61 20\n0D28:0170  63 6F 72 72 65 6E 74 65-20 8A 20 25 31 20 25 32\nRipetendo il comando D, avremo:\n0D28:0180  0D 0A 15 44 6F 6D 4C 75-6E 4D 61 72 4D 65 72 47\n0D28:0190  69 6F 56 65 6E 53 61 62-1D 44 69 67 69 74 61 72\n0D28:01A0  65 20 6C 61 20 6E 75 6F-76 61 20 64 61 74 61 20\n0D28:01B0  28 25 31 29 3A 20 15 4C-27 6F 72 61 20 63 6F 72\n0D28:01C0  72 65 6E 74 65 20 8A 20-25 31 0D 0A 17 44 69 67\n0D28:01D0  69 74 61 72 65 20 6C 61-20 6E 75 6F 76 61 20 6F\n0D28:01E0  72 61 3A 20 15 2C 20 20-20 20 45 6C 69 6D 69 6E\n0D28:01F0  61 72 65 3F 20 28 53 2F-4E 29 43 54 75 74 74 69\nI comandi di debug.exe: E\nE ( enter ). Inserisce dati a partire dalla locazione di memoria specificata. I dati possono essere inseriti sia in esadecimale, sia in formato ASCII. Per inserire una stringa ( ASCII ), è necessario racchiuderla tra virgolette ( singole o doppie ). Il dato presente alla locazione specificata verrà perduto e sarà sostituito dal nuovo dato.\n-E address [list]\ndove address specifica la prima locazione di memoria in cui inserire i dati e list specifica i dati da inserire in byte di memoria consecutivi.\n-D 200 205\n0D28:0200  20 69 20 66 69 6C\ti fil\n-E 200 43,49, 41,4f\n0D28:0200  43 49 41 4F 69 6C\tCIAOil\nIn questo esempio, prima eseguiamo un dump ( stampa ) dei valori delle locazioni di memoria con offset compresi tra 200H e 205H ( quindi, all'interno dello stesso segmento in cui si trova debug.exe ). Il debugger ci dice che in quelle sei locazioni di memoria si trovano i valori esadecimali 20 69 20 66 69 6C, corrispondenti alla stringa ASCII ( spazio )i fil. A questo punto, eseguiamo un enter, inserendo i valori esadecimali 43,49, 41,4f, a partire dalla locazione di memoria con offset 200H. Visto che debug.exe non ci informa se l'inserimento è andato a buon fine ( in realtà, ci avvertirebbe se qualcosa fosse andato storto ), rieseguiamo un dump, in modo da verificare lo stato delle stesse locazioni di memoria stampate la prima volta. Il valore delle prime quattro locazioni di memoria è cambiato. Come per il comando D ( dump ), l'indirizzo della prima locazione di memoria ( address ) può essere espresso sia con il solo valore di offset ( in questo caso, la modifica verrà applicata a partire dalla locazione di memoria con l'offset indicato all'interno dello stesso segmento in cui è allocato debug.exe ), oppure con la coppia segmento:offset ( in questo caso, potremo modificare locazioni di memoria esterne al segmento in cui lavora debug.exe ).\n-E d28:200 00,00,00,00,00,00\n-D d28:200 205\n0D28:0200  00 00 00 00 00 00\t......\nSe viene specificato solo il valore dell'indirizzo della prima locazione di memoria ( address ), ma non i valori da scrivere ( list ), debug.exe mostrerà l'indirizzo di partenza ed il suo contenuto, mettendosi in attesa del nuovo dato da inserire in sostituzione dell'attuale.\nPer modificare questo valore, è sufficiente scrivere il nuovo valore:\nIn questo esempio, il valore 41H presente alla locazione di memoria con offset 100H è stato modificato in 35H. E' possibile inserire valori di byte consecutivi con un solo comando E. Invece di premere il tasto Enter ( Invio ) dopo aver cambiato un valore, è sufficiente premere la barra spaziatrice e debug.exe mostrerà la locazione di memoria successiva, nella quale potrete inserire un nuovo valore. Ripetete questo processo quante volte volete. Premendo la barra spaziatrice tre volte, debug.exe mostrerà le tre locazioni di memoria successive.\nPer ritornare al byte precedente, premete il tasto - ( trattino ). Ogni volta che viene premuto il tasto trattino ( - ), debug.exe torna indietro di un byte. Per uscire dal comando E, è sufficiente premere il tasto Enter ( Invio ). Per inserire dati utilizzando una stringa di testo, è necessario racchiudere la stringa di testo tra le virgolette:\n-E 100 "This is a string of text."\nQuesto comando inserirà la stringa di caratteri ASCII in 25 locazioni di memoria consecutive ( una locazione per ciascun carattere e spazio ), a partire dalla locazione con offset 100H.\nI comandi di debug.exe: F\nF ( fill ). Riempie le locazioni di memoria specificate con il valore specificato. Il valore può essere espresso sia in esadecimale, sia in ASCII.\n-F range list\ndove range specifica la prima e l'ultima locazione di memoria oppure la lunghezza della sequenza, preceduta dalla lettera L, in cui inserire i dati e list specifica i dati da inserire in byte di memoria consecutivi.\n-F 200 20f 61\n-F 210 L10 62\n-F 220 L10 61,63\n0D28:0200  61 61 61 61 61 61 61 61-61 61 61 61 61 61 61 61   a ...\n0D28:0210  62 62 62 62 62 62 62 62-62 62 62 62 62 62 62 62   b ...\n0D28:0220  61 63 61 63 61 63 61 63-61 63 61 63 61 63 61 63   acac ..\nSe range contiene più byte rispetto al numero di valori specificati in list, debug.exe assegna i valori specificati in list a tutti i byte compresi in range. Se una delle locazioni di memoria specificate in range è corrotta o non esiste, debug.exe stampa un messaggio di errore. Se list contiene più valori di quanti siano i byte specificati in range, debug.exe ignorerà i valori in eccedenza.\nI comandi di debug.exe: C\nC ( compare ). Compara due blocchi di memoria,\n-C range address\ndove range specifica la prima e l'ultima locazione di memoria oppure la lunghezza della sequenza, preceduta dalla lettera L, del primo blocco di memoria da comparare, mentre address specifica l'indirizzo della prima locazione di memoria del secondo blocco.\n-C 100 220 500\ncompara il blocco di memoria compreso tra l'offset 100H e l'offset 220H del segmento in cui si trova debug.exe con il blocco di memoria che inizia alla locazione con offset 500H.\n-C 100 L 121 500\ncompara il blocco di memoria che inzia alla locazione con offset 100H ed ha una lunghezza di 121 ( in notazione esadecimale ) byte del segmento in cui si trova debug.exe con il blocco di memoria che inizia alla locazione con offset 500H.\nI comandi di debug.exe: R\nR ( register ). Stampa a video o modifica i contenuti di uno o più registri del microprocessore. Il comando lanciato senza alcun parametro, stampa a video tutti i registri.\nDS=0D28  ES=0D28  SS=0D28  CS=0D28  IP=0100   NV UP EI PL NZ NA PO NC\n0D28:0100 4E            DEC     SI\nE' possibile specificare il registro da stampare a video o modificare.\nSe si specifica un registro ( AX ), debug.exe stampa a video il valore a 16 bit di quel registro in notazione esadecimale, seguito da due punti. Se si desidera MODIFICARNE il valore, basta scrivere il nuovo valore e premere INVIO. Se, invece, non si desidera modificare il valore di quel registro, è sufficiente premere INVIO. Se al posto del nome del registro, si inserisce la lettera F, debug.exe stampa a video lo stato corrente del registro delle FLAG.\nNV UP EI PL NZ NA PO NC  -\nA questo punto, debug.exe attende che inseriate, dopo il trattino, il codice a due lettere della FLAG sulla quale desiderate agire. Ciascuna FLAG ha un codice a due lettere che esprime il suo stato attuale:\nOverflow: ov nv\nDirection: dn up ( increment )\nInterrupt: ei ( enabled ) di ( disabled )\nSign: ng ( neg ) pl ( positive )\nZero: zr nz\nAuxiliary carry: ac na\nParity: pe ( even ) po ( odd )\nCarry: cy nc\nSe si desidera MODIFICARE il valore di una o più FLAG, basta scrivere il o i nuovo/i valore/i e premere INVIO. Se, invece, non si desidera modificare il valore di alcuna FLAG, è sufficiente premere INVIO.\nNV UP EI PL NZ NA PO NC  -pl ei cy\nQuando si apre debug.exe, i registri di segmento sono puntati alla locazione di memoria libera più bassa ( 0D28 nel nostro esempio ), il registro IP punta sempre alla locazione di memoria 100H, tutte le FLAG sono azzerate e tutti gli altri registri sono azzerati eccetto il registro SP ( stack ), che è impostato a FFFE.\nI comandi di debug.exe: A\nA ( assemble ). Assembla istruzioni mnemoniche Assembly in memoria. Il comando è utilizzato per creare codice macchina eseguibile da istruzioni assembly. Tutti i valori numerici devono essere espressi in notazione esadecimale e possono essere composti al massimo da 4 cifre.\ndove address è la locazione di memoria in cui memorizzare l'istruzione. Deve essere espressa in notazione esadecimale e deve essere compresa nell'intervallo 0100-FFFF. Se non viene specificata alcuna locazione, debug.exe utilizzerà l'ultimo indirizzo usato.\nNOTA. Per verificare il funzionamento dell'istruzione A, riempiamo di 0 i primi 128 ( 80 in notazione esadecimale ) byte del segmento di default:\n-F 100 L 80 00\n0D28:0180  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0190  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:01A0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:01B0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:01C0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:01D0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:01E0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:01F0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\nOra, lanciamo il comando A in modalità interattiva, senza alcun parametro. In questo modo, Debug.exe resterà in attesa delle istruzioni CPU da allocare: ogni istruzione inserita e confermata con il tasto Invio verrà allocata nella locazione di memoria successiva a quella appena utilizzata, partendo dalla locazione con offset 100H ( la locazione puntata dai registri CS:IP ). Per uscire dalla istruzione A, premere due volte il tasto Invio.\n0D28:0100 mov ax, 1\n0D28:0100  B8 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0110  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0120  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0130  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0140  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0150  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0160  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\n0D28:0170  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00   ...\nNelle locazioni di memoria 100H, 101H e 102H troviamo: l'istruzione CPU MOV AX ( B8 ) ed il valore da spostare ( 01H ). Come visto in precedenza, l'istruzione B8 ( 10111000 ) nei processori della famiglia 8088 significa proprio: "sposta i prossimi due byte in AX". I due byte che esprimono il valore da spostare sono memorizzati con la parte bassa ( 01 ) prima e la parte alta ( 00 ) dopo. Inseriamo una seconda istruzione:\n0D28:0103 mov BX, 1234\n0D28:0100  B8 01 00 BB 34 12 00 00-00 00 00 00 00 00 00 00   ....4...\nNelle locazioni di memoria 103H, 104H e 105H troviamo: l'istruzione CPU MOV BX ( BB ) ed il valore da spostare ( 1234H ). I due byte che esprimono il valore da spostare sono memorizzati con la parte bassa ( 34 ) prima e la parte alta ( 12 ) dopo. Inseriamo una terza istruzione:\n0D28:0106 add bx,ax\n0D28:0100  B8 01 00 BB 34 12 01 C3-00 00 00 00 00 00 00 00   ....4...\nNelle locazioni di memoria 106H e 107H troviamo l'istruzione CPU ADD BX, AX e metti il risultato in BX ( 01C3 ). Con queste semplici operazioni, abbiamo inserito le istruzioni CPU in memoria. Tali istruzioni, naturalmente, non sono state ancora eseguite. Tanto è vero che se chiediamo a debug.exe di stampare a video i valori dei registri, troveremo AX e BX ancora puliti ( con valore zero ).\n0D28:0100 B80100        MOV     AX,0001\nPer eseguire le istruzioni memorizzate, occorrerà lanciare un comando di esecuzione, quale il comando G ( go ). La sola utilità del comando A risiede proprio nella sua capacità di assemblare le istruzioni inserite, segnalandoci eventuali errori. FINE NOTA\nI comandi di debug.exe: G\nG ( go ). Esegue il programma in memoria. Il comando G utilizza il registro IP come puntatore alla istruzione da eseguire.\n-G [=address] [breakpoint]\ndove address ( preceduto dal segno = per distinguerlo da un breakpoint ) indica la locazione di memoria in cui è inserita la prima istruzione da eseguire. Se non viene specificato alcun address, debug.exe esegue come prima istruzione l'istruzione contenuta alla locazione di memoria contenuta nella coppia di registri CS:IP. Se il programma finisce con l'istruzione INT 20, il registro IP verrà reimpostato all'offset 100H. Se il programma finisce con l'istruzione INT 3, il registro IP resterà puntato all'ultima istruzione eseguita.\nNOTA. Al nostro programma memorizzato dobbiamo solo aggiungere un interrupt. Solitamente, è bene scegliere INT 20, ma in questo caso, per poter vedere i cambiamenti apportati ai registri, scegliamo INT 3:\n0D28:0108 INT 3\nAX=0001  BX=1235  CX=0000  DX=0000  SP=FFEE  BP=0000  SI=0000  DI=0000\nDS=0D28  ES=0D28  SS=0D28  CS=0D28  IP=0108   NV UP EI PL NZ NA PE NC\n0D28:0108 CC            INT     3\nEd ecco la sequenza di istruzioni eseguite:\nA questo punto, se volessimo chiedere a debug.exe di eseguire una seconda volta la stessa sequenza di istruzioni, non potremmo più dare il comando G senza parametri, poichè debug.exe tenterebbe di eseguire l'istruzione contenuta nella locazione di memoria con offset 108H, che è il valore contenuto nel registro IP, ma dovremmo specificare l'offset della locazione di memoria dalla quale iniziare l'esecuzione:\nSe, come ultima istruzione, avessimo eseguito INT 20, ci troveremmo i registri AX e BX reimpostati a zero ed il puntatore IP reimpostato alla locazione di memoria con offset 100H. FINE NOTA\nPrima di lanciare il comando G bisogna sempre verificare il valore contenuto nel registro IP, per assicurarsi che punti alla istruzione che vogliamo sia eseguita. Per vedere il valore del registro IP ed eventualmente modificarlo, c'è il comando:\nIl registro IP deve sempre contenere il valore 100H affinchè venga eseguito l'intero programma. Quando terminate il programma con l'istruzione INT 3 ricordate che i valori di tutti i registri vengono preservati e non azzerati o riportati al loro valore iniziale, mentre il registro IP conterrà l'offset della locazione di memoria dell'ultima istruzione eseguita! INT 20, al contrario, al termine del programma, azzera tutti i registri e imposta il registro IP al suo valore iniziale: 100H. Il patrametro breakpoint specifica da 1 a 10 breakpoint. I breakpoint sono locazioni di memoria in cui il programma si ferma, stampando a video il contenuto dei registri, delle FLAG e l'ultima istruzione eseguita. Se scrivete:\ndebug.exe eseguirà il programma fino all'offset 103H, stampando a video il contenuto di tutti i registri. Vedere i comandi P e T.\nI comandi di debug.exe: N\nN ( Name ). Specifica il nome del file che verrà utilizzato dai comandi L o W.\nI comandi di debug.exe: L\nL ( Load ). Carica un file o il contenuto di un settore del disco in memoria.\n-L address drive start number\naddress specifica la locazione di memoria dalla quale allocare il file o il contenuto di un settore del disco. Se non specificato, debug.exe utilizzerà l'indirizzo contenuto nei registri CS:IP. debug.exe ignora il parametro address per i file EXE. Se il file da caricare è un file EXE, debug.exe alloca il file partendo dall'indirizzo di memoria specificato nell'header del file EXE. L'header viene poi eliminato prima di caricare in memoria il file: le dimensioni del file presente su disco, quindi, saranno differenti rispetto alle dimensioni del file caricato in memoria. Se si desidera esaminare il contenuto di un file EXE ( e non eseguire un debug ), lo si può rinominare con una estensione differente:\nREN FILE1.EXE FILE1.TXT\ndrive specifica il disco che contiene i settori specifici da caricare. E' un numero tra 0 e 4:\nstart specifica, in notazione esadecimale, il primo settore dal quale leggere il contenuto.\nnumber specifica, in notazione esadecimale, il numero di settori da caricare in memoria.\n-L 0100 0 5 7\ndebug.exe caricherà, nelle locazioni di memoria il cui primo indirizzo offset è 0100, 7 settori, a partire dal settore 5, dal floppy disk ( 0 ). La directory root inizia proprio dal settore numero 5 ed occupa 7 settori.\nNOTA ( da Tutorial Assembler di Giorgio OBER ). La numerazione di settori logici proseguirà in sequenza sulla medesima traccia ( Cylinder ) del lato ( Head ) corrente e proseguirà ( in verticale ) sulla stessa traccia del lato successivo fino all'esaurimento di tutte le facce disponibili; per riprendere, lato dopo lato, sulla traccia ( cilindro ) successiva, verso l'interno del disco. Questo modo logico di trovare i settori di un disco è tipico del DOS ed è diverso da quello assoluto/fisico usato dal BIOS e costituito dalla terna di numeri detta sinteticamente CHS ( Cylinder, Head, Sector ). Così, per esempio, il primo settore fisico, il boot record, identificato dal numero 0,0,1, corrisponde al settore logico 0.\n-L 0100 0 0 1\nLa stringa si legge: carica ( L ), a partire dall'indirizzo 0100, dal disco 0 ( driver A ) il settore n°0 ( il primo ) nella quantità 1 ( un solo settore, cioè 512 bytes ). Analogamente per analizzare le 2 copie della FAT ( File Allocation Table ) di un floppy disk da 1,44M, possiamo dare il comando seguente, che le copia a partire dall'indirizzo ( offset ) 1000H. Notoriamente ciascuna FAT occupa, in questo caso, 9 settori, subito dopo quello del boot record, analizzato in precedenza; perciò il comando sarà ( per la prima copia di FAT ): carica ( L ), a partire dall'indirizzo 1000, dal disco 0 ( driver A ), i settori a partire dal n°1 ( il primo ) nella quantità 9:\n-L 1000 0 1 9\noppure ( per la seconda copia di FAT ): carica ( L ), a partire dall'indirizzo 1000, dal disco 0 ( driver A ), i settori a partire dal n°10 ( in exa 0AH ) nella quantità 9:\n-L 1000 0 A 9\nSe vuoi continuare con questo gioco ricorda che, in un floppy disk da 1,44M, la directory occupa i 14 settori successivi alle 2 copie della FAT; è facile concludere che il comando da dare sarà: carica dal disco 0 ( driver A ) i settori a partire dal n°19 ( cioè 1+9+9=19, in esadecimale 13H ) nella quantità 14 ( in esadecimale 0EH ):\n-L 1000 0 13 E\nSe il comando viene invocato senza alcun parametro, debug.exe ricaricherà il file che era stato specificato nella linea di comando all'apertura di debug.exe, all'indirizzo CS:0100. Se alla linea di comando non era stato specificato alcun file, debug.exe caricherà il file che era stato specificato con il comando N.\ndebug.exe, inoltre, imposta i registri BX:CX sul numero di byte che compongono il file ( dimensione del file ).\nAX=0000  BX=0000  CX=1A00  DX=0000  SP=00B8  BP=0000  SI=0000  DI=0000\nDS=0D39  ES=0D39  SS=0D49  CS=0D49  IP=0000   NV UP EI PL NZ NA PO NC\n0D49:0000 0E            PUSH    CS\nNel nostro caso, break.exe è composto da BX=0000 CX=1A00 ( 6656 in notazione decimale ) byte. Il nome del file aperto da debug.exe viene memorizzato dalla locazione di memoria con offset 80H. Per vedere il nome del file, è sufficiente utilizzare il comando:\nSarebbe buona cosa verificare il nome del file in modo da assicurarsi che il file o il programma su cui si sta lavorando sia sul disco.\nI comandi di debug.exe: H\nH ( Hex ). Calcola la somma e la differenza dei due valori dati.\n-h value1 value2\n-H ff 2c5\ndove 03C4 è la somma, mentre FE3A è la differenza.\nI comandi di debug.exe: I\nI ( input ). Legge e stampa a video un byte dalla porta specificata. L'indirizzo della porta può essere un valore composta da 8 o 16 bit. E' possibile avere fino a 65,535 indirizzi di porta ( FFFF ).\n-i 3bc\t# 1 byte di dati dalla porta parallela\nI comandi di debug.exe: M\nM ( move ). Copia il contenuto di un blocco di memoria in un altro blocco.\n-m range address\ndove range specifica gli indirizzi di inizio e di fine, oppure l'indirizzo di inizio e la lunghezza, dell'area di memoria il cui contenuto è da copiare e address specifica il primo indirizzo di memoria dell'area in cui copiare il contenuto.\n-m 0100 110 500\n-m 0100 L11 500\nE' possibile utilizzare questo comando quando si deve inserire una istruzione in un programma già scritto. Supponiamo di dover inserire l'istruzione Mov ax,5600 nella locazione di memoria con offset 160 in un programma su cui stiamo lavorando.\n-m 160 FFFF 1000\nCS:0160 MOV AX,5600\n-m 1000 FFFF 163\nAbbiamo copiato tutto il programma, a partire dalla locazione di memoria con offset 160H, nel blocco di memoria che inizia dall'offset 1000H. Poi, abbiamo assemblato la nuova istruzione alla locazione di memoria con offset 160H. Infine, per riportare il programma nel blocco precedentemente occupato, ( avendolo copiato in parte all'offset 1000H ), abbiamo ricopiato il contenuto del blocco che inizia dalla locazione di memoria con offset 1000H nel blocco che ha inizio dall'offset 163H ( 163 è l'offset successivo alla nuova istruzione inserita ).\nI comandi di debug.exe: O\nO ( output ). Invia un byte alla porta specificata.\n-o port byte\ndove port specifica l'indirizzo della porta a cui effettuare l'invio. Può essere una porta a 8 o 16 bit. byte specifica un valore esadecimale da inviare alla porta specificata.\n-o 3bc 41\ninvia la lettera A alla stampante sulla porta parallela\nI comandi di debug.exe: P\nP ( proceed ). Esegue un loop, una istruzione ripetuta, un interrupt, una subroutine, o esegue un trace all'interno di una qualsiasi altra istruzione.\n-p [=address] [number]\ndove address specifica l'indirizzo della prima istruzione da eseguire, mentre number specifica il numero di istruzioni da eseguire prima di riconsegnare il controllo a debug.exe ( il valore di default è 1 ). Se non viene specificato alcun address, debug.exe utilizzerà l'indirizzo contenuto nei registri CS:IP ( vedi il comando G ). P è simile ai comandi G o T e viene utilizzato per eseguire il debug di un programma, eseguendo una istruzione dopo l'altra. Tuttavia, il comando P esegue in toto i loop e le istruzioni INT ( chiamate di sistema ). Il comando P non può eseguire il trace dei programmi della ROM BIOS.\nI comandi di debug.exe: S\nS ( search ). Cerca all'interno di un'area della memoria un byte o una sequenza di byte.\n-s range list\ndove range specifica l'inizio e la fine dell'area di memoria in cui cercare, mentre list specifica la sequenza di byte o la stringa da cercare. I byte della sequenza di byte devono essere separati da una virgola o da uno spazio, mentre la stringa va inclusa tra virgolette.\n-s 100 250 41 55 66\ncerca, nell'area di memoria compresa fra gli offset 100 e 250, la sequenza di byte: 41 55 66. Per cercare, all'interno della stessa area di memoria, la stringa testuale "Data Institute":\n-s 100 250 "Data Institute"\nIl comando S ( search ) è case sensitive.\nI comandi di debug.exe: T\nT ( trace ). Esegue una istruzione alla volta e stampa a video il contenuto dei registri, lo stato delle FLAG e l'istruzione successiva da eseguire.\n-t [=address] [number]\ndove address specifica la locazione di memoria dalla quale iniziare, mentre number specifica il numero delle istruzioni da eseguire ( valore di default: 1 ). Se address non viene specificato, debug.exe utilizzerà i valori dei registri CS:IP come locazione della istruzione successiva. T è simile ai comandi G e P, eccetto per il fatto che T non può attraversare una sub-routine o una istruzione INT. Raccomando di utilizzare il comando P in tutti i casi si desiderasse procedere passo a passo ( una istruzione alla volta ). Tuttavia, T può essere utilizzato per eseguire un trace nel programma ROM BIOS. Se si desidera disassemblare il programma ROM BIOS, bisogna utilizzare il comando T.\nI comandi di debug.exe: U\nU ( unassemble ). Disassembla, stampando a video i byte ed i corrispondenti codici sorgente ( nella loro forma mnemonica ), con gli indirizzi ed i valori.\ndove range specifica la prima locazione di memoria e l'ultima, o la prima locazione di memoria e la lunghezza, della sequenza da disassemblare. Se non viene specificato alcun range, U disassembla 20 byte. Per esempio, per disassemblare 16 byte ( 10hex ), a partire dalla locazione di memoria con offset 0100H:\n-u 0100 L 10\nCS:0100 MOV AX,5600\nCS:0102 MOV DX,200\nCS:0105 INT 21\nCS:0107 MOV AH,7\nCS:010B CMP AL,1B\nCS:010D JZ 0111\nCS:010F JMP 0107\nNOTA. E' importante riportare alcune osservazioni tratte dal Tutorial Assembler di Giorgio OBER.\nIl comando U prova comunque a tradurre in assembly i bytes che trova; se per qualche ragione il programma contiene tabelle o dati o messaggi ( cosa estremamente probabile ) il risultato sarà del tutto illogico, cioè verranno mostrate sequenze di istruzioni assurde e scorrelate. Bisogna quindi saper localizzare le aree dati di un programma, escludendole.\nL'indirizzo della locazione di partenza è CS:0100 ( perchè è la prima volta ) ma in generale è sempre dato dal valore corrente di CS:IP.\nIl comando U, di solito, ha senso solo dopo aver specificato il nome di un file eseguibile ( con il comando N ) ed averlo caricato in memoria ( con il comando L ), magari dopo aver pulito la memoria chiamata ad ospitarlo ( con il comando F ):\n-f 100 1000 00\nSe viene dato senza parametri, il comando U inizia a tradurre i byte presenti in memoria a partire dall'indirizzo CS:0100H; il segmento di default è ovviamente quello di codice, CS. FINE NOTA.\nI comandi di debug.exe: W\nW ( write ). Scrive su un file\nper salvare un file su disco, o uno specifico numero di settori del disco specificato.\n-w address drive start number\nper scrivere i settori del disco, dove address specifica l'indirizzo iniziale di memoria del file che volete scrivere su disco. Se non specificato, debug.exe inizierà dalla posizione di memoria CS:0100H. drive specifica il drive su cui scrivere:\nstart specifica il primo settore del disco, espresso in notazione esadecimale, su cui scrivere e number specifica il numero totale dei settori su cui scrivere.\nNOTA - NON fate esperimenti con il comando W! Il comando può essere utilizzato per creare nuovi file sul disco, ma solo se usato in modo appropriato. Provare a scrivere direttamente in un settore dell'hard disk sarebbe raramente da considerare un uso appropriato di questo comando! Scrivere direttamente su hard disk utilizzando i numeri di settore può comportare perdita di dati o addirittura l'impossibilità di riavviare il sistema! Windows XP e successivi impediscono di sovrascrivere direttamente i settori dell'hard disk, ma permettono di sovrascrivere i settori di floppy disk, ( A: o B: ). FINE NOTA\nPer scrivere un file su disco, bisogna prima nominarlo con il comando N ( name ), specificando eventualmente anche il percorso, per esempio:\nUna volta assegnato un nome al nostro file, bisogna specificare la dimensione totale del file in byte, impostandola nei registri BX:CX. Fatto questo, salviamo il tutto con il comando W.\nScrittura di 000C8 byte in corso\nFate attenzione: il registro BX è a zero prima di scrivere un file COM su disco. Ciascuna cifra in BX rappresenta 65000 byte ( in decimale ). Per il nostro scopo, BX sarà sempre a zero.\nQuando si usa il comando W per salvare il nostro lavoro in un file sul disco non bisogna dimenticare di caricare nei registri BX e CX la parte alta e bassa del numero a 32 bytes che indica la quantità di bytes da salvare!!\nIl modo più semplice per calcolare il numero da mettere in BX,CX consiste nel prendere l'indirizzo di offset della locazione successiva all'ultima e sottrarre da esso il numero 0100H.\nTi prego di fare molta attenzione: la pratica del comando W è ad altissimo rischio!! Se usata con discrezione, sicurezza e competenza può rivelarsi uno strumento potente, ma non dimenticare che quando la usi ti comporti come un virus: cancelli e sostituisci parti del tuo disco!! Ti consiglio di fare le prove con un dischetto floppy e di non toccare l'HD, se non sei ben certo di quello che fai. Il prossimo esempio mostra come scrivere su un floppy disk i 2 Kbyte ( 4 settori ) attualmente presenti all'indirizzo CS:1000H:\n-w 1000 0 64 4\nSi legge: preleva a partire dalla locazione 1000, i byte da scrivere sul disco 0 ( driver A ); il primo settore da sostituire è il 64 ( valore esadecimale pari a 100 ) nella quantità 4 ( 4 settori, cioè 512*4 bytes = 2048 bytes = 2k ). FINE NOTA\nIl linguaggio Assembly è un linguaggio di bassissimo livello che utilizza prefissi mnemonici seguiti da uno o due operandi. I comandi sono in inglese e utilizzano parole comuni. Per esempio:\nQuesti comandi sono abbastanza comprensibili, proprio grazie all'uso di parole facili da ricordare.\nAlla prima riga, l'istruzione MOV AX,34 significa semplicemente: sposta il numero 34 nel registro AX.\nAlla seconda riga, MOV ,AX significa: sposta il contenuto del registro AX alla locazione di memoria con offset 120.\nAlla terza ed ultima riga, SUB AX,BX significa: sottrai il contenuto del registro BX dal contenuto del registro AX e salva il risultato nel registro AX.\nDebug permette di scrivere piccoli programmi usando le istruzioni, nella loro forma mnemonica, del set delle istruzioni per i microprocessori 8088. Ci viene richiesto di specificare l'indirizzo utilizzato dal programma nella forma di numero offset. Questo significa che dobbiamo fare molta attenzione alle locazioni di memoria di tutte le istruzioni utilizzate nel programma.\nBios call - quando un programma o una periferica esegue una interrupt sub-routine nel chip ROM BIOS ( see INT ).\nBit - La più piccola unità di dato digitale. Un bit rappresenta o uno o zero in logica binaria.\nBYTE - Un byte è un una sequenza di 8 bit con il bit meno significativo a destra. Normalmente, i bit vengiono rappresentati nel seguente ordine:\nCode - i dati scritti in memoria. Codice macchina è l'insieme delle istruzioni del programma espresse in notazione esadecimale. Codice Assembly è il codice del programma scritto in linguaggio assembly. Codice testo è codice ASCII che rappresenta l'insieme dei messaggi del programma.\nData - è l'informazione binaria che troviamo nei chip della memoria del computer sotto forma di un byte o 8 bit. Debug mostra i dati binari sotto forma di numeri in notazione esadecimale: se si desidera vederli come numeri in notazione binaria, bisogna convertire il numero esadecimale in bit binari. A volte i dati vengono rappresentati utilizzando due byte, dando origine ad una word, o quattro byte, originando una double word.\nDebug - un programma utilizzato per vedere, editare, salvare dati in memoria e su disco. Può essere anche utilizzato per assemblare piccoli programmi COM. La parola viene anche utilizzata per la soluzione di problemi inerenti i programmi informatici. Come racconta la leggenda, un computer si bloccò e si dovette pensare a ripararlo. Dopo lungo tempo, si scoprì che il problema era stato causato da un insetto ( bug ) che si era andato a cacciare in uno dei relay.\nDisplay - un altro nome per il monitor, utilizzato per mostrare l'output dei programmi. I display più comuni in uso oggi sono: Mono-graphics ( MGA ), Color graphics ( CGA ), Video graphics ( VGA ) e Super Video graphics ( SVGA ).\nDouble word - per i processori 8088, 80286, 80386 double word è la sequenza di due word o quattro byte. Una double word viene allocata in memoria in ordine inverso, con il byte più significativo nella locazione con offset più alto.\nDOS call - quando un programma o una periferica esegue una interrupt sub-routine in IBMDOS.COM. Tutte le chiamate DOS utilizzano l'istruzione INT 21 ( see INT ).\nEcho - quando un carattere è echoed sul display, semplicemente appare sullo schermo. Ogni volta in cui premiamo un tasto sulla tastiera, quel carattere viene echoed ( stampato ) a video.\nFLAG - il processore ha un registro a 11 bit chiamato registro delle FLAG in cui vengono salvati gli esiti delle operazioni aritmetiche.\nINT - una istruzione utilizzata nei programmi per chiamare una interrupt sub-routine ( software ) che può trovarsi sia nei chip ROM BIOS sia nel sistema operativo MS-DOS. INT 10, per esempio, chiama una interrupt sub-routine 10 che controlla il display.\nInterrupt - comando per interrompere l'attività della CPU per un certo periodo di tempo. Ci sono due tipi di Interrupt:\nUn interrupt software, nel caso in cui una istruzione INT viene eseguita da un programma.\nUn interrupt hardware, nel caso in cui un componente hardware invia un segnale di interrupt al controller degli interrupt.\nLOOP - istruzione 8088 utilizzata per le operazioni ripetute. La sintassi è:\ndove xxxx è l'indirizzo dell'istruzione da eseguire. Il registro CX deve contenere un contatore per il loop: ad ogni esecuzione dell'istruzione, CX verrà decrementato di uno, fino a raggiungere zero. A questo punto, l'istruzione loop viene interrotta e viene eseguita l'istruzione successiva.\nPort - è una scheda inserita in uno slot della scheda madre. Ogni scheda ha il proprio indirizzo di I/O ( input/output ). I processori 8088-80486 possono avere fino a 65,535 porte. Le porte sono accessibili con le istruzioni OUT e IN.\nOUT 03BC 41\nQuesta istruzione invia il dato ( 41 HEX o A maiuscola ) alla porta con indirizzo 03BC. Alcune porte comuni per i computer IBM sono:\nLPT1 ( indirizzo: 03BC )\nLPT2 ( indirizzo: 0378 )\nLPT3 ( indirizzo: 0278 )\nCOM1 ( indirizzo: 03F8 )\nCOM2 ( indirizzo: 02F8 )\nCOM3 ( indirizzo: 03E8 )\nCOM4 ( indirizzo: 02E8 )\nMono ( indirizzo: 03B0-03BF )\nColor ( indirizzo: 03D0-03DF )\nHard ( indirizzo: 0320-032F )\nFloppy ( indirizzo: 03F0-03F7 )\nGiochi: ( indirizzo: 0200-020F )\nOffset - è una parte dell'indirizzo di una cella di memoria contenuta nei chip della RAM. Quando assembliamo o carichiamo un programma COM, il programma sarà sempre allocato in un segmento della memoria. La prima vera locazione di memoria di quel segmento è all'offset 0, mentre l'ultima locazione è all'offset FFFF. Un programma COM deve risiedere tutto nello stesso segmento: questo significa che un programma COM ha meno di 65,535 byte.\nRegister - una locazione per la memorizzazione temporanea. I micro-processori 8088 hanno 14 registri a 16 bit:\nQuattro per i dati: AX, BX, CX, DX\nQuattro per gli indirizzi di segmento: ES, DS, SS, CS\nQuattro per gli indirizzamenti: SP, BP, SI, DI\nUno è il puntatore delle istruzioni ( instruction pointer ): IP\nUno è il registro delle FLAG\nReset - Un pin dei processori 8088 - 80486 utilizzato per forzare il processore alla condizione di reset ( boot ). Il termine Reset è utilizzato anche per descrivere l'atto di annullare un bit, impostandolo a zero.\nReset Address - è un indirizzo preimpostato al quale tutti i processori puntano ogni volta che trovano il pin di reset attivo. L'indirizzo di reset è\nF000:FFF0\t# FFFF0 indirizzo fisico assoluto\nContiene una istruzione JUMP ( ROM BIOS CHIP ) che punta alla prima istruzione del programma ROM BIOS.\nStack - è una locazione di memoria utilizzata dai programmi per memorizzare dati per un breve periodo di tempo. Lo stack si trova all'indirzzo di memoria con offset FFFE, all'inizio della memoria in un programma COM. Le istruzioni PUSH e POP utilizzano lo stack per memorizzare il contenuto dei registri quando una istruzione INT viene eseguita.\nString - gruppo di byte che rappresentano una informazione.\n" This is an example of a string "\nSub-routine - un insieme di istruzioni, creato per realizzare un determinato compito, memorizzato all'esterno del programma principale. Per utilizzare una sub-routine viene eseguita l'istruzione CALL all'interno del programma principale: quando tutte le istruzioni contenute nella sub-routine sono state eseguite, una istruzione RET riporta il controllo al programma che ha chiamato la sub-routine.\nWord - nei processori 8088, 80286, 80386 una word è composta da due byte ed è memorizzata in ordine inverso, con il byte più significativo nella locazione di memoria con offset più alto.\nIl microprocessore 8086/8088\nTutte le informazioni che seguono sono relative alla famiglia di microprocessori 8086, che include i processori 80286, 80386 e 80486. Noi parleremo del set di istruzioni della CPU 8088. Per gli altri microprocessori, dovrete recuperare il set di isrtuzioni addizionale relativo a quella CPU. Questi processori sono compatibili tra di loro: tutti processori successivi comprendono il set di istruzioni per il processore 8088. Il microprocessore 8088 è un chip del tipo HMOS TTL a 40 PIN che utilizza una fonte a 5 volt. IBM lo definisce un processore a 16 bit, ma in realtà si tratta di un processore a 8 bit. Questa discrepanza deriva dal fatto che il processore ha solo 8 PIN per i dati provenienti dall'esterno, ma ha, al suo interno, registri per i dati a 16 bit. In altre parole, i dati vengono trasferiti al processore 8 bit alla volta, ma il processore 8088 opera, attraverso i registri, su gruppi di 16 bit. All'interno del processore 8088 ci sono diversi registri che vengono utilizzati per memorizzare temporaneamente i dati e / o modificarli per un brevissimo tempo. Questi registri possono essere pensati come latch di tipo D ( latch è un circuito elettronico con almeno due stati stabili e che è quindi in grado di memorizzare un bit di informazione ) o flip-flop di tipo JK a 16 bit. Appena completata una operazione sul dato presente in uno dei registri, il dato deve essere spostato in una locazione RAM, per una memorizzazione di maggior durata, fino a quando non verrà salvato su file.\nMICROPROCESSORE HMOS 8088 8-BIT\ninterfaccia bus dati a 8 bit\nregistri interni a 8 - 16 bit\n14 registri attivi ( 16 bit )\nindirizzamento diretto ad un Mega di memoria\noperazioni su byte, word e blocchi\n2 velocità di clock ( 4.77 MHZ e 8 MHZ )\nDESCRIZIONE DELLE FUNZIONI DEI PIN ( immagine CPU 8088 )\nAD7 .. AD0 ( pin 9 .. 16 ): indirizzi di I/O e bus dei dati: questi PIN sono connessi con bus multiplexati che trasportano sia indirizzi di memoria, sia linee di dati ( In un Bus Multiplexato possono viaggiare segnali “semanticamente” differenti, quali ADDR e DATA, indirizzi di memoria e dati ).\nA15 .. A8 ( pin 2 .. 8 e pin 39 ): bus degli indirizzi ( non multiplexati ): connessi con 8 linee del bus degli indirizzi ( rappresentano i bit dal numero 8 al numero 15 ).\nA16 .. A19 ( pin 35 .. 38 ): questi PIN rappresentano le linee di indirizzo di memoria più significative. Complessivamente il bus degli indirizzi, a 20 bit, è rappresentato, quindi, dai PIN: AD0 .. AD7, multiplexati, A15 .. A8 e A16 .. A19. Anche questi quattro ultimi bit più significativi del bus degli indirizzi sono multiplexati con 4 segnali di stato:\nPIN A19\tstatus S6 ( fisso a zero )\nPIN A18\tstatus S5 ( valore dell'Interrupt Enable Flag )\nPIN A17\tstatus S4\nPIN A16\tstatus S3\nLa coppia S3 e S4 indica quale registro di segmento è stato utilizzato per costruire l'indirizzo sul bus degli indirizzi:\n01\tCS ( oppure 0 )\nRD ( pin 32 ): Read: indica che il processore sta effettuando un ciclo di lettura da memoria o da periferica ( I/O ), a seconda dello stato del PIN IO/M o S2 ( PIN 28 ).\nREADY ( pin 22 ): è il segnale inviato dalla locazione di memoria o dalla periferica di I/O per avvertire la CPU che il trasferimento del dato deve essere completato. READY è il risultato della sincronizzazione del segnale RDY, inviato dalla memoria o dal dispositivo di I/O, effettuata dal generatore di clock 8284.\nINTR ( pin 18 ): richiesta di interrupt. Input ( level-triggered interrupt ) attivato durante l'ultimo ciclo di clock di ogni istruzione per comunicare se effettuare un interrupt. Viene lanciata una subroutine attraverso una tabella degli interrupt allocata in memoria. Può essere modificato via software reimpostando il bit interrupt enable.\nTEST ( pin 23 ): l'input viene esaminato dalla istruzione "wait for test". Se l'input TEST è basso, l'esecuzione continua, altrimenti il processore si mette in attesa in stato "idle".\nNMI ( pin 17 ): non maskable interrupt. Richiesta di interrupt. Input ( edge triggered interrupt ) che genera un interrupt di tipo 2. Viene lanciata una subroutine attraverso una tabella degli interrupt allocata in memoria. Non può essere modificato via software.\nRESET ( pin 21 ): comanda la fine dell'attività del processore. Deve restare attivo per almeno 4 cicli di clock.\nCLK ( pin 19 ): clock. Fornisce il tempo per l'attività del processore e del bus controller.\nVCC ( pin 40 ): è il PIN fornitore di corrente a +5 V +/- 10%.\nGND ( pin 1 e 20 ): Acronimo di "GrouND": il punto di connessione con il negativo dell'alimentazione.\nMN/MX ( pin 33 ): MINIMUM/MAXIMUM: indica in quale modalità stia operando il processore.\nIl processore 8088 gestisce indirizzi a 20 bit per indicare il byte di memoria selezionato. La memoria è gestita come un blocco delle dimensioni massime di 1 megabyte, con indirizzi compresi tra i valori:\n00000 ( H )\nFFFFF ( H )\nQuesta memoria viene ulteriormente suddivisa in blocchi più piccoli chiamati segmenti, la cui dimensione è 64 Kbyte. Ci sono quattro tipi di segmento, utilizzati per i dati e per il codice di un programma:\nL'indirizzamento viene realizzato combinando uno dei quattro registri di segmento ad alta velocità, CS, DS, ES, e SS, con un registro IP ( instruction pointer ). Il registro di segmento punta al segmento di memoria, mentre il registro IP punta ad una locazione ( offset ) all'interno di quel segmento. I registri sono divisi in gruppi in base alla loro funzione. I registri con una funzione generica ( General purpose registers ) sono a 16 o a 8 bit. Il sistema operativo alloca nella memoria bassa, dalla locazione 00000 alla locazione 0B000, fino a 3 programmi differenti. L'ultimo indirizzo utilizzato dipende dalla versione DOS e dal numero di driver caricati durante il processo di boot.\nI primi 3FF byte di RAM, a partire dalla locazione 00000, contiene la interrupt vector table, una tabella di indirizzi di memoria, ciascuno dei quali puntati su una\nsub-routine, residentte nell'IBMBIO, IBMDOS o nella ROM BIOS.\nI successivi FF byte, a partire dalla locazione 00400 ( 3FF + 1 ), sono usati dal sistema operativo per memorizzare una lista completa, in formato esadecimale, delle periferiche. Correttamente interpretata, questa lista può essere utilizzata per determinare le periferiche connesse al sistema.\nA partire dalla locazione di memoria 00500, troviamo il programma IBMBIO, che contiene tutte le sub-routine usate dal sistema operativo per dialogare con l'hardware. BIO è un acronimo per\ne contiene istruzioni di basso livello utili a comunicare con periferiche quali: tastiera, stampante, monitor, dischi e chip della scheda madre.\nIl programma successivo caricato è chiamato IBMDOS, che è un pochino più complicato e contiene sub-routine che si interfacciano con il DISK OPERATING SYSTEM. Le istruzioni nell'IBMDOS comunicano con il programma IBMBIO e con l'ultimo programma caricato in memoria: COMMAND.COM. Command.com è l'interprete dei comandi che monitora la tastiera in attesa che l'utente prema il tasto enter. Quando il tasto enter viene premuto, il compito di command.com è di determinare il comando inserito. Il comando può essere o un comando interno o un comando esterno, a seconda di dove sono memorizzate le istruzioni per il comando. I comandi interni si trovano tutti nell'area di command.com, mentre i comandi esterni si trovano tutti sul disco. Dopo aver interpretato il comando, command.com trasmette tutti i parametri al programma IBMDOS. IBMDOS preparerà i parametri per il programma IBMBIO, che attiverà il disco o controllerà l'hardware necessario alla esecuzione del comando inserito.\nMS-DOS è un sistema operativo a tre livelli, composto da tre programmi che sono i tre livelli di programmazione.\nCommand.com è il livello più alto, poichè interpreta i comandi, quali\nIBMDOS è il secondo livello più alto, poichè riceve le istruzioni da command.com e le passa a IBMBIO\nIBMBIO è il livello di programmazione più basso.\nTutte le sub-routine che fanno parte di questi tre programmi, scritte da Microsoft e IBM, eseguono funzioni specifiche nel computer e sull'hardware. Per esempio, ci sono sub-routine che sono state scritte solo per controllare il monitor video e come i dati vengono mostrati sullo schermo. Queste routine sono utilizzate per muovere il cursore, modificare il colore dei caratteri, le dimensioni dei caratteri, stampare stringhe di testo e molte altre funzioni legate al monitor. Altre sub-routine controllano ogni elemento hardware connesso al sistema. Queste sub-routine usate dal sistema operativo richiedono molte istruzioni 8088 per portare a termine un determinato compito e sono molto complesse. Fortunatamente, IBM e MicroSoft hanno creato un sistema che permette al programmatore di utilizzare tutte queste sub-routine, testate e provate, nei suoi programmi. Questo rende la programmazione un po' più facile per un programmatore, visto che spenderà meno tempo a scrivere codice che è già stato scritto da programmatori più esperti. Il processore 8088 ha una istruzione speciale chiamata INT che esegue ognuna di queste sub-routine dall'interno di un programma. INT è ciò che chiamiamo un interrupt software, perchè, quando eseguito da un programma, il microprocessore si interrompe per un momento mentre la sub-routine in IBMBIO o IBMDOS viene eseguita. Terminata la sub-routine, il microprocessore può tornare al programma originario e completare le istruzioni in esso contenute.\nSET DELLE ISTRUZIONI ( 41 ISTRUZIONI ) PER IL PROCESSORE 8088\nPer avere una lista completa delle istruzioni per i processori Intel, vai alla pagina Intel http://developer.intel.com/products/processor/manuals/index.htm e scarica i manuali:\nMOV ( sposta )\nMOV AX,BX\tregister,register Da BX ad AX ( 16 bit )\nMOV AH,BL\tregister,register Da BL ad AH ( 8 bit )\nMOV AX,5\timmediate: 5 ( 0005 ) in AX ( 16 bit )\nMOV AH,5\timmediate: 5 ( 05 ) in AH ( 8 bit )\nCi sono sei modalità differenti di indirizzamento per il trasferimento dei dati, nel processore 8088. Un numero compreso tra parentesi quadre:\nrappresenta una locazione di memoria con offset pari al numero specificato. 150, nel nostro esempio, è una cella di memoria che si trova all'offset 150 della RAM e che contiene 8 bit o un byte di dati.\nDiretto: i valori memorizzati alle locazioni con offset 200 and 201 vengono spostati nel registro AX. Ogni locazione di memoria contiene un byte, ma visto che questo è uno spostamento a 16 bit, verranno utilizzate due locazioni di memoria. I registri AL e AH assumeranno i valori 200 e 201, rispettivamente.\nDiretto: il valore memorizzato alla locazione di memoria con offset 450 viene spostato nel registro AL. Dal momento in cui questo è uno spostamento a 8 bit, verrà utilizzata una sola locazione di memoria. Se ad essere compreso tra parentesi quadre è un registro:\nallora la CPU andrà alla locazione di memoria specificata dal registro. Questo è uno spostamento indiretto: il valore memorizzato alla locazione di memoria puntata dal registro BX verrà trasferito in AX. Supponiamo che il registro BX contenga il valore 345. Il registro AX assumerà il valore delle locazioni di memoria 345 e 346 ( 16 bits ).\nindiretto: come l'esempio precedente, ma qui abbiamo un trasferimento a 8 bit. Lo locazione può essere espressa anche con una espressione ( base + spiazzamento ):\n5 viene aggiunto a BX per avere la locazione esatta\noppure ( base + indice + spiazzamento )\nMOV AX,[BX+SI+8]\t\t( 16 bit )\nMOV AL,[BX+SI+6]\t\t( 8 bit )\nQui abbiamo due spiazzamenti. La CPU calcolerà la locazione di memoria sommando il valore presente in BX, il valore presente in SI ed il valore specificato.\nPUSH, POP ( operazioni sullo stack )\nCi sono quattro registri generali ( general purpose register ) che possiamo utilizzare nei nostri programmi per trasferire dei dati su cui eseguire operazioni aritmetiche. Questo numero sembrerebbe sufficiente, ma nel programmare vi troverete spesso ad invocare qualche registro in più su cui operare. Le istruzioni PUSH e POP permettono di salvare il contenuto dei registri su cui state lavorando in quell'area di memoria chiamata stack. Una volta salvato il contenuto di un registro, è possibile operare su quel registro con altre operazioni e, una volta terminato il lavoro, ripristinare il valore originale del registro recuperandolo dallo stack. Lo stack è un'area che si trova al top del segmento di memoria in cui si trova il nostro programma. La prima locazione disponibile si trova all'offset FFFE, mentre le successive locazioni si trovano nelle celle di memoria inferiori.\nQuesta istruzione salverà il contenuto del registro CX nell'area stack della memoria. Lo stack funziona con il meccanismo: "First In and Last Out" ( il primo dato immesso è l'ultimo ad uscire ). Occorre fare molta attenzione a come utilizziamo le istruzioni PUSH e POP. Pensate allo stack come ad una pila di piatti: il primo piatto che mettiamo nello stack dovrà essere l'ultimo ad essere estratto. Se tenteremo di estrarre i piatti in qualsiasi altro ordine, la pila molto probabilmente crollerà. Facendo la stessa cosa all'interno del nostro programma, sarà il computer ad andare in crash. Vediamo un esempio:\nLe prime tre istruzioni utilizzano i registri CX e AX. Dopo aver incrementato il registro CX ( INC CX ), vengono eseguite due istruzioni PUSH ( PUSH CX e PUSH AX ) che salvano i valori correnti di CX e AX nello stack. CX e AX possono ora essere utilizzati per altre istruzioni, per essere poi reimpostati ai valori iniziali con le istruzioni POP AX e POP CX, in questo ordine esatto. Quando si utilizza l'istruzione INT, la CPU modifica il registro IP per puntare ad una sub-routine che si trova nel chip ROM bios o nel programma IBMDOS.SYS residente nella memoria bassa. Queste sub-routine utilizzano tutti i registri general purpose che erano utilizzati dal nostro programma prima che venisse lanciata l'istruzione INT. IBM dice che tutti i registri vengono salvati prima dell'esecuzione di una istruzione INT, ad eccezione del registro AX. Suggerisco, quindi, di eseguire un PUSH AX ogni volta che utilizziamo l'istruzione INT.\nXCHG ( scambio )\nIN, OUT ( input/output )\nADD ( addizione )\nIl valore presente in BX è sommato al valore presente in AX e il risultato viene salvato in AX. Tutte le istruzioni aritmetiche devono coinvolgere i registri general purpose ( generali ). Per sommare due numeri, inserite un numero in un registro e l'altro numero o in un altro registro o in una locazione di memoria. Io suggesrisco il registro AX, perchè è un registro ottimizzato per svolgere la funzione di accumulatore nelle operazioni aritmetiche. Le altre forme dell'istruzione ADD con le quali è meglio familiarizzare sono:\nIl valore contenuto nelle locazioni di memoria con offset 120 e 121 viene sommato al valore contenuto nel registro AX e il risultato viene salvato in AX. La CPU calcola automaticamente il numero di locazioni di memoria da utilizzare. In questo caso, il registro specificato nella istruzione è un registro a 16 bit e, di conseguenza, richiede due byte di dati dalla memoria, a partire dalla locazione con offset 120 ( il byte meno significativo ) e comprendente la locazione successiva, con offset 121 ( il byte più significativo ).\nIl numero, in formato esadecimale, 5 viene sommato al valore contenuto nel registro AX e il risultato viene salvato in AX. In questo esempio, il numero 5 è in realtà: 0005 in notazione esadecimale. Nell'assemblare un programma, è possibile omettere le sequenze iniziali di zero, come ho fatto io qui. Ci penserà debug.exe ad inserirli.\nADD AH, AL\t# istruzione ADD a 8 bit\nADD AH, \t# istruzione ADD a 8 bit\nQuesto piccolo programma sommerà due numeri esadecimali e memorizzerà il risultato in memoria.\nMOV AX, 3405\t# 3405 nel registro AX\nMOV , AX\t# 3405 nella locazione di memoria con offset 200\nMOV AX, 5892\t# 5892 nel registro AX\nADD AX, \t# somma AX e la locazione di memoria 200\nMOV , AX\t# salva il risultato ( in AX ) nella locazione 202\nINT 20\t\t# fine del programma\nLe locazioni di memoria 202 e 203 conterranno ora il risultato finale: 97 ( byte meno significativo ) 8C ( byte più significativo ), cioè: 8C97.\nINC ( incrementa )\nL'instruzione INC incrementa il valore del registro specificato o della locazione di memoria specificata di 1. E' equivalente a\nIn questo esempio, il registro AX viene incrementato di uno ogni volta che l'istruzione viene eseguita. Se il registro AX contiene il numero 0005, l'istruzione porterà il valore contenuto a 0006. INC può essere utilizzato tutte le volte che si desidera.\nINC BL\t# INC eseguito su un registro a 8 bit\nQuando l'istruzione INC viene eseguita su una locazione di memoria, specificare il tipo di dato:\nincrementa il valore del byte alla locazione 150\nincrementa il valore della WORD alla locazione 150\nSUB ( sottrazione )\nAX=0005  BX=0000  CX=0004  DX=0000\nSP=FFEE  BP=0000  SI=0000  DI=0000\nIl valore a 16 bit contenuto nel registro sorgente ( CX ), 0004 nel nostro esempio, viene sottratto dal valore contenuto nel registro di destinazione ( AX ), 0009 nel nostro esempio, e il risultato viene memorizzato nel registro di destinazione ( AX ), 0005 nel nostro esempio.\nIl valore contenuto nella locazione di memoria specificata ( 160 ) viene sottratto dal valore contenuto nel registro specificato ( CX ) e il risultato viene memorizzato nel registro di destinazione ( CX ).\nIl valore a 8 bit presente nel registro sorgente ( AH ) viene sottratto dal valore contenuto nel registro di destinazione ( AL ) e il risultato viene memorizzato nel registro di destinazione ( AL ). Con l'istruzione SUB, se il risultato della sottrazione è pari a zero, viene impostata la FLAG zero.\nDEC ( decrementa )\nDecrementa di uno il valore contenuto nel registro o nella locazione di memoria specificata ogni volta che viene eseguito. E' equivalente a\ndove 1 viene detratto dal valore contenuto nel registro specificato ( AX ). DEC può essere utilizzato tutte le volte che lo si desidera. Nel nostro esempio, il registro AX verrà decrementato di uno ogni volta che l'istruzione verrà eseguita. Per cui, se il registro AX contiene il numero 0005, l'istruzione DEC detrarrà uno dal quel valore e il registro AX assumerà il valore 0004.\nDecrementa di uno il valore contenuto nel registro specificato a 8 bit.\nDecrementa il valore a un byte memorizzato nella locazione di memoria specificata. Nel nostro esempio, il byte presente all'offset 250 verrà decrementato di uno.\nDecrementa la word ( numero a 16 bit ) memorizzata all'offset specificato. Nel nostro esempio, il numero a 16 bit memorizzato a partire dalla locazione di memoria con offset 200 verrà decrementato di uno. Con l'istruzione DEC, se il risultato della sottrazione è pari a zero, viene impostata la FLAG zero.\nNEG ( negazione - two's complement )\nNega il valore presente nel registro specificato o nella locazione di memoria specificata. Il valore negativo di un numero è rappresentato nella notazione cosiddetta two's complement.\n8000\t10000000_00000000\t# is negative\n100\t00000001_00000000\t# is positive\n7FFF\t01111111_11111111\t# is positive\nFFFF\t11111111_11111111\t# is negative\nCome vengono rappresentati, in un processore della famiglia 8086, i numeri negativi? I processori 80x86 utilizzano la notazione two's complement. In questo sistema, il bit più alto di un numero ( bit H.O. ) è un bit di segno. Se il bit H.O. è zero, il numero è positivo; se il bit H.O. è uno, il numero è negativo. L'algoritmo per convertire un numero positivo nel suo valore negativo, in notazione two's complement, è la seguente:\ninvertire tutti i bit del numero ( operatore NOT logico ).\n0000_0101\t# +5\n1111_1010\t# NOT +5\naggiungere uno al risultato dell'inversione.\n1111_1011\t# ( NOT +5 ) +1 = -5\nPerchè un sistema tanto complesso? La risposta deriva dall'hardware: con il sistema two's complement, la gran parte delle operazioni aritmetiche risultano semplificate. Proviamo, ad esempio, a sommare\nIl risultato è zero. E lo stesso avviene nel sistema two's complement:\nTutti i bit sono a zero, eccetto il nono bit ( carry ) che, in un sistema a 8 bit, viene comunque scartato. Quindi, se ignoriamo quel nono bit ( carry ), abbiamo che ogni volta che sommiamo due valori complementari ( con segno ) utilizzando la notazione two's complement, avremo sempre il risultato corretto. Questo significa che potremo utilizzare lo stesso hardware per le operazioni aritmetiche, sia che operiamo su numeri senza segno, sia che operiamo su numeri con segno.\nCMP ( compara )\nCMP AX, 56\nL'istruzione CMP ( compara ) compara i numeri presenti nei registri o nelle locazioni di memoria specificati e, se i due numeri sono uguali, imposta la FLAG zero. Questa istruzione viene utilizzata per modificare il flusso di un programma in base allo stato della FLAG zero.\nMUL ( moltiplicazione )\nDIV ( divisione )\nPer prevedere i risultati delle istruzioni logiche, occorre prima convertire i valori esadecimali in numeri binari e eseguire la funzione logica su ciascuno dei bit. Le istruzioni logiche servono per la manipolazione dei bit nei registri specificati. Esse rappresentano il solo modo per impostare o azzerare i bit all'interno di qualsiasi registro. Ecco la tabella per convertire numeri in notazione decimale, esadecimale e binaria:\nPer convertire un numero in notazione esadecimale in binario, sostituisci ciascuna cifra esadecimale con i quattro bit del corrispondente binario:\n1010 ( A ) 0101 ( 5 ) = 10100101\nPer convertire un numero in notazione binaria in esadecimale, sostituisci ciascun gruppo di 4 bit con il corrispondente esadecimale:\n8 ( 1000 ) D ( 1101 ) = 8D\nSe il registro AX contiene il numero in notazione esadecimale 45A1, la sua rappresentazione binaria sarà, quindi:\n0100 ( 4 ) 0101 ( 5 ) 1010 ( A ) 0001 ( 1 ) = 0100010110100001\nNOT ( complement )\nL'istruzione NOT inverte il valore di ciascun bit del registro o della locazione di memoria specificata ( one's complement ).\n0011010101000101 ( 3545 )\n1100101010111010 ( NOT AX se AX = 3545 )\nAND - Utilizzata per azzerare singoli bit.\nL'operatore logico AND è un operatore binario, nel senso che accetta DUE operandi, e opera su coppie di singoli bit. Confrontando due bit, AND restituisce:\n1\t1\t= 1\n1\t0\t= 0\n0\t1\t= 0\n0\t0\t= 0\nSi dice che l'operatore AND forza i bit a zero, perchè il solo caso in cui restituisce 1 è quando ambedue i bit in ingresso hanno valore 1. Nel nostro esempio, dobbiamo confrontare, utilizzando l'operatore logico AND, i numeri, in notazione esadecimale: 3545 ( registro AX ) e 1722. L'operatore AND effettuerà il confronto su ciascuna coppia di bit, a partire dal bit meno significativo:\n0001011100100010 ( 1722 )\n0001010100000000 ( 1500 )\nQuindi, ora il registro AX contiene il risultato dell'operazione logica effettuata: 1500 ( in notazione esadecimale ).\nOR ( inclusive or ) - Utilizzata per impostare ( 1 ) singoli bit.\nL'operatore logico OR ( inclusive-OR ) è un operatore binario, nel senso che accetta DUE operandi, e opera su coppie di singoli bit. Confrontando due bit, OR restituisce:\n1\t0\t= 1\n0\t1\t= 1\nXOR ( exclusive or ) - Utilizzata per azzerare un registro.\nChe succede se in realtà voi volete sapere se, in una coppia di bit, uno dei due, ma non ambedue, sono impostati a 1? Confrontando due bit, XOR restituisce:\n1\t1\t= 0\nTEST ( test bit )\nSHL, SHR ( shift left/right )\nROL, ROR ( rotate left/right )\nIstruzioni di stringa\nMOVS ( sposta stringa )\nCMPS ( compara stringa )\nSCAS ( scansiona stringa )\nLODS ( carica da stringa )\nSTOS ( salva in stringa )\nIstruzioni di trasferimento di controllo\nCALL ( vai ad una sub-routine )\nRET ( return da una sub-routine )\nJMP ( jump, salta )\nJZ, JNZ ( jump condizionato )\nLOOP ( iterazione )\nLOOPNE ( iterazione condizionata )\nINT ( interrupt )\nIRET ( return da interrupt )\nControllo del processore\nCLC, STC ( azzera, imposta flag )\nHLT ( ferma la CPU )\nDB ( define byte )\nThe ascii string "My name is John Smith" is stored in memory, starting at offset 150. DB and DW is used to define a data type in memory. The ascii string have to be enclosed in quotations. You can also use DB to load specific byte of data into memory. The following example would load ascii letters A B C D E into memory at the specified memory location.\nXXXX:0150 DB 41 42 43 44 45\nDW ( define word )\nSHL ( shift left )\nSposta i bit dell'operando di destinazione ( il primo ) specificato ( AX, AL ) verso sinistra, per tante volte quanto specificato nell'operando sorgente. Il registro CL può contenere il contatore. I bit spinti fuori dal registro ( o dalla locazione di memoria ) vengono persi, mentre dall'altra parte vengono inseriti degli zero.\nIn questo esempio, AX contiene il valore 0001, e l'istruzione SHL specifica di eseguire un solo shift verso sinistra:\n0000000000000001\t# 0001\n0000000000000010\t# SHL, 1\nOra AX contiene il valore 0002. I bit spostati ed eccedenti i limiti dell'operando di destinazione ( i bit più significativi, nel caso di uno SHIFT LEFT, i meno significativi, nel caso di uno SHIFT RIGHT ) vengono spostati nella flag CF, per poi essere scaricati. Al termine di un'operazione SHL o SHR, la flag CF contiene l'ultimo bit spostato e scaricato dall'operando di destinazione.\nMOV AX, 8000\t# 1000000000000000\nSHL AX,1\t# 0000000000000000\nL'operando di destinazione può essere un registro o una locazione di memoria.\nSHR ( shift right )\nMOV AX,0001\t# 0000000000000001\nSHR AX,1\t# 0000000000000000\nCome SHL, ma SHR sposta i bit dell'operando di destinazione ( il primo ) specificato ( AX ) verso destra.\nROL ( rotate left )\nRuota i bit dell'operando di destinazione ( il primo ) specificato ( AX, AL ) verso sinistra, per tante volte quanto specificato nell'operando sorgente. Il registro CL può contenere il contatore. Tutti i bit spinti fuori dall'operando di destinazione vengono fatti rientrare dall'altro lato ( rotazione ): il bit più significativo diventa il bit meno significativo.\nMOV AX,8000\t# 1000000000000000\nROL AX,1\t# 0000000000000001\nROL AX,1\t# 0000000000000010\nROR ( rotate right )\nCome ROL, ma ROR ruota i bit verso destra: il bit meno significativo diventa il bit più significativo.\nMOV AX,1\t# 0000000000000001\nROR AX,1\t# 1000000000000000\nChiama una sub-routine in un'altra parte del programma. La sub-routine deve terminare con l'istruzione RET ( return ). Completata la sub-routine, il programma torna alla istruzione immediatamente successiva alla istruzione CALL.\nAX=0000  BX=0000  CX=0000  DX=0000\nDS=0D28  ES=0D28  SS=0D28  CS=0D28\nIP=0100   NV UP EI PL NZ NA PO NC\n0D28:0100 0000          ADD     [BX+SI],AL\tDS:0000=CD\n0D28:0100 mov ax, a\n0D28:0103 call 120\n0D28:0106 add ax, 1\n0D28:0109 int 3\n0D28:0120 mov bx, 10\nAX=000B  BX=0010  CX=0000  DX=0000\nIP=0109   NV UP EI PL NZ NA PO NC\n0D28:0109 CC            INT     3\nIn questo esempio, verifichiamo che tutti i registri siano a zero ( R ), poi inseriamo le istruzioni con il comando A:\nscrivo A ( in notazione esadecimale ) nel registro AX\nchiamo la sub-routine alla locazione di memoria 120\nalla locazione di memoria 120, scrivo 10 ( in notazione esadecimale ) nel registro BX\ntorno ( RET ) al programma principale\nsommo 1 al valore contenuto in AX\nchiudo il programma\nL'istruzione INT 3 stampa, alla chiusura del programma, i valori contenuti nei registri: AX contiene B ( A + 1 ), BX contiene 10 ( come da istruzione contenuta nella sub-routine ).\nJUMP trasferisce il controllo ad una diversa istruzione ( interrompendo il flusso di istruzioni ), senza registrare alcuna informazione di ritorno. L'operando di destinazione può essere un valore, un registro ( general purpose register ) o una locazione di memoria.\nQuesta è un'istruzione JUMP senza condizioni: viene eseguita l'istruzione contenuta alla locazione specificata ed il programma continua da quella nuova locazione.\nQuesta è un'istruzione JUMP condizionata: verrà eseguita se la flag zero è impostata ( JZ = jump if zero ). L'indirizzo specificato deve trovarsi entro 80 ( in notazione esadecimale ) byte dall'istruzione JZ ( short jump ). L'istruzione deve seguire immediatamente una istruzione aritmetica, logica o INT.\nQuesta è un'istruzione JUMP condizionata: verrà eseguita se la flag zero non è impostata ( JZ = jump if not zero ). L'indirizzo specificato deve trovarsi entro 80 ( in notazione esadecimale ) byte dall'istruzione JZ ( short jump ). L'istruzione deve seguire immediatamente una istruzione aritmetica, logica o INT.\nL'istruzione LOOP viene utilizzata per eseguire una istruzione o un gruppo di istruzioni più di una volta. Simile alle istruzioni del linguaggio di programmazione basic FOR e NEXT. Il contatore del loop deve essere messo nel registro CX prima dell'istruzione LOOP. Ogni volta in cui viene eseguita l'istruzione LOOP, il registro CX viene decrementato di uno. Arrivato a zero il contatore CX, il programma torna alla istruzione immediatamente successiva all'istruzione LOOP.\n0D28:0100 mov cx,a\n0D28:0103 mov ax,a\n0D28:0106 add ax,2\n0D28:0109 sub ax,1\n0D28:010C loop 106\n0D28:010E int 3\nAX=0014  BX=0000  CX=0000  DX=0000\nIP=010E   NV UP EI PL NZ NA PE NC\n0D28:010E CC            INT     3\nIn questo esempio, come prima istruzione impostiamo il contatore ( il registro CX ) al valore A ( in notazione esadecimale ). Poi, inseriamo il valore A ( in notazione esadecimale ) nel registro AX. Le due istruzioni seguenti eseguono una addizione ( AX + 2 ) ed una sottrazione ( AX -1 ). L'istruzione LOOP 106, eseguirà le operazioni che iniziano dalla locazione 106 per un numero di volte pari al valore contenuto nel registro CX ( A, cioè dieci volte ). Le istruzioni interessate dal LOOP sono quindi:\nGrazie all'ultima istruzione, INT 3, il programma termina stampando il valore contenuto nei registri. Il contatore CX è tornato a zero. Il registro AX contiene il valore 14 ( in notazione esadecimale ), che è il risultato dell'addizione e della sottrazione eseguite 10 volte.\nOra, prova a prevedere cosa farà il prossimo programma. Utilizza il comando A per assemblarlo. Una volta assemblato, uitlizza il comando G per farlo eseguire.\nGli INTERRUPT e le eccezioni sono eventi che indicano la presenza di una condizione, nel sistema, nel processore, o all'interno del programma in esecuzione, che richiede l'attenzione del processore. Il loro compito è, normalmente, di trasferire forzatamente l'esecuzione dal programma correntemente attivo ad una routine chiamata "interrupt handler" ( gestore di interrupt ) o "exception handler". Quando il processore riceve un segnale di interrupt o rileva un'eccezione, il programma in esecuzione in quel momento viene sospeso, mentre il processore esegue la routine relativa all'interrupt o all'eccezione. Completata l'esecuzione della routine, il processore riprende l'esecuzione del programma o della procedura ( task ) precedentemente interrotta ( a meno che l'interrupt ne abbia comportato la chiusura ).\nL'istruzione INT viene utilizzata per accedere al sistema operativo ( IBMBIO e IBMDOS ) così come al chip ROM bios della scheda madre. XX è un numero compreso tra 1 e 256 che rappresenta una delle chiamate INT possibili. Ci sono due tipi di interrupt nei computer IBM:\nl'interrupt hardware, prodotto da una periferica esterna, quali la stampante o un disco. Come fa la CPU ad accorgersi che una periferica sta inviando un segnale di richiesta di interrupt? Visto che un segnale di interrupt può arrivare in qualsiasi momento, la CPU effettua una verifica ad ogni istruzione eseguita. Intercettato un segnale di interrupt, la CPU invia alla periferica che ha generato il segnale di interrupt un segnale di "ricevuta". La periferica che ha generato il segnale di interrupt risponde, a sua volta, inviando alla CPU, sul bus dei dati, un numero a otto bit, chiamato numero di interrupt. Questo processo è chiamato hand-shaking.\nl'interrupt software, prodotto proprio dalla istruzione INT. Il processore 8086 tratta l'interrupt software nello stesso modo in cui tratta lo stesso tipo di interrupt generato dalla periferica hardware.\nL'interrupt 3, per esempio, utilizzato da noi durante l'esecuzione dei programmi inseriti in questa guida, è chiamato "Breakpoint Exception" ( BP ). Viene utilizzato normalmente dai debugger per generare un breakpoint, sostituendo il primo byte di una istruzione ( opcode ) con un opcode per l'istruzione INT 3. Sebbene il registro IP punti all'istruzione immediatamente successiva all'istruzione di breakpoint, lo stato del programma resta essenzialmente immodificato perchè l'istruzione INT 3 non coinvolge alcun registro o locazione di memoria. Il debugger, quindi, può riprendere l'esecuzione del programma interrotto sostituendo l'istruzione INT 3 che aveva innescato il breakpoint con l'opcode originale e decrementando il contenuto, salvato, del registro IP.\nL'istruzione INT lanciata da un programma ( interrupt software ) è simile alla istruzione CALL nel fatto che, quando eseguita, il programma salta ad una diversa locazione di memoria per eseguire l'istruzione seguente e, quando il gruppo di istruzioni saranno completate, l'istruzione RET riporterà il controllo al programma originario. Le istruzioni INT si trovano all'interno del IBMBIO e del IBMDOS o, in qualche caso, nella ROM. Queste istruzioni sono, fondamentalmente, sub-routine allocate nel sistema operativo. Se guardate i PIN del microprocessore 8088, noterete uno dei PIN marcato INTR ( PIN 18 ), chiamato richiesta di interrupt. Quando questo PIN diventa attivo ( 1 ), il microprocessore interrompe l'esecuzione di qualsiasi altro programma ed esegue un piccolo programma di interrupt al fine di gestire la richiesta di quel determinato interrupt. Un solo PIN dedicato agli interrupt hardware non è sufficiente, vista la gran quantità di periferiche connesse al bus di sistema tramite gli slot di I/O ed i chip di supporto della scheda madre. Il chip di controllo degli interrupt 8259 ( interrupt controller chip ) è un circuito integrato programmabile davvero complesso che gestisce fino a 8 differenti interrupt su XT ( IBM Personal Computer eXtended Technology, commercializzato dal 1983 al 1987 ) e fino a 15 interrupt con due 8259 a cascata su AT ( IBM Personal Computer Advanced Technology, in cui i PIN IRQ 8–15 del secondo 8259 venivano connessi al master 8259 attraverso il PIN IRQ 2: ecco perchè gli interrupt disponibili erano 15 e non 16 ). Il chip 8259 è, fondamentalmente, un encoder che ha 8 input e un output. L'output ( PIN 17, INT ) è collegato al PIN INTR del microprocessore e fino a 8 periferiche possono essere collegate ai PIN di input ( IR0 .. IR7 ). Se più di un PIN di input diventa attivo nello stesso tempo, sarà eseguito l'input con il numero di priorità più basso, seguito dal secondo e via di seguito. Il chip 8259 espande così il numero di segnali di interrupt possibili da uno a otto. Vediamo ora cosa accade quando si preme un tasto sulla tastiera. Siete al prompt di DOS e state per inserire il comando DIR in una directory del disco attivo. Il processore, nel frattempo, sta eseguendo molte istruzioni contenute nel programma command.com. Uno degli eventi che deve sicuramente avvenire è l'aggiornamento dell'orario. Come sapete, il computer tiene traccia dell'orario del giorno, contando una determinata quantità di impulsi prodotti dal circuito generatore dell'ora. L'orario del giorno è davvero importante, a tal punto da essere connesso al PIN IRQ0 del chip 8259, PIN che ha la più alta priorità.\nIRQ0 INT 8\nOgni secondo, il circuito orario produce un segnale attivo sulla linea IRQ0. L'output del chip 8259 diventa attivo, attivando a sua volta il PIN INTR del microprocessore, interrompendo la CPU. La CPU termina le istruzioni in esecuzione in quel momento e salva i contenuti di tutti i registri, eseguendo un PUSH di ogni valore sullo stack, allocato nell'area alta della memoria RAM. Terminato il salvataggio dei contenuti dei registri, la CPU invia un segnale al controller 8259, informandolo di essere pronta a servire la richiesta, inserendo un segnale attivo sulla linea del bus di controllo INTA ( interrupt acknowledge ). Il controller 8259, appena ricevuto il segnale INTA prodotto dalla CPU, inserisce l'indirizzo di memoria a 8 bit ( 1 byte ) sul bus dei dati, utilizzato dalla CPU per identificare l'indirizzo di una sub-routine allocata all'interno del chip ROM bios, routine che contiene tutte le istruzioni per aggiornare i byte dell'orario del giorno memorizzati nell'area bassa della memoria RAM. Una volta aggiornato l'orario, la CPU esegue un POP sullo stack per recuperare i dati dei registri. Recuperati i dati, il puntatore delle istruzioni viene reimpostato per puntare al programma in esecuzione prima dell'interrupt e il programma riprende ad eseguire le sue istruzioni come se nulla fosse mai avvenuto. La tabella seguente mostra i livelli di priorità di ciascun input e lo scopo di ciascun interrupt:\nIRQ0\tINT 8\tOrario\nIRQ1\tINT 9\tTastiera\nIRQ2\tINT A\tAdattatore grafico\nIRQ3\tINT B\tAdattatore seriale secondario\nIRQ4\tINT C\tAdattatore seriale primario\nIRQ5\tINT D\tHard disk ( XT )\nIRQ6\tINT E\tFloppy disk\nIRQ7\tINT F\tStampante\nLa interrupt vector table è una tabella di indirizzi, con dimensione di 1000 byte, allocata nello spazio di memoria compreso tra le locazioni:\nl'area più bassa della memoria ram. Ogni voce di questa tabella occupa quattro byte e contiene l'indirizzo di memoria di una subroutine, espresso nel formato:\ncorrispondente ad una delle 256 possibili richieste di interrupt. Ogni indirizzo di interrupt è composto da 4 byte ed è memorizzato in ordine inverso ( LSB MSB, byte meno significativo + byte più significativo ). Per calcolare l'offset di una subroutine di interrupt, basta moltiplicare il numero dell'interrupt per 4.\nQuando viene richiesto un interrupt, la periferica richiedente mette sul bus dei dati un byte, che servirà ad identificare l'offset corretto in cui trovare l'indirizzo reale della sub-routine di quell'interrupt. Se viene richiesto un interrupt 0, il numero 0 viene messo sul bus dei dati, e la CPU, per avere l'indirizzo della subroutine dell'interrupt 0, andrà all'offset 0000. Questa è la stampa della mia tabella degli interrupt su una macchina 286:\nPer trovare l'indirizzo per INT 10, moltiplicate il numero dell'interrupt per 4\nI quattro byte che iniziano all'offset 0040 è l'indirizzo, scritto in ordine inverso, della sobroutine per INT 10.\n2D 1B C4 03\n03C4:1B2D\t# sobroutine per INT 10\nPer utilizzare un interrupt software, il programmatore deve impostare il programma prima di eseguire l'istruzione INT. Molti interrupt richiedono determinati valori affinchè la sub-routine venga eseguita. In altre parole, tutti gli interrupt si aspettano un input dal vostro programma e l'input deve essere memorizzato in uno specifico registro. L'istruzione che esegue la sub-routine appropriata è:\ndove xx è un numero compreso nell'intervallo 0 - FF, a seconda dell'interrupt che si vuole eseguire. Per utilizzare questi interrupt bisogna far riferimento all'elenco degli interrupt fornito da IBM e MicroSoft. Riscrivere questo elenco in questo libro sarebbe poco pratico, a causa delle dimensioni dei contenuti. Vi raccomando di acquistare un libro che contenga almeno gli interrupt più comuni. Questo libro, tuttavia, elenca alcuni degli interrupt più usati nella programmazione. Gli interrupt usati nella programmazione accedono sia a IBMDOS che a IBMBIO ed è meglio conoscere la differenza prima di utilizzarli. In generale, ogni interrupt che chiama IBMDOS avrà una esecuzione leggermente più lenta perchè, come visto precedentemente, IBMDOS è un programma di livello superiore e le sue istruzioni vengono trasmesse a IBMBIO, programma che attiverà i chip. Passare prima da IBMDOS rallenta il programma, ma, visto che siamo ad un livello alto, l'input richiesto al vostro programma è molto più facile da sviluppare e impostare. Gli interrupt DOS vengono spesso chiamati DOS CALLS e sono tutti:\nGli altri tipi di interrupt vengono spesso definiti interrupt BIOS perchè chiamano sub-routine presenti in IBMBIO o nella ROM BIOS. Questi interrupt sono molto più veloci e possono incrementare considerevolmente le prestazioni del programma, ma, essendo il BIO un programma di basso livello, il programmatore deve conoscere l'hardware del sistema e fornire, attraverso il suo programma, un input più articolato. La seguente tabella mostra gli interrupt più utilizzati:\nBIOS CALLS DOS CALLS\n10 | 14 20 | 24\n11 | 15 21 | 25\n12 | 16 22 | 26\n13 | 17 23 | 27\n14 | 19 1A |\nTutti questi interrupt sono sub-routine annidate in uno dei programmi del sistema operativo, e sono ulteriormente suddivise in sub-routine più piccole chiamate funzioni e sub-funzioni. Per esempio, INT 10 ha un totale di 55 funzioni e sub-funzioni all'interno del codice. Per accedere ad una funzione di interrupt, il programma che chiede l'interrupt deve avere il numero della funzione all'interno del registro AH. Il registro AH viene utilizzato esclusivamente per le funzioni. Vediamo un esempio che utilizza l'istruzione INT. Il nostro programma metterà il cursore in mezzo allo schermo e stamperà a video le lettere CPI. Uno sguardo alla lista degli interrupt ci rivela due interrupt necessari a questo programma: INT 10 funzione 2 ( imposta la posizione del cursore ) e INT 10 funzione 9 ( scrive il carattere e gli attributi nella posizione del cursore ).\n0100 Mov AH,2\t# 02 spostamento del cursore\n0102 Mov DH,C\t# fila ( coordinata y )\n0104 Mov DL,24\t# colonna ( coordinata x )\n0106 Mov BH,0\t# numero di pagina\n0108 INT 10\t# chiamata a IBMBIO\n010A Mov AH,9\t# 09 stampa a video di carattere\n010C Mov AL,43\t# codice ASCII del carattere, in esadecimale ( C )\n010E Mov BH,0\t# numero di pagina\n0110 Mov BL,17\t# byte di attributo\n0112 Mov CX,1\t# numero di caratteri da scrivere\n0115 Int 10\t# chiamata a IBMBIO\nIl byte di attributo imposta i colori di sfondo e del carattere da stampare a video. R,G,B sono i canali rosso, verde e blu del tubo catodico ( CRT ). Un bit impostato ( 1 ) attiverà il canale specificato, producendo il colore desiderato.\nB7\tB6\tB5\tB4\tB3\tB2\tB1\tB0\n|x|\t|R|\t|G|\t|B|\t|x|\t|R|\t|G|\t|B|\nBITS 0-1-2\t# controllo colore di testo\nBITS 4-5-6\t# controllo colore di sfondo\nBIT 3\t# intensità ( 1=alta )\nBIT 7\t# intermittenza ( 1=intermittente )\nPer produrre, quindi, uno sfondo blu e il testo bianco, dobbiamo impostare a 1 i bit 4-2-1-0, mentre tutti gli altri bit devono essere a zero. Il numero binario risultante sarà:\n00010111\t# 17 in esadecimale\nOra continuiamo la stesura del programma. Abbiamo stampato la lettera C. Ora dobbiamo ripetere due volte la stessa procedura, per stampare le lettere P e I, preoccupandoci solo di far avanzare il cursore:\n0117 Mov AH,2\t# 02 spostamento del cursore\n0119 Mov DH,C\t# fila ( coordinata y )\n011B Mov DL,25\t# colonna ( coordinata x ): avanzamento di uno\n011D Mov BH,0\t# numero di pagina\n011F Int 10\t# chiamata a IBMBIO\n0121 Mov AH,9\t# 09 stampa a video di carattere\n0123 Mov AL,50\t# codice ASCII del carattere, in esadecimale ( P )\n0125 Mov BH,0\t# numero di pagina\n0127 Mov BL,17\t# byte di attributo\n0129 Mov CX,1\t# numero di caratteri da scrivere\n012C Int 10\t# chiamata a IBMBIO\n012E Mov AH,2\t# 02 spostamento del cursore\n0130 Mov DH,C\t# fila ( coordinata y )\n0132 Mov DL,26\t# colonna ( coordinata x ): avanzamento di uno\n0134 Mov BH,0\t# numero di pagina\n0136 Int 10\t# chiamata a IBMBIO\n0138 Mov AH,9\t# 09 stampa a video di carattere\n013A Mov AL,49\t# codice ASCII del carattere, in esadecimale ( I )\n013C Mov BH,0\t# numero di pagina\n013E Mov BL,17\t# byte di attributo\n0140 Mov CX,1\t# numero di caratteri da scrivere\n0143 Int 10\t# chiamata a IBMBIO\n0145 Int 20\t# End the program\nQuesto programma è stato scritto in questo modo per mostrare come, prima di eseguire una istruzione INT, sia necessario impostare i registri. L'ordine in cui i registri vengono impostati non è importante, ma è importante che ciò avvenga prima dell'istruzione INT. Un programma molto più efficiente sarà mostrato tra breve e ci permetterà di vedere alcune istruzioni avanzate dei processori 8088.\nIl processo di boot inizia quando il circuito di reset, allocato nel chip di generazione dell'orologio, invia un impulso al PIN 21 ( RESET ) della CPU 8088. Questo PIN è connesso al circuito di reset del microprocessore e il suo compito è di impostare un indirizzo di reset sul bus degli indirizzi A0 ... A20. A questo punto, viene eseguita una istruzione di JUMP all'indirizzo F000:FFF0 della ROM bios che punta alla prima istruzione del BIOS. Il programma della ROM bios è di circa 8 Kbyte e controlla tutto l'hardware presente nel sistema. I chip della CPU vengono inizializzati con i valori appropriati di default per controllare cose quali il monitor, i dischi, le porte delle stampanti e la tastiera. Terminata l'inizializzazione dell'hardware, il programma esegue una diagnostica completa: ROM, RAM etc., per completare il processo definito POST o Power On Self Test. Se non è rilevato alcun errore durante l'esecuzione del POST, il BIOS cerca il disco A e verifica se la porta è aperta o chiusa. Nel caso la porta fosse chiusa, la testina si posizionerebbe sulla posizione track 0, head 0, sector 0 del disco A, e il boot loader verrebbe trasferito in memoria. Se la porta al disco A fosse, invece, aperta, il processo di boot continuerebbe dal disco C, se esistente. A questo punto, il boot loader prende il controllo della CPU, eseguendo una serie di istruzioni e cercando nella directory del disco i file di sistema, IBMBIO e IBM DOS. Se questi file sono presenti nel disco, vengono caricati nella memoria bassa, in quell'ordine, insieme ad eventuali driver elencati nell'istruzione\ndel config.sys. Il controllo della CPU passa quindi a IBMDOS, per completare il processo di boot, con il caricamento in memoria di COMMAND.COM ( nello spazio di memoria subito dopo IBMDOS ). Il processo di boot è completo quando il controllo della CPU passa a COMMAND.COM e sullo schermo appare il prompt. Se i file di sistema non vengono trovati, o se sono corrotti, il boot loader mostrerà il messaggio familiare " DISK BOOT FAILURE ".\nEseguire un debug\nDebug è molto utile quando si desideri eseguire un debug di un programma o eseguire un programma per la prima volta. Quando un programma contiene un errore nel codice e viene eseguito, molto probabilmente il computer lo chiuderà costringendoti a riavviare la macchina. Questo può essere evitato eseguendo solo una piccola parte alla volta del programma per verificare che sia OK e che faccia esattamente ciò che ti aspettavi che facesse. Sotto c'è il codice di un piccolo programma e la spiegazione di come io utilizzerei l'indirizzo ed i parametri di breakpoint per eseguire un debug. Assembla il seguente programma e salvalo su disco con il nome BREAK.COM. Per utilizzare i breakpoint dovrai caricare il programma dal disco ogni volta in cui il programma terminerà normalmente.\nCS:0100 MOV AH,9\nCS:0109 INT 21\nCS:010F JMP 107\nCS:0111 INT 20\nQuesto è un semplicissimo programma che stampa un breve messaggio a video. Il messaggio si trova alla locazione di memoria con offset 200 e l'ultimo carattere del messaggio è il dollaro ( $ ) o 24 in esadecimale. Il codice 24 ( $ ) viene chiamato end of file marker ( EOF ), o marcatore di fine file. Una volta stampato a video il messaggio, il programma continua, restando in attesa di un input da parte nostra. Appena avremo premuto un tasto, il codice ascii corrispondente al tasto premuto viene memorizzato nel registro AL. Il programma, quindi, verificherà se il tasto premuto corrisponde al codice ascii 1B. Se sì, il programma termina e il controllo torna a DOS. In caso contrario, il programma salta all'istruzione che richiede un altro input. Il programma terminerà solo quando sarà premuto il tasto ESCAPE. Ho suddiviso il programma in blocchi di codice, ciascuno dei quali esegue un compito preciso. Le funzioni in questo programma sono:\nstampare un messaggio a video ( offset 100-106 )\nNOTA - In questo caso, vediamo che l'istruzione che viene lanciata è INT 21. La funzione dell'interrupt 21 chiamata è la numero 9: DOS 1+ - WRITE STRING TO STANDARD OUTPUT ( STAMPA UNA STRINGA SU STANDARD OUTPUT ). La funzione 9 dell'interrupt 21 richiede di impostare i valori di due registri:\nI registri DS:DX devono, quindi, contenere l'indirizzo di memoria dal quale inizia la stringa di testo da stampare. Questa stringa deve terminare con il simbolo del dollaro ( codice esadecimale 24 ). Nel nostro esempio, la stringa di testo si trova alla locazione di memoria 200. Quindi, prima di eseguire questo codice, occorrerà inserire una stringa, per esempio: "Ciao", alla locazione di memoria 200:\nF 200 L 5 "Ciao$"\nleggere un carattere dalla tastiera ( offset 107-10A )\nNOTA - Anche in questo secondo blocco di codice, viene lanciata l'istruzione INT 21, ma in questo caso la funzione chiamata è la numero 7: DOS 1+ - DIRECT CHARACTER INPUT, WITHOUT ECHO ( MEMORIZZA UN CARATTERE DI INPUT, SENZA ECHO ). La funzione 7 dell'interrupt 21 vuole impostato il registro:\ne memorizza il carattere di input nel registro AL. FINE NOTA\nVerificare se l'input è il carattere 1B ( offset 10B ).\nSe l'input è il carattere 1B, chiudere il programma, saltando all'istruzione contenuta nella locazione di memoria 111 ( offset 10D ).\nAltrimenti, torna all'istruzione contenuta all'offset 107, chiedendo all'utente un altro input ( offset 10F ).\nFine del programma ( offset 111 ).\nPer eseguire il debug, useremo i comandi P e G per avanzare nel programma una istruzione o un interrupt alla volta oppure eseguendo un gruppo di istruzioni fino ad uno specifico offset. Dopo aver spezzato il programma in gruppi di istruzioni che eseguono ciascuno una funzione, eseguiremo questi gruppi di istruzioni per verificare che funzionino correttamente. Per i prossimi passi, fai riferimento al programma appena scritto. Le istruzioni da 0100 a 0106 stampano a video un messaggio ( istruzione INT 21 ). Per testare questo gruppo di istruzioni, usiamo il comando G per impostare un break-point, utilizzato da debug per controllare il flusso del programma. Impostiamo il registro IP a 0100 e scriviamo:\nG=100 107 ( return )\nQuesto comando eseguirà tutte le istruzioni a partire dall'offset 0100 fino all'offset 0107. Se il codice in questa parte del programma è valido, verrà stampato a video il messaggio memorizzato a partire dall'offset 200. Il comando G, infatti, viene usato con un indirizzo di memoria di partenza, seguito dall'indirizzo finale dove il microprocessore interrompe l'esecuzione del programma ( break-point ). Se, per una qualsiasi ragione, il messaggio non venisse stampato a video, potremmo concludere che c'è un problema nel codice, in quel gruppo di istruzioni. Per eseguire una istruzione alla volta, reimposteremo il registro IP a 0100 ed inseriremo il comando P.\nP ( return )\nIl comando P è molto simile al comando T ( trace ), poichè esegue una istruzione alla volta. Tuttavia, il comando P esegue anche l'istruzione INT, al contrario del comando T. Si può utilizzare uno dei due comandi indifferentemente, visto che l'istruzione da testare è un'istruzione MOV, che sarà verificata controllando il valore del registro AH subito dopo l'esecuzione dell'istruzione MOV AH,9. Sia il comando P che il comando T mostrano i contenuti dei registri, dopo aver eseguito una istruzione. Il registro IP viene aggiornato dal microprocessore, puntando alla istruzione seguente del programma. Se questa istruzione appena eseguita funziona in modo appropriato, digitiamo:\nCon questo comando, verrà eseguita l'istruzione MOV successiva, all'offset 102. Ancora una volta, verificheremo i registri per controllare l'esito dell'istruzione MOV DX,200. Ora digitiamo:\ne verrà eseguita l'istruzione INT all'offset 0105, che stamperà a video il messaggio. Questo processo dovrà proseguire fino a quando avremo trovato il codice che non funziona correttamente nel programma, utilizzando i comandi:\nG=100,107\t# test del codice di messaggio\nG=107,10B\t# test dell'input da tastiera\nG=10B,10F\t# test dell'istruzione CMP ( compare )\nG=10F\t\t# test dell'istruzione JUMP\nCreazione di un programma COM che stampa a video un file di testo\nCon un editor di testo, crea un file di testo che vuoi stampare a video, chiudendolo con un simbolo dollaro ( $ ). All'inizio del file di testo, inserisci una riga di spazi, così da conservare 80 byte da utilizzare più tardi. Salva il documento come\nCarica in memoria il file con il comando:\nVerifica, con il comando Dump ( D ) che il file sia stato caricato in memoria correttamente. Noterai che i primi 50 ( in notazione esadecimale ) byte contengono il valore 20, che è il codice ASCII per lo spazio. Durante la creazione del file, infatti, hai inserito 80 spazi, e 80 è la notazione decimale del numero esadecimale 50. Ora assembla il seguente codice, a partire dall'offset 100:\n0D28:0100 mov ah,09\n0D28:0102 mov dx,150\n0D28:0105 int 21\n0D28:0107 int 20\nTrova l'indirizzo di offset della fine del file ( $ ) e verifica che il registro CX sia impostato con il numero di byte. Con il comando name ( n ), inserisci il nuovo nome del file con la nuova estensione .COM ( TEXT.COM ).\nInserisci il comando w per scrivere il nuovo file su disco.\nChiudi debug e scrivi il nome del file COM che hai appena assemblato. Se il programma funziona, il file verrà stampato a video.\nCome assemblare un programma COM\nDopo aver convertito il flusso delle istruzioni del tuo programma in codice scritto su file, puoi iniziare il processo di assemblaggio utilizzando Debug come assembler. Al prompt di debug ( - ), inserisci il comando:\nA 100 (return)\nSulla tua sinistra, vedrai il valore della locazione di memoria, espresso con la coppia: segment-offset. Si parte sempre dall'offset 100 perchè i primi 100 byte sono riservati al Program Segment Prefix ( 0-FF ). Se alla locazione di memoria con offset 100 non c'è alcuna istruzione valida, il programma bloccherà il computer, mandandolo in crash, obbligandoti a riavviare il sistema. Segui questi passi per assemblare un programma che stamperà a video il codice ASCII in una modalità differente.\n0D28:0100 mov cx,7d0\n0D28:0103 mov ah,09\n0D28:0105 mov bh,50\n0D28:0107 mov bl,dl\n0D28:0109 mov al,dl\n0D28:010B int 10\n0D28:010D inc dl\n0D28:010F loop 0103\n0D28:0111 int 20\nQuando si trova in modalità assemble ( A ), debug calcola automaticamente l'offset per l'istruzione successiva. Una volta inserita la prima istruzione all'offset 100, debug mostra l'offset successivo disponibile per l'istruzione successiva. In questo programma, l'istruzione MOV CX,7D0 richiede 3 byte di memoria, a partire dall'offset 0100 fino all'offset 0102. L'istruzione successiva, quindi, ( MOV AH,09 ) partirà dall'offset 0103. L'ultima istruzione del programma deve essere INT 20, per chiudere correttamente il programma. Si tratta di un'istruzione a due byte, allocata, nel nostro esempio, all'offset 0111. Si può evincere l'utilizzo di due byte osservando l'offset successivo: 0113. Per salvare il programma su disco, occorre nominarlo:\n-n a:\_ascii.com (return)\nimpostare la grandezza del file nel registro CX e lanciare il comando Write ( W ).\nIn quest'ultimo esempio, abbiamo inserito il nome del file con il percorso completo, in modo da salvare il file in una directory specifica del disco ( A:\_ ). La dimensione del file è stata calcolata sottraendo 100 ( l'offset di inizio del programma ) da 113 ( il byte successivo all'ultima istruzione ). Se tutto è corretto, possiamo chiudere debug ( q ) e lanciare il programma dal prompt di DOS, dopo aver controllato la corretta creazione del file:\nIl file deve essere di 19 byte ( in notazione decimale ) o 13 byte ( in notazione esadecimale ). Per lanciare il programma:\nPrelevare i byte di stato della tastiera\nL'interrupt 16, funzione 2 restituisce un byte di informazioni relative allo stato di determinati tasti della tastiera, e più precisamente: Invio, Caps lock, Num lock, Scroll lock, Alt, Ctrl, Left shift e right shift. L'interrupt indica se il tasto è attivo, impostando un bit nel registro AL. Se un bit è attivo ( uno ), il tasto è premuto; se il bit è a zero, il tasto corrispondente non è premuto.\nNOTA - A questo punto, la guida si interrompe, con il testo: Still under construction ( ancora in costruzione ). FINE NOTA\nI found only this chapter of this text.\nVotes sent by users for this text: 236\nLearning GNU C ( IT )\nDEBUG / ASSEMBLY TUTORIAL Disclaimer: this link points to content provided by other sites.