Skip to content

gh-152240: Fix test_c_stack_unwind on Linux LoongArch builds#152241

Merged
vstinner merged 1 commit into
python:mainfrom
Loongson-Cloud-Community:loongarch-clang-unwind
Jun 29, 2026
Merged

gh-152240: Fix test_c_stack_unwind on Linux LoongArch builds#152241
vstinner merged 1 commit into
python:mainfrom
Loongson-Cloud-Community:loongarch-clang-unwind

Conversation

@yzewei

@yzewei yzewei commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Fix test_c_stack_unwind on Linux LoongArch builds.

This change teaches _testinternalcapi's manual frame-pointer unwinder about
the LoongArch frame layout. On LoongArch, the frame pointer is the caller's
stack pointer; the previous frame pointer is at fp[-2], and the return
address is at fp[-1].

It also adds -funwind-tables for Linux LoongArch clang builds. clang/LoongArch
emits .debug_frame but not runtime .eh_frame unwind tables by default, so
glibc backtrace() cannot unwind through CPython frames unless unwind tables
are requested.

Validated on Linux LoongArch64:

GCC build:

../cpython/configure --with-pydebug CC=gcc
make -j8
PYTHON_JIT=0 ./python -m test -v test_c_stack_unwind

clang build with clang 20.1.8:

../cpython/configure --with-pydebug CC=clang
make -j8
PYTHON_JIT=0 ./python -m test -v test_c_stack_unwind

Also verified that -funwind-tables makes CPython C objects contain
.eh_frame, and the GNU backtrace test passes.

@python-cla-bot

python-cla-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.

CLA signed

@yzewei

yzewei commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

Fix log:

Gcc:

PYTHON_JIT=0 ./python -m test -v test_c_stack_unwind
== CPython 3.16.0a0 (heads/main-dirty:a00464bc33) [GCC 15.2.0 20250808]
== Linux-6.18.16-main-16k-loongarch64-with-glibc2.42 little-endian
== Python build: release
== cwd: /mnt/build-cpython-main-gcc-release/build/test_python_worker_480813æ
== CPU count: 8
== encodings: locale=UTF-8 FS=utf-8
== resources: all test resources are disabled, use -u option to unskip tests

