Advertisement

debugger crashes when evaluating uninitialized object expression

Started by July 12, 2012 07:08 PM
6 comments, last by WitchLord 12 years, 4 months ago
Hi,

I'm currently developing my own visual source level debugger for AngelScripts running in my application and during this task I stumbled upon something I need some help with.
Assume the following script code:
[source lang="csharp"]class MyTestClass
{
private int myIntVar;
}

void main()
{
...
...
MyTestClass testObj;
...
...
}[/source]
When I set a breakpoint below the line where 'testObj' is declared, i.e. after the object has been constructed and then ask the debugger for the value of the expression 'testObj' then everything goes fine: The debugger reports the object's adress and properly unfolds its members.
However, when I set the break point before this point then the debugger code in my application crashes. I've copied the corresponding code from the debugger add-on and this is the code location that fails:

[source lang="cpp"]// We start from the end, in case the same name is reused in different scopes
for( asUINT n = func->GetVarCount(); n-- > 0; )
{
if( ctx->IsVarInScope(n) && name == ctx->GetVarName(n) )
{
ptr = ctx->GetAddressOfVar(n);
typeId = ctx->GetVarTypeId(n);
break;
}
}[/source]
The code is copied from the debugger add-on code file 'debugger.cpp' inside method
[source lang="cpp"]void CDebugger::PrintValue(const std::string &expr, asIScriptContext *ctx)[/source]
The problem is that the variable 'ptr' shows 0xcdcdcdcd (indicating uninitialized heap memory) after the call to 'GetAddressOfVar' and in the sub sequent code this leads to a crash when the members shall be parsed.

Actually, the whole function is used in copy by me so I assume that the same issue arises in the debugger add-on.

Any ideas would be highly appreciated, many thanks!

Thomas
It looks like a bug in AngelScript.

What version are you using?

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Advertisement
What version are you using?[/quote]
I'm using 2.24.0.a under Visual Studio 2008
I'm not able to reproduce the bug. For me the GetAddressOfVar() is returning null before the variable declaration, and after it is returning the object pointer as it should.

I wrote the following test:


class CMyDebugger2 : public CDebugger

{
public:
CMyDebugger2() : CDebugger() {}

void Output(const std::string &str)
{
// Append output to local buffer instead of the screen
output += str;
}

void TakeCommands(asIScriptContext *ctx)
{
// Suspend the context when we reach the break point
ctx->Suspend();
}

std::string output;
};


bool Test()
{
bool fail = false;
int r;
COutStream out;
asIScriptEngine *engine;
asIScriptModule *mod;
asIScriptContext *ctx;


{
engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
engine->SetMessageCallback(asMETHOD(COutStream,Callback), &out, asCALL_THISCALL);
engine->RegisterGlobalFunction("void assert(bool)", asFUNCTION(Assert), asCALL_GENERIC);

mod = engine->GetModule("test", asGM_ALWAYS_CREATE);
mod->AddScriptSection("test",
"class CTest \n"
"{ \n"
" CTest() { value = 42; } \n"
" int value; \n"
"} \n"
"void Func() \n"
"{ \n"
" CTest t; \n"
" assert( t.value == 42 ); \n"
"} \n");
r = mod->Build();
if( r < 0 )
TEST_FAILED;

CMyDebugger2 debug;

ctx = engine->CreateContext();
ctx->SetLineCallback(asMETHOD(CMyDebugger, LineCallback), &debug, asCALL_THISCALL);

// Set a break point on the line where the object will be created
debug.InterpretCommand("b test:8", ctx);

// Set a break point after the object has been created
debug.InterpretCommand("b test:9", ctx);

ctx->Prepare(mod->GetFunctionByName("Func"));

// It will break twice on line 8. Once when setting up the function stack frame, and then on the first line that is executed
// TODO: The first SUSPEND in the bytecode should be optimized away as it is unnecessary
for( int n = 0; n < 2; n++ )
{
r = ctx->Execute();
if( r != asEXECUTION_SUSPENDED )
TEST_FAILED;

// Now we should be on the line where the object will be created created
if( ctx->GetLineNumber() != 8 )
TEST_FAILED;
else
{
// The address should be null
asIScriptObject *obj = (asIScriptObject*)ctx->GetAddressOfVar(0);
if( obj != 0 )
TEST_FAILED;

debug.PrintValue("t", ctx);
}
}

r = ctx->Execute();
if( r != asEXECUTION_SUSPENDED )
TEST_FAILED;

// Now we should be on the line after the object has been created
if( ctx->GetLineNumber() != 9 )
TEST_FAILED;
else
{
if( !ctx->IsVarInScope(0) )
TEST_FAILED;

asIScriptObject *obj = (asIScriptObject*)ctx->GetAddressOfVar(0);
if( obj == 0 )
TEST_FAILED;
if( *(int*)obj->GetAddressOfProperty(0) != 42 )
TEST_FAILED;

debug.PrintValue("t", ctx);
}

if( debug.output != "Setting break point in file 'test' at line 8\n"
"Setting break point in file 'test' at line 9\n"
"Reached break point 0 in file 'test' at line 8\n"
"test:8; void Func()\n"
"Reached break point 0 in file 'test' at line 8\n"
"test:8; void Func()\n"
"Reached break point 1 in file 'test' at line 9\n"
"test:9; void Func()\n"
"{00A0F220}\n"
" int value = 42\n" )
{
printf("%s", debug.output.c_str());
TEST_FAILED;
}

ctx->Release();

engine->Release();
}

return fail;
}


Does this work for you? Do you see how to modify the test to reproduce your problem?

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I feel I have to elaborate a little bit more on my scenario in order to put my new insights further below into a better context:

1.) My app and my stand-alone remote debugger communicate via a named pipe (full duplex mode)

2.) Whenever my app enters a new loop inside the function 'TakeCommands' and either line number, file name or callstack level have changed it sends a break command to my remote debugger.
This causes the remote debugger to switch to the new code location and relocate the execution point marker.

3.) The remote debugger holds a watch list with expressions. Each time the debugger receives a break command from my app it requests the values of all watch expressions from my app.

4.) When the debugger in my app is halted (i.e. when looping inside 'TakeCommands') it processes these value-request commands and sends back the value-strings to the remote debugger as parsed via the code section that crashes.

I did some more debugging on this (with your test script snippet above) and now realize that the problem only occurs when my remote debugger halts on the very first line of code after the script in my application has been started.

Additionally, the issue goes away when I once run the script beyond the creation point of the discussed script object (which works as long as I don't put a watch on the object name in my remote debugger, so that I won't run into the crash inside my app). After that the debugger also works correctly on the first line of code, i.e. when I re-start the script with the watch already in place on the remote debugger.

I see what the main difference between your code and mine is:

I call 'TakeCommands' once before my first call to mpCtx->Execute().
I do so in order to send my remote debugger the first 'heads-up' when a new script is started and to cause it to halt on the very first line of code. If now the debugger already has something in its watch list it will immediately request these expressions from the debugger in the app.
I.e. the code that crashes in this case is entered even before the very first call to mpCtx->Execute().

I probably need to rework my debugger-heads-up approach so that even the first break command to the debugger is sent from the line callback function, i.e. indirectly via mpCtx->Execute().

In the light of the above: Do you still think this is a potential AngelScript bug or do I just use the engine in an abnormal and incorrect way?

Regards,
Thomas
I now reworked my code to always only enter 'TakeCommands' indirectly via mpCtx->Execute().
In order to do so I initialize
[source lang="csharp"]m_action = STEP_INTO;[/source]
in my derived debugger class before each new debugger run. This causes the first 'LineCallback' call to call 'TakeCommands' in turn which is exactly what I need.

So, from my point the issue is gone, no need for any change here.

Many thanks for the help!

Regards,
Thomas
Advertisement
indeed the problem occurs when calling the GetAddressOfVar() before the first call to Execute(). I'll have to add a verification for this case.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

I've fixed the methods IsVarInScope() and GetAddressOfVar() so that they work as expected even before calling Execute(). The fix is available in revision 1363.

Thanks,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement