zoukankan      html  css  js  c++  java
  • Python实现Linux命令xxd -i功能

    Python实现Linux命令xxd -i功能

    标签: Python xxd


    声明

    本文同时也发布于作业部落,阅读体验可能更好。

    一. Linux xxd -i功能

    Linux系统xxd命令使用二进制或十六进制格式显示文件内容。若未指定outfile参数,则将结果显示在终端屏幕上;否则输出到outfile中。详细的用法可参考linux命令xxd

    本文主要关注xxd命令-i选项。使用该选项可输出以inputfile为名的C语言数组定义。例如,执行echo 12345 > testxxd -i test命令后,输出为:

    unsigned char test[] = {
      0x31, 0x32, 0x33, 0x34, 0x35, 0x0a
    };
    unsigned int test_len = 6;
    

    可见,数组名即输入文件名(若有后缀名则点号替换为下划线)。注意,0x0a表示换行符LF,即' '。

    二. xxd -i常见用途

    当设备没有文件系统或不支持动态内存管理时,有时会将二进制文件(如引导程序和固件)内容存储在C代码静态数组内。此时,借助xxd命令就可自动生成版本数组。举例如下:

    1. 使用Linux命令xdd将二进制文件VdslBooter.bin转换为16进制文件DslBooter.txt:
    xxd -i < VdslBooter.bin > DslBooter.txt
    

    其中,'-i'选项表示输出为C包含文件的风格(数组方式)。重定向符号'<'将VdslBooter.bin文件内容重定向到标准输入,该处理可剔除数组声明和长度变量定义,使输出仅包含16进制数值。

    1. 在C代码源文件内定义相应的静态数组:
    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):

    #!/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语言标识符只能由字母、数字和下划线组成,且不能以数字开头。此外,关键字不能用作标识符。所有,需要对非法字符做处理,其规则参见代码注释:

    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功能的命令行实现如下:

    #def ParseOption(base, cols, strip, inFile, outFile):
    def ParseOption(base = 16, cols = 12, strip = False, inFile = '', outFile = None):
        from optparse import OptionParser
        custUsage = '
      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)!
    Usage:%s' %custUsage
        if len(args) >= 1:
            inFile = args[0]
        if len(args) >= 2:
            outFile = args[1]
    
        return ([base, cols, strip], [inFile, outFile])
    

    被注释掉的def ParseOption(...)原本是以下面的方式调用:

    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风格:

    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
    

    基于上述练习,接着完成本文的重头戏:

    def Xxdi():
        #解析命令行选项及参数
        ([base, cols, strip], [inFile, outFile]) = ParseOption()
    
        import os
        if inFile is '':
            return
        elif 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 = '};
    unsigned 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 + '
    ')
            for i in range(0, len(content), cols):
                line = reduce(lambda x,y: ', '.join([x,y]), content[i:i+cols])
                file.write('    %s,
    ' %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版本号。例如:

    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')为例,测试结果如下:

    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文件内容如下(截取首尾):

    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)即可控制输出位宽。只是,再加完善难免提高代码复杂度,也许会事倍功半。

  • 相关阅读:
    服务器上往Mongo导入json文件里的数据
    在Flask蓝图中使用动态URL前缀
    python 的 optparser库
    lowB 三人组
    Linux基础介绍
    html的q标签、blockquote标签
    单冒号伪元素和双冒号伪元素
    CSS中的伪元素选择器
    CSS中的选择器
    CSS中的关系选择器
  • 原文地址:https://www.cnblogs.com/clover-toeic/p/5244318.html
Copyright © 2011-2022 走看看