2017/03/11

[c/c++]static libraryでAPIを隠すのにもvisibility("hidden")が使えそう

以前、static library(*aというやつね)で、複数ファイルにまたがって使う関数があるけど、外には公開したくないものがあったので、なんとか隠せないかと考えたけど、ダメそうだった。
[c/c++]static libraryで非static関数を隠すのは無理そうだ

 

まあよかろう、と思っていたのだが、ライブラリの機能を追加しているうちに、別のファイルで使っているstatic変数にアクセスしたい状況が発生してしまった。。。
setter/getterを用意すれば使えるものの、それも公開関数になってしまう。
公開しているヘッダファイルに書かなければ使おうとは思わないだろうし、そもそも内輪で使うライブラリなので「使うなよ」で済ませられるのだけど、nmで見るとわかるし、格好が悪い。

 

また調べ始めたところ、これが見つかった。
c - Restricting symbols in a Linux static library - Stack Overflow

前回、shared libraryでしか使えなさそうだと思っていた__attribute((visibility("hidden")))が使えそうなのだ!


まず、ライブラリのソースファイルを用意。
中身はてきとうだ(ちなみに、私は「よしだ」でも「よしお」でもない。"foo", "bar"みたいな感じで使っている。)。

yoshida.h

01: #ifndef YOSHIODA_H__
02: #define YOSHIODA_H__
03: 
04: int yoshida(void);
05: 
06: #endif /* YOSHIODA_H__ */

yoshida.c

01: #include <time.h>
02: #include "yoshida.h"
03: 
04: int __attribute__((visibility("hidden"))) yoshida(void)
05: {
06:     return (int)time(NULL);
07: }

yoshio.h

01: #ifndef YOSHIO_H__
02: #define YOSHIO_H__
03: 
04: int yoshio(void);
05: 
06: #endif /* YOSHIO_H__ */

yoshio.c

01: #include "yoshio.h"
02: #include "yoshida.h"
03: 
04: int yoshio(void)
05: {
06:     return yoshida() * 2;
07: }

yoshida()がライブラリ内だけで使う関数、というイメージだ。

まず、普通にビルド。

$ gcc -O0 -c yoshida.c
$ gcc -O0 -c yoshio.c
$ ar rv yoshi.a yoshida.o yoshio.o
$ nm yoshi.a

yoshida.o:
                 U time
0000000000000000 T yoshida

yoshio.o:
                 U yoshida
0000000000000000 T yoshio

どちらも「T」なので、外部からリンクできる状態だ。

 

で、Stackoverflowに書かれている方法はobjcopyを使うのだが、objcopyはinfileが1つだけのようだから、oファイルを1つにまとめてから実行することになるのかな。

$ ld -r yoshida.o yoshio.o -o yoshi.o
$ nm yoshi.o
                 U time
0000000000000000 T yoshida
0000000000000010 T yoshio

うむ、まとまったようだ。

$ objcopy --localize-hidden yoshi.o yoshi_hidden.o
$ nm yoshi_hidden.o
                 U time
0000000000000000 t yoshida
0000000000000010 T yoshio

おお!
yoshida()が「t」になった!


では、これはグローバル変数でも適用できるのだろうか?

 

yoshida.h

01: #ifndef YOSHIODA_H__
02: #define YOSHIODA_H__
03: 
04: extern int __attribute__((visibility("hidden"))) g_yoshida;
05: 
06: int yoshida(void);
07: 
08: #endif /* YOSHIODA_H__ */

yoshida.c

01: #include 
02: #include "yoshida.h"
03: 
04: int __attribute__((visibility("hidden"))) g_yoshida = 0;
05: 
06: 
07: int __attribute__((visibility("hidden"))) yoshida(void)
08: {
09:     g_yoshida = (int)time(NULL) >> 2;
10: 
11:     return (int)time(NULL);
12: }

yoshio.h

01: #ifndef YOSHIO_H__
02: #define YOSHIO_H__
03: 
04: int yoshio(void);
05: 
06: #endif /* YOSHIO_H__ */

yoshio.c

01: #include "yoshio.h"
02: #include "yoshida.h"
03: 
04: int yoshio(void)
05: {
06:     return yoshida() + g_yoshida;
07: }

$ gcc -c -O0 yoshida.c yoshio.c
$ ld -r yoshida.o yoshio.o -o yoshi.o
$ nm yoshi.o
0000000000000000 B g_yoshida
                 U time
0000000000000000 T yoshida
0000000000000023 T yoshio


$ objcopy --localize-hidden yoshi.o yoshi_hidden.o
$ nm yoshi_hidden.o
0000000000000000 b g_yoshida
                 U time
0000000000000000 t yoshida
0000000000000023 T yoshio

おお、「B」が「b」になった。
B/bはBSSのBで、未初期化データ。
なお、初期値がある場合はD/dとかG/gとか。


ここで安心してはいけない。
ちゃんと、リンクさせてみなくては。

test.c

01: #include 
02: #include "yoshio.h"
03: #include "yoshida.h"
04: 
05: int main(int argc, char *argv[])
06: {
07:     int a;
08: 
09:     a = yoshio();
10:     printf("data=%d\n", a);
11: 
12:     a = yoshida();
13:     printf("data=%d\n", a);
14: 
15:     a = g_yoshida;
16:     printf("data=%d\n", a);
17: 
18:     return 0;
19: }

$ gcc -o tst test.c yoshi_hidden.o
/tmp/ccMESvZf.o: In function `main':
test.c:(.text+0x2c): undefined reference to `yoshida'
test.c:(.text+0x49): undefined reference to `g_yoshida'
/usr/bin/ld: tst: hidden symbol `g_yoshida' isn't defined
/usr/bin/ld: final link failed: Bad value
collect2: error: ld returned 1 exit status

うむ、yoshida()もg_yoshidaもリンクできていない(ヘッダに書いているから、コンパイルはできる)。


今回は、ldでオブジェクトを1つにまとめたものをobjcopyしたが、arで固めたものでもobjcopyで--localize-hiddenできるのだけど、そうしてしまうとライブラリ内ですらhiddenされてしまうので、今回だとyoshio()がyoshida()を参照できなくなってしまう。
だから、オブジェクト内で完結できるようにしてから--localize-hiddenすることになるだろう。

 

-xで「t」や「b」を隠すことができるかと思ったけど、それはできなかった。
残念な気もするが、見栄えは気にしないので、追わないことにする。

 

g_yoshidaに0を代入している。
[c/c++]0を代入したグローバル変数は初期値ありと見なされないようだ
0を代入しても、初期値ありとは見なされず、BSSとして扱われる。
しかし、0を代入しなかった場合、今回は「B」ではなく「C」で扱われていた。

$ nm yoshi_hidden.o
0000000000000004 C g_yoshida
                 U time
0000000000000000 t yoshida
0000000000000023 T yoshio

Cはコモンシンボルというやつで、これは小文字のcがない。つまり、グローバルしかない。
test.cでg_yoshidaにアクセスしたが、リンクも通った。
commonって何だ? nm 謎解き編 - toshi_hirasawaの日記
そういう性質があるので、面倒なことにならないよう、初期化しておく方が無難かも。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。

注: コメントを投稿できるのは、このブログのメンバーだけです。