Linux中为什么要随机linux打印函数堆栈栈的起始地址

C/C++(67)
转载请注明出处:http://blog.csdn.net/wangxiaolong_china
对于一个进程,其空间分布如下图所示:
C程序一般分为:
1.程序段:程序段为程序代码在内存中的映射.一个程序可以在内存中多有个副本.
2.初始化过的数据:在程序运行值初已经对变量进行初始化的
3.未初始化过的数据:在程序运行初未对变量进行初始化的数据
4.堆(stack):存储局部,临时变量,在程序块开始时自动分配内存,结束时自动释放内存.存储函数的返回指针.
5.栈(heap):存储动态内存分配,需要程序员手工分配,手工释放.
多任务操作系统中的每一个进程都运行在一个属于它自己的内存沙盘中,这个沙盘就是虚拟地址空间(virtual address space),在32位模式下,它总是一个4GB的内存地址块。这些虚拟地址通过页表(page table)映射到物理内存,页表由操作系统维护并被处理器引用。每个进程都拥有一套属于它自己的页表,但是还有一个隐情,只要虚拟地址被使能,那么它将会作用于这台机器上运行的所有软件,包括内核本身,因此,有一部分虚拟地址必须保留给内核使用。进程内存空间分布如下图所示:
但是这并不意味着内核使用了这么多的物理内存,仅表示它可以支配这么大的地址空间。可根据内核需要,将其映射到物理内存。内核空间在页表中拥有较高的特权级(ring2或以下),因此,只要用户态的程序试图访问这些页,就会导致一个页错误(page fault)。在Linux中,内核空间是持续存在的,并且在所有进程中都映射到同样的物理内存,内核代码和数据总是可寻址的,随时准备处理中断和系统调用。与之相反,用户模式地址空间的映射随着进程切换的发生而不断的变化,如下图所示:
上图中蓝色区域表示映射到物理内存的虚拟地址,而白色区域表示未映射的部分。可以看出,Firefox使用了相当多的虚拟地址空间,因为它占用内存较多。
Linux进程标准的内存段布局,如下图所示,地址空间中的各个条带对应于不同的内存段(memory segment),如:堆、栈之类的。记住,这些段只是简单的虚拟内存地址空间范围,与Intel处理器的段没有任何关系。
几乎每个进程的虚拟地址空间中各段的分布都与上图完全一致。
这就给远程发掘程序漏洞的人打开了方便之门。一个发掘过程往往需要引用绝对内存地址:栈地址,库函数地址等。远程攻击者必须依赖地址空间分布的一致性,来探索出这些地址。如果让他们猜个正着,那么有人就会被整了。因此,地址空间的随机排布方式便逐渐流行起来,Linux通过对栈、内存映射段、堆的起始地址加上随机的偏移量来打乱布局。但不幸的是,32位地址空间相当紧凑,这给随机化所留下的空间不大,削弱了这种技巧的效果。
进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉。由于栈中数据严格的遵守LIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈中的内容,只需要一个简单的指针指向栈的顶端即可,因此压栈(pushing)和退栈(popping)过程非常迅速、准确。进程中的每一个线程都有属于自己的栈。
通过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),而被Linux的expand_stack()处理,它会调用acct_stack_growth()来检查是否还有合适的地方用于栈的增长。如果栈的大小低于RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会栈溢出(stack
overflow),程序收到一个段错误(segmentation fault)。
动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误。
在栈的下方,是我们的内存映射段。内核将文件的内容直接映射到内存。任何应用程序都可以通过Linux的mmap()系统调用或者Windows的CreateFileMapping()/MapViewOfFile()请求这种映射。内存映射是一种方便高效的文件I/O方式,所以它被用来加载动态库。创建一个不对应于任何文件的匿名内存映射也是可能的,此方法用于存放程序的数据。在Linux中,如果你通过malloc()请求一大块内存,C运行库将会创建这样一个匿名映射而不是使用堆内存。“大块”意味着比MMAP_THRESHOLD还大,缺省128KB,可以通过mallocp()调整。
接下来的一块内存空间是堆。与栈一样,堆用于运行时内存分配;但不同的是,堆用于存储那些生存期与函数调用无关的数据。大部分语言都提供了堆管理功能。在C语言中,堆分配的接口是malloc()函数。如果堆中有足够的空间来满足内存请求,它就可以被语言运行时库处理而不需要内核参与,否则,堆会被扩大,通过brk()系统调用来分配请求所需的内存块。堆管理是很复杂的,需要精细的算法来应付我们程序中杂乱的分配模式,优化速度和内存使用效率。处理一个堆请求所需的时间会大幅度的变动。实时系统通过特殊目的分配器来解决这个问题。堆在分配过程中可能会变得零零碎碎,如下图所示:
最后,我们看看底部的内存段:BSS,数据段,代码段。
在C语言中,BSS和数据段保存的都是静态(全局)变量的内容。区别在于BSS保存的是未被初始化的静态变量内容,他们的值不是直接在程序的源码中设定的。BSS内存区域是匿名的,它不映射到任何文件。如果你写static intcntActiveUsers,则cntActiveUsers的内容就会保存到BSS中去。而数据段则保存在源代码中已经初始化的静态变量的内容。数据段不是匿名的,它映射了一部分的程序二进制镜像,也就是源代码中指定了初始值的静态变量。所以,如果你写static
int cntActiveUsers=10,则cntActiveUsers的内容就保存在了数据段中,而且初始值是10。尽管数据段映射了一个文件,但它是一个私有内存映射,这意味着更改此处的内存不会影响被映射的文件。
你可以通过阅读文件/proc/pid_of_process/maps来检验一个Linux进程中的内存区域。记住:一个段可能包含许多区域。比如,每个内存映射文件在mmap段中都有属于自己的区域,动态库拥有类似BSS和数据段的额外区域。有时人们提到“数据段”,指的是全部的数据段+BSS+堆。
你还可以通过nm和objdump命令来察看二进制镜像,打印其中的符号,它们的地址,段等信息。最后需要指出的是,前文描述的虚拟地址布局在linux中是一种“灵活布局”,而且作为默认方式已经有些年头了,它假设我们有值RLIMT_STACK。但是,当没有该值得限制时,Linux退回到“经典布局”,如下图所示:
C语言程序实例分析如下所示:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:102722次
积分:2784
积分:2784
排名:第9722名
原创:127篇
转载:332篇
(6)(31)(19)(105)(57)(33)(13)(39)(18)(4)(26)(9)(1)(1)(2)(2)(1)(3)(3)(5)(3)(8)(9)(14)(5)(14)(16)(21)(3)(1)(1)(2)(1)(2)访问本页面,您的浏览器需要支持JavaScriptlinux(12)
1. linux版本
我在笔记本上安装了Fedora Core,在台式机上装了ubuntu。最先在使用Fedora Core是历史原因,喜欢这个界面多一点。后来发现ubuntu有个wubi安装方法,不用自己做启动盘,也不用自己去修改C盘根目录的loader,太方便了。估计以后新装linux,优先考虑ubuntu了。
2. &C/C++开发环境
Eclipse太大了。CodeBlock稍微好点。如果只是一般的控制台开发,就用Codelite吧,Codelite差不多是CodeBlock的轻量级版本。vi适合高手使用,可惜我不是。我一般只做基于C/C++的控制台开发,优先使用Codelite,其次CodeBlock。其实makefile文件还是自己写,主要借助IDE的符号定义引用的链接,以及自动补全功能。
3. 版本控制
我用了google code的代码托管,主要使用svn (git没有试过)。其实svn也用得很生疏,以至于,每次提交一个文件,其版本号就是是上次提交一个文件(该文件根本就不是正提交的这个文件)的版本号加1。这样,版本号增长的速度太快,基本上就没用了。
google code的使用比source forge简单。
4. 其它软件
google浏览器。发行版本中包括了firefox,但是我用惯了google浏览器了,尤其是依赖google浏览器的书签同步功能。
stardict。估计是linux下面最好用的英汉翻译字典软件了。
dropbox。一个本地文件夹,里面的所有东西可以同步到免费的服务器上面。用来干什么呢?有兴趣的话,自己去挖掘吧。
amsn。一个MSN的linux版本,特点是稳定。linux下首选。
meld diff。文本比较工具。用diff命令?我还是觉得这个图形界面的meld diff比较好用,醒目。
Alarm Clock。一个小闹钟软件。谈不上有多好,只不过够我用了。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:113303次
积分:1557
积分:1557
排名:第19883名
原创:40篇
转载:15篇
评论:20条
(1)(1)(1)(2)(1)(6)(3)(1)(1)(1)(1)(3)(2)(1)(5)(14)(6)(10)(2)(2)温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!&&|&&
LOFTER精选
网易考拉推荐
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
ore dump 一般是在segmentation fault(段错误)的情况下产生的文件,需要通过ulimit来设置才会得到的。调试的话输入:&gdb filename corefilename就是产生core文件的可执行文件,croe就是产生的core文件名查看栈信息—————---当程序被停住了,你需要做的第一件事就是查看程序是在哪里停住的。当你的程序调用了一个函数,函数的地址,函数参数,函数内的局部变量都会被压入“栈”(Stack)中。你可以用GDB命令来查看当前的栈中的信息。下面是一些查看函数调用栈信息的GDB命令(先通过gdb filename core进入gdb命令行):backtrace或者bt打印当前的函数调用栈的所有信息。如:(gdb) bt#0 func (n=250) at tst.c:6#1 0x in main (argc=1, argv=0xbffff674) at tst.c:30#2 0x400409ed in __libc_start_main () from /lib/libc.so.6从上可以看出函数的调用栈信息:__libc_start_main --& main()--& func()backtracebtn是一个正整数,表示只打印栈顶上n层的栈信息。backtrace &-n&bt &-n&-n表一个负整数,表示只打印栈底下n层的栈信息。如果你要查看某一层的信息,你需要在切换当前的栈,一般来说,程序停止时,最顶层的栈就是当前栈,如果你要查看栈下面层的详细信息,首先要做的是切换当前栈。framefn是一个从0开始的整数,是栈中的层编号。比如:frame 0,表示栈顶,frame 1,表示栈的第二层。up表示向栈的上面移动n层,可以不打n,表示向上移动一层。down表示向栈的下面移动n层,可以不打n,表示向下移动一层。上面的命令,都会打印出移动到的栈层的信息。如果你不想让其打出信息。你可以使用这三个命令:select-frame 对应于 frame 命令。up-silently 对应于 up 命令。down-silently 对应于 down 命令。查看当前栈层的信息,你可以用以下GDB命令:frame 或 f会打印出这些信息:栈的层编号,当前的函数名,函数参数值,函数所在文件及行号,函数执行到的语句。info frameinfo f这个命令会打印出更为详细的当前栈层的信息,只不过,大多数都是运行时的内内地址。比如:函数地址,调用函数的地址,被调用函数的地址,目前的函数是由什么样的程序语言写成的、函数参数地址及值、局部变量的地址等等。如:(gdb) info fStack level 0, frame at 0xbffff5d4:eip = 0x804845d in func (tst.c:6); saved eip 0x8048524called by frame at 0xbffff60csource language c.Arglist at 0xbffff5d4, args: n=250Locals at 0xbffff5d4, Previous frame's sp is 0x0Saved registers:ebp at 0xbffff5d4, eip at 0xbffff5d8info args打印出当前函数的参数名及其值。info locals打印出当前函数中所有局部变量及其值。info catch打印出当前的函数中的异常处理信息。转自http://blog.csdn.net/suxinpingtao51/article/details/
用微信&&“扫一扫”
将文章分享到朋友圈。
用易信&&“扫一扫”
将文章分享到朋友圈。
历史上的今天
loftPermalink:'',
id:'fks_',
blogTitle:'Linux中gdb 调试堆栈',
blogAbstract:'ore dump 一般是在segmentation fault(段错误)的情况下产生的文件,需要通过ulimit来设置才会得到的。调试的话输入:&gdb filename corefilename就是产生core文件的可执行文件,croe就是产生的core文件名',
blogTag:'',
blogUrl:'blog/static/',
isPublished:1,
istop:false,
modifyTime:0,
publishTime:6,
permalink:'blog/static/',
commentCount:0,
mainCommentCount:0,
recommendCount:0,
bsrk:-100,
publisherId:0,
recomBlogHome:false,
currentRecomBlog:false,
attachmentsFileIds:[],
groupInfo:{},
friendstatus:'none',
followstatus:'unFollow',
pubSucc:'',
visitorProvince:'',
visitorCity:'',
visitorNewUser:false,
postAddInfo:{},
mset:'000',
remindgoodnightblog:false,
isBlackVisitor:false,
isShowYodaoAd:false,
hostIntro:'',
hmcon:'0',
selfRecomBlogCount:'0',
lofter_single:''
{list a as x}
{if x.moveFrom=='wap'}
{elseif x.moveFrom=='iphone'}
{elseif x.moveFrom=='android'}
{elseif x.moveFrom=='mobile'}
${a.selfIntro|escape}{if great260}${suplement}{/if}
{list a as x}
推荐过这篇日志的人:
{list a as x}
{if !!b&&b.length>0}
他们还推荐了:
{list b as y}
转载记录:
{list d as x}
{list a as x}
{list a as x}
{list a as x}
{list a as x}
{if x_index>4}{break}{/if}
${fn2(x.publishTime,'yyyy-MM-dd HH:mm:ss')}
{list a as x}
{if !!(blogDetail.preBlogPermalink)}
{if !!(blogDetail.nextBlogPermalink)}
{list a as x}
{if defined('newslist')&&newslist.length>0}
{list newslist as x}
{if x_index>7}{break}{/if}
{list a as x}
{var first_option =}
{list x.voteDetailList as voteToOption}
{if voteToOption==1}
{if first_option==false},{/if}&&“${b[voteToOption_index]}”&&
{if (x.role!="-1") },“我是${c[x.role]}”&&{/if}
&&&&&&&&${fn1(x.voteTime)}
{if x.userName==''}{/if}
网易公司版权所有&&
{list x.l as y}
{if defined('wl')}
{list wl as x}{/list}}

我要回帖

更多关于 linux 函数栈大小 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信