Golang 汇编入门知识总结

作者: samool    分类: Technology    发布时间: 09-27 14:35    浏览次数:     无留言

作者:ivansli,腾讯 IEG 运营开发工程师

在深入学习 Golang 的 runtime 和标准库实现的时候发现,如果对 Golang 汇编没有一定了解的话,很难深入了解其底层实现机制。在这里整理总结了一份基础的 Golang 汇编入门知识,通过学习之后能够对其底层实现有一定的认识。

0. 为什么写本文

平时业务中一直使用 PHP 编写代码,但是一直对 Golang 比较感兴趣,闲暇、周末之余会看一些 Go 底层源码。

近日在分析 go 的某些特性底层功能实现时发现:有些又跟 runtime 运行时有关,而要掌握这一部分的话,有一道坎是绕不过去的,那就是 Go 汇编。索性就查阅了很多大佬们写的资料,在阅读之余整理总结了一下,并在这里分享给大家。

本文使用 Go 版本为 go1.14.1

1. 为什么需要汇编

众所周知,在计算机的世界里,只有 2 种类型。那就是:0 和 1。

计算机工作是由一系列的机器指令进行驱动的,这些指令又是一组二进制数字,其对应计算机的高低电平。而这些机器指令的集合就是机器语言,这些机器语言在最底层是与硬件一一对应的。

显而易见,这样的机器指令有一个致命的缺点:可阅读性太差(恐怕也只有天才和疯子才有能力把控得了)。

为了解决可读性的问题以及代码编辑的需求,于是就诞生了最接近机器的语言:汇编语言(在我看来,汇编语言更像一种助记符,这些人们容易记住的每一条助记符都映射着一条不容易记住的由 0、1 组成的机器指令。你觉得像不像域名与 IP 地址的关系呢?)。

1.1 程序的编译过程

以 C 语言为例来说,从 hello.c 的源码文件到 hello 可执行文件,经过编译器处理,大致分为几个阶段:

编译器在不同的阶段会做不同的事情,但是有一步是可以确定的,那就是:源码会被编译成汇编,最后才是二进制。

2. 程序与进程

源码经过编译之后,得到一个二进制的可执行 文件文件这两个字也就表明,目前得到的这个文件跟其他文件对比,除了是具有一定的格式(Linux 中是 ELF 格式,即:可运行可链接。executable linkable formate)的二进制组成,并没什么区别。

在 Linux 中文件类型大致分为 7 种:

b: 块设备文件c:字符设备文件d:目录-:普通文件l:链接s:socketp:管道

通过上面可以看到,可执行文件 main 与源码文件 main.go,都是同一种类型,属于普通文件。(当然了,在 Unix 中有一句很经典的话:一切皆文件)。

那么,问题来了:

  1. 什么是程序?
  2. 什么是进程?

2.1 程序

维基百科告诉我们:程序是指一组指示计算机或其他具有消息处理能力设备每一步动作的指令,通常用某种程序设计语言编写,运行于某种目标体系结构上。

从某个层面来看,可以把程序分为静态程序、动态程序:静态程序:单纯的指具有一定格式的可执行二进制文件。动态程序:则是静态可执行程序文件被加载到内存之后的一种运行时模型(又称为进程)。

2.2 进程

首先,要知道的是,进程是分配系统资源的最小单位,线程(带有时间片的函数)是系统调度的最小单位。进程包含线程,线程所属于进程。

创建进程一般使用 fork 方法(通常会有个拉起程序,先 fork 自身生成一个子进程。然后,在该子进程中通过 exec 函数把对应程序加载进来,进而启动目标进程。当然,实际上要复杂得多),而创建线程则是使用 pthread 线程库。

以 32 位 Linux 操作系统为例,进程经典的虚拟内存结构模型如下图所示:

其中,有两处结构是静态程序所不具有的,那就是 运行时堆(heap)运行时栈(stack)

运行时堆从低地址向高地址增长,申请的内存空间需要程序员自己或者由 GC 释放。运行时栈从高地址向低地址增长,内存空间在当前栈桢调用结束之后自动释放(并不是清除其所占用内存中数据,而是通过栈顶指针 SP 的移动,来标识哪些内存是正在使用的)。

3. Go 汇编

对于 Go 编译器而言,其输出的结果是一种抽象可移植的汇编代码,这种汇编(Go 的汇编是基于 Plan9 的汇编)并不对应某种真实的硬件架构。Go 的汇编器会使用这种伪汇编,再为目标硬件生成具体的机器指令。

伪汇编这一个额外层可以带来很多好处,最主要的一点是方便将 Go 移植到新的架构上。

相关的信息可以参考 Rob PikeThe Design of the Go Assembler

要了解 Go 的汇编器最重要的是要知道 Go 的汇编器不是对底层机器的直接表示,即 Go 的汇编器没有直接使用目标机器的汇编指令。Go 汇编器所用的指令,一部分与目标机器的指令一一对应,而另外一部分则不是。这是因为编译器套件不需要汇编器直接参与常规的编译过程。

相反,编译器使用了一种半抽象的指令集,并且部分指令是在代码生成后才被选择的。汇编器基于这种半抽象的形式工作,所以虽然你看到的是一条 MOV 指令,但是工具链针对对这条指令实际生成可能完全不是一个移动指令,也许会是清除或者加载。也有可能精确的对应目标平台上同名的指令。概括来说,特定于机器的指令会以他们的本尊出现, 然而对于一些通用的操作,如内存的移动以及子程序的调用以及返回通常都做了抽象。细节因架构不同而不一样,我们对这样的不精确性表示歉意,情况并不明确。

汇编器程序的工作是对这样半抽象指令集进行解析并将其转变为可以输入到链接器的指令。

The most important thing to know about Go’s assembler is that it is not a direct representation of the underlying machine. Some of the details map precisely to the machine, but some do not. This is because the compiler suite needs no assembler pass in the usual pipeline. Instead, the compiler operates on a kind of semi-abstract instruction set, and instruction selection occurs partly after code generation. The assembler works on the semi-abstract form, so when you see an instruction like MOV what the toolchain actually generates for that operation might not be a move instruction at all, perhaps a clear or load.

Or it might correspond exactly to the machine instruction with that name. In general, machine-specific operations tend to appear as themselves, while more general concepts like memory move and subroutine call and return are more abstract. The details vary with architecture, and we apologize for the imprecision; the situation is not well-defined.

The assembler program is a way to parse a description of that semi-abstract instruction set and turn it into instructions to be input to the linker.

Go 汇编使用的是 caller-save模式,被调用函数的入参参数、返回值都由调用者维护、准备。因此,当需要调用一个函数时,需要先将这些工作准备好,才调用下一个函数,另外这些都需要进行内存对齐,对齐的大小是 sizeof(uintptr)。

3.1 几个概念

在深入了解 Go 汇编之前,需要知道的几个概念:

  • 栈:进程、线程、goroutine 都有自己的调用栈,先进后出(FILO)
  • 栈帧:可以理解是函数调用时,在栈上为函数所分配的内存区域
  • 调用者:caller,比如:A 函数调用了 B 函数,那么 A 就是调用者
  • 被调者:callee,比如:A 函数调用了 B 函数,那么 B 就是被调者

3.2 Go 的核心寄存器

go 汇编中有 4 个核心的伪寄存器,这 4 个寄存器是编译器用来维护上下文、特殊标识等作用的:

寄存器说明
SB(Static base pointer)global symbols
FP(Frame pointer)arguments and locals
PC(Program counter)jumps and branches
SP(Stack pointer)top of stack
  • FP: 使用如symbol+offset(FP)的方式,引用 callee 函数的入参参数。例如arg0+0(FP),arg1+8(FP),使用 FP 必须加 symbol ,否则无法通过编译(从汇编层面来看,symbol 没有什么用,加 symbol 主要是为了提升代码可读性)。另外,需要注意的是:往往在编写 go 汇编代码时,要站在 callee 的角度来看(FP),在 callee 看来,(FP)指向的是 caller 调用 callee 时传递的第一个参数的位置。假如当前的 callee 函数是 add,在 add 的代码中引用 FP,该 FP 指向的位置不在 callee 的 stack frame 之内。而是在 caller 的 stack frame 上,指向调用 add 函数时传递的第一个参数的位置,经常在 callee 中用symbol+offset(FP)来获取入参的参数值。
  • SB: 全局静态基指针,一般用在声明函数、全局变量中。
  • SP: 该寄存器也是最具有迷惑性的寄存器,因为会有伪 SP 寄存器和硬件 SP 寄存器之分。plan9 的这个伪 SP 寄存器指向当前栈帧第一个局部变量的结束位置(为什么说是结束位置,可以看下面寄存器内存布局图),使用形如 symbol+offset(SP) 的方式,引用函数的局部变量。offset 的合法取值是 [-framesize, 0),注意是个左闭右开的区间。假如局部变量都是 8 字节,那么第一个局部变量就可以用 localvar0-8(SP) 来表示。与硬件寄存器 SP 是两个不同的东西,在栈帧 size 为 0 的情况下,伪寄存器 SP 和硬件寄存器 SP 指向同一位置。手写汇编代码时,如果是 symbol+offset(SP)形式,则表示伪寄存器 SP。如果是 offset(SP)则表示硬件寄存器 SP。务必注意:对于编译输出(go tool compile -S / go tool objdump)的代码来讲,所有的 SP 都是硬件 SP 寄存器,无论是否带 symbol(这一点非常具有迷惑性,需要慢慢理解。往往在分析编译输出的汇编时,看到的就是硬件 SP 寄存器)。
  • PC: 实际上就是在体系结构的知识中常见的 pc 寄存器,在 x86 平台下对应 ip 寄存器,amd64 上则是 rip。除了个别跳转之外,手写 plan9 汇编代码时,很少用到 PC 寄存器。

通过上面的讲解,想必已经对 4 个核心寄存器的区别有了一定的认识(或者是更加的迷惑、一头雾水)。那么,需要留意的是:如果是在分析编译输出的汇编代码时,要重点看 SP、SB 寄存器(FP 寄存器在这里是看不到的)。如果是,在手写汇编代码,那么要重点看 FP、SP 寄存器。

3.2.1 伪寄存器的内存模型

下图描述了栈桢与各个寄存器的内存关系模型,值得注意的是要站在 callee 的角度来看。

有一点需要注意的是,return addr 也是在 caller 的栈上的,不过往栈上插 return addr 的过程是由 CALL 指令完成的(在分析汇编时,是看不到关于 addr 相关空间信息的。在分配栈空间时,addr 所占用空间大小不包含在栈帧大小内)。

在 AMD64 环境,伪 PC 寄存器其实是 IP 指令计数器寄存器的别名。伪 FP 寄存器对应的是 caller 函数的帧指针,一般用来访问 callee 函数的入参参数和返回值。伪 SP 栈指针对应的是当前 callee 函数栈帧的底部(不包括参数和返回值部分),一般用于定位局部变量。伪 SP 是一个比较特殊的寄存器,因为还存在一个同名的 SP 真寄存器,真 SP 寄存器对应的是栈的顶部。

在编写 Go 汇编时,当需要区分伪寄存器和真寄存器的时候只需要记住一点:伪寄存器一般需要一个标识符和偏移量为前缀,如果没有标识符前缀则是真寄存器。比如(SP)、+8(SP)没有标识符前缀为真 SP 寄存器,而 a(SP)、b+8(SP)有标识符为前缀表示伪寄存器。

3.2.2 几点说明

我们这里对容易混淆的几点简单进行说明:

  • 伪 SP 和硬件 SP 不是一回事,在手写汇编代码时,伪 SP 和硬件 SP 的区分方法是看该 SP 前是否有 symbol。如果有 symbol,那么即为伪寄存器,如果没有,那么说明是硬件 SP 寄存器。
  • 伪 SP 和 FP 的相对位置是会变的,所以不应该尝试用伪 SP 寄存器去找那些用 FP+offset 来引用的值,例如函数的入参和返回值。
  • 官方文档中说的伪 SP 指向 stack 的 top,可能是有问题的。其指向的局部变量位置实际上是整个栈的栈底(除 caller BP 之外),所以说 bottom 更合适一些。
  • 在 go tool objdump/go tool compile -S 输出的代码中,是没有伪 SP 和 FP 寄存器的,我们上面说的区分伪 SP 和硬件 SP 寄存器的方法,对于上述两个命令的输出结果是没法使用的。在编译和反汇编的结果中,只有真实的 SP 寄存器。
3.2.3 IA64 和 plan9 的对应关系

在 plan9 汇编里还可以直接使用的 amd64 的通用寄存器,应用代码层面会用到的通用寄存器主要是: rax, rbx, rcx, rdx, rdi, rsi, r8~r15 这些寄存器,虽然 rbp 和 rsp 也可以用,不过 bp 和 sp 会被用来管理栈顶和栈底,最好不要拿来进行运算。

plan9 中使用寄存器不需要带 r 或 e 的前缀,例如 rax,只要写 AX 即可: MOVQ $101, AX = mov rax, 101

下面是通用通用寄存器的名字在 IA64 和 plan9 中的对应关系:

3.3 常用操作指令

下面列出了常用的几个汇编指令(指令后缀 Q 说明是 64 位上的汇编指令)

助记符指令种类用途示例
MOVQ传送数据传送MOVQ 48, AX // 把 48 传送到 AX
LEAQ传送地址传送LEAQ AX, BX // 把 AX 有效地址传送到 BX
PUSHQ传送栈压入PUSHQ AX // 将 AX 内容送入栈顶位置
POPQ传送栈弹出POPQ AX // 弹出栈顶数据后修改栈顶指针
ADDQ运算相加并赋值ADDQ BX, AX // 等价于 AX+=BX
SUBQ运算相减并赋值SUBQ BX, AX // 等价于 AX-=BX
CMPQ运算比较大小CMPQ SI CX // 比较 SI 和 CX 的大小
CALL转移调用函数CALL runtime.printnl(SB) // 发起调用
JMP转移无条件转移指令JMP 0x0185 //无条件转至 0x0185 地址处
JLS转移条件转移指令JLS 0x0185 //左边小于右边,则跳到 0x0185

4. 汇编分析

说了那么多,it is code show time。

4.1 如何输出 Go 汇编

对于写好的 go 源码,生成对应的 Go 汇编,大概有下面几种

  • 方法 1 先使用go build -gcflags "-N -l" main.go 生成对应的可执行二进制文件 再使用go tool objdump -s "main\." main 反编译获取对应的汇编

反编译时 "main\." 表示只输出 main 包中相关的汇编 "main\.main" 则表示只输出 main 包中 main 方法相关的汇编

  • 方法 2 使用go tool compile -S -N -l main.go 这种方式直接输出汇编
  • 方法 3 使用go build -gcflags="-N -l -S" main.go 直接输出汇编

注意:在使用这些命令时,加上对应的 flag,否则某些逻辑会被编译器优化掉,而看不到对应完整的汇编代码

-l 禁止内联 -N 编译时,禁止优化 -S 输出汇编代码

4.2 Go 汇编示例

go 示例代码

package main func add(a, b int) int{        sum := 0 // 不设置该局部变量sum,add栈空间大小会是0        sum = a+b        return sum} func main(){        println(add(1,2))}

编译 go 源代码,输出汇编

go tool compile -N -l -S main.go

截取主要汇编如下:

"".add STEXT nosplit size=60 args=0x18 locals=0x10        0x0000 00000 (main.go:3)        TEXT    "".add(SB), NOSPLIT, $16-24        0x0000 00000 (main.go:3)        SUBQ    $16, SP  ;;生成add栈空间        0x0004 00004 (main.go:3)        MOVQ    BP, 8(SP)        0x0009 00009 (main.go:3)        LEAQ    8(SP), BP    ;; ...omitted FUNCDATA stuff...        0x000e 00014 (main.go:3)        MOVQ    $0, "".~r2+40(SP) ;;初始化返回值        0x0017 00023 (main.go:4)        MOVQ    $0, "".sum(SP) ;;局部变量sum赋为0        0x001f 00031 (main.go:5)        MOVQ    "".a+24(SP), AX  ;;取参数a        0x0024 00036 (main.go:5)        ADDQ    "".b+32(SP), AX ;;等价于AX=a+b        0x0029 00041 (main.go:5)        MOVQ    AX, "".sum(SP)  ;;赋值局部变量sum        0x002d 00045 (main.go:6)        MOVQ    AX, "".~r2+40(SP) ;;设置返回值        0x0032 00050 (main.go:6)        MOVQ    8(SP), BP        0x0037 00055 (main.go:6)        ADDQ    $16, SP ;;清除add栈空间        0x003b 00059 (main.go:6)        RET    ...... "".main STEXT size=107 args=0x0 locals=0x28        0x0000 00000 (main.go:9)        TEXT    "".main(SB), $40-0    ......        0x000f 00015 (main.go:9)        SUBQ    $40, SP ;; 生成main栈空间        0x0013 00019 (main.go:9)        MOVQ    BP, 32(SP)        0x0018 00024 (main.go:9)        LEAQ    32(SP), BP    ;; ...omitted FUNCDATA stuff...        0x001d 00029 (main.go:10)       MOVQ    $1, (SP) ;;add入参:1        0x0025 00037 (main.go:10)       MOVQ    $2, 8(SP) ;;add入参:2        0x002e 00046 (main.go:10)       CALL    "".add(SB) ;;调用add函数        0x0033 00051 (main.go:10)       MOVQ    16(SP), AX        0x0038 00056 (main.go:10)       MOVQ    AX, ""..autotmp_0+24(SP)        0x003d 00061 (main.go:10)       CALL    runtime.printlock(SB)        0x0042 00066 (main.go:10)       MOVQ    ""..autotmp_0+24(SP), AX        0x0047 00071 (main.go:10)       MOVQ    AX, (SP)        0x004b 00075 (main.go:10)       CALL    runtime.printint(SB)        0x0050 00080 (main.go:10)       CALL    runtime.printnl(SB)        0x0055 00085 (main.go:10)       CALL    runtime.printunlock(SB)        0x005a 00090 (main.go:11)       MOVQ    32(SP), BP        0x005f 00095 (main.go:11)       ADDQ    $40, SP ;;清除main栈空间        0x0063 00099 (main.go:11)       RET    ......

这里列举了一个简单的 int 类型 加法示例,实际开发中会遇到各种参数类型,要复杂的多,这里只是抛砖引玉 :)

4.3 Go 汇编解析

针对 4.2 输出汇编,对重要核心代码进行分析。

4.3.1 add 函数汇编解析
  • TEXT "".add(SB), NOSPLIT|ABIInternal, $16-24

TEXT "".add TEXT 指令声明了 "".add 是 .text 代码段的一部分,并表明跟在这个声明后的是函数的函数体。在链接期,""这个空字符会被替换为当前的包名: 也就是说,"".add 在链接到二进制文件后会变成 main.add

(SB) SB 是一个虚拟的伪寄存器,保存静态基地址(static-base) 指针,即我们程序地址空间的开始地址。"".add(SB) 表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲,它有一个直接的绝对地址: 是一个全局的函数符号。

NOSPLIT: 向编译器表明不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下,编译器自己帮我们插入了这个标记: 它足够聪明地意识到,由于 add 没有任何局部变量且没有它自己的栈帧,所以一定不会超出当前的栈。不然,每次调用函数时,在这里执行栈检查就是完全浪费 CPU 时间了。

$0-16

24 指定了调用方传入的参数+返回值大小(24 字节=入参 a、b 大小 8字节*2+ 返回值8字节)> 通常来讲,帧大小后一般都跟随着一个参数大小,用减号分隔。(这不是一个减法操作,只是一种特殊的语法) 帧大小 $24-8 意味着这个函数有 24 个字节的帧以及 8 个字节的参数,位于调用者的帧上。如果 NOSPLIT 没有在 TEXT 中指定,则必须提供参数大小。对于 Go 原型的汇编函数,go vet 会检查参数大小是否正确。

In the general case, the frame size is followed by an argument size, separated by a minus sign. (It’s not a subtraction, just idiosyncratic syntax.) The frame size $24-8 states that the function has a 24-byte frame and is called with 8 bytes of argument, which live on the caller’s frame. If NOSPLIT is not specified for the TEXT, the argument size must be provided. For assembly functions with Go prototypes, go vet will check that the argument size is correct.
  • SUBQ $16, SPSP 为栈顶指针,该语句等价于 SP-=16(由于栈空间是向下增长的,所以开辟栈空间时为减操作),表示生成 16 字节大小的栈空间。
  • MOVQ $0, "".~r2+40(SP)此时的 SP 为 add 函数栈的栈顶指针,40(SP)的位置则是 add 返回值的位置,该位置位于 main 函数栈空间内。该语句设置返回值类型的 0 值,即初始化返回值,防止得到脏数据(返回值类型为 int,int 的 0 值为 0)。
  • MOVQ "".a+24(SP), AX从 main 函数栈空间获取入参 a 的值,存到寄存器 AX
  • ADDQ "".b+32(SP), AX从 main 函数栈空间获取入参 b 的值,与寄存器 AX 中存储的 a 值相加,结果存到 AX。相当于 AX=a+b
  • MOVQ AX, "".~r2+40(SP)把 a+b 的结果放到 main 函数栈中, add(a+b)返回值所在的位置
  • ADDQ $16, SP归还 add 函数占用的栈空间
