目次: 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年1月12日の日記参照)の続きです。
あまりにもglibcフルアセンブラ版memsetの実装が速くて勝てないので、観念して実装を見たのですが、序盤(1バイト〜32バイト)が弱い理由と、以降(33バイト〜)で勝てない理由がわかりました。
他の実装と違ってglibcはサイズの大きい方から条件を見ています。どうしても条件分岐命令を通る回数が増えるため、序盤に弱いです。
中盤は96バイトまではNEON store x 4と分岐で捌いていて、ループを使いません。分岐もcmpしてbranchではなく、ビットセットされていたら分岐する命令(tbz, tbnz)を使っています(※)。
つまり私が書いたmemsetはループで処理している時点で、ほぼ勝ち目がなかったということです。
グラフでは63バイトまでしか測っていなかったから気づかなかったのですが、ループの2週目に入る65バイトから、さらにボロ負けです。いやはや、これは勝てないですね……。
(※)cmp, branchの2命令をtbz 1命令にする辺り、AArch64アセンブラならではの実装に見えますが、実はCでもif (a & 0x10) とか書くとコンパイラがtbz命令を使います。コンパイラ侮りがたし。
この記事にコメントする
目次: ベンチマーク
先日memsetを書いていたとき(2020年1月12日の日記参照)に気づいたのですが、glibcのフルアセンブラ版memsetの性能が2通り(遅い、速い)あることに気づきました。だいたい1割くらい性能が変わります。
遅いときと比較すると、自作のmemsetの方が速いですが、速いときと比較するとボロ負けします。割と性能が迫っているためか、影響が大きいです。
何が違うんでしょうね?コードは当然同じですから、違いはmemset関数のロードされるアドレスくらいです。まさかなと思って、スタティックリンクしたら安定して速くなりました。
ダイナミックリンクだと、アプリ側は0xaaaac4fba560で、glibcだけ0xffffbf2dce00のような遠いアドレスに飛ばされます。ベンチマーク中は、アプリのコード ←→ glibcのコードを頻繁に行き来することになるので、TLBミスヒットの影響が出ているんですかね……??
真因はわかりませんが、アドレスが関係している可能性は高いです。今後、似たようなことをやるときは、スタティックリンクで測った方が良さそうです。
この記事にコメントする
| < | 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 |
wiki
Linux JM
Java API
2002年
2003年
2004年
2005年
2006年
2007年
2008年
2009年
2010年
2011年
2012年
2013年
2014年
2015年
2016年
2017年
2018年
2019年
2020年
2021年
2022年
2023年
2024年
2025年
過去日記について
アクセス統計
サーバ一覧
サイトの情報合計:
本日: