コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2020年2月6日 >>> 2020年1月24日
link もっと後

2020年2月6日

Zephyr OSで遊ぼう その5 - 独自のSoC定義(続き)

目次: Zephyr

今回はcmakeをパスするまで頑張ります。

エラー内容の調査

今後の方針のためcmakeのエラーを見ます。どうやらSOC_FAMILY_RISCV_PRIVILEGEがないとか、SOC_SERIES_RISCV_SPIKEとは何だ?とか、Kconfig周りの設定で怒っているようです。何も定義していないので当然ですね。

hoge.dtsを変更してcmakeしたときのエラー
warning: UART_CONSOLE (defined at drivers/console/Kconfig:47) was assigned the value 'y' but got the
value 'n'. Check these unsatisfied dependencies: SERIAL_HAS_DRIVER (=n). See
http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_UART_CONSOLE.html and/or look up
UART_CONSOLE in the menuconfig/guiconfig interface. The Application Development Primer, Setting
Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful
too.


warning: RISCV_MACHINE_TIMER (defined at drivers/timer/Kconfig:153) was assigned the value 'y' but
got the value 'n'. Check these unsatisfied dependencies: SOC_FAMILY_RISCV_PRIVILEGE (=n). See
http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_RISCV_MACHINE_TIMER.html and/or look
up RISCV_MACHINE_TIMER in the menuconfig/guiconfig interface. The Application Development Primer,
Setting Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be
helpful too.


warning: The choice symbol BOARD_HOGE (defined at boards/riscv/hoge/Kconfig.board:3) was selected
(set =y), but no symbol ended up as the choice selection. See
http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_BOARD_HOGE.html and/or look up
BOARD_HOGE in the menuconfig/guiconfig interface. The Application Development Primer, Setting
Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful
too.


zephyr/boards/riscv/hoge/hoge_defconfig:4: warning: attempt to assign the value 'y' to the undefined symbol SOC_SERIES_RISCV_SPIKE

zephyr/boards/riscv/hoge/hoge_defconfig:5: warning: attempt to assign the value 'y' to the undefined symbol SOC_RISCV_SPIKE

error: Aborting due to Kconfig warnings

CMake Error at zephyr/cmake/kconfig.cmake:216 (message):
  command failed with return code: 1
Call Stack (most recent call first):
  zephyr/cmake/app/boilerplate.cmake:461 (include)
  CMakeLists.txt:5 (include)

すぐにKconfigを追加と行きたいところですが、その前に少しやることがあります。

ボードのdefconfigを見直す

Hogeボードのdefconfigで余計なもの(SiFive FE310 SoC用の設定)を定義しているので、削りましょう。SiFive由来のドライバの設定、前回のデバイスツリーで定義していないPINMUXやGPIOの設定を削ります。

hoge_defconfigからSiFive用の設定を削る

diff --git a/boards/riscv/hoge/hoge_defconfig b/boards/riscv/hoge/hoge_defconfig
index 3f9626815f..32c4e9e90d 100644
--- a/boards/riscv/hoge/hoge_defconfig
+++ b/boards/riscv/hoge/hoge_defconfig
@@ -1,19 +1,12 @@
 # SPDX-License-Identifier: Apache-2.0
 
 CONFIG_RISCV=y
-CONFIG_SOC_SERIES_RISCV_SIFIVE_FREEDOM=y
-CONFIG_SOC_RISCV_SIFIVE_FREEDOM=y
+CONFIG_SOC_SERIES_RISCV_SPIKE=y
+CONFIG_SOC_RISCV_SPIKE=y
 CONFIG_BOARD_HOGE=y
 CONFIG_CONSOLE=y
 CONFIG_PRINTK=y
 CONFIG_SERIAL=y
-CONFIG_UART_SIFIVE=y
-CONFIG_UART_SIFIVE_PORT_0=y
 CONFIG_UART_CONSOLE=y
-CONFIG_PLIC=y
-CONFIG_PINMUX=y
-CONFIG_PINMUX_SIFIVE=y
 CONFIG_RISCV_MACHINE_TIMER=y
-CONFIG_GPIO=y
-CONFIG_GPIO_SIFIVE=y
 CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=10000000

これで余計なコンフィグが原因で怒られることはないはずです。

SoC用のKconfigの追加

基本的にSoCのKconfigを追加する場合、zephyr/soc/アーキテクチャ名/SoC名/Kconfig.socを足せば良いですが、RISC-V Privileged系のSoCは少し様子が違い、Kconfigが2段構成になります。

Kconfigの依存関係
# zephyr/soc/Kconfig

  source "$(SOC_DIR)/$(ARCH)/*/Kconfig.soc"


# zephyr/soc/riscv/riscv-privilege/Kconfig.soc

  source "soc/riscv/riscv-privilege/*/Kconfig.series"

ファイルを足すべき場所がわかりました。早速Kconfig.seriesを足します。内容は隣にあるSiFiveのディレクトリ(sifive-freedom)などを参考にします。

zephyr/soc/riscv/riscv-privilege/spike/Kconfig.series

# RISCV_SPIKE SOC implementation

# SPDX-License-Identifier: Apache-2.0

config SOC_SERIES_RISCV_SPIKE
        bool "SPIKE series SOC implementation"
        depends on RISCV
        select SOC_FAMILY_RISCV_PRIVILEGE
        help
          Enable support for Spike series SOC

再度cmakeを実行するとSOC_SERIES_RISCV_SPIKEのエラーが消えるはずです。残りのSOC_RISCV_SPIKEはどこで定義すべきでしょうか?

RISC-V PrivilegedのKconfigは2つある

RISC-V Privilegedや他のディレクトリを見ると気づくと思いますが、SoCのKconfigには2つ系列があります。

riscv-privilege/Kconfig.soc -> riscv-privilege/spike/Kconfig.seriesのラインと、
riscv-privilege/Kconfig -> riscv-privilege/spike/Kconfig.socのラインです。先程、追加したのは前者です。名前が紛らわしいのはRISC-V Privileged系列の実装の良くないところですね……。

RISC-V PrivilegedのKconfig依存関係(再整理)

# zephyr/Kconfig

  source "Kconfig.zephyr"


# zephyr/Kconfig.zephyr

  source "$(SOC_DIR)/Kconfig"    # SOC_DIR = soc


# zephyr/soc/Kconfig

  choice
  	prompt "SoC/CPU/Configuration Selection"

  source "$(SOC_DIR)/$(ARCH)/*/Kconfig.soc"    # soc/riscv/riscv-privilege/Kconfig.soc

  endchoice

  menu "Hardware Configuration"
  osource "$(SOC_DIR)/$(ARCH)/Kconfig"
  osource "$(SOC_DIR)/$(ARCH)/*/Kconfig"    # soc/riscv/riscv-privilege/Kconfig


# zephyr/soc/riscv/riscv-privilege/Kconfig.soc

  source "soc/riscv/riscv-privilege/*/Kconfig.series"


# zephyr/soc/riscv/riscv-privilege/Kconfig

  config SOC_FAMILY_RISCV_PRIVILEGE    # riscv-privilege/spike/Kconfig.seriesのselectで有効にする
  config SOC_FAMILY
  config RISCV_HAS_PLIC

  ...

  source "soc/riscv/riscv-privilege/*/Kconfig.soc"

仕組みがわかったところで、後者のKconfig.socも追加しましょう。内容はやはりSiFiveのKconfigを参考にします。ありがとうSiFiveさん。

zephyr/soc/riscv/riscv-privilege/spike/Kconfig.soc

# RISCV_SPIKE SOC configuration options

# SPDX-License-Identifier: Apache-2.0

choice
        prompt "Spike series SOC implementation"
        depends on SOC_SERIES_RISCV_SPIKE

config SOC_RISCV_SPIKE
        bool "Spike SOC implementation"
        select ATOMIC_OPERATIONS_C

endchoice

SOC_SERIES_RISCV_SPIKEは、先程Kconfig.seriesにて定義しました。ATOMIC_OPERATIONS_Cはアトミック演算を行う関数を定義するためのコンフィグです。これを指定しないと、後ほどリンクする際にatomic_cas() がないと言われます。

アトミック演算の定義箇所

//zephyr/include/sys/atomic.h

#ifdef CONFIG_ATOMIC_OPERATIONS_BUILTIN
static inline bool atomic_cas(atomic_t *target, atomic_val_t old_value,
                          atomic_val_t new_value)
{
        return __atomic_compare_exchange_n(target, &old_value, new_value,
                                           0, __ATOMIC_SEQ_CST,
                                           __ATOMIC_SEQ_CST);
}
#elif defined(CONFIG_ATOMIC_OPERATIONS_C)
__syscall int atomic_cas(atomic_t *target, atomic_val_t old_value,
                         atomic_val_t new_value);

#else
extern int atomic_cas(atomic_t *target, atomic_val_t old_value,
                      atomic_val_t new_value);
#endif

これでもまだcmakeに怒られます。linker.ldがどうのこうのと言っています。パスもなにやらおかしいです。

Kconfig追加後のcmakeのエラー
CMake Error at ../../CMakeLists.txt:372 (message):
  Could not find linker script:
  'zephyr/soc/riscv//linker.ld'.
  Corrupted configuration?

このlinker.ldを探しているのはcmakeです。下記の部分で探しています。

cmakeがlinker.ldを探している箇所

# zephyr/CMakeLists.txt

...

if(CONFIG_HAVE_CUSTOM_LINKER_SCRIPT)
  set(LINKER_SCRIPT ${APPLICATION_SOURCE_DIR}/${CONFIG_CUSTOM_LINKER_SCRIPT})
  if(NOT EXISTS ${LINKER_SCRIPT})
    set(LINKER_SCRIPT ${CONFIG_CUSTOM_LINKER_SCRIPT})
    assert_exists(CONFIG_CUSTOM_LINKER_SCRIPT)
  endif()
