Lasiyan
Code

VSCode에서 GDB 메인함수 진입 전 멈춤

#C++#VSCode#GDB#debuginfod

VSCode에서 C/C++ 디버깅을 시작했는데 빌드까지만 되고 그 뒤로 아무 일도 일어나지 않는 문제를 겪었다.

브레이크포인트는 고사하고 main() 첫 줄도 밟히지 않는다. 원인을 찾는 데 한참 걸렸고, 정작 화면에 보이는 경고 메시지는 원인과 무관한 것이었다. 같은 증상을 다시 만났을 때를 위해 정리해 둔다.

증상

  • F5 또는 명령 팔레트의 CMake: Debug로 디버깅을 시작하면 빌드는 정상적으로 끝난다.
  • 그 뒤로 아무 반응이 없다. 브레이크포인트에 걸리지도 않고, 프로그램 출력도 나오지 않는다.
  • CPU 점유율은 올라가지 않는다. 그냥 조용히 멈춰 있다.
  • 강제로 중단하기 전까지 영원히 대기한다.
  • 터미널 패널에는 아래 경고 하나만 덩그러니 남는다.
&"\342\232\240\357\270\217 warning: GDB: Failed to set controlling terminal: 명령을 허용하지 않음\n"

환경

Ubuntu 26.04 LTS
GDB 17.1 (Ubuntu 17.1-2ubuntu1)
GCC 15.2.0
VSCode + Remote SSH
ms-vscode.cpptools 1.32.2
ms-vscode.cmake-tools 1.23.52

같은 소스를 다른 PC에서 열면 아무 설정 없이 F5로 바로 디버깅이 된다. 그래서 프로젝트 설정 문제라고 생각하기 쉬운데, 실제로는 머신과 네트워크 환경의 문제다.

경고는 원인이 아니다

화면에 유일하게 보이는 메시지가 이것뿐이라 자연스럽게 원인으로 의심하게 된다. 하지만 이건 무해한 경고다. 직접 확인해 보면 알 수 있다.

$ gdb -q -batch -ex "tty $(tty)" -ex run --args ./sample_get 172.20.0.91 80 root root 0
warning: GDB: Failed to set controlling terminal: 명령을 허용하지 않음
...
===== UA_HttpGet [/cgi-bin/fwsysget.cgi?FwModId=0&FwCgiVer=0x0001] (378 bytes) =====
HTTP/1.1 200 OK
Server: GoAhead-http

경고가 뜬 직후에 프로그램 출력이 정상적으로 나온다. 이 경고는 gdb가 디버기를 새 세션 리더로 만든 뒤(setsid) 그 pty를 제어 터미널로 붙이려다(TIOCSCTTY) 실패했다는 뜻이다.

SSH 원격 환경처럼 해당 pty가 이미 다른 세션 소유일 때 발생한다. 영향을 받는 것은 터미널 작업 제어(Ctrl+C 전달 등)뿐이고, 표준 출력이나 브레이크포인트와는 관계가 없다.

앞에 붙은 \342\232\240\357\270\217는 경고 이모지(U+26A0 U+FE0F)의 UTF-8 바이트다. 최신 gdb가 경고 메시지에 스타일을 입히면서 생긴 것이고, 이것도 증상과는 무관하다. 거슬리면 set style enabled off로 없앨 수 있지만 문제 해결과는 상관없다.

진짜 원인: 응답 대기

gdb는 실행을 시작할 때 libc 같은 시스템 공유 라이브러리의 디버그 심볼을 debuginfod 서버에서 자동으로 받아오려고 시도한다. 최근 배포판은 이 기능이 기본으로 켜져 있다.

문제는 네트워크에 따라 이 서버로 TCP 연결(443)은 맺어지는데 응답 데이터가 전혀 오지 않는 경우가 있다는 것이다. 타임아웃도 걸리지 않기 때문에 gdb는 그 자리에서 무한정 대기한다. 디버기는 시작조차 하지 못한 상태이므로 main() 첫 줄도 실행되지 않는다.

실제로 debuginfod를 켠 채로 실행해 보면 timeout 15로 보낸 SIGTERM으로도 죽지 않고 2분 넘게 매달려 있었다.

원인 확인 방법

