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.