/* lkcd_common.c - core analysis suite * * Copyright (C) 1999, 2000, 2001, 2002 Mission Critical Linux, Inc. * Copyright (C) 2002 Silicon Graphics, Inc. * Copyright (C) 2002 Free Software Foundation, Inc. * Copyright (C) 2002-2005, 2007, 2009, 2011, 2013 David Anderson * Copyright (C) 2002-2005, 2007, 2009, 2011, 2013 Red Hat, Inc. All rights reserved. * * 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. */ /* * lkcd_uncompress_RLE() is essentially LKCD's __cmpuncompress_page() rountine, * adapted from ../cmd/lcrash/lib/libklib/arch/i386/kl_cmp.c: */ /* * arch/i386/cmp.c * * This file handles compression aspects of crash dump files * for i386 based systems. Most of this is taken from the * IRIX compression code, with exceptions to how the index * is created, because the file format is different with Linux. * * Copyright 1999 Silicon Graphics, Inc. All rights reserved. */ /* * This file has no knowledge of the dump_header_t, dump_header_asm_t or * dump_page_t formats, so it gathers information from them via the version * specific "_v1" or "_v2_v3" type routines. */ #define LKCD_COMMON #include "defs.h" static void dump_dump_page(char *, void *); static int lkcd_uncompress_RLE(unsigned char *, unsigned char *,uint32_t,int *); static int lkcd_uncompress_gzip(unsigned char *, ulong, unsigned char *, ulong); static int hash_page(ulong); static int page_is_cached(void); static int page_is_hashed(long *); static int cache_page(void); struct lkcd_environment lkcd_environment = { 0 }; struct lkcd_environment *lkcd = &lkcd_environment; static int uncompress_errloc; static int uncompress_recover(unsigned char *, ulong, unsigned char *, ulong); ulonglong fix_lkcd_address(ulonglong addr) { int i; ulong offset; for (i = 0; i < lkcd->fix_addr_num; i++) { if ( (addr >=lkcd->fix_addr[i].task) && (addr < lkcd->fix_addr[i].task + STACKSIZE())){ offset = addr - lkcd->fix_addr[i].task; addr = lkcd->fix_addr[i].saddr + offset; } } return addr; } /* * Each version has its own dump initialization. */ int lkcd_dump_init(FILE *fp, int fd, char *dumpfile) { switch (lkcd->version) { case LKCD_DUMP_V1: return(lkcd_dump_init_v1(fp, fd)); case LKCD_DUMP_V2: case LKCD_DUMP_V3: return(lkcd_dump_init_v2_v3(fp, fd)); case LKCD_DUMP_V5: case LKCD_DUMP_V6: return(lkcd_dump_init_v5(fp, fd)); case LKCD_DUMP_V7: return(lkcd_dump_init_v7(fp, fd, dumpfile)); case LKCD_DUMP_V8: case LKCD_DUMP_V9: return(lkcd_dump_init_v8(fp, fd, dumpfile)); default: return FALSE; } } /* * Return the page size value recorded in the dump header. */ uint32_t lkcd_page_size(void) { return lkcd->page_size; } /* * Return the panic task and panic string. */ unsigned long get_lkcd_panic_task(void) { return(lkcd->flags & (LKCD_VALID|LKCD_REMOTE) ? lkcd->panic_task : 0); } void get_lkcd_panicmsg(char *buf) { if (lkcd->flags & (LKCD_VALID|LKCD_REMOTE)) strcpy(buf, lkcd->panic_string); } /* * Called by remote_lkcd_dump_init() the local (!valid) lkcd_environment * is used to store the panic task and panic message for use by the * two routines above. */ void set_remote_lkcd_panic_data(ulong task, char *buf) { if (buf) { if (!(lkcd->panic_string = (char *)malloc(strlen(buf)+1))) { fprintf(stderr, "cannot malloc space for panic message!\n"); clean_exit(1); } strcpy(lkcd->panic_string, buf); } if (task) lkcd->panic_task = task; lkcd->flags |= LKCD_REMOTE; } /* * Does the magic number indicate an LKCD compressed dump? * If so, set the version number for all future forays into the * functions in this file. */ int is_lkcd_compressed_dump(char *s) { int tmpfd; uint64_t magic; uint32_t version; char errbuf[BUFSIZE]; if ((tmpfd = open(s, O_RDONLY)) < 0) { strcpy(errbuf, s); perror(errbuf); return FALSE; } if (read(tmpfd, &magic, sizeof(uint64_t)) != sizeof(uint64_t)) { close(tmpfd); return FALSE; } if (read(tmpfd, &version, sizeof(uint32_t)) != sizeof(uint32_t)) { close(tmpfd); return FALSE; } close(tmpfd); if (!((magic == LKCD_DUMP_MAGIC_NUMBER) || (magic == LKCD_DUMP_MAGIC_LIVE))) return FALSE; switch (version & ~(LKCD_DUMP_MCLX_V0|LKCD_DUMP_MCLX_V1)) { case LKCD_DUMP_V1: lkcd->version = LKCD_DUMP_V1; return TRUE; case LKCD_DUMP_V2: case LKCD_DUMP_V3: lkcd->version = LKCD_DUMP_V2; return TRUE; case LKCD_DUMP_V5: case LKCD_DUMP_V6: lkcd->version = LKCD_DUMP_V5; return TRUE; case LKCD_DUMP_V7: lkcd->version = LKCD_DUMP_V7; return TRUE; case LKCD_DUMP_V8: case LKCD_DUMP_V9: case LKCD_DUMP_V10: lkcd->version = LKCD_DUMP_V8; return TRUE; default: lkcd_print("unsupported LKCD dump version: %ld (%lx)\n", version & ~(LKCD_DUMP_MCLX_V0|LKCD_DUMP_MCLX_V1), version); return FALSE; } } /* * console-only output for info regarding current page. */ static void dump_dump_page(char *s, void *dp) { switch (lkcd->version) { case LKCD_DUMP_V1: dump_dump_page_v1(s, dp); break; case LKCD_DUMP_V2: case LKCD_DUMP_V3: dump_dump_page_v2_v3(s, dp); break; case LKCD_DUMP_V5: dump_dump_page_v5(s, dp); break; case LKCD_DUMP_V7: dump_dump_page_v7(s, dp); break; case LKCD_DUMP_V8: case LKCD_DUMP_V9: dump_dump_page_v8(s, dp); break; } } /* * help -S output, or as specified by arg. */ void dump_lkcd_environment(ulong arg) { int others; if (arg == LKCD_DUMP_HEADER_ONLY) goto dump_header_only; if (arg == LKCD_DUMP_PAGE_ONLY) goto dump_page_only; lkcd_print(" fd: %d\n", lkcd->fd); lkcd_print(" fp: %lx\n", lkcd->fp); lkcd_print(" debug: %ld\n", lkcd->debug); lkcd_print(" flags: %lx (", lkcd->flags); others = 0; if (lkcd->flags & LKCD_VALID) lkcd_print("%sLKCD_VALID", others++ ? "|" : ""); if (lkcd->flags & LKCD_REMOTE) lkcd_print("%sLKCD_REMOTE", others++ ? "|" : ""); if (lkcd->flags & LKCD_NOHASH) lkcd_print("%sLKCD_NOHASH", others++ ? "|" : ""); if (lkcd->flags & LKCD_MCLX) lkcd_print("%sLKCD_MCLX", others++ ? "|" : ""); if (lkcd->flags & LKCD_BAD_DUMP) lkcd_print("%sLKCD_BAD_DUMP", others++ ? "|" : ""); lkcd_print(")\n"); dump_header_only: switch (lkcd->version) { case LKCD_DUMP_V1: dump_lkcd_environment_v1(LKCD_DUMP_HEADER_ONLY); break; case LKCD_DUMP_V2: case LKCD_DUMP_V3: dump_lkcd_environment_v2_v3(LKCD_DUMP_HEADER_ONLY); break; case LKCD_DUMP_V5: dump_lkcd_environment_v5(LKCD_DUMP_HEADER_ONLY); break; case LKCD_DUMP_V7: dump_lkcd_environment_v7(LKCD_DUMP_HEADER_ONLY); break; case LKCD_DUMP_V8: case LKCD_DUMP_V9: dump_lkcd_environment_v8(LKCD_DUMP_HEADER_ONLY); break; } if (arg == LKCD_DUMP_HEADER_ONLY) return; dump_page_only: switch (lkcd->version) { case LKCD_DUMP_V1: dump_lkcd_environment_v1(LKCD_DUMP_PAGE_ONLY); break; case LKCD_DUMP_V2: case LKCD_DUMP_V3: dump_lkcd_environment_v2_v3(LKCD_DUMP_PAGE_ONLY); break; case LKCD_DUMP_V5: dump_lkcd_environment_v5(LKCD_DUMP_PAGE_ONLY); break; case LKCD_DUMP_V7: dump_lkcd_environment_v7(LKCD_DUMP_PAGE_ONLY); break; case LKCD_DUMP_V8: dump_lkcd_environment_v8(LKCD_DUMP_PAGE_ONLY); break; } if (arg == LKCD_DUMP_PAGE_ONLY) return; lkcd_print(" version: %ld\n", lkcd->version); lkcd_print(" page_size: %ld\n", lkcd->page_size); lkcd_print(" page_shift: %d\n", lkcd->page_shift); lkcd_print(" bits: %d\n", lkcd->bits); lkcd_print(" panic_task: %lx\n", lkcd->panic_task); lkcd_print(" panic_string: %s%s", lkcd->panic_string, lkcd->panic_string && strstr(lkcd->panic_string, "\n") ? "" : "\n"); lkcd_print(" get_dp_size: "); if (lkcd->get_dp_size == get_dp_size_v1) lkcd_print("get_dp_size_v1()\n"); else if (lkcd->get_dp_size == get_dp_size_v2_v3) lkcd_print("get_dp_size_v2_v3()\n"); else if (lkcd->get_dp_size == get_dp_size_v5) lkcd_print("get_dp_size_v5()\n"); else lkcd_print("%lx\n", lkcd->get_dp_size); lkcd_print(" get_dp_flags: "); if (lkcd->get_dp_flags == get_dp_flags_v1) lkcd_print("get_dp_flags_v1()\n"); else if (lkcd->get_dp_flags == get_dp_flags_v2_v3) lkcd_print("get_dp_flags_v2_v3()\n"); else if (lkcd->get_dp_flags == get_dp_flags_v5) lkcd_print("get_dp_flags_v5()\n"); else lkcd_print("%lx\n", lkcd->get_dp_flags); lkcd_print(" get_dp_address: "); if (lkcd->get_dp_address == get_dp_address_v1) lkcd_print("get_dp_address_v1()\n"); else if (lkcd->get_dp_address == get_dp_address_v2_v3) lkcd_print("get_dp_address_v2_v3()\n"); else if (lkcd->get_dp_address == get_dp_address_v5) lkcd_print("get_dp_address_v5()\n"); else lkcd_print("%lx\n", lkcd->get_dp_address); lkcd_print(" compression: "); lkcd_print(BITS32() ? "%lx " : "%x ", lkcd->compression); switch (lkcd->compression) { case LKCD_DUMP_COMPRESS_NONE: lkcd_print("(LKCD_DUMP_COMPRESS_NONE)\n"); break; case LKCD_DUMP_COMPRESS_RLE: lkcd_print("(LKCD_DUMP_COMPRESS_RLE)\n"); break; case LKCD_DUMP_COMPRESS_GZIP: lkcd_print("(LKCD_DUMP_COMPRESS_GZIP)\n"); break; default: lkcd_print("(unknown)\n"); break; } lkcd_print("page_header_size: %ld\n", lkcd->page_header_size); lkcd_print(" curpos: %ld\n", lkcd->curpos); lkcd_print(" curpaddr: "); lkcd_print(BITS32() ? "%llx\n" : "%lx\n", lkcd->curpaddr); lkcd_print(" curbufptr: %lx\n", lkcd->curbufptr); lkcd_print(" curhdroffs: %ld\n", lkcd->curhdroffs); lkcd_print(" kvbase: "); lkcd_print(BITS32() ? "%llx\n" : "%lx\n", lkcd->kvbase); lkcd_print(" page_cache_buf: %lx\n", lkcd->page_cache_buf); lkcd_print(" compressed_page: %lx\n", lkcd->compressed_page); lkcd_print(" evict_index: %d\n", lkcd->evict_index); lkcd_print(" evictions: %ld\n", lkcd->evictions); lkcd_print(" benchmark_pages: %ld\n", lkcd->benchmark_pages); lkcd_print(" benchmarks_done: %ld\n", lkcd->benchmarks_done); lkcd_memory_dump(lkcd->fp); } /* * Set the shadow debug flag. */ void set_lkcd_debug(ulong debug) { lkcd->debug = debug; } /* * Set no-hash flag bit. */ void set_lkcd_nohash(void) { lkcd->flags |= LKCD_NOHASH; } /* * Set the file pointer for debug output. */ FILE * set_lkcd_fp(FILE *fp) { lkcd->fp = fp; return fp; } /* * Return the number of pages cached. */ int lkcd_memory_used(void) { int i, pages; struct page_cache_hdr *sp; sp = &lkcd->page_cache_hdr[0]; for (i = pages = 0; i < LKCD_CACHED_PAGES; i++, sp++) { if (LKCD_VALID_PAGE(sp->pg_flags)) pages++; } return pages; } /* * Since the dumpfile pages are temporary tenants of a fixed page cache, * this command doesn't do anything except clear the references. */ int lkcd_free_memory(void) { int i, pages; struct page_cache_hdr *sp; sp = &lkcd->page_cache_hdr[0]; for (i = pages = 0; i < LKCD_CACHED_PAGES; i++, sp++) { if (LKCD_VALID_PAGE(sp->pg_flags)) { sp->pg_addr = 0; sp->pg_hit_count = 0; pages++; } sp->pg_flags = 0; } return pages; } /* * Dump the page cache; */ int lkcd_memory_dump(FILE *fp) { int i, c, pages; struct page_cache_hdr *sp; struct page_hash_entry *phe; ulong pct_cached, pct_hashed; ulong pct_compressed, pct_raw; FILE *fpsave; char buf[BUFSIZE]; int wrap; fpsave = lkcd->fp; lkcd->fp = fp; lkcd_print(" total_pages: %ld\n", lkcd->total_pages); pct_compressed = (lkcd->compressed*100) / (lkcd->hashed ? lkcd->hashed : 1); pct_raw = (lkcd->raw*100) / (lkcd->hashed ? lkcd->hashed : 1); lkcd_print(" hashed: %ld\n", lkcd->hashed); lkcd_print(" compressed: %ld (%ld%%)\n", lkcd->compressed, pct_compressed); lkcd_print(" raw: %ld (%ld%%)\n", lkcd->raw, pct_raw); pct_cached = (lkcd->cached_reads*100) / (lkcd->total_reads ? lkcd->total_reads : 1); pct_hashed = (lkcd->hashed_reads*100) / (lkcd->total_reads ? lkcd->total_reads : 1); lkcd_print(" cached_reads: %ld (%ld%%)\n", lkcd->cached_reads, pct_cached); lkcd_print(" hashed_reads: %ld (%ld%%)\n", lkcd->hashed_reads, pct_hashed); lkcd_print(" total_reads: %ld (hashed or cached: %ld%%) \n", lkcd->total_reads, pct_cached+pct_hashed); lkcd_print("page_hash[%2d]:\n", LKCD_PAGE_HASH); if (LKCD_DEBUG(1)) { for (i = 0; i < LKCD_PAGE_HASH; i++) { phe = &lkcd->page_hash[i]; if (!LKCD_VALID_PAGE(phe->pg_flags)) continue; lkcd_print(" [%2d]: ", i); wrap = 0; while (phe && LKCD_VALID_PAGE(phe->pg_flags)) { sprintf(buf, "%llx@", (ulonglong)phe->pg_addr); sprintf(&buf[strlen(buf)], "%llx,", (ulonglong)phe->pg_hdr_offset); lkcd_print("%18s", buf); phe = phe->next; if (phe && (++wrap == 3)) { lkcd_print("\n "); wrap = 0; } } lkcd_print("\n"); } } else { for (i = 0; i < LKCD_PAGE_HASH; i++) { phe = &lkcd->page_hash[i]; if (!LKCD_VALID_PAGE(phe->pg_flags)) continue; lkcd_print(" [%2d]: ", i); wrap = 0; while (phe && LKCD_VALID_PAGE(phe->pg_flags)) { lkcd_print(BITS32() ? "%9llx," : "%9lx,", phe->pg_addr); phe = phe->next; if (phe && (++wrap == 7)) { lkcd_print("\n "); wrap = 0; } } lkcd_print("\n"); } } lkcd_print("page_cache_hdr[%2d]:\n", LKCD_CACHED_PAGES); lkcd_print(" INDEX PG_ADDR PG_BUFPTR"); lkcd_print(BITS32() ? " PG_HIT_COUNT\n" : " PG_HIT_COUNT\n"); sp = &lkcd->page_cache_hdr[0]; for (i = pages = 0; i < LKCD_CACHED_PAGES; i++, sp++) { if (LKCD_VALID_PAGE(sp->pg_flags)) pages++; if (BITS32()) lkcd_print(" [%2d] %9llx %lx %ld\n", i, sp->pg_addr, sp->pg_bufptr, sp->pg_hit_count); else lkcd_print(" [%2d] %9lx %lx %ld\n", i, sp->pg_addr, sp->pg_bufptr, sp->pg_hit_count); } if (lkcd->mb_hdr_offsets) { lkcd_print("mb_hdr_offsets[%3ld]: \n", lkcd->benchmark_pages); for (i = 0; i < lkcd->benchmark_pages; i += 8) { lkcd_print(" [%3d]", i); c = 0; while ((c < 8) && ((i+c) < lkcd->benchmark_pages)) { lkcd_print(" %8lx", lkcd->mb_hdr_offsets[i+c]); c++; } lkcd_print("\n"); } } else { lkcd_print(" mb_hdr_offsets: NA\n"); } if (lkcd->zones) { lkcd_print(" num_zones: %d / %d\n", lkcd->num_zones, lkcd->max_zones); lkcd_print(" zoned_offsets: %ld\n", lkcd->zoned_offsets); } lkcd_print(" dumpfile_index: %s\n", lkcd->dumpfile_index); lkcd_print(" ifd: %d\n", lkcd->ifd); lkcd_print(" memory_pages: %ld\n", lkcd->memory_pages); lkcd_print(" page_offset_max: %ld\n", lkcd->page_offset_max); lkcd_print(" page_index_max: %ld\n", lkcd->page_index_max); lkcd_print(" page_offsets: %lx\n", lkcd->page_offsets); lkcd->fp = fpsave; return pages; } /* * The lkcd_lseek() routine does the bulk of the work setting things up * so that the subsequent lkcd_read() simply has to do a bcopy(). * Given a physical address, first determine: * * (1) its page offset (lkcd->curpos). * (2) its page address as specified in the dumpfile (lkcd->curpaddr). * * If the page data is already cached, everything will be set up for the * subsequent read when page_is_cached() returns. * * If the page data is not cached, either of the following occurs: * * (1) page_is_hashed() will check whether the page header offset is cached, * and if so, will set up the page variable, and lseek to the header. * * In either case above, the starting point for the page search is set up. * Lastly, cache_page() stores the requested page's data. */ static int save_offset(uint64_t paddr, off_t off) { uint64_t zone, page; int ii, ret; int max_zones; struct physmem_zone *zones; ret = -1; zone = paddr & lkcd->zone_mask; page = (paddr & ~lkcd->zone_mask) >> lkcd->page_shift; if (lkcd->num_zones == 0) { lkcd->zones = malloc(ZONE_ALLOC * sizeof(struct physmem_zone)); if (!lkcd->zones) { return -1; /* This should be fatal */ } BZERO(lkcd->zones, ZONE_ALLOC * sizeof(struct physmem_zone)); lkcd->max_zones = ZONE_ALLOC; lkcd->zones[0].start = zone; lkcd->zones[0].pages = malloc((ZONE_SIZE >> lkcd->page_shift) * sizeof(struct page_desc)); if (!lkcd->zones[0].pages) { return -1; /* this should be fatal */ } BZERO(lkcd->zones[0].pages, (ZONE_SIZE >> lkcd->page_shift) * sizeof(struct page_desc)); lkcd->num_zones++; } retry: /* find the zone */ for (ii=0; ii < lkcd->num_zones; ii++) { if (lkcd->zones[ii].start == zone) { if (lkcd->zones[ii].pages[page].offset != 0) { if (lkcd->zones[ii].pages[page].offset != off) { if (CRASHDEBUG(1) && !STREQ(pc->curcmd, "search")) error(INFO, "LKCD: conflicting page: zone %lld, " "page %lld: %lld, %lld != %lld\n", (unsigned long long)zone, (unsigned long long)page, (unsigned long long)paddr, (unsigned long long)off, (unsigned long long)lkcd->zones[ii].pages[page].offset); return -1; } ret = 0; } else { lkcd->zones[ii].pages[page].offset = off; ret = 1; } break; } } if (ii == lkcd->num_zones) { /* This is a new zone */ if (lkcd->num_zones < lkcd->max_zones) { /* We have room for another one */ lkcd->zones[ii].start = zone; lkcd->zones[ii].pages = malloc( (ZONE_SIZE >> lkcd->page_shift) * sizeof(struct page_desc)); if (!lkcd->zones[ii].pages) { return -1; /* this should be fatal */ } BZERO(lkcd->zones[ii].pages, (ZONE_SIZE >> lkcd->page_shift) * sizeof(struct page_desc)); lkcd->zones[ii].pages[page].offset = off; ret = 1; lkcd->num_zones++; } else { /* need to expand zone */ max_zones = lkcd->max_zones * 2; zones = malloc(max_zones * sizeof(struct physmem_zone)); if (!zones) { return -1; /* This should be fatal */ } BZERO(zones, max_zones * sizeof(struct physmem_zone)); memcpy(zones, lkcd->zones, lkcd->max_zones * sizeof(struct physmem_zone)); free(lkcd->zones); lkcd->zones = zones; lkcd->max_zones = max_zones; goto retry; } } return ret; /* 1 if the page is new */ } static off_t get_offset(uint64_t paddr) { uint64_t zone, page; int ii; zone = paddr & lkcd->zone_mask; page = (paddr % ZONE_SIZE) >> lkcd->page_shift; if (lkcd->zones == 0) { return 0; } /* find the zone */ for (ii=0; ii < lkcd->num_zones; ii++) { if (lkcd->zones[ii].start == zone) { return (lkcd->zones[ii].pages[page].offset); } } return 0; } #ifdef IA64 int lkcd_get_kernel_start(ulong *addr) { if (!addr) return 0; switch (lkcd->version) { case LKCD_DUMP_V8: case LKCD_DUMP_V9: return lkcd_get_kernel_start_v8(addr); default: return 0; } } #endif int lkcd_lseek(physaddr_t paddr) { int err; int eof; void *dp; long page = 0; physaddr_t physaddr; int seeked_to_page = 0; off_t page_offset; dp = lkcd->dump_page; lkcd->curpos = paddr & ((physaddr_t)(lkcd->page_size-1)); lkcd->curpaddr = paddr & ~((physaddr_t)(lkcd->page_size-1)); if (page_is_cached()) return TRUE; /* Faster than paging in lkcd->page_offsets[page] */ if(page_is_hashed(&page)) { seeked_to_page = 1; } /* Find the offset for this page, if known */ if ((page_offset = get_offset(paddr)) > 0) { off_t seek_offset; seek_offset = lseek(lkcd->fd, page_offset, SEEK_SET); if (seek_offset == page_offset) { seeked_to_page = 1; page = 0; /* page doesn't make any sense */ } } if (seeked_to_page) { err = lkcd_load_dump_page_header(dp, page); if (err == LKCD_DUMPFILE_OK) { return(cache_page()); } } /* We have to grind through some more of the dump file */ lseek(lkcd->fd, lkcd->page_offset_max, SEEK_SET); eof = FALSE; while (!eof) { switch (lkcd_load_dump_page_header(dp, page)) { case LKCD_DUMPFILE_OK: break; case LKCD_DUMPFILE_EOF: eof = TRUE; continue; } physaddr = lkcd->get_dp_flags() & (LKCD_DUMP_MCLX_V0|LKCD_DUMP_MCLX_V1) ? (lkcd->get_dp_address() - lkcd->kvbase) << lkcd->page_shift: lkcd->get_dp_address() - lkcd->kvbase; if (physaddr == lkcd->curpaddr) { return(cache_page()); } lseek(lkcd->fd, lkcd->get_dp_size(), SEEK_CUR); } return FALSE; } /* * Everything's been set up by the previous lkcd_lseek(), so all that has * to be done is to read the uncompressed data into the user buffer: * * lkcd->curbufptr points to the uncompressed page base. * lkcd->curpos is the offset into the buffer. */ long lkcd_read(void *buf, long count) { char *p; lkcd->total_reads++; p = lkcd->curbufptr + lkcd->curpos; BCOPY(p, buf, count); return count; } /* * Check whether lkcd->curpaddr is already cached. If it is, update * lkcd->curbufptr to point to the page's uncompressed data. */ static int page_is_cached(void) { int i; for (i = 0; i < LKCD_CACHED_PAGES; i++) { if (!LKCD_VALID_PAGE(lkcd->page_cache_hdr[i].pg_flags)) continue; if (lkcd->page_cache_hdr[i].pg_addr == lkcd->curpaddr) { lkcd->page_cache_hdr[i].pg_hit_count++; lkcd->curbufptr = lkcd->page_cache_hdr[i].pg_bufptr; lkcd->cached_reads++; return TRUE; } } return FALSE; } /* * For an incoming page: * * (1) If it's already hashed just return TRUE. * (2) If the base page_hash_entry is unused, fill it up and return TRUE; * (3) Otherwise, find the last page_hash_entry on the list, allocate and * fill a new one, link it on the list, and return TRUE. * (4) If the malloc fails, quietly return FALSE (with no harm done). */ static int hash_page(ulong type) { struct page_hash_entry *phe; int index; if (lkcd->flags & LKCD_NOHASH) { lkcd->flags &= ~LKCD_NOHASH; return FALSE; } index = LKCD_PAGE_HASH_INDEX(lkcd->curpaddr); for (phe = &lkcd->page_hash[index]; LKCD_VALID_PAGE(phe->pg_flags); phe = phe->next) { if (phe->pg_addr == lkcd->curpaddr) return TRUE; if (!phe->next) break; } if (LKCD_VALID_PAGE(phe->pg_flags)) { if ((phe->next = malloc (sizeof(struct page_hash_entry))) == NULL) return FALSE; phe = phe->next; } phe->pg_flags |= LKCD_VALID; phe->pg_addr = lkcd->curpaddr; phe->pg_hdr_offset = lkcd->curhdroffs; phe->next = NULL; lkcd->hashed++; switch (type) { case LKCD_DUMP_COMPRESSED: lkcd->compressed++; break; case LKCD_DUMP_RAW: lkcd->raw++; break; } return TRUE; } /* * Check whether a page is currently hashed, and if so, return the page * number so that the subsequent search loop will find it immediately. */ static int page_is_hashed(long *pp) { struct page_hash_entry *phe; int index; index = LKCD_PAGE_HASH_INDEX(lkcd->curpaddr); for (phe = &lkcd->page_hash[index]; LKCD_VALID_PAGE(phe->pg_flags); phe = phe->next) { if (phe->pg_addr == lkcd->curpaddr) { *pp = (long)(lkcd->curpaddr >> lkcd->page_shift); lseek(lkcd->fd, phe->pg_hdr_offset, SEEK_SET); lkcd->hashed_reads++; return TRUE; } if (!phe->next) break; } return FALSE; } /* * The caller stores the incoming page's page header offset in * lkcd->curhdroffs. */ int set_mb_benchmark(ulong page) { long mb; if ((mb = LKCD_PAGE_MEGABYTE(page)) >= lkcd->benchmark_pages) return FALSE; if (!lkcd->mb_hdr_offsets[mb]) { lkcd->mb_hdr_offsets[mb] = lkcd->curhdroffs; lkcd->benchmarks_done++; } return TRUE; } /* * Coming into this routine: * * (1) lkcd->curpaddr points to the page address as specified in the dumpfile. * (2) the dump_page header has been copied into lkcd->dump_page. * (3) the file pointer is sitting at the beginning of the page data, * be it compressed or otherwise. * (4) lkcd->curhdroffs contains the file pointer to the incoming page's * header offset. * * If an empty page cache location is available, take it. Otherwise, evict * the entry indexed by evict_index, and then bump evict index. The hit_count * is only gathered for dump_lkcd_environment(). * * If the page is compressed, uncompress it into the selected page cache entry. * If the page is raw, just copy it into the selected page cache entry. * If all works OK, update lkcd->curbufptr to point to the page's uncompressed * data. * */ static int cache_page(void) { int i; ulong type; int found, newsz; uint32_t rawsz; ssize_t bytes ATTRIBUTE_UNUSED; for (i = found = 0; i < LKCD_CACHED_PAGES; i++) { if (LKCD_VALID_PAGE(lkcd->page_cache_hdr[i].pg_flags)) continue; found = TRUE; break; } if (!found) { i = lkcd->evict_index; lkcd->page_cache_hdr[i].pg_hit_count = 0; lkcd->evict_index = (lkcd->evict_index+1) % LKCD_CACHED_PAGES; lkcd->evictions++; } lkcd->page_cache_hdr[i].pg_flags = 0; lkcd->page_cache_hdr[i].pg_addr = lkcd->curpaddr; lkcd->page_cache_hdr[i].pg_hit_count++; type = lkcd->get_dp_flags() & (LKCD_DUMP_COMPRESSED|LKCD_DUMP_RAW); switch (type) { case LKCD_DUMP_COMPRESSED: if (LKCD_DEBUG(2)) dump_dump_page("cmp: ", lkcd->dump_page); newsz = 0; BZERO(lkcd->compressed_page, lkcd->page_size); bytes = read(lkcd->fd, lkcd->compressed_page, lkcd->get_dp_size()); switch (lkcd->compression) { case LKCD_DUMP_COMPRESS_NONE: lkcd_print("dump_header: DUMP_COMPRESS_NONE and " "dump_page: DUMP_COMPRESSED (?)\n"); return FALSE; case LKCD_DUMP_COMPRESS_RLE: if (!lkcd_uncompress_RLE((unsigned char *) lkcd->compressed_page, (unsigned char *)lkcd->page_cache_hdr[i].pg_bufptr, lkcd->get_dp_size(), &newsz) || (newsz != lkcd->page_size)) { lkcd_print("uncompress of page "); lkcd_print(BITS32() ? "%llx failed!\n" : "%lx failed!\n", lkcd->get_dp_address()); lkcd_print("newsz returned: %d\n", newsz); return FALSE; } break; case LKCD_DUMP_COMPRESS_GZIP: if (!lkcd_uncompress_gzip((unsigned char *) lkcd->page_cache_hdr[i].pg_bufptr, lkcd->page_size, (unsigned char *)lkcd->compressed_page, lkcd->get_dp_size())) { lkcd_print("uncompress of page "); lkcd_print(BITS32() ? "%llx failed!\n" : "%lx failed!\n", lkcd->get_dp_address()); return FALSE; } break; } break; case LKCD_DUMP_RAW: if (LKCD_DEBUG(2)) dump_dump_page("raw: ", lkcd->dump_page); if ((rawsz = lkcd->get_dp_size()) == 0) BZERO(lkcd->page_cache_hdr[i].pg_bufptr, lkcd->page_size); else if (rawsz == lkcd->page_size) bytes = read(lkcd->fd, lkcd->page_cache_hdr[i].pg_bufptr, lkcd->page_size); else { lkcd_print("cache_page: " "invalid LKCD_DUMP_RAW dp_size\n"); dump_lkcd_environment(LKCD_DUMP_PAGE_ONLY); return FALSE; } break; default: lkcd_print("cache_page: bogus page:\n"); dump_lkcd_environment(LKCD_DUMP_PAGE_ONLY); return FALSE; } lkcd->page_cache_hdr[i].pg_flags |= LKCD_VALID; lkcd->curbufptr = lkcd->page_cache_hdr[i].pg_bufptr; hash_page(type); return TRUE; } /* * Uncompress an RLE-encoded buffer. */ static int lkcd_uncompress_RLE(unsigned char *cbuf, unsigned char *ucbuf, uint32_t blk_size, int *new_size) { int i; unsigned char value, count, cur_byte; uint32_t ri, wi; /* initialize the read / write indices */ ri = wi = 0; /* otherwise decompress using run length encoding */ while(ri < blk_size) { cur_byte = cbuf[ri++]; if (cur_byte == 0) { count = cbuf[ri++]; if (count == 0) { ucbuf[wi++] = 0; } else { value = cbuf[ri++]; for (i = 0; i <= count; i++) { ucbuf[wi++] = value; } } } else { ucbuf[wi++] = cur_byte; } /* if our write index is beyond the page size, exit out */ if (wi > /* PAGE_SIZE */ lkcd->page_size) { lkcd_print( "Attempted to decompress beyond page boundaries: file corrupted!\n"); return (0); } } /* set return size to be equal to uncompressed size (in bytes) */ *new_size = wi; return 1; } /* Returns the bit offset if it's able to correct, or negative if not */ static int uncompress_recover(unsigned char *dest, ulong destlen, unsigned char *source, ulong sourcelen) { int byte, bit; ulong retlen = destlen; int good_decomp = 0, good_rv = -1; /* Generate all single bit errors */ if (sourcelen > 16384) { lkcd_print("uncompress_recover: sourcelen %ld too long\n", sourcelen); return(-1); } for (byte = 0; byte < sourcelen; byte++) { for (bit = 0; bit < 8; bit++) { source[byte] ^= (1 << bit); if (uncompress(dest, &retlen, source, sourcelen) == Z_OK && retlen == destlen) { good_decomp++; lkcd_print("good for flipping byte %d bit %d\n", byte, bit); good_rv = bit + byte * 8; } /* Put it back */ source[byte] ^= (1 << bit); } } if (good_decomp == 0) { lkcd_print("Could not correct gzip errors.\n"); return -2; } else if (good_decomp > 1) { lkcd_print("Too many valid gzip decompressions: %d.\n", good_decomp); return -3; } else { source[good_rv >> 8] ^= 1 << (good_rv % 8); uncompress(dest, &retlen, source, sourcelen); source[good_rv >> 8] ^= 1 << (good_rv % 8); return good_rv; } } /* * Uncompress a gzip'd buffer. * * Returns FALSE on error. If set, then * a non-negative value of uncompress_errloc indicates the location of * a single-bit error, and the data may be used. */ static int lkcd_uncompress_gzip(unsigned char *dest, ulong destlen, unsigned char *source, ulong sourcelen) { ulong retlen = destlen; int rc = FALSE; switch (uncompress(dest, &retlen, source, sourcelen)) { case Z_OK: if (retlen == destlen) { rc = TRUE; break; } lkcd_print("uncompress: returned length not page size: %ld\n", retlen); rc = FALSE; break; case Z_MEM_ERROR: lkcd_print("uncompress: Z_MEM_ERROR (not enough memory)\n"); rc = FALSE; break; case Z_BUF_ERROR: lkcd_print("uncompress: " "Z_BUF_ERROR (not enough room in output buffer)\n"); rc = FALSE; break; case Z_DATA_ERROR: lkcd_print("uncompress: Z_DATA_ERROR (input data corrupted)\n"); rc = FALSE; break; default: rc = FALSE; break; } if (rc == FALSE) { uncompress_errloc = uncompress_recover(dest, destlen, source, sourcelen); } return rc; } /* * Generic print routine to handle integral and remote daemon usage of */ void lkcd_print(char *fmt, ...) { char buf[BUFSIZE]; va_list ap; if (!fmt || !strlen(fmt)) return; va_start(ap, fmt); (void)vsnprintf(buf, BUFSIZE, fmt, ap); va_end(ap); if (lkcd->fp) fprintf(lkcd->fp, "%s", buf); else console(buf); } /* * Try to read the current dump page header, reporting back either * LKCD_DUMPFILE_EOF, LKCD_DUMPFILE_END or LKCD_DUMPFILE_OK. The header's * file pointer position is saved in lkcd->curhdroffs. If the page is * an even megabyte, save its offset. */ int lkcd_load_dump_page_header(void *dp, ulong page) { uint32_t dp_flags; uint64_t dp_address, physaddr; off_t page_offset; int ret; /* This is wasted effort */ page_offset = lkcd->curhdroffs = lseek(lkcd->fd, 0, SEEK_CUR); if (read(lkcd->fd, dp, lkcd->page_header_size) != lkcd->page_header_size) { if (page > lkcd->total_pages) lkcd_dumpfile_complaint(page, lkcd->total_pages, LKCD_DUMPFILE_EOF); return LKCD_DUMPFILE_EOF; } dp_flags = lkcd->get_dp_flags(); dp_address = lkcd->get_dp_address(); if (dp_flags & LKCD_DUMP_END) { return LKCD_DUMPFILE_END; } if ((lkcd->flags & LKCD_VALID) && (page > lkcd->total_pages)) lkcd->total_pages = page; #ifdef X86 /* * Ugly leftover from very early x86 LKCD versions which used * the kernel unity-mapped virtual address as the dp_address. */ if ((page == 0) && !(lkcd->flags & LKCD_VALID) && (lkcd->version == LKCD_DUMP_V1) && (dp_address == 0xc0000000)) lkcd->kvbase = dp_address; #endif physaddr = dp_flags & (LKCD_DUMP_MCLX_V0|LKCD_DUMP_MCLX_V1) ? (dp_address - lkcd->kvbase) << lkcd->page_shift : dp_address - lkcd->kvbase; if ((ret = save_offset(physaddr, page_offset)) < 0) { return LKCD_DUMPFILE_EOF; /* really an error */ } lkcd->zoned_offsets += ret; /* return = 0 if already known */ if (page_offset > lkcd->page_offset_max) { /* doesn't this mean I have to re-read this dp? */ lkcd->page_offset_max = page_offset; } return LKCD_DUMPFILE_OK; } /* * Register a complaint one time, if appropriate. */ void lkcd_dumpfile_complaint(uint32_t realpages, uint32_t dh_num_pages, int retval) { if (lkcd->flags & LKCD_BAD_DUMP) return; lkcd->flags |= LKCD_BAD_DUMP; if (realpages > dh_num_pages) { lkcd_print( "\n\nWARNING: This dumpfile contains more pages than the amount indicated\n" " in the dumpfile header. This is indicative of a failure during\n" " the post-panic creation of the dumpfile on the dump device.\n\n"); } if (realpages < dh_num_pages) { lkcd_print( "\n\nWARNING: This dumpfile contains fewer pages than the amount indicated\n" " in the dumpfile header. This is indicative of a failure during\n" " the creation of the dumpfile during boot.\n\n"); } } int get_lkcd_regs_for_cpu(struct bt_info *bt, ulong *eip, ulong *esp) { switch (lkcd->version) { case LKCD_DUMP_V8: case LKCD_DUMP_V9: return get_lkcd_regs_for_cpu_v8(bt, eip, esp); default: return -1; } }