Dev

gdbserver ptrace issue on Apple Silicon M1/M2

prostars 2023. 6. 6. 14:40

Docker's Multi-architecture support

애플 실리콘 칩을 기반으로 실행되는 macOS를 위한 'Docker Desktop For Mac'은 애플 실리콘 칩셋(이하 AArch64)에서 AMD64로 빌드된 이미지의 컨테이너 실행을 위해 Qemu 가상화를 사용한다.

'The Magic Behind the Scenes of Docker Desktop'와 4.1.3.0 Release note에서 관련 내용을 확인할 수 있다.

 

이 글은

이전 포스팅 'Full Remote Mode Of CLion With Docker'(이하 이전 포스팅 1번)와 'Remote Debug via GDB/gdbserver Of CLion With Docker'(이하 이전 포스팅 2번)에서 공유한 내용을 AArch64 환경에서 AMD64 바이너리를 디버깅할 때 사용하면 발생하는 이슈와 대응 방법을 공유한다.

 

예제 준비

이전 포스팅 2번에서 사용한 프로젝트를 기반으로 진행한다. 깃 프로젝트를 클론하고 CLion에서 'CMakeLists.txt'를 프로젝트 형식으로 연다. 참고로, 이 글에서 수정된 내용이 적용된 프로젝트는 여기에 있다.

 

docker-compose.yml 파일에 'platform' 속성을 'linux/amd64'로 아래와 같이 추가한다.

version: '3'

services:
  centos_7:
    platform: linux/amd64
    ...

Dockerfile 파일에도 아래와 같이 ssh 로그인 시에 지연을 줄이기 위해서 RUN 명령 하나를 추가한다.

...
RUN chmod +x /home/dev/_attach_process.sh

## for reducing SSH login delay
RUN echo 'UseDNS no' >> /etc/ssh/sshd_config

EXPOSE 22 22244
...

그리고, 이전 포스팅 1번에서 설정한 'CentOS_7' Configuration을 실행한다. 빌드된 도커 이미지를 'Docker Desktop Dashboard'에서 확인해 보면, 'AMD64'로 빌드된 이미지라는 것을 알 수 있다.

위의 이미지로 실행된 컨테이너와 CLion을 연동하는 'Toolchains'와 'CMake' 설정은 이전 포스팅 1번 내용을 참고하여 진행한다.

 

문제 발생

CLion 설정이 끝난 상태에서 아래와 같이 'example_for_remote_debug_of_clion_with_docker'을 실행한다.

그리고, 실행된 프로세스를 컨테이너에서 확인해 보면 예제가 아래와 같이 Qemu 가상화 위에서 실행된 것을 확인할 수 있다.

매우 편리하게 어떠한 추가 설정도 없이 AArch64 환경에서 AMD64로 빌드된 바이너리를 실행할 수 있다. 성능 저하를 제외하고 일반적인 사용성에 문제는 없다. 문제는 디버깅할 때 발생한다. 이제 'main.cpp' 9번째 줄에 브레이크 포인트를 설정하고, 아래와 같이 디버깅을 실행한다.

이전 포스팅 2번에서 성공했던 디버깅 실행이 이번에는 아래와 같이 실패하는 것을 확인할 수 있다.

에러 메시지가 출력되지 않아 어떤 이유인지 아직은 알 수 없다.

 

문제 확인

이전 포스팅 2번에서 gdbserver를 사용하는 'Use Remote GDB Server' 내용으로 설정을 추가하고, 아래와 리모트 디버깅을 실행한다.

이번에는 아래와 같이 에러 메시지를 출력하면서 gdbserver의 실행이 실패한다.

ptrace 시스템 콜 함수가 미구현되어 있다는 에러다. ptrace는 프로세스를 디버깅할 때 필요한 시스템 콜 함수다.

아마도, qemu-x86_64가 ptrace까지 구현하지는 않은 것 같다. 2020년에 이미 이슈 업되었었고, 아직 수정은 되지 않은 것 같다.

도커에서 Qemu를 사용한 가상화에서 gdbserver를 사용할 수 없다는 것을 확인했으니, 다른 방법을 시도해 본다.
'Docker Desktop For Mac' 최근 버전에는 'Beta features'로 아래와 같이 'Use Rosetta for x86/amd64 emulation on Apple Silicon' 설정이 있다.

