Pergunta

Enquanto trabalhava em um projeto recente, fui visitado por um representante de controle de qualidade do cliente, que me fez uma pergunta que eu realmente não havia considerado antes:

Como você sabe que o compilador que você está usando gera código de máquina que corresponde exatamente à funcionalidade do código c e que o compilador é totalmente determinístico?

Para esta pergunta não tive absolutamente nenhuma resposta, pois sempre tomei o compilador como garantido.Ele recebe código e emite código de máquina.Como posso testar se o compilador não está realmente adicionando funcionalidades que eu não solicitei?ou ainda mais perigosamente implementar código de uma maneira ligeiramente diferente daquela que eu esperava?

Estou ciente de que isso talvez não seja realmente um problema para todos e, de fato, a resposta pode ser apenas..."você ultrapassou o limite e lide com isso".No entanto, ao trabalhar em um ambiente incorporado, você confia implicitamente no seu compilador.Como posso provar para mim mesmo e para o controle de qualidade que estou certo ao fazer isso?

Foi útil?

Solução

Para segurança de aplicativos incorporados críticos, as agências de certificação exigem satisfazer o requisito "comprovado em uso" para o compilador.Normalmente existem certos requisitos (como "horário de operação") que precisam ser atendidos e comprovados por documentação detalhada.No entanto, a maioria das pessoas não pode ou não quer atender a esses requisitos porque pode ser muito difícil, especialmente em seu primeiro projeto com um novo alvo/compilador.

Uma outra abordagem é basicamente NÃO confiar na saída do compilador.Qualquer deficiência do compilador e até mesmo dependente da linguagem (Apêndice G do padrão C-90, alguém?) Precisa ser coberta por um conjunto estrito de análise estática, testes de unidade e de cobertura, além dos testes funcionais posteriores.

Um padrão como MISRA-C pode ajudar a restringir a entrada do compilador a um subconjunto "seguro" da linguagem C.Outra abordagem é restringir a entrada de um compilador a um subconjunto de uma linguagem e testar qual é a saída de todo o subconjunto.Se nosso aplicativo for construído apenas com componentes do subconjunto, presume-se que se saiba qual será a saída do compilador.Geralmente é chamado de "qualificação do compilador".

O objetivo de tudo isso é ser capaz de responder à pergunta do representante de controle de qualidade com "Não confiamos apenas no determinismo do compilador, mas é assim que provamos isso...".

Outras dicas

Você pode aplicar esse argumento em qualquer nível:você confia nas bibliotecas de terceiros?você confia no sistema operacional?você confia no processador?

Um bom exemplo de por que isso pode ser uma preocupação válida, é claro, é como Ken Thompson colocou um backdoor no programa de 'login' original...e modificou o compilador C para que, mesmo que você recompilasse o login, ainda tivesse o backdoor.Veja isso postagem para mais detalhes.

Questões semelhantes foram levantadas sobre algoritmos de criptografia – como sabemos que não existe um backdoor no DES para a NSA espionar?

No final, você terá que decidir se confia o suficiente na infraestrutura que está construindo para não se preocupar com isso; caso contrário, terá que começar a desenvolver seus próprios chips de silício!

Você sabe testando.Ao testar, você está testando o código e o compilador.

Você descobrirá que as chances de você ou o criador do compilador terem cometido um erro são muito menores do que as chances de você cometer um erro se escrevesse o programa em questão em alguma linguagem assembly.

Existem trajes de validação de compilador disponíveis.
Aquele que eu lembro é "Perene".

Quando trabalhei em um compilador C para um processador SOC incorporado, tivemos que validar o compilador em relação a este e dois outros processos de validação (dos quais esqueci o nome).Validar o compilador para um certo nível de conformidade com esses testes fazia parte do contrato.

Tudo se resume à confiança.Seu cliente confia em algum compilador?Use isso, ou pelo menos compare o código de saída entre o seu e o deles.

Se eles não confiam em nenhum, existe uma implementação de referência para a linguagem?Você poderia convencê-los a confiar nisso?Em seguida, compare o seu com a referência ou use a referência.

Tudo isso assumindo que você realmente verifica o código real obtido do fornecedor/provedor e verifica se o compilador não foi adulterado, o que deve ser o primeiro passo.

De qualquer forma, isso ainda deixa a questão de como você verificaria, sem ter referências, um compilador do zero.Isso certamente parece muito trabalhoso e requer uma definição da linguagem, que nem sempre está disponível, às vezes a definição é o compilador.

Como você sabe que o compilador que você está usando gera código de máquina que corresponde exatamente à funcionalidade do código c e que o compilador é totalmente determinístico?

Se não, é por isso que você testa o binário resultante e certifica-se de enviar o mesmo binário com o qual testou.E por que, quando você faz alterações “pequenas” no software, você faz testes de regressão para garantir que nenhuma funcionalidade antiga quebrou.

