
Se você remover a primeira palavra da string 'olá mundo', qual deve ser o resultado? Essa é a história de como descobrimos que a resposta pode ser a sua senha de root!
Introdução
Todos os CPUs x86-64 possuem um conjunto de registradores de vetor de 128 bits chamados de registradores XMM. Nunca é demais ter bits suficientes, então CPUs recentes expandiram a largura desses registradores para 256 bits e até mesmo 512 bits.
Os registradores estendidos de 256 bits são chamados de YMM e os registradores de 512 bits são chamados de ZMM.
Esses grandes registradores são úteis em muitas situações, não apenas para cálculos numéricos! Eles são até mesmo usados por funções da biblioteca C padrão, como strcmp, memcpy, strlen e assim por diante.
Vamos dar uma olhada em um exemplo. Aqui estão as primeiras instruções do strlen otimizado para AVX2 do glibc:
(gdb) x/20i __strlen_avx2 ... <__strlen_avx2+9>: vpxor xmm0,xmm0,xmm0 ... <__strlen_avx2+29>: vpcmpeqb ymm1,ymm0,YMMWORD PTR [rdi] <__strlen_avx2+33>: vpmovmskb eax,ymm1 ... <__strlen_avx2+41>: tzcnt eax,eax <__strlen_avx2+45>: vzeroupper <__strlen_avx2+48>: ret
A rotina completa é complicada e lida com muitos casos, mas vamos analisar esse caso simples. Tenha paciência, prometo que há um ponto!
O primeiro passo é inicializar ymm0 com zero, o que é feito simplesmente xorando xmm0 consigo mesmo.
VPXOR xmm0, xmm0, xmm0 > vpxor xmm0, xmm0, xmm0 vpcmpeqb ymm1, ymm0, [rdi] vpmovmskb eax, ymm1 tzcnt eax, eax vzeroupper
Aqui, rdi contém um ponteiro para nossa string, então vpcmpeqb verificará quais bytes em ymm0 correspondem à nossa string e armazenará o resultado em ymm1.
Como já definimos ymm0 para todos os bytes zero, apenas bytes nulos corresponderão.
vpcmpeqb ymm1, ymm0, rdi vpxor xmm0, xmm0, xmm0 > vpcmpeqb ymm1, ymm0, [rdi] vpmovmskb eax, ymm1 tzcnt eax, eax vzeroupper
Agora podemos extrair o resultado para um registrador de propósito geral como eax com vpmovmskb.
Qualquer byte nulo criará um bit 1 e qualquer outro valor criará um bit 0.
vpmovmskb eax, ymm1 vpxor xmm0, xmm0, xmm0 vpcmpeqb ymm1, ymm0, [rdi] > vpmovmskb eax, ymm1 tzcnt eax, eax vzeroupper
Encontrar o primeiro byte nulo agora é apenas uma questão de contar o número de bits zero à direita.
Essa é uma operação comum o suficiente para ter uma instrução para isso - tzcnt (Trailing Zero Count).
tzcnt eax, eax vpxor xmm0, xmm0, xmm0 vpcmpeqb ymm1, ymm0, [rdi] vpmovmskb eax, ymm1 > tzcnt eax, eax vzeroupper
Agora temos a posição do primeiro byte nulo, em apenas quatro instruções de máquina!
Você provavelmente pode imaginar quantas vezes o strlen está sendo executado em seu sistema agora, mas basta dizer que bits e bytes estão fluindo para esses registradores de vetor de todos os lugares do seu sistema constantemente.
Zerando Registradores Você pode ter percebido que eu perdi uma instrução, e essa é a vzeroupper. vzeroupper vpxor xmm0, xmm0, xmm0 vpcmpeqb ymm1, ymm0, [rdi] vpmovmskb eax, ymm1 tzcnt eax, eax > vzeroupper Você acertou, vzeroupper vai zerar os bits superiores dos registradores de vetor. A razão pela qual fazemos isso é porque, se misturarmos registradores XMM e YMM, os registradores XMM são automaticamente promovidos para a largura total. É um pouco como a promoção de inteiros em C. Isso funciona bem, mas processadores superscalares precisam rastrear dependências para saber quais operações podem ser paralelizadas. Essa promoção adiciona uma dependência nesses bits superiores, e isso causa atrasos desnecessários enquanto o processador espera por resultados que ele realmente não precisa. Esses atrasos são o que o glibc está tentando evitar com vzeroupper. Agora, quaisquer resultados futuros não dependerão desses bits, então evitamos com segurança esse gargalo!
O Arquivo de Registradores de Vetor Agora que sabemos o que vzeroupper faz, como ele faz isso? Seu processador não possui um único local físico onde cada registrador reside, ele possui o que é chamado de Arquivo de Registradores e uma Tabela de Alocação de Registradores. Isso é um pouco como gerenciar o heap com malloc e free, se você pensar em cada registrador como um ponteiro. A RAT acompanha qual espaço no arquivo de registradores está atribuído a qual registrador. Na verdade, quando você zera um registrador XMM, o processador não armazena esses bits em nenhum lugar - ele simplesmente define uma sinalização chamada de bit z na RAT. Essa sinalização pode ser aplicada às partes superiores e inferiores dos registradores YMM independentemente, então vzeroupper pode simplesmente definir o bit z e depois liberar quaisquer recursos atribuídos a ele no arquivo de registradores. Uma tabela de alocação de registradores (esquerda) e um arquivo de registradores físicos (direita).
Confira os últimos vídeos publicados no canal