Using random seed: 4035734376
0:00:00 load avg: 0.08 mem: 27.8 MiB Run 1 test sequentially in a single process
0:00:00 load avg: 0.08 mem: 27.8 MiB [1/1] test_c_stack_unwind
test_gnu_backtrace_jit_frames_disappear_after_executor_free (test.test_c_stack_unwind.GnuBacktraceUnwindTests.test_gnu_backtrace_jit_frames_disappear_after_executor_free) ... skipped 'JIT is not available'
test_gnu_backtrace_unwinds_through_jit_frames (test.test_c_stack_unwind.GnuBacktraceUnwindTests.test_gnu_backtrace_unwinds_through_jit_frames) ... #00 0x7fffe3601470 -> other
#01 0x555558360384 -> python
#02 0x5555585569fc -> python
#03 0x55555855e82c -> python
#04 0x55555856d86c -> python
#05 0x555558360384 -> python
#06 0x5555585570a4 -> python
#07 0x555558563cbc -> python
#08 0x55555856d86c -> python
#09 0x555558360384 -> python
#10 0x5555585570a4 -> python
#11 0x555558563cbc -> python
#12 0x55555856d86c -> python
#13 0x555558360384 -> python
#14 0x5555585570a4 -> python
#15 0x555558563cbc -> python
#16 0x55555856d86c -> python
#17 0x555558360384 -> python
#18 0x5555585570a4 -> python
#19 0x555558563cbc -> python
#20 0x55555856d86c -> python
#21 0x555558360384 -> python
#22 0x5555585570a4 -> python
#23 0x555558563cbc -> python
#24 0x55555856d86c -> python
#25 0x555558360384 -> python
#26 0x5555585570a4 -> python
#27 0x555558563cbc -> python
#28 0x55555856d86c -> python
#29 0x555558360384 -> python
#30 0x5555585570a4 -> python
#31 0x555558563cbc -> python
#32 0x55555856d86c -> python
#33 0x555558360384 -> python
#34 0x5555585570a4 -> python
#35 0x555558563cbc -> python
#36 0x55555856d86c -> python
#37 0x555558360384 -> python
#38 0x5555585570a4 -> python
#39 0x555558563cbc -> python
#40 0x55555856d86c -> python
#41 0x555558360384 -> python
#42 0x555558360384 -> python
#43 0x5555585569fc -> python
#44 0x55555855e82c -> python
#45 0x55555856d510 -> python
#46 0x555558605d00 -> python
#47 0x55555860622c -> python
#48 0x555558608efc -> python
#49 0x555558644a6c -> python
#50 0x555558645a34 -> python
#51 0x7ffff22bd964 -> other
#52 0x7ffff22bda6c -> other
#53 0x5555582d694c -> python
{"length": 54, "python_frames": 51, "jit_frames": 0, "other_frames": 3, "jit_backend": null, "unwinder": "gnu_backtrace_unwind"}
ok
test_manual_unwind_finds_expected_frames (test.test_c_stack_unwind.ManualStackUnwindTests.test_manual_unwind_finds_expected_frames) ... #00 0x555558240384 -> python
#01 0x5555584369fc -> python
#02 0x55555843e82c -> python
#03 0x55555844d86c -> python
#04 0x555558240384 -> python
#05 0x5555584370a4 -> python
#06 0x555558443cbc -> python
#07 0x55555844d86c -> python
#08 0x555558240384 -> python
#09 0x5555584370a4 -> python
#10 0x555558443cbc -> python
#11 0x55555844d86c -> python
#12 0x555558240384 -> python
#13 0x5555584370a4 -> python
#14 0x555558443cbc -> python
#15 0x55555844d86c -> python
#16 0x555558240384 -> python
#17 0x5555584370a4 -> python
#18 0x555558443cbc -> python
#19 0x55555844d86c -> python
#20 0x555558240384 -> python
#21 0x5555584370a4 -> python
#22 0x555558443cbc -> python
#23 0x55555844d86c -> python
#24 0x555558240384 -> python
#25 0x5555584370a4 -> python
#26 0x555558443cbc -> python
#27 0x55555844d86c -> python
#28 0x555558240384 -> python
#29 0x5555584370a4 -> python
#30 0x555558443cbc -> python
#31 0x55555844d86c -> python
#32 0x555558240384 -> python
#33 0x5555584370a4 -> python
#34 0x555558443cbc -> python
#35 0x55555844d86c -> python
#36 0x555558240384 -> python
#37 0x5555584370a4 -> python
#38 0x555558443cbc -> python
#39 0x55555844d86c -> python
#40 0x555558240384 -> python
#41 0x555558240384 -> python
#42 0x5555584369fc -> python
#43 0x55555843e82c -> python
#44 0x55555844d510 -> python
#45 0x5555584e5d00 -> python
#46 0x5555584e622c -> python
#47 0x5555584e8efc -> python
#48 0x555558524a6c -> python
#49 0x555558525a34 -> python
#50 0x7ffff31dd964 -> other
#51 0x7ffff31dda6c -> other
#52 0x5555581b694c -> python
{"length": 53, "python_frames": 51, "jit_frames": 0, "other_frames": 2, "jit_backend": null, "unwinder": "manual_frame_pointer_unwind"}
ok

----------------------------------------------------------------------
Ran 3 tests in 1.347s

OK (skipped=1)
0:00:01 load avg: 0.08 mem: 28.3 MiB [1/1] test_c_stack_unwind passed

== Tests result: SUCCESS ==

1 test OK.

Total duration: 1.4 sec
Total tests: run=3 skipped=1
Total test files: run=1/1
Result: SUCCESS

Clang

PYTHON_JIT=0 ./python -m test -v test_c_stack_unwind
== CPython 3.16.0a0 (heads/main-dirty:a00464bc33) [Clang 20.1.8 ]
== Linux-6.18.16-main-16k-loongarch64-with-glibc2.42 little-endian
== Python build: debug
== cwd: /mnt/build-cpython-main-clang-debug/build/test_python_worker_480864æ
== CPU count: 8
== encodings: locale=UTF-8 FS=utf-8
== resources: all test resources are disabled, use -u option to unskip tests

Using random seed: 721941152
0:00:00 load avg: 0.02 mem: 31.4 MiB Run 1 test sequentially in a single process
0:00:00 load avg: 0.02 mem: 31.4 MiB [1/1] test_c_stack_unwind
test_gnu_backtrace_jit_frames_disappear_after_executor_free (test.test_c_stack_unwind.GnuBacktraceUnwindTests.test_gnu_backtrace_jit_frames_disappear_after_executor_free) ... skipped 'JIT is not available'
test_gnu_backtrace_unwinds_through_jit_frames (test.test_c_stack_unwind.GnuBacktraceUnwindTests.test_gnu_backtrace_unwinds_through_jit_frames) ... #00 0x7ffff008dea4 -> other
#01 0x555556ec1308 -> python
#02 0x555556e341e8 -> python
#03 0x555556ff546c -> python
#04 0x555556ffc0c8 -> python
#05 0x555556ff4adc -> python
#06 0x555556e341e8 -> python
#07 0x555556ff5a90 -> python
#08 0x555556ffdbd0 -> python
#09 0x555556ff4adc -> python
#10 0x555556e341e8 -> python
#11 0x555556ff5a90 -> python
#12 0x555556ffdbd0 -> python
#13 0x555556ff4adc -> python
#14 0x555556e341e8 -> python
#15 0x555556ff5a90 -> python
#16 0x555556ffdbd0 -> python
#17 0x555556ff4adc -> python
#18 0x555556e341e8 -> python
#19 0x555556ff5a90 -> python
#20 0x555556ffdbd0 -> python
#21 0x555556ff4adc -> python
#22 0x555556e341e8 -> python
#23 0x555556ff5a90 -> python
#24 0x555556ffdbd0 -> python
#25 0x555556ff4adc -> python
#26 0x555556e341e8 -> python
#27 0x555556ff5a90 -> python
#28 0x555556ffdbd0 -> python
#29 0x555556ff4adc -> python
#30 0x555556e341e8 -> python
#31 0x555556ff5a90 -> python
#32 0x555556ffdbd0 -> python
#33 0x555556ff4adc -> python
#34 0x555556e341e8 -> python
#35 0x555556ff5a90 -> python
#36 0x555556ffdbd0 -> python
#37 0x555556ff4adc -> python
#38 0x555556e341e8 -> python
#39 0x555556ff5a90 -> python
#40 0x555556ffdbd0 -> python
#41 0x555556ff4adc -> python
#42 0x555556e341e8 -> python
#43 0x555556ec1104 -> python
#44 0x555556e341e8 -> python
#45 0x555556ff546c -> python
#46 0x555556ffc0c8 -> python
#47 0x555556ff4adc -> python
#48 0x555556ff4714 -> python
#49 0x5555570e784c -> python
#50 0x5555570e4da0 -> python
#51 0x5555570e4a04 -> python
#52 0x55555711b6f8 -> python
#53 0x55555711bba8 -> python
#54 0x55555711bc0c -> python
#55 0x7ffff0f7d964 -> other
#56 0x7ffff0f7da6c -> other
#57 0x555556d8c9a4 -> python
{"length": 58, "python_frames": 55, "jit_frames": 0, "other_frames": 3, "jit_backend": null, "unwinder": "gnu_backtrace_unwind"}
ok
test_manual_unwind_finds_expected_frames (test.test_c_stack_unwind.ManualStackUnwindTests.test_manual_unwind_finds_expected_frames) ... #00 0x555556961308 -> python
#01 0x5555568d41e8 -> python
#02 0x555556a9546c -> python
#03 0x555556a9c0c8 -> python
#04 0x555556a94adc -> python
#05 0x5555568d41e8 -> python
#06 0x555556a95a90 -> python
#07 0x555556a9dbd0 -> python
#08 0x555556a94adc -> python
#09 0x5555568d41e8 -> python
#10 0x555556a95a90 -> python
#11 0x555556a9dbd0 -> python
#12 0x555556a94adc -> python
#13 0x5555568d41e8 -> python
#14 0x555556a95a90 -> python
#15 0x555556a9dbd0 -> python
#16 0x555556a94adc -> python
#17 0x5555568d41e8 -> python
#18 0x555556a95a90 -> python
#19 0x555556a9dbd0 -> python
#20 0x555556a94adc -> python
#21 0x5555568d41e8 -> python
#22 0x555556a95a90 -> python
#23 0x555556a9dbd0 -> python
#24 0x555556a94adc -> python
#25 0x5555568d41e8 -> python
#26 0x555556a95a90 -> python
#27 0x555556a9dbd0 -> python
#28 0x555556a94adc -> python
#29 0x5555568d41e8 -> python
#30 0x555556a95a90 -> python
#31 0x555556a9dbd0 -> python
#32 0x555556a94adc -> python
#33 0x5555568d41e8 -> python
#34 0x555556a95a90 -> python
#35 0x555556a9dbd0 -> python
#36 0x555556a94adc -> python
#37 0x5555568d41e8 -> python
#38 0x555556a95a90 -> python
#39 0x555556a9dbd0 -> python
#40 0x555556a94adc -> python
#41 0x5555568d41e8 -> python
#42 0x555556961104 -> python
#43 0x5555568d41e8 -> python
#44 0x555556a9546c -> python
#45 0x555556a9c0c8 -> python
#46 0x555556a94adc -> python
#47 0x555556a94714 -> python
#48 0x555556b8784c -> python
#49 0x555556b84da0 -> python
#50 0x555556b84a04 -> python
#51 0x555556bbb6f8 -> python
#52 0x555556bbbba8 -> python
#53 0x555556bbbc0c -> python
#54 0x7ffff028d964 -> other
#55 0x7ffff028da6c -> other
#56 0x55555682c9a4 -> python
{"length": 57, "python_frames": 55, "jit_frames": 0, "other_frames": 2, "jit_backend": null, "unwinder": "manual_frame_pointer_unwind"}
ok

----------------------------------------------------------------------
Ran 3 tests in 2.709s

OK (skipped=1)
0:00:02 load avg: 0.10 mem: 31.9 MiB [1/1] test_c_stack_unwind passed

== Tests result: SUCCESS ==

1 test OK.

Total duration: 2.8 sec
Total tests: run=3 skipped=1
Total test files: run=1/1
Result: SUCCESS

@vstinner vstinner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change LGTM, even if I don't know LoongArch architecture.

The -funwind-tables compiler flag is only needed by GnuBacktraceUnwindTests of test_c_stack_unwind? If yes, is it really worth it to add .eh_frame unwind tables just for a test?

@diegorusso @pablogsal: What do you think about the -funwind-tables flag?

@yzewei

yzewei commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

The change LGTM, even if I don't know LoongArch architecture.

The -funwind-tables compiler flag is only needed by GnuBacktraceUnwindTests of test_c_stack_unwind? If yes, is it really worth it to add .eh_frame unwind tables just for a test?

@diegorusso @pablogsal: What do you think about the -funwind-tables flag?

Indeed, adding -funwind-tables for testing purposes was not sufficiently necessary, and I removed this change. Thanks for the review.

@yzewei yzewei force-pushed the loongarch-clang-unwind branch from 0a7ad9f to 809c42d Compare June 29, 2026 01:10
Comment thread Modules/_testinternalcapi.c
@vstinner vstinner added the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label Jun 29, 2026
@vstinner vstinner merged commit 8458582 into python:main Jun 29, 2026
75 checks passed
@miss-islington-app

Copy link
Copy Markdown

Thanks @yzewei for the PR, and @vstinner for merging it 🌮🎉.. I'm working now to backport this PR to: 3.15.
🐍🍒⛏🤖

@bedevere-app

bedevere-app Bot commented Jun 29, 2026

Copy link
Copy Markdown

GH-152578 is a backport of this pull request to the 3.15 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.15 pre-release feature fixes, bugs and security fixes label Jun 29, 2026
@vstinner

Copy link
Copy Markdown
Member

I merged your change, thanks for the fix!

@yzewei:

Indeed, adding -funwind-tables for testing purposes was not sufficiently necessary, and I removed this change. Thanks for the review.

Hum, all test_c_stack_unwind tests still pass on LoongArch (without -funwind-tables)?

@StanFromIreland:

Please also expand this to all risc64, see the changes in #152370.

"Loongson moved to their own processor instruction set architecture (ISA) in 2021 (...) The ISA has been referred to as "a fork of MIPS64r6" due to a perceived lack of changes judging from instruction listings"

say https://en.wikipedia.org/wiki/Loongson#LoongArch

IMO LoongArch is different enough from RISC-V that it deserves to have its own code path.

@yzewei

yzewei commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

I merged your change, thanks for the fix!

@yzewei:

Indeed, adding -funwind-tables for testing purposes was not sufficiently necessary, and I removed this change. Thanks for the review.

Hum, all test_c_stack_unwind tests still pass on LoongArch (without -funwind-tables)?

@StanFromIreland:

Please also expand this to all risc64, see the changes in #152370.

"Loongson moved to their own processor instruction set architecture (ISA) in 2021 (...) The ISA has been referred to as "a fork of MIPS64r6" due to a perceived lack of changes judging from instruction listings"

say https://en.wikipedia.org/wiki/Loongson#LoongArch

IMO LoongArch is different enough from RISC-V that it deserves to have its own code path.

Thanks for merging these changes!

Regarding the comparison between LoongArch and RISC-V: LoongArch is an independent Instruction Set Architecture (ISA), as clearly explained in the wiki.

Regarding the -funwind-tables flag: Without this flag, Clang/LoongArch builds do not generate the .eh_frame unwind tables required at runtime; although this PR fixes the tests, parts relying on GNU backtrace() would still fail.

In contrast, with GCC/LoongArch builds, GNU backtrace() works correctly even without this flag.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants