文档章节

[翻译]内存 - 第五部分:调试工具

realm520
 realm520
发布于 2016/03/07 20:19
字数 1730
阅读 27
收藏 1

原文地址:https://techtalk.intersec.com/2013/12/memory-part-5-debugging-tools/

介绍

我们花了4篇文章介绍了什么是内存,如何处理内存,内存会给你带来什么问题。最好的开发人员也会写出有bug的代码。通常可接受的估算是每千行代码的bug数,这肯定是一个相当大的数字。因此,即使你熟练的掌握了我们的文章讲述的各种概念,你仍然可能写一些跟内存有关的bug。

内存相关的bug特别难定位和修复。我们以下面这个程序为例:

#include <stdio.h>
 
#define MAX_LINE_SIZE  32
 
static const char *build_message(const char *name)
{
    char message[MAX_LINE_SIZE];
 
    sprintf(message, "hello %s!\n", name);
    return message;
}
 
int main(int argc, char *argv[])
{
    fputs(build_message(argc > 1 ? argv[1] : "world"), stdout);
    return 0;
}



这个程序期望从参数获得一个消息,并打印“Hello <消息>!”(默认消息是"world")。

这个程序的行为是未定义的,有bug,并且可能会崩溃。build_message函数返回了执行它的栈上内存的指针。因为栈的工作机制,这块内存非常可能被后面调用的其他函数改写,很可能是fputs。因此,如果fputs内部使用了足够多的栈内存来改写消息,输出会被破坏(程序甚至可能会崩溃)。其他情况,程序可能会打印正常的消息。更进一步,程序可能会导致buffer溢出,因为使用了不安全的sprintf函数,这个函数没有对输入做任何限制。

因此,程序的行为取决于命令行输入的消息的大小,即fputs实现中MAX_LINE_SIZE的值。这种bug让人恼火的原因是,它的结果不是那么明显:在简单的消息情况下,它“工作”得很好,并且只会在收到正好让问题暴露出来的参数时,错误才会出现。这就是为什么开发人员要用一些工具来帮助验证(或调试)内存管理才能安心。

本文将会介绍几个免费的工具,我们认为一个C(或C++)开发人员的工具库里应该必备。

调试器

第一个是调试器。在Linux平台很可能是gdb。大多数开发人员知道gdb的基本用法:查看程序栈(bt, up, down, frame <id> ...),添加一个断点(break <function|line>, continue ...),单步执行(step, next, fin ...),查看内存(print <expr>, call <func>, x/<FMT> <addr>, ...)等等。当程序因为段错误崩溃时,调试器是大多数开发人员会选择的工具。调试器会捕获信号,并允许查看在那个时刻的程序状态。大多数的段错误都很明显(未初始化指针,空指针解引用...),只需要用调试器花一点功夫。

但是很少被人知道的是,调试器可以放置一个监控点:添加一个动态的断点,每次在一个表达式的结果改变的时候中断程序。这在检测内存被破坏的原因时及其有用:在内存的内容被破坏的位置放一个监控点,程序将会每次在那块内容变化时中断。这对程序的性能影响很小,只要你不监控太多的内存地址,这些监控点由硬件直接管理。

让我们回头看介绍中提到的那个例子:我们用fputs打印第一个指针参数指向的内容,但实际被打印的字符串并不是我们在build_message里面写出来的。看一下这一小部分调试会话:

  • 首先我们在构造消息的地方设置了断点,并且检查sprintf正确的构造了消息。

(gdb) break build_message
Breakpoint 1 at 0x400598: file blah.c, line 7.
(gdb) run
Starting program: /home/fruneau/blah
warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
 
Breakpoint 1, build_message (name=0x4006bf "world") at blah.c:7
7	    sprintf(message, "hello %s!\n", name);
(gdb) n
8	    return message;
(gdb) p message
$1 = "hello world!\n\000\000\000\001\000\000\000\000\000\000\000m\006@\000\000\000\000"



  • 为了在任何消息被修改的时候得到通知,我们在字符串的第一个字符上设置了一个监控点,然后让程序继续运行。调试器告诉我们成功设置了一个硬件监控点,这很好,因为软件监控点对整体性能会有更显著的影响。

(gdb) watch $1[0]
Hardware watchpoint 2: $1[0]
(gdb) c
Continuing.



  • 监控点中断了程序的执行。调试器打印出旧的和新的值,我们可以容易的查看程序。快速的查看一下堆栈,我们发现处于一段动态链接的代码中(很可能处于fputs符号的解析中)。

Hardware watchpoint 2: $1[0]
 
Old value = 104 'h'
New value = 32 ' '
0x00007ffff7def1fc in ?? () from /lib64/ld-linux-x86-64.so.2
(gdb) bt
#0  0x00007ffff7def1fc in ?? () from /lib64/ld-linux-x86-64.so.2
#1  0x00000000004005ff in main (argc=1, argv=0x7fffffffe258) at blah.c:13



这里,调试器告诉我们,内存被改变了。但是理解这个问题,需要知道发生了什么。调试器提供了原始的信息,开发人员还需要分析。通常来说,当你知道要看什么的时候,调试器是一个好工具。

valgrind

valgrind是C/C++开发人员的瑞士军刀。它提供了各种工具,例如内存检查器(memcheck),内存分析器(massif),缓存分析器(cachegrind),CPU分析器(callgrind),还有一些线程检查器(helgrind, DRD, tsan)等等。

valgrind是一个基础的虚拟机,它监控了每一次跟操作系统和虚拟硬件的交互。为了实现这个功能,它执行一个未修改的可执行文件,并用带监控的版本包装每一个CPU指令和系统调用。它提供了相当多的可配置项:你可以定义你的虚拟机的预期行为:核心数,缓存大小,系统调用的行为(有些系统调用在不同版本的内核上行为不一样)。主要的缺点是,由于指令不是直接执行,valgrind带来很大的额外负担,导致性能下降5到50倍(取决于你选择的工具和选项)。

运行valgrind很容易。它不需要修改你的程序或构建系统。最基本的用法:valgrind --tool=<toolname> <yourprogram and arguments>。

memcheck

memcheck是valgrind的默认工具。他是一个内存检查器,它追踪每一次内存访问和分配,并查找这些管理错误:

  • 访问未分配的内存
  • 程序行为依赖未初始化的内存
  • 内存泄漏

为了做到这些,memcheck做的第一件事情就是维护一个已分配内存的注册表。每次一块新的内存被分配,memcheck记住返回的指针,并开始追踪它。

(未完)

© 著作权归作者所有

realm520
粉丝 9
博文 13
码字总数 25892
作品 0
南京
架构师
私信 提问
【译】你不知道的 Chrome 调试工具技巧 第十六天:断点

特别声明 本文是作者 Tomek Sułkowski 发布在 medium 上的一个系列。据作者透露一共有 24 篇,一直更新到 12 月 24 日 版权归原作者所有。 作者在Twitter上推荐我们的中文翻译啦,截图在最后...

dendoink
2018/12/20
0
0
初级iOS开发者进阶之路(包括一些面试心得 )

最近随着公司规模扩大(还是小公司),公司开始计划招人,很幸运,终于能当一回面试官了,可以从面试者到面试官的转变让我对 iOS 招聘有了更多的感受。经过一段时间的面试,我们还是没有找到志...

夜空下最亮的亮点
2017/11/09
0
0
【译】你不知道的 Chrome 调试工具技巧 第十天:custom formatters(自定义格式转换器)

特别声明 本文是作者 Tomek Sułkowski 发布在 medium 上的一个系列。据作者透露一共有 24 篇,一直更新到 12 月 24 日 版权归原作者所有。 译者在翻译前已经和作者沟通得到了翻译整个系列的...

dendoink
2018/12/14
0
0
【译】你不知道的 Chrome 调试工具技巧 第八天:Color picker(颜色选择器)

特别声明 本文是作者 Tomek Sułkowski 发布在 medium 上的一个系列。据作者透露一共有 24 篇,一直更新到 12 月 24 日 版权归原作者所有。 前两篇的翻译链接我已经给到了作者本人,虽然他不...

dendoink
2018/12/12
0
0
【译】你不知道的 Chrome 调试工具技巧 第十八天:Drawer 里的秘密

特别声明 本文是作者 Tomek Sułkowski 发布在 medium 上的一个系列。据作者透露一共有 24 篇,一直更新到 12 月 24 日 版权归原作者所有。 作者在Twitter上推荐我们的中文翻译啦,截图在最后...

dendoink
2018/12/20
0
0

没有更多内容

加载失败,请刷新页面

加载更多

java通过ServerSocket与Socket实现通信

首先说一下ServerSocket与Socket. 1.ServerSocket ServerSocket是用来监听客户端Socket连接的类,如果没有连接会一直处于等待状态. ServetSocket有三个构造方法: (1) ServerSocket(int port);...

Blueeeeeee
今天
6
0
用 Sphinx 搭建博客时,如何自定义插件?

之前有不少同学看过我的个人博客(http://python-online.cn),也根据我写的教程完成了自己个人站点的搭建。 点此:使用 Python 30分钟 教你快速搭建一个博客 为防有的同学不清楚 Sphinx ,这...

王炳明
昨天
5
0
黑客之道-40本书籍助你快速入门黑客技术免费下载

场景 黑客是一个中文词语,皆源自英文hacker,随着灰鸽子的出现,灰鸽子成为了很多假借黑客名义控制他人电脑的黑客技术,于是出现了“骇客”与"黑客"分家。2012年电影频道节目中心出品的电影...

badaoliumang
昨天
15
0
很遗憾,没有一篇文章能讲清楚线程的生命周期!

(手机横屏看源码更方便) 注:java源码分析部分如无特殊说明均基于 java8 版本。 简介 大家都知道线程是有生命周期,但是彤哥可以认真负责地告诉你网上几乎没有一篇文章讲得是完全正确的。 ...

彤哥读源码
昨天
15
0
jquery--DOM操作基础

本文转载于:专业的前端网站➭jquery--DOM操作基础 元素的访问 元素属性操作 获取:attr(name);$("#my").attr("src"); 设置:attr(name,value);$("#myImg").attr("src","images/1.jpg"); ......

前端老手
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部