老铁们,大家好,相信还有很多朋友对于八字算命网站源码分享php和正规八字算命网站的相关问题不太懂,没关系,今天就由我来为大家分享分享八字算命网站源码分享php以及正规八字算命网站的问题,文章篇幅可能偏长,希望可以帮助到大家,下面一起来看看吧!
总览
本文为GDB调试指南,参考GDB调试手册,但加入了很多实例,目前已有的篇目:
启动调试断点设置变量查看单步调试源码查看
每个篇目都加入了示例,可操作性强。
启动调试
前言
GDB(GNUDebugger)是UNIX及UNIX-like下的强大调试工具,可以调试ada,c,c++,asm,minimal,d,fortran,objective-c,go,java,pascal等语言。本文以C程序为例,介绍GDB启动调试的多种方式。
哪类程序可被调试
对于C程序来说,需要在编译时加上-g参数,保留调试信息,否则不能使用GDB进行调试。
但如果不是自己编译的程序,并不知道是否带有-g参数,如何判断一个文件是否带有调试信息呢?
gdb文件
例如:
$gdbhelloworld\nReadingsymbolsfromhelloWorld…(nodebuggingsymbolsfound)…done.\n
如果没有调试信息,会提示nodebuggingsymbolsfound。
如果是下面的提示:
ReadingsymbolsfromhelloWorld…done.\n
则可以进行调试。
readelf查看段信息
例如:
$readelf-ShelloWorld|grepdebug\n[28].debug_arangesPROGBITS00000000000000000000106d\n[29].debug_infoPROGBITS00000000000000000000109d\n[30].debug_abbrevPROGBITS00000000000000000000115b\n[31].debug_linePROGBITS0000000000000000000011b9\n[32].debug_strPROGBITS0000000000000000000011fc\n
helloWorld为文件名,如果没有任何debug信息,则不能被调试。
file查看strip状况
下面的情况也是不可调试的:
filehelloWorld\nhelloWorld:(省略前面内容)stripped\n
如果最后是stripped,则说明该文件的符号表信息和调试信息已被去除,不能使用gdb调试。但是notstripped的情况并不能说明能够被调试。
调试方式运行程序
程序还未启动时,可有多种方式启动调试。
调试启动无参程序
例如:
$gdbhelloWorld\n(gdb)\n
输入run命令,即可运行程序
调试启动带参程序
假设有以下程序,启动时需要带参数:
//来源:公众号【编程珠玑】地址:https://www.yanbinghu.com\n表示不限制core文件大小\nulimit-c10include<stdio.h>\nvoidprintNum(inta)\n{\nprintf(“printNum\\n”);\nwhile(a>0)\n{\nprintf(“%d\\n”,a);\na–;\n}\n}\nvoidprintNum2(inta,intnum)\n{\nprintf(“printNum\\n”);\nwhile(a>num&&a>0)\n{\nprintf(“%d\\n”,a);\na–;\n}\n}\nintdiv(inta,intb)\n{\nprintf(“a=%d,b=%d\\n”,a,b);\ninttemp=a/b;\nreturntemp;\n}\nintmain(intargc,char*argv[])\n{\nprintNum2(12,5);\nprintNum(10);\ndiv(10,0);\nreturn0;\n}\n
编译:
gcc-g-otesttest.c\n
注意,编译时需要带上-g参数,否则不会有调试信息。
根据行号设置断点
b9用法:rbreakfile:regex\nrbreak.\nrbreaktest.c:.对以print开头的函数设置断点\n
设置临时断点
假设某处的断点只想生效一次,那么可以设置临时断点,这样断点后面就不复存在了:
tbreaktest.c:l0禁用所有断点\ndisablebnum启用所有断点\nenablebnum启动标号为bnum的断点,并且在此之后删除该断点\n
断点清除
断点清除主要用到clear和delete命令。常见使用如下:
clear删除函数名为function处的断点\nclearfilename:function删除行号为lineNum处的断点\nclearf:lename:lineNum删除所有breakpoints,watchpoints和catchpoints\ndeletebnuminclude<stdio.h>\ninclude”testGdb.h”\nintmain(void)\n{\ninta=10;//整型\ninti=0;\nintb[]={1,2,3,5};//数组\ncharc[]=”hello,yanbinghu”;//字符数组\n/*申请内存,失败时退出*/\nint*d=(int*)malloc(a*sizeof(int));\nif(NULL==d)\n{\nprintf(“mallocerror\\n”);\nreturn-1;\n}\n/*赋值*/\nfor(i=0;i<10;i++)\n{\nd[i]=i;\n}\nfree(d);\nd=NULL;\nfloate=8.5f;\nreturn0;\n}\n
testGdb.h
inta=11;\n
编译:
$gcc-g-otestGdbtestGdb.o\n
普通变量查看
打印基本类型变量,数组,字符数组
最常见的使用便是使用print(可简写为p)打印变量内容。
例如,打印基本类型,数组,字符数组等直接使用p变量名即可:
(gdb)pa\n$1=10\n(gdb)pb\n$2={1,2,3,5}\n(gdb)pc\n$3=”hello,yanbinghu”\n(gdb)\n
当然有时候,多个函数或者多个文件会有同一个变量名,这个时候可以在前面加上函数名或者文件名来区分:
(gdb)p’testGdb.h’::a\n$1=11\n(gdb)p’main’::b\n$2={1,2,3,5}\n(gdb)\n
这里所打印的a值是我们定义在testGdb.h文件里的,而b值是main函数中的b。
打印指针指向内容
如果还是使用上面的方式打印指针指向的内容,那么打印出来的只是指针地址而已,例如:
(gdb)pd\n$1=(int*)0x602010\n(gdb)\n
而如果想要打印指针指向的内容,需要解引用:
(gdb)p*d\n$2=0\n(gdb)p*d@10\n$3={0,1,2,3,4,5,6,7,8,9}\n(gdb)\n
从上面可以看到,仅仅使用*只能打印第一个值,如果要打印多个值,后面跟上@并加上要打印的长度。
或者@后面跟上变量值:
(gdb)p*d@a\n$2={0,1,2,3,4,5,6,7,8,9}\n(gdb)\n
由于a的值为10,并且是作为整型指针数据长度,因此后面可以直接跟着a,也可以打印出所有内容。
另外值得一提的是,$可表示上一个变量,而假设此时有一个链表linkNode,它有next成员代表下一个节点,则可使用下面方式不断打印链表内容:
(gdb)p*linkNode\n(这里显示linkNode节点内容)\n(gdb)p*$.next\n(这里显示linkNode节点下一个节点的内容)\n
如果想要查看前面数组的内容,你可以将下标一个一个累加,还可以定义一个类似UNIX环境变量,例如:
(gdb)set$index=0\n(gdb)pb[$index++]\n$11=1\n(gdb)pb[$index++]\n$12=2\n(gdb)pb[$index++]\n$13=3\n
这样就不需要每次修改下标去打印啦。
按照特定格式打印变量
对于简单的数据,print默认的打印方式已经足够了,它会根据变量类型的格式打印出来,但是有时候这还不够,我们需要更多的格式控制。常见格式控制字符如下:
x按十六进制格式显示变量。d按十进制格式显示变量。u按十六进制格式显示无符号整型。o按八进制格式显示变量。t按二进制格式显示变量。a按十六进制格式显示变量。c按字符格式显示变量。f按浮点数格式显示变量。
还是以辅助程序来说明,正常方式打印字符数组c:
(gdb)pc\n$18=”hello,yanbinghu”\n
但是如果我们要查看它的十六进制格式打印呢?
(gdb)p/xc\n$19={0x68,0x65,0x6c,0x6c,0x6f,0x2c,0x79,0x61,0x6e,0x62,0x69,0x6e,0x67,0x68,0x75,0x0}\n(gdb)\n
但是如果我们想用这种方式查看浮点数的二进制格式是怎样的是不行的,因为直接打印它首先会被转换成整型,因此最终会得到8:
(gdb)pe\n$1=8.5\n(gdb)p/te\n$2=1000\n(gdb)\n
那么就需要另外一种查看方式了。
查看内存内容
examine(简写为x)可以用来查看内存地址中的值。语法如下:
x/[n][f][u]addr\n
其中:
n表示要显示的内存单元数,默认值为1f表示要打印的格式,前面已经提到了格式控制字符u要打印的单元长度addr内存地址
单元类型常见有如下:
b字节h半字,即双字节w字,即四字节g八字节
我们通过一个实例来看,假如我们要把float变量e按照二进制方式打印,并且打印单位是一字节:
(gdb)x/4tb&e\n0x7fffffffdbd4:00000000000000000000100001000001\n(gdb)\n
可以看到,变量e的四个字节都以二进制的方式打印出来了。
自动显示变量内容
假设我们希望程序断住时,就显示某个变量的值,可以使用display命令。
(gdb)displaye\n1:e=8.5\n
那么每次程序断住时,就会打印e的值。要查看哪些变量被设置了display,可以使用:
(gdb)intodisplay\nAuto-displayexpressionsnowineffect:\nNumEnbExpression\n1:yb\n2:ye\n
如果想要清除可以使用
deletedisplaynumnum为前面变量前的编号,不带num时去使能所有\n
查看寄存器内容
(gdb)inforegisters\nrax0x00\nrbx0x00\nrcx0x7ffff7dd1b00140737351850752\nrdx0x00\nrsi0x7ffff7dd1b30140737351850800\nrdi0xffffffff4294967295\nrbp0x7fffffffdc100x7fffffffdc10\n(内容过多未显示完全)\n
小结
通过不同方式查看变量值或者内存值能够极大的帮助我们判断程序的运行是否符合我们的预期,如果发现观察的值不是我们预期的时候,就需要检查我们的代码了。
单步调试
前言
我们已经了解了GDB基本的启动,设置断点,查看变量等,如果这些内容你还不知道,建议先回顾一下前面的内容。在启动调试设置断点观察之后,没有我们想要的信息怎么办呢?这个时候,就需要单步执行或者跳过当前断点继续执行等等。而本文所说的单步调试并非仅仅指单步执行,而是指在你的控制之下,按要求执行语句。
准备
老规矩,先准备一个示例程序如下:
//来源:公众号【编程珠玑】地址:https://www.yanbinghu.com\n/*gdbStep.c*/\ninclude<stdio.h>\n2\n3/*计算简单乘法,这里没有考虑溢出*/\n4intadd(inta,intb)\n5{\n6intc=a*b;\n7returnc;\n8}\n9intmain(void)\n10{\n(gdb)l\n11inta=13;\n12intb=57;\n13printf(“itwillcalca*b\\n”);\n14intc=add(a,b);\n15printf(“%d*%d=%d\\n”,a,b,c);\n16return0;\n17}\n(gdb)\n
单步执行-next
next命令(可简写为n)用于在程序断住后,继续执行下一条语句,假设已经启动调试,并在第12行停住,如果要继续执行,则使用n执行下一条语句,如果后面跟上数字num,则表示执行该命令num次,就达到继续执行n行的效果了:
$gdbgdbStep将断点设置在12行\n(gdb)run单步执行\n26printf(“itwillcalca+b\\n”);\n(gdb)n2启动调试\n(gdb)b25运行程序\nBreakpoint1,main()atgdbStep.c:25\n25intb=7;\n(gdb)s\n26printf(“itwillcalca+b\\n”);\n(gdb)s继续完成该函数调用\nRuntillexitfrom单步进入,现在已经进入到了add函数内部\nadd(a=13,b=57)atgdbStep.c:6\n6intc=a+b;\n
从上面的过程可以看到,s命令会尝试进入函数,但是如果没有该函数源码,需要跳过该函数执行,可使用finish命令,继续后面的执行。如果没有函数调用,s的作用与n的作用并无差别,仅仅是继续执行下一行。它后面也可以跟数字,表明要执行的次数。
当然它还有一个选项,用来设置当遇到没有调试信息的函数,s命令是否跳过该函数,而执行后面的。默认情况下,它是会跳过的,即step-mode值是off:
(gdb)showstep-mode\nModeofthestepoperationisoff.\n(gdb)setstep-modeon\n(gdb)setstep-modeoff\n
还有一个与step相关的命令是stepi(可简写为si),它与step不同的是,每次执行一条机器指令:
(gdb)si\n0x00000000004005736intc=a+b;\n(gdb)display/i$pc\n1:x/i$pc\n=>0x400573<add+13>:mov-0x18(%rbp),%eax\n(gdb)\n
继续执行到下一个断点-continue
我们可能打了多处断点,或者断点打在循环内,这个时候,想跳过这个断点,甚至跳过多次断点继续执行该怎么做呢?可以使用continue命令(可简写为c)或者fg,它会继续执行程序,直到再次遇到断点处:
$gdbgdbStep\n(gdb)b18继续运行,直到下一次断住\nContinuing.\n1\nBreakpoint1,count(num=10)atgdbStep.c:18\n18i++;\n(gdb)fg跳过三次\nWillignorenext2crossingsofbreakpoint1.Continuing.\n3\n4\n5\nBreakpoint1,count(num=10)atgdbStep.c:18\n18i++;\n
继续运行到指定位置-until
假如我们在25行停住了,现在想要运行到29行停住,就可以使用until命令(可简写为u):
$gdbgdbStep\n(gdb)b25\n(gdb)run\n(gdb)u29\nitwillcalca+b\n3+7=10\nmain()atgdbStep.c:29\n29count(c);\n(gdb)\n
可以看到,在执行u29之后,它在29行停住了。它利用的是临时断点。
跳过执行–skip
skip可以在step时跳过一些不想关注的函数或者某个文件的代码:
$gdbgdbStep\n(gdb)b27\nBreakpoint1at0x4005e4:filegdbStep.c,line27.\n(gdb)skipfunctionadd查看step情况\nNumTypeEnbWhat\n1functionyadd\n(gdb)run\nStartingprogram:/home/hyb/workspaces/gdb/gdbStep\nitwillcalca+b\nBreakpoint1,main()atgdbStep.c:27\n27intc=add(a,b);\n(gdb)s\n28printf(“%d+%d=%d\\n”,a,b,c);\n(gdb)\n
可以看到,再使用skip之后,使用step将不会进入add函数。
step也后面也可以跟文件:
(gdb)skipfilegdbStep.c\n
这样gdbStep.c中的函数都不会进入。
其他相关命令:
skipdelete[num]删除skipskipenable[num]使能skipskipdisable[num]去使能skip
其中num是前面通过infoskip看到的num值,上面可以带或不带该值,如果不带num,则针对所有skip,如果带上了,则只针对某一个skip。
小结
本节主要介绍了一些简单情况的单步调试方法或常见命令使用,但这些已经够用了,毕竟大部分程序的执行或停止都在我们的掌控之中了。
源码查看
前言
我们在调试过程中难免要对照源码进行查看,如果已经开始了调试,而查看源码或者编辑源码却要另外打开一个窗口,那未免显得太麻烦。文本将会介绍如何在GDB调试模式下查看源码或对源码进行编辑。
准备工作
为了说明后面的内容,我们先准备一些源码,分别是main.c:
//来源:公众号【编程珠玑】地址:https://www.yanbinghu.com\n//main.c\ninclude”test.h”\nintmain(void)\n{\nprintf(“itwillprintfrom5to1\\n”);\nprintNum(5);\nprintf(“printend\\n”);\nprintf(“itwillprint1to5\\n”);\nprintNum1(5);\nprintf(“printend\\n”);\nreturn0;\n}\n
头文件test.h:
define_TEST_H\nendif\n
以及test.c:
include<stdio.h>\n3include”test.h”\n2voidprintNum(intn)\n3{\n4if(n<0)\n5return;\n6while(n>0)\n7{\n8printf(“%d\\n”,n);\n9n–;\n10}\n
在这里,l后面跟上函数名printNum,它便列出了printNum函数附近的源码。
设置源码一次列出行数
不知道你有没有发现,在列出函数源码的时候,它并没有列全,因为l每次只显示10行,那么有没有方法每次列出更多呢?
我们可以通过listsize属性来设置,例如设置每次列出20行:
(gdb)setlistsize20\n(gdb)showlistsize\nNumberofsourcelinesgdbwilllistbydefaultis20.\n
这样每次就会列出20行,当然也可以设置为0或者unlimited,这样设置之后,列出就没有限制了,但源码如果较长,查看将会不便。
列出指定行之间的源码
listfirst,last
例如,要列出3到15行之间的源码:
(gdb)l3,15\n3{\n4if(n<0)\n5return;\n6while(n>0)\n7{\n8printf(“%d\\n”,n);\n9n–;\n10}\n11}\n12\n13voidprintNum1(intn)\n14{\n15if(n<0)\n
启始行和结束行号之间用逗号隔开。两者之一也可以省略,例如:
(gdb)list3,\n3{\n4if(n<0)\n5return;\n6while(n>0)\n7{\n8printf(“%d\\n”,n);\n9n–;\n10}\n11}\n12\n
省略结束行的时候,它列出从开始行开始,到指定大小行结束,而省略开始行的时候,到结束行结束,列出设置的大小行,例如默认设置为10行,则到结束行为止,总共列出10行。前面我们也介绍了修改和查看默认列出源码行数的方法。
列出指定文件的源码
前面执行l命令时,默认列出main.c的源码,如果想要看指定文件的源码呢?可以
llocation\n
其中location可以是文件名加行号或函数名,因此可以使用:
(gdb)ltest.c:1\n1include”test.h”\n2voidprintNum(intn)\n3{\n(gdb)\n
指定源码路径
在查看源码之前,首先要确保我们的程序能够关联到源码,一般来说,我们在自己的机器上加上-g参数编译完之后,使用gdb都能查看到源码,但是如果出现下面的情况呢?
源码被移走
例如,我现在将main.c移动到当前的temp目录下,再执行l命令:
(gdb)l\n1main.c:Nosuchfileordirectory.\n(gdb)\n
它就会提示找不到源码文件了,那么怎么办呢?
我们可以使用dir命名指定源码路径,例如:
(gdb)dir./temp\nSourcedirectoriessearched:/home/hyb/workspaces/gdb/sourceCode/./temp:$cdir:$cwd\n
这个时候它就能找到源码路径了。我这里使用的是相对路径,保险起见,你也可以使用绝对路径。
更换源码目录
例如,你编译好的程序文件,放到了另外一台机器上进行调试,或者你的源码文件全都移动到了另外一个目录,怎么办呢?当然你还可以使用前面的方法添加源码搜索路径,也可以使用setsubstitute-pathfromto将原来的路径替换为新的路径,那么我们如何知道原来的源码路径是什么呢?借助readelf命令可以知道:
$readelfmain-p.debug_str\n[0]longunsignedint\n[12]shortint\n[1c]/home/hyb/workspaces/gdb/sourceCode\n[40]main.c\n(显示部分内容)\n
main为你将要调试的程序名,这里我们可以看到原来的路径,那么我们现在替换掉它:
(gdb)setsubstitute-path/home/hyb/workspaces/gdb/sourceCode/home/hyb/workspaces/gdb/sourceCode/temp\n(gdb)showsubstitute-path\nListofallsourcepathsubstitutionrules:\n`/home/hyb/workspaces/gdb/sourceCode’->`/home/hyb/workspaces/gdb/sourceCode/temp’.\n(gdb)\n
设置完成后,可以通过showsubstitute-path来查看设置结果。这样它也能在正确的路径查找源码啦。
需要注意的是,这里对路径做了字符串替换,那么如果你有多个路径,可以做多个替换。甚至可以对指定文件路径进行替换。
最后你也可以通过unsetsubstitute-path[path]取消替换。
编辑源码
为了避免已经启动了调试之后,需要编辑源码,又不想退出,可以直接在gdb模式下编辑源码,它默认使用的编辑器是/bin/ex,但是你的机器上可能没有这个编辑器,或者你想使用自己熟悉的编辑器,那么可以通过下面的方式进行设置:
$EDITOR=/usr/bin/vim\n$exportEDITOR\n
/usr/bin/vim可以替换为你熟悉的编辑器的路径,如果你不知道你的编辑器在什么位置,可借助whereis命令或者witch命令查看:
$whereisvim\nvim:/usr/bin/vim/usr/bin/vim.tiny/usr/bin/vim.basic/usr/bin/vim.gnome/etc/vim/usr/share/vim/usr/share/man/man1/vim.1.gz\n$whichvim\n/usr/bin/vim\n
设置之后,就可以在gdb调试模式下进行编辑源码了,使用命令editlocation,例如:
(gdb)edit3编辑printNum函数\n(gdb)edittest.c:5#编辑test.c第五行\n
可自行尝试,这里的location和前面介绍的一样,可以跟指定文件的特定行或指定文件的指定函数。
编辑完保存后,别忘了重新编译程序:
(gdb)shellgcc-g-omainmain.ctest.c\n
这里要注意,为了在gdb调试模式下执行shell命令,需要在命令之前加上shell,表明这是一条shell命令。这样就能在不用退出GDB调试模式的情况下编译程序了。
另外一种模式
启动时,带上tui(TextUserInterface)参数,会有意想不到的效果,它会将调试在多个文本窗口呈现:
gdbmain-tui\n
但是本文不作介绍,有兴趣的可以探索一下。
小结
本文介绍了GDB调试中的源码查看,源码编辑以及如何在GDB调试模式下执行shell命令。
总结
以上就是本文全部内容,文中难免有不当之处,还请各位多多指教。
END,本文到此结束,如果可以帮助到大家,还望关注本站哦!
