Debugging Linux CMake Projects with gdbserver

Erika

Erika

Update 2/20/20: We have addressed a bug which allows you to debug simple CMake projects using one level of CMakeLists.txt files with gdbserver in Visual Studio 2019 version 16.5 Preview 3. The example launch configuration has been updated below.

Gdbserver is a program that allows you to remotely debug applications running on Linux. It is especially useful in embedded scenarios where your target system may not have the resources to run the full gdb.

Visual Studio 2019 version 16.5 Preview 1 enables remote debugging of CMake projects with gdbserver. In our previous blog post we showed you how to build a CMake application in a Linux docker container. In this post we’re going expand on that set-up to achieve the following workflow:

  1. Cross-compile for ARM in our Linux docker container
  2. Copy the build output back to our local machine
  3. Deploy the program to a separate ARM Linux system (connected over SSH) and debug using gdbserver on the ARM Linux system and a local copy of gdb

This allows you to leverage a specific version of gdb on your local machine and avoid running the full client on your remote system.

Support for this workflow in Visual Studio 2019 version 16.5 Preview 1 is still experimental and requires some manual configuration. Feedback on how you’re using these capabilities and what more you’d like to see is welcome.

Cross-compile a CMake project for ARM

This post assumes you are have already configured Visual Studio 2019 to build a CMake project in a Linux docker container (Ubuntu). Check out our previous post Build C++ Applications in a Linux Docker Container with Visual Studio for more information. However, nothing about this workflow is specific to Docker, so you can follow the same steps to configure any Linux environment (a VM, a remote Linux server, etc.) for build.

The first thing we will do is modify our build to cross-compile for ARM. I’ve created a new Dockerfile based on the image defined in my previous post.

# our local base image created in the previous post
FROM ubuntu-vs

LABEL description="Container to cross-compile for ARM with Visual Studio"

# install new build dependencies (cross-compilers)
RUN apt-get update && apt-get install -y gcc-arm-linux-gnueabi g++-arm-linux-gnueabi

# copy toolchain file from local Windows filesystem to
# Linux container (/absolute/path/)
COPY arm_toolchain.cmake /opt/toolchains/

In this Dockerfile I acquire my cross-compilers and copy a CMake toolchain file from my local Windows filesystem to my Linux Docker container. CMake is also a dependency but I will deploy statically linked binaries directly from Visual Studio in a later step.

CMake toolchain files specify information about compiler and utility paths. I used the example provided by CMake to create a toolchain file on Windows with the following content.

set(CMAKE_SYSTEM_NAME Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)
set(CMAKE_C_COMPILER /usr/bin/arm-linux-gnueabi-gcc)
set(CMAKE_CXX_COMPILER /usr/bin/arm-linux-gnueabi-g++)

Save your toolchain file as ‘arm_toolchain.cmake’ in the directory where your new Dockerfile is saved. Alternatively, you can specify the path to the file relative to the build context as a part of the COPY command.

We can now build an image based on our new Dockerfile and run a container derived from the image:

> docker build -t ubuntu-vs-arm .
> docker run -p 5000:22 -i -t ubuntu-vs-arm /bin/bash

Lastly, we will interact with our docker container directly to start SSH and create a user account to use with our SSH connection.  Again, note that you can enable root login and start SSH from your Dockerfile if you want to avoid any manual and container-specific configuration. Replace <user-name> with the username you would like to use and run:

> service ssh start
> useradd -m -d /home/<user-name> -s /bin/bash -G sudo <user-name>
> passwd <user-name>

You are now ready to build from Visual Studio.

Configure CMake Settings in Visual Studio to cross-compile for ARM

Make sure you have Visual Studio 2019 version 16.5 Preview 1 or later and the Linux development with C++ workload installed. Open Visual Studio and create a new CMake project or open the sample application created in our previous post.

We will then create a new CMake configuration in Visual Studio. Navigate to the CMake Settings Editor and create a new “Linux-Debug” configuration. We will make the following modifications to cross-compile for ARM:

  1. Change the configuration name to arm-Debug (this does not affect build, but will help us reference this specific configuration)
  2. Ensure the remote machine name is set to your Linux docker container
  3. Change the toolset to linux_arm
  4. Specify the full path to your toolchain file on your Linux docker container (/opt/toolchains/arm_toolchain.cmake) as a CMake toolchain file.
  5. Navigate to the underlying CMakeSettings.json file by selecting ‘CMakeSettings.json’ in the description at the top of the editor. In your arm-Debug configuration, set “remoteCopyBuildOutput”: true. This will copy the output of your build back to your local machine for debugging with gdb.

Note that whenever your change your compilers you will need to delete the cache of the modified configuration (Project > CMake Cache (arm-Debug only) > Delete Cache) and reconfigure. If you don’t already have CMake installed, then Visual Studio will prompt you to deploy statically linked binaries directly to your remote machine as a part of the configure step.

Your CMake project is now configured to cross-compile for ARM on your Linux docker container. Once you build the program the executable should be available on both your build system (/home/<user-name>/.vs/…) and your local Windows machine.

Add a second remote connection

Next, I will add a new remote connection to the connection manager. This is the system I will be deploying to and has OS Raspbian (ARM). Make sure ssh is running on this system.

Note: The ability to separate your build system from your deploy system in Visual Studio 2019 version 16.5 Preview 1 does not yet support Visual Studio’s native support for WSL. It also does not support more than one connection to ‘localhost’ in the connection manager. This is due to a bug that will be resolved in the next release of Visual Studio. For this scenario, your docker connection should be the only connection with host name ‘localhost’ and your ARM system should be connected over SSH.

Configure launch.vs.json to debug using gdbserver

Finally, we will configure the debugger. Right-click on the root CMakeLists.txt, click on “Debug and Launch Settings” and select debugger type C/C++ Attach for Linux (gdb). We will manually configure this file (including adding and removing properties) to use gdbserver and a local copy of gdb. My launch file with inline comments is below. Again, this support is new and still requires quite a bit of manual configuration:

{
  "version": "0.2.1",
  "defaults": {},
  "configurations": [
    {
      "type": "cppdbg",
      "name": "gdbserver", // a friendly name for the debug configuration 
      "project": "CMakeLists.txt",
      "projectTarget": "CMakeProject134", // target to invoke, must match the name of the target that exists in the debug drop-down menu
      "cwd": "${workspaceRoot}", // some local directory 
      "program": "C:\\Users\\demo\\source\\repos\\CMakeProject134\\out\\build\\arm-Debug\\CMakeProject134", // full Windows path to the program
      "MIMode": "gdb",
      "externalConsole": true,
      "remoteMachineName": "-1483267367;10.101.11.101 (username=test, port=22, authentication=Password)", // remote system to deploy to, you can force IntelliSense to prompt you with a list of existing connections with ctrl + space
      "miDebuggerPath": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\Common7\\IDE\\VC\\Linux\\bin\\gdb\\8.1\\arm-linux-gnueabihf-gdb.exe", // full Windows path to local instance of gdb
      "setupCommands": [
        {
          "text": "set sysroot ." 
        },
        {
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
        }
      ],
      "pipeTransport": { "disable": true },
      "visualizerFile": "${debugInfo.linuxNatvisPath}",
      "showDisplayString": true,
      "miDebuggerServerAddress": "10.101.11.101:1234", // host name of the remote deploy system and port gdbserver will listen on
      "remotePrelaunchCommand": "gdbserver :1234 /home/test/.vs/CMakeProject134/66f2462c-6a67-40f0-8b92-34f6d03b072f/out/build/arm-Debug/CMakeProject134/CMakeProject134 >& /dev/null", // command to execute on the remote system before gdb is launched including the full path to the output on your remote debug system, >& /dev/null is required
      "remotePrelaunchWait": "2000" // property to specify a wait period after running the prelaunchCommand and before launching the debugger in ms
    }
  ]
}

Now set a breakpoint and make sure arm-Debug is your active CMake configuration and gdbserver is your active debug configuration.

Make sure arm-Debug is your active CMake configuration and gdbserver is your active debug configuration.

When you press F5 the project will build on the remote system specified in CMakeSettings.json, be deployed to the remote system specified in launch.vs.json, and a local debug session will be launched.

Troubleshooting tips:

  1. If your launch configuration is configured incorrectly then you may be unable to connect to your remote debug machine. Make sure to kill any lingering gdbserver processes on the system you are deploying to before attempting to reconnect.
  2. If you do not change your remote build root in CMake Settings, then the relative path to the program on your remote debug machine is the same as the relative path to the program on your remote build machine from ~/.vs/…
  3. You can enable cross-platform logging (Tools > Options > Cross Platform > Logging) to view the commands executed on your remote systems.
  4. Update 2/20/2020: Simple CMake projects using one level of CMakeLists.txt files will need to add “pipeTransport”: { “disable”: true } to their launch configuration. This is available in Visual Studio 2019 version 16.5 Preview 3 or later.

Give us your feedback

Do you have feedback on our Linux tooling or CMake support in Visual Studio? We’d love to hear from you to help us prioritize and build the right features for you. We can be reached via the comments below, Developer Community (you can “Suggest a Feature” to give us new ideas), email (visualcpp@microsoft.com), and Twitter (@VisualC). The best way to suggest new features or file bugs is via Developer Community.

1 comment

Comments are closed.

  • Avatar
    Sohno Mehar

    The @HostListener decorator is used to set up an event binding on the host element and is applied to a method. The example directive relies on the browser’s DOM API to manipulate its host element, both to add and remove class memberships and to receive the click event. Read More!