Showing Posts From

하드웨어

금요일 오후 3시, 라이브 제품에서 버그 리포트가 들어왔다

금요일 오후 3시, 라이브 제품에서 버그 리포트가 들어왔다

금요일 오후 3시, 라이브 제품에서 버그 리포트가 들어왔다 금요일 오후 3시 17분 슬랙에 빨간 점이 떴다. CS팀 채널. "펌웨어팀 @김펌웨어 님, 고객사에서 제품 이상 동작 리포트 들어왔습니다. 확인 부탁드립니다." 첨부된 영상을 봤다. 우리 제품이 멈춰있다. 화면에 아무것도 안 뜬다. LED만 깜빡인다. 양산 나간 지 3개월 된 제품이다. 지금까지 문제없었다. 갑자기 왜. "재현되나요?" 물었다. "고객사에서는 자주 발생한다고 합니다. 저희 테스트에서는 재현 안 됩니다." 제일 싫은 유형이다.일단 로그부터 고객사에 요청했다. "디버그 로그 남아있나요?" 30분 후 답장. "로그 기능 꺼져있었습니다." 당연하지. 양산 버전은 로그 비활성화했다. 플래시 용량 아끼려고. "언제부터 발생했나요?" "정확히는 모르겠습니다. 이번 주에 여러 번 발생했다고 합니다." 증상만 봐서는 모른다. 리셋인지, 하드폴트인지, 워치독인지. 우리 테스트 환경에서는 3시간째 돌려도 멀쩡하다. 고객 환경이 뭐가 다른 거지. 팀장한테 보고했다. "양산 제품 이슈 들어왔습니다. 재현은 안 됩니다." "심각한가?" "고객사에서 자주 발생한다고 합니다." "주말에 대응 가능한가?" 알았다는 뜻이다. 환경 차이 고객사 환경을 조사했다. CS팀이 정리해준 내용.24시간 연속 동작 온도: 실내, 에어컨 있음 전원: 5V 어댑터 네트워크: WiFi, 공유기는 TP-Link 펌웨어 버전: v1.2.3 (최신)우리 테스트 환경이랑 똑같다. 뭐가 다르지. HW팀 민수한테 물었다. "하드웨어 이슈 가능성?" "양산 전에 다 검증했는데요. EMC 테스트도 통과했고." "혹시 로트 문제?" "같은 로트 제품 다른 데서는 문제없어요." 소프트웨어 문제다. 그것도 특정 조건에서만.금요일 오후 5시 팀원들 다 퇴근했다. 팀장만 남아있다. "일단 집에 가. 주말에 보자." 나도 가고 싶다. 근데 못 간다. 고객사는 월요일까지 답변 원한다. 일요일 밤까지 원인 찾아야 한다. 회사 냉장고에서 레드불 두 개 꺼냈다. 오늘 밤샐 것 같다. 일단 코드 리뷰부터 시작했다. v1.2.3 릴리즈 이후 변경점. 아무것도 없다. v1.2.3이 최신이고, 그 이후 수정사항 없다. 그럼 v1.2.3 자체에 버그가 있다는 건데. 3개월 동안 왜 안 나왔지. 테스트 시나리오를 다시 봤다. 우리가 놓친 케이스가 있다. 24시간 고객사는 24시간 연속 동작이라고 했다. 우리 테스트는 최대 8시간. 보통 8시간이면 충분하다. 메모리 릭도 잡히고, 타이밍 이슈도 나온다. 근데 24시간 이상 돌려야 나오는 버그도 있다. 타이머 오버플로우 같은 거. 코드에서 타이머 쓰는 부분을 찾았다. 여러 개다. WiFi 재연결 타이머, 센서 폴링 타이머, 워치독 타이머, RTC... 한 개씩 체크했다. 변수 타입, 오버플로우 조건, 래핑 처리. 있다. WiFi 재연결 로직에서 uint32_t 타이머를 밀리초로 쓴다. 49일마다 오버플로우. 근데 비교 로직이 단순 대소비교다. 오버플로우 케이스를 안 본다. if (current_time > reconnect_time + timeout) { wifi_reconnect(); }49일 넘어가면 current_time이 0으로 돌아간다. reconnect_time은 큰 값. 이 조건이 영원히 참이 된다. WiFi 재연결을 무한 반복한다. 찾았다.금요일 밤 11시 재현 시나리오를 짰다. 타이머를 강제로 49일 근처 값으로 세팅. 보드에 올렸다. 돌렸다. 10분 후 멈췄다. 똑같은 증상. LED만 깜빡이고, 응답 없다. 로그 출력도 멈췄다. 원인 확정이다. 수정은 간단하다. 타이머 비교 로직을 래핑 세이프하게. if ((int32_t)(current_time - reconnect_time) > timeout) { wifi_reconnect(); }부호 있는 정수로 캐스팅하면 오버플로우 케이스도 올바르게 동작한다. 수정했다. 다시 테스트. 이번엔 안 멈춘다. 1시간 돌렸다. 문제없다. 패치 버전을 만들었다. v1.2.4. 빌드하고, 테스트 보드에 올리고, 검증했다. 새벽 2시다. 문서 작업 버그 리포트를 썼다. 원인: WiFi 재연결 타이머 오버플로우 처리 누락발생 조건: 연속 동작 49일 경과 시영향: 제품 응답 없음, LED만 동작수정: 타이머 비교 로직 오버플로우 세이프하게 수정패치 버전: v1.2.4 CS팀에 전달할 자료도 만들었다. "고객사에 전달 부탁드립니다. 월요일 오전까지 v1.2.4 펌웨어로 업데이트하면 해결됩니다." 양산 제품 패치 계획도 세웠다. 이미 나간 제품들은 OTA로 업데이트. 다행히 우리 제품은 OTA 지원한다. 안 했으면 리콜이다. 새벽 3시. 집에 갔다. 토요일 오전 11시에 일어났다. 슬랙 확인. 팀장: "고생했다. 월요일은 오후 출근해라." 고맙다. CS팀: "고객사에 전달했습니다. 월요일 업데이트 예정입니다." 됐다. 침대에 누웠다. 천장을 봤다. 49일. 1180시간. 누가 그렇게 오래 테스트하나. 우리 테스트 시나리오는 8시간이 최대다. 비용 때문에. 시간 때문에. 양산 전에 못 잡은 버그다. 고객이 먼저 발견했다. Low 레벨 개발은 이런 거다. 한 줄 실수가 49일 뒤에 터진다. 웹이면 고쳐서 배포하면 된다. 5분이면 된다. 펌웨어는 OTA 있어도 조마조마하다. 업데이트 중에 전원 나가면 브릭이다. 그래도 찾아서 다행이다. 교훈 같은 건 없다 타이머 오버플로우는 기본 중의 기본이다. 알고 있었다. 그런데 놓쳤다. 코드 리뷰 때 못 봤다. 테스트로도 못 잡았다. 완벽한 코드는 없다. 완벽한 테스트도 없다. 고객이 발견하기 전에 찾으면 좋은 거고, 못 찾으면 이렇게 된다. 금요일 오후 3시에 리포트 들어오고, 주말 날리고, 새벽에 고치고. 이게 펌웨어 개발이다. 월요일에 팀 회의 때 공유할 것이다. "타이머 비교 로직 체크리스트에 추가하자." 다들 "아 그거" 할 거다. 알면서 놓친 거니까. 그래도 체크리스트에 넣는다. 다음에 또 놓칠 테니까. Low 레벨은 이렇다. 같은 실수를 반복하지 않으려고 체크리스트 만들고, 그래도 놓친다. 그럼 또 추가한다. 체크리스트가 100개 넘어간다. 읽는 사람은 없다. 너무 길어서. 일요일 푹 잤다. 아무것도 안 했다. 월요일 오후 출근 예정이다. 고객사 업데이트 결과 확인해야 한다. 문제없이 되면 좋겠다. OTA 실패하면 또 야근이다. 침대에 누워서 생각했다. 웹 개발자들은 지금 뭐 하고 있을까. 카페에서 맥북 켜고 코딩하고 있을까. 배포 버튼 누르면 끝일까. 부럽다. 나는 보드 없으면 일 못 한다. 회사 와야 한다. 오실로스코프 봐야 한다. 디버거 연결하고, 플래시 지우고, 펌웨어 올리고, 리셋하고, 로그 보고. 49일 버그는 집에서 못 잡는다. 장비가 없어서. 그래도 찾았다. 그걸로 됐다.금요일 오후의 빨간 알림은 늘 시작이다. 주말이 끝나고 월요일이 온다.

