当前位置: 首页 > news >正文

广州pc网站建设公司想为一个产品做多个网站

广州pc网站建设,公司想为一个产品做多个网站,深圳高端包装盒设计,广州企业模板建站为什么要学习 16 位汇编#xff1f; 16 位汇编包含了大部分 32 位汇编的知识点。有助于在学习内核的两种模式。 实模式#xff1a;访问真实的物理内存保护模式#xff1a;访问虚拟内存 有助于提升调试能力#xff0c;调试命令与 OllyDbg 和 WinDebug 通用。可以学习实现反…为什么要学习 16 位汇编 16 位汇编包含了大部分 32 位汇编的知识点。有助于在学习内核的两种模式。 实模式访问真实的物理内存保护模式访问虚拟内存 有助于提升调试能力调试命令与 OllyDbg 和 WinDebug 通用。可以学习实现反汇编引擎32 位的汇编引擎实现起来比较麻烦 汇编基础 硬件运行机制 二极管 原则上仅允许电流作单方向传导它在一个方向为低电阻高电流而在另一个方向为高电阻。计算机将高低电压定义为 0 和 1借助二极管的特性完成运算。这就是为什么计算机以二进制形式运算。 门电路 门电路Logic Gate Circuit是数字电子电路中的基本构建块用于实现逻辑运算和控制信号。逻辑门根据不同的逻辑运算规则来处理输入信号并产生相应的输出信号。 常见的逻辑门包括与门AND gate、或门OR gate、非门NOT gate、异或门XOR gate等上图是与门的实现。 算术/逻辑单元 所有的数学运算都可以由位运算组成。将常用运算符封装成一个器件称之为单元。 运算单元通常由两个输入端一个控制端和一个输出端组成。 输入端用来传输操作数输出端用来输出运算结果控制端用于控制运算单元做对应的运算。 机器码 可以控制硬件的二进制数据叫做机器码。 以上面的 ALU 为例假设该 ALU 能进行 8 位二进制数的运算。 则我们可以将表达式 15 23 与上面的 ALU 的输入作如下对应 输入100001111输入200010111控制00 将上面的输入按照 输入1 输入2 控制 的格式可以写作 00 00001111 00010111为了方便阅读用空格隔开这就是机器码。 类似的还有如下机器码 15 2310 00001111 0001011115 ^ 2311 00001111 00010111 助记符 机器码的二进制值难记因此可以将每种功能的二进制控制码取一个容易记住的名字这个名字叫做助记符也称之为指令。 例如 00add01sub10and11xor 因此上面列举的机器码可以转换为如下汇编 15 23 00 00001111 00010111 add 0fh, 17h15 23 10 00001111 00010111 and 0fh, 17h15 ^ 23 11 00001111 00010111 xor 0fh, 17h 汇编 硬件不能识别助记符因此需要将其转换成对应的机器码这个过程叫做汇编。 关于汇编代码有几个关键名词在查阅反汇编器文档时会经常遇到 助记符Mnemonic操作数Operand 微机系统硬件组成 一个典型的硬件系统组成 一个系统不可能由一个硬件单独完成所以划分出多个硬件模块然后由一个硬件模块居中调度称作 CPUCentral Processing Unit。 8086 CPU 8086 是 16 位 CPU有 20 根地址线通过段寄存器寻址扩展了 4 位和 16 根数据线。前缀为 AD 的引脚是用来寻址或存取数据的引脚。这种一个种引脚承担多种功能的特点称为引脚复用。CLK 引脚是 CPU 的是时钟输入引脚它接收来自外部的时钟信号用于同步处理器内部的操作和各种电子元件的工作。时钟信号的频率决定了处理器的工作速度即指令的执行速度。 80286 是 8086 的后续型号也是一款 16 位 CPU。它在 8086 的基础上引入了一些新的特性和改进并提供了更高的性能。80386 是 Intel 推出的第一个 32 位 CPU也被称为 386。 IO 桥 所有硬件模块连接到 I/O 桥由 I/O 桥负责辅助 CPU 与哪一个硬件模块连接。 以上图为例s 决定了 out 的输出是 a 还是 b 。 总线 以下图为例CPU 有 8 位数据/地址总线RAM 是一个 256 字节的存储器。 控制线表示 CPU 的对 RAM 操作例如寻址读写等。数据地址线表示 CPU 操作的 RAM 的地址。 计算机系统组成 计算机系统分层结构图 计算机程序的编译运行 以一个 hello.c 程序为例。 编译 加载可执行文件 执行 8086 CPU 组织架构 8086 功能框图 执行单元EU 执行单元Execution UnitEU是8086系列处理器中的一个重要组成部分它负责执行指令并控制处理器的操作。 算术逻辑单元Arithmetic Logic UnitALUALU 是 EU 的核心部件用于执行算术和逻辑运算。它可以执行加法、减法、乘法、除法等算术操作以及逻辑操作如与、或、非、异或等和移位操作。寄存器组EU 包含多个通用寄存器用于存储数据和中间结果。这些寄存器包括累加器Accumulator、通用寄存器General Purpose Registers、标志寄存器Flags Register等。寄存器提供了快速的数据存储和访问用于执行指令时的数据操作。控制单元EU 的控制单元负责控制指令的执行流程。它根据指令的操作码和其他相关信息生成相应的控制信号以控制 ALU、寄存器和其他组件的操作。控制单元还负责处理分支指令、循环指令和异常情况等。数据通路EU 内部的数据通路用于将指令和数据在不同的组件之间传递。它包括数据总线、地址总线和控制总线等用于传输数据、地址和控制信号。 总线接口单元BIU 总线接口单元Bus Interface UnitBIU是8086系列处理器中的一个重要组成部分它负责处理处理器与系统总线之间的接口和通信。 段管理8086 处理器使用分段的内存管理方式BIU 负责管理段寄存器和段选择子。它从段寄存器中获取段地址并与偏移地址组合成物理地址用于内存访问。指令预取BIU 包含一个指令队列Instruction Queue用于预取和缓存指令。它从内存中获取指令并将其存储在队列中以供执行单元EU使用。这样可以提高指令的获取速度减少对内存的访问次数。总线控制BIU 负责处理与系统总线的交互包括地址传输、数据传输和控制信号的生成。它将处理器的地址、数据和控制信号发送到总线上同时接收来自总线的响应和数据。内存访问和数据传输BIU 负责处理对内存和外部设备的访问。它可以执行内存读取和写入操作并处理与外部设备的数据传输。 寄存器 流水线 CPU 执行指令的过程可以分为一下 5 个步骤其中 124 是必须的。 取指令译码取数据执行存储结果 8086 CPU 将指令的执行分成多个模块这样可以多个模块同时工作从而提高效率。 然而这种优化在程序中分支跳转较多的时候会导致程序运行变慢。因为提前取到的下一条指令是地址与当前指令地址相邻的指令。而当前指令如果为跳转指令则需要消除提前执行的下一条指令的痕迹。因此编译器优化的其中一个方向是尽量减少程序中的分支跳转数量。 debug 的使用 环境配置 获取 debug 在 Windows XP 的 C:\WINDOWS\system32 目录下有一个名为 debug.exe 的程序。debug 是一个命令行工具它提供了一种简单的方式来执行低级别的调试和汇编操作。 不过这个程序只能在 Windows XP 系统下运行高版本的 Windows 系统已经不支持该程序的运行。 安装 DOSBox 为了能够让 debug 在高版本的 Windows 系统下运行方便后续编写汇编程序需要安装 DOSBox 程序来模拟相应环境。也可以使用 msdosplayer 双击在 DOSBox 安装目录下的 DOSBox 0.74-3 Options.bat 可以打开 DOSBox 的配置文件。在文件末尾可以添加 DOSBox 启动时要执行的初始化命令。 这里我添加了如下命令 mount c C:\Program Files (x86)\DOSBox-0.74-3\C set pathC: C:DOSBox 需要手动挂载硬盘即需要将电脑中的某个目录映射到 DOSBox 中作为一个硬盘。这里我将 C:\Program Files (x86)\DOSBox-0.74-3\C手动创建的一个目录挂载为 DOSBox 的 C 盘。将 DOSBox 中的 C: 盘添加为环境变量。切换当前目录为 C 盘。 另外我还将 Windows XP 中的 debug.exe 复制到 DOSBox 挂载的 C 盘中这样就可以再 DOSBox 中运行 debug 进行调试了。 debug 常用命令 ?显示 Debug 命令列表。 u [range]反汇编。没有 range 默认从 CS:IP 或上一次反汇编结束位置开始反汇编。 -u 0AF1:0100 7419 JZ 011B 0AF1:0102 8B0ED596 MOV CX,[96D5] 0AF1:0106 E313 JCXZ 011B 0AF1:0108 B01A MOV AL,1A 0AF1:010A 06 PUSH ES 0AF1:010B 33FF XOR DI,DI 0AF1:010D 8E06B496 MOV ES,[96B4] 0AF1:0111 F2 REPNZ 0AF1:0112 AE SCASB 0AF1:0113 07 POP ES 0AF1:0114 7505 JNZ 011B 0AF1:0116 4F DEC DI 0AF1:0117 893ED596 MOV [96D5],DI 0AF1:011B BB3400 MOV BX,0034 0AF1:011E E00A LOOPNZ 012Aa [addr]在指定地址写入汇编机器码。 -a 110 0AF1:0110 mov ax, ax 0AF1:0112 mov dx, dx 0AF1:0114 mov ax, dx 0AF1:0116 -u 110 l 6 0AF1:0110 89C0 MOV AX,AX 0AF1:0112 89D2 MOV DX,DX 0AF1:0114 89D0 MOV AX,DXr [reg]显示或改变一个或多个寄存器。 -r ax AX 0000 :1234 -r ax AX 1234 :d [range]显示部分内存的内容。 -d 110 0AF1:0110 96 F2 AE 07 75 05 4F 89-3E D5 96 BB 34 00 E0 0A ....u.O....4... 0AF1:0120 C7 96 00 74 03 BB 00 98-BE 77 97 8B 3E B9 98 B9 ...t.....w..... 0AF1:0130 08 00 E8 12 00 80 3C 20-74 09 B0 2E AA B9 03 00 ...... t....... 0AF1:0140 E8 04 00 32 C0 AA C3 B4-00 8A F1 80 FC 01 74 09 ...2..........t. 0AF1:0150 B4 00 8A 07 E8 DC E2 74-02 FE C4 AC 3C 3F 75 27 .......t....?u 0AF1:0160 80 FC 00 74 20 80 FC 01-75 22 3A CE 75 05 80 3C ...t ...u:.u.. 0AF1:0170 20 74 0A 80 3C 3F 75 14-83 F9 01 76 0F 8A 07 AA t..?u....v.... 0AF1:0180 43 46 49 FE C4 8A 07 3C-20 74 01 AA 43 E2 BC C3 CFI.... t..C...e修改内存。 e addr-e 110 0AF1:0110 96.11 F2.22 AE.33 07.44 75.55 -d 110 l 5 0AF1:0110 11 22 33 44 55 .3DUe addr val1[逗号|空格 val2 逗号|空格 val3...]-e 110 1,2,3,4 5 6 7 8 -d 110 l 8 0AF1:0110 01 02 03 04 05 06 07 08 ........e addr 字符串-e 110 sky123 -d 110 l 6 0AF1:0110 73 6B 79 31 32 33 sky123g运行在内存中的可执行文件。 t步入。 p步过。 (n,cx,w)写入文件。 -n text.txt -r cx CX 0000 :100 -w Writing 00100 bytesn要写入的文件的名称。cx要写入的数据的长度。写完文件之后 cx 寄存器的值不会改变还是写之前设置的写入长度。w写文件命令。 其中 [range] 有下面两种种形式 [startaddr] [endaddr]从 startaddr 到 endaddr 。[startaddr l num]从 startaddr 到 startaddr num 。 标志寄存器 标志寄存器反应 ALU 运算结果的状态。 条件标志位 SFZFOFCFAFPFCPU 执行完一条指令后自动设置。反映算术、逻辑运算等指令执行完毕后运算结果的特征。 控制标志位 DFIFTF控制 CPU 的运行方式工作状态。 标志truefalseName名称命题OFOVOverflowNVNot OverflowOverflow Flag是否溢出存在溢出SFNGNegativePLPlusSign Flag结果的符号是正还是负是负数正数看做无符号ZFZRZeroNZNot ZeroZero Flag运算结果是否为 0是 0 PFPEEventPOOddParity Flag结果中二进制位个数的奇偶性是偶数个 1 CFCYCarry yesNCNot carryCarry Flag进位标志有进位AFACAuxiliary CarryNANo Auxiliary CarryAuxiliary Carry Flag辅助进位标志发生辅助进位DFDNDownUPUpDirection Flag方向标志si 、di 递减IFEIEnable InterruptsDIDisable InterruptsInterrupt Flag中断标志允许中断TFSTSingle StepNTNon TrapTrap Flag陷阱标志单步调试模式 进位标志CF(Carry Flag) 如果运算结果的最高位产生了一个进位或借位那么CF 的值为 1 否则为 0 。无符号数溢出 由于 CF 是针对无符号数来说的因此无论是加法还是减法参与运算的数都是无符号数大于等于 0。0 减去任何非零的数 CF 都会置 1 。 奇偶标志PF(Parity Flag) 奇偶标志 PF 用于反映运算结果中最低字节中 1 的个数的奇偶性。如果 1 的个数为偶数则 PF 的值为 1 否则其值为 0 。 辅助进位标志AF(Auxiliary Carry Flag) 在发生下列情况时辅助进位标志 AF 的值被置为 1 否则其值为 0 。 在字操作时发生低字节向高字节进位或借位时在字节操作时发生低 4 位向高 4 位进位或借位时。 零标志ZF(Zero Flag) 如果运算结果为 0 则其值为 1 否则其值为 0 。在判断运算结果是否为 0 时可使用此标志位。 符号标志SF(Sign Flag) 符号标志 SF 用来反映运算结果的符号位它与运算结果的最高位相同。 溢出标志OF(Overflow Flag) 溢出标志 OF 用于反映有符号数加减运算所得结果是否溢出。有符号数溢出 分段与寻址 分段 分段寻址 8086 是 16 位 CPU 但是能访问 1M 的内存这是因为 8086 将内存划分成多段通过段基址段偏移的方式访问。 地址计算方式 地址计算方式内存地址 段基址 * 10h 段偏移 段基址段偏移的方式一般写作 段基址:段偏移 称为逻辑地址。偏移地址称为 EAEffective Address在很多库的文档中会出现这个名称。通过逻辑地址计算出来的内存地址称为物理地址 PAPhysical Address。 段的内存分布如下不同的段之间可以重叠。由于段地址的计算方式段的起始地址关于 0x10 对齐。 内存分布 划分段的原则 段大小可以不是 64K段与段之间不能有重叠8086 CPU 和 DOS 操作系统都不会管内存分段靠自己这样做只是避免程序出问题。 逻辑地址与物理地址 一个物理地址可以由多个逻辑地址表示但基于分段原则一般编程中不会碰到。 可用内存 DOS 系统中应用程序可用内存约 600K 。 段寄存器 8086 中段基址都是存储在段寄存器中段偏移可以用立即数或者通用寄存器指明。 DS数据段默认使用 DX 。CS代码段绑定 CS:IP 使用。SS堆栈段用作函数栈绑定 SS:SP 使用。ES扩展段常用于串操作。 debug 的一些命令也与段寄存器绑定 au代码段 CSde数据段 DS 当然我们也可以指定特定的段例如 d ss:100 。 8086 有 20 跟地址线16 根数据线其中数据线与地址线的低 16 位复用。内部通过地址加法器计算地址。 寻址 指令中用于说明操作数所在的方式称为寻址方式。16 位 CPU 的寻址方式有 7 种32 位 CPU 还会多一种比例因子寻址。 立即寻址 操作数的值存在指令中的方式称作立即寻址。汇编中整数常量称作立即数。立即数可以是 8 位数也可以是 16 位数。 寄存器寻址 操作数的值存储在寄存器的寻址方式称作寄存器寻址。寄存器包括通用寄存器和段寄存器。 注意 段寄存器之间不能赋值。指令指针寄存器不能用作寻址例如不存在 MOV AX, IP 汇编。 直接寻址 操作数值在内存中机器码中存储 16 位段内偏移的寻址方式称作直接寻址。 寄存器间接寻址 操作数值在内存中段内偏移存储在寄存器中的寻址方式称作寄存器间接寻址。间接寻址的寄存器有 BXBPSIDI 。 EA [ ( BX ) ( BP ) ( SI ) ( DI ) ] \text{EA}\begin{bmatrix} (\text{BX})\\ (\text{BP})\\ (\text{SI})\\ (\text{DI}) \end{bmatrix} EA ​(BX)(BP)(SI)(DI)​ ​ 寄存器相对寻址 操作数值在内存中段内偏移存储由 [寄存器 立即数] 计算得来的的寻址方式称作寄存器相对寻址。寄存器相对寻址的寄存器有 BX BP SI DI 。寄存器相对寻址的立即数可以是 8 位可以是 16 位的。 EA [ ( BX ) ( BP ) ( SI ) ( DI ) ] [ 8 位 disp 16 位 disp ] \text{EA}\begin{bmatrix} (\text{BX})\\ (\text{BP})\\ (\text{SI})\\ (\text{DI}) \end{bmatrix}\begin{bmatrix} \text{8 位 disp} \\ \text{16 位 disp} \end{bmatrix} EA ​(BX)(BP)(SI)(DI)​ ​[8 位 disp16 位 disp​] 基址变址寻址 操作数值在内存中段内偏移由 [寄存器 寄存器] 计算得来的寻址方式称作基址变址寻址。可用做基址的寄存器有 BX BP 。BX 默认 DS 段BP 默认 SS 段。可用作变址的寄存器有 SI DI 。 EA [ ( BX ) ( BP ) ] [ ( SI ) ( DI ) ] \text{EA}\begin{bmatrix} (\text{BX})\\ (\text{BP})\\ \end{bmatrix}\begin{bmatrix} (\text{SI})\\ (\text{DI}) \end{bmatrix} EA[(BX)(BP)​][(SI)(DI)​] 基址变址相对寻址 操作数值在内存中段内偏移由 [基址寄存器变址寄存器偏移常量] 计算得来的寻址方式称作基址变址寻址。可用做基址的寄存器有 BX BP 。BX 默认 DS 段BP 默认 SS 段。可用作变址的寄存器有 SI DI 。可用作常量的数值可以是 8 位可以是 16 位。 指令 数据传送类指令 传送指令 MOV (move) 把一个字节或字的操作数从源地址传送至目的地址。 注意 不存在存储器向存储器的传送指令。mov 指令源操作数和目的操作数指定的数据长度应一致。立即数到内存的数据传送指令需要指定数据长度例如 mov byte ptr ds:[bx], 12h 。其他指令由于寄存器自带长度因此不需要指定数据长度。 交换指令 XCHGexchange 情形 寄存器与寄存器之间对换数据寄存器与存储器之间对换数据不能在存储器与存储器之间对换数据 效率mov 优于 xchg 因为 xchg 使用了内部暂存器。 换码指令 XLAT 作用将 BX 指定的缓冲区中 AL 指定的位移处的一个字节取出赋给 AL 即al -- ds:[bx al] 。该指令无操作数。 用途键盘的扫描码需要转为 ASCII 码可以将扫描码做成表扫描码作下标可以查到对应的 ASCII 码。 堆栈操作指令 进栈push reg相当于 sub sp, 2; mov [sp], reg; 。出栈pop reg相当于 mov reg, [sp]; add sp, 2; 。保存所有寄存器环境 16 位pusha/popa32 位pushad/popad 注意 对于 8086 CPUpush 指令的操作数只能是长度为 2 字节的寄存器包括段寄存器或内存。8028680386 及以上的 CPU 的 push 指令支持立即数和寄存器。8086 不支持 pusha 指令80286 才开始支持该指令。pusha 指令会将 16 位通用寄存器 AXCXDXBXSPBPSIDI 中的值依次压入栈中。 标志寄存器传送指令 标志寄存器传送指令用来传送标志寄存器 FLAGS 的内容方便进行对各个标志位的直接操作。 低 8 位传送 LAHFAH ← FLAGS 的低字节 LAHF 指令将标志寄存器的低字节传送给寄存器 AH 。SF/ZF/AF/PF/CF 状态标志位分别送入 AH 的第 7/6/4/2/0 位而 AH 的第 5/3/1 位任意。 SAHFFLAGS 的低字节 ← AH SAHF 将 AH 寄存器内容传送给 FLAGS 的低字节。用 AH 的第 7/6/4/2/0 位相应设置 SF/ZF/AF/PF/CF 标志位。 16 位传送 PUSHFPUSHF 指令将标志寄存器的内容压入堆栈同时栈顶指针 SP 减 2 。POPFPOPF 指令将栈顶字单元内容传送标给志寄存器同时栈顶指针 SP 加 2 。 32 位传送 PUSHFD将 ELFAGS 压栈。POPFD将栈顶 32 字节出栈到 EFLAGS 中。 地址传送指令 地址传送指令将存储器单元的逻辑地址送至指定的寄存器 有效地址传送指令 LEAload EA将存储器操作数的有效地址传送至指定的 16 位寄存器中。LDS r16, mem将主存中 mem 指定的字送至 r16 并将 mem 的下一字送 DS 寄存器。LES r16, mem将主存中 mem 指定的字送至 r16 并将 mem 的下一字送 ES 寄存器。 输入输出指令 8086 通过输入输出指令与外设进行数据交换呈现给程序员的外设是端口Port即 I/O 地址。8086 用于寻址外设端口的地址线为 16 条端口最多为 2 16 2^{16} 2166553664K个端口号为 0000HFFFFH 。每个端口用于传送一个字节的外设数据。 8086 的端口有 64K 个无需分段设计有两种寻址方式 直接寻址只用于寻址 00HFFH 前 256个 端口操作数 i8 表示端口号。间接寻址可用于寻址全部 64K 个端口DX 寄存器的值就是端口号。对大于 FFH 的端口只能采用间接寻址方式。 输入指令 IN以将外设数据传送给 CPU 内的 AL/AX 为例 IN AL, i8字节输入AL ← I/O 端口i8 直接寻址IN AL, DX字节输入AL ← I/O 端口DX 间接寻址IN AX, i8字输入AX ← I/O 端口i8 直接寻址IN AX, DX字输入AX ← I/O 端口DX 间接寻址 输出指令 OUT以将 CPU 内的 AL/AX 数据传送给外设为例 OUT i8, AL字节输出I/O 端口 ← ALi8 直接寻址OUT DX, AL字节输出I/O 端口 ← ALDX 间接寻址OUT i8, AX字输出I/O 端口 ← AXi8 直接寻址OUT DX, AX字输出I/O 端口 ← AXDX 间接寻址 这个指令的其中一个用途是检测虚拟机。在真机环境中由于输入输出指令为特权指令在 3 环执行会触发异常。而在虚拟机中则不会。 算术运算类指令 加法 add加法 ADD reg, imm/reg/memreg ← reg imm/reg/memADD mem, imm/regmem ← mem imm/reg adc带进位加法 ADC reg, imm/reg/memreg ← reg imm/reg/mem CFADC mem, imm/regmem ← mem imm/reg CF inc加一不影响 CF 标志位。 INC reg/memreg/mem ← reg/mem 1 减法 sub减法 SUB reg, imm/reg/memreg ← reg - imm/reg/memSUB mem, imm/regmem ← mem - imm/reg sbb带借位的减法 SBB reg, imm/reg/memreg ← reg - imm/reg/mem - CFSBB mem, imm/regmem ← mem - imm/reg - CF dec减一不影响 CF 标志位。 DEC reg/memreg/mem ← reg/mem - 1 求补指令 NEGnegative NEG 指令对操作数执行求补运算用零减去操作数然后结果返回操作数。求补运算也可以表达成将操作数按位取反后加 1 。 NEG reg/memreg/mem ← 0 - reg/mem 。如果操作数为 0 则 CF 0 否则 CF 1 。 以 x 0 ? 0 : -1 为例我们可以通过 neg 指令将其优化为无分支程序 mov ax, x sub ax, 0 ; CF 标志位清零 neg ax ; 如果 ax 非 0 则 CF 置位 sbb ax, ax ; ax ax - ax - CF - CF 对于其他类似的三目运算我们可以通过加减偏移和乘除系数转换为上述的三目运算因此都可以把分支优化掉。 比较指令 CMPcompare 格式CMP OPD, OPS功能(OPD) - (OPS)说明目的操作数减去源操作数然后根据结果设置标志位但该结果不存入目的地址。影响标志位AFCFOFPFSFZF作用一般后面跟一条条件转移指令根据比较结果跳转到不同的分支用于处理 OPD 和 OPS 大小比较不同的情况。 乘法指令 无符号乘法 位数隐含的被乘数乘积存放的位置举例8位ALAXMUL BL16位AXDX-AXMUL BX32位EAXEDX-EAXMUL ECX 格式MUL reg/mem功能显式操作数*隐式操作数看成无符号数影响标志位CF 和 OF 如果乘积的高一半位AH/DX/EDX包含有乘积的有效位则 CF1 OF1 否则 CF0 OF0 。 有符号乘法 格式 IMUL reg/memIMUL reg, imm80286IMUL reg, reg, imm80286IMUL reg, reg/mem80386 功能有符号数相乘影响标志位CF 和 OF 如果乘积的高一半位AH/DX/EDX不是低位的纯符号扩展则 CF1 OF1 否则 CF0 OF0 。 除法指令 无符号乘法 位数隐含的被除数除数商余数8位AX8位opsALAH16位DX-AX16位opsAXDX32位EDX-EAX32位opsEAXEDX 格式DIV reg/mem影响标志位未定义即指令执行后标志位是任意的不可预测的。除法溢出8 位除法运算结果大于 8 位16 位除法运算结果大于 16 位。 有符号除法 位数隐含的被除数除数商余数8位AX8位opsALAH16位DX-AX16位opsAXDX32位EDX-EAX32位opsEAXEDX 格式IDIV reg/mem影响标志位AFCFOFPFSFZF除法溢出字节除时商不在 -128~127 范围内或者在字除时商不在 -32768~32767 范围内。 符号扩展指令 CBWConvert Byte to Word将 AL 中的符号扩展至 AH 中操作数是隐含且固定的。 XX04 → 0004XXFE → FFFE CWDCovert Word to Doubleword将 AX 中的符号扩展至 DX 中操作数是隐含且固定的。CWDECovert Word to Extended Doubleworld386将 AX 中的符号位扩展至 EAX 的高 16 位操作数是隐含且固定的。CDQCover Doubleword to Quadword386将 EAX 中的符号位扩展至 EDX 中操作数是隐含且固定的。CDQEConvert Doubleword to Quadword Extendedx86-64将 EAX 中的符号位扩展至 RAX 中操作数是隐含且固定的。 位操作类指令 逻辑运算 逻辑与AND 格式AND reg/mem, reg/mem/imm受影响的标志位CF(0)OF(0)PFSFZFAF 无定义 CF进位标志AND 指令总是将 CF 标志设置为 0 即不会影响进位标志。OF溢出标志AND 指令总是将 OF 标志设置为 0 即不会影响溢出标志。PF奇偶标志AND 指令根据结果中的位数 1 的个数来设置奇偶标志。如果结果中的位数 1 是偶数个则PF被设置为 1 否则设置为 0 。SF符号标志AND 指令将结果的最高位符号位复制到 SF 标志位中。如果结果的最高位为 1 则 SF 被设置为 1 表示结果为负数如果结果的最高位为 0 则 SF 被设置为 0 表示结果为非负数。ZF零标志AND 指令将结果的所有位进行按位与操作并将零标志设置为1如果结果为零否则将零标志设置为 0 。AF辅助进位标志AND 指令不会定义或影响辅助进位标志因此对该标志位没有任何影响。 逻辑或OR 格式OR reg/mem, reg/mem/imm受影响的标志位CF(0)OF(0)PFSFZFAF 无定义 逻辑非按位取反NOT 格式NOT reg/mem受影响的标志位无 异或XOR 格式XOR reg/mem, reg/mem/imm受影响的标志位CF(0)OF(0)PFSFZFAF 无定义 TEST 指令 格式TEST reg/mem, reg/mem/imm作用执行 AND 但不影响目标操作数。受影响的标志位CF(0)OF(0)PFSFZFAF 无定义 以 x 0 ? x : -x 为例我们可以通过 cwd 指令和逻辑运算指令将其优化为无分支程序 mov ax, x cwd ; 如果 x 0 则 dx -1 否则 dx 0 xor ax, dx ; 如果 x 0 则将 ax 取反否则 ax 不变 sub ax, dx ; 如果 x 0 则将 ax 加一否则 ax 不变移位指令 算术移位和逻辑移位 格式OP reg/mem, 1/cl影响标志OFZFSFPFCF指令 SALShift Arithmetic Left/SHLShift Logical Left算术左移/逻辑左移 SARShift Arithmetic Right算术右移 SHRShift Logical Right逻辑右移 循环移位 格式OP reg/mem, 1/cl影响标志OFCF其他标志无定义。指令 ROLRotate Left循环左移 RORRotate Right循环右移 RCLRotate through Carry Left带进位循环左移 RCRRotate through Carry Right带进位循环右移 串操作类指令 串操作指令 源操作数使用 SI 默认段为 DS 可段超越。目的操作数使用 DI 默认段为 ES 不可段超越。DF 寄存器决定串操作方向。 DF 值为 0(UP) 则执行完指令之后 SI 和 DI 都加操作的数据长度。DF 值为 1(DN) 则执行完指令之后 DI 和 DI 都减操作的数据长度。 段超越segment override是指在指令中显式地指定要使用的段寄存器而不是使用默认的段寄存器。 MOVSMove String串移动把字节或字操作数从主存的源地址传送至目的地址。 MOVSB字节串传送ES:[DI] ← DS:[SI] (SI ← SI ± 1, DI ← DI ± 1) 。MOVSW字串传送 ES:[DI] ← DS:[SI] (SI ← SI ± 2, DI ← DI ± 2) 。MOVSD双字串传送 ES:[DI] ← DS:[SI] (SI ← SI ± 4, DI ← DI ± 4) 。 STOSStore String串存储把 AL 或 AX 数据传送至目的地址。 STOSB字节串存储ES:[DI] ← AL (DI ← DI ± 1) 。STOSW字串存储ES:[DI] ← AX (DI ← DI ± 2) 。STOSD双字串存储ES:[DI] ← EAX (DI ← DI ± 4) 。 LODSLoad String串读取把指定主存单元的数据传送给 AL 或 AX 。 LODSB字节读取AL ← DS:[SI] (SI ← SI ± 1) 。LODSW字串读取AX ← DS:[SI] (SI ← SI ± 2) 。LODSD双字串读取EAX ← DS:[SI] (SI ← SI ± 4) 。 CMPSCompare String串比较将主存中的源操作数减去至目的操作数以便设置标志进而比较两操作数之间的关系。 CMPSB字节串比较DS:[SI] - ES:[DI] (SI ← SI ± 1, DI ← DI ± 1) 。CMPSW字串比较DS:[SI] - ES:[DI] (SI ← SI ± 2, DI ← DI ± 2) 。CMPSD双字串比较DS:[SI] - ES:[DI] (SI ← SI ± 4, DI ← DI ± 4) 。 SCASScan String串扫描将 AL/AX 减去至目的操作数以便设置标志进而比较 AL/AX 与操作数之间的关系。 SCASB字节串扫描AL - ES:[DI] (DI ← DI ± 1) 。SCASW字串扫描AX - ES:[DI] (DI ← DI ± 2) 。SCASD双字串扫描EAX - ES:[DI] (DI ← DI ± 4) 。 重复前缀指令 串操作指令执行一次仅对数据串中的一个字节或字进行操作。 串操作指令前都可以加一个重复前缀实现串操作的重复执行。重复次数隐含在 CX 寄存器中。 REP每执行一次串指令CX 减 1 直到 CX 0 重复执行结束。 含义当数据串没有结束CX ≠ 0则继续传送。举例 REP LODS/LODSB/LODSW/LODSDREP STOS/STOSB/STOSW/STOSDREP MOVS/MOVSB/MOVSW/MOVSD REPZ每执行一次串指令CX 减 1 并判断 ZF 是否为 0 。只要 CX 0 或 ZF 0 则重复执行结束。 含义当数据串没有结束CX ≠ 0并且串相等ZF 1则继续比较。举例 REPE/REPZ SCAS/SCASB/SCASW/SCASDREPE/REPZ CMPS/CMPSB/CMPSW/CMPSD REPNZ每执行一次串指令CX 减 1 并判断 ZF 是否为 1 。只要 CX 0 或 ZF 1 则重复执行结束。 含义当数据串没有结束CX ≠ 0并且串不相等ZF 0则继续比较。举例 REPNE/REPNZ SCAS/SCASB/SCASW/SCASDREPNE/REPNZ CMPS/CMPSB/CMPSW/CMPSD 流程转移类指令 无条件跳转 直接转移 名称修饰关键字格式功能指令长度示例短跳shortjmp short 标号ip ← 标号偏移20005:EB0B jmp 0012近跳near ptrjmp near 标号ip ← 标号偏移30007:E90A01 jmp 0114远跳far ptrjmp far ptr 标号jmp 段名:标号ip ← 标号偏移cs ← 段地址50000:EA00007C07 jmp 0077C:0000 使用寄存器间接转移 格式jmp regreg 为通用寄存器功能ip ← reg只能用于段内转移 使用 EA 的间接转移 指令说明示例jmp 变量名jmp word ptr [EA]jmp near ptr [EA]从内存中取出两字节的段偏移然后 ip ← [EA]000b:ff260000 jmp[0000]000f:8d1e0000 lea bx, [0000]0013:ff27 jmp [bx]0000:cd 20jmp 变量名jmp dword ptr [EA]jmp far ptr [EA]从内存中取出两字节的段偏移和两字节段基址然后 ip ← [EA] cs ← [EA 2]0021:ff260600 jmp[0002]0025:8d1e0400 lea bx, [0002]0029:ff2f jmp far [bx]0002:00 00 7d 07 条件跳转 根据标志位判断条件成立则跳转条件不成立则不跳。 单条件跳转 指令英文标志说明JZ/JEzeroequalZF 1相等/等于零JNZ/JNEnot zeronot equalZF 0不相等/不等于零JCXZCX is zeroCX 0CX 为 0JSsignSF 1结果为负JNSnot signSF 0结果为正JP/JPEparityparity evenPF 11 为偶数个JNP/JPOnot parityparity oddPF 01 为奇数个JOoverflowOF 1溢出JNOnot overflowOF 0不溢出JCcarryCF 1进位/小于JNCnot carryCF 0不进位/大于等于 无符号数判断 指令英文标志说明JB/JNAEbelownot above or equalCF 1小于/不大于等于JAE/JNBabove or equalnot belowCF 0大于等于/不小于JBE/JNAbelow or equalnot aboveCF 1 || ZF 1小于等于/不大于JA/JNBEabovenot below or equalCF 0 ZF 0大于/不小于等于 有符号判断 指令英文标志说明JL/JNGElessnot geater or equalSF ! OF小于/不大于等于JGE/JNLgreater or equalnot lessSF OF大于等于/不小于JLE/JNGless or equalnot greaterSF ! OF || ZF 1小于等于/不大于JG/JNLEgreaternot less or equalSF OF ZF 0大于/不小于等于 LOOP 格式LOOP 标号 只能用于转移。 指令重复条件LOOPCX ! 0LOOPZ/LOOPECX ! 0 ZF 1LOOPNZ/LOOPNECX ! 0 ZF 0 函数调用相关指令 指令说明功能call (near ptr) 标号段内直接调用push 返回地址jmp 标号call REGcall near ptr|word ptr [EA]段内间接调用push 返回地址jmp 函数地址call far ptr 标号call dword ptr [EA]段间调用push cspush 返回地址jmp 标号ret (n)段内返回pop ipadd sp, nretf (n)段间返回pop ippop csadd sp, n 处理器控制类指令 masm 基础 VSCode 开发环境配置 配置 DOSBox 环境变量安装 VSCode安装 MASM/TASM 和 VSCode DOSBox 插件设置 → 扩展 → MASM/TASM 配置插件 完成上述配置后打开 asm 文件右键会出现“打开DOS环境”等选项这里使用的是 DOS 自带的 MASM 开发环境和 DOSBox 配置文件不需要配置直接可以编译运行 asm 文件。 编译命令 ml /c asm文件.asm link asm文件.obj编译调试脚本VSCode自带这个功能 ml /c %1.asm link %1.obj debug %1.exe如果多个 asm 文件编译则将命令中分别添加参与编译的 asm 文件和生成的 obj 文件即可。 函数和变量声明可以统一放在一个 inc 文件中在使用声明的 asm 文件开头添加 include xxx.inc 即可。 如果想要调试的时候再特定的位置断下来可以在程序中添加 int3 指令或者 db 0cch 。 入口和段 入口 入口点指定使用关键字 end 后跟标号名。如果未指定入口点则默认入口点是整个程序的起始位置。 data_seg segment mov cx,cx mov cx,cx ENTRY: mov cx,cx mov cx,cx data_seg endsend ENTRY段 一个程序必须至少有一个段一个程序中可以定义多个段段不能嵌套段可以重名重名的段会被编译到同一块内存中。段的起始地址关于 0x10 对齐。 段名 segment段名 ends注释 汇编中使用分号;来标注注释汇编中只有行注释没有块注释。 ; 这里是注释 mov ax, bx ; 这里是注释常量 整数 整数可以支持多个进制。数值必须以数字开头如果非数字前面必须加 0 。负数前面可以加负号-。 关键字说明示例无十进制mov ax, 1234D十进制mov ax, 1234dB二进制mov ax, 1011bO八进制mov ax, 76oH十六进制mov ax, 76omov ax, 0abh 字符 字符可以用单引号或双引号例如 mov byte ptr [bx], $ 。 变量 变量可以支持多个类型变量可以有初始值未初始化的值用问号?表示。变量一般定义在一个单独的段中。 变量名 类型 初始值 val dd 5566h关键字意义db字节dw字dd双字dq8 字节dt10 字节 变量使用前需要注意两点 首先要告诉编译器当前使用的是哪个段这样编译器才能提供正确的段偏移。需要给段寄存器设置正确的值。 data_seg segmentg_btVal db 55h data_seg endsuninitdata_seg segmentg_btVal1 db ? uninitdata_seg endscode_seg segment START:assume ds:data_seg ; 告诉编译器当前使用的是哪个段mov ax, data_segmov ds, ax ; 给段寄存器设置正确的值mov al, g_btVal ; 使用变量 code_seg endsend START字符串 字符串都可以用单引号或双引号。字符串一般以美元符$结尾在内存中 $ 是实际跟在字符串后面的这么做是因为有些使用字符串的 API 有要求。 g_szHello db hello,word!$数组 格式 名字 类型 值1[,值2][,值3][,值4][,值5] 名字 类型 数量 dup(初值)[,数量 dup(初值)][,值]示例 g_dbArray1 db 78h, 96h, 43h ; 后面跟初始化的值 g_dbArray2 db 256 dup(0), 128 dup(11h) ; 重复 256 个 0 再跟重复 128 个 1 。 g_dbArray3 db 256 dup(0), 78h, 96h, 43h ; 重复 256 个 0 再跟 78h 96h 43h 。 g_dbArray4 db 256 dup(?) ; 开辟 256 字节的空间不做初始化初始化为 0。属性 masm 提供了很多伪指令可以获取变量的大小和地址称之为变量的属性。这些属性在编译过程中会计算成具体的常量值。 关键字意义seg取段基址offset取段偏移type取元素类型大小length取元素个数size取数据大小length * type 注意 seg 可以作用于段或者段内的变量结果都是得到对应段的基址。length 和 size 都是按定义的数组的第一个“,”前面的部分来计算的。例如前面的 g_dbArray3 计算的 length 是 0x100g_dbArray1 计算的 length 是 1 。区分以下四种用法 lea di, g_dbArray获取 g_dbArray 地址到 DI 寄存器中。lea di, offset g_dbArray获取 g_dbArray 地址到 DI 寄存器中。mov dl, g_dbArray获取 g_dbArray 前 1 个字节到 DI 寄存器中。这里不能用 DX 因为寄存器应该与数组元素大小匹配。mov di, offset g_dbArray获取 g_dbArray 地址到 DI 寄存器中。 示例 mov ax, seg g_dbArray1 mov ax, seg data_seg mov ax, offset g_dbArray1 mov ax, type g_dbArray1 mov ax, length g_dbArray1 mov ax, size g_dbArray1堆栈 stack 关键字让程序在被加载的时候指定 ss bp 和 sp 。使用数组为栈设置大小。 stack_seg segment stackdb 256 dup(0cch) stack_seg ends调用 dos 功能号 DOS 系统提供的功能API通过 21 号中断来调用。每个功能都有一个编号通过 AH 指定功能编号。每个功能的参数查看手册。 例如 AH 为 0x4c 时为退出程序退出码为 AL 。AH 为 0x09 时为输出 $ 结尾的字符字符串地址存放在 DX 中。 利用这两个功能号我们可以实现一个 Hello World 程序。 data_seg segmentg_szHello db hello,word!$ data_seg endsstack_seg segment stackdb 256 dup(0cch) stack_seg endscode_seg segment START:assume ds:data_segmov ax, data_segmov ds, axmov ah, 09mov dx, offset g_szHelloint 21hmov ax, 4c00hint 21h code_seg endsend START中断 中断是 CPU 提供的流程跳转指令类似函数调用。在 00:00 位置存储着一个双字数组大小为 256 称作中断向量表。数组元素为逻辑地址段基址:段偏移段偏移在低 2 字节。int n 的意思是从第 n 个元素获取地址然后跳转执行。 函数 函数结构 函数执行流程 参数入栈返回地址入栈跳转到函数保存栈帧申请局部变量空间保存寄存器环境执行函数功能恢复寄存器环境恢复栈帧弹出返回地址返回[平栈][平栈] 函数定义 函数名 proc [距离][调用约定] [uses reg1 reg2..] [参数:word, 参数名:word..]local 变量:wordlocal 变量:wordret 函数名 endp示例 TestProc PROC far stdcall uses bx dx si di arg1:wordlocal btVal:byteret TestProc ENDP距离 距离关键字说明near函数只能段内调用函数使用 ret 返回调用时 ip 入栈far段内段间都可调用函数使用 retf 返回调用时 ip 和 cs 入栈 如果是用 far 修饰且段内调用汇编器也会手动压一个 cs 寄存器确保 retf 能正常返回。 调用约定 调用约定关键字说明c调用方平栈stdcall被调用方平栈 局部变量 类型局部变量类型备注dbbyte可以直接赋值使用dwword可以直接赋值使用dddword不可以直接赋值使用dqqword不可以直接赋值使用dttword不可以直接赋值使用 一般习惯在局部变量前加 属于一种编程规范。数组局部变量定义local dwBuf[100h]:byte 保存寄存器uses reg1 reg2.. 表示函数中会使用相应的寄存器因此在函数开始和结束位置会保存和恢复相应的寄存器。 invoke 伪指令 invoke 函数名, 参数1, 参数2, 参数3说明 会生成参数入栈代码参数可以是立即数变量寄存器等。注意立即数不能直接 push 汇编器会使用 AX 寄存器中转一下因此注意 AX 寄存器的使用。如果是 C 调用约定会生成平栈代码。如果是局部变量取地址需要使用 addr 伪指令。使用 addr 的时候会用 AX 临时存放指针值因此注意 AX 的使用。 伪指令说明offset获取段内偏移addr获取局部变量地址使用 LEA 指令。专用于 invoke 。 函数声明 如果调用另一个文件中的函数或者在定义函数之前调用函数需要进行函数声明。masm 的函数声明的语法如下 函数名 proto 距离 调用约定 参数列表示例 Fun1 proto fat c pAddr:word宏汇编 表达式 表达式中的求值是在程序链接时完成的所以表达式中的各值必须是在汇编或链接期就能确定也就是说不能将寄存器或者变量运用于表达式。 算术表达式 运算符意义例子加65 32-减size val - 54*乘23h * 65h/除98 / 45mod取模99 / 65 逻辑运算 逻辑运算即位运算逻辑运算符与对应的指令助记符单词是相同的当他们出现在操作码部分时是指令出现在操作数时是逻辑运算符。 运算符意义and位与or位或not按位取反xor异或 关系运算符 关系运算符的结果如果结果为真则所有位都置为 1 即 0xFFFF否则所有位都置为 0 即 0x0000 。 运算符英文例子EQequal等于 NEnot equal不等于 !GTgreater than大于 LTless than小于 GEgreater than or equal大于等于 LEless than or equal小于等于 标号 匿名标号 是匿名标号。b 向上查找最近的 b 是 back 。f 向下查找最近的 f 是 front 。 mov ax, 5566h and 6655h :mov ax, 7788h or 8877hjmp b ; 跳到第 3 行jmp f ; 跳到第 8 行mov ax, not 5566h:mov ax, 5566h xor 7788h调整偏移量指令 ORG 指令ORG 偏移值此指令后的下一个变量或指令从 偏移值 开始存放。两个内容放在同一偏移处后一个内容会覆盖前一个内容。 data_seg segmentg_buf dw 10h dup(0)org 20hg_w dw 65h ; 段偏移 20h 开始存放org 4g_w0 dw 6655h ; 会与 g_buf 的第四个字节开始的数据重复 data_seg ends当前地址指令 $ $ 伪指令代表当前指令或变量的地址段内偏移。常用于计算缓冲区长度和获取当前 IP 值。可与 ORG 配合使用。 结构体 结构体名 struc; 这里定义结构体成员结构体名 ends结构体使用 来初始化。结构体可以通过变量名和寄存器来访问成员。 Student strucm_sz db 64 dub(0)m_id dw 0 Student endsdata_seg segmentg_stu Students Hello, 5566h ; 结构体全局变量 data_seg endsCODE segmentFunc1 PROClocal stu:Students ; 结构体局部变量mov stu.m_id, 6 ; 使用结构体局部变量assume bx:ptr Studentslea bx, stumov [bx].m_id, 6 ; 使用结构体指针ret Func1 ENDPCODE ends宏 equ 语句 不可以重命名可用于常量和表达式可用于字符串可用于指令名给指令取别名可用于类型给类型取别名可用于操作数 COUNT equ 100h ; 后跟数值 SZHELLO equ Hello,world! MOVE equ mov ; 后跟助记符 MYWORD equ dw ; 后跟类型 BX_CONE equ byte ptr [bx] ; 后跟表达式语句 可以被修改只能用于常数 COUNT2 100h ; 后跟数值 COUNT2 100h ; 可以再次赋值 mov ax, COUNT2macro 语句 宏名 macro [参数1][,参数2]...宏体 endm宏会在使用的地方展开宏可以带参数字符串拼接使用 movm macro op1, op2push op2pop op1 endmshift macro n, reg, dmov cl, nrod reg, cl endm 分支 .IF condition; 条件成立时所执行的指令序列 .ENDIF.IF condition; 条件成立时所执行的指令序列 .ELSE; 条件不成立时所执行的指令序列 .ENDIF.IF condition1; condition1 成立时所执行的指令序列 .ELSEIF condition2; condition2 成立时所执行的指令序列 .ENDIF其中条件表达式 condition 的书写方式与 C 语言中条件表达式的书写方式相似也可用括号来组成复杂的条件表达式。 循环 .WHILE condition循环体的指令序列    ; 条件condition”成立时所执行的指令序列 .ENDW多文件编译 源文件 源文件后缀名为 asm每个源文件末尾都需要有 end 头文件 汇编头文件后缀名为 inc头文件包含 include xxx.inc头文件防重复包含 ifndef SECOND_1 SECOND_1 equ 1Func1 proto far stdcall arg1:word, arg2:word extern g_dw:wordendif函数使用 函数在源文件定义在头文件声明即可。 全局变量 全局变量定义在文件中必须使用 public 指明此变量为全局 public 变量名 。全局变量在使用文件中必须使用 extern 指明此变量来自外部文件 extern 变量:类型 。 ; 文件1 public g_wValdata_seg segmentg_wVal dw 5566h data_seg ends; 文件2 extern g_wVal:word
http://www.w-s-a.com/news/45794/

相关文章:

  • 掉关键词网站敏捷软件开发流程
  • 微信小程序格泰网站建设新闻采编与制作专业简历
  • 电子商城建设网站海伦网站建设
  • 南充能够建设网站的公司有专门做设计的一个网站
  • 免费域名申请个人网站阿里巴巴运营的工作内容
  • 怎么建自己的手机网站保定电子商务网站建设
  • 系部网站建设中期检查表创建网站的公司
  • 西宁网站建设优化重庆企业的网站建设
  • 贝壳企业网站管理系统徽与章网站建设宗旨
  • 郑州网站模板动漫设计与制作设计课程
  • 在线制作网站的工具岳阳网站设计改版
  • 网站建设需要汇报哪些内容前端开发的工作内容
  • 无锡阿凡达网站建设美团app开发公司
  • 个性化企业网站制作公司深圳高端网站定制公
  • 专业深圳网站定制开发企业网站开发 流程
  • 网站建设推广的软文php网站平台
  • 如何做代刷网站长外贸网站个性设计
  • 合同网站开发 设计 后期维护如何搭建海外网络
  • 提供网站建设服务优化大师哪个好
  • 军队营房基础建设网站哦咪咖网站建设
  • fifa17做任务网站app下载免费安装
  • 网站开发用哪些技术seo是什么意思为什么要做seo
  • 网站会动的页面怎么做的与网站建设有关的招标文件
  • 公司网站如何做seowordpress付费资源
  • 福田做商城网站建设哪家公司便宜点WordPress安装子目录
  • 南京建设交易中心网站wordpress 拼车
  • 上海今天发生的重大新闻5条河南网站seo费用
  • 广东深圳最新情况临安网站seo
  • 华为快速建站女人做春梦网站
  • 建外贸网站费用手机排行榜zol