Icon Ressource

Deuglyfying C/C++, Teil 2: Schöner Kompilieren

Dieses How-To führt durch den Dschungel der Compiler-Optionen und gibt Hinweise auf besonders hilfreiche und nützliche Optionen.
Weiterhin schlägt er ein System für mögliche Build-Typen vor und endet mit einer Beispielimplementierung all dieser Hinweise in CMake.
Hierbei wird ein besonderer Fokus auf gcc und clang gelegt, generell gelten die Hinweise für andere Compiler jedoch analog.

1. Sprachstandard

Generell sollte die Kommandozeile für den Compileraufruf explizit den verwendeten C/C++-Standard beinhalten.
Dies erfolgt in gcc und clang mit dem Compilerflag "-std=" und Werten, die den entsprechenden Standard angeben.
Gcc und clang lassen zwei mögliche Formate für die angaben zu: der entsprechende Sprachstandard ohne gcc-spezifische Erweiterungen(z.B. c11, c++20) oder mit ihnen(z.B. gnu11, gnu++20)
Generell sollte der neueste mögliche Sprachstandard verwendet werden (besonders bei C++) und auf die Verwendung von compilerspezifischen Erweiterungen verzichtet werden.

2. Optimierungen

Neben dem Aktivieren einzelner spezifischer Optimierungen(auf die hier nicht weiter eingegangen wird) bieten moderne Compiler die Möglichkeit, Optimierungen in Gruppen zu aktivieren.
Im Fall von gcc und clang sind die wichtigsten: -O0, -O1, -O2, -O3, -Os und -Og.
Die Gruppe -O0 steht hierbei für das Deaktivieren aller Optimierungen.
Die Gruppen -O1 bis -O3 optimieren verschieden stark auf Geschwindigkeit, wobei -O3 die stärkste Optimierung darstellt.
Für auf Geschwindigkeit optimierte Builds sollte im Allgemeinen -O2 verwendet werden, da -O3 unter Umständen zu erheblich größerem Kompilat führen kann und Code mit manchen Formen von undefiniertem Verhalten brechen kann.
Die Gruppe -Os ist die Optimierung des Kompilates auf geringe Größe. Sie entspricht weitgehend der Gruppe -O2, lässt jedoch Optimierungen, die den Code vergrößern würden, aus und fügt einige die ihn verlangsamen, aber verkleinern, hinzu.
Zuletzt sei hier die Gruppe -Og genannt, deren Aufgabe es ist, besonders Debugger-freundliche Binaries zu erzeugen. Dafür werden die Optimierungen der Gruppe -O1 angewandt(-O0-Kompilat allein ist kaum lesbar), und es wird auf jene Verzichtet, die das Debuggen erschweren würden.

Eine besondere Kategorie Optimierungen, die hier erwähnt werden soll, ist die Optimierung zur Linkzeit("Link-Time Optimization", kurz LTO), bei der der Linker(der Einblick in das gesamte Programm, nicht nur in eine Datei hat) abschließende Optimierungen vornimmt, die
vor allem in Inlining und der Elimination ungenutzten Codes bestehen. LTO kann in gcc und clang mit dem Flag "-flto" aktiviert werden.

3. Compilerwarnungen

Compilerwarnungen sind ein Feature moderner Compiler, bei dem sie Meldungen ausgeben, wenn Code zwar im weitesten Sinne dem Sprachstandard genügt, aber potentielle Probleme beinhaltet.
Daher empfiehlt es sich, stets so viele Compilerwarnungen wie möglich zu erhalten, und so zu programmieren, dass der Code keine beinhaltet.
Es sei hier nun ein Set Warnungen angegeben, die nach den Erfahrungen des Autors sowohl für C als auch für C++ eine gute Basis bilden, die für alle Prozessorarchitekturen gilt.
Es ist zu beachten, dass das Flag "-Wall" diesen Namen aus historischen Gründen trägt und zwar eine große Zahl von Warnungen aktiviert, aber keineswegs alle.
Warning-Flags für gcc und clang:
-Wall
-Wextra
-Wpedantic
-Waggregate-return
-Wcast-qual
-Wconversion
-Wdate-time
-Wfloat-conversion
-Wfloat-equal
-Winline
-Wmissing-declarations
-Wmissing-include-dirs
-Wnull-dereference
-Wredundant-decls
-Wshadow
-Wsign-conversion
-Wstack-protector
-Wswitch-enum
-Wswitch-default
-Wundef
-Wunused-macros


4. Sanitizer

Bei Sanitizern handelt es sich um Werkzeuge, die in Form einer Bibliothek zu einem Programm oder einer Bibliothek hinzu gelinkt werden, und zur Laufzeit einige Fehler aufdecken können.
Ihr Nutzen liegt darin, dass sie auch Fehler erkennen können, die dem Compiler und statischer Analyse des Codes verschlossen liegen(im Fall eines Thread-Sanitizers beispielsweise manche Formen von Race Conditions).
Die üblichen C++-Compiler verfügen über eine verschiedene Ausstattung an Sanitizern, Visual C++, gcc und clang verfügen alle über einen Adress-Sanitizer(dessen Verwendung mit clang++/g++ unten demonstriert wird),
während clang noch viele weitere bereit stellt.

Zu beachten ist bei der Verwendung von Sanitizern allerdings, dass sie im finalen Build einer Software nicht verwendet werden sollten, da ihre Implikationen für die Performance und den Speicherverbraucht erheblich sind und sie ein Sicherheitsrisiko darstellen.

Das folgende Beispiel demonstriert, wie man mit dem Adress-Sanitizer(ASan) einen Use after Free Fehler finden kann:

use-after-free.cpp:
// compiled with g++/clang++ -fsanitize=address -Og use-after-free.cpp && ./a.out
int main(int argc, char**argv){
    int *a = new int[2];
    delete[] a;
    return a[argc];
}

Ausgabe von ASan für use-after-free:
=================================================================
==23896==ERROR: AddressSanitizer: heap-use-after-free on address 0x602000000014 at pc 0x0000004cb6fc bp 0x7ffcd59937b0 sp 0x7ffcd59937a8
READ of size 4 at 0x602000000014 thread T0
    #0 0x4cb6fb in main /root/use-after-free.cpp:4:12
    #1 0x7fd623e8ce39 in __libc_start_main (/lib64/libc.so.6+0x23e39)
    #2 0x41c2b9 in _start (/root/a.out+0x41c2b9)

0x602000000014 is located 4 bytes inside of 8-byte region [0x602000000010,0x602000000018)
freed by thread T0 here:
    #0 0x4c96dd in operator delete[](void*) (/root/a.out+0x4c96dd)
    #1 0x4cb6c9 in main /root/use-after-free.cpp:3:5

previously allocated by thread T0 here:
    #0 0x4c8e8d in operator new[](unsigned long) (/root/a.out+0x4c8e8d)
    #1 0x4cb6be in main /root/use-after-free.cpp:2:13

SUMMARY: AddressSanitizer: heap-use-after-free /root/use-after-free.cpp:4:12 in main
Shadow bytes around the buggy address:
  0x0c047fff7fb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fc0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fd0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c047fff8000: fa fa[fd]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
  Shadow gap:              cc
==23896==ABORTING

5. Stack Smashing Protection

Stack Smashing Protection ist ein automatisch vom Compiler angewandter Mechanismus, um Stacküberläufe zu verhindern.
Er wird mit dem Flag "-fstack-protector-all" aktiviert. Da dies Performanceeinbußen erzeugt, wurde von Google eine alternative Implementierung "-fstack-protector-strong" eingeführt.
Diese schützt nicht alle Funktionen, sondern nur besonders angreifbare, und stellt daher einen Kompromiss zwischen Performance und Sicherheit dar.

6. Beispielimplementierung in CMake

Diese Beispielimplementierung zeigt für gcc und clang, wie die oben genannten Flags mit CMake angewandt werden können.
Sie verwendet drei verschiedene Build-Konfigurationen, jeweils die für das Debuggen, minimale Größe respektive maximale Geschwindigkeit optimiert sind.
CMake Beispiel:
CMAKE_MINIMUM_REQUIRED(VERSION 3.12 FATAL_ERROR)

PROJECT(Testproject VERSION 0)
ENABLE_LANGUAGE(C)
ENABLE_LANGUAGE(CXX)

# Set and enforce -std=c11 and -std=c++20
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# prevent use of other Compilers than clang and gcc
if(NOT((CMAKE_C_COMPILER_ID STREQUAL "Clang") OR (CMAKE_C_COMPILER_ID STREQUAL "GNU")))
    message(FATAL_ERROR "Only gcc and clang are supported for C-compilation")
elseif(NOT((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") OR (CMAKE_CXX_COMPILER_ID STREQUAL "GNU")))
    message(FATAL_ERROR "Only g++ and clang++ are supported for C++-compilation")
endif()

# Create an option to enable ASan
option(ASAN "Use ASan" OFF)
if(ASAN)
    set(SANITIZER "-fsanitize=address")
else()
    set(SANITIZER "")
endif()

# create 3 Build configurations: Debug, Release (with optimizaiton for speed) and Minisizerel (Release with optimization for size)
set(CMAKE_CONFIGURATION_TYPES "Debug;Release;Minisizerel" CACHE STRING "" FORCE)
set(COMMON_WARNINGS "-Wall -Wextra -Wpedantic -Waggregate-return -Wcast-qual -Wconversion -Wdate-time -Wfloat-conversion -Wfloat-equal -Winline -Wmissing-declarations -Wmissing-include-dirs -Wnull-dereference -Wredundant-decls -Wshadow -Wsign-conversion -Wstack-protector -Wswitch-enum -Wswitch-default -Wundef -Wunused-macros")

set(CMAKE_C_FLAGS "${COMMON_WARNINGS} ${SANITIZER}")
set(CMAKE_CXX_FLAGS "${COMMON_WARNINGS} ${SANITIZER}")

set(CMAKE_C_FLAGS_DEBUG "-Og -fno-lto -fstack-protector-all")
set(CMAKE_CXX_FLAGS_DEBUG "-Og -fno-lto -fstack-protector-all")

set(CMAKE_C_FLAGS_RELEASE "-O2 -flto -DNDEBUG -fstack-protector-strong")
set(CMAKE_CXX_FLAGS_RELEASE "-O2 -flto -DNDEBUG -fstack-protector-strong")

set(CMAKE_C_FLAGS_MINISIZEREL "-Os -flto -DNDEBUG -fstack-protector-strong")
set(CMAKE_CXX_FLAGS_MINISIZEREL "-Os -flto -DNDEBUG -fstack-protector-strong")
  • Like
Reaktionen: Mat und german
Autor
Lowl3v3l
Aufrufe
256
Erstellt am
Letzte Bearbeitung
Bewertung
0,00 Stern(e) 0 Bewertung(en)

Weitere Ressourcen von Lowl3v3l

Oben Unten