【感謝 @剛子-廷皇 的熱心翻譯。如果其他朋友也有不錯的原創或譯文,可以嘗試提交到伯樂線上。】
最近我提交了一分損壞的檔案到資源庫上,被問到資料是否可恢復。根據提出的描述開始著手調查和修復問題,我認為其他人也會對解決的過程感興趣,或者遇到相同狀況後可以幫助到你。
我開啟了檢查(fsck),發現問題出現在一個類上(我過去經常用 $pack 和$obj 命令使得輸出可讀,而且因為我也更傾向於他們):
1 2 3 4 5 |
$ git fsck error: $pack SHA1 checksum mismatch error: index CRC mismatch for object $obj from $pack at offset 51653873 error: inflate: data stream error (incorrect data check) error: cannot unpack $obj from $pack at offset 51653873 |
錯誤的資訊意味著一個位元組在某一個地方混亂了,推測是類裡面描述過的(因為檢驗和的索引和zlib都失敗了)
通過閱讀zlib原始碼,我發現“檢查不正確資料”意味著adler-32
演算法檢驗zlib資料末端沒有匹配已經增加的資料。因此通過zlib壓縮資料是沒有用的,因為到每次末尾都會有錯誤,此時我們了也解到這個crc檔案不能匹配。這個出錯的位元組存在類檔案的任何地方。
第一件事情我從packfile中pull出損壞的檔案。我需要知道到底它多大的類,然後我發現了下面的資訊:
1 2 3 4 |
$ git show-index <$idx | cut -d' ' -f1 | sort -n | grep -A1 51653873 51653873 51664736 |
Show-index命令可以列出類和他們的偏移量。我們除了偏移量以外通通拋棄,然後排列,這樣我們感興趣的偏移量(這個位置是從上面fsck命令得出來的)是遵循下一個物件的偏移量。現在我們知道類檔案長度是10863位元組,然後我們可以抓取它:
1 |
dd if=$pack of=object bs=1 skip=51653873 count=10863 |
我檢查了檔案的十六進位制,搜尋任何明顯的錯誤(例如,一個4K大小執行中的0是檔案系統衝突很好的訊號)。但是所有的地方看起來都很合理。
注意到“object”檔案不適合被zlib直接壓縮;它本身包含git類資訊頭,而且是可變長度的。我們想要去掉它,所以開始直接使用zlib運算元據。你可以用你手工的方式(格式化資訊的描述在Documentation/technical/pack-format.txt裡面),或者你也可以用debugger搞定它。我選擇了後者,建立了一個驗證的包如下:# pack magic and version
1 2 3 4 5 6 7 8 9 |
# pack magic and version printf 'PACK\0\0\0\2' >tmp.pack # pack has one object printf '\0\0\0\1' >>tmp.pack # now add our object data cat object >>tmp.pack # and then append the pack trailer /path/to/git.git/test-sha1 -b <tmp.pack >trailer cat trailer >>tmp.pack |
然後在debugger中執行”git index-pack tmp.pack” 命令(會停在unpack_raw_entry)。做到這裡,我發現有3個自己的資訊頭(資訊頭本身有一個完整的型別和大小)。然後我用下面語句去掉了這些內容:
1 |
dd if=object of=zlib bs=1 skip=3 |
我用一個自定義的C程式過的zlib的解壓鎖結果。但是它報錯了,我得到準確的輸出數字位元組(也就是說,它匹配了上面解碼的git的資訊頭大小)。但是”git hash-object” 命令的結果並沒有相同的shal值。所以現在仍然有一些錯誤的位元組是我不知道的。這個檔案發生在C源程式程式碼,我希望我能注意到一些明顯的錯誤,但是我沒有成功,我甚至都編譯成功了!
我也試過和在資源庫相同路徑下其他版本的檔案作比較,希望有不一樣和不合理的部分。不幸運的是,這碰巧是唯一的修訂檔案,在這個資源庫中。所以我沒有任何東西可以作比較。
於是我採取不同的措施,猜測著衝突是由限制單個位元組引起的,我寫了交換每個位元組的程式,是這解壓縮得到結果。因為這個類檔案壓縮過後僅有10K,花了很多時間解壓出之後的結果是2.5M。
這是我用的程式:
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 |
#include <stdio.h> #include <unistd.h> #include <string.h> #include <signal.h> #include <zlib.h> static int try_zlib(unsigned char *buf, int len) { /* make this absurdly large so we don't have to loop */ static unsigned char out[1024*1024]; z_stream z; int ret; memset(&z, 0, sizeof(z)); inflateInit(&z); z.next_in = buf; z.avail_in = len; z.next_out = out; z.avail_out = sizeof(out); ret = inflate(&z, 0); inflateEnd(&z); return ret >= 0; } /* eye candy */ static int counter = 0; static void progress(int sig) { fprintf(stderr, "\r%d", counter); alarm(1); } int main(void) { /* oversized so we can read the whole buffer in */ unsigned char buf[1024*1024]; int len; unsigned i, j; signal(SIGALRM, progress); alarm(1); len = read(0, buf, sizeof(buf)); for (i = 0; i < len; i++) { unsigned char c = buf[i]; for (j = 0; j <= 0xff; j++) { buf[i] = j; counter++; if (try_zlib(buf, len)) printf("i=%d, j=%x\n", i, j); } buf[i] = c; } alarm(0); fprintf(stderr, "\n"); return 0; } |
編譯和執行的結果:
1 2 |
gcc -Wall -Werror -O3 munge.c -o munge -lz ./munge <zlib |
有一些錯誤出現(如果你在zlib的資訊頭中得到”no data”資訊,zlib認為它執行的很好:))。但是我中途得到了下面的資訊:
1 |
i=5642, j=c7 |
等到執行完結束,末尾獲得了更多的記錄(整理crc檔案匹配了我們損壞的檔案)。有一個很好的機會,在中間的記錄,就是原始碼的問題
在一個十六進位制編輯器中對位元組稍微做了一些調整,zlib解壓縮(毫無錯誤!),然後管道輸出”git hash-object”,報告了損壞檔案的shal值,成功了!
我修正packfile檔案本身:
1 2 3 |
chmod +w $pack printf '\xc7' | dd of=$pack bs=1 seek=51659518 conv=notrunc chmod -w $pack |
發現’\xc7’來自與替換我們的“munge”程式。把原來的物件偏移(51653873)匯出了偏移量51659518 。通過“munge”(5642)增加了代替部分的偏移量,然後增加了之前去掉的3位元組的git資訊頭。
最後,”git fsck” 清理乾淨。
關於衝突本身來說,我很幸運它確實是一個位元組。實際上,證明就是單獨的位。0xc7位元組發生衝突成為0xc5.所以推測由錯誤的硬體引起,或者物理射線。
緊接著,中止關注於解壓縮的輸出是錯的嗎?我本會一直看前面的部分這樣永遠不會發現它。下面是解壓縮後衝突資料的不同,真正的資料檔案:
1 2 |
- cp = strtok (arg, "+"); + cp = strtok (arg, "."); |
調整了一個位元組,最終仍然是有效的,可讀的C碰巧做了完全不同的事情!一次不同嘗試會造成幸運的日子,看看zlib的輸出可能確實有用,正如大多數隨機的改變也許就會破壞C程式碼。
但更重要的是,git的hash、檢驗和引起了在另外一個系統中,很容易不被檢測問題。這個結果仍然會編譯,但是可能就引起一個有趣的bug(歸咎於一些隨機性的提交)