Arquitetura x86

O processador Intel x86 usa arquitetura CISC (Complex Instruction Set Computer), o que significa que há um número modesto de registradores de propósito especial em vez de grandes quantidades de registradores de uso geral. Isso também significa que instruções complexas para fins especiais predominarão.

O processador x86 traça sua herança pelo menos desde o processador Intel 8080 de 8 bits. Muitas peculiaridades no conjunto de instruções x86 são devidas à compatibilidade com versões anteriores desse processador (e com sua variante Zilog Z-80).

O Microsoft Win32 usa o processador x86 no modo plano de 32 bits. Esta documentação se concentrará apenas no modo plano.

Registros

A arquitetura x86 consiste nos seguintes registros inteiros sem privilégios.

eax

Acumulador

ebx

Registro base

ECX

Contador de registro

edx

Registro de dados - pode ser usado para acesso à porta de E/S e funções aritméticas

Esi

Registro de índice de origem

Edi

Registro de índice de destino

ebp

Registro de ponteiro base

Esp

Ponteiro de pilha

Todos os registradores inteiros são de 32 bits. No entanto, muitos deles têm sub-registros de 16 bits ou 8 bits.

machado

Baixo 16 bits de eax

Bx

Baixo 16 bits de ebx

cx

Baixo 16 bits de ecx

dx

Baixo 16 bits de edx

sim

Baixo 16 bits de esi

di

Baixo 16 bits de edi

Bp

Baixo 16 bits de ebp

Sp

Baixo 16 bits de esp

al

Baixo 8 bits de eax

ah

Alta 8 bits de machado

bl

Baixo 8 bits de ebx

Bh

Alta 8 bits de bx

Cl

Baixo 8 bits de ecx

Ch

Alta 8 bits de cx

dl

Baixo 8 bits de edx

Dh

Alta 8 bits de dx

Operar em um sub-registro afeta apenas o sub-registro e nenhuma das partes fora do sub-registro. Por exemplo, armazenar no registro ax deixa os 16 bits altos do registro eax inalterados.

Ao usar o ? (Avaliar expressão) , os registros devem ser prefixados com um sinal de "arroba" ( @ ). Por exemplo, você deve usar ? @ax em vez de ? ax. Isso garante que o depurador reconheça ax como um registro em vez de um símbolo.

No entanto, o (@) não é necessário no comando r (Registros). Por exemplo, r ax=5 sempre será interpretado corretamente.

Dois outros registros são importantes para o estado atual do processador.

EIP

ponteiro de instrução

sinalizadores

sinalizadores

O ponteiro de instrução é o endereço da instrução que está sendo executada.

O registro de sinalizadores é uma coleção de sinalizadores de bit único. Muitas instruções alteram os sinalizadores para descrever o resultado da instrução. Esses sinalizadores podem ser testados por instruções de salto condicionais. Consulte Sinalizadores x86 para obter detalhes.

Convenções de chamada

A arquitetura x86 tem várias convenções de chamada diferentes. Felizmente, todos eles seguem as mesmas regras de preservação de registro e retorno de função:

  • As funções devem preservar todos os registros, exceto eax, ecx e edx, que podem ser alterados em uma chamada de função, e esp, que devem ser atualizados de acordo com a convenção de chamada.

  • O registro eax recebe valores de retorno de função se o resultado for de 32 bits ou menor. Se o resultado for 64 bits, o resultado será armazenado no par edx:eax .

Veja a seguir uma lista de convenções de chamada usadas na arquitetura x86:

  • Win32 (__stdcall)

    Os parâmetros de função são passados na pilha, enviados da direita para a esquerda e o receptor limpa a pilha.

  • Chamada de método C++ nativo (também conhecido como thiscall)

    Os parâmetros de função são passados na pilha, enviados da direita para a esquerda, o ponteiro "this" é passado no registro ecx e o receptor limpa a pilha.

  • COM (__stdcall para chamadas de método C++)

    Os parâmetros de função são passados na pilha, enviados da direita para a esquerda, o ponteiro "this" é enviado por push na pilha e, em seguida, a função é chamada. O computador chamado limpa a pilha.

  • __fastcall

    Os dois primeiros argumentos DWORD ou menores são passados nos registros ecx e edx . Os parâmetros restantes são passados na pilha, enviados da direita para a esquerda. O computador chamado limpa a pilha.

  • __cdecl

    Os parâmetros de função são passados na pilha, enviados da direita para a esquerda e o chamador limpa a pilha. A convenção de chamada __cdecl é usada para todas as funções com parâmetros de comprimento variável.

Exibição do depurador de registros e sinalizadores

