Outils pour utilisateurs

Outils du site


prog:clang

Ceci est une ancienne révision du document !


C++ status, libstdc++ status

Sanitizer

En plus de ceux commun avec gcc, il existe memory.

memory

main.c
int main(int argc, char **argv) {
  int x[10];
  x[0] = 1;
  return x[argc];
}
clang -fsanitize=memory -g main.c -o main
$ clang -fsanitize=memory -g main5.c -o main5 && ./main5==3981==WARNING: MemorySanitizer: use-of-uninitialized-value
    #0 0x492d5e in main /tmp/main5.c:4:3
    #1 0x7f592c432461 in __libc_start_main /home/legarrec/info/portage/sys-libs/glibc-2.25-r9/work/glibc-2.25/csu/../csu/libc-start.c:295
    #2 0x41a129 in _start (/tmp/main5+0x41a129)

SUMMARY: MemorySanitizer: use-of-uninitialized-value /tmp/main5.c:4:3 in main

valgrind détecte l'erreur.

==4011== Syscall param exit_group(status) contains uninitialised byte(s)
==4011==    at 0x4F002B8: _Exit (_exit.c:31)
==4011==    by 0x4E70423: __run_exit_handlers (exit.c:98)
==4011==    by 0x4E704DC: exit (exit.c:105)
==4011==    by 0x4E58468: (below main) (libc-start.c:329)
==4011==  Uninitialised value was created by a stack allocation
==4011==    at 0x400470: main (main5.c:1)

Instrumentation de la stdlib :

L'utilisation de la libraire standard crée de nombreux faux positifs si la stdlib n'est pas instrumentée.

Pour l'instrumenter, il faut suivre les instructions de MemorySanitizerLibcxxHowTo Archive du 29/01/2016 le 13/02/2020

Ma tentative :

LDFLAGS="-L /home/legarrec/info/programmation/llvm-project/build/lib/clang/8.0.0/x86_64-unknown-linux-gnu/lib -lunwind -L/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/lib -lc++abi"
CFLAGS="-fsanitize=memory -stdlib=libc++ -I/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/include -I/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/include/c++/v1"
CXXFLAGS="-fsanitize=memory -stdlib=libc++ -I/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/include -I/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/include/c++/v1" cmake ..
-DCMAKE_BUILD_TYPE=debug -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++
  • Compilation du programme de test sans succès :
clang++
-L /home/legarrec/info/programmation/llvm-project/build/lib/clang/8.0.0/x86_64-unknown-linux-gnu/lib -lunwind
-L/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/lib -lc++abi
-fsanitize=memory,fuzzer -stdlib=libc++
I/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/include -I/home/legarrec/info/programmation/llvm-project_libcxx_msan/build/include/c++/v1
-I ~/info/programmation/poppler/cpp/
pdf_fuzzer.cc -o fuzz_target
-L ~/info/programmation/poppler/build/ -lpoppler
-L ~/info/programmation/poppler/build/cpp/ -lpoppler-cpp

Control Flow Integrity

Sanitize, Fuzz, and Harden Your C++ Code, Archive

Nécessite l'option Gold de llvm.

Il est possible d'activer tous les cfi-* (cfi-vcall, cfi-ncall, cfi-icall, cfi-derived-cast, cfi-unrelated-cast) en une seule fois : -fsanitize=cfi.

a.c
#include <cstdio>
void Bad() { puts("BOOO"); }
struct Expr {
  long a[2];
  long (*Op)(long *);
};
int main(int argc, char **argv) {
  struct Expr e;
  // On écrit indirectement sur la variable Op.
  e.a[2 * argc] = (long)&Bad;
  e.Op(e.a);
}
  • Sans le sanitizer
clang a.c && ./a.out
BOOO
  • Avec le sanitizer
