Re: [PATCH 5/5] scripts/gdb: Add meminfo command

From: Jan Kiszka
Date: Sat Jan 23 2016 - 10:22:43 EST


On 2016-01-20 12:15, Kieran Bingham wrote:
> Provide an equivalent of /proc/meminfo which should be available from
> core dumps, or crashed kernels. This should allow a debugger to identify
> if memory pressures were applicable in the instance of their issue
>
> Signed-off-by: Kieran Bingham <kieran.bingham@xxxxxxxxxx>
> ---
>
> This command has proved to be much more difficult that I first thought it
> would be!
>
> It also poses a couple of interesting issues, which is why I submit this
> patch in a much more unfinished form.

Yeah, seems so - see below ;)

>
> The meminfo implementation at fs/proc/meminfo.c makes several function calls
> to collate information, which makes duplicating here more difficult.
>
> I suspect the best option here is to not present lines of which we can not
> obtain accurate data for, (much better than presenting inaccurate information)
>
> Would this go in agreement with you?

I didn't grab the use cases yet, so just a general suggestion: if the
inaccurate information may still have some value, you can mark it as
inaccurate and still print it.

>
> Finally, do you have any ideas on the best way to manage code which
> is #ifdef'd on kernel config options? (#ifdef CONFIG_HIGHMEM for example).
>
> In a similar vein to the constants.py, I considered that we could iterate all
> of the kernel configuration options and store them in a dictionary some how.
>
> That may be awkward, however, and I wondered what ideas anyone had!

Why not convert the (relevant) configs inside constants.py into a
python variable? CONFIG_IS_ENABLED should make this easy.

>
>
> scripts/gdb/linux/constants.py.in | 22 +++++
> scripts/gdb/linux/proc.py | 173 ++++++++++++++++++++++++++++++++++++++
> 2 files changed, 195 insertions(+)
>
> diff --git a/scripts/gdb/linux/constants.py.in b/scripts/gdb/linux/constants.py.in
> index 739a15d2e984..306bd601ae4e 100644
> --- a/scripts/gdb/linux/constants.py.in
> +++ b/scripts/gdb/linux/constants.py.in
> @@ -12,8 +12,15 @@
> *
> */
>
> +#include <asm/page.h>
> +#include <asm/pgtable.h>
> +#include <asm/thread_info.h>
> +
> #include <linux/fs.h>
> +#include <linux/swap.h>
> #include <linux/mount.h>
> +#include <linux/vmalloc.h>
> +
>
> /* We need to stringify expanded macros so that they can be parsed */
>
> @@ -41,3 +48,18 @@ LX_MNT_NOATIME = MNT_NOATIME
> LX_MNT_NODIRATIME = MNT_NODIRATIME
> LX_MNT_RELATIME = MNT_RELATIME
>
> +/* asm/page.h */
> +LX_PAGE_SHIFT = XSTRING(PAGE_SHIFT)
> +lx_page_shift = gdb.parse_and_eval(LX_PAGE_SHIFT)
> +
> +/* asm/thread_info.h */
> +LX_THREAD_SIZE = XSTRING(THREAD_SIZE)
> +lx_thread_size = gdb.parse_and_eval(LX_THREAD_SIZE)
> +
> +/* linux/vmalloc.h */
> +LX_VMALLOC_TOTAL = XSTRING(VMALLOC_TOTAL)
> +lx_vmalloc_total = gdb.parse_and_eval(LX_VMALLOC_TOTAL)
> +
> +/* linux/swap.h */
> +LX_MAX_SWAPFILES = XSTRING(MAX_SWAPFILES)
> +lx_max_swapfiles = gdb.parse_and_eval(LX_MAX_SWAPFILES)
> diff --git a/scripts/gdb/linux/proc.py b/scripts/gdb/linux/proc.py
> index b79ce2a33a3d..ac9e1aac2403 100644
> --- a/scripts/gdb/linux/proc.py
> +++ b/scripts/gdb/linux/proc.py
> @@ -206,3 +206,176 @@ values of that process namespace"""
> info_opts(MNT_INFO, m_flags)))
>
> LxMounts()
> +
> +
> +bdev_type = utils.CachedType("struct block_device")
> +
> +
> +class LxMeminfo(gdb.Command):
> + """ Identify the memory usage, statistics, and availability
> +
> +Equivalent to cat /proc/meminfo on a running target """
> +
> + def __init__(self):
> + super(LxMeminfo, self).__init__("lx-meminfo", gdb.COMMAND_DATA)
> +
> + def K(self, val):
> + # Convert from PAGES to KB
> + return int(val << (constants.lx_page_shift - 10))
> +
> + def page_K(self, remote_value):
> + # Obtain page value, and Convert from PAGES to KB
> + val = int(gdb.parse_and_eval(remote_value))
> + return self.K(val)
> +
> + def gps(self, enum_zone_stat_item):
> + # Access the Global Page State structure
> + # I would prefer to read this structure in one go and then index
> + # from the enum. But we can't determine the enum values with out
> + # a call to GDB anyway so we may as well take the easy route and
> + # get the value.
> + remote_value = "vm_stat[" + enum_zone_stat_item + "].counter"
> + return int(gdb.parse_and_eval(remote_value))
> +
> + def gps_K(self, enum_zone_stat_item):
> + return self.K(self.gps(enum_zone_stat_item))
> +
> + def nr_blockdev_pages(self):
> + bdevs_head = gdb.parse_and_eval("all_bdevs")
> + pages = 0
> + for bdev in lists.items(bdev_type, "bd_list", bdevs_head):
> + pages += bdev['bd_inode']['i_mapping']['nrpages']
> + return pages
> +
> + def total_swapcache_pages(self):
> + pages = 0
> + for i in range(0, constants.lx_max_swapfiles):
> + swap_space = "swapper_spaces[" + str(i) + "].nrpages"
> + pages += int(gdb.parse_and_eval(swap_space))
> + return pages
> +
> + def vm_commit_limit(self, totalram_pages):
> + overcommit = int(gdb.parse_and_eval("sysctl_overcommit_kbytes"))
> + overcommit_ratio = int(gdb.parse_and_eval("sysctl_overcommit_ratio"))
> + total_swap_pages = int(gdb.parse_and_eval("total_swap_pages"))
> + hugetlb_total_pages = 0 # hugetlb_total_pages()!!
> +
> + if overcommit:
> + allowed = overcommit >> (constants.lx_page_shift - 10)
> + else:
> + allowed = ((totalram_pages - hugetlb_total_pages *
> + overcommit_ratio / 100))
> +
> + allowed += total_swap_pages
> + return allowed
> +
> + # Main lx-meminfo command execution
> + def invoke(self, arg, from_tty):
> + totalram = int(gdb.parse_and_eval("totalram_pages"))
> + freeram = self.gps("NR_FREE_PAGES")
> + reclaimable = self.gps("NR_SLAB_RECLAIMABLE")
> + unreclaimable = self.gps("NR_SLAB_UNRECLAIMABLE")
> + slab = reclaimable + unreclaimable
> + # for_each_zone(zone)
> + # wmark_low += zone->watermark[WMARK_LOW];
> + wmark_low = 0 # Zone parsing is unimplemented
> +
> + available = freeram - wmark_low
> + available += reclaimable - min(reclaimable / 2, wmark_low)
> +
> + bufferram = self.nr_blockdev_pages()

Something goes wrong here:

(gdb) lx-meminfo
Traceback (most recent call last):
File "/data/linux/build-dbg/scripts/gdb/linux/proc.py", line 286, in invoke
bufferram = self.nr_blockdev_pages()
File "/data/linux/build-dbg/scripts/gdb/linux/proc.py", line 246, in nr_blockdev_pages
for bdev in lists.items(bdev_type, "bd_list", bdevs_head):
File "/data/linux/build-dbg/scripts/gdb/linux/lists.py", line 26, in items
yield utils.container_of(next_item, list_type, list_location)
File "/data/linux/build-dbg/scripts/gdb/linux/utils.py", line 52, in container_of
offset_of(typeobj, member)).cast(typeobj)
File "/data/linux/build-dbg/scripts/gdb/linux/utils.py", line 46, in offset_of
element = gdb.Value(0).cast(typeobj)
RuntimeError: Argument must be a type.
Error occurred in Python command: Argument must be a type.

If you need my kernel config to reproduce, just let me know.

> + total_swapcache_pages = self.total_swapcache_pages()
> +
> + file_pages = self.gps("NR_FILE_PAGES")
> + cached = file_pages - total_swapcache_pages - bufferram
> +
> + # LRU Pages
> + active_pages_anon = self.gps("NR_ACTIVE_ANON")
> + inactive_pages_anon = self.gps("NR_INACTIVE_ANON")
> + active_pages_file = self.gps("NR_ACTIVE_FILE")
> + inactive_pages_file = self.gps("NR_INACTIVE_FILE")
> + unevictable_pages = self.gps("NR_UNEVICTABLE")
> + active_pages = active_pages_anon + active_pages_file
> + inactive_pages = inactive_pages_anon + inactive_pages_file
> +
> + totalhigh = int(gdb.parse_and_eval("totalhigh_pages"))
> + # We can't run this on a core dump file ...
> + # if running target ()
> + freehigh = int(gdb.parse_and_eval("nr_free_highpages()"))
> + # else freehigh = 0
> +
> + kernelstack = int(self.gps("NR_KERNEL_STACK") *
> + constants.lx_thread_size / 1024)
> +
> + commitlimit = self.vm_commit_limit(totalram)
> + committed_as = int(gdb.parse_and_eval("vm_committed_as.count"))
> +
> + vmalloc_total = int(constants.lx_vmalloc_total >> 10)
> +
> + gdb.write(
> + "MemTotal: {:8d} kB\n".format(self.K(totalram)) +
> + "MemFree: {:8d} kB\n".format(self.K(freeram)) +
> + "MemAvailable: {:8d} kB\n".format(self.K(available)) +
> + "Buffers: {:8d} kB\n".format(self.K(bufferram)) +
> + "Cached: {:8d} kB\n".format(self.K(cached)) +
> + "SwapCached: {:8d} kB\n".format(self.K(total_swapcache_pages)) +
> + "Active: {:8d} kB\n".format(self.K(active_pages)) +
> + "Inactive: {:8d} kB\n".format(self.K(inactive_pages)) +
> + "Active(anon): {:8d} kB\n".format(self.K(active_pages_anon)) +
> + "Inactive(anon): {:8d} kB\n".format(self.K(inactive_pages_anon)) +
> + "Active(file): {:8d} kB\n".format(self.K(active_pages_file)) +
> + "Inactive(file): {:8d} kB\n".format(self.K(inactive_pages_file)) +
> + "Unevictable: {:8d} kB\n".format(self.K(unevictable_pages)) +
> + "Mlocked: {:8d} kB\n".format(self.gps_K("NR_MLOCK"))
> + )
> + # ifdef CONFIG_HIGHMEM || core dump?
> + gdb.write(
> + "HighTotal: {:8d} kB\n".format(self.K(totalhigh)) +
> + "HighFree: {:8d} kB\n".format(self.K(freehigh)) +
> + "LowTotal: {:8d} kB\n".format(self.K(totalram-totalhigh)) +
> + "LowFree: {:8d} kB\n".format(self.K(freeram-freehigh))
> + )
> + # endif
> + # ifndef CONFIG_MMU
> + # gdb.write(
> + # mmap_pages_allocated
> + # )
> + # endif
> + gdb.write(
> + "SwapTotal: {:8d} kB\n".format(self.K(0)) +
> + "SwapFree: {:8d} kB\n".format(self.K(0)) +
> + "Dirty: {:8d} kB\n".format(self.gps_K("NR_FILE_DIRTY")) +
> + "Writeback: {:8d} kB\n".format(self.gps_K("NR_WRITEBACK")) +
> + "AnonPages: {:8d} kB\n".format(self.gps_K("NR_ANON_PAGES")) +
> + "Mapped: {:8d} kB\n".format(self.gps_K("NR_FILE_MAPPED")) +
> + "Shmem: {:8d} kB\n".format(self.gps_K("NR_SHMEM")) +
> + "Slab: {:8d} kB\n".format(self.K(slab)) +
> + "SReclaimable: {:8d} kB\n".format(self.K(reclaimable)) +
> + "SUnreclaim: {:8d} kB\n".format(self.K(unreclaimable)) +
> + "KernelStack: {:8d} kB\n".format(kernelstack) +
> + "PageTables: {:8d} kB\n".format(self.gps_K("NR_PAGETABLE"))
> + )
> +
> + # if CONFIG_QUICKLIST
> + # "Quicklists: {:8d} kB\n".format(self.K(quicklist)))
> +
> + gdb.write(
> + "NFS_Unstable: {:8d} kB\n".format(self.gps_K("NR_UNSTABLE_NFS")) +
> + "Bounce: {:8d} kB\n".format(self.gps_K("NR_BOUNCE")) +
> + "WritebackTmp: {:8d} kB\n".format(self.gps_K("NR_WRITEBACK_TEMP")) +
> + "CommitLimit: {:8d} kB\n".format(self.K(commitlimit)) +
> + "Committed_AS: {:8d} kB\n".format(self.K(committed_as)) +
> + "VmallocTotal: {:8d} kB\n".format(vmalloc_total) +
> + "VmallocUsed: {:8d} kB\n".format(0) +
> + "VmallocChunk: {:8d} kB\n".format(0)
> + )
> + # if CONFIG_MEMORY_FAILURE
> + # "HardwareCorrupted: %5lu kB\n"
> + # ifdef CONFIG_CMA
> + totalcma_pages = int(gdb.parse_and_eval("totalcma_pages"))
> + gdb.write(
> + "CmaTotal: {:8d} kB\n".format(self.K(totalcma_pages)) +
> + "CmaFree: {:8d} kB\n".format(self.gps_K("NR_FREE_CMA_PAGES"))
> + )
> +
> +LxMeminfo()
>

Jan

--
Siemens AG, Corporate Technology, CT RDA ITP SES-DE
Corporate Competence Center Embedded Linux