O que significa “rep ret”?

Eu estava testando algum código no Visual Studio 2008 e notei o security_cookie . Eu posso entender o ponto, mas não entendo qual é o objective dessa instrução.

  rep ret /* REP to avoid AMD branch prediction penalty */ 

Claro que eu posso entender o comentário 🙂 mas o que é este exaclty de prefixo fazendo em contexto com o ret e o que acontece se ecx for! = 0? Aparentemente, a contagem de loop do ecx é ignorada quando eu depuro, o que é esperado.

O código onde encontrei isso aqui (injetado pelo compilador para segurança):

 void __declspec(naked) __fastcall __security_check_cookie(UINT_PTR cookie) { /* x86 version written in asm to preserve all regs */ __asm { cmp ecx, __security_cookie jne failure rep ret /* REP to avoid AMD branch prediction penalty */ failure: jmp __report_gsfailure } } 

Existe um blog inteiro com o nome desta instrução. E o primeiro post descreve a razão por trás disso: http://repzret.org/p/repzret/

Basicamente, havia um problema no preditor da ramificação da AMD quando um ret byte imediatamente seguia um salto condicional como no código que você citou (e algumas outras situações), e a solução alternativa era adicionar o prefixo rep , que é ignorado por CPU, mas corrige a penalidade do preditor.

Aparentemente, alguns preditores de ramificação dos processadores AMD se comportam mal quando o alvo de uma ramificação ou a queda é uma instrução ret , e adicionar o prefixo rep evita isso.

Quanto ao significado de rep ret , não há menção a essa sequência de instruções na Referência do conjunto de instruções da Intel , e a documentação do rep não está sendo muito útil:

O comportamento do prefixo REP é indefinido quando usado com instruções sem string.

Isso significa que pelo menos o rep não precisa se comportar de maneira repetitiva.

Agora, a partir da referência do conjunto de instruções AMD (1.2.6 Repetir Prefixos):

Os prefixos só devem ser usados ​​com essas instruções de string.

Em geral, os prefixos de repetição só devem ser usados ​​nas instruções de string listadas nas tabelas 1-6, 1-7 e 1-8 acima [que não contêm ret].

Assim, parece realmente um comportamento indefinido, mas pode-se supor que, na prática, os processadores ignoram os prefixos de rep em instruções de ret .

Como a resposta de Trillian aponta, o AMD K8 e o K10 têm um problema com a previsão de ramificação quando ret é um alvo de ramificação ou segue um ramo condicional.

O guia de otimização da AMD para K10 (Barcelona) recomenda 3-byte ret 0 nesses casos, o que gera zero bytes da pilha, além de retornar. Essa versão é significativamente pior do que a da Intel. Ironicamente, também é pior do que rep ret nos processadores AMD posteriores (Bulldozer e em diante). Então, é bom que ninguém mude para usar o ret 0 base na atualização do guia de otimização da Família 10 da AMD.


Os manuais do processador avisam que futuros processadores poderiam diferentemente interpretar uma combinação de um prefixo e uma instrução que ele não modifica. Isso é verdade em teoria, mas ninguém vai fazer uma CPU que não possa rodar muitos binários existentes.

O gcc ainda usa rep ret por default (sem -mtune=intel , ou -march=haswell ou algo assim). Então, a maioria dos binários do Linux tem um repz ret em algum lugar.

O gcc provavelmente parará de usar o rep ret em alguns anos, uma vez que o K10 está completamente obsoleto. Após outros 5 ou 10 anos, quase todos os binários serão construídos com um gcc mais novo que isso. Mais 15 anos depois disso, um fabricante de CPU pode pensar em redirect a sequência de bytes f3 c3 como (parte de) uma instrução diferente.

Ainda haverá binários de código-fonte legado usando o rep ret que não têm compilações mais recentes disponíveis, e que alguém precisa continuar executando, no entanto. Portanto, qualquer que seja o novo recurso f3 c3 != rep ret ele precisa ser desabilitado (por exemplo, com uma configuração de BIOS), e essa configuração realmente altera o comportamento do decodificador de instruções para reconhecer f3 c3 como rep ret . Se essa compatibilidade com versões anteriores de binários legados não for possível (porque não pode ser feita com eficiência de energia em termos de energia e transistores), IDK que tipo de período de tempo você estaria observando. Muito mais de 15 anos, a menos que fosse uma CPU para apenas uma parte do mercado.

Então, é seguro usar o rep ret , porque todo mundo já está fazendo isso. Usar ret 0 é uma má ideia. No novo código, ainda é uma boa ideia usar o rep ret Por mais alguns anos. Provavelmente não existem muitos processadores AMD PhenomII por aí, mas eles são lentos o suficiente sem erros de endereço de retorno extra ou sem o problema.


O custo é bem pequeno. Ele não ocupa nenhum espaço extra na maioria dos casos, porque geralmente é seguido por nop padding de qualquer maneira. No entanto, nos casos em que isso resulta em preenchimento extra, será o pior caso em que 15B de preenchimento é necessário para atingir o próximo limite de 16B. O gcc pode apenas alinhar por 8B nesse caso. (com .p2align 4,,10; para alinhar a 16B se levar 10 ou menos nop bytes, então um .p2align 3 para alinhar sempre a 8B. Use gcc -S -o- para produzir uma saída asm para stdout para ver quando faz isso.)

Então, se nós estimamos que um em cada 16 rep ret termina criando um preenchimento extra onde um ret teria apenas atingido o alinhamento desejado, e que o preenchimento extra vai para um limite de 8B, isso significa que cada rep tem um custo médio de 8 * 1 / 16 = meio byte.

rep ret não é usado com freqüência suficiente para adicionar muito de qualquer coisa. Por exemplo, o firefox com todas as bibliotecas que ele mapeou tem apenas ~ 9k instâncias de rep ret . Então, isso é cerca de 4k bytes, em muitos arquivos. (E menos RAM que isso, já que muitas dessas funções em bibliotecas dinâmicas nunca são chamadas.)

 # disassemble every shared object mapped by a process. ffproc=/proc/$(pgrep firefox)/ objdump -d "$ffproc/exe" $(sudo ls -l "$ffproc"/map_files/ | awk '/\.so/ {print $NF}' | sort -u) | grep 'repz ret' -c objdump: '(deleted)': No such file # I forgot to restart firefox after the libexpat security update 9649 

Isso conta o rep ret em todas as funções em todas as bibliotecas que o firefox mapeou, não apenas as funções que ele chama. Isso é um pouco relevante, porque a menor densidade de código nas funções significa que suas chamadas estão espalhadas por mais páginas de memory. O ITLB e o L2-TLB possuem apenas um número limitado de inputs. A densidade local é importante para o L1I $ (e o cache uop da Intel). De qualquer forma, o rep ret tem um impacto muito pequeno.

Demorei um minuto para pensar em uma razão que /proc//map_files/ não é acessível para o proprietário do processo, mas /proc//maps é. Se um UID = processo raiz (por exemplo, de um binário suid-root) mmap(2) sa 0666 que está em um diretório 0700, então setuid(nobody) , qualquer um que execute esse binário poderia ignorar a restrição de access imposta pela falta de x for other permissão no diretório.