#! /usr/bin/perl
# 
# $Id$
# Generate source files based on .captivesym symbol file
# Copyright (C) 2002 Jan Kratochvil <project-captive@jankratochvil.net>
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; exactly version 2 of June 1991 is required
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA


use vars qw($VERSION);
$VERSION=do { my @r=(q$Revision$=~/\d+/g); sprintf "%d.".("%03d"x$#r),@r; };
use strict;
use warnings;


my %def;
while ($ARGV[0] && $ARGV[0]=~/[.]def$/) {
	map({ open DEF,"<$_" or die "open(\"$_\"): $!"; } shift());
	while (<DEF>) {
		s/;.*$//s;
		next if /^\s*$/s;
		next if /^(?:LIBRARY|EXPORTS)\b/s;
		my($atsign,$symbol,$args,$argscdecl,$isdata)=(/^\s*(\@)?(\S+?)(?:\@(\d+))?(?::(\d+))?(\s+DATA)?\s*$/s);
		die "Invalid line" if !defined $symbol;
		#          popped args          doc  *.def *.def2
		# cdecl    no     stack         _f   f     f:4
		# stdcall  yes    stack         _f@4 f@4
		# fastcall yes    ecx,edx,stack @f@4 @f@4
		die "Invalid attributes for data symbol: $symbol" if $isdata && ($atsign || defined $args);
		die "\@funcname without \@4 suffix not recognized: $symbol" if $atsign && !defined $args;
		die "Invalid \@$args number: $symbol" if defined $args && ($args<0 || ($args%4));
		if (!defined $argscdecl) {	# beware: $argscdecl may eq "0"
			die "Duplicate symbol: $symbol" if exists $def{$symbol};
			}
		else {
			die "cdecl-fixup without previous declaration: $symbol" if !$def{$symbol};
			die "cdecl-fixup with non-cdecl previous declaration: $symbol" if $def{$symbol}{"type"} ne "cdecl";
			die "cdecl-fixup for already fix-uped cdecl: $symbol" if exists $def{$symbol}{"args4"};
			$args=$argscdecl;
			}
		$def{$symbol}={
				"type"=>($isdata ? "data" : (!defined($args) || defined($argscdecl) ? "cdecl" : (!$atsign ? "stdcall" : "fastcall"))),
				(!defined $args ? () : ("args4"=>$args/4)),
				};
		}
	close DEF or warn "close(DEF): $!";
	}

# read source
my %module;	# $module{'module'}{'symbol'}=1/""
my %symbol;	# $symbol{'symbol'}='module'
my %patch;	# $patch{'module'}=1/undef
my %stats;	# $stats{'iswhat'}=42
while (<>) {
	s/#.*$//s;
	next if /^\s*$/s;	# empty
	next if /^\s*#.*/s;	# comment
	my($module,$symbol,$iswhat)=(/^\s*(\S+)\s+(\S+)(?:\s+(undef|pass|wrap))?\s*$/s);
	$iswhat="" if !defined $iswhat;
	die "Invalid line" if !defined $symbol;
	if ($symbol eq "<patch>") {
		die "Invalid line" if $iswhat;
		die "Symbols already present during <patch> for: $module" if $module{$module}{$symbol};
		$patch{$module}=1;
		next;
		}
	die "Symbol already exists: $symbol" if exists $symbol{$symbol};
	if ($iswhat eq "undef") {
		warn "Undefined symbol not in *.def files; 'data' type risk imminent: $symbol" if !$def{$symbol};
		die "Undefined 'data' type symbols are not safe: $symbol" if $def{$symbol} && $def{$symbol}{"type"} eq "data";
		delete $def{$symbol};
		}
	die "Symbol not in *.def files: $symbol" if $iswhat ne "undef" && !$def{$symbol};
	if ($iswhat eq "pass" || $iswhat eq "wrap") {
		die "args count not fixed up for '$iswhat' type: ".$symbol."[".$def{$symbol}{"type"}."]"
				if !exists $def{$symbol}{"args4"} && $def{$symbol}{"type"} ne "data";	# beware: {"args"} may ==0
		die "'$iswhat' not permitted if <patch> not specified for module on symbol: $symbol" if !$patch{$module};
		$def{$symbol}{$iswhat}=1;
		}
	$module{$module}{$symbol}=$iswhat ne "undef";
	$symbol{$symbol}=$module;
	$stats{$iswhat}++;
	}

# file header
print <<"HERE";
/* File generated automatically by captivesym.pl from "$ARGV" */
/* DO NOT EDIT! */

#include "config.h"

#include "captive/ldr_exports.h"	/* for captive_ModuleList_add_builtin() */
#include <glib/gtypes.h>
#include <glib/gmessages.h>
#include <glib/gmacros.h>

#include <string.h>	/* for built-in: strncmp,memmove,strncpy */


extern gboolean captive_debug_messages_disabled;

HERE

for my $symbol (sort keys(%symbol)) {
	my $def=$def{$symbol};
	if (!$def) {
		# use global symbol named '${symbol}' to cause symbol conflict if it is already defined
		print <<"HERE";
#define ${symbol}_undef ${symbol}
void ${symbol}(void)
{
	g_error("%s: Function '$symbol' NOT IMPLEMENTED",G_STRLOC);
}
HERE
		next;
		}
	if ($patch{$symbol{$symbol}} && "data" ne $def->{"type"}) {
		# We do not declare it 'static' as we sometimes make 'extern' references to it
		# such as 'ExInitializeNPagedLookasideList_patchpoint' in libcaptive/ex/lookas.c.
		print "struct captive_ModuleList_patchpoint ${symbol}_patchpoint;\n";
		}
	if ("data" eq $def->{"type"}) {
		next if $def->{"pass"} || $def->{"wrap"};	# FIXME: export for .so
		print "extern void/* ==unknown */ ${symbol};\n";
		print "#define ${symbol}_".$def->{"type"}." ${symbol}\n";
		next;
		}
	if ("cdecl" eq $def->{"type"} && !defined $def->{"args4"} && !$def->{"pass"} && !$def->{"wrap"}) {
		# g_log(,G_LOG_LEVEL_DEBUG,...) not possible if we do not know the arguments count
		my %forbidden=map(($_=>1),qw(strncmp memmove strncpy));	# Prevent: conflicting types for built-in function ...
		print "void/* ==unknown */ ${symbol}(void/* ==unknown */);\n" if !$forbidden{$symbol};
		print "#define ${symbol}_".$def->{"type"}." ${symbol}\n";
		next;
		}

	die "Needed argument count for: $symbol" if !defined $def->{"args4"};
	my @args_out=map("arg$_",0..($def->{"args4"}-1));
	my @args_in=($def->{"type"} ne "fastcall" ? @args_out
			: ("stub_eax","arg1","arg0",@args_out[2..$#args_out]));
	my $attrib=""
			."__attribute__((__".(map(($_ ne "fastcall" ? $_ : "stdcall"),$def->{"type"}))[0]."__)) "
			.($def->{"type"} ne "fastcall" ? "" : "__attribute__((__regparm__(3)))");
	for my $type ("clean","attrib") {
		print
				"typedef guint64"
						." ".($type eq "clean" ? "" : $attrib)
						." (${symbol}_t_".$type.")(".(join(",",map("guint32 $_",
								($type eq "clean" ? @args_out : @args_in))) || "void").");\n";
		}
	if ($def->{"wrap"}) {
		print "${symbol}_t_clean ${symbol}_wrap;\n";
		}
	elsif (!$def->{"pass"}) {	# direct
		print "${symbol}_t_clean $symbol;\n";
		}
	my @args_print=@args_out;
	for my $pass ("outer","clean","inner") {
		if ($pass eq "inner") {
			next if !$def->{"wrap"};	# use two passes only for $def->{"wrap"}
			# swap the roles of @args_in and @args_out
			my @args_xchg=@args_in;
			@args_in=@args_out;
			@args_out=@args_xchg;
			}
		next if $pass eq "clean" && !$def->{"pass"} && !$def->{"wrap"};
		print(
				"guint64".($pass eq "outer" ? " $attrib" : "")
						." ${symbol}"
								.($pass eq "clean" ? "" : 
										($pass eq "inner" ? "_orig" : "_".$def->{"type"}))
								."(".(join(",",map("guint32 $_",($pass eq "clean" ? @args_out : @args_in))) || "void").")\n",
				"{\n",
				"guint64 r;\n",
				($pass ne "outer" && $def->{"type"} eq "fastcall" ? "guint32 "
						.join(",",map("$_=0xDEADF00D","stub_eax",
								($def->{"args4"}<=0 ? "arg0" : ()),
								($def->{"args4"}<=1 ? "arg1" : ())))
						.";\n" : ""),
				"\tg_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"
						."\"%s".($def->{"wrap"} ? ";$pass" : "")
								."(".join(",",map("0x%08x",@args_print)).")...\",".join(",","\"${symbol}\"",map("(unsigned)$_",@args_print))
						.");\n",
				"");
		if ($def->{"pass"} || ($def->{"wrap"} && $pass eq "inner")) {
			print
					"\tg_return_val_if_fail(${symbol}_patchpoint.orig_w32_func!=NULL,0);\n",
					"\tg_assert(${symbol}_patchpoint.through_w32_func==FALSE);\n",
					"\t${symbol}_patchpoint.through_w32_func=TRUE;\n",
					"\tr=(*(${symbol}_t_attrib *)${symbol}_patchpoint.orig_w32_func)(".join(",",@args_in).");\n";
			if (!$def->{"pass"}) {
				print 
						"\tg_assert(${symbol}_patchpoint.through_w32_func==FALSE);\n";
				}
			else {
				print
						"\tif (!captive_debug_messages_disabled)\n",
						"\t\tg_assert(${symbol}_patchpoint.through_w32_func==FALSE);\n",
						"\telse {\n",
						"\t\tg_assert(${symbol}_patchpoint.through_w32_func==TRUE);\n",
						"\t\t${symbol}_patchpoint.through_w32_func=FALSE;\n",
						"\t\t}\n";
				}
			}
		else {
			print
					"\tr=${symbol}".($def->{"wrap"} ? "_wrap" : "")."(".join(",",@args_out).");\n";
			}
		print
				# We diplay just the lower 32-bit of the result EDX:EAX as it is usually not used,
				# the only exception are _all{mul,div,*}(); EDX:EAX convention is always compatible.
				"\tg_log(G_LOG_DOMAIN,G_LOG_LEVEL_DEBUG,"
						."\"... %s".($def->{"wrap"} ? ";$pass" : "")
								."(".join(",",map("0x%08x",@args_print)).")=0x%08x\",".join(",","\"${symbol}\"",map("(unsigned)$_",@args_print)).",(guint32)r"
						.");\n",
				"\treturn r;\n",
				"}\n";
		}
	}

# write function captive_kernel_{exports,patches}()
for my $functype ("exports","patches_debug","patches_nondebug") {
	print <<"HERE";

gboolean captive_kernel_$functype(void)
{
gboolean errbool;

HERE
	for my $module (sort keys(%module)) {
		my $moduleref=$module{$module};
		next if ($functype=~/^patches/) != defined $patch{$module};
		print "\t\terrbool="
				.($functype=~/^patches/ ? "captive_ModuleList_patch" : "captive_ModuleList_add_builtin")
				."(\"$module\",\n";
		for my $symbol (sort keys(%$moduleref)) {
			next if $functype=~/^patches/ && !$def{$symbol};
			(my $symbol_outer=$symbol)=~s/^captive_reactos_//;
			print "\t\t\t\"$symbol_outer\",",(($functype=~/^patches/ && "data" eq $def{$symbol}{"type"}
											&& ($def{$symbol}{"pass"} || $def{$symbol}{"wrap"})) ? ("NULL")
							: ("&${symbol}_",($def{$symbol}{"type"} || "undef"))),
					(($functype!~/^patches/) ? () :
							(",".("data" eq $def{$symbol}{"type"} ? "NULL,NULL" :
									($functype eq "patches_nondebug" && $def{$symbol}{"pass"} ? "&${symbol}_patchpoint,NULL" :
											"&${symbol}_patchpoint,&${symbol}_patchpoint")))),
					",\n";
			}
		print <<"HERE";
			NULL);
	g_return_val_if_fail(errbool,FALSE);

HERE
		}
	print <<"HERE";
	return TRUE;
}
HERE
	}

# exit
my $total=0;
$total+=$_ for (values(%stats));
my $statstring;
for my $statname (sort keys(%stats)) {
	$statstring.=" ".($statname || "define")."=".$stats{$statname}."(".int(100*$stats{$statname}/$total)."%)";
	}
print STDERR "$0: Processed ".scalar(keys(%module))." modules:".$statstring."\n";
exit 0;


__END__

=head1 NAME

captivesym.pl - Generate source files based on .captivesym symbol file

=head1 SYNOPSIS

./captivesym.pl path/to/ntoskrnl.def path/to/hal.def exports.captivesym E<gt>exports.c

=head1 DESCRIPTION

Source files with symbol call type definitions are identified by matching
pattern I<*.def>. The remaining files (I<.captivesym> ones) must
consist of lines with whitespace-separated lines as described below.

=over

=item (B<module>,E<lt>patchE<gt>)

Declare B<module> as mandatory W32 binary file to be patched by libcaptive.
Currently being used only for C<ntoskrnl.exe>.

Any function call even inside such module is trapped and redirected for
libcaptive processing even if it is just for debug-dumping of B<pass> type.

=item (B<module>,B<symbol>)

Name without special attribute declares function fully implemented by GNU/Linux
code. Original W32 binary function will never be called.

You may fully implement function for both E<lt>patchE<gt>ed and
unE<lt>patchE<gt>ed modules.

=item (B<module>,B<symbol>,undef)

Optional "undef" specifies invocation of a generated stub function displaying
C<g_error()> message.

For "unpatched" modules you have to specify all the referenced symbols at least
as this "undef" symbol. For "patched" modules it is not needed for native
W32-PE binary modules importing such symbol but it is still required for W32
.so files to satisfy .so dynamic linker.

It is forbidden to "undef" C<DATA> type of items; you have to cope with it.

=item (B<module>,B<symbol>,pass)

Calls of this function are debug-dumped on its entry/exit but they are fully
left to be solved by W32 binary file being E<lt>patchE<gt>ed.

It is forbidden to specify "pass" for unE<lt>patchE<gt>ed modules.

=item (B<module>,B<symbol>,wrap)

Calls of this function are debug-dumped on its entry/exit. Execution is left
to be solved by your GNU/Linux implementation called B<functionname_wrap>.
You are allowed to call the original W32 binary function named
B<functionname_orig> but you have to use your own prototype declaration for it.
Both B<functionname_wrap> and B<functionname_orig> should be used with standard
GNU/Linux C compiler function call type notwithstanding any real W32
implementation details.

It is forbidden to specify "wrap" for unE<lt>patchE<gt>ed modules.

=back

=begin comment

	choose one:
		podchecker: *** WARNING: node 'http:$_' contains non-escaped | or /
		pod2html: cannot resolve L.lt.http:$_.gt.

=end comment

Source files I<*.def> are required to have on of three item types described at
L<http://msdn.microsoft.com/library/en-us/vclang/html/_core_argument_passing_and_naming_conventions.asp>
(B<argslength> must be dividable by B<4>):

=over

=item cdecl: functionname

=item stdcall: functionname@argslength

=item fastcall: @functionname@argslength

=item cdecl fixup: functionname:argslength

This item must follow (even in some other I<*.def> file) previous B<cdecl>
specification to specify the number of arguments as it is required for B<pass>
or B<wrap> type of B<cdecl> function calls.

=back

=head1 COPYRIGHT

Copyright (C) 2002-2003 Jan Kratochvil <project-captive@jankratochvil.net>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; exactly version 2 of June 1991 is required

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

=cut
