mirror of
https://github.com/NohamR/LLDBMemView.git
synced 2026-02-22 02:25:43 +00:00
Initial commit: LLDB Memory Region Parser web app
This commit is contained in:
360
src.js
Normal file
360
src.js
Normal file
@@ -0,0 +1,360 @@
|
||||
let allRegions = [];
|
||||
let currentSort = { column: null, direction: "asc" };
|
||||
|
||||
// Dark Mode Toggle
|
||||
function toggleDarkMode() {
|
||||
const html = document.documentElement;
|
||||
const sunIcon = document.getElementById("theme-icon-sun");
|
||||
const moonIcon = document.getElementById("theme-icon-moon");
|
||||
|
||||
if (html.classList.contains("dark")) {
|
||||
html.classList.remove("dark");
|
||||
html.classList.add("light");
|
||||
sunIcon.classList.add("hidden");
|
||||
moonIcon.classList.remove("hidden");
|
||||
localStorage.setItem("theme", "light");
|
||||
} else {
|
||||
html.classList.remove("light");
|
||||
html.classList.add("dark");
|
||||
sunIcon.classList.remove("hidden");
|
||||
moonIcon.classList.add("hidden");
|
||||
localStorage.setItem("theme", "dark");
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize theme from localStorage
|
||||
(function initTheme() {
|
||||
const savedTheme = localStorage.getItem("theme") || "light";
|
||||
const html = document.documentElement;
|
||||
const sunIcon = document.getElementById("theme-icon-sun");
|
||||
const moonIcon = document.getElementById("theme-icon-moon");
|
||||
|
||||
if (savedTheme === "dark") {
|
||||
html.classList.remove("light");
|
||||
html.classList.add("dark");
|
||||
if (sunIcon) sunIcon.classList.remove("hidden");
|
||||
if (moonIcon) moonIcon.classList.add("hidden");
|
||||
} else {
|
||||
html.classList.remove("dark");
|
||||
html.classList.add("light");
|
||||
if (sunIcon) sunIcon.classList.add("hidden");
|
||||
if (moonIcon) moonIcon.classList.remove("hidden");
|
||||
}
|
||||
})();
|
||||
|
||||
// Parse Logic
|
||||
function parseMemory() {
|
||||
const input = document.getElementById("input").value;
|
||||
if (!input.trim()) return;
|
||||
|
||||
const lines = input.split("\n");
|
||||
const regions = [];
|
||||
let currentRegion = null;
|
||||
|
||||
// Updated Regex to be more robust
|
||||
// Captures: 1: Start, 2: End, 3: Perms, 4: Name (optional)
|
||||
const regionRegex =
|
||||
/\[(0x[0-9a-fA-F]+)-(0x[0-9a-fA-F]+)\)\s+([r\-][w\-][x\-][s\-]?)(?:\s+(.*))?/i;
|
||||
const dirtyRegex = /Dirty pages:\s+(.+)/i;
|
||||
|
||||
for (let line of lines) {
|
||||
line = line.trim();
|
||||
if (!line) continue;
|
||||
|
||||
const regionMatch = line.match(regionRegex);
|
||||
if (regionMatch) {
|
||||
const start = regionMatch[1];
|
||||
const end = regionMatch[2];
|
||||
const perms = regionMatch[3];
|
||||
const name = regionMatch[4] || "";
|
||||
|
||||
currentRegion = {
|
||||
startStr: start, // Keep string for display
|
||||
endStr: end, // Keep string for display
|
||||
startBig: BigInt(start), // Use BigInt for calculation
|
||||
endBig: BigInt(end), // Use BigInt for calculation
|
||||
perms,
|
||||
name,
|
||||
dirtyPages: [],
|
||||
dirtyCount: 0,
|
||||
};
|
||||
|
||||
// Calculate size safely using BigInt
|
||||
currentRegion.size = currentRegion.endBig - currentRegion.startBig;
|
||||
|
||||
regions.push(currentRegion);
|
||||
} else if (currentRegion) {
|
||||
// Check for metadata lines associated with the previous region
|
||||
const dirtyMatch = line.match(dirtyRegex);
|
||||
if (dirtyMatch) {
|
||||
const pages = dirtyMatch[1].split(",").map((p) => p.trim());
|
||||
currentRegion.dirtyPages = pages;
|
||||
currentRegion.dirtyCount = pages.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allRegions = regions;
|
||||
updateStats(regions);
|
||||
document.getElementById("stats").classList.remove("hidden");
|
||||
document.getElementById("stats").classList.add("grid");
|
||||
document.getElementById("filter-section").classList.remove("hidden");
|
||||
document.getElementById("filter-section").classList.add("flex");
|
||||
|
||||
// Set default sort to Start Address Ascending
|
||||
sortBy("start");
|
||||
}
|
||||
|
||||
function updateStats(regions) {
|
||||
const container = document.getElementById("stats");
|
||||
|
||||
const totalRegions = regions.length;
|
||||
const totalDirty = regions.reduce((acc, r) => acc + r.dirtyCount, 0);
|
||||
|
||||
// BigInt total size summation
|
||||
let totalSizeBytes = 0n;
|
||||
regions.forEach((r) => (totalSizeBytes += r.size));
|
||||
|
||||
const execCount = regions.filter((r) => r.perms.includes("x")).length;
|
||||
const writeCount = regions.filter((r) => r.perms.includes("w")).length;
|
||||
|
||||
const createCard = (label, value, colorClass) => `
|
||||
<div class="bg-white dark:bg-slate-700 p-4 rounded-lg border border-slate-200 dark:border-slate-600 shadow-sm hover:shadow-md transition-shadow">
|
||||
<div class="text-xs font-bold text-slate-400 dark:text-slate-500 uppercase tracking-wider mb-1">${label}</div>
|
||||
<div class="text-xl md:text-2xl font-bold ${colorClass}">${value}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.innerHTML =
|
||||
createCard(
|
||||
"Total Regions",
|
||||
totalRegions,
|
||||
"text-slate-800 dark:text-slate-200",
|
||||
) +
|
||||
createCard(
|
||||
"Total Size",
|
||||
formatBytes(totalSizeBytes),
|
||||
"text-indigo-600 dark:text-indigo-400",
|
||||
) +
|
||||
createCard("Executable", execCount, "text-red-500 dark:text-red-400") +
|
||||
createCard("Writable", writeCount, "text-amber-500 dark:text-amber-400") +
|
||||
createCard(
|
||||
"Dirty Pages",
|
||||
totalDirty,
|
||||
"text-emerald-600 dark:text-emerald-400",
|
||||
);
|
||||
}
|
||||
|
||||
function renderTable(regions) {
|
||||
const tbody = document.getElementById("table-body");
|
||||
const footer = document.getElementById("footer-count");
|
||||
|
||||
if (regions.length === 0) {
|
||||
tbody.innerHTML = `<tr><td colspan="6" class="py-8 text-center text-slate-400">No matching regions found.</td></tr>`;
|
||||
footer.textContent = "0 rows";
|
||||
return;
|
||||
}
|
||||
|
||||
// Limit rendering for performance if massive (simple virtualization cap)
|
||||
const renderLimit = 2000;
|
||||
const dataset = regions.slice(0, renderLimit);
|
||||
|
||||
const html = dataset
|
||||
.map((r) => {
|
||||
const isExecutable = r.perms.includes("x");
|
||||
const isWritable = r.perms.includes("w");
|
||||
const isReadable = r.perms.includes("r");
|
||||
|
||||
// Badges for perms
|
||||
let permBadges = "";
|
||||
if (isReadable)
|
||||
permBadges += `<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 mr-1">R</span>`;
|
||||
else
|
||||
permBadges += `<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-slate-100 dark:bg-slate-700 text-slate-400 dark:text-slate-500 mr-1">-</span>`;
|
||||
|
||||
if (isWritable)
|
||||
permBadges += `<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200 mr-1">W</span>`;
|
||||
else
|
||||
permBadges += `<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-slate-100 dark:bg-slate-700 text-slate-400 dark:text-slate-500 mr-1">-</span>`;
|
||||
|
||||
if (isExecutable)
|
||||
permBadges += `<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-200">X</span>`;
|
||||
else
|
||||
permBadges += `<span class="inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-slate-100 dark:bg-slate-700 text-slate-400 dark:text-slate-500">-</span>`;
|
||||
|
||||
// Row highlighting
|
||||
const rowClass =
|
||||
r.dirtyCount > 0
|
||||
? "bg-orange-50 dark:bg-orange-900/20 hover:bg-orange-100 dark:hover:bg-orange-900/30"
|
||||
: "hover:bg-slate-50 dark:hover:bg-slate-700";
|
||||
|
||||
return `
|
||||
<tr class="${rowClass} transition-colors border-b border-slate-100 dark:border-slate-700 last:border-0">
|
||||
<td class="px-6 py-3 whitespace-nowrap font-mono text-xs text-slate-600 dark:text-slate-400 select-all">${r.startStr}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap font-mono text-xs text-slate-600 dark:text-slate-400 select-all">${r.endStr}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-sm text-slate-700 dark:text-slate-300 font-medium" title="${r.size.toString()} bytes">${formatBytes(r.size)}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-sm">${permBadges}</td>
|
||||
<td class="px-6 py-3 text-sm text-slate-700 dark:text-slate-300 break-all max-w-xs">${r.name || '<span class="text-slate-300 dark:text-slate-600 italic">unnamed</span>'}</td>
|
||||
<td class="px-6 py-3 whitespace-nowrap text-sm">
|
||||
${
|
||||
r.dirtyCount > 0
|
||||
? `<span class="px-2 py-1 text-xs font-bold leading-none text-orange-800 dark:text-orange-200 bg-orange-200 dark:bg-orange-900/50 rounded-full">${r.dirtyCount}</span>`
|
||||
: '<span class="text-slate-300 dark:text-slate-600 text-xs">-</span>'
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
})
|
||||
.join("");
|
||||
|
||||
tbody.innerHTML = html;
|
||||
|
||||
const remaining = regions.length - renderLimit;
|
||||
footer.innerHTML =
|
||||
remaining > 0
|
||||
? `${dataset.length} rows shown (${remaining} hidden for performance)`
|
||||
: `${dataset.length} rows`;
|
||||
}
|
||||
|
||||
// --- Helpers & Utilities ---
|
||||
|
||||
function formatBytes(bigIntBytes) {
|
||||
if (bigIntBytes === 0n) return "0 B";
|
||||
|
||||
// Convert to Number for display formatting (precision loss at PB scale is acceptable for UI)
|
||||
const bytes = Number(bigIntBytes);
|
||||
const k = 1024;
|
||||
const sizes = ["B", "KB", "MB", "GB", "TB", "PB"];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
||||
}
|
||||
|
||||
function sortBy(column) {
|
||||
if (currentSort.column === column) {
|
||||
currentSort.direction = currentSort.direction === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
currentSort.column = column;
|
||||
currentSort.direction = "asc";
|
||||
}
|
||||
|
||||
// Visual indicator helper
|
||||
document
|
||||
.querySelectorAll(".sort-icon")
|
||||
.forEach((el) => (el.textContent = "⇅"));
|
||||
document
|
||||
.querySelectorAll(".sort-icon")
|
||||
.forEach((el) => el.classList.remove("text-indigo-600"));
|
||||
|
||||
// Update active header
|
||||
const headers = document.querySelectorAll("th");
|
||||
headers.forEach((th) => {
|
||||
if (th.onclick.toString().includes(column)) {
|
||||
const icon = th.querySelector(".sort-icon");
|
||||
icon.textContent = currentSort.direction === "asc" ? "▲" : "▼";
|
||||
icon.classList.add("text-indigo-600");
|
||||
icon.classList.remove("invisible");
|
||||
}
|
||||
});
|
||||
|
||||
applyFilters();
|
||||
}
|
||||
|
||||
function applyFilters() {
|
||||
const addrFilter = document
|
||||
.getElementById("addressFilter")
|
||||
.value.toLowerCase();
|
||||
const permsFilter = document.getElementById("permsFilter").value;
|
||||
const dirtyFilter = document.getElementById("dirtyFilter").value;
|
||||
const hideEmpty = document.getElementById("hideEmptyRegions").checked;
|
||||
|
||||
let filtered = allRegions.filter((r) => {
|
||||
// 1. Empty/Permissions check
|
||||
if (hideEmpty && r.perms.startsWith("---")) return false;
|
||||
|
||||
// 2. Address/Name Search
|
||||
if (addrFilter) {
|
||||
const searchStr = (r.startStr + r.endStr + r.name).toLowerCase();
|
||||
if (!searchStr.includes(addrFilter)) return false;
|
||||
}
|
||||
|
||||
// 3. Permissions Dropdown
|
||||
if (permsFilter) {
|
||||
if (
|
||||
permsFilter === "rw" &&
|
||||
(!r.perms.includes("r") || !r.perms.includes("w"))
|
||||
)
|
||||
return false;
|
||||
else if (
|
||||
permsFilter === "rx" &&
|
||||
(!r.perms.includes("r") || !r.perms.includes("x"))
|
||||
)
|
||||
return false;
|
||||
else if (permsFilter.length === 1 && !r.perms.includes(permsFilter))
|
||||
return false;
|
||||
}
|
||||
|
||||
// 4. Dirty Pages
|
||||
if (dirtyFilter === "dirty" && r.dirtyCount === 0) return false;
|
||||
if (dirtyFilter === "clean" && r.dirtyCount > 0) return false;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// Apply Sort
|
||||
filtered.sort((a, b) => {
|
||||
let valA, valB;
|
||||
|
||||
switch (currentSort.column) {
|
||||
case "start":
|
||||
valA = a.startBig;
|
||||
valB = b.startBig;
|
||||
break;
|
||||
case "end":
|
||||
valA = a.endBig;
|
||||
valB = b.endBig;
|
||||
break;
|
||||
case "size":
|
||||
valA = a.size;
|
||||
valB = b.size;
|
||||
break;
|
||||
case "perms":
|
||||
valA = a.perms;
|
||||
valB = b.perms;
|
||||
break; // string comparison
|
||||
case "name":
|
||||
valA = a.name.toLowerCase();
|
||||
valB = b.name.toLowerCase();
|
||||
break;
|
||||
case "dirty":
|
||||
valA = a.dirtyCount;
|
||||
valB = b.dirtyCount;
|
||||
break;
|
||||
}
|
||||
|
||||
if (valA < valB) return currentSort.direction === "asc" ? -1 : 1;
|
||||
if (valA > valB) return currentSort.direction === "asc" ? 1 : -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
renderTable(filtered);
|
||||
}
|
||||
|
||||
function clearAll() {
|
||||
document.getElementById("input").value = "";
|
||||
document.getElementById("table-body").innerHTML =
|
||||
`<tr><td colspan="6" class="py-12 text-center text-slate-400">Waiting for input...</td></tr>`;
|
||||
document.getElementById("stats").classList.add("hidden");
|
||||
document.getElementById("stats").classList.remove("grid");
|
||||
document.getElementById("filter-section").classList.add("hidden");
|
||||
document.getElementById("filter-section").classList.remove("flex");
|
||||
document.getElementById("footer-count").innerText = "0 rows";
|
||||
|
||||
// Reset filters
|
||||
document.getElementById("addressFilter").value = "";
|
||||
document.getElementById("permsFilter").value = "";
|
||||
document.getElementById("dirtyFilter").value = "";
|
||||
|
||||
allRegions = [];
|
||||
currentSort = { column: null, direction: "asc" };
|
||||
}
|
||||
Reference in New Issue
Block a user