SUN编译器的选择--一个链接问题
2012-06-30 22:40
148 查看
Article
Reducing Symbol Scope with Sun Studio C/C++
performance of an application by reducing the runtime relocation costs of the dynamic libraries. To indicate the appropriate linker scoping in a source program, you can now use language extensions built into the Sun Studio C/C++ compilers as described here. Contents: Linker Scoping Linker Scoping with Sun Studio Compilers Examples Windows Compatibility With __declspec Automatic Data Imports Benefits of Linker Scoping Appendix Resources Until the release of the Sun Studio 8 compilers, linker mapfiles were the only way to change the default symbol processing by the linker. With the help of mapfiles, all non-interface1 symbols |
Declaration Specifier | -xldscope value | Reference Binding | Visibility of Definitions |
---|---|---|---|
__global | global | First Module | All Modules |
__symbolic | symbolic | Same Module | All Modules |
__hidden | hidden | Same Module | Same Module only |
Linker scoping specifiers are applicable to
struct, class, and
uniondeclarations
and definitions. Consider the following example:
__hidden struct__symbolic BinaryTree node;
The declaration specifier before the
structkeyword applies to variable
node.
The class key modifier after the
structkeyword applies to the type
BinaryTree.
Quick Note:
Make sure that the compilers were patched with all latest compiler patches from SunSolve. Many of the linker scoping bugs were already fixed and distributed as patches since the release of Sun Studio 8. All these patches are freely downloadable from the SunSolve web
site.
Rules for using these specifiers
A symbol definition may be redeclared with a more restrictive specifier, but may not be redeclared with a less restrictive specifier. This definition corresponds well with the ELF9 definition,
which says that the symbol scoping chosen is the most restrictive.
A symbol may not be declared with a different specifier once the symbol has been defined. This is due to the fact that C++ class members cannot be redeclared. In C++, an entity must be defined exactly once; repeated
definitions of the same entity in separate translation units result in a error.
All virtual functions must be visible to all compilation units that include the class definition because the declaration of virtual functions affects the construction and interpretation of virtual tables.
Additional Notes:
Declaration specifiers apply to all declarations as well as definitions.
Function and variable declarations are unaffected with
-xldscopeflag, only the definitions are affected.
With Sun Studio 8 compilers, out-of-line inline functions were static, and thus always hidden. With Sun Studio 9, out-of-line inline functions are global by default, and are affected by linker scoping specifiers
and
-xldscope.
C does not have (or need)
structlinker scoping.
Library functions declared with the
__hiddenor
__symbolicspecifiers
can be generated inline when building the library. They are not supposed to be overridden by clients. If you intend to allow a client to override a function in a library, you must ensure that the function is not generated inline in the library.
The compiler inlines a function if you:
specify the function name with
-xinline
compile at
-xO4or higher in which case inlining can happen automatically
use the
inlinespecifier, or
use the
#pragma inline
Library functions declared with the
__globalspecifier, should not be declared inline, and should be protected from inlining
by use of the
-xinlinecompiler option.
-xldscopeoption does not apply to tentative10 definitions;
tentative definitions continue to have global scope.
If the source file with static symbols is compiled with
-xldscope=symbolic, and if the same object file is used in building
more than one library, dynamically loading/unloading, referencing the common symbols from those libraries may lead to a crash during run-time, due to the possible symbol conflict. This is due to the globalization of static symbols to support "fix and continue"
debugging. These global names must be interposable for "fix and continue" to work.
If the same object file say x.o, has to be linked in creating more than one library, use object file (x.o) with a different timestamp each time you build a new library ie., compile the original source again just before building a new library. Or compile the
original source to create object files with different names say x_1.o, x_2.o, etc., and use those unique object file names in building new libraries.
The scoping restraints that we specify for a static archive or an object file will not take effect until the file is linked into a shared library or an executable. This behavior can be seen in the following C program
with mixed specifiers:
% cat employee.c __global const float lversion = 1.2; __symbolic int taxrate; __hidden struct employee { int empid; char *name; } Employee; __global void createemployee(int id, char *name) { } __symbolic void deleteemployee(int id) { } __hidden void modifyemployee(int id) { } % cc -c employee.c % elfdump -s employee.o | egrep -i "lver|tax|empl" | grep -v "employee.c" [5] 0x00000004 0x00000004 OBJT GLOB P 0 COMMON taxrate [6] 0x00000004 0x00000008 OBJT GLOB H 0 COMMON Employee [7] 0x00000068 0x00000018 FUNC GLOB H 0 .text modifyemployee [8] 0x00000040 0x00000018 FUNC GLOB P 0 .text deleteemployee [9] 0x00000010 0x0000001c FUNC GLOB D 0 .text createemployee [10] 0x00000000 0x00000004 OBJT GLOB D 0 .rodata lversion
In this example, though different visibility was specified for all the symbols, scoping restraints were not in affect in the ELF relocatable object. Due to this, all symbols have global (
GLOB)
binding. However the object file is holding the corresponding ELF symbol visibility attributes for all the symbols according to their binding type.
Variable
lversionand function
createemployeehave
attribute
D, which stands for
DEFAULTvisibility
(that is,
__global). So those two symbols are visible outside of the defining component, the executable file or shared object.
taxrate&
deleteemployeehave
attribute
P, which stands for
PROTECTEDvisibility
(
__symbolic). A symbol that is defined in the current component is protected, if the symbol is visible in other components, but cannot be preempted. Any reference
to such a symbol from within the defining component must be resolved to the definition in that component. This resolution must occur, even if a symbol definition exists in another component that would interpose by the default rules.
Function
modifyemployeeand structure
Employeewere
HIDDENwith
attribute
H(
__hidden). A symbol
that is defined in the current component is hidden if its name is not visible to other components. Such a symbol is necessarily protected. This attribute is used to control the external interface of a component. An object named by such a symbol can
still be referenced from another component if its address is passed outside.
A hidden symbol contained in a relocatable object is either removed or converted to local (
LOCL) binding when the object
is included in an executable file or shared object. It can be seen in the following example:
% cc -G -o libempl.so employee.o
% elfdump -sN.dynsymlibempl.so | egrep -i "lver|tax|empl"
[5] 0x00000298 0x00000018 FUNC GLOB P 0 .text deleteemployee
[6] 0x00010360 0x00000004 OBJT GLOB P 0 .bss taxrate
[9] 0x000002f4 0x00000004 OBJT GLOB D 0 .rodata lversion
[11] 0x00000268 0x0000001c FUNC GLOB D 0 .text createemployee
% elfdump -sN.symtab libempl.so | egrep -i "lver|tax|empl" \
| grep -v "libempl.so" | grep -v "employee.c"
[19] 0x000002c0 0x00000018 FUNC LOCL H 0 .text modifyemployee
[20] 0x00010358 0x00000008 OBJT LOCL H 0 .bss Employee
[36] 0x00000298 0x00000018 FUNC GLOB P 0 .text deleteemployee
[37] 0x00010360 0x00000004 OBJT GLOB P 0 .bss taxrate
[40] 0x000002f4 0x00000004 OBJT GLOB D 0 .rodata lversion
[42] 0x00000268 0x0000001c FUNC GLOB D 0 .text createemployee
Because of the
__hiddenspecifier,
Employeeand
modifyemployeewere
locally bound (
LOCL) with hidden (
H) visibility and didn't show up in
dynamic symbol table; hence
Employeeand
modifyemployeecan not go into
the procedure linkage table (PLT), and the run-time linker need only deal with four out of six symbols.
Default Scope
At this point, it is worth looking at the default scope of symbols without linker scoping mechanism in force, to practically observe the things we learned so far:
% cat employee.c
const float lversion = 1.2;
int taxrate;
struct employee {
int empid;
char *name;
} Employee;
void createemployee(int id, char *name) { }
void deleteemployee(int id) { }
void modifyemployee(int id) { }
% cc -c employee.c
% elfdump -s employee.o | egrep -i "lver|tax|empl" | grep -v "employee.c"
[5] 0x00000004 0x00000004 OBJT GLOB D 0 COMMON taxrate
[6] 0x00000004 0x00000008 OBJT GLOB D 0 COMMON Employee
[7] 0x00000068 0x00000018 FUNC GLOB D 0 .text modifyemployee
[8] 0x00000040 0x00000018 FUNC GLOB D 0 .text deleteemployee
[9] 0x00000010 0x0000001c FUNC GLOB D 0 .text createemployee
[10] 0x00000000 0x00000004 OBJT GLOB D 0 .rodata lversion
% cc -G -o libempl.so employee.o
% elfdump -sN.dynsymlibempl.so | egrep -i "lver|tax|empl"
[1] 0x00000344 0x00000004 OBJT GLOB D 0 .rodata lversion
[4] 0x000103a8 0x00000008 OBJT GLOB D 0 .bss Employee
[6] 0x000002e8 0x00000018 FUNC GLOB D 0 .text deleteemployee
[9] 0x00000310 0x00000018 FUNC GLOB D 0 .text modifyemployee
[11] 0x000103b0 0x00000004 OBJT GLOB D 0 .bss taxrate
[13] 0x000002b8 0x0000001c FUNC GLOB D 0 .text createemployee
% elfdump -sN.symtab libempl.so | egrep -i "lver|tax|empl" \
| grep -v "libempl.so" | grep -v "employee.c"
[30] 0x00000344 0x00000004 OBJT GLOB D 0 .rodata lversion
[33] 0x000103a8 0x00000008 OBJT GLOB D 0 .bss Employee
[35] 0x000002e8 0x00000018 FUNC GLOB D 0 .text deleteemployee
[38] 0x00000310 0x00000018 FUNC GLOB D 0 .text modifyemployee
[40] 0x000103b0 0x00000004 OBJT GLOB D 0 .bss taxrate
[42] 0x000002b8 0x0000001c FUNC GLOB D 0 .text createemployee
From the above
elfdumpoutput, all the six symbols were having global binding. So, PLT will be holding atleast six symbols.
Suggestions on establishing an object interface
Define all interface symbols using the
__globaldirective, and reduce all other symbols to local using the
-xldscope=hiddencompiler
option. This model provides the most flexibility. All global symbols are interposable, and allow for any copy relocations to be processed correctly. Or,
Define all interface symbols using the
__symbolicdirective, data objects using the
__globaldirective
and reduce all other symbols to local using the
-xldscope=hiddencompiler option. Symbolic symbols are globally visible, but have been internally bound to. This means
that these symbols do not require symbolic runtime relocation, but can not be interposed upon, or have copy relocations against them. Note that the problem of copy relocations only applies to data, but not to functions. This mixed model in which functions
are symbolic and data objects are global will yield more optimization opportunities in the compiler.
In short: if we do not want a user to interpose upon our interfaces, and don't export data items, the second model ie., mixed model with
__symbolic&
__global,
is the best. If in doubt, better stick to the more flexible use of
__global(first model).
Examples
Exporting of symbols in dynamic libraries can be controlled with the help of __global,
__symbolic,
and
__hiddendeclaration specifiers. Look at the following header file:
% [code]cat tax.h
int taxrate = 33;
float calculatetax(float);[/code]
If the
taxrateis not needed by any code outside of the module, we can hide it with
__hiddenspecifier
and compile with
-xldscope=globaloption. Or leave
taxrateto the default
scope, make
calculatetax()visible outside of the module by adding
__globalor
__symbolicspecifiers
and compile the code with
-xldscope=hiddenoption. Let's have a look at both approaches.
First Approach:% [code]more tax.h __hidden int taxrate = 33; float calculatetax(float); % more tax.c #include "tax.h" float calculatetax(float amount) { return ((float) ((amount * taxrate)/100)); } % cc -c -KPIC tax.c % cc -G -o libtax.so tax.o % elfdump -s tax.o | egrep "tax" [8] 0x00000010 0x00000068 FUNC GLOB D 0 .text calculatetax [9] 0x00000000 0x00000004 OBJT GLOB H 0 .data taxrate % elfdump -s libtax.so | egrep "tax" [6] 0x00000240 0x00000068 FUNC GLOB D 0 .text calculatetax [23] 0x00010350 0x00000004 OBJT LOCL H 0 .data taxrate [42] 0x00000240 0x00000068 FUNC GLOB D 0 .text calculatetax [/code] Second Approach: % [code]more tax.h int taxrate = 33; __global float calculatetax(float); % more tax.c #include "tax.h" float calculatetax(float amount) { return ((float) ((amount * taxrate)/100)); } % cc -c-xldscope=hidden -Kpic tax.c % cc -G -o libtax.so tax.o % elfdump -s tax.o | egrep "tax" [8] 0x00000010 0x00000068 FUNC GLOB D 0 .text calculatetax [9] 0x00000000 0x00000004 OBJT GLOB H 0 .data taxrate % elfdump -s libtax.so | egrep "tax" [6] 0x00000240 0x00000068 FUNC GLOB D 0 .text calculatetax [23] 0x00010350 0x00000004 OBJT LOCL H 0 .data taxrate [42] 0x00000240 0x00000068 FUNC GLOB D 0 .text calculatetax [/code] |
-xldscopeflag.
(Appendix (1) shows the binding types with all possible source interfaces (specifiers) and command line option,
-xldscope)
Let's try to build a driver that invokes
calculatetax()function. But at first, let's modify
tax.hslightly
to make
calculatetax()non-interposable (refer to bullet 2, in "Suggestions on establishing an object interface" section); and build
libtax.so
% cat tax.h [7] 0x00000000 0x00000000 FUNC GLOB P 0 UNDEF calculatetax [/code] |
driver.c) is trying to export (
__symbolic)
the definition of
calculatetax(), instead of importing (
__global) it.
The declarations within header files shared between the library and the clients must ensure that clients and implementation have different values for the linker scoping of public symbols. So, the simple fix is to export the symbol while the library being built
and import it when the client program needs it. This can be done by either copying the
tax.hto another file and changing the specifier to
__global,
or by using preprocessor conditionals (with
-Doption) to alter the declaration depending on whether the header file is used in building the library or by a client.
Using separate header files for clients and library, leads to code maintenance problems. Even though using a compiler directive eases the pain of writing and maintaining two header files, unfortunately it places lots of implementation details in the public
header file. The following example illustrates this by introducing the compiler directive
BUILDLIBfor building the library.
% [code]more tax.h int taxrate = 33; #ifdef BUILDLIB __symbolic float calculatetax(float); #else __global float calculatetax(float); #endif[/code] |
BUILDLIBto be non-zero; so, the symbol
calculatetaxwill
be exported. While building a client program with the same header file, the
BUILDLIBvariable is set to zero, and
calculatetaxwill
be made available to the client ie., the symbol will be imported.
You may want to emulate this system by defining macros for your own libraries. This implies that you have to define a compiler switch (analogous to
BUILDLIB) yourself.
This can be done with -D flag of Sun Studio C/C++ compilers. Using -D option at command line is equivalent to including a
#definedirective at the beginning of the
source. Set the switch to non-zero when you're building your library, and then set it to zero when you publish your headers for use by library clients.
Let's continue with the example by adding the directive
BUILDLIBto the compile line that builds
libtax.so.
% [code]make Compiling tax.c .. cc -c-xldscope=hidden -Kpic tax.c Building libtax library .. cc -G -DBUILDLIB -o libtax.so tax.o Building driver program .. cc -ltax -o driver driver.c Executing driver .. ./driver ** Tax on $2525 = 833.25 ** [/code] |
restrictive linker scope for use within the library.
% cat tax_public.h float calculatetax(float); % cat tax_private.h #include "tax_public.h" int taxrate = 33; % cat tax_private.c #include "tax_private.h" __symbolic float calculatetax(float amount) { return ((float) ((amount * taxrate)/100)); }
This code makes the symbol
calculatetaxsymbolic when compiling the tax_private.c; and compiler makes optimizations knowing that
calculatetaxis
symbolic and is only available within the one object file. To make these optimizations known to the entire library, the function would be redeclared in the private header.
% cat tax_private.h #include "tax_public.h" int taxrate = 33; __symbolic float calculatetax(float);
To export the symbol
calculatetax, private header should be used while building the library.
% cat tax_private.c
#include "tax_private.h"
float calculatetax(float amount) {
return ((float) ((amount * taxrate)/100));
}
% cc -c-xldscope=hidden -KPIC tax_private.c
% cc -G -o libtax_private.so tax_private.o
Public header should be used while building the client program, so the client can access
calculatetax, since it will have
global visibility.
% cat driver.c #include <stdio.h> #include "tax_public.h" int main() { printf("** Tax on $2525 = %0.2f **\n", calculatetax(2525)); return (0); } % cc -ltax_private -o driver driver.c % ./driver ** Tax on $2525 = 833.25 **
The trade-off with this alternate approach is that we need two sets of header files, one for exporting the symbols and the other for importing them.
Windows compatibility with __declspec
Sun Studio 9 compilers introduced a new keyword called __declspecand supports
dllexportand
dllimportstorage-class
attributes (or specifiers) to facilitate the porting of applications developed using Microsoft Windows compilers to Solaris.
Syntax:
storage... __declspec( dllimport ) type declarator... storage... __declspec( dllexport ) type declarator...
On Windows, these attributes define the symbols exported (the library as a provider) and imported (the library as a client).
On Sun Solaris,
__declspec(dllimport)maps to
__globaland
the
__declspec(dllexport)maps to the
__symbolicspecifier. Note that
the semantics of these keywords are somewhat different on Microsoft and Sun platforms. So, the applications being developed natively on Sun platform are strongly encouraged to stick to Sun specified syntax, instead of using Microsoft specific extensions to
C/C++.
Just to present the syntax, the following sections, especially
__declspec(
dllexport)
and
__declspec(dllimport), assume that we are going to use
__declspeckeyword
in place of linker scoping specifiers.
__declspec(dllexport)
While building a shared library, all the global symbols of the library should be explicitly exported using the
__declspeckeyword.
To export a symbol, the declaration will be like:
__declspec(dllexport) type name
where "
_declspec(dllexport)" is literal, and type and name declare the symbol.
for example,
__declspec(dllexport) char *printstring(); class __declspec(dllexport) MyClass {...}
Data, functions, classes, or class member functions from a shared library can be exported using the
__declspec(dllexport)keyword.
When building a library, we typically create a header file that contains the function prototypes and/or classes we are exporting, and add
__declspec(dllexport)to
the declarations in the header file.
Sun Studio compilers map
__declspec(dllexport)to
__symbolic;
hence the following two declarations are equivalent:
__symbolic void printstring();
__declspec(dllexport) void printstring();
__declspec(dllimport)
To import the symbols that were exported with
__declspec(dllexport), a client, that wants to use the library must reverse
the declaration by replacing
dllexportwith
dllimport
for example,
__declspec(dllimport) char *printstring(); class __declspec(dllimport) MyClass{...}
A program that uses public symbols defined by a shared library is said to be importing them. While creating header files for applications that use the libraries to build with,
__declspec(dllimport)should
be used on the declarations of the public symbols.
Sun Studio compilers map
__declspec(dllimport)to
__global;
hence the following two declarations are equivalent:
__global void printstring();
__declspec(dllimport) void printstring();
Automatic data imports
Windows C/C++ compilers may accept code that is declared with __declspec(dllexport), but actually imported. Such code
will not compile with Sun Studio compilers. The dllexport/dllimport attributes must be correct. This constraint increases the effort necessary to port existing Windows code to Solaris esp. for large C++ libraries and applications.
For example, the following code may compile and run on Windows, but doesn't compile on Sun.
% cat util.h __declspec(dllexport) long multiply (int, int); % cat util.cpp #include "util.h" long multiply (int x, int y) { return (x * y); } % cat test.cpp #include <stdio.h> #include "util.h" int main() { printf(" 25 * 25 = %ld", multiply(25, 25)); return (0); } % CC -G -o libutil.so util.cpp % CC -o test test.cpp -L. -R. -lutil Undefined first referenced symbol in file multiply test.o (symbol scope specifies local binding) ld: fatal: Symbol referencing errors. No output written to test % elfdump -CsN.symtab test.o | grep multiply [3] 0x00000000 0x00000000 FUNC GLOB P 0 UNDEF long multiply(int,int)
The normal mapping for __declspec(dllexport) is__symbolic, which requires that the symbol be inside the library, which is not true for imported symbols. The correct solution is for customers to change their Microsoft code to use __declspec(dllimport) to declare
imported symbols. (The solution with an example, was already discussed in the previous paragraphs)
To port such code, Sun provided a solution with an undocumented compiler option, that does not involve changing the source base.
-xldscoperefis the new compiler option,
and it accepts two values
global, or
keyword. This option is available
with Sun Studio 9 and later versions by default; and to Sun Studio 8, as a patch 112760-01 (or later) and 112761-01 (or later) for SPARC and x86 platforms respectively
-xldscoperef={global|keyword}
The value
keywordindicates that a symbol will have the linker scoping specified by any keywords given with the symbol's
declarations. This value is the current behavior and the default. The value
globalindicates that all undefined symbols should have global linkage11,
even if a linker scoping keyword says otherwise. Note that
__declspec(dllimport)and
__declspec(dllexport)still
map to
__globaland
__symbolicrespectively; and only undefined symbols
were affected with
-xldscoperef=globaloption.
So, compiling the code from the above example with
-xldscoperef=globalwould succeed and produce the desired result. Since
-xldscoperefis
not a first class option, it has to be passed to the front end with the help of
-Qoptionoption of C++ compiler and with
-Woption
of C compiler. (
-Qoption ccfe -xldscoperef=globalfor C++ and
-W0,-xldscoperef=globalfor
C)
% CC -Qoption ccfe -xldscoperef=global -lutil -o test test.cpp % ./test 25 * 25 = 625 % elfdump -CsN.symtab test.o | grep multiply [3] 0x00000000 0x00000000 FUNC GLOB D 0 UNDEF long multiply(int,int)
Let's conclude by stating some of the benefits of reduced linker scoping.
Benefits of Linker Scoping
The following paragraphs explain some of the benifits of linker scoping feature. We can take advantage of most of the benefits listed, just by reducing the scope of all or most of the symbols in our applicationfrom global to local.
Less chance for name collisions with other libraries:
With C++, namespaces are the preferred method for avoiding name collisions. But applications that rely heavily on C style programming and doesn't use namespace mechanism, are vulnerable to name collisions.
Name collisions are hard to detect and debug. Third party libraries can create havoc when some of their symbol names coincide with those in the application. For example, if a third-party shared library uses a global symbol with the same name as a global symbol
in one of the application's shared libraries, the symbol from the third-party library may interpose on ours and unintentionally change the functionality of the application without any warning. With symbolic scoping, we can make it hard to interpose symbols
and ensure the correct symbol being used during run-time.
Improved performance:
Reducing the exported interfaces of shared objects greatly reduces the runtime overhead of processing these objects and improves the application startup time & the runtime performance. Due to the reduced symbol visibility, the symbol count is reduced, hence
less overhead in runtime symbol lookup, and the relocation count is reduced, hence less overhead in fixing up the objects prior to their use.
Thread-Local Storage (TLS)
Access to thread-local storage can be significantly faster as the the compiler knows the inter-object, intra-linker-module relationship between a reference to a symbol and the definition of that symbol. If the backend knows that a symbol will not be exported
from a dynamic library or executable it can perform optimizations which it couldn't perform before when it only knew the scope relative to the relocatable object being built.
Position Independent Code with
-Kpic
With most symbols hidden, there are fewer symbols in the library, and the library may be able to use the more efficient -Kpic rather than the less efficient -KPIC.
The PIC-compiled code allows the linker to keep a read-only version of the text (code) segment for a given shared library. The dynamic linker can share this text segment among all running processes, referencing it at a given time. PIC helps reducing the number
of relocations.
Improved Security:
The
strip(1)utility is not enough to hide the names of the application's routines and data items; stripping eliminates the local symbols but not the global symbols.
Dynamically linked binaries (both executables and shared libraries) use two symbol tables: the static symbol table and the dynamic symbol table. The dynamic symbol table is used by the runtime linker. It has to be there even in stripped executables, or else
the dynamic linker can not find the symbols it need. The
striputility can only remove the static symbol table.
By making most of the symbols of the application local in scope, the symbol information for such local symbols in a stripped binary is really gone and are not available at runtime; so no one can extract it.
Note even though linker scoping is an easier mechanism to use, it is not the only one and the same could be done with mapfiles too.
Better alignment with the supported interface of the library:
By reducing scope of symbols, the linker symbols that the client can link to are aligned with the supported interface of the library; and the client cannot link to functions that are not supported and may do damage to the operation of the library.
Reduced application binary sizes:
ELF's exported symbol table format is quite a space hog. Due to the reduced linker scope, there will be a noticeable drop in the sizes of the binaries being built
Overcoming 64-bit PLT limit of 32768 (Solaris 8 or previous versions only):
In the 64-bit mode, the linker on Solaris 8 or previous versions currently has a limitation: It can only handle up to 32768 PLT entries. This means that we can't link very large shared libraries in the 64-bit mode. Linker throws the following error message
if the limit is exceeded:
Assertion failed: pltndx < 0x8000
The linker needs PLT entries only for the global symbols. If we use linker scoping to reduce the scope of most of the symbols to local, this limitation is likely to become irrelevant.
Substitution to the difficult to use/manage mapfiles mechanism:
The use of linker mapfiles for linker scoping is difficult with C++ because the mapfiles require linker names, which are not the same names used in the program source (explained in the introductory paragraphs). Linker scoping is a viable alternative to mapfiles
for reducing the scope of symbols. With linker scoping, the header files of the library need not change. The source files may be compiled with the -xldscope flag to indicate the default linker scoping, and individual symbols that wish another linker scoping
are specified in the source.
Note that linker mapfiles provide many features beyond linker scoping, including assigning addresses to symbols and internal library versioning.
Appendix
Linker scope specifiers (__global,
__symbolic,
and
__hidden) will have priority over the command-line
-xldscopeoption.
The following table shows the resulting binding and visibility when the code was compiled with the combination of specifiers and command line option:
Declaration Specifier | -xldscope=global | -xldscope=symbolic | -xldscope=hidden | no -xldscope |
---|---|---|---|---|
__global | GLOB DEFAULT | GLOB DEFAULT | GLOB DEFAULT | GLOB DEFAULT |
__symbolic | GLOB PROTECTED | GLOB PROTECTED | GLOB PROTECTED | GLOB PROTECTED |
__hidden | LOCL HIDDEN | LOCL HIDDEN | LOCL HIDDEN | LOCL HIDDEN |
no specifier | GLOB DEFAULT | GLOB PROTECTED | LOCL HIDDEN | GLOB DEFAULT |
-xldscope=hiddenand
its interface functions defined with
__globalor
__symbolic.
% cat external.h
extern void non_library_function();
inline void non_library_inline() {
non_library_function();
}
% cat public.h
extern void interposable();
extern void non_interposable();
struct container {
virtual void method();
void non_virtual();
};
% cat private.h
extern void inaccessible();
% cat library.c
#include "external.h"
#include "public.h"
#include "private.h"
__global void interposable() { }
__symbolic void non_interposable() { }
__symbolic void container::method() { }
__hidden void container::non_virtual() { }
void inaccessible() {
non_library_inline();
}
Compiling
library.cresults in the following linker scopings in library.o.
------------------------------------------------ function name linker scoping ------------------------------------------------ non_library_function undefined non_library_inline hidden interposable global non_interposable symbolic container::method symbolic container::non_virtual hidden inaccessible hidden ------------------------------------------------
The following example interface shows the usage of symbol visibility specifiers with a class template. With the
__symbolicspecifier
in the template class definition, all members of all instances of class
Stack, will have
symbolicscope,
unless overridden.
% cat stack.cpp
template <class Type>
class__symbolic Stack
{
private:
Type items[25];
int top;
public:
Stack();
Bool isempty();
Bool isfull();
Bool push(const Type & item);
Bool pop(Type & item);
};
In order to specify linker scoping to the template class definition, the Sun Studio 8 compilers on SPARC & x86 platforms must be patched with the latest patches of 113817-01 (or later) & 113819-01 (or later) respectively. This facility is available with Sun
Studio 9 or later versions, by default.
A trivial C++ example showing accidental symbol collision with a 3rd party symbol
% [code]cat mylib_public.h
float getlibversion();
int checklibversion();
%
cat mylib_private.h
#include "mylib_public.h"
const float libversion = 2.2;
%
cat mylib.cpp
#include "mylib_private.h"
float getlibversion() {
return (libversion);
}
int checklibversion() {
return ((getlibversion() < 2.0) ? 1 : 0);
}
%
CC -G -o libmylib.so mylib.cpp
%
cat thirdpartylib.h
const float libversion = 1.5;
float getlibversion();
%
cat thirdpartylib.cpp
#include "thirdpartylib.h"
float getlibversion() {
return (libversion);
}
%
CC -G -o libthirdparty.so thirdpartylib.cpp
%
cat versioncheck.cpp
#include <stdio.h>
#include "mylib_public.h"
int main() {
if (checklibversion()) {
printf("\n** Obsolete version being used .. Can\'t proceed further! **\n");
} else {
printf("\n** Met the library version requirement .. Good to Go! ** \n");
}
return (0);
}
%
CC -o vercheck -lthirdparty -lmylib versioncheck.cpp
%
./vercheck
** Obsolete version being used .. Can't proceed further! **[/code]
Since
checklibversion()and
getlibversion()are within the same load module,
checklibversion()of
myliblibrary
is expecting the
getlibversion()to be called from
myliblibrary. However
linker picked up the
getlibversion()from
thirdpartylibrary since it
was linked before
mylib, when the executable was built.
To avoid failures like this, it is suggested to bind the symbols to their definition within the module itself with symbolic scoping. Compiling
myliblibrary's source
with
-xldscope=symbolicmakes all the symbols of the module to be symbolic in nature. It produces the desired behavior and makes it hard for symbol collisions, by
ensuring that the library will use the local definition of the routine rather than a definition that occurs earlier in the link order:
% [code]CC -G -o libmylib.so-xldscope=symbolic mylib.cpp
%
CC -o vercheck -lthirdparty -lmylib versioncheck.cpp
%
./vercheck
** Met the library version requirement .. Good to Go! **[/code]
Estimating the number of relocations
To get the number of relocations that the linker may perform, run the following commands:
For the total number of relocations:
% elfdump -r <DynamicObject> | grep -v NONE | grep -c R_
For the number of non-symbolic relocations:
% elfdump -r <DynamicObject> | grep -c RELATIVE
For example
[code]% elfdump -r /usr/lib/libc.so | grep -v NONE | grep -c R_
2562
% elfdump -r /usr/lib/libc.so | grep -c RELATIVE
1868
[/code]
The number of symbolic relocations is calculated by subtracting the number of non-symbolic relocations from the total number of relocations. This number also includes the relocations in the procedure linkage table.
Footnoted Definitions
An interface (API) is a specification of functions and use of a software module. In short, it's a set of instructions for other programmers on what all classes, methods, etc., can be used from the module,
which provides the interface.
A source file contains one or more variables, function declarations, function definitions or similar items logically grouped together. From source file, compiler generates the object module, which is the
machine code of the target system. Object modules will be linked with other modules to create the load module, a program in machine language form, ready to run on the system.
static linker,
ld(1), also called link-editor, creates load modules from object modules.
dynamic linker,
ld.so.1(1)performs the runtime linking of dynamic executables and shared libraries. It brings
shared libraries into an executing application and handles the symbols in those libraries as well as in the dynamic executable images. ie., the dynamic linker creates a process image from load modules.
A symbolic relocation is a relocation that requires a lookup in the symbol table. The runtime linker optimizes symbol lookup by caching successive duplicate symbols. These cached relocations are called
"cached symbolic" relocations, and are faster than plain symbolic relocations. A non-symbolic relocationis a simple relative relocation that requires the base address at which the object is mapped to perform the relocation. Non-symbolic relocations
do not require a lookup in the symbol table.
elfdump(1) utility can be used to dump selected parts of an object file, like symbol table, elf header, global offset table.
nm(1) utility displays the symbol table of an ELF object file.
Copy relocations are a technique employed to allow references from non-pic code to external data items, while maintaining the read-only permission of a typical text segment. This relocations use, and overhead,
can be avoided by designing shared objects that do not export data interfaces.
Executable and Linkable Format (ELF) is a portable object file format supported by most UNIX vendors. ELF helps developers by providing a set of binary interface definitions that are cross-platform, and
by making it easier for tool vendors to port to multiple platforms. Having a standard object file format also makes porting of object-manipulating programs easier. Compilers, debuggers, and linkers are some examples of tools that use the ELF format.
Tentative symbols are those symbols that have been created within a file but have not yet been sized, or allocated in storage. These symbols appear as uninitialized C symbols.
The ability of a name in one translation unit to be used as the definition of the same name in another translation unit is called linkage. Linkage can be internal or external. Internal linkage means a
definition can only be used in the translation unit in which it is found. External linkage means the definition can be used in other translation units as well; in other words it can be linked into outside translation units.
Resources:
Sun Studio Interface Specification for Linker ScopingSun Studio C++ User's Guide, Language extensions
Solaris Linker and Libraries Guide
Programming Languages - C++, ISO/IEC 14882 International Standard
Enhancing Applications by Directing Linker Symbol Processing by Greg Nakhimovsky
Interface Creation Using Compilers by Rod Evans
Reducing Application Startup Time by Neelakanth Nadgir
Acknowledgments
Thanks to Lawrence Crowl and Steve Clamage of Sun Microsystems, for the extensive feedback on this article.
About The Author
Giri Mandalika is an engineering consultant at Sun Microsystems working with independent software vendors to make sure their products run well on Sun platform. He holds a Master'sdegree in Computer Science from The University of Texas at Dallas.
相关文章推荐
- 手机中点击一个链接或文本输入框出现一个半透明的背景的问题
- 分享一个链接,MS官方的解释:关于网络上流传的通过修改组策略“解除XP/WIN7系统默认限制20%的网速”来提高网速的问题。希望大家不要被误导。
- 关于编译器编译顺序的一个小问题...
- 一个路径的可能性选择的问题
- 搜索一个问题 C、C++判断操作系统 是 Linux还是windows 还是Unix【编译器内置宏 探索(不是特别满意)】
- 转:VC运行库版本不同导致链接.LIB静态库时发生重复定义问题的一个案例分析和总结
- 创建一个安卓工程时应用名称命名及SDK版本选择问题
- VS一个链接的问题与main函数问题
- 人生是一个连续的过程,没什么东西能影响人的一生,怎么选择不是问题。问题是每天都要努力
- VC运行库版本不同导致链接.LIB静态库时发生重复定义问题的一个案例分析和总结
- 解决Matlab 2012b中无法选择Visual Studio 2012作为编译器的问题
- 纠结了一晚上的问题---点击导航栏的一个文本链接后所有导航栏文本链接颜色都变成访问过的颜色了
- Windows下ICC编译器一个报错问题
- VC运行库版本不同导致链接.LIB静态库时发生重复定义问题的一个案例分析和总结
- ?C++编译链接时的一个小问题
- png库结合zlib库使用出现的一个链接问题的解决
- VC运行库版本不同导致链接.LIB静态库时发生重复定义问题的一个案例分析和总结
- 编写一个程序解决选择问题
- 人生是一个连续的过程,没什么东西能影响人的一生,怎么选择不是问题。问题是每天都要努力
- 宽字符std::wstring的长度和大小问题?sizeof(std::wstring)是固定的32,说明std::wstring是一个普通的C++类,而且和Delphi不一样,没有负方向,因为那个需要编译器的支持