假設源程序文件名爲test.c。html
1. 無選項編譯連接
用法:#gcc test.c
做用:將test.c預處理、彙編、編譯並連接造成可執行文件。這裏未指定輸出文件,默認輸出爲a.out。linux
2. 選項 -o
用法:#gcc test.c -o test
做用:將test.c預處理、彙編、編譯並連接造成可執行文件test。-o選項用來指定輸出文件的文件名。程序員
3. 選項 -E
用法:#gcc -E test.c -o test.i
做用:將test.c預處理輸出test.i文件。redis
4. 選項 -S
用法:#gcc -S test.i
做用:將預處理輸出文件test.i彙編成test.s文件。shell
5. 選項 -c
用法:#gcc -c test.s
做用:將彙編輸出文件test.s編譯輸出test.o文件。express
6. 無選項連接
用法:#gcc test.o -o test
做用:將編譯輸出文件test.o連接成最終可執行文件test。ubuntu
7. 選項-O
用法:#gcc -O1 test.c -o test
做用:使用編譯優化級別1編譯程序。級別爲1~3,級別越大優化效果越好,但編譯時間越長。數組
二. gcc多源文件的編譯方法多線程
若是有多個源文件,基本上有兩種編譯方法:
[假設有兩個源文件爲test.c和testfun.c]app
1. 多個文件一塊兒編譯
用法:#gcc testfun.c test.c -o test
做用:將testfun.c和test.c分別編譯後連接成test可執行文件。
2. 分別編譯各個源文件,以後對編譯後輸出的目標文件連接。
用法:
#gcc -c testfun.c //將testfun.c編譯成testfun.o
#gcc -c test.c //將test.c編譯成test.o
#gcc -o testfun.o test.o -o test //將testfun.o和test.o連接成test
以上兩種方法相比較,第一中方法編譯時須要全部文件從新編譯,而第二種方法能夠只從新編譯修改的文件,未修改的文件不用從新編譯。
3. 若是要編譯的文件都在同一個目錄下,能夠用通配符gcc *.c -o 來進行編譯。
你是否會問,若是是一個項目的話,可能會有上百個文件,這樣的編譯法,人不是要累死在電腦前嗎,或者等到你編譯成功了,豈不是頭髮都白了,呵呵,因此咱們要把上述的編譯過程寫進如下一個文本文件中:
Linux下稱之爲makefile
#這裏能夠寫一些文件的說明
MyFirst: MyFirst.o hello.o
g++ MyFirst.o hello.o -o MyFirst
Hello.o:Hello.cpp
g++ -c Hello.cpp -o Hello.o
MyFirst.o:MyFirst.cpp
g++ -c MyFirst.cpp -o MyFirst.o
makefile 編寫規則:
(1)以「#」開始的行爲註釋
(2)文件依賴關係爲:
target:components
rule
存盤爲MyFirst,在終端輸入:make MyFist
關於makefile的使用,能夠參考:http://www.cnblogs.com/wang_yb/p/3990952.html
3、gdb經常使用命令:
[root@redhat home]#gdb 調試文件:啓動gdb
(gdb) l :(字母l)從第一行開始列出源碼
(gdb) break n :在第n行處設置斷點
(gdb) break func:在函數func()的入口處設置斷點
(gdb) info break: 查看斷點信息
(gdb) r:運行程序
(gdb) n:單步執行
(gdb) c:繼續運行
(gdb) p 變量 :打印變量的值
(gdb) bt:查看函數堆棧
(gdb) finish:退出函數
(gdb) shell 命令行:執行shell命令行
(gdb) set args 參數:指定運行時的參數
(gdb) show args:查看設置好的參數
(gdb) show paths:查看程序運行路徑;
set environment varname [=value] 設置環境變量。如:set env USER=hchen;
show environment [varname] 查看環境變量;
(gdb) cd 至關於shell的cd;
(gdb)pwd :顯示當前所在目錄
(gdb)info program: 來查看程序的是否在運行,進程號,被暫停的緣由。
(gdb)clear 行號n:清除第n行的斷點
(gdb)delete 斷點號n:刪除第n個斷點
(gdb)disable 斷點號n:暫停第n個斷點
(gdb)enable 斷點號n:開啓第n個斷點
(gdb)step:單步調試若是有函數調用,則進入函數;與命令n不一樣,n是不進入調用的函數的
list :簡記爲 l ,其做用就是列出程序的源代碼,默認每次顯示10行。
list 行號:將顯示當前文件以「行號」爲中心的先後10行代碼,如:list 12
list 函數名:將顯示「函數名」所在函數的源代碼,如:list main
list :不帶參數,將接着上一次 list 命令的,輸出下邊的內容。 注意 :若是運行list 命令獲得相似以下的打印,那是由於在編譯程序時沒有加入 -g 選項:
(gdb) list
1 ../sysdeps/i386/elf/start.S: No such file or directory.
in ../sysdeps/i386/elf/start.S
run:簡記爲 r ,其做用是運行程序,當遇到斷點後,程序會在斷點處中止運行,等待用戶輸入下一步的命令。
回車:重複上一條命令。
set args:設置運行程序時的命令行參數,如:set args 33 55
show args:顯示命令行參數
continue:簡訊爲 c ,其做用是繼續運行被斷點中斷的程序。
break:爲程序設置斷點。
break 行號:在當前文件的「行號」處設置斷點,如:break 33
break 函數名:在用戶定義的函數「函數名」處設置斷點,如:break cb_button
info breakpoints:顯示當前程序的斷點設置狀況
disable breakpoints Num:關閉斷點「Num」,使其無效,其中「Num」爲 info breakpoints 中顯示的對應值
enable breakpoints Num:打開斷點「Num」,使其從新生效
step:簡記爲 s ,單步跟蹤程序,當遇到函數調用時,則進入此函數體(通常只進入用戶自定義函數)。
next:簡記爲 n,單步跟蹤程序,當遇到函數調用時,也不進入此函數體;此命令同 step 的主要區別是,step 遇到用戶自定義的函數,將步進到函數中去運行,而 next 則直接調用函數,不會進入到函數體內。
until:當你厭倦了在一個循環體內單步跟蹤時,這個命令能夠運行程序直到退出循環體。
finish: 運行程序,直到當前函數完成返回,並打印函數返回時的堆棧地址和返回值及參數值等信息。
stepi或nexti:單步跟蹤一些機器指令。
print 表達式:簡記爲 p ,其中「表達式」能夠是任何當前正在被測試程序的有效表達式,好比當前正在調試C語言的程序,那麼「表達式」能夠是任何C語言的有效表達式,包括數字,變量甚至是函數調用。
print a:將顯示整數 a 的值
print ++a:將把 a 中的值加1,並顯示出來
print name:將顯示字符串 name 的值
print gdb_test(22):將以整數22做爲參數調用 gdb_test() 函數
print gdb_test(a):將以變量 a 做爲參數調用 gdb_test() 函數
bt:顯示當前程序的函數調用堆棧。
display 表達式:在單步運行時將很是有用,使用display命令設置一個表達式後,它將在每次單步進行指令後,緊接着輸出被設置的表達式及值。如: display a
watch 表達式:設置一個監視點,一旦被監視的「表達式」的值改變,gdb將強行終止正在被調試的程序。如: watch a
kill:將強行終止當前正在調試的程序
help 命令:help 命令將顯示「命令」的經常使用幫助信息
call 函數(參數):調用「函數」,並傳遞「參數」,如:call gdb_test(55)
layout:用於分割窗口,能夠一邊查看代碼,一邊測試:
layout src:顯示源代碼窗口
layout asm:顯示反彙編窗口
layout regs:顯示源代碼/反彙編和CPU寄存器窗口
layout split:顯示源代碼和反彙編窗口
Ctrl + L:刷新窗口
quit:簡記爲 q ,退出gdb
4、gdb 學習小例:
#include <stdio.h> int add_range(int low, int high) { int i, sum; for (i = low; i <= high; i++) sum = sum + i; return sum; } int main(void) { int result[100]; result[0] = add_range(1, 10); result[1] = add_range(1, 100); printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); return 0; }
add_range
函數從low
加到high
,在main
函數中首先從1加到10,把結果保存下來,而後從1加到100,再把結果保存下來,最後打印的兩個結果是:
result[0]=55 result[1]=5105
第一個結果正確, 第二個結果顯然不正確,在小學咱們就據說太高斯小時候的故事,從1加到100應該是5050。一段代碼,第一次運行結果是對的,第二次運行卻不對,這是很 常見的一類錯誤現象,這種狀況不該該懷疑代碼而應該懷疑數據,由於第一次和第二次運行的都是同一段代碼,若是代碼是錯的,那爲何第一次的結果能對呢?然 而第一次和第二次運行時相關的數據卻有可能不一樣,錯誤的數據會致使錯誤的結果。在動手調試以前,讀者先試試只看代碼能不能看出錯誤緣由,只要前面幾章學得 紮實就應該能看出來。
在編譯時要加上-g
選項,生成的可執行文件才能用gdb
進行源碼級調試:
$ gcc -g main.c -o main $ gdb main GNU gdb 6.8-debian Copyright (C) 2008 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html> This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Type "show copying" and "show warranty" for details. This GDB was configured as "i486-linux-gnu"... (gdb)
-g
選項的做用是在可執行文件中加入源代碼的信息,好比可執行文件中第幾條機器指令對應源代碼的第幾行,但並非把整個源文件嵌入到可執行文件中,因此在調試時必須保證gdb
能找到源文件。gdb
提供一個相似Shell的命令行環境,上面的(gdb)
就是提示符,在這個提示符下輸入help
能夠查看命令的類別:
(gdb) help List of classes of commands:
aliases -- Aliases of other commands breakpoints -- Making program stop at certain points data -- Examining data files -- Specifying and examining files internals -- Maintenance commands obscure -- Obscure features running -- Running the program stack -- Examining the stack status -- Status inquiries support -- Support facilities tracepoints -- Tracing of program execution without stopping the program user-defined -- User-defined commands Type "help" followed by a class name for a list of commands in that class. Type "help all" for the list of all commands. Type "help" followed by command name for full documentation. Type "apropos word" to search for commands related to "word". Command name abbreviations are allowed if unambiguous.
也能夠進一步查看某一類別中有哪些命令,例如查看files
類別下有哪些命令可用:
(gdb) help files Specifying and examining files. List of commands:
add-shared-symbol-files -- Load the symbols from shared objects in the dynamic linker's link map add-symbol-file -- Load symbols from FILE add-symbol-file-from-memory -- Load the symbols out of memory from a dynamically loaded object file cd -- Set working directory to DIR for debugger and program being debugged core-file -- Use FILE as core dump for examining memory and registers directory -- Add directory DIR to beginning of search path for source files edit -- Edit specified file or function exec-file -- Use FILE as program for getting contents of pure memory file -- Use FILE as program to be debugged forward-search -- Search for regular expression (see regex(3)) from last line listed generate-core-file -- Save a core file with the current state of the debugged process list -- List specified function or line ...
如今試試用list
命令從第一行開始列出源代碼:
(gdb) list 1
1 #include <stdio.h> 2 3 int add_range(int low, int high) 4 { 5 int i, sum; 6 for (i = low; i <= high; i++) 7 sum = sum + i; 8 return sum; 9 } 10
一次只列10行,若是要從第11行開始繼續列源代碼能夠輸入
(gdb) list
也能夠什麼都不輸直接敲回車,gdb
提供了一個很方便的功能,在提示符下直接敲回車表示重複上一條命令。
(gdb) (直接回車)
11 int main(void) 12 { 13 int result[100]; 14 result[0] = add_range(1, 10); 15 result[1] = add_range(1, 100); 16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); 17 return 0; 18
gdb
的不少經常使用命令有簡寫形式,例如list
命令能夠寫成l
,要列一個函數的源代碼也能夠用函數名作參數:
(gdb) l add_range 1 #include <stdio.h> 2 3 int add_range(int low, int high) 4 { 5 int i, sum; 6 for (i = low; i <= high; i++) 7 sum = sum + i; 8 return sum; 9 } 10
如今退出gdb
的環境:
(gdb) quit
咱們作一個實驗,把源代碼更名或移到別處再用gdb
調試,這樣就列不出源代碼了:
$ mv main.c mian.c $ gdb main ... (gdb) l 5 main.c: No such file or directory. in main.c
可見gcc
的-g
選項並非把源代碼嵌入到可執行文件中的,在調試時也須要源文件。如今把源代碼恢復原樣,咱們繼續調試。首先用start
命令開始執行程序:
$ gdb main ... (gdb) start Breakpoint 1 at 0x80483ad: file main.c, line 14. Starting program: /home/akaedu/main main () at main.c:14 14 result[0] = add_range(1, 10); (gdb)
gdb
停在main
函數中變量定義以後的第一條語句處等待咱們發命令,gdb
列出的這條語句是即將執行的下一條語句。咱們能夠用next
命令(簡寫爲n
)控制這些語句一條一條地執行:
(gdb) n 15 result[1] = add_range(1, 100); (gdb) (直接回車) 16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); (gdb) (直接回車)
result[0]=55 result[1]=5105 17 return 0;
用n
命令依次執行兩行賦值語句和一行打印語句,在執行打印語句時結果馬上打出來了,而後停在return
語句以前等待咱們發命令。雖然咱們徹底控制了程序的執行,但仍然看不出哪裏錯了,由於錯誤不在main
函數中而在add_range
函數中,如今用start
命令從新來過,此次用step
命令(簡寫爲s
)鑽進add_range
函數中去跟蹤執行:
(gdb) start The program being debugged has been started already. Start it from the beginning? (y or n) y Breakpoint 2 at 0x80483ad: file main.c, line 14. Starting program: /home/akaedu/main main () at main.c:14 14 result[0] = add_range(1, 10); (gdb) s add_range (low=1, high=10) at main.c:6 6 for (i = low; i <= high; i++)
此次停在了add_range
函數中變量定義以後的第一條語句處。在函數中有幾種查看狀態的辦法,backtrace
命令(簡寫爲bt
)能夠查看函數調用的棧幀:
(gdb) bt #0 add_range (low=1, high=10) at main.c:6 #1 0x080483c1 in main () at main.c:14
可見當前的add_range
函數是被main
函數調用的,main
傳進來的參數是low=1, high=10
。main
函數的棧幀編號爲1,add_range
的棧幀編號爲0。如今能夠用info
命令(簡寫爲i
)查看add_range
函數局部變量的值:
(gdb) i locals
i = 0 sum = 0
若是想查看main
函數當前局部變量的值也能夠作到,先用frame
命令(簡寫爲f
)選擇1號棧幀而後再查看局部變量:
(gdb) f 1 #1 0x080483c1 in main () at main.c:14 14 result[0] = add_range(1, 10); (gdb) i locals result = {0, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480, ... -1208623680}
注意到result
數組中有不少元素具備雜亂無章的值,咱們知道未經初始化的局部變量具備不肯定的值。到目前爲止一切正常。用s
或n
往下走幾步,而後用print
命令(簡寫爲p
)打印出變量sum
的值:
(gdb) s 7 sum = sum + i; (gdb) (直接回車) 6 for (i = low; i <= high; i++) (gdb) (直接回車) 7 sum = sum + i; (gdb) (直接回車) 6 for (i = low; i <= high; i++) (gdb) p sum $1 = 3
第一次循環i
是1,第二次循環i
是2,加起來是3,沒錯。這裏的$1
表示gdb
保存着這些中間結果,$後面的編號會自動增加,在命令中能夠用$1
、$2
、$3
等編號代替相應的值。因爲咱們原本就知道第一次調用的結果是正確的,再往下跟也沒意義了,能夠用finish
命令讓程序一直運行到從當前函數返回爲止:
(gdb) finish Run till exit from #0 add_range (low=1, high=10) at main.c:6 0x080483c1 in main () at main.c:14 14 result[0] = add_range(1, 10); Value returned is $2 = 55
返回值是55,當前正準備執行賦值操做,用s
命令賦值,而後查看result
數組:
(gdb) s 15 result[1] = add_range(1, 100); (gdb) p result $3 = {55, 0, 0, 0, 0, 0, 134513196, 225011984, -1208685768, -1081160480, ... -1208623680}
第一個值55確實賦給了result
數組的第0個元素。下面用s
命令進入第二次add_range
調用,進入以後首先查看參數和局部變量:
(gdb) s add_range (low=1, high=100) at main.c:6 6 for (i = low; i <= high; i++) (gdb) bt #0 add_range (low=1, high=100) at main.c:6 #1 0x080483db in main () at main.c:15 (gdb) i locals i = 11 sum = 55
因爲局部變量i
和sum
沒初始化,因此具備不肯定的值,又因爲兩次調用是挨着的,i
和sum
正好取了上次調用時的值。i
的初值不是0倒不要緊,在for
循環中會賦值爲0的,但sum
若是初值不是0,累加獲得的結果就錯了。好了,咱們已經找到錯誤緣由,能夠退出gdb
修改源代碼了。若是咱們不想浪費此次調試機會,能夠在gdb
中立刻把sum
的初值改成0繼續運行,看看這一處改了以後還有沒有別的Bug:
(gdb) set var sum=0 (gdb) finish Run till exit from #0 add_range (low=1, high=100) at main.c:6 0x080483db in main () at main.c:15 15 result[1] = add_range(1, 100); Value returned is $4 = 5050 (gdb) n 16 printf("result[0]=%d\nresult[1]=%d\n", result[0], result[1]); (gdb) (直接回車) result[0]=55 result[1]=5050 17 return 0;
這樣結果就對了。修改變量的值除了用set
命令以外也能夠用print
命令,由於print
命令後面跟的是表達式,而咱們知道賦值和函數調用也都是表達式,因此也能夠用print
命令修改變量的值或者調用函數:
(gdb) p result[2]=33
$5 = 33
(gdb) p printf("result[2]=%d\n", result[2])
result[2]=33
$6 = 13
5、多線程調試
1. 多線程調試,最重要的幾個命令:
info threads 查看當前進程的線程。
GDB會爲每一個線程分配一個ID, 後面操做線程的時候會用到這個ID.
前面有*的是當前調試的線程.
thread <ID> 切換調試的線程爲指定ID的線程。
break file.c:100 thread all 在file.c文件第100行處爲全部通過這裏的線程設置斷點。
set scheduler-locking off|on|step
在使用step或者continue命令調試當前被調試線程的時候,其餘線程也是同時執行的,
怎麼只讓被調試程序執行呢?
經過這個命令就能夠實現這個需求。
off 不鎖定任何線程,也就是全部線程都執行,這是默認值。
on 只有當前被調試程序會執行。
step 在單步的時候,除了next過一個函數的狀況
(熟悉狀況的人可能知道,這實際上是一個設置斷點而後continue的行爲)之外,
只有當前線程會執行。
thread apply ID1 ID2 command 讓一個或者多個線程執行GDB命令command
thread apply all command 讓全部被調試線程執行GDB命令command。
2. 使用示例:
線程產生通知:在產生新的線程時, gdb會給出提示信息
(gdb) r
Starting program: /root/thread
[New Thread 1073951360 (LWP 12900)]
[New Thread 1082342592 (LWP 12907)]---如下三個爲新產生的線程
[New Thread 1090731072 (LWP 12908)]
[New Thread 1099119552 (LWP 12909)]
查看線程:使用info threads能夠查看運行的線程。
(gdb) info threads
4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? ()
3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? ()
2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)
注意,行首爲gdb分配的線程ID號,對線程進行切換時,使用該ID號碼。
另外,行首的星號標識了當前活動的線程
切換線程:
使用 thread THREADNUMBER 進行切換,THREADNUMBER 爲上文提到的線程ID號。
下例顯示將活動線程從 1 切換至 4。
(gdb) info threads
4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? ()
3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? ()
2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()
* 1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb) thread 4
[Switching to thread 4 (Thread 1099119552 (LWP 12940))]#0 0xffffe002 in ?? ()
(gdb) info threads
* 4 Thread 1099119552 (LWP 12940) 0xffffe002 in ?? ()
3 Thread 1090731072 (LWP 12939) 0xffffe002 in ?? ()
2 Thread 1082342592 (LWP 12938) 0xffffe002 in ?? ()
1 Thread 1073951360 (LWP 12931) main (argc=1, argv=0xbfffda04) at thread.c:21
(gdb)
以上即爲使用gdb提供的對多線程進行調試的一些基本命令。
另外,gdb也提供對線程的斷點設置以及對指定或全部線程發佈命令的命令
6、調試宏
在GDB下, 咱們沒法print宏定義,由於宏是預編譯的。
可是咱們仍是有辦法來調試宏,這個須要GCC的配合。
在GCC編譯程序的時候,加上
-ggdb3 參數,這樣,你就能夠調試宏了。
另外,你可使用下述的GDB的宏調試命令 來查看相關的宏。
info macro 查看這個宏在哪些文件裏被引用了,以及宏定義是什麼樣的。
macro 查看宏展開的樣子。
七、源文件
GDB時,提示找不到源文件。
須要作下面的檢查:
編譯程序員是否加上了 -g參數 以包含debug信息。
路徑是否設置正確了。
使用GDB的directory命令來設置源文件的目錄。
下面給一個調試/bin/ls的示例(ubuntu下)
$ apt-get source coreutils
$ sudo apt-get install coreutils-dbgsym
$ gdb /bin/ls
GNU gdb (GDB) 7.1-ubuntu
(gdb) list main
1192 ls.c: No such file or directory.
in ls.c
(gdb) directory ~/src/coreutils-7.4/src/
Source directories searched: /home/hchen/src/coreutils-7.4:$cdir:$cwd
(gdb) list main
1192 }
1193 }
1194
1195 int
1196 main (int argc, char **argv)
1197 {
1198 int i;
1199 struct pending *thispend;
1200 int n_files;
1201
8、條件斷點
條件斷點是語法是:
break [where] if [condition]
這種斷點真是很是管用。
尤爲是在一個循環或遞歸中,或是要監視某個變量。
注意,這個設置是在GDB中的,只不過每通過那個斷點時GDB會幫你檢查一下條件是否知足。
9、命令行參數
有時候,咱們須要調試的程序須要有命令行參數, 有三種方法:
gdb命令行的 -args 參數
gdb環境中 set args命令。
gdb環境中 run 參數
10、gdb的變量
有時候,在調試程序時,咱們不僅僅只是查看運行時的變量,
咱們還能夠直接設置程序中的變量,以模擬一些很難在測試中出現的狀況,比較一些出錯,
或是switch的分支語句。使用set命令能夠修改程序中的變量。
另外,你知道gdb中也能夠有變量嗎?
就像shell同樣,gdb中的變量以$開頭,好比你想打印一個數組中的個個元素,你能夠這樣:
(gdb) set $i = 0
(gdb) p a[$i++]
... #而後就一路回車下去了
固然,這裏只是給一個示例,表示程序的變量和gdb的變量是能夠交互的。
11、x命令
也許,你很喜歡用p命令。
因此,當你不知道變量名的時候,你可能會手足無措,由於p命令老是須要一個變量名的。
x命令是用來查看內存的,在gdb中 「help x」 你能夠查看其幫助。
x/x 以十六進制輸出
x/d 以十進制輸出
x/c 以單字符輸出
x/i 反彙編 – 一般,咱們會使用 x/10i $ip-20 來查看當前的彙編($ip是指令寄存器)
x/s 以字符串輸出
12、command命令
如何自動化調試。
這裏向你們介紹command命令,簡單的理解一下,其就是把一組gdb的命令打包,有點像字處理軟件的「宏」。
下面是一個示例:
(gdb) break func
Breakpoint 1 at 0x3475678: file test.c, line 12.
(gdb) command 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>print arg1
>print arg2
>print arg3
>end
(gdb)
當咱們的斷點到達時,自動執行command中的三個命令,把func的三個參數值打出來。