/***************************************************************************/
/*                                                                         */
/*  elffile.c  - ELF file reading routines for injectso                    */
/*                                                                         */
/*  Copyright (C) 2000 Shaun Clowes                                        */
/*                                                                         */
/*  This program is free software; you can redistribute it and/or modify   */
/*  it under the terms of version 2 of the GNU General Public License as   */
/*  published by the Free Software Foundation;                             */
/*                                                                         */
/*  This program 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 this program; if not, write to the Free Software            */
/*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.              */
/*                                                                         */
/***************************************************************************/

#include <config.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#ifdef OS_SOLARIS
   #include <sys/link.h>
#endif
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <errorlog.h>
#include <elffile.h>

/* Private Function Prototypes                                             */
Elf32_Ehdr *open_elf(char *sFileName, int iTargetFd);
int check_executable_header(char *sFileName, Elf32_Ehdr *ptElfHeader);
int check_library_header(char *sFileName, Elf32_Ehdr *ptElfHeader);
int find_segment(Elf32_Ehdr *ptElfHeader, Elf32_Phdr *ptElfPHeader,
                 Elf32_Word p_type);
void dump_dynamic_header(Elf32_Phdr *ptElfPHeader);
char *segment_type(Elf32_Word p_type);
Elf32_Dyn *find_dynamic_entry_file(int iFd, off_t tOffset, Elf32_Sword d_tag);

/* read_target_info - Read important information from the                  */
/* executable file for the target process                                  */
int read_target_info(char *sFileName, PSFileInfo ptFileInfo) {
   Elf32_Ehdr *ptElfHeader;
   Elf32_Phdr *ptElfPHeader;
   int iDynamicSegment, iInterpSegment;
   Elf32_Dyn  *ptElfDyn;
   int iFd;

   /* Open up the target executables object file                           */
   iFd = open(sFileName, O_RDONLY);
   if (iFd < 0) 
      show_abort("Could not open %s, %s\n", sFileName, strerror(errno));
   show_debug(3, "Opened executable target %s as %d\n", sFileName, iFd);
   
   /* Open the file using and check it is a valid ELF object               */
   ptElfHeader = open_elf(sFileName, iFd);

   /* Check the header to see that the file is an ELF executable           */
   check_executable_header(sFileName, ptElfHeader);

   /* Go to the program headers (segment table)                            */
   if (0 == ptElfHeader->e_phoff) 
      show_abort("Could not read ELF program header from target %s\n", sFileName);
   ptElfPHeader = (Elf32_Phdr *) ((char *) ptElfHeader + ptElfHeader->e_phoff);

   /* Find the dynamic section, if it doesn't exist the file can't         */
   /* be dynamically linked                                                */
   if (-1 == (iDynamicSegment = find_segment(ptElfHeader, ptElfPHeader, PT_DYNAMIC))) 
      show_abort("Target %s does not have a dynamic segment, probably statically linked\n",
                 sFileName);

   dump_dynamic_header(&(ptElfPHeader[iDynamicSegment]));

   /* Copy it to the client struct                                         */
   memcpy(&(ptFileInfo->tDynamicSegment), &(ptElfPHeader[iDynamicSegment]),
          sizeof(ptFileInfo->tDynamicSegment));
   
   /* Now find the interpreter section, a normal ELF executable            */
   /* isn't REQUIRED to have one (since it can rely on the default         */
   /* system dynamic linker but we need to know so we can behave           */
   /* in a sane manner so we abort if we don't find one                    */
   if (-1 == (iInterpSegment = find_segment(ptElfHeader, ptElfPHeader, PT_INTERP))) 
      show_abort("Couldn't find intepreter (dynamic linker) segment in target %s, it is "
                 "probably statically linked\n", sFileName);

   dump_dynamic_header(&(ptElfPHeader[iInterpSegment]));

   /* Copy it to the client struct                                         */
   memcpy(&(ptFileInfo->tInterpSegment), &(ptElfPHeader[iInterpSegment]),
          sizeof(ptFileInfo->tInterpSegment));

   /* Now actually read the interpreter from the file (it isn't            */
   /* in core on Solaris, plus its lazy to read it from core)              */
   ptFileInfo->sInterp = malloc(ptFileInfo->tInterpSegment.p_filesz + 1);
   pread(iFd, ptFileInfo->sInterp, ptFileInfo->tInterpSegment.p_filesz, 
         ptFileInfo->tInterpSegment.p_offset);
   ptFileInfo->sInterp[ptFileInfo->tInterpSegment.p_filesz] = '\0';
   
   /* We use the DEBUG dynamic record to locate all the shared             */
   /* libraries loaded in the process we attach to, if the record          */
   /* doesn't exist, we're screwed                                         */
   if (!find_dynamic_entry_file(iFd, 
                                ptElfPHeader[iDynamicSegment].p_offset,
                                DT_DEBUG))
      show_abort("No DT_DEBUG record in dynamic section of target %s\n",
                 sFileName);

   /* Find the PLTGOT entry too, we compare it with the one found in       */
   /* the remote process in an effort to make sure the person              */
   /* attached us to the right pid                                         */
   if (!(ptElfDyn = find_dynamic_entry_file(iFd,
                                            ptElfPHeader[iDynamicSegment].p_offset,
                                            DT_PLTGOT)))
      show_abort("No DT_PLTGOT record in dynamic section of target %s\n",
                 sFileName);

   /* Copy it to the client struct                                         */
   memcpy(&(ptFileInfo->tPltGotDynamic), ptElfDyn, 
          sizeof(ptFileInfo->tPltGotDynamic));

   return(0);
}

/* check_library_info - Check the specified file is a 32 bit ELF shared    */
/* object (library)                                                        */
int check_library_info(char *sFileName) {
   Elf32_Ehdr *ptElfHeader;
   int iFd;

   /* Open up the target executables object file                           */
   iFd = open(sFileName, O_RDONLY);
   if (iFd < 0) 
      show_abort("Could not open %s, %s\n", sFileName, strerror(errno));
   show_debug(3, "Opened library target %s as %d\n", sFileName, iFd);
   
   /* Open the file using and check it is a valid ELF object               */
   ptElfHeader = open_elf(sFileName, iFd);

   /* Check the file is an ELF shared object (library)                     */
   check_library_header(sFileName, ptElfHeader);

   return(0);
}

/* open_elf - Open elf file                                                */
Elf32_Ehdr *open_elf(char *sFileName, int iTargetFd) {
   Elf32_Ehdr *ptElfFile;
   struct stat statbuf;

   if (fstat(iTargetFd, &statbuf)) 
      show_abort("Could not stat ELF file %s (%d = %s)\n",
                 sFileName, errno, strerror(errno));

   if (NULL == (ptElfFile = (Elf32_Ehdr *) 
                            mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, 
                                 iTargetFd, 0))) 
      show_abort("Could not mmap ELF file %s (%d = %s)\n",
                 sFileName, errno, strerror(errno));

   /* Check this is an ELF file                                            */
   if (strncmp(ELFMAG, ptElfFile->e_ident, strlen(ELFMAG)))
      show_abort("File %s is not an ELF file\n", sFileName);

   /* We can only work with 32 bits files                                  */
   if (ptElfFile->e_ident[EI_CLASS] != ELFCLASS32) 
      show_abort("%s is not a 32 bit ELF file\n", sFileName);

   return(ptElfFile);
}

/* check_executable_header - Check the ELF header is an ELF executable      */
int check_executable_header(char *sFileName, Elf32_Ehdr *ptElfHeader) {

   /* Check this is an executable                                          */
   if (ptElfHeader->e_type != ET_EXEC)
      show_abort("%s is not an ELF executable\n", sFileName);

   /* If debugging is on, show basic information                           */
   show_debug(3, "Header information for %s\n\n", sFileName);
   show_debug(3, "Entry point:               0x%08x\n", ptElfHeader->e_entry);
   show_debug(3, "\n");
   show_debug(3, "Program Header (Execution Information)\n");
   show_debug(3, "--------------------------------------\n");
   show_debug(3, "Program Header Offset:           %d\n", ptElfHeader->e_phoff);
   show_debug(3, "Program Header Entry Size:       %d\n", ptElfHeader->e_phentsize);
   show_debug(3, "Entries in Program Header:       %d\n", ptElfHeader->e_phnum);
   show_debug(3, "\n");
   show_debug(3, "Section Header (Linkage Information)\n");
   show_debug(3, "------------------------------------\n");
   show_debug(3, "Section Header Offset:           %d\n", ptElfHeader->e_shoff);
   show_debug(3, "Section Header Entry Size:       %d\n", ptElfHeader->e_shentsize);
   show_debug(3, "Entries in Section Header:       %d\n", ptElfHeader->e_shnum);
   show_debug(3, "Section name string table index: %d\n", ptElfHeader->e_shstrndx);
   show_debug(3, "\n");

   return(0);
}

/* check_library_header - Check the ELF header indicates the ELF file is   */
/* a shared library                                                        */
int check_library_header(char *sFileName, Elf32_Ehdr *ptElfHeader) {

   /* Check this is an executable                                          */
   if (ptElfHeader->e_type != ET_DYN)
      show_abort("%s is not an ELF shared library\n", sFileName);

   /* If debugging is on, show basic information                           */
   show_debug(3, "Header information for %s\n\n", sFileName);
   show_debug(3, "ELF Program Header (Execution Information)\n");
   show_debug(3, "------------------------------------------\n");
   show_debug(3, "Program Header Offset:           %d\n", ptElfHeader->e_phoff);
   show_debug(3, "Program Header Entry Size:       %d\n", ptElfHeader->e_phentsize);
   show_debug(3, "Entries in Program Header:       %d\n", ptElfHeader->e_phnum);
   show_debug(3, "\n");
   show_debug(3, "Section Header (Linkage Information)\n");
   show_debug(3, "------------------------------------\n");
   show_debug(3, "Section Header Offset:           %d\n", ptElfHeader->e_shoff);
   show_debug(3, "Section Header Entry Size:       %d\n", ptElfHeader->e_shentsize);
   show_debug(3, "Entries in Section Header:       %d\n", ptElfHeader->e_shnum);
   show_debug(3, "Section name string table index: %d\n", ptElfHeader->e_shstrndx);
   show_debug(3, "\n");

   return(0);
}

/* find_segment - Find a segment in the segment table                      */
int find_segment(Elf32_Ehdr *ptElfHeader, Elf32_Phdr *ptElfPHeader,
                         Elf32_Word p_type) {
   int i, found = -1;

   show_debug(3, "Searching for segment type %d = %s\n", p_type, segment_type(p_type));
   for (i = 0; (i < ptElfHeader->e_phnum) && (found == -1); i++) {
      if (ptElfPHeader[i].p_type == PT_NULL)
         continue;
      if (ptElfPHeader[i].p_type == p_type) 
         found = i;
      show_debug(3, "Section %d = Type %d = %s\n", 
                 i, ptElfPHeader[i].p_type, segment_type(ptElfPHeader[i].p_type));
   }
   show_debug(3, "\n");

   return(found);
}

/* segment_type() - Returns a pointer to an internal static char buffer    */
/* containing a text description of the ELF segment type specified         */
char *segment_type(Elf32_Word p_type) {
   char *sSectionType;

   switch(p_type) {
      case PT_LOAD:
         sSectionType = "PT_LOAD (Loadable segment)";
         break;
      case PT_DYNAMIC:
         sSectionType = "PT_DYNAMIC (Dynamic linking information)";
         break;
      case PT_INTERP:
         sSectionType = "PT_INTERP (Program interpreter)";
         break;
      case PT_NOTE:
         sSectionType = "PT_NOTE (Auxiliary information)";
         break;
      case PT_PHDR:
         sSectionType = "PT_HDR (Program header in memory)";
         break;
      default:
         sSectionType = "Unknown segment type";
   }

   return(sSectionType);
}

/* dump_dynamic_header - Dump the header of the dynamic section            */
/* to debug output                                                         */
void dump_dynamic_header(Elf32_Phdr *ptElfPHeader) {

   show_debug(3, "Dynamic Section (Dynamic Linking Information)\n");
   show_debug(3, "---------------------------------------------\n");
   show_debug(3, "File offset:                     %d\n", ptElfPHeader->p_offset);
   show_debug(3, "Virtual address:                 0x%08x\n", ptElfPHeader->p_vaddr);
   show_debug(3, "Physical address:                0x%08x\n", ptElfPHeader->p_paddr);
   show_debug(3, "\n");
   
}

/* find_dynamic_entry() - Find an entry in the dynamic table               */
/* at a specified offset in an elf file                                    */
/* NOTE: The pointer returned is to a static variable in this              */
/* function, the program must not store just the resulting                 */
/* pointer but copy the whole thing if they want to keep it                */
Elf32_Dyn *find_dynamic_entry_file(int iFd, off_t tOffset, Elf32_Sword d_tag) {
   static Elf32_Dyn tElfDyn;
   int iFound = 0;
   
   show_debug(3, "Searching for dynamic record tag %d = %s in file\n", d_tag, dynamic_type(d_tag));
   if (-1 == lseek(iFd, tOffset, SEEK_SET)) 
      show_abort("Could not seek to offset %d in file descriptor %d\n", 
                 tOffset, iFd);
   while ((!iFound) &&
          (sizeof(tElfDyn) == (read(iFd, &tElfDyn, sizeof(tElfDyn)))) &&
          (tElfDyn.d_tag != DT_NULL)) {
      show_debug(3, "Tag 0x%08x = %s\n", tElfDyn.d_tag, dynamic_type(tElfDyn.d_tag));
      if (d_tag == tElfDyn.d_tag)
         iFound = 1;
   }
   show_debug(3,"\n");

   if (iFound)
      return(&tElfDyn);
   else
      return(NULL);
}

/* dynamic_type() - Returns a pointer to an internal static char buffer    */
/* containing a text interpretation of a dynamic entry type                */
char *dynamic_type(Elf32_Sword d_tag) {
   char *sDynamicTag;

   switch(d_tag) {
      case DT_NULL:      sDynamicTag = "DT_NULL";     break;
      case DT_NEEDED:    sDynamicTag = "DT_NEEDED";   break;
      case DT_PLTRELSZ:  sDynamicTag = "DT_PLTRELSZ"; break;
      case DT_HASH:      sDynamicTag = "DT_HASH";     break;
      case DT_STRTAB:    sDynamicTag = "DT_STRTAB";   break;
      case DT_SYMTAB:    sDynamicTag = "DT_SYMTAB";   break;
      case DT_RELA:      sDynamicTag = "DT_RELA";     break;
      case DT_RELAENT:   sDynamicTag = "DT_RELAENT";  break;
      case DT_STRSZ:     sDynamicTag = "DT_STRSZ";    break;
      case DT_SYMENT:    sDynamicTag = "DT_SYMENT";   break;
      case DT_INIT:      sDynamicTag = "DT_INIT";     break;
      case DT_FINI:      sDynamicTag = "DT_FINI";     break;
      case DT_SONAME:    sDynamicTag = "DT_SONAME";   break;
      case DT_RPATH:     sDynamicTag = "DT_RPATH";    break;
      case DT_SYMBOLIC:  sDynamicTag = "DT_SYMBOLIC"; break;
      case DT_REL:       sDynamicTag = "DT_REL";      break;
      case DT_RELSZ:     sDynamicTag = "DT_RELSZ";    break;
      case DT_RELENT:    sDynamicTag = "DT_RELENT";   break;
      case DT_PLTREL:    sDynamicTag = "DT_PLTREL";   break;
      case DT_TEXTREL:   sDynamicTag = "DT_TEXTREL";  break;
      case DT_JMPREL:    sDynamicTag = "DT_JMPREL";   break;
      case DT_DEBUG:     sDynamicTag = "DT_DEBUG";    break;
      case DT_PLTGOT:    sDynamicTag = "DT_PLTGOT";   break;  
      default:
         sDynamicTag = "DT_UNKNOWN";
   }

   return(sDynamicTag);

}

/* calc_elf_hash() - Calculate the hash value for a string                 */
unsigned long calc_elf_hash(const unsigned char *sName) {
   unsigned long ulHash = 0;
   unsigned long ulHi;

   while (*sName) {
      ulHash = (ulHash << 4) + *sName++;

      ulHi = ulHash & 0xf0000000;

      /* The algorithm specified in the ELF ABI is as
         follows:

         if (hi != 0)
           hash ^= hi >> 24;

         hash &= ~hi;

         But the following is equivalent and a lot
         faster, especially on modern processors.  */

      ulHash ^= ulHi;
      ulHash ^= ulHi >> 24;
   }

   return(ulHash);
}

