コグノスケ


link 未来から過去へ表示(*)  link 過去から未来へ表示

link もっと前
2022年4月9日 >>> 2022年4月9日
link もっと後

2022年4月9日

glibcと最適化

目次: C言語とlibc

たまに最適化を無効にするとビルドできなくなるソフトウェアがあります。大抵はバグです。しかし何か目的があって、あえてやっている場合もあります。C言語標準ライブラリの実装で有名なglibcもその一つです。

最適化をわざと無効にしてglibcをビルドするとエラーが発生します。コードはglibc-2.35を使いました。

O2とO0でglibcをビルドした場合
$ cd glibc
$ mkdir _build
$ cd _build

$ ../configure CPPFLAGS="-O2 -g" --disable-sanity-checks
$ make

(成功する、ログは省略)

$ cd ../
$ rm -rf _build
$ mkdir _build
$ cd _build

$ ../configure CPPFLAGS="-O0 -g" --disable-sanity-checks
$ make

In file included from <command-line>:
./../include/libc-symbols.h:75:3: error: #error "glibc cannot be compiled without optimization"
   75 | # error "glibc cannot be compiled without optimization"
      |   ^~~~~

最適化を無効にするなと怒られました。コードを見ると__OPTIMIZE__ マクロの定義/未定義を見て検出していました。へえー。

最適化の有無を検出している場所

// glibc/include/libc-symbols.h

/* Some files must be compiled with optimization on.  */
#if !defined __ASSEMBLER__ && !defined __OPTIMIZE__
# error "glibc cannot be compiled without optimization"
#endif

このチェックをごまかして、最適化を有効にしつつも特定の最適化だけ無効にするとどうなるでしょう?

O2でインライン展開のみ無効にした場合
$ cd glibc
$ mkdir __build
$ cd __build

$ ../configure CPPFLAGS="-O2 -g -fno-inline" --disable-sanity-checks --disable-werror
$ make

(...略...)

gcc   -nostdlib -nostartfiles -r -o glibc/__build/elf/librtld.os '-Wl,-(' glibc/__build/elf/dl-allobjs.os glibc/__build/elf/rtld-libc.a -lgcc '-Wl,-)' \
          -Wl,-Map,glibc/__build/elf/librtld.os.map
