4

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?

Adi
  • 77
  • 4
  • 1
    Can you tell us a bit more about the target? for example, is it restricted (small memory)? what OS (RTOS)? is there an Ada implementation for it? (even though you can’t use it directly, you might be able to use some components). – Simon Wright Jul 18 '23 at 21:45
  • The target for us is an ARM R5 in the current project (but later on we may have A72, Infineon, etc). It is an automotive ECU, so (unfortunately) classic autosar and the real time environment and platform is delivered by Vector (I think). There is no Ada implementation for it as far as I know. – Adi Jul 19 '23 at 06:43
  • The memory is limited, but more than the HW itself, the memory is limited because we are one of a dozen teams that will deliver application SW to this ECU. So using libgnat.a will not work in the end (the binary is 1.2mb for this example code), but one step at a time... It would be ideal if I can make this work without using a runtime, but if not I saw some promising looking documentation about runtime options here: https://docs.adacore.com/gnat_ugx-docs/html/gnat_ugx/gnat_ugx/gnat_runtimes.html that I need to look into – Adi Jul 19 '23 at 06:48
  • Are you really only supplying a single function? If so, I would think you could skip creating libMathFunc.a and simply link the object file for the function into your executable. This would eliminate the MathFuncinit and MathFuncfinal calls as well. – Jeffrey R. Carter Jul 19 '23 at 08:02
  • You should probably use `[Interfaces.C.]C_float` for your parameter and return types. If you continue to use `[Standard.]Float`, you can eliminate `with Interfaces.C;`. You can also move `with Ada.Numerics.Elementary_Functions;` to the function body. – Jeffrey R. Carter Jul 19 '23 at 08:06
  • No, this is a minimal working example. The actual code will implement some signal processing functions and use the `Ada.Numerics.Generic_Real_Arrays` package as well. I've updated my code with your recommendations. – Adi Jul 19 '23 at 09:05

3 Answers3

3

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.

Simon Wright
  • 25,108
  • 2
  • 35
  • 62
  • Wow! Thanks a lot for the detailed help! – Adi Jul 19 '23 at 12:16
  • While trying out what you suggested I noticed that gprbuild sets `-mpcu=cortex-m4` when calling gcc. I tried adding `for Switches ("Ada") use ("-mcpu=cortex-r5");` and `for Switches ("C") use ("-mcpu=cortex-r5");` (I'm not even sure if it is correct to set this while also using the `light-cortex-m4` runtime) but that resulted in gprbuild sending both `-mpcu=cortex-m4` _and_ `-mcpu=cortex-r5` switches to gcc and the build failing. So given there is no `cortex-r5` runtimes available, does it mean it is not possible to build for cortex-r5 with the FSF toolchain? – Adi Jul 19 '23 at 12:32
  • Ah... never mind. I understand now that the answer to my question is in your comments towards the end of your answer. – Adi Jul 19 '23 at 12:41
  • 1
    Change `runtime.xml` to select `cpu=cortex-r5`, then (with the gnat_arm_elf compiler on your PATH) `gprbuild -P runtime_build.gpr --RTS=$PWD` - will fail because of references to register `mrs` - move `s-bbarat.ad?` out of the way, it’s for atomic ops on Armv6m (!!!) – Simon Wright Jul 19 '23 at 13:47
1

I took your example and tossed it into a project to replicate your issue. I also got the same undefined symbols as you. My next step was to change your call to gcc to:

gcc main.c -L./lib -L<path_to_libgnat.a> -lMathFunc -lgnat

NOTE: The order of the parameters matters for me.

And that resolved all of the undefined symbols for me. Now the thing here is I don't have Cygwin. I'm using msys2. Since you have to those additional unknown symbols, I think you need to see if you can find out which library those reside in and manually link it too. Your gcc line will become something like:

gcc main.c -L./lib -L<path_to_libgnat.a> -L<path_to_other_lib> -lMathFunc -lgnat -l<other_lib>

Sorry I don't have more than that, but I think it is just a matter of figuring out which lib file to link it and it appears to be cygwin specific, which I cannot replicate.

Since I saw instant success in msys2 adding the gnat library, I mostly posted this so you at least know you are on the right track.

Jere
  • 3,124
  • 7
  • 15
  • Thanks for the help! You were spot-on about cygwin, that was the issue. When I switched to Alire and used that environment to build, it worked fine. That said, the binary is 1.2mb for this simple code. I need to figure out how to reduce that to fit on an embedded target... – Adi Jul 19 '23 at 06:52
-1

The easiest way, I think would be: Grab a copy of GNAT Programming Studio, create a new "library project", set the option for Externially_Built to true and (IIRC) Library_Kind to Static.

I know it's possible to build a statically linked executable, I've done it before, but I've forgotten where that option is (it may be one you have to feed into the 'Parameters' section for the Compiler/Linker/Binder/Whatever). But in any case, GNAT Programming Studio has a Library template that you can just set to "Static" and get what you want.

Shark8
  • 4,095
  • 1
  • 17
  • 31
  • Adi has already done what you suggest in the first paragraph. They now need to do the next step **without using GNAT tools**. – Simon Wright Jul 18 '23 at 21:39