Introdução aos Sistemas Computacionais 2008/09 Departamento de Informática da Faculdade de Ciências da Universidade de Lisboa Fascículo ASM1 Introdução ao Assembly 1. Introdução ao Tema O processador de um computador é o componente encarregado de executar instruções. Em última instância, o processador não faz mais do que executar sequencialmente instruções que não são mais do que ordens codificadas em números binários. A linguagem em que os programas são especificados para poderem ser executados por um processador denomina-se de linguagem máquina. Como para um ser humano é difícil interpretar sequências de números, nunca se programa em linguagem máquina mas em linguagens de nível ligeiramente superior denominadas assembly. Todo o processador tem a sua linguagem máquina e o seu assembly. As instruções assembly são geralmente chamadas de mnemónicas dado poderem ser entendidas como algo que nos ajuda a decorar as instruções em linguagem máquina. A tradução de um programa em assembly para linguagem máquina é feita por um programa denominado assembler. Nesta disciplina vamos considerar o Gnu ASsembler (GAS). As instruções em assembly fazem operações muito elementares, tornando a programação nessa linguagem algo difícil, mas imprescindível em certas circunstâncias. Em última análise, qualquer instrução assembly limita-se a manipular dados armazenados em três possíveis locais: num conjunto de variáveis internas ao processador denominadas registos, em memória, em dispositivos de entrada/saída (E/S). Nesta disciplina vão-nos interessar sobretudo os dois primeiros locais de armazenamento (registos e memória), já que os dados dos dispositivos de E/S são geralmente geridos pelo sistema operativo. A memória é uma parte importante do computador, que aparece de forma conspícua nos programas em assembly. Podemos considerá-la simplesmente como uma grande tabela com células de 8 bits (1 byte). Essas células são numeradas através de endereços, começando em 0, 1, 2, 3,, podendo chegar a 1 ou 2GiB numa configuração típica nos PCs actuais. Um processador tem sempre um número limitado de registos. Os nomes desses registos tipicamente são acrónimos curtos, designando aquilo para que são usados. No caso da arquitectura de processadores que vamos usar como referência, a arquitectura IA-32 da Intel (que engloba os processadores Intel 80386, 80486 e as diferentes gerações de Pentium), existem quatro registos de uso geral denominados eax, ebx, ecx e edx. Existem ainda outros que iremos introduzindo à medida que forem necessários. Esses quatro registos têm 32 bits que é o comprimento dos dados geralmente manipulado pelo reportório de instruções dos processadores incluídos na arquitectura IA-32 da Intel. Por isso, diz-se que estes processadores são de 32 bits. No entanto, estes processadores foram precedidos por outros membros da mesma família, como o 80286 e o 8088, que eram processadores de 16 bits. Por isso, para continuar a poder executar programas escritos para esses processadores, por vezes esses registos são manipulados como se tivessem apenas 16 ou 8 bits. Assim, considerando por exemplo o registo eax, os 16 bits menos significativos (ou seja, com menos peso) deste registo podem ser manipulados sob o nome de ax. O byte menos significativo do ax pode por sua vez ser manipulado sob o nome de al (low) e o mais significativo sob o nome de ah (high). O mesmo se aplica para os outros três registos, podendo ser manipulados como bx, bl, bh, etc. Todos os registos em assembly (ou mais precisamente, no GAS) são antecedidos por %, por exemplo, %eax, %dh, %cx. Daqui em diante vamos abreviar assembly da arquitectura IA-32 ou assembly dos processadores Intel simplesmente para assembly. Em assembly podem-se usar variáveis em memória, que são designadas por um nome (por ex., VAR1). Na realidade esse nome não é traduzido para linguagem máquina, sendo substituído pelo endereço de memória correspondente. O processador não reconhece algo tão simples como um nome de variável! Apenas registos e endereços de memória. A declaração de uma variável através do seu nome reserva o espaço de memória necessário para armazenar o tipo de dados ISC-LEI/FCUL 1
declarados para a variável. A tabela seguinte enumera as palavras chave usadas para declarar cada tipo de dados em assembly: Dimensão 1 byte.byte Palavra Chave (Directiva) 2 byte.word,.hword,.short 4 byte.int,.long 8 byte.quad 16 byte.octa A declaração de variáveis é realizada no interior de um bloco que se inicia com a palavra chave e tem o seguinte formato geral: nome_variável: declaração_dimensão valor_inicial Em breve iremos aprender como se declaram variáveis para as quais não é necessário definir um valor inicial. Em assembly podem-se também usar valores imediatos como operandos das instruções, que são assim chamados por serem colocados na memória imediatamente a seguir à instrução em linguagem máquina que os utiliza (por exemplo, $4, $0x10; $VAR1 denomina o endereço da variável VAR1). Em assembly as instruções levam um sufixo que indica o tamanho dos dados a transferir: b indica byte (8 bits), w indica word ou palavra (16 bits) e l indica longo/inteiro (32 bits). A instrução assembly mais simples é a nop que muito simplesmente não faz nada. Por vezes é útil como forma simples de tapar outras instruções, já que em linguagem máquina é a instrução 0 (zero). Em seguida, em termos de simplicidade, temos a instrução mov (de move) que copia um dado de um registo para outro ou para memória (ou vice-versa). Na realidade não existe uma única instrução mov mas inúmeras variantes da instrução mov. Por exemplo: movl %eax, %ebx # copia o valor no registo %eax para o registo %ebx movl %ebx, %eax movw imediato, %dx movb %dh, endereço O valor do primeiro operando é copiado para o segundo, sobrepondo o valor que estava colocado no registo ou célula de memória. Os operandos podem ser os registos de uso geral, endereços de memória e valores imediatos. Há no entanto algumas restrições: o tamanho dos dois operandos tem de ser sempre idêntico (8, 16 ou 32 bits); só um dos operandos pode ser um valor imediato ou endereço; o segundo operando não pode ser um valor imediato. O carácter # indica que tudo o que está na mesma linha à sua direita é um comentário que não deve ser interpretado pelo assembler. Uma instrução semelhante é a xchg (de exchange) que não apenas copia o primeiro operando para o segundo, mas também o segundo para o primeiro, ou seja, troca os valores dos dois parâmetros. As restrições são semelhantes às do mov, excepto que nenhum operando pode ser um valor imediato (pense porquê, é óbvio). Exemplos: xchgl %eax, %ebx xchgb %ah, VAR_B A especificação do bloco de um programa destinado a instruções inicia-se com a palavra chave (directiva).text e deve conter pelo menos um rótulo associado ao programa principal (main). ISC-LEI/FCUL 2
2. Exercícios Fundamentais 1. Declare, no espaço reservado à declaração de variáveis, o seguinte conjunto de variáveis. a) uma variável de 32 bits, de nome ac e cujo valor inicial seja 125. b) uma variável de 32 bits, de nome tp e valor inicial 32. 2. Escreva um excerto de um programa assembly que execute as seguintes acções: a) copie o conteúdo da variável ac declarada na questão anterior para o registo eax. b) copie o conteúdo do registo eax para o registo ebx. c) copie o conteúdo da variável tp declarada na questão anterior para o registo eax.text.globl main main: ret 3. Considere o seguinte programa escrito em linguagem assembly, para a família de µp80x86, especificado segundo a notação usada nos compiladores da GNU. ac:.long 125 #variável ac de 32 bits com o valor inicial 125 tp:.long 32 #variável tp de 32 bits com o valor inicial 32.text.globl main main: movl ac,%eax movl %eax,%ebx movl tp,%eax nop movl $65535,%edx xchg %eax,%edx ret Indique, nos espaços reservados abaixo, qual o conteúdo de cada um dos registos da CPU aí indicados, no instante imediatamente antes da execução da instrução ret. Utilize a palavrachave indeterminado para indicar um valor desconhecido. Registo EAX 10 Registo EBX 10 Registo ECX 10 Registo EDX 10 ISC-LEI/FCUL 3
4. Escreva um pequeno excerto de um programa assembly que coloque em %eax o conteúdo da célula de memória com endereço 0x100. 5. Escreva um pequeno excerto de um programa assembly que copie o valor da variável VAR1 para a variável VAR2, ambas de 8 bits. 6. Das seguintes instruções assembly da família de µp80x86, especificadas segundo a notação usada no GAS, indique qual a única inválida. movl %eax,%ebx movw %ax,%bx movl %ebx,%eax movw %ax,%ebx 7. Considere o seguinte programa escrito em linguagem assembly, para a família de µp80x86, especificado segundo a notação usada nos compiladores da GNU. ac:.long 9876 tp:.long 123456.text.globl main main: xchgl ac,%eax movl %ecx,%ebx movl %edx, %ecx movl %eax, %ecx movl tp,%edx movl $65535,%eax xchg %eax,%edx ret Indique, nos espaços reservados abaixo, qual o conteúdo de cada uma das variáveis e de cada um dos registos da CPU aí indicados, no instante imediatamente antes da execução da instrução ret. Utilize a palavra-chave indeterminado para indicar um valor desconhecido. Registo EAX 10 Registo EBX 10 Registo ECX 10 Registo EDX 10 Variável ac 10 Variável tp 10 ISC-LEI/FCUL 4
8. Considere o seguinte programa escrito em linguagem assembly, para a família de µp80x86, especificado segundo a notação usada nos compiladores da GNU. ac:.long 128 #variável ac de 32 bits com o valor inicial 128 tp:.long 32 #variável tp de 32 bits com o valor inicial 32.text.globl main main: movl ac,%eax movl tp,%ebx movl $512,%ecx movl $64,%edx nop xchg %ebx,%ecx xchg %ecx,%edx ret Indique, de entre as opções indicadas abaixo, qual a instrução que deve substituir a instrução nop, para que o conteúdo de cada um dos registos da CPU, no instante imediatamente antes da execução da instrução ret, seja o seguinte: EAX = 32 10 EBX=512 10 ECX=64 10 EDX = 128 10 movl %ebx,%eax xchgw %eax,%ebx movl $128,%edx xchgl %eax,%ebx xchgl %eax,%edx 9. Explique qual a diferença, subtil na sintaxe, mas enorme nos resultados produzidos, entre as instruções: movl tp,%ebx movl $tp,%ebx ISC-LEI/FCUL 5
10. Explique qual a diferença, subtil na sintaxe, mas enorme nos resultados produzidos, entre as instruções: movl $512,%ecx movl 512,%ecx 11. Explique qual a diferença na memória reservada pelas seguintes declarações de variáveis, em três programas diferentes. Como as classificaria segundo a sua correcção? ig: ik:.long 24 ig:.long ik:.long 24 ig:.long 16 ik:.long 24 12. Das seguintes instruções assembly da família de µp80x86, especificadas segundo a notação usada no GAS, indique qual a única instrução válida, sabendo que ac e tp são os nomes de duas variáveis de 32 bits. xchgw ac,tp xchgl ac,tp xchgl %ebx,%eax xchgw %ax,%ebx xchgl %ax,%ebx ISC-LEI/FCUL 6
13. Das seguintes instruções assembly da família de µp80x86, especificadas segundo a notação usada no GAS, indique qual a única que produz um resultado diferente das restantes. movl $65535,%eax movl $0xFFFF,%eax movl $0177777,%eax movl $177777,%eax 14. Das seguintes instruções assembly da família de µp80x86, especificadas segundo a notação usada no GAS, indique qual a única inválida. movl $65535,%eax movl $0xFFFF,%eax movl $0177777,%eax movl %eax,0xffff 15. Das seguintes instruções assembly da família de µp80x86, especificadas segundo a notação usada no GAS, indique qual a única inválida. movl $65535,%eax movl $0xFFFF,%eax movl $0177777,%eax movl %eax,$0xffff ISC-LEI/FCUL 7
3. Material de Apoio Obrigatório GNU Assembler for 80186 and higher, Roger Jegerlehner, 1996-2003, formulário de referência rápida disponível na página da disciplina. 4. Exercícios Adicionais Recomendados Caderno de Exercícios de ISC (5ªedição) (GU-ISC-05-10), exercícios OB1 e OB2. Documento disponível na página da disciplina. 5. Bibliografia Gas-Gnu ASsembler e Arquitectura Intel x86 (GU-ISC-03-12): slides 1-8. Assembly para o Assemblador da GNU, Arquitectura Intel IA-32 (TA-ISC-05-10): cap. 1, 2, 3 (até pag. 30) e 4 (até pag. 45) Caderno de Exercícios de ISC (5ªedição), Teresa Chambel, Dulce Domingos, et.al, DI-FCUL, Outubro de 2005 (GU-ISC-05-10). Disponível na página da disciplina. ISC-LEI/FCUL 8