gcc   -nostdlib -nostartfiles -shared -o glibc/__build/elf/ld.so.new         \
          -Wl,-z,combreloc -Wl,-z,relro -Wl,--hash-style=both -Wl,-z,defs      \
          glibc/__build/elf/librtld.os -Wl,--version-script=glibc/__build/ld.map         \
          -Wl,-soname=ld-linux-x86-64.so.2                      \
          -Wl,-defsym=_begin=0
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `_dl_close_worker':
glibc/elf/dl-close.c:383: undefined reference to `malloc'
/usr/bin/ld: glibc/elf/dl-close.c:689: undefined reference to `free'
/usr/bin/ld: glibc/elf/dl-close.c:691: undefined reference to `free'
/usr/bin/ld: glibc/elf/dl-close.c:693: undefined reference to `free'
/usr/bin/ld: glibc/elf/dl-close.c:701: undefined reference to `free'
/usr/bin/ld: glibc/elf/dl-close.c:710: undefined reference to `free'
/usr/bin/ld: glibc/__build/elf/librtld.os:glibc/elf/dl-close.c:715: more undefined references to `free' follow
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `_dl_map_object_deps':
glibc/elf/dl-deps.c:438: undefined reference to `malloc'
/usr/bin/ld: glibc/elf/dl-deps.c:479: undefined reference to `malloc'
/usr/bin/ld: glibc/elf/dl-deps.c:571: undefined reference to `malloc'
/usr/bin/ld: glibc/elf/dl-deps.c:543: undefined reference to `malloc'
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `_dl_error_free':
glibc/elf/dl-exception.c:41: undefined reference to `free'
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `__GI__dl_exception_create':
glibc/elf/dl-exception.c:89: undefined reference to `malloc'

(...略...)

/usr/bin/ld: glibc/elf/../sysdeps/posix/getcwd.c:249: undefined reference to `malloc'
/usr/bin/ld: glibc/elf/../sysdeps/posix/getcwd.c:493: undefined reference to `free'
/usr/bin/ld: glibc/elf/../sysdeps/posix/getcwd.c:469: undefined reference to `realloc'
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `__alloc_dir':
glibc/elf/../sysdeps/unix/sysv/linux/opendir.c:115: undefined reference to `malloc'
/usr/bin/ld: glibc/elf/../sysdeps/unix/sysv/linux/opendir.c:115: undefined reference to `malloc'
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `__closedir':
glibc/dirent/../sysdeps/unix/sysv/linux/closedir.c:50: undefined reference to `free'
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `scratch_buffer_free':
glibc/malloc/../include/scratch_buffer.h:86: undefined reference to `free'
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `__libc_scratch_buffer_set_array_size':
glibc/malloc/scratch_buffer_set_array_size.c:51: undefined reference to `malloc'
/usr/bin/ld: glibc/__build/elf/librtld.os: in function `__strdup':
glibc/string/strdup.c:42: undefined reference to `malloc'
collect2: error: ld returned 1 exit status
make[2]: *** [Makefile:1234: glibc/__build/elf/ld.so] エラー1
make[2]: ディレクトリ 'glibc/elf' から出ます
make[1]: *** [Makefile:483: elf/subdir_lib] エラー2
make[1]: ディレクトリ 'glibc' から出ます
make: *** [Makefile:9: all] エラー2

リンク時に激しく怒られてしまいダメでした。小細工は効きませんね。

O2以外の最適化レベル

デバッグ時に見やすくするためにOgやO1といった最適化レベルを使うことは珍しくないと思いますが、glibcはOgやO1だとおかしなビルドエラーが発生します。

Ogでビルドするとエラー
$ ../configure CPPFLAGS="-Og -g" --disable-sanity-checks

(...略...)

canonicalize.c: In function ‘realpath_stk’:
canonicalize.c:424:50: error: ‘dest’ may be used uninitialized in this function [-Werror=maybe-uninitialized]
  424 |   return scratch_buffer_dupfree (rname_buf, dest - rname);
      |                                             ~~~~~^~~~~~~
cc1: all warnings being treated as errors

警告がエラー扱いされただけなので、configureに --disable-werrorを渡して警告をエラーと見なさないようにするとビルドは成功します(実は前節でもしれっと使っていました)。

しかし良く考えるとO2では無警告なのにOgでは警告が出るとは?なかなか興味深い現象です。警告が出ているrealpath_stk() のコードを見ます。

realpath_stk() のコード

// glibc/stdlib/canonicalize.c

static char *
realpath_stk (const char *name, char *resolved,
              struct scratch_buffer *rname_buf)
{
  char *dest;
  char const *start;
  char const *end;
  int num_links = 0;

  //...

  struct scratch_buffer extra_buffer, link_buffer;
  scratch_buffer_init (&extra_buffer);
  scratch_buffer_init (&link_buffer);
  scratch_buffer_init (rname_buf);
  char *rname_on_stack = rname_buf->data;
  char *rname = rname_on_stack;
  bool end_in_extra_buffer = false;
  bool failed = true;

  /* This is always zero for Posix hosts, but can be 2 for MS-Windows
     and MS-DOS X:/foo/bar file names.  */
  idx_t prefix_len = FILE_SYSTEM_PREFIX_LEN (name);

  if (!IS_ABSOLUTE_FILE_NAME (name))    //★★この条件が成立、かつ★★
    {
      while (!__getcwd (rname, rname_buf->length))    //★★この条件が成立、かつ★★
        {
          if (errno != ERANGE)    //★★この条件が不成立、かつ★★
            {
              dest = rname;
              goto error;
            }
          if (!scratch_buffer_grow (rname_buf))    //★★この条件が成立した場合を考えると★★
            goto error_nomem;    //★★destが未定義のまま、このgotoに到達する★★
          rname = rname_buf->data;
        }
      dest = __rawmemchr (rname, '\0');
      start = name;
      prefix_len = FILE_SYSTEM_PREFIX_LEN (rname);
    }

  //...

error_nomem:
  scratch_buffer_free (&extra_buffer);
  scratch_buffer_free (&link_buffer);

  if (failed || rname == resolved)    //★★しかし、前半のfailed = trueの条件が必ず成立していて、未定義変数の使用は発生しない★★
    {
      scratch_buffer_free (rname_buf);
      return failed ? NULL : resolved;
    }

  return scratch_buffer_dupfree (rname_buf, dest - rname);    //★★ここで未定義変数の使用の警告が出る★★
}

コード上はdestが未定義のまま使われる可能性があるように見えますし、Ogの警告はもっともに思えます。しかしコードを良く見るとdestが未定義のままerror_nomemに来る場合、failed = trueが必ず成立して警告が出る行には到達しません。他にもgoto error_nomemする箇所はありますが、同様に必ずfailed = trueが成立します。

O2だとif文は必ず成立する場合(異常パス)と、しない場合(正常パス)でコードが複製されるようです。if文が成立する側のコードでは、if文の後は不要 = 未使用コードを消去 = dest - rnameというコードそのものがなかったことになる、というメカニズムが働いて警告が出ないのでしょう。たぶん。

GCCの警告は大体合っていますが、これはコンパイラが誤警告を出してしまうちょっと珍しい例でした。個人的には紛らわしいコードを書くんじゃねえ!と思いますが、歴史あるコードですし……何か理由があってこうなっているんでしょう。

編集者:すずき(2022/04/22 03:00)

コメント一覧

  • コメントはありません。
open/close この記事にコメントする



link もっと前
2022年4月9日 >>> 2022年4月9日
link もっと後

管理用メニュー

link 記事を新規作成

<2022>
<<<04>>>
-----12
3456789
10111213141516
17181920212223
24252627282930

最近のコメント5件

  • link 24年4月22日
    hdkさん (04/24 08:36)
    「うちのHHFZ4310は15年突破しまし...」
  • link 24年4月22日
    すずきさん (04/24 00:37)
    「ちゃんと数えてないですけど蛍光管が10年...」
  • link 24年4月22日
    hdkさん (04/23 20:52)
    「おお... うちのHHFZ4310より後...」
  • link 20年6月19日
    すずきさん (04/06 22:54)
    「ディレクトリを予め作成しておけば良いです...」
  • link 20年6月19日
    斎藤さん (04/06 16:25)
    「「Preferencesというメニューか...」

最近の記事3件

  • link 24年5月3日
    すずき (05/06 16:21)
    「[ROCK 3Cの青色LED点滅を止める] 目次: Arduinoゲーミングマシンの流行により、最近のコンピュータは意味もなく...」
  • link 23年6月2日
    すずき (05/06 16:10)
    「[Arduino - まとめリンク] 目次: Arduino一覧が欲しくなったので作りました。 M5Stackとesp32とA...」
  • link 24年4月25日
    すずき (04/29 10:08)
    「[AVIFの変換] AVIFが読めないアプリケーションがたまにあるので、AVIF(AV1 Image File Format)...」
link もっとみる

こんてんつ

open/close wiki
open/close Linux JM
open/close Java API

過去の日記

open/close 2002年
open/close 2003年
open/close 2004年
open/close 2005年
open/close 2006年
open/close 2007年
open/close 2008年
open/close 2009年
open/close 2010年
open/close 2011年
open/close 2012年
open/close 2013年
open/close 2014年
open/close 2015年
open/close 2016年
open/close 2017年
open/close 2018年
open/close 2019年
open/close 2020年
open/close 2021年
open/close 2022年
open/close 2023年
open/close 2024年
open/close 過去日記について

その他の情報

open/close アクセス統計
open/close サーバ一覧
open/close サイトの情報

合計:  counter total
本日:  counter today

link About www.katsuster.net
RDFファイル RSS 1.0

最終更新: 05/06 16:21