Remote Debug via GDB/gdbserver Of CLion With Docker

이전 포스팅에서 이어지는 내용이다. 전체 프로젝트는 여기에서 받을 수 있다. 이번에는 리모트 디버그에 대해서 정리할 것이다. 공식 문서에도 소개가 잘 되어 있지만, 이 문서도 보고 바로 따라하기에는 좀 어렵다.

여기서 정리할 내용이다.

  • Docker Compose 설정
    • GDB 설정
  • Remote Debug via GDB/gdbserver
    • Use Remote GDB Server
    • Use GDB Remote Debug
      • GDB Server로 Target Process를 실행
      • Target Process를 실행 후에 GDB Server를 해당 PID로 Attach

Dockerfile, docker-compose.yml 그리고 CMake의 build type, compile debug option, release option과 같은 세부 사항이나 GDB Server의 옵션 등과 기타 디버그 설정 등도 여기서 모두 설명하면 너무 길어지므로 생략하고, 리모트 디버그 연동만 정리하겠다.

이전 포스팅에서 Debugger 설정을 다음과 같은 상태로 두고 넘어갔었다.

스크린샷 2020-05-01 오후 2.07.39

이제 리모트 디버그를 위해서 도커에 GDB, GDB Server를 설치하고 CLion에도 Debugger 설정을 할 것이다. 이전 포스팅에서 사용한 프로젝트에 이어서 진행하겠다.

Docker Compose 설정

ssh 설정에 이전 포스팅에서 사용한 포트와 같은 포트를 사용할 것이므로 이전 포스팅에서 실행했던 컨테이너를 다음과 같이 삭제해야 한다.

스크린샷 2020-05-01 오후 6.36.12

Dockerfile 수정

다음과 같이 ‘Dockerfile’을 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM centos:7
RUN useradd dev
RUN echo 'dev:mypass' | chpasswd
RUN yum install -y openssh-server rsync
RUN ssh-keygen -N '' -t rsa -f /etc/ssh/ssh_host_rsa_key && \
    ssh-keygen -N '' -t ecdsa -f /etc/ssh/ssh_host_ecdsa_key && \
    ssh-keygen -N '' -t ed25519 -f /etc/ssh/ssh_host_ed25519_key
RUN echo /usr/sbin/sshd >> /root/.bashrc && source /root/.bashrc
RUN sed -i 's/GSSAPIAuthentication yes/GSSAPIAuthentication no/' /etc/ssh/sshd_config
RUN yum install -y gcc gcc-c++ make
RUN yum install -y epel-release
RUN yum install -y cmake3
## for remote debug
RUN yum install -y gdb-gdbserver centos-release-scl-rh
RUN yum --enablerepo=centos-sclo-rh install -y devtoolset-7-gdb
EXPOSE 22 22244
CMD ["/bin/bash"]

‘# for remote debug’ 아래 2줄을 새로 추가하고 GDB Server가 사용할 port도 expose에 추가했다. yum에 centos-release-scl-rh repository를 추가한 이유는 이전 포스팅(url)에서 cmake3를 설치하기 위해서 epel-release를 추가했던 이유와 같다. CentOS 7에 기본 설정된 yum의 repository에는 gdb 7.6.1이 이 글을 쓰는 현재 가장 높은 버전이고, CLion이 지원하는 최소 버전은 7.8.x 이상이다. repository 추가 없이 기본 gdb를 설치하면 다음과 같은 경고를 볼 수 있다.

스크린샷 2020-04-30 오후 3.58.57

docker-compose.yml 추가

다음과 같이 ‘docker-compose.yml’ 파일을 ‘Dockerfile’과 같은 위치에 추가한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
version: '3'
services:
  centos_7:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: centos_7
    image: centos_7
    ports:
      - "22222:22"    # ssh port
      - "22244:22244" # gdbserver port
    stdin_open: true
    tty: true
    # for remote debugging
    security_opt:
      - seccomp:unconfined
    cap_add:
      - SYS_PTRACE

파일이 준비되었으면, 아래와 같이 ‘Run/Debug Configurations’에서 이전에 설정했던 Docker/CentOS_7 설정을 삭제한다.

스크린샷 2020-05-01 오후 5.28.59

