Loaded AMX viewer
#1

Hi!

Some libraries are using black magic and PAWN quirks to generate code at runtime (see amx_assembly). While this is wonderful and allows for expanding PAWN power beyond what it was supposed to be able to do, when something goes wrong, crashdetect returns complete garbage and debugging stuff is neigh impossible. Sometimes crashes are platform specific, code works fine on windows, but fails on linux boxes. So, what I need is a dumper of representation of current state of AMX loaded into samp server memory.

Did anyone already write such a program?

If not, I'm asking you for guidance in starting such an endeavour. My goal would be to write a GUI program running alongside running samp-server, with live AMX viewer. Maybe in future also current PAWN virtual machine state as well (current instruction and stuff), maybe even live memory editing (already possible in-game with samp-introspect mind you). I'd like to write it in C++, becuase why not learn something new along the way (I can mostly understand what is written in C++ (not necessarily what it does though), as long as there are no magic compiler-extension intricacies).

What I think I will need:
1. Some kind of DLL hooking (is there a linux .so equivalent?), or maybe plugin loaded the "official" way which will communicate with the debugger - but are there limitations for what plugin can read? More experienced PAWN coders than me used bugs in AMX VM to read (and write) whatever they want, however I don't know anything about writing plugins more than from what I have read in plugins section starting guide.
2. Cross-platform GUI. I think I'll need something like GTK (#edit: I meant Qt)? I don't know, I have superficial knowledge of those libraries from more than 5 years ago, maybe now there's some better contender.
3. I do not need a C++ tutorial, there's plenty of them on the internet.
4. Huh, I thought there would be more, but for MVP that's enough.

I know how to use OllyDBG, albeit slowly and not always with success.

I know how to write a dumper in PAWN, just read whole memory using #emit when OnRuntimeError is called, but I partially want to learn something new, and, well, adding debugging code directly in code being debugged adds noise (and when debugging someone else's problem, I don't want to edit the AMX provided).

I don't care about 3rd party plugins internal states, as there are better debugging tools for that.

Of course code will be on github under MIT license.

Quite a wall of text. So, what should I read first to start this project? What limitations are there in my way? Which crossplatform GUI library would be best for this specific use-case? Is using official plugin to do this is the best way? Which way would be best to broadcast read data to the debugger? Sockets for remote debugging?

Thank you.
Reply
#2

I guess plugin itself would be able to access any part of samp's server memory (including other plugins) because it would be loaded into process itself and since you will use c++ you can always unprotect memory space for access (mprotect function on unix systems, virtualprotect function on windows).

Launching gui alongside could be a bit of work since you want cross platform. By the way GTK is perfectly fine, but for some reason most people prefer QT (guess its html5 and other quirks). Just use what you are comfortable with, nobody cares about how it looks as long as its functional.
Reply
#3

There is actually a mechanism already implemented into the abstract machine, called the "debug hook". It's basically a callback function that gets called on every BREAK opcode. Check the PAWN implementers guide on this for more information. There is also a whole chapter dedicated to debugging support.
Reply
#4

Alright guys, small step for me, giant step for... well, nobody yet.

Armed with newly found plugin development tutorial and implementer's guide example code (with AMX_ERR_EXIT substituted with AMX_ERR_SLEEP), I've devised the following:

PHP Code:
#include "SDK\amx\amx.h"
#include "SDK\plugincommon.h"
typedef void(*logprintf_t)(charformat, ...);
logprintf_t logprintf;
extern void *pAMXFunctions;
int AMXAPI DebugHandler(AMX *amx)
{
    
logprintf("Shh bby it's okay");
    return 
AMX_ERR_SLEEP;
}
cell AMX_NATIVE_CALL HelloWorld(AMXamxcellparams)
{
    
amx_SetDebugHook(amxDebugHandler);
    return 
1;
}
PLUGIN_EXPORT unsigned int PLUGIN_CALL Supports()
{
    return 
SUPPORTS_VERSION SUPPORTS_AMX_NATIVES;
}
PLUGIN_EXPORT bool PLUGIN_CALL Load(void **ppData)
{
    
pAMXFunctions ppData[PLUGIN_DATA_AMX_EXPORTS];
    
logprintf = (logprintf_t)ppData[PLUGIN_DATA_LOGPRINTF];
    
logprintf(" * Test plugin was loaded.");
    return 
true;
}
PLUGIN_EXPORT void PLUGIN_CALL Unload()
{
    
logprintf(" * Test plugin was unloaded.");
}
AMX_NATIVE_INFO PluginNatives[] =
{
    { 
"HelloWorld"HelloWorld },
    { 
0}
};
PLUGIN_EXPORT int PLUGIN_CALL AmxLoad(AMX *amx)
{
    return 
amx_Register(amxPluginNatives, -1);
}
PLUGIN_EXPORT int PLUGIN_CALL AmxUnload(AMX *amx)
{
    return 
AMX_ERR_NONE;

pawn Code:
#include <a_samp>

native HelloWorld();

main() {
    SetTimer("Booper", 1000, 1);
}

forward Booper();
public Booper()
{
    static i = 0;
    printf("Tick %d %d", gettime(), ++i);
    if (i == 3) {
        printf("Executing debug");
        HelloWorld();
    }
}
Results:
Quote:

[00:18:29] Loading plugin: sandbox
[00:18:29] * Test plugin was loaded.
[00:18:29] Loaded.
[00:18:29] Loaded 5 plugins.

[00:18:30]
[00:18:30] Filterscripts
[00:18:30] ---------------
[00:18:30] Loaded 0 filterscripts.

[00:18:30] Number of vehicle models: 0
[00:18:31] Tick 1515453511 1
[00:18:32] Tick 1515453512 2
[00:18:33] Tick 1515453513 3
[00:18:33] Executing debug
[00:18:34] Shh bby it's okay
[00:18:35] Shh bby it's okay
[00:18:36] Shh bby it's okay
[00:18:37] Shh bby it's okay
[00:18:38] Shh bby it's okay
[00:18:40] Shh bby it's okay
[00:18:41] Shh bby it's okay

My conclusions so far:
1. I think I created compounding DebugHooks (though the function is called SetDebugHook, not AddDebugHook, so maybe just overwriting)
2. Timers are still executed? Are they implemented separately from amx_Exec flow? (#e: Oh, SetTimer is a native function, I followed the wrong train of thought)
3. Code inside timer callback however, is not executed! It seems AMX_ERR_SLEEP works correctly

Now I'll try resuming the amx execution. I've been spoiled with high level languages, so writing a timeout in C(++) is a new experience for me (I know I can't use "sleep" in main thread because it will result in false pausing of the execution and the whole point of checking if debug hook can pause exeuction will be moot)
Reply
#5

Hello again!
So, I've tried to keep my activity on the project down low, but now I see 7 stars on github so I guess it ain't secret anymore.
Summary of my journey so far, with massive amount of irrelevant info and rambling in a quote block if you want to read, otherwise skip:
Quote:

1. Zeex is a goddamn great craftsman. I copied amx_Exec from amx.c included with crashdetect into the function overwriting standard amx execution flow, made it compatible with crashdetect classes and it worked
2. Then I reread this thread and saw that Y_Less mentioned _W macro. I saw that it is not there, so I checked pawn compiler with fixes (whaddya you know, by Zeex as well), got the amx_Exec from there and replaced what I had.
3. Then I wanted to test if I can read CIP on each loop, so I added printf (logprintf was not available in this class), and I got... weird results? I guess logprintf buffers output, and printf does not, or in reverse, or something completely unrelated. So after quick check, I saw log class which uses logprintf, and now all is well.
4. I wanted opcode names as well, so ******d how to do it best in c++, and they recommended std:tring_view.
5. Which required C++17. My MSVC didn't support it, so I had to update visual studio, which progressed at 1% per 10 minutes... In the meantime I used cmake to create a build set up for mingw, and then it turned out mingw updater had only g++ 6.3 which did not fully support C++17 (string_view to be exact). So I had to download g++ 7 and replace everything, but I downloaded x86_64 version, which caused a lot of linker errors and wasted 20 minutes until I downloaded i684 build. I thought C++17 should be mostly compatible with earlier versions, but it turned out there are quite a few BC breaks. Zeex used std::bind1st which was removed in C++17, so I tried with std::bind but got weird errors about templates having problems with resolving unknown-type, etc. etc. So I had to do a quick overview of lambda functions (weirdly reminding me of javascript closures, but with explicit restrictions on passing local parameters), and did the following change:
Code:
std::bind1st(std::mem_fn(&AMXPathFinder::AddSearchPath), &amx_finder))
// to 
[&amx_finder](auto path) { amx_finder.AddSearchPath(path); }
I think bind creates a wrapper function as well, so I think there's no overhead difference (but it feels stupid just to create a closure only to call another function)
6. And then I added this bit
Code:
LogDebugPrint("Current CIP: %d | Current OP: %s | Param or next OP: %d",
 (uint32_t) cip,
 std::string(AMXOpcodeNames[*cip]).c_str(),
 *(cip + 1)
);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
And now I have


In my opinion, this whole project would take experienced C++ programmer about 20 minutes, but as I said, this is my C++ training ground and done during breaks from work, so don't expect too much.

Kinda fun exploring this weird C++sy world. End of rambling

Now I have questions to people interested in this project, in order of importance:

1. How would you like the plugin to communicate with external programs (for example a GUI I mentioned in first post)? Named pipes, sockets, zeromq, or ******'s protobuf?
Y_Less expressed support for sockets on discord, and I'm leaning towards it as well.
2. I'm wondering if updating the code I copied from amx_Exec is good. This is plugin for amx debugging, but fixing and/or introducing new VM bugs/features (such as now adding "#emit" macro opcodes support) might obstruct ours vision
3. I removed JIT and ASM sections of the VM. Should I restore JIT sections? I think when someone runs a debugger, they don't expect the best available performance option, but I'm certain there are JIT specific runtime bugs.
4. Is the samp's implementaion of amx VM for linux (the one with opcode relocation) relatively bug-free? What I mean by that is - does the relocation introduce any new bugs?
5. I would like to rewrite required by amx_Exec "#define"'s with C++ templates (remember that I want to learn C++ basics with this project) - any objections?
5a. There's an alternative, a nuclear option - reimplementing VM completely in C++, like sourcepawn. I don't lean towards it, but it'd be good to know your opinions.
Reply
#6

What do you want to accomplish with this I don’t get it what would be used for
Reply
#7

Quote:
Originally Posted by Crystallize
View Post
What do you want to accomplish with this I don’t get it what would be used for
It would allow pausing of execution and stepping through code line-by-line, viewing variable values, etc. Visual Studio does this for C++/C# and a few other languages, vscode does it for JavaScript/TypeScript, Go and others. Check out some examples by searching for "interactive debugging".
Reply
#8

Quote:
Originally Posted by Misiur
View Post
Hello again!
So, I've tried to keep my activity on the project down low, but now I see 7 stars on github so I guess it ain't secret anymore.
Summary of my journey so far, with massive amount of irrelevant info and rambling in a quote block if you want to read, otherwise skip:


Now I have questions to people interested in this project, in order of importance:

1. How would you like the plugin to communicate with external programs (for example a GUI I mentioned in first post)? Named pipes, sockets, zeromq, or ******'s protobuf?
Y_Less expressed support for sockets on discord, and I'm leaning towards it as well.
2. I'm wondering if updating the code I copied from amx_Exec is good. This is plugin for amx debugging, but fixing and/or introducing new VM bugs/features (such as now adding "#emit" macro opcodes support) might obstruct ours vision
3. I removed JIT and ASM sections of the VM. Should I restore JIT sections? I think when someone runs a debugger, they don't expect the best available performance option, but I'm certain there are JIT specific runtime bugs.
4. Is the samp's implementaion of amx VM for linux (the one with opcode relocation) relatively bug-free? What I mean by that is - does the relocation introduce any new bugs?
5. I would like to rewrite required by amx_Exec "#define"'s with C++ templates (remember that I want to learn C++ basics with this project) - any objections?
5a. There's an alternative, a nuclear option - reimplementing VM completely in C++, like sourcepawn. I don't lean towards it, but it'd be good to know your opinions.
1) I agree with Southclaws, some sort of generic library.

2) Don't update. It is hard to fix code issues on a different VM. I'd say don't even fix VM bugs. If the SA:MP server has a bug, leave it in so that at least te debugging behaviour is consistent.

3) The JIT isn't used in the SA:MP server, the JIT plugin is a totally separate reimplementation (I think).

4) No idea sorry. I don't know of anything caused by the two different versions, but having the code in this plugin as close to the code in the different servers is good for debugging. Otherwise, the plugin may perform differently and say good code isn't (or vice-versa).

5) See 2 and 4.

5a) See 5.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)