#! perl -w use strict; use IO::File; use File::Basename; use Cwd; use Getopt::Long; use File::Path; my $GLOBAL_TAG = 'Global'; my $MIFAULT_HEADER = 'mifault_wrap.h'; main(); sub usage { $0 = basename($0); return <{ix_list}}, { spec => $dir, include => 1 }); $OPT->{include} = 1; } elsif ($option eq 'exclude') { push(@{$OPT->{ix_list}}, { spec => $dir, exclude => 1 }); } else { die; } $OPT->{code_gen} = 1; } sub GenerateOptionHandler { my $uses_arg = shift; my $tag = shift; return sub { my $option = shift || die; my $arg = shift; if ($uses_arg) { die if !$arg; } else { die "$option got \"$arg\"" if $arg && ($arg != 1); $arg = 1; } $OPT->{$tag} = 1 if $tag; $OPT->{$option} = $arg; } } sub GenerateListOptionHandler { my $tag = shift; return sub { my $option = shift || die; my $arg = shift || die "Missing argument for option $option"; $OPT->{$tag} = 1 if $tag; push(@{$OPT->{$option}}, $arg); } } sub LookupOptionHandler { my $option = shift || die; my $arg = shift || die; if ($arg =~ /^(\d+)\@(.+)$/) { die if $OPT->{list}; push(@{$OPT->{lookup}}, { line => $1, file => $2 }); } elsif ($arg =~ /^\@(.+)$/) { die if $OPT->{list}; push(@{$OPT->{lookup}}, { file => $1 }); } elsif ($arg =~ /^\*$/) { die if $OPT->{list}; die if $OPT->{lookup}; $OPT->{list} = 1; } else { die if $OPT->{list}; push(@{$OPT->{lookup}}, { func => $arg }); } $OPT->{query} = 1; } sub main { if (!GetOptions({ verbose => GenerateOptionHandler(0), help => GenerateOptionHandler(0), skip => GenerateOptionHandler(0, 'code_gen'), noscan => GenerateOptionHandler(0, 'code_gen'), exe => GenerateOptionHandler(1), # force => GenerateOptionHandler(0, 'code_gen'), # export => GenerateOptionHandler(1, 'code_gen'), dll => GenerateOptionHandler(1, 'code_gen'), out => GenerateOptionHandler(1, 'code_gen'), code => GenerateOptionHandler(1, 'code_gen'), header => GenerateOptionHandler(1, 'code_gen'), preheader => GenerateOptionHandler(1, 'code_gen'), sources => GenerateOptionHandler(1, 'code_gen'), wrap => GenerateListOptionHandler('code_gen'), publish => GenerateListOptionHandler('code_gen'), addinc => GenerateListOptionHandler('code_gen'), include => \&IncludeExcludeOptionHandler, exclude => \&IncludeExcludeOptionHandler, lookup => \&LookupOptionHandler, }, 'verbose|v', 'help|h|?', 'skip', 'noscan', 'exe|executable|e=s', # 'force|f', # 'export=s', 'dll=s', 'out|output|o=s', 'code|c=s', 'header=s', 'preheader=s', 'sources=s', 'wrap=s', 'publish=s', 'addinc=s', 'include|i=s@', 'exclude|x=s@', 'lookup|l=s@', )){ die usage(); } # Check arguments die usage() if $OPT->{help}; die usage() if !($OPT->{query} xor $OPT->{code_gen}); $OPT->{wrap} = ExpandList("Global Wrapper", $OPT->{wrap}) if $OPT->{wrap}; $OPT->{publish} = ExpandList("Publish", $OPT->{publish}) if $OPT->{publish}; if ($OPT->{code_gen}) { die usage() if (!$OPT->{exe}); die usage() if (!$OPT->{out}); die usage() if (!$OPT->{code}); if (! -f $OPT->{exe}) { die "Executable \"$OPT->{exe}\" does not exist\n"; } if (!$OPT->{noscan} and !$OPT->{include} ) { die "Must specify at least one include directory when scanning source code\n"; } if (! $OPT->{dll} ) { # ISSUE-2002/07/15-daniloa -- Problems with long DLL names? # Magellan appears to have a problem if the DLL name is of # the form: # $OPT->{dll} = fileparse(lc($OPT->{exe}), '\.exe').'_mifault.dll'; # Therefore, we use a short and sweet default: $OPT->{dll} = 'wrap.dll'; } } if ($OPT->{query}) { die usage() if (!$OPT->{exe}); } my $bin_file = $OPT->{exe}; my $out_dir = $OPT->{out}; my $code_dir = $OPT->{code}; # Generate DB from EXE/PDB print "Generating DB\n"; my $DB = GenerateMageDB($bin_file); if ($OPT->{query}) { DoQuery($DB, $OPT->{list}, $OPT->{lookup}); } elsif ($OPT->{code_gen}) { DoCodeGen($DB, $bin_file, $out_dir, $code_dir, $OPT->{ix_list}); } else { die; } } sub DoQuery { my $DB = shift || die; my $list = shift; my $lookup = shift; die if !($list xor $lookup); # assertion if ($list) { PrintMageDB($DB); } else { die if !$lookup; # same assertion as above foreach my $func (@$lookup) { if ($func->{func}) { my $F = LookupFuncByName($DB, $func->{func}); PrintFunc($F) if $F; } elsif ($func->{line}) { my $F = LookupFuncByLine($DB, CanonicalizeFileName($func->{file}), $func->{line}); PrintFunc($F) if $F; } else { die if !$func->{file}; # assert PrintFuncsFromFile($DB, CanonicalizeFileName($func->{file})); } } } } sub DoCodeGen { my $DB = shift || die; my $bin_file = shift || die; my $out_dir = shift || die; my $code_dir = shift || die; my $ix_list = shift || die; $out_dir = CreateAndCanonicalizeDirName($out_dir); $code_dir = CreateAndCanonicalizeDirName($code_dir); my $code_base = basename($code_dir); goto skip if $OPT->{skip}; # Always replace --> this makes --force obsolete rmtree($out_dir); rmtree($code_dir); mkpath_always($out_dir); mkpath_always($code_dir); # Scan source code files based on DB and includes/excludes. my $src_file_list; my $wrap_data; if (!$OPT->{noscan}) { $src_file_list = GenerateSourceFileList($DB, $ix_list); if (!$src_file_list) { die "No source code files generated from include/exclude\n"; } foreach my $src_file (@$src_file_list) { # We require that the dirs for source code be the ones that # the PDB file specifies. my $data = ScanSourceFile($src_file); push (@$wrap_data, @$data) if $data; } } if (($#$wrap_data < 0) and !$OPT->{wrap}) { die "No functions found to wrap\n"; } my $response_file = $code_dir.'\\'.$code_base.'.resp'; print "Generating sword response file\n"; GenerateResponseFile($response_file, $bin_file, $out_dir, $code_dir, $DB, $wrap_data); print "Invoking Sword\n"; DriveSword($response_file); skip: # FUTURE-2002/07/15-daniloa -- Configuration filenames for autogen code # It may be useful to have the filenames used for the autogen code be # configurable. my $files = { source => { old => $code_dir.'\\'.$code_base.'.cpp', new => $code_dir.'\\'.$code_base.'.MiFault.cpp', }, main => { old => $code_dir.'\\'.$code_base.'Main.cpp', new => $code_dir.'\\'.$code_base.'Main.MiFault.cpp', }, inc => { old => $code_dir.'\\'.$code_base.'.h', new => $code_dir.'\\'.$code_base.'.MiFault.h', }, def => { old => $code_dir.'\\'.$code_base.'.def', new => $code_dir.'\\'.$code_base.'.MiFault.def', }, }; print "Modifying wrapper wrappers\n"; ModifyWrapWrappers($files->{source}->{old}, $files->{source}->{new}, $code_base); print "Modifying wrapper main\n"; ModifyWrapMain($files->{main}->{old}, $files->{main}->{new}, $code_base); print "Modifying wrapper include\n"; ModifyWrapInclude($files->{inc}->{old}, $files->{inc}->{new}); print "Modifying wrapper exports\n"; ModifyWrapDef($files->{def}->{old}, $files->{def}->{new}, $code_base); if ($OPT->{sources}) { print "Generating wrapper makefile/sources files for Windows build\n"; GenerateWrapSourcesFile($code_dir, $code_base, $OPT->{sources}, $OPT->{addinc}); } } sub GenerateWrapSourcesFile { my $dir = shift || die; my $base = shift || die; my $inc_dir = shift || die; my $add_inc_path = shift; my $makefile = $dir.'\\'.'makefile'; my $sources = $dir.'\\'.'sources'; my $fh = new IO::File; $fh->open(">$makefile") || die ERROR_CANNOT_OPEN_FOR_OUTPUT($makefile)."\n"; print $fh <open(">$sources") || die ERROR_CANNOT_OPEN_FOR_OUTPUT($sources)."\n"; my $targetname = fileparse(lc($OPT->{dll}), '\.dll'); my $inc_sep = "; \\\n "; $add_inc_path = $add_inc_path ? $inc_sep.join($inc_sep, @{$add_inc_path}) : ''; print $fh <open("<$filename") || die ERROR_CANNOT_OPEN_FOR_INPUT($filename)."\n"; my $line; my $n; my $semi; my $tag; my $func; my $found_mark; my $found_func; my $found_semi; while ($line = $fh->getline()) { $n++; if ($line =~ /$pattern/) { $tag = $1; $func = $2; print "Found: \"$tag\", \"$func\" at line $n of \"$filename\"\n"; if ($found_mark) { die "Found a mark before finding the previous mark's target\n"; } $found_mark = $n; } if ($found_mark) { if ($line =~ /\b$func\b/) { $found_func = $n; } if ($line =~ /;/) { $found_semi = $n; $found_mark = 0; push(@$res, { func => $func, tag => $tag, file => $filename, line => $found_semi, mark_line => $found_mark, func_line => $found_func, semi_line => $found_semi, }); #print "MARK: /Wrap $func Wrap_".$tag."_"."$func wrap.dll ... $filename $n\n"; } } } return $res; } sub DriveSword { my $response_file = shift || die; my $status = system("sword \@$response_file"); my $code = $status / 256; if ($code) { if ($code == 1) { print "-" x 79,"\n"; print "WARNING: sword had warnings! Please review them.\n"; print "-" x 79,"\n"; } else { die "Sword failed with exit code $code\n"; } } } sub GenerateResponseFile { my $filename = shift || die; my $bin_file = shift || die; my $out_dir = shift || die; my $code_dir = shift || die; my $DB = shift || die; my $scan_data = shift || die; $bin_file = CanonicalizeFileName($bin_file); mkpath(dirname($filename)); my $fh = new IO::File; $fh->open(">$filename") || die ERROR_CANNOT_OPEN_FOR_OUTPUT($filename)."\n"; print $fh <{file}, $M->{line}); if (!$F) { die "Could not find caller for: \"$M->{func}\" at line $M->{line} of \"$M->{file}\""; } print $fh "/Wrap $M->{func} Wrap_$M->{tag}_$M->{func} $OPT->{dll} $F->{name} $M->{file} $M->{line}\n"; } foreach my $func (@{$OPT->{wrap}}) { print $fh "/Wrap $func Wrap_".$GLOBAL_TAG."_$func $OPT->{dll}\n"; } foreach my $func (@{$OPT->{publish}}) { print $fh "/Publish $func\n"; } $fh->close; } sub GenerateMageDB { my $bin = shift || die; my $DB = {}; my $template = [ { label => 'Source File' , key => 'path' , pattern => '.+' }, { label => 'Starting Line', key => 'start', pattern => '\d+' }, { label => 'Ending Line' , key => 'end' , pattern => '\d+' }, ]; my @lines = `mage /s $bin /l Functions`; my $code = $? / 256; if ($code) { die "Mage failed with exit code $code\n"; } foreach my $line (@lines) { if ($line =~ /^Function: (\S+)$/) { my $func = $1; if ($DB->{by_name}->{$func}) { die "Multiple instances of function \"$func\" in $bin\n"; } $DB->{by_name}->{$func} = {}; } elsif ($line =~ /^Function:/) { die "Mage output parse error"; } } #map { print "Function: $_\n"; } sort keys %{$DB->{by_name}}; my $F; my $record; my $args = join(' ', keys %{$DB->{by_name}}); @lines = `mage /s $bin /f $args`; $code = $? / 256; if ($code) { die "Mage failed with exit code $code\n"; } foreach my $line (@lines) { if ($line =~ /^Name: (\S+)$/) { my $name = $1; UpdateFunc($DB, $F, $template, $record); $F = { name => $name }; $record = $line; next; } foreach my $item (@$template) { my $label = $item->{label}; my $key = $item->{key}; my $pattern = $item->{pattern}; if ($line =~ /^($label): ($pattern)$/) { if ($F->{$key}) { die "Multiple instances of \"$label\" for function \"$F->{name}\"\n"; } $F->{$key} = $2; last; } } $record .= $line; } UpdateFunc($DB, $F, $template, $record); sub UpdatePath { my $DB = shift || die; my $F = shift || die; my $list; $list = $DB->{by_path}->{lc($F->{path})}; if (!$list) { $list = [ $F ]; } else { my $loc = $#$list + 1; for (my $i = 0; $i <= $#$list; $i++) { if ($list->[$i]->{start} > $F->{start}) { $loc = $i; last; } } $list = [ (@$list)[0..$loc-1], $F, (@$list)[$loc..$#$list] ]; } $DB->{by_path}->{lc($F->{path})} = $list; } sub UpdateFunc { my $DB = shift || die; my $F = shift; my $template = shift || die; my $record = shift; if ($F) { if (ValidateFunc($F, $template, $record)) { $DB->{by_name}->{$F->{name}} = $F; UpdatePath($DB, $F); } else { delete $DB->{by_name}->{$F->{name}}; } } } sub ValidateFunc { my $F = shift || die; my $template = shift || die; my $record = shift; if (! $F->{path} && ! $F->{start} && ! $F->{end}) { return 0; } foreach my $item (@$template) { my $label = $item->{label}; my $key = $item->{key}; if (! $F->{$key}) { die "Missing \"$label\" for function \"$F->{name}\"\n". "DATA:\n".$record."\n"; } } return 1; } return $DB; } sub PrintMageDB { my $DB = shift || die; foreach my $func (sort keys %{$DB->{by_name}}) { my $F = $DB->{by_name}->{$func}; PrintFunc($F); } } sub PrintFuncsFromFile { my $DB = shift || die; my $path = shift || die; $path = lc($path); my $list; $list = $DB->{by_path}->{$path}; die "Invalid path: \"$path\"" if !$list; foreach my $F (@$list) { PrintFunc($F); } } sub LookupFuncByName { my $DB = shift || die; my $name = shift || die; return $DB->{by_name}->{$name}; } sub LookupFuncByLine { my $DB = shift || die; my $path = shift || die; my $line = shift || die; $path = lc($path); my $list; $list = $DB->{by_path}->{$path}; die "Invalid path: \"$path\"" if !$list; foreach my $F (@$list) { return $F if ($F->{start} <= $line && $line <= $F->{end}); } return 0; } sub PrintFunc { my $F = shift || die; print "Function: $F->{name}\n"; print "\tPath: $F->{path}\n"; print "\tDir: ".dirname($F->{path})."\n"; print "\tFile: ".basename($F->{path})."\n"; print "\tRange: [$F->{start}, $F->{end}]\n"; print "\n"; } sub PrintWrapper { my $W = shift || die; print "FOUND: $W->{tag}, $W->{func}\n"; print "START: $W->{start}\n"; print "BODY START$W->{body}BODY_END\n"; print "END: $W->{end}\n"; } sub GetTypedef { my $name = shift || die; my $body = shift || die; my $return_type = ".*"; my $call_conv = ".*"; # The code better have some return type and calling convention. # This should apply to any code we are wrapping, but may not be the case # for assembly routines. my $pattern = < $2, pre => $1, post => $3, }; } else { die "Could not find typedef for: \"$name\"\n"; } } sub GetCall { my $name = shift || die; my $body = shift || die; my $return_type = ".*"; my $call_conv = ".*"; # The code better have some return type and calling convention. # This should apply to any code we are wrapping, but may not be the case # for assembly routines. my $pattern = < $2, pre => $1, post => $3, }; } else { die "Could not find call for: \"$name\"\n"; } } sub ProcessWrapper { my $W = shift || die; my $pfn_t = "FP_TriggeredWrap_$W->{tag}_$W->{func}"; my $pfn = "pfnTriggeredWrap_$W->{tag}_$W->{func}"; my $T = GetTypedef($W->{name}, $W->{body}); my $C = GetCall($W->{name}, $W->{body}); my $template = { 'trigger condition' => { old => < <{pre}$pfn_t$T->{post} $pfn_t $pfn = ($pfn_t) MiFaultLib::Triggered(uFunctionIndex); if ($pfn) DATA }, 'simulation section' => { old => < <{pre}$pfn$C->{post} MiFaultLib::TriggerFinished(); DATA }, }; foreach my $k (keys %$template) { $W->{body} =~ s/$template->{$k}->{old}/$template->{$k}->{new}/m || die "Could not find $k for $W->{name}\n"; } } sub GetNextWrapper { my $left = shift || die; my $W; if ($left =~ /^\/\/\{\{\+([^\}]+)\}\}$/m) { $W->{pre} = $`; $W->{name} = $1; $W->{start} = $&; $left = $'; if ($W->{name} =~ /^Wrap_(.+)_(.+)$/) { $W->{tag} = $1; $W->{func} = $2; } else { die "Improperly named wrapper function: \"$W->{name}\"\n"; } if ($left =~ /^\/\/\{\{\-$W->{name}\}\}$/m) { $W->{body} = $`; $W->{end} = $&; $left = $'; } else { die "End of wrapper not found: \"$W->{name}\"\n"; } #PrintWrapper($W); ProcessWrapper($W); #PrintWrapper($W); } return { found => $W, left => $left }; } sub ModifyWrapWrappers { my $infile = shift || die; my $outfile = shift || die; my $code_base = shift || die; my $fhi = new IO::File; $fhi->open("<$infile") || die ERROR_CANNOT_OPEN_FOR_INPUT($infile)."\n"; my $fho = new IO::File; $fho->open(">$outfile") || die ERROR_CANNOT_OPEN_FOR_OUTPUT($outfile)."\n"; my @lines = $fhi->getlines(); my $file = join('', @lines); $file =~ s/#include \"$code_base.h\"/#include \"$code_base.MiFault.h\"/m || die "Could not replace #include in $infile\n"; my $left = $file; my $found; do { my $ret = GetNextWrapper($left); $left = $ret->{left}; $found = $ret->{found}; if ($found) { print $fho $found->{pre},$found->{start},$found->{body},$found->{end}; } } while ($found); print $fho $left; } sub ModifyWrapMain { my $infile = shift || die; my $outfile = shift || die; my $code_base = shift || die; my $fhi = new IO::File; $fhi->open("<$infile") || die ERROR_CANNOT_OPEN_FOR_INPUT($infile)."\n"; my $fho = new IO::File; $fho->open(">$outfile") || die ERROR_CANNOT_OPEN_FOR_OUTPUT($outfile)."\n"; my @lines = $fhi->getlines(); my $file = join('', @lines); $file =~ s/#include \"$code_base.h\"/#include \"$code_base.MiFault.h\"/m || die "Could not replace #include in $infile\n"; my $module_name = basename($OPT->{exe}); my $template = { '\"switch (dwReason)\" statement' => { old => < < { old => < <{$k}->{old}/$template->{$k}->{new}/m || die "Could not find $k in $infile\n"; } print $fho $file; } sub ModifyWrapInclude { my $infile = shift || die; my $outfile = shift || die; my $fhi = new IO::File; $fhi->open("<$infile") || die ERROR_CANNOT_OPEN_FOR_INPUT($infile)."\n"; my $fho = new IO::File; $fho->open(">$outfile") || die ERROR_CANNOT_OPEN_FOR_OUTPUT($outfile)."\n"; my @lines = $fhi->getlines(); my $file = join('', @lines); my $header = $OPT->{header} ? "#include <$OPT->{header}>" : ''; my $preheader = $OPT->{preheader} ? "#include <$OPT->{preheader}>" : ''; my $template = { 'pragma once' => { old => < < { old => < < $header DATA }, }; foreach my $k (keys %$template) { $file =~ s/$template->{$k}->{old}/$template->{$k}->{new}/m || die "Could not find $k in $infile\n"; } print $fho $file; } sub ModifyWrapDef { my $infile = shift || die; my $outfile = shift || die; my $code_base = shift || die; my $fhi = new IO::File; $fhi->open("<$infile") || die ERROR_CANNOT_OPEN_FOR_INPUT($infile)."\n"; my $fho = new IO::File; $fho->open(">$outfile") || die ERROR_CANNOT_OPEN_FOR_OUTPUT($outfile)."\n"; my @lines = $fhi->getlines(); my $file = join('', @lines); my $old_lib = uc($code_base); my $new_lib = fileparse(uc($OPT->{dll}), '\.DLL'); $file =~ s/LIBRARY \"$old_lib\"/LIBRARY \"$new_lib\"/m || die "Could not replace LIBRARY in $infile\n"; if ($OPT->{export}) { $file .= "$OPT->{export}\n"; } print $fho $file; } sub MatchIncludeExclude { my $ix = shift || die; my $path = shift || die; # We assume the case has been canonicalized. if ($ix->{spec} eq substr($path, 0, length($ix->{spec}))) { return $ix->{include} ? { include => 1 } : { exclude => 1 }; } return 0; } sub GenerateSourceFileList { my $DB = shift || die; my $ix_list = shift || die; my $list; foreach my $file (keys %{$DB->{by_path}}) { my $include; foreach my $ix (@$ix_list) { my $match = MatchIncludeExclude($ix, $file); if ($match) { $include = $match->{include}; } } push (@$list, $file) if $include; } return $list; } sub ExpandList { my $label = shift || die; my $arg = shift || die; my $result; foreach my $w (@{$arg}) { if ($w =~ /^\@(.*)$/) { my $filename = $1; my $fh = new IO::File; $fh->open("<$filename") || die ERROR_CANNOT_OPEN_FOR_INPUT($filename)."\n"; my $file = join('', $fh->getlines()); $file =~ s/#[^\n]*\n//mg; my @f = split(' ', $file); map { $result->{$_} = 1; } @f; } else { $result->{$w} = 1; } } $arg = [ sort keys %$result ]; map { print "$label: $_\n" } @{$arg}; return $arg; } sub mkpath_always { my $dir = shift || die; mkpath($dir); if (! -d $dir) { die "Could not create directory: \"$dir\"\n"; } } sub CreateAndCanonicalizeDirName { my $dir = shift || die; mkpath_always($dir); return CanonicalizeDirName($dir); } ############################################################################### sub ERROR_CANNOT_OPEN_FOR_INPUT { my $filename = shift || die; return "Could not open file for input: \"$filename\""; } sub ERROR_CANNOT_OPEN_FOR_OUTPUT { my $filename = shift || die; return "Could not open file for output: \"$filename\""; }