StellatorOS: Punteros en C


C nos permite utilizar punteros, mediante estas variables podremos almacenar direcciones de memoria en vez de datos directamente, posteriormente se puede hacer referencia a estas direcciones de memoria para escribir o leer en ellas.

 

Antes de comenzar es recomendable que leas estos artículos anteriores:

Técnicamente hablando todos los punteros son direcciones de memoria de 32 bits, pero según el tipo de dato que vayamos a leer/escribir en esa dirección de memoria tendremos que definir de un modo u otro nuestro puntero.

La definición de un puntero a la dirección de memoria de video sería:

char* video_address = 0xb8000;

Si queremos escribir en la dirección de memoria a la que apunta el puntero:

*video_address = 'X';

Veamos como se traduce un puntero a ASM:

vi test2.c
void my_function();

int main(int argc, char *argv[])
{
 my_function();
}

void my_function() {
 int var1 = 9;
 int* pointer = &var1;
}

Definimos una variable de tipo entero var1, le asignamos un valor y luego definimos un puntero que apunta a la posición de memoria donde está el valor de var1.

Compilamos:

cc -g test2.c -o test2

Cargamos en GDG:

gdb -q test2

Veamos nuestra función en ASM:

gef➤  disassemble my_function
Dump of assembler code for function my_function:
=> 0x0000000000201300 <+0>: push   rbp
   0x0000000000201301 <+1>: mov    rbp,rsp
   0x0000000000201304 <+4>: mov    DWORD PTR [rbp-0x4],0x9
   0x000000000020130b <+11>: lea    rax,[rbp-0x4]
   0x000000000020130f <+15>: mov    QWORD PTR [rbp-0x10],rax
   0x0000000000201313 <+19>: pop    rbp
   0x0000000000201314 <+20>: ret    
End of assembler dump.

Ponemos un breakpoint justo antes de llamar a nuestra función:

gef➤  l
1 void my_function();
2 
3 int main(int argc, char *argv[])
4 {
5  my_function();
6 }
7 
8 void my_function() {
9  int var1 = 9;
10  int* pointer = &var1;
gef➤  b 5

Corremos el programa:

gef➤  r

Damos unos pasos mas con la orden "si" hasta llegar a:

   0x0000000000201304 <+4>: mov    DWORD PTR [rbp-0x4],0x9

Con esta instrucción estamos asignando 9 a la dirección de memoria bp-0x4, "creando" la variable var1 en el stackframe.

Seguimos adelante:

gef➤  si
   0x000000000020130b <+11>: lea    rax,[rbp-0x4]

Con esta instrucción estamos guardando la dirección bp-0x4 en el registro ax, esta dirección es donde se encuentra el valor de var1, justo la definición de puntero.

Seguimos adelante:

gef➤  si
 →   0x20130f <my_function+15> mov    QWORD PTR [rbp-0x10], rax

Con esta instrucción estamos "creando" la variable pointer con el valor de ax, si consultamos el valor del puntero(bp-0x10).

gef➤  x/10x $rbp-0x10
0x7fffffffe0e0: 0xffffe0ec 0x00007fff 0x00000001 0x00000001
0x7fffffffe0f0: 0xffffe110 0x00007fff 0x002012f4 0x00000000
0x7fffffffe100: 0xffffe180 0x00007fff

La dirección es: 0x7fffffffe0ec

Si consultamos el contenido de dicha dirección:

gef➤  x/10x 0x7fffffffe0ec-0x10
0x7fffffffe0dc: 0x00000008 0xffffe0ec 0x00007fff 0x00000001
0x7fffffffe0ec: 0x00000009 0xffffe110 0x00007fff 0x002012f4
0x7fffffffe0fc: 0x00000000 0xffffe180

Obtenemos el 9: 0x00000009.

Los punteros se pueden definir directamente a la dirección de memoria donde el compilador guarde la variable, el siguiente ejemplo creará un puntero con el valor de la dirección de memoria donde el compilador ha decidio meter el string.

vi test3.c
void my_function();

int main(int argc, char *argv[])
{
	my_function();
}

void my_function() {
	char* my_string = "Hello";
}

Lo compilamos y lo cargamos en GDB:

cc -g test3.c -o test3
gdb -q test3

El código en ASM de nuestra función es el siguiente:

gef➤  disassemble my_function
Dump of assembler code for function my_function:
   0x0000000000201300 <+0>: push   rbp
   0x0000000000201301 <+1>: mov    rbp,rsp
   0x0000000000201304 <+4>: movabs rax,0x200490
   0x000000000020130e <+14>: mov    QWORD PTR [rbp-0x8],rax
   0x0000000000201312 <+18>: pop    rbp
   0x0000000000201313 <+19>: ret    
End of assembler dump.

La dirección de memoria donde se guarda nuestro string ha sido calculada por el compilador: 0x200490, expliquemos paso a paso como genera el puntero.

   0x0000000000201304 <+4>: movabs rax,0x200490

Asigna la dirección de memoria donde se encuentra nuestro string al registro ax.

   0x000000000020130e <+14>:	mov    QWORD PTR [rbp-0x8],rax

Crea la variable en el stackframe y le asigna el valor de ax.

Si ejecutamos paso a paso el programa mediante la orden "si" y paramos en:

   0x0000000000201312 <+18>:	pop    rbp

Podremos consultar el valor de la variable:

gef➤  x/10x $rbp-0x8
0x7fffffffe0e8: 0x00200490 0x00000000 0xffffe110 0x00007fff
0x7fffffffe0f8: 0x002012f4 0x00000000 0xffffe180 0x00007fff
0x7fffffffe108: 0xffffe190 0x00000001

El valor del puntero es: 0x00200490

Consultamos esa dirección de memoria:

gef➤  x/10x 0x00200490-10
0x200486: 0x00000000 0x00000000 0x65480000 0x006f6c6c
0x200496: 0x1b010000 0x00343b03 0x00050000 0x0b680000
0x2004a6: 0x00500000 0x0c880000

Hello en hexadecimal es 48 65 6c 6c 6f, en el dump de memoria podemos verlo: 0x65480000 0x006f6c6c

Otra manera quizás mas rápida de verlo es ya que ax ha sido copiado a bp-0x8 podemos consultar directamente ax obteniendo el valor de la dirección de memoria a la que apunta en ASCII:

gef➤  registers $rax
$rax   : 0x0000000000200490  →  0x0000006f6c6c6548 ("Hello"?)

La salida anterior indica que ax tiene como valor  0x00200490 y en esta dirección de memoria encontramos el valor 0x6f6c6c6548 que en ASCII es Hello.

En resumidas cuentas un puntero es una variable que no mantiene un valor en sí misma, si no la dirección de memoria donde está el valor final que realmente nos interesa.

Si te ha gustado el artículo puedes invitarme a un redbull aquí.
Autor: kr0m -- 16/06/2020 03:01:07