ref: 655ec332a714d3e5cc6aace798daf832e17e001e
parent: e53511ef4c7d4db443543506e74e4de537da5475
author: cinap_lenrek <[email protected]>
date: Mon Jul 14 02:02:21 EDT 2014
devproc: fix proccrlmemio bugs dont kill the calling process when demand load fails if fixfault() is called from devproc. this happens when you delete the binary of a running process and try to debug the process accessing uncached pages thru /proc/$pid/mem file. fixes to procctlmemio(): - fix missed unlock as txt2data() can error - make sure the segment isnt freed by taking a reference (under p->seglock) - access the page with segment locked (see comment) - get rid of the segment stealer lock other stuff: - move txt2data() and data2txt() to segment.c - add procpagecount() function - make return type mcounseg() to ulong
--- a/sys/src/9/port/devproc.c
+++ b/sys/src/9/port/devproc.c
@@ -154,9 +154,9 @@
void procctlreq(Proc*, char*, int);
int procctlmemio(Proc*, uintptr, int, void*, int);
Chan* proctext(Chan*, Proc*);
-Segment* txt2data(Proc*, Segment*);
int procstopped(void*);
void mntscan(Mntwalk*, Proc*);
+ulong procpagecount(Proc *);
static Traceevent *tevents;
static Lock tlock;
@@ -896,23 +896,7 @@
readnum(0, statbuf+j+NUMSIZE*i, NUMSIZE, l, NUMSIZE);
}
- l = 0;
- eqlock(&p->seglock);
- if(waserror()){
- qunlock(&p->seglock);
- nexterror();
- }
- for(i=0; i<NSEG; i++){
- if(s = p->seg[i]){
- eqlock(s);
- l += mcountseg(s);
- qunlock(s);
- }
- }
- poperror();
- qunlock(&p->seglock);
-
- readnum(0, statbuf+j+NUMSIZE*6, NUMSIZE, l*BY2PG/1024, NUMSIZE);
+ readnum(0, statbuf+j+NUMSIZE*6, NUMSIZE, procpagecount(p)*BY2PG/1024, NUMSIZE);
readnum(0, statbuf+j+NUMSIZE*7, NUMSIZE, p->basepri, NUMSIZE);
readnum(0, statbuf+j+NUMSIZE*8, NUMSIZE, p->priority, NUMSIZE);
memmove(a, statbuf+offset, n);
@@ -1540,6 +1524,32 @@
return p->state == Stopped;
}
+ulong
+procpagecount(Proc *p)
+{
+ Segment *s;
+ ulong pages;
+ int i;
+
+ eqlock(&p->seglock);
+ if(waserror()){
+ qunlock(&p->seglock);
+ nexterror();
+ }
+ pages = 0;
+ for(i=0; i<NSEG; i++){
+ if((s = p->seg[i]) != nil){
+ eqlock(s);
+ pages += mcountseg(s);
+ qunlock(s);
+ }
+ }
+ qunlock(&p->seglock);
+ poperror();
+
+ return pages;
+}
+
int
procctlmemio(Proc *p, uintptr offset, int n, void *va, int read)
{
@@ -1547,110 +1557,118 @@
Pte *pte;
Page *pg;
Segment *s;
- uintptr soff, l;
- char *a = va, *b;
+ uintptr soff;
+ char *a, *b;
+ int i, l;
+ /* Only one page at a time */
+ l = BY2PG - (offset&(BY2PG-1));
+ if(n > l)
+ n = l;
+
+ /*
+ * Make temporary copy to avoid fault while we have
+ * segment locked as we would deadlock when trying
+ * to read the calling procs memory.
+ */
+ a = malloc(n);
+ if(a == nil)
+ error(Enomem);
+ if(waserror()) {
+ free(a);
+ nexterror();
+ }
+
+ if(!read)
+ memmove(a, va, n); /* can fault */
+
for(;;) {
- s = seg(p, offset, 1);
- if(s == 0)
+ s = seg(p, offset, 0);
+ if(s == nil)
error(Ebadarg);
- if(offset+n >= s->top)
- n = s->top-offset;
+ eqlock(&p->seglock);
+ if(waserror()) {
+ qunlock(&p->seglock);
+ nexterror();
+ }
- if(!read && (s->type&SG_TYPE) == SG_TEXT)
- s = txt2data(p, s);
+ for(i = 0; i < NSEG; i++) {
+ if(p->seg[i] == s)
+ break;
+ }
+ if(i == NSEG)
+ error(Egreg); /* segment gone */
- s->steal++;
- soff = offset-s->base;
- if(waserror()) {
- s->steal--;
+ eqlock(s);
+ if(waserror()){
+ qunlock(s);
nexterror();
}
- if(fixfault(s, offset, read, 0) == 0)
- break;
+ if(!read && (s->type&SG_TYPE) == SG_TEXT) {
+ s = txt2data(s);
+ p->seg[i] = s;
+ }
+ incref(s);
+ qunlock(&p->seglock);
poperror();
- s->steal--;
+ poperror();
+ /* segment s still locked, fixfault() unlocks */
+ if(!waserror()){
+ if(fixfault(s, offset, read, 0) == 0)
+ break;
+ poperror();
+ }
+ putseg(s);
}
- poperror();
+
+ /*
+ * Only access the page while segment is locked
+ * as the proc could segfree or relocate the pte
+ * concurrently.
+ */
+ eqlock(s);
+ if(waserror()){
+ qunlock(s);
+ nexterror();
+ }
+ if(offset+n >= s->top)
+ n = s->top-offset;
+ soff = offset-s->base;
pte = s->map[soff/PTEMAPMEM];
- if(pte == 0)
- panic("procctlmemio");
+ if(pte == nil)
+ error(Egreg); /* page gone, should retry? */
pg = pte->pages[(soff&(PTEMAPMEM-1))/BY2PG];
if(pagedout(pg))
- panic("procctlmemio1");
+ error(Egreg); /* page gone, should retry? */
- l = BY2PG - (offset&(BY2PG-1));
- if(n > l)
- n = l;
-
+ /* Map and copy the page */
k = kmap(pg);
- if(waserror()) {
- s->steal--;
- kunmap(k);
- nexterror();
- }
b = (char*)VA(k);
b += offset&(BY2PG-1);
- if(read == 1)
- memmove(a, b, n); /* This can fault */
+ if(read)
+ memmove(a, b, n);
else
memmove(b, a, n);
kunmap(k);
- poperror();
/* Ensure the process sees text page changes */
if(s->flushme)
memset(pg->cachectl, PG_TXTFLUSH, sizeof(pg->cachectl));
- s->steal--;
-
- if(read == 0)
+ if(!read)
p->newtlb = 1;
- return n;
-}
-
-Segment*
-txt2data(Proc *p, Segment *s)
-{
- int i;
- Segment *ps;
-
- ps = newseg(SG_DATA, s->base, s->size);
- ps->image = s->image;
- incref(ps->image);
- ps->fstart = s->fstart;
- ps->flen = s->flen;
- ps->flushme = 1;
-
- qlock(&p->seglock);
- for(i = 0; i < NSEG; i++)
- if(p->seg[i] == s)
- break;
- if(i == NSEG)
- panic("segment gone");
-
qunlock(s);
+ poperror();
putseg(s);
- qlock(ps);
- p->seg[i] = ps;
- qunlock(&p->seglock);
+ poperror();
- return ps;
-}
+ if(read)
+ memmove(va, a, n); /* can fault */
-Segment*
-data2txt(Segment *s)
-{
- Segment *ps;
+ free(a);
+ poperror();
- ps = newseg(SG_TEXT, s->base, s->size);
- ps->image = s->image;
- incref(ps->image);
- ps->fstart = s->fstart;
- ps->flen = s->flen;
- ps->flushme = 1;
-
- return ps;
+ return n;
}
--- a/sys/src/9/port/fault.c
+++ b/sys/src/9/port/fault.c
@@ -58,7 +58,7 @@
}
static void
-faulterror(char *s, Chan *c, int freemem)
+faulterror(char *s, Chan *c, int isfatal)
{
char buf[ERRMAX];
@@ -67,124 +67,16 @@
s = buf;
}
if(up->nerrlab) {
- postnote(up, 1, s, NDebug);
+ if(isfatal)
+ postnote(up, 1, s, NDebug);
error(s);
}
- pexit(s, freemem);
+ pexit(s, 1);
}
-void (*checkaddr)(uintptr, Segment *, Page *);
-uintptr addr2check;
-
-int
-fixfault(Segment *s, uintptr addr, int read, int doputmmu)
+static void
+pio(Segment *s, uintptr addr, uintptr soff, Page **p, int isfatal)
{
- int type;
- Pte **p, *etp;
- uintptr soff, mmuphys=0;
- Page **pg, *old, *new;
- Page *(*fn)(Segment*, uintptr);
-
- addr &= ~(BY2PG-1);
- soff = addr-s->base;
- p = &s->map[soff/PTEMAPMEM];
- if(*p == nil)
- *p = ptealloc();
-
- etp = *p;
- pg = &etp->pages[(soff&(PTEMAPMEM-1))/BY2PG];
- type = s->type&SG_TYPE;
-
- if(pg < etp->first)
- etp->first = pg;
- if(pg > etp->last)
- etp->last = pg;
-
- switch(type) {
- default:
- panic("fault");
- break;
-
- case SG_TEXT: /* Demand load */
- if(pagedout(*pg))
- pio(s, addr, soff, pg);
-
- mmuphys = PPN((*pg)->pa) | PTERONLY|PTEVALID;
- (*pg)->modref = PG_REF;
- break;
-
- case SG_BSS:
- case SG_SHARED: /* Zero fill on demand */
- case SG_STACK:
- if(*pg == nil) {
- new = newpage(1, &s, addr);
- if(s == nil)
- return -1;
- *pg = new;
- }
- goto common;
-
- case SG_DATA:
- common: /* Demand load/pagein/copy on write */
- if(pagedout(*pg))
- pio(s, addr, soff, pg);
-
- /*
- * It's only possible to copy on write if
- * we're the only user of the segment.
- */
- if(read && conf.copymode == 0 && s->ref == 1) {
- mmuphys = PPN((*pg)->pa)|PTERONLY|PTEVALID;
- (*pg)->modref |= PG_REF;
- break;
- }
-
- old = *pg;
- if(old->image == &swapimage && (old->ref + swapcount(old->daddr)) == 1)
- uncachepage(old);
- if(old->ref > 1 || old->image != nil) {
- new = newpage(0, &s, addr);
- if(s == nil)
- return -1;
- *pg = new;
- copypage(old, *pg);
- putpage(old);
- }
- mmuphys = PPN((*pg)->pa) | PTEWRITE | PTEVALID;
- (*pg)->modref = PG_MOD|PG_REF;
- break;
-
- case SG_PHYSICAL:
- if(*pg == nil) {
- fn = s->pseg->pgalloc;
- if(fn)
- *pg = (*fn)(s, addr);
- else {
- new = smalloc(sizeof(Page));
- new->va = addr;
- new->pa = s->pseg->pa+(addr-s->base);
- new->ref = 1;
- *pg = new;
- }
- }
-
- if (checkaddr && addr == addr2check)
- (*checkaddr)(addr, s, *pg);
- mmuphys = PPN((*pg)->pa) |PTEWRITE|PTEUNCACHED|PTEVALID;
- (*pg)->modref = PG_MOD|PG_REF;
- break;
- }
- qunlock(s);
-
- if(doputmmu)
- putmmu(addr, mmuphys, *pg);
-
- return 0;
-}
-
-void
-pio(Segment *s, uintptr addr, uintptr soff, Page **p)
-{
Page *new;
KMap *k;
Chan *c;
@@ -227,22 +119,21 @@
new = newpage(0, 0, addr);
k = kmap(new);
kaddr = (char*)VA(k);
-
while(waserror()) {
- if(strcmp(up->errstr, Eintr) == 0)
+ if(isfatal && strcmp(up->errstr, Eintr) == 0)
continue;
kunmap(k);
putpage(new);
- faulterror(Eioload, c, 0);
+ faulterror(Eioload, c, isfatal);
}
n = devtab[c->type]->read(c, kaddr, ask, daddr);
if(n != ask)
- error(Eioload);
+ error(Eshort);
if(ask < BY2PG)
memset(kaddr+ask, 0, BY2PG-ask);
-
poperror();
kunmap(k);
+
qlock(s);
if(loadrec == nil) { /* This is demand load */
/*
@@ -297,6 +188,115 @@
putpage(new);
if(s->flushme)
memset((*p)->cachectl, PG_TXTFLUSH, sizeof((*p)->cachectl));
+}
+
+void (*checkaddr)(uintptr, Segment *, Page *);
+uintptr addr2check;
+
+int
+fixfault(Segment *s, uintptr addr, int read, int doputmmu)
+{
+ int type;
+ Pte **p, *etp;
+ uintptr soff, mmuphys=0;
+ Page **pg, *old, *new;
+ Page *(*fn)(Segment*, uintptr);
+
+ addr &= ~(BY2PG-1);
+ soff = addr-s->base;
+ p = &s->map[soff/PTEMAPMEM];
+ if(*p == nil)
+ *p = ptealloc();
+
+ etp = *p;
+ pg = &etp->pages[(soff&(PTEMAPMEM-1))/BY2PG];
+ type = s->type&SG_TYPE;
+
+ if(pg < etp->first)
+ etp->first = pg;
+ if(pg > etp->last)
+ etp->last = pg;
+
+ switch(type) {
+ default:
+ panic("fault");
+ break;
+
+ case SG_TEXT: /* Demand load */
+ if(pagedout(*pg))
+ pio(s, addr, soff, pg, doputmmu);
+
+ mmuphys = PPN((*pg)->pa) | PTERONLY|PTEVALID;
+ (*pg)->modref = PG_REF;
+ break;
+
+ case SG_BSS:
+ case SG_SHARED: /* Zero fill on demand */
+ case SG_STACK:
+ if(*pg == nil) {
+ new = newpage(1, &s, addr);
+ if(s == nil)
+ return -1;
+ *pg = new;
+ }
+ goto common;
+
+ case SG_DATA:
+ common: /* Demand load/pagein/copy on write */
+ if(pagedout(*pg))
+ pio(s, addr, soff, pg, doputmmu);
+
+ /*
+ * It's only possible to copy on write if
+ * we're the only user of the segment.
+ */
+ if(read && conf.copymode == 0 && s->ref == 1) {
+ mmuphys = PPN((*pg)->pa)|PTERONLY|PTEVALID;
+ (*pg)->modref |= PG_REF;
+ break;
+ }
+
+ old = *pg;
+ if(old->image == &swapimage && (old->ref + swapcount(old->daddr)) == 1)
+ uncachepage(old);
+ if(old->ref > 1 || old->image != nil) {
+ new = newpage(0, &s, addr);
+ if(s == nil)
+ return -1;
+ *pg = new;
+ copypage(old, *pg);
+ putpage(old);
+ }
+ mmuphys = PPN((*pg)->pa) | PTEWRITE | PTEVALID;
+ (*pg)->modref = PG_MOD|PG_REF;
+ break;
+
+ case SG_PHYSICAL:
+ if(*pg == nil) {
+ fn = s->pseg->pgalloc;
+ if(fn)
+ *pg = (*fn)(s, addr);
+ else {
+ new = smalloc(sizeof(Page));
+ new->va = addr;
+ new->pa = s->pseg->pa+(addr-s->base);
+ new->ref = 1;
+ *pg = new;
+ }
+ }
+
+ if (checkaddr && addr == addr2check)
+ (*checkaddr)(addr, s, *pg);
+ mmuphys = PPN((*pg)->pa) |PTEWRITE|PTEUNCACHED|PTEVALID;
+ (*pg)->modref = PG_MOD|PG_REF;
+ break;
+ }
+ qunlock(s);
+
+ if(doputmmu)
+ putmmu(addr, mmuphys, *pg);
+
+ return 0;
}
/*
--- a/sys/src/9/port/portdat.h
+++ b/sys/src/9/port/portdat.h
@@ -389,8 +389,7 @@
{
Ref;
QLock;
- ushort steal; /* Page stealer lock */
- ushort type; /* segment type */
+ int type; /* segment type */
uintptr base; /* virtual base */
uintptr top; /* virtual top */
ulong size; /* size in pages */
--- a/sys/src/9/port/portfns.h
+++ b/sys/src/9/port/portfns.h
@@ -166,7 +166,7 @@
void* mallocalign(ulong, ulong, long, ulong);
void mallocsummary(void);
Block* mem2bl(uchar*, int);
-int mcountseg(Segment*);
+ulong mcountseg(Segment*);
void mfreeseg(Segment*, uintptr, int);
void microdelay(int);
uvlong mk64fract(uvlong, uvlong);
@@ -214,7 +214,6 @@
void pgrpcpy(Pgrp*, Pgrp*);
void pgrpnote(ulong, char*, long, int);
int pidalloc(Proc*);
-void pio(Segment *, uintptr, uintptr, Page **);
#define poperror() up->nerrlab--
void portcountpagerefs(ulong*, int);
int postnote(Proc*, int, char*, int);
@@ -380,6 +379,7 @@
Segment* dupseg(Segment**, int, int);
Segment* newseg(int, uintptr, ulong);
Segment* seg(Proc*, uintptr, int);
+Segment* txt2data(Segment*);
void hnputv(void*, uvlong);
void hnputl(void*, uint);
void hnputs(void*, ushort);
--- a/sys/src/9/port/proc.c
+++ b/sys/src/9/port/proc.c
@@ -1510,7 +1510,7 @@
s = p->seg[i];
if(s == nil || !canqlock(s))
continue;
- l += (ulong)mcountseg(s);
+ l += mcountseg(s);
qunlock(s);
}
qunlock(&p->seglock);
--- a/sys/src/9/port/segment.c
+++ b/sys/src/9/port/segment.c
@@ -452,10 +452,11 @@
/*
* called with s locked
*/
-int
+ulong
mcountseg(Segment *s)
{
- int i, j, pages;
+ ulong pages;
+ int i, j;
Page *pg;
pages = 0;
@@ -736,3 +737,33 @@
}
}
+Segment*
+txt2data(Segment *s)
+{
+ Segment *ps;
+
+ ps = newseg(SG_DATA, s->base, s->size);
+ ps->image = s->image;
+ incref(ps->image);
+ ps->fstart = s->fstart;
+ ps->flen = s->flen;
+ ps->flushme = 1;
+ qunlock(s);
+ putseg(s);
+ qlock(ps);
+ return ps;
+}
+
+Segment*
+data2txt(Segment *s)
+{
+ Segment *ps;
+
+ ps = newseg(SG_TEXT, s->base, s->size);
+ ps->image = s->image;
+ incref(ps->image);
+ ps->fstart = s->fstart;
+ ps->flen = s->flen;
+ ps->flushme = 1;
+ return ps;
+}
--- a/sys/src/9/port/swap.c
+++ b/sys/src/9/port/swap.c
@@ -222,11 +222,6 @@
if(!canqlock(s)) /* We cannot afford to wait, we will surely deadlock */
return;
- if(s->steal) { /* Protected by /dev/proc */
- qunlock(s);
- return;
- }
-
if(!canflush(p, s)) { /* Able to invalidate all tlbs with references */
qunlock(s);
putseg(s);