Symbol Visibility Using G++

Symbol visibility using g++

Public or hidden, symbols are still there. nm shows all symbols. The difference is that hidden symbols are not available to the dynamic linker, i.e. not exported and can not be interposed.

You might also like the following man gcc:

   -fvisibility=default|internal|hidden|protected
...
A good explanation of the benefits offered by ensuring ELF symbols
have the correct visibility is given by "How To Write Shared
Libraries" by Ulrich Drepper (which can be found at
<http://people.redhat.com/~drepper/>)---however a superior solution
made possible by this option to marking things hidden when the
default is public is to make the default hidden and mark things
public. This is the norm with DLL's on Windows and with
-fvisibility=hidden and "__attribute__ ((visibility("default")))"
instead of "__declspec(dllexport)" you get almost identical
semantics with identical syntax. This is a great boon to those
working with cross-platform projects.

Symbol visibility and gcc warnings

In both cases, an explicit visibility attribute is the intended way to silence the warning on a per-class basis. From gcc/cp/decl2.c:

/* Don't warn about visibility if the class has explicit visibility.  */
if (CLASSTYPE_VISIBILITY_SPECIFIED (type))
vis = VISIBILITY_INTERNAL;

How to use the __attribute__((visibility(default)))?


Is it possible to customize which functions are exposed by modifying the compilation command above?

No. Compilation option -fvisibility=[default|internal|hidden|protected]
(and note it is not a linkage option) makes the compiler attribute the specified dynamic visibility type to all global symbols
generated in the compilation unit except those that are specifically excluded by having a countervailing __attribute__((visibility(....)))
applied in the source code. Which makes the answer to your other question:

Is it possible to make area() public and set_values(int,int) local as shown in the first link without altering the code?

also No.

How would you change the source code to make Rectangle::area() dynamically
visible while all other global symbols are hidden for dynamic linkage by -fvisibility=hidden?
Here is a walk-through:

Let's start with:

rectangle.cpp (1)

class Rectangle {
int width, height;
public:
void set_values (int,int);
int area() {return width*height;}

};

void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}

and simply compile it to a PIC rectangle.o so:

$ g++ -Wall -c -fPIC rectangle.cpp

Then check the global symbol table:

$ nm -C rectangle.o
0000000000000000 T Rectangle::set_values(int, int)

Note that Rectangle::area isn't there. It's not available for
linkage at all, so the question of its dynamic visibility just does not arise.

That is because it is defined inline in the class definition and never called
in the compilation unit, so gcc need not even compile its definition. It vanishes
from the object file.

Rectangle::set_values, on the other hand, is not defined inline, so the compiler
emits a global symbol and definition.

To make Rectangle::area eligible for some visibility type, we first need to make
it a global symbol by not defining it inline:

rectangle.cpp (2)

class Rectangle {
int width, height;
public:
void set_values (int,int);
int area();

};

int Rectangle::area() {return width*height;}

void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}

Recompile and again check the global symbol table:

$ g++ -Wall -c -fPIC rectangle.cpp
$ nm -C rectangle.o
000000000000001a T Rectangle::set_values(int, int)
0000000000000000 T Rectangle::area()

Good. Now a global definition of Rectangle::area appears.

Next let's make a shared library librectangle.so from rectangle.o:

$ g++ -o librectangle.so --shared rectangle.o

Here are the Rectangle::* symbols in its global symbol table:

$ nm -C librectangle.so | grep 'Rectangle::'
00000000000005d4 T Rectangle::set_values(int, int)
00000000000005ba T Rectangle::area()

And here are the Rectangle::* symbols in its dynamic symbol table:

$ nm -CD librectangle.so | grep 'Rectangle::'
00000000000005d4 T Rectangle::set_values(int, int)
00000000000005ba T Rectangle::area()

They're the same.

Now let's hide those symbols for dynamic linkage. We need to recompile rectangle.cpp
then relink the shared library:

$ g++ -Wall -c -fPIC -fvisibility=hidden rectangle.cpp
$ g++ -o librectangle.so --shared rectangle.o

Here again are the Rectangle::* symbols now in the global symbol table:

$ nm -C librectangle.so | grep 'Rectangle::'
0000000000000574 t Rectangle::set_values(int, int)
000000000000055a t Rectangle::area()

They're the same as before.

And here are the Rectangle::* symbols now in the dynamic symbol table:

$ nm -CD librectangle.so | grep 'Rectangle::'; echo Done
Done

Now there are none, thanks to -fvisibility=hidden.

Finally, let's make just Rectangle::area dynamically visible, keeping all
the other global symbols dynamically hidden. We need to change the source code
again:

rectangle.cpp (3)

class Rectangle {
int width, height;
public:
void set_values (int,int);
__attribute__((visibility("default"))) int area();

};

int Rectangle::area() {return width*height;}

void Rectangle::set_values (int x, int y) {
width = x;
height = y;
}

Then recompile and relink:

$ g++ -Wall -c -fPIC -fvisibility=hidden rectangle.cpp
$ g++ -o librectangle.so --shared rectangle.o

The global symbol table still shows:

$ nm -C librectangle.so | grep 'Rectangle::'
00000000000005a4 t Rectangle::set_values(int, int)
000000000000058a T Rectangle::area()

And the dynamic symbol table only shows:

$ nm -CD librectangle.so | grep 'Rectangle::'
000000000000058a T Rectangle::area()

Rectangle::area is now the only symbol that the shared library exposes for
dynamic linkage.

And before you go...

One thing more about:

