diff options
author | Philip Guenther <guenther@cvs.openbsd.org> | 2017-03-05 00:55:02 +0000 |
---|---|---|
committer | Philip Guenther <guenther@cvs.openbsd.org> | 2017-03-05 00:55:02 +0000 |
commit | 5fd91751cffca7923663e498b8314b86732635e0 (patch) | |
tree | 598c4b6b58c2158f68a519ed9ae4e9835fdb1af1 /sys/uvm | |
parent | f46e226ea387f6570231c4ef8bbcc71c6c54231c (diff) |
Handle unshared amaps in uvm_coredump_walkmap() such that untouched pages
don't get written out to the core file but rather are represented via
segments which have memory size greater than their file size. This shrinks
core files and eliminates a case where core dumping fails with EFAULT.
This can still happen in the shared amap case.
Based on a problem report from (and testing by) semarie@
ok stefan@
Diffstat (limited to 'sys/uvm')
-rw-r--r-- | sys/uvm/uvm_unix.c | 147 |
1 files changed, 142 insertions, 5 deletions
diff --git a/sys/uvm/uvm_unix.c b/sys/uvm/uvm_unix.c index 62c48505921..44e59bc4046 100644 --- a/sys/uvm/uvm_unix.c +++ b/sys/uvm/uvm_unix.c @@ -1,4 +1,4 @@ -/* $OpenBSD: uvm_unix.c,v 1.62 2017/03/05 00:45:31 guenther Exp $ */ +/* $OpenBSD: uvm_unix.c,v 1.63 2017/03/05 00:55:01 guenther Exp $ */ /* $NetBSD: uvm_unix.c,v 1.18 2000/09/13 15:00:25 thorpej Exp $ */ /* @@ -134,6 +134,78 @@ uvm_grow(struct proc *p, vaddr_t sp) #ifndef SMALL_KERNEL +#define WALK_CHUNK 32 +/* + * Not all the pages in an amap may be present. When dumping core, + * we don't want to force all the pages to be present: it's a waste + * of time and memory when we already know what they contain (zeros) + * and the ELF format at least can adequately represent them as a + * segment with memory size larger than its file size. + * + * So, we walk the amap with calls to amap_lookups() and scan the + * resulting pointers to find ranges of zero or more present pages + * followed by at least one absent page or the end of the amap. + * When then pass that range to the walk callback with 'start' + * pointing to the start of the present range, 'realend' pointing + * to the first absent page (or the end of the entry), and 'end' + * pointing to the page page the last absent page (or the end of + * the entry). + * + * Note that if the first page of the amap is empty then the callback + * must be invoked with 'start' == 'realend' so it can present that + * first range of absent pages. + */ +int +uvm_coredump_walk_amap(struct vm_map_entry *entry, int *nsegmentp, + uvm_coredump_walk_cb *walk, void *cookie) +{ + struct vm_anon *anons[WALK_CHUNK]; + vaddr_t pos, start, realend, end, entry_end; + vm_prot_t prot; + int nsegment, absent, npages, i, error; + + prot = entry->protection; + nsegment = *nsegmentp; + start = entry->start; + entry_end = MIN(entry->end, VM_MAXUSER_ADDRESS); + + absent = 0; + for (pos = start; pos < entry_end; pos += npages << PAGE_SHIFT) { + npages = (entry_end - pos) >> PAGE_SHIFT; + if (npages > WALK_CHUNK) + npages = WALK_CHUNK; + amap_lookups(&entry->aref, pos - entry->start, anons, npages); + for (i = 0; i < npages; i++) { + if ((anons[i] == NULL) == absent) + continue; + if (!absent) { + /* going from present to absent: set realend */ + realend = pos + (i << PAGE_SHIFT); + absent = 1; + continue; + } + + /* going from absent to present: invoke callback */ + end = pos + (i << PAGE_SHIFT); + if (start != end) { + error = (*walk)(start, realend, end, prot, + nsegment, cookie); + if (error) + return error; + nsegment++; + } + start = realend = end; + absent = 0; + } + } + + if (!absent) + realend = entry_end; + error = (*walk)(start, realend, entry_end, prot, nsegment, cookie); + *nsegmentp = nsegment + 1; + return error; +} + /* * Common logic for whether a map entry should be included in a coredump */ @@ -166,6 +238,15 @@ uvm_should_coredump(struct proc *p, struct vm_map_entry *entry) return 1; } + +/* do nothing callback for uvm_coredump_walk_amap() */ +static int +noop(vaddr_t start, vaddr_t realend, vaddr_t end, vm_prot_t prot, + int nsegment, void *cookie) +{ + return 0; +} + /* * Walk the VA space for a process to identify what to write to * a coredump. First the number of contiguous ranges is counted, @@ -183,10 +264,16 @@ uvm_coredump_walkmap(struct proc *p, uvm_coredump_setup_cb *setup, struct vm_map *map = &vm->vm_map; struct vm_map_entry *entry; vaddr_t end; + int refed_amaps = 0; int nsegment, error; /* - * Walk the map once to count the segments. + * Walk the map once to count the segments. If an amap is + * referenced more than once than take *another* reference + * and treat the amap as exactly one segment instead of + * checking page presence inside it. On the second pass + * we'll recognize which amaps we did that for by the ref + * count being >1...and decrement it then. */ nsegment = 0; vm_map_lock_read(map); @@ -199,25 +286,52 @@ uvm_coredump_walkmap(struct proc *p, uvm_coredump_setup_cb *setup, if (! uvm_should_coredump(p, entry)) continue; + if (entry->aref.ar_amap != NULL) { + if (entry->aref.ar_amap->am_ref == 1) { + uvm_coredump_walk_amap(entry, &nsegment, + &noop, cookie); + continue; + } + + /* + * Multiple refs currently, so take another and + * treat it as a single segment + */ + entry->aref.ar_amap->am_ref++; + refed_amaps++; + } + nsegment++; } /* - * Okay, we have a count in nsegment; invoke the setup callback. + * Okay, we have a count in nsegment. Prepare to + * walk it again, then invoke the setup callback. */ + entry = RBT_MIN(uvm_map_addr, &map->addr); error = (*setup)(nsegment, cookie); if (error) goto cleanup; /* * Setup went okay, so do the second walk, invoking the walk - * callback on the counted segments. + * callback on the counted segments and cleaning up references + * as we go. */ nsegment = 0; - RBT_FOREACH(entry, uvm_map_addr, &map->addr) { + for (; entry != NULL; entry = RBT_NEXT(uvm_map_addr, entry)) { if (! uvm_should_coredump(p, entry)) continue; + if (entry->aref.ar_amap != NULL && + entry->aref.ar_amap->am_ref == 1) { + error = uvm_coredump_walk_amap(entry, &nsegment, + walk, cookie); + if (error) + break; + continue; + } + end = entry->end; if (end > VM_MAXUSER_ADDRESS) end = VM_MAXUSER_ADDRESS; @@ -227,9 +341,32 @@ uvm_coredump_walkmap(struct proc *p, uvm_coredump_setup_cb *setup, if (error) break; nsegment++; + + if (entry->aref.ar_amap != NULL && + entry->aref.ar_amap->am_ref > 1) { + /* multiple refs, so we need to drop one */ + entry->aref.ar_amap->am_ref--; + refed_amaps--; + } } + if (error) { cleanup: + /* clean up the extra references from where we left off */ + if (refed_amaps > 0) { + for (; entry != NULL; + entry = RBT_NEXT(uvm_map_addr, entry)) { + if (entry->aref.ar_amap == NULL || + entry->aref.ar_amap->am_ref == 1) + continue; + if (! uvm_should_coredump(p, entry)) + continue; + entry->aref.ar_amap->am_ref--; + if (refed_amaps-- == 0) + break; + } + } + } vm_map_unlock_read(map); return error; |