]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-149202: Implement PEP 831 – Frame Pointers Everywhere: Enabling System-Level Obser...
authorPablo Galindo Salgado <Pablogsal@gmail.com>
Fri, 1 May 2026 20:16:11 +0000 (21:16 +0100)
committerGitHub <noreply@github.com>
Fri, 1 May 2026 20:16:11 +0000 (21:16 +0100)
Co-authored-by: Savannah Ostrowski <savannah@python.org>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Co-authored-by: Emma Smith <emma@emmatyping.dev>
Doc/howto/perf_profiling.rst
Doc/using/configure.rst
Doc/whatsnew/3.15.rst
Lib/test/test_frame_pointer_unwind.py
Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst [new file with mode: 0644]
configure
configure.ac

index fc4772bbccab57ac4a46105eb309748fb5ac8e01..653f28ddbabfa4b8a6870774f0a6e2377add6e03 100644 (file)
@@ -217,8 +217,9 @@ Example, using the :mod:`sys` APIs in file :file:`example.py`:
 How to obtain the best results
 ------------------------------
 
-For best results, Python should be compiled with
-``CFLAGS="-fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"`` as this allows
+For best results, keep frame pointers enabled. On supported GCC-compatible
+toolchains, CPython builds itself with ``-fno-omit-frame-pointer`` and, when
+available, ``-mno-omit-leaf-frame-pointer`` by default. These flags allow
 profilers to unwind using only the frame pointer and not on DWARF debug
 information. This is because as the code that is interposed to allow ``perf``
 support is dynamically generated it doesn't have any DWARF debugging information
index d5c17560b6658a67434a06bab3b6824b49f839d6..086f6bfa22ad4ac4cd15da68dd6c8d198fe0115c 100644 (file)
@@ -780,6 +780,24 @@ also be used to improve performance.
 
    .. versionadded:: 3.14
 
+.. option:: --without-frame-pointers
+
+   Disable frame pointers, which are enabled by default (see :pep:`831`).
+
+   By default, the build appends ``-fno-omit-frame-pointer`` (and
+   ``-mno-omit-leaf-frame-pointer`` when the compiler supports it) to
+   ``BASECFLAGS`` so profilers, debuggers, and system tracing tools
+   (``perf``, ``eBPF``, ``dtrace``, ``gdb``) can walk the C call stack
+   without DWARF metadata. The flags propagate to third-party C
+   extensions through :mod:`sysconfig`. On compilers that do not
+   understand them, the build silently skips them.
+
+   Downstream packagers and authors of native libraries built with
+   custom build systems should set the same flags so the unwind chain
+   stays unbroken across all native frames.
+
+   .. versionadded:: 3.15
+
 .. option:: --without-mimalloc
 
    Disable the fast :ref:`mimalloc <mimalloc>` allocator
index 83d3cb82195caa066df1c207f5b28dfcc28b51f3..b075441fdeaa3a2d53e2bd0b8a617aeb85bfb0ab 100644 (file)
@@ -86,6 +86,7 @@ Summary -- Release highlights
 * :pep:`782`: :ref:`A new PyBytesWriter C API to create a Python bytes object
   <whatsnew315-pybyteswriter>`
 * :pep:`803`: :ref:`Stable ABI for Free-Threaded Builds <whatsnew315-abi3t>`
+* :pep:`831`: :ref:`Frame pointers everywhere <whatsnew315-frame-pointers>`
 * :ref:`The JIT compiler has been significantly upgraded <whatsnew315-jit>`
 * :ref:`Improved error messages <whatsnew315-improved-error-messages>`
 * :ref:`The official Windows 64-bit binaries now use the tail-calling interpreter
@@ -2262,6 +2263,16 @@ Build changes
   and :option:`-X dev <-X>` is passed to the Python or Python is built in :ref:`debug mode <debug-build>`.
   (Contributed by Donghee Na in :gh:`141770`.)
 
+.. _whatsnew315-frame-pointers:
+
+* CPython is now built with frame pointers enabled by default
+  (:pep:`831`). Pass :option:`--without-frame-pointers` to opt out.
+  Authors of C extensions and native libraries built with custom build
+  systems should add ``-fno-omit-frame-pointer`` and
+  ``-mno-omit-leaf-frame-pointer`` to their own ``CFLAGS`` to keep the
+  unwind chain intact.
+  (Contributed by Pablo Galindo Salgado and Savannah Ostrowski in :gh:`149201`.)
+
 .. _whatsnew315-windows-tail-calling-interpreter:
 
 * 64-bit builds using Visual Studio 2026 (MSVC 18) may now use the new
index c70ec281686715f10f832f74b9bd6e5e5ef1a854..2f9ce2bf049f5889bd4065776324ef51441ce340 100644 (file)
@@ -27,9 +27,8 @@ def _frame_pointers_expected(machine):
     )
 
     if "no-omit-frame-pointer" in cflags:
-        # For example, configure adds -fno-omit-frame-pointer if Python
-        # has perf trampoline (PY_HAVE_PERF_TRAMPOLINE) and Python is built
-        # in debug mode.
+        # For example, configure adds -fno-omit-frame-pointer by default on
+        # supported GCC-compatible builds.
         return True
     if "omit-frame-pointer" in cflags:
         return False
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst
new file mode 100644 (file)
index 0000000..f82ca91
--- /dev/null
@@ -0,0 +1,4 @@
+Enable frame pointers by default for GCC-compatible CPython builds, including
+``-mno-omit-leaf-frame-pointer`` when the compiler supports it, so profilers
+and debuggers can unwind native interpreter frames more reliably. Users can pass
+``--without-frame-pointers`` to opt out.
index 6cd7a1900463ee5bc4d25d9e34be23c51bf6a0df..734aa3a6a721d1e400333881bc95b1433a742b51 100755 (executable)
--- a/configure
+++ b/configure
@@ -1115,6 +1115,7 @@ enable_bolt
 with_strict_overflow
 enable_safety
 enable_slower_safety
+with_frame_pointers
 enable_experimental_jit
 with_dsymutil
 with_address_sanitizer
@@ -1912,6 +1913,8 @@ Optional Packages:
                           is no)
   --with-strict-overflow  if 'yes', add -fstrict-overflow to CFLAGS, else add
                           -fno-strict-overflow (default is no)
+  --without-frame-pointers
+                          build without frame pointers (default is no)
   --with-dsymutil         link debug information into final executable with
                           dsymutil in macOS (default is no)
   --with-address-sanitizer
 
 fi
 
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether to build with frame pointers" >&5
+printf %s "checking whether to build with frame pointers... " >&6; }
+
+# Check whether --with-frame-pointers was given.
+if test ${with_frame_pointers+y}
+then :
+  withval=$with_frame_pointers;
+else case e in #(
+  e) with_frame_pointers=yes ;;
+esac
+fi
+
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $with_frame_pointers" >&5
+printf "%s\n" "$with_frame_pointers" >&6; }
+
 if test "x$ac_cv_gcc_compat" = xyes
 then :
 
+                    frame_pointer_cflags=
+    { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -fno-omit-frame-pointer" >&5
+printf %s "checking whether C compiler accepts -fno-omit-frame-pointer... " >&6; }
+if test ${ax_cv_check_cflags__Werror__fno_omit_frame_pointer+y}
+then :
+  printf %s "(cached) " >&6
+else case e in #(
+  e)
+  ax_check_save_flags=$CFLAGS
+  CFLAGS="$CFLAGS -Werror -fno-omit-frame-pointer"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main (void)
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ax_cv_check_cflags__Werror__fno_omit_frame_pointer=yes
+else case e in #(
+  e) ax_cv_check_cflags__Werror__fno_omit_frame_pointer=no ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+  CFLAGS=$ax_check_save_flags ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror__fno_omit_frame_pointer" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror__fno_omit_frame_pointer" >&6; }
+if test "x$ax_cv_check_cflags__Werror__fno_omit_frame_pointer" = xyes
+then :
+
+      frame_pointer_cflags="-fno-omit-frame-pointer"
+      { printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking whether C compiler accepts -mno-omit-leaf-frame-pointer" >&5
+printf %s "checking whether C compiler accepts -mno-omit-leaf-frame-pointer... " >&6; }
+if test ${ax_cv_check_cflags__Werror__mno_omit_leaf_frame_pointer+y}
+then :
+  printf %s "(cached) " >&6
+else case e in #(
+  e)
+  ax_check_save_flags=$CFLAGS
+  CFLAGS="$CFLAGS -Werror -mno-omit-leaf-frame-pointer"
+  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+int
+main (void)
+{
+
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_compile "$LINENO"
+then :
+  ax_cv_check_cflags__Werror__mno_omit_leaf_frame_pointer=yes
+else case e in #(
+  e) ax_cv_check_cflags__Werror__mno_omit_leaf_frame_pointer=no ;;
+esac
+fi
+rm -f core conftest.err conftest.$ac_objext conftest.beam conftest.$ac_ext
+  CFLAGS=$ax_check_save_flags ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $ax_cv_check_cflags__Werror__mno_omit_leaf_frame_pointer" >&5
+printf "%s\n" "$ax_cv_check_cflags__Werror__mno_omit_leaf_frame_pointer" >&6; }
+if test "x$ax_cv_check_cflags__Werror__mno_omit_leaf_frame_pointer" = xyes
+then :
+
+        frame_pointer_cflags="$frame_pointer_cflags -mno-omit-leaf-frame-pointer"
+
+else case e in #(
+  e) : ;;
+esac
+fi
+
+
+else case e in #(
+  e) : ;;
+esac
+fi
+
+    if test -n "$frame_pointer_cflags" && test "x$with_frame_pointers" != xno; then
+      BASECFLAGS="$frame_pointer_cflags $BASECFLAGS"
+    fi
+
     CFLAGS_NODIST="$CFLAGS_NODIST -std=c11"
 
 
@@ -14124,13 +14233,6 @@ printf "%s\n" "#define PY_HAVE_PERF_TRAMPOLINE 1" >>confdefs.h
 
   PERF_TRAMPOLINE_OBJ=Python/asm_trampoline.o
 
-    if test "x$Py_DEBUG" = xtrue
-then :
-
-    as_fn_append BASECFLAGS " -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"
-
-fi
-
 fi
 
 
index 60511db39fad1e60167b28422dd551f86b6fffa9..c8cb1686d55c07452e889d96027485f19c1c0771 100644 (file)
@@ -2529,7 +2529,30 @@ then
   AX_CHECK_COMPILE_FLAG([-D_FORTIFY_SOURCE=3], [CFLAGS_NODIST="$CFLAGS_NODIST -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3"], [AC_MSG_WARN([-D_FORTIFY_SOURCE=3 not supported])], [-Werror])
 fi
 
+AC_MSG_CHECKING([whether to build with frame pointers])
+AC_ARG_WITH([frame-pointers],
+  [AS_HELP_STRING([--without-frame-pointers],
+                  [build without frame pointers (default is no)])],
+  [],
+  [with_frame_pointers=yes])
+AC_MSG_RESULT([$with_frame_pointers])
+
 AS_VAR_IF([ac_cv_gcc_compat], [yes], [
+    dnl Keep frame pointers in CPython, stdlib objects, and third-party
+    dnl extensions built against this Python (BASECFLAGS propagates via
+    dnl sysconfig) so native profilers can unwind interpreter frames and
+    dnl generated trampolines without DWARF.
+    frame_pointer_cflags=
+    AX_CHECK_COMPILE_FLAG([-fno-omit-frame-pointer], [
+      frame_pointer_cflags="-fno-omit-frame-pointer"
+      AX_CHECK_COMPILE_FLAG([-mno-omit-leaf-frame-pointer], [
+        frame_pointer_cflags="$frame_pointer_cflags -mno-omit-leaf-frame-pointer"
+      ], [], [-Werror])
+    ], [], [-Werror])
+    if test -n "$frame_pointer_cflags" && test "x$with_frame_pointers" != xno; then
+      BASECFLAGS="$frame_pointer_cflags $BASECFLAGS"
+    fi
+
     CFLAGS_NODIST="$CFLAGS_NODIST -std=c11"
 
     PY_CHECK_CC_WARNING([enable], [extra], [if we can add -Wextra])
@@ -3788,11 +3811,6 @@ AC_MSG_RESULT([$perf_trampoline])
 AS_VAR_IF([perf_trampoline], [yes], [
   AC_DEFINE([PY_HAVE_PERF_TRAMPOLINE], [1], [Define to 1 if you have the perf trampoline.])
   PERF_TRAMPOLINE_OBJ=Python/asm_trampoline.o
-
-  dnl perf needs frame pointers for unwinding, include compiler option in debug builds
-  AS_VAR_IF([Py_DEBUG], [true], [
-    AS_VAR_APPEND([BASECFLAGS], [" -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer"])
-  ])
 ])
 AC_SUBST([PERF_TRAMPOLINE_OBJ])