In che modo l'assemblaggio si riferisce al codice macchina / binario

0

In che modo l'assemblaggio si riferisce al codice macchina / binario.

Ad esempio ecco come stampare sullo schermo in mikeOS (un piccolo sistema di assemblaggio puro), mikeOS utilizza NASM per assemblare.

BITS 16
    ORG 32768
    %INCLUDE 'mikedev.inc'

start:
    mov si, mystring
    call os_print_string

    call os_wait_for_key

    ret

    mystring db 'My first MikeOS program!', 0

Dove os_print_string e os_wait_for_key sono definiti come

os_print_string     equ 0003h   ; SI = zero-terminated string 

e

os_wait_for_key     equ 0012h   ; Returns AL = key pressed

in mikedev.inc rispettivamente e definito come

os_call_vectors:
    jmp os_print_string     ; 0003h

in kernal.asm

Ora nasometro deve fare molto più lavoro sotto la scena durante il montaggio, non ho idea di cosa.

In altre parole il linguaggio assembly è un wrapper in una certa misura per il codice macchina, proprio come dire che C è un wrapper per l'assembly. Se ho detto cout > > "Hello World", ad esempio in C ++, viene quindi compilato nell'equivalente dell'assemblaggio e assemblati in codice macchina.

Quindi sto cercando di capire come 0003h e 0012h sembrano dettare tutto ciò che accade quando si stampa sullo schermo. Come fanno questi due valori,

a) Comunicare al sistema CPU / PC quale bus invia i byte corrispondenti che rappresentano la stringa richiesta al bus monitor e non dire alla scheda audio.

b) In questo caso la stringa viene inviata al monitor, ovviamente, ora la mia comprensione è che si dispone di un frame buffer in grado di memorizzare un numero massimo di byte. Quindi, la risoluzione dello schermo è impostata su 1024 x 768, ovvero 786432 pixel e una frequenza di aggiornamento di 60 Hz sullo schermo, pertanto l'FB conterrà questo numero di valori di byte e quindi invierà questo numero di byte al monitor ogni 1 / 60 sec. Il primo byte nell'FB corrispondente al primo pixel sullo schermo e l'ultimo all'ultimo sullo schermo ecc.

Quindi, come fa la CPU / GPU a sapere quale byte inserire in quale posizione nell'FB. È come dire alla GPU 'ok ho bisogno di questo pixel al coord (245,232) verde quindi lascerò a te di mettere questo valore di pixel nella posizione corretta nell'FB' ecc.

Come funziona.

    
posta user12979 18.02.2014 - 13:00
fonte

4 risposte

2

Il linguaggio assembly si traduce quasi direttamente nel codice macchina. mov diventa un'istruzione mov . call diventa un'istruzione call . Gli argomenti sulla stessa riga diventano i campi argomento per quelle istruzioni. C'è un po 'di assistenza negli indirizzi di calcolo, ma non molto oltre.

Il sistema operativo può essere trattato in modo molto simile a una libreria di subroutine. I "numeri magici" che stai chiedendo sono i punti di ingresso del sistema operativo; l'istruzione call , come una chiamata di funzione nei linguaggi di livello superiore, li richiama; corrono fino al loro return , a quel punto il tuo programma riprende da dove era stato interrotto. Il manuale utente del tuo sistema operativo ti dirà quale punto di ingresso invocare per fare cosa, come impostare qualsiasi argomento richiesto (come inserire l'indirizzo della stringa da stampare nel registro si prima di chiamare os_print_string , anche se alcuni potrebbero coinvolgere valori sullo stack piuttosto che metterli in registri), e come leggere i risultati restituiti, se ce ne sono (di nuovo, quali registri avranno il risultato o cosa far uscire dallo stack).

