The function that allocates the structure _virPCIDeviceAddress is:
virPCIGetDeviceAddressFromSysfsLink().

This function is called in the following path, specifically in
virPCIGetPhysicalFunction(), which is itself called from
nodeDeviceSysfsGetPCISRIOVCaps().

[0]             [1]            [2]
-----------------------------------
                 |
                 |
nodeDeviceSysfsGetPCIRelatedDevCaps()
  nodeDeviceSysfsGetPCISRIOVCaps()

Since the tree is a bit large, I've split in 3 parts: [0], [1] and [2].
In that terminology, the bottom function in each stack calls 
nodeDeviceSysfsGetPCIRelatedDevCaps().


[0] src/node_device/node_device_driver.c

virNodeDeviceGetXMLDesc() [libvirt-nodedev.c]
  virNodeDeviceDriver node_device_driver callback <.nodeDeviceGetXMLDesc(), in 
udev/hal/remote>
    nodeDeviceGetXMLDesc()
      update_caps()


[1] src/node_device/node_device_udev.c
   virStateDriver udevStateDriver [callback register, stateInitialize()]
                               |
            --------------------------------------
            |                                    |
            |                           nodeStateInitialize()
            |                                    |
   nodeStateInitialize()              udevEnumerateDevices()
               |                                 |
udevEventHandleCallback()   ||   udevProcessDeviceListEntry
  udevAddOneDevice()
    udevGetDeviceDetails()
      udevProcessPCI()


[2] src/node_device/node_device_hal.c << deprecated >>
virStateDriver halStateDriver callbacks register [stateInitialize(), 
stateInitialize()]
                      |           libhal_ctx_set_device_added()     |-> 
multiple calls in this file 
         -----------------------                 |                  |
         |                     |                 |                  |
nodeStateInitialize() || nodeStateReload || device_added() || dev_refresh()
         |
      dev_create()      libhal_ctx_set_device_new_capability() <HAL callback>
         |                       |
gather_capabilities() || device_cap_added()
  gather_capability()
    caps_tbl[VIR_NODE_DEV_CAP_PCI_DEV]() <gather_fn ptr>
      gather_pci_cap()


We can skip the path [2] given that HAL is deprecated and only udev is 
available on Ubuntu. Trying to follow the path [1] led us to exercise the 
allocation of the structure responsible for the leak in question, but at the 
same time, udevEventHandleCallback() is able to deallocate the variable.
The trigger is the following:

while true; do udevadm trigger; done

It goes to the aforementioned described udev path ([1]), and allocates
one instance of the PCI _virPCIDeviceAddress structure per device; it is
deallocated though according to the following backtrace collected with
gdb:


#0  virNodeDevCapsDefFree (caps=0x558d824d6ba0) at 
../../../src/conf/node_device_conf.c:1709
#1  0x00007f7f1244db6c in virNodeDeviceDefFree (def=0x558d824c29c0) at 
../../../src/conf/node_device_conf.c:146
#2  0x00007f7f1244fe99 in virNodeDeviceAssignDef (devs=0x7f7ee00e68b8, 
def=0x558d824cde50)
    at ../../../src/conf/node_device_conf.c:182
#3  0x00007f7eebe1efae in udevAddOneDevice (device=device@entry=0x558d824d85e0)
    at ../../../src/node_device/node_device_udev.c:1402
#4  0x00007f7eebe202f8 in udevEventHandleCallback (watch=watch@entry=7, 
fd=<optimized out>, events=events@entry=1,
    data=data@entry=0x0) at ../../../src/node_device/node_device_udev.c:1546
#5  0x00007f7f12395cf0 in virEventPollDispatchHandles (fds=<optimized out>, 
nfds=<optimized out>)
    at ../../../src/util/vireventpoll.c:509
#6  virEventPollRunOnce () at ../../../src/util/vireventpoll.c:658
#7  0x00007f7f12394332 in virEventRunDefaultImpl () at 
../../../src/util/virevent.c:314
#8  0x00007f7f1250789d in virNetDaemonRun (dmn=0x558d8249f340) at 
../../../src/rpc/virnetdaemon.c:701


Remains to us reproduce the issue with path [0]. To exercise that path, we can 
run the following command:

while true; do virsh nodedev-dumpxml pci_0000_08_12_0 >/dev/null; done

It worked, I was able to observe the following stack on Valgrind when
exercising the leak path:

16,752 bytes in 1,047 blocks are definitely lost in loss record 580 of 586
at 0x4C2FB55: calloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
by 0x50A6283: virAlloc (viralloc.c:144)
by 0x50F1282: virPCIGetDeviceAddressFromSysfsLink (virpci.c:2453)
by 0x50F1417: virPCIGetPhysicalFunction (virpci.c:2486)
by 0x2BD50141: nodeDeviceSysfsGetPCISRIOVCaps (node_device_linux_sysfs.c:157)
by 0x2BD50141: nodeDeviceSysfsGetPCIRelatedDevCaps 
(node_device_linux_sysfs.c:225)
by 0x2BD4F1D0: update_caps (node_device_driver.c:66)
by 0x2BD4F1D0: nodeDeviceGetXMLDesc (node_device_driver.c:346)
by 0x51C838E: virNodeDeviceGetXMLDesc (libvirt-nodedev.c:292)
by 0x138D6E: remoteDispatchNodeDeviceGetXMLDesc (remote_dispatch.h:12746)
by 0x138D6E: remoteDispatchNodeDeviceGetXMLDescHelper (remote_dispatch.h:12720)
by 0x5213FE8: virNetServerProgramDispatchCall (virnetserverprogram.c:437)
by 0x5213FE8: virNetServerProgramDispatch (virnetserverprogram.c:307)
by 0x520F4F7: virNetServerProcessMsg (virnetserver.c:135)
by 0x520F4F7: virNetServerHandleJob (virnetserver.c:156)
by 0x5106565: virThreadPoolWorker (virthreadpool.c:145)
by 0x5105AE7: virThreadHelper (virthread.c:206)


This is for my libvirt 1.3.1 (Xenial) testing, but I've noticied also in 
Bionic/Eoan testing. Also, 0000:08:12.0 is a Virtual Function, so the 
reproducer requires a SR-IOV capable PCI device.
Not only the virsh nodedev command is capable of exercising the leak path; 
Openstack/Nova composes XMLs for PCI functions and can run libvirt API and so 
cause the same effect.

-- 
You received this bug notification because you are a member of Ubuntu
Bugs, which is subscribed to Ubuntu.
https://bugs.launchpad.net/bugs/1844455

Title:
  Memory leak of struct _virPCIDeviceAddress on libvirt

To manage notifications about this bug go to:
https://bugs.launchpad.net/cloud-archive/+bug/1844455/+subscriptions

-- 
ubuntu-bugs mailing list
[email protected]
https://lists.ubuntu.com/mailman/listinfo/ubuntu-bugs

Reply via email to