開発・デバッグ・テスト

クロスコンパイル

別環境向けの実行ファイルを作る作業。

概要

クロスコンパイル(Cross-Compilation)とは、プログラムをビルド(コンパイル)する環境と、そのプログラムを実際に実行する環境が異なる場合のコンパイル作業を指す。組み込み開発では、x86/x64ベースのPC(ホスト環境)上で、ARM、RISC-V、MIPS、XtensaなどのCPUを搭載したターゲットデバイス向けのバイナリを生成することが一般的である。

組み込みデバイスはメモリ・処理能力が限られており、デバイス上で直接コンパイルすることが現実的でない場合がほとんどである。クロスコンパイルにより、開発者は高性能なPC上でビルド作業を行い、生成したファームウェアをターゲットデバイスに転送・書き込むことができる。

歴史・背景

1970年代〜1980年代の初期マイコン時代、8ビットCPU(Z80、6502など)向けのプログラムはアセンブリ言語で書かれることが多く、ホスト上でのクロスアセンブルが一般的だった。

C言語の普及とともに、GCC(GNU Compiler Collection)がクロスコンパイル対応を強化し、1990年代以降は組み込み向けGCCクロスコンパイラが広く使われるようになった。ARMアーキテクチャの台頭(2000年代以降)により、arm-none-eabi-gcc(ベアメタル向け)やarm-linux-gnueabihf-gcc(Linux向け)が組み込み開発の標準ツールとして定着した。

近年はLLVM/Clangもクロスコンパイル対応が充実し、WebAssembly向けのクロスコンパイルなど新たな用途にも広がっている。CMakeやMesonといったモダンなビルドシステムがクロスコンパイル設定を標準でサポートするようになり、環境構築の手間が大幅に削減された。

技術仕様

クロスコンパイラのターゲットトリプル

GCCのクロスコンパイラは「ターゲットトリプル」と呼ばれる命名規則で識別される:

<アーキテクチャ>-<ベンダー>-<OS>-<ABI>
ターゲットトリプル用途
arm-none-eabiARMベアメタル(RTOS・ファームウェア)
arm-linux-gnueabihfARM Linux(ハードウェアFP付き)
aarch64-linux-gnuARM64 Linux
riscv32-unknown-elfRISC-V 32ビットベアメタル
riscv64-linux-gnuRISC-V 64ビット Linux
xtensa-esp32-elfEspressif ESP32
x86_64-w64-mingw32Windows向けx86_64

代表的なツールチェーンコンポーネント

arm-none-eabi-gcc     # Cコンパイラ
arm-none-eabi-g++     # C++コンパイラ
arm-none-eabi-as      # アセンブラ
arm-none-eabi-ld      # リンカ
arm-none-eabi-objcopy # オブジェクトファイル変換(ELF→HEXなど)
arm-none-eabi-objdump # 逆アセンブル・セクション情報表示
arm-none-eabi-size    # バイナリのセクションサイズ表示
arm-none-eabi-gdb     # GDBデバッガ(ホスト上で動作し、ターゲットに接続)

動作原理

ホストとターゲットの関係

[ホスト環境(PC)]              [ターゲット環境]
  x86_64 Linux/macOS/Windows        ARM Cortex-M4
  
  ソースコード (.c, .cpp, .s)
        |
        v
  クロスコンパイラ (arm-none-eabi-gcc)
        |
        v
  ARMバイナリ (.elf, .bin, .hex)
        |
        v [JTAG/SWD/UART等で転送]
  ターゲットデバイスのFlashに書き込み
        |
        v
  ターゲットデバイス上で実行

Makefileでのクロスコンパイル設定例

# クロスコンパイラの設定
CROSS_COMPILE = arm-none-eabi-
CC      = $(CROSS_COMPILE)gcc
CXX     = $(CROSS_COMPILE)g++
AS      = $(CROSS_COMPILE)as
LD      = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
SIZE    = $(CROSS_COMPILE)size

# ターゲットMCUの指定(Cortex-M4F)
CPU     = -mcpu=cortex-m4
FPU     = -mfpu=fpv4-sp-d16
FLOAT   = -mfloat-abi=hard
THUMB   = -mthumb

CFLAGS  = $(CPU) $(FPU) $(FLOAT) $(THUMB)
CFLAGS += -O2 -Wall -Wextra
CFLAGS += -ffunction-sections -fdata-sections  # 未使用セクションを削除可能にする

# リンカフラグ
LDFLAGS = $(CPU) $(FPU) $(FLOAT) $(THUMB)
LDFLAGS += -T linker_script.ld        # リンカスクリプトの指定
LDFLAGS += -Wl,--gc-sections          # 未使用セクションを削除
LDFLAGS += -Wl,-Map=output.map        # マップファイル生成

# ビルドルール
all: firmware.elf firmware.bin

firmware.elf: $(OBJS)
	$(CC) $(LDFLAGS) -o $@ $^
	$(SIZE) $@

