diff --git a/src/Xenia.Debug/Debugger.cs b/src/Xenia.Debug/Debugger.cs index c11d6a45f..e1e65657e 100644 --- a/src/Xenia.Debug/Debugger.cs +++ b/src/Xenia.Debug/Debugger.cs @@ -12,7 +12,6 @@ using xe.debug.proto; using Xenia.Debug.Utilities; namespace Xenia.Debug { - public class Debugger { private readonly static string kServerHostname = ""; private readonly static int kServerPort = 19000; @@ -35,6 +34,19 @@ namespace Xenia.Debug { pendingRequests = new ConcurrentDictionary(); private uint nextRequestId = 1; + private FileMappingHandle memoryHandle; + public IntPtr Membase { + get; + } + + public unsafe byte* TranslateVirtual(uint address) { + return (byte*)Membase.ToPointer() + address; + } + + public unsafe byte* TranslatePhysical(uint address) { + return (byte*)Membase.ToPointer() + 0xFFFFFFFF + address; + } + public event EventHandler StateChanged; public State CurrentState { @@ -126,6 +138,27 @@ namespace Xenia.Debug { int requestDataOffset = AttachRequest.EndAttachRequest(fbb); var response = await CommitRequest(fbb, RequestData.AttachRequest, requestDataOffset); + System.Diagnostics.Debug.Assert(response.ResponseDataType == + ResponseData.AttachResponse); + var attachResponse = new AttachResponse(); + response.GetResponseData(attachResponse); + + // Open mmap to share memroy. + memoryHandle = FileMapping.OpenFileMapping( + FileMapAccess.FILE_MAP_ALL_ACCESS, false, attachResponse.MemoryFile); + if (memoryHandle.IsInvalid) { + System.Diagnostics.Debug.Fail("Unable to open target memory"); + Detach(); + return; + } + + // Setup the memory system. This maps the emulator memory into our address + // space. + if (!Memory.InitializeMapping(memoryHandle)) { + Detach(); + return; + } + OnStateChanged(State.Attached); } @@ -134,8 +167,17 @@ namespace Xenia.Debug { return; } - socket.Close(); - socket = null; + Memory.UninitializeMapping(); + + if (memoryHandle != null) { + memoryHandle.Close(); + memoryHandle = null; + } + + if (socket != null) { + socket.Close(); + socket = null; + } OnStateChanged(State.Detached); } diff --git a/src/Xenia.Debug/Memory.cs b/src/Xenia.Debug/Memory.cs index ddf216afa..979e864bb 100644 --- a/src/Xenia.Debug/Memory.cs +++ b/src/Xenia.Debug/Memory.cs @@ -6,15 +6,100 @@ using System.Threading.Tasks; using Xenia.Debug.Utilities; namespace Xenia.Debug { - public class Memory : Changeable { + public class Memory : Changeable, IDisposable { private readonly Debugger debugger; + private class MapInfo { + public ulong virtualAddressStart; + public ulong virtualAddressEnd; + public ulong targetAddress; + public IntPtr ptr; + } + private MapInfo[] mapInfos = new MapInfo[]{ + // From memory.cc: + new MapInfo(){virtualAddressStart = 0x00000000, virtualAddressEnd = 0x3FFFFFFF, + targetAddress = 0x0000000000000000}, + new MapInfo(){virtualAddressStart = 0x40000000, virtualAddressEnd = 0x7EFFFFFF, + targetAddress = 0x0000000040000000}, + new MapInfo(){virtualAddressStart = 0x7F000000, virtualAddressEnd = 0x7FFFFFFF, + targetAddress = 0x0000000100000000}, + new MapInfo(){virtualAddressStart = 0x80000000, virtualAddressEnd = 0x8FFFFFFF, + targetAddress = 0x0000000080000000}, + new MapInfo(){virtualAddressStart = 0x90000000, virtualAddressEnd = 0x9FFFFFFF, + targetAddress = 0x0000000080000000}, + new MapInfo(){virtualAddressStart = 0xA0000000, virtualAddressEnd = 0xBFFFFFFF, + targetAddress = 0x0000000100000000}, + new MapInfo(){virtualAddressStart = 0xC0000000, virtualAddressEnd = 0xDFFFFFFF, + targetAddress = 0x0000000100000000}, + new MapInfo(){virtualAddressStart = 0xE0000000, virtualAddressEnd = 0xFFFFFFFF, + targetAddress = 0x0000000100000000}, + new MapInfo(){virtualAddressStart = 0x100000000, virtualAddressEnd = 0x11FFFFFFF, + targetAddress = 0x0000000100000000}, + }; + + public UIntPtr VirtualMembase; + public UIntPtr PhysicalMembase; + public Memory(Debugger debugger) { this.debugger = debugger; } + public bool InitializeMapping(FileMappingHandle mappingHandle) { + ulong mappingBase = 0x100000000; + foreach (MapInfo mapInfo in mapInfos) { + uint targetAddressLow = (uint)mapInfo.targetAddress; + uint targetAddressHigh = (uint)(mapInfo.targetAddress >> 32); + mapInfo.ptr = FileMapping.MapViewOfFileEx( + mappingHandle, FileMapAccess.FILE_MAP_ALL_ACCESS, + targetAddressHigh, targetAddressLow, + mapInfo.virtualAddressEnd - mapInfo.virtualAddressStart + 1, + mappingBase + mapInfo.virtualAddressStart); + if (mapInfo.ptr == IntPtr.Zero) { + System.Diagnostics.Debug.Fail("Unable to place memory at target address"); + return false; + } + } + VirtualMembase = new UIntPtr(mappingBase); + PhysicalMembase = new UIntPtr(mappingBase + 0x100000000); + return true; + } + + public void UninitializeMapping() { + foreach (MapInfo mapInfo in mapInfos) { + FileMapping.UnmapViewOfFile(mapInfo.ptr); + mapInfo.ptr = IntPtr.Zero; + } + } + + private void OnDispose() { + UninitializeMapping(); + } + public MemoryView CreateView() { return new MemoryView(this); } + + #region Dispose + private bool disposed = false; + + ~Memory() { + Dispose(false); + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) { + if (!disposed) { + if (disposing) { + // TODO: dispose managed state (managed objects). + } + OnDispose(); + disposed = true; + } + } + #endregion } } diff --git a/src/Xenia.Debug/Proto/xe/debug/proto/AttachResponse.cs b/src/Xenia.Debug/Proto/xe/debug/proto/AttachResponse.cs index 137aa0cee..0358b24e2 100644 --- a/src/Xenia.Debug/Proto/xe/debug/proto/AttachResponse.cs +++ b/src/Xenia.Debug/Proto/xe/debug/proto/AttachResponse.cs @@ -10,8 +10,25 @@ public sealed class AttachResponse : Table { public static AttachResponse GetRootAsAttachResponse(ByteBuffer _bb, AttachResponse obj) { return (obj.__init(_bb.GetInt(_bb.Position) + _bb.Position, _bb)); } public AttachResponse __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; } + public string MemoryFile { get { int o = __offset(4); return o != 0 ? __string(o + bb_pos) : null; } } + public string FunctionsFile { get { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; } } + public string FunctionsTraceFile { get { int o = __offset(8); return o != 0 ? __string(o + bb_pos) : null; } } - public static void StartAttachResponse(FlatBufferBuilder builder) { builder.StartObject(0); } + public static int CreateAttachResponse(FlatBufferBuilder builder, + int memory_file = 0, + int functions_file = 0, + int functions_trace_file = 0) { + builder.StartObject(3); + AttachResponse.AddFunctionsTraceFile(builder, functions_trace_file); + AttachResponse.AddFunctionsFile(builder, functions_file); + AttachResponse.AddMemoryFile(builder, memory_file); + return AttachResponse.EndAttachResponse(builder); + } + + public static void StartAttachResponse(FlatBufferBuilder builder) { builder.StartObject(3); } + public static void AddMemoryFile(FlatBufferBuilder builder, int memoryFileOffset) { builder.AddOffset(0, memoryFileOffset, 0); } + public static void AddFunctionsFile(FlatBufferBuilder builder, int functionsFileOffset) { builder.AddOffset(1, functionsFileOffset, 0); } + public static void AddFunctionsTraceFile(FlatBufferBuilder builder, int functionsTraceFileOffset) { builder.AddOffset(2, functionsTraceFileOffset, 0); } public static int EndAttachResponse(FlatBufferBuilder builder) { int o = builder.EndObject(); return o; diff --git a/src/Xenia.Debug/Utilities/Disposable.cs b/src/Xenia.Debug/Utilities/Disposable.cs new file mode 100644 index 000000000..97d0348c2 --- /dev/null +++ b/src/Xenia.Debug/Utilities/Disposable.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Xenia.Debug.Utilities { + public class Disposable : IDisposable { + private bool disposed = false; + + protected virtual void OnDispose() { + } + + protected virtual void Dispose(bool disposing) { + if (!disposed) { + if (disposing) { + // TODO: dispose managed state (managed objects). + } + OnDispose(); + disposed = true; + } + } + + ~Disposable() { + Dispose(false); + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Xenia.Debug/Utilities/FileMapping.cs b/src/Xenia.Debug/Utilities/FileMapping.cs new file mode 100644 index 000000000..40c3abcc1 --- /dev/null +++ b/src/Xenia.Debug/Utilities/FileMapping.cs @@ -0,0 +1,142 @@ +using Microsoft.Win32.SafeHandles; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Text; +using System.Threading.Tasks; + +namespace Xenia.Debug.Utilities { + // From: http://1code.codeplex.com/SourceControl/latest#Visual Studio 2008/CSFileMappingClient/Program.cs + + /// + /// Access rights for file mapping objects + /// http://msdn.microsoft.com/en-us/library/aa366559.aspx + /// + [Flags] + public enum FileMapAccess { + FILE_MAP_COPY = 0x0001, + FILE_MAP_WRITE = 0x0002, + FILE_MAP_READ = 0x0004, + FILE_MAP_ALL_ACCESS = 0x000F001F + } + + /// + /// Represents a wrapper class for a file mapping handle. + /// + [SuppressUnmanagedCodeSecurity, + HostProtection(SecurityAction.LinkDemand, MayLeakOnAbort = true)] + public sealed class FileMappingHandle : SafeHandleZeroOrMinusOneIsInvalid { + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + private FileMappingHandle() + : base(true) { + } + + [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] + public FileMappingHandle(IntPtr handle, bool ownsHandle) + : base(ownsHandle) { + base.SetHandle(handle); + } + + [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), + DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool CloseHandle(IntPtr handle); + + protected override bool ReleaseHandle() { + return CloseHandle(base.handle); + } + } + + + /// + /// The class exposes Windows APIs used in this code sample. + /// + [SuppressUnmanagedCodeSecurity] + public class FileMapping { + /// + /// Opens a named file mapping object. + /// + /// + /// The access to the file mapping object. This access is checked against + /// any security descriptor on the target file mapping object. + /// + /// + /// If this parameter is TRUE, a process created by the CreateProcess + /// function can inherit the handle; otherwise, the handle cannot be + /// inherited. + /// + /// + /// The name of the file mapping object to be opened. + /// + /// + /// If the function succeeds, the return value is an open handle to the + /// specified file mapping object. + /// + [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern FileMappingHandle OpenFileMapping( + FileMapAccess dwDesiredAccess, bool bInheritHandle, string lpName); + + + /// + /// Maps a view of a file mapping into the address space of a calling + /// process. + /// + /// + /// A handle to a file mapping object. The CreateFileMapping and + /// OpenFileMapping functions return this handle. + /// + /// + /// The type of access to a file mapping object, which determines the + /// protection of the pages. + /// + /// + /// A high-order DWORD of the file offset where the view begins. + /// + /// + /// A low-order DWORD of the file offset where the view is to begin. + /// + /// + /// The number of bytes of a file mapping to map to the view. All bytes + /// must be within the maximum size specified by CreateFileMapping. + /// + /// + /// If the function succeeds, the return value is the starting address + /// of the mapped view. + /// + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr MapViewOfFile( + FileMappingHandle hFileMappingObject, + FileMapAccess dwDesiredAccess, + uint dwFileOffsetHigh, + uint dwFileOffsetLow, + ulong dwNumberOfBytesToMap); + + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + public static extern IntPtr MapViewOfFileEx( + FileMappingHandle hFileMappingObject, + FileMapAccess dwDesiredAccess, + uint dwFileOffsetHigh, + uint dwFileOffsetLow, + ulong dwNumberOfBytesToMap, + ulong lpBaseAddress); + + + /// + /// Unmaps a mapped view of a file from the calling process's address + /// space. + /// + /// + /// A pointer to the base address of the mapped view of a file that + /// is to be unmapped. + /// + /// + [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + public static extern bool UnmapViewOfFile(IntPtr lpBaseAddress); + } +} diff --git a/src/Xenia.Debug/Xenia.Debug.csproj b/src/Xenia.Debug/Xenia.Debug.csproj index 794b5a7dd..902b61543 100644 --- a/src/Xenia.Debug/Xenia.Debug.csproj +++ b/src/Xenia.Debug/Xenia.Debug.csproj @@ -21,6 +21,7 @@ x64 prompt MinimumRecommendedRules.ruleset + true ..\..\build\bin\Release\ @@ -31,6 +32,7 @@ x64 prompt MinimumRecommendedRules.ruleset + true @@ -88,6 +90,8 @@ + + diff --git a/src/xenia/debug/debugger.cc b/src/xenia/debug/debugger.cc index 8f5e2d358..17837182b 100644 --- a/src/xenia/debug/debugger.cc +++ b/src/xenia/debug/debugger.cc @@ -60,14 +60,13 @@ Debugger::~Debugger() { bool Debugger::StartSession() { std::wstring session_path = xe::to_wstring(FLAGS_debug_session_path); - std::wstring functions_path = xe::join_paths(session_path, L"functions"); + functions_path_ = xe::join_paths(session_path, L"functions"); functions_file_ = - ChunkedMappedMemoryWriter::Open(functions_path, 32 * 1024 * 1024, false); + ChunkedMappedMemoryWriter::Open(functions_path_, 32 * 1024 * 1024, false); - std::wstring functions_trace_path = - xe::join_paths(session_path, L"functions.trace"); + functions_trace_path_ = xe::join_paths(session_path, L"functions.trace"); functions_trace_file_ = ChunkedMappedMemoryWriter::Open( - functions_trace_path, 32 * 1024 * 1024, true); + functions_trace_path_, 32 * 1024 * 1024, true); listen_socket_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (listen_socket_ < 1) { @@ -195,9 +194,12 @@ void Debugger::OnMessage(std::vector buffer) { case proto::RequestData_AttachRequest: { // Send debug info. response_data_type = proto::ResponseData_AttachResponse; - auto response_data = proto::AttachResponseBuilder(fbb); - // - response_data_offset = response_data.Finish().Union(); + response_data_offset = + proto::CreateAttachResponse( + fbb, fbb.CreateString( + xe::to_string(emulator()->memory()->file_name())), + fbb.CreateString(xe::to_string(functions_path_)), + fbb.CreateString(xe::to_string(functions_trace_path_))).Union(); // Allow continuation if we were blocked waiting for a client. accept_fence_.Signal(); diff --git a/src/xenia/debug/debugger.h b/src/xenia/debug/debugger.h index ccd754b32..85481ce2d 100644 --- a/src/xenia/debug/debugger.h +++ b/src/xenia/debug/debugger.h @@ -116,7 +116,9 @@ class Debugger { UINT_PTR client_socket_; std::thread receive_thread_; + std::wstring functions_path_; std::unique_ptr functions_file_; + std::wstring functions_trace_path_; std::unique_ptr functions_trace_file_; std::mutex threads_lock_; diff --git a/src/xenia/debug/proto/messages.fbs b/src/xenia/debug/proto/messages.fbs index 46f12de92..b4d12d532 100644 --- a/src/xenia/debug/proto/messages.fbs +++ b/src/xenia/debug/proto/messages.fbs @@ -26,8 +26,9 @@ table AttachRequest { // } table AttachResponse { - // file paths - // mmap name + memory_file:string; + functions_file:string; + functions_trace_file:string; } union RequestData { diff --git a/src/xenia/debug/proto/messages_generated.h b/src/xenia/debug/proto/messages_generated.h index cedbf6101..48bf73672 100644 --- a/src/xenia/debug/proto/messages_generated.h +++ b/src/xenia/debug/proto/messages_generated.h @@ -167,8 +167,17 @@ inline flatbuffers::Offset CreateAttachRequest(flatbuffers::FlatB } struct AttachResponse FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { + const flatbuffers::String *memory_file() const { return GetPointer(4); } + const flatbuffers::String *functions_file() const { return GetPointer(6); } + const flatbuffers::String *functions_trace_file() const { return GetPointer(8); } bool Verify(flatbuffers::Verifier &verifier) const { return VerifyTableStart(verifier) && + VerifyField(verifier, 4 /* memory_file */) && + verifier.Verify(memory_file()) && + VerifyField(verifier, 6 /* functions_file */) && + verifier.Verify(functions_file()) && + VerifyField(verifier, 8 /* functions_trace_file */) && + verifier.Verify(functions_trace_file()) && verifier.EndTable(); } }; @@ -176,16 +185,25 @@ struct AttachResponse FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table { struct AttachResponseBuilder { flatbuffers::FlatBufferBuilder &fbb_; flatbuffers::uoffset_t start_; + void add_memory_file(flatbuffers::Offset memory_file) { fbb_.AddOffset(4, memory_file); } + void add_functions_file(flatbuffers::Offset functions_file) { fbb_.AddOffset(6, functions_file); } + void add_functions_trace_file(flatbuffers::Offset functions_trace_file) { fbb_.AddOffset(8, functions_trace_file); } AttachResponseBuilder(flatbuffers::FlatBufferBuilder &_fbb) : fbb_(_fbb) { start_ = fbb_.StartTable(); } AttachResponseBuilder &operator=(const AttachResponseBuilder &); flatbuffers::Offset Finish() { - auto o = flatbuffers::Offset(fbb_.EndTable(start_, 0)); + auto o = flatbuffers::Offset(fbb_.EndTable(start_, 3)); return o; } }; -inline flatbuffers::Offset CreateAttachResponse(flatbuffers::FlatBufferBuilder &_fbb) { +inline flatbuffers::Offset CreateAttachResponse(flatbuffers::FlatBufferBuilder &_fbb, + flatbuffers::Offset memory_file = 0, + flatbuffers::Offset functions_file = 0, + flatbuffers::Offset functions_trace_file = 0) { AttachResponseBuilder builder_(_fbb); + builder_.add_functions_trace_file(functions_trace_file); + builder_.add_functions_file(functions_file); + builder_.add_memory_file(memory_file); return builder_.Finish(); } diff --git a/src/xenia/memory.cc b/src/xenia/memory.cc index 4caaaca4f..7d8be1c55 100644 --- a/src/xenia/memory.cc +++ b/src/xenia/memory.cc @@ -16,6 +16,7 @@ #include "xenia/base/logging.h" #include "xenia/base/math.h" +#include "xenia/base/threading.h" #include "xenia/cpu/mmio_handler.h" // TODO(benvanik): move xbox.h out @@ -114,13 +115,17 @@ Memory::~Memory() { } int Memory::Initialize() { + wchar_t file_name[256]; + wsprintf(file_name, L"Local\\xenia_memory_%p", xe::threading::ticks()); + file_name_ = file_name; + // Create main page file-backed mapping. This is all reserved but // uncommitted (so it shouldn't expand page file). #if XE_PLATFORM_WIN32 mapping_ = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE | SEC_RESERVE, // entire 4gb space + 512mb physical: - 1, 0x1FFFFFFF, NULL); + 1, 0x1FFFFFFF, file_name_.c_str()); #else char mapping_path[] = "/xenia/mapping/XXXXXX"; mktemp(mapping_path); diff --git a/src/xenia/memory.h b/src/xenia/memory.h index 14bb09095..f892e5818 100644 --- a/src/xenia/memory.h +++ b/src/xenia/memory.h @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "xenia/base/platform.h" @@ -158,6 +159,8 @@ class Memory { int Initialize(); + const std::wstring& file_name() const { return file_name_; } + inline uint8_t* virtual_membase() const { return virtual_membase_; } inline uint8_t* TranslateVirtual(uint32_t guest_address) const { return virtual_membase_ + guest_address; @@ -211,6 +214,7 @@ class Memory { void UnmapViews(); private: + std::wstring file_name_; uint32_t system_page_size_; uint8_t* virtual_membase_; uint8_t* physical_membase_;