-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
159 lines (143 loc) · 10.3 KB
/
Copy pathProgram.cs
File metadata and controls
159 lines (143 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
using System.Runtime.InteropServices;
namespace SafeRamTester;
internal static class Program
{
[STAThread]
private static void Main()
{
ApplicationConfiguration.Initialize();
Application.Run(new MainForm());
}
}
internal sealed class MainForm : Form
{
private readonly Label _ram = new() { AutoSize = true };
private readonly Label _range = new() { AutoSize = true, Text = "Allocated test address range: not allocated" };
private readonly NumericUpDown _mib = new() { Minimum = 1, Maximum = 1_048_576, ThousandsSeparator = true, Width = 130 };
private readonly NumericUpDown _threads = new() { Minimum = 1, Maximum = 256, Width = 70 };
private readonly Button _go = new() { Text = "GO", Width = 120, Height = 44 };
private readonly Button _cancel = new() { Text = "Cancel", Width = 100, Height = 44, Enabled = false };
private readonly ProgressBar _progress = new() { Dock = DockStyle.Fill, Maximum = 1000 };
private readonly Label _status = new() { AutoSize = true, Font = new Font(SystemFonts.DefaultFont, FontStyle.Bold) };
private readonly ListView _errors = new() { Dock = DockStyle.Fill, View = View.Details, FullRowSelect = true, GridLines = true };
private readonly System.Windows.Forms.Timer _flash = new() { Interval = 450 };
private CancellationTokenSource? _cts;
private ulong _available;
public MainForm()
{
Text = "Safe RAM Address Tester";
Width = 1000; Height = 680; MinimumSize = new Size(760, 500);
_errors.Columns.Add("Virtual address", 180);
_errors.Columns.Add("Pass", 65);
_errors.Columns.Add("Read", 80);
_errors.Columns.Add("Expected", 80);
_errors.Columns.Add("UTC time", 190);
var settings = new FlowLayoutPanel { Dock = DockStyle.Fill, AutoSize = true };
settings.Controls.AddRange([new Label { Text = "Test memory (MiB):", AutoSize = true, Margin = new Padding(3, 7, 3, 3) }, _mib,
new Label { Text = "Worker threads:", AutoSize = true, Margin = new Padding(18, 7, 3, 3) }, _threads, _go, _cancel]);
var layout = new TableLayoutPanel { Dock = DockStyle.Fill, Padding = new Padding(12), ColumnCount = 1, RowCount = 8 };
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 52));
layout.RowStyles.Add(new RowStyle(SizeType.Absolute, 28)); layout.RowStyles.Add(new RowStyle(SizeType.AutoSize));
layout.RowStyles.Add(new RowStyle(SizeType.AutoSize)); layout.RowStyles.Add(new RowStyle(SizeType.Percent, 100));
layout.Controls.Add(_ram); layout.Controls.Add(_range); layout.Controls.Add(new Label { AutoSize = true, ForeColor = Color.DarkSlateGray,
Text = "Windows applications cannot safely address all physical RAM. This tests every byte in memory allocated to this process." });
layout.Controls.Add(settings); layout.Controls.Add(_progress); layout.Controls.Add(_status);
layout.Controls.Add(new Label { Text = "Verification errors (also written immediately to the log):", AutoSize = true }); layout.Controls.Add(_errors);
Controls.Add(layout);
LoadMemoryInfo();
_threads.Value = Math.Max(1, Environment.ProcessorCount);
_go.Click += async (_, _) => await RunTestAsync();
_cancel.Click += (_, _) => _cts?.Cancel();
_flash.Tick += (_, _) => _status.ForeColor = _status.ForeColor == Color.Red ? Color.DarkGreen : Color.Red;
FormClosing += (_, e) => { if (_cts is not null) { _cts.Cancel(); e.Cancel = true; _status.Text = "Stopping workers… close again when stopped."; } };
}
private void LoadMemoryInfo()
{
var m = new MEMORYSTATUSEX(); m.dwLength = (uint)Marshal.SizeOf<MEMORYSTATUSEX>();
if (!GlobalMemoryStatusEx(ref m)) { _ram.Text = "Unable to query RAM."; return; }
_available = m.ullAvailPhys;
_ram.Text = $"Installed/usable physical RAM: {Fmt(m.ullTotalPhys)} Currently available: {Fmt(m.ullAvailPhys)} Process pointer width: {IntPtr.Size * 8}-bit";
ulong suggested = Math.Max(1, Math.Min(m.ullAvailPhys / 2 / 1_048_576, 4096));
_mib.Value = Math.Min((decimal)suggested, _mib.Maximum);
}
private async Task RunTestAsync()
{
ulong requested = (ulong)_mib.Value * 1_048_576;
if (requested > _available * 3 / 4 && MessageBox.Show("This requests more than 75% of currently available RAM and may make Windows unresponsive. Continue?", "High memory request", MessageBoxButtons.YesNo, MessageBoxIcon.Warning) != DialogResult.Yes) return;
SetRunning(true); _errors.Items.Clear(); _progress.Value = 0; _flash.Stop(); _status.ForeColor = SystemColors.ControlText;
_status.Text = "Allocating test blocks…";
_cts = new CancellationTokenSource();
string log = Path.Combine(AppContext.BaseDirectory, $"RAM_Test_{DateTime.Now:yyyy-MM-dd_HH-mm-ss}.log");
try
{
var progress = new Progress<TestUpdate>(ApplyUpdate);
TestResult result = await RamTest.RunAsync(requested, (int)_threads.Value, log, progress, _cts.Token);
_progress.Value = 1000;
_status.Text = $"TEST COMPLETE — {result.ErrorCount:N0} error(s) — Log: {Path.GetFileName(log)}";
_status.ForeColor = result.ErrorCount == 0 ? Color.DarkGreen : Color.Red; _flash.Start();
}
catch (OperationCanceledException) { _status.Text = $"Test cancelled — partial log: {Path.GetFileName(log)}"; }
catch (Exception ex) { _status.Text = "Test stopped: " + ex.Message; MessageBox.Show(ex.ToString(), "RAM test error", MessageBoxButtons.OK, MessageBoxIcon.Error); }
finally { _cts?.Dispose(); _cts = null; SetRunning(false); }
}
private void ApplyUpdate(TestUpdate u)
{
if (u.Start != 0) _range.Text = $"Allocated virtual addresses: 0x{u.Start:X16} through 0x{u.End:X16} (separate blocks; gaps may exist)";
if (u.Fraction >= 0) _progress.Value = Math.Clamp((int)(u.Fraction * 1000), 0, 1000);
if (u.Error is { } er) _errors.Items.Add(new ListViewItem([ $"0x{er.Address:X16}", er.Pass.ToString(), er.Actual.ToString(), er.Expected.ToString(), er.WhenUtc.ToString("O") ]));
if (u.Message is not null) _status.Text = u.Message;
}
private void SetRunning(bool running) { _go.Enabled = !running; _cancel.Enabled = running; _mib.Enabled = !running; _threads.Enabled = !running; }
private static string Fmt(ulong n) => $"{n / 1_073_741_824.0:N2} GiB";
[DllImport("kernel32.dll", SetLastError = true)] private static extern bool GlobalMemoryStatusEx(ref MEMORYSTATUSEX lpBuffer);
[StructLayout(LayoutKind.Sequential)] private struct MEMORYSTATUSEX { public uint dwLength, dwMemoryLoad; public ulong ullTotalPhys, ullAvailPhys, ullTotalPageFile, ullAvailPageFile, ullTotalVirtual, ullAvailVirtual, ullAvailExtendedVirtual; }
}
internal readonly record struct MemoryError(ulong Address, int Pass, byte Actual, byte Expected, DateTime WhenUtc);
internal readonly record struct TestUpdate(double Fraction = -1, ulong Start = 0, ulong End = 0, MemoryError? Error = null, string? Message = null);
internal readonly record struct TestResult(long ErrorCount);
internal static unsafe class RamTest
{
private const int BlockSize = 16 * 1024 * 1024;
private sealed record Block(IntPtr Pointer, int Length);
public static Task<TestResult> RunAsync(ulong bytes, int workers, string logPath, IProgress<TestUpdate> progress, CancellationToken token) => Task.Run(() => Run(bytes, workers, logPath, progress, token), token);
private static TestResult Run(ulong bytes, int workers, string logPath, IProgress<TestUpdate> progress, CancellationToken token)
{
var blocks = new List<Block>();
try
{
ulong left = bytes;
while (left > 0) { token.ThrowIfCancellationRequested(); int size = (int)Math.Min((ulong)BlockSize, left); var p = Marshal.AllocHGlobal(size); blocks.Add(new Block(p, size)); left -= (ulong)size; }
ulong start = blocks.Min(b => (ulong)b.Pointer), end = blocks.Max(b => (ulong)b.Pointer + (ulong)b.Length - 1);
progress.Report(new TestUpdate(Start: start, End: end, Message: $"Testing {bytes:N0} bytes in {blocks.Count:N0} blocks…"));
long completed = 0, errors = 0; long total = checked((long)bytes);
object logLock = new();
using var writer = new StreamWriter(logPath, false) { AutoFlush = true };
writer.WriteLine($"Safe RAM Tester started: {DateTime.Now:O}"); writer.WriteLine($"Bytes: {bytes}; workers: {workers}; passes per address: 10"); writer.WriteLine($"Allocated virtual span: 0x{start:X16}-0x{end:X16}");
var options = new ParallelOptions { MaxDegreeOfParallelism = workers, CancellationToken = token };
Parallel.ForEach(blocks, options, block =>
{
byte* p = (byte*)block.Pointer;
for (int i = 0; i < block.Length; i++)
{
if ((i & 0xFFFF) == 0) token.ThrowIfCancellationRequested();
for (int pass = 1; pass <= 10; pass++)
{
ref byte cell = ref p[i];
Volatile.Write(ref cell, (byte)1); byte actual = Volatile.Read(ref cell); if (actual != 1) Record((ulong)(p + i), pass, actual, 1);
Volatile.Write(ref cell, (byte)0); actual = Volatile.Read(ref cell); if (actual != 0) Record((ulong)(p + i), pass, actual, 0);
}
}
long done = Interlocked.Add(ref completed, block.Length); progress.Report(new TestUpdate(Fraction: (double)done / total));
});
writer.WriteLine($"Completed: {DateTime.Now:O}; errors: {errors}"); return new TestResult(errors);
void Record(ulong address, int pass, byte actual, byte expected)
{
var er = new MemoryError(address, pass, actual, expected, DateTime.UtcNow); Interlocked.Increment(ref errors);
lock (logLock) writer.WriteLine($"ERROR address=0x{address:X16} pass={pass} read={actual} expected={expected} utc={er.WhenUtc:O}");
progress.Report(new TestUpdate(Error: er));
}
}
finally { foreach (var b in blocks) Marshal.FreeHGlobal(b.Pointer); }
}
}