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