| This patch adds the required @order directives to preserve the |
| GLIBC_PRIVATE ABI. |
| |
| commit 31be941e4367c001b2009308839db5c67bf9dcbc |
| Author: Simon Kissane <skissane@gmail.com> |
| Date: Sat Feb 11 20:12:13 2023 +1100 |
| |
| gmon: improve mcount overflow handling [BZ# 27576] |
| |
| When mcount overflows, no gmon.out file is generated, but no message is printed |
| to the user, leaving the user with no idea why, and thinking maybe there is |
| some bug - which is how BZ 27576 ended up being logged. Print a message to |
| stderr in this case so the user knows what is going on. |
| |
| As a comment in sys/gmon.h acknowledges, the hardcoded MAXARCS value is too |
| small for some large applications, including the test case in that BZ. Rather |
| than increase it, add tunables to enable MINARCS and MAXARCS to be overridden |
| at runtime (glibc.gmon.minarcs and glibc.gmon.maxarcs). So if a user gets the |
| mcount overflow error, they can try increasing maxarcs (they might need to |
| increase minarcs too if the heuristic is wrong in their case.) |
| |
| Note setting minarcs/maxarcs too large can cause monstartup to fail with an |
| out of memory error. If you set them large enough, it can cause an integer |
| overflow in calculating the buffer size. I haven't done anything to defend |
| against that - it would not generally be a security vulnerability, since these |
| tunables will be ignored in suid/sgid programs (due to the SXID_ERASE default), |
| and if you can set GLIBC_TUNABLES in the environment of a process, you can take |
| it over anyway (LD_PRELOAD, LD_LIBRARY_PATH, etc). I thought about modifying |
| the code of monstartup to defend against integer overflows, but doing so is |
| complicated, and I realise the existing code is susceptible to them even prior |
| to this change (e.g. try passing a pathologically large highpc argument to |
| monstartup), so I decided just to leave that possibility in-place. |
| |
| Add a test case which demonstrates mcount overflow and the tunables. |
| |
| Document the new tunables in the manual. |
| |
| Signed-off-by: Simon Kissane <skissane@gmail.com> |
| Reviewed-by: DJ Delorie <dj@redhat.com> |
| |
| Conflicts: |
| manual/tunables.texi |
| (missing tunables downstream) |
| |
| diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list |
| index f11ca5b3e8b09b43..dc2999796042dbaf 100644 |
| |
| |
| @@ -149,4 +149,17 @@ glibc { |
| default: 2 |
| } |
| } |
| + |
| + gmon { |
| + minarcs { |
| + type: INT_32 |
| + minval: 50 |
| + default: 50 |
| + } |
| + maxarcs { |
| + type: INT_32 |
| + minval: 50 |
| + default: 1048576 |
| + } |
| + } |
| } |
| diff --git a/gmon/Makefile b/gmon/Makefile |
| index d94593c9d8a882eb..54f05894d4dd8c4a 100644 |
| |
| |
| @@ -25,7 +25,7 @@ include ../Makeconfig |
| headers := sys/gmon.h sys/gmon_out.h sys/profil.h |
| routines := gmon mcount profil sprofil prof-freq |
| |
| -tests = tst-sprofil tst-gmon |
| +tests = tst-sprofil tst-gmon tst-mcount-overflow |
| ifeq ($(build-profile),yes) |
| tests += tst-profile-static |
| tests-static += tst-profile-static |
| @@ -56,6 +56,18 @@ ifeq ($(run-built-tests),yes) |
| tests-special += $(objpfx)tst-gmon-gprof.out |
| endif |
| |
| +CFLAGS-tst-mcount-overflow.c := -fno-omit-frame-pointer -pg |
| +tst-mcount-overflow-no-pie = yes |
| +CRT-tst-mcount-overflow := $(csu-objpfx)g$(start-installed-name) |
| +# Intentionally use invalid config where maxarcs<minarcs to check warning is printed |
| +tst-mcount-overflow-ENV := GMON_OUT_PREFIX=$(objpfx)tst-mcount-overflow.data \ |
| + GLIBC_TUNABLES=glibc.gmon.minarcs=51:glibc.gmon.maxarcs=50 |
| +# Send stderr into output file because we make sure expected messages are printed |
| +tst-mcount-overflow-ARGS := 2>&1 1>/dev/null | cat |
| +ifeq ($(run-built-tests),yes) |
| +tests-special += $(objpfx)tst-mcount-overflow-check.out |
| +endif |
| + |
| CFLAGS-tst-gmon-static.c := $(PIE-ccflag) -fno-omit-frame-pointer -pg |
| CRT-tst-gmon-static := $(csu-objpfx)gcrt1.o |
| tst-gmon-static-no-pie = yes |
| @@ -103,6 +115,14 @@ $(objpfx)tst-gmon.out: clean-tst-gmon-data |
| clean-tst-gmon-data: |
| rm -f $(objpfx)tst-gmon.data.* |
| |
| +$(objpfx)tst-mcount-overflow.o: clean-tst-mcount-overflow-data |
| +clean-tst-mcount-overflow-data: |
| + rm -f $(objpfx)tst-mcount-overflow.data.* |
| + |
| +$(objpfx)tst-mcount-overflow-check.out: tst-mcount-overflow-check.sh $(objpfx)tst-mcount-overflow.out |
| + $(SHELL) $< $(objpfx)tst-mcount-overflow > $@; \ |
| + $(evaluate-test) |
| + |
| $(objpfx)tst-gmon-gprof.out: tst-gmon-gprof.sh $(objpfx)tst-gmon.out |
| $(SHELL) $< $(GPROF) $(objpfx)tst-gmon $(objpfx)tst-gmon.data.* > $@; \ |
| $(evaluate-test) |
| diff --git a/gmon/gmon.c b/gmon/gmon.c |
| index bf76358d5b1aa2da..689bf80141e559ca 100644 |
| |
| |
| @@ -46,6 +46,11 @@ |
| #include <libc-internal.h> |
| #include <not-cancel.h> |
| |
| +#if HAVE_TUNABLES |
| +# define TUNABLE_NAMESPACE gmon |
| +# include <elf/dl-tunables.h> |
| +#endif |
| + |
| #ifdef PIC |
| # include <link.h> |
| |
| @@ -124,6 +129,22 @@ __monstartup (u_long lowpc, u_long highpc) |
| int o; |
| char *cp; |
| struct gmonparam *p = &_gmonparam; |
| + long int minarcs, maxarcs; |
| + |
| +#if HAVE_TUNABLES |
| + /* Read minarcs/maxarcs tunables. */ |
| + minarcs = TUNABLE_GET (minarcs, int32_t, NULL); |
| + maxarcs = TUNABLE_GET (maxarcs, int32_t, NULL); |
| + if (maxarcs < minarcs) |
| + { |
| + ERR("monstartup: maxarcs < minarcs, setting maxarcs = minarcs\n"); |
| + maxarcs = minarcs; |
| + } |
| +#else |
| + /* No tunables, we use hardcoded defaults */ |
| + minarcs = MINARCS; |
| + maxarcs = MAXARCS; |
| +#endif |
| |
| /* |
| * round lowpc and highpc to multiples of the density we're using |
| @@ -146,10 +167,10 @@ __monstartup (u_long lowpc, u_long highpc) |
| } |
| p->fromssize = ROUNDUP(p->textsize / HASHFRACTION, sizeof(*p->froms)); |
| p->tolimit = p->textsize * ARCDENSITY / 100; |
| - if (p->tolimit < MINARCS) |
| - p->tolimit = MINARCS; |
| - else if (p->tolimit > MAXARCS) |
| - p->tolimit = MAXARCS; |
| + if (p->tolimit < minarcs) |
| + p->tolimit = minarcs; |
| + else if (p->tolimit > maxarcs) |
| + p->tolimit = maxarcs; |
| p->tossize = p->tolimit * sizeof(struct tostruct); |
| |
| cp = calloc (p->kcountsize + p->fromssize + p->tossize, 1); |
| diff --git a/gmon/mcount.c b/gmon/mcount.c |
| index 9d4a1a50fa6ab21a..f7180fdb83399a14 100644 |
| |
| |
| @@ -41,6 +41,10 @@ static char sccsid[] = "@(#)mcount.c 8.1 (Berkeley) 6/4/93"; |
| |
| #include <atomic.h> |
| |
| +#include <not-cancel.h> |
| +#include <unistd.h> |
| +#define ERR(s) __write_nocancel (STDERR_FILENO, s, sizeof (s) - 1) |
| + |
| /* |
| * mcount is called on entry to each function compiled with the profiling |
| * switch set. _mcount(), which is declared in a machine-dependent way |
| @@ -170,6 +174,7 @@ done: |
| return; |
| overflow: |
| p->state = GMON_PROF_ERROR; |
| + ERR("mcount: call graph buffer size limit exceeded, gmon.out will not be generated\n"); |
| return; |
| } |
| |
| diff --git a/gmon/sys/gmon.h b/gmon/sys/gmon.h |
| index b4cc3b043a2aec77..af0582a3717085b5 100644 |
| |
| |
| @@ -111,6 +111,8 @@ extern struct __bb *__bb_head; |
| * Always allocate at least this many tostructs. This |
| * hides the inadequacy of the ARCDENSITY heuristic, at least |
| * for small programs. |
| + * |
| + * Value can be overridden at runtime by glibc.gmon.minarcs tunable. |
| */ |
| #define MINARCS 50 |
| |
| @@ -124,8 +126,8 @@ extern struct __bb *__bb_head; |
| * Used to be max representable value of ARCINDEX minus 2, but now |
| * that ARCINDEX is a long, that's too large; we don't really want |
| * to allow a 48 gigabyte table. |
| - * The old value of 1<<16 wasn't high enough in practice for large C++ |
| - * programs; will 1<<20 be adequate for long? FIXME |
| + * |
| + * Value can be overridden at runtime by glibc.gmon.maxarcs tunable. |
| */ |
| #define MAXARCS (1 << 20) |
| |
| diff --git a/gmon/tst-mcount-overflow-check.sh b/gmon/tst-mcount-overflow-check.sh |
| new file mode 100644 |
| index 0000000000000000..27eb5538fd573a6e |
| |
| |
| @@ -0,0 +1,45 @@ |
| +#!/bin/sh |
| +# Test expected messages generated when mcount overflows |
| +# Copyright (C) 2017-2023 Free Software Foundation, Inc. |
| +# Copyright The GNU Toolchain Authors. |
| +# This file is part of the GNU C Library. |
| + |
| +# The GNU C 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. |
| + |
| +# The GNU C 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 the GNU C Library; if not, see |
| +# <https://www.gnu.org/licenses/>. |
| + |
| +LC_ALL=C |
| +export LC_ALL |
| +set -e |
| +exec 2>&1 |
| + |
| +program="$1" |
| + |
| +check_msg() { |
| + if ! grep -q "$1" "$program.out"; then |
| + echo "FAIL: expected message not in output: $1" |
| + exit 1 |
| + fi |
| +} |
| + |
| +check_msg 'monstartup: maxarcs < minarcs, setting maxarcs = minarcs' |
| +check_msg 'mcount: call graph buffer size limit exceeded, gmon.out will not be generated' |
| + |
| +for data_file in $1.data.*; do |
| + if [ -f "$data_file" ]; then |
| + echo "FAIL: expected no data files, but found $data_file" |
| + exit 1 |
| + fi |
| +done |
| + |
| +echo PASS |
| diff --git a/gmon/tst-mcount-overflow.c b/gmon/tst-mcount-overflow.c |
| new file mode 100644 |
| index 0000000000000000..06cc93ef872eb7c1 |
| |
| |
| @@ -0,0 +1,72 @@ |
| +/* Test program to trigger mcount overflow in profiling collection. |
| + Copyright (C) 2017-2023 Free Software Foundation, Inc. |
| + This file is part of the GNU C Library. |
| + |
| + The GNU C 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. |
| + |
| + The GNU C 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 the GNU C Library; if not, see |
| + <https://www.gnu.org/licenses/>. */ |
| + |
| +/* Program with sufficiently complex, yet pointless, call graph |
| + that it will trigger an mcount overflow, when you set the |
| + minarcs/maxarcs tunables to very low values. */ |
| + |
| +#define PREVENT_TAIL_CALL asm volatile ("") |
| + |
| +/* Calls REP(n) macro 16 times, for n=0..15. |
| + * You need to define REP(n) before using this. |
| + */ |
| +#define REPS \ |
| + REP(0) REP(1) REP(2) REP(3) REP(4) REP(5) REP(6) REP(7) \ |
| + REP(8) REP(9) REP(10) REP(11) REP(12) REP(13) REP(14) REP(15) |
| + |
| +/* Defines 16 leaf functions named f1_0 to f1_15 */ |
| +#define REP(n) \ |
| + __attribute__ ((noinline, noclone, weak)) void f1_##n (void) {}; |
| +REPS |
| +#undef REP |
| + |
| +/* Calls all 16 leaf functions f1_* in succession */ |
| +__attribute__ ((noinline, noclone, weak)) void |
| +f2 (void) |
| +{ |
| +# define REP(n) f1_##n(); |
| + REPS |
| +# undef REP |
| + PREVENT_TAIL_CALL; |
| +} |
| + |
| +/* Defines 16 functions named f2_0 to f2_15, which all just call f2 */ |
| +#define REP(n) \ |
| + __attribute__ ((noinline, noclone, weak)) void \ |
| + f2_##n (void) { f2(); PREVENT_TAIL_CALL; }; |
| +REPS |
| +#undef REP |
| + |
| +__attribute__ ((noinline, noclone, weak)) void |
| +f3 (int count) |
| +{ |
| + for (int i = 0; i < count; ++i) |
| + { |
| + /* Calls f1_0(), f2_0(), f1_1(), f2_1(), f3_0(), etc */ |
| +# define REP(n) f1_##n(); f2_##n(); |
| + REPS |
| +# undef REP |
| + } |
| +} |
| + |
| +int |
| +main (void) |
| +{ |
| + f3 (1000); |
| + return 0; |
| +} |
| diff --git a/manual/tunables.texi b/manual/tunables.texi |
| index 7b70e80391ee87f7..00eafcf44b562b9e 100644 |
| |
| |
| @@ -73,6 +73,9 @@ glibc.malloc.check: 0 (min: 0, max: 3) |
| * Elision Tunables:: Tunables in elision subsystem |
| * Hardware Capability Tunables:: Tunables that modify the hardware |
| capabilities seen by @theglibc{} |
| +* gmon Tunables:: Tunables that control the gmon profiler, used in |
| + conjunction with gprof |
| + |
| @end menu |
| |
| @node Tunable names |
| @@ -506,3 +509,59 @@ instead. |
| |
| This tunable is specific to i386 and x86-64. |
| @end deftp |
| + |
| +@node gmon Tunables |
| +@section gmon Tunables |
| +@cindex gmon tunables |
| + |
| +@deftp {Tunable namespace} glibc.gmon |
| +This tunable namespace affects the behaviour of the gmon profiler. |
| +gmon is a component of @theglibc{} which is normally used in |
| +conjunction with gprof. |
| + |
| +When GCC compiles a program with the @code{-pg} option, it instruments |
| +the program with calls to the @code{mcount} function, to record the |
| +program's call graph. At program startup, a memory buffer is allocated |
| +to store this call graph; the size of the buffer is calculated using a |
| +heuristic based on code size. If during execution, the buffer is found |
| +to be too small, profiling will be aborted and no @file{gmon.out} file |
| +will be produced. In that case, you will see the following message |
| +printed to standard error: |
| + |
| +@example |
| +mcount: call graph buffer size limit exceeded, gmon.out will not be generated |
| +@end example |
| + |
| +Most of the symbols discussed in this section are defined in the header |
| +@code{sys/gmon.h}. However, some symbols (for example @code{mcount}) |
| +are not defined in any header file, since they are only intended to be |
| +called from code generated by the compiler. |
| +@end deftp |
| + |
| +@deftp Tunable glibc.mem.minarcs |
| +The heuristic for sizing the call graph buffer is known to be |
| +insufficient for small programs; hence, the calculated value is clamped |
| +to be at least a minimum size. The default minimum (in units of |
| +call graph entries, @code{struct tostruct}), is given by the macro |
| +@code{MINARCS}. If you have some program with an unusually complex |
| +call graph, for which the heuristic fails to allocate enough space, |
| +you can use this tunable to increase the minimum to a larger value. |
| +@end deftp |
| + |
| +@deftp Tunable glibc.mem.maxarcs |
| +To prevent excessive memory consumption when profiling very large |
| +programs, the call graph buffer is allowed to have a maximum of |
| +@code{MAXARCS} entries. For some very large programs, the default |
| +value of @code{MAXARCS} defined in @file{sys/gmon.h} is too small; in |
| +that case, you can use this tunable to increase it. |
| + |
| +Note the value of the @code{maxarcs} tunable must be greater or equal |
| +to that of the @code{minarcs} tunable; if this constraint is violated, |
| +a warning will printed to standard error at program startup, and |
| +the @code{minarcs} value will be used as the maximum as well. |
| + |
| +Setting either tunable too high may result in a call graph buffer |
| +whose size exceeds the available memory; in that case, an out of memory |
| +error will be printed at program startup, the profiler will be |
| +disabled, and no @file{gmon.out} file will be generated. |
| +@end deftp |
| diff --git a/sysdeps/unix/sysv/linux/aarch64/dl-tunables.list b/sysdeps/unix/sysv/linux/aarch64/dl-tunables.list |
| index 5c3c5292025607a1..265f82ef2be42fd0 100644 |
| |
| |
| @@ -24,3 +24,7 @@ |
| |
| # Tunables added in RHEL 8.8.0 |
| @order glibc.rtld.dynamic_sort |
| + |
| +# Tunables added in RHEL 8.9.0 |
| +@order glibc.gmon.minarcs |
| +@order glibc.gmon.maxarcs |
| diff --git a/sysdeps/unix/sysv/linux/i386/dl-tunables.list b/sysdeps/unix/sysv/linux/i386/dl-tunables.list |
| index b9cad4af62d9f2e5..9c1ccb86501c61e7 100644 |
| |
| |
| @@ -31,3 +31,7 @@ |
| |
| # Tunables added in RHEL 8.8.0 |
| @order glibc.rtld.dynamic_sort |
| + |
| +# Tunables added in RHEL 8.9.0 |
| +@order glibc.gmon.minarcs |
| +@order glibc.gmon.maxarcs |
| diff --git a/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/dl-tunables.list b/sysdeps/unix/sysv/linux/powerpc/powerpc64/le/dl-tunables.list |
| index ee1e6fca95e1f2da..c8bb1a8ec0283ac8 100644 |
| |
| |
| @@ -24,3 +24,7 @@ |
| |
| # Tunables added in RHEL 8.8.0 |
| @order glibc.rtld.dynamic_sort |
| + |
| +# Tunables added in RHEL 8.9.0 |
| +@order glibc.gmon.minarcs |
| +@order glibc.gmon.maxarcs |
| diff --git a/sysdeps/unix/sysv/linux/s390/s390-64/dl-tunables.list b/sysdeps/unix/sysv/linux/s390/s390-64/dl-tunables.list |
| index 099e28d8f8e67944..85b3a014ffcadc45 100644 |
| |
| |
| @@ -23,3 +23,7 @@ |
| |
| # Tunables added in RHEL 8.8.0 |
| @order glibc.rtld.dynamic_sort |
| + |
| +# Tunables added in RHEL 8.9.0 |
| +@order glibc.gmon.minarcs |
| +@order glibc.gmon.maxarcs |
| diff --git a/sysdeps/unix/sysv/linux/x86_64/64/dl-tunables.list b/sysdeps/unix/sysv/linux/x86_64/64/dl-tunables.list |
| index b9cad4af62d9f2e5..9c1ccb86501c61e7 100644 |
| |
| |
| @@ -31,3 +31,7 @@ |
| |
| # Tunables added in RHEL 8.8.0 |
| @order glibc.rtld.dynamic_sort |
| + |
| +# Tunables added in RHEL 8.9.0 |
| +@order glibc.gmon.minarcs |
| +@order glibc.gmon.maxarcs |