The userland companion of dt(4) is named btrace(8).  It understands an
awk(1) inspired syntax, a current subset of the bpftrace(8) language, to
enable probes and format informations recorded during the tracing.

The choice of the bpftrace(8) dialect and outputs has been made to be
able to ease the port of opensource applications written with Linux
profiling in mind.

The diff below contains regress, some of which need to be fixed and the
tool itself.  It is still a WIP but most of the features are already
there and the missing pieces can then be added step-by-step.

Ok to continue in tree?

diff --git regress/usr.sbin/btrace/Makefile regress/usr.sbin/btrace/Makefile
new file mode 100644
index 00000000000..2f3737f9120
--- /dev/null
+++ regress/usr.sbin/btrace/Makefile
@@ -0,0 +1,19 @@
+# $OpenBSD: Makefile$
+
+BTRACE?=                ${.CURDIR}/../../../usr.sbin/btrace/obj/btrace
+
+# scripts that don't need /dev/dt
+BT_LANG_SCRIPTS=       arithm beginend comments delete exit map maxoperand \
+                       multismts nsecs+var
+
+BT_KERN_SCRIPTS=
+
+${BT_LANG_SCRIPTS}:
+       ${BTRACE} ${.CURDIR}/${.TARGET}.bt 2>/dev/null | \
+               diff -u ${.CURDIR}/${.TARGET}.ok /dev/stdin
+
+REGRESS_TARGETS=${BT_LANG_SCRIPTS}
+.PHONY: ${REGRESS_TARGETS}
+
+.include <bsd.regress.mk>
+
diff --git regress/usr.sbin/btrace/arithm.bt regress/usr.sbin/btrace/arithm.bt
new file mode 100644
index 00000000000..552ce9082f0
--- /dev/null
+++ regress/usr.sbin/btrace/arithm.bt
@@ -0,0 +1,15 @@
+BEGIN
+{
+       @a = 10;
+       @b = 5;
+
+       printf("a + b = %d\n", @a + @b);
+}
+
+END
+{
+       printf("a - b = %d\n", @a - @b);
+
+       @c = @a + 2 * @b;
+       printf("c = %d, total = %d\n", @c, (@c - @b) / 5);
+}
diff --git regress/usr.sbin/btrace/arithm.ok regress/usr.sbin/btrace/arithm.ok
new file mode 100644
index 00000000000..9575561d2be
--- /dev/null
+++ regress/usr.sbin/btrace/arithm.ok
@@ -0,0 +1,3 @@
+a + b = 15
+a - b = 5
+c = 20, total = 3
diff --git regress/usr.sbin/btrace/beginend.bt 
regress/usr.sbin/btrace/beginend.bt
new file mode 100644
index 00000000000..a2469966995
--- /dev/null
+++ regress/usr.sbin/btrace/beginend.bt
@@ -0,0 +1,13 @@
+// Test BEGIN & END special rules as well as comments...
+// ...inside & outside action blocks
+BEGIN
+{
+
+       printf("Hello from BEGIN\n");
+}
+
+END
+{
+       // Comment inside of a block
+       printf("Bye from END\n");
+}
diff --git regress/usr.sbin/btrace/beginend.ok 
regress/usr.sbin/btrace/beginend.ok
new file mode 100644
index 00000000000..8d52c32c3bd
--- /dev/null
+++ regress/usr.sbin/btrace/beginend.ok
@@ -0,0 +1,2 @@
+Hello from BEGIN
+Bye from END
diff --git regress/usr.sbin/btrace/comments.bt 
regress/usr.sbin/btrace/comments.bt
new file mode 100644
index 00000000000..ab20560f673
--- /dev/null
+++ regress/usr.sbin/btrace/comments.bt
@@ -0,0 +1,10 @@
+/*
+ * Multi-lines comment.
+ */
+END
+// comment after probe
+{
+       printf("followed by a comment\n");
+       // comment that should be skipped (off-by one test)
+}
+/* Another comment */
diff --git regress/usr.sbin/btrace/comments.ok 
regress/usr.sbin/btrace/comments.ok
new file mode 100644
index 00000000000..99150de493b
--- /dev/null
+++ regress/usr.sbin/btrace/comments.ok
@@ -0,0 +1 @@
+followed by a comment
diff --git regress/usr.sbin/btrace/delete.bt regress/usr.sbin/btrace/delete.bt
new file mode 100644
index 00000000000..feffff2033c
--- /dev/null
+++ regress/usr.sbin/btrace/delete.bt
@@ -0,0 +1,12 @@
+BEGIN
+{
+       @map[3 + 1] = 9999;
+       print(@map);
+}
+
+END
+{
+       printf("=> Print after delete:\n");
+       delete(@map[4]);
+       print(@map);
+}
diff --git regress/usr.sbin/btrace/exit.bt regress/usr.sbin/btrace/exit.bt
new file mode 100644
index 00000000000..d7a980641bf
--- /dev/null
+++ regress/usr.sbin/btrace/exit.bt
@@ -0,0 +1,10 @@
+BEGIN
+{
+       printf("exiting now...\n");
+       exit();
+}
+
+END
+{
+       printf("not executed\n");
+}
diff --git regress/usr.sbin/btrace/exit.ok regress/usr.sbin/btrace/exit.ok
new file mode 100644
index 00000000000..9c13bebc7ef
--- /dev/null
+++ regress/usr.sbin/btrace/exit.ok
@@ -0,0 +1 @@
+exiting now...
diff --git regress/usr.sbin/btrace/map.bt regress/usr.sbin/btrace/map.bt
new file mode 100644
index 00000000000..2f7bd07bb38
--- /dev/null
+++ regress/usr.sbin/btrace/map.bt
@@ -0,0 +1,29 @@
+BEGIN
+{
+       @map[8 - 1] = count();
+       printf("=> Print with one element\n");
+       print(@map);
+
+       zero(@map);
+       printf("=> Print after zero:\n");
+       print(@map);
+
+       @map[7] = count();
+       @map[5] = 42;
+       @map[3 + 1] = 9999;
+       @map[897123] = 9997;
+       @top = 2;
+       printf("=> Print top %d:\n", @top);
+       print(@map, @top);
+}
+
+END
+{
+       @map[7] = count();
+       printf("=> Print all map:\n");
+       print(@map);
+
+       clear(@map);
+       printf("=> Print after clear:\n");
+       print(@map);
+}
diff --git regress/usr.sbin/btrace/map.ok regress/usr.sbin/btrace/map.ok
new file mode 100644
index 00000000000..35703d068ed
--- /dev/null
+++ regress/usr.sbin/btrace/map.ok
@@ -0,0 +1,13 @@
+=> Print with one element
+@map[7]: 1
+=> Print after zero:
+@map[7]: 0
+=> Print top 2:
+@map[4]: 9999
+@map[897123]: 9997
+=> Print all map:
+@map[4]: 9999
+@map[897123]: 9997
+@map[5]: 42
+@map[7]: 2
+=> Print after clear:
diff --git regress/usr.sbin/btrace/maxoperand.bt 
regress/usr.sbin/btrace/maxoperand.bt
new file mode 100644
index 00000000000..d1764b5e056
--- /dev/null
+++ regress/usr.sbin/btrace/maxoperand.bt
@@ -0,0 +1,9 @@
+/*
+ * Check the hard limit of operands
+ */
+BEGIN
+{
+       @ok = 1 + 2 * 3 - 4 + 5;
+       printf("That's ok: '%d'\n", @ok); 
+       @notok = 6 * 5 - 4 / 3 + 2 - 1;
+}
diff --git regress/usr.sbin/btrace/maxoperand.ok 
regress/usr.sbin/btrace/maxoperand.ok
new file mode 100644
index 00000000000..c1a7d5ea482
--- /dev/null
+++ regress/usr.sbin/btrace/maxoperand.ok
@@ -0,0 +1,2 @@
+That's ok: '8'
+btrace: too many operands (>5) in expression
diff --git regress/usr.sbin/btrace/multismts.bt 
regress/usr.sbin/btrace/multismts.bt
new file mode 100644
index 00000000000..74eaa498676
--- /dev/null
+++ regress/usr.sbin/btrace/multismts.bt
@@ -0,0 +1,5 @@
+// Test multiple statements inside a single block;
+BEGIN {
+       printf("One"); printf(", %d", "2");
+       printf(" %s three%c\n", "and", "!");
+}
diff --git regress/usr.sbin/btrace/multismts.ok 
regress/usr.sbin/btrace/multismts.ok
new file mode 100644
index 00000000000..a23b9aa48a1
--- /dev/null
+++ regress/usr.sbin/btrace/multismts.ok
@@ -0,0 +1 @@
+One, 2 and three!
diff --git regress/usr.sbin/btrace/nsecs+var.bt 
regress/usr.sbin/btrace/nsecs+var.bt
new file mode 100644
index 00000000000..54866e4efd4
--- /dev/null
+++ regress/usr.sbin/btrace/nsecs+var.bt
@@ -0,0 +1,11 @@
+// Test parsing nsecs in a BEGIN block
+BEGIN {
+       @start = nsecs;
+       @var = 82;
+}
+
+// Overwrite `start' and print everything
+END {
+       @start = 3;
+       printf("start=%u, var=%u\n", @start, @var);
+}
diff --git regress/usr.sbin/btrace/nsecs+var.ok 
regress/usr.sbin/btrace/nsecs+var.ok
new file mode 100644
index 00000000000..6d1656a0838
--- /dev/null
+++ regress/usr.sbin/btrace/nsecs+var.ok
@@ -0,0 +1 @@
+start=3, var=82
diff --git usr.sbin/btrace/Makefile usr.sbin/btrace/Makefile
new file mode 100644
index 00000000000..7c4acbd3f8a
--- /dev/null
+++ usr.sbin/btrace/Makefile
@@ -0,0 +1,21 @@
+# $OpenBSD$
+
+PROG=          btrace
+MAN=           bt.5 btrace.8
+
+SRCS=          bt_parse.y btrace.c ksyms.c map.c printf.c
+
+# Keep in sync with /sys/kern/syscalls.c
+SRCS+=         syscalls.c
+CFLAGS+=       -DPTRACE -DKTRACE -DACCOUNTING -DNFSCLIENT -DSYSVSHM -DSYSVSEM
+CFLAGS+=       -DSYSVMSG
+
+CFLAGS+=       -W -Wall -Wstrict-prototypes -Wno-unused -Wunused-variable
+CFLAGS+=       -I${.CURDIR}
+
+LDADD+=                -lelf
+DPADD+=                ${LIBELF}
+
+DEBUG?=                -g
+
+.include <bsd.prog.mk>
diff --git usr.sbin/btrace/bt.5 usr.sbin/btrace/bt.5
new file mode 100644
index 00000000000..bfdaafc7725
--- /dev/null
+++ usr.sbin/btrace/bt.5
@@ -0,0 +1,126 @@
+.\"    $OpenBSD$
+.\"
+.\" Copyright (c) 2019 Martin Pieuchot <m...@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: September 19 2019 $
+.Dt BT 5
+.Os
+.Sh NAME
+.Nm BT
+.Nd B Tracing language
+.Sh SYNTAX
+.D1 Ar probe Ic \&/ Ar filter Ic \&/ \&{ Ar action Ic \&}
+.Sh DESCRIPTION
+The
+.Nm
+language, also known as BPFtrace syntax, describes how to format and display
+information gathered from specified
+.Ar probe
+events.
+.Pp
+Events are generated by the dynamic tracer
+.Xr dt 4
+when an enabled
+.Ar probe
+is triggered.
+They are periodically collected by
+.Xr btrace 8
+which formats them using the corresponding
+.Ar action .
+If a recorded event doesn't match the optional
+.Ar filter
+it will be silently ignored.
+.Pp
+A valid
+.Nm
+source file contains at least one
+.Ar probe
+clause associated with an
+.Ar action
+statement.
+.Sh PROBE
+The list of available probes may vary from system to system and can be queried
+with
+.Xr btrace 8 .
+.Pp
+The special probes
+.Ic BEGIN
+and
+.Ic END
+may be used to manipulate states before the first event is recorded and after
+the last.
+They cannot be combined with any
+.Ar filter .
+.Sh FILTER
+Define under with condition an event should be recorded when its related
+.Ar probe
+is executed.
+An empty
+.Ar filter
+means record all events.
+.Pp
+Variable names available in filters:
+.Pp
+.Bl -tag -width "pid " -compact
+.It Va pid
+Process ID of the current thread
+.It Va tid
+Thread ID of the current thread
+.El
+.Sh ACTION
+An action is a sequence of statements that are evaluated for each event 
recorded
+by the associated
+.Ar probe .
+.Pp
+Variable names with special meaning:
+.Pp
+.Bl -tag -width "kstack " -compact
+.It Va pid
+Process ID of the current thread
+.It Va tid
+Thread ID of the current thread
+.It Va comm
+Command name of the current process
+.It Va nsecs
+Timestamp of the event in nanoseconds
+.It Va kstack
+Kernel stack of the current thread
+.It Va ustack
+Userland stack of the current thread
+.It Va argN
+Argument
+.Va N
+of the corresponding probe
+.El
+.Sh SEE ALSO
+.Xr awk 1 ,
+.Xr dt 4 ,
+.Xr btrace 8
+.Rs
+.\"%A First Last
+.%T BPFtrace reference guide
+.%U https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
+.\"%D November 1, 1901
+.Re
+.Sh STANDARDS
+The dialect
+of the
+.Nm
+language described in this manual and supported by
+.Xr btrace 8
+is compatible with BPFtrace.
+The syntax is similar to
+.Xr awk 1
+and dtrace.
diff --git usr.sbin/btrace/bt_parse.y usr.sbin/btrace/bt_parse.y
new file mode 100644
index 00000000000..88e04a86132
--- /dev/null
+++ usr.sbin/btrace/bt_parse.y
@@ -0,0 +1,755 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2019 - 2020 Martin Pieuchot <m...@openbsd.org>
+ * Copyright (c) 2019 Tobias Heider <to...@openbsd.org>
+ * Copyright (c) 2015 Ted Unangst <t...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * B tracing language parser.
+ *
+ * The dialect of the language understood by this parser aims to be
+ * compatible with the one understood bpftrace(8), see:
+ *
+ * https://github.com/iovisor/bpftrace/blob/master/docs/reference_guide.md
+ *
+ */
+
+%{
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <ctype.h>
+#include <err.h>
+#include <limits.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include "bt_parser.h"
+
+/* Number of rules to evaluate. */
+struct bt_ruleq                g_rules = TAILQ_HEAD_INITIALIZER(g_rules);
+
+/* Number of probes except BEGIN/END. */
+int                    g_nprobes;
+
+/* List of global variables. */
+SLIST_HEAD(, bt_var)    g_variables;
+
+struct bt_rule *br_new(struct bt_probe *, struct bt_filter *, struct bt_stmt *,
+                    enum bt_rtype);
+struct bt_filter *bf_new(enum bt_operand, enum bt_filtervar, int);
+struct bt_probe        *bp_new(const char *, const char *, const char *, 
int32_t);
+struct bt_arg  *ba_concat(struct bt_arg *, struct bt_arg *);
+struct bt_stmt *bs_new(enum bt_action, struct bt_arg *, struct bt_var *);
+struct bt_stmt *bs_concat(struct bt_stmt *, struct bt_stmt *);
+
+struct bt_var  *bv_find(const char *);
+struct bt_arg  *bv_get(const char *);
+struct bt_stmt *bv_set(const char *, struct bt_arg *);
+
+struct bt_smt  *bn_set(const char *, struct bt_arg *, struct bt_arg *);
+struct bt_stmt *bm_fn(enum bt_action, const char *, struct bt_arg *,
+                    struct bt_arg *);
+
+/*
+ * Lexer
+ */
+const char     *pbuf;
+size_t          plen;
+size_t          pindex;
+int             perrors = 0;
+
+typedef struct {
+       union {
+               long                     number;
+               int                      i;
+               const char              *string;
+               struct bt_probe         *probe;
+               struct bt_filter        *filter;
+               struct bt_stmt          *stmt;
+               struct bt_arg           *arg;
+               enum bt_rtype            rtype;
+       } v;
+       const char                      *filename;
+       int                              lineno;
+       int                              colno;
+} yystype;
+#define YYSTYPE yystype
+
+static void     yyerror(const char *, ...);
+static int      yylex(void);
+%}
+
+%token ERROR OP_EQ OP_NEQ BEGIN END
+/* Builtins */
+%token ARG0 ARG1 ARG2 ARG3 ARG4 ARG5 ARG6 ARG7 ARG8 ARG9
+%token COMM HZ KSTACK USTACK NSECS PID RETVAL TID
+/* Functions */
+%token  F_CLEAR F_DELETE F_EXIT F_PRINT F_PRINTF F_ZERO
+/* Map funcitons */
+%token  M_COUNT
+%token <v.string>      STRING CSTRING
+%token <v.number>      NUMBER
+%type  <v.string>      gvar
+%type  <v.i>           filterval oper builtin func0 func1 funcn mfunc1 mapfunc
+%type  <v.probe>       probe
+%type  <v.filter>      predicate
+%type  <v.stmt>        action stmt stmtlist
+%type  <v.arg>         arg arglist marg term
+%type  <v.rtype>       beginend
+
+%%
+
+grammar                : /* empty */
+               | grammar '\n'
+               | grammar rule
+               | grammar error
+               ;
+
+rule           : beginend action        { br_new(NULL, NULL, $2, $1); }
+               | probe predicate action { br_new($1, $2, $3, B_RT_PROBE); }
+               ;
+
+beginend       : BEGIN                         { $$ = B_RT_BEGIN; }
+               | END                           { $$ = B_RT_END; }
+               ;
+
+probe          : STRING ':' STRING ':' STRING  { $$ = bp_new($1, $3, $5, 0); }
+               | STRING ':' HZ ':' NUMBER      { $$ = bp_new($1, "hz", NULL, 
$5); }
+               ;
+
+
+filterval      : PID                           { $$ = B_FV_PID; }
+               | TID                           { $$ = B_FV_TID; }
+               ;
+
+oper           : OP_EQ                         { $$ = B_OP_EQ; }
+               | OP_NEQ                        { $$ = B_OP_NE; }
+               ;
+
+predicate      : /* empty */                   { $$ = NULL; }
+               | '/' filterval oper NUMBER '/' { $$ = bf_new($3, $2, $4); }
+               | '/' NUMBER oper filterval '/' { $$ = bf_new($3, $4, $2); }
+               ;
+
+builtin                : PID                           { $$ = B_AT_BI_PID; }
+               | TID                           { $$ = B_AT_BI_TID; }
+               | COMM                          { $$ = B_AT_BI_COMM; }
+               | NSECS                         { $$ = B_AT_BI_NSECS; }
+               | KSTACK                        { $$ = B_AT_BI_KSTACK; }
+               | USTACK                        { $$ = B_AT_BI_USTACK; }
+               | ARG0                          { $$ = B_AT_BI_ARG0; }
+               | ARG1                          { $$ = B_AT_BI_ARG1; }
+               | ARG2                          { $$ = B_AT_BI_ARG2; }
+               | ARG3                          { $$ = B_AT_BI_ARG3; }
+               | ARG4                          { $$ = B_AT_BI_ARG4; }
+               | ARG5                          { $$ = B_AT_BI_ARG5; }
+               | ARG6                          { $$ = B_AT_BI_ARG6; }
+               | ARG7                          { $$ = B_AT_BI_ARG7; }
+               | ARG8                          { $$ = B_AT_BI_ARG8; }
+               | ARG9                          { $$ = B_AT_BI_ARG9; }
+               | RETVAL                        { $$ = B_AT_BI_RETVAL; }
+               ;
+
+func0          : F_EXIT                        { $$ = B_AC_EXIT; }
+               ;
+
+func1          : F_CLEAR                       { $$ = B_AC_CLEAR; }
+               | F_ZERO                        { $$ = B_AC_ZERO; }
+               ;
+
+funcn          : F_PRINTF                      { $$ = B_AC_PRINTF; }
+               | F_PRINT                       { $$ = B_AC_PRINT; }
+               ;
+
+mapfunc                : M_COUNT '(' ')'               { $$ = B_AT_MF_COUNT; }
+               ;
+
+term           : '(' term ')'                  { $$ = $2; }
+               | term '+' term                 { $$ = ba_op('+', $1, $3); }
+               | term '-' term                 { $$ = ba_op('-', $1, $3); }
+               | term '/' term                 { $$ = ba_op('/', $1, $3); }
+               | term '*' term                 { $$ = ba_op('*', $1, $3); }
+               | NUMBER                        { $$ = ba_new($1, B_AT_LONG); }
+               | builtin                       { $$ = ba_new(NULL, $1); }
+               | gvar                          { $$ = bv_get($1); }
+               ;
+
+marg           : arg                           { $$ = $1; }
+               | mapfunc                       { $$ = ba_new(NULL, $1); };
+               ;
+
+arg            : CSTRING                       { $$ = ba_new($1, B_AT_STR); }
+               | term
+               ;
+
+gvar           : '@' STRING                    { $$ = $2; }
+
+arglist                : arg
+               | arglist ',' arg               { $$ = ba_concat($1, $3); }
+               ;
+
+stmt           : '\n'                          { $$ = NULL; }
+               | gvar '=' arg ';'              { $$ = bv_set($1, $3); }
+               | gvar '[' arg ']' '=' marg ';' { $$ = bm_set($1, $3, $6); }
+               | funcn '(' arglist ')' ';'     { $$ = bs_new($1, $3, NULL); }
+               | func1 '(' arg ')' ';'         { $$ = bs_new($1, $3, NULL); }
+               | func0 '(' ')' ';'             { $$ = bs_new($1, NULL, NULL); }
+               ;
+
+stmtlist       : stmt 
+               | stmtlist stmt                 { $$ = bs_concat($1, $2); }
+               ;
+
+action         : '{' stmtlist '}'              { $$ = $2; }
+               ;
+
+%%
+
+/* Create a new rule, representing  "probe / filter / { action }" */
+struct bt_rule *
+br_new(struct bt_probe *probe, struct bt_filter *filter, struct bt_stmt *head,
+    enum bt_rtype rtype)
+{
+       struct bt_rule *br;
+
+       br = calloc(1, sizeof(struct bt_rule));
+       if (br == NULL)
+               err(1, "bt_rule: calloc");
+       br->br_probe = probe;
+       br->br_filter = filter;
+       /* SLIST_INSERT_HEAD() nullify the next pointer. */
+       SLIST_FIRST(&br->br_action) = head;
+       br->br_type = rtype;
+
+       if (rtype == B_RT_PROBE) {
+               g_nprobes++;
+               TAILQ_INSERT_TAIL(&g_rules, br, br_next);
+       } else {
+               TAILQ_INSERT_HEAD(&g_rules, br, br_next);
+       }
+
+       return br;
+}
+
+/* Create a new filter */
+struct bt_filter *
+bf_new(enum bt_operand op, enum bt_filtervar var, int val)
+{
+       struct bt_filter *df;
+
+       if (val < 0 || val > INT_MAX)
+               errx(1, "invalid pid '%d'", val);
+
+       df = calloc(1, sizeof(struct bt_filter));
+       if (df == NULL)
+               err(1, "bt_filter: calloc");
+       df->bf_op = op;
+       df->bf_var = var;
+       df->bf_val = val;
+
+       return df;
+}
+
+/* Create a new probe */
+struct bt_probe *
+bp_new(const char *prov, const char *func, const char *name, int32_t rate)
+{
+       struct bt_probe *bp;
+
+       if (rate < 0 || rate > INT32_MAX)
+               errx(1, "only positive values permitted");
+
+       bp = calloc(1, sizeof(struct bt_probe));
+       if (bp == NULL)
+               err(1, "bt_probe: calloc");
+       bp->bp_prov = prov;
+       bp->bp_func = func;
+       bp->bp_name = name;
+       bp->bp_rate = rate;
+
+       return bp;
+}
+
+/* Create a new argument */
+struct bt_arg *
+ba_new0(void *val, enum bt_argtype type)
+{
+       struct bt_arg *ba;
+
+       ba = calloc(1, sizeof(struct bt_arg));
+       if (ba == NULL)
+               err(1, "bt_arg: calloc");
+       ba->ba_value = val;
+       ba->ba_type = type;
+
+       return ba;
+}
+
+/*
+ * Link two arguments together, to build an argument list used in
+ * function calls.
+ */
+struct bt_arg *
+ba_concat(struct bt_arg *da0, struct bt_arg *da1)
+{
+       struct bt_arg *ba = da0;
+
+       assert(da1 != NULL);
+
+       if (da0 == NULL)
+               return da1;
+
+       while (SLIST_NEXT(ba, ba_next) != NULL)
+               ba = SLIST_NEXT(ba, ba_next);
+
+       SLIST_INSERT_AFTER(ba, da1, ba_next);
+
+       return da0;
+}
+
+/* Create an operator argument */
+struct bt_arg *
+ba_op(const char op, struct bt_arg *da0, struct bt_arg *da1)
+{
+       enum bt_argtype type;
+
+       switch (op) {
+       case '+':
+               type = B_AT_OP_ADD;
+               break;
+       case '-':
+               type = B_AT_OP_MINUS;
+               break;
+       case '*':
+               type = B_AT_OP_MULT;
+               break;
+       case '/':
+               type = B_AT_OP_DIVIDE;
+               break;
+       default:
+               assert(0);
+       }
+
+       return ba_new(ba_concat(da0, da1), type);
+}
+
+/* Create a new statement: function call or assignment. */
+struct bt_stmt *
+bs_new(enum bt_action act, struct bt_arg *head, struct bt_var *var)
+{
+       struct bt_stmt *bs;
+
+       bs = calloc(1, sizeof(struct bt_stmt));
+       if (bs == NULL)
+               err(1, "bt_stmt: calloc");
+       bs->bs_act = act;
+       bs->bs_var = var;
+       /* SLIST_INSERT_HEAD() nullify the next pointer. */
+       SLIST_FIRST(&bs->bs_args) = head;
+
+       return bs;
+}
+
+/* Link two statements together, to build an 'action'. */
+struct bt_stmt *
+bs_concat(struct bt_stmt *ds0, struct bt_stmt *ds1)
+{
+       struct bt_stmt *bs = ds0;
+
+       assert(ds1 != NULL);
+
+       if (ds0 == NULL)
+               return ds1;
+
+       while (SLIST_NEXT(bs, bs_next) != NULL)
+               bs = SLIST_NEXT(bs, bs_next);
+
+       SLIST_INSERT_AFTER(bs, ds1, bs_next);
+
+       return ds0;
+}
+
+/* Return the global variable corresponding to `vname'. */
+struct bt_var *
+bv_find(const char *vname)
+{
+       struct bt_var *bv;
+
+       SLIST_FOREACH(bv, &g_variables, bv_next) {
+               if (strcmp(vname, bv->bv_name) == 0)
+                       break;
+       }
+
+       return bv;
+}
+
+/* Find or allocate a global variable. */
+struct bt_var *
+bv_new(const char *vname)
+{
+       struct bt_var *bv;
+
+       bv = calloc(1, sizeof(struct bt_var));
+       if (bv == NULL)
+               err(1, "bt_var: calloc");
+       bv->bv_name = vname;
+       SLIST_INSERT_HEAD(&g_variables, bv, bv_next);
+
+       return bv;
+}
+
+/* Create a 'variable store' statement to assign a value to a variable. */
+struct bt_stmt *
+bv_set(const char *vname, struct bt_arg *vval)
+{
+       struct bt_var *bv;
+
+       bv = bv_find(vname);
+       if (bv == NULL)
+               bv = bv_new(vname);
+       return bs_new(B_AC_STORE, vval, bv);
+}
+
+/* Create an argument that points to a variable. */
+struct bt_arg *
+bv_get(const char *vname)
+{
+       struct bt_var *bv;
+
+       bv = bv_find(vname);
+       if (bv == NULL)
+               yyerror("variable '%s' accessed before being set", vname);
+
+       return ba_new(bv, B_AT_VAR);
+}
+
+struct bt_stmt *
+bm_fn(enum bt_action mact, const char *mname, struct bt_arg *mkey,
+    struct bt_arg *mval)
+{
+       struct bt_var *bv;
+
+       bv = bv_find(mname);
+       if (bv == NULL)
+               bv = bv_new(mname);
+       return bs_new(mact, ba_concat(mval, mkey), bv);
+}
+
+/* Create a 'map store' statement to assign a value to a map entry. */
+struct bt_stmt *
+bm_set(const char *mname, struct bt_arg *mkey, struct bt_arg *mval)
+{
+       return bm_fn(B_AC_INSERT, mname, mkey, mval);
+}
+
+struct keyword {
+       const char      *word;
+       int              token;
+};
+
+int
+kw_cmp(const void *str, const void *xkw)
+{
+       return (strcmp(str, ((const struct keyword *)xkw)->word));
+}
+
+int
+lookup(char *s)
+{
+       static const struct keyword kws[] = {
+               { "!=",         OP_NEQ },
+               { "==",         OP_EQ },
+               { "BEGIN",      BEGIN },
+               { "END",        END },
+               { "arg0",       ARG0 },
+               { "arg1",       ARG1 },
+               { "arg2",       ARG2 },
+               { "arg3",       ARG3 },
+               { "arg4",       ARG4 },
+               { "arg5",       ARG5 },
+               { "arg6",       ARG6 },
+               { "arg7",       ARG7 },
+               { "arg8",       ARG8 },
+               { "arg9",       ARG9 },
+               { "clear",      F_CLEAR },
+               { "comm",       COMM },
+               { "count",      M_COUNT },
+               { "delete",     F_DELETE },
+               { "exit",       F_EXIT },
+               { "hz",         HZ },
+               { "kstack",     KSTACK },
+               { "nsecs",      NSECS },
+               { "pid",        PID },
+               { "print",      F_PRINT },
+               { "printf",     F_PRINTF },
+               { "retval",     RETVAL },
+               { "tid",        TID },
+               { "ustack",     USTACK },
+               { "zero",       F_ZERO },
+       };
+       const struct keyword    *kw;
+
+       kw = bsearch(s, kws, nitems(kws), sizeof(kws[0]), kw_cmp);
+       if (kw != NULL) {
+               return kw->token;
+       } else {
+               return STRING;
+       }
+}
+
+int
+peek(void)
+{
+       if (pbuf != NULL) {
+               if (pindex < plen)
+                       return pbuf[pindex];
+       }
+       return EOF;
+}
+
+int
+lgetc(void)
+{
+       if (pbuf != NULL) {
+               if (pindex < plen) {
+                       yylval.colno++;
+                       return pbuf[pindex++];
+               }
+       }
+       return EOF;
+}
+
+void
+lungetc(void)
+{
+       if (pbuf != NULL && pindex > 0) {
+               yylval.colno--;
+               pindex--;
+       }
+}
+
+int
+yylex(void)
+{
+       unsigned char    buf[1024];
+       unsigned char   *ebuf, *p, *str;
+       int              c, token;
+
+       ebuf = buf + sizeof(buf);
+       p = buf;
+
+again:
+       /* skip whitespaces */
+       for (c = lgetc(); isspace(c); c = lgetc()) {
+               if (c == '\n') {
+                       yylval.lineno++;
+                       yylval.colno = 0;
+               }
+       }
+
+       /* skip single line comments */
+       if (c == '/' && peek() == '/') {
+               for (c = lgetc(); c != EOF; c = lgetc()) {
+                       if (c == '\n') {
+                               yylval.lineno++;
+                               yylval.colno = 0;
+                               goto again;
+                       }
+               }
+       }
+
+       /* skip multi line comments */
+       if (c == '/' && peek() == '*') {
+               int pc;
+
+               for (pc = 0, c = lgetc(); c != EOF; c = lgetc()) {
+                       if (pc == '*' && c == '/')
+                               goto again;
+                       pc = c;
+               }
+       }
+
+       switch (c) {
+       case ',':
+       case '(':
+       case ')':
+       case '{':
+       case '}':
+       case ':':
+       case ';':
+       case '/':
+       case '=':
+               return c;
+       case EOF:
+               return 0;
+       case '"':
+               /* parse C-like string */
+               while ((c = lgetc()) != EOF && c != '"') {
+                       if (c == '\\') {
+                               c = lgetc();
+                               switch (c) {
+                               case '\\':      c = '\\';       break;
+                               case '\'':      c = '\'';       break;
+                               case '"':       c = '"';        break;
+                               case 'a':       c = '\a';       break;
+                               case 'b':       c = '\b';       break;
+                               case 'e':       c = 033;        break;
+                               case 'f':       c = '\f';       break;
+                               case 'n':       c = '\n';       break;
+                               case 'r':       c = '\r';       break;
+                               case 't':       c = '\t';       break;
+                               case 'v':       c = '\v';       break;
+                               default:
+                                       yyerror("'%c' unsuported escape", c);
+                                       return ERROR;
+                               }
+                       }
+                       *p++ = c;
+                       if (p == ebuf) {
+                               yyerror("too long line");
+                               return ERROR;
+                       }
+               }
+               if (c == EOF) {
+                       yyerror("\"%s\" invalid EOF", buf);
+                       return ERROR;
+               }
+               *p++ = '\0';
+               if ((str = strdup(buf)) == NULL)
+                       err(1, "%s", __func__);
+               yylval.v.string = str;
+               return CSTRING;
+       default:
+               break;
+       }
+
+#define allowed_to_end_number(x) \
+       (isspace(x) || x == ')' || x == '/' || x == '{' || x == ';' || x == ']')
+
+       /* parsing number */
+       if (isdigit(c)) {
+               do {
+                       *p++ = c;
+                       if (p == ebuf) {
+                               yyerror("too long line");
+                               return ERROR;
+                       }
+               } while ((c = lgetc()) != EOF && isdigit(c));
+               lungetc();
+               if (c == EOF || allowed_to_end_number(c)) {
+                       const char *errstr = NULL;
+
+                       *p = '\0';
+                       yylval.v.number = strtonum(buf, LONG_MIN, LONG_MAX,
+                           &errstr);
+                       if (errstr) {
+                               yyerror("invalid number '%s' (%s)", buf,
+                                   errstr);
+                               return ERROR;
+                       }
+                       return NUMBER;
+               } else {
+                       while (p > buf + 1) {
+                               --p;
+                               lungetc();
+                       }
+                       c = *--p;
+               }
+       }
+
+#define allowed_in_string(x) (isalnum(c) || c == '!' || c == '=' || c == '_')
+
+       /* parsing next word */
+       if (allowed_in_string(c)) {
+               do {
+                       *p++ = c;
+                       if (p == ebuf) {
+                               yyerror("too long line");
+                               return ERROR;
+                       }
+               } while ((c = lgetc()) != EOF && (allowed_in_string(c)));
+               lungetc();
+               *p = '\0';
+               if ((token = lookup(buf)) == STRING)
+                       if ((yylval.v.string = strdup(buf)) == NULL)
+                               err(1, "%s", __func__);
+               return token;
+       }
+
+       if (c == '\n') {
+               yylval.lineno++;
+               yylval.colno = 0;
+       }
+       if (c == EOF)
+               return 0;
+       return c;
+}
+
+void
+pprint_syntax_error(void)
+{
+       char line[BUFSIZ];
+       int c, indent = yylval.colno;
+       size_t i;
+
+       strlcpy(line, &pbuf[pindex - yylval.colno], sizeof(line) - 1);
+
+       for (i = 0; i < (sizeof(line) - 1) && (c = line[i]) != '\n'; i++) {
+               if (c == '\t')
+                       indent += (8 - 1);
+               fputc(c, stderr);
+       }
+
+       fprintf(stderr, "\n%*c\n", indent, '^');
+}
+
+void
+yyerror(const char *fmt, ...)
+{
+       const char *prefix;
+       va_list va;
+
+       prefix = (yylval.filename != NULL) ? yylval.filename : getprogname();
+
+       fprintf(stderr, "%s:%d:%d: ", prefix, yylval.lineno, yylval.colno);
+       va_start(va, fmt);
+       vfprintf(stderr, fmt, va);
+       va_end(va);
+       fprintf(stderr, ":\n");
+
+       pprint_syntax_error();
+
+       perrors++;
+}
+
+int
+btparse(const char *str, size_t len, const char *filename, int debug)
+{
+       if (debug > 0)
+               yydebug = 1;
+       pbuf = str;
+       plen = len;
+       pindex = 0;
+       yylval.filename = filename;
+       yylval.lineno = 1;
+
+       yyparse();
+
+       return perrors;
+}
diff --git usr.sbin/btrace/bt_parser.h usr.sbin/btrace/bt_parser.h
new file mode 100644
index 00000000000..9e5963cfe03
--- /dev/null
+++ usr.sbin/btrace/bt_parser.h
@@ -0,0 +1,165 @@
+/*     $OpenBSD$       */
+
+/*
+ * Copyright (c) 2019 Martin Pieuchot <m...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef BT_PARSER_H
+#define BT_PARSER_H
+
+#ifndef nitems
+#define nitems(_a)     (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+/*
+ * Representation of a probe.
+ *
+ *     "provider:function:name"
+ * or
+ *     "provider:time_unit:rate"
+ */
+struct bt_probe {
+       const char              *bp_prov;       /* provider */
+       const char              *bp_func;       /* function or time unit */
+       const char              *bp_name;
+       uint32_t                 bp_rate;
+#define bp_unit        bp_func
+};
+
+/*
+ * Representation of a filter (aka predicate).
+ */
+struct bt_filter {
+       enum bt_operand {
+               B_OP_NONE = 1,
+               B_OP_EQ,
+               B_OP_NE,
+       }                        bf_op;
+       enum  bt_filtervar {
+               B_FV_NONE = 1,
+               B_FV_PID,
+               B_FV_TID
+       }                        bf_var;
+       uint32_t                 bf_val;
+};
+
+TAILQ_HEAD(bt_ruleq, bt_rule);
+
+/*
+ * A rule is the language representation of which 'action' to attach to
+ * which 'probe' under which conditions ('filter').  In other words it
+ * represents the following:
+ *
+ *     probe / filter / { action }
+ */
+struct bt_rule {
+       TAILQ_ENTRY(bt_rule)     br_next;       /* linkage in global list */
+       struct bt_probe         *br_probe;
+       struct bt_filter        *br_filter;
+       SLIST_HEAD(, bt_stmt)    br_action;
+
+       enum bt_rtype {
+                B_RT_BEGIN = 1,
+                B_RT_END,
+                B_RT_PROBE,
+       }                        br_type;       /* BEGIN, END or 'probe' */
+
+       uint32_t                 br_pbn;        /* ID assigned by the kernel */
+       void                    *br_cookie;
+};
+
+/*
+ * Global variable representation.
+ */
+struct bt_var {
+       SLIST_ENTRY(bt_var)      bv_next;       /* linkage in global list */
+       const char              *bv_name;       /* name of the variable */
+       struct bt_arg           *bv_value;      /* corresponding value */
+};
+
+/*
+ * Respresentation of an argument.
+ *
+ * A so called "argument" can be any symbol representing a value or
+ * a combination of those through an operation.
+ */
+struct bt_arg {
+       SLIST_ENTRY(bt_arg)      ba_next;
+       void                    *ba_value;
+       enum  bt_argtype {
+               B_AT_STR = 1,                   /* C-style string */
+               B_AT_LONG,                      /* Number (integer) */
+               B_AT_VAR,                       /* global variable (@var) */
+
+               B_AT_BI_PID,
+               B_AT_BI_TID,
+               B_AT_BI_COMM,
+               B_AT_BI_NSECS,
+               B_AT_BI_KSTACK,
+               B_AT_BI_USTACK,
+               B_AT_BI_ARG0,
+               B_AT_BI_ARG1,
+               B_AT_BI_ARG2,
+               B_AT_BI_ARG3,
+               B_AT_BI_ARG4,
+               B_AT_BI_ARG5,
+               B_AT_BI_ARG6,
+               B_AT_BI_ARG7,
+               B_AT_BI_ARG8,
+               B_AT_BI_ARG9,
+               B_AT_BI_ARGS,
+               B_AT_BI_RETVAL,
+
+               B_AT_MF_COUNT,                  /* count() */
+
+               B_AT_OP_ADD,
+               B_AT_OP_MINUS,
+               B_AT_OP_MULT,
+               B_AT_OP_DIVIDE,
+       }                        ba_type;
+};
+
+/*
+ * Statements define what should be done with each event recorded
+ * by the corresponding probe.
+ */
+struct bt_stmt {
+       SLIST_ENTRY(bt_stmt)     bs_next;
+       struct bt_var           *bs_var;        /* for STOREs */
+       SLIST_HEAD(, bt_arg)     bs_args;
+       enum bt_action {
+               B_AC_STORE = 1,                 /* @a = 3 */
+               B_AC_INSERT,                    /* @map[key] = 42 */
+               B_AC_CLEAR,                     /* clear(@map) */
+               B_AC_DELETE,                    /* delete(@map[key]) */
+               B_AC_EXIT,                      /* exit() */
+               B_AC_PRINT,                     /* print(@map, 10) */
+               B_AC_PRINTF,                    /* printf("hello!\n") */
+               B_AC_ZERO,                      /* zero(@map) */
+       }                        bs_act;
+};
+
+struct bt_ruleq                 g_rules;       /* Successfully parsed rules. */
+int                     g_nprobes;     /* # of probes to attach */
+
+int                     btparse(const char *, size_t, const char *, int);
+
+#define ba_new(v, t)    ba_new0((void *)(v), (t))
+struct bt_arg          *ba_new0(void *, enum bt_argtype);
+
+void                    bm_insert(struct bt_var *, struct bt_arg *,
+                            struct bt_arg *);
+
+#endif /* BT_PARSER_H */
diff --git usr.sbin/btrace/btrace.8 usr.sbin/btrace/btrace.8
new file mode 100644
index 00000000000..910e6f0c092
--- /dev/null
+++ usr.sbin/btrace/btrace.8
@@ -0,0 +1,69 @@
+.\"    $OpenBSD$
+.\"
+.\" Copyright (c) 2019 Martin Pieuchot <m...@openbsd.org>
+.\"
+.\" Permission to use, copy, modify, and distribute this software for any
+.\" purpose with or without fee is hereby granted, provided that the above
+.\" copyright notice and this permission notice appear in all copies.
+.\"
+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+.\"
+.Dd $Mdocdate: September 19 2019 $
+.Dt BTRACE 8
+.Os
+.Sh NAME
+.Nm btrace
+.Nd dynamic tracer
+.Sh SYNOPSIS
+.Nm btrace
+.Op Fl lv
+.Op Fl e Ar program | Ar file
+.Sh DESCRIPTION
+The
+.Nm
+utility provides an interface to inspect the internals of the system and
+programs.
+It interprets the
+.Xr bt 5
+program in
+.Ar file
+and communicates with the dynamic tracer device using the interface
+described
+in
+.Xr dt 4 .
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl e Ar program
+Execute
+.Ar program .
+.It Fl l
+List all available probes.
+.It Fl v
+Verbose mode.
+Causes
+.Nm
+to print debugging messages.
+Multiple
+.Fl v
+options increase the verbosity.
+The maximum is 2.
+.El
+.Sh EXIT STATUS
+.Ex -std 
+.Sh SEE ALSO
+.Xr dt 4 ,
+.Xr bt 5
+.Sh HISTORY
+The
+.Nm
+utility first appeared in
+.Ox 6.7 .
+.Sh AUTHORS
+.An Martin Pieuchot Aq Mt m...@openbsd.org
diff --git usr.sbin/btrace/btrace.c usr.sbin/btrace/btrace.c
new file mode 100644
index 00000000000..7868b595864
--- /dev/null
+++ usr.sbin/btrace/btrace.c
@@ -0,0 +1,1035 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2019 - 2020 Martin Pieuchot <m...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/ioctl.h>
+#include <sys/exec_elf.h>
+#include <sys/syscall.h>
+#include <sys/queue.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <locale.h>
+#include <signal.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dev/dt/dtvar.h>
+
+#include "btrace.h"
+#include "bt_parser.h"
+
+/*
+ * Maximum number of operands an arithmetic operation can have.  This
+ * is necessary to stop infinite recursion when evaluating expressions.
+ */
+#define __MAXOPERANDS  5
+
+#define __PATH_DEVDT "/dev/dt"
+
+__dead void             usage(void);
+char                   *read_btfile(const char *);
+
+/*
+ * Retrieve & parse probe information.
+ */
+void                    dtpi_cache(int);
+void                    dtpi_print_list(void);
+char                   *dtpi_func(struct dtioc_probe_info *);
+int                     dtpi_is_unit(const char *);
+struct dtioc_probe_info        *dtpi_get_by_value(const char *, const char *,
+                            const char *);
+
+/*
+ * Main loop and rule evaluation.
+ */
+void                    rules_do(int);
+void                    rules_setup(int);
+void                    rules_apply(struct dt_evt *);
+void                    rules_teardown(int);
+void                    rule_eval(struct bt_rule *, struct dt_evt *);
+
+/*
+ * Language builtins & functions.
+ */
+uint64_t                builtin_nsecs(struct dt_evt *);
+const char             *builtin_kstack(struct dt_evt *);
+const char             *builtin_arg(struct dt_evt *, enum bt_argtype);
+void                    stmt_clear(struct bt_stmt *);
+void                    stmt_delete(struct bt_stmt *, struct dt_evt *);
+void                    stmt_insert(struct bt_stmt *, struct dt_evt *);
+void                    stmt_print(struct bt_stmt *, struct dt_evt *);
+void                    stmt_store(struct bt_stmt *, struct dt_evt *);
+void                    stmt_zero(struct bt_stmt *);
+struct bt_arg          *ba_read(struct bt_arg *);
+long                    ba2long(struct bt_arg *, struct dt_evt *);
+
+/* FIXME: use a real hash. */
+#define ba2hash(_b, _e)        ba2str((_b), (_e))
+
+/*
+ * Debug routines.
+ */
+__dead void             xabort(const char *, ...);
+void                    debug(const char *, ...);
+void                    debugx(const char *, ...);
+const char             *debug_rule_name(struct bt_rule *);
+void                    debug_dump_filter(struct bt_rule *);
+void                    debug_dump_rule(struct bt_rule *);
+
+struct dtioc_probe_info        *dt_dtpis;      /* array of available probes */
+size_t                  dt_ndtpi;      /* # of elements in the array */
+
+int                     verbose = 0;
+volatile sig_atomic_t   quit_pending;
+
+static void
+signal_handler(int sig)
+{
+       quit_pending = sig;
+}
+
+
+int
+main(int argc, char *argv[])
+{
+       int fd = -1, ch, error = 0;
+       const char *filename = NULL, *btscript = NULL;
+       int showprobes = 0;
+
+       setlocale(LC_ALL, "");
+
+#if notyet
+       if (pledge("stdio rpath", NULL) == -1)
+               err(1, "pledge");
+#endif
+
+       while ((ch = getopt(argc, argv, "e:lv")) != -1) {
+               switch (ch) {
+               case 'e':
+                       btscript = optarg;
+                       break;
+               case 'l':
+                       showprobes = 1;
+                       break;
+               case 'v':
+                       verbose++;
+                       break;
+               default:
+                       usage();
+               }
+       }
+
+       argc -= optind;
+       argv += optind;
+
+       if (argc > 0) {
+               if (btscript != NULL)
+                       usage();
+
+               filename = argv[0];
+               btscript = read_btfile(filename);
+               argc--;
+               argv++;
+       }
+
+       if (argc != 0 || (btscript == NULL && !showprobes))
+               usage();
+
+       if (btscript != NULL) {
+               error = btparse(btscript, strlen(btscript), filename, 1);
+               if (error)
+                       return error;
+       }
+
+       if (showprobes || g_nprobes > 0) {
+               fd = open(__PATH_DEVDT, O_RDONLY);
+               if (fd == -1)
+                       err(1, "could not open %s", __PATH_DEVDT);
+       }
+
+       if (showprobes) {
+               dtpi_cache(fd);
+               dtpi_print_list();
+       }
+
+       if (!TAILQ_EMPTY(&g_rules))
+               rules_do(fd);
+
+       if (fd != -1)
+               close(fd);
+
+       return error;
+}
+
+__dead void
+usage(void)
+{
+       fprintf(stderr, "usage: %s [-lv] [-e program|file]\n",
+           getprogname());
+       exit(1);
+}
+
+char *
+read_btfile(const char *filename)
+{
+       static char fcontent[BUFSIZ];
+       long offset;
+       FILE *fp;
+
+       fp = fopen(filename, "r");
+       if (fp == NULL)
+               err(1, "can't open '%s'", filename);
+
+       if (fread(fcontent, sizeof(fcontent) - 1, 1, fp) == 0 && errno != 0)
+               err(1, "can't read '%s'", filename);
+
+       fseek(fp, 0, SEEK_END);
+       offset = ftell(fp);
+       if ((size_t)offset >= sizeof(fcontent))
+               errx(1, "couldn't read all of '%s'", filename);
+
+       fclose(fp);
+       return fcontent;
+}
+
+void
+dtpi_cache(int fd)
+{
+       struct dtioc_probe dtpr;
+
+       if (dt_dtpis != NULL)
+               return;
+
+       memset(&dtpr, 0, sizeof(dtpr));
+       if (ioctl(fd, DTIOCGPLIST, &dtpr))
+               err(1, "DTIOCGPLIST");
+
+       dt_ndtpi = (dtpr.dtpr_size / sizeof(*dt_dtpis));
+       dt_dtpis = reallocarray(NULL, dt_ndtpi, sizeof(*dt_dtpis));
+       if (dt_dtpis == NULL)
+               err(1, "malloc");
+
+       dtpr.dtpr_probes = dt_dtpis;
+       if (ioctl(fd, DTIOCGPLIST, &dtpr))
+               err(1, "DTIOCGPLIST");
+}
+
+void
+dtpi_print_list(void)
+{
+       struct dtioc_probe_info *dtpi;
+       size_t i;
+
+       dtpi = dt_dtpis;
+       for (i = 0; i < dt_ndtpi; i++, dtpi++) {
+               printf("%s:%s:%s\n", dtpi->dtpi_prov, dtpi_func(dtpi),
+                   dtpi->dtpi_name);
+       }
+}
+
+char *
+dtpi_func(struct dtioc_probe_info *dtpi)
+{
+       char *sysnb, func[DTNAMESIZE];
+       const char *errstr;
+       int idx;
+
+       if (strncmp(dtpi->dtpi_prov, "syscall", DTNAMESIZE))
+               return dtpi->dtpi_func;
+
+       /* Translate syscall names */
+       strlcpy(func, dtpi->dtpi_func, sizeof(func));
+       sysnb = func;
+       if (strsep(&sysnb, "%") == NULL)
+               return dtpi->dtpi_func;
+
+       idx = strtonum(sysnb, 1, SYS_MAXSYSCALL, &errstr);
+       if (errstr != NULL)
+               return dtpi->dtpi_func;
+
+       return syscallnames[idx];
+}
+
+int
+dtpi_is_unit(const char *unit)
+{
+       return !strncmp("hz", unit, sizeof("hz"));
+}
+
+struct dtioc_probe_info *
+dtpi_get_by_value(const char *prov, const char *func, const char *name)
+{
+       struct dtioc_probe_info *dtpi;
+       size_t i;
+
+       dtpi = dt_dtpis;
+       for (i = 0; i < dt_ndtpi; i++, dtpi++) {
+               if (prov != NULL &&
+                   strncmp(prov, dtpi->dtpi_prov, DTNAMESIZE))
+                       continue;
+
+               if (func != NULL) {
+                       if (dtpi_is_unit(func))
+                               return dtpi;
+
+                       if (strncmp(func, dtpi_func(dtpi), DTNAMESIZE))
+                               continue;
+               }
+
+               if (strncmp(name, dtpi->dtpi_name, DTNAMESIZE))
+                       continue;
+
+               debug("matched probe %s:%s:%s\n", dtpi->dtpi_prov,
+                   dtpi_func(dtpi), dtpi->dtpi_name);
+               return dtpi;
+       }
+
+       return NULL;
+}
+
+void
+rules_do(int fd)
+{
+       struct sigaction sa;
+
+       memset(&sa, 0, sizeof(sa));
+       sigemptyset(&sa.sa_mask);
+       sa.sa_flags = 0;
+       sa.sa_handler = signal_handler;
+       if (sigaction(SIGINT, &sa, NULL))
+               err(1, "sigaction");
+
+       rules_setup(fd);
+
+       while (!quit_pending && g_nprobes > 0) {
+               static struct dt_evt devtbuf[64];
+               ssize_t rlen;
+               size_t i;
+
+               rlen = read(fd, devtbuf, sizeof(devtbuf) - 1);
+               if (rlen == -1) {
+                       if (errno == EINTR && quit_pending)
+                               break;
+                       err(1, "read");
+               }
+
+               if ((rlen % sizeof(struct dt_evt)) != 0)
+                       err(1, "incorrect read");
+
+
+               for (i = 0; i < nitems(devtbuf); i++) {
+                       struct dt_evt *dtev = &devtbuf[i];
+
+                       if (dtev->dtev_tid == 0)
+                               break;
+
+                       rules_apply(dtev);
+               }
+       }
+
+       rules_teardown(fd);
+
+       if (verbose && fd != -1) {
+               struct dtioc_stat dtst;
+
+               memset(&dtst, 0, sizeof(dtst));
+               if (ioctl(fd, DTIOCGSTATS, &dtst))
+                       warn("DTIOCGSTATS");
+
+               printf("%llu events read\n", dtst.dtst_readevt);
+               printf("%llu events dropped\n", dtst.dtst_dropevt);
+       }
+}
+
+static inline enum dt_operand
+dop2dt(enum bt_operand op)
+{
+       switch (op) {
+       case B_OP_EQ:   return DT_OP_EQ;
+       case B_OP_NE:   return DT_OP_NE;
+       case B_OP_NONE: return DT_OP_NONE;
+       default:        break;
+       }
+       xabort("unknown operand %d", op);
+}
+
+
+static inline enum dt_filtervar
+dvar2dt(enum bt_filtervar var)
+{
+       switch (var) {
+       case B_FV_PID:  return DT_FV_PID;
+       case B_FV_TID:  return DT_FV_TID;
+       case B_FV_NONE: return DT_FV_NONE;
+       default:        break;
+       }
+       xabort("unknown filter %d", var);
+}
+
+
+void
+rules_setup(int fd)
+{
+       struct dtioc_probe_info *dtpi;
+       struct dtioc_req *dtrq;
+       struct bt_rule *r, *rbegin = NULL;
+       struct bt_probe *bp;
+       struct bt_stmt *bs;
+       int dokstack = 0, on = 1;
+
+       TAILQ_FOREACH(r, &g_rules, br_next) {
+               debug_dump_rule(r);
+
+               if (r->br_type != B_RT_PROBE) {
+                       if (r->br_type == B_RT_BEGIN)
+                               rbegin = r;
+                       continue;
+               }
+
+               bp = r->br_probe;
+               dtpi_cache(fd);
+               dtpi = dtpi_get_by_value(bp->bp_prov, bp->bp_func, bp->bp_name);
+               if (dtpi == NULL)
+                       errx(1, "probe not found");
+
+               dtrq = calloc(1, sizeof(*dtrq));
+               if (dtrq == NULL)
+                       err(1, "dtrq: 1alloc");
+
+               r->br_pbn = dtpi->dtpi_pbn;
+               dtrq->dtrq_pbn = dtpi->dtpi_pbn;
+               if (r->br_filter) {
+                       struct bt_filter *df = r->br_filter;
+
+                       dtrq->dtrq_filter.dtf_operand = dop2dt(df->bf_op);
+                       dtrq->dtrq_filter.dtf_variable = dvar2dt(df->bf_var);
+                       dtrq->dtrq_filter.dtf_value = df->bf_val;
+               }
+               dtrq->dtrq_rate = r->br_probe->bp_rate;
+
+               SLIST_FOREACH(bs, &r->br_action, bs_next) {
+                       struct bt_arg *ba;
+
+                       SLIST_FOREACH(ba, &bs->bs_args, ba_next) {
+                               switch (ba->ba_type) {
+                               case B_AT_STR:
+                               case B_AT_LONG:
+                               case B_AT_VAR:
+                                       break;
+                               case B_AT_BI_KSTACK:
+                                       dtrq->dtrq_evtflags |= DTEVT_KSTACK;
+                                       dokstack = 1;
+                                       break;
+                               case B_AT_BI_USTACK:
+                                       dtrq->dtrq_evtflags |= DTEVT_USTACK;
+                                       break;
+                               case B_AT_BI_COMM:
+                                       dtrq->dtrq_evtflags |= DTEVT_EXECNAME;
+                                       break;
+                               case B_AT_BI_PID:
+                               case B_AT_BI_TID:
+                               case B_AT_BI_NSECS:
+                                       break;
+                               case B_AT_BI_ARG0 ... B_AT_BI_ARG9:
+                                       dtrq->dtrq_evtflags |= DTEVT_FUNCARGS;
+                                       break;
+                               case B_AT_BI_RETVAL:
+                                       dtrq->dtrq_evtflags |= DTEVT_RETVAL;
+                                       break;
+                               case B_AT_MF_COUNT:
+                               case B_AT_OP_ADD ... B_AT_OP_DIVIDE:
+                                       break;
+                               default:
+                                       xabort("invalid argument type %d",
+                                           ba->ba_type);
+                               }
+                       }
+               }
+
+               r->br_cookie = dtrq;
+       }
+
+       if (dokstack)
+               kelf_open();
+
+       if (rbegin)
+               rule_eval(rbegin, NULL);
+
+       /* Enable all probes */
+       TAILQ_FOREACH(r, &g_rules, br_next) {
+               if (r->br_type != B_RT_PROBE)
+                       continue;
+
+               dtrq = r->br_cookie;
+               if (ioctl(fd, DTIOCPRBENABLE, dtrq))
+                       err(1, "DTIOCPRBENABLE");
+       }
+
+       if (g_nprobes > 0) {
+               if (ioctl(fd, DTIOCRECORD, &on))
+                       err(1, "DTIOCRECORD");
+       }
+}
+
+void
+rules_apply(struct dt_evt *dtev)
+{
+       struct bt_rule *r;
+
+       TAILQ_FOREACH(r, &g_rules, br_next) {
+               if (r->br_type != B_RT_PROBE || r->br_pbn != dtev->dtev_pbn)
+                       continue;
+
+               rule_eval(r, dtev);
+       }
+}
+
+void
+rules_teardown(int fd)
+{
+       struct dtioc_req *dtrq;
+       struct bt_rule *r, *rend = NULL;
+       int dokstack = 0, off = 0;
+
+       if (g_nprobes > 0) {
+               if (ioctl(fd, DTIOCRECORD, &off))
+                       err(1, "DTIOCRECORD");
+       }
+
+       TAILQ_FOREACH(r, &g_rules, br_next) {
+               if (r->br_type != B_RT_PROBE) {
+                       if (r->br_type == B_RT_END)
+                               rend = r;
+                       continue;
+               }
+
+               dtrq = r->br_cookie;
+               if (dtrq->dtrq_evtflags & DTEVT_KSTACK)
+                       dokstack = 1;
+       }
+
+       if (dokstack)
+               kelf_close();
+
+       if (rend)
+               rule_eval(rend, NULL);
+}
+
+void
+rule_eval(struct bt_rule *r, struct dt_evt *dtev)
+{
+       struct bt_stmt *bs;
+
+       debug("eval rule '%s'\n", debug_rule_name(r));
+
+       SLIST_FOREACH(bs, &r->br_action, bs_next) {
+               switch (bs->bs_act) {
+               case B_AC_STORE:
+                       stmt_store(bs, dtev);
+                       break;
+               case B_AC_INSERT:
+                       stmt_insert(bs, dtev);
+                       break;
+               case B_AC_CLEAR:
+                       stmt_clear(bs);
+                       break;
+               case B_AC_DELETE:
+                       stmt_delete(bs, dtev);
+                       break;
+               case B_AC_EXIT:
+                       exit(0);
+                       break;
+               case B_AC_PRINT:
+                       stmt_print(bs, dtev);
+                       break;
+               case B_AC_PRINTF:
+                       stmt_printf(bs, dtev);
+                       break;
+               case B_AC_ZERO:
+                       stmt_zero(bs);
+                       break;
+               default:
+                       xabort("no handler for action type %d", bs->bs_act);
+               }
+       }
+}
+
+static inline uint64_t
+TIMESPEC_TO_NSEC(struct timespec *ts)
+{
+       return (ts->tv_sec * 1000000000L + ts->tv_nsec);
+}
+
+uint64_t
+builtin_nsecs(struct dt_evt *dtev)
+{
+       uint64_t nsecs;
+
+       if (dtev == NULL) {
+               struct timeval tv;
+
+               gettimeofday(&tv, NULL);
+               nsecs = (tv.tv_sec * 1000000000L + tv.tv_usec * 1000);
+       } else
+               nsecs = TIMESPEC_TO_NSEC(&dtev->dtev_tsp);
+
+       return nsecs;
+}
+
+#include <machine/vmparam.h>
+#define        INKERNEL(va)    ((va) >= VM_MIN_KERNEL_ADDRESS)
+
+const char *
+builtin_stack(struct dt_evt *dtev, int kernel)
+{
+       struct dt_stack_trace *st = &dtev->dtev_kstack;
+       static char buf[4096];
+       size_t i;
+       int n = 0;
+
+       if (st->st_count == 0)
+               return "";
+
+       for (i = 0; i < st->st_count; i++) {
+               if (INKERNEL(st->st_pc[i])) {
+                       if (!kernel)
+                               continue;
+                       n += kelf_snprintsym(buf + n, sizeof(buf) - 1 - n,
+                           st->st_pc[i]);
+               } else if (!kernel) {
+                       n += snprintf(buf + n, sizeof(buf) - 1 - n, "0x%lx\n",
+                           st->st_pc[1]);
+               }
+       }
+
+       return buf;
+}
+
+const char *
+builtin_arg(struct dt_evt *dtev, enum bt_argtype dat)
+{
+       static char buf[sizeof("18446744073709551615")]; /* UINT64_MAX */
+
+       snprintf(buf, sizeof(buf) - 1, "%lu", dtev->dtev_sysargs[dat - 
B_AT_BI_ARG0]);
+       return buf;
+}
+
+void
+stmt_clear(struct bt_stmt *bs)
+{
+       struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
+       struct bt_var *bv = ba->ba_value;
+
+       assert(bs->bs_var == NULL);
+       assert(ba->ba_type = B_AT_VAR);
+
+       map_clear(bv);
+
+       debug("map=%p '%s' clear\n", bv->bv_value, bv->bv_name);
+}
+
+/*
+ * Map delete:         { delete(@map[key]); }
+ *
+ * In this case 'map' is represented by `bv' and 'key' by `bkey'.
+ */
+void
+stmt_delete(struct bt_stmt *bs, struct dt_evt *dtev)
+{
+       struct bt_arg *bkey = SLIST_FIRST(&bs->bs_args);
+       struct bt_var *bv = bs->bs_var;
+
+       assert(SLIST_NEXT(bkey, ba_next) == NULL);
+
+       map_delete(bv, ba2hash(bkey, dtev));
+
+       debug("map=%p '%s' delete key=%p\n", bv->bv_value, bv->bv_name, bkey);
+}
+
+/*
+ * Map insert:         { @map[key] = 42; }
+ *
+ * In this case 'map' is represented by `bv', 'key' by `bkey' and
+ * '42' by `bval'.
+ */
+void
+stmt_insert(struct bt_stmt *bs, struct dt_evt *dtev)
+{
+       struct bt_arg *bkey, *bval = SLIST_FIRST(&bs->bs_args);
+       struct bt_var *bv = bs->bs_var;
+
+       bkey = SLIST_NEXT(bval, ba_next);
+       assert(SLIST_NEXT(bkey, ba_next) == NULL);
+
+       map_insert(bv, ba2hash(bkey, dtev), bval);
+
+       debug("map=%p '%s' insert key=%p bval=%p\n", bv->bv_value, bv->bv_name,
+           bkey, bval);
+}
+
+/*
+ * Print map entries:  { print(@map[, 8]); }
+ *
+ * In this case the global variable 'map' is pointed at by `ba'
+ * and '8' is represented by `btop'.
+ */
+void
+stmt_print(struct bt_stmt *bs, struct dt_evt *dtev)
+{
+       struct bt_arg *btop, *ba = SLIST_FIRST(&bs->bs_args);
+       struct bt_var *bv = ba->ba_value;
+       size_t top = SIZE_T_MAX;
+
+       assert(bs->bs_var == NULL);
+       assert(ba->ba_type = B_AT_VAR);
+
+       /* Parse optional `top' argument. */
+       btop = SLIST_NEXT(ba, ba_next);
+       if (btop != NULL) {
+               assert(SLIST_NEXT(btop, ba_next) == NULL);
+               top = ba2long(btop, dtev);
+       }
+
+       map_print(bv, top);
+
+       debug("map=%p '%s' print (top=%d)\n", bv->bv_value, bv->bv_name, top);
+}
+
+/*
+ * Variable store:     { var = 3; }
+ *
+ * In this case '3' is represented by `ba', the argument of a STORE
+ * action.
+ *
+ * If the argument depends of the value of an event (builtin) or is
+ * the result of an operation, its evaluation is stored in a new `ba'.
+ */
+void
+stmt_store(struct bt_stmt *bs, struct dt_evt *dtev)
+{
+       struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
+       struct bt_var *bv = bs->bs_var;
+
+       assert(SLIST_NEXT(ba, ba_next) == NULL);
+
+       switch (ba->ba_type) {
+       case B_AT_LONG:
+               bv->bv_value = ba;
+               break;
+       case B_AT_BI_NSECS:
+               bv->bv_value = ba_new(builtin_nsecs(dtev), B_AT_LONG);
+               break;
+       case B_AT_OP_ADD ... B_AT_OP_DIVIDE:
+               bv->bv_value = ba_new(ba2long(ba, dtev), B_AT_LONG);
+               break;
+       default:
+               xabort("store not implemented for type %d", ba->ba_type);
+       }
+
+       debug("bv=%p var '%s' store (%p) \n", bv, bv->bv_name, bv->bv_value);
+}
+
+void
+stmt_zero(struct bt_stmt *bs)
+{
+       struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
+       struct bt_var *bv = ba->ba_value;
+
+       assert(bs->bs_var == NULL);
+       assert(ba->ba_type = B_AT_VAR);
+
+       map_zero(bv);
+
+       debug("map=%p '%s' zero\n", bv->bv_value, bv->bv_name);
+}
+struct bt_arg *
+ba_read(struct bt_arg *ba)
+{
+       struct bt_var *bv = ba->ba_value;
+
+       assert(ba->ba_type == B_AT_VAR);
+
+       debug("bv=%p read '%s' (%p)\n", bv, bv->bv_name, bv->bv_value);
+
+       return bv->bv_value;
+}
+
+/*
+ * Helper to evaluate the operation encoded in `ba' and return its
+ * result.
+ */
+static inline long
+baexpr2long(struct bt_arg *ba, struct dt_evt *dtev)
+{
+       static long recursions;
+       struct bt_arg *a, *b;
+       long first, second, result;
+
+       if (++recursions >= __MAXOPERANDS)
+               errx(1, "too many operands (>%d) in expression", __MAXOPERANDS);
+
+       a = ba->ba_value;
+       b = SLIST_NEXT(a, ba_next);
+
+       assert(SLIST_NEXT(b, ba_next) == NULL);
+
+       first = ba2long(a, dtev);
+       second = ba2long(b, dtev);
+
+       switch (ba->ba_type) {
+       case B_AT_OP_ADD:
+               result = first + second;
+               break;
+       case B_AT_OP_MINUS:
+               result = first - second;
+               break;
+       case B_AT_OP_MULT:
+               result = first * second;
+               break;
+       case B_AT_OP_DIVIDE:
+               result = first / second;
+               break;
+       default:
+               xabort("unsuported operation %d", ba->ba_type);
+       }
+
+       debug("ba=%p (%ld op %ld) = %ld\n", ba, first, second, result);
+
+       --recursions;
+
+       return result;
+}
+
+/*
+ * Return the representation of `ba' as long.
+ */
+long
+ba2long(struct bt_arg *ba, struct dt_evt *dtev)
+{
+       long val;
+
+       switch (ba->ba_type) {
+       case B_AT_LONG:
+               val = (long)ba->ba_value;
+               break;
+       case B_AT_VAR:
+               ba = ba_read(ba);
+               val = (long)ba->ba_value;
+               break;
+       case B_AT_BI_NSECS:
+               val = builtin_nsecs(dtev);
+               break;
+       case B_AT_OP_ADD ... B_AT_OP_DIVIDE:
+               val = baexpr2long(ba, dtev);
+               break;
+       default:
+               xabort("no long conversion for type %d", ba->ba_type);
+       }
+
+       debug("ba=%p long='%ld'\n", ba, val);
+
+       return  val;
+}
+
+/*
+ * Return the representation of `ba' as string.
+ */
+const char *
+ba2str(struct bt_arg *ba, struct dt_evt *dtev)
+{
+       static char buf[sizeof("18446744073709551615")]; /* UINT64_MAX */
+       const char *str;
+
+       switch (ba->ba_type) {
+       case B_AT_STR:
+               str = (const char *)ba->ba_value;
+               break;
+       case B_AT_LONG:
+               snprintf(buf, sizeof(buf) - 1, "%ld",(long)ba->ba_value);
+               str = buf;
+               break;
+       case B_AT_BI_KSTACK:
+               str = builtin_stack(dtev, 1);
+               break;
+       case B_AT_BI_USTACK:
+               str = builtin_stack(dtev, 0);
+               break;
+       case B_AT_BI_COMM:
+               str = dtev->dtev_comm;
+               break;
+       case B_AT_BI_PID:
+               snprintf(buf, sizeof(buf) - 1, "%d", dtev->dtev_pid);
+               str = buf;
+               break;
+       case B_AT_BI_TID:
+               snprintf(buf, sizeof(buf) - 1, "%d", dtev->dtev_tid);
+               str = buf;
+               break;
+       case B_AT_BI_NSECS:
+               snprintf(buf, sizeof(buf) - 1, "%llu", builtin_nsecs(dtev));
+               str = buf;
+               break;
+       case B_AT_BI_ARG0 ... B_AT_BI_ARG9:
+               str = builtin_arg(dtev, ba->ba_type);
+               break;
+       case B_AT_BI_RETVAL:
+               snprintf(buf, sizeof(buf) - 1, "%ld", 
(long)dtev->dtev_sysretval);
+               str = buf;
+               break;
+       case B_AT_VAR:
+               str = ba2str(ba_read(ba), dtev);
+               break;
+       case B_AT_OP_ADD ... B_AT_OP_DIVIDE:
+               snprintf(buf, sizeof(buf) - 1, "%ld", ba2long(ba, dtev));
+               str = buf;
+               break;
+       case B_AT_MF_COUNT:
+               assert(0);
+               break;
+       default:
+               xabort("no string conversion for type %d", ba->ba_type);
+       }
+
+       debug("ba=%p str='%s'\n", ba, str);
+
+       return str;
+}
+
+long
+bacmp(struct bt_arg *a, struct bt_arg *b)
+{
+       assert(a->ba_type == b->ba_type);
+       assert(a->ba_type == B_AT_LONG);
+
+       return ba2long(a, NULL) - ba2long(b, NULL);
+}
+
+__dead void
+xabort(const char *fmt, ...)
+{
+       va_list ap;
+
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+
+       fprintf(stderr, "\n");
+       abort();
+}
+
+void
+debug(const char *fmt, ...)
+{
+       va_list ap;
+
+       if (verbose < 2)
+               return;
+
+       fprintf(stderr, "debug: ");
+
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+}
+
+void
+debugx(const char *fmt, ...)
+{
+       va_list ap;
+
+       if (verbose < 2)
+               return;
+
+       va_start(ap, fmt);
+       vfprintf(stderr, fmt, ap);
+       va_end(ap);
+}
+
+static inline const char *
+debug_getfiltervar(struct bt_filter *df)
+{
+       switch (df->bf_var) {
+       case B_FV_PID:  return "pid";
+       case B_FV_TID:  return "tid";
+       case B_FV_NONE: return "";
+       default:
+               xabort("invalid filtervar %d", df->bf_var);
+       }
+
+
+}
+
+static inline const char *
+debug_getfilterop(struct bt_filter *df)
+{
+       switch (df->bf_op) {
+       case B_OP_EQ:   return "==";
+       case B_OP_NE:   return "!=";
+       case B_OP_NONE: return "";
+       default:
+               xabort("invalid operand %d", df->bf_op);
+       }
+}
+
+void
+debug_dump_filter(struct bt_rule *r)
+{
+       if (r->br_filter) {
+               debugx(" / %s %s %u /", debug_getfiltervar(r->br_filter),
+                   debug_getfilterop(r->br_filter), r->br_filter->bf_val);
+       }
+       debugx("\n");
+}
+
+const char *
+debug_rule_name(struct bt_rule *r)
+{
+       struct bt_probe *bp = r->br_probe;
+       static char buf[64];
+
+       if (r->br_type == B_RT_BEGIN)
+               return "BEGIN";
+
+       if (r->br_type == B_RT_END)
+               return "END";
+
+       assert(r->br_type == B_RT_PROBE);
+
+       if (r->br_probe->bp_rate) {
+               snprintf(buf, sizeof(buf) - 1, "%s:%s:%u", bp->bp_prov,
+                   bp->bp_unit, bp->bp_rate);
+       } else {
+               snprintf(buf, sizeof(buf) - 1, "%s:%s:%s", bp->bp_prov,
+                   bp->bp_unit, bp->bp_name);
+       }
+
+       return buf;
+}
+
+void
+debug_dump_rule(struct bt_rule *r)
+{
+       debug("parsed probe '%s'", debug_rule_name(r));
+       debug_dump_filter(r);
+}
diff --git usr.sbin/btrace/btrace.h usr.sbin/btrace/btrace.h
new file mode 100644
index 00000000000..472c91d9655
--- /dev/null
+++ usr.sbin/btrace/btrace.h
@@ -0,0 +1,54 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2019 - 2020 Martin Pieuchot <m...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef BTRACE_H
+#define BTRACE_H
+
+#ifndef nitems
+#define nitems(_a)     (sizeof((_a)) / sizeof((_a)[0]))
+#endif
+
+struct dt_evt;
+struct bt_arg;
+struct bt_var;
+struct bt_stmt;
+
+/* btrace.c */
+const char             *ba2str(struct bt_arg *, struct dt_evt *);
+long                    bacmp(struct bt_arg *, struct bt_arg *);
+
+/* ksyms.c */
+int                     kelf_open(void);
+void                    kelf_close(void);
+int                     kelf_snprintsym(char *, size_t, unsigned long);
+
+/* map.c */
+void                    map_clear(struct bt_var *);
+void                    map_delete(struct bt_var *, const char *);
+void                    map_insert(struct bt_var *, const char *,
+                            struct bt_arg *);
+void                    map_print(struct bt_var *, size_t);
+void                    map_zero(struct bt_var *);
+
+/* printf.c */
+int                     stmt_printf(struct bt_stmt *, struct dt_evt *);
+
+/* syscalls.c */
+extern char            *syscallnames[];
+
+#endif /* BTRACE_H */
diff --git usr.sbin/btrace/ksyms.c usr.sbin/btrace/ksyms.c
new file mode 100644
index 00000000000..de4b43b844e
--- /dev/null
+++ usr.sbin/btrace/ksyms.c
@@ -0,0 +1,178 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2016 Martin Pieuchot <m...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <fcntl.h>
+#include <gelf.h>
+#include <paths.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "btrace.h"
+
+int             kfd = -1;
+Elf            *kelf;
+Elf_Scn                *ksymtab;
+size_t          kstrtabndx, knsymb;
+
+int             kelf_parse(void);
+
+int
+kelf_open(void)
+{
+       int error;
+
+       assert(kfd == -1);
+
+       if (elf_version(EV_CURRENT) == EV_NONE)
+               errx(1, "elf_version: %s", elf_errmsg(-1));
+
+       kfd = open(_PATH_KSYMS, O_RDONLY);
+       if (kfd == -1) {
+               warn("open");
+               return 1;
+       }
+
+       if ((kelf = elf_begin(kfd, ELF_C_READ, NULL)) == NULL) {
+               warnx("elf_begin: %s", elf_errmsg(-1));
+               error = 1;
+               goto bad;
+       }
+
+       if (elf_kind(kelf) != ELF_K_ELF) {
+               error = 1;
+               goto bad;
+       }
+
+       error = kelf_parse();
+       if (error)
+               goto bad;
+
+       return 0;
+
+bad:
+       kelf_close();
+       return error;
+}
+
+void
+kelf_close(void)
+{
+       elf_end(kelf);
+       kelf = NULL;
+       close(kfd);
+       kfd = -1;
+}
+
+int
+kelf_snprintsym(char *str, size_t size, unsigned long pc)
+{
+       GElf_Sym         sym;
+       Elf_Data        *data = NULL;
+       Elf_Addr         offset, bestoff = 0;
+       size_t           i, bestidx = 0;
+       char            *name;
+       int              cnt;
+
+       data = elf_rawdata(ksymtab, data);
+       if (data == NULL)
+               goto fallback;
+
+       for (i = 0; i < knsymb; i++) {
+               if (gelf_getsym(data, i, &sym) == NULL)
+                       continue;
+               if (GELF_ST_TYPE(sym.st_info) != STT_FUNC)
+                       continue;
+               if (pc >= sym.st_value) {
+                       if (pc < (sym.st_value + sym.st_size))
+                               break;
+                       /* Workaround for symbols w/o size, usually asm ones. */
+                       if (sym.st_size == 0 && sym.st_value > bestoff) {
+                               bestidx = i;
+                               bestoff = sym.st_value;
+                       }
+               }
+       }
+
+       if (i == knsymb) {
+               if (bestidx == 0 || gelf_getsym(data, bestidx, &sym) == NULL)
+                       goto fallback;
+       }
+
+       name = elf_strptr(kelf, kstrtabndx, sym.st_name);
+       if (name != NULL)
+               cnt = snprintf(str, size, "%s", name);
+       else
+               cnt = snprintf(str, size, "0x%llx", sym.st_value);
+
+       offset = pc - sym.st_value;
+       if (offset != 0)
+               cnt += snprintf(str + cnt, size - cnt, "+0x%llx\n", offset);
+       else
+               cnt += snprintf(str + cnt, size - cnt, "\n");
+
+       return cnt;
+
+fallback:
+       return snprintf(str, size, "0x%lx\n", pc);
+}
+
+int
+kelf_parse(void)
+{
+       GElf_Shdr        shdr;
+       Elf_Scn         *scn, *scnctf;
+       char            *name;
+       size_t           shstrndx;
+
+       if (elf_getshdrstrndx(kelf, &shstrndx) != 0) {
+               warnx("elf_getshdrstrndx: %s", elf_errmsg(-1));
+               return 1;
+       }
+
+       scn = scnctf = NULL;
+       while ((scn = elf_nextscn(kelf, scn)) != NULL) {
+               if (gelf_getshdr(scn, &shdr) != &shdr) {
+                       warnx("elf_getshdr: %s", elf_errmsg(-1));
+                       return 1;
+               }
+
+               if ((name = elf_strptr(kelf, shstrndx, shdr.sh_name)) == NULL) {
+                       warnx("elf_strptr: %s", elf_errmsg(-1));
+                       return 1;
+               }
+
+               if (strcmp(name, ELF_SYMTAB) == 0 &&
+                   shdr.sh_type == SHT_SYMTAB && shdr.sh_entsize != 0) {
+                       ksymtab = scn;
+                       knsymb = shdr.sh_size / shdr.sh_entsize;
+               }
+
+               if (strcmp(name, ELF_STRTAB) == 0 &&
+                   shdr.sh_type == SHT_STRTAB) {
+                       kstrtabndx = elf_ndxscn(scn);
+               }
+       }
+
+       if (ksymtab == NULL)
+               warnx("symbol table not found");
+
+       return 0;
+}
diff --git usr.sbin/btrace/map.c usr.sbin/btrace/map.c
new file mode 100644
index 00000000000..1a546523a15
--- /dev/null
+++ usr.sbin/btrace/map.c
@@ -0,0 +1,169 @@
+/*     $OpenBSD$ */
+
+/*
+ * Copyright (c) 2020 Martin Pieuchot <m...@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Associative array implemented with RB-Tree.
+ */
+
+#include <sys/queue.h>
+#include <sys/tree.h>
+
+#include <assert.h>
+#include <err.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "bt_parser.h"
+#include "btrace.h"
+
+RB_HEAD(mtree, mentry);
+
+#define        KLEN 64
+
+struct mentry {
+       RB_ENTRY(mentry)         mlink;
+       char                     mkey[KLEN];
+       struct bt_arg           *mval;
+};
+
+int    mcmp(struct mentry *, struct mentry *);
+
+RB_GENERATE(mtree, mentry, mlink, mcmp);
+
+int
+mcmp(struct mentry *me0, struct mentry *me1)
+{
+       return strncmp(me0->mkey, me1->mkey, KLEN - 1);
+}
+
+void
+map_clear(struct bt_var *bv)
+{
+       struct mtree *map = (struct mtree *)bv->bv_value;
+       struct mentry *mep;
+
+       while ((mep = RB_MIN(mtree, map)) != NULL) {
+               RB_REMOVE(mtree, map, mep);
+               free(mep);
+       }
+
+       assert(RB_EMPTY(map));
+       free(map);
+
+       bv->bv_value = NULL;
+}
+
+void
+map_delete(struct bt_var *bv, const char *key)
+{
+       struct mtree *map = (struct mtree *)bv->bv_value;
+       struct mentry me, *mep;
+
+       strlcpy(me.mkey, key, KLEN);
+       mep = RB_REMOVE(mtree, map, &me);
+       free(mep);
+}
+
+void
+map_insert(struct bt_var *bv, const char *key, struct bt_arg *bval)
+{
+       struct mtree *map = (struct mtree *)bv->bv_value;
+       struct mentry me, *mep;
+       long val;
+
+       if (map == NULL) {
+               map = calloc(1, sizeof(struct mtree));
+               if (map == NULL)
+                       err(1, "mtree: calloc");
+               bv->bv_value = (struct bt_arg *)map;
+       }
+
+       strlcpy(me.mkey, key, KLEN);
+       mep = RB_FIND(mtree, map, &me);
+       if (mep == NULL) {
+               mep = calloc(1, sizeof(struct mentry));
+               if (mep == NULL)
+                       err(1, "mentry: calloc");
+
+               strlcpy(mep->mkey, key, KLEN);
+               RB_INSERT(mtree, map, mep);
+       }
+
+       switch (bval->ba_type) {
+       case B_AT_MF_COUNT:
+               if (mep->mval == NULL)
+                       mep->mval = ba_new(0, B_AT_LONG);
+               val = (long)mep->mval->ba_value;
+               val++;
+               mep->mval->ba_value = (void *)val;
+               break;
+       case B_AT_STR:
+       case B_AT_LONG:
+               mep->mval = bval;
+               break;
+       default:
+               errx(1, "no insert support for type %d", bval->ba_type);
+       }
+}
+
+/* Print at most `top' entries of the map ordered by value. */
+void
+map_print(struct bt_var *bv, size_t top)
+{
+       static struct bt_arg nullba = { { NULL }, (void *)0, B_AT_LONG };
+       static struct bt_arg maxba = { { NULL }, (void *)LONG_MAX, B_AT_LONG };
+       struct mtree *map = (void *)bv->bv_value;
+       struct mentry *mep, *mcur;
+       struct bt_arg *bhigh, *bprev;
+       size_t i;
+
+       if (map == NULL)
+               return;
+
+       bprev = &maxba;
+       for (i = 0; i < top; i++) {
+               mcur = NULL;
+               bhigh = &nullba;
+               RB_FOREACH(mep, mtree, map) {
+                       if (bacmp(mep->mval, bhigh) >= 0 &&
+                           bacmp(mep->mval, bprev) < 0 &&
+                           mep->mval != bprev) {
+                               mcur = mep;
+                               bhigh = mcur->mval;
+                       }
+               }
+               if (mcur == NULL)
+                       break;
+               printf("@map[%s]: %s\n", mcur->mkey, ba2str(mcur->mval, NULL));
+               bprev = mcur->mval;
+       }
+}
+
+void
+map_zero(struct bt_var *bv)
+{
+       struct mtree *map = (struct mtree *)bv->bv_value;
+       struct mentry *mep;
+
+       RB_FOREACH(mep, mtree, map) {
+               mep->mval->ba_value = 0;
+               mep->mval->ba_type = B_AT_LONG;
+       }
+}
diff --git usr.sbin/btrace/printf.c usr.sbin/btrace/printf.c
new file mode 100644
index 00000000000..357613bbef0
--- /dev/null
+++ usr.sbin/btrace/printf.c
@@ -0,0 +1,524 @@
+/*     $OpenBSD: printf.c,v 1.26 2016/11/18 15:53:16 schwarze Exp $    */
+
+/*
+ * Copyright (c) 1989 The Regents of the University of California.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the University nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/queue.h>
+
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <dev/dt/dtvar.h>
+#include "bt_parser.h"
+
+#include "btrace.h"
+
+static int      print_escape_str(const char *);
+static int      print_escape(const char *);
+
+static int      getchr(void);
+static double   getdouble(void);
+static int      getint(void);
+static long     getlong(void);
+static unsigned long getulong(void);
+static const char *getstr(void);
+static char    *mklong(const char *, int); 
+static void      check_conversion(const char *, const char *);
+
+static int              rval;
+static struct bt_arg   *gargv;
+static struct dt_evt   *gdevt;
+
+#define isodigit(c)    ((c) >= '0' && (c) <= '7')
+#define octtobin(c)    ((c) - '0')
+#define hextobin(c)    ((c) >= 'A' && (c) <= 'F' ? c - 'A' + 10 : (c) >= 'a' 
&& (c) <= 'f' ? c - 'a' + 10 : c - '0')
+
+#define PF(f, func) { \
+       if (havefieldwidth) \
+               if (haveprecision) \
+                       (void)printf(f, fieldwidth, precision, func); \
+               else \
+                       (void)printf(f, fieldwidth, func); \
+       else if (haveprecision) \
+               (void)printf(f, precision, func); \
+       else \
+               (void)printf(f, func); \
+}
+
+int
+stmt_printf(struct bt_stmt *bs, struct dt_evt *des)
+{
+       struct bt_arg *ba = SLIST_FIRST(&bs->bs_args);
+       char *fmt, *start;
+       int havefieldwidth, haveprecision;
+       int fieldwidth, precision;
+       char convch, nextch;
+       char *format;
+
+       format = (char *)ba2str(ba, gdevt); // XXX modification?
+       gargv = SLIST_NEXT(ba, ba_next);
+       gdevt = des;
+
+#define SKIP1  "#-+ 0"
+#define SKIP2  "0123456789"
+       do {
+               /*
+                * Basic algorithm is to scan the format string for conversion
+                * specifications -- once one is found, find out if the field
+                * width or precision is a '*'; if it is, gather up value. 
+                * Note, format strings are reused as necessary to use up the
+                * provided arguments, arguments of zero/null string are 
+                * provided to use up the format string.
+                */
+
+               /* find next format specification */
+               for (fmt = format; *fmt; fmt++) {
+                       switch (*fmt) {
+                       case '%':
+                               start = fmt++;
+
+                               if (*fmt == '%') {
+                                       putchar ('%');
+                                       break;
+                               } else if (*fmt == 'b') {
+                                       const char *p = getstr();
+                                       if (print_escape_str(p)) {
+                                               return (rval);
+                                       }
+                                       break;
+                               }
+
+                               /* skip to field width */
+                               for (; strchr(SKIP1, *fmt); ++fmt)
+                                       ;
+                               if (*fmt == '*') {
+                                       ++fmt;
+                                       havefieldwidth = 1;
+                                       fieldwidth = getint();
+                               } else
+                                       havefieldwidth = 0;
+
+                               /* skip to field precision */
+                               for (; strchr(SKIP2, *fmt); ++fmt)
+                                       ;
+                               haveprecision = 0;
+                               if (*fmt == '.') {
+                                       ++fmt;
+                                       if (*fmt == '*') {
+                                               ++fmt;
+                                               haveprecision = 1;
+                                               precision = getint();
+                                       }
+                                       for (; strchr(SKIP2, *fmt); ++fmt)
+                                               ;
+                               }
+
+                               if (!*fmt) {
+                                       warnx ("missing format character");
+                                       return(1);
+                               }
+
+                               convch = *fmt;
+                               nextch = *(fmt + 1);
+                               *(fmt + 1) = '\0';
+                               switch(convch) {
+                               case 'c': {
+                                       char p = getchr();
+                                       PF(start, p);
+                                       break;
+                               }
+                               case 's': {
+                                       const char *p = getstr();
+                                       PF(start, p);
+                                       break;
+                               }
+                               case 'd':
+                               case 'i': {
+                                       long p;
+                                       char *f = mklong(start, convch);
+                                       if (!f) {
+                                               warnx("out of memory");
+                                               return (1);
+                                       }
+                                       p = getlong();
+                                       PF(f, p);
+                                       break;
+                               }
+                               case 'o':
+                               case 'u':
+                               case 'x':
+                               case 'X': {
+                                       unsigned long p;
+                                       char *f = mklong(start, convch);
+                                       if (!f) {
+                                               warnx("out of memory");
+                                               return (1);
+                                       }
+                                       p = getulong();
+                                       PF(f, p);
+                                       break;
+                               }
+                               case 'a':
+                               case 'A':
+                               case 'e':
+                               case 'E':
+                               case 'f':
+                               case 'F':
+                               case 'g':
+                               case 'G': {
+                                       double p = getdouble();
+                                       PF(start, p);
+                                       break;
+                               }
+                               default:
+                                       warnx ("%s: invalid directive", start);
+                                       return(1);
+                               }
+                               *(fmt + 1) = nextch;
+                               break;
+
+                       case '\\':
+                               fmt += print_escape(fmt);
+                               break;
+
+                       default:
+                               putchar (*fmt);
+                               break;
+                       }
+               }
+       } while (gargv != NULL);
+
+       return (rval);
+}
+
+
+/*
+ * Print SysV echo(1) style escape string 
+ *     Halts processing string and returns 1 if a \c escape is encountered.
+ */
+static int
+print_escape_str(const char *str)
+{
+       int value;
+       int c;
+
+       while (*str) {
+               if (*str == '\\') {
+                       str++;
+                       /* 
+                        * %b string octal constants are not like those in C.
+                        * They start with a \0, and are followed by 0, 1, 2, 
+                        * or 3 octal digits. 
+                        */
+                       if (*str == '0') {
+                               str++;
+                               for (c = 3, value = 0; c-- && isodigit(*str); 
str++) {
+                                       value <<= 3;
+                                       value += octtobin(*str);
+                               }
+                               putchar (value);
+                               str--;
+                       } else if (*str == 'c') {
+                               return 1;
+                       } else {
+                               str--;
+                               str += print_escape(str);
+                       }
+               } else {
+                       putchar (*str);
+               }
+               str++;
+       }
+
+       return 0;
+}
+
+/*
+ * Print "standard" escape characters 
+ */
+static int
+print_escape(const char *str)
+{
+       const char *start = str;
+       int value;
+       int c;
+
+       str++;
+
+       switch (*str) {
+       case '0': case '1': case '2': case '3':
+       case '4': case '5': case '6': case '7':
+               for (c = 3, value = 0; c-- && isodigit(*str); str++) {
+                       value <<= 3;
+                       value += octtobin(*str);
+               }
+               putchar(value);
+               return str - start - 1;
+               /* NOTREACHED */
+
+       case 'x':
+               str++;
+               for (value = 0; isxdigit((unsigned char)*str); str++) {
+                       value <<= 4;
+                       value += hextobin(*str);
+               }
+               if (value > UCHAR_MAX) {
+                       warnx ("escape sequence out of range for character");
+                       rval = 1;
+               }
+               putchar (value);
+               return str - start - 1;
+               /* NOTREACHED */
+
+       case '\\':                      /* backslash */
+               putchar('\\');
+               break;
+
+       case '\'':                      /* single quote */
+               putchar('\'');
+               break;
+
+       case '"':                       /* double quote */
+               putchar('"');
+               break;
+
+       case 'a':                       /* alert */
+               putchar('\a');
+               break;
+
+       case 'b':                       /* backspace */
+               putchar('\b');
+               break;
+
+       case 'e':                       /* escape */
+#ifdef __GNUC__
+               putchar('\e');
+#else
+               putchar(033);
+#endif
+               break;
+
+       case 'f':                       /* form-feed */
+               putchar('\f');
+               break;
+
+       case 'n':                       /* newline */
+               putchar('\n');
+               break;
+
+       case 'r':                       /* carriage-return */
+               putchar('\r');
+               break;
+
+       case 't':                       /* tab */
+               putchar('\t');
+               break;
+
+       case 'v':                       /* vertical-tab */
+               putchar('\v');
+               break;
+
+       case '\0':
+               warnx("null escape sequence");
+               rval = 1;
+               return 0;
+
+       default:
+               putchar(*str);
+               warnx("unknown escape sequence `\\%c'", *str);
+               rval = 1;
+       }
+
+       return 1;
+}
+
+static char *
+mklong(const char *str, int ch)
+{
+       static char *copy;
+       static int copysize;
+       int len;
+
+       len = strlen(str) + 2;
+       if (copysize < len) {
+               char *newcopy;
+               copysize = len + 256;
+
+               newcopy = realloc(copy, copysize);
+               if (newcopy == NULL) {
+                       copysize = 0;
+                       free(copy);
+                       copy = NULL;
+                       return (NULL);
+               }
+               copy = newcopy;
+       }
+       (void) memmove(copy, str, len - 3);
+       copy[len - 3] = 'l';
+       copy[len - 2] = ch;
+       copy[len - 1] = '\0';
+       return (copy);
+}
+
+static int
+getchr(void)
+{
+       int c;
+
+       if (gargv == NULL)
+               return((int)'\0');
+
+       c = (int)*ba2str(gargv, gdevt);
+       gargv = SLIST_NEXT(gargv, ba_next);
+       return c;
+}
+
+static const char *
+getstr(void)
+{
+       const char *str;
+
+       if (gargv == NULL)
+               return "";
+
+       str = ba2str(gargv, gdevt);
+       gargv = SLIST_NEXT(gargv, ba_next);
+       return str;
+}
+
+static char *number = "+-.0123456789";
+static int
+getint(void)
+{
+       const char *str;
+
+       if (gargv == NULL)
+               return 0;
+
+       str = ba2str(gargv, gdevt);
+       if (strchr(number, *str)) {
+               gargv = SLIST_NEXT(gargv, ba_next);
+               return atoi(str);
+       }
+
+       return 0;
+}
+
+static long
+getlong(void)
+{
+       const char *str;
+       long val;
+       char *ep;
+
+       if (gargv == NULL)
+               return 0UL;
+
+       str = ba2str(gargv, gdevt);
+       gargv = SLIST_NEXT(gargv, ba_next);
+
+       if (*str == '\"' || *str == '\'') {
+               unsigned char c = (unsigned char)str[1];
+               return c;
+       }
+
+       errno = 0;
+       val = strtol(str, &ep, 0);
+       check_conversion(str, ep);
+       return val;
+}
+
+static unsigned long
+getulong(void)
+{
+       const char *str;
+       unsigned long val;
+       char *ep;
+
+       if (gargv == NULL)
+               return 0UL;
+
+       str = ba2str(gargv, gdevt);
+       gargv = SLIST_NEXT(gargv, ba_next);
+
+       if (*str == '\"' || *str == '\'') {
+               unsigned char c = (unsigned char)str[1];
+               return c;
+       }
+
+       errno = 0;
+       val = strtoul(str, &ep, 0);
+       check_conversion(str, ep);
+       return val;
+}
+
+static double
+getdouble(void)
+{
+       const char *str;
+       double val;
+       char *ep;
+
+       if (gargv == NULL)
+               return 0.0;
+
+       str = ba2str(gargv, gdevt);
+       gargv = SLIST_NEXT(gargv, ba_next);
+
+       if (*str == '\"' || *str == '\'') {
+               unsigned char c = (unsigned char)str[1];
+               return c;
+       }
+
+       errno = 0;
+       val = strtod(str, &ep);
+       check_conversion(str, ep);
+       return val;
+}
+
+static void
+check_conversion(const char *s, const char *ep)
+{
+       if (*ep) {
+               if (ep == s)
+                       warnx ("%s: expected numeric value", s);
+               else
+                       warnx ("%s: not completely converted", s);
+               rval = 1;
+       } else if (errno == ERANGE) {
+               warnc(ERANGE, "%s", s);
+               rval = 1;
+       }
+}
diff --git usr.sbin/btrace/syscalls.c usr.sbin/btrace/syscalls.c
new file mode 100644
index 00000000000..47b867e4a7e
--- /dev/null
+++ usr.sbin/btrace/syscalls.c
@@ -0,0 +1,396 @@
+/*     $OpenBSD: syscalls.c,v 1.208 2019/11/27 01:13:04 deraadt Exp $  */
+
+/*
+ * System call names.
+ *
+ * DO NOT EDIT-- this file is automatically generated.
+ * created from;       OpenBSD: syscalls.master,v 1.198 2019/11/27 01:04:13 
deraadt Exp 
+ */
+
+char *syscallnames[] = {
+       "syscall",                      /* 0 = syscall */
+       "exit",                 /* 1 = exit */
+       "fork",                 /* 2 = fork */
+       "read",                 /* 3 = read */
+       "write",                        /* 4 = write */
+       "open",                 /* 5 = open */
+       "close",                        /* 6 = close */
+       "getentropy",                   /* 7 = getentropy */
+       "__tfork",                      /* 8 = __tfork */
+       "link",                 /* 9 = link */
+       "unlink",                       /* 10 = unlink */
+       "wait4",                        /* 11 = wait4 */
+       "chdir",                        /* 12 = chdir */
+       "fchdir",                       /* 13 = fchdir */
+       "mknod",                        /* 14 = mknod */
+       "chmod",                        /* 15 = chmod */
+       "chown",                        /* 16 = chown */
+       "break",                        /* 17 = break */
+       "getdtablecount",                       /* 18 = getdtablecount */
+       "getrusage",                    /* 19 = getrusage */
+       "getpid",                       /* 20 = getpid */
+       "mount",                        /* 21 = mount */
+       "unmount",                      /* 22 = unmount */
+       "setuid",                       /* 23 = setuid */
+       "getuid",                       /* 24 = getuid */
+       "geteuid",                      /* 25 = geteuid */
+#ifdef PTRACE
+       "ptrace",                       /* 26 = ptrace */
+#else
+       "#26 (unimplemented ptrace)",           /* 26 = unimplemented ptrace */
+#endif
+       "recvmsg",                      /* 27 = recvmsg */
+       "sendmsg",                      /* 28 = sendmsg */
+       "recvfrom",                     /* 29 = recvfrom */
+       "accept",                       /* 30 = accept */
+       "getpeername",                  /* 31 = getpeername */
+       "getsockname",                  /* 32 = getsockname */
+       "access",                       /* 33 = access */
+       "chflags",                      /* 34 = chflags */
+       "fchflags",                     /* 35 = fchflags */
+       "sync",                 /* 36 = sync */
+       "msyscall",                     /* 37 = msyscall */
+       "stat",                 /* 38 = stat */
+       "getppid",                      /* 39 = getppid */
+       "lstat",                        /* 40 = lstat */
+       "dup",                  /* 41 = dup */
+       "fstatat",                      /* 42 = fstatat */
+       "getegid",                      /* 43 = getegid */
+       "profil",                       /* 44 = profil */
+#ifdef KTRACE
+       "ktrace",                       /* 45 = ktrace */
+#else
+       "#45 (unimplemented ktrace)",           /* 45 = unimplemented ktrace */
+#endif
+       "sigaction",                    /* 46 = sigaction */
+       "getgid",                       /* 47 = getgid */
+       "sigprocmask",                  /* 48 = sigprocmask */
+       "#49 (obsolete ogetlogin)",             /* 49 = obsolete ogetlogin */
+       "setlogin",                     /* 50 = setlogin */
+#ifdef ACCOUNTING
+       "acct",                 /* 51 = acct */
+#else
+       "#51 (unimplemented acct)",             /* 51 = unimplemented acct */
+#endif
+       "sigpending",                   /* 52 = sigpending */
+       "fstat",                        /* 53 = fstat */
+       "ioctl",                        /* 54 = ioctl */
+       "reboot",                       /* 55 = reboot */
+       "revoke",                       /* 56 = revoke */
+       "symlink",                      /* 57 = symlink */
+       "readlink",                     /* 58 = readlink */
+       "execve",                       /* 59 = execve */
+       "umask",                        /* 60 = umask */
+       "chroot",                       /* 61 = chroot */
+       "getfsstat",                    /* 62 = getfsstat */
+       "statfs",                       /* 63 = statfs */
+       "fstatfs",                      /* 64 = fstatfs */
+       "fhstatfs",                     /* 65 = fhstatfs */
+       "vfork",                        /* 66 = vfork */
+       "gettimeofday",                 /* 67 = gettimeofday */
+       "settimeofday",                 /* 68 = settimeofday */
+       "setitimer",                    /* 69 = setitimer */
+       "getitimer",                    /* 70 = getitimer */
+       "select",                       /* 71 = select */
+       "kevent",                       /* 72 = kevent */
+       "munmap",                       /* 73 = munmap */
+       "mprotect",                     /* 74 = mprotect */
+       "madvise",                      /* 75 = madvise */
+       "utimes",                       /* 76 = utimes */
+       "futimes",                      /* 77 = futimes */
+       "#78 (unimplemented mincore)",          /* 78 = unimplemented mincore */
+       "getgroups",                    /* 79 = getgroups */
+       "setgroups",                    /* 80 = setgroups */
+       "getpgrp",                      /* 81 = getpgrp */
+       "setpgid",                      /* 82 = setpgid */
+       "futex",                        /* 83 = futex */
+       "utimensat",                    /* 84 = utimensat */
+       "futimens",                     /* 85 = futimens */
+       "kbind",                        /* 86 = kbind */
+       "clock_gettime",                        /* 87 = clock_gettime */
+       "clock_settime",                        /* 88 = clock_settime */
+       "clock_getres",                 /* 89 = clock_getres */
+       "dup2",                 /* 90 = dup2 */
+       "nanosleep",                    /* 91 = nanosleep */
+       "fcntl",                        /* 92 = fcntl */
+       "accept4",                      /* 93 = accept4 */
+       "__thrsleep",                   /* 94 = __thrsleep */
+       "fsync",                        /* 95 = fsync */
+       "setpriority",                  /* 96 = setpriority */
+       "socket",                       /* 97 = socket */
+       "connect",                      /* 98 = connect */
+       "getdents",                     /* 99 = getdents */
+       "getpriority",                  /* 100 = getpriority */
+       "pipe2",                        /* 101 = pipe2 */
+       "dup3",                 /* 102 = dup3 */
+       "sigreturn",                    /* 103 = sigreturn */
+       "bind",                 /* 104 = bind */
+       "setsockopt",                   /* 105 = setsockopt */
+       "listen",                       /* 106 = listen */
+       "chflagsat",                    /* 107 = chflagsat */
+       "pledge",                       /* 108 = pledge */
+       "ppoll",                        /* 109 = ppoll */
+       "pselect",                      /* 110 = pselect */
+       "sigsuspend",                   /* 111 = sigsuspend */
+       "sendsyslog",                   /* 112 = sendsyslog */
+       "#113 (unimplemented fktrace)",         /* 113 = unimplemented fktrace 
*/
+       "unveil",                       /* 114 = unveil */
+       "__realpath",                   /* 115 = __realpath */
+       "#116 (obsolete t32_gettimeofday)",             /* 116 = obsolete 
t32_gettimeofday */
+       "#117 (obsolete t32_getrusage)",                /* 117 = obsolete 
t32_getrusage */
+       "getsockopt",                   /* 118 = getsockopt */
+       "thrkill",                      /* 119 = thrkill */
+       "readv",                        /* 120 = readv */
+       "writev",                       /* 121 = writev */
+       "kill",                 /* 122 = kill */
+       "fchown",                       /* 123 = fchown */
+       "fchmod",                       /* 124 = fchmod */
+       "#125 (obsolete orecvfrom)",            /* 125 = obsolete orecvfrom */
+       "setreuid",                     /* 126 = setreuid */
+       "setregid",                     /* 127 = setregid */
+       "rename",                       /* 128 = rename */
+       "#129 (obsolete otruncate)",            /* 129 = obsolete otruncate */
+       "#130 (obsolete oftruncate)",           /* 130 = obsolete oftruncate */
+       "flock",                        /* 131 = flock */
+       "mkfifo",                       /* 132 = mkfifo */
+       "sendto",                       /* 133 = sendto */
+       "shutdown",                     /* 134 = shutdown */
+       "socketpair",                   /* 135 = socketpair */
+       "mkdir",                        /* 136 = mkdir */
+       "rmdir",                        /* 137 = rmdir */
+       "#138 (obsolete t32_utimes)",           /* 138 = obsolete t32_utimes */
+       "#139 (obsolete 4.2 sigreturn)",                /* 139 = obsolete 4.2 
sigreturn */
+       "adjtime",                      /* 140 = adjtime */
+       "getlogin_r",                   /* 141 = getlogin_r */
+       "#142 (obsolete ogethostid)",           /* 142 = obsolete ogethostid */
+       "#143 (obsolete osethostid)",           /* 143 = obsolete osethostid */
+       "#144 (obsolete ogetrlimit)",           /* 144 = obsolete ogetrlimit */
+       "#145 (obsolete osetrlimit)",           /* 145 = obsolete osetrlimit */
+       "#146 (obsolete okillpg)",              /* 146 = obsolete okillpg */
+       "setsid",                       /* 147 = setsid */
+       "quotactl",                     /* 148 = quotactl */
+       "#149 (obsolete oquota)",               /* 149 = obsolete oquota */
+       "#150 (obsolete ogetsockname)",         /* 150 = obsolete ogetsockname 
*/
+       "#151 (unimplemented)",         /* 151 = unimplemented */
+       "#152 (unimplemented)",         /* 152 = unimplemented */
+       "#153 (unimplemented)",         /* 153 = unimplemented */
+       "#154 (unimplemented)",         /* 154 = unimplemented */
+#if defined(NFSCLIENT) || defined(NFSSERVER)
+       "nfssvc",                       /* 155 = nfssvc */
+#else
+       "#155 (unimplemented)",         /* 155 = unimplemented */
+#endif
+       "#156 (obsolete ogetdirentries)",               /* 156 = obsolete 
ogetdirentries */
+       "#157 (obsolete statfs25)",             /* 157 = obsolete statfs25 */
+       "#158 (obsolete fstatfs25)",            /* 158 = obsolete fstatfs25 */
+       "#159 (unimplemented)",         /* 159 = unimplemented */
+       "#160 (unimplemented)",         /* 160 = unimplemented */
+       "getfh",                        /* 161 = getfh */
+       "#162 (obsolete ogetdomainname)",               /* 162 = obsolete 
ogetdomainname */
+       "#163 (obsolete osetdomainname)",               /* 163 = obsolete 
osetdomainname */
+       "#164 (unimplemented ouname)",          /* 164 = unimplemented ouname */
+       "sysarch",                      /* 165 = sysarch */
+       "#166 (unimplemented)",         /* 166 = unimplemented */
+       "#167 (unimplemented)",         /* 167 = unimplemented */
+       "#168 (unimplemented)",         /* 168 = unimplemented */
+       "#169 (obsolete semsys10)",             /* 169 = obsolete semsys10 */
+       "#170 (obsolete msgsys10)",             /* 170 = obsolete msgsys10 */
+       "#171 (obsolete shmsys10)",             /* 171 = obsolete shmsys10 */
+       "#172 (unimplemented)",         /* 172 = unimplemented */
+       "pread",                        /* 173 = pread */
+       "pwrite",                       /* 174 = pwrite */
+       "#175 (unimplemented ntp_gettime)",             /* 175 = unimplemented 
ntp_gettime */
+       "#176 (unimplemented ntp_adjtime)",             /* 176 = unimplemented 
ntp_adjtime */
+       "#177 (unimplemented)",         /* 177 = unimplemented */
+       "#178 (unimplemented)",         /* 178 = unimplemented */
+       "#179 (unimplemented)",         /* 179 = unimplemented */
+       "#180 (unimplemented)",         /* 180 = unimplemented */
+       "setgid",                       /* 181 = setgid */
+       "setegid",                      /* 182 = setegid */
+       "seteuid",                      /* 183 = seteuid */
+       "#184 (obsolete lfs_bmapv)",            /* 184 = obsolete lfs_bmapv */
+       "#185 (obsolete lfs_markv)",            /* 185 = obsolete lfs_markv */
+       "#186 (obsolete lfs_segclean)",         /* 186 = obsolete lfs_segclean 
*/
+       "#187 (obsolete lfs_segwait)",          /* 187 = obsolete lfs_segwait */
+       "#188 (obsolete stat35)",               /* 188 = obsolete stat35 */
+       "#189 (obsolete fstat35)",              /* 189 = obsolete fstat35 */
+       "#190 (obsolete lstat35)",              /* 190 = obsolete lstat35 */
+       "pathconf",                     /* 191 = pathconf */
+       "fpathconf",                    /* 192 = fpathconf */
+       "swapctl",                      /* 193 = swapctl */
+       "getrlimit",                    /* 194 = getrlimit */
+       "setrlimit",                    /* 195 = setrlimit */
+       "#196 (obsolete ogetdirentries48)",             /* 196 = obsolete 
ogetdirentries48 */
+       "mmap",                 /* 197 = mmap */
+       "__syscall",                    /* 198 = __syscall */
+       "lseek",                        /* 199 = lseek */
+       "truncate",                     /* 200 = truncate */
+       "ftruncate",                    /* 201 = ftruncate */
+       "sysctl",                       /* 202 = sysctl */
+       "mlock",                        /* 203 = mlock */
+       "munlock",                      /* 204 = munlock */
+       "#205 (unimplemented sys_undelete)",            /* 205 = unimplemented 
sys_undelete */
+       "#206 (obsolete t32_futimes)",          /* 206 = obsolete t32_futimes */
+       "getpgid",                      /* 207 = getpgid */
+       "#208 (obsolete nnpfspioctl)",          /* 208 = obsolete nnpfspioctl */
+       "utrace",                       /* 209 = utrace */
+       "#210 (unimplemented)",         /* 210 = unimplemented */
+       "#211 (unimplemented)",         /* 211 = unimplemented */
+       "#212 (unimplemented)",         /* 212 = unimplemented */
+       "#213 (unimplemented)",         /* 213 = unimplemented */
+       "#214 (unimplemented)",         /* 214 = unimplemented */
+       "#215 (unimplemented)",         /* 215 = unimplemented */
+       "#216 (unimplemented)",         /* 216 = unimplemented */
+       "#217 (unimplemented)",         /* 217 = unimplemented */
+       "#218 (unimplemented)",         /* 218 = unimplemented */
+       "#219 (unimplemented)",         /* 219 = unimplemented */
+#ifdef SYSVSEM
+       "#220 (unimplemented)",         /* 220 = unimplemented */
+       "semget",                       /* 221 = semget */
+#else
+       "#220 (unimplemented semctl)",          /* 220 = unimplemented semctl */
+       "#221 (unimplemented semget)",          /* 221 = unimplemented semget */
+#endif
+       "#222 (obsolete semop35)",              /* 222 = obsolete semop35 */
+       "#223 (obsolete semconfig35)",          /* 223 = obsolete semconfig35 */
+#ifdef SYSVMSG
+       "#224 (unimplemented)",         /* 224 = unimplemented */
+       "msgget",                       /* 225 = msgget */
+       "msgsnd",                       /* 226 = msgsnd */
+       "msgrcv",                       /* 227 = msgrcv */
+#else
+       "#224 (unimplemented msgctl)",          /* 224 = unimplemented msgctl */
+       "#225 (unimplemented msgget)",          /* 225 = unimplemented msgget */
+       "#226 (unimplemented msgsnd)",          /* 226 = unimplemented msgsnd */
+       "#227 (unimplemented msgrcv)",          /* 227 = unimplemented msgrcv */
+#endif
+#ifdef SYSVSHM
+       "shmat",                        /* 228 = shmat */
+       "#229 (unimplemented)",         /* 229 = unimplemented */
+       "shmdt",                        /* 230 = shmdt */
+#else
+       "#228 (unimplemented shmat)",           /* 228 = unimplemented shmat */
+       "#229 (unimplemented shmctl)",          /* 229 = unimplemented shmctl */
+       "#230 (unimplemented shmdt)",           /* 230 = unimplemented shmdt */
+#endif
+       "#231 (obsolete shmget35)",             /* 231 = obsolete shmget35 */
+       "#232 (obsolete t32_clock_gettime)",            /* 232 = obsolete 
t32_clock_gettime */
+       "#233 (obsolete t32_clock_settime)",            /* 233 = obsolete 
t32_clock_settime */
+       "#234 (obsolete t32_clock_getres)",             /* 234 = obsolete 
t32_clock_getres */
+       "#235 (unimplemented timer_create)",            /* 235 = unimplemented 
timer_create */
+       "#236 (unimplemented timer_delete)",            /* 236 = unimplemented 
timer_delete */
+       "#237 (unimplemented timer_settime)",           /* 237 = unimplemented 
timer_settime */
+       "#238 (unimplemented timer_gettime)",           /* 238 = unimplemented 
timer_gettime */
+       "#239 (unimplemented timer_getoverrun)",                /* 239 = 
unimplemented timer_getoverrun */
+       "#240 (obsolete t32_nanosleep)",                /* 240 = obsolete 
t32_nanosleep */
+       "#241 (unimplemented)",         /* 241 = unimplemented */
+       "#242 (unimplemented)",         /* 242 = unimplemented */
+       "#243 (unimplemented)",         /* 243 = unimplemented */
+       "#244 (unimplemented)",         /* 244 = unimplemented */
+       "#245 (unimplemented)",         /* 245 = unimplemented */
+       "#246 (unimplemented)",         /* 246 = unimplemented */
+       "#247 (unimplemented)",         /* 247 = unimplemented */
+       "#248 (unimplemented)",         /* 248 = unimplemented */
+       "#249 (unimplemented)",         /* 249 = unimplemented */
+       "minherit",                     /* 250 = minherit */
+       "#251 (obsolete rfork)",                /* 251 = obsolete rfork */
+       "poll",                 /* 252 = poll */
+       "issetugid",                    /* 253 = issetugid */
+       "lchown",                       /* 254 = lchown */
+       "getsid",                       /* 255 = getsid */
+       "msync",                        /* 256 = msync */
+       "#257 (obsolete semctl35)",             /* 257 = obsolete semctl35 */
+       "#258 (obsolete shmctl35)",             /* 258 = obsolete shmctl35 */
+       "#259 (obsolete msgctl35)",             /* 259 = obsolete msgctl35 */
+       "#260 (unimplemented)",         /* 260 = unimplemented */
+       "#261 (unimplemented)",         /* 261 = unimplemented */
+       "#262 (unimplemented)",         /* 262 = unimplemented */
+       "pipe",                 /* 263 = pipe */
+       "fhopen",                       /* 264 = fhopen */
+       "#265 (unimplemented)",         /* 265 = unimplemented */
+       "#266 (unimplemented)",         /* 266 = unimplemented */
+       "preadv",                       /* 267 = preadv */
+       "pwritev",                      /* 268 = pwritev */
+       "kqueue",                       /* 269 = kqueue */
+       "#270 (obsolete t32_kevent)",           /* 270 = obsolete t32_kevent */
+       "mlockall",                     /* 271 = mlockall */
+       "munlockall",                   /* 272 = munlockall */
+       "#273 (unimplemented sys_getpeereid)",          /* 273 = unimplemented 
sys_getpeereid */
+       "#274 (unimplemented sys_extattrctl)",          /* 274 = unimplemented 
sys_extattrctl */
+       "#275 (unimplemented sys_extattr_set_file)",            /* 275 = 
unimplemented sys_extattr_set_file */
+       "#276 (unimplemented sys_extattr_get_file)",            /* 276 = 
unimplemented sys_extattr_get_file */
+       "#277 (unimplemented sys_extattr_delete_file)",         /* 277 = 
unimplemented sys_extattr_delete_file */
+       "#278 (unimplemented sys_extattr_set_fd)",              /* 278 = 
unimplemented sys_extattr_set_fd */
+       "#279 (unimplemented sys_extattr_get_fd)",              /* 279 = 
unimplemented sys_extattr_get_fd */
+       "#280 (unimplemented sys_extattr_delete_fd)",           /* 280 = 
unimplemented sys_extattr_delete_fd */
+       "getresuid",                    /* 281 = getresuid */
+       "setresuid",                    /* 282 = setresuid */
+       "getresgid",                    /* 283 = getresgid */
+       "setresgid",                    /* 284 = setresgid */
+       "#285 (obsolete sys_omquery)",          /* 285 = obsolete sys_omquery */
+       "mquery",                       /* 286 = mquery */
+       "closefrom",                    /* 287 = closefrom */
+       "sigaltstack",                  /* 288 = sigaltstack */
+#ifdef SYSVSHM
+       "shmget",                       /* 289 = shmget */
+#else
+       "#289 (unimplemented shmget)",          /* 289 = unimplemented shmget */
+#endif
+#ifdef SYSVSEM
+       "semop",                        /* 290 = semop */
+#else
+       "#290 (unimplemented semop)",           /* 290 = unimplemented semop */
+#endif
+       "#291 (obsolete t32_stat)",             /* 291 = obsolete t32_stat */
+       "#292 (obsolete t32_fstat)",            /* 292 = obsolete t32_fstat */
+       "#293 (obsolete t32_lstat)",            /* 293 = obsolete t32_lstat */
+       "fhstat",                       /* 294 = fhstat */
+#ifdef SYSVSEM
+       "__semctl",                     /* 295 = __semctl */
+#else
+       "#295 (unimplemented)",         /* 295 = unimplemented */
+#endif
+#ifdef SYSVSHM
+       "shmctl",                       /* 296 = shmctl */
+#else
+       "#296 (unimplemented)",         /* 296 = unimplemented */
+#endif
+#ifdef SYSVMSG
+       "msgctl",                       /* 297 = msgctl */
+#else
+       "#297 (unimplemented)",         /* 297 = unimplemented */
+#endif
+       "sched_yield",                  /* 298 = sched_yield */
+       "getthrid",                     /* 299 = getthrid */
+       "#300 (obsolete t32___thrsleep)",               /* 300 = obsolete 
t32___thrsleep */
+       "__thrwakeup",                  /* 301 = __thrwakeup */
+       "__threxit",                    /* 302 = __threxit */
+       "__thrsigdivert",                       /* 303 = __thrsigdivert */
+       "__getcwd",                     /* 304 = __getcwd */
+       "adjfreq",                      /* 305 = adjfreq */
+       "#306 (obsolete getfsstat53)",          /* 306 = obsolete getfsstat53 */
+       "#307 (obsolete statfs53)",             /* 307 = obsolete statfs53 */
+       "#308 (obsolete fstatfs53)",            /* 308 = obsolete fstatfs53 */
+       "#309 (obsolete fhstatfs53)",           /* 309 = obsolete fhstatfs53 */
+       "setrtable",                    /* 310 = setrtable */
+       "getrtable",                    /* 311 = getrtable */
+       "#312 (obsolete t32_getdirentries)",            /* 312 = obsolete 
t32_getdirentries */
+       "faccessat",                    /* 313 = faccessat */
+       "fchmodat",                     /* 314 = fchmodat */
+       "fchownat",                     /* 315 = fchownat */
+       "#316 (obsolete t32_fstatat)",          /* 316 = obsolete t32_fstatat */
+       "linkat",                       /* 317 = linkat */
+       "mkdirat",                      /* 318 = mkdirat */
+       "mkfifoat",                     /* 319 = mkfifoat */
+       "mknodat",                      /* 320 = mknodat */
+       "openat",                       /* 321 = openat */
+       "readlinkat",                   /* 322 = readlinkat */
+       "renameat",                     /* 323 = renameat */
+       "symlinkat",                    /* 324 = symlinkat */
+       "unlinkat",                     /* 325 = unlinkat */
+       "#326 (obsolete t32_utimensat)",                /* 326 = obsolete 
t32_utimensat */
+       "#327 (obsolete t32_futimens)",         /* 327 = obsolete t32_futimens 
*/
+       "#328 (obsolete __tfork51)",            /* 328 = obsolete __tfork51 */
+       "__set_tcb",                    /* 329 = __set_tcb */
+       "__get_tcb",                    /* 330 = __get_tcb */
+};

Reply via email to