栈回溯基础

指令集区分

thumb指令集

  • 长度:thumb指令通常是 16 位。
  • 特点:thumb 指令集是为了压缩指令集长度减少程序占用空间。
  • 对齐方式:2字节对齐,存放 thumb 指令的地址一般会被+1,设置为奇数,用于表示地址上存放的是 thumb 指令。

thumb-2指令集

  • 长度:thumb-2 扩展了thumb指令集,可以是16位或 32位。
  • 特点:32 位长的 thumb-2 指令通过特定的编码模式来表示,指令通常具有一些特定前缀,例如:‘0b11101’‘0b11110’‘0b11111’
  • 对齐方式:2字节对齐,存放 thumb 指令的地址一般会被+1,设置为奇数,用于表示地址上存放的是 thumb 指令。

arm 指令集

  • 长度:arm指令固定为32位。

  • 特点:arm 指令不区分前缀,因为所有arm指令都是32位长,它们是统一的,没有特别的前缀来表示。arm指令集通过处理器的状态决定使用,与thumb指令集通过特定指令(如bx, blx)进行状态切换。

  • 对齐方式:4字节对齐。

Thumb 和 arm 的切换

  • 切换到 arm 模式:在使用 bx 或 blx 是确保地址的最低位为0。
  • 切换到 thumb 模式:在使用 bx 或 blx 是确保地址的最低位为1。

例如:

// 切换到 thumb 模式
add r3, pc, #1
bx r3

  当使用bx跳转指令,跳到一个奇数地址时,实际上是跳到这个 奇数地址 - 1 的位置,然后CPSR寄存器的标志位T位会置1,表示切换到Thumb指令集,执行后面的指令。

函数跳转指令

  ARM 指令集中的跳转指令可以完成从当前指令向前或向后地址空间的跳转,最大 32MB 跳转范围,包括以下 4 条指令:

  • B 跳转指令
  • BL 带返回的跳转指令
  • BLX 带返回的跳转指令,并根据目标地址的最低位切换处理器状态(ARM 或 Thumb(指令+1为奇数表示))
  • BX 不返回跳转指令,并根据目标地址的最低位切换处理器状态(ARM 或 Thumb(指令+1为奇数表示))

B 指令

介绍

  B 指令是最简单的跳转指令,跳转后不在返回。存储在跳转指令中的实际值是相对当前 PC 值的一个偏移量,而不是一个绝对地址,它的值由汇编器来计算(参考寻址方式中的相对寻址)。

格式:

T1 有条件跳转指令:B<c> <label>
比特位15~1211~87~0
指令码1101condimm8

cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
imm8:为8位的立即数(label 偏移量),最大跳转范围 range(-256 ~ 254)
实际偏移量计算:

imm32 = SignExtend(imm8:'0', 32); 

注意:cond 不可为‘1110’ 和‘1111’

适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7

T2 无条件跳转指令:B<label>
比特位15~1110~0
指令码11100imm11

imm11:为11位的立即数(label 偏移量),最大跳转范围 range(-2048 ~ 2046)
实际偏移量计算:

imm32 = SignExtend(imm11:'0', 32);

适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7

T3 有条件跳转指令:B<c>.W <label>
比特位15~11109~65~015~1413121110~0
指令码11110Scondimm610J10J2imm11

S:符号位
cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
J1/J2:计算参数位
imm6/imm11:立即数,最大跳转范围range(-1048576 ~ 1048574)
实际偏移量计算:

imm32 = SignExtend(S:J2:J1:imm6:imm11:'0', 32); 

适用架构:ARMv6T2, ARMv7

T4 无条件跳转指令:B.W <label>
比特位15~11109~015~1413121110~0
指令码11110Simm1010J11J2imm11

S:符号位
J1/J2:计算参数位
imm10/imm11:立即数,最大跳转范围 range (-16777216 ~ 16777214)
实际偏移量计算

I1 = NOT(J1 EOR S);
I2 = NOT(J2 EOR S);
imm32 = SignExtend(S:I1:I2:imm10:imm11:'0', 32); 

适用架构:ARMv6T2, ARMv7

A1 arm 有条件跳转指令:B<c><label>
比特位31~2827~2423~0
指令码cond1010imm24

cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
imm24:立即数,最大跳转范围 range (-33554432 ~ 33554428)
实际偏移量计算:

imm32 = SignExtend(imm24:'00', 32); 

适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7

BX 指令

介绍

  BX 指令中跳转到寄存器所指定的目标地址, 目标地址处的指令既可以是ARM 指令,也可以是Thumb指令。跳转后不在返回,并且根据寄存器中地址的最低位是否为 1 决定使用 Thumb模式还是 arm 模式。

格式

T1 无条件跳转指令:BX<Rm>
比特位15~109~876~32~0
指令码010001110Rm000

Rm:寄存器R1、LR
实际偏移量计算:

m = Uint(Rm); 

适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7

A1 arm 有条件跳转指令:BX<c> <Rm>
比特位31~2827~2019~1615~1211~87~43~0
指令码cond000100101111111111110001Rm

cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
Rm:寄存器R1、LR
实际偏移量计算:

m = Uint(Rm); 

适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7

BL 指令

介绍

  BL 是一个带返回的跳转指令,跳转之前,会在寄存器 R14 中保存当前指令的下一条指令地址,因此,可以通过将 R14 的内容重新加载到 PC 中,来返回到跳转指令之前的位置继续执行。

格式

T1 无条件跳转指令:BL <label>
比特位15~11109~015~1413121110~0
指令码11110Simm1011J11J2imm11

S:符号位
imm6/imm11:立即数,最大跳转范围 range(-16777216 ~ 16777214)
实际偏移量计算:

I1 = NOT(J1 EOR S); 
I2 = NOT(J2 EOR S); 
imm32 = SignExtend(S:I1:I2:imm10:imm11:'0', 32); 

适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7 if J1 == J2 == 1 ARMv6T2, ARMv7 otherwise

A1 arm 有条件跳转指令:BL<c> <label>
比特位31~2827~2423~0
指令码cond1011imm24

cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
imm24:立即数,最大跳转范围 range (-33554432 ~ 33554428)
实际偏移量计算:

imm32 = SignExtend(imm24:'0', 32); 

适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7

BLX 指令

介绍

  BLX 指令是一个即带有返回值的跳转指令,同时也能指示在调整之后使用 Thumb 模式还是 arm 模式,在寄存器 R14 中保存当前指令的下一条指令地址,用于返回调用前的位置。

格式

T1 无条件立即数跳转指令:BLX<label>
比特位15~11109~015~1413121110~10
指令码11110Simm10H11J10J2imm10LH

S:符号位
imm10H/imm10L:立即数,最大跳转范围 range(-16777216 ~ 16777212)
H
实际偏移量计算:

if CurrentInstrSet() == InstrSet_ThumbEE || H == ‘1’ then UNDEFINED;
I1 = NOT(J1 EOR S); I2 = NOT(J2 EOR S); 
imm32 = SignExtend(S:I1:I2:imm10H:imm10L:’00’, 32); 
targetInstrSet = InstrSet_ARM; 

适用架构:ARMv5T*, ARMv6*, ARMv7 if J1 == J2 == 1ARMv6T2, ARMv7 otherwise

T2 无条件寄存器跳转指令:BLX<Rm>
比特位15~109~876~32~0
指令码010001111Rm000

cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
Rm:寄存器R1、LR
实际偏移量计算:

m = Uint(Rm); 

适用架构:ARMv5T*, ARMv6*, ARMv7

A1 arm 无条件立即数跳转指令:BLX<label>
比特位31~2827~252423~0
指令码1111101Himm24

H
imm24:立即数,最大跳转范围 range (-33554432 ~ 33554428)
实际偏移量计算:

imm32 = SignExtend(imm24:H:'0', 32); 
targetInstrSet = InstrSet_Thumb; 

适用架构:ARMv5T*, ARMv6*, ARMv7

A2 arm 无条件寄存器跳转指令:BLX<c> <Rm>
比特位31~2827~2019~1615~1211~87~43~0
指令码cond000100101111111111110011Rm

cond :为条件码指令,例如EQ、NE等,可以根据条件决定是否进行跳转
Rm:寄存器R1、LR
实际偏移量计算:

m = Uint(Rm); 

适用架构:ARMv5T*, ARMv6*, ARMv7

栈操作指令

PUSH 指令

介绍

  PUSH 指令一般在函数开始的地方出现,专门用于将寄存器进行压栈操作。在ARM的指令系统中,满栈递减栈入栈操作的参数入栈顺序是从右到左依次入栈,而参数的出栈顺序则是从左到右的逆操作。对于递增栈,相应的操作则全部取反。

格式

T1 多寄存器压栈指令:PUSH<c> <registers>
比特位15~121110~987~0
指令码1011010Mreg_list

M:表示LR寄存器是否存在于 reg_list 列表中
reg_list:待入栈寄存器列表,以 bit 位表示,真正的寄存器列表 registers = ‘0’:M:‘000000’:reg_list

注:在 thumb1 指令下寄存器过多时可能会使用T1压栈指令进行2次压栈。
适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7

T2 多寄存器压栈指令:PUSH<c>.W <registers>
比特位15~1110~98~65~015141312~0
指令码11101001001 0 11010M0reg_list

M:表示LR寄存器
reg_list:待入栈寄存器列表,以 bit 位表示,真正的寄存器列表 registers = ‘0’:M:‘0’:reg_list

注:reg_list 列表中的寄存器个数小于2个则会出错误。
适用架构:ARMv6T2, ARMv7

T3 单寄存器压栈指令:PUSH<c>.W <register>
比特位15~1110~98~015~1211~0
指令码11111000 0 10 0 1101Rt1 101 00000100

Rt:表示入栈寄存器的值,例如 r3 时 Rt 就是 3
计算方式:

t = UInt(Rt); 			// 转 Rt 转无符号整数
registers = Zeros(16);	// 创建16位寄存器,初始化为0
registers<t> = '1'; 	// 将16位寄存器的第 t 位设置为1

注:t 的值不能为 13 或 15,即不能压栈SP 和 PC 寄存器
适用架构:ARMv6T2, ARMv7

A1 arm 多寄存器压栈指令:PUSH<c> <registers>
比特位31~2827~2019~1615~0
指令码cond100100 1 01101reg_list

cond:条件码,可以根据条件码决定是否压栈
reg_list:待入栈寄存器列表,以 bit 位表示某个寄存器,registers = reg_list

注:如果压栈小于两个寄存器则使用 STMDB / STMFD 指令
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7

A2 arm 单寄存器压栈指令:PUSH<c> <registers>
比特位31~2827~2019~1615~1211~0
指令码cond010 1 0 0 1 01101Rt000000000100

cond:条件码,可以根据条件码决定是否压栈
Rt:需要压栈的寄存器值,r3 就是 3

适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7

STMDB (STMFD) 指令

介绍

  STMDB 指令将寄存器的值存储到指定的内存地址,这个内存地址可以是堆栈,也可以是其他内存地址,常用于函数入口处的压栈操作,其中 DB 表示 down before 先递减,然后将寄存器值存入,在ARM的指令系统中,满栈递减栈入栈操作的参数入栈顺序是从右到左依次入栈,而参数的出栈顺序则是从左到右的逆操作。

格式

T1 多寄存器存储指令:STMDB<Rn>{!}, <registers>
比特位15~1110~98~6543~015141312~0
指令码1110100100W0Rn0M0reg_list

W:Writeback 写回标志,表示对 Rn 地址的操作是否影响 Rn 寄存器
Rn:Rn 基地址寄存器,需要操作的寄存器,Rn! 是会自动设置W=1,例如 sp 时Rn=13
M:表示LR寄存器
reg_list:待写入的寄存器列表, registers = ‘0’:M:‘0’:reg_list

注:W=1且Rn =‘1101’ t是为PUSH指令
适用架构:ARMv6T2, ARMv7

A1 arm 多寄存器存储指令:STMDB<c> <Rn>{!}, <registers>
比特位31~2827~22212019~1615~0
指令码cond100100W0Rnreg_list

cond:条件码,可以根据条件码决定是否压栈
W:Writeback 写回标志,表示对 Rn 地址的操作是否影响 Rn 寄存器
Rn:Rn 基地址寄存器,需要操作的寄存器,Rn! 是会自动设置W=1,例如 sp 时Rn=13
reg_list:reg_list:待写入的寄存器列表, registers = reg_list

注:W=1且Rn =‘1101’ t是为PUSH指令
适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7

栈空间申请操作

SUB 指令

介绍

  SUB 指令可以用于栈申请。在ARM汇编语言中,可以使用SUB指令来调整堆栈指针的值,从而为栈的使用提供空间。

格式

T1 栈申请指令:SUB SP,SP,#<imm>
比特位15~1211~876~0
指令码101100001imm7

imm7:需要减去的立即数
实际值:

imm32 = ZeroExtend(imm7:'00', 32); 

适用架构:ARMv4T, ARMv5T*, ARMv6*, ARMv7

T2 栈申请指令:SUB{S}.W <Rd>, SP, #<const>
比特位15~11109~543~01514~1211~87~0
指令码11110i0 1101S11010imm3Rdimm8

i:参与立即数计算
S:符号位
Rd:省略时默认为SP寄存器
imm3/imm8:需要减去的立即数组合位
实际值计算:

imm32 = ThumbExpandImm(i:imm3:imm8)

适用架构:ARMv6T2, ARMv7

T3 栈申请指令:SUBW<Rd>, SP, #<imm12>
比特位15~11109~543~01514~1211~87~0
指令码11110i1 0101011010imm3Rdimm8

i:参与立即数计算
Rd:省略时默认为SP寄存器
imm3/imm8:需要减去的立即数组合位
实际值计算:

imm32 = ZeroExtend(i:imm3:imm8, 32)

适用架构:ARMv6T2, ARMv7

A1 arm 栈申请指令:SUB{S}<c> <Rd>, SP, #<const>
比特位31~2827~212019~1615~1211~0
指令码cond00 1 0010S1101Rdimm12

cond:条件码,可以根据条件码决定是否压栈
S:符号位
Rd:省略时默认为SP寄存器
imm12:需要减去的立即数组合位
实际值计算:

imm32 = ARMExpandImm(imm12)

适用架构:ARMv4*, ARMv5T*, ARMv6*, ARMv7

cond 条件码

ABI下入栈规范

APCS 规范

  APCS 规范在ARM架构上定义了程序函数调用和栈帧定义以及寄存器的使用的规范,中定义了 FP 和IP 寄存器的作用,进行子函数调用时会都会进行 push {fp, ip, lr, pc} 压栈操作。

APCSReg意义
fpr11栈帧指针寄存器,指向函数栈开始位置
ipr12临时变量寄存器
spr13栈指针寄存器
lrr14链接寄存器
pcr15程序位置寄存器

AAPCS 规范

  AAPCS 属于型的规范,在AAPCS 规范发布之后 APCS 规范基本不在使用,并且在新规范中精简了压栈的寄存器个数。

AAPCSReg意义
spr13栈指针寄存器
lrr14链接寄存器
pcr15程序位置寄存器

函数调用和异常发生的LR行为

  子函数调用函数时前,CPU会自动更新 LR 的值为当前地址的下一条指令地址,注意:是自动,程序员不用管,相当于跟 BX 自动绑定了。更新的值就是下一步要执行的地址。
  如果是中断调用则会把 LR 的值更新为 EXC_RETURN 的值,出现异常时栈中保存的 PC 是发生异常时执行的指令地址。

参考文档

Armv7-M Architecture Reference Manual

🌀路西法 的个人博客拥有更多美文等你来读。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/70180.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Pytorch论文实现之GAN-C约束鉴别器训练自己的数据集

简介 简介:这次介绍复现的论文主要是约束判别器的函数空间,作者认为原来的损失函数在优化判别器关于真样本和假样本的相对输出缺乏显式约束,因为在实践中,在优化生成器时,鉴别器对生成样本的输出会增加,但对真实数据保持不变,而优化鉴别器会导致其对真实数据的输出增加…

Pyecharts系列课程06——热力图(Heatmap)

1. 基础使用 热力图是一种用于展示数据分布的密度或热度的图表,通过颜色深浅来表示数值大小。 a. 简单示例 我们先来看一个简单示例: 简单示例 from pyecharts.charts import HeatMapx_data = ["分类1", "分类2", "分类3"] y_data

交换路由——控制VLAN之间通信

项目 最近一段时间,A公司发现划分VLAN之后,网速提高很多,发生拥堵的情况消失了.但是,部门之间不能互联,也给办公室带来不便.公司要求项目实施各VLAN内主机互通。 部门 VLAN 名称 端口范围 网络ID 计算机 市场部 VLAN 10 shichang f0/1-f/010 192.168.10.0/24 pc0,pc…

使用 Redis 实现 RBAC 权限管理

1. 什么是 RBAC&#xff1f; RBAC&#xff08;Role-Based Access Control&#xff0c;基于角色的访问控制&#xff09;是一种常见的权限管理模型&#xff0c;它通过用户&#xff08;User&#xff09;、角色&#xff08;Role&#xff09;、权限&#xff08;Permission&#xff…

qt-C++笔记之QGraphicsScene和 QGraphicsView中setScene、通过scene得到view、通过view得scene

qt-C++笔记之QGraphicsScene和 QGraphicsView中setScene、通过scene得到view、通过view得scene code review! 文章目录 qt-C++笔记之QGraphicsScene和 QGraphicsView中setScene、通过scene得到view、通过view得scene1.`setScene` 方法2.通过 `scene` 获取它的视图 (`views()`)…

DeepSeek频繁宕机应对方案

第三方监测显示&#xff0c;38%的企业因AI工具不稳定错失热点流量&#xff08;Gartner 2025&#xff09;。当竞品1小时内发布300篇行业内容时&#xff0c;你可能还在为「服务器繁忙」提示焦头烂额。147SEO系统通过智能容错机制&#xff0c;帮助某本地生活平台稳定输出580篇地域…

CentOS/RHEL如何更换国内Yum源

在国内使用CentOS或RHEL系统时&#xff0c;默认的Yum源是国外的&#xff0c;这可能导致软件包的下载速度慢&#xff0c;甚至出现连接超时的问题。为了解决这个问题&#xff0c;我们可以将Yum源切换到国内的镜像源&#xff0c;从而大大提高软件包的下载速度和稳定性。 本文将详…

cs224w课程学习笔记-第2课

cs224w课程学习笔记-第2课 传统图学习 前言一、节点任务1、任务背景2、特征节点度3、特征节点中心性3.1 特征向量中心性&#xff08;Eigenvector Centrality&#xff09;3.2 中介中心性&#xff08;Betweenness Centrality&#xff09;3.3 接近中心性&#xff08;Closeness Cen…

【设计模式】【结构型模式】代理模式(Proxy)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…

平板作为电脑拓展屏

有线串流&#xff08;速度更快&#xff09; spacedesk 打开usb对安卓的连接 用usb线直接连接电脑和平板 无线串流&#xff08;延迟高&#xff0c;不推荐&#xff09; todesk pc和手机端同时下载软件&#xff0c;连接后可以进行远程控制或扩展屏幕 spacedesk 连接到同一个…

[文末数据集]ML.NET库学习010:URL是否具有恶意性分类

