Callgraphs utilisant GraphViz avec CMake et Clang

Nov 24 2020

Mon objectif est de générer des graphiques d'appels en utilisant CMake + Clang + GraphViz au moment de la construction.

En utilisant ces [ 1 , 2 ] processus, je peux créer des graphiques simples. Mais, je ne sais pas comment généraliser le processus à un projet CMake.

J'ai une cible exécutable.

add_executable(${TARGET} ${SOURCES})

Lequel à partir d'une macro, j'ajoute les options pertinentes du graphique à la cible:

target_compile_options(${TARGET} PRIVATE -S -emit-llvm)

Et, ajoutez une commande post build supplémentaire qui génère les graphes d'appel:

add_custom_command(
    TARGET ${TARGET}
    POST_BUILD
    COMMENT "Running clang OPT"
    COMMAND opt -analyze -dot-callgraph
)

Mais le cliquetis tente de créer un exécutable pour la cible. Cela entraîne cette erreur:

[build] lld-link: error: 
Container.test.cpp.obj: unknown file type

Je ne comprends pas non plus comment une commande personnalisée ( optpar exemple) accéderait à la représentation LLVM produite. Il ne semble pas que ma commande personnalisée ait aucune connaissance des fichiers pertinents (même si l'erreur ci-dessus a été corrigée).


Ce que je comprends jusqu'à présent:

  1. CMake add_executableajoute l' -o outfile.exeargument à clang, cela m'empêche de faire les mêmes étapes que celles indiquées dans les processus liés [ 1 , 2 ]
  2. $<TARGET_FILE:${TARGET}> peut être utilisé pour trouver les fichiers produits à partir de clang, mais je ne sais pas si cela fonctionne pour la représentation LLVM.
  3. J'ai essayé de créer une cible personnalisée à la place, mais j'ai eu des problèmes pour obtenir toutes les TARGETsources avec tous les paramètres dans la cible personnalisée.
  4. Le processus décrit ici [ 3 ] pourrait être particulièrement pertinent, -Wl,-save-tempsmais cela semble être une manière assez détournée d'obtenir des IR (en utilisant llvm-dis).
  5. L' unknown file typeerreur est due au fait que l'objet est en fait une LLVMreprésentation, mais je soupçonne que l'éditeur de liens s'attend à un format différent.
  6. Pour que l'éditeur de liens comprenne la LLVMreprésentation, ajoutez -fltoaux options de l'éditeur de liens target_link_options(${TARGET} PRIVATE -flto), (source [ 4 ]). C'est génial, car cela signifie que j'ai presque résolu ce problème ... Je ne sais tout simplement pas comment obtenir le chemin d'accès aux fichiers de sortie bitcode produits dans cmake, une fois que je le fais, je peux les passer à opt (j'espère. ..).
  7. Pour obtenir les objets cibles, la commande cmake suivante peut être utilisée $<TARGET_OBJECTS:${TARGET}>dans le cas de cmake, cela listera les fichiers de bitcode LLVM .o(est-ce à .ocause d'un changement de nom par cmake?).
  8. Le .ofichier dans ce cas est un bitcode, mais l' optoutil n'apparaît qu'avec une représentation llvm. Pour se convertir à cela llvm-dis bitcode.bc –o llvm_asm.ll. En raison de la compilation croisée, je pense que le symbole mutilé est d'un format étrange. Les transmettre llvm-cxxfiltne réussit pas, par exemplellvm-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. Donc adressage 8. c'est un format de découpage de nom MSVC. Cela indique que lors de la compilation sur Windows, clang utilise le nom de format MSVC mangling. Une surprise pour moi ... (source [ 5 ]).
  10. LLVM livré avec llvm-undnameest capable de démêler les symboles. Cet outil, lorsque je l'exécute, génère des erreurs significatives lorsque je lui donne une entrée brute, il semble ne fonctionner qu'avec les symboles corrects. L'outil demumblesemble être un wrapper multi-plateforme et multi-format de llvm-undname et llvm-cxxfilt.

Ma macro cmake presque fonctionnelle est la suivante:

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()

Cependant, cela ne fonctionne pas ... Le contenu de ${FILE}est toujours la liste entière ...

C'est toujours le cas ici:

foreach (FILE IN LISTS $<TARGET_OBJECTS:${TARGET}>) add_custom_command( TARGET ${TARGET}
        POST_BUILD
        COMMAND echo ${FILE}
    )
endforeach()

Le résultat ressemble à:

thinga.obj;thingb.obj

Cela est dû au fait que CMake n'évalue pas l'expression du générateur avant que la boucle for ne soit évaluée. Cela signifie qu'il n'y a qu'une seule boucle ici et qu'elle contient l'expression du générateur (pas une expression de générateur résolue) (source [ 6 ]). Cela signifie que je ne peux pas parcourir les fichiers objets et créer une série de commandes personnalisées pour chaque fichier objet.


J'ajouterai à ce qui précède au fur et à mesure que je découvre les choses, si je comprends tout le processus, je publierai une solution.

Toute aide serait grandement appréciée, cela a été une grande douleur dans le cul.


Ce que j'espère, un moyen de faire en sorte que CMake accepte de construire un exécutable dans un seul fichier de représentation LLVM, en utilisant ce fichier avec opt pour obtenir le callgraph, puis en terminant la compilation avec llc. Je suis un peu contraint cependant, car je suis en train de compiler. En fin de compte, tout ce qui équivaut fera l'affaire ...

Réponses

1 compor Nov 24 2020 at 20:26

Je vais tenter une réponse juste pour rassembler toutes mes réponses aux commentaires jusqu'à présent.

Si vous voulez "subvertir" CMake, cela peut être fait avec quelque chose comme ça (adapté à partir d' ici du point 4 d'OP ci-dessus):

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)

Pour les utilisations où le traitement de chaque fichier est requis, le COMMANDpeut être un script externe (bash / python) qui prend simplement cette liste et génère les fichiers .dot. Le problème avec les expressions génératrices est qu'elles ne sont pas évaluées avant la génération dans CMake et pas dans un foreachcontexte.

Si vous voulez la régénération de déclenchement en fonction de quel objet / fichier code binaire est recompilé, les choses se compliquent depuis CMake a des moyens de préréglage pour appeler les composants d'un ensemble d' outils (compilateur, lien, etc.), d' où la raison pour laquelle je l' ai écrit mon projet basé sur CMake retour alors, mais je vous recommande fortement d'éviter la sur-ingénierie au début, car il semble que vous ne soyez pas encore sûr de ce à quoi vous êtes confronté.

Je n'ai pas pris la peine de faire fonctionner complètement LTO , afin d'obtenir également un exécutable fonctionnel car je n'ai pas une telle configuration sur cet ATM de machine.

Toutes les autres exigences (par exemple, sortie Graphviz, démêlage) peuvent être associées à d'autres cibles / commandes personnalisées.

D'autres solutions pourraient être:

  1. gllvm
  2. pour les llvm-ir-cmake-utils désespérés