diff --git a/configure.ac b/configure.ac index f906a0dfa..f6e8484be 100644 --- a/configure.ac +++ b/configure.ac @@ -466,6 +466,7 @@ dnl Checks for programs. AC_PROG_CC AC_PROG_CC_C99 AM_PROG_CC_C_O +AM_PROG_AS if test "x$ac_cv_prog_cc_c99" = "xno"; then AC_MSG_ERROR([C99 standard compliant C compiler required. Try GCC 3.x or later.]) fi diff --git a/lib/Makefile.am b/lib/Makefile.am index 995360761..b4492b22f 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -29,6 +29,7 @@ include lib/logthrsource/Makefile.am include lib/logthrdest/Makefile.am include lib/signal-slot-connector/Makefile.am include lib/multi-line/Makefile.am +include lib/perf/Makefile.am LSNG_RELEASE = $(shell echo @PACKAGE_VERSION@ | cut -d. -f1,2) @@ -307,7 +308,8 @@ lib_libsyslog_ng_la_SOURCES = \ $(multiline_sources) \ $(logthrsource_sources) \ $(logthrdest_sources) \ - $(signal_slot_connector_sources) + $(signal_slot_connector_sources) \ + $(perf_sources) lib_libsyslog_ng_la_CFLAGS = \ $(AM_CFLAGS) \ diff --git a/lib/apphook.c b/lib/apphook.c index b6ce43715..89ebdecb3 100644 --- a/lib/apphook.c +++ b/lib/apphook.c @@ -49,6 +49,7 @@ #include "timeutils/cache.h" #include "multi-line/multi-line-factory.h" #include "filterx/filterx-globals.h" +#include "perf/perf.h" #include #include @@ -215,6 +216,7 @@ construct_nondumpable_logger(msg_fatal); void app_startup(void) { + perf_global_init(); msg_init(FALSE); iv_set_fatal_msg_handler(app_fatal); iv_init(); @@ -293,6 +295,7 @@ app_shutdown(void) hostname_global_deinit(); crypto_deinit(); msg_deinit(); + perf_global_deinit(); /* NOTE: the iv_deinit() call should come here, but there's some exit diff --git a/lib/perf/Makefile.am b/lib/perf/Makefile.am new file mode 100644 index 000000000..88ad80a99 --- /dev/null +++ b/lib/perf/Makefile.am @@ -0,0 +1,8 @@ +perfincludedir = ${pkgincludedir}/perf + +perfinclude_HEADERS = \ + lib/perf/perf.h + +perf_sources = \ + lib/perf/perf.c \ + lib/perf/trampoline.S diff --git a/lib/perf/perf.c b/lib/perf/perf.c new file mode 100644 index 000000000..3395aeb7d --- /dev/null +++ b/lib/perf/perf.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 Balázs Scheidler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#include "perf.h" +#include "messages.h" +#include +#include +#include +#include + +typedef struct _PerfTrampolineArea +{ + guint8 *area; + gsize size; + gsize code_size; + struct + { + gint num; + gint alloc; + } trampolines; +} PerfTrampolineArea; + +extern void _perf_trampoline_func_start(void); +extern void _perf_trampoline_func_end(void); + +#define PAGE_SIZE 4096 +#define PAGE_MAX_INDEX ((PAGE_SIZE-1)) + +#define SIZE_TO_PAGES(s) (((s + PAGE_MAX_INDEX) & ~PAGE_MAX_INDEX) / PAGE_SIZE) +#define PAGE_TO_SIZE(p) (p * PAGE_SIZE) + +#define MAX_TRAMPOLINES 16384 + +static gboolean +_allocate_trampoline_area(PerfTrampolineArea *self) +{ + guint8 *start = (guint8 *) &_perf_trampoline_func_start; + guint8 *end = (guint8 *) &_perf_trampoline_func_end; + + self->code_size = end - start; + gsize code_pages = SIZE_TO_PAGES(self->code_size * MAX_TRAMPOLINES); + + self->size = PAGE_TO_SIZE(code_pages); + self->area = mmap(NULL, + self->size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); + if (self->area == MAP_FAILED) + return FALSE; + + self->trampolines.num = self->size / self->code_size; + self->trampolines.alloc = 0; + + for (gint i = 0; i < self->trampolines.num; i++) + { + memcpy(self->area + i * self->code_size, start, self->code_size * sizeof(gchar)); + } + return TRUE; +} + +static gpointer +_generate_trampoline(PerfTrampolineArea *self, gpointer target_address) +{ + if (self->trampolines.alloc >= self->trampolines.num) + return NULL; + + gint res = mprotect(self->area, self->size, PROT_READ | PROT_WRITE); + if (res < 0) + return NULL; + + guint8 *trampoline_start = self->area + (self->trampolines.alloc++) * self->code_size; + guint8 *trampoline_end = trampoline_start + self->code_size; + uintptr_t *value_p = (uintptr_t *) (trampoline_end - sizeof(target_address)); + *value_p = (uintptr_t) target_address; + + __builtin___clear_cache((gpointer) trampoline_start, (gpointer) trampoline_end); + + res = mprotect(self->area, self->size, PROT_READ | PROT_EXEC); + if (res < 0) + return NULL; + return (gpointer) trampoline_start; +} + +static gboolean +_is_trampoline_address(PerfTrampolineArea *self, guint8 *address) +{ + return self->area <= address && self->area + self->size > address; +} + +static gboolean +_save_symbol(gpointer address, gsize size, const gchar *symbol_name) +{ + gchar filename[64]; + + g_snprintf(filename, sizeof(filename), "/tmp/perf-%d.map", (int) getpid()); + FILE *mapfile = fopen(filename, "a"); + if (!mapfile) + return FALSE; + fprintf(mapfile, "%0lx %ld %s\n", (uintptr_t) address, size, symbol_name); + fclose(mapfile); + return TRUE; +} + +static PerfTrampolineArea trampolines; + +gpointer +perf_generate_trampoline(gpointer target_address, const gchar *symbol_name) +{ + gpointer t = _generate_trampoline(&trampolines, target_address); + + if (!t) + { + msg_warning_once("WARNING: out of free trampoline slots", + evt_tag_int("max_trampolines", trampolines.trampolines.num)); + return target_address; + } + + _save_symbol(t, trampolines.code_size, symbol_name); + return t; +} + +gboolean +perf_is_trampoline_address(gpointer address) +{ + return _is_trampoline_address(&trampolines, address); +} + +void +perf_global_init(void) +{ + _allocate_trampoline_area(&trampolines); +} + +void +perf_global_deinit(void) +{ + /* we are never freeing the trampolines, as that would invalidate any + * function pointers that point into that area */ +} diff --git a/lib/perf/perf.h b/lib/perf/perf.h new file mode 100644 index 000000000..342ddcec4 --- /dev/null +++ b/lib/perf/perf.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024 Axoflow + * Copyright (c) 2024 Balázs Scheidler + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + * As an additional exemption you are allowed to compile & link against the + * OpenSSL libraries as published by the OpenSSL project. See the file + * COPYING for details. + * + */ + +#ifndef SYSLOG_NG_PERF_H_INCLUDED +#define SYSLOG_NG_PERF_H_INCLUDED + +#include "syslog-ng.h" + +gpointer perf_generate_trampoline(gpointer target_address, const gchar *symbol_name); +gboolean perf_is_trampoline_address(gpointer address); + + +void perf_global_init(void); +void perf_global_deinit(void); + + +#endif diff --git a/lib/perf/trampoline.S b/lib/perf/trampoline.S new file mode 100644 index 000000000..4b7abc4a5 --- /dev/null +++ b/lib/perf/trampoline.S @@ -0,0 +1,19 @@ + .text + .global _perf_trampoline_func_start + .align 16 +_perf_trampoline_func_start: +#ifdef __x86_64__ + endbr64 + sub $8, %rsp + mov _perf_trampoline_target_address(%rip), %r11 + call *%r11 + add $8, %rsp + ret + .align 8 +_perf_trampoline_target_address: + .quad 0x0 +#endif // __x86_64__ + + .global _perf_trampoline_func_end +_perf_trampoline_func_end: + .section .note.GNU-stack,"",@progbits