免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2805 | 回复: 6
打印 上一主题 下一主题

[C] exit return 在程序结束时对内存处理的问题 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2013-12-02 15:09 |只看该作者 |倒序浏览
最近碰到一奇怪的问题,当一个程序return的时候,会出现段错误,但是在exit时却没有,求指教
看程序:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int cpy(char *buf,int *length){

    char *string="123456789123456789123456789123456789";
    memcpy(buf,string,37);
    return -1;
}
int main(){
    char buf[10]={0};
    int length = 10;
    cpy(buf,&length);
    printf("buf=%s\n",buf);
    return -1;  //此处用 exit(-1); 则不会出错
}
知道在memcpy的时候向一个小空间内写了过大的串,但是为什么在exit时不会出错 但是在return时会出错呢?

论坛徽章:
0
2 [报告]
发表于 2013-12-02 15:32 |只看该作者
exit是一个陷阱,该函数不会返回并且会造成进程终止,而return会造成出栈操作,你的越界行为将return返回后继续执行代码的地址给冲掉了(因为栈是从高地址向低地址分配的,char *string的36字节后正好是printf("buf=%s\n",buf)这句的地址),CPU代码保护模块会检查到在非代码段执行代码,会拒绝执行造成段错误

论坛徽章:
0
3 [报告]
发表于 2013-12-02 15:43 |只看该作者
为啥在char *string 36字节后面正好是 printf这句话呢?回复 2# seamine_cu


   

论坛徽章:
0
4 [报告]
发表于 2013-12-02 15:44 |只看该作者
为啥在char *string 36字节后面正好是 printf这句话呢?回复 2# seamine_cu


   

论坛徽章:
7
巳蛇
日期:2014-04-10 08:54:57白羊座
日期:2014-04-22 20:06:262015年亚洲杯之沙特阿拉伯
日期:2015-02-10 14:18:532015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之吉达阿赫利
日期:2015-06-02 11:34:112015亚冠之武里南联
日期:2015-06-24 12:13:082015亚冠之阿尔纳斯尔
日期:2015-08-03 09:08:25
5 [报告]
发表于 2013-12-02 20:17 |只看该作者
回复 3# liuyt1234


    那是因为二楼的解释前半句是对的,(在我的理解看来)后半句是错的(就是关于printf这句)。其实出问题与拷贝了些什么是没有太大的关系的,虽然出问题的是cpy()这个函数,但是导致出段错误的是cpy()里memcpy()的目标而不是源,也就是buf这个变量,这个变量是在main()里传来的,所以我们关心的问题是main()函数,以下是我们使用return时候main()函数的反汇编结果:
  1. 00000000004005d7 <main>:
  2. int main() {
  3.   4005d7:        55                           push   %rbp
  4.   4005d8:        48 89 e5                     mov    %rsp,%rbp
  5.   4005db:        48 83 ec 20                  sub    $0x20,%rsp
  6.     char buf[10] = {0};
  7.   4005df:        48 c7 45 f0 00 00 00         movq   $0x0,-0x10(%rbp)
  8.   4005e6:        00
  9.   4005e7:        66 c7 45 f8 00 00            movw   $0x0,-0x8(%rbp)
  10.     int length = 10;
  11.   4005ed:        c7 45 ec 0a 00 00 00         movl   $0xa,-0x14(%rbp)
  12.     cpy(buf, &length);
  13.   4005f4:        48 8d 55 ec                  lea    -0x14(%rbp),%rdx
  14.   4005f8:        48 8d 45 f0                  lea    -0x10(%rbp),%rax
  15.   4005fc:        48 89 d6                     mov    %rdx,%rsi
  16.   4005ff:        48 89 c7                     mov    %rax,%rdi
  17.   400602:        e8 99 ff ff ff               callq  4005a0 <cpy>
  18.     printf("buf=%s\n", buf);
  19.   400607:        48 8d 45 f0                  lea    -0x10(%rbp),%rax
  20.   40060b:        48 89 c6                     mov    %rax,%rsi
  21.   40060e:        bf e5 06 40 00               mov    $0x4006e5,%edi
  22.   400613:        b8 00 00 00 00               mov    $0x0,%eax
  23.   400618:        e8 53 fe ff ff               callq  400470 <printf@plt>
  24.     return -1;
  25.   40061d:        b8 ff ff ff ff               mov    $0xffffffff,%eax
  26. }
  27.   400622:        c9                           leaveq
  28.   400623:        c3                           retq   
  29.   400624:        66 2e 0f 1f 84 00 00         nopw   %cs:0x0(%rax,%rax,1)
  30.   40062b:        00 00 00
  31.   40062e:        66 90                        xchg   %ax,%ax
