Changeset View
Standalone View
sys/kern/kern_dump.c
- This file was moved from sys/powerpc/powerpc/dump_machdep.c.
Show All 27 Lines | |||||
__FBSDID("$FreeBSD$"); | __FBSDID("$FreeBSD$"); | ||||
#include "opt_watchdog.h" | #include "opt_watchdog.h" | ||||
#include <sys/param.h> | #include <sys/param.h> | ||||
#include <sys/systm.h> | #include <sys/systm.h> | ||||
#include <sys/conf.h> | #include <sys/conf.h> | ||||
#include <sys/cons.h> | #include <sys/cons.h> | ||||
#include <sys/sysctl.h> | |||||
#include <sys/kernel.h> | #include <sys/kernel.h> | ||||
#include <sys/proc.h> | |||||
#include <sys/kerneldump.h> | #include <sys/kerneldump.h> | ||||
#include <sys/sysctl.h> | |||||
#ifdef SW_WATCHDOG | #ifdef SW_WATCHDOG | ||||
#include <sys/watchdog.h> | #include <sys/watchdog.h> | ||||
#endif | #endif | ||||
#include <vm/vm.h> | #include <vm/vm.h> | ||||
#include <vm/vm_param.h> | |||||
#include <vm/pmap.h> | #include <vm/pmap.h> | ||||
#include <machine/dump.h> | |||||
#include <machine/elf.h> | #include <machine/elf.h> | ||||
#include <machine/md_var.h> | #include <machine/md_var.h> | ||||
#include <machine/pcb.h> | |||||
CTASSERT(sizeof(struct kerneldumpheader) == 512); | CTASSERT(sizeof(struct kerneldumpheader) == 512); | ||||
#ifndef __sparc__ | |||||
int do_minidump = 1; | |||||
SYSCTL_INT(_debug, OID_AUTO, minidump, CTLFLAG_RWTUN, &do_minidump, 0, | |||||
"Enable mini crash dumps"); | |||||
#endif | |||||
/* | /* | ||||
markj: I'd prefer to leave this in the MD code and add do_minidump to the MD headers. On sparc it can… | |||||
Not Done Inline ActionsOk. cse_cem_gmail_com: Ok. | |||||
* Don't touch the first SIZEOF_METADATA bytes on the dump device. This | * Don't touch the first SIZEOF_METADATA bytes on the dump device. This | ||||
* is to protect us from metadata and to protect metadata from us. | * is to protect us from metadata and to protect metadata from us. | ||||
*/ | */ | ||||
#define SIZEOF_METADATA (64*1024) | #define SIZEOF_METADATA (64*1024) | ||||
#define MD_ALIGN(x) (((off_t)(x) + PAGE_MASK) & ~PAGE_MASK) | #define MD_ALIGN(x) (((off_t)(x) + PAGE_MASK) & ~PAGE_MASK) | ||||
#define DEV_ALIGN(x) (((off_t)(x) + (DEV_BSIZE-1)) & ~(DEV_BSIZE-1)) | #define DEV_ALIGN(x) (((off_t)(x) + (DEV_BSIZE-1)) & ~(DEV_BSIZE-1)) | ||||
typedef int callback_t(struct pmap_md *, int, void *); | off_t dumplo; | ||||
static struct kerneldumpheader kdh; | |||||
static off_t dumplo, fileofs; | |||||
/* Handle buffered writes. */ | /* Handle buffered writes. */ | ||||
static char buffer[DEV_BSIZE]; | static char buffer[DEV_BSIZE]; | ||||
static size_t fragsz; | static size_t fragsz; | ||||
int dumpsys_minidump = 1; | struct md_pa dump_map[DUMPSYS_MD_PA_NPAIRS]; | ||||
SYSCTL_INT(_debug, OID_AUTO, minidump, CTLFLAG_RD, &dumpsys_minidump, 0, | |||||
"Kernel makes compressed crash dumps"); | |||||
static int | #if !defined(__sparc__) && !defined(__powerpc__) | ||||
buf_write(struct dumperinfo *di, char *ptr, size_t sz) | void | ||||
dumpsys_gen_md_pa_init(void) | |||||
{ | { | ||||
int n, idx; | |||||
bzero(dump_map, sizeof(dump_map)); | |||||
for (n = 0; n < sizeof(dump_map) / sizeof(dump_map[0]); n++) { | |||||
idx = n * 2; | |||||
if (dump_avail[idx] == 0 && dump_avail[idx + 1] == 0) | |||||
break; | |||||
dump_map[n].md_start = dump_avail[idx]; | |||||
dump_map[n].md_size = dump_avail[idx + 1] - dump_avail[idx]; | |||||
} | |||||
} | |||||
#endif | |||||
struct md_pa * | |||||
dumpsys_gen_md_pa_next(struct md_pa *mdp) | |||||
{ | |||||
if (mdp == NULL) | |||||
return (&dump_map[0]); | |||||
mdp++; | |||||
if (mdp->md_size == 0) | |||||
mdp = NULL; | |||||
return (mdp); | |||||
} | |||||
void | |||||
dumpsys_gen_wbinv_all(void) | |||||
{ | |||||
/* nop */; | |||||
} | |||||
void | |||||
dumpsys_gen_unmap_chunk(vm_paddr_t pa __unused, size_t chunk __unused, | |||||
void *va __unused) | |||||
{ | |||||
/* nop */; | |||||
} | |||||
int | |||||
dumpsys_buf_write(struct dumperinfo *di, char *ptr, size_t sz) | |||||
{ | |||||
size_t len; | size_t len; | ||||
int error; | int error; | ||||
while (sz) { | while (sz) { | ||||
len = DEV_BSIZE - fragsz; | len = DEV_BSIZE - fragsz; | ||||
if (len > sz) | if (len > sz) | ||||
len = sz; | len = sz; | ||||
bcopy(ptr, buffer + fragsz, len); | bcopy(ptr, buffer + fragsz, len); | ||||
fragsz += len; | fragsz += len; | ||||
ptr += len; | ptr += len; | ||||
sz -= len; | sz -= len; | ||||
if (fragsz == DEV_BSIZE) { | if (fragsz == DEV_BSIZE) { | ||||
error = di->dumper(di->priv, buffer, 0, dumplo, | error = dump_write(di, buffer, 0, dumplo, | ||||
DEV_BSIZE); | DEV_BSIZE); | ||||
if (error) | if (error) | ||||
return error; | return error; | ||||
dumplo += DEV_BSIZE; | dumplo += DEV_BSIZE; | ||||
Not Done Inline Actionsparens markj: parens | |||||
Not Done Inline ActionsGood catch. cse_cem_gmail_com: Good catch. | |||||
fragsz = 0; | fragsz = 0; | ||||
} | } | ||||
} | } | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | int | ||||
buf_flush(struct dumperinfo *di) | dumpsys_buf_flush(struct dumperinfo *di) | ||||
{ | { | ||||
int error; | int error; | ||||
if (fragsz == 0) | if (fragsz == 0) | ||||
return (0); | return (0); | ||||
error = di->dumper(di->priv, buffer, 0, dumplo, DEV_BSIZE); | error = dump_write(di, buffer, 0, dumplo, DEV_BSIZE); | ||||
dumplo += DEV_BSIZE; | dumplo += DEV_BSIZE; | ||||
fragsz = 0; | fragsz = 0; | ||||
return (error); | return (error); | ||||
} | } | ||||
static int | CTASSERT(PAGE_SHIFT < 20); | ||||
cb_dumpdata(struct pmap_md *md, int seqnr, void *arg) | #define PG2MB(pgs) ((pgs + (1 << (20 - PAGE_SHIFT)) - 1) >> (20 - PAGE_SHIFT)) | ||||
int | |||||
dumpsys_cb_dumpdata(struct md_pa *mdp, int seqnr, void *arg) | |||||
{ | { | ||||
struct dumperinfo *di = (struct dumperinfo*)arg; | struct dumperinfo *di = (struct dumperinfo*)arg; | ||||
vm_offset_t va; | vm_paddr_t pa; | ||||
size_t counter, ofs, resid, sz, maxsz; | void *va; | ||||
uint64_t pgs; | |||||
size_t counter, sz, chunk; | |||||
int c, error, twiddle; | int c, error, twiddle; | ||||
u_int maxdumppgs; | |||||
error = 0; | error = 0; /* catch case in which chunk size is 0 */ | ||||
counter = 0; /* Update twiddle every 16MB */ | counter = 0; /* Update twiddle every 16MB */ | ||||
twiddle = 0; | twiddle = 0; | ||||
va = 0; | |||||
pgs = mdp->md_size / PAGE_SIZE; | |||||
pa = mdp->md_start; | |||||
maxdumppgs = min(di->maxiosize / PAGE_SIZE, MAXDUMPPGS); | |||||
if (maxdumppgs == 0) /* seatbelt */ | |||||
maxdumppgs = 1; | |||||
ofs = 0; /* Logical offset within the chunk */ | printf(" chunk %d: %juMB (%ju pages)", seqnr, (uintmax_t)PG2MB(pgs), | ||||
resid = md->md_size; | (uintmax_t)pgs); | ||||
maxsz = min(DFLTPHYS, di->maxiosize); | |||||
printf(" chunk %d: %lu bytes ", seqnr, (u_long)resid); | dumpsys_wbinv_all(); | ||||
while (pgs) { | |||||
while (resid) { | chunk = pgs; | ||||
sz = min(resid, maxsz); | if (chunk > maxdumppgs) | ||||
va = pmap_dumpsys_map(md, ofs, &sz); | chunk = maxdumppgs; | ||||
sz = chunk << PAGE_SHIFT; | |||||
counter += sz; | counter += sz; | ||||
if (counter >> 24) { | if (counter >> 24) { | ||||
printf("%c\b", "|/-\\"[twiddle++ & 3]); | printf(" %ju", (uintmax_t)PG2MB(pgs)); | ||||
counter &= (1<<24) - 1; | counter &= (1<<24) - 1; | ||||
} | } | ||||
dumpsys_map_chunk(pa, chunk, &va); | |||||
#ifdef SW_WATCHDOG | #ifdef SW_WATCHDOG | ||||
wdog_kern_pat(WD_LASTVAL); | wdog_kern_pat(WD_LASTVAL); | ||||
#endif | #endif | ||||
error = di->dumper(di->priv, (void*)va, 0, dumplo, sz); | |||||
pmap_dumpsys_unmap(md, ofs, va); | error = dump_write(di, va, 0, dumplo, sz); | ||||
dumpsys_unmap_chunk(pa, chunk, va); | |||||
if (error) | if (error) | ||||
break; | break; | ||||
dumplo += sz; | dumplo += sz; | ||||
resid -= sz; | pgs -= chunk; | ||||
ofs += sz; | pa += sz; | ||||
/* Check for user abort. */ | /* Check for user abort. */ | ||||
c = cncheckc(); | c = cncheckc(); | ||||
if (c == 0x03) | if (c == 0x03) | ||||
return (ECANCELED); | return (ECANCELED); | ||||
if (c != -1) | if (c != -1) | ||||
printf("(CTRL-C to abort) "); | printf(" (CTRL-C to abort) "); | ||||
} | } | ||||
printf("... %s\n", (error) ? "fail" : "ok"); | printf(" ... %s\n", (error) ? "fail" : "ok"); | ||||
return (error); | return (error); | ||||
} | } | ||||
int | |||||
dumpsys_foreach_chunk(dumpsys_callback_t cb, void *arg) | |||||
{ | |||||
struct md_pa *mdp; | |||||
int error, seqnr; | |||||
seqnr = 0; | |||||
mdp = dumpsys_md_pa_next(NULL); | |||||
while (mdp != NULL) { | |||||
error = (*cb)(mdp, seqnr++, arg); | |||||
if (error) | |||||
return (-error); | |||||
mdp = dumpsys_md_pa_next(mdp); | |||||
} | |||||
return (seqnr); | |||||
} | |||||
#ifndef DUMPSYS_HAS_MD_DUMPSYS | |||||
Not Done Inline ActionsIt's preferable to use some indirection so that the code path is a bit more consistent across different arches. I'd like to have an MD DUMPSYS macro that calls dumpsys_generic() or so when that makes sense, and the MD dumpsys otherwise. markj: It's preferable to use some indirection so that the code path is a bit more consistent across… | |||||
Not Done Inline ActionsSure. I've removed the macro and added inline wrappers to non-sparc64 MD headers, similar to the other generic / MD- routines. The main obstacle here was that the routine wouldn't compiled on sparc without a minidumpsys() symbol. So, I've added a dumb wrapper that just returns -ENOSYS to sparc64/include/dump.h. cse_cem_gmail_com: Sure. I've removed the macro and added inline wrappers to non-sparc64 MD headers, similar to… | |||||
static off_t fileofs; | |||||
static int | static int | ||||
cb_dumphdr(struct pmap_md *md, int seqnr, void *arg) | cb_dumphdr(struct md_pa *mdp, int seqnr, void *arg) | ||||
{ | { | ||||
struct dumperinfo *di = (struct dumperinfo*)arg; | struct dumperinfo *di = (struct dumperinfo*)arg; | ||||
Elf_Phdr phdr; | Elf_Phdr phdr; | ||||
uint64_t size; | |||||
int error; | int error; | ||||
size = mdp->md_size; | |||||
bzero(&phdr, sizeof(phdr)); | bzero(&phdr, sizeof(phdr)); | ||||
phdr.p_type = PT_LOAD; | phdr.p_type = PT_LOAD; | ||||
phdr.p_flags = PF_R; /* XXX */ | phdr.p_flags = PF_R; /* XXX */ | ||||
phdr.p_offset = fileofs; | phdr.p_offset = fileofs; | ||||
phdr.p_vaddr = md->md_vaddr; | #ifdef __powerpc__ | ||||
phdr.p_paddr = md->md_paddr; | phdr.p_vaddr = (do_minidump? mdp->md_start : ~0L); | ||||
phdr.p_filesz = md->md_size; | phdr.p_paddr = (do_minidump? ~0L : mdp->md_start); | ||||
phdr.p_memsz = md->md_size; | #else | ||||
phdr.p_vaddr = mdp->md_start; | |||||
phdr.p_paddr = mdp->md_start; | |||||
#endif | |||||
phdr.p_filesz = size; | |||||
phdr.p_memsz = size; | |||||
phdr.p_align = PAGE_SIZE; | phdr.p_align = PAGE_SIZE; | ||||
error = buf_write(di, (char*)&phdr, sizeof(phdr)); | error = dumpsys_buf_write(di, (char*)&phdr, sizeof(phdr)); | ||||
fileofs += phdr.p_filesz; | fileofs += phdr.p_filesz; | ||||
return (error); | return (error); | ||||
} | } | ||||
static int | static int | ||||
cb_size(struct pmap_md *md, int seqnr, void *arg) | cb_size(struct md_pa *mdp, int seqnr, void *arg) | ||||
{ | { | ||||
uint32_t *sz = (uint32_t*)arg; | uint64_t *sz = (uint64_t*)arg; | ||||
*sz += md->md_size; | *sz += (uint64_t)mdp->md_size; | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | |||||
foreach_chunk(callback_t cb, void *arg) | |||||
{ | |||||
struct pmap_md *md; | |||||
int error, seqnr; | |||||
seqnr = 0; | |||||
md = pmap_scan_md(NULL); | |||||
while (md != NULL) { | |||||
error = (*cb)(md, seqnr++, arg); | |||||
if (error) | |||||
return (-error); | |||||
md = pmap_scan_md(md); | |||||
} | |||||
return (seqnr); | |||||
} | |||||
int | int | ||||
dumpsys(struct dumperinfo *di) | dumpsys(struct dumperinfo *di) | ||||
{ | { | ||||
struct kerneldumpheader kdh; | |||||
Not Done Inline ActionsThis should be static or global. (I think you fixed this elsewhere already.) markj: This should be static or global. (I think you fixed this elsewhere already.) | |||||
Not Done Inline ActionsYep, just make it static. (I moved the sparc64 one from a file-level static to a function static, so we should probably be consistent.) cse_cem_gmail_com: Yep, just make it static. (I moved the sparc64 one from a file-level static to a function… | |||||
Elf_Ehdr ehdr; | Elf_Ehdr ehdr; | ||||
uint32_t dumpsize; | uint64_t dumpsize; | ||||
off_t hdrgap; | off_t hdrgap; | ||||
size_t hdrsz; | size_t hdrsz; | ||||
int error; | int error; | ||||
#ifndef __powerpc__ | |||||
if (do_minidump) { | |||||
minidumpsys(di); | |||||
return (0); | |||||
} | |||||
#endif | |||||
bzero(&ehdr, sizeof(ehdr)); | bzero(&ehdr, sizeof(ehdr)); | ||||
ehdr.e_ident[EI_MAG0] = ELFMAG0; | ehdr.e_ident[EI_MAG0] = ELFMAG0; | ||||
ehdr.e_ident[EI_MAG1] = ELFMAG1; | ehdr.e_ident[EI_MAG1] = ELFMAG1; | ||||
ehdr.e_ident[EI_MAG2] = ELFMAG2; | ehdr.e_ident[EI_MAG2] = ELFMAG2; | ||||
ehdr.e_ident[EI_MAG3] = ELFMAG3; | ehdr.e_ident[EI_MAG3] = ELFMAG3; | ||||
ehdr.e_ident[EI_CLASS] = ELF_TARG_CLASS; | ehdr.e_ident[EI_CLASS] = ELF_CLASS; | ||||
#if BYTE_ORDER == LITTLE_ENDIAN | #if BYTE_ORDER == LITTLE_ENDIAN | ||||
ehdr.e_ident[EI_DATA] = ELFDATA2LSB; | ehdr.e_ident[EI_DATA] = ELFDATA2LSB; | ||||
#else | #else | ||||
ehdr.e_ident[EI_DATA] = ELFDATA2MSB; | ehdr.e_ident[EI_DATA] = ELFDATA2MSB; | ||||
#endif | #endif | ||||
ehdr.e_ident[EI_VERSION] = EV_CURRENT; | ehdr.e_ident[EI_VERSION] = EV_CURRENT; | ||||
ehdr.e_ident[EI_OSABI] = ELFOSABI_STANDALONE; /* XXX big picture? */ | ehdr.e_ident[EI_OSABI] = ELFOSABI_STANDALONE; /* XXX big picture? */ | ||||
ehdr.e_type = ET_CORE; | ehdr.e_type = ET_CORE; | ||||
ehdr.e_machine = ELF_ARCH; /* Defined in powerpc/include/elf.h */ | ehdr.e_machine = EM_VALUE; | ||||
ehdr.e_phoff = sizeof(ehdr); | ehdr.e_phoff = sizeof(ehdr); | ||||
ehdr.e_flags = 0; | |||||
ehdr.e_ehsize = sizeof(ehdr); | ehdr.e_ehsize = sizeof(ehdr); | ||||
ehdr.e_phentsize = sizeof(Elf_Phdr); | ehdr.e_phentsize = sizeof(Elf_Phdr); | ||||
ehdr.e_shentsize = sizeof(Elf_Shdr); | ehdr.e_shentsize = sizeof(Elf_Shdr); | ||||
dumpsys_md_pa_init(); | |||||
/* Calculate dump size. */ | /* Calculate dump size. */ | ||||
dumpsize = 0L; | dumpsize = 0L; | ||||
ehdr.e_phnum = foreach_chunk(cb_size, &dumpsize); | ehdr.e_phnum = dumpsys_foreach_chunk(cb_size, &dumpsize); | ||||
hdrsz = ehdr.e_phoff + ehdr.e_phnum * ehdr.e_phentsize; | hdrsz = ehdr.e_phoff + ehdr.e_phnum * ehdr.e_phentsize; | ||||
fileofs = MD_ALIGN(hdrsz); | fileofs = MD_ALIGN(hdrsz); | ||||
dumpsize += fileofs; | dumpsize += fileofs; | ||||
hdrgap = fileofs - DEV_ALIGN(hdrsz); | hdrgap = fileofs - DEV_ALIGN(hdrsz); | ||||
/* For block devices, determine the dump offset on the device. */ | /* Determine dump offset on device. */ | ||||
if (di->mediasize > 0) { | if (di->mediasize < SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) { | ||||
if (di->mediasize < | |||||
SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) { | |||||
error = ENOSPC; | error = ENOSPC; | ||||
goto fail; | goto fail; | ||||
} | } | ||||
dumplo = di->mediaoffset + di->mediasize - dumpsize; | dumplo = di->mediaoffset + di->mediasize - dumpsize; | ||||
dumplo -= sizeof(kdh) * 2; | dumplo -= sizeof(kdh) * 2; | ||||
} else | |||||
dumplo = 0; | |||||
mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_POWERPC_VERSION, dumpsize, | mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_VERSION, dumpsize, | ||||
di->blocksize); | di->blocksize); | ||||
printf("Dumping %u MB (%d chunks)\n", dumpsize >> 20, | printf("Dumping %ju MB (%d chunks)\n", (uintmax_t)dumpsize >> 20, | ||||
ehdr.e_phnum); | ehdr.e_phnum); | ||||
/* Dump leader */ | /* Dump leader */ | ||||
error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); | error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); | ||||
if (error) | if (error) | ||||
goto fail; | goto fail; | ||||
dumplo += sizeof(kdh); | dumplo += sizeof(kdh); | ||||
/* Dump ELF header */ | /* Dump ELF header */ | ||||
error = buf_write(di, (char*)&ehdr, sizeof(ehdr)); | error = dumpsys_buf_write(di, (char*)&ehdr, sizeof(ehdr)); | ||||
if (error) | if (error) | ||||
goto fail; | goto fail; | ||||
/* Dump program headers */ | /* Dump program headers */ | ||||
error = foreach_chunk(cb_dumphdr, di); | error = dumpsys_foreach_chunk(cb_dumphdr, di); | ||||
if (error < 0) | if (error < 0) | ||||
goto fail; | goto fail; | ||||
buf_flush(di); | dumpsys_buf_flush(di); | ||||
/* | /* | ||||
* All headers are written using blocked I/O, so we know the | * All headers are written using blocked I/O, so we know the | ||||
* current offset is (still) block aligned. Skip the alignement | * current offset is (still) block aligned. Skip the alignement | ||||
* in the file to have the segment contents aligned at page | * in the file to have the segment contents aligned at page | ||||
* boundary. We cannot use MD_ALIGN on dumplo, because we don't | * boundary. We cannot use MD_ALIGN on dumplo, because we don't | ||||
* care and may very well be unaligned within the dump device. | * care and may very well be unaligned within the dump device. | ||||
*/ | */ | ||||
dumplo += hdrgap; | dumplo += hdrgap; | ||||
/* Dump memory chunks (updates dumplo) */ | /* Dump memory chunks (updates dumplo) */ | ||||
error = foreach_chunk(cb_dumpdata, di); | error = dumpsys_foreach_chunk(dumpsys_cb_dumpdata, di); | ||||
if (error < 0) | if (error < 0) | ||||
goto fail; | goto fail; | ||||
/* Dump trailer */ | /* Dump trailer */ | ||||
error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); | error = dump_write(di, &kdh, 0, dumplo, sizeof(kdh)); | ||||
if (error) | if (error) | ||||
goto fail; | goto fail; | ||||
Show All 9 Lines | fail: | ||||
if (error == ECANCELED) | if (error == ECANCELED) | ||||
printf("\nDump aborted\n"); | printf("\nDump aborted\n"); | ||||
else if (error == ENOSPC) | else if (error == ENOSPC) | ||||
printf("\nDump failed. Partition too small.\n"); | printf("\nDump failed. Partition too small.\n"); | ||||
else | else | ||||
printf("\n** DUMP FAILED (ERROR %d) **\n", error); | printf("\n** DUMP FAILED (ERROR %d) **\n", error); | ||||
return (error); | return (error); | ||||
} | } | ||||
#endif /* !DUMPSYS_HAS_MD_DUMPSYS */ |
I'd prefer to leave this in the MD code and add do_minidump to the MD headers. On sparc it can be hard-wired to 0.