“Escreva um assembler em C.” Por que escrever um tradutor de código de máquina para um idioma de baixo nível em um idioma de nível superior?

13

Meu instrutor de classe de microprocessador nos deu uma tarefa e disse:

"Escreva um assembler em C." - Meu amado professor

Então me pareceu um pouco ilógico.

Se não estou errado, a Linguagem Assembly é o primeiro passo do Código da Máquina para a jornada de idiomas de nível superior. Quero dizer, C é uma linguagem de nível superior ao Assembly. Então, qual é o objetivo de escrever um Assembler em C? O que eles estavam fazendo no passado enquanto a ausência da linguagem C? Eles estavam escrevendo Assembler em Código de Máquina?

Não faz sentido escrever um tradutor de código de máquina para um idioma de baixo nível em um idioma de nível superior.

Digamos que criamos uma nova arquitetura de microprocessador que não existe nem um compilador C para essa arquitetura. O nosso Assembler, escrito em C, será capaz de simular a nova arquitetura? Quero dizer, será inútil ou não?

A propósito, estou ciente de que o GNU Assembler e o Netwide Assembler foram escritos em C. Também estou me perguntando por que eles estão escritos em C?

Por fim, este é o exemplo de código-fonte para um simples assembler que nosso Professor nos deu:

// 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
fonte
2
Nenhum dispositivo existe isoladamente. As cadeias de ferramentas cruzadas são muito prevalentes, principalmente para pequenas arquiteturas.
Lars Viklund
3
Um compilador / montador "cruzado" é executado em um sistema diferente do destino e produz artefatos adequados para uso no sistema de destino. Antigamente, você não tinha necessariamente intercâmbio de dados entre sistemas, mas era necessário inicializar um sistema a partir do zero em termos de si mesmo. Praticamente todo o desenvolvimento moderno de arquiteturas é feito em sistemas estabelecidos, compilando tudo.
Lars Viklund
19
Deseja escrever o assembler em código de máquina em vez de C? Seu professor está sendo legal com você.
Winston Ewert
2
Por que você não se esforçou para escrever todo o seu código no melhor ambiente / linguagem de programação possível? Um montador não é exceção.
Erik Eidt
1
Não existe uma "jornada" fixa em nenhuma direção específica.
Whatsisname

Respostas:

17

As pessoas escreveram assemblies em código de máquina. Eles também escreveram na linguagem assembly - geralmente um subconjunto do idioma que se traduzem; portanto, começam com uma versão "bootstrap" simples do assembler e adicionam recursos a ele conforme necessário para o próprio assembler.

No entanto, nada disso é particularmente necessário. No final, um assembler é um programa de tradução simples (geralmente bastante). Ele pega um arquivo em um formato (texto) e grava um arquivo em outro (geralmente um formato de arquivo objeto).

O fato de o texto inserido representar instruções da máquina em formato textual e o resultado representar as mesmas instruções em formato binário não faz muita diferença para o idioma usado para implementar o assembler - na verdade, idiomas ainda mais altos que o C como o SNOBOL e o Python podem funcionar muito bem - recentemente trabalhei (razoavelmente) em um assembler escrito em Python, e funcionou muito bem para o trabalho.

Na medida em que você inicializa as coisas inicialmente: normalmente em outra máquina que possui ferramentas de desenvolvimento decentes e outras coisas. Se você está desenvolvendo um novo hardware, geralmente começa escrevendo um simulador (ou pelo menos emulador) para a nova máquina, de modo que você está construindo e executando o código em algum sistema host em qualquer caso.

Jerry Coffin
fonte
3
"linguagens ainda mais altas que C, como SNOBOL e Python podem funcionar muito bem" - esse é um ponto muito bom. Para o NASM, nunca consideramos nada de nível superior ao C, mas isso foi em 1995, quando o desempenho era muito mais importante do que é hoje e os idiomas de alto nível eram muito menos avançados do que são hoje. Atualmente, vale a pena considerar as alternativas.
Jules
1
Eu não ouvi o nome SNOBOL desde os anos 80.
Pacmaninbw
Eu escrevi um compilador em Haskell uma vez. A avaliação preguiçosa e o encadeamento de funções tornaram trivialmente simples escrever um otimizador de olho mágico para o código de máquina gerado.
Thorbjørn Ravn Andersen
10

