将stdin复制到stdout会导致奇怪的结果
本文关键字:结果 stdin 复制 stdout | 更新日期: 2023-09-27 17:49:27
我有一个超级简单的程序。我的目的是将标准输入复制到标准输出。下面是源代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace LALA
{
class LALA
{
static void Main()
{
int bufferSize = 40;
Console.OpenStandardInput().CopyTo(Console.OpenStandardOutput(), bufferSize);
}
}
}
如果我将bufferSize
设置为40,那么对于任何输入,我只看到'C o n', 'C o n s'或'C o n s o l e'等。如果我将bufferSize
设置为41,则一切正常。
我的问题是我做错了什么,偶然值高于41的工作?
为了澄清,这是我输入asd: 时的输出c:'some_path>ConsoleApplication2.exe
asd
C o n
惊喜!这似乎是内部缓冲区溢出,我将分享我发现的,尽管它还没有真正证明它对我有意义。
Stream类的CopyTo
方法的代码为:(带反射器)
public void CopyTo(Stream destination, int bufferSize)
{
//bunch of non relevant validations...
this.InternalCopyTo(destination, bufferSize);
}
现在InternalCopyTo
的代码是:
private void InternalCopyTo(Stream destination, int bufferSize)
{
byte[] array = new byte[bufferSize];
int count;
while ((count = this.Read(array, 0, array.Length)) != 0)
{
destination.Write(array, 0, count);
}
}
控制台流实例类型为__ConsoleStream
(System.IO中的密封内部类),其Read
方法代码:
public override int Read([In] [Out] byte[] buffer, int offset, int count)
{
//bunch of non relevant validations...
int errorCode = 0;
int num = __ConsoleStream.ReadFileNative(this._handle, buffer, offset, count, 0, out errorCode);
if (num == -1)
{
__Error.WinIOError(errorCode, string.Empty);
}
return num;
}
最后__ConsoleStream的ReadFileNative
代码:
private unsafe static int ReadFileNative(SafeFileHandle hFile, byte[] bytes, int offset, int count, int mustBeZero, out int errorCode)
{
if (bytes.Length - offset < count)
{
throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition"));
}
if (bytes.Length == 0)
{
errorCode = 0;
return 0;
}
__ConsoleStream.WaitForAvailableConsoleInput(hFile);
int result;
int num;
fixed (byte* ptr = bytes)
{
num = __ConsoleStream.ReadFile(hFile, ptr + (IntPtr)offset / 1, count, out result, Win32Native.NULL);
}
if (num != 0)
{
errorCode = 0;
return result;
}
errorCode = Marshal.GetLastWin32Error();
if (errorCode == 109)
{
return 0;
}
return -1;
}
ReadFile
方法是低级调用:
[DllImport("kernel32.dll", SetLastError = true)]
private unsafe static extern int ReadFile(SafeFileHandle handle, byte* bytes, int numBytesToRead, out int numBytesRead, IntPtr mustBeZero);
我在这一点上的假设是,在幕后,40字节被"保留"到内部数据的某个地方,所以如果缓冲区不超过这个,你会看到保留的数据,在这种情况下控制台应用程序是进程名。
当我有更多时间的时候,我会继续研究这个问题,并尝试重现,这种情况非常特殊,因为两个流都指向同一个"文件",所以你可以在读取它的同时写入它