[libvirt] Memory free in libvirt JNA

Claudio Bley cbley at av-test.de
Mon Oct 8 12:32:59 UTC 2012


Hi Daniel,

At Fri, 28 Sep 2012 22:34:13 +0800,
Daniel Veillard wrote:
> 
> sorry for the delay, I need to focuse one something else ATM !

Me too. So, no worries! ;)

> First do you have a small pointer indicating where in JNA that kind
> of native deallocation must take place, since most of the time JNA
> can do the marshalling all by itself ?

This effects mostly Strings. JNA takes the safe assumption that
functions are returning "const char*"s because it can't distinguish a
string (const char*) from a string (char*). See
https://github.com/twall/jna/blob/master/www/FrequentlyAskedQuestions.md#how-do-i-read-back-a-functions-string-result

So, here is a list of methods of org.libvirt.jna.Libvirt which return
a string (/probably/ a char*, not const char*) which need to be
checked:

virConnectBaselineCPU
virConnectDomainXMLFromNative
virConnectDomainXMLToNative
virConnectFindStoragePoolSources
virConnectGetCapabilities
virConnectGetHostname
virConnectGetType
virConnectGetURI
virDomainGetName
virDomainGetOSType
virDomainGetXMLDesc
virDomainSnapshotGetXMLDesc
virInterfaceGetMACString
virInterfaceGetName
virInterfaceGetXMLDesc
virNWFilterGetName
virNWFilterGetXMLDesc
virNetworkGetBridgeName
virNetworkGetName
virNetworkGetXMLDesc
virNodeDeviceGetName
virNodeDeviceGetParent
virNodeDeviceGetXMLDesc
virSecretGetUsageID
virSecretGetXMLDesc
virStoragePoolGetName
virStoragePoolGetXMLDesc
virStorageVolGetKey
virStorageVolGetName
virStorageVolGetPath
virStorageVolGetXMLDesc

> And second would you have an idea how to systematically detect such
> leaks, the kind of loop suggested to expose the issue is nor really
> practical to chase the leaks ...

I tried valgrind, but it didn't produce any output. mtrace wasn't very
helpful either.

So, I just hacked this up:

,----[ memcheck.py ]
| import gdb
| 
| allocations = {}
| 
| class AllocBreak(gdb.FinishBreakpoint):
|     def stop(self):
|         global allocations
| 
|         if self.return_value != None:
|             callstack = []
|             frame = gdb.selected_frame()
|             
|             while frame:
|                 name = frame.name()
|                 func = frame.function()
|                 sal = frame.find_sal()
| 
|                 funcname = func.print_name if func else '?'
|                 line = sal.line
|                 filename = sal.symtab.filename if sal.symtab else '?'
| 
|                 callstack.append((name, filename, line, funcname))
| 
|                 frame = frame.older()
| 
|             addr = int(str(self.return_value), 16)
|             allocations[addr] = callstack
| 
| 
| class MemAlloc (gdb.Command):
|     "Track allocations."
|     def __init__(self):
|         super(MemAlloc, self).__init__("memalloc", gdb.COMMAND_NONE)
| 
|     def invoke(self, arg, from_tty):
|         top = gdb.selected_frame()
|         frame = top.older()
| 
|         if frame:
|             func = frame.function()
| 
|             if func: #  and func.name.startswith("virAlloc"):
|                 ab = AllocBreak(top, True)
|                 ab.silent = True
| 
| 
| class MemFree(gdb.Command):
|     "Track de-allocations."
|     def __init__(self):
|         super(MemFree, self).__init__("memfree", gdb.COMMAND_NONE)
| 
|     def invoke(self, arg, from_tty):
|         global allocations
| 
|         block = gdb.selected_frame().block()
|         while block:
|             for sym in block:
|                 if sym.is_argument:
|                     addr = int(str(gdb.parse_and_eval(sym.name)), 16)
| 
|                     if addr in allocations:
|                         del allocations[addr]
| 
|                     return
|             block = block.superblock
| 
| 
| class MemReport(gdb.Command):
|     def __init__(self):
|         super(MemReport, self).__init__("memreport", gdb.COMMAND_NONE)
| 
|     def invoke(self, arg, from_tty):
|         global allocations
| 
|         nr = 1
|         for k, v in allocations.iteritems():
|             print "{0}. @{1}".format(nr, k)
|             i = 1
|             gap = 0
|             nr += 1
|             for name, filename, line, funcname in v:
|                 if name:
|                     if gap > 0:
|                         print "  #{0} ... [{1}]".format(i, gap)
|                         i += 1
|                         gap = 0
|                     print "  #{0} {1} {2} {3}:{4}".format(i, name, funcname, filename, line)
|                     i += 1
|                 else:
|                     gap += 1
|             if gap > 0:
|                 print "  #{0} ... [{1}]".format(i, gap)
| 
| MemAlloc()
| MemFree()
| MemReport()
`----

,----[ .gdbinit ]
| set pagination off
| source memcheck.py
| set breakpoint pending on
| 
| break calloc
|   commands
|     silent
|     memalloc
|     cont
|   end
| 
| break free
|   commands
|     silent
|     memfree
|     cont
|   end
| 
| break malloc
|    commands
|      silent
|      memalloc
|      cont
|    end
| 
| disable
| 
| tbreak virInitialize
|   commands
|     silent
|     finish
|   end
`----

Just drop these two files into the current directory, so that GDB will
find them.

Using this short Java program

,----[ Memcheck.java ]
| 
| package org.libvirt;
| 
| import com.sun.jna.Library;
| import com.sun.jna.Native;
| 
| import java.io.InputStream;
| import java.io.InputStreamReader;
| import java.io.BufferedReader;
| import java.io.FileInputStream;
| import java.io.IOException;
| import java.util.Set;
| import java.util.HashSet;
| 
| class Memcheck {
| 	public static void main(String[] args) throws IOException {
| 		Connect conn = null;
| 		Domain dom = null;
| 
| 		try {
| 			conn = new Connect("test:///default", false);
| 
| 			dom = conn.domainDefineXML("<domain type='test' id='2'>" + "  <name>deftest</name>"
| 			                           + "  <uuid>004b96e1-2d78-c30f-5aa5-f03c87d21e70</uuid>" + "  <memory>8388608</memory>"
| 			                           + "  <vcpu>2</vcpu>" + "  <os><type arch='i686'>hvm</type></os>"
| 			                           + "  <on_reboot>restart</on_reboot>" + "  <on_poweroff>destroy</on_poweroff>"
| 			                           + "  <on_crash>restart</on_crash>" + "</domain>");
| 
| 			System.out.println("virDomainGetSchedulerType:" + dom.getSchedulerType()[0]);
| 
| 			dom.undefine();
| 		} catch (LibvirtException e) {
| 			System.out.println("exception caught:" + e);
| 			System.out.println(e.getError());
| 		} finally {
| 			if (conn != null)
| 				try {
| 					dom.free();
| 					conn.close();
| 				} catch (LibvirtException e) {
| 				}
| 		}
| 	}
| }
`----

and running

gdb --args java -cp \
/usr/share/java/jna.jar:target/classes/:target/testclasses/ \
org.libvirt.Memcheck

with libvirt-java @efbe26d yielded this GDB output:

(gdb) run
...
0x00007fffe7dfda14 in ffi_call_unix64 () from /usr/lib/x86_64-linux-gnu/libffi.so.6
Value returned is $1 = 0
(gdb) enable
(gdb) c
...
(gdb) memreport
 1. @140737220921872
   #1 testDomainGetSchedulerType testDomainGetSchedulerType /build/buildd/libvirt-0.9.12/./src/test/test_driver.c:2679
   #2 virDomainGetSchedulerType virDomainGetSchedulerType /build/buildd/libvirt-0.9.12/./src/libvirt.c:6848
   #3 ffi_call_unix64 ? ?:0
   #4 ffi_call ? ?:0
   #5 ... [1]
   #6 Java_com_sun_jna_Function_invokePointer ? ?:0
   #7 ... [7]

>   In the meantime I pushed you initial patch, thanks !

Thank you! Evidently, this really fixed the memleak above. Yay! :)

-- 
AV-Test GmbH, Henricistraße 20, 04155 Leipzig, Germany
Phone: +49 341 265 310 19
Web:<http://www.av-test.org>

Eingetragen am / Registered at: Amtsgericht Stendal (HRB 114076)
Geschaeftsfuehrer (CEO): Andreas Marx, Guido Habicht, Maik Morgenstern




More information about the libvir-list mailing list