안드로이드 ndk 디버그하기 with gdb

Programming/Unreal Engine 2019. 2. 23. 22:24 by 빠재

TL;DR - ndk-gdb를 이용하면 가능하다.

회사에서 언리얼 엔진을 사용하고 있는데, 여기에 자바스크립트 엔진인 ChakraCore를 스크립팅 엔진으로 붙여 사용하고 있었다. 안드로이드 빌드에는 32비트 abi인 armv7 빌드만 있었는데 올해 8월부터 64비트 빌드만 받아준다고 하기에 부랴부랴 자바스크립트 엔진도 64비트를 지원하는 작업을 시작했다.

그 과정에서 디버거의 필요성이 절실했는데, 마침 ndk에 gdb가 포함되어있어 그나마 수월하게 개발할 수 있었다. 하지만 있는 그대로 사용하려면 adb 쉘에서 gdb서버 띄우고 하는 과정이 필요해서 매우 번거롭다. ndk-gdb는 이를 래핑하여 명령 한번에 디버깅을 시작할 수 있다.

ndk-gdb

ndk-gdb의 옵션은 아래와 같다:

$ ./andoid-ndk-r15c/ndk-gdb -h
usage: ndk-gdb.py [-h] [--adb ADB_PATH] [-a | -d | -e | -s SERIAL] [--verbose]
                  [--force] [--port [PORT]] [--delay DELAY] [-p PROJECT]
                  [--attach [PKG_NAME] | --launch [ACTIVITY] | --launch-list]
                  [-x EXEC_FILE] [--nowait] [-t]
                  [--stdcxx-py-pr {auto,none,gnustl,stlport}]
 
optional arguments:
  -h, --help            show this help message and exit
  --adb ADB_PATH        use specific adb command
  --verbose, -v         enable verbose mode
  --force, -f           kill existing debug session if it exists
  --port [PORT]         override the port used on the host
  --delay DELAY         delay in seconds to wait after starting activity.
                        defaults to 0.25, higher values may be needed on
                        slower devices.
  -p PROJECT, --project PROJECT
                        specify application project path
 
 
device selection:
  -a                    directs commands to all interfaces
  -d                    directs commands to the only connected USB device
  -e                    directs commands to the only connected emulator
  -s SERIAL             directs commands to device/emulator with the given
                        serial
 
target selection:
  --attach [PKG_NAME]   attach to application (default) autodetects PKG_NAME
                        if not specified
  --launch [ACTIVITY]   launch application activity launches main activity if
                        ACTIVITY not specified
  --launch-list         list all launchable activity names from manifest
 
 
debugging options:
  -x EXEC_FILE, --exec EXEC_FILE
                        execute gdb commands in EXEC_FILE after connection
  --nowait              do not wait for debugger to attach (may miss early JNI
                        breakpoints)
  -t, --tui             use GDB's tui mode
  --stdcxx-py-pr {auto,none,gnustl,stlport}
                        use C++ library pretty-printer

필요한 옵션은 아래와 같으며 적절히 조합하면 된다. 실제 예제는 아래에 있다.

언리얼 엔진 게임에 사용해 보기

언리얼 엔진에서 갓 빌드한 따끈따끈한 apk를 디버깅해 볼 수 있다.

# 디버깅용 심볼 복사
mkdir -p ${PROJ_DIR}/Intermediate/Android/API/obj/local/armeabi-v7a/system/lib
cp ${PROJ_DIR}/Intermediate/Android/APK/gradle/app/build/intermediates/transforms/mergeJniLibs/debug/0/lib/armeabi-v7a/*.so ${PROJ_DIR}/Intermediate/Android/APK/obj/local/armeabi-v7a/system/lib

# ndk-gdb 실행
${NDK}/ndk-gdb --force --tui --project ${PROJ_DIR}/Intermediate/Android/APK --launch --nowait

이렇게 하면 gdb창이 뜨고 심볼 정보를 읽고 몇 초 뒤에 디버깅을 할 수 있게 된다.

arm64의 경우 armeabi-v7a 부분을 arm64-v8a로 고쳐주면 된다. 그리고 ${PROJ_DIR}/Intermediate/Android/APK/jni/Application.mk 파일 안의 APP_ABI 항목도 arm64-v8a로 고쳐 주어야 한다. 지정한 abi에 맞게 파일이 만들어져야 할텐데 저부분은 버그인듯 싶다.

# 디버깅용 심볼 복사
mkdir -p ${PROJ_DIR}/Intermediate/Android/APK/obj/local/arm64-v8a/system/lib64
cp ${PROJ_DIR}/Intermediate/Android/APK/gradle/app/build/intermediates/transforms/mergeJniLibs/debug/0/lib/arm64-v8a/*.so ${PROJ_DIR}/Intermediate/Android/APK/obj/local/arm64-v8a/system/lib64
 
# gdb 실행
${NDK}/ndk-gdb --force --tui --project ${PROJ_DIR}/Intermediate/Android/APK --launch --nowait

그 다음은 일반 gdb와 똑같이 진행하면 된다. 디버거라고는 평소 비주얼 스튜디오만 사용했기에 처음 gdb를 접했을 때는 콘솔 인터페이스가 적응이 되지 않았지만 사용하다 보니 키보드로 디버깅하는 손맛(?)이 일품이다.

Nav