My code works, I don’t know why.

國王的耳朵是驢耳朵

談談.git 目錄

| Comments

如果有使用git的朋友,可能會知道git是一個分散式版本控制系統。這表示repository會放一份在本機上面。仔細或是有看書的人,會知道這些repository沒有意外會放在工作目錄的.git下面。今天無聊來這個目錄戳戳看。

因為愈寫愈多,還是弄一下目錄好了

測試環境

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
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.2 LTS
Release:  14.04
Codename: trusty

$ git --version
git version 2.3.0

$ git remote show 
origin
$ git remote show origin 
* remote origin
  Fetch URL: https://github.com/pcman-bbs/pcmanx.git
  Push  URL: https://github.com/pcman-bbs/pcmanx.git
  HEAD branch: master
  Remote branches:
    gtk3                          tracked
    master                        tracked
    next-release                  tracked
    show_web_search_in_popup_menu tracked
    transparent_background        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)

.git目錄列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/tmp/pcmanx$ ls -gG --group-directories-first .git
total 56
drwxrwxr-x 2  4096 Mar  3 23:36 branches
drwxrwxr-x 2  4096 Mar  3 23:36 hooks
drwxrwxr-x 2  4096 Mar  3 23:36 info
drwxrwxr-x 3  4096 Mar  3 23:37 logs
drwxrwxr-x 4  4096 Mar  3 23:36 objects
drwxrwxr-x 5  4096 Mar  3 23:37 refs
-rw-rw-r-- 1   264 Mar  3 23:37 config
-rw-rw-r-- 1    73 Mar  3 23:36 description
-rw-rw-r-- 1    23 Mar  3 23:37 HEAD
-rw-rw-r-- 1 12256 Mar  4 23:11 index
-rw-rw-r-- 1    41 Mar  4 23:11 ORIG_HEAD
-rw-rw-r-- 1   829 Mar  3 23:37 packed-refs

背景知識

這邊只列出需要的背景知識,可能有錯。

  • Git 的基本上資料有分blob, tree, commit, tag這四種型態
  • Git 的branch可以視為放一個檔案,這個檔案存放某個commit的HASH資訊

多說無益,直接操作,先來看這四種型態是三小

1
2
3
$ git log
commit fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
...

來看一下 fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304裏面是啥

1
2
3
4
$ git cat-file -p fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
tree 2f6db7ea7db6b66cb4c07a98e78b580f67872db6
parent 3fb419f40c10d67bd1b9b18152fb43cca58ca411
...

可以看到有放

  • tree型態的HASH
  • 上一個commit的HASH
  • commit的訊息

接下來來看tree吧

1
2
3
4
5
$ git cat-file -p 2f6db7ea7db6b66cb4c07a98e78b580f67872db6
...
100644 blob 5bc4b3f69d730535eb3a8aee28d7ef9273225d50  .gitignore
...
040000 tree b0b46378d75b1737257fef9d9eb433489b2d0ea0  build

有沒有和Linux的File system很像?目錄tree資料放的是inodeHASH值和檔案名稱的對應表。

我們可以看到HASH的型態有tree和blob,理所當然來看看blob吧。

1
2
3
4
5
6
7
$ git cat-file -p 5bc4b3f69d730535eb3a8aee28d7ef9273225d50
## autotools generated
Doxyfile
INSTALL
Makefile
Makefile.in
...

很明顯這是一個資料存放的地方。

然後我們來看branch是不是真的放在檔案,檔案內容是某個commit HASH吧,直接看範例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ git checkout -b br1
Switched to a new branch 'br1'
$ git checkout -b br2
Switched to a new branch 'br2'
$ tree .git/refs/
.git/refs/
├── heads
│   ├── br1
│   ├── br2
│   └── master
├── remotes
│   └── origin
│       └── HEAD
└── tags

$ git log
commit fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
...
$ cat .git/refs/heads/br1 
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
$ cat .git/refs/heads/br2
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
$ cat .git/refs/heads/master 
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304

.git第一層檔案

用file看一下,可以看到大部分都是純文字檔:

1
2
3
4
5
6
7
/tmp/pcmanx$ find .git -maxdepth 1 -type f -exec file {} \;
.git/index: Git index, version 2, 144 entries
.git/packed-refs: ASCII text
.git/config: ASCII text
.git/ORIG_HEAD: ASCII text
.git/description: ASCII text
.git/HEAD: ASCII text

接下來分別討論

index

先看下面的操作。

1
2
3
4
5
6
7
8
$ file .git/index 
.git/index: Git index, version 2, 144 entries

$ find -type f | grep -v .git\/ | cut -c 3- | sort | wc -l
144

$ git ls-files | wc -l
144

有興趣的人可以把| wc -l去掉玩看看,基本上這個是在描述repository的目錄結構。也就是說,哪個檔案要放在那個目錄。更精確的來說,協助那個blob hash要放在哪個tree中。根據這邊的說法,這個檔案可以協助

  • 提供產生tree object時需要的資料
  • 提供比對working directory和tree的資訊
  • merge產生衝突時,可以從index取得的資料協助解決衝突。不過這邊不是很懂,就單純字面翻譯了。

packed-refs

簡單來說,考量效率,git提供pack ref目錄的功能,ref的概念請看前面背景知識。

一樣,看例子比較快。

先用git show-ref看看我們有哪些reference

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ git show-ref
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/heads/master
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/HEAD
c86b440bafe24caecfe0dbfbbd73031a1bcd1178 refs/remotes/origin/gtk3
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/master
22da092eb00b78946fd70de24427d702a77b925b refs/remotes/origin/next-release
98ff18f84fad4686012bf6183734eeb1ec5d7f46 refs/remotes/origin/show_web_search_in_popup_menu
9542889d17d276321d6da2de8d1f9d95f76fb483 refs/remotes/origin/transparent_background
7a3629ab3da7c609cf3698319ab606ebae0998e7 refs/tags/0.3.2@221
f57350c72987263c2b745690bdc8013fbbd6067c refs/tags/0.3.3@243
522d1dc2568b5c7c256e05ca69412480e1afcfb3 refs/tags/0.3.4
8e9d9520125f64a0b894a5d575f0c2d98c3ec06b refs/tags/0.3.4@301
7031115a8ea1151efc1536b43a509774ca2c0777 refs/tags/0.3.7
3f4dcbe6e1aa0045fbeb9f1f761ce035e65defea refs/tags/1.1
098d158c8d8a7ce7c6b2c5c47c967c6137beced2 refs/tags/1.2

看看packed-refs裏面是三小?有沒有發現和上面很像啊?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cat .git/packed-refs 
## pack-refs with: peeled fully-peeled 
c86b440bafe24caecfe0dbfbbd73031a1bcd1178 refs/remotes/origin/gtk3
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/master
22da092eb00b78946fd70de24427d702a77b925b refs/remotes/origin/next-release
98ff18f84fad4686012bf6183734eeb1ec5d7f46 refs/remotes/origin/show_web_search_in_popup_menu
9542889d17d276321d6da2de8d1f9d95f76fb483 refs/remotes/origin/transparent_background
7a3629ab3da7c609cf3698319ab606ebae0998e7 refs/tags/0.3.2@221
f57350c72987263c2b745690bdc8013fbbd6067c refs/tags/0.3.3@243
522d1dc2568b5c7c256e05ca69412480e1afcfb3 refs/tags/0.3.4
8e9d9520125f64a0b894a5d575f0c2d98c3ec06b refs/tags/0.3.4@301
7031115a8ea1151efc1536b43a509774ca2c0777 refs/tags/0.3.7
3f4dcbe6e1aa0045fbeb9f1f761ce035e65defea refs/tags/1.1
098d158c8d8a7ce7c6b2c5c47c967c6137beced2 refs/tags/1.2

