„Napisz asembler w C.” Po co pisać tłumacz maszynowy dla języka niskiego poziomu w języku wyższego poziomu?


13

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]);
    }   
}

2
Żadne urządzenie nie istnieje w izolacji. Krzyżowe łańcuchy narzędzi są bardzo rozpowszechnione, szczególnie w przypadku małych architektur.
Lars Viklund,

3
Kompilator / asembler „cross” działa w innym systemie niż docelowy i wytwarza artefakty odpowiednie do użycia w systemie docelowym. W starożytności niekoniecznie trzeba było wymieniać dane między systemami, ale trzeba było ładować system od zera. Niemal wszystkie współczesne prace rozwojowe dla architektur realizowane są na uznanych systemach, wszystko kompilując krzyżowo.
Lars Viklund,

19
Czy chciałbyś napisać asembler w kodzie maszynowym zamiast w C? Twój profesor jest dla ciebie miły.
Winston Ewert

2
Dlaczego nie starałbyś się napisać całego kodu w najlepszym możliwym środowisku / języku programowania? Asembler nie jest wyjątkiem.
Erik Eidt,

1
Nie ma ustalonej „podróży” w żadnym konkretnym kierunku.
whatsisname

Odpowiedzi:


18

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.


3
„nawet wyższe języki niż C, takie jak SNOBOL i Python, mogą działać całkiem nieźle” - to bardzo dobra uwaga. W przypadku NASM tak naprawdę nigdy nie rozważaliśmy niczego wyższego niż C, ale był to rok 1995, kiedy wydajność była o wiele ważniejsza niż obecnie, a języki wysokiego poziomu były znacznie mniej zaawansowane niż obecnie. W dzisiejszych czasach na pewno warto rozważyć alternatywy.
Jules

1
Nie słyszałem nazwy SNOBOL od lat 80.
pacmaninbw

Raz napisałem kompilator w Haskell. Leniwa ocena i tworzenie łańcuchów funkcji sprawiło, że napisanie optymalizatora wizjera dla wygenerowanego kodu maszynowego było niezwykle proste.
Thorbjørn Ravn Andersen

11

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.


1
Pozostałe odpowiedzi są bardzo dobre, ale uważam, że ta jest najprostsza, szczególnie w przypadku pierwszych dwóch zdań. Mówiłem sobie to samo, czytając pytanie.
MetalMikester

Ręczne pisanie asemblera jest obecnie potrzebne tylko w przypadku hacków sprzętowych. Na przykład ustawienie trybu chronionego na niektórych procesorach wymaga określonej sekwencji instrukcji, a logicznie równoważna sekwencja nie jest wystarczająca. Prawie wszystkie normalne programy nie wymagają określonej sekwencji instrukcji dla zadania, które muszą wykonać, w wyniku czego nie ma powodu, aby wymagać określonej sekwencji, a jedynie logicznie równoważny zestaw instrukcji. Optymalizacja kompilatorów robi dokładnie to samo, aby poprawić wydajność wykonywania (liczba instrukcji, czas zegara ściennego, rozmiar pamięci podręcznej kodu).
Mikko Rantalainen

9

Co robili w przeszłości bez języka C? Czy pisali Asembler w Kodzie Maszynowym?

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!

Nie ma dla mnie sensu pisanie tłumacza kodu maszynowego dla języka niskiego poziomu w języku wyższego poziomu. ... [istniejące asemblery] zostały napisane w C. Zastanawiam się także, dlaczego zostały napisane w C?

  • Generalnie łatwiej jest napisać bardziej skomplikowane oprogramowanie w języku wyższego poziomu.
  • Na ogół potrzeba więcej kodu i więcej wysiłku umysłowego, aby śledzić to, co robisz w języku niższego poziomu niż wyższego.
    • Pojedyncza linia C może przełożyć się na wiele instrukcji np. proste przypisanie w C ++ (lub C) zwykle generuje co najmniej 3 instrukcje montażu (ładowanie, modyfikowanie, przechowywanie;) może zająć dwadzieścia instrukcji lub więcej (być może setki), aby zrobić to, co można zrobić za pomocą jednej linii na wyższym poziomie język (jak c ++ lub c.) Na ogół chciałoby się poświęcić czas na rozwiązanie problemu, a nie na zastanawianie się, jak zaimplementować rozwiązanie w kodzie maszynowym.

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ę)

Powiedzmy, że stworzyliśmy zupełnie nową architekturę mikroprocesorową, w której nie ma nawet kompilatora C dla tej architektury.

Bootstrapping to proces uzyskiwania łańcucha narzędzi w nowej architekturze.

podstawowym procesem jest:

  • napisz nowy back-end, który rozumie, jak wygenerować kod dla nowego procesora (lub MCU)
  • skompiluj i przetestuj swój zaplecze
  • skompiluj krzyżowo żądany kompilator (i system operacyjny itp.) przy użyciu nowego zaplecza
  • przenieś te pliki binarne do nowego systemu

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.

Czy nasz asembler napisany w C będzie w stanie symulować nową architekturę?

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.


3

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.


3

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:

  • Pisanie go w języku niższego poziomu byłoby trudniejsze i bardziej czasochłonne.
  • Pisanie go w języku wyższego poziomu mogło być szybsze, ale istniały względy wydajności (asembler używany jako zaplecze kompilatora, szczególnie musi być bardzo szybki, aby zapobiec zbyt dużemu spowolnieniu kompilatora, ponieważ może kończy się to na obsłudze bardzo dużej ilości kodu, a to był przypadek użycia, na który specjalnie chcieliśmy zezwolić) i nie sądzę, aby pierwotni autorzy mieli wspólne języki wyższego poziomu (było to zanim Java stała się popularna, więc świat takie języki były wówczas dość rozdrobnione). Używaliśmy perla do niektórych zadań metaprogramowania (generowanie tabel instrukcji w przydatnym formacie dla zaplecza generatora kodu), ale tak naprawdę nie byłoby odpowiednie dla całego programu.
  • Chcieliśmy przenośności systemu operacyjnego
  • Chcieliśmy przenośności platformy sprzętowej (do produkcji cross-kompilatoró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.


1

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.