크래시 분석 심화

운영 중 발생하는 커널 장애를 근거 기반으로 분석하는 절차를 정리합니다. panic/oops 로그와 call trace 프레임 해석, `crashkernel`/kdump 설정 및 vmcore 수집, `crash` 도구로 태스크·메모리·락 상태 확인, softlockup/hardlockup/RCU stall 구분, WARN/BUG의 위험도 평가, pstore/ramoops/SysRq를 통한 증거 확보, 재현 시나리오 구성과 패치 검증까지 실무형 체크리스트로 상세히 다룹니다.

전제 조건: 커널 디버깅RCU 문서를 먼저 읽으세요. 크래시 분석은 스택/레지스터/로그를 교차 검증하는 작업이므로, 패닉 직전 이벤트와 동시성 맥락을 함께 읽어야 정확도가 올라갑니다.
일상 비유: 이 주제는 사고 블랙박스 복기와 비슷합니다. 충돌 직전 몇 초의 기록이 핵심 단서가 되듯이, panic 전후 trace와 call trace를 시간축으로 정렬해 보는 습관이 중요합니다.

핵심 요약

  • 증거 우선 — 패닉 직후 로그/콘솔/vmcore를 먼저 확보해야 합니다.
  • 분류 우선 — Oops, softlockup, hardlockup, hung task를 먼저 구분해야 분석 경로가 정해집니다.
  • 재현성 확보 — 동일 워크로드/커널/설정에서 재현 가능한지 확인해야 합니다.
  • 운영 안전성 — 프로덕션에서는 패닉 정책과 자동 재부팅 정책을 분리해 설계해야 합니다.
  • 사후 검증 — 패치 후 동일 시나리오에서 재발 여부를 반드시 확인해야 합니다.

단계별 이해

  1. 증거 수집
    시리얼 콘솔, pstore, vmcore 확보 상태를 먼저 점검합니다.
  2. 1차 분류
    메시지 패턴으로 lockup/oops/panic 유형을 분리합니다.
  3. 심층 분석
    crash, Call Trace, taint 플래그로 원인 가설을 좁힙니다.
  4. 재현/검증
    수정 후 같은 조건에서 재현 불가를 확인하고 운영 설정을 재점검합니다.
예제 읽기 가이드: 이 문서는 개념 설명용 의사코드와 실행 가능한 실습 예제를 함께 제공합니다. 코드 주석의 개념 예시는 내부 동작 이해 목적이며, 실습 예제는 실제 점검/복구 절차 재현 목적입니다.
관련 표준: ELF Core File Format, DWARF Debugging Format — 코어 덤프 및 디버그 정보 표준 형식입니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.
관련 페이지: 기본 디버깅 도구(printk, KGDB, ftrace, perf, 새니타이저, lockdep, kdump, bpftrace)는 디버깅 & 트러블슈팅 페이지를 참고하세요.

크래시 유형 분류 가이드

커널 크래시는 증상에 따라 여러 유형으로 분류됩니다. 정확한 분류는 올바른 분석 경로와 해결 전략을 결정하는 첫 번째 단계입니다.

커널 크래시 유형 분류 플로차트 크래시 증상 확인 시스템 응답 가능? NO Hardlockup 완전 멈춤 (NMI 무응답) 증거: 콘솔 멈춤 원인: 인터럽트 비활성화 도구: NMI watchdog, SysRq YES Kernel Panic 발생? YES Kernel Panic BUG()/panic() 호출 증거: "Kernel panic" 메시지 원인: 치명적 일관성 위반 도구: kdump, crash, pstore NO Oops 메시지 있음? YES Oops (복구 가능) NULL deref, GPF, Page Fault 증거: "BUG:", "Oops:" 메시지 원인: 프로세스별 오류 도구: Call Trace, KASAN panic_on_oops=0이면 계속 실행 NO 응답 지연 메시지? YES Softlockup 스케줄링 지연 (20초+) 증거: "soft lockup" 메시지 원인: 긴 루프, 선점 불가 도구: ftrace, perf 시스템은 계속 실행됨 NO RCU Stall RCU 대기 시간 초과 Hung Task I/O 대기 120초+ WARN_ON 경고 (계속 실행) 분류 우선순위 1. 콘솔 메시지 확인 → 2. dmesg 패턴 매칭 → 3. pstore/vmcore 확보 → 4. 유형별 도구 선택
그림: 커널 크래시 유형 분류 플로차트 - 증상별 분석 경로
유형 심각도 시스템 영향 주요 키워드 우선 조치
Hardlockup 최고 완전 멈춤 NMI watchdog NMI 스택 확보, 하드웨어 점검
Kernel Panic 매우 높음 즉시 중단 Kernel panic, BUG() kdump 수집, Call Trace 분석
Oops 높음 프로세스 종료 Oops:, BUG:, NULL pointer RIP 위치 특정, KASAN 재현
Softlockup 중간 응답 지연 soft lockup, stuck for ftrace로 함수 추적
Hung Task 중간 프로세스 블록 hung_task, blocked for I/O 스택 확인, 락 대기 분석
RCU Stall 중간 GC 지연 rcu_sched detected stalls 긴 임계 구역 찾기
WARN_ON 낮음 계속 실행 WARNING:, WARN_ON 조건식 검토, 재현 환경 구축

crashkernel 심화 설정

crashkernel 파라미터는 kdump 캡처 커널이 사용할 메모리를 부팅 시 예약합니다. 올바른 크기 설정은 kdump의 안정성과 시스템 메모리 효율의 균형점에 있습니다.

crashkernel 메모리 크기 결정

파라미터 형식예시설명
고정 크기crashkernel=256M항상 256MB 예약. 가장 단순하고 예측 가능
자동crashkernel=auto시스템 메모리에 따라 자동 결정 (배포판 의존)
범위 기반crashkernel=512M-2G:64M,2G-:256MRAM 512M~2G→64MB 예약, 2G 이상→256MB 예약
오프셋 지정crashkernel=256M@16M물리주소 16MB부터 시작 (레거시 시스템)
high/low 분리crashkernel=256M,high crashkernel=72M,low4GB 이상/이하 영역 분리 예약 (UEFI/대용량 서버)
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# 현재 crashkernel 예약 상태 확인
cat /proc/iomem | grep -i crash
#   2c000000-3bffffff : Crash kernel

# 예약된 메모리 크기 확인
dmesg | grep -i crashkernel
# [    0.000000] Reserving 256MB of memory at 704MB for crashkernel

# kdump 서비스 상태 확인
systemctl status kdump       # RHEL/CentOS
systemctl status kdump-tools # Debian/Ubuntu
kdumpctl status              # RHEL 전용

# 크래시 커널 로드 확인
cat /sys/kernel/kexec_crash_loaded
# 1 = 로드됨, 0 = 미로드

# 크래시 커널이 사용할 크기 추정 (테스트 필요)
# 최소 권장: 기본 커널 + initrd + makedumpfile + 여유
# - 커널 이미지: ~30-50MB
# - initrd:     ~50-100MB
# - makedumpfile 동작 메모리: ~64MB
# - 디바이스 드라이버: 가변
# 보수적 권장: 시스템 RAM 기준
#   RAM ≤ 4GB:   128M
#   RAM ≤ 64GB:  256M
#   RAM ≤ 1TB:   512M
#   RAM > 1TB:   1G 이상 (네트워크 덤프 시 더 필요)
⚠️

crashkernel 설정 시 주의사항:

  • crashkernel=auto는 배포판마다 동작이 다릅니다. RHEL은 RAM 기반 테이블을 사용하지만, 일부 커널에서는 지원하지 않을 수 있습니다.
  • UEFI 시스템에서는 crashkernel=,high/,low 조합이 필요할 수 있습니다. low 영역은 최소 72MB(swiotlb)가 필요합니다.
  • 메모리가 부족하면 캡처 커널이 OOM으로 실패합니다. 반드시 테스트(echo c > /proc/sysrq-trigger)로 검증하십시오.
  • NUMA 시스템에서 crashkernel 메모리는 Node 0에 예약됩니다. Node 0의 메모리가 부족하면 예약에 실패할 수 있습니다.

kdump 생명주기 타임라인

kdump는 커널 크래시 발생부터 분석 완료까지 여러 단계를 거칩니다. 각 단계의 동작과 시간 순서를 이해하면 문제 발생 시 신속하게 대응할 수 있습니다.

kdump 생명주기 타임라인 부팅 시 분석 완료 0. 부팅 초기화 crashkernel 메모리 예약 (grub 설정) 256MB @ 0x2c000000 kexec_load() 호출 1. 정상 실행 프로덕션 커널 워크로드 실행 중 kexec_crash_loaded=1 2. 크래시 발생 panic() 호출 Call Trace 출력 레지스터 덤프 panic_timeout 대기 → crash_kexec() 진입 3. Kexec 전환 기존 커널 중지 캡처 커널 부팅 crashkernel 영역 사용 메모리 보존 (oldmem) 4. 덤프 수집 makedumpfile 실행 /proc/vmcore 읽기 압축 & 필터링 → /var/crash/*.vmcore 시간: 수 분 ~ 수십 분 5. 시스템 재부팅 프로덕션 커널 정상 부팅 자동 재부팅 설정 (panic_timeout=0) 6. 사후 분석 단계 (오프라인) crash 유틸리티 분석 $ crash vmlinux vmcore crash> bt (백트레이스) crash> log (커널 로그) crash> ps (프로세스) crash> files (파일) crash> struct (구조체) crash> dis (디스어셈블) crash> kmem (메모리) crash> mod (모듈) crash> foreach bt (전체) 자동화 스크립트 decode_stacktrace.sh Call Trace 자동 변환 faddr2line RIP 주소 → 소스 라인 로그 기반 분석 pstore/ramoops /sys/fs/pstore/dmesg-* 시리얼 콘솔 로그 패닉 전후 메시지 근본 원인 분석 Git blame/log 관련 커밋 조사 재현 환경 구성 KASAN/lockdep 활성화 최종 단계 패치 작성 → 테스트 → 메일링리스트 제출 → 백포트 → 검증 동일 조건에서 재발 방지 확인
그림: kdump 생명주기 타임라인 - 크래시부터 분석 완료까지 전체 흐름
💡

kdump 동작 시간 특성:

  • 단계 0-3 (부팅~kexec): 수 초 이내 (즉각)
  • 단계 4 (덤프 수집): 가장 시간 소모 — 시스템 RAM 크기와 압축 레벨에 비례
    • 4GB RAM: ~2-5분
    • 64GB RAM: ~10-20분
    • 256GB+ RAM: ~30-60분+ (네트워크 덤프 시 더 길어짐)
  • 단계 5 (재부팅): 정상 부팅 시간과 동일
  • 단계 6 (분석): 사람이 수행 — 수 시간 ~ 수 일

프로덕션 고려사항: 덤프 수집 중 서비스 다운타임 발생. makedumpfile -d 31로 불필요 페이지 제외하여 시간 단축 가능.

kdump 설정 심화

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# RHEL/CentOS: /etc/kdump.conf 주요 설정
path /var/crash                    # 덤프 저장 경로
core_collector makedumpfile -l --message-level 7 -d 31
# -l: lzo 압축 (빠름)
# -d 31: 제로/캐시/유저/프리 페이지 제외
# --message-level 7: 진행 상황 출력

# 네트워크 덤프 (NFS)
nfs my-server.example.com:/export/crash
# 네트워크 덤프 (SSH)
ssh user@my-server.example.com
sshkey /root/.ssh/kdump_id_rsa

# kdump 실패 시 동작 설정
default reboot                     # dump_to_rootfs, halt, poweroff, shell
failure_action reboot              # 덤프 실패 시 재부팅

# 특정 디스크에 직접 덤프
raw /dev/sda3
# 또는 ext4/xfs 파티션
ext4 /dev/sda3
path /crash

# 덤프할 메모리 필터링 (makedumpfile -d 플래그)
# Bit 0 (1):  제로 페이지 제외
# Bit 1 (2):  캐시 페이지 제외
# Bit 2 (4):  캐시 프라이빗 제외
# Bit 3 (8):  유저 페이지 제외
# Bit 4 (16): 프리 페이지 제외
# Bit 5 (32): Hugetlb 프라이빗 제외 (커널 6.0+)
# 일반적: -d 31 (커널 데이터만 보존)
# 상세 분석: -d 1 (유저 메모리 포함)

# Debian/Ubuntu: /etc/default/kdump-tools
USE_KDUMP=1
KDUMP_SYSCTL="kernel.panic_on_oops=1"
KDUMP_COREDIR="/var/crash"
KDUMP_CMDLINE_APPEND="irqpoll nr_cpus=1 reset_devices"

kdump 운영 시 고려사항

☢️

kdump 실패 주요 원인과 대처:

  • 캡처 커널 메모리 부족: crashkernel 크기 증가. 특히 많은 드라이버가 로드된 서버
  • 디스크 공간 부족: vmcore는 수 GB~수십 GB. 로테이션 설정 필수
  • 네트워크 드라이버 미동작: 캡처 커널에서 NIC 리셋 실패 → 로컬 덤프로 fallback
  • IOMMU/DMA 초기화 실패: reset_devices 부팅 파라미터 필요
  • 암호화 디스크: 캡처 커널에서 LUKS 복호화 불가 → 비암호화 파티션 사용
  • Secure Boot: 캡처 커널도 서명 필요. 서명 안 된 커널은 kexec 거부
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# kdump 동작 테스트 (주의: 시스템 크래시 발생!)
# 반드시 비프로덕션 환경에서 테스트

# SysRq로 강제 패닉
echo 1 > /proc/sys/kernel/sysrq
echo c > /proc/sysrq-trigger

# 또는 직접 panic() 호출 테스트 모듈
# echo 1 > /sys/kernel/debug/provoke-crash/type  (CONFIG_LKDTM)

# vmcore 생성 후 검증
crash /usr/lib/debug/boot/vmlinux-$(uname -r) /var/crash/*/vmcore
crash> sys            # 시스템 정보 확인
crash> bt             # 크래시 백트레이스
crash> log            # 커널 로그

# makedumpfile 검증
makedumpfile --check-params -x vmlinux /var/crash/vmcore

makedumpfile 심화

makedumpfile은 vmcore에서 불필요한 페이지를 제거하고 압축하여 덤프 크기를 대폭 줄입니다. 대용량 서버(수백 GB~수 TB RAM)에서는 사실상 표준 도구로 사용됩니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# makedumpfile 압축 알고리즘 비교
# -l: lzo 압축 (기본 권장, 빠른 압축/해제)
# -c: zlib 압축 (높은 압축률, 느림)
# -p: snappy 압축 (가장 빠름, 낮은 압축률)
# --zstd: zstd 압축 (makedumpfile 1.7.0+, zlib급 압축률 + lzo급 속도)

# 압축 알고리즘별 성능 비교 (256GB RAM 서버 기준 예시)
# 알고리즘   덤프 크기   덤프 시간   해제 시간
# 없음       ~12GB      ~90초      -
# lzo (-l)   ~4GB       ~100초     ~30초
# zlib (-c)  ~3GB       ~180초     ~50초
# snappy(-p) ~5GB       ~95초      ~25초
# zstd       ~3.2GB     ~110초     ~35초

# 덤프 레벨별 필터링 효과
makedumpfile -l -d 31 /proc/vmcore /var/crash/vmcore
# -d 31 (0b11111): 제로+캐시+프라이빗캐시+유저+프리 제외
# 일반적으로 원본의 1~5% 수준으로 축소

makedumpfile -l -d 1 /proc/vmcore /var/crash/vmcore
# -d 1: 제로 페이지만 제외 (유저 메모리 포함 - 상세 분석용)

# 덤프 전 예상 크기 확인 (dry-run)
makedumpfile --dry-run -l -d 31 /proc/vmcore 2>&1 | grep "Total size"
# 실제 IO 없이 필터링 결과 크기만 계산

# vmcoreinfo: makedumpfile이 사용하는 커널 메타데이터
# 크래시 커널의 메모리 레이아웃, 심볼 오프셋 등 포함
cat /sys/kernel/vmcoreinfo
# OSRELEASE=6.1.0
# PAGESIZE=4096
# SYMBOL(init_uts_ns)=ffffffff...
# OFFSET(task_struct.pid)=...

# makedumpfile -F: flattened 형식 (파이프/네트워크 전송용)
makedumpfile -l -d 31 -F /proc/vmcore \
    | ssh user@server "cat > /crash/vmcore.flat"
# 수신 측에서 복원:
makedumpfile -R /crash/vmcore < /crash/vmcore.flat

# split: 대용량 vmcore를 여러 파일로 분할 덤프
makedumpfile -l -d 31 --split-dumpfile=5 /proc/vmcore \
    /var/crash/vmcore.{1,2,3,4,5}
# 멀티코어 병렬 덤프 (makedumpfile 1.7.0+)
makedumpfile -l -d 31 --num-threads=4 /proc/vmcore /var/crash/vmcore

# vmcore에서 특정 메모리 영역 추출
makedumpfile --dump-dmesg /var/crash/vmcore /var/crash/dmesg.txt
# vmcore를 열지 않고 dmesg만 빠르게 추출

kdump 네트워크 덤프 심화

대용량 서버에서는 로컬 디스크 대신 네트워크를 통해 vmcore를 원격 저장하는 것이 일반적입니다. NFS와 SSH 두 가지 방식을 지원합니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
### NFS 덤프 설정 ###
# 서버 측: NFS export 설정
# /etc/exports:
/export/crash  10.0.0.0/8(rw,sync,no_root_squash)
exportfs -ra

# 클라이언트 측: /etc/kdump.conf (RHEL)
nfs 10.0.0.100:/export/crash
path /
core_collector makedumpfile -l -d 31 -F --message-level 7

### SSH 덤프 설정 ###
# 1. kdump 전용 SSH 키 생성 (패스프레이즈 없음)
ssh-keygen -t ed25519 -f /root/.ssh/kdump_id_ed25519 -N ""

# 2. 원격 서버에 공개키 등록
ssh-copy-id -i /root/.ssh/kdump_id_ed25519.pub user@crash-server

# 3. /etc/kdump.conf 설정
ssh user@crash-server
sshkey /root/.ssh/kdump_id_ed25519
path /var/crash/%HOST
# %HOST: 호스트명으로 자동 치환
core_collector makedumpfile -l -d 31 -F --message-level 7
# -F (flattened) 필수: SSH 파이프를 통한 전송

# 4. kdump 서비스가 캡처 커널의 initrd에 SSH 키를 포함하도록 재빌드
kdumpctl rebuild   # RHEL
kdump-config rebuild # Debian/Ubuntu

### 네트워크 덤프 시 주의사항 ###
# 1. 캡처 커널에서 네트워크 드라이버가 동작해야 함
#    - 커스텀 NIC 드라이버는 initrd에 포함 필요
#    - RHEL: /etc/kdump.conf에 extra_modules 지정
extra_modules bonding ixgbe mlx5_core
#    - dracut: dracut_args --add-drivers "ixgbe mlx5_core"

# 2. 본딩/팀 인터페이스: 캡처 커널에서 단일 NIC로 폴백
#    kdump는 첫 번째 슬레이브 인터페이스 사용

# 3. VLAN/브리지: kdump에서 자동 설정 (RHEL 8+)
#    복잡한 네트워크 구성은 kdumpctl showmem으로 검증

# 4. 대역폭: 256GB 서버 → -d 31로 ~5GB → 1Gbps에서 ~40초
#    10Gbps NIC 환경에서는 ~4초

가상 환경에서의 kdump

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
### KVM/QEMU 게스트에서 kdump ###
# 게스트 내부에서 일반적인 kdump 설정과 동일
# 추가 고려사항:

# 1. virtio 드라이버: 캡처 커널에서 필수
#    CONFIG_VIRTIO_BLK, CONFIG_VIRTIO_NET, CONFIG_VIRTIO_PCI
#    initrd에 자동 포함 (dracut --add-drivers virtio_blk)

# 2. crashkernel 메모리: 가상머신은 물리 서버보다 적게 필요
#    게스트 4GB RAM → crashkernel=128M 충분

# 3. pvpanic: 게스트 패닉을 호스트에 알림
#    QEMU: -device pvpanic
#    게스트 커널: CONFIG_PVPANIC
#    libvirt XML:
#    <panic model='pvpanic'/>

### 호스트에서 게스트 메모리 덤프 ###
# virsh dump: 호스트에서 직접 게스트 메모리 덤프
virsh dump <domain> /var/crash/guest-vmcore --memory-only
# --memory-only: ELF 형식 (crash 도구 호환)
# 기본: libvirt 자체 형식 (비호환)

# crash로 게스트 메모리 분석
crash <게스트의 vmlinux> /var/crash/guest-vmcore

# QEMU monitor에서 직접 덤프
# (Ctrl+Alt+2로 monitor 진입)
# dump-guest-memory -E /tmp/guest.elf
# -E: ELF 형식, -z: zlib 압축

### 컨테이너 환경 ###
# 컨테이너는 호스트 커널을 공유하므로 별도의 kdump 불필요
# 호스트에서 kdump 설정 → 모든 컨테이너의 크래시 포착
# Kubernetes: DaemonSet으로 kdump 설정 배포
#   - crashkernel= 부트 파라미터는 노드 수준에서 설정
#   - vmcore에서 컨테이너 식별: cgroup 경로 확인
crash> struct task_struct.cgroups <task 주소>
# → cgroup 경로에서 pod/container ID 추출

kdump 트러블슈팅

kdump 자체가 실패하면 vmcore를 얻을 수 없으므로, 사전에 반드시 테스트하고 문제를 해결해야 합니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
### kdump 실패 원인 진단 ###

# 1. 캡처 커널이 로드되었는지 확인
cat /sys/kernel/kexec_crash_loaded
# 0이면 로드 실패 → systemctl status kdump / journalctl -u kdump

# 2. crashkernel 메모리 예약 확인
dmesg | grep -i "crashkernel\|crash kernel\|reserving"
cat /proc/iomem | grep -i crash
# "Crash kernel" 영역이 없으면 예약 실패
# 원인: crashkernel= 파라미터 누락, 메모리 부족, 커널 미지원

# 3. kexec 로드 실패 디버깅
kexec -p -s --debug /boot/vmlinuz-$(uname -r) \
    --initrd=/boot/initrd.img-$(uname -r) \
    --append="root=/dev/sda1 irqpoll nr_cpus=1 reset_devices"
# --debug: 상세 로그 출력
# Secure Boot 관련 오류: kexec_file_load 사용 (-s 옵션)

# 4. 캡처 커널 initrd 내용 확인
# RHEL/CentOS:
lsinitrd /boot/initramfs-$(uname -r)kdump.img
lsinitrd /boot/initramfs-$(uname -r)kdump.img | grep -i "network\|ssh\|nfs"

# Debian/Ubuntu:
lsinitramfs /var/lib/kdump/initrd.img-$(uname -r)

# 5. 캡처 커널 부팅 로그 확인 (시리얼 콘솔 필수)
# 캡처 커널 커맨드라인에 시리얼 콘솔 추가:
# KDUMP_COMMANDLINE_APPEND="... console=ttyS0,115200"
# 또는 /etc/kdump.conf:
# kdump_commandline_append="console=ttyS0,115200n8"

# 6. 캡처 커널 부팅 시 셸 진입 (디버깅용)
# kdump initrd에 rd.shell 추가:
# RHEL: /etc/sysconfig/kdump
KDUMP_COMMANDLINE_APPEND="irqpoll nr_cpus=1 reset_devices rd.shell"
# 캡처 커널 부팅 실패 시 dracut shell로 진입

# 7. initrd 수동 재빌드
# RHEL:
kdumpctl rebuild
# 또는 직접:
dracut -f /boot/initramfs-$(uname -r)kdump.img $(uname -r) \
    --add kdumpbase --add-drivers "ixgbe mlx5_core"

# Debian/Ubuntu:
kdump-config rebuild
⚠️

kdump 트러블슈팅 핵심 원칙:

  • 시리얼 콘솔은 필수: kdump 실패 시 캡처 커널의 부팅 로그를 확인할 수 있는 유일한 방법입니다. IPMI SOL, iLO, iDRAC의 가상 시리얼 포트를 활용하십시오.
  • 비프로덕션에서 테스트: echo c > /proc/sysrq-trigger로 의도적 패닉을 발생시켜 전체 흐름을 검증하십시오.
  • 커널 업데이트 후 재검증: 커널 업데이트 시 캡처 커널 initrd가 자동 재빌드되지 않는 배포판이 있습니다. kdumpctl rebuild를 수행하십시오.
  • 메모리 부족 시: /sys/kernel/kexec_crash_size를 확인하고 crashkernel 값을 증가시키십시오. 최소 192MB(드라이버 적은 환경)에서 최대 2GB(대용량 서버, 네트워크 덤프)까지 필요할 수 있습니다.

kdump 운영 자동화

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# vmcore 자동 로테이션 (cron)
# /etc/cron.daily/kdump-rotate:
#!/bin/bash
CRASH_DIR="/var/crash"
MAX_DUMPS=5
MAX_AGE_DAYS=30

# 오래된 vmcore 삭제
find "$CRASH_DIR" -name "vmcore*" -mtime +$MAX_AGE_DAYS -delete

# 최대 개수 초과 시 가장 오래된 것부터 삭제
ls -1t "$CRASH_DIR"/*/vmcore* 2>/dev/null | tail -n +$((MAX_DUMPS+1)) | \
    xargs -r rm -f

# kdump 상태 모니터링 스크립트
#!/bin/bash
check_kdump() {
    local loaded=$(cat /sys/kernel/kexec_crash_loaded 2>/dev/null)
    local crash_size=$(cat /sys/kernel/kexec_crash_size 2>/dev/null)

    if [[ "$loaded" != "1" ]]; then
        echo "CRITICAL: kdump 캡처 커널 미로드!"
        return 2
    fi

    if [[ -z "$crash_size" || "$crash_size" -eq 0 ]]; then
        echo "CRITICAL: crashkernel 메모리 미예약!"
        return 2
    fi

    # 디스크 여유 공간 확인
    local avail=$(df -BG /var/crash | awk 'NR==2{print $4}' | tr -d 'G')
    if [[ "$avail" -lt 10 ]]; then
        echo "WARNING: /var/crash 여유 공간 ${avail}GB (10GB 미만)"
        return 1
    fi

    echo "OK: kdump 정상 (crashkernel=$((crash_size/1048576))MB, 여유=${avail}GB)"
    return 0
}

VMCOREINFO 메커니즘

VMCOREINFO는 크래시 커널이 원본 커널의 메모리 레이아웃, 심볼 오프셋, 구조체 크기 등을 파악하기 위해 사용하는 메타데이터입니다. makedumpfile이 vmcore에서 불필요한 페이지를 분류하려면 원본 커널의 내부 구조를 알아야 하며, VMCOREINFO가 이 정보를 제공합니다.

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* VMCOREINFO 생성: kernel/crash_core.c */

/* 커널 부팅 시 vmcoreinfo_data에 메타데이터 기록 */
static char *vmcoreinfo_data;
static size_t vmcoreinfo_size;
static unsigned long vmcoreinfo_note[VMCOREINFO_NOTE_SIZE / sizeof(unsigned long)];

