ZYNQのLinuxアプリをVSCodeでデバッグする方法


前編の ZYNQのLinuxアプリをVSCodeでビルドする方法 でビルド方法を紹介したので、次はデバッグできるようにしましょう。

VSCode側の設定

C++のプログラムを編集している画面でF5を押します。Launchのキーだそうですが、launchがまだ設定されていないので下のようなメニューが開きます。GDBかWindowsかはどちらでもいい(どうせ後で書き換えるから)のですが、とりあえずはC++ (GDB/LLDB) を選んでおきましょう。


launch.json が作られるので、以下のように書き換えます。

launch.json
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Remote unit test",
            "type": "cppdbg",
            "request": "launch",
            "program": "${fileDirname}\\${fileBasenameNoExtension}.elf",
            "MIMode": "gdb",
            "miDebuggerPath": "D:/Xilinx/SDK/2018.3/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin/arm-linux-gnueabihf-gdb.exe",
            "miDebuggerServerAddress": "cszmini:9999",
            "args": [],
            "cwd": "${workspaceFolder}",
            "stopAtEntry": false,
            "setupCommands": [
                {
                    "description": "gdb の再フォーマットを有効にする",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                }
            ]
        },
    ]
}

環境に応じて書き換えるべきポイントは以下の2点です。

デバッガのパスの設定

Windows上のARM用GDBのパスを指定します。

"miDebuggerPath": "D:/Xilinx/SDK/2018.3/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin/arm-linux-gnueabihf-gdb.exe",

ターゲットのアドレスの設定

ターゲットのIPアドレス(またはホスト名)と、ポート番号を指定します。

"miDebuggerServerAddress": "cszmini:9999",
"miDebuggerServerAddress": "192.168.1.4:12345",

のようにします。

とりあえず実行

F5を押すとlaunchに書かれたコマンドが実行されます。この時点ではターゲットボードでLinuxは動いていないのでエラーダイアログが出ます。

Linux側の準備

apt install gdbserverで、gdbserverをインストールしておきます。
Linuxのシェル上で、

# gdbserver localhost:9999 ./test.elf

と入力します。上のコマンドはgdbserverを起動して、ポート9999で待ち受けてtest.elfをアタッチします。

VSCodeでいざLaunch

いよいよVSCodeでF5を押してデバッグ開始です。

VSCode上でgdbが起動し、リモートのターゲットボードに接続されます。ステップ実行や変数の参照ができ、デバッグできるようになります。

対するLinux側ではプログラムがリモート操作されてステップ実行されています。

プログラムが終了するか、VSCodeで □ を押すと終了します。

もっと便利にしたい

ユーザ作成のライブラリを使う

ユーザが作成したライブラリを使うには、プロジェクトのフォルダにライブラリを入れ、tasks.jsonのargsに-Lと-lオプションを追加します。次の設定ではcszapiというライブラリが使われるようになります。

"args": [
  "-g",
  "${file}",
  "-L${workspaceFolder}",
  "-lcszapi",
  "-o",
  "${fileDirname}\\${fileBasenameNoExtension}.elf"
],

また、ヘッダファイルのIntelliSenseが動くようにするにはc_cpp_properties.jsonを編集して、インクルードパスを通すようにします。


これでユーザ作成のライブラリが使えるようになります。

ビルドが終わったら自動的にターゲットにコピーしたい

クロスコンパイルなので、出来上がったプログラムはターゲットボード上にコピーしなければなりません。毎回毎回scpや、エクスプローラでCTRL+C,CTRL+Vするのも面倒なので、tasks.jsonに自動化の処理を書いてあげましょう。
tasks.jsonは、"tasks": [で複数のタスクを登録できるようになっていますが、"group": { "kind": "build", "isDefault": true}が付いているタスクが CTRL*SHIFT+B で実行されるようになっています。
また、各タスクには "dependsOn": というパラメータが設定できて、これを使うとタスクの依存関係を設定できます。

つまり、ターゲットボード上にファイルを転送するというDeployというisDefaultなタスクを作り、DeployはBuildに依存するようにすれば、CTRL+SHIFT+Bでビルドとファイルコピーが可能になります。

(自動Deploy版)tasks.json
{
    // tasks.json 形式の詳細についての資料は、
    // https://go.microsoft.com/fwlink/?LinkId=733558 をご覧ください
    "version": "2.0.0",
    "tasks": [
        {
            "label": "Deploy",
            "type": "shell",
            "command": "cp",
            "args": [
                "${fileDirname}\\${fileBasenameNoExtension}.elf",
                "\\\\cszmini\\Share"
            ],
            "problemMatcher": [],
            "dependsOn": "Build by ARM g++",
            "group": {
                "kind": "build",
                "isDefault": true
            }
        },
        {
            "type": "shell",
            "label": "Build by ARM g++",
            "command": "D:/Xilinx/SDK/2018.3/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin/arm-linux-gnueabihf-g++.exe",
            "args": [
                "-g",
                "${file}",
                "-L${workspaceFolder}",
                "-lcszapi",
                "-o",
                "${fileDirname}\\${fileBasenameNoExtension}.elf"
            ],
            "options": {
                "cwd": "D:/Xilinx/SDK/2019.1/gnu/aarch32/nt/gcc-arm-linux-gnueabi/bin"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": "build"
        }
    ]
}

dependsOnの参照はlabelを使って行われるようです。

F5でビルドとgdbの起動ができるようにしたい

launch.jsonのconfigurationsに登録されたタスクに、"preLaunchTask":というパラメータを設定しておけば、このタスクが実行される前に実行すべきタスクを指定できます。
したがって、"preLaunchTask": "Deploy"と書けば、GDB接続の前にDeployタスクが実行されて、ビルドとファイルの転送が行われます。

しかし、Linux上ではgdbserverプロセスを走らせなければならないのですが、このgdbserverを起動する前にtest.elfはターゲットボード上になければなりません。また、gdbserverを停止するにはリモートで接続してプログラムが終了するかkillするしかなく、ちょっと面倒です。

結論を言うと、preLaunchTaskを書いてビルドを自動実行するのではなく、リモートデバッグの場合は「ビルド&ファイル転送」と「デバッガ起動」は分けたほうがいいです。

少々面倒ですが、プログラムを書いてからそれを実行するまでに

  • CTRL+SHIFT+Bでビルド&実行ファイル転送
  • Linux上でgdbserver起動
  • F5でデバッグ開始

と、3ステップ必要です。gdbは巨大なプログラムだから、ちゃんと調べればファイル転送できるのかな?

GLIBCのバージョンが合わないとどうなる?

GLIBCのバージョンが合わない場合、つまりUbuntu 14のシステムをXSDK 2019.1に入っているarm-linux-gnueabihf-gdbでデバッグしようとすると、

readchar: Got EOF

というエラーが出てGDBが接続できません。

実際にやってみると

こうなります。

まとめ

VSCodeでZYNQのLinuxで動くARMのプログラムが書けて、デバッグまでできるようになりました。
これでXSDKを卒業できるかな!?