598 lines
28 KiB
Plaintext
598 lines
28 KiB
Plaintext
// ----------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
//
|
|
// ----------------------------------------------------------------------------
|
|
|
|
//
|
|
//
|
|
//This file contains the code for the "kdfiles" directory / namespace. This namespace
|
|
//allows Singularity processes to transfer files from a kernel debugger to the Singularity
|
|
//machine. The most common and expected use of this will be to execute app binaries
|
|
//directly from the development machine, rather than rebuilding an OS image and rebooting.
|
|
//
|
|
//WinDbg/KD already supports this, as does the NT kernel. This file contains the logic
|
|
//for the namespace, but not for the actual mechanism of interacting with the debugger.
|
|
//That logic lives in Kernel\Singularity\KdFiles.cs.
|
|
//
|
|
//When the kernel boots, it calls KdFilesNamespace.StartNamespaceThread(). This method
|
|
//creates the namespace service provider, then creates a service thread and passes that
|
|
//namespace provider to the service thread. The service thread then allows clients to
|
|
//connect, using DirectoryServiceContract and FileContract.
|
|
//
|
|
//Most requests on DirectoryServiceContract are meaningless for kdfiles, and so are
|
|
//rejected with ErrorCode.NotSupported. Binding to subdirectories is allowed, and
|
|
//doing so just creates a longer relative path within the namespace.
|
|
//
|
|
//When the service thread receives a Bind request on a directory channel, and the new
|
|
//client channel provided is a FileContract, the service thread attempts to open the
|
|
//file on the debugger. If the debugger has been properly configured (using the .kdfiles
|
|
//command), and the KD machine can open the file, send back the response, etc., then
|
|
//the Singularity client can then read the contents of the file using FileContract.
|
|
//
|
|
//The kdfiles namespace is registered at /kdfiles. When a client attempts to bind to
|
|
//a file under this namespace, the kernel will send the portion of the path that is
|
|
//"under" /kdfiles to KD. For example, if you attempt to open /kdfiles/myapp.x86,
|
|
//KD will see this path as /myapp.x86. So make sure that you take this into account
|
|
//when writing your kdfiles associations.
|
|
//
|
|
//For more information, read the Debugging Tools for Windows documentation (debugger.chm).
|
|
//Specifically, read the docs on the .kdfiles command.
|
|
//
|
|
//
|
|
|
|
using System;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
|
|
using Microsoft.SingSharp;
|
|
using Microsoft.Singularity.Channels;
|
|
using Microsoft.Singularity.Directory;
|
|
using Microsoft.Singularity.KernelDebugger;
|
|
|
|
using KdFiles = Microsoft.Singularity.KernelDebugger.KernelDebuggerFiles;
|
|
|
|
namespace Microsoft.Singularity.KernelDebugger
|
|
{
|
|
public class KdFilesNamespace
|
|
{
|
|
public const string ServiceNamespace = "/service/kdfiles";
|
|
|
|
static System.Threading.Thread _namespace_thread;
|
|
|
|
public static void StartNamespaceThread()
|
|
{
|
|
//
|
|
// Create the service provider channel to the Directory.
|
|
// This allows the KdFiles thread to receive requests from apps and from the manifest loader.
|
|
//
|
|
|
|
ServiceProviderContract.Imp! service_provider_imp;
|
|
ServiceProviderContract.Exp! service_provider_exp;
|
|
ServiceProviderContract.NewChannel(out service_provider_imp, out service_provider_exp);
|
|
|
|
DirectoryServiceContract.Imp! rootds = DirectoryService.NewClientEndpoint();
|
|
|
|
char[]! in ExHeap kdfiles_service_name = Bitter.FromString2(ServiceNamespace);
|
|
rootds.SendRegister(kdfiles_service_name, service_provider_imp);
|
|
|
|
switch receive {
|
|
case rootds.AckRegister():
|
|
Dbg("Successfully registered namespace '{0}'.", ServiceNamespace);
|
|
delete rootds;
|
|
break;
|
|
|
|
case rootds.NakRegister(ServiceProviderContract.Imp:Start rejected_service_provider_imp, ErrorCode error):
|
|
Dbg("Failed to register namespace '{0}': error = {1}", ServiceNamespace, SdsUtils.ErrorCodeToString(error));
|
|
delete rejected_service_provider_imp;
|
|
delete service_provider_exp;
|
|
delete rootds;
|
|
return;
|
|
|
|
case rootds.ChannelClosed():
|
|
Dbg("Directory service closed channel before responding!");
|
|
delete service_provider_exp;
|
|
delete rootds;
|
|
return;
|
|
}
|
|
|
|
ServiceThread svc = new ServiceThread(service_provider_exp);
|
|
|
|
Thread! thread = Thread.CreateThread(Thread.CurrentProcess, svc.ThreadMain);
|
|
_namespace_thread = thread;
|
|
|
|
thread.Start();
|
|
|
|
Dbg("Kernel.Main thread has started service thread.");
|
|
}
|
|
|
|
private class ServiceThread
|
|
{
|
|
TRef<ServiceProviderContract.Exp:Start> _service_provider;
|
|
|
|
public ServiceThread([Claims]ServiceProviderContract.Exp:Start! service_provider)
|
|
{
|
|
_service_provider = new TRef<ServiceProviderContract.Exp:Start>(service_provider);
|
|
}
|
|
|
|
public void ThreadMain()
|
|
{
|
|
Dbg("Service thread starting...");
|
|
|
|
ServiceProviderContract.Exp:Start! namespace_provider = ((!)_service_provider).Acquire();
|
|
_service_provider = null;
|
|
|
|
EMap<DirectoryServiceContract.Exp:Ready, DirectoryState!> dirs = new EMap<DirectoryServiceContract.Exp:Ready, DirectoryState!>();
|
|
EMap<FileContract.Exp:Ready, FileState!> files = new EMap<FileContract.Exp:Ready, FileState!>();
|
|
|
|
try {
|
|
Dbg("Service thread ready.");
|
|
|
|
for (;;) {
|
|
Dbg("switch receive");
|
|
switch receive {
|
|
case namespace_provider.ChannelClosed():
|
|
Dbg("Directory has closed service provider channel! Quitting.");
|
|
goto quit;
|
|
|
|
case namespace_provider.Connect(ServiceContract.Exp:Start! client_exp):
|
|
{
|
|
DirectoryServiceContract.Exp dir_client = client_exp as DirectoryServiceContract.Exp;
|
|
if (dir_client != null) {
|
|
Dbg("A client has connected to the kdfiles namespace (at root).");
|
|
DirectoryState new_dir_state = new DirectoryState("/");
|
|
dir_client.SendSuccess();
|
|
dirs.Add(dir_client, new_dir_state);
|
|
namespace_provider.SendAckConnect();
|
|
}
|
|
else {
|
|
Dbg("Client has offered a channel with an unsupported contract. Rejecting client.");
|
|
namespace_provider.SendNackConnect(client_exp);
|
|
}
|
|
break;
|
|
}
|
|
|
|
case dir.Bind(char[]! in ExHeap expath, ServiceContract.Exp:Start! service_exp) in dirs ~> _state:
|
|
{
|
|
// A directory client wants to bind to a directory or a file.
|
|
|
|
DirectoryServiceContract.Exp new_dir;
|
|
FileContract.Exp new_file;
|
|
string! path = Bitter.ToString2(expath);
|
|
|
|
string full_path = JoinPath(_state.AbsolutePath, path);
|
|
|
|
if ((new_dir = service_exp as DirectoryServiceContract.Exp) != null) {
|
|
|
|
Dbg("Received directory bind for '{0}'. Accepting client.", full_path);
|
|
DirectoryState new_state = new DirectoryState(full_path);
|
|
new_dir.SendSuccess();
|
|
dirs.Add(new_dir, new_state);
|
|
|
|
dir.SendAckBind();
|
|
|
|
}
|
|
else if ((new_file = service_exp as FileContract.Exp) != null) {
|
|
|
|
// A directory client wants to bind to a file. Unfortunately, the Singularity
|
|
// directory contract does not allow clients to express what they want to do
|
|
// with the object, once they have bound to it; there is no way to express the
|
|
// Win32 CreateFile flags, such as the access mask, creation disposition, share
|
|
// modes, etc. So for now, we open files for read-only access.
|
|
|
|
Dbg("Received file bind for '{0}'. Opening file...", full_path);
|
|
|
|
long FileHandle;
|
|
long FileLength;
|
|
|
|
bool result = KdFiles.CreateHostFile(
|
|
out FileHandle,
|
|
out FileLength,
|
|
full_path,
|
|
KdFiles.FILE_READ_DATA, // DesiredAccess
|
|
0, // FileAttributes
|
|
KdFiles.FILE_SHARE_READ, // ShareAccess
|
|
KdFiles.FILE_OPEN); // CreateDisposition
|
|
|
|
if (result) {
|
|
Dbg("Successfully opened host file. Length: {0} Handle: {1}", FileLength, FileHandle);
|
|
|
|
FileState new_file_state = new FileState(full_path);
|
|
new_file_state.FileHandle = FileHandle;
|
|
new_file_state.FileLength = FileLength;
|
|
|
|
new_file.SendSuccess();
|
|
files.Add(new_file, new_file_state);
|
|
dir.SendAckBind();
|
|
}
|
|
else {
|
|
Dbg("Failed to open host file.");
|
|
// -XXX- need to translate and propagate real error code
|
|
dir.SendNakBind(service_exp, ErrorCode.NotFound);
|
|
}
|
|
|
|
}
|
|
else {
|
|
Dbg("Received Bind on directory client, but the service contract is not recognized. Rejecting.");
|
|
dir.SendNakBind(service_exp, ErrorCode.ContractNotSupported);
|
|
|
|
}
|
|
|
|
delete expath;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
}
|
|
|
|
case dir.BeginEnumeration() in dirs ~> _state:
|
|
Dbg("Directory client wants to enumerate kdfile directory. Not supported.");
|
|
dir.SendEnumerationTerminated(ErrorCode.NotSupported);
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.GetAttributes(char[]! in ExHeap expath) in dirs ~> _state:
|
|
{
|
|
string path = Bitter.ToString2(expath);
|
|
delete expath;
|
|
string full_path = JoinPath(_state.AbsolutePath, path);
|
|
|
|
Dbg("Received GetAttributes. Opening host file '{0}'...", full_path);
|
|
|
|
long FileHandle;
|
|
long FileLength;
|
|
|
|
bool result = KdFiles.CreateHostFile(
|
|
out FileHandle,
|
|
out FileLength,
|
|
full_path,
|
|
KdFiles.FILE_READ_DATA, // DesiredAccess
|
|
0, // FileAttributes
|
|
KdFiles.FILE_SHARE_READ, // ShareAccess
|
|
KdFiles.FILE_OPEN); // CreateDisposition
|
|
|
|
if (result) {
|
|
Dbg("Successfully opened host file. Length: {0}", FileLength);
|
|
|
|
KdFiles.CloseHostFile(FileHandle);
|
|
|
|
FileAttributesRecord record;
|
|
record.CreationTime = 0;
|
|
record.LastAccessTime = 0;
|
|
record.LastWriteTime = 0;
|
|
record.FileSize = FileLength;
|
|
record.Type = NodeType.File;
|
|
dir.SendAckGetAttributes(record);
|
|
|
|
}
|
|
else {
|
|
|
|
Dbg("Failed to open host file. Sending ErrorCode.NotFound.");
|
|
dir.SendNakGetAttributes(ErrorCode.NotFound);
|
|
}
|
|
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
}
|
|
|
|
case dir.Notify(path, pattern, sendExisting, imp) in dirs ~> _state:
|
|
Dbg("Received Notify on directory channel. Not supported.");
|
|
dir.SendNakNotify(imp, ErrorCode.NotSupported);
|
|
delete path;
|
|
delete pattern;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.QueryACL(path, effective) in dirs ~> _state:
|
|
Dbg("Received QueryACL on directory channel. Not supported.");
|
|
dir.SendNakQueryACL(ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.Register(path, imp) in dirs ~> _state:
|
|
Dbg("Received Register on directory channel. Not supported.");
|
|
dir.SendNakRegister(imp, ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.Deregister(path) in dirs ~> _state:
|
|
Dbg("Received Deregister on directory channel. Not supported.");
|
|
dir.SendNakDeregister(ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.CreateDirectory(path) in dirs ~> _state:
|
|
Dbg("Received CreateDirectory on directory channel. Not supported.");
|
|
dir.SendNakCreateDirectory(ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.DeleteDirectory(path) in dirs ~> _state:
|
|
Dbg("Received DeleteDirectory on directory channel. Not supported.");
|
|
dir.SendNakDeleteDirectory(ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.CreateFile(path) in dirs ~> _state:
|
|
Dbg("Received CreateFile on directory channel. Not supported.");
|
|
dir.SendNakCreateFile(ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.CreateAndBindFile(path, exp) in dirs ~> _state:
|
|
Dbg("Received CreateAndBindFile on directory channel. Not supported.");
|
|
dir.SendNakCreateAndBindFile(exp, ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
|
|
case dir.DeleteFile(path) in dirs ~> _state:
|
|
Dbg("Received DeleteFile on directory channel. Not supported.");
|
|
dir.SendNakDeleteFile(ErrorCode.NotSupported);
|
|
delete path;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.StoreACL(filename, nodepattern, descendantpattern) in dirs ~> _state:
|
|
Dbg("Received StoreACL on directory channel. Not supported.");
|
|
dir.SendNakStoreACL(ErrorCode.NotSupported);
|
|
delete filename;
|
|
delete nodepattern;
|
|
delete descendantpattern;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.CreateLink(linkpath, linkvalue) in dirs ~> _state:
|
|
dir.SendNakCreateLink(ErrorCode.NotSupported);
|
|
delete linkpath;
|
|
delete linkvalue;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.DeleteLink(linkpath) in dirs ~> _state:
|
|
dir.SendNakDeleteLink(ErrorCode.NotSupported);
|
|
delete linkpath;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.GetLinkValue(linkpath) in dirs ~> _state:
|
|
dir.SendNakGetLinkValue(ErrorCode.NotSupported);
|
|
delete linkpath;
|
|
dirs.Add(dir, _state);
|
|
break;
|
|
|
|
case dir.ChannelClosed() in dirs ~> _state:
|
|
Dbg("A directory client has closed its channel.");
|
|
delete dir;
|
|
break;
|
|
|
|
case file.Read(byte[]! in ExHeap buffer, long buffer_offset, long file_offset, long max_length) in files ~> _state:
|
|
{
|
|
long bytes_transferred;
|
|
int error;
|
|
ReadHostFile(_state, buffer, buffer_offset, file_offset, max_length, out bytes_transferred, out error);
|
|
file.SendAckRead(buffer, bytes_transferred, error);
|
|
files.Add(file, _state);
|
|
break;
|
|
}
|
|
|
|
case file.Write(byte[]! in ExHeap buffer, long buffer_offset, long file_offset, long max_length) in files ~> _state:
|
|
Dbg("Received Write on file client. Not yet supported.");
|
|
file.SendAckWrite(buffer, 0, -1);
|
|
files.Add(file, _state);
|
|
break;
|
|
|
|
case file.Close() in files ~> _state:
|
|
Dbg("Received Close on file channel.");
|
|
KdFiles.CloseHostFile(_state.FileHandle);
|
|
file.SendAckClose();
|
|
delete file;
|
|
break;
|
|
|
|
case file.ChannelClosed() in files ~> _state:
|
|
Dbg("A file client has closed its channel.");
|
|
KdFiles.CloseHostFile(_state.FileHandle);
|
|
delete file;
|
|
break;
|
|
|
|
case unsatisfiable:
|
|
Dbg("Unsatisfiable receive!");
|
|
DebugStub.Break();
|
|
break;
|
|
}
|
|
}
|
|
|
|
quit:;
|
|
}
|
|
finally {
|
|
delete namespace_provider;
|
|
dirs.Dispose();
|
|
files.Dispose();
|
|
}
|
|
|
|
Dbg("Service thread has ended.");
|
|
}
|
|
|
|
static void ReadHostFile(
|
|
FileState! _state,
|
|
byte[]! in ExHeap buffer,
|
|
long buffer_offset,
|
|
long file_offset,
|
|
long max_length,
|
|
out long bytes_transferred,
|
|
out int error)
|
|
{
|
|
Dbg("Received Read request for 0x{0:x} bytes at file offset 0x{1:x}", max_length, file_offset);
|
|
|
|
if (buffer_offset < 0
|
|
|| file_offset < 0
|
|
|| max_length < 0
|
|
|| buffer_offset + max_length > buffer.Length
|
|
|| max_length > Int32.MaxValue)
|
|
{
|
|
Dbg("Received Read on file client, but args are bogus.");
|
|
bytes_transferred = 0;
|
|
error = -1;
|
|
return;
|
|
}
|
|
|
|
if (max_length == 0) {
|
|
Dbg("Received zero-length read on file client. Whatever.");
|
|
bytes_transferred = 0;
|
|
error = 0;
|
|
return;
|
|
}
|
|
|
|
if (file_offset >= _state.FileLength) {
|
|
Dbg("The read request begins beyond the end of the file. Returning 0 bytes, but no error code.");
|
|
bytes_transferred = 0;
|
|
error = 0;
|
|
return;
|
|
}
|
|
|
|
// We learned the file size from the host when we opened the file.
|
|
// The FileContract client will issue reads beyond the size of the file,
|
|
// but the KD host will fail these reads. So we clamp the read length.
|
|
|
|
long file_bytes_available = _state.FileLength - file_offset;
|
|
if (max_length > file_bytes_available) {
|
|
Dbg("The read request would exceed the length of the file. Trimming request length from 0x{0:x} to 0x{1:x} bytes.",
|
|
max_length, file_bytes_available);
|
|
max_length = file_bytes_available;
|
|
}
|
|
|
|
// At least one user of FileContract interprets a read that completes with fewer bytes than
|
|
// requested to be an indication of EOF. (bytes_transferred < bytes_requested) But the transfer size
|
|
// to the kernel debugger is rather small (less than 4K), so we loop here, reading as much as we can.
|
|
|
|
long total_bytes_transferred = 0;
|
|
|
|
for (;;) {
|
|
|
|
long bytes_remaining = max_length - total_bytes_transferred;
|
|
|
|
assert bytes_remaining >= 0;
|
|
|
|
if (bytes_remaining == 0) {
|
|
Dbg("Successfully read {0} bytes from host.", total_bytes_transferred);
|
|
bytes_transferred = total_bytes_transferred;
|
|
error = 0;
|
|
return;
|
|
}
|
|
|
|
// Perform one KD transfer.
|
|
|
|
int pass_bytes_requested = (int)Math.Min((long)Int32.MaxValue, bytes_remaining);
|
|
int pass_bytes_transferred;
|
|
bool result;
|
|
unsafe {
|
|
byte* unmanaged_buffer = &buffer[buffer_offset + total_bytes_transferred];
|
|
Dbg("Requesting {0} bytes at address 0x{1:x}", (int)max_length, (UIntPtr)unmanaged_buffer);
|
|
result = KdFiles.ReadHostFile(
|
|
_state.FileHandle,
|
|
file_offset + total_bytes_transferred,
|
|
unmanaged_buffer,
|
|
pass_bytes_requested,
|
|
out pass_bytes_transferred);
|
|
}
|
|
|
|
if (result) {
|
|
if (pass_bytes_transferred > 0) {
|
|
// Full read. Keep looping for more.
|
|
Dbg(" read {0:x} bytes @ buffer offset {1:x}", pass_bytes_transferred, total_bytes_transferred);
|
|
total_bytes_transferred += pass_bytes_transferred;
|
|
continue;
|
|
}
|
|
else {
|
|
// Short read. The transfer ends.
|
|
Dbg("Successfully read {0} bytes from host (short transfer).", total_bytes_transferred);
|
|
bytes_transferred = total_bytes_transferred;
|
|
error = 0;
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
total_bytes_transferred += pass_bytes_transferred;
|
|
Dbg("FAILED to read from host.");
|
|
bytes_transferred = total_bytes_transferred;
|
|
error = -1;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
class DirectoryState
|
|
{
|
|
public DirectoryState(string! path)
|
|
{
|
|
this.AbsolutePath = path;
|
|
}
|
|
|
|
public string! AbsolutePath;
|
|
}
|
|
|
|
class FileState
|
|
{
|
|
public FileState(string! path)
|
|
{
|
|
this.AbsolutePath = path;
|
|
this.FileHandle = 0;
|
|
}
|
|
|
|
public string! AbsolutePath;
|
|
public long FileHandle;
|
|
public long FileLength;
|
|
}
|
|
}
|
|
|
|
static string! JoinPath(string! container, string! relative_path)
|
|
{
|
|
bool container_ends_slash = container.EndsWith("/");
|
|
bool relative_starts_slash = relative_path.StartsWith("/");
|
|
|
|
if (container_ends_slash) {
|
|
if (relative_starts_slash)
|
|
return container + relative_path.Substring(1);
|
|
else
|
|
return container + relative_path;
|
|
}
|
|
else {
|
|
if (relative_starts_slash)
|
|
return container + relative_path;
|
|
else
|
|
return container + "/" + relative_path;
|
|
}
|
|
}
|
|
|
|
|
|
static bool _enable_dbg_print;
|
|
|
|
static void Dbg(string! line)
|
|
{
|
|
if (!_enable_dbg_print)
|
|
return;
|
|
|
|
string! msg = "KdFilesNamespace.sg: " + line;
|
|
DebugStub.WriteLine(msg);
|
|
}
|
|
|
|
static void Dbg(string! format, params object[]! args)
|
|
{
|
|
if (!_enable_dbg_print)
|
|
return;
|
|
|
|
string! line = String.Format(format, args);
|
|
DebugStub.WriteLine(line);
|
|
}
|
|
|
|
}
|
|
|
|
}
|