1 puntos por GN⁺ 2024-09-30 | 1 comentarios | Compartir por WhatsApp
  • En sistemas glibc de 32 bits, después de 2038 pueden fallar la consulta de la hora actual o las llamadas a stat(), por lo que Gentoo necesita una ruta para migrar de forma segura a time_t de 64 bits
  • El soporte time64 de glibc debe usarse junto con Large File Support (LFS), y en entornos de 32 bits coexisten el ABI tradicional, el ABI LFS y el ABI LFS+time64
  • Cuando time_t aparece en APIs, estructuras o argumentos de función, el cambio en el ancho del tipo provoca una ruptura de ABI; mezclar binarios time32 y time64 puede causar fallas en tiempo de ejecución y riesgos de seguridad
  • Como Gentoo es una distribución basada en código fuente, durante una reconstrucción de @world pueden quedar sistemas a medio migrar por fallas o dependencias circulares
  • Tras la corrección del 2024-09-30, quedó claro que cambiar solo libdir no basta, y la restricción clave pasa a ser una marca time64 que incluya el cargador dinámico y varias toolchains de distintos lenguajes

El problema de 2038 y el alcance de la transición a time64

  • Las aplicaciones de 32 bits que usan time_t de 32 bits pueden recibir el error -1 en lugar de la hora actual en 2038, o no poder ejecutar stat() sobre archivos
  • La dirección básica de la transición es cambiar time_t a un tipo de 64 bits
    • musl ya hizo la transición
    • glibc lo soporta como opción
    • algunas distribuciones como Debian ya hicieron el cambio
  • En distribuciones basadas en código fuente como Gentoo, los usuarios recompilan el sistema por sí mismos, así que hay que reducir el tiempo durante el cual los paquetes quedan en estados ABI distintos
  • El riesgo central es que cambiar el ancho de time_t rompe el ABI
    • si una API de biblioteca contiene time_t, todo el código que enlaza con esa biblioteca debe usar el mismo ancho de tipo
    • no es seguro cambiar solo una parte a time64

LFS y los tres sub-ABI de 32 bits

  • En arquitecturas de 32 bits ya existían desde antes problemas con el ancho de tipos relacionados con archivos
    • off_t se usa para offsets de archivos
    • ino_t se usa para números de inode
    • originalmente tenían 32 bits, así que aparecían problemas con archivos mayores a 2GiB o con números de inode fuera del rango de 32 bits
  • Para resolverlo se introdujo Large File Support (LFS)
    • cambia off_t e ino_t a variantes de 64 bits
    • en glibc sigue siendo opcional incluso hoy
    • muchos paquetes activaron LFS upstream y manejaron la ruptura de ABI, pero el problema no quedó resuelto por completo
  • El soporte time64 de glibc requiere usar LFS, así que el diseño resuelve a la vez los problemas de tamaño de archivo y de tiempo
  • En sistemas de 32 bits existen tres sub-ABI
    • ABI tradicional: tipos de 32 bits
    • LFS: off_t de 64 bits, ino_t de 64 bits, time_t de 32 bits
    • time64: LFS + time_t de 64 bits
  • Una misma compilación de glibc es compatible con las tres variantes, pero las bibliotecas que usan estos tipos en su API no pueden mezclar entre sí las tres variantes

Cómo se rompe realmente el ABI

  • Cuando time_t cambia de 32 a 64 bits, cambia la disposición de las estructuras en memoria
    • en una estructura de ejemplo con int a, time_t b, int c en ese orden, el offset de c difiere entre time_t de 32 bits y de 64 bits
    • si se mezclan binarios time32 y time64, pueden leer o escribir campos equivocados, e incluso acceder fuera de rango
  • El tamaño de struct stat también cambia según el ABI
    • valor predeterminado de glibc en x86 de 32 bits: 88 bytes
    • LFS: 96 bytes
    • LFS + time64: 108 bytes
  • Incluso sin usar estructuras, los argumentos de función causan problemas
    • en x86 los argumentos de función se pasan por la pila
    • si uno de los argumentos es time_t, cambia la posición en la pila de los argumentos que vienen después
  • En el experimento de ejemplo, un programa time32 enlazado con una biblioteca recompilada como time64 termina con valores corruptos
    • la salida original era a = 1, un valor de tiempo normal y c = 3
    • si solo la biblioteca se recompila con -D_FILE_OFFSET_BITS=64 -D_TIME_BITS=64, b y c se interpretan incorrectamente
  • Hoy no existe una protección efectiva para impedir esta mezcla de ABI, por lo que puede haber rupturas en tiempo de ejecución y problemas de seguridad

Por qué la transición en Gentoo es más difícil

  • En distribuciones binarias, todos los paquetes se recompilan primero y luego el usuario actualiza en una etapa relativamente atómica
    • puede haber problemas con repositorios de terceros o programas compilados localmente, pero en conjunto el proceso es relativamente seguro
  • Gentoo tiene que cambiar el ABI en el mismo lugar mientras recompila @world
    • entre la recompilación de dos paquetes pueden mezclarse ABI incompatibles
    • si algunas recompilaciones fallan, el sistema puede quedar en un estado a medio migrar
    • por dependencias circulares, recompilar paquetes necesarios puede romper las herramientas de compilación y volver imposible continuar con la recompilación

Medidas de mitigación en evaluación

  • Hay tres líneas principales en discusión
    • cambiar CHOST, el triplete de plataforma, para distinguir el nuevo ABI del ABI tradicional de 32 bits
    • cambiar el libdir del nuevo ABI para instalar las bibliotecas recompiladas por separado de las bibliotecas existentes
    • introducir una distinción de ABI a nivel binario para impedir que binarios de sub-ABI distintos se enlacen entre sí
  • Los tres métodos pueden implementarse con cierto grado de independencia, aunque algunos pueden depender de otros
  • Las cadenas de ejemplo del artículo podrían no coincidir con las cadenas reales de la solución final

Distinguir el ABI con CHOST

  • El triplete de plataforma identifica la plataforma objetivo de la toolchain, y en Gentoo también se usa para distinguir de forma única el ABI dentro del soporte multilib
  • El triplete se compone de cuatro partes: arquitectura, vendor, sistema operativo y libc
    • ejemplo: i386-pc-linux-gnu
    • ejemplo: i686-pc-linux-gnu
    • ejemplo: i686-unknown-linux-gnu
  • Al introducir un ABI nuevo, se ha usado cambiar el campo vendor o añadir la marca del ABI al campo libc
    • en el ABI hardfloat de ARM se usaron formas como armv7a-hardfloat-linux-gnueabi y armv7a-unknown-linux-gnueabihf
  • Para el ABI time64 hay opciones similares
    • i686-gentoo_t64-linux-gnu
    • i686-pc-linux-gnut64
    • armv7a-gentoo_t64-linux-gnueabihf
    • armv7a-unknown-linux-gnueabihft64
  • El cambio de triplete no parece requerir demasiados parches
    • la toolchain GNU y el sistema de build GNU ignoran lo que viene después de gnu en el campo libc
    • Clang sí necesitaría parches para seleccionar automáticamente el ABI correcto según el triplete

Cambio de libdir y preserved-libs

  • libdir es el nombre base del directorio donde se instalan bibliotecas
    • el valor predeterminado habitual es lib
    • en arquitecturas con soporte de 64 bits suele usarse por convención lib64
    • el ABI x32 de x86 usa libx32, y el ABI n32 de MIPS usa lib32
  • Se evalúa cambiar libdir de lib a algo como libt64 para el ABI de 32 bits con time64
  • Un libdir separado ayudaría a reducir la mezcla de ABI durante la transición
    • reduce el riesgo de que ejecutables time64 enlacen por error con bibliotecas time32
    • permite conservar bibliotecas time32 mediante la función preserved-libs de Portage
    • opcionalmente se podría ofrecer un perfil multilib time32 + time64 para mantener compatibilidad con aplicaciones precompiladas existentes de time32
  • Con preserved-libs, los ejecutables existentes seguirían usando bibliotecas time32 hasta ser recompilados, mientras que las bibliotecas recompiladas como time64 se instalarían en el nuevo libdir
  • Cambiar libdir requiere parches en la toolchain
    • glibc podría manejarse de forma especial porque el mismo conjunto de bibliotecas sirve para varios sub-ABI
    • podría hacer falta un ld.so separado para que el .interp de los ejecutables time64 apunte al ld.so correspondiente a time64
  • Para un soporte multilib adecuado, también hace falta un triplete de plataforma específico para ese ABI

