| 1 | #!perl -w
 | 
|---|
| 2 | # mapsymw - mapsym Watcom map files
 | 
|---|
| 3 | 
 | 
|---|
| 4 | # Copyright (c) 2007 Steven Levine and Associates, Inc.
 | 
|---|
| 5 | # All rights reserved.
 | 
|---|
| 6 | 
 | 
|---|
| 7 | # $TLIB$: $ &(#) %n - Ver %v, %f $
 | 
|---|
| 8 | # TLIB: $ $
 | 
|---|
| 9 | 
 | 
|---|
| 10 | # This program is free software licensed under the terms of the GNU
 | 
|---|
| 11 | # General Public License.  The GPL Software License can be found in
 | 
|---|
| 12 | # gnugpl2.txt or at http://www.gnu.org/licenses/licenses.html#GPL
 | 
|---|
| 13 | 
 | 
|---|
| 14 | # 02 Jul 07 SHL Baseline
 | 
|---|
| 15 | # 02 Jul 07 SHL Adapt from mapsymb.pl
 | 
|---|
| 16 | # 28 Jul 07 SHL Relax module name detect
 | 
|---|
| 17 | # 30 Jul 07 SHL Auto-trim libstdc++ symbols from libc06x maps
 | 
|---|
| 18 | # 09 Aug 07 SHL Generate dummy symbol for interior segments with no symbols
 | 
|---|
| 19 | # 08 Nov 07 SHL Drop leading keywords from function definitions
 | 
|---|
| 20 | 
 | 
|---|
| 21 | # mapsym requires each segment to have at least 1 symbol
 | 
|---|
| 22 | # mapsym requires 32 bit segments to have at least 1 symbol with offset > 65K
 | 
|---|
| 23 | # we generate dummy symbols to enforce this
 | 
|---|
| 24 | # mapsym does not understand segment 0
 | 
|---|
| 25 | # we generate Imp flags to support this
 | 
|---|
| 26 | 
 | 
|---|
| 27 | use strict;
 | 
|---|
| 28 | use warnings;
 | 
|---|
| 29 | 
 | 
|---|
| 30 | # use Package::Subpackage Options;
 | 
|---|
| 31 | use POSIX qw(strftime);
 | 
|---|
| 32 | use Getopt::Std;
 | 
|---|
| 33 | use File::Spec;
 | 
|---|
| 34 | use File::Basename;
 | 
|---|
| 35 | 
 | 
|---|
| 36 | our $g_version = '0.2';
 | 
|---|
| 37 | 
 | 
|---|
| 38 | our $g_cmdname;
 | 
|---|
| 39 | our $g_tmpdir;
 | 
|---|
| 40 | our @g_mapfiles;                        # All map files
 | 
|---|
| 41 | our $g_mapfile;                         # Current .map file name
 | 
|---|
| 42 | 
 | 
|---|
| 43 | &initialize;
 | 
|---|
| 44 | 
 | 
|---|
| 45 | our %g_opts;
 | 
|---|
| 46 | 
 | 
|---|
| 47 | &scan_args;
 | 
|---|
| 48 | 
 | 
|---|
| 49 | print "\n";
 | 
|---|
| 50 | 
 | 
|---|
| 51 | foreach $g_mapfile (@g_mapfiles) {
 | 
|---|
| 52 |   &mapsym;
 | 
|---|
| 53 | }
 | 
|---|
| 54 | 
 | 
|---|
| 55 | exit;
 | 
|---|
| 56 | 
 | 
|---|
| 57 | # end main
 | 
|---|
| 58 | 
 | 
|---|
| 59 | #=== initialize() Intialize globals ===
 | 
|---|
| 60 | 
 | 
|---|
| 61 | sub initialize {
 | 
|---|
| 62 | 
 | 
|---|
| 63 |   &set_cmd_name;
 | 
|---|
| 64 |   &get_tmp_dir;
 | 
|---|
| 65 | 
 | 
|---|
| 66 | } # initialize
 | 
|---|
| 67 | 
 | 
|---|
| 68 | #=== mapsym() Generate work file, run mapsym on work file ===
 | 
|---|
| 69 | 
 | 
