Showing Posts From

메모리

메모리 누수 추적 3주간의 기록

메모리 누수 추적 3주간의 기록

메모리 누수 추적 3주간의 기록 1주차: 뭔가 이상하다 제품이 12시간 돌면 죽는다. 정확히 말하면 11시간 43분에서 12시간 18분 사이. 이게 재현이 안 된다는 게 문제다. 월요일 아침. QA팀에서 메일이 왔다. "장시간 구동 시 통신 끊김 현상" 첨부된 로그를 봤다. Heap 할당 실패. 우리 제품은 STM32F4. RAM 192KB. malloc 안 쓴다. 정책이다. 동적 할당은 fragmentation 때문에 금지. 그럼 뭐가 문제지?화요일. Memory map을 뜯어봤다. .bss, .data, .heap, .stack 영역 확인. 링커 스크립트 다시 읽었다. Stack은 0x2000_0000에서 시작. 32KB 할당. Heap은 쓰지 않으니 패스. RTOS는 FreeRTOS. Task별 Stack 사이즈 체크. 총 5개 Task.Main: 2KB Network: 4KB Sensor: 1KB Display: 2KB Logger: 3KB합치면 12KB. 여유 있다. 수요일. uxTaskGetStackHighWaterMark() 함수 추가. 각 Task의 최소 남은 Stack 확인. 12시간 돌렸다. 출력값:Main: 856 bytes Network: 124 bytes ← 이거다 Sensor: 512 bytes Display: 1024 bytes Logger: 648 bytesNetwork Task가 거의 다 썼다. 2주차: Stack을 늘려도 죽는다 목요일. Network Task Stack을 8KB로 증가. 다시 돌렸다. 금요일 아침. 또 죽었다. 14시간 만에. 로그 확인. 이번엔 Network가 아니다. Main Task에서 Hard Fault. 뭐지?팀장님한테 보고. "Stack 늘려도 안 됩니다" "malloc 안 쓰는 거 확실해?" "네, 전체 검색 돌렸습니다" 그 자리에서 같이 코드 리뷰. 30분 보다가 팀장님이 물었다. "printf 쓰니?" 쓴다. 디버그 메시지용. UART로 출력. "버퍼 크기는?" "256 bytes요" "메시지 길이는?" "...확인 안 했습니다" 오후 내내 printf 사용처 체크. 총 87군데. 대부분은 짧다. 그런데 하나 발견. char timestamp[32]; char sensor_data[64]; sprintf(buffer, "Time:%s Data:%s Value:%d Status:%d", timestamp, sensor_data, value, status);buffer는 256인데. 만약 timestamp와 sensor_data가 full이면? 32 + 64 + α = 100자 넘는다. 여전히 256 안에 든다. 근데 뭔가 찝찝하다. 월요일. 모든 sprintf를 snprintf로 교체. 길이 체크 추가. 3일 돌렸다. 또 죽었다. 3주차: 진짜 범인 수요일 밤 10시. 팀원들 다 퇴근했다. 나 혼자 남았다. GDB 연결. Breakpoint 100개 찍었다. 메모리 영역별로. 새벽 2시. 뭔가 보였다. Network Task에서 recv() 호출할 때. Buffer 주소가 이상하다. 0x2001_F000대. 우리 RAM 끝이 0x2003_0000. 0x2001_F000이면 괜찮은 주소 아닌가? 아니다. 링커 스크립트 다시 확인. Stack top: 0x2002_0000 Heap start: 0x2002_0000 겹친다. 누가 설정을 잘못했다. 아니, 초기 설정은 맞았다. 3개월 전 MCU를 F4에서 F7로 바꾸면서. RAM이 192KB에서 320KB로 늘었다. 근데 링커 스크립트는 안 고쳤다.목요일. 링커 스크립트 수정. Stack: 0x2000_0000 ~ 0x2001_0000 (64KB) Heap: 0x2001_0000 ~ 0x2004_0000 (192KB) 빌드. 플래싱. 동작 확인. 72시간 돌렸다. 안 죽는다. 금요일 오후. 팀장님한테 보고. "메모리 레이아웃 문제였습니다" "3주 걸렸네" "...죄송합니다" "괜찮아. 찾았으면 됐지" QA한테 전달. 168시간 테스트 시작. 배운 것들 malloc 안 써도 메모리 문제는 생긴다. 특히 Embedded에서는. Stack과 Heap이 충돌하면? 증상이 random하다. 재현도 어렵다. 디버깅은 지옥이다. Memory map은 항상 의심해야 한다. MCU 바뀔 때마다 체크. RAM 크기 변하면 링커 스크립트도 같이. uxTaskGetStackHighWaterMark()는 신이다. FreeRTOS 쓰면 꼭 넣어라. 각 Task 시작할 때 한 번. 종료할 때 한 번. printf는 위험하다. Buffer overflow 체크 안 하면 망한다. snprintf 쓰자. 길이 제한 걸자. GDB는 만능이 아니다. Embedded 디버깅은 다르다. Hardware 봐야 하고. Timing 봐야 하고. 때로는 오실로스코프가 답이다. 3주가 길다고? 어떤 버그는 3개월 간다. 어떤 건 양산 나가서 발견된다. 그것보단 낫다. 다음 주 월요일. 새 프로젝트 시작. 이번엔 nRF52. RAM이 256KB. 링커 스크립트부터 체크했다. 괜찮다. 이제 다른 버그를 찾으러 간다.메모리 누수가 아니라 메모리 충돌이었다. 더 최악이지 뭐.