Dia 08
Enseñanzas de un quiz mal diseñado
En el quiz les pedí que analizaran el siguiente código.
080483a4 <foo>:
80483a4: push %ebp
80483a5: mov %esp,%ebp
80483a7: sub $0x10,%esp
80483aa: movl $0x0,-0x8(%ebp)
80483b1: mov 0x8(%ebp),%eax
80483b4: movzbl (%eax),%eax
80483b7: mov %al,-0x1(%ebp)
80483ba: movl $0x0,-0x8(%ebp)
80483c1: jmp 80483e1 <foo+0x3d>
80483c3: mov -0x8(%ebp),%eax;
80483c6: add 0x8(%ebp),%eax;
80483c9: movzbl (%eax),%eax
80483cc: cmp -0x1(%ebp),%al
80483cf: jle 80483dd
80483d1: mov -0x8(%ebp),%eax
80483d4: add 0x8(%ebp),%eax
80483d7: movzbl (%eax),%eax
80483da: mov %al,-0x1(%ebp)
80483dd: addl $0x1,-0x8(%ebp)
80483e1: mov -0x8(%ebp),%eax
80483e4: cmp 0xc(%ebp),%eax
80483e7: jl 80483c3 <foo+0x1f>
80483e9: movsbl -0x1(%ebp),%eax
80483ed: leave
80483ee: ret
Al menos nos sirvió para aprender sobre algunas pistas que nos pueden revelar la funcionalidad de código en assembly.
Instrucciones para bytes: Cuando veas instrucciones como movzbl
y movsbl
, debes sospechar que hay alguna movida con caracteres o booleanos. Esas dos instrucciones se utilizan para mover un solo byte de memoria a registro o vicerversa.
Direcciones desalineadas Otra pista de que se están procesando caracteres es el hecho de ver un desplazamiento que no sea multiplo de 4, e.g. -0x1(%ebp). Una variable entera núnca comienza en una dirección así pues estaría desalineada. Alinear los datos en memoria hace que su acceso por parte del procesador sea más eficiente.
Ciclos for en assembly Aprende a identificar las partes de un ciclo for
en assembly. Al menos para el compilador que estoy usando, es común convertir un for
en algo que se asemeja a la siguiente figura.
En el programa, las instrucciones que implenta el for
son:
80483ba: movl $0x0,-0x8(%ebp) } inicialización del control loop var
80483c1: jmp 80483e1 <foo+0x3d> } jump incondicional
80483c3: mov -0x8(%ebp),%eax; \
80483c6: add 0x8(%ebp),%eax; }
80483c9: movzbl (%eax),%eax }
80483cc: cmp -0x1(%ebp),%al }
80483cf: jle 80483dd } cuerpo del for
80483d1: mov -0x8(%ebp),%eax }
80483d4: add 0x8(%ebp),%eax }
80483d7: movzbl (%eax),%eax }
80483da: mov %al,-0x1(%ebp) /
80483dd: addl $0x1,-0x8(%ebp) } incremento del
80483e1: mov -0x8(%ebp),%eax } CLV
80483e4: cmp 0xc(%ebp),%eax } comparación y brinco
80483e7: jl 80483c3 <foo+0x1f> } (note la dirección del brinco)
Otros asuntos - decoración de nombres al compilar C++
Pueden leer sobre name decoration y sus usos en https://en.wikipedia.org/wiki/Name_mangling. En esencia, cuando programamos en lenguajes que permiten sobrecarga de funciones, el linker necesita saber a cuál de las versiones de una función se está refiriendo el código fuente. Por ejemplo, la primera función operator<<
del siguiente programa recibe un entero como parámetro. La segunda función recibe un string.
cout << 42;
cout << "Significado de la vida";
A la hora de enlazar el código con las funciones apropiadas de la librería compartida, el linker debe saber cuál de las versiones del operator<<
usar. Si en vez de decorar el nombre de operator<<
usaramos solo el nombre (pelao) el linker no sabría cuál de las múltiples versiones sobrecargadas de operator<<
usar.
Detalles:
-
En el primer caso, la función
operator<<
fue decorada así:_ZNSolsEi
y corresponde astd::ostream::operator<<(int)
. -
En el segundo caso, la función
operator<<
fue decorada así:_ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
y corresponde astd::basic_ostream<char, std::char_traits<char> >& std::operator<< <std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*)
.