Mój instruktor klasy Mikroprocesor dał nam zadanie i powiedział:
„Napisz asembler w C.” - Mój umiłowany profesorze
Wydawało mi się to trochę nielogiczne.
Jeśli się nie mylę, asembler to pierwszy krok od Kodu Maszynowego do podróży po językach wyższego poziomu. Mam na myśli, że C jest językiem wyższego poziomu niż Zgromadzenie. Po co więc pisać asembler w C? Co robili w przeszłości bez języka C? Czy pisali Asembler w Kodzie Maszynowym?
Nie ma dla mnie sensu pisanie tłumacza kodu maszynowego dla języka niskiego poziomu w języku wyższego poziomu.
Powiedzmy, że stworzyliśmy zupełnie nową architekturę mikroprocesorową, w której nie ma nawet kompilatora C dla tej architektury. Czy nasz asembler napisany w C będzie w stanie symulować nową architekturę? Mam na myśli, czy to będzie bezużyteczne czy nie?
Nawiasem mówiąc, wiem, że GNU Asembler i Netwide Asembler zostały napisane w C. Zastanawiam się również, dlaczego zostały napisane w C?
Na koniec jest to przykładowy kod źródłowy prostego asemblera, który dał nam nasz profesor:
// 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]);
}
}
źródło
Odpowiedzi:
Ludzie pisali asemblery w kodzie maszynowym. Napisali również wtedy w języku asemblera - często podzbiór języka, który sami tłumaczą, więc zaczynają od prostej wersji asemblera „bootstrap”, a następnie dodają do niego funkcje, gdy potrzebują ich dla samego asemblera.
Jednak żadna z tych rzeczy nie jest szczególnie konieczna. W końcu asembler to (zwykle dość) prosty program do tłumaczenia. Pobiera plik w jednym formacie (tekstowym) i zapisuje plik w innym (zwykle w formacie pliku obiektowego).
Fakt, że wprowadzany tekst reprezentuje instrukcje maszynowe w formacie tekstowym, a wynik reprezentuje te same instrukcje w formacie binarnym, nie ma większego znaczenia dla języka używanego do implementacji asemblera - w rzeczywistości nawet wyższe języki niż C ponieważ SNOBOL i Python mogą działać całkiem nieźle - ostatnio pracowałem nad asemblerem napisanym w Pythonie i całkiem dobrze to działało.
Jeśli chodzi o to, jak początkowo ładujesz rzeczy: zazwyczaj na innym komputerze, który ma przyzwoite narzędzia programistyczne i tym podobne. Jeśli projektujesz nowy sprzęt, zwykle zaczynasz od napisania symulatora (lub przynajmniej emulatora) dla nowej maszyny, więc w każdym razie najpierw budujesz i uruchamiasz kod na jakimś systemie hosta.
źródło
Widzisz połączenia, które nie istnieją.
„Napisz asembler” to zadanie programistyczne, podobnie jak każde inne zadanie programistyczne. Korzystasz z narzędzi do obsługi tego zadania, które jest najlepsze do tego zadania. Nie ma nic specjalnego w pisaniu asemblera; nie ma żadnego powodu, aby nie pisać w języku wysokiego poziomu. C jest w rzeczywistości na dość niskim poziomie i prawdopodobnie wolałbym C ++ lub inny język wyższego poziomu.
Język asemblera jest w rzeczywistości całkowicie nieodpowiedni do takich zadań. Przypadki, w których można rozsądnie użyć języka asemblera, są bardzo, bardzo rzadkie. Tylko wtedy, gdy musisz robić rzeczy, których nie można wyrazić w języku wyższego poziomu.
źródło
Asembler jest w zasadzie mnemonikiem kodu maszynowego; każdemu kodowi operacji w języku maszynowym przypisany jest mnemonik zestawu, tzn. w x86 NOP wynosi 0x90. To sprawia, że asembler jest raczej prosty (nb większość asemblerów ma dwa przejścia, jeden do tłumaczenia, a drugi do wygenerowania / rozwiązania adresów / referencji). Pierwszy asembler został napisany i przetłumaczony ręcznie (prawdopodobnie na papierze) na kod maszynowy. Lepsza wersja jest napisana i zmontowana z ręcznie asemblerem, a nowe funkcje są dodawane w ten sposób. W ten sposób można budować kompilatory dla nowych języków; w przeszłości kompilatory często generowały dane wyjściowe asemblera i używały asemblera dla swojego zaplecza!
Podczas self-gospodarzem jest częstym milestone / pożądaną cechą dla języka programowania, montaż jest tak niski poziom, że większość programistów wolą pracę na wyższym poziomie. Tj. Nikt nie chce pisać asemblera w asemblerze (lub cokolwiek innego naprawdę)
Bootstrapping to proces uzyskiwania łańcucha narzędzi w nowej architekturze.
podstawowym procesem jest:
Nie raz trzeba pisać w asemblerze (nowym lub starym), aby to zrobić, należy wybrać najlepszy język do napisania asemblera / back-end / generatora kodu.
Asemblery nie symulują!
Jeśli ktoś opracowuje nowy procesor z nowym (lub istniejącym) językiem maszynowym, zwykle do testowania konieczny jest symulator; tj. uruchamiaj losowe instrukcje i dane przez symulator i porównuj dane wyjściowe z tymi samymi instrukcjami i danymi na prototypowym CPU. Następnie znajdź błędy, napraw błędy, powtórz.
źródło
Wśród powodów pisania asemblera w C (lub innym języku wyższego poziomu) są wszystkie powody, dla których możesz uzasadnić pisanie jakiegokolwiek innego programu w tym języku wyższego poziomu. Najważniejsze w tym przypadku to prawdopodobnie przenośność i użyteczność.
Przenośność: jeśli piszesz asembler w języku ojczystym, masz asembler na tej platformie. Jeśli napiszesz w C, masz asembler na dowolnej platformie z kompilatorem C. Umożliwia to na przykład kompilację kodu dla wbudowanej platformy na stacji roboczej i przeniesienie pliku binarnego zamiast konieczności robienia tego wszystkiego bezpośrednio na urządzeniu docelowym.
Użyteczność: dla większości ludzi czytanie, rozumowanie i modyfikowanie programów jest o wiele bardziej naturalne, gdy program jest w języku wyższego poziomu niż w asemblerze lub (gorzej) surowym kodzie maszynowym. Dlatego łatwiej jest opracowywać i utrzymywać asembler w języku wyższego poziomu, ponieważ można myśleć w kategoriach abstrakcji zapewnianych przez języki wyższego poziomu, zamiast myśleć o szczegółach, za które jesteś odpowiedzialny w niższych językach.
źródło
Odnosząc się konkretnie tylko do tej części pytania:
„Nawiasem mówiąc, wiem, że GNU Asembler i Netwide Asembler zostały napisane w C. Zastanawiam się także, dlaczego zostały napisane w C?”
Mówiąc jako część zespołu, który pierwotnie napisał Netwide Asembler, decyzja wydawała się nam tak oczywista, że w zasadzie nie rozważaliśmy żadnych innych opcji, ale gdybyśmy to zrobili, doszlibyśmy do tego samego wniosku, na podstawie z następujących powodów:
Ułatwiło to podjęcie decyzji: C zgodny z ANSI (obecnie C89) był jedynym językiem, który naprawdę osiągnął wszystkie te punkty. Gdyby w tym czasie istniał znormalizowany C ++, moglibyśmy to rozważyć, ale obsługa C ++ między różnymi systemami była wtedy dość niejednolita, więc pisanie przenośnego C ++ było trochę koszmarem.
źródło
Jedna rzecz nie ma absolutnie nic wspólnego z drugą. Czy przeglądarki internetowe muszą być napisane wyłącznie przy użyciu html, php lub innego języka treści WWW? Nie, dlaczego mieliby? Czy samochody mogą być napędzane tylko innymi samochodami, a nie ludźmi?
Konwersja jednej kropli bitów (niektóre ascii) na inną kroplę bitów (jakiś kod maszynowy) to tylko zadanie programistyczne, językiem programowania używanym do tego zadania jest to, czego chcesz. Możesz i byli asemblery napisane w wielu różnych językach.
Początkowo nowych języków nie można pisać w ich własnym języku, ponieważ nie ma jeszcze dla nich kompilatora / asemblera. Jeśli nie ma kompilatora dla nowego języka, musisz napisać pierwszy w innym języku, a następnie w końcu ładujesz, jeśli ma to sens. (HTML i przeglądarka internetowa, program, który pobiera niektóre bity i wypluwa niektóre bity, nigdy nie zostanie zapisany w html, nie może być).
Nie musi być nowym językiem, może być istniejącym. Nowe kompilatory C lub C ++ nie kompilują się automatycznie od samego początku.
Dla języka asemblera i C pierwsze dwa języki dla prawie wszystkich nowych lub zmodyfikowanych zestawów instrukcji. Nie jesteśmy w przeszłości, jesteśmy w teraźniejszości. Możemy łatwo wygenerować asembler w języku C, Java, Python lub cokolwiek innego dla dowolnego zestawu instrukcji i języka asemblera, nawet jeśli jeszcze nie istnieje. Podobnie istnieje wiele kompilatorów C z możliwością ponownego przeliczania, które możemy wyprowadzać język asemblera dla dowolnego języka asemblera, który chcemy, nawet jeśli asembler jeszcze nie istnieje.
Właśnie to robimy z nowym zestawem instrukcji. Weź komputer, który nie działa na naszym nowym zestawie instrukcji z kompilatorem C, który nie został skompilowany dla naszego nowego zestawu instrukcji ani jego asemblera, utwórz cross asembler i cross kompilator. Opracuj i wykorzystaj to podczas tworzenia i symulacji logiki. Przejdź przez normalne cykle programowania, aby znaleźć błąd, naprawić błąd i przetestować ponownie, aż idealnie wszystkie narzędzia i logika zostaną uznane za gotowe. W zależności od celu, powiedzmy, że jest to mikrokontroler niezdolny do uruchomienia systemu operacyjnego, nigdy nie będziesz miał powodu, aby uruchamiać go tak, że łańcuch narzędzi generuje i uruchamia się przy użyciu natywnego zestawu instrukcji. Zawsze kompilowałbyś się. Inne niż w maszynie do przywracania, nigdy nie ma sensu pisać asemblera w asemblerze.
Tak, jeśli mógłbyś wrócić lub udawać, że wracasz, pierwszym asemblerem był człowiek z ołówkiem i papierem, który napisał coś, co miało dla nich sens, a następnie napisał obok niego kawałki, które miały sens dla logiki. Następnie użyłem przełączników lub w inny sposób, aby wprowadzić bity do maszyny (google pdp8 lub pdp11 lub altair 8800) i zmusić go do zrobienia czegoś. Początkowo nie było żadnych symulatorów komputerowych, wystarczyło, że dobrze zrozumiałeś logikę, wpatrując się w nią wystarczająco długo lub obracając kilka obrotów układu. Narzędzia są dziś na tyle dobre, że można odnieść sukces w A0, ponieważ rzecz jest czymś więcej niż tylko dużym rezystorem, wiele z nich działa, nadal możesz potrzebować obrotu dla rzeczy, których nie możesz całkowicie zasymulować, ale często możesz teraz uruchomić na pierwsze spi bez konieczności oczekiwania na trzeci lub czwarty spin,
W maszynie powrotnej, jak można się spodziewać, bierzesz ręcznie złożony kod i używasz go do powiedzenia załadowania programu z taśmy lub kart. Podajesz także kod asemblerowi w kodzie maszynowym, może nie jest to pełny program, ale sprawia, że programowanie jest trochę łatwiejsze. Następnie to narzędzie jest używane do stworzenia takiego, który poradzi sobie z bardziej zaawansowanym lub skomplikowanym językiem (asembler makr), i do stworzenia jednego bardziej skomplikowanego, a ty uzyskasz FORTRAN lub BASIC lub B lub cokolwiek innego. A potem zaczynasz myśleć o ładowaniu w tym samym języku, przepisując kompilator krzyżowy, aby był kompilatorem natywnym. oczywiście idealnie do tego potrzebujesz jakiegoś środowiska lub systemu operacyjnego.
Podczas tworzenia lub testowania krzemu możemy / musimy wpatrywać się w sygnały, które są zerami i zerami. Narzędzia domyślnie pokazują nam binarne lub szesnastkowe i przy niektórych narzędziach można nawet wyszukiwać, aby wyświetlały niektóre mnemoniki (być może montaż), ale często inżynierowie (krzem / sprzęt i oprogramowanie) potrafią czytać wystarczająco dużo kod maszynowy lub użyj demontażu / listy, aby „zobaczyć” instrukcje.
W zależności od tego, co robisz, możesz po prostu wepchnąć jakiś kod maszynowy do wektorów testowych, zamiast ponownie pisać i rekompilować lub ponownie złożyć test. Na przykład, jeśli masz potok i pobierasz z góry na pewną głębokość, możesz potrzebować lub chcieć wypełnić poza końcem programu pewną liczbę nops lub inne prawdziwe instrukcje, aby rura nie wymiotowała na podstawie niezdefiniowanych instrukcji, i możesz po prostu wybrać wypełnij kod maszynowy na najniższym poziomie listy / pliku zamiast próbować uzyskać do tego kompilator, asembler lub linker.
Podczas testowania procesora oczywiście musisz poradzić sobie z niezdefiniowanymi i być może nie obchodzi cię bitami itp. Więc musisz wejść do kodu maszynowego i zmodyfikować jeden lub więcej określonych bitów w instrukcji w normalnie działającym programie. Czy warto napisać program, aby to zrobić, czy po prostu zrobić to ręcznie? Podobnie podczas testowania ECC chcesz przerzucić jeden lub więcej bitów i zobaczyć, czy zostaną one poprawione lub uwięzione. To prawda, że o wiele łatwiej jest napisać program do zrobienia lub można to zrobić ręcznie.
Oczywiście są też języki, które nie produkują kodu działającego na procesorze, wczesne pascal, java, python itp. Potrzebujesz maszyny wirtualnej napisanej w innym języku, aby używać tych języków. Nie można używać kompilatora Java do tworzenia vm Java, nie ma to sensu w oparciu o projekt języka.
(tak, na pewno po czystej implementacji tych języków, w końcu ktoś zbuduje nieczyste zaplecze, które może czasami celować w prawdziwe zestawy instrukcji, a nie w zestaw instrukcji vm, a następnie w takim przypadku można użyć języka do skompilowania go jako własnego lub vm, jeśli naprawdę czujesz potrzeba. Na przykład interfejs GNU Java dla gcc).
Z czasem i być może nadal nie piszemy kompilatorów C w C. Używamy rzeczy takich jak bison / flex jakiś inny język programowania, którego używamy do generowania C dla nas, czego sami nie chcieliśmy pisać. Pewna część procentowa jest na pewno w C, ale część procentowa jest w innym języku, który używa innego kompilatora, który wprowadza bity i wyprowadza inne bity. Czasami takie podejście stosuje się również do generowania asemblera. Do projektanta kompilatora / asemblera (programy, które mają zadanie wprowadzania bitów, a następnie wyprowadzania innych bitów), co do tego, jak zamierzają je zaimplementować. Parsery generowane przez program mogą być ręcznie zaprogramowane, czasochłonne, więc ludzie szukają skrótu. Tak jak możesz napisać asembler w asemblerze, ale ludzie szukają skrótu.
Przeglądarka internetowa to tylko program, który pobiera niektóre bity i wypluwa inne. Asembler to tylko program, który pobiera niektóre bity i wypluwa inne bity. Kompilator to tylko program, który pobiera niektóre bity i wypluwa inne bity. Itd. Dla nich wszystkich istnieje udokumentowany zestaw reguł dla bitów wejściowych i wyjściowych dla każdego zadania programowania. Te zadania i bity są na tyle ogólne, że można użyć dowolnego DOSTĘPNEGO języka programowania (który jest w stanie wykonywać operacje bit / bajt i radzić sobie z wejściami i wyjściami). Klucz tutaj jest dostępny. Pobierz i wypróbuj linux od podstaw / tutoriala. Wypróbuj pdp8 lub pdp11 lub altair 8800 lub inny symulator z symulowanym panelem przednim.
źródło