Help us understand the problem. What is going on with this article?

Linuxbrew において ARM 系アーキテクチャで brew install が異常終了する問題を回避する

More than 1 year has passed since last update.

はじめに

Homebrew とは、 Mac OS X 上における、ソースコードの取得及びビルドに基づいたパッケージ管理システムです。そして、 Homebrew を Linux の各ディストリビューション向けに移植したものが Linuxbrew です。

Homebrew 及び Linuxbrew の使用により、ソースコードからのビルドに基づいたソフトウェアの導入を単純かつ容易に行うことが出来ます。

ここで、 Raspberry Pi 及び Debian noroot 環境等の ARM 系アーキテクチャで動作する各種 Linux ディストリビューションにおいて、 Linuxbrew を用いて各種パッケージをビルドする時に brew install コマンドを起動すると、以下のようなエラーメッセージを出力して brew install コマンドが異常終了する場合があります。

  $ brew install -dv hello
  # ...()...
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5890 ***
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5850 ***
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5850 ***
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5850 ***
  # ...()...

この問題は、 brew install コマンドを実行する場合において、 Linuxbrew の内部において、各種環境変数が格納されている Ruby 定数 ENV の値が設定されることにより、 Linuxbrew におけるビルド環境が再構築され、この際に、 gcc コンパイラに渡す最適化オプションの一つとして -march=native が設定され、続いて、現行の gcc コンパイラがオプション -march=native に基づいて ARM 系アーキテクチャの識別を行おうとした際に、適切に -march=native のオプションを処理出来ない事により、異常終了する事が原因であると考えられます。

そこで、ディレクトリ $HOMEBREW_PREFIX/bin 以下に、 gcc コンパイラに渡すオプションのうち、 -march=native 及び -mcpu=native を除去するための gcc コマンドのラッパースクリプトを導入することにより、 ARM アーキテクチャ上で動作する Linuxbrew において、brew install コマンドが異常終了する問題を回避することが可能となりました。

本稿では、 ARM 系アーキテクチャ上の Linuxbrew において、 brew install が異常終了する問題について、本問題の概要と本問題の原因及び、本問題を回避する為に作成した ARM アーキテクチャ対応の gcc コンパイラのラッパースクリプトの導入手法について述べます。

まず最初に、 "問題の概要" の章において、 ARM 系アーキテクチャにおける Linuxbrew のコマンドである brew install コマンドが異常終了する問題の概要について述べます。

次に、 "問題の原因" の章において、前章で述べた brew install コマンドの異常終了の問題の原因について述べます。

続いて、 "問題の回避手法" の章において、前々章で述べた brew install コマンドの異常終了の問題を回避する手法について述べます。

最後に、 "結論" の章において、本稿の結論について述べます。

なお本稿では、特段の断りのない限り、環境変数 HOMEBREW_PREFIX には Linuxbrew の導入先となるディレクトリが格納されているものとし、また、 Linuxbrew において使用する gcc コンパイラは、 Linuxbrew によってビルドされたものでは無く、各種 Linux ディストリビューションのパッケージによって導入された gcc のバージョン 4.9 であるとします。

問題の概要

本章では、 Raspberry Pi 及び Debian noroot 環境等の ARM 系アーキテクチャで動作する各種 Linux ディストリビューションにおいて、 Linuxbrew を用いて各種パッケージをビルドする時に brew install コマンドを起動した際に発生する異常終了の概要について述べます。

まず、 ARM 系アーキテクチャで動作する Linuxbrew において、以下のようにして brew install -dv hello コマンドを起動し、 hello コマンドを導入しようとすると、以下のようなメッセージを出力して、 brew install -dv hello が異常終了します。

  $ brew install -dv hello                                                                            
  /opt/.linuxbrew/Library/Homebrew/brew.rb (Formulary::FormulaLoader): loading /opt/.linuxbrew/Library/Taps/homebrew/homebrew-core/Formula/hello.rb
  # ...()...
  ==> Installing hello
  # ...()...
  ==> Downloading https://ftp.gnu.org/gnu/hello/hello-2.10.tar.gz
  # ...()...
  ==> ./configure --disable-debug --disable-dependency-tracking --disable-silent-rules --prefix=/opt/.linuxbrew/Cellar/hello/2.10
  configure: WARNING: unrecognized options: --disable-debug
  checking for a BSD-compatible install... /usr/bin/install -c
  checking whether build environment is sane... yes
  checking for a thread-safe mkdir -p... /bin/mkdir -p
  checking for gawk... no
  checking for mawk... mawk
  checking whether make sets $(MAKE)... yes
  checking whether make supports nested variables... yes
  checking for gcc... gcc-4.9
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5890 ***
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5850 ***
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5850 ***
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b5850 ***
  checking whether the C compiler works... no
  configure: error: in `/tmp/hello-20181024-28876-1i4fut5/hello-2.10':
  configure: error: C compiler cannot create executables
  See `config.log' for more details
  #...()...

上記のメッセージの内容から、 ./configure スクリプトによって gcc コンパイラの検出を行う際に、 gcc コマンドの内部的なメモリ解放に関する異常が発生し、 gcc コンパイラの検出に失敗していることが判ります。

そして、 gcc コンパイラの検出の失敗に伴い、 ./configure スクリプトの実行の続行が出来無くなっているために、最終的に brew install -dv hello コマンドによる hello コマンドのビルドに失敗することが判ります。

また、 brew install -dv --env=std hello コマンドを起動して superenv 環境を無効にして hello コマンドを導入した場合も、同様なエラーメッセージを出力して異常終了することが判ります。

問題の原因

本章では、前章で述べた ARM 系アーキテクチャで動作する Linuxbrew において、 brew install コマンドに基づく各種パッケージのビルドが異常終了する原因について述べます。

まず最初に、 "gcc コンパイラに関する問題" の節において、 ARM アーキテクチャ上で動作する gcc コンパイラに各種最適化オプションを付与した場合の動作について、内部のメモリ処理の問題に起因して gcc コンパイラが異常終了する問題について述べます。

そして、 "Linuxbrew のビルド環境の構築に関する問題" の節において、 ARM 系アーキテクチャで動作する Linuxbrew において Linuxbrew の内部コードによる各種環境変数及びビルド環境の再構築が行われた際に、アーキテクチャに関する最適化オプションが gcc コンパイラに渡されることに関する問題について述べ、この問題によって、前節において述べた gcc コンパイラの動作に関する問題を発生させ、 brew install コマンドの異常終了の原因となる事について述べます。

gcc コンパイラに関する問題

本節では、簡単なテストコードのコンパイルを通じて、 ARM アーキテクチャ上で動作する gcc コンパイラに各種最適化オプションを付与した場合の動作に関する問題について述べます。

まず最初に、以下のような簡単なテストコード test.cARM アーキテクチャ上で動作する gcc コンパイラである gcc-4.9 コマンドを用いてコンパイルを行うことを考えます。

test.c
#include <stdio.h>

void main(void) {
        printf("Hello, world!\n");
}

まず、上記のテストコード test.c を最適化オプションを付けずにコンパイルを行います。

  $ gcc-4.9 -o ./test.bin ./test.c
  $ ./test.bin
  Hello, World!
  $

以上に示した通り、最適化オプションを付けずにテストコード test.c をコンパイルした場合は、問題なくコンパイルが成功し、生成された実行ファイル test.bin も正常に動作します。

ここで、アーキテクチャを最適化するためのオプションの一部である -march=native を付与して、テストコード test.c のコンパイルを行うと、以下に示すエラーメッセージを表示し、 gcc コンパイラである gcc-4.9 が異常終了することが判ります。

  $ gcc-4.9 -march=native -o ./test.bin ./test.c
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b6a30 ***
  中止
  $

上記のエラーメッセージの内容より、 ARM アーキテクチャ上の gcc コンパイラである gcc-4.9 コマンドに最適化オプション -march=native を付与して、ソースコードをコンパイルした場合、 gcc-4.9 コマンド内部で確保されたメモリの二重開放を行う不具合の発生により、最適化オプション -march=native を適切に処理することに問題が発生し、 gcc-4.9 コマンドが異常終了することが判ります。

なお、下記に示す通り、 ARM アーキテクチャ上の gcc コンパイラである gcc-4.9 コマンドにおいて、最適化オプション -mcpu=native を付与した場合も同様に、 gcc コンパイラである gcc-4.9 が異常終了することが判ります。

また、特定のアーキテクチャを指定した最適化オプション -march=armv7 等を gcc-4.9 コマンドに付与した場合は、問題なくテストコード test.c のコンパイルが成功することが判ります。

  $ gcc-4.9 -mcpu=native -o ./test.bin ./test.c
  *** Error in `gcc-4.9': double free or corruption (top): 0x000b6a30 ***
  中止
  $ gcc-4.9 -march=armv7 -o ./test.bin ./test.c
  $ ./test.bin
  Hello, World!
  $

以上より、 ARM アーキテクチャ上の gcc コンパイラである gcc-4.9 コマンドに、アーキテクチャに関する最適化オプション -march=native, -mcpu=native を付与して起動した場合、 gcc コンパイラ内部で確保したメモリの二重開放の問題により、 gcc コンパイラが異常終了することが判りました。

Linuxbrew のビルド環境の構築に関する問題

本節では、 Linuxbrew の内部コードによるビルド環境の再構築が行われた際に、アーキテクチャに関する最適化オプションが gcc コンパイラに渡される様子について述べ、この時に gcc コンパイラに渡された最適化オプションが、前節において述べた問題を発生させる原因となることを述べます。

まず最初に、 Linuxbrew において、 brew install コマンドを起動して各種パッケージのビルドを行った時に、 Linuxbrew の内部において、以下の内部コードに従って、各種環境変数が格納されている Ruby 定数 ENV の再構築を行います。

  • $HOMEBREW_PREFIX/library/Homebrew/extend/ENV.rb
  • $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/shared.rb
  • $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/super.rb

なお、 brew install コマンドに --env=std オプションを付与して、 superenv 環境を無効とした場合は、 $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/super.rb に代えて以下のコードが使用されます。

  • $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/std.rb

ここで、前章において、 brew install -dv hello コマンドを起動して hello コマンドのビルドが異常終了した際に、 01.configure.cc なるエラーログが、ディレクトリ $HOME/.cache/Homebrew/Logs/hello 以下に、次に示す内容で残されていることが判ります。

${HOME}/.cache/Homebrew/Logs/hello/01.configure.cc
# ...(略)...
gcc-4.9 called with: conftest.c
superenv added:    -pipe -O2 -march=native -isystem/opt/.linuxbrew/include -L/opt/.linuxbrew/lib -B/opt/.linuxbrew/opt/glibc/lib -Wl,-rpath=/opt/.linuxbrew/Cellar/hello/2.10/lib -Wl,-rpath=/opt/.linuxbrew/lib -Wl,--dynamic-linker=/opt/.linuxbrew/lib/ld.so
superenv executed: gcc-4.9 -pipe -O2 -march=native conftest.c -isystem/opt/.linuxbrew/include -L/opt/.linuxbrew/lib -B/opt/.linuxbrew/opt/glibc/lib -Wl,-rpath=/opt/.linuxbrew/Cellar/hello/2.10/lib -Wl,-rpath=/opt/.linuxbrew/lib -Wl,--dynamic-linker=/opt/.linuxbrew/lib/ld.so

以上のエラーログ 01.configure.cc の内容より、 brew install -dv hello コマンドを起動して hello コマンドをビルドした際に、 Linuxbrew のビルド環境下において、前記で示した Linuxbrew の内部コード $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/super.rb 等による各種環境変数及びビルド環境の再構築が行われた結果、 ARM アーキテクチャの gcc コンパイラである gcc-4.9 を起動した時に、アーキテクチャに関する最適化フラグの一つである -march=nativegcc-4.9 に引数として渡される事が判ります。

そして、 brew install -dv --env=std hello コマンドを起動し、 superenv 環境を無効化して hello コマンドのビルドを行い、 brew install -dv --env=std hello コマンドが異常終了した際には、 01.configure なるエラーログが、ディレクトリ $HOME/.cache/Homebrew/Logs/hello 以下に、以下の内容で残されていることが判ります。

${HOME}/.cache/Homebrew/Logs/hello/01.configure
2018-10-24 08:26:22 +0000

./configure
--disable-debug
--disable-dependency-tracking
--disable-silent-rules
--prefix=/opt/.linuxbrew/Cellar/hello/2.10

# ...(略)...
CC: /usr/bin/gcc-4.9
# ...(略)...
CFLAGS: -Os -w -pipe -march=native
CXXFLAGS: -Os -w -pipe -march=native
CPPFLAGS: -isystem/opt/.linuxbrew/include -F/opt/.linuxbrew/Frameworks
# ...(略)...

以上のエラーログ 01.configure の内容より、 superenv 環境を無効化した場合であっても同様に、 brew install -dv --env=std hello コマンドを起動して hello コマンドをビルドした際に、 Linuxbrew のビルド環境下において、前記で示した Linuxbrew の内部コード $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/std.rb 等による各種環境変数及びビルド環境の再構築が行われた結果、 ARM アーキテクチャの gcc コンパイラである gcc-4.9 を起動した時に、アーキテクチャに関する最適化フラグの一つである -march=nativegcc-4.9 に引数として渡される事が判ります。

従って、前節で述べた ARM 系アーキテクチャで動作する gcc コンパイラの問題と本節で述べた Linuxbrew のビルド環境の再構築により、 ARM アーキテクチャ上で動作する brew install コマンドの異常終了の問題は以下に述べた原因によって引き起こされることが判りました。

  • ARM 系アーキテクチャ上の Linuxbrew において、 brew install コマンドを起動すると、以下の Linuxbrew の内部コードを用いて各種環境変数を設定し、 Linuxbrew のビルド環境を整える。
    • $HOMEBREW_PREFIX/library/Homebrew/extend/ENV.rb
    • $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/shared.rb
    • $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/super.rb
      • (なお、 brew install コマンドにオプション --env=std を付与した場合は、 $HOMEBREW_PREFIX/library/Homebrew/extend/ENV/std.rb を用いる。)
  • 上記の内部コードに基づいて、 Linuxbrew のビルド環境を再構築した結果、 ARM 系アーキテクチャで動作する gcc コンパイラである gcc-4.9 コマンドには、アーキテクチャの最適化に関するオプション -march=native が付与される。
  • しかし、前節で述べた通り、 ARM 系アーキテクチャで動作する gcc コンパイラである gcc-4.9 コマンドに最適化オプション -march=native を付与すると、 gcc コンパイラ内部で確保されたメモリの二重開放に関する問題が発生し、 gcc-4.9 が異常終了する。
  • ARM 系アーキテクチャで動作する gcc コンパイラの異常終了により、 brew install コマンドに基づいた各種パッケージのビルドが続行出来なくなり、 brew install コマンドが異常終了する。

問題の回避手法

本章では、前々章において述べた、 ARM 系アーキテクチャ上で動作する Linuxbrew 上において、 brew install コマンドが異常終了する問題を回避する手法について述べます。

まず最初に、 "問題回避の概要" の節において、前々章で述べた、 ARM 系アーキテクチャ上の Linuxbrew において、 brew install コマンドが異常終了する問題の回避手法の概要について述べます。

そして、 "gcc ラッパースクリプトの導入" の節において、前々章で述べた問題を回避するための gcc コンパイラのラッパースクリプト及びその導入手法について述べます。

問題回避の概要

本節では、 ARM 系アーキテクチャ上の Linuxbrew において、 brew install コマンドが異常終了する問題の回避手法について述べます。

まず、 Linuxbrew の内部コードの内容より、 Linuxbrew のビルド環境下において、 gcc コンパイラは、ラッパースクリプト $HOMEBREW_PREFIX/Library/Homebrew/shims/super/cc を介して起動されます。

ここで、ラッパースクリプト $HOMEBREW_PREFIX/Library/Homebrew/shims/super/cc の最後の部分は以下のようになっています。

  # ...(略)...
  ####################################################################### main

  dirname, basename = File.split($0)

  cmd = Cmd.new(basename, ARGV)
  tool = cmd.tool
  args = cmd.args

  log(basename, ARGV, tool, args)

  args << { :close_others => false }
  if mac?
    exec "#{dirname}/xcrun", tool, *args
  else
    paths = ENV["PATH"].split(":")
    paths = remove_superbin_from_path(paths)
    paths.unshift "#{ENV["HOMEBREW_PREFIX"]}/bin"
    ENV["PATH"] = paths.join(":")
    exec tool, *args
  end
end

ラッパースクリプト $HOMEBREW_PREFIX/Library/Homebrew/shims/super/cc の内容より、変数 tool には、例えば "gcc-4.9" のような、 gcc コンパイラのプログラム名が、また、変数 args には、 gcc コンパイラに渡されるオプション及び引数の配列がそれぞれ格納されていることが判ります。

そして、 exec tool, *args によって最終的に gcc コンパイラに各種オプション及び引数が渡されて、 gcc コンパイラに制御が移される直前に、環境変数 PATH が編集され、環境変数 PATH が示すパスの先頭に、ディレクトリ $HOMEBREW_PREFIX/bin が置かれていることが判ります。

以上のことより、 Linuxbrew のビルド環境下においては、ディレクトリ $HOMEBREW_PREFIX/bin に置かれている gcc コンパイラが優先的に起動されることが判ります。

従って、 ディレクトリ $HOMEBREW_PREFIX/bin に、 gcc コンパイラに渡されるオプションのうち、 -march=native 及び -mcpu=native を除去して gcc コンパイラを起動するためのラッパースクリプトを置くことによって、 gcc コンパイラにオプション -march=native 及び -mcpu=native が渡されることによって gcc コンパイラが異常終了する問題を回避することが出来、また、 brew install コマンドが異常終了する問題も同時に回避することができることが判ります。

gcc ラッパースクリプトの導入

本節では、前節で述べた内容に基づいて作成した、 Linuxbrew のビルド環境での gcc コンパイラの起動時に、オプション -march=native 及び -mcpu=native を除去するための gcc コンパイラのラッパースクリプトの導入手法について述べます。

前節で述べたように、前々章の問題を回避するために、 Linuxbrew において、 gcc コンパイラを実行する際に、オプション -march=native 及び -mcpu=native を除去するための gcc コンパイラのラッパースクリプトを作成し、 Github において公開しました。

上記の gcc ラッパースクリプト ccLinuxbrew に導入するには、以下の brew install コマンドを実行します。

  $ brew install https://raw.githubusercontent.com/z80oolong/gcc-arm-wrap/master/gcc-arm-wrap.rb

また、以下のように git clone コマンドによってこの git リポジトリを取得した後に、以下の通りに brew install コマンドを実行しても、 gcc ラッパースクリプトを導入出来ます。

  $ git clone https://github.com/z80oolong/gcc-arm-wrap.git
  $ cd gcc-arm-wrap
  $ brew install ./gcc-arm-wrap.rb

上記コマンドの実行後は、 gcc ラッパースクリプト cc が導入される他、 gcc, g++, gcc-4.9, g++-4.9 等のコマンドも cc へのシンボリックリンクとして生成されます。

そして、 gcc ラッパースクリプトを導入した後は、以下のようにして、 brew install コマンドによって hello コマンドの導入を試みます。

  $ brew install -dv hello
  # ...()...
  $ hello
  世界よ、こんにちは!
  $

以上のように、 brew install が問題なく終了し、 hello コマンドが正常に導入されれば、 ARM アーキテクチャ上の Linuxbrew において、 brew install コマンドに基づいた各種パッケージの導入に関する問題が回避されたことが判ります。

結論

本稿では、 "問題の概要" の章において、 ARM アーキテクチャ上で動作する Linuxbrew において、 brew install コマンドに基づいて各種パッケージを導入すると、 gcc コンパイラを起動する段階において、 gcc コンパイラが異常終了し、 brew install コマンドが正常に動作しない問題について述べました。

そして、前々章において、この問題が ARM アーキテクチャ上で動作する gcc において、アーキテクチャの最適化に関するオプションである -march=native 及び -mcpu=native が gcc コンパイラに渡された時に、 gcc コンパイラの内部で確保したメモリを二重開放したことにより発生した不具合に起因することを示し、この不具合が、 Linuxbrew のビルド環境の構築時に、 gcc コンパイラにオプション -march=native を付与する設定が行われることによって引き起こされるため、 brew install コマンドが正常に起動しない原因となることを述べました。

また、前章において、 Linuxbrew のビルド環境が構築された際には、ディレクトリ $HOMEBREW_PREFIX/bin に置かれた gcc コンパイラが優先的に起動されることを示し、この事より、ディレクトリ $HOMEBREW_PREFIX/bin に gcc コンパイラに渡される最適化オプション -march=native, -mcpu=native を除去するラッパースクリプトを置くことで、本稿で述べた問題を回避できることを述べ、前述した gcc コンパイラのラッパースクリプトを作成し、 ARM アーキテクチャ上で動作する Linuxbrew に導入することにより、 brew install コマンドによって各種パッケージを導入する際に異常終了する問題が回避することが出来る事が示されました。

謝辞

本稿を記述するに当たって、 ARM アーキテクチャにおける gcc コンパイラの異常終了に関する問題の概要及びその回避については、 Mokutsumo 氏によるブログ "自己実現武呂具" の記事の "printipiのコンパイルで *** Error in 'g++': double free or corruption (top): 0x01099c18 ***" を参考にしました。 Mokutsumo 氏に心より感謝致します。

また、 Linuxbrew のビルド環境の構築の過程については、 Linuxbrew の本体のリポジトリ内の Ruby コード及び各種ドキュメントを参考にしました。

そして、 Linuxbrew 本体のリポジトリの開発を行っている Shaun Jackman 氏を始めとする Linuxbrew の開発コミュニティの各氏に心より感謝致します。

そして最後に、 Linuxbrew の全ての事に関わる全ての皆様に心より感謝致します。

追記

2018/10/26 現在の追記及び御断り

本稿に関しまして、 Raspberry Pi の表記に誤りがありましたので、ここに、心より深く御詫びして訂正致します。

また、この度の本稿の表記の誤りについて御指摘頂いた khsk 氏には誠に御手数をお掛け致しますと共に、この度の御指摘に心より感謝致します。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away