Callgraphs przy użyciu GraphViz z CMake i Clang

Nov 24 2020

Moim celem jest generowanie wykresów połączeń przy użyciu CMake + Clang + GraphViz w czasie kompilacji.

Korzystając z tych [ 1 , 2 ] procesów mogę tworzyć proste wykresy. Ale nie jestem pewien, jak uogólnić proces na projekt CMake.

Mam wykonywalny cel.

add_executable(${TARGET} ${SOURCES})

Które z poziomu makra dodaję wykresowi odpowiednie opcje do celu:

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

I dodaj dodatkowe polecenie po kompilacji, które generuje wykresy wywołań:

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

Ale brzęk próbuje stworzyć plik wykonywalny dla celu. Powoduje to ten błąd:

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

Nie rozumiem również, w jaki sposób dowolne niestandardowe polecenie ( optna przykład) mogłoby uzyskać dostęp do utworzonej reprezentacji LLVM. Wygląda na to, że moje polecenie niestandardowe nie ma wiedzy o odpowiednich plikach (nawet jeśli powyższy błąd został naprawiony).


Co do tej pory rozumiem:

  1. CMake add_executabledodaje -o outfile.exeargument clang, co uniemożliwia mi wykonanie tych samych kroków, co w połączonych procesach [ 1 , 2 ]
  2. $<TARGET_FILE:${TARGET}> można użyć do znalezienia utworzonych plików z clang, ale nie wiem, czy to działa dla reprezentacji LLVM.
  3. Próbowałem zamiast tego zrobić cel niestandardowy, ale miałem problemy z przeniesieniem wszystkich TARGETźródeł ze wszystkimi ustawieniami do celu niestandardowego.
  4. Proces przedstawiony tutaj [ 3 ] może być szczególnie istotny, -Wl,-save-tempsale wydaje się, że jest to dość okrężny sposób na uzyskanie IR (przy użyciu llvm-dis).
  5. unknown file typeBłąd jest spowodowany obiektu rzeczywiście LLVMreprezentacji, ale podejrzewam, że łącznik oczekuje innego formatu.
  6. Aby konsolidator zrozumiał LLVMreprezentację, dodaj -fltodo opcji konsolidatora target_link_options(${TARGET} PRIVATE -flto)(źródło [ 4 ]). To jest niesamowite, ponieważ oznacza, że ​​prawie rozwiązałem ten problem ... Po prostu nie wiem, jak uzyskać ścieżkę do utworzonych plików wyjściowych kodu bitowego w cmake, kiedy to zrobię, mogę przekazać je do opt (mam nadzieję. ..).
  7. Aby uzyskać obiekty docelowe, można użyć następującego polecenia cmake $<TARGET_OBJECTS:${TARGET}>w przypadku cmake spowoduje to wyświetlenie plików kodu bitowego LLVM .o(Czy jest to .ospowodowane zmianą nazwy przez cmake?).
  8. .oPlik jest w tym przypadku bitcode, jednak optpojawia się tylko narzędziem do reprezentacji LLVM. Aby przekonwertować na to llvm-dis bitcode.bc –o llvm_asm.ll. Uważam, że z powodu kompilacji krzyżowej zniekształcone symbole mają dziwny format. llvm-cxxfiltNie udaje się na przykład przekazać ich dollvm-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. Adresowanie 8. jest to format zniekształcania nazw MSVC. Oznacza to, że podczas kompilacji w systemie Windows clang używa zniekształcenia nazw formatu MSVC. Zaskoczenie dla mnie ... (źródło [ 5 ]).
  10. LLVM jest dostarczany wraz z llvm-undnamenim, jest w stanie rozszyfrować symbole. To narzędzie, gdy uruchamiam go w znacznym stopniu, popełnia błędy, gdy podam surowe dane wejściowe, wydaje się działać tylko z poprawnymi symbolami. Narzędzie demumblewydaje się być wieloplatformowym, wieloformatowym opakowaniem llvm-undname i llvm-cxxfilt.

Moje prawie działające makro cmake wygląda następująco:

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

Jednak to nie działa ... Zawartość ${FILE}to zawsze cała lista ...

Tak jest nadal w tym przypadku:

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

Wynik wygląda następująco:

thinga.obj;thingb.obj

Dzieje się tak, ponieważ CMake nie ocenia wyrażenia generatora, dopóki nie zostanie sprawdzona PO pętli for. Oznacza to, że jest tu tylko jedna pętla i zawiera ona wyrażenie generatora (nie rozwiązane wyrażenie generatora) (źródło [ 6 ]). Oznacza to, że nie mogę przeglądać plików obiektowych i tworzyć serii niestandardowych poleceń dla każdego pliku obiektowego.


Dodam do powyższego, gdy się dowiem, jeśli wymyślę cały proces, opublikuję rozwiązanie.

Każda pomoc byłaby bardzo mile widziana, to był wielki ból w dupie.


Mam nadzieję, że sposób, aby CMake zaakceptował budowanie pliku wykonywalnego do pojedynczego pliku reprezentacji LLVM, używając tego pliku z opcją uzyskania callgraph, a następnie kończąc kompilację llc. Jestem jednak trochę ograniczony, ponieważ kompiluję krzyżowo. Ostatecznie wszystko, co równoważne, zrobi ...

Odpowiedzi

1 compor Nov 24 2020 at 20:26

Spróbuję odpowiedzieć tylko po to, aby zebrać wszystkie moje dotychczasowe odpowiedzi na komentarze.

Jeśli chcesz "obalić" CMake, możesz to zrobić za pomocą czegoś takiego (zaadaptowanego stąd z punktu 4 OP powyżej):

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)

W przypadku zastosowań, w których wymagane jest przetwarzanie każdego pliku, COMMANDmoże to być skrypt zewnętrzny (bash / python), który po prostu pobiera tę listę i generuje pliki .dot. Problem z wyrażeniami generatora polega na tym, że nie są one oceniane do czasu wygenerowania w CMake, a nie w foreachkontekście.

Jeśli chcesz wyzwolić regenerację na podstawie tego, który plik obiektu / kodu bitowego jest ponownie kompilowany, sprawy stają się trudne, ponieważ CMake ma predefiniowane sposoby wywoływania komponentów łańcucha narzędzi (kompilator, link itp.), Stąd dlaczego napisałem mój projekt oparty na CMake z powrotem wtedy, ale zdecydowanie odradzam unikanie przepracowania na początku, ponieważ brzmi to tak, jakbyś nie był jeszcze pewien, z czym masz do czynienia.

Nie przejmowałem się tym, aby LTO działało w pełni, aby również uzyskać działający plik wykonywalny, ponieważ nie mam takiej konfiguracji na tym bankomacie.

Wszystkie inne wymagania (np. Wyjście Graphviz, demangling) można połączyć z dalszymi niestandardowymi celami / poleceniami.

Inne rozwiązania to:

  1. gllvm
  2. dla zdesperowanych llvm-ir-cmake-utils