|---|
| 70 | sub mapsym {
 | 
|---|
| 71 | 
 | 
|---|
| 72 |   # Isolate map file basename
 | 
|---|
| 73 |   my $mapid = basename($g_mapfile);
 | 
|---|
| 74 |   $mapid =~ s/\.[^.]*$//;               # Strip ext
 | 
|---|
| 75 |   verbose_msg("\nProcessing $mapid");
 | 
|---|
| 76 | 
 | 
|---|
| 77 |   fatal("$g_mapfile does not exist.") if ! -f $g_mapfile;
 | 
|---|
| 78 | 
 | 
|---|
| 79 |   open MAPFILE, $g_mapfile or die "open $g_mapfile $!";
 | 
|---|
| 80 | 
 | 
|---|
| 81 |   my $g_wrkfile = File::Spec->catfile($g_tmpdir, "$mapid.map");
 | 
|---|
| 82 |   unlink $g_wrkfile || die "unlink $g_wrkfile $!" if -f $g_wrkfile;
 | 
|---|
| 83 |   open WRKFILE, ">$g_wrkfile" or die "open $g_wrkfile $!";
 | 
|---|
| 84 | 
 | 
|---|
| 85 |   my $modname;
 | 
|---|
| 86 |   my $state = '';
 | 
|---|
| 87 |   my $segcnt = 0;
 | 
|---|
| 88 |   my $symcnt = 0;
 | 
|---|
| 89 |   my $is32bit;
 | 
|---|
| 90 |   my %segsinfo;
 | 
|---|
| 91 |   my $segnum;
 | 
|---|
| 92 |   my $offset;
 | 
|---|
| 93 |   my $segaddr;
 | 
|---|
| 94 |   my $imp;
 | 
|---|
| 95 | 
 | 
|---|
| 96 |   my $segfmt;
 | 
|---|
| 97 |   my $symfmt;
 | 
|---|
| 98 | 
 | 
|---|
| 99 |   while (<MAPFILE>) {
 | 
|---|
| 100 | 
 | 
|---|
| 101 |     chomp;                      # EOL
 | 
|---|
| 102 | 
 | 
|---|
| 103 |     if (/Executable Image: (\S+)\.\w+$/) {
 | 
|---|
| 104 |       $modname = $1;
 | 
|---|
| 105 |       print WRKFILE "Generated by $g_cmdname from $g_mapfile on ",
 | 
|---|
| 106 |                     strftime('%A, %B %d, %Y at %I:%M %p', localtime), "\n\n";
 | 
|---|
| 107 |       print WRKFILE " $modname\n";
 | 
|---|
| 108 |     }
 | 
|---|
| 109 | 
 | 
|---|
| 110 |     $state = 'segments'
 | 
|---|
| 111 |       if /Segment                Class          Group          Address         Size/;
 | 
|---|
| 112 | 
 | 
|---|
| 113 |     $state = 'addresses' if /Address        Symbol/;
 | 
|---|
| 114 | 
 | 
|---|
| 115 |     # Skip don't cares
 | 
|---|
| 116 |     next if /^=/;
 | 
|---|
| 117 |     next if /^ /;
 | 
|---|
| 118 |     next if /^ /;
 | 
|---|
| 119 | 
 | 
|---|
| 120 |     if ($state eq 'segments') {
 | 
|---|
| 121 |       # In
 | 
|---|
| 122 |       # Segment                Class          Group          Address         Size
 | 
|---|
| 123 |       # _TEXT16                CODE           AUTO           0001:00000000   00000068
 | 
|---|
| 124 |       # Out
 | 
|---|
| 125 |       # 0        1         2         3         4         5         6
 | 
|---|
| 126 |       # 123456789012345678901234567890123456789012345678901234567890
 | 
|---|
| 127 |       #  Start         Length     Name                   Class
 | 
|---|
| 128 |       #  0001:00000000 000000030H _MSGSEG32              CODE 32-bit
 | 
|---|
| 129 | 
 | 
|---|
| 130 |       if (/^(\w+)\s+(\w+)\s+\w+\s+([[:xdigit:]]+):([[:xdigit:]]+)\s+([[:xdigit:]]+)$/) {
 | 
|---|
| 131 |         my $segname = $1;
 | 
|---|
| 132 |         my $class = $2;
 | 
|---|
| 133 |         $segnum = $3;                   # Has leading 0's
 | 
|---|
| 134 |         $offset = $4;
 | 
|---|
| 135 |         my $seglen = $5;
 | 
|---|
| 136 | 
 | 
|---|
| 137 |         $segaddr = "$segnum:$offset";
 | 
|---|
| 138 | 
 | 
|---|
| 139 |         if (!$segcnt) {
 | 
|---|
| 140 |           $is32bit = length($offset) == 8;
 | 
|---|
| 141 |           print WRKFILE "\n";
 | 
|---|
| 142 |           if ($is32bit) {
 | 
|---|
| 143 |             print WRKFILE " Start         Length     Name                   Class\n";
 | 
|---|
| 144 |             $segfmt = " %13s 0%8sH %-22s %s\n";
 | 
|---|
| 145 |             $symfmt = " %13s  %3s  %s\n";
 | 
|---|
| 146 |           } else {
 | 
|---|
| 147 |             print WRKFILE " Start     Length Name                   Class\n";
 | 
|---|
| 148 |             $segfmt = " %9s 0%4sH %-22s %s\n";
 | 
|---|
| 149 |             $symfmt = " %9s  %3s  %s\n";
 | 
|---|
| 150 |           }
 | 
|---|
| 151 |         }
 | 
|---|
| 152 | 
 | 
|---|
| 153 |         $seglen = substr($5, -4) if !$is32bit;
 | 
|---|
| 154 | 
 | 
|---|
| 155 |         printf WRKFILE $segfmt, $segaddr, $seglen, $segname, $class;
 | 
|---|
| 156 |         $segcnt++;
 | 
|---|
| 157 |       }
 | 
|---|
| 158 |     } # if segments
 | 
|---|
| 159 | 
 | 
|---|
| 160 |     if ($state eq 'addresses') {
 | 
|---|
| 161 |       # In
 | 
|---|
| 162 |       # Address        Symbol
 | 
|---|
| 163 |       # 0002:0004ae46+ ArcTextProc
 | 
|---|
| 164 |       # Out
 | 
|---|
| 165 |       # 0        1         2         3         4         5         6
 | 
|---|
| 166 |       # 123456789012345678901234567890123456789012345678901234567890
 | 
|---|
| 167 |       #  Address         Publics by Value
 | 
|---|
| 168 |       #  0000:00000000  Imp  WinEmptyClipbrd      (PMWIN.733)
 | 
|---|
| 169 |       #  0002:0001ED40       __towlower_dummy
 | 
|---|
| 170 |       if (/^([[:xdigit:]]+):([[:xdigit:]]+)[+*]?\s+(\w+)$/) {
 | 
|---|
| 171 |         $segnum = $1;
 | 
|---|
| 172 |         $offset = $2;
 | 
|---|
| 173 |         my $sym = $3;
 | 
|---|
| 174 | 
 | 
|---|
| 175 |         my $seginfo;
 | 
|---|
| 176 |         if (defined($segsinfo{$1})) {
 | 
|---|
| 177 |           $seginfo = $segsinfo{$1};
 | 
|---|
| 178 |         }
 | 
|---|
| 179 |         else {
 | 
|---|
| 180 |           $seginfo = {max_offset => 0,
 | 
|---|
| 181 |                       symcnt => 0};
 | 
|---|
| 182 |         }
 | 
|---|
| 183 | 
 | 
|---|
| 184 |         my $n = hex $offset;
 | 
|---|
| 185 |         $seginfo->{max_offset} = $n if $n > $seginfo->{max_offset};
 | 
|---|
| 186 |         $seginfo->{symcnt}++;
 | 
|---|
| 187 | 
 | 
|---|
| 188 |         $segsinfo{$1} = $seginfo;
 | 
|---|
| 189 | 
 | 
|---|
| 190 |         $segaddr = "$segnum:$offset";
 | 
|---|
| 191 | 
 | 
|---|
| 192 |         $imp = $segnum eq '0000' ? 'Imp' : '';
 | 
|---|
| 193 | 
 | 
|---|
| 194 |         # Convert C++ symbols to something mapsym will accept
 | 
|---|
| 195 | 
 | 
|---|
| 196 |         $_ = $sym;
 | 
|---|
| 197 | 
 | 
|---|
| 198 |         # s/\bIdle\b/    /;     # Drop Idle keyword
 | 
|---|
| 199 |         s/\(.*\).*$//;          # Drop (... tails
 | 
|---|
| 200 | 
 | 
|---|
| 201 |         s/::~/_x/;              # Replace ::~ with _x
 | 
|---|
| 202 |         s/::/_/;                # Replace :: with _
 | 
|---|
| 203 | 
 | 
|---|
| 204 |         s/[<,]/_/g;             # Replace < and , with _
 | 
|---|
| 205 |         s/[>]//g;               # Replace > with nothing
 | 
|---|
| 206 |         s/[\[\]]//g;            # Replace [] with nothing
 | 
|---|
| 207 |         s/_*$//;                # Drop trailing _
 | 
|---|
| 208 |         s/\W+\w//;              # Drop leading keywords (including Idle)
 | 
|---|
| 209 | 
 | 
|---|
| 210 |         # Drop leading and trailing _ to match source code
 | 
|---|
| 211 | 
 | 
|---|
| 212 |         s/^_//;                 # Drop leading _ (cdecl)
 | 
|---|
| 213 |         s/_$//;                 # Drop trailing _ (watcall)
 | 
|---|
| 214 | 
 | 
|---|
| 215 |         # Prune to avoid mapsym overflows
 | 
|---|
| 216 |         if ($mapid =~ /libc06/) {
 | 
|---|
| 217 |           # 0001:000b73e0  __ZNSt7codecvtIcc11__mbstate_tEC2Ej
 | 
|---|
| 218 |           # next if / [0-9A-F]{4}:[0-9A-F]{8} {7}S/;
 | 
|---|
| 219 |           next if /\b__Z/;              # Prune libstdc++
 | 
|---|
| 220 |         }
 | 
|---|
| 221 | 
 | 
|---|
| 222 |         if (!$symcnt) {
 | 
|---|
| 223 |           print WRKFILE "\n";
 | 
|---|
| 224 |           if ($is32bit) {
 | 
|---|
| 225 |             print WRKFILE " Address         Publics by Value\n";
 | 
|---|
| 226 |           } else {
 | 
|---|
| 227 |             print WRKFILE " Address     Publics by Value\n";
 | 
|---|
| 228 |           }
 | 
|---|
| 229 |         }
 | 
|---|
| 230 | 
 | 
|---|
| 231 |         printf WRKFILE $symfmt, $segaddr, $imp, $_;
 | 
|---|
| 232 |         $symcnt++;
 | 
|---|
| 233 |       }
 | 
|---|
| 234 |     } # if addresses
 | 
|---|
| 235 | 
 | 
|---|
| 236 |   } # while lines
 | 
|---|
| 237 | 
 | 
|---|
| 238 |   close MAPFILE;
 | 
|---|
| 239 | 
 | 
|---|
| 240 |   # Generate dummy symbols as needed
 | 
|---|
| 241 | 
 | 
|---|
| 242 |   my @keys = sort keys %segsinfo;
 | 
|---|
| 243 |   if (@keys) {
 | 
|---|
| 244 |     my $maxseg = pop @keys;
 | 
|---|
| 245 |     @keys = '0000'..$maxseg;
 | 
|---|
| 246 |   }
 | 
|---|
| 247 | 
 | 
|---|
| 248 |   foreach $segnum (@keys) {
 | 
|---|
| 249 |     if ($segnum != 0) {
 | 
|---|
| 250 |       my $seginfo;
 | 
|---|
| 251 |       if (defined($segsinfo{$segnum})) {
 | 
|---|
| 252 |         $seginfo = $segsinfo{$segnum};
 | 
|---|
| 253 |       }
 | 
|---|
| 254 |       else {
 | 
|---|
| 255 |         $seginfo = {max_offset => 0,
 | 
|---|
| 256 |                     symcnt => 0};
 | 
|---|
| 257 |       }
 | 
|---|
| 258 |       if ($seginfo->{symcnt} == 0) {
 | 
|---|
| 259 |         warn "Segment $segnum has no symbols - generating dummy symbol\n";
 | 
|---|
| 260 |         $_ = "Seg${segnum}_dummy";
 | 
|---|
| 261 |         if ($is32bit) {
 | 
|---|
| 262 |           $segaddr = "$segnum:00010000";
 | 
|---|
| 263 |         } else {
 | 
|---|
| 264 |           $segaddr = "$segnum:0000";
 | 
|---|
| 265 |         }
 | 
|---|
| 266 |         $imp = '';
 | 
|---|
| 267 |         printf WRKFILE $symfmt, $segaddr, $imp, $_;
 | 
|---|
| 268 |         $symcnt++;
 | 
|---|
| 269 |       } elsif ($is32bit && $seginfo->{max_offset} < 0x10000) {
 | 
|---|
| 270 |         warn "32 bit segment $segnum is smaller than 64K - generating dummy symbol\n";
 | 
|---|
| 271 |         $_ = "Seg${segnum}_dummy";
 | 
|---|
| 272 |         $segaddr = "$segnum:00010000";
 | 
|---|
| 273 |         $imp = '';
 | 
|---|
| 274 |         printf WRKFILE $symfmt, $segaddr, $imp, $_;
 | 
|---|
| 275 |         $symcnt++;
 | 
|---|
| 276 |       }
 | 
|---|
| 277 |     }
 | 
|---|
| 278 |   } # foreach
 | 
|---|
| 279 | 
 | 
|---|
| 280 |   close WRKFILE;
 | 
|---|
| 281 | 
 | 
|---|
| 282 |   die "Can not locate module name.  $g_mapfile is probably not a Watcom map file\n" if !defined($modname);
 | 
|---|
| 283 | 
 | 
|---|
| 284 |   my $symfile = "$mapid.sym";
 | 
|---|
| 285 |   unlink $symfile || die "unlink $symfile $!" if -f $symfile;
 | 
|---|
| 286 | 
 | 
|---|
| 287 |   warn "Processed $segcnt segments and $symcnt symbols for $modname\n";
 | 
|---|
| 288 | 
 | 
|---|
| 289 |   system("mapsym $g_wrkfile");
 | 
|---|
| 290 | 
 | 
|---|
| 291 | } # mapsym
 | 
|---|
| 292 | 
 | 
|---|
| 293 | #=== scan_args(cmdLine) Scan command line ===
 | 
|---|
| 294 | 
 | 
|---|
| 295 | sub scan_args {
 | 
|---|
| 296 | 
 | 
|---|
| 297 |   getopts('dhtvV', \%g_opts) || &usage;
 | 
|---|
| 298 | 
 | 
|---|
| 299 |   &help if $g_opts{h};
 | 
|---|
| 300 | 
 | 
|---|
| 301 |   if ($g_opts{V}) {
 | 
|---|
| 302 |     print "$g_cmdname v$g_version";
 | 
|---|
| 303 |     exit;
 | 
|---|
| 304 |   }
 | 
|---|
| 305 | 
 | 
|---|
| 306 |   my $arg;
 | 
|---|
| 307 | 
 | 
|---|
| 308 |   for $arg (@ARGV) {
 | 
|---|
| 309 |     my @maps = glob($arg);
 | 
|---|
| 310 |     usage("File $arg not found") if @maps == 0;
 | 
|---|
| 311 |     push @g_mapfiles, @maps;
 | 
|---|
| 312 |   } # for arg
 | 
|---|
| 313 | 
 | 
|---|
| 314 | } # scan_args
 | 
|---|
| 315 | 
 | 
|---|
| 316 | #=== help() Display scan_args usage help exit routine ===
 | 
|---|
| 317 | 
 | 
|---|
| 318 | sub help {
 | 
|---|
| 319 | 
 | 
|---|
| 320 |   print <<EOD;
 | 
|---|
| 321 | Generate .sym file for Watcom map files.
 | 
|---|
| 322 | Generates temporary map file reformatted for mapsym and
 | 
|---|
| 323 | invokes mapsym to process this map file.
 | 
|---|
| 324 | 
 | 
|---|
| 325 | Usage: $g_cmdname [-d] [-h] [-v] [-V] mapfile...
 | 
|---|
| 326 |  -d      Display debug messages
 | 
|---|
| 327 |  -h      Display this message
 | 
|---|
| 328 |  -v      Display progress messages
 | 
|---|
| 329 |  -V      Display version
 | 
|---|
| 330 | 
 | 
|---|
| 331 |  mapfile List of map files to process
 | 
|---|
| 332 | EOD
 | 
|---|
| 333 | 
 | 
|---|
| 334 |   exit 255;
 | 
|---|
| 335 | 
 | 
|---|
| 336 | } # help
 | 
|---|
| 337 | 
 | 
|---|
| 338 | #=== usage(message) Report Scanargs usage error exit routine ===
 | 