/* 정보 등록 매크로 */
#define VMCOREINFO_SYMBOL(name) \
    vmcoreinfo_append_str("SYMBOL(%s)=%lx\\n", #name, (unsigned long)&name)

#define VMCOREINFO_OFFSET(name, field) \
    vmcoreinfo_append_str("OFFSET(%s.%s)=%lu\\n", #name, #field, \
        (unsigned long)offsetof(struct name, field))

#define VMCOREINFO_SIZE(name) \
    vmcoreinfo_append_str("SIZE(%s)=%lu\\n", #name, \
        (unsigned long)sizeof(struct name))

#define VMCOREINFO_NUMBER(name) \
    vmcoreinfo_append_str("NUMBER(%s)=%ld\\n", #name, (long)name)

#define VMCOREINFO_CONFIG(name) \
    vmcoreinfo_append_str("CONFIG_%s=y\\n", #name)

/* crash_save_vmcoreinfo_init(): 부팅 시 호출 */
void crash_save_vmcoreinfo_init(void)
{
    /* 기본 시스템 정보 */
    VMCOREINFO_OSRELEASE(init_uts_ns.name.release);
    vmcoreinfo_append_str("PAGESIZE=%ld\\n", PAGE_SIZE);

    /* 핵심 심볼 주소 */
    VMCOREINFO_SYMBOL(init_uts_ns);
    VMCOREINFO_SYMBOL(_stext);
    VMCOREINFO_SYMBOL(swapper_pg_dir);
    VMCOREINFO_SYMBOL(mem_map);        /* FLATMEM */
    VMCOREINFO_SYMBOL(mem_section);     /* SPARSEMEM */
    VMCOREINFO_SYMBOL(vmemmap_base);    /* x86_64 */
    VMCOREINFO_SYMBOL(page_offset_base);
    VMCOREINFO_SYMBOL(vmalloc_base);

    /* struct page 레이아웃 (makedumpfile이 페이지 타입을 판별하기 위해 필수) */
    VMCOREINFO_SIZE(page);
    VMCOREINFO_OFFSET(page, flags);
    VMCOREINFO_OFFSET(page, _refcount);
    VMCOREINFO_OFFSET(page, mapping);
    VMCOREINFO_OFFSET(page, lru);
    VMCOREINFO_OFFSET(page, private);

    /* 메모리 모델 (SPARSEMEM_VMEMMAP, FLATMEM 등) */
    VMCOREINFO_NUMBER(MAX_NR_ZONES);
    VMCOREINFO_NUMBER(NR_FREE_PAGES);
    VMCOREINFO_NUMBER(PG_lru);
    VMCOREINFO_NUMBER(PG_private);
    VMCOREINFO_NUMBER(PG_swapcache);
    VMCOREINFO_NUMBER(PG_slab);
    VMCOREINFO_NUMBER(PG_buddy);       /* 프리 페이지 식별 */
    VMCOREINFO_NUMBER(PG_hugetlb);

    /* KASLR 오프셋 (주소 공간 랜덤화) */
    VMCOREINFO_NUMBER(phys_base);
    VMCOREINFO_NUMBER(KERNELOFFSET);

    /* 추가 서브시스템 정보 */
    arch_crash_save_vmcoreinfo();       /* 아키텍처별 추가 정보 */
}
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# VMCOREINFO 내용 확인 (실행 중인 커널)
cat /sys/kernel/vmcoreinfo
# 출력 예시 (16진수 주소:크기 형식의 노트 위치):
# 첫 줄: 물리 주소와 크기 (예: 3e0a9c00 1000)

# makedumpfile로 vmcore 내의 VMCOREINFO 추출
makedumpfile --dump-vmcoreinfo /var/crash/vmcore /tmp/vmcoreinfo.txt

# 출력 예시:
# OSRELEASE=6.1.0-amd64
# PAGESIZE=4096
# SYMBOL(init_uts_ns)=ffffffff82a13580
# SYMBOL(swapper_pg_dir)=ffffffff82c10000
# SYMBOL(mem_section)=ffffffff83412000
# SYMBOL(vmemmap_base)=ffffea0000000000
# OFFSET(page.flags)=0
# OFFSET(page._refcount)=28
# OFFSET(page.mapping)=8
# SIZE(page)=64
# NUMBER(PG_lru)=3
# NUMBER(PG_slab)=7
# NUMBER(PG_buddy)=10
# NUMBER(KERNELOFFSET)=0
# CONFIG_SPARSEMEM_VMEMMAP=y
# CRASHTIME=1707000000

# VMCOREINFO가 makedumpfile에 전달되는 경로:
# 1. 커널 부팅 시 vmcoreinfo_data에 기록
# 2. kexec -p 시 vmcoreinfo를 ELF NOTE로 elfcorehdr에 포함
# 3. 크래시 발생 → 캡처 커널 부팅
# 4. /proc/vmcore의 PT_NOTE 세그먼트에 VMCOREINFO 노출
# 5. makedumpfile이 PT_NOTE에서 VMCOREINFO 파싱
# 6. 심볼/오프셋 정보로 struct page 분석 → 페이지 타입별 필터링
VMCOREINFO 생성 → 전달 → 사용 흐름 1. 커널 부팅 vmcoreinfo_data SYMBOL, OFFSET, SIZE NUMBER, CONFIG 기록 2. kexec -p (캡처 커널 로드) elfcorehdr ELF NOTE에 VMCOREINFO 포함 3. 크래시 발생 crash_save_vmcoreinfo CRASHTIME 타임스탬프 CPU별 레지스터 저장 4. 캡처 커널 /proc/vmcore PT_NOTE 세그먼트에 VMCOREINFO 노출 makedumpfile 파싱 VMCOREINFO에서 심볼/오프셋 추출 → struct page 레이아웃 파악 페이지 타입 분류 page.flags의 PG_lru, PG_slab, PG_buddy 비트로 분류 필터링 적용 (-d 플래그) 유저/캐시/프리/제로 페이지 제외 → 커널 데이터만 vmcore에 저장 VMCOREINFO 주요 항목과 makedumpfile 활용 심볼 주소 (SYMBOL) mem_section → SPARSEMEM 섹션 배열 vmemmap_base → 가상 memmap 시작 page_offset_base → 직접 매핑 시작 _stext → 커널 텍스트 시작 swapper_pg_dir → PGD 주소 → 물리↔가상 주소 변환에 사용 오프셋/크기 (OFFSET/SIZE) page.flags → 페이지 상태 비트 page.mapping → 파일/anon 판별 page._refcount → 사용 여부 SIZE(page) → memmap 순회 단위 → struct page 정확한 파싱에 사용 → 커널 버전간 구조 변경 대응 플래그 번호 (NUMBER) PG_lru → LRU 리스트 페이지 PG_slab → 슬랩 할당 페이지 PG_buddy → 버디 프리 페이지 PG_hugetlb → Huge Page NR_FREE_PAGES → 프리 페이지 수 → -d 플래그 필터링 기준
ℹ️

VMCOREINFO와 KASLR: KASLR(Kernel Address Space Layout Randomization)이 활성화된 시스템에서는 커널 심볼의 실제 주소가 매 부팅마다 달라집니다. VMCOREINFO의 KERNELOFFSET 값이 KASLR 오프셋을 기록하므로, makedumpfile과 crash 유틸리티가 올바른 주소를 계산할 수 있습니다. NUMBER(KERNELOFFSET)=0x2a000000이면 모든 심볼 주소에 이 값이 더해진 것입니다.

vmcore ELF 형식 내부 구조

/proc/vmcore는 ELF64 형식의 가상 파일로, 캡처 커널이 크래시된 커널의 전체 물리 메모리를 ELF 세그먼트로 노출합니다. kexec가 생성한 elfcorehdr가 ELF 헤더와 프로그램 헤더 테이블의 원본이 됩니다.

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* vmcore ELF 구조 개요 */

/* ELF64 Header */
struct elf64_hdr {
    unsigned char e_ident[16];  /* 매직: 0x7f 'E' 'L' 'F' */
    Elf64_Half  e_type;         /* ET_CORE (4) */
    Elf64_Half  e_machine;      /* EM_X86_64 (62) 등 */
    Elf64_Off   e_phoff;        /* 프로그램 헤더 테이블 오프셋 */
    Elf64_Half  e_phnum;        /* 프로그램 헤더 수 (PT_NOTE + PT_LOAD들) */
    /* ... */
};

/* 프로그램 헤더: 두 종류 */
/* 1. PT_NOTE (1개): VMCOREINFO + CPU별 레지스터 */
struct elf64_phdr {
    Elf64_Word  p_type;    /* PT_NOTE (4) */
    Elf64_Off   p_offset;  /* NOTE 데이터 오프셋 */
    Elf64_Xword p_filesz;  /* NOTE 전체 크기 */
    /* ... */
};

/* 2. PT_LOAD (물리 메모리 세그먼트당 1개) */
struct elf64_phdr {
    Elf64_Word  p_type;    /* PT_LOAD (1) */
    Elf64_Off   p_offset;  /* vmcore 파일 내 오프셋 */
    Elf64_Addr  p_vaddr;   /* 가상 주소 (보통 0) */
    Elf64_Addr  p_paddr;   /* 물리 주소 (핵심!) */
    Elf64_Xword p_filesz;  /* 세그먼트 크기 */
    Elf64_Xword p_memsz;   /* 메모리 크기 (= p_filesz) */
    /* ... */
};

/* PT_NOTE 내부 구조 */
/*
 * NOTE 1: VMCOREINFO
 *   name:  "VMCOREINFO"
 *   type:  0 (NT_VMCOREINFO는 커스텀)
 *   desc:  VMCOREINFO 텍스트 데이터
 *
 * NOTE 2~N: CPU별 레지스터 (PRSTATUS)
 *   name:  "CORE"
 *   type:  NT_PRSTATUS (1)
 *   desc:  struct elf_prstatus (레지스터 덤프)
 *          - pr_pid, pr_reg (pt_regs)
 *          크래시 시점 각 CPU의 레지스터 상태
 */
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# vmcore ELF 헤더 분석
readelf -h /var/crash/vmcore
# ELF Header:
#   Type:    CORE (Core file)
#   Machine: Advanced Micro Devices X86-64

# 프로그램 헤더 확인 (PT_NOTE + PT_LOAD 목록)
readelf -l /var/crash/vmcore
# Program Headers:
#   Type   Offset             VirtAddr           PhysAddr
#   NOTE   0x0000000000001000 0x0000000000000000 0x0000000000000000
#   LOAD   0x0000000000040000 0x0000000000000000 0x0000000000001000
#          FileSiz: 0x000000000009f000  MemSiz: 0x000000000009f000
#   LOAD   0x00000000000df000 0x0000000000000000 0x0000000000100000
#          FileSiz: 0x000000003fefffff  MemSiz: 0x000000003fefffff
#   ... (물리 메모리 영역별 PT_LOAD)

# PT_NOTE 내의 VMCOREINFO와 PRSTATUS 확인
readelf -n /var/crash/vmcore
# Notes at offset 0x1000:
#   VMCOREINFO  0x00001234  (VMCOREINFO 메타데이터)
#   CORE        0x00000150  NT_PRSTATUS (CPU 0 레지스터)
#   CORE        0x00000150  NT_PRSTATUS (CPU 1 레지스터)
#   ...

# elfcorehdr: kexec가 예약 메모리에 미리 작성한 ELF 헤더
# 캡처 커널 부팅 파라미터로 전달:
# elfcorehdr=0x3ff00000 (물리 주소)
# 캡처 커널의 /proc/vmcore 드라이버가 이 주소를 읽어 ELF 구조 파악
dmesg | grep elfcorehdr
# [    0.000000] elfcorehdr: 0x3ff00000-0x3ff10000

# /proc/vmcore 구현: fs/proc/vmcore.c
# - read_vmcore(): PT_LOAD 세그먼트의 물리 주소를 ioremap으로 읽기
# - mmap_vmcore(): 대용량 덤프 시 mmap으로 효율적 접근
# - vmcore_init(): elfcorehdr 파싱 → 세그먼트 목록 구성
/proc/vmcore ELF64 구조 ELF64 Header e_type = ET_CORE e_phnum = N+1 Program Header Table phdr[0]: PT_NOTE phdr[1]: PT_LOAD (0~640KB) phdr[2]: PT_LOAD (1MB~...) phdr[N]: PT_LOAD (마지막) PT_NOTE 세그먼트 VMCOREINFO (커널 메타) CPU0 regs CPU1 regs PT_LOAD 세그먼트 (물리 메모리) p_paddr = 물리 시작 주소 (예: 0x100000) p_filesz = 영역 크기 (예: 0x3FF00000 = ~1GB) p_offset = vmcore 파일 내 데이터 위치 캡처 커널이 ioremap/copy_oldmem으로 크래시 메모리 접근 elfcorehdr 생성 과정 kexec -p 실행 캡처 커널 로드 elfcorehdr 생성 /proc/iomem 기반 PT_LOAD crashkernel 영역 저장 예약 메모리에 기록 캡처 커널 부팅 파라미터 elfcorehdr=0x3ff00000 * /proc/iomem의 "System RAM" 영역 → PT_LOAD 세그먼트로 매핑 * crashkernel 예약 영역은 PT_LOAD에서 제외 (캡처 커널 자체 메모리)

kexec 빠른 재부팅

kexec는 kdump(크래시 덤프) 외에도 빠른 재부팅 용도로 널리 사용됩니다. BIOS/UEFI POST 과정과 부트로더를 우회하여 직접 새 커널을 부팅하므로, 대형 서버에서 수 분 걸리는 재부팅 시간을 수 초로 단축할 수 있습니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
### kexec 빠른 재부팅 기본 사용법 ###

# 1. 새 커널 이미지 로드 (일반 kexec, -p가 아님)
KVER=$(uname -r)
VMLINUX=/boot/vmlinuz-$KVER
INITRD=$(ls -1 /boot/initr*${KVER}* | head -1)

kexec -l "$VMLINUX" \
    --initrd="$INITRD" \
    --append="$(cat /proc/cmdline)"
# --append: 현재 부팅 파라미터 재사용
# -l: 일반 로드 (재부팅용), -p: 패닉 로드 (kdump용)

# 2. 즉시 재부팅 (kexec로 전환)
systemctl kexec
# 또는 직접:
kexec -e
# 또는:
reboot   # 일반 reboot는 보통 일반 재부팅 경로입니다 (kexec 보장 아님)

### kexec_file_load (-s 옵션) 사용 ###
kexec -l -s "$VMLINUX" \
    --initrd="$INITRD" \
    --append="$(cat /proc/cmdline)"
# -s: kexec_file_load 사용 (Secure Boot 호환)

### 재부팅 시간 비교 (일반적인 서버 기준) ###
# 일반 재부팅 (reboot):
#   BIOS POST:       30~120초 (서버 하드웨어 초기화)
#   부트로더(GRUB):   5~10초
#   커널 부팅:        10~30초
#   총:              45~160초
#
# kexec 재부팅 (kexec -e):
#   purgatory:       <1초
#   커널 부팅:        10~30초
#   총:              10~31초  ← BIOS POST 완전 건너뜀

### kexec 재부팅 자동화 (커널 업데이트 후) ###
# 새 커널 설치 후 kexec로 빠르게 전환
NEW_KERNEL=$(ls -1t /boot/vmlinuz-* | head -1)
NEW_INITRD=$(ls -1t /boot/initrd.img-* | head -1)

# 현재 커널 파라미터에서 crashkernel 등 유지
CMDLINE=$(cat /proc/cmdline | sed "s|BOOT_IMAGE=[^ ]*||")

kexec -l -s "$NEW_KERNEL" \
    --initrd="$NEW_INITRD" \
    --append="$CMDLINE"

# 확인 후 실행
cat /sys/kernel/kexec_loaded   # 1이면 로드 성공
systemctl kexec                # 서비스 정상 종료 후 kexec
⚠️

kexec 빠른 재부팅 주의사항:

  • 하드웨어 초기화 생략: BIOS POST를 건너뛰므로 하드웨어가 이전 상태를 유지합니다. 일부 디바이스(GPU, RAID 컨트롤러)가 정상 초기화되지 않을 수 있습니다.
  • 파일시스템 정합성: systemctl kexec 대신 kexec -e를 직접 호출하면 파일시스템 동기화 없이 즉시 전환됩니다. 데이터 손실 위험이 있으므로 반드시 systemctl kexec 또는 sync && kexec -e를 사용하십시오.
  • 부트로더 설정 무시: GRUB의 기본 커널 설정이 변경되지 않으므로, 다음 정상 재부팅 시 이전 커널로 돌아갈 수 있습니다.
  • KEXEC_PRESERVE_CONTEXT 플래그: 하이버네이션 복원 전용. 일반 재부팅에서는 사용하지 마십시오.

crashkernel 예약 내부 메커니즘

커널이 crashkernel= 부팅 파라미터를 처리하여 물리 메모리를 예약하는 과정을 상세히 살펴봅니다. 예약 실패 시 디버깅에 필수적인 지식입니다.

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* kernel/crash_core.c - crashkernel 파라미터 파싱 */

/* 부팅 초기(setup_arch → reserve_crashkernel)에서 호출 */
int __init parse_crashkernel(
    char *cmdline,
    unsigned long long system_ram,
    unsigned long long *crash_size,
    unsigned long long *crash_base)
{
    /* 파라미터 파싱 우선순위:
     * 1. crashkernel=X@Y  (고정 크기 + 고정 위치)
     * 2. crashkernel=X    (고정 크기, 커널이 위치 결정)
     * 3. crashkernel=range1:size1,range2:size2,...
     * 4. crashkernel=auto  (배포판 정의 테이블 사용)
     */

    /* "auto" 처리 (커널 내장 테이블) */
    if (strncmp(cmdline, "auto", 4) == 0) {
        /* CONFIG_CRASH_AUTO_STR 또는 아키텍처별 기본값 */
        /* x86_64 기본 (RHEL): "1G-4G:160M,4G-64G:192M,
         *   64G-1T:256M,1T-:512M" */
    }

    /* 범위 기반 파싱: "512M-2G:64M,2G-:256M" */
    /* system_ram이 해당 범위에 속하는 크기 선택 */
    return parse_crashkernel_mem(ck_cmdline, system_ram,
                                 crash_size, crash_base);
}

/* arch/x86/kernel/setup.c - 실제 예약 */
void __init reserve_crashkernel(void)
{
    unsigned long long crash_size, crash_base;

    /* 1. 파라미터 파싱 */
    parse_crashkernel(boot_command_line, memblock_phys_mem_size(),
                      &crash_size, &crash_base);

    /* 2. high/low 분리 처리 */
    if (crash_base == 0) {
        /* 위치 미지정 → 커널이 자동 결정 */

        /* crashkernel=X,high: 4GB 이상 영역에서 할당 시도 */
        crash_base = memblock_phys_alloc_range(
            crash_size, SZ_256M,     /* 256MB 정렬 */
            SZ_4G, MEMBLOCK_ALLOC_ACCESSIBLE);

        if (!crash_base) {
            /* 4GB 이상 실패 → 4GB 미만에서 재시도 */
            crash_base = memblock_phys_alloc_range(
                crash_size, SZ_1M,
                0, SZ_4G);
        }

        /* crashkernel=X,low: 4GB 미만 영역 추가 예약 */
        /* swiotlb(DMA 바운스 버퍼)용 최소 영역 */
        if (high_allocated && low_size) {
            low_base = memblock_phys_alloc_range(
                low_size, SZ_1M, 0, SZ_4G);
        }
    } else {
        /* 위치 지정: crashkernel=X@Y */
        memblock_reserve(crash_base, crash_size);
    }

    /* 3. crashk_res 리소스 등록 (/proc/iomem에 "Crash kernel" 표시) */
    crashk_res.start = crash_base;
    crashk_res.end = crash_base + crash_size - 1;
    insert_resource(&iomem_resource, &crashk_res);

    /* 4. sysfs 노출: /sys/kernel/kexec_crash_size */
    pr_info("Reserving %ldMB of memory at %ldMB for crashkernel\\n",
            (unsigned long)(crash_size >> 20),
            (unsigned long)(crash_base >> 20));
}
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# crashkernel 예약 과정 디버깅

# 부팅 로그에서 예약 성공/실패 확인
dmesg | grep -i "crashkernel\|crash kernel\|Reserving"
# 성공: [    0.000000] Reserving 256MB of memory at 704MB for crashkernel
# 실패: [    0.000000] crashkernel reservation failed - No suitable area found

# memblock 디버깅 (early_param "memblock=debug")
# GRUB: linux ... memblock=debug
dmesg | grep "memblock_reserve.*crash"
# [    0.000000] memblock_reserve: [0x2c000000-0x3bffffff] crashkernel

# /proc/iomem에서 예약 영역 확인
cat /proc/iomem | grep -A1 -i crash
#   2c000000-3bffffff : Crash kernel

# /sys/kernel에서 런타임 확인
cat /sys/kernel/kexec_crash_size   # 예약 크기 (바이트)
cat /sys/kernel/kexec_crash_loaded # 캡처 커널 로드 여부

# crashkernel 예약 실패 시 디버깅 순서:
# 1. dmesg에서 에러 메시지 확인
# 2. /proc/iomem에서 메모리 레이아웃 확인 (4GB 미만 여유 공간)
# 3. NUMA 시스템: Node 0 메모리가 충분한지 확인
#    numactl --hardware | head -10
# 4. 정렬 요구사항: x86_64는 256MB(high) 또는 1MB(low) 정렬
# 5. memblock=debug 부팅 파라미터로 상세 로그 확인
# 6. high/low 분리: crashkernel=256M,high crashkernel=72M,low 시도

early kdump (부팅 초기 크래시 캡처)

일반 kdump는 systemd의 kdump 서비스가 시작된 이후에만 캡처 커널을 로드합니다. 그러나 드라이버 초기화, 파일시스템 마운트 등 부팅 초기 단계에서 크래시가 발생하면 vmcore를 얻을 수 없습니다. early kdump는 initrd/initramfs 단계에서 캡처 커널을 미리 로드하여 이 문제를 해결합니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
### early kdump 동작 원리 ###
# 일반 kdump: 부팅 → systemd → 네트워크 → kdump.service → kexec -p
#   → 이 시점 이전의 크래시는 캡처 불가!
#
# early kdump: 부팅 → initrd 내 dracut 모듈 → kexec -p (매우 초기)
#   → initrd 단계부터 캡처 가능

### RHEL/CentOS에서 early kdump 설정 ###

# 1. /etc/sysconfig/kdump에서 early kdump 활성화
# KDUMP_EARLY=1

# 2. dracut에 early-kdump 모듈 포함하여 initramfs 재빌드
dracut -f --add early-kdump /boot/initramfs-$(uname -r).img $(uname -r)
# 또는 /etc/dracut.conf.d/early-kdump.conf:
# add_dracutmodules+=" early-kdump "

# 3. 커널 파라미터에 rd.earlykdump 추가
# GRUB: linux ... crashkernel=256M rd.earlykdump

# 4. initramfs 재빌드 후 재부팅
# BIOS(RHEL): grub2-mkconfig -o /boot/grub2/grub.cfg
# UEFI(RHEL): grub2-mkconfig -o /boot/efi/EFI/redhat/grub.cfg
# Debian/Ubuntu: update-grub
reboot

# 5. 확인: 부팅 로그에서 early kdump 활성화 여부
dmesg | grep -i "early.*kdump\|earlykdump"
journalctl -b | grep -i "early.*kdump"

### early kdump initrd 내부 동작 ###
# dracut의 early-kdump 모듈 (/usr/lib/dracut/modules.d/99earlykdump/):
# 1. initrd 내에 vmlinuz + kdump initrd를 별도로 포함
# 2. dracut의 초기 훅(pre-trigger)에서 kexec -p 실행
# 3. 이 시점에서 캡처 커널이 로드되어 패닉에 대응 가능
# 4. 이후 정상 부팅 진행 → kdump.service에서 필요 시 재로드

### early kdump의 제약사항 ###
# - initrd 크기 증가 (vmlinuz + kdump initrd 추가 포함)
#   → 일반 initrd ~50MB → early kdump 포함 시 ~150MB
# - 네트워크 덤프 불가 (initrd 단계에서 네트워크 미설정)
#   → 로컬 디스크 덤프만 가능
# - root 파일시스템 마운트 전이므로 /var/crash 사용 불가
#   → dracut 임시 영역에 저장 후 나중에 복사

fadump (Firmware-Assisted Dump)

fadump는 IBM POWER 아키텍처(ppc64le)에서 지원하는 펌웨어 기반 크래시 덤프 메커니즘입니다. kexec 기반 kdump와 달리 펌웨어가 메모리 보존과 CPU 상태 저장을 담당하므로, 일부 kdump의 한계를 극복합니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
### fadump vs kdump 비교 ###
# 항목           fadump (POWER)              kdump (범용)
# ───────────────────────────────────────────────────────────
# 아키텍처       IBM POWER (ppc64le)          x86, arm64, s390x 등
# 메모리 보존    펌웨어가 보존                kexec가 예약 영역 사용
# CPU 상태       펌웨어가 저장                NMI로 다른 CPU 정지+저장
# 부팅 방식      정상 부팅 (같은 커널 재시작) 캡처 커널 (별도 커널)
# 메모리 예약    최소 (부팅 메모리만)         고정 crashkernel 영역
# 초기화 실패    펌웨어가 처리하므로 안정적   캡처 커널 초기화 실패 가능
# 디바이스 접근  정상 부팅이므로 전체 가용    제한된 드라이버만 로드

### fadump 커널 CONFIG ###
# CONFIG_FA_DUMP=y          # fadump 지원
# CONFIG_PRESERVE_FA_DUMP=y # fadump 메모리 보존 (캡처 커널용)

### fadump 설정 (POWER 시스템) ###

# 1. 커널 파라미터
# fadump=on [fadump_reserve_mem=512M]
# crashkernel= 대신 fadump= 사용

# 2. /etc/kdump.conf에서 fadump 사용 (RHEL)
# RHEL은 kdump 인프라를 재사용하되 백엔드만 fadump로 전환
# /etc/sysconfig/kdump:
# KDUMP_FADUMP=yes

# 3. 등록 상태 확인
cat /sys/kernel/fadump_registered
# 1 = fadump 등록됨 (크래시 대응 준비 완료)

cat /sys/kernel/fadump_enabled
# 1 = fadump 활성화됨

# 4. fadump 수동 등록/해제
echo 1 > /sys/kernel/fadump_registered   # 등록
echo 0 > /sys/kernel/fadump_registered   # 해제

### fadump 동작 흐름 ###
# 1. 커널 패닉 발생
# 2. 펌웨어(OPAL/PHYP)가 제어권 인수
# 3. 펌웨어가 모든 CPU 레지스터 + 메모리 레이아웃 저장
# 4. 펌웨어가 정상 부팅 절차 수행 (같은 커널 이미지로)
# 5. 부팅 시 커널이 fadump 메모리 감지
# 6. /proc/vmcore로 크래시 메모리 노출
# 7. kdump 서비스가 makedumpfile로 vmcore 저장
# 8. 저장 완료 후 fadump 메모리 해제 → 정상 운영

### s390x (IBM Z): VMDUMP / stand-alone dump ###
# IBM Z 메인프레임은 하이퍼바이저(z/VM, LPAR)를 통한
# 별도 덤프 메커니즘을 지원합니다.
# CONFIG_CRASH_DUMP + CONFIG_ZFCPDUMP (zfcp 디바이스 덤프)
# vmur (VM reader) 또는 zgetdump 유틸리티로 덤프 수집

kexec 보안 고려사항

kexec는 임의 커널을 로드·실행할 수 있으므로 보안 관점에서 중요한 공격 표면이 됩니다. Secure Boot, Lockdown, IMA 등 다양한 보안 메커니즘과의 상호작용을 이해해야 합니다.

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* kexec 보안 검사 흐름 (kernel/kexec.c, security/security.c) */

/* 1. LSM 보안 훅 */
int security_kernel_load_data(enum kernel_load_data_id id, bool contents)
{
    /* id = LOADING_KEXEC_IMAGE 또는 LOADING_KEXEC_INITRAMFS */
    /* SELinux, AppArmor 등 LSM이 kexec 허용 여부 결정 */
    return call_int_hook(kernel_load_data, id, contents);
}

/* 2. Lockdown 검사 */
/* CONFIG_LOCK_DOWN_KERNEL_FORCE_INTEGRITY 또는 lockdown=integrity */
/* → kexec_load() 시스템 콜 완전 차단 (서명 검증 불가) */
/* → kexec_file_load()만 허용 (커널 내부 서명 검증) */

/* 3. kexec_file_load 서명 검증 */
int kexec_image_verify_sig(struct kimage *image, void *buf,
                           unsigned long buf_len)
{
    /* PE/COFF 서명 검증 (x86 bzImage) */
    /* 또는 모듈 서명 방식 검증 */
    return verify_pefile_signature(buf, buf_len,
                                  VERIFY_USE_SECONDARY_KEYRING,
                                  VERIFYING_KEXEC_PE_SIGNATURE);
}

/* 4. IMA (Integrity Measurement Architecture) 연동 */
/* CONFIG_IMA_APPRAISE_MODSIG, CONFIG_IMA_ARCH_POLICY */
/* IMA 정책에 kexec 이미지 측정/감사 규칙 추가 가능 */
/* ima_policy: "appraise func=KEXEC_KERNEL_CHECK appraise_type=imasig" */

/* 키링(keyring) 계층 구조:
 * .builtin_trusted_keys  → 커널 빌드 시 내장된 키
 * .secondary_trusted_keys → 런타임에 추가 가능한 키
 * .machine (UEFI db 키)  → 펌웨어의 Secure Boot 키
 * .platform (MOK 키)     → Machine Owner Key
 *
 * kexec_file_load는 이 키링들로 커널 이미지 서명 검증
 */
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# Lockdown 모드 확인
cat /sys/kernel/security/lockdown
# [none] integrity confidentiality
# integrity: kexec_load 차단, kexec_file_load는 서명 시 허용
# confidentiality: kexec 완전 차단

# kexec 관련 Lockdown 제한 사항
# lockdown=integrity 일 때:
# - kexec_load (kexec -l 기본) → 차단 (EPERM)
# - kexec_file_load (kexec -l -s) → 서명 검증 후 허용
# - /dev/mem, /dev/kmem → 읽기 전용
# - ACPI 테이블 오버라이드 → 차단

# Secure Boot + kexec 환경에서의 kdump 설정
# 캡처 커널도 서명되어야 함!
# 배포판 커널은 자동으로 서명됨 (Canonical, Red Hat 키)
# 커스텀 커널은 MOK(Machine Owner Key)로 직접 서명 필요:
sbsign --key MOK.key --cert MOK.crt \
    /boot/vmlinuz-custom /boot/vmlinuz-custom.signed

# MOK 등록
mokutil --import MOK.der
# 재부팅 후 MOK Manager에서 등록 확인

# kexec_file_load 서명 검증 실패 디버깅
dmesg | grep -i "kexec.*sig\|PKCS\|verify"
# kexec_file: verification failed: -22 → 서명 없거나 키 미등록

# 시스템 키링 확인
keyctl list %:.builtin_trusted_keys
keyctl list %:.secondary_trusted_keys
keyctl list %:.machine
# kexec 커널 이미지의 서명이 이 키링 중 하나로 검증 가능해야 함

# IMA 정책에서 kexec 감사 확인
cat /sys/kernel/security/ima/policy | grep -i kexec
# appraise func=KEXEC_KERNEL_CHECK appraise_type=imasig

# SELinux에서 kexec 권한 확인
sesearch -A -s unconfined_t -t kernel_t -c system -p module_load
# kexec는 내부적으로 module_load 권한을 사용
☢️

kexec 보안 위험: 서명 검증 없이 kexec를 허용하면, 공격자가 루트 권한을 획득한 후 악의적인 커널을 로드하여 전체 시스템을 장악할 수 있습니다. 이는 Secure Boot의 보안 체인을 완전히 우회하는 것입니다. 프로덕션 환경에서는 반드시 lockdown=integrityCONFIG_KEXEC_SIG_FORCE를 활성화하십시오. kexec_load 시스템 콜은 서명 검증이 불가능하므로 보안 환경에서는 CONFIG_KEXEC=n으로 아예 비활성화하고 CONFIG_KEXEC_FILE만 사용하는 것을 권장합니다.

crash_hotplug (동적 핫플러그 지원)

Linux 6.5부터 도입된 CONFIG_CRASH_HOTPLUG는 CPU나 메모리 핫플러그 이벤트 발생 시 캡처 커널의 elfcorehdr을 자동으로 업데이트합니다. 이전에는 CPU/메모리 추가·제거 후 kdump 서비스를 재시작하여 캡처 커널을 다시 로드해야 했습니다.

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* kernel/crash_core.c - crash_hotplug 핵심 구조 (6.5+) */

/* CPU 핫플러그 콜백 */
static int crash_cpuhp_online(unsigned int cpu)
{
    /* 새 CPU 추가 시:
     * 1. elfcorehdr의 PT_NOTE에 새 CPU의 PRSTATUS 공간 확보
     * 2. 기존 PT_LOAD 세그먼트는 변경 불필요 (메모리 레이아웃 동일)
     * 3. elfcorehdr in-place 업데이트 (캡처 커널 재로드 불필요!)
     */
    crash_handle_hotplug_event(KEXEC_CRASH_HP_ADD_CPU, cpu);
    return 0;
}

/* 메모리 핫플러그 콜백 */
static int crash_memhp_notifier(struct notifier_block *nb,
                                unsigned long action, void *data)
{
    switch (action) {
    case MEM_ONLINE:
        /* 새 메모리 추가:
         * 1. 새 메모리 영역에 대한 PT_LOAD 세그먼트 추가
         * 2. elfcorehdr 업데이트
         * 3. 캡처 커널 재로드 없이 in-place 수정
         */
        crash_handle_hotplug_event(KEXEC_CRASH_HP_ADD_MEMORY, 0);
        break;
    case MEM_OFFLINE:
        /* 메모리 제거: 해당 PT_LOAD 세그먼트 제거 */
        crash_handle_hotplug_event(KEXEC_CRASH_HP_REMOVE_MEMORY, 0);
        break;
    }
    return NOTIFY_OK;
}

/* elfcorehdr 업데이트 핵심 함수 */
void crash_handle_hotplug_event(unsigned int hp_action, unsigned int cpu)
{
    /* kexec_mutex 획득 → elfcorehdr 재생성 → 기존 위치에 덮어쓰기 */
    /* 캡처 커널 이미지 자체는 그대로 유지 */
    /* elfcorehdr만 업데이트하므로 매우 빠름 (ms 단위) */

    mutex_lock(&kexec_mutex);

    /* 새 elfcorehdr 생성 */
    crash_prepare_elf64_headers(…);

    /* 예약 영역 내 elfcorehdr 위치에 덮어쓰기 */
    crash_update_elfcorehdr(image, …);

    mutex_unlock(&kexec_mutex);
}
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# crash_hotplug 지원 여부 확인
grep CONFIG_CRASH_HOTPLUG /boot/config-$(uname -r)
# CONFIG_CRASH_HOTPLUG=y

# sysfs 인터페이스 (6.5+)
cat /sys/kernel/crash_hotplug_cpu     # 1: CPU 핫플러그 자동 대응
cat /sys/kernel/crash_hotplug_memory  # 1: 메모리 핫플러그 자동 대응

# 이전 커널 (6.5 미만)에서의 수동 대응
# CPU/메모리 핫플러그 후 kdump 재로드 필요:
echo 1 > /sys/devices/system/cpu/cpu4/online   # CPU 추가
systemctl restart kdump                         # kdump 재로드 필수!
# 또는:
kdumpctl reload                                 # RHEL 전용

# 6.5+ 커널에서는 자동 처리:
echo 1 > /sys/devices/system/cpu/cpu4/online   # CPU 추가
# → 커널이 자동으로 elfcorehdr 업데이트
# → kdump 재시작 불필요!
dmesg | grep -i "crash hp\|hotplug.*elfcore"
# [  123.456] crash hp: online cpu 4, updating elfcorehdr

# 메모리 핫플러그 시나리오 (가상머신에서 흔함)
# 메모리 추가:
echo online > /sys/devices/system/memory/memory32/state
# 6.5+: elfcorehdr 자동 업데이트 → 새 메모리도 vmcore에 포함
# 6.5-: kdump 재시작 필요, 안 하면 새 메모리가 vmcore에서 누락!

# 핫플러그 환경에서 crashkernel 크기 주의
# 메모리를 대량 추가하면 PT_LOAD 세그먼트 수가 증가
# → elfcorehdr 크기 증가 → 예약 영역 내 공간 부족 가능
# 해결: crashkernel 영역을 넉넉히 설정 (여유분 확보)
💡

crash_hotplug와 가상화: KVM/QEMU, VMware 등 가상 환경에서는 CPU/메모리 핫플러그가 빈번합니다. 클라우드 인스턴스의 동적 리소스 조정(auto-scaling)과 kdump가 함께 동작하려면 6.5+ 커널의 crash_hotplug 기능이 매우 유용합니다. 이전 커널에서는 udev 규칙이나 systemd 서비스로 핫플러그 이벤트 시 kdumpctl reload를 자동 실행하도록 구성하십시오.

Call Trace 심화 분석

커널 Call Trace(백트레이스)는 문제 발생 시점의 함수 호출 스택을 보여줍니다. 정확한 분석을 위해 다양한 유형의 Call Trace를 이해해야 합니다.

Call Trace 유형별 해석

Trace 헤더의미심각도조치
<TASK>프로세스 컨텍스트 스택가변해당 프로세스의 실행 경로 분석
<IRQ>인터럽트 컨텍스트 스택높음인터럽트 핸들러 내 버그
<NMI>NMI 컨텍스트 (watchdog 등)매우 높음hardlockup, 성능 카운터 오버플로
<SOFTIRQ>소프트IRQ 컨텍스트높음네트워크/블록 처리 경로 분석
[exception RIP: ...]예외 발생 지점높음RIP 주소의 명령어 확인
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# Call Trace 전체 소스 위치 변환
./scripts/decode_stacktrace.sh vmlinux /path/to/modules < oops.txt

# 주요 분석 포인트:
# 1. RIP (Instruction Pointer): 오류 발생 정확한 위치
RIP: 0010:my_function+0x28/0x60 [my_module]
# → my_function 시작으로부터 0x28 바이트 오프셋
# → 함수 전체 크기: 0x60 바이트

# 2. 오프셋으로 소스 라인 찾기
./scripts/faddr2line vmlinux 'my_function+0x28/0x60'
# 또는 모듈:
./scripts/faddr2line my_module.ko 'my_function+0x28/0x60'

# 3. objdump으로 어셈블리와 소스 매핑
objdump -dSl vmlinux | grep -A 20 "my_function+0x2"

# 4. 레지스터 값 분석
# RAX에 작은 값 (0x0~0xfff): NULL 포인터 역참조
# RAX에 0xdead000000000100: slab poison (use-after-free)
# RAX에 0x6b6b6b6b6b6b6b6b: slab free poison
# RAX에 0xa5a5a5a5a5a5a5a5: slab init poison
# RAX에 0x5a5a5a5a5a5a5a5a: red zone

# 5. Tainted 플래그 상세
# G: 모든 모듈이 GPL 호환
# P: proprietary 모듈 로드됨
# O: Out-of-tree 모듈 로드됨
# E: 서명 안 된 모듈 로드됨
# W: 이전에 WARN 발생
# C: 스테이징 드라이버 로드됨
# I: 플랫폼 펌웨어 버그 워크어라운드 적용
# D: 이전에 Oops 또는 BUG 발생
# T: 빌드 시간 또는 부팅 시간에 커널 테인트

Call Trace 패턴별 원인 분석

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# 패턴 1: NULL Pointer Dereference
BUG: kernel NULL pointer dereference, address: 0000000000000008
# → 구조체 멤버 접근 시 기본 포인터가 NULL
# → offset 0x008은 구조체의 두 번째 8바이트 멤버
# 대처: pahole 도구로 구조체 레이아웃 확인
pahole -C task_struct vmlinux | grep "0x008"

# 패턴 2: Use After Free
BUG: KASAN: slab-use-after-free in my_function+0x28
Read of size 8 at addr ffff88810a3b4c00 by task test/1234
# → 이미 해제된 slab 오브젝트에 접근
# → KASAN이 alloc/free 콜스택 모두 출력

# 패턴 3: Stack Overflow
BUG: stack guard page was hit at 00000000deadbeef
kernel stack overflow (double-fault)
# → 커널 스택(보통 16KB) 초과
# 원인: 깊은 재귀, 큰 스택 변수 (VLA, 큰 배열)
# 확인: CONFIG_FRAME_WARN=1024 (1KB 이상 스택 프레임 경고)

# 패턴 4: GPF (General Protection Fault)
general protection fault, probably for non-canonical address 0xdead000000000100
# → 비정상 주소 접근 (slab poison 패턴)
# → use-after-free 또는 uninitialized pointer

# 패턴 5: RCU stall
rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
# → CPU가 오래동안 RCU grace period를 완료하지 못함
# 원인: 긴 루프에서 cond_resched() 미호출, 인터럽트 비활성화

Softlockup / Hardlockup 심화

Lockup은 CPU가 일정 시간 이상 응답하지 않는 상황을 말합니다. watchdog 메커니즘이 이를 탐지하며, 시스템 장애 분석에서 가장 자주 마주치는 커널 메시지 중 하나입니다.

Softlockup vs Hardlockup

항목SoftlockupHardlockup
정의CPU가 일정 시간 커널 모드에서 스케줄링 없이 실행CPU가 일정 시간 인터럽트 처리 없이 실행
탐지 메커니즘hrtimer → watchdog kthread 스케줄링 여부NMI 기반 perf counter (PMU)
기본 타임아웃2 × watchdog_thresh (기본 20초)watchdog_thresh (기본 10초)
원인긴 루프, 높은 우선순위 작업 독점인터럽트 비활성화 상태 지속, 스핀락 데드락
심각도경고 (시스템 계속 동작 가능)심각 (시스템 응답 불가)
기본 동작WARN + 스택 트레이스 출력패닉 (watchdog_thresh 설정에 따라)
CONFIG 옵션SOFTLOCKUP_DETECTORHARDLOCKUP_DETECTOR

Watchdog 내부 메커니즘

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* kernel/watchdog.c — watchdog 감지 구조 */
/*
 * [Softlockup 감지]
 *
 *  Per-CPU hrtimer (주기: watchdog_thresh)
 *     │
 *     ├── hrtimer 콜백에서 watchdog_timer_fn() 실행
 *     │   ├── hrtimer_interrupts 카운터 증가
 *     │   ├── watchdog kthread가 마지막 터치한 타임스탬프 확인
 *     │   └── (현재 시각 - 마지막 터치) > 2 × watchdog_thresh ?
 *     │       └── YES → soft lockup 경고 출력
 *     │
 *     └── Per-CPU "watchdog/N" kthread
 *         └── 스케줄링될 때마다 타임스탬프 갱신 (touch)
 *             → kthread가 스케줄링 못 받으면 타임스탬프가 안 갱신
 *             → hrtimer가 stale 타임스탬프 감지 → softlockup!
 *
 * [Hardlockup 감지]
 *
 *  NMI (Non-Maskable Interrupt) — perf PMU counter 기반
 *     │
 *     ├── watchdog_overflow_callback() 에서
 *     │   hrtimer_interrupts 카운터 변화 확인
 *     │
 *     └── hrtimer도 실행 못 함 (인터럽트 비활성화 상태)
 *         → hrtimer_interrupts 변화 없음
 *         → NMI가 이를 감지 → hardlockup!
 */

static int is_softlockup(unsigned long touch_ts,
                          unsigned long period_ts)
{
    unsigned long now = get_timestamp();

    /* watchdog kthread의 마지막 터치로부터 경과 시간 */
    if (time_after(now, touch_ts + period_ts))
        return now - touch_ts;  /* lockup 지속 시간 반환 */

    return 0;
}
ℹ️

핵심 차이: softlockup은 "kthread가 스케줄링되지 못함"을 감지하고, hardlockup은 "hrtimer조차 실행되지 못함"을 감지합니다. hardlockup은 인터럽트까지 막혀 있으므로 NMI만이 이를 감지할 수 있습니다.

Lockup 관련 커널 파라미터

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# watchdog 타임아웃 (초)
sysctl kernel.watchdog_thresh=10    # soft: 20초, hard: 10초

# softlockup 발생 시 패닉 여부
sysctl kernel.softlockup_panic=0    # 0: 경고만, 1: 패닉
# 또는 부트 파라미터: softlockup_panic=1

# hardlockup 발생 시 패닉 여부
sysctl kernel.hardlockup_panic=0    # 0: 경고만 (불가능한 경우 있음), 1: 패닉
# 또는 부트 파라미터: nmi_watchdog=1 (hardlockup 활성화)

# watchdog 비활성화
sysctl kernel.watchdog=0            # 전체 비활성화
sysctl kernel.nmi_watchdog=0        # NMI watchdog만 비활성화
sysctl kernel.soft_watchdog=0       # soft watchdog만 비활성화

# 모든 CPU에서 backtrace 출력
sysctl kernel.softlockup_all_cpu_backtrace=0

# hung_task 탐지 (D 상태 프로세스)
sysctl kernel.hung_task_timeout_secs=120  # 기본 120초
sysctl kernel.hung_task_panic=0           # 1: hung_task 시 패닉

Softlockup 메시지 상세 해부

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# 실제 softlockup 메시지를 한 줄씩 해석합니다

watchdog: BUG: soft lockup - CPU#3 stuck for 22s! [kworker/3:1:1234]
# ├── "CPU#3"       → lockup이 발생한 CPU 번호
# ├── "stuck for 22s" → 22초 동안 스케줄링 안 됨 (> 2×watchdog_thresh)
# └── "[kworker/3:1:1234]" → lockup 당시 실행 중인 태스크
#      kworker/3:1 = CPU 3의 workqueue worker #1
#      1234 = PID

Modules linked in: my_module(OE) nvidia(POE) ext4 mbcache jbd2
# 로드된 커널 모듈 목록 (괄호 안은 Tainted 플래그)
# 모듈별 플래그: O=Out-of-tree, E=unsigned, P=Proprietary

CPU: 3 PID: 1234 Comm: kworker/3:1 Tainted: G        W    OE 6.1.0
# ├── "Tainted: G   W   OE" → Tainted 플래그 (아래 표 참조)
# └── "6.1.0" → 커널 버전

Hardware name: Dell PowerEdge R740/0WGD1O, BIOS 2.17.1 01/15/2023
# 하드웨어 식별 (벤더, 모델, BIOS 버전)

RIP: 0010:_raw_spin_lock+0x15/0x30
# ├── "0010" → CS(Code Segment) 값 (커널 코드 = 0x0010)
# └── "_raw_spin_lock+0x15/0x30"
#      함수명 + 함수 시작으로부터 오프셋 / 함수 전체 크기
#      → _raw_spin_lock 함수의 0x15 바이트 위치에서 멈춤

RSP: 0018:ffffc90001234e50 EFLAGS: 00000246
# ├── RSP → 스택 포인터 (커널 스택 범위 확인에 유용)
# └── EFLAGS: 0x246 → IF=1(인터럽트 활성), ZF=1
#     0x200 = IF(Interrupt Flag) 비트
#     EFLAGS에서 IF=0이면 local_irq_disable() 상태

RAX: 0000000000000001 RBX: ffff888123456000 RCX: 0000000000000000
RDX: 0000000000000001 RSI: ffff888123456040 RDI: ffff888123456080
# 범용 레지스터 값 — 의심스러운 값 확인:
# 0x0000000000000000 (NULL 포인터?)
# 0xdead000000000100 (poison value → use-after-free)
# 0x6b6b6b6b6b6b6b6b (SLAB_POISON → freed memory)

Call Trace:
 <TASK>
 my_locked_function+0x45/0x120 [my_module]
 process_one_work+0x1e8/0x3c0
 worker_thread+0x50/0x3b0
 kthread+0xf5/0x130
 ret_from_fork+0x1f/0x30
 </TASK>

# Call Trace 컨텍스트 마커 해석:
# <TASK>  ... </TASK>  → 프로세스 컨텍스트 스택
# <IRQ>   ... </IRQ>   → 하드웨어 인터럽트 스택
# <NMI>   ... </NMI>   → NMI 스택
# <SOFTIRQ> ... </SOFTIRQ> → softirq 스택
# 마커가 중첩되면 인터럽트가 프로세스를 선점한 것

Tainted 플래그 전체 목록

Tainted: 필드는 커널의 "오염" 상태를 나타냅니다. 각 문자가 특정 조건을 표시하며, 커뮤니티 버그 리포트에서 중요한 정보입니다:

위치문자의미설명
0G/PGPLP=Proprietary 모듈 로드됨, G=GPL-only
1FForced모듈이 modprobe --force로 로드됨
2SUnsafe SMPSMP unsafe 모듈이 SMP 커널에 로드됨
3RForced unloadrmmod --force 사용됨
4MMCEMachine Check Exception 발생
5BBad page페이지 해제 시 불량 페이지 감지
6UUser request사용자가 tainted 플래그를 직접 설정
7DDie최근 OOPS/BUG 발생
8AACPI overrideACPI 테이블이 사용자에 의해 오버라이드됨
9WWarning이전에 WARN_ON 경고 발생
10CStagingstaging 드라이버 로드됨
11IFirmware bug플랫폼 펌웨어 버그 감지
12OOut-of-tree트리 외부 모듈 로드됨
13EUnsigned서명되지 않은 모듈 로드됨
14LSoft lockup이전에 soft lockup 발생
15KLive patch커널 라이브 패치 적용됨
16TTest테스트 taint (KUNIT 등)
17XAux보조 taint (배포판 정의)
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# tainted 값을 사람이 읽을 수 있는 형태로 변환
$ cat /proc/sys/kernel/tainted
12881

# 비트별 분석 (12881 = 0x3251)
$ python3 -c "
t=12881
flags='P F S R M B U D A W C I O E L K T X'.split()
for i,f in enumerate(flags):
    if t & (1<<i): print(f'  Bit {i:2d} ({1<<i:5d}): {f}')
"
#   Bit  0 (    1): P  → Proprietary 모듈
#   Bit  5 (   32): B  → Bad page 감지
#   Bit  6 (   64): U  → User request
#   Bit  9 (  512): W  → WARN 발생 이력
#   Bit 12 ( 4096): O  → Out-of-tree 모듈
#   Bit 13 ( 8192): E  → Unsigned 모듈

Softlockup 원인별 분석

원인Call Trace 특징EFLAGS 특징진단
spinlock 데드락_raw_spin_lock에서 멈춤IF=1 (인터럽트 활성)다른 CPU에서 같은 lock holder 확인
무한 루프특정 함수 내부에서 반복IF=1perf top -C N으로 hot function 확인
preempt_disable 장기화preempt_count > 0IF=1preempt_count 값 확인, cond_resched 누락
RT 태스크 CPU 독점RT 태스크의 일반 코드 실행IF=1ps -eo cls,rtprio,pid,comm
softirq 폭주<SOFTIRQ> 마커 내부IF=1/proc/softirqs 카운트 비교
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# 사례: spinlock 데드락으로 인한 softlockup

watchdog: BUG: soft lockup - CPU#2 stuck for 23s! [my_app:5678]
RIP: 0010:native_queued_spin_lock_slowpath+0x1c5/0x200
Call Trace:
 <TASK>
 _raw_spin_lock+0x30/0x40
 my_data_update+0x42/0x150 [my_module]
 my_ioctl_handler+0x88/0x200 [my_module]
 vfs_ioctl+0x21/0x40
 __x64_sys_ioctl+0x6a/0xa0
 </TASK>

# 분석 절차:
# 1. native_queued_spin_lock_slowpath → qspinlock slow path 진입
#    → 락을 매우 오래 기다리고 있음
# 2. 누가 이 lock을 잡고 있는지 찾아야 함:

# crash 도구에서 lock owner 확인
crash> struct my_data_struct.lock ffff888123456000
  lock = {
    val = {
      counter = 1     # locked 상태
    },
    locked = 1,
    pending = 0,
    locked_pending = 1,
    tail = 0
  }

# 모든 CPU의 backtrace에서 같은 lock 주소를 찾기
crash> bt -a | grep -B5 "my_data_update"
# → CPU 5에서도 같은 함수, 같은 lock을 잡고 대기 중
# → ABBA 데드락 또는 재귀적 lock 획득

Hardlockup 메시지 상세 해부

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# 실제 hardlockup 메시지

NMI watchdog: Watchdog detected hard LOCKUP on cpu 2
# NMI watchdog이 감지 → hrtimer조차 실행 안 됨
# → 인터럽트가 비활성화된 상태에서 CPU가 멈춤

CPU: 2 PID: 5678 Comm: my_driver Tainted: G           OE 6.1.0
RIP: 0010:_raw_spin_lock_irqsave+0x20/0x40
# irqsave → 인터럽트 비활성화 상태에서 spinlock 대기

RSP: 0000:ffffc90001abcde0 EFLAGS: 00000002
# EFLAGS: 0x02 → IF=0! 인터럽트 완전 비활성화!
# 이것이 softlockup과 hardlockup의 핵심 차이:
#   softlockup: EFLAGS IF=1 (인터럽트 활성, 스케줄링만 못 함)
#   hardlockup: EFLAGS IF=0 (인터럽트 비활성, NMI만 가능)

Call Trace:
 <NMI>                # NMI 컨텍스트에서 덤프됨
 _raw_spin_lock_irqsave+0x20/0x40
 my_irq_handler+0x30/0x80 [my_module]
 __handle_irq_event_percpu+0x44/0x1c0
 handle_irq_event+0x36/0x56
 handle_edge_irq+0x82/0x1a0
 __common_interrupt+0x42/0xa0
 common_interrupt+0x80/0xa0
 </NMI>
 <IRQ>                # NMI 진입 전 IRQ 컨텍스트에 있었음
 asm_common_interrupt+0x22/0x40
 </IRQ>
 <TASK>               # 원래 실행 중이던 프로세스 컨텍스트
 some_kernel_function+0x10/0x30
 ...
 </TASK>

# 해석: 프로세스가 some_kernel_function 실행 중
# → 인터럽트 발생 (common_interrupt)
# → my_irq_handler에서 spin_lock_irqsave 대기
# → 인터럽트 비활성화 상태로 무한 대기
# → hardlockup!

Hardlockup 주요 원인과 진단

원인특징진단
IRQ 핸들러 내 데드락Call Trace에 <IRQ>/<NMI>, spin_lock_irqsavelockdep(CONFIG_PROVE_LOCKING)으로 재현
IRQ 핸들러 무한 루프IRQ 핸들러 내부 함수가 RIP에 반복 출현하드웨어 상태 레지스터 확인, 인터럽트 storm
local_irq_disable() 복원 누락EFLAGS IF=0, 일반 코드 경로에서 발생코드 리뷰, lockdep irqsoff tracer
하드웨어 버스 행업MMIO 접근 함수에서 멈춤 (readl, writel)PCIe AER 로그, lspci -vvv, MCE 확인
펌웨어 SMI 과점불규칙, 모든 CPU 동시 stall 가능perf stat -e msr/tsc/으로 SMI 감지
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# hardlockup 디버깅 CONFIG 옵션
CONFIG_HARDLOCKUP_DETECTOR=y
CONFIG_HARDLOCKUP_DETECTOR_PERF=y  # PMU 기반 (x86 기본)
CONFIG_DEBUG_SPINLOCK=y
CONFIG_PROVE_LOCKING=y

# hardlockup 시 시리얼 콘솔이 필수
# 부팅 파라미터:
console=ttyS0,115200n8 console=tty0

# NMI로 수동 크래시 유발 (iLO/IPMI BMC에서)
ipmitool chassis power diag  # NMI 전송
# unknown_nmi_panic=1 설정 시 NMI로 kdump 트리거

# irqsoff tracer: 인터럽트 비활성화 구간 추적
echo irqsoff > /sys/kernel/debug/tracing/current_tracer
echo 1 > /sys/kernel/debug/tracing/tracing_on
# ... 재현 ...
cat /sys/kernel/debug/tracing/trace
# 가장 긴 irqoff 구간과 함수 경로가 출력됨

Softlockup 디버깅 CONFIG 옵션

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
CONFIG_PROVE_LOCKING=y     # lockdep: 데드락 탐지
CONFIG_DEBUG_LOCK_ALLOC=y  # 락 할당 추적
CONFIG_LOCK_STAT=y         # 락 경합 통계
CONFIG_DEBUG_SPINLOCK=y    # 스핀락 디버깅

# perf로 lockup 원인 CPU 프로파일링
perf top -C 3              # CPU 3에서 가장 많이 실행되는 함수
perf record -C 3 -g sleep 10  # CPU 3 프로파일링

Hung Task 감지기

Hung task 감지기는 TASK_UNINTERRUPTIBLE(D 상태) 상태에서 너무 오래 머무는 프로세스를 탐지합니다. softlockup과 달리 CPU를 점유하지 않지만, I/O 대기나 lock 대기에서 빠져나오지 못하는 상황을 잡아냅니다.

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* kernel/hung_task.c — 감지 메커니즘 */
/*
 * khungtaskd 커널 스레드가 주기적으로 모든 태스크를 순회하며
 * TASK_UNINTERRUPTIBLE 상태에서 hung_task_timeout_secs 이상
 * 머무른 태스크를 찾아 경고를 출력합니다.
 *
 * 체크 주기: hung_task_check_interval_secs (기본 = timeout/5)
 */

static void check_hung_task(struct task_struct *t,
                             unsigned long timeout)
{
    unsigned long switch_count = t->nvcsw + t->nivcsw;

    /* 컨텍스트 스위치 카운터가 변하지 않으면 hung */
    if (switch_count == t->last_switch_count) {
        /* timeout 초과 시 경고 출력 */
        pr_err("INFO: task %s:%d blocked for more than "
               "%ld seconds.\\n",
               t->comm, t->pid, timeout);
        sched_show_task(t);  /* 스택 트레이스 출력 */
        hung_task_show_lock = true;

        if (sysctl_hung_task_panic)
            panic("hung_task: blocked tasks");
    }
    t->last_switch_count = switch_count;
}

Hung Task 메시지 상세 해석

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# 실제 hung_task 경고 메시지

INFO: task my_app:3456 blocked for more than 120 seconds.
      Not tainted 6.1.0 #1
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:my_app         state:D stack:13120 pid: 3456 ppid:  1200 flags:0x00004000
Call Trace:
 <TASK>
 __schedule+0x2eb/0x8d0
 schedule+0x5e/0xd0
 io_schedule+0x42/0x70
 folio_wait_bit_common+0x13a/0x310
 __filemap_get_folio+0x1e0/0x430
 filemap_fault+0x105/0x7e0
 __do_fault+0x32/0x130
 handle_mm_fault+0x6df/0xde0
 do_user_addr_fault+0x1c0/0x650
 exc_page_fault+0x78/0x170
 asm_exc_page_fault+0x22/0x30
 </TASK>

# 필드별 해석:
# "state:D"     → TASK_UNINTERRUPTIBLE (시그널로 깨울 수 없음)
# "stack:13120" → 사용 중인 커널 스택 크기 (바이트)
# "flags:0x00004000" → PF_MEMALLOC 등 태스크 플래그
#
# Call Trace 분석:
#   io_schedule → I/O 완료를 대기 중
#   folio_wait_bit_common → 페이지 캐시 I/O 대기
#   filemap_fault → 파일 매핑 페이지 폴트 처리 중
#   → 디스크 I/O가 완료되지 않아 프로세스가 무한 대기
#
# 원인 추정:
#   1. 디스크/스토리지 장애 (HDD/SSD 응답 없음)
#   2. NFS/CIFS 네트워크 파일시스템 서버 응답 없음
#   3. dm-multipath 경로 전환 지연
#   4. 블록 디바이스 드라이버 버그

Hung Task 원인별 진단

원인Call Trace 특징진단 명령어
디스크 I/O 장애io_schedule, blk_mq_get_tagiostat -x 1, dmesg | grep -i error
NFS 서버 응답 없음rpc_wait_bit_killable, nfs_file_*nfsstat -c, rpcdebug -m nfs
mutex 경합mutex_lock_killable, __mutex_lock.constproplockdep, /proc/lock_stat
메모리 부족 (OOM 대기)mem_cgroup_charge, __alloc_pages_slowpathfree -h, slabtop, dmesg | grep oom
dm/MD 장애dm_*, md_*dmsetup status, cat /proc/mdstat
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# Hung task 관련 sysctl 파라미터
kernel.hung_task_timeout_secs=120    # D 상태 타임아웃 (0=비활성화)
kernel.hung_task_panic=0             # 1이면 hung_task 시 패닉+kdump
kernel.hung_task_check_count=32768   # 한 번에 확인할 최대 태스크 수
kernel.hung_task_check_interval_secs=0 # 0=timeout/5 간격으로 체크
kernel.hung_task_warnings=10         # 최대 경고 출력 횟수 (-1=무제한)

# D 상태 프로세스 실시간 확인
ps aux | awk '$8 ~ /^D/ {print}'
# 또는
watch -n1 "cat /proc/*/status 2>/dev/null | grep -B1 'State.*sleeping'"

# SysRq로 모든 D 상태 태스크 덤프
echo w > /proc/sysrq-trigger
# dmesg에 모든 blocked 태스크의 스택이 출력됨
⚠️

NFS와 hung_task: NFS 마운트에서 서버 장애 시 hung_task 경고가 빈번하게 발생합니다. mount -o soft,timeo=50으로 soft mount를 사용하거나, hung_task_timeout_secs를 늘리는 것을 고려하세요. 프로덕션에서 hung_task_panic=1과 NFS를 함께 사용할 때는 주의가 필요합니다.

Lockup 종합 디버깅 순서

Lockup/Hang 디버깅 순서 1) 증상 분류 (로그 메시지) soft lockup / hard LOCKUP / blocked for more than / rcu_preempt stall / 메시지 없음(freeze) 먼저 dmesg + 시리얼 콘솔로 유형을 좁힘 2) EFLAGS.IF 확인 IF=1 → softlockup 가능성 / IF=0 → hardlockup 가능성 인터럽트 활성 여부로 1차 분기 3) Call Trace 컨텍스트 마커 <TASK>: 프로세스 컨텍스트 / <IRQ>: IRQ 핸들러 / <SOFTIRQ>: softirq <NMI>: NMI 덤프 컨텍스트 어느 실행 경로에서 멈췄는지 위치 결정 4) RIP 함수 패턴 해석 _raw_spin_lock*: 스핀락 홀더 추적 mutex_lock*: lock_stat/대기 체인 확인 io_schedule*: 스토리지/NFS/블록 경로 점검 native_halt/idle: starvation 혹은 wakeup 누락 확인 5) 전체 상태 캡처 + vmcore 확보 `echo l > /proc/sysrq-trigger` (모든 CPU backtrace) `echo t > /proc/sysrq-trigger` (모든 태스크 스택), `echo w` (D 상태) 필요 시 `echo c` 또는 원격 NMI로 panic 유도 후 kdump 수집 `crash` 도구로 vmcore 분석하여 재현/원인 고정

