{"action":"created","issue":{"url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/issues/134","repository_url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io","labels_url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/issues/134/labels{/name}","comments_url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/issues/134/comments","events_url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/issues/134/events","html_url":"https://github.com/zhangyachen/zhangyachen.github.io/issues/134","id":335468066,"node_id":"MDU6SXNzdWUzMzU0NjgwNjY=","number":134,"title":"GDB 单步调试汇编","user":{"login":"zhangyachen","id":14103319,"node_id":"MDQ6VXNlcjE0MTAzMzE5","avatar_url":"https://avatars1.githubusercontent.com/u/14103319?v=4","gravatar_id":"","url":"https://api.github.com/users/zhangyachen","html_url":"https://github.com/zhangyachen","followers_url":"https://api.github.com/users/zhangyachen/followers","following_url":"https://api.github.com/users/zhangyachen/following{/other_user}","gists_url":"https://api.github.com/users/zhangyachen/gists{/gist_id}","starred_url":"https://api.github.com/users/zhangyachen/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/zhangyachen/subscriptions","organizations_url":"https://api.github.com/users/zhangyachen/orgs","repos_url":"https://api.github.com/users/zhangyachen/repos","events_url":"https://api.github.com/users/zhangyachen/events{/privacy}","received_events_url":"https://api.github.com/users/zhangyachen/received_events","type":"User","site_admin":false},"labels":[{"id":599629234,"node_id":"MDU6TGFiZWw1OTk2MjkyMzQ=","url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/labels/C","name":"C","color":"f9d0c4","default":false},{"id":734360204,"node_id":"MDU6TGFiZWw3MzQzNjAyMDQ=","url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/labels/%E6%B1%87%E7%BC%96","name":"汇编","color":"fef2c0","default":false}],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":6,"created_at":"2018-06-25T15:55:29Z","updated_at":"2018-06-29T09:37:44Z","closed_at":null,"author_association":"OWNER","body":"之前在看汇编的时候一直是肉眼看GCC -S的结果，缺点是很不直观，无法实时的看到寄存器的值，所以研究了下如何用GDB调试汇编。当然，写这篇文章更重要的一个目的是半年没有写博客了，博客要长草了。^_^\r\n\r\n我调试汇编的需求有几点：\r\n* 能够单步进行汇编调试。\r\n* 能够实时看到寄存器值的变化。\r\n* 能够看到源代码和对应汇编的关系。\r\n\r\n下面分享下用GDB实现上面的3点需求：\r\n### 单步进行汇编调试\r\n使用si和ni。与s与n的区别在于：s与n是C语言级别的单步调试，si与ni是汇编级别的单步调试。\r\n\r\n### 能够实时看到寄存器值的变化。\r\n使用gdb时增加-tui选项，打开gdb后运行`layout regs`命令。注意最好加上-tui，否则很大可能会出现花屏现象。\r\n\r\n![image](https://user-images.githubusercontent.com/14103319/41811532-d76febb4-7743-11e8-8371-72914513ce5c.png)\r\n\r\n###  能够看到源代码和对应汇编的关系\r\n在gdb中运行`set disassemble-next-line on`，表示自动反汇编后面要执行的代码。\r\n\r\n![image](https://user-images.githubusercontent.com/14103319/41811626-bf614a84-7745-11e8-836e-952a3a7b8053.png)\r\n\r\n可以清晰的看出`int c=sum(x,y);`与下面红框内的汇编指令成对应关系。\r\n\r\n如果大家不想用这么原始的方式，可以给GDB安装插件或者使用emacs达到上面的目的，推荐两篇文章：\r\n* [GDB 从裸奔到穿戴整齐](http://www.skywind.me/blog/archives/2036)\r\n* [GDB实用插件(peda, gef, gdbinit)全解](https://blog.csdn.net/gatieme/article/details/63254211)\r\n\r\n最后以一个小例子结束：\r\n```C\r\nint sum(int x,int y){\r\n        return x+y;\r\n}\r\n\r\nint main(){\r\n        int x=10;\r\n        int y=20;\r\n        int c=sum(x,y);\r\n\r\n        return 0;\r\n}\r\n```\r\ngcc版本4.4.7，默认的优化选项。\r\n\r\n我们单步调试下这段代码对应的汇编：\r\n### 设置断点\r\n注意如果想要把断点设置在汇编指令层次函数的开头，应该使用`b *fun`而不是`b func`，这里我们把断点设置在`b *main`\r\n\r\n### 分配栈帧\r\n```\r\n0x0000000000400489 <main+0>:\t 55\tpush   %rbp\r\n0x000000000040048a <main+1>:\t 48 89 e5\tmov    %rsp,%rbp\r\n0x000000000040048d <main+4>:\t 48 83 ec 10\tsub    $0x10,%rsp\r\n```\r\n%rbp和%rsp表示的是当前栈帧的栈底和栈顶。其中%rbp是被调用者需要保存的寄存器。`sub    $0x10,%rsp`表示为main函数分配栈帧空间。\r\n注意这里分配了16字节的栈空间，会有4字节用不上，我个人猜测跟gcc汇编产生的`cfi_def_cfa_offset 16`有关，这个没有深究。\r\n\r\n### int x=10\r\n```\r\n0x0000000000400491 <main+8>:\t c7 45 f4 0a 00\t00 00\tmovl   $0xa,-0xc(%rbp)\r\n```\r\n将x的值放到栈中\r\n\r\n### int y=20\r\n```\r\n0x0000000000400498 <main+15>:         c7 45 f8 14 00\t00 00\tmovl   $0x14,-0x8(%rbp)\r\n```\r\n将y的值放到栈中\r\n\r\n### sum函数调用\r\n```\r\n 0x000000000040049f <main+22>:         8b 55 f8\tmov    -0x8(%rbp),%edx\r\n 0x00000000004004a2 <main+25>:         8b 45 f4\tmov    -0xc(%rbp),%eax\r\n 0x00000000004004a5 <main+28>:         89 d6\tmov    %edx,%esi\r\n 0x00000000004004a7 <main+30>:         89 c7\tmov    %eax,%edi\r\n 0x00000000004004a9 <main+32>:         e8 c6 ff ff ff\tcallq  0x400474\t<sum>\r\n```\r\n将x与y分别赋值到%esi和%edi中，其中%edi和%esi被规定用来传递函数的第一个和第二个参数。（一个疑问是为什么不能直接`mov -0x8(%rbp),%esi`呢？）\r\ncallq会将下一条指令的地址压入栈中，并跳到sum函数的第一条指令。\r\n\r\n### 进入sum函数\r\n```\r\n0x0000000000400474 <sum+0>:\t 55\tpush   %rbp\r\n0x0000000000400475 <sum+1>:\t 48 89 e5\tmov    %rsp,%rbp\r\n0x0000000000400478 <sum+4>:\t 89 7d fc\tmov    %edi,-0x4(%rbp)\r\n0x000000000040047b <sum+7>:\t 89 75 f8\tmov    %esi,-0x8(%rbp)\r\n```\r\n同main函数一样，首先将%rbp保存，然后从%edi和%esi中取出函数参数。\r\n\r\n### 求和\r\n```\r\n0x000000000040047e <sum+10>:\t 8b 45 f8\tmov    -0x8(%rbp),%eax\r\n0x0000000000400481 <sum+13>:\t 8b 55 fc\tmov    -0x4(%rbp),%edx\r\n0x0000000000400484 <sum+16>:\t 8d 04 02\tlea    (%rdx,%rax,1),%eax\r\n```\r\n将x和y相加，这里用到的是lea指令，关于lea指令介绍参考[LEA instruction? ](https://stackoverflow.com/questions/4534617/lea-instruction])，这里不赘述了。\r\n将返回值放到%eax中，%rax寄存器规定存放函数的返回值。像GO语言如果函数可以有多个返回值的话，返回值是放到栈中。\r\n\r\n### sum函数收尾\r\n```\r\n0x0000000000400487 <sum+19>:\t c9\tleaveq\r\n0x0000000000400488 <sum+20>:\t c3\tretq\r\n```\r\n我们先看下现在的栈：\r\n\r\n![image](https://user-images.githubusercontent.com/14103319/41818454-26fc5e9e-77e2-11e8-8d65-ead26e014123.png)\r\n(这里不知道为什么没有sub xx,$rsp，我猜测是gcc发现这个最后一次函数调用，之后不会有栈的增长只会有栈的回退，所以用%rsp和%rbp的结果是一样的。简单验证了下，应该是这样)。\r\n在函数结束时首先需要回收当前函数的栈帧、恢复保存过的寄存器、恢复%rip的值，即返回地址。\r\n\r\nleaveq指令相当于：\r\n```\r\nmov  %rbp,%rsp     \r\npop %rbp\r\n```\r\n作用是释放(deallocate)当前函数的栈帧并恢复被保存的寄存器的值。由此我们也可以看出%rbp的作用：记住%rsp应该回退的位置，否则函数结束时%rsp不知道该回退到哪。\r\n\r\nreq指令相当于：\r\n```\r\npop %rip\r\n```\r\n将上面保存过的callq的下一条指令地址恢复到%rip中。\r\n\r\n### 接收函数返回值\r\n```\r\n0x00000000004004ae <main+37>:         89 45 fc\tmov    %eax,-0x4(%rbp)\r\n```\r\n将%eax的值放入到main函数的栈帧中。\r\n\r\n### return 0\r\n```\r\n0x00000000004004b1 <main+40>:         b8 00 00 00 00\tmov    $0x0,%eax\r\n```\r\n同上面sum函数一样。\r\n\r\n### main函数收尾\r\n```\r\n0x00000000004004b6 <main+45>:         c9\tleaveq\r\n0x00000000004004b7 <main+46>:         c3\tretq\r\n```\r\n如果上面%rsp和%rbp指向同一内存区域看起来不太直观的话，看下现在main函数即将结束时的栈空间：\r\n\r\n![image](https://user-images.githubusercontent.com/14103319/41860367-1cd47d8a-78d1-11e8-8890-ed56b042ef99.png)\r\n\r\n同上面sum函数的解释一样，不再赘述。\r\n\r\n程序运行成功退出。"},"comment":{"url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/issues/comments/401303837","html_url":"https://github.com/zhangyachen/zhangyachen.github.io/issues/134#issuecomment-401303837","issue_url":"https://api.github.com/repos/zhangyachen/zhangyachen.github.io/issues/134","id":401303837,"node_id":"MDEyOklzc3VlQ29tbWVudDQwMTMwMzgzNw==","user":{"login":"wujunze","id":12997869,"node_id":"MDQ6VXNlcjEyOTk3ODY5","avatar_url":"https://avatars1.githubusercontent.com/u/12997869?v=4","gravatar_id":"","url":"https://api.github.com/users/wujunze","html_url":"https://github.com/wujunze","followers_url":"https://api.github.com/users/wujunze/followers","following_url":"https://api.github.com/users/wujunze/following{/other_user}","gists_url":"https://api.github.com/users/wujunze/gists{/gist_id}","starred_url":"https://api.github.com/users/wujunze/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/wujunze/subscriptions","organizations_url":"https://api.github.com/users/wujunze/orgs","repos_url":"https://api.github.com/users/wujunze/repos","events_url":"https://api.github.com/users/wujunze/events{/privacy}","received_events_url":"https://api.github.com/users/wujunze/received_events","type":"User","site_admin":false},"created_at":"2018-06-29T09:37:44Z","updated_at":"2018-06-29T09:37:44Z","author_association":"NONE","body":"好像少了 include 头代码"}}
{"actor":{"display_login":"wujunze"}}