当前位置: 首页 > 编程日记 > 正文

gdb相关(栈和寄存器)

GDB的常用调试命令大家可以查阅gdb手册就可以快速的上手了,在这儿就不给大家分享了,需要的可以到GDB的官网去下载手册。这里重点分享下GDB调试中的一些寄存器和栈的相关知识用于解决下列gdb调试时的问题:

  1. 优化的代码在printf或其它glibc函数处core
  2. 没有检查返回值的函数调用异常导致的异常
  3. 优化的代码的计算异常的中间过程分析
  4. 栈溢出导致的core
  5. 局部变量越界导致栈异常的core

寄存器

通常调试的代码基本上都是在未开启优化的情况下,各个变量都可以直接查看,因此造成很多人调试时基本上不会看寄存器,但是对于线上的生产环境,可能会因为性能的因素,需要打开代码优化,此时出现异常需要调试时就通常需要查看寄存器了,下列是gdb调试中需要了解的寄存器。

  • $rip 指令寄存器,指向当前执行的代码位置
  • $rsp 栈指针寄存器,指向当前栈顶
  • $rax,$rbx,$rcx,$rdx,$rsi,$rdi,$rbp,$r8,$r9,$r10,$r11,$r12,$r13,$r14,$r15 通用寄存器
  • 函数入参

    一般linux下会优先将参数压到寄存器中,只有当寄存器不够所有的参数时,才会将入参压到栈上,一般入参的压栈顺序为$rdi、$rsi、$rdx、$rcx、$r8、$r9,如下
    1. int arg_int(int a, int b, int c,

    2. int d, int e, int f,

    3. int g, int h, int i)

    4. {

    5. return (a + b + c + d + e + f + g + h + i);

    6. }

    arg_int入参
    1. Breakpoint 1, arg_int (a=1, b=2, c=3, d=4, e=5, f=6, g=7, h=8, i=9) at ./reg.cpp:7

    2. ......

    3. (gdb) i r

    4. rax 0x7ffff7639f60 140737343889248

    5. rbx 0x14 20

    6. rcx 0x4 4

    7. rdx 0x3 3

    8. rsi 0x2 2

    9. rdi 0x1 1

    10. rbp 0xa 0xa

    11. rsp 0x7fffffffe4e8 0x7fffffffe4e8

    12. r8 0x5 5

    13. r9 0x6 6

    14. r10 0x7fffffffe3b0 140737488348080

    15. r11 0x7ffff72cbbe0 140737340292064

    16. r12 0x1e 30

    17. r13 0x28 40

    18. r14 0x32 50

    19. r15 0x3c 60

    20. rip 0x4005e4 0x4005e4 <arg_int(int, int, int, int, int, int, int, int, int)>

    第7、8、9个入参将会被压到栈上,具体的栈上的位置信息大家可以自己研究。
    了解了入参时的寄存器知识,也该学以致用了,最典型的需要使用寄存器查看函数入参的场景是glibc的函数,在此以printf函数作为示例。printf最一类典型的core场景,通常是因为指定的格式和实际的类型不符合造成的,但是如果没有源码的话,即使断点抓住了也不知道具体是哪个参数的格式不对,但是通过查看寄存器就可以调试此类问题了
    1. int along[9] = { 10, 20, 30, 40, 50, 60, 70, 80, 90 };

    2. printf("arg_long = %ld\n",

    3. arg_long(along[0], along[1], along[2],

    4. along[3], along[4], along[5],

    5. along[6], along[7], along[8]));

    断点printf时寄存器信息
    1. Breakpoint 3, 0x00007ffff72fb990 in printf () from /lib64/libc.so.6

    2. (gdb) i r

    3. rax 0x0 0

    4. rbx 0x7fffffffe648 140737488348744

    5. rcx 0x4 4

    6. rdx 0x15 21

    7. rsi 0x1ef 495

    8. rdi 0x400858 4196440

    9. rbp 0x0 0x0

    10. rsp 0x7fffffffe538 0x7fffffffe538

    11. r8 0x5 5

    12. r9 0x6 6

    13. r10 0x7fffffffe2c0 140737488347840

    14. r11 0x7ffff72fb990 140737340488080

    15. r12 0x400500 4195584

    16. r13 0x7fffffffe640 140737488348736

    17. r14 0x0 0

    18. r15 0x0 0

    19. rip 0x7ffff72fb990 0x7ffff72fb990 <printf>

    大家可以根据上面的知识来查看寄存器知道具体的输出信息,$rdi是format,先查看format后就可以按顺序来查看后面的参数是否正确了
    1. (gdb) p/s (char*)$rdi

    2. $1 = 0x400858 "arg_long = %ld\n"

    3. (gdb) p/d $rsi

    4. $2 = 495


  • 函数返回值

    函数返回值由$rax保存返回,单步执行上面的用例,printf的返回值如下:
    1. arg_long = 495

    2. ......

    3. (gdb) p/d $rax

    4. $1 = 15

    打印的内容为”arg_long = 495\n”,一共15个字节
    返回值查看的具体使用场景比较广,例如glibc、外部库等各种看不到源码的函数调用的结果都可以通过$rax查看返回值,这儿就不举例了,大家可以自己验证下

  • 函数运行中

    对于开启了优化的场景下,局部变量往往会仅在寄存器中存储,如果需要查看被优化了的局部变量的值,如下:
    1. for (int i = 0 ; i < end ; ++i)

    2. {

    3. total += i;

    4. }

    执行N次后的断点信息如下:
    1. (gdb)

    2. 34 total += i;

    3. (gdb) p i

    4. $3 = <value optimized out>

    5. (gdb) p total

    6. $4 = <value optimized out>

    直接打印i、total都显示被优化了,因此无法直接打印其值,通过打印当前寄存器信息得到了寄存器中的值,然后通过查看汇编和源码对比
    1. 0x0000000000400735 <+120>: jle 0x400740 <main(int, char**)+131>

    2. => 0x0000000000400737 <+122>: add %edx,%esi

    3. 0x0000000000400739 <+124>: add $0x1,%edx

    4. 0x000000000040073c <+127>: cmp %eax,%edx

    5. 0x000000000040073e <+129>: jl 0x400737 <main(int, char**)+122>

    通过汇编可以知道,i对应为寄存器$rdx,total对应为寄存器$rsi,end对应为寄存器$rax
    1. (gdb) i r

    2. rax 0x5 5

    3. rbx 0x7fffffffe648 140737488348744

    4. rcx 0x5 5

    5. rdx 0x2 2

    6. rsi 0x1 1

    7. rdi 0x7fffffffe8a6 140737488349350

    8. rbp 0x0 0x0

    9. rsp 0x7fffffffe540 0x7fffffffe540

    10. r8 0x7ffff7637580 140737343878528

    11. r9 0x7ffff73eb9e0 140737341471200

    12. r10 0x5 5

    13. r11 0x1999999999999999 1844674407370955161

    14. r12 0x400500 4195584

    15. r13 0x7fffffffe640 140737488348736

    16. r14 0x0 0

    17. r15 0x0 0

    18. rip 0x400737 0x400737 <main(int, char**)+122>

    也就是此时i为2、total为1,total += i;执行完后$rsi应该会变为3,这点大家可以尝试验证下。

