みつきんのメモ

組み込みエンジニアです。Interface誌で「Yocto Projectではじめる 組み込みLinux開発入門」連載中

Raspbery Pi Pico Ubuntu20.04上にSDK環境構築

はじめに

ラズベリーパイPico(RPi Pico)が入手できた。

開発環境としてラズパイ4が必要とされているが実際のところそうでもなさそうなのでUbuntu 20.04に開発環境(SDK)を構築しLチカを動かしてみる。

pico_setup.shについて

C/C++の開発環境を構築するためにpico_setup.shというものが提供されている。

試しにUbuntu 20.04で動かしてみる。

$ wget https://raw.githubusercontent.com/raspberrypi/pico-setup/master/pico_setup.sh
$ chmod +x ./pico_setup.sh
$ ./pico_setup.sh

下記のようにエラーになる。

vscode.deb                                         100%[===============================================================================================================>]  57.86M  8.89MB/s    in 6.6s

2021-02-04 13:19:47 (8.82 MB/s) - `vscode.deb' へ保存完了 [60667760/60667760]

パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています
状態情報を読み取っています... 完了
注意、'./vscode.deb' の代わりに 'code:armhf' を選択します
インストールすることができないパッケージがありました。おそらく、あり得
ない状況を要求したか、(不安定版ディストリビューションを使用しているの
であれば) 必要なパッケージがまだ作成されていなかったり Incoming から移
動されていないことが考えられます。
以下の情報がこの問題を解決するために役立つかもしれません:

以下のパッケージには満たせない依存関係があります:
 code:armhf : 依存: libnss3:armhf (>= 2:3.26) しかし、インストールすることができません
              依存: apt:armhf しかし、インストールすることができません
              依存: libxkbfile1:armhf しかし、インストールすることができません
              依存: libsecret-1-0:armhf しかし、インストールすることができません
              依存: libgtk-3-0:armhf (>= 3.10.0) しかし、インストールすることができません
              依存: libxss1:armhf しかし、インストールすることができません
              依存: libgbm1:armhf しかし、インストールすることができません
E: 問題を解決することができません。壊れた変更禁止パッケージがあります。

しかしこれはARM環境向けのVSCodeをインストールしようとして依存関係が解決できないというエラーなので、エディタ以外のSDKのセットアップ自体は正しく終了している。

スクリプトを確認したところ、VSCodeのインストールのあとはラズパイのUARTの有効化処理なので、こちらも必ず実行しなければならないものではない。

PCを再起動するか下記のコマンドで環境変数を読み込みすると、SDKが使用できるようになる。

$ source ~/.bashrc

RPi PicoをPCと接続

ボード側のBOOTSELボタンを押しながら、USBケーブルでPCと接続すると自動的に/media/${USER}/RPI-RP2にマウントされる。

プログラムの書き込み

拡張子がuf2のファイルをRPI-RP2に書き込むと、ボードへの転送からリセットまで行われる。

つまり下記のような流れで処理が行われる。

  1. RPI-RP2へ書き込み
  2. PCからRPi Picoへプログラムを転送
  3. RPi Picoのリセット
  4. 書き込まれたプログラムが開始

Blink(Lチカ)

${PICO_EXAMPLES_PATH}/build/blinkblink.uf2が生成されているのでこれを先ほどマウントされたRPI-RP2にコピーする。

$ cp ${PICO_EXAMPLES_PATH}/build/blink/blink.uf2 /media/${USER}/RPI-RP2

オンボードの緑色のLEDがチカチカするようになる。

Hello World(USB-UART版)

${PICO_EXAMPLES_PATH}/build/hello_world/usbhello_usb.uf2が生成されているのでこれを先ほどマウントされたRPI-RP2にコピーする。

$ cp ${PICO_EXAMPLES_PATH}/build/hello_world/usb/hello_usb.uf2 /media/${USER}/RPI-RP2

ボード上でこのプログラムが実行されるとPCで/dev/ttyACMxというデバイスが検出されるようになる。

$ dmesg | grep ACM
3085:[166271.475390] cdc_acm 1-1:1.0: ttyACM0: USB ACM device

筆者の実行環境では/dev/ttyACM0が認識された。これをminicomなどの端末で開く。

$ minicom -D /dev/ttyACM0

すると次のように表示される。

minicom へようこそ 2.7.1

オプション: I18n 
コンパイルされた日時は:  Dec 23 2019, 02:06:26.
ポート /dev/ttyACM0, 14:59:27

CTRL-@ Z を押すと、説明画面になります。

Hello, world!
Hello, world!
Hello, world!
... (省略) ...

まとめ

Ubuntuでも問題なくRPi Pico向けのSDKを構築し、プログラムをボードに書き込むことができる。

pico_setup.shはVSCodeのインストールでエラーになるが、SDKのセットアップ自体は完了しているので問題はない。

CMakeでGoogleTest(gtest_add_tests vs gtest_discover_tests)

はじめに

CMakeにはCTestというテストランナーがある。

CTestはテスト用の実行ファイル(テストバイナリ)が1つのテストとして認識される。

GoogleTest(GTest)のようなテストフレームワークの場合、1つのテストバイナリに複数のテストケースが含まれることが常となる。

そこでCMakeにはGTestのテストが一つのCTestとして扱われるようにするためのサポート機能がある。

テスト対象のプログラム

CMake C++でユニットテスト入門(初級編)で作成した、なんの役にも立たないテストプログラムを使用する。

使用するのは下記のファイル。

  • hello.cpp
  • hello.h

GTest

aptでインストール

ExternalProjectとかいろいろあるが、まずそれ以外のことを試したいので、googletestのパッケージをaptでインストールする。

$ sudo apt install -y googletest

ちなみに作業環境はUbuntu 20.04

テスト

このサンプルプログラムに対するGoogleTestを以下のように作ってみる。

#include "gtest/gtest.h"
#include "hello.h"
#include <stdexcept>

Hello h;

/* This block will uncomment after.
TEST(HelloTest, NullPtr) {
    EXPECT_THROW(h.hello(nullptr), std::runtime_error);
}
*/

TEST(HelloTest, default_param) {
    EXPECT_EQ(h.hello(), "empty");    
}

TEST(HellsoTest, empty_string) {
    EXPECT_EQ(h.hello(""),  "empty");
}

TEST(HelloTest, normal_case) {
    EXPECT_EQ(h.hello("John Doe"), "Hello John Doe");
}

NullPtrのテストはあえてコメントアウトしておく。

CMakeLists.txt

まずは、テストランナーを使用せずに、GTestをリンクしたテストバイナリを生成する。

cmake_minimum_required(VERSION 3.10)

# To use the googletest
find_package(GTest REQUIRED)

# Add the executable for the testcase which is using googletest
add_executable(test_hello test_hello.cpp hello.cpp)
target_link_libraries(test_hello GTest::GTest GTest::Main)

テストバイナリのビルド

この時点でディレクトリ構成は次のようになっている。

.
├── CMakeLists.txt
├── hello.cpp
├── hello.h
└── test_hello.cpp

次のようにしてビルドする。

$ mkdir build && cd build
$ cmake ..
# make -j $(nproc)

テストバイナリの実行

$ ./test_hello 
Running main() from /home/mickey/work/trash/googletest-release-1.10.0/googletest/src/gtest_main.cc
[==========] Running 3 tests from 1 test suite.
[----------] Global test environment set-up.
[----------] 3 tests from HelloTest
[ RUN      ] HelloTest.default_param
[       OK ] HelloTest.default_param (0 ms)
[ RUN      ] HelloTest.empty_string
[       OK ] HelloTest.empty_string (0 ms)
[ RUN      ] HelloTest.normal_case
[       OK ] HelloTest.normal_case (0 ms)
[----------] 3 tests from HelloTest (0 ms total)

[----------] Global test environment tear-down
[==========] 3 tests from 1 test suite ran. (0 ms total)
[  PASSED  ] 3 tests.

3つのテストがPASSしている。

ここまでで下準備完了。

gtest_add_tests

gtest_add_testsを試す。この機能はCMake 3.1の時点で追加されている

今の形になったのは3.9の頃らしい。

CMakeLists.txtの修正

次のようにしてgtest_add_testsを使用してみる。

cmake_minimum_required(VERSION 3.10)
project(hello)

# Enable the testing features.
enable_testing()

# To use the googletest
find_package(GTest REQUIRED)

# Enable the GoogleTest integration.
include(GoogleTest)

# Add the executable for the testcase which is using googletest
add_executable(test_hello test_hello.cpp hello.cpp)
target_link_libraries(test_hello GTest::GTest GTest::Main)

# Add the test case use the old feature.
gtest_add_tests(TARGET test_hello)

ctestの実行

ctestでテストを実行してみる。

$ ctest
Test project /home/mickey/work/c_lang/gtest/build
    Start 1: HelloTest.NullPtr
1/4 Test #1: HelloTest.NullPtr ................   Passed    0.00 sec
    Start 2: HelloTest.default_param
2/4 Test #2: HelloTest.default_param ..........   Passed    0.00 sec
    Start 3: HelloTest.empty_string
3/4 Test #3: HelloTest.empty_string ...........   Passed    0.00 sec
    Start 4: HelloTest.normal_case
4/4 Test #4: HelloTest.normal_case ............   Passed    0.00 sec

100% tests passed, 0 tests failed out of 4

Total Test time (real) =   0.01 sec

コメントアウトされているはずのNullPtrテストが実行されている。

gtest_add_testsの弱点

一つのテストバイナリを指定すると、GTestのテストケースに対応してGTestを実行してくれるが、下記の問題点をはらんでいる。

  1. テスト抽出のタイミングがcmake実行時
  2. 文字列ベースでテストを抽出するため、コメントアウトされていることを区別しない

つまり、Cコンパイラは当然のように知っているコメント行や#if 0などは感知されないため、TESTマクロの行を愚直に検出してしまう。

また、テストの検出がcmake実行時であるため、テストケースを追加したり削除したりする場合、cmakeから実行し直す必要がある。

gtest_discover_tests

gtest_discover_testsを試す。この機能は3.10で追加されている

CMakeLists.txtの修正

次のようにしてgtest_discover_testsを使用してみる。

cmake_minimum_required(VERSION 3.10)
project(hello)

# Enable the testing features.
enable_testing()

# To use the googletest
find_package(GTest REQUIRED)

# Enable the GoogleTest integration.
include(GoogleTest)

# Add the executable for the testcase which is using googletest
add_executable(test_hello test_hello.cpp hello.cpp)
target_link_libraries(test_hello GTest::GTest GTest::Main)

# Add the test case use the gtest feature.
gtest_discover_tests(test_hello)

ctestの実行

ctestでテストを実行してみる。

$ ctest
Test project /home/mickey/work/c_lang/gtest/build
    Start 1: HelloTest.default_param
1/3 Test #1: HelloTest.default_param ..........   Passed    0.00 sec
    Start 2: HelloTest.empty_string
2/3 Test #2: HelloTest.empty_string ...........   Passed    0.00 sec
    Start 3: HelloTest.normal_case
3/3 Test #3: HelloTest.normal_case ............   Passed    0.00 sec

100% tests passed, 0 tests failed out of 3

Total Test time (real) =   0.01 sec

コメントアウトされているNullPtrテストは実行されていない。

NullPtrコメントの削除

NullPtrのテストをアンコメントして実行してみる。

$ make
$ ctest
Test project /home/mickey/work/c_lang/gtest/build
    Start 1: HelloTest.NullPtr
1/4 Test #1: HelloTest.NullPtr ................   Passed    0.00 sec
    Start 2: HelloTest.default_param
2/4 Test #2: HelloTest.default_param ..........   Passed    0.00 sec
    Start 3: HelloTest.empty_string
3/4 Test #3: HelloTest.empty_string ...........   Passed    0.00 sec
    Start 4: HelloTest.normal_case
4/4 Test #4: HelloTest.normal_case ............   Passed    0.00 sec

100% tests passed, 0 tests failed out of 4

Total Test time (real) =   0.01 sec

cmakeせずに、makeでテストバイナリをビルドし直すだけでNullPtrのテストも実行されるようになった。

gtest_discover_testsの強み

  1. テスト抽出のタイミングがビルド時
  2. コンパイル時の情報をもとに抽出するため、コメントアウトされたテストなどは抽出されない

つまり、コメントや#if 0などははプリプロセッサコンパイラのルールに従って適切に処理されるため、明示的に実行したくないテストに関して実行されてしまうことがない。

また、テストの抽出がビルド時であるため、テストケースを追加したり削除したりする場合、cmakeから実行する必要がない。

まとめ

CMakeにはCTestとGoogleTestをうまく強調するための機能が提供されている。

gtest_add_testsgtest_discover_testsがあるが、 問答無用でgtest_discover_testsの方を使うべき

日本語の情報で検索するとgtest_add_testsが出てくる場合が多いので注意が必要。

参考

CMake C++でユニットテスト入門(初級編)

はじめに

C++でコードを書く時にユニットテストも書きたい。

CMakeにはテストを実行するための仕組みがある。

CTest

CTestはいわゆるテストランナーで、CMakeがサポートするテストランナーの中では一番シンプルなもの。 指定されたものを実行するだけのシンプルなもの。(CMakeに他にテストランナーはなさそう)

CTestに関してはCMake: CTestが詳しい。

テストプログラム

簡単なテストプログラムを作成して実際に動かしてみる。

まずはCTestも何も使用しない単純なプロジェクトを作る。

ファイルの構成は次のようなもの。

├── CMakeLists.txt
├── hello.cpp
├── hello.h
└── main.cpp

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(hello)

add_executable(${PROJECT_NAME} main.cpp hello.cpp)

hello.h

#ifndef HELLO_H
#define HELLO_H

#include <string>

class Hello {
public:
    std::string hello(const char* const p = "") const;
};

#endif //HELLO_H

hello.cpp

#include "hello.h"
#include <sstream>
#include <stdexcept>

std::string Hello::hello(const char *const p) const {
    if (p == nullptr)
        throw std::runtime_error("This method can not accept the nullptr.");
    if (*p == '\0')
        return "empty";
    std::stringstream ss;
    ss << "Hello " << p;
    return ss.str();
}

main.cpp

#include "hello.h"
#include <iostream>
#include <cassert>

int main() {
    Hello h;
    //assert(h.hello(nullptr) == ""); //This will be error, therefore commented out.
    assert(h.hello() == "empty");
    assert(h.hello("") == "empty");
    assert(h.hello("John Doe") == "Hello John Doe");
    std::cout << "done" << std::endl;
    return 0;
}

ビルドと実行

下記のコマンドでビルドする。

$ mkdir build && cd build
$ cmake ..
$ make -j $(nproc)

実行してみる。

$ ./hello 
done

問題なく終了する。

CTestを使ってみる

CMakeLists.txt

下記のように修正する。

cmake_minimum_required(VERSION 3.10)
project(hello)

add_executable(${PROJECT_NAME} main.cpp hello.cpp)

# Enable the testing features.
enable_testing()

# Add the test which is simply run the application.
add_test(NAME run_test COMMAND ${PROJECT_NAME})

enable_testing()でテスト機能を有効化して、CTestで実行可能なadd_testでテストを定義する。

CTestは単純なランナーなので、実はどんな実行ファイルでも指定可能となっている。 ここではサンプルプロジェクトとして作ったhelloが実行されるように指定した。

テストの実行

CTestで追加されたテストはctestコマンドやmake testで実行可能となっている。

下記はctestで実行した例。

$ ctest
Test project /home/mickey/work/c_lang/ctest/build
    Start 1: run_test
1/1 Test #1: run_test .........................   Passed    0.00 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) =   0.01 sec

100% tests passedとなっており、成功したということになっている。

成功/失敗の判定

前述の通りテストは成功したことになっている。CTestではどんな実行ファイルでも指定できると前述したが、 成功/失敗の判断はどの様にしているのか。 これは単純にプログラム(main関数)からの戻り値が0かどうかだけで判断している。

プログラムが異常終了した場合やmainから0以外が返されると失敗になる。

試しにmain.cppの戻り値を0にして実行してみる。

diff --git a/main.cpp b/main.cpp
index 2d870a8..1cc8fe7 100644
--- a/main.cpp
+++ b/main.cpp
@@ -9,5 +9,5 @@ int main() {
     assert(h.hello("") == "empty");
     assert(h.hello("John Doe") == "Hello John Doe");
     std::cout << "done" << std::endl;
-    return 0;
+    return 1;
 }

実行結果は下記の通り。

$ ctest
Test project /home/mickey/work/c_lang/ctest/build
    Start 1: run_test
1/1 Test #1: run_test .........................***Failed    0.00 sec

0% tests passed, 1 tests failed out of 1

Total Test time (real) =   0.01 sec

The following tests FAILED:
      1 - run_test (Failed)
Errors while running CTest

0% tests passedとなって、きちんと失敗することが確認できた。

実用的なテスト

さすがにassertを挟んだだけの、しかもprojectのメインプログラムを指定するというのは、実用的な例とは言えないので、 メインプログラムとは別にきちんとしたユニットテストを追加してみる。

acutest

今回はヘッダファイルのみで構成されており、導入が簡単なacutestを使ってみる。

導入方法はヘッダファイルをダウンロードして、テストのソースコードからインクルードするだけ。

$ wget https://raw.githubusercontent.com/mity/acutest/master/include/acutest.h

再配布について

acutestの再配布の扱いについては、ヘッダに必要なことは全部記述してあるので、そのまま使う分にはヘッダーを追加するだけで問題ないとのこと。

Q: Do I need to distribute file README.md and/or LICENSE.md?

A: No. The header acutest.h includes URL to our repo, copyright note and the MIT license terms inside of it. As long as you leave those intact, we are completely fine if you only add the header into your project. After all, the simple use and all-in-one-header nature of it is our primary aim.

テストの追加

test_hello.cppとして下記の内容で作成する。

#include "acutest.h"
#include "hello.h"
#include <stdexcept>

void test_hello() {
    Hello h;
    TEST_EXCEPTION(h.hello(nullptr), std::runtime_error);
    TEST_ASSERT(h.hello() == "empty");
    TEST_ASSERT(h.hello("") == "empty");
    TEST_ASSERT(h.hello("John Doe") == "Hello John Doe");
}

TEST_LIST = {
   { "hello", test_hello },
   { nullptr, nullptr }
};

main関数はacutest側で用意してくれる。

テスト用のソースコードが複数になるようなケースでは下記のようにする。

#define TEST_NO_MAIN
#include "acutest.h"

CMakeLists.txt

cmake_minimum_required(VERSION 3.10)
project(hello)

add_executable(${PROJECT_NAME} main.cpp hello.cpp)

# Enable the testing features.
enable_testing()

# Add the executable for the acutest case.
add_executable(test_hello test_hello.cpp hello.cpp)

# Add the test which is run the acutest case.
add_test(NAME run_test COMMAND test_hello)

テスト用の実行ファイルを定義して、add_testでそのコマンドを実行するように定義し直す。

テストの実行

ctestを実行する。

$ mkdir build && cd build
$ cmake ..
$ make -j $(nproc)
$ ctest
Test project /home/mickey/work/c_lang/ctest/build
    Start 1: run_test
1/1 Test #1: run_test .........................   Passed    0.00 sec

100% tests passed, 0 tests failed out of 1

Total Test time (real) =   0.01 sec

100% tests passedとなり、テストは成功している。

まとめ

CMakeはCTestというテストランナーを持っている。単純なランナーであるため使用するテストフレームワークは限定されない。 もっというと実行されるテスト用の実行ファイルはテストフレームワークで作成されたものである必要もない。

さまざまなテストフレームワークが利用可能という点でこれは利点ではあるが、 逆にいうとテストが失敗した際にどのテストが失敗したかなどの、テスト結果に対する表示がおおざっぱだというデメリットもある。

CMakeはGoogleTestに特化したテストランナーの機能GoogleTestをCTestと組み合わせて使用するための機能も持っているので、GoogleTestを使用する場合にはそちらを使用すると良さそう。

PlatfromIOでArduino UNO + 2.4 TFT LCD Shield(ILI9341)を動かす

はじめに

SPI接続のILI9341のLCDが欲しくてこれを買ったがSPIではなく届いてみると8ビットパラレルのモジュールだったので絶望した。

きっとポチッとしたときは眠たかったんだと思う。

しかし買ってしまったものは仕方ないので動かしてみることにした。

PlatformIOのインストール

pipでインストールできる。

$ pip3 install platformio --user

ライブラリ

2.4 TFT LCD Shieldを駆動するためのライブラリはAdafruit-TFT-LCDを使う。

ライブラリのインストールはpio lib installで行う。依存関係もこのコマンドで解決される。

環境構築

ベース環境の構築

PlatformIOで環境を構築する。ライブラリのインストールもここで行う。

$ mkdir uno_r3
$ cd uno_r3
$ pio init -b uno
$ pio lib install "adafruit/Adafruit-TFT-LCD"

platformio.iniの修正

依存関係を追加しないとビルドが失敗するので、下記のようにする。

[env:uno]
platform = atmelavr
board = uno
framework = arduino
lib_deps = 
    adafruit/Adafruit-TFT-LCD@0.0.0-alpha+sha.9b701b6d5a
    adafruit/Adafruit BusIO
    Wire
    SPI

サンプルソースのコピー

graphicstestのサンプルをビルドするためソースをsrcディレクトリにコピーする。

$ cp .pio/libdeps/uno/Adafruit-TFT-LCD/examples/graphicstest/graphicstest.ino ./src

修正

このシールドはパチモンなので、チップのデバイスIDチェックの結果が残念なことになる。

そのため、IDを0x9341(ILI9341)に固定する必要がある。

--- graphicstest.ino~    2020-10-16 09:23:26.419496756 +0900
+++ graphicstest.ino  2020-10-16 09:28:54.187508970 +0900
@@ -57,7 +57,7 @@ void setup(void) {
 
   tft.reset();
 
-  uint16_t identifier = tft.readID();
+  uint16_t identifier = 0x9341;
 
   if(identifier == 0x9325) {
     Serial.println(F("Found ILI9325 LCD driver"));

ビルドおよび書き込み

次のコマンドでビルドする。

$ pio run

ターゲットへの書き込みは次のようにする。

$ pio run -t upload

まとめ

ILI9341(8ビットパラレル)のLCD ShieldをArduino UNOで試してみた。

パチモンのため、デバイスIDのチェックが機能しないためIDを固定する必要があるので注意。

それ以外は問題なく動いているようだ。

参考

YoctoProject CVE Checkについて調べる

はじめに

bitbakeではビルドするパッケージの脆弱性CVEによってチェックを行う機能がある。

使い方

local.confに下記を追加する。

INHERIT += "cve-check"

すると、各パッケージのタスクにdo_cve_checkが追加される。

bitbakeを実行すると、このタスクが実行され、未対策の脆弱性があった場合には警告として表示される。

実行例

この機能を実装しているcve-check.bbclassに記載されているコマンドの実行例を示す。

$ bitbake -c cve_check openssl
$ bitbake core-image-sato
$ bitbake -k -c cve_check universe

出力結果

CVEチェックの結果はログファイルとして保存される。

各パッケージに対する出力結果は${WORK_DIR}/temp/cve.logとして保存される。

イメージに対する出力結果はbuild/tmp/deploy/images/イメージ名.cveとして保存される。ラスベリーパイ4向けのcore-image-baseの場合はbuild/tmp/deploy/images/raspberrypi4/core-image-base-raspberrypi4.cveの様になる。

出力結果の例を下記に示す。

PACKAGE NAME: libvorbis
PACKAGE VERSION: 1.3.6
CVE: CVE-2020-20412
CVE STATUS: Patched
CVE SUMMARY: lib/codebook.c in libvorbis before 1.3.6, as used in StepMania 5.0.12 and other products, has insufficient array bounds checki\
ng via a crafted OGG file.
CVSS v2 BASE SCORE: 4.3
CVSS v3 BASE SCORE: 6.5
VECTOR: NETWORK
MORE INFORMATION: https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2020-20412

PACKAGE NAME: pixman
PACKAGE VERSION: 1_0.38.4
CVE: CVE-2013-0800
CVE STATUS: Unpatched
CVE SUMMARY: Integer signedness error in the pixman_fill_sse2 function in pixman-sse2.c in Pixman, as distributed with Cairo and used in Mo\
zilla Firefox before 20.0, Firefox ESR 17.x before 17.0.5, Thunderbird before 17.0.5, Thunderbird ESR 17.x before 17.0.5, SeaMonkey before \
2.17, and other products, allows remote attackers to execute arbitrary code via crafted values that trigger attempted use of a (1) negative\
 box boundary or (2) negative box size, leading to an out-of-bounds write operation.
CVSS v2 BASE SCORE: 6.8
CVSS v3 BASE SCORE: 0.0
VECTOR: NETWORK
MORE INFORMATION: https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2013-0800

適用済みのものはPatched、そうじゃないものはUnpatchedとなっている。

検出の仕組み

cvecheckerがベースとなっているらしい。

これは、オンラインからCVEのデータベースを取得し、SQLite3で脆弱性情報に対してマッチするものがあるかを検索するというもの。

プロダクト名とバージョンの組み合わせによって関係ある脆弱性があるかどうかを簡易的に判定するだけなので、このチェックによって全ての脆弱性に関する危険性を排除できるわけではないとされている。

Yoctoでの実装

do_cve_check

python do_cve_check () {
    """
    Check recipe for patched and unpatched CVEs
    """

    if os.path.exists(d.getVar("CVE_CHECK_DB_FILE")):
        try:
            patched_cves = get_patches_cves(d)
        except FileNotFoundError:
            bb.fatal("Failure in searching patches")
        whitelisted, patched, unpatched = check_cves(d, patched_cves)
        if patched or unpatched:
            cve_data = get_cve_info(d, patched + unpatched)
            cve_write_data(d, patched, unpatched, whitelisted, cve_data)
    else:
        bb.note("No CVE database found, skipping CVE check")

}

get_patches_cves(d)check_cves(d, patched_cves)によってチェックしているようだ。

get_patches_cves

レシピに含まれるファイル名にCVE-IDが含まれているか、ファイル中の文字列にCVE-IDが含まれているものは対策済みとして扱うようにしている。 どちらも正規表現による文字列マッチとなっている。

ファイル名は下記の条件でマッチしている。CVE-1234-211432やみたいなものが引っかかるようになっている。CVEに対する大文字/小文字の混在もOKのようだ。

cve_file_name_match = re.compile(".*([Cc][Vv][Ee]\-\d{4}\-\d+)")

ファイル中の文字列は下記の条件でマッチしている。

cve_match = re.compile("CVE:( CVE\-\d{4}\-\d+)+")

こちらはCVE: CVE-2020-13362のようなものが引っかかるようになっている。

check_cves

こちらはホワイトリストに含まれていれば無視し、それ以外のものをCVE_CHECK_DB_FILEで指定されているデータベースで検索する。

ホワイトリストには下記の2種類がある。

  • CVE_CHECK_WHITELIST(CVE-ID)
  • CVE_CHECK_PN_WHITELIST(パッケージ名)

CVE_CHECK_WHITELISTは特定のCVEを無視したい場合にCVE-IDを指定する。 CVE_CHECK_PN_WHITELISTは特定のパッケージに関するCVEを無視したい場合にパッケージ名を指定する。

データベースの検索条件はCVE_PRODUCTCVE_VERSIONの組み合わせとなっている。

CVE_PRODUCTはデフォルトではBPNが使用される。CVEデータベース上のプロダクト名とBPNがマッチしない場合や、1つのレシピで複数のパッケージを生成する場合などは明示的に設定する必要がある。

flacのレシピでは下記のように設定されている。

CVE_PRODUCT = "libflac flac"

CVE_VERSIONはデフォルトではPVが使用される様になっている。こちらもCVEデータベース上のバージョン表記と一致しない場合などは明示的に設定する必要がある。

CVE_CHECK_DB_FILEはSQLite3のデータベースとなっており、筆者が実行した環境では「"${DL_DIR}/CVE_CHECK/nvdcve_1.1.db"」が指定されている。 このファイルはcve-update-db-nativeによりダウンロードされる。

CVE対策をレシピに含める場合

get_patches_cvesのロジックにより、CVE対策としてパッチを作成する場合、ファイル名を下記のようにするか、

CVE-2020-12723.patch

コメント中に下記のような文字列を含めるかすると、対策済みとして扱われるようになる。

CVE: CVE-2020-13362

まとめ

bitbakeではCVE情報によって脆弱性をチェックする機能がある。

基本的にはプロダクト名とバージョンによって、関係あるCVEが対策済みかどうかを判断する。 レシピ中のSRC_URIで指定されたファイル名やコメントなどによって、データベース上では未対策のCVEでも、対策済みと扱うことができるようになっている。

また、特定のCVEや特定のパッケージをホワイトリストに追加することによって、未対策のものであっても問題ないものに関しては明示的にチェックしないようにすることもできる。

基本的には「プロダクト名+バージョン」や、ファイル名やコメントなどによる簡易的なチェックであり、 これらのチェック機能によって全ての脆弱性を網羅できるわけではないが、これで得られた情報をもとに脆弱性に対する方針を立てることができる。

Docker入門

はじめに

Dockerに入門してみる。

作業環境はUbuntu 20.04

環境設定

aptでも提供されているが、Install Docker Engine on Ubuntuの手順でインストールしたほうがトラブルは少なそう。

apt版のアンインストール

$ sudo apt purge docker docker-engine docker.io containerd runc

インストール

必要なパッケージのインストール

$ sudo apt install \
     apt-transport-https \
     ca-certificates \
     curl \
     gnupg-agent \
     software-properties-common

キーの設定

$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

リポジトリの設定

$ sudo add-apt-repository \
    "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
    $(lsb_release -cs) \
    stable"

インストール

$ sudo apt update
$ sudo apt install docker-ce docker-ce-cli containerd.io

dockerグループへの追加

dockerコマンドをsudoなしで実行できるようにする

$ sudo gpasswd -a ${USER} docker

一度ログアウトして、再ログインすると有効化される。

最初のコンテナ

いろいろなチュートリアルで、最初にDockerを実行するのは下記のようなコマンド。

$ docker run hello-world

docker runがいろいろなことを自動で実行するため便利だが、筆者のような初心者にはなにが起こっているか分かりづらい。

何もない状態からdocker runを実行する場合下記のようなことが行われる。

  1. イメージの取得
  2. イメージからコンテナを作成
  3. コンテナの起動
  4. コンテナへのアタッチ

dockerのコマンドでいうと下記のような感じ

  1. docker pull / docker build
  2. docker create
  3. docker start
  4. docker attach

イメージとコンテナ

Dockerといえばコンテナ。実際コンテナを作ってその上で作業するためのツールだが、コンテナを実行するためにはイメージが必要となる。

f:id:mickey_happygolucky:20210109161749p:plain
イメージとコンテナ

イメージとコンテナにはそれぞれIDが割り振られる(イメージIDとコンテナID)。これらは同じような16進数のハッシュ値なので、最初は混乱するがきちんと別物だということを理解しておく。

イメージはコンテナの金型のようなもので、1つのイメージからいくつもコンテナを実行できる。

f:id:mickey_happygolucky:20210109161837p:plain
複数のコンテナ

イメージIDは1つだが、コンテナIDは2つ必要になる。

コンテナでいろいろ作業して、使い終わったコンテナを削除することもできる。

f:id:mickey_happygolucky:20210109161921p:plain
コンテナの削除

通常コンテナで作業した内容はイメージには反映されない。

ネット上ではコンテナを実行する際下記のようなコマンドを示されることが多い。

$ docker run -it --rm ubuntu

自動的にbashが実行されubuntuが実行されたコンテナにログインされた状態になるため、ここでいろいろと作業することになる。

しかし--rmによってコンテナからexitした時点でコンテナが削除されてしまう。コンテナでの作業内容は基本的にイメージに反映されないため、作業した内容が失われてしまうことになる。

Dockerでの作業状態

Dockerで作業する場合に把握しておくべき状態をまとめる。便宜上実行状態にA-Eで名前をつけることにする。

状態 イメージ コンテナ 遷移する方法(作る時/A->E) 遷移する方法(消す時/E->A) 確認方法
A なし なし docker rmi
B 作成済み なし docker pull / docker build --rmをつけて実行したコンテナからexit / docker rm docker images
C 作成済み 停止中 docker create --rmなしのコンテナからexit docker ps -a
D 作成済み 実行中 docker start Ctrl+P Ctrl+Q でコンテナを抜ける docker ps
E 作成済み アタッチ docker attach / docker exec

大体いまどこかがわかっていれば生きていける。

よく使われるrunはこれらの状態を複数スキップというか自動実行する。

docker run

書式は下記。詳細(run)

docker run [オプション] イメージ [コマンド] [引数...]

表のAの状態でdocker runを実行すると一気にEの状態まで遷移する。ここで指定しているubuntuイメージ名

$ docker run -it ubuntu
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
da7391352a9b: Pull complete 
14428a6d4bcd: Pull complete 
2c2d948710f2: Pull complete 
Digest: sha256:c95a8e48bf88e9849f3e0f723d9f49fa12c5a00cfc6e60d2bc99d87555295e4c
Status: Downloaded newer image for ubuntu:latest
root@9f1316bc678f:/# 

--rmをつけていないので、この状態からexitすると表のCの状態に遷移する。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

実行中のコンテナはいない。

下記のコマンドで停止中のコンテナも表示する。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                      PORTS               NAMES
9f1316bc678f        ubuntu              "/bin/bash"         About a minute ago   Exited (0) 41 seconds ago                       focused_pasteur

コンテナID9f1316bc678fのコンテナが表示される。

このコンテナの元になったイメージを確認する。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              f643c72bc252        6 weeks ago         72.9MB

イメージIDがf643c72bc252のイメージが表示される。

イメージのリポジトリとタグ

先ほど便宜上イメージ名と言ったものは正確にはREPOSITORYという。

1つのリポジトリにはいくつものイメージが存在する。たとえばubuntuの過去のバージョンを選択したい場合はubuntu:18.04のように指定する。

コロン(:)のあとに続く18.04TAGといい、コマンドでREPOSITORYのみ指定した場合、最新を示すlatestというTAGが自動的に選択される。

これらのことからイメージを指定するために必要な識別子はリポジトリ名、タグ名ということになるが、イメージIDと表現を揃えるためイメージ名と呼ぶことにする。

コンテナがある状態でdocker run

停止中とはいえ既にコンテナがある状態で同じイメージに対してdocker runを実行するとどうなるか。

$ docker run -it ubuntu

exitして下記のコマンドを実行する。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                      PORTS               NAMES
fd274bb747d1        ubuntu              "/bin/bash"         13 seconds ago      Exited (0) 9 seconds ago                        elegant_blackburn
9f1316bc678f        ubuntu              "/bin/bash"         18 minutes ago      Exited (0) 17 minutes ago                       focused_pasteur

停止中のコンテナが2つになっている。増えた方のコンテナIDはfd274bb747d1

作成済みのイメージからコンテナへアタッチされたことになる。つまり表のBからEに遷移される。

つまり一度作成したコンテナに対してはdocker runでは操作できないということ。docker runに指定するのがイメージ名なのでコンテナへの識別が必要となる操作はできない。

attach vs exec

実行中のコンテナへ接続する方法はattachexecの2つある。これらのコマンドはコンテナ識別子を指定する。(コンテナID/コンテナ名)

書式は下記。詳細(attach)詳細(exec)

docker attach [オプション] コンテナ
docker exec [オプション] コンテナ コマンド [引数...]

docker attach

docker attachはPID=1のプロセスに接続する。Ctrl+P、Ctrl+Qでデタッチした場合でも再接続可能。

$ docker start 9f1316bc678f
$ docker attach 9f1316bc678f
root@9f1316bc678f:/# echo $$
1

環境変数$$で実行中のPIDを取得できる。ここでは1になっている。

作業途中で抜けて再接続したい場合などはこちらで作業すると良さそう。

docker exec

コンテナ内の指定した任意のコマンドを実行できる。attachとは異なり新規にプロセスを作成する。

$ docker start 9f1316bc678f
$ docker exec -it 9f1316bc678f /bin/bash 
root@9f1316bc678f:/# echo $$
8

こちらに関してはCtrl+P、Ctrl+Qでデタッチした場合、このプロセスに再接続する方法がない。

実行中のコンテナで複数のターミナルを開いて作業したい場合などはこちらを使うと良さそう。

コンテナの削除

docker rmで削除する。

書式は下記。詳細(rm)

docker rm [オプション] コンテナ [コンテナ...]

削除するには下記のようにする。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                     PORTS               NAMES
2b17b5b1a846        ubuntu              "/bin/bash"         8 seconds ago       Exited (0) 6 seconds ago                       ecstatic_jepsen
$ docker rm -f 2b17b5b1a846

-fを指定すると実行中のコンテナも削除できる。コンテナはIDでも名前でもよい。

イメージの削除

docker rmiで削除できる。

書式は下記。詳細(rmi)

docker rmi [オプション] イメージ [イメージ...]

削除するには下記のようにする。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              latest              f643c72bc252        6 weeks ago         72.9MB
hello-world         latest              bf756fb1ae65        12 months ago       13.3kB
$ docker rmi bf756fb1ae65

よく使うオプション(-it)

docker rundocker execで、-itというオプションをつけているのをよく見かける。

Docker-docs-jaによると下記の通り。

option long name 機能
-i --interructive コンテナの STDIN にアタッチ
-t --tty 疑似ターミナル (pseudo-TTY) を割り当て

標準入力を擬似ttyに接続することでインタラクティブなシェルを実現できるとのこと。

逆に言えば単発で実行されるコマンドを実行する場合は不要

$ docker start 9f1316bc678f
$ docker exec 9f1316bc678f cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.1 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.1 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal

docker runの単発実行

任意のコマンドの単発実行について触れたのでdocker runの単発実行についても触れる。

docker runもコマンドを指定することで、起動したコンテナで任意のコマンドを実行することができる。

$ docker run --rm ubuntu cat /etc/os-release

このコマンドを実行すると下記のことが実行される。

  1. イメージの取得
  2. コンテナの作成
  3. コンテナの起動
  4. コンテナ内でコマンドを実行
  5. コンテナ終了
  6. コンテナの削除

表の状態のA->B->C->D->E->D->Bという遷移が自動で行われる。

コンテナの作業内容の保存

コンテナ内でファイルを追加したり環境を構築したりてもコンテナを削除すると全て失われるが、 これらを保持したい場合は、docker commitでイメージにすることができる。

書式は下記。

docker commit [オプション] コンテナ [リポジトリ[:タグ]]

下記のように実行する。

$ docker commit 9f1316bc678f my_env:version1

まとめ

いままでDockerをなんとなく使っていたが今回まとめてみた。

runが高機能すぎて、わからなくてもなんとなく使えてしまうのが良し悪しあると思う。

attachとexecも割と混乱する。

これで君もDockerマスターだ!(嘘)

Giant BoardをYoctoに対応する

はじめに

Giant BoardをDebian GNU/Linux 9 (stretch)で動かす動かしたGiant BoardをYoctoProjectに対応する。

ここでは新規ボード向けにYoctoProjectのレイヤを作る最小限の手順を紹介する。

新規ボードのレイヤを作る

YoctoProjectが未対応のボードに対応するためにはBSPレイヤを作成する。BSPレイヤを作成する場合次のものが必要となる。

今回はmeta-giantboardというレイヤを作成する。

作成の方針

meta-atmel

SAMA5向けのブートストラップなどはmeta-atmelで定義されている。

https://github.com/linux4sam/meta-atmelをベースにGiant Board向けの対応を作成する。

今回はmeta-atmelに取り込んでもらう目的ではないため独自にmeta-giantboardを作成する。この時meta-atmelのレシピの作り方などは参考にさせてもらう。

Giant Board対応の変更

at91bootstrap、u-bootおよびLinuxカーネルへのGiand Board対応の変更についてはgiantboard-toolsから取り込む。

下記のコマンドで一通りのビルドを実行しているものとする。

$ cd
$ git clone https://github.com/Groboards/giantboard-tools.git
$ cd giantboard-tools
$ chmod +x build_menu.sh
$ ./build_menu.sh

作業環境の作成

ベース環境の構築

Dunfell(3.1)をベースに環境を作る。

$ mkdir -p ~/gb-dunfell/layers && cd ~/gb-dunfell/layers
$ git clone git://git.yoctoproject.org/poky.git -b dunfell
$ cd ../

環境変数設定

$ source layers/poky/oe-init-build-env build

自動的にビルドディレクトリに移動される。 これで、bitbake関連のツールが使用可能になる。

meta-giantboardの作成

$ bitbake-layers create-layer meta-giantboard -p 9
$ rm -rf ./meta-giantboard/recipes-example
$ mv ./meta-giantboard/ ../layers/

ビルド対象へ追加

$ bitbake-layers add-layer ../layers/meta-giantboard

Giant BoardのHW情報

CPU

# cat /proc/cpuinfo
processor       : 0
model name      : ARMv7 Processor rev 1 (v7l)
BogoMIPS        : 326.86
Features        : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 vfpd32
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x0
CPU part        : 0xc05
CPU revision    : 1

Hardware        : Atmel SAMA5
Revision        : 0000
Serial          : 0000000000000000

メモリ

# free -h
              total        used        free      shared  buff/cache   available
Mem:           117M         14M         70M        960K         33M         97M
Swap:            0B          0B          0B

マシン定義の作成

$ cd ../layers/meta-giantboard
$ mkdir -p conf/machine

conf/machine/giantboard.confを以下の内容で作成する。

#@TYPE: Machine
#@Name: Groboards Giant Board
#@DESCRIPTION: A single-board computer in the Adafruit Feather form factor.

# Atmel SAMA5 defaults
require conf/machine/include/soc-family.inc
require conf/machine/include/tune-cortexa5.inc

# Atmel SAMA5D2 defaults
SOC_FAMILY ?= "sama5:sama5d2"
DEFAULTTUNE ?= "cortexa5thf-neon-vfpv4"

# Machine Features
MACHINE_FEATURES ?= "apm vfat ext2 serial usbhost usbgadget"

# Virtual package definitions
PREFERRED_PROVIDER_virtual/kernel_sama5 ?= "linux-giantboard"
PREFERRED_PROVIDER_virtual/bootloader_sama5 ?= "u-boot-at91"
PREFERRED_PROVIDER_u-boot_sama5 ?= "u-boot-at91"

PREFERRED_PROVIDER_jpeg ?= "jpeg"
PREFERRED_PROVIDER_jpeg-native ?= "jpeg-native"

# Default settings for serial console
SERIAL_CONSOLES ?= "115200;ttyS0"

# Kernel informations
PREFERRED_VERSION_linux ?= "5.4%"
KERNEL_DEVICETREE ?= " \
                at91-sama5d27_giantboard.dtb \
        "

# Settings for wic image
do_image_wic[depends] += "at91bootstrap:do_deploy u-boot-at91:do_deploy"
IMAGE_FSTYPES += " tar.gz wic.bz2 wic.bmap"
WKS_FILE ?= "sdimage-bootpart.wks"
WIC_CREATE_EXTRA_ARGS ?= "--no-fstab-update"

# File definitions that BOOT partation contains.
IMAGE_BOOT_FILES ?= "BOOT.BIN \
           u-boot.bin \
           uEnv.txt \
           ${KERNEL_DEVICETREE} \
           ${KERNEL_IMAGETYPE} \
"

基本的にconfで定義する変数は再定義できる余地を残しておくために?=??=で定義しておく。この時点で=で強い束縛をしてしまうとlocal.confで再定義できないなど非常に不便になるので注意。

絶対に再定義させないという強い意志 ががない場合は=の使用はおすすめしない。 もしどうしても再定義させたくない場合はコメントなどで再定義できない理由を記載しておくと良い。

以下内容を解説する。

SoC定義

SoCの定義関連。これによってクロスコンパイル時のターゲットや最適化オプションなどが決定される。

# Atmel SAMA5 defaults
require conf/machine/include/soc-family.inc
require conf/machine/include/tune-cortexa5.inc

# Atmel SAMA5D2 defaults
SOC_FAMILY ?= "sama5:sama5d2"
DEFAULTTUNE ?= "cortexa5thf-neon-vfpv4"

Machine Features

ハードウェアが持っている機能について設定する。USBやWiFiなどの有無など。 ビルド全体に関わるコンパイルフラグに影響する。

# Machine Features
MACHINE_FEATURES ?= "apm vfat ext2 serial usbhost usbgadget"

バーチャル関連の定義

virtual/kenrelvirtual/bootloaderなどの仮想ターゲットと実体を紐付けるための定義。

# Virtual package definitions
PREFERRED_PROVIDER_virtual/kernel_sama5 ?= "linux-giantboard"
PREFERRED_PROVIDER_virtual/bootloader_sama5 ?= "u-boot-at91"
PREFERRED_PROVIDER_u-boot_sama5 ?= "u-boot-at91"

PREFERRED_PROVIDER_jpeg ?= "jpeg"
PREFERRED_PROVIDER_jpeg-native ?= "jpeg-native"

このおかげで下記のようにビルドを実行できる。

$ bitbake virtual/kernel

最後のjpeg関連についてはデフォルトでJPEG関連のライブラリをjpegパッケージにするかjpeg-turboパッケージにするかという目的で使用される。

シリアル定義

シリアルコンソールのポートや設定のデフォルトを定義する。

# Default settings for serial console
SERIAL_CONSOLES ?= "115200;ttyS0"

ARMではシリアルポートがttyAMA0だったりする場合があるのでそういう差異を吸収している。

カーネル設定

ターゲット向けにカーネルをビルドする際に必要な情報の定義。 デフォルトで使用するカーネルのバージョンや、defconfig、デバイスツリーを設定している。

# Kernel informations
PREFERRED_VERSION_linux ?= "5.4%"
KERNEL_DEVICETREE ?= " \
                at91-sama5d27_giantboard.dtb \
        "

wicイメージ関連の設定

YoctoではWicというイメージ生成機能を使用できる。

ここではWicイメージを生成するために必要な情報を定義している。

Wicはパーティション情報を含んでおり、SDカードなどのストレージにddやbmaptoolなどで直接OSイメージを流し込むことができる。

# Settings for wic image
do_image_wic[depends] += "at91bootstrap:do_deploy"
IMAGE_FSTYPES += " tar.gz wic.bz2 wic.bmap"
WKS_FILE ?= "sdimage-bootpart.wks"
WIC_CREATE_EXTRA_ARGS ?= "--no-fstab-update"

BOOTパーティションの設定

BOOTパーティションに含めるファイルの定義。

# File definitions that BOOT partation contains.
IMAGE_BOOT_FILES ?= "BOOT.BIN \
           u-boot.bin \
           uEnv.txt \
           ${KERNEL_DEVICETREE} \
           ${KERNEL_IMAGETYPE} \
"

ここでBOOTパーティションに含めるファイルを設定する。

ブートローダのレシピ

SAMA5D2でLinuxブートするためには、次のソフトウェアが必要になる。

ブートローダのレシピはrecipes-bspに配置する。

$ mkdir recipes-bsp
$ mkdir recipes-bsp/at91bootstrap recipes-bsp/u-boot

at91bootstrap

giantboard-toolsを確認すると、使用しているバージョンは下記の通り。

commit 5a89d0a2314761d51b2c750592273a0edd3addf8 (HEAD -> tmp, tag: v3.8.13)
Author: Eugen Hristev <eugen.hristev@microchip.com>
Date:   Wed Jul 10 13:42:17 2019 +0300

    Makefile: prepare 3.8.13
    
    Signed-off-by: Eugen Hristev <eugen.hristev@microchip.com>

これにdefconfigの定義を追加してビルドしている。

recipes-bsp/at91bootstrap/at91bootstrap_3.8.13.bbを下記の内容で作成する。

SUMMARY = "Initial bootstrap for AT91 ARM MPUs"
DESCRIPTION = " \
        at91bootstrap is the second-level bootloader for Atmel AT91  \
        SoCs. It provides a set of algorithms to manage the hardware \
        initialization and to download the main application (or a    \
        third-level bootloader) from specified boot media to         \
        main memory and start it.                                    \
          "
AUTHOR = "Atmel Corporation"
HOMEPAGE = "http://www.at91.com/linux4sam/bin/view/Linux4SAM/AT91Bootstrap"
BUGTRACKER = "https://github.com/linux4sam/at91bootstrap/issues"
SECTION = "bootloaders"
LICENSE = "ATMEL"
LIC_FILES_CHKSUM = "file://main.c;endline=27;md5=a2a70db58191379e2550cbed95449fbd"

COMPATIBLE_MACHINE = '(giantboard)'

SRC_URI = "git://github.com/linux4sam/at91bootstrap.git;protocol=https \
    file://sama5d27_giantboard_sd1_uboot_defconfig \
"

PV = "3.8.13+git${SRCPV}"
SRCREV = "5a89d0a2314761d51b2c750592273a0edd3addf8"

S = "${WORKDIR}/git"

inherit deploy
DEPENDS += "bc-native"

EXTRA_OEMAKE = 'CROSS_COMPILE=${TARGET_PREFIX} CC="${TARGET_PREFIX}gcc ${TOOLCHAIN_OPTIONS}"'

do_unpack_append() {
    bb.build.exec_func('do_copy_defconfig', d)
}

do_copy_defconfig() {
    cp ${WORKDIR}/sama5d27_giantboard_sd1_uboot_defconfig ${S}/board/sama5d27_som1_ek
}

do_configure() {
    oe_runmake distclean
    oe_runmake sama5d27_giantboard_sd1_uboot_defconfig
}

do_compile() {
    oe_runmake
}

do_deploy () {
    install -d ${DEPLOYDIR}
    install ${S}/binaries/boot.bin ${DEPLOYDIR}/BOOT.BIN
}
addtask deploy before do_build after do_compile

結構細かい小技が効いているので解説していく。

ヘッダ部

レシピの概要やこのレシピでパッケージングするOSSの情報について記述する。

SUMMARY = "Initial bootstrap for AT91 ARM MPUs"
DESCRIPTION = " \
        at91bootstrap is the second-level bootloader for Atmel AT91  \
        SoCs. It provides a set of algorithms to manage the hardware \
        initialization and to download the main application (or a    \
        third-level bootloader) from specified boot media to         \
        main memory and start it.                                    \
          "
AUTHOR = "Atmel Corporation"
HOMEPAGE = "http://www.at91.com/linux4sam/bin/view/Linux4SAM/AT91Bootstrap"
BUGTRACKER = "https://github.com/linux4sam/at91bootstrap/issues"
SECTION = "bootloaders"
LICENSE = "ATMEL"
LIC_FILES_CHKSUM = "file://main.c;endline=27;md5=a2a70db58191379e2550cbed95449fbd"

COMPATIBLE_MACHINE = '(giantboard)'

COMPATIBLE_MACHINEを指定することで、MACHINEにgiantboard以外を設定されている時にat91bootstrapのレシピが選択された場合エラーにすることができる。 ブートローダのような明らかに他のボードでは動かないようなものなこのようにしておくとミスが発見しやすくなる。

ライセンスについて

ヘッダ部のうちライセンスの記述はLICENSELIC_FILES_CHKSUM

LICENSE = "ATMEL"
LIC_FILES_CHKSUM = "file://main.c;endline=27;md5=a2a70db58191379e2550cbed95449fbd"

at91bootstrapはAt91bootstrap-licenseというライセンスで配布されている。

LIC_FILES_CHKSUMはレシピ作成時とライセンスが変わっていないかをチェックできる。この例ではmain.cの先頭からendline=27で指定した行までがライセンス記述だという指定。この部分に対してmd5でハッシュを取っている。つまり、この部分の文言に変更があった場合md5の値が変わるためライセンス定義の変更を検出することができる。

ATMELの独自ライセンスであるため、レシピだけライセンス情報を記述するだけでは下記のような警告が表示される。

WARNING: at91bootstrap-3.8.13+gitAUTOINC+5a89d0a231-r0 do_populate_lic: at91bootstrap: No generic license file exists for: ATMEL in any provider

これはLICENSEにbitbakeが管理しているライセンスにないものを指定された場合に出される。この警告によってケアされていないライセンスが混入していないか確認を促すことができる。

この警告を黙らせるためには、layers.confで下記のように設定し、ここで指定したディレクトリにライセンスの内容を記述したファイルを配置しておく。つまりもともとYocto ProjectやOpenEmbeddedで管理していないようなライセンスを取り扱う場合、レイヤの提供者がきちんと管理している必要があるということになる。

# License
LICENSE_PATH += "${LAYERDIR}/licenses"

meta-giantboard直下にlicensesディレクトリを作成する。

$ mkdir licenses

ここではlicenses/ATMELとして下記の内容でファイルを作成している。

----------------------------------------------------------------------------
        ATMEL Microcontroller Software Support
----------------------------------------------------------------------------
Copyright (c) 2006, Atmel Corporation

All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice,
this list of conditions and the disclaimer below.

Atmel's name may not be used to endorse or promote products derived from
this software without specific prior written permission.

DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

ソースファイル関連

パッケージングされるソフトウェアのソースコードの有りか及びパッチファイルや追加のコンフィグファイルをSRC_URLで指定する。PVはパッケージバージョンの指定、SRCREVでどのコミットを使用するかを指定する。

Sはbitbakeがビルド時に参照する変数でソースコード(Source)を配置したディレクトリを指定する。ちなみにビルドディレクトリを指定するB(Build)、生成物を配置するD(Destination)という変数もあり、これらはレシピのデバッグ時にはよく参照することになる。

SRC_URI = "git://github.com/linux4sam/at91bootstrap.git;protocol=https \
    file://sama5d27_giantboard_sd1_uboot_defconfig \
"

PV = "3.8.13+git${SRCPV}"
SRCREV = "5a89d0a2314761d51b2c750592273a0edd3addf8"

S = "${WORKDIR}/git"

依存関係関連

inheritはビルドシステムがbbclassとして提供する便利な関数をレシピ内で使用するために記述する。ここではブートパーティションにファイルを配置するために使用するdeployを使用する。

DEPENDSはこのレシピをビルドするときに依存するレシピを指定する。

inherit deploy
DEPENDS += "bc-native"

環境設定関連

EXTRA_OEMAKEmakeコマンド実行時に渡すパラメータを指定する。ここではクロスコンパイルに必要な設定を行っている。

EXTRA_OEMAKE = 'CROSS_COMPILE=${TARGET_PREFIX} CC="${TARGET_PREFIX}gcc ${TOOLCHAIN_OPTIONS}"'

タスク

実際のビルド処理を実行するにはタスクを定義していく。

基本的には下記のような流れで実行されていく。実際にはライセンスチェックやサニティチェックなど他にも細々とタスクがある。

  1. do_fetch(ソースのダウンロード)
  2. do_unpack(ダウンロードしたソースファイルの展開)
  3. do_configure(ビルド設定/configureスクリプトの実行など)
  4. do_compile(ビルド実行)
  5. do_install(ローカル環境へのインストール/make installなど)
  6. do_package(パッケージ作成/rpm,debなど)

各タスクにはその直前や直後に処理を追加する_prepend_appendをいう関数を定義することができる。 また、タスク自体も自由にに追加や削除ができる。

do_unpack

at91bootstrapをgiantboard向けにビルドするためにはdefconfigを追加する必要がある。このdefconfigはboard/sama5d27_som_ekに配置されている必要があるため、do_unpackタスクの処理を拡張して、ファイルをコピーする。

ただ、do_unpackpython関数として実装されているためshell関数としてdo_unpack_appendを追加すると構文エラーになってしまう。そのためpythonからbb.build.exec_funcでshell関数を呼び出すという方法を取っている。

do_unpack_append() {
    bb.build.exec_func('do_copy_defconfig', d)
}

do_copy_defconfig() {
    cp ${WORKDIR}/sama5d27_giantboard_sd1_uboot_defconfig ${S}/board/sama5d27_som1_ek
}

do_configure

makeコマンドを直接実行指定も良いが、oe_runmake関数を使用すると必要な変数やパラメータなどをあらかじめ設定してmakeコマンドを呼び出してくれるのでこちらを使用するほうが安全。

do_configure() {
    oe_runmake distclean
    oe_runmake sama5d27_giantboard_sd1_uboot_defconfig
}

do_compile

-j $(nproc)のような並列化の指定などもやってくれる。

do_compile() {
    oe_runmake
}

do_deploy

ブートローダはブートパーティションに配置する必要があるので、do_deployタスクを作成しaddtaskで追加している。 do_deployに必要なDEPLOYDIRなどの定義を使用するためにあらかじめinherit deployでbbclassを継承している。

do_deploy () {
    install -d ${DEPLOYDIR}
    install ${S}/binaries/boot.bin ${DEPLOYDIR}/BOOT.BIN
}
addtask deploy before do_build after do_compile

defconfig

SRC_URIで指定したsama5d27_giantboard_sd1_uboot_defconfigはgiantboard-toolsから取り込む。

$ mkdir -p recipes-bsp/at91bootstrap/files
$ cp ~/giantboard-tools/patches/at91bootstrap/sama5d27_giantboard_sd1_uboot_defconfig recipes-bsp/at91bootstrap/files

u-boot

u-bootは下記のコミットにGiant Board対応のための修正を加えている。

commit 003ba1b96b7ee1bffb81b31d62346d676b4afddc (HEAD -> master, origin/master, origin/HEAD)
Author: Christopher Alessandro <calessandro0827@gmail.com>
Date:   Tue Jul 14 14:23:19 2020 -0700

    Add command to force refresh ca-ceritificates to resolve SSL check errors.

レシピをrecipes-bsp/u-boot/u-boot-at91_2019.07.bbとして下記の内容で作成する。

DESCRIPTION = "U-Boot - the Universal Boot Loader"
HOMEPAGE = "http://www.denx.de/wiki/U-Boot/WebHome"
SECTION = "bootloaders"
PROVIDES = "u-boot virtual/bootloader"

inherit deploy

LICENSE = "GPLv2+"
LIC_FILES_CHKSUM = "file://README;beginline=1;endline=22;md5=9915e8cb100eb5dbb366010a0f10296c"

SRCREV = "e5aee22e4be75e75a854ab64503fc80598bc2004"

PV = "v2019.07-at91+git${SRCPV}"

DEPENDS += "bison-native flex-native"

COMPATIBLE_MACHINE = '(giantboard)'

UBRANCH = "master"

SRC_URI = "git://github.com/u-boot/u-boot;protocol=https;branch=${UBRANCH} \
       file://giantboard-fixes.patch \
       file://sama5d27_giantboard_mmc_defconfig \
       file://at91-sama5d27_giantboard.dts \
"

S = "${WORKDIR}/git"

PACKAGE_ARCH = "${MACHINE_ARCH}"

EXTRA_OEMAKE = 'CROSS_COMPILE=${TARGET_PREFIX} CC="${TARGET_PREFIX}gcc ${TOOLCHAIN_OPTIONS}" HOSTCC="${BUILD_CC} ${BUILD_CFLAGS} ${BUILD_LDFLAGS}"'

do_unpack_append() {
    bb.build.exec_func('do_copy_defconfig', d)
}

do_copy_defconfig() {
    cp ${WORKDIR}/sama5d27_giantboard_mmc_defconfig ${S}/configs
    cp ${WORKDIR}/at91-sama5d27_giantboard.dts ${S}/arch/arm/dts/
}

do_configure() {
    oe_runmake distclean
    oe_runmake sama5d27_giantboard_mmc_defconfig
}

do_compile() {
    oe_runmake
}

do_deploy () {
    install -d ${DEPLOYDIR}
    install ${S}/u-boot.bin ${DEPLOYDIR}/u-boot.bin
    cat << 'EOF' > ${DEPLOYDIR}/uEnv.txt
# Main Device Tree
fdtfile=/at91-sama5d27_giantboard.dtb
optargs=atmel.pm_modes=standby,ulp1 giantboard.disable_charging=1   
EOF
}
addtask deploy before do_build after do_compile

レシピの構造はat91bootstrapと大きな違いはないので、個別の解説は省略する。

1つだけ特筆するとすれば、do_deployではuEnv.txtを生成している。

変更点

u-bootのGiant Board向けのファイルは下記の通り。

  • giantboard-fixes.patch
  • sama5d27_giantboard_mmc_defconfig
  • at91-sama5d27_giantboard.dts

これらのファイルもgiantboard-toolsから取り込む。

$ mkdir -p recipes-bsp/u-boot/files
$ cp ~/giantboard-tools/patches/u-boot/giantboard-fixes.patch recipes-bsp/u-boot/files
$ cp ~/giantboard-tools/patches/u-boot/sama5d27_giantboard_mmc_defconfig recipes-bsp/u-boot/files
$ cp ~/giantboard-tools/patches/u-boot/at91-sama5d27_giantboard.dts recipes-bsp/u-boot/files

カーネルのレシピ

recipes-kernel/linuxlinux-giantboard_5.4.bbを作成する。

$ mkdir -p recipes-kernel/linux

カーネルは下記のコミットにGiant Boardサポートの修正を追加している。

commit 219d54332a09e8d8741c1e1982f5eae56099de85 (tag: v5.4)
Author: Linus Torvalds <torvalds@linux-foundation.org>
Date:   Sun Nov 24 16:32:01 2019 -0800

    Linux 5.4

recipes-kernel/linux/linux-giantboard_5.4.bbを下記の内容で作成する。

SECTION = "kernel"
DESCRIPTION = "Linux kernel for Microchip ARM SoCs (aka AT91)"
SUMMARY = "Linux kernel for Microchip ARM SoCs (aka AT91)"
LICENSE = "GPLv2"
LIC_FILES_CHKSUM = "file://COPYING;md5=bbea815ee2795b2f4230826c0c6b8814"

inherit kernel

FILESEXTRAPATHS_prepend := "${THISDIR}/${P}:"

SRCREV = "219d54332a09e8d8741c1e1982f5eae56099de85"

PV = "5.4+git${SRCPV}"

S = "${WORKDIR}/git"

KBRANCH = "master"
SRC_URI = "git://github.com/torvalds/linux.git;protocol=git;branch=${KBRANCH} \
       file://giantboard.patch \
       file://defconfig \
"

KERNEL_MODULE_AUTOLOAD += "atmel_usba_udc g_serial"
COMPATIBLE_MACHINE = "(giantboard)"

カーネルのレシピは少々特殊だが、基本的に必要な機能は実装済みであるのでinherit kernelカーネルのbbclassを継承すればよい。

変更点

カーネルの変更点は大まかに下記のようになっている。

  • at91-sama5d27_giantboard.dts
  • giantboard_defconfig
  • wilc1000ドライバの追加

今回はkernel.bbclassしか継承していないため、git diffで作成したパッチを適用できる。

$ cd ~/giantboard-tools/output/build/linux
$ git add .
$ git diff --cached > ~/giantboard.patch
$ cd ~/gb-dunfell/layers/meta-giantboard/recipes-kernel/linux/
$ mkdir files
$ mv ~/giantboard.patch ./files
$ cp ~/giantboard-tools/patches/kernel/giantboard_defconfig files/defconfig

.cfgフラグメントなどyoctoのカーネルレシピの機能を使用したい場合はkernel-yocto.bbclassを継承する必要がある。この場合パッチのチェックなども強化されるため、kernel-yoctoの作法に則ってレシピやパッチを作成する必要がある。 今回の様にカジュアルに変更を取り込むような場合は、kernel-yocto.bbclassを継承しないという方法も取ることができる。

ビルド

buildディレクトリに移動して、conf/local.confに下記の内容を追加する。

MACHINE = "giantboard"

bitbakeを実行する。

$ bitbake core-image-minimal

シリアルケーブルでボードとPCを接続しminicomを開いた状態で起動する。

Poky (Yocto Project Reference Distro) 3.1.4 giantboard /dev/ttyS0

giantboard login: root
root@giantboard:~# uname -a
Linux giantboard 5.4.0 #1 Mon Nov 25 00:32:01 UTC 2019 armv7l GNU/Linux

Linuxのログインまで行けた。。

まとめ

Yocto環境で新しいボードに対応する例を示した。

これができるようになると、Yoctoでの遊び方の幅が広がる。