임베디드 면접 용어 사전
RTOSstack overflow

스택 오버플로

태스크 스택이 넘쳐 인접 메모리를 침범하는 오류. 워터마크 검사·MPU 가드로 탐지한다.

태스크나 인터럽트가 할당된 스택 범위를 넘어 다른 메모리를 덮어쓰는 현상. 임베디드에서 가장 찾기 어려운 버그 유형이다.

왜 최악인가

스택이 넘치면 → 인접한 다른 태스크의 스택이나 전역 변수를 덮어쓴다
→ 전혀 관계없는 곳에서 이상한 값이 나온다
→ 증상과 원인의 거리가 멀어 추적이 극히 어렵다
→ 재현도 잘 안 된다 (특정 실행 경로에서만 깊어짐)

"어제까지 잘 되던 게 오늘 이상하다" 의 단골 원인이다.

무엇이 스택을 먹나

  • ① 지역 변수 — 특히 배열

    • uint8_t buf[1024]; ← 1KB를 한 번에
  • ② 함수 호출 깊이

    • a() → b() → c() → d() 각 프레임이 누적
  • ③ 재귀 — 깊이가 데이터에 좌우되면 예측 불가

  • ④ 인터럽트 — 자동 스태킹 32바이트 (FPU면 104바이트)

    • 중첩되면 배수로
  • ⑤ printf 계열 — 내부에서 수백 바이트를 쓴다 ★

    • 특히 %f 포매팅은 1KB 이상 쓰는 구현도 있다

printf가 의외의 범인인 경우가 많다. 디버깅용으로 넣은 printf 때문에 스택이 넘치는 일이 실제로 흔하다.

검출 방법 ① — 스택 워터마크 (RTOS)

// 태스크 스택을 생성 시 특정 패턴(0xA5)으로 채워 둔다
// 나중에 남아 있는 패턴의 길이 = 한 번도 안 쓴 깊이
UBaseType_t remaining = uxTaskGetStackHighWaterMark(handle);
if (remaining < 50) { /* 위험 */ }

"역대 최저 여유량" 을 알려 준다. 현재가 아니라 지금까지의 최악이다.

configCHECK_FOR_STACK_OVERFLOW = 2

void vApplicationStackOverflowHook(TaskHandle_t t, char *name) {
    // 여기서 로그를 남기고 리셋
}

방법 2는 스택 끝의 패턴이 손상됐는지 검사하므로 방법 1보다 확실하다. 다만 컨텍스트 스위칭 시점에만 확인하므로, 그 사이에 넘쳤다가 복구되면 놓친다.

검출 방법 ② — MPU 가드 (가장 확실)

  • 스택 바로 아래에 접근 금지 구역을 설정
  • 넘치는 순간 MemManage Fault
  • 즉시, 정확히 잡힌다

소프트웨어 검사와 달리 실시간으로 잡는다. MPU가 있는 MCU라면 개발 단계에서 반드시 켜는 것이 좋다.

검출 방법 ③ — 카나리 값

#define CANARY 0xDEADBEEF
uint32_t stack_canary = CANARY;    // 스택 끝에 배치

void check(void) {
    if (stack_canary != CANARY) { /* 오버플로 */ }
}

RTOS가 없어도 쓸 수 있는 간단한 방법이다.

검출 방법 ④ — 정적 분석

arm-none-eabi-gcc -fstack-usage …    # 함수별 스택 사용량을 .su 파일로 출력
  • main.c:42:6:process_data — 528 static

호출 그래프와 결합하면 최악 스택 깊이를 계산할 수 있다. 재귀나 함수 포인터가 없으면 정확한 상한을 구할 수 있어, 안전 인증에서 요구되기도 한다.

예방

✅ 큰 배열은 static 으로 (.bss 로 이동) ✅ 재귀 사용 금지 (MISRA C 규칙 17.2) ✅ 스택 크기를 워터마크 기준으로 산정 — 실측 + 여유 30~50% ✅ ISR에서 큰 지역 변수 금지 ✅ printf 계열을 임베디드용 경량 구현으로 교체 ✅ 인터럽트 중첩 깊이를 고려해 MSP 크기 결정

크기 산정 방법 — 실무 절차

  1. 넉넉하게 시작한다 (예: 2KB) 2. 최악의 시나리오로 오래 돌린다
    • 모든 기능을 실행하고, 에러 경로도 태운다
  1. 워터마크를 확인해 실제 최대 사용량을 파악
  2. 실측값 + 30~50% 여유로 확정
  3. 양산 후에도 워터마크를 주기적으로 로깅

"돌아가니까 괜찮다"가 가장 위험하다. 평소 경로에서는 안 넘치다가 드문 에러 처리 경로에서 넘치는 일이 많다.

힙과 스택의 충돌

[.data][.bss][힙 ↓ ][빈 공간][ ↑ 스택]

둘이 만나면 → 조용히 서로를 덮어쓴다

하드웨어가 알려 주지 않는다. 링커 스크립트에서 최소 여유 공간을 검사하게 할 수 있다.

_Min_Stack_Size = 0x400; ASSERT(. + _Min_Stack_Size <= ORIGIN(RAM) + LENGTH(RAM), "스택 부족") 링크 시점에 검사하므로 빌드가 실패해 미리 알 수 있다.

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — RTOS — 스케줄링·뮤텍스/세마포어·우선순위 역전·데드락·스택