So, I tried to run my go code on C++ project with dynamic loading. It's working great, except there is some unwanted string on returned value. As I explained down, I got some information from Go that unwanted.
My go code:
package main
import "C"
func main() {}
//export GetTestString
func GetTestString() string {
return "test"
}
I build it with:
go build -buildmode=c-shared -o test.so test.go
Dynamically load it on my CPP project with this function:
typedef struct { const char *p; ptrdiff_t n; } GoString;
void getTestString() {
void *handle;
char *error;
handle = dlopen ("./test.so", RTLD_LAZY);
if (!handle) {
fputs (dlerror(), stderr);
exit(1);
}
// resolve getTestString symbol and assign to fn ptr
auto getTestString = (GoString (*)())dlsym(handle, "GetTestString");
if ((error = dlerror()) != NULL) {
fputs(error, stderr);
exit(1);
}
// call GetTestString()
GoString testString = (*getTestString)();
printf("%s\n", testString.p);
// close file handle when done
dlclose(handle);
}
Output is:
"testtrue ...\n H_T= H_a= H_g= MB, W_a= and cnt= h_a= h_g= h_t= max= ptr siz= tab= top= u_a= u_g=, ..., fp:argp=falsefaultgcingpanicsleepsse41sse42ssse3 (MB)\n addr= base code= ctxt: curg= goid jobs= list= m->p= next= p->m= prev= span= varp=(...)\n, not SCHED efenceerrno objectpopcntscvg: selectsweep (scan (scan) MB in dying= locks= m->g0= nmsys= s=nil\n, goid=, size=, sys: GODEBUGIO waitSignal \ttypes \tvalue=cs fs gctracegs panic: r10 r11 r12 r13 r14 r15 r8 r9 rax rbp rbx rcx rdi rdx rflags rip rsi rsp runningsignal syscallunknownwaiting etypes goalĪ= is not mcount= minutes nalloc= newval= nfree..."
When passing strings via pointer to C you need either use length (n) in GoString to fetch right number of characters as string at p is not \0 terminated. Or you can return *C.char instead of string and use C.CString() to allocate copy on C heap (which you then are responsible for freeing after use). See Cgo documentation here.
What is happening in your code is that printf() simply prints all characters starting from location pointed to by string.p until it hits \0 terminator - that's why you see contents of memory after test.
So you can do either something like:
printf("%.*s\n", testString.n, testString.p);
(but note that most functions that operate on C strings which are expected to be \0 terminated will not work on this pointer unless they also take length of string)
or change Go part to something like this and then free() pointer after use on C side:
func GetTestString() *C.char {
return C.CString("test") // CString will allocate string on C heap
}
Related
I have the following code:
#include <windows.h>
#include <minidumpapiset.h>
#include <strsafe.h>
#include <fileapi.h>
#include <iostream>
#include <signal.h>
#include <minwinbase.h>
#include <new.h>
#include "StackWalker.h"
int minidumpId = 0;
#ifndef _AddressOfReturnAddress
// Taken from: http://msdn.microsoft.com/en-us/library/s975zw7k(VS.71).aspx
#ifdef __cplusplus
#define EXTERNC extern "C"
#else
#define EXTERNC
#endif
// _ReturnAddress and _AddressOfReturnAddress should be prototyped before use
EXTERNC void* _AddressOfReturnAddress(void);
EXTERNC void* _ReturnAddress(void);
EXTERNC int __cdecl _purecall();
#endif
EXCEPTION_POINTERS ExceptionPointers;
EXCEPTION_RECORD ExceptionRecord;
CONTEXT ContextRecord;
void GetExceptionPointers(DWORD exceptionCode, EXCEPTION_POINTERS** exceptionPointers)
{
// The following code was taken from VC++ 8.0 CRT (invarg.c: line 104)
ZeroMemory(&ExceptionPointers, sizeof(EXCEPTION_POINTERS));
ZeroMemory(&ExceptionRecord, sizeof(EXCEPTION_RECORD));
ZeroMemory(&ContextRecord, sizeof(CONTEXT));
// Looks like a workaround for some bug in RtlCaptureContext. But no description.
#ifdef _X86_
__asm {
mov dword ptr[ContextRecord.Eax], eax
mov dword ptr[ContextRecord.Ecx], ecx
mov dword ptr[ContextRecord.Edx], edx
mov dword ptr[ContextRecord.Ebx], ebx
mov dword ptr[ContextRecord.Esi], esi
mov dword ptr[ContextRecord.Edi], edi
mov word ptr[ContextRecord.SegSs], ss
mov word ptr[ContextRecord.SegCs], cs
mov word ptr[ContextRecord.SegDs], ds
mov word ptr[ContextRecord.SegEs], es
mov word ptr[ContextRecord.SegFs], fs
mov word ptr[ContextRecord.SegGs], gs
pushfd
pop[ContextRecord.EFlags]
}
ContextRecord.ContextFlags = CONTEXT_CONTROL;
#pragma warning(push)
#pragma warning(disable : 4311)
ContextRecord.Eip = (ULONG)_ReturnAddress();
ContextRecord.Esp = (ULONG)_AddressOfReturnAddress();
#pragma warning(pop)
ContextRecord.Ebp = *(static_cast<ULONG*>(_AddressOfReturnAddress()) - 1);
#elif defined(_IA64_) || defined(_AMD64_) || defined(_ARM_) || defined(_ARM64_)
CaptureContext(&ContextRecord);
#else /* defined (_IA64_) || defined (_AMD64_) || defined(_ARM_) || defined(_ARM64_) */
ZeroMemory(&ContextRecord, sizeof(ContextRecord));
#endif /* defined (_IA64_) || defined (_AMD64_) || defined(_ARM_) || defined(_ARM64_) */
ExceptionRecord.ExceptionCode = exceptionCode;
ExceptionRecord.ExceptionAddress = _ReturnAddress();
ExceptionRecord.ExceptionFlags = EXCEPTION_NONCONTINUABLE;
*exceptionPointers = &ExceptionPointers;
(*exceptionPointers)->ExceptionRecord = &ExceptionRecord;
(*exceptionPointers)->ContextRecord = &ContextRecord;
}
class DbgLibrary final
{
public:
DbgLibrary()
{
dbgLibrary = LoadLibraryW(L"dbghelp.dll");
}
~DbgLibrary()
{
FreeLibrary(dbgLibrary);
}
explicit operator bool() const
{
return dbgLibrary != NULL;
}
bool WriteMinidump(HANDLE file, EXCEPTION_POINTERS* exceptionPointers) const
{
MINIDUMP_EXCEPTION_INFORMATION exceptionInformation;
exceptionInformation.ThreadId = GetCurrentThreadId();
exceptionInformation.ExceptionPointers = exceptionPointers;
exceptionInformation.ClientPointers = FALSE;
MINIDUMP_CALLBACK_INFORMATION callbackInformation;
callbackInformation.CallbackRoutine = NULL;
callbackInformation.CallbackParam = NULL;
typedef BOOL(WINAPI* LPMINIDUMPWRITEDUMP)(HANDLE processHandle, DWORD ProcessId, HANDLE fileHandle,
MINIDUMP_TYPE DumpType, CONST PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam,
CONST PMINIDUMP_USER_STREAM_INFORMATION UserEncoderParam,
CONST PMINIDUMP_CALLBACK_INFORMATION CallbackParam);
LPMINIDUMPWRITEDUMP pfnMiniDumpWriteDump =
(LPMINIDUMPWRITEDUMP)GetProcAddress(dbgLibrary, "MiniDumpWriteDump");
if (NULL == pfnMiniDumpWriteDump)
{
return false;
}
BOOL isWriteSucceed = pfnMiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), file, MiniDumpNormal,
&exceptionInformation, NULL, &callbackInformation);
return isWriteSucceed;
}
private:
HMODULE dbgLibrary;
};
inline HANDLE CreateNativeFile(const wchar_t* filePath)
{
HANDLE file = NULL;
file = CreateFileW(filePath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
return file;
}
void CreateMiniDump(PEXCEPTION_POINTERS exceptionPointers)
{
const DbgLibrary dbgLibrary;
if (dbgLibrary)
{
wchar_t FILE_PATH[4096];
// Write `exceptionPointers` to the minidump file
StringCbPrintfW(FILE_PATH, sizeof(FILE_PATH), L"%ls\\%ls_%ld.dmp", ".",
L"minidump", minidumpId++);
HANDLE hMinidump = CreateNativeFile(FILE_PATH);
if (hMinidump != INVALID_HANDLE_VALUE)
{
dbgLibrary.WriteMinidump(hMinidump, exceptionPointers);
CloseHandle(hMinidump);
}
}
}
LONG WINAPI SehHandler(PEXCEPTION_POINTERS exceptionPointers)
{
std::cerr << "SehHandler\n";
CreateMiniDump(exceptionPointers);
return EXCEPTION_EXECUTE_HANDLER;
}
void SigsegvHandler(int)
{
std::cerr << "SigsegvHandler\n";
PEXCEPTION_POINTERS exceptionPointers = static_cast<PEXCEPTION_POINTERS>(_pxcptinfoptrs);
// Write minidump file
CreateMiniDump(exceptionPointers);
}
int __cdecl NewHandler(size_t size)
{
std::cerr << "NewHandler\n";
// 'new' operator memory allocation exception
PEXCEPTION_POINTERS exceptionPointers;
GetExceptionPointers(STATUS_NO_MEMORY, &exceptionPointers);
CreateMiniDump(exceptionPointers);
return 0;
}
struct A5 {
void F()
{
while (true)
{
int* a = new int[50000000];
}
}
};
struct A4 {
A5 a;
void F()
{
a.F();
}
};
struct A3 {
A4 a;
void F()
{
a.F();
}
};
struct A2 {
A3 a;
void F()
{
a.F();
}
};
struct A1 {
A2 a;
void F()
{
a.F();
}
};
int main()
{
SetUnhandledExceptionFilter(SehHandler);
signal(SIGSEGV, SigsegvHandler);
_set_new_handler(NewHandler);
A1().F();
return 0;
}
Here two handlers would be invoked: NewHandler and SehHandler. The first one because of bad_alloc in operator new[], the second one because of unhandled exception. In both handlers I create minidump with information about crash.
NewHandler:
Thread 0 (crashed)
0 StackWalker_VC2017.exe!_callnewh [new_handler.cpp : 79 + 0x2]
eip = 0x0040a636 esp = 0x0019fefc ebp = 0x0019ff08 ebx = 0x00311000
esi = 0x00401d10 edi = 0x00655368 eax = 0x0042eed0 ecx = 0x00000000
edx = 0x00655368 efl = 0x00000202
Found by: given as instruction pointer in context
1 StackWalker_VC2017.exe!operator new(unsigned int) [new_scalar.cpp : 40 + 0x8]
eip = 0x00404a05 esp = 0x0019ff10 ebp = 0x0019ff14
Found by: call frame info
2 StackWalker_VC2017.exe!A5::F() [main.cpp : 197 + 0xa]
eip = 0x00401d0a esp = 0x0019ff1c ebp = 0x0019ff28
Found by: call frame info
3 StackWalker_VC2017.exe!main [main.cpp : 239 + 0x8]
eip = 0x00402500 esp = 0x0019ff24 ebp = 0x0019ff28
Found by: call frame info
4 StackWalker_VC2017.exe!static int __scrt_common_main_seh() [exe_common.inl : 288 + 0x1c]
eip = 0x00404c5d esp = 0x0019ff30 ebp = 0x0019ff70
Found by: call frame info
5 kernel32.dll + 0x1fa29
eip = 0x7712fa29 esp = 0x0019ff78 ebp = 0x0019ff80
Found by: call frame info
6 ntdll.dll + 0x67a9e
eip = 0x77c97a9e esp = 0x0019ff88 ebp = 0x0019ffdc
Found by: previous frame's frame pointer
7 ntdll.dll + 0x67a6e
eip = 0x77c97a6e esp = 0x0019ffe4 ebp = 0x0019ffec
Found by: previous frame's frame pointer
SehHandler:
Thread 0 (crashed)
0 KERNELBASE.dll + 0x12b812
eip = 0x76ddb812 esp = 0x0019fe68 ebp = 0x0019fec4 ebx = 0x19930520
esi = 0x00645a90 edi = 0x0042c754 eax = 0x0019fe68 ecx = 0x00000003
edx = 0x00000000 efl = 0x00000212
Found by: given as instruction pointer in context
1 StackWalker_VC2017.exe!_CxxThrowException [throw.cpp : 74 + 0x19]
eip = 0x00405a98 esp = 0x0019fecc ebp = 0x0019fef4
Found by: previous frame's frame pointer
2 StackWalker_VC2017.exe!__scrt_throw_std_bad_alloc() [throw_bad_alloc.cpp : 35 + 0x16]
eip = 0x0040509c esp = 0x0019fefc ebp = 0x0019ff10
Found by: call frame info
3 StackWalker_VC2017.exe!main [main.cpp : 239 + 0x8]
eip = 0x00402500 esp = 0x0019ff24 ebp = 0x0019ff14
Found by: call frame info with scanning
Extracted stacks using breakpad minidump_stackwalk:
The question is why SehHandler stacktrace does not have all function calls?
The main problem is that in project I use crash handlers for logging information in dumps. But creating minidump on each NewHandler call is not inappropriate solution, because sometimes bad_alloc could be fixed and exception thrown in try/catch block, that means that it is expected behaviour. So I want to handle bad_alloc in unhandled exception handler, so that it would definitely be crash. Also problem occurs only in release builds.
As mentioned in https://developercommunity.visualstudio.com/t/stdbad-alloc-failures-are-undebuggable/542559?viewtype=solutions it is bug in msvc. Unfortunately there is no good solution for release builds.
I'm trying to use MS detours, and I don't know if I am doing something wrong; I cannot seem to find an answer to my issue.
I have tried detouring several functions in a process using my injected DLL, but each attempt causes the process to crash.
One of the functions I try to hook is winapi DirectDrawCreate:
DetourTransactionBegin();
DetourUpdateThread( GetCurrentThread() );
DetourAttach( (PVOID *)DirectDrawCreate, hkDirectDrawCreate );
DetourTransactionCommit();
hkDirectDrawCreate is defined as:
HRESULT __stdcall hkDirectDrawCreate( GUID *p1, LPDIRECTDRAW *p2, IUnknown *p3 )
{
if( !pDDC )
return 0x00;
printf( "A call to hkDirectDrawCreate was made\n" );
return DirectDrawCreate( p1, p2, p3 );
}
On the call to DetourAttach the process crashes; the stack trace is:
myProj.dll!detour_skip_jmp(unsigned char * pbCode, void * * ppGlobals) Line 135 C++
myProj.dll!DetourCodeFromPointer(void * pPointer, void * * ppGlobals) Line 984 C++
myProj.dll!DetourAttachEx(void * * ppPointer, void * pDetour, _DETOUR_TRAMPOLINE * * ppRealTrampoline, void * * ppRealTarget, void * * ppRealDetour) Line 1456 C++
myProj.dll!DetourAttach(void * * ppPointer, void * pDetour) Line 1395 C++
The code breaks in 'detour_skip_jmp' at '0x68B028BD':
// First, skip over the import vector if there is one.
if (pbCode[0] == 0xff && pbCode[1] == 0x25) { // jmp [imm32]
68B028B2 mov ecx,1
68B028B7 imul edx,ecx,0
68B028BA mov eax,dword ptr [pbCode]
68B028BD movzx ecx,byte ptr [eax+edx]
68B028C1 cmp ecx,0FFh
68B028C7 jne detour_skip_jmp+82h (68B02912h)
68B028C9 mov edx,1
68B028CE shl edx,0
68B028D1 mov eax,dword ptr [pbCode]
68B028D4 movzx ecx,byte ptr [eax+edx]
68B028D8 cmp ecx,25h
68B028DB jne detour_skip_jmp+82h (68B02912h)
Edit: ppGlobals is NULL, and pbCode gives the error 'Error reading characters of string'
Going back to DetourCodeFromPointer ppGlobals is also NULL there, but I guess it is supposed to be; here is the call:
pDetour = DetourCodeFromPointer(pDetour, NULL);
No doubt the import table has been moved or scrubbed as an anti-hooking technique. Just add a jump at the start of DirectDrawCreate to your hkDirectDrawCreate, then when calling the original jump back to DirectDrawCreate, but be sure it is after your jump to your hook otherwise you're stuck in an endless recursive loop.
I've got a problem with interrupt flag being reset.
After setting the interrupt flag to 0 with asm cli, it comes to a line of code char* c = new char[size], and when it finishes initializing that array, it resets the I flag to 1. How can i make it that flag I stays on current value the whole time, because writing another asm cli after the new[] operator isn't the solution, giving that i have gap between those 2 instructions which enables the interrupts?
Thread::Thread(StackSize stackSize, Time timeSlice) {
#ifndef BCC_BLOCK_IGNORE
DIS_INT // a macro: #define DIS_INT asm cli
#endif
myPCB = new PCB(stackSize,timeSlice,this);
#ifndef BCC_BLOCK_IGNORE
ENB_INT
#endif
}
this is where i set I flag to 0 and call the PCB constructor
PCB::PCB(StackSize stackSize, Time timeSlice, Thread* thread){
time = timeSlice;
myThread = thread;
stack = createStack(stackSize);
...
char* PCB::createStack(StackSize stackSize){
char* stek = new char[stackSize]; // after this line, IF = 1
#ifndef BCC_BLOCK_IGNORE // which is not desired
newSS = FP_SEG(stek+stackSize);
newSP = FP_OFF(stek+stackSize);
asm{
mov oldSS, ss
mov oldSP, sp
mov ss, newSS
mov sp, newSP
push ax
push bx
push cx
push dx
push es
push ds
push si
push di
push bp
mov newSS, ss
mov newSP, sp
mov ss, oldSS
mov sp, oldSP
}
this->stekp = MK_FP(newSS,newSP);
#endif
return stek;
}
Say, I can use Wow64DisableWow64FsRedirection API to disable file system redirection, but is there a way to know if the thread is currently being redirected? In other words, is there an API like GetWow64FsRedirection?
There is no API function that reports this state. You are expected to remember that you disabled redirection.
Sorry, forgot to post a follow-up. As the accepted answer suggests, there's no API to detect that. Too bad, because the information is stored right there in the undocumented section of the thread's TEB struct. (See my comments in the code.)
The code below will retrieve it.
I have to preface it though by saying that it was obtained by reversing the aforementioned API. So it's a highly undocumented stuff that will probably break in the future versions of the OS. So make sure to put version safeguards before using it. It should be OK for all released versions of Windows though, including Windows 10 build 17134:
enum YESNOERR{
ERR = -1,
NO = 0,
YES = 1,
};
struct PROC_STATS{
BOOL b32BitProcessOn64BitOS;
DWORD dwOS_Major;
DWORD dwOS_Minor;
DWORD dwOS_Build;
PROC_STATS()
{
BOOL (WINAPI *pfnIsWow64Process)(HANDLE, PBOOL);
(FARPROC&)pfnIsWow64Process = ::GetProcAddress(::GetModuleHandle(_T("kernel32.dll")), "IsWow64Process");
BOOL bWow64 = FALSE;
b32BitProcessOn64BitOS = pfnIsWow64Process && pfnIsWow64Process(::GetCurrentProcess(), &bWow64) && bWow64;
LONG (WINAPI *pfnRtlGetVersion)(RTL_OSVERSIONINFOEXW*);
(FARPROC&)pfnRtlGetVersion = ::GetProcAddress(::GetModuleHandle(_T("ntdll.dll")), "RtlGetVersion");
OSVERSIONINFOEX osvi = {0};
osvi.dwOSVersionInfoSize = sizeof(osvi);
pfnRtlGetVersion(&osvi);
dwOS_Major = osvi.dwMajorVersion;
dwOS_Minor = osvi.dwMinorVersion;
dwOS_Build = osvi.dwBuildNumber;
}
};
PROC_STATS procStats;
YESNOERR __cdecl GetWow64FsRedirection()
{
//Checks if Wow64 file system redirection is on for the current thread
YESNOERR res = ERR;
__try
{
if(procStats.b32BitProcessOn64BitOS)
{
//Really easy pre-Win10 v.10.0.10041.0
if(procStats.dwOS_Major < 10 ||
(procStats.dwOS_Major == 10 && procStats.dwOS_Build <= 10041))
{
//Win XP, 7, 8.1 & earlier builds of Win10
__asm
{
mov eax, fs:18h ; TEB
mov eax, [eax + 0F70h]
mov eax, [eax + 14C0h]
xor ecx, ecx
test eax, eax ; 0=Wow64FsRedir is on, 1=Off
setz cl
mov [res], ecx
}
}
else
{
//Latest builds of Win10 have a separate WoW TEB block
__asm
{
mov eax, fs:18h ; TEB
mov ecx, [eax + 0FDCh] ; WowTebOffset
test ecx, ecx
jns lbl_no_offset ; it must precede TEB
add eax, ecx
lbl_no_offset:
cmp eax, [eax + 18h] ; pick version of the struct
jz lbl_alt
mov eax, [eax + 14C0h]
jmp lbl_check
lbl_alt:
mov eax, [eax + 0E30h]
lbl_check:
xor ecx, ecx
test eax, eax ; 0=Wow64FsRedir is on, 1=Off
setz cl
mov [res], ecx
}
}
}
else
{
//It's off by default
res = NO;
}
}
__except(1)
{
//Oops, too far in the future -- this no longer works
res = ERR;
}
return res;
}
This is how you can test it:
resWow64FsOn = GetWow64FsRedirection();
_tprintf(L"Wow64FsRedirection=%d\n", resWow64FsOn);
void* pOldV;
if(::Wow64DisableWow64FsRedirection(&pOldV))
{
resWow64FsOn = GetWow64FsRedirection();
_tprintf(L"Wow64FsRedirection=%d\n", resWow64FsOn);
::Wow64RevertWow64FsRedirection(pOldV);
resWow64FsOn = GetWow64FsRedirection();
_tprintf(L"Wow64FsRedirection=%d\n", resWow64FsOn);
}
else
{
_tprintf(L"ERROR: (%d) API Failed\n", ::GetLastError());
}
Another approach is to check for the existence of the wow32.dll in the Windows System32 directory (typically C:\Windows\System32).
On 64-bit systems, this file should reside in the SysWOW64 directory, thus if file redirection is enabled, it will be found.
Similarly, one can check for the nonexistence of wow64.dll, that resides in the System32 directory on 64-bit systems, and if it's not to be found the redirection is enabled.
The pseudo-code for that would be:
bool IsWow64FileSystemRedirectionEnabled()
{
if (!Is64BitOS()) return false;
if (FileExists(GetSystem32Directory() + "\\wow32.dll")) return true;
return false;
}
Where:
Is64BitOS can be implemented as shown here
FileExists can be implemented as shown here
GetSystem32Directory - can be implemented as shown here
I'm trying to call a function in my dll.
the DLL is injected into ANOTHER PROCESS so i need to be able to call the exported function after it's been injected into a target process.
my exported function looks like this:
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
EXTERN_DLL_EXPORT void InjectPacketToServer(unsigned char *packet, int length)
{
int value;
int senderoffset = 0x0075F8D8;
__asm
{
mov eax, senderoffset
mov value, eax
}
memcpy((void*)SEND_CODE_CAVE, (void*)packet, length);
int SenderID = *(int*)value;
int PacketLength = length;
int Send = 0x00577A90;
__asm
{
mov edx, PacketLength
push edx
mov eax, SEND_CODE_CAVE
push eax
mov ecx, [SenderID]
call Send
}
}
I am trying to call it like this:
#include <Windows.h>
typedef int (*InjectPacketToServer)(unsigned char *packet, int length);
InjectPacketToServer Inject;
BYTE packet[3] = { 0x13, 0x01, 0x01};
int length = 3;
int main()
{
HRESULT ret;
HMODULE pModule;
pModule = LoadLibrary("baram.dll");
ret = GetLastError();
Inject = (InjectPacketToServer)GetProcAddress(pModule, "InjectPacketToServer");
ret = GetLastError();
Inject(packet, length);
return ret;
}
I'm getting errors:
ret 0x000003e6 : Invalid access to memory location. HRESULT
on this line:
pModule = LoadLibrary("baram.dll");
can somebody Please tell me what I'm doing wrong here?
help appreciated!
Did you google?
MS support says the cause is:
The Windows NT status code STATUS_ACCESS_VIOLATION is mapped to the Win32 error code ERROR_NOACCESS. As a result, if the operating system loader encounters an access violation (exception C0000005) while mapping the specified DLL file image or executing the startup code, the loader will set the last error to 998 (ERROR_NOACCESS) and the LoadLibrary() function will fail with a return value of NULL.
and you should
To troubleshoot the LoadLibrary() failure, run the application under a debugger and enable first chance exception handling for the C0000005 Access Violation exception. If an access violation occurs when the LoadLibrary() function is called, the application will break into the debugger. The debugger's call stack can then be used to trace where the exception occurred. The stack trace should help you narrow down the actual problem related to the exception being encountered.