本文随便整理,我们上微机原理之前没上汇编,无奈学校用的那本教材垃圾的超越想象力,根本就不是给初学者学习用的,整一个copy paste的产物,没头没尾,莫名其妙。所以只好整理一下Intel 汇编的知识了。本贴只是简单罗列,对他人作用不大。
首先是资料:在Visual Studio里面配置MASM
加载和执行:
地址空间:保护模式4G,实模式1M
基本寄存器:
详细的说明:
实模式下段寄存器用于存放段基址。保护模式下则存放描述符表的指针。
EFLAGS是一系列标志,1时表示set了。0时表示reset了,标志包括:
空间为0~FFFFF,段址和偏址都是16位,段址×16+偏址 = 绝对地址 08F1H:0100H = 09010H
空间4GB,0~FFFFFFFF,masm中用flat模式。段寄存器存的是描述符表中的描述符。所有段都被映射到32位物理地址中。
一个程序至少要用2个段:代码段和数据段。段描述符存在全局描述符表中,是个64位的值。段界限符表示系统中物理内存数。在多段模式下每个进程都有自己的独立空间,界限副就保存期各自的空间。
此外,还有分页模式
常量
默认十进制,可加后缀 10H, 10D, 10O, 10B
可用基本整数表达式
()+- * / MOD
字符和字符串常量
'A', "A", "Goodnight" 'Goodnight'
保留字
标识符
标识变量、常量、过程。。
伪指令
内嵌的特殊代码,如 DWORD .data .code .stack 100h(表示运行时栈的大小)
根据书上示例解释汇编程序结构
TITLE MASM Template (main.asm) ;TITLE是个注释整行的伪指令 ; Description: ; ; Revision date: INCLUDE Irvine32.inc .data ;数据段 myMessage BYTE "MASM program example",0dh,0ah,0 .code ;代码段 main PROC mov eax, 10000h add eax, 40000h sub eax, 20000h call DumpRegs exit ;exit 间接调用了一个ms-windows的函数来终止程序,由Irvine32.inc 定义 main ENDP ;标记main结束 END main ;END表明此行是汇编程序的最后一行,编译器将忽略该行后的所有内容,后面的main指明程序入口点的名字
这个程序的另外一种更加详细的写法:
TITLE MASM Template (main.asm) ; Description: ; ; Revision date: .386 ;最低CPU要求是80386 .model flat, stdcall ; .model flat指定汇编器为保护模式生成代码, stdcall允许调用ms-windows函数 .stack 4096 ExitProcess PROTO, dwExitCode:DWORD ; PROTO指明此程序使用的过程原型ExitProcess是mswindows函数,用于退出进程. DumpRegs还是Irvine32的寄存器dump的过程 DumpRegs PROTO .code main PROC mov eax, 10000h add eax, 40000h sub eax, 50000h call DumpRegs INVOKE ExitProcess, 0 ; 调用过程,传参0作为返回值 main ENDP END main
TITLE Program name (file name) ; desc ; author: ; create date; ; modified: ; mod date: author: INCLUDE Irvine32.inc .data ; var .code main PROC ; executable code exit main ENDP ; other proc END main
列表文件包括程序的源代码、行号、偏移地址、翻译后的机器码和一个符号表,方便阅读
映像文件时包含被连接程序的分段信息的文本文件
MASM的基本内部数据类型
还有DB,DW,DD(32bits 整数或实数),DQ(64bits 整数或实数),DT (10字节)
定义
var1 BYTE 'A' var2 BYTE ? ;不初始化 vard WORD 65535 vard1 SWORD -32768 varf REAL4 -2.1 varf1 REAL10 4.6E-400 varl1 BYTE 10,20,30,40 ;初始化多个值,每个向后偏移一个地址 ;定义字符串 str1 BYTE "GO TO", 0 str3 BYTE 'no',0 ;在多行定义字符串 str4 BYTE "HEY, this is a " BYTE "multi-line string",0dh,0ah, ; 0dh 0ah表示/r/n BYTE "See the reault" 0 ;初始化多个空间 var3 BYTE 20 DUP(0) ; 定义20字节全部初始化为0 var4 BYTE 4 DUP("STACK") ; "STACKSTACKSTACKSTACK" ;intel 使用little endian ; 12345678H ; 0000: 78 ; 0001: 56 ; 0002: 34 ; 0003: 12
注意的是,虽然课本上说字符串多行定义可以,但是实际上,貌似多行定义了以后用LENGTHOF操作只能得到一行的结果。这个需要进一步考证。
符号常量
符号常量实际上不占用存储空间,也不能修改
有三种伪指令用于这个: = EQU TEXTEQU
;name = expression name会被直接替换, = 可重定义, COUNT = 100 mov ax, COUNT ;编译结果是 mov ax,100 ;还可以通过和dup结合漂亮的定义数组 array1 COUNT DUP(0) ;计算数组的大小 list DWORD 10,30,33 ListSize = ($ - list) /4 ; $表示当前语句地址偏移值, /4因为DWORD4字节 ;---------------------------------- ;name equ experssion ; expression 是表达式 ;name equ <text> ; text是文本 ;equ不能重定义 ;-----------------------------------
基本运算指令
符号位影响
MOV系列
MOV系列都是数据传送指令,包括MOV, MOVZX, MOVSX
MOV指令有一些规则必须遵守:
对于尺寸不同的数之间数据传送就必须使用MOVZX和MOVSX,MOVZX是零扩展传送,也就是适用于传送无符号整数,指令会用0填充高位。而对于有符号数则用MOVSX
类似一个功能的是XCHG指令,用来交换两个操作数,但是注意它的两个操作数不能同时是内存操作数。
其他数据操作符
寻址
; 间接寻址 .data var BYTE 10h .code mov esi, OFFSET var ;esi存放var的偏移地址 mov al, [esi] ; mov al, var ; 间接寻址也可用于方便的遍历数组 .data arr BYTE 10,30,50,80 .code mov esi, OFFSET arr mov al, [esi] ; al= 10 inc esi mov ah, [esi] ; ah = 30 ;变址操作数(indexed operand) 把常量和寄存器相加得到一个有效地址,使用任意的32位通用寄存器作为变址寄存器 ; 格式 constant[reg] 或[constant + reg] array[esi] ;等价 [array + esi] ;对于不是一个字节的元素 array2[esi*4] ;第四个DWORD
使用loop做数组求和的例子
TITLE sum (sum.asm) INCLUDE Irvine32.inc .data intarr WORD 100h, 200h, 300h, 400h .code main PROC mov edi, OFFSET intarr ; intarr 的地址 mov ecx, LENGTHOF intarr ; 循环计数器 mov eax, 0 ; 累加器清零 L1: add eax, [edi] ;sum += intarr[edi] add edi, TYPE intarr ; 数组下标+1 loop L1 exit main ENDP END main
运行时栈
CPU直接管理的内存数组,使用SS和ESP两个寄存器保护模式下,SS存段选择子ESP存的是只想堆栈内特定位置的一个32位偏移值,也就就是栈顶了。一般无需手工操作。运行时栈的增长是负的,也就是每压入一个值,栈顶指针ESP减小(一般是4)
对运行时栈的操作有PUSH POP, PUSHFD POPFD, PUSHAD PUSHA POPAD POPA这些
汇编里的过程相当于高级语言里的函数。
procname PROC procname ENDP ; 除了main之外的过程都应该用ret返回 ; main调用了ExitProcess结束进程 ; 过程的返回值和参数通常都用寄存器保存 ; C和C++ 典型情况下使用AL返回8位值,AX返回16位值,EAX返回32位值
所以,调用过程无法就是给寄存器赋值,再call一下。
此外,还可以使用USES 操作符来为PROC伪指令指定要使用的寄存器,它会自动生成push和pop相应寄存器的命令。这个操作符只需要写在PROC伪指令后面就可以
;test 来测试位 test a1, 00001001b ;测试0和3位是否为0,仅当两个都为0时,ZF = 1 ;cmp 测试源和目标是否相等,隐含把目标数-源, 无符号数情况如下: mov ax, 5 cmp ax, 10 ; ZF= 0 CF =1 cmp ax, 5 ; ZF = 1 CF = 0 cmp ax, 3 ; ZF = 0 CF = 0 ; 有符号数情况如下: mov ax, -5 cmp ax, -3 ; SF!= OF cmp ax, -8 ; SF = OF cmp ax, -5 ; ZF = 1
条件跳转都是成对的比如jz 就对应一个jnz,MASM要求跳转的目的地址在本过程内
------------------------------------------未完待续-------------------------------------------------
p.s 贴出暂时要用的程序,从给定字符串计算某个子串出现的次数,写的不好,欢迎指正。
;--Short Assembly program for counting the occurrences of specific substring-- ;Author: Wu Yanxiang ;ID: 0715232024 ;ENV: VC++ 2010 + MASM 8.0 ;TEST case: Pass TITLE SUBSTRINGCNT (main.asm) INCLUDE Irvine32.inc .data INPUTSTR DB "SOME DAY HAVE SHINING SUN DOESNT MEANS ITS A SUNNY DAY UNLESS THE SUN MAKE ME SO CONFORTABLE", 0dh, 0ah, 0 SUBSTRC DB "SUN", 0 OUTPUT DB "SUN:", 0 SUBSTRCNT DD 0 ;occurrence of SUBSTRC FORI DD 0 ; for int i = 0 ... LENSUB DD 0 ; length of sub string LENTOTAL DD 0 ; length of entire string .code main PROC call HASSUBSTR mov edx, OFFSET OUTPUT call WriteString call WriteINT ;invoke system api by process defined in irvine32 exit main ENDP ;----------------------------------------------------- HASSUBSTR PROC USES esi ecx edx ebp ; ; count the occurrence times of SUBSTRC in INPUTSTR ; Return: eax ;----------------------------------------------------- mov LENTOTAL, LENGTHOF INPUTSTR mov LENSUB, LENGTHOF SUBSTRC dec LENTOTAL dec LENSUB mov ecx, LENTOTAL sub ecx, LENSUB inc ecx mov esi, 0 L1: mov dl, 1 ;indicates whether find a substring push ecx mov ecx, LENSUB mov edi, 0 mov esi, FORI L2: mov bl, INPUTSTR[esi] mov bh, SUBSTRC[edi] inc esi inc edi cmp bl, bh jne NOTEQUAL loop L2 jmp L1P2 NOTEQUAL: mov dl, 0 jmp L1P2 ;-------L2END--------------- L1P2: pop ecx cmp dl, 0 jne CONHAS inc FORI loop L1 jmp L1EXIT CONHAS: inc SUBSTRCNT mov ebx, LENSUB add FORI, ebx sub ecx, ebx inc ecx loop L1 L1EXIT: mov eax, SUBSTRCNT ret HASSUBSTR ENDP END main