Shared libraries et modinfo

S'il est relativement simple d'ajouter des métadonnées aux dll dans Windows par le truchement des fichiers resource.h et <project>.rc, il n'y a pas de telle solution «standardisée» sous Linux pour les librairies partagées (shared libraries).

Pourtant, les modules du noyau Linux contiennent bien des métadonnées pouvant être extraites en utilisant l'utilitaire modinfo.

$ modinfo ipv6.ko
filename:       ipv6.ko
alias:          net-pf-10
license:        GPL
description:    IPv6 protocol stack for Linux
author:         Cast of dozens
srcversion:     0D921E6BFEEC960D6AB10EB
depends:
vermagic:       2.6.35.13-91.fc14.i686 SMP mod_unload 686
parm:           disable:Disable IPv6 module such that it is non-functional (int)
parm:           disable_ipv6:Disable IPv6 on all interfaces (int)
parm:           autoconf:Enable IPv6 address autoconfiguration on all interfaces (int)

Les kernel objects renferment en effet une section particulière, nommée modinfo, contenant des chaînes de caractères associant des clés à des valeurs (un peu à la manière d'un fichier ini).

Le contenu de cette section peut aisément être inspecté à l'aide de l'utilitaire objdump.

$ objdump --full-contents --section .modinfo ipv6.ko

ipv6.ko:     file format elf32-i386

Contents of section .modinfo:
 0000 616c6961 733d6e65 742d7066 2d313000  alias=net-pf-10.
 0010 7061726d 3d617574 6f636f6e 663a456e  parm=autoconf:En
 0020 61626c65 20495076 36206164 64726573  able IPv6 addres
 0030 73206175 746f636f 6e666967 75726174  s autoconfigurat
 0040 696f6e20 6f6e2061 6c6c2069 6e746572  ion on all inter
 0050 66616365 73000000 7061726d 74797065  faces...parmtype
 0060 3d617574 6f636f6e 663a696e 74000000  =autoconf:int...
 0070 7061726d 3d646973 61626c65 5f697076  parm=disable_ipv
 0080 363a4469 7361626c 65204950 7636206f  6:Disable IPv6 o
 0090 6e20616c 6c20696e 74657266 61636573  n all interfaces
 00a0 00000000 7061726d 74797065 3d646973  ....parmtype=dis
 00b0 61626c65 5f697076 363a696e 74000000  able_ipv6:int...
 00c0 7061726d 3d646973 61626c65 3a446973  parm=disable:Dis
 00d0 61626c65 20495076 36206d6f 64756c65  able IPv6 module
 00e0 20737563 68207468 61742069 74206973   such that it is
 00f0 206e6f6e 2d66756e 6374696f 6e616c00   non-functional.
 0100 7061726d 74797065 3d646973 61626c65  parmtype=disable
 0110 3a696e74 00000000 6c696365 6e73653d  :int....license=
 0120 47504c00 64657363 72697074 696f6e3d  GPL.description=
 0130 49507636 2070726f 746f636f 6c207374  IPv6 protocol st
 0140 61636b20 666f7220 4c696e75 78000000  ack for Linux...
 0150 61757468 6f723d43 61737420 6f662064  author=Cast of d
 0160 6f7a656e 73000000 73726376 65727369  ozens...srcversi
 0170 6f6e3d30 44393231 45364246 45454339  on=0D921E6BFEEC9
 0180 36304436 41423130 45420000 64657065  60D6AB10EB..depe
 0190 6e64733d 00000000 7665726d 61676963  nds=....vermagic
 01a0 3d322e36 2e33352e 31332d39 312e6663  =2.6.35.13-91.fc
 01b0 31342e69 36383620 534d5020 6d6f645f  14.i686 SMP mod_
 01c0 756e6c6f 61642036 38362000           unload 686 .

Il est d'ailleurs possible de retracer les symboles correspondants à ces chaînes de caractères.

$ nm ipv6.ko | grep "author"
00000150 r __mod_author66
$ nm ipv6.ko | grep "alias"
00000000 r __mod_alias1315
$ nm ipv6.ko | grep "description"
00000124 r __mod_description67
$ nm ipv6.ko | grep "vermagic"
00000198 r __mod_vermagic5
$ nm ipv6.ko | grep "license"
00000118 r __mod_license68

La macro permettant de placer des symboles dans la section modinfo des modules du noyau Linux est définie à la ligne 22 du fichier moduleparam.h. Voici une version épurée de cette dernière.

#define _CAT(a, b) a ## b
#define CAT(a, b) _CAT(a, b)

#define MODINFO(tag, info)                  \
static const char CAT(key, __LINE__)[]      \
    __attribute__((section(".modinfo")))    \
    __attribute__((__used__))               \
    __attribute__((unused))                 \
    __attribute__((aligned(1)))             \
    = tag "=" info

Le premier attribut indique à GCC de placer cette variable statique dans la section modinfo. L'attribut __used__ indique à GCC de ne pas retirer le symbole même si celui-ci n'est pas utilisé alors que l'attribut unused indique à GCC de ne pas générer d'avertissement même si la variable est inutilisée. Finalement, l'attribut aligned(1) indique à GCC de ne pas aligner la variable.

En supposant que la macro est placée dans un en-tête nommé modinfo.h, cette dernière peut être utilisée de la manière suivante — autant au sein d'une librairie que d'un exécutable:

#include <stdio.h>

#include "library.h"
#include "modinfo.h"

MODINFO("VERSION", "1.0");
MODINFO("NAME", "Foo");
MODINFO("COMPILER", __VERSION__);

void foo() {
    printf("Hello !\n");
}

Une fois ce fichier compilé, il est possible d'en extraire les informations placées dans la section modinfo de la même manière que pour les modules du noyau Linux.

$ gcc -fPIC -shared -o library.so library.c
$ modinfo library.so
filename:       library.so
VERSION:        1.0
NAME:           Foo
COMPILER:       4.5.1 20100924 (Red Hat 4.5.1-4)

Bref, une manière somme toute élégante et, surtout, robuste d'incorporer des métadonnées à l'intérieur d'une librairie partagée ou même d'un exécutable.