그리고, 아래와 같이 Docker-compose 설정을 추가한다.

스크린샷 2020-05-02 오후 9.03.30

스크린샷 2020-05-01 오후 5.33.25

위에서 —build 옵션을 선택하지 않으면, ‘Dockerfile’ 또는 ‘Docker-compose.yml’ 파일을 수정하고 도커 이미지를 새로 만들 때마다 기존의 도커 이미지를 삭제해야 하는 불편함이 있다.

스크린샷 2020-05-01 오후 6.55.49

설정을 다 하고 ‘OK’를 클릭하면 위와 같이 지금 설정한 ‘CentOS_7’로 ‘Run/Debug Configurations’이 자동으로 선택되어 있을 것이다. ‘Run’을 눌러서 실행하면 Docker image를 빌드하고 container 실행까지 시켜준다. 이미 저번보다 시간이 짧게 걸릴 것이다.

GDB 설정

Toolchains 설정을 열어서 Debugger 설정을 아래와 같이 수정한다.

1
/opt/rh/devtoolset-7/root/usr/bin/gdb

스크린샷 2020-05-01 오후 1.42.12

그러면, gdb 버전이 8로 인식된다. 참고로, CLion의 버전을 2020.1로 올렸더니 Toolchains 설정의 Credentials가 콤보 박스로 바뀌었고, 상세 설정에는 그동안 설정했던 Credentials 설정이 리스트 업되는 기능이 추가되었다.

CMake 설정이 ‘Remote Host’로 되어있는지 확인한다.

스크린샷 2020-05-01 오후 1.46.42

여기까지 설정하면, 공식 문서에서 ‘Remote GDB Server’라고 명명한 리모트 디버그 기능을 사용할 수 있다.

Remote Debug via GDB/gdbserver

Use Remote GDB Server

스크린샷 2020-05-02 오후 1.32.03

CLion에서 프로젝트를 바로 실행할 수 있을 때는 추가로 설정할 것이 별로 없다. 위와 같이 Breakpoint를 설정하고 디버그 모드로 실행하면, 아래와 같이 Breakpoint가 잡히는 것을 볼 수 있다.

스크린샷 2020-05-02 오후 1.38.50

아무런 추가 설정 없이도 기본적인 리모트 디버그를 사용할 수 있다. GDB Server에 추가 옵션을 사용해야 한다면, 아래와 같이 ‘Run/Debug Configurations’에 ‘Remote GDB Server’ 설정을 추가한다.

스크린샷 2020-05-04 오후 5.22.46

그리고, 아래와 같이 Credentials를 위에서 설정한 ‘Remote Host’로 설정하고, Executable도 현재 프로젝트로 선택한다.

스크린샷 2020-05-04 오후 5.25.34

설정을 다 하고 ‘OK’를 클릭하면 위와 같이 지금 설정한 ‘example | Debug-Remote Host’로 ‘Run/Debug Configurations’이 자동으로 선택되어 있고, ‘Debug’만 활성화되어 있을 것이다. 디버그 모드로 실행하면 프로젝트가 조금 전과 동일하게 동작한다.

Use GDB Remote Debug

Shared Library이거나 실행 조건에 외부 종속이 많아서 복잡한 경우 등 몇 가지 이유로 CLion에서 프로젝트를 직접 실행할 수 없다면, 추가 작업이 필요하다. Run/Debug Configurations에 GDB Remote Debug 설정을 하기 위해서 몇 가지 정보가 필요하다.

  1. 현재 설정한 Remote Host의 Credentials의 전체 이름
  2. 현재 작업 디렉토리와 맵핑되는 리모트의 패스
  3. 현재 작업 패스

현재 설정한 Remote Host의 Credentials의 전체 이름

설정 창을 열고 위에서 설정한 Toolchains의 Credentials의 전체 이름을 어딘가에 메모해둔다.

현재 작업 디렉토리와 맵핑되는 리모트의 패스

아래와 같이 설정 창에서 ‘sftp’로 검색을 하여 메뉴를 줄인다. 그리고, Deployment 설정에서 앞 단계에서 메모해둔 Toolchains의 Credentials의 전체 이름의 Mappings 탭을 선택하여 기본 설정된 Deployment path를 명시적으로 ‘/tmp/example’로 설정한다. 여기서 명시적으로 패스를 지정하는 것은 GDB Server를 실행할 스크립트 구성을 단순하게 하기 위함이다.

스크린샷 2020-05-02 오후 7.40.29

현재 작업 패스

아래와 같이 절대 경로를 선택하면 절대 경로가 클립 보드에 복사된다. 복사한 전체 경로를 어딘가에 메모해둔다.

스크린샷 2020-05-02 오후 4.07.17

이제 필요한 정보를 모두 확인했다. 아래와 같이 'Run/Debug Configurations'에 GDB Remote Debug 설정을 추가한다.

스크린샷 2020-05-02 오후 9.02.47

아래와 같이 Path mappings에 위에서 확인한 현재 작업 디렉토리와 맵핑되는 리모트 패스와 현재 작업 패스를 설정한다.

스크린샷 2020-05-02 오후 7.47.37

위에서 Deployment path를 변경했으므로, 아래와 같이 CMake Project를 Reload 해야 한다.

스크린샷 2020-05-02 오후 7.51.54

이제 공통 설정은 모두 마쳤다.

여기서 두 가지로 갈라지는데, GDB Server로 프로세스를 직접 실행하는 방법과 이미 실행된 프로세스를 GDB Server에서 PID로 Attach 하는 방법이 있다. 두 방법 모두 CLion에서 연결하는 방법은 동일하다.

GDB Server로 Target Process를 실행

CLion에서 디버그 모드로 실행하는 것보다는 번거롭지만 준비할 것은 별로 없다.

스크립트 준비

디버그를 하려고 할 때마다 도커 컨테이너에 들어가서 gdbserver를 실행하는 것은 매우 번거롭다. 아래의 스크립트를 현재 프로젝트에 ‘gdbserver_exec_process.sh’ 파일로 추가한다.

1
2
#!/usr/bin/env bash
docker exec centos_7 /bin/bash -c "gdbserver :22244 /tmp/example/cmake-build-debug-remote-host/example"

디버그 실행

준비한 스크립트 파일을 ‘Run/Debug Configurations’ 설정에 Shell Script 설정으로 추가해서 사용해도 되지만, 추가로 사용할 옵션이 없으므로 간단히 직접 실행한다. 아래와 같이 실행할 수 있다.

스크린샷 2020-05-05 오후 6.53.52

Run ‘gdbserver_exec_process.sh’을 선택하면, 아래와 같이 터미널에 스크립트가 실행되어 연결을 기다리는 것을 볼 수 있다.

스크린샷 2020-05-05 오후 10.30.33

‘main.cpp’에 Breakpoint를 설정하고, 다음과 같이 ‘Run/Debug Configurations’에 위에서 설정했던 ‘remote debugging’을 선택한다.

스크린샷 2020-05-02 오후 9.10.28

디버그를 실행하면, 아래와 같이 Brakpoint가 잡히는 것을 볼 수 있다.

스크린샷 2020-05-05 오후 6.53.00

GDB Server로 디버그할 프로세스를 직접 실행하여 CLion과 연동할 수 있는 것을 확인했다. 아마도 이 기능을 사용할 일은 별로 없을 것이다. GDB Server로 디버그할 프로세스를 직접 실행할 수 있다는 것은 CLion에서 디버그 모드로 직접 실행할 수도 있다는 것이다. 그렇다면, 굳이 이렇게 번거롭게 할 필요 없이, CLion에서 바로 디버그 모드로 들어가는 것이 가장 편리하다.

Target Process를 실행 후에 GDB Server를 해당 PID로 Attach

Shared Library등 CLion에서 직접 실행하기 어려운 프로젝트를 개발할 때 PID로 Attach 하는 방법이 필요하다. 실무에서 CLion에서 직접 디버그 모드로 실행할 수 있는 프로젝트가 그리 많지 않을 것이기에 이 기능이 가장 유용할 것이다. 하지만, 준비할 것이 많다.

스크립트 준비

스크립트를 3개 더 준비해야 한다. GDB Server가 디버그할 프로세스의 PID로 Attach 해야 하는데 매번 ‘ps -ef’ 로 PID를 찾아서 실행할 수는 없다. 아래의 스크립트 3개를 현재 프로젝트에 ‘run_project.sh’ 파일과 ‘_attach_process.sh’ 파일 그리고 ‘gdbserver_attach_process.sh’ 파일을 각각 추가한다.

1
2
#!/usr/bin/env bash
docker exec centos_7 /bin/bash -c "/tmp/example/cmake-build-debug-remote-host/example"
1
2
#!/usr/bin/env bash
ps -ef | grep example | grep -v grep | awk '{print $2}' | { read pid; gdbserver --attach :22244 $pid; }
1
2
#!/usr/bin/env bash
docker exec -it centos_7 /bin/bash /home/dev/_attach_process.sh

Dockerfile 수정

아래와 같이 Dockerfile에 COPY 명령과 RUN 명령을 추가한다.

1
2
3
4
5
6
…
## for remote debugging
…
COPY _attach_process.sh /home/dev/
RUN chmod +x /home/dev/_attach_process.sh
…

‘Dockerfile’을 수정했으면, ‘Run/Debug Configurations’을 ‘CentOS_7’로 선택하고 실행하여 도커 이미지를 다시 만든다. 이 과정에서 컨테이너도 새로 만들어지므로 CMake Project를 Reload하고, 프로젝트를 다시 빌드해야 한다. Reload 방법과 빌드 방법은 위를 참조한다.

main.cpp 수정

지금까지 사용한 단순한 예제로는 프로세스를 실행 중인 상태로 둘 수가 없기 때문에 억지로 실행 중인 상태로 만들겠다. 다음과 같이 수정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <sys/epoll.h>
#include <unistd.h>
#include <iostream>
int main()
{
	int epollFd = epoll_create1(0);
	if (epollFd == -1)
	{
		std::cout << "Error: failed to open an epoll file descriptor" << std::endl;
		return EXIT_FAILURE;
	}
	bool exit = false;
	while (!exit)
	{
		std::cout << "waiting..." << std::endl;
		sleep(1);  // Set breakpoint here!
	}
	return close(epollFd);
}

CLion이 경고를 ‘Condition is always true’ 경고를 표시하고 ‘return close(epollFd);’이 실행되지 않을 것이라고 표시할 텐데 일단 무시한다. 왜 이런 예제를 구성했는지 잠시 후면 알 수 있다.

디버그 실행

준비한 스크립트 중에서 직접 실행하는 파일은 ‘gdbserver_attach_process.sh’ 이고, 위에서와 같은 방식으로 실행할 수 있다. 다만, 스크립트를 실행하기 전에 프로세스가 실행 중이어야만 하므로 위에서 준비한 ‘run_project.sh’를 먼저 실행하면 아래와 같이 터미널이 열리면서 프로젝트가 실행되는 것을 볼 수 있다.

스크린샷 2020-05-03 오후 3.37.02

이제 위에서 준비한 ‘gdbserver_attach_process.sh’를 실행하면 아래와 같이 터미널이 추가로 열리면서 GDB Server가 실행되는 것을 볼 수 있다.

스크린샷 2020-05-03 오후 3.47.53

‘main.cpp’에서 ‘// Set breakpoint here!’ 라인에 Breakpoint를 설정하고, ‘remote debugging’를 실행하면, 아래와 같이 Breakpoint가 잡힌다.

스크린샷 2020-05-02 오후 9.10.28

스크린샷 2020-05-05 오후 7.11.58

아래와 같이 Set Value’ 기능으로 exit 변수의 값을 ‘true’로 수정하고, Step Over를 몇 번 하면 무한 루프를 벗어나고 프로세스가 종료된다.

스크린샷 2020-05-05 오후 7.13.30

디버그 모드가 종료되고 나서 방금 열렸던 터미널들을 확인해보면 모두 종료된 것을 볼 수 있다.

이로써 리모트 디버그 연동을 종류별로 하나씩 정리했다. 실무에서는 여기서 언급하지 않은 각종 디버그 관련 설정들과 CMake 설정들은 조정해야할 것이다. 여기서는 기본적인 리모트 디버그 구성을 위한 최소한의 설정만을 사용했다.

(원글: https://prostars.net/314)


Popit은 페이스북 댓글만 사용하고 있습니다. 페이스북 로그인 후 글을 보시면 댓글이 나타납니다.