diff --git a/COPYING b/COPYING index 3adbc015..5b12c83a 100644 --- a/COPYING +++ b/COPYING @@ -6,6 +6,7 @@ Copyright (c) 2011 Nicholas J. Kain Copyright (c) 2011-2015 Rich Felker Copyright (c) 2014-2019 Marco Paland Copyright (c) 2017-2022 Embedded Artistry LLC +Copyright (c) 2022-2022 mintsuki Copyright (c) 2022 Alexander Monakov Permission is hereby granted, free of charge, to any person obtaining a copy diff --git a/ChangeLog b/ChangeLog index 84faee21..5dec14b0 100644 --- a/ChangeLog +++ b/ChangeLog @@ -44,3 +44,7 @@ 2022-12-05 Alex Kotov libkernaux 0.6.1 released + +2022-06-22 Alex Kotov + + * include/kernaux/stack_trace.h: Added diff --git a/Makefile.am b/Makefile.am index de9909f1..5efb8487 100644 --- a/Makefile.am +++ b/Makefile.am @@ -114,6 +114,9 @@ endif if WITH_PRINTF_FMT libkernaux_la_SOURCES += src/printf_fmt.c endif +if WITH_STACK_TRACE +libkernaux_la_SOURCES += src/stack_trace.c +endif if WITH_UNITS libkernaux_la_SOURCES += src/units.c endif diff --git a/README.md b/README.md index 833b7618..43ba34c1 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ zero). Work-in-progress APIs can change at any time. * [Assertions](/include/kernaux/assert.h) (*non-breaking since* **0.4.0**) * [Example: Assert](/examples/assert.c) * [Example: Panic](/examples/panic.c) - * Stack trace *(planned)* + * [Stack trace](/include/kernaux/stack_trace.h) *(work in progress)* * Generic types * [Display](/include/kernaux/generic/display.h) (*non-breaking since* **?.?.?**) * [Example](/examples/generic_display.c) @@ -173,6 +173,7 @@ explicitly included, use `--without-all`. * `--with[out]-multiboot2` - Multiboot 2 utils * `--with[out]-ntoa` - itoa/ftoa * `--with[out]-printf` - printf +* `--with[out]-stack-trace` - stack trace diff --git a/configure.ac b/configure.ac index 6b632b05..55ef0acb 100644 --- a/configure.ac +++ b/configure.ac @@ -79,6 +79,7 @@ AC_ARG_WITH( [ntoa], AS_HELP_STRING([--without-ntoa], [wit AC_ARG_WITH( [pfa], AS_HELP_STRING([--without-pfa], [without Page Frame Allocator])) AC_ARG_WITH( [printf], AS_HELP_STRING([--without-printf], [without printf])) AC_ARG_WITH( [printf-fmt], AS_HELP_STRING([--without-printf-fmt], [without printf format parser])) +AC_ARG_WITH( [stack-trace], AS_HELP_STRING([--without-stack-trace], [without stack trace])) AC_ARG_WITH( [units], AS_HELP_STRING([--without-units], [without measurement units utils])) dnl Packages (disabled by default) @@ -123,6 +124,7 @@ if test -z "$with_ntoa"; then with_ntoa=no; fi if test -z "$with_pfa"; then with_pfa=no; fi if test -z "$with_printf"; then with_printf=no; fi if test -z "$with_printf_fmt"; then with_printf_fmt=no; fi +if test -z "$with_stack_trace"; then with_stack_trace=no; fi if test -z "$with_units"; then with_units=no; fi ]) AS_IF([test "$with_all" = no], do_without_all) @@ -165,6 +167,7 @@ AS_IF([test "$with_ntoa" = no ], [with_ntoa=no], [wit AS_IF([test "$with_pfa" = no ], [with_pfa=no], [with_pfa=yes]) AS_IF([test "$with_printf" = no ], [with_printf=no], [with_printf=yes]) AS_IF([test "$with_printf_fmt" = no ], [with_printf_fmt=no], [with_printf_fmt=yes]) +AS_IF([test "$with_stack_trace" = no ], [with_stack_trace=no], [with_stack_trace=yes]) AS_IF([test "$with_units" = no ], [with_units=no], [with_units=yes]) dnl Packages (disabled by default) @@ -181,9 +184,9 @@ AS_IF([test "$enable_fixtures" = yes -a "$enable_freestanding" = yes], AC_MSG_ER AS_IF([test "$enable_checks" = yes -a "$with_libc" = yes], AC_MSG_ERROR([can not use package `libc' with tests])) AS_IF([test "$enable_fixtures" = yes -a "$with_libc" = yes], AC_MSG_ERROR([can not use package `libc' with fixtures])) -AS_IF([test "$with_printf" = yes -a "$with_ntoa" = no], AC_MSG_ERROR([package `printf' requires package `ntoa'])) -AS_IF([test "$with_printf" = yes -a "$with_printf_fmt" = no], AC_MSG_ERROR([package `printf' requires package `printf-fmt'])) -AS_IF([test "$with_units" = yes -a "$with_ntoa" = no], AC_MSG_ERROR([package `units' requires package `ntoa'])) +AS_IF([test "$with_printf" = yes -a "$with_ntoa" = no], AC_MSG_ERROR([package `printf' requires package `ntoa'])) +AS_IF([test "$with_printf" = yes -a "$with_printf_fmt" = no], AC_MSG_ERROR([package `printf' requires package `printf-fmt'])) +AS_IF([test "$with_units" = yes -a "$with_ntoa" = no], AC_MSG_ERROR([package `units' requires package `ntoa'])) @@ -228,6 +231,7 @@ AM_CONDITIONAL([WITH_NTOA], [test "$with_ntoa" = yes]) AM_CONDITIONAL([WITH_PFA], [test "$with_pfa" = yes]) AM_CONDITIONAL([WITH_PRINTF], [test "$with_printf" = yes]) AM_CONDITIONAL([WITH_PRINTF_FMT], [test "$with_printf_fmt" = yes]) +AM_CONDITIONAL([WITH_STACK_TRACE], [test "$with_stack_trace" = yes]) AM_CONDITIONAL([WITH_UNITS], [test "$with_units" = yes]) dnl Packages (disabled by default) @@ -280,6 +284,7 @@ AS_IF([test "$with_ntoa" = yes], [AC_DEFINE([WITH_NTOA], AS_IF([test "$with_pfa" = yes], [AC_DEFINE([WITH_PFA], [1], [with Page Frame Allocator])]) AS_IF([test "$with_printf" = yes], [AC_DEFINE([WITH_PRINTF], [1], [with printf])]) AS_IF([test "$with_printf_fmt" = yes], [AC_DEFINE([WITH_PRINTF_FMT], [1], [with printf format parser])]) +AS_IF([test "$with_stack_trace" = yes], [AC_DEFINE([WITH_STACK_TRACE], [1], [with stack trace])]) AS_IF([test "$with_units", = yes], [AC_DEFINE([WITH_UNITS], [1], [with measurement units utils])]) dnl Packages (disabled by default) @@ -312,6 +317,7 @@ AS_IF([test "$with_ntoa" = no], [AC_SUBST([comment_line_ntoa], [ AS_IF([test "$with_pfa" = no], [AC_SUBST([comment_line_pfa], [//])]) AS_IF([test "$with_printf" = no], [AC_SUBST([comment_line_printf], [//])]) AS_IF([test "$with_printf_fmt" = no], [AC_SUBST([comment_line_printf_fmt], [//])]) +AS_IF([test "$with_stack_trace" = no], [AC_SUBST([comment_line_stack_trace], [//])]) AS_IF([test "$with_units" = no], [AC_SUBST([comment_line_units], [//])]) diff --git a/include/Makefile.am b/include/Makefile.am index f58d01df..e612fea1 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -35,5 +35,6 @@ nobase_include_HEADERS = \ kernaux/pfa.h \ kernaux/printf.h \ kernaux/printf_fmt.h \ + kernaux/stack_trace.h \ kernaux/units.h \ kernaux/version.h diff --git a/include/kernaux.h b/include/kernaux.h index 38ae77a0..e57569c5 100644 --- a/include/kernaux.h +++ b/include/kernaux.h @@ -21,5 +21,6 @@ #include #include #include +#include #include #include diff --git a/include/kernaux/stack_trace.h b/include/kernaux/stack_trace.h new file mode 100644 index 00000000..137856e6 --- /dev/null +++ b/include/kernaux/stack_trace.h @@ -0,0 +1,29 @@ +#ifndef KERNAUX_INCLUDED_STACK_TRACE +#define KERNAUX_INCLUDED_STACK_TRACE + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include +#include +#include + +typedef struct KernAux_StackTrace_Frame { + const void *KERNAUX_PRIVATE_FIELD(cur_ptr); +} *KernAux_StackTrace_Frame; + +struct KernAux_StackTrace_Frame KernAux_StackTrace_Frame_create(); + +bool KernAux_StackTrace_Frame_has_next(KernAux_StackTrace_Frame frame); +void KernAux_StackTrace_Frame_use_next(KernAux_StackTrace_Frame frame); + +const void *KernAux_StackTrace_Frame_get_ptr(KernAux_StackTrace_Frame frame); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/stack_trace.c b/src/stack_trace.c new file mode 100644 index 00000000..84e6d6ba --- /dev/null +++ b/src/stack_trace.c @@ -0,0 +1,76 @@ +/** + * The code was inspired by the Limine bootloader. + * + * Copyright (c) 2020-2022 mintsuki + * Copyright (c) 2022 Alex Kotov + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include + +struct KernAux_StackTrace_Frame KernAux_StackTrace_Frame_create() +{ + struct KernAux_StackTrace_Frame frame = { .cur_ptr = NULL }; + +#if defined(ASM_I386) + const size_t *cur_ptr; + KERNAUX_ASM("movl %%ebp, %0" : "=g" (cur_ptr) :: "memory"); + frame.cur_ptr = cur_ptr; +#elif defined(ASM_X86_64) + const size_t *cur_ptr; + KERNAUX_ASM("movq %%rbp, %0" : "=g" (cur_ptr) :: "memory"); + frame.cur_ptr = cur_ptr; +#endif + + return frame; +} + +bool KernAux_StackTrace_Frame_has_next(const KernAux_StackTrace_Frame frame) +{ + KERNAUX_ASSERT(frame); + + if (!frame->cur_ptr) return false; + +#if defined(ASM_X86) + const size_t *const cur_ptr = frame->cur_ptr; + return cur_ptr[1]; +#else + return false; +#endif +} + +void KernAux_StackTrace_Frame_use_next(const KernAux_StackTrace_Frame frame) +{ + KERNAUX_ASSERT(frame); + + if (!frame->cur_ptr) return; + +#if defined(ASM_X86) + const size_t *const cur_ptr = frame->cur_ptr; + frame->cur_ptr = (const void*)cur_ptr[0]; +#endif +} + +const void *KernAux_StackTrace_Frame_get_ptr( + const KernAux_StackTrace_Frame frame +) { + KERNAUX_ASSERT(frame); + + if (!frame->cur_ptr) return NULL; + +#if defined(ASM_X86) + const size_t *const cur_ptr = frame->cur_ptr; + return (const void*)cur_ptr[1]; +#else + return NULL; +#endif +} diff --git a/tests/.gitignore b/tests/.gitignore index e98bf740..8e8e7d2a 100644 --- a/tests/.gitignore +++ b/tests/.gitignore @@ -30,4 +30,5 @@ /test_printf_fmt_gen.c /test_printf_gen /test_printf_gen.c +/test_stack_trace /test_units_human diff --git a/tests/Makefile.am b/tests/Makefile.am index 04419860..14d0ebf9 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -397,6 +397,18 @@ CLEANFILES += test_printf_gen.c test_printf_gen.c: $(top_srcdir)/tests/printf_gen.py $(top_srcdir)/tests/printf_gen.jinja $(top_srcdir)/fixtures/printf.yml $(top_srcdir)/fixtures/printf_orig.yml $(PYTHON) $(top_srcdir)/tests/printf_gen.py $(top_srcdir)/tests/printf_gen.jinja $(top_srcdir)/fixtures/printf.yml $(top_srcdir)/fixtures/printf_orig.yml test_printf_gen.c +#################### +# test_stack_trace # +#################### + +if WITH_STACK_TRACE +TESTS += test_stack_trace +test_stack_trace_LDADD = $(top_builddir)/libkernaux.la +test_stack_trace_SOURCES = \ + main.c \ + test_stack_trace.c +endif + #################### # test_units_human # #################### diff --git a/tests/test_stack_trace.c b/tests/test_stack_trace.c new file mode 100644 index 00000000..8381a910 --- /dev/null +++ b/tests/test_stack_trace.c @@ -0,0 +1,81 @@ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include + +#include +#include +#include + +#define MIN_SIZE 20 +#define MAX_SIZE (MIN_SIZE + 5) + +static size_t min_count = 0; +static const void *min_addresses[MIN_SIZE]; + +static size_t max_count = 0; +static const void *max_addresses[MAX_SIZE]; + +#define PREPARE(descr, lower, upper) \ + do { \ + printf("%s:\n", (descr)); \ +\ + lower##_count = 0; \ + memset(lower##_addresses, 0, sizeof(lower##_addresses)); \ +\ + for ( \ + struct KernAux_StackTrace_Frame frame = \ + KernAux_StackTrace_Frame_create(); \ + KernAux_StackTrace_Frame_has_next(&frame); \ + KernAux_StackTrace_Frame_use_next(&frame) \ + ) { \ + assert(lower##_count < upper##_SIZE); \ + lower##_addresses[lower##_count] = \ + KernAux_StackTrace_Frame_get_ptr(&frame); \ + printf(" %llu: 0x%p\n", \ + (unsigned long long)lower##_count, \ + lower##_addresses[lower##_count]); \ + ++lower##_count; \ + } \ +\ + assert(lower##_count >= 2); \ + putchar('\n'); \ + } while (0) + +static void test1(); + +static void test5_1(); +static void test5_2(); +static void test5_3(); +static void test5_4(); +static void test5_5(); + +void test_main() +{ + PREPARE("test_main (initial)", min, MIN); + + PREPARE("test_main (compare)", max, MAX); + assert(max_count == min_count); + for (size_t index = 1; index < max_count; ++index) { + assert(max_addresses[index] == min_addresses[index]); + } + + test1(); + for (size_t index = 2; index < max_count; ++index) { + assert(max_addresses[index] == min_addresses[index - 1]); + } + + test5_1(); + for (size_t index = 6; index < max_count; ++index) { + assert(max_addresses[index] == min_addresses[index - 5]); + } +} + +void test1() { PREPARE("test1", max, MAX); } + +void test5_1() { test5_2(); } +void test5_2() { test5_3(); } +void test5_3() { test5_4(); } +void test5_4() { test5_5(); } +void test5_5() { PREPARE("test5_5", max, MAX); }