一. Linux xxd -i功能
Linux系统xxd命令使用二进制或十六进制格式显示文件内容。若未指定outfile参数,则将结果显示在终端屏幕上;否则输出到outfile中。详细的用法可参考linux命令xxd。
本文主要关注xxd命令-i选项。使用该选项可输出以inputfile为名的C语言数组定义。例如,执行echo 12345 > test和xxd -i test命令后,输出为:
1
2
3
4
|
unsigned char test[] = {
0x31 , 0x32 , 0x33 , 0x34 , 0x35 , 0x0a
};
unsigned int test_len = 6 ;
|
可见,数组名即输入文件名(若有后缀名则点号替换为下划线)。注意,0x0a表示换行符LF,即'\n'。
二. xxd -i常见用途
当设备没有文件系统或不支持动态内存管理时,有时会将二进制文件(如引导程序和固件)内容存储在C代码静态数组内。此时,借助xxd命令就可自动生成版本数组。举例如下:
1) 使用Linux命令xdd将二进制文件VdslBooter.bin转换为16进制文件DslBooter.txt:
xxd -i < VdslBooter.bin > DslBooter.txt
其中,'-i'选项表示输出为C包含文件的风格(数组方式)。重定向符号'<'将VdslBooter.bin文件内容重定向到标准输入,该处理可剔除数组声明和长度变量定义,使输出仅包含16进制数值。
2) 在C代码源文件内定义相应的静态数组:
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文件的内容会自动展开到上述数组内。通过巧用#include预处理指令,可免去手工拷贝数组内容的麻烦。
三. 类xxd -i功能的Python实现
本节将使用Python2.7语言实现类似xxd -i的功能。
因为作者处于学习阶段,代码中存在许多写法不同但功能相同或相近的地方,旨在提供不同的语法参考,敬请谅解。
首先,请看一段短小却完整的程序(保存为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语言关键字
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新关键字
def IsCKeywords(name):
for x in CKeywords:
if cmp (x, name) = = 0 :
return True
return False
if __name__ = = '__main__' :
print IsCKeywords( 'const' )
#Xxdi()
|
这段代码判断给定的字符串是否为C语言关键字。在Windows系统cmd命令提示符下输入E:\PyTest>python xxdi.py,执行结果为True。
接下来的代码片段将省略头部的脚本和编码声明,以及尾部的'main'段。
生成C数组前,应确保数组名合法。C语言标识符只能由字母、数字和下划线组成,且不能以数字开头。此外,关键字不能用作标识符。所有,需要对非法字符做处理,其规则参见代码注释:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import re
def GenerateCArrayName(inFile):
#字母数字下划线以外的字符均转为下划线
#'int $=5;'的定义在Gcc 4.1.2可编译通过,但此处仍视为非法标识符
inFile = re.sub( '[^0-9a-zA-Z\_]' , '_' , inFile) #'_'改为''可剔除非法字符
#数字开头加双下划线
if inFile[ 0 ].isdigit() = = True :
inFile = '__' + inFile
#若输入文件名为C语言关键字,则将其大写并加下划线后缀作为数组名
#不能仅仅大写或加下划线前,否则易于用户自定义名冲突
if IsCKeywords(inFile) is True :
inFile = '%s_' % inFile.upper()
return inFile
|
以print GenerateCArrayName('1a$if1#1_4.txt')执行时,入参字符串将被转换为__1a_if1_1_4_txt。类似地,_Bool被转换为_BOOL_。
为了尽可能模拟Linux命令风格,还需提供命令行选项和参数。解析模块选用optionparser,其用法详见python命令行解析。类xxd -i功能的命令行实现如下:
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(...)原本是以下面的方式调用:
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等参数值。但这种写法非常别扭,改用缺省参数的函数定义方式,调用时只需要写ParseOption()即可。若读者知道更好的写法,望不吝赐教。
以-h选项调出命令提示,可见非常接近Linux风格:
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
|
基于上述练习,接着完成本文的重头戏:
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():
#解析命令行选项及参数
([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()
#将文件内容"打散"为字节数组
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
#构造数组定义头及长度变量
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语法,而作者调试所用的Linux系统仅装有Python2.4.3。因此,要在Linux系统中运行xddi.py,只能写为file = open(...。但这需要处理文件的关闭和异常,详见理解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
|
经过Windows和Linux系统双重检验后,Xddi()工作基本符合预期。以123456789ABCDEF.txt文件(内容为'123456789ABCDEF')为例,测试结果如下:
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 ;
|
再以稍大的二级制文件为例,执行 python xxdi.py VdslBooter.bin booter.c后,booter.c文件内容如下(截取首尾):
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 ;
|
综上可见,作者实现的xxdi模块与Linux xxd -i功能非常接近,且各有优劣。xxdi优点在于对数组名合法性校验更充分(关键字检查),数组内容表现形式更丰富(8进制和10进制);缺点在于不支持重定向,且数值宽度不固定(如0xb和0xff)。当然,这些缺点并不难消除。例如,用'0x%02x'%val代替hex(val)即可控制输出位宽。只是,再加完善难免提高代码复杂度,也许会事倍功半。
以上所述是小编给大家介绍的Python实现Linux命令xxd -i功能,希望对大家以上帮助!