summaryrefslogtreecommitdiff
path: root/sys/uvm
diff options
context:
space:
mode:
Diffstat (limited to 'sys/uvm')
-rw-r--r--sys/uvm/uvm_unix.c147
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;