为解析共享内存转储构建一个 Python 应用程序

使用 Struct 实用工具提取系统数据进行分析

学习如何在 Linux 平台上解析机器可读的共享内存转储以及如何使用 Python 和 struct 实用工具提取预期的数据格式。在本文中,您首先将学习如何通过读取转储文件的二进制文件格式来决定数据的格式;解析、提取和分析数据时会用到这方面的知识。接下来,您将学习如何基于格式解析文件,然后将结果与预期的格式进行比较从而输出一个校验结果 。

Asha Shivalingaiah, 软件工程师, IBM

Asha Shivalingaiah 是一名软件工程师,在位于 Tivoli 的 IBM Security Solution 的 Australia Development Lab 负责测试工作。自从 2008 年加入 IBM Rational 团队以来,就致力于使用 Rational Rhapsody 和其他 Rational 组合工具来管理软件开发生命周期。她精通建模语言,例如 UML 2 和 SysML,同时还可以熟练地使用 Rational Rhapsody 来进行模块驱的开发。



2011 年 7 月 04 日

内存转储 呈现运行过程中某一点上 Working Memory 的记录状态。这是系统管理中的一个重要工具,因为他们提供系统状况的 “可靠” 证据。

开始之前

我使用 Python 2.4 版本编写本文中的指导和代码示例,您可以从 Python 网站上下载(见 参考资料 )。使用其他版本,您可能会得到与此不同的结果。

在您开始之前,请确定您熟悉以下内容:

  • 传统共享内存的 /dev/shm 实现
  • 在 Linux 系统上手工查看共享内存数据转储
  • 一定的依赖关系(Linux 文件打开、读取、写入以及关闭的概念;使用文件描述符和可以用来打开文件的模式;基本的 Python 结构的概念)
  • 通用的 GNU/Linux

了解 Linux 系统中的共享内容转储

/dev/shm 是传统共享内容概念的实现。这是被广泛使用和接受的在程序间传递数据的方法。在 /dev/shm 中,一个程序或守护程序创建其他进程(具有相关的权限级别)可以访问的内存区。这是一个快捷方便的进程间共享数据的方法。

每个程序都会创建自己的文件;在文本的示例中,我使用的文件名为 dumpmem,位于 /dev/shm/dumpmem。

在 Linux 系统上手工查看共享内存转储

在 Linux 系统中,您不能通过使用通常用于文件显示的 cat 实用工具来查看共享内存文件(常指的是 shm 文件),因为这些 shm 文件是以二进制的格式存储的。如果您试图使用一般的文件查看方法来查看这些文件,它们看起来就像一堆混乱的字符。我使用 hexdump 实用工具来读取 mem 文件并且以可阅读的格式来查看它们;也可以用其他实用工具达到此目的。

对于本文,hexdump 的用法模式看起来像:

hexdump <optional switches> /dev/shm/dumpmem for <switches> supported

请参阅 参考资料 获取关于 hexdump 的更多信息。


定义场景

我们将使用的场景是一个网络嗅探器,它可以分析主机接收到的数据包,并且将数据存储在一个共享 mem 文件(/dev/shm/dumpmem)中,数据包含有关已接收数据包的信息。

通常,该文件像这样:

  • 内存文件存储是 /dev/shm/dumpmem
  • dumpmem 文件格式包含:
    • 4 个字节的源地址表示发送方
    • 4 个字节的目标地址表示接收方
    • 2 个字节的源端口(换句话说,即数据包所使用的源地址上的端口)
    • 2 个字节的目标端口(类似于,数据包将要使用的目标地址上的端口)
    • 2 个字节的协议(数据包是协议的一个组成部分)
    • 4 个字节的时间,表示网络片段发现数据包的时间戳
  • 1 个记录长度 = dumpmem 规格的总和(18 个字节)
  • 内存文件的最大值是 1KB,所以它可以包含 1024 个字节 (1024 / 18 = 56 条记录)

如果您使用 hexdump 工具在 Linux 终端上手工显示文件,那么它看起来如下所示:

清单 1. 显示一个转储文件
# hexdump /dev/shm/fdshm
0000000 0004 0000 0400 0000 fc64 0a00 00fb e000
0000010 14e9 14e9 0011 0000 0000 0000 0000 0000
0000020 0000 0000 0000 0000 0000 0000 0000 0800
0000030 1668 0000 0000 0000 0032 0000 0000 0000
0000040 0000 0000 0001 e000 0000 0000 0002 0000
0000050 0000 0000 0000 0000 0000 0000 0000 0000
0000060 0000 0000 0000 0800 0100 0000 0000 0000
0000070 0008 0000 0000 0000 fc64 0a00 fd64 0a00
0000080 2328 03ea 0006 0000 0000 0000 0000 0000
0000090 0000 0000 0000 0000 0000 0000 0000 0800
00000a0 7700 0001 0000 0000 0040 0000 0000 0000
00000b0 fd64 0a00 fc64 0a00 03ea 2328 0006 0000
00000c0 0000 0000 0000 0000 0000 0000 0000 0000
00000d0 0000 0000 0000 0800 0a00 0000 0000 0000
00000e0 0040 0000 0000 0000 fc64 0a00 fd64 0a00
00000f0 2328 03ec 0006 0000 0000 0000 0000 0000
0000100 0000 0000 0000 0000 0000 0000 0000 0800
0000110 7700 0001 0000 0000 0040 0000 0000 0000

让我们看看解析文件所涉及的步骤。


解析转储文件

理解内存转储文件中的数据(识别格式、解析和读取文件)的步骤相对比较简单:

  1. 打开文件。
  2. 使用文件描述符读取字节。
  3. 需要时将数据转换为某种可阅读的字符串格式。
  4. 验证被读取的缓冲区是否完整,是否有截断或者错误。
  5. 从缓冲区解压数据。
  6. 提取信息。
  7. 打印数据。
  8. 建立一个循环,为共享数据转储的每条记录都执行步骤 1 至步骤 7。 (您并不想手工执行这些操作,是不是?)

让我们更详细地学习此流程。

打开文件

要打开一个共享内存文件,需要使用通用格式 fd = open(fileName, mode)fd 是文件描述符,是指向文件的一个指针。例如,使用:

  • 文件名:/dev/shm/dumpmem
  • 模式:rb (仅以二进制模式读取)
清单 2. 打开共享内存文件
fd = open('/dev/shm/dumpmem ','rb')

读取字节

使用文件描述符读取在之前的功能函数中获得的字节,我使用以下代码。它从文件参数传递过程中读取字节数:

清单 3. 打开共享内存文件
def ReadBytes(self, fd, noBytes):
 '''
 Read file and return the number of bytes as specified
 '''
 data = fd.read(noBytes)
 return data

buffer = ReadBytes('/dev/shm/dumpmem ', 18)
# Pass the file name and pass the number of bytes
# Number of bytes is 18 since in the example scenario each record
#  is of length 18

此处,读取字节还不足以提取必要的信息,如果要读取字符串,则要返回一个缓冲区。需要将缓冲区解析并转换为可理解的字符串格式。

转换数据

Python 的 struct 实用工具可用来处理存储在文件中或者来自其他网络连接的、以及其他来源的二进制数据。Python 的 struct 有两个广泛的功能: packunpack

Pack 的任务是返回一个包含根据给定格式对 v1v2... 的值进行压缩的字符串。参数必须准确匹配格式所需的值。

Unpack 的任务是根据给定的格式对字符串进行拆包(假设由 pack(fmt, ...) 进行包装)。结果是一个数组(即使仅包含一个项目)。字符串必须含有格式所需的确切数据量:len(string) 必须等于 calcsize(fmt)

可接受的格式:

  • 1-byte 格式:
    • b 表示 signed char
    • B 表示 unsigned char
  • 2-byte 格式:
    • h 表示 short integer
    • H 表示 unsigned short
  • 4-byte 格式:
    • l 表示 long
    • L 表示 unsigned long
  • 8-byte 格式:
    • q 表示 long long
    • Q 表示 unsigned long long

对于打包和拆包缓冲区字节所支持的其他格式,请参阅 参考资料 中列出的 Python 文献。

验证缓冲区

要验证已读取的 18 个字节的缓冲区是否完整,验证该缓冲区没有任何截断或错误,您可以使用 calcsize 函数进行检查,如果在读取时字节大小仍为预期的 18 个字节。您可以使用 Python 的 assert 函数。

清单 4. 验证缓冲区是否正确
self.assertEqual(len(buffer), struct.calcsize('llllh'))

# 4 l's is 4*4 bytes = 16 bytes and h is 2 bytes so that is 18 bytes
# we could use QQh which is 2*8 + 2 = 18 bytes as well

拆包数据

现在您已经验证了缓冲区确实是 18 个字节,那么您 可以从缓冲区对数据进行拆包。Struct 提供一个有用的 unpack_from 函数,该函数提供字节数、缓冲区名称以及需要被读取的偏移量:

struct.unpack_from(fmt, buffer[, offset=0])

提取详细信息

在我们的场景中,以下是我们想要提取的详细信息:

清单 5. 要提取的详细信息
sourceAddress = (struct.unpack_from('B', buffer,0),
                     struct.unpack_from('B', buffer,1),
                     struct.unpack_from('B', buffer,2),
                     struct.unpack_from('B', buffer,3))
destinationAddress = (struct.unpack_from('B', buffer,4),
                     struct.unpack_from('B', buffer,5),
                     struct.unpack_from('B', buffer,6),
                     struct.unpack_from('B', buffer,7))
sourcePort = (struct.unpack_from('B', buffer,8),
                     struct.unpack_from('B', buffer,9))
destinationPort = (struct.unpack_from('B', buffer,10),
                     struct.unpack_from('B', buffer,11))
protocolUsed = (struct.unpack_from('B', buffer,12),
                     struct.unpack_from('B', buffer,13))
