Leer la pila de otro proceso?

16

Estoy tratando de leer la pila de un proceso secundario pero sin suerte. Sé que es posible usarlo ptrace, pero ptracela interfaz le permite leer solo una palabra a la vez, y estoy tratando de escanear porciones más grandes de la pila.

También intenté leer los /proc/$pid/memlímites de la pila extraídos del /proc/$pid/mapsarchivo después de usar ptrace para adjuntarlo (como se sugiere aquí ), pero la lectura sigue fallando (incluso cuando se ejecuta como root) aunque el mismo código tiene éxito cuando se intenta lectura de diferentes partes del proceso (por ejemplo, montón).

¿Qué estoy haciendo mal? ¿Hay alguna otra opción?

usuario4537
fuente
¿Llamaste waitpidentre ptrace(PTRACE_ATTACH,…)y read(de lo contrario, hay una posible condición de carrera)? ¿Qué error readdevuelve? ¿El niño está haciendo algo peculiar con su mapeo de memoria? ¿Puede probar su código con un niño simple como sleep?
Gilles 'SO- deja de ser malvado'
Utilicé wait after ptrace, y puse un scanf en el niño para obligarlo a esperar.
user4537
¿Es esto solo en Linux? Solaris también tiene un sistema de archivos / proc, pero es completamente diferente de Linux, incluso filosóficamente. Muchos "archivos binarios".
Bruce Ediger
simplemente haga un sistema ("pstack pid ") y analice la salida ..
vrdhn
Ver ps: el comando completo es demasiado largo para algunos ejemplos
Stéphane Chazelas

Respuestas:

5

ptraceLa interfaz le permite leer solo una palabra a la vez, y estoy tratando de escanear porciones más grandes de la pila

Bueno, solo usa un bucle, entonces. Sinceramente, no veo cómo eso constituye un problema ptrace, lo uso todo el tiempo para acceder de forma remota a los procesos.

Yo uso algo como esto:

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
fuente
Hola Sam, si bien tu código funcionará (y en realidad eso es lo que estoy haciendo actualmente), tiene una gran sobrecarga de rendimiento.
user4537
@ user4536: Ya veo. Tengo otra estrategia en mente que publicaré cuando tenga tiempo de escribirla. ¿Cuáles son sus tamaños de pila típicos?
sam hocevar
Es difícil decirlo realmente, ya que mi investigación no asume un tamaño de pila específico, pero por el bien de este argumento, digamos al menos un par de páginas de largo. ¿Podría dejar una pista sobre su estrategia? ¡De todas formas, gracias por la ayuda!
user4537
1

Aquí hay otra estrategia que podría necesitar ajustes, pero debería ser más eficiente con grandes cantidades de datos. La idea es ejecutar syscalls en el proceso remoto para recuperar el contenido de la pila. Necesitará un código de arquitectura específico, pero si solo apunta a x86 / x86_64, no debería ser demasiado complicado.

  1. Cree una tubería con nombre como "/tmp/fifo"en su proceso de llamada.
  2. Ingrese al proceso rastreado hasta que regrese de una llamada al sistema, usando el PTRACE_SYSCALLpaso, waitpid()para esperar y PTRACE_GETREGS/ PTRACE_PEEKTEXTo verificar el código de operación actualmente ejecutado.
  3. Copia de seguridad de los registros del proceso remoto y un área pequeña de su pila.
  4. Ejecutar llamadas al sistema en el proceso remoto anulando su pila con sus propios datos: open("/tmp/fifo"), write()el contenido de la pila, close()el descriptor.
  5. Restaurar el estado del proceso remoto.
  6. Lea los datos de su proceso de llamadas.

Puede haber alternativas más elegantes a la tubería con nombre, pero no puedo pensar en ninguna en este momento. La razón por la que solo uso syscalls es porque la inyección remota de código es poco confiable en los sistemas modernos debido a varias protecciones de seguridad. El inconveniente es que se bloqueará hasta que el proceso remoto realice una llamada al sistema (que puede ser un problema para algunos programas que en su mayoría realizan cálculos).

Puede ver un código gratuito que implementa la mayor parte del trabajo en este archivo fuente . Comentarios sobre el código son bienvenidos!

sam hocevar
fuente
1

Otra sugerencia

Cuando / si se acepta en el árbol principal del kernel de Linux, podrá usar el parche Cross Memory Attach de Christopher Yeoh . Consulte la documentación de process_vm_readv, por ejemplo.

sam hocevar
fuente
1

Puede leer fácilmente la pila de otro proceso utilizando el sistema de archivos proc (necesitará acceso de root para esto). Antes de leer arbitrariamente / proc / pid / mem, debe consultar los mapas / proc / pid /. Una simple lectura en este archivo muestra muchas entradas. Estamos interesados ​​en la entrada marcada como stack. Una vez que obtenga esto, debe leer los límites inferior y superior de la pila. Ahora simplemente abra el archivo / proc / pid / mem, busque el límite inferior de la pila y lea el tamaño correcto de los datos.

Ajay Brahmakshatriya
fuente
1
¿Estás seguro de que quieres decir memsy no maps? (No puedo ver ninguna memsentrada en mi /procsistema de archivos). El OP ya mencionó la lectura de los límites de la pila /proc/$pid/maps: ¿qué sugiere que hagan de manera diferente?
JigglyNaga
Editado el error tipográfico. Hice exactamente lo que mencioné en mi respuesta y arrojó 132 KB de datos de la pila. Necesitamos más información sobre lo que OP hizo mal. Quizás OP pueda compartir el código que usó para leer los límites de la pila. Si no responde, compartiré el mío.
Ajay Brahmakshatriya
0

Podrías probar lsstack . Utiliza ptrace, al igual que cualquier otro programa exitoso de "leer la pila de otro proceso". No pude obtener un programa usando / proc / $ pid / mem reading para trabajar. Creo que no puede hacerlo de esa manera, aunque, lógicamente, debería hacerlo.

Bruce Ediger
fuente