]> mj.ucw.cz Git - pciutils.git/commitdiff
libpci: Add support for building versioned shared Windows DLL library libpci3.dll
authorPali Rohár <pali@kernel.org>
Sun, 8 May 2022 08:25:59 +0000 (10:25 +0200)
committerPali Rohár <pali@kernel.org>
Sun, 30 Oct 2022 15:00:36 +0000 (16:00 +0100)
PE/COFF format, used by DLL libraries, does not support version symbols
like ELF format. Recommendation from Microsoft for DLL symbol versioning is
to use DLL API sets. But DLL API sets scheme requires for every API change
to generated a new slim forwarding DLL library, which is unsuitable for
distribution which wants just one DLL library with all version symbols.

So instead of Microsoft recommended scheme for DLL versioning, use new
different versioning scheme: Symbol is composed by function name, at (@)
character and version string (version is same as for ELF targets). Symbol
name without version information is added only into the DLL DEF file as
alias to symbol with higest version. So linker at application link time
resolves "unversioned" symbol to the versioned one via this alias and puts
"versioned" symbol into final executable. This works fine if GNU LD is
linking application via import library libpci3.dll.a generated from that
DLL DEF file libpci3.def. But does not work when linking directly to the
DLL library because library itself does not contain aliases. Note that GNU
LD does not support linking to DEF file (it is required to first generated
import library from DEF file).

Note that older GNU LD versions have bug which cause generation of
corrupted DLL files if some symbol contains dot (.) character. Hopefully
this bug was fixed in GNU LD 2.21.

At the end application lspci.exe requires library libpci3.dll with symbols
pci_alloc@LIBPCI_3.0, pci_init@LIBPCI_3.5, pci_fill_info@LIBPCI_3.8 and
therefore libpci3.dll stays backward compatible with future changes.

PE/COFF executables can reference symbols either via name or via its
ordinal number. Because DLL DEF files are generated from libpci version
script and generator ver2def.pl preserves order of symbols, it means that
ordinal numbers stay backward compatible unless order of lines in version
script is changed.

WARNINGS:

GCC an GNU LD for Windows target have some bugs which cause that
-fvisibility=hidden switch and __attribute__((visibility("default"))) does
not work. Seems that they are broken and ignored when building DLL library.
So instead use -Wl,--exclude-all-symbols switch with explicit DLL DEF file
for building DLL library, which seems to work. This switch is supported
since GNU LD 2.21.

GNU LD has also another bug which results in broken DLL library if input
DLL DEF file which describes symbols for exports, contains also symbol
aliases via == operator.

So do not specify symbol aliases in input DLL DEF file for building DLL
library. Instead construct separate DLL DEF file for building libpci3.dll
without symbol aliases and separate DLL DEF file libpci3.def with symbol
aliases for building import library libpci3.dll.a suitable for linking into
target applications. Note that operator == for symbol aliases is supported
since GNU dlltool 2.21.

Generate those two DLL DEF files via new script ver2def.pl from libpci.ver
version script. So exported functions and version symbols would be defined
only at one place in file libpci.ver.

Note that GNU LD for Windows targets has also broken support for version
scripts, it exports nonsense data and completely ignores version
information. So always use only DLL DEF files generated by ver2def.pl
script and never pass original version script to GNU LD.

Due to another bugs in GNU dlltool, ordinals for aliased symbols from DLL
DEF file are calculated incorrectly when building import library. So
calculate ordinals manually in ver2def.pl script and explicitly put then
into generated libpci3.def DLL DEF file for every symbol, including
aliases.

And because aliases are stored only in libpci3.def file (and in import
library libpci3.dll.a generated from that DEF file) and not in DLL library
libpci3.dll itself, it is required to link all libpci applications via
import library and not directly to libpci3.dll. This is limitation of
PE/COFF format used by DLL libraries.

So for building Windows DLL library libpci3.dll is needed to use GNU
binutils 2.21 or new.

Makefile
lib/Makefile
lib/configure
lib/internal.h
lib/libpci.ver
lib/ver2def.pl [new file with mode: 0755]

index de37e50cc4fe98a108644f9c7107f639caa5d3e0..409cefce628710a0d27db7db4c96caac17d106bc 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -53,6 +53,7 @@ CC=cc
 endif
 AR=$(CROSS_COMPILE)ar
 RANLIB=$(CROSS_COMPILE)ranlib
+DLLTOOL=$(CROSS_COMPILE)dlltool
 
 # Base name of the library (overridden on NetBSD, which has its own libpci)
 LIBNAME=libpci
@@ -123,7 +124,7 @@ TAGS:
 
 clean:
        rm -f `find . -name "*~" -o -name "*.[oa]" -o -name "\#*\#" -o -name TAGS -o -name core -o -name "*.orig"`
-       rm -f update-pciids lspci$(EXEEXT) setpci$(EXEEXT) example$(EXEEXT) lib/config.* *.[578] pci.ids.gz lib/*.pc lib/*.so lib/*.so.* tags
+       rm -f update-pciids lspci$(EXEEXT) setpci$(EXEEXT) example$(EXEEXT) lib/config.* *.[578] pci.ids.gz lib/*.pc lib/*.so lib/*.so.* lib/*.dll lib/*.def tags
        rm -rf maint/dist
 
 distclean: clean
@@ -145,7 +146,7 @@ endif
 ifeq ($(SHARED),yes)
 ifeq ($(LIBEXT),dylib)
        ln -sf $(PCILIB) $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(ABI_VERSION).$(LIBEXT)
-else
+else ifeq ($(LIBEXT),so)
        ln -sf $(PCILIB) $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(LIBEXT).$(ABI_VERSION)
 endif
 endif
@@ -156,7 +157,13 @@ endif
 
 install-pcilib: lib/$(PCILIB)
        $(DIRINSTALL) -m 755 $(DESTDIR)$(LIBDIR)
+ifeq ($(SHARED)_$(LIBEXT),yes_dll)
+# DLL library must have executable flag on disk and be placed in same directory as where are EXE files
+       $(DIRINSTALL) -m 755 $(DESTDIR)$(SBINDIR)
+       $(INSTALL) -c -m 755 lib/$(PCILIB) $(DESTDIR)$(SBINDIR)
+else
        $(INSTALL) -c -m 644 lib/$(PCILIB) $(DESTDIR)$(LIBDIR)
+endif
 
 install-lib: $(PCIINC_INS) install-pcilib
        $(DIRINSTALL) -m 755 $(DESTDIR)$(INCDIR)/pci $(DESTDIR)$(PKGCFDIR)
@@ -165,11 +172,14 @@ install-lib: $(PCIINC_INS) install-pcilib
 ifneq ($(PCIIMPLIB),$(PCILIB))
        $(INSTALL) -c -m 644 lib/$(PCIIMPLIB) $(DESTDIR)$(LIBDIR)
 endif
+ifneq ($(PCIIMPDEF),)
+       $(INSTALL) -c -m 644 lib/$(PCIIMPDEF) $(DESTDIR)$(LIBDIR)
+endif
 ifeq ($(SHARED),yes)
 ifeq ($(LIBEXT),dylib)
        ln -sf $(PCILIB) $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(ABI_VERSION).$(LIBEXT)
        ln -sf $(LIBNAME).$(ABI_VERSION).$(LIBEXT) $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(LIBEXT)
-else
+else ifeq ($(LIBEXT),so)
        ln -sf $(PCILIB) $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(LIBEXT).$(ABI_VERSION)
        ln -sf $(LIBNAME).$(LIBEXT).$(ABI_VERSION) $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(LIBEXT)
 endif
@@ -185,13 +195,21 @@ endif
        rm -f $(DESTDIR)$(MANDIR)/man8/lspci.8 $(DESTDIR)$(MANDIR)/man8/setpci.8 $(DESTDIR)$(MANDIR)/man8/update-pciids.8
        rm -f $(DESTDIR)$(MANDIR)/man7/pcilib.7
        rm -f $(DESTDIR)$(MANDIR)/man5/pci.ids.5
+ifeq ($(SHARED)_$(LIBEXT),yes_dll)
+       rm -f $(DESTDIR)$(SBINDIR)/$(PCILIB)
+else
        rm -f $(DESTDIR)$(LIBDIR)/$(PCILIB)
+endif
        rm -f $(DESTDIR)$(PKGCFDIR)/$(PCILIBPC)
        rm -f $(addprefix $(DESTDIR)$(INCDIR)/pci/,$(notdir $(PCIINC_INS)))
 ifneq ($(PCIIMPLIB),$(PCILIB))
        rm -f $(DESTDIR)$(LIBDIR)/$(PCIIMPLIB)
 endif
+ifneq ($(PCIIMPDEF),)
+       rm -f $(DESTDIR)$(LIBDIR)/$(PCIIMPDEF)
+endif
 ifeq ($(SHARED),yes)
+ifneq ($(LIBEXT),dll)
        rm -f $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(LIBEXT)
 ifeq ($(LIBEXT),dylib)
        rm -f $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(ABI_VERSION).$(LIBEXT)
@@ -199,6 +217,7 @@ else
        rm -f $(DESTDIR)$(LIBDIR)/$(LIBNAME).$(LIBEXT).$(ABI_VERSION)
 endif
 endif
+endif
 
 pci.ids.gz: pci.ids
        gzip -9n <$< >$@
index 8a694817397bc8f692e663ff7e66faeb4aeff5f7..ae2314408eaf252fb8356583503e9c9e5d8008f6 100644 (file)
@@ -67,9 +67,20 @@ $(PCILIB): $(addsuffix .o,$(OBJS))
        $(AR) rcs $@ $^
        $(RANLIB) $@
 else
+ifeq ($(LIBEXT),dll)
+all: $(PCIIMPDEF) $(PCIIMPLIB)
+build.def: $(PCIIMPDEF)
+$(PCIIMPDEF): libpci.ver ver2def.pl
+       perl ver2def.pl libpci.ver $(PCILIB) build.def $(PCIIMPDEF)
+$(PCIIMPLIB): $(PCIIMPDEF)
+       $(DLLTOOL) --input-def $< --output-lib $@
+endif
 CFLAGS += -fPIC -fvisibility=hidden
 $(PCILIB): $(addsuffix .o,$(OBJS))
        $(CC) -shared $(CFLAGS) $(LDFLAGS) $(PCILIB_LDFLAGS) -o $@ $^ $(LIB_LDLIBS)
+ifeq ($(LIBEXT),dll)
+$(PCILIB): build.def
+endif
 endif
 
 $(PCILIBPC): libpci.pc.in
index d6e2c0979cb02a97d6a6bf3afcbf19d026c694d8..0f23a4ac140f62ed2e33c1625df8ab12cad1c642 100755 (executable)
@@ -165,6 +165,7 @@ case $sys in
                fi
                ;; esac
                EXEEXT=.exe
+               LIBEXT=dll
                ;;
        beos|haiku)
                case $cpu in
@@ -290,6 +291,8 @@ if [ "$SHARED" = no ] ; then
 else
        if [ "$LIBEXT" = so ]; then
                echo >>$m 'PCILIB=$(LIBNAME).$(LIBEXT).$(VERSION)'
+       elif [ "$LIBEXT" = dll ]; then
+               echo >>$m 'PCILIB=$(LIBNAME)$(ABI_VERSION).$(LIBEXT)'
        else
                echo >>$m 'PCILIB=$(LIBNAME).$(VERSION).$(LIBEXT)'
        fi
@@ -302,11 +305,19 @@ else
                echo >>$m 'PCILIB_LDFLAGS+=-Wl,--version-script=libpci.ver'
        elif [ "$LIBEXT" = dylib ]; then
                echo >>$m 'PCILIB_LDFLAGS+=-Wl,-install_name,$(LIBDIR)/$(PCILIB)'
+       elif [ "$LIBEXT" = dll ]; then
+               echo >>$m 'PCIIMPDEF=$(LIBNAME)$(ABI_VERSION).def'
+               # GCC's -fvisibility=hidden is broken for Windows targets, use -Wl,--exclude-all-symbols instead (supported since GNU LD 2.21)
+               echo >>$m 'PCILIB_LDFLAGS+=-Wl,--exclude-all-symbols'
        fi
 fi
 echo >>$m 'PCILIBPC=$(LIBNAME).pc'
 
-echo >>$m 'PCIIMPLIB=$(PCILIB)'
+if [ "$SHARED" != no ] && [ "$LIBEXT" = dll ]; then
+       echo >>$m 'PCIIMPLIB=$(PCILIB).a'
+else
+       echo >>$m 'PCIIMPLIB=$(PCILIB)'
+fi
 
 echo >>$c "#define PCILIB_VERSION \"$VERSION\""
 sed '/"/{s/^#define \([^ ]*\) "\(.*\)"$/\1=\2/;p;d;};s/^#define \(.*\)/\1=1/' <$c >>$m
index e0185dde7134f399fc5b18f0dc7356b67a3ea5bb..df7b237e92b1072fc78341f128c6012c19244451 100644 (file)
 #else
 #define STATIC_ALIAS(_decl, _for)
 #define DEFINE_ALIAS(_decl, _for) extern _decl __attribute__((alias(#_for)))
+#ifdef _WIN32
+/* GCC does not support asm .symver directive for Windows targets, so define new external global function symbol as alias to internal symbol */
+#define SYMBOL_VERSION(_int, _ext) asm(".globl\t" PCI_STRINGIFY(__MINGW_USYMBOL(_ext)) "\n\t" \
+                                       ".def\t"   PCI_STRINGIFY(__MINGW_USYMBOL(_ext)) ";\t.scl\t2;\t.type\t32;\t.endef\n\t" \
+                                       ".set\t"   PCI_STRINGIFY(__MINGW_USYMBOL(_ext)) "," PCI_STRINGIFY(__MINGW_USYMBOL(_int)))
+#else
 #define SYMBOL_VERSION(_int, _ext) asm(".symver " #_int "," #_ext)
 #endif
+#endif
 #else
 #define VERSIONED_ABI
 #define STATIC_ALIAS(_decl, _for) _decl { return _for; }
 #include "pci.h"
 #include "sysdep.h"
 
+/* Old 32-bit-only versions of MinGW32 do not define __MINGW_USYMBOL macro */
+#ifdef __MINGW32__
+#ifndef __MINGW_USYMBOL
+#define __MINGW_USYMBOL(sym) _##sym
+#endif
+#endif
+
+#define _PCI_STRINGIFY(x) #x
+#define PCI_STRINGIFY(x) _PCI_STRINGIFY(x)
+
 struct pci_methods {
   char *name;
   char *help;
index bd5ae2345dacf9233595605b55337a040eedc05b..33ee0244ab8d8d980ea7913cd696c9d1d88774a9 100644 (file)
@@ -4,6 +4,13 @@
  *  Visibility declarations in the source take precedence over this script,
  *  so we can boldly declare pci_* as public and still keep the internal
  *  functions properly hidden.
+ *
+ *  To preserve compatibility of Windows DLL file, always add new symbol at
+ *  the end of file and never change order of symbols nor version sections.
+ *  On Windows the last referenced version of the symbol is the default one.
+
+ *  For PE/COFF targets this file is processed by ver2def.pl script and not
+ *  by GNU LD linker like for ELF targets.
  */
 
 LIBPCI_3.0 {
diff --git a/lib/ver2def.pl b/lib/ver2def.pl
new file mode 100755 (executable)
index 0000000..18231bc
--- /dev/null
@@ -0,0 +1,47 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+die "Usage: $0 script.ver dllname build.def import.def\n" if @ARGV != 4;
+my ($verfile, $dllname, $builddef, $importdef) = @ARGV;
+open my $verfh, '<', $verfile or die "Cannot open input file $verfile: $!\n";
+my $input = join '', <$verfh>;
+close $verfh;
+my @syms;
+my (%cnt, %last, %ords);
+$input =~ s/\/\*.*?\*\///sg; # Remove C comments
+while ($input =~ m/(\S+)\s*\{((?:[^\{\}]|\{(?2)\})+)\}\s*;/sg) { # Split {...}
+       my ($ver, $block) = ($1, $2);
+       while ($block =~ s/(\S+)\s*:((?:[^\{\}:]|\{(?2)\})+)$//sg) { # Split section:
+               my ($section, $syms) = ($1, $2);
+               next if $section ne 'global';
+               $syms =~ s/\s+//g;
+               foreach (split /;\s*/, $syms) { # Split symbols
+                       $cnt{$_}++;
+                       $last{$_} = $ver;
+                       push @syms, [$_, $ver];
+               }
+       }
+}
+open my $importfh, '>', $importdef or die "Cannot open output file $importdef: $!\n";
+open my $buildfh, '>', $builddef or die "Cannot open output file $builddef: $!\n";
+print $importfh "LIBRARY \"$dllname\"\n";
+print $importfh "EXPORTS\n";
+print $buildfh "EXPORTS\n";
+my $ord = 1;
+foreach (@syms) {
+       my ($sym, $ver) = @{$_};
+       print $importfh "\"$sym\@$ver\" \@$ord\n";
+       if ($last{$sym} ne $ver) {
+               print $buildfh "\"$sym\@$ver\" \@$ord\n";
+       } else {
+               $ords{$sym} = $ord;
+               print $buildfh "\"$sym\@$ver\" = " . (($cnt{$sym} > 1) ? "\"$sym\@\@$ver\"" : $sym) . " \@$ord\n"
+       }
+       $ord++;
+}
+# GNU dlltool has broken calculation of ordinals for aliased symbols, so specify ordinals explicitly
+# GNU LD prior 2.21 has broken handling of symbols with dot character
+# Operator == for defining symbol alias is supported since GNU dlltool 2.21
+print $importfh "$_ \@$ords{$_} == \"$_\@$last{$_}\"\n" foreach sort keys %last;
+close $importfh;
+close $buildfh;