Mon instructeur de classe de microprocesseur nous a confié une mission et a déclaré:
"Écrivez un assembleur en C." - Mon professeur bien-aimé
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?
Soit dit en passant, je sais que GNU Assembler et Netwide Assembler ont été écrits en C. Je me demande également 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]);
}
}
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 selon leurs besoins 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 peut très bien fonctionner - j'ai (assez) récemment travaillé sur un assembleur écrit en Python, et cela fonctionnait assez bien pour le travail.
En ce qui concerne la façon dont vous bootstrap choses initialement: généralement sur une autre machine qui a des outils de développement décents et autres. Si vous développez du 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 au début, vous construisez et exécutez le code sur un système hôte dans tous les cas.
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.
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 (n.b. 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 pour les compilateurs de sortir Assembly, et d'utiliser 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?
Alors que auto-hébergement est un jalon/fonctionnalité 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 dans Assembly (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.
Bootstrapping est le processus d'obtention d'une chaîne d'outils sur une nouvelle architecture.
le processus de base est:
Pas une seule fois vous n'avez besoin d'écrire dans 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; Par exemple, exécutez des instructions et des données aléatoires dans le simulateur et comparez la sortie avec les mêmes instructions et données sur votre processeur prototype. Trouvez ensuite les bogues, corrigez les bogues, répétez.
Abordant spécifiquement cette partie de la question uniquement:
"D'ailleurs, je sais que GNU Assembler et Netwide Assembler 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:
Cela a rendu la décision assez facile: le C conforme 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 mai avons considéré 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.
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 que vous offrent les langages de niveau supérieur plutôt que d'avoir à penser aux minuties dont vous êtes responsable dans les langages inférieurs.
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 peuvent-elles uniquement être conduites 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 et ensuite vous bootstrap si cela a même du sens pour bootstrap. (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 premières langues 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). Il existe de nombreux compilateurs C retargeables que nous pouvons générer 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 idéalement 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 de bootstrap que de telle sorte que la chaîne d'outils génère et s'exécute à l'aide du jeu d'instructions natif. Vous serait toujours une compilation croisée. À part 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 dans la mesure où la chose est plus qu'une simple grande résistance, beaucoup de cela fonctionne, vous pouvez toujours avoir 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 le quatrième spin, simplement parce que nous avons une vaste gamme d'outils verlog et vhdl (non écrits en verilog ni vhdl ni exécutés sur le processeur nouvellement créé).
Dans votre machine de retour, comme on pourrait 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 tout simplement 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'un VM écrit dans un autre langage juste pour utiliser ces langages. Vous ne peut pas utiliser votre Java compilateur pour créer un Java vm, n'a aucun sens basé sur 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 gnu Java frontal pour 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 sort 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 simulateur pdp8 ou pdp11 ou altair 8800 ou un autre avec un panneau avant simulé.