00 LLVM / Clang nedir?
LLVM, derleyici altyapısını yeniden kullanılabilir kütüphaneler halinde sunan modüler bir projedir. Clang ise bu altyapı üzerine inşa edilmiş C/C++/Objective-C ön yüzüdür.
GCC ile karşılaştırma
| Özellik | GCC | Clang / LLVM |
|---|---|---|
| Mimari | Monolitik — ön/arka uç sıkı bağlı | Modüler — her bileşen ayrı kütüphane |
| Derleme hızı | Orta | Genellikle %20–30 daha hızlı |
| Hata mesajları | Temel, bazen kafa karıştırıcı | Renkli, kaynak konumu işaretli, daha net |
| Sanitizer desteği | ASan/UBSan var, TSan sınırlı | ASan/UBSan/TSan/MSan tam destek |
| LTO | LTO + LTRANS (plugin tabanlı) | ThinLTO — paralel, çok daha hızlı |
| Statik analiz | Sınırlı | clang-tidy + clang-analyzer zengin kural seti |
| Lisans | GPL v3 | Apache 2.0 — ticari ürünlerde tercih edilir |
| Embedded destek | arm-none-eabi resmi multlib | --target ile herhangi mimari, baremetal dahil |
Modüler mimari ve IR
LLVM'in gücü, kaynak dilden bağımsız bir ara temsil katmanından gelir: LLVM IR (Intermediate Representation). Clang, C/C++ kaynak kodunu LLVM IR'ye dönüştürür; LLVM backend ise bu IR'yi hedef mimariye (ARM, RISC-V, x86, MIPS…) çevirir.
C/C++ kaynak
│
▼ clang (ön uç)
LLVM IR (.ll / .bc)
│
├──▶ Optimizasyon geçişleri (pass pipeline)
│
▼ LLVM backend
Makine kodu (.o / ELF)
│
▼ lld (LLVM bağlayıcısı)
Yürütülebilir / Shared library
Toolchain bileşenleri
Bu bölümde
- LLVM modüler mimari: ön uç (Clang) + IR + backend + lld ayrı katmanlar
- GCC'ye göre avantajlar: hızlı LTO, güçlü sanitizer, net hata mesajları, Apache lisansı
- Embedded için kritik: --target parametresiyle tek kurulumdan tüm mimarileri hedefle
01 Gömülü cross-compile kurulumu
LLVM'in tek büyük avantajı: ayrı arm-linux-gnueabihf-gcc paketi gerekmez — --target parametresiyle aynı Clang ikili dosyasından tüm mimarileri hedefleyebilirsiniz.
LLVM Toolchain kurulumu
# Ubuntu 22.04 / 24.04 — LLVM 18 resmi repo
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 18
# Alternatif: ubuntu package manager (daha eski sürüm)
sudo apt install clang lld llvm \
clang-tools clang-tidy clang-format \
libc++-dev libc++abi-dev
# Kurulumu doğrula
clang-18 --version
# clang version 18.1.3 (https://github.com/llvm/llvm-project...)
# Target: x86_64-pc-linux-gnu
AArch64 cross-compile — sysroot hazırlama
# ARM64 sysroot için gerekli paketler (QEMU debootstrap ile)
sudo apt install gcc-aarch64-linux-gnu \
binutils-aarch64-linux-gnu \
qemu-user-static debootstrap
# Ubuntu Jammy ARM64 sysroot oluştur
sudo debootstrap --arch=arm64 \
--foreign jammy \
/opt/sysroot-arm64 \
http://ports.ubuntu.com/ubuntu-ports
# Sysroot içinde ikinci aşama
sudo cp /usr/bin/qemu-aarch64-static /opt/sysroot-arm64/usr/bin/
sudo chroot /opt/sysroot-arm64 /debootstrap/debootstrap --second-stage
# Geliştirme kütüphanelerini sysroot'a ekle
sudo chroot /opt/sysroot-arm64 apt install -y \
libstdc++-12-dev libgcc-12-dev
Clang ile AArch64 cross-compile
# Basit C dosyası cross-compile
clang-18 \
--target=aarch64-linux-gnu \
--sysroot=/opt/sysroot-arm64 \
-I/opt/sysroot-arm64/usr/include \
-fuse-ld=lld \
-o hello_arm64 hello.c
# Cross-compiled binary kontrol
file hello_arm64
# hello_arm64: ELF 64-bit LSB executable, ARM aarch64
# ARMv7 (32-bit hard-float) hedef
clang-18 \
--target=armv7-linux-gnueabihf \
--sysroot=/opt/sysroot-armhf \
-march=armv7-a -mfpu=neon-vfpv4 -mfloat-abi=hard \
-fuse-ld=lld \
-o sensor_armhf sensor.c
# RISC-V 64 hedef
clang-18 \
--target=riscv64-linux-gnu \
--sysroot=/opt/sysroot-riscv64 \
-march=rv64gc -mabi=lp64d \
-fuse-ld=lld \
-o firmware_rv64 firmware.c
CMake ile Clang cross-compile toolchain dosyası
set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR aarch64)
set(SYSROOT /opt/sysroot-arm64)
set(TRIPLE aarch64-linux-gnu)
set(LLVM_VER 18)
set(CMAKE_C_COMPILER clang-${LLVM_VER})
set(CMAKE_CXX_COMPILER clang++-${LLVM_VER})
set(CMAKE_AR llvm-ar-${LLVM_VER})
set(CMAKE_NM llvm-nm-${LLVM_VER})
set(CMAKE_RANLIB llvm-ranlib-${LLVM_VER})
set(CMAKE_OBJCOPY llvm-objcopy-${LLVM_VER})
set(CMAKE_STRIP llvm-strip-${LLVM_VER})
set(CMAKE_C_COMPILER_TARGET ${TRIPLE})
set(CMAKE_CXX_COMPILER_TARGET ${TRIPLE})
set(CMAKE_SYSROOT ${SYSROOT})
set(CMAKE_EXE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_MODULE_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_SHARED_LINKER_FLAGS_INIT "-fuse-ld=lld")
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# CMake projesini cross-compile et
cmake -B build-arm64 \
-DCMAKE_TOOLCHAIN_FILE=toolchain-aarch64-clang.cmake \
-DCMAKE_BUILD_TYPE=Release \
-S .
cmake --build build-arm64 -j$(nproc)
Bu bölümde
--target=aarch64-linux-gnuile tek Clang binary'den tüm mimariler hedeflenir--sysrootparametresi hedef sistemin libc ve başlık dosyalarını gösterir-fuse-ld=lldile GNU ld yerine çok daha hızlı LLVM bağlayıcısı kullanılır- CMake toolchain dosyası tüm araçları (AR, NM, STRIP) LLVM sürümlerine yönlendirir
02 Clang ile Linux Kernel Derleme
Linux 5.7'den itibaren kernel resmi olarak Clang ile derlenebilmektedir. Android AOSP, ChromeOS ve bazı gömülü vendor BSP'leri artık yalnızca Clang kullanmaktadır.
Destekleyen kernel sürümleri
| Kernel | Clang desteği | Notlar |
|---|---|---|
| 5.7+ | Temel Clang desteği | x86_64 ve ARM64 stabil |
| 5.10 LTS | LLD bağlayıcı desteği | LD=ld.lld çalışır |
| 5.15 LTS | LLVM=1 kısayolu | Tüm araçlar otomatik set edilir |
| 6.1 LTS+ | CFI (Control Flow Integrity) | Android kernel güvenlik özelliği |
| 6.6 LTS+ | LTO tam destek | ThinLTO kernel build stabil |
Clang ile kernel derleme
# Yöntem 1: LLVM=1 (önerilir — tüm araçları otomatik ayarlar)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- LLVM=1 defconfig
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- LLVM=1 -j$(nproc)
# Yöntem 2: Araçları ayrı ayrı belirt
make ARCH=arm64 \
CROSS_COMPILE=aarch64-linux-gnu- \
CC=clang \
LD=ld.lld \
AR=llvm-ar \
NM=llvm-nm \
STRIP=llvm-strip \
OBJCOPY=llvm-objcopy \
OBJDUMP=llvm-objdump \
READELF=llvm-readelf \
HOSTCC=clang \
HOSTCXX=clang++ \
-j$(nproc)
# LLVM versiyonu belirtmek için (birden fazla sürüm kuruluysa)
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- LLVM=1 LLVM_IAS=1 \
CLANG_TRIPLE=aarch64-linux-gnu- \
-j$(nproc)
LLVM_IAS — LLVM Integrated Assembler
LLVM_IAS=1 Clang'ın yerleşik assembler'ını etkinleştirir. Bazı kernel assembly dosyaları GNU as sözdizimi kullandığından eski kernel sürümlerinde sorun çıkarabilir; 5.15+ ile büyük ölçüde giderilmiştir.
Clang ile kernel derlerken modüller de Clang ile derlenmeli; GCC ile derlenmiş modülleri Clang kernel'ine yüklemek oops'a yol açabilir. make modules_install de aynı LLVM=1 flag'iyle çalıştırın.
Clang kernel build kısıtlamaları
Bu bölümde
LLVM=1bayrağı CC, LD, AR, NM, STRIP, OBJCOPY hepsini otomatik Clang sürümlerine yönlendirir- Android ve ChromeOS kernel'leri yıllar önce Clang'a geçti — kanıtlanmış yaklaşım
- Modüller de aynı araç zinciriyle derlenmeli; karma GCC/Clang modüller sorun çıkarır
03 AddressSanitizer — ASan
ASan, çalışma zamanında bellek erişim hatalarını yakalamak için derleyici enstrümantasyonu kullanan en güçlü araçtır. Gömülü Linux uygulamalarında kritik hataları bulur.
ASan'ın yakaladığı hatalar
char buf[64] ile 65. byte yazımıfree() sonrası pointer kullanımıASan ile derleme ve çalıştırma
# ASan etkinleştir (x86_64 host — geliştirme/test)
clang++ -fsanitize=address \
-fsanitize-address-use-after-scope \
-fno-omit-frame-pointer \
-g -O1 \
-o sensor_asan sensor.cpp
# Çalıştır — hata varsa detaylı rapor üretir
./sensor_asan
# ASan + LSan (memory leak tespiti)
clang++ -fsanitize=address,leak \
-fno-omit-frame-pointer \
-g -O1 \
-o sensor_asan_lsan sensor.cpp
# ASan seçenekleri ortam değişkeniyle kontrol
ASAN_OPTIONS=verbosity=1:halt_on_error=0:log_path=/tmp/asan.log \
./sensor_asan
Cross-ASan — AArch64 gömülü Linux
# AArch64 ASan runtime kütüphanesini bul
clang-18 --target=aarch64-linux-gnu \
--sysroot=/opt/sysroot-arm64 \
-fsanitize=address \
-fno-omit-frame-pointer \
-g -O1 \
-fuse-ld=lld \
-o sensor_arm64_asan sensor.cpp
# ASan runtime'ı hedef cihaza kopyala
ASAN_RT=$(clang-18 --target=aarch64-linux-gnu \
-print-file-name=libclang_rt.asan-aarch64.so)
echo "ASan runtime: $ASAN_RT"
scp $ASAN_RT root@target:/usr/lib/
# Hedef cihazda çalıştırma
ASAN_OPTIONS=symbolize=1:detect_leaks=0 \
LD_PRELOAD=/usr/lib/libclang_rt.asan-aarch64.so \
./sensor_arm64_asan
ASan hata raporu — örnek çıktı
=================================================================
==1234==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xb4c00040
READ of size 4 at 0xb4c00040 thread T0
#0 0x4a3c28 in read_sensor_data sensor.cpp:47
#1 0x4a1b10 in process_frame sensor.cpp:123
#2 0x49f830 in main sensor.cpp:201
0xb4c00040 is located 0 bytes to the right of 128-byte region
[0xb4bfffc0,0xb4c00040) allocated by thread T0 here:
#0 0x4d3420 in malloc (libclang_rt.asan.so)
#1 0x4a3b00 in init_buffer sensor.cpp:35
SUMMARY: AddressSanitizer: heap-buffer-overflow sensor.cpp:47 in read_sensor_data
ASan yaklaşık 2x bellek artışı ve %50–100 performans düşüşü getirir. Sadece geliştirme ve test aşamalarında kullanın; üretim binary'lerine eklemeyin. -O1 optimizasyon seviyesi ASan ile en iyi sonuç verir.
Bu bölümde
-fsanitize=address -fno-omit-frame-pointer -g -O1temel ASan bayrağı seti- Cross-ASan için hedef mimariye özel runtime kütüphanesi gerekir
- ASAN_OPTIONS ile çalışma zamanı davranışı özelleştirilir
- Üretim binary'lerine kesinlikle eklenmemeli — sadece test ortamında
04 UndefinedBehaviorSanitizer — UBSan
UBSan, C ve C++'da tanımsız davranışları (undefined behavior) çalışma zamanında yakalar. Gömülü sistemlerde integer overflow ve hizalama sorunları sık görülen gizli hatalardır.
UBSan denetimleri
| Denetim | Flag | Yakaladığı sorun |
|---|---|---|
| Integer overflow | -fsanitize=signed-integer-overflow | İşaretli tam sayı taşması — C'de UB |
| Integer taşma | -fsanitize=unsigned-integer-overflow | İşaretsiz taşma — bazı kodlarda beklenmedik |
| Null dereference | -fsanitize=null | NULL pointer ile okuma/yazma |
| Hizalama | -fsanitize=alignment | Hizasız bellek erişimi — ARM kritik |
| Shift overflow | -fsanitize=shift | Negatif/büyük miktarda bit kaydırma |
| Array bounds | -fsanitize=bounds | Sabit boyutlu dizi sınır aşımı |
| Enum dönüşüm | -fsanitize=enum | Geçersiz enum değeri ataması |
| Vptr | -fsanitize=vptr | Yanlış dinamik tip kullanımı (C++) |
Minimal UBSan runtime — gömülü için
# Tam UBSan seti
clang++ -fsanitize=undefined \
-fno-omit-frame-pointer \
-g -O2 \
-o app_ubsan app.cpp
# Minimal UBSan — gömülü Linux (düşük overhead)
clang++ \
-fsanitize=signed-integer-overflow,null,alignment,bounds,shift \
-fsanitize-minimal-runtime \
-fno-sanitize-recover=all \
-g -O2 \
-o app_ubsan_min app.cpp
# Hata yakalanınca abort() çağır (varsayılan log+devam yerine)
UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 ./app_ubsan
# Cross-compile AArch64 UBSan
clang++-18 --target=aarch64-linux-gnu \
--sysroot=/opt/sysroot-arm64 \
-fsanitize=undefined \
-fsanitize-minimal-runtime \
-fuse-ld=lld \
-g -O2 \
-o app_arm64_ubsan app.cpp
Hizalama hatası — ARM gömülü tipik senaryo
/* Tipik gömülü ağ paket işleme kodu */
void parse_packet(uint8_t *buf) {
/* HATA: buf+1 muhtemelen uint32_t için hizasız */
uint32_t seq = *(uint32_t *)(buf + 1); /* UBSan: alignment */
uint16_t len = *(uint16_t *)(buf + 5); /* UBSan: alignment */
(void)seq; (void)len;
}
/* Doğru yaklaşım: memcpy kullan */
void parse_packet_safe(uint8_t *buf) {
uint32_t seq;
uint16_t len;
memcpy(&seq, buf + 1, sizeof(seq)); /* OK: hizasız okuma güvenli */
memcpy(&len, buf + 5, sizeof(len)); /* OK */
(void)seq; (void)len;
}
ARM Cortex-A hizalama hataları sessiz yanlış sonuç verebilir (unaligned access etkin) veya Data Abort üretir (strict alignment modunda). UBSan bu sorunları geliştirme aşamasında yakalar; -fsanitize=alignment en değerli kontrol bayraklarından biridir.
Bu bölümde
-fsanitize=undefinedtüm UBSan kontrollerini etkinleştirir-fsanitize-minimal-runtimegömülü için düşük overhead seçeneği- ARM'da hizalama hatası en kritik UBSan denetimi;
memcpyile güvenli okuma yapın -fno-sanitize-recover=allhata yakalanınca programı durdurur (önerilir)
05 ThreadSanitizer — TSan
TSan, çok iş parçacıklı programlarda veri yarışlarını (data race) tespit eden derleyici enstrümantasyon aracıdır. Gömülü Linux uygulamalarında çok kritik eşzamanlılık hatalarını yakalar.
Veri yarışı nedir?
İki iş parçacığı aynı bellek konumuna eş zamanlı eriştiğinde ve en az biri yazma işlemi yapıyorsa ve senkronizasyon mekanizması yoksa veri yarışı oluşur. Sonuç deterministik olmayan, bulunması son derece zor hatalardır.
#include <thread>
#include <cstdio>
int sensor_count = 0; /* paylaşılan global — mutex yok! */
void increment_worker() {
for (int i = 0; i < 100000; ++i)
sensor_count++; /* DATA RACE — TSan yakalar */
}
int main() {
std::thread t1(increment_worker);
std::thread t2(increment_worker);
t1.join();
t2.join();
printf("count=%d (beklenen: 200000)\n", sensor_count);
}
TSan ile derleme ve çalıştırma
# TSan etkinleştir
clang++ -fsanitize=thread \
-fno-omit-frame-pointer \
-g -O1 \
-o app_tsan data_race.cpp -pthread
# TSan seçenekleri
TSAN_OPTIONS="second_deadlock_stack=1:history_size=5:report_bugs=1" \
./app_tsan
# Verbose rapor — tüm stack trace'leri göster
TSAN_OPTIONS="verbosity=1:halt_on_error=0:log_path=/tmp/tsan" \
./app_tsan
# TSan suppression dosyası — bilinen false positive'leri bastır
TSAN_OPTIONS="suppressions=tsan.supp" ./app_tsan
TSan hata raporu — örnek
==================
WARNING: ThreadSanitizer: data race (pid=4567)
Write of size 4 at 0x556123456780 by thread T2:
#0 increment_worker data_race.cpp:9
#1 std::thread::_Invoker... (libstdc++.so)
Previous write of size 4 at 0x556123456780 by thread T1:
#0 increment_worker data_race.cpp:9
#1 std::thread::_Invoker... (libstdc++.so)
Location is global 'sensor_count' of size 4 at 0x556123456780
SUMMARY: ThreadSanitizer: data race data_race.cpp:9 in increment_worker
==================
TSan suppression dosyası
# Bilinen üçüncü taraf kütüphane yarışları
race:libssl*
race:libuv*
# Kasıtlı atomik olmayan erişim (dikkatli kullanın)
race:LoggingBackend::flush
TSan yalnızca userspace uygulamalarda çalışır; kernel space veya baremetal MCU'larda kullanılamaz. Gömülü Linux uygulama katmanı (daemon, service) debug için idealdir. TSan yaklaşık 5-10x yavaşlama ve 5-10x bellek artışı getirir.
Bu bölümde
-fsanitize=thread -g -O1TSan için temel bayraklar- TSAN_OPTIONS ile davranış özelleştirme: history_size, halt_on_error, log_path
- Suppression dosyası bilinen false positive'leri veya kasıtlı iyileştirmeleri bastırır
- Sadece userspace gömülü Linux uygulamaları için kullanılabilir
06 Link Time Optimization — LTO
LTO, tüm derleme birimlerinin birleştirilmesi sırasında optimizasyon yaparak inter-procedural analiz uygular. Gömülü sistemlerde binary boyutunu ve çalışma hızını ciddi ölçüde iyileştirir.
ThinLTO vs Full LTO
| Özellik | Full LTO | ThinLTO |
|---|---|---|
| Mekanizma | Tüm IR tek dosyada birleşir | Her modül için özetler (summary) oluşturulur |
| Bellek kullanımı | Çok yüksek — büyük projelerde OOM riski | Düşük — özet bazlı analiz |
| Derleme süresi | Çok uzun, paralel değil | Paralel — nproc kadar iş parçacığı |
| Optimizasyon kalitesi | En yüksek | Full LTO'ya yakın (%5–10 fark) |
| Incremental derleme | Desteklenmiyor | Destekleniyor (modül özetleri cache'lenir) |
| Gömülü öneri | Küçük projeler için | Büyük projeler, CI pipeline için ideal |
Clang LTO kullanımı
# ThinLTO (önerilir)
clang++ -flto=thin -O2 \
--target=aarch64-linux-gnu \
--sysroot=/opt/sysroot-arm64 \
-fuse-ld=lld \
-o firmware_lto main.cpp driver.cpp sensor.cpp
# Full LTO
clang++ -flto -O2 \
-fuse-ld=lld \
-o firmware_full_lto main.cpp driver.cpp sensor.cpp
# Binary boyut karşılaştırması
clang++ -O2 -o app_no_lto *.cpp && ls -sh app_no_lto
clang++ -O2 -flto -o app_full_lto *.cpp -fuse-ld=lld && ls -sh app_full_lto
clang++ -O2 -flto=thin -o app_thin_lto *.cpp -fuse-ld=lld && ls -sh app_thin_lto
# Tipik sonuç: no-lto=2.4M, full-lto=1.6M, thin-lto=1.7M (%30 küçülme)
CMake ile ThinLTO
cmake_minimum_required(VERSION 3.20)
project(EmbeddedApp CXX)
# Clang ThinLTO ayarları
include(CheckIPOSupported)
check_ipo_supported(RESULT lto_supported OUTPUT lto_output)
if(lto_supported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
# ThinLTO için özel flag (CMake varsayılan full LTO kullanır)
add_compile_options(-flto=thin)
add_link_options(-flto=thin -fuse-ld=lld)
message(STATUS "ThinLTO etkinlestirildi")
else()
message(WARNING "LTO desteklenmiyor: ${lto_output}")
endif()
add_executable(firmware main.cpp sensor.cpp driver.cpp)
Gömülü projelerde LTO sonrası genellikle gözlemlenen iyileşmeler: binary boyutu %20–40 küçülme, çalışma hızı %5–15 artış (inline yapılabilen fonksiyonlar sayesinde), boot süresi %10–20 kısalma (daha küçük text segment = daha az flash okuma).
Bu bölümde
- ThinLTO gömülü projeler için önerilir: paralel, incremental, Full LTO kalitesine yakın
-flto=thin -fuse-ld=lldtemel ThinLTO bayrağı — lld LTO-aware bağlayıcı gerekir- CMake'de
check_ipo_supported()+CMAKE_INTERPROCEDURAL_OPTIMIZATION - Tipik sonuç: %20–40 boyut küçülmesi, %5–15 hız artışı
07 clang-tidy Statik Analiz
clang-tidy, AST (Abstract Syntax Tree) tabanlı bir statik analizördür. GCC'nin uyarı bayraklarından çok daha derin analiz yaparak gömülü C++ kodundaki tasarım sorunlarını da yakalar.
Kural setleri
| Prefix | Kural ailesi | Gömülü için önemi |
|---|---|---|
bugprone-* | Yaygın bug kalıpları | Yüksek — integer dönüşüm, assert yan etki |
modernize-* | Modern C++ geçiş | Orta — nullptr, range-for, auto |
cppcoreguidelines-* | C++ Core Guidelines | Yüksek — ham pointer, reinterpret_cast |
performance-* | Performans sorunları | Yüksek — gereksiz kopya, move semantics |
readability-* | Okunabilirlik | Orta — isim standartları, magic number |
cert-* | SEI CERT kuralları | Yüksek — güvenlik kritik sistemler için |
clang-analyzer-* | Derin akış analizi | Yüksek — null deref, memory leak yolu |
.clang-tidy konfigürasyon dosyası
---
Checks: >
bugprone-*,
-bugprone-easily-swappable-parameters,
cppcoreguidelines-*,
-cppcoreguidelines-avoid-magic-numbers,
modernize-use-nullptr,
modernize-use-override,
modernize-loop-convert,
performance-unnecessary-copy-initialization,
performance-move-const-arg,
readability-identifier-naming,
clang-analyzer-*,
cert-err34-c,
cert-err52-cpp
WarningsAsErrors: 'bugprone-*,cppcoreguidelines-pro-type-reinterpret-cast'
CheckOptions:
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: lower_case
- key: readability-identifier-naming.VariableCase
value: lower_case
- key: readability-identifier-naming.ConstantCase
value: UPPER_CASE
clang-tidy çalıştırma
# Tek dosya analizi
clang-tidy-18 sensor.cpp -- -std=c++17 -I./include
# compile_commands.json ile (CMake projesi)
cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON .
clang-tidy-18 -p build/ sensor.cpp driver.cpp
# Tüm kaynak dosyalar üzerinde paralel çalıştır
run-clang-tidy-18 -p build/ -j$(nproc) 2>&1 | tee clang-tidy-report.txt
# Otomatik düzeltme (bazı kurallar için)
clang-tidy-18 -p build/ --fix --fix-errors sensor.cpp
# Sadece belirli kural ailesi
clang-tidy-18 -checks='-*,bugprone-*' -p build/ sensor.cpp
CI entegrasyonu — GitHub Actions
name: clang-tidy
on: [push, pull_request]
jobs:
tidy:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- name: Install LLVM 18
run: |
wget https://apt.llvm.org/llvm.sh && chmod +x llvm.sh
sudo ./llvm.sh 18
sudo apt install -y clang-tidy-18
- name: Configure
run: cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON
- name: Run clang-tidy
run: |
run-clang-tidy-18 -p build/ -j4 2>&1 | tee report.txt
grep -c "warning:" report.txt && exit 1 || exit 0
Bu bölümde
.clang-tidydosyası projeye özgü kural setini ve isim kurallarını tanımlarcompile_commands.jsonile doğru include path ve flag'ler otomatik algılanırrun-clang-tidy-18 -j$(nproc)büyük projelerde paralel analiz yapar- CI'da
WarningsAsErrorsile kritik kurallar zorunlu hale getirilir
08 Pratik: Embedded C++ Projesini Clang'a Geçirme
Gerçek bir gömülü Linux sensör daemon'ı GCC'den Clang'a geçiriliyor. ASan ile üç kritik hata tespit ediliyor ve düzeltiliyor.
Senaryo
Raspberry Pi 4 üzerinde çalışan bir I2C sensör okuma daemon'ı. GCC ile derleniyor, zaman zaman beklenmedik çökmeler yaşanıyor ama GDB ile üretim ortamında tespit edilemiyor. Proje Clang'a geçirilecek ve ASan ile hatalar bulunacak.
1. CMake toolchain → Clang 2. İlk derleme — uyarılar 3. ASan build 4. Test koşumu → 3 hata raporu 5. Düzeltme 6. UBSan + clang-tidy son kontrol
Adım 1 — CMakeLists.txt Clang'a geçiş
# Clang ile yapılandır
CC=clang-18 CXX=clang++-18 cmake -B build-clang \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_EXE_LINKER_FLAGS="-fuse-ld=lld" \
-S .
cmake --build build-clang 2>&1 | head -50
# sensor_reader.cpp:143:18: warning: implicit conversion from 'int' to 'uint8_t'
# driver/i2c.cpp:87:12: warning: variable 'timeout' set but not used
# driver/i2c.cpp:201:5: warning: 'buf' may not be null-terminated [clang-analyzer]
Adım 2 — ASan build ve test
# ASan build
CC=clang-18 CXX=clang++-18 cmake -B build-asan \
-DCMAKE_BUILD_TYPE=Debug \
-DCMAKE_C_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
-DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer -g" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fuse-ld=lld" \
-S .
cmake --build build-asan
# Test çalıştır — simüle edilmiş I2C verisi ile
ASAN_OPTIONS=halt_on_error=0:log_path=/tmp/asan_sensor \
./build-asan/sensor_daemon --test-mode --iterations=1000
Bulunan Hata 1 — Heap buffer overflow (i2c.cpp:87)
ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 1 at 0xb200002f
#0 i2c_read_register driver/i2c.cpp:87
#1 sensor_poll_loop sensor_reader.cpp:234
/* Hatalı kod — i2c.cpp:85 */
uint8_t rx_buf[SENSOR_REG_COUNT]; /* SENSOR_REG_COUNT = 16 */
for (int i = 0; i <= SENSOR_REG_COUNT; i++) /* HATA: <= yerine < olmalı */
rx_buf[i] = i2c_read_byte(fd); /* 17. yazma = overflow */
Bulunan Hata 2 — Use-after-free (sensor_reader.cpp:312)
ERROR: AddressSanitizer: heap-use-after-free
READ of size 8 at 0xb4c01080
#0 format_reading sensor_reader.cpp:312
#1 publish_mqtt mqtt_client.cpp:156
/* Hatalı kod */
SensorData *data = new SensorData(read_i2c());
process(data);
delete data; /* serbest bırakıldı */
publish(data); /* HATA: silinmiş nesne kullanımı */
/* Düzeltme */
auto data = std::make_unique<SensorData>(read_i2c());
process(*data);
publish(*data); /* unique_ptr kapsam sonunda otomatik siler */
Bulunan Hata 3 — Stack buffer overflow (config.cpp:44)
ERROR: AddressSanitizer: stack-buffer-overflow
WRITE of size 1 at offset 64 of local variable 'dev_path'
#0 build_device_path config.cpp:44
/* Hatalı kod */
char dev_path[64];
snprintf(dev_path, 128, "/dev/i2c-%d", bus_num); /* HATA: boyut 64, limit 128 */
/* Düzeltme */
char dev_path[128];
snprintf(dev_path, sizeof(dev_path), "/dev/i2c-%d", bus_num);
Son kontrol — UBSan + clang-tidy
# Düzeltmeler sonrası UBSan testi
CC=clang-18 CXX=clang++-18 cmake -B build-ubsan \
-DCMAKE_CXX_FLAGS="-fsanitize=undefined -g -O1" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=undefined -fuse-ld=lld" .
cmake --build build-ubsan
UBSAN_OPTIONS=halt_on_error=1 ./build-ubsan/sensor_daemon --test-mode
# Temiz çalışma — UBSan hatası yok
# clang-tidy son tarama
run-clang-tidy-18 -p build-clang/ -j$(nproc) 2>&1 | grep -c warning
# 0 — tüm kritik uyarılar giderildi
# Production build — ThinLTO ile
CC=clang-18 CXX=clang++-18 cmake -B build-prod \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_CXX_FLAGS="-flto=thin -O2" \
-DCMAKE_EXE_LINKER_FLAGS="-flto=thin -fuse-ld=lld" .
cmake --build build-prod
ls -sh build-prod/sensor_daemon build-clang/sensor_daemon
# build-clang/sensor_daemon: 2.1M build-prod/sensor_daemon: 1.4M (%33 küçülme)
GCC ile yıllarca gizli kalan üç kritik bellek hatası, Clang + ASan kombinasyonuyla ilk test çalışmasında tespit edildi. Geliştirme sürecine ASan/UBSan build hedefleri eklemek, üretim çökmelerini proaktif olarak önler.
Bu bölümde
- GCC → Clang geçişi için ayrı CMake build dizini ve CC/CXX ortam değişkenleri
- Hata 1: Off-by-one heap overflow —
<=yerine< - Hata 2: Use-after-free — ham pointer yerine
std::unique_ptr - Hata 3: Stack overflow —
snprintfboyut argümanı hatalı - ThinLTO ile production binary %33 küçüldü