Lire la pile d'un autre processus?

16

J'essaie de lire la pile d'un processus enfant mais sans succès. Je sais qu'il est possible d'utiliser ptrace, mais ptracel'interface de vous permet de lire un seul mot à la fois, et j'essaie de numériser une plus grande partie de la pile.

J'ai également essayé de lire les /proc/$pid/memlimites de la pile extraites du /proc/$pid/mapsfichier après avoir d'abord utilisé ptrace pour y attacher (comme suggéré ici ) mais la lecture continue d'échouer (même lors de l'exécution en tant que root) bien que le même code réussisse lorsqu'il est essayé lecture de différentes parties du processus (par exemple, tas).

Qu'est-ce que je fais mal? Y a-t-il une autre option?

user4537
la source
Avez-vous appelé waitpidentre ptrace(PTRACE_ATTACH,…)et read(sinon il existe une condition de concurrence possible)? Quelle erreur readrevient? L'enfant fait-il quelque chose de particulier avec son mappage de mémoire - pouvez-vous essayer votre code avec un enfant simple comme sleep?
Gilles 'SO- arrête d'être méchant'
J'ai utilisé l'attente après ptrace, et j'ai mis un scanf chez l'enfant pour le forcer à attendre.
user4537
Est-ce uniquement sur Linux? Solaris a également un système de fichiers / proc, mais il est complètement différent de Linux, même philosophiquement. Beaucoup de "fichiers binaires".
Bruce Ediger
il suffit de faire un système ("pstack pid ") et d'analyser la sortie ..
vrdhn
Voir ps: la commande complète est trop longue pour quelques exemples
Stéphane Chazelas

Réponses:

5

ptraceL'interface de vous permet de lire un seul mot à la fois, et j'essaie de numériser une plus grande partie de la pile

Eh bien, utilisez simplement une boucle, alors. Honnêtement, je ne vois pas en quoi cela constitue un problème ptrace, je l'utilise tout le temps pour accéder à distance aux processus.

J'utilise quelque chose comme ça:

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
la source
Salut Sam, alors que votre code fera l'affaire (et en fait c'est ce que je fais actuellement), il a une surcharge de performances importante.
user4537
@ user4536: Je vois. J'ai une autre stratégie en tête que je publierai quand j'aurai le temps de l'écrire. Quelles sont vos tailles de pile typiques?
sam hocevar
C'est difficile à dire car mes recherches ne supposent pas une taille de pile spécifique, mais pour cet argument, disons au moins quelques pages. Pourriez-vous laisser un indice concernant votre stratégie? Merci pour l'aide quand même!
user4537
1

Voici une autre stratégie qui pourrait nécessiter des ajustements mais devrait être plus efficace avec de gros morceaux de données. L'idée est d'exécuter des appels système dans le processus distant afin de récupérer le contenu de la pile. Il aura besoin d'un code d'architecture spécifique mais si vous ne ciblez que x86 / x86_64, cela ne devrait pas être trop compliqué.

  1. Créez un canal nommé tel que "/tmp/fifo"dans votre processus d'appel.
  2. Entrez dans le processus tracé jusqu'à ce qu'il revienne d'un appel système, en utilisant l' PTRACE_SYSCALLétape, waitpid()pour attendre et PTRACE_GETREGS/ PTRACE_PEEKTEXTpour vérifier l'opcode actuellement exécuté.
  3. Sauvegardez les registres du processus distant et une petite zone de sa pile.
  4. Exécuter syscalls sur le processus à distance en remplaçant la pile avec vos propres données: open("/tmp/fifo"), write()le contenu de la pile, close()le descripteur.
  5. Restaurez l'état du processus distant.
  6. Lisez les données fifo de votre processus d'appel.

Il pourrait y avoir des alternatives plus élégantes à la pipe nommée, mais je ne peux pas en penser pour le moment. La raison pour laquelle j'utilise uniquement les appels système est que l'injection de code à distance est assez peu fiable sur les systèmes modernes en raison de diverses protections de sécurité. L'inconvénient est qu'il se bloque jusqu'à ce que le processus distant effectue un appel système (ce qui peut être un problème pour certains programmes qui effectuent principalement des calculs).

Vous pouvez voir du code gratuit implémentant la plupart des travaux dans ce fichier source . Les commentaires sur le code sont les bienvenus!

sam hocevar
la source
1

Une autre suggestion.

Lorsque / s'il est accepté dans l'arborescence principale du noyau Linux, vous pourrez utiliser le patch Cross Memory Attach de Christopher Yeoh . Voir la documentation de process_vm_readv par exemple.

sam hocevar
la source
1

Vous pouvez facilement lire la pile d'un autre processus en utilisant le système de fichiers proc (vous aurez besoin d'un accès root pour cela). Avant de lire arbitrairement le / proc / pid / mem, vous devez consulter le / proc / pid / maps. Une simple lecture dans ce fichier montre beaucoup d'entrées. Nous sommes intéressés par l'entrée marquée comme pile. Une fois que vous obtenez cela, vous devez lire les limites inférieure et supérieure de la pile. Maintenant, ouvrez simplement le fichier / proc / pid / mem, recherchez la limite inférieure de la pile et lisez la taille correcte des données.

Ajay Brahmakshatriya
la source
1
Êtes-vous sûr de vouloir dire memset non maps? (Je ne vois aucune memsentrée sous mon /procsystème de fichiers.) L'OP a déjà mentionné la lecture des limites de la pile /proc/$pid/maps- de quoi suggérez-vous qu'ils font différemment?
JigglyNaga
Modifié la faute de frappe. J'ai fait exactement ce que j'ai mentionné dans ma réponse et il a vidé 132 Ko de données de pile. Nous avons besoin de plus d'informations sur ce qu'OP a fait de mal. Peut-être qu'OP peut partager le code qu'il a utilisé pour lire les limites de la pile. S'il ne répond pas, je partagerai le mien.
Ajay Brahmakshatriya
0

Vous pouvez essayer lsstack . Il utilise ptrace, comme tous les autres programmes réussis "lire la pile d'un autre processus". Je n'ai pas pu faire fonctionner un programme utilisant / proc / $ pid / mem. Je crois que vous ne pouvez pas le faire de cette façon, même si, logiquement, vous devriez.

Bruce Ediger
la source