"Écrivez un assembleur en C." Pourquoi écrire un traducteur de code machine pour une langue de bas niveau dans une langue de niveau supérieur?

13

Mon instructeur de classe de microprocesseur nous a confié une mission et a déclaré:

"Ecrire un assembleur en C." - Mon cher professeur

Cela m'a donc semblé un peu illogique.

Si je ne me trompe pas, le langage d'assemblage est la première étape du code machine vers le voyage des langages de niveau supérieur. Je veux dire que C est un langage de niveau supérieur à Assembly. Quel est donc l'intérêt d'écrire un assembleur en C? Que faisaient-ils dans le passé alors que l'absence de langage C? Écrivaient-ils Assembler dans Machine Code?

Cela n'a pas de sens pour moi d'écrire un traducteur de code machine pour une langue de bas niveau dans une langue de niveau supérieur.

Supposons que nous avons créé une toute nouvelle architecture de microprocesseur qui ne comporte même pas de compilateur C pour cette architecture. Notre assembleur écrit en C pourra-t-il simuler la nouvelle architecture? Je veux dire que ce sera inutile ou non?

D'ailleurs, je sais que l'assembleur GNU et l'assembleur Netwide ont été écrits en C. Je me demande aussi pourquoi ils sont écrits en C?

Enfin, voici l'exemple de code source d'un simple assembleur que notre professeur nous a donné:

// 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]);
    }   
}
mertyildiran
la source
2
Aucun appareil n'existe isolément. Les chaînes d'outils croisées sont très répandues, en particulier pour les petites architectures.
Lars Viklund
3
Un compilateur / assembleur «croisé» s'exécute sur un système différent de celui de la cible et produit des artefacts pouvant être utilisés sur le système cible. Dans les temps anciens, vous n'aviez pas nécessairement d'échange de données entre les systèmes, mais vous deviez démarrer un système à partir de zéro en termes de lui-même. Presque tout le développement moderne des architectures se fait sur des systèmes établis, en compilant tout.
Lars Viklund
19
Souhaitez-vous écrire l'assembleur en code machine au lieu de C? Votre professeur est gentil avec vous.
Winston Ewert
2
Pourquoi ne vous efforceriez-vous pas d'écrire tout votre code dans le meilleur environnement / langage de programmation possible? Un assembleur ne fait pas exception.
Erik Eidt
1
Il n'y a pas de «voyage» fixe dans une direction particulière.
whatsisname

Réponses:

17

Les gens ont écrit des assembleurs en code machine. Ils ont également écrit ensuite en langage assembleur - souvent un sous-ensemble du langage qu'ils traduisent eux-mêmes, ils commencent donc par une version "bootstrap" simple de l'assembleur, puis y ajoutent des fonctionnalités comme ils en ont besoin pour l'assembleur lui-même.

Cependant, rien de tout cela n'est particulièrement nécessaire. En fin de compte, un assembleur est un programme de traduction (généralement assez) simple. Il prend un fichier dans un format (texte) et écrit un fichier dans un autre (généralement un format de fichier objet).

Le fait que le texte entré représente des instructions machine dans un format textuel et que le résultat représente les mêmes instructions au format binaire ne fait pas beaucoup de différence avec le langage utilisé pour implémenter l'assembleur - en fait, des langues encore plus élevées que C telles comme SNOBOL et Python peuvent très bien fonctionner - j'ai (assez) récemment travaillé sur un assembleur écrit en Python, et cela a plutôt bien fonctionné pour le travail.

En ce qui concerne la façon dont vous démarrez les choses au départ: généralement sur une autre machine qui a des outils de développement décents et autres. Si vous développez un nouveau matériel, vous commencez généralement par écrire un simulateur (ou au moins un émulateur) pour la nouvelle machine de toute façon, donc dans un premier temps, vous construisez et exécutez le code sur un système hôte.

Jerry Coffin
la source
3
"des langages encore plus élevés que C tels que SNOBOL et Python peuvent très bien fonctionner" - c'est un très bon point. Pour NASM, nous n'avons jamais vraiment considéré quelque chose de plus élevé que C, mais c'était en 1995 où les performances étaient beaucoup plus importantes qu'elles ne le sont aujourd'hui et les langages de haut niveau étaient beaucoup moins avancés qu'aujourd'hui. De nos jours, il vaut certainement la peine d'envisager les alternatives.
Jules
1
Je n'ai pas entendu le nom SNOBOL depuis les années 80.
pacmaninbw
J'ai écrit un compilateur dans Haskell une fois. L'évaluation paresseuse et le chaînage de la fonction ont rendu trivialement simple l'écriture d'un optimiseur de judas pour le code machine généré.
Thorbjørn Ravn Andersen
10

Vous voyez des connexions qui n'existent pas.

"Ecrire un assembleur" est une tâche de programmation comme toute autre tâche de programmation. Vous utilisez les outils pour gérer la tâche qui convient le mieux à cette tâche. Il n'y a rien de spécial dans l'écriture d'un assembleur; il n'y a aucune raison de ne pas l'écrire dans une langue de haut niveau. C est en fait à un niveau assez bas, et je préférerais probablement C ++ ou un autre langage de niveau supérieur.

Le langage d'assemblage est en fait totalement inadapté à une tâche comme celle-ci. Les cas où vous utiliseriez raisonnablement le langage d'assemblage sont très, très rares. Seulement lorsque vous devez faire des choses qui ne peuvent pas être exprimées dans une langue de niveau supérieur.

gnasher729
la source
1
Les autres réponses sont très bonnes, mais je trouve que celle-ci est la plus simple, surtout avec les deux premières phrases. Je me disais la même chose en lisant la question.
MetalMikester
L'écriture manuelle du langage d'assemblage n'est de nos jours nécessaire que pour les hacks spécifiques au matériel. Par exemple, la configuration du mode protégé sur certains CPU nécessite une séquence d'instructions spécifique et une séquence logiquement équivalente n'est pas suffisante. Presque tous les programmes normaux ne nécessitent aucune séquence d'instructions spécifique pour la tâche qu'ils doivent effectuer et, par conséquent, il n'y a aucune raison d'exiger une séquence spécifique, mais seulement un ensemble d'instructions logiquement équivalent. L'optimisation des compilateurs fait exactement la même chose pour améliorer les performances d'exécution (nombre d'instructions, horloge murale, taille du cache de code).
Mikko Rantalainen
9

Que faisaient-ils dans le passé alors que l'absence de langage C? Écrivaient-ils Assembler dans Machine Code?

L'assemblage est essentiellement un mnémonique pour le code machine; chaque opcode du langage machine reçoit un mnémonique d'assemblage, c'est-à-dire que dans x86 NOP est 0x90. Cela rend l'assembleur plutôt simple (la plupart des assembleurs ont deux passes, une pour traduire et une seconde pour générer / résoudre des adresses / références.) Le premier assembleur a été écrit et traduit à la main (probablement sur papier) en code machine. Une meilleure version est écrite et assemblée avec l'assembleur «assemblé» à la main, de nouvelles fonctionnalités sont ajoutées de cette façon. Des compilateurs pour de nouvelles langues peuvent être construits de cette façon; dans le passé, il était courant que les compilateurs produisent un assemblage et utilisent un assembleur pour leur back-end!

Cela n'a pas de sens pour moi d'écrire un traducteur de code machine pour une langue de bas niveau dans une langue de niveau supérieur. ... [les assembleurs existants] ont été écrits en C. Je me demande aussi pourquoi ils sont écrits en C?

  • Il est généralement plus facile d'écrire un logiciel plus compliqué dans un langage de niveau supérieur.
  • Il faut généralement plus de code et plus d'effort mental pour suivre ce que vous faites dans un langage de niveau inférieur qu'un langage supérieur.
    • Une seule ligne de C pourrait se traduire par de nombreuses instructions ex. une simple affectation en C ++ (ou C) génère généralement au moins 3 instructions d'assemblage (charger, modifier, stocker;) cela pourrait prendre vingt instructions ou plus (peut-être des centaines) pour faire ce qui peut être fait avec une seule ligne à un niveau supérieur langage (comme c ++ ou c.) On voudrait généralement passer son temps à résoudre le problème, et ne pas passer du temps à trouver comment implémenter la solution dans le code machine.

Bien que l' auto-hébergement soit une caractéristique commune / souhaitable pour un langage de programmation, l'assemblage est si bas que la plupart des programmeurs préféreraient travailler à un niveau supérieur. C'est à dire que personne ne veut écrire un assembleur en assembleur (ou quoi que ce soit d'autre vraiment)

Supposons que nous avons créé une toute nouvelle architecture de microprocesseur qui ne comporte même pas de compilateur C pour cette architecture.

Le bootstrap est le processus d'obtention d'une chaîne d'outils sur une nouvelle architecture.

le processus de base est:

  • écrire un nouveau back-end qui comprend comment générer du code pour votre nouveau CPU (ou MCU)
  • compiler et tester votre back-end
  • cross-compile votre compilateur souhaité (et os, etc.) en utilisant votre nouveau back-end
  • transférer ces binaires vers le nouveau système

Pas une seule fois vous n'avez besoin d'écrire dans un assembly (nouveau ou ancien) pour ce faire, vous devez choisir la meilleure langue pour écrire votre assembleur / back-end / générateur de code.

Notre assembleur écrit en C pourra-t-il simuler la nouvelle architecture?

Les assembleurs ne simulent pas!

Si l'on développait un nouveau CPU avec un langage machine nouveau (ou existant), un simulateur est généralement nécessaire pour les tests; c'est-à-dire exécuter des instructions et des données aléatoires à travers le simulateur et comparer la sortie avec les mêmes instructions et données sur votre prototype de CPU. Trouvez ensuite les bogues, corrigez les bogues, répétez.

esoterik
la source
3

Parmi les raisons d'écrire un assembleur en C (ou tout autre langage de niveau supérieur) figurent toutes les raisons que vous pourriez utiliser pour justifier l'écriture de tout autre programme dans ce langage de niveau supérieur. Le principal de ceux dans ce cas est probablement la portabilité et l'utilisabilité.

Portabilité: si vous écrivez votre assembleur dans une langue maternelle, vous avez un assembleur sur cette plate-forme. Si vous l'écrivez en C, vous avez un assembleur sur n'importe quelle plate-forme avec un compilateur C. Cela vous permet, par exemple, de compiler du code pour votre plate-forme embarquée sur votre poste de travail et de déplacer le binaire plutôt que de devoir tout faire directement sur le périphérique cible.

Convivialité: Pour la plupart des gens, lire, raisonner et modifier des programmes est beaucoup plus naturel lorsque le programme est dans un langage de niveau supérieur que lorsqu'il est en assembleur ou (pire) en code machine brut. Par conséquent, il est plus facile de développer et de maintenir l'assembleur dans un langage de niveau supérieur, car vous pouvez penser en termes d'abstractions qui vous sont offertes par les langages de niveau supérieur plutôt que d'avoir à penser aux minuties dont vous êtes responsable dans les langages inférieurs.

Utilisateur non trouvé
la source
3

Abordant spécifiquement cette partie de la question uniquement:

"D'ailleurs, je sais que l'assembleur GNU et l'assembleur Netwide ont été écrits en C. Je me demande aussi pourquoi ils sont écrits en C?"

S'exprimant au sein de l'équipe qui a initialement écrit le Netwide Assembler, la décision nous a semblé si évidente à l'époque que nous n'avons envisagé aucune autre option, mais si nous l'avions fait, nous serions arrivés à la même conclusion, basée sur les raisons suivantes:

  • L'écrire dans une langue de niveau inférieur aurait été plus difficile et beaucoup plus long.
  • L'écrire dans un langage de niveau supérieur aurait pu être plus rapide, mais il y avait des considérations de performances (un assembleur utilisé comme back-end pour un compilateur, en particulier, doit être très rapide afin d'éviter de ralentir trop le compilateur, car il peut finissent par manipuler de très grandes quantités de code, et c'était un cas d'utilisation que nous voulions spécifiquement autoriser) et je ne pense pas que les auteurs principaux avaient des langages de niveau supérieur en commun (c'était avant que Java ne devienne populaire, donc le monde de ces langues étaient plutôt fragmentées à l'époque). Nous avons utilisé perl pour certaines tâches de métaprogrammation (génération de tableaux d'instructions dans un format utile pour le backend du générateur de code), mais cela n'aurait pas vraiment été adapté à l'ensemble du programme.
  • Nous voulions la portabilité du système d'exploitation
  • Nous voulions la portabilité de la plate-forme matérielle (pour la production de compilateurs croisés)

Cela a rendu la décision assez facile: le C compatible ANSI (alias C89 de nos jours) était le seul langage à l'époque qui a vraiment touché tous ces points. S'il y avait eu un C ++ normalisé à l'époque, nous aurions peut- être envisagé cela, mais la prise en charge du C ++ entre différents systèmes était plutôt inégale à l'époque, donc écrire du C ++ portable était un peu un cauchemar.

Jules
la source
1

Une chose n'a absolument rien à voir avec l'autre. Les navigateurs Web doivent-ils être strictement écrits en HTML, PHP ou tout autre langage de contenu Web? Non, pourquoi le feraient-ils? Les voitures ne peuvent-elles être conduites que par d'autres voitures et non par des humains?

La conversion d'un blob de bits (certains ascii) en un autre blob de bits (un code machine) n'est qu'une tâche de programmation, le langage de programmation que vous utilisez pour cette tâche est celui que vous voulez. Vous pouvez et il y a eu des assembleurs écrits dans de nombreuses langues différentes.

Les nouveaux langages ne peuvent pas être écrits dans leur propre langue au départ car il n'y a pas encore de compilateur / assembleur pour eux. S'il n'y a pas de compilateur existant pour une nouvelle langue, vous devez écrire le premier dans une autre langue, puis vous finissez par démarrer si cela a même du sens pour démarrer. (HTML et un navigateur Web, un programme qui prend quelques bits et crache quelques bits ne sera jamais écrit en HTML, ne peut pas l'être).

Ne doit pas nécessairement être une nouvelle langue, peut être une langue existante. Les nouveaux compilateurs C ou C ++ ne se compilent pas automatiquement dès la sortie de la porte.

Pour le langage d'assemblage et C, les deux premiers langages pour presque tous les jeux d'instructions nouveaux ou modifiés. Nous ne sommes pas dans le passé, nous sommes dans le présent. Nous pouvons facilement générer un assembleur en C ou java ou python ou quoi que ce soit pour n'importe quel jeu d'instructions et langage d'assemblage que nous voulons, même s'il n'existe pas encore. De même, il existe de nombreux compilateurs C retargeables que nous pouvons produire en langage assembleur pour tout langage assembleur que nous voulons, même si l'assembleur n'existe pas encore.

C'est exactement ce que nous faisons avec un nouveau jeu d'instructions. Prenez un ordinateur qui ne fonctionne pas sur notre nouveau jeu d'instructions avec son compilateur C qui n'a pas été compilé pour notre nouveau jeu d'instructions ni son assembleur, créez un assembleur croisé et un compilateur croisé. Développez et utilisez cela tout en créant et en simulant la logique. Passez par les cycles de développement normaux de recherche d'un bogue et corrigez un bogue et testez à nouveau, jusqu'à ce que l'idéal soit que tous les outils et la logique soient jugés prêts. Et selon la cible, disons qu'il s'agit d'un microcontrôleur incapable d'exécuter un système d'exploitation, vous n'auriez jamais de raison d'amorcer cela de telle sorte que la chaîne d'outils génère et s'exécute à l'aide du jeu d'instructions natif. Vous feriez toujours une compilation croisée. Sauf dans une machine de retour, il n'est jamais logique d'écrire l'assembleur dans l'assembleur.

Oui, si vous pouviez revenir en arrière ou faire semblant de revenir en arrière, le premier assembleur était un humain avec un crayon et du papier, qui écrivait quelque chose qui avait du sens pour eux, puis écrivait les morceaux à côté qui avaient du sens pour la logique. Ensuite, j'ai utilisé des commutateurs ou un autre moyen pour obtenir les bits dans la machine (google pdp8 ou pdp11 ou altair 8800) et lui faire faire quelque chose. Il n'y avait pas de simulateurs informatiques au départ, vous deviez juste obtenir la bonne logique en la regardant assez longtemps ou en faisant tourner plusieurs tours de la puce. Les outils sont assez bons aujourd'hui pour que vous puissiez réussir A0 en ce sens que la chose est plus qu'une simple grande résistance, beaucoup de cela fonctionne, vous aurez peut-être encore besoin d'un spin pour des choses que vous ne pourriez pas simuler complètement, mais vous pouvez souvent démarrer maintenant sur le premier spi sans avoir à attendre le troisième ou quatrième spin,

Dans votre machine de retour, comme on peut s'y attendre, vous prenez ensuite votre code assemblé à la main et vous l'utilisez pour dire charger un programme à partir d'une bande ou de cartes. Vous codez également à la main un assembleur en code machine, ce n'est peut-être pas un assemblage complet, mais un qui rend la programmation un peu plus facile. Ensuite, cet outil est utilisé pour en créer un qui peut gérer une langue plus avancée ou compliquée (un assembleur de macros), et celui-là pour en créer un de plus compliqué et vous vous retrouvez avec FORTRAN ou BASIC ou B ou autre. Et puis vous commencez à penser au démarrage dans le même langage, en réécrivant le compilateur croisé pour en faire un compilateur natif. bien sûr, vous avez idéalement besoin d'un environnement ou d'un système d'exploitation quelconque pour cela.

Lorsque nous créons ou testons du silicium, nous pouvons / devons regarder les signaux qui sont des uns et des zéros. Les outils nous montreront binaire ou hexadécimal par défaut et il est possible avec certains outils d'avoir même des recherches afin que les outils affichent quelques mnémoniques (assemblage peut-être) mais souvent les ingénieurs (silicium / matériel et logiciel) peuvent soit lire suffisamment de le code machine, ou utilisez le démontage / listage pour "voir" les instructions.

Selon ce que vous faites, vous pouvez simplement insérer du code machine dans les vecteurs de test plutôt que de réécrire et de recompiler ou de réassembler le test. Par exemple, si vous avez un pipeline et une prélecture à une certaine profondeur, vous pourriez avoir besoin ou vouloir remplir après la fin du programme un certain nombre de nops ou d'autres instructions réelles afin que le tuyau ne vomisse pas sur des instructions non définies, et vous pouvez simplement choisir de simplement remplissez le code machine au plus bas niveau de la liste / du fichier plutôt que d'essayer d'obtenir le compilateur ou l'assembleur ou l'éditeur de liens pour le faire.

Lors du test du processeur, vous devez bien sûr traiter les indéfinis et peut-être ne pas vous soucier des bits, etc. Vous devez donc entrer dans le code machine et modifier un ou plusieurs bits spécifiques dans une instruction d'un programme fonctionnant normalement. Vaut-il la peine d'écrire un programme pour le faire ou simplement de le faire à la main? De même, lorsque vous testez ECC, vous voulez retourner un ou plusieurs bits et voir qu'ils sont corrigés ou piégés. Certes, il est beaucoup plus facile d'écrire un programme ou vous pouvez le faire à la main.

Bien sûr, il y a des langages qui ne produisent pas de code qui s'exécute sur un processeur, pascal, java, python, etc. Vous avez besoin d'une machine virtuelle écrite dans un autre langage juste pour utiliser ces langages. Vous ne pouvez pas utiliser votre compilateur Java pour créer une VM Java, cela n'a aucun sens en fonction de la conception du langage.

(oui, bien sûr, après la pure implémentation de ces langages, quelqu'un construit un backend impur qui peut parfois cibler de vrais jeux d'instructions et non le jeu d'instructions vm, puis dans ce cas, vous pouvez utiliser le langage pour le compiler lui-même ou son vm si vous avez vraiment ressenti la Un front-end gnu java vers gcc par exemple).

Au fil du temps et peut-être encore, nous n'écrivons pas de compilateurs C en C. Nous utilisons des choses comme bison / flex un autre langage de programmation que nous utilisons pour générer le C pour nous que nous ne voulions pas écrire nous-mêmes. Un certain pourcentage est en C bien sûr, mais un certain pourcentage est dans un autre langage qui utilise un autre compilateur qui entre des bits et génère d'autres bits. Parfois, cette approche est également utilisée pour générer un assembleur. Au concepteur du compilateur / assembleur (programmes qui ont pour tâche de saisir des bits puis de produire d'autres bits) quant à la façon dont ils vont l'implémenter. Les analyseurs générés par le programme peuvent être programmés à la main, ce qui prend juste du temps, donc les gens recherchent un raccourci. Tout comme vous pouvez écrire un assembleur dans l'assembleur, mais les gens recherchent un raccourci.

Un navigateur Web n'est qu'un programme qui prend quelques bits et en recrache d'autres. Un assembleur est juste un programme qui prend quelques bits et en recrache d'autres. Un compilateur est juste un programme qui prend quelques bits et en recrache d'autres. Etc. Pour tout cela, il existe un ensemble documenté de règles pour les bits d'entrée et de sortie pour chaque tâche de programmation. Ces tâches et bits sont suffisamment génériques pour que tout langage de programmation DISPONIBLE puisse être utilisé (capable de manipuler des bits / octets et de gérer les entrées et les sorties). La clé ici est disponible. Obtenez et essayez le linux à partir de zéro livre / tutoriel. Essayez un pdp8 ou pdp11 ou altair 8800 ou un autre simulateur avec un panneau avant simulé.

old_timer
la source