Aplicações de sistemas embarcados escritas em C muitas vezes precisam, por razões de eficiência, de introduzir código em assembler. Há duas técnicas básicas para este fim:
arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb -c --specs=rdimon.specs -lc -lrdimon -g $1 -o $file.o compile dessa forma, por exemplo, prog1.c, sub1.s e sub2.s e em seguida execute: arm-none-eabi-gcc -mcpu=cortex-m3 -mthumb --specs=rdimon.specs -lc -lrdimon -g prog1.o sub1.o sub2.o -o prog1É preciso, no entanto, saber como o programa em C passa parâmetros para uma função. Se "disassemblarmos" um exemplo qualquer (usando, por exemplo, o flag "-S" ao invocar gcc) verificaremos que parâmetros são passados nos registradores r0, r1, etc, e o retorno da função é devolvido em r0 (na entrada da função os parâmetros são colocados na pilha, pois eventualmente outra função pode ser chamada ou até uma chamada recursiva). Como a função chamada, no nosseo caso, está escrita em assembler, fica trivial acessar diretamente os parâmetros em r0, r1, etc e devolver o resultado em r0 (observe que se um parâmetro for o endereço de um array (vetor) é este endereço que é passado no registrador). Exemplo: suponha que uma aplicação em C precise de uma rotina rápida de ordenação que será escrita em assembler. O programa em C poderá usar o seguinte esqueleto:
#include < stdio.h> int sort(int n, unsigned int array[]); //a rotina sort foi escrita em assembler int main(){ int code; // inicialização do array a[] com N elementos code= sort(N, a); // chama sort com 2 parâmetros, code é um código de retorno printarray(N, a); // exibe no vídeo o array ordenado } e para a subrotina sort.s: .syntax unified .align .text .global sort sort: push {reg-list,lr} // não empilha r0, que vai ter o código de retorno r0 tem o tamanho do array r1 tem o endereço do array aqui vai inserida a rotina de ordenação suponha que o código de retorno seja 0 para sucesso e 1 para falha mov r0, #0 pop {reg-list, pc}
Neste caso será preciso acessar variáveis do programa em C, fazer operações
com seus valores e atribuir novos valores para uma ou mais variáveis.
Aí surge o problema apelidado de "casamento de impedâncias" pois C e assembler são
linguagens muito diferentes. Alem disso pode ser preciso informar ao gcc para não
descartar pelo seu otimizador as operações feitas pelo assembler e também
se registradores, dados na memória ou até os flags foram alterados pelo programa em assembler.
O documento ARM-GCC-Inline-Assembler-Cookbook.pdf
dá uma boa introdução ao assunto, será discutido em aula e vários exemplos foram colocados na pasta
exemplos/maisexemplos/inline/.
Abaixo a 1a página do documento:
Let's start with a simple example of rotating bits. It takes the value of one integer variable, right rotates the bits by one and stores the result in a second integer variable.
asm("mov %0, %1, ror #1" : "=r" (result) : "r" (value));
Each asm statement is divided by colons into up to four parts:
1. The assembler instructions, defined as a single string constant:
"mov %0, %1, ror #1"
2. A list of output operands, separated by commas. Our example uses just one:
"=r" (result) Obs: result é o nome de uma variável do programa em C
3. A comma separated list of input operands. Again our example uses one operand only:
"r" (value) Obs: value é o nome de uma variável do programa em C
4. Clobbered registers, left empty in our example.
You can write assembler instructions in much the same way as you would write assembler programs. However, registers and constants are used in a different way if they refer to expressions of your C program. The connection between registers and C operands is specified in the second and third part of the asm instruction, the list of output and input operands, respectively. The general form is
asm(code : output operand list : input operand list : clobber list);