Saya mengalami masalah saat mem-porting program saya ke Linux karena Linux memiliki visibilitas simbol publik secara default. Saat ini, saya memiliki perpustakaan objek yang dapat dieksekusi dan .so, keduanya ditulis dalam Ada. Mereka berbagi beberapa file, seperti ini:

Umum/my_program_generic.ads

generic package My_Program_Generic is
    Initialized : Boolean := False;

    procedure Initialize;
end My_Program_Generic;

Umum/program_saya_generik.adb

with Ada.Text_IO;

package body My_Program_Generic is
    procedure Initialize is
    begin
        Ada.Text_IO.Put_Line("Initialized: " & Initialized'Img);
        if not Initialized then
            Initialized := True;
            -- Do stuff
            Ada.Text_IO.Put_Line("Did stuff!");
        end if;
    end Initialize;
end My_Program_Generic;

Umum/program_saya.iklan

with My_Program_Generic;
My_Program is new My_Program_Generic;

Baik executable maupun library kemudian memanggil My_Program.Initialize dari kode terpisah. Output (baris pertama & kedua dapat dieksekusi, ketiga adalah perpustakaan):

Initialized: FALSE
Did stuff!
Initialized: TRUE

Masalahnya di sini adalah bahwa visibilitas simbol bersifat publik, jadi tampaknya yang dapat dieksekusi menjalankan fungsi ini dan menginisialisasi semuanya, tetapi kemudian perpustakaan objek bersama menggunakan My_Program.Initialized yang dapat dieksekusi (yang Benar) alih-alih miliknya sendiri (yang Salah ), gagal diinisialisasi, kemudian crash menggunakan variabel yang tidak diinisialisasi.

Saya mencoba mengkompilasi dengan -fvisiblity=hidden untuk mengkompilasi semuanya (dari makefile dan file proyek gnat (.gpr)), yang tampaknya meneruskannya dengan benar ke kompiler (mis. ini ditampilkan di baris perintah gcc -c -fPIC -g -m32 -fvisibility=hidden -gnatA my_file.adb), tetapi tampaknya tidak ada bedanya, dan saya tidak dapat menemukan dokumentasi apa pun untuk mengontrol visibilitas menggunakan nyamuk.

OS saya CentOS 5.6. Saya tidak dapat memutakhirkan ke versi Linux yang lebih baru, tetapi saya dapat memutakhirkan versi GCC atau nyamuk saya ke apa pun yang berfungsi di CentOS 5.6. Versi GCC/gnat saya mengikuti:

$ gcc --version
gcc (GCC) 4.1.2 20080704 (Red Hat 4.1.2-50)
...
$ gnatls -v
GNATLS 4.1.2 20080704 (Red Hat 4.1.2-50)
...

Ya saya tahu itu mengatakan Red Hat, tapi saya menggunakan CentOS. AFAIK mereka sangat kompatibel satu sama lain.


Mungkin semua informasi yang diperlukan untuk menyelesaikan masalah saya dijelaskan sepenuhnya di atas, tetapi inilah sisa kode, makefile, dan file gpr yang dapat Anda gunakan untuk membuat ulang binari saya di mesin Anda (untuk ilustrasi yang lebih lengkap tetapi kurang minimal).

Perpustakaan/perpustakaan_saya.ads

package My_Library is
    procedure Initialize_Library;
    pragma Export (DLL, Initialize_Library, "Initialize_Library");
end My_Library;

Perpustakaan/perpustakaan_saya.adb

with Ada.Text_IO;
with My_Program;

package body My_Library is
    procedure Initialize_Library is
    begin
        Ada.Text_IO.Put_Line("Initializing Library...");
        My_Program.Initialize;
    end Initialize_Library;
end My_Library;

Perpustakaan/dummy.ads

package Dummy is
end Dummy;

Perpustakaan/my_library.gpr

project My_Library is
    for source_dirs use (".","../Common");
    for Library_Src_Dir use "include";
    for object_dir use "obj";
    for library_dir use "lib";
    for library_name use "my_library";
    for library_kind use "dynamic";
    for library_interface use ("dummy");
    for library_auto_init use "true;
    -- Compile 32-bit
    for library_options use ("-m32");
    package compiler is
        for default_switches ("Ada")
            use ("-g", "-m32", "-fvisibility=hidden");
    end compiler;

    for Source_Files use (
        "my_program_generic.ads",
        "my_program_generic.adb",
        "my_program.ads",
        "dummy.ads",
        "my_library.ads",
        "my_library.adb");
end My_Library;

Perpustakaan/Makefile

GNATMAKE=gnatmake
LDFLAGS=-shared
TARGETBASE=libMy_Library.so
GNATMAKEFLAGS=--RTS=/usr/lib/gcc/i386-redhat-linux/4.1.2
TARGET=Debug/$(TARGETBASE)

# Phony target so make all will work
.PHONY: all
all: $(TARGET)

SRCS = \
    ../Common/my_program_generic.ads \
    ../Common/my_program_generic.adb \
    ../Common/my_program.adb \
    dummy.ads \
    my_library.ads \
    my_library.adb

CHOPPATH = chop
OBJPATH = obj
LIBPATH = lib

$(TARGET) : $(SRCS)
    $(GNATMAKE) -Pmy_library $(GNATMAKEFLAGS)
    mv $(LIBPATH)/$(TARGETBASE) $(TARGET)

# Phony target so make clean will work
.PHONY: clean
clean:
    rm -rf $(TARGET) $(CHOPPATH)/*.ads $(CHOPPATH)/*.adb $(OBJPATH)/*.s $(OBJPATH)/*.o $(OBJPATH)/*.ads $(OBJPATH)/*.adb *.s $(LIBPATH)/*.so $(LIBPATH)/*.ali

Exe/my_exe.adb

with Ada.Text_IO;
with My_Program;
with My_Library_Import;

procedure My_Exe is
begin
    Ada.Text_IO.Put_Line("Begin main program.");
    My_Program.Initialize;
    My_Library_Import.Initialize_Library;
end My_Exe;

Exe/my_library_import.ads

package My_Library_Import is
    procedure Initialize_Library;
private
    type External_Initialize_Library_Type is access procedure;
    pragma Convention (DLL_Stdcall, External_Initialize_Library_Type);
    External_Initialize_Library : External_Initialize_Library_Type := null;
end My_Library_Import;

Exe/my_library_import.adb

with Ada.Text_IO;
with Ada.Unchecked_Conversion;
with System;
with Interfaces.C;
with Interfaces.C.Strings;
use type System.Address;

package body My_Library_Import is
    Library_Handle : System.Address := System.Null_Address;
    Library_Name : String := "../Library/Debug/libMy_Library.so";

    -- Interface to libdl to load dynamically linked libraries

    function dlopen(
        File_Name : in Interfaces.C.Strings.Chars_Ptr;
        Flag      : in Integer) return System.Address;
    pragma Import (C, dlopen);

    function dlsym(
        Handle : in System.Address;
        Symbol : in Interfaces.C.Char_Array) return System.Address;
    pragma Import (C, dlsym);

    function dlerror return Interfaces.C.Strings.Chars_Ptr;
    pragma Import (C, dlerror);

    function External_Initialize_Library_Type_Import is new Ada.Unchecked_Conversion(
        System.Address, External_Initialize_Library_Type);

    procedure Initialize_Library is
        Temp_Name : Interfaces.C.Strings.Chars_Ptr;
    begin
        -- Load Library
        Temp_Name := Interfaces.C.Strings.New_Char_Array(Interfaces.C.To_C(Library_Name));
        Library_Handle := dlopen(Temp_Name, 16#101#);  -- RTLD_NOW (0x0001), RTLD_GLOBAL (0x0100)
        Interfaces.C.Strings.Free(Temp_Name);

        -- Check for Load Library failure (did we execute from the right place?)
        if (Library_Handle = System.Null_Address) then
            Ada.Text_IO.Put_Line("dlerror: " &
                Interfaces.C.Strings.Value(dlerror));
            return;
        end if;

        -- Get function access
        External_Initialize_Library := External_Initialize_Library_Type_Import(
            dlsym(Library_Handle, Interfaces.C.To_C("Initialize_Library")));

        -- Initialize library itself
        External_Initialize_Library.all;
    end Initialize_Library;
end My_Library_Import;

Exe/Makefile

CC=gcc
LD=g++
GNATCHOP=gnatchop
GNATMAKE=gnatmake
RC=windres

INCLUDE_PATH = -I.

LDFLAGS=-largs -ldl -lpthread -rdynamic -lstdc++
TARGET_FILE=my_exe
GNATMAKEFLAGS=--RTS=/usr/lib/gcc/i386-redhat-linux/4.1.2
TARGET_PATH=Debug
TARGET=$(TARGET_PATH)/$(TARGET_FILE)

# Phony target so make all will work
.PHONY: all
all : $(TARGET)

SRCS = \
    ../Common/my_program_generic.ads \
    ../Common/my_program_generic.adb \
    ../Common/my_program.adb \
    my_exe.adb \
    my_library_import.ads \
    my_library_import.adb

CHOPPATH = chop
OBJPATH = obj

$(TARGET) : $(SRCS)
    $(GNATCHOP) $^ $(CHOPPATH) -w -r
    rm -rf *.s
    $(GNATMAKE) -m32 -j3 -g -gnatwA -fvisibility=hidden -D $(OBJPATH) -k $(CHOPPATH)/*.adb $(LDFLAGS) $(GNATMAKEFLAGS)
    rm -rf b~$(TARGET_FILE).*
    mv $(TARGET_FILE) $(TARGET)

# Phony target so make clean will work
.PHONY: clean
clean:
    rm -rf $(TARGET) $(CHOPPATH)/*.ads $(CHOPPATH)/*.adb $(OBJPATH)/*.s $(OBJPATH)/*.o $(OBJPATH)/*.ads $(OBJPATH)/*.adb *.s

Saya tidak menggunakan file gpr untuk executable (Exe).

Jalankan program dari folder "Exe" dengan ./Debug/my_exe, dan output lengkap dengan file tambahan adalah sebagai berikut:

$ ./Debug/my_exe
Begin main program.
Initialized: FALSE
Did Stuff!
Initializing Library...
Initialized: TRUE
0
Keith M 8 Desember 2016, 01:03

5 jawaban

Jawaban Terbaik

Kami menemukan solusi lain, yaitu mengubah tanda panggilan dlopen dari RTLD_GLOBAL menjadi RTLD_LOCAL:

Library_Handle := dlopen(Temp_Name, 16#101#);  -- RTLD_NOW (0x0001), RTLD_GLOBAL (0x0100)

Untuk

Library_Handle := dlopen(Temp_Name, 16#1#);  -- RTLD_NOW (0x0001), RTLD_LOCAL (0x0000)

Namun, dengan cara ini, Anda memerlukan jalur dan nama file lengkap setiap kali pustaka dimuat (atau meletakkan jalur di variabel lingkungan LD_LIBRARY_PATH Anda), dan NONE dari simbol yang diekspor akan segera terlihat, jadi Anda perlu memanggil dlsym untuk mengambil simbol apa pun yang perlu Anda gunakan, seperti pada baris dari pertanyaan:

-- Get function access
External_Initialize_Library := External_Initialize_Library_Type_Import(
    dlsym(Library_Handle, Interfaces.C.To_C("Initialize_Library")));

Dengan cara ini, Anda setidaknya dapat mengelola simbol apa yang "terlihat" -- dengan tidak ada satupun yang benar-benar terlihat, dan mengambil simbol yang Anda butuhkan secara eksplisit.

0
Keith M 12 Februari 2019, 20:26

Tidak ada apa pun dari "my_program_generic.ads" yang dapat berakhir di file objek, karena ini adalah paket generik, jadi saya rasa Anda tidak perlu khawatir.

-1
Jacob Sparre Andersen 8 Desember 2016, 20:37

Majikan saya akhirnya dapat meningkatkan ke komputer Linux yang baru, memutakhirkan GCC kami dari versi GCC 4.1.2 20080704 (Red Hat 4.1.2-50) ke 4.4.7 20120313 (Red Hat 4.4.7-18).

Setidaknya pada versi baru ini (4.4.7), masalahnya tidak separah di versi gnat/gcc yang lebih baru, seperti yang diprediksi oleh @BrianDrummond dalam komentar pertanyaan. Hanya simbol yang ada dalam spesifikasi paket yang dibuat publik/global, bahkan ketika dideklarasikan private. Simbol yang hanya muncul di badan paket dibuat privat/lokal.

Namun, masih belum ada cara untuk secara eksplisit menjadikan simbol pribadi/lokal.

Masalah baru ini masih ada di GCC 8.2, versi terbaru saat ini.

0
Keith M 8 Februari 2019, 18:11

Salah satu solusinya adalah menggunakan renames untuk mengubah simbol mana yang bersifat publik. Ambil file ini

Umum/program_saya.iklan

with My_Program_Generic;
My_Program is new My_Program_Generic;

Dan HANYA untuk perpustakaan, buat salinan baru, diedit seperti:

Perpustakaan/my_program_library_private.ads

 with My_Program_Generic;
 My_Program_Library_Private is new My_Program_Generic;

Tapi sekarang referensi kode Anda My_Program tidak akan dikompilasi, jadi buat file baru:

Pustaka/program_saya.iklan

with My_Program_Library_Private;
package My_Program renames My_Program_Library_Private;

Ini memungkinkan Anda untuk menyembunyikan simbol dari yang dapat dieksekusi sambil juga menghindari mengubah isi kode. Sekarang My_Program_Library_Private.Initialize adalah simbol publik, tetapi My_Program.Initialize tidak.

Sebelum:

$ nm -g Debug/libMy_Library.so
000010fc T Initialize_Library
...
00001438 T my_program__initialize

Setelah:

$ nm -g Debug/libMy_Library.so
0000112c T Initialize_Library
...
000011b0 T my_program_library_private__initialize

my_program__* bahkan tidak tercantum dalam simbol.

Dan outputnya sekarang:

$ ./Debug/my_exe
Begin main program.
Initialized: FALSE
Did Stuff!
Initializing Library...
Initialized: FALSE
Did Stuff!
0
Keith M 16 Desember 2016, 22:11

Saya tidak tahu apa yang Anda lakukan secara berbeda dari saya, karena Anda belum memberi tahu kami apa proses pembuatan Anda atau versi OS/kompiler apa yang Anda gunakan. Juga, saya tidak dapat mereproduksi hasil persis Anda, karena Anda belum menyediakan demonstran lengkap.

Saya yakin jawabannya terletak pada fitur tidak berdokumen (tetapi diinginkan) dari rilis terbaru gprbuild (saya menggunakan yang disediakan dengan GNAT GPL 2016, di macOS Sierra dan Debian jessie).

Saya menulis perpustakaan yang berisi instantiator,

with My_Program_Generic;
package Actual is new My_Program_Generic;

Salinan berbeda yang tentu saja juga dalam penutupan program utama, dan paket lain yang akan disertakan hanya di perpustakaan,

package In_Library with Elaborate_Body is
end In_Library;

with Actual;
with Ada.Text_IO;
package body In_Library is
begin
   Ada.Text_IO.Put_Line ("In_Library's elaboration");
   Actual.Initialize;
end In_Library;

Intinya adalah untuk menghindari mengungkapkan keberadaan Actual di perpustakaan, karena jika tidak, pasti akan ada dua versi dalam penutupan program utama.

Saya membangun perpustakaan dengan GPR mandiri ini ,

library project Build is
   for Library_Name use "keith";
   for Library_Kind use "dynamic";
   for Library_Dir use "lib";
   for Library_Src_Dir use "include";
   for Library_Interface use ("In_Library");
   for Object_Dir use ".build";
   for Source_Files use ("my_program_generic.ads",
                         "my_program_generic.adb",
                         "actual.ads",
                         "in_library.ads",
                         "in_library.adb");
end Build;

Dan (yang cukup baru) gprbuild mengakui bahwa Actual tidak ada dalam Library_Interface dan mengubah simbolnya, yang bersifat global, menjadi lokal! !!

Dengan "cukup baru" maksud saya tidak lebih awal dari yang dirilis dengan GNAT GPL 2016.

Anda bisa mendapatkan petunjuk tentang metode yang digunakan untuk mencapai ini dengan memeriksa $prefix/share/gprconfig/linker.xml untuk bagian yang berisi Object_Lister. Sebagai contoh,

<configuration>
  <targets>
    <target name="^i686.*-linux.*$" />
  </targets>
  <hosts>
    <host name="^i686.*-linux.*$" />
  </hosts>
  <config>
 for Object_Lister use ("nm", "-g");
 for Object_Lister_Matcher use " [TDRB] (.*)";

 package Linker is
    for Export_File_Format use "GNU";
    for Export_File_Switch use "-Wl,--version-script=";
 end Linker;
  </config>
</configuration>

Akan digunakan untuk beberapa Linux; sepertinya Anda menggunakan nm -g pada unit antarmuka yang dikompilasi dan menyalin simbol dari beberapa tipe global ke dalam file sementara dalam format GNU, yang diteruskan ke penghubung melalui sakelar --version-script=.

Varian macOS meneruskan simbol dalam format datar, menggunakan sakelar -exported_symbols_list.


Biasanya orang akan mengimpor perpustakaan menggunakan GPR dengan Externally_Built atribut,

library project Keith is
   for Library_Name use "keith";
   for Library_Kind use "dynamic";
   for Library_Dir use "lib";
   for Library_Src_Dir use "include";
   for Externally_Built use "true";
end Keith;

Tetapi gprbuild tetap sadar bahwa unit sumber yang sama ada di proyek perpustakaan dan proyek penggunaan dan menolak untuk membangun, meninggalkan saya untuk menautkannya

$ gnatmake -f \
  -aIlibrary/include -aOlibrary/lib \
  main.adb \
  -bargs -shared \
  -largs -Llibrary/lib -lkeith
2
Simon Wright 10 Desember 2016, 08:49