DIY编程器网

 找回密码
 注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 2240|回复: 0
打印 上一主题 下一主题

[待整理] Thumb指令集之: ARM和Thumb的混合编程

[复制链接]
跳转到指定楼层
楼主
发表于 2014-10-10 07:27:05 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
        11.10  ARM和Thumb的混合编程

        11.10.1  互交工作基础

        Thumb以其较高的代码密度和在窄存储器上的性能,使得它在很多系统中得到广泛应用。但在很多情况下,还是不得不使用ARM指令,这是因为:
         
        ① ARM代码比Thumb代码有更快的执行速度;
        ② ARM处理器的一些特定功能必须由ARM指令实现,其中包括PSR指令、协处理器指令;
        ③ 异常发生时,处理器自动进入ARM状态,如果异常处理程序需要使用Thumb指令也必须通用一个ARM程序头(ARM assembler header)。
         
        基于以上原因,即使程序需要由Thumb代码实现,也必须通过ARM-Thumb互交(ARM-Thumb interworking)进入Thumb状态。
         
        ARM-Thumb互交是指对汇编语言和C/C++语言的ARM和Thumb代码进行连接的方法,它进行两种状态(ARM和Thumb状态)间的切换。在进行这种切换时,有时需使用额外的代码,这些代码被称为Veneer。AAPCS定义了ARM和Thumb过程调用的标准。
         
        从一个ARM例程调用一个Thumb例程,内核必须进行状态切换。状态的变化由CPSR的T位来显示。在跳转到一个例程时BX指令可用于ARM和Thumb状态切换,具体用法如下。
         
        在Thumb状态调用ARM例程时,采用:
         
        BX  Rn;
         
        在ARM状态调用Thumb例程时,采用:
         
        BX{cond}  Rn;
         
        其中,Rn可以是r0~r15中的任意寄存器。
         
        这种带状态切换的跳转指令BX,将寄存器Rn的内容拷贝到程序计数器寄存器PC,因此可以实现4G空间的跳转。指令根据寄存器Rn的bit[0]来决定处理器是否进行状态切换,详细内容参见ARM指令一节。
         
        下面是一段ARM程序,该程序调用虚拟的SWI_writeC子程序从存储器的固定地址取出字符串“hello world”并输出。
         
                AREA    Hello,CODE,READONLY
        SWI_WriteC        EQU        &0 ;软中断调用参数
        SWI_Exit           EQU        &11 ;程序退出软中断调用参数
                ENTRY
        START    ADR    r1,TEXT ;取字符串地址
        LOOP    LDRB    r0,[r1],#1 ;取下一字节内容
                 CMP  r0,#0 ;判断是否为字符串尾
                 SWINE  SWI_WriteC ;软中断调用打印字符
                 BEN  LOOP ;循环
                 SWI  SWI_Exit ;软中断调用退出程序执行
        TEXT      =      “Hello World”,&0a,&0d,0
                 END
         
        下面的代码将上面的ARM代码转换成等价的Thumb代码。
         
                  AREA  HelloW_Thumb,CODE,READONLY
        SWI_WriteC  EQU    &0 ;软中断调用参数
        SWI_Exit  EQU    &11 ;程序退出软中断调用参数
                  ENTRY ;程序入口点
                  CODE32  进入ARM状态
                  ADR r0, START+1 ;取得Thumb代码入口地址
                  BX r0 ;进入Thumb代码
                  CODE16 ;Thumb代码入口点
        START    ADR  r1, TEXT ;r1 -> "Hello World"
        LOOP     LDRB  r0, [r1] ;取下一字节内容
                  ADD  r1, r1, #1 ;地址指针加1  **T
                  CMP r0, #0 ;判断是否为字符串尾
                  BEQ DONE ;完成? **T
                  SWI SWI_WriteC ;如果不是字符串尾
                  B LOOP ;继续循环
        DONE     SWI  SWI_Exit ;程序退出
                  ALIGN ;字对齐
        TEXT     DATA
                        "Hello World",&0a,&0d,&00
                  END
         
        上例中,ARM代码到Thumb代码转换过程中新增加的指令用“**T”标注。
         
        在实现ARM代码和Thumb代码转换时,大部分的ARM指令有等价的Thumb指令,只有少数指令没有。如加载字节指令(LDR)不支持自动变址,软中断指令不能条件执行。
         
        在编写Thumb代码时要注意以下几点。
        ① 汇编器需要知道什么时候产生ARM代码、什么时候产生Thumb代码,程序中使用CODE32和CODE16伪操作提供给编译器这些信息。
        ② 由于处理器上电执行是在ARM状态下完成的,所以要使用Thumb指令必须由ARM指令调用Thumb指令,这一过程是通过“BX  LR”指令来实现的。需要注意的是,在使用“BX  LR”指令前,要对寄存器LR做正确的初始化。
        ③ 在ARM和Thumb混合编程时,常使用ALIGN伪操作保证内存地址对齐。
         
        11.10.2  互交子程序

        编写ARM/Thumb互交代码时,下面两点需要注意。
         
        ① 对于C/C++子程序而言,只要在编译时指定--apcs/interwork选项,汇编器会生成合适的返回代码,使得程序返回到和调用程序相同的状态。
         
        ② 在汇编语言子程序中,用户必须自己编写相应的返回代码,使得程序返回到和调用程序相同的状态。
         
        如果目标代码包含以下内容,应该在编译或汇编时使用--apcs/interwork选项使处理器能够在ARM和Thumb代码间进行正确的切换,这种情况包含以下4种。
        ① 需要返回到ARM状态的Thumb子程序。
        ② 需要返回到Thumb状态的ARM子程序。
        ③ 间接调用ARM子程序的Thumb子程序。
        ④ 间接调用Thumb子程序的ARM子程序。
         
        如果在程序连接阶段,连接器发现ARM子程序和Thumb子程序间存在相互调用,而源文件在编译时没有使用--apcs/interwork选项,则连接器将报告以下错误。
         
        Error: L6239E: Cannot call ARM symbol 'arm_function' in non-interworking object
        armsub.o from THUMB code in thumbmain.o(.text)
         
        其中,“arm_function”为需要进行状态切换的子程序名。
        在这种情况下,用户必须使用--apcs/interwork选项重新对源文件进行编译。
         
        但在下面两种情况下,不必指定--apcs/interwork选项。
        ① 在Thumb状态下,发生异常中断时,处理器自动切换到ARM状态,这时不需要添加状态切换代码。
        ② 当异常发生在Thumb状态时,从异常返回不需要添加状态切换的Veneer代码。
         
        1.使用汇编语言实现互交

        对于汇编程序来说,可以有两种方法来实现程序状态的切换。第一种方法是利用连接器提供的交互子程序Veneer来实现程序状态的切换,这时用户可以使用指令BL来调用子程序;另一种方法是用户自己编写状态切换的程序。
         
        在ARMv4版本及其以前的版本中,可以使用BX指令实现程序状态的切换。
        从ARMv5版本开始,下面的指令也可以用来实现程序的状态切换。
         
        ·  BX(Branch and eXchange)
        ·  BLX、LDR、LDM和POP
         
        下面的两个伪操作用来区分源程序中的ARM代码和Thumb代码。
         
        ·  CODE16
        ·  CODE32
         
        下面简单介绍用于状态切换的指令和伪操作,更详细的信息请分别参见相关章节。
         
        (1)BX指令
        ARM状态下的BX指令,使程序跳转到指令中指定的参数Rm指定的地址执行程序,Rm的第0位拷贝到CPSR中T位,位[31∶1]移入PC。若Rm的bit[0]为1,则跳转时自动将CPSR中的标志位T置位,即把目标地址的代码解释为Thumb代码;若Rm的位bit[0]为0,则跳转时自动将CPSR中的标志位T复位,即把目标地址代码解释为ARM代码。指令的语法格式如下。
         
        BX{<cond>}  <Rm>
         
        ① <cond>
        为指令编码中的条件域。它指示指令在什么条件下执行。当<cond>忽略时,指令为无条件执行(cond=AL(Alway))。
        ② <Rm>
        包含跳转指令的目标地址。如果Rm的bit[0]=0,目标地址处指令为ARM指令;如果Rm的bit[0]=1,目标地址处指令为Thumb指令。
         
        指令操作的伪代码。
        指令操作的伪代码如下面程序段所示。
         
        If  conditionPassed{cond}  then
             T  Flag=Rm[0]
             PC = Rm AND 0xfffffffe
         
        Thumb状态下的BX指令,用于ARM和Thumb代码间的相互调用。
        指令的语法格式。
         
        BX  <Rm>
         
        其中<Rm>为目标地址寄存器,包含程序的跳转地址。BX指令的目标地址寄存器可以是r0~r15中的任意寄存器。
         
                                                                         
                       
                                                                        注意
                       
                                                                        如果Rm[1:0]=0b10,不满足ARM指令的内存对齐方式。指令的执行结果不可预知。如果该指令使用r15作为目标寄存器,其操作方式和使用其他寄存器相同。
                       
         
        指令操作的伪代码如下所示。
         
        T  Flag=Rm[0]
        PC=Rm[31:1]<<1
         
        ARM指令集中的BX指令和Thumb指令集中的BX指令相差较大,它们分别为不同方向的跳转。当r15作为目的寄存器使用时,要特别注意该指令在两个指令集中的区别。
         
        (2)BLX指令
        ARM状态下的BLX指令使用一个寄存器中的绝对地址或标号,用于使程序跳转到Thumb状态或从Thumb状态返回,该指令用分支寄存器的最低位来更新CPSR中的T位,并将返回地址写入到连接寄存器LR中。
        指令的语法格式如下所示:
         
        ① BLX  <target_addr>
        ② BLX{<cond>}  <Rm>
         
        第一种格式中,转移目标按下述方法计算。将指令中指定的24位偏移量进行符号扩展,左移两位形成字偏移量,然后将其累加进程序计数器PC中。这时,程序计数器的内容为BX指令地址加8字节。位H(bit[24])也加到结果地址的第一位(bit[1]),使目标地址为半字地址,以执行接下来的Thumb指令。计算偏移量的工作一般由ARM汇编器来完成。这种形式的跳转指令只能实现±32MB空间的跳转。
         
        第二种格式中,寄存器Rm指定转移目标,Rm的第0位拷贝到CPSR中的T位,bit[31:0]移入PC。
        ·  如果Rm的bit[0]=1,则跳转时自动将CPSR中的标志位T置位,即把目标地址的代码解释为Thumb代码。
        ·  如果Rm的bit[0]=0,则跳转时自动将CPSR中的标志位T复位,即把目标地址的代码解释为ARM代码。
         
        指令操作的伪代码如下面程序段所示。
        第一种格式BLX指令。
         
        LR=address of the instruction after the BLX instruction
        T Flag=1
        PC=PC + PC = PC + (SignExtend(signed_immed_24)<<2) + (H<<1)
         
        第二种格式BLX指令。
         
        If  ConditionPass{cond}  then
               LR = address of the instruction after the branch instruftion
               T Flag=Rm[0]
               PC=Rm AND 0xfffffffe
         
        Thumb状态下带返回链接的跳转指令BLX(1)提供了一种在Thumb状态下无条件调用ARM子程序的方法,当从子程序返回时,通常使用下面的方式之一:
        ·  BX  LR
        ·  加载PC的LDR或LDM指令。
         
        BLX指令不可条件执行,可以实现在大约±4MB的地址空间范围内跳转,实现方法是将一条BLX指令编译成两条16位的Thumb指令,从而实现上述跳转。对编译后的两条指令说明如下。
        ① H=10的跳转指令。该跳转包含跳转偏移量的高位部分。
        ② H=01的跳转指令。该跳转包含跳转偏移量的低位部分。
         
        指令的语法格式
         
        BL  <target_address>
         
        <target_address>
        指定程序跳转的目标地址。指令通过下面的方法计算目标地址。
        ·  将H=10的BL指令的offset_11域左移12位。
        ·  将结果符号扩展为32位。
        ·  将得到的值加到PC寄存器中。
        ·  与H=11的BL指令的offset_11域相加。
         
        因此BL指令可以实现在大约±4MB的地址空间范围内跳转。
         
        指令操作的伪代码为:
         
        if  H==10  then
             LR=PC+(SignExtend(offset_11)<<12)
        Else  if  H==11  then
             PC=LR+(offset_11<<11)
             LR=(address  of  next  instruction)|1
        Else  if  H==01  then
             PC=(LR+(offset_11<<1)) AND 0xFFFFFFFC
             LR=(address of next instruction)|1
        Else  if  H==01  then
             PC=(LR+(offset_11<<1)) AND 0Xfffffffc
             LR=(address of next instruction)|1
             T Flag=0
         
        另外Thumb状态下包含另一种格式的BLX指令,该BLX(2)指令用于ARM和Thumb子程序间的相互调用。程序状态字的T标志位根据目的寄存器的bit[0]位而改变。
         
        指令的语法格式为:
         
        BLX  <Rm>
         
        其中<Rm>为目标地址寄存器,r0~r14寄存器均可以作为目标地址寄存器。
         
                                                                         
                       
                                                                        注意
                       
                                                                        如果在此指令中使用r15作为目的寄存器,指令的执行结果不可预知。
                       
        此指令只在ARMv5版本以上指令集中被支持。
        指令操作的伪代码为:
         
        LR = (address of the instruction after this BLX)|1
        T Flag = Rm[0]
        PC = Rm[31:1]<<1
         
        (3)汇编伪指令
        汇编编译器可以产生ARM代码也可以产生Thumb代码。使用--thumb或--16选项指示编译器产生Thumb代码。由于所有支持Thumb代码的ARM处理器都从ARM状态开始执行,所以必须人为地使用BX或BLX指令,使处理器状态切换到Thumb状态并使用下面的伪操作使编译器产生Thumb代码。
         
        ·  CODE16
        ·  CODE32
         
        CODE32伪操作指示汇编器后面的指令为32位的ARM指令。
         
        ARM和CODE32伪操作的意义相同。
        当汇编器对源程序进行编译时,如果需要,将会在程序中插入空指令,以保证内存单元字对齐。
         
        语法格式如下。
         
        ARM
        CODE32
         
        使用在同时包含ARM指令和Thumb指令的源文件中。当需要从ARM指令序列切换到Thumb指令序列时,使用伪操作ARM(或CODE32);当需要从Thumb指令序列切换到ARM指令序列时使用Thumb伪操作。ARM(或CODE32)伪操作只是指示汇编器后面的指令类型是ARM指令。该伪操作本身并不进行程序状态的切换,要进行状态切换,可以使用BX指令操作。
         
        CODE16伪指令通知编译器,其后的指令序列为16位的Thumb指令。
        语法格式如下。
         
        CODE16
         
        若在汇编源程序中同时包含ARM指令和Thumb指令时,可用CODE16伪指令通知编译器其后的指令序列为16位的Thumb指令。
         
        (4)编程实例
         
              PRESERVE8
              AREA     AddReg,CODE,READONLY ;段名为AddReg,属性为READONLY
              ENTRY ;程序入口
        ; SECTION 1
        main
              ADR r0, ThumbProg + 1 ;确定跳转地址
        ;并将bit[0]置1
        ;使程序切换到thumb状态
              BX  r0 ;程序跳转并执行状态切换
        ; SECTION 2
              CODE16 ;Thumb代码指示伪操作
        ThumbProg
              MOV r2, #2 ;r2 = 2
              MOV r3, #3 ;r2 = 3
              ADD r2, r2, r3 ;r2 = r2 + r3
              ADR r0, ARMProg
              BX  r0 ;程序跳转并执行状态切换
        ; SECTION 3
              CODE32 ;ARM代码指示伪操作
        ARMProg
             MOV r4, #4
             MOV r5, #5
             ADD r4, r4, r5
         
        ; SECTION 4
        stop MOV r0, #0x18 ;设置semihosting软中断号
              LDR r1, =0x20026 ;ADP_Stopped_ApplicationExit
              SWI 0x123456 ;ARM semihosting SWI软中断调用
              END ;文件结束
         
        上面的例子分为4部分,通过下面的步骤编译和运行。
        ① 使用文本编辑器,如notepad,输入上面的代码,并保存成文件addreg.s;
        ② 在命令行中键入汇编命令 armasm &ndash;g addreg.s;
        ③ 在命令行中键入链接命令 armlink addreg.o -o addreg;
        ④ 使用调试器(Debugger)(如,RealView Debuggeror AXD)运行映像文件。可以使用单步执行,观察代码在Thumb状态下的执行。
         
                                                                         
                       
                                                                        注意
                       
                                                                        Thumb代码的地址标号如果用伪操作export声明为“外部的”,则连接器会自动调整该地址标号使其bit[0]等于1;如果该地址标号没有被声明为“外部的”,则使用者必须手动地对标号进行调整,如上例中的ThumProg+1。
                       
         
        (5)ARMv5架构下的状态切换
        在ARMv5体系结构的指令集中,增加了下面两条指令用于ARM和Thumb代码互交。
        ·  BLX  address
        该指令跳转到指令中指定的地址处执行程序并进行程序状态切换,该地址是“PC相关的”,地址范围为±32MB(ARM状态)或±4MB(Thumb状态)。
         
        ·  BLX  register
        在该中格式的跳转指令中寄存器Rm指定转移目标,Rm的第0位拷贝到CPSR中的T位,bit[31:0]移入PC。
         
        使用上面两条指令,在执行程序跳转之前,处理器自动将返回连接寄存器LR的bit[0]位更新为CPSR寄存器的T位,所以无论处理器状态是否发生变化,程序都能正确返回。
         
        当使用LDR、LDM及POP指令向PC寄存器中赋值时,寄存器CPSR中的Thumb位将被设置成PC寄存器的bit[0],这时就实现了程序状态的切换。这种方法在子程序的返回时非常有效,同样的指令可以根据需要返回到ARM状态或Thumb状态。
         
        连接器在对目标代码进行连续时,将代码中的地址标号分为3类。
        ·  ARM指令地址标号。
        ·  Thumb指令地址标号。
        ·  数据(Data)地址标号。
        当连接器重定位Thumb代码中的地址标号时,地址标号的bit[0]位将被自动设置为1。这就意味着跳转指令(这些指令包括BX、BLX和LDR)可以根据目标地址正确的进行状态切换。
         
                                                                         
                       
                                                                        注意
                       
                                                                        上面提到的连接器自动设置目的地址的行为,只有在ARMv5及其以上版本中支持。
                       
         

        2.使用C和C++语言实现互交

        对于不同的C和C++源程序,可以有些程序中包含ARM指令,有些程序中包含Thumb指令,这些程序可以相互调用,只是在编译这些程序时指定--apcs/interwork选项。当使用了--apcs/interwork选项,编译器会自动进行一些相应处理;连接器在检测到程序中存在互交工作时,会生成一些用于程序状态切换的代码。
         
        (1)代码编译
        可以使用下面的指令,将C或C++程序编译为可以执行互交的目标代码。
         
        armcc --c90 --thumb --apcs /interwork
        armcc --c90 --arm --apcs /interwork
        armcc --cpp --thumb --apcs /interwork
        armcc --cpp --arm --apcs /interwork
         
                                                                         
                       
                                                                        注意
                       
                                                                        --cpp是C++文件(文件后缀为.cpp)默认的编译选项。
                       
         
        使用--apcs/interwork选项对文件进行编译时,编译器会进行如下处理。
         
        ·  对于叶子程序(leaf function,即程序中没有其他子程序调用的程序),编译器将程序中的“MOV  PC,LR”指令替换成“BX  LR”指令,因为“MOV  PC”指令不能进行状态切换。
        ·  对于非叶子程序,要进行一系列的指令替换,如:
         
        POP  {r4,r5,pc}
         
        替换为:
         
          POP  {r4,r5}
          POP  {r3}
          BX   r3
         
        下面的例子显示了一段带子程序调用的C语言程序,使用--apcs/interwork选项进行编译时,对代码产生的影响。
        C语言源程序。
         
        Void  func(void)
        {
        ….
        Sub()
         
        ..
        }
         
        使用armcc  --apcs/interwork选项进行编译产生结果如下。
         
        Func
        STMFD  sp!,{r4-r7,lr}
        ….
        BL  sub
        ….
        LDMFD  sp!, {r4-r7,lr}
        BX  lr
         
        使用tcc  --apcs/interwork选项进行编译产生结果如下。
         
        PUSH  {r4-r7,lr}
        ….
        BL  sub
        ….
        POP  {r4-r7}
        POP  {r3}
        BX
         
        (2)C语言的互交实例
        下面的例子显示了一个Thumb状态下的代码通过互交调用ARM子程序;而后又在ARM子程序中调用Thumb指令集的库函数printf()。
         
           /*********************
           *       thumbmain.c  *
           **********************/
           #include <stdio.h>
           extern void arm_function(void);
           int main(void)
           {
                  printf("Hello from Thumb World\n");
                  arm_function();
                  printf("And goodbye from Thumb World\n");
                  return (0);
           }
           /*********************
           *            armsub.c    *
           **********************/
           #include <stdio.h>
           void arm_function(void)
           {
                  printf("Hello and Goodbye from ARM world\n");
           }
         
        使用下面的命令对程序进行编译连接。
        ① 编译生成带互交的Thumb代码。
         
        armcc --thumb -c -g --apcs /interwork -o thumbmain.o thumbmain.c
         
        ② 编译生成带互交的ARM代码。
         
        armcc -c -g --apcs /interwork -o armsub.o armsub.c
         
        ③ 连接目标文件。
         
        armlink thumbmain.o armsub.o -o thumbtoarm.axf
         
        另外,可以使用--info选项使连接器输出由于互交所增加的代码大小。
         
        armlink armsub.o thumbmain.o -o thumbtoarm.axf --info veneers
         
        输出信息如下所示。
         
        Adding Veneers to the image
             Adding TA veneer(4 bytes, Inline) for call to&#39;arm_function&#39;from thumbmain.o(.text).
             Adding AT veneer (8 bytes, Inline) for call to &#39;__0printf&#39; from armsub.o(.text).
             Adding AT veneer (8 bytes, Inline) for call to &#39;__rt_lib_init&#39; from kernel.o(.text).
             Adding AT veneer (12 bytes,Long) for call to&#39;__rt_lib_shutdown&#39;from kernel.o(.text).
             Adding TA veneer (4 bytes, Inline) for call to &#39;__rt_memclr_w&#39; from stdio.o(.text).
             Adding TA veneer (4 bytes, Inline) for call to &#39;__rt_raise&#39; from stdio.o(.text).
             Adding TA veneer (8 bytes, Short) for call to &#39;__rt_exit&#39; from exit.o(.text).
             Adding TA veneer (4 bytes, Inline) for call to &#39;__user_libspace&#39; from free.o(.text).
             Adding TA veneer (4 bytes, Inline) for call to &#39;_fp_init&#39; from lib_init.o(.text).
             Adding TA veneer (4 bytes, Inline) for call to &#39;__heap_extend&#39; from malloc.o(.text).
             Adding AT veneer (8 bytes, Inline) for call to &#39;__raise&#39; from rt_raise.o(.text).
             Adding TA veneer (4 bytes, Inline) for call to &#39;__rt_errno_addr&#39; from ftell.o(.text).
        12 Veneer(s) (total 72 bytes) added to the image.
         
        (3)Thumb状态下的功能指针
        任何指向Thumb函数(由Thumb指令完成的功能函数并且其返回状态也为Thumb状态)的指针,其最低有效位(LSB)必为1。
         
        当重定位Thumb代码中的地址标号时,连接器将自动设置地址的最低有效位。如果在程序中使用绝对地址,连接器将无法完成该设置。因此,如果在Thumb代码中使用绝对地址时,必须手工设置为其地址加1。
         
        下面的例子显示了Thumb代码的功能指针的使用。
         
        typedef int (*FN)();
        myfunc() {
             FN fnptrs[] = {
                  (FN)(0x8084 + 1), // 有效的Thumb地址
                  (FN)(0x8074) // 无效的Thumb地址
             };
             FN* myfunctions = fnptrs;
             myfunctions[0](); // 调用成功
             myfunctions[1](); // 调用失败
        }
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
收藏收藏 分享分享 支持支持 反对反对
您需要登录后才可以回帖 登录 | 注册

本版积分规则

小黑屋|文字版|手机版|DIY编程器网 ( 桂ICP备14005565号-1 )

GMT+8, 2024-5-20 23:07 , 耗时 0.159474 秒, 18 个查询请求 , Gzip 开启.

各位嘉宾言论仅代表个人观点,非属DIY编程器网立场。

桂公网安备 45031202000115号

DIY编程器群(超员):41210778 DIY编程器

DIY编程器群1(满员):3044634 DIY编程器1

diy编程器群2:551025008 diy编程器群2

QQ:28000622;Email:libyoufer@sina.com

本站由桂林市临桂区技兴电子商务经营部独家赞助。旨在技术交流,请自觉遵守国家法律法规,一旦发现将做封号删号处理。

快速回复 返回顶部 返回列表