在gdb调试栈错误前,你需要了解下列的栈知识

  • 函数调用跳转时在新帧的栈首8Bytes存放上一帧的指令地址
  • 通常函数的起始操作为push $rbp,将上帧的栈底地址8Bytes压入栈中
  • 在保存完指令地址和栈底地址后,会进行一次sub xxx,$rsp,为当前函数内所有在栈上的局部变量都申请好需要的栈空间
  • 函数调用前将需要保存的寄存器值和超过6个的参数都压入栈中

栈异常导致的core是线上最常见的core原因之一,常见原因有:

  • 递归调用或大变量消耗栈空间,导致栈溢出

    这类问题往往通过gdb查看栈基本信息就可以定位解决,话不多说,直接上实战
    1. (gdb) bt

    2. #0 0x00007ffff72fb990 in printf () from /lib64/libc.so.6

    3. #1 0x000000000040069f in f1 (ac1=0x7fffffffe360, ac2=0x7fffffffe230) at ./stack.cpp:9

    4. #2 0x00000000004006f9 in f2 (ac=0x7fffffffe360) at ./stack.cpp:18

    5. #3 0x000000000040074a in main (argc=1, argv=0x7fffffffe658) at ./stack.cpp:27

    6. (gdb) info frame 0

    7. Stack frame at 0x7fffffffe1d0:

    8. Locals at 0x7fffffffe1c0, Previous frame's sp is 0x7fffffffe1d0

    9. (gdb) info frame 1

    10. Stack frame at 0x7fffffffe220:

    11. (gdb) info frame 2

    12. Stack frame at 0x7fffffffe350:

    13. (gdb) info frame 3

    14. Stack frame at 0x7fffffffe580:

    Stack frame at xxxx的意义为当前帧的用户栈起始地址
    frame 0的当前栈使用为:0x7fffffffe1d0 - 0x7fffffffe1c0 = 16 Bytes
    frame 1的当前栈使用为:0x7fffffffe220 - 0x7fffffffe1d0 = 80 Bytes
    frame 2的当前栈使用为:0x7fffffffe350 - 0x7fffffffe220 = 304 Bytes
    frame 3的当前栈使用为:0x7fffffffe580 - 0x7fffffffe350 = 560 Bytes
    当前栈使用为:0x7fffffffe580 - 0x7fffffffe1c0
    由此类推,在遇到怀疑栈溢出的时候就可以根据整体栈使用和各帧的栈使用来快速定位出具体是哪层栈造成的溢出。

  • 栈上变量的越界访问导致栈内的数据被改写

    这类问题往往gdb查看时会得到一个错乱的栈信息
    1. Program received signal SIGSEGV, Segmentation fault.

    2. 0x00000000000b0000 in ?? ()

    3. (gdb) bt

    4. #0 0x00000000000b0000 in ?? ()

    5. #1 0x00000000000c0000 in ?? ()

    6. #2 0x00000000000d0000 in ?? ()

    7. #3 0x00000000000e0000 in ?? ()

    8. #4 0x00000000000f0000 in ?? ()

    9. #5 0x0000000000000002 in ?? ()

    10. #6 0x0000000000000003 in ?? ()

    11. ......

    这种情况下通常是栈上前面帧的指令地址被修改导致的,可能刚好被修改的是上一帧的指令地址,也可能修改的是几帧前的指令地址。通常这类问题定位起来非常麻烦,根据$rsp不一定能还原栈信息,定位起来时往往需要大量的猜测和验证。
    下面的示例是在踩了栈后没有其它的函数调用重新写栈信息
    1. ### gdb 查找宏

    2. define find

    3. set $ptr = $arg0

    4. set $cnt = 0

    5. while ( ($ptr<=$arg1) && ($cnt<$arg3) )

    6. if ( *(unsigned long *)$ptr == $arg2 )

    7. x/gx $ptr

    8. set $cnt = $cnt + 1

    9. end

    10. set $ptr = $ptr + 8

    11. end

    12. end

    1. (gdb) p $rsp

    2. $2 = (void *) 0x7fffffffe4d0

    3. (gdb) find 0x7fffffffe000 0x7fffffffe4d0 0x7fffffffe4c0 3

    4. 0x7fffffffe370: 0x00007fffffffe4c0

    5. 0x7fffffffe450: 0x00007fffffffe4c0

    6. (gdb) x/2gx 0x7fffffffe370

    7. 0x7fffffffe370: 0x00007fffffffe4c0 0x00007ffff72fba2a

    8. (gdb) x/2gx 0x7fffffffe450

    9. 0x7fffffffe450: 0x00007fffffffe4c0 0x000000000040066c

    1. addr2line -e ./stack 0x000000000040066c

    2. /data/lambygao/test/./stack.cpp:20

    示例中的find为定义的gdb宏,用于在栈上查找指定的地址值,$rsp为0x7fffffffe4d0,则该帧对应的$rpb为$rsp-16=0x7fffffffe4c0,在栈上查找该值后得到2个地址,分别查看按该地址为帧头来测试,找到了怀疑的附近地址/data/lambygao/test/./stack.cpp:20
    当然如果是必现的core的话,也可以提前抓好栈的信息,然后设置好watch,这点大家可以自己尝试下。

补充:

find 0x7fffffffe000 0x7fffffffe4d0 0x7fffffffe4c0 3
其中查找范围的起始地址,和查找的值这2个值可能不太理解为什么是这2个值,因此补充下面的这张解释图说明下:

因为是在f1内越界访问写的f2内的数组,破坏的是f2帧头的main帧的返回后的执行代码地址和栈底。因此只有在f2函数执行完后出栈后才会core。刚好这个例子是f2调用了f1后只调用了printf,f1帧的栈的面貌没有被其它的调用清理掉,所以尝试找f1帧的栈头信息时刚好可以找到,否则的话找到的会是f2中最后一个调用函数调用入栈写入的$rbp值。

  • $rsp - 16是f1 frame中保存的f2 frame的栈底地址;

  • 按页对齐0x7fffffffe4d0的页起始值是0x7fffffffe000,先搜索的本页地址也就是从0x7fffffffe000到0x7fffffffe4d0,之所以按页来查找也是因为前面的地址页是否存在,实际中可能需要逐渐搜索多页。

相关文章:

bzoj1688[Usaco2005 Open]Disease Manangement 疾病管理*

bzoj1688[Usaco2005 Open]Disease Manangement 疾病管理 题意&#xff1a; n头牛&#xff0c;d种疾病&#xff0c;每头牛都患一些疾病&#xff0c;现在要求选出最多的牛&#xff0c;使这些牛患病的种类数不超过k。n≤1000&#xff0c;d≤15 题解&#xff1a; 状压dp。f[i][S]表…

