如何卸载gdb?

前几天安装最新的gdb过程中出了点问题,想卸载一下。结果执行“make uninstall”命令后,出现下面的结果:

bash-3.2# make uninstall
the uninstall target is not supported in this tree

看起来,gdb不支持直接用“make uninstall”命令卸载,那么如何卸载它呢?我在gdb mailing list里问了一下,收到了Doug Evans回信

Yikes.

A clumsy workaround is to cd into each subdir in the build tree and do
make uninstall there.

只能是进入每个子目录,分别执行“make uninstall”命令了。

后来,他又在一封单独的信中提到,他已经提交了bug,看看会不会有人做一下改进了。

本文转载自我在hellogcc上文章:http://www.hellogcc.org/?p=34112

gdb 7.8.1 release了,7.9还会远吗?

今天早晨收到邮件,gdb 7.8.1版本release了。看了一下release note,应该就是修改了一些bug。在另一封邮件里,Joel Brobecker提到了gdb 7.9 release的时间表:如果一切顺利的话,会在明年的1月26日。Looking forward to it!

参考邮件:
[ANNOUNCEMENT] GDB 7.8.1 released!
next GDB release schedule (GDB 7.9?)

学习Bash shell编程资料推荐

我一直觉得写好Bash shell脚本是一件很cool的事,短短几行代码,就能完成其它编程语言几十行甚至上百行代码才能完成的功能,可惜我自己写Bash shell脚本能力实在不敢恭维。在这篇文章,我把自己认为一些比较好的Bash shell编程资料分享出来,希望可以给大家一点帮助。

我个人看过的最好的Bash shell编程入门资料是《Linux程序设计》的第二章:shell程序设计,看这一章的同时自己动手实践,我觉得入门基本没问题了。

入门之后,我建议大家一定要看CU论坛上shell大牛网中人经典的《shell十三问》系列文章,这些文章把很多shell编程tricky的东西讲的很细,很明白。我每次读都有新的收获。

关于如何调试shell程序,推荐coolshell上的文章:如何调试bash脚本,在我这篇文章结尾提到的bashdb调试器,我这几天还用了一下。虽然比起gdb还差了很远(比如没有命令自动补全功能,等等),但总体感觉还可以,可以提高调试效率。

最后推荐两个网站:
http://www.shellcheck.net/:用来检测shell脚本,帮你发现问题。
http://explainshell.com/:用来帮你理解shell脚本。

好了,动手实践吧。Happy bashing! Happy hacking!

在Solaris上使用LD_OPTIONS环境变量诊断编译链接问题

最近在Solaris上编译一款开源软件,在最后链接阶段出了问题,导致ld程序core dump。由于没有ld程序源代码,导致完全没思路,没办法,只好在stackoverflow上求教:http://stackoverflow.com/questions/26009192/why-the-ld-crash-in-building-libgd。从回复中我才知道可以通过设置LD_OPTIONS环境变量,来了解整个链接过程。举个例子:

LD_OPTIONS=-Dfiles,detail

指定files会输出ld当前处理的文件,detail会提供更多的信息。

可以用“ld -Dhelp”命令打印每个选项的详细帮助信息。

如果想详细了解Solaris下程序的链接过程,可以参考这篇文档:Linker and Libraries Guide

 

2014“十一”回家之旅

眨眼之间,上次回家还是2012年春节的事,离现在快三年了。今年十一,我又踏上回家之旅。

在回去的硬座车厢里,人们大都低头看手机和pad。少数人在聊着天,吃着瓜子,打着扑克。又听到了熟悉的东北方言,很是亲切。在硬座车厢这个临时的大家庭里,很容易把人关系拉近。我不大愿意说话,就是坐车的老配置:一份《体坛周报》,一份《南方周末》,另外又带上了《MacTalk 人生元编程》。十几个小时的时间里,把它们都看完了。《MacTalk》这本书介绍Mac相关的技巧我没有看,其它的文章基本都看了,还是能给人一点启示的。