Você está vendo conexões que não existem.

"Write an assembler" é uma tarefa de programação como qualquer outra tarefa de programação. Você usa as ferramentas para lidar com a tarefa melhor para essa tarefa. Não há nada de especial em escrever um assembler; não há nenhuma razão para não escrever em um idioma de alto nível. C é realmente um nível bastante baixo, e eu provavelmente preferiria C ++ ou alguma outra linguagem de nível superior.

A linguagem assembly é realmente totalmente inadequada para uma tarefa como esta. Os casos em que você usaria razoavelmente a linguagem assembly são muito, muito raros. Somente quando você precisa fazer coisas que não podem ser expressas em um idioma de nível superior.

gnasher729
fonte
1
As outras respostas são muito boas, mas acho que essa é a mais direta, principalmente nas duas primeiras frases. Eu estava me dizendo a mesma coisa ao ler a pergunta.
MetalMikester 14/07
Atualmente, escrever manualmente a linguagem assembly é necessário apenas para hacks específicos de hardware. Por exemplo, a configuração do modo protegido em algumas CPUs requer uma sequência de instruções específica e uma sequência logicamente equivalente não é suficiente. Praticamente todos os programas normais não requerem nenhuma sequência de instruções específica para a tarefa que precisam executar e, como resultado, não há razão para exigir uma sequência específica, mas apenas um conjunto de instruções logicamente equivalente. A otimização dos compiladores faz exatamente a mesma coisa para melhorar o desempenho da execução (contagem de instruções, hora do relógio de parede, tamanho do cache de código).
Mikko Rantalainen
9

O que eles estavam fazendo no passado enquanto a ausência da linguagem C? Eles estavam escrevendo Assembler em Código de Máquina?

Assembly é essencialmente um mnemônico para código de máquina; cada código de operação na linguagem de máquina recebe um assembly mnemônico, ou seja, em x86, o NOP é 0x90. Isso torna o assembler bastante simples (nb a maioria dos assemblers tem duas passagens, uma para traduzir e outra para gerar / resolver endereços / referências.) O primeiro assembler foi escrito e traduzido à mão (provavelmente em papel) em código de máquina. Uma versão melhor é escrita e montada com o montador 'montado' manualmente; novos recursos são adicionados dessa maneira. Compiladores para novos idiomas podem ser construídos dessa maneira; no passado, era comum que os compiladores produzissem montagem e usassem um montador para o back-end!

Não faz sentido escrever um tradutor de código de máquina para um idioma de baixo nível em um idioma de nível superior. ... [montadores existentes] foram escritos em C. Também estou me perguntando por que eles são escritos em C?

  • Geralmente é mais fácil escrever um software mais complicado em um idioma de nível superior.
  • Geralmente, é preciso mais código e mais esforço mental para rastrear o que você está fazendo em um idioma de nível inferior ao de um idioma superior.
    • Uma única linha de C pode se traduzir em muitas instruções, ex. uma tarefa simples em C ++ (ou C) geralmente gera pelo menos três instruções de montagem (carregar, modificar, armazenar;) pode levar vinte instruções ou mais (possivelmente centenas) para fazer o que pode ser feito com uma única linha em um nível superior linguagem (como c ++ ou c.) Em geral, alguém gostaria de gastar seu tempo resolvendo o problema, e não gastando tempo tentando descobrir como implementar a solução no código da máquina.

Enquanto auto-hospedagem seja um marco comum / recurso desejável para uma linguagem de programação, o assembly é tão baixo que a maioria dos programadores prefere trabalhar em um nível superior. Ou seja, ninguém quer escrever um assembler em assembly (ou qualquer outra coisa realmente)

