As instruções x86 exigem que sua própria codificação e todos os seus argumentos estejam presentes na memória ao mesmo tempo?

64

Estou tentando descobrir se é possível executar uma VM Linux cuja RAM é suportada apenas por uma única página física.

Para simular isso, modifiquei o manipulador de falhas de página aninhada no KVM para remover o bit presente de todas as entradas da tabela de páginas aninhadas (NPT), exceto a que corresponde à falha de página processada no momento.

Ao tentar iniciar um convidado Linux, observei que as instruções de montagem que usam operandos de memória, como

add [rbp+0x820DDA], ebp

levar a um loop de falha de página até restaurar o bit atual para a página que contém a instrução e também para a página mencionada no operando (neste exemplo [rbp+0x820DDA]).

Eu estou querendo saber por que esse é o caso. A CPU não deve acessar as páginas de memória seqüencialmente, ou seja, primeiro leia as instruções e depois acesse o operando da memória? Ou o x86 exige que a página de instruções e todas as páginas de operandos estejam acessíveis ao mesmo tempo?

Estou testando no AMD Zen 1.

savvybug
fonte
2
Por que você quer fazer isso?
SS Anne
11
Apenas por interesse técnico :)
savvybug 01/04
14
Voto positivo para a idéia hilária do projeto.
pipe
10
Isso é insano no nível de "inicialize o Linux em um emulador 486 em execução no JavaScript no navegador". Eu amo isso.
chrylis -on strike -
3
Aparentemente, eu levei essa pergunta à mesma conclusão lógica que você já estava pensando, sobre o conjunto mínimo de trabalho para garantir o progresso futuro. Eu já tinha respondido isso antes de você adicionar esse novo primeiro parágrafo à pergunta. : O PI adicionou alguns links e mais detalhes em alguns pontos (por exemplo, o caminhante de páginas tem permissão para armazenar em cache algumas entradas de diretório de páginas de convidados internamente), pois esta pergunta está recebendo muito mais atenção do que eu esperava, graças a alguma forma de chegar ao HNQ.
Peter Cordes

Respostas:

56

Sim, eles exigem o código da máquina e todos os operandos da memória.

A CPU não deve acessar as páginas de memória sequencialmente, ou seja, primeiro leia as instruções e depois acesse o operando da memória?

Sim, é logicamente o que acontece, mas uma exceção de falha de página interrompe esse processo em duas etapas e descarta qualquer progresso. A CPU não tem como lembrar de que instrução estava no meio quando ocorreu uma falha de página.

Quando um manipulador de falhas de página retorna após manipular uma falha de página válida, RIP = o endereço da instrução com falha, para que a CPU tente executá-la do zero .

Seria legal para o sistema operacional modificar o código da máquina da instrução com falha e esperar executar uma instrução diferente após ireto manipulador de falhas de página (ou qualquer outra exceção ou manipulador de interrupção). Portanto, o AFAIK é arquitetonicamente necessário que a CPU refaça a busca de código do CS: RIP no caso de que você está falando. (Supondo que ele retorne ao CS: RIP com falha em vez de agendar outro processo enquanto aguarda o disco na falha da página de disco rígido ou entregando um SIGSEGV a um manipulador de sinal em uma falha de página inválida.)

Provavelmente também é necessário arquiteturalmente para entrada / saída de hypervisor. E mesmo que não seja explicitamente proibido no papel, não é assim que as CPUs funcionam.

A @torek comenta que alguns microprocessadores (CISC) decodificam parcialmente as instruções e despejam o estado do microrregistro em uma falha de página , mas o x86 não é assim.


Algumas instruções são interrompíveis e podem progredir parcialmente, como rep movs(memcpy em uma lata) e outras instruções de string, ou reunir carregamentos / armazenamentos de dispersão. Mas o único mecanismo é atualizar registros arquiteturais como RCX / RSI / RDI para operações de cadeia de caracteres ou os registros de destino e máscara para reuniões (por exemplo, manual para AVX2vpgatherdd ). Não manter o código de operação / decodificação resulta em algum registro interno oculto e reiniciá-lo após o iret de um manipulador de falhas de página. Estas são instruções que fazem vários acessos de dados separados.

Lembre-se também de que o x86 (como a maioria dos ISAs) garante que as instruções sejam atômicas. interrupções / exceções: elas acontecem completamente, ou não acontecem, antes de uma interrupção. Interrompendo uma instrução de montagem enquanto estiver em operação . Por exemplo, add [mem], regseria necessário descartar a carga se a parte da loja falhar, mesmo sem um lockprefixo.


