source: trunk/tools/mapsymw.pl@ 63

Last change on this file since 63 was 63, checked in by Gregg Young, 7 years ago

Updated mapsymw from Steven Levine

  • Property svn:eol-style set to native
File size: 10.3 KB
Line 
1#!perl -w
2# mapsymw - mapsym wrapper Watcom map files
3
4# Copyright (c) 2007-2019 Steven Levine and Associates, Inc.
5# All rights reserved.
6
7# This program is free software licensed under the terms of the GNU
8# General Public License. The GPL Software License can be found in
9# gnugpl2.txt or at http://www.gnu.org/licenses/licenses.html#GPL
10
11# 2007-07-02 SHL Baseline
12# 2008-12-14 SHL Ensure symbols sorted by value - some apps care
13# 2010-05-03 SHL Comments
14# 2010-06-14 SHL Avoid missing C++ symbols
15# 2012-03-19 SHL Segment names must be uppercase for pmdf
16# 2014-06-13 SHL Correct typos
17# 2014-11-01 SHL Generate ibm map files in watcom map file directory
18# 2015-01-31 SHL Use unix slashes to keep kLIBC sh.exe happy
19# 2019-03-02 SHL More C++ logic
20# 2019-03-03 SHL Rework dup logic and sym length limiting
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
28use strict;
29use warnings;
30
31# use Package::Subpackage Options;
32use POSIX qw(strftime);
33use Getopt::Std;
34use File::Basename;
35
36our $g_version = '0.4';
37
38our $g_cmdname;
39our @g_mapfiles; # All map files
40our $g_mapfile; # Current .map file name
41
42&initialize;
43
44our %g_opts;
45
46&scan_args;
47
48print "\n";
49
50foreach $g_mapfile (@g_mapfiles) {
51 $g_mapfile =~ tr|\\|/|; # 2015-01-31 SHL Unix slashes for sh.exe
52 &mapsym;
53}
54
55exit;
56
57# end main
58
59#=== initialize() Initialize globals ===
60
61sub initialize {
62
63 &set_cmd_name;
64
65} # initialize
66
67#=== mapsym() Generate work file, run mapsym on work file ===
68
69sub mapsym {
70
71 # Isolate map file basename
72 my $mapid = basename($g_mapfile);
73 $mapid =~ s/\.[^.]*$//; # Strip ext
74 verbose_msg("\nProcessing $mapid");
75
76 fatal("$g_mapfile does not exist.") if ! -f $g_mapfile;
77
78 open MAPFILE, $g_mapfile or die "open $g_mapfile $!";
79
80 my $g_wrkfile = $g_mapfile . '-ibm';
81 $g_wrkfile =~ s/-watcom-ibm$/-ibm/; # In case renamed from .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 %syms;
92 my $segnum;
93 my $offset;
94 my $segaddr;
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 # First segment - determine address size (16/32 bit)
141 $is32bit = length($offset) == 8;
142 # Output title
143 print WRKFILE "\n";
144 if ($is32bit) {
145 print WRKFILE " Start Length Name Class\n";
146 $segfmt = " %13s 0%8sH %-22s %s\n";
147 $symfmt = " %13s %3s %s\n";
148 } else {
149 print WRKFILE " Start Length Name Class\n";
150 $segfmt = " %9s 0%4sH %-22s %s\n";
151 $symfmt = " %9s %3s %s\n";
152 }
153 }
154
155 $seglen = substr($5, -4) if !$is32bit;
156
157 printf WRKFILE $segfmt, $segaddr, $seglen, $segname, $class;
158 $segcnt++;
159 }
160 } # if segments
161
162 if ($state eq 'addresses') {
163 # In
164 # Address Symbol
165 # 0002:0004ae46+ ArcTextProc
166 # 0002:0d11+ void near IoctlAudioCapability( __2bd9g9REQPACKET far *, short unsigned )
167 # Out
168 # 0 1 2 3 4 5 6
169 # 123456789012345678901234567890123456789012345678901234567890
170 # Address Publics by Value
171 # 0000:00000000 Imp WinEmptyClipbrd (PMWIN.733)
172 # 0002:0001ED40 __towlower_dummy
173 if (/^([[:xdigit:]]+):([[:xdigit:]]+)[+*]?\s+(.+)$/) {
174 $segnum = $1;
175 $offset = $2;
176 my $sym = $3;
177
178 my $seginfo;
179 if (defined($segsinfo{$segnum})) {
180 $seginfo = $segsinfo{$segnum};
181 } else {
182 $seginfo = {max_offset => 0,
183 symcnt => 0};
184 }
185
186 my $n = hex $offset;
187 # Remember max symbol offset
188 $seginfo->{max_offset} = $n if $n > $seginfo->{max_offset};
189 $seginfo->{symcnt}++;
190
191 $segsinfo{$segnum} = $seginfo;
192
193 $segaddr = "$segnum:$offset";
194
195 # Convert C++ symbols to something mapsym will accept
196 warn "\n$sym\n";
197
198 $_ = $sym;
199
200 s/\s+\(.*\)//; # Drop (...) comments preceeded by whitespace
201
202 # Make C++ compatible with mapsym
203
204 # Drop keywords and decorations
205 s/\b(near|int|short|unsigned|long|void|char|const|wchar_t)\b\s*[*&]?\s*//g;
206
207 # s/<[^<>]+>//g; # Replace < > templates with nothing
208
209 s/std::\s*/std/g; # Replace std:: with std
210 s/::~/__x/g; # Replace destructor ::~ with __x
211 s/::/__/g; # Replace :: with __
212
213 s/[\[\]()<>,]/ /g; # Replace [ ] < > and , with spaces
214 s/\s+/_/g; # Replace whitespace with single _
215 s/__+/_/g; # Replace multiple _ with single _
216
217 # Drop leading and trailing _ to match source code
218
219 s/^_//; # Drop leading _ (cdecl)
220 s/_$//; # Drop trailing _ (watcall)
221
222 $_ = substr($_, 0, 63) if length($_) > 63; # limit
223
224 warn "\n$_\n";
225
226 # Prune some libc symbols to avoid mapsym overflows
227 if ($mapid =~ /libc06/) {
228 # 0001:000b73e0 __ZNSt7codecvtIcc11__mbstate_tEC2Ej
229 # next if / [0-9A-F]{4}:[0-9A-F]{8} {7}S/;
230 next if /\b__Z/; # Prune libstdc++
231 }
232
233 if (!$symcnt) {
234 # First symbol - output title
235 print WRKFILE "\n";
236 if ($is32bit) {
237 print WRKFILE " Address Publics by Value\n";
238 } else {
239 print WRKFILE " Address Publics by Value\n";
240 }
241 }
242
243 $syms{$segaddr} = $_;
244
245 $symcnt++;
246 }
247 } # if addresses
248
249 } # while lines
250
251 close MAPFILE;
252
253 # Sort segments
254
255 my @keys = sort keys %segsinfo;
256 if (@keys) {
257 my $maxseg = pop @keys;
258 @keys = '0000'..$maxseg;
259 }
260
261 # Generate dummy symbols for 32-bit segments smaller than 64KB
262
263 foreach $segnum (@keys) {
264 if ($segnum != 0) {
265 my $seginfo;
266 if (defined($segsinfo{$segnum})) {
267 $seginfo = $segsinfo{$segnum};
268 } else {
269 $seginfo = {max_offset => 0,
270 symcnt => 0};
271 }
272 if ($seginfo->{symcnt} == 0) {
273 warn "Segment $segnum has no symbols - generating dummy symbol\n";
274 $_ = "SEG${segnum}_dummy";
275 if ($is32bit) {
276 $segaddr = "$segnum:00010000";
277 } else {
278 $segaddr = "$segnum:0000";
279 }
280 $syms{$segaddr} = $_;
281 $symcnt++;
282 } elsif ($is32bit && $seginfo->{max_offset} < 0x10000) {
283 warn "32 bit segment $segnum is smaller than 64K - generating dummy symbol\n";
284 $_ = "SEG${segnum}_dummy";
285 $segaddr = "$segnum:00010000";
286 $syms{$segaddr} = $_;
287 $symcnt++;
288 }
289 }
290 } # foreach $segnum
291
292 # Generate symbols by value listing
293 # FIXME to warn if may overflow 64K limit
294
295 @keys = sort keys %syms;
296 my %used;
297 foreach $segaddr (@keys) {
298 my $sym = $syms{$segaddr};
299 my $imp = substr($segaddr, 0, 4) eq '0000' ? 'Imp' : '';
300 # Avoid dups
301 if (defined $used{$sym}) {
302 my $seq = $used{$sym};
303 $seq++;
304 $used{$sym} = $seq;
305 $sym = "${sym}_$seq";
306 } else {
307 $used{$sym} = 0;
308 }
309 printf WRKFILE $symfmt, $segaddr, $imp, $sym;
310 } # foreach
311
312 close WRKFILE;
313
314 die "Can not locate module name. $g_mapfile is probably not a Watcom map file\n" if !defined($modname);
315
316 my $symfile = "$mapid.sym";
317 unlink $symfile || die "unlink $symfile $!" if -f $symfile;
318
319 warn "Processed $segcnt segments and $symcnt symbols for $modname\n";
320
321 system("mapsym $g_wrkfile");
322
323} # mapsym
324
325#=== scan_args(cmdLine) Scan command line ===
326
327sub scan_args {
328
329 getopts('dhtvV', \%g_opts) || &usage;
330
331 &help if $g_opts{h};
332
333 if ($g_opts{V}) {
334 print "$g_cmdname v$g_version";
335 exit;
336 }
337
338 my $arg;
339
340 for $arg (@ARGV) {
341 my @maps = glob($arg);
342 usage("File $arg not found") if @maps == 0;
343 push @g_mapfiles, @maps;
344 } # for arg
345
346} # scan_args
347
348#=== help() Display scan_args usage help exit routine ===
349
350sub help {
351
352 print <<EOD;
353Generate .sym file for Watcom map files.
354Generates temporary map file reformatted for mapsym and
355invokes mapsym to process this map file.
356
357Usage: $g_cmdname [-d] [-h] [-v] [-V] mapfile...
358 -d Display debug messages
359 -h Display this message
360 -v Display progress messages
361 -V Display version
362
363 mapfile List of map files to process
364EOD
365
366 exit 255;
367
368} # help
369
370#=== usage(message) Report Scanargs usage error exit routine ===
371
372sub usage {
373
374 my $msg = shift;
375 print "\n$msg\n" if $msg;
376print <<EOD;
377
378Usage: $g_cmdname [-d] [-h] [-v] [-V] mapfile...
379EOD
380 exit 255;
381
382} # usage
383
384#==========================================================================
385#=== SkelFunc standards - Delete unused - Move modified above this mark ===
386#==========================================================================
387
388#=== verbose_msg(message) Display message if verbose ===
389
390sub verbose_msg {
391 if ($g_opts{v}) {
392 my $msg = shift;
393 if (defined $msg) {
394 print STDOUT "$msg\n";
395 } else {
396 print STDOUT "\n";
397 }
398 }
399} # verbose_msg
400
401#==========================================================================
402#=== SkelPerl standards - Delete unused - Move modified above this mark ===
403#==========================================================================
404
405#=== fatal(message) Report fatal error and exit ===
406
407sub fatal {
408 my $msg = shift;
409 print "\n";
410 print STDERR "$g_cmdname: $msg\a\n";
411 exit 254;
412
413} # fatal
414
415#=== set_cmd_name() Set $g_cmdname to script name less path and extension ===
416
417sub set_cmd_name {
418 $g_cmdname = $0;
419 $g_cmdname = basename($g_cmdname);
420 $g_cmdname =~ s/\.[^.]*$//; # Chop ext
421
422} # set_cmd_name
423
424# The end
Note: See TracBrowser for help on using the repository browser.