看一下ref目錄,上面的那些reference並不存在在.git/ref裏面

1
2
3
4
5
6
7
8
9
10
$ tree .git/refs/
.git/refs/
├── heads
│   └── master
├── remotes
│   └── origin
│       └── HEAD
└── tags

4 directories, 2 files

好,生一個branch看看。

1
2
$ git checkout -b I_got_a_new_ref
Switched to a new branch 'I_got_a_new_ref'

喔喔!.git/refs出現了剛才產生的ref

1
2
3
4
5
6
7
8
9
10
11
$ tree .git/refs/
.git/refs/
├── heads
│   ├── I_got_a_new_ref
│   └── master
├── remotes
│   └── origin
│       └── HEAD
└── tags

4 directories, 3 files

跑一下git gc整理一下

1
2
3
4
5
6
$ git gc
Counting objects: 5002, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1068/1068), done.
Writing objects: 100% (5002/5002), done.
Total 5002 (delta 3913), reused 5002 (delta 3913)

疑?剛才的branch不見了。

1
2
3
4
5
6
7
8
9
$ tree .git/refs/
.git/refs/
├── heads
├── remotes
│   └── origin
│       └── HEAD
└── tags

4 directories, 1 file

跑去pack-ref了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ cat .git/packed-refs 
## pack-refs with: peeled fully-peeled 
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/heads/I_got_a_new_ref
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/heads/master
c86b440bafe24caecfe0dbfbbd73031a1bcd1178 refs/remotes/origin/gtk3
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 refs/remotes/origin/master
22da092eb00b78946fd70de24427d702a77b925b refs/remotes/origin/next-release
98ff18f84fad4686012bf6183734eeb1ec5d7f46 refs/remotes/origin/show_web_search_in_popup_menu
9542889d17d276321d6da2de8d1f9d95f76fb483 refs/remotes/origin/transparent_background
7a3629ab3da7c609cf3698319ab606ebae0998e7 refs/tags/0.3.2@221
f57350c72987263c2b745690bdc8013fbbd6067c refs/tags/0.3.3@243
522d1dc2568b5c7c256e05ca69412480e1afcfb3 refs/tags/0.3.4
8e9d9520125f64a0b894a5d575f0c2d98c3ec06b refs/tags/0.3.4@301
7031115a8ea1151efc1536b43a509774ca2c0777 refs/tags/0.3.7
3f4dcbe6e1aa0045fbeb9f1f761ce035e65defea refs/tags/1.1
098d158c8d8a7ce7c6b2c5c47c967c6137beced2 refs/tags/1.2

config

其實這個是個大哉問,就值得專門開一個章節討論。所以我整理到這個頁面,請自行前往閱讀。

ORIG_HEAD

這個檔案很有趣,一開始其實不存在。後來不知道怎麼突然跑出來,問了估狗和男人(man git reset)後得到的答案就是當reset的時候會把舊的HEAD hash放到這邊。為什麼要這樣做呢,當然是反悔用的。你可以man git commitORIG_HEAD就可以看看git commit --amend同效果的範例了。

description

懶得解釋,跳過

HEAD

望文生義,當然是指向目前所在的commit。

等等!

指向目前所在的commit是三小? 一樣,來看例子。

目前clone下來,裏面放的是一個檔案,檔案裏面是什麼呢?一樣是個commit HASH

1
2
3
4
5
6
7
8
$ cat .git/HEAD 
ref: refs/heads/master

$ cat .git/refs/heads/master
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304

$ git cat-file -t fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
commit

那我直接checkout一個不是branch的可以嘛?當然沒問題。 可以看到git會把你checkout的commit HASH寫入.git/HEAD

1
2
3
4
5
6
7
8
9
10
11
12
$ strace git checkout 93f0addca5b2723ff0f1b744b032f359
...
open("/tmp/pcmanx/.git/HEAD.lock", O_RDWR|O_CREAT|O_EXCL, 0666) = 3
write(3, "93f0addca5b2723ff0f1b744b032f359"..., 40) = 40
write(3, "\n", 1)                       = 1
close(3)                                = 0
...
rename("/tmp/pcmanx/.git/HEAD.lock", "/tmp/pcmanx/.git/HEAD") = 0
...

$ cat .git/HEAD 
93f0addca5b2723ff0f1b744b032f359938a286d

第一層目錄

branches

快過期的東西(出處),跳過。

hooks

hook,這個翻成中文很鳥,保留原文。基本上就是一組callback,特定的時候會去呼叫。我目前生意沒有做很大,有需要再去看。列出預設的檔案如下

1
2
3
4
5
6
7
8
9
10
11
12
13
$ ll .git/hooks/
total 48
drwxrwxr-x 2 wen wen 4096 Mar  9 23:10 ./
drwxrwxr-x 8 wen wen 4096 Mar  9 23:21 ../
-rwxrwxr-x 1 wen wen  452 Mar  9 23:10 applypatch-msg.sample*
-rwxrwxr-x 1 wen wen  896 Mar  9 23:10 commit-msg.sample*
-rwxrwxr-x 1 wen wen  189 Mar  9 23:10 post-update.sample*
-rwxrwxr-x 1 wen wen  398 Mar  9 23:10 pre-applypatch.sample*
-rwxrwxr-x 1 wen wen 1642 Mar  9 23:10 pre-commit.sample*
-rwxrwxr-x 1 wen wen 1239 Mar  9 23:10 prepare-commit-msg.sample*
-rwxrwxr-x 1 wen wen 1348 Mar  9 23:10 pre-push.sample*
-rwxrwxr-x 1 wen wen 4898 Mar  9 23:10 pre-rebase.sample*
-rwxrwxr-x 1 wen wen 3611 Mar  9 23:10 update.sample*

info

儲存資訊,廢話。目前裏面只有一個exclude,而且檔案內容還都是註解。但是這邊有說還有其他檔案。另外值得提的是exclude說明有提到.gitignore針對的是git status, git rm, git add, git clean。其他的操作還是得在.git/log/exclude中設定。

logs

這是存放log的地方。log是什麼呢?直接看個例子。

我們先新增一個測試檔案

1
2
3
4
5
6
$ touch test2
$ git add test2
$ git commit test2 -m "test2"
[master 09580e4] test2
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 test2

執行git reflog,可以看到你剛才的動作以及有當次HEAD的commit HASH已經被記起來。

1
2
3
4
5
$ git reflog 
09580e4 HEAD@{0}: commit: test2
fd5d6ab HEAD@{1}: checkout: moving from 93f0addca5b2723ff0f1b744b032f359938a286d to master
93f0add HEAD@{2}: checkout: moving from master to 93f0addca5b2723ff0f1b744b032f359938a286d
...

這東西有什麼用處呢?當然是讓你反悔用的。再來看個例子

首先開一個branch

1
2
$ git checkout -b demo_reflog
Switched to a new branch 'demo_reflog'

新增並commit file1

1
2
3
4
5
6
$ touch file1
$ git add file1
$ git commit file1 -m "file 1"
[demo_reflog c982c70] file 1
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file1

然後新增並commit file2

1
2
3
4
5
6
$ touch file2
$ git add file2
$ git commit file2 -m "file 2"
[demo_reflog b85f4d4] file 2
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 file2

切回master並且宰掉剛才的branch

