"Escribe un ensamblador en C." ¿Por qué escribir un traductor de código de máquina para un idioma de bajo nivel en un idioma de nivel superior?

13

Mi instructor de clase de microprocesador nos asignó una tarea y dijo:

"Escribe un ensamblador en C." - Mi querido profesor

Entonces me pareció un poco ilógico.

Si no me equivoco, el lenguaje ensamblador es el primer paso del código de máquina al viaje de los lenguajes de nivel superior. Quiero decir que C es un lenguaje de nivel superior que el ensamblado. Entonces, ¿cuál es el punto de escribir un ensamblador en C? ¿Qué estaban haciendo en el pasado mientras la ausencia del lenguaje C? ¿Estaban escribiendo Assembler en Machine Code?

Para mí no tiene sentido escribir un traductor de código de máquina para un idioma de bajo nivel en un idioma de nivel superior.

Digamos que hemos creado una nueva arquitectura de microprocesador que ni siquiera hay un compilador de C para esa arquitectura. ¿Podrá nuestro ensamblador escrito en C simular la nueva arquitectura? Quiero decir, ¿será inútil o no?

Por cierto, sé que GNU Assembler y Netwide Assembler se han escrito en C. También me pregunto por qué están escritos en C.

Por último, este es el código fuente de ejemplo para un ensamblador simple que nuestro profesor nos dio:

// 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
fuente
2
No existe ningún dispositivo aislado. Las cadenas de herramientas cruzadas son muy frecuentes, particularmente para arquitecturas pequeñas.
Lars Viklund
3
Un compilador / ensamblador "cruzado" se ejecuta en un sistema diferente al objetivo y produce artefactos adecuados para su uso en el sistema objetivo. En la antigüedad, no necesariamente había intercambio de datos entre sistemas, sino que tenía que arrancar un sistema desde cero en términos de sí mismo. Casi todo el desarrollo moderno para arquitecturas se realiza en sistemas establecidos, compilando todo de forma cruzada.
Lars Viklund
19
¿Desea escribir el ensamblador en código máquina en lugar de C? Tu profesor está siendo amable contigo.
Winston Ewert
2
¿Por qué no te esfuerzas por escribir todo tu código en el mejor entorno / lenguaje de programación posible? Un ensamblador no es una excepción.
Erik Eidt
1
No hay un "viaje" fijo en ninguna dirección en particular.
cuál es el

Respuestas:

17

La gente ha escrito ensambladores en código máquina. También escribieron luego en lenguaje ensamblador, a menudo un subconjunto del lenguaje que traducen ellos mismos, por lo que comienzan con una versión simple "bootstrap" del ensamblador, luego le agregan funciones cuando las necesitan para el ensamblador.

Sin embargo, nada de esto es particularmente una necesidad. Al final, un ensamblador es un programa de traducción (generalmente bastante) simple. Toma un archivo en un formato (texto) y escribe un archivo en otro (generalmente un formato de archivo de objeto).

El hecho de que el texto que se ingresa representa instrucciones de máquina en un formato de texto y el resultado representa las mismas instrucciones en formato binario no hace mucha diferencia al lenguaje que se usa para implementar el ensamblador; de hecho, incluso lenguajes más altos que C como SNOBOL y Python pueden funcionar bastante bien: recientemente trabajé (bastante) en un ensamblador escrito en Python, y funcionó bastante bien para el trabajo.

En cuanto a cómo arrancar las cosas inicialmente: generalmente en otra máquina que tenga herramientas de desarrollo decentes y tal. Si está desarrollando un nuevo hardware, generalmente comienza escribiendo un simulador (o al menos un emulador) para la nueva máquina de todos modos, por lo que al principio está construyendo y ejecutando el código en algún sistema host en cualquier caso.

Jerry Coffin
fuente
3
"incluso los lenguajes superiores a C como SNOBOL y Python pueden funcionar bastante bien", este es un muy buen punto. Para NASM, en realidad nunca consideramos nada de nivel superior a C, pero eso fue en 1995 cuando el rendimiento era mucho más importante de lo que es hoy y los lenguajes de alto nivel eran mucho menos avanzados de lo que son hoy. En estos días, ciertamente vale la pena considerar las alternativas.
Jules
1
No he escuchado el nombre SNOBOL desde la década de 1980.
pacmaninbw
Escribí un compilador en Haskell una vez. La evaluación perezosa y el encadenamiento de la función hicieron trivialmente simple escribir un optimizador de mirilla para el código de máquina generado.
Thorbjørn Ravn Andersen
10

