ada

Build static library from Ada code to be linked without GNAT


I'm trying to create a static library from Ada code that can be linked with some C code without using GNAT tools for the final linking. My use case is that I'm trying to deliver a library written in Ada towards a codebase in C that will be built for an embedded target. The toolchain to build the final binary for the target does not contain GNAT tools and hence the requirement to be able to link the library without GNAT.

When I try to link the library, I get a lot of errors about undefined references.

Here is a minimal code example. C_Ada_Cross.gpr file:

project C_Ada_cross is
   for Library_Name use "MathFunc";
   for Source_Dirs use ("src", ".");
   for Object_Dir use "obj";
   for Library_Kind use "static";
   for Library_Interface use ("MathFunc_Ada");
   for Library_Src_Dir use "include";
   for Library_Dir use "lib";
end C_Ada_cross;

MathFunc_Ada.ads file:

with Interfaces.C; use Interfaces.C;

function MathFunc_Ada(a: C_Float; b: C_Float) return C_Float
    with 
        Export      => True,
        Convention  => C,
        External_Name => "MathFunc_Ada";

MathFunc_Ada.adb file:

with Ada.Numerics.Elementary_Functions; 
function MathFunc_Ada(a: C_Float; b: C_Float) return C_Float is
    use Ada.Numerics.Elementary_Functions;
begin
    return C_Float(sin(Float(a)) + cos(Float(b)));
end MathFunc_Ada;

main.c file:

#include <stdio.h>

extern float MathFunc_Ada(float a, float b);
extern void MathFuncinit();
extern void MathFuncfinal();

void main() {
  MathFuncinit();

  float a = 10.2;
  float b = 20.6;
  float c = MathFunc_Ada(a, b);
  
  printf("%f\n", c);
  MathFuncfinal();
}

To build, I did the following:

gprbuild -P C_Ada_cross.gpr # libMathFunc.a 
gcc main.c -L./lib -lMathFunc

And this generates loads of errors like so:

/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libMathFunc.a(p__MathFunc_0.o):MathFunc_Ada.a:(.text+0x20): undefined reference to `ada__numerics__elementary_functions__sin'
./lib/libMathFunc.a(p__MathFunc_0.o):MathFunc_Ada.a:(.text+0x20): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `ada__numerics__elementary_functions__sin'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libMathFunc.a(p__MathFunc_0.o):MathFunc_Ada.a:(.text+0x2f): undefined reference to `ada__numerics__elementary_functions__cos'
./lib/libMathFunc.a(p__MathFunc_0.o):MathFunc_Ada.a:(.text+0x2f): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `ada__numerics__elementary_functions__cos'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libMathFunc.a(p__MathFunc_0.o):b__mathfunc.ad:(.text+0x118): undefined reference to `system__secondary_stack__ss_stackIP'
./lib/libMathFunc.a(p__MathFunc_0.o):b__mathfunc.ad:(.text+0x118): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `system__secondary_stack__ss_stackIP'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libMathFunc.a(p__MathFunc_0.o):b__mathfunc.ad:(.text+0x14e): undefined reference to `__gnat_runtime_finalize'
./lib/libMathFunc.a(p__MathFunc_0.o):b__mathfunc.ad:(.text+0x14e): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__gnat_runtime_finalize'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libMathFunc.a(p__MathFunc_0.o):b__mathfunc.ad:(.text+0x265): undefined reference to `__gnat_runtime_initialize'
./lib/libMathFunc.a(p__MathFunc_0.o):b__mathfunc.ad:(.text+0x265): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__gnat_runtime_initialize'

and more...

I also tried:

gcc main.c -L./lib -lMathFunc -lgnat -lgnarl -ldl

and that gives different undefined reference errors:

/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: /usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: DWARF error: can't find .debug_ranges section.
./lib/libgnat.a(adaint.o):adaint.c:(.text+0x38): undefined reference to `__imp__wsplitpath'
./lib/libgnat.a(adaint.o):adaint.c:(.text+0x38): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__imp__wsplitpath'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libgnat.a(adaint.o):adaint.c:(.text+0x480): undefined reference to `__mingw_vsprintf'
./lib/libgnat.a(adaint.o):adaint.c:(.text+0x480): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__mingw_vsprintf'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libgnat.a(adaint.o):adaint.c:(.text+0x4d5): undefined reference to `__imp__time64'
./lib/libgnat.a(adaint.o):adaint.c:(.text+0x4d5): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__imp__time64'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libgnat.a(adaint.o):adaint.c:(.text+0x4ec): undefined reference to `__imp__time64'
./lib/libgnat.a(adaint.o):adaint.c:(.text+0x4ec): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__imp__time64'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libgnat.a(adaint.o):adaint.c:(.text+0x4fc): undefined reference to `__imp__localtime64'
./lib/libgnat.a(adaint.o):adaint.c:(.text+0x4fc): relocation truncated to fit: R_X86_64_PC32 against undefined symbol `__imp__localtime64'
/usr/lib/gcc/x86_64-pc-cygwin/9.3.0/../../../../x86_64-pc-cygwin/bin/ld: ./lib/libgnat.a(adaint.o):adaint.c:(.text+0x54f): undefined reference to `__imp__gmtime64'

and more...

The last approach is very similar to the one presented here: https://stackoverflow.com/a/70036096/3979564, but that didn't work for me. In fact, I even tried the exact same code and commands presented there, but that gave me similar errors.

I found this option when searching for solutions: http://www.white-elephant.ch/articles/AUJ%2042-3.pdf, but that seems very convoluted to make a copy of and rewrite original Ada source files.

Given Ada's focus on embedded applications where static linking is the only option in many cases, I feel like there must be a simpler way to build static libraries.

Am I missing something? Is there an easier way to build static libraries in Ada?


Solution

  • Your target CPUs are, I think, all supported by the Alire gnat_arm_elf compiler: -mcpu=cortex-r5, cortex-a72; the Infineon CPUs (even the ones that cost >£10k!) are all ARM Cortex.

    Your customer no doubt provides runtime support for C programs, which will have its limitations; for example, does it provide printf? how do you do logging?

    What are your customer's requirements with regard to certification? (well, certifiability). If they're severe, you might need an AdaCore support contract.

    You quite likely won't want to raise exceptions; exceptions would need Ada runtime support. On the other hand, what to do if someone tries to e.g. divide by zero? You can compile with -gnatp to suppress all checks (actually, I'm not sure whether e.g. an out-of-bounds array access check would get suppressed).

    I tried starting with the light-cortex-m4 runtime, after using alr toolchain --select to install the gnat_arm_elf compiler.

    A first stab at mathfunc.gpr (Alire insisted on lower-case) might be

    project mathfunc is  -- alr insists on lower-case
    
       for Library_Name use "MathFunc";
       for Source_Dirs use ("src/");
       for Object_Dir use "obj";
       for Create_Missing_Dirs use "True";
       for Library_Interface use ("MathFunc_Ada");
       for Library_Src_Dir use "include";
       for Library_Dir use "lib";
    
       for Target use "arm-eabi";
       for Runtime ("ada") use "light-cortex-m4";
    
       package Compiler is
          for Default_Switches ("Ada") use ("-O2", "-gnatp", "-g");
       end Compiler;
    
       package Binder is
          for Switches ("Ada") use ("-n"); -- C main program
       end Binder;
    
    end mathfunc;
    

    (I'm not sure that we need the Binder switch -n).

    Building with this, the symbols in lib/libMathFunc.a are

    $ arm-eabi-nm lib/libMathFunc.a
    
    mathfunc_ada.o:
    00000000 T MathFunc_Ada
             U __aeabi_fadd
             U ada__numerics__elementary_functions__cos
             U ada__numerics__elementary_functions__sin
    00000000 D mathfunc_ada_E
    
    b__mathfunc.o:
    0000002c T MathFuncinit
             U __gnat_binder_ss_count
             U __gnat_default_ss_pool
             U __gnat_default_ss_size
    00000014 R mathfunc_adaB
    00000010 R mathfunc_adaS
             U mathfunc_ada_E
    00000000 D mathfuncmain_E
    00000000 T mathfuncmain__Tsec_default_sized_stacksBIP
    00000068 T mathfuncmain___elabb
    00000000 b mathfuncmain__sec_default_sized_stacks
    

    so you can see that the runtime requires __aeabi_fadd (possibly from GCC support rather than Ada) and a lot of stuff in b__mathfunc.o, which is binder-generated in support of the 'standalone library' support we requested by including Library_Interface in the GPR. Part of this is to do with elaboration; you might be able to get away without any elaboration at all, but at some cost in supportable Ada features.

    Building main, in subdirectory main, using this alire.toml

    name = "main"
    
    (omitted)
    
    [[depends-on]]
    mathfunc = "*"
    
    [[pins]]
    mathfunc = { path='..' }
    

    and this GPR:

    with "mathfunc";
    project Main is
    
       for Languages use ("c");
       for Main use ("main.c");
       for Exec_Dir use ".";
       for Source_Dirs use (".");
       for Object_Dir use "obj";
       for Create_Missing_Dirs use "true";
    
       for Target use "arm-eabi";
       for Runtime ("ada") use "light-cortex-m4";
    
    end Main;
    

    the build fails because of undefined references to printf and MathFuncfinal; the latter wasn't created, but assuming you weren't intending to unload your static library this hardly matters.

    Commenting the calls out, we get

    $ alr build
    ⓘ Building main=0.1.0-dev/main.gpr...
    Compile
       [c]            main.c
    Link
       [archive]      libmain.a
       [index]        libmain.a
       [link]         main.c
    /Users/simon/.cache/alire/dependencies/gnat_arm_elf_12.2.1_f4bfd822/bin/../lib/gcc/arm-eabi/12.2.0/../../../../arm-eabi/bin/ld: warning: cannot find entry symbol _start; defaulting to 0000000000008000
    Build finished successfully in 0.75 seconds.
    

    where the missing _start would be a matter between you and your customer (there must be a way for C programs to start!).

    Sizes:

    $ arm-eabi-size main
       text    data     bss     dec     hex filename
      18024      42     674   18740    4934 main
    

    That still looks quite large to me, more investigation required.


    On my machine (a Mac), that runtime is in

    $HOME/.cache/alire/dependencies/gnat_arm_elf_12.2.1_f4bfd822/arm-eabi/lib/gnat/light-cortex-m4
    

    I'm not sure you need ada_target_properties.

    The Ada source is in gnat/.

    runtime.xml specifies the compiler & linker options used to process your code (also used when rebuilding the library).

    The two GPRs are used for building the runtime.


    Hope that helps a bit! I realise it's a lot to digest.