timeStamp = (struct.unpack_from('B', buffer,14),
                     struct.unpack_from('B', buffer,15),
                     struct.unpack_from('B', buffer,16),
                     struct.unpack_from('B', buffer,17))

注意:根据不同平台,以及 men 结构是大端字节序还是小端字节序,您可能需要交换读取字节的顺序。

打印输出

现在您已从读取的二进制缓冲区获得了拆包的值,那么您可以使用标准的 print 命令来获得必要的输出。

清单 6. 打印详细信息
print "sourceAddress =" ,  
      (struct.unpack_from('B', buffer,0),struct.unpack_from('B', buffer,1),
      struct.unpack_from('B', buffer,2),struct.unpack_from('B', buffer,3))
print "destinationAddress = " ,
      (struct.unpack_from('B', buffer,4),struct.unpack_from('B', buffer,5),
      struct.unpack_from('B', buffer,6),struct.unpack_from('B', buffer,7))
print "sourcePort = " , (struct.unpack_from('H',buffer,8))
print "destinationPort = " , (struct.unpack_from('H',buffer,10))
print "protocolUsed = " , (struct.unpack_from('H',buffer,12))
print "timeStamp = " ,  
      (struct.unpack_from('B', buffer,14),struct.unpack_from('B', buffer,15),
      struct.unpack_from('B', buffer,16),struct.unpack_from('B', buffer,17))

清单 6 的预期输出应该如以下格式:

清单 7. 打印出的输出
sourceAddress =  ((192,), (168,), (10,), (102,))
destinationAddress =  ((207,), (168,), (1,), (103,))
sourcePort =  (11299,)
destinationPort =  (11555,)
protocolUsed =  (256,)
timeStamp =  ((1,), (12,), (0,), (1,))

自动化所有记录的这个流程

现在,要从全部共享内存文件读取和打印所有记录,创建一个循环:

清单 8. 创建一个循环读取和打印所有记录
for element in range (0,56):
#loop 18 since we know the file size and
#the record length: 1024/18 = 56 records
		
      buffer = ReadBytes('/dev/shm/dumpmem ', 18)
      self.assertEqual(len(buffer), struct.calcsize('llllh'))
        
      sourceAddress = struct.unpack_from('B', buffer,0),
                  struct.unpack_from('B', buffer,1),
                  struct.unpack_from('B', buffer,2),
                  struct.unpack_from('B', buffer,3))
      destinationAddress = struct.unpack_from('B', buffer,4),
                       struct.unpack_from('B', buffer,5),
                       struct.unpack_from('B', buffer,6),
                       struct.unpack_from('B', buffer,7))
      sourcePort = struct.unpack_from('B', buffer,8),
                 struct.unpack_from('B', buffer,9)
      destinationPort = struct.unpack_from('B', buffer,10),
                    struct.unpack_from('B', buffer,11))
      protocolUsed = ,struct.unpack_from('B', buffer,12),
                  struct.unpack_from('B', buffer,13))
      timeStamp = struct.unpack_from('B', buffer,14),
                struct.unpack_from('B', buffer,15),
                struct.unpack_from('B', buffer,16),
                struct.unpack_from('B', buffer,17))
        
      print "sourceAddress = " ,  
            struct.unpack_from('B', buffer,0),
            struct.unpack_from('B', buffer,1),
            struct.unpack_from('B', buffer,2),
            struct.unpack_from('B', buffer,3))
      print "destinationAddress =  " ,
            struct.unpack_from('B', buffer,4),
            struct.unpack_from('B', buffer,5),
            struct.unpack_from('B', buffer,6),
            struct.unpack_from('B', buffer,7))
      print "sourcePort =  " ,
            struct.unpack_from('H',buffer,8))
      print "destinationPort =  " ,
            struct.unpack_from('H',buffer,10))
      print "protocolUsed =  " ,
            struct.unpack_from('H',buffer,12))
      print "timeStamp = " ,  
            struct.unpack_from('B', buffer,14),
            struct.unpack_from('B', buffer,15),
            struct.unpack_from('B', buffer,16),
            struct.unpack_from('B', buffer,17))

这就是这么简单!我们解析了 Linux 系统中一个众所周知的二进制 mem 转储格式,使用了 Python 中的 struct 实用工具来读取二进制数据转储并以可阅读的格式进行显示。

参考资料

学习

获得产品和技术

  • 从 Python 站点 下载 Python
  • 以最适合您的方式 评估 IBM 产品:下载产品的试用版本,在线试用产品,在云环境中使用产品,或者在 SOA Sandbox 中花费几个小时来学习如何高效实现面向服务架构。

讨论

  • 加入 developerWorks 中文社区,developerWorks 社区是一个面向全球 IT 专业人员,可以提供博客、书签、wiki、群组、联系、共享和协作等社区功能的专业社交网络社区。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Linux
ArticleID=697111
ArticleTitle=为解析共享内存转储构建一个 Python 应用程序
publish-date=07042011