All of lore.kernel.org
 help / color / mirror / Atom feed
From: "Tzvetomir Stoyanov (VMware)" <tz.stoyanov@gmail.com>
To: rostedt@goodmis.org
Cc: linux-trace-devel@vger.kernel.org
Subject: [PATCH v2 2/2] trace-cmd: [POC] Add support for uprobes
Date: Thu, 18 Jun 2020 11:53:33 +0300	[thread overview]
Message-ID: <20200618085333.164150-3-tz.stoyanov@gmail.com> (raw)
In-Reply-To: <20200618085333.164150-1-tz.stoyanov@gmail.com>

Initial implementaton of trace-cmd support for ftrace uprobes.
Two new trace-cmd record / set argumemnts are introduced:
 --uprobe file:function
 --uprobe-ret file:function
The ftrace (return) probe is set on given function from the file.
Note: the file must contain debug DWARF information.

Signed-off-by: Tzvetomir Stoyanov (VMware) <tz.stoyanov@gmail.com>
---
 Makefile                       |  15 +-
 include/trace-cmd/trace-cmd.h  |  24 ++
 lib/trace-cmd/Makefile         |   9 +-
 lib/trace-cmd/trace-uprobes.c  | 390 +++++++++++++++++++++++++++++++++
 tracecmd/include/trace-local.h |   2 +
 tracecmd/trace-record.c        |  79 ++++++-
 tracecmd/trace-usage.c         |   4 +
 7 files changed, 511 insertions(+), 12 deletions(-)
 create mode 100644 lib/trace-cmd/trace-uprobes.c

