
對于linux下的c程式員來說,幾乎天天都會和linux打交道。但在很多人的眼中,linux是一個易用性極差、靠指令驅動的作業系統,根本無法與有着友好使用者界面的windows相比。确實是這樣的,即使大家的程式是運作在linux下,基于以下種種原因,我們的大部分工作還是在windows下完成的:
第一,除了編譯調試代碼之外,每個程式員還有很多工作要做,像文檔編寫、郵件發送及回複、ppt制作等,這些工作在windows下做要更友善快捷一些。 第二,公司及項目組的資源有限,一般不會為每個開發人員配備一台安裝有linux的機器,而是大家共用一台或少許幾台linux機器。在每台機器上建立多個使用者,需要用來編譯或調試程式的時候,大家用某個使用者登入上去。
這樣,問題就來了:自己平時是在windows下面辦公的,而自己編寫的程式的運作環境又是linux的,如何從windows切換到linux呢?是不是要到專門的linux機器上去編寫代碼呢?我們如何在linux下調試程式呢?本文将一一道來。
<a></a>
隻要在windows下安裝一個叫做securecrt的軟體和一個叫做filezilla的軟體,便可輕松實作windows到linux的切換。
在使用securecrt和filezilla之前,要確定有一台安裝了linux的機器處于運作狀态(一般說來,每個開發小組都會有專門用于測試程式的機器,可以在此機器上安裝linux)。作者使用的linux機器的ip位址為xx.xx.xx.xx,使用者名為zxin10,密碼為yyyy。
打開securecrt軟體,在界面上輸入ip和使用者名,如圖1所示。
圖1 登入界面
然後,單擊圖1中的“connect”,在出現的界面上輸入密碼,如圖2所示。
圖2 密碼輸入界面
密碼輸入正确之後,便登入到了linux系統下,如圖3所示。
圖3 登入成功之後的界面
為了編譯自己的程式,我們需要建立自己的檔案存放目錄,如圖4所示。
圖4 建立個人目錄
目錄建立成功之後,我們便可以轉到目錄中去看一下,如圖5所示。
圖5 轉到建立目錄
此時,“萬事俱備,隻欠東風”,我們接下來要做的工作是利用filezilla軟體将自己在windows下編寫的程式傳上去。
示例程式如下:
<code>/**********************************************************************</code>
<code>* 版權所有 (c)2015, zhou zhaoxiong。</code>
<code>*</code>
<code>* 檔案名稱:hello.c</code>
<code>* 檔案辨別:無</code>
<code>* 内容摘要:示範windows下編寫的程式如何在linux下執行</code>
<code>* 其它說明:無</code>
<code>* 目前版本:v1.0</code>
<code>* 作 者:zhou zhaoxiong</code>
<code>* 完成日期:201501028</code>
<code>**********************************************************************/</code>
<code>#include <stdio.h></code>
<code></code>
<code>* 功能描述:主函數</code>
<code>* 輸入參數:無</code>
<code>* 輸出參數:無</code>
<code>* 返 回 值:0-執行完畢</code>
<code>* 修改日期 版本号 修改人 修改内容</code>
<code>* -------------------------------------------------------------------</code>
<code>* 201501028 v1.0 zhou zhaoxiong 建立</code>
<code>***********************************************************************/</code>
<code>int main()</code>
<code>{</code>
<code>printf("hello, world!\n");</code>
<code>return 0;</code>
<code>}</code>
将該“hello.c”檔案存放在d盤的“test”檔案夾下,并啟動filezilla,如圖6所示。
圖6 啟動filezilla之後的界面
在“主機(h)”中輸入ip位址,在“使用者名(u)”中輸入“zxin10”使用者名,在“密碼(w)”中輸入正确的密碼,“端口(p)”可不填寫而使用預設值,則可登入到linux機器上去。登上去後,轉到“zhouzx”目錄下,并将“hello.c”檔案傳上去,如圖7所示。
圖7 上傳檔案之後的界面
此時,“hello.c”檔案已經傳到了“zhouzx”目錄下,現在可以對該檔案進行編譯了。
使用“gcc -g -o hello hello.c”指令對檔案進行編譯,如圖8所示。
圖8 編譯之後的結果
可以看到,編譯成功之後,有“hello”檔案生成。緊接着,運作“hello”指令,便可看到程式的輸出結果,如圖9所示。
圖9 程式的輸出結果
以上便是将windows下的程式放到linux下去編譯和運作的全過程。這裡隻是示例了簡單的程式,實際軟體開發項目中的程式要複雜很多,但基本操作流程都是類似的。當然,直接在linux下編寫程式也是可以的,如可以利用vi編輯器來寫程式。但由于易用性的原因,我認為,在windows下編寫程式要更友善一點。大家要根據自己的習慣及項目組的要求來選擇合理的代碼編寫的方式。
在實際的軟體開發項目中,程式出現問題是在所難免的。遙想本人參加工作之後首次遇到程式的情景,至今還曆曆在目。之前的經驗告訴我,我們越是驚慌失措,問題就越是解決不了。我們要先讓自己平靜下來,然後再尋找解決程式問題的辦法。
這裡以一個實際的程式為例,以用gdb分析core檔案為例介紹了linux下程式調試的方法,同時示範了常見gdb指令的操作方法。
在linux下執行“ulimit –a”指令檢視程式運作出錯時是否會産生core檔案,指令執行的結果中有“core file size = 0”表示不會産生core檔案,此時要使用“ulimit -c 1000000”指令設定core檔案的大小。
示例程式
<code>* 檔案名稱:gdbdebug.c</code>
<code>* 内容摘要:gdb指令示範程式</code>
<code>* 完成日期:20151008</code>
<code>#include <stdlib.h></code>
<code>#include <string.h></code>
<code>// 資料類型重定義</code>
<code>typedef unsigned char uint8;</code>
<code>typedef signed int int32;</code>
<code>typedef unsigned int uint32;</code>
<code>// 函數聲明</code>
<code>void sleep(uint32 icountms);</code>
<code>void printinfo(void);</code>
<code>int32 main();</code>
<code>* 返 回 值:無</code>
<code>* 修改日期 版本号 修改人 修改内容</code>
<code>* 20151008 v1.0 zhou zhaoxiong 建立</code>
<code>int32 main()</code>
<code>printinfo(); // 在螢幕上輸出消息</code>
<code>* 功能描述: 在螢幕上輸出消息</code>
<code>* 輸入參數: 無</code>
<code>* 輸出參數: 無</code>
<code>* 返 回 值: 無</code>
<code>* 其它說明: 無</code>
<code>* 修改日期 版本号 修改人 修改内容</code>
<code>* ----------------------------------------------------------------------</code>
<code>* 20151008 v1.0 zhou zhaoxiong 建立</code>
<code>************************************************************************/</code>
<code>void printinfo(void)</code>
<code>uint32 iloopflag = 0;</code>
<code>uint32 isum = 0;</code>
<code>uint32 ilen = 0;</code>
<code>uint8 *pctrstr = null;</code>
<code>ilen = strlen(pctrstr);</code>
<code>for (iloopflag = 0; iloopflag < ilen; iloopflag ++) // 列印消息ilen次</code>
<code>printf("printinfo: hello, world!\n");</code>
<code>isum = isum + iloopflag;</code>
<code>sleep(10 * 1000); // 每10s列印一次</code>
<code>return;</code>
<code>* 功能描述: 程式休眠</code>
<code>* 輸入參數: icountms-休眠時間(機關:ms)</code>
<code>* 修改日期 版本号 修改人 修改内容</code>
<code>* ------------------------------------------------------------------</code>
<code>* 20151008 v1.0 zhou zhaoxiong 建立</code>
<code>********************************************************************/</code>
<code>void sleep(uint32 icountms)</code>
<code>struct timeval t_timeout = {0};</code>
<code>if (icountms < 1000)</code>
<code>t_timeout.tv_sec = 0;</code>
<code>t_timeout.tv_usec = icountms * 1000;</code>
<code>else</code>
<code>t_timeout.tv_sec = icountms / 1000;</code>
<code>t_timeout.tv_usec = (icountms % 1000) * 1000;</code>
<code>select(0, null, null, null, &t_timeout); // 調用select函數阻塞程式</code>
在linux上用“gcc -g -o gdbdebug gdbdebug.c”指令對程式進行編譯之後,運作“gdbdebug”指令,發現在目前目錄下出現了core檔案。利用gdb指令對core檔案進行分析的過程如下所示:
<code>~/zhouzhaoxiong/zzx/gdbdebug> gdb gdbdebug core -- 啟動gdb對core檔案的分析</code>
<code>gnu gdb (gdb) suse (7.3-0.6.1)</code>
<code>copyright (c) 2011 free software foundation, inc.</code>
<code>license gplv3+: gnu gpl version 3 or later <http://gnu.org/licenses/gpl.html></code>
<code>this is free software: you are free to change and redistribute it.</code>
<code>there is no warranty, to the extent permitted by law. type "show copying"</code>
<code>and "show warranty" for details.</code>
<code>this gdb was configured as "x86_64-suse-linux".</code>
<code>for bug reporting instructions, please see:</code>
<code><http://www.gnu.org/software/gdb/bugs/>...</code>
<code>reading symbols from /home/zhou/zhouzhaoxiong/zzx/gdbdebug/gdbdebug...done.</code>
<code>core was generated by `gdbdebug'.</code>
<code>program terminated with signal 11, segmentation fault.</code>
<code>#0 0x00007f4a736f9812 in __strlen_sse2 () from /lib64/libc.so.6</code>
<code>(gdb) where -- 檢視程式出問題的地方</code>
<code>#1 0x000000000040061a in printinfo () at gdbdebug.c:64 -- 可以看到,在gdbdebug.c檔案的第64行出的問題</code>
<code>#2 0x00000000004005e5 in main () at gdbdebug.c:41</code>
<code>(gdb) b 41 -- 在gdbdebug.c檔案第41行設立斷點</code>
<code>breakpoint 1 at 0x4005e0: file gdbdebug.c, line 41.</code>
<code>(gdb) b 64 -- 在gdbdebug.c檔案第64行設立斷點</code>
<code>breakpoint 2 at 0x400611: file gdbdebug.c, line 64.</code>
<code>(gdb) info b -- 顯示斷點資訊</code>
<code>num type disp enb address what</code>
<code>1 breakpoint keep y 0x00000000004005e0 in main at gdbdebug.c:41</code>
<code>2 breakpoint keep y 0x0000000000400611 in printinfo at gdbdebug.c:64</code>
<code>(gdb) r -- 運作gdbdebug</code>
<code>starting program: /home/zhou/zhouzhaoxiong/zzx/gdbdebug/gdbdebug</code>
<code>breakpoint 1, main () at gdbdebug.c:41</code>
<code>41 printinfo(); // 在螢幕上輸出消息</code>
<code>(gdb) n -- 執行下一步</code>
<code>breakpoint 2, printinfo () at gdbdebug.c:64</code>
<code>64 ilen = strlen(pctrstr);</code>
<code>(gdb) p ilen -- 列印(輸出)ilen的值</code>
<code>$1 = 0</code>
<code>(gdb) p iloopflag -- 列印(輸出)iloopflag的值</code>
<code>$2 = 0</code>
<code>(gdb) c -- 繼續執行</code>
<code>continuing.</code>
<code>program received signal sigsegv, segmentation fault. -- 程式core掉了</code>
<code>0x00007ffff7ae9812 in __strlen_sse2 () from /lib64/libc.so.6</code>
<code>(gdb) q -- 退出gdb</code>
<code>a debugging session is active.</code>
<code>inferior 1 [process 26640] will be killed.</code>
<code>quit anyway? (y or n) y</code>
<code>~/zhouzhaoxiong/zzx/gdbdebug></code>
從以上分析可知,執行gdbdebug.c檔案的第64行時程式core掉了。此時仔細分析程式,發現pctrstr指針為空。當對一個不存在的指針取長度時,由于找不到位址,程式便崩潰了。修改的辦法也非常的簡單,隻需要讓pctrstr指針指向具體的位址即可。
修改之後的代碼如下:
<code>* 20151008 v1.0 zhou zhaoxiong 建立</code>
<code>* 20151008 v1.0 zhou zhaoxiong 建立</code>
<code>uint8 *pctrstr = "hello, world!"; // 修改了這行代碼</code>
編譯并運作之後,程式正常,說明問題已被我們解決掉。下面是常見的gdb指令的操作示例:
<code>~/zhouzhaoxiong/zzx/gdbdebug> gdb gdbdebug -- 啟動gdb調試</code>
<code>(gdb) b 64 -- 在gdbdebug.c檔案第64行設立斷點</code>
<code>breakpoint 1 at 0x400611: file gdbdebug.c, line 64.</code>
<code>(gdb) b 72 -- 在gdbdebug.c檔案第72行設立斷點</code>
<code>breakpoint 2 at 0x400637: file gdbdebug.c, line 72.</code>
<code>(gdb) info b -- 顯示斷點資訊</code>
<code>1 breakpoint keep y 0x0000000000400611 in printinfo at gdbdebug.c:64</code>
<code>2 breakpoint keep y 0x0000000000400637 in printinfo at gdbdebug.c:72</code>
<code>(gdb) r -- 運作gdbdebug</code>
<code>breakpoint 1, printinfo () at gdbdebug.c:64</code>
<code>(gdb) p ilen -- 列印(輸出)ilen的值</code>
<code>(gdb) n -- 執行下一步</code>
<code>66 for (iloopflag = 0; iloopflag < ilen; iloopflag ++) // 列印消息ilen次</code>
<code>68 printf("printinfo: hello, world!\n");</code>
<code>$3 = 13</code>
<code>printinfo: hello, world! -- 程式的輸出結果</code>
<code>70 isum = isum + iloopflag;</code>
<code>(gdb) p isum -- 列印(輸出)isum的值</code>
<code>$4 = 0</code>
<code>(gdb) n -- 執行下一步</code>
<code>breakpoint 2, printinfo () at gdbdebug.c:72</code>
<code>72 sleep(10 * 1000); // 每10s列印一次</code>
<code>(gdb) n</code>
<code>(gdb) p iloopflag</code>
<code>$5 = 0</code>
<code>$6 = 1</code>
<code>printinfo: hello, world!</code>
<code>(gdb) p isum</code>
<code>$7 = 0</code>
<code>$8 = 1</code>
<code>(gdb) finish -- 一直運作到函數傳回</code>
<code>run till exit from #0 printinfo () at gdbdebug.c:72</code>
<code>(gdb) c -- 繼續執行</code>
<code>(gdb) bt -- 列印目前的函數調用棧的所有資訊</code>
<code>#0 printinfo () at gdbdebug.c:72</code>
<code>#1 0x00000000004005e5 in main () at gdbdebug.c:41</code>
<code>(gdb) q -- 退出gdb</code>
<code>inferior 1 [process 26685] will be killed.</code>
作為linux下調試c/c++程式的工具,大家一定要熟練掌握gdb的用法。
linux具有免費、可靠、安全、穩定、多平台等特點,是以深受全球各大it廠商的追捧。linux作業系統的兩大主要應用領域是伺服器領域和嵌入式linux系統。不管你從事的開發工作是否與linux有關,掌握linux下的軟體開發方法總是有好處的。
本文來自雲栖社群合作夥伴“linux中國”,原文發表于2013-04-02.