O único software que certifiquei é o de aviônicos.A certificação FAA não é rigorosa o suficiente para provar que o software funciona corretamente e, ao mesmo tempo, força você a passar por uma certa quantidade de obstáculos.O truque é estruturar seu 'processo' para que ele melhore a qualidade tanto quanto possível, com o mínimo de saltos estranhos possível.Portanto, qualquer coisa que você saiba que é inútil e não encontrará bugs, você provavelmente poderá escapar.E qualquer coisa que você sabe que deveria fazer porque vai encontrar bugs que não sejam explicitamente solicitados pela FAA, sua melhor aposta é distorcer as palavras até parecer que você está dando ao pessoal da FAA/seu controle de qualidade o que eles pediram.

Na verdade, isso não é tão desonesto quanto parece. Em geral, a FAA se preocupa mais com você sendo consciencioso e confiante de que está tentando fazer um bom trabalho, do que exatamente o que você faz.

Alguma munição intelectual pode ser encontrada na Crosstalk, uma revista para engenheiros de software de defesa.Essa pergunta é o tipo de coisa em que eles passam muitas horas acordados. http://www.stsc.hill.af.mil/crosstalk/2006/08/index.html (Se eu conseguir encontrar minhas anotações antigas de um projeto antigo, voltarei aqui...)

Você nunca pode confiar totalmente no compilador, mesmo nos altamente recomendados.Eles poderiam lançar uma atualização com um bug e seu código compilaria o mesmo.Esse problema é agravado ao atualizar o código antigo com o compilador com bugs, fazer testes e enviar as mercadorias apenas para que o cliente ligue para você 3 meses depois com um problema.

Tudo se resume a testes, e se há uma coisa que aprendi é testar minuciosamente após qualquer alteração não trivial.Se o problema parecer impossível de encontrar, dê uma olhada no assembler compilado e verifique se ele está fazendo o que deveria.

Em diversas ocasiões encontrei bugs no compilador.Uma vez, houve um bug em que variáveis ​​de 16 bits eram incrementadas, mas sem transporte e somente se a variável de 16 bits fizesse parte de uma estrutura externa definida em um arquivo de cabeçalho.

... você confia implicitamente no seu compilador

Você deixará de fazer isso na primeira vez que encontrar um bug do compilador.;-)

Mas, em última análise, é para isso que servem os testes.Não importa para o seu regime de testes como o bug chegou ao seu produto, tudo o que importa é que ele não passou no seu extenso regime de testes.

Bem..você não pode simplesmente dizer que confia na saída do seu compilador - principalmente se trabalhar com código incorporado.Não é difícil encontrar discrepâncias entre o código gerado ao compilar o mesmo código com compiladores diferentes.Este é o caso porque o próprio padrão C é muito flexível.Muitos detalhes podem ser implementados de maneira diferente por diferentes compiladores sem quebrar o padrão.Como lidamos com essas coisas?Evitamos construções dependentes do compilador sempre que possível.Podemos lidar com isso escolhendo um subconjunto mais seguro de C como Misra-C conforme mencionado anteriormente pelo usuário cschol.Raramente preciso inspecionar o código gerado pelo compilador, mas isso também aconteceu comigo algumas vezes.Mas, em última análise, você depende de seus testes para garantir que o código se comporte conforme planejado.

Existe uma opção melhor por aí?Algumas pessoas afirmam que existe.A outra opção é escrever seu código em FAÍSCA/Ada.Eu nunca escrevi código em SPARK, mas meu entendimento é que você ainda teria que vinculá-lo a rotinas escritas em C que lidariam com o material "bare metal".A beleza do SPARK/Ada é que você tem absoluta garantia de que o código gerado por qualquer compilador será sempre o mesmo.Sem qualquer ambiguidade.Além disso, a linguagem permite anotar o código com explicações sobre como o código deve se comportar.O conjunto de ferramentas SPARK usará essas anotações para provar formalmente que o código escrito realmente faz o que as anotações descrevem.Disseram-me que, para sistemas críticos, o SPARK/Ada é uma boa aposta.Eu nunca tentei isso sozinho.

Você não tem certeza se o compilador fará exatamente o que você espera.A razão é, claro, que um compilador é um pedaço de software, e, portanto, é suscetível a bugs.

Os criadores de compiladores têm a vantagem de trabalhar com especificações de alta qualidade, enquanto o restante de nós precisa descobrir o que estamos fazendo à medida que avançamos.No entanto, as especificações do compilador também tem bugs e peças complexas com interações sutis.Portanto, não é exatamente trivial descobrir o que o compilador deveria estar fazendo.

Ainda assim, depois de decidir o que você acha que a especificação da linguagem significa, você pode escrever um teste bom, rápido e automatizado para cada nuance.É aqui que a escrita do compilador tem uma enorme vantagem sobre a escrita de outros tipos de software:em testes.Cada bug se torna um caso de teste automatizado, e o conjunto de testes pode ser muito completo.Os fornecedores de compiladores têm muito mais orçamento para investir na verificação da exatidão do compilador do que você (você já tem um trabalho diário, certo?).

O que isso significa para você?Isso significa que você precisa estar aberto às possibilidades de bugs em seu compilador, mas é provável que você não encontre nenhum.

Eu escolheria um fornecedor de compiladores que provavelmente não sairá do mercado tão cedo, que tenha um histórico de alta qualidade em seus compiladores e que tenha demonstrado sua capacidade de atender (corrigir) seus produtos.Os compiladores parecem ficar mais corretos com o tempo, então eu escolheria um que já exista há uma ou duas décadas.

Concentre sua atenção em acertar o código. Se é claro e simples, então quando você fazer Se você encontrar um bug no compilador, não precisará pensar muito para decidir onde está o problema. Escreva bons testes unitários, o que garantirá que seu código faça o que você espera.

Experimente o teste de unidade.

Se isso não for suficiente, use compiladores diferentes e compare os resultados dos seus testes unitários.Compare as saídas do strace, execute seus testes em uma VM, mantenha um log de disco e E/S de rede e compare-os.

Ou proponha escrever seu próprio compilador e dizer quanto custará.

O máximo que você pode certificar facilmente é que está usando um compilador não adulterado do provedor X.Se eles não confiam no provedor X, o problema é deles (se X for razoavelmente confiável).Se eles não confiam em nenhum fornecedor de compilador, então são totalmente irracionais.

Respondendo à pergunta deles:Certifico-me de que estou usando um compilador não adulterado do X por esses meios.X tem boa reputação, além de ter um bom conjunto de testes que mostram que nosso aplicativo se comporta conforme o esperado.

Todo o resto está começando a abrir a lata de minhocas.Você tem que parar em algum lugar, como diz Rob.

Às vezes, você consegue mudanças comportamentais quando solicita níveis agressivos de otimização.

E otimização e números de ponto flutuante?Esqueça!

Para a maior parte do desenvolvimento de software (pense em aplicativos de desktop), a resposta provavelmente é que você não sabe e não se importa.

Em sistemas críticos para a segurança (pense em usinas nucleares e aviônicos comerciais), você fazer cuidados e agências reguladoras exigirão que você prove isso.Na minha experiência, você pode fazer isso de duas maneiras:

  1. Utilize um compilador qualificado, onde “qualificado” significa que foi verificado de acordo com os padrões estabelecidos pela agência reguladora.
  2. Execute a análise de código objeto.Essencialmente, você compila um trecho de código de referência e, em seguida, analisa manualmente a saída para demonstrar que o compilador não inseriu nenhuma instrução que não possa ser rastreada até seu código-fonte.

Você recebe o que Dijkstra escreveu.

Selecione um compilador formalmente verificado, como o compilador Compcert C.

  1. Alterar o nível de otimização do compilador alterará a saída.
  2. Pequenas alterações em uma função podem tornar o compilador inline ou não inline uma função.
  3. Mudanças no compilador (versões gcc, por exemplo) podem alterar a saída
  4. Certas funções da biblioteca podem ser intrínsecas (ou seja, emitir montagem otimizada), enquanto outras não são.

A boa notícia é que, para a maioria das coisas, isso realmente não importa muito.Onde isso acontecer, você pode considerar a montagem se realmente for importante (por exemplo, em um ISR).

Se você está preocupado com código de máquina inesperado que não produz resultados visíveis, a única maneira provavelmente é entrar em contato com o fornecedor do compilador para obter algum tipo de certificação que satisfaça seu cliente.

Caso contrário, você saberá da mesma forma que sabe sobre bugs em seu código - testes.

O código de máquina dos compiladores modernos pode ser muito diferente e totalmente incompreensível para humanos insignificantes.

Penso que é possível reduzir este problema ao Problema de parada de alguma forma.

O problema mais óbvio é que se você usar algum tipo de programa para analisar o compilador e seu determinismo, como você sabe disso? seu programa é compilado corretamente e produz o resultado correto?

Se você estiver usando outro compilador "seguro", não tenho certeza.Tenho certeza de que escrever um compilador do zero provavelmente seria uma tarefa mais fácil.

Mesmo um compilador qualificado ou certificado pode produzir resultados indesejáveis.Mantenha seu código simples e teste, teste, teste.Isso ou percorrer o código da máquina manualmente, sem permitir nenhum erro humano.Além do sistema operacional ou qualquer ambiente em que você esteja executando (de preferência nenhum sistema operacional, apenas o seu programa).

Esse problema foi resolvido em ambientes de missão crítica desde o início do software e dos compiladores.Como muitos dos outros que responderam também sabem.Cada setor tem suas próprias regras, desde compiladores certificados até estilo de programação (você deve sempre programar dessa maneira, nunca usar isso ou aquilo), muitos testes e revisão por pares.Verificando cada caminho de execução, etc.

Se você não está em um desses setores, você obtém o que obtém.Um programa comercial em um sistema operacional COTS em hardware COTS.Irá falhar, isso é uma garantia.

Se você está preocupado com malicioso bugs no compilador, uma recomendação (IIRC, um requisito da NSA para alguns projetos) é que o binário do compilador seja anterior à escrita do código.Pelo menos então você sabe que ninguém tem adicionado bugs direcionados a seu programa.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top