Пост содержит подробное руководство как организовать удаленную отладку разрабатываемого приложения на .NET 5.0 в Visual Studio Code для устройства на ARM процессоре, на устройстве установлена Armbian (Linux). Благодаря кроссплатформенности .NET 5.0, разработанное приложение будет одинаково работать как в Windows, так и в Linux. Но все становится сложнее, если необходимо взаимодействовать с подсистемами Linux. Каждый раз компилировать в Windows и переносить исполняемые файлы ручным способом на Linux не очень удобно. Один из рабочих примеров для подобного решения является задача отладки взаимодействия приложения на C# в Linux с устройством подключенным по протоколу RS232. В качестве платформы запуска будем использовать Cubietruck (ARM32), и Banana Pi BPI-M64(ARM64), работающие на Armbian.
Постановка задачи
В Visual Studio Code под ОС Windows x64 разрабатываем проект для ARM. Отладка приложения будет состоять из трех задач:
- компиляция под архитектуру ARM
- копирование исполняемых файлов на конечное устройство
- запуск удаленного отладчика на конечном устройстве.
Компиляция под архитектуру ARM будет выполняться с помощью .NET SDK, командой: dotnet build . Копирование файлов будем производить программой Rsync. В качестве pipeTransport для связи с удаленной системой будет использоваться программа PLINK, из пакета PuTTY. Удаленный отладчик от Microsoft — vsdbg.
Предварительные требования
- одноплатный компьютер на ARM процессоре не ниже ARMv7.
- ОС Linux на плате ARM с ядром Linux 5.8 (с более старыми ядрами не проводилась проверка)
- Локальный компьютер для разработки с Visual Studio Code, работающий под управлением Windows 7+ x64, с установленным расширением C# for Visual Studio Code (powered by OmniSharp) и SDK .NET
Тестирование производилось на следующие устройствах:
- Плата Cubietruck установлена версия Armbian_21.02.1_Cubietruck_focal_current_5.10.12.img.xz, основанная на Ubuntu 20.04.1 LTS (Focal Fossa), ядро Linux 5.10.12. uname: Linux cubietruck 5.10.12-sunxi #21.02.1 SMP Wed Feb 3 20:39:30 CET 2021 armv7l armv7l armv7l GNU/Linux.
- Плата Banana Pi BPI-M64 установлена версия Armbian_20.08.2_Bananapim64_bionic_current_5.8.6_minimal.img.xz, основанная на Ubuntu 18.04.5 LTS (Bionic Beaver), ядро Linux 5.8.6. uname: Linux bananapim64 5.8.6-sunxi64 #20.08.2 SMP Fri Sep 4 08:52:31 CEST 2020 aarch64 aarch64 aarch64 GNU/Linux.
Шаг 1 — Установка SDK .NET, Visual Studio Code и расширение поддержки C# в Windows
Установка SDK .NET, Visual Studio Code и расширение поддержки C# в Windows подробно описана в посте Установка .NET 5.0 для ARM на примере Banana Pi BPI-M64 и Cubietruck (Armbian, Linux) и Создание первого приложения на .NET 5.0 в Visual Studio Code для ARM.
Шаг 2 — Установка программ MobaXterm, cwRsync и PuTTY, в Windows
MobaXterm
Терминал MobaXterm — существенно удобнее в работе по сравнению с PuTTY терминалом, и позволяет выполнять загрузку/копирование файлов и папок в один клик. Загрузим по ссылке и установим.
cwRsync
Для копирования сборок и файлов проекта будем использовать программу Rsync. Rsync — программа для UNIX-подобных систем, которая эффективно выполняет синхронизацию файлов и каталогов в двух местах (необязательно локальных) с минимизированием трафика, используя кодирование данных при необходимости. Важным отличием rsync от многих других программ/протоколов является то, что зеркалирование осуществляется одним потоком в каждом направлении (а не по одному или несколько потоков на каждый файл). rsync может копировать или отображать содержимое каталога и копировать файлы, опционально используя сжатие и рекурсию. rsync передаёт только изменения файлов, что отражается на производительности программы.
Но Rsync не предназначена для платформы Windows, поэтому воспользуемся пакетом cwRsync в который входит Rsync для Windows.
cwRsync — это пакет, состоящий из графической оболочки (начиная с версии 5), утилиты Rsync и библиотеки Cygwin. Пакет cwRsync позволяет организовать удалённое резервное копирование и синхронизацию файлов между серверами Windows. Также с помощью cwRsync можно осуществлять резервное копирование файлов Unix сервера на сервер Windows и наоборот. Конфигурирование Rsync под Windows в пакете cwRsync отличается от Unix только указанием путей к каталогам для синхронизации: пути Windows стиля Диск:\путь преобразуются (по правилам cygwin) в /cygdrive/диск/путь/, например, c:\windows нужно указывать как /cygdrive/c/windows.
Создадим папку C:\RemoteCode\cwrsync и распакуем в нее содержимое пакета cwrsync_6.2.0_x64_free.zip, так что бы программа rsync.exe была доступна по пути c:\RemoteCode\cwrsync\rsync.exe
PuTTY
Загрузим PuTTY в виде putty.zip(x64) и распакуем пакет, так что бы программа PUTTY.EXE была доступна по пути c:\RemoteCode\putty\PUTTY.EXE
Шаг 3 — Настройка удаленного входа под root для SSH
Более подробнее можно почитать в руководстве Использование SSH для подключения к удаленному серверу
Для доступа пользователя root по SSH необходимо внести изменения в конфигурационный файл /etc/ssh/sshd_config в Ubuntu. Войдем в систему и установим Midnight Commander в состав которого входит редактор mcedit:
$ sudo apt-get update $ sudo apt-get install -y openssh-server mc $ sudo mcedit /etc/ssh/sshd_config
Удалим строчку #PermitRootLogin prohibit-password и заменим на строку PermitRootLogin yes. Сохраним изменение в файле кнопка «F2» и выйдем из редактора «F10».
Перезагрузим сервер sshd, чтобы изменения вступили в силу, выполним команду:
$ sudo systemctl reload ssh
Теперь можно зайти по SSH на Armbian под пользователем root используя терминал MobaXterm.
Шаг 3 — Настройка ключей аутентификации для доступа SSH в Armbian
Более подробнее можно почитать в руководстве Как настроить ключи SSH в Ubuntu 18.04.
Ключ доступа необходим для избежания хранения пароля root пользователя в открытом виде в конфигурационном файле проекта Visual Studio Code, т.к. все проекты хранятся на корпоративном Git-сервере, и утечка пароля нежелательна. Аутентификация с помощью ключей реализуется путем создания пары ключей: приватного ключа и публичного ключа.
Приватный ключ располагается на клиентском компьютере, этот ключ защищен и хранится в секрете.
Публичный ключ может передаваться любому лицу или размещаться на сервере, доступ к которому вы хотите получить.
Приватный и публичный ключ сгенерируем на плате в Armbian, затем скопируем себе приватный ключ в Windows, и удалим этот ключ в Armbian.
Используя терминал MobaXterm выполним следующую команду, для генерации пары ключей:
$ ssh-keygen -t rsa
Нажмите ENTER, чтобы принять используемые по умолчанию значения. Пароль для ключа устанавливать не требуется, нажать ENTER:
root@bananapim64:~# ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/id_rsa. Your public key has been saved in /root/.ssh/id_rsa.pub. The key fingerprint is: SHA256:xayvVRQvFSxNCiLl1zwqmtfhAn+sMcU7oDCI7SAzQJ0 root@bananapim64 The key's randomart image is: +---[RSA 2048]----+ | .. . ..o . .++. | |. E o + +.=o | |. . = B.. | |.o . = o o | |* o o . S = . | |.= o * O + | | . + * X | | . O . | | o | +----[SHA256]-----+ root@bananapim64:~#
По результату выполнения команды в каталоге ~/.ssh будут сгенерированы два ключа:
- /id_rsa.pub — публичный ключ, остается на Armbian
- /id_rsa — приватный ключ, копируем на компьютер с Windows в папку C:\RemoteCode , и удаляем этот файл в Armbian.
Теперь необходимо прописать публичный ключ в системе для доступа по SSH под пользователем root, для этого необходимо внести содержимое файла id_rsa.pub в файл ~/.ssh/authorized_keys , выполним следующие команды для этого:
$ sudo touch ~/.ssh/authorized_keys $ sudo cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys $ sudo systemctl reload ssh
Теперь необходимо проверить доступность с помощью PuTTY, который был ранее загружен в папку c:\RemoteCode\putty . Для использование ключа id_rsa необходимо его конвертировать из старого формата PEM с помощью программы c:\RemoteCode\putty\PUTTYGEN.EXE . Запускаем программу PUTTYGEN.EXE ,выбираем пункт меню «Conversions», затем нажимаем на пункт«Import key», после чего выбираем ключ c:\RemoteCode\id_rsa . Для конвертации ключа после импорта нажимаем на кнопку «Save private key». Пароль для ключа не задаем, поэтому подтверждаем сохранение ключа без пароля, отвечаем: Да. Сохраняем файл с в новом формате (.ppk), путь c:\RemoteCode\id_rsa.ppk .
Проверим подключение к Armbian с использованием ключа id_rsa.ppk при помощи программы c:\RemoteCode\putty\PLINK.EXE , запустим командную строку в Windows и выполним, где 192.168.43.225 — IP-адрес компьютера на Armbian, и введем пароль входа в учетную запись root:
C:\RemoteCode\putty\PLINK.EXE -ssh root@192.168.43.225 -pw exit
После выполнение этой команды потребуется согласиться с хэшированием ключа удаленного компьютера, если этого не сделать, то потом возникнут проблемы при удаленном подключение по SSH, после выполнения команды закрыть окно терминала.
Затем снова запустить консольное окно и выполнить команду:
C:\RemoteCode\putty\PLINK.EXE -i c:\RemoteCode\id_rsa.ppk root@192.168.43.225 -batch -T echo "hello world"
В результате мы должны увидеть: «hello world». Подключение настроено.
Шаг 4 — Создание консольного приложения и добавление логики
Создадим папку c:\RemoteCode\Projects\ , перейдем в нее и запустим командную строку. В командной стоке выполним команду: dotnet new console -o RemoteAppArm64 , где RemoteAppArm64 — название нового проекта для ARM64, проект для ARM32 будет называться — RemoteAppArm.
Теперь необходимо добавить дополнительную логику в новое приложение. Приложение будет выводить информацию о системе, в которой оно работает. Запустим Visual Studio Code и откроем папку с проектом c:\RemoteCode\Projects\RemoteAppArm64\ .
В функцию Main() поместим следующий код с вызовом исключения для проверки режима отладки, на GitHub версия проекта для ARM64 доступна по ссылке RemoteAppArm64, версия для ARM32 RemoteAppArm:
static void Main(string[] args) { Console.WriteLine("Test .NET console application!"); var str_Framework=RuntimeInformation.FrameworkDescription; var str_OSArch=RuntimeInformation.OSArchitecture.ToString(); var str_OSDesc= RuntimeInformation.OSDescription; var str_OSIdent=RuntimeInformation.RuntimeIdentifier; //output Console.WriteLine($"Версия .NET: {str_Framework}"); Console.WriteLine($"Архитектура ОС: {str_OSArch}"); Console.WriteLine($"Версия ОС: {str_OSDesc}"); Console.WriteLine($"Идентификатор ОС: {str_OSIdent}"); //error Console.WriteLine("Создание исключения"); throw new Exception("А вот и ошибочка!"); Console.WriteLine("Завершение работы программы"); }
Сохраним изменения. Далее, откроем меню Terminal => New Terminal:
Укажем команду сборки проекта: dotnet build
Затем команду для запуска приложения: dotnet run
После запуска, приложение вылетит с ошибкой.
Теперь запустим отладку, меню Run => Start Debugging, будет вызвано исключение.
В разделе Run => VARIABLES => Locals можно посмотреть текущее состояние переменных, так же при наведение указателя на переменную в коде, всплывает подсказка с содержанием переменной.
Отладка в Windows успешно работает!
Шаг 5 — Установка удаленного отладчика vsdbg в Armbian
Установка отладчика vsdbg
Установка vsdbg достаточно простая, при установке обязательно необходимо обратить внимание на архитектуру выбора отладчика, исходя из Runtime Identifiers (RIDs), для нас требуется значение: linux-arm и linux-arm64.
Выполним команду в Armbian, где linux-arm, linux-arm64 — архитектура платформы, в результате будет установлен отладчик vsdbg по пути /usr/share/vsdbg :
$ mkdir -p /usr/share/vsdbg $ curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -r linux-arm64 -v latest -l /usr/share/vsdbg
$ mkdir -p /usr/share/vsdbg $ curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -r linux-arm -v latest -l /usr/share/vsdbg
Результат выполнения:
root@bananapim64:~# mkdir -p /usr/share/vsdbg root@bananapim64:~# curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -r linux-arm64 -v latest -l /usr/share/vsdbg Info: Previous installation at '/usr/share/vsdbg' not found Info: Using vsdbg version '16.9.20122.2' Using arguments Version : 'latest' Location : '/usr/share/vsdbg' SkipDownloads : 'false' LaunchVsDbgAfter : 'false' RemoveExistingOnUpgrade : 'false' Info: Using Runtime ID 'linux-arm64' Downloading https://vsdebugger.azureedge.net/vsdbg-16-9-20122-2/vsdbg-linux-arm64.tar.gz Info: Successfully installed vsdbg at '/usr/share/vsdbg'
root@cubietruck:~# mkdir -p /usr/share/vsdbg root@cubietruck:~# curl -sSL https://aka.ms/getvsdbgsh | bash /dev/stdin -r linux-arm -v latest -l /usr/share/vsdbg Info: Previous installation at '/usr/share/vsdbg' not found Info: Using vsdbg version '16.9.20122.2' Using arguments Version : 'latest' Location : '/usr/share/vsdbg' SkipDownloads : 'false' LaunchVsDbgAfter : 'false' RemoveExistingOnUpgrade : 'false' Info: Using Runtime ID 'linux-arm' Downloading https://vsdebugger.azureedge.net/vsdbg-16-9-20122-2/vsdbg-linux-arm.tar.gz Info: Successfully installed vsdbg at '/usr/share/vsdbg'
Проверить работу отладчика и узнать параметры запуска можно командой: vsdbg -?
root@bananapim64:/usr/share/vsdbg# ./vsdbg -? Microsoft .NET Core Debugger (vsdbg) Copyright (C) Microsoft Corporation. All rights reserved. Options: --interpreter=vscode Standard input and output will contain Debug Adapter Protocol commands. This is currently the only supported interpreter mode and it is used for all Visual Studio products. --pauseEngineForDebugger Wait for a debugger to attach during startup --engineLogging[=] Enable logging to VsDbg-UI or file for the engine. --elapsedTiming Include elapsed timing in engine logging. --connection= Connect to the specified VsDbg-UI instance. --heartbeat= Send a heartbeat message with given period (in seconds). --tty=
Шаг 6 — Конфигурирование launch.json и tasks.json для удаленной отладки
Все подготовительные шаги для удаленной отладки выполнены, остается внести изменения в конфигурацию проекта. Начнем с задач, файл tasks.json содержит выполняемые задачи. Для удаленной отладки необходимо последовательно выполнить две задачи: скомпилировать проект для Linux ARM, скопировать полученные файлы на плату с Armbian, и в конечном итоге запустить удаленный отладчик.
По умолчанию в файле tasks.json определены три задачи: build, publish и watch. Эти три задачи изменять не будем, создадим новую задачу build-linux-arm на основе задачи build. И задачу copy-to-device, которая будет запускать программу rsync для копирование файлов.
На GitHub версия проекта для ARM64 доступна по ссылке RemoteAppArm64, версия для ARM32 RemoteAppArm.
Файл tasks.json, задача build-linux-arm:
{ "label": "build-linux-arm", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/RemoteAppArm64.csproj", "--configuration","Debug", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary", "-r","linux-arm64" ], "problemMatcher": "$msCompile" }
{ "label": "build-linux-arm", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/RemoteAppArm.csproj", "--configuration","Debug", "/property:GenerateFullPaths=true", "/consoleloggerparameters:NoSummary", "-r","linux-arm" ], "problemMatcher": "$msCompile" }
Добавляем параметры:
- «—configuration»,»Debug» — для включения отладки;
- «-r»,»linux-arm64″ — для компиляции под архитектуру ARM.
Файл tasks.json, задача copy-to-device
{ "label": "copy-to-device", "dependsOn":"build-linux-arm", "command": "C:\\RemoteCode\\cwrsync\\rsync.exe", "type": "process", "args": [ "--log-file=rsync.log", "--progress", "-avz" , "-e", "C:\\RemoteCode\\cwrsync\\ssh.exe -i C:\\RemoteCode\\id_rsa -o 'StrictHostKeyChecking no'", "/cygdrive/c/RemoteCode/Projects/RemoteAppArm64/bin/Debug/net5.0/linux-arm64/", "root@192.168.43.225:/root/RemoteAppArm64" ], "problemMatcher": "$msCompile" }
{ "label": "copy-to-device", "dependsOn":"build-linux-arm", "command": "C:\\RemoteCode\\cwrsync\\rsync.exe", "type": "process", "args": [ "--log-file=rsync.log", "--progress", "-avz" , "-e", "C:\\RemoteCode\\cwrsync\\ssh.exe -i C:\\RemoteCode\\id_rsa -o 'StrictHostKeyChecking no'", "/cygdrive/c/RemoteCode/Projects/RemoteAppArm/bin/Debug/net5.0/linux-arm/", "root@192.168.43.12:/root/RemoteAppArm" ], "problemMatcher": "$msCompile" }
Описание параметров:
- «dependsOn»:»build-linux-arm» — зависимость от задачи build-linux-arm, задача copy-to-device выполнится только после задачи build-linux-arm. Параметр dependsOn позволяет указывать несколько задач, но в этом случае они будут выполняться параллельно. Если требуется выполнять последовательно три задачи, то тогда следует в задачу task3 добавить параметр «dependsOn»:»task2″, а в задачу task2 параметр «dependsOn»:»task1″.
- «command»: «c:\\RemoteCode\\cwrsync\\rsync.exe» — запускаемая программа rsync.exe для копирования файлов раздел «args» — содержит список аргументов передаваемые программе rsync.exe.
- «—log-file=rsync.log» — включим журналирование в файл rsync.log, для проверки какие файлы были скопированы.
- «—progress» — визуализация процесса копирования файлов в режиме отладки
- «c:\\RemoteCode\\cwrsync\\ssh.exe -i c:\\RemoteCode\\id_rsa», — приватный ключ доступа к плате ARM на Armbian
- «/cygdrive/c/RemoteCode/Projects/RemoteAppArm64/bin/Debug/net5.0/linux-arm64/» — папка со сборками и файлами, которые необходимо скопировать на плату ARM
- «root@192.168.43.225:/root/RemoteAppArm64» — Сборки и файлы будут скопирована по пути /root/RemoteAppArm64 на плату ARM с IP-адресом 192.168.43.225. На плате ARM64 потребуется создать папку по пути /root/RemoteAppArm64 , на ARM32 — /root/RemoteAppArm !
Создание папки для запускаемых файлов на плате:
$ mkdir -p ~/RemoteAppArm64
$ mkdir -p ~/RemoteAppArm
Файл launch.json. Содержание текущего файла полностью удалим и заменим на представленный вариант:
"configurations": [ { "name": ".NET Core Remote Launch - Framework Dependent (console)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": ["./RemoteAppArm64.dll"], "cwd": "~/RemoteAppArm64", "stopAtEntry": false, "console": "internalConsole", "pipeTransport": { "pipeCwd": "${workspaceRoot}", "pipeProgram": "C:\\RemoteCode\\putty\\PLINK.EXE", "pipeArgs": [ "-i", "C:\\RemoteCode\\id_rsa.ppk", "root@192.168.43.225" ], "debuggerPath": "/usr/share/vsdbg/vsdbg --engineLogging=/var/log/vsdbg.log" }, "preLaunchTask": "copy-to-device", }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}" } ]
"configurations": [ { "name": ".NET Core Remote Launch - Framework Dependent (console)", "type": "coreclr", "request": "launch", "program": "dotnet", "args": ["./RemoteAppArm.dll"], "cwd": "~/RemoteAppArm", "stopAtEntry": false, "console": "internalConsole", "pipeTransport": { "pipeCwd": "${workspaceRoot}", "pipeProgram": "C:\\RemoteCode\\putty\\PLINK.EXE", "pipeArgs": [ "-i", "C:\\RemoteCode\\id_rsa.ppk", "root@192.168.43.12" ], "debuggerPath": "/usr/share/vsdbg/vsdbg --engineLogging=/var/log/vsdbg.log" }, "preLaunchTask": "copy-to-device", }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}" } ]
- «args»: [«./RemoteAppArm64.dll»] — запускаемая сборка RemoteAppArm64.dll
- «cwd»: «~/СonsoleAppUbuntu» — папка ~/СonsoleAppUbuntu с бинарными файлами для запуска
- «pipeTransport» — раздел для связи отладчика используя транспорт в виде программы PLINK.EXE , указываем ключ и IP-адрес подключения
- «debuggerPath»: «~/vsdbg/vsdbg» — путь с программе отладчика на плате в Armbian
- «preLaunchTask»: «copy-to-device» — до выполнения отладки необходимо выполнить задачу copy-to-device, которая соберет проект и выполнить копирование его на плату Arm под Armbian.
Сохраним все изменения, и перейдем к следующему шагу.
Шаг 7 — Проверка удаленной отладки путем вызова исключения.
Если вы все верно выполнили на предыдущих шагах, то после запуска Run => Start Debugging, произойдет компиляция проекта под архитектуру ARM32/ARM64, затем результат будет скопирован на отладочную плату ARM. Для проверки какие файлы были скопированы посмотрите файл rsync.log в папке проекта. Если в файле будет указана сборка включая /RemoteAppArm64.dll или /RemoteAppArm.dll , значит все отлично, и остается последний шаг это связь c отладчиком. После запуска приложения в Visual Studio Code вы увидите:
Удаленная отладка на ARM работает!
Проекты на GitHub — Remote-Debugging-with-VS-Code-On-Windows-to-a-ARM-using—NET
Литература
- Debug .NET Core on Linux using SSH by attaching to a process — Visual Studio Docs
- Remote Debugging On Linux Arm — omnisharp-vscode
- Debug .NET apps on Raspberry Pi — .NET IoT Libraries
- Remote debugging with VS Code on Windows to a Raspberry Pi using .NET Core on ARM — SCOTT HANSELMAN
- Attaching to remote processes — omnisharp-vscode
- Offroad Debugging of .NET Core on Linux OSX from Visual Studio — microsoft/MIEngine
- Remote Debugging of a .Net Core application with VS Code on PLCnext — KAY SUTTKUS Makers Blog
- Tasks (legacy version) — Visual Studio Code
- How to chain tasks in Visual Studio Code using only tasks.json? — stackoverflow
- Debugging — Visual Studio Code
- Visual Studio Code remote debugging of a .Net Core application running on Raspberry Pi and Ubuntu Linux — jenx.si
- Как настроить ключи SSH в Ubuntu 18.04 — DigitalOcean
- How To Use Rsync to Sync Local and Remote Directories — DigitalOcean
- Using “preLaunchTasks” and Naming a Task in Visual Studio Code — stackoverflow
- PuTTY выдает ошибку Unable to use key file — Atlex
- windows rsync from different local drive, other than c, to remote — serverfault