一. Linux xxd -i功能
Linux系統(tǒng)xxd命令使用二進制或十六進制格式顯示文件內(nèi)容。若未指定outfile參數(shù),則將結(jié)果顯示在終端屏幕上;否則輸出到outfile中。詳細的用法可參考linux命令xxd。
本文主要關(guān)注xxd命令-i選項。使用該選項可輸出以inputfile為名的C語言數(shù)組定義。例如,執(zhí)行echo 12345 > test和xxd -i test命令后,輸出為:
1
2
3
4
|
unsigned char test[] = { 0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x0a }; unsigned int test_len = 6 ; |
可見,數(shù)組名即輸入文件名(若有后綴名則點號替換為下劃線)。注意,0x0a表示換行符LF,即'\n'。
二. xxd -i常見用途
當(dāng)設(shè)備沒有文件系統(tǒng)或不支持動態(tài)內(nèi)存管理時,有時會將二進制文件(如引導(dǎo)程序和固件)內(nèi)容存儲在C代碼靜態(tài)數(shù)組內(nèi)。此時,借助xxd命令就可自動生成版本數(shù)組。舉例如下:
1) 使用Linux命令xdd將二進制文件VdslBooter.bin轉(zhuǎn)換為16進制文件DslBooter.txt:
xxd -i < VdslBooter.bin > DslBooter.txt
其中,'-i'選項表示輸出為C包含文件的風(fēng)格(數(shù)組方式)。重定向符號'<'將VdslBooter.bin文件內(nèi)容重定向到標(biāo)準(zhǔn)輸入,該處理可剔除數(shù)組聲明和長度變量定義,使輸出僅包含16進制數(shù)值。
2) 在C代碼源文件內(nèi)定義相應(yīng)的靜態(tài)數(shù)組:
1
2
3
4
5
6
7
|
static const uint8 bootImageArray[] = { #include " ../../DslBooter.txt" }; TargetImage bootImage = { (uint8 * ) bootImageArray, sizeof(bootImageArray) / sizeof(bootImageArray[ 0 ]) }; |
編譯源碼時,DslBooter.txt文件的內(nèi)容會自動展開到上述數(shù)組內(nèi)。通過巧用#include預(yù)處理指令,可免去手工拷貝數(shù)組內(nèi)容的麻煩。
三. 類xxd -i功能的Python實現(xiàn)
本節(jié)將使用Python2.7語言實現(xiàn)類似xxd -i的功能。
因為作者處于學(xué)習(xí)階段,代碼中存在許多寫法不同但功能相同或相近的地方,旨在提供不同的語法參考,敬請諒解。
首先,請看一段短小卻完整的程序(保存為xddi.py):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#!/usr/bin/python #coding=utf-8 #判斷是否C語言關(guān)鍵字 CKeywords = ( "auto" , "break" , "case" , "char" , "const" , "continue" , "default" , "do" , "double" , "else" , "enum" , "extern" , "float" , "for" , "goto" , "if" , "int" , "long" , "register" , "return" , "short" , "signed" , "static" , "sizeof" , "struct" , "switch" , "typedef" , "union" , "unsigned" , "void" , "volatile" , "while" , "_Bool" ) #_Bool為C99新關(guān)鍵字 def IsCKeywords(name): for x in CKeywords: if cmp (x, name) = = 0 : return True return False if __name__ = = '__main__' : print IsCKeywords( 'const' ) #Xxdi() |
這段代碼判斷給定的字符串是否為C語言關(guān)鍵字。在Windows系統(tǒng)cmd命令提示符下輸入E:\PyTest>python xxdi.py,執(zhí)行結(jié)果為True。
接下來的代碼片段將省略頭部的腳本和編碼聲明,以及尾部的'main'段。
生成C數(shù)組前,應(yīng)確保數(shù)組名合法。C語言標(biāo)識符只能由字母、數(shù)字和下劃線組成,且不能以數(shù)字開頭。此外,關(guān)鍵字不能用作標(biāo)識符。所有,需要對非法字符做處理,其規(guī)則參見代碼注釋:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import re def GenerateCArrayName(inFile): #字母數(shù)字下劃線以外的字符均轉(zhuǎn)為下劃線 #'int $=5;'的定義在Gcc 4.1.2可編譯通過,但此處仍視為非法標(biāo)識符 inFile = re.sub( '[^0-9a-zA-Z\_]' , '_' , inFile) #'_'改為''可剔除非法字符 #數(shù)字開頭加雙下劃線 if inFile[ 0 ].isdigit() = = True : inFile = '__' + inFile #若輸入文件名為C語言關(guān)鍵字,則將其大寫并加下劃線后綴作為數(shù)組名 #不能僅僅大寫或加下劃線前,否則易于用戶自定義名沖突 if IsCKeywords(inFile) is True : inFile = '%s_' % inFile.upper() return inFile |
以print GenerateCArrayName('1a$if1#1_4.txt')執(zhí)行時,入?yún)⒆址畬⒈晦D(zhuǎn)換為__1a_if1_1_4_txt。類似地,_Bool被轉(zhuǎn)換為_BOOL_。
為了盡可能模擬Linux命令風(fēng)格,還需提供命令行選項和參數(shù)。解析模塊選用optionparser,其用法詳見python命令行解析。類xxd -i功能的命令行實現(xiàn)如下:
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
|
#def ParseOption(base, cols, strip, inFile, outFile): def ParseOption(base = 16 , cols = 12 , strip = False , inFile = '', outFile = None ): from optparse import OptionParser custUsage = '\n xxdi(.py) [options] inFile [outFile]' parser = OptionParser(usage = custUsage) parser.add_option( '-b' , '--base' , dest = 'base' , help = 'represent values according to BASE(default:16)' ) parser.add_option( '-c' , '--column' , dest = 'col' , help = 'COL octets per line(default:12)' ) parser.add_option( '-s' , '--strip' , action = 'store_true' , dest = 'strip' , help = 'only output C array elements' ) (options, args) = parser.parse_args() if options.base is not None : base = int (options.base) if options.col is not None : cols = int (options.col) if options.strip is not None : strip = True if len (args) = = 0 : print 'No argument, at least one(inFile)!\nUsage:%s' % custUsage if len (args) > = 1 : inFile = args[ 0 ] if len (args) > = 2 : outFile = args[ 1 ] return ([base, cols, strip], [inFile, outFile]) |
被注釋掉的def ParseOption(...)原本是以下面的方式調(diào)用:
1
2
3
|
base = 16 ; cols = 12 ; strip = False ; inFile = ' '; outFile = ' ' ([base, cols, strip], [inFile, outFile]) = ParseOption(base, cols, strip, inFile, outFile) |
其意圖是同時修改base、cols、strip等參數(shù)值。但這種寫法非常別扭,改用缺省參數(shù)的函數(shù)定義方式,調(diào)用時只需要寫ParseOption()即可。若讀者知道更好的寫法,望不吝賜教。
以-h選項調(diào)出命令提示,可見非常接近Linux風(fēng)格:
1
2
3
4
5
6
7
8
|
E:\PyTest>python xxdi.py - h Usage: xxdi(.py) [options] inFile [outFile] Options: - h, - - help show this help message and exit - b BASE, - - base = BASE represent values according to BASE(default: 16 ) - c COL, - - column = COL COL octets per line(default: 12 ) - s, - - strip only output C array elements |
基于上述練習(xí),接著完成本文的重頭戲:
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
|
def Xxdi(): #解析命令行選項及參數(shù) ([base, cols, strip], [inFile, outFile]) = ParseOption() import os if os.path.isfile(inFile) is False : print ''''%s' is not a file!''' % inFile return with open (inFile, 'rb' ) as file : #必須以'b'模式訪問二進制文件 #file = open(inFile, 'rb') #Python2.5以下版本不支持with...as語法 #if True: #不用for line in file或readline(s),以免遇'0x0a'換行 content = file .read() #將文件內(nèi)容"打散"為字節(jié)數(shù)組 if base is 16 : #Hexadecimal content = map ( lambda x: hex ( ord (x)), content) elif base is 10 : #Decimal content = map ( lambda x: str ( ord (x)), content) elif base is 8 : #Octal content = map ( lambda x: oct ( ord (x)), content) else : print '[%s]: Invalid base or radix for C language!' % base return #構(gòu)造數(shù)組定義頭及長度變量 cArrayName = GenerateCArrayName(inFile) if strip is False : cArrayHeader = 'unsigned char %s[] = {' % cArrayName else : cArrayHeader = '' cArrayTailer = '};\nunsigned int %s_len = %d;' % (cArrayName, len (content)) if strip is True : cArrayTailer = '' #print會在每行輸出后自動換行 if outFile is None : print cArrayHeader for i in range ( 0 , len (content), cols): line = ', ' .join(content[i:i + cols]) print ' ' + line + ',' print cArrayTailer return with open (outFile, 'w' ) as file : #file = open(outFile, 'w') #Python2.5以下版本不支持with...as語法 #if True: file .write(cArrayHeader + '\n' ) for i in range ( 0 , len (content), cols): line = reduce ( lambda x,y: ', ' .join([x,y]), content[i:i + cols]) file .write( ' %s,\n' % line) file .flush() file .write(cArrayTailer) |
Python2.5以下版本不支持with...as語法,而作者調(diào)試所用的Linux系統(tǒng)僅裝有Python2.4.3。因此,要在Linux系統(tǒng)中運行xddi.py,只能寫為file = open(...。但這需要處理文件的關(guān)閉和異常,詳見理解Python中的with…as…語法。注意,Python2.5中使用with...as語法時需要聲明from __future__ import with_statement。
可通過platform.python_version()獲取Python版本號。例如:
1
2
3
4
5
6
7
8
|
import platform #判斷Python是否為major.minor及以上版本 def IsForwardPyVersion(major, minor): #python_version()返回'major.minor.patchlevel',如'2.7.11' ver = platform.python_version().split( '.' ) if int (ver[ 0 ]) > = major and int (ver[ 1 ]) > = minor: return True return False |
經(jīng)過Windows和Linux系統(tǒng)雙重檢驗后,Xddi()工作基本符合預(yù)期。以123456789ABCDEF.txt文件(內(nèi)容為'123456789ABCDEF')為例,測試結(jié)果如下:
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
|
E:\PyTest>python xxdi.py - c 5 - b 2 - s 123456789ABCDEF .txt [ 2 ]: Invalid base or radix for C language! E:\Pytest>python xxdi.py - c 5 - b 10 - s 123456789ABCDEF .txt 49 , 50 , 51 , 52 , 53 , 54 , 55 , 56 , 57 , 65 , 66 , 67 , 68 , 69 , 70 , E:\PyTest>python xxdi.py - c 5 - b 10 123456789ABCDEF .txt unsigned char __123456789ABCDEF_txt[] = { 49 , 50 , 51 , 52 , 53 , 54 , 55 , 56 , 57 , 65 , 66 , 67 , 68 , 69 , 70 , }; unsigned int __123456789ABCDEF_txt_len = 15 ; E:\PyTest>python xxdi.py - c 5 - b 8 123456789ABCDEF .txt unsigned char __123456789ABCDEF_txt[] = { 061 , 062 , 063 , 064 , 065 , 066 , 067 , 070 , 071 , 0101 , 0102 , 0103 , 0104 , 0105 , 0106 , }; unsigned int __123456789ABCDEF_txt_len = 15 ; E:\PyTest>python xxdi.py 123456789ABCDEF .txt unsigned char __123456789ABCDEF_txt[] = { 0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x36 , 0x37 , 0x38 , 0x39 , 0x41 , 0x42 , 0x43 , 0x44 , 0x45 , 0x46 , }; unsigned int __123456789ABCDEF_txt_len = 15 ; |
再以稍大的二級制文件為例,執(zhí)行 python xxdi.py VdslBooter.bin booter.c后,booter.c文件內(nèi)容如下(截取首尾):
1
2
3
4
5
6
7
|
unsigned char VdslBooter_bin[] = { 0xff , 0x31 , 0x0 , 0xb , 0xff , 0x3 , 0x1f , 0x5a , 0x0 , 0x0 , 0x0 , 0x0 , / / ... ... ... ... 0x0 , 0x0 , 0x0 , 0x0 , 0xff , 0xff , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , 0x0 , }; unsigned int VdslBooter_bin_len = 53588 ; |
綜上可見,作者實現(xiàn)的xxdi模塊與Linux xxd -i功能非常接近,且各有優(yōu)劣。xxdi優(yōu)點在于對數(shù)組名合法性校驗更充分(關(guān)鍵字檢查),數(shù)組內(nèi)容表現(xiàn)形式更豐富(8進制和10進制);缺點在于不支持重定向,且數(shù)值寬度不固定(如0xb和0xff)。當(dāng)然,這些缺點并不難消除。例如,用'0x%02x'%val代替hex(val)即可控制輸出位寬。只是,再加完善難免提高代碼復(fù)雜度,也許會事倍功半。
以上所述是小編給大家介紹的Python實現(xiàn)Linux命令xxd -i功能,希望對大家以上幫助!