1
2
3
4
5
6
$ git checkout master 
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

$ git branch -D demo_reflog  
Deleted branch demo_reflog (was b85f4d4).

假設上一個動作是手滑誤刪,能不能反悔。 YES YOU CAN!

先看看log

1
2
3
4
5
$ git reflog 
fd5d6ab HEAD@{0}: checkout: moving from demo_reflog to master
b85f4d4 HEAD@{1}: commit: file 2
c982c70 HEAD@{2}: commit: file 1
...

可以知道上次branch最後commit的HASH,那麼我們就可以切過去然後開另外一個branch了。

1
2
$ git checkout -b i_am_back b85f4d4
Switched to a new branch 'i_am_back'

可以看到資料回來了。

1
2
3
4
5
$ git log --pretty=oneline --abbrev-commit
b85f4d4 file 2
c982c70 file 1
fd5d6ab Fix invisible cairo caret issue.
...

當然這還是險招,git本身保留是有保存期限的。有興趣問男人。man git gc

不知道有沒有會問:「啊不是要介紹.git/logs嘛?」

問得好!猜猜剛才git reflogs檔案從哪裡讀出來?

一樣請出strace大大

1
2
3
4
$ strace -e open git reflog 
...
open("/tmp/pcmanx/.git/logs/HEAD", O_RDONLY) = 3
...

有看到.git/logs/HEAD吧?有沒有想看一下裏面是啥?至少我想看。

1
2
3
4
5
6
7
$ cat .git/logs/HEAD
...
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 c982c70f00875b83e4845ca35ef0ca4c70f73e64 Wen.Liao <censored> 1425997414 +0800  commit: file 1
c982c70f00875b83e4845ca35ef0ca4c70f73e64 b85f4d4dca4012a2d1785750061fae9e03e817e1 Wen.Liao <censored> 1425997438 +0800  commit: file 2
b85f4d4dca4012a2d1785750061fae9e03e817e1 fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 Wen.Liao <censored> 1425997673 +0800  checkout: moving from demo_reflog to master
fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304 b85f4d4dca4012a2d1785750061fae9e03e817e1 Wen.Liao <censored> 1425997732 +0800  checkout: moving from master to b85f4d4
b85f4d4dca4012a2d1785750061fae9e03e817e1 b85f4d4dca4012a2d1785750061fae9e03e817e1 Wen.Liao <censored> 1425997746 +0800  checkout: moving from b85f4d4dca4012a2d1785750061fae9e03e817e1 to i_am_back

精確的來說,.git/logs下面不只紀錄HEAD的log,還有各種ref的log。有興趣的朋友可以自行參考這邊,並且進去目錄逛逛。

objects

先看剛clone下來的目錄結構

1
2
3
4
5
6
$ tree .git/objects/
.git/objects/
├── info
└── pack
    ├── pack-6042320de285747900cc3263a47f7bab429cabf8.idx
    └── pack-6042320de285747900cc3263a47f7bab429cabf8.pack

來生一個目錄和並且commit一個東西進去。

1
2
3
4
5
6
7
$ mkdir tree
$ echo test > tree/file
$ git add tree/file 
$ git commit tree/file -m "test tree and file"
[master 2ba01ef] test tree and file
 1 file changed, 1 insertion(+)
 create mode 100644 tree/file

然後看看這個目錄變化吧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ tree .git/objects/
.git/objects/
├── 2b
│   └── a01ef5583f35dd57b5fb5bde732f73bc315b6d
├── 9d
│   └── aeafb9864cf43055ae93beb0afd6c7d144bfa4
├── de
│   └── f69c2c316574aaf328e546486fa750eb9c53a0
├── e1
│   └── b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
├── info
└── pack
    ├── pack-6042320de285747900cc3263a47f7bab429cabf8.idx
    └── pack-6042320de285747900cc3263a47f7bab429cabf8.pack

6 directories, 6 files

接下來把目錄和檔案混起來,用git cat-file -p看看裏面是啥

1
2
3
4
5
6
7
$ git cat-file -p 2ba01ef5583f35dd57b5fb5bde732f73bc315b6d
tree def69c2c316574aaf328e546486fa750eb9c53a0
parent fd5d6abe34f41f8687c1fc5e2ab0a2f65c570304
author Wen.Liao <censored> 1425999641 +0800
committer Wen.Liao <censored> 1425999641 +0800

test tree and file
1
2
$ git cat-file -p 9daeafb9864cf43055ae93beb0afd6c7d144bfa4
test
1
2
3
4
$ git cat-file -p def69c2c316574aaf328e546486fa750eb9c53a0
100644 blob 5bc4b3f69d730535eb3a8aee28d7ef9273225d50  .gitignore
...
040000 tree e1b8ecbb1f19709f3a4867a0ffe08bb2e07acf19  tree
1
2
$ git cat-file -p e1b8ecbb1f19709f3a4867a0ffe08bb2e07acf19
100644 blob 9daeafb9864cf43055ae93beb0afd6c7d144bfa4  file

其實不難猜,你local commit的東西會被處理後變成object,並且依照hash分門別類地存放。

接下來我們來做個測試

1
2
3
4
5
6
$ git gc
Counting objects: 5006, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (1070/1070), done.
Writing objects: 100% (5006/5006), done.
Total 5006 (delta 3914), reused 5001 (delta 3913)

可以看到,剛才的目錄全部消失,而pack目錄的檔案名稱也和前面的不同了。對照上面git gc的訊息,可以知道git gc做了壓縮,並且將這些object一起壓到pack目錄的檔案中了。

1
2
3
4
5
6
7
8
9
$ tree .git/objects/
.git/objects/
├── info
│   └── packs
└── pack
    ├── pack-57d163604c430e1919a18e97c5b1312291b62721.idx
    └── pack-57d163604c430e1919a18e97c5b1312291b62721.pack

2 directories, 3 files

所以我們可以從觀察中做出結論。Git object的存放有兩種型式

  • loose object:就是剛才看到用[HASH前兩碼]/[剩下HASH] 的目錄檔案結構
  • packed object: 把object壓縮成兩個檔案

而looose object型式經過git gc後會有可能轉換成packed object,反之亦然。詳細規則問男人。 由於git gc是用來清除整理local repository,也就是說做了git gc是有可能刪除用不到的資料。詳細情況一樣請問男人man git gc

refs

參考資料

ProGit: Customizing Git - Git Configuration導讀

| Comments

目錄

測試環境

1
2
3
4
5
6
7
8
9
10
11
12
13
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.2 LTS
Release:  14.04
Codename: trusty

$ git --version
git version 2.3.0

$ git remote -v
origin    https://github.com/pcman-bbs/pcmanx.git (fetch)
origin    https://github.com/pcman-bbs/pcmanx.git (push)

簡易使用方式

最簡單的使用方式

  • 新增/更改選項
    • git config 型態 名稱 你要設定的值
      • 常見的型態有--system或是--global
  • 讀取選項
    • git config –get 名稱

值得注意的是,git config 的名稱一般來說會使用.作為分類的參考如 git config test.user.name.first Liao git config test.user.name.last Wen 而在設定檔中,會用[]把第一個.的前面的字串放在前面,然後最後一個.後面的字串和你要設定的值變成 * 最後一個.後面的字串=你要設定的值

你應該要問,那中間的呢?好問題,中間的就放在[]裏面囉。

很模糊?看例子比較快。

1
2
3
4
5
6
7
$ git config test.user.name.first Liao
$ git config test.user.name.last Wen
$ cat .git/config
...
[test "user.name"]
  first = Liao
  last = Wen

git config參考檔案以及順序

要注意的是/etc/gitconfig不一定存在,我的是沒有。所以生一個測試驗證用。

  • /etc/gitconfig
    • git config --system產生
  • ~/.gitconfig
    • git config --global產生
  • 你的local repostiory/.git/config
    • 你的local repostiory下面執行git config產生

檔案讀取順序是由上往下,全部都有資料的話。最後回傳值順序是由下而上。 要練習驗證,所以來看一下。

用strace看git config --get user.name開檔案的順序。

1
2
3
4
5
6
7
8
$ strace -e open git config --get user.name
...
open("/etc/gitconfig", O_RDONLY)        = 3
open("/home/user/.gitconfig", O_RDONLY)  = 3
open(".git/config", O_RDONLY)           = 3
...
Wen.Liao
+++ exited with 0 +++

可以看到/etc/gitcnfig沒有user.name

1
$ cat /etc/gitconfig  | grep name

~/.gitconfig下有Wen.Liao的字串

1
2
3
4
$ cat ~/.gitconfig 
[user]
  name = Wen.Liao
...

同樣的,目前repository下面的config是沒有user.name資料

1
cat .git/config  | grep name

接下來改一下user.name

1
2
3
4
5
6
7
$ strace -e open git config user.name qqq
...
open("/etc/gitconfig", O_RDONLY)        = 3
open("/home/user/.gitconfig", O_RDONLY)  = 3
open(".git/config", O_RDONLY)           = 3
...
+++ exited with 0 +++

可以看到目前目錄下.git/config新增了user.name

1
2
3
4
$ cat .git/config 
...
[user]
  name = qqq

驗證是否.git/config的值真的會比~/.gitconfig優先

1
2
$ git config --get user.name
qqq

git config 選項節錄

Git config 選項有分為server端和client端,大部分選項和client有關,文件中節錄部份,我再節錄我有興趣的部份。想要知道全部的選項可以問男人。man git config。再次提醒,如果是你要套用到全部git repository請用git config --global,不然這些選項只會在你目前git repository生效。

  • core.editor
    • 你要commit 時候會用的編輯器
  • commit.template
    • 這個比較重要,如果你要套用commit template,就用這個。把你自己或是團隊的commit template引入吧。我自己會用# 為template加入註解。
  • color.ui
    • 為你的UI文字上色。可以設成true 或是false

指定使用外部比較命令

git可以指令使用外部比較命令,首先要搞清楚git 會送什麼參數給外部命令,所以我們先寫個script印出參數,並且指定外部diff的時候使用這個script。

1
2
3
4
5
6
7
8
$ cat ~/bin/git_diff.sh 
#!/bin/sh
echo $@

$ git config --global diff.external ~/bin/git_diff.sh

$ git diff HEAD^
src/core/caret.cpp /tmp/6nZQoO_caret.cpp 99fdca2d1d2b90e6452555572d3b0c3cf3ae75b0 100644 src/core/caret.cpp 15b662547eb5a853f02a6fc086e07feed2e45d4f 100644

這些參數的順序是

1 更動的檔案名稱 2 將被更動前的檔案內容另存新檔路徑 3 被更動前的檔案blob hash 4 被更動前檔案權限 5 將被更動後的檔案內容另存新檔路徑 6 被更動後的檔案blob hash 7 被更動後檔案權限

對我們來說,有興趣的是2和5,所以我們可以將命令更動為

1
2
3
4
$ cat ~/bin/git_diff.sh 
#!/bin/sh -x
echo $@
meld $2 $5

meld是一個GUI程式的比對工具。接下來就來測試吧

1
2
3
4
$ git diff HEAD^
+ echo src/core/caret.cpp /tmp/0M6Fgi_caret.cpp 99fdca2d1d2b90e6452555572d3b0c3cf3ae75b0 100644 src/core/caret.cpp 15b662547eb5a853f02a6fc086e07feed2e45d4f 100644
src/core/caret.cpp /tmp/0M6Fgi_caret.cpp 99fdca2d1d2b90e6452555572d3b0c3cf3ae75b0 100644 src/core/caret.cpp 15b662547eb5a853f02a6fc086e07feed2e45d4f 100644
+ meld /tmp/0M6Fgi_caret.cpp src/core/caret.cpp

GUI畫面如下:

手冊上面還有提到merge tools 的設定,本人不感興趣。跳過。

參考資料

談談git Remote

| Comments

在軟體專案開發常常會需要比對不同的repository。舉例來說,你可能在遠端有一套軟體專案,這個專案是從upstream fork 下來,那麼如果要把upstream 新的功能合併到專案,人肉合併往往是最容易出錯又最沒效率的方式。如果這兩個專案都有git,那麼git remote就是你的救星。針對剛才講的更詳細的use case可以看Git Hub的範例

直接拿Use case範例,不囉唆。 Demo情境

  • 有兩個遠端repositories: repo1, repo2
  • 將repo1 clone下來到local repository,另外一個透過git remote加入到local repository
  • 在local建立一個branch,對應到repo2
  • merge repo1到repo2
    • 要注意的是其實以upstream 情況是剛好相反,一般來說repo1,也就是你clone的才是你要開發的專案。選擇這樣的case單純只是吃飽撐著。

詳細行為範例如下

建立測試環境

產生兩個遠端repositories: repo1, repo2

repo1

1
2
3
4
5
6
7
8
9
10
$ mkdir remote_repo1
$ cd remote_repo1/
$ git init .
Initialized empty Git repository in /tmp/remote_repo1/.git/
$ echo "repo1" > myfile
$ git add myfile 
$ git commit myfile -m "init"
[master (root-commit) 043d3c6] init
 1 file changed, 1 insertion(+)
 create mode 100644 myfile

repo2

1
2
3
4
5
6
7
8
9
10
11
$ cd ../
$ mkdir remote_repo2
$ cd remote_repo2/
$ git init .
Initialized empty Git repository in /tmp/remote_repo2/.git/
$ echo "repo2" > myfile
$ git add myfile 
$ git commit myfile -m "init"
[master (root-commit) 5c329bf] init
 1 file changed, 1 insertion(+)
 create mode 100644 myfile

看一下目錄架構

1
2
3
4
5
6
$ cd ../
$ tree remote_repo1 remote_repo2/
remote_repo1
└── myfile
remote_repo2/
└── myfile

新增remote URL

就 git clone,沒啥好說

1
2
3
4
5
6
7
$ git clone remote_repo1/ local_repo
Cloning into 'local_repo'...
done.
$ cd local_repo/
$ git remote -v
origin    /tmp/remote_repo1/ (fetch)
origin    /tmp/remote_repo1/ (push)

建立對應remote URL的local branch

我們用了git remote add 本地如何稱呼remote remote的URL

1
2
3
4
5
6
$ git remote add repo2 /tmp/remote_repo2/
$ git remote -v
origin    /tmp/remote_repo1/ (fetch)
origin    /tmp/remote_repo1/ (push)
repo2 /tmp/remote_repo2/ (fetch)
repo2 /tmp/remote_repo2/ (push)

接下來把remote URL的repository 拉下來

1
2
3
4
5
6
7
$ git fetch repo2
warning: no common commits
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Unpacking objects: 100% (3/3), done.
From /tmp/remote_repo2
 * [new branch]      master     -> repo2/master

然後建立一個branch,對應到repo2的master

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ git checkout -b local_repo2 repo2/master
Branch local_repo2 set up to track remote branch master from repo2.
Switched to a new branch 'local_repo2'

