Разработка Python-приложения для анализа дампов общей памяти

Извлечение системных данных с помощью утилиты struct для дальнейшего анализа

В этой статье рассказывается, как анализировать необработанные дампы общей памяти на платформе Linux и извлекать данные в необходимом формате с помощью Python и утилиты struct. Сначала рассматривается, как определить формат данных путем считывания формата из бинарного файла дампа памяти, так как эта информация потребуется для анализа, извлечения и изучения данных. Далее будет показано, как "разобрать" файл в зависимости от формата, сравнить полученные данные с ожидаемым форматом и вывести окончательный результат проверки.

Обновлено: В разделе Материалы для скачивания можно найти работающее Python-приложение и файл с дампом, который можно использовать или модифицировать для собственных задач. Название файла дампа, используемого в статье, было изменено для соответствия имени файла, доступного для загрузки. Примечание редактора.

Аша Шивалинга, разработчик ПО, IBM

Аша Шивалинга (Asha Shivalingaiah) – разработчик, тестирующий ПО в Australia Development Lab для IBM Security Solution, Tivoli. С момента присоединения к команде IBM Rational в 2008, она работала с Rational Rhapsody и другими инструментами для управления жизненным циклом ПО семейства Rational. Она обладает глубоким опытом в языках моделирования, таких как UML 2 и SysML, а также в использовании Rational Rhapsody для разработки ПО с помощью MDD (model-driven development).



16.02.2012

Под понятием дамп памяти подразумевается содержимое рабочей памяти, зафиксированное в определенный момент выполнения какой-либо операции. Это один из наиболее важных инструментов системного администратора, так как он предоставляет достоверные «улики» о состоянии системы.

До начала работы

Для всех инструкций и примеров кода, а также кода, доступного в разделе Материалы для скачивания этой статьи, использовалась среда Python версии 2.4, которую можно загрузить с Web-сайта Python (см. ссылку в разделе Ресурсы). При использовании других версий могут быть получены другие результаты.

Для успешного усвоения материала, представленного в статье, потребуются:

  • знание о стандартной реализации общей памяти - /dev/shm;
  • умение просматривать дампы общей памяти в Linux системе вручную;
  • определенные знания по Linux (открытие, чтение/запись и закрытие файла, использование дескриптора файла и различные режимы открытия файла) и Python (стандартные структуры);
  • общие знания по GNU/Linux.

Основы работы с дампами общей памяти в Linux

/dev/shm – это реализация стандартной модели общей памяти. Она широко используется и предоставляет средства для передачи данных между программами. В /dev/shm программа или демон может создать фрагмент памяти, к которому смогут получить доступ другие процессы с соответствующим уровнем полномочий. Это простой и быстрый способ обмена данными между процессами.

Каждая программа создает свой собственный файл, в представленных примерах будет использоваться файл devmem, расположенный в каталоге /dev/shm/devmem.

Просмотр дампов общей памяти на платформе Linux в ручном режиме

Так как shm-файлы с дампами общей памяти записаны в бинарном формате, их нельзя просмотреть с помощью утилиты cat, которая обычно используется для просмотра файлов в Linux. Если попытаться просмотреть такой файл каким-нибудь стандартным способом, то он будет выглядеть как фрагмент перемешанного текста. В этой статье для чтения и просмотра shm-файлов будет использоваться утилита hexdump, но существуют и другие утилиты для этой цели.

Ниже приведен пример запуска утилиты hexdump для просмотра файла devmem:

hexdump <различные поддерживаемые опции> /dev/shm/devmem

В разделе Ресурсы можно найти ссылки на материалы с дополнительной информацией по hexdump.


Описание примера

В данной статье будет рассматриваться пример с сетевым "сниффером" (sniffer, программой для анализа трафика), которая анализирует пакеты, полученные хостом, и хранит данные в общем mem-файле (/dev/shm/devmem). В этих данных хранится информация о полученных пакетах.

Обычно подобный файл, выглядит следующим образом:

  • файл памяти хранится в /dev/shm/devmem;
  • в файле devmem содержится следующая структура данных:
    • 4 байта - адрес отправителя, чтобы определить, кто отправил пакет;
    • 4 байта - адрес получателя, чтобы определить, кому предназначался пакет;
    • 2 байта – порт отправки (порт, использовавшийся на стороне отправителя);
    • 2 байта – порт получения (порт, который будет использоваться на стороне получателя);
    • 2 байта – идентификатор протокола, к которому принадлежит пакет;
    • 4 байта – временная метка, когда пакет был обнаружен во фрагменте сети;
  • длина одной записи в файле равна сумме составляющих структуры devmem (в данном случае 18 байт);
  • максимальный размер файла памяти составляет 1 КБ, так что он может содержать 1024 байта или 1024 / 18 = 56 записей.

Если запустить hexdump и вывести содержимое файла в консоль Linux, то будет распечатана следующая информация:

Листинг 1. Просмотр файла дампа
# hexdump /dev/shm/devmem
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 – это дескриптор файла, указывающий на него. В данном примере используются:

  • fileName (имя файла): /dev/shm/devmem;
  • mode (режим открытия): rb (только для чтения в бинарном режиме);
Листинг 2. Открытие файла общей памяти
fd = open('/dev/shm/devmem ','rb')

Считывание байтов

Для чтения байтов используется файловый дескриптор, полученный на предыдущем этапе. Инструкция, приведенная в листинге 3, считывает указанное число байт из файла, также передаваемого в качестве параметра.

Листинг 3. Считывание информации из файла общей памяти
def ReadBytes(self, fd, noBytes):
 '''
 считать файл и возвратить требуемое число байт
 '''
 data = fd.read(noBytes)
 return data

buffer = ReadBytes('/dev/shm/devmem ', 18)
# в метод передается имя файла и количество байт, которых необходимо считать
# количество байт равно 18, так как длина записи в примере также равна 18 байтам

Но для извлечения необходимой информации недостаточно просто считать байты, так как в случае считывания строки из метода будет возвращен байтовый буфер. Это буфер необходимо "разобрать" и преобразовать в строковый формат, удобный для восприятия.

Преобразование данных

Объекты struct, доступные в языке Python, могут использоваться для обработки бинарных данных, хранящихся в файлах, полученных из сетевых подключений или других источников. Объект struct обладает следующими ключевыми методами: pack и unpack.

Задача pack вернуть строку, содержащую значения v1, v2 и т.д., упакованные в определенном порядке. Передаваемые аргументы должны полностью соответствовать значениям, объявленным в формате.

Назначение unpack – "развернуть" строку (возможно упакованную методом pack(fmt, …)) согласно указанному формату. Результатом работы unpack всегда будет кортеж, даже если в строке содержался единственный элемент. Длина строки должна в точности соответствовать количеству данных, объявленных в формате; функция len(string) должна возвращать такое же значение, как и функция calcsize(fmt).

Допустимые форматы:

  • 1 байтные форматы:
    • b для signed char
    • B для unsigned char
  • 2-х байтные форматы:
    • h для short integer
    • H для unsigned short
  • 4-х байтные форматы:
    • l для long
    • L для unsigned long
  • 8 байтные форматы:
    • q для long long
    • Q для unsigned long long

Список других форматов, поддерживаемых при упаковке и распаковке буферов байтов, можно найти в литературе по Python, приведенной в разделе Ресурсы.

Проверка буфера

Чтобы убедиться, что считанный буфер из 18 байт не поврежден и не содержит пропусков или ошибок, можно воспользоваться функцией calcsize. Так можно проверить, что количество байт равно 18, как, и ожидалось при считывании. Для этого можно применить функцию assert, имеющуюся в Python.

Листинг 4. Проверка корректности содержимого буфера
self.assertEqual(len(buffer), struct.calcsize('llllh'))

# 4 символа l – это 4*4 байта = 16 байт, а h – это 2 байта, что в сумме дает 18
# также можно использовать QQh - 2*8 + 2 = 18 байт

Распаковка данных

После проверки, что буфер действительно содержит 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))

Примечание: В зависимости от используемой платформы, и, следовательно, от того является ли структура mem big endian (с обратным порядоком байтов) или little endian (с прямым порядком байтов), может потребоваться инвертировать порядок считывания байтов.

Распечатка результатов работы

После того как распакованные значения были извлечены из бинарного буфера, их можно вывести в стандартный поток вывода с помощью команды 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):
#цикл должен отработать 56 раз, так как известен размер файла - 1024 байта
#и длина записи - 18 байт: 1024/18 = 56 записей
		
      buffer = ReadBytes('/dev/shm/devmem ', 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 разобрать файл общей памяти и использовать объект struct для считывания бинарных данных из дампа и отображения их в формате, удобном для восприятия.


Загрузка

ОписаниеИмяРазмер
Python-приложение для анализа дампа памятиParseBinaryInPython.zip6KB

Ресурсы

Научиться

Получить продукты и технологии

  • Загрузить Python с официального Web-сайта Python.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Linux
ArticleID=794049
ArticleTitle=Разработка Python-приложения для анализа дампов общей памяти
publish-date=02162012