4.3.2 函数栈桢结构模型

根据 4.2 对应汇编绘制的函数栈桢结构模型

还记得前面提到的,Go 汇编使用的是 caller-save模式,被调用函数的参数、返回值、栈位置都需要由调用者维护、准备吗?

在函数栈桢结构中可以看到,add()函数的入参以及返回值都由调用者 main()函数维护。也正是因为如此,GO 有了其他语言不具有的,支持多个返回值的特性。

4.4 Go 汇编语法

这里重点讲一下函数声明、变量声明。

4.4.1 函数声明

来看一个典型的 Go 汇编函数定义

// func add(a, b int) int// 该add函数声明定义在同一个 package name 下的任意 .go文件中// 只有函数头,没有实现 // add函数的Go汇编实现// pkgname 默认是  ""TEXT pkgname·add(SB), NOSPLIT, $16-24    MOVQ a+0(FP), AX    ADDQ b+8(FP), AX    MOVQ AX, ret+16(FP)    RET

Go 汇编实现为什么是 TEXT 开头?仔细观察上面的进程内存布局图就会发现,我们的代码在是存储在.text 段中的,这里也就是一种约定俗成的起名方式。实际上在 plan9 中 TEXT 是一个指令,用来定义一个函数。

定义中的 pkgname 是可以省略的,(非想写也可以写上,不过写上 pkgname 的话,在重命名 package 之后还需要改代码,默认为 "") 编译器会在链接期自动加上所属的包名称。

中点 · 比较特殊,是一个 unicode 的中点,该点在 mac 下的输入方法是 option+shift+9。在程序被链接之后,所有的中点 ·都会被替换为句号 .,比如你的方法是 runtime·main,在编译之后的程序里的符号则是 runtime.main

简单总结一下, Go 汇编实现函数声明,格式为:

 静态基地址(static-base) 指针    |                  |         add函数入参+返回值总大小                  |               |TEXT pkgname·add(SB),NOSPLIT,$16-24      |      |                |函数所属包名  函数名          add函数栈帧大小
  • 函数栈帧大小:局部变量+可能需要的额外调用函数的参数空间的总大小,不包括调用其它函数时的 ret address 的大小。
  • (SB): SB 是一个虚拟寄存器,保存了静态基地址(static-base) 指针,即我们程序地址空间的开始地址。"".add(SB) 表明我们的符号位于某个固定的相对地址空间起始处的偏移位置 (最终是由链接器计算得到的)。换句话来讲,它有一个直接的绝对地址: 是一个全局的函数符号。
  • NOSPLIT: 向编译器表明,不应该插入 stack-split 的用来检查栈需要扩张的前导指令。在我们 add 函数的这种情况下,编译器自己帮我们插入了这个标记: 它足够聪明地意识到,add 不会超出当前的栈,因此没必要调用函数时在这里执行栈检查。
4.4.2 变量声明

汇编里的全局变量,一般是存储在 .rodata或者 .data段中。对应到 Go 代码,就是已初始化过的全局的 const、var 变量/常量。

使用 DATA 结合 GLOBL 来定义一个变量。

DATA 的用法为:

DATA symbol+offset(SB)/width, value

大多数参数都是字面意思,不过这个 offset 需要注意:其含义是该值相对于符号 symbol 的偏移,而不是相对于全局某个地址的偏移。

GLOBL 汇编指令用于定义名为 symbol 的全局变量,变量对应的内存宽度为 width,内存宽度部分必须用常量初始化。

GLOBL ·symbol(SB), width

下面是定义了多个变量的例子:

DATA ·age+0(SB)/4, $8  ;; 数值8为 4字节GLOBL ·age(SB), RODATA, $4 DATA ·pi+0(SB)/8, $3.1415926 ;; 数值3.1415926为float64, 8字节GLOBL ·pi(SB), RODATA, $8 DATA ·year+0(SB)/4, $2020 ;; 数值2020为 4字节GLOBL ·year(SB), RODATA, $4  ;; 变量hello 使用2个DATA来定义DATA ·hello+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello+8(SB)/8, $"   world" ;; `   world` 共8个字节(3个空格)GLOBL ·hello(SB), RODATA, $16 ;; `hello my   world`  共16个字节  DATA ·hello<>+0(SB)/8, $"hello my" ;; `hello my` 共8个字节DATA ·hello<>+8(SB)/8, $"   world" ;; `   world` 共8个字节(3个空格)GLOBL ·hello<>(SB), RODATA, $16 ;; `hello my   world`  共16个字节

大部分都比较好理解,不过这里引入了新的标记 <>,这个跟在符号名之后,表示该全局变量只在当前文件中生效,类似于 C 语言中的 static。如果在另外文件中引用该变量的话,会报 relocation target not found 的错误。

5. 手写汇编实现功能

在 Go 源码中会看到一些汇编写的代码,这些代码跟其他 go 代码一起组成了整个 go 的底层功能实现。下面,我们通过一个简单的 Go 汇编代码示例来实现两数相加功能。

5.1 使用 Go 汇编实现 add 函数

Go 代码

package main func add(a, b int64) int64 func main(){        println(add(2,3))}

Go 源码中 add()函数只有函数签名,没有具体的实现(使用 GO 汇编实现)

使用 Go 汇编实现的 add()函数

TEXT ·add(SB), $0-24 ;; add栈空间为0,入参+返回值大小=24字节        MOVQ x+0(FP), AX ;; 从main中取参数:2        ADDQ y+8(FP), AX ;; 从main中取参数:3         MOVQ AX, ret+16(FP) ;; 保存结果到返回值         RET

把 Go 源码与 Go 汇编编译到一起(我这里,这两个文件在同一个目录)

go build -gcflags "-N -l" .

我这里目录为 demo1,所以得到可执行程序 demo1,运行得到结果:5

5.2 反编译可执行程序

对 5.1 中得到的可执行程序 demo1 使用 objdump 进行反编译,获取汇编代码

go tool objdump -s "main\." demo1

得到汇编

......TEXT main.main(SB) /root/go/src/demo1/main.go  main.go:5   0x4581d0     64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX  main.go:5   0x4581d9     483b6110                CMPQ 0x10(CX), SP  main.go:5   0x4581dd     7655                    JBE 0x458234  main.go:5   0x4581df     4883ec28                SUBQ $0x28, SP ;;生成main栈桢  main.go:5   0x4581e3     48896c2420              MOVQ BP, 0x20(SP)  main.go:5   0x4581e8     488d6c2420              LEAQ 0x20(SP), BP  main.go:6   0x4581ed     48c7042402000000        MOVQ $0x2, 0(SP) ;;参数值 2  main.go:6   0x4581f5     48c744240803000000      MOVQ $0x3, 0x8(SP) ;;参数值 3  main.go:6   0x4581fe     e83d000000              CALL main.add(SB);;call add  main.go:6   0x458203     488b442410              MOVQ 0x10(SP), AX  main.go:6   0x458208     4889442418              MOVQ AX, 0x18(SP)  main.go:6   0x45820d     e8fe2dfdff              CALL runtime.printlock(SB)  main.go:6   0x458212     488b442418              MOVQ 0x18(SP), AX  main.go:6   0x458217     48890424                MOVQ AX, 0(SP)  main.go:6   0x45821b     e87035fdff              CALL runtime.printint(SB)  main.go:6   0x458220     e87b30fdff              CALL runtime.printnl(SB)  main.go:6   0x458225     e8662efdff              CALL runtime.printunlock(SB)  main.go:7   0x45822a     488b6c2420              MOVQ 0x20(SP), BP  main.go:7   0x45822f     4883c428                ADDQ $0x28, SP  main.go:7   0x458233     c3                      RET  main.go:5   0x458234     e89797ffff              CALL runtime.morestack_noctxt(SB)  main.go:5   0x458239     eb95                    JMP main.main(SB) ;; 反编译得到的汇编与add_amd64.s文件中的汇编大致操作一致TEXT main.add(SB) /root/go/src/demo1/add_amd64.s  add_amd64.s:2   0x458240    488b442408    MOVQ 0x8(SP), AX ;; 获取第一个参数  add_amd64.s:3   0x458245    4803442410    ADDQ 0x10(SP), AX ;;参数a+参数b  add_amd64.s:5   0x45824a    4889442418    MOVQ AX, 0x18(SP) ;;保存计算结果  add_amd64.s:7   0x45824f    c3            RET

通过上面操作,可知:

  1. (FP)伪寄存器,只有在编写 Go 汇编代码时使用。FP 伪寄存器指向 caller 传递给 callee 的第一个参数
  2. 使用 go tool compile / go tool objdump 得到的汇编中看不到(FP)寄存器的踪影

6. Go 调试工具

这里推荐 2 个 Go 代码调试工具。

6.1 gdb 调试 Go 代码

测试代码

package main type Ier interface{        add(a, b int) int        sub(a, b int) int} type data struct{        a, b int} func (*data) add(a, b int) int{        return a+b} func (*data) sub(a, b int) int{        return a-b} func main(){        var t Ier = &data{3,4}         println(t.add(1,2))        println(t.sub(3,2))}

编译 go build -gcflags "-N -l" -o main

使用 GDB 调试

> gdb main GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-80.el7Copyright (C) 2013 Free Software Foundation, Inc.License GPLv3+: GNU GPL version 3 or later http://gnu.org/licenses/gpl.htmlThis is free software: you are free to change and redistribute it.There is NO WARRANTY, to the extent permitted by law.  Type "show copying"and "show warranty" for details.This GDB was configured as "x86_64-redhat-linux-gnu".For bug reporting instructions, please see:<http://www.gnu.org/software/gdb/bugs/>...Reading symbols from /root/go/src/interface/main...done.Loading Go Runtime support.(gdb) list   // 显示源码14      func (*data) add(a, b int) int{15              return a+b16      }1718      func (*data) sub(a, b int) int{19              return a-b20      }212223      func main(){(gdb) list24              var t Ier = &data{3,4}2526              println(t.add(1,2))27              println(t.sub(3,2))28      }29(gdb) b 26  // 在源码26行处设置断点Breakpoint 1 at 0x45827c: file /root/go/src/interface/main.go, line 26.(gdb) rStarting program: /root/go/src/interface/main Breakpoint 1, main.main () at /root/go/src/interface/main.go:2626              println(t.add(1,2))(gdb) info locals  // 显示变量t = {tab = 0x487020 <data,main.Ier>, data = 0xc000096000}(gdb) ptype t  // 打印t的结构type = struct runtime.iface {    runtime.itab *tab;    void *data;}(gdb) p *t.tab.inter  // 打印t.tab.inter指针指向的数据$2 = {typ = {size = 16, ptrdata = 16, hash = 2491815843, tflag = 7 '\a', align = 8 '\b', fieldAlign = 8 '\b',    kind = 20 '\024', equal = {void (void *, void *, bool *)} 0x466ec0,    gcdata = 0x484351 "\002\003\004\005\006\a\b\t\n\f\r\016\017\020\022\025\026\030\033\034\036\037\"&(,-5<BUXx\216\231\330\335\377", str = 6568, ptrToThis = 23808}, pkgpath = {bytes = 0x4592b4 ""}, mhdr =  []runtime.imethod = {{name = 277,      ityp = 48608}, {name = 649, ityp = 48608}}}(gdb) disass  // 显示汇编Dump of assembler code for function main.main:   0x0000000000458210 <+0>:     mov    %fs:0xfffffffffffffff8,%rcx   0x0000000000458219 <+9>:     cmp    0x10(%rcx),%rsp   0x000000000045821d <+13>:    jbe    0x458324 <main.main+276>   0x0000000000458223 <+19>:    sub    $0x50,%rsp   0x0000000000458227 <+23>:    mov    %rbp,0x48(%rsp)   0x000000000045822c <+28>:    lea    0x48(%rsp),%rbp   0x0000000000458231 <+33>:    lea    0x10dc8(%rip),%rax        # 0x469000   0x0000000000458238 <+40>:    mov    %rax,(%rsp)   0x000000000045823c <+44>:    callq  0x40a5c0 <runtime.newobject>

常用的 gdb 调试命令

  • run
  • continue
  • break
  • backtrace 与 frame
  • info break、locals
  • list 命令
  • print 和 ptype 命令
  • disass

除了 gdb,另外推荐一款 gdb 的增强版调试工具 cgdb

https://cgdb.github.io/

效果如下图所示,分两个窗口:上面显示源代码,下面是具体的命令行调试界面(跟 gdb 一样):

#### 6.2 delve 调试代码

delve 项目地址

https://github.com/go-delve/delve

带图形化界面的 dlv 项目地址

https://github.com/aarzilli/gdlv

dlv 的安装使用,这里不再做过多讲解,感兴趣的可以尝试一下。

  • gdb 作为调试工具自是不用多说,比较老牌、强大,可以支持多种语言。
  • delve 则是使用 go 语言开发的,用来调试 go 的工具,功能也是十分强大,打印结果可以显示 gdb 支持不了的东西,这里不再做过多讲解,有兴趣的可以查阅相关资料。

7. 总结

对于 Go 汇编基础大致需要熟悉下面几个方面:

通过上面的例子相信已经让你对 Go 的汇编有了一定的理解。当然,对于大部分业务开发人员来说,只要看的懂即可。如果想进一步的了解,可以阅读相关的资料或者书籍。

最后想说的是:鉴于个人能力有限,在阅读过程中你可能会发现存在的一些问题或者缺陷,欢迎各位大佬指正。如果感兴趣的话,也可以一起私下交流。

8. 参考资料

在整理的过程中,部分参考、引用下面链接地址内容。有一些写的还是不错的,感兴趣的同学可以阅读。

[1] https://github.com/cch123/golang-notes/blob/master/assembly.md plan9 assembly

[2] https://segmentfault.com/a/1190000019753885 汇编入门

[3] https://www.davidwong.fr/goasm/ Go Assembly by Example

[4] https://juejin.im/post/6844904005630443533#heading-3

[5] https://github.com/go-internals-cn/go-internals/blob/master/chapter1_assembly_primer/README.md

[6] https://lrita.github.io/2017/12/12/golang-asm/

[7] https://chai2010.cn/advanced-go-programming-book/ch3-asm/ch3-01-basic.html

汇编指令大全

作者: samool    分类: Develop    发布时间: 12-06 23:11    浏览次数:     无留言

一个汇编指令API,供大家查询使用。

MOV(MOVe) 传送指令
PUSH 入栈指令
POP 出栈指令
XCHG(eXCHanG) 交换指令
XLAT(TRANSLATE) 换码指令
LEA (Load Effective Address) 有效地址送寄存器指令
LDS(Load DS with pointer) 指针送寄存器和DS指令
LES(Load ES with pointer) 指针送寄存器和ES指令
LAHF(Load AH with Flags) 标志位送AH指令
SAHF(Store AH into Flgs) AH送标志寄存器指令
PUSHF(PUSH the Flags) 标志进栈指令
POPF(POP the Flags) 标志出栈指令
ADD 加法指令
ADC 带进位加法指令
INC 加1指令
SUB(SUBtract) 不带借位的减法指令
SBB(SuVtrach with borrow) 带借位的减法指令
DEC(DECrement) 减1指领
NEG(NEGate) 求补指令
CMP(CoMPare) 比较指令
MUL(unsinged MULtiple) 无符号数乘法指令
IMUL(sIgned MUL tiple) 有符号数乘法指令
DIV(unsigned DIVide) 无符号数除法指令
IDIV(sIgned DIVide) 有符号数除法指令
CBW(Count Byte to Word) 字节转换为字指令
CWD(Count Word to Doble word) 字转换为双字指令
DAA 压缩的BCD码加法十进制调整指令
DAS 压缩的BCD码减法十进制调整指令
AAA 非压缩的BCD码加法十进制调整指令
AAS 非压缩的BCD码加法十进制调整指令
AND 逻辑与指令
OR 逻辑或指令
XOR 逻辑异或指令
NOT 逻辑非指令
TEST 测试指令
SHL(SHift logical Letf) 逻辑左移指令
SHR(SHift logical Right) 逻辑右移指令
ROL(Rotate Left ) 循环左移指令P58
ROR(Rotate Right) 循环右移指令P58
RCL(Rotate Left through Carry) 带进位循环左移
RCR(Rotate Right through Carry) 带进位循环左移
MOVS(MOVe String) 串传送指令
STOS(STOre into String) 存入串指令
LODS(LOad from string) 从串取指令
REP(REPeat) 重复操作前
CLD(CLear Direction flag) 清除方向标志指令
STD(SeT Direction flag) 设置方向标志指令
CMPS(CoMPare String) 串比较指令
SCAS(SCAn String) 串扫描指令
REPE/REPZ(REPeat while Equal/Zero)相等/为零时重复操作前缀
REPNE/REPNZ(REPeat while Not Equal/Zero)不相等/不为零进重复前缀
IN(INput) 输入指令
OUT(OUTput) 输出指令
JMP(JuMP) 无条件转移指令
JZ,JNZ,JS,JNS,JO,JNO,JP,JNP,JB,JNB,JBE,JNBE,JL,JNL,JLE,JNLE,JCXZ 条件转移指令
LOOP 循环指令P70
LOOPZ/LOOPE 为零/相等时循环指令
LOOPNZ/LOOPNE 不为零/不相等时循环指令
CALL 子程序调用指令
RET(RETun) 子程序返回指令
CLC(CLear Carry) 进位位置0指令
CMC(CoMplement Carry) 进位位求反指令
SRC(SeT Carry) 进位位置1指令
NOP(No OPeretion) 无操作指令
HLT(HaLT) 停机指令
OFFSET 返回偏移地址
SEG 返回段地址
EQU(=) 等值语句
PURGE 解除语句
DUP 操作数字段用复制操作符
SEGMENT,ENDS 段定义指令
ASSUME 段地址分配指令
ORG 起始偏移地址设置指令
$ 地址计数器的当前值
PROC,ENDP 过程定义语句
NAME,TITLE,END 程序开始结束语句
MACRO,ENDM 宏定义指令

JZ OPR //结果为零转移
JNZ OPR //结果不为零转移
JS OPR //结果为负转移
JNS OPR //结果为正转移
JO OPR //溢出转移
JNO OPR //不溢出转移
JP OPR //结果为偶转移
JNP OPR //结果为奇转移
JC OPR //有进位转移
JNC OPR //无进位转移

汇编级超快字符串替换函数

作者: samool    分类: Develop    发布时间: 10-23 15:19    浏览次数:     无留言

 用这个函数进行字符串替换操作,比Delphi自带的ReplaceString要快N倍,效率一流,非常快,不愧为汇编级函数操作,哈哈.

下载函数单元文件:freplace.rar

