Related
I am writing a WinDBG extension to debug a device driver, and need to call an external binary to debug the device's firmware. I would like to show the output of this binary in the WinDBG console.
My initial idea was to simply pipe the output of the binary to a buffer and print that buffer with ControlledOutput. However, I get a 'broken pipe' error when I try to read from the pipe in my extension.
Here is how I create the external process in my extension:
SECURITY_ATTRIBUTES sAttr;
HANDLE childOutRead = NULL;
HANDLE childOutWrite = NULL;
PROCESS_INFORMATION childProcInfo;
STARTUPINFO childStartInfo;
char buf[4096];
sAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
sAttr.bInheritHandle = TRUE;
sAttr.lpSecurityDescriptor = NULL;
CreatePipe(&childOutRead, &childOutWrite, &sAttr, 0);
// don't inherit read end
SetHandleInformation(childOutRead, HANDLE_FLAG_INHERIT, 0);
ZeroMemory(&childProcInfo, sizeof(PROCESS_INFORMATION));
ZeroMemory(&childStartInfo, sizeof(STARTUPINFO));
childStartInfo.cb = sizeof(STARTUPINFO);
childStartInfo.hStdError = childOutWrite;
childStartInfo.hStdOut = childOutWrite;
childStartInfo.hStdIn = GetStdHandle(STD_INPUT_HANDLE);
childStartInfo.dwFlags |= STARTF_USESTDHANDLES;
CreateProcessA(NULL, "myBinary.exe someArgs",
NULL, NULL, TRUE, 0, NULL, NULL,
&childStartInfo, &childProcInfo);
// close the handle not used in parent
CloseHandle(childOutWrite);
// read output
while (1) {
DWORD read;
BOOL r;
DWORD error;
r = ReadFile(childOutRead, buf, sizeof(buf), &read, NULL);
if (!r) {
error = GetLastError();
windbgPrintf("got error 0x%x\n", error);
break;
}
if (read == 0) break;
windbgPrint(buf, read);
}
ReadFile fails with error 0x6D, BROKEN_PIPE. This makes me suspect that the pipe is somehow not being inherited.
I have nearly identical code working in a test outside of WinDBG, so it must be doing something differently. How do I get pipes working in this way inside WinDBG?
I am working on my library which needs to capture and process the standard output (and err) of a child process as it runs. The problem arises when ReadFile is used to read the output, it does not return once the process ends (gets killed or exits).
It looks like ReadFile is not able to detect that the other end of the pipe (the write handle) is closed. According to the documentation it should return FALSE and set the last error to ERROR_BROKEN_PIPE:
If an anonymous pipe is being used and the write handle has been closed, when ReadFile attempts to read using the pipe's corresponding read handle, the function returns FALSE and GetLastError returns ERROR_BROKEN_PIPE.
Here is my code, I have stripped out the irrelevant bits: (NOTE: I have updated the allium_start to follow the suggested changes, I am keeping the original for reference, please use the newer function code to find flaws)
bool allium_start(struct TorInstance *instance, char *config, allium_pipe *output_pipes) {
// Prepare startup info with appropriate information
SecureZeroMemory(&instance->startup_info, sizeof instance->startup_info);
instance->startup_info.dwFlags = STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES pipe_secu_attribs = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
HANDLE pipes[2];
if (output_pipes == NULL) {
CreatePipe(&pipes[0], &pipes[1], &pipe_secu_attribs, 0);
output_pipes = pipes;
}
instance->startup_info.hStdOutput = output_pipes[1];
instance->startup_info.hStdError = output_pipes[1];
instance->stdout_pipe = output_pipes[0]; // Stored for internal reference
// Create the process
bool success = CreateProcessA(
NULL,
cmd,
NULL,
NULL,
config ? true : false,
0,
NULL,
NULL,
&instance->startup_info,
SecureZeroMemory(&instance->process, sizeof instance->process)
);
// Return on failure
if (!success) return false;
}
char *allium_read_stdout_line(struct TorInstance *instance) {
char *buffer = instance->buffer.data;
// Process the input
unsigned int read_len = 0;
while (true) {
// Read data
unsigned long bytes_read;
if (ReadFile(instance->stdout_pipe, buffer, 1, &bytes_read, NULL) == false || bytes_read == 0) return NULL;
// Check if we have reached end of line
if (buffer[0] == '\n') break;
// Proceed to the next character
++buffer; ++read_len;
}
// Terminate the new line with null character and return
// Special handling for Windows, terminate at CR if present
buffer[read_len >= 2 && buffer[-1] == '\r' ? -1 : 0] = '\0';
return instance->buffer.data;
}
The allium_start creates the pipe for output redirection (it uses the same pipe for both stdout and stderr to get merged streams) and then creates the child process. The other allium_read_stdout_line function is responsible for reading the output from the pipe and returning it when it encounters a new line.
The issue occurs at the ReadFile function call, it never returns if there is nothing to read after the process exits, from my understanding all the handles of a process are closed by Windows when it ends, so it looks like ReadFile is not able to detect the fact that the pipe (write handle) at the other end has been closed.
How do I fix this? I have been searching for a solution but I have found none so far, one potential option is to use multi-threading and put ReadFile in a separate thread so that it doesn't block the whole program, by using that method I can check if the process still exists periodically while I wait for the reading to finish... or kill/stop the thread if the process is gone.
I do prefer fixing the issue instead of opting for a workaround, but I am open to any other solutions to make it work. Thanks in advance!
Edit: After reading #RemyLebeau's answer and #RbMm's comments in that answer, it is pretty clear that my understand of how handle inheritance works is fundamentally flawed. So I incorporated their suggestions (SetHandleInformation to disable inheritance of read handle and closing it after creating the child process) into my allium_start function:
bool allium_start(struct TorInstance *instance, char *config, allium_pipe *output_pipes) {
// Prepare startup info with appropriate information
SecureZeroMemory(&instance->startup_info, sizeof instance->startup_info);
instance->startup_info.dwFlags = STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES pipe_secu_attribs = {sizeof(SECURITY_ATTRIBUTES), NULL, true};
HANDLE pipes[2];
if (output_pipes == NULL) {
CreatePipe(&pipes[0], &pipes[1], &pipe_secu_attribs, 0);
output_pipes = pipes;
}
SetHandleInformation(output_pipes[0], HANDLE_FLAG_INHERIT, 0);
instance->startup_info.hStdOutput = output_pipes[1];
instance->startup_info.hStdError = output_pipes[1];
instance->stdout_pipe = output_pipes[0]; // Stored for internal reference
// Create the process
bool success = CreateProcessA(
NULL,
cmd,
NULL,
NULL,
config ? true : false,
0,
NULL,
NULL,
&instance->startup_info,
SecureZeroMemory(&instance->process, sizeof instance->process)
);
// Close the write end of our stdout handle
CloseHandle(output_pipes[1]);
// Return on failure
if (!success) return false;
}
(The below text was originally here before edit 2)
But sadly it still doesn't work :(
Edit 2 (after accepting answer): It does work! See my last comment on the accepted answer.
You are not managing your pipes correctly, or more specifically, you are not controlling the inheritance of your pipe handles. DO NOT let the child process inherit the reading handle of your pipe (output_pipes[0]), otherwise the pipe will not break correctly when the child process ends.
Read MSDN for more details:
Creating a Child Process with Redirected Input and Output
The case of the redirected standard handles that won’t close even though the child process has exited
Use SetHandleInformation() or PROC_THREAD_ATTRIBUTE_LIST to prevent CreateProcess() from passing output_pipes[0] to the child process as an inheritable handle. The child process does not need access to that handle, so there is no need to pass it over the process boundary anyway. It only needs access to the writing handle of your pipe (output_pipes[1]).
For anonymous pipelines, the read process and the write process will have the handler of hRead and hWrite, each of process has its own handler(copy after inheritance). So after your child process exit and close the handler in it, anther hWrite still in parent process. We must pay attention to close hRead in the write process, close hWrite in the read process.
I can reproduce this ReadFile issue, and if closing write handler after setting child's hStdOutput and hStdError, the ReadFile will return 0 after the child process exit.
Here is my code sample,
Parent.cpp:
#include <windows.h>
#include <iostream>
#include <stdio.h>
HANDLE childInRead = NULL;
HANDLE W1 = NULL;
HANDLE W2 = NULL;
HANDLE R2 = NULL;
HANDLE R1 = NULL;
#define BUFSIZE 4096
void CreateChildProcess() {
TCHAR applicationName[] = TEXT("kids.exe");
PROCESS_INFORMATION pi;
STARTUPINFO si;
BOOL success = FALSE;
ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdError = W1;
si.hStdOutput = W1;
si.hStdInput = R2;
si.dwFlags |= STARTF_USESTDHANDLES;
success = CreateProcess(NULL, applicationName, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
if (!success) {
printf("Error creating child process \n");
}
else {
printf("Child process successfuly created \n");
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
}
int main()
{
printf("Parent process running.... \n");
DWORD dRead, dWritten;
CHAR chBuf[BUFSIZE] = { 0 };
BOOL bSuccess = FALSE;
SECURITY_ATTRIBUTES secAttr;
secAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
secAttr.bInheritHandle = TRUE;
secAttr.lpSecurityDescriptor = NULL;
printf("Creating first pipe \n");
if (!CreatePipe(&R1, &W1, &secAttr, 0)) {
printf("\n error creating first pipe \n");
}
printf("Creating second pipe \n");
if (!CreatePipe(&R2, &W2, &secAttr, 0)) {
printf("\n error creating second pipe \n");
}
if (!SetHandleInformation(R1, HANDLE_FLAG_INHERIT, 0)) {
printf("\n R1 SetHandleInformation \n");
}
if (!SetHandleInformation(W2, HANDLE_FLAG_INHERIT, 0)) {
printf("\n W1 SetHandleInformation \n");
}
printf("\n Creating child process..... \n");
HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
CreateChildProcess();
CloseHandle(W1);
CloseHandle(R2);
for (;;) {
printf("Inside for loop \n");
//1. read from stdin
printf("read from stdin:\n");
bSuccess = ReadFile(hStdIn, chBuf, BUFSIZE, &dRead, NULL);
if (!bSuccess) {
printf("error reading \n");
break;
}
//2. write to Pipe2
printf("write to Pipe2...\n");
bSuccess = WriteFile(W2, chBuf, 100, &dWritten, NULL);
if (!bSuccess) {
printf("error reading \n");
break;
}
//3. read from Pipe1
printf("read from Pipe1...\n");
bSuccess = ReadFile(R1, chBuf, BUFSIZE, &dRead, NULL);
if (!bSuccess)
{
printf("error reading :%d \n", GetLastError());
break;
}
//4. write to stdout
printf("write to stdout:\n");
bSuccess = WriteFile(hStdOut, chBuf, 100, &dWritten, NULL);
if (!bSuccess) {
printf("error reading \n");
break;
}
}
getchar();
return 0;
}
Kids.cpp:
#include <windows.h>
#include <stdio.h>
#define BUFSIZE 4096
int main()
{
DWORD dRead, dWritten;
CHAR chBuf[BUFSIZE];
BOOL success = FALSE;
HANDLE stdIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
printf("Child process running....");
if (stdIn == INVALID_HANDLE_VALUE || stdOut == INVALID_HANDLE_VALUE) {
ExitProcess(1);
}
//for (;;) {
success = ReadFile(stdIn, chBuf, BUFSIZE, &dRead, NULL);
//if (!success || dRead == 0) break;
success = WriteFile(stdOut, chBuf, dRead, &dWritten, NULL);
//if (!success) break;
//}
return 0;
}
Im trying to map the file to the memory and use MapViewOfFile(), but it failes with error code 6. I tried just about anything, I also read about big files being the problem, but the problem happens also with a 1kb file.
my code:
HANDLE hFile = CreateFile(pFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
e = GetLastError();
printf("CreateFile Errorcode %d\n", GetLastError());
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Error: could not create handle to file");
printf("CreateFileMapping error code: %d", e)
return 1;
}
printf("successfully created a Handle to try.txt");
HANDLE pMap = CreateFileMapping(hFile, NULL, PAGE_EXECUTE_READWRITE,0 ,0 ,NULL);
e = GetLastError();
if (pMap == INVALID_HANDLE_VALUE)
{
printf("Error: Unable to CreateFileMapping\n");
printf("CreateFileMapping error code: %d", e)
return 1;
}
printf("CreateFileMapping successfull.")
LPVOID lpBase MapViewOfFile(pMap, FILE_MAP_ACCESS| FILE_MAP_EXECUTE, 0, 0, 0);
e = GetLastError();
if (!lpBase)
{
printf("Error: could not map file to memory");
printf("MapViewOfFile Errorcode %d\n", GetLastError());
CloseHandle(hFile);
UnmapViewOfFile(lpBase);
printf("closed hFile handle and unmapped lpBase.")
return 1;
}
the output is the following:
> successfully created a Handle to try.txt
> createFileMapping successfull
> Error: unable to MapViewOfFile
> MapViewOfFile errorcode: 6
> closed hFile handle and unmapped lpBase.
Here:
HANDLE hFile = CreateFile(pFile, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
Replace GENERIC_READ with GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE
Also there is no FILE_MAP_ACCESS, but FILE_MAP_ALL_ACCESS.
I tried this code and it maps test file with success, not sure what you want to do with it further. Also for inspecting problems you can also use: Procmon.exe from sysinternals - it will report you what problems occured during file manipulations.
I would like to ask for help with WINAPI function CreateFileMapping (), which returns constantly NULL. After GetLastError() I get 5 - "ERROR_ACCESS_DENIED 5 (0x5) Access is denied". The file has been created after CreateFile with no problem, but following CreateFileMapping never has bee succesful.
int MapDestFile(LPCWSTR fPath)
{
hDestFile = CreateFile(
fPath,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hSourceFile == NULL)
{
printf("%d\n", GetLastError());
}
hDestMapFile = CreateFileMapping(
hDestFile,
NULL,
PAGE_READWRITE,
0,
10,
NULL
);
if (hDestMapFile == NULL)
{
// here always tell error number 5
printf("%d\n", GetLastError());
}
lpMapAddressDestFile = MapViewOfFile(
hDestMapFile,
FILE_MAP_WRITE,
0,
0,
0);
if (lpMapAddressDestFile == NULL)
{
printf("%d\n", GetLastError());
}
return 1;
}
I would appreciate any suggestions.
Thanks
You need to create the file with GENERIC_WRITE | GENERIC_READ to match PAGE_READWRITE.
That seems self-evident when you think about it. How can you have memory that you can read from backed by a file that you cannot read from? The documentation does call this out explicitly in any case:
PAGE_READWRITE
The file handle that the hFile parameter specifies must be created with the GENERIC_READ and GENERIC_WRITE access rights.
On top of that your error checking on the call to CreateFile is wrong. Take another look at the documentations. Error is indicated by a return value of INVALID_FILE_HANDLE.
I have a Win32 application that I'm making, and it sends a string from one process to another via a named pipe. However, the process that calls ReadFile on the pipe gets the string with some garbled data in it. It returns the number of bytes written correctly, but the last 8 characters or so of the string are garbled.
Here is the code for creating the pipe, and writing to it:
myPipe = CreateNamedPipe(L"\\\\.\\pipe\\testpipe", PIPE_ACCESS_OUTBOUND, PIPE_NOWAIT, 10, 512, 512, 10, NULL);
TCHAR title[128];
GetWindowText(foundHwnd, title, 128);
wstring windowTitle(title);
vector<wstring> splitVec;
boost::split(splitVec, windowTitle, boost::algorithm::is_any_of(wstring(L"|")));
WriteFile(myPipe, splitVec[0].c_str(), splitVec[0].size(), &wrote, NULL);
And here is the code that reads it:
if (WaitNamedPipe(L"\\\\.\\pipe\\testpipe", 5000) == 0) {
MessageBox(NULL, L"Unable to wait for pipe", L"Error", MB_OK);
return false;
}
myPipe = CreateFile(L"\\\\.\\pipe\\testpipe", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (myPipe == INVALID_HANDLE_VALUE) {
MessageBox(NULL, L"Unable to open pipe", L"Error", MB_OK);
return false;
}
// Other code here...
TCHAR buf[512];
DWORD read;
success = ReadFile(myPipe, buf, 512, &read, NULL);
if (read > 0)
MessageBox(NULL, buf, L"Got Data", MB_OK);
When MessageBox is shown, the end of the string is garbled and I have no idea why. Any ideas?
Thanks!
I think the key here is to make sure that your strings are null terminated and that you send the termination character as well. You shouldn't have to send the entire buffer if the communications is synchronous or if you set it up in PIPE_READMODE_MESSAGE. ReadFile will return when either the specified number of bytes has been read or a write operation completes on the other end of the pipe. I believe that the "garbled" text is really garbage in the read buffer on the client side of the pipe and because you are not transmitting the string termination character, it is including this in the text sent to the message box. Either clear your read buffer before sending or send the string termination character with the message and I think it will work without the overhead of sending a full buffer.
Here is sample client from MSDN. Note how the client sends exactly the number of characters in the message + 1 (including the termination character) and receives into a fixed buffer of size 512. If you look at a server example, you'll see the same pattern.
Some observations on the code you posted:
You need to either 1) explicitly send the null terminated byte, or 2) append one to the data you read.
Since you are reading 512 bytes, you should also be sending exactly 512 bytes.
You can send variable length strings instead by first sending the size of the string, and then sending that many bytes. That way when you read the data you will know how many bytes to read for the actual string.
The problem with what you did will be seen as soon as you send 2 things over the pipe, and you read past what you really want in the first read.
If you are only sending 1 thing over the pipe, you can keep your code, but send size() + 1 when you write to the pipe.
ReadFile / WriteFile were meant to send binary data, not necessarily strings. So you can make a function called ReadString and WriteString that implements my suggestion about reading/writing first the size then the actual string.
Try something like this:
Here is the code for creating the pipe, and writing to it:
myPipe = CreateNamedPipe(L"\\\\.\\pipe\\testpipe", PIPE_ACCESS_OUTBOUND, PIPE_NOWAIT, 10, 512, 512, 10, NULL);
TCHAR title[128];
GetWindowText(foundHwnd, title, 128);
WriteFile(myPipe, title, 128*sizeof(TCHAR), &wrote, NULL);//<---In this case we are sending a null terminated string buffer.
And here is the code that reads it:
if (WaitNamedPipe(L"\\\\.\\pipe\\testpipe", 5000) == 0) {
MessageBox(NULL, L"Unable to wait for pipe", L"Error", MB_OK);
return false;
}
myPipe = CreateFile(L"\\\\.\\pipe\\testpipe", GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (myPipe == INVALID_HANDLE_VALUE) {
MessageBox(NULL, L"Unable to open pipe", L"Error", MB_OK);
return false;
}
// Other code here...
TCHAR buf[128];
DWORD read;
success = ReadFile(myPipe, buf, 128*sizeof(TCHAR), &read, NULL);
if (read > 0)
MessageBox(NULL, buf, L"Got Data", MB_OK);
I ran into this problem with "garbage in the pipe" when writing a generic function to read stdout from any process executed at the command prompt. Therefore, I couldn't alter what was being written to the pipe (as is commonly suggested), I could only alter the read side. So, I "cheated".
If the pipe data didn't end in a null terminator, I replaced the last char with one! It seemed to work for me. I saw this work perfectly where there were nulls and where there were not at the end of my data chunks.
I worried that I might lose a critical last char (and it's possible that you might!), but for my immediate purposes, that didn't happen. You might consider adding a null rather than replacing the end under some circumstances...
Here's code snippit:
const unsigned int MAX_PIPE_PEEKS = 100;
DWORD bytesInPipe = 0;
unsigned int pipePeeks=0;
while( (bytesInPipe==0) && (pipePeeks < MAX_PIPE_PEEKS) )
{
bSuccess = PeekNamedPipe( g_hChildStd_OUT_Rd, NULL, 0, NULL,
&bytesInPipe, NULL );
if( !bSuccess ) return bSuccess; // Bail on critical failure
++pipePeeks;
}
if( bytesInPipe > 0 )
{
// Read the data written to the pipe (and implicitly clear it)
DWORD dwRead;
CHAR *pipeContents = new CHAR[ bytesInPipe ];
bSuccess = ReadFile( g_hChildStd_OUT_Rd, pipeContents,
bytesInPipe, &dwRead, NULL );
if( !bSuccess || dwRead == 0 ) return FALSE; // Bail on critical failure
// "Cheat" - eliminate garbage at the end of the pipe
if( pipeContents[ bytesInPipe ] != '\0' )
pipeContents[ bytesInPipe ] = '\0';
}
UPDATE:
After further testing, I found that this is not quite reliable (shocking, huh?). I think I'm on the right track though for a relatively simple solution. Any ideas for getting this quick patch to work?