Skip to content

Instantly share code, notes, and snippets.

@Y-Less
Created March 18, 2017 21:48
Show Gist options
  • Select an option

  • Save Y-Less/fbe3af6819dcaf3915452d2ca3aa8b8f to your computer and use it in GitHub Desktop.

Select an option

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.
#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