diff --git a/.gitignore b/.gitignore
index 504decb..acb445f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,20 @@
*.bin
*.o
*.a
+*.so
+*.exe
+*.out
src/e9patch/e9loader_*.c
e9patch
e9tool
+test/regtest/bugs
+test/regtest/dl
+test/regtest/fini
+test/regtest/init
+test/regtest/inst
+test/regtest/patch
+test/regtest/test
+test/regtest/test.libc
+test/regtest/test.pie
+test/regtest/test_c
+test/regtest/test_c.debug
diff --git a/Makefile b/Makefile
index 060898e..36096b1 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: all clean install dev release debug sanitize
+.PHONY: all clean install check dev release debug sanitize check-debug
#########################################################################
# BUILD COMMON
@@ -66,6 +66,7 @@ clean:
$(MAKE) -C contrib/zydis clean
rm -rf $(E9PATCH_OBJS) $(E9TOOL_OBJS) e9patch e9tool \
src/e9patch/e9loader_*.c e9loader_*.o e9loader_*.bin
+ $(MAKE) -C test/regtest clean-check
src/e9patch/e9loader_elf.c: src/e9patch/e9loader_elf.cpp
$(CXX) -std=c++11 -Wall -fno-stack-protector -Wno-unused-function -fPIC \
@@ -82,6 +83,9 @@ src/e9patch/e9loader_pe.c: src/e9patch/e9loader_pe.cpp
src/e9patch/e9elf.o: src/e9patch/e9loader_elf.c
src/e9patch/e9pe.o: src/e9patch/e9loader_pe.c
+check: all
+ $(MAKE) -C test/regtest
+
install: all
install -d "$(DESTDIR)$(PREFIX)/bin"
install -m 755 e9patch "$(DESTDIR)$(PREFIX)/bin/e9patch"
@@ -164,3 +168,6 @@ debug: dev
sanitize: CXXFLAGS += -O0 -g -fsanitize=address
sanitize: dev
+
+check-debug: debug
+ $(MAKE) -C test/regtest
diff --git a/test/regtest/Makefile b/test/regtest/Makefile
index 4527282..34bc81b 100644
--- a/test/regtest/Makefile
+++ b/test/regtest/Makefile
@@ -1,36 +1,74 @@
+.PHONY: check clean-check
+
+E9TOOL ?= ../../e9tool
+E9COMPILE ?= ../../e9compile.sh
+
FCF_NONE := $(shell \
if gcc -fcf-protection=none --version 2>&1 | grep -q 'unrecognized'; \
then true; \
else echo -fcf-protection=none; fi)
-all:
- gcc -x assembler-with-cpp -o test test.s -no-pie -nostdlib \
+BASE ::= test test.pie bugs test.libc libtest.so test_c test_c.debug example.so
+TRAMPOLINE ::= inst patch dl init fini
+IN ::= $(wildcard *.in)
+EXE ::= $(IN:.in=.exe)
+
+check: regtest $(EXE)
+ ./$^
+
+%.exe: in=$(shell head -1 $<)
+%.exe: %.in $(BASE) $(TRAMPOLINE) $(E9TOOL)
+ $(E9TOOL) $(E9TOOL_OPTIONS) -M 'addr >= &"entry"' $(in)\
+ -E data..data_END -E data2...text -E .text..begin -o $@
+
+test: test.s
+ $(CC) -x assembler-with-cpp -o $@ $< -no-pie -nostdlib \
-Wl,--section-start=.text=0xa000000 -Wl,--section-start=.bss=0xc000000 \
-Wl,-z -Wl,max-page-size=4096 -DPIE=0
- gcc -x assembler-with-cpp -o test.pie test.s -pie -nostdlib \
+
+test.pie: test.s
+ $(CC) -x assembler-with-cpp -o $@ $< -pie -nostdlib \
-Wl,--section-start=.text=0xa000000 -Wl,--section-start=.bss=0xc000000 \
-Wl,-z -Wl,max-page-size=4096 -DPIE=1 \
-Wl,--export-dynamic
- gcc -x assembler-with-cpp -o bugs bugs.s -no-pie -nostdlib \
+
+bugs: bugs.s
+ $(CC) -x assembler-with-cpp -o $@ $< -no-pie -nostdlib \
-Wl,--section-start=.text=0xa000000 -Wl,--section-start=.bss=0xc000000 \
-Wl,-z -Wl,max-page-size=4096 -DPIE=0
- gcc -x assembler-with-cpp -o test.libc test_libc.s -pie -Wl,--export-dynamic
- gcc -x assembler-with-cpp -shared -o libtest.so libtest.s
- gcc -O2 -fPIC $(FCF_NONE) -pie -o test_c test_c.c \
+
+test.libc: test_libc.s
+ $(CC) -x assembler-with-cpp -pie $< -Wl,--export-dynamic -o $@
+
+libtest.so: libtest.s
+ $(CC) -x assembler-with-cpp $< -shared -o $@
+
+test_c: test_c.c
+ $(CC) -O2 -fPIC $(FCF_NONE) -pie -o $@ $< \
-Wl,--export-dynamic -U_FORTIFY_SOURCE
strip test_c
- gcc -O0 -g -fPIC -pie -o test_c.debug test_c.c
- ../../e9compile.sh inst.c -I ../../examples/
- ../../e9compile.sh patch.cpp -std=c++11 -I ../../examples/
- NO_SIMD_CHECK=1 ../../e9compile.sh dl.c -I ../../examples/
- ../../e9compile.sh init.c -I ../../examples/
- ../../e9compile.sh fini.c -I ../../examples/
- g++ -std=c++11 -fPIC -shared -o example.so -O2 \
- ../../examples/plugins/example.cpp -I ../../src/e9tool/
- g++ -std=c++11 -pie -fPIC -o regtest regtest.cpp -O2
- echo "XXX" > FILE.txt
- chmod 0640 FILE.txt
-
-clean:
- rm -f *.log *.out *.exe test test.pie test.libc libtest.so inst inst.o \
- patch patch.o init init.o regtest
+
+test_c.debug: test_c.c
+ $(CC) -O0 -g -fPIC -pie $< -o $@
+
+inst: inst.c ../../examples/stdlib.c $(E9COMPILE)
+ $(E9COMPILE) $< -I../../examples
+
+patch: patch.cpp ../../examples/stdlib.c $(E9COMPILE)
+ $(E9COMPILE) $< -std=c++11 -I../../examples
+
+dl: dl.c ../../examples/stdlib.c $(E9COMPILE)
+ NO_SIMD_CHECK=1 $(E9COMPILE) $< -I../../examples
+
+init: init.c ../../examples/stdlib.c $(E9COMPILE)
+ $(E9COMPILE) $< -I../../examples
+
+fini: fini.c ../../examples/stdlib.c $(E9COMPILE)
+ $(E9COMPILE) $< -I../../examples
+
+example.so: ../../examples/plugins/example.cpp ../../src/e9tool/e9plugin.h
+ $(CXX) -std=c++11 -O2 -fPIC -I../../src/e9tool $< -shared -o $@
+
+clean-check:
+ rm -f $(BASE) $(TRAMPOLINE) $(EXE)
+ rm -f *.out
diff --git a/test/regtest/README.md b/test/regtest/README.md
index be3f6dd..662c2af 100644
--- a/test/regtest/README.md
+++ b/test/regtest/README.md
@@ -3,6 +3,4 @@ README
To run the tests:
- $ make
- $ ./regtest
-
+ make E9TOOL_OPTIONS=
diff --git a/test/regtest/init_dso.cmd b/test/regtest/init_dso.cmd
old mode 100644
new mode 100755
index 94f6855..76ce3fd
--- a/test/regtest/init_dso.cmd
+++ b/test/regtest/init_dso.cmd
@@ -1 +1,2 @@
-LD_PRELOAD=$PWD/init_dso.exe ./test.pie
+#!/bin/sh
+LD_PRELOAD=./init_dso.exe ./test.pie
diff --git a/test/regtest/init_dso_2.cmd b/test/regtest/init_dso_2.cmd
old mode 100644
new mode 100755
index d2d5682..8dab098
--- a/test/regtest/init_dso_2.cmd
+++ b/test/regtest/init_dso_2.cmd
@@ -1 +1,2 @@
-LD_PRELOAD=$PWD/init_dso.exe ./test.pie a b c 1 2 3
+#!/bin/sh
+LD_PRELOAD=./init_dso.exe ./test.pie a b c 1 2 3
diff --git a/test/regtest/regtest b/test/regtest/regtest
new file mode 100755
index 0000000..15775cc
--- /dev/null
+++ b/test/regtest/regtest
@@ -0,0 +1,26 @@
+#!/bin/sh
+fails=()
+for exe in $*
+do
+ tst=${exe%.exe}
+ cmd=$tst.cmd
+ out=$tst.out
+ exp=$tst.exp
+
+ if test -f $cmd
+ then ./exec.sh ./$cmd 1>$out 2>&1
+ else ./exec.sh ./$exe 1>$out 2>&1
+ fi
+ sed -i 's/ (core dumped)$//' $out
+
+ diff -u $out $exp
+ if test $? -ne 0
+ then fails+=($tst)
+ fi
+done
+
+if test "$fails"
+then
+ echo "Failing ${#fails[@]}/$# tests: ${fails[@]}"
+ exit 1
+fi
diff --git a/test/regtest/regtest.cpp b/test/regtest/regtest.cpp
deleted file mode 100644
index efbf913..0000000
--- a/test/regtest/regtest.cpp
+++ /dev/null
@@ -1,270 +0,0 @@
-/*
- * Copyright (C) 2022 National University of Singapore
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-#include
-#include
-#include
-#include
-
-static bool option_tty = false;
-
-#define RED "\33[31m"
-#define GREEN "\33[32m"
-#define YELLOW "\33[33m"
-#define WHITE "\33[0m"
-
-#define error(msg, ...) \
- do \
- { \
- fprintf(stderr, "%serror%s: " msg "\n", \
- (option_tty? RED: ""), (option_tty? WHITE: ""), \
- ##__VA_ARGS__); \
- exit(EXIT_FAILURE); \
- } \
- while (false)
-
-/*
- * Run a single test case.
- */
-static bool runTest(const struct dirent *test, const std::string &options)
-{
- std::string in(test->d_name);
- std::string basename(in, 0, in.size()-3);
- std::string out(basename);
- out += ".out";
- std::string exp(basename);
- exp += ".exp";
- std::string exe(basename);
- exe += ".exe";
- std::string log(basename);
- log += ".log";
- std::string cmd(basename);
- cmd += ".cmd";
- std::string diff(basename);
- diff += ".diff";
-
- // Step (0): reset
- unlink(out.c_str());
- unlink(exe.c_str());
- unlink(log.c_str());
- unlink(diff.c_str());
-
- // Step (1): generate the EXE
- std::string command("../../e9tool ");
- if (options != "")
- {
- command += options;
- command += ' ';
- }
- command += "-M 'addr >= &\"entry\"' ";
- FILE *IN = fopen(in.c_str(), "r");
- if (IN == nullptr)
- error("failed to open file \"%s\": %s", in.c_str(), strerror(errno));
- char c;
- for (int i = 0; (c = getc(IN)) != '\n' && isprint(c) && i < 1024; i++)
- command += c;
- fclose(IN);
- command += " -E data..data_END -E data2...text -E .text..begin -o ";
- command += exe;
- command += " >>";
- command += log;
- command += " 2>&1";
-
- FILE *LOG = fopen(log.c_str(), "w");
- if (LOG != NULL)
- {
- fprintf(LOG, "%s\n", command.c_str());
- fclose(LOG);
- }
- printf("\n\t%s\n", command.c_str());
- int r = system(command.c_str());
- if (r != 0)
- {
- printf("%s%s%s: %sFAILED%s (patching failed with status %d, see %s)\n",
- (option_tty? YELLOW: ""), basename.c_str(), (option_tty? WHITE: ""),
- (option_tty? RED: ""), (option_tty? WHITE: ""),
- r, log.c_str());
- return false;
- }
-
- // Step (2): execute the EXE
- FILE *CMD = fopen(cmd.c_str(), "r");
- command.clear();
- command += "./exec.sh ";
- if (CMD != NULL)
- {
- for (int i = 0; (c = getc(CMD)) != '\n' && isprint(c) && i < 1024; i++)
- command += c;
- fclose(CMD);
- }
- else
- {
- command += "./";
- command += exe;
- }
- command += " >";
- command += out;
- command += " 2>&1";
- printf("\t%s\n", command.c_str());
- r = system(command.c_str());
- if (r != 0 && /*Ignore signals=*/
- !(WIFEXITED(r) && WEXITSTATUS(r) >= 128 && WEXITSTATUS(r) <= 128+32))
- {
- printf("%s%s%s: %sFAILED%s (execution failed with status %d, see %s)\n",
- (option_tty? YELLOW: ""), basename.c_str(), (option_tty? WHITE: ""),
- (option_tty? RED: ""), (option_tty? WHITE: ""),
- r, out.c_str());
- return false;
- }
- command.clear();
- command = "sed -i 's/ (core dumped)//g' ";
- command += out;
- system(command.c_str());
-
- // Step (3): compare the output
- FILE *OUT = fopen(out.c_str(), "r");
- if (OUT == nullptr)
- error("failed to open file \"%s\" for reading: %s", out.c_str(),
- strerror(errno));
- FILE *EXP = fopen(exp.c_str(), "r");
- if (EXP == nullptr)
- {
- if (errno == ENOENT)
- EXP = fopen("/dev/null", "r"); // Missing = empty file
- if (EXP == nullptr)
- error("failed to open file \"%s\" for reading: %s", exp.c_str(),
- strerror(errno));
- }
- const int LIMIT = 100000;
- for (int i = 0; i < LIMIT; i++)
- {
- char c = getc(OUT), d = getc(EXP);
- if (c != d)
- {
- fclose(OUT); fclose(EXP);
- command.clear();
- command += "diff ";
- command += out;
- command += ' ';
- command += exp;
- command += " >";
- command += diff;
- printf("\t%s\n", command.c_str());
- (void)system(command.c_str());
- printf("%s%s%s: %sFAILED%s (miscompare, see %s)\n",
- (option_tty? YELLOW: ""), basename.c_str(),
- (option_tty? WHITE: ""), (option_tty? RED: ""),
- (option_tty? WHITE: ""), diff.c_str());
- return false;
- }
- if (c == EOF)
- break;
- }
- fclose(OUT); fclose(EXP);
-
- // Success!
- printf("%s%s%s: %spassed%s\n",
- (option_tty? YELLOW: ""), basename.c_str(), (option_tty? WHITE: ""),
- (option_tty? GREEN: ""), (option_tty? WHITE: ""));
- return true;
-}
-
-/*
- * Test if directory entry is a test case (i.e., ends with ".in").
- */
-static int isTest(const struct dirent *entry)
-{
- size_t len = strlen(entry->d_name);
- if (len <= 3)
- return false;
- if (entry->d_name[len-1] != 'n' || entry->d_name[len-2] != 'i' ||
- entry->d_name[len-3] != '.')
- return false;
- return true;
-}
-
-/*
- * Entry.
- */
-int main(int argc, char **argv)
-{
- std::string options;
- for (int i = 1; i < argc; i++)
- {
- if (i > 1)
- options += ' ';
- options += argv[i];
- }
-
- option_tty = (isatty(STDOUT_FILENO) && isatty(STDERR_FILENO));
- struct dirent **tests = nullptr;
- int n = scandir(".", &tests, isTest, alphasort);
- if (n < 0)
- error("failed to scan current directory: %s", strerror(errno));
- size_t passed = 0, failed = 0, total = 0;
- std::vector fails;
- for (int i = 0; i < n; i++)
- {
- total++;
- if (runTest(tests[i], options))
- passed++;
- else
- {
- fails.push_back(tests[i]->d_name);
- failed++;
- }
- }
-
- const char *highlight = "", *off = "";
- if (option_tty)
- {
- if (passed == total)
- highlight = GREEN, off = WHITE;
- else if (passed == 0)
- highlight = RED, off = WHITE;
- else
- highlight = YELLOW, off = WHITE;
- }
- putchar('\n');
- printf("PASSED = %s%.2f%%%s (%zu/%zu); FAILED = %s%.2f%%%s (%zu/%zu)\n\n",
- highlight, (double)passed / (double)total * 100.0, off, passed, total,
- highlight, (double)failed / (double)total * 100.0, off, failed, total);
- if (fails.size() > 0)
- {
- printf("FAILED = {");
- bool prev = false;
- for (const auto &fail: fails)
- {
- if (prev)
- putchar(',');
- prev = true;
- printf("%s", fail.c_str());
- }
- printf("}\n\n");
- }
-
- return 0;
-}
-
diff --git a/test/regtest/stat.cmd b/test/regtest/stat.cmd
new file mode 100755
index 0000000..badd04f
--- /dev/null
+++ b/test/regtest/stat.cmd
@@ -0,0 +1,5 @@
+#!/bin/sh
+trap 'rm -f FILE.txt' EXIT HUP INT TERM
+echo XXX > FILE.txt
+chmod 0640 FILE.txt
+./stat.exe