커널 패닉 심화

panic() 함수 내부 동작

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* kernel/panic.c: panic() 실행 순서 */
void panic(const char *fmt, ...)
{
    /* 1. 다른 CPU 정지 (smp_send_stop) */
    /* 2. panic_notifier_list 콜백 호출 */
    /* 3. kmsg_dump() - 로그를 pstore 등에 저장 */
    /* 4. __crash_kexec() - kdump 캡처 커널으로 전환 */
    /* 5. kdump 미설정 시: panic_blink + 무한 루프 */
    /* 6. panic_timeout > 0이면 타임아웃 후 재부팅 */
}

/* panic notifier 등록 (모듈에서 패닉 전 정리 작업) */
static int my_panic_handler(struct notifier_block *nb,
                           unsigned long action, void *data)
{
    /* 최소한의 정리 작업만 수행 */
    /* 주의: 이 시점에서 시스템 상태가 불안정 */
    /* 락 획득, 메모리 할당 등 금지 */
    pr_emerg("my_module: panic cleanup\\n");
    return NOTIFY_DONE;
}

static struct notifier_block my_panic_nb = {
    .notifier_call = my_panic_handler,
    .priority = 200,  /* 높을수록 먼저 호출 */
};

/* 등록/해제 */
atomic_notifier_chain_register(&panic_notifier_list, &my_panic_nb);
atomic_notifier_chain_unregister(&panic_notifier_list, &my_panic_nb);

