diff --git a/tools/Makefile b/tools/Makefile
index 1f601b0..a20519b 100644
--- a/tools/Makefile
+++ b/tools/Makefile
@@ -1,6 +1,6 @@
.SUFFIXES:
-TARGETS := elf2rpl readrpl
+TARGETS := elf2rpl readrpl implcheck
ifeq ($(OS),Windows_NT)
all:
diff --git a/tools/implcheck/Makefile b/tools/implcheck/Makefile
new file mode 100644
index 0000000..73e2b27
--- /dev/null
+++ b/tools/implcheck/Makefile
@@ -0,0 +1,27 @@
+TARGET := implcheck
+SOURCE := .
+INCLUDE := ../common ../ext/cppformat ../ext/excmd/src
+
+CXX := g++
+
+CPPFORMATSRC := ../ext/cppformat/format.cc
+
+CFILES := $(foreach dir,$(SOURCE),$(wildcard $(dir)/*.cpp))
+INCLUDES := $(foreach dir,$(INCLUDE),-I$(dir))
+
+CFLAGS := -O2 -Wall --std=c++14
+LDFLAGS := -lz
+
+all: $(TARGET)
+
+clean:
+ @echo "[RM] $(notdir $(TARGET))"
+ @rm -rf $(TARGET)
+
+install: all
+ @echo Installing $(TARGET)
+ @cp $(TARGET) $(WUT_ROOT)/bin/$(TARGET)
+
+$(TARGET): $(CFILES)
+ @echo "[CXX] $(notdir $<)"
+ $(CXX) $(CFLAGS) $(INCLUDES) $< -o $@ $(LDFLAGS) $(CPPFORMATSRC)
diff --git a/tools/implcheck/implcheck.vcxproj b/tools/implcheck/implcheck.vcxproj
index da7e152..00e2368 100644
--- a/tools/implcheck/implcheck.vcxproj
+++ b/tools/implcheck/implcheck.vcxproj
@@ -76,6 +76,7 @@
true
$(SolutionDir)\bin\
+ $(SolutionDir)\common;$(SolutionDir)\ext\cppformat;$(SolutionDir)\ext\excmd\src;$(SolutionDir)\ext\zlib;$(IncludePath)
false
@@ -84,6 +85,7 @@
false
$(SolutionDir)\bin\
+ $(SolutionDir)\common;$(SolutionDir)\ext\cppformat;$(SolutionDir)\ext\excmd\src;$(SolutionDir)\ext\zlib;$(IncludePath)
@@ -146,6 +148,14 @@
+
+
+
+
+
+
+
+
diff --git a/tools/implcheck/implcheck.vcxproj.filters b/tools/implcheck/implcheck.vcxproj.filters
index 0d8d9e4..05eb364 100644
--- a/tools/implcheck/implcheck.vcxproj.filters
+++ b/tools/implcheck/implcheck.vcxproj.filters
@@ -13,10 +13,37 @@
{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+ {d98b1134-aad7-462b-9790-a12b0f037971}
+
Source Files
+
+ Source Files\ext
+
+
+ Source Files\ext
+
+
+ Source Files\ext
+
+
+ Source Files\ext
+
+
+ Source Files\ext
+
+
+ Source Files\ext
+
+
+ Source Files\ext
+
+
+ Source Files\ext
+
\ No newline at end of file
diff --git a/tools/implcheck/main.cpp b/tools/implcheck/main.cpp
index aefd7f4..a7fb397 100644
--- a/tools/implcheck/main.cpp
+++ b/tools/implcheck/main.cpp
@@ -1,100 +1,339 @@
+#include
+#include
+#include
#include
-#include
-#include
#include
+#include
+#include
+#include
+#include "elf.h"
-int main(int argc, char **argv)
+struct Section
{
- std::set funcExports, dataExports;
- std::string line;
- std::ifstream in;
- std::ofstream out;
+ elf::SectionHeader header;
+ std::vector data;
+};
- if (argc < 4) {
- std::cout << argv[0] << " " << std::endl;
- return 0;
+struct RplInfo
+{
+ std::set funcExports;
+ std::set dataExports;
+};
+
+struct ExportsInfo
+{
+ std::set funcExports;
+ std::set dataExports;
+};
+
+static bool
+readSection(std::ifstream &fh,
+ elf::SectionHeader &header,
+ std::vector &data)
+{
+ // Read section header
+ fh.read(reinterpret_cast(&header), sizeof(elf::SectionHeader));
+
+ if (header.type == elf::SHT_NOBITS || !header.size) {
+ return true;
}
- in.open(argv[1]);
+ // Read section data
+ if (header.flags & elf::SHF_DEFLATED) {
+ auto stream = z_stream {};
+ auto ret = Z_OK;
- if (!in.is_open()) {
- std::cout << "Could not open file " << argv[1] << " for reading" << std::endl;
- return -1;
- }
+ // Read the original size
+ fh.seekg(header.offset.value());
+ be_val size = 0;
+ fh.read(reinterpret_cast(&size), sizeof(uint32_t));
+ data.resize(size);
+ // Inflate
+ memset(&stream, 0, sizeof(stream));
+ stream.zalloc = Z_NULL;
+ stream.zfree = Z_NULL;
+ stream.opaque = Z_NULL;
- while (!in.eof()) {
- std::string strAddr, name;
- in >> strAddr >> name;
+ ret = inflateInit(&stream);
- auto addr = std::stoul(strAddr, nullptr, 16);
-
- if (addr >= 0x10000000) {
- dataExports.insert(name);
+ if (ret != Z_OK) {
+ std::cout << "Couldn't decompress .rpx section because inflateInit returned " << ret << std::endl;
+ data.clear();
+ return false;
} else {
- funcExports.insert(name);
+ std::vector temp;
+ temp.resize(header.size - sizeof(uint32_t));
+ fh.read(temp.data(), temp.size());
+
+ stream.avail_in = header.size;
+ stream.next_in = reinterpret_cast(temp.data());
+ stream.avail_out = static_cast(data.size());
+ stream.next_out = reinterpret_cast(data.data());
+
+ ret = inflate(&stream, Z_FINISH);
+
+ if (ret != Z_OK && ret != Z_STREAM_END) {
+ std::cout << "Couldn't decompress .rpx section because inflate returned " << ret << std::endl;
+ data.clear();
+ return false;
+ }
+
+ inflateEnd(&stream);
+ }
+ } else {
+ data.resize(header.size);
+ fh.seekg(header.offset.value());
+ fh.read(data.data(), header.size);
+ }
+
+ return true;
+}
+
+static void
+readExports(RplInfo &info, const Section §ion)
+{
+ auto exports = reinterpret_cast(section.data.data());
+ auto strTab = section.data.data();
+
+ for (auto i = 0u; i < exports->count; ++i) {
+ // TLS exports have the high bit set in name for some unknown reason...
+ auto name = strTab + (exports->exports[i].name & 0x7FFFFFFF);
+ auto value = exports->exports[i].value;
+
+ if (value >= 0x10000000) {
+ info.dataExports.insert(name);
+ } else {
+ info.funcExports.insert(name);
+ }
+ }
+}
+
+static bool
+readRPL(RplInfo &info, std::ifstream &fh)
+{
+ elf::Header header;
+ fh.read(reinterpret_cast(&header), sizeof(elf::Header));
+
+ if (header.magic != elf::HeaderMagic) {
+ std::cout << "Invalid ELF magic header" << std::endl;
+ return false;
+ }
+
+ // Read sections
+ auto sections = std::vector {};
+
+ for (auto i = 0u; i < header.shnum; ++i) {
+ Section section;
+ fh.seekg(header.shoff + header.shentsize * i);
+
+ if (!readSection(fh, section.header, section.data)) {
+ std::cout << "Error reading section " << i << std::endl;
+ return false;
+ }
+
+ sections.push_back(section);
+ }
+
+ // Find strtab
+ auto shStrTab = reinterpret_cast(sections[header.shstrndx].data.data());
+
+ // Print section data
+ for (auto i = 0u; i < sections.size(); ++i) {
+ auto §ion = sections[i];
+
+ if (section.header.type == elf::SHT_RPL_EXPORTS) {
+ readExports(info, section);
}
}
- in.close();
- in.open(argv[2]);
+ return true;
+}
- if (!in.is_open()) {
- std::cout << "Could not open file " << argv[2] << " for reading" << std::endl;
- return -1;
- }
+static bool
+readTextExports(ExportsInfo &info, std::ifstream &fh)
+{
+ std::string line;
- auto funcCount = funcExports.size();
- auto dataCount = funcExports.size();
-
- while (std::getline(in, line)) {
+ while (std::getline(fh, line)) {
if (line.find("EXPORT(") == 0) {
auto name = line.substr(strlen("EXPORT("));
name = name.erase(name.find(')'), 2);
-
- auto funcItr = funcExports.find(name);
-
- if (funcItr != funcExports.end()) {
- funcExports.erase(funcItr);
- }
-
- auto dataItr = dataExports.find(name);
-
- if (dataItr != dataExports.end()) {
- dataExports.erase(funcItr);
- }
+ info.funcExports.insert(name);
}
}
- auto unimplFuncCount = funcExports.size();
- auto unimplDataCount = funcExports.size();
+ return true;
+}
- out.open(argv[3]);
+int main(int argc, char **argv)
+{
+ excmd::parser parser;
+ excmd::option_state options;
+ using excmd::description;
+ using excmd::value;
- if (!out.is_open()) {
- std::cout << "Could not open file " << argv[3] << " for writing" << std::endl;
+ try {
+ parser.global_options()
+ .add_option("H,help",
+ description { "Show help." })
+ .add_option("f,functions",
+ description { "Print unimplemented function exports." })
+ .add_option("d,data",
+ description { "Print unimplemented data exports." });
+
+ parser.default_command()
+ .add_argument("rpl-path",
+ description { "Path to RPL file" },
+ value {})
+ .add_argument("exports-path",
+ description { "Path to exports.h file" },
+ value {});
+
+ options = parser.parse(argc, argv);
+ } catch (excmd::exception ex) {
+ std::cout << "Error parsing options: " << ex.what() << std::endl;
return -1;
}
- if (funcExports.size()) {
- out << "Unimplemented function exports " << unimplFuncCount << "/" << funcCount << ":" << std::endl;
-
- for (auto &name : funcExports) {
- out << name << std::endl;
- }
-
- out << std::endl;
+ if (options.empty() || options.has("help") || !options.has("rpl-path") || !options.has("exports-path")) {
+ std::cout << argv[0] << " rpl-path exports-path" << std::endl;
+ std::cout << parser.format_help(argv[0]);
+ return 0;
}
- if (dataExports.size()) {
- out << "Unimplemented data exports " << unimplDataCount << "/" << dataCount << ":" << std::endl;
+ auto rplPath = options.get("rpl-path");
+ auto exportsPath = options.get("exports-path");
- for (auto &name : dataExports) {
- out << name << std::endl;
- }
+ // Read RPL
+ std::ifstream fh { rplPath, std::ifstream::binary };
- out << std::endl;
+ if (!fh.is_open()) {
+ std::cout << "Could not open " << rplPath << " for reading" << std::endl;
+ return -1;
}
- return 0;
+ RplInfo rplInfo;
+
+ if (!readRPL(rplInfo, fh)) {
+ std::cout << "Error reading " << rplPath << std::endl;
+ return -1;
+ }
+
+ fh.close();
+
+ // Read exports
+ fh.open(exportsPath, std::ifstream::in);
+
+ if (!fh.is_open()) {
+ std::cout << "Could not open " << exportsPath << " for reading" << std::endl;
+ return -1;
+ }
+
+ ExportsInfo exportsInfo;
+
+ if (!readTextExports(exportsInfo, fh)) {
+ std::cout << "Error reading " << exportsPath << std::endl;
+ return -1;
+ }
+
+ fh.close();
+
+ // Check that we do not have any extra exports which are not exported from RPL.
+ std::set funcUnion, extraExports;
+ int result = 0;
+
+ std::set_union(rplInfo.funcExports.begin(), rplInfo.funcExports.end(),
+ exportsInfo.funcExports.begin(), exportsInfo.funcExports.end(),
+ std::inserter(funcUnion, funcUnion.begin()));
+
+ std::set_difference(funcUnion.begin(), funcUnion.end(),
+ rplInfo.funcExports.begin(), rplInfo.funcExports.end(),
+ std::inserter(extraExports, extraExports.begin()));
+
+ if (!extraExports.empty()) {
+ std::cout << "Error: Exports found in exports.h which do not exist in rpl:" << std::endl;
+
+ for (auto &name : extraExports) {
+ std::cout << " - " << name << std::endl;
+ }
+
+ std::cout << std::endl;
+ result = -1;
+ }
+
+ if (options.has("functions")) {
+ // Print implemented function exports
+ std::set intersection;
+
+ std::set_intersection(rplInfo.funcExports.begin(), rplInfo.funcExports.end(),
+ exportsInfo.funcExports.begin(), exportsInfo.funcExports.end(),
+ std::inserter(intersection, intersection.begin()));
+
+ if (!intersection.empty()) {
+ std::cout << "Implemented function exports:" << std::endl;
+
+ for (auto &name : intersection) {
+ std::cout << " - " << name << std::endl;
+ }
+
+ std::cout << std::endl;
+ }
+
+ // Print unimplemented function exports
+ std::set difference;
+
+ std::set_difference(rplInfo.funcExports.begin(), rplInfo.funcExports.end(),
+ exportsInfo.funcExports.begin(), exportsInfo.funcExports.end(),
+ std::inserter(difference, difference.begin()));
+
+ if (!difference.empty()) {
+ std::cout << "Unimplemented function exports:" << std::endl;
+
+ for (auto &name : difference) {
+ std::cout << " - " << name << std::endl;
+ }
+
+ std::cout << std::endl;
+ }
+ }
+
+ if (options.has("data")) {
+ // Print implemented data exports
+ std::set intersection;
+
+ std::set_intersection(rplInfo.dataExports.begin(), rplInfo.dataExports.end(),
+ exportsInfo.dataExports.begin(), exportsInfo.dataExports.end(),
+ std::inserter(intersection, intersection.begin()));
+
+ if (!intersection.empty()) {
+ std::cout << "Implemented data exports:" << std::endl;
+
+ for (auto &name : intersection) {
+ std::cout << " - " << name << std::endl;
+ }
+
+ std::cout << std::endl;
+ }
+
+ // Print unimplemented data exports
+ std::set difference;
+
+ std::set_difference(rplInfo.dataExports.begin(), rplInfo.dataExports.end(),
+ exportsInfo.dataExports.begin(), exportsInfo.dataExports.end(),
+ std::inserter(difference, difference.begin()));
+
+ if (!difference.empty()) {
+ std::cout << "Unimplemented data exports:" << std::endl;
+
+ for (auto &name : difference) {
+ std::cout << " - " << name << std::endl;
+ }
+
+ std::cout << std::endl;
+ }
+ }
+
+ return result;
}
+