먼저 환경변수가 설정되어 있는지 본다.

$ echo $DEBUGINFOD_URLS
https://debuginfod.ubuntu.com

값이 비어 있으면 이 문제가 아니다. 값이 있다면 서버가 실제로 응답하는지 확인한다.

$ timeout 5 curl -sI https://debuginfod.ubuntu.com
$ echo $?
124

124는 timeout에 걸렸다는 뜻이다. 즉 응답이 오지 않는다. 정상적인 환경이라면 0이 나온다.

직접 환경변수를 설정한 기억이 없어도 값이 잡히는 경우가 많다. 배포판이 시스템 전역으로 설정해 두기 때문이다.

$ cat /etc/debuginfod/*.urls
https://debuginfod.ubuntu.com

/etc/profile.d/debuginfod.sh가 이 파일을 읽어서 DEBUGINFOD_URLS를 export 한다.

이미 멈춰 있는 gdb가 있다면 프로세스 상태로도 확인할 수 있다.

$ cat /proc/<pid>/wchan          # poll, read 계열이면 의심
$ ls -l /proc/<pid>/fd           # debuginfod_client 캐시 파일이나 443 소켓이 보이면 확정

해결

gdb가 애초에 debuginfod를 쳐다보지 않게 만들면 된다. cpptools가 gdb를 초기화할 때 실행하는 setupCommands 단계에 넣는 것이 확실하다.

launch.json을 쓰는 경우, 해당 디버그 설정에 추가한다.

"setupCommands": [
  { "text": "set debuginfod enabled off", "ignoreFailures": true },
  { "text": "set debuginfod urls \"\"", "ignoreFailures": true }
]

CMake Tools의 CMake: Debug로 디버깅하는 경우에는 launch.json을 읽지 않는다. .vscode/settings.jsoncmake.debugConfig에 같은 내용을 넣어야 한다.

{
  "cmake.debugConfig": {
    "setupCommands": [
      { "text": "set debuginfod enabled off", "ignoreFailures": true },
      { "text": "set debuginfod urls \"\"", "ignoreFailures": true }
    ]
  }
}

적용 후에는 정상적으로 실행이 끝난다.

class[0] = none
class[1] = car
class[2] = bus
class[3] = van
class[4] = others
[Inferior 1 (process 63916) exited normally]

~/.gdbinit에 넣으면 안 되는 이유

~/.gdbinitset debuginfod enabled off를 넣는 방법도 시도해 볼 수 있지만 이것만으로는 부족하다.

cpptools가 초기화 과정에서 debuginfod를 다시 켜기 때문에 .gdbinit의 설정이 덮어써진다.

터미널에서 gdb를 직접 실행할 때는 .gdbinit도 유효하므로, 두 경우를 모두 커버하려면 양쪽에 넣어 두는 편이 낫다.

왜 어떤 PC에서는 잘 되는가

이 문제는 머신과 네트워크에 종속적이다. 다른 PC에서 같은 프로젝트가 문제없이 디버깅되는 이유는 다음 중 하나다.

  • 그 네트워크에서는 debuginfod 서버가 정상적으로 응답한다.
  • 배포판 버전이 낮아 DEBUGINFOD_URLS가 설정되어 있지 않다.

그래서 프로젝트 설정을 아무리 뒤져도 답이 나오지 않는다. 소스를 옮겼더니 갑자기 디버깅이 안 된다면 프로젝트가 아니라 머신 쪽을 먼저 의심하는 편이 빠르다.

체크리스트

같은 증상을 다시 만났을 때 순서대로 확인한다.

  1. main() 첫 줄조차 안 밟히고 CPU도 안 튀면서 멈춰 있는가.
  2. echo $DEBUGINFOD_URLS에 값이 있는가.
  3. timeout 5 curl -sI $DEBUGINFOD_URLS가 124로 끝나는가.
  4. 그렇다면 setupCommands에 debuginfod 비활성화 두 줄을 추가한다.
  5. CMake Tools를 쓴다면 launch.json이 아니라 settings.jsoncmake.debugConfig에 넣는다.

화면에 보이는 Failed to set controlling terminal 경고는 무시해도 된다. 그것 때문에 멈추는 것이 아니다.

댓글