c++ c linux pthreads coredump

c++ - Tenedor y núcleo de volteo con roscas.



linux pthreads (4)

Se han planteado puntos similares a los de esta pregunta antes, here y here , y estoy al tanto de la biblioteca de Google coredump (que he evaluado y que no se encuentra, aunque podría intentarlo si entiendo mejor el problema) ).

Quiero obtener un volcado de núcleo de un proceso Linux en ejecución sin interrumpir el proceso. El enfoque natural es decir:

if (!fork()) { abort(); }

Dado que el proceso bifurcado obtiene una copia instantánea fija de la memoria del proceso original, debería obtener un volcado de memoria completo, y dado que la copia utiliza copia en escritura, generalmente debería ser barata. Sin embargo, una deficiencia crítica de este enfoque es que fork() solo bifurca el hilo actual, y todos los otros hilos del proceso original no existirán en la copia bifurcada.

Mi pregunta es si es posible obtener de alguna manera los datos relevantes de los otros subprocesos originales. No estoy completamente seguro de cómo abordar este problema, pero aquí hay un par de preguntas secundarias que he encontrado:

  1. ¿La memoria que contiene todas las pilas de hilos aún está disponible y es accesible en el proceso bifurcado?

  2. ¿Es posible (rápidamente) enumerar todos los subprocesos en ejecución en el proceso original y almacenar las direcciones de las bases de sus pilas? Como lo entiendo, la base de una pila de hilos en Linux contiene un puntero a los datos de contabilidad del hilo del kernel, así que ...

  3. con las direcciones de base de los hilos almacenados, ¿podría leer los datos relevantes para cada uno de los hilos originales en el proceso bifurcado?

Si eso es posible, tal vez solo sea una cuestión de agregar los datos de los otros subprocesos al volcado del núcleo. Sin embargo, si esos datos ya se han perdido en el punto de la bifurcación, entonces no parece haber ninguna esperanza para este enfoque.


¿Está familiarizado con el proceso de punto de control-reinicio? En particular, CRIU ? Me parece que podría ser una opción fácil para ti.

Quiero obtener un volcado de núcleo de un proceso Linux en ejecución sin interrumpir el proceso [y] para obtener de alguna manera los datos relevantes de los otros subprocesos originales.

Olvídate de no interrumpir el proceso. Si lo piensas bien, un volcado de núcleo tiene que interrumpir el proceso durante la duración del volcado; Por lo tanto, su verdadero objetivo debe ser minimizar la duración de esta interrupción. Tu idea original de usar fork() interrumpe el proceso, solo lo hace por un tiempo muy corto.

  1. ¿La memoria que contiene todas las pilas de hilos aún está disponible y es accesible en el proceso bifurcado?

No. La fork() solo retiene el hilo que hace la llamada real, y las pilas para el resto de los hilos se pierden.

Aquí está el procedimiento que usaría, asumiendo que CRIU no es adecuado:

  • Tenga un proceso principal que genere un volcado central del proceso secundario siempre que se detenga el elemento secundario. (Tenga en cuenta que se puede generar más de un evento de detención consecutivo; solo se debe actuar sobre el primero hasta el siguiente evento de continuación).

    Puede detectar los eventos de detener / continuar utilizando waitpid(child,,WUNTRACED|WCONTINUED) .

  • Opcional: use sched_setaffinity() para restringir el proceso a una sola CPU, y sched_setscheduler() (y quizás sched_setparam() ) para quitar la prioridad del proceso a IDLE .

    Puede hacer esto desde el proceso principal, que solo necesita la capacidad CAP_SYS_NICE (que puede otorgar usando setcap ''cap_sys_nice=pe'' parent-binary al binario primario, si tiene habilitadas las capacidades del sistema de archivos como la mayoría de las distribuciones actuales de Linux), Tanto en los conjuntos efectivos como en los permitidos.

    La intención es minimizar el progreso de los otros subprocesos entre el momento en que un subproceso decide que desea una instantánea / volcado, y el momento en que todos los subprocesos se han detenido. No he probado cuánto tardan los cambios en surtir efecto; ciertamente, solo se producen al final de sus horarios actuales lo antes posible. Por lo tanto, este paso probablemente se debe hacer un poco de antemano.

    Personalmente, no me molesto. En mi máquina de cuatro núcleos, el siguiente SIGSTOP solo produce latencias similares entre subprocesos como lo hace un mutex o un semáforo, por lo que no veo ninguna necesidad de esforzarme por lograr una sincronización aún mejor.

  • Cuando un hilo en el proceso hijo decide que quiere tomar una instantánea de sí mismo, envía un SIGSTOP a sí mismo (a través de kill(getpid(), SIGSTOP) ). Esto detiene todos los hilos en el proceso.

    El proceso principal recibirá la notificación de que el niño fue detenido. Primero examinará /proc/PID/task/ para obtener los TID para cada hilo del proceso hijo (y quizás /proc/PID/task/TID/ pseudofiles para otra información), luego se adjunta a cada TID usando ptrace(PTRACE_ATTACH, TID) . Obviamente, ptrace(PTRACE_GETREGS, TID, ...) obtendrá los estados de registro por hilo, que se pueden usar junto con /proc/PID/task/TID/smaps y /proc/PID/task/TID/mem para obtenga el seguimiento de la pila por subproceso y cualquier otra información que le interese (por ejemplo, puede crear un archivo principal compatible con el depurador para cada subproceso).

    Cuando el proceso principal termina de agarrar el volcado, permite que el proceso secundario continúe. Creo que necesita enviar una señal SIGCONT separada para permitir que todo el proceso hijo continúe, en lugar de simplemente confiar en ptrace(PTRACE_CONT, TID) , pero no he comprobado esto; verifica esto, por favor

Creo que lo anterior producirá un retraso mínimo en el tiempo de reloj de pared entre los hilos en el proceso de parada. Las pruebas rápidas en AMD Athlon II X4 640 en Xubuntu y un kernel 3.8.0-29 genérico indican que los bucles ajustados incrementan una variable volátil en los otros subprocesos, solo avanzan los contadores unos pocos miles, dependiendo de la cantidad de subprocesos (hay demasiado ruido en las pocas pruebas que hice para decir algo más específico).

Limitar el proceso a una sola CPU, e incluso a la prioridad IDLE, reducirá drásticamente ese retraso aún más. CAP_SYS_NICE capacidad CAP_SYS_NICE permite que el padre no solo reduzca la prioridad del proceso hijo, sino que también levante la prioridad a los niveles originales; las capacidades del sistema de archivos significan que el proceso principal ni siquiera tiene que estar CAP_SYS_NICE , ya que solo CAP_SYS_NICE suficiente. (Creo que sería lo suficientemente seguro, con algunas buenas comprobaciones en el programa para padres, para instalarlo, por ejemplo, en computadoras de la universidad, donde los estudiantes son muy activos en encontrar formas interesantes de explotar los programas instalados).

Es posible crear un parche (o módulo) del kernel que proporcione un kill(getpid(), SIGSTOP) que también intente sacar a los otros subprocesos de las CPU en ejecución y, por lo tanto, tratar de reducir aún más el retraso entre los subprocesos. . Personalmente, no me molestaría. Incluso sin la manipulación de la CPU / prioridad obtengo la sincronización suficiente (retrasos suficientemente pequeños entre las veces que se detienen los subprocesos).

¿Necesita algún código de ejemplo para ilustrar mis ideas arriba?


Cuando se fork se obtiene una copia completa de la memoria de los procesos en ejecución. Esto incluye todas las pilas de hilos (después de todo, podría tener punteros válidos en ellas). Pero solo el hilo que llama sigue ejecutándose en el hijo.

Usted puede probar esto fácilmente. Haz un programa multiproceso y ejecuta:

pid_t parent_pid = getpid(); if (!fork()) { kill(parent_pid, SIGSTOP); char buffer[0x1000]; pid_t child_pid = getpid(); sprintf(buffer, "diff /proc/%d/maps /proc/%d/maps", parent_pid, child_pid); system(buffer); kill(parent_pid, SIGTERM); return 0; } else for (;;);

Así que toda su memoria está allí y cuando crea un volcado de núcleo contendrá todas las demás pilas de subprocesos (siempre que su tamaño máximo de archivo de núcleo lo permita). Las únicas piezas que faltan son sus conjuntos de registros. Si los necesita, deberá ptrace su padre para obtenerlos.

Sin embargo, debe tener en cuenta que los volcados de memoria no están diseñados para contener información en tiempo de ejecución de más de un hilo, el que causó el volcado de memoria.

Para responder algunas de tus otras preguntas:

Puede enumerar los hilos al pasar por /proc/[pid]/tasks , pero no puede identificar sus bases de pila hasta que las haya ptrace .

Sí, tienes acceso completo a las otras instantáneas de pilas de hilos (ver arriba) desde el proceso bifurcado. No es trivial determinarlos, pero se colocan en un volcado de memoria siempre que el tamaño del archivo lo permita. Lo mejor que puedes hacer es guardarlos en una estructura accesible globalmente si puedes crearlos.


Si su objetivo es tomar una instantánea de todo el proceso para comprender el estado exacto de todos los subprocesos en un punto específico, no puedo ver ninguna forma de hacer esto que no requiera algún tipo de rutina de servicio de interrupción. Debe detener todos los procesadores y grabar el estado actual de cada subproceso.

No conozco ningún sistema que proporcione este tipo de volcado de núcleo de proceso completo. Los esquemas generales del proceso serían:

  1. emita una interrupción en todas las CPU (núcleos lógicos y físicos).
  2. ocupado espera a que todos los núcleos se sincronicen (esto no debería llevar mucho tiempo).
  3. Clone el espacio de memoria del proceso deseado: duplique las tablas de páginas y marque todas las páginas como copia en escritura.
  4. haga que cada procesador compruebe si su subproceso actual está en el proceso de destino. Si es así, grabe el puntero de pila actual para ese hilo.
  5. para cada otro subproceso, examine el bloque de datos del subproceso para el puntero de pila actual y regístrelo.
  6. crear un subproceso de núcleo para guardar los espacios de memoria copiados y los punteros de pila de subprocesos
  7. resumir todos los núcleos.

Esto debería capturar todo el estado del proceso, incluida una instantánea de cualquier proceso que se estuviera ejecutando en el momento en que se emitió la interrupción entre procesadores. Debido a que todos los subprocesos se interrumpen (ya sea mediante el proceso de suspensión del programador estándar o mediante nuestro proceso de interrupción personalizado), todos los estados de registro estarán en una pila en algún lugar de la memoria del proceso. Entonces solo necesitas saber dónde está la parte superior de cada pila de hilos. El uso del mecanismo de copia en escritura para clonar las tablas de la página permite un borrado transparente, mientras que el proceso original puede reanudarse.

Esta es una opción bastante pesada, ya que su funcionalidad principal requiere la suspensión de todos los procesadores durante una cantidad significativa de tiempo (sincronizar, clonar, recorrer todos los subprocesos). Sin embargo, esto debería permitirle capturar exactamente el estado de todos los subprocesos, así como determinar qué subprocesos se estaban ejecutando (y en qué CPU) cuando se alcanzó el punto de control. Asumiría que existe parte del marco para hacer este proceso (en CRIU, por ejemplo). Por supuesto, la reanudación del proceso dará lugar a una tormenta de asignaciones de páginas, ya que la copia en el mecanismo de escritura protege el estado del sistema de comprobación puntual.


Si tiene la intención de obtener el archivo principal en una ubicación no específica, y simplemente obtener la imagen principal del proceso ejecutándose sin matar, entonces puede usar gcore .

Si tiene la intención de obtener el archivo principal en una ubicación específica (condición) y aún así continuar ejecutando el proceso, un enfoque gcore es ejecutar gcore programación desde esa ubicación.

Un enfoque más clásico y limpio sería verificar la API que usa gcore y la incorporó en su aplicación, pero sería un esfuerzo excesivo en comparación con la necesidad la mayor parte del tiempo.

HTH!