Componenti di base del linguaggio assembly e struttura delle istruzioni. Formato dei dati e struttura dei comandi in linguaggio assembly. Nella disciplina "Programmazione di sistema"

Introduzione.

Viene chiamata la lingua in cui è scritto il programma sorgente Entrata lingua e la lingua in cui viene tradotta per l'esecuzione da parte del responsabile del trattamento nei giorni liberi lingua. Viene chiamato il processo di conversione della lingua di input nella lingua di output trasmissione. Poiché i processori sono in grado di eseguire programmi in linguaggio macchina binario, che non viene utilizzato per la programmazione, è necessaria la traduzione di tutti i programmi sorgente. Conosciuto due strade trasmissioni: compilazione e interpretazione.

A compilazione il programma sorgente viene prima tradotto completamente in un programma equivalente nella lingua di output, chiamato oggetto programma e poi eseguito. Questo processo viene implementato utilizzando uno speciale programmi, chiamato compilatore. Viene chiamato un compilatore per il quale il linguaggio di input è una forma simbolica di rappresentazione del linguaggio macchina (di output) dei codici binari assemblatore.

A interpretazioni Ogni riga di testo nel programma sorgente viene analizzata (interpretata) e il comando in essa specificato viene immediatamente eseguito. L'implementazione di questo metodo è affidata a programma interprete. L'interpretazione richiede molto tempo. Per aumentare la sua efficienza, invece di elaborare ogni riga, l'interprete prima le converte tutte squadra stringhe in caratteri (

). La sequenza di simboli generata viene utilizzata per eseguire le funzioni assegnate al programma originale.

Il linguaggio assembly discusso di seguito viene implementato utilizzando la compilazione.

Caratteristiche della lingua.

Caratteristiche principali dell'assemblatore:

● invece dei codici binari, il linguaggio utilizza nomi simbolici - mnemonici. Ad esempio, per il comando di addizione (

) vengono utilizzati i mnemonici

Sottrazioni (

moltiplicazione (

Divisioni (

ecc. I nomi simbolici vengono utilizzati anche per indirizzare le celle di memoria. Per programmare in linguaggio assembly, invece che codici e indirizzi binari, è necessario conoscere solo nomi simbolici che l'assembler traduce in codici binari;

ogni affermazione corrisponde un comando della macchina(codice), ovvero esiste una corrispondenza biunivoca tra comandi macchina e operatori in un programma in linguaggio assembly;

● la lingua fornisce l'accesso a tutti gli oggetti e squadre. I linguaggi di alto livello non hanno questa capacità. Ad esempio, il linguaggio assembly consente di controllare i bit del registro flag e il linguaggio di alto livello (ad esempio,

) non ha questa capacità. Si noti che i linguaggi di programmazione dei sistemi (ad esempio C) occupano spesso una posizione intermedia. In termini di accessibilità, sono più vicini al linguaggio assembly, ma hanno la sintassi di un linguaggio di alto livello;

● linguaggio assembly non è una lingua universale. Ogni gruppo specifico di microprocessori ha il proprio assemblatore. I linguaggi di alto livello non presentano questo inconveniente.

A differenza dei linguaggi di alto livello, la scrittura e il debug di un programma in linguaggio assembly richiedono molto tempo. Nonostante ciò, il linguaggio assembly ha ricevuto ampio utilizzo a causa delle seguenti circostanze:

● un programma scritto in linguaggio assembly è di dimensioni significativamente più piccole e viene eseguito molto più velocemente di un programma scritto in un linguaggio di alto livello. Per alcune applicazioni, questi indicatori svolgono un ruolo primario, ad esempio molti programmi di sistema (compresi i compilatori), programmi per carte di credito, telefono cellulare, driver di dispositivo, ecc.;

● alcune procedure richiedono accesso completo all'hardware, cosa che di solito è impossibile da fare in un linguaggio di alto livello. Questo caso include gli interrupt e i gestori degli interrupt nei sistemi operativi, nonché i controller dei dispositivi nei sistemi embedded in tempo reale.

Nella maggior parte dei programmi, solo una piccola percentuale del codice totale è responsabile di gran parte del tempo di esecuzione del programma. In genere, l'1% del programma è responsabile del 50% del tempo di esecuzione e il 10% del programma è responsabile del 90% del tempo di esecuzione. Pertanto, per scrivere un programma specifico in condizioni reali, vengono utilizzati sia l'assembler che uno dei linguaggi di alto livello.

Formato operatore in linguaggio assembly.

Un programma in linguaggio assembly è un elenco di comandi (istruzioni, frasi), ciascuno dei quali occupa una riga separata e contiene quattro campi: un campo etichetta, un campo operazione, un campo operando e un campo commento. Ogni campo ha una colonna separata.

Campo etichetta.

Al campo etichetta è assegnata la colonna 1. L'etichetta è un nome simbolico, o identificatore, indirizzi memoria. È necessario affinché tu possa:

● effettuare una transizione condizionale o incondizionata al comando;

● ottenere l'accesso al luogo in cui sono archiviati i dati.

Tali dichiarazioni sono dotate di un'etichetta. Per indicare un nome si utilizzano lettere (maiuscole) dell'alfabeto inglese e numeri. Il nome deve avere una lettera all'inizio e un separatore di due punti alla fine. L'etichetta dei due punti può essere scritta su una riga separata e il codice operativo può essere scritto sulla riga successiva nella colonna 2, il che semplifica il lavoro del compilatore. L'assenza dei due punti non permette di distinguere un'etichetta da un codice operazione se si trovano su righe separate.

In alcune versioni del linguaggio assembly, i due punti vengono posizionati solo dopo le etichette delle istruzioni, non dopo le etichette dei dati, e la lunghezza dell'etichetta può essere limitata a 6 o 8 caratteri.

Non dovrebbero esserci nomi identici nel campo dell'etichetta, poiché l'etichetta è associata agli indirizzi di comando. Se durante l'esecuzione del programma non è necessario richiamare un comando o dati dalla memoria, il campo etichetta rimane vuoto.

Campo codice operazione.

Questo campo contiene il codice mnemonico per un comando o pseudo-comando (vedi sotto). Il codice mnemonico del comando viene scelto dagli sviluppatori del linguaggio. Nel linguaggio assembly

viene selezionato il mnemonico per caricare un registro dalla memoria

) e per salvare il contenuto del registro in memoria - un mnemonico

). Nei linguaggi assembly

per entrambe le operazioni è possibile utilizzare rispettivamente lo stesso nome

Se la scelta dei nomi mnemonici può essere arbitraria, allora la necessità di utilizzare due istruzioni macchina è determinata dall'architettura del processore

La mnemonica dei registri dipende anche dalla versione dell'assembler (Tabella 5.2.1).

Campo dell'operando.

Qui si trova Informazioni aggiuntive, necessario per eseguire l'operazione. Nel campo degli operandi per i comandi di salto viene indicato l'indirizzo a cui deve essere effettuato il salto, nonché gli indirizzi e i registri che sono operandi per il comando della macchina. Ad esempio, diamo operandi che possono essere utilizzati per processori a 8 bit

● dati numerici,

presentati in diversi sistemi numerici. Per indicare il sistema numerico utilizzato, la costante è seguita da una delle lettere latine: B,

Di conseguenza, i sistemi numerici binari, ottali, esadecimali e decimali (

Non è necessario scriverlo). Se la prima cifra di un numero esadecimale è A, B, C,

Quindi viene aggiunto uno 0 (zero) insignificante davanti;

● codici dei registri interni del microprocessore e delle celle di memoria

M (fonti o destinatari di informazioni) sotto forma delle lettere A, B, C,

M o i loro indirizzi in qualsiasi sistema numerico (ad esempio, 10B - indirizzo di registro

nel sistema binario);

● identificatori,

per il registro delle coppie di aeromobili,

Le prime lettere sono B,

N; per una coppia di accumulatori e registri di funzioni -

; per il contatore del programma -

;per il puntatore dello stack -

● etichette che indicano gli indirizzi degli operandi o delle istruzioni successive nel condizionale

(se la condizione è soddisfatta) e transizioni incondizionate. Ad esempio, l'operando M1 nel comando

indica la necessità di una transizione incondizionata al comando, il cui indirizzo nel campo dell'etichetta è contrassegnato con l'identificatore M1;

● espressioni,

che sono costruiti collegando i dati discussi sopra utilizzando operatori aritmetici e logici. Tieni presente che il metodo per riservare lo spazio dati dipende dalla versione della lingua. Sviluppatori di linguaggio assembly per

Definire la parola) e successivamente inserita Opzione alternativa.

che era nel linguaggio dei processori fin dall'inizio

Nella versione linguistica

usato

Definire una costante).

I processori elaborano operandi di diversa lunghezza. Per definirlo, gli sviluppatori assemblatori hanno preso decisioni diverse, ad esempio:

II registri di diversa lunghezza hanno nomi diversi: EAX - per posizionare operandi a 32 bit (tipo

); AX - per 16 bit (tipo

e AN - per 8 bit (tipo

● per i processori

I suffissi vengono aggiunti a ciascun codice operazione: suffisso

Per tipo

; suffisso ".B" per il tipo

vengono utilizzati codici operativi diversi per operandi di diversa lunghezza, ad esempio per caricare un byte, una mezza parola (

) e parole in un registro a 64 bit utilizzando i codici operativi

rispettivamente.

Campo commenti.

Questo campo fornisce spiegazioni sulle azioni del programma. I commenti non influenzano il funzionamento del programma e sono destinati agli esseri umani. Potrebbero essere necessari per modificare un programma, che senza tali commenti potrebbe risultare del tutto incomprensibile anche ai programmatori esperti. Un commento inizia con un simbolo e viene utilizzato per spiegare e documentare i programmi. Il carattere iniziale di un commento può essere:

● punto e virgola (;) nelle lingue destinate ai responsabili del trattamento dell'azienda

Punto esclamativo(!) nelle lingue per

Ogni riga di commento separata è preceduta da un carattere iniziale.

Pseudo-comandi (direttive).

Nel linguaggio assembly esistono due tipi principali di comandi:

di base istruzioni che sono l'equivalente del codice macchina del processore. Questi comandi eseguono tutte le elaborazioni previste dal programma;

pseudo-comandi O direttive, progettato per servire il processo di traduzione di un programma in un linguaggio di combinazione di codici. Come esempio nella tabella. 5.2.2 mostra alcuni pseudo-comandi dell'assembler

per la famiglia

.

Durante la programmazione ci sono situazioni in cui, secondo l'algoritmo, la stessa catena di comandi deve essere ripetuta più volte. Per uscire da questa situazione puoi:

● scrivere la sequenza di comandi richiesta ogni volta che si verifica. Questo approccio porta ad un aumento del volume del programma;

● organizzare questa sequenza in una procedura (subroutine) e richiamarla se necessario. Questo output ha i suoi svantaggi: ogni volta è necessario eseguire un comando di chiamata di procedura speciale e un comando di ritorno che, se la sequenza è breve e utilizzata frequentemente, può ridurre notevolmente la velocità del programma.

Il più semplice e metodo efficace la ripetizione ripetuta di una catena di comandi consiste nell'usare macro, che può essere rappresentato come uno pseudo-comando progettato per ritradurre un gruppo di comandi spesso presenti in un programma.

Una macro, o macrocomando, è caratterizzata da tre aspetti: macrodefinizione, macroinversione e macroestensione.

Definizione macro

Questa è una designazione per una sequenza ripetuta ripetutamente di comandi di programma, utilizzata come riferimento nel testo del programma.

La definizione della macro ha la seguente struttura:

Elenco delle espressioni; Definizione macro

Nella struttura della macrodefinizione data si possono distinguere tre parti:

● titolo

macro, incluso il nome

Pseudo-comando

e una serie di parametri;

● contrassegnato da punti corpo macro;

● squadra

la laurea

definizioni macro.

Il set di parametri di definizione macro contiene un elenco di tutti i parametri forniti nel campo dell'operando per il gruppo di istruzioni selezionato. Se questi parametri sono stati forniti in precedenza nel programma, non è necessario indicarli nell'intestazione della definizione della macro.

Per ricomporre il gruppo selezionato di comandi viene utilizzato un appello costituito dal nome

comandi macro ed elenco di parametri con altri valori.

Quando l'assemblatore incontra una definizione di macro durante il processo di compilazione, la memorizza nella tabella delle definizioni di macro. Alle successive apparizioni nel programma del nome (

) di una macro, l'assembler lo sostituisce con il corpo della macro.

Viene chiamato l'utilizzo di un nome macro come codice operativo macro-inversione(chiamata macro) e sostituendolo con il corpo della macro - espansione macro.

Se un programma viene rappresentato come una sequenza di caratteri (lettere, numeri, spazi, segni di punteggiatura e ritorni a capo per passare a una nuova riga), l'espansione della macro consiste nel sostituire alcune catene di questa sequenza con altre catene.

L'espansione delle macro avviene durante il processo di assemblaggio, non durante l'esecuzione del programma. Sono assegnati metodi per manipolare stringhe di caratteri significa macro.

Viene eseguito il processo di assemblaggio in due passaggi:

● Al primo passaggio, tutte le definizioni delle macro vengono conservate e le chiamate alle macro vengono espanse. In questo caso, il programma originale viene letto e convertito in un programma in cui tutte le definizioni di macro vengono rimosse e ogni chiamata di macro viene sostituita dal corpo della macro;

● il secondo passaggio elabora il programma risultante senza macro.

Macro con parametri.

Per lavorare con sequenze ripetute di comandi, i cui parametri possono assumere valori diversi, vengono fornite le definizioni delle macro:

● con effettivo parametri che vengono inseriti nel campo dell'operando della chiamata macro;

● con formale parametri. Durante l'espansione della macro, ogni parametro formale che appare nel corpo della macro viene sostituito dal corrispondente parametro effettivo.

utilizzando macro con parametri.

Il programma 1 contiene due sequenze simili di comandi, che differiscono in quanto la prima scambia P e

E il secondo

Il programma 2 include una macro con due parametri formali P1 e P2. Durante l'espansione della macro, ogni carattere P1 all'interno del corpo della macro viene sostituito dal primo parametro effettivo (P,

), e il simbolo P2 viene sostituito dal secondo parametro attuale (

) dal programma n. 1. Nella chiamata macro

il programma 2 è contrassegnato: P,

Il primo parametro effettivo,

Secondo parametro effettivo.

Programma 1

Programma 2

MOV EBX,Q MOV EAX,Pl

MOV Q,EAX MOV EBX,P2

MOV P,EBX MOV P2,EAX

Funzionalità estese.

Diamo un'occhiata ad alcune funzionalità linguistiche avanzate

Se una macro contenente un comando di salto condizionale e un'etichetta a cui passare viene richiamata due o più volte, l'etichetta verrà duplicata (problema di etichetta duplicata), causando un errore. Pertanto ad ogni chiamata viene assegnata un'etichetta separata come parametro (da parte del programmatore). Nella lingua

l'etichetta è dichiarata locale (

) e grazie a funzionalità avanzate, l'assemblatore genera automaticamente un'etichetta diversa ogni volta che la macro viene espansa.

consente di definire macro all'interno di altre macro. Questa funzionalità avanzata è molto utile in combinazione con il collegamento condizionale di un programma. Consideriamo

SE PAROLE GT 16 M2 MACRO

La macro M2 può essere definita in entrambe le parti dell'istruzione

Tuttavia, la definizione dipende dal processore su cui è assemblato il programma: 16 bit o 32 bit. Se M1 non viene chiamata, la macro M2 non verrà affatto definita.

Un'altra funzionalità avanzata è che le macro possono chiamare altre macro, incluse se stesse: ricorsivo chiamata. In quest'ultimo caso, per evitare un ciclo infinito, la macro deve passarsi un parametro che cambia ad ogni espansione, e inoltre controllo questo parametro e termina la ricorsione quando il parametro raggiunge un determinato valore.

Sull'uso dei mezzi macro in assembler.

Quando si utilizzano le macro, l'assemblatore deve essere in grado di svolgere due funzioni: salvare le definizioni delle macro E espandere le sfide macro.

Salvataggio delle definizioni delle macro.

Tutti i nomi delle macro vengono memorizzati in una tabella. Ogni nome è accompagnato da un puntatore alla macro corrispondente in modo da poterla richiamare in caso di necessità. Alcuni assemblatori hanno una tabella separata per i nomi delle macro, altri hanno una tabella generale in cui, insieme ai nomi delle macro, si trovano tutte le istruzioni e le direttive della macchina.

Quando si incontra una macro durante l'assemblaggio è creato:

nuovo elemento della tabella con il nome della macro, il numero di parametri e un puntatore ad un'altra tabella di definizione della macro dove verrà memorizzato il corpo della macro;

● elenco formale parametri.

Il corpo della macro, che è semplicemente una stringa di caratteri, viene quindi letto e memorizzato nella tabella di definizione della macro. I parametri formali che si verificano nel corpo del ciclo sono contrassegnati carattere speciale.

Rappresentazione interna di una macro

dall'esempio precedente per il programma 2 (p. 244) è:

MOV EAX, MOV EBX, MOV MOV e

dove il punto e virgola viene utilizzato come carattere di ritorno a capo e la e commerciale & viene utilizzata come carattere di parametro formale.

Estensione delle chiamate macro.

Ogni volta che viene incontrata una definizione di macro durante l'assemblaggio, viene memorizzata nella tabella delle macro. Quando viene richiamata una macro, l'assembler interrompe temporaneamente la lettura dei dati di input dal dispositivo di input e inizia a leggere il corpo della macro memorizzata. I parametri formali estratti dal corpo della macro vengono sostituiti da parametri effettivi e forniti dalla chiamata. I parametri e commerciale e prima consentono all'assemblatore di riconoscerli.

Nonostante esistano molte versioni dell'assemblatore, i processi di assemblaggio hanno caratteristiche comuni e sono simili sotto molti aspetti. Il funzionamento di un assemblatore a due passaggi è discusso di seguito.

Assemblatore a due passaggi.

Un programma è composto da una serie di istruzioni. Pertanto, sembrerebbe che durante l'assemblaggio sia possibile utilizzare la seguente sequenza di azioni:

● tradurlo in linguaggio macchina;

● trasferire il codice macchina risultante in un file e la parte corrispondente dell'elenco in un altro file;

● ripetere le procedure elencate fino a tradurre l'intero programma.

Tuttavia, questo approccio non è efficace. Un esempio è il cosiddetto problema collegamento in avanti. Se la prima istruzione è un salto all'istruzione P, situata alla fine del programma, l'assemblatore non può tradurla. Deve prima determinare l'indirizzo dell'operatore P e per fare questo deve leggere l'intero programma. Viene richiamata ogni lettura completa del programma sorgente passaggio. Mostriamo come risolvere il problema del collegamento lookahead utilizzando due passaggi:

al primo passaggio dovresti raccogliere e memorizzare tutte le definizioni dei simboli (comprese le etichette) nella tabella e, al secondo passaggio, leggere e assemblare ciascun operatore. Questo metodo è relativamente semplice, ma un secondo passaggio attraverso il programma originale richiede tempo aggiuntivo dedicato alle operazioni di I/O;

● al primo passaggio dovresti convertire il programma in una forma intermedia e salvarlo in una tabella, ed eseguire il secondo passaggio non secondo il programma originale, ma secondo la tabella. Questo metodo di assemblaggio fa risparmiare tempo, poiché il secondo passaggio non esegue operazioni di I/O.

Primo passaggio.

Gol di primo passaggio- costruire una tabella dei simboli. Come notato in precedenza, un altro obiettivo del primo passaggio è preservare tutte le definizioni delle macro ed espandere le chiamate man mano che appaiono. Di conseguenza, sia la definizione del simbolo che l'espansione della macro avvengono in un unico passaggio. Il simbolo può essere uno dei due etichetta, O Senso, a cui viene assegnato un nome specifico utilizzando la direttiva -you:

;Valore: dimensione del buffer

Assegnando un significato ai nomi simbolici nel campo dell'etichetta del comando, l'assemblatore specifica essenzialmente gli indirizzi che ogni comando avrà durante l'esecuzione del programma. A questo scopo l'assemblatore immagazzina durante il processo di assemblaggio contatore dell'indirizzo dell'istruzione(

) come variabile speciale. All'inizio del primo passaggio, il valore della variabile speciale viene impostato su 0 e incrementato dopo ogni comando elaborato della lunghezza di quel comando. Come esempio nella tabella. 5.2.3 mostra un frammento di programma che indica la lunghezza dei comandi e i valori dei contatori. Al primo passaggio vengono generate le tabelle nomi simbolici, direttive E codici operativi, e se necessario letterale tavolo. Un valore letterale è una costante per la quale l'assemblatore riserva automaticamente memoria. Notiamo subito che i processori moderni contengono istruzioni con indirizzi immediati, quindi i loro assemblatori non supportano i letterali.

Tabella dei nomi dei simboli

contiene un elemento per ciascun nome (Tabella 5.2.4). Ogni elemento della tabella dei nomi simbolici contiene il nome stesso (o un puntatore ad esso), il suo valore numerico e talvolta alcune informazioni aggiuntive, che possono includere:

● la lunghezza del campo dati associato al simbolo;