【数据结构】二叉树的应用。

1、分别采用递归和非递归的方式编写两个函数&#xff0c;求一棵给定二叉树中叶子节点的个数 2、返回一棵给定二叉树在中序遍历下的最后一个结点 3、假设二叉树采用链式方式存储&#xff0c;root为其根节点&#xff0c;p和q分别指向二叉树中任意两个结点&#xff0c;编写一个函…

我为我Windows Home Server 预热

这两天在下载Windows Home Server,所以找一些资料来看. 微软宣布Windows Home Server&#xff08;WHS&#xff09;正式推出&#xff0c;WHS是一个帮助家庭保护&#xff0c;连接并共享他们的数字媒体与文档的新解决方案。用户可以从各大在线商店进行预订&#xff0c;之后会在本月…

c 宏定义用法#define

转自&#xff1a;https://blog.csdn.net/boring_wednesday/article/details/78756696 宏定义 语法 #define name Stuff #define PI 3.14 //定义一个M&#xff0c;值为3.14 #define DO_FOREVER for(;;) //定义一个死循环 #define REG register //定义REG来作为register的别…

Linux学习笔记—— 权限及权限管理

权限及权限管理权限管理&#xff1a;r&#xff1a;w&#xff1a;x&#xff1a;三类用户&#xff1a;u&#xff1a;属主g&#xff1a;属组o&#xff1a;其他用户chown&#xff1a;改变文件属主&#xff08;只有管理员可以使用此命令&#xff09;# chown USERNAME file,...-R&…

【ACM】签到题

#include <stdio.h> int main () {int T,a,b,c,x,ji,ya,e;scanf("%d",&T);while(T--){scanf("%d%d%d%d",&a,&b,&c,&x);ya(a*x)/(c-b);e(a*b*x)/(a*c-a*b);jiex;printf("%d %d %d\n",ji,ya,e);}return 0; }

图解eclipse+myeclipse完全绿色版制作过程

现在在Java开发中&#xff0c;使用的开发工具大部分都是Eclipse&#xff0c;并且和Eclipse关系紧密的要数MyEclipse了&#xff0c;但是 MyEclipse是一个EXE可执行程序&#xff0c;对于没有安装Eclipse与MyEclilpse的电脑来说&#xff0c;首先得先解压Eclipse&#xff0c;然后再…

linux proc/xx/maps文件分析

转载&#xff1a;https://blog.csdn.net/lijzheng/article/details/23618365 Proc/pid/maps显示进程映射了的内存区域和访问权限。对应内核中的操作集为proc_pid_maps_op&#xff0c;具体的导出函数为show_map。内核中进程的一段地址空间用一个vm_area_struct结构体表示&#…

【ACM】熊孩子的乐趣

题目链接&#xff1a;http://acm.nuc.edu.cn/OJ/contest/show/43/1000 【问题描述】 Alice跟Bob是学校里出了名的两个熊孩子&#xff0c;会在任何事情上争个高低&#xff0c;彼此都不服输。幼儿园的老师每次分糖果的时候看到这两个熊孩子也很头疼&#xff0c;两个人都想占便宜…

mysql insertOrUpdate 方法

为什么80%的码农都做不了架构师&#xff1f;>>> 自己对这个方法又有点小心得 分享下 https://my.oschina.net/hccake/blog/777225 mysql "ON DUPLICATE KEY UPDATE" 语法 如果在INSERT语句末尾指定了ON DUPLICATE KEY UPDATE&#xff0c;并且插入行后会导…

软件破解工具整理收集

1.调试工具softice 2.调试工具Trw2000 3.反汇编工具Wdasm8.93 4.Hiew 5.Visual Basic程序调试工具Smartcheck 6.十六进制编辑器&#xff08;如&#xff1a;Ultraedit、WinHex、Hex Workshop 等&#xff09; 7.注册表监视工具RegShot、regmon或RegSnap 8.侦测文件类型工具…

Linux 下 UltraEdit 版本: 16.1.0.18 破解 30 天试用限制

rm -rfd ~/.idm/uex rm -rf ~/.idm/*.spl rm -rf /tmp/*.spl

【ACM】杭电OJ 2149

Public Sale 【问题描述】 虽然不想&#xff0c;但是现实总归是现实&#xff0c;Lele始终没有逃过退学的命运&#xff0c;因为他没有拿到奖学金。现在等待他的&#xff0c;就是像FarmJohn一样的农田生涯。 要种田得有田才行&#xff0c;Lele听说街上正在举行一场别开生面的拍…

PV UV IP

UV &#xff08;网站独立访客&#xff09; 编辑UV是unique visitor的简写&#xff0c;是指通过互联网访问、浏览这个网页的自然人。独立IP&#xff1a;是指独立用户/独立访客。指访问某个站点或点击某条新闻的不同IP地址的人数&#xff0c;在同一天的00:00-24:00内&#xff0c;…

VBA中级班课时3小结

本课内容&#xff1a;工作簿和工作表对象 主讲&#xff1a;rover18 学习时间&#xff1a;2010年11月 本节课将学习工作簿对象Workbooks、Workbook与工作表对象Worksheets、Worksheet。在我们了解了VBA的四大要素——对象、属性、方法和事件后&#xff0c;会发现VBA的程序是对对…

解决firefox ubuntu无法打开页面的问题

firefox备份用户配置信息 https://support.mozilla.org/zh-CN/kb/%E5%A4%87%E4%BB%BD%E4%BD%A0%E7%9A%84%E4%BF%A1%E6%81%AF 把xxxxxxxx.default 覆盖掉xxxxxxxx.default-release里面的内容

js构造函数式编程

1.函数式编程 //创建和初始化地图函数&#xff1a;function initMap(){createMap();//创建地图setMapEvent();//设置地图事件addMapControl();//向地图添加控件}//创建地图函数&#xff1a;function createMap(){var map new BMap.Map("dituContent");//在百度地图容…

【ACM】绝地求生

题目链接&#xff1a;http://acm.nuc.edu.cn/OJ/contest/show/43/1009 【问题描述】 zbt最近喜欢上了《绝地求生》&#xff08;pubg&#xff09;游戏&#xff0c;pubg这个游戏有一种跑毒机制&#xff0c;每次会产生一个圆形的安全区,玩家需要从他的当前位置在一定时间内进入安…

【oracle】dblink创建

目的&#xff1a;oracle中跨数据库查询 两台数据库服务器db_A(本地)和db_B(远程192.168.1.100)&#xff0c;db_A下用户user_a 需要访问到db_B下user_b的数据解决&#xff1a;查询得知使用dblink(即database link 数据库链)实现过程&#xff1a;1、确定用户user_a有没有创建 db…

ASan(Linux),gcc4.8以上版本自带的内存检查工具

转自&#xff1a;http://shafeng.github.io/2017/05/10/asan/ 最近线上的程序总是莫名其妙崩溃,因为我们的项目使用了分布负载的机制,对于玩家的影响其实很小,但是我肯定是忍不了的…程序崩溃的core文件里面完全找不到问题所在,初步分析应该是野指针导致,仔细分析程序之后并没有…

详解使用DockerHub官方的mysql镜像生成容器

为什么80%的码农都做不了架构师&#xff1f;>>> 写在前面&#xff1a;看到网上关于利用DockerHub官方的mysql镜像生成容器此类的文档比较少&#xff0c;故结合自身实践分享给大家&#xff0c;还望多多指教。 我的需求&#xff1a;利用docker 镜像快速建立一个mysql…

【ACM】奇怪的回文数

题目链接&#xff1a;http://acm.nuc.edu.cn/OJ/contest/show/43/1008 【问题描述】 “回文”是指正读反读都能读通的句子&#xff0c;它是古今中外都有的一种修辞方式和文字游戏&#xff0c;如“我为人人&#xff0c;人人为我”等。 在数学中也有这样一类数字有这样的特征…

java I/O之装饰者模式

装饰者&#xff1a; Decorator模式&#xff08;别名Wrapper&#xff09;&#xff1a;动态将职责附加到对象上&#xff0c;若要扩展功能&#xff0c;装饰者提供了比继承更具弹性的代替方案。 装饰者模式意图&#xff1a; 动态的给一个对象添加额外的职责。Decorator比生产子类灵…

ubuntu下wireshark添加root权限

wireshark要监控eth0&#xff0c;但是必须要root权限才行。但是&#xff0c;直接用root运行程序是相当危险&#xff0c;也是非常不方便的。 解决方法如下&#xff1a; 1.添加wireshark用户组sudo groupadd wireshark 2.将dumpcap更改为wireshark用户组sudo chgrp wireshark /…

Oracle导出空表解决办法

在oracle 11g 中&#xff0c;发现传统的exp不能导出空的表 oracle 11g 新增了一个参数&#xff1a;deferred_segment_creation&#xff0c;含义是段延迟创建&#xff0c;默认是true。具体是什么意思呢&#xff1f; 如果这个参数设置为true&#xff0c;你新建了一个表T1&#xf…

【ACM】图像分类

题目链接&#xff1a;http://acm.nuc.edu.cn/OJ/contest/show/43/1003 【问题描述】 现在&#xff0c; 我们需要你来解决一项图像分类任务。 首先我们需要介绍下简单图像的数据存储形式&#xff0c;你可以粗略的认为图像在数字意义就是一个二维矩阵&#xff08;我们这里不考虑…

【译】如何精确判断最终用户响应时间过长的原因?

译者&#xff1a;原始文章有点性能测试工具软文的感觉&#xff0c;毕竟文章来源于某工具官方博客。高手请略过。 对于我这种新手&#xff0c;此文还是给我带来一些惊喜&#xff0c;从上到下地&#xff0c;从表象到根源地&#xff0c;定位他们遇到性能问题-响应时间过长-的根本原…

javascript中重要概念-闭包-深入理解

在上次的分享中javascript--函数参数与闭包--详解&#xff0c;对闭包的解释不够深入。本人经过一段时间的学习&#xff0c;对闭包的概念又有了新的理解。于是便把学习的过程整理成文章&#xff0c;一是为了加深自己闭包的理解&#xff0c;二是给读者提供学习的途径&#xff0c;…

ssl握手过程和ca证书验证

转载&#xff1a;https://www.cnblogs.com/cposture/p/9029014.html SSL 认证 可以将 SSL 服务器与客户端之间的通信配置为使用单向或双向 SSL 认证。 单向 SSL 认证一般是客户端利用服务器传过来的信息验证服务器的合法性&#xff0c;服务器的合法性包括&#xff1a;证书是…

【ACM】练武奇才

题目链接&#xff1a;http://acm.nuc.edu.cn/OJ/contest/show/43/1005 【问题描述】 很久很久以前&#xff0c;constbh大神还在上着小学。一天&#xff0c;在放学的路上&#xff0c;他被一位乞丐叫住&#xff0c;这位乞丐对constbh说&#xff0c;我看你骨骼惊奇&#xff0c;…