Co

Condicionais em x86-64 Assembly

4 exercícios

Sobre Condicionais

RFLAGs

Existe um registo especial chamado rflags. Os seus bits funcionam como flags para várias condições.

Alguns deles estão listados abaixo:

nome símbolo bit
transporte CF 0
zero ZF 6
sinal SF 7
transbordo OF 11

Para uma lista completa, consulta o Manual da Intel.

Instruções de comparação

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

Por exemplo, 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. Recebe dois operandos e atualiza as flags, mas não modifica os seus operandos.

Instrução CMP

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

Se A for o primeiro operando e B o segundo:

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

Ramificação

Por predefinição, o código em x86-64 executa sequencialmente de cima para baixo.

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

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

Em vez disso, o x86-64 fornece instruções que transferem efetivamente a execução para outro local do código. A isto chama-se branching.

Note

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

Quando uma função é chamada, a execução é transferida do chamador para a função chamada. E, ao retornar, a execução é transferida de volta para o chamador.

Se não for encontrado nenhum ret, a execução passa de uma função para a seguinte. Isto pode, por vezes, 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. O seu único operando é um rótulo que contém o endereço do ponto onde a execução continuará.

Considera, 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. Isto define o valor em rax como 5 nesse ponto.

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

Depois de end, a instrução seguinte é ret, que transfere a execução de volta para a função chamadora.

Repara que, como add rax, 10 está localizada depois de jmp end e antes de end, 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 está limpa.

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

Existem muitos sufixos e muitos deles testam a mesma condição que outro. Alguns deles referem-se 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 de forma a referir o seu significado numa 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. Têm a mesma sintaxe, mas com um n antes do sufixo.

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

Note

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

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

Prefere o sufixo que descreve melhor a semântica da tua comparação.

Rótulos locais

Os rótulos são visíveis em todo o ficheiro fonte; 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. Esta 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 este rótulo a partir de qualquer ponto 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 superior. Por exemplo, .example comporta-se 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

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

section .text
fn1:
    ...
.example: ; this is 'fn1.example'
    ...
non_dotted:
    ...
.example: ; this is 'non_dotted.example'
    ...
    ret
Editar via GitHub A ligação abre numa nova janela ou separador

Aprende Condicionais