文章目录 ML.NET库学习010:URL是否具有恶意性分类项目主要目的和原理项目概述主要功能和步骤总结数据集地址ML.NET库学习010:URL是否具有恶意性分类 项目主要目的和原理 项目主要目的: 本项目的目的是通过分析URL的特征,构建一个机器学习模型来判断给定的URL是否具有恶意…

Zotero PDF Translate插件配置百度翻译api

Zotero PDF Translate插件可以使用几种翻译api&#xff0c;虽然谷歌最好用&#xff0c;但是由于众所周知的原因&#xff0c;不稳定。而cnki有字数限制&#xff0c;有道有时也不行。其他的翻译需要申请密钥。本文以百度为例&#xff0c;进行申请 官方有申请教程&#xff1a; Zot…

具身智能在智能巡检机器人中的应用——以开关柜带电操作机器人为例

随着机器人技术和人工智能的迅速发展&#xff0c;具身智能在各行业的应用日益广泛&#xff0c;尤其是在电力行业中的智能巡检领域。传统的电力巡检和维护工作通常需要人工操作&#xff0c;存在着高温、高压、强电磁场等危险环境&#xff0c;且效率较低。开关柜带电操作机器人作…

网络工程师 (43)IP数据报

前言 IP数据报是互联网传输控制协议&#xff08;Internet Protocol&#xff0c;IP&#xff09;的数据报格式&#xff0c;由首部和数据两部分组成。 一、首部 IP数据报的首部是控制部分&#xff0c;包含了数据报传输和处理所需的各种信息。首部可以分为固定部分和可变部分。 固定…

【SpringBoot苍穹外卖】debugDay0 打开前端页面

在某一天学完后&#xff0c;电脑关机&#xff0c;再打开啥都忘了&#xff0c;记起来一点点&#xff0c;前端页面打不开&#xff0c;后端控制台一直循环出错。原来是下面这样哈哈。 查看端口是否被别的程序占用的操作步骤 winR输入cmd打开命令行 netstat -ano | findstr "8…

docker 运行 芋道微服务

jar包打包命令 mvn clean install package -Dmaven.test.skiptrue创建文件夹 docker-ai 文件夹下放入需要jar包的文件夹及 docker-compose.yml 文件 docker-compose.yml 内容&#xff1a;我这里的是ai服务&#xff0c;所以将原先的文件内容做了变更&#xff0c;你们需要用到什…

MySQL-事务隔离级别

事务有四大特性&#xff08;ACID&#xff09;&#xff1a;原子性&#xff0c;一致性&#xff0c;隔离性和持久性。隔离性一般在事务并发的时候需要保证事务的隔离性&#xff0c;事务并发会出现很多问题&#xff0c;包括脏写&#xff0c;脏读&#xff0c;不可重复读&#xff0c;…

【MediaTek】 T750 openwrt-23.05编 cannot find dependency libexpat for libmesode

MediaTek T750 T750 采用先进的 7nm 制程,高度集成 5G 调制解调器和四核 Arm CPU,提供较强的功能和配置,设备制造商得以打造精巧的高性能 CPE 产品,如固定无线接入(FWA)路由器和移动热点。 MediaTek T750 平台是一款综合的芯片组,集成了 5G SoC MT6890、12nm 制程…

五十天精通硬件设计第32天-S参数

系列文章传送门 50天精通硬件设计第一天-总体规划-CSDN博客 目录 1. S参数基础 2. S参数在信号完整性中的作用 3. 单端 vs. 差分S参数 4. S参数的关键特性 5. S参数的获取与使用 6. S参数分析中的常见问题 7. 实际案例:PCIe通道分析 8. 工具推荐 总结 信号完整性中…

pytest asyncio 支持插件 pytest-asyncio

pytest 是 Python 测试框架&#xff0c;但其不支持基于 asyncio 的异步程序&#xff08;例如&#xff0c;测试 FastAPI 异步代码&#xff09;&#xff0c;pytest-asyncio 是一个 pytest 插件&#xff0c;该插件赋予 pytest 可以测试使用 asyncio 库代码的能力。 https://github…