메아리 저널

theseit 쓰레드와 삽질하기

어제에 이어서 버그 얘기 하나 더. 아악...

어제 아침의 버그는 r334 스냅샷을 올리고 나서 누군가가 테스트하다 발견되었다. 간단하게 정리하면,

<lifthrasiir> 스냅샷 올렸는데 심심하면 확인 좀 해 주셈.
<누군가> 알았음. 야 근데 BMS가 없으니 적선 좀...
<lifthrasiir> http://.... 받아라
<누군가> 뭐 이렇게 용량이 커... 작은 걸로 해 봐야 겠다

쫌 있다가,

<누군가> 방금 받은 초경량 BMS 파일을 선택했는데 창이 사라지네
<lifthrasiir> 헐

윈도 버전에서만 문제가 있는 것 같아서 윈도에서 디버깅했는데, 대강 보니까 어떤 이유로 segfault가 나고 죽어 버리는 건 확실했지만 그 위치를 도저히 종잡을 수 없었다. Code::Blocks에 통합된 디버거를 써 봤더니 죽어 버려서 gdb로 수동으로 해 봤는데 어처구니 없게도 gdb가 특정한 위치에만 가면 정보를 내뱉다가 죽어 버리길래 때려 쳤다.

혹시나 싶어서 맥에서 해 보니까 제대로 실행되지 않는 건 마찬가지였지만, 다행히도 SIGSEGV는 안 나고 exception이 났다. 보아하니 starlight::text_decoding_error인데, 이 에러는 유니코드 문자열로 변환할 때마다 날 수 있는 에러라서 위치를 파악해 보려 했으나 실패했다. 어쩌라고...

옛날 리버싱을 하던 감을 발휘해서 IDA Pro를 써 봤다. 그냥 실행해 보니 segfault가 날 때 실행되던 코드가 winmm.dll 쯤에 있는 것 같은데 스택 프레임이 없어서 어쩌다 이렇게 된 건지 감을 잡을 수 없었다. 그래서 일단 대강 감으로 때려 잡아서 적절한 곳까지 실행하고 ("Run at cursor") 맛이 가면 그 앞쪽 구역을 체크해 보고 맛이 안 가면 뒷쪽을 체크하는 식으로 이분 검색을 해 봤다.

그 결과 에러가 나는 시점은 이미지 등등을 읽어 오는 쓰레드를 생성하는 시점 근방에 있는 것 으로 확인되었다. 그래서 혹시나 쓰레드에서 에러가 나는 건가 싶어서 쓰레드 시작점에 중단점을 걸어 보았는데,

exception이 제대로 떠

문제를 간단하게 요약하면, 쓰레드 안에서 catch가 안 된 exception이 발생했는데 (해당 BMS의 경우 파일 이름이 Shift_JIS라서 깨지는 바람에 로딩에 실패했다.) 그게 스택을 열심히 되돌리다가 쓰레드 시작점을 넘어 삼천포로 빠진 것이다. 세상에!!

결국 예외 처리를 강화하는 것으로 문제는 해결되었다. 이번 사건으로 깨달은 것은,

  1. 쓰레드 쓸 때는 첫째도 조심 둘째도 조심. throw() 같은 걸로 적절히 처리해 줬으면 이런 일은 발생하지 않았을 것이다. (물론 이거 넣는다고 컴파일러가 잡아 주는 건 아니지만 적어도 배째라고 unwind하진 않을 것이다.)
  2. 예외 처리 할 때랑 안 할 때를 구별하자.

이 글은 본래 http://mearie.org/journal/2007/10/playing-with-theseit-thread에 썼던 것을 옮겨 온 것입니다.


(rev 1d46270eb038)