目次: Zephyr
Zephyrのビルドに成功し、gdbで追跡したところHello worldのmain関数まで到達していることもわかりました。ここまでくればあとは文字出力だけです。
まずHogeボードのdefconfigにCONFIG_UART_SPIKE=yを加えます。cmakeに「そんなコンフィグはない」と怒られるようになりますが、これから作るのでOKです。
シリアルドライバのディレクトリはzephyr/drivers/serialにあります。ディレクトリの構造を見ると、CMakeLists.txt, Kconfig.*, ソースコードuart_*.cが並んでいます。この3つの組を追加もしくは変更すれば良さそうです。
# SPDX-License-Identifier: Apache-2.0
menuconfig UART_SPIKE
bool "Spike Simulator serial driver"
depends on SOC_RISCV_SPIKE
select SERIAL_HAS_DRIVER
help
This option enables the Spike Simulator serial driver.
名前は何でも良いですが、Kconfig.spikeという名前にしました。Kconfig.socとは違って、ファイルを足すだけではダメで、シリアルドライバのKconfigから読んでもらう必要があります。
...
source "drivers/serial/Kconfig.litex"
source "drivers/serial/Kconfig.rtt"
source "drivers/serial/Kconfig.xlnx"
source "drivers/serial/Kconfig.spike" # ★この行を足す★
endif # SERIAL
Kconfigを追加してcmakeをするとまだ怒られます。CMakeList.txtも変更する必要があるとのことです。
-- Configuring done CMake Error at ../../cmake/extensions.cmake:372 (add_library): No SOURCES given to target: drivers__serial Call Stack (most recent call first): ../../cmake/extensions.cmake:349 (zephyr_library_named) ../../drivers/serial/CMakeLists.txt:3 (zephyr_library) CMake Generate step failed. Build files cannot be regenerated correctly. FAILED: build.ninja
CMakeLists.txtは他の行に習って追加します。CMakeLists.txtを変更しただけだとuart_spike.cが無いと言われるので、touch uart_spike.cで空のファイルを作成します。
...
zephyr_library_sources_if_kconfig(uart_liteuart.c)
zephyr_library_sources_ifdef(CONFIG_UART_RTT_DRIVER uart_rtt.c)
zephyr_library_sources_if_kconfig(uart_xlnx_ps.c)
zephyr_library_sources_if_kconfig(uart_spike.c) # ★この行を足す★
...
他の行に習って追加するだけではつまらないので、CMakeのスクリプトも眺めます。今回使用したzephyr_library_sources_if_kconfig() とzephyr_library_sources_ifdef() との違いは、ソースコードのコメントにある説明がわかりやすいです。
ファイル名を大文字にして、CONFIG_ と連結させた名前(今回のケースだとCONFIG_UART_SPIKE)を生成して、コンフィグが有効ならソースコードをビルド対象に加えるという意味です。Kconfigのコンフィグはy, n, mの3値を取りますが、Zephyrのスクリプトでは、コンフィグが有効だとyという値が入り、無効だと未定義になるようです。
# zephyr/cmake/extensions.cmake
...
# zephyr_library_sources_if_kconfig(fft.c)
# is the same as
# zephyr_library_sources_ifdef(CONFIG_FFT fft.c)
...
function(zephyr_library_sources_if_kconfig item)
get_filename_component(item_basename ${item} NAME_WE)
string(TOUPPER CONFIG_${item_basename} UPPER_CASE_CONFIG)
zephyr_library_sources_ifdef(${UPPER_CASE_CONFIG} ${item})
endfunction()
...
function(zephyr_library_sources_ifdef feature_toggle source)
if(${${feature_toggle}})
zephyr_library_sources(${source} ${ARGN})
endif()
endfunction()
# zephyr/cmake/extensions.cmake
...
function(zephyr_library_sources source)
target_sources(${ZEPHYR_CURRENT_LIBRARY} PRIVATE ${source} ${ARGN})
endfunction()
...
以上の変更でビルドが通り、シリアルドライバを実装する枠が完成しました。続きは次回。
目次: Zephyr
やっとcmakeを通過したので、いよいよビルドに挑みます。
In file included from ../include/arch/cpu.h:25, from ../include/kernel_includes.h:34, from ../include/kernel.h:17, from zephyr/arch/riscv/core/offsets/offsets.c:16: ../include/arch/riscv/arch.h:25:10: fatal error: soc.h: No such file or directory #include <soc.h> ^~~~~~~ compilation terminated. ninja: build stopped: subcommand failed.
他のsoc.hを見ると、soc_common.h, devicetree.hというヘッダをインクルードしているようです。それに習っておきます。
/*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file SoC configuration macros for the Spike series processor
*/
#ifndef __RISCV_SPIKE_SOC_H_
#define __RISCV_SPIKE_SOC_H_
#include <soc_common.h>
#include <devicetree.h>
#endif /* __RISCV_SPIKE_SOC_H_ */
再びビルドすると、今度はPLIC_* がないと怒られます。
zephyr/drivers/interrupt_controller/intc_plic.c: In function 'riscv_plic_irq_enable': zephyr/drivers/interrupt_controller/intc_plic.c:45:41: error: 'DT_PLIC_IRQ_EN' undeclared (first use in this function); did you mean 'PLIC_IRQS'? volatile u32_t *en = (volatile u32_t *)DT_PLIC_IRQ_EN; ^~~~~~~~~~~~~~ PLIC_IRQS
RISC-V Privileged系のSoCではPLICという割り込みコントローラのドライバが自動的に有効になります。今回、動作させるに当たってPLICのサポートは不要なので、zephyr/boards/riscv/hoge/hoge_defconfigで無効にします。具体的にはCONFIG_PLIC=nを足します。
この後cmakeからやり直してみると、
zephyr/drivers/timer/riscv_machine_timer.c: In function 'set_mtimecmp': zephyr/drivers/timer/riscv_machine_timer.c:28:31: error: 'RISCV_MTIMECMP_BASE' undeclared (first use in this function) volatile u32_t *r = (u32_t *)RISCV_MTIMECMP_BASE; ^~~~~~~~~~~~~~~~~~~ zephyr/drivers/timer/riscv_machine_timer.c:28:31: note: each undeclared identifier is reported only once for each function it appears in zephyr/drivers/timer/riscv_machine_timer.c: In function 'mtime': zephyr/drivers/timer/riscv_machine_timer.c:47:31: error: 'RISCV_MTIME_BASE' undeclared (first use in this function) volatile u32_t *r = (u32_t *)RISCV_MTIME_BASE; ^~~~~~~~~~~~~~~~ [87/94] Linking C static library zephyr/kernel/libkernel.a ninja: build stopped: subcommand failed.
CLINTのレジスタmtime, mtimecmpのアドレスを必要としているようです。
他のSoCを眺めてみると、CLINTのレジスタアドレスを定義する場所はsoc.hのようです。しかし肝心のアドレスがわかりません。QEMUのドキュメントに載っていれば簡単でしたが、見当たらなかったのでQEMUのソースコードを見ます。
// https://github.com/qemu/qemu/blob/master/hw/riscv/spike.c
static const struct MemmapEntry {
hwaddr base;
hwaddr size;
} spike_memmap[] = {
[SPIKE_MROM] = { 0x1000, 0x11000 },
[SPIKE_CLINT] = { 0x2000000, 0x10000 },
[SPIKE_DRAM] = { 0x80000000, 0x0 },
};
// https://github.com/qemu/qemu/blob/master/include/hw/riscv/sifive_clint.h
enum {
SIFIVE_SIP_BASE = 0x0,
SIFIVE_TIMECMP_BASE = 0x4000,
SIFIVE_TIME_BASE = 0xBFF8
};
ベースアドレスが0x02000000で、レジスタのオフセットアドレスもわかりました。soc.hに追記します。
(2/19訂正: MTIMECMP_BASEとMTIME_BASEのアドレスが逆だったので、修正しました)
/* Timer configuration */
#define RISCV_MTIMECMP_BASE 0x02004000
#define RISCV_MTIME_BASE 0x0200bff8
再びビルドします。やっとビルドをパスしました。
$ ninja [0/1] Re-running CMake... -- Zephyr version: 2.1.99 -- 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/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 [89/94] Linking C executable zephyr/zephyr_prebuilt.elf Memory region Used Size Region Size %age Used RAM: 12752 B 16 KB 77.83% IDT_LIST: 25 B 2 KB 1.22% [94/94] Linking C executable zephyr/zephyr.elf
まだめでたしめでたし、ではありません。
生成されたバイナリを実行すると何かエラーが表示されるものの、Hello worldは表示されません。残念です。
$ /usr/bin/qemu-system-riscv32 -nographic -machine spike -net none -chardev stdio,id=con,mux=on -serial chardev:con -mon chardev=con,mode=readline -kernel zephyr/build/zephyr/zephyr.elf qemu-system-riscv32: clint: time_hi write not implemented qemu-system-riscv32: clint: time_lo write not implemented qemu-system-riscv32: clint: time_hi write not implemented
このままでは全く何も実行されていないのか、惜しいところまで実行されたのか判別が付きません。以前ご紹介した(2020年1月31日の日記参照)デバッグオプション -sと -Sを使い、gdbで追跡します。
復習ですが、オプション -sはGDBの接続をlocalhost:1234で受け付け、-SはエミュレータをHalted状態で起動しgdbから再開要求をするまで動かないで待っている、という意味です。
$ riscv64-zephyr-elf-gdb zephyr/build/zephyr/zephyr.elf GNU gdb (crosstool-NG 1.24.0.60-a152d61) 8.3.1 Copyright (C) 2019 Free Software Foundation, Inc. ... (gdb) target remote localhost:1234 Remote debugging using localhost:1234 0x00001000 in ?? () (gdb) b main Breakpoint 1 at 0x800006d4: file zephyr/samples/hello_world/src/main.c, line 12. (gdb) c Continuing. Breakpoint 1, main () at zephyr/samples/hello_world/src/main.c:12 12 printk("Hello World! %s
", CONFIG_BOARD);
ターゲット(QEMU)に接続し、mainにブレークを設定して、実行を継続しています。Hello worldのmain関数に到達していることがわかります。素晴らしいですね。printkは呼ばれているものの、文字が出ないことが問題だとわかりました。
Hello worldを拝むには、シリアルのドライバを追加する必要があります。続きはまた次回。
目次: Zephyr
今回はcmakeをパスするまで頑張ります。
今後の方針のためcmakeのエラーを見ます。どうやらSOC_FAMILY_RISCV_PRIVILEGEがないとか、SOC_SERIES_RISCV_SPIKEとは何だ?とか、Kconfig周りの設定で怒っているようです。何も定義していないので当然ですね。
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を追加と行きたいところですが、その前に少しやることがあります。
Hogeボードのdefconfigで余計なもの(SiFive FE310 SoC用の設定)を定義しているので、削りましょう。SiFive由来のドライバの設定、前回のデバイスツリーで定義していないPINMUXやGPIOの設定を削ります。
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を追加する場合、zephyr/soc/アーキテクチャ名/SoC名/Kconfig.socを足せば良いですが、RISC-V Privileged系のSoCは少し様子が違い、Kconfigが2段構成になります。
# 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)などを参考にします。
# 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や他のディレクトリを見ると気づくと思いますが、SoCのKconfigには2つ系列があります。
riscv-privilege/Kconfig.soc -> riscv-privilege/spike/Kconfig.seriesのラインと、
riscv-privilege/Kconfig -> riscv-privilege/spike/Kconfig.socのラインです。先程、追加したのは前者です。名前が紛らわしいのはRISC-V Privileged系列の実装の良くないところですね……。
# 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さん。
# 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がどうのこうのと言っています。パスもなにやらおかしいです。
CMake Error at ../../CMakeLists.txt:372 (message): Could not find linker script: 'zephyr/soc/riscv//linker.ld'. Corrupted configuration?
このlinker.ldを探しているのはcmakeです。下記の部分で探しています。
# 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を定義しているのは下記の部分です。
# 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ファイルで定義するようです。
$ 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はどこから参照されているのでしょうか?これも一応、調べておきましょう。
# 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)を無効にしています。
# 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を見ればわかるはず。
/*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @brief Linker script for the Spike series processor
*/
#include <arch/riscv/common/linker.ld>
これでKconfig周りが整ったはずです。
これでクリアかと思いきや、まだ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を見ると、下記の一行を書いておけば良さそうです。
# SPDX-License-Identifier: Apache-2.0
zephyr_sources()
ここまで変更するとcmakeのエラーが解消します。良く見ると何やらUART_CONSOLEがどうのこうのと文句を言っていますが、後ほど対応します。
$ 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つずつ解消していくことになります。続きはまた次回です。
目次: Zephyr
Hogeボードの定義を一通り実装しました。HogeボードのSoCはqemu_riscv32と同じSiFive FE310にしたことを覚えているでしょうか?Zephyrの移植に興味のある方からすると「ボード名を変えただけで、qemu_riscv32のコピーでは?」と感じるはずです。そんな悲しみの声にお答えすべく、次はSoCの変更を行います。
今回、ターゲットとするSoCはQEMUのSpikeモードです。このモードは、特別なハードウェアが必要なく手軽、偶然にも現状のZephyrが対応していない、簡単なドライバの実装が1つだけ必要、など学習にピッタリです。
Spikeはシミュレータのみで、物理的に存在する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> の記述があります。とりあえずこれを下記のように変更します。
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ディレクトリのファイルを全て削除してから、実行したほうが良いでしょう。
$ 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がないと言って怒られているようですから、追加しましょう。
Zephyrのデバイスツリーはzephyr/dts以下にあります。今回はRISC-V一派のSoCを追加しますから、zephyr/dts/riscvの下に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";
};
};
};
最低限のデバイスだけ定義しています。
このファイルを加えてもまだcmakeに怒られます。Spike SoC用のデバイスツリーに出てこないノード(gpio, spiなど)を参照している箇所があるためです。ボード側の定義からばっさり削除します。
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は通過できません。長くなってきましたので、続きはまた今度。
目次: Zephyr
前回はデフォルトコンフィグがx86になっていたり、ドライバを有効にしないとリンクエラーになったり、おかしなことが起きていました。対策として手動でコンフィグを変えまくりましたが、本来は必要ありません。Zephyrのボード定義では、ボードごとにデフォルトコンフィグを持つことができるからです。
他のボード(zephyr/boards/riscv/qemu_riscv32/qemu_riscv32_defconfigなど)を見ると、ボード名_defconfigというファイルの中にCONFIG_ABCD=yという文がたくさんあります。このファイルに追加すると反映されそうです。
(2/19訂正: CONFIG_SYS_CLOCK_TICKS_PER_SEC=100を追加しないとタイマー割り込みが入りすぎて動かなくなるため、修正しました)
# 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と同じです。
$ 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_ なんとかマクロへの変換ルールは下記にあります。
# 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は小文字で書くことが多いですが、マクロ名は大文字に変換しています。
目次: Zephyr
前回ninjaを実行したときに嫌というほどエラーが出ました。エラーを追う前に、コンフィグがあっているかチェックしたいと思います。
ZephyrはLinux Kernelと似たコンフィグのシステム(Kconfig)を持っています。使い方もLinuxと似ており、ninja menuconfigとすると、ケバケバしい色(私の環境だと真っ白+黄色になる……)の画面が表示されます。
なぜかArchitectureがx86となっていたり、SoCがLiteXになっていたり、色々おかしいです。なぜこうなってしまったかは後にして、手当たり次第でそれらしい値に直すと、Board Selectionに今回追加したHoge targetが出現します。
$ ninja menuconfig Architecture (x86 architecture) ---> ( ) RISCV architectureを選択 SoC/CPU/Configuration Selection (LiteX VexRiscv system implementation) ---> ( ) SiFive Freedom SOC implementationを選択
コンフィグがそれらしくなったらninjaでビルドします。
$ 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にボード名のデフォルト値を設定していました。
つまり、このように書けば良いみたいです。
# SPDX-License-Identifier: Apache-2.0
if BOARD_HOGE
config BOARD
default "hoge"
endif
それ以外のDT_INST_ なんちゃらの定義については、少し複雑です。
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など)がデバイスツリーをどうしているか、見ながら真似してみます。
/* 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) ----
このように全て手動で設定する必要はない(後々不要となる)ので、あまり詳しくなる必要はないですが、
コンフィグがそれらしくなったら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ボード上で動いたということですね。次回は面倒くさかった手動コンフィグの撲滅に挑みます。
目次: 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とかで良いです)で実行しましょう。
$ 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を実行しましょう。
$ 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がないと言われますので、両方とも足します。
$ 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ファイルです。
# 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を見ます。
# 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)を参照すると良いと思います。今回はこんな内容にしました。
# SPDX-License-Identifier: Apache-2.0
config BOARD_HOGE
bool "Hoge target"
depends on SOC_RISCV_SIFIVE_FREEDOM
この定義だとSiFiveのFreedomが搭載されていることになりますが、depends onの先に手を出すには、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 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は見るのが嫌になるくらい大量のエラーを表示して失敗します。続きはまた今度。
目次: Zephyr
Zephyrをqemu_riscv32ボード向けにコンフィグし、ビルドディレクトリ下でninja runを実行すると、最終的に下記のコマンドが実行され、QMEUが起動します。
$ /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: 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のセクション情報通りに配置されます。
$ 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ファイルを逆アセンブルしてみると、
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/boards/riscv/qemu_riscv32/qemu_riscv32.dtsにあるFlashの先頭アドレスを変えるとELFのエントリポイント(0x20400000)も追従します。
chosen {
zephyr,console = &uart0;
zephyr,shell-uart = &uart0;
zephyr,sram = &dtim;
zephyr,flash = &flash0;
};
このchosen以下を検知しているようです。スクリプトscripts/dts/gen_defines.pyを見ると、SPIのときだけ特殊処理になっていて、それ以外はregの値を先頭アドレスとみなしています。
# 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のエントリポイントがどうやって決まるのか、少しわかった気がします。
目次: Zephyr
以前Zephyr OSを実行しました(2019年1月12日の日記参照)が、今回はRISC-V 32bit版で試してみようと思います。
この例ではbuildをビルドディレクトリとします。どこに置いても動くはずですが、私はとりあえずzephyrの下に置いています。
前回同様Zephyr SDKもしくはCrosstool-NGが使えます。参考までにCrosstool-NGのコンフィグを載せておきます。
$ ./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/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をエミュレートしているそうです。
$ 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が何を起動しているか調べてみます。
$ /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を削って起動してください。
$ 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! です。
目次: GCC
新し目のAArch64のクロスコンパイル用ツールチェーンを作ろうとして、かなりハマったのでメモしておきます。
基本的には前回(2019年4月29日の日記参照)ご紹介した手順でビルドします。GCCとglibcのコードを変えなくて良い組み合わせは下記の通りです。特に新しいバージョンを使う理由がなければ、この組み合わせが無難です。
私は新しいGCCが使いたかったので、HEADにしました(バージョン的には10.0相当)。どうやらGCCのエラーチェックが厳しくなるらしく、glibcのビルドが通らなくなります。たくさんエラーが出ますが、一例を挙げると、下記のようなエラーです。
./../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でも同じ症状が出る)にすると、下記のような変なエラーが出ます。
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用のオブジェクトが生成されています。
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と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はコミットログを見る限り、テストのサポート用ライブラリなので、とりあえず無くても動くはずです。ビルド自体をやめるパッチをあてます。
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書き換えよりは多少スマートですけども、クロスコンパイルの時だけこんな指定が必要なのは妙ですね。まだ何か見落としているんでしょうかね?
目次: C言語とlibc
くそ長いですが、C言語の未定義動作怖いね、printfでタイミング以外も動き変えられるよ、という話です。
環境ですがx86_64向けDebian GNU/Linux 9.2で実行しています。またGCCのバージョンはgcc (Debian 9.2.1-22) 9.2.1 20200104です。
未定義動作のため、コンパイラの種類や、GCCのバージョンにより結果が変わると思われます。お家のマシンで試すならご留意ください。
この日記の最後に貼ったプログラム(このプログラムをコンパイルすると、激しい警告が出ます)をgcc -Wall -O2 a.c && ./a.outのように実行すると、
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にバッファオーバーランしない?」と考えた方、するどいです。しかし世の中そう単純ではありません。
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が何かしたんでしょうか、不思議ですね?
どうしてforループを無意味に2分割したのか?くっつけてみたらわかります。Segmentation Fault します。
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つ」に着目して説明します。
3番目の実験の裏打ちとして、試しにループ回数を80回くらいにするとforループが1つだろうが2つだろうが、リターンアドレスがぶっ壊れてSegmentation Fault します。10行目のprintfを有効にするとguard1, guard2がスタックに配置されて、受け止めてくれるので、80回でも耐えます。
バッファオーバーランを期待していた向きには残念(?)かもしれませんが、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;
}
< | 2020 | > | ||||
<< | < | 02 | > | >> | ||
日 | 月 | 火 | 水 | 木 | 金 | 土 |
- | - | - | - | - | - | 1 |
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
合計:
本日: