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

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

(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

Windows/Unix文本格式转换导致“/usr/bin/env: No such file or directory”

今天在用同事写的一个python脚本,一运行就出现了以下错误:

/usr/bin/env: No such file or directory

用ls命令查看了一下,“/usr/bin/env”明明存在:

bash-3.2# ls -lt /usr/bin/env
-r-xr-xr-x 1 root bin 5780 Jan 23  2005 /usr/bin/env

用vi打开这个python脚本,第一行显示:

#!/usr/bin/env python^M

又是一个Windows/Unix文本格式转换问题。用dos2unix转换好以后,运行OK!

P.S. 请参考stakoverflow这个问题:http://stackoverflow.com/questions/3598592/invoking-perl-scripts

我心目中理想的软件开发过程

我心目中理想的软件开发过程是这样的:

(1)RD(程序员)写完一段代码后,会review两遍,这样很多基本的问题就都能被发现。在完成整块代码功能后,通过使用gdb等工具,改变程序的执行流程,以保证每个分支,每条语句都能执行一遍。在这个过程中,要检查变量的值和代码逻辑,看看是否和预期的一样。最后写测试用例,执行集成测试,确保不会出现很低级的bug;执行稳定性测试,确保程序可以长时间运行不出问题。在这一过程结束后,提交代码库,准备code review。

(2)QA(测试人员)在RD将代码提交到版本库后,开始review code,记下问题。然后在code review会议上,和RD讨论,把需要修改的地方记下来。接下来,设计测试用例,测试用例除了包含基本的功能测试和稳定性测试外,还要包含通过仔细阅读代码,找到可能出错的地方。最后进入测试阶段。

关于“error: conflicting types for ‘function’”编译错误的分析

在使用gcc编译C程序时,有时会碰到“error: conflicting types for ‘function’”的编译错误。从字面意义上理解,是说函数的定义和声明不一致。在这篇文章里,我就对这个错误做个简单的分析(使用的gcc版本是4.9.0)。

(一)首先我们看一个函数的定义和声明不一致的例子:

#include <stdio.h>
 
int func(int a);
 
int func(void) {
	return 0;
}
 
int main(void) {
 
	func();
 
	return 0;
}

编译程序:

gcc -g -o a a.c
a.c:5:5: error: conflicting types for ‘func’
 int func(void) {
     ^
a.c:3:5: note: previous declaration of ‘func’ was here
 int func(int a);

可以看到由于“func”的声明和定义不一致(一个有参数,一个没有),所以编译时出现了这个错误。

(二)最近我在把一个老程序从Solaris移植到Linux,编译时也出现了这个错误。但是我发现函数在头文件里的声明和函数定义是完全一样的,这就令我很奇怪。查了将近一天时间,最后得到结论是函数参数类型在函数声明后定义了。简化的代码如下:

#include <stdio.h>

void func(struct A *A);

struct A {
        int a;
};

void func(struct A *A)
{
        printf("%d", A->a);
}

int main(void) {
        // your code goes here
        struct A a = {4};
        func(&a);
        return 0;
}

其中“structure A”的定义放在“func”函数声明之后了,而func函数的参数是“structure A*”类型。编译结果如下:

gcc -g -o a a.c
a.c:3:18: warning: ‘struct A’ declared inside parameter list
 void func(struct A *A);
                  ^
a.c:3:18: warning: its scope is only this definition or declaration, which is probably not what you want
a.c:9:6: error: conflicting types for ‘func’
 void func(struct A *A)
      ^
a.c:3:6: note: previous declaration of ‘func’ was here
 void func(struct A *A);
      ^

可以看到也输出了error: conflicting types for ‘func’”的编译错误,也许编译警告可以给一点提示吧。

我检查了一下程序的Makefile,所有编译警告都关掉了,也许是编译警告太多了吧。

为什么gcc在64位Solaris上编译出来的程序默认是32位的?

最近发现一个问题,gcc在64位Solaris上编译出来的程序默认是32位的,而在64位Linux上编译出来的程序默认就是64位的,觉得有点奇怪,就在stackoverflow上问了一下(http://stackoverflow.com/questions/25560539/how-does-gcc-determine-if-to-generate-a-32-bit-or-64-bit-executable-file-by-defa)。其中一个回答给出了一个链接(https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58833),原来这是Solaris有意而为之。总结一下,有以下几点:

(1)64位的gcc或程序不一定比32位运行快;

(2)Studio程序默认是32位的,gcc最好和它行为保持一致;

(3)从用户体验出发,以前都是默认生成32位程序,现在一下变成64位,用户可能需要改很多配置;

(4)64位Solaris位的gcc可以既编译32位,又编译64位,看用户自己的选择了。

使用SonarQube的一点经验

这两天在折腾SonarQube(也就是Sonar,以下简称为“Sonar”),有一点心得体会在这里记录下来(网上能找到的这里就不写了):

(一)使用“./sonar.sh console”命令

如果不确定Sonar是否配置成功,可以先使用这个命令。成功的启动输出如下:


确定能启动成功后,再使用“./sonar.sh start”命令。

(二)查看sonar log

有时,启动sonar不成功而且提示信息也不够时(像下面这样),


可以通过查看sonar的log,来定位问题。Log文件位置配置在conf目录下单wrapper.conf。

比方说从下面的log就可以定位到问题是缺少java插件:


(三) 导入plugin的license

有些plugin是收费的,需要license。导入license的方法如下:

a)以administrator身份登录;

b)进入Settings->General Settings->Licenses,粘贴license即可。

参考这个链接

(四) 保存和导入quality profile

有的时候,我们想保存修改过的quality profile,然后在其它的SonarQube上使用。

保存profile:Quality Profiles->backup;

导入profile:Quality Profiles->Restore Profile。

参考这个链接

liteIDE写Golang程序引用外面的package不能自动补全的问题

在使用liteIDE开发Golang程序时,会出现Golang自带的package可以自动补全,而引用外面的package则不能自动补全。今天终于在stackoverflow(http://stackoverflow.com/questions/19876902/liteide-no-autocomplete)找到了答案:在使用外面的package时,应该把package安装成功(使用go get, go install命令),才可以使用自动补全。

监控redis的内存使用情况

redis可以配置使用内存的最大值,配置项是maxmemory。如果不设置的话,64位程序默认内存没有限制,而32位程序的默认值为3G。相关代码如下:

    if (server.arch_bits == 32 && server.maxmemory == 0) {
        redisLog(REDIS_WARNING,"Warning: 32 bit instance detected but no memory limit set. Setting 3 GB maxmemory limit with 'noeviction' policy now.");
        server.maxmemory = 3072LL*(1024*1024); /* 3 GB */
        server.maxmemory_policy = REDIS_MAXMEMORY_NO_EVICTION;
    }

 

redis使用zmalloc去做内存的分配和释放工作,这样便于统计当前redis进程消耗了多少内存。

 

通过“info”命令,我们可以得到当前redis已使用的内存值:used_memory字段;通过“config get maxmemory”命令可以得到redis可使用内存的最大值。这样一旦监控程序发现redis使用的内存超过一个百分比(例如:80%),就可以通知我们,进而让我们采取相应的措施。

参考资料:

(1)When would one use malloc over zmalloc?

(2)Why redis need zmalloc?