This article discusses how to debug native Android applications using Microsoft Visual Studio Code, which can be useful for game developers implementing background services, prototyping, or other native processes on the Android platform.

Of course, Android Studio and Visual Studio 2017/2015 also offer Android debugging capabilities, but they're more restrictive with the configuration environment and only allow debugging of applications packaged in an APK - so we are unable to debug any native processes we might be using to prototype, develop services, or other native processes on Android.

Speaking of APKs, note that VSCode also supports debugging APKs via the Android extension.

Prerequisites

The following components are required to enable debugging:

Visual Studio Code with the C/C++ extension installed. GNU Debugger (GDB) from the Android NDK. OS Path Windows <NDK_ROOT>\prebuilt\windows-x86_64\bin Linux <NDK_ROOT>\prebuilt\linux-x86_64\bin macOS <NDK_ROOT>\prebuilt\darwin-x86_64\bin Windows \prebuilt\windows-x86_64\bin Linux \prebuilt\linux-x86_64\bin macOS \prebuilt\darwin-x86_64\bin A build of the target project with debugging enabled. This can be achieved by passing NDK_DEBUG=1 on the ndk-build command line, or by adding it to the project's Application.mk file. Enabling NDK_DEBUG also causes ndk-build to copy the correct version of gdbserver to the project's output directory. This will need to be copied to the target device to enable the debugger connection. Also, if the project's Application.mk file specifies the APP_OPTIM setting, it must be set to debug to disable compiler optimizations. While debugging optimized code it possible, it will be a more limited and difficult debugging experience. Optimization can be disabled by passing APP_OPTIM=debug on the ndk-build command line, or by modifying it in the project's Application.mk file. If the project does not specify the APP_OPTIM setting, setting NDK_DEBUG as described above will automatically disable optimizations.

Debugging Setup

Before debugging the first time, open the project workspace in VSCode and perform the following steps:

Open the Debug menu and click Add Configuration... Choose C/C++ (gdb) Launch as the configuration type. If this option is not available the C/C++ extension is not installed and none of this is going to work. Configure the debugger settings for the project. The required attributes are described below. Attribute Description name The name of the debugging task. This is the name that will be displayed in the UI. type Should be set to cppdebug. request Should be set to launch. program The program to debug. This should point to the local version of the executable with debug symbols (the non-stripped version), which is normally in obj/local/armeabi-v7a under the project's build directory (or obj/local/arm64-v8a for 64 bit builds). cwd Doesn't really have any effect, but is required. Can just be set to ${workspaceFolder}. MIMode Should be set to gdb. miDebuggerPath The path to the gdb executable. This should point to the directory in the Android NDK as indicated above. miDebuggerServerAddress The target address to connect to. Since the device is connected via USB, this should be set to localhost:<port>, where <port> is the TCP port chosen for the debugger connection. This same port must be used when starting gdbserver on the device. additionalSOLibSearchPath Any additional local paths to search for debug symbols. The directory specified for the program attribute will automatically be searched first. This should point to the directories of the non-stripped versions of any additional shared libraries to be debugged (if they are not in the same directory as the main executable). Separate multiple paths with a colon. setupCommands An array of additional gdb commands to be executed during setup of the debugger. This needs to be used to execute the set solib-absolute-prefix command to set the local path to the stripped version of all binaries being debugged. preLaunchTask A task to execute before starting the debugger. See the section below about defining tasks in VSCode. name The name of the debugging task. This is the name that will be displayed in the UI. type Should be set tocppdebug. request Should be set tolaunch. program The program to debug. This should point to the local version of the executable with debug symbols (the non-stripped version), which is normally inobj/local/armeabi-v7aunder the project's build directory (orobj/local/arm64-v8afor 64 bit builds). cwd Doesn't really have any effect, but is required. Can just be set to${workspaceFolder}. MIMode Should be set togdb. miDebuggerPathThe target address to connect to. Since the device is connected via USB, this should be set tolocalhost: , where is the TCP port chosen for the debugger connection. This same port must be used when starting gdbserver on the device. additionalSOLibSearchPath Any additional local paths to search for debug symbols. The directory specified for theattribute will automatically be searched first. This should point to the directories of the non-stripped versions of any additional shared libraries to be debugged (if they are not in the same directory as the main executable). Separate multiple paths with a colon. setupCommands An array of additional gdb commands to be executed during setup of the debugger. This needs to be used to execute thepreLaunchTask A task to execute before starting the debugger. See the section below about defining tasks in VSCode.

The other attributes created for the default configuration can be deleted.

Example launch.json file { "version": "0.2.0", "configurations": [ { "name": "Remote GDB", "type": "cppdbg", "request": "launch", "preLaunchTask": "Forward debug port", "cwd": "${workspaceRoot}", "program": "${workspaceRoot}/app/Intermediate/android/myapp/local/armeabi-v7a/myapp", "additionalSOLibSearchPath": "${workspaceRoot}/app/Intermediate/android/mylib/local/armeabi-v7a", "miDebuggerServerAddress": "localhost:5039", "setupCommands": [ { "text": "set solib-absolute-prefix ${workspaceRoot}/app/path/android", "ignoreFailures": false } ], "windows": { "miDebuggerPath": "C:/Tools/android-ndk-r13b/prebuilt/windows-x86_64/bin/gdb.exe", "MIMode": "gdb" } } ] }

Refer to the C/C++ debugging documentation for additional information. In particular, the logging attribute can be used to enable additional logging output, which can be useful for troubleshooting if the debugger is not working as expected.

Preparing to Debug

With the debugger configuration set, there are some additional steps required to debug the project:

Copy the target binaries to the target device. Copy gdbserver to the target device.

Starting the Debugger

With all preparations complete, it's time to start debugging. Use the same port number in these steps as was used in the debugger configuration above.

Execute gdbserver on the target device (execute permissions may need to be enabled). The server can be run in two modes: execute or attach. Execute mode will load the target executable and then wait for a client to attach. When the client attaches it will begin execution of the program. This mode is necessary to debug an application's initialization. Attach mode will attach to an already running process. It will not interrupt the process. Mode Command Run mode gdbserver :<port> <exe> Attach mode gdbserver :<port> --attach <pid> Run mode gdbserver : Attach mode gdbserver : --attach Forward the debugger port from the device with ADB: adb forward tcp:<port> tcp:<port> Start the debugger in VSCode. Enable the Debug panel by clicking the bug icon on the left side of the VSCode window. Select the debug configuration from the list at the top of the panel. Click the go button to launch the debugger.

Once the debugger starts to connect, the VSCode debug console will display messages from the debugger and allow manual debugger commands to be executed if necessary (the program must be stopped to execute debug commands). The debug panels will display debugging information (variable watches, callstacks, breakpoints, etc.), and a debugger toolbar will provide access to common debug commands. Mouse cursor based display of variables is also supported.

Tasks

VSCode also supports the definition of tasks, which can be used to execute any command line process, typcially as part of building and/or testing code. These tasks can also be used to support debugging. For instance, a task could be defined to forward the debugger port. This could then be incorporated in the debugger configuration using the preLaunchTask attribute to ensure the port is forwarded before attempting to connect the debugger. Tasks can be configured by selecting Configure Tasks... from the Tasks menu. See the VSCode documentation for more information about task configuration.