Friday, October 4, 2013

Solaris Export Symbols

As part of my solution to symbol conflicts I was getting a segfault while calling into a static in a shared library. I've simplified the problem, and found a solution. Basically using __declspec(dllexport) was a bad idea.

/usr/bin/CC -V
CC: Sun C++ 5.10 SunOS_sparc 128228-09 2010/06/24
Usage: CC [ options ] files.  Use 'CC -flags' for details
uname -a
SunOS yourbox 5.10 Generic_142909-17 sun4v sparc SUNW,SPARC-Enterprise-T5120
vi one.h
struct _impl { int widget; };
class DLLEXPORT One
{
public:
  One(int val);
  int work() const;
  struct _impl* m_pData;
};
struct DLLEXPORT Two
{
public:
  static const One obj;
};
vi one.cpp
#include "one.h"
One::One(int val) { m_pData = new _impl; }
int One::work() const { return m_pData->widget; }
const One Two::obj(5);
vi crash.cpp
#include "one.h"
#include <stdio.h>
int main()
{
  Two::obj.work();
  return 0;
}
/usr/bin/CC -g -xldscope=hidden -DDLLEXPORT="__declspec(dllexport)" -c one.cpp -o one.o
/usr/bin/CC -g -G -h libone.so one.o -o libone.so
/usr/bin/CC -g -DDLLEXPORT= -c crash.cpp -o crash.o
/usr/bin/CC -g -L. crash.o -lone -o crash.exe

The compile from .o to .exe gives the following warning, and the binary crashes at runtime.

ld: warning: relocation warning: R_SPARC_COPY: file ./libone.so: symbol Two::obj: relocation bound to a symbol with STV_PROTECTED visibility

# ./crash.exe
Segmentation Fault (core dumped)
# pstack core
core 'core' of 8273:    ./crash.exe
 ff3805a8 __1cDOneEwork6kM_i_ (211e0, ff320040, ee12c, ff346e8c, ff320000, 21184) + 10
 00010e0c main     (1, ffbffc64, ffbffc6c, 21000, ff350780, 0) + c
 000109d0 _start   (0, 0, 0, 0, 0, 0) + 108

Tweaking a few things shows that the problem is related to the shared library symbol restrictions for statics. You can use the One object if you create an instance of it in the binary, but you can't access the static from the shared library. Removing the symbol export restrictions also solves the problem. Using dllimport when compiling the executable has no effect.

rm *.o
rm *.so
/usr/bin/CC -g -DDLLEXPORT= -c one.cpp -o one.o
/usr/bin/CC -g -G -h libone.so one.o -o libone.so
/usr/bin/CC -g -DDLLEXPORT= -c crash.cpp -o crash.o
/usr/bin/CC -g -L. crash.o -lone -o crash.exe
./crash.exe
-- works fine
rm *.o
rm *.so
/usr/bin/CC -g -xldscope=hidden -DDLLEXPORT="__declspec(dllexport)" -c one.cpp -o one.o
/usr/bin/CC -g -G -h libone.so one.o -o libone.so
/usr/bin/CC -g -DDLLEXPORT="__declspec(dllimport)" -c crash.cpp -o crash.o
/usr/bin/CC -g -L. crash.o -lone -o crash.exe
./crash.exe
-- segfault

You would think that the link warning would lead us to the solution.

ld: warning: relocation warning: R_SPARC_COPY: file ./libone.so: symbol Two::obj: relocation bound to a symbol with STV_PROTECTED visibility

From homeunix.net

A symbol’s visibility, although it may be specifed in a relocatable object, defines how that symbol may be accessed once it has become part of an executable or shared object.

R_SPARC_COPY - The link-editor creates this relocation type for dynamic linking. Its offset member refers to a location in a writable segment. The symbol table index specifes a symbol that should exist both in the current object file and in a shared object. During execution, the runtime linker copies data associated with the shared object’s symbol to the location specifed by the offset.