이 설정을 위와 같이 켜고 다시 프로세스를 확인해 보면 아래와 같이 달라지는 것을 확인할 수 있다.

이제 도커는 Qemu 가상화 대신에 Rosetta 가상화를 사용한다. 이 상태에서 다시 디버깅을 실행한다.

ptrace 미구현 에러가 다른 에러로 바뀌었을 뿐, 디버깅 실행에 실패한다. 이번에는 gdbserver를 사용하는 리모트 디버깅을 실행한다.

마찬가지로 리모트 디버깅 실행도 실패한다. PTRACE_PEEKUSER 에러가 gdbserver 예전 버전에 존재했었고 수정되었다는 내용이 있기에 10.2 버전으로 올려서 테스트해 보았으나, 아래와 같이 PTRACE_PEEKUSER 에러가 PTRACE_GETREGS 에러로 바뀌었을 뿐 여전히 실패한다.

그리고, gdbserver는 실행에 실패하고 종료되었지만, example_for_remote_debug_of_clion_with_docker 프로세스는 실행되고 있으니 직접 kill 해야 하지만 이 컨테이너는 종료할 것이므로 무시해도 괜찮다. 아래와 같이 도커 컴포즈를 종료한다.

 

해결 방안 검토

ptrace 이슈로 컨테이너 가상화에서는 아직 gdbserver 실행 문제를 해결할 방법이 없으니, 하이퍼바이저(Hypervisor) 가상화를 사용하는 것으로 방향을 바꾼다. 컨테이너가 일상화된 후로 사용할 일이 없었는데 참 오랜만에 사용해 본다.

 

AArch64 Host OS에서 AMD64 Guest OS를 지원하는 타입 2 가상 머신 소프트웨어로 3개를 테스트했다.

  1. VirtualBox
  2. UTM
  3. Lima

테스트 결과 VirtualBox는 AMD64 우분투의 인스톨러조차 제대로 실행하지 못했기에 바로 제외했다. UTM은 우분투를 잘 설치하고 부팅까지 잘된다. 테스트 수준에서 안정성도 성능이 떨어질 뿐 별다른 문제가 없다. 하지만, Host OS가 VPN 네트워크를 사용하고 있을 때, Guest OS가 Host OS의 VPN 망에 접근하지 못하는 이슈가 있다. 이 이슈는 여기에 안내된 것처럼 가상 머신의 네트워크 설정을 'Emulated VLAN'으로 하면 해결이 되지만, 이렇게 설정하면 Host OS가 Guest OS로 연결을 할 수 없다. 하지만, 이 예제에서는 해당 사항이 없는 이슈다.

Lima도  AMD64 우분투를 잘 설치하고 부팅까지 잘 되며, UTM과 달리 별도의 네트워크 설정 없이 Host OS의 VPN 망에 연결할 수 있다. 하지만, 수분 단위로 Guest OS의 CPU가 100%를 치면서 Guest OS가 응답하지 않는다.

 

위와 같은 테스트 결과로 UTM을 사용하여 ptrace 이슈를 해결하는 것으로 한다. 참고로, VPN 이슈는 UTM의 가상 머신에 네트워크 인터페이스를 추가하면 해결할 수 있다. 이 VPN 이슈에 대한 내용은 다음 포스팅에서 정리할 예정이다.

 

컨테이너 대신 하이퍼바이저

UTM 설치는 여기를 참고바란다. 이 글에서는 Guest OS는 'Ubuntu'로 사용자 ID는 'guest', Password는 'myguest'로 설정되고 ssh 로그인까지 잘된다는 전제하에 계속 진행한다. 설치된 Guest OS의 IP는 '192.168.64.4'로 할당되었다.

 

여기서 Guest OS는 도커 컴포즈를 실행하기 위한 목적으로만 사용할 것이다. 예제로 사용하던 컨테이너를 Guest OS에서 그대로 사용할 것이므로 아래와 같이 GuestOS에 접속해서 Docker와 Docker-Compose를 설치하고, 'guest' 계정을 'docker' 그룹에 추가한다.

$ ssh guest@192.168.64.4
guest@guestos:~$ sudo apt update
guest@guestos:~$ sudo apt install docker.io
guest@guestos:~$ sudo apt install docker-compose
guest@guestos:~$ sudo usermod -aG docker guest
guest@guestos:~$ exit

 

