Loading Ada shared objects in Perl with DynaLoader

2019-04-05 03:00发布

问题:

Long time listener, first time caller. I'm aware this is a somewhat obscure question, and don't expect too much. :-)

I have the following Ada files:

greeter.ads

package Greeter is
    procedure Hello;
end Greeter;

greeter.adb

with Ada.Text_IO; use Ada.Text_IO;
package body Greeter is
    procedure Hello is
    begin
        Put_Line ("Hello, world!");
    end Hello;
end Greeter;

And compile them into a shared object like this:

gnatmake -z -fPIC greeter.adb
gcc -shared -o libgreeter.so greeter.o

This compiles fine. nm shows the following symbols:

$ nm -D libgreeter.so 
                 w _Jv_RegisterClasses
0000000000201028 A __bss_start
                 w __cxa_finalize
                 w __gmon_start__
                 U __gnat_eh_personality
0000000000201028 A _edata
0000000000201038 A _end
00000000000006a8 T _fini
0000000000000520 T _init
                 U ada__text_io__put_line__2
0000000000201018 D greeter_E
000000000000063c T greeter__hello

Now I try to load that shared object in Perl:

#!/usr/bin/env perl

use 5.014;
use strict;
use warnings;

#BEGIN { $ENV{PERL_DL_DEBUG} = 1 };

package Greeter
{
    use constant ADADIR => '/usr/lib/gcc/x86_64-linux-gnu/4.4/rts-native/adalib/';
    use constant OURDIR => do { (my $f = __FILE__) =~ s{[^/]+$}//; $f || "." };

    require DynaLoader;
    our @ISA = 'DynaLoader';

    my $runtime = DynaLoader::dl_load_file(
        ADADIR.'/libgnat.so',
    ) or die DynaLoader::dl_error();

    my $gep = DynaLoader::dl_find_symbol(
        $runtime,
        '__gnat_eh_personality',
    ) or die DynaLoader::dl_error();

    my $libref = DynaLoader::dl_load_file(
        OURDIR.'/libgreeter.so',
        0x01,
    ) or die DynaLoader::dl_error();

    my $func = DynaLoader::dl_find_symbol(
        $libref,
        'greeter__hello',
    ) or die DynaLoader::dl_error();

    print $func, $/;
}

But this bombs out with the following message:

./libgreeter.so: undefined symbol: __gnat_eh_personality at ./greeter.pl line 26.

Does anybody have any hints? Is there something better/easier than DynaLoader that I should be using??

I have a repository with all the relevant files here:

  • https://bitbucket.org/tobyink/ada-perl-mashup

回答1:

I can't help with the Perl side (you require 5.14, Mac OS X has 5.12, Debian 6 has 5.10). That said, I can help with building the library for a C main and direct linking ...

The GNAT build process is sufficiently complicated that there are two tools to support it, gnatmake and gprbuild. It’s likely (writing at September 2015) that gnatmake will lose the ability to build libraries, so gprbuild is the better option.

I think you need a stand-alone library project (that is, one with the initialization and finalization operations that control Ada elaboration; if you don't initialize the Ada library, you'll get SEGVs or other bad behaviours). You'll find the lowdown on building one here.

The greeter.gpr I wrote is

project Greeter is
   for Library_Name use "greeter";
   for Library_Kind use "relocatable";
   for Library_Dir use "lib";
   for Library_Interface use ("greeter");
   for Library_Auto_Init use "true"; -- the default, I think
   for Object_Dir use ".build"; -- to keep temp objects out of the way
end Greeter;

The Library_Name attribute controls the name of the library; libgreeter.dylib on Mac OS X, libgreeter.so on Linux.

The Library_Kind attribute could alternatively be "static", in which case the name would be libgreeter.a. However, stand-alone libraries must be relocatable.

The Library_Dir attribute, which you have to supply (with the two above) to make a library at all, controls where the library is created; in this case, in lib/.

You have to supply the Library_Interface attribute to make it a stand-alone library and generate the initialization and finalization operations that control Ada elaboration. They're called library_nameinit and library_namefinal - here, greeterinit, greeterfinal.

If Library_Auto_Init is "false" you have to call the initialization and finalization operations yourself, if "true", they're managed automagically.

OK, build the library by

gprbuild -p -P greeter

(-p says "create any needed output directories", -P specifies the project file).

I built greeter.c

#include <stdio.h>

extern void greeter_hello();

int main()
{
  greeter__hello();
  return 0;
}

using

$ gcc greeter.c -o greeter -L lib -l greeter

and run (on Linux) using

$ LD_LIBRARY_PATH=./lib ./greeter


回答2:

I'll do the best I can with this, given not much Perl knowledge.

It looks to me like the Dynaloader in perl is a utility that lets you load dynamicly loadable libraries (lib*.so's on Unix systems) into a perl program.

For that to work for an Ada program, there are several things you'll need to take into account.

  1. You need to build your Ada program as a proper dynamic library. It looks like you did that. However, I'm no expert on this, so perhaps you missed something. I'd highly suggest looking over TFM on this.
  2. You need to call your Ada code properly. Ada programs generally require a process called "elaboration" be performed before any actual code may be run. To accomplish this, most Ada compilers create a special entry-point to the program, rather than just using the one associated with your "main" routine. I think Gnat's is something like C_yourprogramname, but don't hold me to that. Even if you are implementing a library of some sort, the elaboration should be run first (execpt in some special circumstances, that do not apply here). However, if you want a routine to be a library routine called from outside of Ada, you generally don't want a "main", so there are some extra steps. How to do this with Gnat is described in their user guide, but in general it involves telling the compiler to not make a "main", calling adainit before running any Ada routine from outside, and calling adafinal when you are all done.