Showing Posts From

Stack

Stack 크기를 256에서 512로 늘렸더니 해결됐다

Stack 크기를 256에서 512로 늘렸더니 해결됐다

Stack 크기를 256에서 512로 늘렸더니 해결됐다 이상한 증상 출근했다. 어제 밤 11시까지 디버깅했는데 똑같다. 보드가 이상하다. 부팅은 된다. UART 로그도 찍힌다. 근데 5분 지나면 먹통이다. 규칙이 없다. 3분에 죽을 때도 있고 10분 버틸 때도 있다. 에러 문구? 없다. HardFault? 안 뜬다. 그냥 조용히 죽는다. "하드웨어 이슈 아닐까요?" HW팀에 물었다. 전원 확인했다. 괜찮다. 클럭도 정상이다. 온도도 문제없다. 결국 내 문제다.로그를 믿지 마라 printf 디버깅부터 시작했다. printf("Task A start\n"); // ... 작업 printf("Task A end\n");Task A는 정상이다. Task B도 정상이다. Task C에서 로그가 끊긴다. "Task C 문제네." 아니었다. Task C 코드를 주석 처리했다. 똑같이 죽는다. 그럼 Task D? 주석 처리. 여전히 죽는다. 3시간 썼다. 의미 없었다. FreeRTOS 쓰고 있었다. Task가 5개다. 우선순위도 다 다르다. 누가 죽이는지 모르겠다. printf는 거짓말한다. 버퍼에 쌓여 있다가 나중에 출력된다. 죽기 직전 로그는 안 나온다. JTAG도 힌트가 없다 오실로스코ープ는 소용없다. 신호는 정상이다. JTAG 붙였다. Breakpoint 걸었다. 한 줄씩 따라갔다. 멀쩡하다. Breakpoint 없이 돌리면? 죽는다. "타이밍 이슈인가?" Interrupt 의심했다. ISR 코드 확인했다. 간단하다. Flag만 세운다. void EXTI_IRQHandler(void) { flag = 1; HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5); }문제없어 보인다.간헐적 버그의 지옥 이틀째다. 팀장이 물었다. "진행 상황이?" "디버깅 중입니다." "언제 끝나?" 모른다. 간헐적 버그는 예측 불가다. 재현이 안 된다. 5분에 죽을 때도 있고 30분 버틸 때도 있다. 규칙을 찾을 수가 없다. 코드 리뷰했다. 동적 할당? 안 쓴다. Malloc 금지다. 전역 변수? 있다. Mutex로 보호했다. 레이스 컨디션? 가능성 있다. 근데 어디서? 밤 10시까지 봤다. 못 찾겠다. 집에 와서도 생각났다. 샤워하다가도 생각했다. 어디가 문제일까. 메모리 주소를 봐라 셋째 날 아침이다. 다른 접근을 해야 한다. 생각을 바꿨다. "증상이 없는데 어떻게 추적하지?" 메모리를 봤다. RAM 사용량을 확인했다. .data: 2048 bytes .bss: 4096 bytes heap: 8192 bytes stack: ???? bytesStack은 런타임에 바뀐다. 컴파일러가 미리 계산 못 한다. Map 파일을 열었다. 각 Task의 Stack 크기가 나온다. TaskA: 256 bytes TaskB: 256 bytes TaskC: 512 bytes TaskD: 256 bytes TaskE: 256 bytesTaskA를 집중했다. 뭘 하는 Task인가? void TaskA(void *param) { char buffer[128]; while(1) { snprintf(buffer, 128, "Sensor: %d", read_sensor()); send_uart(buffer); vTaskDelay(1000); } }Buffer 128바이트다. Stack은 256바이트다. 괜찮아 보인다. 근데 snprintf는 내부적으로 Stack을 더 쓴다. 얼마나? 모른다. libc 구현마다 다르다. 혹시?256을 512로 Stack 크기를 늘렸다. #define TASK_A_STACK_SIZE 512 // was 256컴파일했다. 보드에 올렸다. 전원 켰다. 5분 지났다. 안 죽는다. 10분 지났다. 멀쩡하다. 30분 돌렸다. 정상이다. "이게 문제였어?" 믿기지 않았다. 다시 256으로 줄였다. 3분 만에 죽는다. 512로 늘렸다. 안 죽는다. 확정이다. Stack Overflow였다. 근데 이상하다. FreeRTOS는 Stack Overflow를 감지한다. configCHECK_FOR_STACK_OVERFLOW 옵션이 있다. 켜져 있었다. 왜 안 잡혔을까? 찾아봤다. FreeRTOS는 Context Switch 시점에만 체크한다. 그 사이에 오버플로우 나면 못 잡는다. TaskA는 1초마다 실행된다. 그 1초 사이에 Stack이 터진다. 감지 못 한다. 증상이 이상한 이유 Stack Overflow는 조용하다. 메모리를 덮어쓴다. 바로 크래시 안 난다. 다른 변수를 오염시킨다. 나중에 이상한 동작을 한다. 이번 케이스는 운이 나빴다. Stack 옆에 다른 Task의 제어 구조체가 있었다. 그걸 덮어썼다. FreeRTOS는 TCB(Task Control Block)를 RAM에 둔다. Stack Overflow가 TCB를 건드렸다. Task 스케줄링이 꼬였다. 시스템이 조용히 죽었다. 에러 메시지? 없다. HardFault? 안 뜬다. 그냥 멈춘다. 간헐적인 이유는 간단하다. 실행 경로가 매번 다르다. Sensor 값에 따라 snprintf 포맷팅이 다르다. "1"이랑 "1234"는 Stack 사용량이 다르다. 큰 값이 들어오면? Stack이 깊어진다. 터진다. 작은 값이면? 괜찮다. 규칙 없이 죽는 게 정상이었다. 교훈이랄 것도 없다 Stack 크기는 넉넉하게. 256은 작다. Printf 계열 함수 쓰면 최소 512는 줘야 한다. Floating point 쓰면 1024도 모자랄 수 있다. libc는 Stack을 많이 먹는다. 특히 가변 인자 함수들. snprintf, sscanf, sprintf... 다 조심해야 한다. 디버깅 교훈도 있다. 증상이 없으면 메모리를 봐라. 로그는 거짓말한다. 메모리는 거짓말 안 한다. FreeRTOS Stack Overflow 감지는 완벽하지 않다. 믿지 마라. 직접 계산하거나 넉넉하게 할당하거나. 해결했다. 팀장한테 보고했다. "Stack 크기 문제였습니다." "그게 3일 걸려?" 뭐라 하겠나. "네." 양산 전이라 다행이다 이거 양산 나갔으면 끝이었다. 현장에서 간헐적으로 죽는다? 재현 안 된다? 펌웨어 업데이트로 해결 안 된다? (RAM 크기는 고정이니까) 리콜이다. 보드 전부 회수다. 비용? 억 단위다. 다행히 개발 단계에서 잡았다. 양산 일정은 밀렸지만. 퇴근했다. 밤 11시다. 편의점에서 맥주 샀다. 집에서 혼자 마셨다. "Stack 512면 이제 괜찮겠지?" 모른다. 다른 Task도 체크해야 한다. 내일 할 일이다. 오늘은 여기까지다.Stack 256바이트. 펌웨어 개발자의 3일. 결국 숫자 하나였다.