在家的日子很单调,也很自在。家乡很多老人都已经故去了,而和我年龄相仿的人大多也已离开家乡,去了发展更好的城市。现在镇上的住户大多都是周围乡下和农村的,随之带来的效应便是周围的村子基本已名存实亡了,也许这就是城镇化发展的必然吧。

离别那天,在通辽遇到了好久没联系的老友们,有的居然十几年没见了。虽然现在的人生轨迹都不大一样,但是大家之间并没有因此感到生疏,很开心!回来的路上,买了一本俞敏洪写的《在绝望中寻找希望》,写的很不错,推荐大家都读一下。

我发现只有离开北京,才能让自己真正的放松下来,两个月前的昆山之行亦是如此。我很是希望自己能够多一些这样真正放松的日子。

tcpreplay(3.4版本)程序显示统计结果的一个问题

最近,我在Solaris系统上用tcpreplay程序(sunfreeware网站提供的3.4.4版本的安装包,目前sunfreeware已经不再提供免费的Solaris系统安装包下载了,但还是可以通过其它ftp下载到)辅助测试。发现tcpreplay输出是这样的:

Actual: 400000 packets (78200000 bytes) sent in 18.03 seconds.          Rated: 4337216.0 bps, 33.09 Mbps, 22185.25 pps

如果Rated显示的bpsMbps里的b都指的是byte的话,那么它们应该相差(1000*1000)倍才对,但是很明显4337216.033.09没有相差那么多。

github上找了一下tcpreplay 3.4版本的源代码,发现了这个patch(https://github.com/appneta/tcpreplay/commit/42722b8945209dcbb850eef39e0dbbd582eccc3d)。可以看到第一个bps里的b指的是byte,而第二个Mbps里的b指的是bit。这个tcpreplay安装包很显然没有包含这个patch,所以会让人产生误解。

P.S.打完patch后的tcpreplay输出是这样的:

Actual: 2 packets (1532 bytes) sent in 0.015702 seconds.
Rated: 97567.1 Bps, 0.780 Mbps, 127.37 pps

看着很清晰。

提高个人能力的几个小建议

我是一名软件工程师,本文所提到的建议是我个人的一点心得体会,也许并不一定适合每个人,还望大家根据自身情况做出判断。

(1)拥有一个云笔记。两年以前,我还是习惯将日常工作的知识积累记在本上,但是我逐渐发现,随着笔记越积累越厚,想查找想要的内容并不是一件很容易的事,所以我开始把内容分门别类地记在云笔记上,这样查找起来就方便了很多。另外,我也会把搜索到的网页内容直接拷贝到笔记上,而不是只记录一个链接。这样即使将来网站关闭了,内容也被我保存下来了。

(2)创建自己的个人技术博客。当我在工作中遇到技术问题时,经常从别人的技术博客里找到答案。后来,我就想我的经验和知识也能帮助其他的人。于是,我也开始写自己的博客。其实写文章并不是一件很简单的事,很多内容你认为理解了,但真的提笔写下来的时候,才发现要表达清楚不是很容易。而且随着时间的流逝,发现自己写过的文章越来越多时,也是很有成就感的。目前很多人都有自己的域名,如果你嫌申请域名,搭建博客太麻烦,那么就找个口碑好的博客平台,注册个账号就可以了。但是博客一定要有,过个几年,你就知道这是一个多么正确的决定。

(3)多读英文技术文章。对于我们这些母语不是英文的人,坚持读英文技术文章是可以同时提高英文水平和技术能力的。毕竟目前先进的技术文档和书籍都是英文的,所以我们不仅不应该惧怕英文,而且还应该自己试着去写英文文章,这样才能让自己的文章被世界更多人的读到。此外,也可以订阅感兴趣领域专家的博客RSS。比如我对如何调试系统性能比较感兴趣,我就会订阅Brendan Gregg的博客,这样每当Brendan有新的文章发布时,我都会及时收到通知并阅读。

以上是我自己工作的一点心得体会,希望可以给大家一点帮助吧。

gcc的 “-fpack-struct” 编译选项导致程序core dump的分析

最近team引入gcov来做代码分析。编译好的程序在Solaris上运行的好好的,结果在Linux上一运行就会产生core dump文件。这篇文章就介绍整个分析过程。

首先用gdb分析core文件,显示是strlen调用出了问题:

(gdb) bt
#0  0x00000034e433386f in __strlen_sse42 () from /lib64/libc.so.6
#1  0x000000000053c57a in __gcov_init ()
#2  0x000000000053c4b9 in _GLOBAL__I_65535_0_g_cmd_param () at source/rerun/aicent_ara_rerun.c:963
#3  0x000000000053dc26 in __do_global_ctors_aux ()
#4  0x0000000000403743 in _init ()
#5  0x00007fff6d6b3ce8 in ?? ()
#6  0x000000000053db55 in __libc_csu_init ()
#7  0x00000034e421ecb0 in __libc_start_main () from /lib64/libc.so.6
#8  0x0000000000404449 in _start ()

由于我们使用的gcc是用安装包形式安装的,没有源码。所以就从github上找了相应版本的gcc源代码,希望能有所帮助。以下是__gcov_init函数的代码(https://github.com/gcc-mirror/gcc/blob/gcc-4_4_7-release/gcc/libgcov.c):

void
__gcov_init (struct gcov_info *info)
{
  if (!info->version)
    return;
  if (gcov_version (info, info->version, 0))
    {
      const char *ptr = info->filename;
      gcov_unsigned_t crc32 = gcov_crc32;
      size_t filename_length =  strlen(info->filename);

      /* Refresh the longest file name information */
      if (filename_length > gcov_max_filename)
        gcov_max_filename = filename_length;

      do
    {
      unsigned ix;
      gcov_unsigned_t value = *ptr << 24;

      for (ix = 8; ix--; value <<= 1)
        {
          gcov_unsigned_t feedback;

          feedback = (value ^ crc32) & 0x80000000 ? 0x04c11db7 : 0;
          crc32 <<= 1;
          crc32 ^= feedback;
        }
    }
      while (*ptr++);

      gcov_crc32 = crc32;

      if (!gcov_list)
    atexit (gcov_exit);

      info->next = gcov_list;
      gcov_list = info;
    }
  info->version = 0;
}

结合源代码和core文件可以看出,应该是“size_t filename_length = strlen(info->filename);”这一行出了问题。再结合汇编程序:

(gdb) disassemble __strlen_sse42
Dump of assembler code for function __strlen_sse42:
   0x00000034e4333860 <+0>:     pxor   %xmm1,%xmm1
   0x00000034e4333864 <+4>:     mov    %edi,%ecx
   0x00000034e4333866 <+6>:     mov    %rdi,%r8
   0x00000034e4333869 <+9>:     and    $0xfffffffffffffff0,%rdi
   0x00000034e433386d <+13>:    xor    %edi,%ecx
=> 0x00000034e433386f <+15>:    pcmpeqb (%rdi),%xmm1

是访问rdi寄存器出了问题,而rdi保存的应该是strlen的参数,也就是“info->filename”。试着访问一下rdi寄存器保存的地址:

(gdb) i registers rdi
rdi            0x57c4ac00000000 24704565987246080
(gdb) x/16xb 0x57c4ac00000000
0x57c4ac00000000:       Cannot access memory at address 0x57c4ac00000000

可以看到rdi寄存器保存的地址的确是个无效的地址。

接下来,就要分析一下为什么传入__gcov_initinfo结构体的filename是一个无效指针。首先看一下gcov_info结构体的定义(https://github.com/gcc-mirror/gcc/blob/gcc-4_4_7-release/gcc/gcov-io.h):

/* Information about a single object file.  */
struct gcov_info
{
  gcov_unsigned_t version;  /* expected version number */
  struct gcov_info *next;   /* link to next, used by libgcov */

  gcov_unsigned_t stamp;    /* uniquifying time stamp */
  const char *filename;     /* output file name */

  unsigned n_functions;     /* number of functions */
  const struct gcov_fn_info *functions; /* table of functions */

  unsigned ctr_mask;        /* mask of counters instrumented.  */
  struct gcov_ctr_info counts[0]; /* count data. The number of bits
                     set in the ctr_mask field
                     determines how big this array
                     is.  */
};

查看调用__gcov_init_GLOBAL__I_65535_0_g_cmd_param函数的汇编代码:

(gdb) disassemble _GLOBAL__I_65535_0_g_cmd_param
Dump of assembler code for function _GLOBAL__I_65535_0_g_cmd_param:
   0x000000000053c4ab <+0>:     push   %rbp
   0x000000000053c4ac <+1>:     mov    %rsp,%rbp
   0x000000000053c4af <+4>:     mov    $0x78d4a0,%edi
   0x000000000053c4b4 <+9>:     callq  0x53c4c0 <__gcov_init>
   0x000000000053c4b9 <+14>:    leaveq
   0x000000000053c4ba <+15>:    retq
End of assembler dump.

可以看到传入__gcov_init的参数为0x78d4a0,也就是指向gcov_info结构体的地址,查看这个地址的内容:

(gdb) x/64xb 0x78d4a0
0x78d4a0:       0x52    0x34    0x30    0x34    0x00    0x00    0x00    0x00
0x78d4a8:       0x00    0x00    0x00    0x00    0x82    0xf0    0xc7    0xa5
0x78d4b0:       0x60    0xc4    0x57    0x00    0x00    0x00    0x00    0x00
0x78d4b8:       0x0b    0x00    0x00    0x00    0xac    0xc4    0x57    0x00
0x78d4c0:       0x00    0x00    0x00    0x00    0x01    0x00    0x00    0x00
0x78d4c8:       0x39    0x01    0x00    0x00    0xc0    0xa4    0x47    0x03
0x78d4d0:       0x00    0x00    0x00    0x00    0xe0    0xd8    0x53    0x00
0x78d4d8:       0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00

可以看到对应filename成员的值应该为0x57c4ac0000000b,的确是个无效地址。问题分析到这里,没了思路。后来,在gccbugzilla里找到这个问题:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=43341,才搞清楚是“-fpack-struct=4”这个编译选项导致的。

我们使用的是64位Linux,默认编译生成的可执行文件是64位的。所以gcov_info的默认内存布局应该是(gcov_unsigned_t类型占4个字节,指针类型占8个字节):

Offset 4 bytes 4 bytes
0 version 填充成员
8 next next
16 stamp 填充成员
24 filename filename

当使用“-fpack-struct=4”这个编译选项后,gcov_info的内存布局变为:

Offset 4 bytes 4 bytes
0 version next
8 next stamp
16 filename filename

经过推算,filename成员的值应该为0x57c460,验证一下:

(gdb) p (char*)0x57c460
$1 = 0x57c460 "/home/.../.....gcda"

打印出的是正确的值。在Solaris上没问题的原因是因为64位Solaris默认编译出来的程序是32位的。

看了一下gcc网站对-fpack-struct的介绍(https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fpack-struct-2675),使用这个编译选项会导致ABI(Application Binary Interface)的改变,所以使用时一定要谨慎。

本文转载自我在hellogcc上文章:http://www.hellogcc.org/?p=34087

往GNU邮件组发邮件要用纯文本格式

昨天遇到gcc使用方面的一个问题,就往gcc-help(gcc-help@gcc.gnu.org)邮件组发了一封求助邮件。但是浏览归档(https://gcc.gnu.org/ml/gcc-help/)找不到我发的邮件,应该是没有发送成功。

今天在hellogcc的IRC里请教了一下,才知道应该用纯文本格式发送。我用的是QQ邮箱,没找到如何设置纯文本发送(后经好心网友发邮件提醒:在邮件编辑区之下的“其他选项”中勾选“纯文本”,就可以了)。于是就改用gmail的邮箱发送,果然一下就成功了。以后记住往GNU组织(包括gcc,gdb等所有的邮件组)发邮件一定要用纯文本格式。
P.S. 关于gmail如何使用纯文本发送邮件,可以参考这个链接:https://support.google.com/mail/answer/2645922?hl=zh-Hans