else()
  # Try a board specific linker file
  set(LINKER_SCRIPT ${BOARD_DIR}/linker.ld)    # ★★★これ、もしくは★★★
  if(NOT EXISTS ${LINKER_SCRIPT})
    # If not available, try an SoC specific linker file
    set(LINKER_SCRIPT ${SOC_DIR}/${ARCH}/${SOC_PATH}/linker.ld)    # ★★★これ★★★
  endif()
endif()

if(NOT EXISTS ${LINKER_SCRIPT})
  message(FATAL_ERROR "Could not find linker script: '${LINKER_SCRIPT}'. Corrupted configuration?")
endif()

デバッグメッセージ(message(SOC_PATH ${SOC_PATH}) を書き足すなど)を出して確認すると、SOC_PATHが空になっています。まだ何か足りないようですので、調べます。SOC_PATHを定義しているのは下記の部分です。

cmakeがSOC_PATHを決める箇所

# zephyr/cmake/app/boilerplate.cmake

...

set(SOC_NAME   ${CONFIG_SOC})
set(SOC_SERIES ${CONFIG_SOC_SERIES})
set(SOC_FAMILY ${CONFIG_SOC_FAMILY})

if("${SOC_SERIES}" STREQUAL "")
  set(SOC_PATH ${SOC_NAME})
else()
  set(SOC_PATH ${SOC_FAMILY}/${SOC_SERIES})
endif()

CONFIG_SOCもしくはCONFIG_SOC_FAMILYとCONFIG_SOC_SERIESを定義する必要があるようです。探してみるとCONFIG_SOC_FAMILYはzephyr/soc/riscv/riscv-privilege/Kconfig で定義されています。

もう一方のCONFIG_SOC_SERIESはKconfig.defconfig.seriesというとても変な名前のKconfigファイルで定義するようです。

CONFIG_SOC_SERIESの定義箇所
$ cd zephyr/soc/riscv/riscv-privilege
$ grep -r 'config SOC_SERIES'

miv/Kconfig.defconfig.series:config SOC_SERIES
miv/Kconfig.series:config SOC_SERIES_RISCV32_MIV
sifive-freedom/Kconfig.defconfig.series:config SOC_SERIES
sifive-freedom/Kconfig.series:config SOC_SERIES_RISCV_SIFIVE_FREEDOM

このKconfigはどこから参照されているのでしょうか?これも一応、調べておきましょう。

Kconfig.defconfig.seriesの依存関係

# zephyr/Kconfig

  source "Kconfig.zephyr"

# zephyr/Kconfig.zephyr

  source "$(SOC_DIR)/$(ARCH)/*/Kconfig.defconfig"    # SOC_DIR = soc, ARCH = riscv


# zephyr/soc/riscv/riscv-privilege/Kconfig.defconfig

  source "soc/riscv/riscv-privilege/*/Kconfig.defconfig.series"

Kconfig.defconfig.seriesも他のSoCに習って追加しましょう。SpikeにPLICは実装されていないので、RISCV_HAS_PLICはnにしています。またFlash ROMもないので、XIP(Execution In Place)を無効にしています。

zephyr/soc/riscv/riscv-privilege/spike/Kconfig.defconfig.series

# SPDX-License-Identifier: Apache-2.0

if SOC_SERIES_RISCV_SPIKE

config SOC_SERIES
        default "spike"

config RISCV_HAS_PLIC
        default n

config NUM_IRQS
        default 16

config XIP
        default n

endif #SOC_SERIES_RISCV_SPIKE

肝心のlinker.ldを追加することも忘れないようにします。これも他のSoCを見ればわかるはず。

zephyr/soc/riscv/riscv-privilege/spike/linker.ld

/*
 * SPDX-License-Identifier: Apache-2.0
 */

/**
 * @brief Linker script for the Spike series processor
 */

#include <arch/riscv/common/linker.ld>

これでKconfig周りが整ったはずです。

最後の一手はCMakeList.txt

これでクリアかと思いきや、まだcmakeに怒られます。もう一手だけ必要です。

cmake最後のエラー
CMake Error at ../../soc/riscv/riscv-privilege/CMakeLists.txt:4 (add_subdirectory):
  The source directory

    /home/katsuhiro/share/projects/oss/zephyr/soc/riscv/riscv-privilege/spike

  does not contain a CMakeLists.txt file.


-- Configuring incomplete, errors occurred!

CMakeLists.txtがないと言っています。このファイルには何を書くのが正解なのか、私もイマイチ理解できないですが、他のSoCを見ると、下記の一行を書いておけば良さそうです。

zephyr/soc/riscv/riscv-privilege/spike/CMakeLists.txt

# SPDX-License-Identifier: Apache-2.0

zephyr_sources()

ここまで変更するとcmakeのエラーが解消します。良く見ると何やらUART_CONSOLEがどうのこうのと文句を言っていますが、後ほど対応します。

Hogeボード + Spike SoCでcmake成功

$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/
-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD hoge
-- Loading zephyr/boards/riscv/hoge/hoge.dts as base
hoge.dts.pre.tmp:22.17-25.5: Warning (simple_bus_reg): /soc/serial: missing or empty reg/ranges property
  also defined at hoge.dts.pre.tmp:37.8-39.3
Devicetree header saved to 'zephyr/build/zephyr/include/generated/devicetree_unfixed.h'
Parsing zephyr/Kconfig
Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig'
Merged configuration 'zephyr/samples/hello_world/prj.conf'
Configuration saved to 'zephyr/build/zephyr/.config'
Kconfig header saved to 'zephyr/build/zephyr/include/generated/autoconf.h'

warning: UART_CONSOLE (defined at drivers/console/Kconfig:47) was assigned the value 'y' but got the
value 'n'. Check these unsatisfied dependencies: SERIAL_HAS_DRIVER (=n). See
http://docs.zephyrproject.org/latest/reference/kconfig/CONFIG_UART_CONSOLE.html and/or look up
UART_CONSOLE in the menuconfig/guiconfig interface. The Application Development Primer, Setting
Configuration Values, and Kconfig - Tips and Best Practices sections of the manual might be helpful
too.

-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: zephyr/build

この後はninjaのビルドエラーがたくさん出るので、1つずつ解消していくことになります。続きはまた次回です。

編集者:すずき(2023/09/24 12:05)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月5日

Zephyr OSで遊ぼう その4 - 独自のSoC定義

目次: Zephyr

Hogeボードの定義を一通り実装しました。HogeボードのSoCはqemu_riscv32と同じSiFive FE310にしたことを覚えているでしょうか?Zephyrの移植に興味のある方からすると「ボード名を変えただけで、qemu_riscv32のコピーでは?」と感じるはずです。そんな悲しみの声にお答えすべく、次はSoCの変更を行います。

今回、ターゲットとするSoCはQEMUのSpikeモードです。このモードは、特別なハードウェアが必要なく手軽、偶然にも現状のZephyrが対応していない、簡単なドライバの実装が1つだけ必要、など学習にピッタリです。

Spikeはシミュレータのみで、物理的に存在するSoCではありません。ですが名前がないと説明しづらいので、以降の説明ではSpike SoCと呼びます。

RISC-V SoCディレクトリの構造とSpike SoCの定義場所

将来複雑になるかもしれませんが、今は簡単な構造です。zephyr/soc/riscvの下にSoCが定義されており、litex-vexriscv(LiteX VexRiscv), openisa_rv32m1(OpenISA RV32M1), riscv-privilege(RISC-V Priviledged Architecture対応SoC)の3つのディレクトリが存在しています。このうちriscv-privilegeは、さらに内部にいくつかのSoCの定義を抱えています。

SpikeはRISC-V Priviledgedに対応しているので、今回はriscv-privilegeシリーズの1つとして、新たなSoCを定義すると良さそうです。

ボードの定義を改造

Hogeボードの定義を見直してみると、Kconfig.boardに "depends on SOC_RISCV_SIFIVE_FREEDOM" の記述があり、hoge.dtsに #include <riscv32-fe310.dtsi> の記述があります。とりあえずこれを下記のように変更します。

Kconfig.boardとhoge.dtsの変更点

diff --git a/boards/riscv/hoge/Kconfig.board b/boards/riscv/hoge/Kconfig.board
index 7ece0d6dee..c90614a391 100644
--- a/boards/riscv/hoge/Kconfig.board
+++ b/boards/riscv/hoge/Kconfig.board
@@ -2,4 +2,4 @@

 config BOARD_HOGE
        bool "Hoge target"
-       depends on SOC_RISCV_SIFIVE_FREEDOM
+       depends on SOC_RISCV_SPIKE


diff --git a/boards/riscv/hoge/hoge.dts b/boards/riscv/hoge/hoge.dts
index 21678f49ea..ee0c6589c4 100644
--- a/boards/riscv/hoge/hoge.dts
+++ b/boards/riscv/hoge/hoge.dts
@@ -2,7 +2,7 @@

 /dts-v1/;

