"Scrivi un assemblatore in C." Perché scrivere un traduttore di codice macchina per una lingua di basso livello in una lingua di livello superiore?


13

Il mio istruttore di classe a microprocessore ci ha dato un incarico e ha detto:

"Scrivi un assemblatore in C." - Mio amato professore

Quindi mi è sembrato un po 'illogico.

Se non sbaglio Assembly Language è il primo passo dal codice macchina al viaggio di lingue di livello superiore. Voglio dire che C è un linguaggio di livello superiore rispetto a Assembly. Allora, che senso ha scrivere un assemblatore in C? Che cosa stavano facendo in passato mentre l'assenza del linguaggio C? Stavano scrivendo Assembler in Machine Code?

Non ha senso per me scrivere un traduttore di codice macchina per una lingua di basso livello in una lingua di livello superiore.

Diciamo che abbiamo creato una nuovissima architettura a microprocessore che non esiste nemmeno un compilatore C per quell'architettura. Il nostro assemblatore scritto in C sarà in grado di simulare la nuova architettura? Voglio dire sarà inutile o no?

A proposito, sono consapevole che GNU Assembler e Netwide Assembler sono stati scritti in C. Mi chiedo anche perché siano scritti in C?

Infine, questo è il codice sorgente di esempio per un semplice assemblatore che il nostro professore ci ha fornito:

// to compile, gcc assembler.c -o assembler
// No error check is provided.
// Variable names cannot start with 0-9.
// hexadecimals are twos complement.
// first address of the code section is zero, data section follows the code section.
//fout tables are formed: jump table, ldi table, label table and variable table.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//Converts a hexadecimal string to integer.
int hex2int( char* hex)  
{
    int result=0;

    while ((*hex)!='\0')
    {
        if (('0'<=(*hex))&&((*hex)<='9'))
            result = result*16 + (*hex) -'0';
        else if (('a'<=(*hex))&&((*hex)<='f'))
            result = result*16 + (*hex) -'a'+10;
        else if (('A'<=(*hex))&&((*hex)<='F'))
            result = result*16 + (*hex) -'A'+10; 
        hex++;
    }
    return(result);
}


main()
{   
    FILE *fp;
        char line[100];
        char *token = NULL;
    char *op1, *op2, *op3, *label;
    char ch;
    int  chch;

    int program[1000];
    int counter=0;  //holds the address of the machine code instruction




// A label is a symbol which mark a location in a program. In the example 
// program above, the string "lpp", "loop" and "lp1" are labels.
    struct label  
    {
        int location;
        char *label;
    };
    struct label labeltable[50]; //there can be 50 labels at most in our programs
    int nooflabels = 0; //number of labels encountered during assembly.




// Jump instructions cannot be assembled readily because we may not know the value of 
// the label when we encountered a jump instruction. This happens if the label used by
// that jump instruction appear below that jump instruction. This is the situation 
// with the label "loop" in the example program above. Hence, the location of jump 
// instructions must be stored.
    struct jumpinstruction   
    {
        int location;
        char *label;
    };
    struct jumpinstruction jumptable[100]; //There can be at most 100 jumps
    int noofjumps=0;  //number of jumps encountered during assembly.    




// The list of variables in .data section and their locations.
    struct variable
    {
        int location;
        char *name;
    };
    struct variable variabletable[50]; //There can be 50 varables at most.
    int noofvariables = 0;




//Variables and labels are used by ldi instructions.
//The memory for the variables are traditionally allocated at the end of the code section.
//Hence their addresses are not known when we assemble a ldi instruction. Also, the value of 
//a label may not be known when we encounter a ldi instruction which uses that label.
//Hence, the location of the ldi instructions must be kept, and these instructions must be 
//modified when we discover the address of the label or variable that it uses.
    struct ldiinstruction   
    {
        int location;
        char *name;
    };
    struct ldiinstruction lditable[100];
    int noofldis=0;




    fp = fopen("name_of_program","r");

    if (fp != NULL)
    {
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .code section
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )
                break;
        } 
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");  //get the instruction mnemonic or label

