Linux 動的ライブラリ/静的ライブラリ/共有ライブラリ 備忘録

# Linux 動的ライブラリ/静的ライブラリ/共有ライブラリ 備忘録

## ライブラリの命名規則

1. ライブラリには特別な命名規則があります。`foo`というライブラリは`libfoo.so`または`libfoo.a`というファイル名が期待されます。
ライブラリをリンクする場合、`-l`オプションにライブラリ名を指定します。

    ```shell
    gcc ... -lfoo ...
    ```

    ライブラリを作成する場合、`libfoo.so`または`libfoo.a`というファイル名で作成する必要があります。

## 動的ライブラリの作成

1. ソースファイルをコンパイルしてオブジェクトファイルを作成します。この時、位置非依存コード(position-independent code)の`-fPIC`オプションを指定します。

    ```shell
    gcc -c -fPIC some_file.c
    ```

    オブジェクトファイルの名前は、元のソースコードと同じファイル名で拡張子`.o`となります。

2. オブジェクトファイルから共有ライブラリをリンクします。

    ```shell
    gcc -shared -o libfoo.so.x.y -Wl,-soname,libfoo.so.x some_file.o
    ```

    リンク時に使用する`x`はメジャーバージョン。`y`はマイナーバージョンとなります。

3. `libfoo.so.x.y`ファイルを、システムのダイナミックリンカーが見つけることができる適切な場所にコピーしてください。

    ```shell
    cp libfoo.so.x.y /usr/local/lib
    ```

    `ldconfig`コマンドはダイナミックリンカーの構成および実行時のライブラリ解決を行います。`ldconfig`が検索する共有ライブラリのパスは`/etc/ld.so.conf`で確認できます。

4. `soname`のメカニズムに対応したシンボリックリンクを作成します。

    ```shell
    ln -s libfoo.so.x.y libfoo.so.x
    ln -s libfoo.so.x libfoo.so
    ```

## 静的ライブラリの作成

1. GCCで中間オブジェクトファイルを作成します。

    ```shell
    gcc -c source_file.c ...
    ```

    必要に応じて、さらにソースファイルを追加してください。出来上がったオブジェクトファイルは、ファイル名は同じですが、ファイル名の拡張子は.oになります。

2. `binutils`パッケージの`ar`ツールを使用して、オブジェクトファイルを静的ライブラリ（アーカイブ）にします。

    ```shell
    ar rcs libfoo.a source_file.o ...
    ```

    `libfoo.a`ファイルが作成されます。

3. 静的ライブラリファイルを適切なディレクトリにコピーします。

    静的ライブラリをリンクする場合、GCCはファイル名の拡張子`.a`から、ライブラリが静的リンク用のアーカイブであることを自動的に認識します。

    ```shell
    gcc ... -lfoo ...
    ```

## 共有ライブラリの一覧

`ldconfig`コマンドの`-p`オプションで、現在キャッシュに格納されているディレクトリとライブラリ候補を一覧で表示できます。

```shell
$ ldconfig -p
870 libs found in cache `/etc/ld.so.cache'
        libzvbi.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzvbi.so.0
        libzvbi-chains.so.0 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzvbi-chains.so.0
        libzstd.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libzstd.so.1
        libz.so.1 (libc6,x86-64) => /lib/x86_64-linux-gnu/libz.so.1
        libz.so (libc6,x86-64) => /lib/x86_64-linux-gnu/libz.so
        libyaml-0.so.2 (libc6,x86-64) => /lib/x86_64-linux-gnu/libyaml-0.so.2
        libx265.so.179 (libc6,x86-64) => /lib/x86_64-linux-gnu/libx265.so.179
        libx264.so.155 (libc6,x86-64) => /lib/x86_64-linux-gnu/libx264.so.155
...
```

## オブジェクトファイルの情報表示

`objdump`コマンドの`-x`オプションで、表示可能な全てのヘッダー情報を表示します。

- `.so`ファイルの場合

```shell
$ objdump -x /usr/lib/x86_64-linux-gnu/libjpeg.so
/usr/lib/x86_64-linux-gnu/libjpeg.so:     file format elf64-x86-64
/usr/lib/x86_64-linux-gnu/libjpeg.so
architecture: i386:x86-64, flags 0x00000150:
...
Dynamic Section:
  NEEDED               libc.so.6
  SONAME               libjpeg.so.8