clang -flto -fsanitize=cfi -fvisibility=hidden -fno-sanitize-trap=cfi -g a.c && ./a.out
file.cpp:11:3: runtime error: control flow integrity check for type 'long (long *)' failed during indirect function call
file.cpp:2: note: Bad() defined here
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior cfi_cast_strict.cpp:11:3 in

valgrind ne détecte pas l'erreur.

Erreurs

  • runtime error: control flow integrity check for type '…' failed during cast to unrelated type (vtable address 0x000000000000)

Le fait que l'adresse de la vtable soit nulle indique que la classe n'en a pas. Si une ou plusieurs méthodes sont déclarés virtuelles, elles ont été optimisées par le compilateur.

La solution est donc de supprimer (après vérification) la déclaration virtual.

Issue 515973: Invalid cast in SkTArray.h Archive du 31/07/2015 le 20/06/2021

Unified Diff: include/core/SkTArray.h Archive du 31/07/2015 le 20/06/2021

  • cast to unrelated type aligned_buffer.h
/usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/include/g++-v11/ext/aligned_buffer.h:115:16: runtime error: control flow integrity check for type '...' failed during cast to unrelated type (vtable address 0x747365742f617461)
0x747365742f617461: note: invalid vtable
<memory cannot be printed>
/usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/include/g++-v11/ext/aligned_buffer.h:115:16: note: check failed in ..., vtable located in (unknown)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/include/g++-v11/ext/aligned_buffer.h:115:16 in

L'utilisation de std::make_shared couplé avec le -fsanitize=cfi peu poser problème. Le constructeur de shared_ptr va vouloir caster la zone mémoire avant d'appeler le constructeur. La zone mémoire étant non initialisée, le sanitizer ne va pas reconnaitre le pointeur vtable.

Ce problème existe dans l'implémentation sous clang et gcc.

clang a résolu le problème en ajoutant _LIBCPP_NO_CFI à la fonction _Storage::__get_elem dans le fichier memory.

Pour gcc, il faut faire la même chose avec __attribute__((__no_sanitize__("cfi"))) pour les fonctions __aligned_buffer::_M_ptr dans le fichier aligned_buffer.h.

New: Recent shared_ptr storage change causes CFI cast failures during make_shared Archive du 01/02/2021 le 23/06/2021

Avoid cast<T*> before T is constructed to pacify CFI checks Archive du 01/02/2021 le 23/06/2021

Disable CFI in __get_elem to allow casting a pointer to uninitialized memory Archive du 04/02/2021 le 23/06/2021

  • RTTI symbol not found for class

Pour diagnostiquer une erreur de sanitizer, il est possible de mettre un point d'arrêt avec gdb.

Il est possible d'avoir des warnings de la part de gdb à propos de RTTI (warning: RTTI symbol not found for class). Dans ce cas, le plantage du sanitizer cfi est probablement un faux positif car il travaille sur les informations RTTI.

Exemple de message d'erreur généré par le sanitizer à l'exécution.

/usr/lib/gcc/x86_64-pc-linux-gnu/11.2.1/include/g++-v11/bits/std_function.h:590:9: runtime error: control flow integrity check for type 'std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> (const std::_Any_data &)' failed during indirect function call
/usr/lib/gcc/x86_64-pc-linux-gnu/11.2.1/include/g++-v11/bits/std_function.h:289: note: std::_Function_handler<std::unique_ptr<std::__future_base::_Result_base, std::__future_base::_Result_base::_Deleter> (), std::__future_base::_State_baseV2::_Setter<std::shared_ptr<restbed::Response>, std::shared_ptr<restbed::Response> const&> >::_M_invoke(std::_Any_data const&) defined here
/usr/lib/gcc/x86_64-pc-linux-gnu/11.2.1/include/g++-v11/bits/std_function.h:590:9: note: check failed in /mnt/c/j/build/clang_cfi/test/backend/data/test_load_vertical_eccentric, destination function located in /mnt/c/j/build/clang_cfi/_deps/restbed-build/librestbed.so.4
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/lib/gcc/x86_64-pc-linux-gnu/11.2.1/include/g++-v11/bits/std_function.h:590:9 in

Dans cet exemple, la classe restbed::Response a bien été compilée avec l'option __attribute__((visibility ("default"))).

Ces erreurs peuvent apparaître quand le sanitizer travaille avec une librairie dynamique. Pour que le sanitizer cfi réussisse son travail, il faut que les librairies compilées avec le sanitizer cfi soient liées statiquement.

Pile

a.c
void Bad() { puts("BOOO"); exit(0); }
int main(int argc, char **argv) {
  long array[10];
  array[argc * 13] = (long)&Bad;
}

Nécessite l'option Gold de llvm. L'exemple ne marche que pour du 64 bits.

  • Sans protection
clang a.c && ./a.out
BOOO
Segmentation fault
  • Avec protection safe-stack
clang -fsanitize=safe-stack a.c && ./a.out
Pas de message d'erreur...
  • Avec protection cfi
clang++ cfi_cast_strict.cpp -O0 -fsanitize=cfi -flto=thin -fvisibility=hidden -fno-sanitize-trap=cfi
BOOO
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==12856==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x7f6e125389a0 (pc 0x7f6e125389a0 bp 0x7ffcbb5f9370 sp 0x7ffcbb5f92a8 T12856)
==12856==The signal is caused by a READ memory access.
==12856==Hint: PC is at a non-executable region. Maybe a wild jump?
    #0 0x7f6e125389a0  (/usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/libstdc++.so.6+0x2209a0)

UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV (/usr/lib/gcc/x86_64-pc-linux-gnu/11.1.0/libstdc++.so.6+0x2209a0)
==12856==ABORTING

valgrind détecte l'erreur.

gdb

Pour arrêter le debug lors d'une erreur, on peut utiliser les symboles :

__ubsan::ScopedReport::~ScopedReport
__tsan::ReportRace

How can I break on UBSan reports in gdb and continue? Archive du 12/06/2015 le 18/04/2021

Fuzzer

Super Awesome Fuzzing, Part One Archive du 22/06/2017 le 13/02/2020

fuzz_me.c Archive

Il faut créer un fichier source qui ne contient qu'une seule fonction. La fonction main est dans l'archive statique libFuzzer.a.

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);

L'option de compilation -fsanitize=fuzzer est obligatoire mais il peut être utile de rajouter -fno-omit-frame-pointer -g -fsanitize=address,undefined.

clang++ -fno-omit-frame-pointer -g -fsanitize=address,undefined,fuzzer fuzz_me.cc

Si vous avez le message : error adding symbols: DSO missing from command line, il faut rajouter -lgcc_s.

Pour aller plus loin, même si ça possède de nombreuses fonctionnalités, je préfère afl mais une étude comparative va finir par s'imposer. Archive du 27/09/2020 le 13/02/2020

Couverture de code

Options à ajouter à la compilation : -O0 -fprofile-instr-generate -fcoverage-mapping -mllvm -runtime-counter-relocation

Options à ajouter au lieur : -fprofile-instr-generate -fcoverage-mapping

Puis exécuter le ou les programmes. Attention, pas d'exécution en parallèle.

