Ubuntu上でのopensource COBOLとCライブラリの動的リンク(リンカオプションについて)

# 前書き
いろいろ調べた結果を一通り書いたので非常に長くなっています。
最終的には#解決だけ見てもらえれば解決方法がわかります。

# 概要の前に前提

以前にopensource COBOLからCプログラムを呼び出しを行う記事を書きました
> [opensource COBOLとCプログラムの連携](https://qiita.com/nor51010/items/a4a4b40b96c58cb05d62)

この際、動的リンクにおいて、以下のどちらかで行う必要がありました。

- CALLで呼び出す関数名とCから作成した.soファイルの名前を一致させる
- `-l`オプションであらかじめリンクさせておく

これはopensource COBOLがCALLする関数を探す際、dlsym、dlopenを利用して、ロード済みのシンボルか、関数名と一致する動的ライブラリをオープンすることで、別プログラムを呼び出すためです。

COBOLのプログラムでは1プログラムファイルあたり1関数（プログラムID）が基本であるため、関数名とファイル名は一致する前提での仕様となっています。

しかし、一般的なCライブラリでは、１つのライブラリファイル（so）に、複数の関数があります。
これに対応するためには、コンパイル時の`-l`オプションでコンパイル時に明示的にリンクさせることで、dlsymによる探索を有効にし、COBOLから関数をCALLできるようにする必要がありました。

#ここで本題

前置きの仕様から、`-l`でリンクすればライブラリ内の関数が読めるはずですが、これまでCentOS7の環境で行っていたところ問題なく行えてたこの処理が、Ubuntu17で実行したところ正しく動作しませんでした。
最近、WSLの登場もあり、Ubuntuでの開発や検証を行っていたところこの問題に直面し、だいぶ苦労したため、本エントリにまとめます。
おそらくUbuntu使いの方からすれば当たり前の内容かとは思いますが、、、

#問題点

さっそくですが、以下のようなコードを用意します。
COBOLからCで作成したライブラリ中の関数を呼び出しています。

```cobol:CCALL.cbl
      ******************************************************************
       IDENTIFICATION              DIVISION.
      ******************************************************************
       PROGRAM-ID.                 CCALL.
       AUTHOR.                     nor51010.
      ******************************************************************
       PROCEDURE                   DIVISION.
      ******************************************************************
       MAIN-RTN.
           CALL "C_HELLO".
       MAIN-EXT.
           STOP RUN.
```

```c:CFUNC.c
#include <stdio.h>

int
C_HELLO()
{
    printf("hello world!\n");
    return 0;
}
```

### CentOSでは
まず、CentOS上でためします。
リンクしない場合、当たり前ですが呼び出しに失敗します。

```
$ gcc -shared -fPIC -o libCFUNC.so CFUNC.c
$ cobc -m -o CCALL.so CCALL.cbl
$ ls
CCALL.cbl  CCALL.so  CFUNC.c  libCFUNC.so
$ cobcrun CCALL
libcob: Cannot find module 'C_HELLO'
```

順当にリンクすると問題なく実行できます。

```
$ gcc -shared -fPIC -o libCFUNC.so CFUNC.c
$ cobc -m -L. -lCFUNC -o CCALL.so CCALL.cbl
$ cobcrun CCALL
hello world!
```

###ubuntuでは？

これと同様のことをubuntuで実施します。WSL上のUbuntuでも仮想サーバのUbuntuでも同じでした。
※結論をみればこれは当たり前なのですが、、、

リンクして実行しても関数が見つからずエラーとなってしまいます。

```
$ gcc -shared -fPIC -o libCFUNC.so CFUNC.c
$ cobc -m -L. -lCFUNC -o CCALL.so CCALL.cbl
$ cobcrun CCALL
libcob: Cannot find module 'C_HELLO'
```

リンクファイル名を間違えるとエラーとなるので、リンクに失敗しているわけではなさそうに見えました。

```
$ gcc -shared -fPIC -o libCFUNC.so CFUNC.c
$ cobc -m -L. -lCFUNCX -o CCALL.so CCALL.cbl
/usr/bin/ld: -lCFUNCX が見つかりません
collect2: error: ld returned 1 exit status
```

#調査したこと

後の理解のために、いろいろ調べたことを書いていきます。

問題点の中で、リンクはできているように見えましたがちゃんと動かないということで、やっぱりリンクができていないのではとlddで参照すると、Ubuntuでは明らかにリンクが少ない

```
$ ldd CCALL.so
        linux-vdso.so.1 (0x00007fffc5336000)
        libcob.so.1 => not found
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8e56250000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f8e56a00000)
```

CentOS側で確認すると以下のようになります。

```
$ ldd CCALL.so
        linux-vdso.so.1 =>  (0x00007ffd61de1000)
        libcob.so.1 => not found
        libm.so.6 => /lib64/libm.so.6 (0x00007f7f2cc39000)
        libvbisam.so.1 => not found
        libgmp.so.10 => /lib64/libgmp.so.10 (0x00007f7f2c7b4000)
        libncurses.so.5 => /lib64/libncurses.so.5 (0x00007f7f2c58d000)
        libtinfo.so.5 => /lib64/libtinfo.so.5 (0x00007f7f2c363000)
        libdl.so.2 => /lib64/libdl.so.2 (0x00007f7f2c15f000)
        libCFUNC.so => ./libCFUNC.so (0x00007f7f2bf5d000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f7f2bb90000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f7f2d13d000)
```

そのはずで、opensource COBOLではCOBOL側で利用しているライブラリのリンクをgccに渡しています。cobcの-vオプションで確認できます。

```
$ ${OCBIN}/cobc -v -m -L. -lCFUNC -o CCALL.so CCALL.cbl
preprocessing CCALL.cbl into /tmp/cob179_0.cob
parsing /tmp/cob179_0.cob (CCALL.cbl)
return status: 0
translating /tmp/cob179_0.cob into /tmp/cob179_0.c (CCALL.cbl)
gcc -pipe -I/oscobol/include -Wno-unused -fsigned-char -Wno-pointer-sign -shared -L/oscobol/lib -fPIC -DPIC -Wl,--export-dynamic -o CCALL.so /tmp/cob179_0.c -lcob -lm -lvbisam -lgmp -lncurses -ldl -L"." -l"CFUNC"
```

ですが、自作のものだけでなく、gmpなどほかのライブラリに関してもubuntuでは参照できていませんでした。
ここで注目すべきは、ubuntuでは大部分がリンクできてないにもかかわらず、`libcob`だけはlddでリンクを確認できています。

COBOLプログラムでは、libcobの関数を直接呼び出していますが、作成したCFUNCの関数は`dlsym`に隠蔽されて、Cのコードとしては表れていません。
このため、直接関数名が掛かれているライブラリのみ、ubuntuではリンクされているのではないかと考えました。

仮説の確認のため、新たにCCALLGMP.cblを作成します。

```cobol:CCALLGMP.cbl
      ******************************************************************
       IDENTIFICATION              DIVISION.
      ******************************************************************
       PROGRAM-ID.                 CCALLGMP.
       AUTHOR.                     nor51010.
      ******************************************************************
       DATA                        DIVISION.
      ******************************************************************
       WORKING-STORAGE             SECTION.
       01  NUM1                    PIC 9(16).
       01  NUM2                    PIC 9(16).
      ******************************************************************
       PROCEDURE                   DIVISION.
      ******************************************************************
       MAIN-RTN.
           SET  NUM1 TO 100.
           SET  NUM2 TO 200.
           COMPUTE NUM2 = NUM1 + NUM2.
       MAIN-EXT.
           STOP RUN.
```

このコードをコンパイルして、lddで確認します。

```
$ cobc CCALLGMP.cbl
$ ldd CCALLGMP.so
        linux-vdso.so.1 (0x00007ffff76f4000)
        libcob.so.1 => not found
        libgmp.so.10 => /usr/lib/x86_64-linux-gnu/libgmp.so.10 (0x00007f411fa70000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f411f670000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f4120000000)
```

すると、GMPがリンクされました。
opensource COBOLでは整数の計算において、COBOL特有の計算誤差に関するアプローチのため、GMPライブラリを利用した計算に変換されます。
これにより、「直接関数を呼び出しているライブラリのみリンクされる」といった仮説は正しいと思われます。

しかし、仮説が正しくても問題が解決していませんでした。
このままではopensource COBOLから、Cのライブラリが呼び出せず、即ちubuntu環境では埋め込みSQLのライブラリOCESQLが利用できなくなってしまうなどの影響がでます。

いろいろと考えた結果、GCC自体は、各プラットフォームで最終的なバージョンがことなるため、どこかの動作に違いがあるのではないかということで、想定通りに動いているCentOSと失敗しているUbuntuでコンパイルコマンドの詳細をだし、差分がないか確認しました。

コマンドの詳細は非常に長くなるため割愛しますが、その結果としてリンカである`collect2`に渡す引数に違いがあることがわかりました。

CentOSでは、`-l`でリンクするライブラリの前に`--no-as-needed`が付与され、Ubuntuではそれがないため、直前の`--as-needed`が有効になっているようでした。

このオプションについて調べると、`man ld`で調べると以下のようにでた。
なお、日本語版のldのマニュアルには以下のオプションは記載されていなかった。omg XP.

```
       --as-needed
       --no-as-needed
           This option affects ELF DT_NEEDED tags for dynamic libraries mentioned on the command line after the
           --as-needed option.  Normally the linker will add a DT_NEEDED tag for each dynamic library mentioned on
           the command line, regardless of whether the library is actually needed or not.  --as-needed causes a
           DT_NEEDED tag to only be emitted for a library that at that point in the link satisfies a non-weak
           undefined symbol reference from a regular object file or, if the library is not found in the DT_NEEDED
           lists of other needed libraries, a non-weak undefined symbol reference from another needed dynamic
           library.  Object files or libraries appearing on the command line after the library in question do not
           affect whether the library is seen as needed.  This is similar to the rules for extraction of object files
           from archives.  --no-as-needed restores the default behaviour.
```

非常に簡単に要約すると、`--as-needed`が現れた後は、内部でシンボルを直接参照しているライブラリのみリンクする。このため、dlsymで参照されるような今回のリンクでは、ubuntu上では実際にはリンクされていない状態になってしまった模様。
そして、解決のためには、リンカに直接コマンドを渡すGCCのオプションを利用し、`-Wl,--no-as-needed`を追加する必要がある。

opensource COBOLのコンパイラCOBCでは、GCCに渡すオプションを記載するために、`-B`が用意されている。
以下のようにしてコンパイルし、実行しみると

```
$ cobc -B -Wl,--no-as-needed -L. -lCFUNC CCALL.cbl 
$ cobcrun CCALL
hello world!
```

ようやく正常に呼び出すことができた。

#解決

いろいろわかったあとたどり着いた情報によると、Ubuntuのリンカの挙動が変わっているらしい。
Ubuntuでopensource COBOLからCのライブラリをリンクして呼び出す際にはコンパイルオプション`-Wl,--no-as-needed`を追加することで解決する。

問題となってるオプションの名称までたどり着けば、情報は見つけやすくなったければども、、、
とりあえず普段使わないオプションについていろいろ調べて勉強になりました。
長い道のりでした。

#参考、たどり着いたサイト

[GNU ld の--no-as-neededと--as-neededオプションについて](http://thatsdone-j.blogspot.com/2013/03/gnu-ld-as-needed-and-no-as-needed.html)