summaryrefslogtreecommitdiffhomepage
path: root/vm.c
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2023-07-11 13:30:51 +0200
committerJo-Philipp Wich <jo@mein.io>2023-07-27 13:59:32 +0200
commit29b1c0deba8487d1d7ad86242a1a5e90aa775218 (patch)
tree984a28dea6f2fd88a112f7d49709c32c0a8fa858 /vm.c
parentbb5eba4db8895d862038825b84d5b7a94ee5cbb0 (diff)
vm: introduce basic signal handling infrastructure
Introduce the basic facilities to implement UNIX process signal handling in ucode. The VM structure is extended by a bitmap keeping track of received signal numbers, a sigaction structure which holds a generic signal handler to update the bitmap, a pipe where the generic signal handler will send received signal numbers to and an array of managed signal handler functions, indexed by signal number. Additionally, three new C API functions are added to control signal delivery in the VM instance: - `uc_vm_signal_dispatch()` This function invokes signal handler callbacks for each received signal number, clears the bitmap and empties the pipe. The VM itself will invoke this function after each executed bytecode instruction. In some cases however it is useful for C code driving the VM to force immediate signal delivery, e.g. from within long running C functions that do not return to ucode (to the next bytecode instruction) in a timely manner. - `uc_vm_signal_raise()` This function will deliver the given signal number, so that it is picked up by the next call to `uc_vm_signal_dispatch()`. It is used by the generic C signal handler function to forward received signals to the VM instance. - `uc_vm_signal_notifyfd()` This functions returns the read end of the internal signal pipe. It is mainly useful for integration into select or poll based event loops, in order to be notified when there's pending signals for delivery into the VM instance. Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'vm.c')
-rw-r--r--vm.c138
1 files changed, 138 insertions, 0 deletions
diff --git a/vm.c b/vm.c
index 16bf6c5..d3c26bf 100644
--- a/vm.c
+++ b/vm.c
@@ -21,6 +21,8 @@
#include <math.h>
#include <errno.h>
#include <limits.h>
+#include <fcntl.h>
+#include <unistd.h>
#include "ucode/vm.h"
#include "ucode/compiler.h"
@@ -142,6 +144,69 @@ uc_vm_alloc_global_scope(uc_vm_t *vm)
static void
uc_vm_output_exception(uc_vm_t *vm, uc_exception_t *ex);
+static uc_vm_t *signal_handler_vm;
+
+static void
+uc_vm_signal_handler(int sig)
+{
+ assert(signal_handler_vm);
+
+ uc_vm_signal_raise(signal_handler_vm, sig);
+}
+
+#ifdef __APPLE__
+static int pipe2(int pipefd[2], int flags)
+{
+ if (pipe(pipefd) != 0)
+ return -1;
+
+ if (flags & O_CLOEXEC) {
+ if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 ||
+ fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+
+ return -1;
+ }
+
+ flags &= ~O_CLOEXEC;
+ }
+
+ if (fcntl(pipefd[0], F_SETFL, flags) != 0 ||
+ fcntl(pipefd[1], F_SETFL, flags) != 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+
+ return -1;
+ }
+
+ return 0;
+}
+#endif
+
+static void
+uc_vm_signal_handlers_setup(uc_vm_t *vm)
+{
+ memset(&vm->signal, 0, sizeof(vm->signal));
+
+ vm->signal.sigpipe[0] = -1;
+ vm->signal.sigpipe[1] = -1;
+
+ if (!vm->config->setup_signal_handlers)
+ return;
+
+ if (pipe2(vm->signal.sigpipe, O_CLOEXEC | O_NONBLOCK) != 0)
+ return;
+
+ signal_handler_vm = vm;
+
+ vm->signal.handler = ucv_array_new_length(vm, NSIG - 1);
+
+ vm->signal.sa.sa_handler = uc_vm_signal_handler;
+ vm->signal.sa.sa_flags = SA_RESTART | SA_ONSTACK;
+ sigemptyset(&vm->signal.sa.sa_mask);
+}
+
void uc_vm_init(uc_vm_t *vm, uc_parse_config_t *config)
{
vm->exception.type = EXCEPTION_NONE;
@@ -165,6 +230,8 @@ void uc_vm_init(uc_vm_t *vm, uc_parse_config_t *config)
uc_vm_exception_handler_set(vm, uc_vm_output_exception);
uc_vm_trace_set(vm, 0);
+
+ uc_vm_signal_handlers_setup(vm);
}
void uc_vm_free(uc_vm_t *vm)
@@ -2564,6 +2631,53 @@ uc_vm_output_exception(uc_vm_t *vm, uc_exception_t *ex)
fprintf(stderr, "\n");
}
+uc_exception_type_t
+uc_vm_signal_dispatch(uc_vm_t *vm)
+{
+ uc_exception_type_t ex;
+ uc_value_t *handler;
+ uint64_t mask;
+ size_t i, j;
+ int sig, rv;
+
+ if (!vm->config->setup_signal_handlers)
+ return EXCEPTION_NONE;
+
+ for (i = 0; i < ARRAY_SIZE(vm->signal.raised); i++) {
+ if (!vm->signal.raised[i])
+ continue;
+
+ do {
+ rv = read(vm->signal.sigpipe[0], &sig, sizeof(sig));
+ } while (rv > 0 || (rv == -1 && errno == EINTR));
+
+ for (j = 0; j < 64; j++) {
+ mask = 1ull << j;
+
+ if (vm->signal.raised[i] & mask) {
+ vm->signal.raised[i] &= ~mask;
+
+ sig = i * 64 + j;
+ handler = ucv_array_get(vm->signal.handler, sig);
+
+ if (ucv_is_callable(handler)) {
+ uc_vm_stack_push(vm, ucv_get(handler));
+ uc_vm_stack_push(vm, ucv_int64_new(sig));
+
+ ex = uc_vm_call(vm, false, 1);
+
+ if (ex != EXCEPTION_NONE)
+ return ex;
+
+ ucv_put(uc_vm_stack_pop(vm));
+ }
+ }
+ }
+ }
+
+ return EXCEPTION_NONE;
+}
+
static uc_vm_status_t
uc_vm_execute_chunk(uc_vm_t *vm)
{
@@ -2812,6 +2926,7 @@ uc_vm_execute_chunk(uc_vm_t *vm)
break;
}
+exception:
/* previous instruction raised exception */
if (vm->exception.type != EXCEPTION_NONE) {
/* VM termination was requested */
@@ -2840,6 +2955,10 @@ uc_vm_execute_chunk(uc_vm_t *vm)
chunk = uc_vm_frame_chunk(frame);
}
}
+
+ /* run handler for signal(s) delivered during previous instruction */
+ if (uc_vm_signal_dispatch(vm) != EXCEPTION_NONE)
+ goto exception;
}
return STATUS_OK;
@@ -3057,3 +3176,22 @@ uc_vm_gc_stop(uc_vm_t *vm)
return true;
}
+
+void
+uc_vm_signal_raise(uc_vm_t *vm, int signo)
+{
+ uint8_t signum = signo;
+
+ if (signo <= 0 || signo >= NSIG)
+ return;
+
+ vm->signal.raised[signo / 64] |= (1ull << (signo % 64));
+
+ write(vm->signal.sigpipe[1], &signum, sizeof(signum));
+}
+
+int
+uc_vm_signal_notifyfd(uc_vm_t *vm)
+{
+ return vm->signal.sigpipe[0];
+}