Jump to content
  • Advertisement
Sign in to follow this  
Friggle

debugger crashes when evaluating uninitialized object expression

This topic is 2162 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

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

Share this post


Link to post
Share on other sites
Advertisement
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?

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
indeed the problem occurs when calling the GetAddressOfVar() before the first call to Execute(). I'll have to add a verification for this case.

Share this post


Link to post
Share on other sites
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

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!