// Which file each part of an address space came from. // // mmap of a file copies it into anonymous memory (see sys_mem.c), so the page // tables and the vma records know nothing about files. This remembers the // name beside them -- for /proc//maps, and so that a process killed by a // fault is reported with the library and offset it died in rather than a bare // address. There is no ptrace here and so no debugger: Epiphany crashing at // "rip 0x8a094bea" in a library loaded with dlopen was a dead end without it. // // A dynamic linker maps each library as one span from offset 0 and then // places its segments inside with MAP_FIXED; those inner maps of the same // file are absorbed, so a library is one entry. Only diagnostics read this, // and it is allowed to be approximate at the edges: a span unmapped in part // keeps its name. #include "core/types.h" #include "core/defs.h" #include "core/param.h" #include "arch/memlayout.h" #include "arch/mmu.h" #include "core/spinlock.h" #include "core/sleeplock.h" #include "core/proc.h" #include "core/kmem.h" struct mapname { struct mapname *next; uint64 start, end, off; char path[96]; }; static struct spinlock mnlock; static int mnlock_ready; static void mn_lock(void) { if (!mnlock_ready) { initlock(&mnlock, "mapname"); mnlock_ready = 1; } acquire(&mnlock); } // Forget names wholly inside [start, end), and trim those hanging over an // edge of it. A name the range falls inside stays whole. static void mn_cut(struct mm *m, uint64 start, uint64 end) { struct mapname **pp = &m->names; while (*pp) { struct mapname *n = *pp; if (n->start >= start && n->end <= end) { *pp = n->next; kmfree(n); continue; } if (n->start < start && n->end > start && n->end <= end) n->end = start; else if (n->start >= start && n->start < end && n->end > end) { n->off += end - n->start; n->start = end; } pp = &n->next; } } void mapname_add(struct mm *m, uint64 start, uint64 len, uint64 off, const char *path) { struct mapname *n; if (!path || !*path) return; mn_lock(); for (n = m->names; n; n = n->next) if (n->start <= start && start + len <= n->end && strncmp(n->path, path, sizeof(n->path) - 1) == 0) { release(&mnlock); return; // a segment of a library already named } mn_cut(m, start, start + len); release(&mnlock); if ((n = kmalloc(sizeof(*n))) == 0) return; n->start = start; n->end = start + len; n->off = off; safestrcpy(n->path, path, sizeof(n->path)); mn_lock(); n->next = m->names; m->names = n; release(&mnlock); } void mapname_unmap(struct mm *m, uint64 start, uint64 len) { mn_lock(); mn_cut(m, start, start + len); release(&mnlock); } void mapname_free(struct mm *m) { mn_lock(); struct mapname *n = m->names; m->names = 0; release(&mnlock); while (n) { struct mapname *next = n->next; kmfree(n); n = next; } } // fork: the child's address space is the parent's, names and all. void mapname_copy(struct mm *dst, struct mm *src) { struct mapname *n; mn_lock(); for (n = src->names; n; n = n->next) { struct mapname *c = kmalloc(sizeof(*c)); if (!c) break; *c = *n; c->next = dst->names; dst->names = c; } release(&mnlock); } // The name addr falls in and its offset in the file, copied out. 0 if found. int mapname_find(struct mm *m, uint64 addr, char *out, int cap, uint64 *off) { int r = -1; mn_lock(); for (struct mapname *n = m->names; n; n = n->next) if (n->start <= addr && addr < n->end) { safestrcpy(out, n->path, cap); *off = n->off + (addr - n->start); r = 0; break; } release(&mnlock); return r; } static int put(char *buf, int n, int cap, const char *s) { while (*s && n < cap - 1) buf[n++] = *s++; return n; } static int puthex(char *buf, int n, int cap, uint64 v, int width) { char t[17]; int i = 16; t[16] = 0; do { t[--i] = "0123456789abcdef"[v & 15]; v >>= 4; } while (v && i > 0); while (16 - i < width && i > 0) t[--i] = '0'; return put(buf, n, cap, t + i); } // /proc//maps, in Linux's layout. Named spans and the anonymous records // beside them; permissions are not tracked per page here, so every line // says rw and private, and only the names are to be trusted. int mapname_gen(struct mm *m, char *buf, int cap) { int n = 0; mn_lock(); for (struct mapname *e = m->names; e; e = e->next) { n = puthex(buf, n, cap, e->start, 8); n = put(buf, n, cap, "-"); n = puthex(buf, n, cap, e->end, 8); n = put(buf, n, cap, " r-xp "); n = puthex(buf, n, cap, e->off, 8); n = put(buf, n, cap, " 00:00 0 "); n = put(buf, n, cap, e->path); n = put(buf, n, cap, "\n"); } release(&mnlock); for (int i = 0; i < NVMA; i++) { struct vma *v = &m->vma[i]; if (!v->used) continue; n = puthex(buf, n, cap, v->addr, 8); n = put(buf, n, cap, "-"); n = puthex(buf, n, cap, v->addr + v->length, 8); n = put(buf, n, cap, v->shmid >= 0 ? " rw-s 00000000 00:00 0 [memfd]\n" : " rw-p 00000000 00:00 0\n"); } return n; } // Said when a process dies of a fault: where its instruction pointer was, // and where the return address on top of its stack points, by file. The // second is the caller, which is what is left to go on when the first is in // generated code. void mapname_report(struct proc *p) { char path[96]; uint64 off, ret = 0; if (!p->mm || !p->tf) return; if (mapname_find(p->mm, p->tf->rip, path, sizeof(path), &off) == 0) cprintf("pid %d %s: rip %p is %s+0x%lx\n", p->pid, p->name, (void *)p->tf->rip, path, off); else cprintf("pid %d %s: rip %p is in no file\n", p->pid, p->name, (void *)p->tf->rip); if (fetchaddr(p->tf->rsp, &ret) == 0 && mapname_find(p->mm, ret, path, sizeof(path), &off) == 0) cprintf("pid %d %s: [rsp] %p is %s+0x%lx\n", p->pid, p->name, (void *)ret, path, off); }