본문으로 건너뛰기
Modern Embedded Recipes · 118/152

메모리 오버플로우·오염 진단 — Canary·MPU·Pattern 분석

· Hawk · 5분 읽기

#한 줄 요약

“메모리 오염은 누가 망가뜨렸는지를 잡는 게 본질입니다.” Canary·MPU guard·data watchpoint·desktop ASan을 조합하면 현장에서 범인을 잡습니다.

#어떤 상황에서 쓰나

“평소엔 잘 도는데 1시간쯤 지나면 다른 변수 값이 이상하게 바뀌어요.” “함수가 끝나고 caller로 못 돌아와요 (Hardfault PC가 이상한 영역).” 이런 류는 거의 다 메모리 오염입니다.

오염은 현장에서 일어나지만 증상은 다른 코드에서 나타나므로, 디버깅이 까다롭습니다. 도구로 현장에서 잡아야 합니다.

#핵심 개념 — 오염 vs 증상

[현장] buf[1024]를 [1030]까지 씀 ← 누가
[증상] buf 직후 stack의 LR이 깨짐 → return 시 hardfault ← 어디서 보이는지

증상에서 거꾸로 추적하지 않고, 현장 자체를 잡는 도구를 둡니다.

#도구 1 — Stack canary (Compiler)

Terminal window
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 = 5
New 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으로 돌립니다.

Terminal window
gcc -fsanitize=address -fno-omit-frame-pointer -g \
parser.c parser_test.c -o test
./test
==12345==ERROR: AddressSanitizer: heap-buffer-overflow
WRITE 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_state
Old value = 5
New value = 0xdeadbeef
dma_callback (ch=2) at dma.c:142

DMA가 원래 의도와 다른 주소에 buffer를 적었습니다. DMA destination address가 한 byte 어긋나 g_state 위에 떨어졌습니다.

Watchpoint 없이는 dma_callback과 g_state의 관계를 영원히 못 잡았을 것입니다.

#사례 — NULL pointer + 4

HardFault, BFAR = 0x00000004

