osdev-jpでは、OS開発に有用な情報を収集し公開しています
OS を C++ で開発する場合,標準ライブラリ無しでは言語のコア機能しか使えず C++ の旨味が出し切れません。 そこで C++ 標準ライブラリ libc++ を自作 OS 向けにビルドする方法を紹介します。 動作確認をした環境は Ubuntu 18.04,ビルドに用いたコンパイラは Clang 7.0.1 です。
libc++ は標準 C ライブラリに依存しています。また,アーキテクチャ依存の一部機能を提供するために libc++abi を必要とします。 本記事では標準 C ライブラリとして Newlib を採用しています。
まずは以降の手順で共通に使う変数を定義します。
BASEDIR=/usr/local/src
PREFIX=/usr/local/x86_64-elf
COMMON_CFLAGS="-nostdlibinc -O2 -D__ELF__ -D_LDBL_EQ_DBL -D_GNU_SOURCE -D_POSIX_TIMERS"
CC=clang
CXX=clang++
TARGET_TRIPLE=x86_64-elf
BASEDIR
はビルドに必要なファイルを置くディレクトリへのパスです。どこでもいいでしょう。
PREFIX
はビルドした生成物を最終的に配置するディレクトリへのパスです。これもどこでも大丈夫です。
COMMON_CFLAGS
は基本的にこの通り指定してください。うまくビルドできるように調整してあります。
_LDBL_EQ_DBL
は long double
と double
のビット数が同じであることを伝えるオプションで,math ライブラリのビルドを成功させるのに必要となります。_GNU_SOURCE
は libc++ のビルドで必要となる,標準 C ライブラリには含まれない関数群を有効化するために必要です。_POSIX_TIMERS
は libc++ が要求する clock_gettime
のプロトタイプ宣言を有効化するのに必要です。このオプションを付けたとしてもヘッダファイルの中のプロトタイプ宣言が見えるようになるだけであり,実体はいずれにせよ定義されませんので,libc++ の時刻関係の機能(std::chrono)を使おうとすると clock_gettime
の定義がなくてリンクが失敗するはずです。CC
と CXX
はビルドに用いるコマンドを指定します。システムによってはバージョン付きのコマンド名 clang-7
などを指定する必要があるかもしれません。
TARGET_TRIPLE
はクロスコンパイル用の指定です。トリプルというだけあって machine-vendor-operatingsystem
という形です。しかし,実は 4 番目がある場合があります。 x86_64-elf
は machine=x86_64,vendor=unknown,operatingsystem=none で,4 番目が elf という意味になります。
Newlib のソースコードをダウンロードし,configure して make します。
cd $BASEDIR
git clone --depth 1 --branch fix-build https://github.com/uchan-nos/newlib-cygwin.git
Newlib 公式リポジトリではなく私(uchan)のリポジトリからダウンロードしてきます。公式リポジトリのものをそのまま使うとビルドがエラーになってしまうため,修正を行っています。
cd $BASEDIR
mkdir build_newlib
cd build_newlib
../newlib-cygwin/newlib/configure \
CC=$CC \
CC_FOR_BUILD=$CC \
CFLAGS="-fPIC $COMMON_CFLAGS" \
--target=$TARGET_TRIPLE --prefix=$PREFIX --disable-multilib --disable-newlib-multithread
make -j 4
make install
ソースコードのツリーの外側にビルド用ディレクトリを作ります。 そうするとビルド用ディレクトリを削除しさえすれば何回でもきれいな環境でビルドし直せます。
--disable-multilib
は 32 ビット版と 64 ビット版の両方を同時にビルドする multilib という機能を無効化します。これが有効なままだとビルドに失敗することがあります。
--disable-newlib-multithread
はマルチスレッドサポートを無効にします。
マルチスレッドは OS のサポートが必要なので,無効化しておかないと OS にリンクした際にマルチスレッド関連の関数がいくつか要求されてしまいます。
make install
が完了すると PREFIX
に指定したディレクトリ以下に include
と lib
というディレクトリが生成され,そこにファイルがインストールされるはずです。
libc++abi のソースコードをダウンロードし,cmake して make します。
cd $BASEDIR
git clone --depth 1 --branch llvmorg-7.0.1 https://github.com/llvm/llvm-project.git
念の為,使用するコンパイラバージョン(7.0.1)に合わせたライブラリをダウンロードしています。 他のバージョンでもそのままか,少し修正することでビルド可能かと思います。
--depth 1
とすることで Git の全履歴ではなく特定のコミットだけを取得することになり,clone が高速になります。
cd $BASEDIR
mkdir build_libcxxabi
cd build_libcxxabi
cmake -G "Unix Makefiles" \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
-DCMAKE_CXX_COMPILER=$CXX \
-DCMAKE_CXX_FLAGS="-I$PREFIX/include $COMMON_CFLAGS -D_LIBCPP_HAS_NO_THREADS" \
-DCMAKE_C_COMPILER=$CC \
-DCMAKE_C_FLAGS="-I$PREFIX/include $COMMON_CFLAGS -D_LIBCPP_HAS_NO_THREADS" \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DCMAKE_BUILD_TYPE=Release \
-DLIBCXXABI_LIBCXX_INCLUDES="$BASEDIR/llvm-project/libcxx/include" \
-DLIBCXXABI_ENABLE_EXCEPTIONS=False \
-DLIBCXXABI_ENABLE_THREADS=False \
-DLIBCXXABI_TARGET_TRIPLE=$TARGET_TRIPLE \
-DLIBCXXABI_ENABLE_SHARED=False \
-DLIBCXXABI_ENABLE_STATIC=True \
$BASEDIR/llvm-project/libcxxabi
make -j4
make install
Newlib のときと同様,ソースコードのツリーの外側にビルド用ディレクトリを作ります。
CMAKE_CXX_FLAGS
と CMAKE_C_FLAGS
に指定している -I$PREFIX/include
は,Newlib のヘッダファイルを libc++abi のビルドで利用できるようにします。
このオプションを付けないと libc++abi のビルド時に標準 C ライブラリが参照できずにエラーになります。
-D_LIBCPP_HAS_NO_THREADS
は libc++ のヘッダファイルを読み込む際にマルチスレッド関連の機能を誤って有効化しないために指定します。
cmake のオプションで LIBCXXABI_ENABLE_THREADS=False
と指定しているのでマルチスレッドが無効になって欲しいところですが,これだけではだめなのです。
libc++abi は libc++ のヘッダファイルを利用しますが, LIBCXXABI_ENABLE_THREADS
オプションは libc++abi のファイルにしか効果がなく,libc++ のファイルには影響を及ぼさないようなのです。
libc++ のヘッダファイルを読み込む際にマルチスレッドを無効化するために上記の指定が必要です。
CMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY
はビルドしたライブラリが正しいかどうかのテストを,静的ライブラリに対して実行するようにするオプションです。
参考にしたページ たのしい組み込み CMake
LIBCXXABI_ENABLE_EXCEPTIONS=False
と LIBCXXABI_ENABLE_THREADS=False
は,それぞれ例外とマルチスレッドを無効にする指定です。例外もマルチスレッドも OS のサポートが必要なので,OS そのものの開発時には無効にしておきます。
LIBCXXABI_ENABLE_SHARED=False
と LIBCXXABI_ENABLE_STATIC=True
によって静的ライブラリ(.a)のみを生成するようにします。
OS 本体は普通は静的リンクで作るので,共有ライブラリ(.so)は不要です。
ソースコードは libc++abi のダウンロードのときに一緒にダウンロードされているので,改めてダウンロードする必要はありません。 ビルド用ディレクトリを作って cmake して make するだけです。
cd $BASEDIR
mkdir build_libcxx
cmake -G "Unix Makefiles" \
-DCMAKE_INSTALL_PREFIX=$PREFIX \
-DCMAKE_CXX_COMPILER=$CXX \
-DCMAKE_CXX_FLAGS="-I$PREFIX/include $COMMON_CFLAGS" \
-DCMAKE_CXX_COMPILER_TARGET=$TARGET_TRIPLE \
-DCMAKE_C_COMPILER=$CC \
-DCMAKE_C_FLAGS="-I$PREFIX/include $COMMON_CFLAGS" \
-DCMAKE_C_COMPILER_TARGET=$TARGET_TRIPLE \
-DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
-DCMAKE_BUILD_TYPE=Release \
-DLIBCXX_CXX_ABI=libcxxabi \
-DLIBCXX_CXX_ABI_INCLUDE_PATHS="$BASEDIR/llvm-project/libcxxabi/include" \
-DLIBCXX_CXX_ABI_LIBRARY_PATH="$PREFIX/lib" \
-DLIBCXX_ENABLE_EXCEPTIONS=False \
-DLIBCXX_ENABLE_FILESYSTEM=False \
-DLIBCXX_ENABLE_MONOTONIC_CLOCK=False \
-DLIBCXX_ENABLE_RTTI=False \
-DLIBCXX_ENABLE_THREADS=False \
-DLIBCXX_ENABLE_SHARED=False \
-DLIBCXX_ENABLE_STATIC=True \
$BASEDIR/llvm-project/libcxx
make -j4
make install
LIBCXX_CXX_ABI=libcxxabi
で ABI サポートライブラリとして libc++abi を使うことを指定します。
LIBCXX_CXX_ABI_INCLUDE_PATHS
と LIBCXX_CXX_ABI_LIBRARY_PATH
で libc++abi のヘッダファイル(cxxabi.h)とライブラリファイル(libc++abi.a)が置いてあるパスを指定します。
libc++abi のときと同様に,LIBCXX_ENABLE_機能名=False
として OS のサポートが必要になる機能を軒並み無効化していきます。
指定できるオプションは llvm-project/libcxx/CMakeLists.txt から探すことができます。
この記事では例外,ファイルシステム,単調増加クロック,実行時型情報,マルチスレッドを無効化しています。 これらの機能は OS のサポートが必要となるため,OS 自体の開発では無効化しておくのが良いでしょう。
ビルドされたライブラリを使用する際は,いくつかのオプションをコンパイラおよびリンカに渡す必要があります。 コンパイラに渡すオプションは原則としてライブラリのビルドで用いたオプションと同一のものとします。 ビルド時と使用時にオプションが異なると誤動作の原因となります。
export CPPFLAGS="-I/usr/local/x86_64-elf/include/c++/v1 -I/usr/local/x86_64-elf/include -nostdlibinc -D__ELF__ -D_LDBL_EQ_DBL -D_GNU_SOURCE -D_POSIX_TIMERS"
export LDFLAGS="-L/usr/local/x86_64-elf/lib"
C ライブラリのインクルードディレクトリより先に C++ のインクルードディレクトリを指定するのがポイントです。
こうすることで libc++ のヘッダファイルが先に探索されるようになります。
逆にすると,特に math ライブラリの使用時にエラー(::isinf
が見つからないなど)が発生することがあります。
ライブラリのビルドの際はなるべくクリーンな環境を使うのが良いです。ビルドが安定しますし,気づかぬうちにシステム標準のライブラリが参照されてしまう,というような事故を防げます。 この目的は Docker コンテナで簡単に達成できます。 ということでライブラリのビルド用 Docker コンテナを作りましたのでご利用ください。 https://hub.docker.com/r/uchannos/stdlib-builder