Created
March 18, 2017 21:48
-
-
Save Y-Less/fbe3af6819dcaf3915452d2ca3aa8b8f to your computer and use it in GitHub Desktop.
An experimental include to add `addressof` and `paramsof` function meta information to PAWN.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include <amx_assembly\frame_info> | |
| #include <amx_assembly\disasm> | |
| // This library allows you to get the address of a function at run-time, with | |
| // very little overhead. To do so, just call "addressof": | |
| // | |
| // printf("Func0 = 0x%08x", addressof (Func0)); | |
| // | |
| // However, you can only get the address of a function explicitly enabled for | |
| // for such an operation, otherwise you will get a compile-time error of: | |
| // | |
| // undefined symbol "_yA@main" | |
| // | |
| // If we define a function with "stock", "native", or "public", it is | |
| // automatically made addressable. | |
| // | |
| // This: | |
| // | |
| // stock Func1(a, b, c[]) | |
| // { | |
| // } | |
| // | |
| // Becomes: | |
| // | |
| // forward Func1(a, b, c[]); | |
| // | |
| // stock _yA@Func1(/* Detection Pattern */) | |
| // { | |
| // if (false) Func1(0, 0, ""); | |
| // return "iia"; | |
| // } | |
| // | |
| // stock Func1(a, b, c[]) | |
| // { | |
| // } | |
| // | |
| // If we DON'T define functions like that, you can still explicitly make a | |
| // function addressable with: | |
| // | |
| // REFLECT(Func2(z, File:y, &x)); | |
| // | |
| // Func2(z, File:y, &x) | |
| // { | |
| // } | |
| // | |
| // If you want a stock function or public function to not be addressable, then | |
| // you can use "STOCK" and "PUBLIC" instead (same for "native"/"NATIVE"). | |
| // | |
| // Because all the "_yA@" functions are "stock", this mechanism is very low | |
| // overhead - only those functions that you explicitly DO get the address of | |
| // will have their "_yA@" variants included in the final code. And the | |
| // "addressof" macro itself is also very efficient, as most of it is contained | |
| // within a conditional that will never pass. | |
| // | |
| // As a side-note, this is possibly also the best place to learn about tag | |
| // macros matching function parameter types, because their use in here is very | |
| // simple, constrained, and neat. | |
| // | |
| // As well as adding "addressof", this library also adds "paramsof", which | |
| // returns a string representation of the function parameters, as would be used | |
| // in "CallRemoteFunction" and the like. However, the strings returned are | |
| // SLIGHTLY different to the default ones: | |
| // | |
| // var = i (same as normal). | |
| // Float:var = i (tags are ignored, but make no difference). | |
| // string:var[] = s (the tag is used then removed). | |
| // var[] = a (probably needs following with a length). | |
| // &var = v (as in Slice's includes). | |
| // ... = - (always last). | |
| // | |
| // The only tag that is used is "string:" to differentiate between strings and | |
| // arrays, and this is a special tag - it is defined as nothing, which means | |
| // that once it has been used to generate the return string, it is no longer | |
| // used in the code. | |
| // | |
| // To use, simply do: | |
| // | |
| // new params[32] = paramsof (MyFunc); | |
| // CallLocalFunction("MyFunc", paramsof (Myfunc), 0, 1, 2); | |
| // | |
| // This enables this handy macro: | |
| // | |
| // #define CALL(%0,%1) CallLocalFunction(#%0, paramsof (%0), %1) | |
| // | |
| // Use these instead of "static"/"stock" to create them without expansion. | |
| #define STOCK%0(%1) stock @Si:%0`%1` | |
| #define STATIC%0(%1) static @Si:%0`%1` | |
| #define PUBLIC%0(%1) public @Si:%0`%1` | |
| #define NATIVE%0(%1) native @Si:%0`%1` | |
| #define string: | |
| // Convert "static stock Func()" to "stock static Func()" to maintain scopes. | |
| // Note that this macro breaks on "static" functions with "stock" in the name | |
| // and a space between the name and the opening bracket. I hope that is a very | |
| // small number of things though. | |
| #define static%0\32;stock%1\32;%2( stock static %0%1%2( | |
| // Ensure that "stock" doesn't match twice, by not having brackets in the output | |
| // macro for a while (only put them in AFTER "stock" has failed to match). | |
| #define @Si:%0`%1` %0(%1) | |
| // Consume extra spaces. | |
| #define _yA@%0\32;%1( _yA@%0%1( | |
| #define REFLECT(%0(%1)); forward %0(%1);stock @Si:@St:_yA@%0`&@Sh:a`{if(a)(@Sk:@Sj:%0(%1,,));static s[32]=#;return @St:s;} | |
| // The braces are required despite the inner call being just one statement, | |
| // because they prevent the brackets matching other macros recursively. | |
| #define stock%0(%1) REFLECT(%0(%1));stock%0 @Si:`%1` | |
| #define public%0(%1) REFLECT(%0(%1));public%0 @Si:`%1` | |
| #define native%0(%1) REFLECT(%0(%1));native%0 @Si:`%1` | |
| // Correct "stock static": | |
| #define forward%9static%0(%1);stock%9static%2`%3`{if(a)(%9static%4(%5));%7} forward%0(%1);static stock @Si:@St:_yA@%0`%3`{if(a)(%9%4(%5));%7} | |
| // Strip out return tags. | |
| #define @St:%9:%0(%1){if(a)(@Sk:@Sj:%9:%2(%3));%7} @St:_yA@%0(%1){if(a)(@Sk:@Sj:%2(%3));%7} | |
| // Do the actual parameter type detection. | |
| #define @Sz:%9$%0(%1|||%9const%3|||%5)) %9$%0(%1|||%3|||%5) | |
| #define @Sy:%9$%0(%1|||%3=%9|||%5)) %9$%0(%1|||%3|||%5) | |
| #define @Sn:%9$%0(%1|||%9&%3|||%5));%7; @Sg:%0(%1|||So@$%3|||%5));%7v; | |
| #define @Sa:%9$%0(%1|||%3[%4]|||%5) @Su:%0(%1|||""$%3|||%5) | |
| #define @Sl:%9$%0(%1|||%9...|||%5));%7; @Sm:@Sc:$%0(%1||||||));%7-; | |
| #define @Sb:%9$%0(%1|||%3|||%5));%7; @Sg:%0(%1|||0$%3|||%5));%7d; | |
| #define @Sc:%9$%0(,%1||||||) %0(%1) | |
| #define @Sm:%9$%0(||||||) %0() | |
| #define @Sv:%9$%0(%1|||%8$%2string:%3|||%5,%6));%7; @Sf:%0(%1,%2:%8|||%5|||%6));%7s; | |
| #define @Sw:%9$%0(%1|||%8$%2:%3|||%5,%6));%7; @Sf:%0(%1,%2:%8|||%5|||%6));%7a; | |
| #define @Sx:%9$%0(%1|||%8$%3|||%5,%6));%7; @Sf:%0(%1,%8|||%5|||%6));%7a; | |
| #define @Sd:%9$%0(%1|||%8$%2:%3|||%5,%6) @Sf:%0(%1,%2:%8|||%5|||%6) | |
| #define @Se:%9$%0(%1|||%8$%3|||%5,%6) @Sf:%0(%1,%8|||%5|||%6) | |
| #define @Sf: @Sz:@Sy:@Sc:@Sn:@Sa:@Sl:@Sb:$ | |
| #define @Sg: @Sd:@Se:$ | |
| #define @Su: @Sv:@Sw:@Sx:$ | |
| #define @Sk:@Sj:%0(,,) %0() | |
| #define @Sj:%0(%1,%2) @Sf:%0(|||%1|||%2) | |
| // Passed in place of "&" parameters. | |
| #define addressof_Ref So@ | |
| // The variable used to pass the function address around. | |
| #define addressof_Ret Sp@ | |
| // The name of the search function. | |
| #define addressof_Find Sq@ | |
| // Passed as an identifier to the "_yA@" functions that aren't macros. | |
| // SHOULD ALWAYS BE ZERO!!! | |
| #define addressof_Fake Sr@ | |
| stock | |
| addressof_Ref, | |
| addressof_Ret, | |
| @Sh:addressof_Fake; | |
| // This code uses two nested conditionals because of: | |
| // https://github.com/Zeex/pawn/issues/96 (this doesn't work): | |
| // | |
| // (O@A_() ? ((_yA@%1), 0) : (O@V_)) | |
| // | |
| // Even though it is the obvious solution when you don't want the result of | |
| // "_yA@%1" to be used (as it may not exist), and you can't use a constant | |
| // instead of "O@V_" because then if becomes a constant in a | |
| // condition, which the compiler rightly complains about. Of course, because | |
| // "O@A_()" always returns "false", the entire other block of code | |
| // is jumped over. | |
| #define addressof(%1) (Sq@()?(((_yA@%1(Sr@)),Sp@)?1:2):(Sp@)) | |
| #define paramsof(%1) (_:_yA@%1(Sr@)) | |
| #define _yA@addressof_Find _yA@Sq@ | |
| stock bool:addressof_Find() { | |
| // Start reading code from the point to which this function returns, looking | |
| // for the next "ADDR" op to signal the function call from the macro. | |
| new | |
| redo = 0, | |
| ctx[DisasmContext]; | |
| DisasmInit(ctx, GetCurrentFrameReturn()); | |
| while (DisasmNext(ctx)) { | |
| switch (DisasmGetOpcode(ctx)) { | |
| case OP_PUSH_C: { | |
| if (DisasmGetOperand(ctx) == ref(addressof_Fake)) { | |
| ++redo; | |
| } | |
| } | |
| case OP_CALL: { | |
| // Return the data in a global. | |
| addressof_Ret = DisasmGetOperandReloc(ctx); | |
| if (redo == 1) { | |
| // Re-run the finder, entering the "_yA@" function. | |
| redo = 2; | |
| DisasmInit(ctx, addressof_Ret); | |
| } else { | |
| return false; | |
| } | |
| } | |
| case OP_SYSREQ_C: { | |
| // Trying to take the address of a native. | |
| addressof_Ret = 0; | |
| return false; | |
| } | |
| } | |
| } | |
| // ALWAYS returns false so that the function call within "OP(&func)" will | |
| // never be called thanks to the conditional. | |
| return false; | |
| } | |
| // TODO: AOT decompilation with JIT. | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment