Desarrollo

Tenía el ejecutable. Había perdido el código

Una tarde de verano abrí un adjunto que no debía. El disco quedó medio vacío y con él meses de trabajo en Visual Basic 6. Me quedaba el ejecutable, así que me pasé unos días averiguando si se podía volver atrás desde él. Lo que aprendí sobre qué guarda un binario y qué no me sigue sirviendo hoy.

Hoy hace veintiocho años que Microsoft anunció la disponibilidad mundial de Visual Studio 6.0. Dentro venía Visual Basic 6, que durante bastante tiempo fue la manera más rápida que existía de convertir una idea de negocio en una ventana con botones que hacía algo útil.

Yo lo usé para uno de los primeros encargos serios de mi vida profesional. Y lo perdí entero.

Bueno, entero no. Me quedó el ejecutable, y durante unos días pensé que con eso bastaba.

El entorno de desarrollo de Visual Basic 6 recién abierto, mostrando el diálogo de nuevo proyecto con la opción Standard EXE seleccionada

El diálogo con el que empezaba todo: Visual Basic 6 recién abierto, preguntando qué clase de proyecto quieres. La primera opción, la que viene marcada, es «Standard EXE». Captura de Visual Basic 6.0 (Microsoft, 1998) sobre Windows XP, reproducida con fines informativos. Derechos de sus titulares.

El encargo

Una clínica dietética que trabajaba con un método propio: fases, umbrales, recálculos, alimentos que entran y salen según en qué punto esté el paciente, excepciones para casos concretos. Querían una aplicación para gestionar y seguir a sus pacientes con ese método, no con uno genérico.

Lo difícil de aquel proyecto no fue programarlo. Programarlo eran formularios, una base de datos y unas cuantas pantallas de listados. Lo difícil fue entender el método, y eso se hizo como se hacen estas cosas: a base de reuniones. Muchas. Meses de reuniones para sacar de la cabeza de unos profesionales un conjunto de reglas que ellos aplicaban con naturalidad y que no estaban escritas en ningún sitio de una forma que un programa pudiera ejecutar.

Y aquello tampoco se podía consultar en internet, que era la otra mitad del problema. No hablo solo del método: hablo de casi todo. No había foros llenos de respuestas, no existía Stack Overflow, y la documentación seria estaba en los manuales que venían dentro de la caja. Cuando te atascabas, te atascabas de verdad.

Todo ese trabajo —el método entero, traducido a condiciones, a rangos y a números— acabó donde acaba siempre: dentro del código.

Aquella tarde

Era verano. Hacía calor y estaba agobiado, con prisa, o simplemente tuve un mal momento.

Me llegó un correo con un adjunto de esos que en circunstancias normales no habría abierto jamás. Y digo jamás con conocimiento de causa: sabía perfectamente lo que era un adjunto peligroso, llevaba años sabiéndolo, y aun así lo abrí. Saber no es lo mismo que estar atento, y aquella tarde no estaba atento.

Era un virus.

El ordenador empezó a hacer ruido. Muchísimo ruido. Del tipo que no se corresponde con nada de lo que tú acabas de pedirle, porque tú no le has pedido nada. Me asusté, y al poco lo apagué.

Un disco duro de 3,5 pulgadas abierto, con los platos metálicos apilados y el brazo de los cabezales de lectura extendido sobre ellos

Un Seagate Medalist de finales de los noventa, abierto: los platos y el brazo con los cabezales. No es el mío —el mío siguió cerrado y siguió funcionando perfectamente—, pero es esta pieza la que hace el ruido cuando un disco trabaja sin parar. Foto: Eric Gaba, CC BY-SA 3.0; imagen recortada y disponible bajo la misma licencia.

Después desconecté internet. Volví a arrancar. Probé en modo seguro. Fui haciendo, una detrás de otra, todas las cosas que en aquel momento se me ocurrieron para intentar salvar la situación, que era básicamente el repertorio completo de la época y en un orden que no recuerdo del todo.

Y en algún punto de esa tarde dejé de estar ocupado y me puse a mirar qué quedaba.

El disco estaba medio vacío.

No es una expresión: faltaban cosas por todas partes, y hay un momento muy concreto —el de ir abriendo carpetas y encontrarlas donde no deberían estar, o no encontrarlas— que no se parece a ningún otro. Entre lo que faltaba estaba el código fuente del proyecto de la clínica. Meses de trabajo. Todo.

Nunca supe qué virus era. No lo sé hoy y no lo voy a averiguar, así que no lo voy a nombrar: aquellos años había candidatos de sobra y elegir uno por parecido histórico sería inventarme un dato para que la historia quede más redonda.

«Pero tengo el ejecutable»

Y entonces me acordé de una cosa.

El programa existía. Compilado, instalado, funcionando. El .exe es lo que se prueba y lo que se entrega, así que estaba a salvo en sitios donde el virus no había entrado.

Cuesta explicar el alivio de ese momento a alguien que no haya pasado por él. Ahí tienes el resultado exacto de tu trabajo, ejecutándose, respondiendo a los clics, haciendo los cálculos que costó meses definir. Está todo. Tiene que haber alguna forma de volver atrás.

No había repositorio, claro. Ni control de versiones de ningún tipo: existir existía —CVS llevaba años funcionando—, pero yo no usaba ninguno. Lo que había era una carpeta en un disco, y esa carpeta ya no estaba.

Así que me pasé unos días metido en lo único que parecía prometer algo: averiguar si se podía sacar el proyecto de dentro del compilado.

Dos maneras de compilar

Lo primero que aprendí es que la respuesta dependía de una decisión que yo no recordaba haber tomado.

Visual Basic podía generar el ejecutable de dos formas distintas. Lo cuenta la propia documentación de Microsoft, en la página Compiling Your Project to Native Code:

  • P-code (pseudocódigo): un formato intermedio, en palabras de Microsoft, «entre las instrucciones de alto nivel de tu programa Basic y el código nativo de bajo nivel que ejecuta el procesador».
  • Código nativo: instrucciones del procesador de verdad, generadas —esto lo dice también Microsoft— con «la misma tecnología de compilador de back-end optimizador que Microsoft Visual C++».

Para el que ejecuta el programa la diferencia es de tamaño y de velocidad. Para el que intenta reconstruir el proyecto años después, la diferencia es de otra naturaleza. El p-code está mucho más cerca del lenguaje original: sus instrucciones se corresponden con operaciones de alto nivel, así que un bucle todavía se parece a un bucle. El código nativo ha pasado por un optimizador de C++ que reordena, fusiona y elimina; lo que sale por el otro lado sigue siendo tu programa, pero ya no conserva la forma en que tú lo escribiste.

Dicho en corto: no son el mismo problema. Con p-code habría tenido delante una representación mucho más próxima a lo que escribí; con nativo, algo que después de compilar y optimizar es bastante más difícil de reconstruir.

Hay dos cosas más que descubrí entonces y que cambian bastante la moraleja fácil.

La primera es que la opción no estaba disponible para todo el mundo. La documentación de Microsoft abre esa página diciéndolo con todas las letras: «si tienes la edición Professional o Enterprise de Visual Basic, puedes compilar tu código en formato p-code estándar o en formato de código nativo». La edición Standard no compilaba a nativo. Y cuando Visual Studio 6.0 salió a la venta, aquel 2 de septiembre de 1998, la nota de prensa de Microsoft ponía precio a las tres cajas: 109 dólares la Standard, 549 la Professional, 1.299 la Enterprise. Es decir, que lo que un binario tuyo iba a poder contarte una década más tarde dependía, entre otras cosas, de qué caja te habías podido permitir.

La segunda es que no era «un botón», por mucho que contado así quede mejor. La pestaña Proyecto → Propiedades → Compilar era un panel entero de decisiones: además de los dos modos había Optimize for Fast Code, Optimize for Small Code, No Optimization, Favor Pentium Pro, Create Symbolic Debug Info, y un botón de Advanced Optimizations que abría otra ventana con opciones para desactivar la comprobación de límites de arrays, el control de desbordamiento de enteros y las comprobaciones de coma flotante. La propia ventana avisaba de que activarlas «podría impedir la ejecución correcta de tu programa».

Yo no entré nunca en esa pestaña. Ni una vez. Así que tampoco sé —ni sabré— en cuál de los dos modos estaba compilado aquel ejecutable.

Lo que sí guarda un .exe de Visual Basic

Aquí es donde la investigación se puso interesante de verdad, y donde sigue siéndolo hoy: un ejecutable de VB6 guarda muchísimas más cosas de las que uno esperaría, porque el runtime las necesita para arrancar el programa.

Empezando por lo más llamativo: Visual Basic incrusta información de tipos dentro del propio ejecutable, sin necesidad de una biblioteca de tipos externa, y no se puede desactivar. No es una opción que se te olvidara desmarcar; venía de serie. Todo eso lo ha documentado en detalle David Zimmer, autor del desensamblador libre vbdec, en su trabajo sobre binarios de VB6.

Dentro de esa maraña de estructuras —una cabecera que apunta a la información del proyecto, que apunta a una tabla de objetos, que apunta a los formularios, módulos y clases— sobreviven cosas como estas:

  • En formularios, clases y controles de usuario, los nombres y los prototipos de los miembros públicos: procedimientos, variables, argumentos con sus tipos y el tipo de retorno. Es la infraestructura con la que Visual Basic los expone como objetos COM, así que no vale para las funciones de los módulos .BAS, que internamente no lo son.
  • Los nombres de los eventos.
  • Los valores de las propiedades que fijaste en el diseñador: el Caption de un formulario, el Text de una caja, el Tag de cualquier control. Están ahí, en el binario, y el programa los lee al construir la ventana. Y están tanto si compilaste a p-code como si compilaste a nativo.

Con ese material, una herramienta puede reconstruir una aproximación bastante decente de las pantallas. Pero conviene decir hasta dónde llega eso, porque es fácil venderlo como si el ejecutable llevase dentro el diseñador de formularios intacto, y no es así: cada tipo de control guarda sus propiedades a su manera y necesita que la herramienta sepa interpretarlo —los controles ActiveX de terceros, cada uno el suyo—, y hay que ir recorriendo los controles en orden para llegar al último. Lo que consigas depende del ejecutable concreto, de los controles que usara y de la herramienta que estés pasando. No es «vuelve todo». Es «vuelve bastante, con desigualdades, y sin ninguna garantía».

Y hay una razón de fondo por la que un binario de VB6 conserva tanto, que además explica una rareza del formato. Aunque compiles a nativo, el ejecutable sigue necesitando MSVBVM60.DLL. Lo dice Microsoft en la misma página, y merece la pena leerlo despacio porque suele contarse mal: compilar a nativo significa que «el código que escribes se compila por completo a las instrucciones nativas del procesador», lo cual «acelerará mucho los bucles y los cálculos matemáticos», pero «no elimina la necesidad de la DLL». Entre los servicios que presta esa DLL, Microsoft enumera el arranque y el cierre de la aplicación, «la funcionalidad de los formularios y los controles intrínsecos» y funciones de librería como Format o CLng.

O sea: tu código sí se compila de verdad. Lo que se queda fuera es la maquinaria —las ventanas, los controles, las cadenas, los arrays, la gestión de errores, la creación de objetos—, que vive en la DLL y que el programa invoca. Por eso un binario de VB6 tiene ese aspecto tan reconocible, lleno de llamadas a rutinas con nombre, y por eso, comparado con un C equivalente, ofrece muchísimos más asideros a quien lo mira desde fuera.

Y lo que no guarda

La otra lista es más corta y más definitiva:

  • Los nombres de los procedimientos privados. En las estructuras del ejecutable, donde debería estar el nombre, hay un hueco.
  • Los nombres de las variables locales.
  • Los comentarios. Todos.

Aquí hace falta una precisión que yo no tenía entonces y que es lo que de verdad hay que entender. Que no esté el nombre no significa que no esté el código. La lógica de un procedimiento privado está ahí; se ejecuta cada vez que alguien usa el programa, evidentemente. Lo que falta es la etiqueta: quien mira el binario encuentra un bloque de instrucciones que hace algo, sin nada que le diga qué es ese algo ni cómo lo llamabas tú.

Y no falta por un fallo de las herramientas de decompilación. Falta porque el compilador lo descarta a propósito: a la máquina no le hace ninguna falta saber que esa variable se llamaba pesoObjetivoFase2, y desde luego no le hace falta el párrafo que había encima explicando por qué en la fase 3 no se recalcula.

El ejecutable conservaba las reglas; lo que había perdido era su significado

Durante años he contado esta historia diciendo que el método dietético se había perdido entero, y no es verdad. Es una frase bonita y es falsa.