|---|
| 339 | 
 | 
|---|
| 340 | sub usage {
 | 
|---|
| 341 | 
 | 
|---|
| 342 |   my $msg = shift;
 | 
|---|
| 343 |   print "\n$msg\n" if $msg;
 | 
|---|
| 344 | print <<EOD;
 | 
|---|
| 345 | 
 | 
|---|
| 346 | Usage: $g_cmdname [-d] [-h] [-v] [-V] mapfile...
 | 
|---|
| 347 | EOD
 | 
|---|
| 348 |   exit 255;
 | 
|---|
| 349 | 
 | 
|---|
| 350 | } # usage
 | 
|---|
| 351 | 
 | 
|---|
| 352 | #==========================================================================
 | 
|---|
| 353 | #=== SkelFunc standards - Delete unused - Move modified above this mark ===
 | 
|---|
| 354 | #==========================================================================
 | 
|---|
| 355 | 
 | 
|---|
| 356 | #=== verbose_msg(message) Display message if verbose ===
 | 
|---|
| 357 | 
 | 
|---|
| 358 | sub verbose_msg {
 | 
|---|
| 359 |   if ($g_opts{v}) {
 | 
|---|
| 360 |     my $msg = shift;
 | 
|---|
| 361 |     if (defined $msg) {
 | 
|---|
| 362 |       print STDOUT "$msg\n";
 | 
|---|
| 363 |     } else {
 | 
|---|
| 364 |       print STDOUT "\n";
 | 
|---|
| 365 |     }
 | 
|---|
| 366 |   }
 | 
|---|
| 367 | } # verbose_msg
 | 
|---|
| 368 | 
 | 
|---|
| 369 | #==========================================================================
 | 
|---|
| 370 | #=== SkelPerl standards - Delete unused - Move modified above this mark ===
 | 
|---|
| 371 | #==========================================================================
 | 
|---|
| 372 | 
 | 
|---|
| 373 | #=== fatal(message) Report fatal error and exit ===
 | 
|---|
| 374 | 
 | 
|---|
| 375 | sub fatal {
 | 
|---|
| 376 |   my $msg = shift;
 | 
|---|
| 377 |   print "\n";
 | 
|---|
| 378 |   print STDERR "$g_cmdname: $msg\a\n";
 | 
|---|
| 379 |   exit 254;
 | 
|---|
| 380 | 
 | 
|---|
| 381 | } # fatal
 | 
|---|
| 382 | 
 | 
|---|
| 383 | #=== set_cmd_name() Set $g_cmdname to script name less path and extension ===
 | 
|---|
| 384 | 
 | 
|---|
| 385 | sub set_cmd_name {
 | 
|---|
| 386 |   $g_cmdname = $0;
 | 
|---|
| 387 |   $g_cmdname = basename($g_cmdname);
 | 
|---|
| 388 |   $g_cmdname =~ s/\.[^.]*$//;           # Chop ext
 | 
|---|
| 389 | 
 | 
|---|
| 390 | } # set_cmd_name
 | 
|---|
| 391 | 
 | 
|---|
| 392 | #=== get_tmp_dir() Get TMP dir name with trailing backslash, set Gbl. ===
 | 
|---|
| 393 | 
 | 
|---|
| 394 | sub get_tmp_dir {
 | 
|---|
| 395 | 
 | 
|---|
| 396 |   $g_tmpdir = File::Spec->tmpdir();
 | 
|---|
| 397 |   die "Need to have TMP or TMPDIR or TEMP defined" unless $g_tmpdir;
 | 
|---|
| 398 | 
 | 
|---|
| 399 | } # get_tmp_dir
 | 
|---|
| 400 | 
 | 
|---|
| 401 | # The end
 | 
|---|