패닉 관련 커널 파라미터 총정리

파라미터기본값설명
panic=N0패닉 후 N초 뒤 재부팅. 0=재부팅 안 함. -1=즉시 재부팅
panic_on_oops=N01이면 Oops 시 즉시 패닉 (kdump 유발)
panic_on_warn=N01이면 WARN 시 패닉 (디버깅 전용)
panic_on_rcu_stall=N01이면 RCU stall 시 패닉
panic_on_io_nmi=N01이면 I/O NMI 시 패닉
panic_on_unrecovered_nmi=N01이면 처리 불가 NMI 시 패닉
panic_print=N0패닉 시 추가 정보 출력 (비트마스크)
softlockup_panic=N01이면 softlockup 시 패닉
hardlockup_panic=N01이면 hardlockup 시 패닉
hung_task_panic=N01이면 hung_task 시 패닉
unknown_nmi_panic=N01이면 알 수 없는 NMI 시 패닉 (IPMI 디버깅)
oops_limit=N10000이 횟수 초과 시 패닉 (커널 6.2+)
warn_limit=N0이 횟수 초과 시 패닉 (0=무제한, 커널 6.2+)
# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# panic_print 비트마스크 값
# Bit 0 (1):  모든 태스크 정보 출력
# Bit 1 (2):  시스템 메모리 정보 출력
# Bit 2 (4):  타이머 정보 출력
# Bit 3 (8):  락/의존성 정보 출력 (lockdep)
# Bit 4 (16): ftrace 버퍼 출력
# Bit 5 (32): 모든 printk 메시지 버퍼 출력
# 예: panic_print=63 → 모든 정보 출력

# 프로덕션 서버 권장 패닉 설정
# /etc/sysctl.d/99-panic.conf
kernel.panic = 10              # 10초 후 재부팅
kernel.panic_on_oops = 1       # Oops 시 패닉 (kdump 수집)
kernel.softlockup_panic = 1    # softlockup 시 패닉
kernel.unknown_nmi_panic = 1   # BMC NMI 수신 시 패닉 (원격 디버깅)
kernel.panic_print = 0         # 패닉 시 추가 출력 비활성화 (kdump 방해 가능)

# 개발 환경 권장 패닉 설정
kernel.panic = 0               # 재부팅 안 함 (분석 시간 확보)
kernel.panic_on_oops = 1       # Oops 시 패닉
kernel.panic_on_warn = 0       # WARN은 경고만 (너무 잦음)
kernel.softlockup_panic = 0    # softlockup 경고만

WARN_ON / BUG_ON 심화 분석

WARN 메시지 분석 패턴

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# WARN_ON 출력 예시
------------[ cut here ]------------
WARNING: CPU: 1 PID: 3456 at net/core/skbuff.c:456 skb_under_panic+0x15/0x20
Modules linked in: my_module(OE) ...
CPU: 1 PID: 3456 Comm: my_app Tainted: G        W    OE 6.1.0
RIP: 0010:skb_under_panic+0x15/0x20
Call Trace:
 skb_push+0x45/0x50
 my_tx_handler+0x89/0x120 [my_module]
 dev_hard_start_xmit+0xd4/0x1f0
 ...
---[ end trace 0000000000000000 ]---

# 분석 절차:
# 1. "WARNING: ... at 파일:라인" → 소스 코드 확인
# 2. Call Trace → 호출 경로 추적
# 3. Tainted 플래그 'W' → 이전 WARN 발생 이력

# WARN 발생 횟수 확인
cat /proc/sys/kernel/tainted
# 비트 연산으로 해석:
python3 -c "t=512; print([(i,n) for i,n in enumerate(['P','F','S','R','M','B','U','D','A','W','C','I','O','E','L','K','T','X']) if t & (1<<i)])"

# WARN rate limiting
# WARN_ON_ONCE: 같은 위치에서 한 번만 경고
# WARN_RATELIMITED: 시간당 최대 횟수 제한
# net_warn_ratelimited: 네트워크 경로 전용

WARN/BUG 디버깅 전략

/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* 올바른 WARN/BUG 사용 가이드라인 */

/* GOOD: 복구 가능한 오류 → WARN_ON_ONCE + 에러 반환 */
if (WARN_ON_ONCE(size > MAX_SIZE))
    return -EINVAL;

/* GOOD: 발생하면 안 되는 상태 확인 → WARN_ON */
WARN_ON(refcount_read(&obj->ref) < 0);

/* BAD: BUG()는 커널을 불안정하게 만듦 */
/* BUG_ON(condition); → 거의 항상 사용하지 말 것 */

