jehanne/sys/src/kern/port/umem/segments.c

742 lines
18 KiB
C

/*
* This file is part of Jehanne.
*
* Copyright (C) 2016 Giacomo Tesio <giacomo@tesio.it>
*
* Jehanne is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 2 of the License.
*
* Jehanne is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Jehanne. If not, see <http://www.gnu.org/licenses/>.
*/
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"
#include "../port/umem/internals.h"
#include "init.h"
char *fault_types[] = {
[FaultWrite] "write",
[FaultRead] "read",
[FaultExecute] "exec"
};
char *segment_types[SegmentTypesMask+1]={
[SgLoad] "Load",
[SgBSS] "Bss",
[SgStack] "Stack",
[SgShared] "Shared",
[SgPhysical] "Physical",
};
char *
segment_name(ProcSegment *s)
{
if(s == nil)
panic("segment_name: nil segment, pc %#p", getcallerpc());
switch(s->type){
case SgLoad:
if(s->image % NLOAD)
return "Text";
else
return "Data";
case SgBSS:
case SgStack:
case SgShared:
case SgPhysical:
return segment_types[s->type];
default:
panic("segment_name: unknown segment type %d, pc %#p", s->type, getcallerpc());
}
}
PagePointer
segment_page(ProcSegment* s, uintptr_t va)
{
PagePointer* page;
if(s == nil)
panic("proc_segment: nil process, pc %#p", getcallerpc());
page = table_lookup(s->table, va);
return *page;
}
static ProcSegment*
segment_alloc(SegmentType type, SegPermission permissions, SegFlag flags, uintptr_t base, uintptr_t top)
{
ProcSegment* s;
s = jehanne_mallocz(sizeof(ProcSegment), 1);
if(s == nil)
return nil;
s->r.ref = 1;
s->type = type;
s->permissions = permissions;
s->flags = flags;
s->base = base;
s->top = top;
if(type != SgPhysical /* Physical segments do not need pages */
&& !table_new(&s->table, base, top)){
jehanne_free(s);
return nil;
}
s->sema.prev = &s->sema;
s->sema.next = &s->sema;
return s;
}
/* this constructor is for exclusive use of main.c */
int
segment_userinit(ProcSegment** slot, int data)
{
static int text_initialized;
static int data_initialized;
static int called;
ProcSegment* new;
PagePointer *page;
SegPermission permissions;
uintptr_t base, top;
char *content, *tmp;
int i, shared;
if(called > 2 || (text_initialized && data_initialized))
panic("segment_userinit: third call, pc %#p", getcallerpc());
if(slot == nil)
panic("segment_userinit: nil slot, pc %#p", getcallerpc());
if(*slot != nil)
panic("segment_userinit: dirty slot, pc %#p", getcallerpc());
++called;
if(data){
++data_initialized;
permissions = SgRead|SgWrite;
base = init_data_start;
top = init_data_end;
content = (char*)init_data_out;
} else {
++text_initialized;
permissions = SgRead|SgExecute;
base = init_code_start;
top = init_code_end;
content = (char*)init_code_out;
}
shared = ROUNDUP(top - base, PGSZ)/PGSZ;
new = segment_alloc(
SgLoad,
permissions,
0,
base,
top
);
if(new == nil)
return 0;
for(i = 0; i < shared; ++i){
page = table_lookup(new->table, base+(i*PGSZ));
if(!page_new(page, i == (shared-1)))
panic("segment_userinit: out of memory, pc %#p", getcallerpc());
tmp = page_kmap(*page);
jehanne_memmove(tmp, content+i*PGSZ, MIN(PGSZ, top-base-i*PGSZ));
page_kunmap(*page, &tmp);
}
new->image = data;
if(!CASV(slot, nil, new))
panic("segment_userinit: slot got dirty, pc %#p", getcallerpc());
return 1;
}
int
segment_load(ProcSegment** slot, ElfSegPointer segment, Ldseg* elfinfo)
{
ProcSegment* new;
SegPermission permissions;
uintptr_t top;
if(slot == nil)
panic("segment_load: nil slot, pc %#p", getcallerpc());
if(*slot != nil)
panic("segment_load: dirty slot, pc %#p", getcallerpc());
if(elfinfo == nil)
panic("segment_load: nil elfinfo, pc %#p", getcallerpc());
switch(segment % NLOAD){
case 0:
/* text segment */
permissions = SgRead|SgExecute;
top = elfinfo->pg0vaddr + elfinfo->pg0off + elfinfo->memsz;
break;
case 1:
/* data segment
* we use filesz instead of memsz to keep bss in a
* different segment...
*/
permissions = SgRead|SgWrite;
top = elfinfo->pg0vaddr + elfinfo->pg0off + elfinfo->filesz;
/* ...but rounded to PGSZ */
top = ROUNDUP(top, PGSZ);
break;
default:
panic("segment_load: unknown segment subtype %d, pc %#p", segment%NLOAD, getcallerpc());
}
new = segment_alloc(
SgLoad,
permissions,
0,
elfinfo->pg0vaddr + elfinfo->pg0off,
top
);
if(new == nil)
return 0;
elfsegment_assign(&new->image, segment);
if(!CASV(slot, nil, new))
panic("segment_load: slot got dirty, pc %#p", getcallerpc());
return 1;
}
int
segment_physical(ProcSegment** slot, SegPermission permissions, SegFlag flags, uintptr_t va, uintptr_t pa)
{
ProcSegment* new;
uintptr_t mpa;
unsigned int mattr;
uintptr_t size;
if(slot == nil)
panic("segment_physical: nil slot, pc %#p", getcallerpc());
if(*slot != nil)
panic("segment_physical: dirty slot, pc %#p", getcallerpc());
if(pa == 0)
panic("segment_physical: zero pa, pc %#p", getcallerpc());
if(!rawmem_lookup(pa, nil, &mpa, &mattr, &size))
panic("segment_physical: no physical memory area at %#p, pc %#p", pa, getcallerpc());
if(va == 0)
panic("segment_physical: zero va, pc %#p", getcallerpc());
va = va & ~(PGSZ-1);
new = segment_alloc(
SgPhysical,
permissions&SegPermissionMask&(~SgExecute),
flags&SegFlagMask,
va,
ROUNDUP(va + size, PGSZ)
);
if(new == nil)
return 0;
new->pmem = mpa;
if(!CASV(slot, nil, new))
panic("segment_load: slot got dirty, pc %#p", getcallerpc());
return 1;
}
int
segment_global(ProcSegment** slot, SegFlag flags, uintptr_t va, char *name)
{
ProcSegment* new;
unsigned int mattr;
uintptr_t size;
SegmentType type;
if(slot == nil)
panic("segment_global: nil slot, pc %#p", getcallerpc());
if(*slot == nil)
panic("segment_global: dirty slot, pc %#p", getcallerpc());
if(name == nil)
panic("segment_global: nil name, pc %#p", getcallerpc());
if(!rawmem_find(&name, nil, &mattr, &size))
return 0;
type = mattr&SegmentTypesMask;
if(type != SgShared && type != SgBSS)
if(type > SgPhysical)
panic("segment_global: unknown segment type %d for memory area '%s', pc %#p", type, name, getcallerpc());
else
panic("segment_global: type %s for memory area '%s' not allowed, pc %#p", segment_types[type], name, getcallerpc());
new = segment_alloc(
type,
mattr&SegPermissionMask&(~SgExecute),
flags&mattr&SegFlagMask,
va,
va + size*PGSZ
);
if(new == nil)
return 0;
new->vmem = name;
if(!CASV(slot, nil, new))
panic("segment_load: slot got dirty, pc %#p", getcallerpc());
return 1;
}
int
segment_virtual(ProcSegment** slot, SegmentType type, SegPermission permissions, SegFlag flags, uintptr_t base, uintptr_t top)
{
ProcSegment* new;
if(slot == nil)
panic("segment_virtual: nil slot, pc %#p", getcallerpc());
if(*slot != nil)
panic("segment_virtual: dirty slot, pc %#p", getcallerpc());
if(type == SgLoad || type == SgPhysical)
panic("segment_virtual: type %s, pc %#p", segment_types[type], getcallerpc());
if(type > SgPhysical)
panic("segment_virtual: unknown type %d, pc %#p", type, getcallerpc());
if(permissions & SgExecute)
panic("segment_virtual: executable segment, pc %#p", segment_types[type], getcallerpc());
if(top < base)
panic("segment_virtual: top < base, pc %#p", getcallerpc());
new = segment_alloc(
type,
permissions&SegPermissionMask&(~SgExecute),
flags&SegFlagMask,
base,
top
);
if(new == nil)
return 0;
if(!CASV(slot, nil, new))
panic("segment_load: slot got dirty, pc %#p", getcallerpc());
return 1;
}
static int
segment_fault_physical(uintptr_t *mmuphys, uintptr_t *va, ProcSegment* segment, FaultType type)
{
uintptr_t pa, mmuflags;
switch(type){
default:
panic("segment_fault_text: unknown fault type %d", type);
case FaultExecute:
return 0;
case FaultRead:
case FaultWrite:
if(!rawmem_lookup(segment->pmem, nil, &pa, nil, nil))
return 0;
pa += (*va - segment->base);
mmuflags = PTEVALID;
if((segment->permissions & SgWrite))
mmuflags |= PTEWRITE;
else
mmuflags |= PTERONLY;
if((segment->flags & SgCached) == 0)
mmuflags |= PTEUNCACHED;
*mmuphys = PPN(pa) | mmuflags;
MLG("segment %#p, fault %s *page %ud mmuflags %#p *mmuphys %#p", segment, fault_types[type], mmuflags, *mmuphys);
return 1;
}
}
int
segment_fault(uintptr_t *mmuphys, uintptr_t *va, ProcSegment* segment, FaultType type)
{
PagePointer *page;
SegmentType stype;
if(nil == mmuphys)
panic("segment_fault: nil mmuphys, pc %#p", getcallerpc());
if(nil == va)
panic("segment_fault: nil va, pc %#p", getcallerpc());
if(nil == segment)
panic("segment_fault: nil segment, pc %#p", getcallerpc());
if((segment->permissions&((SegPermission)type)) == 0)
return 0;
MLG("mmuphys %#p, *va %#p segment %#p fault %s", mmuphys, *va, segment, fault_types[type]);
*va = *va&~(PGSZ-1);
stype = segment->type;
if(stype == SgPhysical)
return segment_fault_physical(mmuphys, va, segment, type);
page = table_lookup(segment->table, *va);
if(page == nil)
return 0;
switch(stype){
default:
panic("segment_fault: unknown segment type %d, pc %#p", type, getcallerpc());
case SgLoad:
if(*page == 0){
if(!image_fill(page, segment->image, *va)){
if(*page == 0)
return 0; /* out of memory */
if(segment->image%NLOAD == 0 /* text segment */
&&(segment->permissions&SgWrite)){ /* debug mode */
/* each writable text segment belongs to
* a single process (see segment_debug)
* thus it's not possible that a fault
* from another process sharing this
* segment could have turn *page in a
* writable copy already
*/
panic("segment_fault: race on image_fill (text va %#p)", *va);
}
}
}
if(segment->image % NLOAD)
goto DataSegment;
if(segment->permissions&SgWrite){
/* debuggable text segment */
if(!page_duplicate(page))
return 0;
*mmuphys = PPN(page_pa(*page)) | PTEWRITE|PTEVALID;
} else
*mmuphys = PPN(page_pa(*page)) | PTERONLY|PTEVALID;
break;
case SgBSS:
case SgShared:
case SgStack:
if(*page == 0){
if(!page_new(page, 1) && *page == 0)
return 0;
}
DataSegment:
qlock(&segment->ql);
if(type == FaultRead && sys->copymode == 0 && segment->r.ref == 1) {
*mmuphys = PPN(page_pa(*page))|PTERONLY|PTEVALID;
} else {
if(!page_duplicate_shared(page)){
qunlock(&segment->ql);
return 0;
}
*mmuphys = PPN(page_pa(*page))|PTEWRITE|PTEVALID;
}
qunlock(&segment->ql);
break;
}
return 1;
}
static void
segment_info(ProcSegment* segment, Ldseg *info)
{
info->memsz = segment->top - segment->base;
info->filesz = segment->top - segment->base;
info->pg0vaddr = segment->base & ~(PGSZ-1);
info->pg0off = segment->base & (PGSZ-1);
info->pgsz = PGSZ;
}
void
segment_release(ProcSegment** s)
{
ProcSegment *segment;
if(nil == s)
panic("segment_release: nil segment pointer, pc %#p", getcallerpc());
segment = xchgm(s, nil);
if(nil == segment)
panic("segment_release: nil segment, pc %#p", getcallerpc());
MLG("segment %#p segment->type %d, ref %d", segment, segment->type, segment->r.ref);
if(decref(&segment->r) > 0)
return;
switch(segment->type){
default:
panic("segment_release: unknown segment type %d, pc %#p", segment->type, getcallerpc());
case SgLoad:
if(segment->image > 1
||(up->parentpid == 1 && up->text[0] != '*'))
image_release(segment->image/NLOAD);
/* fall through */
case SgBSS:
case SgShared:
case SgStack:
table_free(&segment->table);
break;
case SgPhysical:
break;
}
jehanne_free(segment);
}
/* replaces the segment in s with a copy with debug enabled/disabled
*
* a debuggable text segment is just a text segment with SgWrite permission
* see
* http://eli.thegreenplace.net/2011/01/27/how-debuggers-work-part-2-breakpoints
* http://www.alexonlinux.com/how-debugger-works
*
* segment_debug take a ProcSegment** because, by replacing the pointer
* in the process' segment table it ensure that only the process
* being debugged will see the breakpoints.
*/
/* assumes proc->seglock wlocked */
int
segment_debug(ProcSegment** s, int* enable)
{
ProcSegment *segment, *new;
SegPermission p;
Ldseg info;
if(nil == s)
panic("segment_debug: nil segment pointer, pc %#p", getcallerpc());
segment = *s;
if(nil == segment)
panic("segment_debug: nil segment, pc %#p", getcallerpc());
if(segment->type != SgLoad || segment->image % NLOAD != 0)
panic("segment_debug: not a text segment, pc %#p", getcallerpc());
p = segment->permissions;
if(enable == nil)
return (p&SgWrite) ? 1 : 0;
if(*enable && (p&SgWrite))
return 0; /* already enabled */
if(*enable == 0 && (p&SgWrite) == 0)
return 0; /* already disabled */
new = nil;
segment_info(segment, &info);
if(!segment_load(&new, segment->image, &info))
return 0;
if(*enable)
new->permissions &= SgWrite;
*s = new;
segment_release(&segment);
return 1;
}
/* assumes proc->seglock wlocked */
int
segment_share(ProcSegment **s)
{
ProcSegment *segment, *new;
Ldseg info;
if(nil == s)
panic("segment_share: nil segment pointer, pc %#p", getcallerpc());
segment = *s;
if(nil == segment)
panic("segment_share: nil segment, pc %#p", getcallerpc());
new = nil;
switch(segment->type){
default:
panic("segment_share: unknown segment type %d, pc %#p", segment->type, getcallerpc());
case SgLoad:
if(segment->image % NLOAD == 0
&& segment->permissions&SgWrite){
/* text segment in debug mode: on sys_rfork(whatever)
* it is going to be copied as a readonly
*/
segment_info(segment, &info);
if(!segment_load(&new, segment->image, &info))
return 0;
*s = new;
MLG("segment %#p Text in DEBUG ref %d FORKED", segment, segment->r.ref);
return 1;
}
break;
case SgStack:
/* stack is always private */
if(!segment_virtual(&new, SgStack, segment->permissions, segment->flags, segment->base, segment->top))
return 0;
qlock(&segment->ql);
if(!table_clone(new->table, segment->table)){
qunlock(&segment->ql);
segment_release(&new);
return 0;
}
qunlock(&segment->ql);
if(segment->r.ref > 1)
procs_flush_segment(segment);
*s = new;
MLG("segment %#p segment->type %s ref %d FORKED", segment, segment_name(segment), segment->r.ref);
return 1;
case SgShared:
case SgPhysical:
case SgBSS:
break;
}
incref(&segment->r);
MLG("segment %#p segment->type %d ref %d ACTUALLY SHARED", segment, segment->type, segment->r.ref);
return 1;
}
/* assumes proc->seglock wlocked */
int
segment_fork(ProcSegment **s)
{
ProcSegment *segment, *new;
Ldseg info;
if(nil == s)
panic("segment_fork: nil segment pointer, pc %#p", getcallerpc());
segment = *s;
if(nil == segment)
panic("segment_fork: nil segment, pc %#p", getcallerpc());
new = nil;
switch(segment->type){
case SgLoad:
if(segment->image % NLOAD != 0){
/* data segment */
segment_info(segment, &info);
if(!segment_load(&new, segment->image, &info))
return 0;
goto CloneTable;
}
if(segment->permissions & SgWrite){
/* text in debug mode, on rfork a new
* readonly segment is created
*/
segment_info(segment, &info);
if(!segment_load(&new, segment->image, &info))
return 0;
*s = new;
MLG("segment %#p Text in DEBUG ref %d FORKED", segment, segment->r.ref);
return 1;
}
/* readonly text segments are shared */
break;
case SgBSS:
case SgStack:
if(!segment_virtual(&new, segment->type, segment->permissions, segment->flags, segment->base, segment->top))
return 0;
CloneTable:
qlock(&segment->ql);
if(!table_clone(new->table, segment->table)){
qunlock(&segment->ql);
segment_release(&new);
return 0;
}
qunlock(&segment->ql);
if(segment->r.ref > 1)
procs_flush_segment(segment);
*s = new;
MLG("segment %#p %s ref %d ACTUALLY FORKED", segment, segment_name(segment), segment->r.ref);
return 1;
case SgShared:
case SgPhysical:
break;
}
MLG("segment %#p %s ref %d SHARED", segment, segment_name(segment), segment->r.ref);
incref(&segment->r);
return 1;
}
/* assumes s->ql locked */
int
segment_grow(ProcSegment *s, uintptr_t top)
{
int res = 0;
if(nil == s)
panic("segment_grow: nil segment, pc %#p", getcallerpc());
if(top == s->top)
return 1;
if(top > s->top)
res = table_resize(&s->table, top);
if(res)
s->top = ROUNDUP(top, PGSZ);
return res;
}
void
segment_free_pages(ProcSegment *s, uintptr_t from, uintptr_t to)
{
uintptr_t va;
PagePointer *page;
if(s == 0)
panic("segment_free_pages: nil segment, pc %#p", getcallerpc());
if(from >= to)
panic("segment_free_pages: empty page set, pc %#p", getcallerpc());
if(s->type == SgPhysical)
return;
qlock(&s->ql);
if(s->r.ref > 1)
procs_flush_segment(s);
for(va = from; va < to; va += PGSZ){
page = table_lookup(s->table, va);
if(*page != 0)
page_dispose(page);
}
qunlock(&s->ql);
}
void
segment_relocate(ProcSegment *s, uintptr_t newbase, uintptr_t newtop)
{
if(s == 0)
panic("segment_relocate: nil segment, pc %#p", getcallerpc());
if(s->top - s->base != newtop - newbase)
panic("segment_relocate: change in size %ulld (old) != %ulld (new), pc %#p", s->top - s->base, newtop - newbase, getcallerpc());
if(s->r.ref > 1)
panic("segment_relocate: relocating shared segment, pc %#p", getcallerpc());
qlock(&s->ql);
s->base = newbase;
s->top = newtop;
s->table->base = newbase;
qunlock(&s->ql);
}
ProcSegment*
proc_segment(Proc *p, uintptr_t va)
{
int i;
ProcSegment *s = nil;
if(p == nil)
panic("proc_segment: nil process, pc %#p", getcallerpc());
rlock(&p->seglock);
for(i = 0; i < NSEG; ++i){
s = p->seg[i];
if(s && s->base <= va && va < s->top)
break;
}
runlock(&p->seglock);
if(i == NSEG)
return nil;
return s;
}
int
proc_segment_detach(Proc *p, uintptr_t va)
{
int i;
ProcSegment *s = nil;
if(p == nil)
panic("proc_segment_detach: nil process, pc %#p", getcallerpc());
wlock(&p->seglock);
for(i = 0; i < NSEG; ++i){
s = p->seg[i];
if(s && s->base <= va && va < s->top){
segment_release(&p->seg[i]);
break;
}
}
wunlock(&p->seglock);
if(i == NSEG)
return 0;
return 1;
}
static void
check_single_page(uintptr_t va, UserPage* page)
{
if(!iskaddr(page))
jehanne_print("%d %s: invalid page off %#p pg %#p\n", up->pid, up->text, va, page);
else
checkmmu(va, page->pa);
}
void
proc_check_pages(void)
{
ProcSegment **sp, **ep, *s;
if(up == nil || up->newtlb)
return;
rlock(&up->seglock);
for(sp=up->seg, ep=&up->seg[NSEG]; sp<ep; sp++){
s = *sp;
if(s == nil || s->table == nil)
continue;
table_walk_pages(s->table, check_single_page);
}
runlock(&up->seglock);
}