Elementary support for -x

- this is applied across libraries.  Syntax for the -x option is the same
  as for -e
- this fails on symbol aliases.  E.g. there are several symbols named like
  strlen in libc: strlen, __GI_strlen, and perhaps others.  This needs to
  be taken into account, and symbol aliases shouldn't be enabled if
  they were added under a different name already
diff --git a/ltrace-elf.c b/ltrace-elf.c
index 5d01581..69f3075 100644
--- a/ltrace-elf.c
+++ b/ltrace-elf.c
@@ -297,7 +297,7 @@
 			lte->symtab_count = shdr.sh_size / shdr.sh_entsize;
 			if ((lte->symtab == NULL
 			     || elf_getdata(scn, lte->symtab) != NULL)
-			    && opt_x != NULL)
+			    && options.static_filter != NULL)
 				error(EXIT_FAILURE, 0,
 				      "Couldn't get .symtab data from \"%s\"",
 				      filename);
@@ -507,11 +507,74 @@
 	return 0;
 }
 
+static int
+populate_this_symtab(struct Process *proc, const char *filename,
+		     struct ltelf *lte, struct library *lib,
+		     Elf_Data *symtab, const char *strtab, size_t size)
+{
+	size_t lib_len = strlen(lib->soname);
+	size_t i;
+	for (i = 0; i < size; ++i) {
+		GElf_Sym sym;
+		if (gelf_getsym(lte->symtab, i, &sym) == NULL) {
+		fail:
+			error(0, errno, "couldn't get symbol #%zd from %s: %s",
+			      i, filename, elf_errmsg(-1));
+			continue;
+		}
+
+		if (sym.st_value == 0)
+			continue;
+
+		const char *name = strtab + sym.st_name;
+		if (!filter_matches_symbol(options.static_filter, name, lib))
+			continue;
+		fprintf(stderr, "%s@%s matches\n", name, lib->soname);
+
+		char *full_name = malloc(strlen(name) + 1 + lib_len + 1);
+		if (full_name == NULL)
+			goto fail;
+		sprintf(full_name, "%s@%s", name, lib->soname);
+
+		target_address_t addr
+			= (target_address_t)(sym.st_value + lte->bias);
+		target_address_t naddr;
+		if (arch_translate_address(proc, addr, &naddr) < 0) {
+			error(0, errno, "couldn't translate address of %s@%s",
+			      name, lib->soname);
+			continue;
+		}
+		if (addr != naddr)
+			naddr += lte->bias;
+
+		struct library_symbol *libsym = malloc(sizeof(*libsym));
+		if (libsym == NULL)
+			goto fail;
+
+		library_symbol_init(libsym, naddr, full_name, 1, LS_TOPLT_NONE);
+		library_add_symbol(lib, libsym);
+	}
+	return 0;
+}
+
+static int
+populate_symtab(struct Process *proc, const char *filename,
+		struct ltelf *lte, struct library *lib)
+{
+	if (lte->symtab != NULL && lte->strtab != NULL)
+		return populate_this_symtab(proc, filename, lte, lib,
+					    lte->symtab, lte->strtab,
+					    lte->symtab_count);
+	else
+		return populate_this_symtab(proc, filename, lte, lib,
+					    lte->dynsym, lte->dynstr,
+					    lte->dynsym_count);
+}
+
 int
 ltelf_read_library(struct library *lib, struct Process *proc,
 		   const char *filename, GElf_Addr bias)
 {
-	 // XXX we leak LTE contents
 	struct ltelf lte = {};
 	if (do_init_elf(&lte, filename, bias) < 0)
 		return -1;
@@ -553,6 +616,10 @@
 	    && populate_plt(proc, filename, &lte, lib) < 0)
 		goto fail;
 
+	if (filter_matches_library(options.static_filter, lib)
+	    && populate_symtab(proc, filename, &lte, lib) < 0)
+		goto fail;
+
 done:
 	do_close_elf(&lte);
 	return status;
diff --git a/options.c b/options.c
index d4e16a8..0e8d106 100644
--- a/options.c
+++ b/options.c
@@ -48,10 +48,6 @@
 /* List of pids given to option -p: */
 struct opt_p_t *opt_p = NULL;	/* attach to process with a given pid */
 
-/* List of global function names given to -x: */
-struct opt_x_t *opt_x = NULL;
-unsigned int opt_x_cnt = 0;
-
 /* List of filenames give to option -F: */
 struct opt_F_t *opt_F = NULL;	/* alternate configuration file(s) */
 
@@ -581,31 +577,8 @@
 			/* Fall Thru */
 
 		case 'x':
-			{
-				struct opt_x_t *p = opt_x;
-
-				/* First, check for duplicate. */
-				while (p && strcmp(p->name, optarg)) {
-					p = p->next;
-				}
-				if (p) {
-					break;
-				}
-
-				/* If not duplicate, add to list. */
-				p = malloc(sizeof(struct opt_x_t));
-				if (!p) {
-					perror("ltrace: malloc");
-					exit(1);
-				}
-				opt_x_cnt++;
-				p->name = optarg;
-				p->found = 0;
-				p->next = opt_x;
-				p->hash = ~(0UL);
-				opt_x = p;
-				break;
-			}
+			parse_filter_chain(optarg, &options.static_filter);
+			break;
 
 		default:
 			err_usage();
diff --git a/options.h b/options.h
index 82b13c8..651d424 100644
--- a/options.h
+++ b/options.h
@@ -22,6 +22,7 @@
 	int bt_depth;	 /* how may levels of stack frames to show */
 #endif /* defined(HAVE_LIBUNWIND) */
 	struct filter *plt_filter;
+	struct filter *static_filter;
 	int hide_caller; /* Whether caller library should be hidden.  */
 };
 extern struct options_t options;
@@ -41,18 +42,8 @@
 	struct opt_F_t *next;
 };
 
-struct opt_x_t {
-	char *name;
-	int found;
-	unsigned long hash;
-	struct opt_x_t *next;
-};
-
 extern struct opt_p_t *opt_p;	/* attach to process with a given pid */
 
 extern struct opt_F_t *opt_F;	/* alternate configuration file(s) */
 
-extern struct opt_x_t *opt_x;	/* list of functions to break at */
-extern unsigned int opt_x_cnt;
-
 extern char **process_options(int argc, char **argv);