位置: 编程技术 - 正文

寒假捉虫记——从一段损坏的调用栈开始折腾(捉虫趣事作文200字)

编辑:rootadmin

推荐整理分享寒假捉虫记——从一段损坏的调用栈开始折腾(捉虫趣事作文200字),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:捉虫活动,捉虫趣事的作文350字四年级,捉虫记作文300字,捉虫趣事作文200字,捉虫小计,暑假捉虫子作文,捉虫记作文300字,暑假捉虫子作文,内容如对您有帮助,希望把文章链接给更多的朋友!

  放假在家,继续调试《家园》。目前的进度是MinGW上的编译链接都已通过,游戏程序也已经可以跑起来并进入主菜单界面,但加载关卡之后就会闪退。这让我想起了以前上中学时玩盗版游戏的日子。那个年代的单机游戏估计大多是用C/C&#;&#;写的,一个不小心的内存操作就会让进程崩掉;而且那个年代的操作系统没现在稳定,可能破解技术也不够先进,从电脑城里买来的五六块钱的盗版游戏质量参差不齐。很多游戏跑着跑着就闪退,有的甚至连打都打不开,让人甚为恼火。如今源代码在手,并且我也是程序员了,可以对闪退的原因一探究竟,再也不用怕。

  不过让人失望的是,用MinGW构建出的程序不会像Linux程序那样在崩溃时吐核。还好这回的闪退是可以必现的,所以就在gdb中运行程序,看看它崩在什么地方。

  结果程序如预期崩溃后,调用栈成了下面这个样子:

Program received signal SIGSEGV, Segmentation fault.0x0ddcf5c0 in ?? ()(gdb) bt#0 0x0ddcf5c0 in ?? ()#1 0xabababab in ?? ()#2 0xabab in ?? ()#3 0x in ?? ()(gdb)

  看样子调用栈已经坏掉了。记得以前在老东家遇到过这种损坏的调用栈,但后来很喜感地发现原来是机器的内存坏了。我相信我家电脑还没有到如此风烛残年的地步。

  在StackOverflow上搜到一篇帖子: set $pc = *(void **)$esp(gdb) set $esp = $esp &#; 4

  可是我检查了一下esp寄存器指向的内存:

(gdb) p $esp$1 = (void *) 0xfa(gdb) x $esp0xfa: 0xf(gdb) x/i 0x7f 0x7f: Cannot access memory at address 0x7f(gdb)

  0x7f显然不可能是一个合法的指令地址。看来我落到剩下那1%的区间里了=A=。

  把这事情分享到朋友圈里后,主程建议我让程序链接tcmalloc试试,看看能否让程序在应用代码进行非法内存操作时就崩溃,兴许那时调用栈还没损坏。可是试过后情况并无改变。不过这倒是提醒了我,以后不妨让自己的程序都链接tcmalloc,这样可以让很多问题都提前暴露。顺便写下,我的tcmalloc链接选项是-L/local/lib -ltcmalloc_minimal -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-free。

  无奈,最后还是通过打日志和单步调试的方法,通过应用代码本身的逻辑定位到了具体崩溃位置。原来程序崩在了一个OpenGL接口——glDrawElements的调用。多留日志和熟练掌握项目代码逻辑真是重要啊。

  如果是在工作中,我的排查工作一般在这一步也就该结束了。因为我从未接触过OpenGL,所以此时我应该让有OpenGL经验的同事来帮忙处理。不过这回不是在工作而是在玩耍,所以我打算满足下自己用牛刀杀鸡、用导弹打蚊子的癖好,好好研究一番,一是看看能否在gdb中恢复出调用栈,二是研究下glDrawElements这个调用为啥会崩溃,趁机接触下OpenGL。

调用栈的恢复

  先看看崩溃的直接原因是什么,看看崩溃时执行的汇编指令是什么:

(gdb) x/i $pc=> 0xddcf5c0: mov (%esi),%edi

  看来esi寄存器里存了一个非法内存地址。

(gdb) p/x $esi$3 = 0xfeee4(gdb) p *(void **)$esiCannot access memory at address 0xfd4(gdb)

  果然如此。

  再看看这之前还执行了什么指令。

(gdb) x/i $pc- 0xcf7a6: add %al,(%eax) 0xcf7a8: add %al,(%eax) 0xcf7aa: add %al,(%eax) 0xcf7ac: add %al,(%eax) 0xcf7ae: add %al,(%eax) 0xcf7b0: add %al,(%eax) 0xcf7b2: add %al,(%eax) 0xcf7b4: add %al,(%eax) 0xcf7b6: add %al,(%eax) 0xcf7b8: cmp $0xff,%bh 0xcf7bb: incl 0xf7(%eax) 0xcf7c1: mov %esp,%ebp 0xcf7c3: push %ebx 0xcf7c4: push %esi 0xcf7c5: push %edi 0xcf7c6: mov 0x8(%ebp),%ebx 0xcf7c9: mov 0xc(%ebp),%eax 0xcf7cc: mov 0x(%ebp),%ebp 0xcf7cf: mov %ebp,%edi 0xcf7d1: shl $0x,%edi 0xcf7d4: lea 0x(%edi),%esi 0xcf7da: mov %esi,(%eax) 0xcf7dc: add $0x4,%eax 0xcf7df: mov 0x1c(%esp),%edx 0xcf7e3: lea (%edx,%ebp,2),%ebp 0xcf7e6: mov %ebp,0x(%esp) 0xcf7ea: movzwl (%edx),%ecx 0xcf7ed: add $0x2,%edx 0xcf7f0: mov 0xc,%esi 0xcf7f6: mov 0x4(%esi),%esi 0xcf7f9: mov %ecx,%edi 0xcf7fb: shl $0x4,%edi 0xcf7fe: add %edi,%esi=> 0xcf: mov (%esi),%edi 0xcf: mov 0x4(%esi),%ebp 0xcf: mov %edi,(%eax) 0xcf: mov %ebp,0x4(%eax) 0xcfa: mov 0x8(%esi),%edi 0xcfd: mov %edi,0x8(%eax) 0xcf: mov 0xc,%esi(gdb)

  看样子在0xcf7bb附近很可能有一个函数头。函数开头通常由两条汇编指令组成——第一条指令保存当前栈帧的帧底地址,第二条指令将当前的栈顶指为栈帧底,开启新栈帧:

push %ebpmove %esp %ebp

  于是从0xcf7bc开始,一路用x命令检查:

(gdb) x/i 0xcf7bc...(gdb) x/i 0xcf7bd...(gdb) x/i 0xcf7be 0xcf7be: add %al,(%eax) 0xcf7c0: push %ebp 0xcf7c1: mov %esp,%ebp 0xcf7c3: push %ebx 0xcf7c4: push %esi 0xcf7c5: push %edi 0xcf7c6: mov 0x8(%ebp),%ebx 0xcf7c9: mov 0xc(%ebp),%eax 0xcf7cc: mov 0x(%ebp),%ebp 0xcf7cf: mov %ebp,%edi 0xcf7d1: shl $0x,%edi 0xcf7d4: lea 0x(%edi),%esi 0xcf7da: mov %esi,(%eax) 0xcf7dc: add $0x4,%eax 0xcf7df: mov 0x1c(%esp),%edx 0xcf7e3: lea (%edx,%ebp,2),%ebp 0xcf7e6: mov %ebp,0x(%esp) 0xcf7ea: movzwl (%edx),%ecx 0xcf7ed: add $0x2,%edx 0xcf7f0: mov 0xc,%esi 0xcf7f6: mov 0x4(%esi),%esi 0xcf7f9: mov %ecx,%edi 0xcf7fb: shl $0x4,%edi 0xcf7fe: add %edi,%esi=> 0xcf: mov (%esi),%edi 0xcf: mov 0x4(%esi),%ebp 0xcf: mov %edi,(%eax) 0xcf: mov %ebp,0x4(%eax) 0xcfa: mov 0x8(%esi),%edi 0xcfd: mov %edi,0x8(%eax) 0xcf: mov 0xc,%esi 0xcf: mov 0x(%esi),%esi 0xcf: mov %ecx,%edi 0xcfb: shl $0x4,%edi 0xcfe: add %edi,%esi 0xcf: mov (%esi),%edi 0xcf: mov %edi,0xc(%eax) 0xcf: add $0x,%eax 0xcf: cmp 0x(%esp),%edx 0xcfc: jne 0xcf7ea(gdb)

  果真如此。地址0xcf7c0和0xcf7c1这两条指令就是典型的函数开头:

0xcf7c0: push %ebp0xcf7c1: mov %esp,%ebp

  从这之后到崩溃处0xcf,有两处修改ebp的指令:

0xcf7cc: mov 0x(%ebp),%ebp...0xcf7e3: lea (%edx,%ebp,2),%ebp

  因此在0xcf: mov (%esi),%edi 崩溃的时候,寄存器中记录的就是错误的栈帧。也就是说,在gdb中查看的调用栈不正常是因为ebp被篡改了。从这两条指令还可以看出,崩溃时ebp的&#;取决于传入函数的参数。

  在指令0xcf7c1: mov %esp,%ebp刚执行之后,esp的&#;和ebp的&#;是相等的。在这之后直到崩溃前,只有三条压栈指令(0xcf7c6至0xcf7cc)会修改esp。它们会使esp自减3个word,即3*4=字节。分析到这里,就有办法恢复ebp的&#;了:

(gdb) set $ebp = $esp &#;

  通过检查esp指向的内存段可以进一步确认:

(gdb) x/8x $esp0xfa: 0xf 0x0c4f 0x0cbb 0xfaxfa: 0xace2 0x0c4f 0x0cd3e 0x0cbb(gdb)

  可见栈在内存段0xfa??附近,0xfa想必就是上一个栈帧的帧底,0xace2就是函数调用前的指令地址,也就是函数的返回地址。

寒假捉虫记——从一段损坏的调用栈开始折腾(捉虫趣事作文200字)

  现在可以看到正确的调用栈了:

(gdb) bt#0 0x0cf in ?? ()#1 0xace2 in nvoglv!DrvPresentBuffers () from C:Windowssystemnvoglv.dll#2 0xaed6 in nvoglv!DrvPresentBuffers () from C:Windowssystemnvoglv.dll#3 0xa in nvoglv!DrvPresentBuffers () from C:Windowssystemnvoglv.dll#4 0xf in ?? () from C:Windowssystemnvoglv.dll#5 0xdd in btgRender () at ../../../src/Game/BTG.c:#6 0xd0c in rndBackgroundRender (radius=, camera=0xc0 <universe&#;>, bDrawStars=1) at ../../../src/SDL/render.c:#7 0xc in rndMainViewRenderFunction (camera=0xc0 <universe&#;>) at ../../../src/SDL/render.c:#8 0xd in mrRegionDraw (reg=0xaa0a0) at ../../../src/SDL/mainrgn.c:#9 0xf in regFunctionsDraw () at ../../../src/Game/Region.c:# 0xca in rndRenderTask (taskContextPtr=0xa) at ../../../src/SDL/render.c:# 0xb0bc7 in taskExecuteAllPending (ticks=4) at ../../../src/Game/Task.c:# 0xbb in utyTasksDispatch () at ../../../src/SDL/utility.c:# 0xdf6 in HWSDL_main (argc=4, argv=0xd8) at ../../../src/SDL/main.c:# 0xe0 in main (argc=4, argv=0xd8) at ../../src/homeworld.c:(gdb)

  崩溃的应用程序代码(BTG.c:)与通过日志和单步调试分析出的结果完全一致。

  看样子崩在了OpenGL内部。既然是崩在了glDrawElements里面,那就要研究下glDrawElements的使用,想必是API使用不当。

glDrawElements的使用

  从glDrawElements的官方文档来看,这个接口的作用是批量绘制多个基本图元(如点、线、三角形和多边形)。不过这个接口并没有参数可以直接传入顶点数据,那个indices参数只是顶点数据的索引而已。这一点让我花了很长时间琢磨。用glDrawElements作关键词搜了很多文章,基本都能看懂,但还是不知道该如何从零开始用起来。

  还是先把这函数放一放,从基本的OpenGL程序开始吧,先写个Helloworld。从网上的文章得知现代OpenGL和过去的OpenGL 1.x在用法上&#;乎有很大不同;而《家园》是很老的游戏了,早在年就已经发行,即便是HomeworldSDL的代码也非常老,更新很缓慢。所以我恐怕还得学习老式OpenGL的用法。幸运的是从这里搜到了Tutorial:

  MinGW上的编译命令:gcc -o window.exe window.c -mwindows -lopengl -lglu

  运行结果如下:

  程序中关键的绘制代码就是Render函数中从glBegin到glEnd的部分。

  这段代码绘制了四个顶点,从而绘制出一个正方形。这种用法是Tutorial 3中提到的Immediate Mode。看来如果要用glDrawElements,关键就是将这段代码替换为glDrawElements。

  现在关键是要知道如何将顶点数据传给glDrawElements。从HomeworldSDL的代码和glDrawElements相关的资料中,我注意到两个概念:Vertex Array Object(VAO)和Vertex Buffer Object(VBO)。Tutorial 3的末尾就简略地提及了这两个概念。简要地说,VAO就是我们要传给glDrawElements的顶点数据,这些数据是以数组形式存放的。glDrawElements被调用时可以从内存里拿这个数据,也可以从显存里拿,后一种方式的性能更好。如果是从显存里拿数据,那glDrawElements拿的数据就是VBO。HomeworldSDL崩溃时用的就是VBO。具体说明可以参考OpenGL官方文档中的Vertex Specification。

  于是归纳了一下,采用VBO方式使用glDrawElements的步骤大致如下:

