汇编学习笔记

汇编学习笔记 汇编进入文件必须考虑大小写 编译过程 进入D盘  d: 1.进入有var的文件夹 2.运行var 3.修改makefile 4.进入存放要编译文件的文件夹 5.nmake 进入某个文件夹,前面不需要加反斜杠,进入其子文件夹需要加反斜杠

如果变量名靠的太紧,程序会无法识别 变量名和文件名要匹配,小心文件名里包含空格等,巨坑啊

借鉴别人代码的时候,在看懂的基础上,直接将源代码直接复制过去,接着修改变量名,然后运行。发现错误后再修改 先实现功能再减少功能,这是一种由整化简的思路 若是由简凑整反而速度会慢

使用CMD时,点击右键,选标记,然后选中命令提示符对话框中的文字,按ctrl+C,就能将里面的文字复制出来了

ExitLoop:      invoke FreeLibrary,hRichEdit   mov eax, msg.wParam       ret

其中 mov eax, msg.wParam       ret 是结束语句

汇编有错,很可能是输入时用的输入法是中文的。 汇编对大小写的要求很明显

asm文件中也可以用include语句包含数据定义和函数声明的头文件。Win32汇编的头文件一般用inc作扩展名,如MASM32软件包附带的Windows.inc文件定义了Win32 API中很多参数和数据结构,其他的inc文件则是不同DLL中的Win32 API函数声明。

资源脚本文件的扩展名一般为rc,经过资源编译器编译成资源文件*.res。

但在Win32环境下,大部分的公用函数封装在DLL文件中,以动态链接的方式供用户程序调用

动态链接的方式供用户程序调用。这时候库文件中只需要包含函数在DLL中的位置信息,不再需要有二进制代码部分。所以链接的时候也只是把库文件中的位置信息取出放入最后的可执行文件中。Win32中这种只包含位置信息的库文件称为导入库。

.model语句用来定义程序工作的模式,它的使用方法是: .model 内存模式[,语言模式][,其他模式] 对Win32程序来说,只有一种内存模式,即flat(平坦)模式,意思是内存是很“平坦”地从0延伸到 4 GB

纵观Win32汇编的源程序,没有一处可以找到ds或es等段寄存器的使用,因为所有的4 GB空间用32位的寄存器全部都能访问到了,不必在头脑中随时记着当前使用的是哪个数据段,这就是“平坦”内存模式带来的好处。

.model语句中还应该指定语言模式,即子程序的调用方式 stdcall,它指出了调用子程序或Win32 API时参数传递的次序和堆栈平衡的方法

option语句 在Win32汇编程序中,需要的只是定义option casemap:none,这个语句定义了程序中的变量和子程序名是否对大小写敏感,由于Win32 API中的API名称是区分大小写的,所以必须指定这个选项,否则在调用API的时候会有问题。

2、数据段 第一类是可读可写的已定义变量。.data段是已初始化数据段,其中定义的数据是可读可写的,在程序装入完成的时候,这些值就已经在内存中了

第二类是可读可写的未定义变量。这些变量一般是当做缓冲区或者在程序执行后才开始使用的,这些数据可以定义在 .data段中,也可以定义在 .data?段中,但一般把它放到 .data?段中。

第一类占内存,第二类不占。只给他分配空间大小。

.const段是常量段,它是可读不可写的。 在程序中如果不小心写了对 .const段中的数据做写操作的指令,会引起保护错误

3、代码段 code段是代码段,在可执行文件中,代码段是放在_TEXT节区中

代码段的属性是由可执行文件PE头部中的属性位决定的,通过编辑磁盘上的 .exe文件,把代码段属性位改成可写,那么在程序中就允许修改自己的代码段。

多个模块链接在一起的时候,只能有一个主模块指定入口地址

用反斜杠()做换行符  invoke  MessageBox,

        Null,              ;父窗口句柄

        offset szText,     ;消息框中的文字

        offset szCaption,  ;标题文字

        MB_OK 反斜杠后面多几个空格或加上注释并不影响换行符的使用,如上例所示,这一点和makefile文件中换行符的规定有所不同。

API 当应用程序要引用系统功能时,要把相应的参数放在各个寄存器中再调用相应的中断,程序控制权转到中断中去执行,完成以后会通过iret中断返回指令回到应用程序中。

AUX in 辅助输入接口

Win32的基础就是由DLL组成的。Win32 API的核心由3个DLL提供 ●   KERNEL32.DLL——系统服务功能。包括内存管理、任务管理和动态链接等。

●   GDI32.DLL——图形设备接口。利用VGA与DRV之类的显示设备驱动程序完成显示文本和矩形等功能。

●   USER32.DLL——用户接口服务。建立窗口和传送消息等。

Win32 API是用堆栈来传递参数的,调用者把参数一个个压入堆栈,DLL中的函数程序再从堆栈中取出参数处理,并在返回之前将堆栈中已经无用的参数丢弃。

HWND类型的窗口句柄(hWnd),LPCTSTR类型的要显示的字符串地址(lpText)和标题字符串地址(lpCaption),还有UINT类型的消息框类型(uType)。

Windows所有编程资料发布的格式也是C格式。

,Win32 API调用中要把参数放入堆栈,顺序是最后一个参数最先进栈

在源程序编译链接成可执行文件后,call MessageBox语句中的MessageBox会被换成一个地址,指向可执行文件中的导入表,导入表中指向MessageBox函数的实际地址会在程序装入内存的时候,根据User32.dll在内存中的位置由Windows系统动态填入。

如果写的时候少写了一句push指令,程序在编译和链接的时候都不会报错,但在执行的时候必定会崩溃,原因是堆栈对不齐了。

invoke伪指令,它的格式是:

    invoke  函数名[,参数1][,参数2]……

对MessageBox的调用在MASM中可以写成: invoke  MessageBox,NULL,offset szText,offset szCaption,MB_OK

invoke并不是80386处理器的指令,而是一个MASM编译器的伪指令,在编译的时候它把上面的指令展开成我们需要的4个push指令和1个call指令,

如果带的参数数量和声明时的数量不符,编译器会报错:

error A2137: too few arguments to INVOKE

有的API函数有返回值,返回值的类型对汇编程序来说也只有dword一种类型,它永远放在eax中。

在调用API函数的时候,函数原型也必须预先声明。 声明函数的格式是:

函数名 proto [距离] [语言] [参数1]:数据类型,[参数2]:数据类型,…… 对于编译器来说,它只关心参数的数量,参数的名称在这里是“无用”的,仅是为了可读性而设置的,可以省略掉

3.22

在调用API函数的时候,函数原型也必须预先声明 否则,编译器会不认这个函数。invoke伪指令也无法检查参数个数。声明函数的格式是:

函数名 proto [距离] [语言] [参数1]:数据类型,[参数2]:数据类型,…… 对Win32汇编来说只存在dword类型的参数,所以所有参数的数据类型永远是dword,另外对于编译器来说,它只关心参数的数量,参数的名称在这里是“无用”的,仅是为了可读性而设置的,可以省略掉,所以下面两句消息框函数的定义实际上是一样的:

MessageBox  Proto  hWnd:dword,lpText:dword,lpCaption:dword,uType:dword

MessageBox  Proto  :dword,:dword,:dword,:dword

在MASM32工具包中已经包括了所有DLL的API函数声明列表,每个DLL对应<DLL名.inc>文件,在源程序中只要使用include语句包含进来就可以了。

 includelib语句 在Win32汇编中使用API函数,程序必须知道调用的API函数存在于哪个DLL中,所以,必须有个文件包括DLL库正确的定位信息,这个任务是由导入库来实现的。

Win32环境中,程序链接的时候仍然要使用函数库来定位函数信息,只不过由于函数代码放在DLL文件中,库文件中只留有函数的定位信息和参数数目等简单信息,这种库文件叫做导入库,一个DLL文件对应一个导入库,如User32.dll文件用于编程的导入库是User32.lib,

和include语句的处理不同,includelib不会把 .lib文件插入到源程序中,它只是告诉链接器在链接的时候到指定的库文件中去找而已

uType —— 定义对话框的类型 的Windows.inc也包括了uType所有这些参数的定义,只要在程序的开头包含这个定义文件:

include         windows.inc

子程序先声明后调用,用proc伪指令,先调用后声明用proto伪指令,proto就是告诉编译器,调用的子程序,定义这条指令的后边

标号

masm中的@@ loc1在别的地方就再也用不到了,对于这种情况,高版本的MASM用@@标号去代替它: 当用@@做标号时,可以用@F和@B来引用它,@F表示本条指令后的第一个@@标号,@B表示本条指令前的第一个@@标号,程序中可以有多个@@标号,@B和@F只寻找匹配最近的一个。

全局变量

所有使用到变量类型的情况中,只有定义全局变量的时候类型才可以用缩写

Win32汇编的全局变量定义在 .data或 .data?段内,可以同时定义变量的类型和长度,格式是:

变量名      类型    初始值1,初始值2,……

变量名      类型    重复数量 dup (初始值1,初始值2,……)

.data?不能指定初始值,在全局变量中,初始值就是0

局部变量 local伪指令必须紧接在子程序定义的伪指令proc后、其他指令开始前,这是因为局部变量的数目必须在子程序开始的时候就确定下来

local       变量名1[[重复数量]][:类型],变量名2[[重复数量]][:类型]…

在一个local语句定义不下的时候,可以有多个local语句

Win32汇编默认的类型是dword,如果定义dword类型的局部变量,则类型可以省略 当定义数组的时候,可以 [] 括号括起来。不能使用定义全局变量的dup伪指令。

用local语句定义了3个变量,@loc1是dword类型,@loc2是word类型,@loc3是byte类型,在程序中分别有3句存取3个局部变量的指令,然后就返回了

和全局变量不一样,局部变量的起始值是随机的,是其他子程序执行后在堆栈里留下的垃圾,所以,对局部变量的值一定要初始化,特别是定义为结构后当参数传递给API函数的时候。

在实际使用中,常常有使用指针存取数据结构的情况,如果使用esi寄存器做指针寻址,可以使用下列语句完成同样的功能:

mov     esi,offset stWndClass

mov     eax,[esi + WNDCLASS.lpfnWndProc]

注意:第二句是[esi + WNDCLASS.lpfnWndProc]而不是[esi + stWndClass.lpfnWndProc]

在不再使用esi寄存器做指针的时候要用assume esi:nothing取消定义。

结构的定义也可以嵌套

NEW_WNDCLASS     struct

DwOption         dword     ?

OldWndClass      WNDCLASS  <>

NEW_WNDCLASS     ends

变量的使用

MASM中,如果要用指定类型之外的长度访问变量,必须显式地指出要访问的长度,这样,编译器忽略语法上的长度检验,仅使用变量的地址。 sizeof判断变量大小 lengthof判断变量数

sizeof szHello是第一行字符的数量

addr 局部变量名和全局变量名

获取变量地址

当addr后跟全局变量名的时候,用法和offset是相同的;当addr后面跟局部变量名的时候,编译器会自动用lea指令先把地址取到eax中,然后用eax来代替变量地址使用。注意addr伪操作符只能在invoke的参数中使用

子程序的定义

子程序的定义方式如下所示。

子程序名  proc [距离][语言类型][可视区域][USES 寄存器列表][,参数:类型]...[VARARG]

         local 局部变量列表

         指令

proc定义函数,proto伪指令声明函数 在写源程序的时候可以有意识地把子程序的位置提到invoke语句的前面,省略掉proto语句,可以简化程序和避免出错。  

子程序名  endp

第四章 第一个窗口程序

_ProcWinMain,它是一个分支结构处理的子程序,功能是把参数uMsg取出来,根据不同的uMsg执行不同的代码,完了以后就退出了,中间也没有任何东西和主程序有关联。

4.2模块和句柄 一个模块代表的是一个运行中的exe文件或dll文件,用来代表这个文件中所有的代码和资源,磁盘上的文件不是模块,装入内存后运行时就叫做模块。

第五章 使用资源 编译资源文件,makefile中比以前多了一个资源编译的隐含规则:

.rc.res:

        rc $<

用到16进制数值的时候并不是用汇编的语法在后面加h,而是用前面加Ox的方法,如1234h写为Ox1234,注释也要用前面加//的方法。

菜单项的定义语句必须包含在begin和end关键字之内,这两个关键字也可以用花括号{ 和 } 代替。

API函数检测参数时发现小于10000h时就可以把它认为是数值型的,大于10000h时就当做字符串指针处理。 实际应用中通常使用16进制数值当做菜单ID。

菜单文字——显示在菜单项中的字符串。如果需要字符串中某个字母带下横线,那么可以在字母前面加&符号,如“字体(F)...”就要写成“字体(&F)...”,带下横线的字母可以被系统自动当做快捷键

同时在exe文件的依赖文件中增加了Menu.res文件。

另外,如果要把加速键的提示信息显示在菜单项的右边,如“字体”菜单项中的“Alt+F”字符,可以在两者中间加 (表示插入一个Tab字符),写为“字体(&F)... Alt+F”,这样Tab后面的字符在显示的时候会右对齐。

CHECKED——表示打上选定标志(对钩)。

GRAYED——表示菜单项是灰化的。

INACTIVE——表示菜单项是禁用的。

MENUBREAK或MENUBARBREAK——表示将这个菜单项和以后的菜单项列到新的列中。

如果要指定超过一个的选项,中间要用逗号隔开,但是也有些小小的限制:GRAYED和INACTIVE不能同时使用,MENUBREAK和MENUBARBREAK也是不能同时使用的。

5.2 光标和图标 装入图标和光标 使用预定义图标和光标的好处是它们的形状会随着系统设置值的不同自动改变,如改变“控制面板”→“鼠标”→“指针”中的设置后,装入的光标会自动改变。 使用图标和光标 WM_SETCURSOR,这个消息是通知窗口重新刷新光标而不是让它设定指定的光标

色彩表和位图数据合在一起就叫做设备无关位图(DIB 用DIB惟一的问题是当将高颜色数的DIB转换到低颜色数的设备  上时,由于色彩只能被转换成设备所能表示的最相近的颜色,所以可能会有很大的颜色失真。 Windows支持的图形文件格式只有bmp,ico与cur等几种,可以广泛用在GDI操作中的只有bmp文件

动态链接库和钩子

静态库仅在编译的时候使用,编译完成后,可执行文件就可以脱离库文件单独使用了,而动态链接库中的代码在程序编译的时候并不会被插入到可执行文件中,在程序运行的时候才将整个库的代码调入内存,所以称为“动态链接”。

动态链接库的缩写为DLL,大部分动态链接库是以扩展名为dll的文件形式存在的

动态链接库文件和可执行文件同样使用标准的PE文件格式,仅文件头中的属性位不同而已

动态链接库是被映射到其他应用程序的地址空间中执行的,它和应用程序可以看成是“一体”的。 个人体会:动态链接库就是程序的一部分,exe在运行时他也在运行。

当dwReason的值是DLL_THREAD_ATTACH的时候,表示应用程序创建了一个新的线程。

与写普通的可执行文件相比,动态链接库的设计流程中多了一个文件,那就是定义文件 *.def

每个PE格式文件的文件头中都可以有一个导出表,只有导出表中列出的函数才可以被其他程序调用

没有在def文件中指定(如例子中的_SetText函数),那么这个函数就仅能被库文件中的代码调用,而无法在其他应用程序中使用,可以把它们叫做私有函数。

动态装入 不要让Windows系统来做动态链接库的装入工作,这些工作由应用程序自己的代码来完成 有3个函数可以用来完成这样的功能:LoadLibrary(装入动态链接库),FreeLibrary(释放动态链接库)和GetProcAddress(获取导出函数地址)。 上述函数都是在库里面的,直接调用就可以。

lpProcName指向要获取的函数名称,函数名也是用以NULL结尾的字符串来定义。有些系统DLL中的函数名称并不是字符串,而是使用数值编号,对于这种情况,lpProcName参数可以指定为函数的编号数值。 如果执行成功,返回值是要获取的函数的入口地址,程序可以保存它并在以后调用。如果执行失败,比如因为版本变化等原因导致需要获取的函数不存在,这时函数返回NULL。

虽然Counter.dll被多个进程同时装入,但是操作系统为它们映射了各不相同的数据段,使它们工作起来互不影响。

.data?的节区名称为 .bbs,加上/section:.bss,S选项就可以将这个段的属性改为共享,这样,当DLL被不同应用程序装载的时候,不但映射到不同进程地址空间中的代码段来自同一段物理内存,.data?段的映射也来自同一段物理内存。

11 钩子函数——远程钩子的安装和使用 1. 钩子程序的结构

钩子程序一般包括3个功能模块:

(1)主程序——用来实现界面或者其他功能。

(2)钩子回调函数——用来接收系统发过来的消息。

(3)钩子的安装和卸载程序。

由于安装钩子回调函数的动态链接库要求是共享数据段的,所以请读者注意Makefile中dll文件的链接选项,它使用了/section:.bss,S选项。 (需要学习动态链接库)

需要共享的变量被放在 .data?段中

动态链接库的入口函数例行公事地返回了一个TRUE来表示允许被装入。

idHook参数指定钩子的类型,例子中要安装的是键盘钩子,所以使用WH_KEYBOARD

hInstance 指定钩子回调函数所在DLL的实例句柄。 dwThreadID是安装钩子后想监控的线程的ID号。

想要安装系统范围的全局钩子的话,可以将这个参数指定为NULL,这样钩子就会被解释成系统范围的,可以用来监控所有的进程及它们的线程。

动态链接库导出的另一个函数是UninstallHook

(学习对话框)

每个击键动作,钩子回调函数会在键按下和释放的时候被调用两次,只需根据 lParam的位31中的标志来记录一次

这些钩子组成一个钩子链,最近加入的钩子放在链表的头部,Windows负责为每种钩子维护一个钩子链。

dwVirtKey参数指定按键的虚拟码,在使用时直接用钩子回调函数的wParam参数就可以了,uScanCode指定按键的扫描码,并用位15来表示是按键按下还是按键释放,和回调函数的lParam参数对比可以看出,lParam参数的高16位

原文地址:https://www.cnblogs.com/yshblog/p/3379663.html