王爽汇编语言综合研究-使用内存空间

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处,如图:

1

可以看到编译器对这些指令的处理为:

*(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

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的代码段

3

分析:由以上指令可以看出,编译器对于变量进行了不同的处理

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';
}

编译后如图

4

      程序中申请了两个整形变量,三个字符型变量,占用内存总共7个字节,编译器将给sp-8,申请了八个字节的空间。刚才的猜想成立。

继续深入,修改上边的代码成

main()
{
}

void f(void)
{
	int i1,i2;
	char c3,c4,c5;
	i1 = 1;
	c3 = 'a'; 
}
编译过程中产生了两个警告,如图
5 
只有i1和c3这两个使用过的变量产生了警告,其它声明后使用过的变量没有产生警告。这说明编译器只会为有用的变量申请内存空间。
反编译后如图
6 
编译器只申请了四个字节的内存空间,用来存放初始化过的i1和c3,刚才的猜想成立。

3、函数的返回值

编译以下程序并调试

int f(void);

int a,b,ab;

main()
{
	int c;
	c = f();
}

int f(void)
{
	ab = a+b;
	return ab;
}

调试

7

如图,编译器会使用ax传递返回值。(函数只允许有一个返回值)那么如果返回值是char型呢?

继续调试

char f(void);

main()
{
	char c;
	c = f();
}

char f(void)
{
	return 'a';
}
反编译后如图
8 

程序说明,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传递返回值的,查看函数返回后寄存器的状态

9

看到ax为0748h,用d命令查看这段内存(20个字节即为0748h到075Bh)

10

运行程序到main函数返回前,即为026c,再查看这段内存空间

11

可以看到写入成功

由上述分析

malloc()函数申请的内存实质是通过ax返回一个偏移地址,段地址为ds

malloc()申请的内存不是位于栈里,函数返回后相应的内存空间也不会被回收,必须由程序员调用free()来回收这段内存,编写程序时必须注意,以免造成内存泄露

posted @ 2010-04-14 01:27  石莹  阅读(1628)  评论(1编辑  收藏  举报