웹개발자는 롤백하고, 나는?

웹개발자는 롤백하고, 나는?

웹개발자는 롤백하고, 나는? 지난주 팀 술자리에서 웹개발 하는 대학 후배가 했던 말이 자꾸만 떠올라. "어제 배포 망쳤는데 1분 만에 롤백했어. 문제 없음." 그러곤 웃었다. 맥주 한 잔 마시더니 말이다. 나는 그 말을 듣고 어떤 표정을 했는지 모르겠다. 웃었을 수도 있고, 그냥 마셨을 수도 있고. 기억이 안 난다. 그냥 '아, 그렇구나' 했던 것 같다. 펌웨어는 그렇지 않다. 한 번 나가면 끝 양산이 나가기 전, 문제가 발견되는 게 가장 좋다. 그럼 다시 테스트하고, 버그 고치고, 다시 나간다. 시간은 걸리지만 그래도 시간이면 된다. 양산이 나간 후, 문제가 발견되는 건 다르다. 시리즈 50만 개가 판매되고 있다고 치자. 그 중 200개 정도에서 특정 상황에서만 먹통이 되는 버그가 있다고 하자. 그럼 어떻게 되나? 회사는 리콜을 결정한다. 50만 개를 다 회수해야 할까? 아니다. 200개만이라도 빼서 수리하는 데 생기는 비용. 택배비. 검수 비용. 인건비. 시간. 신뢰도 하락. 그 숫자가 억 단위가 된다. 수십억일 수도 있다. 그게 내 잘못일 수도 있다. 출근하면 테스트 결과부터 어제 밤 10시 50분에 보드에 올린 펌웨어의 테스트 결과가 10시간 돌려졌다. 실패다. 또다시 실패다. "UART로 로그 떴어?" 아니. 부팅도 안 돼. 하드웨어 이슈가 아닐까. HW팀에 연락했다. "그쪽 회로 정상 맞나요?" 이건 내가 이미 3번째 하는 질문이다. "펌웨어가 Initialize 못 하는 거 아니야?" 둘 다 맞을 수도 있다. 하드웨어 설계 스펙이 펌웨어 예상과 다를 수 있다. Reference Manual은 2년 전 버전이고, 실제 칩은 새로 나온 패키지다. 스펙이 바뀌었을 수도 있다. 누군가는 문서를 수정해야 했는데, 그 누군가가 일을 안 했을 수도 있다. 한국 회사지만, 칩은 중국 제조사다. 중국 매뉴얼을 영어로 번역한 거다. 한국어 번역은 없다. 그 스펙 매뉴얼이 1000페이지다. 찾는 게 뭔지를 정확히 모르면서 1000페이지를 찾는다. 마치 영어 사전에서 스펠링 모르는 단어를 찾는 것처럼. 웹개발자의 세계 회의실에서 웹팀 리드가 웃으면서 말했다. "저희는 버그 발견되면 바로 핫픽스하고 배포하면 돼요. 2시간이면 사용자가 문제 없다고 느껴요." 부러운 게 아니다. 그게 상식이기 때문에 더 부럽다. 생각해보니, 웹은 내가 배포한 버전이 뭐가 됐든 상관없다. 사용자는 항상 최신 버전을 본다. 내가 만든 거 못 봐. 버그도 못 봐. 내가 고쳤으면 끝이다. 앱도 마찬가지다. 물론 앱 스토어 심사 시간이 있지만, 긴급 업데이트는 몇 시간 안에 나간다. 사용자가 다운받으면 끝. 과거 버전은 사라진다. 우리는 다르다. 양산이 나가는 그날 양산이 나가는 날은 회의가 많다. "완전히 테스트했나?" "네, 온도 테스트, 습도 테스트, ESD 테스트, 수명 테스트 다 했습니다." "예상 이슈는?" "진동 테스트에서 한 번 부팅이 느렸는데, 재현이 안 돼서 일단 기록만 해뒀습니다." 그 말을 하는 순간부터 신경 쓴다. "한 번 부팅이 느렸다"는 게 뭔가. 왜 그랬나. 다시 재현되면? 그때는 뭐 할 건가. ECN 절차가 있다. 양산 후 발견된 버그를 고쳐서 나머지 로트에 반영하는 절차. 시간도 걸리고 비용도 든다. 1-2개 로트면 괜찮은데, 5-6개 로트까지 나갔을 땐 점점 비용이 커진다. 가장 최악의 경우는 리콜이다. 밤새는 일상 지난 화요일, 새벽 1시에 보드를 보고 있었다. 오실로스코프에서 SPI 신호가 이상했다. 클록이 들쑥날쑥했다. 펌웨어 버그일까, 하드웨어일까. HW팀 설계자에게 슬랙 메시지를 보냈다. "아직 안 주무셨나요?" 새벽 2시였다. 설계자도 깨어있었다. "어? 너도 봤어?" 그럼 다 같이 본다는 뜻이다. 보드를 들었다 놨다를 반복했다. 회로도를 봤다. 매뉴얼을 봤다. 코드를 봤다. 결론은 저항값 하나였다. Pull-up 저항이 약한 거였다. 나는 펌웨어가 잘못된 줄 알고 2시간을 낭비했다. "이거 저 고치고 다시 보내면 될 거 같은데?" HW팀이 말했다. "알겠습니다." 나는 왜 펌웨어만 의심했을까. 아마도 하드웨어는 기판이 나가면 고칠 수 없다는 생각 때문이었을 것 같다. 펌웨어는 다시 올릴 수 있으니까. 하지만 기판도 다시 나갈 수 있다. 다만 비용이 비싸고, 시간이 오래 걸릴 뿐이다. 돌이킬 수 없는 결정 웹개발자들이 부러운 이유는 뭘까. 아마도 피드백 루프가 빠르다는 것 때문일 것 같다. 내가 코드를 짰다. 1시간 뒤에 사용자가 버그를 발견했다. 1시간 뒤에 나는 버그를 알았다. 1시간 뒤에 내가 고쳤다. 1시간 뒤에 사용자가 고쳐진 버전을 봤다. 4시간 사이클. 그 안에 내가 배웠고, 고쳤고, 다시 배웠다. 우리는 다르다. 내가 코드를 짰다. 2주일을 테스트했다. 배포했다. 3개월 뒤에 버그가 발견됐다. 그 버그는 극단적인 상황에서만 나타난다. 사용자는 그 상황을 만났고, 우리는 그 상황을 못 만들었다. 이제 어떻게 해야 할까. 하드웨어를 회수하고, 펌웨어를 업데이트하고, 다시 배포한다. 3개월이 지난 버전을 수정해야 한다. 그 사이에 나는 다른 프로젝트를 했을 것이다. 코드를 까먹었을 것이다. 그 로직이 왜 그렇게 짜여있었는지 모를 것이다. 그래도 고쳐야 한다. 롤백은 불가능한 말 "혹시 이전 버전으로 롤백할 수 있나?" 그 질문은 펌웨어에서 가능할 수도, 불가능할 수도 있다. OTA(Over The Air) 업데이트를 지원하는 기기라면 가능할 수도 있다. 이전 버전의 바이너리를 기기에 내장시켜놓으면 된다. 하지만 그러려면 플래시 용량이 필요하다. 용량이 크면 가격이 올라간다. 가격이 올라가면 경쟁력이 떨어진다. OTA를 안 지원하는 기기라면 불가능하다. 그 기기를 들고 회사에 와서, 기사가 프로그래머에 연결해서, 플래시를 지우고, 다시 굽는다. 손으로. 유선으로. 사용자가 5000명이라면? 5000번을 해야 한다. 아니면 회수해서 한다. 회수 비용이 더 싸다. 5천만 원 정도. "저희는 롤백 정책이 없습니다. 대신 처음부터 완벽하게 만듭니다." 내 팀장이 지난 입사 면접에서 한 말이다. 완벽하다는 게 뭘까. 테스트? 배포 전에 할 수 있는 모든 테스트를 했다고 해도, 실제 환경에서는 뭐가 나타날지 모른다. 온도가 55도를 넘어가는 환경에서만 문제가 생기는 버그가 있다고 하자. 우리 테스트 챔버는 50도까지만 간다. 누가 이 문제를 찾을까. 사용자다. 밤 10시의 선택 어제도 밤 10시까지 있었다. 내일도 있을 것 같다. 마감이 일주일밖에 안 남았고, 발견된 버그가 3개다. 그 중 하나는 HW 이슈고, 하나는 내가 고쳐야 할 이슈고, 하나는 뭔지 모르는 이슈다. 회의실에서 팀장이 말했다. "정해진 마감까지 모든 버그를 고칠 수 없으면, 가장 중요한 버그만 고쳐서 나가는 것도 옵션입니다." 그 말은 뭔가. 남은 버그는 그냥 양산에 넣는다는 뜻이다. 나중에 ECN으로 고친다는 뜻이다. 아무도 반대하지 않았다. 모두가 알고 있다. 일정이 진짜 타이트하다는 걸. 그리고 완벽함은 환상이라는 걸. 그 순간, 웹개발자 후배가 떠올랐다. "어제 배포 망쳤는데 1분 만에 롤백했어." 1분. 1분이면 내 팀은 한 줄의 코드 리뷰도 못 한다. 모두가 아는 그 불안감 펌웨어 팀의 공통된 불안감이 있다. 그건 배포 바로 다음 날이다. 첫 번째 보고가 오기를 기다린다. 첫 번째 불량이 나오는 건 피할 수 없다. 전체 50만 개 중에 1개가 이상할 확률이 0이 될 수 없다. 하지만 그 1개가 우리 펌웨어 때문인지, 하드웨어 때문인지, 조립 공정 때문인지는 다르다. 만약 우리 펌웨어 때문이라면? 그 말은 50만 개가 모두 같은 문제를 가지고 있을 수도 있다는 뜻이다. 첫 번째 보고가 나오지 않으면, 다음 날을 기다린다. 1주일을 기다린다. 1개월을 기다린다. 기다리는 동안 내 머리는 자동으로 최악의 시나리오를 그린다. "혹시 특정 환경에서만 터지는 버그 있나?" "혹시 메모리 리크 있나?" "혹시 초기화 루틴을 빠뜨린 게 있나?" 밤에는 더 심하다. 근데 왜 계속하는가 질문 자체가 이상하다. 왜냐하면 이미 답을 알고 있으니까. 첫째, 다른 회사 갈 돈이 없다. 전직 시 정착금이 별로 없고, 새 회사에서 연봉 올려줄 가능성도 낮다. 경력 5년이면 중간이다. 주니어도 아니고 시니어도 아니다. 둘째, 이 일에 익숙해졌다. 다른 일을 하려면 또 배워야 한다. SI를 간 후배들 보니 나은 게 별로 없는 것 같았다. 그냥 다른 고통일 뿐이다. 셋째, 그리고 이게 가장 솔직한 이유인데, 나는 이 일이 싫지 않다. 디버깅할 때의 그 쾌감이 있다. 오실로스코프에서 파형을 보고, "아, 이거 타이밍 문제다"라고 깨닫는 그 순간. 아무도 안 돌던 코드가 돌기 시작했을 때의 그 희열. 웹개발자들이 이걸 경험하나? 모르겠다. 아마 비슷한 뭔가가 있을 것 같긴 한데. 다만 내가 만드는 건 서버에서 사라지지 않는다. 하드웨어에 박혀있다. 10년 뒤에도 누군가 쓸 것이다. 내 코드가. 내 버그도. 그게 또 다른 종류의 두려움이면서, 또 다른 종류의 자부심이다. 그래도 롤백은 아니다 웹개발자들을 부러워하는 건 여전하다. 하지만 달라진 게 하나 있다. 이제는 그들의 롤백 가능성이 부럽기보다, 내 돌이킬 수 없음이 당연해 보인다. 펌웨어는 그런 거다. 한 번 나가면 끝이다. 그 대신 뭔가를 얻는다. 책임감. 긴장감. 그리고 가끔씩 오는 성취감. 회사 물건이 고장 안 나고 계속 잘 돌아가는 그걸 보며 느끼는, 말로 설명하기 어려운 안정감. 내일도 9시에 출근한다. 어제 돌린 테스트가 실패했을 것 같다. 또 디버깅을 해야 할 것 같다. 또 밤 10시까지 있을 것 같다. 또 밤에 몰래 코딩을 할 것 같다. 그리고 이 모든 게 반복될 것 같다. 웹개발자는 롤백한다. 나는 그냥 계속 나간다.한 번 나가면 끝. 웹은 롤백. 펌웨어는 앞으로만 간다.