Digamos que criamos uma nova arquitetura de microprocessador que não existe nem um compilador C para essa arquitetura.

Bootstrapping é o processo de obter uma cadeia de ferramentas em uma nova arquitetura.

o processo básico é:

  • escreva um novo back-end que entenda como gerar código para sua nova CPU (ou MCU)
  • compilar e testar seu back-end
  • faça uma compilação cruzada do compilador desejado (e do sistema operacional, etc.) usando seu novo backend
  • transferir esses binários para o novo sistema

Nem uma vez você precisa escrever em assembly (novo ou antigo) para fazer isso; deve-se escolher o melhor idioma para escrever seu assembler / back-end / gerador de código.

O nosso Assembler, escrito em C, será capaz de simular a nova arquitetura?

Montadores não simulam!

Se alguém estava desenvolvendo uma nova CPU com uma linguagem de máquina nova (ou existente), geralmente é necessário um simulador para teste; ou seja, execute instruções e dados aleatórios através do simulador e compare a saída com as mesmas instruções e dados na sua CPU protótipo. Em seguida, encontre erros, corrija erros e repita.

esoterik
fonte
3

Entre os motivos para escrever um assembler em C (ou qualquer outra linguagem de nível superior) estão todos os motivos que você pode usar para justificar a gravação de qualquer outro programa nessa linguagem de nível superior. Os principais dentre os que estão nesse caso são provavelmente portabilidade e usabilidade.

Portabilidade: se você escreve seu assembler em um idioma nativo, possui um assembler nessa plataforma. Se você o escreve em C, você tem um assembler em qualquer plataforma com um compilador C. Isso permite que você, por exemplo, compile código para sua plataforma incorporada em sua estação de trabalho e mova o binário em vez de precisar fazer tudo diretamente no dispositivo de destino.

Usabilidade: para a maioria das pessoas, ler, raciocinar e modificar programas é muito mais natural quando o programa está em uma linguagem de nível superior do que quando está em assembler ou (pior) código de máquina bruta. Portanto, é mais fácil desenvolver e manter o assembler em uma linguagem de nível superior, porque você pode pensar em termos das abstrações oferecidas a você pelas linguagens de nível superior, em vez de ter que pensar nas minúcias pelas quais você é responsável nas inferiores.

Usuário não encontrado
fonte
3

Abordando especificamente esta parte da pergunta:

"A propósito, estou ciente de que o GNU Assembler e o Netwide Assembler foram escritos em C. Também estou me perguntando por que eles estão escritos em C?"

Falando como parte da equipe que originalmente escreveu o Netwide Assembler, a decisão parecia tão óbvia para nós na época que basicamente não consideramos outras opções, mas se o tivéssemos feito, teríamos chegado à mesma conclusão, com base em os seguintes motivos:

  • Escrever em um idioma de nível inferior teria sido mais difícil e consumiria muito mais tempo.
  • Escrever em uma linguagem de nível superior pode ter sido mais rápido, mas houve considerações de desempenho (um montador usado como back-end para um compilador, em particular, precisa ser muito rápido para evitar que o compilador fique lento demais, pois pode acabamos manipulando quantidades muito grandes de código, e esse era um caso de uso que queríamos especificamente permitir) e não acredito que os autores principais tenham linguagens de nível superior em comum (isso foi antes do Java se tornar popular, então o mundo da essas línguas estavam bastante fragmentadas na época). Usamos o perl para algumas tarefas de metaprogramação (gerando tabelas de instruções em um formato útil para o backend do gerador de código), mas não seria realmente adequado para todo o programa.
  • Queríamos portabilidade do sistema operacional
  • Queríamos portabilidade da plataforma de hardware (para produzir compiladores cruzados)

