GCC (GNU Compiler Collection) 완전 가이드

커널 빌드에서 GCC가 만들어내는 산출물을 이해할 수 있도록 옵션 의미를 연결해 설명합니다. 전처리부터 링크까지 단계별 산출물, 경고 정책과 `-Werror` 운용, `-O2` 기반 최적화와 디버그 정보 균형, 아키텍처별 보안 완화 플래그, inline asm/`__builtin_*`/attribute 사용 시 주의점, 크로스 컴파일 툴체인 구성, KCFLAGS를 포함한 Kbuild 통합 운용까지 실무 중심으로 상세히 정리합니다.

전제 조건: 개발 환경 설정빌드 시스템 문서를 먼저 읽으세요. GCC는 리눅스 커널 빌드의 핵심 툴체인으로, Kbuild가 GCC 옵션을 어떻게 조합하는지 이해하면 더 효과적으로 활용할 수 있습니다.
일상 비유: GCC는 다목적 공장 설비와 같습니다. 원재료(소스 코드)를 받아 여러 단계(전처리 → 컴파일 → 어셈블 → 링크)를 거쳐 완제품(실행 파일 또는 커널 이미지)을 생산합니다. 각 생산 단계에서 품질 검사(경고), 최적화, 포장 방식(출력 형식)을 다양하게 제어할 수 있습니다.

핵심 요약

  • GCC — GNU Compiler Collection. C, C++, Fortran, Ada, Go, D, COBOL, Objective-C, Modula-2 등을 지원하는 통합 컴파일러 배포판.
  • 컴파일 4단계 — 전처리(-E) → 컴파일(-S) → 어셈블(-c) → 링크(기본), 각 단계에서 중단 가능.
  • 최적화 레벨-O0(디버깅용) ~ -O3(최대 속도), -Os(코드 크기), -Og(디버깅 친화적 최적화), -Ofast(표준 무시 최대 성능).
  • -ffreestanding — 호스트 환경 없이 동작하는 독립 구현 모드. 리눅스 커널 빌드의 핵심 플래그.
  • 속성(attribute)__attribute__((noreturn)), __attribute__((packed)) 등 함수/변수/타입에 컴파일러 힌트 제공.
  • CPU 취약점 완화-mindirect-branch=thunk-extern(Spectre v2 Retpoline), -mharden-sls=all(SLS), -mbranch-protection=pac-ret+bti(ARM64 PAC/BTI).
  • GCC 플러그인 — 커널 보안 플러그인: structleak(스택 0 초기화), randstruct(구조체 순서 무작위화), stackleak(스택 독약값), latent_entropy(엔트로피 수집).
  • 머신 의존 옵션-mno-red-zone(레드존 금지), -mcmodel=kernel(커널 주소 모델), -mno-sse(FPU 금지) — 커널 필수 플래그.

단계별 이해

  1. 컴파일 4단계 구조 파악
    소스 코드가 실행 파일이 되기까지의 각 단계와 중간 산출물(*.i, *.s, *.o)을 이해합니다.
  2. 경고 옵션 적용
    -Wall -Wextra -Werror로 코드 품질을 높이고, 커널에서 사용되는 경고 억제 패턴을 익힙니다.
  3. 최적화 레벨 선택
    개발 단계는 -Og, 릴리스는 -O2, 커널은 기본적으로 -O2를 사용합니다.
  4. GCC 확장 습득
    커널 코드에서 광범위하게 쓰이는 __attribute__, likely/unlikely, 인라인 어셈블리 문법을 익힙니다.
  5. 크로스 컴파일 환경 설정
    CROSS_COMPILE 변수와 ARCH 환경 변수로 타깃 아키텍처별 빌드를 제어합니다.
  6. 머신 의존 옵션 이해
    커널은 -mno-red-zone, -mcmodel=kernel, -mno-sse 같은 아키텍처 전용 플래그가 필수입니다. 이를 빠뜨리면 런타임 오류가 발생합니다.
  7. CPU 취약점 완화 적용
    Spectre v2(Retpoline), SLS, ARM64 PAC/BTI 완화 옵션을 Kconfig와 연동하여 조건부 적용합니다.
  8. GCC 플러그인 활성화
    CONFIG_GCC_PLUGINS=y로 커널 보안 플러그인을 활성화합니다. 구조체 정보 누출, 스택 초기화, 무작위화를 컴파일러 수준에서 강화합니다.
참고 문헌: 이 문서는 Using the GNU Compiler Collection (GCC) Version 15.2.0 (Richard M. Stallman and the GCC Developer Community, Free Software Foundation) 공식 매뉴얼을 기반으로 합니다. 커널 빌드 관련 내용은 빌드 시스템어셈블리 문서와 연계됩니다.

GCC 개요 및 아키텍처

GCC는 "GNU Compiler Collection"의 약자로, GNU 프로젝트의 핵심 컴파일러 배포판입니다. 원래 "GNU C Compiler"를 의미했으나, C 이외의 언어를 지원하면서 "Collection"으로 확장되었습니다. GCC는 단일 언어에 종속되지 않는 언어 독립 공통 최적화 계층(middle-end)과 각 언어별 프론트엔드(front end), 타깃 아키텍처별 백엔드(back end)로 구성됩니다.

C Front End C++ Front End Fortran FE Ada / Go D / COBOL 기타 FE Middle End — GIMPLE / RTL 옵티마이저 (인라이닝, 루프 최적화, IPA, LTO, 벡터화 … 언어 독립 공통 최적화) x86-64 Back End ARM64 Back End RISC-V Back End MIPS Back End 기타 Back End 머신 코드 출력 (어셈블리 / 오브젝트 파일 / 실행 파일)

GCC가 호출될 때 기본적으로 전처리 → 컴파일 → 어셈블 → 링크의 4단계를 모두 실행합니다. -c 옵션으로 링크 이전 단계에서 멈추거나, -S로 어셈블리 출력, -E로 전처리 결과만 출력할 수 있습니다.

주요 전통 컴파일러 이름

언어GCC 드라이버비고
Cgcc기본 드라이버
C++g++C++ 라이브러리 자동 링크
COBOLgcobolGCC 15 신규 지원
AdagnatGNAT 컴파일러
FortrangfortranGNU Fortran
GogccgoGCC 4.7.1+
DgdcD 2.0 지원
Objective-Cgcc -lobjcGNU ObjC 런타임

지원 언어 및 표준

GCC는 각 언어에 대한 공식 표준을 따르며, GNU 확장이 포함된 버전도 지원합니다. -std= 옵션으로 표준 버전을 명시하고, -pedantic을 추가하면 표준에서 요구하는 진단을 모두 활성화합니다.

C 언어 표준

표준GCC 옵션GNU 확장 포함특징
C89 / C90-std=c90 / -ansi-std=gnu90ANSI C, 가장 오래된 표준
C94 / AMD1-std=iso9899:199409다이그래프 추가
C99-std=c99-std=gnu99가변 배열, inline, // 주석, stdint.h
C11-std=c11-std=gnu11원자 연산, 스레드, 정적 어설션
C17-std=c17-std=gnu17C11 수정판, __STDC_VERSION__ 차이
C23-std=c23-std=gnu23nullptr, _BitInt, constexpr, 2024년 제정 ISO/IEC 9899:2024
C2Y (개발 중)-std=c2y-std=gnu2y실험적·미완성 지원
기본값: C 언어 방언 옵션을 지정하지 않으면 GCC는 -std=gnu23을 기본으로 사용합니다 (GCC 15 기준). 커널 빌드에서는 -std=gnu11을 사용합니다.

C++ 언어 표준

표준GCC 옵션GNU 확장 포함특징
C++98 / C++03-std=c++98-std=gnu++98원조 ISO C++ 표준
C++11-std=c++11-std=gnu++11람다, auto, range-for, 이동 의미론
C++14-std=c++14-std=gnu++14일반 람다, 변수 템플릿
C++17-std=c++17-std=gnu++17구조적 바인딩, if constexpr, std::optional
C++20-std=c++20-std=gnu++20개념(Concepts), 코루틴, 모듈, ranges
C++23-std=c++23-std=gnu++23print, stacktrace, 추가 뷰, ISO/IEC 14882:2024
기본값: C++ 방언 옵션 없이 컴파일하면 GCC는 -std=gnu++17을 기본으로 사용합니다. 커널에서 Rust와 C++ 혼용은 금지되어 있으며, 커널 C++ 코드(있다면)는 제한적 서브셋만 허용합니다.

독립 구현 환경 (Freestanding Environment)

C 표준은 두 가지 구현 환경을 정의합니다. 리눅스 커널은 독립 구현 환경(freestanding environment)에서 동작합니다.

환경설명GCC 옵션예시
HostedOS와 C 표준 라이브러리 전체 사용 가능. int main() 엔트리 포인트.(기본값)일반 사용자 프로그램
FreestandingOS 없이 동작. <float.h>, <limits.h>, <stdarg.h>, <stddef.h>만 보장.-ffreestandingOS 커널, 부트로더, 임베디드 펌웨어
# 리눅스 커널 Makefile에서 GCC 독립 환경 설정
KBUILD_CFLAGS += -ffreestanding   # 독립 구현 모드
KBUILD_CFLAGS += -fno-builtin     # 내장 함수 비활성화
KBUILD_CFLAGS += -fno-stack-protector  # 기본 스택 보호 비활성화(커널이 직접 관리)
KBUILD_CFLAGS += -fno-PIE         # 위치 독립 실행 파일 비활성화

컴파일 단계 상세

GCC는 하나의 명령으로 네 단계를 순차적으로 실행합니다. 각 단계에서 중단하면 중간 산출물을 확인할 수 있습니다.

소스 파일 *.c / *.cc -E 전처리 #include/#define 확장 *.i 출력 -S 컴파일 최적화·코드 생성 *.s 출력 -c 어셈블 기계어 변환 *.o 출력 링크 심볼 해결 ELF 출력 각 단계에서 -E / -S / -c 로 중단 가능 | -o 로 출력 파일 지정
# 단계별 중단 예시
$ gcc -E foo.c -o foo.i       # 전처리만 (매크로 확장 결과 확인)
$ gcc -S foo.c -o foo.s       # 어셈블리 출력 (최적화 결과 확인)
$ gcc -c foo.c -o foo.o       # 오브젝트 파일 생성
$ gcc foo.o bar.o -o program  # 링크

# 한 번에 컴파일 + 링크
$ gcc foo.c bar.c -o program

# 특정 언어 표준으로 컴파일
$ gcc -std=gnu11 -Wall -O2 foo.c -o foo

# 크로스 컴파일 (ARM64 타깃)
$ aarch64-linux-gnu-gcc -march=armv8-a foo.c -o foo.arm64

경고 옵션 (-W...)

GCC는 방대한 경고 체계를 제공합니다. 커널 개발에서는 경고를 오류로 취급(-Werror)하여 코드 품질을 강제하는 경우가 많습니다.

핵심 경고 옵션

옵션설명커널 사용 여부
-Wall가장 유용한 경고들의 묶음. 대부분의 일반적 버그 탐지.✓ 항상 사용
-Wextra-Wall에 포함되지 않은 추가 경고 활성화.✓ 사용
-Werror모든 경고를 오류로 처리. CI/빌드 품질 강제.선택적 사용
-Wpedantic표준에서 요구하는 모든 진단 메시지 출력.부분 사용
-Wshadow변수 은닉(shadow) 경고.✓ 사용
-Wunused미사용 변수/함수/파라미터/라벨 경고.✓ 사용
-Wformat=2printf 형식 문자열 보안 강화 경고.✓ 사용
-Wmissing-prototypes프로토타입 없는 함수 정의 경고 (C only).✓ 사용
-Wimplicit-fallthroughswitch-case 폴스루(fallthrough) 경고.✓ 사용
-Wvla가변 길이 배열(VLA) 사용 경고.✓ 커널에서 금지
-Wstack-usage=N스택 사용량이 N 바이트를 초과하면 경고.✓ 사용 (커널: 1024)
-Wno-unused-parameter미사용 파라미터 경고 억제 (커널 콜백에 필요).✓ 사용
-Wstrict-prototypes엄격한 프로토타입 요구 경고.✓ 사용
# 리눅스 커널의 경고 옵션 일부 (scripts/Makefile.extrawarn 참고)
KBUILD_CFLAGS += -Wall
KBUILD_CFLAGS += -Wextra
KBUILD_CFLAGS += -Wno-unused-parameter
KBUILD_CFLAGS += -Wmissing-prototypes
KBUILD_CFLAGS += -Wstrict-prototypes
KBUILD_CFLAGS += -Wformat=2
KBUILD_CFLAGS += -Wimplicit-fallthrough=5
KBUILD_CFLAGS += -Wvla          # VLA 금지
KBUILD_CFLAGS += -Wframe-larger-than=2048  # 스택 프레임 제한

정적 분석기 (Static Analyzer)

GCC 10+에서 -fanalyzer 옵션으로 절차 간 정적 분석을 활성화할 수 있습니다. 메모리 누수, 이중 해제, 버퍼 오버플로, null 역참조 등을 컴파일 타임에 탐지합니다.

$ gcc -fanalyzer foo.c   # 정적 분석기 활성화

# 주요 분석기 경고 (Wno-analyzer-* 로 억제 가능)
# -Wanalyzer-null-dereference    : null 역참조
# -Wanalyzer-malloc-leak         : 메모리 누수
# -Wanalyzer-double-free         : 이중 해제
# -Wanalyzer-out-of-bounds       : 배열 경계 초과
# -Wanalyzer-use-after-free      : 해제 후 사용

최적화 옵션 (-O...)

GCC 최적화는 수백 가지 개별 패스(pass)로 구성됩니다. -O 레벨은 이 패스들의 묶음을 활성화하는 단축키입니다.

최적화 레벨 비교

레벨설명주요 활성화 패스용도
-O0최적화 없음 (기본값). 컴파일 속도 최대, 디버깅 쉬움.없음개발 초기 디버깅
-O1기본 최적화. 실행 속도/크기 모두 개선, 컴파일 시간 크게 증가 없음.DCE, CSE, 기본 인라이닝일반 개발
-O2권장 최적화. 대부분의 최적화 활성화. 루프 최적화, 인라이닝 강화.-O1 + 루프 최적화, 별칭 분석, 스케줄링리눅스 커널 기본값
-O3적극적 최적화. 벡터화, 함수 복제, 투기적 최적화 포함.-O2 + 벡터화, 함수 복제, loop unrollHPC, 성능 중요 코드
-Os코드 크기 최적화. 실행 속도보다 바이너리 크기를 줄임.-O2 중 크기 증가 패스 제외임베디드, 부트로더
-Og디버깅 친화적 최적화. 디버거 경험을 해치지 않는 수준의 최적화.-O1 서브셋 + 디버깅 정보 보존개발 단계 권장
-Ofast표준 준수 무시 최대 성능. -ffast-math 등 포함.-O3 + -ffast-math + 표준 위반 허용수치 계산 (주의 필요)
-Oz코드 크기 극한 최적화 (LLVM 유래, GCC도 지원).-Os보다 더 강화된 크기 최소화크기 극한 최소화

주요 개별 최적화 플래그

# 인라이닝 제어
-finline-functions          # 모든 함수 인라인 시도 (-O3 포함)
-finline-limit=200          # 인라인 크기 한계 (기본 600)
-fno-inline                  # 인라이닝 비활성화

# LTO (Link-Time Optimization)
-flto                        # 링크 타임 최적화 활성화
-flto=auto                   # 병렬 LTO (코어 수 자동 결정)
-flto-partition=one          # 단일 파티션 LTO

# 루프 최적화
-ftree-vectorize             # 자동 벡터화 (-O3 포함)
-funroll-loops               # 루프 언롤링
-floop-interchange           # 루프 교환으로 캐시 친화성 향상
-fprefetch-loop-arrays       # 루프 내 배열 프리페치

# 수학 최적화 (주의: 부동소수점 정확도 변경)
-ffast-math                  # IEEE 비준수, 공격적 수학 최적화
-fno-math-errno              # errno 없이 수학 함수 최적화

# 함수/데이터 섹션 분리 (링커가 미사용 제거)
-ffunction-sections          # 함수별 개별 섹션
-fdata-sections              # 데이터별 개별 섹션
# 링커 옵션 (ld): --gc-sections  # 미사용 섹션 제거

# PGO (Profile-Guided Optimization)
-fprofile-generate           # 1단계: 프로파일 데이터 수집
-fprofile-use                # 2단계: 프로파일 기반 최적화 적용

--param 세밀 조정

--param name=value 옵션으로 최적화 파라미터를 세밀하게 조정할 수 있습니다.

--param max-inline-insns-single=500  # 인라이닝 최대 명령어 수
--param max-unroll-times=8           # 루프 언롤 최대 횟수
--param large-function-insns=3000    # 큰 함수 기준

디버깅 옵션 (-g...)

디버깅 정보는 ELF 파일에 포함되며, GDB 같은 디버거가 소스 레벨 디버깅을 수행하는 데 사용됩니다. 커널에서는 -g로 빌드한 vmlinux를 KGDB나 crash 도구로 분석합니다.

옵션설명출력 형식
-g기본 디버깅 정보 생성 (OS 기본 형식).플랫폼 의존
-ggdbGDB에 최적화된 디버깅 정보 (DWARF 확장 포함).GDB 전용
-gdwarfDWARF 형식 디버깅 정보 (기본은 DWARF 5).DWARF
-gdwarf-4DWARF 4 버전 명시 (넓은 호환성).DWARF 4
-g1최소 디버깅 정보 (줄 번호만).DWARF 최소
-g3매크로 정의 포함 (가장 상세).DWARF 최대
-gsplit-dwarfDWARF 정보를 별도 .dwo 파일로 분리. 빌드 속도 향상.분산 DWARF
-gbtfBPF Type Format (BTF) 생성. eBPF 프로그램에 필요.BTF
-gctfCompact C Type Format (CTF) 생성.CTF
# 커널 디버깅 빌드 예시
$ make ARCH=x86_64 CONFIG_DEBUG_INFO=y vmlinux
# → -g -gdwarf-4 로 빌드된 vmlinux 생성

# BTF 활성화 (eBPF CO-RE 지원)
$ make CONFIG_DEBUG_INFO_BTF=y vmlinux

# 분할 DWARF (빌드 속도 향상)
$ make CONFIG_DEBUG_INFO_SPLIT=y vmlinux

# GDB로 커널 디버깅
$ gdb vmlinux
(gdb) target remote :1234   # KGDB / QEMU gdbserver
(gdb) bt                     # 스택 트레이스

보안 & 새니타이저 옵션

GCC는 런타임 오류 탐지를 위한 새니타이저(Sanitizer)와 스택 보호, CFI(Control Flow Integrity) 등 다양한 보안 강화 옵션을 제공합니다. 커널은 일부 새니타이저를 자체 통합한 KASAN, KCSAN, KMSAN을 제공합니다.

-fsanitize 옵션

옵션탐지 대상커널 대응
-fsanitize=address힙/스택 버퍼 오버플로, use-after-free, use-after-returnKASAN
-fsanitize=thread데이터 경쟁(data race)KCSAN
-fsanitize=memory미초기화 메모리 읽기KMSAN
-fsanitize=undefined정의되지 않은 동작(UB): 정수 오버플로, null 역참조 등UBSAN
-fsanitize=leak메모리 누수
-fsanitize=bounds배열 경계 초과 (정적 크기)UBSAN 일부
-fsanitize=cfi간접 호출 타입 불일치 (CFI)CFI (Clang)

스택 보호 옵션

# 스택 보호 (Stack Canary)
-fstack-protector            # 기본 스택 카나리 (취약한 함수만)
-fstack-protector-strong     # 강화된 스택 카나리 (권장)
-fstack-protector-all        # 모든 함수에 스택 카나리

# 스택 클래시 방어
-fstack-clash-protection     # 스택-힙 충돌 방어

# 제어 흐름 무결성
-fcf-protection=full         # Intel CET (IBT + SHSTK)
-fcf-protection=branch       # 간접 분기만 보호
-fcf-protection=return       # 반환 주소만 보호

# 포지션 독립 실행 (커널에서는 보통 비활성화)
-fPIC                        # 위치 독립 코드
-fPIE                        # 위치 독립 실행 파일
-fno-PIE                     # PIE 비활성화 (커널 코어)

# 하드닝 옵션 (-fharden-* GCC 14+)
-fharden-compares            # 비교 연산 하드닝
-fharden-conditional-branches  # 조건 분기 하드닝
-fhardcfr-check-exceptions   # CFR 예외 경로 검사

CPU 취약점 완화 옵션 (Spectre / Meltdown)

