Os dumps de memória revelam o estado registrado da memória de trabalho em um determinado momento da operação. Eles são ferramentas importantes na administração de ferramenta, porque fornecem provas "forenses" da condição do sistema.
Para as instruções e amostras de código deste artigo, eu usei o Python versão 2.4, que pode ser obtida por download a partir do site do Python (consulte Recursos abaixo). Talvez você obtenha resultados diferentes com outras versões.
Antes de começar, certifique-se de estar familiarizado com o seguinte:
- A implementação /dev/shm da memória compartilhada tradicional
- A visualização de dados de memória compartilhada manualmente em um sistema Linux
- Certas dependências (conceitos de abertura, leitura, gravação e fechamento de arquivos em Linux; uso do descritor de arquivo e dos modos em que o arquivo pode ser aberto; conceitos básicos de estrutura do Python)
- GNU/Linux, em geral
Entendendo os dumps de memória compartilhada no Linux
/dev/shm é uma implementação do conceito tradicional de memória compartilhada. É um meio amplamente usado e aceito de passar dados entre programas. Em /dev/shm, um programa ou daemon cria uma parte da memória que outros processos (em níveis relevantes de permissão) podem acessar. É um método fácil e rápido de compartilhar dados entre processos.
Cada programa cria o seu próprio arquivo; nos meus exemplos, eu uso o nome de arquivo dumpmem localizado em /dev/shm/dumpmem.
Visualizando o dump de memória compartilhada manualmente no Linux
Não é possível visualizar os arquivos de memória compartilhada (normalmente conhecidos como arquivos shm) usando o utilitário cat que geralmente é utilizado para exibir arquivos em Linux, já que esses arquivos shm estão em um formato binário. Se você tentar visualizá-los com os métodos genéricos de visualização de arquivos, eles parecerão um monte de caracteres distorcidos. Eu uso o utilitário hexdump) para ler os arquivos de memória e visualizá-los em um formato legível; há outros utilitários disponíveis para esse fim.
Para este artigo, o padrão de uso de hexdump
é assim:
hexdump <optional switches> /dev/shm/dumpmem for <switches> supported
Consulte Recursos para obter um link com mais informações sobre o hexdump.
O cenário com o qual trabalharemos é um farejador de rede que analisa os pacotes recebidos pelo host e armazena os dados em um arquivo de memória compartilhada, o /dev/shm/dumpmem. Esses dados contêm informações sobre o pacote recebido.
Geralmente o arquivo é assim:
- O armazenamento do arquivo de memória é /dev/shm/dumpmem
- O formato de arquivo dumpmem contém:
- 4 bytes de endereço de origem para notificar quem o enviou
- 4 bytes de endereço de destino para notificar para quem ele vai
- 2 bytes de porta de origem (em outras palavras, a porta na origem que o pacote usou)
- 2 bytes de porta de destino (da mesma forma, a porta no destino que o pacote usará)
- 2 bytes de protocolo (o protocolo do qual o pacote faz parte)
- 4 bytes de hora para indicar a indicação da hora em que o pacote foi visto pelo fragmento da rede
- 1 comprimento de registro = soma das especificações de dumpmem (ou seja, 18 bytes)
- O tamanho máximo do arquivo de memória é 1 KByte — portanto, ele pode conter 1024 bytes (1024 / 18 = 56 registros)
Se você usar o hexdump e exibir o arquivo manualmente em um terminal Linux, ele ficará assim:
Listagem 1. Exibindo um arquivo de dump
# 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 |
Vamos examinar as etapas envolvidas na análise do arquivo.
As etapas para entender os dados em um arquivo de dump de memória (identificar o formato, analisar e ler o arquivo) são relativamente simples:
- Abra o arquivo.
- Leia os bytes com o descritor de arquivos.
- Converta os dados para um formato legível de cadeia de caractere quando for necessário.
- Verifique se o buffer que foi lido está intacto e se tem truncamentos ou erros.
- Desempacote os dados do buffer.
- Extraia as informações.
- Imprima os dados.
- Desenvolva um loop para realizar as etapas de 1 a 7 em cada registro em um dump de dados compartilhados. (Você não quer fazer isso manualmente, não é?)
Vamos repassar o fluxo de processo com mais detalhes.
Para abrir um arquivo de memória compartilhada, use a forma geral fd = open(fileName, mode).
fd é o descritor de arquivo, um apontador para o arquivo. Para este exemplo, use o seguinte:
- fileName: /dev/shm/dumpmem
- mode: rb (somente leitura no modo binário)
Listagem 2. Abrindo um arquivo de memória compartilhada
fd = open('/dev/shm/dumpmem ','rb')
|
Para ler os bytes usando o descritor de arquivo obtido na chamada de função anterior, eu uso o código a seguir. Ele lê o número indicado de bytes a partir do parâmetro de arquivo que é passado:
Listagem 3. Abrindo um arquivo de memória compartilhada
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
|
Aqui, a leitura dos bytes não é suficiente para extrair as informações necessárias; ela retorna um buffer se a cadeia de caractere é lida. É necessário analisar e converter para um formato de cadeia de caractere inteligível.
O struct do Python pode ser usado para tratar os dados binários armazenados em arquivos ou a partir de conexões de rede, entre outras origens. O struct do Python tem duas funcionalidades amplas:
pack e unpack.
A tarefa de pack é retornar uma cadeia de caractere contendo os valores v1,
v2, ... empacotados de acordo com o formato dado. Os argumentos devem corresponder exatamente aos valores de que o formato precisa.
A função de unpack é desempacotar a cadeia de caractere (supostamente empacotada por pack(fmt, ...)) de acordo com o formato dado. O resultado é uma tupla, mesmo se ele contiver exatamente um item. A cadeia de caractere deve conter exatamente a quantidade de dados de que o formato precisa: len(string) deve ser igual a calcsize(fmt).
Os formatos aceitáveis:
- Formatos de 1 byte
- b para caractere assinado
- B para caractere não assinado
- Formatos de 2 bytes
- h para inteiro curto
- H para curto não assinado
- Formatos de 4 bytes:
- l para longo
- L para longo não assinado
- Formatos de 8 bytes:
- q para longo longo
- Q para longo longo não assinado
Para ver os outros formatos suportados para empacotar e desempacotar os bytes de buffer, consulte a literatura do Python listada em Recursos.
Para se certificar de que o buffer de 18 bytes que foi lido esteja intacto e não tenha truncamentos ou erros, pode-se usar a função calcsize para verificar se o tamanho de byte ainda é 18 conforme o esperado quando é lido. Pode-se usar a função asserts do Python para esse fim.
Listagem 4. Certificando-se de que o buffer está correto
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
|
Agora que você verificou que o buffer é realmente de 18 bytes, é possível
desempacotar os dados do buffer. struct fornece uma função unpack_from bastante útil que fornece o número de bytes, o nome do buffer e a compensação com a qual deve ser lido:
struct.unpack_from(fmt, buffer[, offset=0])
No nosso cenário, estes são os detalhes que queremos extrair:
Listagem 5. Detalhes a serem extraídos
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))
|
Observação: dependendo da plataforma e da estrutura da memória (big endian ou little endian) talvez seja necessário trocar a ordem de leitura dos dados.
Agora que você tem os valores desempacotados do buffer binário que você leu, pode usar os comandos padrão de print para obter a saída necessária.
Listagem 6. Imprimindo os detalhes
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))
|
A saída esperada da Listagem 6 deve ter este formato:
Listagem 7. Saída da impressão
sourceAddress = ((192,), (168,), (10,), (102,)) destinationAddress = ((207,), (168,), (1,), (103,)) sourcePort = (11299,) destinationPort = (11555,) protocolUsed = (256,) timeStamp = ((1,), (12,), (0,), (1,)) |
Automatize o processo para todos os registros
Agora, para ler e imprimir todos os registros de todo o arquivo de memória compartilhada, crie um loop:
Listagem 8. Criando um loop para ler e imprimir todos os registros
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))
|
E isso é tudo! Analisamos um formato conhecido de dump de memória binário no Linux e usamos struct do Python para ler o dump de dados binários e exibi-los em um formato legível.
Aprender
-
Saiba mais sobre o Python na Python Software Foundation.
- Aprenda a usar o utilitário hexdump para exibir arquivos especificados em um formato especificado pelo usuário.
- Procure mais artigos do developerWorks sobre o Python.
-
Na zona Linux do developerWorks,
você encontrará muitos artigos de instruções e tutoriais, bem como downloads, fóruns de discussão e muitos outros recursos para desenvolvedores e administradores Linux.
-
Fique por dentro dos eventos técnicos e webcasts do developerWorks com foco em uma variedade de produtos da IBM e tópicos do segmento de mercado de TI.
-
Participe de um briefing ao vivo e gratuito do developerWorks para se atualizar rapidamente sobre produtos e ferramentas IBM, bem como tendências do segmento de mercado de TI.
-
Acompanhe as Demos on demand do developerWorks
, que abrangem desde demos de instalação e configuração de produtos para iniciantes até funcionalidades avançadas para desenvolvedores experientes.
-
Siga o developerWorks no Twitter ou inscreva-se para receber tweets sobre Linux no developerWorks.
Obter produtos e tecnologias
-
Faça o download do Python a partir do Web site do Python.
-
Avalie produtos IBM
da maneira que for melhor para você: faça download da versão de teste de um produto, avalie um produto on-line, use-o em um ambiente de nuvem ou passe algumas horas na SOA Sandbox
aprendendo a implementar Arquitetura Orientada a Serviços de modo eficiente.
Discutir
-
Participe da comunidade My developerWorks. Entre em contato com outros usuários do developerWorks, enquanto explora os blogs, fóruns, grupos e wikis orientados ao desenvolvedor.
Asha Shivalingaiah é engenheira de software e trabalha como testadora no Australia Development Lab para a IBM Security Solution, Tivoli. Desde que passou a fazer parte da equipe IBM Rational em 2008, trabalha com Rational Rhapsody e outras ferramentas do portfólio Rational para gerenciar o ciclo de vida de desenvolvimento de software. É especialista em linguagens de modelagem como UML 2 e SysML e no uso do Rational Rhapsody para desenvolvimento baseado em modelo.