Remove nested pod "=over/=back" to workaround Pod::Html bugs.
[captive.git] / src / libcaptive / ke / captivesym.pl
index c63a82a..b1abc57 100755 (executable)
@@ -28,21 +28,29 @@ my %def;
 while ($ARGV[0] && $ARGV[0]=~/[.]def$/) {
        map({ open DEF,"<$_" or die "open(\"$_\"): $!"; } shift());
        while (<DEF>) {
-               s/;.*//s;
+               s/;.*$//s;
                next if /^\s*$/s;
                next if /^(?:LIBRARY|EXPORTS)\b/s;
-               my($atsign,$symbol,$args,$isdata)=(/^\s*(\@)?(\S+?)(?:\@(\d+))?(\s+DATA)?\s*$/s);
+               my($atsign,$symbol,$args,$argscdecl,$isdata)=(/^\s*(\@)?(\S+?)(?:\@(\d+))?(?::(\d+))?(\s+DATA)?\s*$/s);
                die "Invalid line" if !defined $symbol;
-               #          popped args          doc  ntoskrnl.def
-               # cdecl    no     stack         _f   f
+               #          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
-               next if $isdata;
-               die "\@funcname without \@4 suffix not recognized" if $atsign && !defined $args;
-               die "Invalid \@$args number" if defined $args && ($args<0 || ($args%4));
-               die "Duplicate symbol: $symbol" if exists $def{$symbol};
+               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"=>(!defined $args ? "cdecl" : (!$atsign ? "stdcall" : "fastcall")),
+                               "type"=>($isdata ? "data" : (!defined($args) || defined($argscdecl) ? "cdecl" : (!$atsign ? "stdcall" : "fastcall"))),
                                (!defined $args ? () : ("args4"=>$args/4)),
                                };
                }
@@ -51,18 +59,38 @@ while ($ARGV[0] && $ARGV[0]=~/[.]def$/) {
 
 # read source
 my %module;    # $module{'module'}{'symbol'}=1/""
-my %symbol;    # $symbol{'symbol'}=1/""
+my %symbol;    # $symbol{'symbol'}='module'
+my %patch;     # $patch{'module'}=1/undef
+my %stats;     # $stats{'iswhat'}=42
 while (<>) {
-       chomp;
-       next if /^\s*$/;        # empty
-       next if /^\s*#.*/;      # comment
-       my($module,$symbol,$isundef)=(/^\s*(\S+)\s+(\S+)(\s+undef)?\s*$/);
+       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};
-       delete $def{$symbol} if $isundef;
-       die "Symbol not in ntoskrnl.def file: $symbol" if !$isundef && !$def{$symbol};
-       $module{$module}{$symbol}=!$isundef;
-       $symbol{$symbol}=!$isundef;
+       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
@@ -77,75 +105,176 @@ print <<"HERE";
 #include <glib/gmessages.h>
 #include <glib/gmacros.h>
 
+#include <string.h>    /* for built-in: strncmp,memmove,strncpy */
+
+
+extern gboolean captive_debug_messages_disabled;
 
 HERE
 
-# undefined symbol stubs
-my $symbols_undef=0;
 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";
-static void ${symbol}_undef(void)
+#define ${symbol}_undef ${symbol}
+void ${symbol}(void)
 {
        g_error("%s: Function '$symbol' NOT IMPLEMENTED",G_STRLOC);
 }
 HERE
-               $symbols_undef++;
+               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";
                }
-       elsif ("cdecl" eq $def->{"type"}) {
-               print "void/* ==unknown */ ${symbol}(void/* ==unknown */);\n",
-                               "#define ${symbol}_".$def->{"type"}." ${symbol}\n";
+       if ($def->{"wrap"}) {
+               print "${symbol}_t_clean ${symbol}_wrap;\n";
                }
-       else {
-               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]));
-               print "guint32 ${symbol}(".(join(",",map("guint32 $_",@args_out)) || "void").");\n",
-                               "static guint32 "
-                                                               ."__attribute__((__".(map(($_ ne "fastcall" ? $_ : "stdcall"),$def->{"type"}))[0]."__)) "
-                                                               .($def->{"type"} ne "fastcall" ? "" : "__attribute__((__regparm__(3))) ")
-                                                               ."${symbol}_".$def->{"type"}."(".(join(",",map("guint32 $_",@args_in)) || "void").")\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",
-                               "\treturn ${symbol}(".join(",",@args_out).");\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";
                }
        }
 
-# export function captive_kernel_exports()
-print <<"HERE";
+# write function captive_kernel_{exports,patches}()
+for my $functype ("exports","patches_debug","patches_nondebug") {
+       print <<"HERE";
 
-gboolean captive_kernel_exports(void)
+gboolean captive_kernel_$functype(void)
 {
 gboolean errbool;
 
 HERE
-for my $module (sort keys(%module)) {
-       my $moduleref=$module{$module};
-       print <<"HERE";
-       errbool=captive_ModuleList_add_builtin("$module",
-HERE
-       for my $symbol (sort keys(%$moduleref)) {
-               my $value=${$moduleref}{$symbol};
-               print "\t\t\t\"$symbol\",&${symbol}_",
-                               ($def{$symbol}{"type"} || "undef"),
-                               ",\n";
-               }
-       print <<"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";
+               }
+       print <<"HERE";
        return TRUE;
 }
 HERE
+       }
 
 # exit
-print STDERR "$0: Processed ".scalar(keys(%module))." modules, ".scalar(keys(%symbol))." symbols",
-               " (".int(100*(1-$symbols_undef/scalar(keys(%symbol)))).'%'." implemented)\n";
-print STDERR "$0: warning: $symbols_undef symbols are NOT IMPLEMENTED yet!\n" if $symbols_undef;
+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;
 
 
@@ -163,16 +292,57 @@ captivesym.pl - Generate source files based on .captivesym symbol file
 
 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
-(B<module>,B<symbol>,[undef]) items.
+consist of lines with whitespace-separated lines as described below.
 
 =over
 
-=item undef
+=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
@@ -195,11 +365,17 @@ L<http://msdn.microsoft.com/library/en-us/vclang/html/_core_argument_passing_and
 
 =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 Jan Kratochvil <project-captive@jankratochvil.net>
+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