soファイルの名前を変えても別プログラムからリンクがうまくいかなかった問題での対応。

Introduction

NVIDIA Caffeを改造したライブラリを作っているのですが、都合上オリジナルのNVIDIA Caffe (libcaffe.so) と共存させる必要があり、その改造Caffe (以下libcaffemod.so) をシステムにインストールします。
オリジナルのNVIDIA Caffeの出力は下記。

1
2
3
4
5
6
7
8
9
$ ls -la
total 121664
drwxr-xr-x 2 t-takeuchi t-takeuchi 4096 1月 18 20:09 .
drwxr-xr-x 16 t-takeuchi t-takeuchi 4096 1月 18 19:01 ..
-rw-r--r-- 1 t-takeuchi t-takeuchi 90556292 1月 18 19:14 libcaffe.a
-rw-r--r-- 1 t-takeuchi t-takeuchi 1038618 1月 18 17:30 libcaffe-nv.a
lrwxrwxrwx 1 t-takeuchi t-takeuchi 16 1月 18 20:09 libcaffe.so -> libcaffe.so.0.17
lrwxrwxrwx 1 t-takeuchi t-takeuchi 18 1月 18 20:09 libcaffe.so.0.17 -> libcaffe.so.0.17.3
-rwxr-xr-x 1 t-takeuchi t-takeuchi 32976216 1月 18 22:57 libcaffe.so.0.17.3

単純にファイル名を変えて、システム (/usr/lib/x86_64-linux-gnu/)にインストールしたところでうまく動きません。
というのも、単純に名前を変えてリンクしようとしてもうまくいきません。

例えば、下記の簡単なプログラム。

1
2
3
4
5
6
7
8
#include <iostream>
#include "caffe/caffe.hpp"

int main()
{
const auto& version = caffe::Caffe::caffe_version();
std::cout << version.c_str() << std::endl;
}

これに、libcaffe.so.0.17.3をlibcaffemod.soに名前を変えてコピーしリンクさせてみます。
CMakeLists.txtは下記。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
cmake_minimum_required(VERSION 3.0.0)

add_definitions("-Wall -std=c++11")

set(PROJ_NAME CaffeModExample)
set(CMAKE_C_FLAGS "-fPIC")
set(CMAKE_CXX_FLAGS "-fPIC")

find_package(Boost COMPONENTS filesystem system REQUIRED)

include_directories(../../include
../../3rdparty
/usr/local/cuda/include
../src)
link_directories(../lib)

add_executable(${PROJ_NAME} main.cpp)

target_link_libraries(${PROJ_NAME} caffemod
${Boost_FILESYSTEM_LIBRARY}
${Boost_SYSTEM_LIBRARY})

set(CompilerFlags
CMAKE_CXX_FLAGS
CMAKE_CXX_FLAGS_DEBUG
CMAKE_CXX_FLAGS_RELEASE
CMAKE_C_FLAGS
CMAKE_C_FLAGS_DEBUG
CMAKE_C_FLAGS_RELEASE
)

set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)

実行してみます。単純にCaffeのバージョンを出力するだけです。

ではリンクしているsoファイルは何になるか?

1
2
$ ldd build/CaffeModExample  | grep caffe
libcaffe.so.0.17 => /home/t-takeuchi/nvcaffe/.build_release/example/../lib/libcaffe.so.0.17 (0x00007fb1a9f6a000)

名前変更前にリンクしています。
動いているのはリンク先の同じディレクトリにオリジナルのlibcaffeが存在するからです。
libcaffe.soを全部削除するとプログラムが動かなくなります。

どうするべきか。

How to?

共有オブジェクトファイルは内部に共有オブジェクトの名前を保持しています。
これが原因で、リンク先がファイル名変更前になっていたのです。

1
2
$ objdump -p libcaffemod.so | grep SONAME
SONAME libcaffe.so.0.17

正攻法はNVIDIA CaffeのMakefileやCMakeLists.txtを書き換えて、正しい名前で出力するようにするべきです。
が、単純に変更してもうまくビルドできませんでした。
なので別の方法を。

PatchELF

PatchELFというLinuxの ELF (Executable and Linkable Format)ファイル を操作するツールを使います。

バイナリで配布はされていないのでソースからビルドします。
最新版を使ってください。少し古い版の0.8はうまく動きませんでした。

1
2
3
4
5
6
7
$ wget https://github.com/NixOS/patchelf/archive/0.10.tar.gz
$ tar -xf 0.10.tar.gz && rm 0.10.tar.gz && cd patchelf-0.10
$ ./bootstrap.sh && ./configure
$ make
$ sudo make install
$ ./src/patchelf --version
patchelf 0.10

make installはしてもしなくても。
同じディレクトリのsrc配下にバイナリが生成されるので。

これを使って先のlibcaffemod.soを変更します。
**–set-soname** コマンドを使います。最初の引数が新しい名前、最後が対象ファイルのパスです。

1
2
3
$ src/patchelf --set-soname libcaffemod.so libcaffemod.so 
$ objdump -p libcaffemod.so | grep SONAME
SONAME libcaffemod.so

見事に変更されました。
一度サンプルプログラムのbuildを削除してからビルドし直して、lddでリンク先を確認してみます。

1
2
$ ldd build/CaffeModExample  | grep caffe
libcaffemod.so => /home/t-takeuchi/nvcaffe/.build_release/example/../lib/libcaffemod.so (0x00007f9019edc000)

きちんと変わっていますね。