fast method_name->source_file lookups, heuristics for finding ioctl names
authorJann Horn <jann@thejh.net>
Sun, 19 May 2013 18:15:01 +0000 (20:15 +0200)
committerJann Horn <jann@thejh.net>
Sun, 19 May 2013 18:18:16 +0000 (20:18 +0200)
.gitignore
Makefile
gen_method_list.c [new file with mode: 0644]
get_ioctl_names.sh [new file with mode: 0755]

index 7f3ee05..c0a67e5 100644 (file)
@@ -6,3 +6,4 @@ moctel_mod.mod.*
 modules.order
 *.o
 Module.symvers
 modules.order
 *.o
 Module.symvers
+gen_method_list
index 5516404..ea0909a 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -2,11 +2,14 @@ obj-m := moctel_mod.o
 KDIR := /lib/modules/$(shell uname -r)/build
 PWD := $(shell pwd)
 
 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
 
        $(MAKE) -C $(KDIR) M=$(PWD) modules
 
-show_ioctl:
+show_ioctl: show_ioctl.c
        gcc -o show_ioctl show_ioctl.c -std=gnu99
        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
  
 clean:
        $(MAKE) -C $(KDIR) M=$(PWD) clean
diff --git a/gen_method_list.c b/gen_method_list.c
new file mode 100644 (file)
index 0000000..94572f8
--- /dev/null
@@ -0,0 +1,177 @@
+#define _GNU_SOURCE
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <dirent.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+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 <kernel_src> <outpath>", 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 (executable)
index 0000000..7fe9fff
--- /dev/null
@@ -0,0 +1,55 @@
+#!/bin/sh
+
+# Invocation: ./get_macro_value.sh <kernel_src> <method_list_file> <method_symbol>
+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' ' ')"
+