Finestre.  Virus.  I Quaderni.  Internet.  ufficio.  Utilità.  Autisti

UNIVERSITÀ NAZIONALE DELL'UZBEKISTAN INtitolata A MIRZO ULUGBEK

FACOLTA' DI TECNOLOGIE INFORMATICHE

Sul tema: analisi semantica di un file EXE.

Completato:

Taskent 2003.

Prefazione.

Linguaggio dell'assembly e struttura delle istruzioni.

EXE struttura del file (analisi semantica).

Struttura di un file COM.

Come funziona e si diffonde il virus.

Disassemblatore.

Programmi.

Prefazione

La professione di programmatore è sorprendente e unica. Nel nostro tempo, la scienza e la vita non possono essere immaginate senza ultima tecnologia. Tutto ciò che è connesso all'attività umana non è completo senza la tecnologia informatica. E questo contribuisce al suo alto sviluppo e perfezione. Anche se lo sviluppo dei personal computer è iniziato non molto tempo fa, durante questo periodo sono stati compiuti passi colossali nei prodotti software e per molto tempo questi prodotti saranno ampiamente utilizzati. Il campo della conoscenza informatica è esploso, così come la relativa tecnologia. Se non prendiamo in considerazione il lato commerciale, allora possiamo dire che non ci sono estranei in questo ambito di attività professionale. Molti sono impegnati nello sviluppo di programmi non a scopo di lucro o guadagno, ma di loro 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 prestazioni di qualità, di lavoro stabile e che soddisfi tutti i requisiti del nostro tempo. Qui vale anche la pena notare la comparsa dei microprocessori negli anni '60, che vennero sostituiti un largo numero insieme della lampada. Esistono alcune varietà di microprocessori molto diverse l'una dall'altra. Questi microprocessori differiscono l'uno dall'altro in termini di profondità di bit e built-in comandi di sistema. I più comuni sono: Intel, IBM, Celeron, AMD, ecc. Tutti questi processori sono correlati all'architettura del processore avanzato da Intel. La diffusione dei microcomputer ha provocato un ripensamento degli atteggiamenti nei confronti del linguaggio assembly per due motivi principali. Innanzitutto, i programmi scritti in linguaggio assembly richiedono molta meno memoria e tempo di esecuzione. In secondo luogo, la conoscenza del linguaggio assembly e del codice macchina risultante fornisce una comprensione dell'architettura della macchina, che difficilmente viene fornita quando si lavora in un linguaggio di alto livello. Sebbene la maggior parte degli ingegneri del software sviluppi in linguaggi di alto livello come Pascal, C o Delphi, che è più facile scrivere programmi, il più potente ed efficiente Software scritta interamente o in parte in linguaggio assembly. I linguaggi di alto livello sono stati progettati per evitare gli speciali tecnicismi di particolari computer. E il linguaggio assembly, a sua volta, è progettato per le specifiche specifiche del processore. Pertanto, per scrivere un programma in linguaggio assembly per un particolare computer, è necessario conoscerne l'architettura. Al giorno d'oggi, la vista del main prodotto softwareè un file EXE. Dati gli aspetti positivi di ciò, l'autore del programma può essere certo della sua inviolabilità. Ma spesso questo è tutt'altro che vero. C'è anche un disassemblatore. Con l'aiuto di un disassemblatore, puoi scoprire interruzioni e codici di programma. Non sarà difficile per una persona esperta di assembler rifare l'intero programma secondo i suoi gusti. Forse è da qui che nasce il problema più insolubile: il virus. Perché le persone scrivono un virus? Alcuni fanno questa domanda con sorpresa, altri con rabbia, ma ciononostante ci sono ancora 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 per vari motivi. Ad alcuni piacciono le chiamate di sistema, altri migliorano le loro conoscenze in assembler. Cercherò di spiegare tutto questo nella mia tesina. Dice anche non solo della struttura del file EXE, ma anche del linguaggio assembly.

^ Lingua dell'assemblea.

È interessante seguire, a partire dall'epoca della comparsa dei primi computer fino ai giorni nostri, la trasformazione delle idee sul linguaggio assembly tra i programmatori.

C'era una volta, l'assemblatore era un linguaggio senza sapere che era impossibile far fare qualcosa di utile a un computer. A poco a poco la situazione è cambiata. Apparvero mezzi di comunicazione più convenienti con un computer. Ma, a differenza di altre lingue, l'assemblatore non è morto, inoltre, in linea di principio non poteva farlo. Perché? Alla ricerca di una risposta, proveremo a capire cos'è in generale il linguaggio assembly.

In breve, il linguaggio assembly è una rappresentazione simbolica del linguaggio macchina. Tutti i processi nella 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 per ogni tipo di computer è diverso. Questo vale anche aspetto programmi scritti in assembler e le idee di cui questo linguaggio è un riflesso.

È impossibile risolvere realmente problemi legati all'hardware (o anche, per di più, legati all'hardware, come migliorare la velocità di un programma), senza conoscere l'assembler.

Un programmatore o qualsiasi altro utente può utilizzare qualsiasi strumento di alto livello, fino alla creazione di programmi mondi virtuali e, forse, nemmeno sospettare che in realtà il computer non esegua i comandi del linguaggio in cui è scritto il suo programma, ma la loro rappresentazione trasformata sotto forma di una sequenza noiosa e noiosa di comandi di un linguaggio completamente diverso: il linguaggio macchina. Ora immaginiamo che un tale utente abbia un problema non standard o semplicemente qualcosa è andato storto. Ad esempio, il suo programma deve funzionare con un dispositivo insolito o eseguire altre azioni che richiedono la conoscenza dei principi dell'hardware del computer. Non importa quanto sia intelligente un programmatore, non importa quanto sia buono il linguaggio 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 collegare i propri moduli con moduli in assembler o supportino l'accesso al livello di programmazione assembler.

Certo, il tempo dei carri informatici è già passato. Come dice il proverbio, non puoi abbracciare l'immensità. Ma c'è qualcosa in comune, una sorta di fondamento su cui si costruisce qualsiasi seria educazione informatica. Questa è la conoscenza dei principi del funzionamento del computer, della sua architettura e del linguaggio di assemblaggio 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 evince che il computer è costituito da più dispositivi fisici, ognuno dei quali è connesso ad un'unità, detta unità di sistema. Logicamente, è chiaro che svolge il ruolo di qualche dispositivo di coordinamento. Diamo un'occhiata all'interno blocco di sistema(non c'è bisogno di provare ad entrare nel monitor - non c'è niente di interessante lì, inoltre è pericoloso): apriamo 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 mira solo a mostrare lo scopo, l'interconnessione e la composizione tipica degli elementi di un moderno personal computer.

Analizziamo il diagramma in Fig. 2 in uno stile un po' anticonvenzionale.
È nella natura umana, incontrando qualcosa di nuovo, cercare alcune associazioni che possano aiutarlo a conoscere l'ignoto. Quali associazioni evoca il computer? Per me, ad esempio, il computer è spesso associato alla persona stessa. Perché?

Una persona che creava un computer da qualche parte nel profondo di se stesso pensava di creare qualcosa di simile a se stesso. Il computer ha organi di percezione delle informazioni dal mondo esterno: questa è una tastiera, un mouse, unità disco magnetico. Sulla fig. 2 questi organi si trovano a destra di bus di sistema. Il computer ha organi che "digeriscono" le informazioni ricevute - questi sono processore e memoria di lavoro. E, infine, il computer dispone di organi vocali che emettono i risultati dell'elaborazione. Questi sono anche alcuni dei dispositivi sulla destra.

Computer moderni, ovviamente, tutt'altro che umano. Possono essere paragonati a esseri 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 istruzioni della macchina. Non importa quanto sia alto il livello di comunicazione con un computer, alla fine tutto si riduce a una noiosa e monotona sequenza di istruzioni della macchina.
Ogni comando della macchina è una sorta di stimolo per l'eccitazione di questo o quel riflesso incondizionato. La reazione a questo stimolo è sempre univoca ed è “cablata” nel blocco di microcomandi sotto forma di microprogramma. Questo microprogramma implementa anche azioni per l'implementazione di un comando della macchina, ma già a livello di segnali forniti a determinati circuiti logici del 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, in realtà, sono solo un piatto su cui il cibo (programmi) viene consegnato secondo determinate regole allo stomaco (computer). Solo lo stomaco di un computer ama il cibo dietetico e monotono: forniscigli informazioni strutturate, sotto forma di sequenze rigorosamente organizzate di zeri e uno, le cui combinazioni costituiscono il linguaggio macchina.

Pertanto, essendo esteriormente un poliglotta, il computer comprende solo una lingua: la lingua delle istruzioni della macchina. Certo, per comunicare e lavorare con un computer, non è necessario conoscere questa lingua, ma prima o poi quasi tutti i programmatori professionisti devono affrontare la necessità di impararla. Fortunatamente, il programmatore non ha bisogno di cercare di capire il significato di varie combinazioni di numeri binari, poiché già negli anni '50 i programmatori iniziarono a utilizzare l'analogo simbolico del linguaggio macchina per la programmazione, 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 quanto sopra, possiamo concludere che, poiché il linguaggio assembly per il computer è "nativo", il programma più efficiente può essere scritto solo in esso (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. Pertanto, in realtà, l'assemblatore scrive principalmente programmi che dovrebbero garantire un lavoro efficiente con l'hardware. A volte parti critiche del programma in termini di tempo di esecuzione o consumo di memoria vengono scritte in assembler. Successivamente, vengono realizzati sotto forma di subroutine e combinati con codice in un linguaggio di alto livello.

Ha senso iniziare a imparare il linguaggio assembly di qualsiasi computer solo dopo aver scoperto quale parte del computer è rimasta visibile e disponibile 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 che sono più o meno disponibili per l'uso da parte del programmatore.

Questi registri possono essere divisi in due grandi gruppi:

^ 16 registri personalizzati;

16 registri di sistema.

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

Come suggerisce il nome, i registri utente vengono chiamati perché il programmatore può usarli quando scrive i 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) scopo generale(RON)):

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

registri di stato e di controllo:

I flag registrano flag/flag;

registro del puntatore di comando eip/ip.

Riso. 3. Registri utente dei microprocessori i486 e Pentium

Perché molti di questi registri sono mostrati con una barra? No, questi non sono registri diversi: sono parti di un grande registro a 32 bit. Possono essere utilizzati nel programma come oggetti separati. Ciò è stato fatto per garantire l'operatività dei programmi scritti per i modelli di microprocessori a 16 bit più giovani di Intel, a partire dall'i8086. I microprocessori i486 e Pentium hanno principalmente registri a 32 bit. Il loro numero, ad eccezione dei registri di segmento, è uguale a quello 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 permettono di accedere alle loro parti “inferiori” (vedi Fig. 3). Mentre guardi questa figura, nota 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. Questo viene fatto, come abbiamo notato sopra, per compatibilità con i modelli di microprocessori a 16 bit più giovani di Intel.

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

eax/ax/ah/al (registro dell'accumulatore) - accumulatore.
Utilizzato per memorizzare i dati intermedi. In alcuni comandi è richiesto l'uso di questo registro;

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

ecx/cx/ch/cl (registro di conteggio) - registro contatore.
Viene utilizzato nei comandi che eseguono alcune azioni ripetitive. Il suo utilizzo è spesso implicito e nascosto nell'algoritmo del comando corrispondente.
Ad esempio, il comando loop organization, oltre a trasferire il controllo ad un comando posto ad un certo indirizzo, analizza e decrementa 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. Alcuni comandi ne richiedono l'uso; per alcuni comandi ciò avviene implicitamente.

I seguenti due registri sono 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 (Source Index register) - indice di origine.
Questo registro nelle operazioni di catena contiene l'indirizzo corrente dell'elemento nella catena di origine;

edi/di (Destination Index register) - indice del ricevente (destinatario).
Questo registro nelle operazioni a catena contiene l'indirizzo corrente nella catena di destinazione.

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

esp/sp (registro puntatore stack) - registro puntatore stack.
Contiene un puntatore all'inizio dello stack nel segmento dello stack corrente.

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

Uno stack è un'area del programma per l'archiviazione temporanea di dati arbitrari. Naturalmente, i dati possono anche essere memorizzati nel segmento dati, ma in questo caso, per ogni dato memorizzato temporaneamente, deve essere creata una cella di memoria denominata separata, che aumenta la dimensione del programma e il numero di nomi utilizzati. La comodità dello stack è che la sua area viene riutilizzata e l'archiviazione dei dati nello stack e il loro recupero da lì vengono eseguiti utilizzando comandi push e pop efficienti senza specificare alcun nome.
Lo stack viene tradizionalmente utilizzato, ad esempio, per memorizzare il contenuto dei registri utilizzati dal 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 trapelato dallo stack al ritorno dalla 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 posti nello stack, può prenderli da lì e utilizzarli nella sua esecuzione. Una caratteristica distintiva dello stack è il peculiare ordine di campionamento dei dati in esso contenuti: in ogni momento, sullo stack è disponibile solo l'elemento in cima, ovvero l'ultimo elemento caricato nello stack. Estraendo l'elemento superiore dalla pila 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) fino agli indirizzi successivamente decrescenti. L'indirizzo dell'elemento superiore accessibile è memorizzato nel registro puntatore dello stack SP. Come qualsiasi altra area della memoria del programma, lo stack deve essere incluso in un segmento o formare un segmento separato. In entrambi i casi, l'indirizzo di segmento di quel segmento viene posto nel registro di stack di segmenti SS. Pertanto, una coppia di registri SS:SP descrive l'indirizzo di una cella dello stack disponibile: SS memorizza l'indirizzo del segmento dello stack e SP memorizza l'offset degli ultimi dati memorizzati nello stack (Fig. 4, a). Prestiamo attenzione al fatto 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 tornare allo stato originale.

Il caricamento sulla pila avviene tramite apposito comando push stack. Questa istruzione prima decrementa di 2 il contenuto del puntatore dello stack, quindi posiziona l'operando all'indirizzo in SP. Se, ad esempio, vogliamo salvare temporaneamente il contenuto del registro AX nello stack, dobbiamo eseguire il comando

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

sposterà lo stack nello stato mostrato in Fig. 1.10, c. Lo stack conterrà ora due elementi, con accesso solo a quello superiore, indicato dal puntatore dello stack SP. Se, dopo qualche tempo, abbiamo bisogno di ripristinare il contenuto originale dei registri salvati nello stack, dobbiamo eseguire i comandi pop (pop) dallo stack:

Pop DS
fai scoppiare l'AX

Quanto deve essere grande lo stack? Dipende da quanto intensamente viene utilizzato nel programma. Se, ad esempio, si prevede di archiviare un array di 10.000 byte nello stack, lo stack deve avere almeno quella dimensione. Va tenuto presente che in alcuni casi lo stack viene utilizzato automaticamente dal sistema, in particolare durante l'esecuzione del comando di interruzione int 21h. Con questo comando, il processore inserisce prima l'indirizzo di ritorno nello stack, quindi il DOS inserisce lì il contenuto dei registri e altre informazioni relative al programma interrotto. Pertanto, anche se il programma non utilizza affatto lo stack, deve comunque essere presente nel programma e avere una dimensione di almeno diverse decine di parole. Nel nostro primo esempio, mettiamo in pila 128 parole, il che è decisamente sufficiente.

^ Struttura del programma dell'Assemblea

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

Le istruzioni di assemblaggio sono di quattro tipi:

comandi o istruzioni che sono controparti simboliche delle istruzioni della macchina. Durante il processo di traduzione, le istruzioni di assemblaggio vengono convertite nei corrispondenti comandi del set di istruzioni del microprocessore;

macro-comandi - frasi del testo del programma progettate in un certo modo e sostituite da altre frasi durante la traduzione;

direttive che dicono al compilatore assembler di eseguire un'azione. 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 del linguaggio assembly

Le frasi che compongono un programma possono essere un costrutto sintattico corrispondente a un comando, una macro, una direttiva o un commento. Affinché il traduttore assembler li riconosca, devono essere formati secondo determinate regole sintattiche. Per fare questo, è meglio usare una descrizione formale della sintassi della lingua, come le regole della grammatica. I modi più comuni per descrivere un linguaggio di programmazione in questo modo sono i diagrammi di sintassi e le forme Backus-Naur estese. 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 in Assembler

Riso. 6. Direttive di formato

Riso. 7. Formato di comandi e macro

Su questi disegni:

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

name - un identificatore che distingue questa direttiva da altre direttive con lo stesso nome. A seguito dell'elaborazione da parte dell'assembler di una certa direttiva, a questo nome possono essere assegnate determinate caratteristiche;

codice operazione (COP) e direttiva is notazione mnemonica istruzione macchina corrispondente, istruzione macro o direttiva traduttore;

operandi - parti del comando, direttive macro o assembler, che denotano oggetti su cui vengono eseguite operazioni. Gli operandi dell'assembler sono descritti da espressioni con costanti numeriche e di testo, etichette di variabili e identificatori che utilizzano segni di operazione e alcune parole riservate.

^ Come usare i diagrammi di sintassi? È molto semplice: tutto quello che devi fare è trovare e poi seguire il percorso dall'input del diagramma (a sinistra) al suo output (a destra). Se tale percorso esiste, allora la frase o la costruzione è sintatticamente corretta. Se non esiste tale percorso, il compilatore non accetterà questa costruzione. Quando si lavora con i diagrammi di sintassi, prestare attenzione alla direzione del bypass indicata dalle frecce, poiché tra i percorsi potrebbero esserci quelli che possono essere seguiti da destra a sinistra. Infatti, i diagrammi sintattici riflettono la logica del traduttore durante l'analisi delle frasi di input del programma.

I caratteri consentiti durante la scrittura del testo dei programmi 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 assembler sono formate da lessemi, che sono sequenze sintatticamente inseparabili di simboli linguistici validi che hanno senso per il traduttore.

I gettoni sono:

gli identificatori sono sequenze di caratteri validi utilizzati per designare oggetti di programma come codici operativi, nomi di variabili e nomi di etichette. La regola per la scrittura degli identificatori è la seguente: un identificatore può essere costituito da uno o più caratteri. Come caratteri, 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ò essere fino a 255 caratteri, sebbene il traduttore accetti solo i primi 32 caratteri e ignori il resto. È possibile regolare la lunghezza dei possibili identificatori utilizzando l'opzione riga di comando mv. Inoltre, è possibile dire al traduttore di distinguere tra lettere maiuscole e minuscole o ignorare la loro differenza (operazione predefinita).

^ Comandi in linguaggio assembly.

I comandi dell'Assembler aprono la possibilità di trasferire i propri requisiti al computer, il meccanismo per trasferire il controllo nel programma (loop e salti) per i confronti logici e l'organizzazione del programma. Tuttavia, le attività di programmazione raramente sono così semplici. La maggior parte dei programmi contiene una serie di loop in cui vengono ripetute diverse istruzioni fino al raggiungimento di un determinato requisito e vari controlli, che determinano quale delle diverse azioni eseguire. Alcuni comandi possono trasferire il controllo modificando la normale sequenza di passaggi modificando direttamente il valore di offset nel puntatore del comando. Come accennato in precedenza, ci sono vari comandi per vari processori, considereremo una serie di alcuni comandi per i processori 80186, 80286 e 80386.

Per descrivere lo stato dei flag dopo l'esecuzione di un determinato comando, utilizzeremo una selezione dalla tabella che riflette la struttura del registro dei flag eflags:

La riga inferiore di questa tabella elenca i valori dei flag dopo l'esecuzione del comando. In questo caso si usano le seguenti notazioni:

1 - dopo l'esecuzione del comando, viene impostato il flag (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 aver eseguito il comando, il flag è indefinito;

spazio - dopo aver eseguito il comando, il flag non cambia;

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

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

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

i8, i16, i32 - operando immediato di dimensione 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
(ADDIZIONE)

Aggiunta

^ Struttura dei comandi:

aggiungi destinazione, fonte

Finalità: addizione di due operandi sorgente e destinazione di dimensioni byte, parola o doppia parola.

Algoritmo di lavoro:

aggiungere gli operandi di origine e di destinazione;

scrivi 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 di destinazione (si verifica un overflow), allora questa situazione dovrebbe essere presa in considerazione analizzando il flag cf ed eventualmente utilizzando il comando adc. Ad esempio, aggiungiamo i valori nel registro ax e nell'area di memoria ch. Quando si aggiunge, è necessario tenere conto della possibilità di overflow.

Registro più registro o memoria:

|000000dw|modregr/rm|

Registro AX (AL) più valore immediato:

|0000010w|--dati--|dati 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à

^ Struttura dei comandi:

Scopo:

trasferimento del controllo a una procedura chiusa o lontana con memorizzazione dell'indirizzo del punto di ritorno sullo stack;

cambio di compito.

Algoritmo di lavoro:
determinato dal tipo di operando:

L'etichetta è vicina: il contenuto del puntatore del comando eip / ip viene inserito nello stack e un nuovo valore di indirizzo corrispondente all'etichetta viene caricato nello stesso registro;

Far label: i contenuti del puntatore di comando eip/ip e cs vengono inseriti nello stack. Quindi vengono caricati negli stessi registri i nuovi valori di indirizzo corrispondenti al segno lontano;

R16, 32 o m16, 32 - definiscono un registro o cella di memoria contenente offset nel segmento di istruzione corrente, dove viene trasferito il controllo. Quando il controllo viene trasferito, il contenuto del puntatore di 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 puntatore dipende dalla modalità operativa del microprocessore:

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

l'esecuzione del comando non influisce sui flag

Quando un'attività viene cambiata, 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 multivariato a una subroutine mantenendo 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
(confronta gli operandi)

Confronto tra operandi

^ Struttura dei comandi:

cmp operando1, operando2

Scopo: confronto di due operandi.

Algoritmo di lavoro:

eseguire la sottrazione (operando1-operando2);

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

Applicazione:
Questa istruzione viene utilizzata per confrontare due operandi mediante sottrazione, mentre gli operandi non vengono modificati. I flag vengono impostati come risultato dell'esecuzione del comando. L'istruzione cmp viene utilizzata con le istruzioni di salto condizionato e l'istruzione set byte per valore setcc.

Codice oggetto (tre formati):

Registro o memoria registrata:

|001110dw|modreg/m|

Valore immediato con registro AX (AL):

|0011110w|--dati--|dati se w=1|

Valore immediato con registro o memoria:

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

DIC
(DEcrementa operando di 1)

Operando decremento di uno

^ Struttura dei comandi:

dec operando

Scopo: decrementa di 1 il valore dell'operando in memoria o registro.

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

Applicazione:
Il comando dec viene utilizzato per decrementare di uno il valore di un byte, parola, doppia parola in memoria o registro. Si noti che il comando non influisce sul flag cf.

Registro: |01001reg|

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

DIV
(DIVIDE senza segno)

Divisione non firmata

Schema di comando:

divisore div

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

^ Algoritmo di lavoro:
Il comando richiede due operandi: il dividendo e il divisore. Il dividendo è specificato implicitamente e la sua dimensione dipende dalla dimensione del divisore, che è specificato nel comando:

se il divisore è in byte, allora il dividendo deve trovarsi nel registro ax. Dopo l'operazione, il quoziente viene posto in al e il resto in ah;

se il divisore è una parola, allora il dividendo deve trovarsi nella coppia di registri dx:ax, con la parte bassa del dividendo in ax. Dopo l'operazione, il quoziente viene posto in ax e il resto in dx;

se il divisore è una parola doppia, allora il dividendo deve trovarsi nella coppia di registro edx:eax, con la parte bassa del dividendo 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, restituendo il risultato della divisione come quoziente e il 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 entrare nel registro eax/ax/al.

Codice oggetto:

|1111011w|mod110r/m|

INT
(Interrompere)

Chiamata di una routine di servizio di interruzione

^ Struttura dei comandi:

int numero_interrupt

Scopo: chiama la routine di servizio di interrupt con il numero di interrupt specificato dall'operando dell'istruzione.

^ Algoritmo di lavoro:

spingere il registro eflags/flags e l'indirizzo di ritorno nello stack. Quando si scrive l'indirizzo di ritorno, viene prima scritto il contenuto del registro di segmento cs, quindi il contenuto del puntatore di comando eip/ip;

azzerare i flag if e tf;

trasferire il controllo al gestore di interrupt 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 istruzione. Il microprocessore, incontrando un comando con opcode 0cch nella sequenza di comandi, chiama l'interrupt handler con il vettore numero 3, che serve per comunicare con il software debugger.

La seconda forma dell'istruzione è lunga due byte, ha un codice operativo di 0cdh e consente di avviare una chiamata a una routine di servizio di interrupt con un numero di vettore nell'intervallo 0-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|

JCC
JCXZ/JECXZ
(Salta se condizione)

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

Salta se la condizione è soddisfatta

Salta se CX/ECX è zero

^ Struttura dei comandi:

etichetta jcc
etichetta jcxz
etichetta jecxz

Scopo: transizione all'interno del segmento corrente di comandi, a seconda di alcune condizioni.

^ Algoritmo di comando (ad eccezione di jcxz/jecxz):
Verifica dello stato dei flag in base al codice operativo (riflette la condizione verificata):

se la condizione sotto test è vera, vai nella cella indicata dall'operando;

se la condizione verificata è falsa, passa il controllo al comando successivo.

Algoritmo di comando jcxz/jecxz:
Verificando la condizione che il contenuto del registro ecx/cx sia uguale a zero:

se la condizione verificata

In base allo scopo, i comandi possono essere distinti (tra parentesi sono riportati esempi di codici operativi mnemonici di comandi di un assemblatore di PC come IBM PC):

esecuzione operazioni aritmetiche(ADD e ADC - addizioni e addizioni con riporto, SUB e SBB - sottrazioni e sottrazioni con prestito, MUL e IMUL - moltiplicazioni senza segno e con segno, DIV e IDIV - divisioni senza segno e con segno, CMP - confronti, ecc.);

esecuzione operazioni logiche(OR, AND, NOT, XOR, TEST, ecc.);

l trasferimento dati (MOV - invio, XCHG - scambio, IN - ingresso nel microprocessore, OUT - ritiro dal microprocessore, ecc.);

l trasferimento del controllo (rami di programma: JMP - ramo incondizionato, CALL - chiamata di procedura, RET - ritorno dalla procedura, J* - ramo condizionato, LOOP - controllo di loop, ecc.);

l elaborazione di stringhe di caratteri (MOVS - trasferimenti, CMPS - confronti, LODS - download, SCAS - scansioni. Questi comandi sono generalmente utilizzati con un prefisso (modificatore di ripetizione) REP;

l interruzioni di programma (INT - interruzioni software, INTO - interruzioni condizionali in caso di overflow, IRET - ritorno dall'interruzione);

l controllo a microprocessore (ST* e CL* - imposta e cancella flag, HLT - stop, WAIT - standby, NOP - idle, ecc.).

Un elenco completo dei comandi assembler può essere trovato nelle opere.

Comandi di trasferimento dati

l MOV dst, src - trasferimento dati (sposta - sposta da src a dst).

Trasferisce: un byte (se src e dst sono in formato byte) o una parola (se src e dst sono in formato word) tra registri o tra registro e memoria, e scrive un valore immediato in un registro o memoria.

Gli operandi dst e src devono avere lo stesso formato - byte o parola.

Src può essere di tipo: r (registro) - registro, m (memoria) - memoria, i (impedenza) - valore immediato. Dst può essere di tipo r, m. Gli operandi non possono essere usati in un comando: rsegm insieme a i; due operandi di tipo m e due operandi di tipo rsegm). L'operando i può anche essere una semplice espressione:

mov AS, (152 + 101B) / 15

La valutazione dell'espressione viene eseguita solo durante la traduzione. Le bandiere non cambiano.

l PUSH src - mette una parola in pila (push - spingere attraverso; push nello stack da src). Spinge il contenuto di src in cima allo stack - qualsiasi registro a 16 bit (incluso il segmento) o due posizioni di memoria contenenti una parola a 16 bit. Le bandiere non cambiano;

l POP dst - estrazione di una parola dallo stack (pop - pop; contare dallo stack in dst). Rimuove una parola dalla parte superiore dello stack e la inserisce in dst - qualsiasi registro a 16 bit (compreso il segmento) o due locazioni di memoria. Le bandiere non cambiano.

Lavoro del corso

Per disciplina" Programmazione del sistema»

Argomento numero 4: "Risoluzione dei problemi per le procedure"

opzione 2

UNIVERSITÀ STATALE DELLA SIBERIA ORIENTALE

TECNOLOGIA E GESTIONE

____________________________________________________________________

COLLEGIO TECNOLOGICO

ESERCIZIO

per la tesina

Disciplina:
Argomento: Problem solving per le procedure
Artista(i): Glavinskaya Arina Alexandrovna
Responsabile: Sesegma Viktorovna Dambaeva
Breve riassunto del lavoro: lo studio delle subroutine su linguaggio assembly,
risoluzione di problemi tramite subroutine
1. Parte teorica: Informazioni di base sul linguaggio assembly (set
comandi, ecc.), Organizzazione dei sottoprogrammi, Modalità di passaggio dei parametri
nei sottoprogrammi
2. Parte pratica: Sviluppa due subroutine, una delle quali converte qualsiasi lettera in maiuscolo (anche per le lettere russe) e l'altra converte la lettera in minuscolo.
converte una determinata lettera in maiuscolo e l'altro converte la lettera in minuscolo.
converte una lettera in minuscolo.
Tempistiche del progetto secondo il programma:
1. Parte teorica - 30% a settimana 7.
2. Parte pratica - 70% entro 11 settimane.
3. Protezione - 100% entro 14 settimane.
Requisiti di progettazione:
1. La liquidazione e la nota esplicativa del progetto di corso devono essere presentate in
copie elettroniche e cartacee.
2. Il volume della relazione deve essere di almeno 20 pagine dattiloscritte, esclusi gli allegati.
3. L'RPP è redatto in conformità con GOST 7.32-91 e firmato dal capo.

Responsabile del lavoro __________________

Esecutore __________________

Data di rilascio " 26 " settembre 2017 G.


Introduzione. 2

1.1 Informazioni di base sul linguaggio assembly. 3

1.1.1 Set di comandi. 4

1.2 Organizzazione di subroutine in linguaggio assembly. 4

1.3 Metodi per passare parametri nelle subroutine. 6

1.3.1 Passare i parametri attraverso i registri.. 6

1.3.2 Passaggio di parametri attraverso lo stack. 7

2 SEZIONE PRATICA.. 9

2.1 Dichiarazione del problema. 9

2.2 Descrizione della soluzione del problema. 9

2.3 Testare il programma.. 7

Conclusione. 8

Riferimenti.. 9


introduzione

È risaputo che programmare in linguaggio Assembly è difficile. Come sai, ora ci sono molte lingue diverse alto livello, che ti consentono di dedicare molto meno sforzo durante la scrittura di programmi. Naturalmente, sorge la domanda quando un programmatore potrebbe aver bisogno di utilizzare Assembler durante la scrittura di programmi. Attualmente, ci sono due aree in cui l'uso del linguaggio assembly è giustificato e spesso necessario.

In primo luogo, questi sono i cosiddetti programmi di sistema dipendenti dalla macchina, che di solito controllano vari dispositivi computer (tali programmi sono chiamati driver). In questi programmi di sistema vengono utilizzate speciali istruzioni della macchina che non devono essere utilizzate in ordinario (o, come si suol dire, applicato) programmi. Questi comandi sono impossibili o molto difficili da specificare in un linguaggio di alto livello.

Il secondo ambito di applicazione di Assembler è legato all'ottimizzazione dell'esecuzione del programma. Molto spesso, i programmi di traduzione (compilatori) da linguaggi di alto livello producono un programma in linguaggio macchina molto inefficiente. Questo di solito si applica a programmi di natura computazionale, in cui una sezione molto piccola (circa il 3-5%) del programma (il ciclo principale) viene eseguita per la maggior parte del tempo. Per risolvere questo problema si possono utilizzare i cosiddetti sistemi di programmazione multilingue, che consentono di scrivere parti del programma in lingue diverse. Di solito, la parte principale del programma è scritta in un linguaggio di programmazione di alto livello (Fortran, Pascal, C, ecc.) e le sezioni del programma in cui il tempo è critico sono scritte in Assembler. In questo caso, la velocità dell'intero programma può aumentare in modo significativo. Spesso questo l'unico modo per forzare il programma a dare un risultato in un tempo accettabile.

Questo tesinaè quello di acquisire abilità pratiche nella programmazione in linguaggio assembly.

Compiti di lavoro:

1. Studiare le informazioni di base sul linguaggio Assembler (la struttura ei componenti del programma in Assembler, il formato dei comandi, l'organizzazione delle subroutine, ecc.);

2. Studiare i tipi di operazioni bit, il formato e la logica dei comandi logici assembler;

3. Risolvere un singolo problema per l'utilizzo di subroutine in Assembler;

4.. Formulare una conclusione sul lavoro svolto.

1 SEZIONE TEORICA

Nozioni di base sul linguaggio assembly

Assembler è un linguaggio di programmazione di basso livello che è un formato per scrivere istruzioni macchina che è conveniente per la percezione umana.

I comandi in linguaggio assembly corrispondono uno a uno ai comandi del processore e, di fatto, rappresentano una comoda forma simbolica di notazione (codice mnemonico) dei comandi e dei loro argomenti. Il linguaggio assembly fornisce anche astrazioni di programmazione di base: collegamento di parti di un programma e dati tramite etichette con nomi simbolici e direttive.

Le direttive di assemblaggio consentono di includere blocchi di dati (descritti esplicitamente o letti da un file) nel programma; ripetere un certo frammento un determinato numero di volte; compilare il frammento secondo la condizione; impostare l'indirizzo di esecuzione del frammento, modificare i valori dell'etichetta durante la compilazione; utilizzare definizioni di macro con parametri, ecc.

Vantaggi e svantaggi

La quantità minima di codice ridondante (l'uso di meno comandi e accessi alla memoria). Di conseguenza - maggiore velocità e dimensioni ridotte del programma;

grandi quantità di codice, un gran numero di piccole attività aggiuntive;

Scarsa leggibilità del codice, difficoltà di supporto (debug, aggiunta funzionalità);

· la difficoltà di implementare paradigmi di programmazione e altre convenzioni un po' complesse, la complessità dello sviluppo congiunto;

Meno librerie disponibili, la loro scarsa compatibilità;

· accesso diretto all'hardware: porte input-output, registri speciali del processore;

massimo "adattamento" alla piattaforma desiderata (uso di istruzioni speciali, caratteristiche tecniche del "ferro");

· non portabilità su altre piattaforme (ad eccezione di quelle binarie compatibili).

Oltre alle istruzioni, il programma può contenere direttive: comandi che non vengono tradotti direttamente in istruzioni macchina, ma controllano il funzionamento del compilatore. Il loro set e la loro sintassi variano notevolmente e dipendono non dalla piattaforma hardware, ma dal compilatore utilizzato (dando origine a dialetti di linguaggi all'interno della stessa famiglia di architetture). Come insieme di direttive, possiamo distinguere:

Definizione dei dati (costanti e variabili);

gestione dell'organizzazione del programma in memoria e dei parametri del file di output;

impostare la modalità del compilatore;

Tutti i tipi di astrazioni (ovvero elementi di linguaggi di alto livello) - dalla progettazione di procedure e funzioni (per semplificare l'implementazione del paradigma di programmazione procedurale) a strutture e cicli condizionali (per il paradigma di programmazione strutturale);

macro.

Set di comandi

Le tipiche istruzioni in linguaggio assembly sono:

Comandi di trasferimento dati (mov, ecc.)

Comandi aritmetici (add, sub, imul, ecc.)

Operazioni logiche e bit per bit (or, and, xor, shr, ecc.)

Comandi per la gestione dell'esecuzione del programma (jmp, loop, ret, ecc.)

Comandi di chiamata di interruzione (a volte indicati come comandi di controllo): int

Comandi I/O alle porte (in, out)

Microcontrollori e microcomputer sono inoltre caratterizzati da comandi che eseguono controlli e transizioni per condizione, ad esempio:

· jne - salta se non uguale;

· jge - salta se maggiore o uguale a .

Affinché la macchina possa eseguire comandi umani a livello hardware, è necessario impostare una certa sequenza di azioni nel linguaggio di "zero e uno". Assembler diventerà un assistente in questa materia. Questa è un'utilità che funziona con la traduzione di comandi in linguaggio macchina. Tuttavia, la scrittura di un programma è un processo molto lungo e complesso. Questo linguaggio non ha lo scopo di creare luce e azioni semplici. Al momento, qualsiasi linguaggio di programmazione utilizzato (Assembler funziona bene) consente di scrivere attività speciali ed efficienti che influiscono notevolmente sul funzionamento dell'hardware. Lo scopo principale è quello di creare micro-istruzioni e piccoli codici. Questo linguaggio offre più funzionalità rispetto, ad esempio, a Pascal o C.

Breve descrizione dei linguaggi assembly

Tutti i linguaggi di programmazione sono divisi in livelli: basso e alto. Qualsiasi sistema sintattico della "famiglia" di Assembler è diverso in quanto combina contemporaneamente alcuni dei vantaggi dei linguaggi più comuni e moderni. Sono anche legati agli altri dal fatto che puoi utilizzare completamente il sistema informatico.

Una caratteristica distintiva del compilatore è la sua facilità d'uso. In questo differisce da quelli che lavorano solo con livelli alti. Se si prende in considerazione uno di questi linguaggi di programmazione, Assembler funziona due volte più velocemente e meglio. Per scriverci dentro programma leggero non ci vorrà molto.

Brevemente sulla struttura della lingua

Se parliamo in generale del lavoro e della struttura del funzionamento del linguaggio, possiamo affermare con certezza che i suoi comandi sono pienamente coerenti con i comandi del processore. Cioè, l'assemblatore utilizza codici mnemonici che sono più convenienti da scrivere per una persona.

A differenza di altri linguaggi di programmazione, Assembler utilizza etichette specifiche anziché indirizzi per scrivere celle di memoria. Vengono tradotti nelle cosiddette direttive con il processo di esecuzione del codice. Si tratta di indirizzi relativi che non pregiudicano il funzionamento del processore (non sono tradotti in linguaggio macchina), ma sono necessari per il riconoscimento da parte dell'ambiente di programmazione stesso.

Ogni riga del processore ha il suo, in questa situazione qualsiasi processo sarà corretto, incluso quello tradotto.

Il linguaggio assembly ha diverse sintassi, che saranno discusse nell'articolo.

Professionisti della lingua

L'adattamento più importante e conveniente del linguaggio assembly sarà che può essere utilizzato per scrivere qualsiasi programma per il processore, che sarà molto compatto. Se il codice è enorme, alcuni processi vengono reindirizzati a RAM. Allo stesso tempo, funzionano tutti abbastanza rapidamente e senza errori, a meno che, ovviamente, non siano controllati da un programmatore qualificato.

Driver, sistemi operativi, BIOS, compilatori, interpreti, ecc. sono tutti programmi in linguaggio assembly.

Quando si utilizza un disassemblatore che si traduce da macchina a macchina, è possibile capire facilmente come funziona questa o quell'attività di sistema, anche se non ci sono spiegazioni per essa. Tuttavia, questo è possibile solo se i programmi sono leggeri. Sfortunatamente, è abbastanza difficile capire codici non banali.

Contro della lingua

Sfortunatamente, è difficile per i programmatori alle prime armi (e spesso professionisti) capire la lingua. L'assemblatore richiede descrizione dettagliata il comando richiesto. A causa del fatto che è necessario utilizzare le istruzioni della macchina, aumenta la probabilità di azioni errate e la complessità dell'esecuzione.

Per scrivere anche di più un semplice programma, il programmatore deve essere qualificato e il suo livello di conoscenza è sufficientemente alto. Lo specialista medio, sfortunatamente, scrive spesso codici errati.

Se la piattaforma per la quale viene creato il programma viene aggiornata, tutti i comandi devono essere riscritti manualmente, come richiesto dalla lingua stessa. L'assemblatore non supporta la funzione di regolazione automatica dello stato di salute dei processi e la sostituzione di eventuali elementi.

Comandi linguistici

Come accennato in precedenza, ogni processore ha il proprio set di istruzioni. Gli elementi più semplici che vengono riconosciuti da qualsiasi tipo sono i seguenti codici:


Utilizzo delle direttive

La programmazione di microcontrollori nel linguaggio (Assembler lo consente e fa un ottimo lavoro di funzionamento) di livello più basso nella maggior parte dei casi si conclude con successo. È meglio utilizzare processori con una risorsa limitata. Per la tecnologia a 32 bit data lingua si adatta alla grande. Spesso puoi vedere le direttive nei codici. Cos'è questo? E a cosa serve?

Per cominciare, è necessario sottolineare che le direttive non sono tradotte in linguaggio macchina. Governano il funzionamento del compilatore. A differenza dei comandi, questi parametri, avendo funzioni diverse, differiscono non a causa di diversi processori, ma a causa di un diverso traduttore. Le direttive principali includono quanto segue:


origine del nome

Qual è il nome della lingua - "Assembler"? Stiamo parlando di un traduttore e di un compilatore, che crittografano i dati. Dall'inglese Assembler non significa altro che un assemblatore. Il programma non è stato compilato a mano, è stata utilizzata una struttura automatica. Inoltre, al momento, utenti e specialisti hanno già cancellato la differenza tra i termini. Spesso l'assemblatore è chiamato linguaggi di programmazione, sebbene sia solo un'utilità.

A causa del nome collettivo generalmente accettato, alcune persone presumono erroneamente che esista un'unica lingua di basso livello (o norme standard per essa). In modo che il programmatore capisca quale struttura in questione, è necessario specificare per quale piattaforma viene utilizzato questo o quel linguaggio assembly.

macro strumenti

I linguaggi assembler, che sono relativamente recenti, hanno strutture macro. Semplificano sia la scrittura che l'esecuzione di un programma. Grazie alla loro presenza, il traduttore esegue il codice scritto molte volte più velocemente. Quando crei una scelta condizionale, puoi scrivere un enorme blocco di comandi, ma è più facile usare le macro. Ti permetteranno di passare rapidamente da un'azione all'altra, nel caso in cui una condizione sia soddisfatta o meno.

Quando si utilizzano le direttive del linguaggio macro, il programmatore riceve le macro Assembler. A volte può essere ampiamente utilizzato, a volte lo è caratteristiche funzionali fino a una squadra. La loro presenza nel codice facilita il lavoro con esso, lo rende più comprensibile e visivo. Tuttavia, dovresti comunque stare attento: in alcuni casi, le macro, al contrario, peggiorano la situazione.

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

Struttura di un'istruzione in linguaggio assembly Un'istruzione in linguaggio assembly indica al compilatore quale azione deve eseguire il microprocessore. Le direttive di assemblaggio sono parametri specificati nel testo del programma che influenzano il processo di assemblaggio o le proprietà del file di output. L'operando specifica il valore iniziale dei dati (nel segmento di dati) o gli elementi su cui deve agire l'istruzione (nel segmento di codice). Un'istruzione può avere uno o due operandi o nessun operando. Il numero di operandi è implicitamente specificato dal codice dell'istruzione. Se il comando o la direttiva devono continuare sulla riga successiva, viene utilizzato il carattere barra rovesciata: "" . Per impostazione predefinita, l'assembler non distingue tra lettere maiuscole e minuscole nei comandi e nelle 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 designare nomi di variabili e nomi di etichette. L'identificativo può essere costituito da uno o più dei seguenti caratteri: tutte le lettere dell'alfabeto latino; numeri da 0 a 9; caratteri speciali: _, @, $, ? . Un punto può essere utilizzato come primo carattere dell'etichetta. I nomi di assembler riservati (direttive, operatori, nomi di comando) non possono essere utilizzati come identificatori. Il primo carattere dell'identificatore deve essere una lettera o un carattere speciale. La lunghezza massima dell'identificatore è di 255 caratteri, ma il traduttore accetta i primi 32 caratteri 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 stringa. 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 stringa. Si consiglia di scriverli in colonna per una maggiore leggibilità del programma.

Commenti L'uso dei commenti in un programma ne migliora la chiarezza, specialmente quando lo scopo di un insieme di istruzioni non è chiaro. I commenti iniziano su qualsiasi riga di un modulo sorgente con un punto e virgola (;). Tutti i caratteri a destra di "; ' alla fine della riga ci sono commenti. Il commento può contenere qualsiasi carattere stampabile, incluso lo "spazio". Il commento può estendersi sull'intera riga o seguire il comando sulla stessa riga.

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

Modelli di memoria Prima di dichiarare i segmenti, è necessario specificare il modello di memoria utilizzando una direttiva. MODEL modificatore modello_di-memoria, convenzione_di_chiamata, tipo_di_sistema operativo, parametro_stack Modelli di memoria in linguaggio assembly di base: Modello di memoria Indirizzamento del codice Indirizzamento dei dati sistema operativo Interleaving di codice e dati TINY NEAR MS-DOS Consentito SMALL NEAR MS-DOS, Windows No MEDIUM FAR NEAR MS-DOS, Windows No COMPACT NEAR FAR MS-DOS, Windows No LARGE FAR MS-DOS, Windows No HUGE FAR MS-DOS, Windows No NEAR Windows 2000, Windows XP, Windows Consentito FLAT NEAR NT,

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

Modelli di memoria Il modello flat presuppone una configurazione di programma non segmentata e viene utilizzato solo su sistemi operativi a 32 bit. Questo modello è simile al modello minuscolo in quanto i dati e il codice risiedono nello stesso segmento a 32 bit. Sviluppare un programma per il modello piatto prima della direttiva. model flat dovrebbe porre una delle direttive: . 386, . 486, . 586 o. 686. La scelta della direttiva di selezione del processore determina l'insieme dei comandi 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 codice è vicino, con tutti gli indirizzi e i puntatori a 32 bit.

modelli di memoria MODEL modifier memory_model, calling_convention, OS_type, stack_parameter Il parametro modifier 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 come vengono passati i parametri quando si chiama una procedura da altri linguaggi, inclusi i linguaggi di alto livello (C++, Pascal). Il parametro può assumere i seguenti valori: C, BASIC, FORTRAN, PASCAL, SYSCALL, STDCALL.

modelli di memoria MODEL modifier memory_model, calling_convention, OS_type, stack_parameter Il parametro OS_type è OS_DOS per impostazione predefinita ed è attualmente l'unico valore supportato per questo parametro. Il parametro stack_param è impostato su: NEARSTACK (il registro SS è uguale a DS, le regioni di dati e stack si trovano nello stesso segmento fisico) FARSTACK (il registro SS non è uguale a DS, le regioni di dati e stack si trovano in segmenti fisici diversi). L'impostazione predefinita è NEARSTACK.

Un esempio di un programma "non fare nulla". 686 P. MODELLO FLAT, STDCALL. DATI. CODE START: RET END START RET - comando a microprocessore. Assicura la corretta chiusura del programma. Il resto del programma è legato al funzionamento del traduttore. . 686 P - I comandi in modalità protetta Pentium 6 (Pentium II) sono consentiti. Questa direttiva seleziona il set di istruzioni assembler supportato specificando il modello di processore. . MODEL FLAT, stdcall - modello di memoria flat. Questo modello di memoria viene utilizzato nel sistema operativo Windows. stdcall è la procedura che chiama la convenzione da usare.

Un esempio di un programma "non fare nulla". 686 P. MODELLO FLAT, STDCALL. DATI. CODICE START: RET END START . DATA - segmento di programma contenente dati. Questo programma non usa lo stack, quindi segment. Manca la pila. . CODE - un segmento del programma contenente il codice. INIZIO - etichetta. END START - la fine del programma e un messaggio al compilatore che il programma deve essere avviato dall'etichetta START. Ogni programma deve contenere una direttiva END che segna la fine codice sorgente programmi. Tutte le righe che seguono la direttiva END vengono ignorate.L'etichetta dopo la direttiva END indica al compilatore 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 Un traduttore è un programma o mezzi tecnici A che converte un programma in uno dei linguaggi di programmazione in un programma nella lingua di destinazione, chiamato codice oggetto. Oltre a supportare la mnemonica delle istruzioni della macchina, ogni traduttore ha il proprio insieme di direttive e macro, spesso incompatibili con qualsiasi altra cosa. I principali tipi di traduttori in linguaggio assembly sono: MASM (Microsoft Assembler), TASM (Borland Turbo Assembler), FASM (Flat Assembler) - un assemblatore multi-pass distribuito liberamente scritto da Tomasz Gryshtar (polacco), NASM (Netwide Assembler) - un assemblatore gratuito per l'architettura Intel x 86 è stato creato da Simon Tatham con Julian Hall ed è attualmente sviluppato da un piccolo team di sviluppo presso Source. Fucina. netto.

Src="https://present5.com/presentation/-29367016_63610977/image-15.jpg" alt="Program translation in Microsoft Visual Studio 2005 1) Crea un progetto selezionando File->Nuovo->Progetto menù E"> Трансляция программы в Microsoft Visual Studio 2005 1) Создать проект, выбрав меню File->New->Project и указав имя проекта (hello. prj) и тип проекта: Win 32 Project. В !} opzioni aggiuntive procedura guidata del progetto per specificare "Progetto vuoto".

Src="https://present5.com/presentation/-29367016_63610977/image-16.jpg" alt="Program translation in Microsoft Visual Studio 2005 2) Nell'albero del progetto (Visualizza->Solution Explorer) aggiungi"> Трансляция программы в 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:

Traduzione del programma in Microsoft Visual Studio 2005 5) Impostare le opzioni del compilatore. Seleziona per bottone giusto nel file di progetto del menu Custom Build Rules...

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

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

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

Programmazione in OS Windows La programmazione in OS Windows si basa sull'utilizzo di funzioni API (Application Program Interface, ovvero interfaccia applicazione software). Il loro numero raggiunge il 2000. Il programma per Windows consiste in gran parte in tali chiamate. Tutte le interazioni con dispositivi esterni e le risorse del sistema operativo si verificano, di regola, attraverso tali funzioni. sala operatoria Sistema Windows utilizza un modello di memoria piatta. L'indirizzo di qualsiasi posizione 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 è una finestra di dialogo), console o struttura senza finestre, struttura classica (finestra, frame).

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

Chiamata delle funzioni API di Windows int Message. Box (HWND h. Wnd, LPCTSTR lp. Text, LPCTSTR lp. Caption, UINT u. Type); 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 stringa a 32 bit, UINT è un numero intero a 32 bit. Il suffisso "A" viene spesso aggiunto al nome delle funzioni per passare alle versioni più recenti delle funzioni.

Chiamata delle funzioni API di Windows int Message. Box (HWND h. Wnd, LPCTSTR lp. Text, LPCTSTR lp. Caption, UINT u. Type); Quando si utilizza MASM, è necessario aggiungere @N N alla fine del nome, il numero di byte occupati dagli argomenti passati nello stack. Per le funzioni API Win 32, questo numero può essere definito come il numero di argomenti n volte 4 (byte in ciascun argomento): N=4*n. Per chiamare una funzione, viene utilizzata l'istruzione CALL dell'assembler. In questo caso, tutti gli argomenti della funzione le vengono passati tramite lo stack (comando PUSH). Direzione di passaggio dell'argomento: DA SINISTRA A DESTRA - DAL BASSO IN ALTO. L'argomento u verrà inserito per primo nello stack. tipo. La chiamata della funzione specificata sarà simile a questa: CALL Message. scatola. [e-mail protetta]

Chiamata delle funzioni API di Windows int Message. Box (HWND h. Wnd, LPCTSTR lp. Text, LPCTSTR lp. Caption, UINT u. Type); Il risultato dell'esecuzione di qualsiasi funzione API è in genere un numero intero, che viene restituito nel registro EAX. La direttiva OFFSET è un "segment offset" o, in termini di linguaggio di alto livello, un "puntatore" all'inizio di una stringa. La direttiva EQU, come #define in C, definisce una costante. La direttiva EXTERN dice al compilatore che una funzione o un identificatore è esterno al modulo.

Un esempio del programma "Ciao a tutti!" . 686 P. MODELLO FLAT, 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. [e-mail protetta]: VICINO. CODICE INIZIO: PUSH MB_OK PUSH OFFSET STR 1 PUSH OFFSET STR 2 PUSH HW CALL Messaggio. scatola. [e-mail protetta] RITORNO FINE INIZIO

La direttiva INVOKE Il traduttore di lingua MASM consente anche di semplificare la chiamata 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 riportati nella descrizione della funzione. le macro del traduttore inseriscono i parametri 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 noti un errore, seleziona una parte di testo e premi Ctrl + Invio
CONDIVIDERE: