InvalidMemoryOperationError

From D Wiki
Revision as of 00:55, 25 January 2015 by Vladimir Panteleev (talk | contribs) (Windows: Set breakpoint before starting)
Jump to: navigation, search

An InvalidMemoryOperationError can occur when an allocation occurs during the execution of the garbage collector. This can happen when the garbage collector runs, finds an unreferenced class instance, calls its finalizer (class destructor, ~this), which in turn does something which attempts to allocate memory. Since the current implementation of the D garbage collector is not re-entrant, this causes a fatal error.

InvalidMemoryOperationError problems are difficult to debug because they do not print a stack trace (since that also requires a memory allocation), and may not occur reliably. This page describes how to find the cause of these errors.

Example test case

A simple program which will throw an InvalidMemoryOperationError:

class C
{
    ~this()
    {
        new ubyte[1024];
    }
}

void main()
{
    foreach (n; 0..100)
        new C;

    import core.memory;
    GC.collect();
}

Debugging

First, you will need to build Druntime with debug information (-g) and stack frames (-gs) enabled. Without stack frames in Druntime, you will not be able to get a reliable stack trace. D version 2.067 or newer is also required, as in older versions the onInvalidMemoryOperationError function is inlined, thus making it impossible to breakpoint.

The easiest way to do this is is to use Digger:

digger build master+CyberShadow/druntime/debug+CyberShadow/phobos/debug

This will build the latest version of D from GitHub, and pull in two branches from my fork with the necessary Makefile changes.

Once you got your project to build with the latest compiler version, see if the error still occurs - it might have been resolved by a change in D. Otherwise, build your program with -g (and -gs if you have to use -release), and continue to the section corresponding to your platform.

Windows

The easiest way to debug this problem on Windows is to use Visual Studio's debugger.

  1. Start Visual Studio, go to FileOpenProject/Solution..., and select your compiled .exe file.
  2. Press Ctrl+B to open the New Breakpoint dialog. In the Function field, type _onInvalidMemoryOperationError. Click OK.
  3. Click Start to start your program.

Your program should now break at the _onInvalidMemoryOperationError function.

Open the stack trace view. It might look like this:

>	test.exe!core.exception.onInvalidMemoryOperationError(...)  Line 531
	test.exe!gc.gc.GC.malloc(...)  Line 471
	test.exe!gc.proxy.gc_qalloc(...)  Line 217 + 0x14 bytes
	test.exe!rt.lifetime.__arrayAlloc(...)  Line 440 + 0x10 bytes
	test.exe!rt.lifetime._d_newarrayU(...)  Line 940
	test.exe!rt.lifetime._d_newarrayT(...)  Line 957 + 0x9 bytes
	test.exe!test.C.~this()  Line 5 + 0x10 bytes
	test.exe!rt.lifetime.rt_finalize2(...)  Line 1403
	test.exe!rt.lifetime.rt_finalizeFromGC(...)  Line 1434 + 0xa bytes
	test.exe!gc.gc.Gcx.sweep()  Line 2616 + 0x15 bytes
	test.exe!gc.gc.Gcx.fullcollect()  Line 2532 + 0x7 bytes
	test.exe!gc.gc.GC.fullCollect()  Line 1177 + 0xb bytes
	test.exe!gc.proxy.gc_collect()  Line 171 + 0xa bytes
	test.exe!core.memory.GC.collect()  Line 170
	test.exe!D main()  Line 15 + 0x5 bytes
	...

Note the bolded line: it indicates the cause of our problem, the allocation inside our destructor.

Linux

On Linux, you can use gdb to breakpoint onInvalidMemoryOperationError:

$ dmd -g program
$ gdb ./program
GNU gdb (Ubuntu 7.7-0ubuntu3) 7.7
Copyright (C) 2014 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./program...done.
(gdb) break onInvalidMemoryOperationError
Breakpoint 1 at 0x415d20: file src/core/exception.d, line 536.
(gdb) run
Starting program: /home/vladimir/tmp/2015-01-25/test
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".

Breakpoint 1, onInvalidMemoryOperationError (pretend_sideffect=0x40e591 <gc.gc.Gcx.mark()+185>)
    at src/core/exception.d:536
536             cast(void*) typeid(InvalidMemoryOperationError).init;
(gdb) where
#0  onInvalidMemoryOperationError (...) at src/core/exception.d:536
#1  0x000000000040b0c2 in gc.gc.GC.malloc() (...) at src/gc/gc.d:469
#2  0x0000000000402f34 in gc_qalloc (...) at src/gc/proxy.d:217
#3  0x0000000000407552 in rt.lifetime.__arrayAlloc() (...) at src/core/memory.d:366
#4  0x00000000004076c5 in _d_newarrayU (...) at src/rt/lifetime.d:939
#5  0x0000000000403ddd in _d_newarrayT (...) at src/rt/lifetime.d:957
#6  0x0000000000401d15 in program.C.__dtor() (...) at program.d:5
#7  0x00000000004192d0 in rt_finalize2 (...) at src/rt/lifetime.d:1400
#8  0x000000000041129a in rt_finalizeFromGC (...) at src/rt/lifetime.d:1434
#9  0x000000000040f002 in gc.gc.Gcx.sweep() (...) at src/gc/gc.d:2390
#10 0x000000000040f6bf in gc.gc.Gcx.fullcollect() (...) at src/gc/gc.d:2532
#11 0x000000000040c453 in gc.gc.GC.fullCollect() (...) at src/gc/gc.d:1177
#12 0x0000000000402db7 in gc_collect () at src/gc/proxy.d:171
#13 0x0000000000402a59 in core.memory.GC.collect() () at src/core/memory.d:169
#14 0x0000000000401d55 in D main () at program.d:15
...

As above, the bolded line (between rt_finalize2 and _d_newarrayT) indicates the problem.