Mein Mikroprozessor Klassenlehrer gab uns einen Auftrag und sagte:
"Schreiben Sie einen Assembler in C." - Mein geliebter Professor
Es kam mir ein bisschen unlogisch vor.
Wenn ich mich nicht irre, ist die Assemblersprache der erste Schritt vom Maschinencode zum Erlernen übergeordneter Sprachen. Ich meine, C ist eine höhere Sprache als Assembly. Was bringt es also, einen Assembler in C zu schreiben? Was machten sie in der Vergangenheit, während die C-Sprache fehlte? Haben sie Assembler in Maschinencode geschrieben?
Es macht für mich keinen Sinn, einen Maschinencode-Übersetzer für eine niedrigere Sprache in einer höheren Sprache zu schreiben.
Angenommen, wir haben eine brandneue Mikroprozessorarchitektur erstellt, für die es nicht einmal einen C-Compiler gibt. Wird unser in C geschriebener Assembler die neue Architektur simulieren können? Ich meine, wird es nutzlos sein oder nicht?
Übrigens ist mir bekannt, dass GNU Assembler und Netwide Assembler in C geschrieben wurden. Ich frage mich auch, warum sie in C geschrieben wurden.
Zuletzt ist dies der Beispielquellcode für einen einfachen Assembler, den uns unser Professor gegeben hat:
// 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]);
}
}
quelle
Antworten:
Die Leute haben Assembler in Maschinencode geschrieben. Sie haben dann auch in Assemblersprache geschrieben - oft eine Teilmenge der Sprache, die sie selbst übersetzen. Sie beginnen also mit einer einfachen "Bootstrap" -Version des Assemblers und fügen ihr dann Funktionen hinzu, wenn sie sie für den Assembler selbst benötigen.
Nichts davon ist jedoch eine besondere Notwendigkeit. Am Ende ist ein Assembler ein (normalerweise recht einfaches) Übersetzungsprogramm. Es nimmt eine Datei in einem (Text-) Format auf und schreibt eine Datei in einem anderen (normalerweise einem Objektdateiformat).
Die Tatsache, dass der eingegebene Text Maschinenanweisungen in einem Textformat darstellt und das Ergebnis dieselben Anweisungen im Binärformat darstellt, hat keinen großen Einfluss auf die Sprache, in der der Assembler implementiert wird - in der Tat sogar in höheren Sprachen als C, z Da SNOBOL und Python recht gut funktionieren können, habe ich (ziemlich) kürzlich an einem Assembler gearbeitet, der in Python geschrieben wurde, und er hat für diesen Job ziemlich gut funktioniert.
So weit wie Sie die Dinge anfangs booten: Normalerweise auf einem anderen Computer mit anständigen Entwicklungstools und so weiter. Wenn Sie neue Hardware entwickeln, schreiben Sie normalerweise ohnehin einen Simulator (oder zumindest einen Emulator) für den neuen Computer. Zunächst müssen Sie den Code also auf einem bestimmten Hostsystem erstellen und ausführen.
quelle
Sie sehen Verbindungen, die nicht existieren.
"Assembler schreiben" ist wie jede andere Programmieraufgabe eine Programmieraufgabe. Sie verwenden die Tools, um die Aufgabe zu erledigen, die für diese Aufgabe am besten geeignet ist. Es ist nichts Besonderes, einen Assembler zu schreiben. Es gibt überhaupt keinen Grund, es nicht in einer höheren Sprache zu schreiben. C ist eigentlich auf einem recht niedrigen Niveau, und ich würde wahrscheinlich C ++ oder eine andere höhere Sprache bevorzugen.
Die Assemblersprache ist für eine solche Aufgabe eigentlich völlig ungeeignet. Die Fälle, in denen Sie vernünftigerweise Assemblersprache verwenden würden, sind sehr, sehr selten. Nur wenn Sie Dinge tun müssen, die nicht in einer höheren Sprache ausgedrückt werden können.
quelle
Assembly ist im Wesentlichen eine Kurzbezeichnung für Maschinencode. Jeder Opcode in der Maschinensprache erhält eine Assembly-Mnemonik, dh in x86 ist NOP 0x90. Dies macht Assembler ziemlich einfach (nb die meisten Assembler haben zwei Durchgänge, einen zum Übersetzen und einen zweiten zum Erzeugen / Auflösen von Adressen / Referenzen.) Der erste Assembler wurde von Hand (wahrscheinlich auf Papier) in Maschinencode geschrieben und übersetzt. Eine bessere Version wird mit dem handmontierten Assembler geschrieben und zusammengestellt. Auf diese Weise werden neue Funktionen hinzugefügt. Auf diese Weise können Compiler für neue Sprachen erstellt werden. In der Vergangenheit war es üblich, dass Compiler Assemblys ausgeben und einen Assembler für ihr Back-End verwenden!
Während Selbsthosting ein häufiger Meilenstein / wünschenswertes Merkmal für eine Programmiersprache ist, ist die Assemblierungsstufe so niedrig, dass die meisten Programmierer es vorziehen würden, auf einer höheren Stufe zu arbeiten. Dh niemand möchte einen Assembler in Assembler schreiben (oder irgendetwas anderes wirklich)
Beim Bootstrapping wird eine Toolkette für eine neue Architektur erstellt.
Der grundlegende Prozess ist:
Um dies zu tun, müssen Sie nicht einmal in Assembly (neu oder alt) schreiben. Sie sollten die beste Sprache für das Schreiben Ihres Assemblers / Back-End / Code-Generators auswählen.
Assembler simulieren nicht!
Wenn eine neue CPU mit einer neuen (oder vorhandenen) Maschinensprache entwickelt wurde, ist normalerweise ein Simulator zum Testen erforderlich. Führen Sie also zufällige Anweisungen und Daten durch den Simulator und vergleichen Sie die Ausgabe mit denselben Anweisungen und Daten auf Ihrer Prototyp-CPU. Dann finden Sie Fehler, beheben Sie Fehler, wiederholen Sie.
quelle
Zu den Gründen, einen Assembler in C (oder einer anderen höheren Programmiersprache) zu schreiben, gehören alle Gründe, die Sie möglicherweise angeben, um das Schreiben eines anderen Programms in dieser höheren Programmiersprache zu rechtfertigen. Das Wichtigste in diesem Fall ist wahrscheinlich die Portabilität und Benutzerfreundlichkeit.
Portabilität: Wenn Sie Ihren Assembler in einer Muttersprache schreiben, haben Sie einen Assembler auf dieser Plattform. Wenn Sie es in C schreiben, haben Sie einen Assembler auf jeder Plattform mit einem C-Compiler. Auf diese Weise können Sie beispielsweise Code für Ihre eingebettete Plattform auf Ihrer Workstation kompilieren und die Binärdatei verschieben, anstatt alles direkt auf dem Zielgerät ausführen zu müssen.
Benutzerfreundlichkeit: Für die meisten Menschen ist das Lesen, Überlegen und Ändern von Programmen weitaus natürlicher, wenn das Programm in einer höheren Programmiersprache vorliegt als in Assembler- oder (schlechteren) Raw-Maschinencode. Daher ist es einfacher, den Assembler in einer höheren Sprache zu entwickeln und zu verwalten, da Sie in Bezug auf die Abstraktionen denken können, die Ihnen von den höheren Sprachen geboten werden, anstatt über die Minutien nachdenken zu müssen, für die Sie in niedrigeren Sprachen verantwortlich sind.
quelle
Nur auf diesen Teil der Frage eingehen:
"Übrigens ist mir bekannt, dass GNU Assembler und Netwide Assembler in C geschrieben wurden. Ich frage mich auch, warum sie in C geschrieben wurden."
Als Teil des Teams, das ursprünglich den Netwide Assembler geschrieben hat, erschien uns die Entscheidung zu diesem Zeitpunkt so offensichtlich, dass wir grundsätzlich keine anderen Optionen in Betracht zogen die folgenden Gründe:
Dies machte die Entscheidung ziemlich einfach: ANSI-konformes C (auch bekannt als C89) war zu diesem Zeitpunkt die einzige Sprache, die all diese Punkte wirklich traf. Hätte es damals ein standardisiertes C ++ gegeben, hätten wir das vielleicht in Betracht gezogen, aber die C ++ - Unterstützung zwischen verschiedenen Systemen war damals ziemlich lückenhaft, so dass das Schreiben von portablem C ++ ein Albtraum war.
quelle
Eine Sache hat absolut nichts mit der anderen zu tun. Sollen Webbrowser ausschließlich mit HTML, PHP oder einer anderen Sprache für Webinhalte geschrieben werden? Nein, warum sollten sie? Können Autos nur von anderen Autos und nicht von Menschen gefahren werden?
Das Konvertieren eines Bitblobs (einige ASCII-Werte) in einen anderen Bitblob (einige Maschinencodes) ist nur eine Programmieraufgabe. Die für diese Aufgabe verwendete Programmiersprache ist die von Ihnen gewünschte. Sie können und es gab Assembler, die in vielen verschiedenen Sprachen geschrieben wurden.
Neue Sprachen können anfänglich nicht in ihrer eigenen Sprache geschrieben werden, da es für sie noch keinen Compiler / Assembler gibt. Wenn es keinen Compiler für eine neue Sprache gibt, müssen Sie den ersten in einer anderen Sprache schreiben und dann booten, wenn dies überhaupt Sinn macht. (html und ein Webbrowser, ein Programm, das ein paar Bits aufnimmt und ein paar ausspuckt, wird niemals in html geschrieben, kann nicht sein).
Muss keine neue Sprache sein, kann eine existierende sein. Neue C- oder C ++ - Compiler kompilieren sich nicht automatisch von Anfang an.
Für Assembler und C die ersten beiden Sprachen für fast alle neuen oder geänderten Befehlssätze. Wir sind nicht in der Vergangenheit, wir sind in der Gegenwart. Wir können leicht einen Assembler in C oder Java oder Python oder was auch immer für jeden Befehlssatz und jede Assemblersprache generieren, die wir wollen, auch wenn sie noch nicht existieren. Ebenso gibt es viele unvergessliche C-Compiler, mit denen wir Assemblersprachen für jede gewünschte Assemblersprache ausgeben können, auch wenn der Assembler noch nicht vorhanden ist.
Genau das machen wir mit einem neuen Befehlssatz. Nehmen Sie einen Computer, auf dem unser neuer Befehlssatz nicht ausgeführt wird, mit seinem C-Compiler, der weder für unseren neuen Befehlssatz noch für seinen Assembler kompiliert wurde, und erstellen Sie einen Cross-Assembler und einen Cross-Compiler. Entwickeln und verwenden Sie dies, während Sie die Logik erstellen und simulieren. Durchlaufen Sie die normalen Entwicklungszyklen, indem Sie einen Fehler finden und beheben und erneut testen, bis im Idealfall alle Tools und die Logik als bereit erachtet werden. Und je nach Ziel, beispielsweise wenn es sich um einen Mikrocontroller handelt, der kein Betriebssystem ausführen kann, hätten Sie niemals einen Grund, dies so zu booten, dass die Toolchain mithilfe des nativen Befehlssatzes generiert und ausgeführt wird. Sie würden immer überqueren kompilieren. Anders als bei einer Wayback-Maschine ist es niemals sinnvoll, den Assembler in Assembler zu schreiben.
Ja, wenn Sie zurückgehen oder so tun könnten, als ob Sie zurückkehren würden, war der erste Assembler ein Mensch mit Bleistift und Papier, der etwas schrieb, das für sie Sinn machte, und dann die Teile daneben schrieb, die für die Logik Sinn machten. Verwenden Sie dann Schalter oder einen anderen Weg, um die Bits in die Maschine zu bekommen (google pdp8 oder pdp11 oder altair 8800) und sie dazu zu bringen, etwas zu tun. Es gab anfangs keine Computersimulatoren. Man musste nur die richtige Logik finden, indem man lange genug darauf starrte oder auch mehrere Umdrehungen des Chips drehte. Die Tools sind heutzutage gut genug, um A0-Erfolge zu erzielen, da das Ding mehr als nur ein großer Widerstand ist. Vieles davon funktioniert. Möglicherweise benötigen Sie noch einen Dreh für Dinge, die Sie nicht vollständig simulieren konnten erster SPI, ohne auf den dritten oder vierten Spin warten zu müssen,
Wie zu erwarten, nehmen Sie auf Ihrem Wayback-Computer Ihren handgefertigten Code und verwenden ihn, um ein Programm von Band oder Karte zu laden. Sie geben einem Assembler auch einen Code in Maschinencode, der möglicherweise nicht vollständig ist, sondern das Programmieren ein wenig erleichtert. Dann wird dieses Werkzeug verwendet, um ein Werkzeug zu erstellen, das eine fortgeschrittenere oder kompliziertere Sprache (einen Makro-Assembler) beherrscht, und dieses Werkzeug wird verwendet, um eine kompliziertere Sprache zu erstellen, und Sie erhalten FORTRAN oder BASIC oder B oder was auch immer. Und dann denken Sie über Bootstrapping in derselben Sprache nach und schreiben den Cross-Compiler neu, um ein nativer Compiler zu werden. Dazu benötigen Sie im Idealfall eine Umgebung oder ein Betriebssystem.
Wenn wir Silizium erzeugen oder testen, müssen wir die Signale betrachten, bei denen es sich um Einsen und Nullen handelt. Die Werkzeuge zeigen uns standardmäßig binär oder hexadezimal an, und es ist mit einigen Werkzeugen möglich, sogar nachzuschlagen, damit die Werkzeuge einige Mnemoniken (vielleicht Assembler) anzeigen, aber oft können die Ingenieure (Silizium / Hardware und Software) entweder genug davon lesen Den Maschinencode oder die Demontage / Auflistung verwenden, um die Anweisungen zu "sehen".
Je nachdem, was Sie gerade tun, schieben Sie möglicherweise nur einen Teil des Maschinencodes in die Testvektoren, anstatt den Test neu zu schreiben und neu zu kompilieren oder neu zusammenzusetzen. Wenn Sie beispielsweise über eine Pipeline verfügen und in einer bestimmten Tiefe vorab abrufen, müssen oder möchten Sie möglicherweise nach dem Ende des Programms einige Nops oder andere echte Anweisungen einfügen, damit die Pipe bei nicht definierten Anweisungen nicht kotzt, und Sie können sich einfach dafür entscheiden Füllen Sie den Maschinencode in der Liste / Datei auf der niedrigsten Ebene aus, anstatt den Compiler, Assembler oder Linker dazu zu bringen.
Während Sie den Prozessor testen, müssen Sie sich natürlich mit undefinierten und möglicherweise nicht interessierenden Bits usw. befassen. Sie müssten also in den Maschinencode gehen und ein oder mehrere spezifische Bits in einer Anweisung in einem normal funktionierenden Programm ändern. Lohnt es sich, ein Programm dafür zu schreiben oder es einfach von Hand zu machen? Ebenso möchten Sie beim Testen von ECC ein oder mehrere Bits umdrehen und feststellen, dass sie korrigiert oder eingeklemmt werden. Zugegeben, es ist viel einfacher, ein Programm zu schreiben, oder man könnte es einfach von Hand machen.
Dann gibt es natürlich Sprachen, die keinen Code produzieren, der auf einem Prozessor, Early Pascal, Java, Python usw. ausgeführt wird. Sie benötigen eine VM, die in einer anderen Sprache geschrieben ist, um diese Sprachen zu verwenden. Sie können Ihren Java-Compiler nicht verwenden, um ein Java-VM zu erstellen, da dies aufgrund des Sprachdesigns keinen Sinn ergibt.
(Ja sicher, nach der reinen Implementierung dieser Sprachen erstellt irgendwann jemand ein unreines Backend, das manchmal auf echte Befehlssätze abzielt, nicht auf den vm-Befehlssatz. In diesem Fall können Sie die Sprache verwenden, um sich selbst oder seine vm zu kompilieren, wenn Sie das wirklich spüren brauchen. Ein Gnu Java Frontend zu gcc zum Beispiel).
Im Laufe der Zeit und wahrscheinlich immer noch schreiben wir keine C-Compiler in C. Wir verwenden Dinge wie Bison / Flex oder eine andere Programmiersprache, die wir verwenden, um das C für uns zu generieren, das wir nicht selbst schreiben wollten. Ein gewisser Prozentsatz ist in C sicher, aber ein gewisser Prozentsatz ist in einer anderen Sprache, die einen anderen Compiler verwendet, der Bits eingibt und andere Bits ausgibt. Manchmal wird dieser Ansatz auch zum Generieren eines Assemblers verwendet. Bis zum Designer des Compilers / Assemblers (Programme, die eine Aufgabe haben, Bits einzugeben und dann andere Bits auszugeben), wie sie es implementieren werden. Die programmgenerierten Parser könnten sicher von Hand programmiert werden, was nur zeitaufwendig ist, sodass die Leute nach einer Verknüpfung suchen. Genau wie Sie einen Assembler in Assembler schreiben könnten, aber die Leute suchen nach einer Abkürzung.
Ein Webbrowser ist nur ein Programm, das einige Bits aufnimmt und andere ausspuckt. Ein Assembler ist nur ein Programm, das einige Bits aufnimmt und andere ausspuckt. Ein Compiler ist nur ein Programm, das einige Bits aufnimmt und andere ausspuckt. Für all diese gibt es einen dokumentierten Regelsatz für die Eingangs- und Ausgangsbits für jede Programmieraufgabe. Diese Tasks und Bits sind so allgemein, dass jede VERFÜGBARE Programmiersprache verwendet werden kann. Der Schlüssel ist hier verfügbar. Holen Sie sich das Linux von Grund auf neu und probieren Sie es aus. Probieren Sie einen PDP8 oder PDP11 oder Altair 8800 oder einen anderen Simulator mit einer simulierten Frontplatte aus.
quelle