TICT TUTORIAL SERIES 1 - Part III | (c) TI-Chess Team 2001 |
TSR Programming in C v 1.05 |
This tutorial will show how to make TSR (Terminate and Stay Resistant) programs.
As demonstration I have chosen to show you how to make a clock which outputs its time on the statusline permanently until it gets removed again.
If you are not comfortable with interrupts and interrupt handler programming read the TICT Tutorial S1P2. I will not explain this part again here.
TIGCC Development Environment for Windows9x/NT - Version 0.9
http://www.ticalc.org/pub/win/asm/tigcc.zipTIGCC Tools Suite for Windows9x/NT - Version 0.97
http://www.ticalc.org/pub/win/tt.zip
TSR stands for Terminate and Stay Resistant. A TSR program installs itself (or gets installed) and will stay permanent in the system even if the program returns from its main routine.
The clock which we will develop here is an excellent example of a TSR program. We want a clock which outputs its time on the statusline, but we want also to use the calculator's commandline as usual. A program which loops within its main routine (like the one developed in tutorial S1P2) cannot be used for this purpose. The clock program has to return from its main routine letting the system think that it has finished.
A simple but COMPLETELY WRONG solution whould be to install an own interrupt handler, but don't restore the old one before returning from the main routine.
What's wrong with this approach?
In general there exists three kinds of memory blocks within the TI:
Used memory blocks which position is NOT locked are, for example, blocks where variables are stored, but which are not used actually.
- unused memory blocks
- used memory blocks which position is NOT locked
- used memory blocks which position is locked
From time to time the AMS performs some kind of heap re-organization (garbage collection). Unlocked memory blocks may change their physical location (address) during this garbage collection.
Only locked memory blocks will keep their positions for sure. They are marked as "not movable".
If a variable is used or if a program is executed the corresponding memory block is locked and will not change its position until it is unlocked again. For programs this unlocking is performed after the program returns from its main routine.
The code of the interrupt routine is also part of your program and by installing the interrupt routine its start address is written into the interrupt table.
If the corresponding interrupt occurs the processor will fetch this address from the interrupt table and tries to execute the routine which should be located there.
And that's the point! If the program exists the corresponding memory block gets unlocked and may be moved to somewhere else by the garbage collection, but the processor will still try to execute code at the previous (and now invalid) address. In almost all cases this will lead to a crash.
How to overcome this problem?
The only valid approach for this problem is NOT to unlock the memory block after the program returns from its main routine, but there is the next problem waiting: The unlocking of the memory block is performed automatically by the AMS !
The solution for this additionally problem is to make an own startup tool, which performs all steps the AMS will perform for a program, but which does NO unlocking at the end.
WARNING: Don't execute this program directly on your calculator. It will crash !!
If you have already read the TICT Tutorial Series 1 Part II there is not much interesting new stuff in the source code of this program.
First there is a minor modification to the interrupt handler. If a new second is counted the time of the clock is written into the statusline by calling the following routine:void PrintTime(void) { char buffer[16]; sprintf(buffer,"%02d:%02d:%02d", hours, minutes, seconds); ST_showHelp(buffer); }Easy, isn't it?And there is also a complete new main routine:
void _main(void) { if (oldint5) { //-------------------------------------- // if TSR is already running, stop it // by restoring the old interrupt vector //-------------------------------------- SetIntVec(AUTO_INT_5, oldint5); oldint5 = NULL; } else { resettime = 1; oldint5 = GetIntVec(AUTO_INT_5); SetIntVec(AUTO_INT_5, myint5handler); } }The global oldint5 holds the address of the old interrupt handling routine. If the program is called the first time oldint5 is NULL and the "else" branch is executed. This branch stores the address of the old interrupt handling routine in oldint5, installs our handler and returns from the main routine.The "if" branch handles the deinstallation of our interrupt handler. So if the main routine is executed a second time (indicated by a non-zero oldint5 value), the clock gots removed again.
As stated previously this program shouldn't be executed directly. It should be used ONLY in combination with the start utility which we will write next.
To make things easy we will include the complete executable into the start utility in form of a C array.
To generate this array just compile the sample program with:tigcc -O2 -outputbin sample.cThis will generate a file called "sample.bin". To generate the C array, we use the ttbin2hex utility of the TIGCC Tools Suite:ttbin2hex sample.bin sample.txtNow sample.txt contains the complete executable in form of hex values which can be used within our start utility.Some additionally hints:
Note that not every program can be used as TSR program. You have to be very carefully what functions your program performs.
Peter J. Rowe informed me that compiling a TSR program with OPTIMIZE_ROM_CALLS will cause a crash, too (Why, Zeljko?).
This utility has to perform two tasks. It is used to install our sample TSR program and it is used to deinstall it again, too.
When the start utility is called it will first try to find an already installed version of our TSR program. To find an already installed version we haven't only installed the program first, but we have also added a 12 byte long "marker" at its beginning.
The marker consists of the generic long word MAGIC_TSR (4 bytes) and of 8 bytes which holds the specific name of the TSR program.Note: it is absolutely important that this marker is unique and cannot be found directly not even in the start utility !!
Suppose we just use the name of the program as marker and this name is stored also in the start utility like this:
unsigned char signature[8] = {'t','s','r','c','l','o','c','k'};By searching the memory for this marker we will also find it in our start utility, but this is not the location we are looking for!
For this reason I have chosen a "combined" marker which cannot be found in its "combined" form in the start utility itself.Here is the routine which will searches the memory for the marker. If the marker is found it will return its address otherwise it returns NULL. The search is performed starting at the end of the heap and stepping backwards until we reach address 0. We are stepping 2 bytes backward at once, because the marker can only start at an even address due to the memory management of the TI.
Note: the search could be performed much faster, but for demonstration reasons this simple search approach will do it as well.unsigned char* FindTSR(unsigned char* signature) { unsigned char* end = HeapEnd(); while (end) { if (*end == ((MAGIC_TSR >> 24) & 0xff) && *(end+1) == ((MAGIC_TSR >> 16) & 0xff) && *(end+2) == ((MAGIC_TSR >> 8) & 0xff) && *(end+3) == ((MAGIC_TSR ) & 0xff) && !strncmp(end+4,signature,8)) { return end; } end-=2; } return NULL; }Deinstallation Routine
If a previously installed version is found by the FindTSR() routine the following routine is called to handle the deinstallation:void RemoveTSR(unsigned char* tsr_address) { enter_ghost_space(); ASM_call(tsr_address + 12 + 0x40002); memset(tsr_address,0,12); HeapFreePtr(tsr_address); }Its not necessary for you that you know the complete details of each performed step, but this routine will call the main routine of the TSR again, overwrites the 12-bytes marker with zeros and unlocks the memory.Installation Routine
If no installed version is found the following installation routine gets called:int InstallTSR(unsigned char* signature) { unsigned char* mem = HeapAllocPtr(SIZE_OF_PRG+12); if (!mem) return 0; *mem++ = (MAGIC_TSR >> 24) & 0xff; *mem++ = (MAGIC_TSR >> 16) & 0xff; *mem++ = (MAGIC_TSR >> 8) & 0xff; *mem++ = (MAGIC_TSR ) & 0xff; memcpy(mem,signature,8); mem+=8; memcpy(mem,sample_prg,SIZE_OF_PRG); EX_patch(mem + 0x40002, mem + SIZE_OF_PRG - 2 + 3 - 2); enter_ghost_space(); ASM_call(mem + 0x40002); return 1; }Again it not necessary for you to know all details. In general it performs the following steps:
- allocate memory for the TSR program and the 12-byte marker
- copy magic marker into the first 4 bytes
- copy the given signature into the next 8 bytes
- copy the code of the TSR program to the allocated memory
- relocate the TSR (make it callable)
- call the main routine (but DON'T free the allocated buffer,-it will remain locked)
Now you are equipped with everything necessary to write your own TSR programs. As you can see from this tutorial it's not really complicated (hopefully ;-) if you some basics.
... and last but not least:
I know that the developed TSR clock is missing some features as well as that its output routine is not perfect.
This is done by intention and should force you to make a better version :-). Experiment with the code to make it better!
Check the TICT HQ Website at http://tict.ticalc.org for more tutorials and software.More useful tips, tricks and hints can also be found at our Messageboard at: http://pub26.ezboard.com/btichessteamhq.
Suggestions, bug reports and similar are VERY welcome (use our Messageboard for this!!).
Like Xavier from the Doors Team I like it to get postcards from all over the world. If you want to thank me, just send me an postcard with greetings on it. Thats enough.
My address is:Thomas Nussbaumer Heinrichstrasse 112a A-8010 Graz Austria... and please: no mail bombs if one of my programs had crashed your calculator!