Welcome, Guest. Please login or register.

Author Topic: From low level exception handling to high level...  (Read 9877 times)

Description:

0 Members and 1 Guest are viewing this topic.

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
From low level exception handling to high level...
« on: October 22, 2006, 03:30:39 AM »
Hi,

I am wondering:

Is it possible to write a routine for Task->tc_TrapCode that can cause a second, higher level routine to be called upon return from the exception (rte) when the system is back in User mode?

What I basically want to do is to use low level traps to invoke higher level error handling such that handling a 680x0 trap, such as divide by zero has the overall effect of calling my high level function immediately after returning to User mode, rather than trying to do something about the error in the 680x0 trap code itself. The reason for this strange request is so that I can do something like this:

Code: [Select]

// hidden in some implementation file
void throwZeroDiv()
{
  throw ZeroDivide();
}

// in 'normal' code
int exampleFunction(int a, int b)
{
  return a/b;
}

//....

try {
  exampleFunction(2,0);
} catch (ZeroDivide& zd) {
  //...
}



Now, it would be easy to call throwZeroDiv() from within the 680x0 trap as set by Task->tc_TrapCode but, of course, the stack would be the SSP rather than the USP. Consequently, the supervisor stack would be unwound and I expect it would all go mental very quickly.

So, what I need to do is write a nice low level 'intermediate' handler in assembler (most likely) which would sneakily modify the stack of the user code to fool it into calling throwZeroDiv() upon executing the rte instruction.

I can see that this would need some careful manipulation of the stack frame. Has anybody actually done anything like this?

I realise that checking to see if an argument is zero and then throwing an appropriate exception directly would seem to be the easiest solution but what I am looking for is a 'zero touch' solution that doesn't require this and therefore doesn't affect the performance of 'good' code in normal operation.
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #1 on: October 22, 2006, 04:06:43 AM »
@Piru

Cheers.

Do you ever sleep? :lol:

Quote
It could well be inside some critical system semaphore being locked, and calling your high level function would just fubar the system.


What if I could guarentee that the the tc_TrapCode would only be set to point to our "handler" above the implementation layer (by which I mean the place where all OS calls are made) ?
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #2 on: October 22, 2006, 05:13:28 PM »
@Piru

Quote
I do. Perhaps not at the same time as others. I also like blood, and... Ehh, wait, forget that.


A bit hypocritical of me, considering what time I posted. Surprised you never picked up on that :lol:

Quote

If you mean that the tc_Trapcode would be set while executing your own code. Yes, that would work. But it could still get lockup condition, unless if you really isolate ALL OS call stuff from user code (for example no user code gets to run at all with any OS lock obtained).


Well now, see that's the thing :-) The purpose of the implementation layer is to hide away the OS. It's even namespaced so that unless you are really trying to deliberately code at an OS level, you can't even reach it.

That said, OS Locks may be held within my code as a consequence of using synchronised calls (using the Lock class, for example). However, the destructor for this class guarentees that any lock it holds will be released.

Consider this:

Code: [Select]


class Lockable {
  // service class, wraps signalsemaphore
  // inherit this class to get threadsafe locking properties
  public:
    void waitLock(); // May throw ObjectDestroyed
    void freeLock();
    // ....other locking methods

  public:
    ~Lockable();

  private:
    OSNative::SignalSemaphore sem;
    bool destroyed;
  // guts...
};

void Lockable::waitLock()
{
  // if this throws ObjectDestroyed, it is considered
  // illegal for the caller to access the object again
  // in any way, shape or form.
  if (destroyed) {
    // too late
    throw ObjectDestroyed();
  }
  OSNative::ObtainSemaphore(&sem);
  if (destroyed) {
    // we got unlucky whilst waiting
    OSNative::releaseSemaphore(&sem);
    throw ObjectDestroyed();
  }
  // ok, we got the lock.
}

void Lockable::freeLock()
{
  if (!destroyed) {
    OSNative::ReleaseSemaphore(&sem);
  }
}

Lockable::~Lockable()
{
  OSNative::ObtainSemaphore(&sem);
  destroyed = true;
  OSNative::ReleaseSemaphore(&sem);

  // Any pending calls to waitLock() in concurrent threads
  // will now throw ObjectDestroyed() as soon as they obtain
  // the semaphore. Wait for the semaphore again before
  // exiting the destructor, to ensure this is the last
  // thread that will touch the object.

  OSNative::ObtainSemaphore(&sem);
  OSNative::ReleaseSemaphore(&sem);
}

// auxilliary class, used to make functions safely synchronizable:

class LockExclusive {
  public:
    LockExclusive(Lockable* obj) : item(obj) { item->waitLock(); }
    ~LockExclusive() { item->freeLock(); }

  private:
     Lockable* item;
};


The above is more or less the locking strategy my high level code uses.

To incorporate our handler, consider:

Code: [Select]

class MyClass : public Lockable {

  public:
   int riskyOperation(int a, int b);

  // ....

  private:
    int n;
}

int MyClass::riskyOperation(int a, int b)
{
  // Irrespective of how this function terminates, the
  // automatic instance of ExclusiveLock should be destroyed
  // correctly.

  LockEsclusive lock(this);
  n = a / b;
  return n;
}


This is the principle way I use locking in high level code. If something throws after 'lock' is instansiated, 'lock' itself will be destroyed and consequently will release the lock on the object it was called for (in this trivial case, our "MyClass" object).

So, if our a/b invokes our tc_TrapCode exception mechanism and 'appears' to magically throw ZeroDivide, our lock ought to be released cleanly.

Or at least, this is the idea.
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #3 on: October 22, 2006, 05:20:58 PM »
Quote

motorollin wrote:
How do you guys know all this stuff?  :-o

--
moto


I don't, that's why I'm asking ;-)
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #4 on: October 22, 2006, 05:40:57 PM »
That would be a side effect of my near schiziophrenic passion for both assembly language (low level) and C++ (other end of the scale)...

Anyway, getting back to the point, what I need to be able to do in order to at least test this idea is implement some sort of low level exception handler (in the 680x0 sense) that modifies the stackframe passed to it such that on executing the rte (return from exception) instruction, the flow of execution jumps into a function of my choosing, making it look as if the actual code went there all by itself. That function then throws the equivalent C++ exception that matches the class of error trapped by our low level code.

In other words, it would make the code appear to be modified as follows:

void throwZeroDivide()
{
  throw ZeroDivide();
}
//....

int n = a/b;

would, as if by magic, appear to be self-modifying, thus:

int n = throwZeroDivide(), a/b;

for the case where b was zero.

The issue is that I'm not sure how to modify the 680x0 stack frame in the low level handler to get the flow of execution to return to the 'wrong' place (throwZeroDivide()) and making it appear that the original code jumped there, rather than being shoved there by the trap.

Suggestions?
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #5 on: October 22, 2006, 06:31:57 PM »
That's what I mean, really. Making it jump to some other place is not complicated, but preserving any contextual data is the hard part. Furthermore, the jump has to be executed immediately after we leave the supervisor state, which is slightly trickier than doing it whilst in the supervisor state - I can only assume this involves poking the stackframe.

Not checked the smart crash sources yet. I'm just working on this as a thought experiment for now :-)

-edit-

*sigh* It's been so long since I did any 'real' programming...
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #6 on: October 22, 2006, 06:59:47 PM »
Hmm, I think I got a bit confused: I was thinking that a7 was the SSP during the trap handling but your code implies it's the USP ?

int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #7 on: October 22, 2006, 09:14:01 PM »
Right, now that makes sense :-D

Sorry, it's been a while ;-)

I don't seem to have my 680x0 UM's handy anyplace atm either...

Assuming that I write a handler that doesn't trash any registers right up until the 'throw ' is invoked, I'm wondering how much the context matters. The actual try { } block that would surround the code at the high level gives you your basic restore point anyway.

-edit-

It's quite amusing really, I only want to do all this just so I can get away with not having to test certain things before executing statements that might result in a problem :-)

I'm sure most people would be perfectly happy with 'if (b==0) { throw ZeroDivide(); }' before any such division...
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #8 on: October 22, 2006, 09:46:48 PM »
Enough theorising for now, methinks it is time to test the idea out :-)
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #9 on: October 23, 2006, 12:24:58 AM »
Quote

motorollin wrote:
I suppose that would be an advantage (and would mean I started hacking at age 3 :lol: )

--
moto


How old do you think he was when he started:


;-)
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #10 on: October 23, 2006, 01:58:47 AM »
Quote

Karlos wrote:
Enough theorising for now, methinks it is time to test the idea out :-)


Well, I tried it with a quick and dirty test program and it appears to work fine:

For those interested:

Code: [Select]

mc68020

SECTION ":0", CODE

XREF _throwBoom
XDEF _trap_68K

_trap_68K
lea    4(a7), a7
move.l #_throwBoom, 2(a7)
rte


All this does trap handler does is cause the execution to jump to a function called "throwBoom()":

Code: [Select]

/*

Evil low level -> high level exception test code

*/

#include
#include
#include


struct Boom {}; // simple exception struct

extern "C" {

void trap_68K(); // defined in trap.asm
void throwBoom() // referenced by trap_68K
{
throw Boom();
}

}


int main(int argN, char** argV)
{
// install our trap
Task* me = FindTask(0);
void* oldTrap = me->tc_TrapCode;
me->tc_TrapCode = (void*)(&trap_68K);
std::puts("Installed custom Trap");

// try it out
try {
int a = 1, b=0;
std::puts("Time to try something naughty...");
int c = a/b;
std::printf("\n%d\n",c);
}
catch (Boom) {
std::puts("Caught a Boom");
}
catch (...) {
std::puts("Caught... something anyway");
}


// restore the original trap
me->tc_TrapCode = oldTrap;
std::puts("Restored Trap");

return 0;
}


Running this produces the anticipated output:

Quote

Installed custom Trap
Time to try something naughty...
Caught a Boom
Restored Trap


In all, I'm quite happy about that. Some more detailed analysis needs to be done, however, to see what it does in more complicated examples ;-)
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #11 on: October 23, 2006, 02:19:12 AM »
Don't worry, I intend to investigate it in greater depth. The state information is largely taken care of by the C++ stack unwinding. I've managed to ascertain so far that in a slightly modified version of this example where a, b and c are declared outside the try {} block that each have the value they had right up until the exception is raised, which is what I was expecting.

If I can ensure it satisfies the same criteria as a normal C++ exception then I'll be happy enough.

Dealing with low level locks etc. is slightly beyond the scope of any language level exception processing mechanism in any event.
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #12 on: October 23, 2006, 02:33:27 AM »
Ok, I've confirmed that objects created prior to the exception   that was raised in this "underhand" way still have their destructors called during the stack unwind. In other words, it seems to be indistinguishable so far from invoking a C++ exception manually :-)
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #13 on: October 23, 2006, 12:54:45 PM »
Right...

Assuming all threads in my code (including the main one) have Task->tc_UserData pointing to a handle (containing a special identifier) that gives access to the actual Thread class instance, the next trick is:

Within the supervisor mode trap, can I call FindTask(0) safely  so that I can test to see if the executing task is one of my Thread instances?

The reason being that when I install my own trap handler, I'd like to be able to get the original tc_TrapCode if my trap decides it cannot handle the error (that is to say, find a reasonable C++ exception to throw for it).

Naturally I'd use my Thread class implementation to store the original tc_TrapCode, so that all the asm handler has to do is first ascertain if the thread is one of mine (which it should be to be even called in the first place) then if it falls through all the cmp #trapNumber, (a7) tests it can load the old tc_TrapCode from the Thread, pop it on the stack then rts to it...

I don't really want to handle things like Line A emulation errors and the like...
int p; // A
 

Offline KarlosTopic starter

  • Sockologist
  • Global Moderator
  • Hero Member
  • *****
  • Join Date: Nov 2002
  • Posts: 16879
  • Country: gb
  • Thanked: 5 times
    • Show all replies
Re: From low level exception handling to high level...
« Reply #14 on: October 23, 2006, 01:38:36 PM »
This idea is starting to shape up quite nicely :-)

From the point of view of describing a trap, do the following exceptions make sense (remember that these exception classes are meant to be meaningful on other platforms than the 68K/AmigaOS) in your opinion?

TRAP #2 (access fault) -> class SegmentationFault
TRAP #3 (address error) -> class BadAlignment
TRAP #5 (our favourite) -> class ZeroDivide
TRAP #6 (chk/chk2) -> class RangeError

these might also make sense, if the FPU is triggered to trap

TRAP #50 (FP zero divide) -> class ZeroDivide
TRAP #51 (FP underflow) -> class Underflow
TRAP #53 (FP overlflow) -> class Overflow
TRAP #54 (FP NAN) -> class InvalidOperand

Naturally, these classes fall into a heirarchy and are ultimately all derived from RuntimeError
int p; // A