kk Blog —— 通用基础

date [-d @int|str] [+%s|"+%F %T"]

debugedit,find-debuginfo 改进及el7

http://vault.centos.org/7.2.1511/os/Source/SPackages/rpm-4.11.3-17.el7.src.rpm

修改tool/debugedit.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
diff --git a/tools/debugedit.c b/tools/debugedit.c
index 0f85885..257f5f8 100644
--- a/tools/debugedit.c
+++ b/tools/debugedit.c
@@ -602,13 +602,14 @@ edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase)
    if (base_dir == NULL)
      p = s;
    else if (has_prefix (s, base_dir))
-     p = s + strlen (base_dir);
+     { p = s + strlen (base_dir); while (*p == '/') p++; }
    else if (has_prefix (s, dest_dir))
-     p = s + strlen (dest_dir);
+     { p = s + strlen (dest_dir); while (*p == '/') p++; }
 
-   if (p)
+   if (p && strlen (p) > 2 && flock(list_file_fd, LOCK_EX) == 0)
      {
        size_t size = strlen (p) + 1;
+     
        while (size > 0)
      {
        ssize_t ret = write (list_file_fd, p, size);
@@ -617,6 +618,7 @@ edit_dwarf2_line (DSO *dso, uint32_t off, char *comp_dir, int phase)
        size -= ret;
        p += ret;
      }
+     flock(list_file_fd, LOCK_UN);
      }
  }
 
@@ -928,17 +930,18 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
      it and the debugger (GDB) cannot safely optimize out the missing
      CU current dir subdirectories.  */
   if (comp_dir && list_file_fd != -1)
-    {
+  {
       char *p;
       size_t size;
 
       if (base_dir && has_prefix (comp_dir, base_dir))
- p = comp_dir + strlen (base_dir);
+     { p = comp_dir + strlen (base_dir); while (*p == '/') p++; }
       else if (dest_dir && has_prefix (comp_dir, dest_dir))
- p = comp_dir + strlen (dest_dir);
+     { p = comp_dir + strlen (dest_dir); while (*p == '/') p++; }
       else
  p = comp_dir;
 
+    if (p && strlen (p) > 2 && flock(list_file_fd, LOCK_EX) == 0) {
       size = strlen (p) + 1;
       while (size > 0)
  {
@@ -949,6 +952,8 @@ edit_attributes (DSO *dso, unsigned char *ptr, struct abbrev_tag *t, int phase)
    p += ret;
  }
     }
+    flock(list_file_fd, LOCK_UN);
+  }
 
   if (found_list_offs && comp_dir)
     edit_dwarf2_line (dso, list_offs, comp_dir, phase);
@@ -1548,7 +1553,7 @@ main (int argc, char *argv[])
     canonicalize_path(dest_dir, dest_dir);
 
   /* Make sure there are trailing slashes in dirs */
-  if (base_dir != NULL && base_dir[strlen (base_dir)-1] != '/')
+  /*if (base_dir != NULL && base_dir[strlen (base_dir)-1] != '/')
     {
       p = malloc (strlen (base_dir) + 2);
       strcpy (p, base_dir);
@@ -1563,7 +1568,7 @@ main (int argc, char *argv[])
       strcat (p, "/");
       free (dest_dir);
       dest_dir = p;
-    }
+    }*/
 
   if (list_file != NULL)
     {

debugedit_el7


find-debuginfo.sh ‘extracting debug info'时改成多进程

1. 首先要像上面那样debugedit.c加入文件锁

c 文件锁flock

2. shell模拟多进程

shell 多进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 先建立fd
tmp_fifo="/tmp/.tmp_fifo"

mkfifo $tmp_fifo
exec 6<>$tmp_fifo
rm $tmp_fifo

# 假设8进程,先往fd中写入8字节
forks=8
for ((i=0;i<$forks;i++))
do  
	echo >&6 
done

# Strip ELF binaries
find "$RPM_BUILD_ROOT" ! -path "${debugdir}/*.debug" -type f \
               \( -perm -0100 -or -perm -0010 -or -perm -0001 \) \
		     -print |
file -N -f - | sed -n -e 's/^\(.*\):[     ]*.*ELF.*, not stripped/\1/p' |
xargs --no-run-if-empty stat -c '%h %D_%i %n' |
while read nlinks inum f; do
  get_debugfn "$f"
  [ -f "${debugfn}" ] && continue

  # If this file has multiple links, keep track and make
  # the corresponding .debug files all links to one file too.
  if [ $nlinks -gt 1 ]; then
    eval linked=\$linked_$inum
    if [ -n "$linked" ]; then
      link=$debugfn
      get_debugfn "$linked"
      echo "hard linked $link to $debugfn"
      ln -nf "$debugfn" "$link"
      continue
    else
      eval linked_$inum=\$f
      echo "file $f has $[$nlinks - 1] other hard links"
    fi
  fi

  # 从fd读取一个字节,开始一个进程
  read -u6
  {
    echo "extracting debug info from $f"
    id=$(/usr/lib/rpm/debugedit_kk -b "$RPM_BUILD_DIR" -d "/usr/src/debug$REPLACE_COMP_DIR" \
			      -i -l "$SOURCEFILE" "$f")
    if $strict && [ -z "$id" ]; then
      echo >&2 "*** ${strict_error}: No build ID note found in $f"
      $strict && exit 2   # 需要先写回一个字节???
    fi

    # A binary already copied into /usr/lib/debug doesn't get stripped,
    # just has its file names collected and adjusted.
    case "$dn" in
    /usr/lib/debug/*)
      [ -z "$id" ] || make_id_link "$id" "$dn/$(basename $f)"
      continue ;;  # 需要先写回一个字节???
    esac

    mkdir -p "${debugdn}"
    if test -w "$f"; then
      strip_to_debug "${debugfn}" "$f"
    else
      chmod u+w "$f"
      strip_to_debug "${debugfn}" "$f"
      chmod u-w "$f"
    fi

    if [ -n "$id" ]; then
      make_id_link "$id" "$dn/$(basename $f)"
      make_id_link "$id" "/usr/lib/debug$dn/$bn" .debug
    fi

    echo >&6 # 一个进程结束写回一个字节
    exit 0   # 退出子进程
  } &
done || exit

wait # 等待所有子进程结束
exec 6>&-  # 删除fd

shell 多进程

http://www.linuxidc.com/Linux/2011-03/33918.htm

一次性并发

1
2
3
4
5
6
7
8
9
10
11
#!/bin/sh
for ((i=1;i<10;i++))
do
{
	echo "run $i "`date +%s`
	sleep $i
	echo "end $i "`date +%s`
	exit 0
} &
done
wait

一次性并发forks个,forks个进程都结束后再并发forks个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/sh
forks=3
n=0
for ((i=1;i<10;i++))
do
{
	{
		echo "run $i "`date +%s`
		sleep $i
		echo "end $i "`date +%s`
		exit 0
	} &
	let n=$n+1
	if [ $n -eq $forks ]; then
		wait
		n=0
	fi
}
done
wait

模拟多线程的一种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/bin/sh

tmp_fifo="/tmp/.tmp_fifo"

mkfifo $tmp_fifo
exec 6<>$tmp_fifo
rm $tmp_fifo

forks=3
for ((i=0;i<$forks;i++))
do
	echo >&6
done

for ((i=1;i<10;i++))
do
	read -u6
	{
		echo "run $i "`date +%s`
		sleep $i
		echo "end $i "`date +%s`
		echo >&6
		exit 0
	} &
done
wait

exec 6>&-

exit 0

用Graphviz + CodeViz生成C/C++函数调用图(call graph)

有时候genfull生成的full.graph没有函数调用关系,在CentOS6上生成的图只有那个函数,在CentOS5上会报一个错误

1
Error: <stdin>: syntax error in line 4 near ';'

可以不用自带的genfull,自己写个脚本生成full.graph

1
2
3
4
5
6
7
8
9
10
11
12
13
14
echo "digraph fullgraph {" > full.graph
echo "node [ fontname=Helvetica, fontsize=12 ];" >> full.graph

find . -name '*.c.cdepn' -exec cat {} \; | \
	awk -F"[ {}]+" '{
		if ($1 == "F") {
			print "\""$2 "\" [label=\"" $2 "\\n" $3 ":\"];"
		} else if ($1 == "C") {
			print "\"" $2 "\" -> \"" $4 "\" [label=\"" $3 "\"];"
		}
	}' \
| sort | uniq -u >> full.graph

echo "}" >> full.graph

http://blog.csdn.net/lanxuezaipiao/article/details/16991731

一、Graphviz + CodeViz简单介绍

CodeViz是《Understanding The Linux Virtual Memory Manager》的作者 Mel Gorman 写的一款分析C/C++源代码中函数调用关系的open source工具(类似的open source软件有 egypt、ncc)。其基本原理是给 GCC 打个补丁(如果你的gcc版本不符合它的要求还得先下载正确的gcc版本),让它在编译每个源文件时 dump 出其中函数的 call graph,然后用 Perl 脚本收集并整理调用关系,转交给Graphviz绘制图形(Graphviz属于后端,CodeViz属于前端)。

CodeViz 原本是作者用来分析 Linux virtual memory 的源码时写的一个小工具,现在已经基本支持 C++ 语言,最新的 1.0.9 版能在 Windows + Cygwin 下顺利地编译使用。

基本介绍就到这儿,如果你对其原理比较感兴趣,可以参考这篇文章:分析函数调用关系图(call graph)的几种方法

二、Graphviz + CodeViz编译安装

CentOS5 需要安装ann_libs http://rpm.pbone.net/index.php3?stat=3&limit=2&srodzaj=3&dl=40&search=ann-libs

1. 安装 GraphViz

调用图的生成依赖于 GraphViz,所以首先要安装 GraphViz。可以下载源码包编译、安装(下载主页:http://www.graphviz.org/Download.php )。 如果是Ubuntu系统可以直接apt安装:

1
sudo apt-get install graphviz

CentOS6

1
yum install graphviz

CentOS5

1
2
3
4
wget http://www.graphviz.org/graphviz-rhel.repo
cp graphviz-rhel.repo /etc/yum.repos.d/
yum list available 'graphviz*'
yum install 'graphviz*'

2. 安装 CodeViz

1
yum -y install glibc-devel glibc-devel.i686/i386

错误

1
2
There is no layout engine support for "dot".
Perhaps "dot -c" needs to be run (with installer's privileges) to register the plugins?

执行dot -c

下载CodeVize源码包: http://www.csn.ul.ie/~mel/projects/codeviz/ 解压:tar xvf codeviz-1.0.12.tar.gz (目前最新版是1.0.12)

进入解压后的目录:cd codeviz-1.0.12/

CodeViz 使用了一个 patch 版本的 GCC 编译器,而且不同的 CodeViz 版本使用的GCC 版本也不同,可以下载 CodeViz 的源码包后查看 Makefile 文件来确定要使用的 GCC 版本,codeviz-1.0.12 使用 GCC-4.6.2。实际上安装 CodeViz 时安装脚本make会检查当前的GCC版本如果不符合则会自动下载对应的 GCC并打 patch,但由于GCC较大如果网速不好且在虚拟机中的话容易下载失败或系统错误什么的,因此这里我们还是分步安装比较好,先安装gcc再回来安装CodeViz。

(1)安装 GCC

下载gcc-4.6.2.tar.gz到 cd codeviz-1.0.12目录下的compilers里。 下载地址:ftp://ftp.gnu.org/pub/gnu/gcc/gcc-4.6.2/gcc-4.6.2.tar.gz

CodeViz 的安装脚本 compilers/install_gcc-4.6.2.sh 会自动检测 compilers 目录下是否有 gcc 的源码包,若没有则自动下载并打 patch。这里前面已经下载,直接移到该目录即可,则剩下的就是解压安装了。install_gcc-3.4.6.sh 会解压缩 gcc打 patch,并将其安装到指定目录,若是没有指定目录,则缺省使用$HOME/gcc-graph,通常指定安装在/usr/ local/gcc-graph(这时需要 root 权限)。

修改install_gcc-4.6.2.sh文件,将make bootstrap改成make bootstrap CXXFLAGS=-fPIC CFLAGS=-fPIC -j4

安装: ./install_gcc-4.6.2.sh

注意:这里可能安装时有些错误,具体错误及解决方案见后面。

(2)安装 CodeViz

./configure && make install-codeviz

注1:不需要 make ,因为make的作用就是检测是否有gcc若没有则下载源码包,所以这里只要安装 codeviz 即可。具体查看 Makefile 文件。

注意:这里为什么不是通常用的make install,因为这里make install的作用是先安装gcc再安装codeviz,而前面已经安装了 gcc,所以这里只需要安装 codeviz ,即make install-codeviz脚本,该脚本也就是将genfull 和 gengraph 复制到/usr/local/bin 目录下。

目前为止,CodeViz 安装完成了。

  • 可以不用分开装,直接make,make install也可以

三、基本实用方法

GraphViz 支持生成不同风格的调用图,但是一些需要安装额外的支持工具或者库程序,有兴趣的朋友可以到官网上查找相关资料。这里重点讲述 CodeViz 的使用方法,具体的图像风格控制不再详述。

CodeViz 使用两个脚本来生成调用图,一个是 genfull,该脚本可以生成项目的完整调用图,因此调用图可能很大很复杂,缺省使用 cdepn 文件来创建调用图;另一个是gengraph,该脚本可以对给定一组函数生成一个小的调用图,还可以生成对应的postscript 文件。安装时这两个脚本被复制到/usr/local/bin 目录下,所以可以直接使用而不需要指定路径。其基本步骤如下:

下面以编译一个简单的test.c文件为例进行说明:

1.使用刚刚安装的gcc-4.6.2来编译当前目录下所有.c文件,gcc/g++为编译的每个 C/C++文件生成.cdepn 文件。只要编译(参数 -c)就行,无需链接。

1
$ /usr/local/gccgraph/bin/gcc test.c

2.调用genful会在当前目录生成一个full.graph文件,该脚本可以生成项目的完整调用图信息文件,记录了所有函数在源码中的位置和它们之间的调用关系。 因此调用图信息文件可能很大很复杂,,缺省使用 cdepn 文件来创建调用图信息文件。

1
$ genfull

3.使用gengraph可以对给定一组函数生成一个小的调用图,显示函数调用关系。

1
$ gengraph

四、简单示例演示

自己编写个简单的程序,看下效果再说~~~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// test.c
#include <stdio.h>

void test3()
{
}
void test2()
{
	test3();
}

void test1()
{
}

int main()
{
	test1();
	test2();
	return 0;
}

按照上面的三个步骤依次进行如下图所示:

打开main.ps看到效果如下,一目了然:

五、进阶使用

当然大家使用CodeViz都不是用来玩的,而是用于真正的项目中,四中简单的使用根本不够,下面来点稍微高深点的。 1.先来分析下上面的执行流程

首先使用刚刚安装的gcc编译我们的.c文件(PS:这里一定要指定刚刚安装gcc的地方,否则用的是系统gcc而非我们安装的gcc),然后genfull创建full.graph文件,可以使用genfull --help或者genfull --man来查看如何使用。最简单的方式是在项目的顶级目录以无参数方式运行。由于项目的完全调用信息非常庞大,所以通常只是简单的生成项目的full.graph,然后在后面使用genfull获取需要的调用信息。若是需要完整信息则将full.graph由dot处理然后查看来生成的postscript文件。(dot是GraphViz中的一个工具,具体使用没有深究过,感兴趣的读者可以自行查阅~~~)。到test.c所在目录运行genfull看到生成了full.graph文件,大家可以用cat查看下。接下来使用gengraph生成函数调用图,可以使用gengraph --helpgengraph --man来查看如何使用。对于我而言,目前只关注下面几个选项就够了,即:

1
2
3
4
5
6
7
8
-f:指定顶级函数,即入口函数,如main等(当然不限定是main了);
-o:指定输出的postfile文件名,不指定的话就是函数名了,如上面的main;
--output-type:指定输出类型,例如png、gif、html和ps,缺省是ps,如上面的main.ps;
-d:指定最大调用层数;
-s:仅仅显示指定的函数,而不对其调用进行展开;
-i:忽略指定的函数
-t:忽略Linux特有的内核函数集;
-k:保留由-s忽略的内部细节形成的中间文件,为sub.graph

2.使用gengraph时的选项参数值要使用"“括起来,例如:

1
gengraph --output-type "png" -f main

3.命名冲突问题

在一个复杂的项目中,full.graph并不十分完美。例如,kernel中的模块有许多同名函数,这时genfull不能区分它们,有两种方法可以解决,其中第一种方法太复杂易错不推荐使用,这里就介绍下第二种方法,即使用genfull的-s选项,-s指定了检测哪些子目录。例如kernel中在mm目录和drivers/char/drm目录下都定义了alloc_pages函数,那么可以以下列方式调用genfull:

1
genfull -s "mm include/linux drivers/block arch/i386"

实际的使用中,-s非常方便,请大家记住这个选项。

4.使用Daemon/Client模式

当full.graph很大时,大量的时间花费到读取输入文件上了,例如kernel的full.graph是很大的,前面生成的大约有15M,这还不是全部内核的函数调用分析信息。为了节省时间,可以讲gengraph以daemon方式运行,这药使用-p选项:

1
gengraph -p -g linux-2.6.25/full.graph

该命令返回时gengraph以daemon方式运行,同时在/tmp目录下生成了codeviz.pipe文件。要生成函数调用图,可以使用-q选项:

1
gengraph -q -t -d 2 -f alloc_pages

要终止gengraph的运行,使用如下命令:

1
echo QUIT > /tmpcodeviz.pipe

六、进阶演示

以分析《嵌入式实时操作系统 uC/OS-II (第二版)》中的第一个范例程序为例,是什么程序不要紧,这里主要看的是如何使用及使用后的效果。

首先分析main():

1.

1
gengraph --output-type gif -f main

分析main()的call graph,得到的图如下,看不出要领:

2.

1
gengraph --output-type gif -f main -s OSInit

暂时不关心OSInit()的内部实现细节(参数 -s),让它显示为一个节点。得到的图如下,有点乱,不过好多了:

3.

1
gengraph --output-type gif -f main -s OSInit -i "OSCPUSaveSR;OSCPURestoreSR"

基本上每个函数都会有进入/退出临界区的代码,忽略之(参数 -i)。得到的图如下,基本清楚了:

4.

1
gengraph --output-type gif -f main -s "OSInit;OSSemCreate" -i "OSCPUSaveSR;OSCPURestoreSR" -k

OSSemCreate()的内部细节似乎也不用关心,不过保留中间文件sub.graph(参数 -k),得到的图如下,

5.

1
dot -Tgif -o main.gif sub.graph

修改sub.graph,使图形符合函数调用顺序,最后得到的图如下,有了这个都不用看代码了:)

接着分析OSTimeDly()的被调用关系:

1
gengraph --output-type gif -r -f OSTimeDly

看看哪些函数调用了OSTimeDly(),参数 -r ,Task()和TaskStart()都是用户编写的函数:

最后看看Task()直接调用了哪些函数:

1
gengraph --output-type gif -d 1 -f Task

只看从Task出发的第一层调用(参数 -d 1):

七、安装过程出现的错误及解决方案

1. 在运行./install_gcc-4.6.2.sh时出现下面错误:

1
gcc configure: error: Building GCC requires GMP 4.2+, MPFR 2.3.1+ and MPC 0.8.0+

从错误中可以看出:GCC编译需要GMP, MPFR, MPC这三个库(有的系统已经安装了就没有这个提示,我的没有安装),有两种安装方法(建议第二种):

(1)二进制源码安装(强烈不推荐)

我使用的版本为gmp-4.3.2,mpfr-2.4.2和mpc-0.8.1,在 ftp://gcc.gnu.org/pub/gcc/infrastructure/ 下载,根据提示的顺序分别安装GMP,MPFR和MPC(mpfr依赖gmp,mpc依赖gmp和mpfr),这里全部自己指定了安装目录,如果没有指定则默认分装在在/usr/include、/usr/lib和/usr/share,管理起来不方便,比如想卸载的时候还得一个个去找:

1
2
3
安装gmp:  ./configure --prefix=/usr/local/gmp-4.3.2; make install
安装mpfr: ./configure --prefix=/usr/local/mpfr-2.4.2 --with-gmp=/usr/local/gmp-4.3.2/; make install
安装mpc:  ./configure --prefix=/usr/local/mpc-0.8.1 --with-gmp=/usr/local/gmp-4.3.2/ --with-mpfr=/usr/local/mpfr-2.4.2/; make install

PS:安装过程中可能又出现新的错误提示,请看2、3、4条。

配置环境变量:我这里指定了安装位置,如果没有指定则这几个库的默认位置是/usr/local/include和/usr/local/lib,不管有没有指定GCC编译时都可能会找不到这三个库,需要确认库位置是否在环境变量LD_LIBRARY_PATH中,查看环境变量内容可以用命令 $echo $LD_LIBRARY_PATH 设置该环境变量命令如下:

1
2
3
指定安装:export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/gmp-4.3.2/lib:/usr/local/mpfr-2.4.2/lib:/usr/local/mpc-0.8.1/lib

默认安装:$export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/local/lib

PS:十分不推荐这种安装方法,一般来说这样的确可以成功安装,但是也不排除安装过程中又出现新的问题,具体看问题5。

(2)gcc自带脚本安装(强烈推荐)

方法(1)的安装方法十分繁琐,安装过程中可能出现各种预料不到的新错误,因此gcc源码包中自带了一个gcc依赖库安装脚本download_prerequisites,位置在gcc源码目录中的contrib/download_prerequisites,因此只需要进入该目录,直接运行脚本安装即可:./download_prerequisites

PS:该脚本内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/bin/sh

# Download some prerequisites needed by gcc.
# Run this from the top level of the gcc source tree and the gcc
# build will do the right thing.
#
# (C) 2010 Free Software Foundation
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see http://www.gnu.org/licenses/.

MPFR=mpfr-2.4.2
GMP=gmp-4.3.2
MPC=mpc-0.8.1

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/$MPFR.tar.bz2 || exit 1
tar xjf $MPFR.tar.bz2 || exit 1
ln -sf $MPFR mpfr || exit 1

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/$GMP.tar.bz2 || exit 1
tar xjf $GMP.tar.bz2  || exit 1
ln -sf $GMP gmp || exit 1

wget ftp://gcc.gnu.org/pub/gcc/infrastructure/$MPC.tar.gz || exit 1
tar xzf $MPC.tar.gz || exit 1
ln -sf $MPC mpc || exit 1

rm $MPFR.tar.bz2 $GMP.tar.bz2 $MPC.tar.gz || exit 1

可见是通过wget的方式下载安装,因此如果没有安装wget则需要先安装下。

大家仔细看下这个脚本,发现非常简单,就是从网上自动下载三个依赖库并解压,然后建立三个改名后的软链接分别指向这三个库,这里建立软链接过程中也可能出错,具体看问题6,大家也可以自己修改脚本,改成直接修改名称然后移到gcc目录下。

技巧:从这里也可以看出,gcc所依赖的库其实只要解压了放在gcc当前目录下就行了,方法(1)的那么多步骤其实都可以省掉,直接将下载的三个压缩包解压后改名移到gcc下面即可,也不用设置环境变量了。

2. 编译gmp时出现错误:

No usable m4 in $PATH or /usr/5bin (see config.log for reasons). 由此可以看出是缺少M4文件。可以去这里下载:http://ftp.gnu.org/gnu/m4/ 然后编译安装,我由于是Ubuntu系统,就直接安装了。

1
sudo apt-get install m4

3. 安装mpfr时出现错误:

configure: error: gmp.h can’t be found, or is unusable.

这是因为在安装mpfr时未先安装gmp导致的,mpfr依赖于gmp。

4. 安装mpc时出现错误:

configure: error: libgmp not found or uses a different ABI.和configure: error: libmpfr not found or uses a different ABI.“。

同样是因为未安装mpc依赖的库gmp和mpfr。

5. 在运行./install_gcc-4.6.2.sh过程中出现错误,即按照gcc过程中出现的问题:

(1)libmpfr.so.1: cannot open shared object file: No such file or directory

分析:该脚本就是安装gcc,但是如果你出现了问题1,并且使用方法(1)解决该问题,那么你后期就可能出现这样的问题,当然你运气没那么背的话一般不会出现这样的问题,反正我运行比较背,出现了这样的问题。

解决方法:可以参考这篇文章 http://blog.csdn.net/leo115/article/details/7671819 解决。

(2)../../gcc-4.6.2/gcc/realmpfr.h:27:17: fatal error: mpc.h: No such file or directory 分析:gcc没找到所依赖的库mpc,原因很多,最有可能是你没设置环境变量或mpc放的地方不对。

解决方法:设置环境变量,看问题1。

(3) /usr/include/stdc-predef.h:30:26: fatal error: bits/predefs.h: No such file or directory

分析:用命令"locate bits/predefs.h"下该头文件的路径,发现是在'/usr/include/x86_64-linux-gnu' 解决方法:设置环境变量:

1
#export C_INCLUDE_PATH=/usr/include/i386-linux-gnu && export CPLUS_INCLUDE_PATH=$C_INCLUDE_PATH

(4) /usr/bin/ld: cannot find crti.o: No such file or directory

分析:同样用"locate crti.o" 找下这个文件,在'/usr/lib/i386-linux-gnu/crti.o'。

解决方法:设置LIBRARY_PATH (LDFLAGS)这个环境变量如下:

1
#export LIBRARY_PATH=/usr/lib/i386-linux-gnu

(5)unwind-dw2.c:1031: error: field `info' has incomplete type

分析:这个错误搞了好久,因为网上找不到对应的解决方法,只说这是gcc的一个bug。

解决方法:深入到源文件中,发现错误的地方是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
static _Unwind_Reason_Code
uw_frame_state_for (struct _Unwind_Context *context, _Unwind_FrameState *fs)
{
	struct dwarf_fde *fde;
	struct dwarf_cie *cie;
	const unsigned char *aug, *insn, *end;

	memset (fs, 0, sizeof (*fs));
	context->args_size = 0;
	context->lsda = 0;

	fde = _Unwind_Find_FDE (context->ra - 1, &context->bases); //这里返回了NULL
	if (fde == NULL)
	{
		/* Couldn't find frame unwind info for this function.  Try a
		 * target-specific fallback mechanism.  This will necessarily
		 * not profide a personality routine or LSDA.  */
#ifdef MD_FALLBACK_FRAME_STATE_FOR
		MD_FALLBACK_FRAME_STATE_FOR (context, fs, success); // 出错的地方
		return _URC_END_OF_STACK;
	success:
		return _URC_NO_REASON;
#else
		return _URC_END_OF_STACK;  //出错返回
#endif
	}
	.....
}

出错的地方用标注了,因为fde返回了NULL,导致不能找到frame unwind info,最重要的是下面这个方法

1
MD_FALLBACK_FRAME_STATE_FOR (context, fs, success);

出错了,为什么返回NULL我肯定研究不出来,只知道这个函数调用失败了,导致不成功,于是我的解决方法十分偷懒,就是将下面的两行注释掉了,直接success,哈哈,勿喷我,因为这样做过后就解决了,后面一路成功~~~

1
2
// MD_FALLBACK_FRAME_STATE_FOR (context, fs, success); // 出错的地方
// return _URC_END_OF_STACK;
  1. 解决ln -s 软链接产生Too many levels of symbolic links错误

从网上查找了一下原因,原来是建立软连接的时候采用的是相对路径,所以才会产生这样的错误,解决方式是采用绝对路径建立软链接:这样问题就解决了。

八、小结

本文查阅了网上的许多资料比较详细的讲解了CodeViz的安装和使用。CodeViz依赖于GraphViz,因而可以生成十分丰富的函数调用图。具体选项的使用及图像格式的选择可由读者根据个人需要和偏好自己揣摩使用。在分析源码的时候,把这些图形打印在手边,在上面做笔记,实在方便收益颇多。

九、参考资料:

  1. http://blog.csdn.net/delphiwcdj/article/details/9936717

  2. http://www.cppblog.com/hacrwang/archive/2007/06/30/27296.html

  3. http://www.cnblogs.com/xuxm2007/archive/2010/10/14/1851086.html

sed命令

替换 \n

1
sed ':a;N;$!ba;s/\n//g' file.txt

http://www.cnblogs.com/dong008259/archive/2011/12/07/2279897.html

sed是一个很好的文件处理工具,本身是一个管道命令,主要是以行为单位进行处理,可以将数据行进行替换、删除、新增、选取等特定工作,下面先了解一下sed的用法

sed命令行格式为:

1
sed [-nefri] 'command' 输入文本        

常用选项:

1
2
3
4
5
-n ∶使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN的资料一般都会被列出到萤幕上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
-e ∶直接在指令列模式上进行 sed 的动作编辑;
-f ∶直接将 sed 的动作写在一个档案内, -f filename 则可以执行 filename 内的sed 动作;
-r ∶sed 的动作支援的是延伸型正规表示法的语法。(预设是基础正规表示法语法)
-i ∶直接修改读取的档案内容,而不是由萤幕输出。       

常用命令:

1
2
3
4
5
6
a  ∶新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c  ∶取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d  ∶删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i  ∶插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p  ∶列印,亦即将某个选择的资料印出。通常 p 会与参数 sed -n 一起运作~
s  ∶取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦!

举例:(假设我们有一文件名为ab)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
删除某行
sed '1d' ab             #删除第一行 
sed '$d' ab             #删除最后一行
sed '1,2d' ab           #删除第一行到第二行
sed '2,$d' ab           #删除第二行到最后一行

显示某行
sed -n '1p' ab          #显示第一行 
sed -n '$p' ab          #显示最后一行
sed -n '1,2p' ab        #显示第一行到第二行
sed -n '2,$p' ab        #显示第二行到最后一行

使用模式进行查询
sed -n '/ruby/p' ab     #查询包括关键字ruby所在所有行
sed -n '/\$/p' ab       #查询包括关键字$所在所有行,使用反斜线\屏蔽特殊含义

增加一行或多行字符串
sed '1a drink tea' ab   #第一行后增加字符串"drink tea"
sed '1,3a drink tea' ab #第一行到第三行后增加字符串"drink tea"
sed '1a drink tea\nor coffee' ab   #第一行后增加多行,使用换行符\n

代替一行或多行
sed '1c Hi' ab          #第一行代替为Hi
sed '1,2c Hi' ab        #第一行到第二行代替为Hi

替换一行中的某部分
格式:sed 's/要替换的字符串/新的字符串/g'   (要替换的字符串可以用正则表达式)
sed -n '/ruby/p' ab | sed 's/ruby/bird/g'    #替换ruby为bird
sed -n '/ruby/p' ab | sed 's/ruby//g'        #删除ruby

插入
sed -i '$a bye' ab         #在文件ab中最后一行直接输入"bye"
cat ab

删除匹配行
sed -i '/匹配字符串/d'  filename  (注:若匹配字符串是变量,则需要“”,而不是‘’。记得好像是)

替换匹配行中的某个字符串
sed -i '/匹配字符串/s/替换源字符串/替换目标字符串/g' filename

awk命令

统计列和
1
awk 'BEGIN { sum+=$1; } END { print sum }'

-F 参数自定义分隔符可以用正则表达式

1
awk -F '[ ;]+' '{print $2}'

http://www.cnblogs.com/ggjucheng/archive/2013/01/13/2858470.html

实例

1
2
3
last -n 5 | awk  '{print $1}'
cat /etc/passwd |awk  -F ':'  '{print $1"\t"$7}'
cat /etc/passwd |awk  -F ':'  'BEGIN {print "name,shell"}  {print $1","$7} END {print "blue,/bin/nosh"}'

awk内置变量

1
2
3
4
5
6
7
8
9
10
11
ARGC            命令行参数个数
ARGV            命令行参数排列
ENVIRON         支持队列中系统环境变量的使用
FILENAME        awk浏览的文件名
FNR             浏览文件的记录数
FS              设置输入域分隔符,等价于命令行 -F选项
NF              浏览记录的域的个数
NR              已读的记录数
OFS             输出域分隔符
ORS             输出记录分隔符
RS              控制记录分隔符

此外,$0变量是指整条记录。$1表示当前行的第一个域,$2表示当前行的第二个域,……以此类推。

print和printf

awk中同时提供了print和printf两种打印输出的函数。

其中print函数的参数可以是变量、数值或者字符串。字符串必须用双引号引用,参数用逗号分隔。如果没有逗号,参数就串联在一起而无法区分。这里,逗号的作用与输出文件的分隔符的作用是一样的,只是后者是空格而已。

printf函数,其用法和c语言中printf基本相似,可以格式化字符串,输出复杂时,printf更加好用,代码更易懂。

awk编程

变量和赋值
1
2
# 统计/etc/passwd的账户人数
awk '{count++;print $0;} END{print "user count is ", count}' /etc/passwd
条件语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (expression) {
	statement;
	statement;
	... ...
}

if (expression) {
	statement;
} else {
	statement2;
}

if (expression) {
	statement1;
} else if (expression1) {
	statement2;
} else {
	statement3;
}
循环语句

awk中的循环语句同样借鉴于C语言,支持while、do/while、for、break、continue,这些关键字的语义和C语言中的语义完全相同。

数组

因为awk中数组的下标可以是数字和字母,数组的下标通常被称为关键字(key)。值和关键字都存储在内部的一张针对key/value应用hash的表格里。由于hash不是顺序存储,因此在显示数组内容时会发现,它们并不是按照你预料的顺序显示出来的。数组和变量一样,都是在使用时自动创建的,awk也同样会自动判断其存储的是数字还是字符串。一般而言,awk中的数组用来从记录中收集信息,可以用于计算总和、统计单词以及跟踪模板被匹配的次数等等。

显示/etc/passwd的账户

1
2
3
4
5
6
7
8
awk -F ':' 'BEGIN {count=0;} {name[count] = $1;count++;}; END{for (i = 0; i < NR; i++) print i, name[i]}' /etc/passwd
0 root
1 daemon
2 bin
3 sys
4 sync
5 games
......