我在将程序移植到Linux时遇到问题,因为默认情况下Linux具有公共符号可见性。目前,我有一个可执行文件和一个.so共享对象库,都是用Ada编写的。他们共享一些文件,例如:
普通/ 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)代替它自己的(它是假的),无法初始化,然后使用未初始化的变量崩溃。
我尝试使用-fvisiblity=hidden
进行编译来编译所有内容(来自makefile和gnat项目文件(.gpr)),这似乎正确地将其传递给编译器(例如,它显示在命令行{ {1}}),但它似乎没有什么区别,我找不到任何使用gnat控制可见性的文档。
我的操作系统是CentOS 5.6。我无法升级到更新版本的Linux,但我可以将我的GCC或gnat版本升级到适用于CentOS 5.6的任何版本。我的GCC / gnat版本如下:
gcc -c -fPIC -g -m32 -fvisibility=hidden -gnatA my_file.adb
是的我知道它说Red Hat,但我使用的是CentOS。 AFAIK无论如何都能完美地相互兼容。
上面已经完全解释了解决我的问题所需的所有信息,但是可以使用其余的代码,makefile和gpr文件在您的计算机上重新创建我的二进制文件(用于一个更完整但不那么简单的插图)。
库/ my_library.ads
$ 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)
...
库/ my_library.adb
package My_Library is
procedure Initialize_Library;
pragma Export (DLL, Initialize_Library, "Initialize_Library");
end My_Library;
库/ dummy.ads
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;
库/ my_library.gpr
package Dummy is
end Dummy;
库/生成文件
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;
埃克/ my_exe.adb
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
埃克/ my_library_import.ads
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;
埃克/ my_library_import.adb
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;
埃克/生成文件
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;
我没有将gpr文件用于可执行文件(Exe)。
执行" Exe"中的程序。 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
的文件夹,以及附加文件的完整输出如下:
./Debug/my_exe
答案 0 :(得分:2)
我不知道你做的与我有什么不同,因为你没有告诉我们你的构建过程是什么,或者你正在使用什么版本的OS /编译器。此外,我无法重现您的确切结果,因为您尚未提供完整的演示器。
我认为答案在于最近发布的 gprbuild (我使用GNAT GPL 2016提供的那个,在macOS Sierra和Debian jessie上)的无证(但是可取)功能。
我写了一个包含实例化器的库,
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并将其全局符号转换为本地强> !!!
最近#34;"我的意思是早于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
答案 1 :(得分:0)
一种解决方案是使用renames
来更改哪些符号是公开的。拿这个文件
普通/ my_program.ads
with My_Program_Generic; My_Program is new My_Program_Generic;
和JUST为库创建一个新副本,编辑如下:
库/ 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!
答案 2 :(得分:0)
我的雇主终于能够升级到新的Linux计算机,将我们的GCC从GCC版本4.1.2 20080704 (Red Hat 4.1.2-50)
升级到4.4.7 20120313 (Red Hat 4.4.7-18)
。
至少从这个新版本(4.4.7)开始,该问题在gnat / gcc的较新版本中没有那么严重,就像问题评论中预测的@BrianDrummond一样。即使声明为private
,也仅将包说明中的符号设置为public / global。仅出现在程序包主体中的符号被设置为私有/本地。
但是,仍然没有办法显式地将符号设置为私有/本地。
当前最新版本的GCC 8.2中仍然存在此新问题。
答案 3 :(得分:0)
我们发现了另一种解决方法,即将dlopen
的呼叫标志从RTLD_GLOBAL
更改为RTLD_LOCAL
:
Library_Handle := dlopen(Temp_Name, 16#101#); -- RTLD_NOW (0x0001), RTLD_GLOBAL (0x0100)
到
Library_Handle := dlopen(Temp_Name, 16#1#); -- RTLD_NOW (0x0001), RTLD_LOCAL (0x0000)
但是,通过这种方式,每次加载库时都需要完整的文件路径和名称(或将路径放入您的LD_LIBRARY_PATH
环境变量中),并且导出的任何符号都不会立即可见,因此您需要调用dlsym
来获取需要使用的任何符号,例如在问题行中:
-- Get function access
External_Initialize_Library := External_Initialize_Library_Type_Import(
dlsym(Library_Handle, Interfaces.C.To_C("Initialize_Library")));
通过这种方式,您至少可以管理哪些符号是“可见的”-通过使任何符号都不实际可见,然后显式地获取所需的符号。
答案 4 :(得分:-1)
&#34; my_program_generic.ads&#34;最终可以在一个目标文件中,因为它是一个通用的包,所以我不认为你有任何担心。