shopt -s globstar dotglob
llvm-profdata-11 merge -output=code.profdata **/*.profraw
mkdir build/coverage
llvm-cov-11 show -use-color --format html -instr-profile=code.profdata ./build/test/calc/geotechnical/test_calc_meyerhof -output-dir=build/coverage
find build/coverage -name "*.html" -exec tidy -i -m --doctype html5 --drop-empty-elements no {} \;
find build/coverage -name "*.html" -exec sed -i -r "s#Created: .*-.*-.* .*:.*h4#</h4#g" {} \;

On commence par fusionner toutes les traces avec llvm-profdata puis on génère le rapport au format HTML.

En option, on indente et on supprime la date pour qu'un diff simple puisse se faire.

Options

  • -ftime-trace

Affiche où clang passe du temps pour compiler. Le fichier .json généré s'ouvre avec Chrome chrome://tracing

time-trace: timeline / flame chart profiler for Clang Archive du 16/01/2019 le 11/11/2019

Utilitaires

clang-include-fixer

Détecte les #include manquants.

# Compilation du code source
mkdir build
cd build
# On doit activer set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
cmake .. 
# Si nécessaire
make -j9 check
python2 /usr/lib/llvm/9/share/clang/run-find-all-symbols.py -binary=/usr/lib/llvm/9/bin/find-all-symbols
clang-include-fixer -db=yaml ../src/2lgc/pattern/publisher/publisher_grpc.h

Attention, contrairement à IWYU, il y a des limitations :

  • Ne détecte pas les #include en trop,
  • Ne détecte pas les #include qui pourrait être optimisés (meta.h à la place de small.h),
  • Ne propose qu'un seul #include à ajouter à la fois,

clang-tidy

clang-tidy détecte les parties du code qui sont soit bancals, soit pourrait être écrite d'une meilleure façon.

CMake

S'intègre parfaitement avec CMake.

Il faut commencer par activer l'option set(CMAKE_EXPORT_COMPILE_COMMANDS ON).

Puis lancer la génération des Makefile avec CC="clang" CXX="clang++" cmake -S . -B build pour générer également le fichier compile_commands.json.

L'analyse se lance avec run-clang-tidy.

Options d'analyse

Le fichier de config peut se générer via clang-tidy --dump-config > .clang-tidy. Il est conseillé de générer ce fichier à chaque changement de version de clang, certaines options pouvant être supprimées ou renommées.

Exemple du début du fichier .clang-tidy:

.clang-tidy
---
Checks:          '*,-llvm-header-guard,-modernize-use-trailing-return-type,-cppcoreguidelines-avoid-magic-numbers,-readability-magic-numbers,-fuchsia-default-arguments-calls'
WarningsAsErrors: '*'
HeaderFilterRegex: ''
...

Clang-Tidy, part 1: Modernize your source code using C++11/C++14 Archive du 16/03/2017 le 13/02/2020

Analyse uniquement certains dossiers

Il est aussi possible de filtrer certains dossiers :

run-clang-tidy-12 '^((?!/path1/|/path2/).)*$'

Gestion les erreurs

  • Ignorer la vérification sur des lignes de code spécifiques
badcode;  // NOLINT
 
// NOLINTNEXTLINE
badcode;
 
badcode;  // NOLINT(cert-err-58-cpp)
 
// NOLINTBEGIN(google*)
Foo(bool param);
// NOLINTEND(google*)

Extensions

clang-format

Il ne modifie pas directement le fichier source.

clang-format -style=Google XXX.cc

clang-format style Archive le 13/02/2020

  • Style

Il est possible de dire que le style est dans un fichier. Pour partir d'un fichier de base :

clang-format -style=llvm -dump-config > .clang-format

Puis pour utiliser le fichier .clang-format, il faut utiliser -style=file. Il ne faut pas remplacer file par le nom du fichier. clang-format va automatiquement chercher le fichier .clang-format et uniquement lui.

  • Messages d'erreurs
YAML:1:4: error: Got empty plain scalar

Le fichier .clang-format est au format UTF-8 avec BOM (EF BB BF). L'ouvrir avec Notepad++ et le réenregistrer au format UTF-8 sans BOM. Error formatting with custom .clang-format Archive du 18/06/2018 le 22/06/2020

YAML:145:3: error: unknown key 'StatementMacros'

La clé StatementMacros n'est pas supportée par la version de clang-format. Il suffit de supprimer la ligne StatementMacros et ses données ou de mettre à jour clang-format.

prog/clang.1664960216.txt.gz · Dernière modification : 2022/10/05 10:56 de root