2016-12-07 20 views
-1

У меня возникли проблемы с переносом моей программы на Linux, поскольку Linux по умолчанию имеет общедоступный символ. В настоящее время у меня есть исполняемый файл и общая библиотека объектов .so, написанная в Ada. Они разделяют некоторые файлы, например таких:Как изменить видимость символов библиотек Ada с помощью gnat?

Общие/my_program_generic.ads

generic package My_Program_Generic is 
    Initialized : Boolean := False; 

    procedure Initialize; 
end My_Program_Generic; 

Общие/my_program_generic.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; 

Общие/my_program.ads

with My_Program_Generic; 
My_Program is new My_Program_Generic; 

Оба исполняемый файл и библиотека затем вызывают My_Program.Initialize из отдельного кода. выхода (первый & второй линия является исполняемой, третьим является библиотека):

Initialized: FALSE 
Did stuff! 
Initialized: TRUE 

Проблема здесь состоит в том, что видимость символа является открытой, так что кажется, работает исполняемое эта функция и инициализирует все, но затем общий объект библиотека использует исполняемый файл My_Program.Initialized (который является True), а не его собственный (который является False), не инициализируется, а затем сбой происходит с помощью неинициализированной переменной.

Я попытался компиляции с -fvisiblity=hidden для компиляции все (как из Makefile и файл комар проекта (.gpr)), который, кажется, правильно передавая его компилятором (например, он показывает в командной строке gcc -c -fPIC -g -m32 -fvisibility=hidden -gnatA my_file.adb), но он, похоже, не изменил ситуацию, и я не могу найти никакой документации для контроля видимости с помощью gnat.

Моей ОС является CentOS 5.6. Я не могу перейти на более новую версию Linux, но я мог бы обновить версии GCC или gnat до всего, что работает на CentOS 5.6. Мой GCC/комар версия следует:

$ 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) 
... 

Да, я знаю, что это говорит Red Hat, но я использую CentOS. AFAIK они абсолютно совместимы друг с другом в любом случае.


Возможно всю информацию, необходимую, чтобы решить мою проблему полностью пояснялось выше, но вот остальная часть кода, Makefiles и файлы GPR вы могли бы использовать, чтобы восстановить свои исполняемые файлы на вашем компьютере (для более полной, но менее минимальная иллюстрация).

Библиотека/my_library.ads

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

Библиотека/my_library.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; 

Библиотека/dummy.ads

package Dummy is 
end Dummy; 

Библиотека/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; 

Library/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 

Я не использую файл GPR для исполняемого файла (Exe).

Выполнить программу из папки «Exe» с ./Debug/my_exe, и полный выход с дополнительными файлами заключается в следующем:

$ ./Debug/my_exe 
Begin main program. 
Initialized: FALSE 
Did Stuff! 
Initializing Library... 
Initialized: TRUE 
+0

Почему не 'Initialized' в частной части пакета, или даже в теле пакета? хотя мне может что-то не хватает в вопросе ... –

+0

@BrianDrummond Перемещение булевых в тело пакета и перекомпиляция обоих двоичных файлов, похоже, ничего не изменили. Есть ли что-нибудь о вопросе, который я могу прояснить? –

+0

Что вы подразумеваете под '_Generic'? Это в смысле Ады, или вы просто имеете в виду «какой-то случайный, но типичный пример»? –

ответ

-1

Ничто из «my_program_generic.ads» не может закончиться в объектный файл, поскольку это общий пакет, поэтому я не думаю, что вам есть о чем беспокоиться.

+0

Очевидно, что мне есть о чем беспокоиться, иначе у меня не было бы результата, показанного в вопросе. Если это неясно, исполняемый файл и библиотека оба создают экземпляр общего пакета. –

+0

Демонстрационный код в вашем вопросе не содержит каких-либо общих установок. Пожалуйста, покажите нам, что на самом деле ** происходит неправильно. –

+0

Потому что это действительно так просто, как 'My_Program новый My_Program_Generic;'. Я не думаю, что добавление таких тривиальных фрагментов кода полезно, но я добавил его в любом случае, так как вы просите об этом. Я также обновлю вопрос с процессом сборки, когда у меня появится шанс, поскольку все, кажется, думают, что это важно, хотя я не понимаю, почему это было бы. –

2

Я не знаю, что вы делаете по-другому от меня, потому что вы не сказали нам, что такое процесс сборки или какие версии OS/компилятора вы используете. Кроме того, я не могу воспроизвести ваш точный результат, потому что вы не предоставили полного демонстратора.

Я считаю, что ответ лежит в незарегистрированной (но желательно) особенности последних релизов gprbuild (я использовал один снабженное GNAT GPL 2016 год, как на MacOS Sierra и Debian Jessie).

Я написал библиотеку, содержащую cоздателем экземпляров,

with My_Program_Generic; 
package Actual is new My_Program_Generic; 

другая копия, которая, конечно, также в закрытии главной программы, а другой пакет должны быть включены только в библиотеке,

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; 

Суть этого заключается в том, чтобы не выявлять наличие в библиотеке Actual, потому что иначе в закрытии основной программы, безусловно, должно быть две версии.

Я построил библиотеку с этим standalone GPR,

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; 

и (достаточно свежая) gprbuild признает, что Actual не в Library_Interfaceи преобразует его символы, которые были глобальными, местный !!!

К «недавнее достаточно» Я не имею в виду не ранее, чем один выпущенный с GNAT GPL 2016.

Вы можете получить подсказку метод, используемый для достижения этой цели путем изучения $prefix/share/gprconfig/linker.xml для раздела, содержащего Object_Lister. Например,

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

будет использоваться для некоторых Linux; похоже, что вы используете nm -g на скомпилированных интерфейсных модулях и копируете символы некоторых глобальных типов во временный файл в формате GNU, который передается компоновщику через переключатель --version-script=.

Вариант macOS передает символы в плоском формате, используя переключатель -exported_symbols_list.


Обычно можно было бы импортировать библиотеку с помощью георадара с Externally_Built attribute,

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; 

но gprbuild оставался в курсе, что одни и те же исходные единицы были в проекте библиотеки и используя проект и отказался строить, оставив меня связать с

$ gnatmake -f \ 
    -aIlibrary/include -aOlibrary/lib \ 
    main.adb \ 
    -bargs -shared \ 
    -largs -Llibrary/lib -lkeith 
+0

Моя версия 'Actual' не находится в' Library_Interface', но ее символ по-прежнему глобальный. Это все еще на Mac, как вы сказали в комментариях к вопросу? Я обновлю вопрос с процессом сборки, когда у меня появится шанс, так как все, кажется, думают, что это важно, хотя я не понимаю, почему это было бы. –

+0

Я сказал, что я пробовал тот же источник/сборку на macOS и на Debian jessie с теми же результатами, но потерял его в редактировании. Назад снова. Символы в actual.o являются глобальными, но символы в libkeith.so (.dylib на Mac) являются локальными !!! –

+0

'Совсем недавно я имею в виду не раньше, чем тот, который был выпущен с GNAT GPL 2016.' Вы хотите сказать, что вы (а) попробовали это с более ранними версиями, а символы оставались общедоступными или что вы (б) t все же пробовал более ранние версии, но что-нибудь более новое, чем 2016, должно работать? –

0

Одно из решений заключается в использовании renames -с обозначать, какие символы являются общедоступными. Возьмите этот файл

Общие/my_program.ads

with My_Program_Generic; 
My_Program is new My_Program_Generic; 

и просто для библиотеки, создать новую копию, отредактированные как так:

Библиотека/my_program_library_private.ads

with My_Program_Generic; 
My_Program_Library_Private is new My_Program_Generic; 

Но теперь ваш код, ссылающийся на My_Program, не будет компилироваться, таким образом, создать новый файл:

Библиотеки/my_program.ads

with My_Program_Library_Private; 
package My_Program renames My_Program_Library_Private; 

Это позволяет скрыть символ из исполняемого файла, а также избежать изменений начинки коды. Теперь My_Program_Library_Private.Initialize является общедоступным символом, но My_Program.Initialize нет.

До:

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

После:

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

my_program__* даже нет в списке символов.

И выход теперь:

$ ./Debug/my_exe 
Begin main program. 
Initialized: FALSE 
Did Stuff! 
Initializing Library... 
Initialized: FALSE 
Did Stuff! 
+0

Это решение работает для меня, но я бы предпочел другое решение. Переименование пакетов и создание новых файлов для их переименования было бы утомительным и трудоемким для моей не-MCVE фактически-относительно большой программы. –