July 30, 2004
On Thu, 29 Jul 2004 21:48:00 -0700, Russ Lewis <spamhole-2001-07-16@deming-os.org> wrote:

> Sean Kelly wrote:
>> In article <Xns9536AC639A46itsFarmer@63.105.9.61>, Farmer says...
>>
>>> So you know where the exception was thrown and where it was caught, but you don't known what happened in between.
>>> I for one, refused to use exceptions in C++ mainly for that reason. And the one time, I did use exceptions, I actually hacked the stacktrace in my code  by hand and a little help from the preprocessor.
>>
>> There have been articles written by very smart people on how to (in code)
>> generate stack traces in C++ when exceptions are thrown.  IIRC the conclusion
>> was that it's just not possible to do very well.  I agree that it would be nice
>> to have compiler support for stack traces when exceptions are thrown.
>
> It is possible to do a portable stack trace if you know the starting address and size of every function in the program.  That is something that could be provided in a portable way by the compiler.  Something like this:
>
> struct FunctionInfo {
>    char[] name;
>      // name would include module name, and, for nested
>      // or literal functions, the nested function name.
>      // Something like:
>      //    foo.bar.baz.MyFunc(int).nestedFunc1(char)
>    char[] linkname;
>      // this would be the C-compatible name, as it is
>      // in the object file
>
>    byte *startingAddr;
>    size_t size;
>    FunctionInfo[] subFunctions;
>      // nested functions and literal functions go here
> };
>
> FunctionInfo[] GetAllFunctions() {...}

I think some little help by the compiler would go a long way to making a stack trace nice and easy to implement. Then someone could write one, put it in phobos, and I could use it (I dont need it right now, I just know I will need it when I write commercial server software in D)

Regan.

-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 30, 2004
On Thu, 29 Jul 2004 21:40:02 -0700, Russ Lewis <spamhole-2001-07-16@deming-os.org> wrote:

> parabolis wrote:
>> Russ Lewis wrote:
>>
>>> I have written some code that allows you to view & dump the current stack.  My thought is that many exception types should, by default,
>>
>> Any chance you will post it?
>
> Well, what I've posted already is the distilled core of the code.  I'll post the whole program at the bottom of this message.
>
>> Why not add char[] stackDump to Exception and fill it in in the Exception construction? You should also be able to add a function printStackTrace() to Exception and all Errors will thus have the data by default.	
>
> IMHO, you shouldn't have stack trace in every Exception because some are likely to be caught and handled...and then doing a stack trace would be wasteful.  Perhaps I am wrong, though.

I agree. If some sort of compiler support was added for this then the catch could indicate stack information was required eg.

try {
}
catch(Exception e, Stack[] s) {
}

or something similar.

> Here's the complete program.  It compiles and (seems to) work on linux.
>
>> import std.thread;
>> import std.string;
>>  byte[] ViewStack(Thread t) {
>>   assert(t.stackBottom > Thread.getESP());
>>   return (cast(byte*)0)[Thread.getESP()..t.stackBottom];
>> }
>>  byte[] ViewStack() {
>>   return ViewStack(Thread.getAll()[0]);
>> }
>>  class Error_DumpsStack : Error {
>>   byte[] stackDump;
>>   this(char[] arg) {
>>     super(arg);
>>     stackDump = ViewStack().dup;
>>   }
>> }
>>  int main() {
>>   byte[] stackView = ViewStack();
>>   byte[] stackCopy = stackView.dup;
>>   try {
>>     throw new Error_DumpsStack("test");
>>   }
>>   catch(Error_DumpsStack e) {
>>     printf("stackView = %d/%p stackCopy=%d/%p stackDump=%d,%p\n", stackView,stackCopy,e.stackDump);
>>     printf("cmp(...) = %d\n", cmp(cast(char[])stackView[stackView.length/2..stackView.length],cast(char[])e.stackDump[e.stackDump.length-stackView.length/2..e.stackDump.length]));
>>   }
>>    return 0;
>> }
>



-- 
Using M2, Opera's revolutionary e-mail client: http://www.opera.com/m2/
July 30, 2004
Russ Lewis wrote:
>> Why not add char[] stackDump to Exception and fill it in in the Exception construction? You should also be able to add a function printStackTrace() to Exception and all Errors will thus have the data by default.
> 
> 
> IMHO, you shouldn't have stack trace in every Exception because some are likely to be caught and handled...and then doing a stack trace would be wasteful.  Perhaps I am wrong, though.

I can see your point. I am just not sure how one would catch an error thrown by, for example, source you do not control. Say std.utf's validate().

Also I was (and still do) think it might be possible without actually copying the entire stack itself. Provided that Exceptions chaining back through a program do not change ESP then it would be possible for my suggested printStackTrace() to simply start walking from ESP back to main().

However if ESP is changed while Exceptions propagate back then then your current implementation fails to display the part of the stack that actually caused the Error/Exception to be thrown. The only way to fix that is copy the entire stack for each new Error/Exception.
July 30, 2004
Russ Lewis wrote:
> 
> We need to add these functions to class Thread:
> 
> 
> class Thread {
> ....
>     static byte[] ViewStack() {
>       Thread t = <getCurrentThread>;
>       return t.ViewStack();
>     }
> 
>     byte[] ViewStack() {
>       // swap the array indices for STACK_GROWS_UP
>       assert(this.stackBottom > Thread.getESP());
>       return (cast(byte*)0)[Thread.getESP()..this.stackBottom];
>     }
> ....
> }

That is pretty slick code... Treating all of memory as an array, slicing it and then duplicating. :)

However I do not understand why you need a reference to the current thread (or to change std.thread.d) to do this. Shouldn't you be able to get the current thread's ESP just by using:
================================================================
    byte[] ViewStack() {
        uint stack_top;
        asm { mov stack_top, ESP; }
        return (cast(byte*)0)[stack_top..this.stackBottom]
    }
================================================================
July 30, 2004
parabolis wrote:
> I can see your point. I am just not sure how one would catch an error thrown by, for example, source you do not control. Say std.utf's validate().
> 
> Also I was (and still do) think it might be possible without actually copying the entire stack itself. Provided that Exceptions chaining back through a program do not change ESP then it would be possible for my suggested printStackTrace() to simply start walking from ESP back to main().
> 
> However if ESP is changed while Exceptions propagate back then then your current implementation fails to display the part of the stack that actually caused the Error/Exception to be thrown. The only way to fix that is copy the entire stack for each new Error/Exception.

I'm pretty sure that 'esp' is the name of the Stack Pointer register in i386.  So yeah, getESP() will probably change as soon as you leave your current stack frame.

Come to think of it, I've seen it change when I just declare another local variable:

  uint a = Thread.getESP();
  uint b = Thread.getESP();
    // b will be a+4, IIRC

Plus, when you go back many stack frames to a catch() block and then run code there, you are overwriting old stack frames and a backtrace is impossible.

I'm starting to think that the "stackDump" variable should be added to Error itself, but only some types of Error would initialize it by default.  But you could add a stack dump at any time if you wanted:

  try {
    CallLibraryFunction();
  }
  catch(Error e) {
    if(e.stackDump.length == 0)
      e.DumpStack();
  }

July 30, 2004
parabolis wrote:
> That is pretty slick code... Treating all of memory as an array, slicing it and then duplicating. :)

I was pretty proud of it when I stumbled upon it :)

> However I do not understand why you need a reference to the current thread (or to change std.thread.d) to do this. Shouldn't you be able to get the current thread's ESP just by using:
> ================================================================
>     byte[] ViewStack() {
>         uint stack_top;
>         asm { mov stack_top, ESP; }
>         return (cast(byte*)0)[stack_top..this.stackBottom]
>     }
> ================================================================

When you dump your own stack, your code is probably far better.  But I have never taken the time to learn asm, so I use library functions instead.

Well, I do think that you should be able to dump the stack from other threads.  For instance, if you are about to die, you might want to do this:

  Thread.pauseAll();
  foreach(Thread t; Thread.getAll()) {
    printf("Stack backtrace of thread %p:\n", t);
    PrintStackBacktrace(t.ViewStack());
  }
  kill_program();

July 30, 2004
Russ Lewis wrote:

> 
> When you dump your own stack, your code is probably far better.  But I have never taken the time to learn asm, so I use library functions instead.

Yeah I kind of suspected you might not know asm. That is why I sent the code. Now your code can be fast too. :)

> 
> Well, I do think that you should be able to dump the stack from other threads.  For instance, if you are about to die, you might want to do this:
> 
>   Thread.pauseAll();
>   foreach(Thread t; Thread.getAll()) {
>     printf("Stack backtrace of thread %p:\n", t);
>     PrintStackBacktrace(t.ViewStack());
>   }
>   kill_program();
> 

Sute I can see why that might be useful. You should be /absolutely/ sure nothing you do after Threa.pauseAll that results in a synchronised block being excuted however.
July 30, 2004
Russ Lewis wrote:
> 
> I'm pretty sure that 'esp' is the name of the Stack Pointer register in i386.  So yeah, getESP() will probably change as soon as you leave your current stack frame.

Yes ESP is the stack pointer and leaving the 'stack frame' means that you either called a function (call ???) or returned from a function (ret). The call instruction pushes the current IP onto the stack and ret results in a previously pushed IP being popped so ESP is pretty much guaranteed to change.

However from what I have seen D's 'stack frame' is nothing more than just the IP that is pushed during a call instruction.

(I am no expert an hope any real experts will point out any errors/over simplifications I made)

> 
> Come to think of it, I've seen it change when I just declare another local variable:
> 
>   uint a = Thread.getESP();
>   uint b = Thread.getESP();
>     // b will be a+4, IIRC

Usually local variables are stored on the stack and so allocating variables is done by moving ESP to account for the amount of variable you just allocated.

(Again - I am no expert an hope any real experts will point out any errors/over simplifications I made)

> 
> Plus, when you go back many stack frames to a catch() block and then run code there, you are overwriting old stack frames and a backtrace is impossible.

I am not yet familiar enough with D's internals to know how the try/catch/finally semantics are actually implemented.

> 
> I'm starting to think that the "stackDump" variable should be added to Error itself, but only some types of Error would initialize it by default.  But you could add a stack dump at any time if you wanted:
> 
>   try {
>     CallLibraryFunction();
>   }
>   catch(Error e) {
>     if(e.stackDump.length == 0)
>       e.DumpStack();
>   }
> 

I was writing a (rather long) post to explain why I still think my suggestion of adding a printStackTrace() function to Exception (instead of Error as Error inherits from Exception) is what you want to do.

I would implement such a function by assuming the try/catch/finally implementation does not write to the stack on the way back towards main. If this is true then all you really need to know to implement printStackTrace() is what the value of ESP was when the Error/Exception was thrown. Then if a used wants a stack trace the printStackTrace() function will provide it.

It has only a cost of 4 bytes additional memory for Exception/Error classes and it is a simple mov instruction to initialise it in the constructor. So this method essentially takes absolutely no time or memory to implement.

Again any oversights or suggestions are requested.


July 30, 2004
parabolis wrote:
> Russ Lewis wrote:
> 
>>
>> I'm pretty sure that 'esp' is the name of the Stack Pointer register in i386.  So yeah, getESP() will probably change as soon as you leave your current stack frame.
> 
> 
> Yes ESP is the stack pointer and leaving the 'stack frame' means that you either called a function (call ???) or returned from a function (ret). The call instruction pushes the current IP onto the stack and ret results in a previously pushed IP being popped so ESP is pretty much guaranteed to change.
> 
> However from what I have seen D's 'stack frame' is nothing more than just the IP that is pushed during a call instruction.
> 
> (I am no expert an hope any real experts will point out any errors/over simplifications I made)
> 
>>
>> Come to think of it, I've seen it change when I just declare another local variable:
>>
>>   uint a = Thread.getESP();
>>   uint b = Thread.getESP();
>>     // b will be a+4, IIRC
> 
> 
> Usually local variables are stored on the stack and so allocating variables is done by moving ESP to account for the amount of variable you just allocated.
> 
> (Again - I am no expert an hope any real experts will point out any errors/over simplifications I made)
> 
>>
>> Plus, when you go back many stack frames to a catch() block and then run code there, you are overwriting old stack frames and a backtrace is impossible.
> 
> 
> I am not yet familiar enough with D's internals to know how the try/catch/finally semantics are actually implemented.
> 
>>
>> I'm starting to think that the "stackDump" variable should be added to Error itself, but only some types of Error would initialize it by default.  But you could add a stack dump at any time if you wanted:
>>
>>   try {
>>     CallLibraryFunction();
>>   }
>>   catch(Error e) {
>>     if(e.stackDump.length == 0)
>>       e.DumpStack();
>>   }
>>
> 
> I was writing a (rather long) post to explain why I still think my suggestion of adding a printStackTrace() function to Exception (instead of Error as Error inherits from Exception) is what you want to do.
> 
> I would implement such a function by assuming the try/catch/finally implementation does not write to the stack on the way back towards main. If this is true then all you really need to know to implement printStackTrace() is what the value of ESP was when the Error/Exception was thrown. Then if a used wants a stack trace the printStackTrace() function will provide it.
> 
> It has only a cost of 4 bytes additional memory for Exception/Error classes and it is a simple mov instruction to initialise it in the constructor. So this method essentially takes absolutely no time or memory to implement.
> 
> Again any oversights or suggestions are requested.

As soon as your catch block calls printStackTrace(), you are creating a new stack frame that overwrites the old call stack.  The moment you call printStackTrace, it becomes impossible to do a stack backtrace from the old ESP.

July 30, 2004
Russ Lewis wrote:

> 
> As soon as your catch block calls printStackTrace(), you are creating a new stack frame that overwrites the old call stack.  The moment you call printStackTrace, it becomes impossible to do a stack backtrace from the old ESP.
> 

I am assuming the ESP is already corrupt by the time you would call printStackTrace(). Thus:
================================================================
    class Exception {
        uint esp_cache;

        this( char[] msg ) {
            asm { mov esp_cache, ESP; }
            esp_cache -= 2; // see note below
            // ...
        }
        void printStackTrace() { /* .... */ }

    class Error : Exception {
    }
================================================================
The "esp_cache -= 2" is assuming ESP reflects a value 8 bytes above the ESP before this object was created. The first is the return address and the second is the pointer passed to this().

Actually the esp_cache adjustment is more involved than just adjusting for the ESP change resulting from calling the constructor. You are going to have to walk back down the stack when subclasses are created and their constructors alter ESP before the Exception class constructor is called.

The code is just to illustrate roughly how the ESP is saved and how it would be accessed in printStackTrace().

Of course you will get strange results if you keep a reference to an Exception class and start using the stack again and then finally call printStackTrace(). Also you get strange results if you just create a new Exception to have it around and then call printStackTrace().

However it may be possible to change things around to alleviate the odd cases.