diff --git a/Makefile b/Makefile
index d737b588..10effd57 100644
--- a/Makefile
+++ b/Makefile
@@ -244,16 +244,23 @@ CUNIT_INSTALLED := $(shell if (echo -e "\#include <CUnit/Basic.h>\n void main(){
 export CUNIT_INSTALLED
 
 DWARF_INSTALLED := $(shell if (echo -e "\#include <libdwarf/libdwarf.h>\n void main(){dwarf_init(-1, 0, 0, 0, 0, 0);}" | $(CC) -xc - -ldwarf >/dev/null 2>&1) ; then echo 1; else echo 0 ; fi)
-export DWARF_INSTALLED
 BFD_INSTALLED := $(shell if (echo -e "\#include <bfd.h>\n void main(){bfd_init();}" | $(CC) -xc  - -lbfd >/dev/null 2>&1) ; then echo 1; else echo 0 ; fi)
-export BFD_INSTALLED
 
+OBJECT_DEBUG=0
 ifeq ($(BFD_INSTALLED), 1)
-LIBS += -lbfd
-endif
 ifeq ($(DWARF_INSTALLED), 1)
+OBJECT_DEBUG=1
+CFLAGS += -DOBJECT_DEBUG
+LIBS += -lbfd
 LIBS += -ldwarf
+else
+$(warning libdwarf is not installed, no uprobes support)
 endif
+else
+$(warning libbfd is not installed, no uprobes support)
+endif
+
+export OBJECT_DEBUG
 
 export CFLAGS
 export INCLUDES
diff --git a/include/trace-cmd/trace-cmd.h b/include/trace-cmd/trace-cmd.h
index f3c95f30..6ac00006 100644
--- a/include/trace-cmd/trace-cmd.h
+++ b/include/trace-cmd/trace-cmd.h
@@ -7,6 +7,7 @@
 #define _TRACE_CMD_H
 
 #include "traceevent/event-parse.h"
+#include "tracefs.h"
 
 #define TRACECMD_MAGIC { 23, 8, 68 }
 
@@ -483,6 +484,29 @@ void tracecmd_plog(const char *fmt, ...);
 void tracecmd_plog_error(const char *fmt, ...);
 int tracecmd_set_logfile(char *logfile);
 
+/* --- Uprobes --- */
+struct tracecmd_uprobe;
+int tracecmd_uprobe_new(struct tracecmd_uprobe **list,
+			char *file, char *func, bool pret);
+int tracecmd_uprobe_create(struct tracecmd_uprobe *probes);
+int tracecmd_uprobe_remove(struct tracecmd_uprobe *probes);
+void tracecmd_uprobe_free(struct tracecmd_uprobe *probes);
+int tracecmd_uprobe_enable(struct tracefs_instance *instance,
+			   struct tracecmd_uprobe *probes);
+int tracecmd_uprobe_disable(struct tracefs_instance *instance,
+			    struct tracecmd_uprobe *probes);
+
+struct tracecmd_uprobe_desc {
+	struct tracecmd_uprobe_desc *next;
+	int active;	/* is the probe configured */
+	char *file;	/* executable file that will be traced */
+	char *symbol;	/* symbol from the file, set as uprobe */
+	char *event;	/* name of the created event for this uprobe */
+};
+struct tracecmd_uprobe_desc *
+tracecmd_uprobes_get_list(struct tracecmd_uprobe *probes);
+void tracecmd_uprobes_free_list(struct tracecmd_uprobe_desc *list);
+
 /* --- System --- */
 unsigned long long tracecmd_generate_traceid(void);
 int tracecmd_count_cpus(void);
diff --git a/lib/trace-cmd/Makefile b/lib/trace-cmd/Makefile
index f8fb8390..5375a76d 100644
--- a/lib/trace-cmd/Makefile
+++ b/lib/trace-cmd/Makefile
@@ -16,6 +16,7 @@ OBJS += trace-util.o
 OBJS += trace-filter-hash.o
 OBJS += trace-msg.o
 OBJS += trace-plugin.o
+OBJS += trace-uprobes.o
 ifeq ($(VSOCK_DEFINED), 1)
 OBJS += trace-timesync.o
 endif
@@ -24,14 +25,8 @@ endif
 OBJS += trace-blk-hack.o
 OBJS += trace-ftrace.o
 
-ifeq ($(BFD_INSTALLED), 1)
-ifeq ($(DWARF_INSTALLED), 1)
+ifeq ($(OBJECT_DEBUG), 1)
 OBJS += trace-obj-debug.o
-else
-$(warning libdwarf is not installed)
-endif
-else
-$(warning libbfd is not installed)
 endif
 
 OBJS := $(OBJS:%.o=$(bdir)/%.o)
diff --git a/lib/trace-cmd/trace-uprobes.c b/lib/trace-cmd/trace-uprobes.c
new file mode 100644
index 00000000..48d9ac6e
--- /dev/null
+++ b/lib/trace-cmd/trace-uprobes.c
@@ -0,0 +1,390 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2020, VMware, Tzvetomir Stoyanov <tz.stoyanov@gmail.com>
+ *
+ */
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <linux/limits.h>
+#include <ctype.h>
+
+#include "tracefs.h"
+#include "trace-cmd-local.h"
+#include "trace-cmd.h"
+
+#ifdef OBJECT_DEBUG
+
+#define UPROBE_FILE "uprobe_events"
+
+struct trace_uprobe_symbols {
+	struct trace_uprobe_symbols *next;
+	char *event;
+	bool ret_probe;
+	struct trace_obj_symbols debug;
+};
+
+struct tracecmd_uprobe {
+	struct tracecmd_uprobe *next;
+
+	char *file;
+	struct trace_obj_debug *debug;
+	struct trace_uprobe_symbols *symbols;
+};
+
+static char *uprobe_event_name(char *file, char *func, bool pret)
+{
+	char *event = NULL;
+	char *fname;
+	char name[10];
+	int len;
+
+	fname = strrchr(file, '/');
+	if (fname)
+		fname++;
+	if (!fname || *fname == '\0')
+		fname = file;
+	strncpy(name, fname, 10);
+	for (len = 0; len < 10 && name[len]; len++) {
+		if (!isalpha(name[len]))
+			name[len] = '_';
+	}
+
+	asprintf(&event, "%c_%.*s_%.10s", pret ? 'r':'p', len, name, func);
+	return event;
+}
+
+/**
+ * tracecmd_uprobe_new - Add new uprobe in the uprobe list
+ * @list - list with uprobes, the new one will be added in this list
+ * @file - executable file that will be traced
+ * @func - function from @file
+ * @pret - indicate if this is return probe (true for return uprobe)
+ *
+ * Returns 0 on success or -1 on failure
+ */
+int tracecmd_uprobe_new(struct tracecmd_uprobe **list,
+			char *file, char *func, bool pret)
+{
+	struct trace_uprobe_symbols *pfunc = NULL;
+	struct tracecmd_uprobe *probe = *list;
+	bool new_file = false;
+
+	while (probe) {
+		if (!strcmp(probe->file, file))
+			break;
+		probe = probe->next;
+	}
+
+	if (!probe) {
+		probe = calloc(1, sizeof(*probe));
+		if (!probe)
+			return -1;
+
+		probe->file = strdup(file);
+		probe->next = *list;
+		new_file = true;
+	}
+
+	pfunc = probe->symbols;
+	while (pfunc) {
+		if (!strcmp(func, pfunc->debug.name) && pret == pfunc->ret_probe)
+			break;
+		pfunc = pfunc->next;
+	}
+
+	if (!pfunc) {
+		pfunc = calloc(1, sizeof(*pfunc));
+		if (!pfunc)
+			goto error;
+		pfunc->debug.name = strdup(func);
+		pfunc->ret_probe = pret;
+		pfunc->event = uprobe_event_name(file, func, pret);
+		pfunc->next = probe->symbols;
+		probe->symbols = pfunc;
+	}
+
+	if (new_file)
+		*list = probe;
+
+	return 0;
+
+error:
+	if (new_file)
+		free(probe);
+
+	return -1;
+}
+
+static void uprobe_symbols_free(struct trace_uprobe_symbols *symbols)
+{
+	struct trace_uprobe_symbols *del;
+
+	while (symbols) {
+		del = symbols;
+		symbols = symbols->next;
+		free(del->debug.name);
+		free(del->event);
+		free(del);
+	}
+}
+
+/**
+ * tracecmd_uprobe_free - Free uprobe list
+ * @list - list with uprobes, that wil be freed
+ *
+ */
+void tracecmd_uprobe_free(struct tracecmd_uprobe *probes)
+{
+	struct tracecmd_uprobe *del;
+
+	while (probes) {
+		del = probes;
+		probes = probes->next;
+		trace_obj_debug_destroy(del->debug);
+		uprobe_symbols_free(del->symbols);
+		free(del->file);
+		free(del);
+	}
+}
+
+static void uprobe_resolve(struct tracecmd_uprobe *probes)
+{
+	while (probes) {
+		if (!probes->debug)
+			probes->debug = trace_obj_debug_create(probes->file);
+		if (probes->debug)
+			trace_obj_debug_get_fileoffset(probes->debug,
+						       &probes->symbols->debug);
+		probes = probes->next;
+	}
+}
+
+static int uprobe_symbols(int fd, char *file, bool add,
+			  struct trace_uprobe_symbols *symbols)
+{
+	char probe_str[BUFSIZ];
+
+	for (; symbols; symbols = symbols->next) {
+		if (add) {
+			if (!symbols->debug.foffset || !symbols->event)
+				continue;
+			snprintf(probe_str, BUFSIZ,
+				 "%c:%s %s:0x%llx", symbols->ret_probe?'r':'p',
+				 symbols->event, file, symbols->debug.foffset);
+		} else {
+			if (!symbols->event)
+				continue;
+			snprintf(probe_str, BUFSIZ,
+				 "-:%s", symbols->event);
+		}
+		write(fd, probe_str, strlen(probe_str));
+	}
+
+	return 0;
+}
+
+static int uprobe_modify(struct tracecmd_uprobe *probes, bool add)
+{
+	char *ufile = tracefs_instance_get_file(NULL, UPROBE_FILE);
+	int fd = -1;
+
+	if (!ufile)
+		return -1;
+	fd = open(ufile, O_WRONLY | O_APPEND);
+	tracefs_put_tracing_file(ufile);
+	if (fd < 0)
+		return -1;
+
+	for (; probes; probes = probes->next) {
+		if (!probes->debug)
+			continue;
+		uprobe_symbols(fd, probes->file, add, probes->symbols);
+	}
+
+	close(fd);
+	return 0;
+}
+
+/**
+ * tracecmd_uprobe_create - Create uprobes in ftrace
+ * @list - list with uprobes, that will be created
+ *
+ * Returns 0 on success or -1 on failure
+ */
+int tracecmd_uprobe_create(struct tracecmd_uprobe *probes)
+{
+	uprobe_resolve(probes);
+	return uprobe_modify(probes, true);
+}
+
+/**
+ * tracecmd_uprobe_remove - Remove uprobes from ftrace
+ * @list - list with uprobes, that will be removed
+ *
+ * Returns 0 on success or -1 on failure
+ */
+int tracecmd_uprobe_remove(struct tracecmd_uprobe *probes)
+{
+	return uprobe_modify(probes, false);
+}
+
+static int uprobe_config(struct tracefs_instance *instance,
+			 struct tracecmd_uprobe *probes, bool enable)
+{
+	struct trace_uprobe_symbols *symb;
+	char event[PATH_MAX];
+
+	for (symb = probes->symbols; symb; symb = symb->next) {
+		if (!symb->event)
+			continue;
+		snprintf(event, PATH_MAX, "events/uprobes/%s/enable", symb->event);
+		tracefs_instance_file_write(instance, event, enable?"1":"0");
+	}
+
+	return 0;
+}
+
+/**
+ * tracecmd_uprobe_enable - Enable uprobes for tracing
+ * @instance - Ftrace instance in which scope the uprobes will be enabled
+ * @list - list with uprobes, that will be enabled
+ *
+ * Returns 0 on success or -1 on failure
+ */
+int tracecmd_uprobe_enable(struct tracefs_instance *instance,
+			   struct tracecmd_uprobe *probes)
+{
+	return uprobe_config(instance, probes, true);
+}
+
+/**
+ * tracecmd_uprobe_disable - Disable uprobes for tracing
+ * @instance - Ftrace instance in which scope the uprobes are enabled
+ * @list - list with uprobes, that will be disabled
+ *
+ * Returns 0 on success or -1 on failure
+ */
+int tracecmd_uprobe_disable(struct tracefs_instance *instance,
+			    struct tracecmd_uprobe *probes)
+{
+	return uprobe_config(instance, probes, false);
+}
+
+/**
+ * tracecmd_uprobes_free_list - Free list with uprobes description
+ * @list - list with uprobes descriptions, that will be freed
+ *
+ * Frees @list returned by tracecmd_uprobes_get_list()
+ * Returns 0 on success or -1 on failure
+ */
+void tracecmd_uprobes_free_list(struct tracecmd_uprobe_desc *list)
+{
+	struct tracecmd_uprobe_desc *del;
+
+	while (list) {
+		del = list;
+		list = list->next;
+		free(del->event);
+		free(del->symbol);
+		free(del->file);
+		free(del);
+	}
+}
+
+/**
+ * tracecmd_uprobes_get_list - Get list with uprobes description
+ * @list - list with configured uprobes
+ *
+ * The returned list should be freed by tracecmd_uprobes_free_list()
+ * Returns pointer to newly allocated list with uprobes description
+ * on success or NULL on failure.
+ */
+struct tracecmd_uprobe_desc *
+tracecmd_uprobes_get_list(struct tracecmd_uprobe *probes)
+{
+	struct trace_uprobe_symbols *s;
+	struct tracecmd_uprobe_desc *desc, *list = NULL;
+
+	for (; probes; probes = probes->next) {
+		for (s = probes->symbols; s; s = s->next) {
+			if (!s->event)
+				continue;
+			desc = calloc(1, sizeof(*desc));
+			if (!desc)
+				goto error;
+			desc->next = list;
+			list = desc;
+			desc->event = strdup(s->event);
+			if (!desc->event)
+				goto error;
+			desc->file = strdup(probes->file);
+			if (!desc->file)
+				goto error;
+			desc->symbol = strdup(s->debug.name);
+			if (!desc->symbol)
+				goto error;
+			if (s->debug.foffset)
+				desc->active = 1;
+		}
+	}
+
+	return list;
+error:
+	tracecmd_uprobes_free_list(list);
+	return NULL;
+}
+
+#else /* !OBJECT_DEBUG */
+
+int tracecmd_uprobe_new(struct tracecmd_uprobe **list,
+			char *file, char *func, bool pret)
+{
+	return -1;
+}
+
+void tracecmd_uprobe_resolve(struct tracecmd_uprobe *probes)
+{
+
+}
+
+int tracecmd_uprobe_create(struct tracecmd_uprobe *probes)
+{
+	return -1;
+}
+
+int tracecmd_uprobe_remove(struct tracecmd_uprobe *probes)
+{
+	return -1;
+}
+
+void tracecmd_uprobe_free(struct tracecmd_uprobe *probes)
+{
+
+}
+
+int tracecmd_uprobe_enable(struct tracefs_instance *instance,
+			   struct tracecmd_uprobe *probes)
+{
+	return -1;
+}
+
+int tracecmd_uprobe_disable(struct tracefs_instance *instance,
+			    struct tracecmd_uprobe *probes)
+{
+	return -1;
+}
+
+struct tracecmd_uprobe_desc *
+tracecmd_uprobes_get_list(struct tracecmd_uprobe *probes)
+{
+	return NULL;
+}
+
+void tracecmd_uprobes_free_list(struct tracecmd_uprobe_desc *list)
+{
+
+}
+
+#endif /* OBJECT_DEBUG */
diff --git a/tracecmd/include/trace-local.h b/tracecmd/include/trace-local.h
index d148aa16..2a476d8a 100644
--- a/tracecmd/include/trace-local.h
+++ b/tracecmd/include/trace-local.h
@@ -216,6 +216,8 @@ struct buffer_instance {
 	struct func_list	*filter_funcs;
 	struct func_list	*notrace_funcs;
 
+	struct tracecmd_uprobe	*uprobes;
+
 	struct opt_list		*options;
 	struct filter_pids	*filter_pids;
 	struct filter_pids	*process_pids;
diff --git a/tracecmd/trace-record.c b/tracecmd/trace-record.c
index bd004574..828d7d6f 100644
--- a/tracecmd/trace-record.c
+++ b/tracecmd/trace-record.c
@@ -5126,7 +5126,8 @@ static void check_function_plugin(void)
 static int __check_doing_something(struct buffer_instance *instance)
 {
 	return is_guest(instance) || (instance->flags & BUFFER_FL_PROFILE) ||
-		instance->plugin || instance->events || instance->get_procmap;
+		instance->plugin || instance->events || instance->get_procmap ||
+		instance->uprobes;
 }
 
 static void check_doing_something(void)
@@ -5541,6 +5542,8 @@ void init_top_instance(void)
 }
 
 enum {
+	OPT_retuprobe		= 239,
+	OPT_uprobe		= 240,
 	OPT_fork		= 241,
 	OPT_tsyncinterval	= 242,
 	OPT_user		= 243,
@@ -5730,6 +5733,22 @@ void trace_reset(int argc, char **argv)
 	exit(0);
 }
 
+static int
+uprobe_param(struct buffer_instance *instance, char *param, bool pret)
+{
+	char *str, *file, *func;
+
+	if (!param)
+		return -1;
+
+	file = strtok_r(param, ":", &str);
+	func = strtok_r(NULL, ":", &str);
+
+	if (!file || !func)
+		return -1;
+	return tracecmd_uprobe_new(&instance->uprobes, file, func, pret);
+}
+
 static void init_common_record_context(struct common_record_context *ctx,
 				       enum trace_cmd curr_cmd)
 {
@@ -5884,6 +5903,8 @@ static void parse_record_options(int argc,
 			{"module", required_argument, NULL, OPT_module},
 			{"tsync-interval", required_argument, NULL, OPT_tsyncinterval},
 			{"fork", no_argument, NULL, OPT_fork},
+			{"uprobe", required_argument, NULL, OPT_uprobe},
+			{"uprobe-ret", required_argument, NULL, OPT_retuprobe},
 			{NULL, 0, NULL, 0}
 		};
 
@@ -6283,6 +6304,14 @@ static void parse_record_options(int argc,
 				die("--fork option used for 'start' command only");
 			fork_process = true;
 			break;
+		case OPT_uprobe:
+			check_instance_die(ctx->instance, "--uprobe");
+			uprobe_param(ctx->instance, optarg, false);
+			break;
+		case OPT_retuprobe:
+			check_instance_die(ctx->instance, "--uprobe-ret");
+			uprobe_param(ctx->instance, optarg, true);
+			break;
 		case OPT_quiet:
 		case 'q':
 			quiet = true;
@@ -6394,6 +6423,13 @@ static void finalize_record_trace(struct common_record_context *ctx)
 
 	set_plugin("nop");
 
+	for_all_instances(instance) {
+		if (instance->uprobes) {
+			tracecmd_uprobe_remove(instance->uprobes);
+			tracecmd_uprobe_free(instance->uprobes);
+		}
+	}
+
 	tracecmd_remove_instances();
 
 	/* If tracing_on was enabled before we started, set it on now */
@@ -6425,6 +6461,42 @@ static bool has_local_instances(void)
 	return false;
 }
 
+static int uprobes_set(struct buffer_instance *instance)
+{
+	struct tracecmd_uprobe_desc *probes, *list;
+	struct event_list *event;
+	int ret;
+
+	ret = tracecmd_uprobe_create(instance->uprobes);
+	if (ret < 0)
+		return ret;
+	probes = tracecmd_uprobes_get_list(instance->uprobes);
+	if (!probes)
+		return -1;
+	ret = 0;
+	for (list = probes; list; list = list->next) {
+		if (!list->active) {
+			warning("Failed to set %s:%s uprobe",
+				list->file, list->symbol);
+			continue;
+		}
+		event = calloc(1, sizeof(*event));
+		if (!event) {
+			ret = -1;
+			goto out;
+		}
+		event->event = strdup(list->event);
+		add_event(instance, event);
+
+		if (!recording_all_events())
+			list_event(event->event);
+	}
+
+out:
+	tracecmd_uprobes_free_list(probes);
+	return ret;
+}
+
 /*
  * This function contains common code for the following commands:
  * record, start, stream, profile.
@@ -6468,6 +6540,11 @@ static void record_trace(int argc, char **argv,
 		/* Some instances may not be created yet */
 		if (instance->tracing_on_init_val < 0)
 			instance->tracing_on_init_val = 1;
+		if (instance->uprobes) {
+			ctx->events = 1;
+			if (uprobes_set(instance) < 0)
+				die("Failed to set uprobes");
+		}
 	}
 
 	if (ctx->events)
diff --git a/tracecmd/trace-usage.c b/tracecmd/trace-usage.c
index ada44c68..4a13af19 100644
--- a/tracecmd/trace-usage.c
+++ b/tracecmd/trace-usage.c
@@ -65,6 +65,8 @@ static struct usage_help usage_help[] = {
 		"               If a negative number is specified, timestamps synchronization is disabled"
 		"               If 0 is specified, no loop is performed - timestamps offset is calculated only twice,"
 		"                                                         at the beginnig and at the end of the trace\n"
+		"          --uprobe set the specified [file:function] as uprobe\n"
+		"          --uprobe-ret set the specified [file:function] as return uprobe\n"
 	},
 	{
 		"set",
@@ -101,6 +103,8 @@ static struct usage_help usage_help[] = {
 		"          --cmdlines-size change kernel saved_cmdlines_size\n"
 		"          --user execute the specified [command ...] as given user\n"
 		"          --fork return immediately if a command is specified\n"
+		"          --uprobe set the specified [file:function] as uprobe\n"
+		"          --uprobe-ret set the specified [file:function] as return uprobe\n"
 	},
 	{
 		"start",
-- 
2.26.2


      parent reply	other threads:[~2020-06-18  8:54 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-06-18  8:53 [PATCH v2 0/2] POC] Add support for uprobes Tzvetomir Stoyanov (VMware)
2020-06-18  8:53 ` [PATCH v2 1/2] trace-cmd: [POC] Add APIs for extracting DWARF information from a file Tzvetomir Stoyanov (VMware)
2020-06-18  8:53 ` Tzvetomir Stoyanov (VMware) [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20200618085333.164150-3-tz.stoyanov@gmail.com \
    --to=tz.stoyanov@gmail.com \
    --cc=linux-trace-devel@vger.kernel.org \
    --cc=rostedt@goodmis.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.