...
SYMBOL TABLE:
no symbols
...
```

- `.a`ファイルの場合

```shell
$ objdump -x /usr/lib/x86_64-linux-gnu/libjpeg.a
In archive /usr/lib/x86_64-linux-gnu/libjpeg.a:

jcapimin.c.o:     file format elf64-x86-64
rw-r--r-- 0/0   3800 Jan  1 09:00 1970 jcapimin.c.o
architecture: i386:x86-64, flags 0x00000011:
HAS_RELOC, HAS_SYMS
start address 0x0000000000000000

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .text         00000496  0000000000000000  0000000000000000  00000040  2**4
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
  1 .data         00000000  0000000000000000  0000000000000000  000004d6  2**0
                  CONTENTS, ALLOC, LOAD, DATA
  2 .bss          00000000  0000000000000000  0000000000000000  000004d6  2**0
                  ALLOC
  3 .rodata.cst8  00000008  0000000000000000  0000000000000000  000004d8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .note.GNU-stack 00000000  0000000000000000  0000000000000000  000004e0  2**0
                  CONTENTS, READONLY
  5 .note.gnu.property 00000020  0000000000000000  0000000000000000  000004e0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .eh_frame     00000148  0000000000000000  0000000000000000  00000500  2**3
                  CONTENTS, ALLOC, LOAD, RELOC, READONLY, DATA
SYMBOL TABLE:
0000000000000000 l    d  .text  0000000000000000 .text
0000000000000000 l       .rodata.cst8   0000000000000000 .LC0
0000000000000000 l    d  .data  0000000000000000 .data
0000000000000000 l    d  .bss   0000000000000000 .bss
0000000000000000 l    d  .rodata.cst8   0000000000000000 .rodata.cst8
0000000000000000 l    d  .note.GNU-stack        0000000000000000 .note.GNU-stack
0000000000000000 l    d  .note.gnu.property     0000000000000000 .note.gnu.property
0000000000000000 l    d  .eh_frame      0000000000000000 .eh_frame
0000000000000000 g     F .text  0000000000000191 jpeg_CreateCompress
0000000000000000         *UND*  0000000000000000 _GLOBAL_OFFSET_TABLE_
0000000000000000         *UND*  0000000000000000 jinit_memory_mgr
0000000000000000         *UND*  0000000000000000 jpeg_natural_order
00000000000001a0 g     F .text  0000000000000009 jpeg_destroy_compress
0000000000000000         *UND*  0000000000000000 jpeg_destroy
00000000000001b0 g     F .text  0000000000000009 jpeg_abort_compress
0000000000000000         *UND*  0000000000000000 jpeg_abort
00000000000001c0 g     F .text  000000000000005b jpeg_suppress_tables
0000000000000220 g     F .text  0000000000000108 jpeg_finish_compress
0000000000000330 g     F .text  0000000000000095 jpeg_write_marker
00000000000003d0 g     F .text  0000000000000055 jpeg_write_m_header
0000000000000430 g     F .text  000000000000000e jpeg_write_m_byte
0000000000000440 g     F .text  0000000000000056 jpeg_write_tables
0000000000000000         *UND*  0000000000000000 jinit_marker_writer
...
```

## オブジェクトファイルのシンボル一覧

`nm`コマンドでオブジェクトファイルのシンボル一覧を確認できます。

- `.so`ファイルの場合

```shell
$ nm /usr/lib/x86_64-linux-gnu/libjpeg.so
nm: /usr/lib/x86_64-linux-gnu/libjpeg.so: no symbols
```

- `.a`ファイルの場合

```shell
$ nm /usr/lib/x86_64-linux-gnu/libjpeg.a
jcapimin.c.o:
0000000000000000 r .LC0
                 U _GLOBAL_OFFSET_TABLE_
                 U jinit_marker_writer
                 U jinit_memory_mgr
0000000000000000 T jpeg_CreateCompress
                 U jpeg_abort
00000000000001b0 T jpeg_abort_compress
                 U jpeg_destroy
00000000000001a0 T jpeg_destroy_compress
0000000000000220 T jpeg_finish_compress
                 U jpeg_natural_order
00000000000001c0 T jpeg_suppress_tables
0000000000000430 T jpeg_write_m_byte
00000000000003d0 T jpeg_write_m_header
0000000000000330 T jpeg_write_marker
0000000000000440 T jpeg_write_tables
...
```

## 共有ライブラリ

共有ライブラリの`.so`ファイルと`.a`ファイルには、それぞれ異なる役割があります。`.so`ファイルには関連する動的リンクの情報があります。`.a`ファイルにはリンクに必要なシンボルテーブルがあります。

- `.so`ファイルの場合

```shell
...
Dynamic Section:
  NEEDED               libc.so.6
  SONAME               libjpeg.so.8
