Ejemplos ejecutables mínimos de Linux con análisis de desmontaje
Dado que este es un detalle de implementación no especificado por los estándares, echemos un vistazo a lo que está haciendo el compilador en una implementación particular.
En esta respuesta, vincularé a respuestas específicas que hacen el análisis, o proporcionaré el análisis directamente aquí, y resumiré todos los resultados aquí.
Todos ellos están en varias versiones de Ubuntu / GCC, y los resultados son probablemente bastante estables en todas las versiones, pero si encontramos alguna variación, especifiquemos versiones más precisas.
Variable local dentro de una función
Ya sea main
o cualquier otra función:
void f(void) {
int my_local_var;
}
Como se muestra en: ¿Qué significa <valor optimizado fuera> en gdb?
-O0
: apilar
-O3
: registra si no se derraman, apilar de otra manera
Para obtener motivación sobre por qué existe la pila, consulte: ¿Cuál es la función de las instrucciones push / pop utilizadas en los registros en el ensamblaje x86?
Variables globales y static
variables de función
/* BSS */
int my_global_implicit;
int my_global_implicit_explicit_0 = 0;
/* DATA */
int my_global_implicit_explicit_1 = 1;
void f(void) {
/* BSS */
static int my_static_local_var_implicit;
static int my_static_local_var_explicit_0 = 0;
/* DATA */
static int my_static_local_var_explicit_1 = 1;
}
char *
y char c[]
Como se muestra en: ¿Dónde se almacenan las variables estáticas en C y C ++?
void f(void) {
/* RODATA / TEXT */
char *a = "abc";
/* Stack. */
char b[] = "abc";
char c[] = {'a', 'b', 'c', '\0'};
}
¿TODO también se colocarán literales de cadena muy grandes en la pila? O.data
? ¿O falla la compilación?
Argumentos de la función
void f(int i, int j);
Debe pasar por la convención de llamada relevante, por ejemplo: https://en.wikipedia.org/wiki/X86_calling_conventions para X86, que especifica registros específicos o ubicaciones de pila para cada variable.
Entonces, como se muestra en ¿Qué significa <valor optimizado fuera> en gdb? , -O0
luego sorbe todo en la pila, mientras-O3
trata de usar registros tanto como sea posible.
Sin embargo, si la función se alinea, se tratan como locales normales.
const
Creo que no hay diferencia porque puedes descartarlo.
Por el contrario, si el compilador puede determinar que algunos datos nunca se escriben, en teoría podría colocarlos en .rodata
incluso si no son constantes.
TODO análisis.
Punteros
Son variables (que contienen direcciones, que son números), igual que todas las demás :-)
malloc
La pregunta no tiene mucho sentido para malloc
, ya que malloc
es una función, y en:
int *i = malloc(sizeof(int));
*i
es una variable que contiene una dirección, por lo que corresponde al caso anterior.
En cuanto a cómo funciona malloc internamente, cuando lo llama, el kernel de Linux marca ciertas direcciones como grabables en sus estructuras de datos internas, y cuando el programa las toca inicialmente, ocurre una falla y el kernel habilita las tablas de páginas, lo que permite el acceso suceder sin segfaul: ¿Cómo funciona la paginación x86?
Sin embargo, tenga en cuenta que esto es básicamente exactamente lo que hace el exec
syscall debajo del capó cuando intenta ejecutar un ejecutable: marca las páginas en las que desea cargar y escribe el programa allí, vea también: ¿Cómo obtiene el núcleo un archivo binario ejecutable que se ejecuta bajo linux? Excepto que exec
tiene algunas limitaciones adicionales sobre dónde cargar (por ejemplo, si el código no es reubicable ).
La syscall exacta utilizada malloc
es para mmap
implementaciones modernas de 2020, y en el pasado brk
se usaba: ¿malloc () usa brk () o mmap ()?
Bibliotecas dinámicas
Básicamente, mmap
vaya a la memoria: /unix/226524/what-system-call-is-used-to-load-libraries-in-linux/462710#462710
variables del entorno y main
'sargv
Arriba de la pila inicial: /unix/75939/where-is-the-environment-string-actual-stored TODO ¿por qué no en .data?