~kdsch/uc

fd699d7c555ff2d279038ec4c82ccdcdac3b04e8 — Karl Schultheisz 2 years ago bc97631 master
improve I/O logic and tests

Test a larger corpus by using a test program with more complex stack
behavior.

Accept one shell command per line of input. The shell splits up command
lines for us. This requires each line to begin with `exec`, which is
less inconvenient than lexing argument strings ourselves.

Ease visual alignment of the output display by adding a dot grid.
9 files changed, 61 insertions(+), 128 deletions(-)

M Makefile
M README.md
M node.c
M node.h
M test/args.txt
M test/crasher.c
M tracer.c
M tracer.h
M uc.c
M Makefile => Makefile +2 -1
@@ 3,7 3,8 @@ LDLIBS = -lunwind-ptrace -lunwind-generic -llzma -lunwind
LDFLAGS = -static

check: uc test/crasher
	valgrind -q ./$< <test/args.txt
	./$< <test/args.txt
	@#valgrind -q ./$< <test/args.txt

uc: line.o node.o tracer.o


M README.md => README.md +20 -14
@@ 6,25 6,31 @@ redundant. uc pares them down.
Example usage, in the case of an AFL++ parallel run:

	for f in findings/*/crashes/id*; do
		printf '%s %s\n' "$TARGET" "$f"
		printf 'exec %s %s\n' "$TARGET" "$f"
	done | ./uc

Output:

```
<location such as "ip=0x435233 offset=0x4 sym=fscanf" or "src/parse.c:2453">
	findings/0/crashes/id...
	findings/2/crashes/id...
	findings/0/crashes/id...
	findings/0/crashes/id...
	findings/3/crashes/id...

<location of another crash>
	findings/0/crashes/id...
	findings/0/crashes/id...
	findings/1/crashes/id...

<...>
root
.  gsignal() 0x40f35b 0xcb
.  .  halve() 0x401d07 0x22
.  .  .  collatz() 0x401d7a 0x44
.  .  .  .  main() 0x401dea 0x6e
.  .  .  .  .  "exec ./test/crasher 8 2" (Floating point exception)
.  .  .  .  .  "exec ./test/crasher 6 2" (Aborted)
.  .  .  .  halve() 0x401d07 0x22
.  .  .  .  .  collatz() 0x401d7a 0x44
.  .  .  .  .  .  main() 0x401dea 0x6e
.  .  .  .  .  .  .  "exec ./test/crasher 4 4" (Illegal instruction)
.  .  .  .  .  .  halve() 0x401d07 0x22
.  .  .  .  .  .  .  collatz() 0x401d7a 0x44
.  .  .  .  .  .  .  .  augment() 0x401d34 0x2b
.  .  .  .  .  .  .  .  .  collatz() 0x401d6e 0x38
.  .  .  .  .  .  .  .  .  .  augment() 0x401d34 0x2b
.  .  .  .  .  .  .  .  .  .  .  collatz() 0x401d6e 0x38
.  .  .  .  .  .  .  .  .  .  .  .  main() 0x401dea 0x6e
.  .  .  .  .  .  .  .  .  .  .  .  .  "exec ./test/crasher 11 3" (Segmentation fault)
```

## Status

M node.c => node.c +2 -12
@@ 178,7 178,7 @@ static void
indent(unsigned level)
{
	for (unsigned i = 0; i < level; i++) {
		printf("  ");
		printf(".  ");
	}
}



@@ 200,23 200,13 @@ node_dump_indent(const struct node *n, unsigned level)
		printf("%s() 0x%lx 0x%lx", n->frame.symbol, n->frame.ip, n->frame.offset);
		break;
	case NODE_CRASH:
		printf("%s {", strsignal(n->crash.signal));
		for (size_t i = 0; n->crash.argv[i]; i++) {
			if (i) {
				printf(", ");
			}
			printf("\"%s\"", n->crash.argv[i]);
		}
		printf("}");
		printf("\"%s\" (%s)", n->crash.cmd, strsignal(n->crash.signal));
		break;
	}

	printf("\n");
	if (n->type != NODE_CRASH) {
		for (size_t i = 0; i < n->callers.len; i++) {
			if (i) {
				printf("\n");
			}
			node_dump_indent(n->callers.nodes[i], level + 1);
		}
	}

M node.h => node.h +1 -1
@@ 11,7 11,7 @@ struct node_array {
};

struct crash {
	const char **argv;
	const char *cmd;
	int signal;
};


M test/args.txt => test/args.txt +4 -5
@@ 1,5 1,4 @@
segv
fpe
fpe2
ill
abrt
exec ./test/crasher 8 2
exec ./test/crasher 6 2
exec ./test/crasher 4 4
exec ./test/crasher 11 3

M test/crasher.c => test/crasher.c +21 -73
@@ 1,96 1,44 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <signal.h>

#define len(x) (sizeof(x)/sizeof(x[0]))
int collatz(int n);

static char *s;
static const int zero;
int sig = 0;

static void
sigill(int n)
int
halve(int n)
{
	if (!n) {
		union {
			int n;
			void (*fn)(int n);
		} u = {.fn = sigill};

		u.n += 2;
		u.fn(0); // achievement unlocked!
	} else {
		sigill(n-1);
	}
	return collatz(n / 2);
}

static void
segfault(int n)
int
augment(int n)
{
	if (!n) {
		printf("%c\n", *s);
	} else {
		segfault(n-1);
	}
	return collatz((3 * n + 1)/2);
}


static void
sigfpe(int n)
int
collatz(int n)
{
	if (!n) {
		printf("%d\n", 1/zero);
	} else {
		sigfpe(n-1);
	if (n == 1) {
		raise(sig);
	} else if (n % 2) {
		return augment(n);
	}
}

static void
sigfpe2(int n)
{
	sigfpe(1);
	(void)n;
}

static void
sigabrt(int n)
{
	if (!n) {
		abort();
	} else {
		sigabrt(n-1);
	}
	return halve(n);
}

int
main(int argc, const char *argv[])
{
	if (argc < 2) {
	if (argc < 3) {
		printf("usage: %s signal seed\n", argv[0]);
		return 1;
	}

	struct {
		const char *type;
		void (*doit)(int n);
	} crash, crashes[] = {
		{"segv", segfault},
		{"fpe", sigfpe},
		{"fpe2", sigfpe2},
		{"ill", sigill},
		{"abrt", sigabrt},
	};

	int n = 0;
	if (argv[2]) {
		n = atoi(argv[2]);
	}

	for (size_t i = 0; i < len(crashes); i++) {
		crash = crashes[i];

		if (strcmp(crash.type, argv[1]) == 0) {
			crash.doit(n);
		}
	}

	sig = atoi(argv[1]);
	collatz(atoi(argv[2]));
	return 2;
}

M tracer.c => tracer.c +6 -6
@@ 41,7 41,7 @@ waitcrash(pid_t child, int *signal)
}

_Noreturn void
childhood(const char **argv)
childhood(const char *cmd)
{
	errno = 0;
	ptrace(PTRACE_TRACEME, 0, NULL, NULL);


@@ 52,7 52,7 @@ childhood(const char **argv)
		assert(0);
	}

	execv(argv[0], (char *const *)argv);
	execl("/bin/sh", "sh", "-c", cmd, NULL);

	if (errno) {
		printf("DEBUG (%s:%d) execve %s\n", __FILE__, __LINE__, strerror(errno));


@@ 62,9 62,9 @@ childhood(const char **argv)
}

enum tracer_status
tracer_init(struct tracer *t, const char **argv)
tracer_init(struct tracer *t, const char *cmd)
{
	t->argv = argv;
	t->cmd = cmd;
	t->as = unw_create_addr_space(&_UPT_accessors, 0);

	if (!t->as) {


@@ 79,7 79,7 @@ tracer_init(struct tracer *t, const char **argv)
	}

	if (!t->child) {
		childhood(argv);
		childhood(cmd);
	}

	int wstatus = 0;


@@ 91,6 91,7 @@ tracer_init(struct tracer *t, const char **argv)
		return TRACER_ERR;
	}

wait:
	errno = 0;
	ptrace(PTRACE_CONT, t->child, SIGCONT, NULL);



@@ 99,7 100,6 @@ tracer_init(struct tracer *t, const char **argv)
		return TRACER_ERR;
	}

wait:
	switch (waitcrash(t->child, &t->signal)) {
	case WAIT_BORING:
		goto wait;

M tracer.h => tracer.h +2 -2
@@ 8,7 8,7 @@
#include <sys/types.h>

struct tracer {
	const char **argv;
	const char *cmd;
	unw_addr_space_t as;
	pid_t child;
	int signal;


@@ 21,7 21,7 @@ enum tracer_status {
	TRACER_ERR,
};

enum tracer_status tracer_init(struct tracer *t, const char **argv);
enum tracer_status tracer_init(struct tracer *t, const char *cmd);
enum trace_msg tracer_step(void *data, struct frame *f);
void tracer_finish(struct tracer *t);


M uc.c => uc.c +3 -14
@@ 5,32 5,21 @@
#include "node.h"
#include "tracer.h"

const char **
argv_new(const char *arg1)
{
	const char **argv = malloc(3 * sizeof(const char *));
	argv[0] = "./test/crasher";
	argv[1] = arg1;
	argv[2] = NULL;
	return argv;
}

int
main(void)
{
	struct node *root = node_new_root();

	for (char *arg; line_next(stdin, &arg);) {
	for (char *cmd; line_next(stdin, &cmd);) {
		struct tracer t;
		const char **argv = argv_new(arg);
		enum tracer_status s = tracer_init(&t, argv);
		enum tracer_status s = tracer_init(&t, cmd);

		if (s != TRACER_OK) {
			continue;
		}

		struct trace_iter i = {.data = &t, .next = tracer_step};
		struct crash c = {.argv = argv, .signal = t.signal};
		struct crash c = {.cmd = cmd, .signal = t.signal};
		node_add_trace(root, &i, &c);
		tracer_finish(&t);
	}