Isso facilitou bastante a decisão: o C compatível com ANSI (atualmente conhecido como C89) era o único idioma na época que realmente atingiu todos esses pontos. Se houvesse uma padronização C ++ volta então nós pode ter considerado que, mas o suporte C ++ entre sistemas diferentes foi bastante volta irregular, em seguida, assim que escrever C ++ portátil foi um pouco de um pesadelo.

Jules
fonte
1

Uma coisa não tem absolutamente nada a ver com a outra. Os navegadores da web devem ser estritamente escritos usando html ou php ou algum outro idioma de conteúdo da web? Não, por que eles? Os carros só podem ser dirigidos por outros carros e não por seres humanos?

Converter um blob de bits (alguns ascii) em outro blob de bits (algum código de máquina) é apenas uma tarefa de programação, a linguagem de programação usada para essa tarefa é a que você desejar. Você pode e houve montadores escritos em vários idiomas diferentes.

Novos idiomas não podem ser escritos em seu próprio idioma inicialmente, pois ainda não existe um compilador / montador para eles. Se não houver um compilador existente para um novo idioma, você precisará escrever o primeiro em outro idioma e, eventualmente, inicializar, se isso fizer sentido. (html e um navegador da web, um programa que absorve alguns bits e cospe alguns bits nunca será escrito em html, não pode ser).

Não precisa ser um novo idioma, pode ser um idioma existente. Novos compiladores C ou C ++ não se compilam automaticamente desde o início.

Para linguagem assembly e C, os dois primeiros idiomas para quase todos os conjuntos de instruções novos ou modificados. Não estamos no passado, estamos no presente. Podemos facilmente gerar um assembler em C ou java ou python ou qualquer outra coisa para qualquer conjunto de instruções e linguagem de montagem que desejamos, mesmo que ainda não exista. Da mesma forma, existem muitos compiladores C retargetable que podemos produzir linguagem assembly para qualquer linguagem assembly que desejamos, mesmo que o assembler ainda não exista.

É exatamente isso que fazemos com um novo conjunto de instruções. Pegue um computador que não esteja em execução em nosso novo conjunto de instruções com seu compilador C que não foi compilado para nosso novo conjunto de instruções nem seu assembler, crie um assembler cruzado e um compilador cruzado. Desenvolva e use isso ao criar e simular a lógica. Percorra os ciclos normais de desenvolvimento de encontrar um bug, corrigir um bug e testar novamente, até idealmente todas as ferramentas e a lógica serem consideradas prontas. E, dependendo do destino, digamos que seja um microcontrolador incapaz de executar um sistema operacional, você nunca teria um motivo para inicializar de tal forma que a cadeia de ferramentas gere e execute usando o conjunto de instruções nativo. Você sempre cruzaria a compilação. Além de em uma máquina de wayback, nunca faz sentido escrever o assembler no assembler.

Sim, se você pudesse voltar ou fingir voltar, o primeiro montador era um humano com um lápis e papel, que escrevia algo que fazia sentido para eles e depois escrevia os bits ao lado que faziam sentido para a lógica. Em seguida, usei switches ou alguma outra maneira de inserir os bits na máquina (google pdp8 ou pdp11 ou altair 8800) e fazendo com que ela fizesse alguma coisa. Inicialmente, não havia simuladores de computador, bastava acertar a lógica, observando-a por tempo suficiente ou girando várias rotações do chip. As ferramentas são boas o suficiente hoje para que você possa obter sucesso em A0, pois a coisa é mais do que apenas um grande resistor; muito disso funciona. Você ainda pode precisar de um giro para coisas que não poderia simular completamente, mas muitas vezes pode inicializar agora no primeiro spi sem ter que esperar pela terceira ou quarta rodada,

Na sua máquina de passagem, como seria de esperar, você pega o código montado à mão e o usa para dizer carregar um programa a partir de fitas ou cartões. Você também codifica um assembler em código de máquina, pode não ser completo, mas facilita a programação. Em seguida, essa ferramenta é usada para criar um que possa lidar com um idioma mais avançado ou complicado (um montador de macros) e aquele para criar um mais complicado e você acaba com FORTRAN ou BASIC ou B ou o que for. E então você começa a pensar em iniciar no mesmo idioma, reescrevendo o compilador cruzado para ser um compilador nativo. é claro que você idealmente precisa de um ambiente ou sistema operacional de algum tipo para isso.