...
SYMBOL TABLE:
no symbols
...
```

- `.a`ファイルの場合

```shell
...
jcapimin.c.o:
...
0000000000000000 T jpeg_CreateCompress
...
```

```c:jcapimin.c
...
GLOBAL(void)
jpeg_CreateCompress(j_compress_ptr cinfo, int version, size_t structsize)
{
...
}
...
```

## 共有ライブラリのリンク

1. コンパイル時のオプションに、共有ライブラリの指定がない場合、エラーになります。

    ```shell
    $ gcc foo.c -o foo.out
    /usr/bin/ld: /tmp/cc2ZAaOQ.o: in function `main':
    test.c:(.text+0x2e): undefined reference to `jpeg_CreateCompress'
    collect2: error: ld returned 1 exit status
    ```

    - `.so`ファイルの場合


    ```shell
    gcc foo.c /usr/lib/x86_64-linux-gnu/libjpeg.so -o foo.out
    ```

    - `.a`ファイルの場合

    ```shell
    gcc foo.c /usr/lib/x86_64-linux-gnu/libjpeg.a -o foo.out
    ```

    共有ライブラリの依存関係がない場合、`.so`ファイルまたは`.a`ファイルでコンパイルできます。

2. 複数の共有ライブラリに依存する場合、`.a`ファイルではコンパイルできません。共有ライブラリの依存関係を解決する必要があります。

    ```shell
    $ gcc foo.c /usr/lib/x86_64-linux-gnu/libavformat.a -o foo.out
    /usr/bin/ld: /usr/lib/x86_64-linux-gnu/libavformat.a(avienc.o): in function `avi_add_ientry':
    (.text+0x1d9): undefined reference to `av_realloc_f'
    /usr/bin/ld: (.text+0x20e): undefined reference to `av_malloc'
    /usr/bin/ld: /usr/lib/x86_64-linux-gnu/libavformat.a(avienc.o): in function `avi_write_counters':
    (.text+0x56b): undefined reference to `av_log'
    /usr/bin/ld: /usr/lib/x86_64-linux-gnu/libavformat.a(avienc.o): in function `update_odml_entry':
    ...
    ```

3. 複数の共有ライブラリに依存する場合、`.so`ファイルをコンパイルオプションに含めると解決できます。

    ```shell
    gcc foo.c /usr/lib/x86_64-linux-gnu/libavformat.so -o foo.out
    ```

4. コンパイル時、`-l`オプションにライブラリ名を指定することで、同様にコンパイルできます。

    ```shell
    gcc foo.c -lavformat -o foo.out
    ```

## コンパイル時の依存関係を`pkg-config`で解決する

1. `pkg-config`はインストールされたライブラリに関するメタデータを返します。

    - `--cflags`オプション

    ```shell
    $ pkg-config --cflags libavformat
    -I/usr/include/x86_64-linux-gnu
    ```

    - `--libs`オプション

    ```shell
    $ pkg-config --libs libavformat
    -lavformat
    ```

2. コンパイル時の依存関係を`pkg-config`コマンドで解決する方法です。

    ```shell
    gcc foo.c $(pkg-config --cflags --libs libavformat) -o foo.out
    ```

3. `pkg-config`は`/usr/lib/pkgconfig`等を検索し、`.pc`ファイルに記録されたライブラリに関するメタデータを返します。以下のようにメタデータは定義されています。

    ```shell
    $ cat /usr/lib/x86_64-linux-gnu/pkgconfig/libavformat.pc
    prefix=/usr
    exec_prefix=${prefix}
    libdir=/usr/lib/x86_64-linux-gnu
    includedir=/usr/include/x86_64-linux-gnu

    Name: libavformat
    Description: FFmpeg container format library
    Version: 58.29.100
    Requires:
    Requires.private: libavcodec >= 58.54.100, libswresample >= 3.5.100, libavutil >= 56.31.100
    Conflicts:
    Libs: -L${libdir}  -lavformat
    Libs.private: -lm -lxml2 -lbz2 -lgme -lstdc++ -lopenmpt -lstdc++ -lchromaprint -lbluray -lz -lgnutls -lssh -ldl
    Cflags: -I${includedir}
    ```