$ git branch -a
* local_repo2
  master
  remotes/origin/HEAD -> origin/master
  remotes/origin/master
  remotes/repo2/master

$ git diff master 
diff --git a/myfile b/myfile
index 464d9cd..27d38ae 100644
 a/myfile
+++ b/myfile
@@ -1 +1 @@
-repo1
+repo2

在local合併兩個Romote Branch

剩下非常直覺,就把兩個local branch merge,整理完衝突收工。

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
$ git merge master 
Auto-merging myfile
CONFLICT (add/add): Merge conflict in myfile
Automatic merge failed; fix conflicts and then commit the result.

$ git status 
On branch local_repo2
Your branch is up-to-date with 'repo2/master'.
You have unmerged paths.
  (fix conflicts and run "git commit")

Unmerged paths:
  (use "git add <file>..." to mark resolution)

  both added:      myfile

no changes added to commit (use "git add" and/or "git commit -a")

$ cat myfile 
<<<<<<< HEAD
repo2
=======
repo1
>>>>>>> master
$ echo shut-up > myfile 

$ git add myfile 

$ git commit -m "Overwrite myfile"
[local_repo2 a76c0a6] Overwrite myfile

請注意這個case不能夠把更動push 回repo2/master 會噴錯誤如下

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
$ git push repo2 
warning: push.default is unset; its implicit value has changed in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the traditional behavior, use:

  git config --global push.default matching

To squelch this message and adopt the new behavior now, use:

  git config --global push.default simple

When push.default is set to 'matching', git will push local branches
to the remote branches that already exist with the same name.

Since Git 2.0, Git defaults to the more conservative 'simple'
behavior, which only pushes the current branch to the corresponding
remote branch that 'git pull' uses to update the current branch.

See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)

fatal: The upstream branch of your current branch does not match
the name of your current branch.  To push to the upstream branch
on the remote, use

    git push repo2 HEAD:master

To push to the branch of the same name on the remote, use

    git push repo2 local_repo2

To choose either option permanently, see push.default in 'git help config'.

簡單翻譯一下,目前local branch名稱在repo2上面沒有,你要嘛

  • 指定push到repo2的master: git push repo2 HEAD:master
  • 要嘛指定push local branch到repo2 repository上

第一個情況需要bared repository,時間關係有空再談。

參考資料

島唄 - the Boom

| Comments

這是一首反省二戰日本政府在沖繩島的決策造成大量當地人冤死悲劇的歌曲。

在二次大戰末期,米軍開始準備入侵日本本島,因此需要佔領沖繩作為前進基地。當時的日本政府在當地強徵年輕男女學生在戰場上做雜務以及醫療協助。同時大力宣傳米軍的邪惡形象,宣稱米軍殘暴邪惡,表示與其被米軍俘虜生不如死,不如自殺比較痛快。網路上可以看到日本政府會發手榴彈給當地居民的資料。加上日本政府強力徵收糧食,估計戰役結束後平民死亡人數預估超過十萬人(出處)。

回到主題,這首歌是1992年出版的。根據Wikipedia的說明,主唱者去沖繩的時候,參觀了姬百合和平祈念資料館,了解這片土地上發生的事情,以及館內倖存者親口說出的故事後,感到非常惆悵。當他離開姬百合和平祈念資料館的時候,映入眼前的是一整片的甘蔗田,讓他動起創作的想法。這就是The boom出版島唄(shima-uta)這首歌的由來。

我不會日文,不過ptt有中文歌詞翻譯的歌詞,也有詳細的背景說明。希望人類記取這些慘痛的教訓,不要再放任不負責任的政府亂搞,最後導致悲劇發生。

參考資料

Ubuntu 14.04 下面的VirtualBox存取USB設備

| Comments

照慣例,列一下測試環境

  • Host OS
1
2
3
4
5
6
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.2 LTS
Release:  14.04
Codename: trusty
  • Guest OS 同上。

  • VirtualBox 版本: Oracle VM VirtualBox Manager 4.3.22


主要有兩件事要做,就是


將使用者加入Virtual Box 群組

把使用者加入vboxusers群組

1
$ sudo usermod -a -G vboxusers $USER

Double check是否已經在vboxusers group 指令

1
$ groups $USER

安裝延伸套件(Optional)

1
$ sudo virtualbox Oracle_VM_VirtualBox_Extension_Pack-4.3.22-98236.vbox-extpack
  • 從主選單中要使用USB VM的Settings->USB選單中,勾選Enable USB 2.0 (EHCI) Controller,請確認VM是Powered off狀態。
  • 驗證:
    • Start VM
    • VM視窗中選Devices -> USB -> 挑你想要使用的USB
    • Guest OS開完機,Linux的話用可以使用lsusb驗證

參考資料

系統函式庫的debug 資訊放在那邊?

| Comments

在查詢hello world中的_start呼叫__libc_start_main部份,使用到了反組譯工具。觀察反組譯的部份發現有可能需要看一下libc本身的__libc_start_main組合語言的行為。以前的經驗,這種情況先拉有debug 資訊的套件來看,所以拉了libc6-dbg下來,結果下來的結果,完全無法反組譯。後來請教網友Scott Tasi才發現我錯很大。

先講結論:

  • libc的debug 資訊和本身的binary完全隔開。所以反組譯要看的仍然是在原本的libc.so這個檔案。

年紀大了才發現能夠從結論中問問題收穫會更多,所以我就來問

  • 問題一:誰來用這些debug 資訊?
  • 問題二:既然debug 資訊不在binary內?那麼怎麼找到額外的debug 資訊?

先來回答誰來用這些debug 資訊?其實這個根本就是廢話,gdb用心酸的啊。其實這只是在鋪梗,這表示找第二個問題的答案就會和gdb有很大的關係,也就是說我們可以把問題縮小到和gdb相關 (註一)

好,我們來重複問題二

  • 既然debug 資訊不在binary內?那麼怎麼找到額外的debug 資訊?

從常理來猜測,顯然是原本的binary有地方告訴gdb「有額外的除錯檔案,請你載入的時候去那邊抓除錯資訊」。我們直接看GDB手冊怎麼說

  • gdb允許把debug 資訊放在binary外面,而gdb可以透過兩種方式取得
    • 執行的binary內提供link讓gdb可以摸到有debug 資訊的檔案。如果是執行檔,可能就稱為執行檔.debug
      • 這個link資料,放兩個東西
        • 不包含directory的檔案名稱
        • 該檔案名稱算出的CRC 碼
    • 透過build ID,因為和我找的無關,跳過。我們focus在第一個方式。

有了debug link資訊後,仍然有幾個細節需要釐清

  • 問題三:debug 資訊檔案放在哪個目錄?
  • 問題四:debug link放在binary 的哪裡?

一樣來看手冊。手冊上說debug 資訊檔案會依以下的順序搜尋

  • 該執行檔的存放目錄
  • 該執行檔的存放目錄下面同樣目錄名稱,但是加上.debug。如/usr/bin -> /usr/bin.debug/
  • 系統預設的debug 目錄

以上是問題三的的解答,剩下問題四我就認為可以把整個故事說完。所以來看問題四吧。

  • 問題四:debug link放在binary 的哪裡?

同樣的在手冊中有提到,binary 中有特別的section稱為.gnu_debuglink,就是存放debug 資訊檔案的資訊。存放的資訊為

  • 存放debug 資訊的檔案名稱
  • padding for 4-byte alignment
  • 4 byte CRC checksum

好啦,有了完整的故事,當然要看是不是在唬爛。我們來看libc.so吧

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
$ pwd
/lib/arm-linux-gnueabi
$ objdump -h libc-2.19.so | grep debug
 68 .gnu_debuglink 00000014  00000000  00000000  001336a0  2**0
$ objdump -s -j .gnu_debuglink libc-2.19.so 

libc-2.19.so:     file format elf32-littlearm

Contents of section .gnu_debuglink:
 0000 6c696263 2d322e31 392e736f 00000000  libc-2.19.so....
 0010 1b8f248c                             ..$. 
 
$ sudo apt-get install libc6-dbg
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  libc6-dbg
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 3,290 kB of archives.
After this operation, 18.1 MB of additional disk space will be used.
Get:1 http://不告訴你/debian/ jessie/main libc6-dbg armel 2.19-13 [3,290 kB]
Fetched 3,290 kB in 4s (701 kB/s)      
Selecting previously unselected package libc6-dbg:armel.
(Reading database ... 46987 files and directories currently installed.)
Preparing to unpack .../libc6-dbg_2.19-13_armel.deb ...
Unpacking libc6-dbg:armel (2.19-13) ...
Setting up libc6-dbg:armel (2.19-13) ...
$ find /usr/lib/debug/ |grep libc-2
/usr/lib/debug/lib/arm-linux-gnueabi/libc-2.19.so

那麼這個和我原本要反組譯libc的結果有什麼關係?嘛,旅行的精華就是在迷路不是嗎?


測試環境

1
2
3
4
5
6
7
8
9
10
11
12
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Debian
Description:  Debian GNU/Linux 8.0 (jessie)
Release:  8.0
Codename: jessie

$ uname -a
Linux debian 3.2.0-4-versatile #1 Debian 3.2.65-1+deb7u1 armv5tejl GNU/Linux

$ gcc -dumpmachine
arm-linux-gnueabi

參考資料


註解

一:精確來說,這應該不是只有gdb才能用,我不過偷懶而已。

Gcc的幾個參數筆記

| Comments

整理一下,反正一定會忘記,就放在這邊,也許以後會再新增

  • gcc –save-temp hello.c
    • gcc中間產生的檔案通通存起來
  • gcc -dumpspecs
    • 印spec,不過我完全看不懂
  • gcc -dumpmachine
    • 印出平台

其他關於巨集部份請參考這邊這邊,很臭很長,抱歉。

ssh登入遠端主機自動設定X Display 變數

| Comments

前言


因為用X用習慣了,常常會連線到另外遠端電腦,然後透過X把該台電腦軟體畫面輸出到本機。這時候的標準流程會是

  • ssh登入到遠端
  • export DISPLAY=你的本機IP:0.0

每次都要打這些,還要查自己本機IP很煩。所以整理了兩光的script提供以後剪下貼上。

另外有人會問為什麼不直接用ssh -X連過去?原因是因為我習慣設完display後,開一個遠端終端機丟在背景然後登出目前的ssh session,這樣的操作在ssh -X下從來沒有成功過。我了遠端終端機後就算放在背景,一樣目前終端機會卡住。如果暴力把目前終端機關掉,那麼所有X的session都GG。實在懶得找原因,反正這是個workaround的世界(煙)。

背景說明


上面操作中,本機需要設定

  • X allow TCP
  • xhost 加入允許連進來的IP

這兩個部份請自行估狗。

另外本機和遠端主機用相同的Distribution資訊如下,測試端的遠端主機是一台沒有GUI的Ubuntu server。

1
2
3
4
5
6
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Ubuntu
Description:  Ubuntu 14.04.2 LTS
Release:  14.04
Codename: trusty

使用方式


把下面的描述放在~/.profile最後面,下次ssh登入後就可以直接開啟X應用程式把畫面丟回本機了。

1
2
3
4
5
6
7
8
9
# Let's export DISPLAY if we are from ssh remote
if [ "$TERM" = "xterm" -a -n "$SSH_CLIENT" ] ; then
    REMOTE_IP=$(echo $SSH_CLIENT | cut -f 1 -d " ")
    if [ -z "$REMOTE_IP" ] ; then
        exit 0
    fi

    export DISPLAY=$REMOTE_IP:0.0
fi

Linux中誰來呼叫C語言中的main?

| Comments

記得很久以前聽說在Linux執行檔案時,真正的起始點並不是main,加上之前有看到單純ld會幫你偷偷link一些沒看過的object檔案,所以這次就來看到底真相為何?

測試環境

因為很假掰想要順便接觸一下ARM的組語,所以這次測試就使用Qemu跑ARM的Debian。

1
2
3
4
5
6
7
8
9
$ lsb_release -a
No LSB modules are available.
Distributor ID:   Debian
Description:  Debian GNU/Linux 8.0 (jessie)
Release:  8.0
Codename: jessie

$ file /bin/ls
/bin/ls: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=571db48d9c9e4625b7da206e748e41c237f2b202, stripped

測試原始碼,一樣是大家熟悉的Hellow world

hello1.c
1
2
3
4
5
6
7
8
#include <stdio.h>

int main()
{
  printf("Hello World\n");

  return 0;
}

不知道各位還記得前面有提過,執行檔中有.text的section。要執行的機械碼會放在這邊。我們先來看看hello1執行檔會從那邊開始?

hello1.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ readelf -h hello1
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0x102f0
  Start of program headers:          52 (bytes into file)
...
Section header string table index: 33

從readelf可以看到起始點為0x102f0,那麼0x102f0是在那邊呢?我們再去看symbol table可以看到很巧的就是.text的起始點。

hello1.c
1
2
3
4
5
6
7
8
$ objdump -t hello1

hello1:     file format elf32-littlearm

SYMBOL TABLE:
00010134 l    d  .interp  00000000              .interp
...
000102f0 l    d  .text    00000000              .text

好了,那麼.text這邊起始的程式是什麼?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Disassembly of section .text:

000102f0 <_start>:
   102f0:       e3a0b000        mov     fp, #0
   102f4:       e3a0e000        mov     lr, #0
   102f8:       e49d1004        pop     {r1}            ; (ldr r1, [sp], #4)
   102fc:       e1a0200d        mov     r2, sp
   10300:       e52d2004        push    {r2}            ; (str r2, [sp, #-4]!)
   10304:       e52d0004        push    {r0}            ; (str r0, [sp, #-4]!)
   10308:       e59fc010        ldr     ip, [pc, #16]   ; 10320 <_start+0x30>
   1030c:       e52dc004        push    {ip}            ; (str ip, [sp, #-4]!)
   10310:       e59f000c        ldr     r0, [pc, #12]   ; 10324 <_start+0x34>
   10314:       e59f300c        ldr     r3, [pc, #12]   ; 10328 <_start+0x38>
   10318:       ebffffeb        bl      102cc <__libc_start_main@plt>
   1031c:       ebfffff0        bl      102e4 <abort@plt>
   10320:       000104b4        .word   0x000104b4
   10324:       00010420        .word   0x00010420
   10328:       00010448        .word   0x00010448

很有趣,沒看到main(),反而看到_start。到底是_start是什麼呢?還記得Linker script嗎?裏面有一個ENTRY指令,可以指定程式從那邊開始跑,先來看一下預設的ENTRY是不是也是_start?

1
2
$ ld --verbose | grep ENTRY
ENTRY(_start)

目前我們只知道執行檔起始點是_start,而不是main,那顯然有人幫你把執行檔加碼,以至於你的執行檔出現了_start。最偷懶的方式就是去找binary看看是不是有這樣的symbol。

1
2
3
4
5
6
7
user@host:/usr/lib$ find -name "*.[ao]" -exec nm -A {} \;  2> /dev/null | grep " _start$"
./arm-linux-gnueabi/crt1.o:00000000 T _start
./arm-linux-gnueabi/gcrt1.o:00000000 T _start
./arm-linux-gnueabi/Scrt1.o:00000000 T _start
./debug/usr/lib/arm-linux-gnueabi/crt1.o:00000000 T _start
./debug/usr/lib/arm-linux-gnueabi/gcrt1.o:00000000 T _start
./debug/usr/lib/arm-linux-gnueabi/Scrt1.o:00000000 T _start

OK,的確有object檔案裡面有_start,我們再來確認編譯的時候會不會link這些檔案。

1
2
3
4
5
6
7
8
9
10
$ gcc -v hello1.c
Using built-in specs.
COLLECT_GCC=gcc
...
COLLECT_GCC_OPTIONS='-v' '-march=armv4t' '-mfloat-abi=soft'
...
-X --hash-style=gnu -m armelf_linux_eabi
...
/usr/lib/gcc/arm-linux-gnueabi/4.9/../../../arm-linux-gnueabi/crt1.o
...

_start會呼叫外部函數__libc_start_main,我們透過LD_DEBUG來看一下。

1
2
3
4
$ LD_DEBUG=all ./hello1 2>&1 |grep __libc_start_main
       890:  symbol=__libc_start_main;  lookup in file=./hello1 [0]
       890:  symbol=__libc_start_main;  lookup in file=/lib/arm-linux-gnueabi/libc.so.6 [0]
       890:  binding file ./hello1 [0] to /lib/arm-linux-gnueabi/libc.so.6 [0]: normal symbol `__libc_start_main' [GLIBC_2.4]

可以看到,在./hello1中有去找__libc_start_main,最後去libc.so.6找,並且找出libc.so.6__libc_start_main的位址(即binding)。而__libc_start_mainprototype如下

1
int __libc_start_main(int (*main) (int, char **, char **), int argc, char ** ubp_av, void (*init) (void), void (*fini) (void), void (*rtld_fini) (void), void (*stack_end));

看到有趣的東西嘛?我有看到

  • main函數當作function pointer傳入
  • main函數的參數
  • 其他不知道三小的function pointer
    • init
    • fini
    • rtld_fini

從這邊我可以猜測這個函數就是呼叫一堆callback function,這些callback function就是上面列的死人骨頭。

手冊的說明可以看到__libc_start_main()是用來執行環境的初始化、呼叫main函數並且傳遞參數,當main函數結束後處理回傳值。手冊提到的範例詳細行為有

  • 檢查權限,確保安全性
  • thread subsystem初始化 (我可不知道什麼thread subsystem唷)
  • rtld_fini註冊release callback function,當shared object結束時使用該callback釋放資源
  • 呼叫init callback function
  • 呼叫main callback function並且帶入參數
  • 當main callback function結束後,將回傳值作為參數呼叫exit

我們再回頭看看_start的組合語言:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
000102f0 <_start>:
   102f0:       e3a0b000        mov     fp, #0
   102f4:       e3a0e000        mov     lr, #0
   102f8:       e49d1004        pop     {r1}            ; (ldr r1, [sp], #4)
   102fc:       e1a0200d        mov     r2, sp
   10300:       e52d2004        push    {r2}            ; (str r2, [sp, #-4]!)
   10304:       e52d0004        push    {r0}            ; (str r0, [sp, #-4]!)
   10308:       e59fc010        ldr     ip, [pc, #16]   ; 10320 <_start+0x30>
   1030c:       e52dc004        push    {ip}            ; (str ip, [sp, #-4]!)
   10310:       e59f000c        ldr     r0, [pc, #12]   ; 10324 <_start+0x34>
   10314:       e59f300c        ldr     r3, [pc, #12]   ; 10328 <_start+0x38>
   10318:       ebffffeb        bl      102cc <__libc_start_main@plt>
   1031c:       ebfffff0        bl      102e4 <abort@plt>
   10320:       000104b4        .word   0x000104b4
   10324:       00010420        .word   0x00010420
   10328:       00010448        .word   0x00010448

有趣的地方是這3個位址

1
2
3
   10320:       000104b4        .word   0x000104b4
   10324:       00010420        .word   0x00010420
   10328:       00010448        .word   0x00010448

這邊可以看到這3個位址分別是

  • 10320: 000104b4 .word 0x000104b4
    • __libc_csu_fini
  • 10324: 00010420 .word 0x00010420
    • main
  • 10328: 00010448 .word 0x00010448
    • __libc_csu_init

也就是說,main__libc_csu_init分別當作第一和第四參數傳給__libc_start_main,而__libc_csu_fini則被丟到stack,一樣傳給__libc_start_main了。

結論

Linux執行程式的起始點並不是main,而是glibc binary中crt1.o準備的_start。這個start主要將你的main,還有一些hook函數丟給__libc_start_main,接下來libc的__libc_start_main樵好事情後才真正執行你的main,並且還要在main結束後清理戰場。

延伸閱讀

參考資料

完整反組譯程式碼

Hello.dis
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
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
$ cat hello1.dis

hello1:     file format elf32-littlearm


Disassembly of section .init:

0001029c <_init>:
   1029c:    e92d4008    push    {r3, lr}
   102a0:    eb000021    bl  1032c <call_weak_fn>
   102a4:    e8bd4008    pop {r3, lr}
   102a8:    e12fff1e    bx  lr

Disassembly of section .plt:

000102ac <puts@plt-0x14>:
   102ac:    e52de004    push    {lr}      ; (str lr, [sp, #-4]!)
   102b0:    e59fe004    ldr lr, [pc, #4] ; 102bc <_init+0x20>
   102b4:    e08fe00e    add lr, pc, lr
   102b8:    e5bef008    ldr pc, [lr, #8]!
   102bc:    00010318   .word   0x00010318

000102c0 <puts@plt>:
   102c0:    e28fc600    add ip, pc, #0, 12
   102c4:    e28cca10    add ip, ip, #16, 20   ; 0x10000
   102c8:    e5bcf318    ldr pc, [ip, #792]!  ; 0x318

000102cc <__libc_start_main@plt>:
   102cc:    e28fc600    add ip, pc, #0, 12
   102d0:    e28cca10    add ip, ip, #16, 20   ; 0x10000
   102d4:    e5bcf310    ldr pc, [ip, #784]!  ; 0x310

000102d8 <__gmon_start__@plt>:
   102d8:    e28fc600    add ip, pc, #0, 12
   102dc:    e28cca10    add ip, ip, #16, 20   ; 0x10000
   102e0:    e5bcf308    ldr pc, [ip, #776]!  ; 0x308

000102e4 <abort@plt>:
   102e4:    e28fc600    add ip, pc, #0, 12
   102e8:    e28cca10    add ip, ip, #16, 20   ; 0x10000
   102ec:    e5bcf300    ldr pc, [ip, #768]!  ; 0x300

Disassembly of section .text:

000102f0 <_start>:
   102f0:    e3a0b000    mov fp, #0
   102f4:    e3a0e000    mov lr, #0
   102f8:    e49d1004    pop {r1}      ; (ldr r1, [sp], #4)
   102fc:    e1a0200d    mov r2, sp
   10300:    e52d2004    push    {r2}      ; (str r2, [sp, #-4]!)
   10304:    e52d0004    push    {r0}      ; (str r0, [sp, #-4]!)
   10308:    e59fc010    ldr ip, [pc, #16]    ; 10320 <_start+0x30>
   1030c:    e52dc004    push    {ip}      ; (str ip, [sp, #-4]!)
   10310:    e59f000c    ldr r0, [pc, #12]    ; 10324 <_start+0x34>
   10314:    e59f300c    ldr r3, [pc, #12]    ; 10328 <_start+0x38>
   10318:    ebffffeb    bl  102cc <__libc_start_main@plt>
   1031c:    ebfffff0    bl  102e4 <abort@plt>
   10320:    000104b4   .word   0x000104b4
   10324:    00010420   .word   0x00010420
   10328:    00010448   .word   0x00010448

0001032c <call_weak_fn>:
   1032c:    e59f3014    ldr r3, [pc, #20]    ; 10348 <call_weak_fn+0x1c>
   10330:    e59f2014    ldr r2, [pc, #20]    ; 1034c <call_weak_fn+0x20>
   10334:    e08f3003    add r3, pc, r3
   10338:    e7932002    ldr r2, [r3, r2]
   1033c:    e3520000    cmp r2, #0
   10340:    012fff1e   bxeq    lr
   10344:    eaffffe3    b   102d8 <__gmon_start__@plt>
   10348:    00010298   .word   0x00010298
   1034c:    0000001c   .word   0x0000001c

00010350 <deregister_tm_clones>:
   10350:    e59f301c    ldr r3, [pc, #28]    ; 10374 <deregister_tm_clones+0x24>
   10354:    e59f001c    ldr r0, [pc, #28]    ; 10378 <deregister_tm_clones+0x28>
   10358:    e0603003    rsb r3, r0, r3
   1035c:    e3530006    cmp r3, #6
   10360:    912fff1e   bxls    lr
   10364:    e59f3010    ldr r3, [pc, #16]    ; 1037c <deregister_tm_clones+0x2c>
   10368:    e3530000    cmp r3, #0
   1036c:    012fff1e   bxeq    lr
   10370:    e12fff13    bx  r3
   10374:    000205ff   .word   0x000205ff
   10378:    000205fc   .word   0x000205fc
   1037c:    00000000   .word   0x00000000

00010380 <register_tm_clones>:
   10380:    e59f1024    ldr r1, [pc, #36]    ; 103ac <register_tm_clones+0x2c>
   10384:    e59f0024    ldr r0, [pc, #36]    ; 103b0 <register_tm_clones+0x30>
   10388:    e0601001    rsb r1, r0, r1
   1038c:    e1a01141    asr r1, r1, #2
   10390:    e0811fa1    add r1, r1, r1, lsr #31
   10394:    e1b010c1    asrs    r1, r1, #1
   10398:    012fff1e   bxeq    lr
   1039c:    e59f3010    ldr r3, [pc, #16]    ; 103b4 <register_tm_clones+0x34>
   103a0:    e3530000    cmp r3, #0
   103a4:    012fff1e   bxeq    lr
   103a8:    e12fff13    bx  r3
   103ac:    000205fc   .word   0x000205fc
   103b0:    000205fc   .word   0x000205fc
   103b4:    00000000   .word   0x00000000

000103b8 <__do_global_dtors_aux>:
   103b8:    e92d4010    push    {r4, lr}
   103bc:    e59f401c    ldr r4, [pc, #28]    ; 103e0 <__do_global_dtors_aux+0x28>
   103c0:    e5d43000    ldrb    r3, [r4]
   103c4:    e3530000    cmp r3, #0
   103c8:    1a000002   bne 103d8 <__do_global_dtors_aux+0x20>
   103cc:    ebffffdf    bl  10350 <deregister_tm_clones>
   103d0:    e3a03001    mov r3, #1
   103d4:    e5c43000    strb    r3, [r4]
   103d8:    e8bd4010    pop {r4, lr}
   103dc:    e12fff1e    bx  lr
   103e0:    000205fc   .word   0x000205fc

000103e4 <frame_dummy>:
   103e4:    e92d4008    push    {r3, lr}
   103e8:    e59f0028    ldr r0, [pc, #40]    ; 10418 <frame_dummy+0x34>
   103ec:    e5903000    ldr r3, [r0]
   103f0:    e3530000    cmp r3, #0
   103f4:    1a000001   bne 10400 <frame_dummy+0x1c>
   103f8:    e8bd4008    pop {r3, lr}
   103fc:    eaffffdf    b   10380 <register_tm_clones>
   10400:    e59f3014    ldr r3, [pc, #20]    ; 1041c <frame_dummy+0x38>
   10404:    e3530000    cmp r3, #0
   10408:    0afffffa   beq 103f8 <frame_dummy+0x14>
   1040c:    e1a0e00f    mov lr, pc
   10410:    e12fff13    bx  r3
   10414:    eafffff7    b   103f8 <frame_dummy+0x14>
   10418:    000204e8   .word   0x000204e8
   1041c:    00000000   .word   0x00000000

00010420 <main>:
   10420:    e92d4800    push    {fp, lr}
   10424:    e28db004    add fp, sp, #4
   10428:    e59f0014    ldr r0, [pc, #20]    ; 10444 <main+0x24>
   1042c:    ebffffa3    bl  102c0 <puts@plt>
   10430:    e3a03000    mov r3, #0
   10434:    e1a00003    mov r0, r3
   10438:    e24bd004    sub sp, fp, #4
   1043c:    e8bd4800    pop {fp, lr}
   10440:    e12fff1e    bx  lr
   10444:    000104c8   .word   0x000104c8

00010448 <__libc_csu_init>:
   10448:    e92d43f8    push    {r3, r4, r5, r6, r7, r8, r9, lr}
   1044c:    e59f6058    ldr r6, [pc, #88]    ; 104ac <__libc_csu_init+0x64>
   10450:    e59f5058    ldr r5, [pc, #88]    ; 104b0 <__libc_csu_init+0x68>
   10454:    e08f6006    add r6, pc, r6
   10458:    e08f5005    add r5, pc, r5
   1045c:    e0656006    rsb r6, r5, r6
   10460:    e1a07000    mov r7, r0
   10464:    e1a08001    mov r8, r1
   10468:    e1a09002    mov r9, r2
   1046c:    ebffff8a    bl  1029c <_init>
   10470:    e1b06146    asrs    r6, r6, #2
   10474:    0a00000a   beq 104a4 <__libc_csu_init+0x5c>
   10478:    e2455004    sub r5, r5, #4
   1047c:    e3a04000    mov r4, #0
   10480:    e2844001    add r4, r4, #1
   10484:    e5b53004    ldr r3, [r5, #4]!
   10488:    e1a00007    mov r0, r7
   1048c:    e1a01008    mov r1, r8
   10490:    e1a02009    mov r2, r9
   10494:    e1a0e00f    mov lr, pc
   10498:    e12fff13    bx  r3
   1049c:    e1540006    cmp r4, r6
   104a0:    1afffff6   bne 10480 <__libc_csu_init+0x38>
   104a4:    e8bd43f8    pop {r3, r4, r5, r6, r7, r8, r9, lr}
   104a8:    e12fff1e    bx  lr
   104ac:    00010088   .word   0x00010088
   104b0:    00010080   .word   0x00010080

000104b4 <__libc_csu_fini>:
   104b4:    e12fff1e    bx  lr

Disassembly of section .fini:

000104b8 <_fini>:
   104b8:    e92d4008    push    {r3, lr}
   104bc:    e8bd4008    pop {r3, lr}
   104c0:    e12fff1e    bx  lr