STV_PROTECTED - A symbol defned in the current component is protected if it is visible in other components but not preemptable, meaning that any reference to such a symbol from within the defning component must be resolved to the defnition in that component, even if there is a defnition in another component that would interpose by the default rules. A symbol with STB_LOCAL binding will not have STV_PROTECTED visibility.

Here's another version of the demo app.

vi tool.h
#include <stdio.h>
class DLLEXPORT Tool
{
public:
  Tool();
  int work() const;
  int* ptr;
};
class DLLEXPORT Helper
{
public:
  static const Tool tool;
};
vi tool.cpp
#include "tool.h"
Tool::Tool() { ptr = new int; printf("constructed\n"); }
int Tool::work() const { printf("about to crash\n"); return *ptr; printf("crashed\n"); }
const Tool Helper::tool;
vi crash.cpp
#include "tool.h"
int main()
{
  printf("calling static\n");
  Helper::tool.work();
  return 0;
}
/usr/bin/CC -g -xldscope=hidden -DDLLEXPORT="__declspec(dllexport)" -c tool.cpp -o tool.o
/usr/bin/CC -g -G -h libtool.so tool.o -o libtool.so
/usr/bin/CC -g -DDLLEXPORT= -c crash.cpp -o crash.o
/usr/bin/CC -g -L. crash.o -ltool -o crash.exe
export LD_LIBRARY_PATH=.
./crash.exe
constructed
calling static
about to crash
Segmentation Fault (core dumped)

The above indicates that the *ptr de-reference causes the crash. The following from oracle.com led me to believe that the problem was the application of the scope specifier to the class and not the variable, but I started playing with the alternative speciviers and found that -DDLLEXPORT="__global" instead of -DDLLEXPORT="__declspec(dllexport)" solved the problem

The dynamic relocations that the dynamic linker performs are only necessary for the global (sometimes referred to as external or exported) symbols. The static linker resolves references to local symbols (for example, names of static functions) statically when it links the binary.

One way of reducing the relocations is to have fewer symbols visible outside the application or library. This can be done by declaring locally used functions and global data private to the application/library. Using static keyword as a function type in C/C++ programs, makes the function local to the module and the symbol will not appear in the dynamic symbol table.

There is a new compiler flag: -xldscope={ global | symbolic| hidden}

Symbols with explicit linker scope qualifiers, declarations of external symbols, static symbols, and local symbols are not affected by the -xldscope option.

The __global specifier can be used to make the symbol definition global in linker scope. This is the default scoping for extern symbols. With global scope, all references to the symbol bind to the definition in the first dynamic load module (shared library) that defines the symbol.

Symbolic scoping ensures that the library uses its own versions of specific functions, no matter what might appear elsewhere in the program. At first the link-editor tries to find the definition of the symbol within the library. If found, the symbol will be bound to the definition during link time; otherwise the search continues outside the library as the case with global symbols.

Hidden linker scoping is the most restrictive scope of all. All references within a dynamic load module bind to a definition within that module and the symbol will not be visible outside of the module.

Linker scoping specifiers are applicable to struct, class, and union declarations and definitions.

Consider the following example: __hidden struct __symbolic BinaryTree node;

The declaration specifier before the struct keyword applies to variable node. The class key modifier after the struct keyword applies to the type BinaryTree.

{ "loggedin": false, "owner": false, "avatar": "", "render": "nothing", "trackingID": "UA-36983794-1", "description": "", "page": { "blogIds": [ 455 ] }, "domain": "holtstrom.com", "base": "\/michael", "url": "https:\/\/holtstrom.com\/michael\/", "frameworkFiles": "https:\/\/holtstrom.com\/michael\/_framework\/_files.4\/", "commonFiles": "https:\/\/holtstrom.com\/michael\/_common\/_files.3\/", "mediaFiles": "https:\/\/holtstrom.com\/michael\/media\/_files.3\/", "tmdbUrl": "http:\/\/www.themoviedb.org\/", "tmdbPoster": "http:\/\/image.tmdb.org\/t\/p\/w342" }