월요일 아침, 어제 밤 테스트가 또 실패했다

월요일 아침, 어제 밤 테스트가 또 실패했다

월요일 아침, 또 실패했다 지난 금요일 오후 5시. 나는 한 가지 결정을 했다. 이번 주말은 펌웨어 테스트에 바치겠다고. 양산 일정이 2주 남았고, 메모리 누수 문제가 자꾸만 나타나고 있었다. 재현율도 일정하지 않아서 더 답답했다. 혹시 특정 시나리오에서만 발생하는 건 아닐까 싶어서, 토요일 밤 10시부터 테스트 케이스를 짜기 시작했다. 테스트 자동화 스크립트를 짜고, 보드에 펌웨어를 올렸다. 그리고 돌렸다. 50번. 100번. 200번. 중간에 서버를 켜서 로그를 쌓기도 했다. 무언가 패턴이 있을 거야. 뭔가 타이밍 이슈가 분명히 있어. 그렇게 스스로를 다독였다.결국 새벽 2시. 내 눈은 이미 초점을 잃은 지 오래였지만, 모니터는 계속 켜져 있었다. 테스트 로그가 한 줄씩 쌓여간다. 잠에서 깨어나다를 반복하면서, 나는 휴대폰 알람을 6시로 맞춰놨다. 지금이라도 테스트 결과를 다시 확인할 수 있으려고. 일요일 아침. 침대에서 일어나자마자 가장 먼저 한 일은 회사 랩탑을 켜는 것이었다. 아내가 있었다면 지금쯤 싸웠을 것 같다. 하지만 나는 혼자였고, 혼자라는 건 아무도 나를 막을 수 없다는 뜻이었다. 테스트 결과 폴더를 열었다. test_result_20250120.txt 손가락이 떨렸다. 마우스를 클릭했다. Test Run 1: PASS Test Run 2: PASS Test Run 3: PASS ... Test Run 127: FAIL - Stack Overflow detected Test Run 128: PASS ... Test Run 200: FAIL - Memory corruption at 0x20019A4C한숨이 나왔다. 스택 오버플로우라니. 내가 이미 다 확인했는데? 재귀 함수도 없는데? malloc도 거의 안 쓰는데? 스택 사이즈는 충분하게 잡아놨는데?일요일 하루 종일 로그를 뒤졌다. 스택 오버플로우 전에 어떤 함수가 호출되었는가. 뭔가 재귀 호출이 숨어 있는 건 아닐까. 콜스택을 추적했다. 라이브러리 함수도 다시 읽어봤다. 혹시 RTOS 태스크 스위칭 중에 문제가 생기는 건 아닐까. 밤 11시. 나는 결론을 내렸다. 이건 펌웨어 이슈가 맞는 것 같은데, 타이밍이 맞아야 재현된다. 아마도 특정 조건—아마도 센서 데이터 처리와 블루투스 통신이 동시에 일어날 때—에서 벌어지는 문제인 것 같다. 그렇다면 월요일 아침, 하드웨어팀에 질문해야 한다.월요일 아침 8시 47분. 회사 자리에 앉자마자 모니터를 켰다. 본메일을 열었다. 토요일 밤 11시에 내가 보낸 이메일이 보였다.제목: 메모리 이슈 관련 질문 - 혹시 하드웨어 문제 아닐까요? 안녕하세요. 펌웨어 테스트 중 다음과 같은 증상을 발견했습니다.약 200번 중 2-3번 스택 오버플로우 발생 재현율이 일정하지 않음 센서 값 읽기와 BLE 전송 중에 주로 발생?혹시 센서 전원 공급 라인이 불안정하거나, 아니면 크리스탈 타이밍이 미세하게 어긋나는 건 아닐까요?제목을 다시 읽으니 한숨이 나왔다. 또 '펌웨어 문제 아니겠죠?' 하는 톤이다. 매번 이런 식이다. 내가 물어보는 것처럼 보이지만, 사실 나는 이미 내 것은 다 확인했다. 이제 남은 건 하드웨어 팀의 회신뿐이다. 카톡이 울렸다. 하드웨어팀 선임: "펌웨어 맞나? 우리 회로는 다 문제없는데..." 당연하다. 매번 이 반복이다. 나 혼자만 자기 것을 의심하고, 상대팀은 절대 자기 것을 의심하지 않는다.나는 다시 펌웨어 코드를 열었다. 이번엔 다르게 접근해보겠다. 스택 오버플로우가 맞다면, 스택 사용량을 동적으로 모니터링하는 코드를 삽입해야겠다. 높이수 마크를 남겨놓고, 런타임에 계속 확인한다면 어디서 스택이 터지는지 알 수 있을 것 같다. 코드를 작성했다. extern unsigned int _estack; extern unsigned int _sstack;uint32_t stack_high_water_mark = 0; uint32_t* stack_top = (uint32_t*)&_sstack;void update_stack_monitor() { uint32_t current_sp; asm volatile ("mov %0, sp" : "=r" (current_sp)); uint32_t used = (uint32_t)stack_top - current_sp; if (used > stack_high_water_mark) { stack_high_water_mark = used; // Log this } }이런 식으로 계속 모니터링하면, 진짜 스택이 얼마나 사용되는지 알 수 있다. 그리고 혹시 내가 놓친 부분이 있다면—혹은 라이브러리에서 몰래 큰 배열을 선언한다면—그걸 잡을 수 있을 것이다. 오전 10시. 회의실에서 양산 미팅이 있었다. 영업팀, 하드웨어팀, 펌웨어팀이 모였다. 일정을 확인했다. 14일 뒤 중국 공장으로 샘플을 보낸다고 했다. 샘플. 그 단어만 들어도 가슴이 철렁했다. 샘플이 괜찮으면 양산이 결정된다. 양산이 결정되면 내 코드는 이제 수정 불가다. 딱 이 상태 그대로 나간다. 나는 손을 들었다. "죄송한데, 아직 메모리 스태빌리티 이슈가 남아있어서요. 최소 1주일이 더 필요할 것 같습니다." 회의실이 조용해졌다. 영업팀 과장이 말했다. "메모리 문제라고? 그게 뭔 문제야?" 하드웨어팀 선임이 말했다. "펌웨어 쪽 문제 아닐까요?" 부장이 나를 봤다. "정확한 원인이 뭐야?" 나는 깊게 숨을 쉬었다. "저... 아직 정확히는 모릅니다. 타이밍 이슈인 것 같은데, 센서 입력과 BLE 통신 사이의 레이스 컨디션일 수도 있고, 아니면 진짜 스택 오버플로우일 수도 있습니다. 자동화 테스트로 재현을 기다리고 있어요. 로그에서 패턴을 찾으면 원인을 특정할 수 있을 것 같습니다." 부장은 고개를 끄덕였다. "좋아, 원인을 빨리 찾아." 회의실을 나왔을 때, 내 머리는 이미 다음 시도를 생각하고 있었다. 혹시 센서 드라이버에서 문제가 있는 건 아닐까. 아니면 타이머 인터럽트의 우선순위 설정이 잘못된 건 아닐까. 혹은 그냥 운이 없었을 거고, 계속 테스트하면 한 번은 성공할 거야. 오후 3시. 다시 보드 위의 LED를 본다. 초록 불이 깜박인다. 실행 중이다. 또 테스트를 돌리고 있다. 200번. 500번. 아마 밤새도록 돌릴 것 같다. 그리고 내일 아침, 월요일과 같은 절망감으로 깨어날 것 같다.결국 오늘도 또 다른 월요일의 시작일 뿐이다.