Отладка в Visual Studio Code

Настройка отладки в Visual Studio Code

  ·  8 min read

Добрался до момента, когда необходимо зафиксировать знания об отладке в Visual Studio Code. В этой статье приведу пример автоматизации сборки и отладки проекта на примере проекта open-isns - конфигурационные файлы сборки проекта в VS Code, а также запуск отладки с повышенными привелегиями. Думаю, некоторые из примеров можно переписать/адаптировать к своим проектам, используя их в качестве шаблонов.

Автоматизация сборки #

В качестве примера сборки отладки использую проект open-isns. Клонирую его с удаленного репозитория:

git clone https://github.com/open-iscsi/open-isns.git

Данный проект собирается с помощью систем сборки Meson и Ninja. Для данного проекта я создал следующую конфигурацию сборки, которая детально представлена ниже.

tasks.json #

Пользовательские задачи представлены в виде трёх частей:

  1. Meson Setup: Настраивает проект с помощью Meson, создавая директорию сборки и применяя указанные параметры.
  2. Ninja Build: Компилирует и собирает проект с помощью Ninja, завися от успешного выполнения Meson Setup. Это задача по умолчанию для сборки.
  3. Clean Build: Удаляет директорию сборки для полной очистки.

Meson Setup #

{
    "label": "Meson Setup",
    "type": "shell",
    "command": "meson",
    "args": [
        "setup", "${workspaceFolder}/builddir",
        "-Ddebug=true",
        "--default-library=shared",
        "-Dsecurity=enabled"
    ],
    "group": "build",
    "problemMatcher": []
}
  • label: "Meson Setup"

    • Уникальное имя задачи, отображаемое в интерфейсе VS Code (например, в меню выбора задач).
  • type: "shell"

    • Указывает, что команда будет выполняться в оболочке (shell).
  • command: "meson"

    • Команда, которая будет выполнена.
  • args

    • Массив аргументов, передаваемых команде meson:
      • "setup": Подкоманда Meson, которая инициализирует проект и создает конфигурацию сборки.
      • "${workspaceFolder}/builddir": Путь к директории, где будут храниться файлы сборки. Переменная ${workspaceFolder} заменяется на путь к корневой папке проекта в VS Code. В данном случае директория сборки — builddir внутри корневой папки.
      • "-Ddebug=true": Устанавливает опцию сборки Meson, включающую отладочную информацию (например, символы отладки для дебаггера).
      • "--default-library=shared": Указывает, что по умолчанию проект будет собираться как разделяемая библиотека (shared library, .so), а не статическая (.a).
      • "-Dsecurity=enabled": Пользовательская опция сборки проекта, которая включает функции безопасности.
  • group: "build"

    • Указывает, что задача относится к группе задач сборки (build). Это позволяет VS Code классифицировать задачу и отображать её в контексте операций сборки (например, при вызове команды Run Build Task).
  • problemMatcher: []

    • Список шаблонов для анализа вывода команды и выявления ошибок/предупреждений. Пустой массив [] означает, что для этой задачи не используется анализ проблем (ошибки компиляции или предупреждения не будут подсвечиваться в редакторе).

Ninja Build #

{
    "label": "Ninja Build",
    "type": "shell",
    "command": "ninja",
    "args": [
        "-C", "${workspaceFolder}/builddir"
    ],
    "group": {
        "kind": "build",
        "isDefault": true
    },
    "problemMatcher": ["$gcc"],
    "dependsOn": ["Meson Setup"]
}
  • label: "Ninja Build"

    • Имя задачи, отображаемое в интерфейсе. Это основная задача для сборки проекта.
  • type: "shell"

    • Как и в предыдущей задаче, команда выполняется в оболочке.
  • command: "ninja"

    • Команда для выполнения — ninja, инструмент сборки, который используется для компиляции и сборки проекта на основе конфигурации, созданной Meson.
  • args

    • Аргументы для команды ninja:
      • "-C": Указывает Ninja, в какой директории выполнять сборку.
      • "${workspaceFolder}/builddir": Путь к директории сборки, где находятся файлы, созданные задачей Meson Setup.
  • group

    • Объект, описывающий группу задачи:
      • kind: "build": Указывает, что это задача сборки.
      • isDefault: true: Делает задачу Ninja Build задачей по умолчанию для группы build. Это означает, что она будет выполняться при вызове команды Run Build Task (Ctrl+Shift+B в VS Code).
  • problemMatcher: ["$gcc"]

    • Указывает, что для анализа вывода команды будет использоваться шаблон $gcc. Этот шаблон позволяет VS Code распознавать ошибки и предупреждения, выдаваемые компилятором GCC (или совместимыми, такими как Clang), и подсвечивать их в редакторе (например, в панели проблем или в коде).
  • dependsOn: ["Meson Setup"]

    • Указывает зависимость от задачи Meson Setup. Это означает, что перед выполнением задачи Ninja Build автоматически выполнится задача Meson Setup, чтобы гарантировать наличие актуальной конфигурации сборки.

