Co

Condicionais em x86-64 Assembly

4 exercícios

Sobre Condicionais

RFLAGs

Existe um registrador especial chamado rflags. Seus bits funcionam como flags para várias condições.

Algumas delas estão listadas abaixo:

nome símbolo bit
carry CF 0
zero ZF 6
sign SF 7
overflow OF 11

Para uma lista completa, consulte o Manual da Intel.

Instruções de comparação

As flags em rflags não são modificadas diretamente. Em vez disso, elas são definidas por muitas instruções diferentes.

Por exemplo, a ZF é definida por muitas operações aritméticas ou bit a bit quando o resultado é zero.

Uma das instruções mais comuns usadas para testar condições é cmp. Ela recebe dois operandos e atualiza as flags, mas não modifica seus operandos.

A instrução CMP

A instrução cmp subtrai o segundo operando do primeiro e define as flags de acordo com o resultado.

Se A é o primeiro operando e B, o segundo:

flag definida quando
CF A < B (sem sinal)
ZF A == B
SF A < B (com sinal, sem overflow)
OF overflow na subtração com sinal

Ramificação

Por padrão, o código em x86-64 é executado sequencialmente de cima para baixo.

Mas há muitas situações em que é necessário mudar esse comportamento. Por exemplo, para executar um conjunto diferente de instruções em resposta a uma condição.

Em linguagens de nível mais alto, isso geralmente é feito com abstrações como as condicionais if...else. No entanto, elas não existem em x86-64.

Em vez disso, o x86-64 fornece instruções que, na prática, transferem a execução para outro ponto do código. Isso se chama branching.

Note

Já vimos duas dessas instruções: call e ret.

Quando uma função é chamada, a execução é transferida de quem chamou para a função chamada. E, no retorno, a execução é transferida de volta para quem chamou.

Se nenhum ret for encontrado, a execução passa direto de uma função para a próxima. Isso às vezes pode ser usado para otimizar o fluxo do código.

Salto incondicional

A instrução jmp transfere incondicionalmente a execução do programa para outro ponto do código. Seu único operando é um rótulo que contém o endereço do ponto em que a execução vai continuar.

Considere, por exemplo, a seguinte função:

fn:
    mov rax, 5
    jmp end

    add rax, 10
end:
    ret

Quando fn é chamada, a execução começa em mov rax, 5. Isso define o valor em rax como 5 naquele ponto.

A próxima instrução é jmp end, que transfere a execução para o rótulo end.

Depois de end, a próxima instrução é ret, que transfere a execução de volta para a função que chamou.

Repare que, como add rax, 10 está depois de jmp end e antes de end, ela nunca é executada. O valor de rax quando fn retorna é 5.

Salto condicional

A família de instruções jcc transfere a execução do programa para outro ponto apenas se uma condição específica for satisfeita. Caso contrário, a execução continua sequencialmente.

Cada condição corresponde a uma ou mais flags em rflags. Algumas variantes de jcc testam se uma flag está definida, outras testam se ela está limpa.

O cc em jcc não é literal, mas se refere ao sufixo específico associado à flag testada.

Existem muitos sufixos, e muitos deles testam a mesma condição que outro. Alguns se referem diretamente a uma flag, de modo que a instrução salta para um rótulo se a flag específica estiver definida:

sufixo salta se
z ZF == 1
c CF == 1
s SF == 1
o OF == 1

Muitos outros são escolhidos para se referir ao seu significado em uma instrução cmp. Por exemplo:

instrução sufixo salta se
cmp A, B e A == B
cmp A, B l A < B (com sinal)
cmp A, B b A < B (sem sinal)
cmp A, B g A > B (com sinal)
cmp A, B a A > B (sem sinal)

É possível adicionar e depois de l, b, g ou a para incluir a igualdade na condição:

cmp rcx, r8
jge two      ; this jumps to 'two' if rcx is greater than, or equal to, r8 in a signed comparison
jbe two      ; this jumps to 'two' if rcx is lesser than, or equal to, r8 in an unsigned comparison

Para todos os sufixos, existem variantes que verificam o comportamento oposto. Elas têm a mesma sintaxe, mas com um n antes do sufixo.

Por exemplo, jnz salta quando ZF não está definida. Do mesmo modo, jnae salta quando A não é >= B (A e B interpretados como inteiros sem sinal).

Note

Alguns sufixos são apelidos para as mesmas condições. Por exemplo, jz (sufixo z, para ZF) e je (sufixo e, para igual) saltam ambos quando ZF está definida. Isso acontece porque, com cmp, a ZF é definida quando a subtração resulta em zero, o que corresponde a os dois operandos serem iguais.

Outros sufixos, porém, testam uma combinação de flags e não podem ser substituídos diretamente por um sufixo de flag única.

Prefira o sufixo que descreve melhor a semântica da sua comparação.

Rótulos locais

Os rótulos são visíveis em todo o arquivo-fonte, eles não são locais a uma função. Por isso, é impossível reutilizar o nome de um rótulo.

Para imitar o comportamento de um rótulo local, o NASM tem uma notação especial para um rótulo declarado com um ponto (.) antes dele. Essa notação define um rótulo que inclui implicitamente o nome do rótulo anterior sem ponto:

section .text
fn1:
    ...
.example: ; this is fn1.example
    ...
    ret

fn2:
    ...
.example: ; this is fn2.example
    ...
    ret

Ainda é possível saltar para esse rótulo de qualquer lugar do código usando o nome completo do rótulo, por exemplo, jmp fn1.example.

No entanto, um salto que usa a parte do rótulo a partir do ponto será feito para o rótulo dentro da função mais externa. Por exemplo, .example se comporta como se fosse local à função:

section .text
fn1:
    ...
.example:
    ...
    jmp .example ; this jumps to fn1.example

fn2:
    ...
.example:
    ...
    jmp .example ; this jumps to fn2.example

Repare que um rótulo sem ponto dentro de uma função, na prática, define outra função:

section .text
fn1:
    ...
.example: ; this is 'fn1.example'
    ...
non_dotted:
    ...
.example: ; this is 'non_dotted.example'
    ...
    ret
Editar via GitHub O link abre em uma nova janela ou aba

Aprenda Condicionais