Search code examples
csymbolsbinutils.so.a

Hide symbols from a 3rd party .a file that is linked into a .so file


I am building a shared (.so) library that is composed of several .a files and a thin API layer that invokes them. I only want my API and external dependencies to be visible, so I build my code using the "hidden" visibility offered by GCC (-fvisibility=hidden).

However, one of the libraries is a proprietary third party .a file (which we have paid to use) and I only have access to its binary. When I link it statically into my .so file, its symbols are visible in my .so's dynamic symbol table. I'm guessing that this is because the library was not built with the hidden visibility options. I'd rather keep these functions hidden as the manage a sensitive part of our software and I don't want third parties linking to those symbols.

Is there any way that I can mark these symbols as "hidden" after the fact so that they do not appear in my .so file's symbol list? I have looked at objdump and objcopy but I'm having a hard time with the terminology.

Other things I have tried:


Solution

  • Here is a worked example of how to solve your problem.

    This is the source for the proprietary static library that you can't recompile:

    $ cat tpa.c
    int tpa(void)
    {
        return 2;
    }
    $ cat tpb.c
    int tpb(void)
    {
        return 3;
    }
    

    The library, libtp.a, must have been built essentially like this1:

    $ gcc -fPIC -c -O1 tpa.c tpb.c
    $ ar rcs libtp.a tpa.o tpb.o
    

    The symbol tables of tpa.o and tpb.o are:-

    $ readelf -s libtp.a
    
    File: libtp.a(tpa.o)
    
    Symbol table '.symtab' contains 10 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS tpa.c
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
         3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
         4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
         5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
         6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
         7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
         8: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
         9: 0000000000000000    10 FUNC    GLOBAL DEFAULT    1 tpa
    
    File: libtp.a(tpb.o)
    
    Symbol table '.symtab' contains 10 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS tpb.c
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
         3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
         4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
         5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
         6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
         7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
         8: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
         9: 0000000000000000    10 FUNC    GLOBAL DEFAULT    1 tpb
    

    where you see that both of the function symbols tpa and tpb are GLOBAL ( = available for linkage) and have DEFAULT dynamic visibility, not HIDDEN.

    Now here's the source code for your own static library, libus.a

    $ cat usa.c
    int usa(void)
    {
        return 5;
    }
    $ cat usb.c
    int usb(void)
    {
        return 7;
    }
    

    Which you build like this:

    $ gcc -fPIC -c -O1 -fvisibility=hidden usa.c usb.c
    $ ar rcs libus.a usa.o usb.o
    

    The function symbols in libus.a are also GLOBAL but their dynamic visibility is HIDDEN:-

    $ readelf -s libus.a
    
    File: libus.a(usa.o)
    
    Symbol table '.symtab' contains 10 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS usa.c
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
         3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
         4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
         5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
         6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
         7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
         8: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
         9: 0000000000000000    10 FUNC    GLOBAL HIDDEN     1 usa
    
    File: libus.a(usb.o)
    
    Symbol table '.symtab' contains 10 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 FILE    LOCAL  DEFAULT  ABS usb.c
         2: 0000000000000000     0 SECTION LOCAL  DEFAULT    1 
         3: 0000000000000000     0 SECTION LOCAL  DEFAULT    2 
         4: 0000000000000000     0 SECTION LOCAL  DEFAULT    3 
         5: 0000000000000000     0 SECTION LOCAL  DEFAULT    5 
         6: 0000000000000000     0 SECTION LOCAL  DEFAULT    6 
         7: 0000000000000000     0 SECTION LOCAL  DEFAULT    7 
         8: 0000000000000000     0 SECTION LOCAL  DEFAULT    4 
         9: 0000000000000000    10 FUNC    GLOBAL HIDDEN     1 usb
    

    Here's the source code for your shared library:

    $ cat usc.c
    extern int tpa(void);
    extern int tpb(void);
    extern int usa(void);
    extern int usb(void);
    
    int usc(void)
    {
        return tpa() * tpb() * usa() * usb();
    }
    

    Which you compile:-

    $ gcc -fPIC -c -O1 usc.c
    

    Now you want to link usc.o, libtp.a and libus.a in your shared library libsus.so. If you do it the ordinary way:

    $ gcc -shared -o libsus.so usc.o -L. -ltp -lus
    

    then you find:

    $ readelf --dyn-syms libsus.so
    
    Symbol table '.dynsym' contains 8 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_finalize
         2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
         4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         5: 0000000000001139    38 FUNC    GLOBAL DEFAULT   12 usc
         6: 000000000000115f    10 FUNC    GLOBAL DEFAULT   12 tpa
         7: 0000000000001169    10 FUNC    GLOBAL DEFAULT   12 tpb
    

    that the HIDDEN visibility symbols from libus.a are absent from the dynamic symbol table, but the DEFAULT visibility symbols from libtp.a are included, which you don't want.

    To exclude the latter as well, link your shared library as follows:

    $ gcc -shared -o libsus.so usc.o -L. -ltp -lus -Wl,--exclude-libs=libtp.a
    

    Then the dynamic symbol table becomes:

    $ readelf --dyn-syms libsus.so
    
    Symbol table '.dynsym' contains 6 entries:
       Num:    Value          Size Type    Bind   Vis      Ndx Name
         0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
         1: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __cxa_finalize
         2: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_registerTMCloneTable
         3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND _ITM_deregisterTMCloneTab
         4: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
         5: 00000000000010f9    38 FUNC    GLOBAL DEFAULT   10 usc
    

    as you want.

    The linker option --exclude-libs is documented:

    --exclude-libs lib,lib,...

    Specifies a list of archive libraries from which symbols should not be automatically exported. The library names may be delimited by commas or colons. Specifying --exclude-libs ALL excludes symbols in all archive libraries from automatic export. ... For ELF targeted ports, symbols affected by this option will be treated as hidden.

    For reassurance that the the tp* symbol definitions have been linked, you can still see them in the full symbol table of the shared library:

    $ readelf -s libsus.so | egrep 'FUNC.*(us|tp)(a|b|c)' 
         5: 00000000000010f9    38 FUNC    GLOBAL DEFAULT   10 usc
        41: 0000000000001133    10 FUNC    LOCAL  DEFAULT   10 usa
        44: 000000000000111f    10 FUNC    LOCAL  DEFAULT   10 tpa
        46: 000000000000113d    10 FUNC    LOCAL  DEFAULT   10 usb
        48: 0000000000001129    10 FUNC    LOCAL  DEFAULT   10 tpb
        50: 00000000000010f9    38 FUNC    GLOBAL DEFAULT   10 usc
    

    Just like the explicity hidden us* symbols, they become LOCAL, not available for further linkage. (You see usc twice in the grep because it is listed as both a global and a dynamic symbol).

    And as you can infer from this, we need not have troubled to compile our own us* code with -fvisibility=hidden, as long as we were going to archive it in libus.a for further linkage. We could have linked the shared library like:

    $ gcc -shared -o libsus.so usc.o -L. -ltp -lus -Wl,--exclude-libs=libtp.a,libus.a
    

    with the same effect.


    [1] I specify -fPIC explicitly to be sure of generating position-independent object code that I can link in a DSO, but this has been the GCC default since GCC 6.