Clean Build #

{
    "label": "Clean Build",
    "type": "shell",
    "command": "rm -rf ${workspaceFolder}/builddir",
    "group": "build",
    "problemMatcher": []
}
  • label: "Clean Build"

    • Имя задачи, используемое для её идентификации в VS Code.
  • type: "shell"

    • Команда выполняется в оболочке.
  • command: "rm -rf ${workspaceFolder}/builddir"

    • Команда для выполнения: удаление директории builddir и всех её содержимого. Флаги:
      • -r: Рекурсивное удаление (включая поддиректории).
      • -f: Игнорирование ошибок, если директория не существует.
      • ${workspaceFolder}/builddir: Путь к директории сборки.
  • group: "build"

    • Задача относится к группе задач сборки, но не является задачей по умолчанию.
  • problemMatcher: []

    • Как и в задаче Meson Setup, анализ проблем не используется, так как команда rm не генерирует вывод, связанный с ошибками компиляции.

Конечная конфигурация #

{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Meson Setup",
            "type": "shell",
            "command": "meson",
            "args": [
                "setup", "${workspaceFolder}/builddir",
                "-Ddebug=true",
                "--default-library=shared",
                "-Dsecurity=enabled"
            ],
            "group": "build",
            "problemMatcher": []
        },
        {
            "label": "Ninja Build",
            "type": "shell",
            "command": "ninja",
            "args": [
                "-C", "${workspaceFolder}/builddir"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": ["$gcc"],
            "dependsOn": ["Meson Setup"]
        },
        {
            "label": "Clean Build",
            "type": "shell",
            "command": "rm -rf ${workspaceFolder}/builddir",
            "group": "build",
            "problemMatcher": []
        }
    ]
}

launch.json #

Файл launch.json настроен для отладки компилируемой программы isnsd с использованием GDB. Он интегрируется с системой сборки Meson/Ninja через задачу Ninja Build.

{
    "name": "Debug",
    "type": "cppdbg",
    "request": "launch",
    "program": "${workspaceFolder}/builddir/isnsd",
    "args": [
        "-f"
    ],
    "stopAtEntry": false,
    "cwd": "${workspaceFolder}/builddir",
    "environment": [],
    "externalConsole": false,
    "MIMode": "gdb",
    "setupCommands": [
        {
            "description": "Enable pretty-printing for gdb",
            "text": "-enable-pretty-printing",
            "ignoreFailures": true
        }
    ],
    "preLaunchTask": "Ninja Build",
    "miDebuggerPath": "/usr/bin/gdb"
}
  • name: "Debug"

    • Имя конфигурации отладки, отображаемое в интерфейсе VS Code (например, в выпадающем меню выбора конфигурации отладки).
  • type: "cppdbg"

    • Тип отладчика, используемый для этой конфигурации. Значение "cppdbg" указывает на расширение Microsoft C/C++ для VS Code, которое поддерживает отладку C/C++ приложений через GDB или LLDB. В данном случае используется GDB (см. поле MIMode).
  • request: "launch"

    • Тип запроса отладки. Значение "launch" означает, что VS Code запустит указанную программу и присоединит к ней отладчик.
  • program: "${workspaceFolder}/builddir/isnsd"

    • Путь к исполняемому файлу, который будет отлаживаться. Переменная ${workspaceFolder} заменяется на путь к корневой папке проекта в VS Code. В данном случае программа — isnsd, расположенная в директории builddir после компиляции.
  • args: ["-f"]

    • Массив аргументов командной строки, передаваемых программе при её запуске. В данном случае программа isnsd запускается с флагом -f. Этот флаг указывает на работу в foreground-режиме (не в фоновом режиме).
  • stopAtEntry: true

    • Указывает, должен ли отладчик останавливаться на точке входа программы (обычно в функции main). Значение true означает, что программа начнет выполнение и остановится в начале точки входа программы.
  • cwd: "${workspaceFolder}/builddir"

    • Рабочая директория (current working directory), в которой будет запущена программа. В данном случае это директория builddir в корне проекта. Это важно, если программа ожидает определённые файлы (например, конфигурации) в своей рабочей директории.
  • environment: []

    • Массив объектов, задающих переменные окружения для программы. Пустой массив означает, что дополнительные переменные окружения не устанавливаются, и программа использует окружение, унаследованное от VS Code.
  • externalConsole: false

    • Указывает, использовать ли внешнюю консоль для вывода программы. Значение false означает, что вывод программы (stdout/stderr) будет отображаться во встроенной консоли VS Code (вкладка “Debug Console”/“Консоль отладки”).
  • MIMode: "gdb"

    • Указывает режим машинного интерфейса (MI, Machine Interface) для отладчика. Значение "gdb" означает, что используется отладчик GDB.
  • setupCommands

    • Массив команд, которые передаются отладчику GDB перед началом отладки. В данном случае есть одна команда:
      {
          "description": "Enable pretty-printing for gdb",
          "text": "-enable-pretty-printing",
          "ignoreFailures": true
      }
      
      • description: Описание команды для удобства чтения.
      • text: Команда GDB -enable-pretty-printing, которая включает улучшенное форматирование вывода структур и объектов в GDB.
      • ignoreFailures: Если true, ошибки выполнения этой команды игнорируются, и отладка продолжается. Это полезно, так как не все версии GDB могут поддерживать эту команду.
  • preLaunchTask: "Ninja Build"

    • Имя задачи, которая должна быть выполнена перед запуском отладки. В данном случае это задача "Ninja Build", которая определена в файле tasks.json.
  • miDebuggerPath: "/usr/bin/gdb"

    • Путь к исполняемому файлу отладчика GDB.

Отладка сборки #

Для отладки достаточно нажать клавишу F5 в VS Code и проект начнет собираться, после чего запустится и остановится на точке входа программы - в функции main:

01-image

В процессе отладки и выполнения шагов по стейтменам (синтаксически завершённая команда) я столкнулся со следущей ошибкой:

** FATAL ERROR **
Error: Error creating pid file /var/run/isnsd.pid: Permission denied

Данная ошибка говорит о том, что выполнение программы запрашивает доступ к файлу /var/run/isnsd.pid или пути /var/run. В таком случае подразумевается, что isnsd требует повышенных привилегий при своей работе. Также isnsd в процессе работы открывает порт 3205:

$ sudo lsof -i :3205
COMMAND   PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
isnsd   26201 root    4u  IPv6 160135      0t0  TCP *:isns (LISTEN)

Повышение привилегий #

Запуск Visual Studio Code с привилегиями отладки может привести к ряду потенциальных проблем: угрозы безопасности, проблемы с файловой системой, проблемы с расширениями и т.п. Но в приведенном выше примере пришлось столкнуться с потребностью в отладке с повышенными привилегиями. Если же к отлаживаемому проекту имеется доверие, то можно реализовать отладку посредством вызова pkexec.

pkexec — это утилита в Linux, позволяющая запускать команды с повышенными привилегиями (обычно от имени root) через PolicyKit (ныне polkit). В отличие от sudo, pkexec предоставляет более гибкую систему авторизации, интегрируясь с графическими средами (например, запрашивает пароль в GUI).

Необходимо создать исполняемый bash скрипт, например /usr/bin/sudo-gdb:

#!/bin/bash

pkexec /usr/bin/gdb "$@"

При запуске, отладчик GDB принимает переданные скрипту аргументы и вызывается из под вызова pkexec, который в свою очередь запросит ввести пароль, чтобы выполнить запуск GDB от имени суперпользователя:

02-image

Таким образом мы запустим отладку лишь авторизовавшись.

Правим в файле launch.json параметр miDebuggerPath, содержащий путь к отладчику, на путь к /usr/bin/sudo-gdb:

"miDebuggerPath": "/usr/bin/sudo-gdb"

Запускаем отладку и получаем запрос на ввод пароля:

03-image

После успешной аутентификации отладка запускается в режиме с повышенными привилегиями. Программа isnsd успешно отлаживается.

Если необходимо убрать постоянный запрос пароля при отладке, то нужно создать разрешающее на запуск GDB с привилегиями правило, например /etc/polkit-1/rules.d/50-allow-gdb.rules:

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.policykit.exec" &&
        action.lookup("program") == "/usr/bin/gdb" &&
        subject.user == "<USER>") {
        return polkit.Result.YES;
    }
});

В качестве <USER> указать своего пользователя, от имено которого вы работаете в пользовательской среде.

Альтернатива с sudo #

Если же изменить запрос привилегий через sudo в файле /usr/bin/sudo-gdb

#!/bin/bash

sudo /usr/bin/gdb "$@"

то при выполнении отладки мы будем получать ошибки при запросе ввода пароля, так как VS Code не предоставляет интерактивный ввод пароля для консольных утилит:

sudo: unable to read password: Input/output error

В этом случае необходимо явно разрешить выполнение GDB посредством sudo от имени нашего пользователя, добавив правило для sudo, например /etc/sudoers.d/50-allow-gdb:

<USER> ALL=(ALL) NOPASSWD: /usr/bin/gdb

В качестве <USER> указать своего пользователя, от имено которого вы работаете в пользовательской среде.


Конечно, многие параметры и разрешения для прочих пользователей и групп - это опциональные настройки. Выше приведены лишь примеры реализации данных разрешений. Выбор подхода зависит лишь от ваших требований, но не стоит забывать о безопасности.