王爽汇编语言综合研究-使用内存空间
1、在c程序中直接访问内存空间
写入以下程序并调试
main() { *(char *)0x2000 = 'a'; *(int *)0x2000 = 0xf; *(char far *)0x20001000 = 'a'; _AX = 0x2000; *(char *)_AX = 'b'; _BX = 0x1000; *(char *)(_BX + _BX) = 'a'; *(char far *)(0x20001000 + _BX) = *(char *)_AX; }
编译连接后用debug调试,反汇编到01FA处,如图:
可以看到编译器对这些指令的处理为:
*(char *)0x2000 = 'a'; -> mov byte ptr ds:[2000h],'a'
*(int *)0x2000 = 0xf; -> mov word ptr ds:[2000h],000Fh
*(char far *)0x20001000 = 'a'; -> mov bx,2000h
mov es,bx
mov bx,1000h
mov byte ptr es:[bx],'a'
_AX = 0x2000; -> mov ax,2000h
*(char *)_AX = 'b'; -> mov bx,ax
mov byte ptr [bx],'b'
_BX = 0x1000; -> mov bx,1000h
*(char *)(_BX+_BX) = 'a'; -> add bx,bx
mov byte ptr ds:[bx],'a'
*(char far*)(0x20001000+_BX)=*(char*)_AX; ->
mov bx,ax
mov al,[bx]
xor cx,cx
add bx,1000h
adc cx,2000h
mov es,cx
mov es:[bx],al
具备了以上的基础知识,我们就可以向任意内存写入数据。可以像汇编一样通过写显存来显示字符。测试以下代码
main() { _BX = 80*12*2+40*2; *(char far *)(0xb8000000 + _BX) = 'a'; *(char far *)(0xb8000000 + _BX + 1) = 0x2; /* *(int far *)(0xb80007d0) = 0x0261; */ }
编译连接后运行,屏幕中央出现了一个绿色的a,测试成功
2、程序中的变量
编写以下程序并测试
int a1,a2,a3; void f(void); main() { int b1,b2,b3; a1 = 0xa1;a2 = 0xa2;a3 = 0xa3; b1 = 0xb1;b2 = 0xb2;b3 = 0xb3; } void f(void) { int c1,c2,c3; a1 = 0x0fa1;a2 = 0x0fa2; a3 = 0x0fa3; c1 = 0xc1; c2 = 0xc2; c3 = 0xc3; }
编译以后反汇编偏移地址为01FA的代码段
分析:由以上指令可以看出,编译器对于变量进行了不同的处理
a、对于全局变量a1,a2,a3,编译器将它保存在了ds:[01A6]开始的内存中,所有函数都可以直接访问该内存
b、对于局部变量b1,b2,b3,编译器将它保存在了栈中。反编译后的子程序指令如下:
push bp ;将bp寄存器入栈,暂存bp寄存器
mov bp,sp ;将sp寄存器暂存在bp中(sp不能用入栈方式保存)
sub sp,+06h ;给sp-6,猜想这里是在栈中留出3个整形数据所占的空间
;改变sp的值是为了保护这些临时变量,不至于在执行push操作时覆盖
mov word ptr [01A6h],0FA1 ;为全局变量赋值
mov word ptr [01A8h],0FA2
mov word ptr [01AAh],0FA3
mov word ptr [bp-06h],00C1 ;为局部变量赋值
mov word ptr [bp-04h],00C2
mov word ptr [bp-02h],00C3
mov sp,bp ;恢复sp寄存器的值(销毁临时变量)
pop bp ;恢复bp寄存器的值
ret
c、当处理到sub sp,06h指令, 编译器会根据变量所需的空间来改变sp寄存器的值(在栈中申请空间),用下面的程序验证这一猜想
main() { } void f(void) { int i1,i2; char c3,c4,c5; i1 = 1;i2 = 2; c3 = 'a'; c4 = 'b';c5 = 'c'; }
编译后如图
程序中申请了两个整形变量,三个字符型变量,占用内存总共7个字节,编译器将给sp-8,申请了八个字节的空间。刚才的猜想成立。
继续深入,修改上边的代码成
main() { } void f(void) { int i1,i2; char c3,c4,c5; i1 = 1; c3 = 'a'; }
编译过程中产生了两个警告,如图
只有i1和c3这两个使用过的变量产生了警告,其它声明后使用过的变量没有产生警告。这说明编译器只会为有用的变量申请内存空间。
反编译后如图
编译器只申请了四个字节的内存空间,用来存放初始化过的i1和c3,刚才的猜想成立。
3、函数的返回值
编译以下程序并调试
int f(void); int a,b,ab; main() { int c; c = f(); } int f(void) { ab = a+b; return ab; }
调试
如图,编译器会使用ax传递返回值。(函数只允许有一个返回值)那么如果返回值是char型呢?
继续调试
char f(void); main() { char c; c = f(); } char f(void) { return 'a'; }
反编译后如图
程序说明,8位的返回值是通过AL来传递的
3、关于malloc函数
编写以下程序
#define Buffer ((char *)*(int far *)0x02000000) main() { Buffer = (char *)malloc(20); Buffer[10] = 0; while (Buffer[10] != 8) { Buffer[Buffer[10]] = 'a'+Buffer[10]; Buffer[10]++; } }
分析程序:
a、程序通过宏定义将Buffer定义为((char *)*(int far *)0x02000000) 。Buffer可以认为存放在0200h:0000的一个指针,那么对Buffer的操作就是对该地址的操作。那么Buffer[10]就是Buffer地址加上10后的地址
b、Buffer = (char *)malloc(20); 表示使用malloc申请一个20个字节的空间,返回的指向申请的空间的指针强制转换成char型指针后赋值给Buffer,由a中分析知,也就是放置到0200h:0000开始的两个字节空间中,该地址指向向系统申请的内存首地址
c、Buffer[10] = 0; Buffer[10] != 8 与 Buffer[10]++; 这两条语句比较容易理解,由a中的分析可知,分别是对申请的内存地址+10的内存单元进行赋值为0,判断是否为8,和自加1的操作
d、while循环前将Buffer[10]置0,循环中每次判断Buffer[10]是否为8,执行完Buffer[Buffer[10]] = 'a'+Buffer[10] 后将Buffer[10]] 自加1,可见Buffer[10]] 在循环中充当计数器的角色。循环的功能是将Buffer[Buffer[10]] = 'a'+Buffer[10] 执行八次。
e、Buffer[Buffer[10]] = 'a'+Buffer[10] 表示将计数器取出(0-7)加上字符a,随着计数器变化,a的变化为(a-h)。并赋值给Buffer[Buffer[10]],表示取出计数器,根据计数器的值在Buffer寻址,那么变化为Buffer[0]到Buffer[7]。可以分析处程序的功能是对Buffer[0]到Buffer[7]的内存写入a到h
用Debug跟踪程序,反汇编到01FA的偏移地址
141A:01FA 55 PUSH BP ;进入函数,保存bp,sp寄存器
141A:01FB 8BEC MOV BP,SP;Buffer = (char *)malloc(20)
141A:01FD B81400 MOV AX,0014 ;为malloc函数传参
141A:0200 50 PUSH AX
141A:0201 E8D902 CALL 04DD ;调用malloc函数
141A:0204 59 POP CX
141A:0205 BB0002 MOV BX,0200 ;使用es:bx来定位0200h:0地址
141A:0208 8EC3 MOV ES,BX
141A:020A 33DB XOR BX,BX
141A:020C 26 ES:
141A:020D 8907 MOV [BX],AX ;将malloc的返回值赋值给0200h:0开始的两个字节中
;Buffer[10] = 0 (Buffer[10] 充当计数器)
141A:020F BB0002 MOV BX,0200 ;用es:bx来定位0200h:0内存
141A:0212 8EC3 MOV ES,BX
141A:0214 33DB XOR BX,BX ;bx置零
141A:0216 26 ES:
141A:0217 8B1F MOV BX,[BX]
141A:0219 C6470A00 MOV BYTE PTR [BX+0A],00 ;将0传入到0200h:000ah中(Buffer[10]);转跳到循环的判断处
141A:021D EB3C JMP 025B;循环体
;al = 'a'+Buffer[10]
141A:021F BB0002 MOV BX,0200 ;es:bx指向0200h:0
141A:0222 8EC3 MOV ES,BX
141A:0224 33DB XOR BX,BX
141A:0226 26 ES:
141A:0227 8B1F MOV BX,[BX] ;将0200h:0中的值放到bx中
141A:0229 8A470A MOV AL,[BX+0A] ;将0200h:000Ah的值放到al中
141A:022C 0461 ADD AL,61 ;al+'a';Buffer[Buffer[10]] = al
141A:022E BB0002 MOV BX,0200 ;es:bx指向0200h:0
141A:0231 8EC3 MOV ES,BX
141A:0233 33DB XOR BX,BX
141A:0235 26 ES:
141A:0236 8B1F MOV BX,[BX] ;将0200h:0数据存放到bx中
141A:0238 50 PUSH AX
141A:0239 53 PUSH BX
141A:023A BB0002 MOV BX,0200 ;es:bx指向0200h:0
141A:023D 8EC3 MOV ES,BX
141A:023F 33DB XOR BX,BX
141A:0241 26 ES:
141A:0242 8B1F MOV BX,[BX] ;将0200h:0中的值放到bx中
141A:0244 8A470A MOV AL,[BX+0A] ;将0200h:000Ah的值放到al中(al中存放计数器)
141A:0247 98 CBW ;al字节转换为字
141A:0248 5B POP BX ;恢复bx值,恢复到0200:0单元内的值,也就是指向申请的缓冲区指针
141A:0249 03D8 ADD BX,AX ;ax值放到bx中,0200:0的值加到bx中
;bx中存放的是指向申请的缓冲区的指针+计数器的值
141A:024B 58 POP AX
141A:024C 8807 MOV [BX],AL ;al的值放到[bx]中,也就是申请的内存首地址+计数器的值;计数器自增1
141A:024E BB0002 MOV BX,0200
141A:0251 8EC3 MOV ES,BX
141A:0253 33DB XOR BX,BX
141A:0255 26 ES:
141A:0256 8B1F MOV BX,[BX]
141A:0258 FE470A INC BYTE PTR [BX+0A] ;Buffer[10]++;判断部分
;Buffer[10] != 8
141A:025B BB0002 MOV BX,0200
141A:025E 8EC3 MOV ES,BX
141A:0260 33DB XOR BX,BX
141A:0262 26 ES:
141A:0263 8B1F MOV BX,[BX]
141A:0265 807F0A08 CMP BYTE PTR [BX+0A],08 ;比较Buffer[10]和8的大小
141A:0269 75B4 JNZ 021F ;不是0则转跳到循环体
141A:026B 5D POP BP ;否则函数返回
141A:026C C3 RET
141A:026D C3 RET
malloc 函数申请的内存在什么地方
因为c中函数是通过ax传递返回值的,查看函数返回后寄存器的状态
看到ax为0748h,用d命令查看这段内存(20个字节即为0748h到075Bh)
运行程序到main函数返回前,即为026c,再查看这段内存空间
可以看到写入成功
由上述分析
malloc()函数申请的内存实质是通过ax返回一个偏移地址,段地址为ds
malloc()申请的内存不是位于栈里,函数返回后相应的内存空间也不会被回收,必须由程序员调用free()来回收这段内存,编写程序时必须注意,以免造成内存泄露