메모리 오버플로우·오염 진단 — Canary·MPU·Pattern 분석
#한 줄 요약
“메모리 오염은 누가 망가뜨렸는지를 잡는 게 본질입니다.” Canary·MPU guard·data watchpoint·desktop ASan을 조합하면 현장에서 범인을 잡습니다.
#어떤 상황에서 쓰나
“평소엔 잘 도는데 1시간쯤 지나면 다른 변수 값이 이상하게 바뀌어요.” “함수가 끝나고 caller로 못 돌아와요 (Hardfault PC가 이상한 영역).” 이런 류는 거의 다 메모리 오염입니다.
오염은 현장에서 일어나지만 증상은 다른 코드에서 나타나므로, 디버깅이 까다롭습니다. 도구로 현장에서 잡아야 합니다.
#핵심 개념 — 오염 vs 증상
[현장] buf[1024]를 [1030]까지 씀 ← 누가[증상] buf 직후 stack의 LR이 깨짐 → return 시 hardfault ← 어디서 보이는지증상에서 거꾸로 추적하지 않고, 현장 자체를 잡는 도구를 둡니다.
#도구 1 — Stack canary (Compiler)
gcc -fstack-protector -fstack-protector-strong main.c// stack canary 자동 삽입void process(uint8_t *in, size_t n) { uint32_t __canary = __stack_chk_guard; uint8_t buf[64]; memcpy(buf, in, n); /* n > 64 → canary 깨짐 */ if (__canary != __stack_chk_guard) __stack_chk_fail();}Newlib에는 __stack_chk_fail이 없습니다. 직접 구현합니다.
uint32_t __stack_chk_guard = 0xDEADBEEF;
void __stack_chk_fail(void) { printf("STACK SMASH\n"); NVIC_SystemReset();}Function-level canary는 오염을 그 함수가 return할 때 잡습니다. 함수 사이의 stack 오염은 못 잡습니다.
#도구 2 — Heap canary
typedef struct { uint32_t magic_head; /* 0xAB12CD34 */ size_t size; uint32_t pad[2];} chunk_hdr_t;
void *my_malloc(size_t n) { chunk_hdr_t *c = real_malloc(sizeof(*c) + n + 4); c->magic_head = 0xAB12CD34; c->size = n; *(uint32_t*)((uint8_t*)(c + 1) + n) = 0xCDEF1234; /* tail */ return c + 1;}
int my_check(void *p) { chunk_hdr_t *c = (chunk_hdr_t*)p - 1; if (c->magic_head != 0xAB12CD34) return -1; if (*(uint32_t*)((uint8_t*)p + c->size) != 0xCDEF1234) return -1; return 0;}my_check를 주기적으로 또는 free 시 호출합니다. Head/tail canary가 깨졌다면 그 영역에 오버플로우가 있었습니다.
#도구 3 — MPU stack guard
각 task stack 아래·위 또는 bottom region에 access를 막는 MPU region을 둡니다.
// task stack: 0x20001000 ~ 0x20002000// guard: 0x20000FE0 ~ 0x20001000 (32 byte)
MPU->RNR = 0;MPU->RBAR = 0x20000FE0;MPU->RASR = (4 << MPU_RASR_SIZE_Pos) /* 32 byte */ | MPU_RASR_ENABLE_Msk | (0 << MPU_RASR_AP_Pos); /* no access */Stack이 guard region에 침범하는 순간 MemManage fault가 떨어집니다. Caller로 돌아간 후가 아닌, 오염 직전에 잡힙니다.
FreeRTOS는 MPU port에서 자동으로 task stack guard를 둡니다 (portUSING_MPU_WRAPPERS).
#도구 4 — Data watchpoint
(gdb) watch *((uint32_t*)0x20001234)(gdb) continue
Hardware watchpoint 2: *((uint32_t*)0x20001234)Old value = 5New value = 1819045216 ← 깨진 값process_packet (data=0x...) at packet.c:78특정 주소가 누가, 어디서 쓰는지 잡습니다. Cortex-M DWT는 보통 4 comparator입니다. 가장 강력한 도구.
/* DWT 직접 설정 (gdb 없이 production에서) */DWT->COMP0 = 0x20001234;DWT->MASK0 = 0; /* exact match */DWT->FUNCTION0 = (5 << 0); /* write 시 */
void DebugMon_Handler(void) { /* PC = DWT 매칭 직전의 명령 */ handle_watch_hit();}#도구 5 — Desktop simulation + ASan
가능하면 HW-independent 코드는 desktop에서 ASan으로 돌립니다.
gcc -fsanitize=address -fno-omit-frame-pointer -g \ parser.c parser_test.c -o test./test==12345==ERROR: AddressSanitizer: heap-buffer-overflowWRITE of size 4 at 0x602000000058 thread T0 #0 0x4007ae in process_packet packet.c:84 #1 0x4007fe in main main.c:23임베디드에서 못 보던 오류가 ASan에서는 ms 단위로 잡힙니다. 모든 모듈을 이렇게 빌드할 필요는 없습니다. Parser·codec·state machine 같은 알고리즘 모듈만 분리해 desktop test에 둡니다.
#도구 6 — Fill pattern으로 stack high-water mark
extern uint32_t _estack;extern uint32_t _Min_Stack_Size;
void stack_fill(void) { uint32_t *bottom = (uint32_t*)((char*)&_estack - (size_t)&_Min_Stack_Size); uint32_t *sp; __asm volatile ("mov %0, sp" : "=r"(sp)); while (bottom < sp) *bottom++ = 0xA5A5A5A5;}
size_t stack_used(void) { uint32_t *bottom = (uint32_t*)((char*)&_estack - (size_t)&_Min_Stack_Size); uint32_t *p = bottom; while (*p == 0xA5A5A5A5) p++; return (char*)&_estack - (char*)p;}부팅 직후 stack을 패턴으로 채우고, 주기적으로 패턴이 어디까지 사라졌는지 봅니다. Stack worst-case 사용량을 정량화할 수 있습니다.
FreeRTOS는 uxTaskGetStackHighWaterMark()로 같은 일을 합니다.
#도구 7 — Sentinel 변수
오염이 잘 일어나는 자리에 사용 안 하는 sentinel을 둡니다.
volatile uint32_t SENTINEL_BEFORE = 0x11223344;char comm_buffer[256];volatile uint32_t SENTINEL_AFTER = 0x55667788;
void check_sentinel(void) { if (SENTINEL_BEFORE != 0x11223344) printf("buf overflow before!\n"); if (SENTINEL_AFTER != 0x55667788) printf("buf overflow after!\n");}비싸진 않고 production에 남겨도 됩니다.
#사례 — “Return 시 hardfault”
[증상] func_A → func_B → return → HardFault, PC = 0xdeadbeef[가설] func_B 안에서 stack overflow로 saved LR이 깨짐Stack canary 사용 → func_B return 직후 __stack_chk_fail 호출. 범위가 좁혀집니다.
void func_B(uint8_t *in, size_t n) { char buf[32]; strncpy(buf, in, n); /* n = 100 → 68 byte overflow */}strncpy에 잘못된 n을 넘긴 게 원인. 호출 측 n 계산을 수정.
#사례 — “변수가 나 모르게 바뀌어요”
uint32_t g_state; /* 갑자기 0xdeadbeef로 바뀜 */
/* watchpoint 시도 */(gdb) watch g_state(gdb) continue
Hardware watchpoint 1: g_stateOld value = 5New value = 0xdeadbeefdma_callback (ch=2) at dma.c:142DMA가 원래 의도와 다른 주소에 buffer를 적었습니다. DMA destination address가 한 byte 어긋나 g_state 위에 떨어졌습니다.
Watchpoint 없이는 dma_callback과 g_state의 관계를 영원히 못 잡았을 것입니다.
#사례 — NULL pointer + 4
HardFault, BFAR = 0x00000004NULL->next 같은 dereference. addr2line으로 fault PC를 source line으로 매핑.
NULL pointer 영역(0x00000000 ~ 0x000000FF)에 no-access MPU region을 두면, write 시도에 그 명령에서 막힙니다.
MPU->RNR = 7;MPU->RBAR = 0x00000000;MPU->RASR = (7 << MPU_RASR_SIZE_Pos) /* 256 byte */ | MPU_RASR_ENABLE_Msk | (0 << MPU_RASR_AP_Pos);#사례 — Use-after-free
chunk_t *c = malloc_chunk();free(c);c->next = NULL; /* ← 이미 free된 메모리 write */Free 시 chunk를 0xDEADBEEF로 채우면 c->next write도 deadbeef.next로 망가뜨림. 다음 alloc 때 corruption이 다른 곳에서 발생합니다.
void debug_free(void *p) { size_t n = chunk_size(p); memset(p, 0xDD, n); real_free(p);}Production에서는 memset이 비싸므로 debug build에만 둡니다.
#자주 보는 함정
Cache invalidate 누락 → “메모리 오염”으로 오해
dma_read_into(buf, 1024);process(buf); /* DDR write 완료, CPU cache는 옛 데이터 */진짜 오염은 아니지만 증상은 똑같습니다. DMA 후 __DSB(); __invalidate_dcache_range(buf, 1024); 또는 buffer를 non-cacheable에 둡니다.
Aliased pointer
uint32_t *a = (uint32_t*)0x20001000;uint8_t *b = (uint8_t*)(a + 1);*b = 5;*(a + 1); /* compiler가 a+1을 cache했으면 옛 값을 봄 */Strict aliasing 위반. -fno-strict-aliasing으로 빌드하거나, memcpy/union을 씁니다.
Unaligned access
ARMv6-M (Cortex-M0)은 unaligned access를 지원하지 않음. 4 byte 정수를 odd address에 쓰면 fault. __packed 구조체에서 흔합니다.
Stack 사용량 미측정
Worst-case stack 사용량을 측정 없이 추정하면 거의 항상 underestimate합니다. Fill pattern으로 측정.
Production에서 watchpoint
Cortex-M은 hardware breakpoint/watchpoint가 제한적입니다. Production에서는 sentinel + canary + periodic check이 현실적입니다.
#정리
- 메모리 오염은 증상이 아니라 현장을 잡아야 풉니다.
- Stack canary, heap canary, MPU guard, data watchpoint, ASan, fill pattern, sentinel — 도구를 layer로 둡니다.
- Desktop ASan은 algorithm 모듈에 가장 빠른 회수율을 줍니다.
- Watchpoint는 변수가 누가 망가뜨리는지 잡는 최강의 도구.
- NULL pointer 영역에 no-access MPU region을 두면 null deref가 현장에서 잡힙니다.
- Cache invalidate 누락은 진짜 오염이 아닌데 오염처럼 보입니다.
- Production은 sentinel + canary + periodic check 조합이 현실적.
다음 편은 타이밍/race 진단입니다.
#관련 항목
Modern Embedded Recipes · 119 of 152
- 1Modern Embedded Recipes — 모던 임베디드 실전 레시피 시리즈 소개
- 2디지털 신호 기초 — Voltage Level·Edge·Setup/Hold 분석
- 3임베디드 클럭과 타이밍 — Skew·Jitter·PLL·MMCM 분석
- 4GPIO 내부 구조 분해 — Push-Pull·Open-Drain·Schmitt Trigger
- 5UART 하드웨어 동작 분석 — Baud Rate·Framing·FIFO
- 6SPI 하드웨어 분석 — Clock Mode·MOSI/MISO·Chip Select
- 7I2C 하드웨어 분석 — Open-Drain·Clock Stretching·Arbitration
- 8ADC 동작 원리 — SAR·Sigma-Delta·Pipelined 비교
- 9DAC 동작 원리 — R-2R Ladder·Sigma-Delta·Settling Time
- 10PWM 신호 생성 분석 — Duty·Frequency·Dead Time·Center-Aligned
- 11CAN 버스 전기적 특성 — Differential·Termination·Dominant/Recessive
- 12RS-485·RS-422 차동 신호 분석 — Termination·Biasing·Topology
- 13LVDS 차동 신호 분석 — Common-Mode·Impedance·Eye Pattern
- 14ARM Cortex-M 시리즈 비교 — M0·M3·M4·M7·M33·M55 분석
- 15ARM Cortex-A 시리즈 비교 — A53·A55·A72·A78·X1 분석
- 16ARM 레지스터 구조 분석 — R0~R15·CPSR·SPSR·Banked Registers
- 17Cortex-M 예외 처리 — Vector Table·NVIC·Tail-Chaining 추적
- 18ARM 메모리 맵 분석 — Normal·Device·Strongly-Ordered Region
- 19ARM L1·L2 캐시 분석 — Set Associative·Inclusive·Maintenance
- 20ARM MPU 활용 — Region·Attribute·Privilege Separation
- 21ARM MMU 기초 분석 — Translation Table·TLB·ASID
- 22ARM TrustZone-M 기초 — Secure/Non-Secure·NSC·MPC
- 23ARM Memory Barrier 실전 — DMB·DSB·ISB·DMA·MMIO
- 24임베디드 크로스 컴파일러 분석 — GCC·Clang·Sysroot 구성
- 25C 컴파일 4단계 — Preprocess·Compile·Assemble·Link 추적
- 26ELF 파일 구조 분석 — Section·Segment·Symbol Table·DWARF
- 27링커 스크립트 기초 — SECTIONS·MEMORY·entry point
- 28링커 스크립트 고급 — Overlay·BSS·init_array·LMA/VMA
- 29임베디드 스타트업 코드 분석 — Reset_Handler·Vector Table·SystemInit
- 30C 런타임 crt0 분석 — Stack·BSS Zero·Data Copy·atexit
- 31임베디드 메모리 레이아웃 — .text·.rodata·.data·.bss·.heap·.stack
- 32임베디드 컴파일러 최적화 분석 — -O0~-O3·-Os·-LTO 비교
- 33Map 파일 분석 — Symbol·Section·Size 추적으로 코드 크기 진단
- 34Make·CMake 크로스 컴파일 — Toolchain File·Sysroot 통합
- 35임베디드 Bootloader 체인 — BootROM·SPL·U-Boot·Kernel·Secure Boot
- 36첫 bare-metal 프로그램 작성 — Linker·Startup·main의 최소 구성
- 37MMIO 레지스터 직접 접근 — volatile·Memory Map·Aliasing 분석
- 38GPIO 드라이버 직접 구현 — STM32 HAL 없이 레지스터로
- 39임베디드 클럭 설정 분석 — HSE·PLL·SYSCLK·AHB/APB 분주
- 40Cortex-M 인터럽트 핸들링 — NVIC·Priority·Vector·EXTI
- 41SysTick 타이머 활용 — 24-bit Counter·1ms Tick·delay 구현
- 42UART 드라이버 구현 — polling·interrupt·DMA 3가지 방식 비교
- 43SPI 드라이버 구현 — Master·Slave·CRC·DMA
- 44I2C 드라이버 구현 — Master·7-bit/10-bit·Clock Stretching 처리
- 45임베디드 DMA 기초 — Memory-to-Memory·Peripheral·Circular Mode
- 46저전력 모드 분석 — Sleep·Stop·Standby·Wake-up Source
- 47IWDG·WWDG 워치독 구현 — Independent vs Window 비교
- 48임베디드 Flash 프로그래밍 — Erase·Program·Read While Write
- 49DDR 초기화 실패 진단 — Timing·Calibration·Walking Bit Test
- 50PWM 출력 실전 — LED 밝기·모터 속도 제어
- 51DC 모터 제어 — H-Bridge·PWM Duty·Encoder Feedback
- 52스테퍼 모터 제어 — Full Step·Half Step·Microstepping
- 53서보 모터 제어 — PWM 1ms~2ms·Closed Loop·PID
- 54Character LCD 제어 — HD44780·4-bit Mode·Custom Char
- 55SPI OLED 제어 — SSD1306·Frame Buffer·Page 단위 갱신
- 56TFT 디스플레이 구동 — RGB565·FSMC·LTDC·DMA2D
- 57환경 센서 활용 — BME280 온습압·SHT3x·BMP180 비교
- 58IMU 센서 활용 — MPU6050·LSM6DSO·Sensor Fusion
- 59CAN 통신 구현 — bxCAN·Filter·Mailbox·CAN-FD
- 60USB Device 기초 — Descriptor·Enumeration·Endpoint·HID/CDC
- 61Ethernet MAC+PHY 통합 — RMII·lwIP·DMA Descriptor
- 62SD Card + FatFs 구현 — SPI/SDIO 모드·CSD/CID·Wear
- 63RTC 활용 — Calendar·Alarm·Wake-up Timer·Backup Domain
- 64RTOS 도입 결정 분석 — Super Loop vs RTOS 트레이드오프
- 65RTOS Task 설계 패턴 — 우선순위·스택·State Machine
- 66RTOS Scheduler 동작 분석 — Tick·Context Switch·Yield
- 67RTOS Semaphore 활용 — Binary·Counting·ISR Give
- 68RTOS Mutex 활용 — Recursive·Priority Inheritance 적용
- 69RTOS Queue 활용 — By-Value·By-Reference·Timeout 패턴
- 70RTOS Event Group 활용 — Bit Wait·Sync·Notify
- 71RTOS Software Timer 활용 — One-shot·Auto-reload·Daemon Task
- 72ISR-Safe API 설계 — Reentrant·Atomic·Defer 패턴
- 73Priority Inversion 진단·예방 — Mars Pathfinder Lesson 추적
- 74Timer Wheel 분석 — Hashed·Hierarchical·O(1) Tick
- 75RTOS 디버깅 기법 — Tracealyzer·SystemView·Stack 추적
- 76임베디드 Linux 부팅 흐름 분석 — BootROM·U-Boot·Kernel·init
- 77U-Boot 활용 — bootcmd·env·tftp·boot.scr 분석
- 78Device Tree 실전 — DTS·DTB·Overlay·Phandle 추적
- 79Device Tree Overlay 적용 — Runtime fragment·dtoverlay
- 80임베디드 커널 빌드 — defconfig·menuconfig·Image·zImage
- 81커널 모듈 기초 — init/exit·Parameter·KBuild·DKMS
- 82캐릭터 드라이버 작성 — file_operations·cdev·register_chrdev
- 83Platform 드라이버 작성 — probe·remove·of_match·DT 바인딩
- 84mmap 4가지 모드 — Anonymous·File·Shared·Huge Page
- 85epoll 실전 — LT·ET·ONESHOT·EXCLUSIVE 비교
- 86UIO·VFIO 분석 — User-Space Driver와 IOMMU 격리
- 87sysfs·configfs 활용 — kobject 기반 User 인터페이스
- 88IRQ Affinity 튜닝 — smp_affinity·isolcpus·irqbalance
- 89루트 파일시스템 구축 — Buildroot 기초·Package·Toolchain
- 90임베디드 동적 메모리 — malloc 위험·결정성·대안 분석
- 91메모리 정렬과 패딩 분석 — Natural·Strict Alignment·Trap
- 92Cache Line Alignment — alignas·Padding·SoA 적용
- 93DMA-Friendly Allocator — dma_alloc_coherent·IOMMU·Pool
- 94Zero-Copy Pipeline — DMA-BUF·sendfile·io_uring·splice
- 95NUMA Memory Topology — numactl·numa_alloc·HBM 적용
- 96SIMD 활용 분석 — Intrinsics·Auto-Vectorization·OpenMP SIMD
- 97ARM NEON 심화 — Matrix Multiply·FFT·Image Filter 적용
- 98임베디드 스택 분석 — high-water·overflow 탐지
- 99임베디드 코드 크기 최적화 — -Os·LTO·Section Garbage Collection
- 100임베디드 전력 최적화 — Sleep Mode·Clock Gating·DVFS
- 101WCET 분석 기법 — Static·Measurement·Hybrid 방법론
- 102Lock-Free Ring Buffer 구현 — SPSC·Power-of-2·Memory Order
- 103Wait-Free Signaling — Atomic Flag·Sequence·Latest-Value
- 104RCU (Read-Copy-Update) 기초 — Quiescent State·Grace Period
- 105Hazard Pointer 분석 — Lock-Free Memory Reclamation
- 106Compare-And-Swap 패턴 — Stack·Counter·Linked List 적용
- 107Atomic Operation 비용 분석 — Fence·Cache Line·Contention
- 108Spinlock vs Mutex 결정 가이드 — Context Switch·Hold Time
- 109ABA 문제 회피 — Tagged Pointer·Hazard·Generation Counter
- 110False Sharing 해결 — Cache Line Padding·SoA 적용
- 111MPMC Queue 구현 — Multi-producer Multi-consumer Lock-Free
- 112임베디드 디버깅 마인드셋 — 가설·격리·재현·이분탐색
- 113JTAG·SWD 안 붙을 때 — 핀·전압·속도·세션 진단
- 114GDB 원격 디버깅 — OpenOCD·J-Link·target remote 구성
- 115Cortex-M 하드폴트 분석 — Stacked Frame·CFSR 읽기
- 116UART 안 찍힐 때 — Bare-metal 체크리스트
- 117임베디드 부팅 실패 진단 — 단계별 Isolation
- 118인터럽트 누락·중복 진단 — Priority·Pending·Re-entry 추적
- 119메모리 오버플로우·오염 진단 — Canary·MPU·Pattern 분석
- 120타이밍·Race 진단 — Heisenbug 잡는 법
- 121통신 프로토콜 분석 — Logic Analyzer와 Protocol Decoder
- 122임베디드 로깅 시스템 설계 — 레벨·버퍼·SWO·Deferred
- 123임베디드 포스트모템 분석 — Core Dump와 Field Crash
- 124FPGA 기초 분석 — LUT·FF·BRAM·DSP 자원 구조
- 125Vivado 사용법 — Project·Constraint·Synth·Impl·Bitstream
- 126PCIe BAR 매핑 분석 — Config Space·Enumeration·MMIO 접근
- 127AXI 인터페이스 — AXI4·AXI4-Lite·AXI-Stream 비교
- 128Zynq PS-PL 통신 — GP·HP·ACP 인터페이스 선택
- 129Mailbox Protocol 분석 — Host와 Accelerator를 잇는 Doorbell
- 130Command Queue·Submission Queue — NVMe·XDMA 공통 패턴
- 131DMA Completion 메커니즘 — Interrupt·Polling·Completion Ring
- 132PCIe Streaming 분석 — BAR Type·MSI-X·Kernel Bypass
- 133Vitis HLS 분석 — Pragma·Pipeline II·Dataflow 실전 감각
- 134HLS 최적화 기법 — Pipeline·Unroll·Partition·Dataflow
- 135Vitis AI 분석 — DPU·xmodel·VART
- 136OpenCL on FPGA — Kernel·Channel·Burst Memory 분석
- 137Intel Quartus 사용법 — Platform Designer·Nios II·HLS
- 138Edge Inference 분석 — Cloud vs Edge·Latency·Privacy
- 139NPU 아키텍처 분석 — Ethos·Hexagon·Systolic Array 비교
- 140딥러닝 Quantization 분석 — PTQ·QAT·INT8·INT4·Calibration
- 141TensorRT 분석 — ONNX→Engine·FP16·INT8·DLA·Multi-Stream
- 142TFLite Micro 분석 — Op Resolver·Tensor Arena·Cortex-M
- 143ONNX Runtime 분석 — Execution Provider와 Cross-Platform 배포
- 144Edge Thermal Management — Throttling·DVFS·Fan Curve·Sustained
- 145NVIDIA Jetson 분석 — Nano·Xavier·Orin·Thor·JetPack·DLA·VPI
- 146Zero-Copy Camera Pipeline — V4L2·DMA-BUF·GPU Import·NPU 직결
- 147온디바이스 LLM 추론 — llama.cpp·GGUF·MLX·KV Cache·NPU Backend
- 148Cortex-M33 TF-M·TrustZone — Secure Firmware·PSA·MCUboot
- 149Matter·Thread 분석 — IoT 통합 표준·Commissioning·Multi-Fabric
- 150PCIe → CXL 진화 — 같은 PHY 위 cache-coherent 프로토콜 추가
- 151QEMU CXL Type 3 디바이스 에뮬레이션 — 노트북에서 CXL 개발 환경 구축
- 152Linux CXL 드라이버 분석 — cxl_pci·cxl_core·region·DAX
관련 글
임베디드 포스트모템 분석 — Core Dump와 Field Crash
Linux coredump·gdb 분석부터 MCU 환경의 mini-dump(Memfault)·last-gasp logging·field debug 패턴까지.
임베디드 로깅 시스템 설계 — 레벨·버퍼·SWO·Deferred
임베디드 환경에서 overhead를 최소화한 로깅. 레벨 분리·circular buffer·SWO/RTT·deferred 처리 패턴.
통신 프로토콜 분석 — Logic Analyzer와 Protocol Decoder
Saleae·DSLogic·oscilloscope·protocol decoder로 UART/SPI/I2C/CAN 신호를 캡처·디코딩하는 실전 패턴.