mirror of
https://github.com/crash-utility/crash
synced 2025-02-01 22:02:36 +00:00
6871d539a8
each hugepage hstate array entry, its hugepage size, its free and total counts, and name string. (anderson@redhat.com)
18028 lines
489 KiB
C
18028 lines
489 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 void 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 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_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");
|
|
|
|
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");
|
|
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();
|
|
|
|
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)
|
|
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):
|
|
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));
|
|
/* FALLTHROUGH */
|
|
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,
|
|
"\nthis kernel may be configured with CONFIG_STRICT_DEVMEM,"
|
|
" which\n renders /dev/mem unusable as a live memory "
|
|
"source.\n\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\n",
|
|
mkstring(buf2, len, LJUST, buf1),
|
|
vaddr);
|
|
}
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* 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;
|
|
|
|
if (!enumerator_value("MM_FILEPAGES", &filepages) ||
|
|
!enumerator_value("MM_ANONPAGES", &anonpages)) {
|
|
filepages = 0;
|
|
anonpages = 1;
|
|
}
|
|
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 i, sync_rss;
|
|
ulong tgid;
|
|
struct task_context *tc1;
|
|
|
|
tgid = task_tgid(task);
|
|
|
|
tc1 = FIRST_CONTEXT();
|
|
for (i = 0; i < RUNNING_TASKS(); i++, tc1++) {
|
|
if (task_tgid(tc1->task) != tgid)
|
|
continue;
|
|
|
|
/* count 0 -> filepages */
|
|
if (!readmem(tc1->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(tc1->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;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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) && cpudata[i]; i++) {
|
|
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 (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 (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);
|
|
}
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* 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\n", buf, kt->__per_cpu_offset[c]);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* 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_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++) {
|
|
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)
|
|
continue;
|
|
|
|
if ((n = page_to_nid(cpu_slab_ptr)) >= 0)
|
|
per_cpu[n]++;
|
|
|
|
si->slab = cpu_slab_ptr;
|
|
do_slab_slub(si, VERBOSE);
|
|
|
|
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 void
|
|
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, 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;
|
|
}
|
|
|
|
if (!page_to_phys(si->slab, &paddr)) {
|
|
error(WARNING,
|
|
"%lx: cannot tranlate slab page to physical address\n",
|
|
si->slab);
|
|
return;
|
|
}
|
|
|
|
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;
|
|
if (!readmem(si->slab + OFFSET(page_freelist), KVADDR, &freelist,
|
|
sizeof(void *), "page.freelist", RETURN_ON_ERROR))
|
|
return;
|
|
/*
|
|
* 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;
|
|
/*
|
|
* 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);
|
|
} else
|
|
objects = (ushort)si->objects;
|
|
|
|
if (!verbose) {
|
|
DUMP_SLAB_INFO_SLUB();
|
|
return;
|
|
}
|
|
|
|
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;
|
|
inuse = si->objects - count_free_objects(si, freelist);
|
|
break;
|
|
}
|
|
}
|
|
|
|
DUMP_SLAB_INFO_SLUB();
|
|
|
|
fprintf(fp, " %s", free_inuse_hdr);
|
|
|
|
#define PAGE_MAPPING_ANON 1
|
|
|
|
if (CRASHDEBUG(1)) {
|
|
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) {
|
|
is_free = FALSE;
|
|
for (is_free = 0, q = freelist; q;
|
|
q = get_freepointer(si, (void *)q)) {
|
|
if (q == BADADDR)
|
|
return;
|
|
if (q & PAGE_MAPPING_ANON)
|
|
break;
|
|
if (p == q) {
|
|
is_free = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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");
|
|
|
|
}
|
|
}
|
|
|
|
static int
|
|
count_free_objects(struct meminfo *si, ulong freelist)
|
|
{
|
|
int c;
|
|
ulong q;
|
|
|
|
c = 0;
|
|
for (q = freelist; q; q = get_freepointer(si, (void *)q)) {
|
|
if (q & PAGE_MAPPING_ANON)
|
|
break;
|
|
c++;
|
|
}
|
|
|
|
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", RETURN_ON_ERROR))
|
|
return BADADDR;
|
|
|
|
return nextfree;
|
|
}
|
|
|
|
static void
|
|
do_node_lists_slub(struct meminfo *si, ulong node_ptr, int node)
|
|
{
|
|
ulong next, 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 = next - OFFSET(page_lru);
|
|
if (first++ == 0)
|
|
fprintf(fp, " %s", slab_hdr);
|
|
do_slab_slub(si, !VERBOSE);
|
|
|
|
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: partial list: page.lru.next: %lx\n",
|
|
si->curname, 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);
|
|
do_slab_slub(si, !VERBOSE);
|
|
|
|
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: page.lru.next: %lx\n",
|
|
si->curname, 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;
|
|
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;
|
|
}
|
|
if (inuse == -1) {
|
|
error(INFO, "%s: partial list: page.inuse: -1\n",
|
|
si->curname);
|
|
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)) {
|
|
error(INFO, "%s: partial list: page.lru.next: %lx\n",
|
|
si->curname, 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: list corrupt: recurses back onto itself\n",
|
|
si->curname);
|
|
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 */
|