https://github.com/python/cpython/commit/323677325735373a06506e5156b7d8e0e96c9660
commit: 323677325735373a06506e5156b7d8e0e96c9660
branch: main
author: Pablo Galindo Salgado <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-05-01T21:16:11+01:00
summary:

gh-149202: Implement PEP 831 – Frame Pointers Everywhere: Enabling System-Level 
Observability for Python (#149201)

Co-authored-by: Savannah Ostrowski <[email protected]>
Co-authored-by: Hugo van Kemenade <[email protected]>
Co-authored-by: Emma Smith <[email protected]>

files:
A 
Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst
M Doc/howto/perf_profiling.rst
M Doc/using/configure.rst
M Doc/whatsnew/3.15.rst
M Lib/test/test_frame_pointer_unwind.py
M configure
M configure.ac

diff --git a/Doc/howto/perf_profiling.rst b/Doc/howto/perf_profiling.rst
index fc4772bbccab57..653f28ddbabfa4 100644
--- a/Doc/howto/perf_profiling.rst
+++ b/Doc/howto/perf_profiling.rst
@@ -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
diff --git a/Doc/using/configure.rst b/Doc/using/configure.rst
index d5c17560b6658a..086f6bfa22ad4a 100644
--- a/Doc/using/configure.rst
+++ b/Doc/using/configure.rst
@@ -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
diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst
index 83d3cb82195caa..b075441fdeaa3a 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -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
diff --git a/Lib/test/test_frame_pointer_unwind.py 
b/Lib/test/test_frame_pointer_unwind.py
index c70ec281686715..2f9ce2bf049f58 100644
--- a/Lib/test/test_frame_pointer_unwind.py
+++ b/Lib/test/test_frame_pointer_unwind.py
@@ -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
index 00000000000000..f82ca91f5ba000
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-04-05-16-10-00.gh-issue-149202.W8sQeR.rst
@@ -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.
diff --git a/configure b/configure
index 6cd7a1900463ee..734aa3a6a721d1 100755
--- 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
@@ -10241,9 +10244,115 @@ fi
 
 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
 
 
diff --git a/configure.ac b/configure.ac
index 60511db39fad1e..c8cb1686d55c07 100644
--- a/configure.ac
+++ b/configure.ac
@@ -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])
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to