c - reales - libro de android studio en español pdf
Obtener inicio y fin de segmentos de proceso C/C++ (4)
Necesito obtener la dirección de inicio y final de los siguientes segmentos de proceso: código, datos, pila, entorno. Entiendo cómo está ubicado en la memoria, pero no sé cómo hacerlo usando llamadas de API o alguna otra cosa. He encontrado cómo iniciar algunos segmentos con este código
#include <stdio.h>
int temp_data = 100;
static int temp_bss;
void print_addr ( void )
{
int local_var = 100;
int *code_segment_address = ( int* ) &print_addr;
int *data_segment_address = &temp_data;
int *bss_address = &temp_bss;
int *stack_segment_address = &local_var;
printf ( "/nAddress of various segments:" );
printf ( "/n/tCode Segment : %p" , code_segment_address );
printf ( "/n/tData Segment : %p" , data_segment_address );
printf ( "/n/tBSS : %p" , bss_address );
printf ( "/n/tStack Segment : %p/n" , stack_segment_address );
}
int main ( )
{
print_addr ();
return 0;
}
Pero no sé cómo encontrar el final de cada segmento. Lo único que tengo idea es que el final de un segmento es el comienzo de otro segmento. Por favor explique cómo puedo hacer esto usando C y la API de Linux.
Como se dijo en otro comentario, la noción de segmento de texto, datos y pila realmente no existe hoy en Linux. El texto del programa se distribuye en las bibliotecas compartidas y la asignación de memoria se realiza con mmap()
lugar de brk()
hace que los datos asignados se extiendan por todo el espacio de direcciones de un programa.
Dicho esto, puede usar la llamada al sistema brk()
para encontrar el final del segmento de datos y puede usar los símbolos etext
, edata
y end
para encontrar los límites del ejecutable. El comienzo del segmento de texto es tradicionalmente fijo (también denominado "dirección de carga") y depende de la configuración de la arquitectura y del enlazador. Tenga en cuenta que su programa probablemente ejecutará código fuera de la sección de texto de su binario y muy probablemente no asigne memoria dinámica con brk.
Consulte las páginas man correspondientes para obtener más detalles.
Los sabores actuales de Windows y Linux usan espacios de direcciones planos, lo que significa que los segmentos de código y datos son los mismos y casi siempre pasan de 0 a 2 ^ 32-1 (para sistemas de 32 bits) y 2 ^ 64-1 (para 64- sistemas de bits). Los diferentes procesos generalmente tienen espacios de direcciones completamente diferentes, además de la memoria compartida. Por lo general, solo algunas partes de los espacios de direcciones tienen asignada memoria, y algunas partes pueden no ser direccionables debido a limitaciones de hardware.
El código del enlazador y los segmentos de datos se convierten en las secciones de la imagen ejecutable, y el formato ELF común en Linux agrega algunas complicaciones ulteriores a eso. El acceso es altamente específico del sistema operativo y, por lo tanto, no es realmente un problema de C ++.
En Windows, puede obtener un puntero al inicio de la imagen cargada a través de GetModuleHandle (0). Al recorrer los encabezados ejecutables, puede encontrar la tabla de secciones COFF, que le permite realizar un mapa inverso de todas las direcciones que forman parte de la imagen ejecutable asignada a sus respectivas secciones. Clasificar otras direcciones es más difícil; pueden pertenecer a otras imágenes de ejecución asignadas (archivos DLL cargados) o pueden pertenecer a rangos de direcciones que se asignaron de otra manera, es decir, directamente a través de VirtualAlloc () o indirectamente de alguna manera (HeapAlloc (), archivos mapeados en memoria, lo que sea).
Si solo desea imprimir buenos rastreos de pila o lo que sea, hay muchas bibliotecas listas para usar que pueden hacer eso por usted. Si quiere hacer sumas de comprobación, las cosas se vuelven mucho más complicadas; es mejor utilizar bibliotecas con firma de código o listas para usar. La respuesta real a su pregunta depende de cuál es realmente su problema real ...
No estoy seguro de que los datos o el segmento de heap estén bien definidos y sean únicos (en particular, en aplicaciones de subprocesos múltiples, o simplemente en aplicaciones que usan bibliotecas dinámicas, incluido libc.so
). En otras palabras, ya no hay un inicio y un final de texto, datos o segmento de montón bien definidos, porque hoy en día un proceso tiene muchos de esos segmentos. Entonces su pregunta ni siquiera tiene sentido en el caso general.
La mayoría de las implementaciones malloc
usan mmap (2) y munmap
mucho más que sbrk
Debes leer más acerca de proc (5) . En particular, su aplicación podría leer /proc/self/maps
(o /proc/1234/maps
para el proceso de pid 1234) o /proc/self/smaps
; pruebe cat /proc/self/maps
y considere usar fopen (3) en "/proc/self/maps"
(luego un bucle en fgets
o readline
, y finalmente y rápidamente fclose
). Tal vez dladdr (3) podría ser relevante.
También puede leer los encabezados ELF de su programa, por ejemplo, de /proc/self/exe
. Ver también readelf (1) y objdump (1) & execve (2) y elf (5) & ld.so (8) y libelf . Lea también el libro de Levine''s Linkers & Loaders y el documento de Drepper: Cómo escribir bibliotecas compartidas .
Ver también esta respuesta a una pregunta relacionada (y también a esa pregunta ). Tenga en cuenta que los sistemas Linux recientes tienen ASLR , por lo que el diseño de la dirección de dos procesos similares que ejecutan el mismo programa en el mismo entorno sería diferente.
Intenta también (1) algún comando simple o tu programa. Comprenderá un poco más las llamadas de sistema relevantes (2) . Lea también Programación avanzada de Linux
Ver man 3 end
para obtener ayuda:
#include <stdio.h>
extern etext;
extern edata;
extern end;
int
main(int ac, char **av, char **env)
{
printf("main %p/n", main);
printf("etext %p/n", &etext);
printf("edata %p/n", &edata);
printf("end %p/n", &end);
return 0;
}
Las direcciones de esos 3 símbolos son la primera dirección después del final del texto, los datos inicializados y los segmentos de datos no inicializados.
Puede obtener las variables de enunciado a través de un 3er parámetro a main()
como en el código de ejemplo anterior, pero también puede subir la pila comenzando con la dirección &argv[0]
. Hay una palabra de valor NULL (32 o 64 bit dependiendo de la CPU) después del último puntero a una cadena de argumento de línea de comando. Después de eso NULL se encuentra el medio ambiente.
La parte superior de la pila es casi imposible de obtener mediante programación: los SO modernos hacen "Aleatorización de diseño de espacio de direcciones" (ASLR) para mitigar los desbordamientos de búfer. El "final" de la pila es turbio, ya que puedes asignarlo a la pila (mediante recursión o alloca()
) hasta que llegues a la parte superior del montón. Entonces, el "final" de la pila depende de los patrones de asignación del programa en cuestión.
También debe tener en cuenta el vector auxiliar ELF. Ver man getauxval
para una interfaz de lenguaje C, y este artículo para obtener alguna explicación. Los programas de usuario nunca tienen un uso para el vector auxiliar ELF, pero está íntimamente relacionado con el enlace dinámico.