Arquitetura x86
O processador Intel x86 usa uma arquitetura de CISC (computador conjunto de instruções) complexa, o que significa que há um número modesto de registros de finalidade especial em vez de grandes quantidades de registros de uso geral. Isso também significa que as instruções complexas de finalidade especial serão predominadas.
O processador x86 rastreia sua herança pelo menos até o processador Intel 8080 de 8 bits. Muitas peculiaridades no conjunto de instruções x86 são devidas à compatibilidade retroativa com o processador original (e com a variante Z-80 da Zilog).
O Microsoft Win32 usa o processador x86 em modo plano de 32 bits. Esta documentação se concentrará apenas no modo simples.
Registradores
A arquitetura x86 consiste nos seguintes registros inteiros sem privilégios.
eax |
Acumulador |
ebx |
Registro base |
ecx |
Registrador de contador |
edx |
Registro de dados – pode ser usado para acesso à porta de E/S e funções aritméticas |
esi |
Registrador de índice de origem |
edi |
Registrador de índice de destino |
ebp |
Registrador de ponteiro base |
esp |
Ponteiro de pilha |
Todos os registros inteiros são de 32 bits. No entanto, muitos deles têm subregistros de 16 bits ou 8 bits.
ax |
16 bits baixos de eax |
bx |
16 bits baixos de ebx |
cx |
16 bits baixos de ecx |
dx |
16 bits baixos de edx |
si |
16 bits baixos de esi |
di |
16 bits baixos de edi |
bp |
16 bits baixos de ebp |
sp |
16 bits baixos de esp |
al |
8 bits baixos de eax |
ah |
8 bits altos de ax |
bl |
8 bits baixos de ebx |
bh |
8 bits altos de bx |
cl |
8 bits baixos de ecx |
ch |
8 bits altos de cx |
dl |
8 bits baixos de edx |
dh |
8 bits altos de dx |
Operar em um subregistro afeta apenas o subregistro e nenhuma das partes fora do subregistro. Por exemplo, armazenar no registro ax deixa os 16 bits mais significativos do registro eax inalterados.
Ao usar o ? (Avaliar Expressão) comando, os registros devem ser prefixados com um sinal "at" (@). 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ão interpretados 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 registrador de sinalizadores é uma coleção de sinalizadores de um único bit. Muitas instruções alteram os sinalizadores para descrever o resultado da instrução. Esses sinalizadores podem então ser testados por instruções de salto condicional. Veja detalhes em Flags x86.
Convenções de chamada
A arquitetura x86 tem várias convenções de chamada diferentes. Felizmente, todos seguem as mesmas regras de preservação de registro e retorno de função:
As funções devem preservar todos os registradores, exceto eax, ecx e edx, que podem ser alterados em uma chamada de função, e esp, que deve obrigatoriamente ser atualizado de acordo com a convenção de chamada.
O registrador eax recebe valores de retorno de função se o resultado tiver 32 bits ou menos. Se o resultado for de 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++ nativa (também conhecida como thiscall)
Os parâmetros de função são passados na pilha, empurrados da direita para a esquerda, o ponteiro "este" é passado no registrador ecx e o receptor limpa a pilha.
COM (__stdcall para chamadas de método C++)
Os parâmetros de função são colocados na pilha, da direita para a esquerda, em seguida, o ponteiro "este" é colocado na pilha e a função é chamada. O computador chamado limpa a pilha.
__fastcall
Os dois primeiros argumentos DWORD ou menores são passados nos registradores ecx e edx. Os parâmetros restantes são passados na pilha, empurrados 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 receptor 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 registradores e sinalizadores
Aqui está um exemplo de exibição do registrador 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 no 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 registros de bit único e têm uma variedade de usos.
A tabela a seguir lista os sinalizadores x86:
Código de Bandeira | Nome da Bandeira | Valor | Sinalizador de status | Descrição |
---|---|---|---|---|
of | 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 desabilitadas – Interrupções habilitadas |
sf | Sinalizador de sinal | 0 1 | plng | Positivo (ou zero) – Negativo |
zf | Sinalizador de zero | 0 1 | nzzr | Nonzero – Zero |
af | Sinalizador de transporte auxiliar | 0 1 | naac | Sem transporte auxiliar – Transporte auxiliar |
pf | Sinalizador de paridade | 0 1 | pepo | Paridade ímpar - Paridade par |
cf | Sinalizador de transporte | 0 1 | nccy | Sem transporte - Com transporte |
tf | Sinalizador de interceptação | Se tf 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 em etapa única. Ele 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 registradores são exibidos como resultado de algum comando na janela Comando do Depurador, o status do sinalizador é exibido. No entanto, se você quiser alterar um sinalizador usando o comando r (Registros), você deve referir-se a ele pelo código de sinalizador .
Na janela Registros do WinDbg, o código do sinalizador é usado para exibir ou alterar sinalizadores. Não há suporte para o sinalizador de status.
Aqui está um exemplo. Na exibição do registrador anterior, o status do sinalizador ng aparece. Isso significa que o sinalizador de sinal está atualmente definido como 1. Para alterar isso, use o seguinte comando:
r sf=0
Isso define o indicador de sinal como zero. Se você fizer outra exibição de registro, o código de status ng não será exibido. Em vez disso, o código de status pl será exibido.
Os sinalizadores de Sinal, Zero e de Transporte são os mais usados.
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 assembler usa uma ou duas letras como abreviação 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 alto. |
NS |
SF=0 |
O resultado da última operação tem seu bit alto apagado. |
O |
OF=1 |
Quando tratada como uma operação de inteiro com sinal, a última operação causou um estouro positivo ou negativo. |
NÃO |
OF=0 |
Quando tratada como uma operação de inteiro com sinal, a última operação não causou um estouro positivo ou 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 tivesse subtraído um operando do outro. As condições a seguir podem ser usadas para checar o resultado de cmpvalor1, valor2.
Nome da condição | Sinalizadores | O significado após uma operação CMP. |
---|---|---|
E |
ZF=1 |
valor1 == valor2. |
NE |
ZF=0 |
valor1 != valor2. |
GE NL | SF=OF |
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. |
G NLE | ZF=0 e SF=OF |
valor1>valor2. Os valores são tratados como inteiros com sinal. |
L NGE | SF!=OF |
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. |
BE NA | CF=1 ou ZF=1 |
valor1<= valor2. Os valores são tratados como inteiros sem sinal. |
NBE A | 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. |
Normalmente, as condições são usadas para agir sobre o resultado de uma instrução cmp ou teste. Por exemplo
cmp eax, 5
jz equal
compara o registro 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 para que o salto seja feito.
Tipos de dados
byte: 8 bits
word: 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 de linguagem do assembly.
Notação | Significado |
---|---|
r, r1, r2... |
Registradores |
m |
Endereço de memória (consulte a seção Modos de Endereçamento seguinte para obter mais informações.) |
n |
Constante imediata |
r/m |
Registro ou memória |
r/#n |
Registrador ou constante imediata |
r/m/#n |
Registrador, memória ou constante imediata |
cc |
Um código de condição listado na seção Condições anteriores. |
T |
"B", "W" ou "D" (byte, word ou dword) |
accT |
Tamanho do acumulador T: al caso T = "B", ax caso T = "W", ou eax caso T = "D" |
Modos de endereçamento
Há vários modos de endereçamento diferentes, mas todos eles tomam o formulário T ptr [expr], em que T é algum tipo de dados (consulte a seção Tipos de Dados anterior) e expr é alguma expressão que envolve constantes e registros.
A notação para a maioria dos modos pode ser deduzida sem muita dificuldade. Por exemplo,
Pipeline
O Pentium é dual-issue, o que significa que ele pode executar até duas ações em um ciclo de clock. No entanto, as regras sobre quando ela é capaz de realizar duas ações ao mesmo tempo (conhecidas como emparelhamento de ) são muito complicadas.
Como o x86 é um processador CISC, você não precisa se preocupar com slots de atraso de salto.
Acesso sincronizado à memória
As instruções de carregamento, modificação e armazenamento podem receber um prefixo de bloqueio , que modifica a instrução da seguinte maneira:
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.
Durante a emissão da instrução, a CPU terá acesso exclusivo ao barramento. Isso garante a atomicidade da operação de carregamento/modificação/armazenamento.
A instrução xchg obedece automaticamente às regras anteriores sempre que troca um valor com memória.
Todas as outras instruções têm padrão de não bloqueio.
Previsão de salto
Prevê-se que saltos incondicionais sejam tomados.
Prevê-se que os saltos condicionais sejam tomados ou não, dependendo se foram feitos na última vez em 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 executado ou não da última vez, ela prevê que saltos condicionais para trás são feitos e saltos condicionais para frente não são feitos.
Alinhamento
O processador x86 corrigirá automaticamente o acesso de memória desalinhado, com leve perda de desempenho. Nenhuma exceção é acionada.
Um acesso à memória será considerado alinhado se o endereço for um múltiplo inteiro do tamanho do objeto. Por exemplo, todos os acessos BYTE são alinhados (tudo é um múltiplo inteiro de 1), os acessos do WORD a endereços pares são alinhados e os endereços DWORD devem ser um múltiplo de 4 para serem alinhados.
O prefixo lock (bloqueio) não deve ser usado para acessos de memória não alinhados.