//========================================   FIRST PASS  ======================================================
            while (token)
            {
                if (strcmp(token,"ldi")==0)        //---------------LDI INSTRUCTION--------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");                                //get the 1st operand of ldi, which is the register that ldi loads
                    op2 = strtok(NULL,"\n\t\r ");                                //get the 2nd operand of ldi, which is the data that is to be loaded
                    program[counter]=0x1000+hex2int(op1);                        //generate the first 16-bit of the ldi instruction
                    counter++;                                                   //move to the second 16-bit of the ldi instruction
                    if ((op2[0]=='0')&&(op2[1]=='x'))                            //if the 2nd operand is twos complement hexadecimal
                        program[counter]=hex2int(op2+2)&0xffff;              //convert it to integer and form the second 16-bit 
                    else if ((  (op2[0])=='-') || ((op2[0]>='0')&&(op2[0]<='9')))       //if the 2nd operand is decimal 
                        program[counter]=atoi(op2)&0xffff;                         //convert it to integer and form the second 16-bit 
                    else                                                           //if the second operand is not decimal or hexadecimal, it is a laber or a variable.
                    {                                                               //in this case, the 2nd 16-bits of the ldi instruction cannot be generated.
                        lditable[noofldis].location = counter;                 //record the location of this 2nd 16-bit  
                        op1=(char*)malloc(sizeof(op2));                         //and the name of the label/variable that it must contain
                        strcpy(op1,op2);                                        //in the lditable array.
                        lditable[noofldis].name = op1;
                        noofldis++;                                             
                    }       
                    counter++;                                                     //skip to the next memory location 
                }                                       

                else if (strcmp(token,"ld")==0)      //------------LD INSTRUCTION---------------------         
                {
                    op1 = strtok(NULL,"\n\t\r ");                //get the 1st operand of ld, which is the destination register
                    op2 = strtok(NULL,"\n\t\r ");                //get the 2nd operand of ld, which is the source register
                    ch = (op1[0]-48)| ((op2[0]-48) << 3);        //form bits 11-0 of machine code. 48 is ASCII value of '0'
                    program[counter]=0x2000+((ch)&0x00ff);       //form the instruction and write it to memory
                    counter++;                                   //skip to the next empty location in memory
                }
                else if (strcmp(token,"st")==0) //-------------ST INSTRUCTION--------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jz")==0) //------------- CONDITIONAL JUMP ------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jmp")==0)  //-------------- JUMP -----------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");           //read the label
                    jumptable[noofjumps].location = counter;    //write the jz instruction's location into the jumptable 
                    op2=(char*)malloc(sizeof(op1));         //allocate space for the label                  
                    strcpy(op2,op1);                //copy the label into the allocated space
                    jumptable[noofjumps].label=op2;         //point to the label from the jumptable
                    noofjumps++;                    //skip to the next empty location in jumptable
                    program[counter]=0x5000;            //write the incomplete instruction (just opcode) to memory
                    counter++;                  //skip to the next empty location in memory.
                }               
                else if (strcmp(token,"add")==0) //----------------- ADD -------------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");    
                    op2 = strtok(NULL,"\n\t\r ");
                    op3 = strtok(NULL,"\n\t\r ");
                    chch = (op1[0]-48)| ((op2[0]-48)<<3)|((op3[0]-48)<<6);  
                    program[counter]=0x7000+((chch)&0x00ff); 
                    counter++; 
                }
                else if (strcmp(token,"sub")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"and")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"or")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"xor")==0)
                {
                    //to be added
                }                       
                else if (strcmp(token,"not")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    op2 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op2[0]-48)<<3);
                    program[counter]=0x7500+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"mov")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"inc")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op1[0]-48)<<3);
                    program[counter]=0x7700+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"dec")==0)
                {
                                    //to be added
                }
                else //------WHAT IS ENCOUNTERED IS NOT AN INSTRUCTION BUT A LABEL. UPDATE THE LABEL TABLE--------
                {
                    labeltable[nooflabels].location = counter;  //buraya bir counter koy. error check
                    op1=(char*)malloc(sizeof(token));
                    strcpy(op1,token);
                    labeltable[nooflabels].label=op1;
                    nooflabels++;
                } 
                token = strtok(NULL,",\n\t\r ");  
            }
        }


//================================= SECOND PASS ==============================

                //supply the address fields of the jump and jz instructions from the 
        int i,j;         
        for (i=0; i<noofjumps;i++)                                                                   //for all jump/jz instructions
        {
            j=0;
            while ( strcmp(jumptable[i].label , labeltable[j].label) != 0 )             //if the label for this jump/jz does not match with the 
                j++;                                                                // jth label in the labeltable, check the next label..
            program[jumptable[i].location] +=(labeltable[j].location-jumptable[i].location-1)&0x0fff;       //copy the jump address into memory.
        }                                                     




                // search for the start of the .data segment
        rewind(fp);  
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .data, if no .data, also ok.
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".data")==0 )
                break;

        }


                // process the .data segment and generate the variabletable[] array.
        int dataarea=0;
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )  //go till the .code segment
                break;
            else if (token[strlen(token)-1]==':')
            {               
                token[strlen(token)-1]='\0';  //will not cause memory leak, as we do not do malloc
                variabletable[noofvariables].location=counter+dataarea;
                op1=(char*)malloc(sizeof(token));
                strcpy(op1,token);
                variabletable[noofvariables].name=op1;
                token = strtok(NULL,",\n\t\r ");
                if (token==NULL)
                    program[counter+dataarea]=0;
                else if (strcmp(token, ".space")==0)
                {
                    token=strtok(NULL,"\n\t\r ");
                    dataarea+=atoi(token);
                }
                else if((token[0]=='0')&&(token[1]=='x')) 
                    program[counter+dataarea]=hex2int(token+2)&0xffff; 
                else if ((  (token[0])=='-') || ('0'<=(token[0])&&(token[0]<='9'))  )
                    program[counter+dataarea]=atoi(token)&0xffff;  
                noofvariables++;
                dataarea++;
            }
        }






// supply the address fields for the ldi instructions from the variable table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<noofvariables)&&( strcmp( lditable[i].name , variabletable[j].name)!=0 ))
                j++;
            if (j<noofvariables)
                program[lditable[i].location] = variabletable[j].location;              
        } 

// supply the address fields for the ldi instructions from the label table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<nooflabels)&&( strcmp( lditable[i].name , labeltable[j].label)!=0 ))
                j++;
            if (j<nooflabels){
                program[lditable[i].location] = (labeltable[j].location)&0x0fff;
                printf("%d %d %d\n", i, j, (labeltable[j].location));   
            }           
        } 

//display the resulting tables
        printf("LABEL TABLE\n");
        for (i=0;i<nooflabels;i++)
            printf("%d %s\n", labeltable[i].location, labeltable[i].label); 
        printf("\n");
        printf("JUMP TABLE\n");
        for (i=0;i<noofjumps;i++)
            printf("%d %s\n", jumptable[i].location, jumptable[i].label);   
        printf("\n");
        printf("VARIABLE TABLE\n");
        for (i=0;i<noofvariables;i++)
            printf("%d %s\n", variabletable[i].location, variabletable[i].name);    
        printf("\n");
        printf("LDI INSTRUCTIONS\n");
        for (i=0;i<noofldis;i++)
            printf("%d %s\n", lditable[i].location, lditable[i].name);  
        printf("\n");
        fclose(fp);
        fp = fopen("RAM","w");
        fprintf(fp,"v2.0 raw\n");
        for (i=0;i<counter+dataarea;i++)
            fprintf(fp,"%04x\n",program[i]);
    }   
}

2
Nessun dispositivo esiste isolatamente. Le toolchain trasversali sono molto diffuse, in particolare per architetture minuscole.
Lars Viklund,

3
Un compilatore / assemblatore "incrociato" viene eseguito su un sistema diverso rispetto alla destinazione e produce artefatti adatti all'uso sul sistema di destinazione. Nei tempi antichi, non si aveva necessariamente l'interscambio di dati tra sistemi, ma si doveva avviare un sistema da zero in termini di se stesso. Praticamente tutto lo sviluppo moderno per le architetture è fatto su sistemi consolidati, compilando in modo incrociato tutto.
Lars Viklund,

19
Desideri scrivere l'assemblatore nel codice macchina anziché in C? Il tuo professore è gentile con te.
Winston Ewert,

2
Perché non dovresti sforzarti di scrivere tutto il tuo codice nel miglior ambiente / linguaggio di programmazione possibile? Un assemblatore non fa eccezione.
Erik Eidt,

1
Non esiste un "viaggio" fisso in nessuna direzione particolare.
whatsisname

Risposte:


17

Le persone hanno scritto assemblatori in codice macchina. Hanno anche scritto poi in linguaggio assembly - spesso un sottoinsieme della lingua che traducono da soli, quindi iniziano con una semplice versione "bootstrap" dell'assemblatore, quindi aggiungono funzionalità quando ne hanno bisogno per l'assemblatore stesso.

Tuttavia, nulla di tutto ciò è particolarmente necessario. Alla fine, un assemblatore è un programma di traduzione (di solito abbastanza semplice). Accetta un file in un formato (di testo) e scrive un file in un altro (di solito un formato di file oggetto).

Il fatto che il testo immesso rappresenti le istruzioni della macchina in un formato testuale e che il risultato rappresenti le stesse istruzioni in formato binario non fa molta differenza rispetto al linguaggio utilizzato per implementare l'assemblatore - in effetti, anche linguaggi più alti di C dato che SNOBOL e Python possono funzionare abbastanza bene - di recente (abbastanza) ho lavorato su un assemblatore scritto in Python, e ha funzionato abbastanza bene per il lavoro.

Per quanto riguarda l'avvio iniziale delle cose: in genere su un altro computer che dispone di strumenti di sviluppo decenti e simili. Se stai sviluppando un nuovo hardware, di solito inizi a scrivere un simulatore (o almeno un emulatore) per la nuova macchina, quindi all'inizio stai costruendo ed eseguendo il codice su un sistema host in ogni caso.


3
"linguaggi anche più alti di C come SNOBOL e Python possono funzionare abbastanza bene" - questo è un ottimo punto. Per NASM, non abbiamo mai considerato nulla di più alto di C, ma era il 1995 quando le prestazioni erano molto più importanti di quanto lo siano oggi e i linguaggi di alto livello erano molto meno avanzati di quanto lo siano oggi. In questi giorni, vale sicuramente la pena considerare le alternative.
Jules,

1
Non ho sentito il nome SNOBOL dagli anni '80.
pacmaninbw,

Una volta ho scritto un compilatore in Haskell. La valutazione e il concatenamento pigri della funzione hanno reso banalmente semplice scrivere un ottimizzatore spioncino per il codice macchina generato.
Thorbjørn Ravn Andersen,

10

Stai vedendo connessioni che non esistono.

"Scrivere un assemblatore" è un'attività di programmazione come qualsiasi altra attività di programmazione. Utilizzare gli strumenti per gestire l'attività più adatta a tale attività. Non c'è niente di speciale nello scrivere un assemblatore; non c'è motivo di non scriverlo in una lingua di alto livello. C è in realtà a un livello abbastanza basso e probabilmente preferirei C ++ o qualche altro linguaggio di livello superiore.

Il linguaggio assembly è in realtà totalmente inadatto per un compito come questo. I casi in cui useresti ragionevolmente il linguaggio assembly sono molto, molto rari. Solo quando devi fare cose che non possono essere espresse in una lingua di livello superiore.


1
Le altre risposte sono molto buone, ma trovo che questa sia la più semplice, specialmente con le prime due frasi. Mi stavo dicendo la stessa cosa quando leggevo la domanda.
MetalMikester,

La scrittura manuale del linguaggio assembly è oggigiorno necessaria solo per gli hack specifici dell'hardware. Ad esempio, l'impostazione della modalità protetta su alcune CPU richiede una sequenza di istruzioni specifica e una sequenza logicamente equivalente non è abbastanza buona. Praticamente tutti i normali programmi non richiedono alcuna sequenza di istruzioni specifica per l'attività che devono svolgere e di conseguenza non c'è motivo di richiedere una sequenza specifica ma solo un insieme logicamente equivalente di istruzioni. L'ottimizzazione dei compilatori fa esattamente la stessa cosa per migliorare le prestazioni di esecuzione (conteggio delle istruzioni, tempo del wall clock, dimensione della cache del codice).
Mikko Rantalainen,

9

Che cosa stavano facendo in passato mentre l'assenza del linguaggio C? Stavano scrivendo Assembler in Machine Code?

Assembly è essenzialmente un mnemonico per il codice macchina; a ogni codice operativo nel linguaggio macchina viene assegnato un assembly mnemonico, ovvero in x86 NOP è 0x90. Questo rende l'assemblatore piuttosto semplice (nb la maggior parte degli assemblatori ha due passaggi, uno da tradurre e un secondo per generare / risolvere indirizzi / riferimenti). Il primo assemblatore è stato scritto e tradotto a mano (probabilmente su carta) in codice macchina. Una versione migliore viene scritta e assemblata con l'assemblatore "assemblato" a mano, nuove funzionalità vengono aggiunte in questo modo. I compilatori per nuove lingue possono essere costruiti in questo modo; in passato era comune per i compilatori produrre output e usare un assemblatore per il loro back-end!

Non ha senso per me scrivere un traduttore di codice macchina per una lingua di basso livello in una lingua di livello superiore. ... [assemblatori esistenti] sono stati scritti in C. Mi chiedo anche perché siano scritti in C?

  • È generalmente più facile scrivere un software più complicato in una lingua di livello superiore.
  • In genere ci vuole più codice e più sforzo mentale per tenere traccia di ciò che si sta facendo in una lingua di livello inferiore rispetto a una più alta.
    • Una singola riga di C potrebbe tradursi in molte istruzioni ex. un semplice incarico in C ++ (o C) di solito genera almeno 3 istruzioni di assemblaggio (carica, modifica, memorizza;) potrebbero essere necessarie venti o più istruzioni (possibilmente centinaia,) per fare ciò che può essere fatto con una singola riga di livello superiore linguaggio (come c ++ o c.) In genere si vorrebbe dedicare il proprio tempo alla risoluzione del problema e non dedicare tempo a capire come implementare la soluzione nel codice macchina.

Mentre l'hosting è una pietra miliare comune / caratteristica desiderabile per un linguaggio di programmazione, assembly è a un livello così basso che la maggior parte dei programmatori preferirebbe lavorare a un livello superiore. Cioè nessuno vuole scrivere un assemblatore in assembly (o qualsiasi altra cosa davvero)

Diciamo che abbiamo creato una nuovissima architettura a microprocessore che non esiste nemmeno un compilatore C per quell'architettura.

Il bootstrap è il processo per ottenere una catena di strumenti su una nuova architettura.

il processo di base è:

  • scrivere un nuovo back-end che capisca come generare codice per la tua nuova CPU (o MCU)
  • compilare e testare il back-end
  • compilare in modo incrociato il compilatore desiderato (e os, ecc.) utilizzando il nuovo back-end
  • trasferire questi binari sul nuovo sistema

Non una volta devi scrivere in assembly (nuovo o vecchio) per fare questo, dovresti scegliere la lingua migliore per scrivere il tuo assemblatore / back-end / generatore di codice.

Il nostro assemblatore scritto in C sarà in grado di simulare la nuova architettura?

Gli assemblatori non simulano!

Se si stava sviluppando una nuova CPU con un linguaggio macchina nuovo (o esistente), di solito è necessario un simulatore per i test; cioè eseguire istruzioni e dati casuali attraverso il simulatore e confrontare l'output con le stesse istruzioni e dati sulla CPU del prototipo. Quindi trova i bug, correggi i bug, ripeti.


3

Tra i motivi per scrivere un assemblatore in C (o qualsiasi altra lingua di livello superiore) ci sono tutti i motivi che potresti usare per giustificare la scrittura di qualsiasi altro programma in quella lingua di livello superiore. I principali tra questi in questo caso sono probabilmente la portabilità e l'usabilità.

Portabilità: se scrivi l'assemblatore in una lingua madre hai un assemblatore su quella piattaforma. Se lo scrivi in ​​C hai un assemblatore su qualsiasi piattaforma con un compilatore C. Ciò consente, ad esempio, di compilare il codice per la piattaforma integrata sulla workstation e spostare il file binario anziché doverlo fare direttamente sul dispositivo di destinazione.

Usabilità: per la maggior parte delle persone, leggere, ragionare e modificare i programmi è molto più naturale quando il programma è in un linguaggio di livello superiore rispetto a quando è in assemblatore o (peggio) codice macchina grezzo. Pertanto, è più facile sviluppare e mantenere l'assemblatore in un linguaggio di livello superiore perché puoi pensare in termini di astrazioni offerte dalle lingue di livello superiore piuttosto che dover pensare alle minuzie di cui sei responsabile in quelle di livello inferiore.


3

Affrontando specificamente solo questa parte della domanda:

"A proposito, sono consapevole che GNU Assembler e Netwide Assembler sono stati scritti in C. Mi chiedo anche perché siano scritti in C?"

Parlando come parte del team che originariamente ha scritto l'assemblatore Netwide, la decisione ci è sembrata così ovvia al momento che praticamente non abbiamo preso in considerazione altre opzioni, ma se lo avessimo fatto saremmo arrivati ​​alla stessa conclusione, sulla base di i seguenti motivi:

  • Scriverlo in una lingua di livello inferiore sarebbe stato più difficile e richiederebbe molto più tempo.
  • Scriverlo in un linguaggio di livello superiore avrebbe potuto essere più veloce, ma c'erano considerazioni sulle prestazioni (un assemblatore usato come back-end per un compilatore, in particolare, deve essere molto veloce per evitare di rallentare troppo il compilatore, come può finiscono per gestire grandi quantità di codice, e questo era un caso d'uso che volevamo specificamente consentire) e non credo che gli autori primari avessero in comune linguaggi di livello superiore (questo era prima che Java diventasse popolare, quindi il mondo di allora tali lingue erano piuttosto frammentate). Abbiamo usato perl per alcune attività di metaprogrammazione (generazione di tabelle di istruzioni in un formato utile per il back-end del generatore di codice), ma non sarebbe stato davvero adatto all'intero programma.
  • Volevamo la portabilità del sistema operativo
  • Volevamo la portabilità della piattaforma hardware (per la produzione di cross-compilatori)

Ciò ha reso la decisione abbastanza semplice: C conforme ANSI (aka C89 in questi giorni) era l'unica lingua al momento che ha davvero colpito tutti quei punti. Se ci fosse stato un C ++ standardizzato allora noi potremmo abbiamo considerato che, ma il supporto C ++ tra sistemi diversi era piuttosto indietro a macchia di leopardo, allora, in modo da scrivere portatile C ++ è stato un po 'un incubo.


1

Una cosa non ha assolutamente nulla a che fare con l'altra. I browser Web devono essere rigorosamente scritti utilizzando HTML o PHP o qualche altra lingua di contenuto Web? No, perché dovrebbero? Le auto possono essere guidate solo da altre auto e non da esseri umani?

La conversione di un BLOB di bit (alcuni ASCII) in un altro BLOB di bit (alcuni codici macchina) è solo un'attività di programmazione, il linguaggio di programmazione che si utilizza per quell'attività è quello che si desidera. Puoi e ci sono stati assemblatori scritti in molte lingue diverse.

Le nuove lingue non possono essere scritte nella loro lingua inizialmente poiché non esiste ancora un compilatore / assemblatore per loro. Se non esiste un compilatore esistente per una nuova lingua, è necessario scrivere la prima in un'altra lingua e alla fine si avvia bootstrap se ciò ha senso anche per bootstrap. (html e un browser web, un programma che accetta alcuni bit e ne espone alcuni non verrà mai scritto in html, non può esserlo).

Non deve essere una nuova lingua, può essere una lingua esistente. I nuovi compilatori C o C ++ non si compilano automaticamente immediatamente.

Per assembly assembly e C le prime due lingue per quasi tutti i set di istruzioni nuovi o modificati. Non siamo nel passato, siamo nel presente. Possiamo facilmente generare un assemblatore in C o java o python o qualsiasi altra cosa per qualsiasi set di istruzioni e linguaggio assembly che vogliamo anche se non esiste ancora. Allo stesso modo ci sono molti compilatori C retargectable che possiamo generare il linguaggio assembly per qualsiasi linguaggio assembly che vogliamo anche se l'assemblatore non esiste ancora.

Questo è esattamente ciò che facciamo con un nuovo set di istruzioni. Prendi un computer che non è in esecuzione sul nostro nuovo set di istruzioni con il suo compilatore C che è stato compilato non per il nostro nuovo set di istruzioni né il suo assemblatore, crea un assemblatore incrociato e un compilatore incrociato. Sviluppalo e usalo mentre crei e simuli la logica. Esegui i normali cicli di sviluppo per trovare un bug, correggere un bug e testarlo di nuovo, fino a quando idealmente tutti gli strumenti e la logica sono considerati pronti. E a seconda del target, supponiamo che sia un microcontrollore incapace di eseguire un sistema operativo, non avresti mai un motivo per avviare l'avvio che tale toolchain genera ed esegue usando il set di istruzioni nativo. Dovresti sempre eseguire la compilazione incrociata. Tranne che in una macchina di ritorno, non ha mai senso scrivere l'assemblatore in assemblatore.

Sì, se potessi tornare indietro o fingere di tornare indietro, il primo assemblatore era un essere umano con una matita e un foglio di carta, che scriveva qualcosa che aveva senso per loro e poi scriveva i pezzi accanto che avevano senso per la logica. Quindi ho usato gli switch o in qualche altro modo per ottenere i bit nella macchina (google pdp8 o pdp11 o altair 8800) e farlo fare qualcosa. Inizialmente non c'erano simulatori di computer che dovevi semplicemente ottenere la logica fissandola abbastanza a lungo o anche facendo girare diversi giri del chip. Gli strumenti sono abbastanza buoni oggi che puoi ottenere il successo di A0 in quanto la cosa è molto più di un grande resistore, molto funziona, potresti ancora aver bisogno di un giro per cose che non potresti simulare completamente, ma spesso puoi avviare ora sul prima spi senza dover aspettare il terzo o il quarto giro,

Nella tua macchina di ritorno come ci si aspetterebbe, prendi quindi il tuo codice assemblato a mano e lo usi per dire carica un programma da nastro o carte. È inoltre possibile codificare manualmente un assemblatore in codice macchina, potrebbe non essere completo, ma che semplifica la programmazione. Quindi quello strumento viene utilizzato per crearne uno in grado di gestire una lingua più avanzata o complicata (un assemblatore di macro), e quello per crearne uno più complicato e si finisce con FORTRAN o BASIC o B o altro. E poi inizi a pensare al bootstrap nella stessa lingua, riscrivendo il compilatore incrociato come compilatore nativo. ovviamente per questo è idealmente necessario un ambiente o un sistema operativo di qualche tipo.

Quando stiamo creando o testando il silicio, possiamo / dobbiamo fissare i segnali che sono uno e zero. Gli strumenti ci mostreranno binario o esadecimale per impostazione predefinita ed è possibile con alcuni strumenti persino avere delle ricerche in modo che gli strumenti mostrino alcuni mnemonici (assemblaggio forse) ma spesso gli ingegneri (silicio / hardware e software) possono leggere abbastanza di il codice macchina, oppure utilizzare l'elenco / smontaggio per "vedere" le istruzioni.

A seconda di ciò che stai facendo, potresti semplicemente inserire un po 'di codice macchina nei vettori del test piuttosto che riscrivere e ricompilare o riassemblare il test. Ad esempio, se si dispone di una pipeline e si esegue il prefetch a una certa profondità, potrebbe essere necessario o si desidera riempire oltre la fine del programma un numero di nops o altre istruzioni reali in modo che la pipa non emetta istruzioni non definite e si può semplicemente scegliere di compilare il codice macchina al livello / elenco più basso anziché cercare di farlo compilare dal compilatore o assemblatore o linker.

Durante il test del processore, ovviamente, è necessario affrontare bit indefiniti e forse non preoccuparsi dei bit, ecc. Quindi è necessario andare nel codice macchina e modificare uno o più bit specifici in un'istruzione in un programma normalmente funzionante. Vale la pena scrivere un programma per farlo o semplicemente farlo a mano. Allo stesso modo durante il test ECC si desidera capovolgere uno o più bit e vedere che vengono corretti o intrappolati. Certo, è molto più facile scrivere un programma da fare o potresti semplicemente farlo a mano.

Quindi, naturalmente, ci sono lingue che non producono codice che gira su un processore, early pascal, java, python, ecc. Hai bisogno di una VM scritta in qualche altra lingua solo per usare quelle lingue. Non puoi usare il tuo compilatore java per creare un java vm, non ha senso in base al design del linguaggio.

(sì, certo dopo la pura implementazione di questi linguaggi, alla fine qualcuno costruisce un backend impuro che a volte può indirizzare insiemi di istruzioni reali non l'insieme di istruzioni vm e quindi in quel caso puoi usare il linguaggio per compilare se stesso o la sua vm se hai davvero sentito il ad esempio un front-end gnu java per gcc).

Con il passare del tempo e forse probabilmente ancora non scriviamo compilatori C in C. Usiamo cose come bisonte / flex qualche altro linguaggio di programmazione che usiamo per generare la C per noi che non volevamo scrivere da soli. Una certa percentuale è in C certo, ma una percentuale è in qualche altro linguaggio che utilizza qualche altro compilatore che immette bit e ne genera altri. A volte questo approccio viene utilizzato anche per generare un assemblatore. Fino al progettista del compilatore / assemblatore (programmi che hanno un compito di inserire bit e quindi di generare altri bit) su come lo implementeranno. I parser generati dal programma potrebbero essere programmati a mano in modo sicuro, richiedendo solo tempo, quindi la gente cerca un collegamento. Proprio come potresti scrivere un assemblatore in assemblatore, ma la gente cerca una scorciatoia.

Un browser Web è solo un programma che accetta alcuni bit e ne espone altri. Un assemblatore è solo un programma che accetta alcuni bit e ne sputa altri bit. Un compilatore è solo un programma che accetta alcuni bit e ne estrae altri. Ecc. Per tutti questi c'è un insieme documentato di regole per i bit di input e di output per ogni task di programmazione. Queste attività e bit sono abbastanza generici da poter utilizzare qualsiasi linguaggio di programmazione DISPONIBILE (che è in grado di eseguire manipolazioni di bit / byte e gestire input e output). La chiave qui è disponibile. Ottieni e prova Linux da zero libro / tutorial. Prova un pdp8 o pdp11 o altair 8800 o un altro simulatore con un pannello frontale simulato.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.