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