Estás viendo conexiones que no existen.

"Escribir un ensamblador" es una tarea de programación como cualquier otra tarea de programación. Utiliza las herramientas para manejar esa tarea que son mejores para esa tarea. No hay nada especial en escribir un ensamblador; no hay ninguna razón para no escribirlo en un lenguaje de alto nivel. C está en realidad en un nivel bastante bajo, y probablemente preferiría C ++ o algún otro lenguaje de nivel superior.

El lenguaje ensamblador es en realidad totalmente inadecuado para una tarea como esta. Los casos en los que usaría razonablemente el lenguaje ensamblador son muy, muy raros. Solo cuando necesita hacer cosas que no se pueden expresar en un lenguaje de nivel superior.

gnasher729
fuente
1
Las otras respuestas son muy buenas, pero creo que esta es la más sencilla, especialmente con las dos primeras oraciones. Me decía lo mismo cuando leía la pregunta.
MetalMikester
Hoy en día, la escritura manual de lenguaje ensamblador solo es necesaria para pirateos específicos de hardware. Por ejemplo, configurar el modo protegido en algunas CPU requiere una secuencia de instrucciones específica y una secuencia lógicamente equivalente no es lo suficientemente buena. Casi todos los programas normales no requieren ninguna secuencia de instrucciones específica para la tarea que necesitan hacer y, como resultado, no hay razón para requerir una secuencia específica, sino solo un conjunto de instrucciones lógicamente equivalentes. Los compiladores de optimización hacen exactamente lo mismo para mejorar el rendimiento de la ejecución (recuento de instrucciones, tiempo de reloj de pared, tamaño de caché de código).
Mikko Rantalainen
9

¿Qué estaban haciendo en el pasado mientras la ausencia del lenguaje C? ¿Estaban escribiendo Assembler en Machine Code?

El ensamblaje es esencialmente un mnemónico para el código de máquina; cada código de operación en el lenguaje de máquina recibe un mnemónico de ensamblaje, es decir, en x86 NOP es 0x90. Esto hace que el ensamblador sea bastante simple (la mayoría de los ensambladores tienen dos pases, uno para traducir y otro para generar / resolver direcciones / referencias). El primer ensamblador fue escrito y traducido a mano (probablemente en papel) al código de la máquina. Una versión mejor está escrita y ensamblada con el ensamblador 'ensamblado' a mano, las nuevas características se agregan de esta manera. Los compiladores para nuevos idiomas se pueden construir de esta manera; ¡en el pasado era común que los compiladores produjeran ensamblajes y usaran un ensamblador para su back-end!

Para mí no tiene sentido escribir un traductor de código de máquina para un idioma de bajo nivel en un idioma de nivel superior. ... [ensambladores existentes] han sido escritos en C. También me pregunto por qué están escritos en C?

  • Generalmente es más fácil escribir una pieza de software más complicada en un lenguaje de nivel superior.
  • Por lo general, se necesita más código y más esfuerzo mental para realizar un seguimiento de lo que está haciendo en un idioma de nivel inferior que en un idioma superior.
    • Una sola línea de C podría traducirse en muchas instrucciones ej. una asignación simple en C ++ (o C) generalmente genera al menos 3 instrucciones de ensamblaje (cargar, modificar, almacenar;) podría tomar veinte instrucciones o más (posiblemente cientos) para hacer lo que se puede hacer con una sola línea en un nivel superior lenguaje (como c ++ o c.) Por lo general, uno querría dedicar su tiempo a resolver el problema y no dedicar tiempo a descubrir cómo implementar la solución en código máquina.

Si bien el autohospedaje es un hito común / característica deseable para un lenguaje de programación, el ensamblaje es de un nivel tan bajo que la mayoría de los programadores preferirían trabajar en un nivel superior. Es decir, nadie quiere escribir un ensamblador en ensamblaje (o cualquier otra cosa realmente)

Digamos que hemos creado una nueva arquitectura de microprocesador que ni siquiera hay un compilador de C para esa arquitectura.

Bootstrapping es el proceso de obtener una cadena de herramientas en una nueva arquitectura.

El proceso básico es:

  • escriba un nuevo back end que comprenda cómo generar código para su nueva CPU (o MCU)
  • compila y prueba tu back-end
  • compile de forma cruzada el compilador deseado (y sistema operativo, etc.) utilizando su nuevo back-end
  • transferir estos binarios al nuevo sistema

No es necesario escribir una vez en ensamblador (nuevo o antiguo) para hacer esto, uno debe elegir el mejor idioma para escribir su ensamblador / back-end / generador de código.

¿Podrá nuestro ensamblador escrito en C simular la nueva arquitectura?

¡Los ensambladores no simulan!

Si uno estaba desarrollando una nueva CPU con un lenguaje de máquina nuevo (o existente), generalmente es necesario un simulador para las pruebas; es decir, ejecute instrucciones y datos aleatorios a través del simulador, y compare la salida con las mismas instrucciones y datos en su CPU prototipo. Luego encuentre errores, corrija errores, repita.

esoterik
fuente
3

Entre las razones para escribir un ensamblador en C (o cualquier otro lenguaje de nivel superior) se encuentran todas las razones que puede usar para justificar la escritura de cualquier otro programa en ese lenguaje de nivel superior. Los principales en este caso son probablemente la portabilidad y la usabilidad.

Portabilidad: si escribe su ensamblador en un idioma nativo, tiene un ensamblador en esa plataforma. Si lo escribe en C, tiene un ensamblador en cualquier plataforma con un compilador de C. Esto le permite, por ejemplo, compilar código para su plataforma integrada en su estación de trabajo y mover el archivo binario en lugar de tener que hacerlo todo directamente en el dispositivo de destino.

Usabilidad: para la mayoría de las personas, leer, razonar y modificar programas es mucho más natural cuando el programa está en un lenguaje de nivel superior que cuando está en ensamblador o (peor) código máquina sin procesar. Por lo tanto, es más fácil desarrollar y mantener el ensamblador en un lenguaje de nivel superior porque puedes pensar en términos de las abstracciones que te brindan los idiomas de nivel superior en lugar de tener que pensar en las minucias de las que eres responsable en los idiomas inferiores.

Usuario no encontrado
fuente
3

Abordar específicamente esta parte de la pregunta solamente:

"Por cierto, sé que GNU Assembler y Netwide Assembler han sido escritos en C. También me pregunto por qué están escritos en C".

Hablando como parte del equipo que originalmente escribió el Netwide Assembler, la decisión nos pareció tan obvia en ese momento que básicamente no consideramos ninguna otra opción, pero si lo hubiéramos hecho, habríamos llegado a la misma conclusión, según las siguientes razones:

  • Escribirlo en un idioma de nivel inferior habría sido más difícil y requeriría mucho más tiempo.
  • Escribirlo en un lenguaje de nivel superior podría haber sido más rápido, pero hubo consideraciones de rendimiento (un ensamblador utilizado como back-end para un compilador, en particular, debe ser muy rápido para evitar ralentizar demasiado el compilador, ya que puede terminan manejando cantidades muy grandes de código, y este fue un caso de uso que específicamente queríamos permitir) y no creo que los autores principales tuvieran ningún lenguaje de nivel superior en común (esto fue antes de que Java se hiciera popular, por lo que el mundo de tales idiomas estaban bastante fragmentados en ese entonces). Usamos perl para algunas tareas de metaprogramación (generar tablas de instrucciones en un formato útil para el generador de código), pero en realidad no hubiera sido adecuado para todo el programa.
  • Queríamos portabilidad del sistema operativo
  • Queríamos portabilidad de la plataforma de hardware (para producir compiladores cruzados)

