Arquitetura x86
O processador Intel x86 usa uma arquitetura 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 instruções complexas de finalidade especial serão predominados.
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 devido à compatibilidade com versões anteriores com esse processador (e com sua variante Zilog Z-80).
O Microsoft Win32 usa o processador x86 no modo simples de 32 bits. Esta documentação se concentrará apenas no modo simples.
Registros
A arquitetura x86 consiste nos seguintes registros inteiros sem privilégios.
Eax |
Acumulador |
Ebx |
Registro base |
ecx |
Registro de contador |
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 registros inteiros são de 32 bits. No entanto, muitos deles têm subregistros de 16 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 |
Baixos 16 bits 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 de ax deixa os 16 bits altos 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á 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 condicional. Consulte Sinalizadores x86 para obter detalhes.
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 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 receberá valores retornados da função se o resultado for 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 por push da direita para a esquerda e o computador chamado limpa a pilha.
Chamada de método C++ nativo (também conhecida como thiscall)
Os parâmetros de função são passados na pilha, enviados por push da direita para a esquerda, o ponteiro "this" é passado no registro ecx e o computador chamado limpa a pilha.
COM (__stdcall para chamadas de método C++)
Os parâmetros de função são passados na pilha, enviados por push 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 por push 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á uma exibição de registro de depurador de exemplo:
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. Esses são registros de bit único e têm uma variedade de usos.
A tabela a seguir lista os sinalizadores x86:
Código de sinalizador | 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 desabilitadas – Interrupções habilitadas |
sf | Sinalizador de Sinal | 0 1 | plng | Positivo (ou zero) – Negativo |
Zf | Sinalizador Zero | 0 1 | nzzr | Diferente de zero - Zero |
af | Sinalizador auxiliar de transporte | 0 1 | naac | Sem transporte auxiliar - Transporte auxiliar |
pf | Sinalizador de paridade | 0 1 | Pepo | Paridade estranha - Paridade mesmo |
cf | Sinalizador de Transporte | 0 1 | nccy | Sem transporte - Transporte |
Tf | Sinalizador de interceptação | 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 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 Esse é 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 sinalizador status exibido. No entanto, se você quiser alterar um sinalizador usando o comando r (Registros), consulte-o pelo código de sinalizador.
Na janela Registros do WinDbg, o código de sinalizador é usado para exibir ou alterar sinalizadores. Não há suporte para o sinalizador status.
Veja um exemplo. Na exibição de registro anterior, o sinalizador status ng é exibido. 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 sinalizador de sinal como zero. Se você fizer outra exibição de registro, o código ng status não será exibido. Em vez disso, o código pl status será exibido.
O Sinalizador de Sinal, o Sinalizador Zero e o Sinalizador de Transporte são os sinalizadores mais comumente 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 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 | Flags | 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 claro. |
O |
OF=1 |
Quando tratada como uma operação de inteiro com sinal, a última operação causou um estouro ou um subfluxo. |
Não |
OF=0 |
Quando tratada como operação de inteiro com sinal, a última operação não causou um estouro ou um fluxo inferior. |
As condições também podem ser usadas para comparar dois valores. A instrução cmp compara seus dois operandos e define sinalizadores como se um operando subtraído do outro. As condições a seguir podem ser usadas para marcar o resultado de cmpvalue1, value2.
Nome da condição | Flags | Significado após uma operação CMP. |
---|---|---|
E |
ZF=1 |
value1 == value2. |
NE |
ZF=0 |
value1 != value2. |
GE NL | SF=OF |
value1>= value2. Os valores são tratados como inteiros com sinal. |
LE NG | ZF=1 ou SF!=OF |
value1<= value2. Os valores são tratados como inteiros com sinal. |
G NLE | ZF=0 e SF=OF |
value1>value2. Os valores são tratados como inteiros com sinal. |
L NGE | SF!=OF |
value1<value2. Os valores são tratados como inteiros com sinal. |
AE NB | CF=0 |
value1>= value2. Os valores são tratados como inteiros sem sinal. |
BE NA | CF=1 ou ZF=1 |
value1<= value2. Os valores são tratados como inteiros sem sinal. |
Um NBE | CF=0 e ZF=0 |
value1>value2. Os valores são tratados como inteiros sem sinal. |
B NAE | CF=1 |
value1<value2. Os valores são tratados como inteiros sem sinal. |
Normalmente, as condições são usadas para agir no resultado de um cmp ou instrução de 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 obtido.
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.
Notation | Significado |
---|---|
r, r1, r2... |
Registros |
m |
Endereço de memória (consulte a seção Modos de Endereçamento bem-sucedidos para obter mais informações.) |
#n |
Constante imediata |
r/m |
Registrar ou memória |
r/#n |
Registrar ou constante imediata |
r/m/#n |
Registrar, 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, word ou dword) |
accT |
Acumulador de tamanho T : al se T = "B", ax if T = "W" ou eax if T = "D" |
Modos de endereçamento
Há vários modos de endereçamento diferentes, mas todos eles assumem o formato T ptr [expr], em que T é algum tipo de dados (consulte a seção Tipos de Dados anterior) e expr é uma expressão que envolve constantes e registros.
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 registro esi , adicione a ele oito vezes o valor do registro edx , adicione três e acesse o byte no endereço resultante".
Pipelining
O Pentium é um problema duplo, o que significa que ele pode executar até duas ações em um tique de relógio. No entanto, as regras sobre quando ele é capaz de executar 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 sincronizado à memória
As instruções carregar, modificar e armazenar 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.
Ao emitir a 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 padrão para não bloqueio.
Previsão de Salto
Prevê-se que saltos incondicionals 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 feito ou não na última vez em que foi executado, ele prevê saltos condicionais para trás conforme feito e avanço de saltos condicionais como não realizados.
Alinhamento
O processador x86 corrigirá automaticamente o acesso de memória não atribuído, em uma penalidade de desempenho. Nenhuma exceção é gerada.
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 par sã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 não atribuídos.
Comentários
https://aka.ms/ContentUserFeedback.
Em breve: Ao longo de 2024, eliminaremos os problemas do GitHub como o mecanismo de comentários para conteúdo e o substituiremos por um novo sistema de comentários. Para obter mais informações, consulteEnviar e exibir comentários de