Leia a pilha de outro processo?

16

Estou tentando ler a pilha de um processo filho, mas sem sorte. Eu sei que é possível usar ptrace, mas ptracea interface permite que você leia apenas uma palavra de cada vez, e estou tentando digitalizar partes maiores da pilha.

Também tentei ler os /proc/$pid/memlimites da pilha como extraídos do /proc/$pid/mapsarquivo depois de usar o ptrace para anexar a ele (como sugerido aqui ), mas a leitura continua falhando (mesmo quando executado como root), embora o mesmo código tenha êxito ao ser tentado leitura de diferentes partes do processo (por exemplo, heap).

O que estou fazendo de errado? Existe alguma outra opção?

user4537
fonte
Você ligou waitpidentre ptrace(PTRACE_ATTACH,…)e read(caso contrário, há uma possível condição de corrida)? Que erro readretorna? A criança está fazendo algo peculiar com seu mapeamento de memória - você pode tentar seu código com uma criança simples como sleep?
Gilles 'SO- stop be evil'
Usei wait after ptrace e coloquei um scanf na criança para forçá-la a esperar.
user4537
Isso é apenas no Linux? O Solaris também possui um sistema de arquivos / proc, mas é completamente diferente do Linux, filosoficamente. Muitos "arquivos binários".
Bruce Ediger 23/02
apenas fazer um sistema ( "pstack pid ") e analisar a saída ..
vrdhn
Veja ps: comando completo é muito longo para alguns exemplos
Stéphane Chazelas

Respostas:

5

ptraceA interface permite que você leia apenas uma palavra de cada vez, e estou tentando digitalizar partes maiores da pilha

Bem, basta usar um loop, então. Sinceramente, não vejo como isso constitui um problema ptrace; uso-o o tempo todo para acessar processos remotamente.

Eu uso algo como isto:

static int memcpy_from_target(pid_t pid, char *dest, long src, size_t n)
{
    static int const align = sizeof(long) - 1;

    while (n)
    {
        size_t todo = MIN(n, sizeof(long) - (src & align));
        long data = ptrace(PTRACE_PEEKTEXT, pid, src - (src & align), 0);
        if (errno)
        {
            perror("ptrace_peektext (memcpy_from_target)");
            return -1;
        }
        memcpy(dest, (char *)&data + (src & align), todo);

        dest += todo; src += todo; n -= todo;
    }

    return 0;
}
sam hocevar
fonte
Olá Sam, enquanto o seu código funciona (e na verdade é isso que estou fazendo atualmente), ele tem uma grande sobrecarga de desempenho.
User4537 13/03/11
@ user4536: Entendo. Tenho outra estratégia em mente que publicarei quando tiver tempo para anotá-la. Quais são os seus tamanhos de pilha típicos?
sam hocevar 13/03/11
É difícil dizer realmente, pois minha pesquisa não assume um tamanho de pilha específico, mas, para o argumento, digamos que tenha pelo menos duas páginas. Você poderia deixar uma dica sobre sua estratégia? Obrigado pela ajuda de qualquer maneira!
user4537
1

Aqui está outra estratégia que pode precisar de ajustes, mas deve ser mais eficiente com grandes blocos de dados. A idéia é executar syscalls no processo remoto para recuperar o conteúdo da pilha. Ele precisará de um código de arquitetura específico, mas se você segmentar apenas x86 / x86_64, não deve ser muito complicado.

  1. Crie um canal nomeado, como "/tmp/fifo"no seu processo de chamada.
  2. Entre no processo rastreado até retornar de um syscall, usando PTRACE_SYSCALLpara avançar, waitpid()para aguardar e PTRACE_GETREGS/ PTRACE_PEEKTEXTpara verificar o código de operação atualmente executado.
  3. Faça backup dos registros do processo remoto e de uma pequena área de sua pilha.
  4. Executar syscalls sobre o processo remoto, substituindo a sua stack com seus próprios dados: open("/tmp/fifo"), write()o conteúdo da pilha, close()o descritor.
  5. Restaure o estado do processo remoto.
  6. Leia os dados do fifo do seu processo de chamada.

Pode haver alternativas mais elegantes para o pipe nomeado, mas não consigo pensar em nenhuma agora. A razão de eu usar somente syscalls é porque a injeção remota de código não é confiável nos sistemas modernos devido a várias proteções de segurança. A desvantagem é que ele travará até que o processo remoto faça uma syscall (o que pode ser um problema para alguns programas que geralmente fazem cálculos).

Você pode ver algum código gratuito implementando a maior parte do trabalho neste arquivo de origem . Comentários sobre o código são bem-vindos!

sam hocevar
fonte
1

Outra sugestão.

Quando / se for aceito na árvore principal do kernel Linux, você poderá usar o patch Cross Memory Attach de Christopher Yeoh . Consulte a documentação de process_vm_readv, por exemplo.

sam hocevar
fonte
1

Você pode ler facilmente a pilha de outro processo usando o sistema de arquivos proc (você precisará de acesso root para isso). Antes de ler arbitrariamente a partir do / proc / pid / mem, você precisa consultar o / proc / pid / maps. Uma simples leitura neste arquivo mostra muitas entradas. Estamos interessados ​​na entrada marcada como pilha. Depois de conseguir isso, você precisa ler os limites inferior e superior da pilha. Agora simplesmente abra o arquivo / proc / pid / mem, procure o limite inferior da pilha e leia o tamanho correto dos dados.

Ajay Brahmakshatriya
fonte
1
Você tem certeza que quer dizer memse não maps? (Não consigo ver nenhuma memsentrada no meu /procsistema de arquivos.) O OP já mencionou a leitura dos limites da pilha /proc/$pid/maps- o que você sugere que eles façam de maneira diferente?
JigglyNaga
Editado o erro de digitação. Fiz exatamente o que mencionei na minha resposta e despejou 132 KBs de dados da pilha. Precisamos de mais informações sobre o que o OP fez de errado. Talvez o OP possa compartilhar o código que ele usou para ler os limites da pilha. Se ele não responder, vou compartilhar o meu.
Ajay Brahmakshatriya
0

Você pode tentar lsstack . Ele usa o ptrace, assim como qualquer outro programa bem-sucedido de "leia a pilha de outro processo". Não consegui obter um programa usando / proc / $ pid / mem reading para funcionar. Acredito que você não pode fazer dessa maneira, embora, logicamente, deva.

Bruce Ediger
fonte