Olvidé que incluso escribí esta pregunta.
Algunas explicaciones están en orden primero:
- código PIC no puede ser cargado por el sistema operativo en cualquier posición de memoria en [más?] Sistema operativo moderno. Después de que todo está cargado, pasa por una fase que arregla el segmento de texto (donde termina el material ejecutable) para que aborde correctamente las variables globales; para lograr esto, el segmento de texto debe poder escribirse.
- Los datos ejecutables PIC pueden ser cargados una vez por el SO y compartidos entre múltiples usuarios/procesos. Para que el sistema operativo haga esto, sin embargo, el segmento de texto debe ser de solo lectura, lo que significa que no hay reparaciones. El código se compila para usar una Tabla de Compensación Global (GOT) para que pueda abordar los asuntos globales relativos al GOT, aliviando la necesidad de arreglos.
- Si un objeto compartido se crea sin PIC, aunque se recomienda enfáticamente, no parece que sea estrictamente necesario; si el sistema operativo debe reparar el segmento de texto, se lo fuerza a cargarlo en la memoria marcada como lectura-escritura ... lo que impide el uso compartido de procesos/usuarios.
- Si se construye un binario ejecutable/con/PIC, no sé qué va mal debajo del capó, pero he sido testigo de algunas herramientas se vuelven inestables (misteriosos bloqueos & similares).
Las respuestas:
- Mezcla PIC/no-PIC, o el uso de PIC en ejecutables puede causar difícil de predecir y localizar a inestabilidades. No tengo una explicación técnica de por qué.
- ... para incluir segfaults, errores de bus, corrupción de pila, y probablemente más.
- El no PIC en objetos compartidos probablemente no ocasione problemas graves, aunque puede generar más RAM si la biblioteca se utiliza muchas veces en procesos y/o usuarios.
actualización (4/17)
Desde entonces, he descubierto la causa de algunos de los accidentes que había visto con anterioridad. Para ilustrar:
/*header.h*/
#include <map>
typedef std::map<std::string,std::string> StringMap;
StringMap asdf;
/*file1.cc*/
#include "header.h"
/*file2.cc*/
#include "header.h"
int main(int argc, char** argv) {
for(int ii = 0; ii < argc; ++ii) {
asdf[argv[ii]] = argv[ii];
}
return 0;
}
... entonces:
$ g++ file1.cc -shared -PIC -o libblah1.so
$ g++ file1.cc -shared -PIC -o libblah2.so
$ g++ file1.cc -shared -PIC -o libblah3.so
$ g++ file1.cc -shared -PIC -o libblah4.so
$ g++ file1.cc -shared -PIC -o libblah5.so
$ g++ -zmuldefs file2.cc -Wl,-{L,R}$(pwd) -lblah{1..5} -o fdsa
# ^^^^^^^^^
# This is the evil that made it possible
$ args=(this is the song that never ends);
$ eval ./fdsa $(for i in {1..100}; do echo -n ${args[*]}; done)
Ese ejemplo particular puede no llegar a chocar, pero es básicamente la situación que existía en el código de ese grupo. Si se bloquea, es probable que esté en el destructor, por lo general un error doblemente libre.
Muchos años antes, agregaron -zmuldefs
a su versión para deshacerse de los errores de símbolos definidos de forma múltiple. El compilador emite código para ejecutar constructores/destructores en objetos globales. -zmuldefs
les obliga a vivir en el mismo lugar en la memoria, pero todavía ejecuta los constructores/destructores una vez para el archivo ejecutable y cada biblioteca que incluyó el encabezado ofensivo, de ahí el doble libre.