type significa que psutil info example memory winapi program-structure

memory - significa - psutil python example



¿Cómo se ve un programa en la memoria? (7)

¿Cómo se organiza un programa (por ejemplo, C o C ++) en la memoria de la computadora? Como que sé un poco sobre segmentos, variables, etc., pero básicamente no tengo una comprensión sólida de toda la estructura.

Dado que la estructura en memoria puede diferir, supongamos una aplicación de consola C ++ en Windows.

Algunos consejos para lo que busco específicamente:

  • Esquema de una función, y cómo se llama?
  • Cada función tiene un marco de pila, ¿qué contiene eso y cómo está organizado en la memoria?
  • Argumentos de función y valores de retorno
  • ¿Variables globales y locales?
  • const variables estáticas?
  • Enhebrar el almacenamiento local ..

Se aceptan enlaces a material similar a un tutorial, pero no hay material de referencia que asuma conocimiento de ensamblador, etc.


¡Qué gran pregunta!

Primero quieres aprender sobre la memoria virtual . Sin eso, nada más tendrá sentido. En resumen, los punteros C / C ++ no son direcciones de memoria física. Los punteros son direcciones virtuales. Hay una función especial de CPU (la MMU, unidad de administración de memoria) que los mapea transparentemente en la memoria física. Solo el sistema operativo puede configurar la MMU.

Esto proporciona seguridad (no hay ningún valor de puntero C / C ++ que pueda apuntar al espacio de direcciones virtuales de otro proceso, a menos que ese proceso esté compartiendo memoria intencionalmente con usted) y permite que el sistema operativo haga cosas realmente mágicas que ahora damos por hecho (como intercambiar de forma transparente parte de la memoria de un proceso en el disco y luego volver a cargarla de forma transparente cuando el proceso intente usarla).

El espacio de direcciones de un proceso (también conocido como espacio de direcciones virtuales, también conocido como memoria direccionable) contiene:

  • una gran región de memoria reservada para el núcleo de Windows, que el proceso no puede tocar;

  • regiones de memoria virtual que están "sin asignar", es decir, no hay nada cargado allí, no hay memoria física asignada a esas direcciones, y el proceso se bloqueará si intenta acceder a ellas;

  • parte los diversos módulos (archivos EXE y DLL) que se han cargado (cada uno de estos contiene código de máquina, constantes de cadena y otros datos); y

  • cualquier otra memoria que el proceso haya asignado desde el sistema.

Ahora, normalmente, un proceso permite que la biblioteca C Runtime Library o las bibliotecas Win32 realicen la mayor parte de la administración de memoria de nivel super-bajo, que incluye la configuración:

  • una pila (para cada hilo), donde se almacenan variables locales y argumentos de funciones y valores de retorno; y

  • un montón, donde la memoria se asigna si el proceso llama a malloc o hace una new X

Para obtener más información sobre la pila está estructurada, lea sobre las convenciones de llamadas . Para obtener más información sobre cómo está estructurado el montón, lee sobre las implementaciones de malloc . En general, la pila realmente es una pila, una estructura de datos de último en entrar, que contiene argumentos, variables locales y el resultado temporal ocasional, y no mucho más. Dado que es fácil para un programa escribir directamente después del final de la pila (el error común de C / C ++ después del cual se nombra este sitio), las bibliotecas del sistema normalmente se aseguran de que haya una página no asignada adyacente a la pila. Esto hace que el proceso se cuelgue instantáneamente cuando ocurre un error de este tipo, por lo que es mucho más fácil de depurar (y el proceso se cancela antes de que pueda causar más daño).

El montón no es realmente un montón en el sentido de estructura de datos. Es una estructura de datos mantenida por la biblioteca CRT o Win32 que toma las páginas de la memoria del sistema operativo y las distribuye cada vez que el proceso solicita pequeñas piezas de memoria a través de malloc y sus amigos. (Tenga en cuenta que el sistema operativo no microgestiona esto, un proceso puede en gran medida administrar su espacio de direcciones como quiera, si no le gusta la forma en que lo hace).

Un proceso también puede solicitar páginas directamente desde el sistema operativo, utilizando una API como VirtualAlloc o MapViewOfFile .

¡Hay más, pero será mejor que me detenga!


De hecho, no llegarás lejos en este asunto con al menos un poco de conocimiento en Assembler. Me gustaría recomendar un sitio de reversión (tutorial), por ejemplo, OpenRCE.org.


El libro de Stevens "Advanced Unix Programming" tiene varias páginas con esa respuesta exacta si puede conseguirlo. Por supuesto, deberías ser el dueño del libro.


Para entender la estructura de la estructura de pila, puede consultar http://en.wikipedia.org/wiki/Call_stack

Le da información sobre la estructura de la pila de llamadas, cómo se almacenan los locales, los globales, la dirección de retorno en la pila de llamadas


Puede que no sea la información más precisa, pero MS Press proporciona algunos capítulos de muestra del libro Inside Microsoft® Windows® 2000, Third Edition , que contiene información sobre procesos y su creación junto con imágenes de algunas estructuras de datos importantes.

También me encontré con este PDF que resume parte de la información anterior en una bonita tabla.

Pero toda la información proporcionada es más desde el punto de vista del sistema operativo y no demasiado detallada sobre los aspectos de la aplicación.


Puede ser esto lo que estás buscando:

http://en.wikipedia.org/wiki/Portable_Executable

El formato de archivo PE es la estructura de archivo binario de los binarios de Windows (.exe, .dll, etc.). Básicamente, están mapeados en la memoria de esa manera. Aquí se describen más detalles con una explicación de cómo usted mismo puede echar un vistazo a la representación binaria de dlls cargados en la memoria:

http://msdn.microsoft.com/en-us/magazine/cc301805.aspx

Editar:

Ahora entiendo que desea aprender cómo se relaciona el código fuente con el código binario en el archivo PE. Ese es un gran campo.

En primer lugar, debe comprender los conceptos básicos sobre la arquitectura de la computadora que implicará aprender los conceptos básicos generales del código ensamblador. Cualquier curso universitario de "Introducción a la Arquitectura de Computación" servirá. La literatura incluye, por ejemplo, "John L. Hennessy y David A. Patterson. Arquitectura de la computadora: un enfoque cuantitativo" o "Andrew Tanenbaum, organización de la informática estructurada".

Después de leer esto, debes entender qué es una pila y su diferencia para el montón. Qué son el puntero de pila y el puntero de base y cuál es la dirección de retorno, cuántos registros hay, etc.

Una vez que haya entendido esto, es relativamente fácil juntar las piezas:

Un objeto C ++ contiene código y datos, es decir, variables miembro. Una clase

class SimpleClass { int m_nInteger; double m_fDouble; double SomeFunction() { return m_nInteger + m_fDouble; } }

serán 4 + 8 bytes consecutivos en la memoria. Qué sucede cuando lo haces:

SimpleClass c1; c1.m_nInteger = 1; c1.m_fDouble = 5.0; c1.SomeFunction();

Primero, el objeto c1 se crea en la pila, es decir, el puntero de pila esp se reduce en 12 bytes para hacer espacio. Luego, la constante "1" se escribe en la dirección de memoria esp-12 y la constante "5.0" se escribe en esp-8.

Entonces llamamos a una función que significa dos cosas.

  1. La computadora debe cargar la parte del archivo PE binario en la memoria que contiene la función SomeFunction (). SomeFunction solo estará en la memoria una vez, sin importar cuántas instancias de SimpleClass crees.

  2. La computadora debe ejecutar la función SomeFunction (). Eso significa varias cosas:

    1. Llamar a la función también implica pasar todos los parámetros, a menudo esto se hace en la pila. SomeFunction tiene un (!) Parámetro, el puntero this, es decir, el puntero a la dirección de memoria en la pila donde acabamos de escribir los valores "1" y "5.0"
    2. Guarde el estado actual del programa, es decir, la dirección de instrucción actual que es la dirección del código que se ejecutará si SomeFunction regresa. Llamar a una función significa presionar la dirección de retorno en la pila y configurar el puntero de instrucción (registrar eip) en la dirección de la función SomeFunction.
    3. Dentro de la función SomeFunction, la pila anterior se guarda almacenando el antiguo puntero base (ebp) en la pila (push ebp) y convirtiendo el puntero de la pila en el nuevo puntero base (mov ebp, esp).
    4. Se ejecuta el código binario real de SomeFunction que llamará a la instrucción de máquina que convierte m_nInteger en un doble y lo agrega a m_fDoble. m_nInteger y m_fDouble se encuentran en la pila, en ebp - x bytes.
    5. El resultado de la adición se almacena en un registro y la función regresa. Eso significa que la pila se descarta, lo que significa que el puntero de la pila vuelve al puntero base. El puntero base se retrocede (el siguiente valor en la pila) y luego el puntero de la instrucción se establece en la dirección de retorno (nuevamente el siguiente valor en la pila). Ahora estamos de vuelta en el estado original pero en algún registro acecha el resultado de SomeFunction ().

Sugiero que te construyas un ejemplo tan simple y avances en el desmontaje. En la compilación de depuración, el código será fácil de entender y Visual Studio muestra los nombres de las variables en la vista de desensamblaje. Vea lo que los registros esp, ebp y eip do, donde en la memoria se asigna su objeto, donde está el código, etc.