De C para montagem


16

Suponha que tenhamos o seguinte código C para um avr-8bit:

int v1=1;
int v2=2;
v2=v2+v1;

Eu esperava o seguinte desmontar

ldi r18, 1;
ldi r19, 2;
add r19, r18;

mas depois eu corri:

avr-gcc -mmcu=atmega2560 Test.c -o Test.elf

e

avr-objdump -S Test.elf > Test.lss

Eu tenho o seguinte desmontar

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0
    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01
    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03
    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02
    add r24, r18
    adc r25, r19
    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

existe alguém que possa me ajudar a entender o resultado do desmontador?

Edit: Usando char a montagem se torna:

ldi r24, 0x01
std Y+1, r24
ldi r24, 0x02
std Y+2, r24
ldd r25, Y+2
ldd r24, Y+1
add r24, r25
std Y+2, r24

Quando há instrução std?

Respostas:


20

Resposta curta: seus registros são de 8 bits e seus valores são de 16 bits. É, portanto, manipulá-los em duas partes.

Resposta longa:

    ldi r24, 0x01   ; 1
    ldi r25, 0x00   ; 0

Armazene o valor de 16 bits 1 nos registradores de 8 bits r24, r25.

    std Y+2, r25    ; 0x02
    std Y+1, r24    ; 0x01

Armazene-o nos locais de pilha Y + 1, Y + 2.

    ldi r24, 0x02   ; 2
    ldi r25, 0x00   ; 0

Armazene o valor de 16 bits 2 nos registradores de 8 bits r24, r25.

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Armazene-o nos locais de pilha Y + 3, Y + 4.

    ldd r18, Y+3    ; 0x03
    ldd r19, Y+4    ; 0x04
    ldd r24, Y+1    ; 0x01
    ldd r25, Y+2    ; 0x02

Copie-os de volta da pilha para (r18, r19) e (r24, r25)

    add r24, r18
    adc r25, r19

Adicione (r18, r19) a (r24, r25), incluindo a continuação da segunda adição

    std Y+4, r25    ; 0x04
    std Y+3, r24    ; 0x03

Armazene-o novamente na pilha.

Para obter sua montagem original, tente duas coisas:

  • use variáveis ​​"char"
  • use a opção do compilador "-O2"

Editar : o motivo pelo qual o compilador armazena variáveis ​​na pilha, em vez de mantê-las nos registros, é porque elas são armazenadas com o tipo de armazenamento "automático" padrão. ele pode otimizar-los em registros, mas ele não tem que, mesmo se você declará-los com "registrar" classe de armazenamento.

Embora esse não seja um requisito estrito da linguagem, é um comportamento normal do compilador. Se em algum momento você usar o endereço da v1, ele deverá ser atribuído a um local de armazenamento e salvo lá sempre que o valor de "v1" for alterado. Portanto, para salvar a contabilidade de se a v1 deve ou não ser armazenada em um registro ou na pilha, ela a mantém na pilha e trata cada linha de código separadamente.


Obrigado! Está mais claro agora! Encontre minha edição na pergunta.
DarkCoffee

11
Veja minha edição. Tente -O2 também. Talvez -O3, embora isso possa produzir código quebrado.
Pjc50

3
Muitos códigos incorporados com os quais trabalho definem tipos extras específicos para seu tamanho, como "uint8, uint16, uint32" para as entradas não assinadas, por exemplo. Dessa forma, você sempre sabe exatamente com que tipo de variável está lidando. Especialmente em pequenas embarcações, assinadas, flutuantes, "int" de tamanho / assinatura indefinidos, tudo custará na melhor das hipóteses os ciclos de CPU e causará, na pior das hipóteses, erros graves.
John U #

Compiladores reais pararam de se comportar assim cerca de 10 a 15 anos atrás. O problema de alocação de registros é resolvido principalmente e os compiladores são muito bons nisso. Eles sabem exatamente quando uma variável deve estar na pilha e quando pode estar em um registro, se vale a pena movê-la e quando fazê-lo. A contabilidade é feita em tempo de compilação, e os próprios compiladores possuem gigabytes de memória. A grande exceção é o modo de depuração, por razões óbvias, mas tudo está na pilha.
MSalters

@ pjc50 -O3pode produzir código quebrado? [carece de fontes?] (e não, C código que chama Undefined Comportamento e, em seguida, rompe com alguma configuração de otimização não conta)
marcelm

4

Como encontrei algum código de exemplo, farei meu comentário como resposta - outros já explicaram o problema.

Muitos códigos incorporados com os quais trabalho definem tipos extras específicos para seu tamanho, como "uint8, uint16, uint32" para as entradas não assinadas, por exemplo. Dessa forma, você sempre sabe exatamente com que tipo de variável está lidando. Especialmente em pequenas embarcações, assinadas, flutuantes, "int" de tamanho / assinatura indefinidos, tudo custará na melhor das hipóteses os ciclos de CPU e causará, na pior das hipóteses, erros graves.

Aqui estão nossos #defines atuais:

/*
 * Example - the basic data types from our embedded code
 */
typedef unsigned char       uint8;  /*  8 bits */
typedef unsigned short int  uint16; /* 16 bits */
typedef unsigned long int   uint32; /* 32 bits */

typedef char                int8;   /*  8 bits */
typedef short int           int16;  /* 16 bits */
typedef int                 int32;  /* 32 bits */

typedef volatile int8       vint8;  /*  8 bits */
typedef volatile int16      vint16; /* 16 bits */
typedef volatile int32      vint32; /* 32 bits */

typedef volatile uint8      vuint8;  /*  8 bits */
typedef volatile uint16     vuint16; /* 16 bits */
typedef volatile uint32     vuint32; /* 32 bits */

3
Boa ideia; uint8_t e seus amigos agora fazem parte do padrão: stackoverflow.com/questions/16937459/…
pjc50

Quão útil! Nós meio que herdamos aqueles com um projeto que era o C89, então é bom saber que há uma versão oficial.
John U #

2

Seu código C usa variáveis ​​inteiras de 16 bits (int). O compilador não consegue ler sua mente, portanto, compila exatamente o que está no arquivo de origem. Portanto, se você deseja variáveis ​​de 8 bits, deve usar o tipo respectivo.

Como resultado, você ainda poderá armazenar os valores na memória (embora mais simples). Eu não sou tão bom em C, mas IMHO, existem algumas opções para atribuir a variável a algum registro, se você quiser que algumas variáveis ​​estejam nos registros em vez da RAM. Algo como:

register unsigned char VARNAME asm("r3");

Observe que nem todos os registros estão disponíveis para esses truques.

Então, a conclusão? Escreva seus programas em assembly. Eles sempre serão menores, mais rápidos e fáceis para leitura / suporte.


Assembly é mais fácil de ler do que C?
dext0rb

@ dext0rb - Sim. Claro, se você conhece bem os dois. Se você conhece apenas C, será difícil ler o assembly e quaisquer outros idiomas.
johnfound

Eu tenho que discordar do último ponto, os programas escritos no assembler são muito mais difíceis de ler. Basta comparar o código fonte fornecido acima. O código C é muito mais claro e mais curto, bem como sua intenção. Essa diferença cresce apenas à medida que as estruturas são usadas.
soandos

@soandos - o código C é mais curto, sim. Mais claro? Não tenho certeza. Se assim fosse, a pergunta acima não teria que ser feita. Na verdade, o preço da "falta" é o "embaçamento" dos detalhes.
johnfound

Certamente, o cara que diz "eu não sou tão bom em C" estará proclamando as virtudes da pura assembléia. : D
dext0rb
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.