firmware.bin: firmware.elf
	$(OBJCOPY) -O binary $< $@

%.o: %.c
	$(CC) $(CFLAGS) -c -o $@ $<

CMakeでのクロスコンパイル設定例

CMakeではツールチェーンファイルでクロスコンパイル設定を記述する:

# toolchain-arm-cortex-m4.cmake
set(CMAKE_SYSTEM_NAME Generic)
set(CMAKE_SYSTEM_PROCESSOR arm)

# クロスコンパイラの指定
set(CMAKE_C_COMPILER arm-none-eabi-gcc)
set(CMAKE_CXX_COMPILER arm-none-eabi-g++)
set(CMAKE_ASM_COMPILER arm-none-eabi-gcc)

# コンパイルフラグ
set(CPU_FLAGS "-mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard")
set(CMAKE_C_FLAGS_INIT "${CPU_FLAGS}")
set(CMAKE_CXX_FLAGS_INIT "${CPU_FLAGS}")

# ライブラリ検索パスをターゲット向けに設定
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
# ビルド時にツールチェーンファイルを指定
cmake -DCMAKE_TOOLCHAIN_FILE=toolchain-arm-cortex-m4.cmake ..
make

用途・ユースケース

マイコン(MCU)向けファームウェア開発:

  • STM32(ARM Cortex-M): arm-none-eabi-gcc を使用
  • ESP32(Xtensa LX6/LX7): ESP-IDF付属のツールチェーンを使用
  • RISC-Vマイコン: riscv32-unknown-elf-gcc を使用

組み込みLinux向けアプリ開発:

  • Raspberry Pi向けアプリをaarch64-linux-gnu-gccでビルド
  • YoctoやBuildrootによる組み込みLinuxディストリビューションのビルド

Windows向けソフトウェアのLinuxビルド(MinGW):

  • x86_64-w64-mingw32-gcc でLinux上からWindows向け実行ファイルをビルド

Webアプリの組み込み機器向け最適化:

  • WebAssemblyターゲットへのクロスコンパイル(Emscripten等)

実装・開発のポイント

1. ライブラリの依存関係に注意

クロスコンパイル時は、ターゲット向けのライブラリが必要である。ホスト環境のライブラリと混在すると問題が発生する:

# 誤り:ホストのライブラリが参照される可能性がある
arm-none-eabi-gcc main.c -I/usr/include -L/usr/lib

# 正しい:ターゲット向けのライブラリとインクルードパスを指定
arm-none-eabi-gcc main.c \
    -I/opt/arm-toolchain/arm-none-eabi/include \
    -L/opt/arm-toolchain/arm-none-eabi/lib

2. エンディアン・サイズの注意

ホストとターゲットでエンディアンやデータ型サイズが異なる場合がある。固定幅整数型を使うことが推奨される:

#include <stdint.h>

// 推奨:固定幅型を使用
uint32_t sensor_value;   // 常に32ビット
int16_t  temperature;    // 常に16ビット

// 非推奨:環境依存のサイズになりえる
unsigned int value;  // 32ビットか64ビットか環境依存
int temp;

3. デバッグ情報の設定

クロスコンパイル時にデバッグ情報を付加することで、GDBリモートデバッグが可能になる:

# デバッグビルド(-gでデバッグ情報を付加)
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -g3 -O0 \
    -T linker.ld -o firmware_debug.elf main.c

# リリースビルド(最適化優先)
arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -O2 -DNDEBUG \
    -T linker.ld -o firmware_release.elf main.c

4. Docker/コンテナでの環境統一

チーム開発ではビルド環境の差異を排除するため、Dockerでクロスコンパイル環境を統一する方法が有効である:

FROM ubuntu:22.04
RUN apt-get update && apt-get install -y \
    gcc-arm-none-eabi \
    binutils-arm-none-eabi \
    cmake \
    make \
    ninja-build
WORKDIR /workspace
# コンテナ内でビルド
docker run --rm -v $(pwd):/workspace my-crossbuild-env \
    cmake -B build -DCMAKE_TOOLCHAIN_FILE=toolchain.cmake && \
    cmake --build build

他技術との比較

比較項目クロスコンパイルネイティブコンパイルエミュレータでのビルド
ビルド速度高速(ホストのCPUフル活用)低速(ターゲットのCPU依存)中程度(エミュレーションのオーバーヘッド)
環境構築やや複雑(ツールチェーン設定)簡単複雑(エミュレータ設定含む)
デバッグリモートデバッグが必要ローカルデバッグ可能エミュレータ経由でデバッグ
実機確認必要(転送・書き込みが必要)不要部分的に省略可能

ツールチェーンはクロスコンパイラを中心とした開発ツール一式であり、クロスコンパイルを実施するための基盤となる。ファームウェアはクロスコンパイルによって生成される成果物の代表例である。

YoctoBuildrootは、組み込みLinux向けにクロスコンパイル環境ごと自動構築するディストリビューションビルドシステムである。

関連用語

参考リンク