2. Linguagem assembly¶
Um programa em linguagem assembly é formado por uma sequência de linhas de texto. Cada linha contém uma instrução, diretiva ou label.
Uma instrução é formada por uma mnemónica, que identifica a instrução,
seguida dos parâmetros.
No exemplo, sub é a mnemónica da instrução e r0, r0, 1
são os parâmetros.
sub r0, r0, #1
O P16 dispõe das instruções listadas na tabela (Tabela 2.1) [2]. A quantidade e o tipo dos parâmetros dependem da instrução em causa e podem ser registos do processador, constantes numéricas ou símbolos.
As mnemónicas das instruções e o nome dos registos podem ser escritos em letras maiúsculas ou minúsculas (case insensitive [1]).
Tabela 2.1 Instruções do P16¶
ldr rd, label
add rd, rn, rm
and rd, rn, rm
pop rd
sub rd, rn, rm
orr rd, rn, rm
push rs
adc rd, rn, rm
eor rd, rn, rm
ldr rd, [rn, #constant]
sbc rd, rn, rm
mvn rd, rs
ldrb rd, [rn, #constant]
add rd, rn, #constant
lsl rd, rn, #constant
ldr rd, [rn, rm]
sub rd, rn, #constant
lsr rd, rn, #constant
ldrb rd, [rn, rm]
cmp rn, rm
asr rd, rn, #constant
str rs, [rn, #constant]
bzs/beq label
ror rd, rn, #constant
strb rs, [rn, #constant]
bzc/bne label
rrx rd, rn
str rs, [rn, rm]
bcs/blo label
mov rd, rs
strb rs, [rn, rm]
bcc/bhs label
movs pc, lr
msr cpsr, rs
bge label
mov rd, #constant
msr spsr, rs
blt label
movt rd, #constant
mrs rd, cpsr
b label
mrs rd, spsr
bl label
Label
Se for necessário referenciar uma instrução ou variável, precede-se essa instrução ou variável de uma label.
cycle:
sub r0, r0, #1
. . .
b cycle
No exemplo acima, a label cycle: precede a instrução sub r0, r0, #1
para indicar o local para onde a instrução b cycle deve «saltar»
ao reiniciar um novo ciclo.
Comentários
Os comentários podem ser inseridos em qualquer lugar
quando delimitados pelas marcas '/*' e '*/' como em linguagem C,
ou depois do carácter ';' até ao fim da linha.
/* Main cycle.
Iterates until counter reach zero.
*/
cycle:
sub r0, r0, 1 ; decrement counter
. . .
b cycle
2.1. Diretivas¶
Diretivas de compilação são comandos que permitem controlar a operação do assembler.
Sintaticamente uma diretiva é identificada por uma palavra chave iniciada pelo carácter '.'. No texto do programa, uma diretiva e os seus parâmetros ocupam a mesma posição da mnemónica da instrução e dos respetivos parâmetros.
Na linguagem assembly do P16 existem diretivas para definir dados do programa, para controlar a localização dos dados e do código máquina em memória e para definir símbolos.
Diretivas para definição de dados do programa
|
Reserva uma sequência de bytes, cada um inicializado com o valor da respetiva expressão. Sem argumento não produz efeito. |
|
Reserva uma sequência de words, cada uma inicializada com o valor da respetiva expressão. Sem argumento não produz efeito. Uma word é composta por dois bytes, sendo o de menor peso colocado na posição de menor endereço que deve ser um endereço par. |
|
Reserva um bloco de memória com a dimensão em bytes indicada pelo parâmetro size, sendo cada byte inicializado com o valor da expressão fill. Se o argumento fill for omitido o bloco será preenchido com zeros. |
|
Reserva uma porção de memória para alojar a sequência de strings definida. As strings produzidas são compostas apenas pelos caracteres indicados, não contêm terminador. |
|
Reserva uma porção de memória para alojar a sequência de strings definida. As strings produzidas são compostas pelos caracteres indicados e terminadas com o valor zero (como na linguagem C). |
|
Avança o contador de localização até um valor múltiplo de
2^n. O novo valor do contador terá zero nos n bits
de menor peso. A omissão de argumento é equivalente a
|
Exemplo de utilização da diretiva .word na definição de uma variável de 16 bits
inicializada com o valor mil. Este valor é representado a 16 bits (0000 0011 1110 1000),
sendo o byte de menor peso guardado na posição de memória de endereço menor
e o byte de maior peso guardado na posição de endereço maior.
counter:
.word 1000
Exemplo de utilização da diretiva .byte na definição de um array
de três posições iniciadas com 3, 4 e 5, sucessivamente. São ocupadas três
posições de memória. A posição de endereço mais baixo recebe o valor 3,
a seguinte o valor 4 e a última o valor 5.
array:
.byte 3, 4, 5
Exemplo de utilização da diretiva .asciz para definição de um array
de caracteres iniciado com a string "Portugal", no formato da linguagem C.
Neste formato cada posição do array guarda o código de um carácter,
começando no endereço mais baixo e pela ordem de escrita.
A terminação da string é assinalada com o valor zero
na posição a seguir à do último carácter.
Neste exemplo são ocupadas nove posições de memória,
oito para os códigos dos caracteres e uma para o terminador.
message:
.asciz "Portugal"
Diretivas para definição de secções
|
Define uma secção, designada por name. |
|
Define uma secção com o nome |
|
Define uma secção com o nome |
|
Define uma secção com o nome |
|
Define uma secção com o nome |
|
Define uma secção com o nome |
A inserção de uma diretiva de definição de secção no texto do programa, significa que todos os elementos de programa, instruções ou variáveis, definidos depois dessa diretiva, são alojados nessa secção.
Diretivas para definição de símbolos
|
Define o símbolo name como sendo equivalente a value. |
A diretiva .equ permite definir um símbolo e associar-lhe um valor.
Esse valor pode ser expresso na forma de um número,
outro símbolo também definido com .equ, uma label
ou uma expressão envolvendo qualquer um dos anteriores.
O valor deve ser calculável pelo assembler, isto é,
não pode envolver símbolos que não estejam definidos.
Esta diretiva pode ser posicionada em qualquer lugar do texto do programa, tanto antes como depois da invocação do respetivo símbolo.
2.2. Símbolos¶
Um símbolo é uma palavra iniciada por uma letra seguida de mais letras ou dígitos.
Pode conter o carácter '_', tanto no início da palavra, como entre caracteres.
A formação de símbolos é case sensitive [1].
Os símbolos podem ser definidos através de labels ou da diretiva .equ.
label
Uma label é formada pelo símbolo seguido do carácter ':'.
Na invocação de uma label utiliza-se apenas o símbolo sem o carácter ':'.
Um símbolo do tipo label é equivalente ao endereço de memória
onde o código binário da instrução ou o valor da variável que lhe sucede está alojado.
No exemplo seguinte, a label counter: define o símbolo counter,
cujo valor associado é o endereço de memória onde está alojado o conteúdo
da variável.
counter:
.word 0
Para melhor evidência, a label costuma colocar-se na linha anterior à da instrução ou variável a que se refere.
.equ
No exemplo seguinte, a diretiva .equ define o símbolo MODE_MASK
equivalente ao valor binário 1110.
.equ MODE_MASK, 0b00001110
- Em geral, na composição léxica de símbolos usam-se as seguintes convenções:
na label, letras minúsculas e o carácter
'_'na separação de palavras em símbolos compostos.na diretiva
.equ, letras maiúsculas e o carácter'_'na separação de palavras em símbolos compostos.
2.3. Expressões¶
Os parâmetros constantes das instruções ou diretivas são definidos através de expressões. Estas podem ser valores numéricos simples ou expressões envolvendo vários operadores.
O valor de uma expressão ou sub-expressão é um número natural (conjunto N) no domínio \(0\) a \(2^{16} - 1\).
Exemplos de expressões:
mov r0, #3
movt r0, #((1 << 13) >> 8)
.word 1000 / 2 + 56
As expressões podem conter símbolos definidos com a diretiva .equ
com base noutra expressão.
Exemplo de expressão com símbolos:
.equ MASK, 1 << 4
.equ VALUE, 2055
mov r0, #VALUE & MASK
No cálculo das expressões, o resultado de cada operação é avaliado. Se a sua magnitude não for inferior a \(2^{16}\) (não codificável a 16 bits), é emitida uma mensagem de aviso.
No caso da subtração ou da operação unária negativo, um suposto resultado negativo com magnitude inferior a \(2^{16}\) é tratado como um resultado correto.
Por exemplo a expressão -1 é codificada da mesma forma que a expressão 0xffff.
No seguinte exemplo, ambas as instruções carregam o valor 0xff em R0.
mov r0, #-1 >> 8
mov r0, #0xffff >> 8
2.3.1. Operadores aritméticos¶
2.3.2. Operadores relacionais¶
Os operadores relacionais produzem valores booleanos. O valor booleano verdadeiro é representado como o valor inteiro 1, e o valor booleano falso é representado como o valor inteiro 0.
Na seguinte instrução, o registo destino (R0) é afetado com o valor 1.
mov r0, #3 == (5 - 2)
2.3.3. Operadores lógicos¶
Nestas operações os operandos são valores numéricos encarados como valores booleanos. Um valor numérico zero é encarado como falso e um valor numérico diferente de zero é encarado como verdadeiro.
Na seguinte instrução, o registo destino (R0) é afetado com o valor 1. Ambos os valores, 4 e 2, são encarados como valores booleanos verdadeiro, por serem diferentes de zero:
mov r0, #4 && 2
2.3.4. Operadores bit-a-bit¶
Nestas operações, cada bit de um operando é operado com o bit da mesma posição do outro operando.
Na seguinte instrução, o registo destino (R0) é afetado com o valor 6.
mov r0, #7 & 0b1110
2.3.5. Operadores deslocamento¶
<< |
deslocar para a esquerda |
>> |
deslocar para a direita |
A operação << insere zero na posição de menor peso do resultado.
Como os valores são encarados como números naturais, a operação >> insere sempre zero na posição de maior peso do resultado.
2.3.6. Operador condicional¶
Este operador tem o mesmo significado que na linguagem C.
condition_expression ? true_expression : false_expression
O valor produzido por este operador é igual ao da expressão true_expression,
se o valor de condition_expression for avaliado como verdadeiro
ou é igual ao da expressão false_expression
se o valor de condition_expression for avaliado como falso.
2.4. Contador de localização¶
Existe um contador de localização, associado a cada secção, que é inicializado a zero.
À medida que as instruções ou diretivas são processadas, o contador de localização é aumentado da dimensão de memória necessária para armazenar o código máquina da instrução ou o conteúdo da variável.
A linguagem assembly do P16 usa o símbolo '.' (um ponto isolado)
como identificador do contador de localização.
No contexto da linguagem assembly,
este símbolo substitui a label referente à instrução ou diretiva corrente.
b .
No exemplo, a instrução branch realiza um salto para a posição onde ela própria se encontra, confinando o processamento à execução repetida desta instrução.
2.5. Secções¶
As secções são zonas de memória que alojam elementos do programa – o código de instruções ou os dados do programa, segundo critérios de afinidade. O caso mais simples consiste em agrupar o código das instruções numa secção e as variáveis noutra secção.
Antes de especificar qualquer instrução ou diretiva
deve-se definir a secção que as vai conter.
A secção corrente é definida pela diretiva .section
ou pelas diretivas especificas .text. .rodata, .data, .bss ou .stack.
O programa da Listagem 2.1 é composto pela secção .data (linha 1)
onde se alojam as variáveis x, y e z,
especificadas pela diretivas que se seguem,
e pela secção .text (linha 9) onde se aloja o código máquina das instruções que se seguem.
A secção .data está localizada no endereço 0x1000 e tem dimensão de seis bytes.
A secção .text está localizada no endereço 0x4000 e tem a dimensão de 22 (0x16) bytes.
Os valores dos endereços usados neste exemplo são arbitrários.
Conforme se pode ver na Secção 3.2,
os endereços das secções são atribuídos em fase posterior à da escrita do programa.
P16 assembler v1.4.99 (Jan 10 2024) sections/seccoes1.lst Wed Jan 10 14:22:21 2024
Sections
Index Name Address Size
0 .data 0000 0006 6
1 .text 0006 0016 22
Symbols
Name Type Value Section
addressof_x LABEL 0016 22 .text
addressof_y LABEL 0018 24 .text
addressof_z LABEL 001A 26 .text
main LABEL 0006 6 .text
x LABEL 0000 0 .data
y LABEL 0002 2 .data
z LABEL 0004 4 .data
Code listing
1 .data
2 x:
3 0000 1E 00 .word 30
4 y:
5 0002 04 00 .word 4
6 z:
7 0004 00 00 .word 0
8
9 .text
10 main:
11 0006 70 0C ldr r0, addressof_x
12 0008 00 00 ldr r0, [r0]
13 000A 61 0C ldr r1, addressof_y
14 000C 11 00 ldr r1, [r1]
15 000E 80 80 add r0, r0, r1
16 0010 41 0C ldr r1, addressof_z
17 0012 10 20 str r0, [r1]
18 0014 0F B7 mov pc, lr
19
20 addressof_x:
21 0016 00 00 .word x
22 addressof_y:
23 0018 02 00 .word y
24 addressof_z:
25 001A 04 00 .word z
25
Fragmentação
Uma secção pode ser fragmentada ao longo do texto do programa.
Por exemplo, para que as variáveis possam ser definidas
junto ao código das funções que as utilizam,
mas alojadas na secção .data,
haverá fragmentos de .text entrecortados por fragmentos de .data.
Durante a compilação, os fragmentos de uma secção são concatenados,
pela ordem em que aparecem ao longo da descrição do programa para formar
a composição final de cada uma das secções.
P16 assembler v1.4.99 (Jan 10 2024) sections/seccoes2.lst Wed Jan 10 14:40:42 2024
Sections
Index Name Address Size
0 .data 1000 0003 3
1 .text 3000 0010 16
Symbols
Name Type Value Section
accumulate LABEL 3008 12296 .text
addr_counter LABEL 300E 12302 .text
addr_ptr LABEL 3006 12294 .text
counter LABEL 1002 4098 .data
ptr LABEL 1000 4096 .data
strtok LABEL 3000 12288 .text
Code listing
1 /*--------------------------------------------
2 Function strtok
3 */
4 .data
5 ptr:
6 1000 00 00 .word 0
7
8 .text
9 strtok:
10 3000 20 0C ldr r0, addr_ptr
11 3002 00 00 ldr r0, [r0]
12 ; ...
13 3004 0F B7 mov pc, lr
14
15 addr_ptr:
16 3006 00 10 .word ptr
17
18 /*--------------------------------------------
19 Function accumulate;
20 */
21 .data
22 counter:
23 1002 00 .byte 0
24
25 .text
26 accumulate:
27 3008 20 0C ldr r0, addr_counter
28 300A 00 08 ldrb r0, [r0]
29 ; ...
30 300C 0F B7 mov pc, lr
31
32 addr_counter:
33 300E 02 10 .word counter
33
Neste exemplo, mostram-se os endereços calculados de forma coerente,
embora tendo por base valores novamente arbitrários.
A variável ptr é alojada na secção .data
e a sua definição surge junto da função strtok que a utiliza.
O mesmo acontece com a variável counter e a função accumulate.
A secção text está separada em dois fragmentos
o primeiro contém o código da função strtok
e o segundo contém o código da função accumulate.
Na memória as duas variáveis ocupam posições contíguas
– ptr ocupa as posições de endereço 0x1000 e 0x1001
e counter a posição de endereço 0x1002.
O código das funções strtok e accumulate ocupam também zonas de memória contíguas,
respetivamente, a gama de endereços 0x3000 a 0x3007
e a gama de endereços 0x3008 a 0x300f.
2.6. Regras sintáticas¶
A linguagem assembly do P16 é semelhante à usada pelo assembler AS da GNU quando usado no desenvolvimento de programas para a arquitetura ARM. O objetivo é facilitar ao estudante a transição para essa arquitetura.
|
O elemento |
|
|
|
O elemento |
|
Elemento terminal |
Descrevem-se na Listagem 2.3, em notação Wirth Syntax Notation (WSN) Tabela 2.6 [2], as regras sintáticas a aplicar na escrita de programas em linguagem assembly do P16.
program = statement { statement }.
statement =
[label] [instruction | directive] "EOL" .
directive =
( ".section" identifier )
| ".text"
| ".rodata"
| ".data"
| ".bss"
| ".stack"
| ".align" [ expression ]
| ".equ" identifier "," expression )
| ( ".byte" | ".word" ) expression { "," expression }
| ".space" expression [ "," expression ] )
| ( ".ascii" | ".asciz" ) string { "," string } .
instruction =
"ldr" reg0-15 "," ( "[" ("pc" | "r15") "," expression "]" ) | identifier
| ( ( "ldr" | "str" ) ["b"] reg0-15 ","
( "[" reg0-7 ["," (reg0-15 | #expression)] "]" )
| ( "mov" | "movt" ) reg0-15, (reg0-15 | #expression)
| ( "push" | "pop" ) ["{"] reg0-15
| ( "add" | "sub" ) reg0-15, reg0-7, (reg0-15 | #expression)
| ( "adc" | "sbc" ) reg0-15, reg0-7, reg0-15
| "cmp" reg0-7, reg0-15
| ( "and" | "orr" | "eor" ) reg0-15, reg0-7, reg0-15
| ( "mvn" | "not") reg0-15, reg0-15
| ( "lsl" | "lsr" | "asr" | "ror" ) reg0-15, reg0-7, #expression
| "rrx" reg0-15, reg0-7
| "msr" psw "," reg0-15
| "mrs" reg0-15 "," psw
| ( "bzs" | "beq" | "bzc" | "bne" | "bcs" | "blo" | "bcc" | "bhs"
| "blt" | "bge" | "bl" | "b" ) identifier
| "movs pc, lr" .
reg0-7 = "r0" | "r1" | "r2" | "r3" | "r4" | "r5" | "r6" | "r7" .
| "R0" | "R1" | "R2" | "R3" | "R4" | "R5" | "R6" | "R7" .
reg0-15 = reg0-7
| "r8" | "r9" | "r10" | "r11" | "r12" | "r13" | "r14" | "r15"
| "R8" | "R9" | "R10" | "R11" | "R12" | "R13" | "R14" | "R15"
| "sp" | "lr" | "pc" | "SP" | "LR" | "PC" .
psw = "cpsr" | "spsr" | "CPSR" | "SPSR".
expression = logical_or_expression
| logical_or_expression "?" expression ":" expression .
logical_or_expression = logical_and_expression
| logical_or_expression "||" logical_and_expression .
logical_and_expression = inclusive_or_expression
| logical_and_expression "&&" inclusive_or_expression .
inclusive_or_expression = exclusive_or_expression
| inclusive_or_expression "|" exclusive_or_expression .
exclusive_or_expression = and_expression
| exclusive_or_expression "^" and_expression .
and_expression = equality_expression
| and_expression "&" equality_expression .
equality_expression = relational_expression
| equality_expression "==" relational_expression
| equality_expression "!=" relational_expression .
relational_expression = shift_expression
| relational_expression "<" shift_expression
| relational_expression ">" shift_expression
| relational_expression "<=" shift_expression
| relational_expression ">=" shift_expression .
shift_expression = additive_expression
| shift_expression "<<" additive_expression
| shift_expression ">>" additive_expression .
additive_expression = multiplicative_expression
| additive_expression "+" multiplicative_expression
| additive_expression "-" multiplicative_expression .
multiplicative_expression = unary_expression
| multiplicative_expression "*" unary_expression
| multiplicative_expression "/" unary_expression
| multiplicative_expression "%" unary_expression .
unary_expression = primary_expression
| "+" primary_expression
| "-" primary_expression
| "!" primary_expression
| "~" primary_expression .
primary_expression = literal | identifier | "(" expression ")" .
identifier = ("." | alphabet | "_") { "." | alphabet | number | "_" }.
label = identifier ":" .
literal = decimal | hexadecimal | octal | binary | "'" character "'" .
decimal = "0" | (("1" | ... | "9") { decimal_digit } ) .
hexadecimal = "0" ("x" | "X") hexadecimal_digit { hexadecimal_digit } .
octal = "0" ("1" | ... | "7") { octal_digit } .
binary = "0" ("b" | "B") ("0" | "1") { "0" | "1" } .
octal_digit = "0" | "1" | ... | "6" | "7" .
decimal_digit = "0" | "1" | ... | "8" | "9" .
hexadecimal_digit = decimal_digit | "a" | ... | "f" | "A" | ... | "F" .
alphabet = "a" | ... | "z" | "A" | ... | "Z" .
character = alphabet | decimal_digit
| "[" | "]" | "{" | "}" | "(" | ")" | "<" | ">"
| "=" | "|" | "&" | "%" | "$" | "#" | "/" | "?" | "!" | "_" | "*"
| "\b" | "\t" | "\n" | "\f" | "\r" | "\\" | "\"" | "\'"
| ( "\" ( decimal | hexadecimal | octal | binary ) ) .
string = "\"" character { character } "\"" .
"EOL" = control character for end of line
2.7. Limitações sintáticas¶
Não é possível definir símbolos iguais a mnemónicas de instruções. Por exemplo, não pode existir um símbolo "b" porque coincide com a mnemónica da instrução branch.
Se ocorrer omissão do último elemento sintático, a visualização do erro é assinalada corretamente, mas é dada indicação da linha seguinte.
Footnotes