Par ce premier exemple, nous allons explorer ce que permet LD_PRELOAD.
2.1
Le problème
Parfois, je trouve que les messages d'erreur renvoyés par ma machine sont un peu agressifs.
S i s xxx . 1 çq
ls: impossible d'acceder a xxx.jpg: Aucun fichier ou dossier de ce
type
15
_ _ _56 Open S i l i c i u m Magazine N°4 www.opensilici u m.co m
Pourquoi tant de haine ?? ]e suis déjà désolé de ne pas retrouver mon fichier,j'apprécierais donc que cette machine fasse preuve d'un minimum de compassion à mon égard, plutôt que de me renvoyer un message si abrupt ! ]e crois même y discerner une vague insinuation que je ne tape pas les bonnes commandes ! Non mais c'est qui le chef ??
L D_P R E LOAD va nous permettre de lui apprendre la 'politesse,
2.2
Premier essai
La première étape est de trouver quelle fonction af
fiche ces messages insultants. Pour cela, on peut utiliser lt race, qui affiche les appels de fonction effectués par le processus :
S ltrace ·0 S i tty ' ls xxx,jpg 2>&1 1 sec -e s . */# ICI LE COUPABLE 1 =/ g '
1 l " � , ]
, �IIIIU I Uldliull ( ) = 0xb7797694
1
ff ICI LE COUPABLE ! ;;errorl� , l , �xh7;Rfba4 , ex826C,jeJ , extlS8fbdt, = �
eXlt i2 "Jnfln i shed "
�
1Normalement, c'est-à-dire sans l 'option - 0 <fichier>, ltrate affiche la trace des fonctions détectées sur la sortie d'erreur, Or, ls va envoyer son message d'erreur sur cette même sortie d'erreur, ce qui peut rendre les choses difficiles à lire, j'avoue que dans ce cas simple, la difficulté aurait été toute relative, mais il m'a été difficile de résister à chercher
la petite commande qui va bien. ;)
C'est donc apparemment la fonction e r ro r ( ) qui est responsable de ma mauvaise humeur.
Après avoir regardé un peu sa page de manuel, on peut redéfinir cette fonction pour imiter son comportement en l'adaptant à notre besoin,
Dans cette première version, pour simplifier, on ne va pas
gérer les deux premiers paramètres de cette fonction (status et errnum) et on sortira dans tous les cas à la fin (en théorie cela dépend de status).
#defl ne _GNU_SOURCE
#incl ude <std i o . h>
#i ncl ude <stda r g , h>
#incl ude <stdl i b . h;>
#define PREFIXUOLI "Monsieur, vous m ' en voyez desol ee, " \ 'ma i s je croi s que l a commande a renvoye une erreur . . . " \ 'je vous l ' i ndi que ci -apres . \n " \
"Je sui s a votre d i spos i t i on pour toute autre requete ; " \ 'je sui s enti erement devouee a vous satisfai re ."
void error ( i nt statu s , i nt errnum, const char *format , . . . }
Le monde merveilleux de LD]RELOAD
vaJ i st api
char *chai ne_formatee ;
Il formatons l a cha i ne de l ' erreur va_start(ap, format } ;
vasprintf(&chai ne_formatee , format , ap} ; va_end(ap} ;
Il affi chons cette chai ne avec un
LD PRELOAD
Il prefixe qui rend l es choses pl us agreabl es
fprintf( stde r r , "%s\n%s\n" , PREFIXUOLI , chai ne_forma tee } ; Il 1 i berons 1 a memoi re
free( chai ne_forma tee } ; li on qui tte exi t ( EXIUAI LURE} ;
La seule chose un peu moins évidente dans cette fonction, si vous n'y êtes pas habitué, est la gestion des arguments variables (avec les macros va_start ( ), va_end ( ) et la fonc
tion vasprintf( ), pour lesquelles vous pourrez au besoin consulter les pages de manuel correspondantes),
Compilons ce code (my _e rror. c) sous -la forme d'une librairie partagée libmy _e rror. so :
1 S gcc -Wa l l ·'PIC - c - c �Le'rnr n �y errnr c
1
i gcc - sha red ··PI: -11· .-SU'd lé WI . b�y " 1 I J I . c" I,llbmy_error.so�y e'rJ ' , Ü ,
- - - ----
--Ce petit makeiile nous permettra d'automatiser la chose :
CC=gcc LD=gcc
CFLAGS_OYNLIB=$ (CFLAGS} ·Wa l l ·fPIC LOFLAGS_OYNLIB=$( LOFLAGS } ·fPIC ·shared OBJECTS=mLerror . 0
a l l : l i bmLerror . so l i bmLerro r . so : $ ( OBJECTS )
$ ( lO) $ ( LOFLAGS_OYNLIB) ·Wl , · soname ·Wl , $@ .0 $@ $A
%.0: % . C
$(CC) $ ( CFLAGS_OYNLIB) ·c ·0 $ * . 0 $* . c
O n v a ensuite charger cette librairie partagée'au démar
rage de notre processus t5 xxx.j pg, en plus des librairies normales dont a besoin l'exécutable ls. Il suffit pour cela de définir la variable d'environnement LD]RELOAD avec le
chemin vers notre librairie :
Au lancement de cette commande t5 xxx.j pg, la fonction error ( ) sera donc définie avec exactement le même prototype
dans 2 librairies partagées différentes (la nôtre et la 'libc).
1 EN C O U V E RT U R E 1 R E P E R E S 1 E X P É R I M E NTATION 1 D O M OT I Q U f 1 RESI':AU 1
Mais, comme on parle de PREloading, notre librairie sera chargée la PREmière, et c'est donc elle qui recevra les appels à cette fonction.
Testons :
S i S xxx . j pg
Monsieur. vous m ' en voyez desol ee. mai s je croi s que l a commanae renvcye une erreur . . . je vous l i ndi QJe ci · aJres.
Je s u ' s a voUe c' spos ' , ; or Jour toute adtre reqlJete je sui s ert'ere�er, cevouee a VOJS sa:i sfa i re .
' mpos s ' b l e a ' a::éder à xxx . jJg 1
Aaaah c'est quand même plus agréable. :)
2.3
dlsym(RTLD_NEXT, ... )
,le chaînon manquant
La fonction error( ) de notre exemple était simple, donc facile à imiter. Mais si on veut implémenter toutes les carac
téristiques de cette fonction (gestion de status et errnum, écriture vers la sortie d'erreur et non pas la sortie standard, ajout du message correspondant à strerror(errno), etc.), on va finalement compliquer notre code de manière assez importante. Ce qu'on voudrait faire, c'est juste afficher notre message sympathique, puis passer la main à la fonction error( ) originelle, celle de la libc.
Le souci, c'est qu'en écrivant une fonction avec le même prototype, on a, en quelque sorte, caché cette fonction ori
ginelle : si on tente de l'appeler directement, c'est vers notre fonction que l 'on sera redirigé.
On peut néanmoins obtenir un pointeur vers cette fonction error( ) originelle de la manière suivante :
typeof(error) *Ubc_error;
[ . . . )
Ubc_error = dlsym(RTLD_NEXT, "error" ) ;
Quelques explications s'imposent. L a fonction dlsym O fait partie de la l ibrairie l i b dl. Cette librairie permet d'implémenter un système de plugins dans une application, le plus souvent en utilisant uniquement les 3 fonctions suivantes :
-dtopen O pour charger une librairie ;
-dlsym ( ) pour obtenir un pointeur vers un des symboles de cette librairie (en général, on lui passe un nom de fonction et on obtient un pointeur vers cette fonction) ; -dlclose ( ) une fois qu'on n'a plus besoin de cette librairie.
Notre technique basée sur LD_PRELOAD est un cas un peu particulier. Premièrement, les librairies en question, et plus particulièrement la libc, ont déjà été chargées au lan
cement du processus. Donc, on ne va pas utiliser dlopen ( )
n i dlcloseO, et dlsym O va devoir chercher le symbole dans ces librairies déjà chargées. Deuxièmement, on veut pointer sur la fonction e rrorO de la libc et non celle de libmy _error. 50. On doit donc indiquer à dlsym O qu'il faut commencer la recherche après la librairie courante, à savoir libmy _e rror. 50. En indiquant RTLD_NEXT comme premier argument. dlsym O va satisfaire ces deux exigences.
On sait maintenant tout ce qu'il faut pour écrire notre deuxième version :
#defi ne _GNUOURCE
#include <dlfcn,h> Il pour dl sym( )
#incl ude <stdio , h>
#incl ude <stda rg , h>
#incl ude <stdl i b . h>
#define PREFIXE_POLI "Monsieur, vous m ' en voyez desolee, " \ 'ma i s je croi s que l a commande a renvoye une erreur . . . " \
"je vous l ' i ndique c i · apres . \n " \
"Je sui s a votre di spos i t i on pour toute autre requete ; " \ 'je s u i s entierement devouee a vous sati sfai re . "
voi d erro r ( i n t status , i n t errnum, const char *Format , . . ,) ( typeof(error) *libc_error;
vaJ i st api
char *chai ne_formatee;
Il affi chons 1 e message sympathi que fpri ntH stder r , "%s\n " , PREFIXUOLI ) ; Il formatons l a chaine d e l ' erreur va_start(ap, forma t ) ;
vaspri ntf(&chai ne_formatee, format , ap) ; va_end(ap) ;
Il recuperons un poi nteur vers l a "vraie"
Il Fonct i on error ( ) de l a l i bc libc_error = dlsym(RTLD_NEXT, "error") ;
1 1 appelons cette fonct i on
(*libc_error) (status, errnum, "\5", chaine_Formatee) i
Il 1 i berons 1 a memoi re free( chai ne_forma tee ) ;
O n peut remarquer que c e code n'est guère plus complexe
que le précédent, alors que mine de rien on a mis en place toutes les fonctionnalités de la fonction error( i originelle ...
Testons :
!
! ma ke lee '1Ia" ·fP:C · e ·0 �y erro r . o �y errJ r . c ._---, lCC .tPI C · sha red ·',·il . · loname ·�· . i i bmy error . so -0 ' l bny err:r . SO r'Lerror . o: i LD _PRELOAD=! PIŒlibmL error. 50 I l xxx . j rg
'Ionsieur. vous m ' en vey'z desol ee. cd i , ,le uoi s que I d WII;lldlld" d renvoye Uêe erreur . . . j" VOliS l ' i nd i que ci · Jpr" ,; .