Callgraphs mit GraphViz mit CMake und Clang

Nov 24 2020

Mein Ziel ist es, Anrufgraphen mit CMake + Clang + GraphViz zur Erstellungszeit zu generieren.

Mit diesen [ 1 , 2 ] Prozessen kann ich einfache Diagramme erstellen. Ich bin mir jedoch nicht sicher, wie ich den Prozess auf ein CMake-Projekt verallgemeinern soll.

Ich habe ein ausführbares Ziel.

add_executable(${TARGET} ${SOURCES})

Welche innerhalb eines Makros füge ich dem Ziel die graphrelevanten Optionen hinzu:

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

Fügen Sie einen zusätzlichen Post-Build-Befehl hinzu, der die Aufrufdiagramme generiert:

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

Der Clang versucht jedoch, eine ausführbare Datei für das Ziel zu erstellen. Dies führt zu diesem Fehler:

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

Ich verstehe auch nicht, wie ein benutzerdefinierter Befehl ( optzum Beispiel) auf die erzeugte LLVM-Darstellung zugreifen würde. Es sieht nicht so aus, als ob mein benutzerdefinierter Befehl die relevanten Dateien kennt (selbst wenn der obige Fehler behoben wurde).


Was ich bisher verstehe:

  1. CMake add_executablefügt das -o outfile.exeArgument zu clang hinzu. Dies hindert mich daran, die gleichen Schritte auszuführen, die in den verknüpften Prozessen gezeigt werden [ 1 , 2 ].
  2. $<TARGET_FILE:${TARGET}> kann verwendet werden, um die erzeugten Dateien von clang zu finden, aber ich weiß nicht, ob dies für die LLVM-Darstellung funktioniert.
  3. Ich habe stattdessen versucht, ein benutzerdefiniertes Ziel zu erstellen, hatte jedoch Probleme, alle TARGETQuellen mit allen Einstellungen in das benutzerdefinierte Ziel zu übernehmen.
  4. Der hier beschriebene Prozess [ 3 ] mag besonders relevant sein, -Wl,-save-tempsaber dies scheint ein ziemlich umständlicher Weg zu sein, um IR zu erhalten (unter Verwendung von llvm-dis).
  5. Der unknown file typeFehler ist darauf zurückzuführen, dass das Objekt tatsächlich LLVMdargestellt wird, aber ich vermute, dass der Linker ein anderes Format erwartet.
  6. Fügen Sie den Linker-Optionen hinzu (Quelle [ 4 ]), damit der Linker die LLVMDarstellung versteht . Das ist großartig, weil es bedeutet, dass ich das fast gelöst habe ... Ich weiß nur nicht, wie ich den Pfad zu den produzierten Bitcode-Ausgabedateien in cmake bekommen soll. Sobald ich das tue, kann ich sie an opt übergeben (ich hoffe). ..).-fltotarget_link_options(${TARGET} PRIVATE -flto)
  7. Um die Zielobjekte abzurufen, kann $<TARGET_OBJECTS:${TARGET}>im Fall von cmake der folgende Befehl cmake verwendet werden. Hier werden die LLVM-Bitcodedateien aufgelistet .o(liegt das .oan einer Umbenennung durch cmake?).
  8. Die .oDatei ist in diesem Fall Bitcode, das optTool wird jedoch nur in einer llvm-Darstellung angezeigt. Um dies zu konvertieren llvm-dis bitcode.bc –o llvm_asm.ll. Aufgrund der Kreuzkompilierung glaube ich, dass das verstümmelte Symbol ein seltsames Format hat. Ihre Weitergabe llvm-cxxfiltist beispielsweise nicht erfolgreichllvm-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. Adressierung 8. Dies ist also ein MSVC-Format für die Namensverfälschung. Dies zeigt an, dass beim Kompilieren unter Windows Clang der Name Mangling im MSVC-Format verwendet wird. Eine Überraschung für mich ... (Quelle [ 5 ]).
  10. LLVM llvm-undnamewird mitgeliefert und kann die Symbole entwirren. Dieses Tool, wenn ich es ausführe, weist erhebliche Fehler auf, wenn ich ihm eine Rohdateneingabe gebe. Es scheint nur mit korrekten Symbolen zu funktionieren. Das Tool demumblescheint ein plattformübergreifender Multiformat-Wrapper von llvm-undname und llvm-cxxfilt zu sein.

11. Mein fast funktionierendes cmake-Makro lautet wie folgt:

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

Dies funktioniert jedoch nicht ... Der Inhalt von ${FILE}ist immer die gesamte Liste ...

Dies ist hier immer noch der Fall:

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

Das Ergebnis sieht aus wie:

thinga.obj;thingb.obj

Dies liegt daran, dass CMake den Generatorausdruck erst auswertet, nachdem die for-Schleife ausgewertet wurde. Das heißt, es gibt hier nur eine Schleife, die den Generatorausdruck (keinen aufgelösten Generatorausdruck) enthält (Quelle [ 6 ]). Dies bedeutet, dass ich keine Objektdateien durchlaufen und für jede Objektdatei eine Reihe von benutzerdefinierten Befehlen erstellen kann.


Ich werde das oben Gesagte ergänzen, wenn ich etwas herausfinde. Wenn ich den gesamten Prozess herausfinde, werde ich eine Lösung veröffentlichen.

Jede Hilfe wäre sehr dankbar, das war ein großer Schmerz im Arsch.


Was ich mir erhoffe, ist eine Möglichkeit, CMake dazu zu bringen, das Erstellen einer ausführbaren Datei für eine einzelne LLVM-Repräsentationsdatei zu akzeptieren. Verwenden Sie diese Datei mit opt, um den Callgraph abzurufen, und beenden Sie dann die Kompilierung mit llc. Ich bin allerdings ein wenig eingeschränkt, da ich Cross-Compilieren bin. Letztendlich wird alles Äquivalente tun ...

Antworten

1 compor Nov 24 2020 at 20:26

Ich werde versuchen, eine Antwort zu finden, um alle meine bisherigen Kommentarantworten zu sammeln.

Wenn Sie CMake "untergraben" möchten, können Sie dies mit etwas Ähnlichem tun (angepasst von hier aus OPs Punkt 4 oben):

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)

Für Anwendungen, bei denen die Verarbeitung für jede Datei erforderlich ist, COMMANDkann dies ein externes Skript (Bash / Python) sein, das nur diese Liste verwendet und die DOT-Dateien generiert. Das Problem mit Generatorausdrücken besteht darin, dass sie erst in CMake und nicht in einem foreachKontext bis zur Generierungszeit ausgewertet werden .

Wenn Sie die Regeneration basierend auf der neu kompilierten Objekt- / Bitcodedatei auslösen möchten, wird es schwierig, da CMake voreingestellte Möglichkeiten zum Aufrufen der Komponenten einer Toolchain (Compiler, Link usw.) hat. Deshalb habe ich mein CMake-basiertes Projekt zurückgeschrieben dann, aber ich würde dringend empfehlen, zu Beginn ein Überengineering zu vermeiden, da es so klingt, als ob Sie sich noch nicht sicher sind, was Sie erwartet.

Ich habe mich nicht darum gekümmert, LTO vollständig zum Laufen zu bringen , um auch eine funktionierende ausführbare Datei zu erhalten, da ich auf diesem Geldautomaten kein solches Setup habe.

Alle anderen Anforderungen (z. B. Graphviz-Ausgabe, Entwirrung) können mit weiteren benutzerdefinierten Zielen / Befehlen verknüpft werden.

Andere Lösungen könnten sein:

  1. gllvm
  2. für die verzweifelten llvm-ir-cmake-utils