Bug 608239: Backout changeset 99233ad2ff70 a=fennec-should-load-pages
authorMark Finkle <mfinkle@mozilla.com>
Fri, 29 Oct 2010 10:07:54 -0400
changeset 56683 1cf4edfb9525b7df05b38e413edd77dd94695271
parent 56682 799a83ba5f8637d6bbf730ce89ab89fbf33c4477
child 56684 3b73e3b886531fae356cbbeebcf5d70023cff7f9
push idunknown
push userunknown
push dateunknown
reviewersfennec-should-load-pages
bugs608239
milestone2.0b8pre
Bug 608239: Backout changeset 99233ad2ff70 a=fennec-should-load-pages
ipc/glue/GeckoChildProcessHost.cpp
ipc/glue/SharedMemoryBasic_android.cpp
other-licenses/android/APKOpen.cpp
other-licenses/android/APKOpen.h
other-licenses/android/linker.c
toolkit/xre/nsAndroidStartup.cpp
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -64,20 +64,16 @@
 #include "mozilla/Omnijar.h"
 #include <sys/stat.h>
 
 #ifdef XP_WIN
 #include "nsIWinTaskbar.h"
 #define NS_TASKBAR_CONTRACTID "@mozilla.org/windows-taskbar;1"
 #endif
 
-#ifdef ANDROID
-#include "APKOpen.h"
-#endif
-
 using mozilla::MonitorAutoEnter;
 using mozilla::ipc::GeckoChildProcessHost;
 
 template<>
 struct RunnableMethodTraits<GeckoChildProcessHost>
 {
     static void RetainCallee(GeckoChildProcessHost* obj) { }
     static void ReleaseCallee(GeckoChildProcessHost* obj) { }
@@ -387,30 +383,16 @@ GeckoChildProcessHost::PerformAsyncLaunc
 #endif
 
   FilePath exePath;
   GetPathToBinary(exePath);
 
 #ifdef ANDROID
   // The java wrapper unpacks this for us but can't make it executable
   chmod(exePath.value().c_str(), 0700);
-  int cacheCount = 0;
-  const struct lib_cache_info * cache = getLibraryCache();
-  nsCString cacheStr;
-  while (cache &&
-         cacheCount++ < MAX_LIB_CACHE_ENTRIES &&
-         strlen(cache->name)) {
-    mFileMap.push_back(std::pair<int,int>(cache->fd, cache->fd));
-    cacheStr.Append(cache->name);
-    cacheStr.AppendPrintf(":%d;", cache->fd);
-    cache++;
-  }
-  // fill the last arg with something if there's no cache
-  if (cacheStr.IsEmpty())
-    cacheStr.AppendLiteral("-");
 #endif
 
   // remap the IPC socket fd to a well-known int, as the OS does for
   // STDOUT_FILENO, for example
   int srcChannelFd, dstChannelFd;
   channel().GetClientFileDescriptorMapping(&srcChannelFd, &dstChannelFd);
   mFileMap.push_back(std::pair<int,int>(srcChannelFd, dstChannelFd));
 
@@ -464,20 +446,16 @@ GeckoChildProcessHost::PerformAsyncLaunc
   // can't pretend being the child that's forked off.
   std::string mach_connection_name = StringPrintf("org.mozilla.machname.%d",
                                                   base::RandInt(0, std::numeric_limits<int>::max()));
   childArgv.push_back(mach_connection_name.c_str());
 #endif
 
   childArgv.push_back(childProcessType);
 
-#ifdef ANDROID
-  childArgv.push_back(cacheStr.get());
-#endif
-
   base::LaunchApp(childArgv, mFileMap,
 #if defined(OS_LINUX) || defined(OS_MACOSX)
                   newEnvVars,
 #endif
                   false, &process, arch);
 
 #ifdef XP_MACOSX
   // Wait for the child process to send us its 'task_t' data.
--- a/ipc/glue/SharedMemoryBasic_android.cpp
+++ b/ipc/glue/SharedMemoryBasic_android.cpp
@@ -51,17 +51,25 @@
 #include "base/process_util.h"
 
 #include "SharedMemoryBasic.h"
 
 //
 // Temporarily go directly to the kernel interface until we can
 // interact better with libcutils.
 //
-#include <linux/ashmem.h>
+#define ASHMEM_DEVICE  		"/dev/ashmem"
+#define ASHMEM_NAME_LEN		256
+#define __ASHMEMIOC 0x77
+#define ASHMEM_SET_NAME		_IOW(__ASHMEMIOC, 1, char[ASHMEM_NAME_LEN])
+#define ASHMEM_GET_NAME		_IOR(__ASHMEMIOC, 2, char[ASHMEM_NAME_LEN])
+#define ASHMEM_SET_SIZE		_IOW(__ASHMEMIOC, 3, size_t)
+#define ASHMEM_GET_SIZE		_IO(__ASHMEMIOC, 4)
+#define ASHMEM_SET_PROT_MASK	_IOW(__ASHMEMIOC, 5, unsigned long)
+#define ASHMEM_GET_PROT_MASK	_IO(__ASHMEMIOC, 6)
 
 namespace mozilla {
 namespace ipc {
 
 static void
 LogError(const char* what)
 {
   __android_log_print(ANDROID_LOG_ERROR, "Gecko",
@@ -89,17 +97,17 @@ SharedMemoryBasic::~SharedMemoryBasic()
 }
 
 bool
 SharedMemoryBasic::Create(size_t aNbytes)
 {
   NS_ABORT_IF_FALSE(-1 == mShmFd, "Already Create()d");
 
   // Carve a new instance off of /dev/ashmem
-  int shmfd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600);
+  int shmfd = open(ASHMEM_DEVICE, O_RDWR, 0600);
   if (-1 == shmfd) {
     LogError("ShmemAndroid::Create():open");
     return false;
   }
 
   if (ioctl(shmfd, ASHMEM_SET_SIZE, aNbytes)) {
     LogError("ShmemAndroid::Unmap():ioctl(SET_SIZE)");
     close(shmfd);
--- a/other-licenses/android/APKOpen.cpp
+++ b/other-licenses/android/APKOpen.cpp
@@ -29,39 +29,29 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-/*
- * This custom library loading code is only meant to be called
- * during initialization. As a result, it takes no special 
- * precautions to be threadsafe. Any of the library loading functions
- * like mozload should not be available to other code.
- */
-
 #include <jni.h>
 #include <android/log.h>
 #include <sys/time.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <sys/mman.h>
-#include <sys/limits.h>
 #include <errno.h>
 #include <string.h>
 #include <stdio.h>
-#include <stdlib.h>
 #include <fcntl.h>
 #include <endian.h>
 #include <unistd.h>
 #include <zlib.h>
-#include <linux/ashmem.h>
 #include "dlfcn.h"
 #include "APKOpen.h"
 
 /* compression methods */
 #define STORE    0
 #define DEFLATE  8
 #define LZMA    14
 
@@ -114,37 +104,17 @@ struct cdir_end {
   uint32_t cdir_size;
   uint32_t cdir_offset;
   uint16_t comment_size;
   char     comment[0];
 } __attribute__((__packed__));
 
 static size_t zip_size;
 static int zip_fd;
-static struct mapping_info * lib_mapping;
-
-NS_EXPORT const struct mapping_info *
-getLibraryMapping()
-{
-  return lib_mapping;
-}
-
-static int
-createAshmem(size_t bytes)
-{
-  int fd = open("/" ASHMEM_NAME_DEF, O_RDWR, 0600);
-  if (fd < 0)
-    return -1;
-
-  if (!ioctl(fd, ASHMEM_SET_SIZE, bytes))
-    return fd;
-
-  close(fd);
-  return -1;
-}
+NS_EXPORT struct mapping_info * lib_mapping;
 
 static void * map_file (const char *file)
 {
   int fd = open(file, O_RDONLY);
   if (fd == -1) {
     __android_log_print(ANDROID_LOG_ERROR, "GeckoMapFile", "map_file open %s", strerror(errno));
     return NULL;
   }
@@ -345,90 +315,16 @@ extractLib(const struct cdir_entry *entr
   ret = inflateEnd(&strm);
   if (ret != Z_OK)
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "inflateEnd failed: %s", strm.msg);
 
   if (strm.total_out != letoh32(entry->uncompressed_size))
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "File not fully uncompressed! %d / %d", strm.total_out, letoh32(entry->uncompressed_size));
 }
 
-static int cache_count = 0;
-static struct lib_cache_info *cache_mapping = NULL;
-
-NS_EXPORT const struct lib_cache_info *
-getLibraryCache()
-{
-  return cache_mapping;
-}
-
-static void
-ensureLibCache()
-{
-  if (!cache_mapping)
-    cache_mapping = (struct lib_cache_info *)calloc(MAX_LIB_CACHE_ENTRIES,
-                                                    sizeof(*cache_mapping));
-}
-
-static void
-fillLibCache(const char *buf)
-{
-  ensureLibCache();
-
-  char * str = strdup(buf);
-  if (!str)
-    return;
-
-  char * saveptr;
-  char * nextstr = str;
-  do {
-    struct lib_cache_info *info = &cache_mapping[cache_count];
-
-    char * name = strtok_r(nextstr, ":", &saveptr);
-    if (!name)
-      break;
-    nextstr = NULL;
-
-    char * fd_str = strtok_r(NULL, ";", &saveptr);
-    if (!fd_str)
-      break;
-
-    long int fd = strtol(fd_str, NULL, 10);
-    if (fd == LONG_MIN || fd == LONG_MAX)
-      break;
-    strncpy(info->name, name, MAX_LIB_CACHE_NAME_LEN - 1);
-    info->fd = fd;
-  } while (cache_count++ < MAX_LIB_CACHE_ENTRIES);
-  free(str);
-}
-
-static int
-lookupLibCacheFd(const char *libName)
-{
-  if (!cache_mapping)
-    return -1;
-
-  int count = cache_count;
-  while (count--) {
-    struct lib_cache_info *info = &cache_mapping[count];
-    if (!strcmp(libName, info->name))
-      return info->fd;
-  }
-  return -1;
-}
-
-static void
-addLibCacheFd(const char *libName, int fd)
-{
-  ensureLibCache();
-
-  struct lib_cache_info *info = &cache_mapping[cache_count++];
-  strncpy(info->name, libName, MAX_LIB_CACHE_NAME_LEN - 1);
-  info->fd = fd;
-}
-
 static void * mozload(const char * path, void *zip,
                       struct cdir_entry *cdir_start, uint16_t cdir_entries)
 {
 #ifdef DEBUG
   struct timeval t0, t1;
   gettimeofday(&t0, 0);
 #endif
 
@@ -437,17 +333,19 @@ static void * mozload(const char * path,
   void * data = ((void *)&file->data) + letoh16(file->filename_size) + letoh16(file->extra_field_size);
   void * handle;
 
   if (extractLibs) {
     char fullpath[256];
     snprintf(fullpath, 256, "/data/data/org.mozilla.fennec/%s", path + 4);
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "resolved %s to %s", path, fullpath);
     extractFile(fullpath, entry, data);
+    __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "%s: 1", fullpath);
     handle = __wrap_dlopen(fullpath, RTLD_LAZY);
+    __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "%s: 2", fullpath);
     if (!handle)
       __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't load %s because %s", fullpath, __wrap_dlerror());
 #ifdef DEBUG
     gettimeofday(&t1, 0);
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "%s: spent %d", path,
                         (((long long)t1.tv_sec * 1000000LL) +
                           (long long)t1.tv_usec) -
                         (((long long)t0.tv_sec * 1000000LL) +
@@ -455,58 +353,42 @@ static void * mozload(const char * path,
 #endif
     return handle;
   }
 
   size_t offset = letoh32(entry->offset) + sizeof(*file) + letoh16(file->filename_size) + letoh16(file->extra_field_size);
 
   int fd = zip_fd;
   void * buf = NULL;
-  uint32_t lib_size = letoh32(entry->uncompressed_size);
   if (letoh16(file->compression) == DEFLATE) {
-    int cache_fd = lookupLibCacheFd(path + 4);
-    fd = cache_fd;
-    if (fd < 0)
-      fd = createAshmem(lib_size);
-#ifdef DEBUG
-    else
-      __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loading %s from cache", path + 4);
-#endif
-    if (fd < 0) {
-      __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't get an ashmem buffer");
-      return NULL;
-    }
-    buf = mmap(NULL, lib_size,
-               PROT_READ | PROT_WRITE,
-               MAP_SHARED, fd, 0);
+    fd = -1;
+    buf = mmap(NULL, letoh32(entry->uncompressed_size),
+               PROT_READ | PROT_WRITE | PROT_EXEC,
+               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     if (buf == (void *)-1) {
       __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't mmap decompression buffer");
-      close(fd);
       return NULL;
     }
 
     offset = 0;
 
-    if (cache_fd < 0) {
-      extractLib(entry, data, buf);
-      addLibCacheFd(path + 4, fd);
-    }
+    extractLib(entry, data, buf);
     data = buf;
   }
 
 #ifdef DEBUG
-  __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loading %s with len %d (0x%08x) and offset %d (0x%08x)", path, lib_size, lib_size, offset, offset);
+  __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Loading %s with len %d and offset %d", path, letoh32(entry->uncompressed_size), offset);
 #endif
 
   handle = moz_mapped_dlopen(path, RTLD_LAZY, fd, data,
-                             lib_size, offset);
+                             letoh32(entry->uncompressed_size), offset);
   if (!handle)
     __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "Couldn't load %s because %s", path, __wrap_dlerror());
   if (buf)
-    munmap(buf, lib_size);
+    munmap(buf, letoh32(entry->uncompressed_size));
 
 #ifdef DEBUG
   gettimeofday(&t1, 0);
   __android_log_print(ANDROID_LOG_ERROR, "GeckoLibLoad", "%s: spent %d", path, (((long long)t1.tv_sec * 1000000LL) + (long long)t1.tv_usec) -
                (((long long)t0.tv_sec * 1000000LL) + (long long)t0.tv_usec));
 #endif
 
   return handle;
@@ -663,22 +545,18 @@ ChildProcessInit(int argc, char* argv[])
   for (i = 0; i < (argc - 1); i++) {
     if (strcmp(argv[i], "-omnijar"))
       continue;
 
     i = i + 1;
     break;
   }
 
-  fillLibCache(argv[argc - 1]);
   loadLibs(argv[i]);
 
-  // don't pass the last arg - it's only recognized by the lib cache
-  argc--;
-
   typedef GeckoProcessType (*XRE_StringToChildProcessType_t)(char*);
   typedef nsresult (*XRE_InitChildProcess_t)(int, char**, GeckoProcessType);
   XRE_StringToChildProcessType_t fXRE_StringToChildProcessType =
     (XRE_StringToChildProcessType_t)__wrap_dlsym(xul_handle, "XRE_StringToChildProcessType");
   XRE_InitChildProcess_t fXRE_InitChildProcess =
     (XRE_InitChildProcess_t)__wrap_dlsym(xul_handle, "XRE_InitChildProcess");
 
   GeckoProcessType proctype = fXRE_StringToChildProcessType(argv[--argc]);
--- a/other-licenses/android/APKOpen.h
+++ b/other-licenses/android/APKOpen.h
@@ -40,21 +40,11 @@
 struct mapping_info {
   char * name;
   char * file_id;
   uintptr_t base;
   size_t len;
   size_t offset;
 };
 
-const struct mapping_info * getLibraryMapping();
-
-#define MAX_LIB_CACHE_ENTRIES 32
-#define MAX_LIB_CACHE_NAME_LEN 32
-
-struct lib_cache_info {
-  char name[MAX_LIB_CACHE_NAME_LEN];
-  int fd;
-};
-
-const struct lib_cache_info * getLibraryCache();
+extern struct mapping_info * lib_mapping;
 
 #endif /* APKOpen_h */
--- a/other-licenses/android/linker.c
+++ b/other-licenses/android/linker.c
@@ -925,29 +925,28 @@ load_segments(int fd, size_t offset, voi
             /* we want to map in the segment on a page boundary */
             tmp = base + (phdr->p_vaddr & (~PAGE_MASK));
             /* add the # of bytes we masked off above to the total length. */
             len = phdr->p_filesz + (phdr->p_vaddr & PAGE_MASK);
 
             TRACE("[ %d - Trying to load segment from '%s' @ 0x%08x "
                   "(0x%08x). p_vaddr=0x%08x p_offset=0x%08x ]\n", pid, si->name,
                   (unsigned)tmp, len, phdr->p_vaddr, phdr->p_offset);
-            if (fd == -1 || PFLAGS_TO_PROT(phdr->p_flags) & PROT_WRITE) {
+            if (fd == -1) {
                 pbase = mmap(tmp, len, PROT_WRITE,
-                             MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+                             MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1,
+                             0);
                 if (pbase != MAP_FAILED) {
                     memcpy(pbase, header + ((phdr->p_offset) & (~PAGE_MASK)), len);
                     mprotect(pbase, len, PFLAGS_TO_PROT(phdr->p_flags));
-                } else
-                    DL_ERR("%s: Memcpy mapping of segment failed!", si->name);
-            } else {
+                }
+            } else
                 pbase = mmap(tmp, len, PFLAGS_TO_PROT(phdr->p_flags),
-                             MAP_SHARED | MAP_FIXED, fd,
+                             MAP_PRIVATE | MAP_FIXED, fd,
                              offset + ((phdr->p_offset) & (~PAGE_MASK)));
-            }
             if (pbase == MAP_FAILED) {
                 DL_ERR("%d failed to map segment from '%s' @ 0x%08x (0x%08x). "
                       "p_vaddr=0x%08x p_offset=0x%08x", pid, si->name,
                       (unsigned)tmp, len, phdr->p_vaddr, phdr->p_offset);
                 goto fail;
             }
 
             report_mapping(si->name, pbase, len, phdr->p_offset & (~PAGE_MASK));
@@ -1234,17 +1233,17 @@ load_mapped_library(const char * name, i
     si->dynamic = (unsigned *)-1;
     if (alloc_mem_region(si) < 0)
         goto fail;
 
     TRACE("[ %5d allocated memory for %s @ %p (0x%08x) ]\n",
           pid, name, (void *)si->base, (unsigned) ext_sz);
 
     /* Now actually load the library's segments into right places in memory */
-    if (load_segments(fd, offset, mem, si) < 0) {
+    if (load_segments(offset ? fd : -1, offset, mem, si) < 0) {
         if (si->ba_index >= 0) {
             ba_free(&ba_nonprelink, si->ba_index);
             si->ba_index = -1;
         }
         goto fail;
     }
 
     /* this might not be right. Technically, we don't even need this info
@@ -1390,38 +1389,16 @@ static int reloc_library(soinfo *si, Elf
 {
     Elf32_Sym *symtab = si->symtab;
     const char *strtab = si->strtab;
     Elf32_Sym *s;
     unsigned base;
     Elf32_Rel *start = rel;
     unsigned idx;
 
-    /* crappy hack to ensure we don't write into the read-only region */
-
-    /* crappy hack part 1: find the read only region */
-    int cnt;
-    void * ro_region_end = si->base;
-    Elf32_Ehdr *ehdr = (Elf32_Ehdr *)si->base;
-    Elf32_Phdr *phdr = (Elf32_Phdr *)((unsigned char *)si->base + ehdr->e_phoff);
-    for (cnt = 0; cnt < ehdr->e_phnum; ++cnt, ++phdr) {
-        if (phdr->p_type != PT_LOAD ||
-            PFLAGS_TO_PROT(phdr->p_flags) & PROT_WRITE ||
-            phdr->p_vaddr != 0)
-            continue;
-
-        ro_region_end = si->base + phdr->p_filesz;
-        break;
-    }
-
-    void * remapped_page = NULL;
-    void * copy_page = mmap(NULL, PAGE_SIZE,
-                            PROT_READ | PROT_WRITE,
-                            MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
-
     for (idx = 0; idx < count; ++idx) {
         unsigned type = ELF32_R_TYPE(rel->r_info);
         unsigned sym = ELF32_R_SYM(rel->r_info);
         unsigned reloc = (unsigned)(rel->r_offset + si->base);
         unsigned sym_addr = 0;
         char *sym_name = NULL;
 
         DEBUG("%5d Processing '%s' relocation at index %d\n", pid,
@@ -1497,33 +1474,16 @@ static int reloc_library(soinfo *si, Elf
 #endif
                 sym_addr = (unsigned)(s->st_value + base);
 	    }
             COUNT_RELOC(RELOC_SYMBOL);
         } else {
             s = NULL;
         }
 
-        /* crappy hack part 2: make this page writable */
-        void * reloc_page = reloc & ~PAGE_MASK;
-        if (reloc < ro_region_end && reloc_page != remapped_page) {
-            if (remapped_page != NULL)
-                mprotect(remapped_page, PAGE_SIZE, PROT_READ | PROT_EXEC);
-            memcpy(copy_page, reloc_page, PAGE_SIZE);
-            munmap(reloc_page, PAGE_SIZE);
-            if (mmap(reloc_page, PAGE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
-                     MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
-                     -1, 0) == MAP_FAILED)
-                DL_ERR("failed to map page for %s at 0x%08x! errno=%d",
-                       si->name, reloc_page, errno);
-
-            memcpy(reloc_page, copy_page, PAGE_SIZE);
-            remapped_page = reloc_page;
-        }
-
 /* TODO: This is ugly. Split up the relocations by arch into
  * different files.
  */
         switch(type){
 #if defined(ANDROID_ARM_LINKER)
         case R_ARM_JUMP_SLOT:
             COUNT_RELOC(RELOC_ABSOLUTE);
             MARK(rel->r_offset);
@@ -1619,17 +1579,16 @@ static int reloc_library(soinfo *si, Elf
 
         default:
             DL_ERR("%5d unknown reloc type %d @ %p (%d)",
                   pid, type, rel, (int) (rel - start));
             return -1;
         }
         rel++;
     }
-    munmap(copy_page, PAGE_SIZE);
     return 0;
 }
 
 #if defined(ANDROID_SH_LINKER)
 static int reloc_library_a(soinfo *si, Elf32_Rela *rela, unsigned count)
 {
     Elf32_Sym *symtab = si->symtab;
     const char *strtab = si->strtab;
--- a/toolkit/xre/nsAndroidStartup.cpp
+++ b/toolkit/xre/nsAndroidStartup.cpp
@@ -70,17 +70,17 @@ struct AutoAttachJavaThread {
 
     PRBool attached;
 };
 
 static void*
 GeckoStart(void *data)
 {
 #ifdef MOZ_CRASHREPORTER
-    const struct mapping_info *info = getLibraryMapping();
+    struct mapping_info *info = lib_mapping;
     while (info->name) {
       CrashReporter::AddLibraryMapping(info->name, info->file_id, info->base,
                                        info->len, info->offset);
       info++;
     }
 #endif
 
     AutoAttachJavaThread attacher;