Spectre, Meltdown 등 마이크로아키텍처 투기 실행 취약점을 컴파일러 수준에서 완화합니다. 커널은 CONFIG_RETPOLINE 등 Kconfig 기호로 조건부 적용합니다.

옵션취약점설명
-mindirect-branch=thunkSpectre v2간접 분기를 retpoline thunk로 치환 (x86)
-mindirect-branch=thunk-externSpectre v2외부 thunk 사용 — 커널 기본값
-mfunction-return=thunk-externSpectre v2 / RET함수 반환을 외부 thunk로 치환
-mindirect-branch-registerSpectre v2간접 분기를 레지스터 기반으로만 허용
-mharden-sls=allSLS (Straight-Line Speculation)직선 추측 실행 완화 — GCC 12+, 커널 사용
-mharden-sls=retbrSLSret/br 명령어 뒤 INT3 삽입
-mbranch-protection=standardARM64 ROP/JOPPAC + BTI 동시 활성화 (ARMv8.3+/8.5+)
-mbranch-protection=pac-retARM64 ROP반환 주소 포인터 인증(PAC-RET)
-mbranch-protection=btiARM64 JOP분기 타깃 식별자(BTI)
-mspeculative-load-hardeningSpectre v1투기적 로드 하드닝 (주로 Clang)
-ftrivial-auto-var-init=zero정보 누출스택 변수 자동 0 초기화 (GCC 12+)
# 커널 Spectre v2 완화 (arch/x86/Makefile 발췌)
ifdef CONFIG_MITIGATION_RETPOLINE
  KBUILD_CFLAGS += -mindirect-branch=thunk-extern
  KBUILD_CFLAGS += -mfunction-return=thunk-extern
  KBUILD_CFLAGS += -mindirect-branch-register
endif

ifdef CONFIG_MITIGATION_SLS
  KBUILD_CFLAGS += -mharden-sls=all
endif

# ARM64 PAC/BTI (arch/arm64/Makefile 발췌)
ifdef CONFIG_ARM64_PTR_AUTH_KERNEL
  KBUILD_CFLAGS += -mbranch-protection=pac-ret
endif
ifdef CONFIG_ARM64_BTI_KERNEL
  KBUILD_CFLAGS += -mbranch-protection=pac-ret+bti
endif

# 스택 변수 자동 초기화 (정보 누출 방지)
ifdef CONFIG_INIT_STACK_ALL_ZERO
  KBUILD_CFLAGS += -ftrivial-auto-var-init=zero
endif
C 언어 자체 심화: C89~C23 표준 역사, __attribute__ 30+개 완전 참조, typeof/statement-expr/computed-goto, 커널 핵심 매크로(container_of/likely/READ_ONCE), sparse 어노테이션, 커널 타입 시스템은 C 언어 완전 가이드 & 커널 C 관용어를 참조하세요.

GCC 확장 기능

GCC는 C 표준을 넘어서는 강력한 확장 기능을 제공합니다. 리눅스 커널은 이 확장들을 광범위하게 활용합니다. -pedantic 없이 -std=gnu11로 컴파일하면 대부분 사용 가능합니다.

함수 속성 (__attribute__)

/* 함수 속성 — 커널에서 자주 쓰이는 것들 */

/* noreturn: 함수가 반환하지 않음을 컴파일러에 알림 */
static __attribute__((noreturn)) void panic(const char *fmt, ...);

/* noinline: 인라이닝 금지 */
static __attribute__((noinline)) int slow_path(int x);

/* always_inline: 항상 인라인 강제 */
static inline __attribute__((always_inline)) u32 fast_read(void *p);

/* cold: 자주 호출되지 않는 함수 (오류 경로) */
static __attribute__((cold)) void error_handler(int err);

/* hot: 자주 호출되는 핫 함수 */
static __attribute__((hot)) int fast_path(int x);

/* pure: 부작용 없는 순수 함수 (같은 입력 → 같은 출력) */
static __attribute__((pure)) int hash(const char *key, int len);

/* const: pure보다 강함 — 메모리 접근도 없음 */
static __attribute__((const)) int popcount(u32 n);

/* visibility: 심볼 가시성 제어 */
__attribute__((visibility("hidden"))) void internal_func(void);

/* constructor / destructor: main() 전/후 실행 */
__attribute__((constructor)) static void init_on_load(void);
__attribute__((destructor))  static void cleanup(void);

/* format: printf 스타일 형식 문자열 검사 */
extern int myprintf(const char *fmt, ...)
    __attribute__((format(printf, 1, 2)));

/* section: 특정 링커 섹션에 배치 */
__attribute__((section(".init.text"))) static int __init_func(void);

/* used: 미사용처럼 보여도 제거 금지 */
__attribute__((used)) static int debug_var = 0;

/* unused: 미사용 경고 억제 */
__attribute__((unused)) static int reserve;

변수 & 타입 속성

/* packed: 구조체 패딩 제거 */
struct __attribute__((packed)) ethernet_header {
    u8  dest[6];
    u8  src[6];
    u16 ethertype;
};

/* aligned: 정렬 요구 사항 지정 */
u8 dma_buf[4096] __attribute__((aligned(4096)));

/* may_alias: 엄격한 별칭 분석 제외 */
typedef unsigned int __attribute__((may_alias)) u32_alias;

/* deprecated: 사용 시 경고 */
extern void old_api(void) __attribute__((deprecated("use new_api() instead")));

/*커널 매크로로 래핑된 형태 */
__cold     /* = __attribute__((cold))    */
__hot      /* = __attribute__((hot))     */
__pure     /* = __attribute__((pure))    */
__packed   /* = __attribute__((packed))  */
__aligned(n) /* = __attribute__((aligned(n))) */

분기 예측 힌트

/* likely / unlikely — 분기 예측 힌트 */
#define likely(x)   __builtin_expect(!!(x), 1)
#define unlikely(x) __builtin_expect(!!(x), 0)

static int process_packet(struct sk_buff *skb)
{
    if (unlikely(skb == NULL))     /* 오류 경로: 거의 발생하지 않음 */
        return -EINVAL;

    if (likely(skb->len > 0))      /* 일반 경로: 거의 항상 참 */
        return do_process(skb);

    return -EMSGSIZE;
}

인라인 어셈블리 (Inline Assembly)

GCC의 인라인 어셈블리는 기본 asm확장 asm 두 가지 형식을 제공합니다. 커널에서는 주로 확장 asm을 사용합니다.

/* 확장 asm 문법:
   asm [volatile] ( 어셈블리 코드
                  : 출력 피연산자
                  : 입력 피연산자
                  : 클러버 목록 ); */

/* 예제 1: 원자적 add 연산 (x86-64) */
static inline void atomic_add(int i, atomic_t *v)
{
    asm volatile("lock addl %1, %0"
                 : "+m" (v->counter)   /* 출력: 메모리 읽기+쓰기 */
                 : "ir" (i)            /* 입력: 즉시값 또는 레지스터 */
                 : "cc");             /* 클러버: 조건 코드 레지스터 */
}

/* 예제 2: CPUID 명령어 */
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
                                   unsigned int *ecx, unsigned int *edx)
{
    asm volatile("cpuid"
                 : "=a" (*eax), "=b" (*ebx),
                   "=c" (*ecx), "=d" (*edx)
                 : "0" (*eax), "2" (*ecx));
}

/* 예제 3: 메모리 배리어 */
static inline void mb(void)
{
    asm volatile("mfence" ::: "memory");
    /* "memory" 클러버: 컴파일러 재배치 금지 */
}

/* 피연산자 제약 문자 요약 */
/* r = 범용 레지스터, m = 메모리, i = 즉시 정수 */
/* a/b/c/d = 특정 레지스터 (eax/ebx/ecx/edx) */
/* "+" = 읽기+쓰기, "=" = 쓰기 전용, "" = 읽기 전용 */

GCC 내장 함수 (Built-in Functions)

/* 비트 연산 */
int n = __builtin_popcount(0xABCD);  /* 설정된 비트 수 */
int z = __builtin_clz(0x1000);       /* 선행 0 비트 수 */
int t = __builtin_ctz(0x1000);       /* 후행 0 비트 수 */
int p = __builtin_parity(0xFF);      /* 패리티 (1의 개수 홀짝) */

/* 바이트 스왑 */
uint16_t s = __builtin_bswap16(val);  /* 16비트 바이트 스왑 */
uint32_t s = __builtin_bswap32(val);  /* 32비트 바이트 스왑 */
uint64_t s = __builtin_bswap64(val);  /* 64비트 바이트 스왑 */

/* 오버플로 체크 산술 (GCC 5+) */
int result;
if (__builtin_add_overflow(a, b, &result)) {
    pr_err("오버플로 발생!\n");
}
__builtin_mul_overflow(a, b, &result);
__builtin_sub_overflow(a, b, &result);

/* 예측 분기 */
__builtin_expect(expr, expected_val);   /* likely/unlikely 기반 */
__builtin_expect_with_probability(expr, val, 0.9);

/* 프레임 주소 / 반환 주소 */
void *fp = __builtin_frame_address(0);  /* 현재 스택 프레임 */
void *ra = __builtin_return_address(0); /* 반환 주소 */

/* 메모리 조작 */
__builtin_memcpy(dst, src, n);
__builtin_memset(ptr, val, n);

/* 컴파일 타임 상수 판별 */
if (__builtin_constant_p(x)) {
    /* x가 컴파일 타임 상수일 때 최적화 경로 */
}

/* 원자적 메모리 접근 (레거시 __sync 계열) */
__sync_fetch_and_add(&counter, 1);
__sync_bool_compare_and_swap(&ptr, old, new);

GCC C 언어 확장 — 커널 활용 예

/* 가변 길이 배열 (VLA) — 커널에서는 금지! */
/* void foo(int n) { int arr[n]; } */

/* 중첩 함수 (GCC 확장) */
void outer(void)
{
    int x = 10;
    void inner(void) { printf("%d\n", x); }  /* GCC 확장 */
    inner();
}

/* 구문 표현식 (statement expression) — 커널 max/min 매크로 기반 */
#define max(a, b) ({                \
    typeof(a) _a = (a);             \
    typeof(b) _b = (b);             \
    _a > _b ? _a : _b;              \
})

/* typeof — 타입 추론 */
typeof(x) tmp = x;     /* x와 같은 타입의 변수 선언 */

/* 복합 리터럴 */
struct point p = (struct point){ .x = 1, .y = 2 };