Marcar la incompatibilidad a nivel binario

  • Cuando se mezclan binarios de ABI distintos, normalmente el linker o el cargador dinámico deberían bloquearlo
    • si un programa de 64 bits enlaza con una biblioteca de 32 bits, el linker lo rechaza con file in wrong format
    • el cargador dinámico también lo rechaza con errores como wrong ELF class: ELFCLASS32
  • Para distinguir ABI existentes se usan varios mecanismos
    • ELFCLASS32 y ELFCLASS64
    • identificadores de máquina como EM_386 y EM_X86_64
    • el campo flags en ARM y MIPS
    • secciones de atributos específicas de cada arquitectura
  • Para time32 y time64 hace falta un mecanismo similar, pero no es sencillo
    • no parece existir un mecanismo general reutilizable
    • hace falta una solución que funcione en varias arquitecturas
    • una opción realista sería añadir una nueva sección ELF note e implementar soporte en la toolchain
  • También hay que considerar que el usuario podría desactivar la protección
    • si software precompilado sin código fuente no llama a APIs que usan time_t, podría seguir funcionando con las bibliotecas del sistema
    • una medida que lo impida siempre podría ser peor que el problema original
  • Si se usa un libdir separado, se puede hacer una verificación QA no fatal relativamente simple
    • se distingue a los ejecutables time64 mediante .interp
    • se verifica que programas time32 no carguen bibliotecas desde libt64
    • se verifica que programas time64 no carguen bibliotecas directamente desde lib

Límites de las aplicaciones precompiladas de 32 bits

  • Aparte de los paquetes compilados desde código fuente, en x86 y PowerPC existen aplicaciones que solo se distribuyen como binarios precompilados antiguos
    • esto aplica especialmente a software propietario y juegos viejos
  • Esas aplicaciones enfrentan tanto problemas de compatibilidad con las bibliotecas del sistema como el propio problema de 2038
  • Para los problemas de compatibilidad, la estructura multilib actual ofrece cierta solución
    • en amd64 ya existen mecanismos para soportar software de 32 bits, con un layout multilib y compilación de múltiples versiones de bibliotecas
    • eso puede extenderse distinguiendo abi_x86_32 y abi_x86_t64
    • se podría crear un nuevo perfil x86 multilib que soporte ambos ABI
  • El fallo de los programas de 32 bits en sí después de 2038 sigue siendo un problema más difícil
    • es posible controlar la hora del sistema con faketime
    • también es posible ejecutar una VM con el reloj retrocedido

Corrección del 2024-09-30: solo con libdir no alcanza

  • La idea inicial era demasiado optimista, y cambiar solo libdir no permite una separación estable
  • Como todos los libdir aparecen listados en ld.so.conf, no se puede depender de hardcodear rutas de libdir dentro de ld.so
    • incluso en prefijos LLVM personalizados ya se ajustan rutas, y ese caso también requeriría manejo especial
  • Por eso, el cambio de libdir probablemente tenga que depender de una distinción de incompatibilidad binaria
  • Hay tres objetivos básicos que deben cumplirse
    • el cargador dinámico debe distinguir entre binarios time32 y time64
    • todos los binarios sin una marca time64 explícita deben considerarse time32 por compatibilidad hacia atrás
    • todos los binarios nuevos deben llevar una marca time64 explícita, incluyendo binarios construidos en entornos no C como Rust
  • Esto implica un trabajo del nivel de parchear múltiples toolchains de múltiples lenguajes
    • es difícil que Gentoo lo mantenga solo de forma local y hace falta cooperación de varias partes
    • las arquitecturas objetivo suelen considerarse legacy o ya no reciben suficiente soporte
  • También queda la duda de si otras toolchains producirán ejecutables time64 correctos
    • si no se ajustan para respetar _TIME_BITS como hace un programa en C, podrían fijar un ancho específico para time_t y romperse
  • Como todos los binarios sin marca explícita time64 usarían bibliotecas time32, Gentoo dejaría de poder ejecutar binarios de terceros que no hayan sido parchados para llevar la marca correcta
  • También se evalúan alternativas con objetivos más modestos
    • inyectar RPATH en todos los ejecutables time64 para forzar directamente el libdir de time64
    • este método no impide por completo que el cargador dinámico use bibliotecas time32, pero podría facilitar la transición sin grandes problemas de compatibilidad
  • En sentido inverso, también se contempla no cambiar permanentemente el libdir time64 y cambiar temporalmente el libdir time32
    • se inyecta RPATH en programas existentes y se renombra el libdir
    • las nuevas bibliotecas time64 se instalan en el libdir tradicional
    • los nuevos programas time64 no tendrían un RPATH que fuerce bibliotecas time32
    • esto tendría la ventaja de mantener compatibilidad con otras distribuciones que ya hicieron la transición

Tareas pendientes

  • Si se implementan las tres soluciones, se podría ofrecer una ruta de transición más limpia y relativamente segura para sistemas Gentoo de 32 bits con glibc
  • Aun así, estas soluciones aplican sobre todo a paquetes compilados desde código fuente
  • Las aplicaciones precompiladas de 32 bits seguirán teniendo el problema de 2038, incluso si se mantiene la compatibilidad de ABI
  • El diseño completo sigue siendo un borrador y puede cambiar conforme avancen los experimentos, las discusiones y el envío de parches

1 comentarios

 
GN⁺ 2024-09-30
Opiniones de Hacker News
  • En Gentoo hay algunas opciones que el artículo no cubre, y parece que se omitieron porque, por el diseño del sistema de Gentoo, implican mucho trabajo

    1. Permitir compilar un paquete como objetivo sin tener que instalarlo. El punto clave es que en Gentoo la compilación y la instalación de paquetes son un solo paso, así que no se pueden compilar primero varios elementos que dependen entre sí y luego instalar atómicamente los resultados. Durante una actualización en la que ocurre un cambio de ABI, el sistema es propenso a quedar parcialmente roto
    2. Extender la gestión de versiones de .so habitual para reflejar también los cambios de ABI de los paquetes dependientes. Normalmente, las bibliotecas compartidas llevan números de versión tanto en el nombre de archivo como en la versión interna, como libfoo.so.1.0.0, y el paquete rastrea las rupturas de su propia ABI. Para soportar time_t de 64 bits, habría que agregar un componente de versión controlado por la ABI de dependencia de cada .so. El resultado sería parecido a lo que el artículo llama “usar otro libdir”, pero aunque podría convertirse en una base reutilizable para futuros cambios de ABI, probablemente sería mucho más invasivo
    • Para “compilar sin instalar”, lo que mejor parece encajar son las actualizaciones parciales por etapas
      Se podrían programar varias compilaciones de paquetes nuevos y compilarlas en un sandbox; luego, las compilaciones nuevas mirarían primero el sandbox mediante una unión y harían fallback al sistema. Cuando todo esté compilado, se empaquetan los resultados y se mueven del sandbox al sistema real. Así toda la actualización de Gentoo podría volverse transaccional, lo que también traería grandes beneficios en otros aspectos
    • Gentoo ya soporta el punto 1 mediante la opción de especificar el directorio donde se instalará la imagen terminada. Basta con cambiar ROOT, que normalmente se configura como /
      Se puede recompilar todo @system y @world, instalarlo en el subdirectorio indicado y luego sincronizarlo todo de una vez. Si es posible, conviene hacerlo desde una sesión live; en teoría también sería posible hacer un bind mount de / en un subdirectorio de la nueva ubicación instalada, entrar con chroot y sincronizar hacia el / superior real
      https://devmanual.gentoo.org/ebuild-writing/variables/#root
    • Gentoo ya conserva las bibliotecas anteriores hasta que todas las dependencias se actualizan, así que este problema puede resolverse codificando el cambio de ABI en la arquitectura y el SONAME, que es donde originalmente deberían registrarse los cambios de ABI
  • La forma en que Mac OS X manejó off_t e ino_t podría servir de pista. Las llamadas y estructuras existentes mantuvieron su comportamiento, se agregaron nuevas llamadas y tipos con el sufijo 64, y se podía elegir el destino real de las referencias mediante macros del preprocesador, aunque rara vez se usaban directamente
    En cambio, el OS y el SDK están versionados, y al compilar se puede especificar la versión más antigua del OS en la que debe ejecutarse el binario. Con base en eso, los headers elegían automáticamente las macros adecuadas, y las anotaciones de API nuevas/deprecadas también usaban el mismo mecanismo para generar enlaces débiles o advertencias. Al principio se hacía con el preprocesador, pero hoy el compilador entiende de forma más sofisticada lo que Apple llama disponibilidad de API, así que parece posible aplicar el mismo enfoque en otras plataformas

    • No resuelve el problema central que describe el TFA. Las aplicaciones que usan distintas “versiones de OS objetivo de compilación” ya no pueden enlazarse entre sí
      Aunque estén ejecutándose en OS v.B, la aplicación X declarada para OS v.B podría no poder enlazarse con la aplicación Y declarada para OS v.A. En realidad, este enfoque se parece bastante a lo que ya hacen casi todas las plataformas; hacerlo de otra forma rompería de inmediato la compatibilidad con binarios existentes
    • Las bibliotecas auxiliares que no son glibc no van a definir varias copias de funciones según el tamaño de off_t, y tampoco habrá un switch en los headers que elija de forma transparente el conjunto correcto de funciones según el tamaño de tipo que quiera el programa cliente
      Aun así, el artículo enfatiza que time_t es un problema mayor que off_t. Una razón plausible es que time_t está mucho más extendido. off_t es un tipo POSIX que participa en relativamente pocas interfaces, mientras que time_t forma parte de ISO C y se usa por todos lados. Además, mucho código en C asume que time_t es un tipo entero del mismo ancho que int, mientras que esas suposiciones sobre off_t son menos comunes
    • Suena como una solución elegante, pero en la práctica se lee como un hack espantoso. Las macros sin tipo son una pesadilla con la que no quiero volver a lidiar
    • Solo funciona cuando se puede obligar a toda la plataforma a seguir el cambio. Es una buena solución, pero hay que controlar la biblioteca C. Gentoo no controla lo que hace la libc, y los usuarios pueden usar GNU libc, musl u otra cosa
  • Debian también fue muy doloroso. Es posible que algunos hayan terminado quemados, y mucha gente apuntaba a las distribuciones basadas en código fuente diciendo “para ellos será muy fácil”

    • Me gustaría ver material que detalle cómo fue de dolorosa la transición a time64 en Debian. Vista desde afuera, pareció relativamente fluida y sin demasiada polémica; por ejemplo, mucho mejor que la fusión de /usr
    • Hice las transiciones de m68k, powerpc y sh4, y también ayudé en parte con hppa; con la ayuda de otros desarrolladores de Debian, sigo vivo
    • Si “fácil” significa “decirles a los usuarios que recompilen todo de una vez”, entonces parece correcto
  • Cada vez que veo a alguien sufrir con este tipo de problemas, siento que fue una gran suerte que en los primeros ports de FreeBSD a amd64 se haya impulsado este cambio. Pudimos definir los tipos básicos de la ABI y decidimos mirar hacia el futuro en vez de al pasado.
    amd64 tenía una característica interesante que facilitó este trabajo. Como los argumentos de función de 32 bits se convertían automáticamente a 64 bits durante las llamadas a funciones, si se pasaba un entero de tiempo de 32 bits a una función que esperaba un time_t de 64 bits, en su mayoría simplemente funcionaba durante el trabajo inicial de la plataforma. Así que los detalles menores se podían dejar para después.
    En ese momento ya había otras plataformas de 64 bits, pero no tenían time_t de 64 bits, y FreeBSD/amd64 fue la primera de esa familia alrededor de 2003~2005. Según recuerdo, sparc64 también migró a time_t de 64 bits.
    El mayor problema era que en aquel entonces tzcode no era seguro para 64 bits. El algoritmo de normalización de struct tm caía en una condición degenerada en la que intentaba calcular iterativamente el día/mes/año de time_t(2^62). En vez de cambiar tzcode de forma importante, recuerdo que se hizo que fallara más o menos antes del año 1900 o después del año 10000. Es muy probable que upstream lo haya corregido hace mucho.
    Durante algunos años estuvimos jugando al whack-a-mole con confusiones de tiempo de 32/64 bits en código de terceros, causadas por tratar de forma descuidada int/long/time_t en estructuras de datos de archivos o de red, pero en general no fue un gran problema. Usar time_t de 64 bits desde el primer día evitó la mayoría de los problemas, y hacerlo desde el principio fue fácil. Linux perdió una gran oportunidad de hacer lo mismo cuando inició el port a amd64/x86_64.
    Además, en ese momento no pudimos terminar lo de ino_t de 64 bits. Los números de inode de 32 bits estaban expuestos en muchísimos lugares, como las estructuras on-disk del sistema de archivos y la estructura de directorios de UFS. Cuando FreeBSD/amd64 todavía era una plataforma de menor categoría, no había una forma realista de resolverlo desde el principio sin sacudir mucho otras arquitecturas tier-1. Hice el trabajo dos veces, pero al final otra persona lo terminó, y de paso se corrigieron constantes que eran demasiado cortas, como la longitud de la ruta de los mountpoints.

    • Según entiendo, todos los ports de Linux de 64 bits usaron desde el principio time_t, off_t e ino_t de 64 bits. El problema actual es migrar Linux de 32 bits a time_t de 64 bits.
    • FreeBSD también fue más agresivo con off_t, que es de 64 bits desde la versión 2.0. En las versiones de Linux de 32 bits todavía quedan rastros de los tamaños antiguos.
      Entiendo que la parte de que los argumentos de función de 32 bits se convierten automáticamente a 64 bits durante la llamada solo funciona con argumentos sin signo. Es porque al cargarlos en %edi se borra la parte superior de %rdi. La especificación de la SysV ABI para x86-64 no dice que todos los valores en registros o en la pila se extiendan al valor completo de 64 bits, y la nota sobre booleanos también dice algo como que solo importa el byte inferior, lo que sugiere que esa es la regla general.
    • Si lo que se quiere decir es que cuando apareció amd64 FreeBSD también porteó el time_t de i386 a 64 bits, eso me sorprende bastante. Me pregunto si otras arquitecturas de 32 bits, como Motorola 68000 o sparc32, también migraron a time_t de 64 bits.
  • En un viejo y gran sistema Unix de 32 bits, para manejar fechas futuras, una vez reemplacé las funciones de libc con time_t de 32 bits con signo por sus equivalentes con time_t de 32 bits sin signo. Con eso ganamos 68 años más después de 2038, y para entonces yo ya no estaré.
    La desventaja es que no se pueden representar fechas anteriores a 1970, el Unix epoch, pero como era un sistema de calendario, no importaba. Si las fechas pasadas fueran importantes, también se podría mover el epoch varias décadas o reducir la resolución temporal de 1 segundo a 2 segundos. Cada opción tiene problemas sutiles, así que depende del caso de uso.

    • Si podían cambiar todo el sistema de con signo a sin signo, me pregunto por qué no lo cambiaron a 64 bits.
  • En la página de manual original de BSD, la sección “Bugs” de tunefs tenía el famoso chiste “You can tune a file system, but you can't tune a fish.”, y según “Expert C Programming”, en el código fuente de esa página de manual había este comentario junto al chiste:
    “Si quitas esto, el demonio de UNIX te perseguirá en cuatro patas desde ahora hasta que time_t haga wrap around.”
    Cuando se escribió esa frase en los años 70, 2038 sin duda debía parecer un futuro inimaginablemente lejano.
    https://progforperf.github.io/Expert_C_Programming.pdf

  • Lo que más me queda es que, aunque respeto el esfuerzo, como usuario quisiera pasarme a una distribución no basada en código fuente como Debian y dar por terminado este problema.

    • Las dificultades de una distribución basada en código fuente parecen venir de intentar hacer una actualización in-place mientras se introduce un cambio incompatible en la ABI. Por eso cambiarse a una distribución completamente distinta puede ser tan disruptivo como hacer una instalación limpia de Gentoo usando la ABI nueva; quizá tome menos tiempo, pero al menos es del mismo nivel.
    • En Gentoo también hay una forma sencilla de hacerlo. Arrancas desde algo como un USB, ejecutas mkfs.ext4 o el sistema de archivos que uses sobre las particiones / y /usr, las montas, desempaquetas stage3, entras al chroot y ejecutas emerge $all-my-packages-that-where-installed-before-mkfs.
      En vez de actualizar gradualmente, puedes instalar una copia nueva de Gentoo.
    • La distinción de que basta con cambiarse a una distribución no basada en código fuente es un poco más sutil. Una distribución basada en código fuente como NixOS no tiene el mismo problema. La clave no es tanto si se compila desde código fuente, sino la forma en que Gentoo compila e instala los paquetes.
      Si hay software de terceros de código cerrado, incluso en un sistema binario todavía pueden aparecer problemas. También pueden aparecer en paquetes first-party que se instalan por separado como un paso independiente.
  • No soy experto en C, pero pensaba que alias de tipos como off_t se habían introducido para poder cambiarlos más adelante. Pero parece que claramente no funciona así; me pregunto si lo entendí mal.

    • Es la diferencia entre compatibilidad de código fuente y compatibilidad binaria. Si usas un typedef como off_t, normalmente no tienes que reescribir el código, pero sí tienes que recompilar todo lo que use ese tipo.
    • Hasta cierto punto funciona, pero no encaja bien en distribuciones basadas en código fuente. Si, después de cambiar la definición de off_t, pudieras reconstruir @world de forma atómica, no habría problema; pero las distribuciones basadas en código fuente no reconstruyen @world de forma atómica, sino paquete por paquete.
      Entonces puede pasar que libc.so use un off_t de 64 bits, mientras gcc fue compilado suponiendo un off_t de 32 bits, y que gcc se detenga. También podrían romperse paquetes necesarios para reconstruir @world, como bash, coreutils, make o binutils, y en ese punto quedas bloqueado. Por eso estas actualizaciones requieren cuidado.
    • Esa es apenas la primera pieza del rompecabezas, quizá solo media pieza. Como dice el artículo, en cuanto off_t entra en una estructura, se usa en una llamada a función o se integra en un protocolo, la abstracción desaparece y el tamaño real importa.
      Si mezclas código viejo y nuevo al cargar una biblioteca o comunicarte mediante un protocolo, los offsets empiezan a quedar corridos y comienzan los choques. Al final, la transición obliga a todos a separar los programas entre “legacy” y “portados o al menos revisados”, lo que es muy doloroso.
    • Los alias de tipos solo facilitan las cosas a nivel de código fuente. No son una abstracción real, y mucho menos completa. Por ejemplo, si cambias el tipo interno por un tipo de punto flotante, la semántica cambia mucho y queda totalmente expuesta al código del usuario.
      Incluso cambiarlo por un tipo más grande con semántica similar puede romper cosas. Un ejemplo simple es el padding de estructuras, y también hay muchos casos de uso donde se convierte un puntero a entero y luego de vuelta; si cambia la representación interna, inevitablemente puede romperse. Que eso sea una buena práctica es otro tema, pero no es algo raro. El punto clave es la compatibilidad ABI.
    • Funciona, pero el problema con un cambio de ABI es que, al hacerlo, hay que cambiar todo en todas partes al mismo tiempo. Básicamente no hay nada que impida enlazar una biblioteca compilada con off_t de 32 bits con otra compilada con off_t de 64 bits, y el comportamiento resultante puede ser muy impredecible.
  • En la estructura de ejemplo, se decía que el offset de c era 8 con time_t de 32 bits y 12 con el tipo de 64 bits, pero en realidad me parece que debería ser 16. Como b debe estar alineado a 64 bits, tendría que haber padding entre a y b. De hecho, eso refuerza aún más el argumento del autor.

    • La mayoría de las ABI de x86 no imponen padding para tipos de 64 bits, porque en esa época no existían cargas de 64 bits.
  • Viendo todo esto, la extraña representación del tiempo de Windows —contar en unidades de 100 ns con 64 bits desde el 1 de enero de 1601 a las 00:00 GMT, considerado según el calendario gregoriano— también tiene una pequeña ventaja. La resolución es excelente y seguirá funcionando incluso hasta que se conquiste toda la galaxia.