#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/cons.h>
#include <sys/sysctl.h>
#include <sys/device.h>
#include <sys/kernel.h>
#include <sys/kerneldump.h>
#include <sys/kbio.h>
#include <vm/vm.h>
#include <vm/pmap.h>
#include <machine/elf.h>
#include <machine/md_var.h>
#include <machine/thread.h>
#include <machine/vmparam.h>
CTASSERT(sizeof(struct kerneldumpheader) == 512);
static int do_minidump = 1;
TUNABLE_INT("debug.minidump", &do_minidump);
SYSCTL_INT(_debug, OID_AUTO, minidump, CTLFLAG_RW, &do_minidump, 0,
"Enable mini crash dumps");
#define SIZEOF_METADATA (64*1024)
#define MD_ALIGN(x) (((off_t)(x) + PAGE_MASK) & ~PAGE_MASK)
#define DEV_ALIGN(x) roundup2((off_t)(x), DEV_BSIZE)
struct md_pa {
vm_paddr_t md_start;
vm_paddr_t md_size;
};
typedef int callback_t(struct md_pa *, int, void *);
static struct kerneldumpheader kdh;
static off_t dumplo, fileofs;
static char buffer[DEV_BSIZE];
static size_t fragsz;
static struct md_pa dump_map[VM_PHYSSEG_MAX+1];
static void
md_pa_init(void)
{
int n;
bzero(dump_map, sizeof(dump_map));
for (n = 0; n < NELEM(dump_map); n++) {
if (dump_avail[n].phys_beg == 0 && dump_avail[n].phys_end == 0)
break;
dump_map[n].md_start = dump_avail[n].phys_beg;
dump_map[n].md_size = dump_avail[n].phys_end -
dump_avail[n].phys_beg;
}
}
static struct md_pa *
md_pa_first(void)
{
return (&dump_map[0]);
}
static struct md_pa *
md_pa_next(struct md_pa *mdp)
{
mdp++;
if (mdp->md_size == 0)
mdp = NULL;
return (mdp);
}
static int
buf_write(struct dumperinfo *di, char *ptr, size_t sz)
{
size_t len;
int error;
while (sz) {
len = DEV_BSIZE - fragsz;
if (len > sz)
len = sz;
bcopy(ptr, buffer + fragsz, len);
fragsz += len;
ptr += len;
sz -= len;
if (fragsz == DEV_BSIZE) {
error = dev_ddump(di->priv, buffer, 0, dumplo,
DEV_BSIZE);
if (error)
return error;
dumplo += DEV_BSIZE;
fragsz = 0;
}
}
return (0);
}
static int
buf_flush(struct dumperinfo *di)
{
int error;
if (fragsz == 0)
return (0);
error = dev_ddump(di->priv, buffer, 0, dumplo, DEV_BSIZE);
dumplo += DEV_BSIZE;
fragsz = 0;
return (error);
}
#define PG2MB(pgs) ((pgs + (1 << 8) - 1) >> 8)
static int
cb_dumpdata(struct md_pa *mdp, int seqnr, void *arg)
{
struct dumperinfo *di = (struct dumperinfo*)arg;
vm_paddr_t a, pa;
void *va;
uint64_t pgs;
size_t counter, sz, chunk;
int i, c, error;
int max_iosize;
error = 0;
counter = 0;
va = NULL;
pgs = mdp->md_size / PAGE_SIZE;
pa = mdp->md_start;
max_iosize = min(MAXPHYS, di->maxiosize);
kprintf(" chunk %d: %ldMB (%ld pages)", seqnr, PG2MB(pgs), pgs);
cnpoll(TRUE);
while (pgs) {
chunk = pgs;
if (chunk > (max_iosize/PAGE_SIZE))
chunk = max_iosize/PAGE_SIZE;
sz = chunk << PAGE_SHIFT;
counter += sz;
if (counter >> 24) {
kprintf(" %ld", PG2MB(pgs));
counter &= (1<<24) - 1;
}
for (i = 0; i < chunk; i++) {
a = pa + i * PAGE_SIZE;
va = pmap_kenter_temporary(trunc_page(a), i);
}
smp_invltlb();
error = dev_ddump(di->priv, va, 0, dumplo, sz);
if (error)
break;
dumplo += sz;
pgs -= chunk;
pa += sz;
c = cncheckc();
if (c == 0x03) {
error = ECANCELED;
goto done;
}
if (c != -1 && c != NOKEY)
kprintf(" (CTRL-C to abort) ");
}
kprintf(" ... %s\n", (error) ? "fail" : "ok");
done:
cnpoll(FALSE);
return (error);
}
static int
cb_dumphdr(struct md_pa *mdp, int seqnr, void *arg)
{
struct dumperinfo *di = (struct dumperinfo*)arg;
Elf_Phdr phdr;
uint64_t size;
int error;
size = mdp->md_size;
bzero(&phdr, sizeof(phdr));
phdr.p_type = PT_LOAD;
phdr.p_flags = PF_R;
phdr.p_offset = fileofs;
phdr.p_vaddr = mdp->md_start;
phdr.p_paddr = mdp->md_start;
phdr.p_filesz = size;
phdr.p_memsz = size;
phdr.p_align = PAGE_SIZE;
error = buf_write(di, (char*)&phdr, sizeof(phdr));
fileofs += phdr.p_filesz;
return (error);
}
static int
cb_size(struct md_pa *mdp, int seqnr, void *arg)
{
uint64_t *sz = (uint64_t*)arg;
*sz += (uint64_t)mdp->md_size;
return (0);
}
static int
foreach_chunk(callback_t cb, void *arg)
{
struct md_pa *mdp;
int error, seqnr;
seqnr = 0;
mdp = md_pa_first();
while (mdp != NULL) {
error = (*cb)(mdp, seqnr++, arg);
if (error)
return (-error);
mdp = md_pa_next(mdp);
}
return (seqnr);
}
void
md_dumpsys(struct dumperinfo *di)
{
Elf_Ehdr ehdr;
uint64_t dumpsize;
off_t hdrgap;
size_t hdrsz;
int error;
if (dumpthread == NULL) {
savectx(&dumppcb);
dumpthread = curthread;
}
if (do_minidump) {
minidumpsys(di);
return;
}
bzero(&ehdr, sizeof(ehdr));
ehdr.e_ident[EI_MAG0] = ELFMAG0;
ehdr.e_ident[EI_MAG1] = ELFMAG1;
ehdr.e_ident[EI_MAG2] = ELFMAG2;
ehdr.e_ident[EI_MAG3] = ELFMAG3;
ehdr.e_ident[EI_CLASS] = ELF_CLASS;
#if BYTE_ORDER == LITTLE_ENDIAN
ehdr.e_ident[EI_DATA] = ELFDATA2LSB;
#else
ehdr.e_ident[EI_DATA] = ELFDATA2MSB;
#endif
ehdr.e_ident[EI_VERSION] = EV_CURRENT;
ehdr.e_ident[EI_OSABI] = ELFOSABI_STANDALONE;
ehdr.e_type = ET_CORE;
ehdr.e_machine = EM_X86_64;
ehdr.e_phoff = sizeof(ehdr);
ehdr.e_flags = 0;
ehdr.e_ehsize = sizeof(ehdr);
ehdr.e_phentsize = sizeof(Elf_Phdr);
ehdr.e_shentsize = sizeof(Elf_Shdr);
md_pa_init();
dumpsize = 0L;
ehdr.e_phnum = foreach_chunk(cb_size, &dumpsize);
hdrsz = ehdr.e_phoff + ehdr.e_phnum * ehdr.e_phentsize;
fileofs = MD_ALIGN(hdrsz);
dumpsize += fileofs;
hdrgap = fileofs - DEV_ALIGN(hdrsz);
if (di->mediasize < SIZEOF_METADATA + dumpsize + sizeof(kdh) * 2) {
error = ENOSPC;
goto fail;
}
dumplo = di->mediaoffset + di->mediasize - dumpsize;
dumplo -= sizeof(kdh) * 2;
mkdumpheader(&kdh, KERNELDUMPMAGIC, KERNELDUMP_AMD64_VERSION,
dumpsize, di->blocksize);
kprintf("Dumping %llu MB (%d chunks)\n", (long long)dumpsize >> 20,
ehdr.e_phnum);
error = dev_ddump(di->priv, &kdh, 0, dumplo, sizeof(kdh));
if (error)
goto fail;
dumplo += sizeof(kdh);
error = buf_write(di, (char*)&ehdr, sizeof(ehdr));
if (error)
goto fail;
error = foreach_chunk(cb_dumphdr, di);
if (error < 0)
goto fail;
buf_flush(di);
dumplo += hdrgap;
error = foreach_chunk(cb_dumpdata, di);
if (error < 0)
goto fail;
error = dev_ddump(di->priv, &kdh, 0, dumplo, sizeof(kdh));
if (error)
goto fail;
dev_ddump(di->priv, NULL, 0, 0, 0);
kprintf("\nDump complete\n");
return;
fail:
if (error < 0)
error = -error;
if (error == ECANCELED)
kprintf("\nDump aborted\n");
else if (error == ENOSPC)
kprintf("\nDump failed. Partition too small.\n");
else
kprintf("\n** DUMP FAILED (ERROR %d) **\n", error);
}