O pior número de páginas de espaço de usuário convidado presentes para avançar pode ser 6 (mais subárvores de tabela de página de kernel de convidado separadas para cada uma):

  • movsqou movswinstruções de 2 bytes que ultrapassam o limite de uma página; portanto, são necessárias as duas páginas para decodificar.
  • operando fonte qword [rsi]também uma divisão de página
  • operando de destino qword [rdi]também uma divisão de página

Se alguma dessas 6 páginas falhar, estamos de volta à estaca zero.

rep movsdtambém é uma instrução de 2 bytes, e progredir em uma etapa dela teria o mesmo requisito. Casos semelhantes, como push [mem]ou pop [mem]podem ser construídos com uma pilha desalinhada.

Uma das razões (ou benefícios colaterais) para / de tornar as cargas coletadas / armazenamentos de dispersão "interruptíveis" (atualizando o vetor de máscara com o progresso) é evitar aumentar esse espaço mínimo para executar uma única instrução. Também para melhorar a eficiência de lidar com várias falhas durante uma coleta ou dispersão.


O @Brandon aponta nos comentários que um convidado precisará de suas tabelas de páginas na memória , e as divisões de páginas no espaço do usuário também podem ser divisões de 1GiB, de modo que os dois lados estejam em subárvores diferentes do PML4 de nível superior. O passeio pela página HW precisará tocar em todas essas páginas da tabela de páginas de convidados para progredir. Uma situação dessa patologia dificilmente acontecerá por acaso.

O TLB (e os internos do caminhante de páginas) têm permissão para armazenar em cache alguns dos dados da tabela de páginas e não são necessários para reiniciar o percurso da página do zero, a menos que o SO tenha invlpgcriado ou definido um novo diretório de página de nível superior CR3. Nenhuma delas é necessária ao alterar uma página de não presente para presente; O x86 on paper garante que não é necessário (portanto, o "cache negativo" de PTEs não presentes não é permitido, pelo menos não visível ao software). Portanto, a CPU pode não VMexit, mesmo que algumas páginas da tabela de páginas físicas do convidado não estejam realmente presentes.

Os contadores de desempenho da PMU podem ser ativados e configurados de modo que a instrução também exija um evento perf para gravar em um buffer PEBS para essa instrução. Com a máscara de um contador configurada para contar apenas as instruções de espaço do usuário, não o kernel, é bem possível que continue tentando estourar o contador e armazenar uma amostra no buffer sempre que você retornar ao espaço do usuário, produzindo uma falha de página.

Peter Cordes
fonte
15
O pior caso para uma única instrução pode ser algo como " push dword [foo" (ou até apenas call [foo]) com tudo desalinhado no "limite da tabela de ponteiros de diretório de páginas" (adicionando até 6 páginas, 6 tabelas de páginas, 6 diretórios de páginas, 6 PDPTs e um PML4); com o recurso "amostragem precisa baseada em eventos com buffer PEBS" da CPU ativado e configurado para que os pushdados de monitoramento de desempenho sejam adicionados ao buffer PEBS. Para um "número mínimo de páginas fornecidas pelo host para que o hóspede possa progredir em casos patológicos", eu gostaria de ter pelo menos 16 páginas.
Brendan
4
Observe que esse tipo de coisa sempre foi comum nas arquiteturas CISC-y. Alguns microprocessadores decodificam parcialmente as instruções e despejam o estado do microrregistro em uma falha de página, mas outros não exigem e / ou exigem que os operandos de endereço das instruções "loop-y" (DBRA em m68k, MOVC3 / MOVC5 em Vax etc.) estejam em registros semelhantes ao seu exemplo do REP MOVS.
torek 2/04
11
@Brendan: alguém contou o pior caso em uma instrução VAX como cerca de 50 páginas. Eu esqueço os detalhes, mas você obviamente colocaria a instrução em um limite de página, usaria algo como a pesquisa de tabela de conversão com a tabela que mede um limite de página, use (rX) [rY] com os indiretos nos limites de página e em breve. As instruções mais cabeludas levaram até 6 operandos (carregá-las em r0-r5) e todas as seis podem ser duplas indiretas, eu acho.
torek 2/04
3
O sistema operacional pode alterar a instrução, mas também pode mudar EIP. Portanto, há uma pergunta lógica de acompanhamento. Qual é o número mínimo de páginas necessárias, assumindo um esquema de correção de instruções inteligente? Por exemplo, copie o valor não alinhado para um buffer temporário alinhado, emule a instrução e IRET para a próxima instrução.
MSalters
11
A página que contém as iretinstruções do sistema operacional também precisa estar na memória. Esta é uma instrução de um byte, portanto, uma página extra. O endereço de interrupção do manipulador de falhas da página também precisa estar na memória, mas pode ser a mesma página acima.
Stig Hemmer