# Atributos e prefixos

{% hint style="info" %}
Os dois tópicos [atributos](https://mentebinaria.gitbook.io/assembly/aprofundando-em-assembly/atributos) e [prefixos](https://mentebinaria.gitbook.io/assembly/aprofundando-em-assembly/prefixos) já explicaram esse assunto antes no livro, mas do ponto de vista do Assembly. Aqui será abordado o assunto mais voltado ao código de máquina e **com mais informações**.
{% endhint %}

Na arquitetura x86 as instruções contém o que é conhecido como "atributos", onde existe um determinado valor padrão para o atributo e é possível modificá-lo com um prefixo.

Como pode ser observado na ilustração exibida no tópico [Formato das instruções](https://mentebinaria.gitbook.io/assembly/apendices/codigo-de-maquina/formato-das-instrucoes), prefixos são bytes que podem (são opcionais na grande maioria das instruções) ser adicionados antes do *opcode* de uma instrução.

Uma instrução pode ter mais de um prefixo (até 4 legados). O prefixo REX existente somente em x86-64 precisa obrigatoriamente vir antes do *opcode* e depois dos demais prefixos. Mas exceto por ele, todos os outros prefixos podem ser adicionados em qualquer ordem que não fará diferença na instrução. Por exemplo a instrução `mov eax, [ebx]` em modo de 16-bit seria compilada como na imagem:

![Print do x86-visualizer.](https://2466397120-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Lif0YmrEB3G3dh1VxSC%2Fuploads%2FpwFFlB0ZmrzeLwi9HD69%2FCaptura_de_tela_de_2022-04-03_15-02-52.png?alt=media\&token=6e2022d9-c54f-4e43-831d-0ea7d498987a)

Onde `67 66 8B 03` e `66 67 8B 03` dariam na mesma, o processador executaria as duas instruções de maneira totalmente equivalente.

### Atributo address-size

{% content-ref url="../../a-base/modos-de-operacao" %}
[modos-de-operacao](https://mentebinaria.gitbook.io/assembly/a-base/modos-de-operacao)
{% endcontent-ref %}

Em modo de 16-bit e modo de 32-bit, desde o processador i386, é possível usar tanto [endereçamento](https://mentebinaria.gitbook.io/assembly/a-base/enderecamento) de 16-bit como de 32-bit. No exemplo anterior a instrução `mov eax, [ebx]` foi compilada no modo de 16-bit, porém usando endereçamento e operando de 32-bit.

O atributo **address-size** determina o modo de endereçamento da instrução. Em modo 16-bit o atributo **address-size** por padrão é de 16-bit. E em modo de 32-bit o atributo é por padrão de 32-bit. Já em modo de 64-bit o endereçamento padrão é 64-bit.

O prefixo conhecido como **address-size override**, cujo o byte é `67`, serve para usar o modo de endereçamento não-padrão. Ou seja, ao usar o prefixo se estiver em modo de 16-bit o endereçamento será de 32-bit. E se estiver em modo de 32-bit o endereçamento será de 16-bit. Já se estiver em modo de 64-bit o endereçamento será de 32-bit.

Por isso o prefixo é adicionado em 16-bit para instruções que usam endereçamento de 32-bit. O mesmo também é feito na situação oposta:

![Print do x86-visualizer.](https://2466397120-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Lif0YmrEB3G3dh1VxSC%2Fuploads%2FeWBkT2Oyn3FVeGXsvO9X%2FCaptura_de_tela_de_2022-04-03_15-12-37.png?alt=media\&token=817b36de-9f59-4a54-9ae3-667501e1774e)

### Atributo operand-size

Assim como é possível alternar entre endereçamento de 16-bit e 32-bit nos modos de 16-bit (*real mode*) e 32-bit (*protected mode*). Também é possível alternar o tamanho dos operandos usados em operações.

Assim como também foi demonstrado no primeiro exemplo a instrução de 16-bit fez uma operação com um valor de 32-bit (o registrador EAX teve seu valor alterado para os 4 bytes presentes no endereço `[EBX]`).

E para isso foi usado o prefixo **operand-size override**, o byte `66`. E na mesma lógica do `address-size override` ele alterna o tamanho do operando para o seu tamanho não-padrão. Onde em modos de 32-bit e 64-bit o tamanho padrão de operando é de 32-bit, e em modo de 16-bit o tamanho padrão é de 16-bit.

{% hint style="info" %}
Vale citar um erro que eu vi um senhor cometer uma vez: Ele acreditava que em modo de 32-bit era possível usar registradores de 64-bit e endereçamento de 64-bit. Bem, isso está **errado** como você pode notar pela explicação acima.\
\
Em modo de 16-bit é possível usar registradores e endereçamento de 32-bit alterando os atributos **address-size** e **operand-size**. Mas o mesmo não se aplica para 64-bit porque o uso de operandos de 64-bit é feito por meio do prefixo REX, que **só existe em modo de 64-bit**. E em modo de 32-bit só é possível alternar entre endereçamento de 32-bit e 16-bit usando o prefixo `67`.
{% endhint %}

### Atributo segment

{% content-ref url="../../aprofundando-em-assembly/registradores-de-segmento" %}
[registradores-de-segmento](https://mentebinaria.gitbook.io/assembly/aprofundando-em-assembly/registradores-de-segmento)
{% endcontent-ref %}

Qual segmento de memória será acessado pela instrução é definido em um atributo. O segmento padrão da instrução é definido de acordo com qual registrador foi usado como **base**:

<table data-header-hidden><thead><tr><th width="269.31598008148484">Registrador base</th><th>Segmento</th></tr></thead><tbody><tr><td>Registrador base</td><td>Segmento</td></tr><tr><td>RIP</td><td>CS</td></tr><tr><td>SP/ESP/RSP</td><td>SS</td></tr><tr><td>BP/EBP/RBP</td><td>SS</td></tr><tr><td>Qualquer outro registrador</td><td>DS</td></tr></tbody></table>

Para alterar o atributo de segmento para um outro segmento de memória é usado um prefixo distinto por segmento:&#x20;

| Segmento | Byte do prefixo |
| -------- | --------------- |
| CS       | `2E`            |
| DS       | `3E`            |
| ES       | `26`            |
| FS       | `64`            |
| GS       | `65`            |
| SS       | `36`            |

Exemplo:

![Print do x86-visualizer.](https://2466397120-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-Lif0YmrEB3G3dh1VxSC%2Fuploads%2F1kQVyvDt0t35KU36Wspi%2FCaptura_de_tela_de_2022-04-03_15-39-33.png?alt=media\&token=93f89a7b-6269-482a-89cc-b1dab5938cc9)

### Prefixos REP/REPE e REPNE

As instruções de movimentação de dados (`movsb`, `movsw`, `movsd` e `movsq`) bem como outras como `scasb`, `lodsb`, `in`, `out` etc. podem ser executadas em *loop* usando o prefixo REPE ou REPNE.

No caso das instruções `MOVS*` é possível usar o prefixo REPE, que nesse caso também pode ser chamado só de `REP` mas os dois mnemônicos produzem o mesmo byte (`F3`).

Ao usar esse prefixo na instrução, assim como foi [explicado anteriormente](https://mentebinaria.gitbook.io/assembly/apendices/formato-das-instrucoes#cisc), ela é executada em *loop* enquanto o valor de ECX não for zero. E a cada iteração do *loop* o valor do registrador é decrementado. Na verdade se CX ou ECX será usado isso é definido pelo atributo **address-size** e pode ser alternado com o prefixo **address-size override**. Por exemplo na sintaxe do NASM ficaria assim:

```nasm
bits 16
; ...
a32 rep movsb
```

Assim ECX seria usado ao invés de CX. Onde `a32` é uma palavra-chave usada no NASM para denotar que o **address-size** daquela instrução deve ser de 32-bit. Se usado em modo de 16-bit ele adiciona o prefixo `67`, mas se estiver em modo de 32-bit então nenhum prefixo será adicionado tendo em vista que o **address-size** padrão já é de 32-bit.

{% hint style="info" %}
Sim, também existe `a16` e `a64`. Como também existe `o16`, `o32` e `o64` para denotar o tamanho do **operand-size**. Mas detalhe que `a64` e `o64` denotam o uso do prefixo REX que só existe em modo de 64-bit.
{% endhint %}

Nas instruções `CMPS*` e `SCAS*` o prefixo `REPE` (ou `REPZ`) repete a instrução enquanto a [*zero flag*](https://mentebinaria.gitbook.io/assembly/aprofundando-em-assembly/flags-do-processador#status-flags) estiver setada. Já `REPNE` (ou `REPNZ`) repete enquanto a *zero flag* estiver zerada.

### Prefixo LOCK

O prefixo LOCK (byte `F0`) é usado para fazer operações de escrita atômica em um determinado endereço de memória. Ou seja o prefixo garante que outros núcleos do processador não escrevam naquele endereço ao mesmo tempo, exigindo que essa operação finalize antes de outra que escreva no mesmo endereço seja executada.

Esse prefixo só pode ser usado nas seguintes instruções: `ADD`, `ADC`, `AND`, `BTC`, `BTR`, `BTS`, `CMPXCHG`, `CMPXCH8B`, `CMPXCHG16B`, `DEC`, `INC`, `NEG`, `NOT`, `OR`, `SBB`, `SUB`, `XOR`, `XADD` e `XCHG`. Isso, obviamente, quando o operando destino (o que está sendo escrito) é um operando na memória.

Na sintaxe do NASM o prefixo pode ser usado simplesmente com a palavra-chave `lock` antes da instrução. Como em:

```nasm
lock add [ebx], 4
```

### Prefixos de branch hint

É possível manualmente você instruir para o sistema de ***branch prediction*** do processador quais saltos condicionais provavelmente irão ocorrer ou não usando dois prefixos:

* `2E` - Instrui para o processador que o pulo provavelmente **não** ocorrerá.
* `3E` - Instrui para o processador que provavelmente o pulo ocorrerá.

Na sintaxe do NASM esses prefixos podem ser adicionados em saltos condicionais com as palavra-chaves `false` e `true` respectivamente. Como em:

```
false jz my_label
```

Todavia esses prefixos são obsoletos e até mesmo ignorados por processadores mais novos, tendo em vista que processadores mais modernos usam um algoritmo para determinar qual salto é mais provável de ser tomado ou não. E também saltos para trás são considerados tomados e saltos para frente como não tomados. Isso por causa da forma como compiladores geram código para *loops* e condicionais.

Em versões mais modernas do NASM ele simplesmente irá ignorar o `false` ou `true` e não adicionará prefixo algum.
