crash/memory.c
Dave Anderson d5b362edf7 Implement a new "offline" internal crash variable that can be set to
either "show" (the default) or "hide".  When set to "hide", certain
command output associated with offline cpus will be hidden from view,
and the output will indicate that the cpu is "[OFFLINE]".  The new
variable can be set during invocation on the crash command line via
the option "--offline [show|hide]".  During runtime, or in a .crashrc
or other crash input file, the variable can be set by entering
"set offline [show|hide]".  The commands or options that are affected
when the variable is set to "hide" are as follows:

  o  On X86_64 machines, the "bt -E" option will not search exception
     stacks associated with offline cpus.
  o  On X86_64 machines, the "mach" command will append "[OFFLINE]"
     to the addresses of IRQ and exception stacks associated with
     offline cpus.
  o  On X86_64 machines, the "mach -c" command will not display the
     cpuinfo_x86 data structure associated with offline cpus.
  o  The "help -r" option has been fixed so as to not attempt to
     display register sets of offline cpus from ELF kdump vmcores,
     compressed kdump vmcores, and ELF kdump clones created by
     "virsh dump --memory-only".
  o  The "bt -c" option will not accept an offline cpu number.
  o  The "set -c" option will not accept an offline cpu number.
  o  The "irq -s" option will not display statistics associated with
     offline cpus.
  o  The "timer" command will not display hrtimer data associated
     with offline cpus.
  o  The "timer -r" option will not display hrtimer data associated
     with offline cpus.
  o  The "ptov" command will append "[OFFLINE]" when translating a
     per-cpu address offset to a virtal address of an offline cpu.
  o  The "kmem -o" option will append "[OFFLINE]" to the base per-cpu
     virtual address of an offline cpu.
  o  The "kmem -S" option in CONFIG_SLUB kernels will not display
     per-cpu data associated with offline cpus.
  o  When a per-cpu address reference is passed to the "struct"
     command, the data structure will not be displayed for offline
     cpus.
  o  When a per-cpu symbol and cpu reference is passed to the "p"
     command, the data will not be displayed for offline cpus.
  o  When the "ps -[l|m]" option is passed the optional "-C [cpus]"
     option, the tasks queued on offline cpus are not shown.
  o  The "runq" command and the "runq [-t/-m/-g/-d]" options will not
     display runqueue data for offline cpus.
  o  The "ps" command will replace the ">" active task indicator to
     a "-" for offline cpus.

The initial system information banner and the "sys" command will
display the total number of cpus as before, but will append the count
of offline cpus.  Lastly, a fix has been made for the initialization
time determination of the maximum number of per-cpu objects queued
in a CONFIG_SLAB kmem_cache so as to continue checking all cpus
higher than the first offline cpu.  These changes in behavior are not
dependent upon the setting of the crash "offline" variable.
(qiaonuohan@cn.fujitsu.com)
2014-10-06 15:32:37 -04:00

18234 lines
493 KiB
C

/* memory.c - core analysis suite
*
* Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc.
* Copyright (C) 2002-2014 David Anderson
* Copyright (C) 2002-2014 Red Hat, Inc. All rights reserved.
* Copyright (C) 2002 Silicon Graphics, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include "defs.h"
#include <sys/mman.h>
#include <ctype.h>
#include <netinet/in.h>
struct meminfo { /* general purpose memory information structure */
ulong cache; /* used by the various memory searching/dumping */
ulong slab; /* routines. Only one of these is used per cmd */
ulong c_flags; /* so stuff whatever's helpful in here... */
ulong c_offset;
ulong c_num;
ulong s_mem;
void *s_freep;
ulong *s_index;
ulong s_inuse;
ulong cpucached_cache;
ulong cpucached_slab;
ulong inuse;
ulong order;
ulong slabsize;
ulong num_slabs;
ulong objects;
ulonglong spec_addr;
ulong flags;
ulong size;
ulong objsize;
int memtype;
int free;
int slab_offset;
char *reqname;
char *curname;
ulong *addrlist;
int *kmem_bufctl;
ulong *cpudata[NR_CPUS];
ulong *shared_array_cache;
int current_cache_index;
ulong found;
ulong retval;
char *ignore;
int errors;
int calls;
int cpu;
int cache_count;
ulong get_shared;
ulong get_totalram;
ulong get_buffers;
ulong get_slabs;
char *slab_buf;
char *cache_buf;
ulong *cache_list;
struct vmlist {
ulong addr;
ulong size;
} *vmlist;
ulong container;
int *freelist;
int freelist_index_size;
};
/*
* Search modes
*/
#define SEARCH_ULONG (0)
#define SEARCH_UINT (1)
#define SEARCH_USHORT (2)
#define SEARCH_CHARS (3)
#define SEARCH_DEFAULT (SEARCH_ULONG)
/* search mode information */
struct searchinfo {
int mode;
int vcnt;
int val;
int context;
int memtype;
int do_task_header;
int tasks_found;
struct task_context *task_context;
ulong vaddr_start;
ulong vaddr_end;
ulonglong paddr_start;
ulonglong paddr_end;
union {
/* default ulong search */
struct {
ulong value[MAXARGS];
char *opt_string[MAXARGS];
ulong mask;
} s_ulong;
/* uint search */
struct {
uint value[MAXARGS];
char *opt_string[MAXARGS];
uint mask;
} s_uint;
/* ushort search */
struct {
ushort value[MAXARGS];
char *opt_string[MAXARGS];
ushort mask;
} s_ushort;
/* string (chars) search */
struct {
char *value[MAXARGS];
int len[MAXARGS];
int started_flag; /* string search needs history */
} s_chars;
} s_parms;
char buf[BUFSIZE];
};
static char *memtype_string(int, int);
static char *error_handle_string(ulong);
static void dump_mem_map(struct meminfo *);
static void dump_mem_map_SPARSEMEM(struct meminfo *);
static void fill_mem_map_cache(ulong, ulong, char *);
static void page_flags_init(void);
static int page_flags_init_from_pageflag_names(void);
static int page_flags_init_from_pageflags_enum(void);
static int translate_page_flags(char *, ulong);
static void dump_free_pages(struct meminfo *);
static int dump_zone_page_usage(void);
static void dump_multidimensional_free_pages(struct meminfo *);
static void dump_free_pages_zones_v1(struct meminfo *);
static void dump_free_pages_zones_v2(struct meminfo *);
struct free_page_callback_data;
static int dump_zone_free_area(ulong, int, ulong, struct free_page_callback_data *);
static void dump_page_hash_table(struct meminfo *);
static void kmem_search(struct meminfo *);
static void kmem_cache_init(void);
static void kmem_cache_init_slub(void);
static ulong max_cpudata_limit(ulong, ulong *);
static void kmem_cache_downsize(void);
static int ignore_cache(struct meminfo *, char *);
static char *is_kmem_cache_addr(ulong, char *);
static char *is_kmem_cache_addr_common(ulong, char *);
static void kmem_cache_list(void);
static void dump_kmem_cache(struct meminfo *);
static void dump_kmem_cache_percpu_v1(struct meminfo *);
static void dump_kmem_cache_percpu_v2(struct meminfo *);
static void dump_kmem_cache_slub(struct meminfo *);
static void dump_kmem_cache_info_v2(struct meminfo *);
static void kmem_cache_list_common(void);
static ulong get_cpu_slab_ptr(struct meminfo *, int, ulong *);
static unsigned int oo_order(ulong);
static unsigned int oo_objects(ulong);
static char *vaddr_to_kmem_cache(ulong, char *, int);
static char *is_slab_overload_page(ulong, ulong *, char *);
static ulong vaddr_to_slab(ulong);
static void do_slab_chain(int, struct meminfo *);
static void do_slab_chain_percpu_v1(long, struct meminfo *);
static void do_slab_chain_percpu_v2(long, struct meminfo *);
static void do_slab_chain_percpu_v2_nodes(long, struct meminfo *);
static void do_slab_chain_slab_overload_page(long, struct meminfo *);
static int slab_freelist_index_size(void);
static int do_slab_slub(struct meminfo *, int);
static void do_kmem_cache_slub(struct meminfo *);
static void save_slab_data(struct meminfo *);
static int slab_data_saved(struct meminfo *);
static void dump_saved_slab_data(void);
static void dump_slab(struct meminfo *);
static void dump_slab_percpu_v1(struct meminfo *);
static void dump_slab_percpu_v2(struct meminfo *);
static void dump_slab_overload_page(struct meminfo *);
static int verify_slab_v1(struct meminfo *, ulong, int);
static int verify_slab_v2(struct meminfo *, ulong, int);
static int verify_slab_overload_page(struct meminfo *, ulong, int);
static void gather_slab_free_list(struct meminfo *);
static void gather_slab_free_list_percpu(struct meminfo *);
static void gather_slab_free_list_slab_overload_page(struct meminfo *);
static void gather_cpudata_list_v1(struct meminfo *);
static void gather_cpudata_list_v2(struct meminfo *);
static void gather_cpudata_list_v2_nodes(struct meminfo *, int);
static int check_cpudata_list(struct meminfo *, ulong);
static int check_shared_list(struct meminfo *, ulong);
static void gather_slab_cached_count(struct meminfo *);
static void dump_slab_objects(struct meminfo *);
static void dump_slab_objects_percpu(struct meminfo *);
static void dump_vmlist(struct meminfo *);
static void dump_vmap_area(struct meminfo *);
static int dump_page_lists(struct meminfo *);
static void dump_kmeminfo(void);
static int page_to_phys(ulong, physaddr_t *);
static void display_memory(ulonglong, long, ulong, int, void *);
static char *show_opt_string(struct searchinfo *);
static void display_with_pre_and_post(void *, ulonglong, struct searchinfo *);
static ulong search_ulong(ulong *, ulong, int, struct searchinfo *);
static ulong search_uint(ulong *, ulong, int, struct searchinfo *);
static ulong search_ushort(ulong *, ulong, int, struct searchinfo *);
static ulong search_chars(ulong *, ulong, int, struct searchinfo *);
static ulonglong search_ulong_p(ulong *, ulonglong, int, struct searchinfo *);
static ulonglong search_uint_p(ulong *, ulonglong, int, struct searchinfo *);
static ulonglong search_ushort_p(ulong *, ulonglong, int, struct searchinfo *);
static ulonglong search_chars_p(ulong *, ulonglong, int, struct searchinfo *);
static void search_virtual(struct searchinfo *);
static void search_physical(struct searchinfo *);
static int next_upage(struct task_context *, ulong, ulong *);
static int next_kpage(ulong, ulong *);
static int next_physpage(ulonglong, ulonglong *);
static int next_vmlist_vaddr(ulong, ulong *);
static int next_module_vaddr(ulong, ulong *);
static int next_identity_mapping(ulong, ulong *);
static int vm_area_page_dump(ulong, ulong, ulong, ulong, ulong,
struct reference *);
static void rss_page_types_init(void);
static int dump_swap_info(ulong, ulong *, ulong *);
static void swap_info_init(void);
static char *get_swapdev(ulong, char *);
static void fill_swap_info(ulong);
static char *vma_file_offset(ulong, ulong, char *);
static ssize_t read_dev_kmem(ulong, char *, long);
static void dump_memory_nodes(int);
static void dump_zone_stats(void);
#define MEMORY_NODES_DUMP (0)
#define MEMORY_NODES_INITIALIZE (1)
static void node_table_init(void);
static int compare_node_data(const void *, const void *);
static void do_vm_flags(ulonglong);
static ulonglong get_vm_flags(char *);
static void PG_reserved_flag_init(void);
static void PG_slab_flag_init(void);
static ulong nr_blockdev_pages(void);
void sparse_mem_init(void);
void dump_mem_sections(void);
void list_mem_sections(void);
ulong sparse_decode_mem_map(ulong, ulong);
char *read_mem_section(ulong);
ulong nr_to_section(ulong);
int valid_section(ulong);
int section_has_mem_map(ulong);
ulong section_mem_map_addr(ulong);
ulong valid_section_nr(ulong);
ulong pfn_to_map(ulong);
static int get_nodes_online(void);
static int next_online_node(int);
static ulong next_online_pgdat(int);
static int vm_stat_init(void);
static int vm_event_state_init(void);
static int dump_vm_stat(char *, long *, ulong);
static int dump_vm_event_state(void);
static int dump_page_states(void);
static int generic_read_dumpfile(ulonglong, void *, long, char *, ulong);
static int generic_write_dumpfile(ulonglong, void *, long, char *, ulong);
static int page_to_nid(ulong);
static int get_kmem_cache_list(ulong **);
static int get_kmem_cache_slub_data(long, struct meminfo *);
static ulong compound_head(ulong);
static long count_partial(ulong, struct meminfo *);
static ulong get_freepointer(struct meminfo *, void *);
static int count_free_objects(struct meminfo *, ulong);
char *is_slab_page(struct meminfo *, char *);
static void do_cpu_partial_slub(struct meminfo *, int);
static void do_node_lists_slub(struct meminfo *, ulong, int);
static int devmem_is_restricted(void);
static int switch_to_proc_kcore(void);
static int verify_pfn(ulong);
static void dump_per_cpu_offsets(void);
static void dump_page_flags(ulonglong);
static ulong kmem_cache_nodelists(ulong);
static void dump_hstates(void);
/*
* Memory display modes specific to this file.
*/
#define DISPLAY_8 (0x2)
#define DISPLAY_16 (0x4)
#define DISPLAY_32 (0x8)
#define DISPLAY_64 (0x10)
#define SHOW_OFFSET (0x20)
#define SYMBOLIC (0x40)
#define HEXADECIMAL (0x80)
#define DECIMAL (0x100)
#define UDECIMAL (0x200)
#define ASCII_ENDLINE (0x400)
#define NO_ASCII (0x800)
#define SLAB_CACHE (0x1000)
#define DISPLAY_ASCII (0x2000)
#define NET_ENDIAN (0x4000)
#define DISPLAY_RAW (0x8000)
#define NO_ERROR (0x10000)
#define SLAB_CACHE2 (0x20000)
#define DISPLAY_TYPES (DISPLAY_RAW|DISPLAY_ASCII|DISPLAY_8|\
DISPLAY_16|DISPLAY_32|DISPLAY_64)
#define ASCII_UNLIMITED ((ulong)(-1) >> 1)
static ulong DISPLAY_DEFAULT;
/*
* Verify that the sizeof the primitive types are reasonable.
*/
void
mem_init(void)
{
if (sizeof(char) != SIZEOF_8BIT)
error(FATAL, "unsupported sizeof(char): %d\n", sizeof(char));
if (sizeof(short) != SIZEOF_16BIT)
error(FATAL, "unsupported sizeof(short): %d\n", sizeof(short));
if ((sizeof(int) != SIZEOF_32BIT) && (sizeof(int) != SIZEOF_64BIT))
error(FATAL, "unsupported sizeof(int): %d\n", sizeof(int));
if ((sizeof(long) != SIZEOF_32BIT) && (sizeof(long) != SIZEOF_64BIT))
error(FATAL, "unsupported sizeof(long): %d\n", sizeof(long));
if (sizeof(void *) != sizeof(long))
error(FATAL, "pointer size: %d is not sizeof(long): %d\n", sizeof(void *), sizeof(long));
DISPLAY_DEFAULT = (sizeof(long) == 8) ? DISPLAY_64 : DISPLAY_32;
}
/*
* Stash a few popular offsets and some basic kernel virtual memory
* items used by routines in this file.
*/
void
vm_init(void)
{
char buf[BUFSIZE];
int i, len, dimension;
struct syment *sp_array[2];
ulong value1, value2;
char *kmem_cache_node_struct, *nodelists_field;
MEMBER_OFFSET_INIT(task_struct_mm, "task_struct", "mm");
MEMBER_OFFSET_INIT(mm_struct_mmap, "mm_struct", "mmap");
MEMBER_OFFSET_INIT(mm_struct_pgd, "mm_struct", "pgd");
MEMBER_OFFSET_INIT(mm_struct_rss, "mm_struct", "rss");
if (!VALID_MEMBER(mm_struct_rss))
MEMBER_OFFSET_INIT(mm_struct_rss, "mm_struct", "_rss");
MEMBER_OFFSET_INIT(mm_struct_anon_rss, "mm_struct", "_anon_rss");
MEMBER_OFFSET_INIT(mm_struct_file_rss, "mm_struct", "_file_rss");
if (!VALID_MEMBER(mm_struct_anon_rss)) {
MEMBER_OFFSET_INIT(mm_struct_rss_stat, "mm_struct", "rss_stat");
MEMBER_OFFSET_INIT(mm_rss_stat_count, "mm_rss_stat", "count");
}
MEMBER_OFFSET_INIT(mm_struct_total_vm, "mm_struct", "total_vm");
MEMBER_OFFSET_INIT(mm_struct_start_code, "mm_struct", "start_code");
MEMBER_OFFSET_INIT(vm_area_struct_vm_mm, "vm_area_struct", "vm_mm");
MEMBER_OFFSET_INIT(vm_area_struct_vm_next, "vm_area_struct", "vm_next");
MEMBER_OFFSET_INIT(vm_area_struct_vm_end, "vm_area_struct", "vm_end");
MEMBER_OFFSET_INIT(vm_area_struct_vm_start,
"vm_area_struct", "vm_start");
MEMBER_OFFSET_INIT(vm_area_struct_vm_flags,
"vm_area_struct", "vm_flags");
MEMBER_OFFSET_INIT(vm_area_struct_vm_file, "vm_area_struct", "vm_file");
MEMBER_OFFSET_INIT(vm_area_struct_vm_offset,
"vm_area_struct", "vm_offset");
MEMBER_OFFSET_INIT(vm_area_struct_vm_pgoff,
"vm_area_struct", "vm_pgoff");
MEMBER_SIZE_INIT(vm_area_struct_vm_flags, "vm_area_struct", "vm_flags");
MEMBER_OFFSET_INIT(vm_struct_addr, "vm_struct", "addr");
MEMBER_OFFSET_INIT(vm_struct_size, "vm_struct", "size");
MEMBER_OFFSET_INIT(vm_struct_next, "vm_struct", "next");
MEMBER_OFFSET_INIT(vmap_area_va_start, "vmap_area", "va_start");
MEMBER_OFFSET_INIT(vmap_area_va_end, "vmap_area", "va_end");
MEMBER_OFFSET_INIT(vmap_area_list, "vmap_area", "list");
MEMBER_OFFSET_INIT(vmap_area_flags, "vmap_area", "flags");
MEMBER_OFFSET_INIT(vmap_area_vm, "vmap_area", "vm");
if (INVALID_MEMBER(vmap_area_vm))
MEMBER_OFFSET_INIT(vmap_area_vm, "vmap_area", "private");
STRUCT_SIZE_INIT(vmap_area, "vmap_area");
if (VALID_MEMBER(vmap_area_va_start) &&
VALID_MEMBER(vmap_area_va_end) &&
VALID_MEMBER(vmap_area_flags) &&
VALID_MEMBER(vmap_area_list) &&
VALID_MEMBER(vmap_area_vm) &&
kernel_symbol_exists("vmap_area_list"))
vt->flags |= USE_VMAP_AREA;
MEMBER_OFFSET_INIT(page_next, "page", "next");
if (VALID_MEMBER(page_next))
MEMBER_OFFSET_INIT(page_prev, "page", "prev");
if (INVALID_MEMBER(page_next))
ANON_MEMBER_OFFSET_INIT(page_next, "page", "next");
MEMBER_OFFSET_INIT(page_list, "page", "list");
if (VALID_MEMBER(page_list)) {
ASSIGN_OFFSET(page_list_next) = OFFSET(page_list) +
OFFSET(list_head_next);
ASSIGN_OFFSET(page_list_prev) = OFFSET(page_list) +
OFFSET(list_head_prev);
}
MEMBER_OFFSET_INIT(page_next_hash, "page", "next_hash");
MEMBER_OFFSET_INIT(page_inode, "page", "inode");
MEMBER_OFFSET_INIT(page_offset, "page", "offset");
MEMBER_OFFSET_INIT(page_count, "page", "count");
if (INVALID_MEMBER(page_count)) {
MEMBER_OFFSET_INIT(page_count, "page", "_count");
if (INVALID_MEMBER(page_count))
ANON_MEMBER_OFFSET_INIT(page_count, "page", "_count");
}
MEMBER_OFFSET_INIT(page_flags, "page", "flags");
MEMBER_SIZE_INIT(page_flags, "page", "flags");
MEMBER_OFFSET_INIT(page_mapping, "page", "mapping");
if (INVALID_MEMBER(page_mapping))
ANON_MEMBER_OFFSET_INIT(page_mapping, "page", "mapping");
if (INVALID_MEMBER(page_mapping) &&
(THIS_KERNEL_VERSION < LINUX(2,6,17)) &&
MEMBER_EXISTS("page", "_mapcount"))
ASSIGN_OFFSET(page_mapping) = MEMBER_OFFSET("page", "_mapcount") +
STRUCT_SIZE("atomic_t") + sizeof(ulong);
MEMBER_OFFSET_INIT(page_index, "page", "index");
if (INVALID_MEMBER(page_index))
ANON_MEMBER_OFFSET_INIT(page_index, "page", "index");
MEMBER_OFFSET_INIT(page_buffers, "page", "buffers");
MEMBER_OFFSET_INIT(page_lru, "page", "lru");
if (INVALID_MEMBER(page_lru))
ANON_MEMBER_OFFSET_INIT(page_lru, "page", "lru");
MEMBER_OFFSET_INIT(page_pte, "page", "pte");
MEMBER_OFFSET_INIT(mm_struct_pgd, "mm_struct", "pgd");
MEMBER_OFFSET_INIT(swap_info_struct_swap_file,
"swap_info_struct", "swap_file");
MEMBER_OFFSET_INIT(swap_info_struct_swap_vfsmnt,
"swap_info_struct", "swap_vfsmnt");
MEMBER_OFFSET_INIT(swap_info_struct_flags,
"swap_info_struct", "flags");
MEMBER_OFFSET_INIT(swap_info_struct_swap_map,
"swap_info_struct", "swap_map");
MEMBER_OFFSET_INIT(swap_info_struct_swap_device,
"swap_info_struct", "swap_device");
MEMBER_OFFSET_INIT(swap_info_struct_prio, "swap_info_struct", "prio");
MEMBER_OFFSET_INIT(swap_info_struct_max, "swap_info_struct", "max");
MEMBER_OFFSET_INIT(swap_info_struct_pages, "swap_info_struct", "pages");
MEMBER_OFFSET_INIT(swap_info_struct_inuse_pages, "swap_info_struct",
"inuse_pages");
MEMBER_OFFSET_INIT(swap_info_struct_old_block_size,
"swap_info_struct", "old_block_size");
MEMBER_OFFSET_INIT(block_device_bd_inode, "block_device", "bd_inode");
MEMBER_OFFSET_INIT(block_device_bd_list, "block_device", "bd_list");
MEMBER_OFFSET_INIT(block_device_bd_disk, "block_device", "bd_disk");
MEMBER_OFFSET_INIT(inode_i_mapping, "inode", "i_mapping");
MEMBER_OFFSET_INIT(address_space_nrpages, "address_space", "nrpages");
if (INVALID_MEMBER(address_space_nrpages))
MEMBER_OFFSET_INIT(address_space_nrpages, "address_space", "__nrpages");
MEMBER_OFFSET_INIT(gendisk_major, "gendisk", "major");
MEMBER_OFFSET_INIT(gendisk_fops, "gendisk", "fops");
MEMBER_OFFSET_INIT(gendisk_disk_name, "gendisk", "disk_name");
STRUCT_SIZE_INIT(block_device, "block_device");
STRUCT_SIZE_INIT(address_space, "address_space");
STRUCT_SIZE_INIT(gendisk, "gendisk");
STRUCT_SIZE_INIT(blk_major_name, "blk_major_name");
if (VALID_STRUCT(blk_major_name)) {
MEMBER_OFFSET_INIT(blk_major_name_next, "blk_major_name",
"next");
MEMBER_OFFSET_INIT(blk_major_name_name, "blk_major_name",
"name");
MEMBER_OFFSET_INIT(blk_major_name_major, "blk_major_name",
"major");
}
STRUCT_SIZE_INIT(kmem_slab_s, "kmem_slab_s");
STRUCT_SIZE_INIT(slab_s, "slab_s");
STRUCT_SIZE_INIT(slab, "slab");
STRUCT_SIZE_INIT(kmem_cache_s, "kmem_cache_s");
STRUCT_SIZE_INIT(pgd_t, "pgd_t");
/*
* slab: overload struct slab over struct page
* https://lkml.org/lkml/2013/10/16/155
*/
if (MEMBER_EXISTS("kmem_cache", "freelist_cache")) {
vt->flags |= SLAB_OVERLOAD_PAGE;
ANON_MEMBER_OFFSET_INIT(page_s_mem, "page", "s_mem");
ANON_MEMBER_OFFSET_INIT(page_freelist, "page", "freelist");
ANON_MEMBER_OFFSET_INIT(page_active, "page", "active");
}
if (!VALID_STRUCT(kmem_slab_s) && VALID_STRUCT(slab_s)) {
vt->flags |= PERCPU_KMALLOC_V1;
MEMBER_OFFSET_INIT(kmem_cache_s_num, "kmem_cache_s", "num");
MEMBER_OFFSET_INIT(kmem_cache_s_next, "kmem_cache_s", "next");
MEMBER_OFFSET_INIT(kmem_cache_s_name, "kmem_cache_s", "name");
MEMBER_OFFSET_INIT(kmem_cache_s_objsize,
"kmem_cache_s", "objsize");
MEMBER_OFFSET_INIT(kmem_cache_s_flags, "kmem_cache_s", "flags");
MEMBER_OFFSET_INIT(kmem_cache_s_gfporder,
"kmem_cache_s", "gfporder");
MEMBER_OFFSET_INIT(kmem_cache_s_slabs,
"kmem_cache_s", "slabs");
MEMBER_OFFSET_INIT(kmem_cache_s_slabs_full,
"kmem_cache_s", "slabs_full");
MEMBER_OFFSET_INIT(kmem_cache_s_slabs_partial,
"kmem_cache_s", "slabs_partial");
MEMBER_OFFSET_INIT(kmem_cache_s_slabs_free,
"kmem_cache_s", "slabs_free");
MEMBER_OFFSET_INIT(kmem_cache_s_cpudata,
"kmem_cache_s", "cpudata");
ARRAY_LENGTH_INIT(len, NULL, "kmem_cache_s.cpudata", NULL, 0);
MEMBER_OFFSET_INIT(kmem_cache_s_colour_off,
"kmem_cache_s", "colour_off");
MEMBER_OFFSET_INIT(slab_s_list, "slab_s", "list");
MEMBER_OFFSET_INIT(slab_s_s_mem, "slab_s", "s_mem");
MEMBER_OFFSET_INIT(slab_s_inuse, "slab_s", "inuse");
MEMBER_OFFSET_INIT(slab_s_free, "slab_s", "free");
MEMBER_OFFSET_INIT(cpucache_s_avail, "cpucache_s", "avail");
MEMBER_OFFSET_INIT(cpucache_s_limit, "cpucache_s", "limit");
STRUCT_SIZE_INIT(cpucache_s, "cpucache_s");
} else if (!VALID_STRUCT(kmem_slab_s) &&
!VALID_STRUCT(slab_s) &&
(VALID_STRUCT(slab) || (vt->flags & SLAB_OVERLOAD_PAGE))) {
vt->flags |= PERCPU_KMALLOC_V2;
if (VALID_STRUCT(kmem_cache_s)) {
MEMBER_OFFSET_INIT(kmem_cache_s_num, "kmem_cache_s", "num");
MEMBER_OFFSET_INIT(kmem_cache_s_next, "kmem_cache_s", "next");
MEMBER_OFFSET_INIT(kmem_cache_s_name, "kmem_cache_s", "name");
MEMBER_OFFSET_INIT(kmem_cache_s_colour_off, "kmem_cache_s",
"colour_off");
MEMBER_OFFSET_INIT(kmem_cache_s_objsize, "kmem_cache_s",
"objsize");
MEMBER_OFFSET_INIT(kmem_cache_s_flags, "kmem_cache_s", "flags");
MEMBER_OFFSET_INIT(kmem_cache_s_gfporder,
"kmem_cache_s", "gfporder");
MEMBER_OFFSET_INIT(kmem_cache_s_lists, "kmem_cache_s", "lists");
MEMBER_OFFSET_INIT(kmem_cache_s_array, "kmem_cache_s", "array");
ARRAY_LENGTH_INIT(len, NULL, "kmem_cache_s.array", NULL, 0);
} else {
STRUCT_SIZE_INIT(kmem_cache_s, "kmem_cache");
MEMBER_OFFSET_INIT(kmem_cache_s_num, "kmem_cache", "num");
MEMBER_OFFSET_INIT(kmem_cache_s_next, "kmem_cache", "next");
if (INVALID_MEMBER(kmem_cache_s_next)) {
/*
* slab/slub unification starting in Linux 3.6.
*/
MEMBER_OFFSET_INIT(kmem_cache_s_next, "kmem_cache", "list");
MEMBER_OFFSET_INIT(kmem_cache_list, "kmem_cache", "list");
MEMBER_OFFSET_INIT(kmem_cache_name, "kmem_cache", "name");
MEMBER_OFFSET_INIT(kmem_cache_size, "kmem_cache", "size");
STRUCT_SIZE_INIT(kmem_cache, "kmem_cache");
}
MEMBER_OFFSET_INIT(kmem_cache_s_name, "kmem_cache", "name");
MEMBER_OFFSET_INIT(kmem_cache_s_colour_off, "kmem_cache",
"colour_off");
if (MEMBER_EXISTS("kmem_cache", "objsize"))
MEMBER_OFFSET_INIT(kmem_cache_s_objsize, "kmem_cache",
"objsize");
else if (MEMBER_EXISTS("kmem_cache", "buffer_size"))
MEMBER_OFFSET_INIT(kmem_cache_s_objsize, "kmem_cache",
"buffer_size");
else if (MEMBER_EXISTS("kmem_cache", "size"))
MEMBER_OFFSET_INIT(kmem_cache_s_objsize, "kmem_cache",
"size");
MEMBER_OFFSET_INIT(kmem_cache_s_flags, "kmem_cache", "flags");
MEMBER_OFFSET_INIT(kmem_cache_s_gfporder,
"kmem_cache", "gfporder");
if (MEMBER_EXISTS("kmem_cache", "lists"))
MEMBER_OFFSET_INIT(kmem_cache_s_lists, "kmem_cache", "lists");
else if (MEMBER_EXISTS("kmem_cache", "nodelists") ||
MEMBER_EXISTS("kmem_cache", "node")) {
nodelists_field = MEMBER_EXISTS("kmem_cache", "node") ?
"node" : "nodelists";
vt->flags |= PERCPU_KMALLOC_V2_NODES;
MEMBER_OFFSET_INIT(kmem_cache_s_lists, "kmem_cache", nodelists_field);
if (MEMBER_TYPE("kmem_cache", nodelists_field) == TYPE_CODE_PTR) {
int nr_node_ids;
/*
* nodelists now a pointer to an outside array
*/
vt->flags |= NODELISTS_IS_PTR;
if (kernel_symbol_exists("nr_node_ids")) {
get_symbol_data("nr_node_ids", sizeof(int),
&nr_node_ids);
vt->kmem_cache_len_nodes = nr_node_ids;
} else
vt->kmem_cache_len_nodes = 1;
} else {
/*
* This should never happen with kmem_cache.node,
* only with kmem_cache.nodelists
*/
ARRAY_LENGTH_INIT(vt->kmem_cache_len_nodes, NULL,
"kmem_cache.nodelists", NULL, 0);
}
}
MEMBER_OFFSET_INIT(kmem_cache_s_array, "kmem_cache", "array");
ARRAY_LENGTH_INIT(len, NULL, "kmem_cache.array", NULL, 0);
}
if (VALID_STRUCT(slab)) {
MEMBER_OFFSET_INIT(slab_list, "slab", "list");
MEMBER_OFFSET_INIT(slab_s_mem, "slab", "s_mem");
MEMBER_OFFSET_INIT(slab_inuse, "slab", "inuse");
MEMBER_OFFSET_INIT(slab_free, "slab", "free");
/*
* slab members were moved to an anonymous union in 2.6.39.
*/
if (INVALID_MEMBER(slab_list))
ANON_MEMBER_OFFSET_INIT(slab_list, "slab", "list");
if (INVALID_MEMBER(slab_s_mem))
ANON_MEMBER_OFFSET_INIT(slab_s_mem, "slab", "s_mem");
if (INVALID_MEMBER(slab_inuse))
ANON_MEMBER_OFFSET_INIT(slab_inuse, "slab", "inuse");
if (INVALID_MEMBER(slab_free))
ANON_MEMBER_OFFSET_INIT(slab_free, "slab", "free");
}
MEMBER_OFFSET_INIT(array_cache_avail, "array_cache", "avail");
MEMBER_OFFSET_INIT(array_cache_limit, "array_cache", "limit");
STRUCT_SIZE_INIT(array_cache, "array_cache");
/*
* kmem_list3 renamed to kmem_cache_node in kernel 3.11-rc1
*/
kmem_cache_node_struct = STRUCT_EXISTS("kmem_cache_node") ?
"kmem_cache_node" : "kmem_list3";
MEMBER_OFFSET_INIT(kmem_list3_slabs_partial,
kmem_cache_node_struct, "slabs_partial");
MEMBER_OFFSET_INIT(kmem_list3_slabs_full,
kmem_cache_node_struct, "slabs_full");
MEMBER_OFFSET_INIT(kmem_list3_slabs_free,
kmem_cache_node_struct, "slabs_free");
MEMBER_OFFSET_INIT(kmem_list3_free_objects,
kmem_cache_node_struct, "free_objects");
MEMBER_OFFSET_INIT(kmem_list3_shared, kmem_cache_node_struct, "shared");
/*
* Common to slab/slub
*/
ANON_MEMBER_OFFSET_INIT(page_slab, "page", "slab_cache");
ANON_MEMBER_OFFSET_INIT(page_slab_page, "page", "slab_page");
ANON_MEMBER_OFFSET_INIT(page_first_page, "page", "first_page");
} else if (MEMBER_EXISTS("kmem_cache", "cpu_slab") &&
STRUCT_EXISTS("kmem_cache_node")) {
vt->flags |= KMALLOC_SLUB;
STRUCT_SIZE_INIT(kmem_cache, "kmem_cache");
MEMBER_OFFSET_INIT(kmem_cache_size, "kmem_cache", "size");
MEMBER_OFFSET_INIT(kmem_cache_objsize, "kmem_cache", "objsize");
if (INVALID_MEMBER(kmem_cache_objsize))
MEMBER_OFFSET_INIT(kmem_cache_objsize, "kmem_cache",
"object_size");
MEMBER_OFFSET_INIT(kmem_cache_offset, "kmem_cache", "offset");
MEMBER_OFFSET_INIT(kmem_cache_order, "kmem_cache", "order");
MEMBER_OFFSET_INIT(kmem_cache_local_node, "kmem_cache", "local_node");
MEMBER_OFFSET_INIT(kmem_cache_objects, "kmem_cache", "objects");
MEMBER_OFFSET_INIT(kmem_cache_inuse, "kmem_cache", "inuse");
MEMBER_OFFSET_INIT(kmem_cache_align, "kmem_cache", "align");
MEMBER_OFFSET_INIT(kmem_cache_node, "kmem_cache", "node");
MEMBER_OFFSET_INIT(kmem_cache_cpu_slab, "kmem_cache", "cpu_slab");
MEMBER_OFFSET_INIT(kmem_cache_list, "kmem_cache", "list");
MEMBER_OFFSET_INIT(kmem_cache_name, "kmem_cache", "name");
MEMBER_OFFSET_INIT(kmem_cache_flags, "kmem_cache", "flags");
MEMBER_OFFSET_INIT(kmem_cache_cpu_freelist, "kmem_cache_cpu", "freelist");
MEMBER_OFFSET_INIT(kmem_cache_cpu_page, "kmem_cache_cpu", "page");
MEMBER_OFFSET_INIT(kmem_cache_cpu_node, "kmem_cache_cpu", "node");
MEMBER_OFFSET_INIT(kmem_cache_cpu_partial, "kmem_cache_cpu", "partial");
ANON_MEMBER_OFFSET_INIT(page_inuse, "page", "inuse");
ANON_MEMBER_OFFSET_INIT(page_offset, "page", "offset");
ANON_MEMBER_OFFSET_INIT(page_slab, "page", "slab");
if (INVALID_MEMBER(page_slab))
ANON_MEMBER_OFFSET_INIT(page_slab, "page", "slab_cache");
ANON_MEMBER_OFFSET_INIT(page_slab_page, "page", "slab_page");
ANON_MEMBER_OFFSET_INIT(page_first_page, "page", "first_page");
ANON_MEMBER_OFFSET_INIT(page_freelist, "page", "freelist");
if (INVALID_MEMBER(kmem_cache_objects)) {
MEMBER_OFFSET_INIT(kmem_cache_oo, "kmem_cache", "oo");
ANON_MEMBER_OFFSET_INIT(page_objects, "page", "objects");
}
if (VALID_MEMBER(kmem_cache_node)) {
ARRAY_LENGTH_INIT(len, NULL, "kmem_cache.node", NULL, 0);
vt->flags |= CONFIG_NUMA;
}
ARRAY_LENGTH_INIT(len, NULL, "kmem_cache.cpu_slab", NULL, 0);
STRUCT_SIZE_INIT(kmem_cache_node, "kmem_cache_node");
STRUCT_SIZE_INIT(kmem_cache_cpu, "kmem_cache_cpu");
MEMBER_OFFSET_INIT(kmem_cache_node_nr_partial,
"kmem_cache_node", "nr_partial");
MEMBER_OFFSET_INIT(kmem_cache_node_nr_slabs,
"kmem_cache_node", "nr_slabs");
MEMBER_OFFSET_INIT(kmem_cache_node_partial,
"kmem_cache_node", "partial");
MEMBER_OFFSET_INIT(kmem_cache_node_full,
"kmem_cache_node", "full");
} else {
MEMBER_OFFSET_INIT(kmem_cache_s_c_nextp,
"kmem_cache_s", "c_nextp");
MEMBER_OFFSET_INIT(kmem_cache_s_c_name,
"kmem_cache_s", "c_name");
MEMBER_OFFSET_INIT(kmem_cache_s_c_num,
"kmem_cache_s", "c_num");
MEMBER_OFFSET_INIT(kmem_cache_s_c_org_size,
"kmem_cache_s", "c_org_size");
MEMBER_OFFSET_INIT(kmem_cache_s_c_flags,
"kmem_cache_s", "c_flags");
MEMBER_OFFSET_INIT(kmem_cache_s_c_offset,
"kmem_cache_s", "c_offset");
MEMBER_OFFSET_INIT(kmem_cache_s_c_firstp,
"kmem_cache_s", "c_firstp");
MEMBER_OFFSET_INIT(kmem_cache_s_c_gfporder,
"kmem_cache_s", "c_gfporder");
MEMBER_OFFSET_INIT(kmem_cache_s_c_magic,
"kmem_cache_s", "c_magic");
MEMBER_OFFSET_INIT(kmem_cache_s_c_align,
"kmem_cache_s", "c_align");
MEMBER_OFFSET_INIT(kmem_slab_s_s_nextp,
"kmem_slab_s", "s_nextp");
MEMBER_OFFSET_INIT(kmem_slab_s_s_freep,
"kmem_slab_s", "s_freep");
MEMBER_OFFSET_INIT(kmem_slab_s_s_inuse,
"kmem_slab_s", "s_inuse");
MEMBER_OFFSET_INIT(kmem_slab_s_s_mem,
"kmem_slab_s", "s_mem");
MEMBER_OFFSET_INIT(kmem_slab_s_s_index,
"kmem_slab_s", "s_index");
MEMBER_OFFSET_INIT(kmem_slab_s_s_offset,
"kmem_slab_s", "s_offset");
MEMBER_OFFSET_INIT(kmem_slab_s_s_magic,
"kmem_slab_s", "s_magic");
}
if (!kt->kernel_NR_CPUS) {
if (enumerator_value("WORK_CPU_UNBOUND", (long *)&value1))
kt->kernel_NR_CPUS = (int)value1;
else if ((i = get_array_length("__per_cpu_offset", NULL, 0)))
kt->kernel_NR_CPUS = i;
else if (ARRAY_LENGTH(kmem_cache_s_cpudata))
kt->kernel_NR_CPUS = ARRAY_LENGTH(kmem_cache_s_cpudata);
else if (ARRAY_LENGTH(kmem_cache_s_array))
kt->kernel_NR_CPUS = ARRAY_LENGTH(kmem_cache_s_array);
else if (ARRAY_LENGTH(kmem_cache_cpu_slab))
kt->kernel_NR_CPUS = ARRAY_LENGTH(kmem_cache_cpu_slab);
}
if (CRASHDEBUG(1))
fprintf(fp, "kernel NR_CPUS: %d %s\n", kt->kernel_NR_CPUS,
kt->kernel_NR_CPUS ? "" : "(unknown)");
if (kt->kernel_NR_CPUS > NR_CPUS) {
error(WARNING,
"kernel-configured NR_CPUS (%d) greater than compiled-in NR_CPUS (%d)\n",
kt->kernel_NR_CPUS, NR_CPUS);
error(FATAL, "recompile crash with larger NR_CPUS\n");
}
if (machdep->init_kernel_pgd)
machdep->init_kernel_pgd();
else if (symbol_exists("swapper_pg_dir")) {
value1 = symbol_value("swapper_pg_dir");
for (i = 0; i < NR_CPUS; i++)
vt->kernel_pgd[i] = value1;
} else if (symbol_exists("cpu_pgd")) {
len = get_array_length("cpu_pgd", &dimension, 0);
if ((len == NR_CPUS) && (dimension == machdep->ptrs_per_pgd)) {
value1 = symbol_value("cpu_pgd");
for (i = 0; i < NR_CPUS; i++) {
value2 = i *
(SIZE(pgd_t) * machdep->ptrs_per_pgd);
vt->kernel_pgd[i] = value1 + value2;
}
error(WARNING,
"no swapper_pg_dir: using first entry of cpu_pgd[%d][%d]\n\n",
dimension, len);
} else {
error(WARNING,
"unrecognized dimensions: cpu_pgd[%d][%d]\n",
dimension, len);
value1 = symbol_value("cpu_pgd");
for (i = 0; i < NR_CPUS; i++)
vt->kernel_pgd[i] = value1;
error(WARNING,
"no swapper_pg_dir: using first entry of cpu_pgd[%d][%d]\n\n",
dimension, len);
}
} else
error(FATAL, "no swapper_pg_dir or cpu_pgd symbols exist?\n");
get_symbol_data("high_memory", sizeof(ulong), &vt->high_memory);
if (kernel_symbol_exists("mem_section"))
vt->flags |= SPARSEMEM;
else if (kernel_symbol_exists("mem_map")) {
get_symbol_data("mem_map", sizeof(char *), &vt->mem_map);
vt->flags |= FLATMEM;
} else
vt->flags |= DISCONTIGMEM;
sparse_mem_init();
vt->vmalloc_start = machdep->vmalloc_start();
if (IS_VMALLOC_ADDR(vt->mem_map))
vt->flags |= V_MEM_MAP;
vt->total_pages = BTOP(VTOP(vt->high_memory));
switch (get_syment_array("totalram_pages", sp_array, 2))
{
case 1:
get_symbol_data("totalram_pages", sizeof(ulong),
&vt->totalram_pages);
break;
case 2:
if (!(readmem(sp_array[0]->value, KVADDR,
&value1, sizeof(ulong),
"totalram_pages #1", RETURN_ON_ERROR)))
break;
if (!(readmem(sp_array[1]->value, KVADDR,
&value2, sizeof(ulong),
"totalram_pages #2", RETURN_ON_ERROR)))
break;
vt->totalram_pages = MAX(value1, value2);
break;
}
if (symbol_exists("totalhigh_pages")) {
switch (get_syment_array("totalhigh_pages", sp_array, 2))
{
case 1:
get_symbol_data("totalhigh_pages", sizeof(ulong),
&vt->totalhigh_pages);
break;
case 2:
if (!(readmem(sp_array[0]->value, KVADDR,
&value1, sizeof(ulong),
"totalhigh_pages #1", RETURN_ON_ERROR)))
break;
if (!(readmem(sp_array[1]->value, KVADDR,
&value2, sizeof(ulong),
"totalhigh_pages #2", RETURN_ON_ERROR)))
break;
vt->totalhigh_pages = MAX(value1, value2);
break;
}
vt->total_pages += vt->totalhigh_pages;
}
if (symbol_exists("num_physpages"))
get_symbol_data("num_physpages", sizeof(ulong),
&vt->num_physpages);
if (kernel_symbol_exists("mem_map"))
get_symbol_data("max_mapnr", sizeof(ulong), &vt->max_mapnr);
if (kernel_symbol_exists("nr_swapfiles"))
get_symbol_data("nr_swapfiles", sizeof(unsigned int),
&vt->nr_swapfiles);
STRUCT_SIZE_INIT(page, "page");
STRUCT_SIZE_INIT(free_area, "free_area");
STRUCT_SIZE_INIT(free_area_struct, "free_area_struct");
STRUCT_SIZE_INIT(zone, "zone");
STRUCT_SIZE_INIT(zone_struct, "zone_struct");
STRUCT_SIZE_INIT(kmem_bufctl_t, "kmem_bufctl_t");
STRUCT_SIZE_INIT(swap_info_struct, "swap_info_struct");
STRUCT_SIZE_INIT(mm_struct, "mm_struct");
STRUCT_SIZE_INIT(vm_area_struct, "vm_area_struct");
STRUCT_SIZE_INIT(pglist_data, "pglist_data");
if (VALID_STRUCT(pglist_data)) {
vt->flags |= ZONES;
if (symbol_exists("pgdat_list") && !IS_SPARSEMEM())
vt->flags |= NODES;
/*
* Determine the number of nodes the best way possible,
* starting with a default of 1.
*/
vt->numnodes = 1;
if (symbol_exists("numnodes"))
get_symbol_data("numnodes", sizeof(int), &vt->numnodes);
if (get_nodes_online())
vt->flags |= NODES_ONLINE;
MEMBER_OFFSET_INIT(pglist_data_node_zones,
"pglist_data", "node_zones");
MEMBER_OFFSET_INIT(pglist_data_node_mem_map,
"pglist_data", "node_mem_map");
MEMBER_OFFSET_INIT(pglist_data_node_start_paddr,
"pglist_data", "node_start_paddr");
MEMBER_OFFSET_INIT(pglist_data_node_start_mapnr,
"pglist_data", "node_start_mapnr");
MEMBER_OFFSET_INIT(pglist_data_node_size,
"pglist_data", "node_size");
MEMBER_OFFSET_INIT(pglist_data_node_id,
"pglist_data", "node_id");
MEMBER_OFFSET_INIT(pglist_data_node_next,
"pglist_data", "node_next");
MEMBER_OFFSET_INIT(pglist_data_bdata, "pglist_data", "bdata");
MEMBER_OFFSET_INIT(pglist_data_nr_zones, "pglist_data",
"nr_zones");
MEMBER_OFFSET_INIT(pglist_data_node_start_pfn, "pglist_data",
"node_start_pfn");
MEMBER_OFFSET_INIT(pglist_data_pgdat_next, "pglist_data",
"pgdat_next");
MEMBER_OFFSET_INIT(pglist_data_node_present_pages,
"pglist_data", "node_present_pages");
MEMBER_OFFSET_INIT(pglist_data_node_spanned_pages,
"pglist_data", "node_spanned_pages");
ARRAY_LENGTH_INIT(vt->nr_zones, pglist_data_node_zones,
"pglist_data.node_zones", NULL,
SIZE_OPTION(zone_struct, zone));
vt->ZONE_HIGHMEM = vt->nr_zones - 1;
if (VALID_STRUCT(zone_struct)) {
MEMBER_OFFSET_INIT(zone_struct_free_pages,
"zone_struct", "free_pages");
MEMBER_OFFSET_INIT(zone_struct_free_area,
"zone_struct", "free_area");
MEMBER_OFFSET_INIT(zone_struct_zone_pgdat,
"zone_struct", "zone_pgdat");
MEMBER_OFFSET_INIT(zone_struct_name, "zone_struct",
"name");
MEMBER_OFFSET_INIT(zone_struct_size, "zone_struct",
"size");
if (INVALID_MEMBER(zone_struct_size))
MEMBER_OFFSET_INIT(zone_struct_memsize,
"zone_struct", "memsize");
MEMBER_OFFSET_INIT(zone_struct_zone_start_pfn,
"zone_struct", "zone_start_pfn");
MEMBER_OFFSET_INIT(zone_struct_zone_start_paddr,
"zone_struct", "zone_start_paddr");
MEMBER_OFFSET_INIT(zone_struct_zone_start_mapnr,
"zone_struct", "zone_start_mapnr");
MEMBER_OFFSET_INIT(zone_struct_zone_mem_map,
"zone_struct", "zone_mem_map");
MEMBER_OFFSET_INIT(zone_struct_inactive_clean_pages,
"zone_struct", "inactive_clean_pages");
MEMBER_OFFSET_INIT(zone_struct_inactive_clean_list,
"zone_struct", "inactive_clean_list");
ARRAY_LENGTH_INIT(vt->nr_free_areas,
zone_struct_free_area, "zone_struct.free_area",
NULL, SIZE(free_area_struct));
MEMBER_OFFSET_INIT(zone_struct_inactive_dirty_pages,
"zone_struct", "inactive_dirty_pages");
MEMBER_OFFSET_INIT(zone_struct_active_pages,
"zone_struct", "active_pages");
MEMBER_OFFSET_INIT(zone_struct_pages_min,
"zone_struct", "pages_min");
MEMBER_OFFSET_INIT(zone_struct_pages_low,
"zone_struct", "pages_low");
MEMBER_OFFSET_INIT(zone_struct_pages_high,
"zone_struct", "pages_high");
vt->dump_free_pages = dump_free_pages_zones_v1;
} else if (VALID_STRUCT(zone)) {
MEMBER_OFFSET_INIT(zone_vm_stat, "zone", "vm_stat");
MEMBER_OFFSET_INIT(zone_free_pages, "zone", "free_pages");
if (INVALID_MEMBER(zone_free_pages) &&
VALID_MEMBER(zone_vm_stat)) {
long nr_free_pages = 0;
if (!enumerator_value("NR_FREE_PAGES", &nr_free_pages))
error(WARNING,
"cannot determine NR_FREE_PAGES enumerator\n");
ASSIGN_OFFSET(zone_free_pages) = OFFSET(zone_vm_stat) +
(nr_free_pages * sizeof(long));
}
MEMBER_OFFSET_INIT(zone_free_area,
"zone", "free_area");
MEMBER_OFFSET_INIT(zone_zone_pgdat,
"zone", "zone_pgdat");
MEMBER_OFFSET_INIT(zone_name, "zone",
"name");
MEMBER_OFFSET_INIT(zone_zone_mem_map,
"zone", "zone_mem_map");
MEMBER_OFFSET_INIT(zone_zone_start_pfn,
"zone", "zone_start_pfn");
MEMBER_OFFSET_INIT(zone_spanned_pages,
"zone", "spanned_pages");
MEMBER_OFFSET_INIT(zone_present_pages,
"zone", "present_pages");
MEMBER_OFFSET_INIT(zone_pages_min,
"zone", "pages_min");
MEMBER_OFFSET_INIT(zone_pages_low,
"zone", "pages_low");
MEMBER_OFFSET_INIT(zone_pages_high,
"zone", "pages_high");
MEMBER_OFFSET_INIT(zone_watermark,
"zone", "watermark");
MEMBER_OFFSET_INIT(zone_nr_active,
"zone", "nr_active");
MEMBER_OFFSET_INIT(zone_nr_inactive,
"zone", "nr_inactive");
MEMBER_OFFSET_INIT(zone_all_unreclaimable,
"zone", "all_unreclaimable");
MEMBER_OFFSET_INIT(zone_flags, "zone", "flags");
MEMBER_OFFSET_INIT(zone_pages_scanned, "zone",
"pages_scanned");
ARRAY_LENGTH_INIT(vt->nr_free_areas, zone_free_area,
"zone.free_area", NULL, SIZE(free_area));
vt->dump_free_pages = dump_free_pages_zones_v2;
}
} else
vt->numnodes = 1;
node_table_init();
sprintf(buf, "%llx", (ulonglong)
MAX((uint64_t)vt->max_mapnr * PAGESIZE(),
machdep->memory_size()));
vt->paddr_prlen = strlen(buf);
if (vt->flags & PERCPU_KMALLOC_V1)
vt->dump_kmem_cache = dump_kmem_cache_percpu_v1;
else if (vt->flags & PERCPU_KMALLOC_V2)
vt->dump_kmem_cache = dump_kmem_cache_percpu_v2;
else if (vt->flags & KMALLOC_SLUB)
vt->dump_kmem_cache = dump_kmem_cache_slub;
else
vt->dump_kmem_cache = dump_kmem_cache;
if (!(vt->flags & (NODES|ZONES))) {
get_array_length("free_area", &dimension, 0);
if (dimension)
vt->dump_free_pages = dump_multidimensional_free_pages;
else
vt->dump_free_pages = dump_free_pages;
}
if (!(vt->vma_cache = (char *)malloc(SIZE(vm_area_struct)*VMA_CACHE)))
error(FATAL, "cannot malloc vm_area_struct cache\n");
if (symbol_exists("page_hash_bits")) {
unsigned int page_hash_bits;
get_symbol_data("page_hash_bits", sizeof(unsigned int),
&page_hash_bits);
len = (1 << page_hash_bits);
builtin_array_length("page_hash_table", len, NULL);
get_symbol_data("page_hash_table", sizeof(void *),
&vt->page_hash_table);
vt->page_hash_table_len = len;
STRUCT_SIZE_INIT(page_cache_bucket, "page_cache_bucket");
if (VALID_STRUCT(page_cache_bucket))
MEMBER_OFFSET_INIT(page_cache_bucket_chain,
"page_cache_bucket", "chain");
} else if (symbol_exists("page_hash_table")) {
vt->page_hash_table = symbol_value("page_hash_table");
vt->page_hash_table_len = 0;
} else if (CRASHDEBUG(1))
error(NOTE, "page_hash_table does not exist in this kernel\n");
kmem_cache_init();
page_flags_init();
rss_page_types_init();
vt->flags |= VM_INIT;
}
/*
* This command displays the contents of memory, with the output formatted
* in several different manners. The starting address may be entered either
* symbolically or by address. The default output size is the size of a long
* data type, and the default output format is hexadecimal. When hexadecimal
* output is used, the output will be accompanied by an ASCII translation.
* These are the options:
*
* -p address argument is a physical address.
* -u address argument is a user virtual address.
* -d display output in signed decimal format (default is hexadecimal).
* -D display output in unsigned decimal format (default is hexadecimal).
* -s displays output symbolically when appropriate.
* -8 display output in 8-bit values.
* -16 display output in 16-bit values.
* -32 display output in 32-bit values (default on 32-bit machines).
* -64 display output in 64-bit values (default on 64-bit machines).
*
* The default number of items to display is 1, but a count argument, if any,
* must follow the address.
*/
void
cmd_rd(void)
{
int c, memtype;
ulong flag;
long count;
ulonglong addr, endaddr;
ulong offset;
struct syment *sp;
FILE *tmpfp;
char *outputfile;
flag = HEXADECIMAL|DISPLAY_DEFAULT;
endaddr = 0;
offset = 0;
memtype = KVADDR;
tmpfp = NULL;
outputfile = NULL;
count = -1;
while ((c = getopt(argcnt, args, "axme:r:pfudDusSNo:81:3:6:")) != EOF) {
switch(c)
{
case 'a':
flag &= ~DISPLAY_TYPES;
flag |= DISPLAY_ASCII;
break;
case '8':
flag &= ~DISPLAY_TYPES;
flag |= DISPLAY_8;
break;
case '1':
if (!STREQ(optarg, "6")) {
error(INFO,
"invalid option: %c%s\n", c, optarg);
argerrs++;
} else {
flag &= ~DISPLAY_TYPES;
flag |= DISPLAY_16;
}
break;
case '3':
if (!STREQ(optarg, "2")) {
error(INFO,
"invalid option: %c%s\n", c, optarg);
argerrs++;
} else {
flag &= ~DISPLAY_TYPES;
flag |= DISPLAY_32;
}
break;
case '6':
if (!STREQ(optarg, "4")) {
error(INFO,
"invalid option: %c%s\n", c, optarg);
argerrs++;
} else {
flag &= ~DISPLAY_TYPES;
flag |= DISPLAY_64;
}
break;
case 'e':
endaddr = htoll(optarg, FAULT_ON_ERROR, NULL);
break;
case 'r':
flag &= ~DISPLAY_TYPES;
flag |= DISPLAY_RAW;
outputfile = optarg;
if ((tmpfp = fopen(outputfile, "w")) == NULL)
error(FATAL, "cannot open output file: %s\n",
outputfile);
set_tmpfile2(tmpfp);
break;
case 's':
case 'S':
if (flag & DISPLAY_DEFAULT) {
flag |= SYMBOLIC;
if (c == 'S') {
if (flag & SLAB_CACHE)
flag |= SLAB_CACHE2;
else
flag |= SLAB_CACHE;
}
} else {
error(INFO, "-%c option"
" is only allowed with %d-bit display\n",
c, DISPLAY_DEFAULT == DISPLAY_64 ?
64 : 32);
argerrs++;
}
break;
case 'o':
offset = stol(optarg, FAULT_ON_ERROR, NULL);
flag |= SHOW_OFFSET;
break;
case 'p':
memtype &= ~(UVADDR|KVADDR|XENMACHADDR|FILEADDR);
memtype = PHYSADDR;
break;
case 'u':
memtype &= ~(KVADDR|PHYSADDR|XENMACHADDR|FILEADDR);
memtype = UVADDR;
break;
case 'd':
flag &= ~(HEXADECIMAL|DECIMAL);
flag |= DECIMAL;
break;
case 'D':
flag &= ~(HEXADECIMAL|UDECIMAL);
flag |= UDECIMAL;
break;
case 'm':
if (!(kt->flags & ARCH_XEN))
error(FATAL, "-m option only applies to xen architecture\n");
memtype &= ~(UVADDR|KVADDR|FILEADDR);
memtype = XENMACHADDR;
break;
case 'f':
if (!pc->dumpfile)
error(FATAL,
"-f option requires a dumpfile\n");
memtype &= ~(KVADDR|UVADDR|PHYSADDR|XENMACHADDR);
memtype = FILEADDR;
break;
case 'x':
flag |= NO_ASCII;
break;
case 'N':
flag |= NET_ENDIAN;
break;
default:
argerrs++;
break;
}
}
if (argerrs || !args[optind])
cmd_usage(pc->curcmd, SYNOPSIS);
if (*args[optind] == '(')
addr = evall(args[optind], FAULT_ON_ERROR, NULL);
else if (hexadecimal(args[optind], 0))
addr = htoll(args[optind], FAULT_ON_ERROR, NULL);
else if ((sp = symbol_search(args[optind])))
addr = (ulonglong)sp->value;
else {
fprintf(fp, "symbol not found: %s\n", args[optind]);
fprintf(fp, "possible alternatives:\n");
if (!symbol_query(args[optind], " ", NULL))
fprintf(fp, " (none found)\n");
return;
}
if (flag & SHOW_OFFSET)
addr += offset;
if (args[++optind])
count = stol(args[optind], FAULT_ON_ERROR, NULL);
if (count == -1) {
if (endaddr) {
long bcnt;
if (endaddr <= addr)
error(FATAL, "invalid ending address: %llx\n",
endaddr);
bcnt = endaddr - addr;
switch (flag & (DISPLAY_TYPES))
{
case DISPLAY_64:
count = bcnt/8;
break;
case DISPLAY_32:
count = bcnt/4;
break;
case DISPLAY_16:
count = bcnt/2;
break;
case DISPLAY_8:
case DISPLAY_ASCII:
case DISPLAY_RAW:
count = bcnt;
break;
}
if (bcnt == 0)
count = 1;
} else {
if ((flag & DISPLAY_TYPES) == DISPLAY_RAW)
error(FATAL, "-r option requires either a count"
" argument or the -e option\n");
count = (flag & DISPLAY_ASCII) ? ASCII_UNLIMITED : 1;
}
} else if (endaddr)
error(WARNING,
"ending address ignored when count is specified\n");
if ((flag & HEXADECIMAL) && !(flag & SYMBOLIC) && !(flag & NO_ASCII) &&
!(flag & DISPLAY_ASCII))
flag |= ASCII_ENDLINE;
if (memtype == KVADDR) {
if (!COMMON_VADDR_SPACE() && !IS_KVADDR(addr))
memtype = UVADDR;
}
display_memory(addr, count, flag, memtype, outputfile);
}
/*
* display_memory() does the work for cmd_rd(), but can (and is) called by
* other routines that want to dump raw data. Based upon the flag, the
* output format is tailored to fit in an 80-character line. Hexadecimal
* output is accompanied by an end-of-line ASCII translation.
*/
#define MAX_HEXCHARS_PER_LINE (32)
/* line locations where ASCII output starts */
#define ASCII_START_8 (51 + VADDR_PRLEN)
#define ASCII_START_16 (43 + VADDR_PRLEN)
#define ASCII_START_32 (39 + VADDR_PRLEN)
#define ASCII_START_64 (37 + VADDR_PRLEN)
#define ENTRIES_8 (16) /* number of entries per line per size */
#define ENTRIES_16 (8)
#define ENTRIES_32 (4)
#define ENTRIES_64 (2)
struct memloc { /* common holder of read memory */
uint8_t u8;
uint16_t u16;
uint32_t u32;
uint64_t u64;
uint64_t limit64;
};
static void
display_memory(ulonglong addr, long count, ulong flag, int memtype, void *opt)
{
int i, a, j;
size_t typesz, sz;
long written;
void *location;
char readtype[20];
char *addrtype;
struct memloc mem;
int displayed, per_line;
int hx, lost;
char hexchars[MAX_HEXCHARS_PER_LINE+1];
char ch;
int linelen;
char buf[BUFSIZE];
char slab[BUFSIZE];
int ascii_start;
ulong error_handle;
char *hex_64_fmt = BITS32() ? "%.*llx " : "%.*lx ";
char *dec_64_fmt = BITS32() ? "%12lld " : "%15ld ";
char *dec_u64_fmt = BITS32() ? "%12llu " : "%20lu ";
if (count <= 0)
error(FATAL, "invalid count request: %ld\n", count);
switch (memtype)
{
case KVADDR:
addrtype = "KVADDR";
break;
case UVADDR:
addrtype = "UVADDR";
break;
case PHYSADDR:
addrtype = "PHYSADDR";
break;
case XENMACHADDR:
addrtype = "XENMACHADDR";
break;
case FILEADDR:
addrtype = "FILEADDR";
break;
default:
addrtype = NULL;
break;
}
if (CRASHDEBUG(4))
fprintf(fp, "<addr: %llx count: %ld flag: %lx (%s)>\n",
addr, count, flag, addrtype);
if (flag & DISPLAY_RAW) {
for (written = 0; written < count; written += sz) {
sz = BUFSIZE > (count - written) ?
(size_t)(count - written) : (size_t)BUFSIZE;
readmem(addr + written, memtype, buf, (long)sz,
"raw dump to file", FAULT_ON_ERROR);
if (fwrite(buf, 1, sz, pc->tmpfile2) != sz)
error(FATAL, "cannot write to: %s\n",
(char *)opt);
}
close_tmpfile2();
fprintf(fp, "%ld bytes copied from 0x%llx to %s\n",
count, addr, (char *)opt);
return;
}
BZERO(&mem, sizeof(struct memloc));
hx = lost = linelen = typesz = per_line = ascii_start = 0;
location = NULL;
switch (flag & (DISPLAY_TYPES))
{
case DISPLAY_64:
ascii_start = ASCII_START_64;
typesz = SIZEOF_64BIT;
location = &mem.u64;
sprintf(readtype, "64-bit %s", addrtype);
per_line = ENTRIES_64;
if (machine_type("IA64"))
mem.limit64 = kt->end;
break;
case DISPLAY_32:
ascii_start = ASCII_START_32;
typesz = SIZEOF_32BIT;
location = &mem.u32;
sprintf(readtype, "32-bit %s", addrtype);
per_line = ENTRIES_32;
break;
case DISPLAY_16:
ascii_start = ASCII_START_16;
typesz = SIZEOF_16BIT;
location = &mem.u16;
sprintf(readtype, "16-bit %s", addrtype);
per_line = ENTRIES_16;
break;
case DISPLAY_8:
ascii_start = ASCII_START_8;
typesz = SIZEOF_8BIT;
location = &mem.u8;
sprintf(readtype, "8-bit %s", addrtype);
per_line = ENTRIES_8;
break;
case DISPLAY_ASCII:
typesz = SIZEOF_8BIT;
location = &mem.u8;
sprintf(readtype, "ascii");
per_line = 60;
displayed = 0;
break;
}
if (flag & NO_ERROR)
error_handle = RETURN_ON_ERROR|QUIET;
else
error_handle = FAULT_ON_ERROR;
for (i = a = 0; i < count; i++) {
if(!readmem(addr, memtype, location, typesz,
readtype, error_handle)) {
addr += typesz;
lost += 1;
continue;
}
if (!(flag & DISPLAY_ASCII) && (((i - lost) % per_line) == 0)) {
if ((i - lost)) {
if (flag & ASCII_ENDLINE) {
fprintf(fp, " %s", hexchars);
}
fprintf(fp, "\n");
}
fprintf(fp, "%s: ",
mkstring(buf, VADDR_PRLEN, RJUST|LONGLONG_HEX,
MKSTR(&addr)));
hx = 0;
BZERO(hexchars, MAX_HEXCHARS_PER_LINE+1);
linelen = VADDR_PRLEN + strlen(": ");
}
switch (flag & DISPLAY_TYPES)
{
case DISPLAY_64:
if ((flag & (HEXADECIMAL|SYMBOLIC|DISPLAY_DEFAULT)) ==
(HEXADECIMAL|SYMBOLIC|DISPLAY_DEFAULT)) {
if ((!mem.limit64 || (mem.u64 <= mem.limit64)) &&
in_ksymbol_range(mem.u64) &&
strlen(value_to_symstr(mem.u64, buf, 0))) {
fprintf(fp, "%-16s ", buf);
linelen += strlen(buf)+1;
break;
}
if ((flag & SLAB_CACHE) &&
vaddr_to_kmem_cache(mem.u64, slab,
!VERBOSE)) {
if ((flag & SLAB_CACHE2) || CRASHDEBUG(1))
sprintf(buf, "[%llx:%s]",
(ulonglong)mem.u64,
slab);
else
sprintf(buf, "[%s]", slab);
fprintf(fp, "%-16s ", buf);
linelen += strlen(buf)+1;
break;
}
}
if (flag & HEXADECIMAL) {
fprintf(fp, hex_64_fmt, LONG_LONG_PRLEN,
mem.u64);
linelen += (LONG_LONG_PRLEN + 1);
}
else if (flag & DECIMAL)
fprintf(fp, dec_64_fmt, mem.u64);
else if (flag & UDECIMAL)
fprintf(fp, dec_u64_fmt, mem.u64);
break;
case DISPLAY_32:
if ((flag & (HEXADECIMAL|SYMBOLIC|DISPLAY_DEFAULT)) ==
(HEXADECIMAL|SYMBOLIC|DISPLAY_DEFAULT)) {
if (in_ksymbol_range(mem.u32) &&
strlen(value_to_symstr(mem.u32, buf, 0))) {
fprintf(fp, INT_PRLEN == 16 ?
"%-16s " : "%-8s ", buf);
linelen += strlen(buf)+1;
break;
}
if ((flag & SLAB_CACHE) &&
vaddr_to_kmem_cache(mem.u32, slab,
!VERBOSE)) {
if ((flag & SLAB_CACHE2) || CRASHDEBUG(1))
sprintf(buf, "[%x:%s]",
mem.u32, slab);
else
sprintf(buf, "[%s]", slab);
fprintf(fp, INT_PRLEN == 16 ?
"%-16s " : "%-8s ", buf);
linelen += strlen(buf)+1;
break;
}
}
if (flag & NET_ENDIAN)
mem.u32 = htonl(mem.u32);
if (flag & HEXADECIMAL) {
fprintf(fp, "%.*x ", INT_PRLEN, mem.u32 );
linelen += (INT_PRLEN + 1);
}
else if (flag & DECIMAL)
fprintf(fp, "%12d ", mem.u32 );
else if (flag & UDECIMAL)
fprintf(fp, "%12u ", mem.u32 );
break;
case DISPLAY_16:
if (flag & NET_ENDIAN)
mem.u16 = htons(mem.u16);
if (flag & HEXADECIMAL) {
fprintf(fp, "%.*x ", SHORT_PRLEN, mem.u16);
linelen += (SHORT_PRLEN + 1);
}
else if (flag & DECIMAL)
fprintf(fp, "%5d ", mem.u16);
else if (flag & UDECIMAL)
fprintf(fp, "%5u ", mem.u16);
break;
case DISPLAY_8:
if (flag & HEXADECIMAL) {
fprintf(fp, "%.*x ", CHAR_PRLEN, mem.u8);
linelen += (CHAR_PRLEN + 1);
}
else if (flag & DECIMAL)
fprintf(fp, "%3d ", mem.u8);
else if (flag & UDECIMAL)
fprintf(fp, "%3u ", mem.u8);
break;
case DISPLAY_ASCII:
if (isprint(mem.u8)) {
if ((a % per_line) == 0) {
if (displayed && i)
fprintf(fp, "\n");
fprintf(fp, "%s: ",
mkstring(buf, VADDR_PRLEN,
RJUST|LONGLONG_HEX,
MKSTR(&addr)));
}
fprintf(fp, "%c", mem.u8);
displayed++;
a++;
} else {
if (count == ASCII_UNLIMITED)
return;
a = 0;
}
break;
}
if (flag & HEXADECIMAL) {
char* ptr;
switch (flag & DISPLAY_TYPES)
{
case DISPLAY_64:
ptr = (char*)&mem.u64;
for (j = 0; j < SIZEOF_64BIT; j++) {
ch = ptr[j];
if ((ch >= 0x20) && (ch < 0x7f)) {
hexchars[hx++] = ch;
}
else {
hexchars[hx++] = '.';
}
}
break;
case DISPLAY_32:
ptr = (char*)&mem.u32;
for (j = 0; j < (SIZEOF_32BIT); j++) {
ch = ptr[j];
if ((ch >= 0x20) && (ch < 0x7f)) {
hexchars[hx++] = ch;
} else {
hexchars[hx++] = '.';
}
}
break;
case DISPLAY_16:
ptr = (char*)&mem.u16;
for (j = 0; j < SIZEOF_16BIT; j++) {
ch = ptr[j];
if ((ch >= 0x20) && (ch < 0x7f)) {
hexchars[hx++] = ch;
} else {
hexchars[hx++] = '.';
}
}
break;
case DISPLAY_8:
ptr = (char*)&mem.u8;
for (j = 0; j < SIZEOF_8BIT; j++) {
ch = ptr[j];
if ((ch >= 0x20) && (ch < 0x7f)) {
hexchars[hx++] = ch;
} else {
hexchars[hx++] = '.';
}
}
break;
}
}
addr += typesz;
}
if ((flag & ASCII_ENDLINE) && hx) {
pad_line(fp, ascii_start - linelen, ' ');
fprintf(fp, " %s", hexchars);
}
if (lost != count )
fprintf(fp,"\n");
}
/*
* cmd_wr() is the sister routine of cmd_rd(), used to modify the contents
* of memory. Like the "rd" command, the starting address may be entered
* either symbolically or by address. The default modification size
* is the size of a long data type. Write permission must exist on the
* /dev/mem. The flags are similar to those used by rd:
*
* -p address argument is a physical address.
* -u address argument is user virtual address (only if ambiguous).
* -k address argument is user virtual address (only if ambiguous).
* -8 write data in an 8-bit value.
* -16 write data in a 16-bit value.
* -32 write data in a 32-bit values (default on 32-bit machines).
* -64 write data in a 64-bit values (default on 64-bit machines).
*
* Only one value of a given datasize may be modified.
*/
void
cmd_wr(void)
{
int c;
ulonglong value;
int addr_entered, value_entered;
int memtype;
struct memloc mem;
ulong addr;
void *buf;
long size;
struct syment *sp;
if (DUMPFILE())
error(FATAL, "not allowed on dumpfiles\n");
memtype = 0;
buf = NULL;
addr = 0;
size = sizeof(void*);
addr_entered = value_entered = FALSE;
while ((c = getopt(argcnt, args, "fukp81:3:6:")) != EOF) {
switch(c)
{
case '8':
size = 1;
break;
case '1':
if (!STREQ(optarg, "6")) {
error(INFO,
"invalid option: %c%s\n", c, optarg);
argerrs++;
} else
size = 2;
break;
case '3':
if (!STREQ(optarg, "2")) {
error(INFO,
"invalid option: %c%s\n", c, optarg);
argerrs++;
} else
size = 4;
break;
case '6':
if (!STREQ(optarg, "4")) {
error(INFO,
"invalid option: %c%s\n", c, optarg);
argerrs++;
} else
size = 8;
break;
case 'p':
memtype &= ~(UVADDR|KVADDR|FILEADDR);
memtype = PHYSADDR;
break;
case 'u':
memtype &= ~(PHYSADDR|KVADDR|FILEADDR);
memtype = UVADDR;
break;
case 'k':
memtype &= ~(PHYSADDR|UVADDR|FILEADDR);
memtype = KVADDR;
break;
case 'f':
/*
* Unsupported, but can be forcibly implemented
* by removing the DUMPFILE() check above and
* recompiling.
*/
if (!pc->dumpfile)
error(FATAL,
"-f option requires a dumpfile\n");
memtype &= ~(PHYSADDR|UVADDR|KVADDR);
memtype = FILEADDR;
break;
default:
argerrs++;
break;
}
}
if (argerrs)
cmd_usage(pc->curcmd, SYNOPSIS);
if (args[optind]) {
if (*args[optind] == '(')
addr = evall(args[optind], FAULT_ON_ERROR, NULL);
else if (hexadecimal(args[optind], 0))
addr = htoll(args[optind], FAULT_ON_ERROR, NULL);
else if ((sp = symbol_search(args[optind])))
addr = sp->value;
else {
fprintf(fp, "symbol not found: %s\n", args[optind]);
fprintf(fp, "possible alternatives:\n");
if (!symbol_query(args[optind], " ", NULL))
fprintf(fp, " (none found)\n");
return;
}
addr_entered = TRUE;
if (args[++optind]) {
value = stol(args[optind], FAULT_ON_ERROR, NULL);
value_entered = TRUE;
switch (size)
{
case 1:
mem.u8 = (uint8_t)value;
buf = (void *)&mem.u8;
break;
case 2:
mem.u16 = (uint16_t)value;
buf = (void *)&mem.u16;
break;
case 4:
mem.u32 = (uint32_t)value;
buf = (void *)&mem.u32;
break;
case 8:
mem.u64 = (uint64_t)value;
buf = (void *)&mem.u64;
break;
}
}
}
if (!addr_entered || !value_entered)
cmd_usage(pc->curcmd, SYNOPSIS);
if (!memtype)
memtype = vaddr_type(addr, CURRENT_CONTEXT());
switch (memtype)
{
case UVADDR:
if (!IS_UVADDR(addr, CURRENT_CONTEXT())) {
error(INFO, "invalid user virtual address: %llx\n",
addr);
cmd_usage(pc->curcmd, SYNOPSIS);
}
break;
case KVADDR:
if (!IS_KVADDR(addr)) {
error(INFO, "invalid kernel virtual address: %llx\n",
addr);
cmd_usage(pc->curcmd, SYNOPSIS);
}
break;
case PHYSADDR:
break;
case FILEADDR:
break;
case AMBIGUOUS:
error(INFO,
"ambiguous address: %llx (requires -p, -u or -k)\n",
addr);
cmd_usage(pc->curcmd, SYNOPSIS);
}
writemem(addr, memtype, buf, size, "write memory", FAULT_ON_ERROR);
}
char *
format_stack_entry(struct bt_info *bt, char *retbuf, ulong value, ulong limit)
{
char buf[BUFSIZE];
char slab[BUFSIZE];
if (BITS32()) {
if ((bt->flags & BT_FULL_SYM_SLAB) && accessible(value)) {
if ((!limit || (value <= limit)) &&
in_ksymbol_range(value) &&
strlen(value_to_symstr(value, buf, 0)))
sprintf(retbuf, INT_PRLEN == 16 ?
"%-16s" : "%-8s", buf);
else if (vaddr_to_kmem_cache(value, slab, !VERBOSE)) {
if ((bt->flags & BT_FULL_SYM_SLAB2) || CRASHDEBUG(1))
sprintf(buf, "[%lx:%s]", value, slab);
else
sprintf(buf, "[%s]", slab);
sprintf(retbuf, INT_PRLEN == 16 ?
"%-16s" : "%-8s", buf);
} else
sprintf(retbuf, "%08lx", value);
} else
sprintf(retbuf, "%08lx", value);
} else {
if ((bt->flags & BT_FULL_SYM_SLAB) && accessible(value)) {
if ((!limit || (value <= limit)) &&
in_ksymbol_range(value) &&
strlen(value_to_symstr(value, buf, 0)))
sprintf(retbuf, "%-16s", buf);
else if (vaddr_to_kmem_cache(value, slab, !VERBOSE)) {
if ((bt->flags & BT_FULL_SYM_SLAB2) || CRASHDEBUG(1))
sprintf(buf, "[%lx:%s]", value, slab);
else
sprintf(buf, "[%s]", slab);
sprintf(retbuf, "%-16s", buf);
} else
sprintf(retbuf, "%016lx", value);
} else
sprintf(retbuf, "%016lx", value);
}
return retbuf;
}
/*
* For processors with "traditional" kernel/user address space distinction.
*/
int
generic_is_kvaddr(ulong addr)
{
return (addr >= (ulong)(machdep->kvbase));
}
/*
* NOTE: Perhaps even this generic version should tighten up requirements
* by calling uvtop()?
*/
int
generic_is_uvaddr(ulong addr, struct task_context *tc)
{
return (addr < (ulong)(machdep->kvbase));
}
/*
* Raw dump of a task's stack, forcing symbolic output.
*/
void
raw_stack_dump(ulong stackbase, ulong size)
{
display_memory(stackbase, size/sizeof(ulong),
HEXADECIMAL|DISPLAY_DEFAULT|SYMBOLIC, KVADDR, NULL);
}
/*
* Raw data dump, with the option of symbolic output.
*/
void
raw_data_dump(ulong addr, long count, int symbolic)
{
long wordcnt;
ulonglong address;
int memtype;
switch (sizeof(long))
{
case SIZEOF_32BIT:
wordcnt = count/SIZEOF_32BIT;
if (count % SIZEOF_32BIT)
wordcnt++;
break;
case SIZEOF_64BIT:
wordcnt = count/SIZEOF_64BIT;
if (count % SIZEOF_64BIT)
wordcnt++;
break;
default:
break;
}
if (pc->curcmd_flags & MEMTYPE_FILEADDR) {
address = pc->curcmd_private;
memtype = FILEADDR;
} else if (pc->curcmd_flags & MEMTYPE_UVADDR) {
address = (ulonglong)addr;
memtype = UVADDR;
} else {
address = (ulonglong)addr;
memtype = KVADDR;
}
display_memory(address, wordcnt,
HEXADECIMAL|DISPLAY_DEFAULT|(symbolic ? SYMBOLIC : ASCII_ENDLINE),
memtype, NULL);
}
/*
* Quietly checks the accessibility of a memory location.
*/
int
accessible(ulong kva)
{
ulong tmp;
return(readmem(kva, KVADDR, &tmp, sizeof(ulong),
"accessible check", RETURN_ON_ERROR|QUIET));
}
/*
* readmem() is by far *the* workhorse of this whole program. It reads
* memory from /dev/kmem, /dev/mem the dumpfile or /proc/kcore, whichever
* is appropriate:
*
* addr a user, kernel or physical memory address.
* memtype addr type: UVADDR, KVADDR, PHYSADDR, XENMACHADDR or FILEADDR
* buffer supplied buffer to read the data into.
* size number of bytes to read.
* type string describing the request -- helpful when the read fails.
* error_handle what to do if the read fails: FAULT_ON_ERROR kills the command
* immediately; RETURN_ON_ERROR returns FALSE; QUIET suppresses
* the error message.
*/
#define PRINT_ERROR_MESSAGE ((!(error_handle & QUIET) && !STREQ(pc->curcmd, "search")) || \
(CRASHDEBUG(1) && !STREQ(pc->curcmd, "search")) || CRASHDEBUG(2))
#define INVALID_UVADDR "invalid user virtual address: %llx type: \"%s\"\n"
#define INVALID_KVADDR "invalid kernel virtual address: %llx type: \"%s\"\n"
#define SEEK_ERRMSG "seek error: %s address: %llx type: \"%s\"\n"
#define READ_ERRMSG "read error: %s address: %llx type: \"%s\"\n"
#define WRITE_ERRMSG "write error: %s address: %llx type: \"%s\"\n"
#define PAGE_EXCLUDED_ERRMSG "page excluded: %s address: %llx type: \"%s\"\n"
#define RETURN_ON_PARTIAL_READ() \
if ((error_handle & RETURN_PARTIAL) && (size < orig_size)) { \
if (CRASHDEBUG(1)) \
error(INFO, "RETURN_PARTIAL: \"%s\" read: %ld of %ld\n",\
type, orig_size - size, orig_size); \
return TRUE; \
}
int
readmem(ulonglong addr, int memtype, void *buffer, long size,
char *type, ulong error_handle)
{
int fd;
long cnt, orig_size;
physaddr_t paddr;
ulonglong pseudo;
char *bufptr;
if (CRASHDEBUG(4))
fprintf(fp, "<readmem: %llx, %s, \"%s\", %ld, %s, %lx>\n",
addr, memtype_string(memtype, 1), type, size,
error_handle_string(error_handle), (ulong)buffer);
bufptr = (char *)buffer;
orig_size = size;
if (size <= 0) {
if (PRINT_ERROR_MESSAGE)
error(INFO, "invalid size request: %ld type: \"%s\"\n",
size, type);
goto readmem_error;
}
fd = REMOTE_MEMSRC() ? pc->sockfd : (ACTIVE() ? pc->mfd : pc->dfd);
/*
* Screen out any error conditions.
*/
switch (memtype)
{
case UVADDR:
if (!CURRENT_CONTEXT()) {
if (PRINT_ERROR_MESSAGE)
error(INFO, "no current user process\n");
goto readmem_error;
}
if (!IS_UVADDR(addr, CURRENT_CONTEXT())) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_UVADDR, addr, type);
goto readmem_error;
}
break;
case KVADDR:
if (LKCD_DUMPFILE())
addr = fix_lkcd_address(addr);
if (!IS_KVADDR(addr)) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_KVADDR, addr, type);
goto readmem_error;
}
break;
case PHYSADDR:
case XENMACHADDR:
break;
case FILEADDR:
return generic_read_dumpfile(addr, buffer, size, type, error_handle);
}
while (size > 0) {
switch (memtype)
{
case UVADDR:
if (!uvtop(CURRENT_CONTEXT(), addr, &paddr, 0)) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_UVADDR, addr, type);
goto readmem_error;
}
break;
case KVADDR:
if (!kvtop(CURRENT_CONTEXT(), addr, &paddr, 0)) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_KVADDR, addr, type);
goto readmem_error;
}
break;
case PHYSADDR:
paddr = addr;
break;
case XENMACHADDR:
pseudo = xen_m2p(addr);
if (pseudo == XEN_MACHADDR_NOT_FOUND) {
pc->curcmd_flags |= XEN_MACHINE_ADDR;
paddr = addr;
} else
paddr = pseudo | PAGEOFFSET(addr);
break;
}
/*
* Compute bytes till end of page.
*/
cnt = PAGESIZE() - PAGEOFFSET(paddr);
if (cnt > size)
cnt = size;
if (CRASHDEBUG(4))
fprintf(fp, "<%s: addr: %llx paddr: %llx cnt: %ld>\n",
readmem_function_name(), addr,
(unsigned long long)paddr, cnt);
if (memtype == KVADDR)
pc->curcmd_flags |= MEMTYPE_KVADDR;
else
pc->curcmd_flags &= ~MEMTYPE_KVADDR;
switch (READMEM(fd, bufptr, cnt,
(memtype == PHYSADDR) || (memtype == XENMACHADDR) ? 0 : addr, paddr))
{
case SEEK_ERROR:
if (PRINT_ERROR_MESSAGE)
error(INFO, SEEK_ERRMSG, memtype_string(memtype, 0), addr, type);
goto readmem_error;
case READ_ERROR:
if (PRINT_ERROR_MESSAGE) {
if ((pc->flags & DEVMEM) && (kt->flags & PRE_KERNEL_INIT) &&
devmem_is_restricted() && switch_to_proc_kcore())
return(readmem(addr, memtype, bufptr, size,
type, error_handle));
error(INFO, READ_ERRMSG, memtype_string(memtype, 0), addr, type);
}
goto readmem_error;
case PAGE_EXCLUDED:
RETURN_ON_PARTIAL_READ();
if (PRINT_ERROR_MESSAGE)
error(INFO, PAGE_EXCLUDED_ERRMSG, memtype_string(memtype, 0), addr, type);
goto readmem_error;
default:
break;
}
addr += cnt;
bufptr += cnt;
size -= cnt;
}
return TRUE;
readmem_error:
switch (error_handle)
{
case (FAULT_ON_ERROR):
case (QUIET|FAULT_ON_ERROR):
if (pc->flags & IN_FOREACH)
RESUME_FOREACH();
RESTART();
case (RETURN_ON_ERROR):
case (RETURN_PARTIAL|RETURN_ON_ERROR):
case (QUIET|RETURN_ON_ERROR):
break;
}
return FALSE;
}
/*
* Accept anything...
*/
int
generic_verify_paddr(physaddr_t paddr)
{
return TRUE;
}
/*
* Read from /dev/mem.
*/
int
read_dev_mem(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
int readcnt;
if (!machdep->verify_paddr(paddr)) {
if (CRASHDEBUG(1) && !STREQ(pc->curcmd, "search"))
error(INFO, "verify_paddr(%lx) failed\n", paddr);
return READ_ERROR;
}
/*
* /dev/mem disallows anything >= __pa(high_memory)
*
* However it will allow 64-bit lseeks to anywhere, and when followed
* by pulling a 32-bit address from the 64-bit file position, it
* quietly returns faulty data from the (wrapped-around) address.
*/
if (vt->high_memory && (paddr >= (physaddr_t)(VTOP(vt->high_memory)))) {
readcnt = 0;
errno = 0;
goto try_dev_kmem;
}
if (lseek(fd, (off_t)paddr, SEEK_SET) == -1)
return SEEK_ERROR;
next_read:
errno = 0;
readcnt = read(fd, bufptr, cnt);
if ((readcnt != cnt) && CRASHDEBUG(4)) {
if (errno)
perror("/dev/mem");
error(INFO, "read(/dev/mem, %lx, %ld): %ld (%lx)\n",
paddr, cnt, readcnt, readcnt);
}
try_dev_kmem:
/*
* On 32-bit intel architectures high memory can can only be accessed
* via vmalloc'd addresses. However, /dev/mem returns 0 bytes, and
* non-reserved memory pages can't be mmap'd, so the only alternative
* is to read it from /dev/kmem.
*/
if ((readcnt != cnt) && BITS32() && !readcnt && !errno &&
IS_VMALLOC_ADDR(addr))
readcnt = read_dev_kmem(addr, bufptr, cnt);
/*
* The 2.6 valid_phys_addr_range() can potentially shorten the
* count of a legitimate read request. So far this has only been
* seen on an ia64 where a kernel page straddles an EFI segment.
*/
if ((readcnt != cnt) && readcnt && (machdep->flags & DEVMEMRD) &&
!errno) {
if (CRASHDEBUG(1) && !STREQ(pc->curcmd, "search"))
error(INFO, "read(/dev/mem, %lx, %ld): %ld (%lx)\n",
paddr, cnt, readcnt, readcnt);
cnt -= readcnt;
bufptr += readcnt;
goto next_read;
}
if (readcnt != cnt)
return READ_ERROR;
return readcnt;
}
/*
* Write to /dev/mem.
*/
int
write_dev_mem(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
if (!machdep->verify_paddr(paddr)) {
if (CRASHDEBUG(1))
error(INFO, "verify_paddr(%lx) failed\n", paddr);
return WRITE_ERROR;
}
if (lseek(fd, (off_t)paddr, SEEK_SET) == -1)
return SEEK_ERROR;
if (write(fd, bufptr, cnt) != cnt)
return WRITE_ERROR;
return cnt;
}
/*
* The first required reads of memory are done in kernel_init(),
* so if there's a fatal read error of /dev/mem, display a warning
* message if it appears that CONFIG_STRICT_DEVMEM is in effect.
* On x86 and x86_64, only the first 256 pages of physical memory
* are accessible:
*
* #ifdef CONFIG_STRICT_DEVMEM
* int devmem_is_allowed(unsigned long pagenr)
* {
* if (pagenr <= 256)
* return 1;
* if (!page_is_ram(pagenr))
* return 1;
* return 0;
* }
* #endif
*
* It would probably suffice to simply check for the existence of
* devmem_is_allowed(), but on x86 and x86_64 verify pfn 256 reads OK,
* and 257 fails.
*
* Update: a patch has been posted to LKML to fix the off-by-one error
* by changing "<= 256" to "< 256":
*
* https://lkml.org/lkml/2012/8/28/357
*
* The X86/X86_64 lower-boundary pfn check below has been changed
* (preemptively) from 256 to 255.
*
* In any case, if that x86/x86_64 check fails to prove CONFIG_STRICT_DEVMEM
* is configured, then the function will check that "jiffies" can be read,
* as is done for the other architectures.
*
*/
static int
devmem_is_restricted(void)
{
long tmp;
int restricted;
/*
* Check for pre-CONFIG_STRICT_DEVMEM kernels.
*/
if (!kernel_symbol_exists("devmem_is_allowed")) {
if (machine_type("ARM") || machine_type("ARM64") ||
machine_type("X86") || machine_type("X86_64") ||
machine_type("PPC") || machine_type("PPC64"))
return FALSE;
}
restricted = FALSE;
if (STREQ(pc->live_memsrc, "/dev/mem")) {
if (machine_type("X86") || machine_type("X86_64")) {
if (readmem(255*PAGESIZE(), PHYSADDR, &tmp,
sizeof(long), "devmem_is_allowed - pfn 255",
QUIET|RETURN_ON_ERROR) &&
!(readmem(257*PAGESIZE(), PHYSADDR, &tmp,
sizeof(long), "devmem_is_allowed - pfn 257",
QUIET|RETURN_ON_ERROR)))
restricted = TRUE;
}
if (kernel_symbol_exists("jiffies") &&
!readmem(symbol_value("jiffies"), KVADDR, &tmp,
sizeof(ulong), "devmem_is_allowed - jiffies",
QUIET|RETURN_ON_ERROR))
restricted = TRUE;
if (restricted)
error(INFO,
"this kernel may be configured with CONFIG_STRICT_DEVMEM,"
" which\n renders /dev/mem unusable as a live memory "
"source.\n");
}
return restricted;
}
static int
switch_to_proc_kcore(void)
{
close(pc->mfd);
if (file_exists("/proc/kcore", NULL))
error(INFO, "trying /proc/kcore as an alternative to /dev/mem\n\n");
else
return FALSE;
if ((pc->mfd = open("/proc/kcore", O_RDONLY)) < 0) {
error(INFO, "/proc/kcore: %s\n", strerror(errno));
return FALSE;
}
if (!proc_kcore_init(fp)) {
error(INFO, "/proc/kcore: initialization failed\n");
return FALSE;
}
pc->flags &= ~DEVMEM;
pc->flags |= PROC_KCORE;
pc->readmem = read_proc_kcore;
pc->writemem = write_proc_kcore;
pc->live_memsrc = "/proc/kcore";
return TRUE;
}
/*
* Read from memory driver.
*/
int
read_memory_device(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
if (pc->curcmd_flags & XEN_MACHINE_ADDR)
return READ_ERROR;
if (!machdep->verify_paddr(paddr)) {
if (CRASHDEBUG(1))
error(INFO, "verify_paddr(%lx) failed\n", paddr);
return READ_ERROR;
}
lseek(fd, (loff_t)paddr, SEEK_SET);
if (read(fd, bufptr, cnt) != cnt)
return READ_ERROR;
return cnt;
}
/*
* Write to memory driver.
*/
int
write_memory_device(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
if (!(MEMORY_DRIVER_DEVICE_MODE & S_IWUSR))
return (error(FATAL, "cannot write to %s!\n", pc->live_memsrc));
if (lseek(fd, (loff_t)paddr, SEEK_SET) == -1)
return SEEK_ERROR;
if (write(fd, bufptr, cnt) != cnt)
return WRITE_ERROR;
return cnt;
}
/*
* Read from an MCLX formatted dumpfile.
*/
int
read_mclx_dumpfile(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
if (vas_lseek((ulong)paddr, SEEK_SET))
return SEEK_ERROR;
if (vas_read((void *)bufptr, cnt) != cnt)
return READ_ERROR;
return cnt;
}
/*
* Write to an MCLX formatted dumpfile. This only modifies the buffered
* copy only; if it gets flushed, the modification is lost.
*/
int
write_mclx_dumpfile(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
if (vas_lseek((ulong)paddr, SEEK_SET))
return SEEK_ERROR;
if (vas_write((void *)bufptr, cnt) != cnt)
return WRITE_ERROR;
return cnt;
}
/*
* Read from an LKCD formatted dumpfile.
*/
int
read_lkcd_dumpfile(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
set_lkcd_fp(fp);
if (!lkcd_lseek(paddr))
return SEEK_ERROR;
if (lkcd_read((void *)bufptr, cnt) != cnt)
return READ_ERROR;
return cnt;
}
/*
* Write to an LKCD formatted dumpfile. (dummy routine -- not allowed)
*/
int
write_lkcd_dumpfile(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
return (error(FATAL, "cannot write to an LKCD compressed dump!\n"));
}
/*
* Read from network daemon.
*/
int
read_daemon(int fd, void *bufptr, int cnt, ulong vaddr, physaddr_t paddr)
{
if (remote_memory_read(pc->rmfd, bufptr, cnt, paddr, -1) == cnt)
return cnt;
if (!IS_VMALLOC_ADDR(vaddr) || DUMPFILE())
return READ_ERROR;
/*
* On 32-bit architectures w/memory above ~936MB,
* that memory can only be accessed via vmalloc'd
* addresses. However, /dev/mem returns 0 bytes,
* and non-reserved memory pages can't be mmap'd, so
* the only alternative is to read it from /dev/kmem.
*/
if (BITS32() && remote_memory_read(pc->rkfd, bufptr, cnt, vaddr, -1) == cnt)
return cnt;
return READ_ERROR;
}
/*
* Write to network daemon.
*/
int
write_daemon(int fd, void *bufptr, int cnt, ulong addr, physaddr_t paddr)
{
return (error(FATAL, "writing to daemon not supported yet [TBD]\n"));
}
/*
* Turn the memtype bitmask into a string.
*/
static
char *memtype_string(int memtype, int debug)
{
static char membuf[40];
switch (memtype)
{
case UVADDR:
sprintf(membuf, debug ? "UVADDR" : "user virtual");
break;
case KVADDR:
sprintf(membuf, debug ? "KVADDR" : "kernel virtual");
break;
case PHYSADDR:
sprintf(membuf, debug ? "PHYSADDR" : "physical");
break;
case XENMACHADDR:
sprintf(membuf, debug ? "XENMACHADDR" : "xen machine");
break;
case FILEADDR:
sprintf(membuf, debug ? "FILEADDR" : "dumpfile");
break;
default:
if (debug)
sprintf(membuf, "0x%x (?)", memtype);
else
sprintf(membuf, "unknown");
break;
}
return membuf;
}
/*
* Turn the error_handle bitmask into a string,
* Note: FAULT_ON_ERROR == 0
*/
static
char *error_handle_string(ulong error_handle)
{
static char ebuf[20];
int others;
sprintf(ebuf, "(");
others = 0;
if (error_handle & RETURN_ON_ERROR)
sprintf(&ebuf[strlen(ebuf)], "%sROE", others++ ? "|" : "");
if (error_handle & FAULT_ON_ERROR)
sprintf(&ebuf[strlen(ebuf)], "%sFOE", others++ ? "|" : "");
if (error_handle & QUIET)
sprintf(&ebuf[strlen(ebuf)], "%sQ", others++ ? "|" : "");
if (error_handle & HEX_BIAS)
sprintf(&ebuf[strlen(ebuf)], "%sHB", others++ ? "|" : "");
if (error_handle & RETURN_PARTIAL)
sprintf(&ebuf[strlen(ebuf)], "%sRP", others++ ? "|" : "");
strcat(ebuf, ")");
return ebuf;
}
/*
* Sister routine to readmem().
*/
int
writemem(ulonglong addr, int memtype, void *buffer, long size,
char *type, ulong error_handle)
{
int fd;
long cnt;
physaddr_t paddr;
char *bufptr;
if (CRASHDEBUG(1))
fprintf(fp, "writemem: %llx, %s, \"%s\", %ld, %s %lx\n",
addr, memtype_string(memtype, 1), type, size,
error_handle_string(error_handle), (ulong)buffer);
if (size < 0) {
if (PRINT_ERROR_MESSAGE)
error(INFO, "invalid size request: %ld\n", size);
goto writemem_error;
}
bufptr = (char *)buffer;
fd = ACTIVE() ? pc->mfd : pc->dfd;
/*
* Screen out any error conditions.
*/
switch (memtype)
{
case UVADDR:
if (!CURRENT_CONTEXT()) {
if (PRINT_ERROR_MESSAGE)
error(INFO, "no current user process\n");
goto writemem_error;
}
if (!IS_UVADDR(addr, CURRENT_CONTEXT())) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_UVADDR, addr, type);
goto writemem_error;
}
break;
case KVADDR:
if (!IS_KVADDR(addr)) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_KVADDR, addr, type);
goto writemem_error;
}
break;
case PHYSADDR:
break;
case FILEADDR:
return generic_write_dumpfile(addr, buffer, size, type, error_handle);
}
while (size > 0) {
switch (memtype)
{
case UVADDR:
if (!uvtop(CURRENT_CONTEXT(), addr, &paddr, 0)) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_UVADDR, addr, type);
goto writemem_error;
}
break;
case KVADDR:
if (!kvtop(CURRENT_CONTEXT(), addr, &paddr, 0)) {
if (PRINT_ERROR_MESSAGE)
error(INFO, INVALID_KVADDR, addr, type);
goto writemem_error;
}
break;
case PHYSADDR:
paddr = addr;
break;
}
/*
* Compute bytes till end of page.
*/
cnt = PAGESIZE() - PAGEOFFSET(paddr);
if (cnt > size)
cnt = size;
switch (pc->writemem(fd, bufptr, cnt, addr, paddr))
{
case SEEK_ERROR:
if (PRINT_ERROR_MESSAGE)
error(INFO, SEEK_ERRMSG, memtype_string(memtype, 0), addr, type);
goto writemem_error;
case WRITE_ERROR:
if (PRINT_ERROR_MESSAGE)
error(INFO, WRITE_ERRMSG, memtype_string(memtype, 0), addr, type);
goto writemem_error;
default:
break;
}
addr += cnt;
bufptr += cnt;
size -= cnt;
}
return TRUE;
writemem_error:
switch (error_handle)
{
case (FAULT_ON_ERROR):
case (QUIET|FAULT_ON_ERROR):
RESTART();
case (RETURN_ON_ERROR):
case (QUIET|RETURN_ON_ERROR):
break;
}
return FALSE;
}
/*
* When /dev/mem won't allow access, try /dev/kmem.
*/
static ssize_t
read_dev_kmem(ulong vaddr, char *bufptr, long cnt)
{
ssize_t readcnt;
if (pc->kfd < 0) {
if ((pc->kfd = open("/dev/kmem", O_RDONLY)) < 0)
return 0;
}
if (lseek(pc->kfd, vaddr, SEEK_SET) == -1)
return 0;
readcnt = read(pc->kfd, bufptr, cnt);
if (readcnt != cnt)
readcnt = 0;
return readcnt;
}
/*
* Generic dumpfile read/write functions to handle FILEADDR
* memtype arguments to readmem() and writemem(). These are
* not to be confused with pc->readmem/writemem plug-ins.
*/
static int
generic_read_dumpfile(ulonglong addr, void *buffer, long size, char *type,
ulong error_handle)
{
int fd;
int retval;
retval = TRUE;
if (!pc->dumpfile)
error(FATAL, "command requires a dumpfile\n");
if ((fd = open(pc->dumpfile, O_RDONLY)) < 0)
error(FATAL, "%s: %s\n", pc->dumpfile,
strerror(errno));
if (lseek(fd, addr, SEEK_SET) == -1) {
if (PRINT_ERROR_MESSAGE)
error(INFO, SEEK_ERRMSG,
memtype_string(FILEADDR, 0), addr, type);
retval = FALSE;
} else if (read(fd, buffer, size) != size) {
if (PRINT_ERROR_MESSAGE)
error(INFO, READ_ERRMSG,
memtype_string(FILEADDR, 0), addr, type);
retval = FALSE;
}
close(fd);
return retval;
}
static int
generic_write_dumpfile(ulonglong addr, void *buffer, long size, char *type,
ulong error_handle)
{
int fd;
int retval;
retval = TRUE;
if (!pc->dumpfile)
error(FATAL, "command requires a dumpfile\n");
if ((fd = open(pc->dumpfile, O_WRONLY)) < 0)
error(FATAL, "%s: %s\n", pc->dumpfile,
strerror(errno));
if (lseek(fd, addr, SEEK_SET) == -1) {
if (PRINT_ERROR_MESSAGE)
error(INFO, SEEK_ERRMSG,
memtype_string(FILEADDR, 0), addr, type);
retval = FALSE;
} else if (write(fd, buffer, size) != size) {
if (PRINT_ERROR_MESSAGE)
error(INFO, WRITE_ERRMSG,
memtype_string(FILEADDR, 0), addr, type);
retval = FALSE;
}
close(fd);
return retval;
}
/*
* Translates a kernel virtual address to its physical address. cmd_vtop()
* sets the verbose flag so that the pte translation gets displayed; all
* other callers quietly accept the translation.
*/
int
kvtop(struct task_context *tc, ulong kvaddr, physaddr_t *paddr, int verbose)
{
physaddr_t unused;
return (machdep->kvtop(tc ? tc : CURRENT_CONTEXT(), kvaddr,
paddr ? paddr : &unused, verbose));
}
/*
* Translates a user virtual address to its physical address. cmd_vtop()
* sets the verbose flag so that the pte translation gets displayed; all
* other callers quietly accept the translation.
*
* This routine can also take mapped kernel virtual addresses if the -u flag
* was passed to cmd_vtop(). If so, it makes the translation using the
* kernel-memory PGD entry instead of swapper_pg_dir.
*/
int
uvtop(struct task_context *tc, ulong vaddr, physaddr_t *paddr, int verbose)
{
return(machdep->uvtop(tc, vaddr, paddr, verbose));
}
/*
* The vtop command does a verbose translation of a user or kernel virtual
* address into it physical address. The pte translation is shown by
* passing the VERBOSE flag to kvtop() or uvtop(). If it's a user virtual
* address, the vm_area_struct data containing the page is displayed.
* Lastly, the mem_map[] page data containing the address is displayed.
*/
void
cmd_vtop(void)
{
int c;
ulong vaddr, context;
int others;
ulong vtop_flags, loop_vtop_flags;
struct task_context *tc;
vtop_flags = loop_vtop_flags = 0;
tc = NULL;
while ((c = getopt(argcnt, args, "ukc:")) != EOF) {
switch(c)
{
case 'c':
switch (str_to_context(optarg, &context, &tc))
{
case STR_PID:
case STR_TASK:
vtop_flags |= USE_USER_PGD;
break;
case STR_INVALID:
error(FATAL, "invalid task or pid value: %s\n",
optarg);
break;
}
break;
case 'u':
vtop_flags |= UVADDR;
break;
case 'k':
vtop_flags |= KVADDR;
break;
default:
argerrs++;
break;
}
}
if (argerrs || !args[optind])
cmd_usage(pc->curcmd, SYNOPSIS);
if (!tc && !(tc = CURRENT_CONTEXT()))
error(FATAL, "no current user process\n");
if ((vtop_flags & (UVADDR|KVADDR)) == (UVADDR|KVADDR))
error(FATAL, "-u and -k options are mutually exclusive\n");
others = 0;
while (args[optind]) {
vaddr = htol(args[optind], FAULT_ON_ERROR, NULL);
if (!(vtop_flags & (UVADDR|KVADDR))) {
switch (vaddr_type(vaddr, tc))
{
case UVADDR:
loop_vtop_flags = UVADDR;
break;
case KVADDR:
loop_vtop_flags = KVADDR;
break;
case AMBIGUOUS:
error(FATAL,
"ambiguous address: %lx (requires -u or -k)\n",
vaddr);
break;
}
} else
loop_vtop_flags = 0;
if (others++)
fprintf(fp, "\n");
do_vtop(vaddr, tc, vtop_flags | loop_vtop_flags);
if (REMOTE() && CRASHDEBUG(1)) {
ulong paddr = remote_vtop(tc->processor, vaddr);
if (paddr)
fprintf(fp, "rvtop(%lx)=%lx\n", vaddr, paddr);
}
optind++;
}
}
/*
* Do the work for cmd_vtop(), or less likely, foreach().
*/
void
do_vtop(ulong vaddr, struct task_context *tc, ulong vtop_flags)
{
physaddr_t paddr;
ulong vma, page;
int page_exists;
struct meminfo meminfo;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
int memtype = 0;
switch (vtop_flags & (UVADDR|KVADDR))
{
case UVADDR:
memtype = UVADDR;
break;
case KVADDR:
memtype = KVADDR;
break;
case (UVADDR|KVADDR):
error(FATAL, "-u and -k options are mutually exclusive\n");
break;
default:
switch (vaddr_type(vaddr, tc))
{
case UVADDR:
memtype = UVADDR;
break;
case KVADDR:
memtype = KVADDR;
break;
case AMBIGUOUS:
error(FATAL,
"ambiguous address: %lx (requires -u or -k)\n",
vaddr);
break;
}
break;
}
page_exists = paddr = 0;
switch (memtype) {
case UVADDR:
fprintf(fp, "%s %s\n",
mkstring(buf1, UVADDR_PRLEN, LJUST, "VIRTUAL"),
mkstring(buf2, VADDR_PRLEN, LJUST, "PHYSICAL"));
if (!IN_TASK_VMA(tc->task, vaddr)) {
fprintf(fp, "%s (not accessible)\n\n",
mkstring(buf1, UVADDR_PRLEN, LJUST|LONG_HEX,
MKSTR(vaddr)));
return;
}
if (!uvtop(tc, vaddr, &paddr, 0)) {
fprintf(fp, "%s %s\n\n",
mkstring(buf1, UVADDR_PRLEN, LJUST|LONG_HEX,
MKSTR(vaddr)),
(XEN() && (paddr == PADDR_NOT_AVAILABLE)) ?
"(page not available)" : "(not mapped)");
page_exists = FALSE;
} else {
fprintf(fp, "%s %s\n\n",
mkstring(buf1, UVADDR_PRLEN, LJUST|LONG_HEX,
MKSTR(vaddr)),
mkstring(buf2, VADDR_PRLEN, LJUST|LONGLONG_HEX,
MKSTR(&paddr)));
page_exists = TRUE;
}
uvtop(tc, vaddr, &paddr, VERBOSE);
fprintf(fp, "\n");
vma = vm_area_dump(tc->task, UVADDR, vaddr, 0);
if (!page_exists) {
if (swap_location(paddr, buf1))
fprintf(fp, "\nSWAP: %s\n", buf1);
else if (vma_file_offset(vma, vaddr, buf1))
fprintf(fp, "\nFILE: %s\n", buf1);
}
break;
case KVADDR:
fprintf(fp, "%s %s\n",
mkstring(buf1, VADDR_PRLEN, LJUST, "VIRTUAL"),
mkstring(buf2, VADDR_PRLEN, LJUST, "PHYSICAL"));
if (!IS_KVADDR(vaddr)) {
fprintf(fp, "%-8lx (not a kernel virtual address)\n\n",
vaddr);
return;
}
if (vtop_flags & USE_USER_PGD) {
if (!uvtop(tc, vaddr, &paddr, 0)) {
fprintf(fp, "%s %s\n\n",
mkstring(buf1, UVADDR_PRLEN,
LJUST|LONG_HEX, MKSTR(vaddr)),
(XEN() &&
(paddr == PADDR_NOT_AVAILABLE)) ?
"(page not available)" :
"(not mapped)");
page_exists = FALSE;
} else {
fprintf(fp, "%s %s\n\n",
mkstring(buf1, UVADDR_PRLEN,
LJUST|LONG_HEX, MKSTR(vaddr)),
mkstring(buf2, VADDR_PRLEN,
LJUST|LONGLONG_HEX, MKSTR(&paddr)));
page_exists = TRUE;
}
uvtop(tc, vaddr, &paddr, VERBOSE);
} else {
if (!kvtop(tc, vaddr, &paddr, 0)) {
fprintf(fp, "%s %s\n\n",
mkstring(buf1, VADDR_PRLEN,
LJUST|LONG_HEX, MKSTR(vaddr)),
(XEN() &&
(paddr == PADDR_NOT_AVAILABLE)) ?
"(page not available)" :
"(not mapped)");
page_exists = FALSE;
} else {
fprintf(fp, "%s %s\n\n",
mkstring(buf1, VADDR_PRLEN,
LJUST|LONG_HEX, MKSTR(vaddr)),
mkstring(buf2, VADDR_PRLEN,
LJUST|LONGLONG_HEX, MKSTR(&paddr)));
page_exists = TRUE;
}
kvtop(tc, vaddr, &paddr, VERBOSE);
}
break;
}
fprintf(fp, "\n");
if (page_exists && phys_to_page(paddr, &page)) {
if ((pc->flags & DEVMEM) && (paddr >= VTOP(vt->high_memory)))
return;
BZERO(&meminfo, sizeof(struct meminfo));
meminfo.flags = ADDRESS_SPECIFIED;
meminfo.spec_addr = paddr;
meminfo.memtype = PHYSADDR;
dump_mem_map(&meminfo);
}
}
/*
* Runs PTOV() on the physical address argument or translates
* a per-cpu offset and cpu specifier.
*/
void
cmd_ptov(void)
{
int c, len, unknown;
ulong vaddr;
physaddr_t paddr, paddr_test;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
int others;
char *cpuspec;
ulong *cpus;
while ((c = getopt(argcnt, args, "")) != EOF) {
switch(c)
{
default:
argerrs++;
break;
}
}
if (argerrs || !args[optind])
cmd_usage(pc->curcmd, SYNOPSIS);
others = 0;
cpuspec = NULL;
cpus = NULL;
while (args[optind]) {
cpuspec = strchr(args[optind], ':');
if (cpuspec) {
*cpuspec++ = NULLCHAR;
cpus = get_cpumask_buf();
if (STREQ(cpuspec, ""))
SET_BIT(cpus, CURRENT_CONTEXT()->processor);
else
make_cpumask(cpuspec, cpus, FAULT_ON_ERROR, NULL);
}
paddr = htoll(args[optind], FAULT_ON_ERROR, NULL);
if (cpuspec) {
sprintf(buf1, "[%d]", kt->cpus-1);
len = strlen(buf1) + 2;
fprintf(fp, "%sPER-CPU OFFSET: %llx\n",
others++ ? "\n" : "", (ulonglong)paddr);
fprintf(fp, " %s %s\n",
mkstring(buf1, len, LJUST, "CPU"),
mkstring(buf2, VADDR_PRLEN, LJUST, "VIRTUAL"));
for (c = 0; c < kt->cpus; c++) {
if (!NUM_IN_BITMAP(cpus, c))
continue;
vaddr = paddr + kt->__per_cpu_offset[c];
sprintf(buf1, "[%d]", c);
fprintf(fp, " %s%lx",
mkstring(buf2, len, LJUST, buf1),
vaddr);
if (hide_offline_cpu(c))
fprintf(fp, " [OFFLINE]\n");
else
fprintf(fp, "\n");
}
FREEBUF(cpus);
} else {
vaddr = PTOV(paddr);
unknown = BITS32() && (!kvtop(0, vaddr, &paddr_test, 0) ||
(paddr_test != paddr));
fprintf(fp, "%s%s %s\n", others++ ? "\n" : "",
mkstring(buf1, VADDR_PRLEN, LJUST, "VIRTUAL"),
mkstring(buf2, VADDR_PRLEN, LJUST, "PHYSICAL"));
fprintf(fp, "%s %s\n", unknown ?
mkstring(buf1, VADDR_PRLEN, LJUST, "unknown") :
mkstring(buf1, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(vaddr)),
mkstring(buf2, VADDR_PRLEN, LJUST|LONGLONG_HEX,
MKSTR(&paddr)));
}
optind++;
}
}
/*
* Runs PTOB() on the page frame number to get the page address.
*/
void
cmd_ptob(void)
{
ulonglong value;
optind = 1;
if (!args[optind])
cmd_usage(pc->curcmd, SYNOPSIS);
while (args[optind]) {
value = stoll(args[optind], FAULT_ON_ERROR, NULL);
fprintf(fp, "%llx: %llx\n", value, PTOB(value));
optind++;
}
}
/*
* Runs BTOP() on the address to get the page frame number.
*/
void
cmd_btop(void)
{
ulonglong value;
optind = 1;
if (!args[optind])
cmd_usage(pc->curcmd, SYNOPSIS);
while (args[optind]) {
value = htoll(args[optind], FAULT_ON_ERROR, NULL);
fprintf(fp, "%llx: %llx\n", value, BTOP(value));
optind++;
}
}
/*
* This command displays basic virtual memory information of a context,
* consisting of a pointer to its mm_struct, its RSS and total virtual
* memory size; and a list of pointers to each vm_area_struct, its starting
* and ending address, and vm_flags value. The argument can be a task
* address or a PID number; if no args, the current context is used.
*/
void
cmd_vm(void)
{
int c;
ulong flag;
ulong value;
ulong single_vma;
ulonglong llvalue;
struct task_context *tc;
struct reference reference, *ref;
unsigned int radix;
int subsequent;
flag = 0;
single_vma = 0;
radix = 0;
ref = NULL;
BZERO(&reference, sizeof(struct reference));
while ((c = getopt(argcnt, args, "f:pmvR:P:xd")) != EOF) {
switch(c)
{
case 'f':
if (flag)
argerrs++;
else {
llvalue = htoll(optarg, FAULT_ON_ERROR, NULL);
do_vm_flags(llvalue);
return;
}
break;
case 'p':
if (flag)
argerrs++;
else
flag |= PHYSADDR;
break;
case 'm':
if (flag)
argerrs++;
else
flag |= PRINT_MM_STRUCT;
break;
case 'v':
if (flag)
argerrs++;
else
flag |= PRINT_VMA_STRUCTS;
break;
case 'R':
if (ref) {
error(INFO, "only one -R option allowed\n");
argerrs++;
} else if (flag && !(flag & PHYSADDR))
argerrs++;
else {
ref = &reference;
ref->str = optarg;
flag |= PHYSADDR;
}
break;
case 'P':
if (flag)
argerrs++;
else {
flag |= PRINT_SINGLE_VMA;
single_vma = htol(optarg, FAULT_ON_ERROR, NULL);
}
break;
case 'x':
if (radix == 10)
error(FATAL,
"-d and -x are mutually exclusive\n");
radix = 16;
break;
case 'd':
if (radix == 16)
error(FATAL,
"-d and -x are mutually exclusive\n");
radix = 10;
break;
default:
argerrs++;
break;
}
}
if (argerrs)
cmd_usage(pc->curcmd, SYNOPSIS);
if (radix == 10)
flag |= PRINT_RADIX_10;
else if (radix == 16)
flag |= PRINT_RADIX_16;
if (!args[optind]) {
if (!ref)
print_task_header(fp, CURRENT_CONTEXT(), 0);
vm_area_dump(CURRENT_TASK(), flag, single_vma, ref);
return;
}
subsequent = 0;
while (args[optind]) {
switch (str_to_context(args[optind], &value, &tc))
{
case STR_PID:
for (tc = pid_to_context(value); tc; tc = tc->tc_next) {
if (!ref)
print_task_header(fp, tc, subsequent++);
vm_area_dump(tc->task, flag, single_vma, ref);
}
break;
case STR_TASK:
if (!ref)
print_task_header(fp, tc, subsequent++);
vm_area_dump(tc->task, flag, single_vma, ref);
break;
case STR_INVALID:
error(INFO, "%sinvalid task or pid value: %s\n",
subsequent++ ? "\n" : "", args[optind]);
break;
}
optind++;
}
}
/*
* Translate a vm_flags value.
*/
#define VM_READ 0x00000001ULL /* currently active flags */
#define VM_WRITE 0x00000002ULL
#define VM_EXEC 0x00000004ULL
#define VM_SHARED 0x00000008ULL
#define VM_MAYREAD 0x00000010ULL /* limits for mprotect() etc */
#define VM_MAYWRITE 0x00000020ULL
#define VM_MAYEXEC 0x00000040ULL
#define VM_MAYSHARE 0x00000080ULL
#define VM_GROWSDOWN 0x00000100ULL /* general info on the segment */
#define VM_GROWSUP 0x00000200ULL
#define VM_NOHUGEPAGE 0x00000200ULL /* MADV_NOHUGEPAGE marked this vma */
#define VM_SHM 0x00000400ULL /* shared memory area, don't swap out */
#define VM_PFNMAP 0x00000400ULL
#define VM_DENYWRITE 0x00000800ULL /* ETXTBSY on write attempts.. */
#define VM_EXECUTABLE 0x00001000ULL
#define VM_LOCKED 0x00002000ULL
#define VM_IO 0x00004000ULL /* Memory mapped I/O or similar */
#define VM_SEQ_READ 0x00008000ULL /* App will access data sequentially */
#define VM_RAND_READ 0x00010000ULL /* App will not benefit from clustered reads */
#define VM_DONTCOPY 0x00020000ULL /* Do not copy this vma on fork */
#define VM_DONTEXPAND 0x00040000ULL /* Cannot expand with mremap() */
#define VM_RESERVED 0x00080000ULL /* Don't unmap it from swap_out */
#define VM_BIGPAGE 0x00100000ULL /* bigpage mappings, no pte's */
#define VM_BIGMAP 0x00200000ULL /* user wants bigpage mapping */
#define VM_WRITECOMBINED 0x00100000ULL /* Write-combined */
#define VM_NONCACHED 0x00200000ULL /* Noncached access */
#define VM_HUGETLB 0x00400000ULL /* Huge tlb Page*/
#define VM_ACCOUNT 0x00100000ULL /* Memory is a vm accounted object */
#define VM_NONLINEAR 0x00800000ULL /* Is non-linear (remap_file_pages) */
#define VM_MAPPED_COPY 0x01000000ULL /* T if mapped copy of data (nommu mmap) */
#define VM_HUGEPAGE 0x01000000ULL /* MADV_HUGEPAGE marked this vma */
#define VM_INSERTPAGE 0x02000000ULL /* The vma has had "vm_insert_page()" done on it */
#define VM_ALWAYSDUMP 0x04000000ULL /* Always include in core dumps */
#define VM_CAN_NONLINEAR 0x08000000ULL /* Has ->fault & does nonlinear pages */
#define VM_MIXEDMAP 0x10000000ULL /* Can contain "struct page" and pure PFN pages */
#define VM_SAO 0x20000000ULL /* Strong Access Ordering (powerpc) */
#define VM_PFN_AT_MMAP 0x40000000ULL /* PFNMAP vma that is fully mapped at mmap time */
#define VM_MERGEABLE 0x80000000ULL /* KSM may merge identical pages */
static void
do_vm_flags(ulonglong flags)
{
int others;
others = 0;
fprintf(fp, "%llx: (", flags);
if (flags & VM_READ) {
fprintf(fp, "READ");
others++;
}
if (flags & VM_WRITE)
fprintf(fp, "%sWRITE", others++ ? "|" : "");
if (flags & VM_EXEC)
fprintf(fp, "%sEXEC", others++ ? "|" : "");
if (flags & VM_SHARED)
fprintf(fp, "%sSHARED", others++ ? "|" : "");
if (flags & VM_MAYREAD)
fprintf(fp, "%sMAYREAD", others++ ? "|" : "");
if (flags & VM_MAYWRITE)
fprintf(fp, "%sMAYWRITE", others++ ? "|" : "");
if (flags & VM_MAYEXEC)
fprintf(fp, "%sMAYEXEC", others++ ? "|" : "");
if (flags & VM_MAYSHARE)
fprintf(fp, "%sMAYSHARE", others++ ? "|" : "");
if (flags & VM_GROWSDOWN)
fprintf(fp, "%sGROWSDOWN", others++ ? "|" : "");
if (kernel_symbol_exists("expand_upwards")) {
if (flags & VM_GROWSUP)
fprintf(fp, "%sGROWSUP", others++ ? "|" : "");
} else if (flags & VM_NOHUGEPAGE)
fprintf(fp, "%sNOHUGEPAGE", others++ ? "|" : "");
if (flags & VM_SHM) {
if (THIS_KERNEL_VERSION > LINUX(2,6,17))
fprintf(fp, "%sPFNMAP", others++ ? "|" : "");
else
fprintf(fp, "%sSHM", others++ ? "|" : "");
}
if (flags & VM_DENYWRITE)
fprintf(fp, "%sDENYWRITE", others++ ? "|" : "");
if (flags & VM_EXECUTABLE)
fprintf(fp, "%sEXECUTABLE", others++ ? "|" : "");
if (flags & VM_LOCKED)
fprintf(fp, "%sLOCKED", others++ ? "|" : "");
if (flags & VM_IO)
fprintf(fp, "%sIO", others++ ? "|" : "");
if (flags & VM_SEQ_READ)
fprintf(fp, "%sSEQ_READ", others++ ? "|" : "");
if (flags & VM_RAND_READ)
fprintf(fp, "%sRAND_READ", others++ ? "|" : "");
if (flags & VM_DONTCOPY)
fprintf(fp, "%sDONTCOPY", others++ ? "|" : "");
if (flags & VM_DONTEXPAND)
fprintf(fp, "%sDONTEXPAND", others++ ? "|" : "");
if (flags & VM_RESERVED)
fprintf(fp, "%sRESERVED", others++ ? "|" : "");
if (symbol_exists("nr_bigpages") && (THIS_KERNEL_VERSION == LINUX(2,4,9))) {
if (flags & VM_BIGPAGE)
fprintf(fp, "%sBIGPAGE", others++ ? "|" : "");
if (flags & VM_BIGMAP)
fprintf(fp, "%sBIGMAP", others++ ? "|" : "");
} else {
if ((THIS_KERNEL_VERSION < LINUX(2,4,21)) &&
(flags & VM_WRITECOMBINED))
fprintf(fp, "%sWRITECOMBINED", others++ ? "|" : "");
if ((THIS_KERNEL_VERSION < LINUX(2,4,21)) &&
(flags & VM_NONCACHED))
fprintf(fp, "%sNONCACHED", others++ ? "|" : "");
if (flags & VM_HUGETLB)
fprintf(fp, "%sHUGETLB", others++ ? "|" : "");
if (flags & VM_ACCOUNT)
fprintf(fp, "%sACCOUNT", others++ ? "|" : "");
}
if (flags & VM_NONLINEAR)
fprintf(fp, "%sNONLINEAR", others++ ? "|" : "");
if (flags & VM_HUGEPAGE) {
if (MEMBER_EXISTS("mm_struct", "pmd_huge_pte"))
fprintf(fp, "%sHUGEPAGE", others++ ? "|" : "");
else
fprintf(fp, "%sMAPPED_COPY", others++ ? "|" : "");
}
if (flags & VM_INSERTPAGE)
fprintf(fp, "%sINSERTPAGE", others++ ? "|" : "");
if (flags & VM_ALWAYSDUMP)
fprintf(fp, "%sALWAYSDUMP", others++ ? "|" : "");
if (flags & VM_CAN_NONLINEAR)
fprintf(fp, "%sCAN_NONLINEAR", others++ ? "|" : "");
if (flags & VM_MIXEDMAP)
fprintf(fp, "%sMIXEDMAP", others++ ? "|" : "");
if (flags & VM_SAO)
fprintf(fp, "%sSAO", others++ ? "|" : "");
if (flags & VM_PFN_AT_MMAP)
fprintf(fp, "%sPFN_AT_MMAP", others++ ? "|" : "");
if (flags & VM_MERGEABLE)
fprintf(fp, "%sMERGEABLE", others++ ? "|" : "");
fprintf(fp, ")\n");
}
/*
* Read whatever size vm_area_struct.vm_flags happens to be into a ulonglong.
*/
static ulonglong
get_vm_flags(char *vma_buf)
{
ulonglong vm_flags = 0;
if (SIZE(vm_area_struct_vm_flags) == sizeof(short))
vm_flags = USHORT(vma_buf + OFFSET(vm_area_struct_vm_flags));
else if (SIZE(vm_area_struct_vm_flags) == sizeof(long))
vm_flags = ULONG(vma_buf+ OFFSET(vm_area_struct_vm_flags));
else if (SIZE(vm_area_struct_vm_flags) == sizeof(long long))
vm_flags = ULONGLONG(vma_buf+ OFFSET(vm_area_struct_vm_flags));
else
error(INFO, "questionable vm_area_struct.vm_flags size: %d\n",
SIZE(vm_area_struct_vm_flags));
return vm_flags;
}
/*
* vm_area_dump() primarily does the work for cmd_vm(), but is also called
* from IN_TASK_VMA(), do_vtop(), and foreach(). How it behaves depends
* upon the flag and ref arguments:
*
* UVADDR do_vtop() when dumping the VMA for a uvaddr
* UVADDR|VERIFY_ADDR IN_TASK_VMA() macro checks if a uvaddr is in a VMA
* PHYSADDR cmd_vm() or foreach(vm) for -p and -R options
* PRINT_MM_STRUCT cmd_vm() or foreach(vm) for -m option
* PRINT_VMA_STRUCTS cmd_vm() or foreach(vm) for -v option
* PRINT_INODES open_files_dump() backdoors foreach(vm)
*
* ref cmd_vm() or foreach(vm) for -R option that searches
* for references -- and only then does a display
*/
#define PRINT_VM_DATA() \
{ \
fprintf(fp, "%s %s ", \
mkstring(buf4, VADDR_PRLEN, CENTER|LJUST, "MM"), \
mkstring(buf5, VADDR_PRLEN, CENTER|LJUST, "PGD")); \
fprintf(fp, "%s %s\n", \
mkstring(buf4, 6, CENTER|LJUST, "RSS"), \
mkstring(buf5, 8, CENTER|LJUST, "TOTAL_VM")); \
\
fprintf(fp, "%s %s ", \
mkstring(buf4, VADDR_PRLEN, CENTER|LJUST|LONG_HEX, \
MKSTR(tm->mm_struct_addr)), \
mkstring(buf5, VADDR_PRLEN, CENTER|LJUST|LONG_HEX, \
MKSTR(tm->pgd_addr))); \
\
sprintf(buf4, "%ldk", (tm->rss * PAGESIZE())/1024); \
sprintf(buf5, "%ldk", (tm->total_vm * PAGESIZE())/1024); \
fprintf(fp, "%s %s\n", \
mkstring(buf4, 6, CENTER|LJUST, NULL), \
mkstring(buf5, 8, CENTER|LJUST, NULL)); \
}
#define PRINT_VMA_DATA() \
fprintf(fp, "%s%s%s%s%s %6llx%s%s\n", \
mkstring(buf4, VADDR_PRLEN, CENTER|LJUST|LONG_HEX, MKSTR(vma)), \
space(MINSPACE), \
mkstring(buf2, UVADDR_PRLEN, RJUST|LONG_HEX, MKSTR(vm_start)), \
space(MINSPACE), \
mkstring(buf3, UVADDR_PRLEN, RJUST|LONG_HEX, MKSTR(vm_end)), \
vm_flags, space(MINSPACE), buf1);
#define FILENAME_COMPONENT(P,C) \
((STREQ((P), "/") && STREQ((C), "/")) || \
(!STREQ((C), "/") && strstr((P),(C))))
#define VM_REF_SEARCH (0x1)
#define VM_REF_DISPLAY (0x2)
#define VM_REF_NUMBER (0x4)
#define VM_REF_VMA (0x8)
#define VM_REF_PAGE (0x10)
#define VM_REF_HEADER (0x20)
#define DO_REF_SEARCH(X) ((X) && ((X)->cmdflags & VM_REF_SEARCH))
#define DO_REF_DISPLAY(X) ((X) && ((X)->cmdflags & VM_REF_DISPLAY))
#define VM_REF_CHECK_HEXVAL(X,V) \
(DO_REF_SEARCH(X) && ((X)->cmdflags & VM_REF_NUMBER) && ((X)->hexval == (V)))
#define VM_REF_CHECK_DECVAL(X,V) \
(DO_REF_SEARCH(X) && ((X)->cmdflags & VM_REF_NUMBER) && ((X)->decval == (V)))
#define VM_REF_CHECK_STRING(X,S) \
(DO_REF_SEARCH(X) && (string_exists(S)) && FILENAME_COMPONENT((S),(X)->str))
#define VM_REF_FOUND(X) ((X) && ((X)->cmdflags & VM_REF_HEADER))
ulong
vm_area_dump(ulong task, ulong flag, ulong vaddr, struct reference *ref)
{
struct task_context *tc;
ulong vma;
ulong vm_start;
ulong vm_end;
ulong vm_next, vm_mm;
char *dentry_buf, *vma_buf, *file_buf;
ulonglong vm_flags;
ulong vm_file, inode;
ulong dentry, vfsmnt;
ulong single_vma;
unsigned int radix;
int single_vma_found;
int found;
struct task_mem_usage task_mem_usage, *tm;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
char buf5[BUFSIZE];
char vma_header[BUFSIZE];
tc = task_to_context(task);
tm = &task_mem_usage;
get_task_mem_usage(task, tm);
single_vma = 0;
single_vma_found = FALSE;
if (flag & PRINT_SINGLE_VMA) {
single_vma = vaddr;
vaddr = 0;
}
if (flag & PRINT_RADIX_10)
radix = 10;
else if (flag & PRINT_RADIX_16)
radix = 16;
else
radix = 0;
if (ref) {
ref->cmdflags = VM_REF_SEARCH;
if (IS_A_NUMBER(ref->str)) {
ref->hexval = htol(ref->str, FAULT_ON_ERROR, NULL);
if (decimal(ref->str, 0))
ref->decval = dtol(ref->str,
FAULT_ON_ERROR, NULL);
ref->cmdflags |= VM_REF_NUMBER;
}
}
if (VM_REF_CHECK_HEXVAL(ref, tm->mm_struct_addr) ||
VM_REF_CHECK_HEXVAL(ref, tm->pgd_addr)) {
print_task_header(fp, tc, 0);
PRINT_VM_DATA();
fprintf(fp, "\n");
return (ulong)NULL;
}
if (!(flag & (UVADDR|PRINT_MM_STRUCT|PRINT_VMA_STRUCTS|PRINT_SINGLE_VMA)) &&
!DO_REF_SEARCH(ref))
PRINT_VM_DATA();
if (!tm->mm_struct_addr)
return (ulong)NULL;
if (flag & PRINT_MM_STRUCT) {
dump_struct("mm_struct", tm->mm_struct_addr, radix);
return (ulong)NULL;
}
readmem(tm->mm_struct_addr + OFFSET(mm_struct_mmap), KVADDR,
&vma, sizeof(void *), "mm_struct mmap", FAULT_ON_ERROR);
sprintf(vma_header, "%s%s%s%s%s FLAGS%sFILE\n",
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "VMA"),
space(MINSPACE),
mkstring(buf2, UVADDR_PRLEN, CENTER|RJUST, "START"),
space(MINSPACE),
mkstring(buf3, UVADDR_PRLEN, CENTER|RJUST, "END"),
space(MINSPACE));
if (!(flag & (PHYSADDR|VERIFY_ADDR|PRINT_VMA_STRUCTS|PRINT_SINGLE_VMA)) &&
!DO_REF_SEARCH(ref))
fprintf(fp, "%s", vma_header);
for (found = FALSE; vma; vma = vm_next) {
if ((flag & PHYSADDR) && !DO_REF_SEARCH(ref))
fprintf(fp, "%s", vma_header);
inode = 0;
BZERO(buf1, BUFSIZE);
vma_buf = fill_vma_cache(vma);
vm_mm = ULONG(vma_buf + OFFSET(vm_area_struct_vm_mm));
vm_end = ULONG(vma_buf + OFFSET(vm_area_struct_vm_end));
vm_next = ULONG(vma_buf + OFFSET(vm_area_struct_vm_next));
vm_start = ULONG(vma_buf + OFFSET(vm_area_struct_vm_start));
vm_flags = get_vm_flags(vma_buf);
vm_file = ULONG(vma_buf + OFFSET(vm_area_struct_vm_file));
if (flag & PRINT_SINGLE_VMA) {
if (vma != single_vma)
continue;
fprintf(fp, "%s", vma_header);
single_vma_found = TRUE;
}
if (flag & PRINT_VMA_STRUCTS) {
dump_struct("vm_area_struct", vma, radix);
continue;
}
if (vm_file && !(flag & VERIFY_ADDR)) {
file_buf = fill_file_cache(vm_file);
dentry = ULONG(file_buf + OFFSET(file_f_dentry));
dentry_buf = NULL;
if (dentry) {
dentry_buf = fill_dentry_cache(dentry);
if (VALID_MEMBER(file_f_vfsmnt)) {
vfsmnt = ULONG(file_buf +
OFFSET(file_f_vfsmnt));
get_pathname(dentry, buf1, BUFSIZE,
1, vfsmnt);
} else {
get_pathname(dentry, buf1, BUFSIZE,
1, 0);
}
}
if ((flag & PRINT_INODES) && dentry) {
inode = ULONG(dentry_buf +
OFFSET(dentry_d_inode));
}
}
if (!(flag & UVADDR) || ((flag & UVADDR) &&
((vaddr >= vm_start) && (vaddr < vm_end)))) {
found = TRUE;
if (flag & VERIFY_ADDR)
return vma;
if (DO_REF_SEARCH(ref)) {
if (VM_REF_CHECK_HEXVAL(ref, vma) ||
VM_REF_CHECK_HEXVAL(ref, (ulong)vm_flags) ||
VM_REF_CHECK_STRING(ref, buf1)) {
if (!(ref->cmdflags & VM_REF_HEADER)) {
print_task_header(fp, tc, 0);
PRINT_VM_DATA();
ref->cmdflags |= VM_REF_HEADER;
}
if (!(ref->cmdflags & VM_REF_VMA) ||
(ref->cmdflags & VM_REF_PAGE)) {
fprintf(fp, "%s", vma_header);
ref->cmdflags |= VM_REF_VMA;
ref->cmdflags &= ~VM_REF_PAGE;
ref->ref1 = vma;
}
PRINT_VMA_DATA();
}
if (vm_area_page_dump(vma, task,
vm_start, vm_end, vm_mm, ref)) {
if (!(ref->cmdflags & VM_REF_HEADER)) {
print_task_header(fp, tc, 0);
PRINT_VM_DATA();
ref->cmdflags |= VM_REF_HEADER;
}
if (!(ref->cmdflags & VM_REF_VMA) ||
(ref->ref1 != vma)) {
fprintf(fp, "%s", vma_header);
PRINT_VMA_DATA();
ref->cmdflags |= VM_REF_VMA;
ref->ref1 = vma;
}
ref->cmdflags |= VM_REF_DISPLAY;
vm_area_page_dump(vma, task,
vm_start, vm_end, vm_mm, ref);
ref->cmdflags &= ~VM_REF_DISPLAY;
}
continue;
}
if (inode) {
fprintf(fp, "%lx%s%s%s%s%s%6llx%s%lx %s\n",
vma, space(MINSPACE),
mkstring(buf2, UVADDR_PRLEN, RJUST|LONG_HEX,
MKSTR(vm_start)), space(MINSPACE),
mkstring(buf3, UVADDR_PRLEN, RJUST|LONG_HEX,
MKSTR(vm_end)), space(MINSPACE),
vm_flags, space(MINSPACE), inode, buf1);
} else {
PRINT_VMA_DATA();
if (flag & (PHYSADDR|PRINT_SINGLE_VMA))
vm_area_page_dump(vma, task,
vm_start, vm_end, vm_mm, ref);
}
if (flag & UVADDR)
return vma;
}
}
if (flag & VERIFY_ADDR)
return (ulong)NULL;
if ((flag & PRINT_SINGLE_VMA) && !single_vma_found)
fprintf(fp, "(not found)\n");
if ((flag & UVADDR) && !found)
fprintf(fp, "(not found)\n");
if (VM_REF_FOUND(ref))
fprintf(fp, "\n");
return (ulong)NULL;
}
static int
vm_area_page_dump(ulong vma,
ulong task,
ulong start,
ulong end,
ulong mm,
struct reference *ref)
{
physaddr_t paddr;
ulong offs;
char *p1, *p2;
int display;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
if (mm == symbol_value("init_mm"))
return FALSE;
if (!ref || DO_REF_DISPLAY(ref))
fprintf(fp, "%s %s\n",
mkstring(buf1, UVADDR_PRLEN, LJUST, "VIRTUAL"),
mkstring(buf2, MAX(PADDR_PRLEN, strlen("PHYSICAL")),
LJUST, "PHYSICAL"));
if (DO_REF_DISPLAY(ref)) {
start = ref->ref2;
}
while (start < end) {
display = DO_REF_SEARCH(ref) ? FALSE : TRUE;
if (VM_REF_CHECK_HEXVAL(ref, start)) {
if (DO_REF_DISPLAY(ref))
display = TRUE;
else {
ref->cmdflags |= VM_REF_PAGE;
ref->ref2 = start;
return TRUE;
}
}
if (uvtop(task_to_context(task), start, &paddr, 0)) {
sprintf(buf3, "%s %s\n",
mkstring(buf1, UVADDR_PRLEN, LJUST|LONG_HEX,
MKSTR(start)),
mkstring(buf2, MAX(PADDR_PRLEN,
strlen("PHYSICAL")), RJUST|LONGLONG_HEX,
MKSTR(&paddr)));
if (VM_REF_CHECK_HEXVAL(ref, paddr)) {
if (DO_REF_DISPLAY(ref))
display = TRUE;
else {
ref->cmdflags |= VM_REF_PAGE;
ref->ref2 = start;
return TRUE;
}
}
} else if (paddr && swap_location(paddr, buf1)) {
sprintf(buf3, "%s SWAP: %s\n",
mkstring(buf2, UVADDR_PRLEN, LJUST|LONG_HEX,
MKSTR(start)), buf1);
if (DO_REF_SEARCH(ref)) {
if (VM_REF_CHECK_DECVAL(ref,
THIS_KERNEL_VERSION >= LINUX(2,6,0) ?
__swp_offset(paddr) : SWP_OFFSET(paddr))) {
if (DO_REF_DISPLAY(ref))
display = TRUE;
else {
ref->cmdflags |= VM_REF_PAGE;
ref->ref2 = start;
return TRUE;
}
}
strcpy(buf4, buf3);
p1 = strstr(buf4, "SWAP:") + strlen("SWAP: ");
p2 = strstr(buf4, " OFFSET:");
*p2 = NULLCHAR;
if (VM_REF_CHECK_STRING(ref, p1)) {
if (DO_REF_DISPLAY(ref))
display = TRUE;
else {
ref->cmdflags |= VM_REF_PAGE;
ref->ref2 = start;
return TRUE;
}
}
}
} else if (vma_file_offset(vma, start, buf1)) {
sprintf(buf3, "%s FILE: %s\n",
mkstring(buf2, UVADDR_PRLEN, LJUST|LONG_HEX,
MKSTR(start)), buf1);
if (DO_REF_SEARCH(ref)) {
extract_hex(strstr(buf3, "OFFSET:") +
strlen("OFFSET: "), &offs, 0, 0);
if (VM_REF_CHECK_HEXVAL(ref, offs)) {
if (DO_REF_DISPLAY(ref))
display = TRUE;
else {
ref->cmdflags |= VM_REF_PAGE;
ref->ref2 = start;
return TRUE;
}
}
}
} else {
sprintf(buf3, "%s (not mapped)\n",
mkstring(buf1, UVADDR_PRLEN, LJUST|LONG_HEX,
MKSTR(start)));
}
if (display)
fprintf(fp, "%s", buf3);
start += PAGESIZE();
}
return FALSE;
}
/*
* Cache the passed-in vm_area_struct.
*/
char *
fill_vma_cache(ulong vma)
{
int i;
char *cache;
vt->vma_cache_fills++;
for (i = 0; i < VMA_CACHE; i++) {
if (vt->cached_vma[i] == vma) {
vt->cached_vma_hits[i]++;
cache = vt->vma_cache + (SIZE(vm_area_struct)*i);
return(cache);
}
}
cache = vt->vma_cache + (SIZE(vm_area_struct)*vt->vma_cache_index);
readmem(vma, KVADDR, cache, SIZE(vm_area_struct),
"fill_vma_cache", FAULT_ON_ERROR);
vt->cached_vma[vt->vma_cache_index] = vma;
vt->vma_cache_index = (vt->vma_cache_index+1) % VMA_CACHE;
return(cache);
}
/*
* If active, clear the vm_area_struct references.
*/
void
clear_vma_cache(void)
{
int i;
if (DUMPFILE())
return;
for (i = 0; i < VMA_CACHE; i++) {
vt->cached_vma[i] = 0;
vt->cached_vma_hits[i] = 0;
}
vt->vma_cache_fills = 0;
vt->vma_cache_index = 0;
}
/*
* Check whether an address is a user stack address based
* upon its vm_area_struct flags.
*/
int
in_user_stack(ulong task, ulong vaddr)
{
ulong vma;
ulonglong vm_flags;
char *vma_buf;
if ((vma = vm_area_dump(task, UVADDR|VERIFY_ADDR, vaddr, 0))) {
vma_buf = fill_vma_cache(vma);
vm_flags = get_vm_flags(vma_buf);
if (vm_flags & VM_GROWSDOWN)
return TRUE;
else if (kernel_symbol_exists("expand_upwards") &&
(vm_flags & VM_GROWSUP))
return TRUE;
/*
* per-thread stack
*/
if ((vm_flags & (VM_READ|VM_WRITE)) == (VM_READ|VM_WRITE))
return TRUE;
}
return FALSE;
}
/*
* Set the const value of filepages and anonpages
* according to MM_FILEPAGES and MM_ANONPAGES.
*/
static void
rss_page_types_init(void)
{
long anonpages, filepages;
if (VALID_MEMBER(mm_struct_rss))
return;
if (VALID_MEMBER(mm_struct_rss_stat))
{
if (!enumerator_value("MM_FILEPAGES", &filepages) ||
!enumerator_value("MM_ANONPAGES", &anonpages))
{
filepages = 0;
anonpages = 1;
}
tt->filepages = filepages;
tt->anonpages = anonpages;
}
}
static struct tgid_context *
tgid_quick_search(ulong tgid)
{
struct tgid_context *last, *next;
tt->tgid_searches++;
last = tt->last_tgid;
if (tgid == last->tgid) {
tt->tgid_cache_hits++;
return last;
}
next = last + 1;
if ((next < (tt->tgid_array + RUNNING_TASKS())) &&
(tgid == next->tgid)) {
tt->tgid_cache_hits++;
return next;
}
return NULL;
}
/*
* Fill in the task_mem_usage structure with the RSS, virtual memory size,
* percent of physical memory being used, and the mm_struct address.
*/
void
get_task_mem_usage(ulong task, struct task_mem_usage *tm)
{
struct task_context *tc;
long rss = 0;
BZERO(tm, sizeof(struct task_mem_usage));
if (IS_ZOMBIE(task) || IS_EXITING(task))
return;
tc = task_to_context(task);
if (!tc || !tc->mm_struct) /* probably a kernel thread */
return;
tm->mm_struct_addr = tc->mm_struct;
if (!task_mm(task, TRUE))
return;
if (VALID_MEMBER(mm_struct_rss))
/*
* mm_struct.rss or mm_struct._rss exist.
*/
tm->rss = ULONG(tt->mm_struct + OFFSET(mm_struct_rss));
else {
/*
* Latest kernels have mm_struct.mm_rss_stat[].
*/
if (VALID_MEMBER(mm_struct_rss_stat)) {
long anonpages, filepages;
anonpages = tt->anonpages;
filepages = tt->filepages;
rss += LONG(tt->mm_struct +
OFFSET(mm_struct_rss_stat) +
OFFSET(mm_rss_stat_count) +
(filepages * sizeof(long)));
rss += LONG(tt->mm_struct +
OFFSET(mm_struct_rss_stat) +
OFFSET(mm_rss_stat_count) +
(anonpages * sizeof(long)));
}
/* Check whether SPLIT_RSS_COUNTING is enabled */
if (VALID_MEMBER(task_struct_rss_stat)) {
int sync_rss;
struct tgid_context tgid, *tgid_array, *tg, *first, *last;
tgid_array = tt->tgid_array;
tgid.tgid = task_tgid(task);
if (!(tg = tgid_quick_search(tgid.tgid)))
tg = (struct tgid_context *)bsearch(&tgid, tgid_array, RUNNING_TASKS(),
sizeof(struct tgid_context), sort_by_tgid);
if (tg) {
/* find the first element which has the same tgid */
first = tg;
while ((first > tgid_array) && ((first - 1)->tgid == first->tgid))
first--;
/* find the last element which have same tgid */
last = tg;
while ((last < (tgid_array + (RUNNING_TASKS() - 1))) &&
(last->tgid == (last + 1)->tgid))
last++;
while (first <= last)
{
/* count 0 -> filepages */
if (!readmem(first->task +
OFFSET(task_struct_rss_stat) +
OFFSET(task_rss_stat_count), KVADDR,
&sync_rss,
sizeof(int),
"task_struct rss_stat MM_FILEPAGES",
RETURN_ON_ERROR))
continue;
rss += sync_rss;
/* count 1 -> anonpages */
if (!readmem(first->task +
OFFSET(task_struct_rss_stat) +
OFFSET(task_rss_stat_count) +
sizeof(int),
KVADDR, &sync_rss,
sizeof(int),
"task_struct rss_stat MM_ANONPAGES",
RETURN_ON_ERROR))
continue;
rss += sync_rss;
if (first == last)
break;
first++;
}
tt->last_tgid = last;
}
}
/*
* mm_struct._anon_rss and mm_struct._file_rss should exist.
*/
if (VALID_MEMBER(mm_struct_anon_rss))
rss += LONG(tt->mm_struct + OFFSET(mm_struct_anon_rss));
if (VALID_MEMBER(mm_struct_file_rss))
rss += LONG(tt->mm_struct + OFFSET(mm_struct_file_rss));
tm->rss = (unsigned long)rss;
}
tm->total_vm = ULONG(tt->mm_struct + OFFSET(mm_struct_total_vm));
tm->pgd_addr = ULONG(tt->mm_struct + OFFSET(mm_struct_pgd));
if (is_kernel_thread(task))
return;
tm->pct_physmem = ((double)(tm->rss*100)) /
((double)(MIN(vt->total_pages,
vt->num_physpages ? vt->num_physpages : vt->total_pages)));
}
/*
* cmd_kmem() is designed as a multi-purpose kernel memory investigator with
* the flag argument sending it off in a multitude of areas. To date, the
* following options are defined:
*
* -f displays the contents of the system free_area[] array headers;
* also verifies that the page count equals nr_free_pages
* -F same as -f, but also dumps all pages linked to that header.
* -p displays basic information about each page in the system
* mem_map[] array.
* -s displays kmalloc() slab data.
* -S same as -s, but displays all kmalloc() objects.
* -v displays the vmlist entries.
* -c displays the number of pages in the page_hash_table.
* -C displays all entries in the page_hash_table.
* -i displays informational data shown by /proc/meminfo.
* -h hugepage information from hstates[] array
*
* -P forces address to be defined as a physical address
* address when used with -f, the address can be either a page pointer
* or a physical address; the free_area header containing the page
* (if any) is displayed.
* When used with -p, the address can be either a page pointer or a
* physical address; its basic mem_map page information is displayed.
* When used with -c, the page_hash_table entry containing the
* page pointer is displayed.
*/
/* Note: VERBOSE is 0x1, ADDRESS_SPECIFIED is 0x2 */
#define GET_TOTALRAM_PAGES (ADDRESS_SPECIFIED << 1)
#define GET_SHARED_PAGES (ADDRESS_SPECIFIED << 2)
#define GET_FREE_PAGES (ADDRESS_SPECIFIED << 3)
#define GET_FREE_HIGHMEM_PAGES (ADDRESS_SPECIFIED << 4)
#define GET_ZONE_SIZES (ADDRESS_SPECIFIED << 5)
#define GET_HIGHEST (ADDRESS_SPECIFIED << 6)
#define GET_BUFFERS_PAGES (ADDRESS_SPECIFIED << 7)
#define GET_SLAB_PAGES (ADDRESS_SPECIFIED << 8)
#define GET_PHYS_TO_VMALLOC (ADDRESS_SPECIFIED << 9)
#define GET_ACTIVE_LIST (ADDRESS_SPECIFIED << 10)
#define GET_INACTIVE_LIST (ADDRESS_SPECIFIED << 11)
#define GET_INACTIVE_CLEAN (ADDRESS_SPECIFIED << 12) /* obsolete */
#define GET_INACTIVE_DIRTY (ADDRESS_SPECIFIED << 13) /* obsolete */
#define SLAB_GET_COUNTS (ADDRESS_SPECIFIED << 14)
#define SLAB_WALKTHROUGH (ADDRESS_SPECIFIED << 15)
#define GET_VMLIST_COUNT (ADDRESS_SPECIFIED << 16)
#define GET_VMLIST (ADDRESS_SPECIFIED << 17)
#define SLAB_DATA_NOSAVE (ADDRESS_SPECIFIED << 18)
#define GET_SLUB_SLABS (ADDRESS_SPECIFIED << 19)
#define GET_SLUB_OBJECTS (ADDRESS_SPECIFIED << 20)
#define VMLIST_VERIFY (ADDRESS_SPECIFIED << 21)
#define SLAB_FIRST_NODE (ADDRESS_SPECIFIED << 22)
#define CACHE_SET (ADDRESS_SPECIFIED << 23)
#define SLAB_OVERLOAD_PAGE_PTR (ADDRESS_SPECIFIED << 24)
#define SLAB_BITFIELD (ADDRESS_SPECIFIED << 25)
#define GET_ALL \
(GET_SHARED_PAGES|GET_TOTALRAM_PAGES|GET_BUFFERS_PAGES|GET_SLAB_PAGES)
void
cmd_kmem(void)
{
int i;
int c;
int sflag, Sflag, pflag, fflag, Fflag, vflag, zflag, oflag, gflag;
int nflag, cflag, Cflag, iflag, lflag, Lflag, Pflag, Vflag, hflag;
struct meminfo meminfo;
ulonglong value[MAXARGS];
char buf[BUFSIZE];
char *p1;
int spec_addr, escape;
spec_addr = 0;
sflag = Sflag = pflag = fflag = Fflag = Pflag = zflag = oflag = 0;
vflag = Cflag = cflag = iflag = nflag = lflag = Lflag = Vflag = 0;
gflag = hflag = 0;
escape = FALSE;
BZERO(&meminfo, sizeof(struct meminfo));
BZERO(&value[0], sizeof(ulonglong)*MAXARGS);
while ((c = getopt(argcnt, args, "gI:sSFfpvczCinl:L:PVoh")) != EOF) {
switch(c)
{
case 'V':
Vflag = 1;
break;
case 'n':
nflag = 1;
break;
case 'z':
zflag = 1;
break;
case 'i':
iflag = 1;
break;
case 'h':
hflag = 1;
break;
case 'C':
Cflag = 1, cflag = 0;;
break;
case 'c':
cflag = 1, Cflag = 0;
break;
case 'v':
vflag = 1;
break;
case 's':
sflag = 1; Sflag = 0;
break;
case 'S':
Sflag = 1; sflag = 0;
break;
case 'F':
Fflag = 1; fflag = 0;
break;;
case 'f':
fflag = 1; Fflag = 0;
break;;
case 'p':
pflag = 1;
break;
case 'I':
meminfo.ignore = optarg;
break;
case 'l':
if (STREQ(optarg, "a")) {
meminfo.flags |= GET_ACTIVE_LIST;
lflag = 1; Lflag = 0;
} else if (STREQ(optarg, "i")) {
meminfo.flags |= GET_INACTIVE_LIST;
lflag = 1; Lflag = 0;
} else if (STREQ(optarg, "ic")) {
meminfo.flags |= GET_INACTIVE_CLEAN;
lflag = 1; Lflag = 0;
} else if (STREQ(optarg, "id")) {
meminfo.flags |= GET_INACTIVE_DIRTY;
lflag = 1; Lflag = 0;
} else
argerrs++;
break;
case 'L':
if (STREQ(optarg, "a")) {
meminfo.flags |= GET_ACTIVE_LIST;
Lflag = 1; lflag = 0;
} else if (STREQ(optarg, "i")) {
meminfo.flags |= GET_INACTIVE_LIST;
Lflag = 1; lflag = 0;
} else if (STREQ(optarg, "ic")) {
meminfo.flags |= GET_INACTIVE_CLEAN;
Lflag = 1; lflag = 0;
} else if (STREQ(optarg, "id")) {
meminfo.flags |= GET_INACTIVE_DIRTY;
Lflag = 1; lflag = 0;
} else
argerrs++;
break;
case 'P':
Pflag = 1;
break;
case 'o':
oflag = 1;
break;
case 'g':
gflag = 1;
break;
default:
argerrs++;
break;
}
}
if (argerrs)
cmd_usage(pc->curcmd, SYNOPSIS);
if ((sflag + Sflag + pflag + fflag + Fflag + Vflag + oflag +
vflag + Cflag + cflag + iflag + lflag + Lflag + gflag + hflag) > 1) {
error(INFO, "only one flag allowed!\n");
cmd_usage(pc->curcmd, SYNOPSIS);
}
if (sflag || Sflag || !(vt->flags & KMEM_CACHE_INIT))
kmem_cache_init();
while (args[optind]) {
if (hexadecimal(args[optind], 0)) {
value[spec_addr++] =
htoll(args[optind], FAULT_ON_ERROR, NULL);
} else {
if (meminfo.reqname)
error(FATAL,
"only one kmem_cache reference is allowed\n");
meminfo.reqname = args[optind];
if (args[optind][0] == '\\') {
meminfo.reqname = &args[optind][1];
escape = TRUE;
} else
meminfo.reqname = args[optind];
if (!sflag && !Sflag)
cmd_usage(pc->curcmd, SYNOPSIS);
}
optind++;
}
for (i = 0; i < spec_addr; i++) {
if (Pflag)
meminfo.memtype = PHYSADDR;
else
meminfo.memtype = IS_KVADDR(value[i]) ?
KVADDR : PHYSADDR;
if (fflag) {
meminfo.spec_addr = value[i];
meminfo.flags = ADDRESS_SPECIFIED;
if (meminfo.calls++)
fprintf(fp, "\n");
vt->dump_free_pages(&meminfo);
fflag++;
}
if (pflag) {
meminfo.spec_addr = value[i];
meminfo.flags = ADDRESS_SPECIFIED;
dump_mem_map(&meminfo);
pflag++;
}
if (sflag || Sflag) {
if (vt->flags & KMEM_CACHE_UNAVAIL)
error(FATAL,
"kmem cache slab subsystem not available\n");
meminfo.flags = Sflag ? VERBOSE : 0;
if (meminfo.memtype == PHYSADDR) {
if (value[i] < VTOP(vt->high_memory)) {
value[i] = PTOV(value[i]);
meminfo.memtype = KVADDR;
} else
error(WARNING,
"cannot make virtual-to-physical translation: %llx\n",
value[i]);
}
if ((p1 = is_kmem_cache_addr(value[i], buf))) {
if (meminfo.reqname)
error(FATAL,
"only one kmem_cache reference is allowed\n");
meminfo.reqname = p1;
meminfo.cache = value[i];
meminfo.flags |= CACHE_SET;
if ((i+1) == spec_addr) { /* done? */
if (meminfo.calls++)
fprintf(fp, "\n");
vt->dump_kmem_cache(&meminfo);
}
meminfo.flags &= ~CACHE_SET;
} else {
meminfo.spec_addr = value[i];
meminfo.flags = ADDRESS_SPECIFIED;
if (Sflag && (vt->flags & KMALLOC_SLUB))
meminfo.flags |= VERBOSE;
if (meminfo.calls++)
fprintf(fp, "\n");
vt->dump_kmem_cache(&meminfo);
}
if (sflag)
sflag++;
if (Sflag)
Sflag++;
}
if (vflag) {
meminfo.spec_addr = value[i];
meminfo.flags = ADDRESS_SPECIFIED;
dump_vmlist(&meminfo);
vflag++;
}
if (cflag) {
meminfo.spec_addr = value[i];
meminfo.flags = ADDRESS_SPECIFIED;
if (meminfo.calls++)
fprintf(fp, "\n");
dump_page_hash_table(&meminfo);
cflag++;
}
if (lflag) {
meminfo.spec_addr = value[i];
meminfo.flags |= (ADDRESS_SPECIFIED|VERBOSE);
if (meminfo.calls++)
fprintf(fp, "\n");
dump_page_lists(&meminfo);
lflag++;
}
if (gflag) {
if (i)
fprintf(fp, "\n");
dump_page_flags(value[i]);
gflag++;
}
/*
* no value arguments allowed!
*/
if (zflag || nflag || iflag || Fflag || Cflag || Lflag ||
Vflag || oflag || hflag) {
error(INFO,
"no address arguments allowed with this option\n");
cmd_usage(pc->curcmd, SYNOPSIS);
}
if (!(sflag + Sflag + pflag + fflag + vflag + cflag +
lflag + Lflag + gflag)) {
meminfo.spec_addr = value[i];
meminfo.flags = ADDRESS_SPECIFIED;
if (meminfo.calls++)
fprintf(fp, "\n");
else
kmem_cache_init();
kmem_search(&meminfo);
}
}
if (iflag == 1)
dump_kmeminfo();
if (pflag == 1)
dump_mem_map(&meminfo);
if (fflag == 1)
vt->dump_free_pages(&meminfo);
if (Fflag == 1) {
meminfo.flags = VERBOSE;
vt->dump_free_pages(&meminfo);
}
if (hflag == 1)
dump_hstates();
if (sflag == 1) {
if (!escape && STREQ(meminfo.reqname, "list"))
kmem_cache_list();
else if (vt->flags & KMEM_CACHE_UNAVAIL)
error(FATAL,
"kmem cache slab subsystem not available\n");
else
vt->dump_kmem_cache(&meminfo);
}
if (Sflag == 1) {
if (STREQ(meminfo.reqname, "list"))
kmem_cache_list();
else if (vt->flags & KMEM_CACHE_UNAVAIL)
error(FATAL,
"kmem cache slab subsystem not available\n");
else {
meminfo.flags = VERBOSE;
vt->dump_kmem_cache(&meminfo);
}
}
if (vflag == 1)
dump_vmlist(&meminfo);
if (Cflag == 1) {
meminfo.flags = VERBOSE;
dump_page_hash_table(&meminfo);
}
if (cflag == 1)
dump_page_hash_table(&meminfo);
if (nflag == 1)
dump_memory_nodes(MEMORY_NODES_DUMP);
if (zflag == 1)
dump_zone_stats();
if (lflag == 1) {
dump_page_lists(&meminfo);
}
if (Lflag == 1) {
meminfo.flags |= VERBOSE;
dump_page_lists(&meminfo);
}
if (Vflag == 1) {
dump_vm_stat(NULL, NULL, 0);
dump_page_states();
dump_vm_event_state();
}
if (oflag == 1)
dump_per_cpu_offsets();
if (gflag == 1)
dump_page_flags(0);
if (!(sflag + Sflag + pflag + fflag + Fflag + vflag +
Vflag + zflag + oflag + cflag + Cflag + iflag +
nflag + lflag + Lflag + gflag + hflag + meminfo.calls))
cmd_usage(pc->curcmd, SYNOPSIS);
}
static void
PG_reserved_flag_init(void)
{
ulong pageptr;
int count;
ulong vaddr, flags;
char *buf;
if (enumerator_value("PG_reserved", (long *)&flags)) {
vt->PG_reserved = 1 << flags;
if (CRASHDEBUG(2))
fprintf(fp, "PG_reserved (enum): %lx\n", vt->PG_reserved);
return;
}
vaddr = kt->stext ? kt->stext : symbol_value("sys_read");
if (!phys_to_page((physaddr_t)VTOP(vaddr), &pageptr))
return;
buf = (char *)GETBUF(SIZE(page));
if (!readmem(pageptr, KVADDR, buf, SIZE(page),
"reserved page", RETURN_ON_ERROR|QUIET)) {
FREEBUF(buf);
return;
}
flags = ULONG(buf + OFFSET(page_flags));
count = INT(buf + OFFSET(page_count));
if (count_bits_long(flags) == 1)
vt->PG_reserved = flags;
else
vt->PG_reserved = 1 << (ffsl(flags)-1);
if (count == -1)
vt->flags |= PGCNT_ADJ;
if (CRASHDEBUG(2))
fprintf(fp,
"PG_reserved: vaddr: %lx page: %lx flags: %lx => %lx\n",
vaddr, pageptr, flags, vt->PG_reserved);
FREEBUF(buf);
}
static void
PG_slab_flag_init(void)
{
int bit;
ulong pageptr;
ulong vaddr, flags, flags2;
char buf[BUFSIZE]; /* safe for a page struct */
/*
* Set the old defaults in case all else fails.
*/
if (enumerator_value("PG_slab", (long *)&flags)) {
vt->PG_slab = flags;
if (CRASHDEBUG(2))
fprintf(fp, "PG_slab (enum): %lx\n", vt->PG_slab);
} else if (VALID_MEMBER(page_pte)) {
if (THIS_KERNEL_VERSION < LINUX(2,6,0))
vt->PG_slab = 10;
else if (THIS_KERNEL_VERSION >= LINUX(2,6,0))
vt->PG_slab = 7;
} else if (THIS_KERNEL_VERSION >= LINUX(2,6,0)) {
vt->PG_slab = 7;
} else {
if (try_get_symbol_data("vm_area_cachep", sizeof(void *), &vaddr) &&
phys_to_page((physaddr_t)VTOP(vaddr), &pageptr) &&
readmem(pageptr, KVADDR, buf, SIZE(page),
"vm_area_cachep page", RETURN_ON_ERROR|QUIET)) {
flags = ULONG(buf + OFFSET(page_flags));
if ((bit = ffsl(flags))) {
vt->PG_slab = bit - 1;
if (CRASHDEBUG(2))
fprintf(fp,
"PG_slab bit: vaddr: %lx page: %lx flags: %lx => %ld\n",
vaddr, pageptr, flags, vt->PG_slab);
}
}
}
if (vt->flags & KMALLOC_SLUB) {
/*
* PG_slab and the following are hardwired for
* kernels prior to the pageflags enumerator.
*/
#define PG_compound 14 /* Part of a compound page */
#define PG_reclaim 17 /* To be reclaimed asap */
vt->PG_head_tail_mask = ((1L << PG_compound) | (1L << PG_reclaim));
if (enumerator_value("PG_tail", (long *)&flags))
vt->PG_head_tail_mask = (1L << flags);
else if (enumerator_value("PG_compound", (long *)&flags) &&
enumerator_value("PG_reclaim", (long *)&flags2)) {
vt->PG_head_tail_mask = ((1L << flags) | (1L << flags2));
if (CRASHDEBUG(2))
fprintf(fp, "PG_head_tail_mask: %lx\n",
vt->PG_head_tail_mask);
}
} else {
if (enumerator_value("PG_tail", (long *)&flags))
vt->PG_head_tail_mask = (1L << flags);
else if (enumerator_value("PG_compound", (long *)&flags) &&
enumerator_value("PG_reclaim", (long *)&flags2)) {
vt->PG_head_tail_mask = ((1L << flags) | (1L << flags2));
if (CRASHDEBUG(2))
fprintf(fp, "PG_head_tail_mask: %lx (PG_compound|PG_reclaim)\n",
vt->PG_head_tail_mask);
}
}
if (!vt->PG_slab)
error(INFO, "cannot determine PG_slab bit value\n");
}
/*
* dump_mem_map() displays basic data about each entry in the mem_map[]
* array, or if an address is specified, just the mem_map[] entry for that
* address. Specified addresses can either be physical address or page
* structure pointers.
*/
/* Page flag bit values */
#define v22_PG_locked 0
#define v22_PG_error 1
#define v22_PG_referenced 2
#define v22_PG_dirty 3
#define v22_PG_uptodate 4
#define v22_PG_free_after 5
#define v22_PG_decr_after 6
#define v22_PG_swap_unlock_after 7
#define v22_PG_DMA 8
#define v22_PG_Slab 9
#define v22_PG_swap_cache 10
#define v22_PG_skip 11
#define v22_PG_reserved 31
#define v24_PG_locked 0
#define v24_PG_error 1
#define v24_PG_referenced 2
#define v24_PG_uptodate 3
#define v24_PG_dirty 4
#define v24_PG_decr_after 5
#define v24_PG_active 6
#define v24_PG_inactive_dirty 7
#define v24_PG_slab 8
#define v24_PG_swap_cache 9
#define v24_PG_skip 10
#define v24_PG_inactive_clean 11
#define v24_PG_highmem 12
#define v24_PG_checked 13 /* kill me in 2.5.<early>. */
#define v24_PG_bigpage 14
/* bits 21-30 unused */
#define v24_PG_arch_1 30
#define v24_PG_reserved 31
#define v26_PG_private 12
#define PGMM_CACHED (512)
static void
dump_mem_map_SPARSEMEM(struct meminfo *mi)
{
ulong i;
long total_pages;
int others, page_not_mapped, phys_not_mapped, page_mapping;
ulong pp, ppend;
physaddr_t phys, physend;
ulong tmp, reserved, shared, slabs;
ulong PG_reserved_flag;
long buffers;
ulong inode, offset, flags, mapping, index;
uint count;
int print_hdr, pg_spec, phys_spec, done;
int v22;
char hdr[BUFSIZE];
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
char *page_cache;
char *pcache;
ulong section, section_nr, nr_mem_sections, section_size;
long buffersize;
char *outputbuffer;
int bufferindex;
buffersize = 1024 * 1024;
outputbuffer = GETBUF(buffersize + 512);
char style1[100];
char style2[100];
char style3[100];
char style4[100];
sprintf((char *)&style1, "%%lx%s%%%dllx%s%%%dlx%s%%8lx %%2d%s",
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
VADDR_PRLEN,
space(MINSPACE),
space(MINSPACE));
sprintf((char *)&style2, "%%-%dlx%s%%%dllx%s%s%s%s %2s ",
VADDR_PRLEN,
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, " "),
space(MINSPACE),
mkstring(buf4, 8, CENTER|RJUST, " "),
" ");
sprintf((char *)&style3, "%%-%dlx%s%%%dllx%s%s%s%s %%2d ",
VADDR_PRLEN,
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, "-------"),
space(MINSPACE),
mkstring(buf4, 8, CENTER|RJUST, "-----"));
sprintf((char *)&style4, "%%-%dlx%s%%%dllx%s%%%dlx%s%%8lx %%2d ",
VADDR_PRLEN,
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
VADDR_PRLEN,
space(MINSPACE));
v22 = VALID_MEMBER(page_inode); /* page.inode vs. page.mapping */
if (v22) {
sprintf(hdr, "%s%s%s%s%s%s%s%sCNT FLAGS\n",
mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE"),
space(MINSPACE),
mkstring(buf2, MAX(PADDR_PRLEN, strlen("PHYSICAL")),
RJUST, "PHYSICAL"),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, "INODE"),
space(MINSPACE),
mkstring(buf4, 8, CENTER|LJUST, "OFFSET"),
space(MINSPACE-1));
} else {
sprintf(hdr, "%s%s%s%s%s%s%sCNT FLAGS\n",
mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE"),
space(MINSPACE),
mkstring(buf2, MAX(PADDR_PRLEN, strlen("PHYSICAL")),
RJUST, "PHYSICAL"),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, "MAPPING"),
space(MINSPACE),
mkstring(buf4, 8, CENTER|RJUST, "INDEX"));
}
mapping = index = 0;
reserved = shared = slabs = buffers = inode = offset = 0;
pg_spec = phys_spec = print_hdr = FALSE;
switch (mi->flags)
{
case ADDRESS_SPECIFIED:
switch (mi->memtype)
{
case KVADDR:
if (is_page_ptr(mi->spec_addr, NULL))
pg_spec = TRUE;
else {
if (kvtop(NULL, mi->spec_addr, &phys, 0)) {
mi->spec_addr = phys;
phys_spec = TRUE;
}
else
return;
}
break;
case PHYSADDR:
phys_spec = TRUE;
break;
default:
error(FATAL, "dump_mem_map: no memtype specified\n");
break;
}
print_hdr = TRUE;
break;
case GET_ALL:
shared = 0;
reserved = 0;
buffers = 0;
slabs = 0;
break;
case GET_SHARED_PAGES:
shared = 0;
break;
case GET_TOTALRAM_PAGES:
reserved = 0;
break;
case GET_BUFFERS_PAGES:
buffers = 0;
break;
case GET_SLAB_PAGES:
slabs = 0;
break;
default:
print_hdr = TRUE;
break;
}
page_cache = GETBUF(SIZE(page) * PGMM_CACHED);
done = FALSE;
total_pages = 0;
nr_mem_sections = NR_MEM_SECTIONS();
bufferindex = 0;
/*
* Iterate over all possible sections
*/
for (section_nr = 0; section_nr < nr_mem_sections ; section_nr++) {
if (CRASHDEBUG(2))
fprintf(fp, "section_nr = %ld\n", section_nr);
/*
* If we are looking up a specific address, jump directly
* to the section with that page
*/
if (mi->flags & ADDRESS_SPECIFIED) {
ulong pfn;
physaddr_t tmp;
if (pg_spec) {
if (!page_to_phys(mi->spec_addr, &tmp))
return;
pfn = tmp >> PAGESHIFT();
} else
pfn = mi->spec_addr >> PAGESHIFT();
section_nr = pfn_to_section_nr(pfn);
}
if (!(section = valid_section_nr(section_nr))) {
#ifdef NOTDEF
break; /* On a real sparsemem system we need to check
* every section as gaps may exist. But this
* can be slow. If we know we don't have gaps
* just stop validating sections when we
* get to the end of the valid ones.
* In the future find a way to short circuit
* this loop.
*/
#endif
if (mi->flags & ADDRESS_SPECIFIED)
break;
continue;
}
if (print_hdr) {
if (!(pc->curcmd_flags & HEADER_PRINTED))
fprintf(fp, "%s", hdr);
print_hdr = FALSE;
pc->curcmd_flags |= HEADER_PRINTED;
}
pp = section_mem_map_addr(section);
pp = sparse_decode_mem_map(pp, section_nr);
phys = (physaddr_t) section_nr * PAGES_PER_SECTION() * PAGESIZE();
section_size = PAGES_PER_SECTION();
for (i = 0; i < section_size;
i++, pp += SIZE(page), phys += PAGESIZE()) {
if ((i % PGMM_CACHED) == 0) {
ppend = pp + ((PGMM_CACHED-1) * SIZE(page));
physend = phys + ((PGMM_CACHED-1) * PAGESIZE());
if ((pg_spec && (mi->spec_addr > ppend)) ||
(phys_spec &&
(PHYSPAGEBASE(mi->spec_addr) > physend))) {
i += (PGMM_CACHED-1);
pp = ppend;
phys = physend;
continue;
}
fill_mem_map_cache(pp, ppend, page_cache);
}
pcache = page_cache + ((i%PGMM_CACHED) * SIZE(page));
if (received_SIGINT())
restart(0);
if ((pg_spec && (pp == mi->spec_addr)) ||
(phys_spec && (phys == PHYSPAGEBASE(mi->spec_addr))))
done = TRUE;
if (!done && (pg_spec || phys_spec))
continue;
flags = ULONG(pcache + OFFSET(page_flags));
if (SIZE(page_flags) == 4)
flags &= 0xffffffff;
count = UINT(pcache + OFFSET(page_count));
switch (mi->flags)
{
case GET_ALL:
case GET_BUFFERS_PAGES:
if (VALID_MEMBER(page_buffers)) {
tmp = ULONG(pcache +
OFFSET(page_buffers));
if (tmp)
buffers++;
} else if (THIS_KERNEL_VERSION >= LINUX(2,6,0)) {
if ((flags >> v26_PG_private) & 1)
buffers++;
} else
error(FATAL,
"cannot determine whether pages have buffers\n");
if (mi->flags != GET_ALL)
continue;
/* FALLTHROUGH */
case GET_SLAB_PAGES:
if (v22) {
if ((flags >> v22_PG_Slab) & 1)
slabs++;
} else if (vt->PG_slab) {
if ((flags >> vt->PG_slab) & 1)
slabs++;
} else {
if ((flags >> v24_PG_slab) & 1)
slabs++;
}
if (mi->flags != GET_ALL)
continue;
/* FALLTHROUGH */
case GET_SHARED_PAGES:
case GET_TOTALRAM_PAGES:
if (vt->PG_reserved)
PG_reserved_flag = vt->PG_reserved;
else
PG_reserved_flag = v22 ?
1 << v22_PG_reserved :
1 << v24_PG_reserved;
if (flags & PG_reserved_flag) {
reserved++;
} else {
if ((int)count >
(vt->flags & PGCNT_ADJ ? 0 : 1))
shared++;
}
continue;
}
page_mapping = VALID_MEMBER(page_mapping);
if (v22) {
inode = ULONG(pcache + OFFSET(page_inode));
offset = ULONG(pcache + OFFSET(page_offset));
} else if (page_mapping) {
mapping = ULONG(pcache +
OFFSET(page_mapping));
index = ULONG(pcache + OFFSET(page_index));
}
page_not_mapped = phys_not_mapped = FALSE;
if (v22) {
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style1, pp, phys, inode,
offset, count);
} else {
if ((vt->flags & V_MEM_MAP)) {
if (!machdep->verify_paddr(phys))
phys_not_mapped = TRUE;
if (!kvtop(NULL, pp, NULL, 0))
page_not_mapped = TRUE;
}
if (page_not_mapped)
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style2, pp, phys);
else if (!page_mapping)
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style3, pp, phys, count);
else
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style4, pp, phys,
mapping, index, count);
}
others = 0;
#define sprintflag(X) sprintf(outputbuffer + bufferindex, X, others++ ? "," : "")
if (v22) {
if ((flags >> v22_PG_DMA) & 1)
bufferindex += sprintflag("%sDMA");
if ((flags >> v22_PG_locked) & 1)
bufferindex += sprintflag("%slocked");
if ((flags >> v22_PG_error) & 1)
bufferindex += sprintflag("%serror");
if ((flags >> v22_PG_referenced) & 1)
bufferindex += sprintflag("%sreferenced");
if ((flags >> v22_PG_dirty) & 1)
bufferindex += sprintflag("%sdirty");
if ((flags >> v22_PG_uptodate) & 1)
bufferindex += sprintflag("%suptodate");
if ((flags >> v22_PG_free_after) & 1)
bufferindex += sprintflag("%sfree_after");
if ((flags >> v22_PG_decr_after) & 1)
bufferindex += sprintflag("%sdecr_after");
if ((flags >> v22_PG_swap_unlock_after) & 1)
bufferindex += sprintflag("%sswap_unlock_after");
if ((flags >> v22_PG_Slab) & 1)
bufferindex += sprintflag("%sslab");
if ((flags >> v22_PG_swap_cache) & 1)
bufferindex += sprintflag("%sswap_cache");
if ((flags >> v22_PG_skip) & 1)
bufferindex += sprintflag("%sskip");
if ((flags >> v22_PG_reserved) & 1)
bufferindex += sprintflag("%sreserved");
bufferindex += sprintf(outputbuffer+bufferindex, "\n");
} else if (THIS_KERNEL_VERSION > LINUX(2,4,9)) {
if (vt->flags & PAGEFLAGS)
bufferindex += translate_page_flags(outputbuffer+bufferindex, flags);
else
bufferindex += sprintf(outputbuffer+bufferindex, "%lx\n", flags);
} else {
if ((flags >> v24_PG_locked) & 1)
bufferindex += sprintflag("%slocked");
if ((flags >> v24_PG_error) & 1)
bufferindex += sprintflag("%serror");
if ((flags >> v24_PG_referenced) & 1)
bufferindex += sprintflag("%sreferenced");
if ((flags >> v24_PG_uptodate) & 1)
bufferindex += sprintflag("%suptodate");
if ((flags >> v24_PG_dirty) & 1)
bufferindex += sprintflag("%sdirty");
if ((flags >> v24_PG_decr_after) & 1)
bufferindex += sprintflag("%sdecr_after");
if ((flags >> v24_PG_active) & 1)
bufferindex += sprintflag("%sactive");
if ((flags >> v24_PG_inactive_dirty) & 1)
bufferindex += sprintflag("%sinactive_dirty");
if ((flags >> v24_PG_slab) & 1)
bufferindex += sprintflag("%sslab");
if ((flags >> v24_PG_swap_cache) & 1)
bufferindex += sprintflag("%sswap_cache");
if ((flags >> v24_PG_skip) & 1)
bufferindex += sprintflag("%sskip");
if ((flags >> v24_PG_inactive_clean) & 1)
bufferindex += sprintflag("%sinactive_clean");
if ((flags >> v24_PG_highmem) & 1)
bufferindex += sprintflag("%shighmem");
if ((flags >> v24_PG_checked) & 1)
bufferindex += sprintflag("%schecked");
if ((flags >> v24_PG_bigpage) & 1)
bufferindex += sprintflag("%sbigpage");
if ((flags >> v24_PG_arch_1) & 1)
bufferindex += sprintflag("%sarch_1");
if ((flags >> v24_PG_reserved) & 1)
bufferindex += sprintflag("%sreserved");
if (phys_not_mapped)
bufferindex += sprintflag("%s[NOT MAPPED]");
bufferindex += sprintf(outputbuffer+bufferindex, "\n");
}
if (bufferindex > buffersize) {
fprintf(fp, "%s", outputbuffer);
bufferindex = 0;
}
if (done)
break;
}
if (done)
break;
}
if (bufferindex > 0) {
fprintf(fp, "%s", outputbuffer);
}
switch (mi->flags)
{
case GET_TOTALRAM_PAGES:
mi->retval = total_pages - reserved;
break;
case GET_SHARED_PAGES:
mi->retval = shared;
break;
case GET_BUFFERS_PAGES:
mi->retval = buffers;
break;
case GET_SLAB_PAGES:
mi->retval = slabs;
break;
case GET_ALL:
mi->get_totalram = total_pages - reserved;
mi->get_shared = shared;
mi->get_buffers = buffers;
mi->get_slabs = slabs;
break;
case ADDRESS_SPECIFIED:
mi->retval = done;
break;
}
FREEBUF(outputbuffer);
FREEBUF(page_cache);
}
static void
dump_mem_map(struct meminfo *mi)
{
long i, n;
long total_pages;
int others, page_not_mapped, phys_not_mapped, page_mapping;
ulong pp, ppend;
physaddr_t phys, physend;
ulong tmp, reserved, shared, slabs;
ulong PG_reserved_flag;
long buffers;
ulong inode, offset, flags, mapping, index;
ulong node_size;
uint count;
int print_hdr, pg_spec, phys_spec, done;
int v22;
struct node_table *nt;
char hdr[BUFSIZE];
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
char *page_cache;
char *pcache;
long buffersize;
char *outputbuffer;
int bufferindex;
buffersize = 1024 * 1024;
outputbuffer = GETBUF(buffersize + 512);
char style1[100];
char style2[100];
char style3[100];
char style4[100];
if (IS_SPARSEMEM()) {
dump_mem_map_SPARSEMEM(mi);
return;
}
sprintf((char *)&style1, "%%lx%s%%%dllx%s%%%dlx%s%%8lx %%2d%s",
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
VADDR_PRLEN,
space(MINSPACE),
space(MINSPACE));
sprintf((char *)&style2, "%%-%dlx%s%%%dllx%s%s%s%s %2s ",
VADDR_PRLEN,
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, " "),
space(MINSPACE),
mkstring(buf4, 8, CENTER|RJUST, " "),
" ");
sprintf((char *)&style3, "%%-%dlx%s%%%dllx%s%s%s%s %%2d ",
VADDR_PRLEN,
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, "-------"),
space(MINSPACE),
mkstring(buf4, 8, CENTER|RJUST, "-----"));
sprintf((char *)&style4, "%%-%dlx%s%%%dllx%s%%%dlx%s%%8lx %%2d ",
VADDR_PRLEN,
space(MINSPACE),
(int)MAX(PADDR_PRLEN, strlen("PHYSICAL")),
space(MINSPACE),
VADDR_PRLEN,
space(MINSPACE));
v22 = VALID_MEMBER(page_inode); /* page.inode vs. page.mapping */
if (v22) {
sprintf(hdr, "%s%s%s%s%s%s%s%sCNT FLAGS\n",
mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE"),
space(MINSPACE),
mkstring(buf2, MAX(PADDR_PRLEN, strlen("PHYSICAL")),
RJUST, "PHYSICAL"),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, "INODE"),
space(MINSPACE),
mkstring(buf4, 8, CENTER|LJUST, "OFFSET"),
space(MINSPACE-1));
} else {
sprintf(hdr, "%s%s%s%s%s%s%sCNT FLAGS\n",
mkstring(buf1, VADDR_PRLEN, CENTER, "PAGE"),
space(MINSPACE),
mkstring(buf2, MAX(PADDR_PRLEN, strlen("PHYSICAL")),
RJUST, "PHYSICAL"),
space(MINSPACE),
mkstring(buf3, VADDR_PRLEN, CENTER|RJUST, "MAPPING"),
space(MINSPACE),
mkstring(buf4, 8, CENTER|RJUST, "INDEX"));
}
mapping = index = 0;
reserved = shared = slabs = buffers = inode = offset = 0;
pg_spec = phys_spec = print_hdr = FALSE;
switch (mi->flags)
{
case ADDRESS_SPECIFIED:
switch (mi->memtype)
{
case KVADDR:
if (is_page_ptr(mi->spec_addr, NULL))
pg_spec = TRUE;
else {
if (kvtop(NULL, mi->spec_addr, &phys, 0)) {
mi->spec_addr = phys;
phys_spec = TRUE;
}
else
return;
}
break;
case PHYSADDR:
phys_spec = TRUE;
break;
default:
error(FATAL, "dump_mem_map: no memtype specified\n");
break;
}
print_hdr = TRUE;
break;
case GET_ALL:
shared = 0;
reserved = 0;
buffers = 0;
slabs = 0;
break;
case GET_SHARED_PAGES:
shared = 0;
break;
case GET_TOTALRAM_PAGES:
reserved = 0;
break;
case GET_BUFFERS_PAGES:
buffers = 0;
break;
case GET_SLAB_PAGES:
slabs = 0;
break;
default:
print_hdr = TRUE;
break;
}
page_cache = GETBUF(SIZE(page) * PGMM_CACHED);
done = FALSE;
total_pages = 0;
bufferindex = 0;
for (n = 0; n < vt->numnodes; n++) {
if (print_hdr) {
if (!(pc->curcmd_flags & HEADER_PRINTED))
fprintf(fp, "%s%s", n ? "\n" : "", hdr);
print_hdr = FALSE;
pc->curcmd_flags |= HEADER_PRINTED;
}
nt = &vt->node_table[n];
total_pages += nt->size;
pp = nt->mem_map;
phys = nt->start_paddr;
if ((vt->flags & V_MEM_MAP) && (vt->numnodes == 1))
node_size = vt->max_mapnr;
else
node_size = nt->size;
for (i = 0; i < node_size;
i++, pp += SIZE(page), phys += PAGESIZE()) {
if ((i % PGMM_CACHED) == 0) {
ppend = pp + ((PGMM_CACHED-1) * SIZE(page));
physend = phys + ((PGMM_CACHED-1) * PAGESIZE());
if ((pg_spec && (mi->spec_addr > ppend)) ||
(phys_spec &&
(PHYSPAGEBASE(mi->spec_addr) > physend))) {
i += (PGMM_CACHED-1);
pp = ppend;
phys = physend;
continue;
}
fill_mem_map_cache(pp, ppend, page_cache);
}
pcache = page_cache + ((i%PGMM_CACHED) * SIZE(page));
if (received_SIGINT())
restart(0);
if ((pg_spec && (pp == mi->spec_addr)) ||
(phys_spec && (phys == PHYSPAGEBASE(mi->spec_addr))))
done = TRUE;
if (!done && (pg_spec || phys_spec))
continue;
flags = ULONG(pcache + OFFSET(page_flags));
if (SIZE(page_flags) == 4)
flags &= 0xffffffff;
count = UINT(pcache + OFFSET(page_count));
switch (mi->flags)
{
case GET_ALL:
case GET_BUFFERS_PAGES:
if (VALID_MEMBER(page_buffers)) {
tmp = ULONG(pcache +
OFFSET(page_buffers));
if (tmp)
buffers++;
} else if (THIS_KERNEL_VERSION >= LINUX(2,6,0)) {
if ((flags >> v26_PG_private) & 1)
buffers++;
} else
error(FATAL,
"cannot determine whether pages have buffers\n");
if (mi->flags != GET_ALL)
continue;
/* FALLTHROUGH */
case GET_SLAB_PAGES:
if (v22) {
if ((flags >> v22_PG_Slab) & 1)
slabs++;
} else if (vt->PG_slab) {
if ((flags >> vt->PG_slab) & 1)
slabs++;
} else {
if ((flags >> v24_PG_slab) & 1)
slabs++;
}
if (mi->flags != GET_ALL)
continue;
/* FALLTHROUGH */
case GET_SHARED_PAGES:
case GET_TOTALRAM_PAGES:
if (vt->PG_reserved)
PG_reserved_flag = vt->PG_reserved;
else
PG_reserved_flag = v22 ?
1 << v22_PG_reserved :
1 << v24_PG_reserved;
if (flags & PG_reserved_flag) {
reserved++;
} else {
if ((int)count >
(vt->flags & PGCNT_ADJ ? 0 : 1))
shared++;
}
continue;
}
page_mapping = VALID_MEMBER(page_mapping);
if (v22) {
inode = ULONG(pcache + OFFSET(page_inode));
offset = ULONG(pcache + OFFSET(page_offset));
} else if (page_mapping) {
mapping = ULONG(pcache +
OFFSET(page_mapping));
index = ULONG(pcache + OFFSET(page_index));
}
page_not_mapped = phys_not_mapped = FALSE;
if (v22) {
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style1, pp, phys, inode,
offset, count);
} else {
if ((vt->flags & V_MEM_MAP)) {
if (!machdep->verify_paddr(phys))
phys_not_mapped = TRUE;
if (!kvtop(NULL, pp, NULL, 0))
page_not_mapped = TRUE;
}
if (page_not_mapped)
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style2, pp, phys);
else if (!page_mapping)
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style3, pp, phys, count);
else
bufferindex += sprintf(outputbuffer+bufferindex,
(char *)&style4, pp, phys,
mapping, index, count);
}
others = 0;
#define sprintflag(X) sprintf(outputbuffer + bufferindex, X, others++ ? "," : "")
if (v22) {
if ((flags >> v22_PG_DMA) & 1)
bufferindex += sprintflag("%sDMA");
if ((flags >> v22_PG_locked) & 1)
bufferindex += sprintflag("%slocked");
if ((flags >> v22_PG_error) & 1)
bufferindex += sprintflag("%serror");
if ((flags >> v22_PG_referenced) & 1)
bufferindex += sprintflag("%sreferenced");
if ((flags >> v22_PG_dirty) & 1)
bufferindex += sprintflag("%sdirty");
if ((flags >> v22_PG_uptodate) & 1)
bufferindex += sprintflag("%suptodate");
if ((flags >> v22_PG_free_after) & 1)
bufferindex += sprintflag("%sfree_after");
if ((flags >> v22_PG_decr_after) & 1)
bufferindex += sprintflag("%sdecr_after");
if ((flags >> v22_PG_swap_unlock_after) & 1)
bufferindex += sprintflag("%sswap_unlock_after");
if ((flags >> v22_PG_Slab) & 1)
bufferindex += sprintflag("%sslab");
if ((flags >> v22_PG_swap_cache) & 1)
bufferindex += sprintflag("%sswap_cache");
if ((flags >> v22_PG_skip) & 1)
bufferindex += sprintflag("%sskip");
if ((flags >> v22_PG_reserved) & 1)
bufferindex += sprintflag("%sreserved");
bufferindex += sprintf(outputbuffer+bufferindex, "\n");
} else if (THIS_KERNEL_VERSION > LINUX(2,4,9)) {
if (vt->flags & PAGEFLAGS)
bufferindex += translate_page_flags(outputbuffer+bufferindex, flags);
else
bufferindex += sprintf(outputbuffer+bufferindex, "%lx\n", flags);
} else {
if ((flags >> v24_PG_locked) & 1)
bufferindex += sprintflag("%slocked");
if ((flags >> v24_PG_error) & 1)
bufferindex += sprintflag("%serror");
if ((flags >> v24_PG_referenced) & 1)
bufferindex += sprintflag("%sreferenced");
if ((flags >> v24_PG_uptodate) & 1)
bufferindex += sprintflag("%suptodate");
if ((flags >> v24_PG_dirty) & 1)
bufferindex += sprintflag("%sdirty");
if ((flags >> v24_PG_decr_after) & 1)
bufferindex += sprintflag("%sdecr_after");
if ((flags >> v24_PG_active) & 1)
bufferindex += sprintflag("%sactive");
if ((flags >> v24_PG_inactive_dirty) & 1)
bufferindex += sprintflag("%sinactive_dirty");
if ((flags >> v24_PG_slab) & 1)
bufferindex += sprintflag("%sslab");
if ((flags >> v24_PG_swap_cache) & 1)
bufferindex += sprintflag("%sswap_cache");
if ((flags >> v24_PG_skip) & 1)
bufferindex += sprintflag("%sskip");
if ((flags >> v24_PG_inactive_clean) & 1)
bufferindex += sprintflag("%sinactive_clean");
if ((flags >> v24_PG_highmem) & 1)
bufferindex += sprintflag("%shighmem");
if ((flags >> v24_PG_checked) & 1)
bufferindex += sprintflag("%schecked");
if ((flags >> v24_PG_bigpage) & 1)
bufferindex += sprintflag("%sbigpage");
if ((flags >> v24_PG_arch_1) & 1)
bufferindex += sprintflag("%sarch_1");
if ((flags >> v24_PG_reserved) & 1)
bufferindex += sprintflag("%sreserved");
if (phys_not_mapped)
bufferindex += sprintflag("%s[NOT MAPPED]");
bufferindex += sprintf(outputbuffer+bufferindex, "\n");
}
if (bufferindex > buffersize) {
fprintf(fp, "%s", outputbuffer);
bufferindex = 0;
}
if (done)
break;
}
if (done)
break;
}
if (bufferindex > 0) {
fprintf(fp, "%s", outputbuffer);
}
switch (mi->flags)
{
case GET_TOTALRAM_PAGES:
mi->retval = total_pages - reserved;
break;
case GET_SHARED_PAGES:
mi->retval = shared;
break;
case GET_BUFFERS_PAGES:
mi->retval = buffers;
break;
case GET_SLAB_PAGES:
mi->retval = slabs;
break;
case GET_ALL:
mi->get_totalram = total_pages - reserved;
mi->get_shared = shared;
mi->get_buffers = buffers;
mi->get_slabs = slabs;
break;
case ADDRESS_SPECIFIED:
mi->retval = done;
break;
}
FREEBUF(outputbuffer);
FREEBUF(page_cache);
}
/*
* Stash a chunk of PGMM_CACHED page structures, starting at addr, into the
* passed-in buffer. The mem_map array is normally guaranteed to be
* readable except in the case of virtual mem_map usage. When V_MEM_MAP
* is in place, read all pages consumed by PGMM_CACHED page structures
* that are currently mapped, leaving the unmapped ones just zeroed out.
*/
static void
fill_mem_map_cache(ulong pp, ulong ppend, char *page_cache)
{
long size, cnt;
ulong addr;
char *bufptr;
/*
* Try to read it in one fell swoop.
*/
if (readmem(pp, KVADDR, page_cache, SIZE(page) * PGMM_CACHED,
"page struct cache", RETURN_ON_ERROR|QUIET))
return;
/*
* Break it into page-size-or-less requests, warning if it's
* not a virtual mem_map.
*/
size = SIZE(page) * PGMM_CACHED;
addr = pp;
bufptr = page_cache;
while (size > 0) {
/*
* Compute bytes till end of page.
*/
cnt = PAGESIZE() - PAGEOFFSET(addr);
if (cnt > size)
cnt = size;
if (!readmem(addr, KVADDR, bufptr, size,
"virtual page struct cache", RETURN_ON_ERROR|QUIET)) {
BZERO(bufptr, size);
if (!(vt->flags & V_MEM_MAP) && ((addr+size) < ppend))
error(WARNING,
"mem_map[] from %lx to %lx not accessible\n",
addr, addr+size);
}
addr += cnt;
bufptr += cnt;
size -= cnt;
}
}
static void
dump_hstates()
{
char *hstate;
int i, len, order;
long nr, free;
ulong vaddr;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
if (!kernel_symbol_exists("hstates")) {
error(INFO, "hstates[] array does not exist\n");
option_not_supported('h');
}
if (INVALID_MEMBER(hstate_name)) {
STRUCT_SIZE_INIT(hstate, "hstate");
MEMBER_OFFSET_INIT(hstate_order, "hstate", "order");
MEMBER_OFFSET_INIT(hstate_nr_huge_pages, "hstate", "nr_huge_pages");
MEMBER_OFFSET_INIT(hstate_free_huge_pages, "hstate", "free_huge_pages");
MEMBER_OFFSET_INIT(hstate_name, "hstate", "name");
if (INVALID_SIZE(hstate) ||
INVALID_MEMBER(hstate_order) ||
INVALID_MEMBER(hstate_name) ||
INVALID_MEMBER(hstate_nr_huge_pages) ||
INVALID_MEMBER(hstate_free_huge_pages)) {
error(INFO, "hstate structure or members have changed\n");
option_not_supported('h');
}
}
fprintf(fp, "%s",
mkstring(buf1, VADDR_PRLEN, CENTER, "HSTATE"));
fprintf(fp, " SIZE FREE TOTAL NAME\n");
len = get_array_length("hstates", NULL, 0);
hstate = GETBUF(SIZE(hstate));
for (i = 0; i < len; i++) {
vaddr = symbol_value("hstates") + (SIZE(hstate) * i);
if (!readmem(vaddr, KVADDR, hstate,
SIZE(hstate), "hstate", RETURN_ON_ERROR))
break;
order = INT(hstate + OFFSET(hstate_order));
if (!order)
continue;
fprintf(fp, "%lx ", vaddr);
pages_to_size(1 << order, buf1);
shift_string_left(first_space(buf1), 1);
fprintf(fp, "%s ", mkstring(buf2, 5, RJUST, buf1));
free = LONG(hstate + OFFSET(hstate_free_huge_pages));
sprintf(buf1, "%ld", free);
fprintf(fp, "%s ", mkstring(buf2, 6, RJUST, buf1));
nr = LONG(hstate + OFFSET(hstate_nr_huge_pages));
sprintf(buf1, "%ld", nr);
fprintf(fp, "%s ", mkstring(buf2, 6, RJUST, buf1));
fprintf(fp, "%s\n", hstate + OFFSET(hstate_name));
}
FREEBUF(hstate);
}
static void
page_flags_init(void)
{
if (!page_flags_init_from_pageflag_names())
page_flags_init_from_pageflags_enum();
PG_reserved_flag_init();
PG_slab_flag_init();
}
static int
page_flags_init_from_pageflag_names(void)
{
int i, len;
char *buffer, *nameptr;
char namebuf[BUFSIZE];
ulong mask;
void *name;
MEMBER_OFFSET_INIT(trace_print_flags_mask, "trace_print_flags", "mask");
MEMBER_OFFSET_INIT(trace_print_flags_name, "trace_print_flags", "name");
STRUCT_SIZE_INIT(trace_print_flags, "trace_print_flags");
if (INVALID_SIZE(trace_print_flags) ||
INVALID_MEMBER(trace_print_flags_mask) ||
INVALID_MEMBER(trace_print_flags_name) ||
!kernel_symbol_exists("pageflag_names") ||
!(len = get_array_length("pageflag_names", NULL, 0)))
return FALSE;
buffer = GETBUF(SIZE(trace_print_flags) * len);
if (!readmem(symbol_value("pageflag_names"), KVADDR, buffer,
SIZE(trace_print_flags) * len, "pageflag_names array",
RETURN_ON_ERROR)) {
FREEBUF(buffer);
return FALSE;
}
if (!(vt->pageflags_data = (struct pageflags_data *)
malloc(sizeof(struct pageflags_data) * len))) {
error(INFO, "cannot malloc pageflags_data cache\n");
FREEBUF(buffer);
return FALSE;
}
if (CRASHDEBUG(1))
fprintf(fp, "pageflags from pageflag_names: \n");
for (i = 0; i < len; i++) {
mask = ULONG(buffer + (SIZE(trace_print_flags)*i) +
OFFSET(trace_print_flags_mask));
name = VOID_PTR(buffer + (SIZE(trace_print_flags)*i) +
OFFSET(trace_print_flags_name));
if ((mask == -1UL) && !name) { /* Linux 3.5 and earlier */
len--;
break;
}
if (!read_string((ulong)name, namebuf, BUFSIZE-1)) {
error(INFO, "failed to read pageflag_names entry\n",
i, name, mask);
goto pageflags_fail;
}
if (!(nameptr = (char *)malloc(strlen(namebuf)+1))) {
error(INFO, "cannot malloc pageflag_names space\n");
goto pageflags_fail;
}
strcpy(nameptr, namebuf);
vt->pageflags_data[i].name = nameptr;
vt->pageflags_data[i].mask = mask;
if (CRASHDEBUG(1)) {
fprintf(fp, " %08lx %s\n",
vt->pageflags_data[i].mask,
vt->pageflags_data[i].name);
}
}
FREEBUF(buffer);
vt->nr_pageflags = len;
vt->flags |= PAGEFLAGS;
return TRUE;
pageflags_fail:
FREEBUF(buffer);
free(vt->pageflags_data);
vt->pageflags_data = NULL;
return FALSE;
}
static int
page_flags_init_from_pageflags_enum(void)
{
int c;
int p, len;
char *nameptr;
char buf[BUFSIZE];
char *arglist[MAXARGS];
if (!(vt->pageflags_data = (struct pageflags_data *)
malloc(sizeof(struct pageflags_data) * 32))) {
error(INFO, "cannot malloc pageflags_data cache\n");
return FALSE;
}
p = 0;
pc->flags2 |= ALLOW_FP;
open_tmpfile();
if (dump_enumerator_list("pageflags")) {
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (!strstr(buf, " = "))
continue;
c = parse_line(buf, arglist);
if (strstr(arglist[0], "__NR_PAGEFLAGS")) {
len = atoi(arglist[2]);
if (!len || (len > 32))
goto enum_fail;
vt->nr_pageflags = len;
break;
}
if (!(nameptr = (char *)malloc(strlen(arglist[0])))) {
error(INFO, "cannot malloc pageflags name space\n");
goto enum_fail;
}
strcpy(nameptr, arglist[0] + strlen("PG_"));
vt->pageflags_data[p].name = nameptr;
vt->pageflags_data[p].mask = 1 << atoi(arglist[2]);
p++;
}
} else
goto enum_fail;
close_tmpfile();
pc->flags2 &= ~ALLOW_FP;
if (CRASHDEBUG(1)) {
fprintf(fp, "pageflags from enum: \n");
for (p = 0; p < vt->nr_pageflags; p++)
fprintf(fp, " %08lx %s\n",
vt->pageflags_data[p].mask,
vt->pageflags_data[p].name);
}
vt->flags |= PAGEFLAGS;
return TRUE;
enum_fail:
close_tmpfile();
pc->flags2 &= ~ALLOW_FP;
for (c = 0; c < p; c++)
free(vt->pageflags_data[c].name);
free(vt->pageflags_data);
vt->pageflags_data = NULL;
vt->nr_pageflags = 0;
return FALSE;
}
static int
translate_page_flags(char *buffer, ulong flags)
{
char buf[BUFSIZE];
int i, others;
sprintf(buf, "%lx", flags);
if (flags) {
for (i = others = 0; i < vt->nr_pageflags; i++) {
if (flags & vt->pageflags_data[i].mask)
sprintf(&buf[strlen(buf)], "%s%s",
others++ ? "," : " ",
vt->pageflags_data[i].name);
}
}
strcat(buf, "\n");
strcpy(buffer, buf);
return(strlen(buf));
}
/*
* dump_page_hash_table() displays the entries in each page_hash_table.
*/
#define PGHASH_CACHED (1024)
static void
dump_page_hash_table(struct meminfo *hi)
{
int i;
int len, entry_len;
ulong page_hash_table, head;
struct list_data list_data, *ld;
struct gnu_request req;
long total_cached;
long page_cache_size;
ulong this_addr, searchpage;
int errflag, found, cnt, populated, verbose;
uint ival;
ulong buffer_pages;
char buf[BUFSIZE];
char hash_table[BUFSIZE];
char *pcache, *pghash_cache;
if (!vt->page_hash_table) {
if (hi->flags & VERBOSE)
option_not_supported('C');
if (symbol_exists("nr_pagecache")) {
buffer_pages = nr_blockdev_pages();
get_symbol_data("nr_pagecache", sizeof(int), &ival);
page_cache_size = (ulong)ival;
page_cache_size -= buffer_pages;
fprintf(fp, "page cache size: %ld\n", page_cache_size);
if (hi->flags & ADDRESS_SPECIFIED)
option_not_supported('c');
} else
option_not_supported('c');
return;
}
ld = &list_data;
if (hi->spec_addr && (hi->flags & ADDRESS_SPECIFIED)) {
verbose = TRUE;
searchpage = hi->spec_addr;
} else if (hi->flags & VERBOSE) {
verbose = TRUE;
searchpage = 0;
} else {
verbose = FALSE;
searchpage = 0;
}
if (vt->page_hash_table_len == 0)
error(FATAL, "cannot determine size of page_hash_table\n");
page_hash_table = vt->page_hash_table;
len = vt->page_hash_table_len;
entry_len = VALID_STRUCT(page_cache_bucket) ?
SIZE(page_cache_bucket) : sizeof(void *);
populated = 0;
if (CRASHDEBUG(1))
fprintf(fp, "page_hash_table length: %d\n", len);
get_symbol_type("page_cache_size", NULL, &req);
if (req.length == sizeof(int)) {
get_symbol_data("page_cache_size", sizeof(int), &ival);
page_cache_size = (long)ival;
} else
get_symbol_data("page_cache_size", sizeof(long),
&page_cache_size);
pghash_cache = GETBUF(sizeof(void *) * PGHASH_CACHED);
if (searchpage)
open_tmpfile();
hq_open();
for (i = total_cached = 0; i < len; i++,
page_hash_table += entry_len) {
if ((i % PGHASH_CACHED) == 0) {
readmem(page_hash_table, KVADDR, pghash_cache,
entry_len * PGHASH_CACHED,
"page hash cache", FAULT_ON_ERROR);
}
pcache = pghash_cache + ((i%PGHASH_CACHED) * entry_len);
if (VALID_STRUCT(page_cache_bucket))
pcache += OFFSET(page_cache_bucket_chain);
head = ULONG(pcache);
if (!head)
continue;
if (verbose)
fprintf(fp, "page_hash_table[%d]\n", i);
if (CRASHDEBUG(1))
populated++;
BZERO(ld, sizeof(struct list_data));
ld->flags = verbose;
ld->start = head;
ld->searchfor = searchpage;
ld->member_offset = OFFSET(page_next_hash);
cnt = do_list(ld);
total_cached += cnt;
if (ld->searchfor)
break;
if (received_SIGINT())
restart(0);
}
hq_close();
fprintf(fp, "%spage_cache_size: %ld ", verbose ? "\n" : "",
page_cache_size);
if (page_cache_size != total_cached)
fprintf(fp, "(found %ld)\n", total_cached);
else
fprintf(fp, "(verified)\n");
if (CRASHDEBUG(1))
fprintf(fp, "heads containing page(s): %d\n", populated);
if (searchpage) {
rewind(pc->tmpfile);
found = FALSE;
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (CRASHDEBUG(1) && STRNEQ(buf, "<readmem:"))
continue;
if (strstr(buf, "page_hash_table")) {
strcpy(hash_table, buf);
continue;
}
if (strstr(buf, "page_cache_size"))
continue;
if (CRASHDEBUG(1) &&
!hexadecimal(strip_linefeeds(buf), 0))
continue;
this_addr = htol(strip_linefeeds(buf),
RETURN_ON_ERROR, &errflag);
if (this_addr == searchpage) {
found = TRUE;
break;
}
}
close_tmpfile();
if (found) {
fprintf(fp, "%s", hash_table);
fprintf(fp, "%lx\n", searchpage);
hi->retval = TRUE;
}
}
}
/*
* dump_free_pages() displays basic data about pages currently resident
* in the free_area[] memory lists. If the flags contains the VERBOSE
* bit, each page slab base address is dumped. If an address is specified
* only the free_area[] data containing that page is displayed, along with
* the page slab base address. Specified addresses can either be physical
* address or page structure pointers.
*/
char *free_area_hdr1 = \
"AREA SIZE FREE_AREA_STRUCT BLOCKS PAGES\n";
char *free_area_hdr2 = \
"AREA SIZE FREE_AREA_STRUCT\n";
static void
dump_free_pages(struct meminfo *fi)
{
int i;
int order;
ulong free_area;
char *free_area_buf;
ulong *pp;
int nr_mem_lists;
struct list_data list_data, *ld;
long cnt, total_free, chunk_size;
int nr_free_pages;
char buf[BUFSIZE];
char last_free[BUFSIZE];
char last_free_hdr[BUFSIZE];
int verbose, errflag, found;
physaddr_t searchphys;
ulong this_addr;
physaddr_t this_phys;
int do_search;
ulong kfp, offset;
int flen, dimension;
if (vt->flags & (NODES|ZONES))
error(FATAL, "dump_free_pages called with (NODES|ZONES)\n");
nr_mem_lists = ARRAY_LENGTH(free_area);
dimension = ARRAY_LENGTH(free_area_DIMENSION);
if (nr_mem_lists == 0)
error(FATAL, "cannot determine size/dimensions of free_area\n");
if (dimension)
error(FATAL,
"dump_free_pages called with multidimensional free area\n");
ld = &list_data;
total_free = 0;
searchphys = 0;
chunk_size = 0;
do_search = FALSE;
get_symbol_data("nr_free_pages", sizeof(int), &nr_free_pages);
switch (fi->flags)
{
case GET_FREE_HIGHMEM_PAGES:
error(FATAL, "GET_FREE_HIGHMEM_PAGES invalid in this kernel\n");
case GET_FREE_PAGES:
fi->retval = (ulong)nr_free_pages;
return;
case ADDRESS_SPECIFIED:
switch (fi->memtype)
{
case KVADDR:
if (!page_to_phys(fi->spec_addr, &searchphys)) {
if (!kvtop(NULL, fi->spec_addr, &searchphys, 0))
return;
}
break;
case PHYSADDR:
searchphys = fi->spec_addr;
break;
default:
error(FATAL, "dump_free_pages: no memtype specified\n");
}
do_search = TRUE;
break;
}
verbose = (do_search || (fi->flags & VERBOSE)) ? TRUE : FALSE;
free_area_buf = GETBUF(nr_mem_lists * SIZE(free_area_struct));
kfp = free_area = symbol_value("free_area");
flen = MAX(VADDR_PRLEN, strlen("FREE_AREA_STRUCT"));
readmem(free_area, KVADDR, free_area_buf,
SIZE(free_area_struct) * nr_mem_lists,
"free_area_struct", FAULT_ON_ERROR);
if (do_search)
open_tmpfile();
if (!verbose)
fprintf(fp, "%s", free_area_hdr1);
hq_open();
for (i = 0; i < nr_mem_lists; i++) {
pp = (ulong *)(free_area_buf + (SIZE(free_area_struct)*i));
chunk_size = power(2, i);
if (verbose)
fprintf(fp, "%s", free_area_hdr2);
fprintf(fp, "%3d ", i);
sprintf(buf, "%ldk", (chunk_size * PAGESIZE())/1024);
fprintf(fp, "%5s ", buf);
fprintf(fp, "%s %s",
mkstring(buf, flen, CENTER|LONG_HEX, MKSTR(kfp)),
verbose ? "\n" : "");
if (is_page_ptr(*pp, NULL)) {
BZERO(ld, sizeof(struct list_data));
ld->flags = verbose;
ld->start = *pp;
ld->end = free_area;
cnt = do_list(ld);
total_free += (cnt * chunk_size);
} else
cnt = 0;
if (!verbose)
fprintf(fp, "%6ld %6ld\n", cnt, cnt * chunk_size );
free_area += SIZE(free_area_struct);
kfp += SIZE(free_area_struct);
}
hq_close();
fprintf(fp, "\nnr_free_pages: %d ", nr_free_pages);
if (total_free != nr_free_pages)
fprintf(fp, "(found %ld)\n", total_free);
else
fprintf(fp, "(verified)\n");
if (!do_search)
return;
found = FALSE;
rewind(pc->tmpfile);
order = offset = this_addr = 0;
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (CRASHDEBUG(1) && STRNEQ(buf, "<readmem"))
continue;
if (strstr(buf, "nr_free_pages") ||
STREQ(buf, "\n"))
continue;
if (strstr(buf, "AREA")) {
strcpy(last_free_hdr, buf);
continue;
}
if (strstr(buf, "k")) {
strcpy(last_free, buf);
chunk_size = power(2, order) * PAGESIZE();
order++;
continue;
}
if (CRASHDEBUG(1) && !hexadecimal(strip_linefeeds(buf), 0))
continue;
errflag = 0;
this_addr = htol(strip_linefeeds(buf),
RETURN_ON_ERROR, &errflag);
if (errflag)
continue;
if (!page_to_phys(this_addr, &this_phys))
continue;
if ((searchphys >= this_phys) &&
(searchphys < (this_phys+chunk_size))) {
if (searchphys > this_phys)
offset = (searchphys - this_phys)/PAGESIZE();
found = TRUE;
break;
}
}
close_tmpfile();
if (found) {
order--;
fprintf(fp, "%s", last_free_hdr);
fprintf(fp, "%s", last_free);
fprintf(fp, "%lx ", this_addr);
if (order) {
switch (fi->memtype)
{
case KVADDR:
fprintf(fp, "(%lx is ", (ulong)fi->spec_addr);
break;
case PHYSADDR:
fprintf(fp, "(%llx is %s", fi->spec_addr,
PAGEOFFSET(fi->spec_addr) ? "in " : "");
break;
}
fprintf(fp, "%s of %ld pages) ",
ordinal(offset+1, buf), power(2, order));
}
fi->retval = TRUE;
fprintf(fp, "\n");
}
}
/*
* Dump free pages on kernels with a multi-dimensional free_area array.
*/
char *free_area_hdr5 = \
" AREA SIZE FREE_AREA_STRUCT BLOCKS PAGES\n";
char *free_area_hdr6 = \
" AREA SIZE FREE_AREA_STRUCT\n";
static void
dump_multidimensional_free_pages(struct meminfo *fi)
{
int i, j;
struct list_data list_data, *ld;
long cnt, total_free;
ulong kfp, free_area;
physaddr_t searchphys;
int flen, errflag, verbose, nr_free_pages;
int nr_mem_lists, dimension, order, do_search;
ulong sum, found, offset;
char *free_area_buf, *p;
ulong *pp;
long chunk_size;
ulong this_addr;
physaddr_t this_phys;
char buf[BUFSIZE];
char last_area[BUFSIZE];
char last_area_hdr[BUFSIZE];
if (vt->flags & (NODES|ZONES))
error(FATAL,
"dump_multidimensional_free_pages called with (NODES|ZONES)\n");
ld = &list_data;
if (SIZE(free_area_struct) % sizeof(ulong))
error(FATAL, "free_area_struct not long-word aligned?\n");
total_free = 0;
searchphys = 0;
chunk_size = 0;
do_search = FALSE;
get_symbol_data("nr_free_pages", sizeof(int), &nr_free_pages);
switch (fi->flags)
{
case GET_FREE_HIGHMEM_PAGES:
error(FATAL, "GET_FREE_HIGHMEM_PAGES invalid in this kernel\n");
case GET_FREE_PAGES:
fi->retval = (ulong)nr_free_pages;
return;
case ADDRESS_SPECIFIED:
switch (fi->memtype)
{
case KVADDR:
if (!page_to_phys(fi->spec_addr, &searchphys)) {
if (!kvtop(NULL, fi->spec_addr, &searchphys, 0))
return;
}
break;
case PHYSADDR:
searchphys = fi->spec_addr;
break;
default:
error(FATAL,
"dump_multidimensional_free_pages: no memtype specified\n");
}
do_search = TRUE;
break;
}
verbose = (do_search || (fi->flags & VERBOSE)) ? TRUE : FALSE;
flen = MAX(VADDR_PRLEN, strlen("FREE_AREA_STRUCT"));
nr_mem_lists = ARRAY_LENGTH(free_area);
dimension = ARRAY_LENGTH(free_area_DIMENSION);
if (!nr_mem_lists || !dimension)
error(FATAL, "cannot determine free_area dimensions\n");
free_area_buf =
GETBUF((nr_mem_lists * SIZE(free_area_struct)) * dimension);
kfp = free_area = symbol_value("free_area");
readmem(free_area, KVADDR, free_area_buf,
(SIZE(free_area_struct) * nr_mem_lists) * dimension,
"free_area arrays", FAULT_ON_ERROR);
if (do_search)
open_tmpfile();
hq_open();
for (i = sum = found = 0; i < dimension; i++) {
if (!verbose)
fprintf(fp, "%s", free_area_hdr5);
pp = (ulong *)(free_area_buf +
((SIZE(free_area_struct)*nr_mem_lists)*i));
for (j = 0; j < nr_mem_lists; j++) {
if (verbose)
fprintf(fp, "%s", free_area_hdr6);
sprintf(buf, "[%d][%d]", i, j);
fprintf(fp, "%7s ", buf);
chunk_size = power(2, j);
sprintf(buf, "%ldk", (chunk_size * PAGESIZE())/1024);
fprintf(fp, "%5s ", buf);
fprintf(fp, "%s %s",
mkstring(buf, flen, CENTER|LONG_HEX, MKSTR(kfp)),
verbose ? "\n" : "");
if (is_page_ptr(*pp, NULL)) {
BZERO(ld, sizeof(struct list_data));
ld->flags = verbose;
ld->start = *pp;
ld->end = free_area;
cnt = do_list(ld);
total_free += (cnt * chunk_size);
} else
cnt = 0;
if (!verbose)
fprintf(fp,
"%6ld %6ld\n", cnt, cnt * chunk_size );
pp += (SIZE(free_area_struct)/sizeof(ulong));
free_area += SIZE(free_area_struct);
kfp += SIZE(free_area_struct);
}
fprintf(fp, "\n");
}
hq_close();
fprintf(fp, "nr_free_pages: %d ", nr_free_pages);
if (total_free != nr_free_pages)
fprintf(fp, "(found %ld)\n", total_free);
else
fprintf(fp, "(verified)\n");
if (!do_search)
return;
found = FALSE;
rewind(pc->tmpfile);
order = offset = this_addr = 0;
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (CRASHDEBUG(1) && STRNEQ(buf, "<readmem:"))
continue;
if (STRNEQ(buf, "nr_free_pages:"))
continue;
if (strstr(buf, "AREA")) {
strcpy(last_area_hdr, buf);
p = fgets(buf, BUFSIZE, pc->tmpfile);
strcpy(last_area, strip_linefeeds(buf));
p = strstr(buf, "k");
*p = NULLCHAR;
while (*p != ' ')
p--;
chunk_size = atol(p+1) * 1024;
if (chunk_size == PAGESIZE())
order = 0;
else
order++;
continue;
}
errflag = 0;
this_addr = htol(strip_linefeeds(buf),
RETURN_ON_ERROR, &errflag);
if (errflag)
continue;
if (!page_to_phys(this_addr, &this_phys))
continue;
if ((searchphys >= this_phys) &&
(searchphys < (this_phys+chunk_size))) {
if (searchphys > this_phys)
offset = (searchphys - this_phys)/PAGESIZE();
found = TRUE;
break;
}
}
close_tmpfile();
if (found) {
fprintf(fp, "%s", last_area_hdr);
fprintf(fp, "%s\n", last_area);
fprintf(fp, "%lx ", this_addr);
if (order) {
switch (fi->memtype)
{
case KVADDR:
fprintf(fp, "(%lx is ", (ulong)fi->spec_addr);
break;
case PHYSADDR:
fprintf(fp, "(%llx is %s", fi->spec_addr,
PAGEOFFSET(fi->spec_addr) ? "in " : "");
break;
}
fprintf(fp, "%s of %ld pages) ",
ordinal(offset+1, buf), power(2, order));
}
fi->retval = TRUE;
fprintf(fp, "\n");
}
}
/*
* Dump free pages in newer kernels that have zones. This is a work in
* progress, because although the framework for memory nodes has been laid
* down, complete support has not been put in place.
*/
static char *zone_hdr = "ZONE NAME SIZE FREE";
static void
dump_free_pages_zones_v1(struct meminfo *fi)
{
int i, n;
ulong node_zones;
ulong size;
long zone_size_offset;
long chunk_size;
int order, errflag, do_search;
ulong offset, verbose, value, sum, found;
ulong this_addr;
physaddr_t this_phys, searchphys;
ulong zone_mem_map;
ulong zone_start_paddr;
ulong zone_start_mapnr;
struct node_table *nt;
char buf[BUFSIZE], *p;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char last_node[BUFSIZE];
char last_zone[BUFSIZE];
char last_area[BUFSIZE];
char last_area_hdr[BUFSIZE];
if (!(vt->flags & (NODES|ZONES)))
error(FATAL,
"dump_free_pages_zones_v1 called without (NODES|ZONES)\n");
if (fi->flags & ADDRESS_SPECIFIED) {
switch (fi->memtype)
{
case KVADDR:
if (!page_to_phys(fi->spec_addr, &searchphys)) {
if (!kvtop(NULL, fi->spec_addr, &searchphys, 0))
return;
}
break;
case PHYSADDR:
searchphys = fi->spec_addr;
break;
default:
error(FATAL,
"dump_free_pages_zones_v1: no memtype specified\n");
}
do_search = TRUE;
} else {
searchphys = 0;
do_search = FALSE;
}
verbose = (do_search || (fi->flags & VERBOSE)) ? TRUE : FALSE;
chunk_size = 0;
zone_size_offset = 0;
if (VALID_MEMBER(zone_struct_size))
zone_size_offset = OFFSET(zone_struct_size);
else if (VALID_MEMBER(zone_struct_memsize))
zone_size_offset = OFFSET(zone_struct_memsize);
else
error(FATAL,
"zone_struct has neither size nor memsize field\n");
if (do_search)
open_tmpfile();
hq_open();
for (n = sum = found = 0; n < vt->numnodes; n++) {
nt = &vt->node_table[n];
node_zones = nt->pgdat + OFFSET(pglist_data_node_zones);
for (i = 0; i < vt->nr_zones; i++) {
if (fi->flags == GET_FREE_PAGES) {
readmem(node_zones+
OFFSET(zone_struct_free_pages),
KVADDR, &value, sizeof(ulong),
"node_zones free_pages",
FAULT_ON_ERROR);
sum += value;
node_zones += SIZE(zone_struct);
continue;
}
if (fi->flags == GET_FREE_HIGHMEM_PAGES) {
if (i == vt->ZONE_HIGHMEM) {
readmem(node_zones+
OFFSET(zone_struct_free_pages),
KVADDR, &value, sizeof(ulong),
"node_zones free_pages",
FAULT_ON_ERROR);
sum += value;
}
node_zones += SIZE(zone_struct);
continue;
}
if (fi->flags == GET_ZONE_SIZES) {
readmem(node_zones+zone_size_offset,
KVADDR, &size, sizeof(ulong),
"node_zones {mem}size", FAULT_ON_ERROR);
sum += size;
node_zones += SIZE(zone_struct);
continue;
}
if ((i == 0) && (vt->flags & NODES)) {
if (n) {
fprintf(fp, "\n");
pad_line(fp,
VADDR_PRLEN > 8 ? 74 : 66, '-');
fprintf(fp, "\n");
}
fprintf(fp, "%sNODE\n %2d\n",
n ? "\n" : "", nt->node_id);
}
fprintf(fp, "%s%s %s START_PADDR START_MAPNR\n",
i > 0 ? "\n" : "",
zone_hdr,
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST,
"MEM_MAP"));
fprintf(fp, "%3d ", i);
readmem(node_zones+OFFSET(zone_struct_name), KVADDR,
&value, sizeof(void *),
"node_zones name", FAULT_ON_ERROR);
if (read_string(value, buf, BUFSIZE-1))
fprintf(fp, "%-9s ", buf);
else
fprintf(fp, "(unknown) ");
readmem(node_zones+zone_size_offset, KVADDR,
&size, sizeof(ulong),
"node_zones {mem}size", FAULT_ON_ERROR);
fprintf(fp, "%6ld ", size);
readmem(node_zones+OFFSET(zone_struct_free_pages),
KVADDR, &value, sizeof(ulong),
"node_zones free_pages", FAULT_ON_ERROR);
fprintf(fp, "%6ld ", value);
readmem(node_zones+OFFSET(zone_struct_zone_start_paddr),
KVADDR, &zone_start_paddr, sizeof(ulong),
"node_zones zone_start_paddr", FAULT_ON_ERROR);
readmem(node_zones+OFFSET(zone_struct_zone_start_mapnr),
KVADDR, &zone_start_mapnr, sizeof(ulong),
"node_zones zone_start_mapnr", FAULT_ON_ERROR);
readmem(node_zones+OFFSET(zone_struct_zone_mem_map),
KVADDR, &zone_mem_map, sizeof(ulong),
"node_zones zone_mem_map", FAULT_ON_ERROR);
fprintf(fp, "%s %s %s\n",
mkstring(buf1, VADDR_PRLEN,
CENTER|LONG_HEX,MKSTR(zone_mem_map)),
mkstring(buf2, strlen("START_PADDR"),
CENTER|LONG_HEX|RJUST,
MKSTR(zone_start_paddr)),
mkstring(buf3, strlen("START_MAPNR"),
CENTER|LONG_DEC|RJUST,
MKSTR(zone_start_mapnr)));
sum += value;
if (value)
found += dump_zone_free_area(node_zones+
OFFSET(zone_struct_free_area),
vt->nr_free_areas, verbose, NULL);
node_zones += SIZE(zone_struct);
}
}
hq_close();
if (fi->flags & (GET_FREE_PAGES|GET_ZONE_SIZES|GET_FREE_HIGHMEM_PAGES)) {
fi->retval = sum;
return;
}
fprintf(fp, "\nnr_free_pages: %ld ", sum);
if (sum == found)
fprintf(fp, "(verified)\n");
else
fprintf(fp, "(found %ld)\n", found);
if (!do_search)
return;
found = FALSE;
rewind(pc->tmpfile);
order = offset = this_addr = 0;
last_node[0] = NULLCHAR;
last_zone[0] = NULLCHAR;
last_area[0] = NULLCHAR;
last_area_hdr[0] = NULLCHAR;
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (CRASHDEBUG(1) && STRNEQ(buf, "<readmem"))
continue;
if (STRNEQ(buf, "nr_free_pages:"))
continue;
if (STRNEQ(buf, "NODE")) {
p = fgets(buf, BUFSIZE, pc->tmpfile);
strcpy(last_node, strip_linefeeds(buf));
continue;
}
if (STRNEQ(buf, "ZONE")) {
p = fgets(buf, BUFSIZE, pc->tmpfile);
strcpy(last_zone, strip_linefeeds(buf));
continue;
}
if (STRNEQ(buf, "AREA")) {
strcpy(last_area_hdr, buf);
p = fgets(buf, BUFSIZE, pc->tmpfile);
strcpy(last_area, strip_linefeeds(buf));
p = strstr(buf, "k");
*p = NULLCHAR;
while (*p != ' ')
p--;
chunk_size = atol(p+1) * 1024;
if (chunk_size == PAGESIZE())
order = 0;
else
order++;
continue;
}
if (CRASHDEBUG(0) &&
!hexadecimal(strip_linefeeds(buf), 0))
continue;
errflag = 0;
this_addr = htol(strip_linefeeds(buf),
RETURN_ON_ERROR, &errflag);
if (errflag)
continue;
if (!page_to_phys(this_addr, &this_phys))
continue;
if ((searchphys >= this_phys) &&
(searchphys < (this_phys+chunk_size))) {
if (searchphys > this_phys)
offset = (searchphys - this_phys)/PAGESIZE();
found = TRUE;
break;
}
}
close_tmpfile();
if (found) {
if (strlen(last_node))
fprintf(fp, "NODE\n%s\n", last_node);
fprintf(fp, "%s %s START_PADDR START_MAPNR\n",
zone_hdr,
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "MEM_MAP"));
fprintf(fp, "%s\n", last_zone);
fprintf(fp, "%s", last_area_hdr);
fprintf(fp, "%s\n", last_area);
fprintf(fp, "%lx ", this_addr);
if (order) {
switch (fi->memtype)
{
case KVADDR:
fprintf(fp, "(%lx is ", (ulong)fi->spec_addr);
break;
case PHYSADDR:
fprintf(fp, "(%llx is %s", fi->spec_addr,
PAGEOFFSET(fi->spec_addr) ? "in " : "");
break;
}
fprintf(fp, "%s of %ld pages) ",
ordinal(offset+1, buf), power(2, order));
}
fi->retval = TRUE;
fprintf(fp, "\n");
}
}
/*
* Callback function for free-list search for a specific page.
*/
struct free_page_callback_data {
physaddr_t searchphys;
long block_size;
ulong page;
int found;
};
static int
free_page_callback(void *page, void *arg)
{
struct free_page_callback_data *cbd = arg;
physaddr_t this_phys;
if (!page_to_phys((ulong)page, &this_phys))
return FALSE;
if ((cbd->searchphys >= this_phys) &&
(cbd->searchphys < (this_phys + cbd->block_size))) {
cbd->page = (ulong)page;
cbd->found = TRUE;
return TRUE;
}
return FALSE;
}
/*
* Same as dump_free_pages_zones_v1(), but updated for numerous 2.6 zone
* and free_area related data structure changes.
*/
static void
dump_free_pages_zones_v2(struct meminfo *fi)
{
int i, n;
ulong node_zones;
ulong size;
long zone_size_offset;
long chunk_size;
int order, errflag, do_search;
ulong offset, verbose, value, sum, found;
ulong this_addr;
physaddr_t phys, this_phys, searchphys, end_paddr;
struct free_page_callback_data callback_data;
ulong pp;
ulong zone_mem_map;
ulong zone_start_paddr;
ulong zone_start_pfn;
ulong zone_start_mapnr;
struct node_table *nt;
char buf[BUFSIZE], *p;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char last_node[BUFSIZE];
char last_zone[BUFSIZE];
char last_area[BUFSIZE];
char last_area_hdr[BUFSIZE];
if (!(vt->flags & (NODES|ZONES)))
error(FATAL,
"dump_free_pages_zones_v2 called without (NODES|ZONES)\n");
if (fi->flags & ADDRESS_SPECIFIED) {
switch (fi->memtype)
{
case KVADDR:
if (!page_to_phys(fi->spec_addr, &searchphys)) {
if (!kvtop(NULL, fi->spec_addr, &searchphys, 0))
return;
}
break;
case PHYSADDR:
searchphys = fi->spec_addr;
break;
default:
error(FATAL,
"dump_free_pages_zones_v2: no memtype specified\n");
}
do_search = TRUE;
callback_data.searchphys = searchphys;
callback_data.found = FALSE;
} else {
searchphys = 0;
do_search = FALSE;
}
verbose = (do_search || (fi->flags & VERBOSE)) ? TRUE : FALSE;
zone_size_offset = 0;
chunk_size = 0;
this_addr = 0;
if (VALID_MEMBER(zone_spanned_pages))
zone_size_offset = OFFSET(zone_spanned_pages);
else
error(FATAL, "zone struct has no spanned_pages field\n");
if (do_search)
open_tmpfile();
hq_open();
for (n = sum = found = 0; n < vt->numnodes; n++) {
nt = &vt->node_table[n];
node_zones = nt->pgdat + OFFSET(pglist_data_node_zones);
for (i = 0; i < vt->nr_zones; i++) {
if (fi->flags == GET_FREE_PAGES) {
readmem(node_zones+
OFFSET(zone_free_pages),
KVADDR, &value, sizeof(ulong),
"node_zones free_pages",
FAULT_ON_ERROR);
sum += value;
node_zones += SIZE(zone);
continue;
}
if (fi->flags == GET_FREE_HIGHMEM_PAGES) {
readmem(node_zones+OFFSET(zone_name), KVADDR,
&value, sizeof(void *),
"node_zones name", FAULT_ON_ERROR);
if (read_string(value, buf, BUFSIZE-1) &&
STREQ(buf, "HighMem"))
vt->ZONE_HIGHMEM = i;
if (i == vt->ZONE_HIGHMEM) {
readmem(node_zones+
OFFSET(zone_free_pages),
KVADDR, &value, sizeof(ulong),
"node_zones free_pages",
FAULT_ON_ERROR);
sum += value;
}
node_zones += SIZE(zone);
continue;
}
if (fi->flags == GET_ZONE_SIZES) {
readmem(node_zones+zone_size_offset,
KVADDR, &size, sizeof(ulong),
"node_zones size", FAULT_ON_ERROR);
sum += size;
node_zones += SIZE(zone);
continue;
}
if ((i == 0) && ((vt->flags & NODES) || (vt->numnodes > 1))) {
if (n) {
fprintf(fp, "\n");
pad_line(fp,
VADDR_PRLEN > 8 ? 74 : 66, '-');
fprintf(fp, "\n");
}
fprintf(fp, "%sNODE\n %2d\n",
n ? "\n" : "", nt->node_id);
}
fprintf(fp, "%s%s %s START_PADDR START_MAPNR\n",
i > 0 ? "\n" : "",
zone_hdr,
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST,
"MEM_MAP"));
fprintf(fp, "%3d ", i);
readmem(node_zones+OFFSET(zone_name), KVADDR,
&value, sizeof(void *),
"node_zones name", FAULT_ON_ERROR);
if (read_string(value, buf, BUFSIZE-1))
fprintf(fp, "%-9s ", buf);
else
fprintf(fp, "(unknown) ");
readmem(node_zones+zone_size_offset, KVADDR,
&size, sizeof(ulong),
"node_zones size", FAULT_ON_ERROR);
fprintf(fp, "%6ld ", size);
readmem(node_zones+OFFSET(zone_free_pages),
KVADDR, &value, sizeof(ulong),
"node_zones free_pages", FAULT_ON_ERROR);
fprintf(fp, "%6ld ", value);
if (VALID_MEMBER(zone_zone_mem_map)) {
readmem(node_zones+OFFSET(zone_zone_mem_map),
KVADDR, &zone_mem_map, sizeof(ulong),
"node_zones zone_mem_map", FAULT_ON_ERROR);
}
readmem(node_zones+ OFFSET(zone_zone_start_pfn),
KVADDR, &zone_start_pfn, sizeof(ulong),
"node_zones zone_start_pfn", FAULT_ON_ERROR);
zone_start_paddr = PTOB(zone_start_pfn);
if (!VALID_MEMBER(zone_zone_mem_map)) {
if (IS_SPARSEMEM() || IS_DISCONTIGMEM()) {
zone_mem_map = 0;
if (size) {
phys = PTOB(zone_start_pfn);
if (phys_to_page(phys, &pp))
zone_mem_map = pp;
}
} else if (vt->flags & FLATMEM) {
zone_mem_map = 0;
if (size)
zone_mem_map = nt->mem_map +
(zone_start_pfn * SIZE(page));
} else
error(FATAL, "\ncannot determine zone mem_map: TBD\n");
}
if (zone_mem_map)
zone_start_mapnr =
(zone_mem_map - nt->mem_map) /
SIZE(page);
else
zone_start_mapnr = 0;
fprintf(fp, "%s %s %s\n",
mkstring(buf1, VADDR_PRLEN,
CENTER|LONG_HEX,MKSTR(zone_mem_map)),
mkstring(buf2, strlen("START_PADDR"),
CENTER|LONG_HEX|RJUST,
MKSTR(zone_start_paddr)),
mkstring(buf3, strlen("START_MAPNR"),
CENTER|LONG_DEC|RJUST,
MKSTR(zone_start_mapnr)));
sum += value;
if (value) {
if (do_search) {
end_paddr = nt->start_paddr +
((physaddr_t)nt->size *
(physaddr_t)PAGESIZE());
if ((searchphys >= nt->start_paddr) &&
(searchphys < end_paddr))
found += dump_zone_free_area(node_zones+
OFFSET(zone_free_area),
vt->nr_free_areas, verbose,
&callback_data);
if (callback_data.found)
goto done_search;
} else
found += dump_zone_free_area(node_zones+
OFFSET(zone_free_area),
vt->nr_free_areas, verbose, NULL);
}
node_zones += SIZE(zone);
}
}
done_search:
hq_close();
if (fi->flags & (GET_FREE_PAGES|GET_ZONE_SIZES|GET_FREE_HIGHMEM_PAGES)) {
fi->retval = sum;
return;
}
fprintf(fp, "\nnr_free_pages: %ld ", sum);
if (sum == found)
fprintf(fp, "(verified)\n");
else
fprintf(fp, "(found %ld)\n", found);
if (!do_search)
return;
found = FALSE;
rewind(pc->tmpfile);
order = offset = 0;
last_node[0] = NULLCHAR;
last_zone[0] = NULLCHAR;
last_area[0] = NULLCHAR;
last_area_hdr[0] = NULLCHAR;
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (CRASHDEBUG(1) && STRNEQ(buf, "<readmem"))
continue;
if (STRNEQ(buf, "nr_free_pages:"))
continue;
if (STRNEQ(buf, "NODE")) {
p = fgets(buf, BUFSIZE, pc->tmpfile);
strcpy(last_node, strip_linefeeds(buf));
continue;
}
if (STRNEQ(buf, "ZONE")) {
p = fgets(buf, BUFSIZE, pc->tmpfile);
strcpy(last_zone, strip_linefeeds(buf));
continue;
}
if (STRNEQ(buf, "AREA")) {
strcpy(last_area_hdr, buf);
p = fgets(buf, BUFSIZE, pc->tmpfile);
strcpy(last_area, strip_linefeeds(buf));
p = strstr(buf, "k");
*p = NULLCHAR;
while (*p != ' ')
p--;
chunk_size = atol(p+1) * 1024;
if (chunk_size == PAGESIZE())
order = 0;
else
order++;
continue;
}
if (CRASHDEBUG(0) &&
!hexadecimal(strip_linefeeds(buf), 0))
continue;
errflag = 0;
this_addr = htol(strip_linefeeds(buf),
RETURN_ON_ERROR, &errflag);
if (errflag)
continue;
if (!page_to_phys(this_addr, &this_phys))
continue;
if ((searchphys >= this_phys) &&
(searchphys < (this_phys+chunk_size))) {
if (searchphys > this_phys)
offset = (searchphys - this_phys)/PAGESIZE();
found = TRUE;
break;
}
}
close_tmpfile();
if (found) {
if (strlen(last_node))
fprintf(fp, "NODE\n%s\n", last_node);
fprintf(fp, "%s %s START_PADDR START_MAPNR\n",
zone_hdr,
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "MEM_MAP"));
fprintf(fp, "%s\n", last_zone);
fprintf(fp, "%s", last_area_hdr);
fprintf(fp, "%s\n", last_area);
fprintf(fp, "%lx ", this_addr);
if (order) {
switch (fi->memtype)
{
case KVADDR:
fprintf(fp, "(%lx is ", (ulong)fi->spec_addr);
break;
case PHYSADDR:
fprintf(fp, "(%llx is %s", fi->spec_addr,
PAGEOFFSET(fi->spec_addr) ? "in " : "");
break;
}
fprintf(fp, "%s of %ld pages)",
ordinal(offset+1, buf), chunk_size/PAGESIZE());
}
fi->retval = TRUE;
fprintf(fp, "\n");
}
}
static char *
page_usage_hdr = "ZONE NAME FREE ACTIVE INACTIVE_DIRTY INACTIVE_CLEAN MIN/LOW/HIGH";
/*
* Display info about the non-free pages in each zone.
*/
static int
dump_zone_page_usage(void)
{
int i, n;
ulong value, node_zones;
struct node_table *nt;
ulong inactive_dirty_pages, inactive_clean_pages, active_pages;
ulong free_pages, pages_min, pages_low, pages_high;
char namebuf[BUFSIZE];
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
if (!VALID_MEMBER(zone_struct_inactive_dirty_pages) ||
!VALID_MEMBER(zone_struct_inactive_clean_pages) ||
!VALID_MEMBER(zone_struct_active_pages) ||
!VALID_MEMBER(zone_struct_pages_min) ||
!VALID_MEMBER(zone_struct_pages_low) ||
!VALID_MEMBER(zone_struct_pages_high))
return FALSE;
fprintf(fp, "\n");
for (n = 0; n < vt->numnodes; n++) {
nt = &vt->node_table[n];
node_zones = nt->pgdat + OFFSET(pglist_data_node_zones);
if ((vt->numnodes > 1) && (vt->flags & NODES)) {
fprintf(fp, "%sNODE\n %2d\n",
n ? "\n" : "", nt->node_id);
}
fprintf(fp, "%s\n", page_usage_hdr);
for (i = 0; i < vt->nr_zones; i++) {
readmem(node_zones+OFFSET(zone_struct_free_pages),
KVADDR, &free_pages, sizeof(ulong),
"node_zones free_pages", FAULT_ON_ERROR);
readmem(node_zones+
OFFSET(zone_struct_inactive_dirty_pages),
KVADDR, &inactive_dirty_pages, sizeof(ulong),
"node_zones inactive_dirty_pages",
FAULT_ON_ERROR);
readmem(node_zones+
OFFSET(zone_struct_inactive_clean_pages),
KVADDR, &inactive_clean_pages, sizeof(ulong),
"node_zones inactive_clean_pages",
FAULT_ON_ERROR);
readmem(node_zones+OFFSET(zone_struct_active_pages),
KVADDR, &active_pages, sizeof(ulong),
"node_zones active_pages", FAULT_ON_ERROR);
readmem(node_zones+OFFSET(zone_struct_pages_min),
KVADDR, &pages_min, sizeof(ulong),
"node_zones pages_min", FAULT_ON_ERROR);
readmem(node_zones+OFFSET(zone_struct_pages_low),
KVADDR, &pages_low, sizeof(ulong),
"node_zones pages_low", FAULT_ON_ERROR);
readmem(node_zones+OFFSET(zone_struct_pages_high),
KVADDR, &pages_high, sizeof(ulong),
"node_zones pages_high", FAULT_ON_ERROR);
readmem(node_zones+OFFSET(zone_struct_name), KVADDR,
&value, sizeof(void *),
"node_zones name", FAULT_ON_ERROR);
if (read_string(value, buf1, BUFSIZE-1))
sprintf(namebuf, "%-8s", buf1);
else
sprintf(namebuf, "(unknown)");
sprintf(buf2, "%ld/%ld/%ld",
pages_min, pages_low, pages_high);
fprintf(fp, "%3d %s %7ld %7ld %15ld %15ld %s\n",
i,
namebuf,
free_pages,
active_pages,
inactive_dirty_pages,
inactive_clean_pages,
mkstring(buf3, strlen("MIN/LOW/HIGH"),
CENTER, buf2));
node_zones += SIZE(zone_struct);
}
}
return TRUE;
}
/*
* Dump the num "order" contents of the zone_t free_area array.
*/
char *free_area_hdr3 = "AREA SIZE FREE_AREA_STRUCT\n";
char *free_area_hdr4 = "AREA SIZE FREE_AREA_STRUCT BLOCKS PAGES\n";
static int
dump_zone_free_area(ulong free_area, int num, ulong verbose,
struct free_page_callback_data *callback_data)
{
int i, j;
long chunk_size;
int flen, total_free, cnt;
char buf[BUFSIZE];
ulong free_area_buf[3];
char *free_area_buf2;
char *free_list_buf;
ulong free_list;
struct list_data list_data, *ld;
int list_count;
ulong *free_ptr;
list_count = 0;
free_list_buf = free_area_buf2 = NULL;
if (VALID_STRUCT(free_area_struct)) {
if (SIZE(free_area_struct) != (3 * sizeof(ulong)))
error(FATAL,
"unrecognized free_area_struct size: %ld\n",
SIZE(free_area_struct));
list_count = 1;
} else if (VALID_STRUCT(free_area)) {
if (SIZE(free_area) == (3 * sizeof(ulong)))
list_count = 1;
else {
list_count = MEMBER_SIZE("free_area",
"free_list")/SIZE(list_head);
free_area_buf2 = GETBUF(SIZE(free_area));
free_list_buf = GETBUF(SIZE(list_head));
readmem(free_area, KVADDR, free_area_buf2,
SIZE(free_area), "free_area struct",
FAULT_ON_ERROR);
}
} else error(FATAL,
"neither free_area_struct or free_area structures exist\n");
ld = &list_data;
if (!verbose)
fprintf(fp, "%s", free_area_hdr4);
total_free = 0;
flen = MAX(VADDR_PRLEN, strlen("FREE_AREA_STRUCT"));
if (list_count > 1)
goto multiple_lists;
for (i = 0; i < num; i++,
free_area += SIZE_OPTION(free_area_struct, free_area)) {
if (verbose)
fprintf(fp, "%s", free_area_hdr3);
fprintf(fp, "%3d ", i);
chunk_size = power(2, i);
sprintf(buf, "%ldk", (chunk_size * PAGESIZE())/1024);
fprintf(fp, " %7s ", buf);
readmem(free_area, KVADDR, free_area_buf,
sizeof(ulong) * 3, "free_area_struct", FAULT_ON_ERROR);
fprintf(fp, "%s ",
mkstring(buf, flen, CENTER|LONG_HEX, MKSTR(free_area)));
if (free_area_buf[0] == free_area) {
if (verbose)
fprintf(fp, "\n");
else
fprintf(fp, "%6d %6d\n", 0, 0);
continue;
}
if (verbose)
fprintf(fp, "\n");
BZERO(ld, sizeof(struct list_data));
ld->flags = verbose | RETURN_ON_DUPLICATE;
ld->start = free_area_buf[0];
ld->end = free_area;
if (VALID_MEMBER(page_list_next))
ld->list_head_offset = OFFSET(page_list);
else if (VALID_MEMBER(page_lru))
ld->list_head_offset = OFFSET(page_lru)+
OFFSET(list_head_next);
else error(FATAL,
"neither page.list or page.lru exist?\n");
cnt = do_list(ld);
if (cnt < 0) {
error(pc->curcmd_flags & IGNORE_ERRORS ? INFO : FATAL,
"corrupted free list from free_area_struct: %lx\n",
free_area);
if (pc->curcmd_flags & IGNORE_ERRORS)
break;
}
if (!verbose)
fprintf(fp, "%6d %6ld\n", cnt, cnt*chunk_size);
total_free += (cnt * chunk_size);
}
return total_free;
multiple_lists:
for (i = 0; i < num; i++,
free_area += SIZE_OPTION(free_area_struct, free_area)) {
readmem(free_area, KVADDR, free_area_buf2,
SIZE(free_area), "free_area struct", FAULT_ON_ERROR);
for (j = 0, free_list = free_area; j < list_count;
j++, free_list += SIZE(list_head)) {
if (verbose)
fprintf(fp, "%s", free_area_hdr3);
fprintf(fp, "%3d ", i);
chunk_size = power(2, i);
sprintf(buf, "%ldk", (chunk_size * PAGESIZE())/1024);
fprintf(fp, " %7s ", buf);
readmem(free_list, KVADDR, free_list_buf,
SIZE(list_head), "free_area free_list",
FAULT_ON_ERROR);
fprintf(fp, "%s ",
mkstring(buf, flen, CENTER|LONG_HEX, MKSTR(free_list)));
free_ptr = (ulong *)free_list_buf;
if (*free_ptr == free_list) {
if (verbose)
fprintf(fp, "\n");
else
fprintf(fp, "%6d %6d\n", 0, 0);
continue;
}
if (verbose)
fprintf(fp, "\n");
BZERO(ld, sizeof(struct list_data));
ld->flags = verbose | RETURN_ON_DUPLICATE;
ld->start = *free_ptr;
ld->end = free_list;
ld->list_head_offset = OFFSET(page_lru) +
OFFSET(list_head_next);
if (callback_data) {
ld->flags &= ~VERBOSE;
ld->flags |= (LIST_CALLBACK|CALLBACK_RETURN);
ld->callback_func = free_page_callback;
ld->callback_data = (void *)callback_data;
callback_data->block_size = chunk_size * PAGESIZE();
}
cnt = do_list(ld);
if (cnt < 0) {
error(pc->curcmd_flags & IGNORE_ERRORS ? INFO : FATAL,
"corrupted free list %d from free_area struct: %lx\n",
j, free_area);
if (pc->curcmd_flags & IGNORE_ERRORS)
goto bailout;
}
if (callback_data && callback_data->found) {
fprintf(fp, "%lx\n", callback_data->page);
goto bailout;
}
if (!verbose)
fprintf(fp, "%6d %6ld\n", cnt, cnt*chunk_size);
total_free += (cnt * chunk_size);
}
}
bailout:
FREEBUF(free_area_buf2);
FREEBUF(free_list_buf);
return total_free;
}
/*
* dump_kmeminfo displays basic memory use information typically shown
* by /proc/meminfo, and then some...
*/
char *kmeminfo_hdr = " PAGES TOTAL PERCENTAGE\n";
static void
dump_kmeminfo(void)
{
int i, len;
ulong totalram_pages;
ulong freeram_pages;
ulong used_pages;
ulong shared_pages;
ulong buffer_pages;
ulong subtract_buffer_pages;
ulong totalswap_pages, totalused_pages;
ulong totalhigh_pages;
ulong freehighmem_pages;
ulong totallowmem_pages;
ulong freelowmem_pages;
long nr_file_pages, nr_slab;
ulong swapper_space_nrpages;
ulong pct;
ulong value1, value2;
uint tmp;
struct meminfo meminfo;
struct gnu_request req;
long page_cache_size;
ulong get_totalram;
ulong get_buffers;
ulong get_slabs;
struct syment *sp_array[2];
char buf[BUFSIZE];
BZERO(&meminfo, sizeof(struct meminfo));
meminfo.flags = GET_ALL;
dump_mem_map(&meminfo);
get_totalram = meminfo.get_totalram;
shared_pages = meminfo.get_shared;
get_buffers = meminfo.get_buffers;
get_slabs = meminfo.get_slabs;
/*
* If vm_stat array exists, override page search info.
*/
if (vm_stat_init()) {
if (dump_vm_stat("NR_SLAB", &nr_slab, 0))
get_slabs = nr_slab;
else if (dump_vm_stat("NR_SLAB_RECLAIMABLE", &nr_slab, 0)) {
get_slabs = nr_slab;
if (dump_vm_stat("NR_SLAB_UNRECLAIMABLE", &nr_slab, 0))
get_slabs += nr_slab;
}
}
fprintf(fp, "%s", kmeminfo_hdr);
/*
* Get total RAM based upon how the various versions of si_meminfo()
* have done it, latest to earliest:
*
* Prior to 2.3.36, count all mem_map pages minus the reserved ones.
* From 2.3.36 onwards, use "totalram_pages" if set.
*/
if (symbol_exists("totalram_pages")) {
totalram_pages = vt->totalram_pages ?
vt->totalram_pages : get_totalram;
} else
totalram_pages = get_totalram;
fprintf(fp, "%10s %7ld %11s ----\n", "TOTAL MEM",
totalram_pages, pages_to_size(totalram_pages, buf));
/*
* Get free pages from dump_free_pages() or its associates.
* Used pages are a free-bee...
*/
meminfo.flags = GET_FREE_PAGES;
vt->dump_free_pages(&meminfo);
freeram_pages = meminfo.retval;
pct = (freeram_pages * 100)/totalram_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"FREE", freeram_pages, pages_to_size(freeram_pages, buf), pct);
used_pages = totalram_pages - freeram_pages;
pct = (used_pages * 100)/totalram_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"USED", used_pages, pages_to_size(used_pages, buf), pct);
/*
* Get shared pages from dump_mem_map(). Note that this is done
* differently than the kernel -- it just tallies the non-reserved
* pages that have a count of greater than 1.
*/
pct = (shared_pages * 100)/totalram_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"SHARED", shared_pages, pages_to_size(shared_pages, buf), pct);
subtract_buffer_pages = 0;
if (symbol_exists("buffermem_pages")) {
get_symbol_data("buffermem_pages", sizeof(int), &tmp);
buffer_pages = (ulong)tmp;
} else if (symbol_exists("buffermem")) {
get_symbol_data("buffermem", sizeof(int), &tmp);
buffer_pages = BTOP(tmp);
} else if ((THIS_KERNEL_VERSION >= LINUX(2,6,0)) &&
symbol_exists("nr_blockdev_pages")) {
subtract_buffer_pages = buffer_pages = nr_blockdev_pages();
} else
buffer_pages = 0;
pct = (buffer_pages * 100)/totalram_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"BUFFERS", buffer_pages, pages_to_size(buffer_pages, buf), pct);
if (CRASHDEBUG(1))
error(NOTE, "pages with buffers: %ld\n", get_buffers);
/*
* page_cache_size has evolved from a long to an atomic_t to
* not existing at all.
*/
if (symbol_exists("page_cache_size")) {
get_symbol_type("page_cache_size", NULL, &req);
if (req.length == sizeof(int)) {
get_symbol_data("page_cache_size", sizeof(int), &tmp);
page_cache_size = (long)tmp;
} else
get_symbol_data("page_cache_size", sizeof(long),
&page_cache_size);
page_cache_size -= subtract_buffer_pages;
} else if (symbol_exists("nr_pagecache")) {
get_symbol_data("nr_pagecache", sizeof(int), &tmp);
page_cache_size = (long)tmp;
page_cache_size -= subtract_buffer_pages;
} else if (dump_vm_stat("NR_FILE_PAGES", &nr_file_pages, 0)) {
char *swapper_space = GETBUF(SIZE(address_space));
swapper_space_nrpages = 0;
if (symbol_exists("swapper_spaces") &&
(len = get_array_length("swapper_spaces", NULL, 0))) {
for (i = 0; i < len; i++) {
if (!readmem(symbol_value("swapper_spaces") +
i * SIZE(address_space), KVADDR,
swapper_space, SIZE(address_space),
"swapper_space", RETURN_ON_ERROR))
break;
swapper_space_nrpages += ULONG(swapper_space +
OFFSET(address_space_nrpages));
}
} else if (symbol_exists("swapper_space") &&
readmem(symbol_value("swapper_space"), KVADDR,
swapper_space, SIZE(address_space), "swapper_space",
RETURN_ON_ERROR))
swapper_space_nrpages = ULONG(swapper_space +
OFFSET(address_space_nrpages));
page_cache_size = nr_file_pages - swapper_space_nrpages -
buffer_pages;
FREEBUF(swapper_space);
} else
page_cache_size = 0;
pct = (page_cache_size * 100)/totalram_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"CACHED", page_cache_size,
pages_to_size(page_cache_size, buf), pct);
/*
* Although /proc/meminfo doesn't show it, show how much memory
* the slabs take up.
*/
pct = (get_slabs * 100)/totalram_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"SLAB", get_slabs, pages_to_size(get_slabs, buf), pct);
if (symbol_exists("totalhigh_pages")) {
switch (get_syment_array("totalhigh_pages", sp_array, 2))
{
case 1:
get_symbol_data("totalhigh_pages", sizeof(ulong),
&totalhigh_pages);
break;
case 2:
if (!(readmem(sp_array[0]->value, KVADDR,
&value1, sizeof(ulong),
"totalhigh_pages #1", RETURN_ON_ERROR)))
break;
if (!(readmem(sp_array[1]->value, KVADDR,
&value2, sizeof(ulong),
"totalhigh_pages #2", RETURN_ON_ERROR)))
break;
totalhigh_pages = MAX(value1, value2);
break;
}
pct = totalhigh_pages ?
(totalhigh_pages * 100)/totalram_pages : 0;
fprintf(fp, "\n%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"TOTAL HIGH", totalhigh_pages,
pages_to_size(totalhigh_pages, buf), pct);
meminfo.flags = GET_FREE_HIGHMEM_PAGES;
vt->dump_free_pages(&meminfo);
freehighmem_pages = meminfo.retval;
pct = freehighmem_pages ?
(freehighmem_pages * 100)/totalhigh_pages : 0;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL HIGH\n",
"FREE HIGH", freehighmem_pages,
pages_to_size(freehighmem_pages, buf), pct);
totallowmem_pages = totalram_pages - totalhigh_pages;
pct = (totallowmem_pages * 100)/totalram_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL MEM\n",
"TOTAL LOW", totallowmem_pages,
pages_to_size(totallowmem_pages, buf), pct);
freelowmem_pages = freeram_pages - freehighmem_pages;
pct = (freelowmem_pages * 100)/totallowmem_pages;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL LOW\n",
"FREE LOW", freelowmem_pages,
pages_to_size(freelowmem_pages, buf), pct);
}
/*
* get swap data from dump_swap_info().
*/
fprintf(fp, "\n");
if (symbol_exists("swapper_space") || symbol_exists("swapper_spaces")) {
if (dump_swap_info(RETURN_ON_ERROR, &totalswap_pages,
&totalused_pages)) {
fprintf(fp, "%10s %7ld %11s ----\n",
"TOTAL SWAP", totalswap_pages,
pages_to_size(totalswap_pages, buf));
pct = totalswap_pages ? (totalused_pages * 100) /
totalswap_pages : 100;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL SWAP\n",
"SWAP USED", totalused_pages,
pages_to_size(totalused_pages, buf), pct);
pct = totalswap_pages ?
((totalswap_pages - totalused_pages) *
100) / totalswap_pages : 0;
fprintf(fp, "%10s %7ld %11s %3ld%% of TOTAL SWAP\n",
"SWAP FREE",
totalswap_pages - totalused_pages,
pages_to_size(totalswap_pages - totalused_pages,
buf), pct);
} else
error(INFO,
"swap_info[%ld].swap_map at %lx is inaccessible\n",
totalused_pages, totalswap_pages);
}
dump_zone_page_usage();
}
/*
* Emulate 2.6 nr_blockdev_pages() function.
*/
static ulong
nr_blockdev_pages(void)
{
struct list_data list_data, *ld;
int i, bdevcnt;
ulong inode, address_space;
ulong nrpages;
char *block_device_buf, *inode_buf, *address_space_buf;
ld = &list_data;
BZERO(ld, sizeof(struct list_data));
get_symbol_data("all_bdevs", sizeof(void *), &ld->start);
if (empty_list(ld->start))
return 0;
ld->flags |= LIST_ALLOCATE;
ld->end = symbol_value("all_bdevs");
ld->list_head_offset = OFFSET(block_device_bd_list);
block_device_buf = GETBUF(SIZE(block_device));
inode_buf = GETBUF(SIZE(inode));
address_space_buf = GETBUF(SIZE(address_space));
bdevcnt = do_list(ld);
/*
* go through the block_device list, emulating:
*
* ret += bdev->bd_inode->i_mapping->nrpages;
*/
for (i = nrpages = 0; i < bdevcnt; i++) {
readmem(ld->list_ptr[i], KVADDR, block_device_buf,
SIZE(block_device), "block_device buffer",
FAULT_ON_ERROR);
inode = ULONG(block_device_buf + OFFSET(block_device_bd_inode));
readmem(inode, KVADDR, inode_buf, SIZE(inode), "inode buffer",
FAULT_ON_ERROR);
address_space = ULONG(inode_buf + OFFSET(inode_i_mapping));
readmem(address_space, KVADDR, address_space_buf,
SIZE(address_space), "address_space buffer",
FAULT_ON_ERROR);
nrpages += ULONG(address_space_buf +
OFFSET(address_space_nrpages));
}
FREEBUF(ld->list_ptr);
FREEBUF(block_device_buf);
FREEBUF(inode_buf);
FREEBUF(address_space_buf);
return nrpages;
}
/*
* dump_vmlist() displays information from the vmlist.
*/
static void
dump_vmlist(struct meminfo *vi)
{
char buf[BUFSIZE];
char buf1[BUFSIZE];
char buf2[BUFSIZE];
ulong vmlist;
ulong addr, size, next, pcheck, count, verified;
physaddr_t paddr;
int mod_vmlist;
if (vt->flags & USE_VMAP_AREA) {
dump_vmap_area(vi);
return;
}
get_symbol_data("vmlist", sizeof(void *), &vmlist);
next = vmlist;
count = verified = 0;
mod_vmlist = kernel_symbol_exists("mod_vmlist");
while (next) {
if (!(pc->curcmd_flags & HEADER_PRINTED) && (next == vmlist) &&
!(vi->flags & (GET_HIGHEST|GET_PHYS_TO_VMALLOC|
GET_VMLIST_COUNT|GET_VMLIST|VMLIST_VERIFY))) {
fprintf(fp, "%s ",
mkstring(buf, MAX(strlen("VM_STRUCT"), VADDR_PRLEN),
CENTER|LJUST, "VM_STRUCT"));
fprintf(fp, "%s SIZE\n",
mkstring(buf, (VADDR_PRLEN * 2) + strlen(" - "),
CENTER|LJUST, "ADDRESS RANGE"));
pc->curcmd_flags |= HEADER_PRINTED;
}
readmem(next+OFFSET(vm_struct_addr), KVADDR,
&addr, sizeof(void *),
"vmlist addr", FAULT_ON_ERROR);
readmem(next+OFFSET(vm_struct_size), KVADDR,
&size, sizeof(ulong),
"vmlist size", FAULT_ON_ERROR);
if (vi->flags & (GET_VMLIST_COUNT|GET_VMLIST)) {
/*
* Preceding GET_VMLIST_COUNT set vi->retval.
*/
if (vi->flags & GET_VMLIST) {
if (count < vi->retval) {
vi->vmlist[count].addr = addr;
vi->vmlist[count].size = size;
}
}
count++;
goto next_entry;
}
if (!(vi->flags & ADDRESS_SPECIFIED) ||
((vi->memtype == KVADDR) &&
((vi->spec_addr >= addr) && (vi->spec_addr < (addr+size))))) {
if (vi->flags & VMLIST_VERIFY) {
verified++;
break;
}
fprintf(fp, "%s%s %s - %s %6ld\n",
mkstring(buf,VADDR_PRLEN, LONG_HEX|CENTER|LJUST,
MKSTR(next)), space(MINSPACE-1),
mkstring(buf1, VADDR_PRLEN, LONG_HEX|RJUST,
MKSTR(addr)),
mkstring(buf2, VADDR_PRLEN, LONG_HEX|LJUST,
MKSTR(addr+size)),
size);
}
if ((vi->flags & ADDRESS_SPECIFIED) &&
(vi->memtype == PHYSADDR)) {
for (pcheck = addr; pcheck < (addr+size);
pcheck += PAGESIZE()) {
if (!kvtop(NULL, pcheck, &paddr, 0))
continue;
if ((vi->spec_addr >= paddr) &&
(vi->spec_addr < (paddr+PAGESIZE()))) {
if (vi->flags & GET_PHYS_TO_VMALLOC) {
vi->retval = pcheck +
PAGEOFFSET(paddr);
return;
} else
fprintf(fp,
"%s%s %s - %s %6ld\n",
mkstring(buf, VADDR_PRLEN,
LONG_HEX|CENTER|LJUST,
MKSTR(next)), space(MINSPACE-1),
mkstring(buf1, VADDR_PRLEN,
LONG_HEX|RJUST, MKSTR(addr)),
mkstring(buf2, VADDR_PRLEN,
LONG_HEX|LJUST,
MKSTR(addr+size)), size);
break;
}
}
}
next_entry:
readmem(next+OFFSET(vm_struct_next),
KVADDR, &next, sizeof(void *),
"vmlist next", FAULT_ON_ERROR);
if (!next && mod_vmlist) {
get_symbol_data("mod_vmlist", sizeof(void *), &next);
mod_vmlist = FALSE;
}
}
if (vi->flags & GET_HIGHEST)
vi->retval = addr+size;
if (vi->flags & GET_VMLIST_COUNT)
vi->retval = count;
if (vi->flags & VMLIST_VERIFY)
vi->retval = verified;
}
static void
dump_vmap_area(struct meminfo *vi)
{
int i, cnt;
ulong start, end, vm_struct, flags;
struct list_data list_data, *ld;
char *vmap_area_buf;
ulong size, pcheck, count, verified;
physaddr_t paddr;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
#define VM_VM_AREA 0x4 /* mm/vmalloc.c */
vmap_area_buf = GETBUF(SIZE(vmap_area));
start = count = verified = size = 0;
ld = &list_data;
BZERO(ld, sizeof(struct list_data));
ld->flags = LIST_HEAD_FORMAT|LIST_HEAD_POINTER|LIST_ALLOCATE;
get_symbol_data("vmap_area_list", sizeof(void *), &ld->start);
ld->list_head_offset = OFFSET(vmap_area_list);
ld->end = symbol_value("vmap_area_list");
cnt = do_list(ld);
for (i = 0; i < cnt; i++) {
if (!(pc->curcmd_flags & HEADER_PRINTED) && (i == 0) &&
!(vi->flags & (GET_HIGHEST|GET_PHYS_TO_VMALLOC|
GET_VMLIST_COUNT|GET_VMLIST|VMLIST_VERIFY))) {
fprintf(fp, "%s ",
mkstring(buf1, MAX(strlen("VMAP_AREA"), VADDR_PRLEN),
CENTER|LJUST, "VMAP_AREA"));
fprintf(fp, "%s ",
mkstring(buf1, MAX(strlen("VM_STRUCT"), VADDR_PRLEN),
CENTER|LJUST, "VM_STRUCT"));
fprintf(fp, "%s SIZE\n",
mkstring(buf1, (VADDR_PRLEN * 2) + strlen(" - "),
CENTER|LJUST, "ADDRESS RANGE"));
pc->curcmd_flags |= HEADER_PRINTED;
}
readmem(ld->list_ptr[i], KVADDR, vmap_area_buf,
SIZE(vmap_area), "vmap_area struct", FAULT_ON_ERROR);
flags = ULONG(vmap_area_buf + OFFSET(vmap_area_flags));
if (flags != VM_VM_AREA)
continue;
start = ULONG(vmap_area_buf + OFFSET(vmap_area_va_start));
end = ULONG(vmap_area_buf + OFFSET(vmap_area_va_end));
vm_struct = ULONG(vmap_area_buf + OFFSET(vmap_area_vm));
size = end - start;
if (vi->flags & (GET_VMLIST_COUNT|GET_VMLIST)) {
/*
* Preceding GET_VMLIST_COUNT set vi->retval.
*/
if (vi->flags & GET_VMLIST) {
if (count < vi->retval) {
vi->vmlist[count].addr = start;
vi->vmlist[count].size = size;
}
}
count++;
continue;
}
if (!(vi->flags & ADDRESS_SPECIFIED) ||
((vi->memtype == KVADDR) &&
((vi->spec_addr >= start) && (vi->spec_addr < (start+size))))) {
if (vi->flags & VMLIST_VERIFY) {
verified++;
break;
}
fprintf(fp, "%s%s %s%s %s - %s %7ld\n",
mkstring(buf1,VADDR_PRLEN, LONG_HEX|CENTER|LJUST,
MKSTR(ld->list_ptr[i])), space(MINSPACE-1),
mkstring(buf2,VADDR_PRLEN, LONG_HEX|CENTER|LJUST,
MKSTR(vm_struct)), space(MINSPACE-1),
mkstring(buf3, VADDR_PRLEN, LONG_HEX|RJUST,
MKSTR(start)),
mkstring(buf4, VADDR_PRLEN, LONG_HEX|LJUST,
MKSTR(start+size)),
size);
}
if ((vi->flags & ADDRESS_SPECIFIED) &&
(vi->memtype == PHYSADDR)) {
for (pcheck = start; pcheck < (start+size);
pcheck += PAGESIZE()) {
if (!kvtop(NULL, pcheck, &paddr, 0))
continue;
if ((vi->spec_addr >= paddr) &&
(vi->spec_addr < (paddr+PAGESIZE()))) {
if (vi->flags & GET_PHYS_TO_VMALLOC) {
vi->retval = pcheck +
PAGEOFFSET(paddr);
FREEBUF(ld->list_ptr);
return;
} else
fprintf(fp,
"%s%s %s%s %s - %s %7ld\n",
mkstring(buf1,VADDR_PRLEN,
LONG_HEX|CENTER|LJUST,
MKSTR(ld->list_ptr[i])),
space(MINSPACE-1),
mkstring(buf2, VADDR_PRLEN,
LONG_HEX|CENTER|LJUST,
MKSTR(vm_struct)), space(MINSPACE-1),
mkstring(buf3, VADDR_PRLEN,
LONG_HEX|RJUST, MKSTR(start)),
mkstring(buf4, VADDR_PRLEN,
LONG_HEX|LJUST,
MKSTR(start+size)), size);
break;
}
}
}
}
FREEBUF(ld->list_ptr);
if (vi->flags & GET_HIGHEST)
vi->retval = start+size;
if (vi->flags & GET_VMLIST_COUNT)
vi->retval = count;
if (vi->flags & VMLIST_VERIFY)
vi->retval = verified;
}
/*
* dump_page_lists() displays information from the active_list,
* inactive_dirty_list and inactive_clean_list from each zone.
*/
static int
dump_page_lists(struct meminfo *mi)
{
int i, c, n, retval;
ulong node_zones, pgdat;
struct node_table *nt;
struct list_data list_data, *ld;
char buf[BUFSIZE];
ulong value;
ulong inactive_clean_pages, inactive_clean_list;
int nr_active_pages, nr_inactive_pages;
int nr_inactive_dirty_pages;
ld = &list_data;
retval = FALSE;
nr_active_pages = nr_inactive_dirty_pages = -1;
BZERO(ld, sizeof(struct list_data));
ld->list_head_offset = OFFSET(page_lru);
if (mi->flags & ADDRESS_SPECIFIED)
ld->searchfor = mi->spec_addr;
else if (mi->flags & VERBOSE)
ld->flags |= VERBOSE;
if (mi->flags & GET_ACTIVE_LIST) {
if (!symbol_exists("active_list"))
error(FATAL,
"active_list does not exist in this kernel\n");
if (symbol_exists("nr_active_pages"))
get_symbol_data("nr_active_pages", sizeof(int),
&nr_active_pages);
else
error(FATAL,
"nr_active_pages does not exist in this kernel\n");
ld->end = symbol_value("active_list");
readmem(ld->end, KVADDR, &ld->start, sizeof(void *),
"LIST_HEAD contents", FAULT_ON_ERROR);
if (mi->flags & VERBOSE)
fprintf(fp, "active_list:\n");
if (ld->start == ld->end) {
c = 0;
ld->searchfor = 0;
if (mi->flags & VERBOSE)
fprintf(fp, "(empty)\n");
} else {
hq_open();
c = do_list(ld);
hq_close();
}
if ((mi->flags & ADDRESS_SPECIFIED) && ld->searchfor) {
fprintf(fp, "%lx\n", ld->searchfor);
retval = TRUE;
} else {
fprintf(fp, "%snr_active_pages: %d ",
mi->flags & VERBOSE ? "\n" : "",
nr_active_pages);
if (c != nr_active_pages)
fprintf(fp, "(found %d)\n", c);
else
fprintf(fp, "(verified)\n");
}
}
if (mi->flags & GET_INACTIVE_LIST) {
if (!symbol_exists("inactive_list"))
error(FATAL,
"inactive_list does not exist in this kernel\n");
if (symbol_exists("nr_inactive_pages"))
get_symbol_data("nr_inactive_pages", sizeof(int),
&nr_inactive_pages);
else
error(FATAL,
"nr_active_pages does not exist in this kernel\n");
ld->end = symbol_value("inactive_list");
readmem(ld->end, KVADDR, &ld->start, sizeof(void *),
"LIST_HEAD contents", FAULT_ON_ERROR);
if (mi->flags & VERBOSE)
fprintf(fp, "inactive_list:\n");
if (ld->start == ld->end) {
c = 0;
ld->searchfor = 0;
if (mi->flags & VERBOSE)
fprintf(fp, "(empty)\n");
} else {
hq_open();
c = do_list(ld);
hq_close();
}
if ((mi->flags & ADDRESS_SPECIFIED) && ld->searchfor) {
fprintf(fp, "%lx\n", ld->searchfor);
retval = TRUE;
} else {
fprintf(fp, "%snr_inactive_pages: %d ",
mi->flags & VERBOSE ? "\n" : "",
nr_inactive_pages);
if (c != nr_inactive_pages)
fprintf(fp, "(found %d)\n", c);
else
fprintf(fp, "(verified)\n");
}
}
if (mi->flags & GET_INACTIVE_DIRTY) {
if (!symbol_exists("inactive_dirty_list"))
error(FATAL,
"inactive_dirty_list does not exist in this kernel\n");
if (symbol_exists("nr_inactive_dirty_pages"))
get_symbol_data("nr_inactive_dirty_pages", sizeof(int),
&nr_inactive_dirty_pages);
else
error(FATAL,
"nr_inactive_dirty_pages does not exist in this kernel\n");
ld->end = symbol_value("inactive_dirty_list");
readmem(ld->end, KVADDR, &ld->start, sizeof(void *),
"LIST_HEAD contents", FAULT_ON_ERROR);
if (mi->flags & VERBOSE)
fprintf(fp, "%sinactive_dirty_list:\n",
mi->flags & GET_ACTIVE_LIST ? "\n" : "");
if (ld->start == ld->end) {
c = 0;
ld->searchfor = 0;
if (mi->flags & VERBOSE)
fprintf(fp, "(empty)\n");
} else {
hq_open();
c = do_list(ld);
hq_close();
}
if ((mi->flags & ADDRESS_SPECIFIED) && ld->searchfor) {
fprintf(fp, "%lx\n", ld->searchfor);
retval = TRUE;
} else {
fprintf(fp, "%snr_inactive_dirty_pages: %d ",
mi->flags & VERBOSE ? "\n" : "",
nr_inactive_dirty_pages);
if (c != nr_inactive_dirty_pages)
fprintf(fp, "(found %d)\n", c);
else
fprintf(fp, "(verified)\n");
}
}
if (mi->flags & GET_INACTIVE_CLEAN) {
if (INVALID_MEMBER(zone_struct_inactive_clean_list))
error(FATAL,
"inactive_clean_list(s) do not exist in this kernel\n");
get_symbol_data("pgdat_list", sizeof(void *), &pgdat);
if ((mi->flags & VERBOSE) &&
(mi->flags & (GET_ACTIVE_LIST|GET_INACTIVE_DIRTY)))
fprintf(fp, "\n");
for (n = 0; pgdat; n++) {
nt = &vt->node_table[n];
node_zones = nt->pgdat + OFFSET(pglist_data_node_zones);
for (i = 0; i < vt->nr_zones; i++) {
readmem(node_zones+OFFSET(zone_struct_name),
KVADDR, &value, sizeof(void *),
"zone_struct name", FAULT_ON_ERROR);
if (!read_string(value, buf, BUFSIZE-1))
sprintf(buf, "(unknown) ");
if (mi->flags & VERBOSE) {
if (vt->numnodes > 1)
fprintf(fp, "NODE %d ", n);
fprintf(fp,
"\"%s\" inactive_clean_list:\n",
buf);
}
readmem(node_zones +
OFFSET(zone_struct_inactive_clean_pages),
KVADDR, &inactive_clean_pages,
sizeof(ulong), "inactive_clean_pages",
FAULT_ON_ERROR);
readmem(node_zones +
OFFSET(zone_struct_inactive_clean_list),
KVADDR, &inactive_clean_list,
sizeof(ulong), "inactive_clean_list",
FAULT_ON_ERROR);
ld->start = inactive_clean_list;
ld->end = node_zones +
OFFSET(zone_struct_inactive_clean_list);
if (mi->flags & ADDRESS_SPECIFIED)
ld->searchfor = mi->spec_addr;
if (ld->start == ld->end) {
c = 0;
ld->searchfor = 0;
if (mi->flags & VERBOSE)
fprintf(fp, "(empty)\n");
} else {
hq_open();
c = do_list(ld);
hq_close();
}
if ((mi->flags & ADDRESS_SPECIFIED) &&
ld->searchfor) {
fprintf(fp, "%lx\n", ld->searchfor);
retval = TRUE;
} else {
if (vt->numnodes > 1)
fprintf(fp, "NODE %d ", n);
fprintf(fp, "\"%s\" ", buf);
fprintf(fp,
"inactive_clean_pages: %ld ",
inactive_clean_pages);
if (c != inactive_clean_pages)
fprintf(fp, "(found %d)\n", c);
else
fprintf(fp, "(verified)\n");
}
node_zones += SIZE(zone_struct);
}
readmem(pgdat + OFFSET_OPTION(pglist_data_node_next,
pglist_data_pgdat_next), KVADDR,
&pgdat, sizeof(void *), "pglist_data node_next",
FAULT_ON_ERROR);
}
}
return retval;
}
/*
* Check whether an address is a kmem_cache_t address, and if so, return
* a pointer to the static buffer containing its name string. Otherwise
* return NULL on failure.
*/
#define PERCPU_NOT_SUPPORTED "per-cpu slab format not supported yet\n"
static char *
is_kmem_cache_addr(ulong vaddr, char *kbuf)
{
ulong cache, cache_cache, name;
long next_offset, name_offset;
if (vt->flags & KMEM_CACHE_UNAVAIL) {
error(INFO, "kmem cache slab subsystem not available\n");
return NULL;
}
if (vt->flags & KMALLOC_SLUB)
return is_kmem_cache_addr_common(vaddr, kbuf);
if ((vt->flags & KMALLOC_COMMON) && !symbol_exists("cache_cache"))
return is_kmem_cache_addr_common(vaddr, kbuf);
name_offset = vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2) ?
OFFSET(kmem_cache_s_name) : OFFSET(kmem_cache_s_c_name);
next_offset = vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2) ?
OFFSET(kmem_cache_s_next) : OFFSET(kmem_cache_s_c_nextp);
cache = cache_cache = symbol_value("cache_cache");
do {
if (cache == vaddr) {
if (vt->kmem_cache_namelen) {
readmem(cache+name_offset, KVADDR, kbuf, vt->kmem_cache_namelen, "name array", FAULT_ON_ERROR);
} else {
readmem(cache+name_offset, KVADDR, &name, sizeof(name), "name", FAULT_ON_ERROR);
if (!read_string(name, kbuf, BUFSIZE-1)) {
if (vt->flags &
(PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2))
error(WARNING,
"cannot read kmem_cache_s.name string at %lx\n",
name);
else
error(WARNING,
"cannot read kmem_cache_s.c_name string at %lx\n",
name);
sprintf(kbuf, "(unknown)");
}
}
return kbuf;
}
readmem(cache+next_offset, KVADDR, &cache, sizeof(long),
"kmem_cache_s next", FAULT_ON_ERROR);
if (vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2))
cache -= next_offset;
} while (cache != cache_cache);
return NULL;
}
/*
* Note same functionality as above, but instead it just
* dumps all slab cache names and their addresses.
*/
static void
kmem_cache_list(void)
{
ulong cache, cache_cache, name;
long next_offset, name_offset;
char *cache_buf;
int has_cache_chain;
ulong cache_chain;
char buf[BUFSIZE];
if (vt->flags & KMEM_CACHE_UNAVAIL) {
error(INFO, "kmem cache slab subsystem not available\n");
return;
}
if (vt->flags & (KMALLOC_SLUB|KMALLOC_COMMON)) {
kmem_cache_list_common();
return;
}
if (symbol_exists("cache_chain")) {
has_cache_chain = TRUE;
cache_chain = symbol_value("cache_chain");
} else {
has_cache_chain = FALSE;
cache_chain = 0;
}
name_offset = vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2) ?
OFFSET(kmem_cache_s_name) : OFFSET(kmem_cache_s_c_name);
next_offset = vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2) ?
OFFSET(kmem_cache_s_next) : OFFSET(kmem_cache_s_c_nextp);
cache = cache_cache = symbol_value("cache_cache");
cache_buf = GETBUF(SIZE(kmem_cache_s));
do {
readmem(cache, KVADDR, cache_buf, SIZE(kmem_cache_s),
"kmem_cache buffer", FAULT_ON_ERROR);
if (vt->kmem_cache_namelen) {
BCOPY(cache_buf+name_offset, buf,
vt->kmem_cache_namelen);
} else {
name = ULONG(cache_buf + name_offset);
if (!read_string(name, buf, BUFSIZE-1)) {
if (vt->flags &
(PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2))
error(WARNING,
"cannot read kmem_cache_s.name string at %lx\n",
name);
else
error(WARNING,
"cannot read kmem_cache_s.c_name string at %lx\n",
name);
sprintf(buf, "(unknown)");
}
}
fprintf(fp, "%lx %s\n", cache, buf);
cache = ULONG(cache_buf + next_offset);
if (has_cache_chain && (cache == cache_chain))
readmem(cache, KVADDR, &cache, sizeof(char *),
"cache_chain", FAULT_ON_ERROR);
if (vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2))
cache -= next_offset;
} while (cache != cache_cache);
FREEBUF(cache_buf);
}
/*
* Translate an address to its physical page number, verify that the
* page in fact belongs to the slab subsystem, and if so, return the
* name of the cache to which it belongs.
*/
static char *
vaddr_to_kmem_cache(ulong vaddr, char *buf, int verbose)
{
physaddr_t paddr;
ulong page;
ulong cache;
if (!kvtop(NULL, vaddr, &paddr, 0)) {
if (verbose)
error(WARNING,
"cannot make virtual-to-physical translation: %lx\n",
vaddr);
return NULL;
}
if (!phys_to_page(paddr, &page)) {
if (verbose)
error(WARNING,
"cannot find mem_map page for address: %lx\n",
vaddr);
return NULL;
}
if ((vt->flags & KMALLOC_SLUB) ||
((vt->flags & KMALLOC_COMMON) &&
VALID_MEMBER(page_slab) && VALID_MEMBER(page_first_page))) {
readmem(compound_head(page)+OFFSET(page_slab),
KVADDR, &cache, sizeof(void *),
"page.slab", FAULT_ON_ERROR);
} else if (VALID_MEMBER(page_next))
readmem(page+OFFSET(page_next),
KVADDR, &cache, sizeof(void *),
"page.next", FAULT_ON_ERROR);
else if (VALID_MEMBER(page_list_next))
readmem(page+OFFSET(page_list_next),
KVADDR, &cache, sizeof(void *),
"page.list.next", FAULT_ON_ERROR);
else if (VALID_MEMBER(page_lru))
readmem(page+OFFSET(page_lru)+OFFSET(list_head_next),
KVADDR, &cache, sizeof(void *),
"page.lru.next", FAULT_ON_ERROR);
else
error(FATAL, "cannot determine slab cache from page struct\n");
return(is_kmem_cache_addr(cache, buf));
}
static char *
is_slab_overload_page(ulong vaddr, ulong *page_head, char *buf)
{
ulong cache;
char *p;
if ((vt->flags & SLAB_OVERLOAD_PAGE) &&
is_page_ptr(vaddr, NULL) &&
VALID_MEMBER(page_slab) && VALID_MEMBER(page_first_page)) {
readmem(compound_head(vaddr)+OFFSET(page_slab),
KVADDR, &cache, sizeof(void *),
"page.slab", FAULT_ON_ERROR);
p = is_kmem_cache_addr(cache, buf);
if (p)
*page_head = compound_head(vaddr);
return p;
}
return NULL;
}
/*
* Translate an address to its physical page number, verify that the
* page in fact belongs to the slab subsystem, and if so, return the
* address of the slab to which it belongs.
*/
static ulong
vaddr_to_slab(ulong vaddr)
{
physaddr_t paddr;
ulong page;
ulong slab;
if (!kvtop(NULL, vaddr, &paddr, 0)) {
error(WARNING,
"cannot make virtual-to-physical translation: %lx\n",
vaddr);
return 0;
}
if (!phys_to_page(paddr, &page)) {
error(WARNING, "cannot find mem_map page for address: %lx\n",
vaddr);
return 0;
}
slab = 0;
if (vt->flags & KMALLOC_SLUB)
slab = compound_head(page);
else if (vt->flags & SLAB_OVERLOAD_PAGE)
slab = page;
else if ((vt->flags & KMALLOC_COMMON) && VALID_MEMBER(page_slab_page))
readmem(page+OFFSET(page_slab_page),
KVADDR, &slab, sizeof(void *),
"page.slab_page", FAULT_ON_ERROR);
else if (VALID_MEMBER(page_prev))
readmem(page+OFFSET(page_prev),
KVADDR, &slab, sizeof(void *),
"page.prev", FAULT_ON_ERROR);
else if (VALID_MEMBER(page_list_prev))
readmem(page+OFFSET(page_list_prev),
KVADDR, &slab, sizeof(void *),
"page.list.prev", FAULT_ON_ERROR);
else if (VALID_MEMBER(page_lru))
readmem(page+OFFSET(page_lru)+OFFSET(list_head_prev),
KVADDR, &slab, sizeof(void *),
"page.lru.prev", FAULT_ON_ERROR);
else
error(FATAL, "unknown definition of struct page?\n");
return slab;
}
/*
* Initialize any data required for scouring the kmalloc subsystem more
* efficiently.
*/
char slab_hdr[100] = { 0 };
char kmem_cache_hdr[100] = { 0 };
char free_inuse_hdr[100] = { 0 };
static void
kmem_cache_init(void)
{
ulong cache, cache_end, max_cnum, max_limit, max_cpus, tmp, tmp2;
long cache_count, num_offset, next_offset;
char *cache_buf;
if (vt->flags & KMEM_CACHE_UNAVAIL)
return;
if ((vt->flags & KMEM_CACHE_DELAY) && !(pc->flags & RUNTIME))
return;
if (DUMPFILE() && (vt->flags & KMEM_CACHE_INIT))
return;
please_wait("gathering kmem slab cache data");
if (!strlen(slab_hdr)) {
if (vt->flags & KMALLOC_SLUB)
sprintf(slab_hdr,
"SLAB%sMEMORY%sNODE TOTAL ALLOCATED FREE\n",
space(VADDR_PRLEN > 8 ? 14 : 6),
space(VADDR_PRLEN > 8 ? 12 : 4));
else
sprintf(slab_hdr,
"SLAB%sMEMORY%sTOTAL ALLOCATED FREE\n",
space(VADDR_PRLEN > 8 ? 14 : 6),
space(VADDR_PRLEN > 8 ? 12 : 4));
}
if (!strlen(kmem_cache_hdr))
sprintf(kmem_cache_hdr,
"CACHE%sNAME OBJSIZE ALLOCATED TOTAL SLABS SSIZE\n",
space(VADDR_PRLEN > 8 ? 12 : 4));
if (!strlen(free_inuse_hdr))
sprintf(free_inuse_hdr, "FREE / [ALLOCATED]\n");
if (vt->flags & KMALLOC_SLUB) {
kmem_cache_init_slub();
please_wait_done();
return;
}
num_offset = vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2) ?
OFFSET(kmem_cache_s_num) : OFFSET(kmem_cache_s_c_num);
next_offset = vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2) ?
OFFSET(kmem_cache_s_next) : OFFSET(kmem_cache_s_c_nextp);
max_cnum = max_limit = max_cpus = cache_count = tmp2 = 0;
/*
* Pre-2.6 versions used the "cache_cache" as the head of the
* slab chain list. 2.6 uses the "cache_chain" list_head.
* In 3.6 SLAB and SLUB use the "slab_caches" list_head.
*/
if (vt->flags & PERCPU_KMALLOC_V2) {
if (kernel_symbol_exists("cache_chain")) {
get_symbol_data("cache_chain", sizeof(ulong), &cache);
cache_end = symbol_value("cache_chain");
} else if (kernel_symbol_exists("slab_caches")) {
vt->flags |= KMALLOC_COMMON;
get_symbol_data("slab_caches", sizeof(ulong), &cache);
cache_end = symbol_value("slab_caches");
} else {
error(INFO,
"unable to initialize kmem slab cache subsystem\n\n");
return;
}
cache -= next_offset;
} else
cache = cache_end = symbol_value("cache_cache");
if (!(pc->flags & RUNTIME))
kmem_cache_downsize();
cache_buf = GETBUF(SIZE(kmem_cache_s));
hq_open();
do {
cache_count++;
if (!readmem(cache, KVADDR, cache_buf, SIZE(kmem_cache_s),
"kmem_cache buffer", RETURN_ON_ERROR)) {
FREEBUF(cache_buf);
vt->flags |= KMEM_CACHE_UNAVAIL;
error(INFO,
"%sunable to initialize kmem slab cache subsystem\n\n",
DUMPFILE() ? "\n" : "");
hq_close();
return;
}
if (!hq_enter(cache)) {
error(WARNING,
"%sduplicate kmem_cache entry in cache list: %lx\n",
DUMPFILE() ? "\n" : "", cache);
error(INFO, "unable to initialize kmem slab cache subsystem\n\n");
vt->flags |= KMEM_CACHE_UNAVAIL;
hq_close();
return;
}
tmp = (ulong)(UINT(cache_buf + num_offset));
if (tmp > max_cnum)
max_cnum = tmp;
if ((tmp = max_cpudata_limit(cache, &tmp2)) > max_limit)
max_limit = tmp;
/*
* Recognize and bail out on any max_cpudata_limit() failures.
*/
if (vt->flags & KMEM_CACHE_UNAVAIL) {
FREEBUF(cache_buf);
hq_close();
return;
}
if (tmp2 > max_cpus)
max_cpus = tmp2;
cache = ULONG(cache_buf + next_offset);
switch (vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2))
{
case PERCPU_KMALLOC_V1:
cache -= next_offset;
break;
case PERCPU_KMALLOC_V2:
if (cache != cache_end)
cache -= next_offset;
break;
}
} while (cache != cache_end);
hq_close();
FREEBUF(cache_buf);
vt->kmem_max_c_num = max_cnum;
vt->kmem_max_limit = max_limit;
vt->kmem_max_cpus = max_cpus;
vt->kmem_cache_count = cache_count;
if (CRASHDEBUG(2)) {
fprintf(fp, "kmem_cache_init:\n");
fprintf(fp, " kmem_max_c_num: %ld\n", vt->kmem_max_c_num);
fprintf(fp, " kmem_max_limit: %ld\n", vt->kmem_max_limit);
fprintf(fp, " kmem_max_cpus: %ld\n", vt->kmem_max_cpus);
fprintf(fp, " kmem_cache_count: %ld\n", vt->kmem_cache_count);
}
if (!(vt->flags & KMEM_CACHE_INIT)) {
if (vt->flags & PERCPU_KMALLOC_V1)
ARRAY_LENGTH_INIT(vt->kmem_cache_namelen,
kmem_cache_s_name, "kmem_cache_s.name",
NULL, sizeof(char));
else if (vt->flags & PERCPU_KMALLOC_V2)
vt->kmem_cache_namelen = 0;
else
ARRAY_LENGTH_INIT(vt->kmem_cache_namelen,
kmem_cache_s_c_name, "kmem_cache_s.c_name",
NULL, 0);
}
please_wait_done();
vt->flags |= KMEM_CACHE_INIT;
}
static ulong
kmem_cache_nodelists(ulong cache)
{
ulong nodelists = 0;
if (vt->flags & NODELISTS_IS_PTR) {
/*
* nodelists is pointer to the array
*/
if (!readmem(cache+OFFSET(kmem_cache_s_lists), KVADDR,
&nodelists, sizeof(ulong), "nodelists pointer",
RETURN_ON_ERROR))
error(WARNING, "cannot read kmem_cache nodelists pointer");
return nodelists;
} else
return cache+OFFSET(kmem_cache_s_lists);
}
static void
kmem_cache_downsize(void)
{
char *cache_buf;
ulong kmem_cache;
uint buffer_size, object_size;
int nr_node_ids;
int nr_cpu_ids;
if (vt->flags & KMALLOC_SLUB) {
if (kernel_symbol_exists("kmem_cache") &&
VALID_MEMBER(kmem_cache_objsize) &&
try_get_symbol_data("kmem_cache",
sizeof(ulong), &kmem_cache) &&
readmem(kmem_cache + OFFSET(kmem_cache_objsize),
KVADDR, &object_size, sizeof(int),
"kmem_cache objsize/object_size", RETURN_ON_ERROR)) {
ASSIGN_SIZE(kmem_cache) = object_size;
if (CRASHDEBUG(1))
fprintf(fp, "\nkmem_cache_downsize: %ld to %ld\n",
STRUCT_SIZE("kmem_cache"),
SIZE(kmem_cache));
}
return;
}
if ((THIS_KERNEL_VERSION < LINUX(2,6,22)) ||
!(vt->flags & PERCPU_KMALLOC_V2_NODES) ||
(!kernel_symbol_exists("cache_cache") &&
!kernel_symbol_exists("kmem_cache_boot")) ||
(!MEMBER_EXISTS("kmem_cache", "buffer_size") &&
!MEMBER_EXISTS("kmem_cache", "size"))) {
return;
}
if (vt->flags & NODELISTS_IS_PTR) {
/*
* More recent kernels have kmem_cache.array[] sized
* by the number of cpus plus the number of nodes.
*/
if (kernel_symbol_exists("kmem_cache_boot") &&
MEMBER_EXISTS("kmem_cache", "object_size") &&
readmem(symbol_value("kmem_cache_boot") +
MEMBER_OFFSET("kmem_cache", "object_size"),
KVADDR, &object_size, sizeof(int),
"kmem_cache_boot object_size", RETURN_ON_ERROR))
ASSIGN_SIZE(kmem_cache_s) = object_size;
else if (kernel_symbol_exists("cache_cache") &&
MEMBER_EXISTS("kmem_cache", "object_size") &&
readmem(symbol_value("cache_cache") +
MEMBER_OFFSET("kmem_cache", "object_size"),
KVADDR, &object_size, sizeof(int),
"cache_cache object_size", RETURN_ON_ERROR))
ASSIGN_SIZE(kmem_cache_s) = object_size;
else
object_size = 0;
/*
* Older kernels have kmem_cache.array[] sized by
* the number of cpus; real value is nr_cpu_ids,
* but fallback is kt->cpus.
*/
if (kernel_symbol_exists("nr_cpu_ids"))
get_symbol_data("nr_cpu_ids", sizeof(int),
&nr_cpu_ids);
else
nr_cpu_ids = kt->cpus;
ARRAY_LENGTH(kmem_cache_s_array) = nr_cpu_ids;
if (!object_size)
ASSIGN_SIZE(kmem_cache_s) = OFFSET(kmem_cache_s_array) +
sizeof(ulong) * nr_cpu_ids;
if (CRASHDEBUG(1))
fprintf(fp, "\nkmem_cache_downsize: %ld to %ld\n",
STRUCT_SIZE("kmem_cache"), SIZE(kmem_cache_s));
return;
}
cache_buf = GETBUF(SIZE(kmem_cache_s));
if (!readmem(symbol_value("cache_cache"), KVADDR, cache_buf,
SIZE(kmem_cache_s), "kmem_cache buffer", RETURN_ON_ERROR)) {
FREEBUF(cache_buf);
return;
}
buffer_size = UINT(cache_buf +
MEMBER_OFFSET("kmem_cache", "buffer_size"));
if (buffer_size < SIZE(kmem_cache_s)) {
if (kernel_symbol_exists("nr_node_ids")) {
get_symbol_data("nr_node_ids", sizeof(int),
&nr_node_ids);
vt->kmem_cache_len_nodes = nr_node_ids;
} else
vt->kmem_cache_len_nodes = 1;
if (buffer_size >= (uint)(OFFSET(kmem_cache_s_lists) +
(sizeof(void *) * vt->kmem_cache_len_nodes)))
ASSIGN_SIZE(kmem_cache_s) = buffer_size;
else
error(WARNING,
"questionable cache_cache.buffer_size: %d\n",
buffer_size);
if (CRASHDEBUG(1)) {
fprintf(fp,
"\nkmem_cache_downsize: %ld to %d\n",
STRUCT_SIZE("kmem_cache"), buffer_size);
fprintf(fp,
"kmem_cache_downsize: nr_node_ids: %ld\n",
vt->kmem_cache_len_nodes);
}
}
FREEBUF(cache_buf);
}
/*
* Stash a list of presumably-corrupted slab cache addresses.
*/
static void
mark_bad_slab_cache(ulong cache)
{
size_t sz;
if (vt->nr_bad_slab_caches) {
sz = sizeof(ulong) * (vt->nr_bad_slab_caches + 1);
if (!(vt->bad_slab_caches = realloc(vt->bad_slab_caches, sz))) {
error(INFO, "cannot realloc bad_slab_caches array\n");
vt->nr_bad_slab_caches = 0;
return;
}
} else {
if (!(vt->bad_slab_caches = (ulong *)malloc(sizeof(ulong)))) {
error(INFO, "cannot malloc bad_slab_caches array\n");
return;
}
}
vt->bad_slab_caches[vt->nr_bad_slab_caches++] = cache;
}
static int
bad_slab_cache(ulong cache)
{
int i;
for (i = 0; i < vt->nr_bad_slab_caches; i++) {
if (vt->bad_slab_caches[i] == cache)
return TRUE;
}
return FALSE;
}
/*
* Determine the largest cpudata limit for a given cache.
*/
static ulong
max_cpudata_limit(ulong cache, ulong *cpus)
{
int i;
ulong cpudata[NR_CPUS];
int limit;
ulong max_limit;
ulong shared;
ulong *start_address;
if (vt->flags & PERCPU_KMALLOC_V2_NODES)
goto kmem_cache_s_array_nodes;
if (vt->flags & PERCPU_KMALLOC_V2)
goto kmem_cache_s_array;
if (INVALID_MEMBER(kmem_cache_s_cpudata)) {
*cpus = 0;
return 0;
}
if (!readmem(cache+OFFSET(kmem_cache_s_cpudata),
KVADDR, &cpudata[0],
sizeof(ulong) * ARRAY_LENGTH(kmem_cache_s_cpudata),
"cpudata array", RETURN_ON_ERROR))
goto bail_out;
for (i = max_limit = 0; (i < ARRAY_LENGTH(kmem_cache_s_cpudata)) &&
cpudata[i]; i++) {
if (!readmem(cpudata[i]+OFFSET(cpucache_s_limit),
KVADDR, &limit, sizeof(int),
"cpucache limit", RETURN_ON_ERROR))
goto bail_out;
if (limit > max_limit)
max_limit = limit;
}
*cpus = i;
return max_limit;
kmem_cache_s_array:
if (!readmem(cache+OFFSET(kmem_cache_s_array),
KVADDR, &cpudata[0],
sizeof(ulong) * ARRAY_LENGTH(kmem_cache_s_array),
"array cache array", RETURN_ON_ERROR))
goto bail_out;
for (i = max_limit = 0; (i < ARRAY_LENGTH(kmem_cache_s_array)) &&
cpudata[i]; i++) {
if (!readmem(cpudata[i]+OFFSET(array_cache_limit),
KVADDR, &limit, sizeof(int),
"array cache limit", RETURN_ON_ERROR))
goto bail_out;
if (limit > max_limit)
max_limit = limit;
}
/*
* If the shared list can be accessed, check its size as well.
*/
if (VALID_MEMBER(kmem_list3_shared) &&
VALID_MEMBER(kmem_cache_s_lists) &&
readmem(cache+OFFSET(kmem_cache_s_lists)+OFFSET(kmem_list3_shared),
KVADDR, &shared, sizeof(void *), "kmem_list3 shared",
RETURN_ON_ERROR|QUIET) &&
readmem(shared+OFFSET(array_cache_limit),
KVADDR, &limit, sizeof(int), "shared array_cache limit",
RETURN_ON_ERROR|QUIET)) {
if (limit > max_limit)
max_limit = limit;
}
*cpus = i;
return max_limit;
kmem_cache_s_array_nodes:
if (CRASHDEBUG(3))
fprintf(fp, "kmem_cache: %lx\n", cache);
if (!readmem(cache+OFFSET(kmem_cache_s_array), KVADDR, &cpudata[0],
sizeof(ulong) * MIN(NR_CPUS, ARRAY_LENGTH(kmem_cache_s_array)),
"array cache array", RETURN_ON_ERROR))
goto bail_out;
for (i = max_limit = 0; i < kt->cpus; i++) {
if (check_offline_cpu(i))
continue;
if (!cpudata[i])
break;
if (!readmem(cpudata[i]+OFFSET(array_cache_limit),
KVADDR, &limit, sizeof(int),
"array cache limit", RETURN_ON_ERROR)) {
error(INFO,
"kmem_cache: %lx: invalid array_cache pointer: %lx\n",
cache, cpudata[i]);
mark_bad_slab_cache(cache);
return max_limit;
}
if (CRASHDEBUG(3))
fprintf(fp, " array limit[%d]: %d\n", i, limit);
if ((unsigned int)limit > INT_MAX)
error(INFO,
"kmem_cache: %lx: invalid array limit[%d]: %d\n",
cache, i, limit);
else if (limit > max_limit)
max_limit = limit;
}
*cpus = i;
/*
* Check the shared list of all the nodes.
*/
start_address = (ulong *)GETBUF(sizeof(ulong) * vt->kmem_cache_len_nodes);
if (VALID_MEMBER(kmem_list3_shared) && VALID_MEMBER(kmem_cache_s_lists) &&
readmem(kmem_cache_nodelists(cache), KVADDR, &start_address[0],
sizeof(ulong) * vt->kmem_cache_len_nodes, "array nodelist array",
RETURN_ON_ERROR)) {
for (i = 0; i < vt->kmem_cache_len_nodes; i++) {
if (start_address[i] == 0)
continue;
if (readmem(start_address[i] + OFFSET(kmem_list3_shared),
KVADDR, &shared, sizeof(void *),
"kmem_list3 shared", RETURN_ON_ERROR|QUIET)) {
if (!shared)
break;
} else
continue;
if (readmem(shared + OFFSET(array_cache_limit),
KVADDR, &limit, sizeof(int), "shared array_cache limit",
RETURN_ON_ERROR|QUIET)) {
if (CRASHDEBUG(3))
fprintf(fp,
" shared node limit[%d]: %d\n",
i, limit);
if ((unsigned int)limit > INT_MAX)
error(INFO,
"kmem_cache: %lx: shared node limit[%d]: %d\n",
cache, i, limit);
else if (limit > max_limit)
max_limit = limit;
break;
}
}
}
FREEBUF(start_address);
return max_limit;
bail_out:
vt->flags |= KMEM_CACHE_UNAVAIL;
error(INFO, "unable to initialize kmem slab cache subsystem\n\n");
*cpus = 0;
return 0;
}
/*
* Determine whether the current slab cache is contained in
* the comma-separated list from a "kmem -I list1,list2 ..."
* command entry.
*/
static int
ignore_cache(struct meminfo *si, char *name)
{
int i, argc;
char *p1;
char *arglist[MAXARGS];
char buf[BUFSIZE];
if (!si->ignore)
return FALSE;
strcpy(buf, si->ignore);
p1 = buf;
while (*p1) {
if (*p1 == ',')
*p1 = ' ';
p1++;
}
argc = parse_line(buf, arglist);
for (i = 0; i < argc; i++) {
if (STREQ(name, arglist[i]))
return TRUE;
}
return FALSE;
}
/*
* dump_kmem_cache() displays basic information about kmalloc() slabs.
* At this point, only kmem_cache_s structure data for each slab is dumped.
*
* TBD: Given a specified physical address, and determine which slab it came
* from, and whether it's in use or not.
*/
#define SLAB_C_MAGIC 0x4F17A36DUL
#define SLAB_MAGIC_ALLOC 0xA5C32F2BUL /* slab is alive */
#define SLAB_MAGIC_DESTROYED 0xB2F23C5AUL /* slab has been destroyed */
#define SLAB_CFLGS_BUFCTL 0x020000UL /* bufctls in own cache */
#define KMEM_SLAB_ADDR (1)
#define KMEM_BUFCTL_ADDR (2)
#define KMEM_OBJECT_ADDR_FREE (3)
#define KMEM_OBJECT_ADDR_INUSE (4)
#define KMEM_OBJECT_ADDR_CACHED (5)
#define KMEM_ON_SLAB (6)
#define KMEM_OBJECT_ADDR_SHARED (7)
#define KMEM_SLAB_OVERLOAD_PAGE (8)
#define KMEM_SLAB_FREELIST (9)
#define DUMP_KMEM_CACHE_INFO_V1() \
{ \
char b1[BUFSIZE]; \
fprintf(fp, "%s %-18s %8ld ", \
mkstring(b1, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(si->cache)), \
buf, si->size); \
fprintf(fp, "%9ld %8ld %5ld %3ldk\n", \
vt->flags & PERCPU_KMALLOC_V1 ? \
si->inuse - si->cpucached_cache : \
si->inuse, si->num_slabs * si->c_num, \
si->num_slabs, si->slabsize/1024); \
}
#define DUMP_KMEM_CACHE_INFO_V2() dump_kmem_cache_info_v2(si)
static void
dump_kmem_cache_info_v2(struct meminfo *si)
{
char b1[BUFSIZE];
char b2[BUFSIZE];
int namelen, sizelen, spacelen;
fprintf(fp, "%s ",
mkstring(b1, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(si->cache)));
namelen = strlen(si->curname);
sprintf(b2, "%ld", si->size);
sizelen = strlen(b2);
spacelen = 0;
if (namelen++ > 18) {
spacelen = 29 - namelen - sizelen;
fprintf(fp, "%s%s%ld ", si->curname,
space(spacelen <= 0 ? 1 : spacelen), si->size);
if (spacelen > 0)
spacelen = 1;
sprintf(b1, "%c%dld ", '%', 9 + spacelen - 1);
} else {
fprintf(fp, "%-18s %8ld ", si->curname, si->size);
sprintf(b1, "%c%dld ", '%', 9);
}
fprintf(fp, b1, vt->flags & (PERCPU_KMALLOC_V2) ?
si->inuse - si->cpucached_cache : si->inuse);
fprintf(fp, "%8ld %s%5ld %s%3ldk\n",
si->num_slabs * si->c_num,
si->num_slabs < 100000 ? " " : "", si->num_slabs,
(si->slabsize/1024) < 1000 ? " " : "", si->slabsize/1024);
}
#define DUMP_SLAB_INFO() \
{ \
char b1[BUFSIZE], b2[BUFSIZE]; \
ulong allocated, freeobjs, slab; \
if (vt->flags & SLAB_OVERLOAD_PAGE) \
slab = si->slab - OFFSET(page_lru); \
else \
slab = si->slab; \
if (vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2)) { \
allocated = si->s_inuse - si->cpucached_slab; \
freeobjs = si->c_num - allocated - si->cpucached_slab; \
} else { \
allocated = si->s_inuse; \
freeobjs = si->c_num - si->s_inuse; \
} \
fprintf(fp, "%s %s %5ld %9ld %4ld\n", \
mkstring(b1, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(slab)), \
mkstring(b2, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(si->s_mem)), \
si->c_num, allocated, \
vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2) ? \
freeobjs + si->cpucached_slab : freeobjs); \
}
static void
dump_kmem_cache(struct meminfo *si)
{
char buf[BUFSIZE];
char kbuf[BUFSIZE];
char *reqname;
ulong cache_cache;
ulong name, magic;
int cnt;
char *p1;
if (vt->flags & (PERCPU_KMALLOC_V1|PERCPU_KMALLOC_V2))
error(FATAL,
"dump_kmem_cache called with PERCPU_KMALLOC_V[12] set\n");
si->found = si->retval = 0;
reqname = NULL;
if ((!(si->flags & VERBOSE) || si->reqname) &&
!(si->flags & (ADDRESS_SPECIFIED|GET_SLAB_PAGES)))
fprintf(fp, "%s", kmem_cache_hdr);
si->addrlist = (ulong *)GETBUF((vt->kmem_max_c_num+1) * sizeof(ulong));
cnt = 0;
if (si->flags & CACHE_SET) {
readmem(si->cache+OFFSET(kmem_cache_s_c_nextp),
KVADDR, &cache_cache, sizeof(ulong),
"kmem_cache next", FAULT_ON_ERROR);
} else
si->cache = cache_cache = symbol_value("cache_cache");
if (si->flags & ADDRESS_SPECIFIED) {
if (!(p1 = vaddr_to_kmem_cache(si->spec_addr, kbuf, VERBOSE))) {
error(INFO,
"address is not allocated in slab subsystem: %lx\n",
si->spec_addr);
return;
}
if (si->reqname && (si->reqname != p1))
error(INFO,
"ignoring pre-selected %s cache for address: %lx\n",
si->reqname, si->spec_addr, si->reqname);
reqname = p1;
} else
reqname = si->reqname;
si->cache_buf = GETBUF(SIZE(kmem_cache_s));
do {
if ((si->flags & VERBOSE) && !si->reqname &&
!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, "%s%s", cnt++ ? "\n" : "", kmem_cache_hdr);
readmem(si->cache, KVADDR, si->cache_buf, SIZE(kmem_cache_s),
"kmem_cache buffer", FAULT_ON_ERROR);
if (vt->kmem_cache_namelen) {
BCOPY(si->cache_buf + OFFSET(kmem_cache_s_c_name),
buf, vt->kmem_cache_namelen);
} else {
name = ULONG(si->cache_buf +
OFFSET(kmem_cache_s_c_name));
if (!read_string(name, buf, BUFSIZE-1)) {
error(WARNING,
"cannot read kmem_cache_s.c_name string at %lx\n",
name);
sprintf(buf, "(unknown)");
}
}
if (reqname && !STREQ(reqname, buf))
goto next_cache;
if (ignore_cache(si, buf)) {
fprintf(fp, "%lx %-18s [IGNORED]\n", si->cache, buf);
goto next_cache;
}
si->curname = buf;
if (CRASHDEBUG(1))
fprintf(fp, "cache: %lx %s\n", si->cache, si->curname);
console("cache: %lx %s\n", si->cache, si->curname);
magic = ULONG(si->cache_buf + OFFSET(kmem_cache_s_c_magic));
if (magic == SLAB_C_MAGIC) {
si->size = ULONG(si->cache_buf +
OFFSET(kmem_cache_s_c_org_size));
if (!si->size) {
if (STREQ(si->curname, "kmem_cache"))
si->size = SIZE(kmem_cache_s);
else {
error(INFO,
"\"%s\" cache: c_org_size: %ld\n",
si->curname, si->size);
si->errors++;
}
}
si->c_flags = ULONG(si->cache_buf +
OFFSET(kmem_cache_s_c_flags));
si->c_offset = ULONG(si->cache_buf +
OFFSET(kmem_cache_s_c_offset));
si->order = ULONG(si->cache_buf +
OFFSET(kmem_cache_s_c_gfporder));
si->c_num = ULONG(si->cache_buf +
OFFSET(kmem_cache_s_c_num));
do_slab_chain(SLAB_GET_COUNTS, si);
if (!(si->flags & (ADDRESS_SPECIFIED|GET_SLAB_PAGES)))
DUMP_KMEM_CACHE_INFO_V1();
if (si->flags == GET_SLAB_PAGES)
si->retval += (si->num_slabs *
(si->slabsize/PAGESIZE()));
if (si->flags & (VERBOSE|ADDRESS_SPECIFIED)) {
si->slab = (si->flags & ADDRESS_SPECIFIED) ?
vaddr_to_slab(si->spec_addr) : 0;
do_slab_chain(SLAB_WALKTHROUGH, si);
if (si->found) {
fprintf(fp, "%s", kmem_cache_hdr);
DUMP_KMEM_CACHE_INFO_V1();
fprintf(fp, "%s", slab_hdr);
DUMP_SLAB_INFO();
switch (si->found)
{
case KMEM_BUFCTL_ADDR:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp,
"(ON-SLAB kmem_bufctl_t)\n");
break;
case KMEM_SLAB_ADDR:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp,
"(ON-SLAB kmem_slab_t)\n");
break;
case KMEM_ON_SLAB:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp,
"(unused part of slab)\n");
break;
case KMEM_OBJECT_ADDR_FREE:
fprintf(fp, "%s",
free_inuse_hdr);
fprintf(fp, " %lx\n",
si->container ? si->container :
(ulong)si->spec_addr);
break;
case KMEM_OBJECT_ADDR_INUSE:
fprintf(fp, "%s",
free_inuse_hdr);
fprintf(fp, " [%lx]\n",
si->container ? si->container :
(ulong)si->spec_addr);
break;
}
break;
}
}
} else {
error(INFO, "\"%s\" cache: invalid c_magic: %lx\n",
si->curname, magic);
si->errors++;
}
next_cache:
si->cache = ULONG(si->cache_buf + OFFSET(kmem_cache_s_c_nextp));
} while (si->cache != cache_cache);
FREEBUF(si->cache_buf);
if ((si->flags & ADDRESS_SPECIFIED) && !si->found)
error(INFO, "%s: address not found in cache: %lx\n",
reqname, si->spec_addr);
if (si->errors)
error(INFO, "%ld error%s encountered\n",
si->errors, si->errors > 1 ? "s" : "");
FREEBUF(si->addrlist);
}
/*
* dump_kmem_cache() adapted for newer percpu slab format.
*/
static void
dump_kmem_cache_percpu_v1(struct meminfo *si)
{
int i;
char buf[BUFSIZE];
char kbuf[BUFSIZE];
char *reqname;
ulong cache_cache;
ulong name;
int cnt;
uint tmp_val; /* Used as temporary variable to read sizeof(int) and
assigned to ulong variable. We are doing this to mask
the endian issue */
char *p1;
if (!(vt->flags & PERCPU_KMALLOC_V1))
error(FATAL,
"dump_kmem_cache_percpu called without PERCPU_KMALLOC_V1\n");
si->found = si->retval = 0;
reqname = NULL;
if ((!(si->flags & VERBOSE) || si->reqname) &&
!(si->flags & (ADDRESS_SPECIFIED|GET_SLAB_PAGES)))
fprintf(fp, "%s", kmem_cache_hdr);
si->addrlist = (ulong *)GETBUF((vt->kmem_max_c_num+1) * sizeof(ulong));
si->kmem_bufctl = (int *)GETBUF((vt->kmem_max_c_num+1) * sizeof(int));
for (i = 0; i < vt->kmem_max_cpus; i++)
si->cpudata[i] = (ulong *)
GETBUF(vt->kmem_max_limit * sizeof(ulong));
cnt = 0;
if (si->flags & CACHE_SET) {
readmem(si->cache+OFFSET(kmem_cache_s_next),
KVADDR, &cache_cache, sizeof(ulong),
"kmem_cache_s next", FAULT_ON_ERROR);
} else
si->cache = cache_cache = symbol_value("cache_cache");
if (si->flags & ADDRESS_SPECIFIED) {
if (!(p1 = vaddr_to_kmem_cache(si->spec_addr, kbuf, VERBOSE))) {
error(INFO,
"address is not allocated in slab subsystem: %lx\n",
si->spec_addr);
return;
}
if (si->reqname && (si->reqname != p1))
error(INFO,
"ignoring pre-selected %s cache for address: %lx\n",
si->reqname, si->spec_addr, si->reqname);
reqname = p1;
} else
reqname = si->reqname;
do {
if ((si->flags & VERBOSE) && !si->reqname &&
!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, "%s%s", cnt++ ? "\n" : "", kmem_cache_hdr);
if (vt->kmem_cache_namelen) {
readmem(si->cache+OFFSET(kmem_cache_s_name),
KVADDR, buf, vt->kmem_cache_namelen,
"name array", FAULT_ON_ERROR);
} else {
readmem(si->cache+OFFSET(kmem_cache_s_name),
KVADDR, &name, sizeof(ulong),
"name", FAULT_ON_ERROR);
if (!read_string(name, buf, BUFSIZE-1)) {
error(WARNING,
"cannot read kmem_cache_s.name string at %lx\n",
name);
sprintf(buf, "(unknown)");
}
}
if (reqname && !STREQ(reqname, buf))
goto next_cache;
if (ignore_cache(si, buf)) {
fprintf(fp, "%lx %-18s [IGNORED]\n", si->cache, buf);
goto next_cache;
}
si->curname = buf;
readmem(si->cache+OFFSET(kmem_cache_s_objsize),
KVADDR, &tmp_val, sizeof(uint),
"objsize", FAULT_ON_ERROR);
si->size = (ulong)tmp_val;
if (!si->size) {
if (STREQ(si->curname, "kmem_cache"))
si->size = SIZE(kmem_cache_s);
else {
error(INFO, "\"%s\" cache: objsize: %ld\n",
si->curname, si->size);
si->errors++;
}
}
readmem(si->cache+OFFSET(kmem_cache_s_flags),
KVADDR, &tmp_val, sizeof(uint),
"kmem_cache_s flags", FAULT_ON_ERROR);
si->c_flags = (ulong)tmp_val;
readmem(si->cache+OFFSET(kmem_cache_s_gfporder),
KVADDR, &tmp_val, sizeof(uint),
"gfporder", FAULT_ON_ERROR);
si->order = (ulong)tmp_val;
readmem(si->cache+OFFSET(kmem_cache_s_num),
KVADDR, &tmp_val, sizeof(uint),
"kmem_cache_s num", FAULT_ON_ERROR);
si->c_num = (ulong)tmp_val;
do_slab_chain_percpu_v1(SLAB_GET_COUNTS, si);
if (!(si->flags & (ADDRESS_SPECIFIED|GET_SLAB_PAGES))) {
DUMP_KMEM_CACHE_INFO_V1();
if (CRASHDEBUG(3))
dump_struct("kmem_cache_s", si->cache, 0);
}
if (si->flags == GET_SLAB_PAGES)
si->retval += (si->num_slabs *
(si->slabsize/PAGESIZE()));
if (si->flags & (VERBOSE|ADDRESS_SPECIFIED)) {
gather_cpudata_list_v1(si);
si->slab = (si->flags & ADDRESS_SPECIFIED) ?
vaddr_to_slab(si->spec_addr) : 0;
do_slab_chain_percpu_v1(SLAB_WALKTHROUGH, si);
if (si->found) {
fprintf(fp, "%s", kmem_cache_hdr);
DUMP_KMEM_CACHE_INFO_V1();
fprintf(fp, "%s", slab_hdr);
gather_slab_cached_count(si);
DUMP_SLAB_INFO();
switch (si->found)
{
case KMEM_BUFCTL_ADDR:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp,"(kmem_bufctl_t)\n");
break;
case KMEM_SLAB_ADDR:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp, "(slab_s)\n");
break;
case KMEM_ON_SLAB:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp, "(unused part of slab)\n");
break;
case KMEM_OBJECT_ADDR_FREE:
fprintf(fp, "%s", free_inuse_hdr);
fprintf(fp, " %lx\n",
si->container ? si->container :
(ulong)si->spec_addr);
break;
case KMEM_OBJECT_ADDR_INUSE:
fprintf(fp, "%s", free_inuse_hdr);
fprintf(fp, " [%lx]\n",
si->container ? si->container :
(ulong)si->spec_addr);
break;
case KMEM_OBJECT_ADDR_CACHED:
fprintf(fp, "%s", free_inuse_hdr);
fprintf(fp,
" %lx (cpu %d cache)\n",
si->container ? si->container :
(ulong)si->spec_addr, si->cpu);
break;
}
break;
}
}
next_cache:
readmem(si->cache+OFFSET(kmem_cache_s_next),
KVADDR, &si->cache, sizeof(ulong),
"kmem_cache_s next", FAULT_ON_ERROR);
si->cache -= OFFSET(kmem_cache_s_next);
} while (si->cache != cache_cache);
if ((si->flags & ADDRESS_SPECIFIED) && !si->found)
error(INFO, "%s: address not found in cache: %lx\n",
reqname, si->spec_addr);
if (si->errors)
error(INFO, "%ld error%s encountered\n",
si->errors, si->errors > 1 ? "s" : "");
FREEBUF(si->addrlist);
FREEBUF(si->kmem_bufctl);
for (i = 0; i < vt->kmem_max_cpus; i++)
FREEBUF(si->cpudata[i]);
}
/*
* Updated for 2.6 slab substructure.
*/
static void
dump_kmem_cache_percpu_v2(struct meminfo *si)
{
int i;
char buf[BUFSIZE];
char kbuf[BUFSIZE];
char *reqname;
ulong cache_end;
ulong name, page_head;
int cnt;
uint tmp_val; /* Used as temporary variable to read sizeof(int) and
assigned to ulong variable. We are doing this to mask
the endian issue */
char *p1;
if (!(vt->flags & PERCPU_KMALLOC_V2))
error(FATAL,
"dump_kmem_cache_percpu called without PERCPU_KMALLOC_V2\n");
si->found = si->retval = 0;
reqname = NULL;
if ((!(si->flags & VERBOSE) || si->reqname) &&
!(si->flags & (ADDRESS_SPECIFIED|GET_SLAB_PAGES)))
fprintf(fp, "%s", kmem_cache_hdr);
si->addrlist = (ulong *)GETBUF((vt->kmem_max_c_num+1) * sizeof(ulong));
si->kmem_bufctl = (int *)GETBUF((vt->kmem_max_c_num+1) * sizeof(int));
if (vt->flags & SLAB_OVERLOAD_PAGE) {
si->freelist = si->kmem_bufctl;
si->freelist_index_size = slab_freelist_index_size();
}
for (i = 0; i < vt->kmem_max_cpus; i++)
si->cpudata[i] = (ulong *)
GETBUF(vt->kmem_max_limit * sizeof(ulong));
if(vt->flags & PERCPU_KMALLOC_V2_NODES)
si->shared_array_cache = (ulong *)
GETBUF(vt->kmem_cache_len_nodes *
(vt->kmem_max_limit+1) * sizeof(ulong));
else
si->shared_array_cache = (ulong *)
GETBUF((vt->kmem_max_limit+1) * sizeof(ulong));
cnt = 0;
if (si->flags & CACHE_SET)
readmem(si->cache+OFFSET(kmem_cache_s_next),
KVADDR, &cache_end, sizeof(ulong),
"kmem_cache_s next", FAULT_ON_ERROR);
else {
if (vt->flags & KMALLOC_COMMON) {
get_symbol_data("slab_caches", sizeof(ulong), &si->cache);
si->cache -= OFFSET(kmem_cache_s_next);
cache_end = symbol_value("slab_caches");
} else {
get_symbol_data("cache_chain", sizeof(ulong), &si->cache);
si->cache -= OFFSET(kmem_cache_s_next);
cache_end = symbol_value("cache_chain");
}
}
if (si->flags & ADDRESS_SPECIFIED) {
if ((p1 = is_slab_overload_page(si->spec_addr, &page_head, kbuf))) {
si->flags |= SLAB_OVERLOAD_PAGE_PTR;
si->spec_addr = page_head;
} else if (!(p1 = vaddr_to_kmem_cache(si->spec_addr, kbuf, VERBOSE))) {
error(INFO,
"address is not allocated in slab subsystem: %lx\n",
si->spec_addr);
return;
}
if (si->reqname && (si->reqname != p1))
error(INFO,
"ignoring pre-selected %s cache for address: %lx\n",
si->reqname, si->spec_addr, si->reqname);
reqname = p1;
} else
reqname = si->reqname;
do {
if ((si->flags & VERBOSE) && !si->reqname &&
!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, "%s%s", cnt++ ? "\n" : "", kmem_cache_hdr);
if (vt->kmem_cache_namelen) {
readmem(si->cache+OFFSET(kmem_cache_s_name),
KVADDR, buf, vt->kmem_cache_namelen,
"name array", FAULT_ON_ERROR);
} else {
readmem(si->cache+OFFSET(kmem_cache_s_name),
KVADDR, &name, sizeof(ulong),
"name", FAULT_ON_ERROR);
if (!read_string(name, buf, BUFSIZE-1)) {
error(WARNING,
"cannot read kmem_cache_s.name string at %lx\n",
name);
sprintf(buf, "(unknown)");
}
}
if (reqname && !STREQ(reqname, buf))
goto next_cache;
if (ignore_cache(si, buf)) {
fprintf(fp, "%lx %-18s [IGNORED]\n", si->cache, buf);
goto next_cache;
}
if (bad_slab_cache(si->cache)) {
fprintf(fp, "%lx %-18s [INVALID/CORRUPTED]\n", si->cache, buf);
goto next_cache;
}
si->curname = buf;
readmem(si->cache+OFFSET(kmem_cache_s_objsize),
KVADDR, &tmp_val, sizeof(uint),
"objsize", FAULT_ON_ERROR);
si->size = (ulong)tmp_val;
if (!si->size) {
if (STREQ(si->curname, "kmem_cache"))
si->size = SIZE(kmem_cache_s);
else {
error(INFO, "\"%s\" cache: objsize: %ld\n",
si->curname, si->size);
si->errors++;
}
}
readmem(si->cache+OFFSET(kmem_cache_s_flags),
KVADDR, &tmp_val, sizeof(uint),
"kmem_cache_s flags", FAULT_ON_ERROR);
si->c_flags = (ulong)tmp_val;
readmem(si->cache+OFFSET(kmem_cache_s_gfporder),
KVADDR, &tmp_val, sizeof(uint),
"gfporder", FAULT_ON_ERROR);
si->order = (ulong)tmp_val;
readmem(si->cache+OFFSET(kmem_cache_s_num),
KVADDR, &tmp_val, sizeof(uint),
"kmem_cache_s num", FAULT_ON_ERROR);
si->c_num = (ulong)tmp_val;
if (vt->flags & PERCPU_KMALLOC_V2_NODES) {
if (vt->flags & SLAB_OVERLOAD_PAGE)
do_slab_chain_slab_overload_page(SLAB_GET_COUNTS, si);
else
do_slab_chain_percpu_v2_nodes(SLAB_GET_COUNTS, si);
} else
do_slab_chain_percpu_v2(SLAB_GET_COUNTS, si);
if (!(si->flags & (ADDRESS_SPECIFIED|GET_SLAB_PAGES))) {
DUMP_KMEM_CACHE_INFO_V2();
if (CRASHDEBUG(3))
dump_struct("kmem_cache_s", si->cache, 0);
}
if (si->flags == GET_SLAB_PAGES)
si->retval += (si->num_slabs *
(si->slabsize/PAGESIZE()));
if (si->flags & (VERBOSE|ADDRESS_SPECIFIED)) {
if (!(vt->flags & PERCPU_KMALLOC_V2_NODES))
gather_cpudata_list_v2(si);
si->slab = (si->flags & ADDRESS_SPECIFIED) ?
vaddr_to_slab(si->spec_addr) : 0;
if (vt->flags & PERCPU_KMALLOC_V2_NODES) {
if (vt->flags & SLAB_OVERLOAD_PAGE)
do_slab_chain_slab_overload_page(SLAB_WALKTHROUGH, si);
else
do_slab_chain_percpu_v2_nodes(SLAB_WALKTHROUGH, si);
} else
do_slab_chain_percpu_v2(SLAB_WALKTHROUGH, si);
if (si->found) {
fprintf(fp, "%s", kmem_cache_hdr);
DUMP_KMEM_CACHE_INFO_V2();
fprintf(fp, "%s", slab_hdr);
gather_slab_cached_count(si);
DUMP_SLAB_INFO();
switch (si->found)
{
case KMEM_BUFCTL_ADDR:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp,"(kmem_bufctl_t)\n");
break;
case KMEM_SLAB_ADDR:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp, "(slab)\n");
break;
case KMEM_ON_SLAB:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp, "(unused part of slab)\n");
break;
case KMEM_SLAB_FREELIST:
fprintf(fp, " %lx ",
(ulong)si->spec_addr);
fprintf(fp, "(on-slab freelist)\n");
break;
case KMEM_SLAB_OVERLOAD_PAGE:
si->flags &= ~ADDRESS_SPECIFIED;
dump_slab_objects_percpu(si);
si->flags |= ADDRESS_SPECIFIED;
break;
case KMEM_OBJECT_ADDR_FREE:
fprintf(fp, "%s", free_inuse_hdr);
fprintf(fp, " %lx\n",
si->container ? si->container :
(ulong)si->spec_addr);
break;
case KMEM_OBJECT_ADDR_INUSE:
fprintf(fp, "%s", free_inuse_hdr);
fprintf(fp, " [%lx]\n",
si->container ? si->container :
(ulong)si->spec_addr);
break;
case KMEM_OBJECT_ADDR_CACHED:
fprintf(fp, "%s", free_inuse_hdr);
fprintf(fp,
" %lx (cpu %d cache)\n",
si->container ? si->container :
(ulong)si->spec_addr, si->cpu);
break;
case KMEM_OBJECT_ADDR_SHARED:
fprintf(fp, "%s", free_inuse_hdr);
fprintf(fp,
" %lx (shared cache)\n",
si->container ? si->container :
(ulong)si->spec_addr);
break;
}
break;
}
}
next_cache:
readmem(si->cache+OFFSET(kmem_cache_s_next),
KVADDR, &si->cache, sizeof(ulong),
"kmem_cache_s next", FAULT_ON_ERROR);
if (si->cache != cache_end)
si->cache -= OFFSET(kmem_cache_s_next);
} while (si->cache != cache_end);
if ((si->flags & ADDRESS_SPECIFIED) && !si->found)
error(INFO, "%s: address not found in cache: %lx\n",
reqname, si->spec_addr);
if (si->errors)
error(INFO, "%ld error%s encountered\n",
si->errors, si->errors > 1 ? "s" : "");
FREEBUF(si->addrlist);
FREEBUF(si->kmem_bufctl);
for (i = 0; i < vt->kmem_max_cpus; i++)
FREEBUF(si->cpudata[i]);
FREEBUF(si->shared_array_cache);
}
/*
* Walk through the slab chain hanging off a kmem_cache_s structure,
* gathering basic statistics.
*
* TBD: Given a specified physical address, determine whether it's in this
* slab chain, and whether it's in use or not.
*/
#define INSLAB(obj, si) \
((ulong)((ulong)(obj) & ~(si->slabsize-1)) == si->s_mem)
static void
do_slab_chain(int cmd, struct meminfo *si)
{
ulong tmp, magic;
ulong kmem_slab_end;
char *kmem_slab_s_buf;
si->slabsize = (power(2, si->order) * PAGESIZE());
kmem_slab_end = si->cache + OFFSET(kmem_cache_s_c_offset);
switch (cmd)
{
case SLAB_GET_COUNTS:
si->slab = ULONG(si->cache_buf + OFFSET(kmem_cache_s_c_firstp));
if (slab_data_saved(si))
return;
si->num_slabs = si->inuse = 0;
if (si->slab == kmem_slab_end)
return;
kmem_slab_s_buf = GETBUF(SIZE(kmem_slab_s));
do {
if (received_SIGINT()) {
FREEBUF(kmem_slab_s_buf);
restart(0);
}
readmem(si->slab, KVADDR, kmem_slab_s_buf,
SIZE(kmem_slab_s), "kmem_slab_s buffer",
FAULT_ON_ERROR);
magic = ULONG(kmem_slab_s_buf +
OFFSET(kmem_slab_s_s_magic));
if (magic == SLAB_MAGIC_ALLOC) {
tmp = ULONG(kmem_slab_s_buf +
OFFSET(kmem_slab_s_s_inuse));
si->inuse += tmp;
si->num_slabs++;
} else {
fprintf(fp,
"\"%s\" cache: invalid s_magic: %lx\n",
si->curname, magic);
si->errors++;
FREEBUF(kmem_slab_s_buf);
return;
}
si->slab = ULONG(kmem_slab_s_buf +
OFFSET(kmem_slab_s_s_nextp));
} while (si->slab != kmem_slab_end);
FREEBUF(kmem_slab_s_buf);
save_slab_data(si);
break;
case SLAB_WALKTHROUGH:
if (!si->slab)
si->slab = ULONG(si->cache_buf +
OFFSET(kmem_cache_s_c_firstp));
if (si->slab == kmem_slab_end)
return;
if (CRASHDEBUG(1)) {
fprintf(fp, "search cache: [%s] ", si->curname);
if (si->flags & ADDRESS_SPECIFIED)
fprintf(fp, "for %llx", si->spec_addr);
fprintf(fp, "\n");
}
si->slab_buf = kmem_slab_s_buf = GETBUF(SIZE(kmem_slab_s));
do {
if (received_SIGINT()) {
FREEBUF(kmem_slab_s_buf);
restart(0);
}
readmem(si->slab, KVADDR, kmem_slab_s_buf,
SIZE(kmem_slab_s), "kmem_slab_s buffer",
FAULT_ON_ERROR);
dump_slab(si);
if (si->found) {
FREEBUF(kmem_slab_s_buf);
return;
}
si->slab = ULONG(kmem_slab_s_buf +
OFFSET(kmem_slab_s_s_nextp));
} while (si->slab != kmem_slab_end);
FREEBUF(kmem_slab_s_buf);
break;
}
}
/*
* do_slab_chain() adapted for newer percpu slab format.
*/
#define SLAB_BASE(X) (PTOB(BTOP(X)))
#define INSLAB_PERCPU(obj, si) \
((ulong)((ulong)(obj) & ~(si->slabsize-1)) == SLAB_BASE(si->s_mem))
#define SLAB_CHAINS (3)
static char *slab_chain_name_v1[] = {"full", "partial", "free"};
static void
do_slab_chain_percpu_v1(long cmd, struct meminfo *si)
{
int i, tmp, s;
int list_borked;
char *slab_s_buf;
ulong specified_slab;
ulong last;
ulong slab_chains[SLAB_CHAINS];
list_borked = 0;
si->slabsize = (power(2, si->order) * PAGESIZE());
si->cpucached_slab = 0;
if (VALID_MEMBER(kmem_cache_s_slabs)) {
slab_chains[0] = si->cache + OFFSET(kmem_cache_s_slabs);
slab_chains[1] = 0;
slab_chains[2] = 0;
} else {
slab_chains[0] = si->cache + OFFSET(kmem_cache_s_slabs_full);
slab_chains[1] = si->cache + OFFSET(kmem_cache_s_slabs_partial);
slab_chains[2] = si->cache + OFFSET(kmem_cache_s_slabs_free);
}
if (CRASHDEBUG(1)) {
fprintf(fp, "[ %s: %lx ", si->curname, si->cache);
fprintf(fp, "full: %lx partial: %lx free: %lx ]\n",
slab_chains[0], slab_chains[1], slab_chains[2]);
}
switch (cmd)
{
case SLAB_GET_COUNTS:
si->flags |= SLAB_GET_COUNTS;
si->flags &= ~SLAB_WALKTHROUGH;
si->cpucached_cache = 0;
si->num_slabs = si->inuse = 0;
gather_cpudata_list_v1(si);
slab_s_buf = GETBUF(SIZE(slab_s));
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"first slab", QUIET|RETURN_ON_ERROR)) {
error(INFO,
"%s: %s list: bad slab pointer: %lx\n",
si->curname, slab_chain_name_v1[s],
slab_chains[s]);
list_borked = 1;
continue;
}
if (slab_data_saved(si)) {
FREEBUF(slab_s_buf);
return;
}
if (si->slab == slab_chains[s])
continue;
last = slab_chains[s];
do {
if (received_SIGINT()) {
FREEBUF(slab_s_buf);
restart(0);
}
if (!verify_slab_v1(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab - OFFSET(slab_s_list);
readmem(si->slab, KVADDR, slab_s_buf,
SIZE(slab_s), "slab_s buffer",
FAULT_ON_ERROR);
tmp = INT(slab_s_buf + OFFSET(slab_s_inuse));
si->inuse += tmp;
if (ACTIVE())
gather_cpudata_list_v1(si);
si->s_mem = ULONG(slab_s_buf +
OFFSET(slab_s_s_mem));
gather_slab_cached_count(si);
si->num_slabs++;
si->slab = ULONG(slab_s_buf +
OFFSET(slab_s_list));
si->slab -= OFFSET(slab_s_list);
/*
* Check for slab transition. (Tony Dziedzic)
*/
for (i = 0; i < SLAB_CHAINS; i++) {
if ((i != s) &&
(si->slab == slab_chains[i])) {
error(NOTE,
"%s: slab chain inconsistency: %s list\n",
si->curname,
slab_chain_name_v1[s]);
list_borked = 1;
}
}
} while (si->slab != slab_chains[s] && !list_borked);
}
FREEBUF(slab_s_buf);
if (!list_borked)
save_slab_data(si);
break;
case SLAB_WALKTHROUGH:
specified_slab = si->slab;
si->flags |= SLAB_WALKTHROUGH;
si->flags &= ~SLAB_GET_COUNTS;
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!specified_slab) {
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"slabs", QUIET|RETURN_ON_ERROR)) {
error(INFO,
"%s: %s list: bad slab pointer: %lx\n",
si->curname,
slab_chain_name_v1[s],
slab_chains[s]);
list_borked = 1;
continue;
}
last = slab_chains[s];
} else
last = 0;
if (si->slab == slab_chains[s])
continue;
if (CRASHDEBUG(1)) {
fprintf(fp, "search cache: [%s] ", si->curname);
if (si->flags & ADDRESS_SPECIFIED)
fprintf(fp, "for %llx", si->spec_addr);
fprintf(fp, "\n");
}
do {
if (received_SIGINT())
restart(0);
if (!verify_slab_v1(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab - OFFSET(slab_s_list);
dump_slab_percpu_v1(si);
if (si->found) {
return;
}
readmem(si->slab+OFFSET(slab_s_list),
KVADDR, &si->slab, sizeof(ulong),
"slab list", FAULT_ON_ERROR);
si->slab -= OFFSET(slab_s_list);
} while (si->slab != slab_chains[s] && !list_borked);
}
break;
}
}
/*
* Try to preclude any attempt to translate a bogus slab structure.
*/
static int
verify_slab_v1(struct meminfo *si, ulong last, int s)
{
char slab_s_buf[BUFSIZE];
struct kernel_list_head *list_head;
unsigned int inuse;
ulong s_mem;
char *list;
int errcnt;
list = slab_chain_name_v1[s];
errcnt = 0;
if (!readmem(si->slab, KVADDR, slab_s_buf,
SIZE(slab_s), "slab_s buffer", QUIET|RETURN_ON_ERROR)) {
error(INFO, "%s: %s list: bad slab pointer: %lx\n",
si->curname, list, si->slab);
return FALSE;
}
list_head = (struct kernel_list_head *)
(slab_s_buf + OFFSET(slab_s_list));
if (!IS_KVADDR((ulong)list_head->next) ||
!accessible((ulong)list_head->next)) {
error(INFO, "%s: %s list: slab: %lx bad next pointer: %lx\n",
si->curname, list, si->slab,
(ulong)list_head->next);
errcnt++;
}
if (last && (last != (ulong)list_head->prev)) {
error(INFO, "%s: %s list: slab: %lx bad prev pointer: %lx\n",
si->curname, list, si->slab,
(ulong)list_head->prev);
errcnt++;
}
inuse = UINT(slab_s_buf + OFFSET(slab_s_inuse));
if (inuse > si->c_num) {
error(INFO, "%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
if (!last)
goto no_inuse_check_v1;
switch (s)
{
case 0: /* full -- but can be one singular list */
if (VALID_MEMBER(kmem_cache_s_slabs_full) &&
(inuse != si->c_num)) {
error(INFO,
"%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
break;
case 1: /* partial */
if ((inuse == 0) || (inuse == si->c_num)) {
error(INFO,
"%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
break;
case 2: /* free */
if (inuse > 0) {
error(INFO,
"%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
break;
}
no_inuse_check_v1:
s_mem = ULONG(slab_s_buf + OFFSET(slab_s_s_mem));
if (!IS_KVADDR(s_mem) || !accessible(s_mem)) {
error(INFO, "%s: %s list: slab: %lx bad s_mem pointer: %lx\n",
si->curname, list, si->slab, s_mem);
errcnt++;
}
si->errors += errcnt;
return(errcnt ? FALSE : TRUE);
}
/*
* Updated for 2.6 slab substructure.
*/
static char *slab_chain_name_v2[] = {"partial", "full", "free"};
static void
do_slab_chain_percpu_v2(long cmd, struct meminfo *si)
{
int i, tmp, s;
int list_borked;
char *slab_buf;
ulong specified_slab;
ulong last;
ulong slab_chains[SLAB_CHAINS];
list_borked = 0;
si->slabsize = (power(2, si->order) * PAGESIZE());
si->cpucached_slab = 0;
slab_chains[0] = si->cache + OFFSET(kmem_cache_s_lists) +
OFFSET(kmem_list3_slabs_partial);
slab_chains[1] = si->cache + OFFSET(kmem_cache_s_lists) +
OFFSET(kmem_list3_slabs_full);
slab_chains[2] = si->cache + OFFSET(kmem_cache_s_lists) +
OFFSET(kmem_list3_slabs_free);
if (CRASHDEBUG(1)) {
fprintf(fp, "[ %s: %lx ", si->curname, si->cache);
fprintf(fp, "partial: %lx full: %lx free: %lx ]\n",
slab_chains[0], slab_chains[1], slab_chains[2]);
}
switch (cmd)
{
case SLAB_GET_COUNTS:
si->flags |= SLAB_GET_COUNTS;
si->flags &= ~SLAB_WALKTHROUGH;
si->cpucached_cache = 0;
si->num_slabs = si->inuse = 0;
gather_cpudata_list_v2(si);
slab_buf = GETBUF(SIZE(slab));
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"first slab", QUIET|RETURN_ON_ERROR)) {
error(INFO,
"%s: %s list: bad slab pointer: %lx\n",
si->curname,
slab_chain_name_v2[s],
slab_chains[s]);
list_borked = 1;
continue;
}
if (slab_data_saved(si)) {
FREEBUF(slab_buf);
return;
}
if (si->slab == slab_chains[s])
continue;
last = slab_chains[s];
do {
if (received_SIGINT()) {
FREEBUF(slab_buf);
restart(0);
}
if (!verify_slab_v2(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab - OFFSET(slab_list);
readmem(si->slab, KVADDR, slab_buf,
SIZE(slab), "slab buffer",
FAULT_ON_ERROR);
tmp = INT(slab_buf + OFFSET(slab_inuse));
si->inuse += tmp;
if (ACTIVE())
gather_cpudata_list_v2(si);
si->s_mem = ULONG(slab_buf +
OFFSET(slab_s_mem));
gather_slab_cached_count(si);
si->num_slabs++;
si->slab = ULONG(slab_buf +
OFFSET(slab_list));
si->slab -= OFFSET(slab_list);
/*
* Check for slab transition. (Tony Dziedzic)
*/
for (i = 0; i < SLAB_CHAINS; i++) {
if ((i != s) &&
(si->slab == slab_chains[i])) {
error(NOTE,
"%s: slab chain inconsistency: %s list\n",
si->curname,
slab_chain_name_v2[s]);
list_borked = 1;
}
}
} while (si->slab != slab_chains[s] && !list_borked);
}
FREEBUF(slab_buf);
if (!list_borked)
save_slab_data(si);
break;
case SLAB_WALKTHROUGH:
specified_slab = si->slab;
si->flags |= SLAB_WALKTHROUGH;
si->flags &= ~SLAB_GET_COUNTS;
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!specified_slab) {
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"slabs", QUIET|RETURN_ON_ERROR)) {
error(INFO,
"%s: %s list: bad slab pointer: %lx\n",
si->curname,
slab_chain_name_v2[s],
slab_chains[s]);
list_borked = 1;
continue;
}
last = slab_chains[s];
} else
last = 0;
if (si->slab == slab_chains[s])
continue;
if (CRASHDEBUG(1)) {
fprintf(fp, "search cache: [%s] ", si->curname);
if (si->flags & ADDRESS_SPECIFIED)
fprintf(fp, "for %llx", si->spec_addr);
fprintf(fp, "\n");
}
do {
if (received_SIGINT())
restart(0);
if (!verify_slab_v2(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab - OFFSET(slab_list);
dump_slab_percpu_v2(si);
if (si->found) {
return;
}
readmem(si->slab+OFFSET(slab_list),
KVADDR, &si->slab, sizeof(ulong),
"slab list", FAULT_ON_ERROR);
si->slab -= OFFSET(slab_list);
} while (si->slab != slab_chains[s] && !list_borked);
}
break;
}
}
/*
* Added To Traverse the Nodelists
*/
static void
do_slab_chain_percpu_v2_nodes(long cmd, struct meminfo *si)
{
int i, tmp, s, node;
int list_borked;
char *slab_buf;
ulong specified_slab;
ulong last;
ulong slab_chains[SLAB_CHAINS];
ulong *start_address;
int index;
list_borked = 0;
slab_buf = NULL;
si->slabsize = (power(2, si->order) * PAGESIZE());
si->cpucached_slab = 0;
start_address = (ulong *)GETBUF(sizeof(ulong) * vt->kmem_cache_len_nodes);
if (!readmem(kmem_cache_nodelists(si->cache), KVADDR,
&start_address[0], sizeof(ulong) * vt->kmem_cache_len_nodes,
"array nodelist array", RETURN_ON_ERROR))
error(INFO, "cannot read kmem_cache nodelists array");
switch (cmd)
{
case SLAB_GET_COUNTS:
si->flags |= (SLAB_GET_COUNTS|SLAB_FIRST_NODE);
si->flags &= ~SLAB_WALKTHROUGH;
si->cpucached_cache = 0;
si->num_slabs = si->inuse = 0;
slab_buf = GETBUF(SIZE(slab));
for (index = 0; (index < vt->kmem_cache_len_nodes); index++)
{
if (vt->flags & NODES_ONLINE) {
node = next_online_node(index);
if (node < 0)
break;
if (node != index)
continue;
}
if (start_address[index] == 0)
continue;
slab_chains[0] = start_address[index] + OFFSET(kmem_list3_slabs_partial);
slab_chains[1] = start_address[index] + OFFSET(kmem_list3_slabs_full);
slab_chains[2] = start_address[index] + OFFSET(kmem_list3_slabs_free);
gather_cpudata_list_v2_nodes(si, index);
si->flags &= ~SLAB_FIRST_NODE;
if (CRASHDEBUG(1)) {
fprintf(fp, "[ %s: %lx ", si->curname, si->cache);
fprintf(fp, "partial: %lx full: %lx free: %lx ]\n",
slab_chains[0], slab_chains[1], slab_chains[2]);
}
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"first slab", QUIET|RETURN_ON_ERROR)) {
error(INFO,
"%s: %s list: bad slab pointer: %lx\n",
si->curname,
slab_chain_name_v2[s],
slab_chains[s]);
list_borked = 1;
continue;
}
if (slab_data_saved(si)) {
FREEBUF(slab_buf);
FREEBUF(start_address);
return;
}
if (si->slab == slab_chains[s])
continue;
last = slab_chains[s];
do {
if (received_SIGINT()) {
FREEBUF(slab_buf);
FREEBUF(start_address);
restart(0);
}
if (!verify_slab_v2(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab - OFFSET(slab_list);
readmem(si->slab, KVADDR, slab_buf,
SIZE(slab), "slab buffer",
FAULT_ON_ERROR);
tmp = INT(slab_buf + OFFSET(slab_inuse));
si->inuse += tmp;
si->s_mem = ULONG(slab_buf +
OFFSET(slab_s_mem));
gather_slab_cached_count(si);
si->num_slabs++;
si->slab = ULONG(slab_buf +
OFFSET(slab_list));
si->slab -= OFFSET(slab_list);
/*
* Check for slab transition. (Tony Dziedzic)
*/
for (i = 0; i < SLAB_CHAINS; i++) {
if ((i != s) &&
(si->slab == slab_chains[i])) {
error(NOTE,
"%s: slab chain inconsistency: %s list\n",
si->curname,
slab_chain_name_v2[s]);
list_borked = 1;
}
}
} while (si->slab != slab_chains[s] && !list_borked);
}
}
if (!list_borked)
save_slab_data(si);
break;
case SLAB_WALKTHROUGH:
specified_slab = si->slab;
si->flags |= (SLAB_WALKTHROUGH|SLAB_FIRST_NODE);
si->flags &= ~SLAB_GET_COUNTS;
slab_buf = GETBUF(SIZE(slab));
for (index = 0; (index < vt->kmem_cache_len_nodes); index++)
{
if (vt->flags & NODES_ONLINE) {
node = next_online_node(index);
if (node < 0)
break;
if (node != index)
continue;
}
if (start_address[index] == 0)
continue;
slab_chains[0] = start_address[index] + OFFSET(kmem_list3_slabs_partial);
slab_chains[1] = start_address[index] + OFFSET(kmem_list3_slabs_full);
slab_chains[2] = start_address[index] + OFFSET(kmem_list3_slabs_free);
gather_cpudata_list_v2_nodes(si, index);
si->flags &= ~SLAB_FIRST_NODE;
if (CRASHDEBUG(1)) {
fprintf(fp, "[ %s: %lx ", si->curname, si->cache);
fprintf(fp, "partial: %lx full: %lx free: %lx ]\n",
slab_chains[0], slab_chains[1], slab_chains[2]);
}
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!specified_slab) {
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"slabs", QUIET|RETURN_ON_ERROR)) {
error(INFO, "%s: %s list: "
"bad slab pointer: %lx\n",
si->curname,
slab_chain_name_v2[s],
slab_chains[s]);
list_borked = 1;
continue;
}
last = slab_chains[s];
} else
last = 0;
if (si->slab == slab_chains[s])
continue;
readmem(si->slab, KVADDR, slab_buf,
SIZE(slab), "slab buffer",
FAULT_ON_ERROR);
si->s_mem = ULONG(slab_buf +
OFFSET(slab_s_mem));
if (CRASHDEBUG(1)) {
fprintf(fp, "search cache: [%s] ", si->curname);
if (si->flags & ADDRESS_SPECIFIED)
fprintf(fp, "for %llx", si->spec_addr);
fprintf(fp, "\n");
}
do {
if (received_SIGINT())
{
FREEBUF(start_address);
FREEBUF(slab_buf);
restart(0);
}
if (!verify_slab_v2(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab - OFFSET(slab_list);
dump_slab_percpu_v2(si);
if (si->found) {
FREEBUF(start_address);
FREEBUF(slab_buf);
return;
}
readmem(si->slab+OFFSET(slab_list),
KVADDR, &si->slab, sizeof(ulong),
"slab list", FAULT_ON_ERROR);
si->slab -= OFFSET(slab_list);
} while (si->slab != slab_chains[s] && !list_borked);
}
}
break;
}
FREEBUF(slab_buf);
FREEBUF(start_address);
}
static int
slab_freelist_index_size(void)
{
struct datatype_member datatype, *dm;
dm = &datatype;
BZERO(dm, sizeof(*dm));
dm->name = "freelist_idx_t";
if (is_typedef(dm->name))
return DATATYPE_SIZE(dm);
if (CRASHDEBUG(1))
error(INFO, "freelist_idx_t does not exist\n");
return sizeof(int);
}
static void
do_slab_chain_slab_overload_page(long cmd, struct meminfo *si)
{
int i, tmp, s, node;
int list_borked;
char *page_buf;
ulong specified_slab;
ulong last;
ulong slab_chains[SLAB_CHAINS];
ulong *start_address;
int index;
list_borked = 0;
page_buf = NULL;
si->slabsize = (power(2, si->order) * PAGESIZE());
si->cpucached_slab = 0;
start_address = (ulong *)GETBUF(sizeof(ulong) * vt->kmem_cache_len_nodes);
if (!readmem(kmem_cache_nodelists(si->cache), KVADDR,
&start_address[0], sizeof(ulong) * vt->kmem_cache_len_nodes,
"array nodelist array", RETURN_ON_ERROR))
error(INFO, "cannot read kmem_cache nodelists array");
switch (cmd)
{
case SLAB_GET_COUNTS:
si->flags |= (SLAB_GET_COUNTS|SLAB_FIRST_NODE);
si->flags &= ~SLAB_WALKTHROUGH;
si->cpucached_cache = 0;
si->num_slabs = si->inuse = 0;
page_buf = GETBUF(SIZE(page));
for (index = 0; (index < vt->kmem_cache_len_nodes); index++)
{
if (vt->flags & NODES_ONLINE) {
node = next_online_node(index);
if (node < 0)
break;
if (node != index)
continue;
}
if (start_address[index] == 0)
continue;
slab_chains[0] = start_address[index] + OFFSET(kmem_list3_slabs_partial);
slab_chains[1] = start_address[index] + OFFSET(kmem_list3_slabs_full);
slab_chains[2] = start_address[index] + OFFSET(kmem_list3_slabs_free);
gather_cpudata_list_v2_nodes(si, index);
si->flags &= ~SLAB_FIRST_NODE;
if (CRASHDEBUG(1)) {
fprintf(fp, "[ %s: %lx ", si->curname, si->cache);
fprintf(fp, "partial: %lx full: %lx free: %lx ]\n",
slab_chains[0], slab_chains[1], slab_chains[2]);
}
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"first slab", QUIET|RETURN_ON_ERROR)) {
error(INFO,
"%s: %s list: bad page/slab pointer: %lx\n",
si->curname,
slab_chain_name_v2[s],
slab_chains[s]);
list_borked = 1;
continue;
}
if (slab_data_saved(si)) {
FREEBUF(page_buf);
FREEBUF(start_address);
return;
}
if (si->slab == slab_chains[s])
continue;
last = slab_chains[s];
do {
if (received_SIGINT()) {
FREEBUF(page_buf);
FREEBUF(start_address);
restart(0);
}
if (!verify_slab_overload_page(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab;
readmem(si->slab - OFFSET(page_lru), KVADDR, page_buf,
SIZE(page), "page (slab) buffer",
FAULT_ON_ERROR);
tmp = INT(page_buf + OFFSET(page_active));
si->inuse += tmp;
si->s_mem = ULONG(page_buf +
OFFSET(page_s_mem));
gather_slab_cached_count(si);
si->num_slabs++;
si->slab = ULONG(page_buf +
OFFSET(page_lru));
/*
* Check for slab transition. (Tony Dziedzic)
*/
for (i = 0; i < SLAB_CHAINS; i++) {
if ((i != s) &&
(si->slab == slab_chains[i])) {
error(NOTE,
"%s: slab chain inconsistency: %s list\n",
si->curname,
slab_chain_name_v2[s]);
list_borked = 1;
}
}
} while (si->slab != slab_chains[s] && !list_borked);
}
}
if (!list_borked)
save_slab_data(si);
break;
case SLAB_WALKTHROUGH:
if (si->flags & SLAB_OVERLOAD_PAGE_PTR) {
specified_slab = si->spec_addr;
si->slab = si->spec_addr + OFFSET(page_lru);
} else {
specified_slab = si->slab;
if (si->slab)
si->slab += OFFSET(page_lru);
}
si->flags |= (SLAB_WALKTHROUGH|SLAB_FIRST_NODE);
si->flags &= ~SLAB_GET_COUNTS;
page_buf = GETBUF(SIZE(page));
for (index = 0; (index < vt->kmem_cache_len_nodes); index++)
{
if (vt->flags & NODES_ONLINE) {
node = next_online_node(index);
if (node < 0)
break;
if (node != index)
continue;
}
if (start_address[index] == 0)
continue;
slab_chains[0] = start_address[index] + OFFSET(kmem_list3_slabs_partial);
slab_chains[1] = start_address[index] + OFFSET(kmem_list3_slabs_full);
slab_chains[2] = start_address[index] + OFFSET(kmem_list3_slabs_free);
gather_cpudata_list_v2_nodes(si, index);
si->flags &= ~SLAB_FIRST_NODE;
if (CRASHDEBUG(1)) {
fprintf(fp, "[ %s: %lx ", si->curname, si->cache);
fprintf(fp, "partial: %lx full: %lx free: %lx ]\n",
slab_chains[0], slab_chains[1], slab_chains[2]);
}
for (s = 0; s < SLAB_CHAINS; s++) {
if (!slab_chains[s])
continue;
if (!specified_slab) {
if (!readmem(slab_chains[s],
KVADDR, &si->slab, sizeof(ulong),
"slabs", QUIET|RETURN_ON_ERROR)) {
error(INFO, "%s: %s list: "
"bad page/slab pointer: %lx\n",
si->curname,
slab_chain_name_v2[s],
slab_chains[s]);
list_borked = 1;
continue;
}
last = slab_chains[s];
} else
last = 0;
if (si->slab == slab_chains[s])
continue;
readmem(si->slab - OFFSET(page_lru), KVADDR, page_buf,
SIZE(page), "page (slab) buffer",
FAULT_ON_ERROR);
si->s_mem = ULONG(page_buf +
OFFSET(page_s_mem));
if (CRASHDEBUG(1)) {
fprintf(fp, "search cache: [%s] ", si->curname);
if (si->flags & ADDRESS_SPECIFIED)
fprintf(fp, "for %llx", si->spec_addr);
fprintf(fp, "\n");
}
do {
if (received_SIGINT())
{
FREEBUF(start_address);
FREEBUF(page_buf);
restart(0);
}
if (!verify_slab_overload_page(si, last, s)) {
list_borked = 1;
continue;
}
last = si->slab;
dump_slab_overload_page(si);
if (si->found) {
FREEBUF(start_address);
FREEBUF(page_buf);
return;
}
readmem(si->slab, KVADDR, &si->slab,
sizeof(ulong), "slab list",
FAULT_ON_ERROR);
} while (si->slab != slab_chains[s] && !list_borked);
}
}
break;
}
FREEBUF(page_buf);
FREEBUF(start_address);
}
/*
* Try to preclude any attempt to translate a bogus slab structure.
*/
static int
verify_slab_v2(struct meminfo *si, ulong last, int s)
{
char slab_buf[BUFSIZE];
struct kernel_list_head *list_head;
unsigned int inuse;
ulong s_mem;
char *list;
int errcnt;
list = slab_chain_name_v2[s];
errcnt = 0;
if (!readmem(si->slab, KVADDR, slab_buf,
SIZE(slab), "slab buffer", QUIET|RETURN_ON_ERROR)) {
error(INFO, "%s: %s list: bad slab pointer: %lx\n",
si->curname, list, si->slab);
return FALSE;
}
list_head = (struct kernel_list_head *)(slab_buf + OFFSET(slab_list));
if (!IS_KVADDR((ulong)list_head->next) ||
!accessible((ulong)list_head->next)) {
error(INFO, "%s: %s list: slab: %lx bad next pointer: %lx\n",
si->curname, list, si->slab,
(ulong)list_head->next);
errcnt++;
}
if (last && (last != (ulong)list_head->prev)) {
error(INFO, "%s: %s list: slab: %lx bad prev pointer: %lx\n",
si->curname, list, si->slab,
(ulong)list_head->prev);
errcnt++;
}
inuse = UINT(slab_buf + OFFSET(slab_inuse));
if (inuse > si->c_num) {
error(INFO, "%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
if (!last)
goto no_inuse_check_v2;
switch (s)
{
case 0: /* partial */
if ((inuse == 0) || (inuse == si->c_num)) {
error(INFO,
"%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
break;
case 1: /* full */
if (inuse != si->c_num) {
error(INFO,
"%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
break;
case 2: /* free */
if (inuse > 0) {
error(INFO,
"%s: %s list: slab: %lx bad inuse counter: %ld\n",
si->curname, list, si->slab, inuse);
errcnt++;
}
break;
}
no_inuse_check_v2:
s_mem = ULONG(slab_buf + OFFSET(slab_s_mem));
if (!IS_KVADDR(s_mem) || !accessible(s_mem)) {
error(INFO, "%s: %s list: slab: %lx bad s_mem pointer: %lx\n",
si->curname, list, si->slab, s_mem);
errcnt++;
}
si->errors += errcnt;
return(errcnt ? FALSE : TRUE);
}
static int
verify_slab_overload_page(struct meminfo *si, ulong last, int s)
{
char *page_buf;
struct kernel_list_head *list_head;
unsigned int active;
ulong s_mem;
char *list;
int errcnt;
list = slab_chain_name_v2[s];
page_buf = GETBUF(SIZE(page));
errcnt = 0;
if (!readmem(si->slab - OFFSET(page_lru), KVADDR, page_buf,
SIZE(page), "page (slab) buffer", QUIET|RETURN_ON_ERROR)) {
error(INFO, "%s: %s list: bad slab pointer: %lx\n",
si->curname, list, si->slab);
FREEBUF(page_buf);
return FALSE;
}
list_head = (struct kernel_list_head *)(page_buf + OFFSET(page_lru));
if (!IS_KVADDR((ulong)list_head->next) ||
!accessible((ulong)list_head->next)) {
error(INFO, "%s: %s list: page/slab: %lx bad next pointer: %lx\n",
si->curname, list, si->slab,
(ulong)list_head->next);
errcnt++;
}
if (last && (last != (ulong)list_head->prev)) {
error(INFO, "%s: %s list: page/slab: %lx bad prev pointer: %lx\n",
si->curname, list, si->slab,
(ulong)list_head->prev);
errcnt++;
}
active = UINT(page_buf + OFFSET(page_active));
if (active > si->c_num) {
error(INFO, "%s: %s list: page/slab: %lx bad active counter: %ld\n",
si->curname, list, si->slab, active);
errcnt++;
}
if (!last)
goto no_inuse_check_v2;
switch (s)
{
case 0: /* partial */
if ((active == 0) || (active == si->c_num)) {
error(INFO,
"%s: %s list: page/slab: %lx bad active counter: %ld\n",
si->curname, list, si->slab, active);
errcnt++;
}
break;
case 1: /* full */
if (active != si->c_num) {
error(INFO,
"%s: %s list: page/slab: %lx bad active counter: %ld\n",
si->curname, list, si->slab, active);
errcnt++;
}
break;
case 2: /* free */
if (active > 0) {
error(INFO,
"%s: %s list: page/slab: %lx bad active counter: %ld\n",
si->curname, list, si->slab, active);
errcnt++;
}
break;
}
no_inuse_check_v2:
s_mem = ULONG(page_buf + OFFSET(page_s_mem));
if (!IS_KVADDR(s_mem) || !accessible(s_mem)) {
error(INFO, "%s: %s list: page/slab: %lx bad s_mem pointer: %lx\n",
si->curname, list, si->slab, s_mem);
errcnt++;
}
si->errors += errcnt;
FREEBUF(page_buf);
return(errcnt ? FALSE : TRUE);
}
/*
* If it's a dumpfile, save the essential slab data to avoid re-reading
* the whole slab chain more than once. This may seem like overkill, but
* if the problem is a memory leak, or just the over-use of the buffer_head
* cache, it's painful to wait each time subsequent kmem -s or -i commands
* simply need the basic slab counts.
*/
struct slab_data {
ulong cache_addr;
int num_slabs;
int inuse;
ulong cpucached_cache;
};
#define NO_SLAB_DATA ((void *)(-1))
static void
save_slab_data(struct meminfo *si)
{
int i;
if (si->flags & SLAB_DATA_NOSAVE) {
si->flags &= ~SLAB_DATA_NOSAVE;
return;
}
if (ACTIVE())
return;
if (vt->slab_data == NO_SLAB_DATA)
return;
if (!vt->slab_data) {
if (!(vt->slab_data = (struct slab_data *)
malloc(sizeof(struct slab_data) * vt->kmem_cache_count))) {
error(INFO, "cannot malloc slab_data table");
vt->slab_data = NO_SLAB_DATA;
return;
}
for (i = 0; i < vt->kmem_cache_count; i++) {
vt->slab_data[i].cache_addr = (ulong)NO_SLAB_DATA;
vt->slab_data[i].num_slabs = 0;
vt->slab_data[i].inuse = 0;
vt->slab_data[i].cpucached_cache = 0;
}
}
for (i = 0; i < vt->kmem_cache_count; i++) {
if (vt->slab_data[i].cache_addr == si->cache)
break;
if (vt->slab_data[i].cache_addr == (ulong)NO_SLAB_DATA) {
vt->slab_data[i].cache_addr = si->cache;
vt->slab_data[i].num_slabs = si->num_slabs;
vt->slab_data[i].inuse = si->inuse;
vt->slab_data[i].cpucached_cache = si->cpucached_cache;
break;
}
}
}
static int
slab_data_saved(struct meminfo *si)
{
int i;
if (ACTIVE() || !vt->slab_data || (vt->slab_data == NO_SLAB_DATA))
return FALSE;
for (i = 0; i < vt->kmem_cache_count; i++) {
if (vt->slab_data[i].cache_addr == si->cache) {
si->inuse = vt->slab_data[i].inuse;
si->num_slabs = vt->slab_data[i].num_slabs;
si->cpucached_cache = vt->slab_data[i].cpucached_cache;
return TRUE;
}
}
return FALSE;
}
static void
dump_saved_slab_data(void)
{
int i;
if (!vt->slab_data || (vt->slab_data == NO_SLAB_DATA))
return;
for (i = 0; i < vt->kmem_cache_count; i++) {
if (vt->slab_data[i].cache_addr == (ulong)NO_SLAB_DATA)
break;
fprintf(fp,
" cache: %lx inuse: %5d num_slabs: %3d cpucached_cache: %ld\n",
vt->slab_data[i].cache_addr,
vt->slab_data[i].inuse,
vt->slab_data[i].num_slabs,
vt->slab_data[i].cpucached_cache);
}
}
/*
* Dump the contents of a kmem slab.
*/
static void
dump_slab(struct meminfo *si)
{
si->s_mem = ULONG(si->slab_buf + OFFSET(kmem_slab_s_s_mem));
si->s_mem = PTOB(BTOP(si->s_mem));
if (si->flags & ADDRESS_SPECIFIED) {
if (INSLAB(si->slab, si) && (si->spec_addr >= si->slab) &&
(si->spec_addr < (si->slab+SIZE(kmem_slab_s)))) {
si->found = KMEM_SLAB_ADDR;
return;
}
if (INSLAB(si->spec_addr, si))
si->found = KMEM_ON_SLAB; /* But don't return yet... */
else
return;
}
si->s_freep = VOID_PTR(si->slab_buf + OFFSET(kmem_slab_s_s_freep));
si->s_inuse = ULONG(si->slab_buf + OFFSET(kmem_slab_s_s_inuse));
si->s_index = ULONG_PTR(si->slab_buf + OFFSET(kmem_slab_s_s_index));
if (!(si->flags & ADDRESS_SPECIFIED)) {
fprintf(fp, "%s", slab_hdr);
DUMP_SLAB_INFO();
}
dump_slab_objects(si);
}
/*
* dump_slab() adapted for newer percpu slab format.
*/
static void
dump_slab_percpu_v1(struct meminfo *si)
{
int tmp;
readmem(si->slab+OFFSET(slab_s_s_mem),
KVADDR, &si->s_mem, sizeof(ulong),
"s_mem", FAULT_ON_ERROR);
/*
* Include the array of kmem_bufctl_t's appended to slab.
*/
tmp = SIZE(slab_s) + (SIZE(kmem_bufctl_t) * si->c_num);
if (si->flags & ADDRESS_SPECIFIED) {
if (INSLAB_PERCPU(si->slab, si) &&
(si->spec_addr >= si->slab) &&
(si->spec_addr < (si->slab+tmp))) {
if (si->spec_addr >= (si->slab + SIZE(slab_s)))
si->found = KMEM_BUFCTL_ADDR;
else
si->found = KMEM_SLAB_ADDR;
} else if (INSLAB_PERCPU(si->spec_addr, si))
si->found = KMEM_ON_SLAB; /* But don't return yet... */
else
return;
}
readmem(si->slab+OFFSET(slab_s_inuse),
KVADDR, &tmp, sizeof(int),
"inuse", FAULT_ON_ERROR);
si->s_inuse = tmp;
readmem(si->slab+OFFSET(slab_s_free),
KVADDR, &si->free, SIZE(kmem_bufctl_t),
"kmem_bufctl_t", FAULT_ON_ERROR);
gather_slab_free_list_percpu(si);
gather_slab_cached_count(si);
if (!(si->flags & ADDRESS_SPECIFIED)) {
fprintf(fp, "%s", slab_hdr);
DUMP_SLAB_INFO();
}
dump_slab_objects_percpu(si);
}
/*
* Updated for 2.6 slab substructure.
*/
static void
dump_slab_percpu_v2(struct meminfo *si)
{
int tmp;
readmem(si->slab+OFFSET(slab_s_mem),
KVADDR, &si->s_mem, sizeof(ulong),
"s_mem", FAULT_ON_ERROR);
/*
* Include the array of kmem_bufctl_t's appended to slab.
*/
tmp = SIZE(slab) + (SIZE(kmem_bufctl_t) * si->c_num);
if (si->flags & ADDRESS_SPECIFIED) {
if (INSLAB_PERCPU(si->slab, si) &&
(si->spec_addr >= si->slab) &&
(si->spec_addr < (si->slab+tmp))) {
if (si->spec_addr >= (si->slab + SIZE(slab)))
si->found = KMEM_BUFCTL_ADDR;
else
si->found = KMEM_SLAB_ADDR;
} else if (INSLAB_PERCPU(si->spec_addr, si))
si->found = KMEM_ON_SLAB; /* But don't return yet... */
else
return;
}
readmem(si->slab+OFFSET(slab_inuse),
KVADDR, &tmp, sizeof(int),
"inuse", FAULT_ON_ERROR);
si->s_inuse = tmp;
readmem(si->slab+OFFSET(slab_free),
KVADDR, &si->free, SIZE(kmem_bufctl_t),
"kmem_bufctl_t", FAULT_ON_ERROR);
gather_slab_free_list_percpu(si);
gather_slab_cached_count(si);
if (!(si->flags & ADDRESS_SPECIFIED)) {
fprintf(fp, "%s", slab_hdr);
DUMP_SLAB_INFO();
}
dump_slab_objects_percpu(si);
}
static void
dump_slab_overload_page(struct meminfo *si)
{
int tmp;
ulong slab_overload_page, freelist;
slab_overload_page = si->slab - OFFSET(page_lru);
readmem(slab_overload_page + OFFSET(page_s_mem),
KVADDR, &si->s_mem, sizeof(ulong),
"page.s_mem", FAULT_ON_ERROR);
readmem(slab_overload_page + OFFSET(page_freelist),
KVADDR, &freelist, sizeof(ulong),
"page.freelist", FAULT_ON_ERROR);
if (si->flags & ADDRESS_SPECIFIED) {
if ((si->spec_addr >= slab_overload_page) &&
(si->spec_addr < (slab_overload_page+SIZE(page)))) {
si->found = KMEM_SLAB_OVERLOAD_PAGE;
} else if (INSLAB_PERCPU(si->spec_addr, si))
si->found = KMEM_ON_SLAB; /* But don't return yet... */
else
return;
}
readmem(slab_overload_page + OFFSET(page_active),
KVADDR, &tmp, sizeof(int),
"active", FAULT_ON_ERROR);
si->s_inuse = tmp;
gather_slab_free_list_slab_overload_page(si);
gather_slab_cached_count(si);
if (!(si->flags & ADDRESS_SPECIFIED)) {
fprintf(fp, "%s", slab_hdr);
DUMP_SLAB_INFO();
}
dump_slab_objects_percpu(si);
}
/*
* Gather the free objects in a slab into the si->addrlist, checking for
* specified addresses that are in-slab kmem_bufctls, and making error checks
* along the way. Object address checks are deferred to dump_slab_objects().
*/
#define INOBJECT(addr, obj) ((addr >= obj) && (addr < (obj+si->size)))
static void
gather_slab_free_list(struct meminfo *si)
{
ulong *next, obj;
ulong expected, cnt;
BNEG(si->addrlist, sizeof(ulong) * (si->c_num+1));
if (!si->s_freep)
return;
cnt = 0;
expected = si->c_num - si->s_inuse;
next = si->s_freep;
do {
if (cnt == si->c_num) {
error(INFO,
"\"%s\" cache: too many objects found in slab free list\n",
si->curname);
si->errors++;
return;
}
/*
* Off-slab kmem_bufctls are contained in arrays of object
* pointers that point to:
* 1. next kmem_bufctl (or NULL) if the object is free.
* 2. to the object if it the object is in use.
*
* On-slab kmem_bufctls resides just after the object itself,
* and point to:
* 1. next kmem_bufctl (or NULL) if object is free.
* 2. the containing slab if the object is in use.
*/
if (si->c_flags & SLAB_CFLGS_BUFCTL)
obj = si->s_mem + ((next - si->s_index) * si->c_offset);
else
obj = (ulong)next - si->c_offset;
si->addrlist[cnt] = obj;
if (si->flags & ADDRESS_SPECIFIED) {
if (INSLAB(next, si) &&
(si->spec_addr >= (ulong)next) &&
(si->spec_addr < (ulong)(next + 1))) {
si->found = KMEM_BUFCTL_ADDR;
return;
}
}
cnt++;
if (!INSLAB(obj, si)) {
error(INFO,
"\"%s\" cache: address not contained within slab: %lx\n",
si->curname, obj);
si->errors++;
}
readmem((ulong)next, KVADDR, &next, sizeof(void *),
"s_freep chain entry", FAULT_ON_ERROR);
} while (next);
if (cnt != expected) {
error(INFO,
"\"%s\" cache: free object mismatch: expected: %ld found: %ld\n",
si->curname, expected, cnt);
si->errors++;
}
}
/*
* gather_slab_free_list() adapted for newer percpu slab format.
*/
#define BUFCTL_END 0xffffFFFF
static void
gather_slab_free_list_percpu(struct meminfo *si)
{
int i;
ulong obj;
ulong expected, cnt;
int free_index;
ulong kmembp;
short *kbp;
BNEG(si->addrlist, sizeof(ulong) * (si->c_num+1));
if (CRASHDEBUG(1))
fprintf(fp, "slab: %lx si->s_inuse: %ld si->c_num: %ld\n",
si->slab, si->s_inuse, si->c_num);
if (si->s_inuse == si->c_num )
return;
kmembp = si->slab + SIZE_OPTION(slab_s, slab);
readmem((ulong)kmembp, KVADDR, si->kmem_bufctl,
SIZE(kmem_bufctl_t) * si->c_num,
"kmem_bufctl array", FAULT_ON_ERROR);
if (CRASHDEBUG(1)) {
for (i = 0; (SIZE(kmem_bufctl_t) == sizeof(int)) &&
(i < si->c_num); i++)
fprintf(fp, "%d ", si->kmem_bufctl[i]);
for (kbp = (short *)&si->kmem_bufctl[0], i = 0;
(SIZE(kmem_bufctl_t) == sizeof(short)) && (i < si->c_num);
i++)
fprintf(fp, "%d ", *(kbp + i));
fprintf(fp, "\n");
}
cnt = 0;
expected = si->c_num - si->s_inuse;
if (SIZE(kmem_bufctl_t) == sizeof(int)) {
for (free_index = si->free; free_index != BUFCTL_END;
free_index = si->kmem_bufctl[free_index]) {
if (cnt == si->c_num) {
error(INFO,
"\"%s\" cache: too many objects found in slab free list\n",
si->curname);
si->errors++;
return;
}
obj = si->s_mem + (free_index*si->size);
si->addrlist[cnt] = obj;
cnt++;
}
} else if (SIZE(kmem_bufctl_t) == sizeof(short)) {
kbp = (short *)&si->kmem_bufctl[0];
for (free_index = si->free; free_index != BUFCTL_END;
free_index = (int)*(kbp + free_index)) {
if (cnt == si->c_num) {
error(INFO,
"\"%s\" cache: too many objects found in slab free list\n",
si->curname);
si->errors++;
return;
}
obj = si->s_mem + (free_index*si->size);
si->addrlist[cnt] = obj;
cnt++;
}
} else
error(FATAL,
"size of kmem_bufctl_t (%d) not sizeof(int) or sizeof(short)\n",
SIZE(kmem_bufctl_t));
if (cnt != expected) {
error(INFO,
"\"%s\" cache: free object mismatch: expected: %ld found: %ld\n",
si->curname, expected, cnt);
si->errors++;
}
}
static void
gather_slab_free_list_slab_overload_page(struct meminfo *si)
{
int i, active;
ulong obj, objnr, cnt, freelist;
unsigned char *ucharptr;
unsigned short *ushortptr;
unsigned int *uintptr;
if (CRASHDEBUG(1))
fprintf(fp, "slab page: %lx active: %ld si->c_num: %ld\n",
si->slab - OFFSET(page_lru), si->s_inuse, si->c_num);
if (si->s_inuse == si->c_num )
return;
readmem(si->slab - OFFSET(page_lru) + OFFSET(page_freelist),
KVADDR, &freelist, sizeof(void *), "page freelist",
FAULT_ON_ERROR);
readmem(freelist, KVADDR, si->freelist,
si->freelist_index_size * si->c_num,
"freelist array", FAULT_ON_ERROR);
BNEG(si->addrlist, sizeof(ulong) * (si->c_num+1));
cnt = objnr = 0;
ucharptr = NULL;
ushortptr = NULL;
uintptr = NULL;
active = si->s_inuse;
switch (si->freelist_index_size)
{
case 1: ucharptr = (unsigned char *)si->freelist; break;
case 2: ushortptr = (unsigned short *)si->freelist; break;
case 4: uintptr = (unsigned int *)si->freelist; break;
}
for (i = 0; i < si->c_num; i++) {
switch (si->freelist_index_size)
{
case 1: objnr = (ulong)*ucharptr++; break;
case 2: objnr = (ulong)*ushortptr++; break;
case 4: objnr = (ulong)*uintptr++; break;
}
if (objnr >= si->c_num) {
error(INFO,
"\"%s\" cache: invalid/corrupt freelist entry: %ld\n",
si->curname, objnr);
si->errors++;
}
if (i >= active) {
obj = si->s_mem + (objnr * si->size);
si->addrlist[cnt++] = obj;
if (CRASHDEBUG(1))
fprintf(fp, "%ld ", objnr);
} else if (CRASHDEBUG(1))
fprintf(fp, "[%ld] ", objnr);
}
if (CRASHDEBUG(1))
fprintf(fp, "\n");
}
/*
* Dump the FREE, [ALLOCATED] and <CACHED> objects of a slab.
*/
#define DUMP_SLAB_OBJECT() \
for (j = on_free_list = 0; j < si->c_num; j++) { \
if (obj == si->addrlist[j]) { \
on_free_list = TRUE; \
break; \
} \
} \
\
if (on_free_list) { \
if (!(si->flags & ADDRESS_SPECIFIED)) \
fprintf(fp, " %lx\n", obj); \
if (si->flags & ADDRESS_SPECIFIED) { \
if (INOBJECT(si->spec_addr, obj)) { \
si->found = \
KMEM_OBJECT_ADDR_FREE; \
si->container = obj; \
return; \
} \
} \
} else { \
if (!(si->flags & ADDRESS_SPECIFIED)) \
fprintf(fp, " [%lx]\n", obj); \
cnt++; \
if (si->flags & ADDRESS_SPECIFIED) { \
if (INOBJECT(si->spec_addr, obj)) { \
si->found = \
KMEM_OBJECT_ADDR_INUSE; \
si->container = obj; \
return; \
} \
} \
}
static void
dump_slab_objects(struct meminfo *si)
{
int i, j;
ulong *next;
int on_free_list;
ulong cnt, expected;
ulong bufctl, obj;
gather_slab_free_list(si);
if ((si->flags & ADDRESS_SPECIFIED) && (si->found & ~KMEM_ON_SLAB))
return;
cnt = 0;
expected = si->s_inuse;
si->container = 0;
if (CRASHDEBUG(1))
for (i = 0; i < si->c_num; i++) {
fprintf(fp, "si->addrlist[%d]: %lx\n",
i, si->addrlist[i]);
}
if (!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, "%s", free_inuse_hdr);
/* For on-slab bufctls, c_offset is the distance between the start of
* an obj and its related bufctl. For off-slab bufctls, c_offset is
* the distance between objs in the slab.
*/
if (si->c_flags & SLAB_CFLGS_BUFCTL) {
for (i = 0, next = si->s_index; i < si->c_num; i++, next++) {
obj = si->s_mem +
((next - si->s_index) * si->c_offset);
DUMP_SLAB_OBJECT();
}
} else {
/*
* Get the "real" s_mem, i.e., without the offset stripped off.
* It contains the address of the first object.
*/
readmem(si->slab+OFFSET(kmem_slab_s_s_mem),
KVADDR, &obj, sizeof(ulong),
"s_mem", FAULT_ON_ERROR);
for (i = 0; i < si->c_num; i++) {
DUMP_SLAB_OBJECT();
if (si->flags & ADDRESS_SPECIFIED) {
bufctl = obj + si->c_offset;
if ((si->spec_addr >= bufctl) &&
(si->spec_addr <
(bufctl + SIZE(kmem_bufctl_t)))) {
si->found = KMEM_BUFCTL_ADDR;
return;
}
}
obj += (si->c_offset + SIZE(kmem_bufctl_t));
}
}
if (cnt != expected) {
error(INFO,
"\"%s\" cache: inuse object mismatch: expected: %ld found: %ld\n",
si->curname, expected, cnt);
si->errors++;
}
}
/*
* dump_slab_objects() adapted for newer percpu slab format.
*/
static void
dump_slab_objects_percpu(struct meminfo *si)
{
int i, j;
int on_free_list, on_cpudata_list, on_shared_list;
ulong cnt, expected;
ulong obj, freelist;
if ((si->flags & ADDRESS_SPECIFIED) && (si->found & ~KMEM_ON_SLAB))
if (!(si->found & KMEM_SLAB_OVERLOAD_PAGE))
return;
cnt = 0;
expected = si->s_inuse;
si->container = 0;
if (CRASHDEBUG(1))
for (i = 0; i < si->c_num; i++) {
fprintf(fp, "si->addrlist[%d]: %lx\n",
i, si->addrlist[i]);
}
if (!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, "%s", free_inuse_hdr);
for (i = 0, obj = si->s_mem; i < si->c_num; i++, obj += si->size) {
on_free_list = FALSE;
on_cpudata_list = FALSE;
on_shared_list = FALSE;
for (j = 0; j < si->c_num; j++) {
if (obj == si->addrlist[j]) {
on_free_list = TRUE;
break;
}
}
on_cpudata_list = check_cpudata_list(si, obj);
on_shared_list = check_shared_list(si, obj);
if (on_free_list && on_cpudata_list) {
error(INFO,
"\"%s\" cache: object %lx on both free and cpu %d lists\n",
si->curname, obj, si->cpu);
si->errors++;
}
if (on_free_list && on_shared_list) {
error(INFO,
"\"%s\" cache: object %lx on both free and shared lists\n",
si->curname, obj);
si->errors++;
}
if (on_cpudata_list && on_shared_list) {
error(INFO,
"\"%s\" cache: object %lx on both cpu %d and shared lists\n",
si->curname, obj, si->cpu);
si->errors++;
}
if (on_free_list) {
if (!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, " %lx\n", obj);
if (si->flags & ADDRESS_SPECIFIED) {
if (INOBJECT(si->spec_addr, obj)) {
si->found =
KMEM_OBJECT_ADDR_FREE;
si->container = obj;
return;
}
}
} else if (on_cpudata_list) {
if (!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, " %lx (cpu %d cache)\n", obj,
si->cpu);
cnt++;
if (si->flags & ADDRESS_SPECIFIED) {
if (INOBJECT(si->spec_addr, obj)) {
si->found =
KMEM_OBJECT_ADDR_CACHED;
si->container = obj;
return;
}
}
} else if (on_shared_list) {
if (!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, " %lx (shared cache)\n", obj);
cnt++;
if (si->flags & ADDRESS_SPECIFIED) {
if (INOBJECT(si->spec_addr, obj)) {
si->found =
KMEM_OBJECT_ADDR_SHARED;
si->container = obj;
return;
}
}
} else {
if (!(si->flags & ADDRESS_SPECIFIED))
fprintf(fp, " [%lx]\n", obj);
cnt++;
if (si->flags & ADDRESS_SPECIFIED) {
if (INOBJECT(si->spec_addr, obj)) {
si->found =
KMEM_OBJECT_ADDR_INUSE;
si->container = obj;
return;
}
}
}
}
if (cnt != expected) {
error(INFO,
"\"%s\" cache: inuse object mismatch: expected: %ld found: %ld\n",
si->curname, expected, cnt);
si->errors++;
}
if ((si->flags & ADDRESS_SPECIFIED) &&
(vt->flags & SLAB_OVERLOAD_PAGE)) {
readmem(si->slab - OFFSET(page_lru) + OFFSET(page_freelist),
KVADDR, &freelist, sizeof(ulong), "page.freelist",
FAULT_ON_ERROR);
if ((si->spec_addr >= freelist) && (si->spec_addr < si->s_mem))
si->found = KMEM_SLAB_FREELIST;
}
}
/*
* Determine how many of the "inuse" slab objects are actually cached
* in the kmem_cache_s header. Set the per-slab count and update the
* cumulative per-cache count. With the addition of the shared list
* check, the terms "cpucached_cache" and "cpucached_slab" are somewhat
* misleading. But they both are types of objects that are cached
* in the kmem_cache_s header, just not necessarily per-cpu.
*/
static void
gather_slab_cached_count(struct meminfo *si)
{
int i;
ulong obj;
int in_cpudata, in_shared;
si->cpucached_slab = 0;
for (i = 0, obj = si->s_mem; i < si->c_num; i++, obj += si->size) {
in_cpudata = in_shared = 0;
if (check_cpudata_list(si, obj)) {
in_cpudata = TRUE;
si->cpucached_slab++;
if (si->flags & SLAB_GET_COUNTS) {
si->cpucached_cache++;
}
}
if (check_shared_list(si, obj)) {
in_shared = TRUE;
if (!in_cpudata) {
si->cpucached_slab++;
if (si->flags & SLAB_GET_COUNTS) {
si->cpucached_cache++;
}
}
}
if (in_cpudata && in_shared) {
si->flags |= SLAB_DATA_NOSAVE;
if (!(si->flags & VERBOSE))
error(INFO,
"\"%s\" cache: object %lx on both cpu %d and shared lists\n",
si->curname, obj, si->cpu);
}
}
}
/*
* Populate the percpu object list for a given slab.
*/
static void
gather_cpudata_list_v1(struct meminfo *si)
{
int i, j;
int avail;
ulong cpudata[NR_CPUS];
if (INVALID_MEMBER(kmem_cache_s_cpudata))
return;
readmem(si->cache+OFFSET(kmem_cache_s_cpudata),
KVADDR, &cpudata[0],
sizeof(ulong) * ARRAY_LENGTH(kmem_cache_s_cpudata),
"cpudata array", FAULT_ON_ERROR);
for (i = 0; (i < ARRAY_LENGTH(kmem_cache_s_cpudata)) &&
cpudata[i]; i++) {
BZERO(si->cpudata[i], sizeof(ulong) * vt->kmem_max_limit);
readmem(cpudata[i]+OFFSET(cpucache_s_avail),
KVADDR, &avail, sizeof(int),
"cpucache avail", FAULT_ON_ERROR);
if (!avail)
continue;
if (avail > vt->kmem_max_limit) {
error(INFO,
"\"%s\" cache: cpucache_s.avail %d greater than limit %ld\n",
si->curname, avail, vt->kmem_max_limit);
si->errors++;
}
if (CRASHDEBUG(2))
fprintf(fp, "%s: cpu[%d] avail: %d\n",
si->curname, i, avail);
readmem(cpudata[i]+SIZE(cpucache_s),
KVADDR, si->cpudata[i],
sizeof(void *) * avail,
"cpucache avail", FAULT_ON_ERROR);
if (CRASHDEBUG(2))
for (j = 0; j < avail; j++)
fprintf(fp, " %lx\n", si->cpudata[i][j]);
}
}
/*
* Updated for 2.6 slab percpu data structure, this also gathers
* the shared array_cache list as well.
*/
static void
gather_cpudata_list_v2(struct meminfo *si)
{
int i, j;
int avail;
ulong cpudata[NR_CPUS];
ulong shared;
readmem(si->cache+OFFSET(kmem_cache_s_array),
KVADDR, &cpudata[0],
sizeof(ulong) * ARRAY_LENGTH(kmem_cache_s_array),
"array_cache array", FAULT_ON_ERROR);
for (i = 0; (i < ARRAY_LENGTH(kmem_cache_s_array)) &&
cpudata[i]; i++) {
BZERO(si->cpudata[i], sizeof(ulong) * vt->kmem_max_limit);
readmem(cpudata[i]+OFFSET(array_cache_avail),
KVADDR, &avail, sizeof(int),
"array cache avail", FAULT_ON_ERROR);
if (!avail)
continue;
if (avail > vt->kmem_max_limit) {
error(INFO,
"\"%s\" cache: array_cache.avail %d greater than limit %ld\n",
si->curname, avail, vt->kmem_max_limit);
si->errors++;
}
if (CRASHDEBUG(2))
fprintf(fp, "%s: cpu[%d] avail: %d\n",
si->curname, i, avail);
readmem(cpudata[i]+SIZE(array_cache),
KVADDR, si->cpudata[i],
sizeof(void *) * avail,
"array_cache avail", FAULT_ON_ERROR);
if (CRASHDEBUG(2))
for (j = 0; j < avail; j++)
fprintf(fp, " %lx (cpu %d)\n", si->cpudata[i][j], i);
}
/*
* If the shared list contains anything, gather them as well.
*/
BZERO(si->shared_array_cache, sizeof(ulong) * vt->kmem_max_limit);
if (!VALID_MEMBER(kmem_list3_shared) ||
!VALID_MEMBER(kmem_cache_s_lists) ||
!readmem(si->cache+OFFSET(kmem_cache_s_lists)+
OFFSET(kmem_list3_shared), KVADDR, &shared, sizeof(void *),
"kmem_list3 shared", RETURN_ON_ERROR|QUIET) ||
!readmem(shared+OFFSET(array_cache_avail),
KVADDR, &avail, sizeof(int), "shared array_cache avail",
RETURN_ON_ERROR|QUIET) || !avail)
return;
if (avail > vt->kmem_max_limit) {
error(INFO,
"\"%s\" cache: shared array_cache.avail %d greater than limit %ld\n",
si->curname, avail, vt->kmem_max_limit);
si->errors++;
return;
}
if (CRASHDEBUG(2))
fprintf(fp, "%s: shared avail: %d\n",
si->curname, avail);
readmem(shared+SIZE(array_cache), KVADDR, si->shared_array_cache,
sizeof(void *) * avail, "shared array_cache avail",
FAULT_ON_ERROR);
if (CRASHDEBUG(2))
for (j = 0; j < avail; j++)
fprintf(fp, " %lx (shared list)\n", si->shared_array_cache[j]);
}
/*
* Updated gather_cpudata_list_v2 for per-node kmem_list3's in kmem_cache
*/
static void
gather_cpudata_list_v2_nodes(struct meminfo *si, int index)
{
int i, j;
int avail;
ulong cpudata[NR_CPUS];
ulong shared;
ulong *start_address;
start_address = (ulong *) GETBUF(sizeof(ulong) * vt->kmem_cache_len_nodes);
readmem(si->cache+OFFSET(kmem_cache_s_array),
KVADDR, &cpudata[0],
sizeof(ulong) * vt->kmem_max_cpus,
"array_cache array", FAULT_ON_ERROR);
for (i = 0; (i < vt->kmem_max_cpus) && cpudata[i] && !(index); i++) {
if (si->cpudata[i])
BZERO(si->cpudata[i], sizeof(ulong) * vt->kmem_max_limit);
else
continue;
readmem(cpudata[i]+OFFSET(array_cache_avail),
KVADDR, &avail, sizeof(int),
"array cache avail", FAULT_ON_ERROR);
if (!avail)
continue;
if (avail > vt->kmem_max_limit) {
error(INFO,
"\"%s\" cache: array_cache.avail %d greater than limit %ld\n",
si->curname, avail, vt->kmem_max_limit);
si->errors++;
continue;
}
if (CRASHDEBUG(2))
fprintf(fp, "%s: cpu[%d] avail: %d\n",
si->curname, i, avail);
readmem(cpudata[i]+SIZE(array_cache),
KVADDR, si->cpudata[i],
sizeof(void *) * avail,
"array_cache avail", FAULT_ON_ERROR);
if (CRASHDEBUG(2))
for (j = 0; j < avail; j++)
fprintf(fp, " %lx (cpu %d)\n", si->cpudata[i][j], i);
}
/*
* If the shared list contains anything, gather them as well.
*/
if (si->flags & SLAB_FIRST_NODE) {
BZERO(si->shared_array_cache, sizeof(ulong) *
vt->kmem_max_limit * vt->kmem_cache_len_nodes);
si->current_cache_index = 0;
}
if (!readmem(kmem_cache_nodelists(si->cache), KVADDR, &start_address[0],
sizeof(ulong) * vt->kmem_cache_len_nodes , "array nodelist array",
RETURN_ON_ERROR) ||
!readmem(start_address[index] + OFFSET(kmem_list3_shared), KVADDR, &shared,
sizeof(void *), "kmem_list3 shared", RETURN_ON_ERROR|QUIET) || !shared ||
!readmem(shared + OFFSET(array_cache_avail), KVADDR, &avail, sizeof(int),
"shared array_cache avail", RETURN_ON_ERROR|QUIET) || !avail) {
FREEBUF(start_address);
return;
}
if (avail > vt->kmem_max_limit) {
error(INFO,
"\"%s\" cache: shared array_cache.avail %d greater than limit %ld\n",
si->curname, avail, vt->kmem_max_limit);
si->errors++;
FREEBUF(start_address);
return;
}
if (CRASHDEBUG(2))
fprintf(fp, "%s: shared avail: %d\n",
si->curname, avail);
readmem(shared+SIZE(array_cache), KVADDR, si->shared_array_cache + si->current_cache_index,
sizeof(void *) * avail, "shared array_cache avail",
FAULT_ON_ERROR);
if ((si->current_cache_index + avail) >
(vt->kmem_max_limit * vt->kmem_cache_len_nodes)) {
error(INFO,
"\"%s\" cache: total shared array_cache.avail %d greater than total limit %ld\n",
si->curname,
si->current_cache_index + avail,
vt->kmem_max_limit * vt->kmem_cache_len_nodes);
si->errors++;
FREEBUF(start_address);
return;
}
if (CRASHDEBUG(2))
for (j = si->current_cache_index; j < (si->current_cache_index + avail); j++)
fprintf(fp, " %lx (shared list)\n", si->shared_array_cache[j]);
si->current_cache_index += avail;
FREEBUF(start_address);
}
/*
* Check whether a given address is contained in the previously-gathered
* percpu object cache.
*/
static int
check_cpudata_list(struct meminfo *si, ulong obj)
{
int i, j;
for (i = 0; i < vt->kmem_max_cpus; i++) {
for (j = 0; si->cpudata[i][j]; j++)
if (si->cpudata[i][j] == obj) {
si->cpu = i;
return TRUE;
}
}
return FALSE;
}
/*
* Check whether a given address is contained in the previously-gathered
* shared object cache.
*/
static int
check_shared_list(struct meminfo *si, ulong obj)
{
int i;
if (INVALID_MEMBER(kmem_list3_shared) ||
!si->shared_array_cache)
return FALSE;
for (i = 0; si->shared_array_cache[i]; i++) {
if (si->shared_array_cache[i] == obj)
return TRUE;
}
return FALSE;
}
/*
* Search the various memory subsystems for instances of this address.
* Start with the most specific areas, ending up with at least the
* mem_map page data.
*/
static void
kmem_search(struct meminfo *mi)
{
struct syment *sp;
struct meminfo tmp_meminfo;
char buf[BUFSIZE];
ulong vaddr, orig_flags;
physaddr_t paddr;
ulong offset;
ulong task;
ulong show_flags;
struct task_context *tc;
vaddr = 0;
pc->curcmd_flags &= ~HEADER_PRINTED;
pc->curcmd_flags |= IGNORE_ERRORS;
switch (mi->memtype)
{
case KVADDR:
vaddr = mi->spec_addr;
break;
case PHYSADDR:
vaddr = mi->spec_addr < VTOP(vt->high_memory) ?
PTOV(mi->spec_addr) : BADADDR;
break;
}
orig_flags = mi->flags;
mi->retval = 0;
/*
* Check first for a possible symbolic display of the virtual
* address associated with mi->spec_addr or PTOV(mi->spec_addr).
*/
if (((vaddr >= kt->stext) && (vaddr <= kt->end)) ||
IS_MODULE_VADDR(mi->spec_addr)) {
if ((sp = value_search(vaddr, &offset))) {
show_flags = SHOW_LINENUM | SHOW_RADIX();
if (module_symbol(sp->value, NULL, NULL, NULL, 0))
show_flags |= SHOW_MODULE;
show_symbol(sp, offset, show_flags);
fprintf(fp, "\n");
}
}
/*
* Check for a valid mapped address.
*/
if ((mi->memtype == KVADDR) && IS_VMALLOC_ADDR(mi->spec_addr)) {
if (kvtop(NULL, mi->spec_addr, &paddr, 0)) {
mi->flags = orig_flags | VMLIST_VERIFY;
dump_vmlist(mi);
if (mi->retval) {
mi->flags = orig_flags;
dump_vmlist(mi);
fprintf(fp, "\n");
mi->spec_addr = paddr;
mi->memtype = PHYSADDR;
goto mem_map;
}
}
}
/*
* If the address is physical, check whether it's in vmalloc space.
*/
if (mi->memtype == PHYSADDR) {
mi->flags = orig_flags;
mi->flags |= GET_PHYS_TO_VMALLOC;
mi->retval = 0;
dump_vmlist(mi);
mi->flags &= ~GET_PHYS_TO_VMALLOC;
if (mi->retval) {
if ((sp = value_search(mi->retval, &offset))) {
show_symbol(sp, offset,
SHOW_LINENUM | SHOW_RADIX());
fprintf(fp, "\n");
}
dump_vmlist(mi);
fprintf(fp, "\n");
goto mem_map;
}
}
/*
* Check whether the containing page belongs to the slab subsystem.
*/
mi->flags = orig_flags;
mi->retval = 0;
if ((vaddr != BADADDR) && vaddr_to_kmem_cache(vaddr, buf, VERBOSE)) {
BZERO(&tmp_meminfo, sizeof(struct meminfo));
tmp_meminfo.spec_addr = vaddr;
tmp_meminfo.memtype = KVADDR;
tmp_meminfo.flags = mi->flags;
vt->dump_kmem_cache(&tmp_meminfo);
fprintf(fp, "\n");
}
if ((vaddr != BADADDR) && is_slab_page(mi, buf)) {
BZERO(&tmp_meminfo, sizeof(struct meminfo));
tmp_meminfo.spec_addr = vaddr;
tmp_meminfo.memtype = KVADDR;
tmp_meminfo.flags = mi->flags;
vt->dump_kmem_cache(&tmp_meminfo);
fprintf(fp, "\n");
}
/*
* Check free list.
*/
mi->flags = orig_flags;
mi->retval = 0;
vt->dump_free_pages(mi);
if (mi->retval)
fprintf(fp, "\n");
if (vt->page_hash_table) {
/*
* Check the page cache.
*/
mi->flags = orig_flags;
mi->retval = 0;
dump_page_hash_table(mi);
if (mi->retval)
fprintf(fp, "\n");
}
/*
* Check whether it's a current task or stack address.
*/
if ((mi->memtype == KVADDR) && (task = vaddr_in_task_struct(vaddr)) &&
(tc = task_to_context(task))) {
show_context(tc);
fprintf(fp, "\n");
} else if ((mi->memtype == KVADDR) && (task = stkptr_to_task(vaddr)) &&
(tc = task_to_context(task))) {
show_context(tc);
fprintf(fp, "\n");
}
mem_map:
mi->flags = orig_flags;
pc->curcmd_flags &= ~HEADER_PRINTED;
if (vaddr != BADADDR)
dump_mem_map(mi);
else
mi->retval = FALSE;
if (!mi->retval)
fprintf(fp, "%llx: %s address not found in mem map\n",
mi->spec_addr, memtype_string(mi->memtype, 0));
}
/*
* Determine whether an address is a page pointer from the mem_map[] array.
* If the caller requests it, return the associated physical address.
*/
int
is_page_ptr(ulong addr, physaddr_t *phys)
{
int n;
ulong ppstart, ppend;
struct node_table *nt;
ulong pgnum, node_size;
ulong nr, sec_addr;
ulong nr_mem_sections;
ulong coded_mem_map, mem_map, end_mem_map;
physaddr_t section_paddr;
if (IS_SPARSEMEM()) {
nr_mem_sections = NR_MEM_SECTIONS();
for (nr = 0; nr < nr_mem_sections ; nr++) {
if ((sec_addr = valid_section_nr(nr))) {
coded_mem_map = section_mem_map_addr(sec_addr);
mem_map = sparse_decode_mem_map(coded_mem_map, nr);
end_mem_map = mem_map + (PAGES_PER_SECTION() * SIZE(page));
if ((addr >= mem_map) && (addr < end_mem_map)) {
if ((addr - mem_map) % SIZE(page))
return FALSE;
if (phys) {
section_paddr = PTOB(section_nr_to_pfn(nr));
pgnum = (addr - mem_map) / SIZE(page);
*phys = section_paddr + ((physaddr_t)pgnum * PAGESIZE());
}
return TRUE;
}
}
}
return FALSE;
}
for (n = 0; n < vt->numnodes; n++) {
nt = &vt->node_table[n];
if ((vt->flags & V_MEM_MAP) && (vt->numnodes == 1))
node_size = vt->max_mapnr;
else
node_size = nt->size;
ppstart = nt->mem_map;
ppend = ppstart + (node_size * SIZE(page));
if ((addr < ppstart) || (addr >= ppend))
continue;
/*
* We're in the mem_map range -- but it is a page pointer?
*/
if ((addr - ppstart) % SIZE(page))
return FALSE;
if (phys) {
pgnum = (addr - nt->mem_map) / SIZE(page);
*phys = ((physaddr_t)pgnum * PAGESIZE()) + nt->start_paddr;
}
return TRUE;
}
return FALSE;
#ifdef PRE_NODES
ppstart = vt->mem_map;
ppend = ppstart + (vt->total_pages * vt->page_struct_len);
if ((addr < ppstart) || (addr >= ppend))
return FALSE;
if ((addr - ppstart) % vt->page_struct_len)
return FALSE;
return TRUE;
#endif
}
/*
* Return the physical address associated with this page pointer.
*/
static int
page_to_phys(ulong pp, physaddr_t *phys)
{
return(is_page_ptr(pp, phys));
}
/*
* Return the page pointer associated with this physical address.
*/
int
phys_to_page(physaddr_t phys, ulong *pp)
{
int n;
ulong pgnum;
struct node_table *nt;
physaddr_t pstart, pend;
ulong node_size;
if (IS_SPARSEMEM()) {
ulong map;
map = pfn_to_map(phys >> PAGESHIFT());
if (map) {
*pp = map;
return TRUE;
}
return FALSE;
}
for (n = 0; n < vt->numnodes; n++) {
nt = &vt->node_table[n];
if ((vt->flags & V_MEM_MAP) && (vt->numnodes == 1))
node_size = vt->max_mapnr;
else
node_size = nt->size;
pstart = nt->start_paddr;
pend = pstart + ((ulonglong)node_size * PAGESIZE());
if ((phys < pstart) || (phys >= pend))
continue;
/*
* We're in the physical range -- calculate the page.
*/
pgnum = BTOP(phys - pstart);
*pp = nt->mem_map + (pgnum * SIZE(page));
return TRUE;
}
return FALSE;
#ifdef PRE_NODES
if (phys >= (vt->total_pages * PAGESIZE()))
return FALSE;
pgnum = PTOB(BTOP(phys)) / PAGESIZE();
*pp = vt->mem_map + (pgnum * vt->page_struct_len);
return TRUE;
#endif
}
/*
* Fill the caller's buffer with up to maxlen non-NULL bytes
* starting from kvaddr, returning the number of consecutive
* non-NULL bytes found. If the buffer gets filled with
* maxlen bytes without a NULL, then the caller is reponsible
* for handling it.
*/
int
read_string(ulong kvaddr, char *buf, int maxlen)
{
int i;
BZERO(buf, maxlen);
readmem(kvaddr, KVADDR, buf, maxlen,
"read_string characters", QUIET|RETURN_ON_ERROR);
for (i = 0; i < maxlen; i++) {
if (buf[i] == NULLCHAR) {
BZERO(&buf[i], maxlen-i);
break;
}
}
return i;
}
/*
* "help -v" output
*/
void
dump_vm_table(int verbose)
{
int i;
struct node_table *nt;
int others;
ulong *up;
others = 0;
fprintf(fp, " flags: %lx %s(",
vt->flags, count_bits_long(vt->flags) > 4 ? "\n " : "");
if (vt->flags & NODES)
fprintf(fp, "%sNODES", others++ ? "|" : "");
if (vt->flags & NODES_ONLINE)
fprintf(fp, "%sNODES_ONLINE", others++ ? "|" : "");
if (vt->flags & ZONES)
fprintf(fp, "%sZONES", others++ ? "|" : "");
if (vt->flags & PERCPU_KMALLOC_V1)
fprintf(fp, "%sPERCPU_KMALLOC_V1", others++ ? "|" : "");
if (vt->flags & PERCPU_KMALLOC_V2)
fprintf(fp, "%sPERCPU_KMALLOC_V2", others++ ? "|" : "");
if (vt->flags & COMMON_VADDR)
fprintf(fp, "%sCOMMON_VADDR", others++ ? "|" : "");
if (vt->flags & KMEM_CACHE_INIT)
fprintf(fp, "%sKMEM_CACHE_INIT", others++ ? "|" : "");
if (vt->flags & V_MEM_MAP)
fprintf(fp, "%sV_MEM_MAP", others++ ? "|" : "");
if (vt->flags & KMEM_CACHE_UNAVAIL)
fprintf(fp, "%sKMEM_CACHE_UNAVAIL", others++ ? "|" : "");
if (vt->flags & DISCONTIGMEM)
fprintf(fp, "%sDISCONTIGMEM", others++ ? "|" : "");
if (vt->flags & FLATMEM)
fprintf(fp, "%sFLATMEM", others++ ? "|" : "");
if (vt->flags & SPARSEMEM)
fprintf(fp, "%sSPARSEMEM", others++ ? "|" : "");\
if (vt->flags & SPARSEMEM_EX)
fprintf(fp, "%sSPARSEMEM_EX", others++ ? "|" : "");\
if (vt->flags & KMEM_CACHE_DELAY)
fprintf(fp, "%sKMEM_CACHE_DELAY", others++ ? "|" : "");\
if (vt->flags & PERCPU_KMALLOC_V2_NODES)
fprintf(fp, "%sPERCPU_KMALLOC_V2_NODES", others++ ? "|" : "");\
if (vt->flags & VM_STAT)
fprintf(fp, "%sVM_STAT", others++ ? "|" : "");\
if (vt->flags & KMALLOC_SLUB)
fprintf(fp, "%sKMALLOC_SLUB", others++ ? "|" : "");\
if (vt->flags & KMALLOC_COMMON)
fprintf(fp, "%sKMALLOC_COMMON", others++ ? "|" : "");\
if (vt->flags & SLAB_OVERLOAD_PAGE)
fprintf(fp, "%sSLAB_OVERLOAD_PAGE", others++ ? "|" : "");\
if (vt->flags & USE_VMAP_AREA)
fprintf(fp, "%sUSE_VMAP_AREA", others++ ? "|" : "");\
if (vt->flags & CONFIG_NUMA)
fprintf(fp, "%sCONFIG_NUMA", others++ ? "|" : "");\
if (vt->flags & VM_EVENT)
fprintf(fp, "%sVM_EVENT", others++ ? "|" : "");\
if (vt->flags & PGCNT_ADJ)
fprintf(fp, "%sPGCNT_ADJ", others++ ? "|" : "");\
if (vt->flags & PAGEFLAGS)
fprintf(fp, "%sPAGEFLAGS", others++ ? "|" : "");\
if (vt->flags & SWAPINFO_V1)
fprintf(fp, "%sSWAPINFO_V1", others++ ? "|" : "");\
if (vt->flags & SWAPINFO_V2)
fprintf(fp, "%sSWAPINFO_V2", others++ ? "|" : "");\
if (vt->flags & NODELISTS_IS_PTR)
fprintf(fp, "%sNODELISTS_IS_PTR", others++ ? "|" : "");\
if (vt->flags & VM_INIT)
fprintf(fp, "%sVM_INIT", others++ ? "|" : "");\
fprintf(fp, ")\n");
if (vt->kernel_pgd[0] == vt->kernel_pgd[1])
fprintf(fp, " kernel_pgd[NR_CPUS]: %lx ...\n",
vt->kernel_pgd[0]);
else {
fprintf(fp, " kernel_pgd[NR_CPUS]: ");
for (i = 0; i < NR_CPUS; i++) {
if ((i % 4) == 0)
fprintf(fp, "\n ");
fprintf(fp, "%lx ", vt->kernel_pgd[i]);
}
fprintf(fp, "\n");
}
fprintf(fp, " high_memory: %lx\n", vt->high_memory);
fprintf(fp, " vmalloc_start: %lx\n", vt->vmalloc_start);
fprintf(fp, " mem_map: %lx\n", vt->mem_map);
fprintf(fp, " total_pages: %ld\n", vt->total_pages);
fprintf(fp, " max_mapnr: %ld\n", vt->max_mapnr);
fprintf(fp, " totalram_pages: %ld\n", vt->totalram_pages);
fprintf(fp, " totalhigh_pages: %ld\n", vt->totalhigh_pages);
fprintf(fp, " num_physpages: %ld\n", vt->num_physpages);
fprintf(fp, " page_hash_table: %lx\n", vt->page_hash_table);
fprintf(fp, "page_hash_table_len: %d\n", vt->page_hash_table_len);
fprintf(fp, " kmem_max_c_num: %ld\n", vt->kmem_max_c_num);
fprintf(fp, " kmem_max_limit: %ld\n", vt->kmem_max_limit);
fprintf(fp, " kmem_max_cpus: %ld\n", vt->kmem_max_cpus);
fprintf(fp, " kmem_cache_count: %ld\n", vt->kmem_cache_count);
fprintf(fp, " kmem_cache_namelen: %d\n", vt->kmem_cache_namelen);
fprintf(fp, "kmem_cache_len_nodes: %ld\n", vt->kmem_cache_len_nodes);
fprintf(fp, " nr_bad_slab_caches: %d\n", vt->nr_bad_slab_caches);
if (!vt->nr_bad_slab_caches)
fprintf(fp, " bad_slab_caches: (unused)\n");
else {
for (i = 0; i < vt->nr_bad_slab_caches; i++) {
fprintf(fp, " bad_slab_caches[%d]: %lx\n",
i, vt->bad_slab_caches[i]);
}
}
fprintf(fp, " paddr_prlen: %d\n", vt->paddr_prlen);
fprintf(fp, " numnodes: %d\n", vt->numnodes);
fprintf(fp, " nr_zones: %d\n", vt->nr_zones);
fprintf(fp, " nr_free_areas: %d\n", vt->nr_free_areas);
for (i = 0; i < vt->numnodes; i++) {
nt = &vt->node_table[i];
fprintf(fp, " node_table[%d]: \n", i);
fprintf(fp, " id: %d\n", nt->node_id);
fprintf(fp, " pgdat: %lx\n", nt->pgdat);
fprintf(fp, " size: %ld\n", nt->size);
fprintf(fp, " present: %ld\n", nt->present);
fprintf(fp, " mem_map: %lx\n", nt->mem_map);
fprintf(fp, " start_paddr: %llx\n", nt->start_paddr);
fprintf(fp, " start_mapnr: %ld\n", nt->start_mapnr);
}
fprintf(fp, " dump_free_pages: ");
if (vt->dump_free_pages == dump_free_pages)
fprintf(fp, "dump_free_pages()\n");
else if (vt->dump_free_pages == dump_free_pages_zones_v1)
fprintf(fp, "dump_free_pages_zones_v1()\n");
else if (vt->dump_free_pages == dump_free_pages_zones_v2)
fprintf(fp, "dump_free_pages_zones_v2()\n");
else if (vt->dump_free_pages == dump_multidimensional_free_pages)
fprintf(fp, "dump_multidimensional_free_pages()\n");
else
fprintf(fp, "%lx (unknown)\n", (ulong)vt->dump_free_pages);
fprintf(fp, " dump_kmem_cache: ");
if (vt->dump_kmem_cache == dump_kmem_cache)
fprintf(fp, "dump_kmem_cache()\n");
else if (vt->dump_kmem_cache == dump_kmem_cache_percpu_v1)
fprintf(fp, "dump_kmem_cache_percpu_v1()\n");
else if (vt->dump_kmem_cache == dump_kmem_cache_percpu_v2)
fprintf(fp, "dump_kmem_cache_percpu_v2()\n");
else if (vt->dump_kmem_cache == dump_kmem_cache_slub)
fprintf(fp, "dump_kmem_cache_slub()\n");
else
fprintf(fp, "%lx (unknown)\n", (ulong)vt->dump_kmem_cache);
fprintf(fp, " slab_data: %lx\n", (ulong)vt->slab_data);
if (verbose)
dump_saved_slab_data();
fprintf(fp, " cpu_slab_type: %d\n", vt->cpu_slab_type);
fprintf(fp, " nr_swapfiles: %d\n", vt->nr_swapfiles);
fprintf(fp, " last_swap_read: %lx\n", vt->last_swap_read);
fprintf(fp, " swap_info_struct: %lx\n", (ulong)vt->swap_info_struct);
fprintf(fp, " mem_sec: %lx\n", (ulong)vt->mem_sec);
fprintf(fp, " mem_section: %lx\n", (ulong)vt->mem_section);
fprintf(fp, " ZONE_HIGHMEM: %d\n", vt->ZONE_HIGHMEM);
fprintf(fp, "node_online_map_len: %d\n", vt->node_online_map_len);
if (vt->node_online_map_len) {
fprintf(fp, " node_online_map: ");
up = (ulong *)vt->node_online_map;
for (i = 0; i < vt->node_online_map_len; i++) {
fprintf(fp, "%s%lx", i ? ", " : "[", *up);
up++;
}
fprintf(fp, "]\n");
} else {
fprintf(fp, " node_online_map: (unused)\n");
}
fprintf(fp, " nr_vm_stat_items: %d\n", vt->nr_vm_stat_items);
fprintf(fp, " vm_stat_items: %s", (vt->flags & VM_STAT) ?
"\n" : "(not used)\n");
for (i = 0; i < vt->nr_vm_stat_items; i++)
fprintf(fp, " [%d] %s\n", i, vt->vm_stat_items[i]);
fprintf(fp, " nr_vm_event_items: %d\n", vt->nr_vm_event_items);
fprintf(fp, " vm_event_items: %s", (vt->flags & VM_EVENT) ?
"\n" : "(not used)\n");
for (i = 0; i < vt->nr_vm_event_items; i++)
fprintf(fp, " [%d] %s\n", i, vt->vm_event_items[i]);
fprintf(fp, " PG_reserved: %lx\n", vt->PG_reserved);
fprintf(fp, " PG_slab: %ld (%lx)\n", vt->PG_slab,
(ulong)1 << vt->PG_slab);
fprintf(fp, " PG_head_tail_mask: %lx\n", vt->PG_head_tail_mask);
fprintf(fp, " nr_pageflags: %d\n", vt->nr_pageflags);
fprintf(fp, " pageflags_data: %s\n",
vt->nr_pageflags ? "" : "(not used)");
for (i = 0; i < vt->nr_pageflags; i++) {
fprintf(fp, " %s[%d] %08lx: %s\n",
i < 10 ? " " : "", i,
vt->pageflags_data[i].mask,
vt->pageflags_data[i].name);
}
dump_vma_cache(VERBOSE);
}
/*
* Calculate the amount of memory referenced in the kernel-specific "nodes".
*/
uint64_t
total_node_memory()
{
int i;
struct node_table *nt;
uint64_t total;
for (i = total = 0; i < vt->numnodes; i++) {
nt = &vt->node_table[i];
if (CRASHDEBUG(1)) {
console("node_table[%d]: \n", i);
console(" id: %d\n", nt->node_id);
console(" pgdat: %lx\n", nt->pgdat);
console(" size: %ld\n", nt->size);
console(" present: %ld\n", nt->present);
console(" mem_map: %lx\n", nt->mem_map);
console(" start_paddr: %lx\n", nt->start_paddr);
console(" start_mapnr: %ld\n", nt->start_mapnr);
}
if (nt->present)
total += (uint64_t)((uint64_t)nt->present * (uint64_t)PAGESIZE());
else
total += (uint64_t)((uint64_t)nt->size * (uint64_t)PAGESIZE());
}
return total;
}
/*
* Dump just the vm_area_struct cache table data so that it can be
* called from above or for debug purposes.
*/
void
dump_vma_cache(ulong verbose)
{
int i;
ulong vhits;
if (!verbose)
goto show_hits;
for (i = 0; i < VMA_CACHE; i++)
fprintf(fp, " cached_vma[%2d]: %lx (%ld)\n",
i, vt->cached_vma[i],
vt->cached_vma_hits[i]);
fprintf(fp, " vma_cache: %lx\n", (ulong)vt->vma_cache);
fprintf(fp, " vma_cache_index: %d\n", vt->vma_cache_index);
fprintf(fp, " vma_cache_fills: %ld\n", vt->vma_cache_fills);
fflush(fp);
show_hits:
if (vt->vma_cache_fills) {
for (i = vhits = 0; i < VMA_CACHE; i++)
vhits += vt->cached_vma_hits[i];
fprintf(stderr, "%s vma hit rate: %2ld%% (%ld of %ld)\n",
verbose ? "" : " ",
(vhits * 100)/vt->vma_cache_fills,
vhits, vt->vma_cache_fills);
}
}
/*
* Guess at the "real" amount of physical memory installed, formatting
* it in a MB or GB based string.
*/
char *
get_memory_size(char *buf)
{
uint64_t total;
ulong next_gig;
#ifdef OLDWAY
ulong mbs, gbs;
#endif
total = machdep->memory_size();
if ((next_gig = roundup(total, GIGABYTES(1)))) {
if ((next_gig - total) <= MEGABYTES(64))
total = next_gig;
}
return (pages_to_size((ulong)(total/PAGESIZE()), buf));
#ifdef OLDWAY
gbs = (ulong)(total/GIGABYTES(1));
mbs = (ulong)(total/MEGABYTES(1));
if (gbs)
mbs = (total % GIGABYTES(1))/MEGABYTES(1);
if (total%MEGABYTES(1))
mbs++;
if (gbs)
sprintf(buf, mbs ? "%ld GB %ld MB" : "%ld GB", gbs, mbs);
else
sprintf(buf, "%ld MB", mbs);
return buf;
#endif
}
/*
* For use by architectures not having machine-specific manners for
* best determining physical memory size.
*/
uint64_t
generic_memory_size(void)
{
if (machdep->memsize)
return machdep->memsize;
return (machdep->memsize = total_node_memory());
}
/*
* Determine whether a virtual address is user or kernel or ambiguous.
*/
int
vaddr_type(ulong vaddr, struct task_context *tc)
{
int memtype, found;
if (!tc)
tc = CURRENT_CONTEXT();
memtype = found = 0;
if (machdep->is_uvaddr(vaddr, tc)) {
memtype |= UVADDR;
found++;
}
if (machdep->is_kvaddr(vaddr)) {
memtype |= KVADDR;
found++;
}
if (found == 1)
return memtype;
else
return AMBIGUOUS;
}
/*
* Determine the first valid user space address
*/
static int
address_space_start(struct task_context *tc, ulong *addr)
{
ulong vma;
char *vma_buf;
if (!tc->mm_struct)
return FALSE;
fill_mm_struct(tc->mm_struct);
vma = ULONG(tt->mm_struct + OFFSET(mm_struct_mmap));
if (!vma)
return FALSE;
vma_buf = fill_vma_cache(vma);
*addr = ULONG(vma_buf + OFFSET(vm_area_struct_vm_start));
return TRUE;
}
int
generic_get_kvaddr_ranges(struct vaddr_range *rp)
{
int cnt;
if (XEN_HYPER_MODE())
return 0;
cnt = 0;
rp[cnt].type = KVADDR_UNITY_MAP;
rp[cnt].start = machdep->kvbase;
rp[cnt++].end = vt->vmalloc_start;
rp[cnt].type = KVADDR_VMALLOC;
rp[cnt].start = vt->vmalloc_start;
rp[cnt++].end = (ulong)(-1);
return cnt;
}
/*
* Search for a given value between a starting and ending address range,
* applying an optional mask for "don't care" bits. As an alternative
* to entering the starting address value, -k means "start of kernel address
* space". For processors with ambiguous user/kernel address spaces,
* -u or -k must be used (with or without -s) as a differentiator.
*/
void
cmd_search(void)
{
int i, c, memtype, ranges, context, max;
ulonglong start, end;
ulong value, mask, len;
ulong uvaddr_start, uvaddr_end;
ulong kvaddr_start, kvaddr_end, range_end;
int sflag, Kflag, Vflag, pflag, tflag;
struct searchinfo searchinfo;
struct syment *sp;
struct node_table *nt;
struct vaddr_range vaddr_ranges[MAX_KVADDR_RANGES];
struct vaddr_range *vrp;
struct task_context *tc;
#define vaddr_overflow(ADDR) (BITS32() && ((ADDR) > 0xffffffffULL))
#define uint_overflow(VALUE) ((VALUE) > 0xffffffffUL)
#define ushort_overflow(VALUE) ((VALUE) > 0xffffUL)
context = max = 0;
start = end = 0;
value = mask = sflag = pflag = Kflag = Vflag = memtype = len = tflag = 0;
kvaddr_start = kvaddr_end = 0;
uvaddr_start = UNINITIALIZED;
uvaddr_end = COMMON_VADDR_SPACE() ? (ulong)(-1) : machdep->kvbase;
BZERO(&searchinfo, sizeof(struct searchinfo));
vrp = &vaddr_ranges[0];
ranges = machdep->get_kvaddr_ranges(vrp);
if (CRASHDEBUG(1)) {
fprintf(fp, "kvaddr ranges:\n");
for (i = 0; i < ranges; i++) {
fprintf(fp, " [%d] %lx %lx ", i,
vrp[i].start, vrp[i].end);
switch (vrp[i].type)
{
case KVADDR_UNITY_MAP:
fprintf(fp, "KVADDR_UNITY_MAP\n");
break;
case KVADDR_START_MAP:
fprintf(fp, "KVADDR_START_MAP\n");
break;
case KVADDR_VMALLOC:
fprintf(fp, "KVADDR_VMALLOC\n");
break;
case KVADDR_MODULES:
fprintf(fp, "KVADDR_MODULES\n");
break;
case KVADDR_VMEMMAP:
fprintf(fp, "KVADDR_VMEMMAP\n");
break;
}
}
}
searchinfo.mode = SEARCH_ULONG; /* default search */
while ((c = getopt(argcnt, args, "tl:ukKVps:e:v:m:hwcx:")) != EOF) {
switch(c)
{
case 'u':
if (XEN_HYPER_MODE())
error(FATAL,
"-u option is not applicable to the "
"Xen hypervisor\n");
if (is_kernel_thread(CURRENT_TASK()) ||
!task_mm(CURRENT_TASK(), TRUE))
error(FATAL,
"current context has no user address space\n");
if (!sflag) {
address_space_start(CURRENT_CONTEXT(),
&uvaddr_start);
start = (ulonglong)uvaddr_start;
}
memtype = UVADDR;
sflag++;
break;
case 'p':
if (XEN_HYPER_MODE())
error(FATAL,
"-p option is not applicable to the "
"Xen hypervisor\n");
memtype = PHYSADDR;
if (!sflag) {
nt = &vt->node_table[0];
start = nt->start_paddr;
}
sflag++;
break;
case 'V':
case 'K':
case 'k':
if (XEN_HYPER_MODE())
error(FATAL,
"-%c option is not applicable to the "
"Xen hypervisor\n", c);
if (!sflag)
start = vrp[0].start;
memtype = KVADDR;
sflag++;
if (c == 'K')
Kflag++;
else if (c == 'V')
Vflag++;
break;
case 's':
if ((sp = symbol_search(optarg)))
start = (ulonglong)sp->value;
else
start = htoll(optarg, FAULT_ON_ERROR, NULL);
sflag++;
break;
case 'e':
if ((sp = symbol_search(optarg)))
end = (ulonglong)sp->value;
else
end = htoll(optarg, FAULT_ON_ERROR, NULL);
if (!end)
error(FATAL, "invalid ending address: 0\n");
break;
case 'l':
len = stol(optarg, FAULT_ON_ERROR, NULL);
break;
case 'm':
mask = htol(optarg, FAULT_ON_ERROR, NULL);
break;
case 'h':
if (searchinfo.mode != SEARCH_DEFAULT)
error(INFO, "WARNING: overriding previously"
" set search mode with \"h\"\n");
searchinfo.mode = SEARCH_USHORT;
break;
case 'w':
if (searchinfo.mode != SEARCH_DEFAULT)
error(INFO, "WARNING: overriding previously"
" set search mode with \"w\"\n");
searchinfo.mode = SEARCH_UINT;
break;
case 'c':
if (searchinfo.mode != SEARCH_DEFAULT)
error(INFO, "WARNING: overriding previously"
" set search type with \"c\"\n");
searchinfo.mode = SEARCH_CHARS;
break;
case 'x':
context = dtoi(optarg, FAULT_ON_ERROR, NULL);
break;
case 't':
if (XEN_HYPER_MODE())
error(FATAL,
"-t option is not applicable to the "
"Xen hypervisor\n");
tflag++;
break;
default:
argerrs++;
break;
}
}
if (tflag && (memtype || start || end || len))
error(FATAL,
"-t option cannot be used with other "
"memory-selection options\n");
if (XEN_HYPER_MODE()) {
memtype = KVADDR;
if (!sflag)
error(FATAL,
"the \"-s start\" option is required for"
" the Xen hypervisor\n");
} else if (!memtype) {
memtype = KVADDR;
if (!tflag && !sflag++)
start = vrp[0].start;
}
if (argerrs || (!sflag && !tflag) || !args[optind] ||
(len && end) || !memtype)
cmd_usage(pc->curcmd, SYNOPSIS);
searchinfo.memtype = memtype;
/*
* Verify starting address.
*/
switch (memtype)
{
case UVADDR:
if (vaddr_overflow(start) ||
!IS_UVADDR((ulong)start, CURRENT_CONTEXT())) {
error(INFO, "invalid user virtual address: %llx\n",
start);
cmd_usage(pc->curcmd, SYNOPSIS);
}
break;
case KVADDR:
if (tflag)
break;
if (vaddr_overflow(start) ||
!IS_KVADDR((ulong)start)) {
error(INFO, "invalid kernel virtual address: %llx\n",
(ulonglong)start);
cmd_usage(pc->curcmd, SYNOPSIS);
}
break;
case AMBIGUOUS:
error(INFO,
"ambiguous virtual address: %llx (requires -u or -k)\n",
(ulonglong)start);
cmd_usage(pc->curcmd, SYNOPSIS);
}
/*
* Set up ending address if necessary.
*/
if (!end && !len && !tflag) {
switch (memtype)
{
case UVADDR:
end = (ulonglong)uvaddr_end;
break;
case KVADDR:
if (XEN_HYPER_MODE())
end = (ulong)(-1);
else {
range_end = 0;
for (i = 0; i < ranges; i++) {
if (vrp[i].end > range_end)
range_end = vrp[i].end;
}
end = (ulonglong)range_end;
}
break;
case PHYSADDR:
nt = &vt->node_table[vt->numnodes-1];
end = nt->start_paddr + (nt->size * PAGESIZE());
break;
}
} else if (len)
end = start + len;
/*
* Final verification and per-type start/end variable setting.
*/
switch (memtype)
{
case UVADDR:
uvaddr_start = (ulong)start;
if (end > (ulonglong)uvaddr_end) {
error(INFO,
"ending address %lx is in kernel space: %llx\n", end);
cmd_usage(pc->curcmd, SYNOPSIS);
}
if (end < (ulonglong)uvaddr_end)
uvaddr_end = (ulong)end;
if (uvaddr_end < uvaddr_start) {
error(INFO,
"ending address %lx is below starting address %lx\n",
uvaddr_end, uvaddr_start);
cmd_usage(pc->curcmd, SYNOPSIS);
}
break;
case KVADDR:
if (tflag)
break;
kvaddr_start = (ulong)start;
kvaddr_end = (ulong)end;
if (kvaddr_end < kvaddr_start) {
error(INFO,
"ending address %lx is below starting address %lx\n",
kvaddr_end, kvaddr_start);
cmd_usage(pc->curcmd, SYNOPSIS);
}
break;
case PHYSADDR:
if (end < start) {
error(INFO,
"ending address %llx is below starting address %llx\n",
(ulonglong)end, (ulonglong)start);
cmd_usage(pc->curcmd, SYNOPSIS);
}
break;
}
if (mask) {
switch (searchinfo.mode)
{
case SEARCH_ULONG:
searchinfo.s_parms.s_ulong.mask = mask;
break;
case SEARCH_UINT:
searchinfo.s_parms.s_uint.mask = mask;
break;
case SEARCH_USHORT:
searchinfo.s_parms.s_ushort.mask = mask;
break;
case SEARCH_CHARS:
error(INFO, "mask ignored on string search\n");
break;
}
}
if (context) {
switch (searchinfo.mode)
{
case SEARCH_ULONG:
max = PAGESIZE()/sizeof(long);
break;
case SEARCH_UINT:
max = PAGESIZE()/sizeof(int);
break;
case SEARCH_USHORT:
max = PAGESIZE()/sizeof(short);
break;
case SEARCH_CHARS:
error(FATAL, "-x option is not allowed with -c\n");
break;
}
if (context > max)
error(FATAL,
"context value %d is too large: maximum is %d\n",
context, max);
searchinfo.context = context;
}
searchinfo.vcnt = 0;
searchinfo.val = UNUSED;
while (args[optind]) {
switch (searchinfo.mode)
{
case SEARCH_ULONG:
if (can_eval(args[optind])) {
value = eval(args[optind], FAULT_ON_ERROR, NULL);
searchinfo.s_parms.s_ulong.opt_string[searchinfo.vcnt] =
mask ? NULL : args[optind];
} else if (symbol_exists(args[optind])) {
value = symbol_value(args[optind]);
searchinfo.s_parms.s_ulong.opt_string[searchinfo.vcnt] =
mask ? NULL : args[optind];
} else
value = htol(args[optind], FAULT_ON_ERROR, NULL);
searchinfo.s_parms.s_ulong.value[searchinfo.vcnt] = value;
searchinfo.vcnt++;
break;
case SEARCH_UINT:
if (can_eval(args[optind])) {
value = eval(args[optind], FAULT_ON_ERROR, NULL);
searchinfo.s_parms.s_uint.opt_string[searchinfo.vcnt] =
mask ? NULL : args[optind];
} else if (symbol_exists(args[optind])) {
value = symbol_value(args[optind]);
searchinfo.s_parms.s_uint.opt_string[searchinfo.vcnt] =
mask ? NULL : args[optind];
} else
value = htol(args[optind], FAULT_ON_ERROR, NULL);
searchinfo.s_parms.s_uint.value[searchinfo.vcnt] = value;
if (uint_overflow(value))
error(FATAL, "value too large for -w option: %lx %s\n",
value, show_opt_string(&searchinfo));
searchinfo.vcnt++;
break;
case SEARCH_USHORT:
if (can_eval(args[optind])) {
value = eval(args[optind], FAULT_ON_ERROR, NULL);
searchinfo.s_parms.s_ushort.opt_string[searchinfo.vcnt] =
mask ? NULL : args[optind];
} else if (symbol_exists(args[optind])) {
value = symbol_value(args[optind]);
searchinfo.s_parms.s_ushort.opt_string[searchinfo.vcnt] =
mask ? NULL : args[optind];
} else
value = htol(args[optind], FAULT_ON_ERROR, NULL);
searchinfo.s_parms.s_ushort.value[searchinfo.vcnt] = value;
if (ushort_overflow(value))
error(FATAL, "value too large for -h option: %lx %s\n",
value, show_opt_string(&searchinfo));
searchinfo.vcnt++;
break;
case SEARCH_CHARS:
/* parser can deliver empty strings */
if (strlen(args[optind])) {
searchinfo.s_parms.s_chars.value[searchinfo.vcnt] =
args[optind];
searchinfo.s_parms.s_chars.len[searchinfo.vcnt] =
strlen(args[optind]);
searchinfo.vcnt++;
}
break;
}
optind++;
}
if (!searchinfo.vcnt)
cmd_usage(pc->curcmd, SYNOPSIS);
switch (memtype)
{
case PHYSADDR:
searchinfo.paddr_start = start;
searchinfo.paddr_end = end;
search_physical(&searchinfo);
break;
case UVADDR:
searchinfo.vaddr_start = uvaddr_start;
searchinfo.vaddr_end = uvaddr_end;
search_virtual(&searchinfo);
break;
case KVADDR:
if (XEN_HYPER_MODE()) {
searchinfo.vaddr_start = kvaddr_start;
searchinfo.vaddr_end = kvaddr_end;
search_virtual(&searchinfo);
break;
}
if (tflag) {
searchinfo.tasks_found = 0;
tc = FIRST_CONTEXT();
for (i = 0; i < RUNNING_TASKS(); i++, tc++) {
searchinfo.vaddr_start = GET_STACKBASE(tc->task);
searchinfo.vaddr_end = GET_STACKTOP(tc->task);
searchinfo.task_context = tc;
searchinfo.do_task_header = TRUE;
search_virtual(&searchinfo);
}
break;
}
for (i = 0; i < ranges; i++) {
if ((kvaddr_start >= vrp[i].end) ||
(kvaddr_end <= vrp[i].start))
continue;
switch (vrp[i].type)
{
case KVADDR_UNITY_MAP:
case KVADDR_START_MAP:
if (Vflag)
continue;
break;
case KVADDR_VMALLOC:
case KVADDR_MODULES:
case KVADDR_VMEMMAP:
if (Kflag)
continue;
break;
}
pc->curcmd_private = vrp[i].type;
searchinfo.vaddr_start =
kvaddr_start > vrp[i].start ?
kvaddr_start : vrp[i].start;
searchinfo.vaddr_end =
(kvaddr_end < vrp[i].end) ?
kvaddr_end : vrp[i].end;
search_virtual(&searchinfo);
}
break;
}
}
/*
* Do the work for cmd_search().
*/
static char *
show_opt_string(struct searchinfo *si)
{
char *opt_string;
int index;
index = (si->val == UNUSED) ? si->vcnt : si->val;
switch (si->mode)
{
case SEARCH_USHORT:
opt_string = si->s_parms.s_ushort.opt_string[index];
break;
case SEARCH_UINT:
opt_string = si->s_parms.s_uint.opt_string[index];
break;
case SEARCH_ULONG:
default:
opt_string = si->s_parms.s_ulong.opt_string[index];
break;
}
if (!opt_string)
return "";
else if (FIRSTCHAR(opt_string) == '(')
return opt_string;
else {
sprintf(si->buf, "(%s)", opt_string);
return si->buf;
}
}
#define SEARCHMASK(X) ((X) | mask)
static void
display_with_pre_and_post(void *bufptr, ulonglong addr, struct searchinfo *si)
{
int ctx, memtype, t, amount;
ulonglong addr_d;
ulong flag;
char buf[BUFSIZE];
ctx = si->context;
memtype = si->memtype;
flag = HEXADECIMAL|NO_ERROR|ASCII_ENDLINE;
switch (si->mode)
{
case SEARCH_USHORT:
t = sizeof(ushort);
break;
case SEARCH_UINT:
t = sizeof(uint);
break;
case SEARCH_ULONG:
default:
t = sizeof(ulong);
break;
}
switch (t)
{
case 8:
flag |= DISPLAY_64;
break;
case 4:
flag |= DISPLAY_32;
break;
case 2:
flag |= DISPLAY_16;
break;
}
amount = ctx * t;
addr_d = addr - amount < 0 ? 0 : addr - amount;
display_memory(addr_d, ctx, flag, memtype, NULL);
BZERO(buf, BUFSIZE);
fprintf(fp, "%s: ", mkstring(buf, VADDR_PRLEN,
RJUST|LONGLONG_HEX, MKSTR(&addr)));
switch(si->mode)
{
case SEARCH_ULONG:
fprintf(fp, "%lx %s\n", *((ulong *)bufptr),
show_opt_string(si));
break;
case SEARCH_UINT:
fprintf(fp, "%x %s\n", *((uint *)bufptr),
show_opt_string(si));
break;
case SEARCH_USHORT:
fprintf(fp, "%x %s\n", *((ushort *)bufptr),
show_opt_string(si));
break;
}
addr_d = addr + t;
display_memory(addr_d, ctx, flag, memtype, NULL);
fprintf(fp, "\n");
}
static ulong
search_ulong(ulong *bufptr, ulong addr, int longcnt, struct searchinfo *si)
{
int i;
ulong mask = si->s_parms.s_ulong.mask;
for (i = 0; i < longcnt; i++, bufptr++, addr += sizeof(long)) {
for (si->val = 0; si->val < si->vcnt; si->val++) {
if (SEARCHMASK(*bufptr) ==
SEARCHMASK(si->s_parms.s_ulong.value[si->val])) {
if (si->do_task_header) {
print_task_header(fp, si->task_context,
si->tasks_found);
si->do_task_header = FALSE;
si->tasks_found++;
}
if (si->context)
display_with_pre_and_post(bufptr, addr, si);
else
fprintf(fp, "%lx: %lx %s\n", addr, *bufptr,
show_opt_string(si));
}
}
}
return addr;
}
/* phys search uses ulonglong address representation */
static ulonglong
search_ulong_p(ulong *bufptr, ulonglong addr, int longcnt, struct searchinfo *si)
{
int i;
ulong mask = si->s_parms.s_ulong.mask;
for (i = 0; i < longcnt; i++, bufptr++, addr += sizeof(long)) {
for (si->val = 0; si->val < si->vcnt; si->val++) {
if (SEARCHMASK(*bufptr) ==
SEARCHMASK(si->s_parms.s_ulong.value[si->val])) {
if (si->context)
display_with_pre_and_post(bufptr, addr, si);
else
fprintf(fp, "%llx: %lx %s\n", addr, *bufptr,
show_opt_string(si));
}
}
}
return addr;
}
static ulong
search_uint(ulong *bufptr, ulong addr, int longcnt, struct searchinfo *si)
{
int i;
int cnt = longcnt * (sizeof(long)/sizeof(int));
uint *ptr = (uint *)bufptr;
uint mask = si->s_parms.s_uint.mask;
for (i = 0; i < cnt; i++, ptr++, addr += sizeof(int)) {
for (si->val = 0; si->val < si->vcnt; si->val++) {
if (SEARCHMASK(*ptr) ==
SEARCHMASK(si->s_parms.s_uint.value[si->val])) {
if (si->do_task_header) {
print_task_header(fp, si->task_context,
si->tasks_found);
si->do_task_header = FALSE;
si->tasks_found++;
}
if (si->context)
display_with_pre_and_post(ptr, addr, si);
else
fprintf(fp, "%lx: %x %s\n", addr, *ptr,
show_opt_string(si));
}
}
}
return addr;
}
/* phys search uses ulonglong address representation */
static ulonglong
search_uint_p(ulong *bufptr, ulonglong addr, int longcnt, struct searchinfo *si)
{
int i;
int cnt = longcnt * (sizeof(long)/sizeof(int));
uint *ptr = (uint *)bufptr;
uint mask = si->s_parms.s_uint.mask;
for (i = 0; i < cnt; i++, ptr++, addr += sizeof(int)) {
for (si->val = 0; si->val < si->vcnt; si->val++) {
if (SEARCHMASK(*ptr) ==
SEARCHMASK(si->s_parms.s_uint.value[si->val])) {
if (si->context)
display_with_pre_and_post(ptr, addr, si);
else
fprintf(fp, "%llx: %x %s\n", addr, *ptr,
show_opt_string(si));
}
}
}
return addr;
}
static ulong
search_ushort(ulong *bufptr, ulong addr, int longcnt, struct searchinfo *si)
{
int i;
int cnt = longcnt * (sizeof(long)/sizeof(short));
ushort *ptr = (ushort *)bufptr;
ushort mask = si->s_parms.s_ushort.mask;
for (i = 0; i < cnt; i++, ptr++, addr += sizeof(short)) {
for (si->val = 0; si->val < si->vcnt; si->val++) {
if (SEARCHMASK(*ptr) ==
SEARCHMASK(si->s_parms.s_ushort.value[si->val])) {
if (si->do_task_header) {
print_task_header(fp, si->task_context,
si->tasks_found);
si->do_task_header = FALSE;
si->tasks_found++;
}
if (si->context)
display_with_pre_and_post(ptr, addr, si);
else
fprintf(fp, "%lx: %x %s\n", addr, *ptr,
show_opt_string(si));
}
}
}
return addr;
}
/* phys search uses ulonglong address representation */
static ulonglong
search_ushort_p(ulong *bufptr, ulonglong addr, int longcnt, struct searchinfo *si)
{
int i;
int cnt = longcnt * (sizeof(long)/sizeof(short));
ushort *ptr = (ushort *)bufptr;
ushort mask = si->s_parms.s_ushort.mask;
for (i = 0; i < cnt; i++, ptr++, addr += sizeof(short)) {
for (si->val = 0; si->val < si->vcnt; si->val++) {
if (SEARCHMASK(*ptr) ==
SEARCHMASK(si->s_parms.s_ushort.value[si->val])) {
if (si->context)
display_with_pre_and_post(ptr, addr, si);
else
fprintf(fp, "%llx: %x %s\n", addr, *ptr,
show_opt_string(si));
}
}
}
return addr;
}
/*
* String search "memory" to remember possible matches that cross
* page (or search buffer) boundaries.
* The cross_match zone is the last strlen-1 chars of the page for
* each of the possible targets.
*/
struct cross_match {
int cnt; /* possible hits in the cross_match zone */
ulong addr; /* starting addr of crossing match zone for this target */
ulonglong addr_p; /* for physical search */
char hit[BUFSIZE]; /* array of hit locations in the crossing match zone */
/* This should really be the much-smaller MAXARGLEN, but
* no one seems to be enforcing that in the parser.
*/
} cross[MAXARGS];
ulong cross_match_next_addr; /* the expected starting value of the next page */
ulonglong cross_match_next_addr_p; /* the expected starting value of the next physical page */
#define CHARS_CTX 56
static void
report_match(struct searchinfo *si, ulong addr, char *ptr1, int len1, char *ptr2, int len2)
{
int i;
if (si->do_task_header) {
print_task_header(fp, si->task_context, si->tasks_found);
si->do_task_header = FALSE;
si->tasks_found++;
}
fprintf(fp, "%lx: ", addr);
for (i = 0; i < len1; i++) {
if (isprint(ptr1[i]))
fprintf(fp, "%c", ptr1[i]);
else
fprintf(fp, ".");
}
for (i = 0; i < len2; i++) {
if (isprint(ptr2[i]))
fprintf(fp, "%c", ptr2[i]);
else
fprintf(fp, ".");
}
fprintf(fp, "\n");
}
static ulong
search_chars(ulong *bufptr, ulong addr, int longcnt, struct searchinfo *si)
{
int i, j;
int len;
char *target;
int charcnt = longcnt * sizeof(long);
char *ptr = (char *)bufptr;
/* is this the first page of this search? */
if (si->s_parms.s_chars.started_flag == 0) {
for (j = 0; j < si->vcnt; j++) {
cross[j].cnt = 0; /* no hits */
}
cross_match_next_addr = (ulong)-1; /* no page match for first page */
si->s_parms.s_chars.started_flag++;
}
if (cross_match_next_addr == addr) {
for (j = 0; j < si->vcnt; j++) {
if (cross[j].cnt) {
target = si->s_parms.s_chars.value[j];
len = si->s_parms.s_chars.len[j];
for (i = 0; i < len - 1; i++) {
if (cross[j].hit[i] &&
!strncmp(&target[len - 1 - i], ptr, i + 1))
report_match(si, cross[j].addr + i,
target, len,
&ptr[i+1],
CHARS_CTX - len);
}
}
}
}
/* set up for possible cross matches on this page */
cross_match_next_addr = addr + charcnt;
for (j = 0; j < si->vcnt; j++) {
len = si->s_parms.s_chars.len[j];
cross[j].cnt = 0;
cross[j].addr = addr + longcnt * sizeof(long) - (len - 1);
for (i = 0; i < len - 1; i++)
cross[j].hit[i] = 0;
}
for (i = 0; i < charcnt; i++, ptr++, addr++) {
for (j = 0; j < si->vcnt; j++) {
target = si->s_parms.s_chars.value[j];
len = si->s_parms.s_chars.len[j];
if ((i + len) > charcnt) {
/* check for cross match */
if (!strncmp(target, ptr, charcnt - i)) {
cross[j].hit[len + i - charcnt - 1] = 1;
cross[j].cnt++;
}
} else {
if (!strncmp(target, ptr, len)) {
int slen = CHARS_CTX;
if ((i + CHARS_CTX) > charcnt)
slen = charcnt - i;
report_match(si, addr, ptr, slen, (char *)0, 0);
}
}
}
}
return addr;
}
static void
report_match_p(ulonglong addr, char *ptr1, int len1, char *ptr2, int len2)
{
int i;
fprintf(fp, "%llx: ", addr);
for (i = 0; i < len1; i++) {
if (isprint(ptr1[i]))
fprintf(fp, "%c", ptr1[i]);
else
fprintf(fp, ".");
}
for (i = 0; i < len2; i++) {
if (isprint(ptr2[i]))
fprintf(fp, "%c", ptr2[i]);
else
fprintf(fp, ".");
}
fprintf(fp, "\n");
}
static ulonglong
search_chars_p(ulong *bufptr, ulonglong addr_p, int longcnt, struct searchinfo *si)
{
int i, j;
int len;
char *target;
int charcnt = longcnt * sizeof(long);
char *ptr = (char *)bufptr;
/* is this the first page of this search? */
if (si->s_parms.s_chars.started_flag == 0) {
for (j = 0; j < si->vcnt; j++) {
cross[j].cnt = 0; /* no hits */
}
cross_match_next_addr_p = (ulonglong)-1; /* no page match for first page */
si->s_parms.s_chars.started_flag++;
}
if (cross_match_next_addr_p == addr_p) {
for (j = 0; j < si->vcnt; j++) {
if (cross[j].cnt) {
target = si->s_parms.s_chars.value[j];
len = si->s_parms.s_chars.len[j];
for (i = 0; i < len - 1; i++) {
if (cross[j].hit[i] &&
!strncmp(&target[len - 1 - i], ptr, i + 1))
report_match_p(cross[j].addr_p + i,
target, len,
&ptr[i+1],
CHARS_CTX - len);
}
}
}
}
/* set up for possible cross matches on this page */
cross_match_next_addr_p = addr_p + charcnt;
for (j = 0; j < si->vcnt; j++) {
len = si->s_parms.s_chars.len[j];
cross[j].cnt = 0;
cross[j].addr_p = addr_p + longcnt * sizeof(long) - (len - 1);
for (i = 0; i < len - 1; i++)
cross[j].hit[i] = 0;
}
for (i = 0; i < charcnt; i++, ptr++, addr_p++) {
for (j = 0; j < si->vcnt; j++) {
target = si->s_parms.s_chars.value[j];
len = si->s_parms.s_chars.len[j];
if ((i + len) > charcnt) {
/* check for cross match */
if (!strncmp(target, ptr, charcnt - i)) {
cross[j].hit[len + i - charcnt - 1] = 1;
cross[j].cnt++;
}
} else {
if (!strncmp(target, ptr, len)) {
int slen = CHARS_CTX;
if ((i + CHARS_CTX) > charcnt)
slen = charcnt - i;
report_match_p(addr_p, ptr, slen, (char *)0, 0);
}
}
}
}
return addr_p;
}
static void
search_virtual(struct searchinfo *si)
{
ulong start, end;
ulong pp, next, *ubp;
int wordcnt, lastpage;
ulong page;
physaddr_t paddr;
char *pagebuf;
ulong pct, pages_read, pages_checked;
time_t begin, finish;
start = si->vaddr_start;
end = si->vaddr_end;
pages_read = pages_checked = 0;
begin = finish = 0;
pagebuf = GETBUF(PAGESIZE());
if (start & (sizeof(long)-1)) {
start &= ~(sizeof(long)-1);
error(INFO, "rounding down start address to: %lx\n", start);
}
if (CRASHDEBUG(1)) {
begin = time(NULL);
fprintf(fp, "search_virtual: start: %lx end: %lx\n",
start, end);
}
next = start;
for (pp = VIRTPAGEBASE(start); next < end; next = pp) {
pages_checked++;
lastpage = (VIRTPAGEBASE(next) == VIRTPAGEBASE(end));
if (LKCD_DUMPFILE())
set_lkcd_nohash();
/*
* Keep it virtual for Xen hypervisor.
*/
if (XEN_HYPER_MODE()) {
if (!readmem(pp, KVADDR, pagebuf, PAGESIZE(),
"search page", RETURN_ON_ERROR|QUIET)) {
if (CRASHDEBUG(1))
fprintf(fp,
"search suspended at: %lx\n", pp);
goto done;
}
goto virtual;
}
switch (si->memtype)
{
case UVADDR:
if (!uvtop(CURRENT_CONTEXT(), pp, &paddr, 0) ||
!phys_to_page(paddr, &page)) {
if (!next_upage(CURRENT_CONTEXT(), pp, &pp))
goto done;
continue;
}
break;
case KVADDR:
if (!kvtop(CURRENT_CONTEXT(), pp, &paddr, 0) ||
!phys_to_page(paddr, &page)) {
if (!next_kpage(pp, &pp))
goto done;
continue;
}
break;
}
if (!readmem(paddr, PHYSADDR, pagebuf, PAGESIZE(),
"search page", RETURN_ON_ERROR|QUIET)) {
pp += PAGESIZE();
continue;
}
virtual:
pages_read++;
ubp = (ulong *)&pagebuf[next - pp];
if (lastpage) {
if (end == (ulong)(-1))
wordcnt = PAGESIZE()/sizeof(long);
else
wordcnt = (end - next)/sizeof(long);
} else
wordcnt = (PAGESIZE() - (next - pp))/sizeof(long);
switch (si->mode)
{
case SEARCH_ULONG:
next = search_ulong(ubp, next, wordcnt, si);
break;
case SEARCH_UINT:
next = search_uint(ubp, next, wordcnt, si);
break;
case SEARCH_USHORT:
next = search_ushort(ubp, next, wordcnt, si);
break;
case SEARCH_CHARS:
next = search_chars(ubp, next, wordcnt, si);
break;
default:
/* unimplemented search type */
next += wordcnt * (sizeof(long));
break;
}
if (CRASHDEBUG(1))
if ((pp % (1024*1024)) == 0)
console("%lx\n", pp);
pp += PAGESIZE();
}
done:
if (CRASHDEBUG(1)) {
finish = time(NULL);
pct = (pages_read * 100)/pages_checked;
fprintf(fp,
"search_virtual: read %ld (%ld%%) of %ld pages checked in %ld seconds\n",
pages_read, pct, pages_checked, finish - begin);
}
FREEBUF(pagebuf);
}
static void
search_physical(struct searchinfo *si)
{
ulonglong start_in, end_in;
ulong *ubp;
int wordcnt, lastpage;
ulonglong pnext, ppp;
char *pagebuf;
ulong pct, pages_read, pages_checked;
time_t begin, finish;
ulong page;
start_in = si->paddr_start;
end_in = si->paddr_end;
pages_read = pages_checked = 0;
begin = finish = 0;
pagebuf = GETBUF(PAGESIZE());
if (start_in & (sizeof(ulonglong)-1)) {
start_in &= ~(sizeof(ulonglong)-1);
error(INFO, "rounding down start address to: %llx\n",
(ulonglong)start_in);
}
if (CRASHDEBUG(1)) {
begin = time(NULL);
fprintf(fp, "search_physical: start: %llx end: %llx\n",
start_in, end_in);
}
pnext = start_in;
for (ppp = PHYSPAGEBASE(start_in); pnext < end_in; pnext = ppp) {
pages_checked++;
lastpage = (PHYSPAGEBASE(pnext) == PHYSPAGEBASE(end_in));
if (LKCD_DUMPFILE())
set_lkcd_nohash();
if (!phys_to_page(ppp, &page) ||
!readmem(ppp, PHYSADDR, pagebuf, PAGESIZE(),
"search page", RETURN_ON_ERROR|QUIET)) {
if (!next_physpage(ppp, &ppp))
break;
continue;
}
pages_read++;
ubp = (ulong *)&pagebuf[pnext - ppp];
if (lastpage) {
if (end_in == (ulonglong)(-1))
wordcnt = PAGESIZE()/sizeof(long);
else
wordcnt = (end_in - pnext)/sizeof(long);
} else
wordcnt = (PAGESIZE() - (pnext - ppp))/sizeof(long);
switch (si->mode)
{
case SEARCH_ULONG:
pnext = search_ulong_p(ubp, pnext, wordcnt, si);
break;
case SEARCH_UINT:
pnext = search_uint_p(ubp, pnext, wordcnt, si);
break;
case SEARCH_USHORT:
pnext = search_ushort_p(ubp, pnext, wordcnt, si);
break;
case SEARCH_CHARS:
pnext = search_chars_p(ubp, pnext, wordcnt, si);
break;
default:
/* unimplemented search type */
pnext += wordcnt * (sizeof(long));
break;
}
ppp += PAGESIZE();
}
if (CRASHDEBUG(1)) {
finish = time(NULL);
pct = (pages_read * 100)/pages_checked;
fprintf(fp,
"search_physical: read %ld (%ld%%) of %ld pages checked in %ld seconds\n",
pages_read, pct, pages_checked, finish - begin);
}
FREEBUF(pagebuf);
}
/*
* Return the next mapped user virtual address page that comes after
* the passed-in address.
*/
static int
next_upage(struct task_context *tc, ulong vaddr, ulong *nextvaddr)
{
ulong vma, total_vm;
char *vma_buf;
ulong vm_start, vm_end;
ulong vm_next;
if (!tc->mm_struct)
return FALSE;
fill_mm_struct(tc->mm_struct);
vma = ULONG(tt->mm_struct + OFFSET(mm_struct_mmap));
total_vm = ULONG(tt->mm_struct + OFFSET(mm_struct_total_vm));
if (!vma || (total_vm == 0))
return FALSE;
vaddr = VIRTPAGEBASE(vaddr) + PAGESIZE(); /* first possible page */
for ( ; vma; vma = vm_next) {
vma_buf = fill_vma_cache(vma);
vm_start = ULONG(vma_buf + OFFSET(vm_area_struct_vm_start));
vm_end = ULONG(vma_buf + OFFSET(vm_area_struct_vm_end));
vm_next = ULONG(vma_buf + OFFSET(vm_area_struct_vm_next));
if (vaddr <= vm_start) {
*nextvaddr = vm_start;
return TRUE;
}
if ((vaddr > vm_start) && (vaddr < vm_end)) {
*nextvaddr = vaddr;
return TRUE;
}
}
return FALSE;
}
/*
* Return the next mapped kernel virtual address in the vmlist
* that is equal to or comes after the passed-in address.
* Prevent repeated calls to dump_vmlist() by only doing it
* one time for dumpfiles, or one time per (active) command.
*/
static int
next_vmlist_vaddr(ulong vaddr, ulong *nextvaddr)
{
int i, retval;
ulong cnt;
struct meminfo meminfo, *mi;
static int count = 0;
static struct vmlist *vmlist = NULL;
static ulong cmdgencur = BADVAL;
/*
* Search the stashed vmlist if possible.
*/
if (vmlist && ACTIVE()) {
if (pc->cmdgencur != cmdgencur) {
free(vmlist);
vmlist = NULL;
}
}
if (vmlist) {
for (i = 0, retval = FALSE; i < count; i++) {
if (vaddr <= vmlist[i].addr) {
*nextvaddr = vmlist[i].addr;
retval = TRUE;
break;
}
if (vaddr < (vmlist[i].addr + vmlist[i].size)) {
*nextvaddr = vaddr;
retval = TRUE;
break;
}
}
return retval;
}
mi = &meminfo;
BZERO(mi, sizeof(struct meminfo));
mi->flags = GET_VMLIST_COUNT;
dump_vmlist(mi);
cnt = mi->retval;
if (!cnt)
return FALSE;
mi->vmlist = (struct vmlist *)GETBUF(sizeof(struct vmlist)*cnt);
mi->flags = GET_VMLIST;
dump_vmlist(mi);
for (i = 0, retval = FALSE; i < cnt; i++) {
if (vaddr <= mi->vmlist[i].addr) {
*nextvaddr = mi->vmlist[i].addr;
retval = TRUE;
break;
}
if (vaddr < (mi->vmlist[i].addr + mi->vmlist[i].size)) {
*nextvaddr = vaddr;
retval = TRUE;
break;
}
}
if (!vmlist) {
vmlist = (struct vmlist *)
malloc(sizeof(struct vmlist)*cnt);
if (vmlist) {
BCOPY(mi->vmlist, vmlist,
sizeof(struct vmlist)*cnt);
count = cnt;
cmdgencur = pc->cmdgencur;
}
}
FREEBUF(mi->vmlist);
return retval;
}
/*
* Determine whether a virtual address is inside a vmlist segment.
*/
int
in_vmlist_segment(ulong vaddr)
{
ulong next;
if (next_vmlist_vaddr(vaddr, &next) &&
(vaddr == next))
return TRUE;
return FALSE;
}
/*
* Return the next kernel module virtual address that is
* equal to or comes after the passed-in address.
*/
static int
next_module_vaddr(ulong vaddr, ulong *nextvaddr)
{
int i;
ulong start, end;
struct load_module *lm;
for (i = 0; i < st->mods_installed; i++) {
lm = &st->load_modules[i];
start = lm->mod_base;
end = lm->mod_base + lm->mod_size;
if (vaddr >= end)
continue;
/*
* Either below or in this module.
*/
if (vaddr < start)
*nextvaddr = start;
else
*nextvaddr = vaddr;
return TRUE;
}
return FALSE;
}
/*
* Return the next kernel virtual address page in a designated
* kernel virtual address range that comes after the passed-in,
* untranslatable, address.
*/
static int
next_kpage(ulong vaddr, ulong *nextvaddr)
{
ulong vaddr_orig;
vaddr_orig = vaddr;
vaddr = VIRTPAGEBASE(vaddr) + PAGESIZE(); /* first possible page */
if (vaddr < vaddr_orig) /* wrapped back to zero? */
return FALSE;
switch (pc->curcmd_private)
{
case KVADDR_UNITY_MAP:
return next_identity_mapping(vaddr, nextvaddr);
case KVADDR_VMALLOC:
return next_vmlist_vaddr(vaddr, nextvaddr);
case KVADDR_VMEMMAP:
*nextvaddr = vaddr;
return TRUE;
case KVADDR_START_MAP:
*nextvaddr = vaddr;
return TRUE;
case KVADDR_MODULES:
return next_module_vaddr(vaddr, nextvaddr);
}
return FALSE;
}
/*
* Return the next physical address page that comes after
* the passed-in, unreadable, address.
*/
static int
next_physpage(ulonglong paddr, ulonglong *nextpaddr)
{
int n;
ulonglong node_start;
ulonglong node_end;
struct node_table *nt;
for (n = 0; n < vt->numnodes; n++) {
nt = &vt->node_table[n];
node_start = nt->start_paddr;
node_end = nt->start_paddr + (nt->size * PAGESIZE());
if (paddr >= node_end)
continue;
if (paddr < node_start) {
*nextpaddr = node_start;
return TRUE;
}
if (paddr < node_end) {
*nextpaddr = paddr + PAGESIZE();
return TRUE;
}
}
return FALSE;
}
/*
* Display swap statistics.
*/
void
cmd_swap(void)
{
int c;
while ((c = getopt(argcnt, args, "")) != EOF) {
switch(c)
{
default:
argerrs++;
break;
}
}
if (argerrs)
cmd_usage(pc->curcmd, SYNOPSIS);
dump_swap_info(VERBOSE, NULL, NULL);
}
/*
* Do the work for cmd_swap().
*/
#define SWP_USED 1
#define SWAP_MAP_BAD 0x8000
char *swap_info_hdr = \
"SWAP_INFO_STRUCT TYPE SIZE USED PCT PRI FILENAME\n";
static int
dump_swap_info(ulong swapflags, ulong *totalswap_pages, ulong *totalused_pages)
{
int i, j;
int swap_device, prio;
ulong pages, usedswap;
ulong flags, swap_file, max, swap_map, pct;
ulong vfsmnt;
ulong swap_info, swap_info_ptr;
ushort *smap;
ulong inuse_pages, totalswap, totalused;
char *devname;
char buf[BUFSIZE];
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
char buf5[BUFSIZE];
if (!symbol_exists("nr_swapfiles"))
error(FATAL, "nr_swapfiles doesn't exist in this kernel!\n");
if (!symbol_exists("swap_info"))
error(FATAL, "swap_info doesn't exist in this kernel!\n");
swap_info_init();
swap_info = symbol_value("swap_info");
if (swapflags & VERBOSE)
fprintf(fp, "%s", swap_info_hdr);
totalswap = totalused = 0;
for (i = 0; i < vt->nr_swapfiles; i++,
swap_info += (vt->flags & SWAPINFO_V1 ?
SIZE(swap_info_struct) : sizeof(void *))) {
if (vt->flags & SWAPINFO_V2) {
if (!readmem(swap_info, KVADDR, &swap_info_ptr,
sizeof(void *), "swap_info pointer",
QUIET|RETURN_ON_ERROR))
continue;
if (!swap_info_ptr)
continue;
fill_swap_info(swap_info_ptr);
} else
fill_swap_info(swap_info);
if (MEMBER_SIZE("swap_info_struct", "flags") == sizeof(uint))
flags = UINT(vt->swap_info_struct +
OFFSET(swap_info_struct_flags));
else
flags = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_flags));
if (!(flags & SWP_USED))
continue;
swap_file = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_swap_file));
swap_device = INT(vt->swap_info_struct +
OFFSET_OPTION(swap_info_struct_swap_device,
swap_info_struct_old_block_size));
pages = INT(vt->swap_info_struct +
OFFSET(swap_info_struct_pages));
totalswap += pages;
pages <<= (PAGESHIFT() - 10);
inuse_pages = 0;
if (MEMBER_SIZE("swap_info_struct", "prio") == sizeof(short))
prio = SHORT(vt->swap_info_struct +
OFFSET(swap_info_struct_prio));
else
prio = INT(vt->swap_info_struct +
OFFSET(swap_info_struct_prio));
if (MEMBER_SIZE("swap_info_struct", "max") == sizeof(int))
max = UINT(vt->swap_info_struct +
OFFSET(swap_info_struct_max));
else
max = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_max));
if (VALID_MEMBER(swap_info_struct_inuse_pages)) {
if (MEMBER_SIZE("swap_info_struct", "inuse_pages") == sizeof(int))
inuse_pages = UINT(vt->swap_info_struct +
OFFSET(swap_info_struct_inuse_pages));
else
inuse_pages = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_inuse_pages));
}
swap_map = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_swap_map));
if (swap_file) {
if (VALID_MEMBER(swap_info_struct_swap_vfsmnt)) {
vfsmnt = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_swap_vfsmnt));
get_pathname(swap_file, buf, BUFSIZE,
1, vfsmnt);
} else if (VALID_MEMBER
(swap_info_struct_old_block_size)) {
devname = vfsmount_devname(file_to_vfsmnt(swap_file),
buf1, BUFSIZE);
get_pathname(file_to_dentry(swap_file),
buf, BUFSIZE, 1, file_to_vfsmnt(swap_file));
if ((STREQ(devname, "devtmpfs") || STREQ(devname, "udev"))
&& !STRNEQ(buf, "/dev/"))
string_insert("/dev", buf);
} else {
get_pathname(swap_file, buf, BUFSIZE, 1, 0);
}
} else
sprintf(buf, "(unknown)");
smap = NULL;
if (vt->flags & SWAPINFO_V1) {
smap = (ushort *)GETBUF(sizeof(ushort) * max);
if (!readmem(swap_map, KVADDR, smap,
sizeof(ushort) * max, "swap_info swap_map data",
RETURN_ON_ERROR|QUIET)) {
if (swapflags & RETURN_ON_ERROR) {
*totalswap_pages = swap_map;
*totalused_pages = i;
FREEBUF(smap);
return FALSE;
} else
error(FATAL,
"swap_info[%d].swap_map at %lx is inaccessible\n",
i, swap_map);
}
}
usedswap = 0;
if (smap) {
for (j = 0; j < max; j++) {
switch (smap[j])
{
case SWAP_MAP_BAD:
case 0:
continue;
default:
usedswap++;
}
}
FREEBUF(smap);
} else
usedswap = inuse_pages;
totalused += usedswap;
usedswap <<= (PAGESHIFT() - 10);
pct = (usedswap * 100)/pages;
if (swapflags & VERBOSE) {
sprintf(buf1, "%lx", (vt->flags & SWAPINFO_V2) ?
swap_info_ptr : swap_info);
sprintf(buf2, "%ldk", pages);
sprintf(buf3, "%ldk", usedswap);
sprintf(buf4, "%2ld%%", pct);
sprintf(buf5, "%d", prio);
fprintf(fp, "%s %s %s %s %s %s %s\n",
mkstring(buf1,
MAX(VADDR_PRLEN, strlen("SWAP_INFO_STRUCT")),
CENTER|LJUST, NULL),
swap_device ? "PARTITION" : " FILE ",
mkstring(buf2, 10, CENTER|RJUST, NULL),
mkstring(buf3, 10, CENTER|RJUST, NULL),
mkstring(buf4, 4, CENTER|RJUST, NULL),
mkstring(buf5, 4, RJUST, NULL), buf);
}
}
if (totalswap_pages)
*totalswap_pages = totalswap;
if (totalused_pages)
*totalused_pages = totalused;
return TRUE;
}
/*
* Determine the swap_info_struct usage.
*/
static void
swap_info_init(void)
{
struct gnu_request *req;
if (vt->flags & (SWAPINFO_V1|SWAPINFO_V2))
return;
req = (struct gnu_request *)GETBUF(sizeof(struct gnu_request));
if ((get_symbol_type("swap_info", NULL, req) == TYPE_CODE_ARRAY) &&
((req->target_typecode == TYPE_CODE_PTR) ||
(req->target_typecode == TYPE_CODE_STRUCT))) {
switch (req->target_typecode)
{
case TYPE_CODE_STRUCT:
vt->flags |= SWAPINFO_V1;
break;
case TYPE_CODE_PTR:
vt->flags |= SWAPINFO_V2;
break;
}
} else {
if (THIS_KERNEL_VERSION >= LINUX(2,6,33))
vt->flags |= SWAPINFO_V2;
else
vt->flags |= SWAPINFO_V1;
}
FREEBUF(req);
}
/*
* Translate a PTE into a swap device and offset string.
*/
char *
swap_location(ulonglong pte, char *buf)
{
char swapdev[BUFSIZE];
if (!pte)
return NULL;
if (THIS_KERNEL_VERSION >= LINUX(2,6,0))
sprintf(buf, "%s OFFSET: %lld",
get_swapdev(__swp_type(pte), swapdev), __swp_offset(pte));
else
sprintf(buf, "%s OFFSET: %llx",
get_swapdev(SWP_TYPE(pte), swapdev), SWP_OFFSET(pte));
return buf;
}
/*
* Given the type field from a PTE, return the name of the swap device.
*/
static char *
get_swapdev(ulong type, char *buf)
{
unsigned int i, swap_info_len;
ulong swap_info, swap_info_ptr, swap_file;
ulong vfsmnt;
if (!symbol_exists("nr_swapfiles"))
error(FATAL, "nr_swapfiles doesn't exist in this kernel!\n");
if (!symbol_exists("swap_info"))
error(FATAL, "swap_info doesn't exist in this kernel!\n");
swap_info_init();
swap_info = symbol_value("swap_info");
swap_info_len = (i = ARRAY_LENGTH(swap_info)) ?
i : get_array_length("swap_info", NULL, 0);
sprintf(buf, "(unknown swap location)");
if (type >= swap_info_len)
return buf;
switch (vt->flags & (SWAPINFO_V1|SWAPINFO_V2))
{
case SWAPINFO_V1:
swap_info += type * SIZE(swap_info_struct);
fill_swap_info(swap_info);
break;
case SWAPINFO_V2:
swap_info += type * sizeof(void *);
if (!readmem(swap_info, KVADDR, &swap_info_ptr,
sizeof(void *), "swap_info pointer",
RETURN_ON_ERROR|QUIET))
return buf;
if (!swap_info_ptr)
return buf;
fill_swap_info(swap_info_ptr);
break;
}
swap_file = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_swap_file));
if (swap_file) {
if (VALID_MEMBER(swap_info_struct_swap_vfsmnt)) {
vfsmnt = ULONG(vt->swap_info_struct +
OFFSET(swap_info_struct_swap_vfsmnt));
get_pathname(swap_file, buf, BUFSIZE, 1, vfsmnt);
} else if (VALID_MEMBER (swap_info_struct_old_block_size)) {
get_pathname(file_to_dentry(swap_file),
buf, BUFSIZE, 1, 0);
} else {
get_pathname(swap_file, buf, BUFSIZE, 1, 0);
}
}
return buf;
}
/*
* If not currently stashed, cache the passed-in swap_info_struct.
*/
static void
fill_swap_info(ulong swap_info)
{
if (vt->last_swap_read == swap_info)
return;
if (!vt->swap_info_struct && !(vt->swap_info_struct = (char *)
malloc(SIZE(swap_info_struct))))
error(FATAL, "cannot malloc swap_info_struct space\n");
readmem(swap_info, KVADDR, vt->swap_info_struct, SIZE(swap_info_struct),
"fill_swap_info", FAULT_ON_ERROR);
vt->last_swap_read = swap_info;
}
/*
* If active, clear references to the swap_info references.
*/
void
clear_swap_info_cache(void)
{
if (ACTIVE())
vt->last_swap_read = 0;
}
/*
* Translage a vm_area_struct and virtual address into a filename
* and offset string.
*/
#define PAGE_CACHE_SHIFT (machdep->pageshift) /* This is supposed to change! */
static char *
vma_file_offset(ulong vma, ulong vaddr, char *buf)
{
ulong vm_file, vm_start, vm_offset, vm_pgoff, dentry, offset;
ulong vfsmnt;
char file[BUFSIZE];
char *vma_buf, *file_buf;
if (!vma)
return NULL;
vma_buf = fill_vma_cache(vma);
vm_file = ULONG(vma_buf + OFFSET(vm_area_struct_vm_file));
if (!vm_file)
goto no_file_offset;
file_buf = fill_file_cache(vm_file);
dentry = ULONG(file_buf + OFFSET(file_f_dentry));
if (!dentry)
goto no_file_offset;
file[0] = NULLCHAR;
if (VALID_MEMBER(file_f_vfsmnt)) {
vfsmnt = ULONG(file_buf + OFFSET(file_f_vfsmnt));
get_pathname(dentry, file, BUFSIZE, 1, vfsmnt);
} else
get_pathname(dentry, file, BUFSIZE, 1, 0);
if (!strlen(file))
goto no_file_offset;
vm_start = ULONG(vma_buf + OFFSET(vm_area_struct_vm_start));
vm_offset = vm_pgoff = 0xdeadbeef;
if (VALID_MEMBER(vm_area_struct_vm_offset))
vm_offset = ULONG(vma_buf +
OFFSET(vm_area_struct_vm_offset));
else if (VALID_MEMBER(vm_area_struct_vm_pgoff))
vm_pgoff = ULONG(vma_buf +
OFFSET(vm_area_struct_vm_pgoff));
else
goto no_file_offset;
offset = 0;
if (vm_offset != 0xdeadbeef)
offset = VIRTPAGEBASE(vaddr) - vm_start + vm_offset;
else if (vm_pgoff != 0xdeadbeef) {
offset = ((vaddr - vm_start) >> PAGE_CACHE_SHIFT) + vm_pgoff;
offset <<= PAGE_CACHE_SHIFT;
}
sprintf(buf, "%s OFFSET: %lx", file, offset);
return buf;
no_file_offset:
return NULL;
}
/*
* Translate a PTE into its physical address and flags.
*/
void
cmd_pte(void)
{
int c;
ulonglong pte;
while ((c = getopt(argcnt, args, "")) != EOF) {
switch(c)
{
default:
argerrs++;
break;
}
}
if (argerrs)
cmd_usage(pc->curcmd, SYNOPSIS);
while (args[optind]) {
pte = htoll(args[optind], FAULT_ON_ERROR, NULL);
machdep->translate_pte((ulong)pte, NULL, pte);
optind++;
}
}
static char *node_zone_hdr = "ZONE NAME SIZE";
/*
* On systems supporting memory nodes, display the basic per-node data.
*/
static void
dump_memory_nodes(int initialize)
{
int i, j;
int n, id, node, flen, slen, badaddr;
ulong node_mem_map;
ulong temp_node_start_paddr;
ulonglong node_start_paddr;
ulong node_start_pfn;
ulong node_start_mapnr;
ulong node_spanned_pages, node_present_pages;
ulong free_pages, zone_size, node_size, cum_zone_size;
ulong zone_start_paddr, zone_start_mapnr, zone_mem_map;
physaddr_t phys;
ulong pp;
ulong zone_start_pfn;
ulong bdata;
ulong pgdat;
ulong node_zones;
ulong value;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
char buf5[BUFSIZE];
struct node_table *nt;
node = slen = 0;
if (!(vt->flags & (NODES|NODES_ONLINE)) && initialize) {
nt = &vt->node_table[0];
nt->node_id = 0;
if (symbol_exists("contig_page_data"))
nt->pgdat = symbol_value("contig_page_data");
else
nt->pgdat = 0;
nt->size = vt->total_pages;
nt->mem_map = vt->mem_map;
nt->start_paddr = 0;
nt->start_mapnr = 0;
if (CRASHDEBUG(1)) {
fprintf(fp, "node_table[%d]: \n", 0);
fprintf(fp, " id: %d\n", nt->node_id);
fprintf(fp, " pgdat: %lx\n", nt->pgdat);
fprintf(fp, " size: %ld\n", nt->size);
fprintf(fp, " present: %ld\n", nt->present);
fprintf(fp, " mem_map: %lx\n", nt->mem_map);
fprintf(fp, " start_paddr: %llx\n", nt->start_paddr);
fprintf(fp, " start_mapnr: %ld\n", nt->start_mapnr);
}
return;
}
if (initialize) {
pgdat = UNINITIALIZED;
/*
* This order may have to change based upon architecture...
*/
if (symbol_exists("pgdat_list") &&
(VALID_MEMBER(pglist_data_node_next) ||
VALID_MEMBER(pglist_data_pgdat_next))) {
get_symbol_data("pgdat_list", sizeof(void *), &pgdat);
vt->flags &= ~NODES_ONLINE;
} else if (vt->flags & NODES_ONLINE) {
if ((node = next_online_node(0)) < 0) {
error(WARNING,
"cannot determine first node from node_online_map\n\n");
return;
}
if (!(pgdat = next_online_pgdat(node))) {
error(WARNING,
"cannot determine pgdat list for this kernel/architecture\n\n");
return;
}
}
} else
pgdat = vt->node_table[0].pgdat;
if (initialize && (pgdat == UNINITIALIZED)) {
error(WARNING, "cannot initialize pgdat list\n\n");
return;
}
for (n = 0, badaddr = FALSE; pgdat; n++) {
if (n >= vt->numnodes)
error(FATAL, "numnodes out of sync with pgdat_list?\n");
nt = &vt->node_table[n];
readmem(pgdat+OFFSET(pglist_data_node_id), KVADDR, &id,
sizeof(int), "pglist node_id", FAULT_ON_ERROR);
if (VALID_MEMBER(pglist_data_node_mem_map)) {
readmem(pgdat+OFFSET(pglist_data_node_mem_map), KVADDR,
&node_mem_map, sizeof(ulong),
"node_mem_map", FAULT_ON_ERROR);
} else {
node_mem_map = BADADDR;
badaddr = TRUE;
}
if (VALID_MEMBER(pglist_data_node_start_paddr)) {
readmem(pgdat+OFFSET(pglist_data_node_start_paddr),
KVADDR, &temp_node_start_paddr, sizeof(ulong),
"pglist node_start_paddr", FAULT_ON_ERROR);
node_start_paddr = temp_node_start_paddr;
}
else if (VALID_MEMBER(pglist_data_node_start_pfn)) {
readmem(pgdat+OFFSET(pglist_data_node_start_pfn),
KVADDR, &node_start_pfn, sizeof(ulong),
"pglist node_start_pfn", FAULT_ON_ERROR);
node_start_mapnr = node_start_pfn;
node_start_paddr = PTOB(node_start_pfn);
if (badaddr && IS_SPARSEMEM()) {
if (!verify_pfn(node_start_pfn))
error(WARNING, "questionable node_start_pfn: %lx\n",
node_start_pfn);
phys = PTOB(node_start_pfn);
if (phys_to_page(phys, &pp))
node_mem_map = pp;
}
} else error(INFO,
"cannot determine zone starting physical address\n");
if (VALID_MEMBER(pglist_data_node_start_mapnr))
readmem(pgdat+OFFSET(pglist_data_node_start_mapnr),
KVADDR, &node_start_mapnr, sizeof(ulong),
"pglist node_start_mapnr", FAULT_ON_ERROR);
if (VALID_MEMBER(pglist_data_node_size))
readmem(pgdat+OFFSET(pglist_data_node_size),
KVADDR, &node_size, sizeof(ulong),
"pglist node_size", FAULT_ON_ERROR);
else if (VALID_MEMBER(pglist_data_node_spanned_pages)) {
readmem(pgdat+OFFSET(pglist_data_node_spanned_pages),
KVADDR, &node_spanned_pages, sizeof(ulong),
"pglist node_spanned_pages", FAULT_ON_ERROR);
node_size = node_spanned_pages;
} else error(INFO, "cannot determine zone size\n");
if (VALID_MEMBER(pglist_data_node_present_pages))
readmem(pgdat+OFFSET(pglist_data_node_present_pages),
KVADDR, &node_present_pages, sizeof(ulong),
"pglist node_present_pages", FAULT_ON_ERROR);
else
node_present_pages = 0;
if (VALID_MEMBER(pglist_data_bdata))
readmem(pgdat+OFFSET(pglist_data_bdata), KVADDR, &bdata,
sizeof(ulong), "pglist bdata", FAULT_ON_ERROR);
else
bdata = BADADDR;
if (initialize) {
nt->node_id = id;
nt->pgdat = pgdat;
if (VALID_MEMBER(zone_struct_memsize))
nt->size = 0; /* initialize below */
else
nt->size = node_size;
nt->present = node_present_pages;
nt->mem_map = node_mem_map;
nt->start_paddr = node_start_paddr;
nt->start_mapnr = node_start_mapnr;
if (CRASHDEBUG(1)) {
fprintf(fp, "node_table[%d]: \n", n);
fprintf(fp, " id: %d\n", nt->node_id);
fprintf(fp, " pgdat: %lx\n", nt->pgdat);
fprintf(fp, " size: %ld\n", nt->size);
fprintf(fp, " present: %ld\n", nt->present);
fprintf(fp, " mem_map: %lx\n", nt->mem_map);
fprintf(fp, " start_paddr: %llx\n", nt->start_paddr);
fprintf(fp, " start_mapnr: %ld\n", nt->start_mapnr);
}
}
if (!initialize) {
if (n) {
fprintf(fp, "\n");
pad_line(fp, slen, '-');
}
flen = MAX(VADDR_PRLEN, strlen("BOOTMEM_DATA"));
fprintf(fp, "%sNODE %s %s %s %s\n",
n ? "\n\n" : "",
mkstring(buf1, 8, CENTER, "SIZE"),
mkstring(buf2, flen, CENTER|LJUST, "PGLIST_DATA"),
mkstring(buf3, flen, CENTER|LJUST, "BOOTMEM_DATA"),
mkstring(buf4, flen, CENTER|LJUST, "NODE_ZONES"));
node_zones = pgdat + OFFSET(pglist_data_node_zones);
sprintf(buf5, " %2d %s %s %s %s\n", id,
mkstring(buf1, 8, CENTER|LJUST|LONG_DEC,
MKSTR(node_size)),
mkstring(buf2, flen, CENTER|LJUST|LONG_HEX,
MKSTR(pgdat)),
bdata == BADADDR ?
mkstring(buf3, flen, CENTER, "----") :
mkstring(buf3, flen, CENTER|LONG_HEX, MKSTR(bdata)),
mkstring(buf4, flen, CENTER|LJUST|LONG_HEX,
MKSTR(node_zones)));
fprintf(fp, "%s", buf5);
j = 12 + strlen(buf1) + strlen(buf2) + strlen(buf3) +
count_leading_spaces(buf4);
for (i = 1; i < vt->nr_zones; i++) {
node_zones += SIZE_OPTION(zone_struct, zone);
INDENT(j);
fprintf(fp, "%lx\n", node_zones);
}
fprintf(fp, "%s START_PADDR START_MAPNR\n",
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST,
"MEM_MAP"));
fprintf(fp, "%s %s %s\n",
mkstring(buf1, VADDR_PRLEN,
CENTER|LONG_HEX, MKSTR(node_mem_map)),
mkstring(buf2, strlen(" START_PADDR "),
CENTER|LONGLONG_HEX|RJUST, MKSTR(&node_start_paddr)),
mkstring(buf3, strlen("START_MAPNR"),
CENTER|LONG_DEC|RJUST,
MKSTR(node_start_mapnr)));
sprintf(buf2, "%s %s START_PADDR START_MAPNR",
node_zone_hdr,
mkstring(buf1, VADDR_PRLEN, CENTER|RJUST,
"MEM_MAP"));
slen = strlen(buf2);
fprintf(fp, "\n%s\n", buf2);
}
node_zones = pgdat + OFFSET(pglist_data_node_zones);
cum_zone_size = 0;
for (i = 0; i < vt->nr_zones; i++) {
if (CRASHDEBUG(7))
fprintf(fp, "zone %d at %lx\n", i, node_zones);
if (VALID_MEMBER(zone_struct_size))
readmem(node_zones+OFFSET(zone_struct_size),
KVADDR, &zone_size, sizeof(ulong),
"zone_struct size", FAULT_ON_ERROR);
else if (VALID_MEMBER(zone_struct_memsize)) {
readmem(node_zones+OFFSET(zone_struct_memsize),
KVADDR, &zone_size, sizeof(ulong),
"zone_struct memsize", FAULT_ON_ERROR);
nt->size += zone_size;
} else if (VALID_MEMBER(zone_spanned_pages)) {
readmem(node_zones+ OFFSET(zone_spanned_pages),
KVADDR, &zone_size, sizeof(ulong),
"zone spanned_pages", FAULT_ON_ERROR);
} else error(FATAL,
"zone_struct has neither size nor memsize field\n");
readmem(node_zones+
OFFSET_OPTION(zone_struct_free_pages,
zone_free_pages), KVADDR, &free_pages,
sizeof(ulong), "zone[_struct] free_pages",
FAULT_ON_ERROR);
readmem(node_zones+OFFSET_OPTION(zone_struct_name,
zone_name), KVADDR, &value, sizeof(void *),
"zone[_struct] name", FAULT_ON_ERROR);
if (!read_string(value, buf1, BUFSIZE-1))
sprintf(buf1, "(unknown) ");
if (VALID_STRUCT(zone_struct)) {
if (VALID_MEMBER(zone_struct_zone_start_paddr))
{
readmem(node_zones+OFFSET
(zone_struct_zone_start_paddr),
KVADDR, &zone_start_paddr,
sizeof(ulong),
"node_zones zone_start_paddr",
FAULT_ON_ERROR);
} else {
readmem(node_zones+
OFFSET(zone_struct_zone_start_pfn),
KVADDR, &zone_start_pfn,
sizeof(ulong),
"node_zones zone_start_pfn",
FAULT_ON_ERROR);
zone_start_paddr =
PTOB(zone_start_pfn);
}
readmem(node_zones+
OFFSET(zone_struct_zone_start_mapnr),
KVADDR, &zone_start_mapnr,
sizeof(ulong),
"node_zones zone_start_mapnr",
FAULT_ON_ERROR);
} else {
readmem(node_zones+
OFFSET(zone_zone_start_pfn),
KVADDR, &zone_start_pfn,
sizeof(ulong),
"node_zones zone_start_pfn",
FAULT_ON_ERROR);
zone_start_paddr = PTOB(zone_start_pfn);
if (IS_SPARSEMEM()) {
zone_mem_map = 0;
zone_start_mapnr = 0;
if (zone_size) {
phys = PTOB(zone_start_pfn);
zone_start_mapnr = phys/PAGESIZE();
}
} else if (!(vt->flags & NODES) &&
INVALID_MEMBER(zone_zone_mem_map)) {
readmem(pgdat+OFFSET(pglist_data_node_mem_map),
KVADDR, &zone_mem_map, sizeof(void *),
"contig_page_data mem_map", FAULT_ON_ERROR);
if (zone_size)
zone_mem_map += cum_zone_size * SIZE(page);
} else readmem(node_zones+
OFFSET(zone_zone_mem_map),
KVADDR, &zone_mem_map,
sizeof(ulong),
"node_zones zone_mem_map",
FAULT_ON_ERROR);
if (zone_mem_map)
zone_start_mapnr =
(zone_mem_map - node_mem_map) /
SIZE(page);
else if (!IS_SPARSEMEM())
zone_start_mapnr = 0;
}
if (IS_SPARSEMEM()) {
zone_mem_map = 0;
if (zone_size) {
phys = PTOB(zone_start_pfn);
if (phys_to_page(phys, &pp))
zone_mem_map = pp;
}
} else if (!(vt->flags & NODES) &&
INVALID_MEMBER(zone_struct_zone_mem_map) &&
INVALID_MEMBER(zone_zone_mem_map)) {
readmem(pgdat+OFFSET(pglist_data_node_mem_map),
KVADDR, &zone_mem_map, sizeof(void *),
"contig_page_data mem_map", FAULT_ON_ERROR);
if (zone_size)
zone_mem_map += cum_zone_size * SIZE(page);
else
zone_mem_map = 0;
} else
readmem(node_zones+
OFFSET_OPTION(zone_struct_zone_mem_map,
zone_zone_mem_map), KVADDR, &zone_mem_map,
sizeof(ulong), "node_zones zone_mem_map",
FAULT_ON_ERROR);
if (!initialize) {
fprintf(fp, " %2d %-9s %7ld ",
i, buf1, zone_size);
cum_zone_size += zone_size;
fprintf(fp, "%s %s %s\n",
mkstring(buf1, VADDR_PRLEN,
RJUST|LONG_HEX,MKSTR(zone_mem_map)),
mkstring(buf2, strlen("START_PADDR"),
LONG_HEX|RJUST,MKSTR(zone_start_paddr)),
mkstring(buf3, strlen("START_MAPNR"),
LONG_DEC|RJUST,
MKSTR(zone_start_mapnr)));
}
node_zones += SIZE_OPTION(zone_struct, zone);
}
if (initialize) {
if (vt->flags & NODES_ONLINE) {
if ((node = next_online_node(node+1)) < 0)
pgdat = 0;
else if (!(pgdat = next_online_pgdat(node))) {
error(WARNING,
"cannot determine pgdat list for this kernel/architecture (node %d)\n\n",
node);
pgdat = 0;
}
} else
readmem(pgdat + OFFSET_OPTION(pglist_data_node_next,
pglist_data_pgdat_next), KVADDR,
&pgdat, sizeof(void *), "pglist_data node_next",
FAULT_ON_ERROR);
} else {
if ((n+1) < vt->numnodes)
pgdat = vt->node_table[n+1].pgdat;
else
pgdat = 0;
}
}
if (n != vt->numnodes) {
if (CRASHDEBUG(2))
error(NOTE, "changing numnodes from %d to %d\n",
vt->numnodes, n);
vt->numnodes = n;
}
if (!initialize && IS_SPARSEMEM())
dump_mem_sections();
}
/*
* At least verify that page-shifted physical address.
*/
static int
verify_pfn(ulong pfn)
{
int i;
physaddr_t mask;
if (!machdep->max_physmem_bits)
return TRUE;
mask = 0;
for (i = machdep->max_physmem_bits; i < machdep->bits; i++)
mask |= ((physaddr_t)1 << i);
if (mask & PTOB(pfn))
return FALSE;
return TRUE;
}
static void
dump_zone_stats(void)
{
int i, n;
ulong pgdat, node_zones;
char *zonebuf;
char buf1[BUFSIZE];
int ivalue;
ulong value1;
ulong value2;
ulong value3;
ulong value4;
ulong value5;
ulong value6;
long min, low, high;
value1 = value2 = value3 = value4 = value5 = value6 = 0;
min = low = high = 0;
pgdat = vt->node_table[0].pgdat;
zonebuf = GETBUF(SIZE_OPTION(zone_struct, zone));
vm_stat_init();
for (n = 0; pgdat; n++) {
node_zones = pgdat + OFFSET(pglist_data_node_zones);
for (i = 0; i < vt->nr_zones; i++) {
if (!readmem(node_zones, KVADDR, zonebuf,
SIZE_OPTION(zone_struct, zone),
"zone buffer", FAULT_ON_ERROR))
break;
value1 = ULONG(zonebuf +
OFFSET_OPTION(zone_struct_name, zone_name));
if (!read_string(value1, buf1, BUFSIZE-1))
sprintf(buf1, "(unknown) ");
if (VALID_MEMBER(zone_struct_size))
value1 = value6 = ULONG(zonebuf +
OFFSET(zone_struct_size));
else if (VALID_MEMBER(zone_struct_memsize)) {
value1 = value6 = ULONG(zonebuf +
OFFSET(zone_struct_memsize));
} else if (VALID_MEMBER(zone_spanned_pages)) {
value1 = ULONG(zonebuf +
OFFSET(zone_spanned_pages));
value6 = ULONG(zonebuf +
OFFSET(zone_present_pages));
} else error(FATAL,
"zone struct has unknown size field\n");
if (VALID_MEMBER(zone_watermark)) {
if (!enumerator_value("WMARK_MIN", &min) ||
!enumerator_value("WMARK_LOW", &low) ||
!enumerator_value("WMARK_HIGH", &high)) {
min = 0;
low = 1;
high = 2;
}
value2 = ULONG(zonebuf + OFFSET(zone_watermark) +
(sizeof(long) * min));
value3 = ULONG(zonebuf + OFFSET(zone_watermark) +
(sizeof(long) * low));
value4 = ULONG(zonebuf + OFFSET(zone_watermark) +
(sizeof(long) * high));
} else {
value2 = ULONG(zonebuf + OFFSET_OPTION(zone_pages_min,
zone_struct_pages_min));
value3 = ULONG(zonebuf + OFFSET_OPTION(zone_pages_low,
zone_struct_pages_low));
value4 = ULONG(zonebuf + OFFSET_OPTION(zone_pages_high,
zone_struct_pages_high));
}
value5 = ULONG(zonebuf + OFFSET_OPTION(zone_free_pages,
zone_struct_free_pages));
fprintf(fp,
"NODE: %d ZONE: %d ADDR: %lx NAME: \"%s\"\n",
n, i, node_zones, buf1);
if (!value1) {
fprintf(fp, " [unpopulated]\n");
goto next_zone;
}
fprintf(fp, " SIZE: %ld", value1);
if (value6 < value1)
fprintf(fp, " PRESENT: %ld", value6);
fprintf(fp, " MIN/LOW/HIGH: %ld/%ld/%ld",
value2, value3, value4);
if (VALID_MEMBER(zone_vm_stat))
dump_vm_stat("NR_FREE_PAGES", (long *)&value5,
node_zones + OFFSET(zone_vm_stat));
if (VALID_MEMBER(zone_nr_active) &&
VALID_MEMBER(zone_nr_inactive)) {
value1 = ULONG(zonebuf +
OFFSET(zone_nr_active));
value2 = ULONG(zonebuf +
OFFSET(zone_nr_inactive));
fprintf(fp,
"\n NR_ACTIVE: %ld NR_INACTIVE: %ld FREE: %ld\n",
value1, value2, value5);
if (VALID_MEMBER(zone_vm_stat)) {
fprintf(fp, " VM_STAT:\n");
dump_vm_stat(NULL, NULL, node_zones +
OFFSET(zone_vm_stat));
}
} else if (VALID_MEMBER(zone_vm_stat) &&
dump_vm_stat("NR_ACTIVE", (long *)&value1,
node_zones + OFFSET(zone_vm_stat)) &&
dump_vm_stat("NR_INACTIVE", (long *)&value2,
node_zones + OFFSET(zone_vm_stat))) {
fprintf(fp, "\n VM_STAT:\n");
dump_vm_stat(NULL, NULL, node_zones +
OFFSET(zone_vm_stat));
} else {
if (VALID_MEMBER(zone_vm_stat)) {
fprintf(fp, "\n VM_STAT:\n");
dump_vm_stat(NULL, NULL, node_zones +
OFFSET(zone_vm_stat));
} else
fprintf(fp, " FREE: %ld\n", value5);
goto next_zone;
}
if (VALID_MEMBER(zone_all_unreclaimable)) {
ivalue = UINT(zonebuf +
OFFSET(zone_all_unreclaimable));
fprintf(fp, " ALL_UNRECLAIMABLE: %s ",
ivalue ? "yes" : "no");
} else if (VALID_MEMBER(zone_flags) &&
enumerator_value("ZONE_ALL_UNRECLAIMABLE",
(long *)&value1)) {
value2 = ULONG(zonebuf + OFFSET(zone_flags));
value3 = value2 & (1 << value1);
fprintf(fp, " ALL_UNRECLAIMABLE: %s ",
value3 ? "yes" : "no");
}
if (VALID_MEMBER(zone_pages_scanned)) {
value1 = ULONG(zonebuf +
OFFSET(zone_pages_scanned));
fprintf(fp, "PAGES_SCANNED: %ld ", value1);
}
fprintf(fp, "\n");
next_zone:
fprintf(fp, "\n");
node_zones += SIZE_OPTION(zone_struct, zone);
}
if ((n+1) < vt->numnodes)
pgdat = vt->node_table[n+1].pgdat;
else
pgdat = 0;
}
FREEBUF(zonebuf);
}
/*
* Gather essential information regarding each memory node.
*/
static void
node_table_init(void)
{
int n;
ulong pgdat;
/*
* Override numnodes -- some kernels may leave it at 1 on a system
* with multiple memory nodes.
*/
if ((vt->flags & NODES) && (VALID_MEMBER(pglist_data_node_next) ||
VALID_MEMBER(pglist_data_pgdat_next))) {
get_symbol_data("pgdat_list", sizeof(void *), &pgdat);
for (n = 0; pgdat; n++) {
readmem(pgdat + OFFSET_OPTION(pglist_data_node_next,
pglist_data_pgdat_next), KVADDR,
&pgdat, sizeof(void *), "pglist_data node_next",
FAULT_ON_ERROR);
}
if (n != vt->numnodes) {
if (CRASHDEBUG(2))
error(NOTE, "changing numnodes from %d to %d\n",
vt->numnodes, n);
vt->numnodes = n;
}
} else
vt->flags &= ~NODES;
if (!(vt->node_table = (struct node_table *)
malloc(sizeof(struct node_table) * vt->numnodes)))
error(FATAL, "cannot malloc node_table %s(%d nodes)",
vt->numnodes > 1 ? "array " : "", vt->numnodes);
BZERO(vt->node_table, sizeof(struct node_table) * vt->numnodes);
dump_memory_nodes(MEMORY_NODES_INITIALIZE);
qsort((void *)vt->node_table, (size_t)vt->numnodes,
sizeof(struct node_table), compare_node_data);
if (CRASHDEBUG(2))
dump_memory_nodes(MEMORY_NODES_DUMP);
}
/*
* The comparison function must return an integer less than,
* equal to, or greater than zero if the first argument is
* considered to be respectively less than, equal to, or
* greater than the second. If two members compare as equal,
* their order in the sorted array is undefined.
*/
static int
compare_node_data(const void *v1, const void *v2)
{
struct node_table *t1, *t2;
t1 = (struct node_table *)v1;
t2 = (struct node_table *)v2;
return (t1->node_id < t2->node_id ? -1 :
t1->node_id == t2->node_id ? 0 : 1);
}
/*
* Depending upon the processor, and whether we're running live or on a
* dumpfile, get the system page size.
*/
uint
memory_page_size(void)
{
uint psz;
if (machdep->pagesize)
return machdep->pagesize;
if (REMOTE_MEMSRC())
return remote_page_size();
switch (pc->flags & MEMORY_SOURCES)
{
case DISKDUMP:
psz = diskdump_page_size();
break;
case XENDUMP:
psz = xendump_page_size();
break;
case KDUMP:
psz = kdump_page_size();
break;
case NETDUMP:
psz = netdump_page_size();
break;
case MCLXCD:
psz = (uint)mclx_page_size();
break;
case LKCD:
#if 0 /* REMIND: */
psz = lkcd_page_size(); /* dh_dump_page_size is HW page size; should add dh_page_size */
#else
psz = (uint)getpagesize();
#endif
break;
case DEVMEM:
case MEMMOD:
case CRASHBUILTIN:
case KVMDUMP:
case PROC_KCORE:
psz = (uint)getpagesize();
break;
case S390D:
psz = s390_page_size();
break;
case SADUMP:
psz = sadump_page_size();
break;
default:
psz = 0;
error(FATAL, "memory_page_size: invalid pc->flags: %lx\n",
pc->flags & MEMORY_SOURCES);
}
return psz;
}
/*
* If the page size cannot be determined by the dumpfile (like kdump),
* and the processor default cannot be used, allow the force-feeding
* of a crash command-line page size option.
*/
void
force_page_size(char *s)
{
int k, err;
ulong psize;
k = 1;
err = FALSE;
psize = 0;
switch (LASTCHAR(s))
{
case 'k':
case 'K':
LASTCHAR(s) = NULLCHAR;
if (!decimal(s, 0)) {
err = TRUE;
break;
}
k = 1024;
/* FALLTHROUGH */
default:
if (decimal(s, 0))
psize = dtol(s, QUIET|RETURN_ON_ERROR, &err);
else if (hexadecimal(s, 0))
psize = htol(s, QUIET|RETURN_ON_ERROR, &err);
else
err = TRUE;
break;
}
if (err)
error(INFO, "invalid page size: %s\n", s);
else
machdep->pagesize = psize * k;
}
/*
* Return the vmalloc address referenced by the first vm_struct
* on the vmlist. This can normally be used by the machine-specific
* xxx_vmalloc_start() routines.
*/
ulong
first_vmalloc_address(void)
{
static ulong vmalloc_start = 0;
ulong vm_struct, vmap_area;
if (DUMPFILE() && vmalloc_start)
return vmalloc_start;
if (vt->flags & USE_VMAP_AREA) {
get_symbol_data("vmap_area_list", sizeof(void *), &vmap_area);
if (!vmap_area)
return 0;
if (!readmem(vmap_area - OFFSET(vmap_area_list) +
OFFSET(vmap_area_va_start), KVADDR, &vmalloc_start,
sizeof(void *), "first vmap_area va_start", RETURN_ON_ERROR))
non_matching_kernel();
} else if (kernel_symbol_exists("vmlist")) {
get_symbol_data("vmlist", sizeof(void *), &vm_struct);
if (!vm_struct)
return 0;
if (!readmem(vm_struct+OFFSET(vm_struct_addr), KVADDR,
&vmalloc_start, sizeof(void *),
"first vmlist addr", RETURN_ON_ERROR))
non_matching_kernel();
}
return vmalloc_start;
}
/*
* Return the highest vmalloc address in the vmlist.
*/
ulong
last_vmalloc_address(void)
{
struct meminfo meminfo;
static ulong vmalloc_limit = 0;
if (!vmalloc_limit || ACTIVE()) {
BZERO(&meminfo, sizeof(struct meminfo));
meminfo.memtype = KVADDR;
meminfo.spec_addr = 0;
meminfo.flags = (ADDRESS_SPECIFIED|GET_HIGHEST);
dump_vmlist(&meminfo);
vmalloc_limit = meminfo.retval;
}
return vmalloc_limit;
}
/*
* Determine whether an identity-mapped virtual address
* refers to an existant physical page, and if not bump
* it up to the next node.
*/
static int
next_identity_mapping(ulong vaddr, ulong *nextvaddr)
{
int n, retval;
struct node_table *nt;
ulonglong paddr, pstart, psave, pend;
ulong node_size;
paddr = VTOP(vaddr);
psave = 0;
retval = FALSE;
for (n = 0; n < vt->numnodes; n++) {
nt = &vt->node_table[n];
if ((vt->flags & V_MEM_MAP) && (vt->numnodes == 1))
node_size = vt->max_mapnr;
else
node_size = nt->size;
pstart = nt->start_paddr;
pend = pstart + ((ulonglong)node_size * PAGESIZE());
/*
* Check the next node.
*/
if (paddr >= pend)
continue;
/*
* Bump up to the next node, but keep looking in
* case of non-sequential nodes.
*/
if (paddr < pstart) {
if (psave && (psave < pstart))
continue;
*nextvaddr = PTOV(pstart);
psave = pstart;
retval = TRUE;
continue;
}
/*
* We're in the physical range.
*/
*nextvaddr = vaddr;
retval = TRUE;
break;
}
return retval;
}
/*
* Return the L1 cache size in bytes, which can be found stored in the
* cache_cache.
*/
int
l1_cache_size(void)
{
ulong cache;
ulong c_align;
int colour_off;
int retval;
retval = -1;
if (VALID_MEMBER(kmem_cache_s_c_align)) {
cache = symbol_value("cache_cache");
readmem(cache+OFFSET(kmem_cache_s_c_align),
KVADDR, &c_align, sizeof(ulong),
"c_align", FAULT_ON_ERROR);
retval = (int)c_align;
} else if (VALID_MEMBER(kmem_cache_s_colour_off)) {
cache = symbol_value("cache_cache");
readmem(cache+OFFSET(kmem_cache_s_colour_off),
KVADDR, &colour_off, sizeof(int),
"colour_off", FAULT_ON_ERROR);
retval = colour_off;
}
return retval;
}
/*
* Multi-purpose routine used to query/control dumpfile memory usage.
*/
int
dumpfile_memory(int cmd)
{
int retval;
retval = 0;
switch (cmd)
{
case DUMPFILE_MEM_USED:
if (REMOTE_DUMPFILE())
retval = remote_memory_used();
else if (pc->flags & NETDUMP)
retval = netdump_memory_used();
else if (pc->flags & KDUMP)
retval = kdump_memory_used();
else if (pc->flags & XENDUMP)
retval = xendump_memory_used();
else if (pc->flags & KVMDUMP)
retval = kvmdump_memory_used();
else if (pc->flags & DISKDUMP)
retval = diskdump_memory_used();
else if (pc->flags & LKCD)
retval = lkcd_memory_used();
else if (pc->flags & MCLXCD)
retval = vas_memory_used();
else if (pc->flags & S390D)
retval = s390_memory_used();
else if (pc->flags & SADUMP)
retval = sadump_memory_used();
break;
case DUMPFILE_FREE_MEM:
if (REMOTE_DUMPFILE())
retval = remote_free_memory();
else if (pc->flags & NETDUMP)
retval = netdump_free_memory();
else if (pc->flags & KDUMP)
retval = kdump_free_memory();
else if (pc->flags & XENDUMP)
retval = xendump_free_memory();
else if (pc->flags & KVMDUMP)
retval = kvmdump_free_memory();
else if (pc->flags & DISKDUMP)
retval = diskdump_free_memory();
else if (pc->flags & LKCD)
retval = lkcd_free_memory();
else if (pc->flags & MCLXCD)
retval = vas_free_memory(NULL);
else if (pc->flags & S390D)
retval = s390_free_memory();
else if (pc->flags & SADUMP)
retval = sadump_free_memory();
break;
case DUMPFILE_MEM_DUMP:
if (REMOTE_DUMPFILE())
retval = remote_memory_dump(0);
else if (pc->flags & NETDUMP)
retval = netdump_memory_dump(fp);
else if (pc->flags & KDUMP)
retval = kdump_memory_dump(fp);
else if (pc->flags & XENDUMP)
retval = xendump_memory_dump(fp);
else if (pc->flags & KVMDUMP)
retval = kvmdump_memory_dump(fp);
else if (pc->flags & DISKDUMP)
retval = diskdump_memory_dump(fp);
else if (pc->flags & LKCD)
retval = lkcd_memory_dump(set_lkcd_fp(fp));
else if (pc->flags & MCLXCD)
retval = vas_memory_dump(fp);
else if (pc->flags & S390D)
retval = s390_memory_dump(fp);
else if (pc->flags & PROC_KCORE)
retval = kcore_memory_dump(fp);
else if (pc->flags & SADUMP)
retval = sadump_memory_dump(fp);
break;
case DUMPFILE_ENVIRONMENT:
if (pc->flags & LKCD) {
set_lkcd_fp(fp);
dump_lkcd_environment(0);
} else if (pc->flags & REM_LKCD)
retval = remote_memory_dump(VERBOSE);
break;
}
return retval;
}
/*
* Functions for sparse mem support
*/
ulong
sparse_decode_mem_map(ulong coded_mem_map, ulong section_nr)
{
return coded_mem_map +
(section_nr_to_pfn(section_nr) * SIZE(page));
}
void
sparse_mem_init(void)
{
ulong addr;
ulong mem_section_size;
int len, dimension;
if (!IS_SPARSEMEM())
return;
MEMBER_OFFSET_INIT(mem_section_section_mem_map, "mem_section",
"section_mem_map");
if (!MAX_PHYSMEM_BITS())
error(FATAL,
"CONFIG_SPARSEMEM kernels not supported for this architecture\n");
if ((len = get_array_length("mem_section", &dimension, 0) ==
(NR_MEM_SECTIONS() / _SECTIONS_PER_ROOT_EXTREME())) || !dimension)
vt->flags |= SPARSEMEM_EX;
if (IS_SPARSEMEM_EX()) {
machdep->sections_per_root = _SECTIONS_PER_ROOT_EXTREME();
mem_section_size = sizeof(void *) * NR_SECTION_ROOTS();
} else {
machdep->sections_per_root = _SECTIONS_PER_ROOT();
mem_section_size = SIZE(mem_section) * NR_SECTION_ROOTS();
}
if (CRASHDEBUG(1)) {
fprintf(fp, "PAGESIZE=%d\n",PAGESIZE());
fprintf(fp,"mem_section_size = %ld\n", mem_section_size);
fprintf(fp, "NR_SECTION_ROOTS = %ld\n", NR_SECTION_ROOTS());
fprintf(fp, "NR_MEM_SECTIONS = %ld\n", NR_MEM_SECTIONS());
fprintf(fp, "SECTIONS_PER_ROOT = %ld\n", SECTIONS_PER_ROOT() );
fprintf(fp, "SECTION_ROOT_MASK = 0x%lx\n", SECTION_ROOT_MASK());
fprintf(fp, "PAGES_PER_SECTION = %ld\n", PAGES_PER_SECTION());
if (IS_SPARSEMEM_EX() && !len)
error(WARNING, "SPARSEMEM_EX: questionable section values\n");
}
if (!(vt->mem_sec = (void *)malloc(mem_section_size)))
error(FATAL, "cannot malloc mem_sec cache\n");
if (!(vt->mem_section = (char *)malloc(SIZE(mem_section))))
error(FATAL, "cannot malloc mem_section cache\n");
addr = symbol_value("mem_section");
readmem(addr, KVADDR,vt->mem_sec ,mem_section_size,
"memory section root table", FAULT_ON_ERROR);
}
char *
read_mem_section(ulong addr)
{
if ((addr == 0) || !IS_KVADDR(addr))
return 0;
readmem(addr, KVADDR, vt->mem_section, SIZE(mem_section),
"memory section", FAULT_ON_ERROR);
return vt->mem_section;
}
ulong
nr_to_section(ulong nr)
{
ulong addr;
ulong *mem_sec = vt->mem_sec;
if (IS_SPARSEMEM_EX()) {
if (SECTION_NR_TO_ROOT(nr) >= NR_SECTION_ROOTS()) {
if (!STREQ(pc->curcmd, "rd") &&
!STREQ(pc->curcmd, "search") &&
!STREQ(pc->curcmd, "kmem"))
error(WARNING,
"sparsemem: invalid section number: %ld\n",
nr);
return 0;
}
}
if (IS_SPARSEMEM_EX()) {
if ((mem_sec[SECTION_NR_TO_ROOT(nr)] == 0) ||
!IS_KVADDR(mem_sec[SECTION_NR_TO_ROOT(nr)]))
return 0;
addr = mem_sec[SECTION_NR_TO_ROOT(nr)] +
(nr & SECTION_ROOT_MASK()) * SIZE(mem_section);
} else
addr = symbol_value("mem_section") +
(SECTIONS_PER_ROOT() * SECTION_NR_TO_ROOT(nr) +
(nr & SECTION_ROOT_MASK())) * SIZE(mem_section);
if (!IS_KVADDR(addr))
return 0;
return addr;
}
/*
* We use the lower bits of the mem_map pointer to store
* a little bit of information. There should be at least
* 3 bits here due to 32-bit alignment.
*/
#define SECTION_MARKED_PRESENT (1UL<<0)
#define SECTION_HAS_MEM_MAP (1UL<<1)
#define SECTION_MAP_LAST_BIT (1UL<<2)
#define SECTION_MAP_MASK (~(SECTION_MAP_LAST_BIT-1))
int
valid_section(ulong addr)
{
char *mem_section;
if ((mem_section = read_mem_section(addr)))
return (ULONG(mem_section +
OFFSET(mem_section_section_mem_map)) &&
SECTION_MARKED_PRESENT);
return 0;
}
int
section_has_mem_map(ulong addr)
{
char *mem_section;
if ((mem_section = read_mem_section(addr)))
return (ULONG(mem_section +
OFFSET(mem_section_section_mem_map))
&& SECTION_HAS_MEM_MAP);
return 0;
}
ulong
section_mem_map_addr(ulong addr)
{
char *mem_section;
ulong map;
if ((mem_section = read_mem_section(addr))) {
map = ULONG(mem_section +
OFFSET(mem_section_section_mem_map));
map &= SECTION_MAP_MASK;
return map;
}
return 0;
}
ulong
valid_section_nr(ulong nr)
{
ulong addr = nr_to_section(nr);
if (valid_section(addr))
return addr;
return 0;
}
ulong
pfn_to_map(ulong pfn)
{
ulong section, page_offset;
ulong section_nr;
ulong coded_mem_map, mem_map;
section_nr = pfn_to_section_nr(pfn);
if (!(section = valid_section_nr(section_nr)))
return 0;
if (section_has_mem_map(section)) {
page_offset = pfn - section_nr_to_pfn(section_nr);
coded_mem_map = section_mem_map_addr(section);
mem_map = sparse_decode_mem_map(coded_mem_map, section_nr) +
(page_offset * SIZE(page));
return mem_map;
}
return 0;
}
void
dump_mem_sections(void)
{
ulong nr,addr;
ulong nr_mem_sections;
ulong coded_mem_map, mem_map, pfn;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char buf3[BUFSIZE];
char buf4[BUFSIZE];
nr_mem_sections = NR_MEM_SECTIONS();
fprintf(fp, "\n");
pad_line(fp, BITS32() ? 59 : 67, '-');
fprintf(fp, "\n\nNR %s %s %s PFN\n",
mkstring(buf1, VADDR_PRLEN, CENTER|LJUST, "SECTION"),
mkstring(buf2, MAX(VADDR_PRLEN,strlen("CODED_MEM_MAP")),
CENTER|LJUST, "CODED_MEM_MAP"),
mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "MEM_MAP"));
for (nr = 0; nr < nr_mem_sections ; nr++) {
if ((addr = valid_section_nr(nr))) {
coded_mem_map = section_mem_map_addr(addr);
mem_map = sparse_decode_mem_map(coded_mem_map,nr);
pfn = section_nr_to_pfn(nr);
fprintf(fp, "%2ld %s %s %s %s\n",
nr,
mkstring(buf1, VADDR_PRLEN,
CENTER|LONG_HEX, MKSTR(addr)),
mkstring(buf2, MAX(VADDR_PRLEN,
strlen("CODED_MEM_MAP")),
CENTER|LONG_HEX|RJUST, MKSTR(coded_mem_map)),
mkstring(buf3, VADDR_PRLEN,
CENTER|LONG_HEX|RJUST, MKSTR(mem_map)),
pc->output_radix == 10 ?
mkstring(buf4, VADDR_PRLEN,
LONG_DEC|LJUST, MKSTR(pfn)) :
mkstring(buf4, VADDR_PRLEN,
LONG_HEX|LJUST, MKSTR(pfn)));
}
}
}
void
list_mem_sections(void)
{
ulong nr,addr;
ulong nr_mem_sections = NR_MEM_SECTIONS();
ulong coded_mem_map;
for (nr = 0; nr <= nr_mem_sections ; nr++) {
if ((addr = valid_section_nr(nr))) {
coded_mem_map = section_mem_map_addr(addr);
fprintf(fp,
"nr=%ld section = %lx coded_mem_map=%lx pfn=%ld mem_map=%lx\n",
nr,
addr,
coded_mem_map,
section_nr_to_pfn(nr),
sparse_decode_mem_map(coded_mem_map,nr));
}
}
}
/*
* For kernels containing the node_online_map or node_states[],
* return the number of online node bits set.
*/
static int
get_nodes_online(void)
{
int i, len, online;
struct gnu_request req;
ulong *maskptr;
long N_ONLINE;
ulong mapaddr;
if (!symbol_exists("node_online_map") &&
!symbol_exists("node_states"))
return 0;
len = mapaddr = 0;
if (symbol_exists("node_online_map")) {
if (LKCD_KERNTYPES()) {
if ((len = STRUCT_SIZE("nodemask_t")) < 0)
error(FATAL,
"cannot determine type nodemask_t\n");
mapaddr = symbol_value("node_online_map");
} else {
len = get_symbol_type("node_online_map", NULL, &req)
== TYPE_CODE_UNDEF ? sizeof(ulong) : req.length;
mapaddr = symbol_value("node_online_map");
}
} else if (symbol_exists("node_states")) {
if ((get_symbol_type("node_states", NULL, &req) != TYPE_CODE_ARRAY) ||
!(len = get_array_length("node_states", NULL, 0)) ||
!enumerator_value("N_ONLINE", &N_ONLINE))
return 0;
len = req.length / len;
mapaddr = symbol_value("node_states") + (N_ONLINE * len);
}
if (!(vt->node_online_map = (ulong *)malloc(len)))
error(FATAL, "cannot malloc node_online_map\n");
if (!readmem(mapaddr, KVADDR,
(void *)&vt->node_online_map[0], len, "node_online_map",
QUIET|RETURN_ON_ERROR))
error(FATAL, "cannot read node_online_map/node_states\n");
vt->node_online_map_len = len/sizeof(ulong);
online = 0;
maskptr = (ulong *)vt->node_online_map;
for (i = 0; i < vt->node_online_map_len; i++, maskptr++)
online += count_bits_long(*maskptr);
if (CRASHDEBUG(1)) {
fprintf(fp, "node_online_map: [");
for (i = 0; i < vt->node_online_map_len; i++)
fprintf(fp, "%s%lx", i ? ", " : "", vt->node_online_map[i]);
fprintf(fp, "] -> nodes online: %d\n", online);
}
if (online)
vt->numnodes = online;
return online;
}
/*
* Return the next node index, with "first" being the first acceptable node.
*/
static int
next_online_node(int first)
{
int i, j, node;
ulong mask, *maskptr;
if ((first/BITS_PER_LONG) >= vt->node_online_map_len) {
error(INFO, "next_online_node: %d is too large!\n", first);
return -1;
}
maskptr = (ulong *)vt->node_online_map;
for (i = node = 0; i < vt->node_online_map_len; i++, maskptr++) {
mask = *maskptr;
for (j = 0; j < BITS_PER_LONG; j++, node++) {
if (mask & 1) {
if (node >= first)
return node;
}
mask >>= 1;
}
}
return -1;
}
/*
* Modify appropriately for architecture/kernel nuances.
*/
static ulong
next_online_pgdat(int node)
{
char buf[BUFSIZE];
ulong pgdat;
/*
* Default -- look for type: struct pglist_data node_data[]
*/
if (LKCD_KERNTYPES()) {
if (!kernel_symbol_exists("node_data"))
goto pgdat2;
/*
* Just index into node_data[] without checking that it is
* an array; kerntypes have no such symbol information.
*/
} else {
if (get_symbol_type("node_data", NULL, NULL) != TYPE_CODE_ARRAY)
goto pgdat2;
open_tmpfile();
sprintf(buf, "whatis node_data");
if (!gdb_pass_through(buf, fp, GNU_RETURN_ON_ERROR)) {
close_tmpfile();
goto pgdat2;
}
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (STRNEQ(buf, "type = "))
break;
}
close_tmpfile();
if ((!strstr(buf, "struct pglist_data *") &&
!strstr(buf, "pg_data_t *")) ||
(count_chars(buf, '[') != 1) ||
(count_chars(buf, ']') != 1))
goto pgdat2;
}
if (!readmem(symbol_value("node_data") + (node * sizeof(void *)),
KVADDR, &pgdat, sizeof(void *), "node_data", RETURN_ON_ERROR) ||
!IS_KVADDR(pgdat))
goto pgdat2;
return pgdat;
pgdat2:
if (LKCD_KERNTYPES()) {
if (!kernel_symbol_exists("pgdat_list"))
goto pgdat3;
} else {
if (get_symbol_type("pgdat_list",NULL,NULL) != TYPE_CODE_ARRAY)
goto pgdat3;
open_tmpfile();
sprintf(buf, "whatis pgdat_list");
if (!gdb_pass_through(buf, fp, GNU_RETURN_ON_ERROR)) {
close_tmpfile();
goto pgdat3;
}
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (STRNEQ(buf, "type = "))
break;
}
close_tmpfile();
if ((!strstr(buf, "struct pglist_data *") &&
!strstr(buf, "pg_data_t *")) ||
(count_chars(buf, '[') != 1) ||
(count_chars(buf, ']') != 1))
goto pgdat3;
}
if (!readmem(symbol_value("pgdat_list") + (node * sizeof(void *)),
KVADDR, &pgdat, sizeof(void *), "pgdat_list", RETURN_ON_ERROR) ||
!IS_KVADDR(pgdat))
goto pgdat3;
return pgdat;
pgdat3:
if (symbol_exists("contig_page_data") && (node == 0))
return symbol_value("contig_page_data");
return 0;
}
/*
* Make the vm_stat[] array contents easily accessible.
*/
static int
vm_stat_init(void)
{
char buf[BUFSIZE];
char *arglist[MAXARGS];
int i, count, stringlen, total;
int c ATTRIBUTE_UNUSED;
struct gnu_request *req;
char *start;
long enum_value;
if (vt->flags & VM_STAT)
return TRUE;
if ((vt->nr_vm_stat_items == -1) || !symbol_exists("vm_stat"))
goto bailout;
/*
* look for type: type = atomic_long_t []
*/
if (LKCD_KERNTYPES()) {
if (!symbol_exists("vm_stat"))
goto bailout;
/*
* Just assume that vm_stat is an array; there is
* no symbol info in a kerntypes file.
*/
} else {
if (!symbol_exists("vm_stat") ||
get_symbol_type("vm_stat", NULL, NULL) != TYPE_CODE_ARRAY)
goto bailout;
vt->nr_vm_stat_items = get_array_length("vm_stat", NULL, 0);
}
open_tmpfile();
req = (struct gnu_request *)GETBUF(sizeof(struct gnu_request));
req->command = GNU_GET_DATATYPE;
req->name = "zone_stat_item";
req->flags = GNU_PRINT_ENUMERATORS;
gdb_interface(req);
FREEBUF(req);
stringlen = 1;
count = -1;
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (strstr(buf, "{") || strstr(buf, "}"))
continue;
clean_line(buf);
c = parse_line(buf, arglist);
if (STREQ(arglist[0], "NR_VM_ZONE_STAT_ITEMS")) {
if (LKCD_KERNTYPES())
vt->nr_vm_stat_items =
MAX(atoi(arglist[2]), count);
break;
} else {
stringlen += strlen(arglist[0]);
count++;
}
}
total = stringlen + vt->nr_vm_stat_items +
(sizeof(void *) * vt->nr_vm_stat_items);
if (!(vt->vm_stat_items = (char **)malloc(total))) {
close_tmpfile();
error(FATAL, "cannot malloc vm_stat_items cache\n");
}
BZERO(vt->vm_stat_items, total);
start = (char *)&vt->vm_stat_items[vt->nr_vm_stat_items];
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (strstr(buf, "{") || strstr(buf, "}"))
continue;
c = parse_line(buf, arglist);
if (enumerator_value(arglist[0], &enum_value))
i = enum_value;
else {
close_tmpfile();
goto bailout;
}
if (i < vt->nr_vm_stat_items) {
vt->vm_stat_items[i] = start;
strcpy(start, arglist[0]);
start += strlen(arglist[0]) + 1;
}
}
close_tmpfile();
vt->flags |= VM_STAT;
return TRUE;
bailout:
vt->nr_vm_stat_items = -1;
return FALSE;
}
/*
* Either dump all vm_stat entries, or return the value of
* the specified vm_stat item. Use the global counter unless
* a zone-specific address is passed.
*/
static int
dump_vm_stat(char *item, long *retval, ulong zone)
{
char *buf;
ulong *vp;
ulong location;
int i, maxlen, len;
if (!vm_stat_init()) {
if (!item)
if (CRASHDEBUG(1))
error(INFO,
"vm_stat not available in this kernel\n");
return FALSE;
}
buf = GETBUF(sizeof(ulong) * vt->nr_vm_stat_items);
location = zone ? zone : symbol_value("vm_stat");
readmem(location, KVADDR, buf,
sizeof(ulong) * vt->nr_vm_stat_items,
"vm_stat", FAULT_ON_ERROR);
if (!item) {
if (!zone)
fprintf(fp, " VM_STAT:\n");
for (i = maxlen = 0; i < vt->nr_vm_stat_items; i++)
if ((len = strlen(vt->vm_stat_items[i])) > maxlen)
maxlen = len;
vp = (ulong *)buf;
for (i = 0; i < vt->nr_vm_stat_items; i++)
fprintf(fp, "%s%s: %ld\n",
space(maxlen - strlen(vt->vm_stat_items[i])),
vt->vm_stat_items[i], vp[i]);
return TRUE;
}
vp = (ulong *)buf;
for (i = 0; i < vt->nr_vm_stat_items; i++) {
if (STREQ(vt->vm_stat_items[i], item)) {
*retval = vp[i];
return TRUE;
}
}
return FALSE;
}
/*
* Dump the cumulative totals of the per_cpu__page_states counters.
*/
int
dump_page_states(void)
{
struct syment *sp;
ulong addr, value;
int i, c, fd, len, instance, members;
char buf[BUFSIZE];
char *arglist[MAXARGS];
struct entry {
char *name;
ulong value;
} *entry_list;
struct stat stat;
char *namebuf, *nameptr;
if (!(sp = per_cpu_symbol_search("per_cpu__page_states"))) {
if (CRASHDEBUG(1))
error(INFO, "per_cpu__page_states"
"not available in this kernel\n");
return FALSE;
}
instance = members = len = 0;
sprintf(buf, "ptype struct page_state");
open_tmpfile();
if (!gdb_pass_through(buf, fp, GNU_RETURN_ON_ERROR)) {
close_tmpfile();
return FALSE;
}
fflush(pc->tmpfile);
fd = fileno(pc->tmpfile);
fstat(fd, &stat);
namebuf = GETBUF(stat.st_size);
nameptr = namebuf;
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (strstr(buf, "struct page_state") ||
strstr(buf, "}"))
continue;
members++;
}
entry_list = (struct entry *)
GETBUF(sizeof(struct entry) * members);
rewind(pc->tmpfile);
i = 0;
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (strstr(buf, "struct page_state") ||
strstr(buf, "}"))
continue;
strip_ending_char(strip_linefeeds(buf), ';');
c = parse_line(buf, arglist);
strcpy(nameptr, arglist[c-1]);
entry_list[i].name = nameptr;
if (strlen(nameptr) > len)
len = strlen(nameptr);
nameptr += strlen(nameptr)+2;
i++;
}
close_tmpfile();
open_tmpfile();
for (c = 0; c < kt->cpus; c++) {
addr = sp->value + kt->__per_cpu_offset[c];
dump_struct("page_state", addr, RADIX(16));
}
i = 0;
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (strstr(buf, "struct page_state")) {
instance++;
i = 0;
continue;
}
if (strstr(buf, "}"))
continue;
strip_linefeeds(buf);
extract_hex(buf, &value, ',', TRUE);
entry_list[i].value += value;
i++;
}
close_tmpfile();
fprintf(fp, " PAGE_STATES:\n");
for (i = 0; i < members; i++) {
sprintf(buf, "%s", entry_list[i].name);
fprintf(fp, "%s", mkstring(buf, len+2, RJUST, 0));
fprintf(fp, ": %ld\n", entry_list[i].value);
}
FREEBUF(namebuf);
FREEBUF(entry_list);
return TRUE;
}
/*
* Dump the cumulative totals of the per_cpu__vm_event_state
* counters.
*/
static int
dump_vm_event_state(void)
{
int i, c, maxlen, len;
struct syment *sp;
ulong addr;
ulong *events, *cumulative;
if (!vm_event_state_init())
return FALSE;
events = (ulong *)GETBUF((sizeof(ulong) * vt->nr_vm_event_items) * 2);
cumulative = &events[vt->nr_vm_event_items];
sp = per_cpu_symbol_search("per_cpu__vm_event_states");
for (c = 0; c < kt->cpus; c++) {
addr = sp->value + kt->__per_cpu_offset[c];
if (CRASHDEBUG(1)) {
fprintf(fp, "[%d]: %lx\n", c, addr);
dump_struct("vm_event_state", addr, RADIX(16));
}
readmem(addr, KVADDR, events,
sizeof(ulong) * vt->nr_vm_event_items,
"vm_event_states buffer", FAULT_ON_ERROR);
for (i = 0; i < vt->nr_vm_event_items; i++)
cumulative[i] += events[i];
}
fprintf(fp, "\n VM_EVENT_STATES:\n");
for (i = maxlen = 0; i < vt->nr_vm_event_items; i++)
if ((len = strlen(vt->vm_event_items[i])) > maxlen)
maxlen = len;
for (i = 0; i < vt->nr_vm_event_items; i++)
fprintf(fp, "%s%s: %ld\n",
space(maxlen - strlen(vt->vm_event_items[i])),
vt->vm_event_items[i], cumulative[i]);
FREEBUF(events);
return TRUE;
}
static int
vm_event_state_init(void)
{
int i, stringlen, total;
int c ATTRIBUTE_UNUSED;
long count, enum_value;
struct gnu_request *req;
char *arglist[MAXARGS];
char buf[BUFSIZE];
char *start;
if (vt->flags & VM_EVENT)
return TRUE;
if ((vt->nr_vm_event_items == -1) ||
!per_cpu_symbol_search("per_cpu__vm_event_states"))
goto bailout;
if (!enumerator_value("NR_VM_EVENT_ITEMS", &count))
return FALSE;
vt->nr_vm_event_items = count;
open_tmpfile();
req = (struct gnu_request *)GETBUF(sizeof(struct gnu_request));
req->command = GNU_GET_DATATYPE;
req->name = "vm_event_item";
req->flags = GNU_PRINT_ENUMERATORS;
gdb_interface(req);
FREEBUF(req);
stringlen = 1;
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (strstr(buf, "{") || strstr(buf, "}"))
continue;
clean_line(buf);
c = parse_line(buf, arglist);
if (STREQ(arglist[0], "NR_VM_EVENT_ITEMS"))
break;
else
stringlen += strlen(arglist[0]);
}
total = stringlen + vt->nr_vm_event_items +
(sizeof(void *) * vt->nr_vm_event_items);
if (!(vt->vm_event_items = (char **)malloc(total))) {
close_tmpfile();
error(FATAL, "cannot malloc vm_event_items cache\n");
}
BZERO(vt->vm_event_items, total);
start = (char *)&vt->vm_event_items[vt->nr_vm_event_items];
rewind(pc->tmpfile);
while (fgets(buf, BUFSIZE, pc->tmpfile)) {
if (strstr(buf, "{") || strstr(buf, "}"))
continue;
c = parse_line(buf, arglist);
if (enumerator_value(arglist[0], &enum_value))
i = enum_value;
else {
close_tmpfile();
goto bailout;
}
if (i < vt->nr_vm_event_items) {
vt->vm_event_items[i] = start;
strcpy(start, arglist[0]);
start += strlen(arglist[0]) + 1;
}
}
close_tmpfile();
vt->flags |= VM_EVENT;
return TRUE;
bailout:
vt->nr_vm_event_items = -1;
return FALSE;
}
/*
* Dump the per-cpu offset values that are used to
* resolve per-cpu symbol values.
*/
static void
dump_per_cpu_offsets(void)
{
int c;
char buf[BUFSIZE];
fprintf(fp, "PER-CPU OFFSET VALUES:\n");
for (c = 0; c < kt->cpus; c++) {
sprintf(buf, "CPU %d", c);
fprintf(fp, "%7s: %lx", buf, kt->__per_cpu_offset[c]);
if (hide_offline_cpu(c))
fprintf(fp, " [OFFLINE]\n");
else
fprintf(fp, "\n");
}
}
/*
* Dump the value(s) of a page->flags bitmap.
*/
void
dump_page_flags(ulonglong flags)
{
int c ATTRIBUTE_UNUSED;
int sz, val, found, largest, longest, header_printed;
char buf1[BUFSIZE];
char buf2[BUFSIZE];
char header[BUFSIZE];
char *arglist[MAXARGS];
ulonglong tmpflag;
found = longest = largest = header_printed = 0;
open_tmpfile();
if (dump_enumerator_list("pageflags")) {
rewind(pc->tmpfile);
while (fgets(buf1, BUFSIZE, pc->tmpfile)) {
if (strstr(buf1, " = ")) {
c = parse_line(buf1, arglist);
if ((sz = strlen(arglist[0])) > longest)
longest = sz;
if (strstr(arglist[0], "PG_") &&
((val = atoi(arglist[2])) > largest))
largest = val;
}
}
} else
error(FATAL, "enum pageflags does not exist in this kernel\n");
largest = (largest+1)/4 + 1;
sprintf(header, "%s BIT VALUE\n",
mkstring(buf1, longest, LJUST, "PAGE-FLAG"));
rewind(pc->tmpfile);
if (flags)
fprintf(pc->saved_fp, "FLAGS: %llx\n", flags);
fprintf(pc->saved_fp, "%s%s", flags ? " " : "", header);
while (fgets(buf1, BUFSIZE, pc->tmpfile)) {
if (strstr(buf1, " = ") && strstr(buf1, "PG_")) {
c = parse_line(buf1, arglist);
val = atoi(arglist[2]);
tmpflag = 1ULL << val;
if (!flags || (flags & tmpflag)) {
fprintf(pc->saved_fp, "%s%s %2d %.*lx\n",
flags ? " " : "",
mkstring(buf2, longest, LJUST,
arglist[0]), val,
largest, (ulong)(1ULL << val));
if (flags & tmpflag)
found++;
}
}
}
if (flags && !found)
fprintf(pc->saved_fp, " (none found)\n");
close_tmpfile();
}
/*
* Support for slub.c slab cache.
*/
static void
kmem_cache_init_slub(void)
{
if (CRASHDEBUG(1) &&
!(vt->flags & CONFIG_NUMA) && (vt->numnodes > 1))
error(WARNING,
"kmem_cache_init_slub: numnodes: %d without CONFIG_NUMA\n",
vt->numnodes);
kmem_cache_downsize();
vt->cpu_slab_type = MEMBER_TYPE("kmem_cache", "cpu_slab");
vt->flags |= KMEM_CACHE_INIT;
}
static void
kmem_cache_list_common(void)
{
int i, cnt;
ulong *cache_list;
ulong name;
char buf[BUFSIZE];
cnt = get_kmem_cache_list(&cache_list);
for (i = 0; i < cnt; i++) {
fprintf(fp, "%lx ", cache_list[i]);
readmem(cache_list[i] + OFFSET(kmem_cache_name),
KVADDR, &name, sizeof(char *),
"kmem_cache.name", FAULT_ON_ERROR);
if (!read_string(name, buf, BUFSIZE-1))
sprintf(buf, "(unknown)\n");
fprintf(fp, "%s\n", buf);
}
FREEBUF(cache_list);
}
#define DUMP_KMEM_CACHE_INFO_SLUB() dump_kmem_cache_info_slub(si)
static void
dump_kmem_cache_info_slub(struct meminfo *si)
{
char b1[BUFSIZE];
char b2[BUFSIZE];
int namelen, sizelen, spacelen;
fprintf(fp, "%s ",
mkstring(b1, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(si->cache)));
namelen = strlen(si->curname);
sprintf(b2, "%ld", si->objsize);
sizelen = strlen(b2);
spacelen = 0;
if (namelen++ > 18) {
spacelen = 29 - namelen - sizelen;
fprintf(fp, "%s%s%ld ", si->curname,
space(spacelen <= 0 ? 1 : spacelen), si->objsize);
if (spacelen > 0)
spacelen = 1;
sprintf(b1, "%c%dld ", '%', 9 + spacelen - 1);
} else {
fprintf(fp, "%-18s %8ld ", si->curname, si->objsize);
sprintf(b1, "%c%dld ", '%', 9);
}
fprintf(fp, b1, si->inuse);
fprintf(fp, "%8ld %5ld %4ldk\n",
si->num_slabs * si->objects,
si->num_slabs, si->slabsize/1024);
}
static void
dump_kmem_cache_slub(struct meminfo *si)
{
int i;
ulong name, oo;
unsigned int size, objsize, objects, order, offset;
char *reqname, *p1;
char kbuf[BUFSIZE];
char buf[BUFSIZE];
if (INVALID_MEMBER(kmem_cache_node_nr_slabs)) {
error(INFO,
"option requires kmem_cache_node.nr_slabs member!\n"
"(the kernel must be built with CONFIG_SLUB_DEBUG)\n");
return;
}
order = objects = 0;
si->cache_count = get_kmem_cache_list(&si->cache_list);
si->cache_buf = GETBUF(SIZE(kmem_cache));
if (VALID_MEMBER(page_objects) &&
OFFSET(page_objects) == OFFSET(page_inuse))
si->flags |= SLAB_BITFIELD;
if (!si->reqname &&
!(si->flags & (ADDRESS_SPECIFIED|GET_SLAB_PAGES)))
fprintf(fp, "%s", kmem_cache_hdr);
if (si->flags & ADDRESS_SPECIFIED) {
if ((p1 = is_slab_page(si, kbuf))) {
si->flags |= VERBOSE;
si->slab = (ulong)si->spec_addr;
} else if (!(p1 = vaddr_to_kmem_cache(si->spec_addr, kbuf,
VERBOSE))) {
error(INFO,
"address is not allocated in slab subsystem: %lx\n",
si->spec_addr);
goto bailout;
}
if (si->reqname && (si->reqname != p1))
error(INFO,
"ignoring pre-selected %s cache for address: %lx\n",
si->reqname, si->spec_addr, si->reqname);
reqname = p1;
} else
reqname = si->reqname;
for (i = 0; i < si->cache_count; i++) {
BZERO(si->cache_buf, SIZE(kmem_cache));
if (!readmem(si->cache_list[i], KVADDR, si->cache_buf,
SIZE(kmem_cache), "kmem_cache buffer",
RETURN_ON_ERROR|RETURN_PARTIAL))
goto next_cache;
name = ULONG(si->cache_buf + OFFSET(kmem_cache_name));
if (!read_string(name, buf, BUFSIZE-1))
sprintf(buf, "(unknown)");
if (reqname) {
if (!STREQ(reqname, buf))
continue;
fprintf(fp, "%s", kmem_cache_hdr);
}
if (ignore_cache(si, buf)) {
fprintf(fp, "%lx %-18s [IGNORED]\n",
si->cache_list[i], buf);
goto next_cache;
}
objsize = UINT(si->cache_buf + OFFSET(kmem_cache_objsize));
size = UINT(si->cache_buf + OFFSET(kmem_cache_size));
offset = UINT(si->cache_buf + OFFSET(kmem_cache_offset));
if (VALID_MEMBER(kmem_cache_objects)) {
objects = UINT(si->cache_buf +
OFFSET(kmem_cache_objects));
order = UINT(si->cache_buf + OFFSET(kmem_cache_order));
} else if (VALID_MEMBER(kmem_cache_oo)) {
oo = ULONG(si->cache_buf + OFFSET(kmem_cache_oo));
objects = oo_objects(oo);
order = oo_order(oo);
} else
error(FATAL, "cannot determine "
"kmem_cache objects/order values\n");
si->cache = si->cache_list[i];
si->curname = buf;
si->objsize = objsize;
si->size = size;
si->objects = objects;
si->slabsize = (PAGESIZE() << order);
si->inuse = si->num_slabs = 0;
si->slab_offset = offset;
if (!get_kmem_cache_slub_data(GET_SLUB_SLABS, si) ||
!get_kmem_cache_slub_data(GET_SLUB_OBJECTS, si))
goto next_cache;
DUMP_KMEM_CACHE_INFO_SLUB();
if (si->flags & ADDRESS_SPECIFIED) {
if (!si->slab)
si->slab = vaddr_to_slab(si->spec_addr);
do_slab_slub(si, VERBOSE);
} else if (si->flags & VERBOSE) {
do_kmem_cache_slub(si);
if (!reqname && ((i+1) < si->cache_count))
fprintf(fp, "%s", kmem_cache_hdr);
}
next_cache:
if (reqname)
break;
}
bailout:
FREEBUF(si->cache_list);
FREEBUF(si->cache_buf);
}
/*
* Emulate the total count calculation done by the
* slab_objects() sysfs function in slub.c.
*/
static int
get_kmem_cache_slub_data(long cmd, struct meminfo *si)
{
int i, n, node;
ulong total_objects, total_slabs;
ulong cpu_slab_ptr, node_ptr;
ulong node_nr_partial, node_nr_slabs;
int full_slabs, objects;
long p;
short inuse;
ulong *nodes, *per_cpu;
struct node_table *nt;
/*
* nodes[n] is not being used (for now)
* per_cpu[n] is a count of cpu_slab pages per node.
*/
nodes = (ulong *)GETBUF(2 * sizeof(ulong) * vt->numnodes);
per_cpu = nodes + vt->numnodes;
total_slabs = total_objects = 0;
for (i = 0; i < kt->cpus; i++) {
cpu_slab_ptr = get_cpu_slab_ptr(si, i, NULL);
if (!cpu_slab_ptr)
continue;
if ((node = page_to_nid(cpu_slab_ptr)) < 0)
goto bailout;
switch (cmd)
{
case GET_SLUB_OBJECTS:
if (!readmem(cpu_slab_ptr + OFFSET(page_inuse),
KVADDR, &inuse, sizeof(short),
"page inuse", RETURN_ON_ERROR))
return FALSE;
total_objects += inuse;
break;
case GET_SLUB_SLABS:
total_slabs++;
break;
}
per_cpu[node]++;
}
for (n = 0; n < vt->numnodes; n++) {
if (vt->flags & CONFIG_NUMA) {
nt = &vt->node_table[n];
node_ptr = ULONG(si->cache_buf +
OFFSET(kmem_cache_node) +
(sizeof(void *) * nt->node_id));
} else
node_ptr = si->cache +
OFFSET(kmem_cache_local_node);
if (!node_ptr)
continue;
if (!readmem(node_ptr + OFFSET(kmem_cache_node_nr_partial),
KVADDR, &node_nr_partial, sizeof(ulong),
"kmem_cache_node nr_partial", RETURN_ON_ERROR))
goto bailout;
if (!readmem(node_ptr + OFFSET(kmem_cache_node_nr_slabs),
KVADDR, &node_nr_slabs, sizeof(ulong),
"kmem_cache_node nr_slabs", RETURN_ON_ERROR))
goto bailout;
switch (cmd)
{
case GET_SLUB_OBJECTS:
if ((p = count_partial(node_ptr, si)) < 0)
return FALSE;
total_objects += p;
break;
case GET_SLUB_SLABS:
total_slabs += node_nr_partial;
break;
}
full_slabs = node_nr_slabs - per_cpu[n] - node_nr_partial;
objects = si->objects;
switch (cmd)
{
case GET_SLUB_OBJECTS:
total_objects += (full_slabs * objects);
break;
case GET_SLUB_SLABS:
total_slabs += full_slabs;
break;
}
if (!(vt->flags & CONFIG_NUMA))
break;
}
switch (cmd)
{
case GET_SLUB_OBJECTS:
si->inuse = total_objects;
break;
case GET_SLUB_SLABS:
si->num_slabs = total_slabs;
break;
}
FREEBUF(nodes);
return TRUE;
bailout:
FREEBUF(nodes);
return FALSE;
}
static void
do_cpu_partial_slub(struct meminfo *si, int cpu)
{
ulong cpu_slab_ptr;
void *partial;
cpu_slab_ptr = ULONG(si->cache_buf + OFFSET(kmem_cache_cpu_slab)) +
kt->__per_cpu_offset[cpu];
readmem(cpu_slab_ptr + OFFSET(kmem_cache_cpu_partial), KVADDR,
&partial, sizeof(void *), "kmem_cache_cpu.partial",
RETURN_ON_ERROR);
fprintf(fp, "CPU %d PARTIAL:\n%s", cpu,
partial ? "" : " (empty)\n");
/*
* kmem_cache_cpu.partial points to the first page of per cpu partial
* list.
*/
while (partial) {
si->slab = (ulong)partial;
if (!is_page_ptr(si->slab, NULL)) {
error(INFO, "%s: invalid partial list slab pointer: %lx\n",
si->curname, si->slab);
break;
}
if (!do_slab_slub(si, VERBOSE))
break;
readmem((ulong)partial + OFFSET(page_next), KVADDR, &partial,
sizeof(void *), "page.next", RETURN_ON_ERROR);
}
}
static void
do_kmem_cache_slub(struct meminfo *si)
{
int i, n;
ulong cpu_slab_ptr, node_ptr;
ulong node_nr_partial, node_nr_slabs;
ulong *per_cpu;
struct node_table *nt;
per_cpu = (ulong *)GETBUF(sizeof(ulong) * vt->numnodes);
for (i = 0; i < kt->cpus; i++) {
if (hide_offline_cpu(i)) {
fprintf(fp, "CPU %d [OFFLINE]\n", i);
continue;
}
cpu_slab_ptr = ULONG(si->cache_buf + OFFSET(kmem_cache_cpu_slab)) +
kt->__per_cpu_offset[i];
fprintf(fp, "CPU %d KMEM_CACHE_CPU:\n %lx\n", i, cpu_slab_ptr);
cpu_slab_ptr = get_cpu_slab_ptr(si, i, NULL);
fprintf(fp, "CPU %d SLAB:\n%s", i,
cpu_slab_ptr ? "" : " (empty)\n");
if (cpu_slab_ptr) {
if ((n = page_to_nid(cpu_slab_ptr)) >= 0)
per_cpu[n]++;
si->slab = cpu_slab_ptr;
if (!do_slab_slub(si, VERBOSE))
continue;
}
if (VALID_MEMBER(kmem_cache_cpu_partial))
do_cpu_partial_slub(si, i);
if (received_SIGINT())
restart(0);
}
for (n = 0; n < vt->numnodes; n++) {
if (vt->flags & CONFIG_NUMA) {
nt = &vt->node_table[n];
node_ptr = ULONG(si->cache_buf +
OFFSET(kmem_cache_node) +
(sizeof(void *)* nt->node_id));
} else
node_ptr = si->cache +
OFFSET(kmem_cache_local_node);
if (node_ptr) {
if (!readmem(node_ptr + OFFSET(kmem_cache_node_nr_partial),
KVADDR, &node_nr_partial, sizeof(ulong),
"kmem_cache_node nr_partial", RETURN_ON_ERROR))
break;
if (!readmem(node_ptr + OFFSET(kmem_cache_node_nr_slabs),
KVADDR, &node_nr_slabs, sizeof(ulong),
"kmem_cache_node nr_slabs", RETURN_ON_ERROR))
break;
} else
node_nr_partial = node_nr_slabs = 0;
fprintf(fp, "KMEM_CACHE_NODE NODE SLABS PARTIAL PER-CPU\n");
fprintf(fp, "%lx%s", node_ptr, space(VADDR_PRLEN > 8 ? 2 : 10));
fprintf(fp, "%4d %5ld %7ld %7ld\n",
n, node_nr_slabs, node_nr_partial, per_cpu[n]);
do_node_lists_slub(si, node_ptr, n);
if (!(vt->flags & CONFIG_NUMA))
break;
}
fprintf(fp, "\n");
FREEBUF(per_cpu);
}
#define DUMP_SLAB_INFO_SLUB() \
{ \
char b1[BUFSIZE], b2[BUFSIZE]; \
fprintf(fp, " %s %s %4d %5d %9d %4d\n", \
mkstring(b1, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(si->slab)), \
mkstring(b2, VADDR_PRLEN, LJUST|LONG_HEX, MKSTR(vaddr)), \
node, objects, inuse, objects - inuse); \
}
static int
do_slab_slub(struct meminfo *si, int verbose)
{
physaddr_t paddr;
ulong vaddr, objects_vaddr;
ushort inuse, objects;
ulong freelist, cpu_freelist, cpu_slab_ptr;
int i, free_objects, cpu_slab, is_free, node;
ulong p, q;
if (!si->slab) {
if (CRASHDEBUG(1))
error(INFO, "-S option not supported for CONFIG_SLUB\n");
return FALSE;
}
if (!page_to_phys(si->slab, &paddr)) {
error(INFO,
"%s: invalid slab address: %lx\n",
si->curname, si->slab);
return FALSE;
}
node = page_to_nid(si->slab);
vaddr = PTOV(paddr);
if (verbose)
fprintf(fp, " %s", slab_hdr);
if (!readmem(si->slab + OFFSET(page_inuse), KVADDR, &inuse,
sizeof(ushort), "page.inuse", RETURN_ON_ERROR))
return FALSE;
if (!readmem(si->slab + OFFSET(page_freelist), KVADDR, &freelist,
sizeof(void *), "page.freelist", RETURN_ON_ERROR))
return FALSE;
/*
* Pre-2.6.27, the object count and order were fixed in the
* kmem_cache structure. Now they may change, say if a high
* order slab allocation fails, so the per-slab object count
* is kept in the slab.
*/
if (VALID_MEMBER(page_objects)) {
objects_vaddr = si->slab + OFFSET(page_objects);
if (si->flags & SLAB_BITFIELD)
objects_vaddr += sizeof(ushort);
if (!readmem(objects_vaddr, KVADDR, &objects,
sizeof(ushort), "page.objects", RETURN_ON_ERROR))
return FALSE;
/*
* Strip page.frozen bit.
*/
if (si->flags & SLAB_BITFIELD) {
if (__BYTE_ORDER == __LITTLE_ENDIAN) {
objects <<= 1;
objects >>= 1;
}
if (__BYTE_ORDER == __BIG_ENDIAN)
objects >>= 1;
}
if (CRASHDEBUG(1) && (objects != si->objects))
error(NOTE, "%s: slab: %lx oo objects: %ld "
"slab objects: %d\n",
si->curname, si->slab,
si->objects, objects);
if (objects == (ushort)(-1)) {
error(INFO, "%s: slab: %lx invalid page.objects: -1\n",
si->curname, si->slab);
return FALSE;
}
} else
objects = (ushort)si->objects;
if (!verbose) {
DUMP_SLAB_INFO_SLUB();
return TRUE;
}
for (i = 0, cpu_slab = -1; i < kt->cpus; i++) {
cpu_slab_ptr = get_cpu_slab_ptr(si, i, &cpu_freelist);
if (!cpu_slab_ptr)
continue;
if (cpu_slab_ptr == si->slab) {
cpu_slab = i;
/*
* Later slub scheme uses the per-cpu freelist
* so count the free objects by hand.
*/
if (cpu_freelist)
freelist = cpu_freelist;
if ((free_objects = count_free_objects(si, freelist)) < 0)
return FALSE;
inuse = si->objects - free_objects;
break;
}
}
DUMP_SLAB_INFO_SLUB();
fprintf(fp, " %s", free_inuse_hdr);
#define PAGE_MAPPING_ANON 1
if (CRASHDEBUG(8)) {
fprintf(fp, "< SLUB: free list START: >\n");
i = 0;
for (q = freelist; q; q = get_freepointer(si, (void *)q)) {
if (q & PAGE_MAPPING_ANON) {
fprintf(fp,
"< SLUB: free list END: %lx (%d found) >\n",
q, i);
break;
}
fprintf(fp, " %lx\n", q);
i++;
}
if (!q)
fprintf(fp, "< SLUB: free list END (%d found) >\n", i);
}
for (p = vaddr; p < vaddr + objects * si->size; p += si->size) {
hq_open();
is_free = FALSE;
for (is_free = 0, q = freelist; q;
q = get_freepointer(si, (void *)q)) {
if (q == BADADDR) {
hq_close();
return FALSE;
}
if (q & PAGE_MAPPING_ANON)
break;
if (p == q) {
is_free = TRUE;
break;
}
if (!hq_enter(q)) {
hq_close();
error(INFO,
"%s: slab: %lx duplicate freelist object: %lx\n",
si->curname, si->slab, q);
return FALSE;
}
}
hq_close();
if (si->flags & ADDRESS_SPECIFIED) {
if ((si->spec_addr < p) ||
(si->spec_addr >= (p + si->size))) {
if (!(si->flags & VERBOSE))
continue;
}
}
fprintf(fp, " %s%lx%s",
is_free ? " " : "[",
p, is_free ? " " : "]");
if (is_free && (cpu_slab >= 0))
fprintf(fp, "(cpu %d cache)", cpu_slab);
fprintf(fp, "\n");
}
return TRUE;
}
static int
count_free_objects(struct meminfo *si, ulong freelist)
{
int c;
ulong q;
hq_open();
c = 0;
for (q = freelist; q; q = get_freepointer(si, (void *)q)) {
if (q & PAGE_MAPPING_ANON)
break;
if (!hq_enter(q)) {
error(INFO, "%s: slab: %lx duplicate freelist object: %lx\n",
si->curname, si->slab, q);
break;
}
c++;
}
hq_close();
return c;
}
static ulong
get_freepointer(struct meminfo *si, void *object)
{
ulong vaddr, nextfree;
vaddr = (ulong)(object + si->slab_offset);
if (!readmem(vaddr, KVADDR, &nextfree,
sizeof(void *), "get_freepointer", QUIET|RETURN_ON_ERROR)) {
error(INFO, "%s: slab: %lx invalid freepointer: %lx\n",
si->curname, si->slab, vaddr);
return BADADDR;
}
return nextfree;
}
static void
do_node_lists_slub(struct meminfo *si, ulong node_ptr, int node)
{
ulong next, last, list_head, flags;
int first;
if (!node_ptr)
return;
list_head = node_ptr + OFFSET(kmem_cache_node_partial);
if (!readmem(list_head, KVADDR, &next, sizeof(ulong),
"kmem_cache_node partial", RETURN_ON_ERROR))
return;
fprintf(fp, "NODE %d PARTIAL:\n%s", node,
next == list_head ? " (empty)\n" : "");
first = 0;
while (next != list_head) {
si->slab = last = next - OFFSET(page_lru);
if (first++ == 0)
fprintf(fp, " %s", slab_hdr);
if (!is_page_ptr(si->slab, NULL)) {
error(INFO,
"%s: invalid partial list slab pointer: %lx\n",
si->curname, si->slab);
return;
}
if (!do_slab_slub(si, !VERBOSE))
return;
if (received_SIGINT())
restart(0);
if (!readmem(next, KVADDR, &next, sizeof(ulong),
"page.lru.next", RETURN_ON_ERROR))
return;
if (!IS_KVADDR(next) ||
((next != list_head) &&
!is_page_ptr(next - OFFSET(page_lru), NULL))) {
error(INFO,
"%s: partial list slab: %lx invalid page.lru.next: %lx\n",
si->curname, last, next);
return;
}
}
#define SLAB_STORE_USER (0x00010000UL)
flags = ULONG(si->cache_buf + OFFSET(kmem_cache_flags));
if (INVALID_MEMBER(kmem_cache_node_full) ||
!(flags & SLAB_STORE_USER)) {
fprintf(fp, "NODE %d FULL:\n (not tracked)\n", node);
return;
}
list_head = node_ptr + OFFSET(kmem_cache_node_full);
if (!readmem(list_head, KVADDR, &next, sizeof(ulong),
"kmem_cache_node full", RETURN_ON_ERROR))
return;
fprintf(fp, "NODE %d FULL:\n%s", node,
next == list_head ? " (empty)\n" : "");
first = 0;
while (next != list_head) {
si->slab = next - OFFSET(page_lru);
if (first++ == 0)
fprintf(fp, " %s", slab_hdr);
if (!is_page_ptr(si->slab, NULL)) {
error(INFO, "%s: invalid full list slab pointer: %lx\n",
si->curname, si->slab);
return;
}
if (!do_slab_slub(si, !VERBOSE))
return;
if (received_SIGINT())
restart(0);
if (!readmem(next, KVADDR, &next, sizeof(ulong),
"page.lru.next", RETURN_ON_ERROR))
return;
if (!IS_KVADDR(next)) {
error(INFO, "%s: full list slab: %lx page.lru.next: %lx\n",
si->curname, si->slab, next);
return;
}
}
}
static char *
is_kmem_cache_addr_common(ulong vaddr, char *kbuf)
{
int i, cnt;
ulong *cache_list;
ulong name;
int found;
cnt = get_kmem_cache_list(&cache_list);
for (i = 0, found = FALSE; i < cnt; i++) {
if (cache_list[i] != vaddr)
continue;
if (!readmem(cache_list[i] + OFFSET(kmem_cache_name),
KVADDR, &name, sizeof(char *),
"kmem_cache.name", RETURN_ON_ERROR))
break;
if (!read_string(name, kbuf, BUFSIZE-1))
sprintf(kbuf, "(unknown)");
found = TRUE;
break;
}
FREEBUF(cache_list);
return (found ? kbuf : NULL);
}
/*
* Kernel-config-neutral page-to-node evaluator.
*/
static int
page_to_nid(ulong page)
{
int i;
physaddr_t paddr;
struct node_table *nt;
physaddr_t end_paddr;
if (!page_to_phys(page, &paddr)) {
error(INFO, "page_to_nid: invalid page: %lx\n", page);
return -1;
}
for (i = 0; i < vt->numnodes; i++) {
nt = &vt->node_table[i];
end_paddr = nt->start_paddr +
((physaddr_t)nt->size * (physaddr_t)PAGESIZE());
if ((paddr >= nt->start_paddr) && (paddr < end_paddr))
return i;
}
error(INFO, "page_to_nid: cannot determine node for pages: %lx\n",
page);
return -1;
}
/*
* Allocate and fill the passed-in buffer with a list of
* the current kmem_cache structures.
*/
static int
get_kmem_cache_list(ulong **cache_buf)
{
int cnt;
ulong vaddr;
struct list_data list_data, *ld;
get_symbol_data("slab_caches", sizeof(void *), &vaddr);
ld = &list_data;
BZERO(ld, sizeof(struct list_data));
ld->flags |= LIST_ALLOCATE;
ld->start = vaddr;
ld->list_head_offset = OFFSET(kmem_cache_list);
ld->end = symbol_value("slab_caches");
if (CRASHDEBUG(3))
ld->flags |= VERBOSE;
cnt = do_list(ld);
*cache_buf = ld->list_ptr;
return cnt;
}
/*
* Get the address of the head page of a compound page.
*/
static ulong
compound_head(ulong page)
{
ulong flags, first_page;;
first_page = page;
if (!readmem(page+OFFSET(page_flags), KVADDR, &flags, sizeof(ulong),
"page.flags", RETURN_ON_ERROR))
return first_page;
if ((flags & vt->PG_head_tail_mask) == vt->PG_head_tail_mask)
readmem(page+OFFSET(page_first_page), KVADDR, &first_page,
sizeof(ulong), "page.first_page", RETURN_ON_ERROR);
return first_page;
}
long
count_partial(ulong node, struct meminfo *si)
{
ulong list_head, next, last;
short inuse;
ulong total_inuse;
ulong count = 0;
count = 0;
total_inuse = 0;
list_head = node + OFFSET(kmem_cache_node_partial);
if (!readmem(list_head, KVADDR, &next, sizeof(ulong),
"kmem_cache_node.partial", RETURN_ON_ERROR))
return -1;
hq_open();
while (next != list_head) {
if (!readmem(next - OFFSET(page_lru) + OFFSET(page_inuse),
KVADDR, &inuse, sizeof(ushort), "page.inuse", RETURN_ON_ERROR)) {
hq_close();
return -1;
}
last = next - OFFSET(page_lru);
if (inuse == -1) {
error(INFO,
"%s: partial list slab: %lx invalid page.inuse: -1\n",
si->curname, last);
break;
}
total_inuse += inuse;
if (!readmem(next, KVADDR, &next, sizeof(ulong),
"page.lru.next", RETURN_ON_ERROR)) {
hq_close();
return -1;
}
if (!IS_KVADDR(next) ||
((next != list_head) &&
!is_page_ptr(next - OFFSET(page_lru), NULL))) {
error(INFO, "%s: partial list slab: %lx invalid page.lru.next: %lx\n",
si->curname, last, next);
break;
}
/*
* Keep track of the last 1000 entries, and check
* whether the list has recursed back onto itself.
*/
if ((++count % 1000) == 0) {
hq_close();
hq_open();
}
if (!hq_enter(next)) {
error(INFO,
"%s: partial list slab: %lx duplicate slab entry: %lx\n",
si->curname, last, next);
hq_close();
return -1;
}
}
hq_close();
return total_inuse;
}
char *
is_slab_page(struct meminfo *si, char *buf)
{
int i, cnt;
ulong page_slab, page_flags, name;
ulong *cache_list;
char *retval;
if (!(vt->flags & KMALLOC_SLUB))
return NULL;
if (!is_page_ptr((ulong)si->spec_addr, NULL))
return NULL;
if (!readmem(si->spec_addr + OFFSET(page_flags), KVADDR,
&page_flags, sizeof(ulong), "page.flags",
RETURN_ON_ERROR|QUIET))
return NULL;
if (!(page_flags & (1 << vt->PG_slab)))
return NULL;
if (!readmem(si->spec_addr + OFFSET(page_slab), KVADDR,
&page_slab, sizeof(ulong), "page.slab",
RETURN_ON_ERROR|QUIET))
return NULL;
retval = NULL;
cnt = get_kmem_cache_list(&cache_list);
for (i = 0; i < cnt; i++) {
if (page_slab == cache_list[i]) {
if (!readmem(cache_list[i] + OFFSET(kmem_cache_name),
KVADDR, &name, sizeof(char *),
"kmem_cache.name", QUIET|RETURN_ON_ERROR))
goto bailout;
if (!read_string(name, buf, BUFSIZE-1))
goto bailout;
retval = buf;
break;
}
}
bailout:
FREEBUF(cache_list);
return retval;
}
/*
* Figure out which of the kmem_cache.cpu_slab declarations
* is used by this kernel, and return a pointer to the slab
* page being used. Return the kmem_cache_cpu.freelist pointer
* if requested.
*/
static ulong
get_cpu_slab_ptr(struct meminfo *si, int cpu, ulong *cpu_freelist)
{
ulong cpu_slab_ptr, page, freelist;
if (cpu_freelist)
*cpu_freelist = 0;
switch (vt->cpu_slab_type)
{
case TYPE_CODE_STRUCT:
cpu_slab_ptr = ULONG(si->cache_buf +
OFFSET(kmem_cache_cpu_slab) +
OFFSET(kmem_cache_cpu_page));
if (cpu_freelist && VALID_MEMBER(kmem_cache_cpu_freelist))
*cpu_freelist = ULONG(si->cache_buf +
OFFSET(kmem_cache_cpu_slab) +
OFFSET(kmem_cache_cpu_freelist));
break;
case TYPE_CODE_ARRAY:
cpu_slab_ptr = ULONG(si->cache_buf +
OFFSET(kmem_cache_cpu_slab) + (sizeof(void *)*cpu));
if (cpu_slab_ptr && cpu_freelist &&
VALID_MEMBER(kmem_cache_cpu_freelist)) {
if (readmem(cpu_slab_ptr + OFFSET(kmem_cache_cpu_freelist),
KVADDR, &freelist, sizeof(void *),
"kmem_cache_cpu.freelist", RETURN_ON_ERROR))
*cpu_freelist = freelist;
}
if (cpu_slab_ptr && VALID_MEMBER(kmem_cache_cpu_page)) {
if (!readmem(cpu_slab_ptr + OFFSET(kmem_cache_cpu_page),
KVADDR, &page, sizeof(void *),
"kmem_cache_cpu.page", RETURN_ON_ERROR))
cpu_slab_ptr = 0;
else
cpu_slab_ptr = page;
}
break;
case TYPE_CODE_PTR:
cpu_slab_ptr = ULONG(si->cache_buf + OFFSET(kmem_cache_cpu_slab)) +
kt->__per_cpu_offset[cpu];
if (cpu_slab_ptr && cpu_freelist &&
VALID_MEMBER(kmem_cache_cpu_freelist)) {
if (readmem(cpu_slab_ptr + OFFSET(kmem_cache_cpu_freelist),
KVADDR, &freelist, sizeof(void *),
"kmem_cache_cpu.freelist", RETURN_ON_ERROR))
*cpu_freelist = freelist;
}
if (cpu_slab_ptr && VALID_MEMBER(kmem_cache_cpu_page)) {
if (!readmem(cpu_slab_ptr + OFFSET(kmem_cache_cpu_page),
KVADDR, &page, sizeof(void *),
"kmem_cache_cpu.page", RETURN_ON_ERROR))
cpu_slab_ptr = 0;
else
cpu_slab_ptr = page;
}
break;
default:
cpu_slab_ptr = 0;
error(FATAL, "cannot determine location of kmem_cache.cpu_slab page\n");
}
return cpu_slab_ptr;
}
/*
* In 2.6.27 kmem_cache.order and kmem_cache.objects were merged
* into the kmem_cache.oo, a kmem_cache_order_objects structure.
* oo_order() and oo_objects() emulate the kernel functions
* of the same name.
*/
static unsigned int oo_order(ulong oo)
{
return (oo >> 16);
}
static unsigned int oo_objects(ulong oo)
{
return (oo & ((1 << 16) - 1));
}
#ifdef NOT_USED
ulong
slab_to_kmem_cache_node(struct meminfo *si, ulong slab_page)
{
int node;
ulong node_ptr;
if (vt->flags & CONFIG_NUMA) {
node = page_to_nid(slab_page);
node_ptr = ULONG(si->cache_buf +
OFFSET(kmem_cache_node) +
(sizeof(void *)*node));
} else
node_ptr = si->cache + OFFSET(kmem_cache_local_node);
return node_ptr;
}
ulong
get_kmem_cache_by_name(char *request)
{
int i, cnt;
ulong *cache_list;
ulong name;
char buf[BUFSIZE];
ulong found;
cnt = get_kmem_cache_list(&cache_list);
cache_buf = GETBUF(SIZE(kmem_cache));
found = 0;
for (i = 0; i < cnt; i++) {
readmem(cache_list[i] + OFFSET(kmem_cache_name),
KVADDR, &name, sizeof(char *),
"kmem_cache.name", FAULT_ON_ERROR);
if (!read_string(name, buf, BUFSIZE-1))
continue;
if (STREQ(buf, request)) {
found = cache_list[i];
break;
}
}
FREEBUF(cache_list);
return found;
}
#endif /* NOT_USED */