/* __auto_type (C++ auto 유사, GCC 4.9+) */
__auto_type result = some_function();

/* 레이블 값 (computed goto) — 커널 디스패치 테이블에서 활용 */
void *dispatch_table[] = { &&case_a, &&case_b, &&case_c };
goto *dispatch_table[op];
case_a: handle_a(); goto done;
case_b: handle_b(); goto done;
case_c: handle_c(); goto done;
done:;

고급 함수 속성

/* naked: 프롤로그/에필로그 없는 순수 어셈블리 함수 */
__attribute__((naked))
static void raw_entry(void)
{
    asm volatile(
        "xorl %eax, %eax\n"
        "retq\n"
    );
}

/* target: 함수별 ISA 타깃 지정 */
__attribute__((target("avx2,bmi2")))
static void process_avx2(float *data, int n) { /* AVX2 명령어 사용 가능 */ }

/* target_clones: 자동 함수 멀티버저닝 — 런타임에 CPU 감지 후 최적 버전 선택 */
__attribute__((target_clones("avx2", "sse4.2", "default")))
int compute_sum(const int *arr, int n)
{
    int sum = 0;
    for (int i = 0; i < n; i++) sum += arr[i];
    return sum;
}
/* GCC가 compute_sum.avx2 / compute_sum.sse4.2 / compute_sum 세 버전을 자동 생성 */

/* alias: 함수 별칭 (ABI 호환성 유지) */
int real_api(int x);
int __attribute__((alias("real_api"))) compat_api(int x);

/* ifunc: 런타임 리졸버 기반 간접 함수 */
static typeof(memcpy) *resolve_memcpy(void) {
    if (__builtin_cpu_supports("avx")) return memcpy_avx;
    return memcpy_generic;
}
typeof(memcpy) fast_copy __attribute__((ifunc("resolve_memcpy")));

/* optimize: 함수별 최적화 레벨 지정 */
__attribute__((optimize("O3,unroll-loops")))
static void hot_loop(int *data, int n) { /* 이 함수만 -O3 */ }

__attribute__((optimize("O0")))
static void debug_dump(void) { /* 이 함수만 최적화 없음 */ }

/* ms_abi / sysv_abi: Windows ↔ Linux ABI 전환 (x86-64) */
__attribute__((ms_abi))
typedef long (*win_fn_t)(void *arg);  /* Windows x64 호출 규약 */

__attribute__((sysv_abi))
typedef long (*linux_fn_t)(void *arg); /* System V AMD64 호출 규약 */

/* availability / visibility 심볼 제어 */
__attribute__((visibility("default")))  void exported_func(void); /* 공개 심볼 */
__attribute__((visibility("hidden")))   void internal_func(void); /* 내부 심볼 */
__attribute__((visibility("protected")))void readonly_func(void);  /* 재정의 불가 */

/* error / warning: 사용 시 컴파일 오류/경고 발생 */
extern void bad_function(void)
    __attribute__((error("bad_function을 호출할 수 없습니다")));

/* access: 포인터 인수의 접근 패턴 선언 (GCC 10+) */
extern void safe_copy(void *dst, const void *src, size_t n)
    __attribute__((access(write_only, 1, 3), access(read_only, 2, 3)));

추가 내장 함수 (__builtin_*)

/* 타입 호환성 검사 (컴파일 타임) */
if (__builtin_types_compatible_p(typeof(x), int)) {
    /* x의 타입이 int인 경우에만 실행 (데드 코드 제거) */
}

/* 컴파일 타임 조건부 표현식 */
#define safe_abs(x) \
    __builtin_choose_expr(__builtin_types_compatible_p(typeof(x), long), \
                          labs(x), abs(x))

/* CPU 기능 런타임 확인 */
__builtin_cpu_init();                        /* CPU 기능 감지 초기화 (보통 자동) */
if (__builtin_cpu_supports("avx2")) { ... } /* AVX2 지원 여부 */
if (__builtin_cpu_supports("sha"))  { ... } /* SHA 명령어 지원 */
if (__builtin_cpu_is("intel"))      { ... } /* Intel CPU 여부 */

/* 도달 불가 표시 / 강제 트랩 */
__builtin_unreachable();   /* 이 지점 도달 불가 → 최적화 힌트 + 경고 제거 */
__builtin_trap();          /* 즉시 비정상 종료 (UD2 / BRK 명령어) */

/* 오브젝트 크기 (버퍼 오버플로 방어) */
size_t sz = __builtin_object_size(ptr, 0);         /* 정적 상한 크기 */
size_t dz = __builtin_dynamic_object_size(ptr, 0); /* 동적 크기 (GCC 12+) */

/* 원자 연산 (C11 _Atomic 기반 권장; 하위 호환용 __atomic_* 계열) */
__atomic_fetch_add(&counter, 1, __ATOMIC_SEQ_CST);
__atomic_compare_exchange_n(&ptr, &old, new_val,
                              0, __ATOMIC_SEQ_CST, __ATOMIC_RELAXED);
__atomic_load_n(&flag, __ATOMIC_ACQUIRE);
__atomic_store_n(&flag, val, __ATOMIC_RELEASE);

/* x86 전용 내장 함수 */
__builtin_ia32_pause();     /* PAUSE 명령 (spinlock 효율화) */
__builtin_ia32_lfence();    /* LFENCE — 로드 배리어 */
__builtin_ia32_mfence();    /* MFENCE — 전체 배리어 */
__builtin_ia32_sfence();    /* SFENCE — 스토어 배리어 */
__builtin_ia32_rdtsc();     /* RDTSC — 타임스탬프 카운터 읽기 */

크로스 컴파일

크로스 컴파일이란 현재 실행 중인 플랫폼(호스트)과 다른 플랫폼(타깃)을 위한 코드를 생성하는 것입니다. 리눅스 커널은 x86-64 호스트에서 ARM64, RISC-V, MIPS 등 다양한 타깃으로 크로스 컴파일합니다.

크로스 컴파일러 툴체인 명명 규칙

# 형식: <arch>-<vendor>-<os>-<abi>-gcc
aarch64-linux-gnu-gcc       # ARM64 리눅스 (glibc)
arm-linux-gnueabihf-gcc     # ARM 32bit 하드 부동소수점
riscv64-linux-gnu-gcc       # RISC-V 64bit 리눅스
mips-linux-gnu-gcc          # MIPS 리눅스
powerpc64le-linux-gnu-gcc   # PowerPC 64bit LE 리눅스
x86_64-w64-mingw32-gcc      # x86-64 Windows (MinGW)

리눅스 커널 크로스 컴파일

# ARM64 커널 빌드
$ ARCH=arm64 \
  CROSS_COMPILE=aarch64-linux-gnu- \
  make defconfig
$ ARCH=arm64 \
  CROSS_COMPILE=aarch64-linux-gnu- \
  make -j$(nproc) Image.gz modules dtbs

# RISC-V 64bit 커널 빌드
$ ARCH=riscv \
  CROSS_COMPILE=riscv64-linux-gnu- \
  make defconfig
$ ARCH=riscv \
  CROSS_COMPILE=riscv64-linux-gnu- \
  make -j$(nproc)

# Clang 크로스 컴파일 (LLVM 툴체인)
$ make LLVM=1 ARCH=arm64 defconfig
$ make LLVM=1 ARCH=arm64 -j$(nproc)

# CROSS_COMPILE 변수가 컴파일러 prefix로 사용됨
# aarch64-linux-gnu-gcc, aarch64-linux-gnu-ld, aarch64-linux-gnu-objcopy ...

아키텍처별 GCC 주요 플래그

아키텍처주요 옵션설명
x86-64-march=x86-64-v3AVX2 포함 x86-64 v3 마이크로아키텍처
x86-64-mtune=native현재 CPU 특성에 맞게 튜닝
ARM64-march=armv8-aARMv8-A 기본 아키텍처
ARM64-march=armv8.5-a+memtagMemory Tagging Extension(MTE) 활성화
RISC-V-march=rv64gcRISC-V 64bit general + compressed
ARM 32bit-mfpu=neon -mfloat-abi=hardNEON SIMD + 하드 부동소수점 ABI

-march vs -mtune vs -mcpu

옵션역할예시
-march=arch명령어 집합(ISA) 수준 지정. 해당 ISA 명령어를 생성 가능.-march=armv8.2-a
-mtune=cpu특정 CPU를 위한 코드 스케줄링 최적화. ISA는 변경 안 함.-mtune=cortex-a72
-mcpu=cpu-march-mtune을 동시에 설정. 특정 CPU 타깃.-mcpu=cortex-a72

리눅스 커널에서의 GCC 활용

리눅스 커널은 GCC의 다양한 기능을 적극적으로 활용합니다. 커널 내부에는 GCC 버전에 따라 조건적으로 기능을 사용하는 코드가 많습니다.

커널 빌드의 핵심 GCC 플래그

# 커널 기본 CFLAGS (Makefile 참고)
-std=gnu11             # GNU C11 표준
-ffreestanding         # 독립 구현 환경 (OS 없이 동작)
-fno-builtin           # GCC 내장 함수 비활성화
-fno-stack-protector   # 커널이 스택 보호를 직접 관리
-fno-strict-aliasing   # 포인터 별칭 최적화 비활성화 (포인터 캐스팅 허용)
-fno-common            # 공통 심볼 허용 안 함 (중복 전역 변수 오류)
-fno-asynchronous-unwind-tables  # async unwind 테이블 제거 (크기 절감)
-fno-delete-null-pointer-checks  # null 포인터 역참조 코드 삭제 방지
-O2                    # 최적화 레벨 2 (기본)
-Wall -Wextra          # 경고 활성화
-pipe                  # 임시 파일 대신 파이프 사용 (빌드 속도 향상)
-Werror=implicit-function-declaration  # 함수 선언 누락을 오류로

__init / __exit 섹션

/* __init: 초기화 완료 후 메모리 해제 가능한 코드 */
static int __init my_driver_init(void)
{
    /* 이 코드는 초기화 후 커널이 메모리 해제 가능 */
    return 0;
}

/* __exit: 모듈이 내장될 때 제거되는 코드 */
static void __exit my_driver_exit(void)
{
    /* built-in 커널 모듈에서는 이 코드 없음 */
}

/* __initdata: 초기화 후 제거되는 데이터 */
static const char __initconst version_str[] = "1.0";

/* section 확인 (GCC가 .init.text, .exit.text 섹션에 배치) */
#define __init     __attribute__((section(".init.text"))) __cold
#define __exit     __attribute__((section(".exit.text"))) __cold
#define __initdata __attribute__((section(".init.data")))

커널에서 자주 쓰이는 GCC 매크로 패턴

/* BUILD_BUG_ON: 컴파일 타임 어설션 */
BUILD_BUG_ON(sizeof(struct my_struct) != 64);

/* IS_ENABLED: Kconfig 심볼 컴파일 타임 확인 */
if (IS_ENABLED(CONFIG_SOME_FEATURE)) {
    /* 데드 코드 제거(dead code elimination)로 최적화 */
}

/* ARRAY_SIZE: 배열 크기 (sizeof 기반) */
int arr[10];
pr_info("size = %zu\n", ARRAY_SIZE(arr));  /* = 10 */

/* container_of: 멤버 포인터로 구조체 포인터 획득 */
struct work_struct *work = ...;
struct my_data *data = container_of(work, struct my_data, work);
/* 내부: ((struct my_data *)((char *)(ptr) - offsetof(struct my_data, work))) */

/* READ_ONCE / WRITE_ONCE: volatile 메모리 접근 (컴파일러 재배치 방지) */
int val = READ_ONCE(shared_var);
WRITE_ONCE(shared_var, new_val);

/* data_race: 데이터 경쟁이 의도적임을 표시 (KCSAN 억제) */
int snapshot = data_race(counter);

/* 커널 GCC 버전 체크 */
#if GCC_VERSION >= 140000
    /* GCC 14+ 전용 코드 */
#endif

컴파일러 최적화와 커널 이슈

주의: -fno-strict-aliasing 필수
커널은 void *char *를 다양한 타입 포인터로 캐스팅하는 경우가 많습니다. GCC의 엄격한 별칭 규칙(strict aliasing)을 적용하면 이런 코드를 잘못 최적화할 수 있습니다. 따라서 커널 빌드는 항상 -fno-strict-aliasing을 사용합니다.
주의: -fno-delete-null-pointer-checks 필수
GCC는 null 포인터 역참조 이후의 코드를 "도달 불가"로 간주하고 삭제할 수 있습니다. 커널에서는 의도적으로 null 포인터를 확인한 후 처리하는 패턴이 있어, 이 최적화를 비활성화해야 합니다.

커널 보안 강화 매크로 패턴

/* __randomize_layout: 구조체 필드 순서 무작위화 (CONFIG_GCC_PLUGIN_RANDSTRUCT) */
struct task_struct {
    /* ... 필드들 ... */
} __randomize_layout;

/* __no_randomize_layout: 무작위화 제외 (ABI 고정 구조체) */
struct pt_regs {
    u64 r15, r14, r13; /* ABI에서 순서 고정 */
    /* ... */
} __no_randomize_layout;

/* OPTIMIZER_HIDE_VAR: 컴파일러 최적화로부터 변수 숨김 */
#define OPTIMIZER_HIDE_VAR(var) \
    asm("" : "+r" (var))       /* 최적화기가 값을 추적 불가 */
OPTIMIZER_HIDE_VAR(secret_key);

/* barrier_data: 포인터 관련 컴파일러 최적화 방지 */
#define barrier_data(ptr) \
    asm volatile("" : "+r"(ptr) :: "memory")
barrier_data(buf);  /* buf 포인터 escape → 이후 접근 최적화 금지 */

/* noinstr: 어떠한 instrumentation도 금지 (인터럽트 비허용 임계 구간) */
noinstr void do_nmi(struct pt_regs *regs, long error_code)
{
    /* KASAN/KCSAN/ftrace/lockdep 등 모든 계측 금지 */
}

/* instrumentation_begin/end: 임계 구간 내 특정 지점에서만 허용 */
noinstr void exception_entry(struct pt_regs *regs)
{
    instrumentation_begin();
    do_some_work(regs);   /* 여기서는 계측 허용 */
    instrumentation_end();
}

/* 새니타이저 선택적 제외 */
__no_sanitize_address         /* = __attribute__((no_sanitize_address)) */
__no_kcsan                     /* KCSAN 제외 */
__no_sanitize_undefined        /* UBSAN 제외 */

/* pragma를 이용한 경고 억제 */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
    pr_info(dynamic_fmt_string, arg);
#pragma GCC diagnostic pop

/* GCC_VERSION 기반 조건부 기능 사용 */
#include <linux/compiler-version.h>
#if GCC_VERSION >= 150000  /* GCC 15.0+ */
    /* GCC 15 신규 기능: 개선된 -fanalyzer, gcobol 지원 등 */
#elif GCC_VERSION >= 140000  /* GCC 14.0+ */
    /* -fharden-compares, -fharden-conditional-branches */
#endif

LTO를 활성화하면 링크 타임에 전체 커널 소스를 한 번에 최적화합니다. 불필요한 심볼 제거, 크로스 파일 인라이닝, IPA(Interprocess Analysis)가 가능해집니다.

# 커널 LTO 활성화 (GCC WHOPR 방식)
# CONFIG_LTO_GCC=y → KBUILD_CFLAGS += -flto -fno-fat-lto-objects
$ make CONFIG_LTO_GCC=y -j$(nproc) vmlinux

# LTO 빌드 파이프라인:
# 1. gcc -flto -c foo.c → foo.o (GIMPLE IR + 심볼 메타 포함)
# 2. ld --plugin=liblto_plugin.so *.o → 전체 GIMPLE 최적화 (IPA)
# 3. 최종 머신 코드 생성 (크로스 파일 인라이닝, 전역 DCE)

# LTO 병렬 처리 옵션
-flto=auto    # 사용 가능한 코어 수 자동 결정 (권장)
-flto=8       # 8개 병렬 파티션 고정
-flto=jobserver # make 잡 서버 활용

# LTO 디버그 (IR 덤프)
$ lto-dump -list foo.o     # LTO 오브젝트의 심볼 목록
$ lto-dump -dump-body foo.o # GIMPLE IR 덤프

# 링크 맵으로 심볼 배치 확인
$ make LDFLAGS+="-Wl,-Map=vmlinux.map" vmlinux
$ grep __init_begin vmlinux.map

GCC 최소 버전 요구사항

리눅스 커널은 커널 버전마다 최소 GCC 버전을 요구합니다. 공식 기준은 Documentation/process/changes.rst와 커널 소스 내 scripts/min-tool-version.sh입니다.

커널 버전최소 GCC비고
4.0 ~ 4.83.2x86 레거시 지원, 기본 C99/GCC 확장
4.9 ~ 5.14.6-std=gnu89에서 -std=gnu11으로 전환 시작
5.2 ~ 5.144.9C11 _Generic·_Static_assert 활용, -std=gnu11 기본
5.15 ~ 현재5.1scripts/cc-version.sh가 빌드 전에 체크; scripts/min-tool-version.sh gcc5.1.0
/* include/linux/compiler-gcc.h — GCC_VERSION 매크로 정의 */
#define GCC_VERSION (__GNUC__ * 10000 \
                   + __GNUC_MINOR__ * 100 \
                   + __GNUC_PATCHLEVEL__)
/* 예: GCC 12.3.0 → GCC_VERSION = 120300 */

/* 버전 조건부 기능 선택 패턴 (커널 코드 내부) */
#if GCC_VERSION >= 120000   /* GCC 12.0+ */
# define __counted_by(m)  __attribute__((__counted_by__(m)))
#else
# define __counted_by(m)  /* noop — older GCC */
#endif
최소 버전 강제 방식: 현재 커널(5.15+)은 compiler-gcc.h#error가 아니라 빌드 시작 시 scripts/cc-version.shscripts/min-tool-version.sh를 호출해 버전을 확인합니다. 기준 미달이면 *** C compiler is too old. 메시지와 함께 make가 즉시 중단됩니다.
# 현재 시스템 GCC 버전 확인
$ gcc --version
gcc (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0

# 커널 소스에서 요구하는 최소 버전 확인
$ ./scripts/min-tool-version.sh gcc
5.1.0

# GCC_VERSION 구성 확인 (12*10000 + 3*100 + 0 = 120300)
$ gcc -dM -E - < /dev/null | grep -E '__GNUC__'
#define __GNUC__            12
#define __GNUC_MINOR__       3
#define __GNUC_PATCHLEVEL__  0

빌드 시 GCC 플래그 추가 (KCFLAGS / KCPPFLAGS)

KCFLAGS는 커널 빌드 시 전체 C 파일에 GCC 플래그를 외부에서 추가하는 변수입니다. KBUILD_CFLAGSMakefile 내부 기본 플래그라면, KCFLAGS는 이를 덮어쓰지 않고 뒤에 이어 붙여 기존 플래그를 재정의할 수 있습니다.

변수대상용도
KCFLAGS전체 C 파일최적화·디버그 플래그 추가 (KBUILD_CFLAGS 뒤에 추가)
KCPPFLAGS전처리 단계-D 매크로 정의, 전처리기 전용 옵션
KBUILD_CFLAGS전체 C 파일Makefile 내부 기본 플래그 (직접 수정 비권장)
# 특정 서브시스템 디버그 빌드: 최적화 해제 + 프레임 포인터 보존
$ make KCFLAGS="-O0 -fno-omit-frame-pointer" drivers/net/

# 전처리 매크로 추가 (CONFIG 변수가 아닌 임시 디버그용)
$ make KCPPFLAGS="-DDEBUG_SPINLOCK=1" kernel/locking/

# 복합 사용: 전처리 + 컴파일 플래그 동시 지정
$ make KCPPFLAGS="-DDEBUG_LOCK=1" \
       KCFLAGS="-O1 -fno-inline" \
       -j$(nproc) vmlinux

# V=1로 실제 gcc 커맨드라인 확인 (KCFLAGS 적용 위치 확인)
$ make V=1 init/main.o
# 출력 예: gcc [KBUILD_CFLAGS...] [KCFLAGS...] -c init/main.c -o init/main.o
#   → KCFLAGS는 KBUILD_CFLAGS 뒤에 위치하여 앞의 플래그 재정의 가능

파일별 컴파일 플래그 (per-file CFLAGS)

서브시스템 Makefile에서 특정 파일에만 GCC 플래그를 추가하거나 제거할 수 있습니다. 성능 크리티컬 파일과 디버그 대상 파일을 개별 처리할 때 유용합니다.

# drivers/example/Makefile — per-file CFLAGS 활용 예

obj-$(CONFIG_EXAMPLE) := example.o

# 특정 파일에 플래그 추가 (최적화 해제 + 디버그 심볼)
CFLAGS_example_main.o        := -O0 -g -DDEBUG_EXAMPLE

# 다른 파일에 고최적화 적용
CFLAGS_example_hot.o         := -O3 -funroll-loops

# 특정 플래그 제거 후 교체 (-O2 제거 → -O0 대체)
CFLAGS_REMOVE_example_slow.o := -O2
CFLAGS_example_slow.o        := -O0

# 서브디렉토리 전체에 인클루드 경로 추가
ccflags-y += -I$(src)/include
ccflags-y += -I$(srctree)/include/linux

# 어셈블리 파일별 플래그
AFLAGS_entry.o := -D__ASSEMBLY__

# 실제 커널 사례: ftrace 프로파일링 제외 (arch/x86/kernel/Makefile)
CFLAGS_REMOVE_cpufeature.o = -pg   # -pg는 mcount 호출 삽입 → 재귀 방지
CFLAGS_REMOVE_cpu.o        = -pg
플래그 적용 순서: $(KBUILD_CFLAGS) $(ccflags-y) $(CFLAGS_foo.o) 순으로 적용됩니다. CFLAGS_REMOVE_foo.o에 지정한 플래그는 이 전체 목록에서 제거됩니다. 따라서 ccflags-y로 추가된 플래그도 CFLAGS_REMOVE로 제거할 수 있습니다.

스택 사용량 분석 (-fstack-usage)

-fstack-usage 플래그를 사용하면 GCC가 각 함수의 스택 프레임 크기를 .su 파일로 출력합니다. 커널은 CONFIG_FRAME_WARN으로 과도한 스택 사용 함수를 빌드 시 감지합니다.

# -fstack-usage로 .su 파일 생성
$ gcc -fstack-usage -O2 -c foo.c
$ cat foo.su
foo.c:42:5:process_packet	224	static
foo.c:87:6:handle_error	48	static
#   형식: 파일:줄:열:함수명  TAB  바이트수  TAB  [static|dynamic|bounded]
#   static  : 컴파일 타임에 스택 크기 확정 (VLA 없음)
#   dynamic : VLA·alloca 등 런타임에 크기 결정
#   bounded : 동적이지만 컴파일러가 상한을 파악

# 커널 빌드에 적용: 특정 서브시스템 스택 분석
$ make KCFLAGS="-fstack-usage" drivers/net/
$ find drivers/net/ -name '*.su' -exec sort -t'	' -k2 -rn {} + | head -20
# 스택을 가장 많이 사용하는 함수 상위 20개 출력
항목기본값설명
CONFIG_FRAME_WARN2048 (x86-64) / 1024 (arm64)스택 프레임 경고 임계값(바이트). 초과 시 빌드 경고 발생
-Wframe-larger-than=N자동 설정CONFIG_FRAME_WARN 값으로 Kbuild가 자동 적용
scripts/checkstack.plobjdump 출력 파싱으로 스택 크기 추출 (LTO/최적화 결과 반영)
# checkstack.pl로 커널 전체 스택 사용 상위 함수 출력
$ objdump -d vmlinux | scripts/checkstack.pl x86_64 | head -20
4096 process_one_work [vmlinux]:
3840 ext4_find_entry [vmlinux]:
...

# 특정 임계값 초과 함수만 필터 (1024 바이트 초과)
$ objdump -d vmlinux | scripts/checkstack.pl x86_64 \
  | awk 'NR>1 && $1+0 > 1024'

# CONFIG_FRAME_WARN 경고 빌드 출력 예시
# drivers/gpu/drm/amd/display/dc/core/dc.c:1234:5:
# warning: the frame size of 2304 bytes is larger than 2048 bytes [-Wframe-larger-than=]
VLA(Variable Length Array) 금지: 커널은 -Wvla 플래그로 VLA 사용을 금지합니다. VLA는 스택 크기를 컴파일 타임에 예측할 수 없어 -fstack-usage에서 dynamic으로 표시되며 스택 오버플로 취약점의 원인이 됩니다. 가변 길이가 필요하면 kmalloc()이나 고정 크기 배열을 사용하세요.

gcov 코드 커버리지 도구

gcov는 GCC에 포함된 테스트 커버리지 프로그램입니다. 컴파일 시 계측(instrumentation) 코드를 삽입하여 실행 중 각 코드 줄이 몇 번 실행되었는지 기록합니다. 리눅스 커널은 CONFIG_GCOV_KERNEL로 gcov를 지원합니다.

gcov 사용 흐름

소스 코드 foo.c --coverage 계측된 바이너리 foo.gcno 생성 실행 커버리지 데이터 foo.gcda 생성 gcov 커버리지 리포트 foo.c.gcov .gcno (노트 파일) + .gcda (데이터 파일) → gcov 분석
# 1단계: 커버리지 계측 빌드
$ gcc --coverage -O0 foo.c -o foo   # --coverage = -fprofile-arcs -ftest-coverage
# foo.gcno 파일 생성 (노트 파일)

# 2단계: 프로그램 실행 (커버리지 데이터 수집)
$ ./foo
# foo.gcda 파일 생성 (실행 통계)

# 3단계: gcov 리포트 생성
$ gcov foo.c
# foo.c.gcov 파일 생성 (줄별 실행 횟수 주석 포함)

# lcov / genhtml로 HTML 리포트 생성
$ lcov --capture --directory . --output-file coverage.info
$ genhtml coverage.info --output-directory out/
$ xdg-open out/index.html

# gcov-tool: 오프라인 .gcda 처리 도구
$ gcov-tool merge -o merged dir1 dir2

# gcov-dump: .gcno / .gcda 덤프
$ gcov-dump foo.gcno

커널 gcov 활성화

# 커널 설정
CONFIG_GCOV_KERNEL=y
CONFIG_GCOV_FORMAT_AUTODETECT=y

# 개별 파일에 gcov 활성화
# drivers/mydriver/Makefile
GCOV_PROFILE_myfile.o := y
GCOV_PROFILE          := y   # 디렉터리 전체

# debugfs로 커버리지 데이터 접근
$ ls /sys/kernel/debug/gcov/
$ find /sys/kernel/debug/gcov -name "*.gcda" | xargs cp -t /tmp/gcov/
$ gcov -o /path/to/kernel/build /tmp/gcov/*.gcda

lto-dump: LTO 오브젝트 파일 덤프

# LTO로 빌드된 오브젝트 파일 분석
$ gcc -flto -c foo.c -o foo.o
$ lto-dump -list foo.o        # 심볼 목록
$ lto-dump -dump-body foo.o   # GIMPLE IR 덤프

전처리기 옵션

전처리기(cpp)는 #include, #define, 조건부 컴파일 등을 처리합니다. GCC는 전처리기 옵션을 직접 전달할 수 있습니다.

옵션설명예시
-D macro매크로 정의-DDEBUG -DVERSION=3
-U macro매크로 정의 해제-UNDEBUG
-I dir헤더 파일 검색 경로 추가-I./include -I/usr/local/include
-include file소스 시작 전 파일 자동 포함-include config.h
-MMakefile 의존성 규칙 출력빌드 시스템 통합
-MM시스템 헤더 제외 의존성 출력Kbuild에서 활용
-MD의존성 파일 생성 + 컴파일 계속*.d 파일 생성
-MMD시스템 헤더 제외 의존성 파일 생성Kbuild 기본 사용
-E전처리 결과만 출력 (컴파일 안 함)매크로 확장 디버깅
-fdebug-cpp전처리기 디버그 정보 출력매크로 추적
-fmacro-prefix-map=old=new매크로 내 파일 경로 재맵핑재현 가능 빌드
# 의존성 파일 생성 예시 (Kbuild 방식)
$ gcc -MMD -MP -c foo.c -o foo.o
# foo.d 생성:
# foo.o: foo.c foo.h bar.h
# foo.h:
# bar.h:

# 재현 가능 빌드를 위한 경로 정규화
$ gcc -fmacro-prefix-map=/home/user/linux=. \
       -ffile-prefix-map=/home/user/linux=. \
       -c foo.c -o foo.o

GCC는 컴파일 후 링커(ld)를 내부적으로 호출합니다. -Wl,옵션으로 링커 옵션을 직접 전달하고, -l/-L로 라이브러리를 지정합니다. 커널은 arch/*/kernel/vmlinux.lds.S 링커 스크립트로 섹션 배치를 완전히 제어합니다.

기본 링크 옵션

옵션설명비고
-l name라이브러리 링크 (libname.a / libname.so 자동 탐색)-lpthread -lm
-L dir라이브러리 검색 경로 추가-L/usr/local/lib
-static모든 라이브러리를 정적 링크 (.a 파일만 사용)임베디드, 독립 실행 파일
-shared공유 라이브러리(.so) 생성gcc -shared -fPIC -o libfoo.so
-nostdlib표준 라이브러리·시작 파일(crt*.o) 링크 안 함✓ 커널, 부트로더
-nodefaultlibslibgcc 등 기본 라이브러리 링크 안 함맞춤 런타임
-nostartfilescrt0.o 등 시작 파일 링크 안 함커스텀 엔트리 포인트
-rdynamic동적 심볼 테이블을 모두 내보냄 (dlopen 플러그인)백트레이스/플러그인 시스템
-T script링커 스크립트 명시적 지정커널: arch/x86/kernel/vmlinux.lds
-e symbol엔트리 포인트 심볼 지정커널: -e startup_64
-Map=file링크 맵 파일 생성 (심볼 배치 확인)-Wl,-Map=vmlinux.map
-r재배치 가능 오브젝트 생성 (partial link)커널 built-in.a 빌드

-Wl 링커 옵션 전달

# 미사용 섹션 제거 (크기 최적화) — -ffunction-sections -fdata-sections와 쌍
-Wl,--gc-sections

# 링크 맵 생성 (심볼/섹션 배치 확인)
-Wl,-Map=vmlinux.map

# soname 설정 (공유 라이브러리 버전)
-Wl,-soname,libfoo.so.1

# rpath — 런타임 라이브러리 검색 경로
-Wl,-rpath,/opt/myapp/lib

# 심볼 버저닝 (ABI 호환 관리)
-Wl,--version-script=version.map

# 정적 라이브러리 전체 포함 (심볼 체인 문제 해결)
-Wl,--whole-archive libfoo.a -Wl,--no-whole-archive

# 미정의 심볼 오류 강제 (커널 빌드 품질)
-Wl,--no-undefined

# 빌드 ID 생성 (바이너리 식별자)
-Wl,--build-id=sha1

# PIE 링크
-Wl,-pie

# 동적 링커 명시적 지정
-Wl,--dynamic-linker=/lib64/ld-linux-x86-64.so.2

# 링크 순서에 무관한 정적 라이브러리 처리
-Wl,--start-group libA.a libB.a -Wl,--end-group

커널 링커 스크립트 구조

/* arch/x86/kernel/vmlinux.lds.S 발췌 (실제는 전처리 후 vmlinux.lds) */
OUTPUT_FORMAT("elf64-x86-64")
ENTRY(startup_64)

SECTIONS {
    . = __START_KERNEL;          /* 커널 가상 주소 시작 */

    .text : {
        _text = .;
        *(.text.hot .text.hot.*)  /* hot 코드 (캐시 친화) */
        *(.text .text.*)          /* 일반 코드 */
        *(.text.cold .text.cold.*)/* cold 코드 (오류 경로) */
        _etext = .;
    }

    .init.text : AT(ADDR(.init.text) - LOAD_OFFSET) {
        __init_begin = .;
        *(.init.text .init.text.*) /* __init 함수 → 초기화 후 해제 */
        __init_end = .;
    }

    .data : {
        _data = .;
        *(.data .data.*)
        _edata = .;
    }

    .bss : {
        __bss_start = .;
        *(.bss .bss.*)
        __bss_stop = .;
    }
}

머신 의존 옵션

GCC는 타깃 아키텍처별로 세밀한 옵션을 제공합니다. 커널 빌드에서는 -mno-red-zone, -mcmodel=kernel, -mno-sse 같은 옵션이 정확성과 보안에 직결됩니다.

x86 / x86-64 주요 옵션

옵션설명커널 활용
-m32 / -m6432bit / 64bit 코드 생성 강제
-mno-red-zonex86-64 ABI의 128바이트 레드존 사용 금지✓ 필수 — 인터럽트/NMI 핸들러 안전
-mcmodel=kernel커널 주소 공간 모델 (최상위 2GB 내 접근)✓ x86-64 커널 필수
-mcmodel=large모든 주소를 64bit 절대 주소로 접근BPF JIT 등 특수 목적
-mno-mmxMMX 명령어 사용 금지✓ 커널 코어
-mno-sse / -mno-sse2SSE/SSE2 사용 금지 (FPU 상태 저장 불필요)✓ 커널 코어
-mno-avxAVX 사용 금지✓ 커널 코어
-maccumulate-outgoing-args스택 아규먼트를 미리 할당 (프레임 크기 일정)✓ 커널
-mskip-rax-setup가변인수 함수 진입 시 %al 초기화 생략✓ 커널 (System V ABI 불필요)
-mpreferred-stack-boundary=3스택 8바이트 정렬 강제 (2³=8)✓ 일부 아키텍처
-march=x86-64-v3AVX2+BMI+F16C 포함 x86-64 v3 마이크로아키텍처최신 서버 최적화
-mtune=native현재 실행 CPU 특성에 맞게 스케줄링 최적화호스트 빌드
-masm=intelIntel 문법으로 어셈블리 출력 (-S와 함께)어셈블리 분석

ARM64 주요 옵션

옵션설명비고
-mgeneral-regs-only범용 레지스터만 사용 (FP/SIMD 레지스터 금지)✓ ARM64 커널 기본
-mabi=lp64LP64 ABI (long + pointer = 64bit) — Linux 기본
-mabi=ilp32ILP32 ABI (int+long+pointer = 32bit)특수 목적
-mbranch-protection=standardPAC + BTI 동시 활성화 (ARMv8.3+/ARMv8.5+)커널 CFI 강화
-mbranch-protection=pac-ret반환 주소 포인터 인증(PAC-RET)만 활성화ROP 방어
-moutline-atomics원자 연산을 런타임에 LSE/LL-SC 방식 선택이식성 (커널 기본)
-mno-outline-atomics원자 연산 인라인 고정 (LSE 또는 LL-SC)성능 최적화
-mfix-cortex-a53-835769Cortex-A53 에라타 835769 완화RaspberryPi 3 등
-mfix-cortex-a53-843419Cortex-A53 에라타 843419 완화
# x86-64 커널 핵심 머신 옵션 (arch/x86/Makefile 발췌)
KBUILD_CFLAGS += -mno-red-zone
KBUILD_CFLAGS += -mcmodel=kernel
KBUILD_CFLAGS += -mno-mmx -mno-sse -mno-sse2 -mno-sse3
KBUILD_CFLAGS += -mno-avx
KBUILD_CFLAGS += -maccumulate-outgoing-args

# ARM64 커널 핵심 머신 옵션 (arch/arm64/Makefile 발췌)
KBUILD_CFLAGS += -mgeneral-regs-only
KBUILD_CFLAGS += -mabi=lp64
KBUILD_CFLAGS += -mfix-cortex-a53-835769
KBUILD_CFLAGS += -mfix-cortex-a53-843419

중간 표현 덤프 & 컴파일 분석

GCC 내부 중간 표현(IR)인 GIMPLE과 RTL을 덤프하면 최적화 결과를 추적하거나 컴파일러 버그를 신고할 때 활용할 수 있습니다. 또한 -fopt-info로 벡터화·인라이닝 결정을 리포트받을 수 있습니다.

GIMPLE / RTL 덤프 옵션

# GIMPLE 패스 덤프 — 최적화 전/후 비교
$ gcc -O2 -fdump-tree-all foo.c -o foo    # 모든 GIMPLE 패스 (*.NNN.* 파일군)
$ gcc -O2 -fdump-tree-optimized foo.c     # 최적화 완료 후 최종 GIMPLE
$ gcc -O2 -fdump-tree-cfg foo.c            # 제어 흐름 그래프 (CFG)
$ gcc -O2 -fdump-tree-dce foo.c            # Dead Code Elimination 후
$ gcc -O2 -fdump-tree-vectorize foo.c      # 자동 벡터화 결과
$ gcc -O2 -fdump-tree-inline foo.c         # 인라이닝 적용 후

# RTL 덤프 — 어셈블리 직전 단계
$ gcc -O2 -fdump-rtl-final foo.c          # 최종 RTL
$ gcc -O2 -fdump-rtl-combine foo.c        # RTL combine 패스 후

# IPA (Interprocedural Analysis) 덤프
$ gcc -O2 -fdump-ipa-cgraph foo.c         # 함수 호출 그래프
$ gcc -O2 -fdump-ipa-inline foo.c         # IPA 인라이닝 결정

# 최적화 결정 리포트 (-fopt-info)
$ gcc -O3 -fopt-info-vec=vec.log foo.c     # 벡터화된/못된 루프 목록
$ gcc -O2 -fopt-info-inline=inline.log foo.c # 인라이닝 결정 사유
$ gcc -O2 -fopt-info-loop=loop.log foo.c   # 루프 최적화 정보
$ gcc -O2 -fopt-info-all=opts.log foo.c    # 모든 최적화 정보

# GIMPLE DOT 그래프 출력 (Graphviz 시각화)
$ gcc -O2 -fdump-tree-cfg-graph foo.c
$ dot -Tpng foo.c.*.cfg.dot -o cfg.png

# 진단 메시지 제어
$ gcc -fdiagnostics-color=always foo.c   # 컬러 진단 강제
$ gcc -fmax-errors=10 foo.c             # 오류 10개 후 중단
$ gcc -fmessage-length=0 foo.c          # 메시지 줄 바꿈 없음 (CI 파싱용)
$ gcc -fdiagnostics-format=json foo.c    # JSON 형식 출력 (IDE 통합)
$ gcc -fdiagnostics-show-caret foo.c    # 소스 내 오류 위치 캐럿(^) 표시

함수 멀티버저닝 (Function Multiversioning)

동일 함수를 여러 ISA 버전으로 컴파일하고 런타임에 CPU 기능을 감지하여 최적 버전을 자동 선택합니다 (x86에서 ifunc 메커니즘 기반).

/* target_clones: 자동 멀티버저닝 — GCC가 세 버전을 자동 생성 */
__attribute__((target_clones("avx512f,avx512bw", "avx2", "sse4.2", "default")))
int vector_sum(const int *arr, int n)
{
    int sum = 0;
    for (int i = 0; i < n; i++) sum += arr[i];
    return sum;
}
/* 생성 결과: vector_sum.avx512f_avx512bw, vector_sum.avx2,
              vector_sum.sse4.2, vector_sum (default)
   + ifunc 리졸버: __cpu_indicator_init() 호출 → 최적 버전 선택 */

/* target: 단일 함수에 다른 ISA 강제 */
__attribute__((target("avx2,bmi2,popcnt")))
static void process_avx2(float *data, int n)
{
    /* 이 함수 내에서만 AVX2/BMI2/POPCNT 명령어 사용 가능 */
}

/* 수동 CPU 기능 감지 + 멀티버저닝 */
typedef void (*compress_fn_t)(void *dst, const void *src, size_t n);

static compress_fn_t resolve_compress(void)
{
    if (__builtin_cpu_supports("avx512bw")) return compress_avx512;
    if (__builtin_cpu_supports("avx2"))    return compress_avx2;
    return compress_generic;
}
compress_fn_t compress __attribute__((ifunc("resolve_compress")));

GCC 플러그인

GCC 플러그인 API는 컴파일 파이프라인에 커스텀 GIMPLE 패스를 삽입합니다. 리눅스 커널은 scripts/gcc-plugins/에 보안 강화 플러그인들을 포함합니다 (CONFIG_GCC_PLUGINS=y).

커널 내장 GCC 플러그인

플러그인 파일Kconfig기능
structleak_plugin.soCONFIG_GCC_PLUGIN_STRUCTLEAK스택 구조체 자동 0 초기화 — 커널↔유저스페이스 정보 누출 방지
structleak_plugin.soCONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL참조 전달 구조체도 모두 초기화 (강화 모드)
latent_entropy_plugin.soCONFIG_GCC_PLUGIN_LATENT_ENTROPY부팅 시 잠재 엔트로피 수집 보강 (RNG 초기화 개선)
randomize_layout_plugin.soCONFIG_GCC_PLUGIN_RANDSTRUCT구조체 필드 순서 무작위화 — exploit에서 필드 오프셋 예측 불가
stackleak_plugin.soCONFIG_GCC_PLUGIN_STACKLEAK함수 진입 시 스택 프레임을 독약값으로 채움 — 정보 누출 방지
# 커널 플러그인 빌드 활성화
$ make menuconfig   # → Security options → GCC plugins 활성화
$ make CONFIG_GCC_PLUGINS=y -j$(nproc) vmlinux

# 플러그인 직접 사용법
$ gcc -fplugin=/path/to/myplugin.so source.c

# 플러그인에 인수 전달
$ gcc -fplugin=myplugin.so \
       -fplugin-arg-myplugin-verbose=1 \
       -fplugin-arg-myplugin-threshold=100 source.c

# 플러그인 API 버전 확인
$ gcc -print-file-name=plugin  # 플러그인 include 경로
$ ls $(gcc -print-file-name=plugin)/include/
/* GCC 플러그인 최소 골격 (plugin_gcc_version 필수 확인) */
#include "gcc-plugin.h"
#include "tree.h"
#include "gimple.h"
#include "gimple-iterator.h"

int plugin_is_GPL_compatible;  /* GPL 호환 선언 필수 */

static unsigned int my_gimple_pass(function *fun)
{
    basic_block bb;
    FOR_EACH_BB_FN(bb, fun) {
        gimple_stmt_iterator gsi;
        for (gsi = gsi_start_bb(bb); !gsi_end_p(gsi); gsi_next(&gsi)) {
            gimple *stmt = gsi_stmt(gsi);
            /* GIMPLE 문장 분석/변환 */
        }
    }
    return 0;
}

static struct pass_data my_pass_data = {
    .type    = GIMPLE_PASS,
    .name    = "my_security_pass",
    .execute = my_gimple_pass,
};

int plugin_init(struct plugin_name_args *info,
                struct plugin_gcc_version *ver)
{
    if (!plugin_default_version_check(ver, &gcc_version))
        return 1;  /* 버전 불일치 → 실패 */
    register_callback(info->base_name,
                      PLUGIN_PASS_MANAGER_SETUP,
                      NULL, &my_pass_setup);
    return 0;
}
Clang 전환 추세: 커널 6.1+에서 Clang/LLVM 기반 빌드(make LLVM=1)가 점점 완성도가 높아지고 있습니다. Clang은 GCC 플러그인 대신 자체 -fsanitize=kernel-address, -fsanitize=cfi, LTO=thin 등을 제공합니다. 두 컴파일러 모두 CONFIG_CC_IS_GCC / CONFIG_CC_IS_CLANG으로 구분됩니다.

커널 모듈 빌드와 GCC

리눅스 커널 모듈은 커널 소스 트리 밖에서도 빌드할 수 있습니다(out-of-tree 빌드). Kbuild 시스템이 GCC 호출을 담당하며, 개발자는 간단한 Makefile만 작성하면 됩니다.

out-of-tree 모듈 Kbuild 구조

Kbuild는 obj-m 변수로 빌드할 모듈을 선언합니다. 멀티 소스 파일 모듈은 모듈명-y 변수에 오브젝트 파일을 나열합니다.

# Kbuild 파일 (또는 Makefile 상단 Kbuild 섹션)

# 단일 파일 모듈 (mymodule.c → mymodule.ko)
obj-m := mymodule.o

# 멀티 파일 모듈: mymodule.ko = a.o + b.o + c.o
obj-m           := mymodule.o
mymodule-y      := a.o b.o c.o
mymodule-$(CONFIG_MY_FEATURE) += feature.o   # 조건부 오브젝트

# 모듈별 GCC 플래그 추가
ccflags-y       += -I$(src)/include -DMODULE_VERSION="\"1.0\""
ldflags-y       += -T $(src)/mymodule.lds   # 커스텀 링커 스크립트

# 파일별 플래그 (최적화 개별 제어)
CFLAGS_a.o        := -O0 -g   # a.c만 디버그 빌드
CFLAGS_REMOVE_b.o := -O2      # b.c에서 -O2 제거

make -C $(KDIR) M=$(PWD) 패턴

out-of-tree 모듈의 표준 Makefile 골격입니다. KERNELRELEASE 변수로 Kbuild 내부/외부를 구분합니다.

# ~/mymodule/Makefile — 표준 out-of-tree 모듈 Makefile

KDIR    ?= /lib/modules/$(shell uname -r)/build
PWD     := $(shell pwd)

ifeq ($(KERNELRELEASE),)
# ─── 외부 호출: Kbuild 시스템으로 위임 ───────────────────────

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KDIR) M=$(PWD) modules_install

clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean

else
# ─── Kbuild 내부 호출: 모듈 선언 ─────────────────────────────

obj-m     := mymodule.o
mymodule-y := a.o b.o

endif
# 실행 중인 커널용 모듈 빌드 (기본)
$ make

# 특정 커널 헤더 트리 지정
$ make KDIR=/usr/src/linux-headers-6.8.0-100-generic

# KDIR 설정 방법
# 1. 설치된 커널 헤더 패키지 (권장, 배포판 커널용)
#    apt install linux-headers-$(uname -r)
KDIR = /lib/modules/$(uname -r)/build

# 2. 자체 빌드한 커널 소스 트리 (make 후 디렉토리 지정)
KDIR = /usr/src/linux-6.8

크로스 컴파일 조합 (ARCH + CROSS_COMPILE + KDIR)

타겟 아키텍처용 커널 모듈을 빌드할 때 ARCH, CROSS_COMPILE, KDIR 세 변수를 조합합니다. KDIR은 타겟 아키텍처로 빌드된 커널 소스 트리 또는 헤더 경로를 지정합니다.

# ARM64 (aarch64) 모듈 크로스 빌드
$ make \
    ARCH=arm64 \
    CROSS_COMPILE=aarch64-linux-gnu- \
    KDIR=/path/to/arm64-kernel-build \
    -j$(nproc)

# RISC-V 64비트 모듈 크로스 빌드
$ make \
    ARCH=riscv \
    CROSS_COMPILE=riscv64-linux-gnu- \
    KDIR=/path/to/riscv64-kernel-build

# ARM (32비트) + 스테이징 루트에 설치
$ make \
    ARCH=arm \
    CROSS_COMPILE=arm-linux-gnueabihf- \
    KDIR=/path/to/arm-kernel-build \
    INSTALL_MOD_PATH=/staging/rootfs \
    modules_install

# LLVM 크로스 컴파일 (Clang 기반)
$ make \
    LLVM=1 \
    ARCH=arm64 \
    KDIR=/path/to/arm64-llvm-kernel-build

모듈 설치 및 연계 도구

빌드된 모듈을 시스템에 설치하고 관리하는 표준 워크플로입니다.

# 스테이징 루트에 설치 (크로스 컴파일 배포 시)
$ make modules_install INSTALL_MOD_PATH=/staging/rootfs
# → /staging/rootfs/lib/modules/$(KERNELRELEASE)/extra/mymodule.ko

# 현재 시스템에 직접 설치
$ sudo make modules_install
$ sudo depmod -a               # 모듈 의존성 데이터베이스 갱신 (modules.dep 재생성)
$ sudo modprobe mymodule       # 모듈 로드 (의존성 자동 처리)

# 모듈 정보 확인
$ modinfo mymodule.ko
filename:       /lib/modules/6.8.0/extra/mymodule.ko
version:        1.0
description:    My kernel module
...

# 로드된 모듈 확인 및 제거
$ lsmod | grep mymodule
$ sudo rmmod mymodule

# 빌드 아티팩트 정리
$ make clean                   # .o, .ko, .mod.c 등 제거
모듈 서명(Module Signing): CONFIG_MODULE_SIG=y 커널에서는 모듈에 서명이 필요합니다. scripts/sign-file로 수동 서명하거나 make modules_install 후 별도 서명 스텝을 추가합니다. Secure Boot 환경에서 CONFIG_MODULE_SIG_FORCE=y가 활성화된 경우 미서명 모듈 로드는 차단됩니다.

GCC 사용 시 주의사항 및 문제 해결

GCC 사용 중 발생하는 일반적인 문제와 그 원인, 해결 방법을 정리합니다.

자주 발생하는 문제

문제원인해결 방법
mysterious 최적화 버그-O2/O3에서만 발생하는 버그UB 확인: -fsanitize=undefined, -fno-strict-aliasing 추가
부동소수점 결과 불일치-ffast-math 사용 시 IEEE 준수 무시-ffast-math 제거 또는 -fno-unsafe-math-optimizations
스택 오버플로재귀, 큰 로컬 배열, VLA-Wframe-larger-than=N으로 탐지, VLA 제거
LTO 링크 오류버전 불일치 오브젝트 파일 혼합동일 GCC 버전으로 모두 재빌드
미사용 코드 삭제 실패__attribute__((used)) 없는 심볼__attribute__((used)) 또는 링커 스크립트로 보존
C++ name mangling 충돌C/C++ 혼용 헤더에 extern "C" 누락C 헤더에 extern "C" { ... } 추가
컴파일 속도 저하헤더 파일 남발, LTO 사용사전 컴파일 헤더(-fprecompiled-headers), -gsplit-dwarf, unity 빌드

유용한 진단 옵션

# 어떤 명령어가 실제로 실행되는지 확인
$ gcc -v foo.c -o foo         # 상세 빌드 과정 출력

# 내부 spec 파일 확인
$ gcc -dumpspecs

# 기본 검색 경로 확인
$ gcc -print-search-dirs

# 전처리된 결과 확인 (매크로 확장)
$ gcc -E -dM foo.c            # 정의된 매크로 목록
$ gcc -E foo.c | less         # 전처리 결과 확인

# 어떤 최적화가 활성화되었는지 확인
$ gcc -O2 -Q --help=optimizers

# 특정 최적화 비활성화 (디버깅)
$ gcc -O2 -fno-tree-vectorize foo.c -o foo

# 어셈블리 출력으로 최적화 결과 확인
$ gcc -O2 -S -fverbose-asm foo.c -o foo.s

# 어떤 버전의 GCC인지 확인
$ gcc --version
$ gcc -dumpversion
$ gcc -dumpfullversion
다음 학습:
  • Git — mainline/linux-next 추적, 토픽 브랜치와 커밋 규약, format-patc
  • GNU Assembler (as) 완전 가이드 — GAS 섹션·심볼·재배치 지시자, 매크로/조건 조립, CFI 언와인드 정보, 아키텍처별 문