building a windows API database

David Miller compsol at ptd.net
Thu Mar 27 13:39:06 CST 2003


Hi, I have attached the script I am working on for this project.  It is not 
finished.  Currently it will ignore APIs with no name or ordinal 
###  simply because I haven't figured out how to handle them yet.  There 
seems to be a general consensus on wine-devel we don't need a MySQL 
database so I am working toward a text file solution instead.  I do think 
MySQL could be useful though if someone made a nice interface to query the 
database.  Any help, comments, and suggestions are always welcome.  :)

Currently this script will:

locate dlls and dump import and/or export information with dumpbin
cross reference imports in an HTML map
parse the dumped exports and try to determine the status of each API based 
on entries in the wine .spec files.

If you do not have access to perl on a windows system and dumpbin (from 
visual C++) I can provide sample output.


At 11:35 AM 3/25/2003 -0800, you wrote:
>Hi, I am interested in your project of
>having an API database.
>
>I am building it, storing the information
>in a server (MySQL), in order to retreive
>from it any information about apis
>and their relationship with other apis
>and constants.
>
>I don't know if you have something yet,
>and would like to share with you
>the project.
>
>Greetings...
>
>__________________________________________________
>Do you Yahoo!?
>Yahoo! Platinum - Watch CBS' NCAA March Madness, live on your desktop!
>http://platinum.yahoo.com
-------------- next part --------------
#!/usr/bin/perl

#
#
# Version 1.5
#
#
# Scan a specified path for dlls and cross reference imports in imports.html
# Dump all dumpbin /imports and /exports output to imports.txt and exports.txt respectively
# Parse imports.txt and exports.txt and place output in imported_api.html and exported_api.txt
#
#
# I am using ActiveState ActivePerl 5.8 on Windows XP
# 
#
# BUGS
#
# Cannot handle spaces in directory or file names
# Errors are not reported when parsing exports
#
#
# TODO
#
# Format output in a matrix
# Possibly make format selectable on command line, HTML or matrix output


if (scalar @ARGV <=0) {
	usage();
}

sub usage() {
	print("Usage winapi_list.pl <option> <argument>\n\n");
	print("Available options:\n\n");
	print("-p\tSpecify a path to search for dlls.\n");
	print("\tUse with -d (dump) option.\n\n");
	print("-d\tRequires -p option.  Dumps imports or exports.\n");
	print("\tPossible values are imports, exports, and all.\n\n");
	print("-e\tCreate a text file containing a list of APIs\n");
	print("\texported by each dll.\n\n");
	print("-h\tCreate an HTML map cross referencing DLL imports.\n");
	print("\tPossible values are api, noapi, and all.\n\n");
	print("-b\tSpecify the path to the wine spec files and build\n");
	print("\ta database of APIs and whether they are implemented\n");
	print("\tin wine based on the information in the wine .spec files.\n");
	print("\tThis option is not complete, and will ignore APIs with no name.\n");
	}



use Getopt::Std;
getopts ("ep:h:d:b:");

if ($opt_p) {
	$dir=$opt_p;
	find_dlls();
}

if ($opt_d) {
	if ($opt_d ne "imports" & $opt_d ne "exports" & $opt_d ne "all") {
		usage();
		exit 1;
		}
if ($opt_d eq "imports") {
	dump_imports();
	}
if ($opt_d eq "exports") {
	dump_exports();
	}
if ($opt_d eq "all") {
	dump_imports();
	dump_exports();
	}
}

if ($opt_h) {

if ($opt_h ne "api" & $opt_h ne "noapi" & $opt_h ne "all") {
	usage();
	exit 1;
	}

if ($opt_h eq "api") {
	generate_html();	}

if ($opt_h eq "noapi") {
	create_html_noapi();
	}
if ($opt_h eq "all") {
	generate_html();
	create_html_noapi();
	}
}

if ($opt_e) {
	parse_exports();
	}

if ($opt_b) {
	build_export_database();
	}


sub find_dlls() {

print("Generating a list of dlls...\n");

#foreach $dir {
 push @dir,$dir;

 while ($dirs=pop @dir){
   $whereami=`cd`;
   chomp($whereami);
   chomp($dirs);
   chdir $dirs;

   while (<*>) {
	next if ($_ =~ /dllcache/);
      if (-d $_) {
       push @dir,$dirs."\\".$_;
     }

else {
	push (@dlllist, "$_  $dirs\n") if /\.dll$/;
     }

   chdir $whereami;
 }


print("Sorting and converting dlls to lowercase...\n");

@dlllist = map (lc, @dlllist);
@dlllist = sort @dlllist;

}

sub dump_imports {

open (STDOUT2, ">&STDOUT");
print("Dumping imports...\n");
open (STDOUT, "> imports.txt") || die "Unable to open imports.txt for writing!";
	
foreach $line (@dlllist) {
	chomp($line);
	($a,$b)=split(/\s\s+/,$line);
	print STDOUT2 ("Dumping imports for $a\n");
	system("dumpbin /imports $b/$a");
	}
open(STDOUT, ">&STDOUT2");
}


sub dump_exports {

print("Dumping exports...\n");
open (STDOUT2, ">&STDOUT");
open (STDOUT, "> exports.txt") || die "Unable to open exports.txt for writing!";

foreach $line (@dlllist) {
	chomp($line);
	($a,$b)=split(/\s\s+/,$line);
	print STDOUT2 ("Dumping exports for $a\n");
	system("dumpbin /exports $b/$a");
	}
open (STDOUT, ">&STDOUT2");
}
}

#
###### TODO: Rewrite this portion of the code
#

sub create_html_noapi {

open(DATA, "imports.txt") || die "Unable to open imports.txt. Please try winapi_list.pl -p <path> -d imports -h\n";
open(IMPORTS, ">imports.html") || die "Unable to open imports.html for writing!";
print STDOUT2 ("Generating HTML...\n");

while (<DATA>) {
	sort_imports();
	}

sub sort_imports {
	chomp($_);
	s/^\s*(.*?)\s*$/$1/;
	($a,$b,$c,$d,$e)=split(/\s+/,$_);

		if ($d =~ /\.dll$/) {
		($path,$name)=split(/\//,$d);
		#($name,$b)=split(/\./,$name);
		print IMPORTS "<br>\n<li> <A NAME=\"$name\"> $name imports: <br>\n";
	}

	if ($a =~ /\.dll$/) {
		push (@imports, "$a");
	}
	elsif ($a =~ /Summary|DUMPBIN/) {
		@alpha = sort (map (lc, @imports));
		foreach $line (@alpha) {
			#($line,$b)=split(/\./,$line);
			print IMPORTS "<A HREF=\"#$line\">$line</A><br>\n";
		}

		undef @imports;
	}

	else { 
		next;
		}


	}
close(DATA);
close(IMPORTS);

}

sub generate_html {

open(IMPORTS, "> imported_api.html");
print STDOUT2 ("\nParsing import list.  This may take several minutes.\n\n");
open(DATA, "imports.txt") || die "Unable to open imports.txt. Please try winapi_list.pl -p <path> -d imports -h\n";
LINE: while(<DATA>) {
	chomp($_);
	s/^\s*(.*?)\s*$/$1/;
	($a,$b,$c,$d,$e)=split(/\s+/,$_);
	$fullname = $d if ($d =~ /dll|DLL/);
	($path,$name)=split(/\//,$fullname);
	if ($d =~ /dll|DLL/) {
	($name,$ext)=split(/\./,$name);
	print IMPORTS "<br><li> <A NAME=\"$name\"> $name imports: <br>";
	}

	($importdll, $_)=(split(/\./,$a)) if ($a =~ /dll|DLL/);
	$importdll = lc($importdll);

#
# I _think_ this will ignore everything that is not needed...
# This includes errors, which are reported, but details are ignored.
#

	next LINE if ($importdll =~ /sys|SYS/);
	next LINE if /Address of/|/Section contains|.data1$|.shared$|.MSIMESH$|.rdata$|INIT$|PAGEKD$/;
	next LINE if ($a =~ /ExceptionCode/ | $a =~ /ExceptionFlags/ | $a =~ /ExceptionAddress/);
	next LINE if ($a =~ /NumberParameters/ | $a =~ /ExceptionInformation/ | $a =~ /CONTEXT/);
	next LINE if ($a =~ /Eax/ | $a =~ /Ebx/ | $a =~ /Ecx/ | $a =~ /Edx/ | $a =~ /Eip/ | $a =~ /SegCs/);
	next LINE if ($a =~ /SegSs/ | $a =~ /SegFs/ | $a =~ /Dr0/ | $a =~ /Dr1/ | $a =~ /Dr2/);
	next LINE if /Microsoft|File Type|Table|Dump of|date stamp|following imports|first forwarder|.dll|bound|.data$|.reloc$|.rsrc$|.text$|Bound|.bss$|.orpc$|.instanc$|.CRT$/;
	next LINE if ($b =~ /Characteristics/);
	next LINE if (length $a == 0 | length $b == 0);
	next LINE if ($a =~ /Summary/);


	if (/rdina/ & length $c ==0) {
		print IMPORTS ("<A HREF=\"#$importdll\">$importdll\.$a $b</A><br>\n");
	}

	elsif ($c =~ /^[0-9]+$/) {
		print IMPORTS ("<A HREF=\"#$importdll\">$importdll\.$b $c</A><br>\n");
	}


	elsif ($a =~ /[\da-fA-F]/ & length $c == 0) {
		print IMPORTS ("<A HREF=\"#$importdll\">$importdll\.$b</A><br>\n");
	}

	elsif (length $c == 0) {
		print IMPORTS ("<A HREF=\"#$importdll\">$importdll\.$a $b</A><br>\n");
	}

	elsif ($a =~ /DUMPBIN/) {
		print STDOUT2 ("\nError during dump of $name!  Import data may be incomplete!\n\n");
	}

	else {
		print IMPORTS ("<A HREF=\"#$importdll\">$importdll\.$c</A><br>\n");
	}
}

close(DATA);
close(IMPORTS);
}

sub parse_exports {

print STDOUT2 ("\nParsing export list.  This may take several minutes.\n\n");
open(DATA, "exports.txt") || die "Unable to open exports.txt.  Please try winapi_list.pl -p <path> -d exports -e\n";
open(EXPORTS, ">exported_api.txt");
LINE: while(<DATA>) {
	chomp($_);
	s/^\s*(.*?)\s*$/$1/;
	($a,$b,$c,$d,$e)=split(/\s+/,$_);
	$fullname = $d if ($d =~ /.dll$/ & $a =~ /Dump/);
	next LINE if (/Microsoft|File Type|Table|Dump of|date stamp|number of|first forwarder|bound import|.reloc$/ & ($a != /DUMPBIN/));
	next LINE if (/.orpc$|.bss$|.data1$|.instanc$|.CRT|.text$|.rsrc$|.data$/ & ($a != /DUMPBIN/));
	next LINE if (length $a == 0 | $a == "Summary");
	next LINE if /ordinal base/;


	($path,$name)=split(/\//,$fullname);

	if ($c =~ /NONAME/) {
		print EXPORTS ("$name\tordinal $a\n");
		}

	elsif ($a =~ /DUMPBIN/) {
		print STDOUT2 ("\nError during dump of $name!  Export data may be incomplete!\n\n");
	}

	elsif ($d =~ /forwarded/) {
		print EXPORTS ("$name\t$c\n");
		}
	elsif (length $d != 0 & $A != /DUMPBIN/) {
		print EXPORTS ("$name\t$d\n");
	}

	else {
		next LINE;
	}

}
}

sub build_export_database { 

$dir=$opt_b;
 push @dir,$dir;

 while ($dirs=pop @dir){
   $whereami=`cd`;
   chomp($whereami);
   chomp($dirs);
   chdir $dirs;
   while (<*>) {
     next if ($_ eq ".");
     next if ($_ eq "..");
     if (-d $_) {
     next if (-l $_);
       push @dir,$dirs."/".$_;
     } else {

$filename = "$dirs\/$_";
($spec,$junk)=split(/\./, $_);
open (FILE, "< $filename") if /\.spec$/;
LINE: while (<FILE>) {
	s/\(.*\)//;
	s/-interrupt//;
	s/-register//;
	s/-noimport//;
	s/-ret64//;
	s/-norelay//;
	s/-i386//;
	s/-noname//;
	($a,$b,$c,$d,$e)=split(/\s+/,$_);
next LINE if ($a eq "");
	if ($a eq "@" & $c eq $d) {
		push @apilist,"$spec $b $c\n";
	}

	elsif ($a eq "@") {
		push @apilist,"$spec $b $c $d\n";
	}

	elsif ($c eq "@") {
		push @apilist,"$spec $b $d\n";
	}
	elsif ($c eq $d) {
		push @apilist,"$spec $a $b $c\n";
	}
	else {
		push @apilist,"$spec $b $c $d $e\n";
	}
}
	}
    }
   close $filename;
   chdir $whereami;
 }

push @apilist,"ENDOFLIST";
open(DATA, "exported_api.txt") || die "Unable to open exported api list!  Please try winapi_list.pl -p <path> -d exports -e -b\n";
LINE: while(<DATA>) {
chomp($_);
($dll,$export,$ordinal)=split(/\s+/,$_);
foreach $line (@apilist) {
($a,$b,$c,$d,$e)=split(/\s+/,$line);
chomp($line);
if ($a eq $export | $b eq $export | $c eq $export | $d eq $export |$e eq $export ) {
	next LINE if ($export eq "ordinal");
	if ($a eq "stub" | $b eq "stub" | $c eq "stub" | $d eq "stub" | $e eq "stub") {
		print("$export stub\n");
		next LINE;
		}
	if ($a eq "forward" | $b eq "forward" | $c eq "forward" | $d eq "forward" | $e eq "forward") {
		print("$export forward\n");
		next LINE;
		}
	if ($a eq "stdcall" | $b eq "stdcall" | $c eq "stdcall" | $d eq "stdcall" | $e eq "stdcall") {
		print("$export stdcall\n");
		next LINE;
		}
	if ($a eq "cdecl" | $b eq "cdecl" | $c eq "cdecl" | $d eq "cdecl" | $e eq "cdecl") {
		print("$export cdecl\n");
		next LINE;
		}
	if ($a eq "pascal" | $b eq "pascal" | $c eq "pascal" | $d eq "pascal" | $e eq "pascal") {
		print("$export pascal\n");
		next LINE;
		}
	if ($a eq "pascal16" | $b eq "pascal16" | $c eq "pascal16" | $d eq "pascal16" | $e eq "pascal16") {
		print("$export pascal16\n");
		next LINE;
		}
	if ($a eq "varargs" | $b eq "varargs" | $c eq "varargs" | $d eq "varargs" | $e eq "varargs") {
		print("$export varargs\n");
		next LINE;
		}
	}
	elsif ($a eq "ENDOFLIST") {
		print ("$export is unimplemented\n");
		next LINE;
		}
	else {
		}
}
}
}


More information about the wine-devel mailing list