xref: /freebsd/crypto/openssl/util/mkdef.pl (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
1#! /usr/bin/env perl
2# Copyright 2018-2022 The OpenSSL Project Authors. All Rights Reserved.
3#
4# Licensed under the Apache License 2.0 (the "License").  You may not use
5# this file except in compliance with the License.  You can obtain a copy
6# in the file LICENSE in the source distribution or at
7# https://www.openssl.org/source/license.html
8
9# Generate a linker version script suitable for the given platform
10# from a given ordinals file.
11
12use strict;
13use warnings;
14
15use Getopt::Long;
16use FindBin;
17use lib "$FindBin::Bin/perl";
18
19use OpenSSL::Ordinals;
20
21use lib '.';
22use configdata;
23
24use File::Spec::Functions;
25use lib catdir($config{sourcedir}, 'Configurations');
26use platform;
27
28my $name = undef;               # internal library/module name
29my $ordinals_file = undef;      # the ordinals file to use
30my $version = undef;            # the version to use for the library
31my $OS = undef;                 # the operating system family
32my $type = 'lib';               # either lib or dso
33my $verbose = 0;
34my $ctest = 0;
35my $debug = 0;
36
37# For VMS, some modules may have case insensitive names
38my $case_insensitive = 0;
39
40GetOptions('name=s'     => \$name,
41           'ordinals=s' => \$ordinals_file,
42           'version=s'  => \$version,
43           'OS=s'       => \$OS,
44           'type=s'     => \$type,
45           'ctest'      => \$ctest,
46           'verbose'    => \$verbose,
47           # For VMS
48           'case-insensitive' => \$case_insensitive)
49    or die "Error in command line arguments\n";
50
51die "Please supply arguments\n"
52    unless $name && $ordinals_file && $OS;
53die "--type argument must be equal to 'lib' or 'dso'"
54    if $type ne 'lib' && $type ne 'dso';
55
56# When building a "variant" shared library, with a custom SONAME, also customize
57# all the symbol versions.  This produces a shared object that can coexist
58# without conflict in the same address space as a default build, or an object
59# with a different variant tag.
60#
61# For example, with a target definition that includes:
62#
63#         shlib_variant => "-opt",
64#
65# we build the following objects:
66#
67# $ perl -le '
68#     for (@ARGV) {
69#         if ($l = readlink) {
70#             printf "%s -> %s\n", $_, $l
71#         } else {
72#             print
73#         }
74#     }' *.so*
75# libcrypto-opt.so.1.1
76# libcrypto.so -> libcrypto-opt.so.1.1
77# libssl-opt.so.1.1
78# libssl.so -> libssl-opt.so.1.1
79#
80# whose SONAMEs and dependencies are:
81#
82# $ for l in *.so; do
83#     echo $l
84#     readelf -d $l | egrep 'SONAME|NEEDED.*(ssl|crypto)'
85#   done
86# libcrypto.so
87#  0x000000000000000e (SONAME)             Library soname: [libcrypto-opt.so.1.1]
88# libssl.so
89#  0x0000000000000001 (NEEDED)             Shared library: [libcrypto-opt.so.1.1]
90#  0x000000000000000e (SONAME)             Library soname: [libssl-opt.so.1.1]
91#
92# We case-fold the variant tag to upper case and replace all non-alnum
93# characters with "_".  This yields the following symbol versions:
94#
95# $ nm libcrypto.so | grep -w A
96# 0000000000000000 A OPENSSL_OPT_1_1_0
97# 0000000000000000 A OPENSSL_OPT_1_1_0a
98# 0000000000000000 A OPENSSL_OPT_1_1_0c
99# 0000000000000000 A OPENSSL_OPT_1_1_0d
100# 0000000000000000 A OPENSSL_OPT_1_1_0f
101# 0000000000000000 A OPENSSL_OPT_1_1_0g
102# $ nm libssl.so | grep -w A
103# 0000000000000000 A OPENSSL_OPT_1_1_0
104# 0000000000000000 A OPENSSL_OPT_1_1_0d
105#
106(my $SO_VARIANT = uc($target{"shlib_variant"} // '')) =~ s/\W/_/g;
107
108my $libname = $type eq 'lib' ? platform->sharedname($name) : platform->dsoname($name);
109
110my %OS_data = (
111    solaris     => { writer     => \&writer_linux,
112                     sort       => sorter_linux(),
113                     platforms  => { UNIX                       => 1 } },
114    "solaris-gcc" => 'solaris', # alias
115    linux       => 'solaris',   # alias
116    "bsd-gcc"   => 'solaris',   # alias
117    aix         => { writer     => \&writer_aix,
118                     sort       => sorter_unix(),
119                     platforms  => { UNIX                       => 1 } },
120    VMS         => { writer     => \&writer_VMS,
121                     sort       => OpenSSL::Ordinals::by_number(),
122                     platforms  => { VMS                        => 1 } },
123    vms         => 'VMS',       # alias
124    WINDOWS     => { writer     => \&writer_windows,
125                     sort       => OpenSSL::Ordinals::by_name(),
126                     platforms  => { WIN32                      => 1,
127                                     _WIN32                     => 1 } },
128    windows     => 'WINDOWS',   # alias
129    WIN32       => 'WINDOWS',   # alias
130    win32       => 'WIN32',     # alias
131    32          => 'WIN32',     # alias
132    NT          => 'WIN32',     # alias
133    nt          => 'WIN32',     # alias
134    mingw       => 'WINDOWS',   # alias
135    nonstop     => { writer     => \&writer_nonstop,
136                     sort       => OpenSSL::Ordinals::by_name(),
137                     platforms  => { TANDEM                     => 1 } },
138   );
139
140do {
141    die "Unknown operating system family $OS\n"
142        unless exists $OS_data{$OS};
143    $OS = $OS_data{$OS};
144} while(ref($OS) eq '');
145
146my %disabled_uc = map { my $x = uc $_; $x =~ s|-|_|g; $x => 1 } keys %disabled;
147
148my %ordinal_opts = ();
149$ordinal_opts{sort} = $OS->{sort} if $OS->{sort};
150$ordinal_opts{filter} =
151    sub {
152        my $item = shift;
153        return
154            $item->exists()
155            && platform_filter($item)
156            && feature_filter($item);
157    };
158my $ordinals = OpenSSL::Ordinals->new(from => $ordinals_file);
159
160my $writer = $OS->{writer};
161$writer = \&writer_ctest if $ctest;
162
163$writer->($ordinals->items(%ordinal_opts));
164
165exit 0;
166
167sub platform_filter {
168    my $item = shift;
169    my %platforms = ( $item->platforms() );
170
171    # True if no platforms are defined
172    return 1 if scalar keys %platforms == 0;
173
174    # For any item platform tag, return the equivalence with the
175    # current platform settings if it exists there, return 0 otherwise
176    # if the item platform tag is true
177    for (keys %platforms) {
178        if (exists $OS->{platforms}->{$_}) {
179            return $platforms{$_} == $OS->{platforms}->{$_};
180        }
181        if ($platforms{$_}) {
182            return 0;
183        }
184    }
185
186    # Found no match?  Then it's a go
187    return 1;
188}
189
190sub feature_filter {
191    my $item = shift;
192    my @features = ( $item->features() );
193
194    # True if no features are defined
195    return 1 if scalar @features == 0;
196
197    my $verdict = ! grep { $disabled_uc{$_} } @features;
198
199    if ($disabled{deprecated}) {
200        foreach (@features) {
201            next unless /^DEPRECATEDIN_(\d+)_(\d+)(?:_(\d+))?$/;
202            my $symdep = $1 * 10000 + $2 * 100 + ($3 // 0);
203            $verdict = 0 if $config{api} >= $symdep;
204            print STDERR "DEBUG: \$symdep = $symdep, \$verdict = $verdict\n"
205                if $debug && $1 == 0;
206        }
207    }
208
209    return $verdict;
210}
211
212sub sorter_unix {
213    my $by_name = OpenSSL::Ordinals::by_name();
214    my %weight = (
215        'FUNCTION'      => 1,
216        'VARIABLE'      => 2
217       );
218
219    return sub {
220        my $item1 = shift;
221        my $item2 = shift;
222
223        my $verdict = $weight{$item1->type()} <=> $weight{$item2->type()};
224        if ($verdict == 0) {
225            $verdict = $by_name->($item1, $item2);
226        }
227        return $verdict;
228    };
229}
230
231sub sorter_linux {
232    my $by_version = OpenSSL::Ordinals::by_version();
233    my $by_unix = sorter_unix();
234
235    return sub {
236        my $item1 = shift;
237        my $item2 = shift;
238
239        my $verdict = $by_version->($item1, $item2);
240        if ($verdict == 0) {
241            $verdict = $by_unix->($item1, $item2);
242        }
243        return $verdict;
244    };
245}
246
247sub writer_linux {
248    my $thisversion = '';
249    my $currversion_s = '';
250    my $prevversion_s = '';
251    my $indent = 0;
252
253    for (@_) {
254        if ($thisversion && $_->version() ne $thisversion) {
255            die "$ordinals_file: It doesn't make sense to have both versioned ",
256                "and unversioned symbols"
257                if $thisversion eq '*';
258            print <<"_____";
259}${prevversion_s};
260_____
261            $prevversion_s = " OPENSSL${SO_VARIANT}_$thisversion";
262            $thisversion = '';  # Trigger start of next section
263        }
264        unless ($thisversion) {
265            $indent = 0;
266            $thisversion = $_->version();
267            $currversion_s = '';
268            $currversion_s = "OPENSSL${SO_VARIANT}_$thisversion "
269                if $thisversion ne '*';
270            print <<"_____";
271${currversion_s}{
272    global:
273_____
274        }
275        print '        ', $_->name(), ";\n";
276    }
277
278    print <<"_____";
279    local: *;
280}${prevversion_s};
281_____
282}
283
284sub writer_aix {
285    for (@_) {
286        print $_->name(),"\n";
287    }
288}
289
290sub writer_nonstop {
291    for (@_) {
292        print "-export ",$_->name(),"\n";
293    }
294}
295
296sub writer_windows {
297    print <<"_____";
298;
299; Definition file for the DLL version of the $libname library from OpenSSL
300;
301
302LIBRARY         "$libname"
303
304EXPORTS
305_____
306    for (@_) {
307        print "    ",$_->name();
308        if (platform->can('export2internal')) {
309            print "=". platform->export2internal($_->name());
310        }
311        print "\n";
312    }
313}
314
315sub collect_VMS_mixedcase {
316    return [ 'SPARE', 'SPARE' ] unless @_;
317
318    my $s = shift;
319    my $s_uc = uc($s);
320    my $type = shift;
321
322    return [ "$s=$type", 'SPARE' ] if $s_uc eq $s;
323    return [ "$s_uc/$s=$type", "$s=$type" ];
324}
325
326sub collect_VMS_uppercase {
327    return [ 'SPARE' ] unless @_;
328
329    my $s = shift;
330    my $s_uc = uc($s);
331    my $type = shift;
332
333    return [ "$s_uc=$type" ];
334}
335
336sub writer_VMS {
337    my @slot_collection = ();
338    my $collector =
339        $case_insensitive ? \&collect_VMS_uppercase : \&collect_VMS_mixedcase;
340
341    my $last_num = 0;
342    foreach (@_) {
343        my $this_num = $_->number();
344        $this_num = $last_num + 1 if $this_num =~ m|^\?|;
345
346        while (++$last_num < $this_num) {
347            push @slot_collection, $collector->(); # Just occupy a slot
348        }
349        my $type = {
350            FUNCTION    => 'PROCEDURE',
351            VARIABLE    => 'DATA'
352           } -> {$_->type()};
353        push @slot_collection, $collector->($_->name(), $type);
354    }
355
356    print <<"_____" if defined $version;
357IDENTIFICATION=$version
358_____
359    print <<"_____" unless $case_insensitive;
360CASE_SENSITIVE=YES
361_____
362    print <<"_____";
363SYMBOL_VECTOR=(-
364_____
365    # It's uncertain how long aggregated lines the linker can handle,
366    # but it has been observed that at least 1024 characters is ok.
367    # Either way, this means that we need to keep track of the total
368    # line length of each "SYMBOL_VECTOR" statement.  Fortunately, we
369    # can have more than one of those...
370    my $symvtextcount = 16;     # The length of "SYMBOL_VECTOR=("
371    while (@slot_collection) {
372        my $set = shift @slot_collection;
373        my $settextlength = 0;
374        foreach (@$set) {
375            $settextlength +=
376                + 3             # two space indentation and comma
377                + length($_)
378                + 1             # postdent
379                ;
380        }
381        $settextlength--;       # only one space indentation on the first one
382        my $firstcomma = ',';
383
384        if ($symvtextcount + $settextlength > 1024) {
385            print <<"_____";
386)
387SYMBOL_VECTOR=(-
388_____
389            $symvtextcount = 16; # The length of "SYMBOL_VECTOR=("
390        }
391        if ($symvtextcount == 16) {
392            $firstcomma = '';
393        }
394
395        my $indent = ' '.$firstcomma;
396        foreach (@$set) {
397            print <<"_____";
398$indent$_ -
399_____
400            $symvtextcount += length($indent) + length($_) + 1;
401            $indent = '  ,';
402        }
403    }
404    print <<"_____";
405)
406_____
407
408    if (defined $version) {
409        $version =~ /^(\d+)\.(\d+)\.(\d+)/;
410        my $libvmajor = $1;
411        my $libvminor = $2 * 100 + $3;
412        print <<"_____";
413GSMATCH=LEQUAL,$libvmajor,$libvminor
414_____
415    }
416}
417
418sub writer_ctest {
419    print <<'_____';
420/*
421 * Test file to check all DEF file symbols are present by trying
422 * to link to all of them. This is *not* intended to be run!
423 */
424
425int main()
426{
427_____
428
429    my $last_num = 0;
430    for (@_) {
431        my $this_num = $_->number();
432        $this_num = $last_num + 1 if $this_num =~ m|^\?|;
433
434        if ($_->type() eq 'VARIABLE') {
435            print "\textern int ", $_->name(), '; /* type unknown */ /* ',
436                  $this_num, ' ', $_->version(), " */\n";
437        } else {
438            print "\textern int ", $_->name(), '(); /* type unknown */ /* ',
439                  $this_num, ' ', $_->version(), " */\n";
440        }
441
442        $last_num = $this_num;
443    }
444    print <<'_____';
445}
446_____
447}
448