Per quanto riguarda la domanda (b) - Tutto ciò che il sistema operativo ei suoi driver di dispositivo, o una libreria di funzioni collegata al tuo codice assembly, normalmente gestiscono per te. Se hai davvero bisogno di saperlo (ad esempio perché stai scrivendo un sistema operativo o driver di dispositivo), dovrai studiare la documentazione per il tuo hardware specifico per capire come comunicare con esso ... ma cosa ti succederà fare è scrivere una libreria di funzioni che svolgono il lavoro necessario e impacchettandola in modo che i programmi principali invochino solo quelle funzioni. In altre parole, per la maggior parte dei programmi I / O è come lavorare in un linguaggio di livello superiore; la libreria runtime fa tutto il lavoro e tutto ciò che serve sapere è come usarlo. (Il codice assemblatore sane è criticamente dipendente dalla scrittura di buone funzioni in modo da non sprecare tempo all'infinito reinventando le ruote!)

    
risposta data 18.02.2014 - 14:59
fonte
2

Rispetto a ciò che sono os_print_string = 0003h e altri "numeri magici", sono gli indirizzi dei punti di ingresso, non la subroutine vera e propria, ma una tabella di salto che a sua volta salta alla posizione effettiva della subroutine del SO.

Quando ho iniziato a imparare il linguaggio assembly, era su un sistema che non aveva molta organizzazione nel layout ROM. Se si desidera utilizzare le routine ROM, è necessario impostare i registri della CPU e chiamare direttamente l'indirizzo della routine.

Tutto bene e bene finché la ROM non viene aggiornata e tutti gli indirizzi cambiano ... fortunatamente, la ROM in questione non ha mai ricevuto alcun aggiornamento che potesse interrompere quelle chiamate. Se lo fosse, molti titoli di software avrebbero smesso di funzionare.

Le tabelle di salto risolvono questo problema garantendo che il punto di ingresso pubblicato per una routine del sistema operativo non cambi mai, anche se il codice macchina si sposta tra diverse versioni del sistema operativo. In questo caso le voci della tabella di salto sono a incrementi di 3 byte, quindi la tabella sarà simile a:

0000h    JP someaddr           C3 LL HH
0003h    JP print_string       C3 LL HH
0006h    JP yetanotheraddr     C3 LL HH
0009h    JP yetanotheraddr     C3 LL HH
000Ch    JP yetanotheraddr     C3 LL HH
000Fh    JP yetanotheraddr     C3 LL HH
0012h    JP wait_key           C3 LL HH
...
...

LL e HH possono essere cambiati a seconda della endianità della CPU, C3 è l'opcode per JP su una Z80.

Non è necessario alcun RET tra questi perché il JP NON salva l'indirizzo di esecuzione corrente su uno stack (mentre il CALL che ti ha fatto lì lo fa) quindi l'ultimo RET nella routine del sistema operativo farà proseguire l'esecuzione subito dopo la chiamata a la tabella dei salti.

Per quanto riguarda il modo in cui il sistema sa quali indirizzi nel framebuffer manipolare, ci si prende cura di os_print_string (e qualsiasi altra routine del sistema operativo correlata allo schermo è disponibile, potrebbe esserci qualcosa come os_set_point). L'indirizzo del framebuffer, l'hardware, l'implementazione, ecc. Possono cambiare, ma al tuo codice interessa solo una routine del sistema operativo che stampa i caratteri sul display (o imposta punti, o disegna linee, o ...)

    
risposta data 18.02.2014 - 19:10
fonte
2

Paul Glover ha spiegato correttamente il funzionamento di quei valori di chiamata di sistema come indirizzi che contengono istruzioni di salto per l'effettivo Routine del sistema operativo.

Cioè la CPU / GPU NON sa dove posizionare i pixel, ma le routine o i driver del sistema operativo lo fanno. Si passano gli argomenti (x, y, r, g, b) e le routine del sistema operativo mettono i valori r, g, b nelle posizioni dei byte corrette per il buffer in uso. Se eg. hai uno schermo da 10x10 pixel, il suo buffer è una matrice di 10x10x3 = 300 intensità di colore.

Se ognuno di essi è un byte, l'impostazione del pixel (3,5) su giallo potrebbe significare l'impostazione del buffer [3 * (3 * 10 + 5)] su 0xFF (completamente rosso), buffer [3 * (3 * 10 +5) da +1] a 0xFF (completamente verde) e buffer [3 * (3 * 10 + 5) da +2] a 0x00 (non del tutto blu). Lo schermo stesso (cioè il suo controllore) legge i valori dal buffer e imposta i singoli punti di colore sulle intensità specificate.

Se c'è una GPU, il processo potrebbe essere diverso in quanto le routine del sistema operativo mettono in memoria i "progetti", dicono alla GPU di visualizzarli (passando l'indirizzo o posizionandoli a un indirizzo fisso a tale scopo), che la GPU calcola i pixel e li colloca nel buffer.

buffer [n] è solo una scorciatoia per la memoria [buffer_start + n], dove la memoria può trovarsi sullo stesso bus della RAM, o potrebbe trovarsi su un bus I / O designato.

Anche le istruzioni asm sono numeri e anche i loro 'argomenti' o 'operandi' sono numeri.

Quando cerchi di capire come funzionano questi meccanismi a livello di macchina (a differenza del livello del sistema operativo), potrebbe essere utile qualche analogia con il mondo fisico.

Supponiamo di avere una macchina. Ci sono tre pedali, una leva per selezionare una marcia e un volante. Supponiamo che i pedali possano essere solo in alto o in basso, ci sono un massimo di 7 marce e 4 stati del volante. Quella configurazione ti darà una macchina a 8 bit;)

Ora l'ingegnere che costruisce quella macchina decide di collegarlo. La mia proposta sarebbe:

    Wheel                 Pedals                Gears
