crash/s390dbf.c
Dave Anderson aad69086a9 Fix the s390dbf time stamps for S390X kernel versions 4.11 and 4.14.
With kernel commit ea417aa8a38bc7db ("s390/debug: make debug event
time stamps relative to the boot TOD clock") for s390dbf time is
stored relative to the kernel boot time.  In order to still show
absolute time since 1970 we have to detect those kernels and re-add
the boot time before printing the records.  We can use the
tod_to_timeval() symbol to check for those kernels because the
patch has removed the symbol.  With kernel commit 6e2ef5e4f6cc5734
("s390/time: add support for the TOD clock epoch extension")
the symbol name for storing the boot time has changed from
"sched_clock_base_cc" to "tod_clock_base".  This commit is currently
on the s390 features branch and will be integrated in Linux 4.14.
(holzheu@linux.vnet.ibm.com)
2017-08-08 14:41:01 -04:00

1496 lines
35 KiB
C

/*
* s390 debug feature command for crash
*
* Copyright (C) IBM Corp. 2006
* Author(s): Michael Holzheu <holzheu@de.ibm.com>
*
* 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.
*/
#if defined(S390) || defined(S390X)
#include "defs.h"
#include <iconv.h>
#include <ctype.h>
/*
* Compat layer to integrate lcrash commands into crash
* Maps lcrash API to crash functions
*/
#define KL_NBPW sizeof(long)
#define KL_ERRORFP stderr
#define MAX_ARGS 128
#define MAX_CMDLINE 256
#define C_FALSE 0x00000001 /* Command takes no arguments */
#define C_TRUE 0x00000002 /* Command requires arguments */
#define C_ALL 0x00000004 /* All elements */
#define C_PERM 0x00000008 /* Allocate perminant blocks */
#define C_TEMP 0x00000000 /* For completeness */
#define C_FULL 0x00000010 /* Full output */
#define C_LIST 0x00000020 /* List items */
#define C_NEXT 0x00000040 /* Follow links */
#define C_WRITE 0x00000080 /* Write output to file */
#define C_NO_OPCHECK 0x00000100 /* Don't reject bad cmd line options */
#define C_ITER 0x00000200 /* set iteration threshold */
#define C_LFLG_SHFT 12
#define KL_ARCH_S390 0
#define KL_ARCH_S390X 1
#ifdef __s390x__
#define KL_ARCH KL_ARCH_S390X
#define FMTPTR "l"
#define KL_PTRSZ 8
#else
#define KL_ARCH KL_ARCH_S390
#define FMTPTR "ll"
#define KL_PTRSZ 4
#endif
/* Start TOD time of kernel in usecs for relative time stamps */
static uint64_t tod_clock_base_us;
typedef unsigned long uaddr_t;
typedef unsigned long kaddr_t;
typedef struct _syment {
char *s_name;
kaddr_t s_addr;
} syment_t;
typedef struct option_s {
struct option_s *op_next;
char op_char;
char *op_arg;
} option_t;
typedef struct command_s {
int flags;
char cmdstr[MAX_CMDLINE];
char *command;
char *cmdline;
option_t *options;
int nargs;
char *args[MAX_ARGS];
char *pipe_cmd;
FILE *ofp;
FILE *efp;
} command_t;
static inline syment_t* kl_lkup_symaddr(kaddr_t addr)
{
static syment_t sym;
struct syment *crash_sym;
crash_sym = value_search(addr, &sym.s_addr);
if (!crash_sym)
return NULL;
sym.s_name = crash_sym->name;
return &sym;
}
static inline syment_t* kl_lkup_symname(char* name)
{
static syment_t sym;
sym.s_addr = symbol_value(name);
sym.s_name = NULL;
if(!sym.s_addr)
return NULL;
else
return &sym;
}
static inline void GET_BLOCK(kaddr_t addr, int size, void* ptr)
{
readmem(addr, KVADDR,ptr,size,"GET_BLOCK",FAULT_ON_ERROR);
}
static inline kaddr_t KL_VREAD_PTR(kaddr_t addr)
{
unsigned long ptr;
readmem(addr, KVADDR,&ptr,sizeof(ptr),"GET_BLOCK",FAULT_ON_ERROR);
return (kaddr_t)ptr;
}
static inline uint32_t KL_GET_UINT32(void* ptr)
{
return *((uint32_t*)ptr);
}
static inline uint64_t KL_GET_UINT64(void* ptr)
{
return *((uint64_t*)ptr);
}
static inline kaddr_t KL_GET_PTR(void* ptr)
{
return *((kaddr_t*)ptr);
}
static inline void* K_PTR(void* addr, char* struct_name, char* member_name)
{
return addr+MEMBER_OFFSET(struct_name,member_name);
}
static inline unsigned long KL_ULONG(void* ptr, char* struct_name, char*
member_name)
{
return ULONG(ptr+MEMBER_OFFSET(struct_name,member_name));
}
static inline uint32_t KL_VREAD_UINT32(kaddr_t addr)
{
uint32_t rc;
readmem(addr, KVADDR,&rc,sizeof(rc),"KL_VREAD_UINT32",FAULT_ON_ERROR);
return rc;
}
static inline uint32_t KL_INT(void* ptr, char* struct_name, char* member_name)
{
return UINT(ptr+MEMBER_OFFSET(struct_name,member_name));
}
static inline int set_cmd_flags(command_t *cmd, int flags, char *extraops)
{
return 0;
}
/* Time of day clock value for 1970/01/01 */
#define TOD_UNIX_EPOCH (0x8126d60e46000000LL - (0x3c26700LL * 1000000 * 4096))
/* Time of day clock value for 1970/01/01 in usecs */
#define TOD_UNIX_EPOCH_US (TOD_UNIX_EPOCH >> 12)
static inline void kl_s390tod_to_timeval(uint64_t todval, struct timeval *xtime)
{
uint64_t todval_us;
/* Convert TOD to usec (51th bit of TOD is us) */
todval_us = todval >> 12;
/* Add base if we have relative time stamps */
todval_us += tod_clock_base_us;
/* Subtract EPOCH that we get time in usec since 1970 */
todval_us -= TOD_UNIX_EPOCH_US;
xtime->tv_sec = todval_us / 1000000;
xtime->tv_usec = todval_us % 1000000;
}
static inline int kl_struct_len(char* struct_name)
{
return STRUCT_SIZE(struct_name);
}
static inline kaddr_t kl_funcaddr(kaddr_t addr)
{
struct syment *crash_sym;
crash_sym = value_search(addr, &addr);
if (!crash_sym)
return -1;
else
return crash_sym->value;
}
#define CMD_USAGE(cmd, s) \
fprintf(cmd->ofp, "Usage: %s %s\n", cmd->command, s); \
fprintf(cmd->ofp, "Enter \"help %s\" for details.\n",cmd->command);
/*
* s390 debug feature implementation
*/
#ifdef DBF_DYNAMIC_VIEWS /* views defined in shared libs */
#include <dlfcn.h>
#endif
/* Local flags
*/
#define LOAD_FLAG (1 << C_LFLG_SHFT)
#define VIEWS_FLAG (2 << C_LFLG_SHFT)
#define SAVE_DBF_FLAG (4 << C_LFLG_SHFT)
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#endif
/* Stuff which has to match with include/asm-s390/debug.h */
#define DBF_VERSION_V1 1
#define DBF_VERSION_V2 2
#define PAGE_SIZE 4096
#define DEBUG_MAX_VIEWS 10 /* max number of views in proc fs */
#define DEBUG_MAX_PROCF_LEN 64 /* max length for a proc file name */
#define DEBUG_SPRINTF_MAX_ARGS 10
/* define debug-structures for lcrash */
#define DEBUG_DATA(entry) (char*)(entry + 1)
typedef struct debug_view_s debug_view_t;
/* struct to hold contents of struct __debug_entry from dump
*/
typedef struct debug_entry_s{
union {
struct {
unsigned long long clock:52;
unsigned long long exception:1;
unsigned long long level:3;
unsigned long long cpuid:8;
} fields;
unsigned long long stck;
} id;
kaddr_t caller; /* changed from void* to kaddr_t */
} __attribute__((packed)) debug_entry_t;
/* typedef struct __debug_entry debug_entry_t; */
static unsigned int dbf_version;
/* struct is used to manage contents of structs debug_info from dump
* in lcrash
*/
typedef struct debug_info_s {
struct debug_info_s *next;
struct debug_info_s *prev;
kaddr_t next_dbi; /* store next ptr of struct in dump */
kaddr_t prev_dbi; /* store prev ptr of struct in dump */
int level;
int nr_areas;
int page_order;
int buf_size;
int entry_size;
void **areas; /* contents of debug areas from dump */
int active_area;
int *active_entry; /* change to uint32_t ? */
debug_view_t *views[DEBUG_MAX_VIEWS];
char name[DEBUG_MAX_PROCF_LEN];
kaddr_t addr;
int pages_per_area_v2;
void ***areas_v2;
} debug_info_t;
/* functions to generate dbf output
*/
typedef int (debug_header_proc_t) (debug_info_t* id, debug_view_t* view,
int area, debug_entry_t* entry,
char* out_buf);
typedef int (debug_format_proc_t) (debug_info_t* id, debug_view_t* view,
char* out_buf, const char* in_buf);
typedef int (debug_prolog_proc_t) (debug_info_t* id, debug_view_t* view,
char* out_buf);
struct debug_view_s {
char name[DEBUG_MAX_PROCF_LEN];
debug_prolog_proc_t* prolog_proc;
debug_header_proc_t* header_proc;
debug_format_proc_t* format_proc;
void* private_data;
};
#define LCRASH_DB_VIEWS 1000
static debug_info_t *debug_area_first = NULL;
static debug_info_t *debug_area_last = NULL;
static debug_view_t *debug_views[LCRASH_DB_VIEWS];
static int initialized = 0;
static iconv_t ebcdic_ascii_conv = 0;
void s390dbf_usage(command_t * cmd);
static int add_lcrash_debug_view(debug_view_t *);
static int dbe_size = 0;
static void
EBCASC(char *inout, size_t len)
{
iconv(ebcdic_ascii_conv, &inout, &len, &inout, &len);
}
/*
* prints header for debug entry
*/
static int
dflt_header_fn(debug_info_t * id, debug_view_t *view,
int area, debug_entry_t * entry, char *out_buf)
{
struct timeval time_val;
unsigned long long time;
char *except_str;
kaddr_t caller;
int rc = 0;
char *caller_name;
int offset;
char caller_buf[30];
unsigned int level;
syment_t *caller_sym;
debug_entry_t lentry; /* store byte swapped values of entry */
lentry.id.stck = KL_GET_UINT64(&entry->id);
lentry.caller = KL_GET_PTR(&entry->caller);
level = lentry.id.fields.level;
time = lentry.id.stck;
kl_s390tod_to_timeval(time, &time_val);
if (lentry.id.fields.exception)
except_str = "*";
else
except_str = "-";
caller = lentry.caller;
if(KL_ARCH == KL_ARCH_S390){
caller &= 0x7fffffff;
}
caller_sym = kl_lkup_symaddr(caller);
if(caller_sym){
caller_name = caller_sym->s_name;
offset = caller - kl_funcaddr(caller);
}
else {
sprintf(caller_buf, "%llx", (unsigned long long)caller);
caller_name = caller_buf;
offset = 0;
}
if(KL_ARCH == KL_ARCH_S390X){
rc += sprintf(out_buf,
"%02i %011lu:%06lu %1u %1s %02i <%20s+%04i> ",
area, time_val.tv_sec, time_val.tv_usec, level,
except_str, entry->id.fields.cpuid, caller_name,
offset);
} else {
rc += sprintf(out_buf,
"%02i %011lu:%06lu %1u %1s %02i <%-20s+%04i> ",
area, time_val.tv_sec, time_val.tv_usec, level,
except_str, lentry.id.fields.cpuid, caller_name,
offset);
}
return rc;
}
/*
* prints debug header in raw format
*/
static int
raw_header_fn(debug_info_t * id, debug_view_t *view,
int area, debug_entry_t * entry, char *out_buf)
{
int rc;
rc = sizeof(debug_entry_t);
if (out_buf == NULL)
goto out;
memcpy(out_buf,entry,sizeof(debug_entry_t));
out:
return rc;
}
/*
* prints debug data in raw format
*/
static int
raw_format_fn(debug_info_t * id, debug_view_t *view,
char *out_buf, const char *in_buf)
{
int rc;
rc = id->buf_size;
if (out_buf == NULL || in_buf == NULL)
goto out;
memcpy(out_buf, in_buf, id->buf_size);
out:
return rc;
}
/*
* prints debug data in hex/ascii format
*/
static int
hex_ascii_format_fn(debug_info_t * id, debug_view_t *view,
char *out_buf, const char *in_buf)
{
int i, rc = 0;
if (out_buf == NULL || in_buf == NULL) {
rc = id->buf_size * 4 + 3;
goto out;
}
for (i = 0; i < id->buf_size; i++) {
rc += sprintf(out_buf + rc, "%02x ",
((unsigned char *) in_buf)[i]);
}
rc += sprintf(out_buf + rc, "| ");
for (i = 0; i < id->buf_size; i++) {
unsigned char c = in_buf[i];
if (isascii(c) && isprint(c))
rc += sprintf(out_buf + rc, "%c", c);
else
rc += sprintf(out_buf + rc, ".");
}
rc += sprintf(out_buf + rc, "\n");
out:
return rc;
}
/*
* prints debug data in sprintf format
*/
static int
sprintf_format_fn(debug_info_t * id, debug_view_t *view,
char *out_buf, const char *in_buf)
{
#define _BUFSIZE 1024
char buf[_BUFSIZE];
int i, k, rc = 0, num_longs = 0, num_strings = 0;
int num_used_args ATTRIBUTE_UNUSED;
/* use kaddr_t to store long values of 32bit and 64bit archs here */
kaddr_t inbuf_cpy[DEBUG_SPRINTF_MAX_ARGS];
/* store ptrs to strings to be deallocated at end of this function */
uaddr_t to_dealloc[DEBUG_SPRINTF_MAX_ARGS];
kaddr_t addr;
memset(buf, 0, sizeof(buf));
memset(inbuf_cpy, 0, sizeof(inbuf_cpy));
memset(to_dealloc, 0, sizeof(to_dealloc));
if (out_buf == NULL || in_buf == NULL) {
rc = id->buf_size * 4 + 3;
goto out;
}
/* get the format string into buf */
addr = KL_GET_PTR((void*)in_buf);
GET_BLOCK(addr, _BUFSIZE, buf);
k = 0;
for (i = 0; buf[i] && (buf[i] != '\n'); i++) {
if (buf[i] != '%')
continue;
if (k == DEBUG_SPRINTF_MAX_ARGS) {
fprintf(KL_ERRORFP,
"\nToo much parameters in sprinf view (%i)\n"
,k + 1);
fprintf(KL_ERRORFP, "Format String: %s)\n", buf);
break;
}
/* for sprintf we have only unsigned long values ... */
if (buf[i+1] != 's'){
/* we use KL_GET_PTR here to read ulong value */
addr = KL_GET_PTR((void*) in_buf + ((k + 1)* KL_NBPW));
inbuf_cpy[k] = addr;
} else { /* ... or ptrs to strings in debug areas */
inbuf_cpy[k] = (uaddr_t) malloc(_BUFSIZE);
to_dealloc[num_strings++] = inbuf_cpy[k];
addr = KL_GET_PTR((void*) in_buf + ((k + 1)* KL_NBPW));
GET_BLOCK(addr, _BUFSIZE,
(void*)(uaddr_t)(inbuf_cpy[k]));
}
k++;
}
/* count of longs fit into one entry */
num_longs = id->buf_size / KL_NBPW; /* sizeof(long); */
if(num_longs < 1) /* bufsize of entry too small */
goto out;
if(num_longs == 1) { /* no args, just print the format string */
rc = sprintf(out_buf + rc, "%s", buf);
goto out;
}
/* number of arguments used for sprintf (without the format string) */
num_used_args = MIN(DEBUG_SPRINTF_MAX_ARGS, (num_longs - 1));
rc = sprintf(out_buf + rc, buf, (uaddr_t)(inbuf_cpy[0]),
(uaddr_t)(inbuf_cpy[1]), (uaddr_t)(inbuf_cpy[2]),
(uaddr_t)(inbuf_cpy[3]), (uaddr_t)(inbuf_cpy[4]),
(uaddr_t)(inbuf_cpy[5]), (uaddr_t)(inbuf_cpy[6]),
(uaddr_t)(inbuf_cpy[7]), (uaddr_t)(inbuf_cpy[8]),
(uaddr_t)(inbuf_cpy[9]));
out:
while (num_strings--){
free((char*)(to_dealloc[num_strings]));
}
return rc;
}
/***********************************
* functions for debug-views
***********************************/
/*
* prints out actual debug level
*/
static int
prolog_level_fn(debug_info_t * id,
debug_view_t *view, char *out_buf)
{
int rc = 0;
if (out_buf == NULL) {
rc = 2;
goto out;
}
rc = sprintf(out_buf, "%i\n", id->level);
out:
return rc;
}
/*
* prints out actual pages_per_area
*/
static int
prolog_pages_fn(debug_info_t * id,
debug_view_t *view, char *out_buf)
{
int rc = 0;
if (out_buf == NULL) {
rc = 2;
goto out;
}
rc = sprintf(out_buf, "%i\n", id->pages_per_area_v2);
out:
return rc;
}
/*
* prints out prolog
*/
static int
prolog_fn(debug_info_t * id,
debug_view_t *view, char *out_buf)
{
int rc = 0;
rc = sprintf(out_buf, "AREA TIME LEVEL EXCEPTION CP CALLING FUNCTION"
" + OFFSET DATA\n==================================="
"=======================================\n");
return rc;
}
/*
* prints debug data in hex format
*/
static int
hex_format_fn(debug_info_t * id, debug_view_t *view,
char *out_buf, const char *in_buf)
{
int i, rc = 0;
for (i = 0; i < id->buf_size; i++) {
rc += sprintf(out_buf + rc, "%02x ",
((unsigned char *) in_buf)[i]);
}
rc += sprintf(out_buf + rc, "\n");
return rc;
}
/*
* prints debug data in ascii format
*/
static int
ascii_format_fn(debug_info_t * id, debug_view_t *view,
char *out_buf, const char *in_buf)
{
int i, rc = 0;
if (out_buf == NULL || in_buf == NULL) {
rc = id->buf_size + 1;
goto out;
}
for (i = 0; i < id->buf_size; i++) {
unsigned char c = in_buf[i];
if (!isprint(c))
rc += sprintf(out_buf + rc, ".");
else
rc += sprintf(out_buf + rc, "%c", c);
}
rc += sprintf(out_buf + rc, "\n");
out:
return rc;
}
/*
* prints debug data in ebcdic format
*/
static int
ebcdic_format_fn(debug_info_t * id, debug_view_t *view,
char *out_buf, const char *in_buf)
{
int i, rc = 0;
if (out_buf == NULL || in_buf == NULL) {
rc = id->buf_size + 1;
goto out;
}
for (i = 0; i < id->buf_size; i++) {
char c = in_buf[i];
EBCASC(&c, 1);
if (!isprint(c))
rc += sprintf(out_buf + rc, ".");
else
rc += sprintf(out_buf + rc, "%c", c);
}
rc += sprintf(out_buf + rc, "\n");
out:
return rc;
}
debug_view_t ascii_view = {
"ascii",
&prolog_fn,
&dflt_header_fn,
&ascii_format_fn,
};
debug_view_t ebcdic_view = {
"ebcdic",
&prolog_fn,
&dflt_header_fn,
&ebcdic_format_fn,
};
debug_view_t hex_view = {
"hex",
&prolog_fn,
&dflt_header_fn,
&hex_format_fn,
};
debug_view_t level_view = {
"level",
&prolog_level_fn,
NULL,
NULL,
};
debug_view_t pages_view = {
"pages",
&prolog_pages_fn,
NULL,
NULL,
};
debug_view_t raw_view = {
"raw",
NULL,
&raw_header_fn,
&raw_format_fn,
};
debug_view_t hex_ascii_view = {
"hex_ascii",
&prolog_fn,
&dflt_header_fn,
&hex_ascii_format_fn,
};
debug_view_t sprintf_view = {
"sprintf",
&prolog_fn,
&dflt_header_fn,
&sprintf_format_fn,
};
static debug_entry_t *
debug_find_oldest_entry(debug_entry_t *entries, int num, int entry_size)
{
debug_entry_t *result, *current;
int i;
uint64_t clock1, clock2;
result = entries;
current = entries;
for (i=0; i < num; i++) {
if (current->id.stck == 0)
break;
clock1 = current->id.fields.clock;
clock2 = result->id.fields.clock;
clock1 = KL_GET_UINT64(&clock1);
clock2 = KL_GET_UINT64(&clock2);
if (clock1 < clock2)
result = current;
current = (debug_entry_t *) ((char *) current + entry_size);
}
return result;
}
/*
* debug_format_output:
* - calls prolog, header and format functions of view to format output
*/
static int
debug_format_output_v1(debug_info_t * debug_area, debug_view_t *view,
FILE * ofp)
{
int i, j, len;
int nr_of_entries;
debug_entry_t *act_entry, *last_entry;
char *act_entry_data;
char buf[2048];
size_t items ATTRIBUTE_UNUSED;
/* print prolog */
if (view->prolog_proc) {
len = view->prolog_proc(debug_area, view, buf);
items = fwrite(buf,len, 1, ofp);
memset(buf, 0, 2048);
}
/* print debug records */
if (!(view->format_proc) && !(view->header_proc))
goto out;
if(debug_area->entry_size <= 0){
fprintf(ofp, "Invalid entry_size: %i\n",debug_area->entry_size);
goto out;
}
nr_of_entries = (PAGE_SIZE << debug_area->page_order) / debug_area->entry_size;
for (i = 0; i < debug_area->nr_areas; i++) {
act_entry = debug_find_oldest_entry(debug_area->areas[i],
nr_of_entries,
debug_area->entry_size);
last_entry = (debug_entry_t *) ((char *) debug_area->areas[i] +
(PAGE_SIZE << debug_area->page_order) -
debug_area->entry_size);
for (j = 0; j < nr_of_entries; j++) {
act_entry_data = (char*)act_entry + dbe_size;
if (act_entry->id.stck == 0)
break; /* empty entry */
if (view->header_proc) {
len = view->header_proc(debug_area, view, i,
act_entry, buf);
items = fwrite(buf,len, 1, ofp);
memset(buf, 0, 2048);
}
if (view->format_proc) {
len = view->format_proc(debug_area, view,
buf, act_entry_data);
items = fwrite(buf,len, 1, ofp);
memset(buf, 0, 2048);
}
act_entry =
(debug_entry_t *) (((char *) act_entry) +
debug_area->entry_size);
if (act_entry > last_entry)
act_entry = debug_area->areas[i];
}
}
out:
return 1;
}
/*
* debug_format_output_v2:
* - calls prolog, header and format functions of view to format output
*/
static int
debug_format_output_v2(debug_info_t * debug_area,
debug_view_t *view, FILE * ofp)
{
int i, j, k, len;
debug_entry_t *act_entry;
char *act_entry_data;
char buf[2048];
size_t items ATTRIBUTE_UNUSED;
/* print prolog */
if (view->prolog_proc) {
len = view->prolog_proc(debug_area, view, buf);
items = fwrite(buf,len, 1, ofp);
memset(buf, 0, 2048);
}
/* print debug records */
if (!(view->format_proc) && !(view->header_proc))
goto out;
if(debug_area->entry_size <= 0){
fprintf(ofp, "Invalid entry_size: %i\n",debug_area->entry_size);
goto out;
}
for (i = 0; i < debug_area->nr_areas; i++) {
int nr_entries_per_page = PAGE_SIZE/debug_area->entry_size;
for (j = 0; j < debug_area->pages_per_area_v2; j++) {
act_entry = debug_area->areas_v2[i][j];
for (k = 0; k < nr_entries_per_page; k++) {
act_entry_data = (char*)act_entry + dbe_size;
if (act_entry->id.stck == 0)
break; /* empty entry */
if (view->header_proc) {
len = view->header_proc(debug_area,
view, i, act_entry, buf);
items = fwrite(buf,len, 1, ofp);
memset(buf, 0, 2048);
}
if (view->format_proc) {
len = view->format_proc(debug_area,
view, buf, act_entry_data);
items = fwrite(buf,len, 1, ofp);
memset(buf, 0, 2048);
}
act_entry = (debug_entry_t *) (((char *)
act_entry) + debug_area->entry_size);
}
}
}
out:
return 1;
}
static debug_info_t *
find_debug_area(const char *area_name)
{
debug_info_t* act_debug_info = debug_area_first;
while(act_debug_info != NULL){
if (strcmp(act_debug_info->name, area_name) == 0)
return act_debug_info;
act_debug_info = act_debug_info->next;
}
return NULL;
}
static void tod_clock_base_init(void)
{
if (kernel_symbol_exists("tod_clock_base")) {
/*
* Kernels >= 4.14 that contain 6e2ef5e4f6cc5734 ("s390/time:
* add support for the TOD clock epoch extension")
*/
get_symbol_data("tod_clock_base", sizeof(tod_clock_base_us),
&tod_clock_base_us);
/* Bit for usecs is at position 59 - therefore shift 4 */
tod_clock_base_us >>= 4;
} else if (kernel_symbol_exists("sched_clock_base_cc") &&
!kernel_symbol_exists("tod_to_timeval")) {
/*
* Kernels >= 4.11 that contain ea417aa8a38bc7db ("s390/debug:
* make debug event time stamps relative to the boot TOD clock")
*/
get_symbol_data("sched_clock_base_cc",
sizeof(tod_clock_base_us), &tod_clock_base_us);
/* Bit for usecs is at position 51 - therefore shift 12 */
tod_clock_base_us >>= 12;
} else {
/* All older kernels use absolute time stamps */
tod_clock_base_us = 0;
}
}
static void
dbf_init(void)
{
if (!initialized) {
tod_clock_base_init();
if(dbf_version >= DBF_VERSION_V2)
add_lcrash_debug_view(&pages_view);
add_lcrash_debug_view(&ascii_view);
add_lcrash_debug_view(&level_view);
add_lcrash_debug_view(&ebcdic_view);
add_lcrash_debug_view(&hex_view);
add_lcrash_debug_view(&hex_ascii_view);
add_lcrash_debug_view(&sprintf_view);
add_lcrash_debug_view(&raw_view);
ebcdic_ascii_conv = iconv_open("ISO-8859-1", "EBCDIC-US");
initialized = 1;
}
}
static debug_view_t*
get_debug_view(kaddr_t addr)
{
void* k_debug_view;
int k_debug_view_size;
debug_view_t* rc;
rc = (debug_view_t*)malloc(sizeof(debug_view_t));
memset(rc, 0, sizeof(debug_view_t));
k_debug_view_size = kl_struct_len("debug_view");
k_debug_view = malloc(k_debug_view_size);
GET_BLOCK(addr, k_debug_view_size, k_debug_view);
strncpy(rc->name,K_PTR(k_debug_view,"debug_view","name"),
DEBUG_MAX_PROCF_LEN);
free(k_debug_view);
return rc;
}
static void
free_debug_view(debug_view_t* view)
{
if(view)
free(view);
}
static void
debug_get_areas_v1(debug_info_t* db_info, void* k_dbi)
{
kaddr_t mem_pos;
kaddr_t dbe_addr;
int area_size, i;
/* get areas */
/* place to hold ptrs to debug areas in lcrash */
area_size = PAGE_SIZE << db_info->page_order;
db_info->areas = (void**)malloc(db_info->nr_areas * sizeof(void *));
memset(db_info->areas, 0, db_info->nr_areas * sizeof(void *));
mem_pos = KL_ULONG(k_dbi,"debug_info","areas");
for (i = 0; i < db_info->nr_areas; i++) {
dbe_addr = KL_VREAD_PTR(mem_pos);
db_info->areas[i] = (debug_entry_t *) malloc(area_size);
/* read raw data for debug area */
GET_BLOCK(dbe_addr, area_size, db_info->areas[i]);
mem_pos += KL_NBPW;
}
}
static void
debug_get_areas_v2(debug_info_t* db_info, void* k_dbi)
{
kaddr_t area_ptr;
kaddr_t page_array_ptr;
kaddr_t page_ptr;
int i,j;
db_info->areas_v2=(void***)malloc(db_info->nr_areas * sizeof(void **));
area_ptr = KL_ULONG(k_dbi,"debug_info","areas");
for (i = 0; i < db_info->nr_areas; i++) {
db_info->areas_v2[i] = (void**)malloc(db_info->pages_per_area_v2
* sizeof(void*));
page_array_ptr = KL_VREAD_PTR(area_ptr);
for(j=0; j < db_info->pages_per_area_v2; j++) {
page_ptr = KL_VREAD_PTR(page_array_ptr);
db_info->areas_v2[i][j] = (void*)malloc(PAGE_SIZE);
/* read raw data for debug area */
GET_BLOCK(page_ptr, PAGE_SIZE, db_info->areas_v2[i][j]);
page_array_ptr += KL_NBPW;
}
area_ptr += KL_NBPW;
}
}
static debug_info_t*
get_debug_info(kaddr_t addr,int get_areas)
{
void *k_dbi;
kaddr_t mem_pos;
kaddr_t view_addr;
debug_info_t* db_info;
int i;
int dbi_size;
/* get sizes of kernel structures */
if(!(dbi_size = kl_struct_len("debug_info"))){
fprintf (KL_ERRORFP,
"Could not determine sizeof(struct debug_info)\n");
return(NULL);
}
if(!(dbe_size = kl_struct_len("__debug_entry"))){
fprintf(KL_ERRORFP,
"Could not determine sizeof(struct __debug_entry)\n");
return(NULL);
}
/* get kernel debug_info structure */
k_dbi = malloc(dbi_size);
GET_BLOCK(addr, dbi_size, k_dbi);
db_info = (debug_info_t*)malloc(sizeof(debug_info_t));
memset(db_info, 0, sizeof(debug_info_t));
/* copy members */
db_info->level = KL_INT(k_dbi,"debug_info","level");
db_info->nr_areas = KL_INT(k_dbi,"debug_info","nr_areas");
db_info->pages_per_area_v2= KL_INT(k_dbi,"debug_info","pages_per_area");
db_info->page_order = KL_INT(k_dbi,"debug_info","page_order");
db_info->buf_size = KL_INT(k_dbi,"debug_info","buf_size");
db_info->entry_size = KL_INT(k_dbi,"debug_info","entry_size");
db_info->next_dbi = KL_ULONG(k_dbi,"debug_info","next");
db_info->prev_dbi = KL_ULONG(k_dbi,"debug_info","prev");
db_info->addr = addr;
strncpy(db_info->name,K_PTR(k_dbi,"debug_info","name"),
DEBUG_MAX_PROCF_LEN);
if(get_areas){
if(dbf_version == DBF_VERSION_V1)
debug_get_areas_v1(db_info,k_dbi);
else
debug_get_areas_v2(db_info,k_dbi);
} else {
db_info->areas = NULL;
}
/* get views */
mem_pos = (uaddr_t) K_PTR(k_dbi,"debug_info","views");
memset(&db_info->views, 0, DEBUG_MAX_VIEWS * sizeof(void*));
for (i = 0; i < DEBUG_MAX_VIEWS; i++) {
view_addr = KL_GET_PTR((void*)(uaddr_t)mem_pos);
if(view_addr == 0){
break;
} else {
db_info->views[i] = get_debug_view(view_addr);
}
mem_pos += KL_NBPW;
}
free(k_dbi);
return db_info;
}
static void
free_debug_info_v1(debug_info_t * db_info)
{
int i;
if(db_info->areas){
for (i = 0; i < db_info->nr_areas; i++) {
free(db_info->areas[i]);
}
}
for (i = 0; i < DEBUG_MAX_VIEWS; i++) {
free_debug_view(db_info->views[i]);
}
free(db_info->areas);
free(db_info);
}
static void
free_debug_info_v2(debug_info_t * db_info)
{
int i,j;
if(db_info->areas) {
for (i = 0; i < db_info->nr_areas; i++) {
for(j = 0; j < db_info->pages_per_area_v2; j++) {
free(db_info->areas_v2[i][j]);
}
free(db_info->areas[i]);
}
free(db_info->areas);
db_info->areas = NULL;
}
for (i = 0; i < DEBUG_MAX_VIEWS; i++) {
free_debug_view(db_info->views[i]);
}
free(db_info);
}
static void
debug_write_output(debug_info_t *db_info, debug_view_t *db_view, FILE * fp)
{
if (dbf_version == DBF_VERSION_V1) {
debug_format_output_v1(db_info, db_view, fp);
free_debug_info_v1(db_info);
} else {
debug_format_output_v2(db_info, db_view, fp);
free_debug_info_v2(db_info);
}
}
static int
get_debug_areas(void)
{
kaddr_t act_debug_area;
syment_t *debug_sym;
debug_info_t *act_debug_area_cpy;
if(!(debug_sym = kl_lkup_symname("debug_area_first"))){
printf("Did not find debug_areas");
return -1;
}
act_debug_area = KL_VREAD_PTR(debug_sym->s_addr);
while(act_debug_area != 0){
act_debug_area_cpy = get_debug_info(act_debug_area,0);
act_debug_area = act_debug_area_cpy->next_dbi;
if(debug_area_first == NULL){
debug_area_first = act_debug_area_cpy;
} else {
debug_area_last->next = act_debug_area_cpy;
}
debug_area_last = act_debug_area_cpy;
}
return 0;
}
static void
free_debug_areas(void)
{
debug_info_t* next;
debug_info_t* act_debug_info = debug_area_first;
while(act_debug_info != NULL){
next = act_debug_info->next;
if(dbf_version == DBF_VERSION_V1)
free_debug_info_v1(act_debug_info);
else
free_debug_info_v2(act_debug_info);
act_debug_info = next;
}
debug_area_first = NULL;
debug_area_last = NULL;
}
static debug_view_t *
find_lcrash_debug_view(const char *name)
{
int i;
for (i = 0; (i < LCRASH_DB_VIEWS) && (debug_views[i] != NULL); i++) {
if (strcmp(debug_views[i]->name, name) == 0)
return debug_views[i];
}
return NULL;
}
static void
print_lcrash_debug_views(FILE * ofp)
{
int i;
fprintf(ofp, "REGISTERED VIEWS\n");
fprintf(ofp, "=====================\n");
for (i = 0; i < LCRASH_DB_VIEWS; i++) {
if (debug_views[i] == NULL) {
return;
}
fprintf(ofp, " - %s\n", debug_views[i]->name);
}
}
static int
add_lcrash_debug_view(debug_view_t *view)
{
int i;
for (i = 0; i < LCRASH_DB_VIEWS; i++) {
if (debug_views[i] == NULL) {
debug_views[i] = view;
return 0;
}
if (strcmp(debug_views[i]->name, view->name) == 0)
return -1;
}
return -1;
}
static int
list_one_view(char *area_name, char *view_name, command_t * cmd)
{
debug_info_t *db_info;
debug_view_t *db_view;
if ((db_info = find_debug_area(area_name)) == NULL) {
fprintf(cmd->efp, "Debug log '%s' not found!\n", area_name);
return -1;
}
db_info = get_debug_info(db_info->addr,1);
if ((db_view = find_lcrash_debug_view(view_name)) == NULL) {
fprintf(cmd->efp, "View '%s' not registered!\n", view_name);
return -1;
}
debug_write_output(db_info, db_view, cmd->ofp);
return 0;
}
static int
list_areas(FILE * ofp)
{
debug_info_t* act_debug_info = debug_area_first;
fprintf(ofp, "Debug Logs:\n");
fprintf(ofp, "==================\n");
while(act_debug_info != NULL){
fprintf(ofp, " - %s\n", act_debug_info->name);
act_debug_info = act_debug_info->next;
}
return 0;
}
static int
list_one_area(const char *area_name, command_t * cmd)
{
debug_info_t *db_info;
int i;
if ((db_info = find_debug_area(area_name)) == NULL) {
fprintf(cmd->efp, "Debug log '%s' not found!\n", area_name);
return -1;
}
fprintf(cmd->ofp, "INSTALLED VIEWS FOR '%s':\n", area_name);
fprintf(cmd->ofp, "================================================"
"==============================\n");
for (i = 0; i < DEBUG_MAX_VIEWS; i++) {
if (db_info->views[i] != NULL) {
fprintf(cmd->ofp, " - %s ", db_info->views[i]->name);
if (find_lcrash_debug_view(db_info->views[i]->name))
fprintf(cmd->ofp, "(available)\n");
else
fprintf(cmd->ofp, "(not available)\n");
}
}
fprintf(cmd->ofp, "================================================="
"=============================\n");
return 0;
}
#ifdef DBF_DYNAMIC_VIEWS
static int
load_debug_view(const char *path, command_t * cmd)
{
void *library;
const char *error;
debug_view_t *(*view_init_func) (void);
library = dlopen(path, RTLD_LAZY);
if (library == NULL) {
fprintf(cmd->efp, "Could not open %s: %s\n", path, dlerror());
return (1);
}
dlerror();
view_init_func = dlsym(library, "debug_view_init");
error = dlerror();
if (error) {
fprintf(stderr, "could not find debug_view_init(): %s\n",
error);
exit(1);
}
add_lcrash_debug_view((*view_init_func) ());
fprintf(cmd->ofp, "view %s loaded\n", path);
fflush(stdout);
return 0;
}
#endif
static int
save_one_view(const char *dbf_dir_name, const char *area_name,
const char *view_name, command_t *cmd)
{
char path_view[PATH_MAX];
debug_info_t *db_info;
debug_view_t *db_view;
FILE *view_fh;
db_info = find_debug_area(area_name);
if (db_info == NULL) {
fprintf(cmd->efp, "Debug log '%s' not found!\n", area_name);
return -1;
}
db_info = get_debug_info(db_info->addr, 1);
db_view = find_lcrash_debug_view(view_name);
if (db_view == NULL) {
fprintf(cmd->efp, "View '%s' not registered!\n", view_name);
return -1;
}
sprintf(path_view, "%s/%s/%s", dbf_dir_name, area_name, view_name);
view_fh = fopen(path_view, "w");
if (view_fh == NULL) {
fprintf(cmd->efp, "Could not create file: %s (%s)\n",
path_view, strerror(errno));
return -1;
}
debug_write_output(db_info, db_view, view_fh);
fclose(view_fh);
return 0;
}
static int
save_one_area(const char *dbf_dir_name, const char *area_name, command_t *cmd)
{
char dir_name_area[PATH_MAX];
debug_info_t *db_info;
int i;
db_info = find_debug_area(area_name);
if (db_info == NULL) {
fprintf(cmd->efp, "Debug log '%s' not found!\n", area_name);
return -1;
}
sprintf(dir_name_area, "%s/%s", dbf_dir_name, area_name);
if (mkdir(dir_name_area, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) {
fprintf(cmd->efp, "Could not create directory: %s (%s)\n",
dir_name_area, strerror(errno));
return -1;
}
for (i = 0; i < DEBUG_MAX_VIEWS; i++) {
if (db_info->views[i] == NULL)
continue;
if (!find_lcrash_debug_view(db_info->views[i]->name))
continue;
save_one_view(dbf_dir_name, area_name, db_info->views[i]->name,
cmd);
}
return 0;
}
static void
save_dbf(const char *dbf_dir_name, command_t *cmd)
{
debug_info_t *act_debug_info = debug_area_first;
FILE *ofp = cmd->ofp;
fprintf(ofp, "Saving s390dbf to directory \"%s\"\n", dbf_dir_name);
if (mkdir(dbf_dir_name, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) != 0) {
fprintf(cmd->efp, "Could not create directory: %s (%s)\n",
dbf_dir_name, strerror(errno));
return;
}
while (act_debug_info != NULL) {
save_one_area(dbf_dir_name, act_debug_info->name, cmd);
act_debug_info = act_debug_info->next;
}
}
/*
* s390dbf_cmd() -- Run the 's390dbf' command.
*/
static int
s390dbf_cmd(command_t * cmd)
{
syment_t *dbf_version_sym;
int rc = 0;
/* check version */
if(!(dbf_version_sym = kl_lkup_symname("debug_feature_version"))){
fprintf(KL_ERRORFP,
"Could not determine debug_feature_version\n");
return -1;
}
dbf_version = KL_VREAD_UINT32(dbf_version_sym->s_addr);
if ((dbf_version != DBF_VERSION_V1) && (dbf_version != DBF_VERSION_V2)){
fprintf(cmd->efp,"lcrash does not support the"
" debug feature version of the dump kernel:\n");
fprintf(cmd->efp,"DUMP: %i SUPPORTED: %i and %i\n",
dbf_version, DBF_VERSION_V1, DBF_VERSION_V2);
return -1;
}
dbf_init();
if (cmd->flags & C_ALL) {
return (0);
}
#ifdef DBF_DYNAMIC_VIEWS
if (cmd->flags & LOAD_FLAG) {
printf("loading: %s\n", cmd->args[0]);
return (load_debug_view(cmd->args[0], cmd));
}
#endif
if (cmd->flags & VIEWS_FLAG) {
print_lcrash_debug_views(cmd->ofp);
return (0);
}
if (cmd->nargs > 2) {
s390dbf_usage(cmd);
return (1);
}
if(get_debug_areas() == -1)
return -1;
if (cmd->flags & SAVE_DBF_FLAG) {
if (cmd->nargs != 2) {
fprintf(cmd->efp, "Specify directory name for -s\n");
return 1;
}
save_dbf(cmd->args[1], cmd);
return 0;
}
switch (cmd->nargs) {
case 0:
rc = list_areas(cmd->ofp);
break;
case 1:
rc = list_one_area(cmd->args[0], cmd);
break;
case 2:
rc = list_one_view(cmd->args[0], cmd->args[1], cmd);
break;
}
free_debug_areas();
return rc;
}
#define _S390DBF_USAGE " [-v] [-s dirname] [debug log] [debug view]"
/*
* s390dbf_usage() -- Print the usage string for the 's390dbf' command.
*/
void
s390dbf_usage(command_t * cmd)
{
CMD_USAGE(cmd, _S390DBF_USAGE);
}
/*
* s390 debug feature command for crash
*/
char *help_s390dbf[] = {
"s390dbf",
"s390dbf prints out debug feature logs",
"[-v] [-s dirname] [debug log] [debug view]"
"",
"Display Debug logs:",
" + If called without parameters, all active debug logs are listed.",
" + If called with the name of a debug log, all debug-views for which",
" the debug-log has registered are listed. It is possible thatsome",
" of the debug views are not available to 'crash'.",
" + If called with the name of a debug-log and an available viewname,",
" the specified view is printed.",
" + If called with '-s dirname', the s390dbf is saved to the specified",
" directory",
" + If called with '-v', all debug views which are available to",
" 'crash' are listed",
NULL
};
void cmd_s390dbf()
{
int i,c;
command_t cmd = {
.ofp = fp,
.efp = stderr,
.cmdstr = "s390dbf",
.command = "s390dbf",
};
cmd.nargs=argcnt - 1;
for (i=1; i < argcnt; i++)
cmd.args[i-1] = args[i];
while ((c = getopt(argcnt, args, "vs")) != EOF) {
switch(c) {
case 'v':
cmd.flags |= VIEWS_FLAG;
break;
case 's':
cmd.flags |= SAVE_DBF_FLAG;
break;
default:
s390dbf_usage(&cmd);
return;
}
}
s390dbf_cmd(&cmd);
}
#endif