/***************************************************************************/
/*                                                                         */
/*  procdbg.c  - Process modification (ptrace and procfs) 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 <sys/types.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <fcntl.h>
#include <procdbg.h>
#include <errorlog.h>
#ifdef OS_SOLARIS
   #include <procfs.h>
   #include <sys/ptrace.h> /* TODO: Remove once all ptrace references gone */
   #include <sys/user.h>
#elif OS_LINUX 
   #include <sys/user.h>
   #include <sys/ptrace.h>
#endif

#undef offsetof
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)

/* Globals                                                                 */
unsigned long iFilterSig = (1 << SIGILL) | (1 << SIGIOT) | (1<< SIGBUS) | 
                           (1 << SIGSEGV) | (1 << SIGSYS);
unsigned long iCaughtSig = 0;
int iTracing = 0;
int iAlarmSig = 0;
pid_t tPid = -1;
#ifdef OS_SOLARIS
   int iCtlFd = -1, iMemFd = -1, iStatusFd = -1;
#endif

/* Local functions                                                         */
int ptrace_continue();

/* call_proc - Call the specified address in the remote                    */
/* process                                                                 */
int call_proc(void *pvAddr) {
   void *pvEip;
   SProcStatus tProcStatus;
#if CPU_SPARC
   char aLinkageBuffer[LINKAGE_SPACE];   
#endif

#if CPU_SPARC
   /* We need call linkage space at all times on sparc                     */
   /* Push some spare space onto the stack                                 */
   show_debug(3, "Making linkage space!\n");
   memset(aLinkageBuffer, 0x0, sizeof(aLinkageBuffer));
   push_stack_proc(aLinkageBuffer, sizeof(aLinkageBuffer));   
#endif

   /* Write a saved eip into the remote process, this                      */
   /* address must NOT be a valid page (i.e it MUST cause                  */
   /* a segfault). For fun we use 0x41414140 (not 41414141                 */
   /* since its nice to have an aligned address)                           */
   pvEip = (void *) 0x41414140;
#if OS_LINUX && CPU_IA32
   push_stack_proc(&pvEip, sizeof(pvEip));
#elif CPU_SPARC
   read_status(&(tProcStatus));
   show_debug(3, "Current PC is 0x%08x\n", PC(tProcStatus));
   SAVEDPC(tProcStatus) = (unsigned long) pvEip;
   if (!write_regs(&(tProcStatus)))
      show_abort("Could not modify saved PC in target process\n");
#endif 

   /* Now modify the current EIP of the process to point                   */
   /* where we need it to point                                            */
   read_status(&(tProcStatus));
   PC(tProcStatus) = (unsigned long) pvAddr;
#if CPU_SPARC
   NPC(tProcStatus) = PC(tProcStatus) + 4;
#endif
   if (!write_regs(&(tProcStatus)))
      show_abort("Could not modify PC in target process\n");
   read_status(&(tProcStatus));
   show_debug(3, "PC 0x%08x NPC 0x%08x\n", PC(tProcStatus), NPC(tProcStatus));
   
   /* Now let it go, it should load the library then SEGV on               */
   /* return to 0x41414140 so we catch the SEGV                            */
   ptrace_continue();

   /* We expect the process to segfault but a SIGALRM may                  */
   /* well happen before then. If so we record that and                    */
   /* when we restart the target process we'll send the                    */
   /* SIGALRM                                                              */
   wait_stop(SIGSEGV);
   
   /* Check that the program segfaulted at 0x41414140 as expected          */
   read_status(&(tProcStatus));
   pvEip = (void *) PC(tProcStatus);

   show_debug(3, "EIP of process is %p\n", pvEip);

#ifdef CPU_SPARC
   if (pvEip != (void *) 0x41414148)
#else
   if (pvEip != (void *) 0x41414140)
#endif
      show_abort("Target process segfaulted at %p where 0x41414140 "
                 "was expected\n", pvEip);

   return(0);
}

/* push_stack_proc - Push specified data onto the remote                   */
/* stack                                                                   */
void *push_stack_proc(void *pvAddr, int iSize) {
   void *pvEsp;
   SProcStatus tProcStatus;

   /* If there is no data to be pushed, exit                               */
   if (!iSize)
      return(0);

   /* Get the current ESP                                                  */
   read_status(&(tProcStatus));
   pvEsp = (void *) SP(tProcStatus);

   show_debug(3, "Current ESP is %p\n", pvEsp);

   /* Now subtract the amount of space we need for the                     */
   /* data to be pushed                                                    */
   (unsigned long) pvEsp -= iSize;

   /* Round it to a multiple of 8 (if it isn't already)                    */
   /* (We use 8 since Sparc requires double word alignment)                */
   pvEsp = (void *) round_down((unsigned long) pvEsp, STACK_ALIGN);

   /* Write the ESP to the process                                         */
   SP(tProcStatus) = (unsigned long) pvEsp;
   if (!write_regs(&(tProcStatus)))
      show_abort("Could not modify stack pointer in target process\n");
   read_status(&(tProcStatus));
   show_debug(3, "New SP is 0x%08x\n", SP(tProcStatus));
   
   /* Now write the data itself to the remote process                      */
   if (iSize > (write_proc(pvEsp, pvAddr, iSize)))
      show_abort("Could not write to remote process\n");

   return(pvEsp);
}

/* restore_syscall - Determine if we interrupted a syscall in the          */
/* process. If we did and the user hasn't specified not to we restore      */
/* the registers so that the syscall will be restarted (read the function  */
/* for details for SparcLinux where we're dependent on the BSD signal      */
/* semantics being in effect to restart slow syscalls and we have to do    */
/* horrifying things to make it work). If the user specified not to        */
/* restart syscalls we just set EINTR and hope :)                          */
int restore_syscall(SProcStatus *ptProcStatus, int iOptNoSysRestart) {
#if OS_LINUX
#if CPU_SPARC
   unsigned long ulOldPc;
   unsigned long ulTrapInst;
   unsigned long ulOldSysInst, ulOldNxtInst;

   /* Check if we were in a syscall                                        */
   if (ptProcStatus->regs.psr & PSR_C) {
      /* Get the last instruction                                          */
      read_proc((void *)(ptProcStatus->regs.pc - 4), &ulOldSysInst, sizeof(ulOldSysInst));
      if (ulOldSysInst == TRAP) {
         /* Ok, we were, now we're in a hard place in terms of             */
         /* determining what the %o0 1st parm was to the syscall           */
         /* since it isn't saved anywhere we can easily access. So         */
         /* instead we set a breakpoint where the interrupted syscall      */
         /* instruction is and on pc  and restart the process, it          */
         /* should stop on one or the other (the syscall if the            */
         /* system restarts it, or pc  if the syscall is not to be         */
         /* be restarted). Not clean, but it works.                        */
         show_debug(3, "System call interrupted (psr 0x%08x, o0 0x%08x)\n",
                    ptProcStatus->regs.psr, ptProcStatus->regs.regs[R_O0]);
         
         if (iOptNoSysRestart) {
            show_debug(3, "Not attempting to restart system call\n");
            ptProcStatus->regs.regs[R_O0] = EINTR;
         } else {
            /* Now we set a software breakpoint on the syscall and pc.     */
            /* Page permissions do not apply since we're a debugger        */
            read_proc((void *)(ptProcStatus->regs.pc), &ulOldNxtInst, sizeof(ulOldNxtInst));
            ulTrapInst = BTRAP;
            write_proc((void *)(ptProcStatus->regs.pc - 4), &ulTrapInst, sizeof(ulTrapInst));
            write_proc((void *)(ptProcStatus->regs.pc), &ulTrapInst, sizeof(ulTrapInst));
            ulOldPc = ptProcStatus->regs.pc;

            ptrace_continue();
            wait_stop(SIGTRAP);

            /* Restore the old instructions                                */
            write_proc((void *)(ptProcStatus->regs.pc - 4), &ulOldSysInst, sizeof(ulOldSysInst));
            write_proc((void *)(ptProcStatus->regs.pc), &ulOldNxtInst, sizeof(ulOldNxtInst));

            /* At this point either the system call is ready to restart    */
            /* or execution is ready to continue.                          */
            read_status(ptProcStatus);

            /* If the system call isn't going to restart (breakpoint on    */
            /* pc instead of pc - 4) then we show a message and set EINTR  */
            if (ptProcStatus->regs.pc == ulOldPc) {
               show_debug(3, "Attempt to restart failed, setting EINTR\n");
               ptProcStatus->regs.regs[R_O0] = EINTR;
            }
         }
      }
   }
#elif CPU_IA32
   /* If the orig_eax doesn't equal eax the program was in a system        */
   /* call when we interrupted it so we restore the old eax and            */
   /* restore eip to eip - 2 so the system call will be retried            */
   if ((ptProcStatus->regs.eax != ptProcStatus->regs.orig_eax) &&
       (ptProcStatus->regs.orig_eax != 0xffffff00)) {
      show_debug(3, "System call interrupted (eax 0x%08x, orig_eax 0x%08x)\n",
                 ptProcStatus->regs.eax, ptProcStatus->regs.orig_eax);
      if (iOptNoSysRestart) {
         show_debug(3, "Not attempting to restart system call\n");
         ptProcStatus->regs.eax = EINTR;
      } else {
         show_debug(3, "Restoring EIP to retry\n");
         (unsigned long) ptProcStatus->regs.eip = (unsigned long) ptProcStatus->regs.eip - 2; 
         ptProcStatus->regs.eax = ptProcStatus->regs.orig_eax;
      }
   }
#endif
#elif OS_SOLARIS

   SRunFunc tArgs;
   prgreg_t tSavedReg;

   if (ptProcStatus->pr_lwp.pr_flags & PR_ASLEEP) {
      /* Ok, we're in a syscall, break it                                  */
      show_debug(3, "System call interrupted (pc 0x%08x)\n",
                 PC(*ptProcStatus));
      tSavedReg = ptProcStatus->pr_lwp.pr_reg[R_O0];

      /* Write a PCSRUN command to the control file                        */
      tArgs.uFunc = PCRUN;
      tArgs.uArg = PRSABORT | PRSTOP; 
      
      pwrite(iCtlFd, &tArgs, sizeof(tArgs), 0x0);

      /* Now wait for a stop                                               */
      wait_stop(0);

      read_status(ptProcStatus);
      show_debug(3, "After system call interrupted (pc 0x%08x)\n",
                 PC(*ptProcStatus));

      if (iOptNoSysRestart) {
         show_debug(3, "Not attempting to restart system call\n");
         ptProcStatus->pr_lwp.pr_reg[R_O0] = EINTR;
      } else {
         show_debug(3, "Restoring PC and o0 to retry\n");

         /* Its trivial to restart the syscall, just go back an            */
         /* instruction and restore o0. (We could also just write all the  */
         /* old registers back but this is easy)                           */ 
         ptProcStatus->pr_lwp.pr_reg[R_PC] = ptProcStatus->pr_lwp.pr_reg[R_PC] - 4;
         ptProcStatus->pr_lwp.pr_reg[R_nPC] = ptProcStatus->pr_lwp.pr_reg[R_nPC] - 4;
         ptProcStatus->pr_lwp.pr_reg[R_O0] = tSavedReg;
      }

      write_regs(ptProcStatus);
   }

#endif
   return(0);
}

/* dump_core - Send the process SIGSEGV and SIGABRT so it                  */
/* will dump core                                                          */
int dump_core() {

   kill(tPid, SIGSEGV); 
   kill(tPid, SIGABRT);  

   ptrace(PTRACE_DETACH, tPid, NULL, SIGSTOP);

   show_abort("Sent signals to core dump target process!\n"); 

   return(0);
}

/* read_status - Read the general purpose registers (and other applicable  */
/* status information) from the target                                     */
size_t read_status(SProcStatus *ptStatus) {
#ifdef OS_LINUX
   memset(&(ptStatus->regs), 0x0, sizeof(ptStatus->regs));

   return(!ptrace(PTRACE_GETREGS, tPid, &(ptStatus->regs), &(ptStatus->regs)));
#elif OS_SOLARIS
   memset(ptStatus, 0x0, sizeof(*ptStatus));

   /* Tooo easy, just read it from the status file                         */
   return(pread(iStatusFd, ptStatus, sizeof(*ptStatus), 0x0));
#endif
}

/* write_regs - Write the general purpose registers to the                 */
/* remote process                                                          */
size_t write_regs(SProcStatus *ptStatus) {
#if OS_LINUX
   return(!ptrace(PTRACE_SETREGS, tPid, &(ptStatus->regs), &(ptStatus->regs)));
#elif OS_SOLARIS
   SWriteRegsFunc tArgs;

   tArgs.uFunc = PCSREG;
   memcpy(&(tArgs.tRegs), &(ptStatus->pr_lwp.pr_reg), sizeof(tArgs.tRegs));

   return(pwrite(iCtlFd, &tArgs, sizeof(tArgs), 0x0));
#endif
}
   
/* read_proc - Read data from a process being debugged                     */
size_t read_proc(void *pvAddr, void *pvBuffer, size_t iCount) {
#if (OS_LINUX)
   return(peek_proc(PTRACE_PEEKDATA, pvAddr, pvBuffer, iCount, 0));
#elif (OS_SOLARIS)
   return(pread(iMemFd, pvBuffer, iCount, (off_t) pvAddr));
#endif
}

/* write_proc - Write data into the process being                          */
/*              debugged                                                   */
size_t write_proc(void *pvAddr, void *pvBuffer, size_t iCount) {
#if OS_LINUX
   return(poke_proc(PTRACE_POKEDATA, pvAddr, pvBuffer, iCount, 0));
#elif OS_SOLARIS
   size_t tWriteRc;
   tWriteRc = pwrite(iMemFd, pvBuffer, iCount, (off_t) pvAddr);
   if (tWriteRc != iCount)
      show_debug(3, "Failed write to process, %d %s\n",
                 errno, strerror(errno));
   return(tWriteRc);
#endif
}

/* peek_proc - Use a specfied ptrace operation to                          */
/* peek into the user or process area of the remote                        */
/* process                                                                 */
size_t peek_proc(int iOp, void *pvAddr, void *pvBuffer, 
                 size_t iCount, int iErrorHandle) {
   void *pvCurrent;
   void *pvCurrentAddr;
   size_t iRead = 0;
   long lWord = 0x0;
   long lMaxCpy = 0;
   long lMinCpy = 0;

#ifdef CPU_IA32
   /* IA32 doesn't care about alignment when reading                       */
   /* memory                                                               */
   pvCurrentAddr = pvAddr;
#elif CPU_SPARC
   /* SPARC always requires 4 byte alignment                               */
   pvCurrentAddr = (void *) round_down((unsigned long) pvAddr, 4);
#endif

   pvCurrent = pvBuffer;
   while (iRead < iCount) {
      errno = 0;
#if OS_LINUX
      lWord = ptrace(iOp, tPid, pvCurrentAddr, NULL);
#else
      lWord = ptrace(iOp, tPid, (int) pvCurrentAddr, NULL);
#endif
      if (errno != 0) {
         if (iErrorHandle < 2) 
                 show_debug(3, "Process read at address 0x%08x failed, "
                               "%s\n", pvCurrentAddr, strerror(errno));
         if (iErrorHandle == 0)
            return(iRead);
         if (iErrorHandle > 0)
            lWord = 0x0;
      }
      lMinCpy = (((unsigned long) pvCurrentAddr < (unsigned long) pvAddr) ?
                 ((unsigned long) pvAddr - (unsigned long) pvCurrentAddr) : 
                 0);
      lMaxCpy = ((iCount - iRead) > sizeof(lWord) ?
                 sizeof(lWord) :
                 (iCount - iRead));
      memcpy(pvCurrent, (void *) ((unsigned long) &lWord + lMinCpy), lMaxCpy - lMinCpy); 
      iRead += lMaxCpy - lMinCpy;
      pvCurrentAddr += sizeof(lWord);
      pvCurrent += lMaxCpy - lMinCpy;
   }
      
   return(iRead);
}

/* peek_proc - Use a specfied ptrace operation to                          */
/* poke into the user or process area of the remote                        */
/* process. The caller can specify 1 for iErrorHandle                      */
/* to indicate that errors should be ignored                               */
size_t poke_proc(int iOp, void *pvAddr, void *pvBuffer, 
                 size_t iCount, int iErrorHandle) {
   void *pvCurrent;
   size_t iWritten = 0;
   long lWord = 0;

   pvCurrent = pvBuffer;
   while (iWritten < iCount) {
      errno = 0;
      if (iCount - iWritten < sizeof(lWord)) {
         /* Ok, there isn't a complete word left to be                     */
         /* written to the remote process, instead                         */
         /* we get the current word at this location                       */
         /* then only modify a bit of it                                   */
         errno = 0;
#ifdef OS_LINUX
         lWord = ptrace(PTRACE_PEEKDATA, tPid, pvAddr, NULL);
#else
         lWord = ptrace(PTRACE_PEEKDATA, tPid, (int) pvAddr, NULL);
#endif
         if (errno != 0) {
            show_debug(3, "Could not read data from process at address 0x%08x\n", 
                       pvAddr);
            if (iErrorHandle == 0)
               return(iWritten);
         }
         memcpy(&lWord, pvCurrent, iCount - iWritten);
      } else 
         /* Get the next word from the buffer to be                        */
         /* copied                                                         */
         memcpy(&lWord, pvCurrent, sizeof(lWord)); 

      /* Push the word into the remote process                             */
#ifdef OS_LINUX
      lWord = ptrace(iOp, tPid, pvAddr, lWord);
#else
      lWord = ptrace(iOp, tPid, (int) pvAddr, lWord);
#endif
      if (errno != 0) {
         show_debug(3, "Write to process at address 0x%08x failed, %d %s\n", 
                       pvAddr, errno, strerror(errno));
         if (iErrorHandle == 0)
            return(iWritten);
      }

      /* Move on to the next bit of data to be pushed                      */
      iWritten += sizeof(lWord);
      pvAddr += sizeof(lWord);
      pvCurrent += sizeof(lWord);
   }
      
   return(iWritten);
}

