Step 569: add disassembly register view model
This commit is contained in:
@@ -4000,4 +4000,13 @@ target_link_libraries(step568_test PRIVATE
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
add_executable(step569_test tests/step569_test.cpp)
|
||||
target_include_directories(step569_test PRIVATE src)
|
||||
target_link_libraries(step569_test PRIVATE
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::tree-sitter::tree-sitter
|
||||
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
|
||||
tree_sitter_javascript tree_sitter_typescript
|
||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||
|
||||
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||
|
||||
125
editor/src/DisassemblyRegisterViewModel.h
Normal file
125
editor/src/DisassemblyRegisterViewModel.h
Normal file
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
// Step 569: Disassembly + Register View Baseline
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct DisassemblyInstruction {
|
||||
std::uint64_t address = 0;
|
||||
std::string opcode;
|
||||
std::string operands;
|
||||
bool isCurrentInstruction = false;
|
||||
};
|
||||
|
||||
struct RegisterValue {
|
||||
std::string name;
|
||||
std::uint64_t value = 0;
|
||||
};
|
||||
|
||||
class DisassemblyRegisterViewModel {
|
||||
public:
|
||||
bool setInstructions(const std::vector<DisassemblyInstruction>& instructions, std::string* error) {
|
||||
if (!error) return false;
|
||||
error->clear();
|
||||
if (instructions.empty()) return fail(error, "instruction_list_empty");
|
||||
|
||||
for (const auto& instruction : instructions) {
|
||||
if (instruction.address == 0) return fail(error, "instruction_address_invalid");
|
||||
if (instruction.opcode.empty()) return fail(error, "instruction_opcode_missing");
|
||||
}
|
||||
|
||||
instructions_ = instructions;
|
||||
std::stable_sort(instructions_.begin(),
|
||||
instructions_.end(),
|
||||
[](const DisassemblyInstruction& a, const DisassemblyInstruction& b) {
|
||||
return a.address < b.address;
|
||||
});
|
||||
clearCurrentIp();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool setCurrentInstructionPointer(std::uint64_t address, std::string* error) {
|
||||
if (!error) return false;
|
||||
error->clear();
|
||||
if (address == 0) return fail(error, "instruction_pointer_invalid");
|
||||
|
||||
bool found = false;
|
||||
clearCurrentIp();
|
||||
for (auto& instruction : instructions_) {
|
||||
if (instruction.address == address) {
|
||||
instruction.isCurrentInstruction = true;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) return fail(error, "instruction_pointer_not_found");
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<DisassemblyInstruction> windowAroundAddress(std::uint64_t centerAddress,
|
||||
std::size_t radius) const {
|
||||
std::vector<DisassemblyInstruction> window;
|
||||
if (instructions_.empty()) return window;
|
||||
|
||||
std::size_t centerIndex = instructions_.size();
|
||||
for (std::size_t i = 0; i < instructions_.size(); ++i) {
|
||||
if (instructions_[i].address == centerAddress) {
|
||||
centerIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (centerIndex == instructions_.size()) return window;
|
||||
|
||||
const std::size_t begin = centerIndex > radius ? centerIndex - radius : 0;
|
||||
const std::size_t end = std::min(instructions_.size(), centerIndex + radius + 1);
|
||||
for (std::size_t i = begin; i < end; ++i) window.push_back(instructions_[i]);
|
||||
return window;
|
||||
}
|
||||
|
||||
bool setRegisterValue(const std::string& registerName,
|
||||
std::uint64_t value,
|
||||
std::string* error) {
|
||||
if (!error) return false;
|
||||
error->clear();
|
||||
if (registerName.empty()) return fail(error, "register_name_missing");
|
||||
registers_[registerName] = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool hasRegister(const std::string& registerName) const {
|
||||
return registers_.count(registerName) != 0;
|
||||
}
|
||||
|
||||
std::uint64_t registerValueOrZero(const std::string& registerName) const {
|
||||
auto it = registers_.find(registerName);
|
||||
if (it == registers_.end()) return 0;
|
||||
return it->second;
|
||||
}
|
||||
|
||||
std::vector<RegisterValue> sortedRegisters() const {
|
||||
std::vector<RegisterValue> out;
|
||||
for (const auto& kv : registers_) out.push_back({kv.first, kv.second});
|
||||
std::stable_sort(out.begin(), out.end(), [](const RegisterValue& a, const RegisterValue& b) {
|
||||
return a.name < b.name;
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
const std::vector<DisassemblyInstruction>& instructions() const { return instructions_; }
|
||||
|
||||
private:
|
||||
std::vector<DisassemblyInstruction> instructions_;
|
||||
std::map<std::string, std::uint64_t> registers_;
|
||||
|
||||
static bool fail(std::string* error, const char* code) {
|
||||
*error = code;
|
||||
return false;
|
||||
}
|
||||
|
||||
void clearCurrentIp() {
|
||||
for (auto& instruction : instructions_) instruction.isCurrentInstruction = false;
|
||||
}
|
||||
};
|
||||
156
editor/tests/step569_test.cpp
Normal file
156
editor/tests/step569_test.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
// Step 569: Disassembly + Register View Baseline (12 tests)
|
||||
|
||||
#include "DisassemblyRegisterViewModel.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
static int passed = 0, failed = 0;
|
||||
#define TEST(name) { std::cout << " " << #name << "... "; }
|
||||
#define PASS() { std::cout << "PASS\n"; ++passed; }
|
||||
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
|
||||
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
|
||||
|
||||
static DisassemblyInstruction inst(std::uint64_t address, const std::string& opcode, const std::string& operands) {
|
||||
return {address, opcode, operands, false};
|
||||
}
|
||||
|
||||
void test_set_instructions_success() {
|
||||
TEST(set_instructions_success);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setInstructions({inst(0x1000, "mov", "rax, rbx"), inst(0x1004, "ret", "")}, &error), "set should succeed");
|
||||
CHECK(model.instructions().size() == 2, "instruction count mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_set_instructions_rejects_empty() {
|
||||
TEST(set_instructions_rejects_empty);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(!model.setInstructions({}, &error), "empty instruction list should fail");
|
||||
CHECK(error == "instruction_list_empty", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_set_instructions_rejects_invalid_address() {
|
||||
TEST(set_instructions_rejects_invalid_address);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(!model.setInstructions({inst(0, "mov", "rax,rbx")}, &error), "zero address should fail");
|
||||
CHECK(error == "instruction_address_invalid", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_set_instructions_rejects_missing_opcode() {
|
||||
TEST(set_instructions_rejects_missing_opcode);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(!model.setInstructions({inst(0x1000, "", "rax,rbx")}, &error), "missing opcode should fail");
|
||||
CHECK(error == "instruction_opcode_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_instructions_are_sorted_by_address() {
|
||||
TEST(instructions_are_sorted_by_address);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setInstructions({inst(0x1010, "ret", ""), inst(0x1000, "push", "rbp")}, &error), "set should succeed");
|
||||
CHECK(model.instructions()[0].address == 0x1000, "first instruction should be lowest address");
|
||||
CHECK(model.instructions()[1].address == 0x1010, "second instruction should be highest address");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_set_current_instruction_pointer_success() {
|
||||
TEST(set_current_instruction_pointer_success);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setInstructions({inst(0x1000, "nop", ""), inst(0x1001, "ret", "")}, &error), "set should succeed");
|
||||
CHECK(model.setCurrentInstructionPointer(0x1001, &error), "set ip should succeed");
|
||||
CHECK(!model.instructions()[0].isCurrentInstruction, "first instruction should not be current");
|
||||
CHECK(model.instructions()[1].isCurrentInstruction, "second instruction should be current");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_set_current_instruction_pointer_rejects_unknown_address() {
|
||||
TEST(set_current_instruction_pointer_rejects_unknown_address);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setInstructions({inst(0x1000, "nop", "")}, &error), "set should succeed");
|
||||
CHECK(!model.setCurrentInstructionPointer(0x2000, &error), "unknown ip should fail");
|
||||
CHECK(error == "instruction_pointer_not_found", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_window_around_address_returns_radius_slice() {
|
||||
TEST(window_around_address_returns_radius_slice);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setInstructions({inst(0x1000, "a", ""), inst(0x1001, "b", ""), inst(0x1002, "c", ""), inst(0x1003, "d", "")}, &error), "set should succeed");
|
||||
const auto window = model.windowAroundAddress(0x1001, 1);
|
||||
CHECK(window.size() == 3, "window size mismatch");
|
||||
CHECK(window[0].address == 0x1000, "window start mismatch");
|
||||
CHECK(window[2].address == 0x1002, "window end mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_window_around_unknown_address_is_empty() {
|
||||
TEST(window_around_unknown_address_is_empty);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setInstructions({inst(0x1000, "a", "")}, &error), "set should succeed");
|
||||
const auto window = model.windowAroundAddress(0x9999, 1);
|
||||
CHECK(window.empty(), "window should be empty for unknown center");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_set_register_value_success() {
|
||||
TEST(set_register_value_success);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setRegisterValue("rax", 123, &error), "set register should succeed");
|
||||
CHECK(model.hasRegister("rax"), "register should exist");
|
||||
CHECK(model.registerValueOrZero("rax") == 123, "register value mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_set_register_value_rejects_empty_name() {
|
||||
TEST(set_register_value_rejects_empty_name);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(!model.setRegisterValue("", 123, &error), "empty register name should fail");
|
||||
CHECK(error == "register_name_missing", "wrong error");
|
||||
PASS();
|
||||
}
|
||||
|
||||
void test_sorted_registers_order_by_name() {
|
||||
TEST(sorted_registers_order_by_name);
|
||||
DisassemblyRegisterViewModel model;
|
||||
std::string error;
|
||||
CHECK(model.setRegisterValue("rbx", 2, &error), "set rbx failed");
|
||||
CHECK(model.setRegisterValue("rax", 1, &error), "set rax failed");
|
||||
const auto regs = model.sortedRegisters();
|
||||
CHECK(regs.size() == 2, "register count mismatch");
|
||||
CHECK(regs[0].name == "rax", "register order mismatch");
|
||||
CHECK(regs[1].name == "rbx", "register order mismatch");
|
||||
PASS();
|
||||
}
|
||||
|
||||
int main() {
|
||||
std::cout << "Step 569: Disassembly + Register View Baseline\n";
|
||||
|
||||
test_set_instructions_success(); // 1
|
||||
test_set_instructions_rejects_empty(); // 2
|
||||
test_set_instructions_rejects_invalid_address(); // 3
|
||||
test_set_instructions_rejects_missing_opcode(); // 4
|
||||
test_instructions_are_sorted_by_address(); // 5
|
||||
test_set_current_instruction_pointer_success(); // 6
|
||||
test_set_current_instruction_pointer_rejects_unknown_address();// 7
|
||||
test_window_around_address_returns_radius_slice(); // 8
|
||||
test_window_around_unknown_address_is_empty(); // 9
|
||||
test_set_register_value_success(); // 10
|
||||
test_set_register_value_rejects_empty_name(); // 11
|
||||
test_sorted_registers_order_by_name(); // 12
|
||||
|
||||
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||
return failed == 0 ? 0 : 1;
|
||||
}
|
||||
34
progress.md
34
progress.md
@@ -10336,3 +10336,37 @@ stack/debug correlation gating.
|
||||
**Phase 31a totals (564-568):**
|
||||
- **Steps completed:** 5
|
||||
- **New tests in this phase plan:** 56/56 passing
|
||||
|
||||
### Step 569: Disassembly + Register View Baseline
|
||||
**Status:** PASS (12/12 tests)
|
||||
|
||||
Implements a baseline disassembly/register inspection model for low-level runtime
|
||||
debug views with deterministic instruction ordering and register state access.
|
||||
|
||||
**Files added:**
|
||||
- `editor/src/DisassemblyRegisterViewModel.h` - disassembly/register module:
|
||||
- instruction schema with current-IP annotation state
|
||||
- instruction-set validation and deterministic address ordering
|
||||
- current instruction pointer selection and window slicing
|
||||
- register set/update/query helpers with stable sorted output
|
||||
- `editor/tests/step569_test.cpp` - 12 tests covering:
|
||||
- instruction load success and validation failures
|
||||
- deterministic instruction ordering behavior
|
||||
- current-IP selection behavior and unknown-address rejection
|
||||
- centered disassembly window behavior
|
||||
- register set/query validation behavior
|
||||
- register ordering behavior
|
||||
|
||||
**Files modified:**
|
||||
- `editor/CMakeLists.txt` - `step569_test` target
|
||||
|
||||
**Verification run:**
|
||||
- `cmake -S editor -B editor/build-native` - PASS
|
||||
- `cmake --build editor/build-native --target step569_test step568_test` - PASS
|
||||
- `./editor/build-native/step569_test` - PASS (12/12)
|
||||
- `./editor/build-native/step568_test` - PASS (8/8) regression coverage
|
||||
|
||||
**Architecture gate check:**
|
||||
- `editor/src/DisassemblyRegisterViewModel.h` within header-size limit (`125` <= `600`)
|
||||
- `editor/tests/step569_test.cpp` within test-file size guidance (`156` lines)
|
||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||
|
||||
Reference in New Issue
Block a user