El método estaba dentro del ejecutable. Tenía que estarlo: el programa lo aplicaba. Si en algún sitio había una condición equivalente a If peso > 90 Then, esa comparación seguía ejecutándose todos los días en el ordenador de la clínica, y una reconstrucción suficientemente terca habría acabado dando con ella.

Lo que se había perdido es otra cosa, y no es menor: qué era ese 90. Por qué noventa y no noventa y dos. Qué representaba la variable que se comparaba con él. Qué concepto del método había detrás de esa frontera. Qué excepción me había explicado el cliente en una reunión para que esa línea estuviera escrita así y no de la otra manera. Cómo se llamaban las cosas, que es como se sabe qué son.

El ejecutable conservaba las reglas. Lo que había perdido era su significado.

Y esa es la idea con la que me quedé de todo aquello, que sigue valiendo para cualquier binario de cualquier lenguaje: un binario está hecho para conservar lo que necesita la máquina, no lo que necesitarás tú para reconstruir las decisiones. Son dos cosas distintas. La cara es la segunda.

No podía dejar tirado al cliente

El resto es bastante menos interesante y bastante más largo.

El cliente esperaba su aplicación. Yo había perdido el código. El problema era mío —había abierto yo el correo— y no había ninguna conversación que tener con nadie sobre de quién era la culpa. Así que me senté y lo hice otra vez desde cero.

No hay más épica que esa. Era lo que tocaba.

Por qué la segunda vez fue mucho más rápida

Y aquí está lo que tardé años en entender.

Rehacerlo llevó bastante menos que hacerlo. En su momento me pareció un consuelo tonto, de esos que uno se cuenta para no hundirse. Pero el motivo es concreto y no tiene nada de consuelo: las reuniones ya habían ocurrido. Ya conocía el método. Ya entendía las reglas, y las excepciones, y por qué la fase 3 era distinta. Ya había tomado, discutido y corregido casi todas las decisiones difíciles. Lo que tenía por delante era volver a implementar algo que ya había entendido una vez.

Pensaba que había perdido meses de trabajo. En realidad había perdido meses de código. Los meses que había dedicado a entender el problema seguían conmigo.

Lo que tuve que repetir fue teclear, que es la parte barata.

Y que quede claro lo que no estoy diciendo: no estoy diciendo que programar sea trivial, ni que la implementación no tenga dificultad propia. Estoy diciendo otra cosa, que es que implementar una solución que ya has comprendido cuesta muchísimo menos que llegar a comprenderla. Son dos trabajos distintos y solo uno de los dos estaba en el disco.

Por eso tampoco me sirve la conclusión fácil de que el código no era el activo. Sí lo era: perderlo fue una catástrofe, me costó meses y podría haberme costado el cliente. Lo que pasa es que no era el único, y resulta que era el reponible de los dos.

Lo que hago distinto desde entonces

La lección evidente es «haz copias», y no la voy a desarrollar aquí porque ya he escrito sobre por qué una copia que no has restaurado es solo una hipótesis y sobre la falsa sensación de seguridad que dan las medidas que nadie ha comprobado nunca. Sí, hoy uso Git para todo, incluso para cosas que no lo merecen. Eso está resuelto hace mucho.

Las otras dos son menos obvias y me han servido más.

Un ejecutable no es una copia del código. Es un derivado con pérdidas, y lo que pierde no es material aleatorio: se queda por el camino, con bastante precisión, la capa que explica las decisiones. Tener el .exe y creer que tienes el proyecto es tener la traducción y creer que tienes el original.

Las decisiones hay que escribirlas en algún sitio. En los comentarios, que son gratis y son lo único del código que habla de intenciones. Y fuera del código también, porque los comentarios envejecen y desaparecen en la primera refactorización: por qué noventa y no noventa y dos, qué se descartó y con qué argumento, qué dijo el cliente que hizo que esto sea así. Eso no cabe en un git log y es, con diferencia, la documentación que más veces me ha salvado después.

Una nota de 2026

Hay una versión contemporánea de esto que me viene a la cabeza cada vez que cuento la historia.

Hoy una parte del código se escribe pidiéndoselo a un modelo. El código acaba en el repositorio, versionado y a salvo. La conversación que lo produjo —el problema tal y como lo planteaste, las restricciones que diste, las tres alternativas que descartaste y por qué, la razón por la que al final quedó así— se cierra con la pestaña.

