How do you link external shared libraries to a native extension?

1.4k Views Asked by At

I'm writing a pty native extension and want to link libutil so that I may use forkpty and openpty from <pty.h>.

I'm using the two commands taken from the official guide:

g++ -fPIC -lutil -I/home/crunchex/work/dart-sdk -c pty.cc -o pty.o
gcc -shared -Wl,-soname,libpty.so -o libpty.so pty.o

and I'm getting the following error:

/home/crunchex/work/dart-sdk/bin/dart: symbol lookup error: /home/crunchex/work/pty/bin/packages/pty/libpty.so: undefined symbol: forkpty

This may be more of a g++/gcc question, but as far as I can tell I'm doing that part right by adding -lutil and including <pty.h>. libutil.so is installed on my Ubuntu 14.04 system, so I'm fairly certain it's there.

Here's my test extension:

#include <string.h>
#include <pty.h>

#include "include/dart_api.h"

Dart_NativeFunction ResolveName(Dart_Handle name,
                                int argc,
                                bool* auto_setup_scope);

DART_EXPORT Dart_Handle pty_Init(Dart_Handle parent_library) {
  if (Dart_IsError(parent_library)) {
    return parent_library;
  }

  Dart_Handle result_code =
      Dart_SetNativeResolver(parent_library, ResolveName, NULL);
  if (Dart_IsError(result_code)) {
    return result_code;
  }

  return Dart_Null();
}

Dart_Handle HandleError(Dart_Handle handle) {
  if (Dart_IsError(handle)) {
    Dart_PropagateError(handle);
  }
  return handle;
}

//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
void PtyFork(Dart_NativeArguments args) {
  Dart_EnterScope();

  struct winsize winp;
  winp.ws_col = 80;
  winp.ws_row = 24;
  winp.ws_xpixel = 0;
  winp.ws_ypixel = 0;
  int master = -1;
  char name[40];
  pid_t pid = forkpty(&master, name, NULL, &winp);

  Dart_ExitScope();
}
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//
//\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\//

struct FunctionLookup {
  const char* name;
  Dart_NativeFunction function;
};

FunctionLookup function_list[] = {
  {"PtyFork", PtyFork},
  {NULL, NULL}};

Dart_NativeFunction ResolveName(Dart_Handle name, int argc, bool* auto_setup_scope) {
  if (!Dart_IsString(name)) return NULL;
  Dart_NativeFunction result = NULL;
  Dart_EnterScope();
  const char* cname;
  HandleError(Dart_StringToCString(name, &cname));

  for (int i=0; function_list[i].name != NULL; ++i) {
    if (strcmp(function_list[i].name, cname) == 0) {
      result = function_list[i].function;
      break;
    }
  }

  Dart_ExitScope();
  return result;
}
2

There are 2 best solutions below

0
On BEST ANSWER

Copied from https://code.google.com/p/dart/issues/detail?id=22257#c4

The problem is that libutil, part of libc6, needs to be linked into your native extension shared library on the link command line, not the compile command line.

First, the -lutil library specification should go on the linking line, rather than the compiling line: gcc -shared -Wl,-soname,libpty.so -o libpty.so pty.o -lutil

This puts a dependency on the shared library libutil.so into your shared library, and when it is loaded by dlload, the dependencies are also loaded and linked.

This fails unless the -lutil option is put after pty.o on your command
line, since linked libraries must be put in reverse dependency order on the linker command line.

After doing this, the output of objdump on libpty.so includes:

   objdump -x libpty.so 
Dynamic Section: 
   NEEDED               libutil.so.1 
   NEEDED               libc.so.6 
   SONAME               libpty.so 
   INIT                 0x00000000000009c0 
   FINI                 0x0000000000000db4 
   INIT_ARRAY           0x0000000000201dd0 
.... 
Version References: 
   required from libutil.so.1: 
     0x09691a75 0x00 04 GLIBC_2.2.5 
   required from libc.so.6: 
     0x09691a75 0x00 03 GLIBC_2.2.5 
     0x0d696914 0x00 02 GLIBC_2.4 
.... 
0000000000000000  w      *UND*        0000000000000000               
_ITM_registerTMCloneTable 
0000000000000000       F *UND*        0000000000000000               
forkpty@@GLIBC_2.2.5 
0000000000000000  w    F *UND*        0000000000000000               
__cxa_finalize@@GLIBC_2.2.5 
00000000000009c0 g     F .init        0000000000000000              _init 

and running the test program main.dart no longer fails.

If you don't want to link a shared library into your library, then you need a static library, but there are many problems with this - it is not impossible, but much harder. Then, the problem is that you may only have libutil.so on your system, not libutil.a, so your shared library will need to load libutil when it is loaded.

The dlopen function used by Dart to load your shared library should
recursively load other shared libraries it depends on, but this may or may not be working. When I compile with -lutil in the link step, the shared libraries shown by ldd libpty.so are just libc.so.6, and some standard linker ones ld-linux-.. and linux-vdso. So I don't see libutil there.

To link the functions you need statically into your shared library, you
would need something like

gcc -shared -Wl,-whole-archive /usr/lib/x86_64-linux-gnu/libutil.a   
-Wl,-no-whole-archive -Wl,-soname,libpty.so -o libpty.so pty.o 

But since the libutil.a in the distribution is not compiled with -wPIC, it can't be linked into a shared library:

/usr/bin/ld: /usr/lib/x86_64-linux-gnu/libutil.a(login.o): relocation   
R_X86_64_32 against `.rodata.str1.1' can not be used when making a shared object; recompile with -fPIC 
/usr/lib/x86_64-linux-gnu/libutil.a(login.o): error adding symbols: Bad   
value 

I think the best bet is to make the shared library dependency upon
libutil.so work.

0
On

If this is reported:

/home/crunchex/work/pty/bin/packages/pty/libpty.so: undefined symbol: forkpty

then the tool definitely needed that your libpty either provide this symbol, or request to be linked against another library that provides it. So this:

gcc -shared -Wl,-soname,libpty.so -o libpty.so pty.o

is definitely incomplete.

Normally when tools like that allow you to create your own library that may need to call some of the functions that the "user of your library" provides, then it at least requires to be linked against a "stub" library. There's usually provided a library named like libXXXXstubVV.a (VV may be a version number or something).

May happen that it has something to do with the problem that happens in static libraries. For static libraries this can't be solved any other way than putting the libraries in the correct order. For dynamic libraries it's usually solved by providing a request to be linked against some dependent library that is expected to provide lacking symbols - and this "stub" library should do it.