在 win7 64位下学习汇编,无法使用 debug 和 masm,可以利用 DosBox 这个软件。
DosBox 是一个 DOS 模拟程序,由于它采用的是 SDL 库,所以可以很方便的移植到其他的平台,支持 Windows、Linux、Mac OS X、Android 、webOS等系统。
1、先下载好 debug 和 masm ,解压到 d:\masm 目录下。
2、运行 DOSBox 后,我们可以看到Z : \ >,这是 DOSBox 里的虚拟盘,使用 mount 命令将 d:\masm 挂载到虚拟的 d 盘: mount d d:\masm
3、使用 "d:" 命令进入虚拟 d 盘,即可运行 debug 和 masm 程序。
可以通过配置 dosbox-0.74.conf 文件(或者通过开始菜单中的 DosBox 0.74 -> Options -> DosBox 0.74 Options 找到),在打开的文本末尾会找到 [autoexec] ,在它之后加上:
mount d d:\masm
set PATH=Z:;d: d:
cd source
这样就不需要每次进入 DosBox 都要重新配置了,并且设置了环境变量,可以在代码目录直接进行编译和链接了。
radasm 是一款汇编语言的 IDE,很好用,但是只能在 32 位系统下运行。官网下载只是一个编辑器,不带编译汇编功能,可以从网上下载整合好OD、masm 的汉化版,安装好之后,如果配置下 masm 的环境变量,“选项"——>"环境变量"——>将 PATH 设置成如 C:\RadASM\masm32\bin; 即可。
linux 下可以使用 objdump 将二进制文件反汇编,如 objdump -d test1
Hello,World!
windows 版:
data segment ;数据段 hello db 'Hello,World!$',0 data ends code segment ;代码段 assume cs:code,ds:data start: ;入口 mov ax,data mov ds,ax lea dx,hello mov ah,9h int 21h mov ah,4ch int 21h code ends end start ;标志入口点
linux 版:
.section .data msg: .ascii "Hello,World!\n" len= . -msg .section .text #.globl main .globl _start #main: _start: movl $4,%eax movl $1,%ebx movl $msg,%ecx movl $len,%edx int $0x80 movl $1,%eax movl $0,%ebx int $0x80
使用 gcc hello.s 会编译错误,是因为用cc编译的话_start这个符号已经有定义了,这个是c runtime入口,cc编译的C程序的真正开始位置是_start symbol,这个_start在它初始化运行环境后调用main,因此你需要使用as编译这个汇编程序,因为gcc已经把_start占用了,如果非要用gcc可以使用gcc -nostdlib hello.s 编译, -nostdlib 让 gcc 不链接标准库。或者,把 _start 改成 main。
debug 的常用命令:
1、R: 寄存器显示与更改(Register)
格式:R [寄存器]
寄存器:AX BX CX DX SP BP SI DI DS ES CS IP PC F
若R不带参数,则显示所有寄存器的内容和状态标志、下一指令。
若指定新值,在显示内容后,给出冒句提示输入新值。回车结束。
对状态字F,在连字符 - 后以空格间隔输入新值,次序不计。
若直接回车,则跳过修改,寄存器内容不变。
2、D: 显示内存内容(Dump)
格式:D [段地址:起始偏移地址] [结尾偏移地址]
内存内容显示指令,以十六进制和ASCII码形式显示指定范围内的内存内容。
若不指定范围,第一次按目标程序的 CS:IP 的位址开始显示显示 128byte 的内容,以后使用上次显示的未地址的下一地址为开始进行显示。
3、E: 数据的输入(Entering)
格式:E [段地址:起始偏移地址] 数值列表(可以是数字、字符、字符串、机器码,以空格隔开)
向内存区域输入数据。数据以十六进制形式,或以ASCII码形式均可,覆盖掉原有数据。
十六进制时要用空格、逗号或制表符加以分隔。字符串则要用单引号或双引号括起且区分大小写。
若不指写段址,则默认为 DS 值,每写完一数据地址自动增加。
如省略数值列表,可以提问的方式逐个修改从某一地址开始的内存单元中的内容,点号前为该字节原始值,点号后请求输入。这时若按空格,跳过这一字节,输入下一字节;按回车,结束输入;按减号或连字符,显示或修改前一字节内容。 字符串只能以数值列表的形式输入,不能按提问的方式输入。
4、U: 反汇编(Unassemble)
格式:U [段地址:起始偏移地址] [结尾偏移地址]
将机器指定解码为汇编语言的助记符,若地址范围中无段址时,默认使用CS值。若不含末地址或长度,则自给定始地址起反汇编32个字节。以后由前次U最后一指令的下一指令做32字节的反汇编。
若从没用过U,则于CS:IP开始进行反汇编。
只能对8086指令解码,对其它以DB来显示。
5、T: 程序执行跟踪(Trace)
格式:T [=段地址:起始偏移地址] [指令数]
没有 [指令数] 时,默认执行单条指令,显示寄存器及下一条指令。
没有 [=段地址:起始偏移地址] 时,则默认为 CS:IP 寄存器的值。
6、A: 编写汇编代码(Assemble)
格式:A [段地址:起始偏移地址]
用途:程序允许在指定位置(若无缺省为IP指针位置)进行汇编程序书写,相比于 E 命令写入机器指令要方便很多,使用方法也类似于 E 命令的提问方式。
对内存地址加[]以与立即数区分。
栈:push pop
任意时刻,SS:SP 指向栈顶元素。
push ax ——> SP=SP-2 && mov SS:SP ax
pop ax ——> mov ax SS:SP && SP=SP+2
jmp short 标号
jmp near ptr 标号
jmp far ptr 标号:标号为四字节,前两个字节是IP(高位地址放高位,低位地址放低位),后两个字节是CS(高位地址放高位,低位地址放低位),不同于上面的,这里给出的是目标的 CS:IP 的绝对地址
(CS)=标号所在段的段地址
(IP)=标号所在段的偏移地址
jmp word ptr 内存单元地址(段内转移):跳转到 [内存单元地址] 中的值指定的地址,此处 [内存单元地址] 存放一个字。
jmp dword ptr 内存单元地址(段间转移):此处 [内存单元地址] 存放两个字,(CS)=(内存单元地址+2),(IP)=(内存单元地址)
jcxz 标号:条件转移指令,都是短转移,对应的机器码中包含转移的位移,而不是目的地址。如果 cx 为 0,则转移到标号处执行,否则不跳转。当 (cx)=0 时,(IP)=(IP)+8 位位移。相当于 if( (cx)==0) jmp short 标号;
loop 标号:(cx)--; if ( (cx) !=0) jmp short 标号
call 和 jmp 的区别,只是多做了一步把当前地址入栈的操作。
mul 与 div
8位相乘:AX中
16位相乘:高位放于DX中,你们放于AX中
DOS下,一般合法程序都不会使用 0:200 ~ 0:2ff (00200h~002ffh)的 256 个字节空间,所以我们可以拿来学习。
8个通用寄存器:
AX (Accumulator) 累加寄存器,可分为 AH 和 AL,属于数据
BX (Base) 基址寄存器,可分为 BH 和 BL
CX (Counter) 计数寄存器,可分为 CH 和 CL
DX (Data) 数据寄存器,可分为 DH 和 DL
SP (Stack Pointer) 堆栈指针寄存器,用来指示栈顶的偏移地址
BP (Base Pointer) 基址指针寄存器,与段寄存器 SS 联用,可作为堆栈区的一个基地址,以相对方式访问堆栈中的存储单元。
SI (Source Index) 源变址寄存器,与段寄存器 DS 联用,用来确定数据段中某一存储单元的地址。
DI (Destination Index) 目的变址寄存器,可与 DS、ES 联用,用来确定数据段或附加段中某一存储单元的地址。
四个段寄存器:
CS (Code Segment) 存放程序段首地址
DS (Data Segment) 存放数据段首地址
ES (Extra Segment) 存放数据段首地址
SS (Stack Segment) 存放堆栈段首地址
两个控制寄存器:
IP
FR