Es la misma asimetría de antes, otra vez: se conserva el resultado y se evapora el contexto que llevó hasta él. Con el agravante de que ahora la parte que se pierde puede ser justo la que hiciste tú, porque entender el problema sigue siendo el trabajo caro y escribir la implementación es cada vez menos trabajo de nadie.

Solo que esta vez no hace falta ningún virus.

(Sigo sin saber en cuál de los dos modos estaba compilado aquel ejecutable. Nunca abrí esa pestaña, así que nunca lo supe. Tampoco habría cambiado gran cosa: ninguno de los dos guardaba lo que yo estaba buscando.)

Preguntas frecuentes

¿Se puede recuperar el código fuente de un ejecutable de Visual Basic 6?

Parcialmente, y nunca el proyecto original. Existen herramientas específicas para analizar binarios de VB6, y lo que consiguen depende de cómo se compilara el programa, de los controles que utilizara y de la propia herramienta. Un ejecutable en p-code conserva una representación intermedia más próxima al código de alto nivel, lo que facilita el análisis frente a uno compilado a código nativo, que ha pasado por un compilador optimizador y resulta bastante más difícil de reconstruir. En ambos casos el resultado es una reconstrucción sin los nombres originales de lo privado y sin un solo comentario.

¿Qué diferencia hay entre compilar a p-code y a código nativo en Visual Basic 6?

Microsoft describe el p-code como un formato intermedio entre las instrucciones de alto nivel del programa y el código nativo que ejecuta el procesador. La compilación nativa elimina ese paso intermedio y genera instrucciones de máquina con la misma tecnología de compilador optimizador que utilizaba Visual C++. La consecuencia para cualquier análisis posterior es que el p-code conserva mucho mejor la forma del código original, mientras que el nativo ha pasado por un optimizador que reordena y transforma. La opción, además, solo estaba disponible en las ediciones Professional y Enterprise: la Standard compilaba únicamente a p-code.

¿Qué información sobrevive dentro de un ejecutable de Visual Basic 6?

Más de la que suele suponerse, porque el runtime la necesita para arrancar el programa. Visual Basic incrusta información de tipos en el propio ejecutable, sin biblioteca de tipos externa y sin posibilidad de desactivarlo. En formularios, clases y controles de usuario sobreviven los nombres y prototipos de los miembros públicos —procedimientos, variables, argumentos con sus tipos y tipo de retorno—, los nombres de los eventos y los valores de las propiedades fijadas en el diseñador, como el título de un formulario o el texto de un control. No ocurre igual con las funciones de los módulos .BAS, que internamente no son objetos COM. Con eso puede reconstruirse una aproximación de las pantallas, aunque cada tipo de control guarda sus propiedades a su manera y el resultado varía según el binario y la herramienta.

¿Se pierde la lógica de los procedimientos privados al compilar?

No. La lógica está dentro del ejecutable y se ejecuta cada vez que se usa el programa. Lo que no está es el nombre: en las estructuras internas del binario, donde iría el nombre de un procedimiento privado hay un hueco. Ocurre lo mismo con los nombres de las variables locales y con los comentarios. Quien analiza el binario encuentra bloques de instrucciones que hacen algo, sin nada que indique qué es ese algo ni cómo lo llamaba quien lo escribió. No es una limitación de las herramientas: el compilador descarta esa información porque la máquina no la necesita.

¿Un ejecutable de VB6 compilado a nativo deja de necesitar el runtime de Visual Basic?

No. La documentación de Microsoft es explícita: compilar a código nativo hace que el código escrito por el programador se compile por completo a instrucciones del procesador, lo que acelera bucles y cálculos, pero no elimina la necesidad de MSVBVM60.DLL. Entre los servicios que presta esa biblioteca están el arranque y el cierre de la aplicación, la funcionalidad de los formularios y los controles intrínsecos y funciones de librería como Format o CLng. Por eso un binario de VB6 conserva tantos puntos de apoyo reconocibles para quien lo analiza desde fuera.

Otras noticias

El blog, día a día

45 artículos publicados 3 mar 2026 — 2 mar 2027

¿Te ha resultado útil o quieres comentar algo?

Hablemos →