Callgraphs usando GraphViz com CMake e Clang
Meu objetivo é gerar gráficos de chamadas usando CMake + Clang + GraphViz em tempo de construção.
Usando esses [ 1 , 2 ] processos, posso criar gráficos simples. Mas, não tenho certeza de como generalizar o processo para um projeto CMake.
Eu tenho um alvo executável.
add_executable(${TARGET} ${SOURCES})
Que, de dentro de uma macro, adiciono as opções relevantes do gráfico ao destino:
target_compile_options(${TARGET} PRIVATE -S -emit-llvm)
E, adicione um comando adicional de pós-construção que gera os gráficos de chamadas:
add_custom_command(
TARGET ${TARGET}
POST_BUILD
COMMENT "Running clang OPT"
COMMAND opt -analyze -dot-callgraph
)
Mas o clang tenta criar um executável para o alvo. Isso resulta neste erro:
[build] lld-link: error:
Container.test.cpp.obj: unknown file type
Eu também não entendo como qualquer comando personalizado ( opt
por exemplo) acessaria a representação LLVM produzida. Parece que meu comando personalizado não tem nenhum conhecimento dos arquivos relevantes (mesmo que o erro acima tenha sido corrigido).
O que eu entendi até agora:
- CMake
add_executable
adiciona o-o outfile.exe
argumento para clang, isso me impede de fazer as mesmas etapas mostradas nos processos vinculados [ 1 , 2 ] $<TARGET_FILE:${TARGET}>
pode ser usado para localizar os arquivos produzidos a partir do clang, mas não sei se isso funciona para representação LLVM.- Tentei fazer um destino personalizado em vez disso, mas tive problemas para obter todas as
TARGET
fontes com todas as configurações no destino personalizado. - O processo descrito aqui [ 3 ] pode ser especialmente relevante,
-Wl,-save-temps
mas esta parece ser uma maneira bastante indireta de obter IR (usando llvm-dis). - O
unknown file type
erro é devido ao objeto realmente ser umaLLVM
representação, mas suspeito que o vinculador espera um formato diferente. - Para fazer o vinculador entender a
LLVM
representação, adicione-flto
às opções do vinculadortarget_link_options(${TARGET} PRIVATE -flto)
(fonte [ 4 ]). Isso é incrível, porque significa que quase resolvi isso ... Só não sei como obter o caminho para os arquivos de saída de código de bits produzidos no cmake, uma vez que o fizer, posso passá-los para opt (espero. ..). - Para obter os objetos de destino, o seguinte comando cmake pode ser usado
$<TARGET_OBJECTS:${TARGET}>
no caso de cmake, isso irá listar os arquivos de código de bits LLVM.o
(é.o
devido a uma renomeação por cmake?). - O
.o
arquivo, neste caso, é o código de bits, no entanto, aopt
ferramenta parece apenas uma representação llvm. Para converter para issollvm-dis bitcode.bc –o llvm_asm.ll
. Devido à compilação cruzada, acredito que o símbolo mutilado tem um formato estranho. Transmiti-los parallvm-cxxfilt
não é bem-sucedido, por exemplollvm-cxxfilt --no-strip-underscore --types ?streamReconstructedExpression@?$BinaryExpr@AEBV?$reverse_iterator@PEBD@std@@AEBV12@@Catch@@EEBAXAEAV?$basic_ostream@DU?$char_traits@D@std@@@std@@@Z
- Portanto, endereçando 8. este é um formato de mutilação de nome MSVC. Isso indica que, ao compilar no Windows, o clang usa o nome do formato MSVC mutilado. Uma surpresa para mim ... (fonte [ 5 ]).
- O LLVM vem com
llvm-undname
ele é capaz de demangle os símbolos. Esta ferramenta, quando eu a executo, apresenta erros significativos quando eu dou entrada bruta, ela parece funcionar apenas com os símbolos corretos. A ferramentademumble
parece ser uma plataforma cruzada, wrapper multi-formato de llvm-undname e llvm-cxxfilt.
11. Minha macro cmake quase funcionando é a seguinte:
macro (add_clang_callgraph TARGET)
if(CALLGRAPH)
target_compile_options(${TARGET} PRIVATE -emit-llvm)
target_link_options(${TARGET} PRIVATE -flto) foreach (FILE $<TARGET_OBJECTS:${TARGET}>) add_custom_command( TARGET ${TARGET}
POST_BUILD
COMMAND llvm-dis ${FILE} COMMAND opt -dot-callgraph ${FILE}.ll
COMMAND demumble ${FILE}.ll.callgraph.dot > ${FILE}.dot
)
endforeach()
endif()
endmacro()
No entanto, isso não funciona ... O conteúdo de ${FILE}
é sempre a lista inteira ...
Este ainda é o caso aqui:
foreach (FILE IN LISTS $<TARGET_OBJECTS:${TARGET}>) add_custom_command( TARGET ${TARGET}
POST_BUILD
COMMAND echo ${FILE}
)
endforeach()
O resultado é parecido com:
thinga.obj;thingb.obj
Isso ocorre porque o CMake não avalia a expressão do gerador até APÓS o loop for ser avaliado. Ou seja, há apenas um loop aqui e ele contém a expressão geradora (não uma expressão geradora resolvida) (fonte [ 6 ]). Isso significa que não posso percorrer os arquivos de objeto e criar uma série de comandos personalizados para cada arquivo de objeto.
Vou acrescentar o que precede à medida que descobrir as coisas, se eu descobrir todo o processo, postarei uma solução.
Qualquer ajuda seria muito apreciada, isso tem sido um grande pé no saco.
O que estou esperando é uma maneira de fazer o CMake aceitar construir um executável para um único arquivo de representação LLVM, usando esse arquivo com opt para obter o callgraph e, em seguida, terminar a compilação com llc
. No entanto, estou um pouco restrito, pois estou compilando. Em última análise, qualquer coisa equivliente fará ...
Respostas
Vou tentar uma resposta apenas para reunir todas as minhas respostas de comentários até agora.
Se você quiser "subverter" o CMake, pode fazer algo assim (adaptado daqui do ponto 4 do OP acima):
cmake_minimum_required(VERSION 3.0.2)
project(hello)
set(CMAKE_C_COMPILER clang)
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} "-flto") add_executable(hello main.c hello.c) # decide your bitcode generation method here # target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -emit-llvm)
target_compile_options(hello PUBLIC ${CMAKE_C_FLAGS} -c -flto) # this is just to print add_custom_target(print_hello_objs COMMAND ${CMAKE_COMMAND} -E echo $<JOIN:$<TARGET_OBJECTS:hello>," ">)
# this does some linking
# fill in details here as you need them (e.g., name, location, etc.)
add_custom_target(link_hello_objs
COMMAND llvm-link -o foo.bc $<TARGET_OBJECTS:hello>
COMMAND_EXPAND_LISTS)
Para usos em que o processamento de cada arquivo é necessário, o COMMAND
pode ser um script externo (bash / python) que apenas pega essa lista e gera os arquivos .dot. O problema com as expressões geradoras é que elas não são avaliadas até o tempo de geração no CMake e não em um foreach
contexto.
Se você deseja acionar a regeneração com base em qual arquivo de objeto / bitcode é recompilado, as coisas ficam complicadas, pois o CMake tem maneiras predefinidas de invocar os componentes de um conjunto de ferramentas (compilador, link, etc.), por isso escrevi meu projeto baseado no CMake de volta então, mas eu recomendo fortemente evitar a superengenharia no início, pois parece que você ainda não tem certeza do que está enfrentando.
Eu não me preocupei em fazer o LTO funcionar totalmente, a fim de também obter um executável funcional, uma vez que não tenho tal configuração neste caixa eletrônico.
Todos os outros requisitos (por exemplo, saída do Graphviz, demangling) podem ser associados a outros destinos / comandos personalizados.
Outras soluções podem ser:
- gllvm
- para os desesperados llvm-ir-cmake-utils