如何使用gnat更改Ada库的符号可见性?

问题描述 投票:0回答:5

我将程序移植到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)而不是它自己的(这是False),无法初始化,然后使用未初始化的变量崩溃。

我尝试使用-fvisiblity=hidden编译所有内容(来自makefile和gnat项目文件(.gpr)),这似乎正确地将它传递给编译器(例如它显示在命令行gcc -c -fPIC -g -m32 -fvisibility=hidden -gnatA my_file.adb上),但它没有似乎有所作为,我找不到任何使用gnat控制可见性的文档。

我的操作系统是CentOS 5.6。我无法升级到更新版本的Linux,但我可以将我的GCC或gnat版本升级到适用于CentOS 5.6的任何版本。我的GCC / gnat版本如下:

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

是的,我知道它说红帽,但我正在使用CentOS。无论如何,AFAIK彼此完全兼容。


上面已经完全解释了解决我的问题所需的所有信息,但是这里可以使用其余的代码,makefile和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;

库/ 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)。

使用./Debug/my_exe从“Exe”文件夹执行程序,带有附加文件的完整输出如下:

$ ./Debug/my_exe
Begin main program.
Initialized: FALSE
Did Stuff!
Initializing Library...
Initialized: TRUE
linux visibility ada gnat
5个回答
2
投票

我不知道你的做法与我有什么不同,因为你没有告诉我们你的构建过程是什么,或者你正在使用什么版本的OS /编译器。此外,我无法重现您的确切结果,因为您还没有提供完整的演示器。

我相信答案在于最近发布的gprbuild的一个未记录的(但是可取的)功能(我在macOS Sierra和Debian jessie上使用了GNAT GPL 2016提供的功能)。

我写了一个包含实例化器的库,

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的GPR导入库,

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

一种解决方案是使用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!

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也是如此。仅出现在包体中的符号是私有/本地的。

但是,仍然无法明确地将符号设为私有/本地。

这个新问题仍然存在于当前最新版本的GCC 8.2中。


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环境变量中),并且导出的符号中的NONE将立即可见,因此您需要调用dlsym用于获取您需要使用的任何符号,例如问题中的行:

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

这样,您至少可以管理哪些符号是“可见的” - 实际上看不到它们,并且明确地获取您需要的符号。


-1
投票

“my_program_generic.ads”中的任何内容都不能以对象文件结尾,因为它是一个通用包,因此我认为您无需担心。

© www.soinside.com 2019 - 2024. All rights reserved.