汇编过程

汇编系列文章:
https://www.jianshu.com/nb/29822876

stack的使用

  • 使用SS和ESP进行寻址,每一次push和pop都是对栈顶操作,栈顶在低地址,同时是指向顶部存储的内容。如PUSH后,会先移动后再push进去东西。
  • PUSH、POP都是只有一个操作数,因为只有一个栈。
  • 注意由于后进先出,PUSH和POP的操作对象顺序相反。
  • PUSHFD、POPFD:用于EFLAGS寄存器
  • PUSHAD、POPAD:所有32位通用寄存器,顺序:EAX、ECX、EDX、EBX、ESP、EBP、ESI、EDI
  • PUSHA、POPA用于16位

过程procedure定义:

  • 输入输出可以使用寄存器
  • 使用PROC和ENDP定义,里面使用ret,直接用call调用
  • ret用来进行EIP赋值,继续执行调用前的下一条的指令
  • USES操作符:在PROC之后加上USES esi ecx可以让编译器自动在PROC内部加上两个寄存器的push和pop操作
  • 用机器码编译出来的procedures
  • 自己生成lib时需要先把每一个asm编译成obj然后把他们添加在lib文件中
  • 使用include来声明函数原型

64位

  • 用PROTO,使用的栈指针是RSP,每次减8;
  • 前四个参数必须放在RCX、RDX、R8、R9里
  • 必须在stack中预留至少32bytes,子过程调用时栈指针在16-byte边界上对齐

Stack Frames栈帧

  • 也叫做activation record,用于记录调用过程传递的参数、返回地址、保存的寄存器及局部变量的值
  • 调用的时候参数入栈,调用过程,被调用的过程将EBP推入栈中,在函数调用时把原来的栈顶esp变成被调用函数的栈底ebp。如果需要局部变量,会在esp上减小常数以存放。
    • 子过程中,push ebpmov ebp, esp,最后pop ebp
  • stack parameters:比用寄存器传递参数更方便,直接把参数倒序入栈。
    • 值传递时,为了保证栈对齐,保护模式中只使用32位值作为参数,返回值放在EAX里面,如果被调用过程没有把参数清除就自己清除这个栈。
    • 引用传递时,传递offset
  • 栈传参数&清除
    • 在call之前push,如push两个参数,在子过程中可以使用[esp + 8]、[esp + 12]获取参数
    • 最后可以由子过程ret 8来清除参数(相当于让esp移动),或者在caller中进行(让esp移动)
  • 局部变量
    • 如函数中,int x = 10,就在改变ebp、esp之后sub esp 4,并且把局部变量存进去。
    • 高到低:参数、return地址、ebp、局部变量
  • ENTER:为被调用过程保留栈帧(push ebpmov ebp espsub esp, n
  • LEAVE:mov esp ebppop ebp
  • LOCAL伪指令:local var1:BYTE, var2:DWORD这样声明
  • LEA:获取栈帧中内容或者局部变量的offset,而OFFSET只能获取常offsets

INVOKE

  • INVOKE后跟PROC、参数列表
  • 64位不可用

PROC

  • label PROC [attributes] [USES list], params

PROTO

  • label PROTO paramslist

参数类型

  • 输入参数、输出参数、输入输出参数

汇编hello-world

汇编系列文章:
https://www.jianshu.com/nb/29822876

来源:

https://www.cnblogs.com/del/archive/2010/04/03/1703689.html
https://blog.csdn.net/u011770174/article/details/77914375
https://bbs.csdn.net/topics/390495916
https://www.zhihu.com/question/22095837
https://www.cnblogs.com/yshblog/p/3379663.html

首先放上一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
.386
.model flat, stdcall
option casemap:none

include masm32.inc
include kernel32.inc
includelib masm32.lib
includelib kernel32.lib

.data
szText db "Hello World!", 0

.code
start:
invoke StdOut, addr szText
ret
end start

对程序中内容进行逐一解释:

  • .386表明使用的指令集
  • .model flat, stdcall 表示平坦内存模式,API调用时右边的参数先入栈
  • option casemap:none 表示大小写敏感
  • include的.inc文件与.h类似,若没有就需要进行类似StdOut PROTO STDCALL:DWORD的声明
  • includelib指定使用的库文件
  • .data与.code分别指定数据与代码
  • invoke伪指令用于函数调用
  • ret 是用于子程序返回的指令, 这里用它代替 ExitProcess(在没有生成 Win32 窗口时是可以的)
  • end start让汇编程序结束汇编并且指明程序开始地址,汇编程序看到这个后就会结束并且找到start标签作为起始位置

但是这样输出变量(内存地址上的值)就不方便,可以使用printf函数(这里是crt_printf),如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.386
.model flat, stdcall

include msvcrt.inc
includelib msvcrt.lib

.data
szFmt db 'Hello world! No %d', 0

.code
start:
mov eax, 11
invoke crt_printf, addr szFmt, eax
ret
end start

汇编指令

汇编系列文章:
https://www.jianshu.com/nb/29822876

寄存器

通用寄存器:

  • EAX~EDX,EBP、ESP、ESI、EDI,EFLAGS、EIP

段寄存器:

  • CS、SS、DS、ES、FS、GS

寄存器E开头表示32位,H结尾表示高8位,L结尾表示低8位

64位下,原有的32位E开头的变为R开头就是64位,添加R8-R15(R8对应的32位为R8D,16位R8W,8位R8B)

一些特殊用途寄存器:

  • EAX:accumulator;
  • ECX:loop counter;
  • ESP:stack pointer;
  • ESI,EDI:index register;
  • EBP:extended frame pointer;
  • CS:code;
  • DS:data;
  • SS:stack;
  • ES,FS,GS:additional;
  • EIP:instruction pointer;
  • EFLAGS:flags
    • carry:无符号溢出
    • overflow:有符号溢出
    • sign:负数
    • zero
    • auxiliary carry:半进位(考虑第4位)
    • parity:奇偶校验(1的个数是偶数)

基本符号

数字

  • +-
  • 进制后缀:h(若以字母开头则需要加个0)、d、b、o

数字表达式

  • 优先级:括号>正负>乘除>MOD>加减

字符&字符串 常数

  • 单引号双引号都行,一个ASCII一个byte,字符串不会自动加结束符号

保留字 标记符

  • 不区分大小写
  • ASCII 1-247,首字母为字母_@?$

伪指令directives

  • 简化代码,编译器相关,大小写不敏感

指令instructions

  • [Label: ] Mnemonic (Operand) [;comment]
  • Label:data(不带冒号) 和 code
  • 指令注记符&操作数(常量、常量表达式、内存(data label)和寄存器)
    • 常量和常量表达式是立即数
  • 注释:单行用分号;多行用COMMENT+自己的符号,最后再用这个符号结尾(注意检查中间的代码有没有这个符号)
  • 不带操作数的如stc(set carry flag),带一个操作数的如inc

汇编执行

编译-链接-执行

  • listing file可以查看自己的程序如何被编译

数据定义

  • 内部数据类型(INT)
    • 8bit:BYTE、SBYTE
    • 16bit:WORD、SWORD
    • 32bit:DWORD、SDWORD
    • 64bit:QWORD
    • 80bit:TBYTE
  • 内部数据类型(REAL)
    • 4byte(32bit):REAL4
    • 8byte:REAL8
    • 10byte:REAL10
  • 语句
    • [name] directive initializer[,initializer…]
    • 如:value1 BYTE 10
    • 声明内存中数据
    • 可以用?表示未初始化,可以减小生成的exe文件大小
    • 数组直接用逗号分开
  • 字符串:
    • 直接用BYTE,如str2 BYTE 'Error: halting program',0
    • 一行结尾处写逗号,下一行可以继续,如:
      1
      2
      menu BYTE "Checking Account",0dh,0ah,0dh,0ah,
      "1. Create a new account",0dh,0ah,
      其中,0dh,0ah可以换行,前者表示 carriage return回车(及到行首),后者表示到下一行
  • 重复 DUP:
    • var BYTE 10, 3 DUP(1), 2 其实表示var 10, 1, 1, 1, 2,也就是先写重复个数,再写要重复什么
  • WORD可以储存16位整数,也可以储存两个字符
  • 小端序:小端存低位,但是像字符串和数组什么的都是按照低位到高位排列的,比如数组12h, 34h是从低位到高位,但是12h内部是1为高位,如果用小端序存储得到的实际存储顺序是34h, 12h(高位到低位)

符号常量

  • 等号伪指令:32bit整数(表达式或者常数),编译器会处理
  • 当前位置的计数器$:如获取数组长度,可以
    1
    2
    list BYTE 10,20,30,40
    ListSize = ($ - list)
    • 需要注意:这样计算得到的是byte个数,如果是WORD数组的长度应该除以2
  • EQU伪指令:
    • 可能是数或者字符串表达式,不可以重定义,如:
      1
      2
      PI EQU <3.1416>
      pressKey EQU <"Press any key to continue...",0>
  • TEXTEQU伪指令:
    • 可以重定义,是宏定义

64位:

  • MASM中,支持64位变成,但是不支持INVOKE、ADDR、.model、.386、.stack等

数据转移

直接内存操作:

  • .data中声明的内存地址的label,在代码中可以直接被解引用(dereferenced),可以在外层加上中括号
    MOV:
  • 先destination再source
  • 不能两个内存地址
  • CS、EIP、IP不能为destination
  • DS、SS、ES等也不能直接被立即数mov
  • 注意:64位中,如果mov一个32位内存地址的到64位,高位清除,8或者16位不会;但是如果mov一个数,都会直接清除高位
    MOVZX:
  • 可以扩展高位为0后再赋值
    MOVSX:
  • 可以扩展高位为符号位后赋值
    XCHG:
  • 交换,至少一个寄存器,不能有立即数
    Direct-Offset Operands:
  • data label的内容其实是内存地址,所以加上常数相当于进行偏移

加减法:

  • INC、DEC:1个操作数
  • ADD、SUB:先destination,destination <- destination +/- source
  • NEG
  • FLAGS:ZF(zero)、SF(sign)、CF(Carry)、OF(Overflow)

数据相关操作

  • OFFSET:考虑从数据段开始的偏移
  • PTR:可以转换数据类型,只是一个指针,如WORD PTR myDouble就是取myDouble对应内存区域的一个WORD大小的数据
  • TYPE:用来获得数据类型的大小(byte数)
  • LENGTHOF:计数元素的个数(相当于数组元素的个数)
  • SIZEOF:相当于LENGTHOF乘TYPE
  • 注:一个声明包含多行的逗号分隔的,但不包含换行后再次写类型的
  • LABEL可以用来对同一个内存地址进行不同的解释,在一个声明intList BYTE 00h,10h,00h,20h前加上wordList LABEL WORD可以让这两个label指向同一个内存地址,但是对数据类型的解读不同。

间接寻址indirect addressing

  • 直接把地址赋值给一个寄存器,用[]就可以取寻找这个寄存器对应的地址上的内容,相当于在指针前面用*
  • 一定要注意这里用的是寄存器,不是label
  • 地址赋值时,可以用OFFSET。但是要注意解引用时使用的类型是否与需要的类型一致(如一个WORD的地址,如果赋给eax会有问题,会将之后的地址上一部分也用到,如tax WORD 0 1,因为小端序,如果mov eax, OFFSET tax并且mov ebx, [eax]就会得到ebx上为65536)。
  • 要注意,对于所有的内存地址,增加1只是加1BYTE,如果它本身是WORD或者更大的单位,需要增加2或者更多,或者直接增加TYPE label名称
  • 此时如果要对内存地址上的内容增加,不能直接写INC [eax],而要指明上面的类型。如tax WORD 65535 1mov eax, OFFSET tax,进行一些操作后mov ebx, [eax],若上述操作是inc DWORD PTR [eax]则得到131072,否则inc WORD PTR [eax]则得到65536。

变址操作数Indexed Operands

对于数组来说,可以用[label+reg]或者label[reg]的形式访问label代表的数组中的第若干个值,但是不能不在label+reg外面加括号,也不能把reg换成某个label。
其实本质上,label就是一个内存地址,不能让内存地址相加。
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
.386
.model flat, stdcall

include msvcrt.inc
includelib msvcrt.lib

.data
szFmt db 'Now: %d', 0dh, 0ah, 0
tax DWORD 1, 2

.code
start:
mov ebx, tax
invoke crt_printf, addr szFmt, ebx ;1
mov ebx, [tax]
invoke crt_printf, addr szFmt, ebx ;1
mov ebx, tax + 4
invoke crt_printf, addr szFmt, ebx ;2 因为DWORD所以加4
mov ebx, [tax + 4]
invoke crt_printf, addr szFmt, ebx ;2
mov ebx, tax[4]
invoke crt_printf, addr szFmt, ebx ;2
mov eax, 4
;mov ebx, tax + eax
;invoke crt_printf, addr szFmt, ebx ; 报错
mov ebx, [tax + eax]
invoke crt_printf, addr szFmt, ebx ;2
mov eax, 4 ; invoke这个函数时会改掉eax,所以需要再次mov
mov ebx, tax[eax]
invoke crt_printf, addr szFmt, ebx ;2
ret
end start

索引比例Index Scaling

为了避免上面索引的过程还要判断加几,可以把加的数改成TYPE label名

指针

可以在声明data时直接用ptrW DWORD arrayW或者ptrW DWORD OFFSET arrayW把arrayW的内存地址赋值给ptrW的内存地址

JMP

JMP 代码label
直接跳转到对应的代码标签处继续执行。

LOOP

LOOP 代码label
在循环中,每一次都会将ECX减1,之后如果不为0就跳转到代码label处。
注意:如果在循环里面使用invoke,需要首先保存ecx的值,之后再还原
如:

1
2
3
4
5
6
7
	mov ecx, 5
l1:
mov eax, ecx
pushad
invoke crt_printf, addr szFmt, eax
popad
loop l1

如果双层或者多层循环,需要先把外层循环的ecx保留下来,之后恢复。
64位中,使用RCX进行循环计数。

shift&rotate

  • 右移时:逻辑移位Logical Shift符号位为0,算术移位Arithmetic Shift符号位为原来的符号位
  • SHL、SAL指令为左移,SHR为逻辑右移(符号位为0),SAR为算术右移(符号位保持),丢掉的那一位放在Carry Flag里面
  • ROL为向左循环移位,原来的最高位在被移动到最低位的同时也移动到CF里面;ROR向右,CF存储原来的最低位
  • RCL为向左循环,把原来的CF拷到最低位,原来的最高位放入CF中;RCR同理,最低位->CF,CF->最高位
  • SHLD(Shift left double),操作数是dest,source,n,相当于用source接在dest后面做左移,移出去的放在CF,不改变source;SHRD类似,低位移出去的放在CF,相当于source接在前面

乘除

  • MUL是unsigned,把操作数乘AL/AX/EAX/RAX后放在AX/DX:AX/EDX:EAX/RDX:RAX中,若AH/DX/EDX/RDX中非0,会修改CF
  • IMUL是signed int mul,结果中会扩展符号位以保持结果的正负,可能修改OF
  • DIV是Unsigned,它的操作数为除数,AX/DX:AX/EDX:EAX为被除数,商放在AL/AX/EAX,余数放在AH/DX/EDX
  • IDIV是signed integer division,需要符号位扩展(指令有CBW即al扩展到ax,convert byte to word,CWD即ax扩展到dx:ax,CDQ)。注意负数时余数与被除数正负保持一致(49/-5=0xFFF7(-9)…0x0004,-49/-5=0x0009…0xFFFC(-4),-49/5=0xFFF7…0xFFFC)

扩展加减

  • ADC在加的时候还加上CF,SBB在减的时候还减去CF,以实现多位的加减

汇编环境配置

汇编系列文章:
https://www.jianshu.com/nb/29822876

来源:https://blog.csdn.net/u013761036/article/details/52186683

环境

VS2017 Community

步骤

  • 从官网(http://www.masm32.com/)下载后安装;
  • 语法高亮
    • VS中工具-扩展和更新,联网下载AsmHighlighter,重启安装即可
  • 在VS中新建项目:
    • 选择空解决方案
    • 手动新建空项目(Visual C++内)
    • 项目上右键-生成依赖项-生成自定义 选择masm
    • 项目上右键-属性-连接器 下:
      • 常规-附加库目录 设置为masm32路径下lib文件夹
      • 系统-子系统 选择 控制台程序
    • 新建asm文件
    • 项目属性中 Microsoft Macro Assembler-General中设置Include Paths中设置masm32路径下include文件夹

深度学习网络

SVM Support Vector Machine 支持向量机

https://blog.csdn.net/v_july_v/article/details/7624837

https://www.jianshu.com/p/96e8fad1a2a4

线性分类器:找超平面分开。logistic函数(sigmoid函数)将自变量映射到(0,1)上,回归就是学习参数矩阵,找到后直接判断结果是正还是负即可。

几何间隔:点到分类面的距离。

支持向量的距离最近,为1/||w||

非线性可分则选择核函数将数据映射到高维空间解决问题。

避免过拟合可以使用别的损失函数,加上正则化项

CNN

http://www.cnblogs.com/lc1217/p/7324935.html

RCNN

https://www.cnblogs.com/skyfsm/p/6806246.html // 挺生动形象的

https://blog.csdn.net/lhanchao/article/details/72287377

候选区域选择、CNN特征提取、分类与边界回归

检测时每一类都使用一个SVM(用于判断是否是这类)

RCNN里面用到的Selective Search算法见:

https://blog.csdn.net/small_munich/article/details/79595257

对输入图像进行分割算法产生许多小的子区域。其次,根据这些子区域之间相似性(相似性标准主要有颜色、纹理、大小等等)进行区域合并,不断的进行区域迭代合并。每次迭代过程中对这些合并的子区域做bounding boxes(外切矩形),

SPP-NET

在RCNN中,提取特征耗时较长,因此在初始没有提取特征的时候就先进行卷积运算,减少卷积运算数目,以加速。

同时取消掉了图像归一化的过程,同时使用空间金字塔池化。

空间金字塔池化:一种可伸缩的池化层,输出都是特定尺寸的特征。金字塔可以通过多尺度增加特征鲁棒性。

Fast-RCNN

简化版的池化层(无金字塔),加入了候选框映射功能以反向传播。多任务Loss层(SoftmaxLoss代替SVM,SmoothL1Loss取代Bounding box回归),将分类与边框回归进行合并。多连接层通过SVD加速

Faster-RCNN

使用EdgeBoxes(https://blog.csdn.net/wsj998689aa/article/details/39476551)提取候选框,并且是在特征图上提取。

YOLO

划分为固定网络,如果样本中心落在对应网络,就负责回归。

Numpy

https://www.yiibai.com/numpy/

NumPy 是一个 Python 包。 它代表 “Numeric Python”。 它是一个由多维数组对象和用于处理数组的例程集合组成的库。

类型

NumPy 中定义的最重要的对象是称为 ndarray 的 N 维数组类型。 它描述相同类型的元素集合。 可以使用基于零的索引访问集合中的项目。

ndarray中的每个元素在内存中使用相同大小的块。 ndarray中的每个元素是数据类型对象的对象(称为 dtype)。

numpy.array(object, dtype = None, copy = True, order = None, subok = False, ndmin = 0)
a = np.array([1,2,3])
a = np.array([[1, 2], [3, 4]])
a = np.array([1, 2, 3,4,5], ndmin = 2)
a = np.array([1, 2, 3], dtype = complex)

astype可以用来进行类型转换
finfo可以用来查看取值范围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用端记号  
import numpy as np
dt = np.dtype('>i4')
print dt

dt = np.dtype([('age',np.int8)])
a = np.array([(10,),(20,),(30,)], dtype = dt)
# 或者直接用a = np.array([1,2], dtype = dt)
print a
print a['age']

student = np.dtype([('name','S20'), ('age', 'i1'), ('marks', 'f4')])
a = np.array([('abc', 21, 50),('xyz', 18, 75)], dtype = student)
print a

每个内建类型都有一个唯一定义它的字符代码:

  • ‘b’:布尔值
  • ‘i’:符号整数
  • ‘u’:无符号整数
  • ‘f’:浮点
  • ‘c’:复数浮点
  • ‘m’:时间间隔
  • ‘M’:日期时间
  • ‘O’:Python 对象
  • ‘S’, ‘a’:字节串
  • ‘U’:Unicode
  • ‘V’:原始数据(void)

字节序:

http://www.ruanyifeng.com/blog/2016/11/byte-order.html
降序(Big-endian)大端字节序存储时 由左到右
升序(Little-endian)小端字节序存储时 由右向左
1byte是8bit,所以需要两位16进制

数组属性

  • ndarray.shape
    • 可读可写
  • reshape
    • 类似直接修改shape,但不修改原来的数组
  • dim
    • 返回数组的维度
  • itemsize
    • 字节单位的长度,如int8(-128~127)的字节长度为1
  • flags
    • C_CONTIGUOUS (C) 数组位于单一的、C 风格的连续区段内
    • F_CONTIGUOUS (F) 数组位于单一的、Fortran 风格的连续区段内
    • OWNDATA (O) 数组的内存从其它对象处借用
    • WRITEABLE (W) 数据区域可写入。 将它设置为flase会锁定数据,使其只读
    • ALIGNED (A) 数据和任何元素会为硬件适当对齐
    • UPDATEIFCOPY (U) 这个数组是另一数组的副本。当这个数组释放时,源数组会由这个数组中的元素更新

数组创建

  • empty:未初始化,指定shape,dtype和order
  • zeros:初始化为0
  • ones:初始化为1
  • asarray:接受列表、列表的元组、元组、元组的元组、元组的列表等各种形式的输入,并且可以指定dtype和order
  • frombuffer:缓冲区作为参数,指定dtype、count(读取的数字数量,默认-1读取全部)、offset
  • fromiter:迭代器
  • range:设定start、stop、step、dtype
  • linspace:设定start、stop、num、endpoint(是否包含stop)、retstep(返回样例及步长)、dtype
  • logspace:对数轴上均匀分布,start(起始为base ** start)、stop、num、endpoint、base(默认为10)、dtype

数组方法

  • 切片:用起始、终止、步长。如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    a = np.arange(10)
    s = slice(2,7,2)
    print a[s]

    >>> a = np.arange(10)
    >>> a
    array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
    >>> a[3:8]
    array([3, 4, 5, 6, 7])
    >>> a[3:8:2]
    array([3, 5, 7])
    >>> a+9
    array([ 9, 10, 11, 12, 13, 14, 15, 16, 17, 18])
    >>> (a+9)[a[3:8:2]]
    array([12, 14, 16])
  • 第二列用a[...,1] ,第二行用a[1,...],第二列及以后所有为a[...,1:]

    • 高级索引:

      • 整数索引:y = x[[0,1,2], [0,1,0]]包括数组中(0,0),(1,1)和(2,0)位置处的元素。
      • 布尔索引:x[x > 5]可以输出大于5的所有元素值,a[~np.isnan(a)]可以过滤nan
    • 广播

      • 对数组的算术运算通常在相应的元素上进行。 如果两个阵列具有完全相同的形状,则这些操作被无缝执行。在 NumPy 中仍然可以对形状不相似的数组进行操作,因为它拥有广播功能。 较小的数组会广播到较大数组的大小,以便使它们的形状可兼容。
        1
        2
        3
        4
        5
        6
        >>> np.array([1,2])+1
        array([2, 3])
        >>> np.array([1,2])*3
        array([3, 6])
        >>> np.array([1,2])*np.array([2,3])
        array([2, 6])
        广播示例
    • 迭代:数组变成迭代器,np.nditer

    • 转置:直接a.T

    • 修改形状:reshape

    • flat:一维迭代器

    • flatten:数组副本,可以带参数order

    • ravel

    • transpose:翻转给定的维度,同.T