Determinar se um processo específico é de 32 ou 64 bits

14

Dado um kernel Linux 2.6.x ou mais recente e a área de usuário existente capaz de executar os binários ELF32 e ELF64 (ou seja, bem passado Como sei que minha CPU suporta sistemas operacionais de 64 bits no Linux? ), Como posso determinar se um determinado processo ( PID) está sendo executado no modo de 32 ou 64 bits?

A solução ingênua seria executar:

file -L /proc/pid/exe | grep -o 'ELF ..-bit [LM]SB'

mas essas informações são expostas diretamente, /procsem depender libmagic?

Flexo
fonte

Respostas:

21

Se você deseja limitar-se à detecção de ELF, pode ler o cabeçalho ELF de /proc/$PID/exesi mesmo. É bastante trivial: se o quinto byte do arquivo for 1, é um binário de 32 bits. Se for 2, é de 64 bits. Para verificação de sanidade adicional:

  1. Se os primeiros 5 bytes forem 0x7f, "ELF", 1: é um binário ELF de 32 bits.
  2. Se os primeiros 5 bytes forem 0x7f, "ELF", 2: é um binário ELF de 64 bits.
  3. Caso contrário: é inconclusivo.

Você também pode usar objdump, mas isso tira a sua libmagicdependência e a substitui por uma libelf.

Outra maneira : você também pode analisar o /proc/$PID/auxvarquivo. De acordo com proc(5):

Ele contém o conteúdo das informações do interpretador ELF passadas para o processo no momento da execução. O formato é um ID longo não assinado mais um valor longo não assinado para cada entrada. A última entrada contém dois zeros.

Os significados das unsigned longteclas estão em /usr/include/linux/auxvec.h. Você quer AT_PLATFORM, o que é 0x00000f. Não me cite, mas parece que o valor deve ser interpretado como um char *para obter a descrição da plataforma da string.

Você pode achar útil essa pergunta sobre StackOverflow .

Outra maneira : você pode instruir o vinculador dinâmico ( man ld) a despejar informações sobre o executável. Ele imprime na saída padrão a estrutura AUXV decodificada. Aviso: isso é um hack, mas funciona.

LD_SHOW_AUXV=1 ldd /proc/$SOME_PID/exe | grep AT_PLATFORM | tail -1

Isso mostrará algo como:

AT_PLATFORM:     x86_64

Eu tentei em um binário de 32 bits e consegui i686.

Como isso funciona: LD_SHOW_AUXV=1instrui o Dynamic Linker a despejar a estrutura AUXV decodificada antes de executar o executável. A menos que você realmente goste de tornar sua vida interessante, você deseja evitar executar o referido executável. Uma maneira de carregá-lo e vinculá-lo dinamicamente sem realmente chamar sua main()função é executá ldd(1)-lo. A desvantagem: LD_SHOW_AUXVé ativada pelo shell, para que você obtenha despejos das estruturas AUXV para: o subshell ldd, e o seu binário de destino. Então, greppara AT_PLATFORM, mantemos apenas a última linha.

Analisando auxv : se você analisar a auxvestrutura você mesmo (sem depender do carregador dinâmico), haverá um pouco de um dilema: a auxvestrutura segue a regra do processo descrito, sizeof(unsigned long)sendo 4 para processos de 32 bits e 8 para 64 processos de bits. Podemos fazer isso funcionar para nós. Para que isso funcione em sistemas de 32 bits, todos os códigos de chave devem ser 0xffffffffou menos. Em um sistema de 64 bits, os 32 bits mais significativos serão zero. As máquinas Intel são pouco conhecidas, portanto esses 32 bits seguem os menos significativos na memória.

Como tal, tudo o que você precisa fazer é:

1. Read 16 bytes from the `auxv` file.
2. Is this the end of the file?
3.     Then it's a 64-bit process.
4.     Done.
5. Is buf[4], buf[5], buf[6] or buf[7] non-zero?
6.     Then it's a 32-bit process.
7.     Done.
8. Go to 1.

Analisando o arquivo de mapas : isso foi sugerido por Gilles, mas não funcionou completamente. Aqui está uma versão modificada que faz. Depende da leitura do /proc/$PID/mapsarquivo. Se o arquivo listar endereços de 64 bits, o processo será de 64 bits. Caso contrário, são 32 bits. O problema reside no fato de o kernel simplificar a saída eliminando os zeros à esquerda dos endereços hexadecimais nos grupos de 4, para que o tamanho não funcione. awkpara o resgate:

if ! [ -e /proc/$pid/maps ]; then
    echo "No such process"
else
    case $(awk </proc/$pid/maps -- 'END { print substr($1, 0, 9); }') in
    *-) echo "32 bit process";;
    *[0-9A-Fa-f]) echo "64 bit process";;
    *) echo "Insufficient permissions.";;
    esac
 fi

Isso funciona verificando o endereço inicial do último mapa de memória do processo. Eles estão listados como 12345678-deadbeef. Portanto, se o processo for de 32 bits, esse endereço terá oito dígitos hexadecimais e o nono será um hífen. Se for de 64 bits, o endereço mais alto será mais longo que isso. O nono caractere será um dígito hexadecimal.

Esteja ciente: todos, exceto o primeiro e o último método, precisam do kernel Linux 2.6.0 ou mais recente, pois o auxvarquivo não estava lá antes.

Alexios
fonte
1
Hmmm, gostaria de saber se o cabeçalho ELF está em /proc/[pid]/auxv: "as informações do interpretador ELF passadas para o processo no momento da execução. O formato é um ID longo não assinado mais um valor longo não assinado para cada entrada" ( man proc).
goldilocks
1
O cabeçalho em si não é. Acabei de hdeditar um e faltava o número mágico. Pode haver alguma informação relevante lá, mas acho que estaria sujeita a alterações mais frequentes do que o próprio cabeçalho da ELF. Também foi introduzido na 2.6.0, por isso não é tão onipresente quanto /proc/PID/exe. Mas não têm a arquitetura de informação. Vou atualizar minha resposta.
Alexs #
auxv acabou por ser mais complicado do que eu esperava - sizeof(unsigned long)é de 8 em 64 bits ou 4 em 32 bits, o que significa que, para interpretá-lo corretamente, você precisa saber se o processo é de 64 ou 32 bits!
Flexo
Você está absolutamente correto. Isso é muito chato. Heurística rápida: se os bytes 16x + y (4≤y≤7) forem zero no arquivo, você verá um executável de 64 bits. Este é um argumento: estou assumindo uma pequena máquina endian e que todos os auxvcódigos de chave se encaixam em 32 bits unsigned long, então os 32 bits mais significativos em uma caixa de 64 bits seriam zero.
Alexios
6

Olhe dentro /proc/$pid/maps. Os intervalos de endereços têm mais de 32 bits (8 dígitos hexadecimais) ou 64 bits (16 dígitos hexadecimais). Isso funciona para qualquer tipo de executável, independentemente do formato. Você só pode obter informações sobre processos em execução como o mesmo usuário (a menos que você seja root).

if ! [ -e /proc/$pid/maps ]; then
  echo No such process
elif grep -q '^........[^-]' /proc/$pid/maps; then
  echo 64-bit
elif grep -q . /proc/$pid/maps; then
  echo 32-bit
else
  echo Insufficient permissions
fi

Se você não tem permissão para acessar esse arquivo, acho que a única maneira é tentar analisar o executável. (Enquanto você sempre pode ler /proc/$pid/stat, nenhum dos campos mostrados para processos em execução como usuários diferentes revela o tamanho do bit do processo.) Você pode adivinhar o executável do processo ps -o comm=e pesquisá-lo no PATH- mas cuidado com o processo pode ter sido lançado com um diferente PATHou pode ter reescrito seu argv[0]. Você pode analisar o executável - se estiver disposto a assumir o ELF, observe o quinto byte .

Gilles 'SO- parar de ser mau'
fonte
Testei sua receita e ela falhou. OpenSuSE 12.2, x86-64, kernel 3.4.63-2.44-padrão, / bin / bash. As linhas / proc / $ pid / maps para o binário e o primeiro heap são escritas no estilo de 32 bits, mas todas as outras estão no estilo de 64 bits. Provavelmente, eles são impressos usando "% 08x", mas de qualquer maneira essa receita deve ser ajustada.
Netch
Estou recebendo uma mistura de valores de 8, 12 e 16 nybble em todas as caixas com as quais tentei. Sem verificar a fonte, meu palpite é que o kernel ajusta o preenchimento para o múltiplo mais baixo de 16 bits maior que o intervalo de endereços de cada linha impressa, para que você tenha que encontrar a sequência mais longa de caracteres hexadecimais e verifique.
Alexs #
MAS, como o vsyscallmapa é sempre o mais alto, você pode simplesmente mudar headpara tail- o que, infelizmente, não funciona porque o proc não é implementado seek(2), por isso terá que ser algo mais feio, comoawk /proc/self/maps -- 'END { print substr($1, 0, 9); }'
Alexios
@ Netch De fato, estupidamente olhei para o vsyscall e empilhei as linhas e não prestei atenção ao mapeamento do executável. Obrigado, atualizei para procurar qualquer linha que não seja de 32 bits. Pena, é mais feio, mas esse é o mais confiável (pelo menos é certo no x86, não verifiquei com outras arquiteturas duplas, como sparc e arm).
Gilles 'SO- stop be evil'