delphi代码

  1. unit FReplace;   
  2.   
  3. interface  
  4.   
  5. Type   
  6. TFastPosProc = function(   
  7. const aSourceString, aFindString : String;   
  8. const aSourceLen, aFindLen, StartPos : integer  
  9. ) : integer;   
  10.   
  11. function FastReplace(   
  12. var aSourceString : String;   
  13. const aFindString, aReplaceString : String;   
  14. CaseSensitive : Boolean = False) : String;   
  15.   
  16. function FastPos(   
  17. const aSourceString, aFindString : String;   
  18. const aSourceLen, aFindLen, StartPos : integer  
  19. ) : integer;   
  20.   
  21. function FastPosNoCase(   
  22. const aSourceString, aFindString : String;   
  23. const aSourceLen, aFindLen, StartPos : integer  
  24. ) : integer;   
  25.   
  26. implementation  
  27.   
  28. // This TYPE declaration will become apparent later.  
  29. //The first thing to note here is that I’m passing the SourceLength and FindL  
  30. //ength. As neither Source nor Find will alter at any point during FastReplace  
  31. //, there’s no need to call the LENGTH subroutine each time!  
  32. function FastPos(   
  33. const aSourceString, aFindString : String;   
  34. const aSourceLen, aFindLen, StartPos : integer  
  35. ) : integer;   
  36. var  
  37. SourceLen : integer;   
  38. begin  
  39.   // Next, we determine how many bytes we need to  
  40.   // scan to find the "start" of aFindString.  
  41.   SourceLen := aSourceLen;   
  42.   SourceLen := SourceLen - aFindLen;   
  43.   if (StartPos-1) > SourceLen then begin  
  44.     Result := 0;   
  45.     Exit;   
  46.   end;   
  47.   SourceLen := SourceLen - StartPos;   
  48.   SourceLen := SourceLen +2;   
  49.   // The ASM starts here.  
  50.   asm  
  51.     // Delphi uses ESI, EDI, and EBX a lot,  
  52.     // so we must preserve them.  
  53.     push ESI   
  54.     push EDI   
  55.     push EBX   
  56.     // Get the address of sourceString[1]  
  57.     // and Add (StartPos-1).  
  58.     // We do this for the purpose of finding  
  59.     // the NEXT occurrence, rather than  
  60.     // always the first!  
  61.     mov EDI, aSourceString   
  62.     add EDI, StartPos   
  63.     Dec EDI   
  64.     // Get the address of aFindString.  
  65.     mov ESI, aFindString   
  66.     // Note how many bytes we need to  
  67.     // look through in aSourceString  
  68.     // to find aFindString.  
  69.     mov ECX, SourceLen   
  70.     // Get the first char of aFindString;  
  71.     // note how it is done outside of the  
  72.     // main loop, as it never changes!  
  73.     Mov  Al, [ESI]   
  74.     // Now the FindFirstCharacter loop!  
  75.     @ScaSB:   
  76.     // Get the value of the current  
  77.     // character in aSourceString.  
  78.     // This is equal to ah := EDI^, that  
  79.     // is what the [] are around [EDI].  
  80.     Mov  Ah, [EDI]   
  81.     // Compare this character with aDestString[1].  
  82.     cmp  Ah,Al   
  83.     // If they're not equal we don't  
  84.     // compare the strings.  
  85.     jne  @NextChar   
  86.     // If they're equal, obviously we do!  
  87.     @CompareStrings:   
  88.     // Put the length of aFindLen in EBX.  
  89.     mov  EBX, aFindLen   
  90.     // We DEC EBX to point to the end of  
  91.     // the string; that is, we don't want to  
  92.     // add 1 if aFindString is 1 in length!  
  93.     dec  EBX   
  94.        
  95.     // add by ShengQuanhu  
  96.     // If EBX is zero, then we've successfully  
  97.     // compared each character; i.e. it's A MATCH!  
  98.     // It will be happened when aFindLen=1  
  99.     Jz @EndOfMatch   
  100.     //add end  
  101.   
  102.     //Here’s another optimization tip. People at this point usually PUSH ESI and  
  103. //so on and then POP ESI and so forth at the end–instead, I opted not to chan  
  104. //ge ESI and so on at all. This saves lots of pushing and popping!  
  105.     @CompareNext:   
  106.     // Get aFindString character +  
  107.     // aFindStringLength (the last char).  
  108.     mov  Al, [ESI+EBX]   
  109.     // Get aSourceString character (current  
  110.     // position + aFindStringLength).  
  111.     mov  Ah, [EDI+EBX]   
  112.     // Compare them.  
  113.     cmp  Al, Ah   
  114.     Jz   @Matches   
  115.     // If they don't match, we put the first char  
  116.     // of aFindString into Al again to continue  
  117.     // looking for the first character.  
  118.     Mov  Al, [ESI]   
  119.     Jmp  @NextChar   
  120.     @Matches:   
  121.     // If they match, we DEC EBX (point to  
  122.     // previous character to compare).  
  123.     Dec  EBX   
  124.     // If EBX <> 0 ("J"ump "N"ot "Z"ero), we  
  125.     // continue comparing strings.  
  126.     Jnz  @CompareNext   
  127.        
  128.     //add by Shengquanhu  
  129.     @EndOfMatch:   
  130.     //add end  
  131.   
  132.     // If EBX is zero, then we've successfully  
  133.     // compared each character; i.e. it's A MATCH!  
  134.     // Move the address of the *current*  
  135.     // character in EDI.  
  136.     // Note, we haven't altered EDI since  
  137.     // the first char was found.  
  138.     mov  EAX, EDI   
  139.     // This is an address, so subtract the  
  140.     // address of aSourceString[1] to get  
  141.     // an actual character position.  
  142.     sub  EAX, aSourceString   
  143.     // Inc EAX to make it 1-based,  
  144.     // rather than 0-based.  
  145.     inc  EAX   
  146.     // Put it into result.  
  147.     mov  Result, EAX   
  148.     // Finish this routine!  
  149.     jmp  @TheEnd   
  150.     @NextChar:   
  151.     //This is where I jump to when I want to continue searching for the first char  
  152. //acter of aFindString in aSearchString:  
  153.     // Point EDI (aFindString[X]) to  
  154.     // the next character.  
  155.     Inc  EDI   
  156.     // Dec ECX tells us that we've checked  
  157.     // another character, and that we're  
  158.     // fast running out of string to check!  
  159.     dec  ECX   
  160.     // If EBX <> 0, then continue scanning  
  161.     // for the first character.  
  162.   
  163.     //add by shengquanhu  
  164.     //if ah is chinese char,jump again  
  165.     jz   @Result0   
  166.     cmp  ah, $80  
  167.     jb   @ScaSB   
  168.     Inc  EDI   
  169.     Dec  ECX   
  170.     //add by shengquanhu end  
  171.   
  172.     jnz  @ScaSB   
  173.        
  174.     //add by shengquanhu  
  175.     @Result0:   
  176.     //add by shengquanhu end  
  177.   
  178.     // If EBX = 0, then move 0 into RESULT.  
  179.     mov  Result,0  
  180.     // Restore EBX, EDI, ESI for Delphi  
  181.     // to work correctly.  
  182.     // Note that they're POPped in the  
  183.     // opposite order they were PUSHed.  
  184.     @TheEnd:   
  185.     pop  EBX   
  186.     pop  EDI   
  187.     pop  ESI   
  188.   end;   
  189. end;   
  190.   
  191. //This routine is an identical copy of FastPOS except where commented! The ide  
  192. //a is that when grabbing bytes, it ANDs them with $df, effectively making the  
  193. //m lowercase before comparing. Maybe this would be quicker if aFindString was  
  194. // made lowercase in one fell swoop at the beginning of the function, saving a  
  195. //n AND instruction each time.  
  196. function FastPosNoCase(   
  197. const aSourceString, aFindString : String;   
  198. const aSourceLen, aFindLen, StartPos : integer  
  199. ) : integer;   
  200. var  
  201. SourceLen : integer;   
  202. begin  
  203.   SourceLen := aSourceLen;   
  204.   SourceLen := SourceLen - aFindLen;   
  205.   if (StartPos-1) > SourceLen then begin  
  206.     Result := 0;   
  207.     Exit;   
  208.   end;   
  209.   SourceLen := SourceLen - StartPos;   
  210.   SourceLen := SourceLen +2;   
  211.   asm  
  212.     push ESI   
  213.     push EDI   
  214.     push EBX   
  215.        
  216.     mov EDI, aSourceString   
  217.     add EDI, StartPos   
  218.     Dec EDI   
  219.     mov ESI, aFindString   
  220.     mov ECX, SourceLen   
  221.     Mov  Al, [ESI]   
  222.        
  223.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  224.     cmp Al, $7A  
  225.     ja @ScaSB   
  226.        
  227.     cmp Al, $61  
  228.     jb @ScaSB   
  229.     //end------------------------------------------  
  230.   
  231.     // Make Al uppercase.  
  232.     and  Al, $df  
  233.        
  234.     @ScaSB:   
  235.     Mov  Ah, [EDI]   
  236.        
  237.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  238.     cmp Ah, $7A  
  239.     ja @CompareChar   
  240.        
  241.     cmp Ah, $61  
  242.     jb @CompareChar   
  243.     //end------------------------------------------  
  244.   
  245.     // Make Ah uppercase.  
  246.     and  Ah, $df  
  247.        
  248.     @CompareChar:   
  249.     cmp  Ah,Al   
  250.     jne  @NextChar   
  251.     @CompareStrings:   
  252.     mov  EBX, aFindLen   
  253.     dec  EBX   
  254.        
  255.     //add by ShengQuanhu  
  256.     Jz   @EndOfMatch   
  257.     //add end  
  258.   
  259.     @CompareNext:   
  260.     mov  Al, [ESI+EBX]   
  261.     mov  Ah, [EDI+EBX]   
  262.        
  263.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  264.     cmp Ah, $7A  
  265.     ja @LowerAh   
  266.        
  267.     cmp Al, $61  
  268.     jb @LowerAh   
  269.     //end------------------------------------------  
  270.   
  271.     // Make Al and Ah uppercase.  
  272.     and  Al, $df  
  273.        
  274.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  275.     @LowerAh:   
  276.     cmp Ah, $7A  
  277.     ja @CompareChar2   
  278.        
  279.     cmp Ah, $61  
  280.     jb @CompareChar2   
  281.     //end------------------------------------------  
  282.   
  283.     and  Ah, $df  
  284.        
  285.     @CompareChar2:   
  286.     cmp  Al, Ah   
  287.     Jz   @Matches   
  288.     Mov  Al, [ESI]   
  289.        
  290.     //add by shengquanhu:just modified the lowercase 'a'..'z'  
  291.     cmp Al, $7A  
  292.     ja @NextChar   
  293.        
  294.     cmp Al, $61  
  295.     jb @NextChar   
  296.     //end------------------------------------------  
  297.   
  298.     // Make Al uppercase.  
  299.     and  Al, $df  
  300.     Jmp  @NextChar   
  301.     @Matches:   
  302.     Dec  EBX   
  303.     Jnz  @CompareNext   
  304.        
  305.     //add by Shengquanhu  
  306.     @EndOfMatch:   
  307.     //add end  
  308.   
  309.     mov  EAX, EDI   
  310.     sub  EAX, aSourceString   
  311.     inc  EAX   
  312.     mov  Result, EAX   
  313.     jmp  @TheEnd   
  314.     @NextChar:   
  315.     Inc  EDI   
  316.     dec  ECX   
  317.     //add by shengquanhu  
  318.     //if ah is chinese char,jump again  
  319.     jz   @Result0   
  320.     cmp  ah, $80  
  321.     jb   @ScaSB   
  322.     Inc  EDI   
  323.     Dec  ECX   
  324.     //add by shengquanhu end  
  325.     jnz  @ScaSB   
  326.     @Result0:   
  327.     mov  Result,0  
  328.     @TheEnd:   
  329.     pop  EBX   
  330.     pop  EDI   
  331.     pop  ESI   
  332.   end;   
  333. end;   
  334.   
  335. //My move isn’t as fast as MOVE when source and destination are both DWord al  
  336. //igned, but it’s certainly faster when they’re not. As we’re moving charac  
  337. //ters in a string, it isn’t very likely at all that both source and destinat  
  338. //ion are DWord aligned, so moving bytes avoids the cycle penalty of reading/w  
  339. //riting DWords across physical boundaries.  
  340. procedure MyMove(   
  341. const Source; var Dest; Count : Integer);   
  342. asm  
  343.   // Note: When this function is called,  
  344. // Delphi passes the parameters as follows:  
  345. // ECX = Count  
  346. // EAX = Const Source  
  347. // EDX = Var Dest  
  348.   // If there are no bytes to copy, just quit  
  349.   // altogether; there's no point pushing registers.  
  350.   cmp   ECX,0  
  351.   Je    @JustQuit   
  352.   // Preserve the critical Delphi registers.  
  353.   push  ESI   
  354.   push  EDI   
  355.   // Move Source into ESI (generally the  
  356.   // SOURCE register).  
  357.   // Move Dest into EDI (generally the DEST  
  358.   // register for string commands).  
  359.   // This might not actually be necessary,  
  360.   // as I'm not using MOVsb etc.  
  361.   // I might be able to just use EAX and EDX;  
  362.   // there could be a penalty for not using  
  363.   // ESI, EDI, but I doubt it.  
  364.   // This is another thing worth trying!  
  365.   mov   ESI, EAX   
  366.   mov   EDI, EDX   
  367.   // The following loop is the same as repNZ  
  368.   // MovSB, but oddly quicker!  
  369.     @Loop:   
  370.   // Get the source byte.  
  371.   Mov   AL, [ESI]   
  372.   // Point to next byte.  
  373.   Inc   ESI   
  374.   // Put it into the Dest.  
  375.   mov   [EDI], AL   
  376.   // Point dest to next position.  
  377.   Inc   EDI   
  378.   // Dec ECX to note how many we have left to copy.  
  379.   Dec   ECX   
  380.   // If ECX <> 0, then loop.  
  381.   Jnz   @Loop   
  382.   // Another optimization note.  
  383.   // Many people like to do this.  
  384.   // Mov AL, [ESI]  
  385.   // Mov [EDI], Al  
  386.   // Inc ESI  
  387.   // Inc ESI  
  388. //There’s a hidden problem here. I won’t go into too much detail, but the Pe  
  389. //ntium can continue processing instructions while it’s still working out the  
  390. // result of INC ESI or INC EDI. If, however, you use them while they’re stil  
  391. //l being calculated, the processor will stop until they’re calculated (a pen  
  392. //alty). Therefore, I alter ESI and EDI as far in advance as possible of using  
  393. // them.  
  394.   // Pop the critical Delphi registers  
  395.   // that we've altered.  
  396.   pop   EDI   
  397.   pop   ESI   
  398.   @JustQuit:   
  399. end;   
  400.   
  401. //Point 1: I pass VAR aSourceString rather than just aSourceString. This is be  
  402. //cause I’ll just be passed a pointer to the data rather than a 10M copy of t  
  403. //he data itself, which is much quicker!  
  404. function FastReplace(   
  405. var aSourceString : String;   
  406. const aFindString, aReplaceString : String;   
  407. CaseSensitive : Boolean = False) : String;   
  408. var  
  409. // Size already passed to SetLength,  
  410.   // the REAL size of RESULT.  
  411.   ActualResultLen,   
  412. // Position of aFindString is aSourceString.  
  413.   CurrentPos,   
  414. // Last position the aFindString was found at.  
  415.   LastPos,   
  416. // Bytes to copy (that is, lastpos to this pos).  
  417.   BytesToCopy,   
  418. // The "running" result length, not the actual one.  
  419.   ResultLen,   
  420. // Length of aFindString, to save  
  421.   // calling LENGTH repetitively.  
  422.   FindLen,   
  423. // Length of aReplaceString, for the same reason.  
  424.   ReplaceLen,   
  425. SourceLen         : Integer;   
  426. // This is where I explain the  
  427.   // TYPE TFastPosProc from earlier!  
  428.   FastPosProc       : TFastPosProc;   
  429. begin  
  430.   //As this function has the option of being case-insensitive, I’d need to call  
  431. // either FastPOS or FastPOSNoCase. The problem is that you’d have to do this  
  432. // within a loop. This is a bad idea, since the result never changes throughou  
  433. //t the whole operation–in which case we can determine it in advance, like so  
  434. //:  
  435.   if CaseSensitive then  
  436.   FastPosProc := FastPOS   
  437.   else  
  438.   FastPOSProc := FastPOSNoCase;   
  439.   // I don't think I actually need  
  440.   // this, but I don't really mind!  
  441.   Result := '';   
  442.   // Get the lengths of the strings.  
  443.   FindLen := Length(aFindString);   
  444.   ReplaceLen := Length(aReplaceString);   
  445.   SourceLen := Length(aSourceString);   
  446.   // If we already have room for the replacements,  
  447.   // then set the length of the result to  
  448.   // the length of the SourceString.  
  449.   if ReplaceLen <= FindLen then  
  450.   ActualResultLen := SourceLen   
  451.   else  
  452.   // If not, we need to calculate the  
  453.     // worst-case scenario.  
  454.     // That is, the Source consists ONLY of  
  455.     // aFindString, and we're going to replace  
  456.     // every one of them!  
  457.     ActualResultLen :=   
  458.   SourceLen +   
  459.   (SourceLen * ReplaceLen div FindLen) +   
  460.   ReplaceLen;   
  461.   // Set the length of Result; this  
  462.   // will assign the memory, etc.  
  463.   SetLength(Result,ActualResultLen);   
  464.   CurrentPos := 1;   
  465.   ResultLen := 0;   
  466.   LastPos := 1;   
  467.   //Again, I’m eliminating an IF statement in a loop by repeating code–this ap  
  468. //proach results in very slightly larger code, but if ever you can trade some  
  469. //memory in exchange for speed, go for it!  
  470.   if ReplaceLen > 0 then begin  
  471.     repeat  
  472.     // Get the position of the first (or next)  
  473.       // aFindString in aSourceString.  
  474.       // Note that there's no If CaseSensitive,  
  475.       // I just call FastPOSProc, which is pointing  
  476.       // to the correct pre-determined routine.  
  477.       CurrentPos :=   
  478.     FastPosProc(aSourceString, aFindString,   
  479.     SourceLen, FindLen, CurrentPos);   
  480.     // If 0, then we're finished.  
  481.       if CurrentPos = 0 then break;   
  482.     // Number of bytes to copy from the  
  483.       // source string is CurrentPos - lastPos,  
  484.       // i.e. " cat " in "the cat the".  
  485.       BytesToCopy := CurrentPos-LastPos;   
  486.     // Copy chars from aSourceString  
  487.       // to the end of Result.  
  488.       MyMove(aSourceString[LastPos],   
  489.     Result[ResultLen+1], BytesToCopy);   
  490.     // Copy chars 

小试牛刀破解别人的软件

作者: samool    分类: Develop    发布时间: 07-31 13:54    浏览次数:     4 条留言

 今天中午小试牛刀,一个朋友发了一个软件给我,让我看看能不能破解,我拿到软件后,运行,用SPY++拖到界面上看了一下,原来是Delphi写的软件,这时已经有20%的把握了,再用peid看一下加壳没有,发现乌龟没顶壳,哈哈哈,这下又增加20%的把握,接着DeDe进行反编译,得到汇编代码,输出工程文件,用Delphi打开该工程,找到注册窗口,直接进入注册函数,得到如下代码,简单看了一下,直接跳过注册判断,直接注册。用OllyICE修改汇编代码,保存文件,运行,注册,随便输入注册码,注册成功,搞定,收工。

XML/HTML代码
  1. procedure TFrmReg.Button2Click(Sender : TObject);   
  2. begin   
  3. (*   
  4. 005404E8   55                     push    ebp   
  5. 005404E9   8BEC                   mov     ebp, esp   
  6. 005404EB   81C410FEFFFF           add     esp, $FFFFFE10   
  7. 005404F1   53                     push    ebx   
  8. 005404F2   56                     push    esi   
  9. 005404F3   57                     push    edi   
  10. 005404F4   33C9                   xor     ecx, ecx   
  11. 005404F6   898D14FEFFFF           mov     [ebp+$FFFFFE14], ecx   
  12. 005404FC   898D10FEFFFF           mov     [ebp+$FFFFFE10], ecx   
  13. 00540502   898D18FEFFFF           mov     [ebp+$FFFFFE18], ecx   
  14. 00540508   898D1CFEFFFF           mov     [ebp+$FFFFFE1C], ecx   
  15. 0054050E   898D20FEFFFF           mov     [ebp+$FFFFFE20], ecx   
  16. 00540514   894DFC                 mov     [ebp-$04], ecx   
  17. 00540517   894DF8                 mov     [ebp-$08], ecx   
  18. 0054051A   894DF4                 mov     [ebp-$0C], ecx   
  19. 0054051D   894DF0                 mov     [ebp-$10], ecx   
  20. 00540520   8BF0                   mov     esi, eax   
  21. 00540522   33C0                   xor     eax, eax   
  22. 00540524   55                     push    ebp   
  23.   
  24. * Possible String Reference to: '閣<?胄_^[嬪]?   
  25. |   
  26. 00540525   68BC065400             push    $005406BC   
  27.   
  28. ***** TRY   
  29. |   
  30. 0054052A   64FF30                 push    dword ptr fs:[eax]   
  31. 0054052D   648920                 mov     fs:[eax], esp   
  32. 00540530   8D9520FEFFFF           lea     edx, [ebp+$FFFFFE20]   
  33. 00540536   8B8608030000           mov     eax, [esi+$0308]   
  34.   
  35. * Reference to: Controls.TControl.GetText(TControl):TCaption;   
  36. |   
  37. 0054053C   E847E0F1FF             call    0045E588   
  38. 00540541   83BD20FEFFFF00         cmp     dword ptr [ebp+$FFFFFE20], +$00   
  39. 00540548   750F                   jnz     00540559   
  40.   
  41. * Possible String Reference to: '请输入注册码'   
  42. |   
  43. 0054054A   B8D4065400             mov     eax, $005406D4   
  44.   
  45. * Reference to: Dialogs.ShowMessage(AnsiString);   
  46. |   
  47. 0054054F   E86C7EEFFF             call    004383C0   
  48. 00540554   E92D010000             jmp     00540686   
  49. 00540559   8D55FC                 lea     edx, [ebp-$04]   
  50. 0054055C   8B8608030000           mov     eax, [esi+$0308]   
  51.   
  52. * Reference to: Controls.TControl.GetText(TControl):TCaption;   
  53. |   
  54. 00540562   E821E0F1FF             call    0045E588   
  55. 00540567   8D951CFEFFFF           lea     edx, [ebp+$FFFFFE1C]   
  56. 0054056D   8BC6                   mov     eax, esi   
  57.   
  58. |   
  59. 0054056F   E8E0FEFFFF             call    00540454   
  60. 00540574   8B951CFEFFFF           mov     edx, [ebp+$FFFFFE1C]   
  61. 0054057A   8B45FC                 mov     eax, [ebp-$04]   
  62.   
  63. * Reference to: System.@LStrCmp;   
  64. |   
  65. 0054057D   E84249ECFF             call    00404EC4   
  66. 00540582   0F85F4000000           jnz     0054067C   
  67. 00540588   8D55F8                 lea     edx, [ebp-$08]   
  68. 0054058B   33C0                   xor     eax, eax   
  69.   
  70. * Reference to : TClientSocket._PROC_0050CC28()   
  71. |   
  72. 0054058D   E896C6FCFF             call    0050CC28   
  73. 00540592   8B45F8                 mov     eax, [ebp-$08]   
  74.   
  75. * Reference to: System.@LStrLen(String):Integer;   
  76. |           or: System.@DynArrayLength;   
  77. |           or: System.DynArraySize(Pointer):Integer;   
  78. |           or: Variants.DynArraySize(Pointer):Integer;   
  79. |   
  80. 00540595   E8DE47ECFF             call    00404D78   
  81. 0054059A   8BD8                   mov     ebx, eax   
  82. 0054059C   85DB                   test    ebx, ebx   
  83. 0054059E   7E32                   jle     005405D2   
  84. 005405A0   BF01000000             mov     edi, $00000001   
  85. 005405A5   8B45F8                 mov     eax, [ebp-$08]   
  86. 005405A8   0FB64438FF             movzx   eax, byte ptr [eax+edi-$01]   
  87. 005405AD   83C018                 add     eax, +$18   
  88. 005405B0   8D8D18FEFFFF           lea     ecx, [ebp+$FFFFFE18]   
  89. 005405B6   BA02000000             mov     edx, $00000002   
  90.   
  91. * Reference to: SysUtils.IntToHex(Integer;Integer):AnsiString;overload;   
  92. |   
  93. 005405BB   E83C94ECFF             call    004099FC   
  94. 005405C0   8B9518FEFFFF           mov     edx, [ebp+$FFFFFE18]   
  95. 005405C6   8D45F4                 lea     eax, [ebp-$0C]   
  96.   
  97. * Reference to: System.@LStrCat;   
  98. |   
  99. 005405C9   E8B247ECFF             call    00404D80   
  100. 005405CE   47                     inc     edi   
  101. 005405CF   4B                     dec     ebx   
  102. 005405D0   75D3                   jnz     005405A5
  103. 将这句修改为   jmp 005405D2 或者删除这条语句即完成破解.   
  104. 005405D2   8D9510FEFFFF           lea     edx, [ebp+$FFFFFE10]   
  105. 005405D8   33C0                   xor     eax, eax   
  106.   
  107. * Reference to: System.ParamStr(Integer):String;   
  108. |   
  109. 005405DA   E86925ECFF             call    00402B48   
  110. 005405DF   8B8510FEFFFF           mov     eax, [ebp+$FFFFFE10]   
  111. 005405E5   8D9514FEFFFF           lea     edx, [ebp+$FFFFFE14]   
  112.   
  113. * Reference to: SysUtils.ExtractFilePath(AnsiString):AnsiString;   
  114. |   
  115. 005405EB   E82499ECFF             call    00409F14   
  116. 005405F0   8B9514FEFFFF           mov     edx, [ebp+$FFFFFE14]   
  117. 005405F6   8D45F0                 lea     eax, [ebp-$10]   
  118.   
  119. * Possible String Reference to: 'smsinfo'   
  120. |   
  121. 005405F9   B9EC065400             mov     ecx, $005406EC   
  122.   
  123. * Reference to: System.@LStrCat3;   
  124. |   
  125. 005405FE   E8C147ECFF             call    00404DC4   
  126. 00540603   8B45F0                 mov     eax, [ebp-$10]   
  127.   
  128. * Reference to: SysUtils.FileCreate(AnsiString):Integer;overload;   
  129. |   
  130. 00540606   E8B996ECFF             call    00409CC4   
  131.   
  132. * Reference to: InGlobal.Sleep(Cardinal);   
  133. |           or: SConnect.CloseRegKey(HKEY);   
  134. |           or: System.EndThread(Integer);   
  135. |           or: SysUtils.FileClose(Integer);   
  136. |           or: SysUtils.StrNextChar(PChar):PChar;   
  137. |           or: Windows.LockSegment(System.THandle);   
  138. |   
  139. 0054060B   E87097ECFF             call    00409D80   
  140. 00540610   8B55F0                 mov     edx, [ebp-$10]   
  141. 00540613   8D8524FEFFFF           lea     eax, [ebp+$FFFFFE24]   
  142.   
  143. * Reference to: System.@Assign(TTextRec;TTextRec;String):Integer;   
  144. |   
  145. 00540619   E80E29ECFF             call    00402F2C   
  146. 0054061E   8D8524FEFFFF           lea     eax, [ebp+$FFFFFE24]   
  147.   
  148. * Reference to: System.@Append(TTextRec;TTextRec):Integer;   
  149. |   
  150. 00540624   E89F26ECFF             call    00402CC8   
  151.   
  152. |   
  153. 00540629   E85A23ECFF             call    00402988   
  154. 0054062E   8B55F4                 mov     edx, [ebp-$0C]   
  155. 00540631   8D8524FEFFFF           lea     eax, [ebp+$FFFFFE24]   
  156.   
  157. * Reference to: Classes.TStream.WriteComponent(TStream;TComponent);   
  158. |           or: Classes.TWriter.WriteRootComponent(TWriter;TComponent);   
  159. |           or: DB.TWideStringField.GetAsWideString(TWideStringField):WideString;   
  160. |           or: DB.TDateTimeField.GetValue(TDateTimeField;TDateTime;TDateTime):Boolean;   
  161. |           or: DB.TSQLTimeStampField.GetValue(TSQLTimeStampField;TSQLTimeStamp;TSQLTimeStamp):Boolean;   
  162. |           or: DB.TSQLTimeStampField.SetAsSQLTimeStamp(TSQLTimeStampField;TSQLTimeStamp;TSQLTimeStamp);   
  163. |   
  164. 00540637   E8584BECFF             call    00405194   
  165.   
  166. * Reference to: System.@WriteLn(TTextRec;TTextRec):Pointer;   
  167. |   
  168. 0054063C   E89B2FECFF             call    004035DC   
  169.   
  170. |   
  171. 00540641   E84223ECFF             call    00402988   
  172. 00540646   8D8524FEFFFF           lea     eax, [ebp+$FFFFFE24]   
  173.   
  174. * Reference to: System.Flush(Text;Text):Integer;   
  175. |   
  176. 0054064C   E88B29ECFF             call    00402FDC   
  177.   
  178. |   
  179. 00540651   E83223ECFF             call    00402988   
  180. 00540656   8D8524FEFFFF           lea     eax, [ebp+$FFFFFE24]   
  181.   
  182. * Reference to: System.@Close(TTextRec;TTextRec):Integer;   
  183. |   
  184. 0054065C   E89329ECFF             call    00402FF4   
  185.   
  186. |   
  187. 00540661   E82223ECFF             call    00402988   
  188.   
  189. * Possible String Reference to: '注册成功,请重启软件。'   
  190. |   
  191. 00540666   B8FC065400             mov     eax, $005406FC   
  192.   
  193. * Reference to: Dialogs.ShowMessage(AnsiString);   
  194. |   
  195. 0054066B   E8507DEFFF             call    004383C0   
  196. 00540670   C7864C02000001000000   mov     dword ptr [esi+$024C], $00000001   
  197. 0054067A   EB0A                   jmp     00540686   
  198.   
  199. * Possible String Reference to: '注册失败,注册码不正确.'   
  200. |   
  201. 0054067C   B81C075400             mov     eax, $0054071C   
  202.   
  203. * Reference to: Dialogs.ShowMessage(AnsiString);   
  204. |   
  205. 00540681   E83A7DEFFF             call    004383C0   
  206. 00540686   33C0                   xor     eax, eax   
  207. 00540688   5A                     pop     edx   
  208. 00540689   59                     pop     ecx   
  209. 0054068A   59                     pop     ecx   
  210. 0054068B   648910                 mov     fs:[eax], edx   
  211.   
  212. ****** FINALLY   
  213. |   
  214.   
  215. * Possible String Reference to: '_^[嬪]?   
  216. |   
  217. 0054068E   68C3065400             push    $005406C3   
  218. 00540693   8D8510FEFFFF           lea     eax, [ebp+$FFFFFE10]   
  219. 00540699   BA04000000             mov     edx, $00000004   
  220.   
  221. * Reference to: System.@LStrArrayClr(void;void;Integer);   
  222. |   
  223. 0054069E   E83944ECFF             call    00404ADC   
  224. 005406A3   8D8520FEFFFF           lea     eax, [ebp+$FFFFFE20]   
  225.   
  226. * Reference to: System.@LStrClr(void;void);   
  227. |   
  228. 005406A9   E80A44ECFF             call    00404AB8   
  229. 005406AE   8D45F0                 lea     eax, [ebp-$10]   
  230. 005406B1   BA04000000             mov     edx, $00000004   
  231.   
  232. * Reference to: System.@LStrArrayClr(void;void;Integer);   
  233. |   
  234. 005406B6   E82144ECFF             call    00404ADC   
  235. 005406BB   C3                     ret   
  236.   
  237.   
  238. * Reference to: System.@HandleFinally;   
  239. |   
  240. 005406BC   E9773CECFF             jmp     00404338   
  241. 005406C1   EBD0                   jmp     00540693   
  242.   
  243. ****** END   
  244. |   
  245. 005406C3   5F                     pop     edi   
  246. 005406C4   5E                     pop     esi   
  247. 005406C5   5B                     pop     ebx   
  248. 005406C6   8BE5                   mov     esp, ebp   
  249. 005406C8   5D                     pop     ebp   
  250. 005406C9   C3                     ret   
  251.   
  252. *)   
  253. end;  

8088 汇编速查手册

作者: samool    分类: Develop    发布时间: 06-28 13:58    浏览次数:     无留言

软件的破解技术与保护技术这两者之间本身就是矛与盾的关系,它们是在互相斗争中发展进化的。这种技术上的较量归根到底是一种利益的冲突。软件开发者为了维护自身的商业利益,不断地寻找各种有效的技术来保护自身的软件版权,以增加其保护强度,推迟软件被破解的时间;而破解者则或受盗版所带来的高额利润的驱使,或出于纯粹的个人兴趣,而不断制作新的破解工具并针对新出现的保护方式进行跟踪分析以找到相应的破解方法。从理论上说,几乎没有破解不了的保护。对软件的保护仅仅靠技术是不够的,而这最终要靠人们的知识产权意识和法制观念的进步以及生活水平的提高。但是如果一种保护技术的强度强到足以让破解者在软件的生命周期内无法将其完全破解,这种保护技术就可以说是非常成功的。软件保护方式的设计应在一开始就作为软件开发的一部分来考虑,列入开发计划和开发成本中,并在保护强度、成本、易用性之间进行折衷考虑,选择一个合适的平衡点。

  在桌面操作系统中,微软的产品自然是独霸天下,一般个人用户接触得最多,研究得自然也更多一些。在DOS时代之前就有些比较好的软件保护技术,而在DOS中使用得最多的恐怕要算软盘指纹防拷贝技术了。由于DOS操作系统的脆弱性,在其中运行的普通应用程序几乎可以访问系统中的任何资源,如直接访问任何物理内存、直接读写任何磁盘扇区、直接读写任何I/O端口等,这给软件保护者提供了极大的自由度,使其可以设计出一些至今仍为人称道的保护技术;自Windows 95开始(特别是WinNT和Windows 2000这样严格意义上的多用户操作系统),操作系统利用硬件特性增强了对自身的保护,将自己运行在Ring 0特权级中,而普通应用程序则运行在最低的特权级Ring 3中,限制了应用程序所能访问的资源,使得软件保护技术在一定程度上受到一些限制。开发者要想突破Ring 3的限制,一般需要编写驱动程序,如读写并口上的软件狗的驱动程序等,这增加了开发难度和周期,自然也增加了成本。同时由于Win32程序内存寻址使用的是相对来说比较简单的平坦寻址模式(相应地其采用的PE文件格式也比以前的16-bit的EXE程序的格式要容易处理一些),并且Win32程序大量调用系统提供的API,而Win32平台上的调试器如SoftICE等恰好有针对API设断点的强大功能,这些都给跟踪破解带来了一定的方便。

