mirror of
https://github.com/NohamR/LLDBMemView.git
synced 2026-02-21 18:15:42 +00:00
Add region selection and command generation features
This commit is contained in:
@@ -25,4 +25,5 @@ Dirty pages: 0x100004000, 0x100005000
|
|||||||
|
|
||||||
Then paste it into the input area of this tool to visualize the memory regions.
|
Then paste it into the input area of this tool to visualize the memory regions.
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
BIN
docs/images/screen2.png
Normal file
BIN
docs/images/screen2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
44
index.html
44
index.html
@@ -64,7 +64,7 @@
|
|||||||
class="w-full h-48 p-4 font-mono text-xs bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all resize-y text-slate-800 dark:text-slate-200"
|
class="w-full h-48 p-4 font-mono text-xs bg-slate-50 dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 transition-all resize-y text-slate-800 dark:text-slate-200"
|
||||||
placeholder="[0x0000000100000000-0x0000000100004000) r-x __TEXT..."></textarea>
|
placeholder="[0x0000000100000000-0x0000000100004000) r-x __TEXT..."></textarea>
|
||||||
|
|
||||||
<div class="mt-4 flex gap-3">
|
<div class="mt-4 flex gap-3 flex-wrap">
|
||||||
<button onclick="parseMemory()"
|
<button onclick="parseMemory()"
|
||||||
class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2.5 rounded-lg font-medium transition shadow-sm active:transform active:scale-95">
|
class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-6 py-2.5 rounded-lg font-medium transition shadow-sm active:transform active:scale-95">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||||
@@ -76,6 +76,16 @@
|
|||||||
</svg>
|
</svg>
|
||||||
Parse Memory
|
Parse Memory
|
||||||
</button>
|
</button>
|
||||||
|
<button onclick="generateCommands()" id="generate-btn"
|
||||||
|
class="flex items-center gap-2 bg-emerald-600 hover:bg-emerald-700 text-white px-6 py-2.5 rounded-lg font-medium transition shadow-sm active:transform active:scale-95 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||||
|
disabled>
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
Generate Commands
|
||||||
|
</button>
|
||||||
<button onclick="clearAll()"
|
<button onclick="clearAll()"
|
||||||
class="flex items-center gap-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 px-6 py-2.5 rounded-lg font-medium transition shadow-sm">
|
class="flex items-center gap-2 bg-white dark:bg-slate-700 border border-slate-300 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-600 text-slate-700 dark:text-slate-200 px-6 py-2.5 rounded-lg font-medium transition shadow-sm">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24"
|
||||||
@@ -134,11 +144,41 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Command Output Section -->
|
||||||
|
<div id="command-output"
|
||||||
|
class="hidden p-6 bg-slate-50 dark:bg-slate-800/50 border-b border-slate-100 dark:border-slate-700">
|
||||||
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<h3 class="text-sm font-semibold text-slate-700 dark:text-slate-300 flex items-center gap-2">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-indigo-500" fill="none"
|
||||||
|
viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
Generated Commands (<span id="selected-count">0</span> selected)
|
||||||
|
</h3>
|
||||||
|
<button onclick="copyCommands(event)"
|
||||||
|
class="flex items-center gap-2 bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg font-medium transition shadow-sm text-sm">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
||||||
|
</svg>
|
||||||
|
Copy All
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<pre id="commands-text"
|
||||||
|
class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-600 rounded-lg p-4 text-xs font-mono text-slate-800 dark:text-slate-200 overflow-x-auto custom-scrollbar max-h-64"></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Table -->
|
<!-- Table -->
|
||||||
<div class="overflow-x-auto custom-scrollbar">
|
<div class="overflow-x-auto custom-scrollbar">
|
||||||
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
|
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-700">
|
||||||
<thead class="bg-slate-50 dark:bg-slate-800 sticky top-0 z-10">
|
<thead class="bg-slate-50 dark:bg-slate-800 sticky top-0 z-10">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th scope="col" class="px-6 py-3 text-left">
|
||||||
|
<input type="checkbox" id="select-all" onchange="toggleSelectAll()"
|
||||||
|
class="rounded text-indigo-600 focus:ring-indigo-500 cursor-pointer">
|
||||||
|
</th>
|
||||||
<th scope="col"
|
<th scope="col"
|
||||||
class="px-6 py-3 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700 group"
|
class="px-6 py-3 text-left text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-700 group"
|
||||||
onclick="sortBy('start')">
|
onclick="sortBy('start')">
|
||||||
@@ -181,7 +221,7 @@
|
|||||||
class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700">
|
class="bg-white dark:bg-slate-800 divide-y divide-slate-200 dark:divide-slate-700">
|
||||||
<!-- Content injected by JS -->
|
<!-- Content injected by JS -->
|
||||||
<tr class="text-center text-slate-400">
|
<tr class="text-center text-slate-400">
|
||||||
<td colspan="6" class="py-12">Waiting for input...</td>
|
<td colspan="7" class="py-12">Waiting for input...</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
147
src.js
147
src.js
@@ -1,5 +1,6 @@
|
|||||||
let allRegions = [];
|
let allRegions = [];
|
||||||
let currentSort = { column: null, direction: "asc" };
|
let currentSort = { column: null, direction: "asc" };
|
||||||
|
let selectedRegions = new Set();
|
||||||
|
|
||||||
// Dark Mode Toggle
|
// Dark Mode Toggle
|
||||||
function toggleDarkMode() {
|
function toggleDarkMode() {
|
||||||
@@ -95,6 +96,7 @@ function parseMemory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
allRegions = regions;
|
allRegions = regions;
|
||||||
|
selectedRegions.clear();
|
||||||
updateStats(regions);
|
updateStats(regions);
|
||||||
document.getElementById("stats").classList.remove("hidden");
|
document.getElementById("stats").classList.remove("hidden");
|
||||||
document.getElementById("stats").classList.add("grid");
|
document.getElementById("stats").classList.add("grid");
|
||||||
@@ -189,7 +191,10 @@ function renderTable(regions) {
|
|||||||
: "hover:bg-slate-50 dark:hover:bg-slate-700";
|
: "hover:bg-slate-50 dark:hover:bg-slate-700";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<tr class="${rowClass} transition-colors border-b border-slate-100 dark:border-slate-700 last:border-0">
|
<tr class="${rowClass} transition-colors border-b border-slate-100 dark:border-slate-700 last:border-0" data-start="${r.startStr}" data-end="${r.endStr}">
|
||||||
|
<td class="px-6 py-3 whitespace-nowrap">
|
||||||
|
<input type="checkbox" class="row-checkbox rounded text-indigo-600 focus:ring-indigo-500 cursor-pointer" data-start="${r.startStr}" data-end="${r.endStr}" onchange="updateSelection()" ${selectedRegions.has(r.startStr) ? "checked" : ""}>
|
||||||
|
</td>
|
||||||
<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.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 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 text-slate-700 dark:text-slate-300 font-medium" title="${r.size.toString()} bytes">${formatBytes(r.size)}</td>
|
||||||
@@ -214,6 +219,9 @@ function renderTable(regions) {
|
|||||||
remaining > 0
|
remaining > 0
|
||||||
? `${dataset.length} rows shown (${remaining} hidden for performance)`
|
? `${dataset.length} rows shown (${remaining} hidden for performance)`
|
||||||
: `${dataset.length} rows`;
|
: `${dataset.length} rows`;
|
||||||
|
|
||||||
|
// Update select-all checkbox state
|
||||||
|
updateSelectAllCheckbox();
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Helpers & Utilities ---
|
// --- Helpers & Utilities ---
|
||||||
@@ -249,7 +257,7 @@ function sortBy(column) {
|
|||||||
// Update active header
|
// Update active header
|
||||||
const headers = document.querySelectorAll("th");
|
const headers = document.querySelectorAll("th");
|
||||||
headers.forEach((th) => {
|
headers.forEach((th) => {
|
||||||
if (th.onclick.toString().includes(column)) {
|
if (th.onclick && th.onclick.toString().includes(column)) {
|
||||||
const icon = th.querySelector(".sort-icon");
|
const icon = th.querySelector(".sort-icon");
|
||||||
icon.textContent = currentSort.direction === "asc" ? "▲" : "▼";
|
icon.textContent = currentSort.direction === "asc" ? "▲" : "▼";
|
||||||
icon.classList.add("text-indigo-600");
|
icon.classList.add("text-indigo-600");
|
||||||
@@ -260,6 +268,136 @@ function sortBy(column) {
|
|||||||
applyFilters();
|
applyFilters();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Selection and Command Generation ---
|
||||||
|
|
||||||
|
function toggleSelectAll() {
|
||||||
|
const selectAllCheckbox = document.getElementById("select-all");
|
||||||
|
const checkboxes = document.querySelectorAll(".row-checkbox");
|
||||||
|
|
||||||
|
checkboxes.forEach((checkbox) => {
|
||||||
|
checkbox.checked = selectAllCheckbox.checked;
|
||||||
|
const start = checkbox.dataset.start;
|
||||||
|
const end = checkbox.dataset.end;
|
||||||
|
|
||||||
|
if (selectAllCheckbox.checked) {
|
||||||
|
selectedRegions.add(start);
|
||||||
|
} else {
|
||||||
|
selectedRegions.delete(start);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateGenerateButton();
|
||||||
|
updateSelectedCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelection() {
|
||||||
|
const checkboxes = document.querySelectorAll(".row-checkbox");
|
||||||
|
|
||||||
|
checkboxes.forEach((checkbox) => {
|
||||||
|
const start = checkbox.dataset.start;
|
||||||
|
if (checkbox.checked) {
|
||||||
|
selectedRegions.add(start);
|
||||||
|
} else {
|
||||||
|
selectedRegions.delete(start);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateSelectAllCheckbox();
|
||||||
|
updateGenerateButton();
|
||||||
|
updateSelectedCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectAllCheckbox() {
|
||||||
|
const selectAllCheckbox = document.getElementById("select-all");
|
||||||
|
const checkboxes = document.querySelectorAll(".row-checkbox");
|
||||||
|
|
||||||
|
if (checkboxes.length === 0) {
|
||||||
|
selectAllCheckbox.checked = false;
|
||||||
|
selectAllCheckbox.indeterminate = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedCount = Array.from(checkboxes).filter((cb) => cb.checked).length;
|
||||||
|
|
||||||
|
if (checkedCount === 0) {
|
||||||
|
selectAllCheckbox.checked = false;
|
||||||
|
selectAllCheckbox.indeterminate = false;
|
||||||
|
} else if (checkedCount === checkboxes.length) {
|
||||||
|
selectAllCheckbox.checked = true;
|
||||||
|
selectAllCheckbox.indeterminate = false;
|
||||||
|
} else {
|
||||||
|
selectAllCheckbox.checked = false;
|
||||||
|
selectAllCheckbox.indeterminate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateGenerateButton() {
|
||||||
|
const generateBtn = document.getElementById("generate-btn");
|
||||||
|
generateBtn.disabled = selectedRegions.size === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSelectedCount() {
|
||||||
|
const countSpan = document.getElementById("selected-count");
|
||||||
|
if (countSpan) {
|
||||||
|
countSpan.textContent = selectedRegions.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateCommands() {
|
||||||
|
if (selectedRegions.size === 0) return;
|
||||||
|
|
||||||
|
const commands = [];
|
||||||
|
|
||||||
|
// Get selected regions in order
|
||||||
|
const selectedRegionObjs = allRegions.filter((r) =>
|
||||||
|
selectedRegions.has(r.startStr),
|
||||||
|
);
|
||||||
|
|
||||||
|
selectedRegionObjs.forEach((region) => {
|
||||||
|
const startAddr = region.startStr;
|
||||||
|
const endAddr = region.endStr;
|
||||||
|
const command = `memory read --outfile /tmp/dump_${startAddr}_${endAddr}.bin --binary ${startAddr} ${endAddr}`;
|
||||||
|
commands.push(command);
|
||||||
|
});
|
||||||
|
|
||||||
|
const commandsText = document.getElementById("commands-text");
|
||||||
|
commandsText.textContent = commands.join("\n");
|
||||||
|
|
||||||
|
document.getElementById("command-output").classList.remove("hidden");
|
||||||
|
|
||||||
|
// Scroll to commands
|
||||||
|
document
|
||||||
|
.getElementById("command-output")
|
||||||
|
.scrollIntoView({ behavior: "smooth", block: "nearest" });
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyCommands(event) {
|
||||||
|
const commandsText = document.getElementById("commands-text").textContent;
|
||||||
|
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(commandsText)
|
||||||
|
.then(() => {
|
||||||
|
// Visual feedback
|
||||||
|
const copyBtn = event.target.closest("button");
|
||||||
|
const originalText = copyBtn.innerHTML;
|
||||||
|
|
||||||
|
copyBtn.innerHTML = `
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||||
|
</svg>
|
||||||
|
Copied!
|
||||||
|
`;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyBtn.innerHTML = originalText;
|
||||||
|
}, 2000);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Failed to copy:", err);
|
||||||
|
alert("Failed to copy to clipboard");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function applyFilters() {
|
function applyFilters() {
|
||||||
const addrFilter = document
|
const addrFilter = document
|
||||||
.getElementById("addressFilter")
|
.getElementById("addressFilter")
|
||||||
@@ -343,11 +481,12 @@ function applyFilters() {
|
|||||||
function clearAll() {
|
function clearAll() {
|
||||||
document.getElementById("input").value = "";
|
document.getElementById("input").value = "";
|
||||||
document.getElementById("table-body").innerHTML =
|
document.getElementById("table-body").innerHTML =
|
||||||
`<tr><td colspan="6" class="py-12 text-center text-slate-400">Waiting for input...</td></tr>`;
|
`<tr><td colspan="7" class="py-12 text-center text-slate-400">Waiting for input...</td></tr>`;
|
||||||
document.getElementById("stats").classList.add("hidden");
|
document.getElementById("stats").classList.add("hidden");
|
||||||
document.getElementById("stats").classList.remove("grid");
|
document.getElementById("stats").classList.remove("grid");
|
||||||
document.getElementById("filter-section").classList.add("hidden");
|
document.getElementById("filter-section").classList.add("hidden");
|
||||||
document.getElementById("filter-section").classList.remove("flex");
|
document.getElementById("filter-section").classList.remove("flex");
|
||||||
|
document.getElementById("command-output").classList.add("hidden");
|
||||||
document.getElementById("footer-count").innerText = "0 rows";
|
document.getElementById("footer-count").innerText = "0 rows";
|
||||||
|
|
||||||
// Reset filters
|
// Reset filters
|
||||||
@@ -357,4 +496,6 @@ function clearAll() {
|
|||||||
|
|
||||||
allRegions = [];
|
allRegions = [];
|
||||||
currentSort = { column: null, direction: "asc" };
|
currentSort = { column: null, direction: "asc" };
|
||||||
|
selectedRegions.clear();
|
||||||
|
updateGenerateButton();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user