Is it possible to make area() public and set_values(int,int) local as shown in the first link without altering the code?

Making a symbol hidden for dynamic linkage doesn't make it local. Dynamic visibility (default|internal|hidden|protected)
is an attribute of global symbols only. For linkage purposes, local symbols don't exist. The only ways to
make a symbol local that would otherwise be global are:-

  • In C or C++ source, qualify its definition with the static keyword
  • In C++ source, enclose its definition in an anonymous namespace

Then the symbol does not appear in the global, or dynamic, symbol tables.

Hiding symbol names in library

The visibility("hidden") attribute does not suppress a symbol from
an object file and cannot prevent a symbol being extracted by nm. It just
instructs the dynamic linker that the symbol cannot be called from outside
a shared library that contains it.

Consider a source file file.c containing your example functions:

int f_b1(){
return 21 ;
}

int f_b3(){
return f_b1() ;
}

Compile the file:

gcc -c -o file.o file.c

Run nm file.o to list the symbols. Output:

0000000000000000 T f_b1
000000000000000b T f_b3

Now run objdump -t file.o for fuller information about the symbols. Output:

file.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 file.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 000000000000000b f_b1
000000000000000b g F .text 000000000000000b f_b3

Here we see that f_b1 and f_b3 are global (g) functions (F) in the .text
section.

Now modify the file like this:

__attribute__((visibility ("hidden"))) int f_b1(void){
return 21 ;
}

__attribute__((visibility ("hidden"))) int f_b3(void){
return f_b1() ;
}

Run objdump again:

file.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 file.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g F .text 000000000000000b .hidden f_b1
000000000000000b g F .text 000000000000000b .hidden f_b3

The output is the same, except that the symbols f_b1 and f_b3 are now marked
.hidden. They still have external (global) linkage and could be statically called, for
example, from other modules within a library that contains them, but could
not be dymamically called from outside that library.

So, if you want to conceal f_b1 and f_b3 from dynamic linkage in a shared
library, you can use visibility ("hidden") as shown.

If you want to conceal f_b1 and f_b3 from static linkage in a static
library, you cannot use the visibility attribute to do that at all.

In the case of a static library, you can "hide" a symbol only be giving it
internal instead of external linkage. The way to do that is by prefixing the
standard static keyword. But internal linkage means that the symbol is
visible only within its own compilation unit: it can't be referenced from
other modules. It is not available to the linker at all.

Modify file.c again, like this:

static int f_b1(void){
return 21 ;
}

static int f_b3(void){
return f_b1() ;
}

And run objump again:

file.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 file.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l F .text 000000000000000b f_b1
000000000000000b l F .text 000000000000000b f_b3
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment

You see that f_b1 and f_b3 are still reported as functions in the .text
section, but are now classified local (l), not global. That is internal linkage.
Run nm file.o and the output is:

0000000000000000 t f_b1
000000000000000b t f_b3

That is the same as for the original file, except that instead of 'T' flags
we now have 't' flags. Both flags mean that the symbol is in the .text section,
but 'T' means it is global and 't' means it is local.

Apparently, what you would like nm to report for this file is no symbols at all.
You should now understand that nm file.o will report a symbol if it exists in
file.o, but its existence has got nothing to do with whether it is visible
for static or dynamic linkage.

To make the function symbols disappear, compile file.c yet again
(still with the static keyword), this time with optimisation enabled:

gcc -c -O1 -o file.o file.c

Now, objdump reports:

file.o:     file format elf64-x86-64

SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 file.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .comment 0000000000000000 .comment

f_b1 and f_b3 are gone, and nm file.o reports nothing at all. Why?
Because static tells the compiler that these symbols can only be called
from within the file it is compiling, and optimisation decides that there
is no need to refer to them; so the compiler eliminates them from the
object code. But if they weren't already invisible to linker, without
optimisation, then we couldn't optimise them away.

Bottom line: It doesn't matter whether nm can extract a symbol. If the
symbol is local/internal, it can't be linked, either statically or dynamically.
If the symbol is marked .hidden then it can't be dynamically linked. You
can use visibility("hidden") to mark a symbol .hidden. Use the standard
static keyword to make a symbol local/internal.

Gcc hide visibility of symbols coming from included static library

You need to compile libStatic.a also with flag -fvisibility=hidden.

How to apply -fvisibility option to symbols in static libraries?

Basically, visibility is handled during linking, and the linker doesn't seem impose it on static archives. A related question (though not a duplicate) was asked on SO here.

What I would advise you to do is to replace your linking stage: gcc -shared -o mylib.so foo.o libbar.a into a two stages process where you get back the object files:

  • ar x libbar.a (possibly into a suitable, empty directory)
  • gcc -fvisibility=hidden -shared -o mylib.so foo.o tempdir/*.o

Symbol visibility and manipulation in static libraries

Not possible. A static library can be thought of as a single file archive of all the unlinked object files. So what is visible were one to link an object file is visible from the static library as a whole.

The only way to "hide" things is by not providing the header file but nothing stops you user recreating those header files themself.

Can I use gcc visibility attributes on an enumeration?

The visibility attribute applies to symbols like functions and variables. A definition of an enumeration type that doesn't contain a variable name doesn't create any symbols.

Enumeration type without a variable:

enum msg { OK, FAIL };

An enumeration variable:

enum msg message;

Enumeration type together with a variable:

enum msg { OK, FAIL } message;

In the first case, there's no symbol the visibility attribute could affect at all.



Related Topics



Leave a reply



Submit