ORC é uma ferramenta para encontrar violações da regra de definição única do C++ no conjunto de ferramentas OSX.
ORC é uma brincadeira com DWARF, que é uma brincadeira com ELF. ORC é um acrônimo; enquanto O significa ODR, em um ataque de ironia, R e C representam palavras múltiplas (possivelmente conflitantes).
Existem muitos artigos sobre a Regra de Uma Definição (ODR), incluindo o próprio Padrão C++. A essência da regra é que se um símbolo for definido em um programa, ele só poderá ser definido uma vez. Alguns símbolos recebem uma exceção a esta regra e podem ser definidos várias vezes. No entanto, esses símbolos devem ser definidos por sequências de tokens idênticas .
Observe que algumas configurações do compilador também podem afetar sequências de token - por exemplo, a ativação ou desativação do RTTI pode alterar a definição de um símbolo (neste caso, uma tabela vtable).
Qualquer símbolo que quebre a regra acima é uma violação de ODR (ODRV). Em alguns casos, o vinculador pode capturar a definição do símbolo duplicado e emitir um aviso ou erro. No entanto, a Norma afirma que um vinculador não é obrigado a fazer isso. Andy G descreve bem:
por motivos de desempenho, o padrão C++ determina que se você violar a regra de uma definição com relação aos modelos, o comportamento será simplesmente indefinido. Como o vinculador não se importa, as violações desta regra são silenciosas. fonte
ODRVs sem modelo são possíveis, e o vinculador também pode permanecer silencioso sobre eles.
Um ODRV geralmente significa que você tem um símbolo cujo layout binário difere dependendo da unidade de compilação que o construiu. Por causa da regra, entretanto, quando um vinculador encontra múltiplas definições, ele é livre para escolher qualquer uma delas e usá-la como layout binário para um símbolo. Quando o layout escolhido não corresponde ao layout binário interno do símbolo em uma unidade de compilação, o comportamento é indefinido.
Muitas vezes o depurador é inútil nesses cenários. Ele também usará uma única definição de símbolo para todo o programa e, quando você tentar depurar um ODRV, o depurador poderá fornecer dados incorretos ou apontar para um local em um arquivo que não parece correto. No final, o depurador parecerá estar mentindo para você, mas silenciosamente não oferecerá pistas sobre qual é o problema subjacente.
Como todos os bugs, os ODRVs levam tempo para serem corrigidos, então por que você deveria corrigir uma violação de ODR em código testado (e presumivelmente funcionando)?
ORC é uma ferramenta que realiza o seguinte:
Exceto um bug na ferramenta, o ORC não gera falsos positivos. Tudo o que relata é um ODRV.
Neste momento, o ORC não detecta todas as possíveis violações da Regra de Uma Definição. Esperamos expandir e melhorar o que ele pode capturar ao longo do tempo. Até então, isso significa que, embora o ORC seja uma verificação valiosa, uma verificação limpa não garante que um programa esteja livre de ODRVs.
ORC pode encontrar:
Uma observação sobre vtables: ORC detectará métodos virtuais que estão em slots diferentes. (Que é um tipo desagradável de programa corrompido.) Neste ponto, ele não detectará uma classe que possua métodos virtuais que sejam um "superconjunto" de uma classe duplicada que viola o ODR.
Além das principais fontes de ORC, tentamos fornecer vários exemplos de aplicativos que contêm ODRVs que a ferramenta deve capturar.
ORC foi originalmente concebido em macOS. Embora a sua implementação atual se concentre aí, ela não precisa ficar restrita a esse conjunto de ferramentas.
ORC é gerenciado pelo cmake e é construído usando as convenções de construção típicas de um projeto gerenciado pelo CMake:
mkdir build
cd build
cmake -GXcode ..
Existem vários aplicativos de amostra aos quais o ORC está integrado para fins de teste. Eles podem ser selecionados por meio do pop-up de destinos no Xcode.
ORC usa Tracy como sua ferramenta de criação de perfil preferida e é habilitada por padrão. Para desativar o Tracy, especifique a linha de comando cmake assim:
cmake .. -GXcode -DTRACY_ENABLE=OFF
A dependência do Tracy é necessária mesmo se a criação de perfil estiver desabilitada (ela será compilada fora do tempo de execução). Observe que esta opção está armazenada em cache, portanto, você deve desativá-la explicitamente OFF
ou ON
. Executar novamente a invocação da linha de comando com a opção ausente fará com que seu valor anterior seja usado.
ORC pode ser chamado diretamente da linha de comando ou inserido na cadeia de ferramentas na etapa do vinculador. A saída permanece inalterada; é simplesmente uma questão de conveniência no seu fluxo de trabalho.
Este modo é útil se você tiver o comando do vinculador e seus argumentos e quiser procurar ODRVs separados da construção real.
Arquivo de configuração (veja abaixo)
'forward_to_linker' = false
'standalone_mode' = false
Você precisa dos argumentos de linha de comando ld
do XCode. Construa com Xcode (se você não consegue vincular, o ORC não pode ajudar), copie o comando link e cole-o após a invocação do ORC. Algo como:
/path/to/orc /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -target ... Debug/lem_mac
(É uma linha de comando enorme, abreviada aqui.)
ORC será executado e registrará violações de ODR no console.
Se você tiver uma lista de arquivos de biblioteca para o ORC processar, ele também poderá fazer isso.
Arquivo de configuração (veja abaixo)
'forward_to_linker' = false
'standalone_mode' = true
Neste modo, basta passar uma lista de arquivos de biblioteca para o ORC processar.
Arquivo de configuração (veja abaixo)
'forward_to_linker' = true
'standalone_mode' = false
Para usar o ORC em seu projeto de compilação do Xcode, substitua as seguintes variáveis por um caminho totalmente qualificado para os scripts ORC:
"LIBTOOL": "/absolute/path/to/orc",
"LDTOOL": "/absolute/path/to/orc",
"ALTERNATE_LINKER": "/absolute/path/to/orc",
Com essas configurações em vigor, o Xcode deve usar ORC como ferramenta para as fases libtool
e ld
da construção do projeto. Devido à configuração forward_to_linker
, o ORC invocará a ferramenta de link adequada para produzir um arquivo binário. Assim que isso for concluído, o ORC iniciará sua varredura.
Entre outras configurações, o ORC pode ser configurado para sair ou simplesmente avisar quando um ODRV for detectado.
ORC percorrerá o diretório atual, procurando por um arquivo de configuração chamado:
.orc-config
ou_orc-config
Se encontrados, muitos switches podem controlar a lógica do ORC. Por favor, veja _orc_config
no repositório para exemplos. ORC preferirá .orc-config
então é simples copiar o _orc_config
original e alterar valores localmente em .orc-config
.
Por exemplo:
error: ODRV (structure:byte_size); conflict in `object`
compilation unit: a.o:
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 4 (0x4)
compilation unit: main.o:
definition location: /Volumes/src/orc/extras/struct0/src/main.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 1 (0x1)
structure:byte_size
é conhecida como categoria ODRV e detalha exatamente que tipo de violação esse erro representa. As duas unidades de compilação que estão em conflito são então geradas, juntamente com as informações DWARF que resultaram na colisão.
struct object { ... }
In ao:
e In main.o
estão os 2 arquivos ou arquivos objeto que não correspondem. O ODR provavelmente é causado por configurações incompatíveis de compilação ou #define na compilação desses arquivos. byte_size
é o valor real que causa um erro.
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
Em qual linha e arquivo o objeto foi declarado. Portanto, linha 3 de a.cpp
neste exemplo.
Para a mesma versão do ORC e a mesma entrada, o ORC sempre escreverá a mesma saída. Onde "o mesmo" é idêntico em bytes e uma ferramenta de comparação não mostrará diferenças.
Alcançar (e provavelmente manter) resultados consistentes é surpreendentemente desafiador em um aplicativo altamente multithread.
Tenha em mente, entretanto, que isso NÃO se aplica a diferentes versões do ORC. Mudanças no ORC quase certamente resultarão em mudanças na produção.
Também não há garantia de que uma “pequena” alteração nos arquivos de entrada garantirá uma “pequena” alteração na saída do ORC. Este comportamento é desejável e provavelmente será uma área de melhorias futuras.
orc_test
) Um aplicativo de teste de unidade é fornecido para garantir que o ORC esteja capturando o que pretende capturar. orc_test
introduz um "sistema de construção" em miniatura para gerar arquivos objeto de fontes conhecidas para produzir violações conhecidas de ODR. Em seguida, ele processa os arquivos de objeto usando o mesmo mecanismo da ferramenta de linha de comando ORC e compara os resultados com uma lista de relatórios ODRV esperada.
Cada teste de unidade na bateria é discreto e contém:
odrv_test.toml
, um arquivo TOML de alto nível que descreve os parâmetros do testeEm geral, um único teste deverá provocar uma única violação de ODR, mas isto pode não ser possível em todos os casos.
Esses arquivos são arquivos de origem C++ padrão. Sua quantidade e tamanho devem ser muito pequenos – grandes apenas o suficiente para causar o ODRV pretendido.
odrv_test.toml
O arquivo de configurações descreve para o aplicativo de teste quais fontes precisam ser compiladas, quais sinalizadores de compilação devem ser usados para o teste e quais ODRVs o sistema precisa estar atento como resultado da vinculação do(s) arquivo(s) de objeto gerado(s). ) junto.
As fontes de teste são especificadas com uma diretiva [[source]]
:
[[ source ]]
path = " one.cpp "
obj = " one "
flags = [
" -Dfoo=1 "
]
O campo path
descreve um caminho para o arquivo relativo a odrv_test.toml
. É o único campo obrigatório.
O campo obj
especifica o nome do arquivo objeto (temporário) a ser criado. Se este nome for omitido, será utilizado um nome pseudoaleatório.
O campo flags
especifica flags de compilação que serão usados especificamente para esta unidade de compilação. Usando este campo, é possível reutilizar o mesmo arquivo fonte com diferentes flags de compilação para obter um ODRV.
ODRVs são especificados com a diretiva [[odrv]]
:
[[ odrv ]]
category = " subprogram:vtable_elem_location "
linkage_name = " _ZNK6object3apiEv "
O campo category
descreve o tipo específico de violação de ODR que o aplicativo de teste deve esperar encontrar.
O campo linkage_name
descreve o símbolo específico que causou o ODRV. Atualmente não é utilizado, mas será aplicado à medida que o aplicativo de teste amadurecer.
Os sinalizadores a seguir não estão em uso no momento ou sofrerão grandes alterações à medida que o aplicativo de teste de unidade continua a amadurecer.
[compile_flags]
: Uma série de sinalizadores de compilação que devem ser aplicados a cada arquivo fonte no teste de unidade.
[orc_test_flags]
: uma série de configurações de tempo de execução para passar ao aplicativo de teste para este teste.
[orc_flags]
: Uma série de configurações de tempo de execução para passar ao mecanismo ORC para este teste.