Setting Up a C/C++ Debug Environment in VSCode

How to set up a C/C++ debug environment in VSCode, with a walkthrough of `task.json` and `launch.json` and sample configurations.

Today I read Configure launch.json for C/C++ debugging in Visual Studio Code and got a basic VSCode C/C++ debug setup working. This post records how I configured the C/C++ debug environment in VSCode on Windows/WSL, as a reference for other beginners. Before starting, you need VSCode installed, a working WSL environment, and gcc on your PATH.

The article explains the meaning of some of the attributes in launch.json and tasks.json, and includes my own configurations for reference.

Steps

launch.json

To set up debugging, first configure launch.json. launch.json configures VSCode’s debug environment. It contains a list of debug configurations; each one is an object with a set of fields that define how to start and run the debugger. In VSCode, you can enter debug mode with F5 or by clicking the debug button in the sidebar. VSCode then looks for a launch.json file in the current working directory. If it can’t find one, VSCode prompts you to create it; if it finds one, VSCode loads the configurations from launch.json and starts the debugger accordingly.

A launch.json file is used to configure the debugger in Visual Studio Code.

Visual Studio Code generates a launch.json (under a .vscode folder in your project) with almost all of the required information. To get started with debugging you need to fill in the program field with the path to the executable you plan to debug. This must be specified for both the launch and attach (if you plan to attach to a running instance at any point) configurations.

Launch.json Code

Here is my configuration, for reference only

{  "version": "0.2.0",  "configurations": [    {      "name": "c/c++ gdb",      "type": "lldb",      "request": "launch",      "args": [],      "cwd": "${fileDirname}",      "console": "integratedTerminal",      "windows": {        "program": "${fileDirname}/${fileBasenameNoExtension}.exe",        "preLaunchTask": "compile c file(Windows)"      },      "linux": {        "program": "${fileDirname}/${fileBasenameNoExtension}.out",        "preLaunchTask": "compile c file(Linux)"      }    }  ]}

Attributes

  • version: version of this file format
    No need to change this

  • configurations: List of configurations.
    An array of configurations holding your debug setups. Each debug configuration is an object with a set of fields that define how to start and run the debugger

  • type: Type of configuration
    Specifies which debugger to use. The available values depend on the debugger extensions you have installed. Common values for the type field include node, java, lldb (mainly for debugging c/cpp and other LLVM-supported languages), chrome (JavaScript, TypeScript), and so on

  • name: Name of configuration; appears in the launch configuration dropdown menu.
    Exactly what it says; here c/c++ gdb is the name in launch.json

configure name
  • preLaunchTask: Task to run before debug session starts.
    A task executed before debugging starts, such as compiling your code to produce an executable (.exe, .out e.g.). If the task fails, the debug session won’t start. Since we’re setting up a c/cpp debug environment here, set preLaunchTask to build active file (any name works), as long as it matches the label attribute of the task object in tasks.json

  • program: Path to the program to debug.
    Specify the debug program’s path. Taking Windows as an example, if we wrote hello.c, the program to debug is filePath/hello.exe. We can simplify program with the following pre-defined variables

    Common pre-defined variables provided by VSCode for configuration files:

    • ${workspaceFolder}: The path of the folder opened in VS Code.
    • ${workspaceRootFolderName}: The name of the folder opened in VS Code without any slashes (/).
    • ${file}: The current opened file.
    • ${fileWorkspaceFolder}: The current opened file’s workspace folder.
    • ${relativeFile}: The current opened file relative to workspaceFolder.
    • ${relativeFileDirname}: The current opened file’s dirname relative to workspaceFolder.
    • ${fileBasename}: The current opened file’s basename.
    • ${fileBasenameNoExtension}: The current opened file’s basename with no file extension.
    • ${fileDirname}: The current opened file’s dirname.
    • ${fileExtname}: The current opened file’s extension.
    • ${lineNumber}: The current selected line number in the active file.
    • ${selectedText}: The current selected text in the active file.
    • ${execPath}: The location of the VS Code executable.
    • ${defaultBuildTask}: The name of the default build task.
  • request: Indicates whether the configuration section is intended to launch the program or attach to an already running instance.
    Usually pick launch; attach may come up in frontend debugging

  • console: Terminal type to use.
    A matter of personal preference. The options are

    • internalConsole: the TERMINAL in the VSCode Panel

    • integratedTerminal: the DEBUG CONSOLE in the VSCode Panel

    • externalTerminal: an external integrated terminal

  • cwd: Program working directory.
    The cwd field sets the Current Working Directory. When you start the debugger, this directory is used as the program’s working directory; it’s usually set to "cwd": "${fileDirname}"

  • window/linux/osx: specific launch configuration attributes
    Attributes set separately for the Windows, Linux/WSL, and MacOS platforms

tasks.json

For the debugger to work, your code has to be compiled into an executable first (.exe, .out e.g.), so in preLaunch we defined two tasks to compile the c file.

Using tasks.json

Tasks.json Code

{  "version": "2.0.0",  "tasks": [    {      "type": "shell",      "label": "compile c file(Windows)",      "command": "gcc",      "args": [        "-g",        "${file}",        "-o",        "${fileDirname}\\${fileBasenameNoExtension}.exe"      ]    },    {      "type": "shell",      "label": "compile c file(Linux)",      "command": "gcc",      "args": [        "-g",        "${file}",        "-o",        "${fileDirname}/${fileBasenameNoExtension}.out"      ]    }  ]}

Attribute

  • tasks: The task configurations. Usually these are enrichments of task already defined in the external task runner.
    For example, the two tasks defined earlier in launch.json

    "windows": {    "program": "${fileDirname}/${fileBasenameNoExtension}.exe",    "preLaunchTask": "build active file(Windows)"},"linux": {    "program": "${fileDirname}/${fileBasenameNoExtension}.out",    "preLaunchTask": "build active file(Linux)"}
  • type: Defines whether the task is run as a process or as a command inside a shell.
    The options are shell and process. The gcc command runs in a shell, so pick shell here

  • label: The task’s user interface label
    The task’s name; launch.json uses the label to specify which tasks to run

  • command: The command to be executed. Can be an external program or a shell command.
    gcc works here; you can also pick clang or another c/cpp compiler

  • args: Arguments passed to the command when this task is invoked.
    The command’s arguments, such as -o, -g, and so on

Outro

​ Tested with the classic hello.c, and debugging works fine. I hope this post helps you 🕊️

demo
demo