Esta página de Andrew Tolmach agrupa um conjunto relevante de apontadores para escrever / depurar código assembly X86-64 e em particular as suas notas sobre o assembly X86-64.
.text .globl main main: ... mov $0, %rax # código de saída ret .data ...Pode compilar e executar este ficheiro de forma não-interactiva com o comando seguinte:
gcc -g -no-pie ficheiro.s && ./a.out
10 42 7 -9Para vizualizar um inteiro, poderão utilizar a função seguinte :
print_int: mov %rdi, %rsi mov $message, %rdi # argumentos para printf mov $0, %rax call printf ret .data message: .string "%d\n"
false 20 truePoderá ser útil escrever uma função print_bool para mostrar um boleano.
let x = 2 let y = x * x print (y + x)Alocar-se-á as variáveis x e y no mesmo segmento de dados. O resultado esperado é 6.
print (let x = 3 in x * x) print (let x = 3 in (let y = x + x in x * y) + (let z = x + 3 in z / z))Alocar-se-á as variáveis x, y e z na pilha. O resultado esperado é
9 19
set <ident> = <expr>a vizualização do valor de uma expressão, com a sintaxe
print <expr>Aqui, <ident> designa o nome de uma variável e <expr> uma expressão aritmética. As expressões aritméticas podem ser construidas a partir de constantes inteiros, variáveis, da soma, da substracção, da multiplicação, da divisão, de parêntesis e de uma construção let in que introduz uma variável local. Mais formalmente a sintaxe das expressões aritméticas é a seguinte :
<expr> ::= <constante inteira> | <ident> | ( <expr> ) | <expr> + <expr> | <expr> - <expr> | <expr> * <expr> | <expr> / <expr> | - <expr> | let <ident> = <expr> in <expr>Um exemplo de programa Arith pode ser :
set x = 1 + 2 + 3*4 print (let y = 10 in x + y)Os identificadores das variáveis são formados de letras e de algarismos mas não podem começar por um algarismo. As palavras set, print, let et in são palavras reservadas, i.e. não podem ser utilizados como identificadores de variáveis. A prioridade dos operadors aritméticos é a usual e a construção let in tem a prioridade mais baixa.
Para ajudar à construção deste compilador, é-vos dado a sua estrutura de base (na forma de um conjunto de ficheiro OCaml e de um Makefile) que pode ser descarregado aqui : arithc.zip. Uma vez este arquivo descomprimido obtém-se um directório arithc/ que contém os ficheiros seguintes :
ast.mli | a sintaxe abstracta de Arith (completo) |
lexer.mll | o lexer (completo) |
parser.mly | o parser (completo) |
x86_64.mli, x86_64.ml | para gerar código X86-64 (completo) |
compile.ml | o processo de compilação em si (por completar) |
main.ml | o programa principal (completo) |
Makefile | para automatizar a compilação (completo) |
O código fornecido compila ; para iniciar o processo de compilação, basta executar make num terminal, ou melhor, compilar dentro do Emacs com M-x compile ou ainda C-c C-c. O código fornecido é no entanto incompleto : o código assembly produzido é vazio. Deve completar o ficheiro compile.ml. Quando o processo de compilação falha, pode colocar o cursor do editor no local de erro com o comando Emacs M-x next-error ou ainda Ctrl-x `.
O executável produzido chama-se arithc e pode ser invocado com qualquer ficheiro Arith com a extenção .exp. Assim
./arithc fichiero.exptem por efeito produzir um ficheiro ficheiro.s que contém o código assembly resultante. Deve poder executar este ficheiro assembly de forma não-interactiva com o comando
gcc -g -no-pie ficheiro.s && ./a.outPara depurar, poderá utilizar por exemplo Nemiver com o comando nemiver a.out, e executar passo a passo com F7.
Nota para os utilizadores de macOS : é preciso modificar a linha let mangle = mangle_none no ficheiro x86_64.ml fornecido, e substituí-la por let mangle = mangle_leading_underscore. É necessário também subsituir let lab = abslab por let lab = rellab.
As variáveis globais são alocadas no segmento de dados (diretiva .data do assembly ; corresponde aqui ao campo data do tipo X8_64.program).
As variáveis locais são alocadas no fundo da pilha. O espaço necessário para o conjunto das variáveis locais será alocado no início da execução do programa (via uma substracção adequada sobre %rsp). O registo %rbp é posicionado por forma a apontar para o fundo da pilha ; assim toda a referência a uma variável local poderá ser feita com base em %rbp.
Nota para os utilizadores de macOS : antes de chamar uma função da biblioteca como por exemplo printf, a pilha deve ficar alinhada sobre 16 bytes. Quando o valor de frame_size é conhecido/calculado (ver código fornecido), será necessário juntar-lhe 8 se é um múltiplo de 16 (porque o endereço de retorno já ocupa 8 bytes na pilha).
Indicações : poder-se-á proceder elemento (da linguagem) por elemento, testando de cada vez, na ordem seguinte :
Testar-se-á no final com o ficheiro test.exp (igualmente fornecido), e cujo resultado deverá ser :
60 50 0 10 55 60 20 43