GCC (GNU Compiler Collection) 완전 가이드
커널 빌드에서 GCC가 만들어내는 산출물을 이해할 수 있도록 옵션 의미를 연결해 설명합니다. 전처리부터 링크까지 단계별 산출물, 경고 정책과 `-Werror` 운용, `-O2` 기반 최적화와 디버그 정보 균형, 아키텍처별 보안 완화 플래그, inline asm/`__builtin_*`/attribute 사용 시 주의점, 크로스 컴파일 툴체인 구성, KCFLAGS를 포함한 Kbuild 통합 운용까지 실무 중심으로 상세히 정리합니다.
핵심 요약
- 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 금지) — 커널 필수 플래그.
단계별 이해
- 컴파일 4단계 구조 파악
소스 코드가 실행 파일이 되기까지의 각 단계와 중간 산출물(*.i, *.s, *.o)을 이해합니다. - 경고 옵션 적용
-Wall -Wextra -Werror로 코드 품질을 높이고, 커널에서 사용되는 경고 억제 패턴을 익힙니다. - 최적화 레벨 선택
개발 단계는-Og, 릴리스는-O2, 커널은 기본적으로-O2를 사용합니다. - GCC 확장 습득
커널 코드에서 광범위하게 쓰이는__attribute__,likely/unlikely, 인라인 어셈블리 문법을 익힙니다. - 크로스 컴파일 환경 설정
CROSS_COMPILE변수와ARCH환경 변수로 타깃 아키텍처별 빌드를 제어합니다. - 머신 의존 옵션 이해
커널은-mno-red-zone,-mcmodel=kernel,-mno-sse같은 아키텍처 전용 플래그가 필수입니다. 이를 빠뜨리면 런타임 오류가 발생합니다. - CPU 취약점 완화 적용
Spectre v2(Retpoline), SLS, ARM64 PAC/BTI 완화 옵션을 Kconfig와 연동하여 조건부 적용합니다. - GCC 플러그인 활성화
CONFIG_GCC_PLUGINS=y로 커널 보안 플러그인을 활성화합니다. 구조체 정보 누출, 스택 초기화, 무작위화를 컴파일러 수준에서 강화합니다.
GCC 개요 및 아키텍처
GCC는 "GNU Compiler Collection"의 약자로, GNU 프로젝트의 핵심 컴파일러 배포판입니다. 원래 "GNU C Compiler"를 의미했으나, C 이외의 언어를 지원하면서 "Collection"으로 확장되었습니다. GCC는 단일 언어에 종속되지 않는 언어 독립 공통 최적화 계층(middle-end)과 각 언어별 프론트엔드(front end), 타깃 아키텍처별 백엔드(back end)로 구성됩니다.
GCC가 호출될 때 기본적으로 전처리 → 컴파일 → 어셈블 → 링크의 4단계를 모두 실행합니다. -c 옵션으로 링크 이전 단계에서 멈추거나, -S로 어셈블리 출력, -E로 전처리 결과만 출력할 수 있습니다.
주요 전통 컴파일러 이름
| 언어 | GCC 드라이버 | 비고 |
|---|---|---|
| C | gcc | 기본 드라이버 |
| C++ | g++ | C++ 라이브러리 자동 링크 |
| COBOL | gcobol | GCC 15 신규 지원 |
| Ada | gnat | GNAT 컴파일러 |
| Fortran | gfortran | GNU Fortran |
| Go | gccgo | GCC 4.7.1+ |
| D | gdc | D 2.0 지원 |
| Objective-C | gcc -lobjc | GNU ObjC 런타임 |
지원 언어 및 표준
GCC는 각 언어에 대한 공식 표준을 따르며, GNU 확장이 포함된 버전도 지원합니다. -std= 옵션으로 표준 버전을 명시하고, -pedantic을 추가하면 표준에서 요구하는 진단을 모두 활성화합니다.
C 언어 표준
| 표준 | GCC 옵션 | GNU 확장 포함 | 특징 |
|---|---|---|---|
| C89 / C90 | -std=c90 / -ansi | -std=gnu90 | ANSI C, 가장 오래된 표준 |
| C94 / AMD1 | -std=iso9899:199409 | — | 다이그래프 추가 |
| C99 | -std=c99 | -std=gnu99 | 가변 배열, inline, // 주석, stdint.h |
| C11 | -std=c11 | -std=gnu11 | 원자 연산, 스레드, 정적 어설션 |
| C17 | -std=c17 | -std=gnu17 | C11 수정판, __STDC_VERSION__ 차이 |
| C23 | -std=c23 | -std=gnu23 | nullptr, _BitInt, constexpr, 2024년 제정 ISO/IEC 9899:2024 |
| C2Y (개발 중) | -std=c2y | -std=gnu2y | 실험적·미완성 지원 |
-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++23 | print, stacktrace, 추가 뷰, ISO/IEC 14882:2024 |
-std=gnu++17을 기본으로 사용합니다. 커널에서 Rust와 C++ 혼용은 금지되어 있으며, 커널 C++ 코드(있다면)는 제한적 서브셋만 허용합니다.
독립 구현 환경 (Freestanding Environment)
C 표준은 두 가지 구현 환경을 정의합니다. 리눅스 커널은 독립 구현 환경(freestanding environment)에서 동작합니다.
| 환경 | 설명 | GCC 옵션 | 예시 |
|---|---|---|---|
| Hosted | OS와 C 표준 라이브러리 전체 사용 가능. int main() 엔트리 포인트. | (기본값) | 일반 사용자 프로그램 |
| Freestanding | OS 없이 동작. <float.h>, <limits.h>, <stdarg.h>, <stddef.h>만 보장. | -ffreestanding | OS 커널, 부트로더, 임베디드 펌웨어 |
# 리눅스 커널 Makefile에서 GCC 독립 환경 설정
KBUILD_CFLAGS += -ffreestanding # 독립 구현 모드
KBUILD_CFLAGS += -fno-builtin # 내장 함수 비활성화
KBUILD_CFLAGS += -fno-stack-protector # 기본 스택 보호 비활성화(커널이 직접 관리)
KBUILD_CFLAGS += -fno-PIE # 위치 독립 실행 파일 비활성화
컴파일 단계 상세
GCC는 하나의 명령으로 네 단계를 순차적으로 실행합니다. 각 단계에서 중단하면 중간 산출물을 확인할 수 있습니다.
# 단계별 중단 예시
$ 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=2 | printf 형식 문자열 보안 강화 경고. | ✓ 사용 |
-Wmissing-prototypes | 프로토타입 없는 함수 정의 경고 (C only). | ✓ 사용 |
-Wimplicit-fallthrough | switch-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 unroll | HPC, 성능 중요 코드 |
-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 기본 형식). | 플랫폼 의존 |
-ggdb | GDB에 최적화된 디버깅 정보 (DWARF 확장 포함). | GDB 전용 |
-gdwarf | DWARF 형식 디버깅 정보 (기본은 DWARF 5). | DWARF |
-gdwarf-4 | DWARF 4 버전 명시 (넓은 호환성). | DWARF 4 |
-g1 | 최소 디버깅 정보 (줄 번호만). | DWARF 최소 |
-g3 | 매크로 정의 포함 (가장 상세). | DWARF 최대 |
-gsplit-dwarf | DWARF 정보를 별도 .dwo 파일로 분리. 빌드 속도 향상. | 분산 DWARF |
-gbtf | BPF Type Format (BTF) 생성. eBPF 프로그램에 필요. | BTF |
-gctf | Compact 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-return | KASAN |
-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=thunk | Spectre v2 | 간접 분기를 retpoline thunk로 치환 (x86) |
-mindirect-branch=thunk-extern | Spectre v2 | 외부 thunk 사용 — 커널 기본값 |
-mfunction-return=thunk-extern | Spectre v2 / RET | 함수 반환을 외부 thunk로 치환 |
-mindirect-branch-register | Spectre v2 | 간접 분기를 레지스터 기반으로만 허용 |
-mharden-sls=all | SLS (Straight-Line Speculation) | 직선 추측 실행 완화 — GCC 12+, 커널 사용 |
-mharden-sls=retbr | SLS | ret/br 명령어 뒤 INT3 삽입 |
-mbranch-protection=standard | ARM64 ROP/JOP | PAC + BTI 동시 활성화 (ARMv8.3+/8.5+) |
-mbranch-protection=pac-ret | ARM64 ROP | 반환 주소 포인터 인증(PAC-RET) |
-mbranch-protection=bti | ARM64 JOP | 분기 타깃 식별자(BTI) |
-mspeculative-load-hardening | Spectre 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
__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-v3 | AVX2 포함 x86-64 v3 마이크로아키텍처 |
| x86-64 | -mtune=native | 현재 CPU 특성에 맞게 튜닝 |
| ARM64 | -march=armv8-a | ARMv8-A 기본 아키텍처 |
| ARM64 | -march=armv8.5-a+memtag | Memory Tagging Extension(MTE) 활성화 |
| RISC-V | -march=rv64gc | RISC-V 64bit general + compressed |
| ARM 32bit | -mfpu=neon -mfloat-abi=hard | NEON 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
컴파일러 최적화와 커널 이슈
커널은
void *나 char *를 다양한 타입 포인터로 캐스팅하는 경우가 많습니다. GCC의 엄격한 별칭 규칙(strict aliasing)을 적용하면 이런 코드를 잘못 최적화할 수 있습니다. 따라서 커널 빌드는 항상 -fno-strict-aliasing을 사용합니다.
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 (Link-Time Optimization)
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.8 | 3.2 | x86 레거시 지원, 기본 C99/GCC 확장 |
| 4.9 ~ 5.1 | 4.6 | -std=gnu89에서 -std=gnu11으로 전환 시작 |
| 5.2 ~ 5.14 | 4.9 | C11 _Generic·_Static_assert 활용, -std=gnu11 기본 |
| 5.15 ~ 현재 | 5.1 | scripts/cc-version.sh가 빌드 전에 체크; scripts/min-tool-version.sh gcc → 5.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
compiler-gcc.h의 #error가 아니라 빌드 시작 시 scripts/cc-version.sh가 scripts/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_CFLAGS가 Makefile 내부 기본 플래그라면, 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_WARN | 2048 (x86-64) / 1024 (arm64) | 스택 프레임 경고 임계값(바이트). 초과 시 빌드 경고 발생 |
-Wframe-larger-than=N | 자동 설정 | CONFIG_FRAME_WARN 값으로 Kbuild가 자동 적용 |
scripts/checkstack.pl | — | objdump 출력 파싱으로 스택 크기 추출 (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=]
-Wvla 플래그로 VLA 사용을 금지합니다. VLA는 스택 크기를 컴파일 타임에 예측할 수 없어 -fstack-usage에서 dynamic으로 표시되며 스택 오버플로 취약점의 원인이 됩니다. 가변 길이가 필요하면 kmalloc()이나 고정 크기 배열을 사용하세요.
gcov 코드 커버리지 도구
gcov는 GCC에 포함된 테스트 커버리지 프로그램입니다. 컴파일 시 계측(instrumentation) 코드를 삽입하여 실행 중 각 코드 줄이 몇 번 실행되었는지 기록합니다. 리눅스 커널은 CONFIG_GCOV_KERNEL로 gcov를 지원합니다.
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 |
-M | Makefile 의존성 규칙 출력 | 빌드 시스템 통합 |
-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
링크 옵션 (-l, -L, -Wl, ...)
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) 링크 안 함 | ✓ 커널, 부트로더 |
-nodefaultlibs | libgcc 등 기본 라이브러리 링크 안 함 | 맞춤 런타임 |
-nostartfiles | crt0.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 / -m64 | 32bit / 64bit 코드 생성 강제 | |
-mno-red-zone | x86-64 ABI의 128바이트 레드존 사용 금지 | ✓ 필수 — 인터럽트/NMI 핸들러 안전 |
-mcmodel=kernel | 커널 주소 공간 모델 (최상위 2GB 내 접근) | ✓ x86-64 커널 필수 |
-mcmodel=large | 모든 주소를 64bit 절대 주소로 접근 | BPF JIT 등 특수 목적 |
-mno-mmx | MMX 명령어 사용 금지 | ✓ 커널 코어 |
-mno-sse / -mno-sse2 | SSE/SSE2 사용 금지 (FPU 상태 저장 불필요) | ✓ 커널 코어 |
-mno-avx | AVX 사용 금지 | ✓ 커널 코어 |
-maccumulate-outgoing-args | 스택 아규먼트를 미리 할당 (프레임 크기 일정) | ✓ 커널 |
-mskip-rax-setup | 가변인수 함수 진입 시 %al 초기화 생략 | ✓ 커널 (System V ABI 불필요) |
-mpreferred-stack-boundary=3 | 스택 8바이트 정렬 강제 (2³=8) | ✓ 일부 아키텍처 |
-march=x86-64-v3 | AVX2+BMI+F16C 포함 x86-64 v3 마이크로아키텍처 | 최신 서버 최적화 |
-mtune=native | 현재 실행 CPU 특성에 맞게 스케줄링 최적화 | 호스트 빌드 |
-masm=intel | Intel 문법으로 어셈블리 출력 (-S와 함께) | 어셈블리 분석 |
ARM64 주요 옵션
| 옵션 | 설명 | 비고 |
|---|---|---|
-mgeneral-regs-only | 범용 레지스터만 사용 (FP/SIMD 레지스터 금지) | ✓ ARM64 커널 기본 |
-mabi=lp64 | LP64 ABI (long + pointer = 64bit) — Linux 기본 | |
-mabi=ilp32 | ILP32 ABI (int+long+pointer = 32bit) | 특수 목적 |
-mbranch-protection=standard | PAC + 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-835769 | Cortex-A53 에라타 835769 완화 | RaspberryPi 3 등 |
-mfix-cortex-a53-843419 | Cortex-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.so | CONFIG_GCC_PLUGIN_STRUCTLEAK | 스택 구조체 자동 0 초기화 — 커널↔유저스페이스 정보 누출 방지 |
structleak_plugin.so | CONFIG_GCC_PLUGIN_STRUCTLEAK_BYREF_ALL | 참조 전달 구조체도 모두 초기화 (강화 모드) |
latent_entropy_plugin.so | CONFIG_GCC_PLUGIN_LATENT_ENTROPY | 부팅 시 잠재 엔트로피 수집 보강 (RNG 초기화 개선) |
randomize_layout_plugin.so | CONFIG_GCC_PLUGIN_RANDSTRUCT | 구조체 필드 순서 무작위화 — exploit에서 필드 오프셋 예측 불가 |
stackleak_plugin.so | CONFIG_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;
}
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 등 제거
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
- 빌드 시스템 (Kbuild/Kconfig) — GCC 옵션이 실제 커널 빌드에서 어떻게 조합되는지
- 어셈블리 — GCC 인라인 어셈블리와 커널 어셈블리 상세
- 커널 개발 도구 — GCC 외 Clang, sparse, coccinelle 등 전체 도구 생태계
- 성능 최적화 — GCC 최적화와 커널 성능 튜닝 연계
- GNU Make — GCC를 구동하는 빌드 도구
- GDB 완전 가이드 — GCC로 빌드한 커널·모듈 디버깅
- GNU Binutils — objdump/readelf로 GCC 출력 분석
- 메모리 배리어 — GCC 메모리 접근 순서와 컴파일러 배리어
관련 문서
- Git — mainline/linux-next 추적, 토픽 브랜치와 커밋 규약, format-patc
- GNU Assembler (as) 완전 가이드 — GAS 섹션·심볼·재배치 지시자, 매크로/조건 조립, CFI 언와인드 정보, 아키텍처별 문