🦬

glibc をビルドしてみよう

2024/09/07に公開

TL;DR

# ソースの入手
wget https://ftp.gnu.org/gnu/glibc/glibc-2.39.tar.gz
tar zxf glibc-2.39.tar.gz

# ディレクトリの準備
mkdir glibc-build glibc-install  # ビルド, インストール用にソースと別ディレクトリが必要
cd glibc-build

# ビルド・インストール
../glibc-2.39/configure --prefix=`realpath ../glibc-install` CFLAGS="-O2 -D_FORTIFY_SOURCE=2"  # ビルドエラーの回避のため CFLAGS の指定が必要
make -j
make install

# 動作確認
LD_PRELOAD=`realpath ../glibc-install/lib/libc.so.6` <your-app>

概要

glibc は、C 標準ライブラリ libc のメジャーな実装です。
libc は C 言語を直接使わない場合でも間接的に利用していることが多くあり、単に C 言語用のライブラリというだけでなく Linux システムの基盤としても重要といえるものです。

この記事では glibc を自分でビルドして利用してみます。
また、おまけとして glibc に簡単な変更を加えて動作確認するところまで行ってみます。

実際にビルドを試した際、公式の README や INSTALL のガイドだけでは解決しづらいハマりどころがあったので同じようなことを試したい方に向けて参考になれば幸いです。

目的

この記事は以下のような目的を想定して書いています。

  • システムの libc のバージョンが古いなどの理由で自分でビルドしたものを使いたい[1]
  • 学習用途で glibc を変更して利用してみたい。

環境

glibc をビルドして利用するには、基本的に Linux 環境が必要です。
Mac を利用されている方向けには環境を用意する方法について補足を書いたので適宜ご参照ください。

今回は aarch64 Ubuntu 24.04 環境 (VM) を利用しました。
ビルド用に gcc 等いくつかソフトウェアを用意する必要があります (参考)。
sudo apt install build-essential bison 等で事前にインストールしておきましょう。
ソフトウェアのバージョン等は以下の折り畳み内に示しておきます。

環境の詳細
ubuntu@primary:~$ uname -a
Linux primary 6.8.0-41-generic #41-Ubuntu SMP PREEMPT_DYNAMIC Fri Aug  2 23:26:06 UTC 2024 aarch64 aarch64 aarch64
GNU/Linux

ubuntu@primary:~$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo

ubuntu@primary:~$ gcc --version
gcc (Ubuntu 13.2.0-23ubuntu4) 13.2.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

ubuntu@primary:~$ ldd --version
ldd (Ubuntu GLIBC 2.39-0ubuntu8.3) 2.39
Copyright (C) 2024 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Roland McGrath and Ulrich Drepper.

ubuntu@primary:~$ ld --version
GNU ld (GNU Binutils for Ubuntu) 2.42
Copyright (C) 2024 Free Software Foundation, Inc.
This program is free software; you may redistribute it under the terms of
the GNU General Public License version 3 or (at your option) a later version.
This program has absolutely no warranty.

ソースの入手

glibc のソースは GNU の公式リリース から入手できます。

$ wget https://ftp.gnu.org/gnu/glibc/glibc-2.39.tar.gz
$ tar zxf glibc-2.39.tar.gz

また、glibc のソースは Ubuntu packages 等でも配布されています (glibc-source (2.39-0ubuntu8.3))。
確実にホストの Linux 環境とバージョンを合わせたい場合などはこちらを利用したほうが良いかもしれません。

ビルド

基本的な流れとしては伝統的な configure, make, make install の 3 ステップです。
以下で詳しい手順について説明します。

ディレクトリの準備

ビルド・インストールはソースのディレクトリと分けて行う必要があります。
ソースディレクトリ内で直接 ./configure を実行すると以下のようなエラーになります。

configure: error: you must configure in a separate build directory

また、--prefix でインストール先のディレクトリを指定しないと警告が出ます。これは、システムの libc を誤って置きかえてしまったりすると復旧が大変になるためです。