● bit di riallocazione della memoria (che indicano se il valore di un simbolo cambia se il programma viene caricato ad un indirizzo diverso da quello previsto dall'assemblatore);

● informazioni sulla possibilità di accesso al simbolo dall'esterno della procedura.

I nomi simbolici sono etichette. Possono essere specificati utilizzando gli operatori (ad esempio,

Tabella direttiva.

Questa tabella elenca tutte le direttive, o pseudo-comandi, che si incontrano durante l'assemblaggio di un programma.

Tabella dei codici operazione.

Per ciascun codice operazione, la tabella ha colonne separate: designazione del codice operazione, operando 1, operando 2, valore esadecimale del codice operazione, lunghezza del comando e tipo di comando (Tabella 5.2.5). I codici operazione sono divisi in gruppi a seconda del numero e del tipo di operandi. Il tipo di comando determina il numero del gruppo e specifica la procedura richiamata per elaborare tutti i comandi in quel gruppo.

Secondo passaggio.

Gol del secondo passaggio- creazione di un programma oggetto e stampa, se necessario, del protocollo assembly; informazioni di output necessarie al linker per collegare procedure assemblate in momenti diversi in un unico file eseguibile.

Nel secondo passaggio (come nel primo), le righe contenenti le istruzioni vengono lette ed elaborate una per una. L'operatore originale e l'operatore di output ne derivano in formato esadecimale oggetto Il codice può essere stampato o inserito in un buffer per la stampa successiva. Dopo aver reimpostato il contatore dell'indirizzo del comando, viene richiamato il comando successiva affermazione.

Il programma sorgente può contenere errori, ad esempio:

il simbolo dato non è definito o è definito più di una volta;

● l'opcode è rappresentato da un nome non valido (a causa di un errore di battitura), non ha abbastanza operandi, o ha troppi operandi;

● nessun operatore

Alcuni assemblatori possono rilevare un simbolo indefinito e sostituirlo. Tuttavia, nella maggior parte dei casi, quando incontra un'istruzione di errore, l'assemblatore visualizza un messaggio di errore sullo schermo e tenta di continuare il processo di assemblaggio.

Articoli dedicati al linguaggio assembly.

UNIVERSITÀ NAZIONALE DELL'UZBEKISTAN INNOLATA A MIRZO ULUGBEK

FACOLTÀ DI TECNOLOGIA INFORMATICA

Sull'argomento: analisi semantica di un file EXE.

Completato:

Taskent 2003.

Prefazione.

Linguaggio assembly e struttura dei comandi.

Struttura del file EXE (analisi semantica).

Struttura del file COM.

Il principio di azione e diffusione del virus.

Disassemblatore.

Programmi.

Prefazione

La professione di un programmatore è straordinaria e unica. Al giorno d’oggi è impossibile immaginare la scienza e la vita senza le tecnologie più recenti. Non si può fare a meno di tutto ciò che riguarda l’attività umana informatica. E questo contribuisce al suo elevato sviluppo e perfezione. Sebbene lo sviluppo dei personal computer sia iniziato non molto tempo fa, durante questo periodo sono stati compiuti passi colossali nei prodotti software e questi prodotti saranno ampiamente utilizzati per molto tempo. Il campo della conoscenza informatica ha subito un'esplosione, così come la tecnologia corrispondente. Se non prendiamo in considerazione il lato commerciale, allora possiamo dire che non ci sono estranei in questo settore dell'attività professionale. Molte persone sviluppano programmi non a scopo di lucro o di reddito, ma di propria spontanea volontà, per passione. Naturalmente, ciò non dovrebbe influire sulla qualità del programma e in questo settore, per così dire, c'è concorrenza e richiesta di esecuzione di qualità, lavoro stabile e rispetto di tutti i requisiti moderni. Qui vale anche la pena notare la comparsa dei microprocessori negli anni '60, che sostituirono un gran numero di set di lampade. Esistono alcuni tipi di microprocessori molto diversi tra loro. Questi microprocessori differiscono tra loro per la profondità di bit e i comandi di sistema integrati. I più comuni sono: Intel, IBM, Celeron, AMD, ecc. Tutti questi processori sono legati all'architettura avanzata dei processori Intel. La diffusione dei microcomputer ha causato una riconsiderazione dell'atteggiamento nei confronti del linguaggio assembly per due ragioni principali. Innanzitutto, i programmi scritti in linguaggio assembly richiedono memoria e tempo di esecuzione notevolmente inferiori. In secondo luogo, la conoscenza del linguaggio assembly e del codice macchina risultante fornisce una comprensione dell'architettura della macchina, cosa che difficilmente si ottiene quando si lavora in un linguaggio di alto livello. Sebbene la maggior parte dei professionisti del software sviluppi in linguaggi di alto livello come Pascal, C o Delphi, che è più semplice quando si scrivono programmi, i più potenti ed efficaci Software scritto interamente o parzialmente in linguaggio assembly. I linguaggi di alto livello sono stati progettati per evitare speciali caratteristiche tecniche computer specifici. E il linguaggio assembly, a sua volta, è progettato per le specifiche specifiche del processore. Pertanto, per scrivere un programma in linguaggio assembly per un computer specifico, è necessario conoscerne l'architettura. In questi giorni, il punto di vista principale prodotto softwareè un file EXE. Considerando lati positivi Ciò significa che l'autore del programma può essere sicuro della sua integrità. Ma spesso questo è tutt’altro che vero. C'è anche un disassemblatore. Utilizzando un disassemblatore, puoi scoprire interruzioni e codici di programma. Non sarà difficile per una persona esperta in assembler rifare l'intero programma secondo i suoi gusti. Forse è qui che sorge il problema più insolubile: il virus. Perché le persone scrivono un virus? Alcuni fanno questa domanda con sorpresa, altri con rabbia, ma nonostante ciò continuano ad esserci persone interessate a questo compito non dal punto di vista di causare danni, ma come interesse per la programmazione del sistema. I virus sono scritti da ragioni varie. Ad alcune persone piacciono le chiamate di sistema, altri migliorano la loro conoscenza dell'assemblatore. Cercherò di spiegare tutto questo nel mio lavoro del corso. Inoltre dice non solo della struttura del file EXE ma anche del linguaggio assembly.

^ Linguaggio assemblativo.

È interessante seguire, dalla comparsa dei primi computer fino ai giorni nostri, la trasformazione delle idee dei programmatori sul linguaggio assembly.

Un tempo l'assembly era un linguaggio senza il quale non si poteva far fare nulla di utile a un computer. A poco a poco la situazione è cambiata. Sono comparsi mezzi più convenienti per comunicare con un computer. Ma, a differenza di altri linguaggi, l'assemblatore non è morto, inoltre, in linea di principio non poteva farlo. Perché? Alla ricerca di una risposta, proviamo a capire cos'è il linguaggio assembly in generale.

In breve, il linguaggio assembly è una rappresentazione simbolica del linguaggio macchina. Tutti i processi in una macchina al livello hardware più basso sono guidati solo da comandi (istruzioni) del linguaggio macchina. Da ciò è chiaro che, nonostante il nome comune, il linguaggio assembly è diverso per ogni tipo di computer. Questo vale anche aspetto programmi scritti in linguaggio assembly e idee di cui questo linguaggio è un riflesso.

È impossibile risolvere veramente problemi legati all'hardware (o anche, inoltre, dipendenti dall'hardware, come l'aumento della velocità di un programma), senza la conoscenza dell'assemblatore.

Un programmatore o qualsiasi altro utente può utilizzare qualsiasi strumento di alto livello, anche programmi per costruire mondi virtuali, e forse nemmeno sospettare che in realtà il computer non esegue i comandi del linguaggio in cui è scritto il suo programma, ma la loro rappresentazione trasformata sotto forma di sequenze noiose e noiose di comandi da un linguaggio completamente diverso: il linguaggio macchina. Ora immaginiamo che un utente del genere abbia un problema non standard o che qualcosa semplicemente non funzioni. Ad esempio, il suo programma deve funzionare con qualche dispositivo insolito o eseguire altre azioni che richiedono la conoscenza dei principi operativi dell'hardware del computer. Non importa quanto sia intelligente il programmatore, non importa quanto sia buona la lingua in cui ha scritto il suo meraviglioso programma, non può fare a meno della conoscenza dell'assemblatore. E non è un caso che quasi tutti i compilatori di linguaggi di alto livello contengano mezzi per connettere i loro moduli con moduli assembler o supportino l'accesso al livello di programmazione assembly.

Certo, il tempo dei generalisti informatici è già passato. Come si suol dire, non puoi abbracciare l'immensità. Ma c'è qualcosa in comune, una sorta di base su cui è costruita qualsiasi seria educazione informatica. Questa è la conoscenza dei principi del funzionamento del computer, della sua architettura e del linguaggio assembly come riflesso e incarnazione di questa conoscenza.

Un tipico computer moderno (basato su i486 o Pentium) è costituito dai seguenti componenti (Figura 1).

Riso. 1. Computer e periferiche

Riso. 2. Schema a blocchi personal computer

Dalla figura (Figura 1) si può vedere che il computer è composto da più dispositivi fisici, ciascuno dei quali è collegato ad una unità, chiamata unità di sistema. Se pensiamo in modo logico, è chiaro che svolge il ruolo di una sorta di dispositivo di coordinamento. Diamo un'occhiata all'interno dell'unità di sistema (non c'è bisogno di provare ad entrare nel monitor: non c'è niente di interessante lì e inoltre è pericoloso): apri la custodia e vediamo alcune schede, blocchi, cavi di collegamento. Per comprendere il loro scopo funzionale, diamo un'occhiata allo schema a blocchi di un tipico computer (Fig. 2). Non pretende l'accuratezza assoluta e intende solo mostrare lo scopo, l'interconnessione e la composizione tipica degli elementi di un moderno personal computer.

Parliamo del diagramma in Fig. 2 in uno stile un po' non convenzionale.
È normale che una persona, quando incontra qualcosa di nuovo, cerchi alcune associazioni che possano aiutarla a comprendere l'ignoto. Quali associazioni evoca il computer? Ad esempio, spesso associo un computer alla persona stessa. Perché?

Quando una persona creava un computer, da qualche parte nel profondo di sé pensava che stava creando qualcosa di simile a se stesso. Il computer è dotato di organi per ricevere informazioni dal mondo esterno: una tastiera, un mouse e unità disco magnetiche. Nella fig. 2 questi organi sono posti a destra dei bus di sistema. Il computer ha organi che "digeriscono" le informazioni ricevute: questi sono processore e RAM. Infine, il computer dispone di organi vocali che producono i risultati dell'elaborazione. Questi sono anche alcuni dei dispositivi sulla destra.

Computer moderni, ovviamente, è tutt'altro che umano. Possono essere paragonati a creature che interagiscono con il mondo esterno a livello di un insieme ampio ma limitato di riflessi incondizionati.
Questo insieme di riflessi forma un sistema di comandi della macchina. Non importa a quale livello si comunichi con un computer, alla fine si riduce a una sequenza noiosa e monotona di comandi della macchina.
Ogni comando della macchina è una sorta di stimolo per eccitare l'uno o l'altro riflesso incondizionato. La reazione a questo stimolo è sempre inequivocabile e “cablata” nel blocco di microcomandi sotto forma di microprogramma. Questo microprogramma implementa azioni per implementare un comando macchina, ma a livello dei segnali forniti a determinati logica computer, controllando così vari sottosistemi del computer. Questo è il cosiddetto principio del controllo del microprogramma.

Continuando l'analogia con una persona, notiamo: affinché un computer possa mangiare correttamente, sono stati inventati molti sistemi operativi, compilatori per centinaia di linguaggi di programmazione, ecc .. Ma tutti sono, in realtà, solo un piatto su cui il cibo (programmi) viene consegnato secondo determinate regole stomaco (computer). Solo lo stomaco del computer ama la dieta, il cibo monotono: forniscigli informazioni strutturate, sotto forma di sequenze rigorosamente organizzate di zero e uno, le cui combinazioni costituiscono il linguaggio macchina.

Pertanto, sebbene esteriormente poliglotta, il computer capisce solo una lingua: la lingua delle istruzioni della macchina. Naturalmente, per comunicare e lavorare con un computer, non è necessario conoscere questa lingua, ma quasi ogni programmatore professionista prima o poi si trova ad affrontare la necessità di studiarla. Fortunatamente, il programmatore non deve cercare di comprendere il significato di varie combinazioni di numeri binari, poiché già negli anni '50 i programmatori iniziarono a utilizzare per la programmazione un analogo simbolico del linguaggio macchina, chiamato linguaggio assembly. Questo linguaggio riflette accuratamente tutte le caratteristiche del linguaggio macchina. Ecco perché, a differenza dei linguaggi di alto livello, il linguaggio assembly è diverso per ogni tipo di computer.

Da tutto quanto sopra possiamo concludere che, poiché il linguaggio assembly è "nativo" per un computer, solo al suo interno è possibile scrivere il programma più efficace (a condizione che sia scritto da un programmatore qualificato). C'è un piccolo "ma" qui: questo è un processo molto laborioso che richiede molta attenzione ed esperienza pratica. Quindi, in realtà, scrivono principalmente i programmi in assembler che dovrebbero fornire lavoro efficace con hardware. A volte le sezioni del programma critiche in termini di tempo di esecuzione o consumo di memoria vengono scritte in assembler. Successivamente vengono formalizzati sotto forma di subroutine e combinati con codice in un linguaggio di alto livello.

Ha senso iniziare ad apprendere il linguaggio assembly di qualsiasi computer solo dopo aver scoperto quale parte del computer è lasciata visibile e accessibile per la programmazione in questo linguaggio. Questo è il cosiddetto modello di programma per computer, parte del quale è il modello di programma del microprocessore, che contiene 32 registri, in un modo o nell'altro, disponibili per l'uso da parte del programmatore.

Questi registri possono essere suddivisi in due grandi gruppi:

^ 16 registri utenti;

16 registri di sistema.

I programmi in linguaggio assembly utilizzano i registri in modo molto intensivo. La maggior parte dei registri ha uno scopo funzionale specifico.

Come suggerisce il nome, i registri utente sono chiamati registri utente perché il programmatore può utilizzarli durante la scrittura dei suoi programmi. Questi registri includono (Fig. 3):

Otto registri a 32 bit che possono essere utilizzati dai programmatori per memorizzare dati e indirizzi (chiamati anche registri per scopi generali (GPR)):

sei registri di segmento: cs, ds, ss, es, fs, gs;

registri di stato e controllo:

I flag registrano eflags/flags;

Registro del puntatore di comando eip/ip.

Riso. 3. Registri utente dei microprocessori i486 e Pentium

Perché molti di questi registri sono indicati con barre? No, questi non sono registri diversi: fanno parte di un unico grande registro a 32 bit. Possono essere utilizzati nel programma come oggetti separati. Ciò è stato fatto per garantire la funzionalità dei programmi scritti per i modelli più giovani di microprocessori Intel a 16 bit, a partire da i8086. I microprocessori i486 e Pentium hanno principalmente registri a 32 bit. Il loro numero, ad eccezione dei registri dei segmenti, è lo stesso dell'i8086, ma la dimensione è maggiore, il che si riflette nelle loro designazioni: hanno
prefisso e (esteso).

^ Registri di uso generale
Tutti i registri di questo gruppo consentono di accedere alle loro parti “inferiori” (vedi Fig. 3). Osservando questa figura, si noti che solo le parti inferiori a 16 e 8 bit di questi registri possono essere utilizzate per l'autoindirizzamento. I 16 bit superiori di questi registri non sono disponibili come oggetti indipendenti. Ciò è stato fatto, come notato in precedenza, per la compatibilità con i modelli più recenti di microprocessori Intel a 16 bit.

Elenchiamo i registri appartenenti al gruppo dei registri di uso generale. Poiché questi registri sono fisicamente posizionati nel microprocessore all'interno di un'unità aritmetica logica (ALU), sono anche chiamati registri ALU:

eax/ax/ah/al (registro dell'accumulatore) - batteria.
Utilizzato per memorizzare dati intermedi. Alcuni comandi richiedono l'utilizzo di questo registro;

ebx/bx/bh/bl (registro base) - registro base.
Utilizzato per memorizzare l'indirizzo di base di alcuni oggetti in memoria;

ecx/cx/ch/cl (registro di conteggio) - registro del contatore.
Utilizzato in team che eseguono alcune azioni ripetitive. Il suo utilizzo è spesso implicito e nascosto nell'algoritmo del comando corrispondente.
Ad esempio, il comando per organizzare un loop loop, oltre a trasferire il controllo a un comando situato ad un determinato indirizzo, analizza e diminuisce di uno il valore del registro ecx/cx;

edx/dx/dh/dl (registro dati) - registro dati.
Proprio come il registro eax/ax/ah/al, memorizza i dati intermedi. In alcuni comandi il suo utilizzo è obbligatorio; Per alcuni comandi ciò avviene implicitamente.

I due registri seguenti vengono utilizzati per supportare le cosiddette operazioni a catena, ovvero operazioni che elaborano in sequenza catene di elementi, ciascuna delle quali può essere lunga 32, 16 o 8 bit:

esi/si (registro Indice della Fonte) - indice della fonte.
Questo registro nelle operazioni concatenate contiene l'indirizzo corrente dell'elemento nella catena di origine;

edi/di (registro Indice di destinazione) - indice del destinatario (destinatario).
Questo registro nelle operazioni concatenate contiene l'indirizzo corrente nella catena di destinazione.

Nell'architettura del microprocessore, una struttura dati come uno stack è supportata a livello hardware e software. Per lavorare con lo stack, nel sistema di istruzioni del microprocessore sono presenti comandi speciali e nel modello software del microprocessore sono presenti registri speciali per questo:

esp/sp (registro del puntatore dello stack) - registro del puntatore dello stack.
Contiene un puntatore alla parte superiore dello stack nel segmento dello stack corrente.

ebp/bp (registro del puntatore base) - registro del puntatore base dello stack frame.
Progettato per organizzare l'accesso casuale ai dati all'interno dello stack.

Uno stack è un'area di programma per la memorizzazione temporanea di dati arbitrari. Naturalmente i dati possono anche essere memorizzati in un segmento dati, ma in questo caso per ogni dato memorizzato temporaneamente deve essere creata una cella di memoria con nome separato, il che aumenta la dimensione del programma e il numero di nomi utilizzati. La comodità dello stack sta nel fatto che la sua area è riutilizzabile e la memorizzazione dei dati nello stack e il loro recupero da lì vengono eseguiti utilizzando gli efficaci comandi push e pop senza specificare alcun nome.
Lo stack viene tradizionalmente utilizzato, ad esempio, per salvare il contenuto dei registri utilizzati da un programma prima di chiamare una subroutine che, a sua volta, utilizzerà i registri del processore "per i propri scopi". Il contenuto originale dei registri viene estratto dallo stack dopo il ritorno della subroutine. Un'altra tecnica comune consiste nel passare i parametri richiesti a una subroutine tramite lo stack. La subroutine, sapendo in quale ordine i parametri sono messi nello stack, può prenderli da lì e utilizzarli durante la sua esecuzione. Caratteristica distintiva Lo stack è un ordine univoco in cui vengono recuperati i dati in esso contenuti: in ogni momento, sullo stack è disponibile solo l'elemento in cima, ovvero l'elemento più recentemente inserito nello stack. Estraendo l'elemento in cima dallo stack si rende disponibile l'elemento successivo. Gli elementi dello stack si trovano nell'area di memoria allocata per lo stack, a partire dal fondo dello stack (cioè dal suo indirizzo massimo) in corrispondenza di indirizzi decrescenti. L'indirizzo dell'elemento superiore e accessibile è memorizzato nel registro SP del puntatore dello stack. Come qualsiasi altra area della memoria del programma, lo stack deve far parte di un segmento o formare un segmento separato. In entrambi i casi, l'indirizzo di segmento di questo segmento viene posto nel registro dello stack di segmenti SS. Pertanto, la coppia di registri SS:SP descrive l'indirizzo di una cella dello stack accessibile: SS memorizza l'indirizzo del segmento dello stack e SP memorizza l'offset degli ultimi dati memorizzati nello stack (Fig. 4, a). Si noti che nello stato iniziale, il puntatore dello stack SP punta a una cella che si trova sotto il fondo dello stack e non è inclusa in esso.

Fig 4. Organizzazione dello stack: a - stato iniziale, b - dopo aver caricato un elemento (in questo esempio, il contenuto del registro AX), c - dopo aver caricato il secondo elemento (contenuto del registro DS), d - dopo averne scaricato uno elemento, e - dopo aver scaricato due elementi e ritornati al loro stato originale.

Il caricamento nello stack viene effettuato da un comando speciale per lavorare con lo stack (push). Questa istruzione prima decrementa il contenuto del puntatore dello stack di 2 e poi posiziona l'operando all'indirizzo in SP. Se, ad esempio, vogliamo memorizzare temporaneamente il contenuto del registro AX nello stack, dovremmo eseguire il comando

Lo stack si porta nello stato mostrato in Fig. 1.10, b. Si può vedere che il puntatore dello stack viene spostato di due byte verso l'alto (verso gli indirizzi inferiori) e l'operando specificato nel comando push viene scritto in questo indirizzo. Il seguente comando di caricamento dello stack è ad es.

metterà lo stack nello stato mostrato in Fig. 1.10, c. Lo stack ora memorizzerà due elementi e solo quello in alto, indicato dallo stack pointer SP, sarà accessibile. Se dopo qualche tempo abbiamo bisogno di ripristinare il contenuto originale dei registri memorizzati sullo stack, dobbiamo eseguire i comandi pop (push) per scaricare dallo stack:

Pop DS
popAX

Quanto dovrebbe essere grande lo stack? Dipende da quanto intensamente viene utilizzato nel programma. Se, ad esempio, prevedi di archiviare un array di 10.000 byte nello stack, lo stack deve avere almeno queste dimensioni. Va tenuto presente che in alcuni casi lo stack viene utilizzato automaticamente dal sistema, in particolare quando si esegue il comando int 21h interrupt. Con questo comando, il processore inserisce prima l'indirizzo di ritorno nello stack, quindi il DOS inserisce nello stack il contenuto dei registri e altre informazioni relative al programma interrotto. Pertanto, anche se un programma non utilizza affatto uno stack, esso deve essere comunque presente nel programma ed avere una dimensione di almeno diverse dozzine di parole. Nel nostro primo esempio abbiamo allocato 128 parole nello stack, il che è sicuramente sufficiente.

^ Struttura di un programma assembler

Un programma in linguaggio assembly è una raccolta di blocchi di memoria chiamati segmenti di memoria. Un programma può essere costituito da uno o più segmenti di blocco di questo tipo. Ogni segmento contiene una raccolta di frasi linguistiche, ciascuna delle quali occupa una riga separata di codice di programma.

Esistono quattro tipi di istruzioni assembler:

comandi o istruzioni che sono analoghi simbolici dei comandi della macchina. Durante il processo di traduzione, le istruzioni dell'assembler vengono convertite nei corrispondenti comandi del set di istruzioni del microprocessore;

macrocomandi - frasi di testo del programma formattate in un certo modo, sostituite durante la trasmissione da altre frasi;

direttive, che sono istruzioni al traduttore assembler per eseguire determinate azioni. Le direttive non hanno controparti nella rappresentazione della macchina;

righe di commento contenenti qualsiasi carattere, comprese le lettere dell'alfabeto russo. I commenti vengono ignorati dal traduttore.

^ Sintassi dell'assembly

Le frasi che compongono un programma possono essere un costrutto sintattico corrispondente a un comando, macro, direttiva o commento. Affinché il traduttore assembler li riconosca, devono essere formati secondo determinate regole sintattiche. Per fare ciò, è meglio utilizzare una descrizione formale della sintassi della lingua, come le regole grammaticali. I modi più comuni per descrivere un linguaggio di programmazione in questo modo sono i diagrammi di sintassi e le forme estese di Backus-Naur. Per uso pratico i diagrammi di sintassi sono più convenienti. Ad esempio, la sintassi delle istruzioni in linguaggio assembly può essere descritta utilizzando i diagrammi di sintassi mostrati nelle figure seguenti.

Riso. 5. Formato della frase di assemblaggio

Riso. 6. Formato della direttiva

Riso. 7. Formato dei comandi e delle macro

In queste immagini:

nome dell'etichetta - un identificatore il cui valore è l'indirizzo del primo byte della frase nel codice sorgente del programma che designa;

name - un identificatore che distingue questa direttiva da altre direttive con lo stesso nome. In seguito all'elaborazione da parte dell'assemblatore di una particolare direttiva, a quel nome possono essere assegnate determinate caratteristiche;

un codice operativo (OPC) e una direttiva sono simboli mnemonici per la corrispondente istruzione macchina, istruzione macro o direttiva del traduttore;

gli operandi sono parti di un comando, una macro o una direttiva assembler che designano gli oggetti su cui vengono eseguite le azioni. Gli operandi del linguaggio assembly sono descritti da espressioni con costanti numeriche e di testo, etichette e identificatori di variabile utilizzando segni di operatore e alcune parole riservate.

^ Come utilizzare i diagrammi di sintassi? È molto semplice: tutto quello che devi fare è trovare e seguire il percorso dall'input del diagramma (a sinistra) al suo output (a destra). Se tale percorso esiste, la frase o la costruzione sono sintatticamente corrette. Se non esiste un percorso di questo tipo, il compilatore non accetterà questa costruzione. Quando si lavora con i diagrammi sintattici prestare attenzione al senso di attraversamento indicato dalle frecce, poiché tra i percorsi ce ne possono essere alcuni da seguire da destra a sinistra. In sostanza, i diagrammi di sintassi riflettono la logica dell'operazione del traduttore durante l'analisi delle frasi di input del programma.

I caratteri accettabili durante la scrittura del testo del programma sono:

Tutto lettere: A-Z, a-z. In questo caso le lettere maiuscole e minuscole sono considerate equivalenti;

Numeri da 0 a 9;

Segni ?, @, $, _, &;

Separatori, . ()< > { } + / * % ! " " ? \ = # ^.

Le frasi in linguaggio assembly sono formate da lessemi, che sono sequenze sintatticamente inseparabili di simboli linguistici validi che hanno senso per il traduttore.

I lessemi sono:

gli identificatori sono sequenze di caratteri validi utilizzati per designare oggetti del programma come codici operativi, nomi di variabili e nomi di etichette. La regola per scrivere gli identificatori è la seguente: un identificatore può essere costituito da uno o più caratteri. Come simboli puoi usare lettere dell'alfabeto latino, numeri e alcuni caratteri speciali: _, ?, $, @. Un identificatore non può iniziare con un carattere numerico. La lunghezza dell'identificatore può arrivare fino a 255 caratteri, sebbene il traduttore accetti solo i primi 32 e ignori il resto. È possibile regolare la lunghezza dei possibili identificatori utilizzando l'opzione riga di comando mv. Inoltre, è possibile chiedere al traduttore di distinguere tra lettere maiuscole e minuscole o di ignorare la loro differenza (operazione eseguita per impostazione predefinita).

^Comandi dell'assemblatore.

I comandi dell'assemblatore rivelano la capacità di trasferire i propri requisiti al computer, un meccanismo per trasferire il controllo in un programma (cicli e transizioni) per confronti logici e organizzazione del programma. Tuttavia, le attività programmabili raramente sono così semplici. La maggior parte dei programmi contiene una serie di cicli in cui diversi comandi vengono ripetuti fino al raggiungimento di un determinato requisito e vari controlli che determinano quale delle diverse azioni deve essere eseguita. Alcune istruzioni possono trasferire il controllo modificando la normale sequenza di passi modificando direttamente il valore di offset nel puntatore dell'istruzione. Come accennato in precedenza, esistono comandi diversi per processori diversi, ma esamineremo alcuni comandi per i processori 80186, 80286 e 80386.

Per descrivere lo stato dei flag dopo aver eseguito un determinato comando, utilizzeremo una selezione da una tabella che riflette la struttura del registro dei flag eflags:

La riga inferiore di questa tabella mostra i valori dei flag dopo l'esecuzione del comando. Vengono utilizzate le seguenti notazioni:

1 - dopo l'esecuzione del comando, il flag viene impostato (uguale a 1);

0 - dopo l'esecuzione del comando, il flag viene resettato (uguale a 0);

r - il valore del flag dipende dal risultato del comando;

Dopo l'esecuzione del comando, il flag non viene definito;

spazio: dopo l'esecuzione del comando, il flag non cambia;

La seguente notazione viene utilizzata per rappresentare gli operandi nei diagrammi di sintassi:

r8, r16, r32 - un operando in uno dei registri di dimensione byte, parola o doppia parola;

m8, m16, m32, m48 - dimensione dell'operando di memoria byte, parola, doppia parola o 48 bit;

i8, i16, i32 - dimensione operando immediata byte, parola o doppia parola;

a8, a16, a32 - indirizzo relativo (offset) nel segmento di codice.

Comandi (in ordine alfabetico):

*Questi comandi sono descritti in dettaglio.

AGGIUNGERE
(AGGIUNTA)

Aggiunta

^ Schema dei comandi:

aggiungi destinazione, fonte

Scopo: aggiunta di due operandi sorgente e destinazione di dimensione byte, parola o doppia parola.

Algoritmo di lavoro:

aggiungere gli operandi sorgente e destinazione;

scrivere il risultato dell'addizione al ricevitore;

impostare le bandiere.

Stato dei flag dopo l'esecuzione del comando:

Applicazione:
Il comando add viene utilizzato per aggiungere due operandi interi. Il risultato dell'addizione viene posto all'indirizzo del primo operando. Se il risultato dell'addizione va oltre i limiti dell'operando del ricevitore (si verifica un overflow), allora questa situazione dovrebbe essere presa in considerazione analizzando il flag cf e il successivo possibile utilizzo del comando adc. Ad esempio, aggiungiamo i valori nel registro ax e nell'area di memoria ch. Quando si aggiunge, tenere conto della possibilità di trabocco.

Registro più registro o memoria:

|000000dw|modregr/rm|

Registro AX (AL) più valore immediato:

|0000010w|--data--|data se w=1|

Registro o memoria più valore immediato:

|100000sw|mod000r/m|--data--|data se BW=01|

CHIAMATA
(CHIAMATA)

Chiamare una procedura o un'attività

^ Schema dei comandi:

Scopo:

trasferire il controllo a una procedura vicina o lontana con la memorizzazione dell'indirizzo del punto di ritorno sullo stack;

cambio di compiti.

Algoritmo di lavoro:
determinato dal tipo di operando:

Near label - il contenuto del puntatore al comando eip/ip viene inserito nello stack e il nuovo valore dell'indirizzo corrispondente all'etichetta viene caricato nello stesso registro;

Etichetta far: il contenuto del puntatore ai comandi eip/ip e cs viene inserito nello stack. Quindi negli stessi registri vengono caricati nuovi valori di indirizzo corrispondenti all'etichetta lontana;

R16, 32 o m16, 32 - definiscono un registro o cella di memoria contenente gli offset nel segmento di istruzione corrente a cui viene trasferito il controllo. Quando il controllo viene trasferito, il contenuto del puntatore al comando eip/ip viene inserito nello stack;

Puntatore di memoria: definisce una posizione di memoria contenente un puntatore di 4 o 6 byte alla procedura chiamata. La struttura di tale puntatore è 2+2 o 2+4 byte. L'interpretazione di tale indicatore dipende dalla modalità operativa del microprocessore:

^ Stato dei flag dopo l'esecuzione del comando (eccetto il cambio di attività):

l'esecuzione del comando non influisce sui flag

Quando si cambia attività, i valori dei flag vengono modificati in base alle informazioni sul registro eflags nel segmento di stato TSS dell'attività a cui si passa.
Applicazione:
Il comando di chiamata consente di organizzare un trasferimento di controllo flessibile e multivariante a una subroutine preservando l'indirizzo del punto di ritorno.

Codice oggetto (quattro formati):

Indirizzamento diretto in un segmento:

|11101000|disp-basso|diep-alto|

Indirizzamento indiretto in un segmento:

|11111111|mod010r/m|

Indirizzamento indiretto tra segmenti:

|11111111|mod011r/m|

Indirizzamento diretto tra segmenti:

|10011010|offset-basso|offset-alto|seg-basso|seg-alto|

CMP
(operandi CoMPare)

Confronto degli operandi

^ Schema dei comandi:

cmp operando1,operando2

Scopo: confronto di due operandi.

Algoritmo di lavoro:

eseguire la sottrazione(operando1-operando2);

a seconda del risultato, imposta i flag, non modificare operando1 e operando2 (ovvero, non ricordare il risultato).

Applicazione:
Questo comando utilizzato per confrontare due operandi mediante sottrazione senza modificare gli operandi. In base ai risultati del comando, vengono impostati i flag. Il comando cmp viene utilizzato con i comandi di salto condizionale e il comando imposta byte per valore setcc.

Codice oggetto (tre formati):

Registro o memoria con registro:

|001110dw|modregr/m|

Valore immediato con registro AX (AL):

|0011110w|--data--|data se w=1|

Valore immediato con registro o memoria:

|100000sw|mod111r/m|--data--|data se sw=0|

DIC
(Diminuisce l'operando di 1)

Diminuzione di un operando di uno

^ Schema dei comandi:

operando dec

Scopo: Diminuisce di 1 il valore di un operando in memoria o registro.

Algoritmo di lavoro:
il comando sottrae 1 dall'operando. Stato dei flag dopo l'esecuzione del comando:

Applicazione:
L'istruzione dec viene utilizzata per diminuire di uno il valore di un byte, una parola, una doppia parola in memoria o un registro. Tuttavia, tieni presente che il comando non influisce sul flag cf.

Registro: |01001reg|

^ Registro o memoria: |1111111w|mod001r/m|

DIV
(DIVide senza segno)

Divisione senza segno

Schema della squadra:

divisore div

Scopo: eseguire un'operazione di divisione tra due valori binari senza segno.

^ Algoritmo operativo:
Il comando richiede due operandi: il dividendo e il divisore. Il dividendo è specificato in modo implicito e la sua dimensione dipende dalla dimensione del divisore, specificata nel comando:

se il divisore ha la dimensione di un byte, il dividendo deve trovarsi nel registro dell'ascia. Dopo l'operazione si pone il quoziente in al ed il resto in ah;

se il divisore ha le dimensioni di una parola, allora il dividendo deve essere posizionato nella coppia di registri dx:ax, con la parte di ordine inferiore del dividendo posizionata in ax. Dopo l'operazione si pone il quoziente in ax ed il resto in dx;

se il divisore è una parola doppia, allora il dividendo deve essere posizionato nella coppia di registri edx:eax, con la parte di ordine inferiore del dividendo posizionata in eax. Dopo l'operazione, il quoziente viene posto in eax e il resto in edx.

^ Stato dei flag dopo l'esecuzione del comando:

Applicazione:
Il comando esegue una divisione intera degli operandi, producendo il risultato della divisione come quoziente e resto della divisione. Quando si esegue un'operazione di divisione, può verificarsi un'eccezione: 0 - errore di divisione. Questa situazione si verifica in uno dei due casi seguenti: il divisore è 0 o il quoziente è troppo grande per rientrare nel registro eax/ax/al.

Codice oggetto:

|1111011w|mod110r/m|

INT
(Interrompere)

Chiamata alla routine del servizio di interruzione

^ Schema dei comandi:

int numero_interruzione

Scopo: richiamare la routine del servizio di interruzione con il numero di interruzione specificato dall'operando del comando.

^ Algoritmo operativo:

inserisci il registro flag eflags/flags e l'indirizzo del mittente nello stack. Quando si scrive un indirizzo di ritorno, viene scritto prima il contenuto del registro di segmento cs, poi il contenuto del puntatore al comando eip/ip;

reimpostare i flag if e tf a zero;

trasferire il controllo al programma di servizio di interruzione con il numero specificato. Il meccanismo di trasferimento del controllo dipende dalla modalità operativa del microprocessore.

^ Stato dei flag dopo l'esecuzione del comando:

Applicazione:
Come puoi vedere dalla sintassi, ci sono due forme di questo comando:

int 3 - ha il proprio codice operativo individuale 0cch e occupa un byte. Questa circostanza rende molto conveniente l'utilizzo in vari debugger software per impostare punti di interruzione sostituendo il primo byte di qualsiasi comando. Il microprocessore, incontrando un comando con codice operativo 0cch nella sequenza di comandi, chiama il programma di elaborazione degli interrupt con il vettore numero 3, che serve per comunicare con il debugger del software.

La seconda forma del comando occupa due byte, ha un codice operativo 0cdh e consente di avviare una chiamata a una routine di servizio di interruzione con un numero di vettore compreso tra 0 e 255. Le caratteristiche del trasferimento del controllo, come notato, dipendono dalla modalità operativa del microprocessore.

Codice oggetto (due formati):

Registro: |01000reg|

^ Registro o memoria: |1111111w|mod000r/m|

J.C.C.
JCXZ/JECXZ
(Salta se condizione)

(Salta se CX=Zero/Salta se ECX=Zero)

Salta se la condizione è soddisfatta

Salta se CX/ECX è zero

^ Schema dei comandi:

etichetta jcc
etichetta jcxz
etichetta jecxz

Scopo: transizione all'interno del segmento di comando corrente in base ad alcune condizioni.

^ Algoritmo di comando (eccetto jcxz/jecxz):
Controllo dello stato dei flag in base al codice operativo (riflette la condizione da controllare):

se la condizione da testare è vera, vai alla cella indicata dall'operando;

se la condizione da verificare è falsa, trasferisci il controllo al comando successivo.

Algoritmo per il comando jcxz/jecxz:
Verifica della condizione che il contenuto del registro ecx/cx sia uguale a zero:

se la condizione viene controllata

Struttura dei comandi in linguaggio assembly La programmazione a livello dei comandi della macchina è il livello minimo al quale è possibile la programmazione del computer. Il sistema di comando della macchina deve essere sufficiente per implementare le azioni richieste impartendo istruzioni all'attrezzatura della macchina. Ogni istruzione macchina è composta da due parti: una operativa, che determina “cosa fare” e un operando, che determina gli oggetti di elaborazione, cioè “cosa fare”. Un'istruzione macchina del microprocessore, scritta in linguaggio Assembly, è una singola riga che ha la seguente forma: etichetta operando/i comando/direttiva; commenti L'etichetta, il comando/direttiva e l'operando sono separati da almeno uno spazio o un carattere di tabulazione. Gli operandi del comando sono separati da virgole.

Struttura dei comandi del linguaggio assembly Un comando assembler dice al traduttore quale azione dovrebbe eseguire il microprocessore. Le direttive di assembly sono parametri specificati nel testo del programma che influiscono sul processo di assembly o sulle proprietà del file di output. L'operando specifica il valore iniziale dei dati (nel segmento dati) o gli elementi su cui viene eseguita l'azione del comando (nel segmento codice). Un'istruzione può avere uno o due operandi oppure nessun operando. Il numero di operandi è implicitamente specificato dal codice istruzione. Se è necessario continuare un comando o una direttiva nella riga successiva, viene utilizzato il carattere barra rovesciata: "" . Per impostazione predefinita, l'assemblatore non distingue tra lettere maiuscole e minuscole durante la scrittura di comandi e direttive. Esempi di direttive e comandi Count db 1 ; Nome, direttiva, un operando mov eax, 0 ; Comando, due operandi

Gli identificatori sono sequenze di caratteri validi utilizzati per denotare nomi di variabili e nomi di etichette. L'identificatore può essere costituito da uno o più dei seguenti caratteri: tutte le lettere dell'alfabeto latino; numeri da 0 a 9; caratteri speciali: _, @, $, ? . È possibile utilizzare un punto come primo carattere dell'etichetta. I nomi assembler riservati (direttive, operatori, nomi di comandi) non possono essere utilizzati come identificatori. Il primo carattere dell'identificatore deve essere una lettera o un carattere speciale. Lunghezza massima L'identificatore ha 255 caratteri, ma il traduttore accetta i primi 32 e ignora il resto. Tutte le etichette scritte su una riga che non contiene una direttiva assembler devono terminare con i due punti ":". L'etichetta, il comando (direttiva) e l'operando non devono iniziare in una posizione particolare nella riga. Si consiglia di scriverli in colonna per una maggiore leggibilità del programma.

Etichette Tutte le etichette scritte su una riga che non contiene una direttiva assembler devono terminare con i due punti ":". L'etichetta, il comando (direttiva) e l'operando non devono iniziare in una posizione particolare nella riga. Si consiglia di scriverli in colonna per una maggiore leggibilità del programma.

Commenti L'uso dei commenti in un programma ne migliora la chiarezza, soprattutto quando lo scopo di un insieme di comandi non è chiaro. I commenti iniziano su qualsiasi riga del modulo sorgente con un punto e virgola (;). Tutti i caratteri a destra di "; " alla fine della riga c'è un commento. Un commento può contenere qualsiasi carattere stampabile, compreso lo spazio. Un commento può occupare l'intera riga o seguire un comando sulla stessa riga.

Struttura del programma in linguaggio assembly Un programma scritto in linguaggio assembly può essere costituito da diverse parti chiamate moduli, ciascuna delle quali può definire uno o più dati, stack e segmenti di codice. Qualsiasi programma completo in linguaggio assembly deve includere un modulo principale, o principale, da cui inizia la sua esecuzione. Il modulo può contenere segmenti di programma, dati e segmenti di stack dichiarati utilizzando le direttive appropriate.

Modelli di memoria Prima di dichiarare i segmenti, è necessario specificare il modello di memoria utilizzando una direttiva. MODEL modificatore memory_model, calling_convention, OS_type, stack_parameter Modelli di memoria di base del linguaggio assembly: modello di memoria Indirizzamento del codice Indirizzamento dei dati sistema operativo Codice e dati interleaving PICCOLO VICINO MS-DOS Accettabile PICCOLO VICINO MS-DOS, Windows No MEDIO LONTANO VICINO MS-DOS, Windows No COMPATTO VICINO LONTANO MS-DOS, Windows No GRANDE LONTANO MS-DOS, Windows No ENORME LONTANO MS-DOS, Windows No VICINO a Windows 2000, Windows XP, Windows Accettabile FLAT VICINO a NT,

Modelli di memoria Il modello piccolo funziona solo con applicazioni MS-DOS a 16 bit. In questo modello, tutti i dati e il codice si trovano su un segmento fisico. La dimensione del file di programma in questo caso non supera i 64 KB. Il modello piccolo supporta un segmento di codice e un segmento di dati. Dati e codice vengono indirizzati il ​​più vicino possibile quando si utilizza questo modello. Il modello medio supporta più segmenti di codice e un segmento dati, con tutti i riferimenti nei segmenti di codice considerati lontani per impostazione predefinita e i riferimenti in un segmento dati considerati vicini. Il modello compatto supporta diversi segmenti di dati che utilizzano l'indirizzamento dei dati lontani (far) e un segmento di codice che utilizza l'indirizzamento vicino (near). Il modello di grandi dimensioni supporta più segmenti di codice e più segmenti di dati. Per impostazione predefinita, tutti i riferimenti al codice e ai dati sono considerati lontani. Il modello enorme è quasi equivalente al modello di memoria grande.

Modelli di memoria Il modello flat presuppone una configurazione del programma non segmentata e viene utilizzato solo nei sistemi operativi a 32 bit. Questo modello è simile al modello tiny in quanto i dati e il codice si trovano in un unico segmento, ma è a 32 bit. Sviluppare un programma per il modello piatto prima della direttiva. il modello flat dovrebbe inserire una delle direttive: . 386, . 486, . 586 o. 686. La scelta della direttiva di selezione del processore determina l'insieme di istruzioni disponibili durante la scrittura dei programmi. La lettera p dopo la direttiva sulla selezione del processore indica la modalità operativa protetta. L'indirizzamento di dati e codici è vicino, con tutti gli indirizzi e i puntatori a 32 bit.

Modelli di memoria. MODIFICATORE MODEL memory_model, calling_convention, OS_type, stack_parameter Il parametro modificatore viene utilizzato per definire i tipi di segmento e può assumere i seguenti valori: utilizzare 16 (i segmenti del modello selezionato vengono utilizzati come 16 bit) utilizzare 32 (i segmenti del modello selezionato vengono utilizzati come 32 bit). Il parametro Calling_Convention viene utilizzato per determinare il metodo di passaggio dei parametri quando si chiama una procedura da altri linguaggi, compresi i linguaggi di alto livello (C++, Pascal). Il parametro può assumere i seguenti valori: C, BASIC, FORTRAN, PASCAL, SYSCALL, STDCALL.

Modelli di memoria. Modificatore MODEL memory_model, calling_convention, OS_type, stack_parameter Il parametro OS_type è OS_DOS per impostazione predefinita e su questo momento questo è l'unico valore supportato per questo parametro. Il parametro stack_parameter è impostato su: NEARSTACK (il registro SS è uguale a DS, i dati e le aree dello stack sono allocati nello stesso segmento fisico) FARSTACK (il registro SS non è uguale a DS, i dati e le aree dello stack sono allocati in segmenti fisici diversi). Il valore predefinito è NEARSTACK.

Un esempio di un programma che non fa nulla. 686 P. MODELLO PIATTO, STDCALL. DATI. CODICE START: RET END START RET - comando del microprocessore. Garantisce che il programma termini correttamente. Il resto del programma riguarda il funzionamento del traduttore. . 686 P - Sono consentiti i comandi in modalità protetta Pentium 6 (Pentium II). Questa direttiva seleziona il set supportato di istruzioni assembler, indicando il modello del processore. . MODELLO FLAT, stdcall - modello di memoria flat. Questo modello di memoria viene utilizzato nel sistema operativo Windows. stdcall - la convenzione di chiamata della procedura utilizzata.

Un esempio di un programma che non fa nulla. 686 P. MODELLO PIATTO, STDCALL. DATI. CODICE INIZIO: RET FINE INIZIO. DATA è un segmento di programma contenente dati. Questo programma non utilizza lo stack, quindi il segmento. Manca lo STACK. . CODE è un segmento di programma contenente codice. INIZIO - etichetta. END START - la fine del programma e un messaggio al compilatore che informa che l'esecuzione del programma dovrebbe iniziare con l'etichetta START. Ogni programma deve contenere una direttiva END per contrassegnare la fine codice sorgente programmi. Tutte le righe che seguono la direttiva END vengono ignorate.L'etichetta specificata dopo la direttiva END indica al traduttore il nome del modulo principale da cui inizia l'esecuzione del programma. Se il programma contiene un modulo, l'etichetta dopo la direttiva END può essere omessa.

Traduttori in linguaggio assembly Traduttore - programma o mezzi tecnici, che converte un programma rappresentato in uno dei linguaggi di programmazione in un programma nella lingua di destinazione, chiamato codice oggetto. Oltre a supportare i mnemonici delle istruzioni macchina, ogni traduttore ha il proprio set di direttive e macro strumenti, spesso incompatibili con qualsiasi altra cosa. I principali tipi di traduttori del linguaggio assembly: MASM (Microsoft Assembler), TASM (Borland Turbo Assembler), FASM (Flat Assembler) - un assemblatore multi-pass distribuito gratuitamente scritto da Tomasz Gryshtar (polacco), NASM (Netwide Assembler) - un traduttore gratuito assemblatore per l'architettura Intel x 86, è stato creato da Simon Tatham con Julian Hall ed è attualmente in fase di sviluppo da un piccolo team di sviluppatori presso Source. Fucina. netto.

Src="https://present5.com/presentation/-29367016_63610977/image-15.jpg" alt="Tradurre un programma in Microsoft Visual Studio 2005 1) Creare un progetto selezionando File->Nuovo- >Menù Progetto E"> Трансляция программы в Microsoft Visual Studio 2005 1) Создать проект, выбрав меню File->New->Project и указав имя проекта (hello. prj) и тип проекта: Win 32 Project. В дополнительных опциях мастера проекта указать “Empty Project”.!}

Src="https://present5.com/presentation/-29367016_63610977/image-16.jpg" alt="Traduzione del programma in Microsoft Visual Studio 2005 2) Nell'albero del progetto (Visualizza->Esplora soluzioni) aggiungere"> Трансляция программы в Microsoft Visual Studio 2005 2) В дереве проекта (View->Solution Explorer) добавить файл, в котором будет содержаться текст программы: Source. Files->Add->New. Item.!}

Traduzione del programma in Microsoft Visual Studio 2005 3) Selezionare il tipo di file Code C++, ma specificare il nome con l'estensione. asm:

Tradurre il programma in Microsoft Visual Studio 2005 5) Impostare i parametri del compilatore. Fare clic con il pulsante destro del mouse sul menu Regole di creazione personalizzate nel file di progetto...

Traduci il programma in Microsoft Visual Studio 2005 e seleziona Microsoft Macro Assembler nella finestra che appare.

Traduzione del programma in Microsoft Visual Studio 2005 Controlla con il tasto destro nel file ciao. asm del menu Proprietà e installare Generale->Strumento: Microsoft Macro Assembler.

Src="https://present5.com/presentation/-29367016_63610977/image-22.jpg" alt="Traduzione del programma in Microsoft Visual Studio 2005 6) Compila il file selezionando Compila->Crea ciao. prj."> Трансляция программы в Microsoft Visual Studio 2005 6) Откомпилировать файл, выбрав Build->Build hello. prj. 7) Запустить программу, нажав F 5 или выбрав меню Debug->Start Debugging.!}

Programmazione nel sistema operativo Windows La programmazione nel sistema operativo Windows si basa sull'uso delle funzioni API (Application Program Interface, ovvero l'interfaccia dell'applicazione software). Il loro numero arriva a 2000. Il programma Windows consiste in gran parte in tali chiamate. Tutte le interazioni con dispositivi esterni e le risorse del sistema operativo avvengono, di regola, attraverso tali funzioni. sala operatoria Sistema Windows utilizza un modello di memoria flat. L'indirizzo di qualsiasi cella di memoria sarà determinato dal contenuto di un registro a 32 bit. Esistono 3 tipi di strutture di programma per Windows: finestra di dialogo (la finestra principale è finestra di dialogo), struttura a console o senza finestre, struttura classica (a finestre, frame).

Chiamata Funzioni di Windows API Nel file della guida, qualsiasi funzione API viene presentata come tipo nome_funzione (FA 1, FA 2, FA 3) Tipo – tipo del valore restituito; FAx – un elenco di argomenti formali nell'ordine in cui appaiono, ad esempio int Message. Box(HWND h. Wnd, LPCTSTR lp. Testo, LPCTSTR lp. Didascalia, UINT u. Tipo); Questa funzione visualizza una finestra con un messaggio e uno o più pulsanti di uscita. Significato dei parametri: h. Wnd è un descrittore della finestra in cui apparirà la finestra del messaggio, lp. Testo: testo che apparirà nella finestra, lp. Didascalia: testo nel titolo della finestra, u. Tipo - tipo di finestra; in particolare è possibile determinare il numero di pulsanti di uscita.

Chiamata delle funzioni di messaggio int dell'API di Windows. Box(HWND h. Wnd, LPCTSTR lp. Testo, LPCTSTR lp. Didascalia, UINT u. Tipo); Quasi tutti i parametri delle funzioni API sono in realtà numeri interi a 32 bit: HWND è un numero intero a 32 bit, LPCTSTR è un puntatore a 32 bit a una stringa, UINT è un numero intero a 32 bit. Il suffisso "A" viene spesso aggiunto al nome della funzione per passare alle versioni più recenti della funzione.

Chiamata delle funzioni di messaggio int dell'API di Windows. Box(HWND h. Wnd, LPCTSTR lp. Testo, LPCTSTR lp. Didascalia, UINT u. Tipo); Quando si utilizza MASM, è necessario aggiungere @N N alla fine del nome, il numero di byte che gli argomenti passati occupano nello stack. Per le funzioni API Win 32, questo numero può essere definito come il numero di argomenti n moltiplicato per 4 (byte in ciascun argomento): N=4*n. Per chiamare una funzione, utilizzare l'istruzione assembler CALL. In questo caso tutti gli argomenti della funzione gli vengono passati tramite lo stack (comando PUSH). Direzione del passaggio degli argomenti: DA SINISTRA A DESTRA - BOTTOM UP. L'argomento u verrà inserito per primo nello stack. Tipo. La chiamata alla funzione specificata sarà simile a questa: CALL Message. Scatola. A@16

Chiamata delle funzioni di messaggio int dell'API di Windows. Box(HWND h. Wnd, LPCTSTR lp. Testo, LPCTSTR lp. Didascalia, UINT u. Tipo); Il risultato dell'esecuzione di qualsiasi funzione API è solitamente un numero intero restituito nel registro EAX. La direttiva OFFSET rappresenta un "offset in un segmento" o, tradotto in termini di linguaggio di alto livello, un "puntatore" all'inizio di una riga. La direttiva EQU, come #define in SI, definisce una costante. La direttiva EXTERN dice al traduttore che la funzione o l'identificatore è esterno a questo modulo.

Esempio di un programma “Ciao a tutti!” . 686 P. MODELLO PIATTO, STDCALL. STACK 4096. DATA MB_OK EQU 0 STR 1 DB "Il mio primo programma", 0 STR 2 DB "Ciao a tutti!", 0 HW DD ? Messaggio ESTERNO. Scatola. A@16: VICINO. CODICE AVVIO: PUSH MB_OK PUSH OFFSET STR 1 PUSH OFFSET STR 2 PUSH HW CALL Messaggio. Scatola. A@16 RET FINE AVVIO

La direttiva INVOKE Il traduttore del linguaggio MASM consente anche di semplificare le chiamate di funzione utilizzando uno strumento macro: la direttiva INVOKE: funzione INVOKE, parametro1, parametro2, ... Non è necessario aggiungere @16 alla chiamata di funzione; i parametri sono scritti esattamente nell'ordine in cui sono indicati nella descrizione della funzione. Per mezzo della macro del traduttore, i parametri vengono inseriti nello stack. Per utilizzare la direttiva INVOKE, è necessario disporre di una descrizione del prototipo della funzione utilizzando la direttiva PROTO nella forma: Messaggio. Scatola. A PROTO: DWORD, : DWORD Se un programma utilizza molte funzioni API di Win 32, è consigliabile utilizzare la direttiva include C: masm 32includeuser 32. inc

Argomento 2.5 Nozioni di base sulla programmazione del processore

All'aumentare della lunghezza del programma diventa sempre più difficile ricordare i codici delle varie operazioni. I mnemonici forniscono qualche aiuto in questo senso.

Viene chiamato il linguaggio di codifica dei comandi simbolici assemblatore.

linguaggio assemblyè un linguaggio in cui ogni enunciato corrisponde esattamente a un comando della macchina.

Assemblea chiamata conversione di un programma dal linguaggio assembly, cioè preparazione di un programma in linguaggio macchina sostituendo nomi simbolici di operazioni con codici macchina e indirizzi simbolici con numeri assoluti o relativi, nonché incorporando programmi di libreria e generando sequenze di istruzioni simboliche specificando parametri specifici in micro-team. Questo programma si trova solitamente nella ROM o viene inserito nella RAM da alcuni supporti esterni.

Il linguaggio assembly ha diverse caratteristiche che lo distinguono dai linguaggi di alto livello:

1. Questa è una corrispondenza biunivoca tra le istruzioni in linguaggio assembly e le istruzioni macchina.

2. Un programmatore in linguaggio assembly ha accesso a tutti gli oggetti e le istruzioni presenti sulla macchina target.