0 0  locked         0 0 0  roll (nop)       0 0 0  no gear
1 1  straight       1 0 0  change gear      1 1 1  7 = reverse
1 0  left           0 1 0  brake            0 0 1  1.
0 1  right          0 0 1  accelerate       0 1 0 ... 1 1 0   2., ..., 6.

Quindi, "accelerare girando a sinistra in 2a marcia" avrebbe un codice operativo assemblato di

1 0  0 0 1  0 1 0 == 0x8A
ie. (wheel << 6) | (pedals << 3) | (gears) = encoded instruction.

Qualsiasi opcode con più di un bit di pedale impostato non sarebbe valido, tranne per la combinazione di "cambio marcia" e "freno".

O forse, la marcia è solo un operando quando si cambiano le marce, e l'opcode per 'accelerare mentre si gira a sinistra nella marcia attuale' è 0x88. O forse, l'ingegnere non sono io (non lo sono), i bit sono posizionati in modo diverso, e ne derivano opcode diffferenti.

Il punto è che l'ingegnere decide, e una volta che è stato costruito, i bit sono "cablati" per avere un significato preciso, e il modo in cui sono caricati specifica il modo in cui devono essere concatenati per produrre numeri che sono istruzioni valide.

Dovresti pensare ai bit come a interruttori o all'interfaccia utente della CPU stessa. Abbastanza simile a un organo di una chiesa, in cui sono presenti leve che determinano quali gruppi di tubi ricevono la pressione dell'aria attraverso l'esecuzione di note sulla tastiera.

Per quanto riguarda la relazione tra il testo assemblatore e il codice macchina attuale:

  • ogni rappresentazione testuale di un'istruzione ha solitamente un solo modo per essere codificata.
  • ogni istruzione codificata può avere un numero arbitrario di modi per scriverlo testualmente, come per JNE e JNZ, che controllano entrambi il flag Zero e salta se non è impostato (e producono la stessa istruzione codificata), ma rappresentano diversi motivi per farlo (risultato operazione precedente! = 0 vs confronto precedente non uguale) come un modo per semplificare comprendere il codice sorgente per gli umani.
  • Potrebbero esserci istruzioni della macchina valide che non hanno alcuna rappresentazione del testo.

Se vuoi comprendere appieno la codifica delle istruzioni per qualsiasi piattaforma, non c'è modo di aggirare il riferimento alle istruzioni.

    
risposta data 21.04.2014 - 14:39
fonte
1

Quindi idealmente il linguaggio assembly ha una relazione uno a uno con il codice macchina. Una riga o un comando mnemonico (aggiungi, sub, xor ...) segue l'istruzione di una macchina (aggiungi, sub, xor ...).

Quindi, per semplificare la programmazione, aggiungono macro, quindi, proprio come le macro in altre lingue, puoi risparmiare un po 'di digitazione.

Allo stesso modo il set di istruzioni ti permette di effettuare chiamate di funzione, la sintassi è ovviamente specifica per la tua architettura e per l'assemblatore, quindi puoi salvarne alcuni digitandoli esattamente come in qualsiasi altra lingua.

Poi hai chiamate di sistema e sfortunatamente ci si affida troppo alle chiamate di sistema quando si apprende il "linguaggio assembly". Le chiamate al sistema di apprendimento sono argomentative su qualcosa che fai dopo aver imparato il linguaggio dell'assemblaggio. L'equivalente C sta imparando C quindi apprende le funzioni della libreria C, piuttosto che apprendere "alcuni" C mentre apprende le funzioni della libreria C. Chiamate di sistema, che su x86 si riducono in int this o int (10h, 21h, ecc.). Fondamentalmente si attiva un altro codice scritto tipicamente da qualcun altro che è il sistema operativo o il bios o qualsiasi altra cosa si voglia chiamare. E c'è più codice programmato in qualche lingua, compilato e / o assemblato. Ad esempio, è probabile che vi siano pile di codice dietro la semplice chiamata alla stringa di stampa. Comunque tu chiami una funzione di chiamata di sistema è definita da quel sistema operativo su cui stai vivendo, in questo caso sembra che tu abbia i numeri magici che stai chiedendo riguardo a specifici e definiti da quel sistema operativo / ambiente su cui stai lavorando cima. Dovresti ottenere la documentazione per quell'ambiente / sistema e poi imparare quali registri, ecc. Devono essere impostati su cosa e come effettuare la chiamata. Questo sarebbe vero per qualsiasi assemblatore. Se l'assemblatore ha il proprio ambiente, allora si usano le definizioni degli assemblatori per le sue chiamate di sistema e si limita a fare ciò che dice. Proprio come memset in C, è ben definito da qualche parte che è necessario creare una chiamata di funzione che ha i parametri corretti nel posto giusto come definito dalla documentazione per quella funzione, compilare e collegare correttamente, eseguirlo nel posto giusto e che la funzione avverrà come documentato e restituirà come documentato.

    
risposta data 20.02.2014 - 21:44
fonte

Leggi altre domande sui tag