/* GOOD: 치명적 상태에서도 에러 반환 선호 */
if (impossible_state) {
    pr_crit("Impossible state detected: %d\\n", state);
    WARN_ON_ONCE(1);
    return -EIO;
}

/* 커널 6.2+: warn_limit / oops_limit 활용 */
/* warn_limit=100: WARN이 100회 초과 시 패닉 */
/* 이를 통해 빈번한 WARN이 시스템을 degradation시키는 것을 방지 */

pstore / ramoops

패닉 시 콘솔 출력이나 시리얼 로그를 놓칠 수 있습니다. pstore는 패닉 직전의 커널 로그를 비휘발성 저장소(RAM, EFI 변수, NVRAM 등)에 보존합니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# pstore/ramoops 설정 (부트 파라미터)
ramoops.mem_address=0x700000000    # RAM 예약 시작 주소
ramoops.mem_size=0x400000          # 4MB 예약
ramoops.console_size=0x100000      # 콘솔 로그 1MB
ramoops.record_size=0x40000        # 각 패닉 레코드 256KB
ramoops.pmsg_size=0x40000          # 사용자 공간 메시지 256KB
ramoops.ftrace_size=0x40000        # ftrace 버퍼 256KB
ramoops.ecc=1                      # ECC 활성화

# 디바이스 트리에서 ramoops 설정 (임베디드)
ramoops {
    compatible = "ramoops";
    memory-region = <&ramoops_mem>;
    console-size = <0x100000>;
    record-size  = <0x40000>;
    pmsg-size    = <0x40000>;
    ftrace-size  = <0x40000>;
    ecc-size     = <16>;
};

# pstore 마운트 및 확인
mount -t pstore pstore /sys/fs/pstore/
ls /sys/fs/pstore/
# dmesg-ramoops-0   → 이전 패닉의 dmesg
# console-ramoops-0 → 이전 패닉의 콘솔 로그
# pmsg-ramoops-0    → 사용자 공간 메시지

# pstore 내용 읽기
cat /sys/fs/pstore/dmesg-ramoops-0

# EFI 기반 pstore (UEFI 시스템)
# 자동으로 EFI 변수에 저장됨
efivar -l | grep dump-type
cat /sys/fs/pstore/dmesg-efi-*

SysRq 디버깅 키

Magic SysRq 키는 시스템이 응답하지 않을 때도 커널에 직접 명령을 보낼 수 있는 디버깅 인터페이스입니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# SysRq 활성화
sysctl kernel.sysrq=1         # 전체 활성화
sysctl kernel.sysrq=438       # 특정 기능만 (비트마스크)
# Bit 0 (1):   sysrq 완전 비활성화
# Bit 1 (2):   로그 레벨 변경
# Bit 2 (4):   콘솔 로그 활성화
# Bit 3 (8):   모든 프로세스 정보
# Bit 4 (16):  SAK (Secure Access Key)
# Bit 5 (32):  sync
# Bit 6 (64):  읽기전용 리마운트
# Bit 7 (128): 프로세스 시그널링 (E, I)
# Bit 8 (256): reboot

# 주요 SysRq 키 (echo X > /proc/sysrq-trigger 또는 Alt+SysRq+X)
echo h > /proc/sysrq-trigger  # 도움말
echo t > /proc/sysrq-trigger  # 모든 태스크 스택 덤프 (softlockup 분석)
echo w > /proc/sysrq-trigger  # D 상태 (uninterruptible) 태스크 덤프
echo l > /proc/sysrq-trigger  # 모든 CPU 백트레이스
echo m > /proc/sysrq-trigger  # 메모리 정보 (OOM 분석)
echo p > /proc/sysrq-trigger  # 현재 CPU 레지스터 덤프
echo c > /proc/sysrq-trigger  # 강제 패닉 (kdump 테스트)

# R-E-I-S-U-B: 안전한 재부팅 시퀀스
# R: 키보드 raw 모드 해제
# E: 모든 프로세스에 SIGTERM
# I: 모든 프로세스에 SIGKILL
# S: 파일시스템 sync
# U: 파일시스템 읽기전용 리마운트
# B: 즉시 재부팅

# 각 단계 사이에 잠시 대기
for key in r e i s u b; do
    echo $key > /proc/sysrq-trigger
    sleep 2
done

RCU Stall 디버깅

RCU(Read-Copy-Update) stall은 CPU가 RCU grace period를 완료하지 못할 때 발생합니다. 네트워크, 파일시스템 등 RCU를 광범위하게 사용하는 서브시스템에서 자주 나타납니다.

💡

RCU stall 메시지의 필드별 상세 해석(idle 필드, CPU 플래그, softirq 카운터, kthread starved 등), 실전 사례 분석, 예방 패턴은 RCU 심화 — RCU CPU Stall 경고 심화 페이지에서 다룹니다.

# 실습 예제: 운영 환경에서 재현 가능한 점검 절차
# RCU stall 메시지 예시
rcu: INFO: rcu_preempt detected stalls on CPUs/tasks:
rcu: 	3-...0: (1 GPs behind) idle=02f/1/0x40000002 softirq=1234/1234 fqs=4567
rcu: 	(detected by 0, t=21003 jiffies, g=12345, q=678 ncpus=8)
rcu: rcu_preempt kthread starved for 21003 jiffies!

# 핵심 필드 요약:
# 3-...0          → CPU 3, 플래그 5자리 (온라인/오프라인/틱/dyntick/irq)
# (1 GPs behind)  → 현재 GP보다 1 뒤처짐
# idle=AAA/BBB/CCC → dynticks nesting / NMI nesting / dynticks 카운터
#   CCC 짝수=idle, 홀수=활성
# softirq=처리/발생 → 차이가 크면 softirq 폭주
# fqs=N           → force-quiescent-state 스캔 횟수
# t=21003         → grace period 경과 jiffies
# g=12345         → grace period 번호 (gp_seq)
# kthread starved → RCU kthread가 스케줄링 못 받음
#   state 0x0=RUNNING(CPU 시간 부족), 0x2=UNINTERRUPTIBLE

# RCU stall 주요 원인:
# 1. 인터럽트 비활성화 상태에서 오래 실행
# 2. preempt_disable() 상태에서 긴 루프
# 3. 큰 데이터 구조 순회 중 cond_resched() 누락
# 4. softirq 처리가 너무 오래 걸림
# 5. RT 태스크가 RCU kthread보다 높은 우선순위로 독점
# 6. 가상머신 steal time (모든 CPU 동시 stall)

# RCU stall 관련 커널 파라미터
echo 60 > /sys/module/rcupdate/parameters/rcu_cpu_stall_timeout
echo 1 > /sys/module/rcupdate/parameters/rcu_cpu_stall_suppress  # 억제

# RCU 상태 모니터링
cat /sys/kernel/debug/rcu/rcu_preempt/rcudata
cat /sys/kernel/debug/rcu/rcu_preempt/rcuexp
/* 개념 예시: 커널 내부 동작 이해를 위한 코드 */
/* RCU stall 방지 패턴 */

/* BAD: RCU read-side에서 긴 루프 */
rcu_read_lock();
list_for_each_entry_rcu(entry, &large_list, node) {
    do_expensive_work(entry);  /* RCU stall 위험! */
}
rcu_read_unlock();

/* GOOD: cond_resched_rcu()로 주기적 양보 */
rcu_read_lock();
list_for_each_entry_rcu(entry, &large_list, node) {
    do_expensive_work(entry);
    cond_resched_rcu();  /* RCU unlock → resched → lock */
}
rcu_read_unlock();

/* GOOD: 수동 RCU 구간 재시작 */
rcu_read_lock();
list_for_each_entry_rcu(entry, &large_list, node) {
    do_expensive_work(entry);
    if (need_resched()) {
        rcu_read_unlock();
        cond_resched();
        rcu_read_lock();
    }
}
rcu_read_unlock();

크래시 디버깅 체크리스트

커널 크래시 발생 시 체계적인 대응 절차를 따르면 복구 시간을 단축하고 재발을 방지할 수 있습니다. 아래 플로차트는 즉시 조치부터 근본 원인 분석까지 전체 프로세스를 보여줍니다.

크래시 복구 체크리스트 플로차트 1단계: 즉시 대응 (5분 이내) ☐ 시스템 응답성 확인 (ping, SSH 연결) ☐ 콘솔 메시지 수집 (시리얼/IPMI SOL) ☐ pstore 내용 백업 (/sys/fs/pstore/*) ☐ vmcore 수집 확인 (/var/crash/*.vmcore) ☐ 서비스 영향 범위 파악 및 우회 페일오버, 로드밸런서 제외, 사용자 공지 2단계: 초기 분석 (30분 이내) ☐ 크래시 유형 분류 (Oops/Panic/Lockup) ☐ Call Trace 최상위 함수 확인 ☐ RIP 주소로 크래시 위치 특정 ☐ 최근 변경사항 확인 (커널 업그레이드 등) ☐ 긴급도 평가: 즉시 재부팅 vs 분석 우선 재현 가능성, 영업 영향도 고려 3단계: 심층 분석 (수 시간 ~ 수 일) ☐ crash 유틸리티로 vmcore 전체 분석 ☐ 레지스터/메모리 값 검사 (poison, NULL) ☐ 모든 CPU 백트레이스 확인 (foreach bt) ☐ 락 상태/소유자 확인 (데드락 의심 시) ☐ 관련 커널 로그/트레이스 교차 검증 ftrace, perf, KASAN 출력 결합 ☐ 재현 환경 구성 (QEMU/테스트 머신) ☐ 새니타이저 활성화 재현 (KASAN/lockdep) ☐ Git blame/log로 관련 커밋 조사 ☐ 메일링리스트/버그 트래커 검색 ☐ 근본 원인 가설 수립 및 검증 코드 레벨 동작 추적 4단계: 해결 및 검증 ☐ 패치 작성 및 리뷰 ☐ 유닛 테스트 추가 ☐ 재현 환경에서 수정 검증 ☐ 스트레스 테스트 통과 ☐ 업스트림 제출 또는 백포트 ☐ 스테이징 배포 및 모니터링 ☐ 프로덕션 배포 (카나리/블루그린) ☐ 동일 조건 재발 여부 확인 (1주+) ☐ 포스트모텀 문서 작성 타임라인, 근본원인, 예방책 ☐ 모니터링 개선 조기 경보 시스템 추가 ☐ 팀 공유 및 교육 ✓ 사고 종료 5단계: 장기 예방 (지속) ☐ CI/CD에 재현 테스트 추가 ☐ 프로덕션 새니타이저 샘플링 도입 (KFENCE) ☐ 정기 kdump 테스트 자동화 ☐ 크래시 패턴 대시보드 구축 ☐ 커널 업그레이드 전략 수립 ☐ 리눅스 메일링리스트 구독 및 패치 모니터링 ☐ 유사 사례 데이터베이스 구축 ☐ 온콜 대응 플레이북 업데이트 체크리스트 원칙: 증거 확보 우선 → 영향 최소화 → 근본 원인 제거 → 재발 방지
그림: 크래시 복구 체크리스트 - 즉시 조치부터 장기 예방까지 전체 프로세스
💡

커널 크래시 발생 시 분석 순서:

  1. dmesg / 시리얼 콘솔 로그 확보: 패닉 메시지, Call Trace, 레지스터 값
  2. Oops 유형 확인: NULL deref, GPF, page fault, BUG, soft/hardlockup
  3. RIP 위치 분석: faddr2line 또는 addr2line으로 소스 라인 확인
  4. Call Trace 분석: 호출 경로를 역추적하여 근본 원인 파악
  5. 레지스터 값 검사: 의심스러운 값(poison, NULL, 범위 초과)
  6. vmcore 분석: crash 도구로 전체 시스템 상태 조사
  7. 재현: 스트레스 테스트, CONFIG_PROVE_LOCKING, KASAN 활성화
  8. 패치 & 검증: 수정 후 동일 조건에서 재현 불가 확인
도구용도필요 CONFIG
decode_stacktrace.shCall Trace를 소스 라인으로 변환DEBUG_INFO
faddr2line함수+오프셋 → 소스 위치DEBUG_INFO
crashvmcore 분석DEBUG_INFO, CRASH_DUMP
KASAN메모리 오류 실시간 탐지KASAN
KFENCE저오버헤드 메모리 오류 샘플링KFENCE
lockdep데드락 탐지PROVE_LOCKING
KCSAN동시성 버그 탐지KCSAN
UBSAN정의되지 않은 동작 탐지UBSAN
pstore/ramoops패닉 로그 보존PSTORE, PSTORE_RAM
ftrace함수 호출 추적FTRACE
SysRq응답 불가 시 디버깅MAGIC_SYSRQ

크래시 분석과 관련된 다른 주제를 더 깊이 이해하고 싶다면 다음 문서를 참고하세요.