十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python AArch64 分支保护实战:`-X perf_jit` 性能剖析集成中的 BTI/PAC 汇编跳板

Python AArch64 分支保护实战:`-X perf_jit` 性能剖析集成中的 BTI/PAC 汇编跳板 Python AArch64 分支保护实战-X perf_jit性能剖析集成中的 BTI/PAC 汇编跳板【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpythonCPython 的-X perf_jit选项会让解释器把运行时生成的机器码通过 perf jitdump 接口暴露给 Linuxperf分析工具而 AArch64 平台上用于该机制的汇编跳板trampoline需要正确插入 BTIBranch Target Identification与 PACPointer Authentication Code指令。本文围绕 CPython 近期一项核心变更——Add branch protections for AArch64 (BTI/PAC) in assembly code used by-X perf_jit变更说明见 Misc/NEWS.d/next/Core_and_Builtins/2026-05-12-16-47-23.gh-issue-139808.iIs7_E.rst展开完整讲清 jitdump 工作原理、AArch64 跳板汇编的 BTI/PAC 条件编译逻辑、GNU property 段声明以及这些保护指令对 DWARF 栈展开信息生成的连带影响帮助你在 Arm 服务器上正确启用并理解 Python 性能剖析链路。-X perf_jit与 perf jitdump变更所处的技术场景要理解这条 NEWS 条目先要弄清它保护的是哪段代码。-X perf_jit是 CPython 的命令行选项用于启用与 Linuxperf剖析器的集成选项说明见 Doc/using/cmdline.rst 第 646 行附近完整使用指南见 Doc/howto/perf_profiling.rst。启用后Python 会在进程运行时动态生成的机器码如 JIT 跳板被perf采样时能够被正确解析为带符号、可栈回溯的代码。其底层是 Linux perf 的jitdump 接口实现位于 Python/perf_jit_trampoline.c。核心机制可以概括为jitdump 文件进程首次需要记录 JIT 代码时perf_map_jit_init()创建/tmp/jit-PID.dump文件并写入魔数0x4A695444JiTD、格式版本、ELF 目标架构、时间戳等头部信息内存映射作为信号该文件的第一页以PROT_READ | PROT_EXEC权限mmap进进程地址空间——perf正是通过扫描/proc/.../maps中匹配 jitdump 命名模式的映射来判断目标进程是否在使用该接口注意 macOS 上为兼容 samply 跳过了这一步映射事件写入每生成一段 JIT 代码就写入一个PerfUnwindingInfo事件含 DWARF.eh_frame数据供--call-graph dwarf栈回溯使用和一个PerfLoad事件含虚拟地址、代码尺寸、函数名以及机器码字节本身函数名按py::函数名:文件名格式生成便于在混合语言剖析结果中区分 Python 函数合成 DSO 的地址布局perf inject -j会为每段 JIT 代码合成 ELF 文件/tmp/jitted-PID-N.so包含 ELF 头、.text与 unwind 信息。为避免地址区间互相覆盖导致样本归属混乱CPython 在代码区域之间加入固定 paddingtrampoline_api.code_padding按 unwind 数据尺寸 16 字节对齐计算保证每个合成 DSO 的地址范围互不重叠。Doc/howto/perf_profiling.rst给出的典型使用方式为perf record需--call-graph dwarf才能在 JIT 代码内正确展开调用栈$ perf record -F 9999 -g -k 1 --call-graph dwarf -o perf.data python -Xperf_jit my_script.py也可以设置环境变量PYTHON_PERF_JIT_SUPPORT达到与-X perf_jit相同的效果见 Doc/howto/perf_profiling.rst 第 246 行附近。在 AArch64 上jitdump 记录的主要对象就是跳板trampoline——解释器与 JIT 生成的代码跳转到符号或相互调用时超过指令编码距离范围的目标需要一个中转跳板。这次变更正是让AArch64 跳板汇编与内核启用 BTI/PAC 时的分支保护要求保持一致。AArch64 跳板汇编结构与设计跳板实现位于 Python/asm_trampoline_aarch64.S。在 Linux 上导出的符号是_Py_trampoline_func_start/_Py_trampoline_func_endmacOS 下为带双下划线的__Py_trampoline_func_start等函数体极短SIGN_LR /* PAC 签名 LR条件存在 */ stp x29, x30, [sp, -16]! /* 保存帧指针 x29 与链接寄存器 x30 */ mov x29, sp /* 建立帧指针 */ blr x3 /* 经 x3 中保存的目标地址间接调用 */ ldp x29, x30, [sp], 16 /* 恢复 x29、x30 */ VERIFY_LR /* PAC 校验 LR条件存在 */ ret /* 返回 */SIGN_LR/VERIFY_LR是本次变更引入的条件汇编宏签名插入在保存寄存器之前校验位于ret之前、恢复 x30 之后位置选择与 PAC 的autiaspauthenticate and add implicit SP语义严格匹配。该汇编文件只在__aarch64__ __AARCH64EL__ !__ILP32__64 位 ARM 小端、非 ILP32条件下编译并在 Linux 目标末尾附加.note.GNU-stack段标记栈不可执行——这是安全加固PIE/RELRO 场景下避免可执行栈的常规做法。BTI分支目标标识指令的按需插入BTI 是 Armv8.5-A 引入的分支目标标识机制开启 BTI 的 CPU 要求所有间接分支br/blr等的目标地址处必须放置bti指令否则触发分支预测异常。汇编文件顶部用编译器特征宏条件启用第 7–15 行#if defined(__ARM_FEATURE_BTI_DEFAULT) __ARM_FEATURE_BTI_DEFAULT 1 #define BTI_J hint 36 /* bti j: for jumps, IE br instructions */ #define BTI_C hint 34 /* bti c: for calls, IE bl instructions */ #define GNU_PROPERTY_AARCH64_BTI 1 /* bit 0 GNU Notes is for BTI support */ #else #define BTI_J #define BTI_C #define GNU_PROPERTY_AARCH64_BTI 0 #endif要点用hint助记符而非bti助记符书写是为了保证汇编器在没有 BTI 指令支持的版本上也能编译通过由特征宏__ARM_FEATURE_BTI_DEFAULT由-mbranch-protectionstandard等编译选项触发决定是否实际生效bti cBTI_C针对调用类分支bl/blrbti jBTI_J针对跳转类分支br。由于该跳板只被blr类调用目标使用实际生效的是BTI_C——在 PAC 不可用的回退路径中SIGN_LR直接退化为BTI_C见下文。PAC指针认证对链接寄存器的签名与校验PACPointer Authentication Code用 CPU 内的密钥对寄存器值做认证签名可抵御返回地址篡改。AArch64 上最常见的是对 LRx30的paciasp/autiasp配对。汇编文件中的条件编译第 17–30 行#if defined(__ARM_FEATURE_PAC_DEFAULT) #if __ARM_FEATURE_PAC_DEFAULT 1 #define SIGN_LR hint 25 /* paciasp: sign with the A key */ #define VERIFY_LR hint 29 /* autiasp: verify with the A key */ #elif __ARM_FEATURE_PAC_DEFAULT 2 #define SIGN_LR hint 27 /* pacibsp: sign with the b key */ #define VERIFY_LR hint 31 /* autibsp: verify with the b key */ #endif #define GNU_PROPERTY_AARCH64_POINTER_AUTH 2 /* bit 1 GNU Notes is for PAC support */ #else #define SIGN_LR BTI_C #define VERIFY_LR #define GNU_PROPERTY_AARCH64_POINTER_AUTH 0 #endif可以看到三层递进__ARM_FEATURE_PAC_DEFAULT的低两位分别指示运行时默认启用A 密钥paciasp/autiasp还是B 密钥pacibsp/autibsp签名汇编据此选择hint编码回退路径如果只有 BTI 而 PAC 不可用SIGN_LR退化为BTI_C仍然提供分支目标保护VERIFY_LR为空无签名可验证两个特性都不可用时SIGN_LR/VERIFY_LR均为空操作跳板退化为最基础的保存—跳转—恢复序列行为与未启用分支保护前一致。这一设计保证了同一份跳板汇编能编译进不同-mbranch-protection等级的构建none/standard/full且与python主可执行文件自身的分支保护属性保持一致——否则动态加载/直接调用跳板时可能因保护属性不匹配而触发异常。GNU property 段向动态链接器声明保护能力仅插入bti/paciasp指令还不够进程的PT_GNU_PROPERTY动态段必须声明对应的 Arm64 特性内核与动态链接器才会在执行跳板时启用相应硬件机制。汇编文件末尾第 63–76 行手工构造了这个 ELF note#if GNU_PROPERTY_AARCH64_BTI ! 0 || GNU_PROPERTY_AARCH64_POINTER_AUTH ! 0 || GNU_PROPERTY_AARCH64_GCS ! 0 .pushsection .note.gnu.property, a; .balign 8; .long 4; .long 0x10; .long 0x5; /* NT_GNU_PROPERTY_TYPE_0 */ .asciz GNU; .long 0xc0000000; /* GNU_PROPERTY_AARCH64_FEATURE_1_AND */ .long 4; .long (GNU_PROPERTY_AARCH64_BTI|GNU_PROPERTY_AARCH64_POINTER_AUTH|GNU_PROPERTY_AARCH64_GCS); .long 0; .popsection; #endif其中0xc0000000是GNU_PROPERTY_AARCH64_FEATURE_1_AND属性类型数据字是三个 bit 的按位或bit 0 BTIbit 1 PACbit 2 GCSGuarded Control Stack代码同样预留了__ARM_FEATURE_GCS_DEFAULT的检测。只有当至少一个特性实际启用时才输出该段未启用分支保护的构建不会携带多余声明。文件头注释中引用的 Arm ABI 规范文档AAELF64说明了这些 GNU Note 的标准语义。连带影响DWARF 展开信息必须感知 BTI/PAC 指令这是容易被忽视、但对perf --call-graph dwarf正确性至关重要的一环。jitdump 文件中每个代码段都附带由 Python/jit_unwind.c 运行时生成的 DWARF.eh_frameCIE FDE展开描述必须与真实指令序列一一对应。由于SIGN_LR/VERIFY_LR是否存在是编译期决定的jit_unwind.c中的 FDE 生成逻辑同样用相同的特征宏条件分支Python/jit_unwind.c#elif defined(__aarch64__) defined(__AARCH64EL__) !defined(__ILP32__) /* AArch64 calling convention unwinding rules */ #if defined(__ARM_FEATURE_PAC_DEFAULT) || \ (defined(__ARM_FEATURE_BTI_DEFAULT) __ARM_FEATURE_BTI_DEFAULT 1) DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance past SIGN_LR (4 bytes) #endif #if defined(__ARM_FEATURE_PAC_DEFAULT) DWRF_U8(DWRF_CFA_AARCH64_negate_ra_state); // Saved LR is PAC-signed from here #endif DWRF_U8(DWRF_CFA_advance_loc | 1); // Advance by 1 instruction (4 bytes) DWRF_U8(DWRF_CFA_def_cfa_offset); // CFA SP 16 ...两个细节值得注意当 PAC 或 BTI 任一特性存在时展开序列先advance_loc 14 字节跨过SIGN_LR指令否则 CFA 偏移计算会与stp x29, x30, [sp, -16]!错位 4 字节当启用 PAC 时插入两条DWRF_CFA_AARCH64_negate_ra_stateDWARF 5 的 Arm64 专有操作码保存 x30 时 LR 处于已签名状态置位从栈上恢复后签名被消耗清除。展开器据此正确处理autiasp与ret之间的关系。同样的编译期一致性也体现在 Python/perf_jit_trampoline.c 初始化时按_PyJitUnwind_EhFrameSize(0)实测 EH 数据尺寸来计算code_padding——EH 数据因 BTI/PAC 指令的存在而变大padding 随之自动适配无需人工维护常量文件头注释中提到的历史常量值 0x50 即由此机制取代。与 JIT 跳板 arena 的关系除了asm_trampoline_aarch64.S中的通用跳板函数JIT 代码页内部还会按目标符号动态生成跳板槽位Python/jit.c 中的patch_aarch64_trampoline()从跳板 arena 中取TRAMPOLINE_SIZE大小的槽位并逐字写入跳转指令第 572 行附近跳板总数按符号掩码统计第 657–667 行并计入代码页尺寸与jit_trampoline_size统计。这些 JIT 代码页在-X perf_jit下正是通过上文_PyPerfJit_WriteNamedCode→ jitdump 事件被perf记录的路径因此汇编跳板的分支保护属性会沿arena 跳板 汇编跳板 jitdump 展开信息整条链路生效。验证路径与相关测试仓库中与该集成相关的测试入口Lib/test/test_perf_profiler.py验证 Linux perf jitdump 集成生成与解析 jitdump 文件Lib/test/test_samply_profiler.py验证 macOS 上 samply 消费同一 jitdump 接口的场景该测试解释了源码中 macOS 跳过 mmap 信号、时间戳改用mach_absolute_time()时钟域等分支命令行选项本身的行为在 Lib/test/test_cmd_line.py 中覆盖-X perf_jit的解析。在真实 AArch64 系统上验证 BTI/PAC 是否按预期生效可以从两个层面入手均为只读检查一是用readelf -n查看 python 二进制的GNU Property段确认AARCH64_FEATURE_1的 bit 0/bit 1 置位情况与编译时的__ARM_FEATURE_BTI_DEFAULT/__ARM_FEATURE_PAC_DEFAULT一致二是运行perf record -F 9999 -g -k 1 --call-graph dwarf -o perf.data python -Xperf_jit my_script.py后检查perf report能否在py::前缀的符号上正确展开调用栈——若展开描述与实际指令错位通常正是jit_unwind.c与汇编跳板的条件编译宏不同步所致。小结这条 NEWS 条目对应的变更看似只是给汇编加两条指令实际牵动四处代码保持严格的编译期同步跳板汇编中的SIGN_LR/VERIFY_LR条件宏Python/asm_trampoline_aarch64.S、.note.gnu.property段的特性位声明、运行时 DWARF FDE 生成的advance_loc与negate_ra_state序列Python/jit_unwind.c以及 jitdump padding 按 EH 尺寸自适应的计算Python/perf_jit_trampoline.c。理解这条链路你才能在启用-mbranch-protection的 Arm 服务器上放心使用-X perf_jit做 Python 性能剖析并能在展开异常时快速定位是汇编、GNU property 还是展开描述三者的哪一侧失配。【免费下载链接】cpythonThe Python programming language项目地址: https://gitcode.com/GitHub_Trending/cp/cpython创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表