/* wait_stop - Wait for the process to stop (after having been             */
/* attached to (usually)). You can pass an signal you expect to be         */
/* received to cause the stop (or specify 0 if you don't anticipate a      */
/* signal stopping the process, not valid under Linux). If a signal other  */
/* than the one specified is received it is placed into a list of signals  */
/* to be replayed to the process at detach time (if it is not in the list  */
/* of signals to be filtered, iFilterSig).                                 */
int wait_stop(int iExpectedSignal) {
   int iSignal = 0;
#if OS_LINUX
   int iStatus;

   /* We wait for the signal specified in the target                       */
   /* process                                                              */
   while (1) {
      if (-1 == waitpid(tPid, &iStatus, WUNTRACED))
         show_abort("Could not wait on target process\n");
      if (WIFEXITED(iStatus)) 
         show_abort("Target process exited :(\n");
      else if (WIFSTOPPED(iStatus)) {
         iSignal = WSTOPSIG(iStatus);
         if (iSignal == iExpectedSignal)
            break;
         else if ((iSignal > (sizeof(iCaughtSig) * 8)) ||
                  ((1 << iSignal) & iFilterSig))
            show_debug(3, "Process received filtered signal %d\n", iSignal);
         else
         {
            iCaughtSig |= (1 << iSignal);
            show_debug(3, "Process received signal %d, stored for replay\n",
                       iSignal);
         }
      } else
         show_abort("Abnormal status received from wait on target process\n");

      /* If we're still in the loop we let the process continue            */
      ptrace_continue();
   }
#elif OS_SOLARIS
   SStopFunc tStopArgs;
   SProcStatus tProcStatus;
   unsigned long lCmd;
   int iDone;
   short pr_what;
   short pr_why;

   iDone = 0;
   while (!iDone) {
      tStopArgs.uFunc = PCTWSTOP;
      tStopArgs.uArg = 10 * 1000; /* Wait ten seconds for a stop           */
      if (sizeof(tStopArgs) != write(iCtlFd, &tStopArgs, sizeof(tStopArgs)))
         show_abort("Could not stop target process, %d %s\n", errno, strerror(errno));

      read_status(&tProcStatus);
      pr_what = tProcStatus.pr_lwp.pr_what;
      pr_why = tProcStatus.pr_lwp.pr_why;
      
      /* Check if the target has stopped (the above may have timed out)    */
      if (!(tProcStatus.pr_lwp.pr_flags & PR_STOPPED))
         show_abort("Target process did not stop when expected\n");

      if ((iExpectedSignal) &&
          (tProcStatus.pr_lwp.pr_why != PR_SIGNALLED))
         show_abort("Target process did not stop with signal as expected\n");

      /* Signal processing                                                 */
      if (tProcStatus.pr_lwp.pr_why == PR_SIGNALLED) {
         iSignal = tProcStatus.pr_lwp.pr_what;
         if (iSignal == iExpectedSignal)
            iDone = 1;
         else if ((iSignal > (sizeof(iCaughtSig) * 8)) ||
                  ((1 << iSignal) & iFilterSig))
            show_debug(3, "Process received filtered signal %d\n", iSignal);
         else
         {
            iCaughtSig |= (1 << iSignal);
            show_debug(3, "Process received signal %d, stored for replay\n",
                       iSignal);
         }

         /* Clear the signal from the process                              */
         lCmd = PCCSIG;
         pwrite(iCtlFd, &lCmd, sizeof(lCmd), 0x0);
      } else if (!iExpectedSignal)
         iDone = 1;
   }
#endif

   return(iSignal);
}

/* ptrace_attach - Attach to a pid                                         */
int ptrace_attach(pid_t tAttachPid) {
#if OS_LINUX 
   int iRet;

   iRet = ptrace(PTRACE_ATTACH, tAttachPid, tAttachPid, tAttachPid);
   if (!iRet) 
      tPid = tAttachPid;
   else
      return(-1);
#elif OS_SOLARIS
   char sFileName[64];
   unsigned long lCmd;
   STraceFunc tTraceArgs;

   /* We open the ctrl, memory and status proc files for the               */
   /* process                                                              */
    snprintf(sFileName, sizeof(sFileName), "/proc/%d/ctl", tAttachPid);
   if (-1 == (iCtlFd = open(sFileName, O_WRONLY)))
      return(-1);
    snprintf(sFileName, sizeof(sFileName), "/proc/%d/status", tAttachPid);
   if (-1 == (iStatusFd = open(sFileName, O_RDONLY)))
      return(-1);
    snprintf(sFileName, sizeof(sFileName), "/proc/%d/as", tAttachPid);
   if (-1 == (iMemFd = open(sFileName, O_RDWR)))
      return(-1);
   
   tPid = tAttachPid;

   /* Write a stop message to the control file                             */
   lCmd = PCDSTOP;
   write(iCtlFd, &(lCmd), sizeof(lCmd));

   /* Specify that all signals should result in a stop                     */
   tTraceArgs.uFunc = PCSTRACE;
   prfillset(&(tTraceArgs.tSignals));

   if (sizeof(tTraceArgs) != write(iCtlFd, &tTraceArgs, sizeof(tTraceArgs)))
      show_abort("Could not trace signals in target, %d %s\n", errno, strerror(errno));
#endif

   return(0);
}
   
/* ptrace_continue - Allow the process to continue.                        */
int ptrace_continue() {
#if OS_LINUX
#if CPU_SPARC
   /* This is FUCKED, I have no idea what they were                        */
   /* thinking when they did this                                          */
   ptrace(PTRACE_CONT, tPid, 1, NULL);
#else
   ptrace(PTRACE_CONT, tPid, NULL, NULL);
#endif
#elif OS_SOLARIS
   SRunFunc tArgs;

   /* Write a PCSRUN command to the control file                           */
   tArgs.uFunc = PCRUN;
   tArgs.uArg = PRSABORT; /* Abort any running syscall (though none should */
                          /* be in cases where this function is called)    */
   
   pwrite(iCtlFd, &tArgs, sizeof(tArgs), 0x0);
#endif

   return(0);
}

/* ptrace_detach - Exported function so error logging functions            */
/* can successfully detach the process before exiting                      */
int ptrace_detach() {
   int iSignal;
#if OS_LINUX
   int iRc;

   if (tPid == -1)
      return(0);

   iRc = ptrace(PTRACE_DETACH, tPid, NULL, NULL);

   for (iSignal = 1; iSignal < (sizeof(iCaughtSig) * 8); iSignal++) {
      if ((1 << iSignal) & iCaughtSig) {
         show_debug(2, "Replaying signal %d to process\n", iSignal);
         kill(tPid, iSignal);
      }
   }

   return(iRc);
#elif OS_SOLARIS
   STraceFunc tTraceArgs;
   SKillFunc tKillArgs;

   if (iCtlFd == -1)
      return(0);

   /* Stop tracing any signals                                             */
   tTraceArgs.uFunc = PCSTRACE;
   premptyset(&(tTraceArgs.tSignals));

   if (sizeof(tTraceArgs) != write(iCtlFd, &tTraceArgs, sizeof(tTraceArgs)))
      show_abort("Could not untrace signals in target, %d %s\n", 
                 errno, strerror(errno));

   /* Send any signals we noticed while playing with the process (currently*/
   /* we only watch out for SIGALRM)                                       */
   for (iSignal = 1; iSignal < (sizeof(iCaughtSig) * 8); iSignal++) {
      if ((1 << iSignal) & iCaughtSig) {
         show_debug(2, "Replaying signal %d to process\n", iSignal);
         tKillArgs.uFunc = PCKILL;
         tKillArgs.uArg = iSignal;
         pwrite(iCtlFd, &tKillArgs, sizeof(tKillArgs), 0x0);
      }
   }

   /* Now allow the process to continue                                    */
   ptrace_continue();

   /* Close all the files                                                  */
   close(iCtlFd);
   close(iMemFd);
   close(iStatusFd);

   return(0);
#endif
}
   
/* round_down - Round off a number to a multiple of a                      */
/* specified base, number is rounded DOWN to the nearest                   */
/* multiple (this mimics the way most stacks grow)                         */
unsigned long round_down(unsigned long uNum, unsigned long uBase) {
   return(uNum - (uNum % uBase));
}

/* round_up - Round off a number to a multiple of a                        */
/* specified base, number is rounded UP to the nearest                     */
/* multiple                                                                */
unsigned long round_up(unsigned long uNum, unsigned long uBase) {
   unsigned long uRemainder;

   if (!(uRemainder = uNum % uBase))
      return(uNum);

   return(uNum + (uBase - uRemainder));
}

