diff options
Diffstat (limited to 'src/pal/tests/palsuite/debug_api')
29 files changed, 1696 insertions, 0 deletions
diff --git a/src/pal/tests/palsuite/debug_api/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/CMakeLists.txt new file mode 100644 index 0000000000..a568131d3f --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/CMakeLists.txt @@ -0,0 +1,9 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +# TODO: make these tests compile +# add_subdirectory(DebugBreak) +# add_subdirectory(WriteProcessMemory) + +add_subdirectory(OutputDebugStringA) +add_subdirectory(OutputDebugStringW) + diff --git a/src/pal/tests/palsuite/debug_api/DebugBreak/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/DebugBreak/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/DebugBreak/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/debug_api/DebugBreak/test1/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/DebugBreak/test1/CMakeLists.txt new file mode 100644 index 0000000000..db66cbe1d0 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/DebugBreak/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_debugbreak_test1 + ${SOURCES} +) + +add_dependencies(paltest_debugbreak_test1 coreclrpal) + +target_link_libraries(paltest_debugbreak_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/debug_api/DebugBreak/test1/test1.c b/src/pal/tests/palsuite/debug_api/DebugBreak/test1/test1.c new file mode 100644 index 0000000000..2b10b9ad9d --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/DebugBreak/test1/test1.c @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: test1.c +** +** Purpose: Tests that DebugBreak works in the grossest fashion. +** +** +**============================================================*/ + +#include <palsuite.h> + +int __cdecl main(int argc, char *argv[]) +{ + BOOL bTry = FALSE; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + PAL_TRY + { + DebugBreak(); + if (!bTry) + { + Fail("DebugBreak: Continued in Try block.\n"); + } + } + PAL_EXCEPT(EXCEPTION_EXECUTE_HANDLER) + { + bTry = TRUE; + } + PAL_ENDTRY; + + if (!bTry) + { + Fail("DebugBreak: Did not reach the exception block.\n"); + } + + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/debug_api/DebugBreak/test1/testinfo.dat b/src/pal/tests/palsuite/debug_api/DebugBreak/test1/testinfo.dat new file mode 100644 index 0000000000..25c480eccb --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/DebugBreak/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Debug +Function = DebugBreak +Name = DebugBreak test #1 +TYPE = DEFAULT +EXE1 = test1 +Description += Tests that DebugBreak "works". This will require case by case += manual interpretation. diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringA/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/CMakeLists.txt new file mode 100644 index 0000000000..5bc7fdb6e9 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test1.c +) + +add_executable(paltest_outputdebugstringa_test1 + ${TESTSOURCES} +) + +add_dependencies(paltest_outputdebugstringa_test1 coreclrpal) + +target_link_libraries(paltest_outputdebugstringa_test1 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + helper.c +) + +add_executable(paltest_outputdebugstringa_test1_helper + ${HELPERSOURCES} +) + +add_dependencies(paltest_outputdebugstringa_test1_helper coreclrpal) + +target_link_libraries(paltest_outputdebugstringa_test1_helper + pthread + m + coreclrpal +) + + diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/helper.c b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/helper.c new file mode 100644 index 0000000000..90073dfedd --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/helper.c @@ -0,0 +1,35 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: helper.c +** +** Purpose: Intended to be the child process of a debugger. Calls +** OutputDebugStringA once with a normal string, once with an empty +** string +** +** +**============================================================*/ + +#include <palsuite.h> + +int __cdecl main(int argc, char *argv[]) +{ + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + OutputDebugStringA("Foo!\n"); + + OutputDebugStringA(""); + + /* give a chance to the debugger process to read the debug string before + exiting */ + Sleep(1000); + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/test1.c b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/test1.c new file mode 100644 index 0000000000..080c6ac53e --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/test1.c @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: test1.c +** +** Purpose: Debugs the helper application. Checks that certain events, in +** particular the OUTPUT_DEBUG_STRING_EVENT, is generated correctly +** and gives the correct values. +** +** +**============================================================*/ + +#include <palsuite.h> + +const int DELAY_MS = 2000; + +struct OutputCheck +{ + DWORD ExpectedEventCode; + DWORD ExpectedUnicode; + char *ExpectedStr; +}; + +int __cdecl main(int argc, char *argv[]) +{ + + PROCESS_INFORMATION pi; + STARTUPINFO si; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + /* Create a new process. This is the process to be Debugged */ + if(!CreateProcess( NULL, "helper", NULL, NULL, + FALSE, 0, NULL, NULL, &si, &pi)) + { + Fail("ERROR: CreateProcess failed to load executable 'helper'. " + "GetLastError() returned %d.\n",GetLastError()); + } + + /* This is the main loop. It exits when the process which is being + debugged is finished executing. + */ + + while(1) + { + DWORD dwRet = 0; + dwRet = WaitForSingleObject(pi.hProcess, + DELAY_MS /* Wait for 2 seconds max*/ + ); + + if (dwRet != WAIT_OBJECT_0) + { + Trace("WaitForSingleObjectTest:WaitForSingleObject " + "failed (%x) after waiting %d seconds for the helper\n", + GetLastError(), DELAY_MS / 1000); + } + else + { + DWORD dwExitCode; + + /* check the exit code from the process */ + if( ! GetExitCodeProcess( pi.hProcess, &dwExitCode ) ) + { + DWORD dwError; + + dwError = GetLastError(); + CloseHandle ( pi.hProcess ); + CloseHandle ( pi.hThread ); + Fail( "GetExitCodeProcess call failed with error code %d\n", + dwError ); + } + + if(dwExitCode != STILL_ACTIVE) { + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + break; + } + Trace("still executing %d..\n", dwExitCode); + } + } + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/testinfo.dat b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/testinfo.dat new file mode 100644 index 0000000000..d49e9048d1 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringA/test1/testinfo.dat @@ -0,0 +1,13 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Debug +Function = OutputDebugStringA +Name = OutputDebugStringA test #1 +TYPE = DEFAULT +EXE1 = test1 +EXE2 = helper +Description +=Tests that OutputDebugString generates the correct debugging event behaviour. diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringW/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/CMakeLists.txt new file mode 100644 index 0000000000..f6aa0cb2d9 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) + diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/CMakeLists.txt new file mode 100644 index 0000000000..9202baeaef --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/CMakeLists.txt @@ -0,0 +1,19 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(SOURCES + test1.c +) + +add_executable(paltest_outputdebugstringw_test1 + ${SOURCES} +) + +add_dependencies(paltest_outputdebugstringw_test1 coreclrpal) + +target_link_libraries(paltest_outputdebugstringw_test1 + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/test1.c b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/test1.c new file mode 100644 index 0000000000..88b55427bc --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/test1.c @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: test1.c +** +** Purpose: Intended to be the child process of a debugger. Calls +** OutputDebugStringW once with a normal string, once with an empty +** string +** +** +**============================================================*/ + +#define UNICODE +#include <palsuite.h> + +int __cdecl main(int argc, char *argv[]) +{ + WCHAR *str1; + WCHAR *str2; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + str1 = convert("Foo!"); + str2 = convert(""); + + OutputDebugStringW(str1); + + OutputDebugStringW(str2); + + free(str1); + free(str2); + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/testinfo.dat b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/testinfo.dat new file mode 100644 index 0000000000..d6bc4ac5a1 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/OutputDebugStringW/test1/testinfo.dat @@ -0,0 +1,12 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Debug +Function = OutputDebugStringW +Name = OutputDebugStringW test #1 +TYPE = DEFAULT +EXE1 = test1 +Description +=Tests that OutputDebugString generates the correct debugging event behaviour. diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/CMakeLists.txt new file mode 100644 index 0000000000..078b55691f --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +add_subdirectory(test1) +add_subdirectory(test3) +add_subdirectory(test4) + diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/CMakeLists.txt new file mode 100644 index 0000000000..39130aac1c --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test1.c +) + +add_executable(paltest_writeprocessmemory_test1 + ${TESTSOURCES} +) + +add_dependencies(paltest_writeprocessmemory_test1 coreclrpal) + +target_link_libraries(paltest_writeprocessmemory_test1 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + helper.c +) + +add_executable(paltest_writeprocessmemory_test1_helper + ${HELPERSOURCES} +) + +add_dependencies(paltest_writeprocessmemory_test1_helper coreclrpal) + +target_link_libraries(paltest_writeprocessmemory_test1_helper + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/commonconsts.h b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/commonconsts.h new file mode 100644 index 0000000000..eb7d511534 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/commonconsts.h @@ -0,0 +1,46 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: commonconsts.h +** +** +**============================================================*/ + +#ifndef _COMMONCONSTS_H_ +#define _COMMONCONSTS_H_ + +#include <pal.h> + +const int TIMEOUT = 40000; + +const WCHAR szcToHelperEvName[] = { 'T', 'o', '\0' }; +const WCHAR szcFromHelperEvName[] = { 'F', 'r', 'o', 'm', '\0' }; + +const char initialValue = '-'; +const char nextValue = '|'; +const char guardValue = '*'; +const char *commsFileName = "AddrNLen.dat"; + +/* PEDANTIC and PEDANTIC0 is a helper macro that just grumps about any + * zero return codes in a generic way. with little typing */ +#define PEDANTIC(function, parameters) \ +{ \ + if (! (function parameters) ) \ + { \ + Trace("%s: NonFatal failure of %s%s for reasons %u and %u\n", \ + __FILE__, #function, #parameters, GetLastError(), errno); \ + } \ +} +#define PEDANTIC1(function, parameters) \ +{ \ + if ( (function parameters) ) \ + { \ + Trace("%s: NonFatal failure of %s%s for reasons %u and %u\n", \ + __FILE__, #function, #parameters, GetLastError(), errno); \ + } \ +} + +#endif diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/helper.c b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/helper.c new file mode 100644 index 0000000000..1a7318969c --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/helper.c @@ -0,0 +1,243 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: helper.c +** +** Purpose: This helper process sets up a several blocks of memory, +** then uses a file to tell its parent process where that memory is +** So it can do a WriteProcessMemory on it. When the parent process is done +** we check here that it was written properly. +** +** +**============================================================*/ + +#include "commonconsts.h" + +#include <palsuite.h> + +struct allhandles_t +{ + HANDLE hEvToHelper; + HANDLE hEvFromHelper; + char *valuesFileName; +}; + + +/* function: wpmDoIt + * + * This is a general WriteProcessMemory testing function that sets up + * the RAM pointed to and tells the companion process on the other end + * of the handles in 'Comms' to attempt to alter 'lenDest' bytes at + * '*pDest'. + * + * '*pBuffer'[0..'lenBuffer'] is expected to be a guard region + * surrounding the '*pDest'[0..'lenDest'] region so that this function + * can verify that only the proper bytes were altered. + */ + +int wpmDoIt(struct allhandles_t Comms, + char * pBuffer, unsigned int lenBuffer, + char * pDest, unsigned int lenDest, + const char* storageDescription) +{ + char *pCurr; + FILE *commsFile; + DWORD dwRet; + + if (pBuffer > pDest || lenDest > lenBuffer) + { + Trace("WriteProcessMemory::DoIt() test implementation: " + "(pBuffer > pDest || lenDest > lenBuffer)\n"); + return FALSE; + } + + /* set up the storage */ + memset(pBuffer, guardValue, lenBuffer); + memset(pDest, initialValue, lenDest); + + /* tell the parent what RAM to adjust */ + if(!(commsFile = fopen(Comms.valuesFileName, "w"))) + { + Trace("WriteProcessMemory: fopen of '%S' failed (%u). \n", + Comms.valuesFileName, GetLastError()); + return FALSE; + } + if (!fprintf(commsFile, "%u %u '%s'\n", + pDest, lenDest, storageDescription)) + { + Trace("WriteProcessMemory: fprintf to '%S' failed (%u). \n", + Comms.valuesFileName, GetLastError()); + return FALSE; + } + PEDANTIC1(fclose, (commsFile)); + + /* Tell the parent the data is ready for it to adjust */ + PEDANTIC(ResetEvent, (Comms.hEvToHelper)); + PEDANTIC(SetEvent, (Comms.hEvFromHelper)); + + dwRet = WaitForSingleObject(Comms.hEvToHelper, TIMEOUT); /* parent is done */ + if (dwRet != WAIT_OBJECT_0) + { + Trace("helper WaitForSingleObjectTest: WaitForSingleObject " + "failed (%u)\n", GetLastError()); + return FALSE; + } + + /* check the stuff that SHOULD have changed */ + for (pCurr = pDest; pCurr < (pDest + lenDest); pCurr++) + { + if ( *pCurr != nextValue) + { + Trace("When testing '%s': alteration test failed " + "at %u offset %u. Found '%c' instead of '%c'\n.", + storageDescription, pDest, pCurr - pDest, *pCurr, nextValue); + Trace(" 'Altered' string: '%.*s'\n",lenBuffer, pBuffer); + return FALSE; + } + } + /* check the stuff that should NOT have changed */ + for (pCurr = pBuffer; pCurr < pDest; pCurr++ ) + { + if ( *pCurr != guardValue) + { + Trace("When testing '%s': leading guard zone test failed " + "at %u offset %u. Found '%c' instead of '%c'\n.", + storageDescription, pDest, pCurr - pBuffer, *pCurr, guardValue); + Trace(" 'Altered' string: '%.*s'\n",lenBuffer, pBuffer); + return FALSE; + } + } + for (pCurr = pDest + lenDest; pCurr < (pBuffer + lenBuffer); pCurr++ ) + { + if ( *pCurr != guardValue) + { + Trace("When testing '%s': trailing guard zone test failed " + "at %u offset %u. Found '%c' instead of '%c'\n.", + storageDescription, pDest + lenDest, pCurr - pBuffer, *pCurr, guardValue); + Trace(" 'Altered' string: '%.*s'\n",lenBuffer, pBuffer); + return FALSE; + } + } + + return TRUE; +} + +int __cdecl main(int argc, char *argv[]) +{ + + BOOL success = TRUE; /* assume success */ + struct allhandles_t Comms = {0,0,0} ; + + /* variables to track storage to alter */ + char *pTarget = NULL; + unsigned int sizeTarget; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* hook up with the events created by the parent */ + Comms.hEvToHelper = OpenEventW(EVENT_ALL_ACCESS, 0, szcToHelperEvName); + if (!Comms.hEvToHelper) + { + Fail("WriteProcessMemory: OpenEvent of '%S' failed (%u). " + "(the event should already exist!)\n", + szcToHelperEvName, GetLastError()); + } + Comms.hEvFromHelper = OpenEventW(EVENT_ALL_ACCESS, 0, szcFromHelperEvName); + if (!Comms.hEvToHelper) + { + Trace("WriteProcessMemory: OpenEvent of '%S' failed (%u). " + "(the event should already exist!)\n", + szcFromHelperEvName, GetLastError()); + success = FALSE; + goto EXIT; + } + Comms.valuesFileName = argv[1]; + + { + char autoAllocatedOnStack[51]; + + /* Get the parent process to write to the local stack */ + success &= wpmDoIt(Comms, autoAllocatedOnStack, + sizeof(autoAllocatedOnStack), + autoAllocatedOnStack + sizeof(int), + sizeof(autoAllocatedOnStack) - 2 * sizeof(int), + "const size array on stack with int sized guards"); + } + + /* Get the parent process to write to stuff on the heap */ + sizeTarget = 2 * sizeof(int) + 23 ; /* 23 is just a random prime > 16 */ + if (!(pTarget = malloc(sizeTarget))) + { + Trace("WriteProcessMemory helper: unable to allocate '%s'->%d bytes of memory" + "(%u).\n", + argv[3], sizeTarget, GetLastError()); + success = FALSE; + goto EXIT; + + } + success &= wpmDoIt(Comms, pTarget, sizeTarget, + pTarget + sizeof(int), + sizeTarget - 2 * sizeof(int), + "array on heap with int sized guards"); + + /* just to be nice try something 16 - 2 * sizeof(int) bytes long */ + { + char autoAllocatedOnStack[16]; + + /* Get the parent process to write to the local stack */ + success &= wpmDoIt(Comms, autoAllocatedOnStack, + sizeof(autoAllocatedOnStack), + autoAllocatedOnStack + sizeof(int), + sizeof(autoAllocatedOnStack) - 2 * sizeof(int), + "another 16 byte array on stack with int sized guards inside"); + } + + /* NOTE: Don't try 0 bytes long. Win32 WriteProcessMemory claims + * it writes 8 bytes in that case! */ + + /* and 1 byte long... */ + { + char autoAllocatedOnStack[1+ 2 * sizeof(int)]; + + /* Get the parent process to write to the local stack */ + success &= wpmDoIt(Comms, autoAllocatedOnStack, + sizeof(autoAllocatedOnStack), + autoAllocatedOnStack + sizeof(int), + 1, + "no bytes with int sized guards outside on stack"); + } + + +EXIT: + /* Tell the parent that we are done */ + if (!DeleteFile(Comms.valuesFileName)) + { + Trace("helper: DeleteFile failed so parent (test1) is unlikely " + "to exit cleanly\n"); + } + PEDANTIC(ResetEvent, (Comms.hEvToHelper)); + if (!SetEvent(Comms.hEvFromHelper)) + { + Trace("helper: SetEvent failed so parent (test1) is unlikely " + "to exit cleanly\n"); + } + + free(pTarget); + PEDANTIC(CloseHandle, (Comms.hEvToHelper)); + PEDANTIC(CloseHandle, (Comms.hEvFromHelper)); + + if (!success) + { + Fail(""); + } + + PAL_Terminate(); + + return success ? PASS : FAIL; +} diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/test1.c b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/test1.c new file mode 100644 index 0000000000..8de029d973 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/test1.c @@ -0,0 +1,189 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: test1.c +** +** Purpose: Create a child process and some events for communications with it. +** When the child gets back to us with a memory location and a length, +** Call WriteProcessMemory on this location and check to see that it +** writes successfully. +** +** +**============================================================*/ + +#define UNICODE + +#include "commonconsts.h" + +#include <palsuite.h> + +int __cdecl main(int argc, char *argv[]) +{ + + PROCESS_INFORMATION pi; + STARTUPINFO si; + HANDLE hEvToHelper; + HANDLE hEvFromHelper; + DWORD dwExitCode; + + + DWORD dwRet; + char cmdComposeBuf[MAX_PATH]; + PWCHAR uniString; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* Create the signals we need for cross process communication */ + hEvToHelper = CreateEvent(NULL, TRUE, FALSE, szcToHelperEvName); + if (!hEvToHelper) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "GetLastError() returned %d.\n", szcToHelperEvName, + GetLastError()); + } + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "(already exists!)\n", szcToHelperEvName); + } + hEvFromHelper = CreateEvent(NULL, TRUE, FALSE, szcFromHelperEvName); + if (!hEvToHelper) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "GetLastError() returned %d.\n", szcFromHelperEvName, + GetLastError()); + } + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "(already exists!)\n", szcFromHelperEvName); + } + ResetEvent(hEvFromHelper); + ResetEvent(hEvToHelper); + + if (!sprintf(cmdComposeBuf, "helper %s", commsFileName)) + { + Fail("Could not convert command line\n"); + } + uniString = convert(cmdComposeBuf); + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + /* Create a new process. This is the process that will ask for + * memory munging */ + if(!CreateProcess( NULL, uniString, NULL, NULL, + FALSE, 0, NULL, NULL, &si, &pi)) + { + Trace("ERROR: CreateProcess failed to load executable '%S'. " + "GetLastError() returned %u.\n", + uniString, GetLastError()); + free(uniString); + Fail(""); + } + free(uniString); + + while(1) + { + FILE *commsFile; + char* pSrcMemory; + char* pDestMemory; + int Count; + SIZE_T wpmCount; + char incomingCMDBuffer[MAX_PATH + 1]; + + /* wait until the helper tells us that it has given us + * something to do */ + dwRet = WaitForSingleObject(hEvFromHelper, TIMEOUT); + if (dwRet != WAIT_OBJECT_0) + { + Trace("test1 WaitForSingleObjectTest: WaitForSingleObject " + "failed (%u)\n", GetLastError()); + break; /* no more work incoming */ + } + + /* get the parameters to test WriteProcessMemory with */ + if (!(commsFile = fopen(commsFileName, "r"))) + { + /* no file means there is no more work */ + break; + } + if ( NULL == fgets(incomingCMDBuffer, MAX_PATH, commsFile)) + { + Fail ("unable to read from communication file %s " + "for reasons %u & %u\n", + errno, GetLastError()); + } + PEDANTIC1(fclose,(commsFile)); + sscanf(incomingCMDBuffer, "%u %u", &pDestMemory, &Count); + if (argc > 1) + { + Trace("Preparing to write to %u bytes @ %u ('%s')\n", + Count, pDestMemory, incomingCMDBuffer); + } + + /* compose some data to write to the client process */ + if (!(pSrcMemory = malloc(Count))) + { + Trace("could not dynamically allocate memory to copy from " + "for reasons %u & %u\n", + errno, GetLastError()); + goto doneIteration; + } + memset(pSrcMemory, nextValue, Count); + + /* do the work */ + dwRet = WriteProcessMemory(pi.hProcess, + pDestMemory, + pSrcMemory, + Count, + &wpmCount); + if (!dwRet) + { + Trace("%s: Problem: on a write to %u bytes @ %u ('%s')\n", + argv[0], Count, pDestMemory, incomingCMDBuffer); + Trace("test1 WriteProcessMemory returned a%u(!=0) (GLE=%u)\n", + GetLastError()); + } + if(Count != wpmCount) + { + Trace("%s: Problem: on a write to %u bytes @ %u ('%s')\n", + argv[0], Count, pDestMemory, incomingCMDBuffer); + Trace("The number of bytes written should have been " + "%u, but was reported as %u.\n", Count, wpmCount); + } + free(pSrcMemory); + + doneIteration: + PEDANTIC(ResetEvent, (hEvFromHelper)); + PEDANTIC(SetEvent, (hEvToHelper)); + } + + /* wait for the child process to complete */ + WaitForSingleObject ( pi.hProcess, TIMEOUT ); + /* this may return a failure code on a success path */ + + /* check the exit code from the process */ + if( ! GetExitCodeProcess( pi.hProcess, &dwExitCode ) ) + { + Trace( "GetExitCodeProcess call failed with error code %u\n", + GetLastError() ); + dwExitCode = FAIL; + } + + + PEDANTIC(CloseHandle, (hEvToHelper)); + PEDANTIC(CloseHandle, (hEvFromHelper)); + PEDANTIC(CloseHandle, (pi.hThread)); + PEDANTIC(CloseHandle, (pi.hProcess)); + + PAL_TerminateEx(dwExitCode); + return dwExitCode; +} diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/testinfo.dat b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/testinfo.dat new file mode 100644 index 0000000000..0946f8f138 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test1/testinfo.dat @@ -0,0 +1,16 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Debug +Function = WriteProcessMemory +Name = Check that writing text to process memory succeeds. +TYPE = DEFAULT +EXE1 = test1 +EXE2 = helper +Description += Create a child process and attempt to write to its memory += at the places and lengths it specifies via a data file. += the child verifies that all the specified memory was altered += with no overruns. diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/CMakeLists.txt new file mode 100644 index 0000000000..ecc0e06dac --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test3.c +) + +add_executable(paltest_writeprocessmemory_test3 + ${TESTSOURCES} +) + +add_dependencies(paltest_writeprocessmemory_test3 coreclrpal) + +target_link_libraries(paltest_writeprocessmemory_test3 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + helper.c +) + +add_executable(paltest_writeprocessmemory_test3_helper + ${HELPERSOURCES} +) + +add_dependencies(paltest_writeprocessmemory_test3_helper coreclrpal) + +target_link_libraries(paltest_writeprocessmemory_test3_helper + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/commonconsts.h b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/commonconsts.h new file mode 100644 index 0000000000..c1cec18e2d --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/commonconsts.h @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Header: commonconsts.h +** +** +==============================================================*/ + +#ifndef _COMMONCONSTS_H_ +#define _COMMONCONSTS_H_ + +#include <pal.h> + +const int TIMEOUT = 40000; + +const WCHAR szcToHelperEvName[] = { 'T', 'o', '\0' }; +const WCHAR szcFromHelperEvName[] = { 'F', 'r', 'o', 'm', '\0' }; + +const char initialValue = '-'; +const char nextValue = '|'; +const char guardValue = '*'; +const char *commsFileName = "AddrNLen.dat"; + +/* PEDANTIC and PEDANTIC0 is a helper macro that just grumps about any + * zero return codes in a generic way. with little typing */ +#define PEDANTIC(function, parameters) \ +{ \ + unsigned int retval = (function parameters); \ + if ( !retval ) \ + { \ + Trace("%s: NonFatal failure of %s%s (returned %u) " \ + "for reasons %u and %u.\n", \ + __FILE__, #function, #parameters, retval, GetLastError(), errno); \ + } \ +} +#define PEDANTIC1(function, parameters) \ +{ \ + unsigned int retval = (function parameters); \ + if ( retval ) \ + { \ + Trace("%s: NonFatal failure of %s%s (returned %u) " \ + "for reasons %u and %u\n", \ + __FILE__, #function, #parameters, retval, GetLastError(), errno); \ + } \ +} + +#endif diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/helper.c b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/helper.c new file mode 100644 index 0000000000..170e2064cb --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/helper.c @@ -0,0 +1,256 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: helper.c + +** +==============================================================*/ + + +/* +** +** Purpose: This helper process sets up a several blocks of memory +** that should be unwritable from the parent process, then uses a file +** to tell its parent process where that memory is so it can attempt a +** WriteProcessMemory on it. When the parent process is done we check +** here that it was (properly) unable to change the contents of the +** memory. +*/ + +#include "commonconsts.h" + +#include <palsuite.h> + +struct allhandles_t +{ + HANDLE hEvToHelper; + HANDLE hEvFromHelper; + char *valuesFileName; +}; + + +/* function: wpmVerifyCant + * + * This is a general WriteProcessMemory testing function that sets up + * the RAM pointed to and tells the companion process on the other end + * of the handles in 'Comms' to attempt to alter 'lenDest' bytes at + * '*pDest'. + * + * However, the memory at pDest[0..lenDest] is expected to be unwritable by + * the companion process. The companion is expects this. This function + * verifies that no bytes were affected + */ + +int wpmVerifyCant(struct allhandles_t Comms, + char * pDest, unsigned int lenDest, + unsigned int lenLegitDest, + DWORD dwExpectedErrorCode, + const char* storageDescription) +{ + char *pCurr; + FILE *commsFile; + DWORD dwRet; + + unsigned int lenSafe = min(lenDest, lenLegitDest); + + PAL_TRY + { + memset(pDest, initialValue, lenSafe); + } + PAL_EXCEPT_EX (setup, EXCEPTION_EXECUTE_HANDLER) + { + Trace("WriteProcessMemory: bug in test values for '%s' (%p, %u, %u), " + "the initial memset threw an exception.\n", + storageDescription, pDest, lenDest, lenSafe); + } + PAL_ENDTRY; + + /* tell the parent what RAM to attempt to adjust */ + if(!(commsFile = fopen(Comms.valuesFileName, "w"))) + { + Trace("WriteProcessMemory: fopen of '%S' failed (%u). \n", + Comms.valuesFileName, GetLastError()); + return FALSE; + } + if (!fprintf(commsFile, "%u %u %u '%s'\n", + pDest, lenDest, dwExpectedErrorCode, storageDescription)) + { + Trace("WriteProcessMemory: fprintf to '%S' failed (%u). \n", + Comms.valuesFileName, GetLastError()); + return FALSE; + } + PEDANTIC1(fclose, (commsFile)); + + /* Tell the parent the data is ready for it to adjust */ + PEDANTIC(ResetEvent, (Comms.hEvToHelper)); + PEDANTIC(SetEvent, (Comms.hEvFromHelper)); + + dwRet = WaitForSingleObject(Comms.hEvToHelper, TIMEOUT); + if (dwRet != WAIT_OBJECT_0) + { + Trace("helper WaitForSingleObjectTest: WaitForSingleObject " + "failed (%u)\n", GetLastError()); + return FALSE; + } + + PAL_TRY + { + /* check the stuff (as much as we can) that should NOT have changed */ + for (pCurr = pDest; pCurr < (pDest + lenSafe); pCurr++ ) + { + if ( *pCurr != initialValue) + { + Trace("When testing '%s': real memory values preservation failed " + "at %u offset %u. Found '%c' instead of '%c'\n.", + storageDescription, pDest, pCurr - pDest, + *pCurr, initialValue); + return FALSE; + } + } + } + PAL_EXCEPT_EX (testing, EXCEPTION_EXECUTE_HANDLER) + { + Trace("WriteProcessMemory: bug in test values for '%s' (%p, %u, %u), " + "the verification pass threw an exception.\n", + storageDescription, pDest, lenDest, lenSafe); + } + PAL_ENDTRY; + + return TRUE; +} + +int __cdecl main(int argc, char *argv[]) +{ + BOOL success = TRUE; /* assume success */ + struct allhandles_t Comms = {0,0,0} ; + + SYSTEM_INFO sysinfo; + + char* Memory; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* hook up with the events created by the parent */ + Comms.hEvToHelper = OpenEventW(EVENT_ALL_ACCESS, 0, szcToHelperEvName); + if (!Comms.hEvToHelper) + { + Fail("WriteProcessMemory: OpenEvent of '%S' failed (%u). " + "(the event should already exist!)\n", + szcToHelperEvName, GetLastError()); + success = FALSE; + goto EXIT; + } + Comms.hEvFromHelper = OpenEventW(EVENT_ALL_ACCESS, 0, szcFromHelperEvName); + if (!Comms.hEvToHelper) + { + Trace("WriteProcessMemory: OpenEvent of '%S' failed (%u). " + "(the event should already exist!)\n", + szcFromHelperEvName, GetLastError()); + success = FALSE; + goto EXIT; + } + Comms.valuesFileName = argv[1]; + + /* test setup */ + GetSystemInfo(&sysinfo); + + { + unsigned int allocSize = sysinfo.dwPageSize * 2; + unsigned int writeLen = allocSize * 2; + + /* First test: overrun the allocated memory */ + Memory = (char*)VirtualAlloc(NULL, allocSize, + MEM_COMMIT, PAGE_READWRITE); + + if(Memory == NULL) + { + Fail("ERROR: Attempted to commit two pages, but the " + " VirtualAlloc call failed. " + "GetLastError() returned %u.\n",GetLastError()); + } + success &= wpmVerifyCant(Comms, Memory, writeLen, allocSize, + ERROR_INVALID_ADDRESS, + "should not write beyond committed allocation"); + + PEDANTIC1(VirtualFree, (Memory, allocSize, + MEM_DECOMMIT | MEM_RELEASE)); + } + + { + /* Allocate the memory as readonly */ + unsigned int allocSize = sysinfo.dwPageSize * 2; + unsigned int writeLen = allocSize; + + Memory = (char*)VirtualAlloc(NULL, allocSize, + MEM_COMMIT, PAGE_READONLY); + + if(Memory == NULL) + { + Fail("ERROR: Attempted to commit two pages readonly, but the " + " VirtualAlloc call failed. " + "GetLastError() returned %u.\n",GetLastError()); + } + success &= wpmVerifyCant(Comms, Memory, writeLen, 0, + ERROR_NOACCESS, + "should not write in READONLY allocation"); + + PEDANTIC1(VirtualFree, (Memory, allocSize, + MEM_DECOMMIT | MEM_RELEASE)); + } + + + { + /* attempt to write to memory that is not committed yet */ + unsigned int allocSize = sysinfo.dwPageSize * 2; + unsigned int writeLen = allocSize; + + Memory = (char*)VirtualAlloc(NULL, allocSize, + MEM_RESERVE, PAGE_NOACCESS); + + if(Memory == NULL) + { + Fail("ERROR: Attempted to reserve two pages, but the " + " VirtualAlloc call failed. " + "GetLastError() returned %u.\n",GetLastError()); + } + success &= wpmVerifyCant(Comms, Memory, writeLen, 0, + ERROR_INVALID_ADDRESS, + "should not write in memory that is" + " RESERVED but not COMMITTED"); + + PEDANTIC1(VirtualFree, (Memory, allocSize, MEM_RELEASE)); + } + + +EXIT: + /* Tell the parent that we are done */ + if (!DeleteFile(Comms.valuesFileName)) + { + Trace("helper: DeleteFile failed so parent (test1) is unlikely " + "to exit cleanly\n"); + } + PEDANTIC(ResetEvent, (Comms.hEvToHelper)); + if (!SetEvent(Comms.hEvFromHelper)) + { + Trace("helper: SetEvent failed so parent (test1) is unlikely " + "to exit cleanly\n"); + } + + PEDANTIC(CloseHandle, (Comms.hEvToHelper)); + PEDANTIC(CloseHandle, (Comms.hEvFromHelper)); + + if (!success) + { + Fail(""); + } + + PAL_Terminate(); + + return success ? PASS : FAIL; +} diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/test3.c b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/test3.c new file mode 100644 index 0000000000..063cb4cbec --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/test3.c @@ -0,0 +1,205 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: test3.c +** +** Purpose: Create a child process and debug it. When the child +** raises an exception, it sends back a memory location. Call +** WriteProcessMemory on the memory location, but attempt to write +** more than the memory allows. This should cause an error and the +** data should be unchanged. +** +** +==============================================================*/ + +#define UNICODE + +#include "commonconsts.h" + +#include <palsuite.h> + +int __cdecl main(int argc, char *argv[]) +{ + + PROCESS_INFORMATION pi; + STARTUPINFO si; + HANDLE hEvToHelper; + HANDLE hEvFromHelper; + DWORD dwExitCode; + + + DWORD dwRet; + BOOL success = TRUE; /* assume success */ + char cmdComposeBuf[MAX_PATH]; + PWCHAR uniString; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + /* Create the signals we need for cross process communication */ + hEvToHelper = CreateEvent(NULL, TRUE, FALSE, szcToHelperEvName); + if (!hEvToHelper) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "GetLastError() returned %u.\n", szcToHelperEvName, + GetLastError()); + } + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "(already exists!)\n", szcToHelperEvName); + } + hEvFromHelper = CreateEvent(NULL, TRUE, FALSE, szcFromHelperEvName); + if (!hEvToHelper) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "GetLastError() returned %u.\n", szcFromHelperEvName, + GetLastError()); + } + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + Fail("WriteProcessMemory: CreateEvent of '%S' failed. " + "(already exists!)\n", szcFromHelperEvName); + } + + if (!sprintf(cmdComposeBuf, "helper %s", commsFileName)) + { + Fail("Could not convert command line\n"); + } + uniString = convert(cmdComposeBuf); + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + /* Create a new process. This is the process that will ask for + * memory munging */ + if(!CreateProcess( NULL, uniString, NULL, NULL, + FALSE, 0, NULL, NULL, &si, &pi)) + { + Trace("ERROR: CreateProcess failed to load executable '%S'. " + "GetLastError() returned %u.\n", + uniString, GetLastError()); + free(uniString); + Fail(""); + } + free(uniString); + + while(1) + { + FILE *commsFile; + char* pSrcMemory; + char* pDestMemory; + int Count; + SIZE_T wpmCount; + DWORD dwExpectedErrorCode; + + char incomingCMDBuffer[MAX_PATH + 1]; + + /* wait until the helper tells us that it has given us + * something to do */ + dwRet = WaitForSingleObject(hEvFromHelper, TIMEOUT); + if (dwRet != WAIT_OBJECT_0) + { + Trace("test1 WaitForSingleObjectTest: WaitForSingleObject " + "failed (%u)\n", GetLastError()); + break; /* no more work incoming */ + } + + /* get the parameters to test WriteProcessMemory with */ + if (!(commsFile = fopen(commsFileName, "r"))) + { + /* no file means there is no more work */ + break; + } + if ( NULL == fgets(incomingCMDBuffer, MAX_PATH, commsFile)) + { + Trace ("unable to read from communication file %s " + "for reasons %u & %u\n", + errno, GetLastError()); + success = FALSE; + PEDANTIC1(fclose,(commsFile)); + /* it's not worth continuing this trial */ + goto doneIteration; + } + PEDANTIC1(fclose,(commsFile)); + sscanf(incomingCMDBuffer, "%u %u %u", + &pDestMemory, &Count, &dwExpectedErrorCode); + if (argc > 1) + { + Trace("Preparing to write to %u bytes @ %u ('%s')\n", + Count, pDestMemory, incomingCMDBuffer); + } + + /* compose some data to write to the client process */ + if (!(pSrcMemory = malloc(Count))) + { + Trace("could not dynamically allocate memory to copy from " + "for reasons %u & %u\n", + errno, GetLastError()); + success = FALSE; + goto doneIteration; + } + memset(pSrcMemory, nextValue, Count); + + /* do the work */ + dwRet = WriteProcessMemory(pi.hProcess, + pDestMemory, + pSrcMemory, + Count, + &wpmCount); + + if(dwRet != 0) + { + Trace("ERROR: Situation: '%s', return code: %u, bytes 'written': %u\n", + incomingCMDBuffer, dwRet, wpmCount); + Trace("ERROR: WriteProcessMemory did not fail as it should, as " + "it attempted to write to a range of memory which was " + "not completely accessible.\n"); + success = FALSE; + } + + if(GetLastError() != dwExpectedErrorCode) + { + Trace("ERROR: GetLastError() should have returned " + "%u , but instead it returned %u.\n", + dwExpectedErrorCode, GetLastError()); + success = FALSE; + } + free(pSrcMemory); + + doneIteration: + PEDANTIC(ResetEvent, (hEvFromHelper)); + PEDANTIC(SetEvent, (hEvToHelper)); + } + + + /* wait for the child process to complete */ + WaitForSingleObject ( pi.hProcess, TIMEOUT ); + /* this may return a failure code on a success path */ + + /* check the exit code from the process */ + if( ! GetExitCodeProcess( pi.hProcess, &dwExitCode ) ) + { + Trace( "GetExitCodeProcess call failed with error code %u\n", + GetLastError() ); + dwExitCode = FAIL; + } + if(!success) + { + dwExitCode = FAIL; + } + + PEDANTIC(CloseHandle, (hEvToHelper)); + PEDANTIC(CloseHandle, (hEvFromHelper)); + PEDANTIC(CloseHandle, (pi.hThread)); + PEDANTIC(CloseHandle, (pi.hProcess)); + + PAL_Terminate(dwExitCode); + return dwExitCode; +} diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/testinfo.dat b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/testinfo.dat new file mode 100644 index 0000000000..23ad3ae567 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test3/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Debug +Function = WriteProcessMemory +Name = Check that you can't write from writable to protected memory. +TYPE = DEFAULT +EXE1 = test3 +EXE2 = helper +Description += Create a child process and debug it. When the child += raises an exception, it sends back a memory location. Call += WriteProcessMemory on the memory location, but attempt to write += more than the memory allows. This should cause an error and the += data should be unchanged. diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/CMakeLists.txt b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/CMakeLists.txt new file mode 100644 index 0000000000..cf1ce13862 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 2.8.12.2) + +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +set(TESTSOURCES + test4.c +) + +add_executable(paltest_writeprocessmemory_test4 + ${TESTSOURCES} +) + +add_dependencies(paltest_writeprocessmemory_test4 coreclrpal) + +target_link_libraries(paltest_writeprocessmemory_test4 + pthread + m + coreclrpal +) + + +set(HELPERSOURCES + helper.c +) + +add_executable(paltest_writeprocessmemory_test4_helper + ${HELPERSOURCES} +) + +add_dependencies(paltest_writeprocessmemory_test4_helper coreclrpal) + +target_link_libraries(paltest_writeprocessmemory_test4_helper + pthread + m + coreclrpal +) diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/helper.c b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/helper.c new file mode 100644 index 0000000000..b653ea5057 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/helper.c @@ -0,0 +1,67 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: helper.c +** +** Purpose: This helper process sets up a block of memory, then +** raises an exception to pass that memory location back to the +** parent process. When the parent process is done calling WriteProcessMemory +** we check here that it was written properly. +** +** +**============================================================*/ + +#include <palsuite.h> +const int MY_EXCEPTION=999; + +int __cdecl main(int argc, char *argv[]) +{ + + char* Memory; + char* TheArray[1]; + int i; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + Memory = (char*)VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READONLY); + + if(Memory == NULL) + { + Fail("ERROR: Attempted to allocate two pages, but the VirtualAlloc " + "call failed. GetLastError() returned %d.\n",GetLastError()); + } + + + TheArray[0] = Memory; + + + /* Need to sleep for a couple seconds. Otherwise this process + won't be being debugged when the first exception is raised. + */ + Sleep(4000); + + RaiseException(MY_EXCEPTION, 0, 1, (ULONG_PTR*)TheArray); + + for(i=0; i<4096; ++i) + { + if(Memory[i] != '\0') + { + Fail("ERROR: The memory should be unchanged after the " + "invalid call to WriteProcessMemory, but the char " + "at index %d has changed.\n",i); + } + } + + + + + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/test4.c b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/test4.c new file mode 100644 index 0000000000..51db23499b --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/test4.c @@ -0,0 +1,124 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================= +** +** Source: test4.c +** +** Purpose: Create a child process and debug it. When the child +** raises an exception, it sends back a memory location. Call +** WriteProcessMemory on a restricted memory location and ensure that +** it fails. +** +** +**============================================================*/ + +#include <palsuite.h> +const int MY_EXCEPTION=999; + +int __cdecl main(int argc, char *argv[]) +{ + + PROCESS_INFORMATION pi; + STARTUPINFO si; + DEBUG_EVENT DebugEv; + DWORD dwContinueStatus = DBG_CONTINUE; + int Count, ret; + char* DataBuffer[4096]; + char* Memory; + + if(0 != (PAL_Initialize(argc, argv))) + { + return FAIL; + } + + ZeroMemory( &si, sizeof(si) ); + si.cb = sizeof(si); + ZeroMemory( &pi, sizeof(pi) ); + + memset(DataBuffer, 'z', 4096); + + /* Create a new process. This is the process to be Debugged */ + if(!CreateProcess( NULL, "helper", NULL, NULL, + FALSE, 0, NULL, NULL, &si, &pi)) + { + Fail("ERROR: CreateProcess failed to load executable 'helper'. " + "GetLastError() returned %d.\n",GetLastError()); + } + + /* Call DebugActiveProcess, because the process wasn't created as a + debug process. + */ + if(DebugActiveProcess(pi.dwProcessId) == 0) + { + Fail("ERROR: Failed calling DebugActiveProcess on the process " + "which was created to debug. GetLastError() returned %d.\n", + GetLastError()); + } + + + /* Call WaitForDebugEvent, which will wait until the helper process + raises an exception. + */ + + while(1) + { + if(WaitForDebugEvent(&DebugEv, INFINITE) == 0) + { + Fail("ERROR: WaitForDebugEvent returned 0, indicating failure. " + "GetLastError() returned %d.\n",GetLastError()); + } + + /* We're waiting for the helper process to send this exception. + When it does, we call WriteProcess. If it gets called more than + once, it is ignored. + */ + + if(DebugEv.u.Exception.ExceptionRecord.ExceptionCode == MY_EXCEPTION) + { + + Memory = (LPVOID) + DebugEv.u.Exception.ExceptionRecord.ExceptionInformation[0]; + + /* Write to this memory which we have no access to. */ + + ret = WriteProcessMemory(pi.hProcess, + Memory, + DataBuffer, + 4096, + &Count); + + if(ret != 0) + { + Fail("ERROR: WriteProcessMemory should have failed, as " + "it attempted to write to a range of memory which was " + "not accessible.\n"); + } + + if(GetLastError() != ERROR_NOACCESS) + { + Fail("ERROR: GetLastError() should have returned " + "ERROR_NOACCESS , but intead it returned " + "%d.\n",GetLastError()); + } + } + + if(DebugEv.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) + { + break; + } + + if(ContinueDebugEvent(DebugEv.dwProcessId, + DebugEv.dwThreadId, dwContinueStatus) == 0) + { + Fail("ERROR: ContinueDebugEvent failed to continue the thread " + "which had a debug event. GetLastError() returned %d.\n", + GetLastError()); + } + } + + + PAL_Terminate(); + return PASS; +} diff --git a/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/testinfo.dat b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/testinfo.dat new file mode 100644 index 0000000000..c6f4edb5d6 --- /dev/null +++ b/src/pal/tests/palsuite/debug_api/WriteProcessMemory/test4/testinfo.dat @@ -0,0 +1,17 @@ +# Licensed to the .NET Foundation under one or more agreements. +# The .NET Foundation licenses this file to you under the MIT license. +# See the LICENSE file in the project root for more information. + +Version = 1.0 +Section = Debug +Function = WriteProcessMemory +Name = Check that you can't write to protected memory. +TYPE = DEFAULT +EXE1 = test4 +EXE2 = helper +Description += Create a child process and debug it. When the child += raises an exception, it sends back a memory location. Call += WriteProcessMemory on a restricted memory location and ensure that += it fails. + |