Resumo Detalhado 1 (ASC) - PDF
Document Details
Tags
Related
- 09__AOK__Ligjërata 09__Bashkësia e instruksioneve x86. Procedurat (funksionet) -- MSc. Valdrin Haxhiu.pdf
- Lecture 02 Background on Intel's ISA PDF
- Lecture 1: Introduction to Computer Organization and Architecture PDF
- Lec8-4471029-ISA (Tradeoff) PDF
- Virtualisation et Cloud - Chapitre 1 PDF
- Principles of Software Security IFN657 Lecture 3 PDF
Summary
This document provides a detailed summary of the components of x86 architecture, including registers, the arithmetic logic unit (ALU), the floating-point unit (FPU), and the control unit. It also covers data and instruction sections, and various addressing modes.
Full Transcript
3\. Arquitetura do Reportório de Instruções x86\ \ **\ *3.1 COMPONENTENTES DA ARQUITETURA DO REPORTÓRIO DE INSTRUÇÕES x86:*\ \ Define o modelo abstrato do computador que é visto pelo software, este último interage com a máquina física através deste reportório, que funciona como uma interface. Acaba...
3\. Arquitetura do Reportório de Instruções x86\ \ **\ *3.1 COMPONENTENTES DA ARQUITETURA DO REPORTÓRIO DE INSTRUÇÕES x86:*\ \ Define o modelo abstrato do computador que é visto pelo software, este último interage com a máquina física através deste reportório, que funciona como uma interface. Acaba por seguir o modelo de Von Neumann quanto aos componentes.\ **\ **A Unidade Central de Processamento é composta pelas seguintes unidades:** - **Registos.\ ** - **Unidade de Aritmética e Lógica.\ ** - **Unidade de Vírgula Flutuante / SIMD.\ ** - **Unidade de Controlo.** ***3.1.1 Registos:***\ \ **Os Registos correspondem ao espaço de armazenamento no CPU usado pelas instruções, já que é de acesso mais rápido do que a memória principal, embora mais pequeno.\ **No total existem 16 Registos Gerais de 64 Bits cada. Os Registos Gerais podem possuir partes com 32,16 ou 8 Bits, em que que, os prefixos R e E correspondem respetivamente a 64 e 32 Bits, e que os Sufixos X, H/L correspondem respetivamente a 16 e 8 Bits. No caso dos Registos Register, cada sufixo D,W e B representa respetivamente 32 Bits, 16 Bits e 8 Bits. - \(A) Accumulator: RAX -- EAX, AX, AH e AL - \(B) Base: RBX -- RBX, EBX, BX, BH e BL - \(C) Counter: RCX -- RCX, ECX, CS, CH e CL - \(D) Data: RDX -- RDX, EDX, DX, DH e DL - (SI) Source Index: RSI -- RSI, ESI e SI - \(DI) Destination Index: RDI -- RDI, EDI e DI - (BP) Base Pointer: RBP -- RBP, EBP e BP - (SP) Stack Pointer: RSP -- RSP, ESP e SP - \(R) Register: Rn -- Rn, RnD, RnW e RnB **E ainda existe o Program Counter (RIP), e Sinalização e Controlo (RFlags).**\ \ \ \ \ **O RFlags é o registo utilizado pelo CPU para sinalizar estados resultantes da execução de instruções, receber ordens básicas de controlo do Programa e gestão do sistema. Este tem como principais flags:** - **CF (Carry Flag) -- Serve para sinalizar que ocorreu transporte, ou empréstimo em operações entre números naturais.\ ** - **OV (Overflow Flag) -- Serve para sinalizar que ocorreu transbordo (overflow) numa operação entre números inteiros.\ ** - **ZF (Zero Flag) -- Serve para sinalizar que uma operação teve como resultado zero.\ ** - **SF (Sinal Flag) -- Serve para sinalizar que o resultado tem o sinal negativo.** ***3.1.2 Unidade de Aritmética e Lógica (ALU):\ \ *A Unidade de Aritmética e Lógica realiza as seguintes operações: adição, subtração, multiplicação e divisão com números naturais ou inteiros; operações lógicas booleanas; e ainda faz o deslocamento e rotação de Bits.**\ \ Como entrada pode aceitar valores imediatos (carregados junto com as instruções) e valores provenientes dos Registos Gerais e da Memória Principal. Em termos de saída, pode devolver o resultado para um registo geral, ou para a memória principal, mas também pode devolver informação complementar ao resultado armazenado no registo de Flags (RFlags).\ \ ***3.1.3 Unidade de Vírgula Flutuante (FPU / SIMD):\ ***\ **A Unidade de Vírgula Flutuante, ou a FPU, implementa o Padrão IEEE 754-2008, e partilha recursos com a Unidade SIMD (unidade vetorial), esta realiza as seguintes operações em vírgula flutuante: adição, subtração, multiplicação, divisão; raiz quadrada; funções trigonométricas; e comparação.** Em termos de entradas e de saídas, acaba por aceder aos Registos SSE2 (da unidade vetorial).\ \ ***3.1.4 Unidade de Controlo:***\ \ **A Unidade de Controlo realiza o ciclo fundamental de execução, que consiste em:** - **Ler (fetch)\ O endereço para leitura de instruções é mantido no registo RIP. Dado que as instruções x86 têm tamanhos variáveis é necessário conhecer o tamanho da instrução que vai ser executada para que esse tamanho seja adicionado a RIP (e fique a apontar para a instrução seguinte).\ ** - **Descodificar\ De modo que permita identificar a instrução a executar, após esta já é conhecido o tamanho da instrução.\ ** - **Executar\ Dependendo da instrução pode utilizar uma das unidades funcionais, no caso ALU / FPU / SIMD, ou pode aceder (ler/escrever) à memória.\ ** ***3.2 Dados e Instruções:*** **Um programa de computador ou software corresponde a uma sequência de instruções que descreve como o computador deve realizar uma determinada tarefa.\ Este define essencialmente uma ou mais secções de dados, e uma secção de código.**\ \ **A estrutura de um programa consiste em:** - **Section.data -- Zona de Dados Inicializados\ ** - **Section.bss -- Zona de Dados Não-Inicializados\ ** - **Section.text -- Zona de Código** **\ É importante referir que a declaração de variáveis apenas reserva espaço em memória, não define tipos.**\ \ ***3.2.1 Secção de Dados:*\ \ A localização dos dados em memória é definida pelas variáveis, as quais correspondem a endereços de memória simbólicos, que na compilação acabam por ser traduzidos para endereços respetivos de memória virtual, ou seja, para endereços numéricos.**\ **Os dados podem-se dividir consoante o seu tipo e o seu tamanho, nomeadamente:** - **Naturais representados em Binário e Inteiros representados em CP2. (Byte, Word (2 Bytes), dword (4 Bytes), qword (8 Bytes)).\ ** - **Vírgula Flutuante representados em Padrão IEEE 754. (32 Bits (4 Bytes) e 64 Bits (8 Bytes)).\ ** - **Endereços de Memória (8 Bytes), em que cada um corresponde apenas a um único dado.\ ** - **Bits e Cadeia (string) de Bits (1 ou mais Bits)\ ** - **Byte e Cadeia (string) de Bytes (1 ou mais Bytes)** **A representação ASCII (American Standard Code for Information Interchange) estabelece uma correspondência entre um conjunto de 128 caracteres baseados no alfabeto inglês e um byte específico.**\ \ **A cadeia de Bytes mais usada é a cadeia de caracteres, normalmente designada como apenas string.** Estes caracteres de uma forma geral estão representados em ASCII, mas podem ser usadas noutras representações como o UTF8 (que inclui o ASCII).\ \ **Os ficheiros de texto usam em geral a representação ASCII, ou UTF8.**\ \ **Os dados na memória são armazenados na memória a partir do Byte menos significativos.\ Um vetor de dados (array) contém múltiplos dados de um mesmo tipo.\ Por seu lado, uma estrutura de dados contém múltiplos dados que podem ser de tipos diferentes.**\ ***3.2.2 Secção de Código:***\ \ **Esta corresponde à zona onde se encontram as instruções do programa, as quais se encontram codificadas em Binário (linguagem-máquina). Esta secção não pode ser escrita, ou seja alterada, durante a execução do programa, só pode ser lida.**\ \ Relativamente ao Endereçamento de Instruções, o Program Counter (PC), que está no Registo RIP, contém o endereço da memória a partir de onde vai ser carregada a próxima instrução.\ \ ***3.3 Reportório de Instruções:***\ \ **O Reportório de Instruções corresponde ao conjunto das instruções que um dado processador suporta.\ \ Todos os processadores compatíveis com o Reportório de Instruções x86 conseguem executar as mesmas instruções.\ \ Um programa executável é composto por código e dados, em que o código é formado por uma sequencia de instruções do reportório x86, as quais são representadas em linguagem-máquina (binário), mas podem ser apresentadas para humanos em Assembly (texto em ASCII).\ \ Um Reportório de Instruções pode evoluir através da inclusão de novas instruções, ou de modificações de instruções já existentes, no entanto, qualquer modificação deve assegurar a compatibilidade para os programas já existentes.**\ \ As Instruções no Assembly podem se dividir nas seguintes categorias: - Gerais: nop, mov, xchg , cbtw, cwtl, cwtd, cltd, stc, clc, cmc, syscall, lea; - Aritméticas: add, sub, mul, div, neg; - Lógicas: not, and, or, xor, sal(r), shl(r), rcl(r), rol(r), test; - Controlo de fluxo: jmp, jc, jo, jz, js, cmp, loop; - Pilha e funções: push, pop, pushf, popf, call, ret; Algumas instruções só se aplicam a números Naturais ou Inteiros: - As Operações aritméticas sobre valores sem sinal (unsigned) aplicam-se quando se trabalha com números naturais, exemplo de instruções: mul, div, ja, jb, jna, jnb, jc; - As Operações aritméticas sobre valores com sinal (signed) o aplicam-se quando se trabalha com números inteiros, exemplos de instruções: imul, idiv, cbtw, cwtl, cwtd, jg, jl, jng, jnl, jo, js;\ \ \ \ \ \ \ \ \~ ***3.4 instruções de programas:\ \ ***Uma Instrução especifica uma determinada operação a realizar pelo processador, esta pode ou não admitir Operandos.\ \ Relativamente à Especificação dos Operandos, as Instruções podem ser: - **Implícitas;**\ Em que a especificação de operandos não faz parte da instrução. - **Explícitas;**\ Em que a especificação dos operandos faz parte da instrução. - **Mista;**\ Em que a instrução especifica simultaneamente operandos explícitos e implícitos. Relativamente à Especificação Explícita dos Operandos, temos 3 tipos de operandos que podemos colocar, estes podem ser dos seguintes tipos: - **Valor Imediato;\ Em que o operando faz parte da instrução, ou seja, está dentro dela.\ ** - **Registo;\ Em que o operando é lido ou escrito de um registo.\ ** - **Memória;\ Em que o operando é lido ou escrito da memória.\ ** - ![](media/image2.png)**I/O;\ Em que o operando é lido ou escrito de portas de entrada ou de saída.\ \ ** As Instruções com 2 Operandos, podem variar dentro da seguinte tabela, relativamente aos tipos de operando:\ \ \ O 2º Operando, caso seja de Destino, nunca pode corresponder a Valores Imediatos. ***3.4.1 Instruções gerais:\ \ *Relativamente a Instruções Gerais, temos as seguintes:*\ \ 3.4.1.1 Instrução geral nop:\ \ *Esta Instrução não realiza qualquer tipo de operação, apenas consome tempo, não possui operandos, deste modo apenas consome memória.\ As suas aplicações consistem no alinhamento de endereços de instruções, o que pode melhorar o desempenho, e no anulamento de instruções do programa, substituindo-as por NOP.** ***3.4.1.2 instrução geral mov:\ \ *Esta Instrução copia conteúdos do primeiro operador (op1) para o segundo operador (op2), em que OP1 pode ser um valor imediato, registo ou memória, mas o OP2 só pode ser um registo ou uma memória.\ Na possibilidade de nenhum dos operandos corresponder a um registo, é necessário indicar a quantidade de bits a copiar.*\ \ 3.4.1.3 instrução geral xchg:\ \ *Esta Instrução troca os conteúdos de OP2 com OP1, em que os operandos têm obrigatoriamente que ser do tipo registo ou do tipo memória. Não podem ser os dois do tipo memória.** ***3.4.1.4 Instruções Gerais MOVZX e MOVSX:\ \ *Estas Instruções servem respetivamente para fazer a Expansão de Bits em Números Naturais e para Inteiros. Ambos funcionam da mesma maneira, ou seja, MOVZX / MOVSX op2, op1.\ Relativamente aos operandos, o tamanho do op2 tem que ser superior ao de op1, de outro modo não dá para expandir os Bits, o op2 tem que ser um registo, mas op1 pode ser um registo ou memória.** ***3.4.1.5 Instruções Gerais Carry Flag:\ ***\ **As Instruções STC, CLC e CMC fazem o seguinte:** - **STC (Set Transport Flag) -- Coloca a CF (Carry Flag) a 1.** - **CLC (Clear Transport Flag) -- Coloca a CF (Carry Flag) a 0.** - **CMC (Complement Transport Flag) -- Inverte a CF (Carry Flag).** ***3.4.1.6 INSTRUÇÃO GERAL SYSCALL:\ \ *Esta Instrução é utilizada para chamar os Serviços do Sistema Operativo, ou seja, terminar o programa por exemplo, só possui operandos implícitos, e antes de a utilizar é necessário carregar um valor no registo RAX que especifica o serviço pretendido, o resultado da chamada é colocado em RAX.***\ * ***3.4.1.7 instruções gerais do tipo c?t?:\ \ *Estas Instruções têm como objetivo tratar da Expansão de Bits com Sinal do Registo A (sem operandos).** ***3.4.2 restrições arquiteturais:\ \ *Como foi visto anteriormente, há várias restrições relativamente aos operandos em certas instruções, tais como:** - **Um operando imediato não pode ser usado para guardar valores, porque se encontra codificado juntamente com a instrução, na zona de código que é só de leitura.\ ** - **Só um dos operandos pode estar na memória, porque a codificação de instruções só suporta a especificação de um endereço de memória\ ** - **Em geral, os operandos devem ter o mesmo tamanho, tal como em operações de movimento de dados e aritmética/lógica. Excetuam-se instruções cuja natureza dos operandos é diferente.** ***\ 3.4.3 Instruções Aritméticas:\ \ *Relativamente a Instruções Aritméticas temos as seguintes:*\ \ 3.4.3.1 adição e subtração:***\ \ **Temos as Operações ADD e SUB, do tipo ADD op2,op1 ou SUB op2,op1.\ O resultado da operação ficará armazenado em op2, o respetivo operando é perdido, mas pode ser copiado para outro registo caso se queira. As Flags são afetadas de acordo com o resultado.\ \ Estas operações funcionam com naturais e com inteiros.\ \ Para determinar se foi excedida a capacidade de representação deve-se testar a flag adequada para o tipo de números, ou seja:** - **Se foi uma operação entre naturais, testar a Flag do Transporte.** - **Se foi uma operação entre inteiros, testar a Flag do Overflow.** **O resultado das Flags fica armazenado, até que seja alterado.\ \ *3.4.3.2 Multiplicação e Divisão:*\ \ Temos ainda as Operações MUL e DIV, do mesmo tipo, estas que utilizam um operando explicito e um implícito, o qual corresponde aos Registos AX, logo multiplica-se ou divide-se o operando pelo valor dentro do Registo AX, o qual depende do tipo de dado que o operando é, ou seja, se o operando for QW, então é pelo RAX, etc.\ \ Dependendo do tipo do número, na Multiplicação utiliza-se uma instrução diferente:** - **Para Números Naturais: MUL op;** - **Para Números Inteiros: IMUL op;** **Quanto à Divisão, é da mesma forma:** - **Para Números Naturais: DIV op;** - **Para Números Inteiros: IDIV op;** **Na Multiplicação podem ser afetadas as Flags de Carry e Overflow, dependendo do tipo de dado, enquanto que na Divisão as Flags são afetadas de forma imprevisível.\ *3.4.3.3 Outras Instruções Aritméticas:*\ \ Temos ainda as Operações do Incremento e Decremento, estas respetivamente adicionam e subtraem 1 dos seus operadores. O resultado ficará em op, sendo que a Flag de Transporte não será afetada, as restantes podem ser afetadas.** - **INC op =\> op = op + 1;** - **DEC op =\> op = op -- 1;** **Por fim, ainda temos a Operação do Simétrico, em que basicamente transporta o Operador no seu simétrico, do tipo:** - **NEG op.** **\ *3.4.4 instruções aritméticas em vírgula flutuante:\ \ *Os Cálculos sobre valores representados em Vírgula Flutuante, ou seja em Padrão IEE 754, são feitos na sua maioria pela Unidade de Cálculo em Vírgula Flutuante (FPU), a qual é um componente da CPU.** - Os Cálculos podem ser feitos através de Instruções mais antigas, como as x87, ou com os registos em pilha; ou através das Instruções mais recentes SSE, que utilizam os registos XMM, YMM, etc... - **Os vários formatos do Padrão IEE 754, no caso, binary32, binary64, etc, só são suportados no conjunto de instruções SSE, estes utilizam os registos XMM de 128 Bits, ou seja, xmm0, xmm1, etc.** **De modo a realizar estes cálculos, primeiro tem que ser colocar os valores num dos registos, fazer a respetiva conversão para Vírgula Flutuante, e só depois realizar a Operação Aritmética.\ ** ***3.4.4.1 etapas da artimética em vírgula flutuante:\ \ *A cópia para um Registo XMM faz-se da seguinte maneira:** - **MOVSS / MOVSD op2, op1.\ Copia respetivamente 32 ou 64 Bits de op1 para op2, em que um tem que ser um dos 16 registos XMM e o outro um registo geral ou um endereço de memória.** **A conversão de um Número Inteiro para Vírgula Flutuante, ou inverso, ou entre IEE 754, faz-se para que os dois operandos sejam do mesmo tipo e tamanho e assim poderem realizar a operação, faz-se da seguinte maneira:** - **CVTSS2SI ou CVTSD2SI, ou seja, "convert from SS (binary32) or SD (binary64) to SI (Número Inteiro).** - **CVTSI2SS ou CVTSI2SD, ou seja, "convert from SI (Número Inteiro) to SS (binary32) or to SD (binary64).** - **CVTSS2SD ou CVTSD2SS, ou seja, "convert from SS (binary32) to SD (binary64) or from SD (binary64) to SS (binary32).** **Por fim, realiza-se a operação:** - **Adição (op2 = op2 + op1): ADDSS op2, op1 / ADDSD op2, op1** - **Subtração (op2 = op2 -- op1): SUBSS op2, op1 / SUBSD op2, op1 o** - **Multiplicação (op2 = op2 \* op1): MULSS op2, op1 / MULSD op2, op1 o** - **Divisão (op2 = op2 / op1) : DIVSS op2, op1 / DIVSD op2, op1** **Estas operações não afetam as flags como na ALU.***\ \ **\ 3.4.5 instruções lógicas:\ \ ***Relativamente a Instruções Lógicas, temos as seguintes:\ ***\ 3.4.5.1 instrução lógica not:\ ***\ **A Instrução Not basicamente faz a inversão Bit a Bit de um determinado operando em Binário, op = NOT op,** este que pode ser um registo ou memória, entre 8 e 64 Bits. Caso o operando seja um número inteiro positivo, então esta operação dará origem ao seu Simétrico representado em Complemento para um.**\ As Flags nunca serão afetadas.**\ \ ***3.4.5.2 Instrução Lógica OR:\ \ *A Instrução OR combina os dois operandos através da operação disjuntiva OR, ou seja, op2 = op2 OR op1. Quando em Binário um dos operandos tem 1 numa posição, então o resultado também terá.\ \ Quanto às Flags, a CF e OF são colocadas a 0, visto que nunca teriam possibilidade de alterar de valor, no entanto, a ZF e a SF podem alterar, visto que a troca de Bits pode fazer com que o resultado seja 0, ou que seja negativo.\ **\ **É importante referir que a Instrução não conhece os operandos, e por isso, mesmo na operação entre números naturais podem ser levantada essas duas Flags.**\ \ Isto é útil no caso de querermos converter um determinado número natural (entre 0 e 9) para a sua representação decimal na tabela de ASCII, isto faz-se com a utilização de uma máscara 0x30 (em hexadecimal). Basicamente, como todos os algarismos entre 0 e 9 na Tabela de ASCII são do tipo 3n, 30, 31, etc, então ao utilizarmos essa máscara vamos adicionar o 3 à esquerda, e o tal algarismo fica à direita, deste modo dá origem à sua representação em decimal na Tabela ASCII.\ \ ***3.4.5.3 Instrução Lógica AND:\ ***\ **A Instrução AND, tal como todos as Instruções Lógicas, é semelhante à Instrução OR, ou seja, acaba por aplicar a Operação Conjuntiva AND a dois operandos, deste modo, caso os dois operandos tenham Bits a 1 em Binário, então será aplicado um 1. Utiliza-se da seguinte forma: op2 = op2 AND op1\ \ As Flags CF e OF são colocadas a zero, as restantes são afetadas de acordo com o resultado.\ \ **Esta pode ser aplicada no caso de querermos converter um Carácter ASCII na sua respetiva correspondente em Binário, isto através da Máscara 0x0F.\ \ ***3.4.5.4 Instrução Lógica TEST (AND):\ \ *A Instrução TEST, é bastante semelhante à Instrução AND, a única diferença é que não altera o OP2, deste modo, apenas é utilizada para testar um conjunto de Bits, e acaba por alterar os valores das Flags, consoante o resultado obtido na operação.*\ ***\ \ ***3.4.5.5 instrução lógica xor:\ ***\ **Esta Instrução aplica a Operação XOR em dois operandos, basicamente caso um deles tenha um 1 e outro um 0 em Binário, então coloca um 1 nessa posição no resultado. Utiliza-se da seguinte forma: op2 = op2 XOR op1.\ \ As Flags CF e OF são colocadas a zero, as restantes são afetadas de acordo com o resultado\ É útil, caso queiramos apagar o conteúdo de um registo, sendo mais eficiente do que a Instrução MOV.**\ ***3.5 Deslocamento e rotação de bits:\ \ ***Existem operações que permitem deslocar um determinado número de Bits, seja para a esquerda ou para a direita, ou seja de outras formas.\ \ ***3.5.1.1 Deslocamento Lógico:***\ \ **Basicamente as Instruções SHL (Shift Left) e SHR (Shift Right) empurram um determinando número de Bits** **respetivamente para a Esquerda e para a Direita.** - **Funcionam os dois da seguinte forma: SHL / SHR op2, op1, em que op2 corresponde ao registo ou memória que é deslocado, e op1 ao número de Bits a deslocar, este que é um valor imediato.** **Pode ser utilizado para fazer multiplicações e divisões de naturais por 2, e todas as Flags podem ser afetadas.** ***3.5.1.2 Deslocamento Aritmético:\ \ *Basicamente funciona de maneira semelhante às Instruções SHL e SHR do Deslocamento Lógico, mas neste caso as Instruções SAL (Shift Arithmetic Left) e SAR (Shift Arithmethic Right) diferem, em que o SAR preserva antes o Bit de Sinal, e só depois empurra um determinado número n de Bits.\ Deste modo pode fazer multiplicações e divisões de inteiros por 2, em que todas as Flags podem ser alteradas.\ \ *3.5.3 Instruções de Rotação de Bits:\ \ *Temos dois tipos de Rotação, cada uma com duas instruções.** - **Instrução ROL (Rotate Left) e ROR (Rotate Right):\ Estas que basicamente empurram respetivamente um número de Bits à esquerda e à direita, neste caso todos os Bits são influenciados, porque há uma rotação geral, visto que os que estão mais à direita, passariam para a esquerda, e etc.\ ** - **Instruções RCL (Rotate Carry Flag Left) e RCR (Rotate Carry Flag Right):\ Estas que deslocam respetivamente a Carry Flag para a esquerda e para a direita, basicamente empurram um determinado número número de Bits para um lado, e depois adicionam a Carry Flag ao lado oposto.** **Ambas as Instruções utilizam-se da seguinte maneira: (Instrução) op2, op1;\ Nestes dois casos, apenas a Carry Flag e a Sinal Flag podem ser alteradas, as outras não mudam.**\ \ ***3.5.3 Aplicações destas Operações:***\ \ Podem ser utilizadas da seguinte maneira: - Aritmética com vírgula-flutuante quando não existe suporte de hardware; obriga a vários deslocamentos de bits para igualar os expoentes. - Geração de hashes; Aritmética polinomial para CRC, Reed-Solomon Codes \... - Sistemas embutidos (embedded); Leitura/escrita de portos ao nível do bit - Programação de jogos; Onde o desempenho é crucial - Manipulação de bitmaps; Mudar a profundidade de cor e outras operações comuns. ***3.5 instruções do Controlo do Fluxo:***\ \ **O Controlo do Fluxo corresponde à decisão de qual a próxima instrução a executar no programa, podendo não ser necessariamente sequencial. Deste modo, são necessárias instruções de modo a poder controlar este dito fluxo para nosso proveito, e assim surgem as seguintes instruções:** - **Instruções de Comparação;\ Basicamente são úteis no âmbito de averiguar se uma dada condição é válida, para assim poder executar a linha de código seguinte.\ ** - **Instruções de Salto: Condicionais e Incondicionais;\ Estas são as que realmente alterar diretamente o Controlo do Fluxo, visto que podem encaminhar o computador a ler outras instruções que não as sequenciais.** **Como podemos perceber, é então extremamente necessário indicar o endereço para o qual se quer saltar, e por isto surgem os Rótulos ou Etiquetas, estes são meros nomes, sem nenhum significado intrínseco, que representam determinados endereços de memória. Um Rótulo só corresponde a um único endereço.** - **Um Rótulo pode utilizar letras, números e outros símbolos, no entanto, a única regra é que tem sempre que começar pelo símbolo \_.\ ** - **Os Rótulos facilitam a referenciação de dados e instruções, visto que tornam mais claro para o utilizador e para a máquina para onde se vai saltar, e o que se vai fazer.** **\ *3.5.1 Instruções de Comparação e SALTOS:*\ \ A Instrução utilizada na comparação é a CMP op2, op1, esta efetua OP2 = OP1, não guardando o resultado em nenhum registo, mas pode afetar as Flags. Caso a condição se verifique, então salta para a linha de código seguinte.\ \ Relativamente aos Saltos, destacamos 2 tipos:** - **Salto Incondicional;\ Este basicamente desvia o fluxo de execução de imediato para o endereço que está no rótulo, e deste modo realiza as suas instruções. Funciona da seguinte maneira: JMP (rótulo).\ ** - **Salto Condicional;\ Existem muitas formas de provocar o Salto Condicional, a decisão de desviar a execução para um determinado rótulo depende do estado das Flags, já que cada forma de salto condicional testa Flags Especificas.\ Caso a determinada condição se verificar, o fluxo de execução é desviado para o endereço codificado no rótulo, caso contrário, não acontece nada e simplesmente ignora.** ***3.5.1.12 Tipos de Saltos Condicionais:\ \ *A Utilização do Salto Condicional pode-se fazer através de várias condições caso seja antecedido por uma Instrução de Comparação, tais como:** - **Saltar se OP2 = OP1, (Jump if Equal) -\> JE (rótulo);\ ** - **Saltar se OP2 =/= OP1 (Jump if Not Equal) -\> JNE (rótulo);\ ** - **Se OP1 e OP2 forem naturais:\ Saltar se OP2 \> OP1 (Jump if Above) -\> JA (rótulo);\ Saltar se OP2 \>= OP1 (Jump if Above or Equal) -\> JAE (rótulo);\ Saltar se OP2 \< OP1 (Jump if Below) -\> JB (rótulo);\ Saltar se OP2 \ JBE (rótulo);\ ** - **Se OP1 e OP2 forem inteiros:\ Saltar se OP2 \> OP1 (Jump if Greater) -\> JG (rótulo);\ Saltar se OP2 \>= OP1 (Jump if Greater or Equal) -\> JGE (rótulo);\ Saltar se OP2 \< OP1 (Jump if Lower) -\> JL (rótulo);\ Saltar se OP2 \ JLE (rótulo);\ \ ** **Pode-se também utilizar Saltos Condicionais com teste individual de Flags:** - **Saltar se CF = 1 (Jump if Carry) -\> JC (rótulo);** - **Saltar se CF = 0 (Jump if Not Carry) -\> JNC (rótulo);\ ** - **Saltar se OF = 1 (Jump if Overflow) -\> JO (rótulo);** - **Saltar se OF = 0 (Jump if Not Overflow) -\> JNO (rótulo);\ ** - **Saltar se ZF = 1 (Jump if Zero) -\> JZ (rótulo);** - **Saltar se ZF = 0 (Jump if Not Zero) -\> JNZ (rótulo);\ ** - **Saltar se SF = 1 (Jump if Signal) -\> JS (rótulo);** - **Saltar se SF = 0 (Jump if Not Signal) -\> JNS (rótulo);\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ** ***3.5.2 Controlo do Fluxo com if-then E IF-THEN-ELSE:\ \ *Em Assembly, a estrutura If-Then é construída através de Instruções de Comparação e Jump's, ou seja, é feita a avaliação de uma determinada expressão através de uma Instrução de Comparação, caso esta seja verdade com o auxílio de Instruções de Salto Condicional irá ser encaminhada para um determinado rótulo, caso seja falsa o código segue o rumo sequencial.**\ \ **A Instrução de Salto Condicional a usar depende do tipo dos números, ou seja se são Inteiros ou Naturais, e do Operador de Comparação entre os operandos OP1 e OP2.\ \ A Estrutura If-Then-Else em Assembly é construída de maneira semelhante à If-Then, a única diferença é que neste caso a Instrução de Salto, mesmo que a expressão seja falsa, encaminha o fluxo de execução para um outro rótulo.\ \ Também é possível colocar If's Aninhados, ou nested, em Assembly, isto é feito de maneira semelhante, no entanto, caso a expressão seja falsa, irá haver de seguida outra Instrução Condicional, esta que irá verificar uma determinada condição, que caso verdadeira altera o fluxo de execução, e caso falsa pode continuar a ser verificada por outras Instruções, ou simplesmente é encaminhada para um outro lado, tal como um else.\ \ Quando se pretenda testar uma condição que apenas depende do estado de uma única Flag, pode-se apenas utilizar uma única instrução de salto condicional, visto que só há dois outcomes possíveis.**\ \ \ ***3.5.3 Controlo do Fluxo com Ciclo For:***\ \ **Também é possível replicar o ciclo for em Assembly, isto através de uma Instrução de Comparação que verifica uma determinada condição que caso seja verdadeira salta para um determinado rótulo, normalmente o ciclo, e este encaminha novamente para o rótulo anterior, que volta a verificar a condição, e que caso seja verdadeira repete este loop, mas caso seja falsa, acaba o loop.\ \ Deste modo, também é possível colocar for's aninhados, ou nested, isto funciona da mesma maneira que os If's aninhados.\ **\ \ ***3.5.4 Controlo do Fluxo com Ciclo While e Do-While:***\ \ **Podemos ainda replicar os ciclos while em Assembly, isto através de Instruções de Comparação que verificam a validade de uma determinada expressão, caso esta seja verdadeira encaminha o fluxo de execução para um determinado rótulo, em que neste é novamente encaminhado para o rótulo anterior, voltando a verificar a condição, e que caso seja verdadeira repete o loop, caso seja falsa, termina o ciclo while.\ \ Através disto, é possível recriar o ciclo do-while, ou seja, digamos que o inverso do anterior, em que colocamos a Instrução de Comparação no fim, e que caso seja verdadeira encaminha o fluxo de execução para o inicio do rótulo, caso seja falsa termina o ciclo.\ ** ***3.6 Endereçamento da Memória:\ \ *A Memória Principal de um Computador, ou seja, a sua Memória RAM, é constituída por uma sequencia de células de 1 Byte, em que cada posição ou localização de uma célula designa-se de endereço.\ \ O acesso à memória é algo muito importante no funcionamento de um computador, sendo uma peça vital deste, para se poder aceder à memória, é necessário de antemão especificar a Operação (Leitura ou Escrita), o Endereço Inicial e o Tamanho da Transferência:\ \ Deste modo, os Endereços são importantes na medida de se querer aceder a alguma célula específica da Memória ou a um conjunto consecutivo destas, este processo requer a utilização de um dos modos de endereçamento x86 (de 64 Bits) à memória.\ ** ***3.6.1.1 Modos de Endereçamento:\ \ *Relativamente aos Modos de Endereçamento, ou seja, atribuir valores a Registos, já tínhamos visto anteriormente os tipos: Valor Imediato, Registo e Memória, e agora expandem-se para os seguintes:** - **Endereçamento Direto;\ Este Modo de Endereçamento pode ser de duas maneiras distintas, o Endereçamento Simbólico, que utiliza o rótulo, e o Endereçamento Numérico, que utiliza valores imediatos.** - - - Este modo pode ser utilizado para aceder a um vetor (Array) de números naturais, através de mov \[vetor + 1\], al, por exemplo..**\ ** - **Endereçamento Base;\ Este Modo de Endereçamento utiliza um registo de base, em que cujo conteúdo é o endereço, visto que ao longo do programa o conteúdo do registo pode ser alterado, este permite uma constante atualização do mesmo, logo permite um acesso dinâmico à memória.\ Este corresponde a uma forma de endereçamento calculado, visto que o programa calcula em tempo real o endereço a utilizar.\ ** - **Endereçamento Indexado;\ Este Modo de Endereçamento utiliza um registo como índice, geralmente um número natural, para um vetor de dados simples, e uma escala com o tamanho de cada dado do vetor, este que têm de ser de um único tamanho, seja Byte, Word, DWord, etc, e por causa disto, a escala só pode ter um dos valores 1,2,4 e 8, em que cada um destes representa um tipo de tamanho do dado.\ **É uma forma de Endereçamento calculado, assemelhando-se então ao Endereçamento Base, na medida que em tempo real é que calcula o endereço a utilizar.**\ **Normalmente é bastante utilizado em conjunção com um rótulo e/ou um registo de base que define o endereço onde começa indexação. ***3.6.1.2 Cálculo do Endereço de Memória:\ ***\ **Uma das perguntas que surge, é de como o computador calcula o Endereço de Memória a utilizar. Este processo é através de instruções específicas, as quais utilizam parenteses retos \[\], estes que se leem da seguinte forma: "Conteúdo do Endereço de Memória dado por".\ \ Relativamente ao Endereçamento direto à memória calcula-se da seguinte forma:** - **\[rótulo + constante\].** **Temos ainda a Expressão Geral de Endereçamento á Memória (em NASM):** - **\[rótulo + base + (índice x escala) ± constante\].** **Relativamente ao Endereço Efetivo, este calcula-se da mesma maneira, mas sem os parenteses.\ \ \ **Nestes casos, é preferencial que se utilize Registos de 64 Bits, mas pode-.se utilizar de 32 Bits, visto que também funcionam.**\ \ \ Temos ainda uma Instrução Geral, a qual carrega o endereço de memória (não o conteúdo) definido por OP1 em OP2, no caso, a Instrução LEA (Load Effective Adresse\]. Esta utiliza-se da seguinte maneira:** - **LEA OP2, OP1.\ ** **A Instrução MOV pode obter o endereço de uma variável, mas não o de uma Expressão de Endereçamento, deste modo a Instrução LEA é mais útil nestes casos.\ ** ***3.6.1.3 Tamanho da Transferência:***\ \ **Um Endereço de Memória de uma variável não define implicitamente o tamanho de uma transferência, visto que cada variável apenas corresponde a uma Expressão Simbólica que designa o endereço do dado, mas não o seu tamanho, deste modo é necessário especificar o número de Bytes a transferir.** - **Por exemplo: mov dword \[var\_dword\], 1\ ** ***3.6.2 ENDEREÇAMENTO DE VETORES:*** **O Apontador (ou ponteiro) é uma variável, do tipo qword, que contém um endereço de memória que aponta para uma determinada localização do programa.** **O Alinhamento da Memória faz-se de maneiras distintas dependendo da secção em questão:** - **Quanto aos Dados, as operações da ALU não tem qualquer tipo de requisitos de alinhamento para estes, no entanto, as Instruções SSE (vetoriais), as quais acedem a memória, necessitam que os dados estejam alinhados com endereços de 16 Bytes.\ Pode-se forçar o alinhamento de Dados da seguinte maneira:** **align 16** **vetor: resd 100\ ** - **Quanto ao Código, a execução de ciclos pode beneficiar de alinhamentos de dados específicos para a sua primeira instrução, o alinhamento pode-se forçar através da Instrução NOP, que acaba por otimizar o código.** **Considerando um exemplo para o conteúdo de um vetor de dados, no caso o Sistema ARGB, em que um pixel, corresponde a um vetor de 4 Bytes, em que por ordem temos o MSB Alpha, depois Red, Green e por fim o LSB Blue.** **Uma das técnicas utilizadas para manipular uma imagem consiste na alteração individual dos seus canais, ou seja, alterar especificamente o valor de um dos 4 Bytes, em todos os Pixels.** - **As Imagens em geral possuem desde algumas dezenas de KPixeis até dezenas de MPixeis, as com maior resolução possuem mais, e as com menor possuem menos. O tamanho de uma imagem tem impacto no espaço necessário para se armazenar, seja no tempo de transmissão (pela internet) ou pelo tempo de processamento por aplicação de filtros e outras técnicas de manipulação de imagem.** **Como se pode perceber, é pouco prático fazer a alteração manual de cada um dos Bytes em todos os Pixeis, visto que seriam muitas operações e consumiria muito tempo.\ \ Deste modo, através do Endereçamento de Vetores facilita-se este processo:** - **O Endereçamento Direto, por ser uma forma de endereçamento não-calculada, é pouco prática.\ ** - **O Endereçamento de Base, por sua vez, é uma forma de endereçamento calculada, e deste modo, torna-se relativamente eficaz nestes processos. No entanto, caso os dados estejam sempre organizados num vetor, é mais prático utilizar o Endereçamento Indexado.\ ** - **O Endereçamento Indexado, por sua vez, é uma forma de endereçamento calculada, e no caso de vetores, esta expressão de endereçamento expõe de forma mais clara o padrão de acesso aos dados, para outros padrões de acesso utiliza-se o Endereçamento de Base ou uma Combinação de ambos.** ![](media/image4.png)***3.7 armazenamento temporário -- a pilha:\ \ *O mapa de Memória Virtual de um programa em execução consiste em dois espaços de endereços:\ ** - **Espaço do Utilizador; até 128TiB.\ É onde está o código e dados do programa em execução.\ ** - **Espaço do Kernel; até 128TiB.\ É onde são mapeados o código e dados do núcleo do Sistema Operativo (kernel), possibilitando o acesso aos seus serviços.** **Dentro do Espaço de Utilizador, existem outras unidades, tais como a zona de código e de dados, e ainda a Heap, a Stack e a Zona de Mapeamento de Bibliotecas.**\ \ **O Heap e a Stack correspondem a duas formas de armazenamento temporárias:** - **A Pilha (Stack) é uma forma de armazenamento do tipo Last In First Out (LIFO), em que a ordem pela qual retiramos o conteúdo, sejam dados ou endereços, da pilha é inversa à ordem pela qual o colocamos.\ ** - **O Amontoado (Heap), por seu lado, armazena vários conjuntos de dados de forma não-organizada, o acesso a esta zona da memória faz-se através de um serviço do Sistema Operativo.** ***3.7.1 Secção da Pilha:\ ***\ **A Pilha (ou Stack) de um programa é uma zona de memória para armazenamento temporário de dados e de endereços, é colocada numa secção de memória que se encontra no topo do espaço de endereçamento do Utilizador.\ \ Cada elemento na Pilha tem 64 Bits (8 Bytes), e comporta-se como um vetor de tamanho variável de quad words.\ \ O seu tamanho máximo é definido com um valor por omissão (8MiB), mas pode ser redefinido.**\ \ \ \ \ \ \ \ \ ***3.7.2 Registo Apontador para o Topo da Pilha:***\ \ **Lembrando que no modo de Endereçamento da Base faz-se uso de um registo que contém um endereço de memória, este registo comporta-se como um Apontador para a memória, na medida em que o endereço pode mudar.**\ \ **Deste modo, temos também um Registo Apontador da Pilha (Registo RSP) e a organização dos dados na pilha é feita de forma invertida, tal como foi dito anteriormente, cresce no sentido dos endereços decrescentes, em que o topo da pilha é indicado pelo Registo RSP.\ Durante a execução de um programa é comum que a pilha cresça e diminua em função das necessidades de armazenamento temporário.\ **\ \ ***3.7.3 Operações utilizadas na Pilha:\ \ *Em geral, utiliza-se as instruções:** - **Push op;\ Em que se empurra o op para a Pilha.** - **Pop op;\ Em que se puxa da pilha para op.** **Nestes casos, o operando pode ser um registo, memória ou um valor imediato, este último só pode ser para a Instrução Push, e tem necessariamente de ser de 64 Bits, isto porque qualquer instrução com esta operação reserva sempre 8 Bytes na memória.\ \ Tudo o que é guardado na Pilha deve ser descartado logo que possível, libertando o espaço ocupando, este processo denomina-se de Reposição da Pilha.\ De uma maneira geral, a Pilha serve para salvaguarda temporária de dados.\ \ Temos ainda duas Instruções:** - **Push rax;\ Em que realiza a reserva de 8 Bytes no topo da pilha, e copia o operando para o topo da pilha.** - **Pop rax;\ Em que realiza a cópia de 8 Bytes no topo da pilha num operando, e faz a reposição da Pilha-** ***3.7.4 aplicações da Pilha:***\ \ **Tem como principais aplicações:** - **Considerando que existe um número limitado de Registos é possível que num dado instante se encontrem todos em utilização, deste modo, caso seja preciso salvaguardar temporariamente o conteúdo de um ou mais registos, devido a operandos implícitos ou operações sycall, pode recorrer-se à Pilha.\ **\ **O conteúdo destes registos é salvaguardado na Pilha, e logo que possível deve ser recuperado, e libertar o espaço da Pilha.\ A Pilha não deve ser o primeiro registo a que se deve recorrer.** - **Também se pode utilizar a Pilha para salvaguardar, não só determinados valores, mas também o Estado de Flag's, isto pode ser aplicado quando existem instruções que alteram o estado de uma Flag entre a instrução que aciona essa Flag e a Instrução que usa o seu estado.\ \ A sua aplicação é da seguinte maneira pushf / popf para respetivamente guardar e retirar apenas os 16 Bits menos significativos, ou pushfq / popfq para respetivamente guardar e retirar todo o registo de 64 Bits.\ Qualquer uma das instruções push reserva 8 Bytes.\ Estas instruções apenas possuem um operando (implícito) que é o registo das Flags (RFlags).\ ** - **Também se pode utilizar a Pilha para servir de Variável-Global, este espaço é reservado ao se reduzir o valor de RSP na quantidade necessária para as variáveis locais.\ A Pilha, ou Variável-Global, pode ser acedida como qualquer outra zona de memória, através de expressões de endereçamento relativas a RSP ou RBP, ou seja, através de Instruções como: mov RAX, \[RSP\] ou mov RBX, \[RSP+8\].\ \ Uma vez que não existem rótulos para a pilha, e que o valor de RSP não é fixo ao longo da execução do programa, é habitual usar-se o registo RBP para fixar o endereço de inicio das variáveis e usá-los nas expressões de endereçamento.** ***3.7.4.1 Estado da Pilha no Inicio de um Programa:***\ \ **No início do programa, a Pilha já contém dados que incluem:** - **Vetor de Parametros passados ao programa;\ ** - **Vetor de Variaveis de Ambiente;** **De modo a encontrar estes vetores deve-se agir consoante a maneira de como o programa é ligado:** - **Se for ligado com LD;\ argc -\> Topo da Pilha;\ argv -\> Vetor na Pilha terminado por 0;\ envp -\> Vetor na Pilha terminado por 0;\ ** - **Se for ligado com GCC;\ argc -\> Número de Parametros em RDI;\ argv -\> Endereço do Vetor em RSI;\ envp -\> Endereço do Vetor em RDX;\ \ ** ***3.7.4.2 Parâmetros do Programa:*\ \ Um programa pode receber parâmetros como input ao ser executado, estes dois parâmetros podem ser acedidos na Pilha, estando em ASCII, tendo que ser convertidos para Binário.*\ *** ***3.8 Funções e Procedimentos em x86 (Assembly):***\ \ **As Funções são utilizadas de modo a abstrair uma determinada parte de um programa noutro, de modo a otimizar o procedimento e poupar memória.\ \ Em Assembly, uma função representa-se através dos chamados rótulos, os quais vimos anteriormente como funcionam.** - **Deste modo, caso queiramos chamar uma função, ou seja, executá-la, em Assembly, devemos utilizar a seguinte instrução: call \[Rótulo\].\ ** - **O Retorno de uma função é feita através da Instrução ret, esta retira o endereço que está no topo da pilha, e continua a execução do programa a partir desse endereço.\ ** **Uma Função em Assembly, funciona da seguinte maneira:** 1. **A Operação call começa por empurrar para a Pilha o endereço da Instrução que a sucede no código do programa, ou seja dá push do endereço para o registo RIP.** 2. **Vai ser criado um novo endereço RSP, no topo da Pilha, de 8 Bytes, assim aumentando o tamanho da Pilha em 8 Bytes.** 3. **No final da função, a Operação ret obtém o endereço armazenado no topo da Pilha, e este é colocado no registo RIP, ou seja, extrai-se o endereço de RSP através da Instrução pop, e este é armazenado no registo RIP.** 4. **Por fim, a execução retorna ao código que a invocou.** **\ A passagem de parâmetros do código para uma função pode ser feita através de Registos Gerais ou através da Pilha:** - **A passagem de parâmetros por Registos pode ser feita através de qualquer Registo Geral (exceto RSP).** - **A passagem de parâmetros pela Pilha, é feita tal e qual, no entanto, obriga à reposição da Pilha no código chamador, ou seja, mover RSP.** **No caso de uma função gerar um resultado que deve ser enviado para o código chamador, então pode se fazer da mesma maneira que no envio de parâmetros, ou seja, através do Registo RAX (mais rápido), ou por outros, ou através da Pilha (menos simples, visto que obriga à reposição).\ \ O código de uma função é executado no mesmo processo que do código que a invoca, ou seja, os conteúdos que se encontram nos Registos da Função serão idênticos aos do código que a invocou, e no fim da execução desta, os seus Registos vão se sobrepor aos do código chamador.** **Deste modo, é necessário que ocorra uma Salvaguarda de Registos, para que os conteúdos não sejam sobrepostos e se perca conteúdo importante.** - **Esta faz-se antes da chamada da função, e logo a seguir à função terminar.** **Também é importante, que no início da função haja uma reposição do conteúdo dos Registos, esta deve ser feita no mesmo contexto em que foi feita a salvaguarda.\ \ Estes dois processos podem ser feitos através da Pilha.** ***3.8.1 Aplicações das Funções:***\ \ **As Linguagens de Alto-Nível Modernas tendem a passar à função, não valores, mas sim os respetivos endereços para a zona de memória em que estes se encontram, isto é útil quando se trabalha sob um grande conjunto de dados.\ \ Caso a função produza um número muito grande de dados, então o chamador também passa para um endereço de memória para a zona onde a função deve armazenar esses resultados.\ \ Tal como visto anteriormente, uma Função divide-se em 3 principais fases:** - **Preparação;\ Nesta fase, é feita a Salvaguarda dos Registos para a Pilha, de modo que os valores armazenados nestes não interferem com o código da função, e também a reserva de armazenamento local na pilha (stack frame), ou seja, inicializar as variáveis locais à função.\ ** - **Realizar Propósito da Funçãp:\ A seguir, concede-se acesso aos parâmetros e armazenamento local, e o código é executada, e o respetivo resultado será guardado nos Registos ou na zona dos parâmetros na pilha.\ ** - **Conclusão:\ Na última fase, é descartado o espaço na pilha para armazenamento local das variáveis, ocorre a Reposição dos Registos, ou seja, vai-se buscar o valor dos Registos inicialmente Salvaguardados, e por fim, termina-se a função através da instrução ret.** **\ Considerando uma determinada função myfunc que recebe 8 parâmetros, obriga deste modo a utilizar a pilha e a sua respetiva Reposição, na fase de Preparação.** - **É preciso ainda, fazer a Salvaguarda dos Registos e ainda criar espaço para as variáveis locais na Pilha, o que dará à Pilha o seguinte conteúdo:\ \ \ \ ** - ![](media/image6.png)**Considerando que esta chama uma outra função utilfunc, a qual tem apenas 3 parâmetros, e assim não obriga que se utilize a pilha para a passagem de parâmetros, deste modo, dá origem a uma Zona Vermelha dentro da Pilha, ou seja, um espaço disponível para uso do programa.\ \ Este pode ser utilizado para armazenar variáveis locais e parâmetros para outras funções, e assim evita a reserva / libertação de memória na pilha. Como não é chamada outra função, as variaveis locais são criadas na Zona Vermelha, ao contrário da função myfunc.\ ** ***3.9 Ferramentas de Sistema:\ ***\ **A Tradução, Geração e Carregamento de Programas faz-se de uma maneira sequencial:** 1. **O Código escrito numa Linguagem de Alto-Nível, como C e Java, tem antes que ser compilado numa Linguagem de Baixo-Nível como Assembly, ou seja Traduzido, através do Compilador.\ ** 2. **O Código já em Assembly, por ação do Assembler, é novamente traduzido mas desta vez em Linguagem-Máquina, sendo convertido num Módulo Objeto, esta Conversão é feita Instrução a Instrução, a do Compilador não.\ ** - **Há determinados compiladores que produzem diretamente Módulos Objetos através de Código escrito em Linguagens de Alto-Nível, deste modo, saltam a 1ª Etapa.\ ** 3. **Este Módulo Objeto é produzido para cada ficheiro originalmente convertido, ou seja, se tivermos dentro de um mesmo programa, várias funções em ficheiros diferentes, ou no caso de utilizarmos bibliotecas externas, é necessário ainda os juntar, e assim temos o Linker (ou Ligador), que junta os Módulos Objetos e converte num único ficheiro executável em Linguagem Máquina.\ ** - **Este processo denomina-se de Ligação Estática.\ ** 4. **O Loader, ou Carregador, observa as Instruções de Carregamento escritas pelo Linker, a partir destas, este vai reservar uma zona na memória, vai carregar a Linguagem Máquina para um endereço já determinado pelo Linker, carrega os dados e prepara a zona de dados e a Pilha, etc.\ ** 5. **Por fim, quando estiver tudo preparado, o ficheiro é finalmente executado.** ***\ \ 3.9.1 Compilador:\ ***\ **O Compilador tem duas principais zonas, a Zona de Front End e a Zona Back End, entre estas há uma representação intermédia daquilo que consiste o programa.\ ** - **A Zona Front End do Compilador encarrega-se da análise e procura de erros dentro do código, os quais podem ser do Tipo Léxico, Sintaxe ou Semânticos. Após a análise, é formado a representação intermedia do programa.\ \ ** - **A Zona Back End do Compilador encarrega-se da geração e otimização do código em Linguagem de Baixo-Nível, ou seja Assembly, ou de Módulos Objetos em Linguagem-Máquina.\ \ \ ** **O Compilador verifica tal como foi visto anteriormente, erros do tipo Léxico, Sintaxe ou Semanticos:** - **Erros Léxicos;\ Correspondem a erros de quando se utiliza keywords inexistentes dentro de uma Linguagem de Programação.\ ** - **Erros Sintáticos;\ Correspondem a erros de quando há uma mal utilização das keywords de uma determinada Linguagem de Programação.\ ** - **Erros Semanticos;\ Correspondem a erros de quando, por exemplo, quando há uma referencia a variaveis inexistentes, ou a rótulos que não existem no código.** **O Assembler tal como o Compilador, também verifica estes três tipos de erros.\ \ Há ainda aquilo que chamamos de Erros Funcionais, estes que não conseguem ser detetados pelo Compilador ou pelo Assembler, que aparecem sempre que o programa não funciona corretamente, e ocorrem devido a uma utilização de instruções, ou operandos, que produzem um resultado diferente do esperado.\ \ funções, as quais são a análise e procura de erros no código, e a conversão de código em Linguagem de Baixo-Nível ou em Objetos\ O Compilador antes de converter o Código em Linguagem de Baixo-Nível, ou num Módulo Objeto, vai antes fazer uma Análise (Front End) ao Programa, com o objetivo de encontrar erros.**\ \ ***3.9.2 Processo de Geração de Programas:***\ \ **O Processo de Geração de Programas, tal como foi visto anteriormente, é uma sequencia de etapas que levam desde a produção de um módulo objeto até a produção de um ficheiro executável. E\ \ Este é composto pelas seguintes etapas:** ***3.9.2.1 Produção de um Módulo Objeto:***\ \ **O Módulo Objeto pode ser obtido através do Compilador ou do Assembler, em que basicamente, se converte o código em Linguagem de Alto-Nível ou de Baixo-Nível diretamente para um código em Linguagem Máquina denominado de Módulo Objeto.\ \ Este fornece informações para construir um programa a partir de determinadas partes:** - **Cabeçalho;\ Este descreve conteúdos do Módulo Objeto.\ ** - **Segmento de Código (texto);\ Consiste nas Instruções Traduzidas.** - **Segmento de Dados Estáticos;\ Corresponde à zona onde se encontram os dados alocados para a vida do programa.\ ** - **Informação de Recolocação;\ Esta é utilizada para conteúdos que dependem da localização absoluta do programa carregado.\ ** - **Tabela Simbólica;\ Corresponde a definições globais e referencias externas, ou seja, variáveis e rótulos.\ ** - **Informação para Depuração (Debug);\ É utilizada para associar com o código fonte durante a depuração.** ***3.9.2.2 Linker de Módulos Objetos:*** **O Linker produz uma imagem executável, que é feita ao juntar os segmentos, ou seja Módulos Objetos relevantes sejam estes funções ou bibliotecas, resolve os rótulos (determina os seus endereços) e ainda corrige referencias dependentes da localização e externas.\ \ \ Este pode ainda deixar a correção de dependências de localização para um Loader que faça recolocação, no entanto, com Memória Virtual isto é desnecessário, visto que o programa pode ser carregado para uma localização absoluta no espaço de memória virtual.**\ \ \ Já vimos anteriormente a definição de Ligação Estática, e agora temos a Ligação Dinamica: - Através desta, a função da biblioteca só é ligada / carregada quando é chamada, deste modo, exige que o código da função seja recolocável, e evita que a imagem tenha de incluir todo o código de biblioteca que alguma vez possa ser referenciado transitoriamente, e automaticamente escolhe novas versões da biblioteca. - Ao contrário da Ligação Estática que liga todo o código das bibliotecas utilizadas. ***3.9.2.3 Analisar um Executável ELF:\ \ *Este é composto pelos seguintes componentes:** - **Informação Geral; Pode ser visualizada com: \$ file nome\_do\_executavel.** - **Cabeçalho; Pode ser visualizado com: \$ readelf -h nome\_do\_executavel.\ Neste está identificado se o programa é de 32 ou 64 Bits, a arquitetura da máquina (x86-64), o Sistema Operativo, a ABi, e ainda o endereço de entrada do programa.** - **Lista de Cabeçalhos de Programa; Pode ser visualizada com: \$ readelf -l nome\_do\_executavel.** - **Lista de Secções; Pode ser visualizada com: \$ readelf -s nome\_do\_executavel.** - **Disassembly; Pode ser visualizada com: \$ objdump -M intel -d nome\_do\_executavel.** - **Características do ELF usadas pelo Linker (ld); Podem ser visualizadas com: % ld -verbose.\ ** ***3.9.2.4 carregamento de um programa:\ \ *O Carregamento do ficheiro imagem, no disco, para a memória, consiste em:** 1. **Leitura do Cabeçalho para determinar os tamanhos das secções de dados.\ ** 2. **Criar o espaço de endereçamento virtual.\ ** 3. **Copiar código (text) e dados inicializados para a memória, ou criar entradas nas tabelas de páginas de modo a forçar o seu carregamento quando forem acedidos.\ ** 4. **Preparar argumentos na pilha.\ ** 5. **Iniciar Registos do Processador.\ ** 6. **E por fim, passar o controlo para a rotina de arranque, ou seja, copiar argumentos para os registos e chamar o \_start, ou seja, executar o programa, e por fim o programa termina com a chamada do sistema sys\_exit.***\ * **Por fim, denominamos de Processo o nome da entidade do Sistema Operativo que representa um programa em execução.\ **\ ***3.9.2.5 depurando um programa:\ \ *A Depuração (ou Debug) é o processo de identificar e corrigir comportamentos anómalos do programa, e suporta-se habitualmente em:** - **Instrumentação;\ Em que adiciona instruções que revelam o conteúdo de Registos e memória (o erro pode ser mascarado devido às alterações).\ ** - **Utilização de uma ferramenta auxiliar debugger, tal como gdb.\ ** **A Ferramenta Debugger ajuda a identificar a origem de erros funcionais, através da execução passo a passo, colocação de breakpoints (pontos de paragem), estes que podem ser condicionais, e observação do conteúdo dos Registos e Memória em tempo de execução.\ \ Uma vez descoberto um erro, é necessário alterar o programa.** ***\ ***\ \ \ \ \ \ \ \ \ 4. Processador:\ ***\ *O Processador (CPU -- Unidade Central de Processamento) corresponde à parte ativa do computador, esta realiza todo o trabalho de manipulação de dados e de tomada de decisões.** **É composto por dois principais blocos:** - **Circuito de Dados (Datapath);\ Corresponde ao Hardware que realiza as operações requeridas, é constituído por três partes, nomeadamente, as Unidades Funcionais (como a ALU e a FPU), pelos Registos e por Barramentos.\ ** - **Unidade de Controlo;\ Corresponde ao Hardware que controla o circuito de dados em termos de comutasdores, seleção de operação, movimento de dados, etc.** **4.2 *Tipos de Processadores:*\ \ Os Processadores podem variar de tamanho e de arquitetura dependendo do tipo de coisas que se pretende priorizar, temos vários tipos de Processadores, tais como:** - **Processador RISC (Reduced Instruction Set Computer);\ Este possui Instruções mais simples, o que leva o computador a ser mais rápido, é também conhecido por Arquitetura load/store.\ ** - **Processador CISC (Complex Instruction Set Computer);\ Esta Arquitetura de Processador implementa x86, em que o Hardware traduz as instruções para micro-operações simples. O micromotor e o desempenho são semelhantes aos do RISC, no entanto, neste os compiladores evitam instruções complexas, visto que é feita a tradução em instruções simples. A arquitetura x86 original tem muitas limitações, visto que não suporta proteção e nem memória virtual, só a cota de mercado x86 é que torna esta solução viável.\ ** **Por seu lado, a Arquitetura RISC comparada com a CISC, privilegia operações que envolvam registos,\ fornece um número adequado de registos, e limita o número de instruções de acesso à memória, deste modo favorece formatos de instruções mais simples, logo fáceis de descodificar, e assim maximizando a taxa com que as instruções são inciadas.** **\ *\ \ \ \ \ \ \ \ 4.2.1 blocos do processador:\ \ *Tal como vimos anteriormente, o Processador possui dois Blocos fundamentais, o Circuito de Dados e a Unidade de Controlo.\ *\ 4.2.1 Circuito de Dados do processador:*\ \ O Circuito de Dados de um Processador corresponde ao conjunto de blocos que são utilizados para a circulação e o processamento de informação.\ **![](media/image8.png)**\ Este Circuito de Dados contém:** - **Unidades Funcionais, tais como a ALU (Unidade de Aritmética e Lógica) e a FPU, etc;\ ** - **Registos;\ ** - **Barramentos Internos, que correspondem a linhas de dados e controlo dentro do CPU que permitem a ligação dos Registos às Unidades Funcionais, deste modo tornando possível a transferência de dados entre ambos;\ \ ** ***4.2.1.1 Unidades funcionais (alu e fpu):\ \ *Dentro das Unidades Funcionais destacam-se:** - **A maioria das ALU's realiza as operações vistas anteriormente, ou seja, operações aritméticas sobre Inteiros, operações lógicas sobre Bits e ainda operações de deslocamento e rotações de Bits. No entanto, determinadas ALU's podem realizar operações mais complexas, o que respetivamente necessita que esta seja maior, mais cara e que consuma mais energia.\ \ Nesta Unidade, as Entradas correspondem às operações e aos respetivos operandos, e as Saídas aos resultados do cálculo.\ ** - **A Unidade de Vírgula Flutuante, a FPU, corresponde à Unidade de Cálculo em Vírgula-Flutuante, deste modo realiza as operações aritméticas sobre valores deste tipo, representados num dos formatos especificados no Padrão IEE 754.\ \ \ \ \ \ \ ** ***4.2.1.2 Registos:*** **Os Registos Físicos são atribuídos aos Registos da Arquitetura x86 de acordo com a necessidade das instruções indicadas, ou seja, um dado Registo Físico pode a corresponder a outro Registo "Virtual" noutra arquitetura do reportório de instruções.\ \ Deste modo, é utilizada uma tabela, a Register Alias Table (RAT), que a cada Registo Físico corresponde ao seu respetivo Registo dentro da Arquitetura, neste caso na Arquitetura x86** ***4.2.1.3 Barramentos:*** **Dentro do Circuito de Dados existem vários Barramentos, os quais se encarregam de transferir as instruções, micro-operações, dados, endereços e permitem a ligação à Unidade de Controlo.\ \ *4.2.2 Unidade de Controlo do Processador:*** **Tal como foi visto anteriormente, a Unidade de Controlo (UC) implementa o ciclo fundamental de execução, ou seja, para cada instrução são executadas 3 operações:** - **Leitura (fetch);** - **Descodificação (decode);** - **Execução (execute);** **Este ciclo pode ser realizado sequencialmente, ou seja, a leitura da próxima instrução só ocorre quando for terminada a instrução atual, mas também pode ocorrer paralelamente, isto caso cada operação do ciclo for independente, utilizando uma conduta, o que é o caso de quando temos vários programas em execução ao mesmo tempo.\ \ Relativamente ao Ciclo Fundamental de Execução:** ***4.2.2.1 Leitura de Instruções:*** **Corresponde ao acesso à memória de instruções, em que se vai identificar o endereço da próxima instrução e consequentemente colocá-lo no Registo RIP, esta operação pode envolver aritmética.** ***4.2.2.2 Descodificação de Instruções:*\ \ Corresponde à interpretação de instruções, ou seja, a conversão dos Bits destas para sinais de controlo que controlam outras partes do CPU.\ É daqui que surge o Microcódigo, o qual é gerado por descodificação das instruções complexas, deste modo deve ser descodificado novamente de modo a dar origem aos sinais de controlo.\ \ *4.2.2.3 Execução de Instruções:*\ \ Dependendo da classe da instrução pode fazer o seguinte:** - **Utiliza diferentes Unidades Funcionais (ALU, FPU ou SIMD) para calcular seja o resultado aritmético, o endereço de memória para load/store, ou o endereço de destino do salto (branch).** - **Aceder à memória de dados para load/store.** - **Atualizar EIP, ou seja:\ EIP \ Buffer de Despacho (Buffer antes da etapa de execução), este permite reordenar as instruções a executar, na medida em que faz uma pequena análise das relações de interdependência das instruções e agrupa determinadas instruções para serem executadas fora de ordem.\ -\> Buffer de Reordenação (Buffer depois da etapa de execução) em que depois de serem executadas as instruções, os resultados devem ficar de acordo com a ordem inicial das instruções. ***4.3.2.2.1 Otimização do Ciclo do Relógio:*** **A Otimização do Ciclo do Relógio é fundamental, isto porque um Estágio não leva sempre o tempo máximo para realizar o seu trabalho, deste modo, o tempo médio necessário é inferior ao máximo, o que implica um aumento de Latência e pior desempenho.** - **Deste modo surge a necessidade da utilização de Buffers que adaptam variações no tempo de cada estágio.\ ** - **No entanto, caso um Buffer fique vazio vai provocar uma paragem na conduta, introduzindo uma bolha, ou seja, o estágio vai ficar sem trabalho durante um ciclo de relógio.** ***4.3.2.2.2 Impasses:*** **Estas situações são conhecidas por Impasses (hazards), ou seja, situações que impedem o início da próxima instrução no próximo ciclo, as quais podem criar uma bolha na conduta.\ Estes podem ser de vários tipos, tais como:** - **Impasses Estruturais;\ Em que um recurso de hardware que é necessário está ocupado.\ ** - **Impasses de Dados;\ Em que surge a necessidade de aguardar pela conclusão de uma leitura / escrita de uma instrução anterior.\ ** - **Impasse de Controlo;\ Em que surge quando uma decisão de ação de controlo depende de uma instrução anterior, tal como uma instrução de salto.\ ** ***4.3.2.2.3 Execução Especulativa e Previsão Dinâmica de Saltos:*** **Tal como vimos anteriormente, as Instruções de Controlo do Fluxo interferem com o Paralelismo de Instruções na medida em que podem originar Impasses de Controlo, visto a possibilidade de ocorrerem desvios no fluxo de execução faz com que a execução completa ou parcial de instruções seja especulativa.\ ** **Deste modo, surge a questão de quando é que se deve executar especulativamente, isto deve acontecer quando:** - **O Fluxo estiver em linha com a Especulação, visto que então existe um ganho de desempenho.\ ** - **Se não estiver, então é preciso reiniciar a execução, ou seja, eliminar tudo o que foi feito até aquele momento, no entanto, o custo é baixo.\ ** - **Quando não há execução especulativa, é impossível de utilizar condutas.\ ** **Este tipo de execução está diretamente relacionado com a previsão dinâmica de saltos, estes que podem totalizar cerca de 20% das instruções de um programa, tornando assim necessário registar padrões de saltos recentes, em que o sucesso da previsão depende de quando o padrão de saltos é previsível, ou seja, quantas vezes já ocorreu antes.\ ** ***4.3.3 Execução Simultanea de Programas (Multitasking):*** **O Conjunto do Sistema Operativo / CPU permite a execução simultânea de vários processos, o qual realiza pelo menos uma tarefa (thread), mas pode realizar mais do que uma tarefa.\ \ Este conjunto pode executar múltiplas tarefas de várias formas, as quais também podem ser caracterizadas por níveis de granularidade:** - **Multitarefa de Granularidade Grosseira; (Course MT)\ Neste caso, as tarefas só alternam depois de ser feita uma paragem longa (stall), deste modo, simplifica o hardware, mas não esconde as paragens curtas. (impasses de dados por exemplo)\ ** - **Multitarefa de Granularidade Fina; (Fine MT)\ Neste caso, as tarefas só alternam depois de cada ciclo de relógio da conduta, também é conhecida por multitarefa temporal (super-threading), neste caso, a execução de instruções está entrelaçada, em que quando uma tarefa é forçada a parar (stall), outras são executadas de modo a aproveitar o tempo, e assim resolve os impasses de dados (devido a dependência de dados).\ ** - ![](media/image12.png)**Multitarefa Simultânea; (SMT)\ Este tipo de Multitarefa é possível num processador escalonado dinamicamente com múltiplas execuções (multiple issue), ou seja, ordena tarefas independentes e permite o paralelismo de tarefas. A CPU neste caso suporta o armazenamento do estado arquitetural de cada tarefa, e as instruções vão sendo realizadas assim que as unidades funcionais estiverem disponíveis, entre tarefas que dependem umas das outras, é feita uma devida ordenação e através da mudança de nomes dos registos.** ***4.3.3.1 Tipos de Arquiteturas de Computadores:***\ \ **Temos diversas arquiteturas que dão focus no fluxo de instruções e nos dados, as quais podem se classificar considerando a sua Data Stream e a Instruction Streams (ambos input).**