Esto hizo que la decisión fuera bastante fácil: C en cumplimiento de ANSI (también conocido como C89 en estos días) era el único lenguaje en el momento que realmente alcanzó todos esos puntos. Si hubiera habido un C ++ estandarizado en ese entonces, podríamos haberlo considerado, pero el soporte de C ++ entre diferentes sistemas era bastante irregular en ese entonces, por lo que escribir C ++ portátil fue una pesadilla.

Jules
fuente
1

Una cosa no tiene absolutamente nada que ver con la otra. ¿Los navegadores web deben escribirse estrictamente con html o php u otro lenguaje de contenido web? No, ¿por qué lo harían? ¿Los automóviles solo pueden ser conducidos por otros automóviles y no por humanos?

Convertir un blob de bits (algunos ascii) en otro blob de bits (algún código de máquina) es solo una tarea de programación, el lenguaje de programación que utiliza para esa tarea es el que desee. Puede y ha habido ensambladores escritos en muchos idiomas diferentes.

Inicialmente, los nuevos idiomas no se pueden escribir en su propio idioma ya que todavía no hay un compilador / ensamblador para ellos. Si no hay un compilador existente para un nuevo idioma, tiene que escribir el primero en otro idioma y, finalmente, puede iniciar si eso tiene sentido. (html y un navegador web, un programa que toma algunos bits y escupe algunos bits nunca se escribirá en html, no puede ser).

No tiene que ser un idioma nuevo, puede ser uno existente. Los nuevos compiladores de C o C ++ no se compilan automáticamente desde el principio.

Para lenguaje ensamblador y C, los dos primeros idiomas para casi todos los conjuntos de instrucciones nuevos o modificados. No estamos en el pasado, estamos en el presente. Podemos generar fácilmente un ensamblador en C o Java o Python o lo que sea para cualquier conjunto de instrucciones y lenguaje de ensamblaje que queramos, incluso si aún no existe. Del mismo modo, hay muchos compiladores de C que se pueden volver a armar que podemos generar en lenguaje ensamblador para cualquier lenguaje ensamblador que queramos, incluso si el ensamblador aún no existe.

Eso es exactamente lo que hacemos con un nuevo conjunto de instrucciones. Tome alguna computadora que no se ejecute en nuestro nuevo conjunto de instrucciones con su compilador C que se compiló no para nuestro nuevo conjunto de instrucciones ni para su ensamblador, cree un ensamblador cruzado y un compilador cruzado. Desarrolle y use eso mientras crea y simula la lógica. Realice los ciclos de desarrollo normales para encontrar un error, corregir un error y probar nuevamente, hasta que idealmente todas las herramientas y la lógica se consideren listas. Y dependiendo del objetivo, digamos que es un microcontrolador incapaz de ejecutar un sistema operativo, nunca tendría una razón para arrancar de tal manera que la cadena de herramientas genere y se ejecute utilizando el conjunto de instrucciones nativo. Siempre cruzarías la compilación. Aparte de en una máquina wayback, nunca tiene sentido escribir el ensamblador en ensamblador.

Sí, si pudieras regresar o fingir regresar, el primer ensamblador era un humano con un lápiz y papel, que escribía algo que tenía sentido para ellos y luego escribía los bits al lado que tenían sentido para la lógica. Luego usé interruptores o alguna otra forma de introducir los bits en la máquina (google pdp8 o pdp11 o altair 8800) y hacer que haga algo. Inicialmente, no había simuladores de computadora, solo tenía que tener la lógica correcta mirándolo lo suficiente o también girando varias revoluciones del chip. Hoy en día, las herramientas son lo suficientemente buenas como para que puedas obtener el éxito A0 en el sentido de que la cosa es más que una gran resistencia, muchas de ellas funcionan, es posible que aún necesites un giro para cosas que no podrías simular por completo, pero a menudo puedes arrancar ahora en el primer spi sin tener que esperar al tercer o cuarto giro,

En su máquina wayback como era de esperar, entonces toma su código ensamblado a mano y lo usa para decir cargar un programa de cinta o tarjetas. También puede codificar manualmente un ensamblador en código de máquina, puede que no sea completo, pero que hace que la programación sea un poco más fácil. Luego, esa herramienta se usa para crear una que pueda manejar un lenguaje más avanzado o complicado (un ensamblador de macros), y esa para crear una más complicada y terminas con FORTRAN o BASIC o B o lo que sea. Y luego comienzas a pensar en bootstrapping en el mismo idioma, reescribiendo el compilador cruzado para que sea un compilador nativo. por supuesto, idealmente necesita un entorno o sistema operativo de algún tipo para eso.

Cuando estamos creando o probando silicio, podemos / debemos mirar las señales que son unos y ceros. Las herramientas nos mostrarán binario o hexadecimal por defecto y es posible con algunas herramientas incluso tener búsquedas para que las herramientas muestren algunos mnemotécnicos (ensamblaje quizás) pero a menudo los ingenieros (silicio / hardware y software) pueden leer suficiente el código de máquina, o use el desmontaje / listado para "ver" las instrucciones

Dependiendo de lo que esté haciendo, podría insertar un poco de código de máquina en los vectores de prueba en lugar de volver a escribir y recompilar o volver a ensamblar la prueba. Por ejemplo, si tiene una tubería y una captación previa a cierta profundidad, es posible que necesite o desee completar más allá del final del programa una cantidad de nops u otras instrucciones reales para que la tubería no vomite en instrucciones indefinidas, y puede elegir simplemente complete el código de la máquina en el archivo / listado de nivel más bajo en lugar de intentar que el compilador o ensamblador o enlazador lo haga.

Al probar el procesador, por supuesto, debe lidiar con indefinidos y tal vez no le interesen los bits, etc. Por lo tanto, deberá ingresar al código de la máquina y modificar uno o más bits específicos en una instrucción en un programa que normalmente funciona. ¿Vale la pena escribir un programa para hacer esto o simplemente hacerlo a mano? Del mismo modo, al probar ECC, desea voltear uno o más bits y ver si se corrigen o quedan atrapados. De acuerdo, es mucho más fácil escribir un programa para hacer o puedes hacerlo a mano.

Luego, por supuesto, hay idiomas que no producen código que se ejecuta en un procesador, pascal temprano, java, python, etc. Necesita una máquina virtual escrita en otro idioma solo para usar esos idiomas. No puede usar su compilador de Java para hacer un Java VM, no tiene sentido según el diseño del lenguaje.

(sí, claro, después de la implementación pura de estos lenguajes, eventualmente alguien construye un backend impuro que a veces puede apuntar a conjuntos de instrucciones reales, no al conjunto de instrucciones vm y luego, en ese caso, puede usar el lenguaje para compilarlo él mismo o su vm si realmente sintió el necesidad. Un front end de gnu java a gcc por ejemplo).

Con el tiempo y probablemente aún no escribimos compiladores de C en C. Usamos cosas como bison / flex algún otro lenguaje de programación que usamos para generar el C para nosotros que no queríamos escribir nosotros mismos. Algún porcentaje está en C seguro, pero otro porcentaje está en algún otro lenguaje que usa algún otro compilador que ingresa bits y genera otros bits. A veces, este enfoque también se utiliza para generar un ensamblador. Hasta el diseñador del compilador / ensamblador (programas que tienen la tarea de ingresar bits y luego emitir otros bits) en cuanto a cómo lo van a implementar. Los analizadores generados por el programa podrían programarse a mano con seguridad, lo que lleva mucho tiempo para que la gente busque un atajo. Al igual que podría escribir un ensamblador en ensamblador, pero la gente busca un atajo.

Un navegador web es solo un programa que toma algunos bits y escupe algunos otros bits. Un ensamblador es solo un programa que toma algunos bits y escupe algunos otros bits. Un compilador es solo un programa que toma algunos bits y escupe algunos otros bits. Etc. Para todos estos hay un conjunto documentado de reglas para los bits de entrada y de salida para cada tarea de programación. Estas tareas y bits son lo suficientemente genéricos como para que se pueda utilizar cualquier lenguaje de programación DISPONIBLE (que sea capaz de manipular bit / byte y manejar las entradas y salidas). La clave aquí está disponible. Obtenga y pruebe el libro / tutorial de Linux desde cero. Pruebe un pdp8 o pdp11 o altair 8800 u otro simulador con un panel frontal simulado.

viejo contador de tiempo
fuente