ARM code for beginners

Part 8 — ARM code tricks




    Let`s say we`re trying to write a program which logs the times at which a RISC OS machine is turned on and off.  It needs to be a module, which is loaded in the machine`s boot sequence, and it writes its findings to a log file.  It`s easy to get the module to write the time and date when the machine is turned on, since it can do this in its initialisation code.  However, we cannot guarantee that the user will shut the machine down properly, so the module can`t know exactly when the machine has had the power cut.

    The solution is to make the module continually keep a record of when it was last 'living' by writing the time into the computer`s battery-backed memory.  Then, when it starts up again, it can re-read this value, deduce when the machine was turned off and write the information to the log file.  Now comes the hard part: how to implement this in ARM code.  We could use claim TickerV (&1C), a vector called every centisecond.  Then every 6000 calls (i.e. one minute), we write the value to the CMOS RAM.  However, RISC OS provides us with OS_CallAfter and OS_CallEvery, SWIs which do 'exactly what they say on the tin' (see figure 1).  They are closely bound up with OS_AddCallBack.  We can use OS_Byte 161 / 162 to read and write CMOS RAM; bytes 30-45 are allocated 'for the user'.
 
 
The CallBack SWIs
 

OS_CallAfter
OS_CallEvery

call with: R0 = cs to wait
 R1 = address of routine to call
 R2 = value of R12 to pass to routine

Calls a CallBack routine after a specified amount of time; OS_CallAfter calls the routine once, OS_CallEvery carries on until told to stop with OS_RemoveTickerEvent.

OS_AddCallBack

call with: R0 = address to call
 R1 = value of R12 to pass to routine

This is equivalent to OS_CallAfter, but there is no delay: RISC OS calls the routine as soon as it is in user mode with interrupts enabled.

OS_RemoveTickerEvent

call with: R0 = address of routine being called
 R1 = value of R12 passed to routine

This will stop the OS calling a routine previously registered with OS_CallAfter or OS_CallEvery; more often used with the latter.  It will generate an error if the values of R0 and R1 are not those passed previously.
 

SWI trapping

    You may conceivably have a problem in ARM code that can only be solved by trapping SWIs.  Depending on the SWI you want, the solutions start neat and end up very fiddly.  Sometimes, RISC OS makes it easy for you, and you can use OS_Claim to claim that SWI‘s vector (see figure 2 for a short list).

    Some modules will provide mechanisms for programmers to claim certain SWIs of theirs.  The Wimp is an example of this; you can trap Wimp_Poll quite neatly by calling the FilterManager SWIs, as detailed in Part 7, last issue.

    Another, slightly messier solution is to create a module with the same SWI chunk as those of the module you`re trying to intercept.  WimpSWIVe, by Andrew Clover, does this for the Wimp and provides a 'neat' claiming structure around what is essentially a messy patch (in that only one module can do it at a time).

    If you need an OS_ SWI which isn‘t covered by the usual list of vectors, there is a table of vectors for them starting at address &1F033FC in memory which Acorn have kept consistent throughout RISC OS, but not publicised.  So to trap OS_CRC (SWI &5B), you should poke address (&1F033FC + (&5B * 4)) with the address of your new routine, preserve its old contents, and branch there when you‘ve finished, preserving all registers.

    Otherwise, you must resort to claiming hardware vectors which constitute the carpeting of RISC OS.  Pull them out at your peril; these deserve an article to themselves due to the different way they are handled under different processors and versions of the operating system.
 
 
A few SWIs trappable through vectors

The complete list can be found in the Programmers Reference Manual, page 1-78, or in the StrongHelp SWI manual under the OS_ calls list.  The vectors are called with the SWI‘s usual parameters (e.g. OS_CLI with R0 pointing to the command string).  Return from a vector routine with either MOV PC,R14 to claim the SWI or LDMFD R13!,{pc } to intercept it; with the latter, ensure you have restored any registers stacked previously (including the old R14!).

Post-processing (i.e. altering the parameters the SWI returns) can be achieved by calling the SWI from within your routine and intercepting it on return, but be warned that your vector routine will be called again as a result and should do nothing; otherwise the machine will come to an abrupt halt as the SWI is endlessly re-executed.

ByteV (vector &06)
OS_Byte is used to indirect a lot of system calls... see what happens if you trap OS_Byte 19 (wait for vsync), intercept it, and make it return immediately.

FileV (vector &08)
Vector for calls to OS_File.  Ideas for claimants include modules to stop certain files being altered or deleted as FSLock does.

MouseV (vector &1A)
Claiming this will allow you to replace the mouse input with co-ordinates and buttons, such as a joystick or keys, since OS_Mouse is used to read all mouse data.

ColourV (vector &22)
All calls to ColourTrans are indirected through this, with R8 containing the offset of the SWI number called (i.e. 0 = ColourTrans_SelectTable).  You could create some bizarre desktop colour schemes by playing with this.
 

A bit of S and M?

    Under RISC OS 3.7, there is a SWI, OS_CallASWI, whose sole purpose is to call a SWI whose number is passed in R10.  There is a point to this; if you remember, all ARM instructions, including each individual SWI number, are four byte instruction.  Except for this SWI, there`s no 'neat' way of calling a SWI by number.

    One way of doing it is to use self-modifying code.  We assemble and store an instruction in a particular address, then run the program counter over it.  The fragment of code in figure 2 assembles and runs a SWI OS_CLI instruction.

    Before the StrongARM, ARM processors had one cache, a small area of memory on the chip where instructions and data were stored when they were accessed frequently.  If the ARM chip needed to refer to a memory location which it had previously cached, it needn‘t bother to look in the main memory, which resulted in a speed increase.  The StrongARM has two caches; one for data and one for instructions.  The upshot of this is that if we try to modify what the chip thinks of as 'code', we`ll run into problems.  In fact, Acorn have decreed that self-modifying code should not be used by RISC OS programs unless it is absolutely necessary.  Where a piece of code is altered, you need to use the SWI OS_SynchroniseCodeAreas to tell it which area of memory has potentially changed the code it had (see figure 4).

    The other thing to note is the way instructions are assembled in memory:

1110 1111 0000 0000 0000 0000 0000 0101 = &EF000005

    The top four bits represent one of sixteen condition codes (this will always happen).  Setting the next four bits high indicates that it is a SWI instruction; all the lower bits are the SWI number.  Other instructions follow similar patterns; looking at the way an instruction is represented as a bit-pattern helps to show the problems faced with immediate constants.  Full information is printed in the Acorn Assembly manual.
 
 
This fragment of code assembles and runs a SWI OS_CLI instruction:

MOV R10,#&EF000000
ADD R10,R10,#5
STR R10,here
ADR R0,hello
.here
MOV R0,R0 ; Our standard NO-OP instruction
MOV PC,R14
.hello
EQUS "Echo Self assembly!":EQUB 0
 


 
OS_SynchroniseCodeAreas (SWI &6E)

Call with: R0 = 0 to synchronise whole memory
 R0 = 1 for just a specific range of addresses
 R1 = lowest address (when R0 = 1)
 R2 = highest address, inclusive (when R0 = 0)

This SWI should be called when code has been altered; StrongARMs may need to re-read information in its caches.  Any code that modifies code in other parts of memory (which Acorn discourage strongly) should call this for the appropriate address range before trying to execute the new code.  It should be noted that this SWI is relatively slow; hence use with caution in speed-dependent programs.

    Next time, I`ll summarise these tips and produce examples of their use, along with a more familiar worked example.