警告の内容
*** On GNU/Linux systems the GNU C Library should not be installed into
*** /usr/local since this might make your system totally unusable.
*** We strongly advise to use a different prefix.  For details read the FAQ.
*** If you really mean to do this, run configure again using the extra
*** parameter `--disable-sanity-checks'.

今回は、以下のようなディレクトリ構成を採用します。

.
├── glibc-2.39  # ソースコード
├── glibc-build  # ビルド実行用のディレクトリ
└── glibc-install  # インストール先のディレクトリ

以下のコマンドで用意しておきましょう。

$ mkdir glibc-build glibc-install  # ビルド, インストール用にソースと別ディレクトリが必要
$ cd glibc-build

ビルドの実行

ビルドの際は、特に configure の際のオプションに注意が必要です。

$ ../glibc-2.39/configure --prefix=`realpath ../glibc-install` CFLAGS="-O2 -D_FORTIFY_SOURCE=2"
$ make -j
$ make install

正しくインストールされれば、glibc-install/ 以下のように以下のような構造で libc がインストールされるはずです。

.
├── bin
│   ├── ld.so -> ../lib/ld-linux-aarch64.so.1
│   ├── ldd
│   └── (略)
├── etc
│   ├── ld.so.cache
│   └── rpc
├── include
│   ├── stdio.h
│   ├── stdlib.h
│   └── (略)
├── lib
│   ├── ld-linux-aarch64.so.1
│   ├── libc.so.6
│   └── (略)
├── sbin
├── share
├── tmp
└── var

ビルドの失敗例

(試行過程に興味が無ければ次のセクションまで飛ばしてください)

configureCFLAGS を全く指定せずに make を実行した場合、私の環境では以下のエラーでビルドが止まってしまいました。

$ ../glibc-2.39/configure --prefix=`realpath ../glibc-install`
$ make -j
wctomb.c:57:1: error: ‘artificial’ attribute ignored [-Werror=attributes]
   57 | libc_hidden_def (wctomb)
      | ^~~~~~~~~~~~~~~
cc1: all warnings being treated as errors

この attribute のエラーだけに関して言えば --disable-werror のオプションで回避できるのですが、今度は別のところで止まってしまいました。

$ ../glibc-2.39/configure --prefix=`realpath ../glibc-install` --disable-werror
$ make -j
In file included from <command-line>:
syslog.c: In function ‘__vsyslog_internal’:
syslog.c:95:30: error: inlining failed in call to ‘always_inline’ ‘syslog’: function not inlinable
   95 | ldbl_strong_alias (__syslog, syslog)
      |                              ^~~~~~
./../include/libc-symbols.h:143:26: note: in definition of macro ‘_strong_alias’
  143 |   extern __typeof (name) aliasname __attribute__ ((alias (#name))) \
      |                          ^~~~~~~~~
../sysdeps/generic/math_ldbl_opt.h:14:44: note: in expansion of macro ‘strong_alias’
   14 | #define ldbl_strong_alias(name, aliasname) strong_alias (name, aliasname)
      |                                            ^~~~~~~~~~~~
syslog.c:95:1: note: in expansion of macro ‘ldbl_strong_alias’
   95 | ldbl_strong_alias (__syslog, syslog)
      | ^~~~~~~~~~~~~~~~~
syslog.c:138:7: note: called from here
  138 |       syslog (INTERNALLOG, "syslog: unknown facility/priority: %x", pri);
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

パッと見では解決策がよくわからなかったのですが、調べたところ以下のコメントを参考に _FORTIFY_SOURCE=2 を指定することでビルドエラーを回避できました。

_FORTIFY_SOURCE については、man feature_test_macros にドキュメントがあります。正直なところ詳細は筆者がよくわかっていないのですが、バッファーオーバーフロー等のチェックの程度についての指定であるようです。

動作確認

自分でビルドした glibc を用いる上で最も手軽なやり方は、LD_PRELOADLD_LIBRARY_PATH 環境変数を使って実行時に指定した共有ライブラリをロードするようにする方法です。

簡単な Hello world のプログラムを用意しておきます。

// hello.c
#include <stdio.h>

int main() {
        printf("Hello, World!\n");
}
$ gcc hello.c  # 実行ファイル a.out を準備

LD_PRELOAD または LD_LIBRARY_PATH を用いて自分でビルドした libc をロードさせるようにして実行します。

$ LD_PRELOAD=glibc-install/lib/libc.so.6 ./a.out
Hello, World!

$ LD_LIBRARY_PATH=glibc-install/lib/ ./a.out
Hello, World!

自分でビルドした libc を用いてもプログラムが正常に実行できることが確認できました。

変更を加えて動かす

最後におまけとして、glibc のソースに簡単な変更を加えて動かしてみましょう。
文字列を標準出力に書きだす puts() 関数のソースコードを以下のように変更して、You called puts! という文字列が追加で出力されるようにしてみます。

int
_IO_puts (const char *str)
{
  int result = EOF;
  size_t len = strlen (str);
  _IO_acquire_lock (stdout);

  if ((_IO_vtable_offset (stdout) != 0
       || _IO_fwide (stdout, -1) == -1)
      && _IO_sputn (stdout, "You called puts!\n", 17) == 17  // added this line!
      && _IO_sputn (stdout, str, len) == len
      && _IO_putc_unlocked ('\n', stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (stdout);
  return result;
}

glibc のソースコードを変更した上で、make および make install を実行し直してください。
最後に、Hello world のプログラムを再度変更後の glibc をロードするようにして実行します。

$ LD_PRELOAD="glibc-install/lib/libc.so.6" ./a.out
You called puts!
Hello, World!

puts() への変更が反映されていることを確認できました[2]

おわりに

今回は glibc をビルドする方法およびビルドした glibc を用いてプログラムを動かす方法について説明しました。
なかなか libc をいじらなければならない機会というのは多くないと思いますが、普段気づかずに libc の機能を使っていることはたくさんあるので、ソースを読んだりしてみるといろいろと発見があり面白いです。
この記事が libc をいじってみるきっかけになったりすれば幸いです。

最後まで読んでいただきありがとうございました!

参考

補足

Mac 利用者向けの環境構築手順

glibc は MacOS 上では直接ビルドできません。
configure 実行時に以下のようなエラーになってしまいます。

configure: error:
*** The GNU C library is currently unavailable for this platform.
*** If you are interested in seeing glibc on this platform visit
*** the "How to submit a new port" in the wiki:
***   https://sourceware.org/glibc/wiki/#Development
*** and join the community!

筆者自身も Mac を利用していますが、今回は multipass を利用して構築した Linux VM を利用しています (おそらく Docker 等利用する方法でも問題無いでしょう)。
multipass による Ubuntu VM 環境の作成は以下のような手順で行うことができます。

$ brew install --cask multipass
$ multipass launch
$ multipass shell

デフォルトの資源配分でも実行はできますが少し心許ないです。変更したい場合は以下のようにできます。

$ multipass set local.primary.cpus=4
$ multipass set local.primary.memory=8.0GiB
$ multipass set local.primary.disk=32.0GiB

詳しくは公式の README 等を参照してください。

脚注
  1. この用途であれば基本的には可能であれば Docker や VM の利用をおすすめします ↩︎

  2. hello.c のソースコード中では printf() を呼んでいますが、フォーマット指定のない printf() はコンパイラの最適化によって puts() に置きかえられています ↩︎

GitHubで編集を提案

Discussion