Quando estamos criando ou testando silício, podemos / precisamos olhar para os sinais que são uns e zeros. As ferramentas nos mostram binário ou hexadecimal por padrão e, com algumas ferramentas, é possível até fazer pesquisas, para que as ferramentas exibam algumas mnemônicas (montagem talvez), mas muitas vezes os engenheiros (silício / hardware e software) podem ler o suficiente de o código da máquina ou use a desmontagem / listagem para "ver" as instruções.

Dependendo do que você está fazendo, basta inserir algum código de máquina nos vetores de teste, em vez de reescrever e recompilar ou remontar o teste. Por exemplo, se você tiver um pipeline e fizer uma pré-busca a alguma profundidade, poderá precisar ou desejar preencher, após o final do programa, algum número de repetições ou outras instruções reais para que o tubo não vomite em instruções indefinidas, e você pode optar por apenas preencha o código da máquina no arquivo / lista de nível mais baixo, em vez de tentar obter o compilador, montador ou vinculador para fazer isso.

Durante o teste do processador, é claro que você precisa lidar com bits indefinidos e talvez não se importar, etc. Portanto, você precisará entrar no código da máquina e modificar um ou mais bits específicos em uma instrução em um programa normalmente funcionando. Vale a pena escrever um programa para fazer isso ou apenas fazer à mão. Da mesma forma, ao testar o ECC, você deseja inverter um ou mais bits e ver se eles são corrigidos ou presos. É claro que é muito mais fácil escrever um programa ou você pode fazê-lo manualmente.

É claro que existem linguagens que não produzem código que roda em um processador, pascal inicial, java, python etc. Você precisa de uma VM escrita em alguma outra linguagem apenas para usar essas linguagens. Você não pode usar o seu compilador java para criar um java vm, não faz sentido com base no design da linguagem.

(sim, após a implementação pura dessas linguagens, alguém cria um back-end impuro que às vezes pode direcionar conjuntos de instruções reais e não o conjunto de instruções vm e, nesse caso, você pode usar o idioma para compilar a si mesmo ou a sua vm, se realmente sentiu Um front end gnu java para o gcc, por exemplo).

Com o tempo e talvez ainda não escrevamos compiladores C em C. Usamos coisas como bison / flex alguma outra linguagem de programação que usamos para gerar o C para nós que não queríamos escrever por conta própria. Alguma porcentagem está em C com certeza, mas alguma porcentagem está em alguma outra linguagem que usa outro compilador que insere bits e gera outros bits. Às vezes, essa abordagem também é usada para gerar um montador. Até o designer do compilador / montador (programas que têm uma tarefa para inserir bits e depois gerar outros bits) sobre como eles vão implementá-lo. Os analisadores gerados pelo programa podem ser programados à mão com certeza, consumindo muito tempo para que as pessoas procurem um atalho. Assim como você pode escrever um assembler no assembler, mas as pessoas procuram um atalho.

Um navegador da web é apenas um programa que capta alguns bits e cospe outros. Um assembler é apenas um programa que capta alguns bits e cospe outros. Um compilador é apenas um programa que pega alguns bits e cospe outros. Etc. Para tudo isso, existe um conjunto documentado de regras para os bits de entrada e de saída de cada tarefa de programação. Essas tarefas e bits são genéricos o suficiente para que qualquer linguagem de programação AVAILABLE possa ser usada (capaz de manipular bit / byte e lidar com as entradas e saídas). A chave aqui está disponível. Obtenha e experimente o linux do zero book / tutorial. Experimente um pdp8 ou pdp11 ou altair 8800 ou outro simulador com um painel frontal simulado.

old_timer
fonte