embedded-deck
TEKNİK REHBER LİNUX ARAÇLAR LLVM-CLANG 2026

LLVM / Clang
Gömülü Derleme Araç Zinciri

Modüler LLVM altyapısını kullanarak embedded hedefler için cross-compile, sanitizer ve LTO ile üretim kalitesinde araç zinciri kur.

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

ÖzellikGCCClang / LLVM
MimariMonolitik — ön/arka uç sıkı bağlıModüler — her bileşen ayrı kütüphane
Derleme hızıOrtaGenellikle %20–30 daha hızlı
Hata mesajlarıTemel, bazen kafa karıştırıcıRenkli, kaynak konumu işaretli, daha net
Sanitizer desteğiASan/UBSan var, TSan sınırlıASan/UBSan/TSan/MSan tam destek
LTOLTO + LTRANS (plugin tabanlı)ThinLTO — paralel, çok daha hızlı
Statik analizSınırlıclang-tidy + clang-analyzer zengin kural seti
LisansGPL v3Apache 2.0 — ticari ürünlerde tercih edilir
Embedded destekarm-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

clang / clang++C ve C++ ön yüzü — GCC drop-in yerine geçer, aynı flag seti
lldLLVM bağlayıcısı — GNU ld/gold'dan 3-5x daha hızlı, LTO aware
llvm-arStatik kütüphane yöneticisi — LTO nesnelerini destekler
llvm-nmSembol tablosu listeci — LTO bitcode içinde de çalışır
llvm-objcopyNesne dosyası dönüştürücü — GNU objcopy yerine geçer
llvm-stripBinary sıkıştırıcı — debug sembollerini kaldırır
clang-tidyStatik analizör + linter — 300+ kural seti
clang-formatKod biçimlendirici — LLVM/Google/Mozilla stilleri
lldbLLVM debug aracı — GDB'ye alternatif, Python scriptable

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

bash
# 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

bash
# 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

bash
# 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ı

cmake — toolchain-aarch64-clang.cmake
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)
bash
# 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-gnu ile tek Clang binary'den tüm mimariler hedeflenir
  • --sysroot parametresi hedef sistemin libc ve başlık dosyalarını gösterir
  • -fuse-ld=lld ile 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

KernelClang desteğiNotlar
5.7+Temel Clang desteğix86_64 ve ARM64 stabil
5.10 LTSLLD bağlayıcı desteğiLD=ld.lld çalışır
5.15 LTSLLVM=1 kısayoluTüm araçlar otomatik set edilir
6.1 LTS+CFI (Control Flow Integrity)Android kernel güvenlik özelliği
6.6 LTS+LTO tam destekThinLTO kernel build stabil

Clang ile kernel derleme

bash
# 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.

DİKKAT

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ı

CONFIG_MODVERSIONSBazı sürümlerde Clang ile çakışma — devre dışı bırakın veya 6.1+ kullanın
CONFIG_GCC_PLUGINSGCC plugin API — Clang'da desteklenmez, devre dışı bırakın
CONFIG_STACKPROTECTOR_STRONGClang destekler; ancak çok eski BSP toolchain'lerinde sorun çıkabilir
Satıcı BSP patch'leriEski MediaTek/Qualcomm BSP'leri GCC-spesifik inline asm içerebilir — patch gerekebilir

Bu bölümde

  • LLVM=1 bayrağı 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

Heap buffer overflowmalloc ile ayrılan belleğin dışına yazma/okuma
Stack buffer overflowYerel dizi sınırı aşımı — char buf[64] ile 65. byte yazımı
Use-after-freefree() sonrası pointer kullanımı
Use-after-returnYerel değişkene döndürülen pointer ile işlem
Double freeAynı pointer'ın iki kez serbest bırakılması
Global buffer overflowGlobal dizi sınırı aşımı
Memory leakLeakSanitizer (LSan) ile birlikte kullanımda

ASan ile derleme ve çalıştırma

bash
# 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

bash
# 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ı

asan output
=================================================================
==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
NOT

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 -O1 temel 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

DenetimFlagYakaladığı 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=nullNULL pointer ile okuma/yazma
Hizalama-fsanitize=alignmentHizasız bellek erişimi — ARM kritik
Shift overflow-fsanitize=shiftNegatif/büyük miktarda bit kaydırma
Array bounds-fsanitize=boundsSabit boyutlu dizi sınır aşımı
Enum dönüşüm-fsanitize=enumGeçersiz enum değeri ataması
Vptr-fsanitize=vptrYanlış dinamik tip kullanımı (C++)

Minimal UBSan runtime — gömülü için

bash
# 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

c — alignment_bug.c
/* 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;
}
GÖMÜLİ NOT

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=undefined tüm UBSan kontrollerini etkinleştirir
  • -fsanitize-minimal-runtime gömülü için düşük overhead seçeneği
  • ARM'da hizalama hatası en kritik UBSan denetimi; memcpy ile güvenli okuma yapın
  • -fno-sanitize-recover=all hata 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.

cpp — data_race_example.cpp
#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

bash
# 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

tsan output
==================
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ı

tsan.supp
# Bilinen üçüncü taraf kütüphane yarışları
race:libssl*
race:libuv*
# Kasıtlı atomik olmayan erişim (dikkatli kullanın)
race:LoggingBackend::flush
KISITLAMA

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 -O1 TSan 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

ÖzellikFull LTOThinLTO
MekanizmaTüm IR tek dosyada birleşirHer modül için özetler (summary) oluşturulur
Bellek kullanımıÇok yüksek — büyük projelerde OOM riskiDüşük — özet bazlı analiz
Derleme süresiÇok uzun, paralel değilParalel — nproc kadar iş parçacığı
Optimizasyon kalitesiEn yüksekFull LTO'ya yakın (%5–10 fark)
Incremental derlemeDesteklenmiyorDestekleniyor (modül özetleri cache'lenir)
Gömülü öneriKüçük projeler içinBüyük projeler, CI pipeline için ideal

Clang LTO kullanımı

bash
# 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 — CMakeLists.txt
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)
PERFORMANS

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=lld temel 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

PrefixKural ailesiGö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 GuidelinesYüksek — ham pointer, reinterpret_cast
performance-*Performans sorunlarıYüksek — gereksiz kopya, move semantics
readability-*OkunabilirlikOrta — isim standartları, magic number
cert-*SEI CERT kurallarıYüksek — güvenlik kritik sistemler için
clang-analyzer-*Derin akış analiziYüksek — null deref, memory leak yolu

.clang-tidy konfigürasyon dosyası

.clang-tidy
---
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

bash
# 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

yaml — .github/workflows/clang-tidy.yml
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-tidy dosyası projeye özgü kural setini ve isim kurallarını tanımlar
  • compile_commands.json ile doğru include path ve flag'ler otomatik algılanır
  • run-clang-tidy-18 -j$(nproc) büyük projelerde paralel analiz yapar
  • CI'da WarningsAsErrors ile 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ş

bash
# 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

bash
# 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)

asan output — hata 1
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)

asan output — hata 2
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)

asan output — hata 3
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

bash
# 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)
ÖZET

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 — snprintf boyut argümanı hatalı
  • ThinLTO ile production binary %33 küçüldü