Hooking in BASIC+
A respected colleague recently asked for ideas on how to audit the OpenInsight Editor++ so he can track when a stored procedure is compiled. We proposed a wrapper around the Editor++ commuter module. While our colleague was familiar with this technique, it occurred to us that some developers might not know how to implement this form of hooking into BASIC+. Software hooking is a well known technique used by developers and tools within various platforms. The concept allows a custom process to intercept function calls in order to monitor the messages being generated or, in some cases, to change the behavior of the function. This article will document how to create a stored procedure wrapper that can be used to hook into both developer stored procedures as well as (most) system stored procedures.
Caution – Enter at Your Own Risk
We should begin with a healthy amount of disclaimers. While our experience with hooking has been generally positive, great care should be taken before introducing a BASIC+ hook. This is not meant to discourage people. We just want to avoid angry mobs in case something goes horribly wrong. 🙂 Therefore, proceed with caution. As in all situations where a change in the core environment is being made, make sure you have a current backup handy and always test this in a development environment before releasing into production (if applicable). It should also go without saying that there is no obligation by Revelation Software to support your system if you make changes to system stored procedures. Management is not responsible for loss or damage!
Hook, Line, and Sinker
The steps for creating a BASIC+ hook are really quite simple:
- Back-up the object code for the targeted stored procedure. Name it something like $ProcedureName_ORIG.
- Create a new stored procedure using the original name of the targeted stored procedure. Pass in the same arguments that the targeted stored procedure uses. Define it as a subroutine or a function based on the target’s type.
- In this new stored procedure, make a direct call to ProcedureName_ORIG.
When OpenInsight tries to call the targeted stored procedure, the call will go to your routine automatically. You can capture the incoming arguments before calling the renamed procedure. Outgoing arguments and a function’s return value can be also observed after the renamed procedure has finished executing. This is very similar to implementing pre and post event handlers with respect to the System Event Handler or a custom MFS with respect to the BFS.
Worth the Effort?
This is not all that difficult to do, especially if you have a sample routine to follow (see below) and there are many good reasons for introducing hooks. As mentioned at the beginning of the article, developers can use them to track activity in specific tools, perhaps to log the activity for auditing purposes. This technique can be quite useful for learning more about system stored procedures, especially if they have not been fully documented. Finally, developers can modify or even replace the functionality of the original stored procedure. Case in point, we used a hook wrapper for RUN_OECGI_REQUEST to better understand how it received OECGI HTTP Requests and how HTTP Responses were intended to be formatted. This helped us to write our own dispatcher routine for the SRP HTTP Framework product.
To test the suggestion we gave our colleague, we created a hook for the Editor++ commuter module. Here is what our routine looks like:
Function RTI_Editor_Events(Action, File, Record, Param1, Param2, Param3, Param4) Declare function RTI_Editor_Events_Orig Ans = RTI_Editor_Events_Orig(Action, File, Record, Param1, Param2, Param3, Param4) Return Ans
Forging the Signature
Obviously in order for this to work the hooking stored procedure needs to emulate the same signature of the targeted stored procedure. That is, they both must be subroutines or they both must be functions. Likewise, they both must use the same argument list. This leads us to a natural question: how do we get access to the targeted stored procedure’s signature?
In an ideal situation, documentation is your friend. Hopefully the subroutine versus function distinction is spelled out or is easy to infer (e.g., event commuter modules are almost always functions). If all else fails, make the hooking stored procedure a subroutine. If the calling process generates a VNAV, then this is most likely due to it expecting a return value from a function call. Simply change the hooking stored procedure to a function and the problem should go away.
While many system stored procedures are documented, their arguments lists are not always complete. In these cases it might be necessary to examine the object code. Arguments are stored in an @VM delimited list located at the end of the object code and preceded by a Char(0). Unfortunately, none of the current OpenInsight record editors can display object code, so other tools must be used. One could write the object code to an OS file and use an external editor to view the content. However, AREV has always been able to view object code…which means ARev32 can do the same! In the case of $RTI_EDITOR_EVENTS, here is what it looks like:
Likewise, developers who use the SRP Editor can also display object code. However, with the SRP Editor, this is unnecessary because the argument list is automatically parsed for you when you invoke Call Tips:
It’s a Wrap
Hooking BASIC+ stored procedures is not something we do everyday, but the technique has been quite useful in many situations to better understand the inner workings of OpenInsight or to help troubleshoot problems. It you have additional ideas on this or questions, start a conversation in the SRP forums. We would be quite happy to explore this further.