32

I'm looking for ways to restrict the number of C symbols exported to a Linux static library (archive). I'd like to limit these to only those symbols that are part of the official API for the library. I already use 'static' to declare most functions as static, but this restricts them to file scope. I'm looking for a way to restrict to scope to the library.

I can do this for shared libraries using the techniques in Ulrich Drepper's How to Write Shared Libraries, but I can't apply these techniques to static archives. In his earlier Good Practices in Library Design paper, he writes:

The only possibility is to combine all object files which need certain internal resources into one using 'ld -r' and then restrict the symbols which are exported by this combined object file. The GNU linker has options to do just this.

Could anyone help me discover what these options might be? I've had some success with 'strip -w -K prefix_*', but this feels brutish. Ideally, I'd like a solution that will work with both GCC 3 and 4.

Thanks!

5 Answers5

15

I don't believe GNU ld has any such options; Ulrich must have meant objcopy, which has many such options: --localize-hidden, --localize-symbol=symbolname, --localize-symbols=filename.

The --localize-hidden in particular allows one to have a very fine control over which symbols are exposed. Consider:

int foo() { return 42; }
int __attribute__((visibility("hidden"))) bar() { return 24; }

gcc -c foo.c
nm foo.o
000000000000000b T bar
0000000000000000 T foo

objcopy --localize-hidden foo.o bar.o
nm bar.o
000000000000000b t bar
0000000000000000 T foo

So bar() is no longer exported from the object (even though it is still present and usable for debugging). You could also remove bar() all together with objcopy --strip-unneeded.

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • A problem I see with this, is that you have to do it on all object files. Another problem is the case of the OP, when you want to have internal functions in a static library spread out over several object files, if you "hide" a symbol in one object file, how can it be accessed from another object file in the same static library? – Some programmer dude May 24 '12 at 09:40
  • Thanks. Had no idea objcopy existed and it just solved a major headache for me. – Emil H Sep 12 '12 at 20:50
12

Static libraries can not do what you want for code compiled with either GCC 3.x or 4.x.

If you can use shared objects (libraries), the GNU linker does what you need with a feature called a version script. This is usually used to provide version-specific entry points, but the degenerate case just distinguishes between public and private symbols without any versioning. A version script is specified with the --version-script= command line option to ld.

The contents of a version script that makes the entry points foo and bar public and hides all other interfaces:

{ global: foo; bar; local: *; };

See the ld doc at: http://sourceware.org/binutils/docs/ld/VERSION.html#VERSION

I'm a big advocate of shared libraries, and this ability to limit the visibility of globals is one their great virtues.

A document that provides more of the advantages of shared objects, but written for Solaris (by Greg Nakhimovsky of happy memory), is at http://developers.sun.com/solaris/articles/linker_mapfiles.html

I hope this helps.

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
tpgould
  • 1,746
  • 10
  • 10
10

The merits of this answer will depend on why you're using static libraries. If it's to allow the linker to drop unused objects later then I have little to add. If it's for the purpose of organisation - minimising the number of objects that have to be passed around to link applications - this extension of Employed Russian's answer may be of use.

At compile time, the visibility of all symbols within a compilation unit can be set using:

-fvisibility=hidden
-fvisibility=default

This implies one can compile a single file "interface.c" with default visibility and a larger number of implementation files with hidden visibility, without annotating the source. A relocatable link will then produce a single object file where the non-api functions are "hidden":

ld -r interface.o implementation0.o implementation1.o -o relocatable.o

The combined object file can now be subjected to objcopy:

objcopy --localize-hidden relocatable.o mylibrary.o

Thus we have a single object file "library" or "module" which exposes only the intended API.


The above strategy interacts moderately well with link time optimisation. Compile with -flto and perform the relocatable link by passing -r to the linker via the compiler:

gcc -fuse-linker-plugin -flto -nostdlib -Wl,-r {objects} -o relocatable.o

Use objcopy to localise the hidden symbols as before, then call the linker a final time to strip the local symbols and whatever other dead code it can find in the post-lto object. Sadly, relocatable.o is unlikely to have retained any lto related information:

gcc -nostdlib -Wl,-r,--discard-all relocatable.o mylibrary.o

Current implementations of lto appear to be active during the relocatable link stage. With lto on, the hidden=>local symbols were stripped by the final relocatable link. Without lto, the hidden=>local symbols survived the final relocatable link.

Future implementations of lto seem likely to preserve the required metadata through the relocatable link stage, but at present the outcome of the relocatable link appears to be a plain old object file.

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
Jon Chesterfield
  • 2,251
  • 1
  • 20
  • 30
  • I'm explicitly adding the visibility=hidden attribute to certain functions to make them local later on in the object file. This works, but when I'm this object file (or the static lib that contains it) to another project, it can overwrite internal functions by implementing a function with the same name/signature. Is there anyway to prevent this, or better yet have the object file call the function defined within the object file and have the application call it's own definition? – Arno Moonen Apr 13 '21 at 10:12
  • I believe the localize hidden step will do that for you. You may also be interested in the elf weak attribute. – Jon Chesterfield Apr 14 '21 at 12:03
  • I tested it with just "localize hidden" and then the library would call the function from the application instead of it's own function. What I've done now is use `nm` to get a list of the "localized" symbols and add a prefix to all of them using the redefinition option of `objcopy`. This seems to work well. – Arno Moonen Apr 15 '21 at 13:33
4

This is a refinement of the answers from EmployedRussian and JonChesterfield, which may be helpful if you're generating both dynamic and static libraries.

Start with the standard mechanism for hiding symbols in DSOs (the dynamic version of your lib). Compile all files with -fvisibility=hidden. In the header file which defines your API, change the declarations of the classes and functions you want to make public:

   #define DLL_PUBLIC __attribute__ ((visibility ("default")))
   extern DLL_PUBLIC int my_api_func(int);

See here for details. This works for both C and C++. This is sufficient for DSOs, but you'll need to add these build steps for static libraries:

ld -r obj1.o obj2.o ... objn.o -o static1.o
objcopy --localize-hidden static1.o static2.o
ar -rcs mylib.a static2.o

The ar step is optional - you can just link against static2.o.

EML
  • 9,619
  • 6
  • 46
  • 78
0

My way of doing it is to mark everything that is not to be exported with INTERNAL, include guard all .h files, compile dev builds with -DINTERNAL= and compile release builds with a single .c file that includes all other library .c files with -DINTERNAL=static.

Joshua
  • 40,822
  • 8
  • 72
  • 132
  • It would work if you have everything in one file or you can compile to it. Often you do not. Sometimes you need to have small files to combine with other languages (for example Haskell - that's the reason I found this page). – Maciej Piechotka Feb 02 '10 at 21:14