NULL->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

  1. 1Modern Embedded Recipes — 모던 임베디드 실전 레시피 시리즈 소개
  2. 2디지털 신호 기초 — Voltage Level·Edge·Setup/Hold 분석
  3. 3임베디드 클럭과 타이밍 — Skew·Jitter·PLL·MMCM 분석
  4. 4GPIO 내부 구조 분해 — Push-Pull·Open-Drain·Schmitt Trigger
  5. 5UART 하드웨어 동작 분석 — Baud Rate·Framing·FIFO
  6. 6SPI 하드웨어 분석 — Clock Mode·MOSI/MISO·Chip Select
  7. 7I2C 하드웨어 분석 — Open-Drain·Clock Stretching·Arbitration
  8. 8ADC 동작 원리 — SAR·Sigma-Delta·Pipelined 비교
  9. 9DAC 동작 원리 — R-2R Ladder·Sigma-Delta·Settling Time
  10. 10PWM 신호 생성 분석 — Duty·Frequency·Dead Time·Center-Aligned
  11. 11CAN 버스 전기적 특성 — Differential·Termination·Dominant/Recessive
  12. 12RS-485·RS-422 차동 신호 분석 — Termination·Biasing·Topology
  13. 13LVDS 차동 신호 분석 — Common-Mode·Impedance·Eye Pattern
  14. 14ARM Cortex-M 시리즈 비교 — M0·M3·M4·M7·M33·M55 분석
  15. 15ARM Cortex-A 시리즈 비교 — A53·A55·A72·A78·X1 분석
  16. 16ARM 레지스터 구조 분석 — R0~R15·CPSR·SPSR·Banked Registers
  17. 17Cortex-M 예외 처리 — Vector Table·NVIC·Tail-Chaining 추적
  18. 18ARM 메모리 맵 분석 — Normal·Device·Strongly-Ordered Region
  19. 19ARM L1·L2 캐시 분석 — Set Associative·Inclusive·Maintenance
  20. 20ARM MPU 활용 — Region·Attribute·Privilege Separation
  21. 21ARM MMU 기초 분석 — Translation Table·TLB·ASID
  22. 22ARM TrustZone-M 기초 — Secure/Non-Secure·NSC·MPC
  23. 23ARM Memory Barrier 실전 — DMB·DSB·ISB·DMA·MMIO
  24. 24임베디드 크로스 컴파일러 분석 — GCC·Clang·Sysroot 구성
  25. 25C 컴파일 4단계 — Preprocess·Compile·Assemble·Link 추적
  26. 26ELF 파일 구조 분석 — Section·Segment·Symbol Table·DWARF
  27. 27링커 스크립트 기초 — SECTIONS·MEMORY·entry point
  28. 28링커 스크립트 고급 — Overlay·BSS·init_array·LMA/VMA
  29. 29임베디드 스타트업 코드 분석 — Reset_Handler·Vector Table·SystemInit
  30. 30C 런타임 crt0 분석 — Stack·BSS Zero·Data Copy·atexit
  31. 31임베디드 메모리 레이아웃 — .text·.rodata·.data·.bss·.heap·.stack
  32. 32임베디드 컴파일러 최적화 분석 — -O0~-O3·-Os·-LTO 비교
  33. 33Map 파일 분석 — Symbol·Section·Size 추적으로 코드 크기 진단
  34. 34Make·CMake 크로스 컴파일 — Toolchain File·Sysroot 통합
  35. 35임베디드 Bootloader 체인 — BootROM·SPL·U-Boot·Kernel·Secure Boot
  36. 36첫 bare-metal 프로그램 작성 — Linker·Startup·main의 최소 구성
  37. 37MMIO 레지스터 직접 접근 — volatile·Memory Map·Aliasing 분석
  38. 38GPIO 드라이버 직접 구현 — STM32 HAL 없이 레지스터로
  39. 39임베디드 클럭 설정 분석 — HSE·PLL·SYSCLK·AHB/APB 분주
  40. 40Cortex-M 인터럽트 핸들링 — NVIC·Priority·Vector·EXTI
  41. 41SysTick 타이머 활용 — 24-bit Counter·1ms Tick·delay 구현
  42. 42UART 드라이버 구현 — polling·interrupt·DMA 3가지 방식 비교
  43. 43SPI 드라이버 구현 — Master·Slave·CRC·DMA
  44. 44I2C 드라이버 구현 — Master·7-bit/10-bit·Clock Stretching 처리
  45. 45임베디드 DMA 기초 — Memory-to-Memory·Peripheral·Circular Mode
  46. 46저전력 모드 분석 — Sleep·Stop·Standby·Wake-up Source
  47. 47IWDG·WWDG 워치독 구현 — Independent vs Window 비교
  48. 48임베디드 Flash 프로그래밍 — Erase·Program·Read While Write
  49. 49DDR 초기화 실패 진단 — Timing·Calibration·Walking Bit Test
  50. 50PWM 출력 실전 — LED 밝기·모터 속도 제어
  51. 51DC 모터 제어 — H-Bridge·PWM Duty·Encoder Feedback
  52. 52스테퍼 모터 제어 — Full Step·Half Step·Microstepping
  53. 53서보 모터 제어 — PWM 1ms~2ms·Closed Loop·PID
  54. 54Character LCD 제어 — HD44780·4-bit Mode·Custom Char
  55. 55SPI OLED 제어 — SSD1306·Frame Buffer·Page 단위 갱신
  56. 56TFT 디스플레이 구동 — RGB565·FSMC·LTDC·DMA2D
  57. 57환경 센서 활용 — BME280 온습압·SHT3x·BMP180 비교
  58. 58IMU 센서 활용 — MPU6050·LSM6DSO·Sensor Fusion
  59. 59CAN 통신 구현 — bxCAN·Filter·Mailbox·CAN-FD
  60. 60USB Device 기초 — Descriptor·Enumeration·Endpoint·HID/CDC
  61. 61Ethernet MAC+PHY 통합 — RMII·lwIP·DMA Descriptor
  62. 62SD Card + FatFs 구현 — SPI/SDIO 모드·CSD/CID·Wear
  63. 63RTC 활용 — Calendar·Alarm·Wake-up Timer·Backup Domain
  64. 64RTOS 도입 결정 분석 — Super Loop vs RTOS 트레이드오프
  65. 65RTOS Task 설계 패턴 — 우선순위·스택·State Machine
  66. 66RTOS Scheduler 동작 분석 — Tick·Context Switch·Yield
  67. 67RTOS Semaphore 활용 — Binary·Counting·ISR Give
  68. 68RTOS Mutex 활용 — Recursive·Priority Inheritance 적용
  69. 69RTOS Queue 활용 — By-Value·By-Reference·Timeout 패턴
  70. 70RTOS Event Group 활용 — Bit Wait·Sync·Notify
  71. 71RTOS Software Timer 활용 — One-shot·Auto-reload·Daemon Task
  72. 72ISR-Safe API 설계 — Reentrant·Atomic·Defer 패턴
  73. 73Priority Inversion 진단·예방 — Mars Pathfinder Lesson 추적
  74. 74Timer Wheel 분석 — Hashed·Hierarchical·O(1) Tick
  75. 75RTOS 디버깅 기법 — Tracealyzer·SystemView·Stack 추적
  76. 76임베디드 Linux 부팅 흐름 분석 — BootROM·U-Boot·Kernel·init
  77. 77U-Boot 활용 — bootcmd·env·tftp·boot.scr 분석
  78. 78Device Tree 실전 — DTS·DTB·Overlay·Phandle 추적
  79. 79Device Tree Overlay 적용 — Runtime fragment·dtoverlay
  80. 80임베디드 커널 빌드 — defconfig·menuconfig·Image·zImage
  81. 81커널 모듈 기초 — init/exit·Parameter·KBuild·DKMS
  82. 82캐릭터 드라이버 작성 — file_operations·cdev·register_chrdev
  83. 83Platform 드라이버 작성 — probe·remove·of_match·DT 바인딩
  84. 84mmap 4가지 모드 — Anonymous·File·Shared·Huge Page
  85. 85epoll 실전 — LT·ET·ONESHOT·EXCLUSIVE 비교
  86. 86UIO·VFIO 분석 — User-Space Driver와 IOMMU 격리
  87. 87sysfs·configfs 활용 — kobject 기반 User 인터페이스
  88. 88IRQ Affinity 튜닝 — smp_affinity·isolcpus·irqbalance
  89. 89루트 파일시스템 구축 — Buildroot 기초·Package·Toolchain
  90. 90임베디드 동적 메모리 — malloc 위험·결정성·대안 분석
  91. 91메모리 정렬과 패딩 분석 — Natural·Strict Alignment·Trap
  92. 92Cache Line Alignment — alignas·Padding·SoA 적용
  93. 93DMA-Friendly Allocator — dma_alloc_coherent·IOMMU·Pool
  94. 94Zero-Copy Pipeline — DMA-BUF·sendfile·io_uring·splice
  95. 95NUMA Memory Topology — numactl·numa_alloc·HBM 적용
  96. 96SIMD 활용 분석 — Intrinsics·Auto-Vectorization·OpenMP SIMD
  97. 97ARM NEON 심화 — Matrix Multiply·FFT·Image Filter 적용
  98. 98임베디드 스택 분석 — high-water·overflow 탐지
  99. 99임베디드 코드 크기 최적화 — -Os·LTO·Section Garbage Collection
  100. 100임베디드 전력 최적화 — Sleep Mode·Clock Gating·DVFS
  101. 101WCET 분석 기법 — Static·Measurement·Hybrid 방법론
  102. 102Lock-Free Ring Buffer 구현 — SPSC·Power-of-2·Memory Order
  103. 103Wait-Free Signaling — Atomic Flag·Sequence·Latest-Value
  104. 104RCU (Read-Copy-Update) 기초 — Quiescent State·Grace Period
  105. 105Hazard Pointer 분석 — Lock-Free Memory Reclamation
  106. 106Compare-And-Swap 패턴 — Stack·Counter·Linked List 적용
  107. 107Atomic Operation 비용 분석 — Fence·Cache Line·Contention
  108. 108Spinlock vs Mutex 결정 가이드 — Context Switch·Hold Time
  109. 109ABA 문제 회피 — Tagged Pointer·Hazard·Generation Counter
  110. 110False Sharing 해결 — Cache Line Padding·SoA 적용
  111. 111MPMC Queue 구현 — Multi-producer Multi-consumer Lock-Free
  112. 112임베디드 디버깅 마인드셋 — 가설·격리·재현·이분탐색
  113. 113JTAG·SWD 안 붙을 때 — 핀·전압·속도·세션 진단
  114. 114GDB 원격 디버깅 — OpenOCD·J-Link·target remote 구성
  115. 115Cortex-M 하드폴트 분석 — Stacked Frame·CFSR 읽기
  116. 116UART 안 찍힐 때 — Bare-metal 체크리스트
  117. 117임베디드 부팅 실패 진단 — 단계별 Isolation
  118. 118인터럽트 누락·중복 진단 — Priority·Pending·Re-entry 추적
  119. 119메모리 오버플로우·오염 진단 — Canary·MPU·Pattern 분석
  120. 120타이밍·Race 진단 — Heisenbug 잡는 법
  121. 121통신 프로토콜 분석 — Logic Analyzer와 Protocol Decoder
  122. 122임베디드 로깅 시스템 설계 — 레벨·버퍼·SWO·Deferred
  123. 123임베디드 포스트모템 분석 — Core Dump와 Field Crash
  124. 124FPGA 기초 분석 — LUT·FF·BRAM·DSP 자원 구조
  125. 125Vivado 사용법 — Project·Constraint·Synth·Impl·Bitstream
  126. 126PCIe BAR 매핑 분석 — Config Space·Enumeration·MMIO 접근
  127. 127AXI 인터페이스 — AXI4·AXI4-Lite·AXI-Stream 비교
  128. 128Zynq PS-PL 통신 — GP·HP·ACP 인터페이스 선택
  129. 129Mailbox Protocol 분석 — Host와 Accelerator를 잇는 Doorbell
  130. 130Command Queue·Submission Queue — NVMe·XDMA 공통 패턴
  131. 131DMA Completion 메커니즘 — Interrupt·Polling·Completion Ring
  132. 132PCIe Streaming 분석 — BAR Type·MSI-X·Kernel Bypass
  133. 133Vitis HLS 분석 — Pragma·Pipeline II·Dataflow 실전 감각
  134. 134HLS 최적화 기법 — Pipeline·Unroll·Partition·Dataflow
  135. 135Vitis AI 분석 — DPU·xmodel·VART
  136. 136OpenCL on FPGA — Kernel·Channel·Burst Memory 분석
  137. 137Intel Quartus 사용법 — Platform Designer·Nios II·HLS
  138. 138Edge Inference 분석 — Cloud vs Edge·Latency·Privacy
  139. 139NPU 아키텍처 분석 — Ethos·Hexagon·Systolic Array 비교
  140. 140딥러닝 Quantization 분석 — PTQ·QAT·INT8·INT4·Calibration
  141. 141TensorRT 분석 — ONNX→Engine·FP16·INT8·DLA·Multi-Stream
  142. 142TFLite Micro 분석 — Op Resolver·Tensor Arena·Cortex-M
  143. 143ONNX Runtime 분석 — Execution Provider와 Cross-Platform 배포
  144. 144Edge Thermal Management — Throttling·DVFS·Fan Curve·Sustained
  145. 145NVIDIA Jetson 분석 — Nano·Xavier·Orin·Thor·JetPack·DLA·VPI
  146. 146Zero-Copy Camera Pipeline — V4L2·DMA-BUF·GPU Import·NPU 직결
  147. 147온디바이스 LLM 추론 — llama.cpp·GGUF·MLX·KV Cache·NPU Backend
  148. 148Cortex-M33 TF-M·TrustZone — Secure Firmware·PSA·MCUboot
  149. 149Matter·Thread 분석 — IoT 통합 표준·Commissioning·Multi-Fabric
  150. 150PCIe → CXL 진화 — 같은 PHY 위 cache-coherent 프로토콜 추가
  151. 151QEMU CXL Type 3 디바이스 에뮬레이션 — 노트북에서 CXL 개발 환경 구축
  152. 152Linux CXL 드라이버 분석 — cxl_pci·cxl_core·region·DAX