Observando a instrução obsoleta buscando no x86 com código auto-modificador

Foi-me dito e li nos manuais da Intel que é possível escrever instruções na memory, mas a fila de pré-busca de instruções já pegou as instruções obsoletas e executará essas instruções antigas. Eu não tive sucesso em observar esse comportamento. Minha metodologia é a seguinte.

O manual de desenvolvimento de software da Intel declara da seção 11.6 que

Uma gravação em um local de memory em um segmento de código que está atualmente em cache no processador faz com que a linha (ou linhas) de cache associada seja invalidada. Essa verificação é baseada no endereço físico da instrução. Além disso, a família P6 e os processadores Pentium verificam se uma gravação em um segmento de código pode modificar uma instrução que foi pré-buscada para execução. Se a gravação afetar uma instrução pré-buscada, a fila de pré-busca será invalidada. Esta última verificação é baseada no endereço linear da instrução.

Então, parece que se eu espero executar instruções obsoletas, eu preciso ter dois endereços lineares diferentes se referirem à mesma página física. Então, eu mapeio um arquivo para dois endereços diferentes.

int fd = open("code_area", O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO); assert(fd>=0); write(fd, zeros, 0x1000); uint8_t *a1 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FILE | MAP_SHARED, fd, 0); uint8_t *a2 = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FILE | MAP_SHARED, fd, 0); assert(a1 != a2); 

Eu tenho uma function de assembly que leva um único argumento, um ponteiro para a instrução que eu quero alterar.

 fun: push %rbp mov %rsp, %rbp xorq %rax, %rax # Return value 0 # A far jump simulated with a far return # Push the current code segment %cs, then the address we want to far jump to xorq %rsi, %rsi mov %cs, %rsi pushq %rsi leaq copy(%rip), %r15 pushq %r15 lretq copy: # Overwrite the two nops below with `inc %eax'. We will notice the change if the # return value is 1, not zero. The passed in pointer at %rdi points to the same physical # memory location of fun_ins, but the linear addresses will be different. movw $0xc0ff, (%rdi) fun_ins: nop # Two NOPs gives enough space for the inc %eax (opcode FF C0) nop pop %rbp ret fun_end: nop 

Em C, copio o código para o arquivo mapeado na memory. Eu invoco a function do endereço linear a1 , mas passo um ponteiro para a2 como o destino da modificação do código.

 #define DIFF(a, b) ((long)(b) - (long)(a)) long sz = DIFF(fun, fun_end); memcpy(a1, fun, sz); void *tochange = DIFF(fun, fun_ins); int val = ((int (*)(void*))a1)(tochange); 

Se a CPU pegou o código modificado, val == 1. Caso contrário, se as instruções antigas foram executadas (duas nops), val == 0.

Eu corri isso em um 1.7GHz Intel Core i5 (2011 macbook air) e um Intel Xeon (R) CPU X3460 @ 2.80GHz. Porém, toda vez que vejo val == 1 indicando que a CPU sempre nota a nova instrução.

Alguém já experimentou o comportamento que quero observar? Meu raciocínio está correto? Estou um pouco confuso sobre o manual mencionando processadores P6 e Pentium, e qual a falta de mencionar meu processador Core i5. Talvez alguma outra coisa esteja acontecendo que faz com que a CPU libere sua fila de pré-busca de instrução? Qualquer insight seria muito útil!

Eu acho, você deve verificar o contador de desempenho MACHINE_CLEARS.SMC (parte do evento MACHINE_CLEARS ) da CPU (está disponível no Sandy Bridge 1 , que é usado no seu powerbook Air, e também disponível no seu Xeon, que é o Nehalem 2 – pesquisa “smc”). Você pode usar oprofile , perf ou oprofile Intel para encontrar seu valor:

http://software.intel.com/sites/products/documentation/doclib/iss/2013/amplifier/lin/ug_docs/GUID-F0FD7660-58B5-4B5D-AA9A-E1AF21DDCA0E.htm

Máquina Limpa

Descrição métrica

Certos events exigem que todo o pipeline seja limpo e reiniciado logo depois da última instrução descontinuada. Essa métrica mede três desses events: violações de pedido de memory, código de autodetecção e determinadas cargas para intervalos de endereços ilegais.

Problemas possíveis

Uma parte significativa do tempo de execução é gasta no processamento de limpezas de máquinas. Examine os events MACHINE_CLEARS para determinar a causa específica.

SMC: http://software.intel.com/sites/products/documentation/doclib/stdxe/2013/amplifierxe/win/win_reference/snb/events/machine_clears.html

MACHINE_CLEARS Código do evento: 0xC3 Máscara SMC: 0x04

Código de auto-modificação (SMC) detectado.

Número de limpezas de máquinas com código auto-modificável detectadas.

A Intel também diz sobre o smc http://software.intel.com/en-us/forums/topic/345561 (vinculado à taxonomia do Intel Performance Bottleneck Analyzer

Este evento é acionado quando o código de autoditificação é detectado. Isso pode ser normalmente usado por pessoas que fazem edição binária para forçar a tomar determinado caminho (por exemplo, hackers). Esse evento conta o número de vezes que um programa grava em uma seção de código. O código de auto-modificação causa uma penalidade severa em todos os processadores Intel 64 e IA-32. A linha de cache modificada é gravada de volta nos caches L2 e LLC. Além disso, as instruções precisariam ser recarregadas, causando penalidade de desempenho.

Eu acho que você verá alguns desses events. Se eles são, então a CPU foi capaz de detectar ato de auto-modificar o código e levantou o “Machine Clear” – reboot completa do pipeline. Os primeiros estágios são Fetch e eles solicitarão o cache L2 para o novo opcode. Estou muito interessado na contagem exata de events do SMC por execução do seu código – isso nos dará alguma estimativa sobre latências … (o SMC é contado em algumas unidades onde 1 unidade é considerada como sendo de 1.5 cpu ciclos – B.6.2. 6 de manual de otimização de intel)

Podemos ver que a Intel diz “reiniciado logo após a última instrução aposentada.”, Então eu acho que a última instrução aposentada será mov ; e seus nops já estão no pipeline. Mas a SMC será levantada na aposentadoria de mov e vai matar tudo no pipeline, incluindo nops.

Esta reboot do pipeline induzida pelo SMC não é barata, Agner tem algumas medições no Optimizing_assembly.pdf – “17.10 Código auto-modificador (todos os processadores)” (acho que qualquer Core2 / CoreiX é como o PM aqui):

A penalidade para executar um trecho de código imediatamente após modificá-lo é de aproximadamente 19 clocks para P1, 31 para PMMX e 150-300 para PPro, P2, P3, PM. O P4 limpará todo o cache de rastreamento após o código de auto-modificação. O 80486 e processadores anteriores requerem um salto entre o código modificado e modificado para liberar o cache de código. …

Código auto-modificador não é considerado uma boa prática de programação. Ele deve ser usado somente se o ganho de velocidade for substancial e o código modificado for executado tantas vezes que a vantagem supera as penalidades por usar código auto-modificador.

O uso de endereços lineares diferentes para falhar O detector de SMC foi recomendado aqui: https://stackoverflow.com/a/10994728/196561 – Eu tentarei encontrar a documentação da intel real … Não posso realmente responder à sua pergunta real agora.

Pode haver algumas dicas aqui: Manual de otimização, 248966-026, abril de 2012 “3.6.9 Mixing Code and Data”:

Colocar dados graváveis ​​no segmento de código pode ser impossível distinguir do código de auto-modificação. Dados graváveis ​​no segmento de código podem sofrer a mesma penalidade de desempenho que o código de auto-modificação.

e próxima seção

O software deve evitar gravar em uma página de código na mesma subpágina de 1 KB que está sendo executada ou buscar código na mesma subpágina de 2 KB do que está sendo gravado. Além disso, o compartilhamento de uma página contendo código executado diretamente ou de maneira especulativa com outro processador como uma página de dados pode acionar uma condição do SMC que faz com que todo o pipeline da máquina e o cache de rastreamento sejam limpos. Isso se deve à condição do código de autodetecção.

Portanto, há possivelmente alguns esquemas que controlam interseções de subpáginas graváveis ​​e executáveis.

Você pode tentar fazer modificação do outro thread (código de modificação cruzada) – mas a synchronization de thread muito cuidadosa e o flush de pipeline são necessários (você pode include alguns forçamentos brutos de atrasos no thread do gravador; CPUID logo após a synchronization é desejado). Mas você deve saber que eles já corrigiram isso usando ” armas nucleares ” – verifique patente US6857064 .

Estou um pouco confuso sobre o manual mencionando processadores P6 e Pentium

Isso é possível se você tivesse buscado, decodificado e executado alguma versão obsoleta do manual de instruções da Intel. Você pode redefinir o pipeline e verificar esta versão: Número do pedido: 325462-047BR, junho de 2013 “11.6 CÓDIGO AUTOMATIZADO”. Esta versão ainda não diz nada sobre CPUs mais recentes, mas menciona que quando você está modificando usando endereços virtuais diferentes, o comportamento pode não ser compatível entre microarquiteturas (pode funcionar em seu Nehalem / Sandy Bridge e pode não funcionar em .. Skymont)

11.6 CÓDIGO DE MODIFICAÇÃO AUTOMÁTICA Uma gravação em um local de memory em um segmento de código que está atualmente em cache no processador faz com que a linha (ou linhas) de cache associada seja invalidada. Essa verificação é baseada no endereço físico da instrução. Além disso, a família P6 e os processadores Pentium verificam se uma gravação em um segmento de código pode modificar uma instrução que foi pré-buscada para execução. Se a gravação afetar uma instrução pré-buscada, a fila de pré-busca será invalidada. Esta última verificação é baseada no endereço linear da instrução. Para os processadores Pentium 4 e Intel Xeon, uma gravação ou um snoop de uma instrução em um segmento de código, onde a instrução de destino já está decodificada e residente no cache de rastreamento, invalida todo o cache de rastreamento. O último comportamento significa que os programas que modificam o código podem causar grave degradação de desempenho quando executados nos processadores Pentium 4 e Intel Xeon.

Na prática, a verificação de endereços lineares não deve criar problemas de compatibilidade entre os processadores IA-32. Aplicativos que incluem código de auto-modificação usam o mesmo endereço linear para modificar e buscar a instrução.

O software de sistema, como um depurador, que possivelmente modifica uma instrução usando um endereço linear diferente do usado para buscar a instrução, executará uma operação de serialização, como uma instrução CPUID, antes que a instrução modificada seja executada, o que automaticamente ressincronizará o cache de instruções e a fila de pré-busca. (Consulte a Seção 8.1.3, “Manipulando o código de modificação automática e cruzada” para obter mais informações sobre o uso de código de modificação automática.)

Para processadores Intel486, uma gravação em uma instrução no cache a modificará no cache e na memory, mas se a instrução tiver sido pré-buscada antes da gravação, a versão antiga da instrução poderá ser a executada. Para evitar que a instrução antiga seja executada, libere a unidade de pré-busca de instrução codificando uma instrução de salto imediatamente após qualquer gravação que modifique uma instrução.

Atualização REAL , pesquisei por “SMC Detection” (com aspas) e há alguns detalhes de como o Core2 / Core iX moderno detecta o SMC e também muitas listas de erratas com Xeons e Pentiums pendurados no detector SMC:

  1. http://www.google.com/patents/US6237088 Sistema e método para rastrear instruções em andamento em um pipeline @ 2001

  2. DOI 10.1535 / itj.1203.03 (google for it, existe uma versão gratuita em citeseerx.ist.psu.edu) – o “INCLUSION FILTER” foi adicionado em Penryn para diminuir o número de detecções falsas de SMC; o “mecanismo de detecção de inclusão existente” é mostrado na Fig. 9

  3. http://www.google.com/patents/US6405307 – patente mais antiga na lógica de detecção de SMC

De acordo com a patente US6237088 (FIG5, resumo) há “buffer de endereço de linha” (com muitos endereços lineares um endereço por instrução buscada – ou em outras palavras o buffer cheio de IPs buscados com precisão de linha de cache). Cada loja, ou fase de “endereço da loja” mais exata de cada loja será alimentada em comparador paralelo para verificar, irá armazenar interseções para qualquer uma das instruções atualmente em execução ou não.

Ambas as patentes não dizem claramente, elas usarão endereço físico ou lógico na lógica SMC … L1i na Sandy bridge é VIPT ( Virtualmente indexado, marcado fisicamente , endereço virtual para o índice e endereço físico na tag.) De acordo com http : //nick-black.com/dankwiki/index.php/Sandy_Bridge, então temos o endereço físico no momento em que o cache L1 retorna os dados. Acho que a Intel pode usar endereços físicos na lógica de detecção do SMC.

Ainda mais, http://www.google.com/patents/US6594734 @ 1999 (publicado em 2003, lembre-se que o ciclo de design da CPU é de cerca de 3 a 5 anos) diz na seção “Resumo” que a SMC agora está na TLB e usa endereços físicos (ou em outras palavras – por favor, não tente enganar o detector SMC):

O código de auto-modificação é detectado usando um buffer lookaside de tradução . [Que] possui endereços de páginas físicas armazenados nele, através dos quais snoops podem ser executados usando o endereço de memory física de um armazenamento na memory. … Para fornecer uma granularidade mais precisa do que uma página de endereços, os bits FINE HIT são incluídos em cada input no cache, associando informações no cache a partes de uma página na memory.

(parte da página, referida como quadrantes na patente US6594734, soa como subpáginas 1K, não é?)

Então eles dizem

Portanto , os rastreamentos, acionados pelas instruções de armazenamento na memory , podem executar a detecção de SMC comparando o endereço físico de todas as instruções armazenadas no cache de instruções com o endereço de todas as instruções armazenadas na página ou páginas de memory associadas. Se houver uma correspondência de endereço, isso indica que um local de memory foi modificado. No caso de uma correspondência de endereço, indicando uma condição de SMC, o cache de instrução e o pipeline de instrução são liberados pela unidade de aposentadoria e novas instruções são buscadas da memory para armazenamento no cache de instruções.

Como os rastreamentos para detecção de SMC são físicos e o ITLB normalmente aceita como input um endereço linear para converter em um endereço físico, o ITLB é formado adicionalmente como uma memory endereçável por conteúdo nos endereços físicos e inclui uma porta de comparação de input adicional como porta de espionagem ou porta de tradução reversa)

– Então, para detectar o SMC, eles forçam as lojas a encaminharem o endereço físico de volta ao buffer de instruções via snoop (espiões similares serão entregues de outros núcleos / cpus ou de gravações de DMA em nossos caches …), se fis da snoop. conflitos de endereços com linhas de cache, armazenados no buffer de instruções, reiniciaremos o pipeline via sinal SMC entregue do iTLB para a unidade de aposentadoria. Pode imaginar o quanto os relógios cpu serão desperdiçados em tal loop snoop do dTLB via iTLB e para a unidade de aposentadoria (ele não pode aposentar a instrução next “nop”, embora tenha sido executado antes de mov e não tenha efeitos colaterais). Mas WAT? O ITLB possui input de endereço físico e segundo CAM (grande e quente) apenas para suportar e defender contra códigos auto-modificados.

PS: E se trabalharmos com páginas enormes (4M ou 1G)? O L1TLB tem inputs de páginas enormes, e pode haver muitas falsas detectações de SMC para 1/4 de 4 MB de página …

PPS: Existe uma variante, que o manuseio errôneo do SMC com diferentes endereços lineares estava presente apenas no início do P6 / Ppro / P2 …

Foi-me dito e li nos manuais da Intel que é possível escrever instruções na memory, mas a fila de pré-busca de instruções [já] já conseguiu as instruções obsoletas e [poderá] executar essas instruções antigas. Eu não tive sucesso em observar esse comportamento.

Sim, você seria.

Todos ou quase todos os processadores Intel modernos são mais rigorosos que o manual:

Eles bisbilhotam o pipeline com base no endereço físico, não apenas linear.

As implementações do processador podem ser mais rigorosas que os manuais.

Eles podem optar por ser assim porque encontraram códigos que não aderem às regras dos manuais, que eles não querem quebrar.

Ou … porque a maneira mais fácil de aderir à especificação arquitetônica (que, no caso da SMC, costumava ser oficialmente “até a próxima instrução de serialização”, mas na prática, para o código legado, era “até a próxima ramificação tomada está a mais de “bytes de distância”) pode ser mais estrito.