From 73d712f1a25728984f7f9b81d7c3d02694648b4f Mon Sep 17 00:00:00 2001 From: Jann Horn Date: Sun, 19 May 2013 20:15:01 +0200 Subject: [PATCH 1/1] fast method_name->source_file lookups, heuristics for finding ioctl names --- .gitignore | 1 + Makefile | 7 +- gen_method_list.c | 177 +++++++++++++++++++++++++++++++++++++++++++++ get_ioctl_names.sh | 55 ++++++++++++++ 4 files changed, 238 insertions(+), 2 deletions(-) create mode 100644 gen_method_list.c create mode 100755 get_ioctl_names.sh diff --git a/.gitignore b/.gitignore index 7f3ee05..c0a67e5 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ moctel_mod.mod.* modules.order *.o Module.symvers +gen_method_list diff --git a/Makefile b/Makefile index 5516404..ea0909a 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,14 @@ obj-m := moctel_mod.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) -all: show_ioctl +all: show_ioctl gen_method_list $(MAKE) -C $(KDIR) M=$(PWD) modules -show_ioctl: +show_ioctl: show_ioctl.c gcc -o show_ioctl show_ioctl.c -std=gnu99 + +gen_method_list: gen_method_list.c + gcc -o gen_method_list gen_method_list.c -std=gnu99 clean: $(MAKE) -C $(KDIR) M=$(PWD) clean diff --git a/gen_method_list.c b/gen_method_list.c new file mode 100644 index 0000000..94572f8 --- /dev/null +++ b/gen_method_list.c @@ -0,0 +1,177 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static int out_fd; +char outbuf[10000]; +int outbuf_i = 0; + +void clear_out(void) { + if (write(out_fd, outbuf, outbuf_i) != outbuf_i) { + fprintf(stderr, "write to outfile did not succeed\n"); + exit(1); + } + outbuf_i = 0; +} + +void write_out(char *buf) { + size_t len = strlen(buf); + assert(len <= 500); + memcpy(outbuf+outbuf_i, buf, len); + outbuf_i += len; + if (outbuf_i > 9500) clear_out(); +} + +/* Maps a textfile into RAM. Returns a pointer to a read-only memory + area containing the 0-terminated data. len includes the nullbyte. + Free it using munmap. */ +char *map_textfile(char *path, size_t *len) { + int fd = open(path, O_RDONLY); + if (fd == -1) fprintf(stderr, "can't open %s: %m\n", path), exit(1); + + struct stat st; + if (fstat(fd, &st)) fprintf(stderr, "can't stat %s: %m\n", path), exit(1); + *len = st.st_size; + + char *data = mmap(NULL, *len+1, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); + if (data == MAP_FAILED) fprintf(stderr, "can't mmap %s: %m\n", path), exit(1); + + close(fd); + + if ((*len & 4095) == 0) { + // TODO: is this ok? does the previous mmap make sure that that address exists? + void *nulls_addr = mmap(data+*len, 1, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0); + if (nulls_addr != data+*len) + fprintf(stderr, "the mmap trick for 0-termination failed :(\n"), exit(1); + *len += 1; + } + + return data; +} + +bool isichar(char c) { + return (c>='a'&&c<='z') || (c>='A'&&c<='Z') || (c>='0'&&c<='9') || c=='_'; +} + +void handle_file(char *name, char *path) { + size_t data_len; + char *data = map_textfile(name, &data_len); + + bool wrote_path = false; + + /* look for stuff that looks like a method definition. this means: + * - line starts with a non-whitespace + * - there is a token (the name) followed by an opening paren + * - the next { is earlier than the next ; + */ + char *s = data; + while (1) { + char *lineend = strchr(s, '\n'); + if (*s == ' ' || *s == '\t' || *s == '\n' || *s == '\0') goto next; + if (lineend) *lineend = '\0'; + char *paren = strchr(s, '('); + if (paren == NULL) goto next; + *lineend = '\n'; + char *next_curly = strchr(paren, '{'); + if (next_curly == NULL) goto next; + char *next_semicolon = strchr(paren, ';'); + if (next_semicolon != NULL && next_semicolon < next_curly) + goto next; /* declaration, not definition */ + char *nameend = paren; + while (nameend>s+1 && !isichar(nameend[-1])) nameend--; /* let nameend point behind the name */ + *nameend = '\0'; + char *name = nameend-1; + while (name>s && isichar(name[-1])) name--; + if (!wrote_path) { + write_out(path); + write_out(":"); + wrote_path = true; + } else { + write_out(","); + } + write_out(name); + +next: + if (!lineend) break; + s = lineend+1; + } + + munmap(data, data_len); + write_out("\n"); +} + +// Recursive code dir traversal function +void run(char *path) { + DIR *dir = opendir("."); + if (dir == NULL) perror("can't open ."), exit(1); + + struct dirent dent_buf; + struct dirent *cur_ent = NULL; + while (1) { + readdir_r(dir, &dent_buf, &cur_ent); + if (cur_ent == NULL) break; + if (cur_ent->d_name[0] == '.') continue; /* ., .. or hidden */ + + if (cur_ent->d_type == DT_UNKNOWN) { + fprintf(stderr, "your fs returned DT_UNKNOWN in readdir. it's allowed, but dumb." + "\nhandling that case would bloat the code, so... fix it if you need it.\n"); + exit(1); + } + + if (cur_ent->d_type == DT_DIR) { + if (strcmp(cur_ent->d_name, "debian") == 0) continue; + if (chdir(cur_ent->d_name)) + fprintf(stderr, "can't chdir into %s: %m\n", cur_ent->d_name), exit(1); + char *subpath = NULL; + asprintf(&subpath, "%s%s/", path, cur_ent->d_name); + if (subpath == NULL) + fprintf(stderr, "can't create subpath\n"), exit(1); + run(subpath); + free(subpath); + if (fchdir(dirfd(dir))) perror("can't chdir back"), exit(1); + } + + if (cur_ent->d_type == DT_REG) { + char *dot = strrchr(cur_ent->d_name, '.'); + if (dot == NULL) continue; + if (dot[1] != 'c' || dot[2] != '\0') continue; + // it's a C source file + char *subpath = NULL; + asprintf(&subpath, "%s%s", path, cur_ent->d_name); + if (subpath == NULL) + fprintf(stderr, "can't create subpath\n"), exit(1); + handle_file(cur_ent->d_name, subpath); + free(subpath); + } + } + + closedir(dir); +} + +int main(int argc, char **argv) { + if (argc != 3) { + fputs("bad invocation: want ./gen_method_list ", stderr); + exit(1); + } + char *kernel_src_path = argv[1]; + char *out_path = argv[2]; + + out_fd = open(out_path, O_WRONLY|O_CREAT|O_EXCL, 0755); + if (out_fd == -1) fprintf(stderr, "can't create outfile at %s: %m\n", out_path), exit(1); + + if (chdir(kernel_src_path)) + fprintf(stderr, "can't access kernel sources at %s: %m\n", kernel_src_path), exit(1); + + run(""); + clear_out(); +} diff --git a/get_ioctl_names.sh b/get_ioctl_names.sh new file mode 100755 index 0000000..7fe9fff --- /dev/null +++ b/get_ioctl_names.sh @@ -0,0 +1,55 @@ +#!/bin/sh + +# Invocation: ./get_macro_value.sh +kernel_src="$1" +method_list_file="$2" +method_symbol="$3" + +if [ $# -ne 3 ]; then + echo "bad invocation" + exit 1 +fi + +if [ ! -d "$kernel_src" ]; then + echo "error: $kernel_src is not a directory" + exit 1 +fi + +# first search for the file containing the symbol +symbol_file="$(grep "[,:]$method_symbol\(,\|$\)" "$method_list_file" | cut -d':' -f1)" +if [ -z "$symbol_file" ]; then + echo "error: can't find method in method list file" + exit 1 +fi +echo "$symbol_file" +if [ "$(echo "$symbol_file"|wc -l)" -gt 1 ]; then + echo "error: too many hits" + exit 1 +fi +cd "$kernel_src" +grep_res="$(grep -R -n "\(^\| \)$method_symbol *\($\|(\)" "$symbol_file")" +if [ -z "$grep_res" ]; then + echo "error: can't find method in source" + exit 1 +fi +echo "$grep_res" +if [ "$(echo "$grep_res"|wc -l)" -gt 1 ]; then + echo "error: too many results" +fi +symbol_line="$(echo "$grep_res" | cut -d':' -f1)" +echo "symbol is in $symbol_file, line $symbol_line" + +# now isolate the method's code +partial_symbol_file="$(cat "$symbol_file" | tail -n "+$symbol_line")" +symbol_lines="$(echo "$partial_symbol_file" | grep -n '^}' | head -n 1 | cut -d':' -f1)" +echo "symbol is $symbol_lines lines long" +symbol_code="$(echo "$partial_symbol_file" | head -n "$symbol_lines")" +echo "dumping symbol code:" +echo "" +echo "$(echo "$symbol_code" | sed 's|^| |g')" +echo "" + +# grep for case statements +case_exprs="$(echo "$symbol_code" | grep 'case *[a-zA-Z0-9_]*:' | sed 's|.*case *\([a-zA-Z0-9_]*\):.*|\1|g')" +echo "ioctls found: $(echo "$case_exprs" | tr '\n' ' ')" + -- 2.20.1