Related
I'm working on a software product that runs intensive operations on the main thread. Running them on a separate thread is not supported by design and won't be changed.
At the same time we need to handle mouse movements coming from UI. In one case mouse cursor freezes because the main thread is being busy with computations.
Seems a good case for introducing asynchronous operation: run computations asynchronously in a separate thread while main thread is still handling mouse movements. But as I said before it is not supported in the current design.
Recently I came across an idea to run two tasks asynchronously in one thread. Meaning that thread context is switched between two tasks and each task is partially executed for a quantum of time until each of them gets finished.
Is this possible in C++? The version of the language (11 or 14) does not matter.
The software uses WinApi and standard message queue to receive mouse events.
Tried to look at Microsoft PPL but from my understanding the lib does not help in this case.
Thanks everyone for help.
What you are looking for is cooperative multi-tasking. This is possible on a single thread. You can take a look at coroutines, e.g. in boost or the standard library (since C++20).
You can also roll your own, stripped down version. The key ingerdients are:
Each task needs to store its context (e.g. parameters) itself
Each task needs a way to suspend and resume operations. It decides on its own when to suspend.
You might need some form of scheduler that keeps track of all the tasks and run them frequently. You might want to design it in a way that the GUI main loop calls into your scheduler which runs for approximately 30-50 ms at most by passing the available time budget to each of the tasks it keeps track of.
This is quite feasible if threads are not an option at all.
Boost.Coroutine, Boost.Context, and Boost.Asio all support single thread concurrency at some level or another. Coroutines are cooperative, reentrant, interruptible, resumable functions. Context is user land context switching. Asio executors can schedule many different tasks to run on one thread. For your case, I think you can take your pick as to what you're comfortable putting into your application.
EDIT
Boost.Fiber implements mini thread-like "fibers" on top of the Context library.
Here is how I would implement my own run to completion cooperative multitasking:
enum class eStep
{
START,
STEP1,
STEP2,
DONE
};
struct sLongFuncContext
{
//whatver is meaning full to go from one step to the next
};
eStep long_func_split_in_steps(eStep aStep,sLongFuncContext &aContext)
{
eStep next;
switch (aStep)
{
case eStep::START:
// execute first part of func, save context
next = eStep::STEP1;
break;
case eStep::STEP1:
// execute 2nd part of func, save context
next = eStep::STEP2;
break;
case eStep::STEP2:
next = eStep::DONE;
break;
// repeat
};
return (next);
}
int main()
{
eStep step = eStep::START;
sLongFuncContext context;
while (step != eStep::DONE)
{
// do a part of the long function
step = long_func_split_in_steps(step,context);
// handle mouse events
// ...
}
return 0;
}
Since you are targeting windows but doesn't have access to c++ 20 coroutines (using old compiler) you can use winapi Fibers which is like heavy coroutines .
It's documented here :
Fibers Win32 apps
And this is an example of using it :
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
VOID
__stdcall
ReadFiberFunc(LPVOID lpParameter);
VOID
__stdcall
WriteFiberFunc(LPVOID lpParameter);
void DisplayFiberInfo(void);
typedef struct
{
DWORD dwParameter; // DWORD parameter to fiber (unused)
DWORD dwFiberResultCode; // GetLastError() result code
HANDLE hFile; // handle to operate on
DWORD dwBytesProcessed; // number of bytes processed
} FIBERDATASTRUCT, *PFIBERDATASTRUCT, *LPFIBERDATASTRUCT;
#define RTN_OK 0
#define RTN_USAGE 1
#define RTN_ERROR 13
#define BUFFER_SIZE 32768 // read/write buffer size
#define FIBER_COUNT 3 // max fibers (including primary)
#define PRIMARY_FIBER 0 // array index to primary fiber
#define READ_FIBER 1 // array index to read fiber
#define WRITE_FIBER 2 // array index to write fiber
LPVOID g_lpFiber[FIBER_COUNT];
LPBYTE g_lpBuffer;
DWORD g_dwBytesRead;
int __cdecl _tmain(int argc, TCHAR *argv[])
{
LPFIBERDATASTRUCT fs;
if (argc != 3)
{
printf("Usage: %s <SourceFile> <DestinationFile>\n", argv[0]);
return RTN_USAGE;
}
//
// Allocate storage for our fiber data structures
//
fs = (LPFIBERDATASTRUCT) HeapAlloc(
GetProcessHeap(), 0,
sizeof(FIBERDATASTRUCT) * FIBER_COUNT);
if (fs == NULL)
{
printf("HeapAlloc error (%d)\n", GetLastError());
return RTN_ERROR;
}
//
// Allocate storage for the read/write buffer
//
g_lpBuffer = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, BUFFER_SIZE);
if (g_lpBuffer == NULL)
{
printf("HeapAlloc error (%d)\n", GetLastError());
return RTN_ERROR;
}
//
// Open the source file
//
fs[READ_FIBER].hFile = CreateFile(
argv[1],
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL
);
if (fs[READ_FIBER].hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile error (%d)\n", GetLastError());
return RTN_ERROR;
}
//
// Open the destination file
//
fs[WRITE_FIBER].hFile = CreateFile(
argv[2],
GENERIC_WRITE,
0,
NULL,
CREATE_NEW,
FILE_FLAG_SEQUENTIAL_SCAN,
NULL
);
if (fs[WRITE_FIBER].hFile == INVALID_HANDLE_VALUE)
{
printf("CreateFile error (%d)\n", GetLastError());
return RTN_ERROR;
}
//
// Convert thread to a fiber, to allow scheduling other fibers
//
g_lpFiber[PRIMARY_FIBER]=ConvertThreadToFiber(&fs[PRIMARY_FIBER]);
if (g_lpFiber[PRIMARY_FIBER] == NULL)
{
printf("ConvertThreadToFiber error (%d)\n", GetLastError());
return RTN_ERROR;
}
//
// Initialize the primary fiber data structure. We don't use
// the primary fiber data structure for anything in this sample.
//
fs[PRIMARY_FIBER].dwParameter = 0;
fs[PRIMARY_FIBER].dwFiberResultCode = 0;
fs[PRIMARY_FIBER].hFile = INVALID_HANDLE_VALUE;
//
// Create the Read fiber
//
g_lpFiber[READ_FIBER]=CreateFiber(0,ReadFiberFunc,&fs[READ_FIBER]);
if (g_lpFiber[READ_FIBER] == NULL)
{
printf("CreateFiber error (%d)\n", GetLastError());
return RTN_ERROR;
}
fs[READ_FIBER].dwParameter = 0x12345678;
//
// Create the Write fiber
//
g_lpFiber[WRITE_FIBER]=CreateFiber(0,WriteFiberFunc,&fs[WRITE_FIBER]);
if (g_lpFiber[WRITE_FIBER] == NULL)
{
printf("CreateFiber error (%d)\n", GetLastError());
return RTN_ERROR;
}
fs[WRITE_FIBER].dwParameter = 0x54545454;
//
// Switch to the read fiber
//
SwitchToFiber(g_lpFiber[READ_FIBER]);
//
// We have been scheduled again. Display results from the
// read/write fibers
//
printf("ReadFiber: result code is %lu, %lu bytes processed\n",
fs[READ_FIBER].dwFiberResultCode, fs[READ_FIBER].dwBytesProcessed);
printf("WriteFiber: result code is %lu, %lu bytes processed\n",
fs[WRITE_FIBER].dwFiberResultCode, fs[WRITE_FIBER].dwBytesProcessed);
//
// Delete the fibers
//
DeleteFiber(g_lpFiber[READ_FIBER]);
DeleteFiber(g_lpFiber[WRITE_FIBER]);
//
// Close handles
//
CloseHandle(fs[READ_FIBER].hFile);
CloseHandle(fs[WRITE_FIBER].hFile);
//
// Free allocated memory
//
HeapFree(GetProcessHeap(), 0, g_lpBuffer);
HeapFree(GetProcessHeap(), 0, fs);
return RTN_OK;
}
VOID
__stdcall
ReadFiberFunc(
LPVOID lpParameter
)
{
LPFIBERDATASTRUCT fds = (LPFIBERDATASTRUCT)lpParameter;
//
// If this fiber was passed NULL for fiber data, just return,
// causing the current thread to exit
//
if (fds == NULL)
{
printf("Passed NULL fiber data; exiting current thread.\n");
return;
}
//
// Display some information pertaining to the current fiber
//
DisplayFiberInfo();
fds->dwBytesProcessed = 0;
while (1)
{
//
// Read data from file specified in the READ_FIBER structure
//
if (!ReadFile(fds->hFile, g_lpBuffer, BUFFER_SIZE,
&g_dwBytesRead, NULL))
{
break;
}
//
// if we reached EOF, break
//
if (g_dwBytesRead == 0) break;
//
// Update number of bytes processed in the fiber data structure
//
fds->dwBytesProcessed += g_dwBytesRead;
//
// Switch to the write fiber
//
SwitchToFiber(g_lpFiber[WRITE_FIBER]);
} // while
//
// Update the fiber result code
//
fds->dwFiberResultCode = GetLastError();
//
// Switch back to the primary fiber
//
SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}
VOID
__stdcall
WriteFiberFunc(
LPVOID lpParameter
)
{
LPFIBERDATASTRUCT fds = (LPFIBERDATASTRUCT)lpParameter;
DWORD dwBytesWritten;
//
// If this fiber was passed NULL for fiber data, just return,
// causing the current thread to exit
//
if (fds == NULL)
{
printf("Passed NULL fiber data; exiting current thread.\n");
return;
}
//
// Display some information pertaining to the current fiber
//
DisplayFiberInfo();
//
// Assume all writes succeeded. If a write fails, the fiber
// result code will be updated to reflect the reason for failure
//
fds->dwBytesProcessed = 0;
fds->dwFiberResultCode = ERROR_SUCCESS;
while (1)
{
//
// Write data to the file specified in the WRITE_FIBER structure
//
if (!WriteFile(fds->hFile, g_lpBuffer, g_dwBytesRead,
&dwBytesWritten, NULL))
{
//
// If an error occurred writing, break
//
break;
}
//
// Update number of bytes processed in the fiber data structure
//
fds->dwBytesProcessed += dwBytesWritten;
//
// Switch back to the read fiber
//
SwitchToFiber(g_lpFiber[READ_FIBER]);
} // while
//
// If an error occurred, update the fiber result code...
//
fds->dwFiberResultCode = GetLastError();
//
// ...and switch to the primary fiber
//
SwitchToFiber(g_lpFiber[PRIMARY_FIBER]);
}
void
DisplayFiberInfo(
void
)
{
LPFIBERDATASTRUCT fds = (LPFIBERDATASTRUCT)GetFiberData();
LPVOID lpCurrentFiber = GetCurrentFiber();
//
// Determine which fiber is executing, based on the fiber address
//
if (lpCurrentFiber == g_lpFiber[READ_FIBER])
printf("Read fiber entered");
else
{
if (lpCurrentFiber == g_lpFiber[WRITE_FIBER])
printf("Write fiber entered");
else
{
if (lpCurrentFiber == g_lpFiber[PRIMARY_FIBER])
printf("Primary fiber entered");
else
printf("Unknown fiber entered");
}
}
//
// Display dwParameter from the current fiber data structure
//
printf(" (dwParameter is 0x%lx)\n", fds->dwParameter);
}
Given that you are using winapi and UI so you already have message processing I would suggest that you break up the problematic operation into more steps and use custom messages. Have each step in the problematic operation post the message that triggers the next step. Since this is something windows already handles (dealing with messages) it should fit much more neatly into what you already have than trying to use coroutines or windows fibers.
This will slow down overall processing of the problematic operation somewhat but will keep the UI responsive.
However I would also seriously consider abandoning the single-threaded approach. If your problematic operation simply takes input and produces an output shoving that operation onto a separate thread and dealing with the result when it comes (again via a posted message) is often a very reasonable solution.
Quick summary
In a nut shell i wish to access the debug information regarding the stack, preferably for passing information to Logger.
I wish for the information to tell me the Function Name, Line Number and File Name.
I've got the symbols and i'm attempting to access the junk values in them and turn them in to English. However nothing seems to work.
I have commented the code for people to read and see if they can help me effectively walk the stack to pull the information out i need.
So far i can point out SymGetModuleBase() does not return a positive number only 0, according to MSDN it fails if returns 0. Which is correct as it returns a memory address.
SymGetSymFromAddr() fails to return true, which i'm assuming gets the name of the stack frame/function
SymGetLineFromAddr() goes on to fail as well and doesn't return the line number location in the file and also doesn't gather the file path.
I believe this is due to the process parameter being invalid. I will elaborate below.
Attempts to locate and fix the problem
I have read the MSDN documentation repeatedly and feel like i'm banging my head off the wall, i've done pretty much what it said and i feel like it's just not working.
However i have noticed SymInitialize() should be called prior to attempting this, which i do call. This changed the GetLastError() value from 6 ERROR_INVALID_HANDLE to 0 ERROR_SUCCESS. Yet SymGetModuleBase() still returns 0 no matter if SymInitialize() although GetLastError() reports different error codes depending on SymInitialize() use. It should return a valid virtual memory address this is where i think the main problem lies in the code.
HANDLE process = ::GetCurrentProcess(); this line in the code below returns 0xffffffffffffffff very suspect if you ask me. This should return a pseudo virtual memory address but it to me anyway looks like a false result. This happens every time i run the program which leads me to think ::GetCurrentProcess() this is either got a bug, or doesn't work somehow. According to MSDN this is the correct a up to date way of getting the current process and i don't know how to get a valid HANDLE to a the process another way. So i can't pass the first parameter in SymGetModuleBase() the correct process, although i maybe wrong.
Full code for the function
void Logger::WriteStackFrames(log::TextColor tc)
{
// Initalize some memory
DWORD machine = IMAGE_FILE_MACHINE_AMD64;
HANDLE process = ::GetCurrentProcess();
HANDLE thread = GetCurrentThread();
// Initalize more memory
CONTEXT context;
STACKFRAME stack_frame;
// Set some memory
memset(&context, 0, sizeof(CONTEXT));
memset(&stack_frame, 0, sizeof(STACKFRAME));
// Capture the context
RtlCaptureContext(&context);
// Initalize a few things here and there
stack_frame.AddrPC.Offset = context.Rip;
stack_frame.AddrPC.Mode = AddrModeFlat;
stack_frame.AddrStack.Offset = context.Rsp;
stack_frame.AddrStack.Mode = AddrModeFlat;
stack_frame.AddrFrame.Offset = context.Rbp;
stack_frame.AddrFrame.Mode = AddrModeFlat;
// Randomly saw this was supposed to be called prior to StackWalk so tried it
if (!SymInitialize(process, 0, false))
{
wprintf(L"SymInitialize unable to find process!! Error: %d\r\n", GetLastError());
}
for (ULONG frame = 0; ; frame++)
{
// Set text color
SetTextColor(tc);
// Check for frames
BOOL result = StackWalk(machine, process, thread, &stack_frame, &context, 0,
SymFunctionTableAccess, SymGetModuleBase, 0);
// Get memory address of base module. Returns 0 although when SymInitialize is called before it the GetLastError returns 0 without return 6
DWORD64 module_base = SymGetModuleBase(process, stack_frame.AddrPC.Offset);
if (module_base == 0) {
wprintf(L"SymGetModuleBase is unable to get virutal address!! Error: %d\r\n", GetLastError());
}
// Initalize more memory
MODULEINFO module_info;
SecureZeroMemory(&module_info, sizeof(MODULEINFO));
// Get the file name of the file containing the function
TCHAR module_buffer[log::MaxPath];
DWORD mod_file = GetModuleFileName((HINSTANCE)module_base, module_buffer, log::MaxPath);
if ((module_base != 0) && (mod_file != 0))
{
module_info.module_name = module_buffer;
}
// Initalize more memory and clear it out
PIMAGEHLP_SYMBOL64 symbol;
IMAGEHLP_LINE64 line_num;
SecureZeroMemory(&symbol, sizeof(PIMAGEHLP_SYMBOL64));
SecureZeroMemory(&symbol, sizeof(IMAGEHLP_LINE64));
// Get the symbol
TCHAR symbol_buffer[log::MaxPath];
symbol = (PIMAGEHLP_SYMBOL)symbol_buffer;
symbol->SizeOfStruct = (sizeof(IMAGEHLP_SYMBOL) + log::MaxPath);
symbol->MaxNameLength = 254;
// Attempt to get name from symbol (fails)
LPSTR name_buffer = new CHAR[254];
if (SymGetSymFromAddr(process, stack_frame.AddrPC.Offset, 0, symbol))
{
name_buffer = symbol->Name;
}
// Set the size of something
DWORD offset = 0;
line_num.SizeOfStruct = sizeof(IMAGEHLP_LINE64);
// Attempt to get the line and file name of where the symbol is
if (SymGetLineFromAddr(process, stack_frame.AddrPC.Offset, &offset, &line_num))
{
module_info.line = line_num.LineNumber;
module_info.file = line_num.FileName;
}
// Initalize memory
LPWSTR console_message = new TCHAR[log::MaxMsgLength];
LPWSTR file_message = new TCHAR[log::MaxMsgLength];
// Set some strings
swprintf(console_message, log::MaxMsgLength, L">> Frame %02lu: called from: %016X Stack: %016X Frame: %016X Address return: %016X\r\n",
frame, stack_frame.AddrPC.Offset, stack_frame.AddrStack.Offset, stack_frame.AddrFrame.Offset, stack_frame.AddrReturn.Offset);
swprintf(file_message, log::MaxMsgLength, L"Frame %02lu: called from: %016X Stack: %016X Frame: %016X Address return: %016X\r\n",
frame, stack_frame.AddrPC.Offset, stack_frame.AddrStack.Offset, stack_frame.AddrFrame.Offset, stack_frame.AddrReturn.Offset);
/* When the symbol can yield the name, line and file name the above strings
will also include that information */
// To go here . . .
// Write some strings
wprintf(console_message);
WriteAsync(file_message);
// Delete some memory
if (console_message) {
delete[] console_message; console_message = nullptr;
}
if (file_message) {
delete[] file_message; file_message = nullptr;
}
// If nothing else to do break loop
if (!result) {
break;
}
}
}
What i hope to achieve
Although i realize this will only work in debug mode that is fine, and i know i could write a macro using the __LINE__ __FUNCTION__ __FILE__ macros but that isn't what i'm looking for.
The results should be a wind up from the bottom stack showing the memory addresses of the calling PC, stack and frame. This works.
However it should also show me which the Name of the function, the Line number and the File path. This doesn't work.
FYI: I realize i need to add the code in to the generate the string and output it, but the code isn't capable of getting the information for the strings so that isn't coded in yet.
Please if anyone can help me, it would be fantastic all the code is focused around the "DbgHelp.h" windows file and most information is available on MSDN. So for the long question but i felt i should provide everything i know.
::GetCurrentProcess() = 0xffffffffffffffff
is not suspicious.
I tried a few variations on your code pulling bits from here and there - in the end I could not get it to work as I was using clang/mingw and it was not generating .pdb files. However, maybe the code will work for you as you are using MSVC. Anyway, here it is, in case it helps
I also noticed you hard coded machine type to AMD - I assume that is correct for you, but below I have an ifdef I found that sets it for other archs.
#include <windows.h>
#include <excpt.h>
#include <imagehlp.h>
#include <binutils/bfd.h>
#include <psapi.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdbool.h>
#include <psapi.h>
#include <dbghelp.h>
#define MAX_SYMBOL_LEN 1024
typedef struct CallstackEntry
{
DWORD64 offset; // if 0, we have no valid entry
CHAR name[MAX_SYMBOL_LEN];
CHAR undName[MAX_SYMBOL_LEN];
CHAR undFullName[MAX_SYMBOL_LEN];
DWORD64 offsetFromSmybol;
DWORD offsetFromLine;
DWORD lineNumber;
CHAR lineFileName[MAX_SYMBOL_LEN];
DWORD symType;
LPCSTR symTypeString;
CHAR moduleName[MAX_SYMBOL_LEN];
DWORD64 baseOfImage;
CHAR loadedImageName[MAX_SYMBOL_LEN];
} CallstackEntry;
typedef enum CallstackEntryType
{
firstEntry,
nextEntry,
lastEntry
} CallstackEntryType;
void _backtrace (void)
{
HANDLE process = ::GetCurrentProcess();
HANDLE thread = GetCurrentThread();
if (!SymInitialize(process, 0, true)) {
wprintf(L"SymInitialize unable to find process!! Error: %d\r\n",~
GetLastError());
}
DWORD symOptions = SymGetOptions();
symOptions |= SYMOPT_LOAD_LINES;
symOptions |= SYMOPT_FAIL_CRITICAL_ERRORS;
symOptions = SymSetOptions(symOptions);
char szSearchPath[MAX_SYMBOL_LEN] = {0};
SymGetSearchPath(process, szSearchPath, MAX_SYMBOL_LEN);
char szUserName[MAX_SYMBOL_LEN] = {0};
DWORD dwSize = MAX_SYMBOL_LEN;
GetUserNameA(szUserName, &dwSize);
CHAR search_path_debug[MAX_SYMBOL_LEN];
size_t maxLen = MAX_SYMBOL_LEN;
#if _MSC_VER >= 1400
maxLen = _TRUNCATE;
#endif
_snprintf_s(search_path_debug, maxLen,~
"SymInit: Symbol-SearchPath: '%s', symOptions: %d, UserName: '%s'\n",
szSearchPath, symOptions, szUserName);
search_path_debug[MAX_SYMBOL_LEN - 1] = 0;
printf(search_path_debug);
// Initalize more memory
CONTEXT context;
memset(&context, 0, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_FULL;
RtlCaptureContext(&context);
// Initalize a few things here and there
STACKFRAME stack;
memset(&stack, 0, sizeof(STACKFRAME));
stack.AddrPC.Offset = context.Rip;
stack.AddrPC.Mode = AddrModeFlat;
stack.AddrStack.Offset = context.Rsp;
stack.AddrStack.Mode = AddrModeFlat;
stack.AddrFrame.Offset = context.Rbp;
stack.AddrFrame.Mode = AddrModeFlat;
#ifdef _M_IX86
auto machine = IMAGE_FILE_MACHINE_I386;
#elif _M_X64
auto machine = IMAGE_FILE_MACHINE_AMD64;
#elif _M_IA64
auto machine = IMAGE_FILE_MACHINE_IA64;
#else
#error "platform not supported!"
#endif
for (ULONG frame = 0; ; frame++) {
BOOL result = StackWalk(machine,~
process,~
thread,~
&stack,
&context,
0,
SymFunctionTableAccess,~
SymGetModuleBase,~
0);
CallstackEntry csEntry;
csEntry.offset = stack.AddrPC.Offset;
csEntry.name[0] = 0;
csEntry.undName[0] = 0;
csEntry.undFullName[0] = 0;
csEntry.offsetFromSmybol = 0;
csEntry.offsetFromLine = 0;
csEntry.lineFileName[0] = 0;
csEntry.lineNumber = 0;
csEntry.loadedImageName[0] = 0;
csEntry.moduleName[0] = 0;
IMAGEHLP_SYMBOL64 symbol {};
symbol.SizeOfStruct = sizeof(IMAGEHLP_SYMBOL64);
symbol.MaxNameLength = MAX_SYMBOL_LEN;
// Initalize more memory and clear it out
if (SymGetSymFromAddr64(process,~
stack.AddrPC.Offset,
&csEntry.offsetFromSmybol,~
&symbol)) {
}
IMAGEHLP_LINE64 line {};
line.SizeOfStruct = sizeof(line);
if (SymGetLineFromAddr64(process,~
stack.AddrPC.Offset,
&csEntry.offsetFromLine,~
&line)) {
}
printf("Frame %lu:\n"
" Symbol name: %s\n"
" PC address: 0x%08LX\n"
" Stack address: 0x%08LX\n"
" Frame address: 0x%08LX\n"
"\n",
frame,
symbol.Name,
(ULONG64)stack.AddrPC.Offset,
(ULONG64)stack.AddrStack.Offset,
(ULONG64)stack.AddrFrame.Offset
);
// If nothing else to do break loop
if (!result) {
break;
}
}
}
I am basically trying to cast or copy my struct to my other process section view but I keep getting an error
C2760: syntax error: unexpected token 'identifier', expected 'declaration'
This is what I am doing:
type RPM(UINT_PTR ReadAddress)
{
if (hDriver == INVALID_HANDLE_VALUE) {
return {};
}
DWORD64 Bytes;
KM_READ_REQUEST ReadRequest{};
type response{};
ReadRequest.ProcessId = PID;
ReadRequest.Address = ReadAddress;
ReadRequest.Size = sizeof(type);
ReadRequest.Output = &response;
The problem is here:
auto pBuf = (ReadRequest)MapViewOfFile(hMapFile, FILE_MAP_WRITE, 0, 0, 4096);
if (!pBuf)
{
printf("OpenFileMappingA(write) fail! Error: %u\n", GetLastError());
system("pause");
}
printf("MapViewOfFile(write) created ! \n");
I am having another problem trying to read an unknown value from my kernel driver. It basically reads memory and then changes that value to another thing based on what I am reading from if its int, float, etc..
PKM_READ_REQUEST ReadInput = (PKM_READ_REQUEST)SharedSection; // cast readRequest to our struct which is in SharedSection.
void* ReadOutput = ReadInput->Output;
Status = ReadKernelMemory(Process, ReadInput->Address, ReadOutput, ReadInput->Size);
I am trying to copy it to my shared section so I can read it from user mode, but idk how to cast it or what the value would be.
memcpy(SharedSection, &ReadOutput, sizeof(ReadOutput));
This is how I want to try to read it, but cast it as the same way because I don't want to read it as void, I want to read it as the value that was given from my kernel mode.
auto pBuf = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 4096);
if (!pBuf)
{
printf("OpenFileMappingA(write) fail! Error: %u\n", GetLastError());
system("pause");
}
printf("MapViewOfFile(write) created ! \n");
BTW, I am using the un-documented function mmcopyvirtualmemory in my kernel driver.
1.
auto pBuf = (ReadRequest)MapViewOfFile(hMapFile, FILE_MAP_WRITE, 0, 0, 4096);
The ReadRequest is not a type but an object, If you want to write the file map address as the struct KM_READ_REQUEST, you should convert the return pointer to the type of PKM_READ_REQUEST, and also take the control of the size of file map:
auto pBuf = (PKM_READ_REQUEST)MapViewOfFile(hMapFile, FILE_MAP_WRITE, 0, 0, sizeof(KM_READ_REQUEST));
So that you can set the PID,Address,Size and Output for it.
2.
memcpy(SharedSection, &ReadOutput, sizeof(ReadOutput));
ReadOutput is already the address of output value, So you don't
need the operation &.
Sizeof(a pointer) is always equal to 4(in 32-bit) and 8(in
64-bit);
You'd better use a new variable to store copied values, Instead of overwriting previous data。
So
type new_var;
memcpy(&new_var, ReadOutput, sizeof(KM_READ_REQUEST));
EDIT: Answer your comments,
You can set a single Event to communicate between driver and UM.
App:
hDevice = CreateFile(Device);
hEvent = CreateEvent(...);
DeviceIoControl(hDevice, IOCTL_SET_EVENT, &hEvent,...);
WaitForSingleObject(hEvent, INFINITE);
Driver:
case IOCTL_SET_EVENT:
{
HANDLE hUserEvent = *(HANDLE *)pIrp->AssociatedIrp.SystemBuffer;
status = ObReferenceObjectByHandle(hUserEvent, EVENT_MODIFY_STATE,*ExEventObjectType, KernelMode, (PVOID*)&pDevExt->pEvent, NULL);
ObDereferenceObject(pDevExt->pEvent);
break;
}
Then set event:
KeSetEvent(pdx->pEvent,...);
I've been attempting to use ReadFileScatter today in my code (which sounds like exactly what I need), so far without a lot of luck. Google'ing the internet for what goes wrong doesn't give me much insight.
The documentation states:
The array must contain enough elements to store nNumberOfBytesToRead bytes of data, plus one element for the terminating NULL. For example, if there are 40 KB to be read and the page size is 4 KB, the array must have 11 elements that includes 10 for the data and one for the NULL.
Each buffer must be at least the size of a system memory page and must be aligned on a system memory page size boundary. The system reads one system memory page of data into each buffer.
The function stores the data in the buffers in sequential order. For example, it stores data into the first buffer, then into the second buffer, and so on until each buffer is filled and all the data is stored, or there are no more buffers.
So far I've been attempting to do just that. I allocated a bunch of bytes using VirtualAlloc (which ensures the page boundary constraint), add a terminator NULL to the list, ensure the data on disk is on the system boundary (and implicitly a disk sector size boundary) as well and issue the call.
Without further due, here's the minimum test case in C++:
// Setup: c:\tmp\test.dat is a file with at least 12K of stuff.
// I attempt to read page 2/3, e.g. offset [4096-4096+8192>
// TEST:
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
auto pageSize = systemInfo.dwPageSize;
std::cout << "Page size: "<< pageSize << std::endl;
// Allocate 2 pages that are aligned with one in the middle:
auto buffer = reinterpret_cast<char*>(VirtualAlloc(NULL, pageSize * 3, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
// Create read buffer:
std::vector<FILE_SEGMENT_ELEMENT> elements;
{
FILE_SEGMENT_ELEMENT element1;
element1.Buffer = buffer;
elements.push_back(element1);
}
{
FILE_SEGMENT_ELEMENT element2;
element2.Buffer = buffer + pageSize * 2;
elements.push_back(element2);
}
{
FILE_SEGMENT_ELEMENT terminator;
terminator.Buffer = NULL;
elements.push_back(terminator);
}
// [..] Physical sector size is normally checked as well. In my case it's 512 bytes,
// so I guess that's irrelevant here.
//
// Open file:
auto fileHandle = CreateFile(
"c:\\tmp\\test.dat",
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH,
NULL);
auto err = GetLastError();
if (err != ERROR_ALREADY_EXISTS && err != ERROR_SUCCESS)
{
throw std::exception(); // FIXME.
}
OVERLAPPED overlapped;
memset(&overlapped, 0, sizeof(OVERLAPPED));
LARGE_INTEGER tmp;
tmp.QuadPart = 4096; // Read from disk page 1
overlapped.Offset = tmp.LowPart;
overlapped.OffsetHigh = tmp.HighPart;
overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
auto succes = ReadFileScatter(fileHandle, elements.data(), pageSize * 2, NULL, &overlapped);
err = GetLastError();
if (!succes && err != ERROR_IO_PENDING && err != ERROR_SUCCESS)
{
throw std::exception(); // The call always ends up here with error 87: Invalid parameter
}
WaitForSingleObject(overlapped.hEvent, INFINITE);
std::cout << "Call succeeded!" << std::endl;
// FIXME: Proper exception handling.
// Clean up:
VirtualFree(buffer, pageSize * 3, MEM_DECOMMIT | MEM_RELEASE);
CloseHandle(overlapped.hEvent);
CloseHandle(fileHandle);
In the code, the error is noted with the comment // The call always ends up here with error 87: Invalid parameter. However, as far as I can see, I check all the boxes that they describe on MSDN... so...
What am I doing wrong here?
I'm developing an app which will need to work with different types of archives. As many of the archive types as possible is good. I have choosen a 7zip.dll as an engine of archive-worker. But there is a problem, does anyone knows how to uncompress a file from archive to memory buffer? As I see, 7zip.dll supports only uncompressing to hard disk. Also, it would be nice to load archive from memory buffer. Has anyone tried to do something like that?
Not sure if I completely understand your needs (for example, don't you need the decompressed file on disk?).
I was looking at LZMA SDK 9.20 and its lzma.txt readme file, and there are plenty of hints that decompression to memory is possible - you may just need to use the C API rather than the C++ interface. Check out, for example, the section called Single-call Decompressing:
When to use: RAM->RAM decompressing
Compile files: LzmaDec.h + LzmaDec.c + Types.h
Compile defines: no defines
Memory Requirements:
- Input buffer: compressed size
- Output buffer: uncompressed size
- LZMA Internal Structures: state_size (16 KB for default settings)
Also, there is this function:
SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen,
const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
You can utilize these by memory-mapping the archive file. To the best of my knowledge, if your process creates a memory-mapped file with exclusive access (so no other process can access it) and does no explicit flushing, all changes to the file will be kept in memory until the mapping is destroyed or the file closed. Alternatively, you could just load the archive contents in memory.
For the sake of completeness, I hacked together several examples into a demo of using memory mapping in Windows.
#include <stdio.h>
#include <time.h>
#include <Windows.h>
#include <WinNT.h>
// This demo will limit the file to 4KiB
#define FILE_SIZE_MAX_LOWER_DW 4096
#define FILE_SIZE_MAX_UPPER_DW 0
#define MAP_OFFSET_LOWER_DW 0
#define MAP_OFFSET_UPPER_DW 0
#define TEST_ITERATIONS 1000
#define INT16_SIZE 2
typedef short int int16;
// NOTE: This will not work for Windows less than XP or 2003 Server!
int main()
{
HANDLE hFile, hFileMapping;
PBYTE mapViewStartAddress;
// Note: with no explicit security attributes, the process needs to have
// the necessary rights (e.g. read, write) to this location.
LPCSTR path = "C:\\Users\\mcmlxxxvi\\Desktop\\test.dat";
// First, open a file handle.
hFile = CreateFile(path,
GENERIC_READ | GENERIC_WRITE, // The file is created with Read/Write permissions
FILE_SHARE_READ, // Set this to 0 for exclusive access
NULL, // Optional security attributes
CREATE_ALWAYS, // File is created if not found, overwritten otherwise
FILE_ATTRIBUTE_TEMPORARY, // This affects the caching behaviour
0); // Attributes template, can be left NULL
if ((hFile) == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "Unable to open file");
return 1;
}
// Then, create a memory mapping for the opened file.
hFileMapping = CreateFileMapping(hFile, // Handle for an opened file
NULL, // Optional security attributes
PAGE_READWRITE, // File can be mapped for Read/Write access
FILE_SIZE_MAX_UPPER_DW, // Maximum file size split in DWORDs.
FILE_SIZE_MAX_LOWER_DW, // NOTE: I may have these two mixed up!
NULL); // Optional name
if (hFileMapping == 0)
{
CloseHandle(hFile);
fprintf(stderr, "Unable to open file for mapping.");
return 1;
}
// Next, map a view (a continuous portion of the file) to a memory region
// The view must start and end at an offset that is a multiple of
// the allocation granularity (roughly speaking, the machine page size).
mapViewStartAddress = (PBYTE)MapViewOfFile(hFileMapping, // Handle to a memory-mapped file
FILE_MAP_READ | FILE_MAP_WRITE, // Maps the view for Read/Write access
MAP_OFFSET_UPPER_DW, // Offset in the file from which
MAP_OFFSET_LOWER_DW, // the view starts, split in DWORDs.
FILE_SIZE_MAX_LOWER_DW); // Size of the view (here, entire file)
if (mapViewStartAddress == 0)
{
CloseHandle(hFileMapping);
CloseHandle(hFile);
fprintf(stderr, "Couldn't map a view of the file.");
return 1;
}
// This is where actual business stuff belongs.
// This example application does iterations of reading and writing
// random numbers for the entire length of the file.
int16 value;
errno_t result = 0;
srand((int)time(NULL));
for (int i = 0; i < TEST_ITERATIONS; i++)
{
// Write
for (int j = 0; j < FILE_SIZE_MAX_LOWER_DW / INT16_SIZE; j++)
{
value = rand();
result = memcpy_s(mapViewStartAddress + j * INT16_SIZE, INT16_SIZE, &value, INT16_SIZE);
if (result != 0)
{
CloseHandle(hFileMapping);
CloseHandle(hFile);
fprintf(stderr, "File write error during iteration #%d, error %d", i, GetLastError());
return 1;
}
}
// Read
SetFilePointer(hFileMapping, 0, 0, FILE_BEGIN);
for (int j = 0; j < FILE_SIZE_MAX_LOWER_DW / sizeof(int); j++)
{
result = memcpy_s(&value, INT16_SIZE, mapViewStartAddress + j * INT16_SIZE, INT16_SIZE);
if (result != 0)
{
CloseHandle(hFileMapping);
CloseHandle(hFile);
fprintf(stderr, "File read error during iteration #%d, error %d", i, GetLastError());
return 1;
}
}
}
// End business stuff
CloseHandle(hFileMapping);
CloseHandle(hFile);
return 0;
}