复制代码
以下是我们使用exit()时的反汇编结果:
  1. 0000000000400617 <main>:
  2.   400617:        55                           push   %rbp
  3.   400618:        48 89 e5                     mov    %rsp,%rbp
  4.   40061b:        48 83 ec 20                  sub    $0x20,%rsp
  5.   40061f:        48 c7 45 f0 00 00 00         movq   $0x0,-0x10(%rbp)
  6.   400626:        00
  7.   400627:        66 c7 45 f8 00 00            movw   $0x0,-0x8(%rbp)
  8.   40062d:        c7 45 ec 0a 00 00 00         movl   $0xa,-0x14(%rbp)
  9.   400634:        48 8d 55 ec                  lea    -0x14(%rbp),%rdx
  10.   400638:        48 8d 45 f0                  lea    -0x10(%rbp),%rax
  11.   40063c:        48 89 d6                     mov    %rdx,%rsi
  12.   40063f:        48 89 c7                     mov    %rax,%rdi
  13.   400642:        e8 99 ff ff ff               callq  4005e0 <cpy>
  14.   400647:        48 8d 45 f0                  lea    -0x10(%rbp),%rax
  15.   40064b:        48 89 c6                     mov    %rax,%rsi
  16.   40064e:        bf 25 07 40 00               mov    $0x400725,%edi
  17.   400653:        b8 00 00 00 00               mov    $0x0,%eax
  18.   400658:        e8 43 fe ff ff               callq  4004a0 <printf@plt>
  19.   40065d:        bf ff ff ff ff               mov    $0xffffffff,%edi
  20.   400662:        e8 79 fe ff ff               callq  4004e0 <exit@plt>
  21.   400667:        66 0f 1f 84 00 00 00         nopw   0x0(%rax,%rax,1)
  22.   40066e:        00 00
复制代码
你会看到,printf以前的代码都是一样的,我们的buf变量被分配到了栈的 -0x10处,也就是虽然你申明buf只有10个元素,但是(在我的机器上)你给他拷贝16个元素都不会出现段错误,但是一旦超过16个元素(远没有到36个字符这里),栈就要溢出了,但是栈溢出也不一定会出段错误,push %rbp 这句就是把原来的栈的地址保存在栈上,栈溢出的结果是这个保存在这儿的原来的栈就被冲调了,而我们调用return的时候产生的retq会试图返回到我们原来保存的栈的地址,但是这个地址用来自string里的内容给替换了(之前就说过这个内容是什么我们根本就不用关心,不过因为string被定义为"123456789123456789123456789123456789",去掉前16个字符,是"89123456789123456789",我的系统是64位,64位系统会使用8位的地址,所以替换以后的地址是"89123456",也就是3906085646303836472(前面8个字节对应的64位整数),这个数不知道指到哪儿去了,光对这个程序来说,这个地址不在这个程序的进程空间内,因此肯定是没有权限访问的,于是就段错误了),而另一边,调用exit的时候产生的是 callq 指令,我们知道exit()最终会直接退出系统,在这之前它都不会涉及任何无权访问的内存,因此不会出段错误。

论坛徽章:
0
6 [报告]
发表于 2013-12-03 11:29 |只看该作者
基本是对的,不过进程的地址都是独立的,所以不存在 不在这个程序的进程空间内 的情况。但是内存段是有可执行和不可执行区分的。如果地址在不可执行范围内,就会出段错误

论坛徽章:
0
7 [报告]
发表于 2013-12-03 19:24 |只看该作者
说的好明白 谢谢啦
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP