This is possible! As Dietrich said, all exported symbols in .o
files in a static library are public, and if a file needs to refer to a symbol in another .o
file it needs to be exported from that file (and therefore public). But there's an easy workaround - pre-link all of your .o
files into a single one. Then you only need to export the public symbols.
This is apparently called "Single Object Prelinking", and there's an option to do it in XCode that treert mentioned. But you can do it with just standard command line tools (example repo here):
Check it out (this is on Mac).
First lets create some test files
$ cat private.c
int internal_private_function() {
return 5;
}
$ cat public.c
extern int internal_private_function();
int public_function() {
return internal_private_function();
}
Compile them
$ clang -c private.c -o private.o
$ clang -c public.c -o public.o
Add them to a static library (it's basically a zip file, but in a decades old format).
$ ar -r libeverything_public.a public.o private.o
Check what symbols are in it.
$ objdump -t libeverything_public.a
libeverything_public.a(private.o): file format Mach-O 64-bit x86-64
SYMBOL TABLE:
0000000000000000 g F __TEXT,__text _internal_private_function
libeverything_public.a(public.o): file format Mach-O 64-bit x86-64
SYMBOL TABLE:
0000000000000000 g F __TEXT,__text _public_function
0000000000000000 *UND* _internal_private_function
Ok as you can see both functions are visible, and both symbols are g
which means global.
Now lets prelink into a single file and then put that on its own in a static library.
$ ld -r -o prelinked.o private.o public.o
$ ar -r libeverything_public_prelinked.a prelinked.o
$ objdump -t libeverything_public_prelinked.a
libeverything_public_prelinked.a(prelinked.o): file format Mach-O 64-bit x86-64
SYMBOL TABLE:
0000000000000020 l O __TEXT,__eh_frame EH_Frame1
0000000000000038 l O __TEXT,__eh_frame func.eh
0000000000000060 l O __TEXT,__eh_frame EH_Frame1
0000000000000078 l O __TEXT,__eh_frame func.eh
0000000000000000 g F __TEXT,__text _internal_private_function
0000000000000010 g F __TEXT,__text _public_function
Similar result - they're in one file but both still present and global. Finally let's filter them out (this is Mac specific). We need a list of symbols to export:
$ cat exported_symbols_osx.lds
_public_function
Then use the -exported_symbols_list
option.
$ ld -r -exported_symbols_list exported_symbols_osx.lds -o prelinked_filtered.o private.o public.o
$ ar -r libfiltered_prelinked.a prelinked_filtered.o
ar: creating archive libfiltered_prelinked.a
$ objdump -t libfiltered_prelinked.a
libfiltered_prelinked.a(prelinked_filtered.o): file format Mach-O 64-bit x86-64
SYMBOL TABLE:
0000000000000000 l F __TEXT,__text _internal_private_function
0000000000000020 l O __TEXT,__eh_frame EH_Frame1
0000000000000038 l O __TEXT,__eh_frame func.eh
0000000000000060 l O __TEXT,__eh_frame EH_Frame1
0000000000000078 l O __TEXT,__eh_frame func.eh
0000000000000010 g F __TEXT,__text _public_function
Tada! _internal_private_function
is now a local symbol. You can add the -x
option (or alternatively run strip -x
) to change the name to a random meaningless value (here l001
).
$ ld -r -x -exported_symbols_list exported_symbols_osx.lds -o prelinked_filtered.o private.o public.o
$ objdump -t prelinked_filtered.o
prelinked_filtered.o: file format Mach-O 64-bit x86-64
SYMBOL TABLE:
0000000000000000 l F __TEXT,__text l001
0000000000000020 l O __TEXT,__eh_frame EH_Frame1
0000000000000038 l O __TEXT,__eh_frame func.eh
0000000000000060 l O __TEXT,__eh_frame EH_Frame1
0000000000000078 l O __TEXT,__eh_frame func.eh
0000000000000010 g F __TEXT,__text _public_function
Here's what Apple's linker has to say about -x
:
Do not put non-global symbols in the output file's symbol table. Non-global symbols are useful when debugging and getting symbol names in back traces, but are not used at runtime. If -x is used with -r non-global symbol names are not removed, but instead replaced with a unique, dummy name that will be automatically removed when linked into a final linked image. This allows dead code stripping, which uses symbols to break up code and data, to work properly and provides the security of having source symbol names removed.
All of this is the same on Linux except -exported_symbols_list
. On Linux I think you have to use --version-script
with a file like this:
V0 {
global:
_public_function;
local:
*;
};
But I haven't tested this yet. Both this file and the exported_symbols_list
files support wildcards.