JUL
16
2007

Hints for static globals

First, did I say thank you to Kenny Duffus in this blog? Then Big Thanks Kenny, and the Akademy 2k7 Team!

There were hacking days @ Akademy but also at least two hacking midnights; the latter (after moving out from Glasgow's Free House pub) was used by me and Holger Schröder to realize what can be wrong at run time in case of larger KDE apps.

[image:2887 width=500]

Finally we are able to run Kexi 2.0 (alpha) on Windows as easily as on Linux:
[image:2888 width=500]

What is the problem? When you start porting you application or library to Windows (with gcc, msvc, ...) you can get a runtime error like "The application failed to initialize properly (0xc0000005)". Expect this before entering into main().

The reason for such oddity is that on Windows constructors are not executed for global static data placed in dlls. Yeah, I've heard one gently "uuuuuuu" during the Akademy's lightning talks when mentioned that ;)

So if you have anything like


FooClass foo;

in your .cpp file of your library, and FooClass is nontrivial and have method(s) that crash e.g. because of uninitialized pointers, you won't be able to even enter to a breakpoint set within main() to locate the problem.

Solution how to avoid this bug: use K_GLOBAL_STATIC macro for delayed instantiation on the heap. It's also good for efficiency, and as such it has been already advertised on Linux/Unix platforms.

Solution how to fix the problem if you found it in your just-compiled-on-windows app? You obviously need to find any places with such globals. You can analyze library's object dump or perhaps you just remember all such places in _your_ code. Anyway, I did not find a sane and worth to implement Krazy check for this kind of dangerous code.

If you're looking for a brainless way for performing detection - just check it on runtime. Let's focus on case when global member is QObject-derived.
Use gdb on Linux/Unix to set pending breakpoint for setParent():


gdb yourapp
b QObject::setParent(QObject*)
b main
r

setParent() is called from every QObject constructor and apparently the breakpoint at setParent() worked better for me than setting breakpoints directly within constructors. Note again: this has to be executed on Linux/Unix. On windows gdb cannot set breakoints this way (i.e. before entering into main).

You can set breakpoint on any other method that you know will be called during static initialization of the library on Linux/Unix.

Now the only thing you have to do is to 1) note down all the occurences when execution broke at setParent() and before main(); 2) replace their direct instantiation with
K_GLOBAL_STATIC or K_GLOBAL_STATIC_WITH_ARGS.


K_GLOBAL_STATIC(FooClass, foo)

There was at least one app in KDEEDU with this problem. Good to see some more applications running.

Comments

With mingw there is a way to set breakpoints before main()

1. start gdb with an application

gdb bin\kate

2. display infos about the loaded application

(gdb) info file
Symbols from "D:\daten\kdesdk-mingw-build/bin\kate.exe".
Local exec file:
`D:\daten\kdesdk-mingw-build/bin\kate.exe', file type pei-i386.
Entry point: 0x4012a0
0x00401000 - 0x00401a24 is .text
0x00402000 - 0x00402040 is .data
0x00403000 - 0x004030e0 is .rdata
0x00404000 - 0x004040c0 is .bss
0x00405000 - 0x004052d0 is .idata

3. to set a breakpoint at the applications entry point enter

b *0x4012a0

4. then run the application.

r

After loading all dll's the application will stop at the entry point


By rhabacker at Tue, 07/17/2007 - 12:36

>The reason for such oddity is that on Windows constructors are not executed for >global static data placed in dlls.

Do you have a real test case for this ? I've tried to verify this behavior with the appended testcase but with mingw I cannot see any problem.

----------- dll.h -------------

#ifdef MAKE_DLL
#define DLL_EXPORT __declspec(dllexport)
#else
#define DLL_EXPORT __declspec(dllimport)
#endif

class DLL_EXPORT DllTest {
public:
DllTest();
~DllTest();
void testFunc();
};

void DLL_EXPORT testFunc(void);

----------- dll.cpp -------------
#define MAKE_DLL 1

#include
#include

DllTest::DllTest()
{
printf("DllTest class constructor called\n");
}

DllTest::~DllTest()
{
printf("DllTest class destructor called\n");
}

void DllTest::testFunc()
{
printf("testfunc from dll called\n");
}

static DllTest dllTest;

void testFunc(void)
{
printf("testfunc from dll called\n");
}

---------- exe.cpp --------------

#include
#include

class ExeTest {
public:
ExeTest() { printf("exe global class instance constructor called\n"); }
~ExeTest() { printf("exe global class instance destructor called\n"); }
};

ExeTest exeTest;

int main()
{
printf("main called\n");
testFunc();
return 0;
}

--------- CMakeLists.txt ----------

add_library(dll SHARED dll.cpp)
target_link_libraries(dll)

add_executable (exe exe.cpp )
target_link_libraries(exe dll)

---------- output ---------

DllTest class constructor called
exe global class instance constructor called
main called
testfunc from dll called
exe global class instance destructor called
DllTest class destructor called


By rhabacker at Wed, 07/18/2007 - 20:50

Hmm, to be sure about gcc ask Harald - I have discussed the problem with him at a BoF. Maybe there is a difference if you do not export the DllTest class?

--
regards, Jarosław Staniek
KDE4/KOffice/Kexi development


By Jarosław Staniek at Tue, 07/24/2007 - 06:52