在线咨询
有事点这里
有事点这里
看不懂这篇文章?联系我们
("麦洛克菲"长期致力于内核安全的推广与普及,我们更专业!)
求职QQ群:223902435。讨论各种求职笔试面试问题
作者:admin 时间:2015-10-31
标题:Linux VIM GCC与GDB开发,编译与调试程序

VIM使用

VIMLinux上一个非常重要的文本编辑工具,C程序员可以通过VIM来编辑C源代码。Vim是从 vi 发展出来的一个文本编辑器。代码补完、编译及错误跳转等方便编程的功能特别丰富,在程序员中被广泛使用。和Emacs并列成为类Unix系统用户最喜欢的编辑器。

VIM有两种模式,一种是命令模式,一种是编辑模式。学习vim要首先过2关。第一关是理解vim的设计思路,vim设计之初就是整个文本编辑都用键盘而非鼠标来完成,键盘上几乎每个键都有固定的用法,且vim的制作者希望用户在普通模式(也就是命令模式,只可输入命令)完成大部分的编辑工作,将此模式设计为默认模式,初学者打开vim,如果直接输入单词,结果就会滴滴乱响,这是因为vim把用户输入的单词理解为命令了。第二关是命令关,vim有过百条命令对应编辑的需要,如果能熟练使用vim这些命令,编辑速度确实比鼠标要快很多,但是想全都记住它们也是一件难事。

首先,打开VIM之后,VIM默认的是命令模式。这个时候,你是无法输入字符的。而可以运行各种VIM命令。

如果读者朋友的Linux系统没有自带VIM程序,比如运行VIM命令,系统提示找不到该命令,那么需要首先安装VIM,才能使用。在Ubuntu系统中,运行:sudo apt-get install vim-full来安装VIM工具。

vim filename: 如果filename对应的文件存在则打开,如果filename对应的文件不存在,则创建。

打开文件之后,可以操作下面的命令,来操作对应的文档:

 

j:向下移动光标

k:向上移动光标

l:向左移动光标

h:向右移动光标

 

ctrl+f:下翻页

ctrl+b:上翻页

 

 

wwrite 保存

wq:保存并退出

 

q!:不保存强制退出

q:退出

 

ctrl+[:退出编辑状态

 

dd:删除一行

xp:交换2个字符

O:大写O,在光标之上插入一行,由命令模式进入编辑模式

O:小写O,在光标之下插入一行,由命令模式进入编辑模式

yy:复制一行

y:复制

x:剪切

 

i:光标当前位置插入,由命令模式进入编辑模式

a:光标之后的位置插入,由命令模式进入编辑模式

 

$:移动光标到行尾

^:移动光标到行首

gg:文件开头

G:文件末尾

%:跳到括号匹配处

ngg:跳到第N

#:向后查找

*:向前查找

m,ns/a/b/g:在第M,N行之间,A替换为B

<<>>:缩进

v->y:选择拷贝

ctrl +p:自动补齐。

 

上面的这些命令,读者朋友们可以在Linux系统中反复练习。掌握上面这些命令,开发C语言程序就基本上就够用了。VIM除了上面的命令之外,还有一个专门的配制文件,用来配置VIM的各种风格,包括语法高亮显示,自动缩进等功能,以让VIM的界面更加的友好。VIM配置文件的位置可以在:

l         /etc/vim/vimrc

l         ~/.vimrc

 

如果只对各个用户在自己的当前目录下的.vimrc修改的话,修改内容只对本用户有效,要想全部有效,可以修改/etc/vim/vimrc,则对所有用户有效。下面是一个比较典型的VIM配置文件。可以将下面的内容拷贝到vimrc中并保存。其中VIM中注释一条语句,是用字符放在一行的开头,就把这一行注释了。

1.apt-get install vim-full

2.设置/etc/vim/vimrc加上下面语句:

"语法高亮度显示

syntax on

"去掉有关vi一致性模式,避免以前版本的一些bug和局限

set nocompatible

"显示行号

set number

"检测文件的类型

filetype on

"记录历史的行数

set history=1000

"背景使用黑色

set background=dark

"vim使用自动对齐,也就是把当前行的对起格式应用到下一行

set autoindent

"依据上面的对起格式,智能的选择对起方式,对于类似C语言编

set smartindent

"设置Windows风格的C/C++自动缩进,第一行设置tab键为4个空格,第二行设置当行之间交错时使用4个空格

set tabstop=4

set shiftwidth=4

"设置匹配模式,类似当输入一个左括号时会匹配相应的那个右括号

set showmatch

"去除vimGUI版本中的toolbar

set guioptions-=T

"在编辑过程中,在右下角显示光标位置的状态行

set ruler

"默认情况下,寻找匹配是高亮度显示的,该设置关闭高亮显示

set nohls

"使用此设置会快速找到答案,当你找要匹配的单词时,别忘记回车

set incsearch

GCC编译程序

学会了VIM之后,就可以用VIMLinux系统下用C语言写程序了。首先,打开Linux的终端控制台,然后执行cd命令,切换到当前用户目录下。比如笔者的当前目录就是:

/home/zyr/

在当前目录下,新建一个文件夹:projects。执行命令为:

mkdir projects

这样目录就建好了。执行:

cd projects

切换到projects目录下。然后执行:

vim main.c

打开了VIM编辑工具,创建了一个main.c文件,利用VIMmain.c文件中输入如下代码。然后执行:

Ctrl+[

退出编辑状态。

然后执行:

Shift+:

Wq

保存文件main.c之后退出。代码写好之后,剩下的任务就是如何将源文件编译为可执行程序。在Linux上要编译C代码,需要gcc或者g++工具。在控制台下运行gcc或者g++命令,如果系统提示命令未找到,则是因为系统没有安装这些编译工具。那么在ubuntu中执行下面的命令,可以为系统安装好编译所用到的各种工具:

sudo apt-get  install  build-essential

安装好了编译工具之后,再运行下面的命令:

gcc –o hello  main.c

 

这条命令是什么意思呢?这就是用GCC工具把main.c源文件编译成可执行程序hello。注意,对于.c文件,使用gcc编译命令,对于.cpp文件,则使用g++编译命令。

GCC的各个参数如下:

 

GCC常用选项

选项

含义

--help

--target-help

显示 gcc 帮助说明。‘target-help’是显示目标机器特定的命令行选项。

--version

显示 gcc 版本号和版权信息 。

-o outfile

输出到指定的文件。

-x language

指明使用的编程语言。允许的语言包括:c c++ assembler none 。 ‘none’意味着恢复默认行为,即根据文件的扩展名猜测源文件的语言。

-v

打印较多信息,显示编译器调用的程序。

-###

-v 类似,但选项被引号括住,并且不执行命令。

-E

仅作预处理(code.i),不进行编译、汇编和链接。

-S

仅编译到汇编语言(code.s),不进行汇编和链接。

-c

编译、汇编到目标代码(code.o),不进行链接。

-pipe

使用管道代替临时文件。

-combine

将多个源文件一次性传递给汇编器。

 

其他GCC选项

更多有用的GCC选项:

命令

描述

-shared

生成共享目标文件。通常用在建立共享库时。

-static

禁止使用共享连接。

-l library

-llibrary

进行链接时搜索名为library的库。

例子: $ gcc test.c -lm -o test

-Idir

dir 加入到搜索头文件的路径列表中。

例子: $ gcc test.c -I../inc_dir -o test

-Ldir

dir 加入到搜索库文件的路径列表中。

例子: $ gcc -I/home/foo -L/home/foo -ltest test.c -o test

-Dname

预定义一个名为name 的宏,值为1

例子: $ gcc -DTEST_CONFIG test.c -o test

-Dname =definition

预定义名为name ,值为definition 的宏。

-ggdb

-ggdblevel

为调试器 gdb 生成调试信息。level 可以为123,默认值为2

-g

-glevel

生成操作系统本地格式的调试信息。-g -ggdb 并不太相同, -g 会生成 gdb 之外的信息。level 取值同上。

-Wall

会打开一些很有用的警告选项,建议编译时加此选项。

-w

禁止显示所有警告信息。

 

Optimization

-O0

禁止编译器进行优化。默认为此项。

-O

-O1

尝试优化编译时间和可执行文件大小。

-O2

更多的优化,会尝试几乎全部的优化功能,但不会进行“空间换时间”的优化方法。

-O3

-O2 的基础上再打开一些优化选项:-finline-functions -funswitch-loops -fgcse-after-reload

-Os

对生成文件大小进行优化。它会打开 -O2 开的全部选项,除了会那些增加文件大小的。

 

Standard

-ansi

支持符合ANSI标准的C程序。这样就会关闭GNU C中某些不兼容ANSI C的特性。

-std=c89

-iso9899:1990

指明使用标准 ISO C90 作为标准来编译程序。

-std=c99

-std=iso9899:1999

指明使用标准 ISO C99 作为标准来编译程序。

    命令执行完之后,如果一切顺利而程序没有各种语法错误,就会生成一个hello可执行程序。然后,运行编译好的程序如下:

./hello

程序就会在控制台程序上打印出一条hello world字符串。

 

一般来说,在Linux系统中,并不是通过命令行来编译源代码的,这样很不方便。于是有了一个叫Makefile的文件。这个文件可以来定义如何编译各种源文件,最后编译生成一个可执行文件。

同样以上面的hello world程序为例。将当前目录切换在Projects目录下,运行如下命令打开并编辑一个Makefile文件:

vim Makefile

然后在Makefile里输入:

hello:main.o

       gcc -o hello main.o

main.o:main.c

       gcc -c main.c -o main.o

clean:

       rm -f *.o hello

 

注意,246行都是以一个tab字符(按下键盘上的tab键)开始,否则编译的时候会有问题。执行:

wq

命令保存编辑好的Makefile文件。运行命令ls查看一下,你会发现在Projects目录下多了一个Makefile文件。然后运行:

make

gcc就会自动读取Makefile文件的内容,依次执行编译。最后链接成可执行的文件hello。这个时候ls一下,就会发现在projects目录下多了一个hello main.o。其中main.o是一个中间文件。编译完成之后,同样运行一下:

./hello

得到了同样的程序执行结果:打印输出了一个hello world字符串。

上面的Makefile针对的是一个源文件。那么假如一个程序项目里有多个源文件,应该怎么写Makefile呢?

 

    Makefile

 

  1 .overpass:main.o http.o httppost.o

  2.     g++ -g -o  overpass main.o http.o httppost.o -L. -lhiredis -lpthread

  3. main.o:main.cpp

  4.     g++ -g -c main.cpp -o main.o

  5. http.o:http.cpp

  6.     g++ -g -c http.cpp -o http.o

  7. httppost.o:httppost.cpp

  8.     g++ -g -c httppost.cpp -o httppost.o

  9. clean:

  10.     rm -f *.o overpass

 

其中,2,4,6,8,10行后面都是一个tab字符,这一点要特别注意,否则在执行make的时候,GCC会报错。

 

 

GDB调试程序

 

GDBLinuxUNIX环境下的一个强大而流行的程序调试器。一般来说GDB主要调试的是C/C++的程序。要调试C/C++的程序,首先在编译时,必须要把调试信息加到可执行文件中。使用编译器(cc/gcc/g++)的 -g 参数可以做到这一点。如:

> cc -g hello.c -o hello

> g++ -g hello.cpp -o hello

如果没有-g,你将看不见程序的函数名、变量名,所代替的全是运行时的内存地址。当你用-g把调试信息加入之后,并成功编译目标代码以后,现在来看看如何用gdb来调试它。

启动GDB的方法有以下几种:

1gdb <program>

program也就是你的执行文件,一般在当前目录下。

2gdb <program> core

gdb同时调试一个运行程序和core文件,core是程序非法执行后core dump后产生的文件,记录了程序在出错时的一些内存环境和出错信息。

3gdb <program> <PID>

如果你的程序是一个服务程序,那么你可以指定这个服务程序运行时的进程IDgdb会自动attach上去,并调试它。program应该在PATH环境变量中搜索得到。

GDB启动时,可以加上一些GDB的启动开关,详细的开关可以用gdb -help查看。我在下面只例举一些比较常用的参数:

-symbols <file>

-s <file>

从指定文件中读取符号表。

-se file

从指定文件中读取符号表信息,并把它用在可执行文件中。

-core <file>

-c <file>

调试core dumpcore文件。

-directory <directory>

-d <directory>

加入一个源文件的搜索路径。默认搜索路径是环境变量中PATH所定义的路径。

源程序:a.c

 

1 #include <stdio.h>

2

3 int func(int n)

4 {

5    int sum=0,i;

6    for(i=0; i<n; i++)

7    {

8        sum+=i;

9    }

10   return sum;

11 }

12

13

14 void main(void)

15 {

16       int i;

17       long result = 0;

18       for(i=1; i<=100; i++)

19       {

20            result += i;

21       }

22

23       printf("result[1-100] = %d \n", result );

24       printf("result[1-250] = %d \n", func(250) );

25 }

 

Linux下编译生成执行文件:

gcc -g a.c -o a

 

下面使用GDB进行相关调试。

启动gdb

gdb a

GNU gdb 5.1.1

Copyright 2002 Free Software Foundation, Inc.

GDB is free software, covered by the GNU General Public License, and you are

welcome to change it and/or distribute copies of it under certain conditions.

Type "show copying" to see the conditions.

There is absolutely no warranty for GDB. Type "show warranty" for details.

This GDB was configured as "i386-suse-linux"...

(gdb) l <-------------------- l命令相当于list,从第一行开始例出原码。

1 #include <stdio.h>

2

3 int func(int n)

4 {

5     int sum=0,i;

6     for(i=0; i<n; i++)

7     {

8            sum+=i;

9     }

10   return sum;

(gdb) <-------------------- 直接回车表示,重复上一次命令

11 }

12

13

14 main()

15 {

16 int i;

17 long result = 0;

18 for(i=1; i<=100; i++)

19 {

20 result += i;

(gdb) break  a.c:16<-------------------- 设置断点,在源程序第16行处。

Breakpoint 1 at 0x8048496: file a.c, line 16.

(gdb) break func <-------------------- 设置断点,在函数func()入口处。

Breakpoint 2 at 0x8048456: file a.c, line 5.

(gdb) info break <-------------------- 查看断点信息。

Num Type Disp Enb Address What

1 breakpoint keep y 0x08048496 in main at a.c:16

2 breakpoint keep y 0x08048456 in func at a.c:5

(gdb) r <--------------------- 运行程序,run命令简写

Starting program: /home/xyz/ a

 

Breakpoint 1, main () at a.c:17 <---------- 在断点处停住。

17 long result = 0;

(gdb) n <--------------------- 单条语句执行,next命令简写。

18 for(i=1; i<=100; i++)

(gdb) n

20 result += i;

(gdb) n

18 for(i=1; i<=100; i++)

(gdb) n

20 result += i;

(gdb) c <--------------------- 继续运行程序,continue命令简写。

Continuing.

result[1-100] = 5050 <----------程序输出。

 

Breakpoint 2, func (n=250) at a.c:5

5 int sum=0,i;

(gdb) n

6 for(i=1; i<=n; i++)

(gdb) p i <--------------------- 打印变量i的值,print命令简写。

$1 = 134513808

(gdb) n

8 sum+=i;

(gdb) n

6 for(i=1; i<=n; i++)

(gdb) p sum

$2 = 1

(gdb) n

8 sum+=i;

(gdb) p i

$3 = 2

(gdb) n

6 for(i=1; i<=n; i++)

(gdb) p sum

$4 = 3

(gdb) bt <--------------------- 查看函数堆栈。

#0 func (n=250) at a.c:5

#1 0x080484e4 in main () at a.c:24

#2 0x400409ed in __libc_start_main () from /lib/libc.so.6

(gdb) finish <--------------------- 退出函数。

Run till exit from #0 func (n=250) at a.c:5

0x080484e4 in main () at a.c:24

24 printf("result[1-250] = %d \n", func(250) );

Value returned is $6 = 31375

(gdb) c <--------------------- 继续运行。

Continuing.

result[1-250] = 31375 <----------程序输出。

 

Program exited with code 027. <--------程序退出,调试结束。

(gdb) q <--------------------- 退出gdb

 

    上面的例子向大家列举了GDB调试程序的基本用法。下面就利用GDB来调试一个segement fault错误:

 

    1 : #include <stdio.h>

    2 : #include <stdlib.h>

 

    3 : int main(int argc, char **argv)

    4 : {

    5 :   char *buf;

    6 :

    7 :   buf = malloc(1<<31);

    8 :

    9 :   fgets(buf, 1024, stdin);

    10:   printf("%s\n", buf);

    11:

    12:   return 1;

    13: }

 

 

    prompt> gcc -g segfault.c

 

    prompt > a.out

    Hello World!

    Segmentation fault

    prompt >

 

    prompt > gdb a.out

    GNU gdb 5.0

    Copyright 2000 Free Software Foundation, Inc.

    GDB is free software, covered by the GNU General Public License, and you are

    welcome to change it and/or distribute copies of it under certain conditions.

    Type "show copying" to see the conditions.

    There is absolutely no warranty for GDB.  Type "show warranty" for details.

    This GDB was configured as "i686-pc-linux-gnu"...

    (gdb)

 

    首先运行这个程序看会发生什么情况:

 

    (gdb) run

    Starting program: /home/dgawd/cpsc/363/a.out

    test string

 

    Program received signal SIGSEGV, Segmentation fault.

    0x4007fc13 in _IO_getline_info () from /lib/libc.so.6

       从上面运行的结果看出,在收到了SIGSEGV信号。该信号意味着程序在试着访问了一个无效的内存地址。先用backtrace命令看看:

    (gdb) backtrace

    #0  0x4007fc13 in _IO_getline_info () from /lib/libc.so.6

    #1  0x4007fb6c in _IO_getline () from /lib/libc.so.6

    #2  0x4007ef51 in fgets () from /lib/libc.so.6

    #3  0x80484b2 in main (argc=1, argv=0xbffffaf4) at segfault.c:10

    #4  0x40037f5c in __libc_start_main () from /lib/libc.so.6

       接下来只关心程序自己相关的代码,所以选择第3栈帧,看看程序是在什么地方崩溃的。

    (gdb) frame 3

    #3  0x80484b2 in main (argc=1, argv=0xbffffaf4) at segfault.c:10

    10  fgets(buf, 1024, stdin)

       程序很显然崩溃在第10行对fgets()的调用上。首先,可以认为fgets()本身工作正常(不然,那就会出现很多程序的错误)。那么问题一定出现在传递的参数上,而stdin是一个由stdio创建的全局变量,所以stdin没有问题。那么剩下唯一可以怀疑的地方就是变量buf了。所以查看buf的值:

    (gdb) print buf

    $1 = 0x0

       Buf的值为NULL,这是错误的。不能传空指针给gets()。所以自然要检查程序第8buf分配后的情况。于是先结束程序:

    (gdb) kill

    Kill the program being debugged? (y or n) y

       然后在第8行设置断点:

    (gdb) break segfault.c:8

Breakpoint 1 at 0x8048486: file segfault.c, line 8.

再运行程序:

    (gdb) run

    Starting program: /home/dgawd/cpsc/363/a.out

 

    Breakpoint 1, main (argc=1, argv=0xbffffaf4) at segfault.c:8

    8         buf = malloc(1<<31);

       接着,来检查buf在分配前的值。由于buf没有被初始化,其中的值一定是垃圾数据:

    (gdb) print buf

    $2 = 0xbffffaa8 "��#\61**^_!fa*1"

 

       然后,运行内存分配函数:

 

    (gdb) next

    10  fgets(buf, 1024, stdin);

    (gdb) print buf

    $3 = 0x0

       调用malloc()后,buf的值为NULL。这意味着malloc()分配内存失败。再次检查内存分配语句:

    7 :   buf = malloc(1<<31);

       发现1<<31的值为429497295,或者4GB。很少有机器的内存可以达到4G。所以,malloc()分配失败是必然。于是将内存分配的大小降为1<<9,然后再次运行程序,就可以得到想要的结果了:

    prompt >

    Hello World!

 

prompt >

 

n       gdb hello

n       list

n       r-run

n       b-break for func,line

n       c-continue

n       s-step into

n       n-step over

n       p-print

n       q-quit

n       多线程:

n       gdb -p pid

n       set scheduler-locking on

n       info threads

n       thread 2

n       b functionname

n       c/s/n