💻 计算机组成原理(PART B)
请注意,本文最近一次更新于:2022-07-25,文章内容可能已经不具有时效性,请谨慎参考
本文最后更新于:2022年7月25日星期一下午2点59分 +08:00
计算机组成原理B,这里开始学习汇编了
AL
DOSBox入门
DOSBoxⅠ
DOSBoxⅡ
DOSBoxⅢ
DOSBoxⅣ
DOSBoxⅤ
DOSBox Ⅵ
DOSBox 指令补充Ⅰ
DOSBox不完全指北
DOSBOX 指令补充Ⅰ
中断代码 | 功能 | 入口参数 | 出口参数 |
---|---|---|---|
INT 20 | 程序正常退出 | CS=PSP段地址 | |
INT 21 | 系统功能调用 | AH=功能号 | |
INT 22 | 程序结束处理 | ||
INT 23 | Ctrl-Break处理 | AL=0(忽略) | |
INT 24 | 严重错误处理 | AL=驱动器号 | AL=1(重试) AL=2(通过INT 23H终止) Cy=1(出错) |
INT 25 | 绝对磁盘读 | CX=读入扇区数 DX=起始逻辑扇区数 DS:BX=缓冲区地址 AL=驱动器号 |
Cy=0(正确) |
INT 26 | 绝对磁盘写 | CX=读入扇区数 DX=起始逻辑扇区数 DS:BX=缓冲区地址 |
|
INT 27 | 驻留退出 | CS=PSP段地址 DX=程序末地址+1 |
功能号设置在AH中,设置好其余入口参数后向DOS发出INT 21H指令,最后得到出口参数
由于太多啦,这里只给出一些常见的
|调用代码|功能|入口参数|出口参数|
|—|—|—|—|
|00H|程序终止|CS=PSP段地址| |
|01H|键盘键入字符| |AL=输入的字符|
|02H|显示输出|DL=显示的字符| |
|03H|串行设备输入| |AL=输入的字符|
|04H|串行设备输出|DL=输出的字符| |
|05H|打印输出|DL=输出的字符| |
|06H|直接控制台I/O|DL=0FFH(输入请求)
DL=字符(输出请求)|AL=输入的字符|
|07H|直接控制台I/O(不显示输入)| |AL=输入的字符|
|08H|键盘输入字符(无回显)| | |
|09H|显示字符串|DS:DX=缓冲区首址| |
|0AH|输入字符串|DS:DX=缓冲区首址| |
|0BH|检查标准输入状态| |AL=00(无按键)
AL=0FFH(有按键)|
|0CH|清除输入缓冲区
并执行指定的标准输入功能|AL=功能号
DS:DX=缓冲区首址|AL=输入的数据|
|4CH|带返回码的结果|AL=进程返回码| |
功能速览
DOSBox 初级汇编基础
- DOSBox是一种模拟器软件,主要是在IBM PC兼容机下,模拟旧时的操作系统:MS-DOS
- 支持许多IBM PC兼容的显卡和声卡,为本地的DOS程序提供执行环境,使这些程序可以正常运行于大多数现代计算机上的不同操作系统
- 获取
获取方式点我就行啦
提取码:hiaj - 安装
安装流程不能再简单啦,不建议放在C盘噢
- 指令
1
mount X dir %一行出奇迹
- 参数说明一下下:
- X: 虚拟盘命名,随意命名,比如C,G
- dir: 虚拟盘创建的文件夹位置,比如D:\DOSBox\SourceCode
- 参数说明一下下:
- 比如我在D:\DOSBox\SourceCode目录下有一个hello.asm,下面以此为例说明运行asm的流程
- 流程如下
- 创建虚拟环境
- 进入虚拟环境
- 编译asm源文件
- 链接编译为可执行文件
- 执行并大功告成
- 关于debug
- debug asm文件需要有
edit.com
,如果压缩包中没有读者可以前往官网下载 - edit.com文件建议放置在放置asm源文件的同级目录中,这样方便直接在DOSBox中直接调用
- 调用debug流程方法
- 创建虚拟环境
- 进入虚拟环境
- 执行edit指令
1
2
3
4
5
6
7
8
9
10
11%运行asm
mount g d:\DOSBox\SourceCode %创建虚拟环境
g: %进入虚拟环境
masm hello.asm %编译源文件
link hello.obj %编译可执行文件
hello.exe %运行可执行文件
%调试asm
mount g d:\DOSBox\SourceCode
g:
edit hello.asm %在edit中打开asm文件
edit hello.LST %打开hellO的清单文件,LST是后续断点调试很重要的文件诺
- debug asm文件需要有
高级调试入门
- 反汇编就是将存储器中的二进制数据翻译成有意义的助记符的形式,用以帮助理解程序,常用命令格式如下:
- -U:从当前指令指针IP指示位置开始对约连续32字节内容反汇编,需要注意的是,反汇编的结果可能并不完全是程序内容,这是因为从指示位置起始到程序结束可能本身没有32字节长度
- -U offset:offset指指定的偏移地址,将从该地址开始对连续约32字节内容反汇编,例如-U 0123就将从IP=0123开始反汇编
- -U ofs1 ofs2:设定两个指定的偏移地址,将对这两个偏移地址区间内程序反汇编
- 用以显示或修改寄存器内容
- 在显示寄存器内容时,标志寄存器F或程序状态字寄存器PSW将被表示为各个分离的标志位,其表示意义如下所示:
# 溢出 方向 中断 符号 零 辅助进位 奇偶 进位 0 NV UP DI PL NZ NA PO NC 1 OV DN EI NG ZR AC PE CY - 常用命令格式如下:
- -R:显示所有寄存器当前的内容和当前即将执行的指令
- -R RX:RX为指定寄存器名,限制指定的寄存器当前的内容,并等待键入新值,如果不想键入数值可以直接回车,比如-R AX Enter就是显示AX寄存器的内容而不加修改
- -R F:显示标志寄存器F的各个标志位的内容并等待键入新的标志位,同样地,无需修改直接回车
- 让程序在Debug控制下运行,一般有全程和断电运行两种方式,指令如下
- -G:控制程序在当前IP处运行直到程序结束,用于快速观察程序的运行情况
- -G ofs:控制程序由当前IP处运行,直至指定的断电IP=ofs处,此时程序暂停并显示哥哥寄存器的当前值以及断点处指令,同时返回Debug提示符”-“
- 控制程序运行一条指令后暂停,并显示各个寄存器的当前值及断点处指令,然后返回Debug提示符”-“
- -T:就没什么说的啦
- 单步命令一般用于需对程序运行作仔细分析的地方,如判断分支转移、观察运算结果等
- 以十六进制以及ASCII码两种方式显示内存区的二进制数据,通常用来观察数据段内的缓冲区内容,常见指令格式如下:
- -D:从0000单元开始,连续显示128个内存单元的内容
- -D ofs ofs:从指定单元开始显示到指定单元结束
- 用于在Debug环境下直接键入汇编语言语句,生成简单的可执行代码而不必经过完整的汇编语言编程步骤,或用来在调试过程中临时修改某条指令,常见指令格式如下:
- -A:从当前IP处输入汇编语句
- -A ofs:从指定的IP=ofs处输入汇编语句
- Debug模式默认使用十六进制,故在输入时不能使用H
- 用来将被调试程序重新转载进内存中,一般用于程序运行结束后继续进行调试程序,或者从头开始调试程序
- 键入Q退出调试状态,回到DOSBox界面
DOSBox实验指南
实验一: 顺序结构
- 本实例旨在利用顺序结构实现计算$w=\frac{(v-(x\times y+z-2))}{x}$的计算,其中x,y,z,v均为有符号字类型数据,将x,y,z,v的值存放在字变量X,Y,Z,V中,将结果存放在双变量W中
- 汇编语言作为底层语言是无法像高级语言一样轻松实现运算结果,这要求我们按照优先级顺序拆分运算表达式合理设计程序,下面给出
mermaid
流程图graph TD n0(开始) ==> n1 n1["x*y->(BX:CX)"] ==> n2 n2["将z扩展为双字->(DX:AX)"] ==> n3 n3["(BX:CX)+(DX:AX)->(BX:CX)"]==>n4 n4["(BX:CX)-2->(BX:CX)"]==>n5 n5["将V扩展为双字->(DX:AX)"]==>n6 n6["(BX:AX)-(BX:CX)->(DX:AX)"]==>n7 n7["(DX:AX)/X,商->W,余数->W+2"]==>id(结束)
- 代码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52DATA SEGMENT
X DW 2
Y DW 2
Z DW 3
V DW 10
W DW 2 DUP(?)
DATA ENDS
STACK SEGMENT STACK PARA STACK 'STACK'
DB 200 DUP(0)
BUFFER DB 10 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME DS:DATA,CS:CODE,SS:STACK
START: MOV AX, SEG DATA
MOV DS, AX ;DATA→AX
LEA SI, BUFFER
MOV AX, X
IMUL Y ;(x)*(y)→DX:AX
MOV CX, AX
MOV BX, DX ;(DX:AX)→(BX:CX)
MOV AX, Z
CWD ;(Z) 符号扩展
ADD CX, AX
ADC BX, DX ;(BX:CX)+(DX:AX)→(BX:CX)
SUB CX, 2
SBB BX,0 ;(BX:CX)-2→(BX:CX)
MOV AX,V
CWD ;(V)符号扩展
SUB AX,CX
SBB DX,BX ;(DX:AX)-(BX:CX)→(DX:X)
IDIV X ;(DX:AX)/X
MOV DL,AL
PUSH CX
MOV CL,4
SHR AL,CL
OR AL,30H
MOV [SI],AL
INC SI
MOV AL,DL
AND AL,0FH
OR AL,30H
MOV [SI],AL
INC SI
MOV AL,'$'
MOV [SI],AL
MOV DX,OFFSET BUFFER
MOV AH,09H
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS ;退出DOS 状态
END STARTAL 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52DATA SEGMENT
X DW 2
Y DW 2
Z DW 3
V DW 10
W DW 2 DUP(?)
DATA ENDS
STACK SEGMENT STACK PARA STACK 'STACK'
DB 200 DUP(0)
BUFFER DB 10 DUP(?)
STACK ENDS
CODE SEGMENT
ASSUME DS:DATA,CS:CODE,SS:STACK
START: MOV AX, SEG DATA
MOV DS, AX ;DATA→AX
LEA SI, BUFFER
MOV AX, X
IMUL Y ;(x)*(y)→DX:AX
MOV CX, AX
MOV BX, DX ;(DX:AX)→(BX:CX)
MOV AX, Z
CWD ;(Z) 符号扩展
ADD CX, AX
ADC BX, DX ;(BX:CX)+(DX:AX)→(BX:CX)
SUB CX, 2
SBB BX,0 ;(BX:CX)-2→(BX:CX)
MOV AX,V
CWD ;(V)符号扩展
SUB AX,CX
SBB DX,BX ;(DX:AX)-(BX:CX)→(DX:X)
IDIV X ;(DX:AX)/X
MOV DL,AL
PUSH CX
MOV CL,4
SHR AL,CL
OR AL,30H
MOV [SI],AL
INC SI
MOV AL,DL
AND AL,0FH
OR AL,30H
MOV [SI],AL
INC SI
MOV AL,'$'
MOV [SI],AL
MOV DX,OFFSET BUFFER
MOV AH,09H
INT 21H
MOV AH,4CH
INT 21H
CODE ENDS ;退出DOS 状态
END STARTLEA
:Load Effective Address,取有效地址指令- 指令功能:取源操作数地址的偏移量,并将它传送到目的操作数所在单元
- 指令格式:LEA reg16,mem
DUP
:duplicate,重复- 指令功能:用来定义重复的字节、字、双字、结构等内存缓冲区
- 指令格式:数据类型 重复次数 dup(重复数据值)
- 一些栗子:
- DB 10 DUP(?):表示定义一个容量为10的起始数据不定的字节内存区
- DD 5 DUP(0):表示定义一个容量为5的数据为0的双字内存区
- BUFFER DB 10 DUP(?):表示定义了一个名为BUFFER的大小为10的字节类型的数组
INC
:目标操作数自增1指令CWD
:Change Word to Double Word- 指令功能:将一个字类型变量扩张为双字类型变量
- 顺便白送你一份类似的指令族
CBW
:change byte to word % 8->16,e.g. AL->AXCWDE
: change word to extended word % 16->32 AX->EAXCDQ
:change double word to quadword % 32->64 e.g. EAX->EDX:EAX
INT
:Interrupt routine- 指令功能:引发中断,调用中断例程
- 指令格式:INT n % n表示中断号,也称
中断类型码
,范围在0-255的整数 - 执行指令int n时,CPU从中断向量表中找到第n号表项,修改CS和IP,使得
- IP=4n,CS=4n+2
- 程序中的
INT 21H
表示直接返回DOS
MOV AH,4CH
的含义是带返回码结束$
是汇编语言中的一个预定义符号,等价于从当前正在汇编的段的偏移地址,$可用于表达式任意位置XLAT
:Translate汇编语言查表指令
,缩写为XLAT- 指令格式:XLAT TABLE Table为一待查表格的首地址,以DS:[BX+AL]为地址
- 指令功能:提取存储器中的一个字节再送入AL,执行XLAT将使待查内容送到累加器,即AL<–((BX)+(AL))
- 该指令只能是字节操作,所以表格的最大容量为256字节。指令不影响标志位
- 举个例子:
1
2
3
4X DW 1122H,3344H,5566H,7788H
LEA BX,X
MOV AL,03H
XLAT
实验二:分支结构
- 本实例的目的是:已知在内存中有一个字节单元NUM,存有带符号数据,要求计算出它的绝对值后,放入RESULT单元中
- 本程序的
mermaid
流程图如下graph TD n1(开始)-->n2[初始化] n2-->n3[将X送入到AL中] n3-->n4{AL>=0?} n4--N-->n5[将AL内容求补] n5-->n6[AL内容送入RESULT单元] n4--Y-->n6 n6-->n7(结束)
- 代码分析
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17DATA SEGMENT
X DB -15
RESULT DB ?
DATA ENDS
CODE SEGMENT
ASSUME DS:DATA,CS:CODE
START: MOV AX,DATA
MOV DS,AX ;初始化
MOV AL,X ;X取到AL中
TEST AL,80H ;测试AL正负
JZ NEXT ;为正,转NEXT
NEG AL ;否则AL求补
NEXT: MOV RESULT,AL ;送结果
MOV AH,4CH
INT 21H ;返回DOS
CODE ENDS
END START ;汇编结束- Test: 测试指令
- 指令格式:TEST OPD,OPS
- 指令功能:将源地址和目标地址的内容执行按位与运算,结果不送入目的地址中
- 举个例子
1
2TEST AL,01H %判断AX中的最低位是否为1
JNZ L %如果不是0就跳转L
- NEG:求相反数的指令,再次提醒负数是以补码形式存在噢:补码=原码按位取反+1
- Test: 测试指令
实验三:循环结构
- 本实验的目的是:在一串给定个数的数中寻找最大值,并且放置至指定的存储单元
graph TD n1(开始)-->n2["初始化寄存器:DS,SS,SP"] n2-->n3["数组首偏移地址->BX<br>数组元素个数->CX,0->AX"] n3-->n4[比较BX与AX的值] n4-->n5{AX>BX?} n5--Y-->n6[BX->AX] n6-->n7["BX+1->Bx"] n7-->n8[CX-1->CX] n5--N-->n7 n8-->n9{CX>0?} n9--Y-->n4 n9--N-->n10["AX->[MAX]"] n10-->n11(结束)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39DATA SEGMENT
BUFFER DW 2,4,6,1,8
COUNT EQU ($-BUFFER)/2 %取得数组元素个数,注意直接减法是字节数,而元素是字类型,所以除2
MAX DW ?
DATA ENDS
STACK SEGMENT PARA STACK 'STACK'
DB 64 DUP(?)
TOP EQU $-STACK
STACK ENDS
CODE SEGMENT
ASSUME CS: CODE,DS:DATA,SS:STACK
START PROC FAR %PROC 定义的一个过程,封装函数
BEGIN: PUSH DS %压栈,为最后RET准备
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV AX,SEG STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV CX,COUNT;%存放数的个数
LEA BX,BUFFER;%存放buffer首指针
MOV AX,[BX];%取第一个数至AX
INC BX;%BX +1
INC BX: %BX+1 注意指针要加2才能移动到下一个元素
DEC CX;%CX -1
AGAIN: CMP AX,[BX]
JGE NEXT
MOV AX,[BX]
NEXT: INC BX
INC BX
LOOP AGAIN
RET %return
START ENDP- 汇编程序的另一种写法,但不推荐
- PROC:Process,封装一个过程
- RET:Return,配合PROC使用,在程序结束时返回
- 相当于MOV AX 4CH; INT 21H
- COUNT $-BUFFER:这里$就指代BUFFER数组后的下一个指针位置;BUFFER表示数组首地址,整体效果就是计算数组中元素的个数并传入COUNT中储存
- 值得强调的是要指向下一个元素时,一定要注意指针移动的次数
- 汇编程序的另一种写法,但不推荐
实验四:多重循环架构
在一串给定个数的数中寻找最小值,并且放置至指定的存储单元。每个数用16位表示
graph TD n1(开始)--DS,SS,SP-->n2[寄存器初始化] n2--Buf->BX-Num->CX-0->AX-->n3[数组偏移地址与大小传递] n3--BX?AX-->n4[数据大小比较] n4-->n5{AX>BX} n5-->n6[BX->AX] n6-->n7[BX+1->BX] n5-->n7 n7-->n8[CX-1->CX] n8-->n9{CX>0} n9-->n4 n9-->n10[AX->min] n10-->n11(结束)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35DATA SEGMENT
BUFFER DW 1,2,3,4,5,6 %初始化数据
COUNT EQU $-BUFFER %取得数组大小
min DW ?
DATA ENDS
STACK SEGMENT PARA STACK STACK
DB 64 DUP(?)
TOP EQU $-STACK
STACK ENDS
CODE SEGMENT
ASSUME CS:CODE DS:DATA SS:STACK
START PROC FAR
BEGIN: PUSH DS
MOV AX,0
PUSH AX
MOV AX,DATA
MOV DS,AX
MOV AX,SEG STACK
MOV SS,AX
MOV AX,TOP
MOV SP,AX
MOV CX,COUNT
LEA BX,BUFFER
MOV AX,[BX]
INC BX
DEC CX
AGAIN: CMP AX,[BX]
JGE NEXT
MOV AX,[BX]
NEXT: INC BX
LOOP AGAIN
RET
START ENDP
CODE ENDS
END BEGIN一编写程序完成求1+2+3+……N的累加和,直到累加和超过1000为止。统计被累加的自然数的个数送CN单元,累加和送SUM
graph TD n1(start)-->n2["initialize data,AX(0),BX(0)"] n2-->n3[BX++] n3-->n4[AX+=BX] n4-->n5{"AX≤N"} n5--Y-->n3 n5--N-->n6["N(BX),SUM(AX)"] n6-->n7(END)
DATA SEGMENT
SUM DW ?
N DW ?
DATA ENDS
CODE SEGMENT
ASSUME CS:CODE DS:DATA
START:
MOV AX,DATA
MOV DS,AX
MOV AX,0
MOV BX,0
LP:
INC BX
ADD AX,BX
CMP AX,1000
JBE LP
MOV SUM,AX
MOV N,BX
MOV AH,4CH
INT 21H
CODE ENDS
END START
实验五:冒泡排序
- 将以BUF为首地址的字存储区中存放的N个有符号数从大到小排列存入BUF区
graph TD n1(start)-->n2["initialize,CX(N),(CX)-1->CX"] n2-->n3["(CX)->DX,0->BX"] n3-->n4["(BUF[BX])->AX"] n4-->n5{"AX≥BUF[BX+2]"} n5--N-->n6["(AX)->BUF[BX]"] n5--Y-->n7["(BX)+2->BX"] n6-->n7 n7-->n8["(CX)-1->CX"] n8-->n9{"(CX)=0?"} n9--N-->n4 n9--Y-->n10["(DX)->CX"] n10-->n11["(CX)-1->CX"] n11-->n12{"(CX)=0?"} n12--N-->n3 n12--Y-->n13(END)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33DATA SEGMENT
BUF DW 3,-4,6,7,8,2,0,-8,-9,-10,20
N=($-BUF)/2
DATA ENDS
STACK SEGMENT STACK
DB 200 DUP(0)
STACK ENDS
CODE SEGMENT
ASSUME CS:COD,DS:DATA,SS:STACK
START:
MOV AX,DATA
MOV DS,AX
MOV CX,N
DEC CX
LOOP1:
MOV DX,CX
MOV BX,0
LOOP2:
MOV AX,BUF[BX]
CMP AX,BUF[BX+2]
JNE L
XCHG AX,BUP[BX+2]
MOV BUF[BX],AX
L:
ADD BX,2
DEC CX
JNE LOOP2
MOV CX,DX
LOOP LOOP1
MOV AH,4CH
INT 21H
CODE ENDS
END START
实验六:字符串相关
- 指令:MOVS,更精细可以使用 MOVSB 或 MOVSW,分为传送字节和传送字
- 格式:MOVS dest, src
- 功能:将源操作数DS:SI所指向的存储单元数据送到ES:DI所指向的存储单元。当方向标志符DF=0时,传送后SI与DI都加一;当DF=1时,SI与DI都减一
- 指令:CMPS,更精细可以使用CMPSB 或 CMPSW
- 格式:CMPS dest, src
- 功能:与CMP类似,只不过CMP是对两个单个数据进行比较,CMPS是对两个数据串进行比较
- 指令:SCAS,更精细可以使用SCASB 或 SCASW
- 格式:SCAS dest
- 功能:SCAS指令将累加器AL(或AX)的内容与目的串(ES:DI)中的数据进行比较,比较结果不改变操作数,之影响标志位。它执行与CMPS指令同样的不返回结果的减法操作,不同之处只是源操作数为AL(或AX)
- 指令:LODS,更精细可以使用LODSB 或 LODSW
- 格式:LODS src
- 功能:LODS指令将DS:SI指向的源串中的数据装入到AL(或AX)中,并根据方向标志符DF自动修改指针SI,以达到让SI指向下一个装入数据字节(或字)
- 等价:
1
2
3
4
5
6
7
8
9
10
11%LODSB指令的等价指令表述为:
MOV AL,[SI];
INC SI; % DF=0时
DEC SI % DF=1时
%LODSD指令等价指令表述为:
MOV AL,[SI];
INC SI; %DF=0
INC SI;
DEC SI;%DF=1
DEC SI;
- 指令:STOS,更精细可以使用 STOSB 或 STOSW
- 格式:STOS dest
- 功能:STOS指令将AL(或AX)中的内容存储到由ES:DI指向的目的串中,并根据DF自动修改DI指针
- 等价:
1
2
3
4
5
6
7
8
9
10%STOSB
MOV ES:[DI], AL
INC DI %DF=0
DEC DI %DF=1
%STOSW
MOV EX:[DI], AX
INC DI %DF=0
INC DI
DEC DI %DF=1
DEC DI
- DAA,(Decimal Adjust After Addition),即加法的十进制调整指令
- 功能:如果AL寄存器中低四位大于9或者辅助进位标志符AF=1,则AL=AL+06H且AL=1;如果AL的高四位大于9或者进位标志符CF=1,则AL=AL+60H且CF=1
- 栗子:
1
2
3
4MOV AL 37H
MOV BL 35H
ADD AL,BL %十六进制加法 37H+35H=6CH
DAA %DAA调整 0CH+06H->12H 30H+30H->60H 最终结果为72H
This is an identification card as an honored membership of FeynmanDirac
Happy to see you follow FeynmanDirac, enjoy science together
备用人机验证