-#include <riscv32-fe310.dtsi>
+#include <riscv32-spike.dtsi>

 / {
        model = "Hoge";

この状態でcmakeをやり直します。念のためbuildディレクトリのファイルを全て削除してから、実行したほうが良いでしょう。

Kconfig.boardとhoge.dtsを変更してcmake
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD hoge
-- Loading zephyr/boards/riscv/hoge/hoge.dts as base
In file included from <command-line>:
zephyr/boards/riscv/hoge/hoge.dts:5:10: fatal error: riscv32-spike.dtsi: No such file or directory
 #include <riscv32-spike.dtsi>
          ^~~~~~~~~~~~~~~~~~~~
compilation terminated.
CMake Error at zephyr/cmake/dts.cmake:140 (message):
  command failed with return code: 1
Call Stack (most recent call first):
  zephyr/cmake/app/boilerplate.cmake:460 (include)
  CMakeLists.txt:5 (include)


-- Configuring incomplete, errors occurred!

エラーをみるにriscv32-spike.dtsiがないと言って怒られているようですから、追加しましょう。

SoCのデバイスツリー

Zephyrのデバイスツリーはzephyr/dts以下にあります。今回はRISC-V一派のSoCを追加しますから、zephyr/dts/riscvの下にriscv32-spike.dtsiを作りましょう。

riscv32-spike.dtsi

/* SPDX-License-Identifier: Apache-2.0 */

/ {
        #address-cells = <1>;
        #size-cells = <1>;
        compatible = "spike-dev";
        model = "spike";
        soc {
                #address-cells = <1>;
                #size-cells = <1>;
                compatible = "spike-soc", "simple-bus";
                ranges;

                clint: clint@2000000 {
                        compatible = "riscv,clint0";
                        reg = <0x02000000 0x10000>;
                        reg-names = "control";
                };

                mem: mem@80000000 {
                        compatible = "spike,mem";
                        reg = <0x80000000 0x4000>;
                        reg-names = "mem";
                };

                uart0: serial {
                        compatible = "spike,uart-spike";
                        label = "uart_0";
                };
        };
};

最低限のデバイスだけ定義しています。

clint
割り込みコントローラ(Core Local Interruptor)です。タイマーのレジスタ(mtime, mtimecmp)を持っているコアです。
mem
SRAM領域です。QEMUは数百MBくらいメモリ領域があった気がしますが、そんなに要らないので16KBにしています。
uart0
シリアルデバイスです。実はこの名前は適切ではありません。Spikeは特殊なゲスト、ホスト間の通信により文字出力を実現していて、UARTハードウェアを模している訳ではないからです。レジスタ領域の定義が不要なのも、ハードウェアアクセスではないからです。

このファイルを加えてもまだcmakeに怒られます。Spike SoC用のデバイスツリーに出てこないノード(gpio, spiなど)を参照している箇所があるためです。ボード側の定義からばっさり削除します。

hoge.dtsのさらなる変更

diff --git a/boards/riscv/hoge/hoge.dts b/boards/riscv/hoge/hoge.dts
index 21678f49ea..e572d5a769 100644
--- a/boards/riscv/hoge/hoge.dts
+++ b/boards/riscv/hoge/hoge.dts
@@ -11,34 +11,10 @@
        chosen {
                zephyr,console = &uart0;
                zephyr,shell-uart = &uart0;
-               zephyr,sram = &dtim;
-               zephyr,flash = &flash0;
+               zephyr,sram = &mem;
        };
 };
 
-&gpio0 {
-       status = "okay";
-};
-
 &uart0 {
        status = "okay";
-       current-speed = <115200>;
-       clock-frequency = <16000000>;
-};
-
-&spi0 {
-       status = "okay";
-
-       #address-cells = <1>;
-       #size-cells = <0>;
-       reg = <0x10014000 0x1000 0x20400000 0xc00000>;
-       flash0: flash@0 {
-               compatible = "issi,is25lp128", "jedec,spi-nor";
-               size = <134217728>;
-               label = "FLASH0";
-               jedec-id = [96 60 18];
-               reg = <0>;
-               // Dummy entry
-               spi-max-frequency = <0>;
-       };
 };

これでもまだcmakeは通過できません。長くなってきましたので、続きはまた今度。

編集者:すずき(2023/09/24 12:05)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月4日

Zephyr OSで遊ぼう その3 - ボードのデフォルトコンフィグ

目次: Zephyr

前回はデフォルトコンフィグがx86になっていたり、ドライバを有効にしないとリンクエラーになったり、おかしなことが起きていました。対策として手動でコンフィグを変えまくりましたが、本来は必要ありません。Zephyrのボード定義では、ボードごとにデフォルトコンフィグを持つことができるからです。

他のボード(zephyr/boards/riscv/qemu_riscv32/qemu_riscv32_defconfigなど)を見ると、ボード名_defconfigというファイルの中にCONFIG_ABCD=yという文がたくさんあります。このファイルに追加すると反映されそうです。
(2/19訂正: CONFIG_SYS_CLOCK_TICKS_PER_SEC=100を追加しないとタイマー割り込みが入りすぎて動かなくなるため、修正しました)

ボードのdefconfig: zephyr/boards/riscv/hoge/hoge_defconfig

# SPDX-License-Identifier: Apache-2.0

CONFIG_RISCV=y
CONFIG_SOC_SERIES_RISCV_SIFIVE_FREEDOM=y
CONFIG_SOC_RISCV_SIFIVE_FREEDOM=y
CONFIG_BOARD_HOGE=y
CONFIG_CONSOLE=y
CONFIG_PRINTK=y
CONFIG_SERIAL=y
CONFIG_UART_SIFIVE=y
CONFIG_UART_SIFIVE_PORT_0=y
CONFIG_UART_CONSOLE=y
CONFIG_PLIC=y
CONFIG_PINMUX=y
CONFIG_PINMUX_SIFIVE=y
CONFIG_RISCV_MACHINE_TIMER=y
CONFIG_GPIO=y
CONFIG_GPIO_SIFIVE=y
CONFIG_SYS_CLOCK_TICKS_PER_SEC=100
CONFIG_SYS_CLOCK_HW_CYCLES_PER_SEC=10000000

CONFIG_BOARD_HOGEを除けば、ほぼqemu_riscv32_defconfigと同じです。

ボードのdefconfigを追加した後のcmakeとビルド
$ cd build
$ rm -r *

$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD hoge
-- Loading zephyr/boards/riscv/hoge/hoge.dts as base
Devicetree header saved to 'zephyr/build/zephyr/include/generated/devicetree_unfixed.h'
Parsing zephyr/Kconfig
Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig'
Merged configuration 'zephyr/samples/hello_world/prj.conf'
Configuration saved to 'zephyr/build/zephyr/.config'
Kconfig header saved to 'zephyr/build/zephyr/include/generated/autoconf.h'
-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: zephyr/build


$ ninja

[0/1] Re-running CMake...
-- Zephyr version: 2.1.99
-- Selected BOARD hoge
-- Loading zephyr/boards/riscv/hoge/hoge.dts as base
Devicetree header saved to 'zephyr/build/zephyr/include/generated/devicetree_unfixed.h'
Parsing zephyr/Kconfig
Loaded configuration 'zephyr/build/zephyr/.config'
No change to configuration in 'zephyr/build/zephyr/.config'
No change to Kconfig header in 'zephyr/build/zephyr/include/generated/autoconf.h'
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: zephyr/build
[97/102] Linking C executable zephyr/zephyr_prebuilt.elf
Memory region         Used Size  Region Size  %age Used
             ROM:       12761 B        12 MB      0.10%
             RAM:        4048 B        16 KB     24.71%
        IDT_LIST:         553 B         2 KB     27.00%
[102/102] Linking C executable zephyr/zephyr.elf

この状態でcmakeから実行すると、ビルド時に文句を言われなくなります。buildディレクトリには、以前cmakeを実行したときの設定ファイルが残っていますので、一度全て削除してから再度cmakeを実行した方が良いでしょう。特に -DBOARDを変更するときは削除したほうが良いです(cmakeにも同様の内容を注意されるはず)。

次回以降は、独自のSoCの追加に挑もうと思います。

補足: デバイスツリーとDT_INST_ マクロの関係

前回は説明を省きましたが、デバイスツリーからDT_INST_ なんとかマクロへの変換ルールは下記にあります。

DT_INST_ マクロを生成している箇所

# zephyr/cmake/dts.cmake

...

  #
  # Run gen_defines.py to create a .conf file and a header file
  #

  set(CMD_NEW_EXTRACT ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/scripts/dts/gen_defines.py
  --dts ${BOARD}.dts.pre.tmp
  --bindings-dirs ${DTS_ROOT_BINDINGS}
  --conf-out ${DEVICETREE_CONF}
  --header-out ${DEVICETREE_UNFIXED_H}
  --dts-out ${PROJECT_BINARY_DIR}/zephyr.dts # As a debugging aid
  )

...


# zephyr/scripts/dts/gen_defines.py

def main():
    global conf_file
    global header_file
    global flash_area_num

    ...

    for node in sorted(edt.nodes, key=lambda node: node.dep_ordinal):
        write_node_comment(node)

        # Flash partition nodes are handled as a special case. It
        # would be nicer if we had bindings that would let us
        # avoid that, but this will do for now.
        if node.name.startswith("partition@"):
            write_flash_partition(node, flash_area_num)
            flash_area_num += 1

        if node.enabled and node.matching_compat:
            write_regs(node)
            write_irqs(node)
            write_props(node)
            write_clocks(node)
            write_spi_dev(node)
            write_bus(node)
            write_existence_flags(node)

    ...

def write_existence_flags(node):
    # Generate #defines of the form
    #
    #   #define DT_INST_<instance no.>_<compatible string> 1
    #
    # for enabled nodes. These are flags for which devices exist.

    for compat in node.compats:
        instance_no = node.edt.compat2enabled[compat].index(node)
        out(f"INST_{instance_no}_{str2ident(compat)}", 1)

...

def str2ident(s):
    # Converts 's' to a form suitable for (part of) an identifier

    return s.replace("-", "_") \
            .replace(",", "_") \
            .replace("@", "_") \
            .replace("/", "_") \
            .replace(".", "_") \
            .replace("+", "PLUS") \
            .upper()

基本的にノードのcompatibleがそのまま使われますが、C言語のマクロ名として使用できない文字はアンダースコアに置換されます。compatibleは小文字で書くことが多いですが、マクロ名は大文字に変換しています。

編集者:すずき(2023/09/24 12:04)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月3日

Zephyr OSで遊ぼう その2 - ボードを追加してビルド

目次: Zephyr

前回ninjaを実行したときに嫌というほどエラーが出ました。エラーを追う前に、コンフィグがあっているかチェックしたいと思います。

ZephyrはLinux Kernelと似たコンフィグのシステム(Kconfig)を持っています。使い方もLinuxと似ており、ninja menuconfigとすると、ケバケバしい色(私の環境だと真っ白+黄色になる……)の画面が表示されます。


menuconfigの画面

なぜかArchitectureがx86となっていたり、SoCがLiteXになっていたり、色々おかしいです。なぜこうなってしまったかは後にして、手当たり次第でそれらしい値に直すと、Board Selectionに今回追加したHoge targetが出現します。

menuconfigで手修正する箇所
$ ninja menuconfig

Architecture (x86 architecture)  --->
  ( ) RISCV architectureを選択

SoC/CPU/Configuration Selection (LiteX VexRiscv system implementation)  --->
  ( ) SiFive Freedom SOC implementationを選択

コンフィグがそれらしくなったらninjaでビルドします。

menuconfigで手修正後のビルド
$ ninja

zephyr/samples/hello_world/src/main.c:12:30: error: 'CONFIG_BOARD' undeclared (first use in this function); did you mean 'CONFIG_ARCH'?
  printk("Hello World! %s\n", CONFIG_BOARD);
                              ^~~~~~~~~~~~
                              CONFIG_ARCH

...

In file included from ../include/devicetree.h:12,
                 from ../soc/riscv/riscv-privilege/sifive-freedom/soc.h:15,
                 from ../include/arch/riscv/arch.h:25, 
                 from ../include/arch/cpu.h:23,
                 from ../include/kernel_includes.h:34, 
                 from ../include/kernel.h:17,
                 from zephyr/drivers/interrupt_controller/intc_plic.c:13:
zephyr/drivers/interrupt_controller/intc_plic.c: In function 'riscv_plic_irq_enable':
zephyr/include/generated/devicetree_fixups.h:8:25: error: 'DT_INST_0_SIFIVE_PLIC_1_0_0_IRQ_EN_BASE_ADDRESS' undeclared (first use in this function)
 #define DT_PLIC_IRQ_EN  DT_INST_0_SIFIVE_PLIC_1_0_0_IRQ_EN_BASE_ADDRESS
                         ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

...

先程よりはマシになりましたが、まだエラーが山のように出ます。エラーの種類を大別するとCONFIG_BOARDの定義がない、DT_INST_ なんちゃらの定義がない、に分けられます。

最初のCONFIG_BOARDについては、いかにもボード側で定義しなければならなさそうな名前です。おなじみ他のボードを参照(boards/riscv/qemu_riscv32/Kconfig.defconfigなど)すると、Kconfig.boardで定義したconfig BOARD_HOGEが定義されているときに限り、CONFIG_BOARDにボード名のデフォルト値を設定していました。

つまり、このように書けば良いみたいです。

Kconfig.defconfig

# SPDX-License-Identifier: Apache-2.0

if BOARD_HOGE

config BOARD
	default "hoge"

endif

それ以外のDT_INST_ なんちゃらの定義については、少し複雑です。

Zephyrのデバイスツリーの扱い

Linuxではデバイスツリーファイル(*.dts)をデバイスツリーコンパイラdtcでコンパイルして、Flattened Device Tree(fdt)という形式のバイナリにします。fdtはカーネル内部に組み込んだり、ブートローダが起動時にカーネルに渡すなどして利用されます。

しかしZephyrはそもそもdtcを使わず、Pythonで処理します。デバイスツリーを書き間違えるとPythonスクリプトが怒ってくるのですが、これが理由みたいです。なんだと?と思う方はZephyrのドキュメント(Devicetree - Zephyr Project Documentation)をご参照ください。下記に引用します。

Note: In addition to the Python code above, the standard dtc DTS compiler is also run on the devicetree. This is just to catch any errors or warnings it generates. The output is unused.

ドキュメントのGenerated macrosの章を見ると、デバイスツリーからDT_INST_ なんちゃら、というマクロを生成する、という説明があり、ビルドエラーの原因となったマクロの仲間のようです。

デバイスツリーを足そう

推測するにデバイスツリーを何も定義していないことが、DT_INST_ なんちゃらマクロが存在しないことの原因ではないでしょうか?他のボード(boards/riscv/qemu_riscv32/qemu_riscv32.dtsなど)がデバイスツリーをどうしているか、見ながら真似してみます。

hoge.dts

/* SPDX-License-Identifier: Apache-2.0 */

/dts-v1/;

#include <riscv32-fe310.dtsi>

/ {
	model = "Hoge Board";
	compatible = "hoge,hoge";

	chosen {
		zephyr,console = &uart0;
		zephyr,shell-uart = &uart0;
		zephyr,sram = &dtim;
		zephyr,flash = &flash0;
	};
};

&gpio0 {
	status = "okay";
};

&uart0 {
	status = "okay";
	current-speed = <115200>;
	clock-frequency = <16000000>;
};

&spi0 {
	status = "okay";

	#address-cells = <1>;
	#size-cells = <0>;
	reg = <0x10014000 0x1000 0x20400000 0xc00000>;
	flash0: flash@0 {
		compatible = "issi,is25lp128", "jedec,spi-nor";
		size = <134217728>;
		label = "FLASH0";
		jedec-id = [96 60 18];
		reg = <0>;
		// Dummy entry
		spi-max-frequency = <0>;
	};
};

ほぼ全てコピーしただけです。ビルドしてみると、最後の方まで行きますが、いくつかシンボルがないと言われます。ログはフルパスが出ていて鬱陶しいので意味が残る程度に削っています。

デバイスツリー定義後のビルドエラー
$ ninja

...

riscv64-zephyr-elf/bin/ld: zephyr/arch/arch/riscv/core/libarch__riscv__core.a(isr.S.obj): in function `.L0 ':
zephyr/arch/riscv/core/isr.S:244: undefined reference to `_sw_isr_table'
riscv64-zephyr-elf/bin/ld: zephyr/kernel/libkernel.a(sched.c.obj): in function `z_reset_time_slice':
zephyr/kernel/sched.c:276: undefined reference to `z_clock_elapsed'
riscv64-zephyr-elf/bin/ld: zephyr/kernel/libkernel.a(timeout.c.obj): in function `elapsed':
zephyr/kernel/timeout.c:69: undefined reference to `z_clock_elapsed'
collect2: error: ld returned 1 exit status
%
ninja: build stopped: subcommand failed.

これらのシンボルはドライバが提供するもののようですので、コンフィグでそれらしきものをONにしていきます。

コンフィグ追加
General Architecture Options  --->
  Interrupt Configuration  --->
    [ ] Use generated IRQ tablesを選択

General Kernel Options  --->
  [ ] Execute in place

Device Drivers  --->
  [ ] Serial Drivers  ----
    [ ] Enable UART Interrupt supportを選択
    [ ] SiFive Freedom serial driver (NEW)  ----
      [ ] Enable SIFIVE Port 0 (NEW)  ---- を選択

  [ ] Console drivers  ---
    [ ] Use UART for console (NEW) を選択

  Timer Drivers  --->
    [ ] RISCV Machine Timerを選択

  [ ] GPIO Drivers  ----
    [ ] SiFive Freedom Processor GPIO driver (NEW)  ---- を選択

  [ ] Enable board pinmux driver  ----
    [ ] SiFive Freedom SOC pinmux driver (NEW)  ----

このように全て手動で設定する必要はない(後々不要となる)ので、あまり詳しくなる必要はないですが、

SiFive関連
SiFiveのSoCなのでコンフィグを片っ端からONにしています
Use generated IRQ tables
_sw_isr_tableシンボルの定義に関わっています
Execute in Place(XIP)
ONにしないと、ビルドしたときにRAMサイズから溢れているというエラーで怒られます
RISCV Machine Timer
z_clock_elapsedシンボルの定義に関わっています

コンフィグがそれらしくなったらninjaでビルドして、実行します。

コンフィグ追加後、ビルド&実行
$ ninja

...

Memory region         Used Size  Region Size  %age Used
             ROM:       13165 B        12 MB      0.10%
             RAM:        3808 B        16 KB     23.24%
        IDT_LIST:         569 B         2 KB     27.78%
[98/98] Linking C executable zephyr/zephyr.elf

$ /usr/bin/qemu-system-riscv32 -nographic -machine sifive_e -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel /home/katsuhiro/share/projects/oss/zephyr/build/zephyr/zephyr.elf

*** Booting Zephyr OS build zephyr-v2.1.0-1471-g7e7a4426d835  ***
Hello World! hoge

実行できました。Hello Worldの後ろがボード名(CONFIG_BOARD)です。無事、アプリがhogeボード上で動いたということですね。次回は面倒くさかった手動コンフィグの撲滅に挑みます。

編集者:すずき(2023/09/24 12:04)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月2日

Zephyr OSで遊ぼう その1 - ボードを追加してcmake

目次: Zephyr

先日(2020年1月31日の日記参照)RISC-V 32bit版のZephyr OSが動作しました。気分を変えて、別のボードへの移植に挑戦してみたいと思います。

名前は何でも良いのですが、とりあえずhogeボードということにします。-DBOARD=hogeを指定してcmakeを実行すると、そんなものはないと怒られ、猛烈な勢いでヘルプメッセージが出るとともに、サポートされているボードの一覧が出てきます。

存在しないボードを指定したときの、ボード一覧
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD hoge
No board named 'hoge' found
see usage:

...

Supported Boards:

...

  riscv:
    hifive1
    hifive1_revb
    litex_vexriscv
    m2gl025_miv
    qemu_riscv32
    qemu_riscv64
    rv32m1_vega_ri5cy
    rv32m1_vega_zero_riscy
  x86:
    acrn
    gpmrb
    minnowboard
    qemu_x86_64
    qemu_x86_coverage
    qemu_x86
    qemu_x86_nommu
    up_squared

...

まず、この一覧にhogeボードを載せるのが第一段階です。ボードの一覧を調べているcmakeのスクリプトを見ます。

ボード一覧を調べている箇所

# zephyr/cmake/app/boilerplate.cmake

...

foreach(root ${BOARD_ROOT})
  # NB: find_path will return immediately if the output variable is
  # already set
  find_path(BOARD_DIR
    NAMES ${BOARD}_defconfig
    PATHS ${root}/boards/*/*
    NO_DEFAULT_PATH
    )
  if(BOARD_DIR AND NOT (${root} STREQUAL ${ZEPHYR_BASE}))
    set(USING_OUT_OF_TREE_BOARD 1)
  endif()

...

これで探しているように見えます。boards/*/* ディレクトリに「ボード名_defconfig」という名前のファイルがあれば良さそうです。足してからcmakeを再度、存在しないボード名(hoge2とかで良いです)で実行しましょう。

defconfigを足した後のボード一覧
$ mkdir boards/riscv/hoge
$ touch boards/riscv/hoge/hoge_defconfig

$ cmake -G Ninja -DBOARD=hoge2 ../samples/hello_world/

...

  riscv:
    hifive1
    hifive1_revb
    hoge        ★hogeが出てきた★
    litex_vexriscv
    m2gl025_miv
    qemu_riscv32
    qemu_riscv64
    rv32m1_vega_ri5cy
    rv32m1_vega_zero_riscy

...

やりました。ボードの一覧にhogeが出ました。BOARD=hogeにしてcmakeを実行しましょう。

defconfigを足した後のcmake
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Selected BOARD hoge
Parsing zephyr/Kconfig
zephyr/scripts/kconfig/kconfig.py: Kconfig.zephyr:26: 'zephyr/boards/riscv/hoge/Kconfig.defconfig' not found (in 'source "$(BOARD_DIR)/Kconfig.defconfig"'). Check that environment variables are set correctly (e.g. $srctree, which is set to 'zephyr'). Also note that unset environment variables expand to the empty string.
CMake Error at zephyr/cmake/kconfig.cmake:214 (message):
  command failed with return code: 1
Call Stack (most recent call first):
  zephyr/cmake/app/boilerplate.cmake:461 (include)
  CMakeLists.txt:5 (include)

-- Configuring incomplete, errors occurred!

まだ色々とエラーが出ています。エラーメッセージはKconfig.defconfigがないと言っています。このファイルを足すと、今度はKconfig.boardがないと言われますので、両方とも足します。

Kconfig.defconfig, Kconfig.boardを足した後のcmake
$ touch boards/riscv/hoge/Kconfig.defconfig
$ touch boards/riscv/hoge/Kconfig.board

$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD hoge
Parsing zephyr/Kconfig
Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig'
Merged configuration 'zephyr/samples/hello_world/prj.conf'

warning: <choice> (defined at boards/Kconfig:19) defined with type unknown

error: Aborting due to non-whitelisted Kconfig warning 'warning: <choice> (defined at boards/Kconfig:19)
defined with type unknown'. If this warning doesn't point to an actual problem, you can add it to
the whitelist at the top of zephyr/scripts/kconfig/kconfig.py.

CMake Error at zephyr/cmake/kconfig.cmake:214 (message):
  command failed with return code: 1
Call Stack (most recent call first):
  zephyr/cmake/app/boilerplate.cmake:461 (include)
  CMakeLists.txt:5 (include)


-- Configuring incomplete, errors occurred!

今度はchoiceが無いと言っています。エラーを出しているのは下記のcmakeファイルです。

エラーを出しているkconfig.cmake
# zephyr/cmake/kconfig.cmake

execute_process(
  COMMAND
  ${PYTHON_EXECUTABLE}
  ${ZEPHYR_BASE}/scripts/kconfig/kconfig.py
  ${input_configs_are_handwritten}
  ${KCONFIG_ROOT}
  ${DOTCONFIG}
  ${AUTOCONF_H}
  ${PARSED_KCONFIG_SOURCES_TXT}
  ${input_configs}
  WORKING_DIRECTORY ${APPLICATION_SOURCE_DIR}
  # The working directory is set to the app dir such that the user
  # can use relative paths in CONF_FILE, e.g. CONF_FILE=nrf5.conf
  RESULT_VARIABLE ret
  )
if(NOT "${ret}" STREQUAL "0")
  message(FATAL_ERROR "command failed with return code: ${ret}")
endif()

このままkconfig.pyを見ても良いですが、おそらく時間のムダです。親切なエラーメッセージが出ているからです。メッセージの言うとおりboards/Kconfigを見ます。

choiceのエラーの原因
# boards/Kconfig

...

# Note: $BOARD_DIR might be a glob pattern

choice
	prompt "Board Selection"

source "$(BOARD_DIR)/Kconfig.board"    ★このファイルが空★

endchoice

先ほど作成したKconfig.boardが空っぽだったため、choiceの選択肢が存在せず怒られているようです。書く内容については、別のボード(例えばzephyr/boards/riscv/qemu_riscv32/Kconfig.board)を参照すると良いと思います。今回はこんな内容にしました。

Kconfig.boardを書き換え

# SPDX-License-Identifier: Apache-2.0
 
config BOARD_HOGE
	bool "Hoge target"
	depends on SOC_RISCV_SIFIVE_FREEDOM

この定義だとSiFiveのFreedomが搭載されていることになりますが、depends onの先に手を出すには、SoCの定義を加えなければなりません。一度に紹介しても訳がわからないので、今回はボードに焦点を絞ります。

書き換えた後にもう一度cmakeを実行すると、成功します。

Kconfig.boardを書き換えた後にcmake実行
$ cmake -G Ninja -DBOARD=hoge ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD hoge
Parsing zephyr/Kconfig
Loaded configuration 'zephyr/boards/riscv/hoge/hoge_defconfig'
Merged configuration 'zephyr/samples/hello_world/prj.conf'
Configuration saved to 'zephyr/build/zephyr/.config'
-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: zephyr/build


$ ninja

[0/1] Re-running CMake...
-- Zephyr version: 2.1.99
-- Selected BOARD hoge
Parsing zephyr/Kconfig
Loaded configuration 'zephyr/build/zephyr/.config'
No change to 'zephyr/build/zephyr/.config'
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: zephyr/build
[5/83] Building C object zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj
FAILED: zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj
ccache /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc -DBUILD_VERSION=zephyr-v2.1.0-1471-g7e7a4426d835 -DKERNEL -D_FORTIFY_SOURCE=2 -D__ZEPHYR__=1 -I../kernel/include -I../arch/riscv/include -I../include -Izephyr/include/generated -I../soc/riscv/litex-vexriscv -isystem ../lib/libc/minimal/include -isystem /home/katsuhiro/x-tools/riscv64-zephyr-elf/lib/gcc/riscv64-zephyr-elf/8.3.0/include -isystem /home/katsuhiro/x-tools/riscv64-zephyr-elf/lib/gcc/riscv64-zephyr-elf/8.3.0/include-fixed -Os -imacroszephyr/build/zephyr/include/generated/autoconf.h -ffreestanding -fno-common -g -mabi=ilp32 -march=rv32ima -imacroszephyr/include/toolchain/zephyr_stdint.h -Wall -Wformat -Wformat-security -Wno-format-zero-length -Wno-main -Wno-pointer-sign -Wpointer-arith -Wno-unused-but-set-variable -Werror=implicit-int -fno-asynchronous-unwind-tables -fno-pie -fno-pic -fno-strict-overflow -fno-reorder-functions -fno-defer-pop -fmacro-prefix-map=zephyr/samples/hello_world=CMAKE_SOURCE_DIR -fmacro-prefix-map=zephyr=ZEPHYR_BASE -ffunction-sections -fdata-sections -std=c99 -nostdinc -MD -MT zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj -MF zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj.d -o zephyr/CMakeFiles/offsets.dir/arch/riscv/core/offsets/offsets.c.obj   -c zephyr/arch/riscv/core/offsets/offsets.c
In file included from ../include/sys/atomic.h:468,
                 from ../include/kernel_includes.h:21, 
                 from ../include/kernel.h:17,
                 from zephyr/arch/riscv/core/offsets/offsets.c:16:
zephyr/include/generated/syscalls/atomic.h:25:19: error: conflicting types for 'atomic_cas'
 static inline int atomic_cas(atomic_t * target, atomic_val_t old_value, atomic_val_t new_value)
                   ^~~~~~~~~~
In file included from ../include/kernel_includes.h:21, 
                 from ../include/kernel.h:17,
                 from zephyr/arch/riscv/core/offsets/offsets.c:16:
../include/sys/atomic.h:44:20: note: previous definition of 'atomic_cas' was here
 static inline bool atomic_cas(atomic_t *target, atomic_val_t old_value,

せっかくcmakeがうまくいった、と思ったのも束の間で、ninjaは見るのが嫌になるくらい大量のエラーを表示して失敗します。続きはまた今度。

編集者:すずき(2023/09/24 12:04)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年2月1日

Zephyrのエントリアドレス

目次: Zephyr

Zephyrをqemu_riscv32ボード向けにコンフィグし、ビルドディレクトリ下でninja runを実行すると、最終的に下記のコマンドが実行され、QMEUが起動します。

QEMUの起動コマンド
$ /usr/bin/qemu-system-riscv32 -nographic -machine sifive_e -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf -s -S

GDBで追うとわかりますが、QEMUの実行開始アドレスは0x1000です。0x1000には0x20400000にジャンプするコードだけが置かれています。

0x1000にあるコード

   0x1000:      lui     t0,0x20400
=> 0x1004:      jr      t0
   0x1008:      unimp
   0x100a:      unimp

HiFive1実機であれば0x20400000はQSPI Flashがマッピングされているアドレスです(SiFive FE310-G000 Manual v2p3を参照)。QEMUの場合はELFファイルbuild/zephyr/zephyr.elfのセクション情報通りに配置されます。

0x1000にあるコード
$ riscv64-zephyr-elf-readelf -a build/zephyr/zephyr.elf  | less

...

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] vector            PROGBITS        20400000 000094 000010 00  AX  0   0  4
  [ 2] exceptions        PROGBITS        20400010 0000a4 000258 00  AX  0   0  4
  [ 3] text              PROGBITS        20400268 0002fc 002ad4 00  AX  0   0  4
  [ 4] sw_isr_table      PROGBITS        20402d3c 002dd0 000200 00  WA  0   0  4
  [ 5] devconfig         PROGBITS        20402f3c 002fd0 000060 00   A  0   0  4
  [ 6] rodata            PROGBITS        20402f9c 003030 000215 00   A  0   0  4
  [ 7] datas             PROGBITS        80000000 003248 000018 00  WA  0   0  4
  [ 8] initlevel         PROGBITS        80000018 003260 000060 00  WA  0   0  4
  [ 9] _k_mutex_area     PROGBITS        80000078 0032c0 000014 00  WA  0   0  4
  [10] bss               NOBITS          80000090 0032e0 00013c 00  WA  0   0  8
  [11] noinit            NOBITS          800001d0 0032e0 000e00 00  WA  0   0 16

...

ジャンプ先の0x20400000にはvectorというセクションが対応していることがわかります。ELFファイルを逆アセンブルしてみると、

0x20400000にあるコード

zephyr/zephyr.elf:     file format elf32-littleriscv


Disassembly of section vector:

20400000 <__start>:

        /*
         * Set mtvec (Machine Trap-Vector Base-Address Register)
         * to __irq_wrapper.
         */
        la t0, __irq_wrapper
20400000:       00000297                auipc   t0,0x0
20400004:       01028293                addi    t0,t0,16 # 20400010 <__irq_wrapper>
        csrw mtvec, t0
20400008:       30529073                csrw    mtvec,t0

        /* Jump to __initialize */
        tail __initialize
2040000c:       4b40106f                j       204014c0 <__initialize>

以上のように __startという関数が居るようです。実装はzephyr/soc/riscv/riscv-privilege/common/vector.Sにあります。

Zephyrとデバイスツリー

このエントリポイントアドレスはどのように決まるのでしょう?Zephyrでは、ボード用のデバイスツリーzephyr/boards/riscv/qemu_riscv32/qemu_riscv32.dtsにあるFlashの先頭アドレスを変えるとELFのエントリポイント(0x20400000)も追従します。

ボード用のデバイスツリーのchosenノード

        chosen {
                zephyr,console = &uart0;
                zephyr,shell-uart = &uart0;
                zephyr,sram = &dtim;
                zephyr,flash = &flash0;
        };

このchosen以下を検知しているようです。スクリプトscripts/dts/gen_defines.pyを見ると、SPIのときだけ特殊処理になっていて、それ以外はregの値を先頭アドレスとみなしています。

Flashのサイズを見ている箇所

# zephyr/scripts/dts/gen_defines.py

...

def write_flash_node(edt):
    # Writes output for the top-level flash node pointed at by
    # zephyr,flash in /chosen

    node = edt.chosen_node("zephyr,flash")

    out_comment(f"/chosen/zephyr,flash ({node.path if node else 'missing'})")

    if not node:
        # No flash node. Write dummy values.
        out("FLASH_BASE_ADDRESS", 0)
        out("FLASH_SIZE", 0)
        return

    if len(node.regs) != 1:
        err("expected zephyr,flash to have a single register, has "
            f"{len(node.regs)}")

    if node.on_bus == "spi" and len(node.bus_node.regs) == 2:
        reg = node.bus_node.regs[1]  # QSPI flash
    else:
        reg = node.regs[0]

    out("FLASH_BASE_ADDRESS", hex(reg.addr))
    if reg.size:
        out("FLASH_SIZE", reg.size//1024)

...

FlashのアドレスはDT_FLASH_BASE_ADDRESSというマクロで参照できます。エントリーポイントを最終的に決めるのはリンカースクリプトです。SoCで独自のリンカースクリプトを実装することもできるようになっていますが、RISC-Vの多くのSoCは、下記のスクリプトを使っています。

エントリーポイントを決めている箇所

// zephyr/include/arch/riscv/common/linker.ld

MEMORY
{
#ifdef CONFIG_XIP
    ROM (rx)  : ORIGIN = DT_FLASH_BASE_ADDRESS, LENGTH = KB(DT_FLASH_SIZE)
#endif
    RAM (rwx) : ORIGIN = CONFIG_SRAM_BASE_ADDRESS, LENGTH = KB(CONFIG_SRAM_SIZE)
    /* Used by and documented in include/linker/intlist.ld */
    IDT_LIST  (wx)      : ORIGIN = 0xFFFFF7FF, LENGTH = 2K
}

Zephyrのエントリポイントがどうやって決まるのか、少しわかった気がします。

編集者:すずき(2023/09/24 12:02)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年1月31日

Hello! Zephyr OS!! (RISC-V 32bit版)

目次: Zephyr

以前Zephyr OSを実行しました(2019年1月12日の日記参照)が、今回はRISC-V 32bit版で試してみようと思います。

この例ではbuildをビルドディレクトリとします。どこに置いても動くはずですが、私はとりあえずzephyrの下に置いています。

クロスコンパイラの準備

前回同様Zephyr SDKもしくはCrosstool-NGが使えます。参考までにCrosstool-NGのコンフィグを載せておきます。

crosstool-NG RISC-V版のビルド
$ ./ct-ng menuconfig

Target options  --->
  Target Architecture (riscv)  --->
  [*] Build a multilib toolchain (READ HELP!!!)
  Bitness: (64-bit)  --->
  (rv32ima) Architecture level
  (ilp32) Generate code for the specific ABI

Toolchain options  --->
  (zephyr) Tuple's vendor string

Debug facilities  --->
  [*] gdb  --->
    [*]     Build a static cross gdb

一見すると32bit CPUのコードをビルドする予定なのに、Bitness: 64bitにしており、不思議なコンフィグに見えるかもしれませんが、Zephyr OSのビルドはクセがあって、この設定が必要です。

Architecture level, Generate code for the specific ABIはGCCが生成するバイナリの命令セットを指定しており、それぞれ -march=rv32ima, -mabi=ilp32に対応します。特に何も指定しないとrv32gc, ilp32fになるようです。

ZephyrのCMakeスクリプト
zephyr/cmake/toolchain/xtools/target.cmake

set(CROSS_COMPILE_TARGET_riscv   riscv64-zephyr-elf)

set(CROSS_COMPILE_TARGET ${CROSS_COMPILE_TARGET_${ARCH}})

ZephyrのCMakefileを見るとわかるんですが、riscv64もriscv32も区別せず同じコンパイラでビルドし、しかもコンパイラ名は常にriscv64-zephyr-elf-gccだと思っています。したがって64bit版をビルドする必要があり、multilibを有効にしています。

(余談)riscv向けの -mabi, -marchオプションの値を決めているのはzephyr/cmake/compiler/gcc/target.cmakeで、rv32ima固定になっています


-    list(APPEND TOOLCHAIN_C_FLAGS -mabi=ilp32 -march=rv32ima)
+    list(APPEND TOOLCHAIN_C_FLAGS -mabi=ilp32f -march=rv32gc)

上記のように変えると、Crosstool-NGでArchitecture level, Generate code for the specific ABIの設定をしなくても良くなりますが、動くかどうかは試していません。

ビルドの方法

実ボードを持っていないのでQEMUで試します。SiFive HiFive1をエミュレートしているそうです。

環境変数のセットアップとcmakeの実行
$ cat ~/.zephyrrc 

export ZEPHYR_TOOLCHAIN_VARIANT=xtools
export XTOOLS_TOOLCHAIN_PATH=/home/katsuhiro/x-tools

$ cd zephyr
$ source zephyr-env.sh

$ mkdir build
$ cd build

$ cmake -G Ninja -DBOARD=qemu_riscv32 ../samples/hello_world/

-- Zephyr version: 2.1.99
-- Found PythonInterp: /usr/bin/python3 (found suitable version "3.7.6", minimum required is "3.6")
-- Selected BOARD qemu_riscv32
-- Loading /home/katsuhiro/share/projects/oss/zephyr/boards/riscv/qemu_riscv32/qemu_riscv32.dts as base
Devicetree configuration written to /home/katsuhiro/share/projects/oss/zephyr/build/zephyr/include/generated/devicetree.conf
Parsing /home/katsuhiro/share/projects/oss/zephyr/Kconfig
Loaded configuration '/home/katsuhiro/share/projects/oss/zephyr/boards/riscv/qemu_riscv32/qemu_riscv32_defconfig'
Merged configuration '/home/katsuhiro/share/projects/oss/zephyr/samples/hello_world/prj.conf'
Configuration saved to '/home/katsuhiro/share/projects/oss/zephyr/build/zephyr/.config'
-- The C compiler identification is GNU 8.3.0
-- The CXX compiler identification is GNU 8.3.0
-- The ASM compiler identification is GNU
-- Found assembler: /home/katsuhiro/x-tools/riscv64-zephyr-elf/bin/riscv64-zephyr-elf-gcc
-- Cache files will be written to: /home/katsuhiro/.cache/zephyr
-- Configuring done
-- Generating done
-- Build files have been written to: /home/katsuhiro/share/projects/oss/zephyr/build

実行するときはninja runで良いんですが、前回同様にninjaが何を起動しているか調べてみます。

Zephyr OS実行
$ /usr/bin/qemu-system-riscv32 -nographic -machine sifive_e -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel /home/katsuhiro/share/projects/oss/zephyr/build/zephyr/zephyr.elf -s -S

(gdbからcontinueをすると、下記が出力される)

*** Booting Zephyr OS build zephyr-v2.1.0-1471-g7e7a4426d835  ***
Hello World! qemu_riscv32

オプション -sはGDBの接続をlocalhost:1234で受け付けます、という意味です。オプション -SはエミュレータをHalted状態で起動します。もしGDBをつなぐ必要がなければ -sや -Sを削って起動してください。

GDB接続
$ riscv64-zephyr-elf-gdb

GNU gdb (crosstool-NG 1.24.0.60-a152d61) 8.3.1
Copyright (C) 2019 Free Software Foundation, Inc.

...

(gdb) set arch riscv:rv32
The target architecture is assumed to be riscv:rv32

(gdb) target remote localhost:1234

Remote debugging using localhost:1234
warning: No executable has been specified and target does not support
determining executable automatically.  Try using the "file" command.
0x00001000 in ?? ()

(gdb) continue

Continuing.

実行できました。おなじみのHello World! です。

編集者:すずき(2023/09/24 12:01)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



2020年1月27日

クロスビルド用ツールチェーン - GCC 10.0にしたらハマった

目次: GCC

新し目のAArch64のクロスコンパイル用ツールチェーンを作ろうとして、かなりハマったのでメモしておきます。

基本的には前回(2019年4月29日の日記参照)ご紹介した手順でビルドします。GCCとglibcのコードを変えなくて良い組み合わせは下記の通りです。特に新しいバージョンを使う理由がなければ、この組み合わせが無難です。

  • gcc: 8.3.0 (tag: releases/gcc-8.3.0)
  • glibc: 2.28 (tag: glibc-2.28)

私は新しいGCCが使いたかったので、HEADにしました(バージョン的には10.0相当)。どうやらGCCのエラーチェックが厳しくなるらしく、glibcのビルドが通らなくなります。たくさんエラーが出ますが、一例を挙げると、下記のようなエラーです。

GCC HEAD (GCC 10.0相当) でglibc 2.28をビルドするとコンパイルエラー
./../include/libc-symbols.h:534:26: error: '__EI___errno_location' specifies less restrictive attributes than its target '__errno_location': 'const', 'nothrow' [-Werror=missing-attributes]
  534 |   extern __typeof (name) __EI_##name \
      |                          ^~~~~

エラーはglibcを新しくすると解決されるかと思いきや、よりおかしなことになります。例えばGCC 8.3のままglibc 2.30(おそらく2.29でも同じ症状が出る)にすると、下記のような変なエラーが出ます。

GCC 8.3でglibc 2.30をビルドするとリンクエラー
crosstool-builder-new-aarch64/buildroot/lib/gcc/aarch64-unknown-linux-gnu/8.3.0/../../../../aarch64-unknown-linux-gnu/bin/ld: crosstool-builder-new-aarch64/build/glibc/support/links-dso-program.o: Relocations in generic ELF (EM: 62)
crosstool-builder-new-aarch64/buildroot/lib/gcc/aarch64-unknown-linux-gnu/8.3.0/../../../../aarch64-unknown-linux-gnu/bin/ld: crosstool-builder-new-aarch64/build/glibc/support/links-dso-program.o: Relocations in generic ELF (EM: 62)
...

エラーの原因となっているオブジェクトlinks-dso-program.oを調べると、AArch64向けにビルドしているにも関わらず、なぜかx86_64用のオブジェクトが生成されています。

glibc/support下に生成されたオブジェクト
build/glibc/support$ file *.o

echo-container.o:    ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
links-dso-program.o: ELF 64-bit LSB relocatable, x86-64, version 1 (SYSV), with debug_info, not stripped    ★★★★これ★★★★
shell-container.o:   ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
stamp.o:             empty
test-container.o:    ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped
true-container.o:    ELF 64-bit LSB relocatable, ARM aarch64, version 1 (SYSV), with debug_info, not stripped

いったい何ですかね、これ。バグなのか、仕様なのかわかりません……。

GCC 10.0とglibc 2.30の組み合わせでビルドする方法

GCCとglibcをお互い最新にした組み合わせ、すなわち下記の組み合わせにしたとき、

  • gcc: 10.0 (HEAD)
  • glibc: 2.30 (tag: glibc-2.30)

先ほど説明した、両方のエラーに遭遇してビルドできませんので、glibcにパッチを当ててビルドエラーを回避します。

まずはコンパイルエラーを無視するパッチです。本来はエラーを無視するのではなく、エラーが指摘している事項を直すべきですけど、今回の主眼ではないのと、いずれglibc本家が直るだろうことを期待しておきます。

glibcのビルドエラーをあえて無視するパッチ

diff --git a/Makeconfig b/Makeconfig
index fd36c58c04..106688e210 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -916,7 +916,8 @@ ifeq	"$(strip $(+cflags))" ""
 endif	# $(+cflags) == ""
 
 +cflags += $(cflags-cpu) $(+gccwarn) $(+merge-constants) $(+math-flags) \
-	   $(+stack-protector)
+	   $(+stack-protector) \
+	   -Wno-zero-length-bounds -Wno-array-bounds -Wno-maybe-uninitialized
 +gcc-nowarn := -w
 
 # Each sysdeps directory can contain header files that both will be

次のlinks-dso-programはコミットログを見る限り、テストのサポート用ライブラリなので、とりあえず無くても動くはずです。ビルド自体をやめるパッチをあてます。

glibcのsupport/links-dso-programをビルドさせないパッチ

diff --git a/support/Makefile b/support/Makefile
index ab66913a02..19c3de2043 100644
--- a/support/Makefile
+++ b/support/Makefile
@@ -184,12 +184,12 @@ CFLAGS-support_paths.c = \
 		-DSBINDIR_PATH=\"$(sbindir)\" \
 		-DROOTSBINDIR_PATH=\"$(rootsbindir)\"
 
-ifeq (,$(CXX))
-LINKS_DSO_PROGRAM = links-dso-program-c
-else
-LINKS_DSO_PROGRAM = links-dso-program
-LDLIBS-links-dso-program = -lstdc++ -lgcc -lgcc_s $(libunwind)
-endif
+#ifeq (,$(CXX))
+#LINKS_DSO_PROGRAM = links-dso-program-c
+#else
+#LINKS_DSO_PROGRAM = links-dso-program
+#LDLIBS-links-dso-program = -lstdc++ -lgcc -lgcc_s $(libunwind)
+#endif
 
 ifeq (yes,$(have-selinux))
 LDLIBS-$(LINKS_DSO_PROGRAM) += -lselinux

クロスコンパイル環境は、各モジュールのバージョンアップですぐ壊れてしまって辛いです。ARMがこれだけ覇権を握っているにも関わらず、gccもglibcもあまりチェックしてないんですかね……??

後日追記

わざわざMakefileを書き換えなくてもmake LINKS_DSO_PROGRAM= のように、make実行時にLINKS_DSO_PROGRAM変数の値を強制的に空文字列に上書きすれば回避可能でした。理由は昔の日記で書いた通り(2019年9月17日の日記参照)、コマンドラインからの変数指定はMakefile内の代入より強いからです。

Makefile書き換えよりは多少スマートですけども、クロスコンパイルの時だけこんな指定が必要なのは妙ですね。まだ何か見落としているんでしょうかね?

編集者:すずき(2023/09/24 11:43)

コメント一覧

  • superzerosさん(2020/04/15 19:59)
    ARM64 target (cortexa53)と x86_64 host (Ryzen7)のクロスコンパイラのビルドで、当方にもご指摘のエラーがありました。で、そのパッチを拝借したところ、見事GLIBCのコンパイルが通りました。

    ありがとうございました!

    なんと申しましょうか、とても柔軟かつ大胆に繊細な対応をなされますね。

    補足:当方の各パッケージのバージョンは latest(2020-04-15)
    linux-5.6.4 (headers)
    gcc-9.3.0
    glibc-2.31
    binutils-2.34
    configure やBUILD手法は archlinux PKGBUILD と Cross LFS BOOK 等のWEB情報を参考にして適当にやっております(笑
  • すずきさん(2020/04/15 23:13)
    コメントありがとうございます。お役に立ったようで良かったです。

    動作に支障無さそうなところだったので、かなり適当に変えてしまいました。

    大胆というか適当というか……。
  • superzerosさん(2020/04/16 21:07)
    少し気になったのでglibcの履歴を調べてみたら・・・

    glibc-2.31/support/links-dso-program-c.c
    glibc-2.31/support/links-dso-program.cc

    このファイルは、2.29で最初に追加され

    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    #include <iostream>
    using namespace std;
    int
    main (int argc, char **argv)
    /* Complexity to keep gcc from optimizing this away. */
    cout << (argc > 1 ? argv[1] : "null");
    return 0;


    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    で、続いて2.30では

    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    #include <iostream>
    /* makedb needs selinux dso's. */
    #ifdef HAVE_SELINUX
    # include <selinux/selinux.h>
    #endif
    using namespace std;
    /* The purpose of this file is to indicate to the build system which
    shared objects need to be copied into the testroot, such as gcc or
    selinux support libraries. This program is never executed, only
    scanned for dependencies on shared objects, so the code below may
    seem weird - it's written to survive gcc optimization and force
    such dependencies.
    */
    int
    main (int argc, char **argv)
    /* Complexity to keep gcc from optimizing this away. */
    cout << (argc > 1 ? argv[1] : "null");
    #ifdef HAVE_SELINUX
    /* This exists to force libselinux.so to be required. */
    cout << "selinux " << is_selinux_enabled ();
    #endif
    return 0;


    \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\

    2.29では、どういう経緯で誰によって導入されたのか、知りませんけど
    2.30では、SElinux(国家安全保障局)がどうたらこうたらの追加です
    誰のためになるんでしょうか ・・・

    自分の所有するハードウエアを自分で使いこなしたいだけのわたしからすれば
    バージョンアップに紛れ込んだオープンソースへの破壊活動みたいなもんですよ
    貴重な時間を奪われるところでしたが、おかげさまですぐに解決できてよかったです
    貴方に感謝いたします

      \(^o^)/
  • すずきさん(2020/04/18 23:05)
    詳細は調べていないので、コード中のコメントからの想像ですが……破壊活動というほどではないかなと思います。

    SELinux が有効なときは、SELinux のライブラリがないと動かないのでしょう。このヘルパープログラムは SELinux のライブラリを使っているから、ビルドシステムはテスト用の rootfs に SELinux のライブラリをコピーしてくれ、ということを示すための変更だと思います。
open/close この記事にコメントする



2020年1月26日

C言語の未定義動作と最適化

目次: C言語とlibc

くそ長いですが、C言語の未定義動作怖いね、printfでタイミング以外も動き変えられるよ、という話です。

環境ですがx86_64向けDebian GNU/Linux 9.2で実行しています。またGCCのバージョンはgcc (Debian 9.2.1-22) 9.2.1 20200104です。

未定義動作のため、コンパイラの種類や、GCCのバージョンにより結果が変わると思われます。お家のマシンで試すならご留意ください。

1番目の実験

この日記の最後に貼ったプログラム(このプログラムをコンパイルすると、激しい警告が出ます)をgcc -Wall -O2 a.c && ./a.outのように実行すると、

1番目の実験: あれ?バッファオーバーランは…?
0: 0 0 0
1: 0 1 0
2: 0 2 0
3: 0 3 0
4: 0 4 0
...
47: 0 47 0
48: 0 48 0
49: 0 49 0
1770: 1770

こうなります。0〜59の和は1770です。あってます。良かったですね。

なに?そういう問題じゃない?「なぜarray終端を超えてguard2にバッファオーバーランしない?」と考えた方、するどいです。しかし世の中そう単純ではありません。

2番目の実験

10行目のprintfのコメントを外してローカル変数のアドレスを表示させると、

2番目の実験: 10行目のprintfを有効、突然のバッファオーバーラン
0x7ffd9b348a10 0x7ffd9b348ae0 0x7ffd9b348bb0
0: 0 0 52
1: 0 1 53
2: 0 2 54
3: 0 3 55
4: 0 4 56
...
45: 0 45 0
46: 0 46 0
47: 0 47 0
48: 0 48 0
49: 0 49 0
1770: 1770

こうなります。突然オーバーランするようになりました。printfが何かしたんでしょうか、不思議ですね?

3番目の実験

どうしてforループを無意味に2分割したのか?くっつけてみたらわかります。Segmentation Fault します。

3番目の実験: ループを1つにするとクラッシュ
0: 0 0 0
1: 0 1 0
2: 0 2 0
3: 0 3 0
4: 0 4 0
...
45: 0 45 0
46: 0 46 0
47: 0 47 0
48: 0 48 0
49: 0 49 0
Segmentation fault

もう意味不明ですよね。何が起こっているんでしょう?

タネ明かし

この60回のforループは「配列の終端を超えたアクセス」がC言語仕様上の未定義動作なので、何が起きても正しい、つまりどの結果も正しいです。

これだけだと、何言ってんのか意味不明だと思うので「printf有効/無効」「forループ1つ/2つ」に着目して説明します。

1番目の実験(printf無効、forループ2つ)
プログラムを見るとguard1, guard2に対してmemset 0した後、参照のみで代入しません。コンパイラはguard1, guard2をスタックに配置せず、配列への参照(guard1[i], guard2[i])は全て「定数の0」に置換します。
(GIMPLEを見たら033t.fre1で0に置換されるようです)
このときarrayのバッファオーバーランはスタックに退避されているレジスタ値などを書きつぶしますが、ギリギリ続行できています。
2番目の実験(printf有効、forループ2つ)
1番目と変わりないと思いきや、printfがguard1, guard2のアドレス参照をするため、定数の0に置換すると返せるアドレスがなくなり結果が変わってしまいます。このため、guard1, guard2はスタックに配置されます。
このときarrayのバッファーオーバーランは隣に配置されたguard2を書きつぶします。
3番目の実験(printf無効、forループ1つ
いわゆる偶然の結果です。1番目と同様にguard1, guard2はスタックに配置されず、arrayのバッファオーバーランによりスタックに退避したレジスタ値などが壊れます。forループが1つ減ったことでスタックに退避されるレジスタが1つ減って(8バイト分余裕がなくなる)、1番目の実験でギリギリリターンアドレスを壊されずに耐えていたものが、耐えられなくなります。

3番目の実験の裏打ちとして、試しにループ回数を80回くらいにするとforループが1つだろうが2つだろうが、リターンアドレスがぶっ壊れてSegmentation Fault します。10行目のprintfを有効にするとguard1, guard2がスタックに配置されて、受け止めてくれるので、80回でも耐えます。

難解なC言語仕様、曖昧な利用者の理解、過激なコンパイラの最適化、が招く結末

バッファオーバーランを期待していた向きには残念(?)かもしれませんが、guard1, guard2はメモリ上に置いても置かなくても、C言語仕様に矛盾しないなら、どっちでも良いです。もっというとC言語仕様に矛盾しないなら、コンパイラの最適化は何をやってもOK です。

この「C言語仕様に矛盾しないなら」はおそらくコンパイラ開発者には常識なのでしょうけども、C言語の仕様は人間に優しくないのと、大多数のC言語プログラマは言語仕様(特に未定義動作)を理解しておらず、何となく使っています。

難解な仕様、曖昧な理解、過激な最適化の相乗効果により、今日も世界のどこかで
「最適化で動きが変になっちゃったよ……。どうして…どうして……?」
とコンパイラとすれ違ったプログラマが泣いているでしょう。。。

参考

大したものではありませんが、ソースコードを載せておきます。

実験用ソースコード

#include <stdio.h>
#include <string.h>

int undefined()
{
	int guard1[50];
	int array[50];
	int guard2[50];
	int sum = 0, i;

	memset(guard1, 0, sizeof(guard1));
	memset(guard2, 0, sizeof(guard2));
	//printf("%p %p %p\n", &guard1[0], &array[0], &guard2[0]);

	for (i = 0; i < 60; i++) {
		array[i] = i;
	}

	for (i = 0; i < 60; i++) {
		sum += array[i];
	}

	for (i = 0; i < 50; i++) {
		printf("%2d: %d %d %d\n", i, guard1[i], array[i], guard2[i]);
	}

	return sum;
}

int main(int argc, char *argv[])
{
	int sum1 = 0, sum2 = 0, i;

	sum1 = undefined();

	for (i = 0; i < 60; i++) {
		sum2 += i;
	}

	printf("%d: %d\n", sum1, sum2);

	return 0;
}
編集者:すずき(2023/02/04 20:17)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2020年2月6日 >>> 2020年1月24日
link もっと後

管理用メニュー

link 記事を新規作成

<2020>
<<<02>>>
------1
2345678
9101112131415
16171819202122
23242526272829

最近のコメント20件

  • link 24年10月1日
    すずきさん (10/06 03:41)
    「xrdpで十分動作しているので、Wayl...」
  • link 24年10月1日
    hdkさん (10/03 19:05)
    「GNOMEをお使いでしたら今はWayla...」
  • link 24年10月1日
    すずきさん (10/03 10:12)
    「私は逆にVNCサーバーに繋ぐ使い方をした...」
  • link 24年10月1日
    hdkさん (10/03 08:30)
    「おー、面白いですね。xrdpはすでに立ち...」
  • link 14年6月13日
    2048player...さん (09/26 01:04)
    「最後に、この式を出すのに紙4枚(A4)も...」
  • link 14年6月13日
    2048playerさん (09/26 01:00)
    「今のところ最も簡略化した式です。\n--...」
  • link 14年6月13日
    2048playerさん (09/16 01:00)
    「返信ありがとうございます。\nコメントが...」
  • link 14年6月13日
    すずきさん (09/12 21:19)
    「コメントありがとうございます。同じ結果に...」
  • link 14年6月13日
    2048playerさん (09/08 17:30)
    「私も2048の最高スコアを求めたのですが...」
  • link 14年6月13日
    2048さん (09/08 17:16)
    「私も2048の最高スコアを求めたのですが...」
  • link 14年6月13日
    2048playerさん (09/08 16:10)
    「私も2048の最高スコアを求めたのですが...」
  • link 02年8月4日
    lxbfYeaaさん (07/12 10:11)
    「555」
  • link 24年6月17日
    すずきさん (06/23 00:12)
    「ありがとうございます。バルコニーではない...」
  • link 24年6月17日
    hdkさん (06/22 22:08)
    「GPSの最初の同期を取る時は見晴らしのい...」
  • link 24年5月16日
    すずきさん (05/21 11:41)
    「あー、確かにdpkg-reconfigu...」
  • link 24年5月16日
    hdkさん (05/21 08:55)
    「システム全体のlocale設定はDebi...」
  • link 24年5月17日
    すずきさん (05/20 13:16)
    「そうですねえ、普通はStandardなの...」
  • link 24年5月17日
    hdkさん (05/19 07:45)
    「なるほど、そういうことなんですね。Exc...」
  • link 24年5月17日
    すずきさん (05/19 03:41)
    「Standardだと下記の設定になってい...」
  • link 24年5月17日
    hdkさん (05/18 22:16)
    「ドメインを変えたせいで別サイト扱いになっ...」

最近の記事3件

  • link 24年10月31日
    すずき (11/04 15:17)
    「[DENSOの最終勤務日] 最終勤務日でした、入門カードや会社のPCを返却してきました。在籍期間はNSITEXE(品川のオフィ...」
  • link 22年7月8日
    すずき (11/02 20:34)
    「[マンガ紹介 - まとめリンク] 目次: マンガ紹介一覧が欲しくなったので作りました。5作品乙女ゲームの破滅フラグしかない悪役...」
  • link 24年10月30日
    すずき (11/02 20:33)
    「[マンガ紹介] 目次: マンガ紹介お気に入りのマンガ紹介シリーズ。最近完結した短めの作品を紹介します。マイナススキル持ち四人が...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www2.katsuster.net
RDFファイル RSS 1.0

最終更新: 11/04 15:17