diff --git a/echion/__main__.py b/echion/__main__.py index eb15828..e7693ce 100644 --- a/echion/__main__.py +++ b/echion/__main__.py @@ -31,19 +31,24 @@ def detach(pid: int) -> None: def attach(args: argparse.Namespace) -> None: from hypno import inject_py - script = dedent( - f""" - from echion.bootstrap.attach import attach - attach({args.__dict__!r}) - """ - ).strip() - pid = args.pid or args.where try: + pipe_name = None if args.where: pipe_name = Path(tempfile.gettempdir()) / f"echion-{pid}" os.mkfifo(pipe_name) + # This named pipe is likely created by the superuser, so we need to + # make it writable by everyone to allow the target process to write + # to it. + os.chmod(pipe_name, 0o666) + + script = dedent( + f""" + from echion.bootstrap.attach import attach + attach({args.__dict__!r}, {repr(str(pipe_name)) if pipe_name is not None else str(None)}) + """ + ).strip() inject_py(pid, script) @@ -53,6 +58,7 @@ def attach(args: argparse.Namespace) -> None: from time import monotonic as time end = time() + args.exposure + while not args.where: try: os.kill(pid, 0) @@ -61,11 +67,12 @@ def attach(args: argparse.Namespace) -> None: if end is not None and time() > end: break os.sched_yield() + except (KeyboardInterrupt, ProcessLookupError): pass # Read the output - if args.where and pipe_name.exists(): + if args.where and pipe_name is not None and pipe_name.exists(): with pipe_name.open("r") as f: while True: line = f.readline() @@ -76,7 +83,7 @@ def attach(args: argparse.Namespace) -> None: detach(pid) finally: - if args.where and pipe_name.exists(): + if args.where and pipe_name is not None and pipe_name.exists(): pipe_name.unlink() diff --git a/echion/bootstrap/attach.py b/echion/bootstrap/attach.py index 04d892c..3862e27 100644 --- a/echion/bootstrap/attach.py +++ b/echion/bootstrap/attach.py @@ -6,7 +6,7 @@ import typing as t -def attach(config: t.Dict[str, str]) -> None: +def attach(config: t.Dict[str, str], pipe_name: t.Optional[str] = None) -> None: os.environ["ECHION_CPU"] = str(int(config["cpu"])) os.environ["ECHION_NATIVE"] = str(int(config["native"])) os.environ["ECHION_OUTPUT"] = config["output"] @@ -15,6 +15,12 @@ def attach(config: t.Dict[str, str]) -> None: from echion.bootstrap import start + # This is used in where mode for IPC. + if pipe_name is not None: + from echion.core import set_pipe_name + + set_pipe_name(pipe_name) + start() diff --git a/echion/config.h b/echion/config.h index efa7ff0..dfcb66f 100644 --- a/echion/config.h +++ b/echion/config.h @@ -26,6 +26,9 @@ static int native = 0; // Where mode static int where = 0; +// Pipe name (where mode IPC) +static std::string pipe_name; + // ---------------------------------------------------------------------------- static PyObject * set_interval(PyObject *Py_UNUSED(m), PyObject *args) @@ -77,3 +80,16 @@ set_where(PyObject *Py_UNUSED(m), PyObject *args) Py_RETURN_NONE; } + +// ---------------------------------------------------------------------------- +static PyObject * +set_pipe_name(PyObject *Py_UNUSED(m), PyObject *args) +{ + const char *name; + if (!PyArg_ParseTuple(args, "s", &name)) + return NULL; + + pipe_name = name; + + Py_RETURN_NONE; +} diff --git a/echion/core.pyi b/echion/core.pyi index 67d052c..51f7854 100644 --- a/echion/core.pyi +++ b/echion/core.pyi @@ -23,3 +23,4 @@ def set_interval(interval: int) -> None: ... def set_cpu(cpu: bool) -> None: ... def set_native(native: bool) -> None: ... def set_where(where: bool) -> None: ... +def set_pipe_name(name: str) -> None: ... diff --git a/echion/coremodule.cc b/echion/coremodule.cc index 2768d0c..40ce2ac 100644 --- a/echion/coremodule.cc +++ b/echion/coremodule.cc @@ -138,15 +138,13 @@ _sampler() if (where) { - auto pipe_name = std::filesystem::temp_directory_path() / ("echion-" + std::to_string(getpid())); std::ofstream pipe(pipe_name, std::ios::out); - if (!pipe) - { - std::cerr << "Failed to open pipe " << pipe_name << std::endl; - return; - } - do_where(pipe); + if (pipe) + do_where(pipe); + + else + std::cerr << "Failed to open pipe " << pipe_name << std::endl; running = 0; @@ -398,6 +396,7 @@ static PyMethodDef echion_core_methods[] = { {"set_cpu", set_cpu, METH_VARARGS, "Set whether to use CPU time instead of wall time"}, {"set_native", set_native, METH_VARARGS, "Set whether to sample the native stacks"}, {"set_where", set_where, METH_VARARGS, "Set whether to use where mode"}, + {"set_pipe_name", set_pipe_name, METH_VARARGS, "Set the pipe name"}, {NULL, NULL, 0, NULL} /* Sentinel */ };