Callgraphs usando GraphViz com CMake e Clang

Nov 24 2020

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 ( optpor 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:

  1. CMake add_executableadiciona o -o outfile.exeargumento para clang, isso me impede de fazer as mesmas etapas mostradas nos processos vinculados [ 1 , 2 ]
  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.
  3. Tentei fazer um destino personalizado em vez disso, mas tive problemas para obter todas as TARGETfontes com todas as configurações no destino personalizado.
  4. O processo descrito aqui [ 3 ] pode ser especialmente relevante, -Wl,-save-tempsmas esta parece ser uma maneira bastante indireta de obter IR (usando llvm-dis).
  5. O unknown file typeerro é devido ao objeto realmente ser uma LLVMrepresentação, mas suspeito que o vinculador espera um formato diferente.
  6. Para fazer o vinculador entender a LLVMrepresentação, adicione -fltoàs opções do vinculador target_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. ..).
  7. 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.odevido a uma renomeação por cmake?).
  8. O .oarquivo, neste caso, é o código de bits, no entanto, a optferramenta parece apenas uma representação llvm. Para converter para isso llvm-dis bitcode.bc –o llvm_asm.ll. Devido à compilação cruzada, acredito que o símbolo mutilado tem um formato estranho. Transmiti-los para llvm-cxxfiltnã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
  9. 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 ]).
  10. O LLVM vem com llvm-undnameele é 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 ferramenta demumbleparece 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

1 compor Nov 24 2020 at 20:26

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 COMMANDpode 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 foreachcontexto.

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:

  1. gllvm
  2. para os desesperados llvm-ir-cmake-utils