輸出預處理結果到文件。
$ gcc -E main.c -o main.i
保留文件頭注釋。
$ gcc -C -E main.c -o main.i
參數(shù) -Dname 定義宏 (源文件中不能定義該宏),-Uname 取消 GCC 設置中定義的宏。
$ tail -n 10 main.c
int main(int argc, char* argv[])
{
#if __MY__
printf("a");
#else
printf("b");
#endif
return EXIT_SUCCESS;
}
$ gcc -E main.c -D__MY__ | tail -n 10
int main(int argc, char* argv[])
{
printf("a");
return 0;
}
-Idirectory 設置頭文件(.h)的搜索路徑。
$ gcc -g -I./lib -I/usr/local/include/cbase main.c mylib.c
查看依賴文件。
$ gcc -M -I./lib main.c
$ gcc -MM -I./lib main.c # 忽略標準庫
我們可以將 C 源代碼編譯成匯編語言 (.s)。
$ gcc -S main.c
$ head -n 20 main.s
.file "main.c"
.section .rodata
.LC0:
.string "Hello, World!"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $.LC0, (%esp)
call test
movl $0, %eax
leave
ret
.size main, .-main
.ident "GCC: (Ubuntu 4.4.1-4ubuntu9) 4.4.1"
.section .note.GNU-stack,"",@progbits
使用 -fverbose-asm 參數(shù)可以獲取變量注釋。如果需要指定匯編格式,可以使用 "-masm=intel"參數(shù)。
參數(shù) -c 僅生成目標文件 (.o),然后需要調用鏈接器 (link) 將多個目標文件鏈接成單一可執(zhí)行文件。
$ gcc -g -c main.c mylib.c
參數(shù) -l 鏈接其他庫,比如 -lpthread 鏈接 libpthread.so?;蛑付?-static 參數(shù)進行靜態(tài)鏈接。我們還可以直接指定鏈接庫 (.so, .a) 完整路徑。
$ gcc -g -o test main.c ./libmy.so ./libtest.a
$ ldd ./test
linux-gate.so.1 => (0xb7860000)
./libmy.so (0xb785b000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7710000)
/lib/ld-linux.so.2 (0xb7861000)
另外一種做法就是用 -L 指定庫搜索路徑。
$ gcc -g -o test -L/usr/local/lib -lgdsl main.c
$ ldd ./test
linux-gate.so.1 => (0xb77b6000)
libgdsl.so.1 => /usr/local/lib/libgdsl.so.1 (0xb779b000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7656000)
/lib/ld-linux.so.2 (0xb77b7000)
使用 "-fPIC -shared" 參數(shù)生成動態(tài)庫。
$ gcc -fPIC -c -O2 mylib.c
$ gcc -shared -o libmy.so mylib.o
$ nm libmy.so
... ...
00000348 T _init
00002010 b completed.6990
00002014 b dtor_idx.6992
... ...
0000047c T test
靜態(tài)庫則需要借助 ar 工具將多個目標文件 (.o) 打包。
c$ gcc -c mylib.c
$ ar rs libmy.a mylib.o
ar: creating libmy.a
參數(shù) -O0 關閉優(yōu)化 (默認);-O1 (或 -O) 讓可執(zhí)行文件更小,速度更快;-O2 采用幾乎所有的優(yōu)化手段。
$ gcc -O2 -o test main.c mylib.c
參數(shù) -g 在對象文件 (.o) 和執(zhí)行文件中生成符號表和源代碼行號信息,以便使用 gdb 等工具進行調試。
$ gcc -g -o test main.c mylib.c
$ readelf -S test
There are 38 section headers, starting at offset 0x18a8:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
... ...
[27] .debug_aranges PROGBITS 00000000 001060 000060 00 0 0 8
[28] .debug_pubnames PROGBITS 00000000 0010c0 00005b 00 0 0 1
[29] .debug_info PROGBITS 00000000 00111b 000272 00 0 0 1
[30] .debug_abbrev PROGBITS 00000000 00138d 00014b 00 0 0 1
[31] .debug_line PROGBITS 00000000 0014d8 0000f1 00 0 0 1
[32] .debug_frame PROGBITS 00000000 0015cc 000058 00 0 0 4
[33] .debug_str PROGBITS 00000000 001624 0000d5 01 MS 0 0 1
[34] .debug_loc PROGBITS 00000000 0016f9 000058 00 0 0 1
... ...
參數(shù) -pg 會在程序中添加性能分析 (profiling) 函數(shù),用于統(tǒng)計程序中最耗費時間的函數(shù)。程序執(zhí)行后,統(tǒng)計信息保存在 gmon.out 文件中,可以用 gprof 命令查看結果。
$ gcc -g -pg main.c mylib.c
作為內置和最常用的調試器,GDB 顯然有著無可辯駁的地位。熟練使用 GDB,就好像所有 Linux 下的開發(fā)人員建議你用 VIM 一樣,是個很 "奇怪" 的情節(jié)。
測試用源代碼。
#include <stdio.h>
int test(int a, int b)
{
int c = a + b;
return c;
}
int main(int argc, char* argv[])
{
int a = 0x1000;
int b = 0x2000;
int c = test(a, b);
printf("%d\n", c);
printf("Hello, World!\n");
return 0;
}
編譯命令 (注意使用 -g 參數(shù)生成調試符號):
$ gcc -g -o hello hello.c
開始調試:
$ gdb hello
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
This GDB was configured as "i486-linux-gnu"...
(gdb)
在調試過程中查看源代碼是必須的。list (縮寫 l) 支持多種方式查看源碼。
(gdb) l # 顯示源代碼
2
3 int test(int a, int b)
4 {
5 int c = a + b;
6 return c;
7 }
8
9 int main(int argc, char* argv[])
10 {
11 int a = 0x1000;
(gdb) l # 繼續(xù)顯示
12 int b = 0x2000;
13 int c = test(a, b);
14 printf("%d\n", c);
15
16 printf("Hello, World!\n");
17 return 0;
18 }
(gdb) l 3, 10 # 顯示特定范圍的源代碼
3 int test(int a, int b)
4 {
5 int c = a + b;
6 return c;
7 }
8
9 int main(int argc, char* argv[])
10 {
(gdb) l main # 顯示特定函數(shù)源代碼
5 int c = a + b;
6 return c;
7 }
8
9 int main(int argc, char* argv[])
10 {
11 int a = 0x1000;
12 int b = 0x2000;
13 int c = test(a, b);
14 printf("%d\n", c);
可以用如下命令修改源代碼顯示行數(shù)。
(gdb) set listsize 50
可以使用函數(shù)名或者源代碼行號設置斷點。
(gdb) b main # 設置函數(shù)斷點
Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) b 13 # 設置源代碼行斷點
Breakpoint 2 at 0x8048429: file hello.c, line 13.
(gdb) b # 將下一行設置為斷點 (循環(huán)、遞歸等調試很有用)
Breakpoint 5 at 0x8048422: file hello.c, line 12.
(gdb) tbreak main # 設置臨時斷點 (中斷后失效)
Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) info breakpoints # 查看所有斷點
Num Type Disp Enb Address What
2 breakpoint keep y 0x0804841b in main at hello.c:11
3 breakpoint keep y 0x080483fa in test at hello.c:5
(gdb) d 3 # delete: 刪除斷點 (還可以用范圍 "d 1-3",無參數(shù)時刪除全部斷點)
(gdb) disable 2 # 禁用斷點 (還可以用范圍 "disable 1-3")
(gdb) enable 2 # 啟用斷點 (還可以用范圍 "enable 1-3")
(gdb) ignore 2 1 # 忽略 2 號中斷 1 次
當然少不了條件式中斷。
(gdb) b test if a == 10
Breakpoint 4 at 0x80483fa: file hello.c, line 5.
(gdb) info breakpoints
Num Type Disp Enb Address What
4 breakpoint keep y 0x080483fa in test at hello.c:5
stop only if a == 10
可以用 condition 修改條件,注意表達式不包含 if。
(gdb) condition 4 a == 30
(gdb) info breakpoints
Num Type Disp Enb Address What
2 breakpoint keep y 0x0804841b in main at hello.c:11
ignore next 1 hits
4 breakpoint keep y 0x080483fa in test at hello.c:5
stop only if a == 30
通常情況下,我們會先設置 main 入口斷點。
(gdb) b main
Breakpoint 1 at 0x804841b: file hello.c, line 11.
(gdb) r # 開始執(zhí)行 (Run)
Starting program: /home/yuhen/Learn.c/hello
Breakpoint 1, main () at hello.c:11
11 int a = 0x1000;
(gdb) n # 單步執(zhí)行 (不跟蹤到函數(shù)內部, Step Over)
12 int b = 0x2000;
(gdb) n
13 int c = test(a, b);
(gdb) s # 單步執(zhí)行 (跟蹤到函數(shù)內部, Step In)
test (a=4096, b=8192) at hello.c:5
5 int c = a + b;
(gdb) finish # 繼續(xù)執(zhí)行直到當前函數(shù)結束 (Step Out)
Run till exit from #0 test (a=4096, b=8192) at hello.c:5
0x0804843b in main () at hello.c:13
13 int c = test(a, b);
Value returned is $1 = 12288
(gdb) c # Continue: 繼續(xù)執(zhí)行,直到下一個斷點。
Continuing.
12288
Hello, World!
Program exited normally.
查看調用堆棧無疑是調試過程中非常重要的事情。
(gdb) where # 查看調用堆棧 (相同作用的命令還有 info s 和 bt)
#0 test (a=4096, b=8192) at hello.c:5
#1 0x0804843b in main () at hello.c:13
(gdb) frame # 查看當前堆棧幀,還可顯示當前代碼
#0 test (a=4096, b=8192) at hello.c:5
5 int c = a + b;
(gdb) info frame # 獲取當前堆棧幀更詳細的信息
Stack level 0, frame at 0xbfad3290:
eip = 0x80483fa in test (hello.c:5); saved eip 0x804843b
called by frame at 0xbfad32c0
source language c.
Arglist at 0xbfad3288, args: a=4096, b=8192
Locals at 0xbfad3288, Previous frame's sp is 0xbfad3290
Saved registers:
ebp at 0xbfad3288, eip at 0xbfad328c
可以用 frame 修改當前堆棧幀,然后查看其詳細信息。
(gdb) frame 1
#1 0x0804843b in main () at hello.c:13
13 int c = test(a, b);
(gdb) info frame
Stack level 1, frame at 0xbfad32c0:
eip = 0x804843b in main (hello.c:13); saved eip 0xb7e59775
caller of frame at 0xbfad3290
source language c.
Arglist at 0xbfad32b8, args:
Locals at 0xbfad32b8, Previous frame's sp at 0xbfad32b4
Saved registers:
ebp at 0xbfad32b8, eip at 0xbfad32bc
(gdb) info locals # 顯示局部變量
c = 0
(gdb) info args # 顯示函數(shù)參數(shù)(自變量)
a = 4096
b = 8192
我們同樣可以切換 frame,然后查看不同堆棧幀的信息。
(gdb) p a # print 命令可顯示局部變量和參數(shù)值
$2 = 4096
(gdb) p/x a # 十六進制輸出
$10 = 0x1000
(gdb) p a + b # 還可以進行表達式計算
$5 = 12288
x 命令內存輸出格式:
set variable 可用來修改變量值。
(gdb) set variable a=100
(gdb) info args
a = 100
b = 8192
x 命令可以顯示指定地址的內存數(shù)據(jù)。
格式: x/nfu [address]
(gdb) x/8w 0x0804843b # 按四字節(jié)(w)顯示 8 組內存數(shù)據(jù)
0x804843b <main+49>: 0x8bf04589 0x4489f045 0x04c70424 0x04853024
0x804844b <main+65>: 0xfecbe808 0x04c7ffff 0x04853424 0xfecfe808
(gdb) x/8i 0x0804843b # 顯示 8 行匯編指令
0x804843b <main+49>: mov DWORD PTR [ebp-0x10],eax
0x804843e <main+52>: mov eax,DWORD PTR [ebp-0x10]
0x8048441 <main+55>: mov DWORD PTR [esp+0x4],eax
0x8048445 <main+59>: mov DWORD PTR [esp],0x8048530
0x804844c <main+66>: call 0x804831c <printf@plt>
0x8048451 <main+71>: mov DWORD PTR [esp],0x8048534
0x8048458 <main+78>: call 0x804832c <puts@plt>
0x804845d <main+83>: mov eax,0x0
(gdb) x/s 0x08048530 # 顯示字符串
0x8048530: "%d\n"
除了通過 "info frame" 查看寄存器值外,還可以用如下指令。
(gdb) info registers # 顯示所有寄存器數(shù)據(jù)
eax 0x1000 4096
ecx 0xbfad32d0 -1079168304
edx 0x1 1
ebx 0xb7fa1ff4 -1208344588
esp 0xbfad3278 0xbfad3278
ebp 0xbfad3288 0xbfad3288
esi 0x8048480 134513792
edi 0x8048340 134513472
eip 0x80483fa 0x80483fa <test+6>
eflags 0x286 [ PF SF IF ]
cs 0x73 115
ss 0x7b 123
ds 0x7b 123
es 0x7b 123
fs 0x0 0
gs 0x33 51
(gdb) p $eax # 顯示單個寄存器數(shù)據(jù)
$11 = 4096
我對 AT&T 匯編不是很熟悉,還是設置成 intel 格式的好。
(gdb) set disassembly-flavor intel # 設置反匯編格式
(gdb) disass main # 反匯編函數(shù)
Dump of assembler code for function main:
0x0804840a <main+0>: lea ecx,[esp+0x4]
0x0804840e <main+4>: and esp,0xfffffff0
0x08048411 <main+7>: push DWORD PTR [ecx-0x4]
0x08048414 <main+10>: push ebp
0x08048415 <main+11>: mov ebp,esp
0x08048417 <main+13>: push ecx
0x08048418 <main+14>: sub esp,0x24
0x0804841b <main+17>: mov DWORD PTR [ebp-0x8],0x1000
0x08048422 <main+24>: mov DWORD PTR [ebp-0xc],0x2000
0x08048429 <main+31>: mov eax,DWORD PTR [ebp-0xc]
0x0804842c <main+34>: mov DWORD PTR [esp+0x4],eax
0x08048430 <main+38>: mov eax,DWORD PTR [ebp-0x8]
0x08048433 <main+41>: mov DWORD PTR [esp],eax
0x08048436 <main+44>: call 0x80483f4 <test>
0x0804843b <main+49>: mov DWORD PTR [ebp-0x10],eax
0x0804843e <main+52>: mov eax,DWORD PTR [ebp-0x10]
0x08048441 <main+55>: mov DWORD PTR [esp+0x4],eax
0x08048445 <main+59>: mov DWORD PTR [esp],0x8048530
0x0804844c <main+66>: call 0x804831c <printf@plt>
0x08048451 <main+71>: mov DWORD PTR [esp],0x8048534
0x08048458 <main+78>: call 0x804832c <puts@plt>
0x0804845d <main+83>: mov eax,0x0
0x08048462 <main+88>: add esp,0x24
0x08048465 <main+91>: pop ecx
0x08048466 <main+92>: pop ebp
0x08048467 <main+93>: lea esp,[ecx-0x4]
0x0804846a <main+96>: ret
End of assembler dump.
可以用 "b *address" 設置匯編斷點,然后用 si 和 ni 進行匯編級單步執(zhí)行,這對于分析指針和尋址非常有用。
查看進程相關信息,尤其是 maps 內存數(shù)據(jù)是非常有用的。
(gdb) help info proc stat
Show /proc process information about any running process.
Specify any process id, or use the program being debugged by default.
Specify any of the following keywords for detailed info:
mappings -- list of mapped memory regions.
stat -- list a bunch of random process info.
status -- list a different bunch of random process info.
all -- list all available /proc info.
(gdb) info proc mappings !# 相當于 cat /proc/{pid}/maps
process 22561
cmdline = '/home/yuhen/Learn.c/hello'
cwd = '/home/yuhen/Learn.c'
exe = '/home/yuhen/Learn.c/hello'
Mapped address spaces:
Start Addr End Addr Size Offset objfile
0x8048000 0x8049000 0x1000 0 /home/yuhen/Learn.c/hello
0x8049000 0x804a000 0x1000 0 /home/yuhen/Learn.c/hello
0x804a000 0x804b000 0x1000 0x1000 /home/yuhen/Learn.c/hello
0x8a33000 0x8a54000 0x21000 0x8a33000 [heap]
0xb7565000 0xb7f67000 0xa02000 0xb7565000
0xb7f67000 0xb80c3000 0x15c000 0 /lib/tls/i686/cmov/libc-2.9.so
0xb80c3000 0xb80c4000 0x1000 0x15c000 /lib/tls/i686/cmov/libc-2.9.so
0xb80c4000 0xb80c6000 0x2000 0x15c000 /lib/tls/i686/cmov/libc-2.9.so
0xb80c6000 0xb80c7000 0x1000 0x15e000 /lib/tls/i686/cmov/libc-2.9.so
0xb80c7000 0xb80ca000 0x3000 0xb80c7000
0xb80d7000 0xb80d9000 0x2000 0xb80d7000
0xb80d9000 0xb80da000 0x1000 0xb80d9000 [vdso]
0xb80da000 0xb80f6000 0x1c000 0 /lib/ld-2.9.so
0xb80f6000 0xb80f7000 0x1000 0x1b000 /lib/ld-2.9.so
0xb80f7000 0xb80f8000 0x1000 0x1c000 /lib/ld-2.9.so
0xbfee2000 0xbfef7000 0x15000 0xbffeb000 [stack]
可以在 pthread_create 處設置斷點,當線程創(chuàng)建時會生成提示信息。
(gdb) c
Continuing.
[New Thread 0xb7e78b70 (LWP 2933)]
(gdb) info threads # 查看所有線程列表
* 2 Thread 0xb7e78b70 (LWP 2933) test (arg=0x804b008) at main.c:24
1 Thread 0xb7e796c0 (LWP 2932) 0xb7fe2430 in __kernel_vsyscall ()
(gdb) where # 顯示當前線程調用堆棧
#0 test (arg=0x804b008) at main.c:24
#1 0xb7fc580e in start_thread (arg=0xb7e78b70) at pthread_create.c:300
#2 0xb7f478de in clone () at ../sysdeps/unix/sysv/linux/i386/clone.S:130
(gdb) thread 1 # 切換線程
[Switching to thread 1 (Thread 0xb7e796c0 (LWP 2932))]#0 0xb7fe2430 in __kernel_vsyscall ()
(gdb) where # 查看切換后線程調用堆棧
#0 0xb7fe2430 in __kernel_vsyscall ()
#1 0xb7fc694d in pthread_join (threadid=3085405040, thread_return=0xbffff744) at pthread_join.c:89
#2 0x08048828 in main (argc=1, argv=0xbffff804) at main.c:36
調試子進程。
(gdb) set follow-fork-mode child
臨時進入 Shell 執(zhí)行命令,Exit 返回。
(gdb) shell
調試時直接調用函數(shù)。
(gdb) call test("abc")
使用 "--tui" 參數(shù),可以在終端窗口上部顯示一個源代碼查看窗。
$ gdb --tui hello
查看命令幫助。
(gdb) help b
最后就是退出命令。
(gdb) q
和 Linux Base Shell 習慣一樣,對于記不住的命令,可以在輸入前幾個字母后按 Tab 補全。
在 Windows 下我們已經習慣了用 Windbg 之類的工具調試 dump 文件,從而分析并排除程序運行時錯誤。在 Linux 下我們同樣可以完成類似的工作 —— Core Dump。
我們先看看相關的設置。
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 20
file size (blocks, -f) unlimited
pending signals (-i) 16382
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) unlimited
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
"core file size (blocks, -c) 0" 意味著在程序崩潰時不會生成 core dump 文件,我們需要修改一下設置。如果你和我一樣懶得修改配置文件,那么就輸入下面這樣命令吧。
$ sudo sh -c "ulimit -c unlimited; ./test" # test 是可執(zhí)行文件名。
等等…… 我們還是先準備個測試目標。
#include <stdio.h>
#include <stdlib.h>
void test()
{
char* s = "abc";
*s = 'x';
}
int main(int argc, char** argv)
{
test();
return (EXIT_SUCCESS);
}
很顯然,我們在 test 里面寫了一個不該寫的東東,這無疑會很嚴重。生成可執(zhí)行文件后,執(zhí)行上面的命令。
$ sudo sh -c "ulimit -c unlimited; ./test"
Segmentation fault (core dumped)
$ ls -l
total 96
-rw------- 1 root root 167936 2010-01-06 13:30 core
-rwxr-xr-x 1 yuhen yuhen 9166 2010-01-06 13:16 test
這個 core 文件就是被系統(tǒng) dump 出來的,我們分析目標就是它了。
$ sudo gdb test core
GNU gdb (GDB) 7.0-ubuntu
Copyright (C) 2009 Free Software Foundation, Inc.
Reading symbols from .../dist/Debug/test...done.
warning: Can't read pathname for load map: Input/output error.
Reading symbols from /lib/tls/i686/cmov/libpthread.so.0... ...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/tls/i686/cmov/libpthread.so.0
Reading symbols from /lib/tls/i686/cmov/libc.so.6... ...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2... ...done.
(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0 0x080483f4 in test () at main.c:16
warning: Source file is more recent than executable.
16 *s = 'x';
最后這幾行提示已經告訴我們錯誤的原因和代碼位置,接下來如何調試就是 gdb 的技巧了,可以先輸入 where 看看調用堆棧。
(gdb) where
#0 0x080483f4 in test () at main.c:16
#1 0x08048401 in main (argc=1, argv=0xbfd53e44) at main.c:22
(gdb) p s
$1 = 0x80484d0 "abc"
(gdb) info files
Symbols from ".../dist/Debug/test".
Local core dump file:
Local exec file:
`.../dist/Debug/test', file type elf32-i386.
Entry point: 0x8048330
0x08048134 - 0x08048147 is .interp
... ...
0x08048330 - 0x080484ac is .text
0x080484ac - 0x080484c8 is .fini
0x080484c8 - 0x080484d4 is .rodata
很顯然 abc 屬于 .rodata,嚴禁調戲。
附:如果你調試的是 Release (-O2) 版本,而且刪除(strip)了符號表,那還是老老實實數(shù)匯編代碼吧??梢娪?Debug 版本試運行是很重要滴?。。?/p>
Unix-like 環(huán)境下最常用的編輯器,應該掌握最基本的快捷鍵操作。
在 OSX 下可以用 macvim 代替,畢竟圖形化界面要更方便一點。
全局配置文件:/etc/vim/vimrc用戶配置文件:~/.vimrc
" 顯示行號
set nu
" 高亮當前行
set cursorline
" 用空格代替Tab
set expandtab
" 自動縮進
set autoindent
set smartindent
set smarttab
set cindent
" 縮進寬度
set tabstop=4
set shiftwidth=4
" 語法高亮
syntax on
" 禁止在 Makefile 中將 Tab 轉換成空格
autocmd FileType make set noexpandtab
一個完整的 Makefile 通常由 "顯式規(guī)則"、"隱式規(guī)則"、"變量定義"、"指示符"、"注釋" 五部分組成。
(1) 在工作目錄按 "GNUmakefile、makefile、Makefile (推薦)" 順序查找執(zhí)行,或 -f 指定。(2) 如果不在 make 命令行顯式指定目標規(guī)則名,則默認使用第一個有效規(guī)則。(3) Makefile 中 $、# 有特殊含義,可以進行轉義 "#"、"$$"。(4) 可以使用 \ 換行 (注釋行也可以使用),但其后不能有空格,新行同樣必須以 Tab 開頭和縮進。
注意: 本文中提到的目標文件通常是 ".o",類似的還有源文件 (.c)、頭文件 (.h) 等。
規(guī)則組成方式:
target...: prerequisites...
command
...
沒有命令行的規(guī)則只能指示依賴關系,沒有依賴項的規(guī)則指示 "如何" 構建目標,而非 "何時" 構建。
目標的依賴列表可以通過 GCC -MM 參數(shù)獲得。
規(guī)則處理方式:
當我們不編寫顯式規(guī)則時,隱式規(guī)則就會生效。當然我們可以修改隱式規(guī)則的命令。
%.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
未定義規(guī)則或者不包含命令的規(guī)則都會使用隱式規(guī)則。
# 隱式規(guī)則
%.o: %.c
@echo $<
@echo $^
$(CC) $(CFLAGS) -o $@ -c $<
all: test.o main.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^
main.o: test.o test.h
輸出:
$ make
./lib/test.c
./lib/test.c
gcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.c
./src/main.c
./src/main.c test.o ./lib/test.h
gcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.c
gcc -Wall -g -std=c99 -I./lib -I./src -lpthread -o test test.o main.o
test.o 規(guī)則不存在,使用隱式規(guī)則。main.o 沒有命令,使用隱式規(guī)則的同時,還會合并依賴列表。
可以有多個隱式規(guī)則,比如:
%.o: %.c
...
%o: %c %h
...
在隱式規(guī)則前添加特定的目標,就形成了模式規(guī)則。
test.o main.o: %.o: %.c
$(CC) $(CFLAGS) -o $@ -c $<
在實際項目中我們通常將源碼文件分散在多個目錄中,將這些路徑寫入 Makefile 會很麻煩,此時可以考慮用 VPATH 變量指定搜索路徑。
all: lib/test.o src/main.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^
改寫成 VPATH 方式后,要調整項目目錄就簡單多了。
# 依賴目標搜索路徑
VPATH = ./src:./lib
# 隱式規(guī)則
%.o:%.c
-@echo "source file: $<"
$(CC) $(CFLAGS) -o $@ -c $<
all:test.o main.o
$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^
執(zhí)行:
$ make
source file: ./lib/test.c
gcc -Wall -g -std=c99 -I./lib -I./src -o test.o -c ./lib/test.c
source file: ./src/main.c
gcc -Wall -g -std=c99 -I./lib -I./src -o main.o -c ./src/main.c
gcc -Wall -g -std=c99 -I./lib -I./src -lpthread -o test test.o main.o
還可使用 make 關鍵字 vpath。比 VPATH 變量更靈活,甚至可以單獨為某個文件定義路徑。
vpath %.c ./src:./lib # 定義匹配模式(%匹配任意個字符)和搜索路徑。
vpath %.c # 取消該模式
vpath # 取消所有模式
相同的匹配模式可以定義多次,make 會按照定義順序搜索這多個定義的路徑。
vpath %.c ./src
vpath %.c ./lib
vpath %.h ./lib
VPATH 和 vpath 定義的搜索路徑僅對 makefile 規(guī)則有效,對 gcc/g++ 命令行無效,比如不能用它定義命令行頭文件搜索路徑參數(shù)。
當我們?yōu)榱藞?zhí)行命令而非創(chuàng)建目標文件時,就會使用偽目標了,比如 clean。偽目標總是被執(zhí)行。
clean:
-rm *.o
.PHONY: clean
使用 "-" 前綴可以忽略命令錯誤,".PHONY" 的作用是避免和當前目錄下的文件名沖突 (可能引發(fā)隱式規(guī)則)。
每條命令都在一個獨立 shell 環(huán)境中執(zhí)行,如希望在同一 shell 執(zhí)行,可以用 ";" 將命令寫在一行。
test:
cd test; cp test test.bak
提示: 可以用 "\" 換行,如此更美觀一些。
默認情況下,多行命令會順序執(zhí)行。但如果命令出錯,默認會終止后續(xù)執(zhí)行??梢蕴砑?"-" 前綴來忽略命令錯誤。另外還可以添加 "@" 來避免顯示命令行本身。
all: test.o main.o
@echo "build ..."
@$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^
執(zhí)行其他規(guī)則:
all: test.o main.o
$(MAKE) info
@$(CC) $(CFLAGS) $(LDFLAGS) -o $(OUT) $^
info:
@echo "build..."
Makefile 支持類似 shell 的變量功能,相當于 C 宏,本質上就是文本替換。變量名區(qū)分大小寫。變量名建議使用字母、數(shù)字和下劃線組成。引用方式 $(var) 或 ${var}。引用未定義變量時,輸出空。
首先注意的是 "=" 和 ":=" 的區(qū)別。
A = "a: $(C)"
B := "b: $(C)"
C = "haha..."
all:
@echo $A
@echo $B
輸出:
$ make
a: haha...
b:
由于 B 定義時 C 尚未定義,所以直接展開的結果就是空。修改一下,再看。
C = "none..."
A = "a: $(C)"
B := "b: $(C)"
C = "haha..."
all:
@echo $A
@echo $B
輸出:
$ make
a: haha...
b: none...
可見 A 和 B 的展開時機的區(qū)別。
除了使用 "="、":=" 外,還可以用 "define ... endef" 定義多行變量 (宏,遞歸展開,只需在調用時添加 @ 即可)。
define help
echo ""
echo " make release : Build release version."
echo " make clean : Clean templ files."
echo ""
endef
debug:
@echo "Build debug version..."
@$(help)
@$(MAKE) $(OUT) DEBUG=1
release:
@echo "Build release version..."
@$(help)
@$(MAKE) clean $(OUT)
"?=" 表示變量為空或未定義時才進行賦值操作。
A ?= "a"
A ?= "A"
B = "B"
all:
@echo $A
@echo $B
輸出:
$ make
a
B
"+=" 追加變量值。注意變量展開時機。
A = "$B"
A += "..."
B = "haha"
all:
@echo $A
輸出:
$ make
haha ...
使用 "$(VAR:A=B)" 可以將變量 VAR 中所有以 A 結尾的單詞替換成以 B 結尾。
A = "a.o b.o c.o"
all:
@echo $(A:o=c)
輸出:
$ make
a.c b.c c.o
命令行變量會替換 Makefile 中定義的變量值,除非使用 override。
A = "aaa"
override B = "bbb"
C += "ccc"
override D += "ddd"
all:
@echo $A
@echo $B
@echo $C
@echo $D
執(zhí)行:
$ make A="111" B="222" C="333" D="444"
111
bbb
333
444 ddd
我們注意到追加方式在使用 override 后才和命令行變量合并。
僅在某個特定目標中生效,相當于局部變量。
test1: A = "abc"
test1:
@echo "test1" $A
test2:
@echo "test2" $A
輸出:
$ make test1 test2
test1 abc
test2
還可以定義模式變量。
test%: A = "abc"
test1:
@echo "test1" $A
test2:
@echo "test2" $A
輸出:
$ make test1 test2
test1 abc
test2 abc
在變量定義中使用通配符則需要借助 wildcard。
FILES = $(wildcard *.o)
all:
@echo $(FILES)
和 shell 一樣,可以使用 "export VAR" 將變量設定為環(huán)境變量,以便讓命令和遞歸調用的 make 命令能接收到參數(shù)。
例如: 使用 GCC C_INCLUDE_PATH 環(huán)境變量來代替 -I 參數(shù)。
C_INCLUDE_PATH := ./lib:/usr/include:/usr/local/include
export C_INCLUDE_PATH
沒有條件判斷是不行滴。
CFLAGS = -Wall -std=c99 $(INC_PATHS)
ifdef DEBUG
CFLAGS += -g
else
CFLAGS += -O3
endif
類似的還有: ifeq、ifneq、ifndef格式: ifeq (ARG1, ARG2) 或 ifeq "ARG1" "ARG2"
# DEBUG == 1
ifeq "$(DEBUG)" "1"
...
else
...
endif
# DEBUG 不為空
ifneq ($(DEBUG), )
...
else
...
endif
實際上,我們可以用 if 函數(shù)來代替。相當于編程語言中的三元表達式 "?:"。
CFLAGS = -Wall $(if $(DEBUG), -g, -O3) -std=c99 $(INC_PATHS)
*nix 下的 "配置" 都有點 "腳本語言" 的感覺。
make 支持函數(shù)的使用,調用方法 "$(function args)" 或 "${function args}"。多個參數(shù)之間用"," (多余的空格可能會成為參數(shù)的一部分)。
例如: 將 "Hello, World!" 替換成 "Hello, GNU Make!"。
A = Hello, World!
all:
@echo $(subst World, GUN Make, $(A))
注意: 字符串沒有用引號包含起來,如果字符串中有引號字符,使用 "\" 轉義。
這個 foreach 很好,執(zhí)行結果輸出 "[1] [2] [3]"。
A = 1 2 3
all:
@echo $(foreach x,$(A),[$(x)])
我們還可以自定義一個函數(shù),其實就是用一個變量來代替復雜的表達式,比如對上面例子的改寫。
A = x y z
func = $(foreach x, $(1), [$(x)])
all:
@echo $(call func, $(A))
@echo $(call func, 1 2 3)
傳遞的參數(shù)分別是 "$(1), $(2) ..."。
用 define 可以定義一個更復雜一點的多行函數(shù)。
A = x y z
define func
echo "$(2): $(1) -> $(foreach x, $(1), [$(x)])"
endef
all:
@$(call func, $(A), char)
@$(call func, 1 2 3, num)
輸出:
$ make
char: x y z -> [x] [y] [z]
num: 1 2 3 -> [1] [2] [3]
eval 函數(shù)的作用是動態(tài)生成 Makefile 內容。
define func
$(1) = $(1)...
endef
$(eval $(call func, A))
$(eval $(call func, B))
all:
@echo $(A) $(B)
上面例子的執(zhí)行結果實際上是 "動態(tài)" 定義了兩個變量而已。當然,借用 foreach 可以更緊湊一些。
$(foreach x, A B, $(eval $(call func, $(x))))
執(zhí)行 shell 命令,這個非常實用。
A = $(shell uname)
all:
@echo $(A)
更多的函數(shù)列表和詳細信息請參考相關文檔。
include 指令會讀取其他的 Makefile 文件內容,并在當前位置展開。通常使用 ".mk" 作為擴展名,支持文件名通配符,支持相對和絕對路徑。
Makefile 常用目標名:
make 常用命令參數(shù):
順序執(zhí)行多個目標:
$ make clean debug
Scons 采用 Python 編寫,用來替換 GNU Make 的自動化編譯構建工具。相比 Makefile 和類似的老古董,scons 更智能,更簡單。
在項目目錄下創(chuàng)建名為 SConstruct (或 Sconstruct、 sconstruct) 的文件,作用類似 Makefile。實質上就是 py 源文件。
簡單樣本:
Program("test", ["main.c"])
常用命令:
$ scons!! ! ! ! ! # 構建,輸出詳細信息。
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Building targets ...
gcc -o main.o -c main.c
gcc -o test main.o
scons: done building targets.
$ scons -c! ! ! ! ! # 清理,類似 make clean。
scons: Reading SConscript files ...
scons: done reading SConscript files.
scons: Cleaning targets ...
Removed main.o
Removed test
scons: done cleaning targets.
$ scons -Q! ! ! ! ! # 構建,簡化信息輸出。
gcc -o main.o -c main.c
gcc -o test main.o
$ scons -i! ! ! ! ! # 忽略錯誤,繼續(xù)執(zhí)行。
$ scons -n! ! ! ! ! # 輸出要執(zhí)行的命令,但并不真的執(zhí)行。
$ scons -s! ! ! ! ! # 安靜執(zhí)行,不輸出任何非錯誤信息。
$ scons -j 2! ! ! ! ! # 并行構建。
如需調試,建議插入 "import pdb; pdb.set_trace()",命令行參數(shù) "--debug=pdb" 并不好用??捎?SConscript(path/filename) 包含其他設置文件 (或列表),按慣例命名為 SConscript。
影響 scons 執(zhí)行的環(huán)境 (Environment ) 因素包括:
簡單程序,可直接使用默認構建環(huán)境實例。
env = DefaultEnvironment(CCFLAGS = "-g")! ! # 返回默認構建環(huán)境實例,并設置參數(shù)。
Program("test", ["main.c"])! ! ! ! # 相當于 env.Program()
輸出:
gcc -o main.o -c -g main.c
gcc -o test main.o
如需多個構建環(huán)境,可用 Environment 函數(shù)創(chuàng)建。同一環(huán)境可編譯多個目標,比如用相同設置編譯靜態(tài)庫和目標執(zhí)行程序。
env = Environment(CCFLAGS = "-O3")
env.Library("my", ["test.c"], srcdir = "lib")
env.Program("test", ["main.c"], LIBS = ["my"], LIBPATH = ["."])
輸出:
gcc -o lib/test.o -c -O3 lib/test.c
ar rc libmy.a lib/test.o
ranlib libmy.a
gcc -o main.o -c -O3 main.c
gcc -o test main.o -L. -lmy
常用環(huán)境參數(shù):
除直接提供鍵值參數(shù)外,還可用名為 parse_flags 的特殊參數(shù)一次性提供,它會被 ParseFlags 方法自動分解。
env = Environment(parse_flags = "-Ilib -L.")
print env["CPPPATH"], env["LIBPATH"]
輸出:
['lib'] ['.']
調用 Dictionary 方法返回環(huán)境參數(shù)字典,或直接用 Dump 方法返回 Pretty-Print 字符串。
print env.Dictionary(); ! print env.Dictionary("LIBS", "CPPPATH")
print env.Dump(); ! ! print env.Dump("LIBS")
用 "ENV" 鍵訪問執(zhí)行環(huán)境字典。系統(tǒng)不會自動拷貝外部環(huán)境變量,需自行設置。
import os
env = DefaultEnvironment(ENV = os.environ)
print env["ENV"]["PATH"]
同一構建環(huán)境,可用相關方法編譯多個目標。無需關心這些方法調用順序,系統(tǒng)會自動處理依賴關系,安排構建順序。
如果沒有構建環(huán)境實例,那么這些函數(shù)將使用默認環(huán)境實例。
用首個位置參數(shù)指定目標文件名 (不包括擴展名),或用 target、source 指定命名參數(shù)。source 是單個源文件名 (包含擴展名) 或列表。
Program("test1", "main.c")
Program("test2", ["main.c", "lib/test.c"])! ! # 列表
Program("test3", Split("main.c lib/test.c"))!! # 分解成列表
Program("test4", "main.c lib/test.c".split())! # 分解成列表
Glob 用通配符匹配多個文件,還可用 srcdir 指定源碼目錄簡化文件名列表。為方法單獨提供環(huán)境參數(shù)僅影響該方法,不會修改環(huán)境對象。
Library("my", "test.c", srcdir = "lib")
Program("test2", Glob("*.c"), LIBS = ["my"], LIBPATH = ["."], CPPPATH = "lib")
輸出:
gcc -o lib/test.o -c lib/test.c
ar rc libmy.a lib/test.o
ranlib libmy.a
gcc -o main.o -c -Ilib main.c
gcc -o test2 main.o -L. -lmy
創(chuàng)建共享庫。
SharedLibrary("my", "test.c", srcdir = "lib")
Program("test", Glob("*.c"), LIBS = ["my"], LIBPATH = ["."], CPPPATH = "lib")
輸出:
gcc -o lib/test.os -c -fPIC lib/test.c
gcc -o libmy.dylib -dynamiclib lib/test.os
gcc -o main.o -c -Ilib main.c
gcc -o test main.o -L. -lmy
編譯方法返回列表,第一元素是目標文件全名。
print env.Library("my", "test.c", srcdir = "lib")
輸出:
['libmy.a']
Append: 追加參數(shù)數(shù)據(jù)。
env = Environment(X = "a")
env.Append(X = "b")! ! ! # "a" + "b"。
env.Append(X = ["c"]) !! # 如果原參數(shù)或新值是列表,那么 [] + []。
print env["X"]
輸出:
['ab', 'c']
AppendUnique: 判斷要追加的數(shù)據(jù)是否已經存在。delete_existing 參數(shù)刪除原數(shù)據(jù),然后添加到列表尾部。原參數(shù)值必須是列表。
env = Environment(X = ["a", "b", "c"])
env.AppendUnique(X = "d")
env.AppendUnique(1, X = "b")
print env["X"]
輸出:
['a', 'c', 'd', 'b']
Prepend, PrependUnique: 將值添加到頭部。
env = Environment(X = ["a", "b", "c"])
env.Prepend(X = "d")
print env["X"]
輸出:
['d', 'a', 'b', 'c']
AppendENVPath, PrependENVPath: 向執(zhí)行環(huán)境追加路徑,去重。
env = Environment()
print env["ENV"]["PATH"]
env.AppendENVPath("PATH", "./lib")
env.AppendENVPath("PATH", "./lib")
print env["ENV"]["PATH"]
輸出:
/opt/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin
/opt/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:./lib
Replace: 替換參數(shù)。如目標不存在,新增。
env = Environment(CCFLAGS = ["-g"])
env.Replace(CCFLAGS = "-O3")
print env["CCFLAGS"]
輸出:
-O3
SetDefault: 和 Python dict.setdefault 作用相同,僅在目標鍵不存在時添加。
env = Environment(CCFLAGS = "-g")
env.SetDefault(CCFLAGS = "-O3")
env.SetDefault(LIBS = ["m", "pthread"])
print env["CCFLAGS"], env["LIBS"]
輸出:
-g ['m', 'pthread']
MergeFlags: 合并參數(shù)字典,去重。
env = Environment(CCFLAGS = ["option"], CPPATH = ["/usr/local/include"])
env.MergeFlags({"CCFLAGS" : "-O3" })
env.MergeFlags("-I/usr/opt/include -O3 -I/usr/local/include")
print env['CCFLAGS'], env["CPPPATH"]
輸出:
['option', '-O3'] ['/usr/opt/include', '/usr/local/include']
ParseFlags: 分解參數(shù)。
env = Environment()
d = env.ParseFlags("-I/opt/include -L/opt/lib -lfoo")
env.MergeFlags(d)
print d
print env["CPPPATH"], env["LIBS"], env["LIBPATH"]
輸出:
{'LIBPATH': ['/opt/lib'], 'LIBS': ['foo'], ..., 'CPPPATH': ['/opt/include']}
['/opt/include'] ['foo'] ['/opt/lib']
Clone: 環(huán)境對象深度復制,可指定覆蓋參數(shù)。
env = Environment(CCFLAGS = ["-g"], LIBS = ["m", "pthread"])
env2 = env.Clone(CCFLAGS = "-O3")
print env2["CCFLAGS"], env2["LIBS"]
輸出:
-O3 ['m', 'pthread']
NoClean: 指示 "scons -c" 不要清理這些文件。
my = Library("my", "test.c", srcdir = "lib")
test = Program("test", "main.c")
NoClean(test, my) ! ! ! ! ! # 也可直接使用文件名,注意是 libmy.a。
subst: 展開所有環(huán)境參數(shù)。
print env["CCCOM"]
print env.subst("$CCCOM")
輸出:
'$CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCCOMCOM $SOURCES'
'gcc -o -c -O3'
各方法詳細信息可參考 "man scons" 或 在線手冊。
當依賴文件發(fā)生變更時,需重新編譯目標程序。可使用 Decider 決定變更探測方式,可選項包括:
用 touch 更新某個源文件修改時間,即便文件內容沒有變化,timestamp-newer 也會讓 scons 重新編譯該目標文件。
env.Decider("timestamp-newer")
env.Program("test", "main.c")
某些時候,scons 無法探測到依賴關系,那么可以用 Depends 顯式指定依賴。
env.Decider("timestamp-newer")
test = env.Program("test", "main.c")
env.Depends(test, ["lib/test.h"])
Ignore 忽略依賴關系,Require 指定編譯順序。下例中,指示在編譯 my 前必須先構建 test,即便它們之間沒有任何依賴關系。
my = env.Library("my", "test.c", srcdir = "lib")
test = env.Program("test", "main.c")
env.Requires(my, test)
AlwaysBuild 指示目標總是被編譯。不管依賴項是否變更,這個目標總是會被重新構建。
my = env.Library("my", "test.c", srcdir = "lib")
env.AlwaysBuild(my)
scons 提供了三種不同的命令行參數(shù):
所有鍵值都保存在 ARGUMENTS 字典中,可用 Help 函數(shù)添加幫助信息。
vars = Variables(None, ARGUMENTS)
vars.Add('RELEASE', 'Set to 1 to build for release', 0)
env = Environment(variables = vars)
Help(vars.GenerateHelpText(env))
if not GetOption("help"):
print ARGUMENTS
print ARGUMENTS.get("RELEASE", "0")
輸出:
$ scons -Q -h
RELEASE: Set to 1 to build for release
default: 0
actual: 0
Use scons -H for help about command-line options.
$ scons -Q RELEASE=1
{'RELEASE': '1'}
1
$ scons -Q
{}
0
另有 BoolVariable、EnumVariable、ListVariable、PathVariable 等函數(shù)對參數(shù)做進一步處理。
Program、Library 等編譯目標文件名,可通過 COMMAND_LINE_TARGETS 列表獲取。
print COMMAND_LINE_TARGETS
Library("my", "lib/test.c")
env = Environment()
env.Program("test", "main.c")
輸出:
$ scons -Q test
['test']
gcc -o main.o -c main.c
gcc -o test main.o
$ scons -Q libmy.a
['libmy.a']
gcc -o lib/test.o -c lib/test.c
ar rc libmy.a lib/test.o
ranlib libmy.a
$ scons -Q -c test libmy.a
['test', 'libmy.a']
Removed main.o
Removed test
Removed lib/test.o
Removed libmy.a
除非用 Default 函數(shù)指定默認目標,否則 scons 會構建所有目標。多次調用 Default 的結果會被合并,保存在 DEFAULT_TARGETS 列表中。
my = Library("my", "lib/test.c")
test = Program("test", "main.c")
Default(my)! ! ! ! ! # 可指定多個目標,比如 Default(my, test)。
輸出:
$ scons -Q
gcc -o lib/test.o -c lib/test.c
ar rc libmy.a lib/test.o
ranlib libmy.a
就算指定了默認目標,我們依然可以用 "scons -Q ." 來構建所有目標,清理亦同。
附: scons 還有 Install、InstallAs、Alias、Package 等方法用來處理安裝和打包,詳細信息可參考官方手冊。
SCons User GuideMan page of SCons
通常情況下,我們只需簡單設置用戶信息和著色即可。
$ git config --global user.name "Q.yuhen"
$ git config --global user.email qyuhen@abc.com
$ git config --global color.ui true
可以使用 "--list" 查看當前設置。
$ git config --list
創(chuàng)建項目目錄,然后執(zhí)行 git init 初始化。這會在項目目錄創(chuàng)建 .git 目錄,即為元數(shù)據(jù)信息所在。
$ git init
通常我們還需要創(chuàng)建一個忽略配置文件 ".gitignore",并不是什么都需要加到代碼倉庫中的。
$ cat > .gitignore << end
> *.[oa]
> *.so
> *~
> !a.so
> test
> tmp/
> end
如果作為 Server 存在,那么可以忽略工作目錄,以純代碼倉庫形式存在。
$ git --bare init
在客戶端,我們可以調用 clone 命令克隆整個項目。支持 SSH / HTTP/ GIT 等協(xié)議。
$ git clone ssh://user@server:3387/git/myproj
$ git clone git://github.com/schacon/grit.git mygrit
Git 分為 "工作目錄"、"暫存區(qū)"、"代碼倉庫" 三個部分。
文件通過 "git add " 被添加到暫存區(qū),如此暫存區(qū)將擁有一份文件快照。
$ git add .
$ git add file1 file2
$ git add *.c
"git add" 除了添加新文件到暫存區(qū)進行跟蹤外,還可以刷新已被跟蹤文件的暫存區(qū)快照。需要注意的是,被提交到代碼倉庫的是暫存區(qū)的快照,而不是工作目錄中的文件。
"git commit -m " 命令將暫存區(qū)的快照提交到代碼倉庫。
$ git commit -m "message"
在執(zhí)行 commit 提交時,我們通常會直接使用 "-a" 參數(shù)。該參數(shù)的含義是:刷新暫存區(qū)快照,提交時同時移除被刪除的文件。但該參數(shù)并不會添加未被跟蹤的新文件,依然需要執(zhí)行 "git add "操作。
$ git commit -am "message"
可以使用 "git status" 查看暫存區(qū)狀態(tài),通常包括 "當前工作分支 (Branch)"、"被修改的已跟蹤文件(Changed but not updated)",以及 "未跟蹤的新文件 (Untracked files)" 三部分信息。
$ git status
# On branch master
# Changed but not updated:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: readme
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# install
no changes added to commit (use "git add" and/or "git commit -a")
要比較三個區(qū)域的文件差別,需要使用 "git diff" 命令。
使用 "git diff [file]" 查看工作目錄和暫存區(qū)的差異。使用 "git diff --staged [file]" 或 "git diff --cached [file]" 查看暫存區(qū)和代碼倉庫的差異。
$ git diff readme
diff --git a/readme b/readme
index e69de29..df8285e 100644
--- a/readme
+++ b/readme
@@ -0,0 +1,2 @@
+1111111111111111111
+
查看當前所有未提交的差異,包括工作目錄和暫存區(qū)。
$ git diff HEAD
作為代碼管理工作,我們隨時可以 "反悔"。
使用 "git reset HEAD " 命令可以取消暫存區(qū)的文件快照(即恢復成最后一個提交版本),這不會影響工作目錄的文件修改。
使用 "git checkout -- " 從倉庫恢復工作目錄文件,暫存區(qū)不受影響。
$ git chekcout -- readme
在 Git 中 "HEAD" 表示倉庫中最后一個提交版本,"HEAD^" 是倒數(shù)第二個版本,"HEAD~2" 則是更老的版本。
我們可以直接 "簽出" 代碼倉庫中的某個文件版本到工作目錄,該操作同時會取消暫存區(qū)快照。
$ git checkout HEAD^ readme
如果想將整個項目回溯到以前的某個版本,可以使用 "git reset"??梢赃x擇的參數(shù)包括默認的 "--mixed" 和 "--hard",前者不會取消工作目錄的修改,而后者則放棄全部的修改。該操作會丟失其后的日志。
$ git reset --hard HEAD^
每次提交都會為整個項目創(chuàng)建一個版本,我們可以通過日志來查看相關信息。
參數(shù) "git log -p" 可以查看詳細信息,包括修改的內容。參數(shù) "git log -2" 查看最后兩條日志。參數(shù) "git log --stat" 可以查看統(tǒng)計摘要。
$ git log --stat -2 -p
commit c11364da1bde38f55000bc6dea9c1dda426c00f9
Author: Q.yuhen <qyuhen@hotmail.com>
Date: Sun Jul 18 15:53:55 2010 +0800
b
---
0 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/install b/install
new file mode 100644
index 0000000..e69de29
commit 784b289acc8dccd1d2d9742d17f586ccaa56a3f0
Author: Q.yuhen <qyuhen@hotmail.com>
Date: Sun Jul 18 15:33:24 2010 +0800
a
---
0 files changed, 0 insertions(+), 0 deletions(-)
diff --git a/readme b/readme
new file mode 100644
index 0000000..e69de29
馬有失蹄,使用 "git commit --amend" 可以重做最后一次提交。
$ git commit --amend -am "b2"
[master 6abac48] b2
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 abc
create mode 100644 install
$ git log
commit 6abac48c014598890c6c4f47b4138f6be020e403
Author: Q.yuhen <qyuhen@hotmail.com>
Date: Sun Jul 18 15:53:55 2010 +0800
b2
commit 784b289acc8dccd1d2d9742d17f586ccaa56a3f0
Author: Q.yuhen <qyuhen@hotmail.com>
Date: Sun Jul 18 15:33:24 2010 +0800
a
使用 "git show" 可以查看日志中文件的變更信息,默認顯示最后一個版本 (HEAD)。
$ git show readme
$ git show HEAD^ readme
可以使用標簽 (tag) 對最后提交的版本做標記,如此可以方便記憶和操作,這通常也是一個里程碑的標志。
$ git tag v0.9
$ git tag
v0.9
$ git show v0.9
commit 3fcdd49fc0f0a45cd283a86bc743b4e5a1dfdf5d
Author: Q.yuhen <qyuhen@hotmail.com>
Date: Sun Jul 18 14:53:55 2010 +0800
...
可以直接用標簽號代替日志版本號進行操作。
$ git log v0.9
commit 3fcdd49fc0f0a45cd283a86bc743b4e5a1dfdf5d
Author: Q.yuhen <qyuhen@hotmail.com>
Date: Sun Jul 18 14:53:55 2010 +0800
a
在不方便共享代碼倉庫,或者修改一個沒有權限的代碼時,可以考慮通過補丁文件的方式來分享代碼修改。
輸出補?。?/p>
$ git diff > patch.txt
$ git diff HEAD HEAD~ > patch.txt
合并補?。?/p>
$ git apply < patch.txt
用 Git 一定得習慣用分支進行工作。
使用 "git branch " 創(chuàng)建分支,還可以創(chuàng)建不以當前版本為起點的分支 "git branch
HEAD^"。使用 "git checkout " 切換分支。```$ git branch yuhen$ git checkout yuhenSwitched to branch 'yuhen'$ git branch master* yuhen```使用 "git chekcout -b " 一次完成分支創(chuàng)建和切換操作。```$ git checkout -b yuhenSwitched to a new branch 'yuhen'$ git branch master* yuhen```在分支中完成提交,然后切換回主分支進行合并 (git merge) 和 刪除 (git branch -d ) 操作。```$ git checkout masterSwitched to branch 'master'$ git merge yuhenUpdating 6abac48..7943312Fast-forward 0 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 abc.txt$ git branch -d yuhenDeleted branch yuhen (was 7943312).$ git branch* master```附注: 如果當前工作目錄有未提交的內容,直接切換到其他分支會將變更一同帶入。## 6.5 服務器(1) 首先克隆服務器代碼倉庫。```$ git clone git@192.168.1.202:/git.server/project1 # SSH```完成克隆后,可以用 origin 來代替服務器地址。使用 "git remote" 命令查看相關信息。```$ git remoteorigin$ git remote show origin* remote origin Fetch URL: ... Push URL: ... HEAD branch: master Remote branch: master tracked Local branch configured for 'git pull': master merges with remote master Local ref configured for 'git push': master pushes to master (up to date)```還可以創(chuàng)建新的 remote 設置。```$ git remote add project1 git@192.168.1.202:/git.server/project1$ git remoteoriginproject1$ git remote rm project1```(2) 在將代碼提交 (push) 到服務器之前,首先要確認相關更新已經合并到主分支。還應該先從服務器刷新 (pull) 最新代碼,以確保自己的提交不會和別人最新提交的代碼沖突。```$ git pull origin master$ git push origin master```(3) 要提交標簽到服務器,需要額外操作 (先執(zhí)行 git push 提交,然后再執(zhí)行該指令)。```$ git push origin --tags```## 6.6 管理檢查損壞情況。```$ git fsck```清理無用數(shù)據(jù)。```$ git gc```# 7. Debug在初學匯編時,MS-DOS debug.com 命令是個最佳的實驗工具。## 7.1 命令常用命令:- 輸入指令: a [address]- 反匯編: u [range]- 執(zhí)行: g [=address] [breakpoint]- 執(zhí)行: p [=address] [number]- 單步: t- 查看寄存器: r- 修改寄存器: r - 內存顯示: d [range]- 內存比較: c - 內存修改: e - 內存填充: f - 參數(shù)格式:- range: 表示一段內存范圍,可以是 " ",或 "L"。- list: 表示一個或多個內存字節(jié)值,用英文逗號分隔。### 7.1.1 匯編輸入?yún)R編指令,轉換成機器碼存入指定位置。```a [address]```address 可以是偏移量,或者完整的段地址 (CS:SA)。```-a 1001396:0100 mov bx, fefe1396:0103 mov ax, bx1396:0105-u 100 1031396:0100 BBFEFE MOV BX,FEFE1396:0103 89D8 MOV AX,BX```除了輸入?yún)R編指令,我們還可以使用 db 和 dw 這兩個偽指令。```-a1396:0105 db 1,2,3,41396:0109 dw 5,6,7,81396:0111 db "Hello, World!"1396:011E-d 1001396:0100 BB FE FE 89 D8 01 02 03-04 05 00 06 00 07 00 08 ................1396:0110 00 48 65 6C 6C 6F 2C 20-57 6F 72 6C 64 21 33 44 .Hello, World!3D1396:0120 55 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 U...............1396:0130 11 22 33 00 00 00 00 00-00 00 00 00 00 00 00 00 ."3.............1396:0140 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0150 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0160 33 44 55 33 00 00 00 00-00 00 00 00 00 00 00 00 3DU3............1396:0170 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................```對應的,我們可以用 U 命令進行反匯編。```u [range]```如果省略 range,則從上次結束位置繼續(xù)反匯編。```-u 100 1051396:0100 BBFEFE MOV BX,FEFE1396:0103 89D8 MOV AX,BX1396:0105 0102 ADD [BP+SI],AX-u1396:0107 0304 ADD AX,[SI]1396:0109 050006 ADD AX,06001396:010C 0007 ADD [BX],AL1396:010E 0008 ADD [BX+SI],CL1396:0110 004865 ADD [BX+SI+65],CL1396:0113 6C DB 6C1396:0114 6C DB 6C1396:0115 6F DB 6F1396:0116 2C20 SUB AL,201396:0118 57 PUSH DI1396:0119 6F DB 6F1396:011A 726C JB 01881396:011C 64 DB 641396:011D 2101 AND [BX+DI],AX1396:011F 0203 ADD AL,[BP+DI]1396:0121 0000 ADD [BX+SI],AL1396:0123 0000 ADD [BX+SI],AL1396:0125 0000 ADD [BX+SI],AL```### 7.1.2 比較比較兩段內存區(qū)域的差異。```c ```- range: 表示第一段內存區(qū)域。- address: 是第二段內存的起始地址。```-d 1001396:0100 BB FE FE 89 D8 01 02 03-04 05 00 06 00 07 00 08 ................1396:0110 00 48 65 6C 6C 6F 2C 20-57 6F 72 6C 64 21 33 44 .Hello, World!3D1396:0120 55 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 U...............1396:0130 11 22 33 00 00 00 00 00-00 00 00 00 00 00 00 00 ."3.............-c 100l3 1601396:0100 BB 33 1396:01601396:0101 FE 44 1396:01611396:0102 FE 55 1396:0162-c 100 102 1601396:0100 BB 33 1396:01601396:0101 FE 44 1396:01611396:0102 FE 55 1396:0162```### 7.1.3 顯示顯示內存信息。```d [range]```可以不指定 range,從上次顯示尾部繼續(xù)顯示后續(xù)內容。也可以不指定長度或結束地址。```-d 1001396:0100 BB FE FE 89 D8 01 02 03-04 05 00 06 00 07 00 08 ................1396:0110 00 48 65 6C 6C 6F 2C 20-57 6F 72 6C 64 21 33 44 .Hello, World!3D1396:0120 55 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 U...............1396:0130 11 22 33 00 00 00 00 00-00 00 00 00 00 00 00 00 ."3.............-d1396:0180 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0190 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:01A0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:01B0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................-d 100l51396:0100 BB FE FE 89 D8 .....-d 100 11f1396:0100 BB FE FE 89 D8 01 02 03-04 05 00 06 00 07 00 08 ................1396:0110 00 48 65 6C 6C 6F 2C 20-57 6F 72 6C 64 21 33 44 .Hello, World!3D```### 7.1.4 修改修改內存數(shù)據(jù)。```e [list]```使用逗號分隔多個值。```-d 1001396:0100 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................-e 100 1,2,3,4,5,6-d 1001396:0100 01 02 03 04 05 06 00 00-00 00 00 00 00 00 00 00 ................1396:0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................也可以按字節(jié)輸入修改值。調試器會給出當前值,在符號 "." 后輸入新值,空格鍵繼續(xù)下一字節(jié),回車結束。``````-e 1001396:0100 01.aa 02.bb 03.cc-d 1001396:0100 AA BB CC 04 05 06 00 00-00 00 00 00 00 00 00 00 ................1396:0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................```### 7.1.5 填充使用特定數(shù)據(jù)填充內存。```f ```可以是多個字節(jié)。```-f 100l6 ff-d 1001396:0100 FF FF FF FF FF FF 00 00-00 00 00 00 00 00 00 00 ................1396:0110 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................1396:0120 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................-f 100 120 1,2,3,4,5-d 1001396:0100 01 02 03 04 05 01 02 03-04 05 01 02 03 04 05 01 ................1396:0110 02 03 04 05 01 02 03 04-05 01 02 03 04 05 01 02 ................1396:0120 03 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................```我們通常用該命令清空某個內存,以便觀察操作結果。```f 100l50 00```### 7.1.6 運行運行匯編指令。```g [=address] [breakpoint]```注意不能省略地址前的 "="。如果不輸入開始地址,則使用 CS:IP。```-a 1001396:0100 mov bx, 10001396:0103 mov ax, bx1396:0105 add ax, 20001396:0108-g =100 108AX=3000 BX=1000 CX=0000 DX=0000 SP=FFE6 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0108 NV UP EI PL NZ NA PE NC1396:0108 0405 ADD AL,05```命令 P 比 G 更方便一些,可以直接指定要執(zhí)行的指令數(shù)。```p [=address] [number]```number 默認是 1。```-p =100 3AX=0000 BX=1000 CX=0000 DX=0000 SP=FFE6 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0103 NV UP EI PL NZ NA PE NC1396:0103 89D8 MOV AX,BXAX=1000 BX=1000 CX=0000 DX=0000 SP=FFE6 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0105 NV UP EI PL NZ NA PE NC1396:0105 050020 ADD AX,2000AX=3000 BX=1000 CX=0000 DX=0000 SP=FFE6 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0108 NV UP EI PL NZ NA PE NC1396:0108 0405 ADD AL,05```剩下一個命令是 T,它單步執(zhí)行匯編指令。```-r ip ; 修改寄存器 IP,調整開始執(zhí)行位置IP 4444:100-tAX=1000 BX=1000 CX=0000 DX=0000 SP=FFE4 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0103 NV UP EI PL NZ NA PE NC1396:0103 89D8 MOV AX,BX-tAX=1000 BX=1000 CX=0000 DX=0000 SP=FFE4 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0105 NV UP EI PL NZ NA PE NC1396:0105 050020 ADD AX,2000-tAX=3000 BX=1000 CX=0000 DX=0000 SP=FFE4 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0108 NV UP EI PL NZ NA PE NC1396:0108 0405 ADD AL,05```### 7.1.7 計算計算兩個值的 "和" 與 "差"。```h ```第一個結果是 "和",第二個是 "差"。```-h 2000 10003000 1000```### 7.1.8 復制復制內存塊。```m ```range 是源內存地址范圍,address 是目標起始地址。```-d 1001396:0100 BB 00 10 89 D8 05 00 20-04 05 01 02 03 04 05 01 ....... ........1396:0110 02 03 04 05 01 02 03 04-05 01 02 03 04 05 01 02 ................-m 100l6 110-d 1001396:0100 BB 00 10 89 D8 05 00 20-04 05 01 02 03 04 05 01 ....... ........1396:0110 BB 00 10 89 D8 05 03 04-05 01 02 03 04 05 01 02 ................```### 7.1.9 寄存器顯示或修改寄存器內容。```r [register]```演示:```-rAX=3000 BX=1000 CX=0000 DX=0000 SP=FFE4 BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0108 NV UP EI PL NZ NA PE NC1396:0108 0405 ADD AL,05-r ipIP 0108:100```### 7.1.10 退出退出調試器。```q```## 7.2 8086 尋址模式### 7.2.1 立即尋址方式直接將操作數(shù)存放在指令中。該操作數(shù)是為常數(shù),通常用來初始化寄存器。```-a1396:0100 mov ax, 12341396:0103-tAX=1234 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0103 NV UP EI PL NZ NA PO NC1396:0103 0000 ADD [BX+SI],AL DS:0000=CD```### 7.2.2 寄存器尋址方式操作數(shù)存放于寄存器中,通過寄存器名完成操作。```-a 1001396:0100 mov ax, 55551396:0103 mov bx, ax1396:0105-p =100 2AX=5555 BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0103 NV UP EI PL NZ NA PO NC1396:0103 89C3 MOV BX,AXAX=5555 BX=5555 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=1396 ES=1396 SS=1396 CS=1396 IP=0105 NV UP EI PL NZ NA PO NC1396:0105 0000 ADD [BX+SI],AL DS:5555=00```### 7.2.3 直接尋址方式直接在指令中用常數(shù)操作數(shù)指定偏移地址。```-a 100139B:0100 mov ax, [0010] ; 從 DS:0010 處讀取數(shù)據(jù)139B:0103-p =100 1AX=0DFF BX=0000 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=139B ES=139B SS=139B CS=139B IP=0103 NV UP EI PL NZ NA PO NC139B:0103 BE0200 MOV SI,0002-d 0010l6139B:0010 FF 0D 17 03 FF 0D ......```### 7.2.4 寄存器間接尋址方式將偏移地址存放在寄存器中,通過寄存器間接讀取目標數(shù)據(jù)。```-a 1001396:0100 mov bx, 00101396:0103 mov ax, [bx] ; 相當于 "mov ax, [0010]"1396:0105-p =100 2AX=0000 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=1000 ES=1396 SS=1396 CS=1396 IP=0103 NV UP EI PL NZ NA PO NC1396:0103 8B07 MOV AX,[BX] DS:0010=E85BAX=E85B BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=1000 ES=1396 SS=1396 CS=1396 IP=0105 NV UP EI PL NZ NA PO NC1396:0105 0000 ADD [BX+SI],AL DS:0010=5B-d 0010l61000:0010 5B E8 59 00 E8 D8 ......```### 7.2.5 寄存器相對尋址方式偏移地址 = 寄存器內容 + 偏移常數(shù)。"COUNT[BX]" 或 "[BX + COUNT]"。```-a 100139B:0100 mov bx, 0010139B:0103 mov ax, 2[bx] ; 相當于 "mov ax, [0010 + 2]"139B:0106-p =100 2AX=0000 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=139B ES=139B SS=139B CS=139B IP=0103 NV UP EI PL NZ NA PO NC139B:0103 8B4702 MOV AX,[BX+02] DS:0012=0317AX=0317 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=139B ES=139B SS=139B CS=139B IP=0106 NV UP EI PL NZ NA PO NC139B:0106 0000 ADD [BX+SI],AL DS:0010=FF-d 0010l6139B:0010 FF 0D 17 03 FF 0D ......```### 7.2.6 基址變址尋址方式偏移地址 = 基址寄存器內容 + 變址寄存器內容。```"[BX][DI]" 也可寫成 "[BX + DI]"-a 100139B:0100 mov bx, 0010139B:0103 mov di, 2139B:0106 mov ax, [bx][di] ; 相當于 "mov ax, [0010 + 2]"139B:0108-p =100 3AX=0317 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0000DS=139B ES=139B SS=139B CS=139B IP=0103 NV UP EI PL NZ NA PO NC139B:0103 BF0200 MOV DI,0002AX=0317 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0002DS=139B ES=139B SS=139B CS=139B IP=0106 NV UP EI PL NZ NA PO NC139B:0106 8B01 MOV AX,[BX+DI] DS:0012=0317AX=0317 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0002DS=139B ES=139B SS=139B CS=139B IP=0108 NV UP EI PL NZ NA PO NC139B:0108 0000 ADD [BX+SI],AL DS:0010=FF-d 0010l4139B:0010 FF 0D 17 03 ....```### 7.2.7 相對基址變址尋址方式偏移地址 = 基址寄存器內容 + 變址寄存器內容 + 偏移常數(shù)。"MASK[BX][SI]" 或 "MASK[BX + SI]" 或 "[MASK + BX + SI]"```-a 100139B:0100 mov bx, 0010139B:0103 mov si, 2139B:0106 mov ax, 2[bx][si] ; 相當于 "mov ax, [0010 + 2 + 2]"139B:0109-p =100 3AX=0317 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0000 DI=0002DS=139B ES=139B SS=139B CS=139B IP=0103 NV UP EI PL NZ NA PO NC139B:0103 BE0200 MOV SI,0002AX=0317 BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0002 DI=0002DS=139B ES=139B SS=139B CS=139B IP=0106 NV UP EI PL NZ NA PO NC139B:0106 8B4002 MOV AX,[BX+SI+02] DS:0014=0DFFAX=0DFF BX=0010 CX=0000 DX=0000 SP=FFEE BP=0000 SI=0002 DI=0002DS=139B ES=139B SS=139B CS=139B IP=0109 NV UP EI PL NZ NA PO NC139B:0109 0000 ADD [BX+SI],AL DS:0012=17-d 0010l6139B:0010 FF 0D 17 03 FF 0D ......```比例變址尋址方式: COUNT[ESI * 4] == [ ESI * 4 + COUNT]基址比例變址尋址方式: [EAX][EDX * 8] == [EDX * 8 + EAX]相對基址比例變址尋址方式: MASK[EBP][EDI * 4] == [EDI * 4 + EBP + MASK]# 8. Binutils## 8.1 addr2line將程序地址(VA)轉換為源代碼文件名和行號。參數(shù):- f: 顯示函數(shù)名。- s: 僅顯示文件名,不包括路徑。- p: 以 Pretty-Print 方式顯示。- e: 文件名。```$ addr2line -pfe test 8028783```## 8.2 ar用來創(chuàng)建、修改、提取靜態(tài)庫文件。參數(shù):- s: 創(chuàng)建或更新靜態(tài)庫索引,相當于 ranlib。- r: 替換庫文件中的老舊目標文件。- c: 刪除已有文件,創(chuàng)建新靜態(tài)庫。- t: 顯示包內容。- x: 展開包成員。生成靜態(tài)庫。```$ ar rs libfunc.a func.o```查看靜態(tài)庫組成。```$ ar t libfunc.a```展開靜態(tài)庫。```$ ar x libfunc.a```## 8.3 gccGNU 編譯器。參數(shù):- c: 生成目標文件,但不做鏈接。- g: 生成必要的調試信息。- I: 添加 include 頭文件搜索路徑。(字母 i 大寫)- L: 添加 library 搜索路徑。- l: 鏈接庫文件。比如 -lm 表示鏈接 libm.so 。- static: 靜態(tài)鏈接。- fPIC: 生成位置無關代碼,通常是共享庫。- O: 優(yōu)化代碼,分為 0, 1, 2, 3 四個等級。- M, MM: 查看依賴文件。- Wall: 顯示所以可能的警告信息。編譯程序。```$ gcc -g -Wall -std=c99 -I./include -I/usr/include/gc -o test -lgc main.o func.o```生成動態(tài)庫。```$ gcc -c func.c$ gcc -fPIC -shared -o libfunc.so func.o```## 8.4 ldd通過模擬運行,查看可執(zhí)行文件動態(tài)庫加載。通常用于查看動態(tài)庫加載失敗信息。參數(shù):- v: 顯示詳細信息。```$ ldd test```## 8.5 nm查看目標文件符號表中定義的符號。參數(shù):- l: 顯示文件名和行號。- n: 按地址排序。```$ nm func.o```## 8.6 objcopy用于把一種目標文件中的內容復制到另一種類型的目標文件中。## 8.7 objdump顯示目標文件信息,通常用于反匯編。參數(shù):- a: 顯示靜態(tài)庫信息,類似 ls -l。- g: 顯示調試信息。- x: 顯示頭部信息。- d: 反匯編。- l: 反匯編時輸出文件名和行號。- M: 反匯編參數(shù),比如指定 intel 或 att 指令格式。- S: 反匯編時輸出 C 源碼。```$ objdump -dS -M intel test```## 8.8 readelf用于顯示 ELF 文件詳細信息。參數(shù):- a: 全部信息。- h: ELF 頭。- l: Program 段。- S: Section 頭。- x: 以二進制顯示段內容。- p: 以字符串顯示段內容。- 顯示 section table 信息。```$ readelf -S test```顯示 section 二進制內容,可以是 -S 輸出的段序號或段名稱。```$ readelf -x 13 test$ readelf -x .text test```顯示 section 字符串內容。```$ readelf -p .strtab test```## 8.9 size列出目標文件段和總體大小。參數(shù):- A: 更詳細信息。```$ size test```## 8.10 strings顯示目標文件中的所有可打印字符串。```$ strings test```## 8.11 strip刪除目標文件符號。參數(shù):- s: 刪除全部符號。- d: 僅刪除調試符號。```$ strip test```# 9. Manpages雖然比不上 MSDN 豪華,但也是日常開發(fā)離不了的東西。```$ sudo apt-get install manpages-dev```然后就可以用如下命令查看標準庫函數(shù)手冊了```$ man 3 ```如:```man 3 printf```還可以用 -k 參數(shù)搜索所有相關的信息```$ man -k printfprintf (1) - format and print dataprintf (3) - formatted output conversionvsnprintf (3) - formatted output conversionvsprintf (3) - formatted output conversionvswprintf (3) - formatted wide-character output conversionvwprintf (3) - formatted wide-character output conversionwprintf (3) - formatted wide-character output conversion```查看函數(shù)所在手冊文件```$ man -wa printf/usr/share/man/man1/printf.1.gz/usr/share/man/man3/printf.3.gzManPages Section:1 - commands2 - system calls3 - library calls4 - special files5 - file formats and convertions6 - games for linux7 - macro packages and conventions8 - system management commands9 - others```
更多建議: