mirror of
https://github.com/SSCLI/sscli_20021101
synced 2026-06-08 12:28:57 +00:00
9fa3874800
Moved the original file to the archive subfolder.
1127 lines
30 KiB
C
1127 lines
30 KiB
C
/*++
|
|
|
|
|
|
Copyright (c) 2002 Microsoft Corporation. All rights reserved.
|
|
|
|
The use and distribution terms for this software are contained in the file
|
|
named license.txt, which can be found in the root of this distribution.
|
|
By using this software in any fashion, you are agreeing to be bound by the
|
|
terms of this license.
|
|
|
|
You must not remove this notice, or any other, from this software.
|
|
|
|
|
|
Module Name:
|
|
|
|
debug.c
|
|
|
|
Abstract:
|
|
|
|
Implementation of Win32 debugging API functions.
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "pal/palinternal.h"
|
|
#include "pal/dbgmsg.h"
|
|
#include "../thread/process.h"
|
|
#include "pal/context.h"
|
|
#include "pal/debug.h"
|
|
#include "pal/misc.h"
|
|
|
|
#include <signal.h>
|
|
#include <unistd.h>
|
|
#if HAVE_PROCFS_CTL
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#else // HAVE_PROCFS_CTL
|
|
#include <sys/ptrace.h>
|
|
#endif // HAVE_PROCFS_CTL
|
|
#if HAVE_VM_READ
|
|
#include <mach/mach.h>
|
|
#endif // HAVE_VM_READ
|
|
#include <errno.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
|
|
SET_DEFAULT_DEBUG_CHANNEL(DEBUG);
|
|
|
|
#if HAVE_PROCFS_CTL
|
|
#define CTL_ATTACH "attach"
|
|
#define CTL_DETACH "detach"
|
|
#define CTL_WAIT "wait"
|
|
#else // HAVE_PROCFS_CTL
|
|
|
|
#ifdef PTRACE_PEEKDATA
|
|
#define PT_READ_D PTRACE_PEEKDATA
|
|
#define PT_WRITE_D PTRACE_POKEDATA
|
|
#define PT_ATTACH PTRACE_ATTACH
|
|
#define PT_DETACH PTRACE_DETACH
|
|
#endif // !defined(PTRACE_PEEKDATA)
|
|
|
|
#if HAVE_CADDR_T
|
|
#define PT_ADDR(x) ((caddr_t) (x))
|
|
#else // HAVE_CADDR_T
|
|
#define PT_ADDR(x) ((int) (x))
|
|
#endif // HAVE_CADDR_T
|
|
|
|
#endif // HAVE_PROCFS_CTL
|
|
|
|
/* ------------------- Constant definitions ----------------------------------*/
|
|
|
|
const BOOL DBG_ATTACH = TRUE;
|
|
const BOOL DBG_DETACH = FALSE;
|
|
static const char PAL_OUTPUTDEBUGSTRING[]= "PAL_OUTPUTDEBUGSTRING";
|
|
|
|
/* ------------------- Static function prototypes ----------------------------*/
|
|
|
|
#if !HAVE_VM_READ && !HAVE_PROCFS_CTL
|
|
static int
|
|
DBGWriteProcMem_Int(DWORD processId, int *addr, int data);
|
|
static int
|
|
DBGWriteProcMem_IntWithMask(DWORD processId, int *addr, int data,
|
|
unsigned int mask);
|
|
#endif // !HAVE_VM_READ && !HAVE_PROCFS_CTL
|
|
static int
|
|
DBGSetProcessAttached(DWORD attachedProcId, DWORD attacherProcId, BOOL bAttach);
|
|
|
|
/*++
|
|
Function:
|
|
FlushInstructionCache
|
|
|
|
The FlushInstructionCache function flushes the instruction cache for
|
|
the specified process.
|
|
|
|
Remarks
|
|
|
|
This is a no-op for x86 architectures where the instruction and data
|
|
caches are coherent in hardware. For non-X86 architectures, this call
|
|
usually maps to a kernel API to flush the D-caches on all processors.
|
|
|
|
--*/
|
|
BOOL
|
|
PALAPI
|
|
FlushInstructionCache(
|
|
IN HANDLE hProcess,
|
|
IN LPCVOID lpBaseAddress,
|
|
IN SIZE_T dwSize)
|
|
{
|
|
BOOL Ret;
|
|
|
|
ENTRY("FlushInstructionCache (hProcess=%p, lpBaseAddress=%p dwSize=%d)\
|
|
\n", hProcess, lpBaseAddress, dwSize);
|
|
|
|
Ret = DBG_FlushInstructionCache(lpBaseAddress, dwSize);
|
|
|
|
LOGEXIT("FlushInstructionCache returns BOOL %d\n", Ret);
|
|
return Ret;
|
|
}
|
|
|
|
|
|
/*++
|
|
Function:
|
|
OutputDebugStringA
|
|
|
|
See MSDN doc.
|
|
--*/
|
|
VOID
|
|
PALAPI
|
|
OutputDebugStringA(
|
|
IN LPCSTR lpOutputString)
|
|
{
|
|
char *env_string;
|
|
|
|
ENTRY("OutputDebugStringA (lpOutputString=%p (%s))\n",
|
|
lpOutputString?lpOutputString:"NULL",
|
|
lpOutputString?lpOutputString:"NULL");
|
|
|
|
/* as we don't support debug events, we are going to output the debug string
|
|
to stderr instead of generating OUT_DEBUG_STRING_EVENT */
|
|
if ( (lpOutputString != NULL) &&
|
|
(NULL != (env_string = MiscGetenv(PAL_OUTPUTDEBUGSTRING))))
|
|
{
|
|
fprintf(stderr, "%s", lpOutputString);
|
|
}
|
|
|
|
LOGEXIT("OutputDebugStringA returns\n");
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
OutputDebugStringW
|
|
|
|
See MSDN doc.
|
|
--*/
|
|
VOID
|
|
PALAPI
|
|
OutputDebugStringW(
|
|
IN LPCWSTR lpOutputString)
|
|
{
|
|
CHAR *lpOutputStringA;
|
|
int strLen;
|
|
|
|
ENTRY("OutputDebugStringW (lpOutputString=%p (%S))\n",
|
|
lpOutputString ? lpOutputString: W16_NULLSTRING,
|
|
lpOutputString ? lpOutputString: W16_NULLSTRING);
|
|
|
|
if (lpOutputString == NULL)
|
|
{
|
|
OutputDebugStringA("");
|
|
goto EXIT;
|
|
}
|
|
|
|
if ((strLen = WideCharToMultiByte(CP_ACP, 0, lpOutputString, -1, NULL, 0,
|
|
NULL, NULL))
|
|
== 0)
|
|
{
|
|
ASSERT("failed to get wide chars length\n");
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
goto EXIT;
|
|
}
|
|
|
|
/* strLen includes the null terminator */
|
|
if ((lpOutputStringA = (LPSTR) malloc(strLen * sizeof(CHAR))) == NULL)
|
|
{
|
|
ERROR("Insufficient memory available !\n");
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
goto EXIT;
|
|
}
|
|
|
|
if(! WideCharToMultiByte(CP_ACP, 0, lpOutputString, -1,
|
|
lpOutputStringA, strLen, NULL, NULL))
|
|
{
|
|
ASSERT("failed to convert wide chars to multibytes\n");
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
free(lpOutputStringA);
|
|
goto EXIT;
|
|
}
|
|
|
|
OutputDebugStringA(lpOutputStringA);
|
|
free(lpOutputStringA);
|
|
|
|
EXIT:
|
|
LOGEXIT("OutputDebugStringW returns\n");
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
DebugBreak
|
|
|
|
See MSDN doc.
|
|
--*/
|
|
VOID
|
|
PALAPI
|
|
DebugBreak(
|
|
VOID)
|
|
{
|
|
ENTRY("DebugBreak()\n");
|
|
|
|
DBG_DebugBreak();
|
|
|
|
LOGEXIT("DebugBreak returns\n");
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
GetThreadContext
|
|
|
|
See MSDN doc.
|
|
--*/
|
|
BOOL
|
|
PALAPI
|
|
GetThreadContext(
|
|
IN HANDLE hThread,
|
|
IN OUT LPCONTEXT lpContext)
|
|
{
|
|
BOOL ret;
|
|
ENTRY("GetThreadContext (hThread=%p, lpContext=%p)\n",hThread,lpContext);
|
|
|
|
if(hThread == hPseudoCurrentThread)
|
|
{
|
|
hThread = PROCGetRealCurrentThread();
|
|
}
|
|
|
|
ret = CONTEXT_GetThreadContext(hThread, lpContext);
|
|
|
|
LOGEXIT("GetThreadContext returns ret:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
SetThreadContext
|
|
|
|
See MSDN doc.
|
|
--*/
|
|
BOOL
|
|
PALAPI
|
|
SetThreadContext(
|
|
IN HANDLE hThread,
|
|
IN CONST CONTEXT *lpContext)
|
|
{
|
|
BOOL ret;
|
|
ENTRY("SetThreadContext (hThread=%p, lpContext=%p)\n",hThread,lpContext);
|
|
|
|
if(hThread == hPseudoCurrentThread)
|
|
{
|
|
hThread = PROCGetRealCurrentThread();
|
|
}
|
|
|
|
ret = CONTEXT_SetThreadContext(hThread, lpContext);
|
|
|
|
LOGEXIT("SetThreadContext returns ret:%d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
ReadProcessMemory
|
|
|
|
See MSDN doc.
|
|
--*/
|
|
BOOL
|
|
PALAPI
|
|
ReadProcessMemory(
|
|
IN HANDLE hProcess,
|
|
IN LPCVOID lpBaseAddress,
|
|
IN LPVOID lpBuffer,
|
|
IN DWORD nSize,
|
|
OUT LPDWORD lpNumberOfBytesRead
|
|
)
|
|
{
|
|
DWORD processId;
|
|
volatile BOOL ret = FALSE;
|
|
volatile DWORD numberOfBytesRead = 0;
|
|
#if HAVE_VM_READ
|
|
kern_return_t result;
|
|
vm_map_t task;
|
|
int bytesToRead;
|
|
#elif HAVE_PROCFS_CTL
|
|
int fd;
|
|
char memPath[64];
|
|
#else
|
|
unsigned nbInts;
|
|
int* ptrInt;
|
|
int* lpTmpBuffer;
|
|
#endif
|
|
#if !HAVE_PROCFS_CTL
|
|
LPVOID lpBaseAddressAligned;
|
|
unsigned int offset;
|
|
#endif // !HAVE_PROCFS_CTL
|
|
|
|
ENTRY("ReadProcessMemory (hProcess=%p,lpBaseAddress=%p, lpBuffer=%p, "
|
|
"nSize=%u, lpNumberOfBytesRead=%p)\n",hProcess,lpBaseAddress,
|
|
lpBuffer, nSize, lpNumberOfBytesRead);
|
|
|
|
if (!(processId = PROCGetProcessIDFromHandle(hProcess)))
|
|
{
|
|
ERROR("Invalid process handler hProcess:%p.",hProcess);
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
goto EXIT;
|
|
}
|
|
|
|
// Check if the read request is for the current process.
|
|
// We don't need ptrace in that case.
|
|
if (GetCurrentProcessId() == processId)
|
|
{
|
|
TRACE("We are in the same process, so ptrace is not needed\n");
|
|
|
|
PAL_TRY {
|
|
memcpy(lpBuffer, lpBaseAddress, nSize);
|
|
numberOfBytesRead = nSize;
|
|
ret = TRUE;
|
|
} PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
} PAL_ENDTRY
|
|
goto EXIT;
|
|
}
|
|
|
|
#if HAVE_VM_READ
|
|
result = task_for_pid(mach_task_self(), processId, &task);
|
|
if (result != KERN_SUCCESS)
|
|
{
|
|
ERROR("No Mach task for pid %d: %d\n", processId, ret);
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
goto EXIT;
|
|
}
|
|
// vm_read_overwrite usually requires that the address be page-aligned
|
|
// and the size be a multiple of the page size. We can't differentiate
|
|
// between the cases in which that's required and those in which it
|
|
// isn't, so we do it all the time.
|
|
lpBaseAddressAligned = (LPVOID) ((DWORD) lpBaseAddress & ~PAGE_MASK);
|
|
offset = ((DWORD) lpBaseAddress & PAGE_MASK);
|
|
while (nSize > 0)
|
|
{
|
|
vm_size_t bytesRead;
|
|
char data[PAGE_SIZE];
|
|
|
|
bytesToRead = PAGE_SIZE - offset;
|
|
if (bytesToRead > nSize)
|
|
{
|
|
bytesToRead = nSize;
|
|
}
|
|
bytesRead = PAGE_SIZE;
|
|
result = vm_read_overwrite(task, (vm_address_t) lpBaseAddressAligned,
|
|
PAGE_SIZE, (vm_address_t) data, &bytesRead);
|
|
if (result != KERN_SUCCESS || bytesRead != PAGE_SIZE)
|
|
{
|
|
ERROR("vm_read_overwrite failed for %d bytes from %p in %d: %d\n",
|
|
PAGE_SIZE, (char *) lpBaseAddressAligned, task, result);
|
|
if (result <= KERN_RETURN_MAX)
|
|
{
|
|
SetLastError(ERROR_INVALID_ACCESS);
|
|
}
|
|
else
|
|
{
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
goto EXIT;
|
|
}
|
|
memcpy(lpBuffer + numberOfBytesRead, data + offset, bytesToRead);
|
|
numberOfBytesRead += bytesToRead;
|
|
lpBaseAddressAligned = (char *) lpBaseAddressAligned + PAGE_SIZE;
|
|
nSize -= bytesToRead;
|
|
offset = 0;
|
|
}
|
|
ret = TRUE;
|
|
#else // HAVE_VM_READ
|
|
// Attach the process before calling ptrace otherwise it fails.
|
|
if (DBGAttachProcess(processId))
|
|
{
|
|
#if HAVE_PROCFS_CTL
|
|
snprintf(memPath, sizeof(memPath), "/proc/%u/mem", processId);
|
|
fd = open(memPath, O_RDONLY);
|
|
if (fd == -1)
|
|
{
|
|
ERROR("Failed to open %s\n", memPath);
|
|
SetLastError(ERROR_INVALID_ACCESS);
|
|
goto CLEANUP2;
|
|
}
|
|
|
|
if (lseek(fd, (DWORD) lpBaseAddress, SEEK_SET) == -1)
|
|
{
|
|
ERROR("Failed to seek to base address\n");
|
|
SetLastError(ERROR_INVALID_ACCESS);
|
|
goto CLEANUP2;
|
|
}
|
|
|
|
numberOfBytesRead = read(fd, lpBuffer, nSize);
|
|
|
|
#else // HAVE_PROCFS_CTL
|
|
offset = (unsigned int)lpBaseAddress % sizeof(int);
|
|
lpBaseAddressAligned = (LPVOID) ((char*)lpBaseAddress - offset);
|
|
nbInts = (nSize + offset)/sizeof(int) +
|
|
((nSize + offset)%sizeof(int) ? 1:0);
|
|
|
|
/* before transferring any data to lpBuffer we should make sure that all
|
|
data is accessible for read. so we need to use a temp buffer for that.*/
|
|
if (!(lpTmpBuffer = malloc(nbInts * sizeof(int))))
|
|
{
|
|
ERROR("Insufficient memory available !\n");
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
goto CLEANUP1;
|
|
}
|
|
|
|
for (ptrInt = lpTmpBuffer; nbInts; ptrInt++,
|
|
((int*)lpBaseAddressAligned)++, nbInts--)
|
|
{
|
|
errno = 0;
|
|
*ptrInt = ptrace(PT_READ_D, processId,
|
|
PT_ADDR(lpBaseAddressAligned), 0);
|
|
if (*ptrInt == -1 && errno)
|
|
{
|
|
if (errno == EFAULT)
|
|
{
|
|
ERROR("ptrace(PT_READ_D, pid:%d, addr:%p, data:0) failed"
|
|
" errno=%d (%s)\n", processId, lpBaseAddressAligned,
|
|
errno, strerror(errno));
|
|
|
|
SetLastError(ptrInt == lpTmpBuffer ? ERROR_ACCESS_DENIED :
|
|
ERROR_PARTIAL_COPY);
|
|
}
|
|
else
|
|
{
|
|
ASSERT("ptrace(PT_READ_D, pid:%d, addr:%p, data:0) failed"
|
|
" errno=%d (%s)\n", processId, lpBaseAddressAligned,
|
|
errno, strerror(errno));
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
|
|
goto CLEANUP2;
|
|
}
|
|
}
|
|
|
|
/* transfer data from temp buffer to lpBuffer */
|
|
memcpy( (char *)lpBuffer, ((char*)lpTmpBuffer) + offset, nSize);
|
|
numberOfBytesRead = nSize;
|
|
#endif // HAVE_PROCFS_CTL
|
|
ret = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* Failed to attach processId */
|
|
goto EXIT;
|
|
}
|
|
|
|
CLEANUP2:
|
|
#if HAVE_PROCFS_CTL
|
|
if (fd != -1)
|
|
{
|
|
close(fd);
|
|
}
|
|
#else // HAVE_PROCFS_CTL
|
|
if (lpTmpBuffer)
|
|
{
|
|
free(lpTmpBuffer);
|
|
}
|
|
#endif // HAVE_PROCFS_CTL
|
|
|
|
#if !HAVE_PROCFS_CTL
|
|
CLEANUP1:
|
|
#endif // HAVE_PROCFS_CTL
|
|
if (!DBGDetachProcess(processId))
|
|
{
|
|
/* Failed to detach processId */
|
|
ret = FALSE;
|
|
}
|
|
#endif // HAVE_VM_READ
|
|
EXIT:
|
|
if (lpNumberOfBytesRead)
|
|
{
|
|
*lpNumberOfBytesRead = numberOfBytesRead;
|
|
}
|
|
LOGEXIT("ReadProcessMemory returns BOOL %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
WriteProcessMemory
|
|
|
|
See MSDN doc.
|
|
--*/
|
|
BOOL
|
|
PALAPI
|
|
WriteProcessMemory(
|
|
IN HANDLE hProcess,
|
|
IN LPVOID lpBaseAddress,
|
|
IN LPVOID lpBuffer,
|
|
IN DWORD nSize,
|
|
OUT LPDWORD lpNumberOfBytesWritten
|
|
)
|
|
|
|
{
|
|
DWORD processId;
|
|
volatile BOOL ret = FALSE;
|
|
#if HAVE_VM_READ
|
|
kern_return_t result;
|
|
vm_map_t task;
|
|
#elif HAVE_PROCFS_CTL
|
|
int fd;
|
|
char memPath[64];
|
|
DWORD bytesWritten;
|
|
#else
|
|
unsigned int FirstIntOffset;
|
|
unsigned int LastIntOffset;
|
|
unsigned int FirstIntMask;
|
|
unsigned int LastIntMask;
|
|
unsigned int nbInts;
|
|
int *lpTmpBuffer = 0, *lpInt;
|
|
LPVOID lpBaseAddressAligned;
|
|
#endif
|
|
|
|
ENTRY("WriteProcessMemory (hProcess=%p,lpBaseAddress=%p, lpBuffer=%p, "
|
|
"nSize=%u, lpNumberOfBytesWritten=%p)\n",
|
|
hProcess,lpBaseAddress, lpBuffer, nSize, lpNumberOfBytesWritten);
|
|
|
|
if (!(nSize && (processId = PROCGetProcessIDFromHandle(hProcess))))
|
|
{
|
|
ERROR("Invalid nSize:%u number or invalid process handler "
|
|
"hProcess:%p\n", nSize, hProcess);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto EXIT;
|
|
}
|
|
|
|
if (lpNumberOfBytesWritten)
|
|
{
|
|
*lpNumberOfBytesWritten = 0;
|
|
}
|
|
|
|
// Check if the write request is for the current process.
|
|
// In that case we don't need ptrace.
|
|
if (GetCurrentProcessId() == processId)
|
|
{
|
|
TRACE("We are in the same process so we don't need ptrace\n");
|
|
|
|
PAL_TRY {
|
|
memcpy(lpBaseAddress, lpBuffer, nSize);
|
|
|
|
if (lpNumberOfBytesWritten)
|
|
{
|
|
*lpNumberOfBytesWritten = nSize;
|
|
}
|
|
ret = TRUE;
|
|
} PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
} PAL_ENDTRY
|
|
goto EXIT;
|
|
}
|
|
|
|
#if HAVE_VM_READ
|
|
result = task_for_pid(mach_task_self(), processId, &task);
|
|
if (result != KERN_SUCCESS)
|
|
{
|
|
ERROR("No Mach task for pid %d: %d\n", processId, ret);
|
|
SetLastError(ERROR_INVALID_HANDLE);
|
|
goto EXIT;
|
|
}
|
|
result = vm_write(task, (vm_address_t) lpBaseAddress,
|
|
(vm_address_t) lpBuffer, nSize);
|
|
if (result != KERN_SUCCESS)
|
|
{
|
|
ERROR("vm_write failed for %d bytes from %p in %d: %d\n",
|
|
nSize, lpBaseAddress, task, result);
|
|
if (result <= KERN_RETURN_MAX)
|
|
{
|
|
SetLastError(ERROR_ACCESS_DENIED);
|
|
}
|
|
else
|
|
{
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
goto EXIT;
|
|
}
|
|
if (lpNumberOfBytesWritten)
|
|
{
|
|
*lpNumberOfBytesWritten = nSize;
|
|
}
|
|
ret = TRUE;
|
|
#else // HAVE_VM_READ
|
|
/* Attach the process before calling ptrace otherwise it fails */
|
|
if (DBGAttachProcess(processId))
|
|
{
|
|
#if HAVE_PROCFS_CTL
|
|
snprintf(memPath, sizeof(memPath), "/proc/%u/mem", processId);
|
|
fd = open(memPath, O_WRONLY);
|
|
if (fd == -1)
|
|
{
|
|
ERROR("Failed to open %s\n", memPath);
|
|
SetLastError(ERROR_INVALID_ACCESS);
|
|
goto CLEANUP2;
|
|
}
|
|
|
|
if (lseek(fd, (DWORD) lpBaseAddress, SEEK_SET) == -1)
|
|
{
|
|
ERROR("Failed to seek to base address\n");
|
|
SetLastError(ERROR_INVALID_ACCESS);
|
|
goto CLEANUP2;
|
|
}
|
|
|
|
bytesWritten = write(fd, lpBuffer, nSize);
|
|
if (lpNumberOfBytesWritten != NULL)
|
|
{
|
|
*lpNumberOfBytesWritten = bytesWritten;
|
|
}
|
|
|
|
#else // HAVE_PROCFS_CTL
|
|
FirstIntOffset = (unsigned int)lpBaseAddress % sizeof(int);
|
|
FirstIntMask = -1;
|
|
FirstIntMask <<= (FirstIntOffset * 8);
|
|
|
|
nbInts = (nSize + FirstIntOffset) / sizeof(int) +
|
|
(((nSize + FirstIntOffset)%sizeof(int)) ? 1:0);
|
|
lpBaseAddressAligned = (LPVOID)((char*)lpBaseAddress - FirstIntOffset);
|
|
|
|
if ((lpTmpBuffer = malloc(nbInts * sizeof(int))) == NULL)
|
|
{
|
|
ERROR("Insufficient memory available !\n");
|
|
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
|
goto CLEANUP1;
|
|
}
|
|
|
|
memcpy( (char *)lpTmpBuffer + FirstIntOffset, (char *)lpBuffer, nSize);
|
|
lpInt = lpTmpBuffer;
|
|
|
|
LastIntOffset = (nSize + FirstIntOffset) % sizeof(int);
|
|
LastIntMask = -1;
|
|
LastIntMask >>= ((sizeof(int) - LastIntOffset) * 8);
|
|
|
|
if (nbInts == 1)
|
|
{
|
|
if (DBGWriteProcMem_IntWithMask(processId, lpBaseAddressAligned,
|
|
*lpInt,
|
|
LastIntMask & FirstIntMask)
|
|
== 0)
|
|
{
|
|
goto CLEANUP2;
|
|
}
|
|
if (lpNumberOfBytesWritten)
|
|
{
|
|
*lpNumberOfBytesWritten = nSize;
|
|
}
|
|
ret = TRUE;
|
|
goto CLEANUP2;
|
|
}
|
|
|
|
if (DBGWriteProcMem_IntWithMask(processId,
|
|
((int*)lpBaseAddressAligned)++,
|
|
*lpInt++, FirstIntMask)
|
|
== 0)
|
|
{
|
|
goto CLEANUP2;
|
|
}
|
|
|
|
while (--nbInts > 1)
|
|
{
|
|
if (DBGWriteProcMem_Int(processId, ((int*)lpBaseAddressAligned)++,
|
|
*lpInt++) == 0)
|
|
{
|
|
goto CLEANUP2;
|
|
}
|
|
}
|
|
|
|
if (DBGWriteProcMem_IntWithMask(processId, lpBaseAddressAligned,
|
|
*lpInt, LastIntMask ) == 0)
|
|
{
|
|
goto CLEANUP2;
|
|
}
|
|
|
|
if (lpNumberOfBytesWritten)
|
|
{
|
|
*lpNumberOfBytesWritten = nSize;
|
|
}
|
|
#endif // HAVE_PROCFS_CTL
|
|
ret = TRUE;
|
|
}
|
|
else
|
|
{
|
|
/* Failed to attach processId */
|
|
goto EXIT;
|
|
}
|
|
|
|
CLEANUP2:
|
|
#if HAVE_PROCFS_CTL
|
|
if (fd != -1)
|
|
{
|
|
close(fd);
|
|
}
|
|
#else // HAVE_PROCFS_CTL
|
|
if (lpTmpBuffer)
|
|
{
|
|
free(lpTmpBuffer);
|
|
}
|
|
#endif // HAVE_PROCFS_CTL
|
|
|
|
#if !HAVE_PROCFS_CTL
|
|
CLEANUP1:
|
|
#endif // !HAVE_PROCFS_CTL
|
|
if (!DBGDetachProcess(processId))
|
|
{
|
|
/* Failed to detach processId */
|
|
ret = FALSE;
|
|
}
|
|
#endif // HAVE_VM_READ
|
|
EXIT:
|
|
LOGEXIT("WriteProcessMemory returns BOOL %d\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
#if !HAVE_VM_READ && !HAVE_PROCFS_CTL
|
|
/*++
|
|
Function:
|
|
DBGWriteProcMem_Int
|
|
|
|
Abstract
|
|
write one int to a process memory address
|
|
|
|
Parameter
|
|
processId : process handle
|
|
addr : memory address where the int should be written
|
|
data : int to be written in addr
|
|
|
|
Return
|
|
Return 1 if it succeeds, or 0 if it's fails
|
|
--*/
|
|
static
|
|
int
|
|
DBGWriteProcMem_Int(IN DWORD processId,
|
|
IN int *addr,
|
|
IN int data)
|
|
{
|
|
if (ptrace( PT_WRITE_D, processId, PT_ADDR(addr), data ) == -1)
|
|
{
|
|
if (errno == EFAULT)
|
|
{
|
|
ERROR("ptrace(PT_WRITE_D, pid:%d caddr_t:%p data:%x) failed "
|
|
"errno:%d (%s)\n", processId, addr, data, errno, strerror(errno));
|
|
SetLastError(ERROR_INVALID_ADDRESS);
|
|
}
|
|
else
|
|
{
|
|
ASSERT("ptrace(PT_WRITE_D, pid:%d caddr_t:%p data:%x) failed "
|
|
"errno:%d (%s)\n", processId, addr, data, errno, strerror(errno));
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
DBGWriteProcMem_IntWithMask
|
|
|
|
Abstract
|
|
write one int to a process memory address space using mask
|
|
|
|
Parameter
|
|
processId : process ID
|
|
addr : memory address where the int should be written
|
|
data : int to be written in addr
|
|
mask : the mask used to write only a parts of data
|
|
|
|
Return
|
|
Return 1 if it succeeds, or 0 if it's fails
|
|
--*/
|
|
static
|
|
int
|
|
DBGWriteProcMem_IntWithMask(IN DWORD processId,
|
|
IN int *addr,
|
|
IN int data,
|
|
IN unsigned int mask )
|
|
{
|
|
int readInt;
|
|
|
|
if (mask != ~0)
|
|
{
|
|
errno = 0;
|
|
if (((readInt = ptrace( PT_READ_D, processId, PT_ADDR(addr), 0 )) == -1)
|
|
&& errno)
|
|
{
|
|
if (errno == EFAULT)
|
|
{
|
|
ERROR("ptrace(PT_READ_D, pid:%d, caddr_t:%p, 0) failed "
|
|
"errno:%d (%s)\n", processId, addr, errno, strerror(errno));
|
|
SetLastError(ERROR_INVALID_ADDRESS);
|
|
}
|
|
else
|
|
{
|
|
ASSERT("ptrace(PT_READ_D, pid:%d, caddr_t:%p, 0) failed "
|
|
"errno:%d (%s)\n", processId, addr, errno, strerror(errno));
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
data = (data & mask) | (readInt & ~mask);
|
|
}
|
|
return DBGWriteProcMem_Int(processId, addr, data);
|
|
}
|
|
#endif // !HAVE_VM_READ && !HAVE_PROCFS_CTL
|
|
|
|
/*++
|
|
Function:
|
|
DBGAttachProcess
|
|
|
|
Abstract
|
|
|
|
Attach the indicated process to the current process.
|
|
|
|
if the indicated process is already attached by the current process, then
|
|
increment the number of attachment pending. if ot, attach it to the current
|
|
process (with PT_ATTACH).
|
|
|
|
Parameter
|
|
processId : process ID to attach
|
|
Return
|
|
Return true if it succeeds, or false if it's fails
|
|
--*/
|
|
BOOL
|
|
DBGAttachProcess(DWORD processId)
|
|
{
|
|
int attchmentCount;
|
|
int savedErrno;
|
|
#if HAVE_PROCFS_CTL
|
|
int fd;
|
|
char ctlPath[1024];
|
|
#endif // HAVE_PROCFS_CTL
|
|
|
|
attchmentCount =
|
|
DBGSetProcessAttached(processId, GetCurrentProcessId(), DBG_ATTACH);
|
|
|
|
if (attchmentCount == -1)
|
|
{
|
|
/* Failed to set the process as attached */
|
|
goto EXIT;
|
|
}
|
|
|
|
if (attchmentCount == 1)
|
|
{
|
|
#if HAVE_PROCFS_CTL
|
|
struct timespec waitTime;
|
|
|
|
// FreeBSD has some trouble when a series of attach/detach sequences
|
|
// occurs too close together. When this happens, we'll be able to
|
|
// attach to the process, but waiting for the process to stop
|
|
// (either via writing "wait" to /proc/<pid>/ctl or via waitpid)
|
|
// will hang. If we pause for a very short amount of time before
|
|
// trying to attach, we don't run into the bug.
|
|
// PR 35175 in the FreeBSD bug database is likely related to this
|
|
// problem, which occurs regardless of whether we use ptrace
|
|
// or procfs to communicate with the other process.
|
|
waitTime.tv_sec = 0;
|
|
waitTime.tv_nsec = 50000000;
|
|
nanosleep(&waitTime, NULL);
|
|
|
|
sprintf(ctlPath, "/proc/%d/ctl", processId);
|
|
fd = open(ctlPath, O_WRONLY);
|
|
if (fd == -1)
|
|
{
|
|
ERROR("Failed to open %s: errno is %d (%s)\n", ctlPath,
|
|
errno, strerror(errno));
|
|
goto DETACH1;
|
|
}
|
|
|
|
if (write(fd, CTL_ATTACH, sizeof(CTL_ATTACH)) < sizeof(CTL_ATTACH))
|
|
{
|
|
ERROR("Failed to attach to %s: errno is %d (%s)\n", ctlPath,
|
|
errno, strerror(errno));
|
|
close(fd);
|
|
goto DETACH1;
|
|
}
|
|
|
|
if (write(fd, CTL_WAIT, sizeof(CTL_WAIT)) < sizeof(CTL_WAIT))
|
|
{
|
|
ERROR("Failed to wait for %s: errno is %d (%s)\n", ctlPath,
|
|
errno, strerror(errno));
|
|
goto DETACH2;
|
|
}
|
|
|
|
close(fd);
|
|
#else // HAVE_PROCFS_CTL
|
|
if (ptrace( PT_ATTACH, processId, 0, 0 ) == -1)
|
|
{
|
|
if (errno != ESRCH)
|
|
{
|
|
ASSERT("ptrace(PT_ATTACH, pid:%d) failed errno:%d (%s)\n",
|
|
processId, errno, strerror(errno));
|
|
}
|
|
goto DETACH1;
|
|
}
|
|
|
|
if (waitpid(processId, NULL, WUNTRACED) == -1)
|
|
{
|
|
if (errno != ESRCH)
|
|
{
|
|
ASSERT("waitpid(pid:%d, NULL, WUNTRACED) failed.errno:%d"
|
|
" (%s)\n", processId, errno, strerror(errno));
|
|
}
|
|
goto DETACH2;
|
|
}
|
|
#endif // HAVE_PROCFS_CTL
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
DETACH2:
|
|
#if HAVE_PROCFS_CTL
|
|
if (write(fd, CTL_DETACH, sizeof(CTL_DETACH)) < sizeof(CTL_DETACH))
|
|
{
|
|
ASSERT("Failed to detach from %s: errno is %d (%s)\n", ctlPath,
|
|
errno, strerror(errno));
|
|
}
|
|
close(fd);
|
|
#else // HAVE_PROCFS_CTL
|
|
if (ptrace(PT_DETACH, processId, 0, 0) == -1)
|
|
{
|
|
ASSERT("ptrace(PT_DETACH, pid:%d) failed. errno:%d (%s)\n", processId,
|
|
errno, strerror(errno));
|
|
}
|
|
#endif // HAVE_PROCFS_CTL
|
|
DETACH1:
|
|
savedErrno = errno;
|
|
DBGSetProcessAttached(processId, GetCurrentProcessId(), DBG_DETACH);
|
|
errno = savedErrno;
|
|
EXIT:
|
|
if (errno == ESRCH || errno == ENOENT || errno == EBADF)
|
|
{
|
|
ERROR("Invalid process ID:%d\n", processId);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
}
|
|
else
|
|
{
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
DBGDetachProcess
|
|
|
|
Abstract
|
|
Detach the indicated process from the current process.
|
|
|
|
if the indicated process is already attached by the current process, then
|
|
decrement the number of attachment pending and detach it from the current
|
|
process (with PT_DETACH) if there's no more attachment left.
|
|
|
|
Parameter
|
|
processId : process handle
|
|
|
|
Return
|
|
Return true if it succeeds, or true if it's fails
|
|
--*/
|
|
BOOL
|
|
DBGDetachProcess(DWORD processId)
|
|
{
|
|
int nbAttachLeft;
|
|
#if HAVE_PROCFS_CTL
|
|
int fd;
|
|
char ctlPath[1024];
|
|
#endif // HAVE_PROCFS_CTL
|
|
|
|
nbAttachLeft = DBGSetProcessAttached(processId, GetCurrentProcessId(),
|
|
DBG_DETACH);
|
|
if (nbAttachLeft == -1)
|
|
{
|
|
/* Failed to set the process as detached */
|
|
return FALSE;
|
|
}
|
|
|
|
/* check if there's no more attachment left on processId */
|
|
if (nbAttachLeft == 0)
|
|
{
|
|
#if HAVE_PROCFS_CTL
|
|
sprintf(ctlPath, "/proc/%d/ctl", processId);
|
|
fd = open(ctlPath, O_WRONLY);
|
|
if (fd == -1)
|
|
{
|
|
if (errno == ENOENT)
|
|
{
|
|
ERROR("Invalid process ID: %d\n", processId);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
}
|
|
else
|
|
{
|
|
ERROR("Failed to open %s: errno is %d (%s)\n", ctlPath,
|
|
errno, strerror(errno));
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
if (write(fd, CTL_DETACH, sizeof(CTL_DETACH)) < sizeof(CTL_DETACH))
|
|
{
|
|
ERROR("Failed to detach from %s: errno is %d (%s)\n", ctlPath,
|
|
errno, strerror(errno));
|
|
close(fd);
|
|
return FALSE;
|
|
}
|
|
close(fd);
|
|
|
|
#else // HAVE_PROCFS_CTL
|
|
if (ptrace(PT_DETACH, processId, PT_ADDR(1), 0) == -1)
|
|
{
|
|
if (errno == ESRCH)
|
|
{
|
|
ERROR("Invalid process ID: %d\n", processId);
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
}
|
|
else
|
|
{
|
|
ASSERT("ptrace(PT_DETACH, pid:%d) failed. errno:%d (%s)\n",
|
|
processId, errno, strerror(errno));
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
}
|
|
return FALSE;
|
|
}
|
|
#endif // HAVE_PROCFS_CTL
|
|
|
|
if (kill(processId, SIGCONT) == -1)
|
|
{
|
|
ERROR("Failed to continue the detached process:%d errno:%d (%s)\n",
|
|
processId, errno, strerror(errno));
|
|
return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/*++
|
|
Function:
|
|
DBGSetProcessAttached
|
|
|
|
Abstract
|
|
saves the current process Id in the attached process structure
|
|
|
|
Parameter
|
|
attachedProcId : process ID of the attached process
|
|
attacherProcId : process ID of the attacher process
|
|
bAttach : true (false) to set the process as attached (as detached)
|
|
Return
|
|
returns the number of attachment left on attachedProcId, or -1 if it fails
|
|
--*/
|
|
static int
|
|
DBGSetProcessAttached(DWORD attachedProcId,
|
|
DWORD attacherProcId,
|
|
BOOL bAttach)
|
|
{
|
|
SHMPROCESS *shmprocess;
|
|
int ret = -1;
|
|
|
|
SHMLock();
|
|
|
|
if (attachedProcId == GetCurrentProcessId())
|
|
{
|
|
ERROR("The attached process couldn't be the current process\n");
|
|
SetLastError(ERROR_INVALID_PARAMETER);
|
|
goto EXIT;
|
|
}
|
|
|
|
if(!(shmprocess = PROCFindSHMProcess(attachedProcId)))
|
|
{
|
|
ASSERT("Process 0x%08x was not found in shared memory.\n",
|
|
attachedProcId);
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
goto EXIT;
|
|
}
|
|
|
|
if ((shmprocess->attachedByProcId != attacherProcId) &&
|
|
(shmprocess->attachedByProcId !=0))
|
|
{
|
|
ERROR("process ID:%d already attached by an other process ID:%d\n",
|
|
attachedProcId, shmprocess->attachedByProcId);
|
|
SetLastError(ERROR_NOACCESS);
|
|
goto EXIT;
|
|
}
|
|
|
|
if (bAttach)
|
|
{
|
|
shmprocess->attachedByProcId = attacherProcId;
|
|
shmprocess->attachCount++;
|
|
}
|
|
else
|
|
{
|
|
if (shmprocess->attachCount-- <= 0)
|
|
{
|
|
ASSERT("attachCount <= 0 check for extra PROCSetProcessDetached calls\n");
|
|
SetLastError(ERROR_INTERNAL_ERROR);
|
|
goto EXIT;
|
|
}
|
|
|
|
if (shmprocess->attachCount == 0)
|
|
{
|
|
shmprocess->attachedByProcId = 0;
|
|
}
|
|
}
|
|
ret = shmprocess->attachCount;
|
|
|
|
EXIT:
|
|
SHMRelease();
|
|
return ret;
|
|
}
|
|
|