(注:以下的程序都在32位的intel CPU上运行的visual studio开发环境中完成)
一、
1.内存分布图:
1-1:
unsigned char x[]={0x12,0x56,0x34,0x78,0x55,0xaa,0x88,0x99};
x:
地址 内容
初始地址 x[0]:0x12
地址+1 x[1]:0x56
+1 x[2]:0x34
......
1-2:
x3=0x3fa00000
x3:
地址 内容
初始地址 0x00
+1 0x00
0xa0
0x3f
1-3:
x1=1.25f
1.25=1.01(b)
根据IEEE 754标准,单精度浮点数(32位)的表示形式为:
符号位(1位) + 指数部分(8位) + 尾数部分(23位)
按照IEEE 754标准,将1.01表示成规格化形式为1.01 x 2^0。
因此,x1变量中对应的单精度浮点数内存中的十六进制表示如下:
- 符号位:0(正数)
- 指数部分:2^0对应的偏移值为127,即127+0=127,用8位二进制表示为01111111
- 尾数部分:去掉整数部分的1和小数点后面的部分01,再补足23位,即01000000000000000000000
综合起来,x1这个1.25这个浮点数内存中对应的十六进制表示为:0x3FA00000
x1:......(同1-2)
2.地址对应值
unsigned char x[]={0x12,0x56,0x34,0x78,0x55,0xaa,0x88,0x99};
x[0]:0xb8000100
字节:0x12
字:0x5612
双字:0x78345612
8字节:0x9988aa5578345612
3.sum=x+y
3-1:每次进行一个字节的求和运算,高位字节在运算时加上低位字节的进位:
mov esi, offset x ; 将 x 的地址加载到 esi 寄存器
mov edi, offset y ; 将 y 的地址加载到 edi 寄存器
mov ebx, offset sum ; 将 sum 的地址加载到 ebx 寄存器
mov ecx, 8 ; 循环计数器,8 为数组长度
L1:
mov al, [esi] ; 将 x 中的一个字节加载到 al 寄存器
add al, [edi] ; 将 y 中的一个字节加到 al 寄存器
add al, [ebx] ; 将 sum 中的一个字节加到 al 寄存器
mov [ebx], al ; 将 al 寄存器的值存回 sum 对应位置
inc esi ; 移动到 x 数组的下一个字节
inc edi ; 移动到 y 数组的下一个字节
inc ebx ; 移动到 sum 数组的下一个字节
loop L1 ; 循环
3-2:每次进行两个字节的求和运算,考虑进位:
mov esi, offset x ; 将 x 的地址加载到 esi 寄存器
mov edi, offset y ; 将 y 的地址加载到 edi 寄存器
mov ebx, offset sum ; 将 sum 的地址加载到 ebx 寄存器
mov ecx, 4 ; 循环计数器,因为每次处理两个字节,所以长度除以2
L2:
mov ax, [esi] ; 将 x 中的两个字节加载到 ax 寄存器
add ax, [edi] ; 将 y 中的两个字节加到 ax 寄存器
add ax, [ebx] ; 将 sum 中的两个字节加到 ax 寄存器
mov [ebx], ax ; 将 ax 寄存器的值存回 sum 对应位置
add esi, 2 ; 移动到 x 数组的下一个字
add edi, 2 ; 移动到 y 数组的下一个字
add ebx, 2 ; 移动到 sum 数组的下一个字
loop L2 ; 循环
3-3:每次进行四个字节的求和运算,考虑进位:
mov esi, offset x ; 将 x 的地址加载到 esi 寄存器
mov edi, offset y ; 将 y 的地址加载到 edi 寄存器
mov ebx, offset sum ; 将 sum 的地址加载到 ebx 寄存器
mov ecx, 2 ; 循环计数器,因为每次处理四个字节,所以长度除以4
L3:
mov eax, [esi] ; 将 x 中的四个字节加载到 eax 寄存器
add eax, [edi] ; 将 y 中的四个字节加到 eax 寄存器
add eax, [ebx] ; 将 sum 中的四个字节加到 eax 寄存器
mov [ebx], eax ; 将 eax 寄存器的值存回 sum 对应位置
add esi, 4 ; 移动到 x 数组的下一个双字
add edi, 4 ; 移动到 y 数组的下一个双字
add ebx, 4 ; 移动到 sum 数组的下一个双字
loop L3 ; 循环
3-4:sum = x[1] + y[1]
mov al, byte ptr x+1 ; 将 x 数组中索引为 1 的字节加载到 al 寄存器
add al, byte ptr y+1 ; 将 y 数组中索引为 1 的字节加到 al 寄存器
mov sum, al ; 将 al 寄存器的值存入 sum 变量中
二、简单程序
.386
.model flat, stdcall
option casemap:none
.code
main PROC
mov eax,1
add eax,2
ret
main ENDP
END main
配置环境步骤:
1.在visual studio创建新项目,选择C++下面的空项目
2.右击项目,选择添加,选择新建项
3.在新建项中创建C++文件,并把文件后缀名改为.asm,然后单击添加
4.右击项目,选择生成依赖项,选择生成自定义,然后选择masm自定义项文件,然后确定
5.编写代码后运行
三、分段函数
1.编写汇编语言程序段完成以下的分段函数的值的计算
f(x,y)={-1, x<1,y<<1
0, 1<=x<=5,1<=y<=5
1, x>5,y>5}
实现:
; 使用 flat 模式,.386 指令集
.model flat
.386
_DATA SEGMENT
x DB 3 ; 存储变量 x,大小为 1 字节
y DB 5 ; 存储变量 y,大小为 1 字节
_DATA ENDS
_BSS SEGMENT
fxy DB 01H DUP(0) ; 存储变量 fxy,大小为 1 字节,初始化为 0
_BSS ENDS
_TEXT SEGMENT
main PROC
_start:
push ebp
mov ebp, esp
; 比较 x 和 1 的值
cmp x, 1
jge GreaterThanOne
; 如果 x<1,则跳转到 GreaterThanOne 标签
cmp y, 1
jl GreaterThanOne
; 如果 y>=1,则跳转到 GreaterThanOne 标签
mov fxy, -1
jmp pEnd
GreaterThanOne:
; 比较 x 和 1 的值
cmp x, 1
jl NotOneToFive
; 如果 x>=1,则跳转到 NotOneToFive 标签
cmp x, 5
jg NotOneToFive
; 比较 y 和 1 的值
cmp y, 1
jl NotOneToFive
; 如果 y>=1,则跳转到 NotOneToFive 标签
cmp y, 5
jg NotOneToFive
; 如果 x 和 y 均在 1-5 的范围内,则设置 fxy 为 0
mov fxy, 0
jmp pEnd
NotOneToFive:
; 比较 x 和 5 的值
cmp x, 5
jle pEnd
; 如果 x>5,则跳转到 pEnd 标签
cmp y, 5
jle pEnd
; 如果 y>5,则跳转到 pEnd 标签
mov fxy, 1
pEnd:
xor eax, eax
pop ebp
ret 0
main ENDP
_TEXT ENDS
END _start
2.计算分段函数的值,分段函数如下:
f(x)=1 x:[10-100]
=2 x:[0-10)
=3 x:[-100,0)
=4 x:other value
已知对应的部分C语言代码如下
int main()
{
int x;
int fValue;
scanf("%d",&x)
__asm
{
?
}
printf("f(%d)=%d",x,fValue);
return 0;
}
请完成题目中“?”部分的汇编代码
mov eax, x ; 将x的值加载到eax寄存器
cmp eax, 10 ; 比较x是否大于等于10
jl less_than_10 ; 如果x小于10,跳转到less_than_10标签
cmp eax, 100 ; 比较x是否小于等于100
jg greater_than_100 ; 如果x大于100,跳转到greater_than_100标签
mov fValue, 1 ; 如果x在[10-100]范围内,将fValue置为1
jmp done ; 跳转到done标签
less_than_10:
cmp eax, 0 ; 比较x是否大于等于0
jl less_than_0 ; 如果x小于0,跳转到less_than_0标签
mov fValue, 2 ; 如果x在[0-10)范围内,将fValue置为2
jmp done ; 跳转到done标签
less_than_0:
cmp eax, -100 ; 比较x是否大于等于-100
jl other_value ; 如果x小于-100,跳转到other_value标签
mov fValue, 3 ; 如果x在[-100,0)范围内,将fValue置为3
jmp done ; 跳转到done标签
greater_than_100:
mov fValue, 4 ; 如果x大于100,将fValue置为4
jmp done ; 跳转到done标签
other_value:
mov fValue, 4 ; 如果x不在以上范围内,将fValue置为4
done:
四、地址表
1.有以下示例代码,请用地址表的方式实现以下的多路分支语句:
int fx
switch(x)
{
case 1: fx=1; break;
case 2: fx=10;break;
case 3: fx=100;break;
case 4: fx=1000;break;
default: fx=0;
}
代码:
.386 ; 使用 80386 指令集
.model flat, stdcall ; 使用 flat 内存模型和 stdcall 调用约定
option casemap:none ; 设置大小写敏感,不进行大小写转换
ExitProcess PROTO, dwExitCode:DWORD ; 声明一个外部函数 ExitProcess,用于退出程序
.data ; 数据段开始
x dd ? ; 定义变量 x,大小为 DWORD(4字节)
fx dd ? ; 定义变量 fx,大小为 DWORD(4字节)
address_table DD offset case_1, offset case_2, offset case_3, offset case_4, offset default_case ; 地址表,存储各个case标签的地址
.code ; 代码段开始
main proc ; 主过程开始
start: ; start标签
; 读取x的值
mov eax, [x]
; 计算跳转目标地址
mov edx, offset address_table ; 把地址表的起始地址存入edx寄存器
add edx, eax ; 把edx寄存器的值加上eax,得到跳转目标的地址
mov ecx, [edx] ; 把跳转目标的地址存入ecx寄存器
; 跳转到相应的case标签
jmp ecx
case_1: ; case_1标签
; fx = 1
mov eax, 1
mov [fx], eax
jmp pend
case_2: ; case_2标签
; fx = 10
mov eax, 10
mov [fx], eax
jmp pend
case_3: ; case_3标签
; fx = 100
mov eax, 100
mov [fx], eax
jmp pend
case_4: ; case_4标签
; fx = 1000
mov eax, 1000
mov [fx], eax
jmp pend
default_case: ; default_case标签
; fx = 0
xor eax, eax
mov [fx], eax
jmp pend
pend: ; pend标签
invoke ExitProcess, 0 ; 调用系统函数ExitProcess退出程序
main endp ; 主过程结束
end start ; 程序结束
2.采用地址表的方法编写汇编程序实现以下的C程序的功能:
void main()
{
int grade=90;
switch(grade/10)
{
case 9:
printf("A");
break;
case 8:
printf("B");
break;
case 7:
printf("C");
break;
case 6:
printf("D");
break;
default:
printf("E");
}}
代码:
.386
.model flat
_DATA SEGMENT
score DB 80 ; 存放分数成绩,注意这里定义为一个字节,范围为0-100
grade DB 0 ; 存放等级成绩,定义为一个字节
branchTable DD aBranch ; 分支表,存放各个分支标号的地址
DD bBranch
DD cBranch
DD dBranch
DD eBranch
_DATA ENDS
_TEXT SEGMENT
main PROC ; 程序的入口点
_start::
push ebp ; 保存调用者的基址指针
mov ebp, esp ; 将栈顶指针赋给ebp
mov al, score ; 将分数值赋给al寄存器,注意将高位清零
xor ah, ah
mov bl, 10 ; 将10赋给bl寄存器
div bl ; 使用除法指令将分数除以10,商存放在al中,余数被丢弃
;得到的商在al中,范围为10-0
;若将branchTable当做一个数组来访问,则branchTable[0]存放的是转成优秀的代码的入口地址
;branchTable[1]存放的是转成优秀的代码的入口地址,因此数组下标的访问形式为[10-分数%10]
cmp al, 6 ; 比较得到的商和6的大小,判断是否小于6
jb pEnd ; 如果小于6,则跳转到pEnd结束标签
xor ebx, ebx ; 清零ebx寄存器
mov bl, 10 ; 将10赋给bl寄存器
sub bl, al ; 将10减去得到的商的值,结果存放在bl中,即10-商
mov eax, ebx ; 将得到的差值赋给eax寄存器,作为数组下标
lea ecx, branchTable ; 取分支表的地址,存放在ecx中
jmp DWORD PTR [ecx+eax*4] ; 从分支表中取出对应的地址,并跳转到该地址
aBranch:: ; aBranch分支处理代码
mov grade, 'A' ; 将等级成绩'A'赋给grade变量
jmp pEnd ; 跳转到pEnd结束标签
bBranch:: ; bBranch分支处理代码
mov grade, 'B' ; 将等级成绩'B'赋给grade变量
jmp pEnd ; 跳转到pEnd结束标签
cBranch:: ; cBranch分支处理代码
mov grade, 'C' ; 将等级成绩'C'赋给grade变量
jmp pEnd ; 跳转到pEnd结束标签
dBranch:: ; dBranch分支处理代码
mov grade, 'D' ; 将等级成绩'D'赋给grade变量
jmp pEnd ; 跳转到pEnd结束标签
eBranch:: ; eBranch分支处理代码
mov grade, 'E' ; 将等级成绩'E'赋给grade变量
jmp pEnd ; 跳转到pEnd结束标签
pEnd: ; 结束标签
xor eax, eax ; 清零eax寄存器
pop ebp ; 恢复调用者的基址指针
ret 0 ; 返回并结束程序
main ENDP
_TEXT ENDS
END _start
五、子程序
1.addxy子程序:
addxy PROC
push ebp
mov ebp,esp
mov eax,_x$[ebp]
add eax,_y$[ebp]
mov sum,eax
pop ebp
ret
addxy ENDP
2.subxy子程序:
subxy PROC
push ebp
mov ebp,esp
mov eax,_x$[ebp]
sub eax,_y$[ebp]
mov sum,eax
pop ebp
ret
subxy ENDP
3.定义符号常量:
_x$ = 8 表示定义了一个名为 _x$
的符号常量,并将其值设置为 8。这意味着在后续的代码中,可以使用 _x$
来代表数值 8。
_y$ = 12
_sum$ = 16
或者:
x equ 5
表示定义了一个名为 x
的符号常量,并将其值设置为 5。这意味着在后续的代码中,可以使用 x
来代表数值 5。
4.call指令:
call
指令用于调用子程序。
call addxy
表示调用addxy子程序
5.