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 movzbly 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: