«Напишите ассемблер на языке C.» Зачем писать переводчик машинного кода для языка низкого уровня на языке более высокого уровня?

13

Мой преподаватель класса Микропроцессор дал нам задание и сказал:

«Напишите ассемблер в Си» - Мой любимый профессор

Так что это показалось мне немного нелогичным.

Если я не ошибаюсь, язык ассемблера - это первый шаг от машинного кода к путешествию на языки более высокого уровня. Я имею в виду, что C - это язык более высокого уровня, чем Assembly. Так какой смысл писать ассемблер на C? Что они делали в прошлом при отсутствии языка Си? Они писали ассемблер в машинном коде?

Для меня нет смысла писать переводчик машинного кода для языка низкого уровня на языке более высокого уровня.

Допустим, мы создали совершенно новую микропроцессорную архитектуру, в которой даже нет компилятора C для этой архитектуры. Сможет ли наш Ассемблер, написанный на С, имитировать новую архитектуру? Я имею в виду, будет ли это бесполезно или нет?

Кстати, я знаю, что GNU Assembler и Netwide Assembler были написаны на C. Мне также интересно, почему они написаны на C?

Наконец, это пример исходного кода для простого ассемблера, который наш профессор дал нам:

// to compile, gcc assembler.c -o assembler
// No error check is provided.
// Variable names cannot start with 0-9.
// hexadecimals are twos complement.
// first address of the code section is zero, data section follows the code section.
//fout tables are formed: jump table, ldi table, label table and variable table.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


//Converts a hexadecimal string to integer.
int hex2int( char* hex)  
{
    int result=0;

    while ((*hex)!='\0')
    {
        if (('0'<=(*hex))&&((*hex)<='9'))
            result = result*16 + (*hex) -'0';
        else if (('a'<=(*hex))&&((*hex)<='f'))
            result = result*16 + (*hex) -'a'+10;
        else if (('A'<=(*hex))&&((*hex)<='F'))
            result = result*16 + (*hex) -'A'+10; 
        hex++;
    }
    return(result);
}


main()
{   
    FILE *fp;
        char line[100];
        char *token = NULL;
    char *op1, *op2, *op3, *label;
    char ch;
    int  chch;

    int program[1000];
    int counter=0;  //holds the address of the machine code instruction




// A label is a symbol which mark a location in a program. In the example 
// program above, the string "lpp", "loop" and "lp1" are labels.
    struct label  
    {
        int location;
        char *label;
    };
    struct label labeltable[50]; //there can be 50 labels at most in our programs
    int nooflabels = 0; //number of labels encountered during assembly.




// Jump instructions cannot be assembled readily because we may not know the value of 
// the label when we encountered a jump instruction. This happens if the label used by
// that jump instruction appear below that jump instruction. This is the situation 
// with the label "loop" in the example program above. Hence, the location of jump 
// instructions must be stored.
    struct jumpinstruction   
    {
        int location;
        char *label;
    };
    struct jumpinstruction jumptable[100]; //There can be at most 100 jumps
    int noofjumps=0;  //number of jumps encountered during assembly.    




// The list of variables in .data section and their locations.
    struct variable
    {
        int location;
        char *name;
    };
    struct variable variabletable[50]; //There can be 50 varables at most.
    int noofvariables = 0;




//Variables and labels are used by ldi instructions.
//The memory for the variables are traditionally allocated at the end of the code section.
//Hence their addresses are not known when we assemble a ldi instruction. Also, the value of 
//a label may not be known when we encounter a ldi instruction which uses that label.
//Hence, the location of the ldi instructions must be kept, and these instructions must be 
//modified when we discover the address of the label or variable that it uses.
    struct ldiinstruction   
    {
        int location;
        char *name;
    };
    struct ldiinstruction lditable[100];
    int noofldis=0;




    fp = fopen("name_of_program","r");

    if (fp != NULL)
    {
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .code section
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )
                break;
        } 
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");  //get the instruction mnemonic or label

//========================================   FIRST PASS  ======================================================
            while (token)
            {
                if (strcmp(token,"ldi")==0)        //---------------LDI INSTRUCTION--------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");                                //get the 1st operand of ldi, which is the register that ldi loads
                    op2 = strtok(NULL,"\n\t\r ");                                //get the 2nd operand of ldi, which is the data that is to be loaded
                    program[counter]=0x1000+hex2int(op1);                        //generate the first 16-bit of the ldi instruction
                    counter++;                                                   //move to the second 16-bit of the ldi instruction
                    if ((op2[0]=='0')&&(op2[1]=='x'))                            //if the 2nd operand is twos complement hexadecimal
                        program[counter]=hex2int(op2+2)&0xffff;              //convert it to integer and form the second 16-bit 
                    else if ((  (op2[0])=='-') || ((op2[0]>='0')&&(op2[0]<='9')))       //if the 2nd operand is decimal 
                        program[counter]=atoi(op2)&0xffff;                         //convert it to integer and form the second 16-bit 
                    else                                                           //if the second operand is not decimal or hexadecimal, it is a laber or a variable.
                    {                                                               //in this case, the 2nd 16-bits of the ldi instruction cannot be generated.
                        lditable[noofldis].location = counter;                 //record the location of this 2nd 16-bit  
                        op1=(char*)malloc(sizeof(op2));                         //and the name of the label/variable that it must contain
                        strcpy(op1,op2);                                        //in the lditable array.
                        lditable[noofldis].name = op1;
                        noofldis++;                                             
                    }       
                    counter++;                                                     //skip to the next memory location 
                }                                       

                else if (strcmp(token,"ld")==0)      //------------LD INSTRUCTION---------------------         
                {
                    op1 = strtok(NULL,"\n\t\r ");                //get the 1st operand of ld, which is the destination register
                    op2 = strtok(NULL,"\n\t\r ");                //get the 2nd operand of ld, which is the source register
                    ch = (op1[0]-48)| ((op2[0]-48) << 3);        //form bits 11-0 of machine code. 48 is ASCII value of '0'
                    program[counter]=0x2000+((ch)&0x00ff);       //form the instruction and write it to memory
                    counter++;                                   //skip to the next empty location in memory
                }
                else if (strcmp(token,"st")==0) //-------------ST INSTRUCTION--------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jz")==0) //------------- CONDITIONAL JUMP ------------------
                {
                    //to be added
                }
                else if (strcmp(token,"jmp")==0)  //-------------- JUMP -----------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");           //read the label
                    jumptable[noofjumps].location = counter;    //write the jz instruction's location into the jumptable 
                    op2=(char*)malloc(sizeof(op1));         //allocate space for the label                  
                    strcpy(op2,op1);                //copy the label into the allocated space
                    jumptable[noofjumps].label=op2;         //point to the label from the jumptable
                    noofjumps++;                    //skip to the next empty location in jumptable
                    program[counter]=0x5000;            //write the incomplete instruction (just opcode) to memory
                    counter++;                  //skip to the next empty location in memory.
                }               
                else if (strcmp(token,"add")==0) //----------------- ADD -------------------------------
                {
                    op1 = strtok(NULL,"\n\t\r ");    
                    op2 = strtok(NULL,"\n\t\r ");
                    op3 = strtok(NULL,"\n\t\r ");
                    chch = (op1[0]-48)| ((op2[0]-48)<<3)|((op3[0]-48)<<6);  
                    program[counter]=0x7000+((chch)&0x00ff); 
                    counter++; 
                }
                else if (strcmp(token,"sub")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"and")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"or")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"xor")==0)
                {
                    //to be added
                }                       
                else if (strcmp(token,"not")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    op2 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op2[0]-48)<<3);
                    program[counter]=0x7500+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"mov")==0)
                {
                    //to be added
                }
                else if (strcmp(token,"inc")==0)
                {
                    op1 = strtok(NULL,"\n\t\r ");
                    ch = (op1[0]-48)| ((op1[0]-48)<<3);
                    program[counter]=0x7700+((ch)&0x00ff);  
                    counter++;
                }
                else if (strcmp(token,"dec")==0)
                {
                                    //to be added
                }
                else //------WHAT IS ENCOUNTERED IS NOT AN INSTRUCTION BUT A LABEL. UPDATE THE LABEL TABLE--------
                {
                    labeltable[nooflabels].location = counter;  //buraya bir counter koy. error check
                    op1=(char*)malloc(sizeof(token));
                    strcpy(op1,token);
                    labeltable[nooflabels].label=op1;
                    nooflabels++;
                } 
                token = strtok(NULL,",\n\t\r ");  
            }
        }


//================================= SECOND PASS ==============================

                //supply the address fields of the jump and jz instructions from the 
        int i,j;         
        for (i=0; i<noofjumps;i++)                                                                   //for all jump/jz instructions
        {
            j=0;
            while ( strcmp(jumptable[i].label , labeltable[j].label) != 0 )             //if the label for this jump/jz does not match with the 
                j++;                                                                // jth label in the labeltable, check the next label..
            program[jumptable[i].location] +=(labeltable[j].location-jumptable[i].location-1)&0x0fff;       //copy the jump address into memory.
        }                                                     




                // search for the start of the .data segment
        rewind(fp);  
        while(fgets(line,sizeof line,fp)!= NULL)  //skip till .data, if no .data, also ok.
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".data")==0 )
                break;

        }


                // process the .data segment and generate the variabletable[] array.
        int dataarea=0;
        while(fgets(line,sizeof line,fp)!= NULL)
        {
            token=strtok(line,"\n\t\r ");
            if (strcmp(token,".code")==0 )  //go till the .code segment
                break;
            else if (token[strlen(token)-1]==':')
            {               
                token[strlen(token)-1]='\0';  //will not cause memory leak, as we do not do malloc
                variabletable[noofvariables].location=counter+dataarea;
                op1=(char*)malloc(sizeof(token));
                strcpy(op1,token);
                variabletable[noofvariables].name=op1;
                token = strtok(NULL,",\n\t\r ");
                if (token==NULL)
                    program[counter+dataarea]=0;
                else if (strcmp(token, ".space")==0)
                {
                    token=strtok(NULL,"\n\t\r ");
                    dataarea+=atoi(token);
                }
                else if((token[0]=='0')&&(token[1]=='x')) 
                    program[counter+dataarea]=hex2int(token+2)&0xffff; 
                else if ((  (token[0])=='-') || ('0'<=(token[0])&&(token[0]<='9'))  )
                    program[counter+dataarea]=atoi(token)&0xffff;  
                noofvariables++;
                dataarea++;
            }
        }






// supply the address fields for the ldi instructions from the variable table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<noofvariables)&&( strcmp( lditable[i].name , variabletable[j].name)!=0 ))
                j++;
            if (j<noofvariables)
                program[lditable[i].location] = variabletable[j].location;              
        } 

// supply the address fields for the ldi instructions from the label table
        for( i=0; i<noofldis;i++)
        {
            j=0;
            while ((j<nooflabels)&&( strcmp( lditable[i].name , labeltable[j].label)!=0 ))
                j++;
            if (j<nooflabels){
                program[lditable[i].location] = (labeltable[j].location)&0x0fff;
                printf("%d %d %d\n", i, j, (labeltable[j].location));   
            }           
        } 

//display the resulting tables
        printf("LABEL TABLE\n");
        for (i=0;i<nooflabels;i++)
            printf("%d %s\n", labeltable[i].location, labeltable[i].label); 
        printf("\n");
        printf("JUMP TABLE\n");
        for (i=0;i<noofjumps;i++)
            printf("%d %s\n", jumptable[i].location, jumptable[i].label);   
        printf("\n");
        printf("VARIABLE TABLE\n");
        for (i=0;i<noofvariables;i++)
            printf("%d %s\n", variabletable[i].location, variabletable[i].name);    
        printf("\n");
        printf("LDI INSTRUCTIONS\n");
        for (i=0;i<noofldis;i++)
            printf("%d %s\n", lditable[i].location, lditable[i].name);  
        printf("\n");
        fclose(fp);
        fp = fopen("RAM","w");
        fprintf(fp,"v2.0 raw\n");
        for (i=0;i<counter+dataarea;i++)
            fprintf(fp,"%04x\n",program[i]);
    }   
}
mertyildiran
источник
2
Ни одно устройство не существует в изоляции. Кросс-цепочки инструментов очень распространены, особенно для крошечных архитектур.
Ларс Виклунд
3
«Перекрестный» компилятор / ассемблер работает в системе, отличной от целевой, и создает артефакты, подходящие для использования в целевой системе. В древние времена вам не обязательно приходилось обмениваться данными между системами, но вам приходилось загружать систему с нуля с точки зрения ее самой. Практически все современные разработки для архитектур выполняются на устоявшихся системах, которые кросс-компилируются.
Ларс Виклунд
19
Хотите написать ассемблер в машинном коде вместо C? Ваш профессор хорошо к вам относится.
Уинстон Эверт
2
Почему бы вам не попытаться написать весь свой код в наилучшей среде / языке программирования? Ассемблер не исключение.
Эрик Эйдт
1
Не существует фиксированного «путешествия» в каком-либо конкретном направлении.
whatsisname

Ответы:

17

Люди написали ассемблеры в машинном коде. Они также написали тогда на языке ассемблера - часто это подмножество языка, который они переводят сами, поэтому они начинают с простой «загрузочной» версии ассемблера, а затем добавляют к ней функции, которые нужны им для самого ассемблера.

Тем не менее, ничего из этого не является особенно необходимостью. В конце концов, ассемблер - это (обычно довольно) простая программа перевода. Он принимает файл в одном (текстовом) формате и записывает файл в другом (обычно в формате объектного файла).

Тот факт, что вводимый текст представляет машинные инструкции в текстовом формате, а результат представляет те же инструкции в двоичном формате, не имеет большого значения для языка, который используется для реализации ассемблера - на самом деле, даже языки выше, чем C, такие поскольку SNOBOL и Python могут работать довольно хорошо - я (довольно) недавно работал над ассемблером, написанным на Python, и он работал довольно хорошо для этой работы.

Относительно того, как вы изначально загружаете вещи: обычно на другой машине, на которой есть достойные инструменты разработки и тому подобное. Если вы разрабатываете новое оборудование, вы обычно начинаете с написания симулятора (или, по крайней мере, эмулятора) для новой машины, так что сначала вы в любом случае создаете и запускаете код на некоторой хост-системе.

Джерри Гроб
источник
3
«даже языки выше, чем C, такие как SNOBOL и Python, могут работать очень хорошо» - это очень хороший момент. Для NASM мы никогда не рассматривали ничего более высокого уровня, чем C, но это было в 1995 году, когда производительность была намного важнее, чем сегодня, а языки высокого уровня были намного менее продвинутыми, чем сегодня. В наши дни, безусловно, стоит рассмотреть альтернативы.
Жюль
1
Я не слышал имя СНОБОЛ с 1980-х годов.
pacmaninbw
Я написал компилятор в Haskell один раз. Ленивое вычисление и создание цепочки функций облегчают написание оптимизатора глазка для сгенерированного машинного кода.
Турбьерн Равн Андерсен
10

Вы видите связи, которые не существуют.

«Написать ассемблер» - это задача программирования, как и любая другая задача программирования. Вы используете инструменты для решения этой задачи, которые лучше всего подходят для этой задачи. В написании ассемблера нет ничего особенного; нет никаких оснований вообще не писать это на языке высокого уровня. C на самом деле находится на довольно низком уровне, и я, вероятно, предпочел бы C ++ или какой-либо другой язык более высокого уровня.

Язык ассемблера на самом деле совершенно не подходит для такой задачи, как эта. Случаи, когда вы разумно используете ассемблер, очень и очень редки. Только когда вам нужно делать то, что не может быть выражено на языке более высокого уровня.

gnasher729
источник
1
Другие ответы очень хороши, но я считаю, что это самый простой ответ, особенно с первыми двумя предложениями. Я говорил себе то же самое, когда читал вопрос.
MetalMikester
Написание ассемблера вручную в настоящее время необходимо только для аппаратных взломов. Например, настройка защищенного режима на некоторых процессорах требует определенной последовательности команд, а логически эквивалентная последовательность недостаточно хороша. Практически все обычные программы не требуют какой-либо определенной последовательности инструкций для задачи, которую они должны выполнять, и в результате нет никаких причин требовать какой-либо конкретной последовательности, а только какой-то логически эквивалентный набор инструкций. Оптимизирующие компиляторы делают одно и то же для улучшения производительности выполнения (счетчик команд, время настенных часов, размер кэша кода).
Микко Ранталайнен,
9

Что они делали в прошлом при отсутствии языка Си? Они писали ассемблер в машинном коде?

Сборка по сути является мнемоникой для машинного кода; каждому операционному коду на машинном языке присваивается мнемоника сборки, т. е. в x86 значение NOP равно 0x90. Это делает ассемблер довольно простым (хотя большинство ассемблеров имеют два прохода, один для перевода и второй для генерации / разрешения адресов / ссылок.) Первый ассемблер был написан и переведен вручную (вероятно, на бумаге) в машинный код. Лучшая версия написана и собрана с помощью «собранного» ассемблера вручную, таким образом добавляются новые функции. Компиляторы для новых языков могут быть построены таким образом; в прошлом компиляторы часто выводили сборку и использовали ассемблер для своей задней части!

Для меня нет смысла писать переводчик машинного кода для языка низкого уровня на языке более высокого уровня. ... [существующие ассемблеры] были написаны на C. Мне также интересно, почему они написаны на C?

  • Как правило, проще написать более сложную часть программного обеспечения на языке более высокого уровня.
  • Обычно требуется больше кода и больше умственных усилий, чтобы отследить, что вы делаете на языке более низкого уровня, чем язык более высокого уровня.
    • Одна строка C может переводиться во многие инструкции ex. простое присваивание в C ++ (или C) обычно генерирует как минимум 3 инструкции по сборке (загрузить, изменить, сохранить;), чтобы сделать то, что можно сделать с помощью одной строки на более высоком уровне, может потребоваться двадцать или более инструкций (возможно, сотни); язык (например, c ++ или c.) Как правило, хотелось бы тратить свое время на решение проблемы, а не тратить время на выяснение того, как реализовать решение в машинном коде.

В то время как самостоятельное размещение является распространенной вехой / желательной функцией для языка программирования, ассемблер настолько низок, что большинство программистов предпочли бы работать на более высоком уровне. Т.е. никто не хочет писать ассемблер на ассемблере (или что-нибудь еще на самом деле)

Допустим, мы создали совершенно новую микропроцессорную архитектуру, в которой даже нет компилятора C для этой архитектуры.

Начальная загрузка - это процесс создания цепочки инструментов для новой архитектуры.

основной процесс:

  • написать новый бэкэнд, который понимает, как генерировать код для вашего нового процессора (или MCU)
  • скомпилируйте и протестируйте свой бэкэнд
  • кросс-компиляция желаемого компилятора (и ОС, и т. д.), используя новый бэкэнд
  • перенести эти двоичные файлы в новую систему

Для этого вам не нужно писать на ассемблере (новом или старом), нужно выбирать лучший язык для написания вашего ассемблера / бэкэнда / генератора кода.

Сможет ли наш Ассемблер, написанный на С, имитировать новую архитектуру?

Ассемблеры не симулируют!

Если кто-то разрабатывал новый ЦП с новым (или существующим) машинным языком, для тестирования обычно требуется симулятор; То есть запускать случайные инструкции и данные через симулятор и сравнивать вывод с теми же инструкциями и данными на вашем прототипе ЦП. Затем найдите ошибки, исправьте ошибки, повторите.

Esoterik
источник
3

Среди причин написания ассемблера на C (или любом другом языке более высокого уровня) - все причины, которые вы можете использовать для оправдания написания любой другой программы на этом языке более высокого уровня. Главными среди тех, кто в этом случае, являются, вероятно, мобильность и удобство использования.

Переносимость: если вы пишете на ассемблере на родном языке, у вас есть ассемблер на этой платформе. Если вы пишете это на C, у вас есть ассемблер на любой платформе с C-компилятором. Это позволяет, например, скомпилировать код для вашей встроенной платформы на рабочей станции и перемещать двоичный файл, а не делать все это прямо на целевом устройстве.

Удобство использования: для большинства людей чтение, рассуждение и модификация программ гораздо более естественны, когда программа написана на языке более высокого уровня, чем на ассемблере или (что еще хуже) необработанного машинного кода. Следовательно, легче разрабатывать и поддерживать ассемблер на языке более высокого уровня, потому что вы можете думать с точки зрения абстракций, предоставляемых вам языками более высокого уровня, а не думать о мелочах, за которые вы отвечаете, на языках более низкого уровня.

Пользователь не найден
источник
3

Рассматривая конкретно эту часть вопроса только:

«Кстати, я знаю, что GNU Assembler и Netwide Assembler были написаны на C. Мне также интересно, почему они написаны на C?»

Выступая как часть команды, которая изначально написала Netwide Assembler, решение казалось нам настолько очевидным в то время, что мы в принципе не рассматривали никаких других вариантов, но если бы мы сделали это, мы пришли бы к такому же выводу, основываясь на следующие причины:

  • Записать его на языке более низкого уровня было бы сложнее и намного больше времени.
  • Написание этого на языке более высокого уровня могло бы быть быстрее, но были соображения производительности (ассемблер, используемый в качестве внутреннего компонента для компилятора, в частности, должен быть очень быстрым, чтобы предотвратить слишком медленное замедление работы компилятора, так как это может в конечном итоге обрабатывать очень большие объемы кода, и это был случай использования, который мы специально хотели разрешить), и я не верю, что у первичных авторов были общие языки более высокого уровня (это было до того, как Java стала популярной, поэтому мир такие языки были довольно фрагментированы тогда). Мы использовали perl для некоторых задач метапрограммирования (генерирование таблиц инструкций в полезном формате для бэкэнда генератора кода), но на самом деле он не подходил для всей программы.
  • Мы хотели переносимости операционной системы
  • Мы хотели переносимости аппаратной платформы (для производства кросс-компиляторов)

Это сделало решение довольно простым: ANSI-совместимый C (сегодня он же C89) был единственным языком в то время, который действительно затронул все эти пункты. Если бы в то время существовал стандартизированный C ++, мы могли бы это учитывать, но поддержка C ++ между различными системами тогда была довольно неоднозначной, поэтому написание переносимого C ++ было чем-то вроде кошмара.

Жюль
источник
1

Одна вещь не имеет абсолютно никакого отношения к другой. Должны ли веб-браузеры быть написаны с использованием html, php или другого языка веб-контента? Нет, с чего бы это? Можно ли управлять машинами только другими машинами, а не людьми?

Преобразование одного блока битов (некоторый ascii) в другой блок битов (некоторый машинный код) - это просто задача программирования, язык программирования, который вы используете для этой задачи, - любой, какой вы хотите. Вы можете и там были ассемблеры, написанные на разных языках.

Новые языки не могут быть изначально написаны на их собственном языке, поскольку для них еще нет компилятора / ассемблера. Если для нового языка не существует компилятора, вы должны написать первый на каком-то другом языке, а затем в конечном итоге начать загрузку, если это даже имеет смысл для начальной загрузки. (HTML и веб-браузер, программа, которая принимает некоторые биты и выплевывает некоторые биты, никогда не будет написана на HTML, не может быть).

Не обязательно должен быть новый язык, может быть существующим. Новые компиляторы C или C ++ автоматически не компилируются прямо из шлюза.

Для ассемблера и C первые два языка почти для всех новых или модифицированных наборов инструкций. Мы не в прошлом, мы в настоящем. Мы можем легко сгенерировать ассемблер на C, Java, Python или любом другом для любого набора команд и языка ассемблера, который мы хотим, даже если он еще не существует. Аналогичным образом, существует много перекомпилируемых компиляторов Си, которые мы можем выводить на ассемблере для любого языка ассемблера, который нам нужен, даже если ассемблер еще не существует.

Это именно то, что мы делаем с новым набором инструкций. Возьмем какой-нибудь компьютер, не работающий в нашем новом наборе команд с компилятором C, который был скомпилирован не для нашего нового набора команд, ни для его ассемблера, создайте кросс-ассемблер и кросс-компилятор. Разработайте и используйте это при создании и моделировании логики. Пройдите обычные циклы разработки: найдите ошибку, исправьте ее и протестируйте снова, пока в идеале все инструменты и логика не будут сочтены готовыми. И в зависимости от цели, скажем, что это микроконтроллер, неспособный к запуску операционной системы, у вас никогда не будет причины запускать его так, чтобы набор инструментов генерировался и работал с использованием собственного набора команд. Вы всегда будете кросс-компилировать. За исключением машины обратного хода, никогда не имеет смысла писать ассемблер на ассемблере.

Да, если бы вы могли вернуться или притвориться, что вернулись, первым ассемблером был человек с карандашом и бумагой, который написал что-то, что имело смысл, а затем написал рядом с ним кусочки, которые имели смысл для логики. Затем использовали переключатели или каким-либо другим способом, чтобы получить биты в машину (Google pdp8 или pdp11 или altair 8800) и заставить его что-то делать. Вначале не было компьютерных симуляторов, вам просто нужно было правильно понять логику, пристально глядя на нее или вращая несколько оборотов чипа. Инструменты достаточно хороши сегодня, чтобы вы могли добиться успеха A0 в том смысле, что это нечто большее, чем просто большой резистор, большая его часть работает, вам все еще может понадобиться вращение для вещей, которые вы не можете полностью смоделировать, но вы часто можете загружаться сейчас на первый spi без необходимости ждать третьего или четвертого вращения,

