diff --git a/.gitignore b/.gitignore index 9875358..6ba7f7d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ +.cache/ +.vscode/ .direnv/ +build/ .pre-commit-config.yaml # Created by https://www.toptal.com/developers/gitignore/api/c++,c,cmake @@ -95,3 +98,41 @@ _deps *-prefix/ # End of https://www.toptal.com/developers/gitignore/api/c++,c,cmake +# Created by https://www.toptal.com/developers/gitignore/api/c++ +# Edit at https://www.toptal.com/developers/gitignore?templates=c++ + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +# End of https://www.toptal.com/developers/gitignore/api/c++ diff --git a/CMakeLists.txt b/CMakeLists.txt index da2aa01..de98228 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,4 +6,3 @@ set(CMAKE_C_STANDARD 17) include_directories(include) add_subdirectory(src) - diff --git a/flake.lock b/flake.lock index 513a7e2..b4bf5e4 100644 --- a/flake.lock +++ b/flake.lock @@ -105,6 +105,26 @@ "type": "github" } }, + "nur-xin": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1714033851, + "narHash": "sha256-Mi7m3p9vmtNOdyD1hLse/tzxDuV3bwP0gKrmOBPiQ4c=", + "ref": "refs/heads/master", + "rev": "809554f41ac44acc4b1ec21473746c2af9993f2f", + "revCount": 149, + "type": "git", + "url": "https://git.xinyang.life/xin/nur.git" + }, + "original": { + "type": "git", + "url": "https://git.xinyang.life/xin/nur.git" + } + }, "pre-commit-hooks": { "inputs": { "flake-compat": "flake-compat", @@ -133,6 +153,7 @@ "inputs": { "flake-utils": "flake-utils", "nixpkgs": "nixpkgs", + "nur-xin": "nur-xin", "pre-commit-hooks": "pre-commit-hooks" } }, diff --git a/flake.nix b/flake.nix index 6966514..f79213a 100644 --- a/flake.nix +++ b/flake.nix @@ -2,6 +2,10 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; + nur-xin = { + url = "git+https://git.xinyang.life/xin/nur.git"; + inputs.nixpkgs.follows = "nixpkgs"; + }; pre-commit-hooks = { url = "github:cachix/pre-commit-hooks.nix"; inputs.nixpkgs.follows = "nixpkgs"; @@ -11,7 +15,13 @@ outputs = { self, ... }@inputs: with inputs; flake-utils.lib.eachDefaultSystem (system: let - pkgs = import nixpkgs { inherit system; }; + pkgs = import nixpkgs { + inherit system; overlays = [ + (self: super: { + mini-gdbstub = nur-xin.legacyPackages.${system}.mini-gdbstub; + }) + ]; + }; in { checks = { @@ -35,6 +45,8 @@ clang-tools cmake gdb + cli11 + mini-gdbstub ]; }; } diff --git a/include/api.h b/include/api.h deleted file mode 100644 index bf966f2..0000000 --- a/include/api.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef _DIFFTEST_API_H_ -#define _DIFFTEST_API_H_ - -#include - -extern "C" { - -typedef struct { - enum { - ACT_NONE, - ACT_BREAKPOINT, - ACT_WATCH, - ACT_RWATCH, - ACT_WWATCH, - ACT_SHUTDOWN - } reason; - size_t data; -} gdb_action_t; - -typedef enum { BP_SOFTWARE = 0, BP_WRITE, BP_READ, BP_ACCESS } bp_type_t; - -struct target_ops { - void (*cont)(void *args, gdb_action_t *res); - void (*stepi)(void *args, gdb_action_t *res); - int (*read_reg)(void *args, int regno, size_t *value); - int (*write_reg)(void *args, int regno, size_t value); - int (*read_mem)(void *args, size_t addr, size_t len, void *val); - int (*write_mem)(void *args, size_t addr, size_t len, void *val); - bool (*set_bp)(void *args, size_t addr, bp_type_t type); - bool (*del_bp)(void *args, size_t addr, bp_type_t type); - void (*on_interrupt)(void *args); -}; -} - -#endif \ No newline at end of file diff --git a/include/api.hpp b/include/api.hpp new file mode 100644 index 0000000..7a0f2d3 --- /dev/null +++ b/include/api.hpp @@ -0,0 +1,68 @@ +#ifndef _DIFFTEST_API_H_ +#define _DIFFTEST_API_H_ + +#include +#include +#include +#include +#include + +// Target dynamic library has to implement these functions +struct TargetOps { + typedef void (*cont_t)(void *args, gdb_action_t *res); + cont_t cont; + + typedef void (*stepi_t)(void *args, gdb_action_t *res); + stepi_t stepi; + + typedef int (*read_reg_t)(void *args, int regno, size_t *value); + read_reg_t read_reg; + + typedef int (*write_reg_t)(void *args, int regno, size_t value); + write_reg_t write_reg; + + typedef int (*read_mem_t)(void *args, size_t addr, size_t len, void *val); + read_mem_t read_mem; + + typedef int (*write_mem_t)(void *args, size_t addr, size_t len, void *val); + write_mem_t write_mem; + + typedef bool (*set_bp_t)(void *args, size_t addr, bp_type_t type); + set_bp_t set_bp; + + typedef bool (*del_bp_t)(void *args, size_t addr, bp_type_t type); + del_bp_t del_bp; + + typedef void (*on_interrupt_t)(void *args); + on_interrupt_t on_interrupt; + + typedef void (*init_t)(void *args); + init_t init; +}; + +struct TargetMeta { + std::string name; + std::filesystem::path libpath; + void *dlhandle; +}; + +class Target { +public: + TargetOps ops; + TargetMeta meta; + arch_info_t arch; + size_t argsize; + std::vector args; // used as a buffer to store target specific values + + gdb_action_t last_res; + + Target(){}; + Target(const std::string &name, const std::string &prefix, + const std::filesystem::path &path); + ~Target(); + + bool is_on_breakpoint() const; + bool is_on_breakpoint(const gdb_action_t &res) const; +}; + +#endif \ No newline at end of file diff --git a/include/config.hpp b/include/config.hpp new file mode 100644 index 0000000..f08593f --- /dev/null +++ b/include/config.hpp @@ -0,0 +1,14 @@ +#include +#include +#include +#include +#include + +struct Config { + std::filesystem::path memory_file; + std::vector refs; + std::filesystem::path dut; + int cli_parse(int argc, char **argv); +}; + +extern Config config; diff --git a/include/difftest.hpp b/include/difftest.hpp index 3fe1dcd..e4fbdd2 100644 --- a/include/difftest.hpp +++ b/include/difftest.hpp @@ -1,4 +1,73 @@ #ifndef _DIFFTEST_DIFFTEST_H_ #define _DIFFTEST_DIFFTEST_H_ +#include "api.hpp" +#include +#include +#include +#include + +#include +class Difftest { +private: + Target dut; + std::vector refs; + +public: + Difftest(Target &&dut, std::vector &&refs); + + void setup(const std::filesystem::path &memory_file); + gdb_action_t stepi(); + gdb_action_t cont(); + static bool check(Target &dut, Target &ref) { + for (int r = 0; r < dut.arch.reg_num; r++) { + size_t regdut = 0, regref = 0; + dut.ops.read_reg(dut.args.data(), r, ®dut); + ref.ops.read_reg(ref.args.data(), r, ®ref); + if (regdut != regref) { + std::cout << "reg: " << r << " dut: " << regdut << " ref: " << regref + << std::endl; + throw std::runtime_error("Difftest failed"); + } + } + return true; + }; + bool check_all(); + + class Iterator { + private: + Difftest &difftest; + size_t index; + bool on_dut; + + public: + Iterator(Difftest &difftest, size_t index, bool on_dut) + : difftest(difftest), index(index), on_dut(on_dut) {} + + Iterator &operator++() { + if (on_dut) { + on_dut = false; + } else { + ++index; + } + return *this; + } + + bool operator!=(const Iterator &other) const { + return index != other.index || on_dut != other.on_dut; + } + + Target &operator*() { + if (on_dut) { + return difftest.dut; + } else { + return difftest.refs.at(index); + } + } + }; + + Iterator begin() { return Iterator(*this, 0, true); } + + Iterator end() { return Iterator(*this, refs.size(), false); } +}; #endif \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index cf986e3..e30dab7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,2 +1,3 @@ -add_executable(test main.cpp) - +add_executable(diffu cli.cpp difftest.cpp loader.cpp main.cpp) +target_link_libraries(diffu PRIVATE gdbstub) +set_target_properties(diffu PROPERTIES ENABLE_EXPORTS 1) diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 0000000..e17214b --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,25 @@ +#include "config.hpp" +#include +#include + +int Config::cli_parse(int argc, char **argv) { + CLI::App app; + app.add_option("-m,--memory", memory_file, "Content of memory") + ->required() + ->check(CLI::ExistingFile); + + app.add_option("--ref", refs, "Reference dynamic library") + ->required() + ->check(CLI::ExistingFile); + + app.add_option("--dut", dut, "Design under test") + ->required() + ->check(CLI::ExistingFile); + + app.set_config("-c,--config") + ->transform(CLI::FileOnDefaultPath("./difftest.toml")); + + CLI11_PARSE(app, argc, argv); + + return 0; +} \ No newline at end of file diff --git a/src/difftest.cpp b/src/difftest.cpp new file mode 100644 index 0000000..cb6aaf3 --- /dev/null +++ b/src/difftest.cpp @@ -0,0 +1,98 @@ +#include "api.hpp" +#include +#include +#include + +#include + +Difftest::Difftest(Target &&dut, std::vector &&refs) { + this->dut = std::move(dut); + this->refs = std::move(refs); + + for (const auto &ref : refs) { + if (dut.arch.reg_byte != ref.arch.reg_byte || + dut.arch.reg_num != ref.arch.reg_num) { + throw std::runtime_error("Ref and dut must have the same architecture"); + } + } +} + +void Difftest::setup(const std::filesystem::path &memory_file) { + std::ifstream is = std::ifstream(memory_file, std::ios::binary); + + // Seek to the end to determine the file size + is.seekg(0, std::ios::end); + std::streampos memsize = is.tellg(); + is.seekg(0, std::ios::beg); + + std::vector membuf(memsize); + is.read(membuf.data(), memsize); + is.close(); + + // Initialize memory + // TODO: reset vector should not be hardcoded + // for(auto target : *this) { + for (auto it = this->begin(); it != this->end(); ++it) { + auto &target = *it; + printf("init addr: %p\n", target.ops.init); + target.ops.init(target.args.data()); + target.ops.write_mem(target.args.data(), 0x80000000UL, membuf.size(), + membuf.data()); + target.ops.write_reg(target.args.data(), 32, 0x80000000UL); + } +} + +bool Difftest::check_all() { + for (auto &ref : refs) { + check(dut, ref); + } + return true; +} + +gdb_action_t Difftest::stepi() { + bool breakflag = false; + Target *pbreak; + for (auto it = this->begin(); it != this->end(); ++it) { + auto &target = *it; + target.ops.stepi(target.args.data(), &target.last_res); + if (target.is_on_breakpoint()) { + breakflag = true; + pbreak = ⌖ + } + } + + if (breakflag) { + gdb_action_t ret = {.reason = gdb_action_t::ACT_BREAKPOINT}; + pbreak->ops.read_reg(pbreak->args.data(), 32, &ret.data); + return ret; + } + return {gdb_action_t::ACT_NONE, 0}; +} + +gdb_action_t Difftest::cont() { + bool breakflag = false; + Target *pbreak; + check_all(); + std::cerr << "setup finished." << std::endl; + while (true) { + // for(auto &target : *this) { + for (auto it = this->begin(); it != this->end(); ++it) { + auto &target = *it; + target.ops.stepi(target.args.data(), &target.last_res); + + if (target.is_on_breakpoint()) { + breakflag = true; + pbreak = ⌖ + } + } + + check_all(); + + if (breakflag) { + gdb_action_t ret = {.reason = gdb_action_t::ACT_BREAKPOINT}; + pbreak->ops.read_reg(pbreak->args.data(), 32, &ret.data); + return ret; + } + } + return {gdb_action_t::ACT_NONE, 0}; +} diff --git a/src/loader.cpp b/src/loader.cpp new file mode 100644 index 0000000..ae91bb1 --- /dev/null +++ b/src/loader.cpp @@ -0,0 +1,76 @@ +#include "api.hpp" +#include +#include +#include +#include + +Target::Target(const std::string &name, const std::string &func_prefix, + const std::filesystem::path &path) { + + std::cout << path.c_str() << std::endl; + meta = {.name = name, + .libpath = path, + .dlhandle = dlopen(path.c_str(), RTLD_LAZY)}; + + if (!meta.dlhandle) { + throw std::runtime_error(dlerror()); + } + +#define LOAD_SYMBOL(ops, handle, prefix, name) \ + do { \ + ops.name = reinterpret_cast( \ + dlsym(handle, (prefix + #name).c_str())); \ + if (!ops.name) \ + goto load_error; \ + } while (0); + + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, cont); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, stepi); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, read_reg); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, write_reg); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, read_mem); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, write_mem); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, set_bp); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, del_bp); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, on_interrupt); + LOAD_SYMBOL(ops, meta.dlhandle, func_prefix, init); + +#undef LOAD_SYMBOL + + size_t *argsize_sym; + argsize_sym = reinterpret_cast(dlsym(meta.dlhandle, "argsize")); + if (!argsize_sym) + goto load_error; + + argsize = *argsize_sym; + args = std::vector(argsize); + + arch_info_t *arch_sym; + arch_sym = + reinterpret_cast(dlsym(meta.dlhandle, "isa_arch_info")); + if (!arch_sym) + goto load_error; + return; + +load_error: + std::string err = std::string(dlerror()); + dlclose(meta.dlhandle); + throw std::runtime_error(err); +} + +Target::~Target() { + std::cout << "Destruct target " << meta.name << std::endl; + dlclose(meta.dlhandle); +} + +bool Target::is_on_breakpoint() const { return is_on_breakpoint(last_res); } + +bool Target::is_on_breakpoint(const gdb_action_t &res) const { + if (res.reason == gdb_action_t::ACT_BREAKPOINT || + res.reason == gdb_action_t::ACT_RWATCH || + res.reason == gdb_action_t::ACT_WATCH || + res.reason == gdb_action_t::ACT_WWATCH) { + return true; + } + return false; +} diff --git a/src/main.cpp b/src/main.cpp index 6752843..2379e9c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,23 @@ -#include +#include "api.hpp" +#include "config.hpp" +#include "difftest.hpp" -using reg_t = uint32_t; +int main(int argc, char **argv) { + Config config; + int ret = 0; + ret = config.cli_parse(argc, argv); + if (ret) + return ret; -int main() { return 0; } + std::vector refs; + Target dut = Target{"dut", "nemu_", config.dut}; + for (const auto &ref_libpath : config.refs) { + refs.emplace_back(ref_libpath.string(), "nemu_", ref_libpath); + } + + Difftest difftest{std::move(dut), std::move(refs)}; + difftest.setup(config.memory_file); + difftest.cont(); + + return 0; +}