Index: lib/sanitizer_common/sanitizer_procmaps_linux.cc =================================================================== --- lib/sanitizer_common/sanitizer_procmaps_linux.cc +++ lib/sanitizer_common/sanitizer_procmaps_linux.cc @@ -22,14 +22,169 @@ #include #endif +// On x86-64 FreeBSD prior to v9.2 and v10.0 sysctl(KERN_PROC_VMMAP) takes +// too long to perform so we have to use a workaround that reads '/dev/kmem' +// directly. +#define SANITIZER_USE_FREEBSD_MMAP_WORKAROUND \ + (SANITIZER_FREEBSD && SANITIZER_WORDSIZE == 64) + +#if SANITIZER_USE_FREEBSD_MMAP_WORKAROUND +#define _KVM_VNODE // To declare 'struct vnode'. +#include +#include +#endif + namespace __sanitizer { // Linker initialized. ProcSelfMapsBuff MemoryMappingLayout::cached_proc_self_maps_; StaticSpinMutex MemoryMappingLayout::cache_lock_; // Linker initialized. +#if SANITIZER_USE_FREEBSD_MMAP_WORKAROUND +static void HandleKmemFileError(const char *func) { + Report("ERROR: %s('/dev/kmem') failed, errno %d\n", func, errno); + Die(); +} + +static uptr OpenKmemFile() { + uptr Fd = OpenFile("/dev/kmem", /* write= */ false); + if (internal_iserror(Fd)) + HandleKmemFileError("open"); + return Fd; +} + +static void CloseKmemFile(int fd) { + if (internal_close(fd) != 0) + HandleKmemFileError("close"); +} + +static void ReadKmemFile(void *dest, int fd, const void *addr, size_t size) { + errno = 0; + lseek(fd, (off_t) addr, SEEK_SET); + // Since 'p' may have its high-order bit raised, the returning value may be + // negative; so, we check 'errno' instead of considering returning value. + if (errno != 0) + HandleKmemFileError("seek"); + + if (read(fd, dest, size) != (ssize_t) size) + HandleKmemFileError("read"); +} + +// From /usr/src/sys/kern/vfs_cache.c . +struct namecache { + LIST_ENTRY(namecache) nc_hash; // hash chain + LIST_ENTRY(namecache) nc_src; // source vnode list + TAILQ_ENTRY(namecache) nc_dst; // destination vnode list + struct vnode *nc_dvp; // vnode of parent of name + struct vnode *nc_vp; // vnode the name refers to + u_char nc_flag; // flag bits + u_char nc_nlen; // length of name + char nc_name[1]; // segment name + NUL +}; + +static void ReadVmEntryFilePath(char *path, size_t path_size, + const struct vm_map_entry *entry) { + CHECK_NE(path, NULL); + CHECK_GT(path_size, 0); + + internal_memset((void*)path, '\0', path_size); + + // Path buffer size except the terminating NUL character. + const size_t PathRoom = path_size - 1; + if (PathRoom == 0) + return; + + const struct vm_object *OP = entry->object.vm_object; + if (OP == NULL) + return; + + uptr Fd = OpenKmemFile(); + + struct vm_object Object; + while (OP != NULL) { + ReadKmemFile(&Object, Fd, OP, sizeof(Object)); + OP = Object.backing_object; + } + + if (Object.type != OBJT_VNODE) + return; + + struct vnode *VP = (struct vnode*)Object.handle; + struct vnode Vnode; + struct namecache Namecache; + + while (VP != NULL) { + ReadKmemFile(&Vnode, Fd, VP, sizeof(Vnode)); + + struct namecache *Data = (struct namecache*)TAILQ_FIRST(&Vnode.v_cache_dst); + if (Data == NULL) + break; + + ReadKmemFile(&Namecache, Fd, Data, sizeof(Namecache)); + + size_t Len = Namecache.nc_nlen; + if (Len > 0) { + Len = Min(Len, PathRoom) + 1; // '+ 1' is for leading '/'. + if (Len < PathRoom) + internal_memmove((void*)(path + Len), path, PathRoom - Len); + + path[0] = '/'; + ReadKmemFile((void*)(path + 1), Fd, &Data->nc_name, Len - 1); + } + + VP = Namecache.nc_dvp; + } + + CloseKmemFile(Fd); +} +#endif + static void ReadProcMaps(ProcSelfMapsBuff *proc_maps) { -#if SANITIZER_FREEBSD +#if SANITIZER_USE_FREEBSD_MMAP_WORKAROUND + // Get process VM map. + const pid_t Pid = getpid(); + const int Mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, Pid }; + struct kinfo_proc Proc; + size_t ProcSize = sizeof(struct kinfo_proc); + int Err = sysctl(Mib, 4, &Proc, &ProcSize, NULL, 0); + CHECK_EQ(Err, 0); + CHECK_EQ(ProcSize, sizeof(struct kinfo_proc)); + + // Count VM entries. + uptr Fd = OpenKmemFile(); + const struct vm_map *KmemMap = &Proc.ki_vmspace->vm_map; + const struct vm_map_entry *Header = &KmemMap->header; + const struct vm_map_entry *EP; + struct vm_map_entry E; + + size_t Count = 0; + EP = Header; + do { + ReadKmemFile(&E, Fd, EP, sizeof(E)); + Count++; + } while ((EP = E.next) != Header); + size_t Size = Count * sizeof(struct vm_map_entry); + + // Load VM entries. + size_t AllocatedCount = Count + 3; + size_t AllocatedSize = AllocatedCount * sizeof(struct vm_map_entry); + struct vm_map_entry *VmMap = + (struct vm_map_entry*)MmapOrDie(AllocatedSize, "ReadProcMaps()"); + size_t I = 0; + EP = Header; + do { + ReadKmemFile(&E, Fd, EP, sizeof(E)); + CHECK_LT(I, AllocatedCount); + VmMap[I++] = E; + } while ((EP = E.next) != Header); + CHECK_LE(I, AllocatedCount); + + proc_maps->data = (char*)VmMap; + proc_maps->mmaped_size = AllocatedSize; + proc_maps->len = Size; + + CloseKmemFile(Fd); +#elif SANITIZER_FREEBSD const int Mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, getpid() }; size_t Size = 0; int Err = sysctl(Mib, 4, NULL, &Size, NULL, 0); @@ -167,7 +322,26 @@ if (!end) end = &dummy; if (!offset) offset = &dummy; if (!protection) protection = &dummy; -#if SANITIZER_FREEBSD +#if SANITIZER_USE_FREEBSD_MMAP_WORKAROUND + const struct vm_map_entry *VmEntry = (struct vm_map_entry*)current_; + + *start = VmEntry->start; + *end = VmEntry->end; + *offset = VmEntry->offset; + + *protection = 0; + if ((VmEntry->protection & VM_PROT_READ) != 0) + *protection |= kProtectionRead; + if ((VmEntry->protection & VM_PROT_WRITE) != 0) + *protection |= kProtectionWrite; + if ((VmEntry->protection & VM_PROT_EXECUTE) != 0) + *protection |= kProtectionExecute; + + if (filename != NULL && filename_size > 0) + ReadVmEntryFilePath(filename, filename_size, VmEntry); + + current_ += sizeof(struct vm_map_entry); +#elif SANITIZER_FREEBSD struct kinfo_vmentry *VmEntry = (struct kinfo_vmentry*)current_; *start = (uptr)VmEntry->kve_start;