CGDB是GDB的前端,在终端窗口中意图形化的形式来调试代码(基于ncurse),非常方便。相对于GDB来说,可以很大的提高效率。 这篇文章就来分享一下CGDB的最基本使用方法,如果是第一次听说,强烈建议您体验一下,一定会爱上它的!有bug的示例代码includeunistd。hincludestdlib。hincludestdio。hincludestring。hincludetypedefstructUSERDATA{chardata〔32〕;unsignedshortdatalen;unsignedintflag;}attribute((packed));constunsignedchargdatahello;功能:加载一段数据参数1:data〔OUT〕:数据被加载的缓冲区参数2:len〔OUT〕:实际被加载的数据的长度返回值:0成功,else失败staticintgetdata(unsignedchardata,unsignedintlen){assert(datalen);memcpy((void)data,(void)gdata,strlen(gdata));lenstrlen(gdata);return0;}intmain(intargc,charargv〔〕){创建结构体变量structUSERDATAuserdata;userdata。flag0xA5;往结构体变量中加载数据if(0getdata(userdata。data,userdata。datalen)){printf(getdataok!);printf(datalend,datas,userdata。datalen,userdata。data);printf(userdata。flag0xx,userdata。flag);期望值:0xA5}else{printf(getdatafailed!);}return0;} 在编译之前,先看一下代码,你能发现其中的bug吗? 当然了,在编译的时候,编译器以Warning的方式给出了风险提示。因为示例代码很简单,所以很容易发现。 但是在一个项目中,如果不喜欢消除编译Warning警告的话,这个bug还是比较隐蔽的。 编译测试代码:gccgtest。cotest 因为要使用GDB调试,所以别忘了加上g选项。GDB调试操作gdb。test(gdb)r直接全速执行一次(gdb)rStartingprogram:homecaptaindemos2022cgdbtestteststart。。。getdataok!datalen5,datahellouserdata。flag0x0〔Inferior1(process9933)exitednormally〕 发现userdata。flag的值不对,决定在调用getdata之前的那行下一个断点,然后从头开始执行: 嵌入式物联网需要学的东西真的非常多,千万不要学错了路线和内容,导致工资要不上去! 无偿分享大家一个资料包,差不多150多G。里面学习内容、面经、项目都比较新也比较全!某鱼上买估计至少要好几十。 点击这里找小助理0元领取:嵌入式物联网学习资料(头条) 查看代码行号:(gdb)lmain18lenstrlen(gdata);19return0;20}2122intmain(intargc,charargv〔〕)23{24structUSERDATAuserdata;25userdata。flag0xA5;26if(0getdata(userdata。data,userdata。datalen))27{ 下断点在25行:(gdb)b25Breakpoint1at0x400771:filetest。c,line25。 开始运行:(gdb)rStartingprogram:homecaptaindemos2022cgdbtestBreakpoint1,main(argc1,argv0x7fffffffdc58)attest。c:2525userdata。flag0xA5; 在断点处停了下来,此时该赋值语句还没有执行,所以先单步执行一次:(gdb)step26if(0getdata(userdata。data,userdata。datalen)) 此时,打印一下这个变量userdata。flag的值和地址: 因为待会进入被调用函数,这个变量就不可见了,所以需要通过地址来打印。(gdb)printuserdata。flag1(unsignedint)0x7fffffffdb62(gdb)printxuserdata。flag20xa5 此时赋值是正确的,再接着往下执行,进入被调用函数getdata()了,(gdb)stepgetdata(data0x7fffffffdb40n333377377377177,len0x7fffffffdb60)attest。c:1616assert(datalen); 这个函数一共就4行代码,我们每单步执行一句,就打印一下userdata。flag变量的内容。 单步执行下一行memcpy处,并且看一下userdata。flag变量地址处的内容是否仍然为:0xa5:(gdb)step17memcpy((void)data,(void)gdata,strlen(gdata));(gdb)printx0x7fffffffdb6230xa5 继续单步执行(因为不需要跟进memcpy、strlen的内部,所以使用next命令),并打印:(gdb)next18lenstrlen(gdata);这一句即将被执行(gdb)printx0x7fffffffdb6240xa5(gdb)next19return0;(gdb)printx0x7fffffffdb6250x0 发现问题了:在执行lenstrlen(gdata)语句之后,变量userdata。flag地址中的内容就被改变了。 再仔细检查一下代码,就可以诊断出是数据类型使用错了。 解决bug:getdata()函数的最后一个参数,应该是unsignedshort型指针才正确。 问题是解决了,但是回过头来看一下gdb的调试过程,还是比较繁琐的:调试指令和代码显示夹杂在一起,需要敲很多指令。CGDB调试操作 启动CGDB之后,终端窗口被评分为上下两部分:上面是代码窗口,下面是调试窗口。 按下ESC键进入代码窗口,此时可以上下浏览代码,并且可以进行一系列的操作:空格键:设置或者取消断点; o:查看代码所在的文件; 或者?:在代码中搜索字符串; 还有很多方便的快捷键::缩小代码窗口; :扩大代码窗口; gg:光标移动到文件头部; GG:光标移动到文件尾部; ctrlb:代码向上翻一页; ctrlu:代码向上翻半页; ctrlf:代码向下翻一页; ctrld:代码向下翻半页; 按下i键回到调试窗口,进入调试模式,使用的调试指令与GDB几乎一样! 也就是说:可以在实时查看代码的情况下进行调试操作,大大提高了效率。 我们按照上面GDB的调试过程走一遍: 按下ESC键进入代码窗口,此时代码前面的行号如果是白色的,表示所在的当前行。 按下j键,向下移动高亮的当前行。当移动到25行时,如下: 按下空格键,表示在此行设置一个断点,此时行号变成红色的: 并且在调试窗口打印一行信息:(gdb)Breakpoint1at0x400771:filetest。c,line25。 按下i键回到调试操作窗口,然后输入运行指令r,会在第25行停下来的,如下绿色的箭头所示: 当然了,调试窗口也会打印出相关信息:(gdb)rStartingprogram:homecaptaindemos2022cgdbtestBreakpoint1,main(argc1,argv0x7fffffffdc58)attest。c:25 单步step执行这条赋值语句,然后打印一下userdata。flag的值和地址:(gdb)printxuserdata。flag1:xuserdata。flag0xa5(gdb)printuserdata。flag2:userdata。flag(unsignedint)0x7fffffffdb62 此时,赋值语句正确执行,打印的值也是符合预期的。 再执行单步指令,进入函数getdata()内部:(gdb)stepgetdata(data0x7fffffffdb40n333377377377177,len0x7fffffffdb60)attest。c:16 此时,上面的代码窗口自动进入getdata()相关的代码,如下所示: 继续单步,在执行赋值语句lenstrlen(gdata);之前打印一下变量userdata。flag地址中的内容:(gdb)printx0x7fffffffdb6220xa5 正确!然后执行赋值语句之后,再次打印:(gdb)next(gdb)printx0x7fffffffdb6230x0 发现问题:在执行lenstrlen(gdata)语句之后,变量userdata。flag地址中的内容就被改变了。 小结: CGDB的操作过程,虽然我写的比较啰嗦,但是实际使用起来,真的是非常的丝滑,就像巧克力一样! 原文链接:https:mp。weixin。qq。comsemJWAkbIxPTLzdvKYaE1eA 转载自:嵌入式大杂烩 原文链接:比GDB更方便的代码调试工具:CGDB 本文来源网络,免费传达知识,版权归原作者所有。如涉及作品版权问题,请联系我进行删除。