Aqui está um exemplo de exibição de registro do depurador:

eax=00000000 ebx=008b6f00 ecx=01010101 edx=ffffffff esi=00000000 edi=00465000
eip=77f9d022 esp=05cffc48 ebp=05cffc54 iopl=0         nv up ei ng nz na po nc
cs=001b  ss=0023  ds=0023  es=0023  fs=0038  gs=0000             efl=00000286

Na depuração do modo de usuário, você pode ignorar o iopl e toda a última linha da exibição do depurador.

Sinalizadores x86

No exemplo anterior, os códigos de duas letras no final da segunda linha são sinalizadores. Estes são registradores de bit único e têm uma variedade de usos.

A tabela a seguir lista os sinalizadores x86:

Código da bandeira Nome do Sinalizador Valor Status do sinalizador Descrição
de Sinalizador de estouro 0 1 nvov Sem estouro - Estouro
df Sinalizador de direção 0 1 updn Direção para cima - Direção para baixo
if Sinalizador de interrupção 0 1 diei Interrupções desativadas - Interrupções habilitadas
sf Sinalizador de Sinal 0 1 por favor Positivo (ou zero) - Negativo
ZF Bandeira Zero 0 1 não Diferente de zero - Zero
af Bandeira de transporte auxiliar 0 1 naac Sem transporte auxiliar - Transporte auxiliar
pf Sinalizador de paridade 0 1 Pepo Paridade ímpar - Paridade par
cf Bandeira de Carry 0 1 bom trabalho Sem carregador - Carregador
que diabos Bandeira de armadilha Se tf for igual a 1, o processador gerará uma exceção STATUS_SINGLE_STEP após a execução de uma instrução. Esse sinalizador é usado por um depurador para implementar o rastreamento de etapa única. Não deve ser usado por outros aplicativos.
IOPL Nível de privilégio de E/S Nível de privilégio de E/S Este é um inteiro de dois bits, com valores entre zero e 3. Ele é usado pelo sistema operacional para controlar o acesso ao hardware. Ele não deve ser usado por aplicativos.

Quando os registros são exibidos como resultado de algum comando na janela Comando do Depurador, é o status do sinalizador que é exibido. No entanto, se você quiser alterar um sinalizador usando o comando r (Registros), você deve se referir a ele pelo código do sinalizador.

Na janela Registros do WinDbg, o código do sinalizador é usado para exibir ou alterar sinalizadores. O status do sinalizador não é suportado.

Veja um exemplo. Na exibição do registro anterior, o status do sinalizador ng é exibido. Isso significa que o sinalizador de sinal está definido como 1. Para alterar isso, use o seguinte comando:

r sf=0

Isso define o sinalizador de sinal como zero. Se você fizer outra exibição de registro, o código de status ng não aparecerá. Em vez disso, o código de status pl será exibido.

A bandeira de sinal, a bandeira zero e a bandeira de transporte são as bandeiras mais usadas.

Condições

Uma condição descreve o estado de um ou mais sinalizadores. Todas as operações condicionais no x86 são expressas em termos de condições.

O montador usa uma abreviação de uma ou duas letras para representar uma condição. Uma condição pode ser representada por várias abreviações. Por exemplo, AE ("acima ou igual") é a mesma condição que NB ("não abaixo"). A tabela a seguir lista algumas condições comuns e seu significado.

Nome da condição Sinalizadores Significado

Z

ZF=1

O resultado da última operação foi zero.

NZ

ZF = 0

O resultado da última operação não foi zero.

C

CF=1

A última operação exigiu um transporte ou empréstimo. (Para inteiros sem sinal, isso indica estouro.)

NC

CF=0

A última operação não exigiu um transporte ou empréstimo. (Para inteiros sem sinal, isso indica estouro.)

S

SF=1

O resultado da última operação tem seu conjunto de bits altos.

NS

SF=0

O resultado da última operação tem seu bit alto claro.

O

OF=1

Quando tratada como uma operação inteira assinada, a última operação causou um estouro ou estouro negativo.

NO

OF=0

Quando tratada como operação inteira assinada, a última operação não causou um estouro ou estouro negativo.

As condições também podem ser usadas para comparar dois valores. A instrução cmp compara seus dois operandos e, em seguida, define sinalizadores como se subtraísse um operando do outro. As condições a seguir podem ser usadas para verificar o resultado de cmp value1, value2.

Nome da condição Sinalizadores Ou seja, após uma operação CMP.

E

ZF=1

valor1 == valor2.

NE

ZF = 0

valor1 != valor2.

GE NL

SF=DE

valor1>= valor2. Os valores são tratados como inteiros com sinal.

LE NG

ZF=1 ou SF!=OF

valor1<= valor2. Os valores são tratados como inteiros com sinal.

BOM JOGO NLE

ZF=0 e SF=OF

valor1>valor2. Os valores são tratados como inteiros com sinal.

L NGE

SF!=DE

valor1<valor2. Os valores são tratados como inteiros com sinal.

AE NB

CF=0

valor1>= valor2. Os valores são tratados como inteiros sem sinal.

SEJA NA

CF=1 ou ZF=1

valor1<= valor2. Os valores são tratados como inteiros sem sinal.

Um NBE

CF=0 e ZF=0

valor1>valor2. Os valores são tratados como inteiros sem sinal.

B NAE

CF=1

valor1<valor2. Os valores são tratados como inteiros sem sinal.

As condições são normalmente usadas para agir sobre o resultado de uma instrução cmp ou de teste . Por exemplo,

cmp eax, 5
jz equal

Compara o registrador EAX com o número 5 calculando a expressão (EAX - 5) e definindo sinalizadores de acordo com o resultado. Se o resultado da subtração for zero, o sinalizador zr será definido e a condição jz será verdadeira, então o salto será realizado.

Tipos de dados

  • Byte: 8 bits

  • Palavra: 16 bits

  • DWORD: 32 bits

  • qword: 64 bits (inclui duplos de ponto flutuante)

  • Tword: 80 bits (inclui duplos estendidos de ponto flutuante)

  • oword: 128 bits

Notação

A tabela a seguir indica a notação usada para descrever as instruções da linguagem assembly.

Notação Significado

R, R1, R2...

Registros

m

Endereço de memória (consulte a seção Modos de endereçamento subsequentes para obter mais informações.)

n

Constante imediata

r/m

Registro ou memória

r/#n

Registro ou constante imediata

r/m/#n

Registro, memória ou constante imediata

Cc

Um código de condição listado na seção Condições anterior.

T

"B", "W" ou "D" (byte, palavra ou dword)

conta T

Acumulador tamanho T: al se T = "B", ax se T = "W" ou eax se T = "D"

Modos de endereçamento

Existem vários modos de endereçamento diferentes, mas todos eles assumem a forma T ptr [expr], onde T é algum tipo de dados (consulte a seção Tipos de dados anterior) e expr é alguma expressão envolvendo constantes e registradores.

A notação para a maioria dos modos pode ser deduzida sem muita dificuldade. Por exemplo, BYTE PTR [esi+edx*8+3] significa "pegue o valor do registrador esi , adicione a ele oito vezes o valor do registrador edx , adicione três e acesse o byte no endereço resultante".

Pipelining

O Pentium é de dupla emissão, o que significa que pode executar até duas ações em um tique de clock. No entanto, as regras sobre quando é capaz de realizar duas ações ao mesmo tempo (conhecidas como emparelhamento) são muito complicadas.

Como o x86 é um processador CISC, você não precisa se preocupar com slots de atraso de salto.

Acesso à memória sincronizada

As instruções de carregamento, modificação e armazenamento podem receber um prefixo de bloqueio , que modifica a instrução da seguinte maneira:

  1. Antes de emitir a instrução, a CPU liberará todas as operações de memória pendentes para garantir a coerência. Todas as pré-buscas de dados são abandonadas.

  2. Ao emitir a instrução, a CPU terá acesso exclusivo ao barramento. Isso garante a atomicidade da operação de carga/modificação/armazenamento.

A instrução xchg obedece automaticamente às regras anteriores sempre que troca um valor com a memória.

Todas as outras instruções são padronizadas para não bloquear.

Previsão de salto

Prevê-se que saltos incondicionais sejam realizados.

Prevê-se que saltos condicionais sejam feitos ou não, dependendo se eles foram feitos na última vez que foram executados. O cache para registrar o histórico de saltos é limitado em tamanho.

Se a CPU não tiver um registro de se o salto condicional foi dado ou não na última vez que foi executado, ela prevê saltos condicionais para trás como realizados e saltos condicionais para frente como não realizados.

Alinhamento

O processador x86 corrigirá automaticamente o acesso à memória não alinhada, com uma penalidade de desempenho. Nenhuma exceção é levantada.

Um acesso à memória é considerado alinhado se o endereço for um múltiplo inteiro do tamanho do objeto. Por exemplo, todos os acessos BYTE estão alinhados (tudo é um múltiplo inteiro de 1), os acessos WORD a endereços pares estão alinhados e os endereços DWORD devem ser um múltiplo de 4 para serem alinhados.

O prefixo de bloqueio não deve ser usado para acessos de memória desalinhados.