1、初始化,准备好顶点数据

1)用glGenBuffers申请Buffer Object的名字,也就是为即将分配的显存申请ID。

2)用glBindBuffer绑定Buffer Object,这样在再次调用glBindBuffer之前,接下来的Buffer Object相关的操作都是针对当前绑定的Buffer Object。target参数需是GL_ARRAY_BUFFER。

3)用glBufferData分配并初始化一段显存,将顶点数据传进显存。

4)再次调用glBindBuffer解绑Buffer Object。

2、准备好索引数组,步骤和1类&#;,只是调用glBindBuffer时target需是GL_ELEMENT_ARRAY_BUFFER。3、调用glVertexPointer,指定顶点数据。4、调用glDrawElements进行绘制。5、如果所有绘制工作完成,之前的显存不再需使用,就要调用glDeleteBuffers释放显存。

  需要注明的是,以上只是一种简单的使用VBO的方式,并不是说glDrawElements一定要严&#;按照这个流程。在顶点和索引数据都不会变化的情况下,3~4两步可以反复执行。索引数组也不一定要放在显存里,可以在调用glDrawElements的时候直接通过参数将内存中的数组传进去,这时就不需要第2步。同理如果顶点数据也不用VBO的话,那第1步也省去了,glGenBuffers、glBindBuffer、glBufferData和glDeleteBuffers这几个函数都不用调用。

  很快,我的glDrawElements版本的Helloworld出炉了:

  MinGW上的编译命令:gcc -o window.exe window.c -DUSE_VBO -mwindows -lopengl -lglu

  不过先别高兴太早,报错了:

$ gcc -o window.exe window.c -DUSE_VBO -mwindows -lopengl -lgluwindow.c: In function 'InitVBO':window.c::: error: 'GL_ARRAY_BUFFER' undeclared (first use in this function) glBindBuffer(GL_ARRAY_BUFFER, vboTransVerts); ^window.c::: note: each undeclared identifier is reported only once for each function it appears inwindow.c::: error: 'GL_STATIC_DRAW' undeclared (first use in this function) glBufferData(GL_ARRAY_BUFFER, sizeof(transVerts), transVerts, GL_STATIC_DRAW); ^window.c::: error: 'GL_ELEMENT_ARRAY_BUFFER' undeclared (first use in this function) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndices); ^window.c: In function 'Render':window.c::: error: 'GL_ARRAY_BUFFER' undeclared (first use in this function) glBindBuffer(GL_ARRAY_BUFFER, vboTransVerts); ^window.c::: error: 'GL_ELEMENT_ARRAY_BUFFER' undeclared (first use in this function) glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndices);

  原来GL/gl.h和GL/glu.h里都没有定义GL_ARRAY_BUFFER、GL_ELEMENT_ARRAY_BUFFER和GL_STATIC_DRAW。从两篇StackOverflow的帖子里得知,GL/gl.h(包括MinGW提供的)只提供了OpenGL 1.1(还是1.2?)的接口声明。不仅如此,通过nm工具,发现libopengl.a和opengl.dll并未导出glGenBuffers等VBO接口。

  于是我只好参考SDL的源代码,写出了可以工作的glDrawElements版Helloworld:

  MinGW上的编译命令还是不变:gcc -o window.exe window.c -DUSE_VBO -mwindows -lopengl -lglu

  这下知道glDrawElements怎么用了。再回到HomeworldSDL的代码中,我发现程序没有调用glBufferData开辟显存空间就调用了glDrawElements。想必这就是崩溃的原因。于是我将我的window.c中的glBufferData调用删除,再运行程序,果然就崩溃了,并且崩溃处的汇编代码和之前查看的基本一模一样。

  这虫子算是确确实实地捉到了。

  说实话,作为完全没接触过OpenGL的新手,我到处搜资料,花了好些时间才掌握了glDrawElements的用法。虽然Unity和CUDA的经验让我在相关概念的理解上没什么大碍,但我花了一天左右时间才真正能用这函数写一个OpenGL小程序。这是因为还有好些其它OpenGL的函数要了解;而且到最后一步还发现,为了像崩溃的代码一样,在这函数中使用OpenGL的顶点缓存对象(vertex buffer object,简称vbo),OpenGL的初始化会麻烦很多。正如《关于游戏开发,学校没有教给我的十件事》一文写道:“了解和理解是不一样的……当你做一个项目时,你可能会想‘我知道怎么做’。然而,除非你之前做过,否则你仅仅是有怎么做的想法。‘我知道’和‘我有一个如何做的想法’是不同的,它们的区别可能会让你头疼好几个小时。”想必这是每个做过实际项目的程序员的感受。

shader漫反射模拟 给物体增加光照可以提升场景的立体感,可以用shader实现光照模拟效果.一般物体光照包括了环境光,漫反射和镜面光,通常光滑的物体可以看到镜面光效果,

学习Nehe Lesson2 && Lesson3 Lesson2和Lesson3是关于多边形绘制的内容,程序结构和第一课完全一样,只是在intDrawGLScene(GLvoid)这个函数中添加了绘图的内容。绘制的是基本图形三角形

跨平台显示MMD模型 跨平台显示MMD模型过年了,祝大家喜气洋洋,心想事成!然后呢,过年后,依然进行Qt以及3D的研究。最近翻出以前的技术研究成果,并且花了将近一个

标签: 捉虫趣事作文200字

本文链接地址:https://www.jiuchutong.com/biancheng/373121.html 转载请保留说明!

上一篇:测试Qt Quick在各个平台上的3D渲染性能

下一篇:shader漫反射模拟(什么是漫反射贴图)

  • 环境保护税的应税污染物有哪些
  • 过路费定额发票还能入账吗
  • 食品类发票明细有哪些面包方便面
  • 财政补贴交不交印花税
  • 怎么用一证通报税
  • 预缴税款什么时候可以手动填写申报表
  • 进项税额转出是在借方还是贷方
  • 向职工支付职工福利费
  • 建筑行业未收款先开发票如何做账?
  • 纳税调减事项有
  • 收到股本的现金怎么做账
  • 一般开发间接费
  • 预收账款缴纳企税怎么算
  • 小规模没有进项票可以开销项票吗
  • 专票抵扣是税额吗
  • 资管产品征税
  • 事业单位没有税号 选个人可以吗
  • 非营利组织报销做账怎么做
  • 金融性负债总额是什么
  • 去年红字发票怎么做账
  • 增值税的征收范围包括在中华人民共和国境内
  • win11预览版怎么变回正式版
  • 付出去的款项退回的会计分录如何做
  • php随机ua
  • 如何计算经营性负债
  • 定额备用金的核算可分为哪几类
  • 如何处理收到的短信
  • 进程mmc.exe
  • 营改增后企业要交哪些税
  • 公司想减少注册资本
  • 存货的毁损通过什么科目核算
  • 纳税人代扣代缴
  • 非货币性资产交换的记忆口诀
  • vue错误提示
  • 安全费用的适用范围
  • 收不回的应收账款会计分录
  • 前端后端选择
  • 布里奇和瓦内莎
  • 员工出差过程中猝死赔偿
  • 核定征收的企业注销需要缴纳个人所得税吗
  • 消费税会计分录完整案例
  • vue treegrid
  • js实现拖拽选区的功能
  • 企业向慈善机构捐款会计分录
  • 存货降价销售的会计分录
  • 所得税季度申报表怎么填
  • python从键盘输入正整数n,计算1+2+3
  • Android ViewPager2 + Fragment 联动
  • 收到退货怎么做会计分录
  • 员工领了备用金怎么办
  • 分页存储过程是什么
  • 资产处置开票填什么项目
  • mysql,if
  • 会计库存商品属于什么科目
  • 煤炭资源税优惠政策
  • 产品包装设计费属什么费用
  • 维护费抵减增值税会计科目
  • 银行利息收入怎么计算
  • 2020年扶贫拨款
  • 企业的应付职工薪酬属于流动负债
  • 企业项目的特点
  • 建筑企业怎么结算成本
  • 股东实收资本超额到位
  • 农产品计算抵扣税率 最新
  • 会计建账的步骤
  • 城镇土地使用税减免税政策
  • 案例分析应收账款管理存在的问题
  • 小企业的建账流程分为什么内容
  • win10的数据使用量是什么
  • 电脑开机一直显示windowsxp
  • WebProxy.exe - WebProxy是什么进程
  • mac的dock栏怎么设置
  • 东芝笔记本配件
  • jquery根据value获取元素
  • 手机sd卡满了怎么办
  • python for循环删除元素
  • webview自定义视频播放器
  • 稳岗补贴是否需要发放
  • 江西省税务局官网查询系统
  • 伊朗开心果进口价格
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设