#!perl -w # mapsymw - mapsym wrapper form OpenWatcom map files # Generate IBM format map file and run mapsym # Writes IBM format map file to OpenWatcom map file directory # Copyright (c) 2007-2019 Steven Levine and Associates, Inc. # All rights reserved. # This program is free software licensed under the terms of the GNU # General Public License. The GPL Software License can be found in # gnugpl2.txt or at http://www.gnu.org/licenses/licenses.html#GPL # 2007-07-02 SHL Baseline # 2008-12-14 SHL Ensure symbols sorted by value - some apps care # 2012-03-19 SHL Segment names must be uppercase for pmdf # 2014-06-13 SHL Correct typos # 2014-11-01 SHL Generate ibm map files in watcom map file directory # 2015-01-31 SHL Use unix slashes to keep kLIBC sh.exe happy # 2019-03-02 SHL More C++ logic # 2019-03-03 SHL Rework dup logic and sym length limiting # 2019-03-22 SHL Disable debug code # 2019-04-09 SHL Comments # mapsym requires each segment to have at least 1 symbol # mapsym requires 32 bit segments to have at least 1 symbol with offset > 65K # we generate dummy symbols to enforce this # mapsym does not understand segment 0 # we generate Imp flags to support this use strict; use warnings; # use Package::Subpackage Options; use POSIX qw(strftime); use Getopt::Std; use File::Basename; our $g_version = '0.4'; our $g_cmdname; our @g_mapfiles; # All map files our $g_mapfile; # Current .map file name &initialize; our %g_opts; &scan_args; print "\n"; foreach $g_mapfile (@g_mapfiles) { $g_mapfile =~ tr|\\|/|; # 2015-01-31 SHL Unix slashes for sh.exe &mapsym; } exit; # end main #=== initialize() Initialize globals === sub initialize { &set_cmd_name; } # initialize #=== mapsym() Generate work file, run mapsym on work file === sub mapsym { # Isolate map file basename my $mapid = basename($g_mapfile); $mapid =~ s/\.[^.]*$//; # Strip ext verbose_msg("\nProcessing $mapid"); fatal("$g_mapfile does not exist.") if ! -f $g_mapfile; open MAPFILE, $g_mapfile or die "open $g_mapfile $!"; my $g_wrkfile = $g_mapfile . '-ibm'; $g_wrkfile =~ s/-watcom-ibm$/-ibm/; # In case renamed from .map unlink $g_wrkfile || die "unlink $g_wrkfile $!" if -f $g_wrkfile; open WRKFILE, ">$g_wrkfile" or die "open $g_wrkfile $!"; my $modname; my $state = ''; my $segcnt = 0; my $symcnt = 0; my $is32bit; my %segsinfo; my %syms; my $segnum; my $offset; my $segaddr; my $segfmt; my $symfmt; while () { chomp; # EOL if (/Executable Image: (\S+)\.\w+$/) { $modname = $1; print WRKFILE "Generated by $g_cmdname from $g_mapfile on ", strftime('%A, %B %d, %Y at %I:%M %p', localtime), "\n\n"; print WRKFILE " $modname\n"; } $state = 'segments' if /Segment Class Group Address Size/; $state = 'addresses' if /Address Symbol/; # Skip don't cares next if /^=/; next if /^ /; next if /^$/; if ($state eq 'segments') { # In # Segment Class Group Address Size # _TEXT16 CODE AUTO 0001:00000000 00000068 # Out # 0 1 2 3 4 5 6 # 123456789012345678901234567890123456789012345678901234567890 # Start Length Name Class # 0001:00000000 000000030H _MSGSEG32 CODE 32-bit if (/^(\w+)\s+(\w+)\s+\w+\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:xdigit:]]+)$/) { my $segname = $1; my $class = $2; $segnum = $3; # Has leading 0's $offset = $4; my $seglen = $5; $segaddr = "$segnum:$offset"; if (!$segcnt) { # First segment - determine address size (16/32 bit) $is32bit = length($offset) == 8; # Output title print WRKFILE "\n"; if ($is32bit) { print WRKFILE " Start Length Name Class\n"; $segfmt = " %13s 0%8sH %-22s %s\n"; $symfmt = " %13s %3s %s\n"; } else { print WRKFILE " Start Length Name Class\n"; $segfmt = " %9s 0%4sH %-22s %s\n"; $symfmt = " %9s %3s %s\n"; } } $seglen = substr($5, -4) if !$is32bit; printf WRKFILE $segfmt, $segaddr, $seglen, $segname, $class; $segcnt++; } } # if segments if ($state eq 'addresses') { # In # Address Symbol # 0002:0004ae46+ ArcTextProc # 0002:0d11+ void near IoctlAudioCapability( __2bd9g9REQPACKET far *, short unsigned ) # Out # 0 1 2 3 4 5 6 # 123456789012345678901234567890123456789012345678901234567890 # Address Publics by Value # 0000:00000000 Imp WinEmptyClipbrd (PMWIN.733) # 0002:0001ED40 __towlower_dummy if (/^([[:xdigit:]]+):([[:xdigit:]]+)[+*]?\s+(.+)$/) { $segnum = $1; $offset = $2; my $sym = $3; my $seginfo; if (defined($segsinfo{$segnum})) { $seginfo = $segsinfo{$segnum}; } else { $seginfo = {max_offset => 0, symcnt => 0}; } my $n = hex $offset; # Remember max symbol offset $seginfo->{max_offset} = $n if $n > $seginfo->{max_offset}; $seginfo->{symcnt}++; $segsinfo{$segnum} = $seginfo; $segaddr = "$segnum:$offset"; # Convert C++ symbols to something mapsym will accept # warn "\n$sym\n"; $_ = $sym; s/\s+\(.*\)//; # Drop (...) comments preceeded by whitespace # Make C++ compatible with mapsym # Drop keywords and decorations s/\b(near|int|short|unsigned|long|void|char|const|wchar_t)\b\s*[*&]?\s*//g; # s/<[^<>]+>//g; # Replace < > templates with nothing s/std::\s*/std/g; # Replace std:: with std s/::~/__x/g; # Replace destructor ::~ with __x s/::/__/g; # Replace :: with __ s/[\[\]()<>,]/ /g; # Replace [ ] < > and , with spaces s/\s+/_/g; # Replace whitespace with single _ s/__+/_/g; # Replace multiple _ with single _ # Drop leading and trailing _ to match source code s/^_//; # Drop leading _ (cdecl) s/_$//; # Drop trailing _ (watcall) $_ = substr($_, 0, 63) if length($_) > 63; # limit # warn "\n$_\n"; # Prune some libc symbols to avoid mapsym overflows if ($mapid =~ /libc06/) { # 0001:000b73e0 __ZNSt7codecvtIcc11__mbstate_tEC2Ej # next if / [0-9A-F]{4}:[0-9A-F]{8} {7}S/; next if /\b__Z/; # Prune libstdc++ } if (!$symcnt) { # First symbol - output title print WRKFILE "\n"; if ($is32bit) { print WRKFILE " Address Publics by Value\n"; } else { print WRKFILE " Address Publics by Value\n"; } } $syms{$segaddr} = $_; $symcnt++; } } # if addresses } # while lines close MAPFILE; # Sort segments my @keys = sort keys %segsinfo; if (@keys) { my $maxseg = pop @keys; @keys = '0000'..$maxseg; } # Generate dummy symbols for 32-bit segments smaller than 64KB foreach $segnum (@keys) { if ($segnum != 0) { my $seginfo; if (defined($segsinfo{$segnum})) { $seginfo = $segsinfo{$segnum}; } else { $seginfo = {max_offset => 0, symcnt => 0}; } if ($seginfo->{symcnt} == 0) { warn "Segment $segnum has no symbols - generating dummy symbol\n"; $_ = "SEG${segnum}_dummy"; if ($is32bit) { $segaddr = "$segnum:00010000"; } else { $segaddr = "$segnum:0000"; } $syms{$segaddr} = $_; $symcnt++; } elsif ($is32bit && $seginfo->{max_offset} < 0x10000) { warn "32 bit segment $segnum is smaller than 64K - generating dummy symbol\n"; $_ = "SEG${segnum}_dummy"; $segaddr = "$segnum:00010000"; $syms{$segaddr} = $_; $symcnt++; } } } # foreach $segnum # Generate symbols by value listing # FIXME to warn if may overflow 64K limit @keys = sort keys %syms; my %used; foreach $segaddr (@keys) { my $sym = $syms{$segaddr}; my $imp = substr($segaddr, 0, 4) eq '0000' ? 'Imp' : ''; # Avoid dups if (defined $used{$sym}) { my $seq = $used{$sym}; $seq++; $used{$sym} = $seq; $sym = "${sym}_$seq"; } else { $used{$sym} = 0; } printf WRKFILE $symfmt, $segaddr, $imp, $sym; } # foreach close WRKFILE; die "Can not locate module name. $g_mapfile is probably not a Watcom map file\n" if !defined($modname); my $symfile = "$mapid.sym"; unlink $symfile || die "unlink $symfile $!" if -f $symfile; warn "Processed $segcnt segments and $symcnt symbols for $modname\n"; system("mapsym $g_wrkfile"); } # mapsym #=== scan_args(cmdLine) Scan command line === sub scan_args { getopts('dhtvV', \%g_opts) || &usage; &help if $g_opts{h}; if ($g_opts{V}) { print "$g_cmdname v$g_version"; exit; } my $arg; for $arg (@ARGV) { my @maps = glob($arg); usage("File $arg not found") if @maps == 0; push @g_mapfiles, @maps; } # for arg } # scan_args #=== help() Display scan_args usage help exit routine === sub help { print <