Page 1 of 1

Exceptional madness

Posted: Sat Mar 14, 2009 15:08
by Speedo
I seem to have found a rather obscure issue with the expat parser module - getting a segfault when an exception thrown from that module is destructed.

Main function

Code: Select all

#if OGRE_PLATFORM == OGRE_PLATFORM_WIN32
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

void ReportException(const char* const msg)
{
  MessageBox(0, msg, "An exception occured!", MB_OK | MB_ICONERROR | MB_TASKMODAL);
}

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)

#else

void ReportException(const char* const msg)
{
  std::cerr << msg << std::endl;
}

int main(int argc, char** argv)

#endif
{
  // setup logs
  Ogre::LogManager logMgr;

  logMgr.createLog("Ogre.log", true, false);
  logMgr.createLog("MekWars.log", false, false);
  logMgr.createLog("Console", false, false, true);

  logMgr.setLogDetail(Ogre::LL_BOREME);
  logMgr.getLog("MekWars.log")->setLogDetail(Ogre::LL_BOREME);
  logMgr.getLog("Console")->setLogDetail(Ogre::LL_BOREME);

  logMgr.getLog("MekWars.log")->logMessage("MechWars Version " + VersionString( ));

  try
  {
    // start and run the game
    GameStateManager game;

    GameStateManager::GameStatePtr menu(new MainMenuState);
    game.QueueStateChange(GameStateManager::Action_Push, menu);   

    game.Run( );
  }
  catch (const CEGUI::Exception& e)
  {
    std::string msg = "Exception \"" + std::string(e.getMessage( ).c_str( )) + "\", file: " +
                      std::string(e.getFileName( ).c_str( )) + " line: " +
                      boost::lexical_cast<std::string>(e.getLine( ));
    logMgr.getLog("MekWars.log")->logMessage(msg, Ogre::LML_CRITICAL);
    ReportException(msg.c_str( ));
  }
  catch (const std::exception& e)
  {
    logMgr.getLog("MekWars.log")->logMessage(e.what( ), Ogre::LML_CRITICAL);
    ReportException(e.what( ));
  }

  return 0;
}


The segfault occurs as soon control leaves the CEGUI::Exception catch block.

Here's the catch. The segfault only happens with an exception thrown when the expat parser throws for a malformed xml file. No problem with any other CEGUI exceptions that I've encountered so far. And, it only happens if the exception propogates back to the main function. Catch it as soon as it's thrown - no problem.

So then, the GameStateManager ctor

Code: Select all

GameStateManager::GameStateManager( )
: m_configMgr(new ConfigManager),
  m_root(new Ogre::Root("", "", "")),
  m_window(nullptr),
  m_guiRenderer( ),
  m_guiSystem( ),
  m_inputMgr( ),
  m_soundMgr( ),
  m_gameTime( ),
  m_lastTime(0)
{
  LoadPlugins( );
  AddResourceLocations( );
  CreateRenderSystem( );
  CreateRenderWindow( );
  CreateScene( );
  InitializeResources( );
  StartOIS( );
  StartCEGUI( );
}


And GameStateManager::StartCEGUI, where the exception is thrown (loading the scheme).

Code: Select all

void GameStateManager::StartCEGUI( )
{
  // create CEGUI components
  m_guiRenderer.reset(new CEGUI::OgreCEGUIRenderer(m_window,
    Ogre::RENDER_QUEUE_OVERLAY, false, 0, m_root->getSceneManager("default")));
  m_guiSystem.reset(new CEGUI::System(m_guiRenderer.get( )));

  CEGUI::Logger::getSingleton( ).setLoggingLevel(CEGUI::Informative);

  m_guiSystem->getResourceProvider( )->setDefaultResourceGroup("gui");

  CEGUI::SchemeManager::getSingleton( ).loadScheme("TaharezLook.scheme");
  m_guiSystem->setDefaultTooltip("TaharezLook/Tooltip");
  m_guiSystem->setDefaultMouseCursor("TaharezLook", "MouseArrow");
}


So to review:
Only exceptions from the expat parser. Only if they propogate back to main.

Just in case there was a problem with the 0.6.2 SDK I built 0.6.2 from source, still get the segfault. Here's a disassembly and call stack after the segfault: http://uberspeedo.googlepages.com/exception.jpg

I have no idea what the problem might be, but there you have it. :shock:

Posted: Sun Mar 15, 2009 09:21
by CrazyEddie
Hi, and welcome :)

Thanks for raising the issue. Due to the amount of info and what have you here, it will take some time to test this, obviously :)

I'm actually wondering if it's related to another issue we already need to investigate with regards to exceptions propagating from C++ code, through C code, and back into C++ code. Since that issue also appeared when using Expat (but apparently only on the Mac) - to link these two issues is not a great leap to make, I think.

CE.

Posted: Tue Mar 24, 2009 13:23
by CrazyEddie
Hi,

I've looked into this issue today, but unfortunately was unable to produce the same results :?

The only thing I did not do was test when running with Ogre. It's a extreme long-shot, but perhaps there is an issue there relating to Ogre's memory management functions? Although this of course does not explain why other CEGUI::Exceptions from other parts of the system, or exceptions caught places other than main, do not suffer the same issue.

I've marked the mantis ticket for this 'unable to reproduce' and set it for feedback. Although I'm not certain what other information might be useful at the moment.

I will check a couple of other things, as well as running a test under Ogre.

Very odd indeed.

CE.

Posted: Tue Mar 24, 2009 23:42
by Speedo
I spent the last while tinkering with it, I think I've got it down to a fairly minimum example. This is still using Ogre though (I've actually never used CEGUI without Ogre :| ), but hopefully now you'll be able to reproduce it well enough to see if the problem is in CEGUI or something with Ogre.

Ignore everything I said earlier it only happening when the exception propogates and so on. It's bs. :)
The key, it seems, is that it only happens when the CEGUI system is destroyed before the destruction of the exception from the expat module. In my real prog the CEGUI and Ogre system stuff is handled by smart pointers in the GameStateManager class, so that was the case.

Here's the code (meant to be run from the Ogre samples directory).

Code: Select all

#include <Ogre.h>
#include <CEGUI.h>
#include <OgreCEGUIRenderer.h>

#include <Windows.h>

using namespace Ogre;

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  try
  {
    Root* root = new Root;

    Ogre::ResourceGroupManager::getSingleton( ).addResourceLocation("../../../Media/gui", "FileSystem");
    Ogre::ResourceGroupManager::getSingleton( ).addResourceLocation("../../../Media/fonts", "FileSystem");

    if (!root->restoreConfig( ) && !root->showConfigDialog( ))
    {
      delete root;
      throw std::exception("cannot load config");
    }

    root->initialise(true, "test");
    ResourceGroupManager::getSingleton( ).initialiseAllResourceGroups( );

    SceneManager* scene = root->createSceneManager(ST_GENERIC, "default");
    Camera* cam = scene->createCamera("cam");
    Viewport* vp = root->getAutoCreatedWindow( )->addViewport(cam);

    CEGUI::OgreCEGUIRenderer* renderer = 0;
    CEGUI::System* system = 0;

    try
    {
      renderer = new CEGUI::OgreCEGUIRenderer(root->getAutoCreatedWindow( ),
        RENDER_QUEUE_OVERLAY, false, 0, scene);
      system = new CEGUI::System(renderer);

      // uncomment for exception that does not segfault
      //system->setDefaultFont((CEGUI::utf8*)"nonexistant");

      // malform the scheme file to get exception that does segfault
      CEGUI::SchemeManager::getSingleton( ).loadScheme((CEGUI::utf8*)"TaharezLookSkin.scheme");
    }
    catch (CEGUI::Exception&)
    {     
      delete system;
      delete renderer;
    }

    delete system;
    delete renderer;
    delete root;
  }
  catch (std::exception& e)
  {
    MessageBox(0, e.what( ), "Exception", MB_OK | MB_ICONERROR | MB_TASKMODAL);
  }

  return 0;
}

Posted: Wed Mar 25, 2009 10:05
by CrazyEddie
Thanks so much for updating with the latest info, and especially for providing a minimal example. I shall now see if I can get some fireworks :)

CE.

Posted: Wed Mar 25, 2009 12:49
by CrazyEddie
I have been able to reproduce the issue; I doubt it's an Ogre only thing, but from what I can tell it's a Win32 only thing - but that's not much help.

Basically, the issue is that the Expat parser module gets unloaded when you delete / destroy CEGUI::System, this causes an issue due to the caught exception having originated from that now unloaded module.

I have to dig into this a little more thoroughly and then consider our options. Obviously the Win32 mantra as far as this kind of thing goes is "don't throw exceptions from within DLLs", for my money, as advice goes, that's frankly a crock of shit, and I shall refrain from going into all out rant mode :mrgreen:

The only advice I can give currently is equally lame and amounts to don't destroy CEGUI::System when handling an exception (potentially) from a loadable module.

I'll advise further once I have suitable fixes and/or workarounds.

CE.

Posted: Wed Mar 25, 2009 15:21
by Speedo
The only advice I can give currently is equally lame and amounts to don't destroy CEGUI::System when handling an exception (potentially) from a loadable module.


Well, luckily it's fairly trivial for me to make sure that my GameStateManager instance isn't destroyed until after any fatal exceptions are caught. :)