一、数据传输指令
───────────────────────────────────────
    它们在存贮器和寄存器、寄存器和输入输出端口之间传送数据.
    1. 通用数据传送指令.
        MOV    传送字或字节.
        MOVSX  先符号扩展,再传送.
        MOVZX  先零扩展,再传送.
        PUSH    把字压入堆栈.
        POP    把字弹出堆栈.
        PUSHA  把AX,CX,DX,BX,SP,BP,SI,DI依次压入堆栈.
        POPA    把DI,SI,BP,SP,BX,DX,CX,AX依次弹出堆栈.
        PUSHAD  把EAX,ECX,EDX,EBX,ESP,EBP,ESI,EDI依次压入堆栈.
        POPAD  把EDI,ESI,EBP,ESP,EBX,EDX,ECX,EAX依次弹出堆栈.
        BSWAP  交换32位寄存器里字节的顺序
        XCHG    交换字或字节.( 至少有一个操作数为寄存器,段寄存器不可作为操作数)
        CMPXCHG 比较并交换操作数.( 第二个操作数必须为累加器AL/AX/EAX )
        XADD    先交换再累加.( 结果在第一个操作数里 )
        XLAT    字节查表转换.
                ── BX 指向一张 256 字节的表的起点, AL 为表的索引值 (0-255,即
                0-FFH); 返回 AL 为查表结果. ( [BX+AL]->AL )
    2. 输入输出端口传送指令.
        IN      I/O端口输入. ( 语法: IN 累加器, {端口号│DX} )
        OUT    I/O端口输出. ( 语法: OUT {端口号│DX},累加器 )
          输入输出端口由立即方式指定时, 其范围是 0-255; 由寄存器 DX 指定时,
          其范围是 0-65535.
    3. 目的地址传送指令.
        LEA    装入有效地址.
          例: LEA DX,string  ;把偏移地址存到DX.
        LDS    传送目标指针,把指针内容装入DS.
          例: LDS SI,string  ;把段地址:偏移地址存到DS:SI.
        LES    传送目标指针,把指针内容装入ES.
          例: LES DI,string  ;把段地址:偏移地址存到ES:DI.
        LFS    传送目标指针,把指针内容装入FS.
          例: LFS DI,string  ;把段地址:偏移地址存到FS:DI.
        LGS    传送目标指针,把指针内容装入GS.
          例: LGS DI,string  ;把段地址:偏移地址存到GS:DI.
        LSS    传送目标指针,把指针内容装入SS.
          例: LSS DI,string  ;把段地址:偏移地址存到SS:DI.
    4. 标志传送指令.
        LAHF    标志寄存器传送,把标志装入AH.
        SAHF    标志寄存器传送,把AH内容装入标志寄存器.
        PUSHF  标志入栈.
        POPF    标志出栈.
        PUSHD  32位标志入栈.
        POPD    32位标志出栈.

二、算术运算指令
───────────────────────────────────────
          ADD    加法.
        ADC    带进位加法.
        INC    加 1.
        AAA    加法的ASCII码调整.
        DAA    加法的十进制调整.
        SUB    减法.
        SBB    带借位减法.
        DEC    减 1.
        NEC    求反(以 0 减之).
        CMP    比较.(两操作数作减法,仅修改标志位,不回送结果).
        AAS    减法的ASCII码调整.
        DAS    减法的十进制调整.
        MUL    无符号乘法.
        IMUL    整数乘法.
          以上两条,结果回送AH和AL(字节运算),或DX和AX(字运算),
        AAM    乘法的ASCII码调整.
        DIV    无符号除法.
        IDIV    整数除法.
          以上两条,结果回送:
              商回送AL,余数回送AH, (字节运算);
          或  商回送AX,余数回送DX, (字运算).
        AAD    除法的ASCII码调整.
        CBW    字节转换为字. (把AL中字节的符号扩展到AH中去)
        CWD    字转换为双字. (把AX中的字的符号扩展到DX中去)
        CWDE    字转换为双字. (把AX中的字符号扩展到EAX中去)
        CDQ    双字扩展.    (把EAX中的字的符号扩展到EDX中去)

三、逻辑运算指令
───────────────────────────────────────
          AND    与运算.
        OR      或运算.
        XOR    异或运算.
        NOT    取反.
        TEST    测试.(两操作数作与运算,仅修改标志位,不回送结果).
        SHL    逻辑左移.
        SAL    算术左移.(=SHL)
        SHR    逻辑右移.
        SAR    算术右移.(=SHR)
        ROL    循环左移.
        ROR    循环右移.
        RCL    通过进位的循环左移.
        RCR    通过进位的循环右移.
          以上八种移位指令,其移位次数可达255次.
              移位一次时, 可直接用操作码.  如 SHL AX,1.
              移位>1次时, 则由寄存器CL给出移位次数.
                如  MOV CL,04
                    SHL AX,CL

四、串指令
───────────────────────────────────────
             DS:SI  源串段寄存器  :源串变址.
            ES:DI  目标串段寄存器:目标串变址.
            CX      重复次数计数器.
            AL/AX  扫描值.
            D标志  0表示重复操作中SI和DI应自动增量; 1表示应自动减量.
            Z标志  用来控制扫描或比较操作的结束.
        MOVS    串传送.
            ( MOVSB  传送字符.    MOVSW  传送字.    MOVSD  传送双字. )
        CMPS    串比较.
            ( CMPSB  比较字符.    CMPSW  比较字. )
        SCAS    串扫描.
            把AL或AX的内容与目标串作比较,比较结果反映在标志位.
        LODS    装入串.
            把源串中的元素(字或字节)逐一装入AL或AX中.
            ( LODSB  传送字符.    LODSW  传送字.    LODSD  传送双字. )
        STOS    保存串.
            是LODS的逆过程.
        REP            当CX/ECX<>0时重复.
        REPE/REPZ      当ZF=1或比较结果相等,且CX/ECX<>0时重复.
        REPNE/REPNZ    当ZF=0或比较结果不相等,且CX/ECX<>0时重复.
        REPC          当CF=1且CX/ECX<>0时重复.
        REPNC          当CF=0且CX/ECX<>0时重复.

五、程序转移指令
───────────────────────────────────────
     1>无条件转移指令 (长转移)
        JMP    无条件转移指令
        CALL    过程调用
        RET/RETF过程返回.
    2>条件转移指令 (短转移,-128到+127的距离内)
        ( 当且仅当(SF XOR OF)=1时,OP1<OP2 )
        JA/JNBE 不小于或不等于时转移.
        JAE/JNB 大于或等于转移.
        JB/JNAE 小于转移.
        JBE/JNA 小于或等于转移.
          以上四条,测试无符号整数运算的结果(标志C和Z).
        JG/JNLE 大于转移.
        JGE/JNL 大于或等于转移.
        JL/JNGE 小于转移.
        JLE/JNG 小于或等于转移.
          以上四条,测试带符号整数运算的结果(标志S,O和Z).
        JE/JZ  等于转移.
        JNE/JNZ 不等于时转移.
        JC      有进位时转移.
        JNC    无进位时转移.
        JNO    不溢出时转移.
        JNP/JPO 奇偶性为奇数时转移.
        JNS    符号位为 "0" 时转移.
        JO      溢出转移.
        JP/JPE  奇偶性为偶数时转移.
        JS      符号位为 "1" 时转移.
    3>循环控制指令(短转移)
        LOOP            CX不为零时循环.
        LOOPE/LOOPZ    CX不为零且标志Z=1时循环.
        LOOPNE/LOOPNZ  CX不为零且标志Z=0时循环.
        JCXZ            CX为零时转移.
        JECXZ          ECX为零时转移.
    4>中断指令
        INT    中断指令
        INTO    溢出中断
        IRET    中断返回
    5>处理器控制指令
        HLT    处理器暂停, 直到出现中断或复位信号才继续.
        WAIT    当芯片引线TEST为高电平时使CPU进入等待状态.
        ESC    转换到外处理器.
        LOCK    封锁总线.
        NOP    空操作.
        STC    置进位标志位.
        CLC    清进位标志位.
        CMC    进位标志取反.
        STD    置方向标志位.
        CLD    清方向标志位.
        STI    置中断允许位.
        CLI    清中断允许位.

六、伪指令
───────────────────────────────────────
          DW      定义字(2字节).
        PROC    定义过程.
        ENDP    过程结束.
        SEGMENT 定义段.
        ASSUME  建立段寄存器寻址.
        ENDS    段结束.
        END    程序结束.