예제 디렉토리로 이동한 후에 아래와 같이 파일 3개를 Guest OS로 복사한다.

$ scp Dockerfile docker-compose.yml _attach_process.sh guest@192.168.64.4:

이제 Guest OS에서 예제의 도커 컴포즈를 실행한다.

$ ssh guest@192.168.64.4 "docker-compose up -d"
Warning: Permanently added '192.168.64.4' (ED25519) to the list of known hosts.
guest@192.168.64.4's password:
Starting centos_7 ...
Starting centos_7 ... done

이제 이 글에서 Guest OS를 직접 접속할 일은 없다. Guest OS에서 실행 중인 컨테이너를 사용한다.

 

문제 해결 확인

이전 포스팅 1번에서 설정했던 'Toolchains' 설정을 열어보면 아래와 같이 설정이 깨져있는 것을 확인할 수 있다. 위에서 도커 컴포즈를 내리면서 현재 'Remote Host' 설정이 바라보던 컨테이너가 사라졌기 때문이다.

'Toolchains'의 SSH 연결 Credentials 설정에서 Host 설정을 'localhost'에서 아래와 같이 Guest OS의 IP로 수정한다. 'Test Connection'를 클릭해 보면 성공할 것이다.

이제 'OK'를 클릭해서 'SSH Configurations' 창을 닫고 나오면 아래와 같이 'Toolchains' 설정이 이전과 같이 모두 정상으로 나올 것이다.

다시 'OK'를 클릭해서 설정 창을 닫으면, 아래와 같이 CMake Project가 Reload 될 것이다. 자동으로 Reload 되지 않았다면 'Reload'를 클릭한다.

이제 이 예제 프로젝트는 Guest OS에서 실행 중인 컨테이너와 연동되었다. ptrace 이슈로 실패했던 디버깅을 실행해 보자.

위와 같이 잘 동작하는 것을 확인할 수 있다. 

 

gdbserver 실행 방식 동작 확인

명시적인 gdbserver 설정을 사용하는 'Remote GDB Server' 디버깅을 실행하기 전에, 아래와 같이 'Remote GDB Server' 설정에서 'Credentials'와 'target remote'의 값을 아래와 같이 Guest OS의 IP로 설정하고, 포트는 컨테이너에서 gdbserver 용으로 노출한 22244로 수정한다.

그리고, 방금 설정한 'example' Run Configurations를 실행한다. 이때, 실행 파일을 못 찾아서 에러가 발생한다면, 아래와 같이 파일을 가져온다.

$ scp -P 22222 dev@192.168.64.4:/tmp/example/cmake-build-debug-remote-host/example_for_remote_debug_of_clion_with_docker ./cmake-build-debug-remote-host/

Debug Console 창을 열어보면 아래와 같이 gdbserver가 잘 실행되고 22244 포트로 문제없이 연결된 것을 확인할 수 있다.

 

Process Attach 방식 동작 확인

디버깅할 프로세스에 gdbserver를 연결하기 위해 이전 포스팅 2번의 'Use GDB Remote Debug' 항목의 내용을 설정한다. 그리고, 'gdbserver_attach_process.sh' 스크립트 파일을 아래와 같이 수정한다.

#!/usr/bin/env bash

ssh -t guest@192.168.64.4 "docker exec -it centos_7 /bin/bash /home/dev/_attach_process.sh"

이전 포스팅 2번에서 설정했던 'remote debugging' Run Configurations에서 'localhost'를 Guest OS의 IP로 아래와 같이 수정한다.

그리고, 아래와 같이 예제를 먼저 실행해 둔다.

그리고, 'gdbserver_attach_process.sh' 스크립트를 실행하면 실행된 프로세스에 gdbserver가 연결하고 22244 포트로 접속을 기다리는 것을 볼 수 있다.

위와 같은 상태에서 'remote debugging' Run Configurations를 실행한다.

위와 같이 실행 중인 프로세스에 연결해서 브레이크 포인트가 잡히는 것을 확인할 수 있다.

이제 AArch64 환경에서 AMD64 바이너리를 디버깅할 때 문제가 되었던 ptrace 이슈를 회피했다. 소제목에는 해결이라고 적었지만, 말 그대로 회피했을 뿐이다. Qemu에서 ptrace 이슈가 해결되면 바로 컨테이너로 돌아가는 것이 더 효율적이다.

반응형