Comprendere le basi della programmazione nei linguaggi orientati alla macchina è utile per:



Migliore comprensione dell'architettura dei PC e uso più competente dei computer;

Sviluppare strutture più razionali di algoritmi per programmi per la risoluzione di problemi applicati;

La possibilità di visualizzare e correggere programmi eseguibili con estensione .exe e .com, compilati da qualsiasi linguaggio di alto livello, in caso di perdita dei programmi sorgente (chiamando i programmi specificati nel debugger del programma DEBUG e decompilando la loro visualizzazione in assembly lingua);

Compilazione di programmi per risolvere i problemi più critici (un programma scritto in un linguaggio orientato alla macchina è solitamente più efficace - più breve e più veloce nel 30-60% dei programmi ottenuti come risultato della traduzione da linguaggi di alto livello)

Implementare le procedure incluse nel programma principale sotto forma di frammenti separati nel caso in cui non possano essere implementate né nel linguaggio di alto livello utilizzato né utilizzando le procedure di servizio del sistema operativo.

Un programma in linguaggio assembly può essere eseguito solo su una famiglia di computer, mentre un programma scritto in un linguaggio di alto livello può potenzialmente essere eseguito su macchine diverse.

L'alfabeto del linguaggio assembly è composto da caratteri ASCII.

I numeri sono solo numeri interi. Ci sono:

I numeri binari terminano con la lettera B;

Numeri decimali che terminano con la lettera D;

I numeri esadecimali terminano con la lettera H.

RAM, registri, presentazione dati

Per una certa serie di parlamentari viene utilizzato un linguaggio di programmazione individuale: il linguaggio assembly.

Il linguaggio assembly occupa una posizione intermedia tra i codici macchina e i linguaggi di alto livello. La programmazione in questo linguaggio è più semplice. Un programma in linguaggio assembly fa un uso più efficiente delle capacità di una macchina specifica (più precisamente, un MP) rispetto a un programma in linguaggio di alto livello (che è più semplice per un programmatore che per un assemblatore). Diamo un'occhiata ai principi di base della programmazione in linguaggi orientati alla macchina usando l'esempio del linguaggio assembly per MP KR580VM80. Una metodologia generale viene utilizzata per programmare nella lingua. Tecniche tecniche specifiche per la registrazione dei programmi sono associate alle caratteristiche dell'architettura e del sistema di comando dell'MP target.

Modello software sistema a microprocessore basato su MP KR580VM80

Modello software dell'MPS secondo la Figura 1

Memoria delle porte MP

S Z AC. P C

Immagine 1

Dal punto di vista del programmatore, l'MP KR580VM80 dispone dei seguenti registri accessibili dal programma.

UN– Registro accumulatore a 8 bit. È il registro principale del MP. Qualsiasi operazione eseguita in una ALU comporta l'inserimento nell'accumulatore di uno degli operandi da elaborare. Il risultato di un'operazione nell'ALU viene solitamente memorizzato anche in A.

B, C, D, E, H, L– Registri di uso generale (GPR) a 8 bit. Memoria interiore deputato. Progettato per memorizzare le informazioni elaborate, nonché i risultati dell'operazione. Quando si elaborano parole a 16 bit, i registri formano coppie BC, DE, HL e il doppio registro è chiamato la prima lettera: B, D, H. In una coppia di registri, il più alto è il primo registro. I registri H e L hanno una proprietà speciale, utilizzata sia per memorizzare dati che per memorizzare indirizzi a 16 bit di celle RAM.

Florida– registro flag (registro dei segni) registro a 8 bit in cui sono memorizzati cinque segni del risultato dell'esecuzione di operazioni aritmetiche e logiche nell'MP. Formato FL secondo l'immagine

Bit C (CY - riporto) - riporto, impostato a 1 se si è verificato un riporto dall'ordine più alto del byte durante l'esecuzione di operazioni aritmetiche.

Bit P (parità) – parità, impostato a 1 se il numero di uno nei bit del risultato è pari.

La cifra AC è un riporto aggiuntivo, progettato per memorizzare il valore del riporto dal tetrado di ordine inferiore del risultato.

Bit Z (zero) – impostato a 1 se il risultato dell'operazione è 0.

Bit S (segno) – è impostato su 1 se il risultato è negativo e su 0 se il risultato è positivo.

SP– stack pointer, un registro a 16 bit, progettato per memorizzare l'indirizzo della cella di memoria in cui è stato scritto l'ultimo byte inserito nello stack.

RS– program counter (contatore di programma), un registro a 16 bit, progettato per memorizzare l'indirizzo della successiva istruzione da eseguire. Il contenuto del contatore del programma viene automaticamente incrementato di 1 immediatamente dopo aver recuperato il byte di istruzione successivo.

Nell'area di memoria iniziale dell'indirizzo 0000Н – 07FF si trova programma di controllo e programmi dimostrativi. Questa è l'area ROM.

0800 – 0AFF - area indirizzi per la registrazione dei programmi in studio. (RAM).

0В00 – 0ВВ0 - area di indirizzi per la scrittura dei dati. (RAM).

0ВВ0 – indirizzo iniziale dello stack. (RAM).

Uno stack è un'area di RAM appositamente organizzata destinata alla memorizzazione temporanea di dati o indirizzi. L'ultimo numero scritto nello stack viene estratto per primo. Il puntatore dello stack memorizza l'indirizzo dell'ultima cella dello stack in cui sono scritte le informazioni. Quando viene richiamata una subroutine, l'indirizzo di ritorno al programma principale viene automaticamente memorizzato nello stack. Di norma, all'inizio di ciascuna subroutine il contenuto di tutti i registri coinvolti nella sua esecuzione viene salvato nello stack e alla fine della subroutine viene ripristinato dallo stack.

Formato dei dati e struttura dei comandi del linguaggio assembly

La memoria dell'MP KR580VM80 è un array di parole di 8 bit chiamate byte.Ogni byte ha il proprio indirizzo di 16 bit, che determina la sua posizione nella sequenza delle celle di memoria. L'MP può indirizzare 65536 byte di memoria, che possono essere contenuti sia nella ROM che nella RAM.

Formato dei dati

I dati vengono archiviati in memoria come parole da 8 bit:

D7 D6 D5 D4 D3 D2 D1 D0

Il bit meno significativo è il bit 0, il bit più significativo è il bit 7.

Un comando è caratterizzato dal suo formato, cioè dal numero di bit ad esso assegnati, che sono divisi byte per byte in determinati campi funzionali.

Formato del comando

I comandi MP KR580VM80 hanno il formato a uno, due o tre byte. I comandi multibyte devono essere inseriti in lingue adiacenti. Il formato del comando dipende dalle specifiche dell'operazione eseguita.

Il primo byte del comando contiene il codice dell'operazione, scritto in forma mnemonica.

Determina il formato del comando e le azioni che devono essere eseguite dall'MP sui dati durante la sua esecuzione, nonché il metodo di indirizzamento e può anche contenere informazioni sulla posizione dei dati.

Il secondo e il terzo byte possono contenere dati su cui vengono eseguite le operazioni o indirizzi che indicano la posizione dei dati. I dati su cui vengono eseguite le azioni sono chiamati operandi.

Formato del comando a byte singolo secondo la Figura 2

Figura 4

Nei comandi in linguaggio assembly, il codice operativo ha una forma abbreviata di scrittura delle parole inglesi: una notazione mnemonica. La mnemonica (dal greco mnemonico - l'arte della memorizzazione) rende più facile ricordare i comandi in base al loro scopo funzionale.

Prima dell'esecuzione, il programma sorgente viene tradotto utilizzando un programma di traduzione chiamato assembler nel linguaggio delle combinazioni di codici - linguaggio macchina, in questa forma viene inserito nella memoria dell'MP e viene quindi utilizzato durante l'esecuzione del comando.


Metodi di indirizzamento

Tutti i codici degli operandi (input e output) devono trovarsi da qualche parte. Possono essere posizionati nei registri interni dell'MP (i più convenienti e opzione rapida). Possono trovarsi nella memoria di sistema (l'opzione più comune). Infine, possono trovarsi nei dispositivi I/O (il caso più raro). La posizione degli operandi è determinata dal codice istruzione. Esistere metodi diversi, con il quale il codice istruzione può determinare dove prendere l'operando di input e dove posizionare l'operando di output. Questi metodi sono chiamati metodi di indirizzamento.

Per MP KR580VM80 esistono i seguenti metodi di indirizzamento:

Diretto;

Registrati;

Indiretto;

Impilato.

Diretto l'indirizzamento presuppone che l'operando (di input) si trovi in ​​memoria immediatamente dopo il codice dell'istruzione. L'operando è solitamente una costante che deve essere inviata da qualche parte, aggiunta a qualcosa, ecc. i dati sono contenuti nel secondo o nel secondo e terzo byte del comando, con il byte basso di dati situato nel secondo byte del comando, e il byte alto nel terzo byte di comando.

Dritto L'indirizzamento (noto anche come assoluto) presuppone che l'operando (input o output) si trovi in ​​memoria all'indirizzo, il cui codice si trova all'interno del programma immediatamente dopo il codice dell'istruzione. Utilizzato nei comandi a tre byte.

Registrati l'indirizzamento presuppone che l'operando (ingresso o uscita) sia nel registro interno dell'MP. Utilizzato nei comandi a byte singolo

Indiretto L'indirizzamento (implicito) presuppone che il registro interno dell'MP contenga non l'operando stesso, ma il suo indirizzo in memoria.

Pila l'indirizzamento presuppone che il comando non contenga un indirizzo. Indirizzamento delle celle di memoria utilizzando il contenuto del registro SP a 16 bit (stack pointer).

Sistema di comando

Il sistema di comando dell'MP è un elenco completo di azioni elementari che l'MP è in grado di eseguire. L'MP controllato da questi comandi esegue azioni semplici, come operazioni aritmetiche e logiche elementari, trasferimento di dati, confronto di due valori, ecc. Il numero di comandi dell'MP KR580VM80 è 78 (tenendo conto delle modifiche 244).

Si distinguono i seguenti gruppi di comandi:

Trasmissione dati;

Aritmetica;

Rompicapo;

Comandi di salto;

Comandi di input/output, controllo e stack.


Simboli e abbreviazioni utilizzati nella descrizione dei comandi e nella composizione dei programmi

Simbolo Riduzione
INDIRIZZO Indirizzo a 16 bit
DATI Dati a 8 bit
DATI 16 Dati a 16 bit
PORTA Indirizzo del dispositivo I/O a 8 bit
BYTE2 Secondo byte del comando
BYTE 3 Terzo byte di comando
R, R1, R2 Uno dei registri: A, B, C, D, E, H, L
R.P. Una delle coppie di registri: B - specifica la coppia BC; D - specifica una coppia DE; H – specifica la coppia HL
RH Primo registro della coppia
R.L. Secondo registro della coppia
Λ Moltiplicazione logica
V Aggiunta logica
Addizione modulo due
M Una cella di memoria il cui indirizzo specifica il contenuto della coppia di registri HL, ovvero M = (HL)



Superiore