Search code examples
c++g++dartdart-native-extension

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


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;
}

Solution

  • 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.