[OpenCV] cv2.fillPoly 도중 Segmentation fault + gdb 사용법
Introduction
얼마 전 모델 학습을 돌리다가 시작 후 3~4일쯤 지나서
Segmentation fault (core dumped)
라는 메시지와 함께 아무 에러 트레이스백 없이 프로세스가 종료되는 현상이 반복적으로 발생했다.
따로 디버거를 활성화 시켜 놓고 돌리지도 않았고, 무엇보다도 학습 시작 후 한참 이후에 아무 정보 없이 프로세스가 종료되니 디버그를 도저히 진행할 수가 없었다.
그런데 어느날, 학습 시작 후 몇 분만에 에러가 재현되는 학습 세팅을 발견해 비로소 디버그 작업에 착수할 수 있었다.
빠르게 제목의 상황의 해결법을 보고 싶다면 우측 목차를 통해 문제 해결 단락으로 넘어가자!
삽질의 과정
파이썬 스크립트 실행 도중 Segmentation fault 에러가 발생할 때 파이썬 디버거 (pdb) 를 활성화 시켜 놓는 것은 크게 도움이 되지 않았다. 일반적인 파이썬 에러 레이즈로 인한 시스템 종료는 캐치하고 바로 디버그 모드로 진입할 수 있지만, 이건 리눅스 자체의 메모리 에러로 인해 발생되는 프로세스 종료이기 때문에 에러 발생 -> SystemExit의 과정을 거치지 않고 냅다 프로세스가 종료되어 버리고, 따라서 디버거를 통해 구체적으로 어떤 코드라인이 에러를 발생시키는 지 파악할 수 없었다.
그렇다고 거대한 코드를 한줄한줄 실행시키거나 매 지점마다 로그를 출력하게 하는 것도 그렇게 좋은 방법은 아니었다. 그러던 중 회사 동료 분이 gdb를 사용하면 에러 발생 지점을 파악할 수 있다고 추천해 주셔서 gdb를 시도해보게 되었다.
사실 gdb 자체에 대해서 그렇게 깊이 파 보진 않았고, 문제 해결에만 집중했기 때문에 (gdb를 사용하는 중에도 문제가 있었다...) 이 글에서는 어떤 과정을 통해 문제를 해결했는지만 소개한다.
gdb
$ mkdir -p ~/.config/gdb
$ cd ~/.config/gdb
$ wget https://hg.python.org/cpython/rawfile/3.6/Tools/gdb/libpython.py
$ sudo apt-get install python3.8-dbg
위 코드를 통해 libpython과 python3.8-dbg를 설치해 준다. 하나는 3.6이고 하나는 3.8이라 현재 파이썬 버전에 맞추어 버전을 수정하고 싶겠지만, 찾아본 결과 둘 다 저게 최신 버전이다.
$ vim ~/.gdbinit
gdbinit 파일을 생성하고, 아래와 같이 내용을 작성해 준다 (vim 불편하다...).
python
import gdb
import sys
import os
sys.path.insert(0, os.path.expanduser("~/.config/gdb"))
def setup_python(event):
import libpython
gdb.events.new_objfile.connect(setup_python)
end
이렇게 작성한 후 :wq를 통해 저장 후 나가자.
$ gdb -ex r --args {script}.py {args}
그리고 문제가 발생하는 파이썬 스크립트를 위와 같은 형태의 커맨드로 실행해 주자.
코드 실행 중 Segmentation fault 에러가 발생하면 실행을 멈추고 인터랙션 모드로 진입한다.
여기서 info auto-load 커맨드를 실행해서 출력되는 결과에 python3.8-gdb.py 가 포함되어 있지 않다면 아래와 명령어를 통해 수동으로 해당 파일의 디렉토리를 추가해 주자.
(pdb) source /usr/share/gdb/auto-load/usr/bin/python3.8-gdb.py
gdb가 저 파일을 찾을 수 있어야 파이썬 관련 커맨드를 날려 디버그를 진행할 수 있는데, 본인 컴에서는 저걸 자꾸 못 찾는 문제가 있었다. 그런데 다른 사람들의 해결 과정을 보면 알아서 locate 되는 경우가 대다수인 것 같다. 아무튼 본인은 이 부분에서 해결 방법을 찾는다고 엄청 시간을 날렸다.
만약 python3.8-gdb.py 이게 위에 적어둔 경로가 아닌 다른 디렉토리에 있는 걸 확인하면 그 경로를 대신 적어주자. 뭔가 gdbinit 파일을 잘 수정하면 이렇게 수동으로 안 해도 될것 같은데, 본인은 거기까지 가 보진 못 했다.
아무튼 이제 각종 파이썬 관련 gdb 명령어를 통해 디버그를 진행할 수 있다. 명령어들은 여기를 참고하면 좋다.
(pdb) py-list
위 명령어를 입력하면 파이썬 에러 트레이스백을 출력해 어느 코드라인에서 Segmentation fault가 발생했는 지 알 수 있다.
그런데 여기서 로컬 변수를 출력하거나 조사식을 입력하는 건 또 다른 차원의 문제인데, 이와 관련해서는 모아둔 참고자료들을 적어둘 테니 그쪽을 참고하면 좋겠다. 리스트 정도는 어렵지 않게 열어볼 수 있고, numpy array를 열어보려면 조금 쉽지 않은 것 같다...
문제 발생 지점
뭐.. 제목에서도 알 수 있듯이 에러 발생은 cv2.fillPoly 에서 발생했었다. 그런데 들어가는 인자가 numpy array이다 보니 또 직접적으로 어떤 value가 들어가서 에러를 발생시키는 건지 확인하기가 어려웠다.
여기서 뭔가 array shape가 문제일 것 같다는 쎄함을 느끼고 어레이 shape 출력 라인을 함수 호출 직전에 추가해 다시 돌려보니.. [N, 0, 2] shape의 numpy array가 피딩되고 있음을 확인했다.
한번 토이 샘플을 만들어 재연을 시도해 본 결과...
>>> a
array([], shape=(1, 0, 2), dtype=float64)
>>> cv2.fillPoly(mask, a.astype(np.int32), 1)
Segmentation fault (core dumped)
....
컨투어 데이터에서 포인트 갯수 axis 쪽이 0 사이즈로 들어가면 Segmentation fault가 발생하는 거였다. 사실 비었으면 비어있는 대로 안 그리고 넘어갈 거라 안일하게 생각했는데 이런 곳에서 치명적인 에러를 발생시키고 있었다.
문제 해결
어차피 그릴 컨투어 포인트가 없으니 간단한 분기를 통해 포인트 갯수가 유효할 때만 polygon을 그리도록 처리를 해 주자. 오랜 삽질 과정에 비해 너무나 간단하게 해결이 되었다.
for polygon in polygons:
if len(polygon) > 0:
mask = cv2.fillPoly(mask, [polygon], 255)
대충 적은거라 안 돌아가거나 문법이 틀릴 수도 있다.
사실 Segmentation fault 가 발생하면 일반적인 파이썬 디버깅을 통해서는 아예 에러 발생 지점을 찾을 수도 없기 때문에, gdb를 이용해 문제를 파악하고 해결한 것이 그렇게 삽질이라곤 할 수 없다.
하지만 이렇게 저 에러가 발생 가능한 상황을 하나 배워갔으니, 다음부터는 좀더 빠르게 문제의 원인을 짚어낼 수 있으면 좋겠다. 그리고 cv2 함수를 사용할 때에는 굉장히 방어적이어야 하는 것 같다.
아래는 참고할 만한 자료들이다.
References
gdb에서 numpy array 다루기
https://blog.semicolonsoftware.de/debugging-numerical-c-c-fortran-code-with-gdb-and-numpy/
gdb에서 numpy array 다루기 2
https://www.codeproject.com/Articles/669606/Analyzing-C-Cplusplus-matrix-in-the-gdb-debugger-w
gdb 사용법 관련 참고 블로그 글
http://egloos.zum.com/mcchae/v/11230168
gdb 파이썬 관련 디버그 명령어들
https://confluence.desy.de/pages/viewpage.action?pageId=135772481
DebuggingWithGdb - Python Wiki
https://wiki.python.org/moin/DebuggingWithGdb