Как вы и ожидали, на своей машине обратного хода вы берете собранный вручную код и используете его для загрузки программы с ленты или карт. Вы также вручную пишете ассемблерный код в машинном коде, который может быть не полноценным, а немного упрощающим программирование. Затем этот инструмент используется для создания инструмента, который может обрабатывать более продвинутый или сложный язык (макро-ассемблер), и инструмента для создания более сложного языка, и в итоге вы получаете FORTRAN или BASIC или B или любой другой. И тогда вы начинаете думать о начальной загрузке на том же языке, переписывая кросс-компилятор, чтобы он стал нативным компилятором. конечно, в идеале для этого вам нужна какая-то среда или операционная система.

Когда мы создаем или тестируем кремний, мы можем / должны смотреть на сигналы, которые являются единицами и нулями. Инструменты будут показывать нам двоичный или шестнадцатеричный по умолчанию, и с некоторыми инструментами можно даже искать, чтобы инструменты отображали некоторую мнемонику (возможно, сборку), но часто инженеры (кремний / аппаратное и программное обеспечение) могут либо прочитать достаточно машинный код, или используйте разборку / листинг, чтобы «увидеть» инструкции.

В зависимости от того, что вы делаете, вы можете просто вставить некоторый машинный код в векторы теста, а не переписывать и перекомпилировать или пересобирать тест. Например, если у вас есть конвейер и предварительная выборка на некоторой глубине, вам может понадобиться или вы захотите заполнить после конца программы некоторое количество nops или других реальных инструкций, чтобы канал не поддавался неопределенным инструкциям, и вы можете просто выбрать просто заполните машинный код на листинге / файле самого низкого уровня, а не пытайтесь заставить компилятор, ассемблер или компоновщик сделать это.

Конечно, при тестировании процессора вам нужно иметь дело с неопределенными и, возможно, безразличными битами и т. Д. Поэтому вам нужно будет перейти к машинному коду и изменить один или несколько конкретных битов в инструкции в нормально работающей программе. Стоит ли писать программу для этого или просто делать это вручную. Аналогично, при тестировании ECC вы хотите перевернуть один или несколько битов и увидеть, что они исправлены или захвачены. Конечно, гораздо проще написать программу, либо вы можете просто сделать это вручную.

Конечно, есть языки, которые не производят код, который выполняется на процессоре, раннем паскале, Java, Python и т. Д. Вам нужна виртуальная машина, написанная на каком-то другом языке, чтобы использовать эти языки. Вы не можете использовать свой Java-компилятор для создания Java VM, не имеет смысла, основываясь на дизайне языка.

(да, конечно, после чистой реализации этих языков в конечном итоге кто-то создает неочищенный бэкэнд, который иногда может нацеливаться на реальные наборы команд, а не на набор команд vm, и тогда в этом случае вы можете использовать язык для самостоятельной компиляции или его vm, если вы действительно почувствовали нужно. GNU Java-интерфейс для GCC, например).

Со временем и, вероятно, все еще, мы не пишем компиляторы C на C. Мы используем такие вещи, как bison / flex, какой-то другой язык программирования, который мы используем для генерации C для нас, который мы не хотели писать сами. Некоторый процент в C уверен, но некоторый процент в другом языке, который использует другой компилятор, который вводит биты и выводит другие биты. Иногда этот подход также используется для генерации ассемблера. До разработчика компилятора / ассемблера (программы, у которых есть задача вводить биты, а затем выводить другие биты) относительно того, как они собираются его реализовать. Сгенерированные программой парсеры могут быть запрограммированы вручную, просто отнимает много времени, поэтому люди ищут ярлык. Точно так же, как вы могли бы написать ассемблер на ассемблере, но люди ищут ярлык.

Веб-браузер - это просто программа, которая принимает некоторые биты и выплевывает некоторые другие биты. Ассемблер - это просто программа, которая принимает некоторые биты и выплевывает некоторые другие биты. Компилятор - это просто программа, которая принимает некоторые биты и выплевывает некоторые другие биты. И т. Д. Для всего этого есть задокументированный набор правил для входных и выходных битов для каждой задачи программирования. Эти задачи и биты являются достаточно общими, чтобы можно было использовать любой ДОСТУПНЫЙ язык программирования (который способен выполнять бит / байты и работать с входами и выходами). Ключ здесь доступен. Получить и попробовать Linux с нуля книги / учебник. Попробуйте pdp8 или pdp11 или altair 8800 или другой симулятор с имитируемой передней панелью.

Старожил
источник