Skip to content

Latest commit

 

History

History
1839 lines (1404 loc) · 39.6 KB

File metadata and controls

1839 lines (1404 loc) · 39.6 KB

🔝 Retour au Sommaire

19.1.2 Shared Objects (.so) Linux

Introduction aux Shared Objects

Un Shared Object (objet partagé) est le format de bibliothèque partagée utilisé par Linux et les systèmes Unix. C'est l'équivalent Linux des DLL Windows. Ces fichiers portent généralement l'extension .so et suivent des conventions de nommage spécifiques.

Philosophie Unix/Linux

Sur Linux, tout repose sur les bibliothèques partagées :

  • La bibliothèque C standard : libc.so.6
  • Les bibliothèques graphiques : libX11.so, libgtk-3.so
  • Les pilotes : libGL.so (OpenGL)
  • Et des milliers d'autres...

Cette approche favorise :

  • La modularité du système
  • Les mises à jour de sécurité centralisées
  • L'économie de ressources

Format ELF (Executable and Linkable Format)

Les .so utilisent le format ELF, standard sur Linux :

┌─────────────────────────────────────┐
│       En-tête ELF (ELF Header)      │  ← Métadonnées du fichier
├─────────────────────────────────────┤
│    Table des en-têtes de programme  │  ← Segments pour l'exécution
├─────────────────────────────────────┤
│    Table des symboles (.symtab)     │  ← Symboles de débogage
├─────────────────────────────────────┤
│  Table des symboles dynamiques      │  ← Symboles exportés
│         (.dynsym)                   │
├─────────────────────────────────────┤
│    Section .text                    │  ← Code exécutable
├─────────────────────────────────────┤
│    Section .data                    │  ← Données initialisées
├─────────────────────────────────────┤
│    Section .bss                     │  ← Données non initialisées
├─────────────────────────────────────┤
│    Section .rodata                  │  ← Données en lecture seule
├─────────────────────────────────────┤
│    Autres sections...               │
└─────────────────────────────────────┘

Conventions de nommage Linux

Structure typique d'un nom de bibliothèque

Les bibliothèques Linux suivent une convention stricte :

lib<nom>.so.<version majeure>.<version mineure>.<version patch>

Exemples :

  • libmath.so.1.2.3
  • libcurl.so.4.6.0
  • libpthread.so.0

Anatomie du nom

libmylib.so.2.5.1
│   │    │  │ │ │
│   │    │  │ │ └─ Patch (corrections de bugs)
│   │    │  │ └─── Mineure (nouvelles fonctionnalités)
│   │    │  └───── Majeure (incompatibilités)
│   │    └──────── Extension Shared Object
│   └───────────── Nom de la bibliothèque
└───────────────── Préfixe standard

Liens symboliques (symlinks)

Linux utilise des liens symboliques pour la gestion des versions :

libmylib.so.2.5.1    ← Fichier réel  
libmylib.so.2        → libmylib.so.2.5.1  (lien symbolique)  
libmylib.so          → libmylib.so.2      (lien symbolique)

Utilité :

  • libmylib.so : Utilisé lors de la compilation (développement)
  • libmylib.so.2 : Utilisé lors de l'exécution (runtime)
  • libmylib.so.2.5.1 : Fichier réel avec version complète

Exemple :

ls -la /usr/lib/x86_64-linux-gnu/libz.so*  
lrwxrwxrwx 1 root root     13 libz.so -> libz.so.1.2.11  
lrwxrwxrwx 1 root root     13 libz.so.1 -> libz.so.1.2.11
-rw-r--r-- 1 root root 116960 libz.so.1.2.11

Créer votre première Shared Object

Structure de base

Contrairement à Windows, pas besoin de directive spéciale. Toutes les fonctions publiques sont exportées par défaut.

library libmylib;

{$mode objfpc}{$H+}

uses
  SysUtils;

// Fonction à exporter
function Addition(a, b: Integer): Integer; cdecl; public;  
begin
  Result := a + b;
end;

// Procédure à exporter
procedure AfficherMessage(msg: PChar); cdecl; public;  
begin
  WriteLn('Message reçu : ', msg);
end;

// Fonction de version
function GetVersion: PChar; cdecl; public;  
begin
  Result := '1.0.0';
end;

// Section exports - OBLIGATOIRE
exports
  Addition,
  AfficherMessage,
  GetVersion;

begin
  // Initialisation (optionnel)
end.

Points clés :

  • Convention cdecl (standard C sur Linux)
  • Mot-clé public pour rendre visible
  • Directive exports liste les symboles exportés
  • Pas de décoration de noms comme sur Windows

Compilation basique

Pour compiler une bibliothèque partagée :

fpc -olibmylib.so libmylib.pas

Le compilateur génère automatiquement :

  • Le fichier .so principal
  • Position Independent Code (PIC) nécessaire

Compilation avec options recommandées

fpc -olibmylib.so -CX -XX -Xs libmylib.pas

Options :

  • -CX : Smart linking
  • -XX : Smart linking plus agressif
  • -Xs : Strip des symboles de débogage

Pour la version de développement :

fpc -g -olibmylib.so libmylib.pas

L'option -g inclut les symboles de débogage.

Créer les liens symboliques

Créez manuellement les liens de version :

# Créer le lien pour le développement
ln -s libmylib.so libmylib.so.1

# Créer le lien pour le runtime
ln -s libmylib.so.1 libmylib.so.1.0.0

# Ou inversement (plus logique)
# Fichier réel
mv libmylib.so libmylib.so.1.0.0

# Liens symboliques
ln -s libmylib.so.1.0.0 libmylib.so.1  
ln -s libmylib.so.1 libmylib.so

Vérifier la bibliothèque

Lister les symboles exportés :

nm -D libmylib.so | grep "T "

Sortie exemple :

0000000000001150 T Addition
0000000000001170 T AfficherMessage
0000000000001190 T GetVersion

Afficher les informations ELF :

readelf -h libmylib.so

Vérifier les dépendances :

ldd libmylib.so

Sortie exemple :

linux-vdso.so.1 (0x00007ffd2e3f1000)  
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8b9c200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8b9c600000)

Utilisation depuis FreePascal

Liaison statique (à la compilation)

program TestLib;

{$mode objfpc}{$H+}

uses
  SysUtils;

// Déclaration des fonctions externes
function Addition(a, b: Integer): Integer; cdecl; external 'mylib';  
procedure AfficherMessage(msg: PChar); cdecl; external 'mylib';  
function GetVersion: PChar; cdecl; external 'mylib';

begin
  WriteLn('Version de la bibliothèque : ', GetVersion);
  WriteLn('5 + 3 = ', Addition(5, 3));
  AfficherMessage('Hello depuis l''application !');
end.

Compilation :

fpc -o testlib testlib.pas

Exécution :

./testlib

Note : Le nom 'mylib' sera transformé en libmylib.so automatiquement.

Spécifier le chemin complet

Si la bibliothèque n'est pas dans les chemins standards :

function Addition(a, b: Integer): Integer; cdecl;
  external '/home/user/libs/libmylib.so';

Ou avec une constante :

const
  {$IFDEF CPUX64}
  MYLIB_PATH = '/usr/lib/x86_64-linux-gnu/libmylib.so';
  {$ELSE}
  MYLIB_PATH = '/usr/lib/i386-linux-gnu/libmylib.so';
  {$ENDIF}

function Addition(a, b: Integer): Integer; cdecl; external MYLIB_PATH;

Liaison dynamique (au runtime)

Pour charger la bibliothèque dynamiquement :

program ChargementDynamique;

{$mode objfpc}{$H+}

uses
  SysUtils, dl;

type
  TAdditionFunc = function(a, b: Integer): Integer; cdecl;
  TGetVersionFunc = function: PChar; cdecl;

var
  LibHandle: Pointer;
  Addition: TAdditionFunc;
  GetVersion: TGetVersionFunc;
  Erreur: PChar;

begin
  // Charger la bibliothèque
  LibHandle := dlopen('libmylib.so', RTLD_LAZY);

  if LibHandle = nil then
  begin
    Erreur := dlerror();
    WriteLn('Erreur lors du chargement : ', Erreur);
    Halt(1);
  end;

  try
    // Charger les fonctions
    Addition := TAdditionFunc(dlsym(LibHandle, 'Addition'));
    if Addition = nil then
    begin
      WriteLn('Fonction Addition non trouvée');
      Halt(1);
    end;

    GetVersion := TGetVersionFunc(dlsym(LibHandle, 'GetVersion'));
    if GetVersion = nil then
    begin
      WriteLn('Fonction GetVersion non trouvée');
      Halt(1);
    end;

    // Utilisation
    WriteLn('Version : ', GetVersion);
    WriteLn('10 + 15 = ', Addition(10, 15));

  finally
    // Décharger la bibliothèque
    dlclose(LibHandle);
  end;
end.

Fonctions dlopen :

  • dlopen() : Charge la bibliothèque
  • dlsym() : Récupère l'adresse d'un symbole
  • dlerror() : Récupère le dernier message d'erreur
  • dlclose() : Décharge la bibliothèque

Flags pour dlopen :

  • RTLD_LAZY : Résolution paresseuse (à la demande)
  • RTLD_NOW : Résolution immédiate (toutes les fonctions)
  • RTLD_GLOBAL : Symboles disponibles pour les bibliothèques chargées après
  • RTLD_LOCAL : Symboles privés (défaut)

Chemins de recherche des bibliothèques

Ordre de recherche

Linux cherche les bibliothèques dans cet ordre :

  1. Répertoires spécifiés dans RPATH (compilé dans l'exécutable)
  2. Variable d'environnement LD_LIBRARY_PATH
  3. Répertoires dans /etc/ld.so.conf
  4. Répertoires système standards :
    • /lib
    • /usr/lib
    • /lib64
    • /usr/lib64
    • /usr/local/lib

Voir les chemins actuels

ldconfig -v 2>/dev/null | grep -v ^$'\t'

Variable LD_LIBRARY_PATH

Ajouter temporairement un chemin :

export LD_LIBRARY_PATH=/home/user/mylibs:$LD_LIBRARY_PATH
./mon_programme

Pour un programme spécifique :

LD_LIBRARY_PATH=/home/user/mylibs ./mon_programme

Attention : Ne modifiez pas LD_LIBRARY_PATH de manière permanente dans .bashrc - c'est une mauvaise pratique qui peut causer des problèmes système.

RPATH et RUNPATH

Intégrer le chemin directement dans l'exécutable :

Avec fpc :

fpc -k'-rpath=/home/user/mylibs' -o testlib testlib.pas

Vérifier le RPATH :

readelf -d testlib | grep PATH

RPATH vs RUNPATH :

  • RPATH : Priorité haute, cherché avant LD_LIBRARY_PATH
  • RUNPATH : Priorité basse, cherché après LD_LIBRARY_PATH

Configuration système avec ldconfig

Pour installer une bibliothèque système :

  1. Copier la bibliothèque :
sudo cp libmylib.so.1.0.0 /usr/local/lib/
  1. Créer les liens symboliques :
cd /usr/local/lib  
sudo ln -s libmylib.so.1.0.0 libmylib.so.1  
sudo ln -s libmylib.so.1 libmylib.so
  1. Mettre à jour le cache :
sudo ldconfig
  1. Vérifier :
ldconfig -p | grep mylib

Fichier de configuration personnalisé

Créer /etc/ld.so.conf.d/myapp.conf :

/opt/myapp/lib

Puis :

sudo ldconfig

Gestion des versions et compatibilité

Versionnage SONAME

Le SONAME (Shared Object Name) est crucial pour la compatibilité binaire.

Structure :

libmylib.so.MAJOR

Règles de versionnage :

  • MAJOR : Incrémenté pour incompatibilité ABI
  • MINOR : Incrémenté pour nouvelles fonctionnalités compatibles
  • PATCH : Incrémenté pour corrections de bugs

Définir le SONAME à la compilation

Avec l'éditeur de liens :

fpc -k'-soname,libmylib.so.1' -olibmylib.so.1.0.0 libmylib.pas

Vérifier le SONAME :

readelf -d libmylib.so.1.0.0 | grep SONAME

Sortie :

0x000000000000000e (SONAME)    Library soname: [libmylib.so.1]

Installer plusieurs versions

Vous pouvez avoir plusieurs versions majeures simultanément :

/usr/lib/
├── libmylib.so.1.5.2  (ancienne version)
├── libmylib.so.1 → libmylib.so.1.5.2
├── libmylib.so.2.0.0  (nouvelle version)
├── libmylib.so.2 → libmylib.so.2.0.0
└── libmylib.so → libmylib.so.2  (développement)

Les anciennes applications utilisent libmylib.so.1, les nouvelles libmylib.so.2.

Script de gestion des versions

#!/bin/bash
# install_lib.sh

LIB_NAME=$1  
MAJOR=$2  
MINOR=$3  
PATCH=$4

FULL_NAME="${LIB_NAME}.so.${MAJOR}.${MINOR}.${PATCH}"  
SONAME="${LIB_NAME}.so.${MAJOR}"  
LINKNAME="${LIB_NAME}.so"

# Copier le fichier
sudo cp $FULL_NAME /usr/local/lib/

# Créer les liens
cd /usr/local/lib  
sudo ln -sf $FULL_NAME $SONAME  
sudo ln -sf $SONAME $LINKNAME

# Mettre à jour le cache
sudo ldconfig

echo "Bibliothèque installée :"  
ls -l /usr/local/lib/${LIB_NAME}.so*

Utilisation :

./install_lib.sh libmylib 1 0 0

Visibilité des symboles

Contrôle de l'export

Par défaut, tous les symboles publics sont exportés. Vous pouvez contrôler cela.

Méthode 1 : Attribut de visibilité

// Visible depuis l'extérieur
function FonctionPublique: Integer; cdecl; public;

// Caché (usage interne uniquement)
function FonctionPrivee: Integer; cdecl;
  attribute(visibility('hidden'));

Méthode 2 : Version script

Créez un fichier version.map :

VERS_1.0 {
    global:
        Addition;
        AfficherMessage;
        GetVersion;
    local:
        *;  # Tout le reste est privé
};

Compilation :

fpc -k'--version-script=version.map' -olibmylib.so libmylib.pas

Avantages :

  • Réduction de la taille de la table des symboles
  • Amélioration des performances de chargement
  • Prévention des conflits de symboles
  • Meilleure encapsulation

Vérifier les symboles exportés

# Tous les symboles
nm -D libmylib.so

# Uniquement les symboles publics
nm -D libmylib.so | grep " T "

# Symboles avec démangling C++
nm -DC libmylib.so

Types de symboles :

  • T : Code (text section)
  • D : Données initialisées
  • B : Données non initialisées (BSS)
  • U : Undefined (nécessite une autre bibliothèque)

Position Independent Code (PIC)

Qu'est-ce que le PIC ?

Le Position Independent Code permet à une bibliothèque d'être chargée à n'importe quelle adresse mémoire.

Pourquoi c'est important :

  • Sécurité : ASLR (Address Space Layout Randomization)
  • Partage en mémoire entre processus
  • Flexibilité du chargeur système

FreePascal et PIC

FreePascal génère automatiquement du PIC pour les bibliothèques partagées sur Linux.

Vérifier si une bibliothèque utilise PIC :

readelf -d libmylib.so | grep TEXTREL

Si la sortie est vide, c'est bon (PIC activé). Si vous voyez TEXTREL, le PIC n'est pas complet.

Forcer la génération PIC

fpc -Cg -olibmylib.so libmylib.pas

L'option -Cg force la génération PIC.

Performance du PIC

Le PIC a un léger coût en performance (environ 2-5%) dû à :

  • Indirection supplémentaire pour accéder aux données globales
  • Utilisation de la GOT (Global Offset Table)

Ce coût est négligeable par rapport aux bénéfices de sécurité et flexibilité.

Exemple complet : Bibliothèque de calcul matriciel

Créons une bibliothèque complète pour les opérations sur les matrices.

Code de la bibliothèque

library libmatrix;

{$mode objfpc}{$H+}

uses
  SysUtils;

type
  PMatrix = ^TMatrix;
  TMatrix = record
    rows: Integer;
    cols: Integer;
    data: PDouble;
  end;

const
  MATRIX_SUCCESS = 0;
  MATRIX_ERROR_NULL_POINTER = -1;
  MATRIX_ERROR_INVALID_DIMENSIONS = -2;
  MATRIX_ERROR_OUT_OF_MEMORY = -3;

var
  LastError: Integer = MATRIX_SUCCESS;
  LastErrorMsg: string = '';

// Créer une matrice
function matrix_create(rows, cols: Integer): PMatrix; cdecl; public;  
var
  m: PMatrix;
begin
  Result := nil;
  LastError := MATRIX_SUCCESS;

  if (rows <= 0) or (cols <= 0) then
  begin
    LastError := MATRIX_ERROR_INVALID_DIMENSIONS;
    LastErrorMsg := 'Dimensions invalides';
    Exit;
  end;

  try
    New(m);
    m^.rows := rows;
    m^.cols := cols;
    GetMem(m^.data, rows * cols * SizeOf(Double));
    FillChar(m^.data^, rows * cols * SizeOf(Double), 0);
    Result := m;
  except
    LastError := MATRIX_ERROR_OUT_OF_MEMORY;
    LastErrorMsg := 'Mémoire insuffisante';
  end;
end;

// Détruire une matrice
procedure matrix_destroy(m: PMatrix); cdecl; public;  
begin
  if m = nil then Exit;

  if m^.data <> nil then
    FreeMem(m^.data);
  Dispose(m);
end;

// Définir une valeur
function matrix_set(m: PMatrix; row, col: Integer; value: Double): Integer;
  cdecl; public;
begin
  LastError := MATRIX_SUCCESS;
  Result := MATRIX_SUCCESS;

  if m = nil then
  begin
    LastError := MATRIX_ERROR_NULL_POINTER;
    Result := LastError;
    Exit;
  end;

  if (row < 0) or (row >= m^.rows) or (col < 0) or (col >= m^.cols) then
  begin
    LastError := MATRIX_ERROR_INVALID_DIMENSIONS;
    Result := LastError;
    Exit;
  end;

  PDouble(m^.data + (row * m^.cols + col) * SizeOf(Double))^ := value;
end;

// Obtenir une valeur
function matrix_get(m: PMatrix; row, col: Integer; out value: Double): Integer;
  cdecl; public;
begin
  LastError := MATRIX_SUCCESS;
  Result := MATRIX_SUCCESS;
  value := 0;

  if m = nil then
  begin
    LastError := MATRIX_ERROR_NULL_POINTER;
    Result := LastError;
    Exit;
  end;

  if (row < 0) or (row >= m^.rows) or (col < 0) or (col >= m^.cols) then
  begin
    LastError := MATRIX_ERROR_INVALID_DIMENSIONS;
    Result := LastError;
    Exit;
  end;

  value := PDouble(m^.data + (row * m^.cols + col) * SizeOf(Double))^;
end;

// Addition de matrices
function matrix_add(a, b: PMatrix): PMatrix; cdecl; public;  
var
  result_matrix: PMatrix;
  i, j: Integer;
  val_a, val_b: Double;
begin
  Result := nil;
  LastError := MATRIX_SUCCESS;

  if (a = nil) or (b = nil) then
  begin
    LastError := MATRIX_ERROR_NULL_POINTER;
    Exit;
  end;

  if (a^.rows <> b^.rows) or (a^.cols <> b^.cols) then
  begin
    LastError := MATRIX_ERROR_INVALID_DIMENSIONS;
    LastErrorMsg := 'Dimensions incompatibles';
    Exit;
  end;

  result_matrix := matrix_create(a^.rows, a^.cols);
  if result_matrix = nil then Exit;

  for i := 0 to a^.rows - 1 do
    for j := 0 to a^.cols - 1 do
    begin
      matrix_get(a, i, j, val_a);
      matrix_get(b, i, j, val_b);
      matrix_set(result_matrix, i, j, val_a + val_b);
    end;

  Result := result_matrix;
end;

// Afficher une matrice (pour débogage)
procedure matrix_print(m: PMatrix); cdecl; public;  
var
  i, j: Integer;
  value: Double;
begin
  if m = nil then
  begin
    WriteLn('Matrix is NULL');
    Exit;
  end;

  WriteLn(Format('Matrix %dx%d:', [m^.rows, m^.cols]));
  for i := 0 to m^.rows - 1 do
  begin
    Write('[');
    for j := 0 to m^.cols - 1 do
    begin
      matrix_get(m, i, j, value);
      Write(Format('%8.2f', [value]));
      if j < m^.cols - 1 then Write(' ');
    end;
    WriteLn(']');
  end;
end;

// Obtenir le dernier code d'erreur
function matrix_get_last_error: Integer; cdecl; public;  
begin
  Result := LastError;
end;

// Obtenir le dernier message d'erreur
procedure matrix_get_last_error_message(buffer: PChar; size: Integer);
  cdecl; public;
begin
  if buffer <> nil then
    StrLCopy(buffer, PChar(LastErrorMsg), size - 1);
end;

// Version de la bibliothèque
function matrix_version: PChar; cdecl; public;  
begin
  Result := '1.0.0';
end;

exports
  matrix_create,
  matrix_destroy,
  matrix_set,
  matrix_get,
  matrix_add,
  matrix_print,
  matrix_get_last_error,
  matrix_get_last_error_message,
  matrix_version;

begin  
end.

Compilation

fpc -olibmatrix.so.1.0.0 -CX -XX libmatrix.pas

# Créer les liens
ln -s libmatrix.so.1.0.0 libmatrix.so.1  
ln -s libmatrix.so.1 libmatrix.so

Programme de test

program test_matrix;

{$mode objfpc}{$H+}

uses
  SysUtils;

type
  PMatrix = ^TMatrix;
  TMatrix = record
    rows: Integer;
    cols: Integer;
    data: PDouble;
  end;

// Déclarations externes
function matrix_create(rows, cols: Integer): PMatrix; cdecl; external 'matrix';  
procedure matrix_destroy(m: PMatrix); cdecl; external 'matrix';  
function matrix_set(m: PMatrix; row, col: Integer; value: Double): Integer;
  cdecl; external 'matrix';
function matrix_get(m: PMatrix; row, col: Integer; out value: Double): Integer;
  cdecl; external 'matrix';
function matrix_add(a, b: PMatrix): PMatrix; cdecl; external 'matrix';  
procedure matrix_print(m: PMatrix); cdecl; external 'matrix';  
function matrix_version: PChar; cdecl; external 'matrix';

var
  m1, m2, m3: PMatrix;
begin
  WriteLn('libmatrix version : ', matrix_version);
  WriteLn;

  // Créer deux matrices 2x2
  m1 := matrix_create(2, 2);
  m2 := matrix_create(2, 2);

  // Remplir la première matrice
  matrix_set(m1, 0, 0, 1.0);
  matrix_set(m1, 0, 1, 2.0);
  matrix_set(m1, 1, 0, 3.0);
  matrix_set(m1, 1, 1, 4.0);

  // Remplir la deuxième matrice
  matrix_set(m2, 0, 0, 5.0);
  matrix_set(m2, 0, 1, 6.0);
  matrix_set(m2, 1, 0, 7.0);
  matrix_set(m2, 1, 1, 8.0);

  // Afficher
  WriteLn('Matrice 1 :');
  matrix_print(m1);
  WriteLn;

  WriteLn('Matrice 2 :');
  matrix_print(m2);
  WriteLn;

  // Addition
  m3 := matrix_add(m1, m2);
  WriteLn('Matrice 1 + Matrice 2 :');
  matrix_print(m3);

  // Libération
  matrix_destroy(m1);
  matrix_destroy(m2);
  matrix_destroy(m3);

  WriteLn;
  WriteLn('Test terminé !');
end.

Compilation et exécution

# Compiler le test
fpc -o test_matrix test_matrix.pas

# Exécuter (si la bibliothèque est dans le répertoire courant)
LD_LIBRARY_PATH=. ./test_matrix

Sortie attendue :

libmatrix version : 1.0.0

Matrice 1 :
[    1.00     2.00]
[    3.00     4.00]

Matrice 2 :
[    5.00     6.00]
[    7.00     8.00]

Matrice 1 + Matrice 2 :
[    6.00     8.00]
[   10.00    12.00]

Test terminé !

Utilisation depuis C/C++

Une des forces des bibliothèques Linux est l'interopérabilité parfaite avec C.

Créer un fichier d'en-tête C

Fichier matrix.h :

#ifndef MATRIX_H
#define MATRIX_H

#ifdef __cplusplus
extern "C" {
#endif

// Structure opaque
typedef struct Matrix Matrix;

// Codes d'erreur
#define MATRIX_SUCCESS 0
#define MATRIX_ERROR_NULL_POINTER -1
#define MATRIX_ERROR_INVALID_DIMENSIONS -2
#define MATRIX_ERROR_OUT_OF_MEMORY -3

// Fonctions de la bibliothèque
Matrix* matrix_create(int rows, int cols);  
void matrix_destroy(Matrix* m);  
int matrix_set(Matrix* m, int row, int col, double value);  
int matrix_get(Matrix* m, int row, int col, double* value);  
Matrix* matrix_add(Matrix* a, Matrix* b);  
void matrix_print(Matrix* m);  
int matrix_get_last_error(void);  
void matrix_get_last_error_message(char* buffer, int size);  
const char* matrix_version(void);

#ifdef __cplusplus
}
#endif

#endif // MATRIX_H

Programme C utilisant la bibliothèque

Fichier test_matrix.c :

#include <stdio.h>
#include <stdlib.h>
#include "matrix.h"

int main() {
    Matrix *m1, *m2, *m3;

    printf("libmatrix version : %s\n\n", matrix_version());

    // Créer les matrices
    m1 = matrix_create(2, 2);
    m2 = matrix_create(2, 2);

    // Remplir m1
    matrix_set(m1, 0, 0, 1.0);
    matrix_set(m1, 0, 1, 2.0);
    matrix_set(m1, 1, 0, 3.0);
    matrix_set(m1, 1, 1, 4.0);

    // Remplir m2
    matrix_set(m2, 0, 0, 5.0);
    matrix_set(m2, 0, 1, 6.0);
    matrix_set(m2, 1, 0, 7.0);
    matrix_set(m2, 1, 1, 8.0);

    // Afficher
    printf("Matrice 1 :\n");
    matrix_print(m1);
    printf("\nMatrice 2 :\n");
    matrix_print(m2);

    // Addition
    m3 = matrix_add(m1, m2);
    printf("\nMatrice 1 + Matrice 2 :\n");
    matrix_print(m3);

    // Libération
    matrix_destroy(m1);
    matrix_destroy(m2);
    matrix_destroy(m3);

    printf("\nTest terminé !\n");
    return 0;
}

Compilation du programme C

# Compiler avec liaison dynamique
gcc -o test_matrix_c test_matrix.c -L. -lmatrix -Wl,-rpath,.

# Ou spécifier le chemin de recherche au runtime
gcc -o test_matrix_c test_matrix.c -L. -lmatrix  
LD_LIBRARY_PATH=. ./test_matrix_c

Options :

  • -L. : Chercher les bibliothèques dans le répertoire courant
  • -lmatrix : Lier avec libmatrix.so
  • -Wl,-rpath,. : Ajouter le répertoire courant au RPATH

Utilisation depuis Python avec ctypes

Fichier test_matrix.py :

#!/usr/bin/env python3
import ctypes  
from ctypes import c_int, c_double, c_char_p, POINTER

# Charger la bibliothèque
lib = ctypes.CDLL('./libmatrix.so')

# Définir les types
class Matrix(ctypes.Structure):
    pass

MatrixPtr = POINTER(Matrix)

# Déclarer les fonctions
lib.matrix_create.argtypes = [c_int, c_int]  
lib.matrix_create.restype = MatrixPtr

lib.matrix_destroy.argtypes = [MatrixPtr]  
lib.matrix_destroy.restype = None

lib.matrix_set.argtypes = [MatrixPtr, c_int, c_int, c_double]  
lib.matrix_set.restype = c_int

lib.matrix_get.argtypes = [MatrixPtr, c_int, c_int, POINTER(c_double)]  
lib.matrix_get.restype = c_int

lib.matrix_add.argtypes = [MatrixPtr, MatrixPtr]  
lib.matrix_add.restype = MatrixPtr

lib.matrix_print.argtypes = [MatrixPtr]  
lib.matrix_print.restype = None

lib.matrix_version.argtypes = []  
lib.matrix_version.restype = c_char_p

# Utilisation
def main():
    print(f"libmatrix version : {lib.matrix_version().decode()}\n")

    # Créer les matrices
    m1 = lib.matrix_create(2, 2)
    m2 = lib.matrix_create(2, 2)

    # Remplir m1
    lib.matrix_set(m1, 0, 0, 1.0)
    lib.matrix_set(m1, 0, 1, 2.0)
    lib.matrix_set(m1, 1, 0, 3.0)
    lib.matrix_set(m1, 1, 1, 4.0)

    # Remplir m2
    lib.matrix_set(m2, 0, 0, 5.0)
    lib.matrix_set(m2, 0, 1, 6.0)
    lib.matrix_set(m2, 1, 0, 7.0)
    lib.matrix_set(m2, 1, 1, 8.0)

    # Afficher
    print("Matrice 1 :")
    lib.matrix_print(m1)
    print("\nMatrice 2 :")
    lib.matrix_print(m2)

    # Addition
    m3 = lib.matrix_add(m1, m2)
    print("\nMatrice 1 + Matrice 2 :")
    lib.matrix_print(m3)

    # Libération
    lib.matrix_destroy(m1)
    lib.matrix_destroy(m2)
    lib.matrix_destroy(m3)

    print("\nTest terminé !")

if __name__ == "__main__":
    main()

Exécution :

chmod +x test_matrix.py
./test_matrix.py

Débogage des bibliothèques partagées

Outils essentiels

ldd - Dépendances

Afficher les dépendances d'une bibliothèque :

ldd libmatrix.so

Sortie exemple :

linux-vdso.so.1 (0x00007ffc8b3e2000)  
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f9a2c400000)
/lib64/ld-linux-x86-64.so.2 (0x00007f9a2c800000)

Signification :

  • linux-vdso.so.1 : Bibliothèque virtuelle du kernel
  • libc.so.6 : Bibliothèque C standard
  • /lib64/ld-linux-x86-64.so.2 : Chargeur dynamique

Détecter les dépendances manquantes :

ldd libmatrix.so | grep "not found"

nm - Symboles

Lister tous les symboles :

nm libmatrix.so

Filtrer les symboles exportés :

nm -D libmatrix.so | grep " T "

Avec démangling (pour C++) :

nm -DC libmatrix.so

Types de symboles :

  • T : Symbole dans la section text (code)
  • D : Symbole dans la section data (données initialisées)
  • B : Symbole dans la section BSS (données non initialisées)
  • U : Symbole non défini (externe)
  • W : Symbole faible
  • t, d, b : Versions locales (minuscules)

objdump - Désassemblage

Afficher les informations de l'en-tête :

objdump -f libmatrix.so

Désassembler une fonction :

objdump -d libmatrix.so | grep -A 20 "matrix_create"

Afficher toutes les sections :

objdump -h libmatrix.so

readelf - Format ELF

Informations générales :

readelf -h libmatrix.so

Symboles dynamiques :

readelf -s libmatrix.so

Sections :

readelf -S libmatrix.so

Segments :

readelf -l libmatrix.so

Informations dynamiques :

readelf -d libmatrix.so

strace - Tracer les appels système

Voir quelles bibliothèques sont chargées :

strace -e openat ./test_matrix 2>&1 | grep ".so"

Tracer tous les appels liés aux fichiers :

strace -e trace=file ./test_matrix

ltrace - Tracer les appels de bibliothèque

ltrace ./test_matrix

Filtrer pour une bibliothèque spécifique :

ltrace -l libmatrix.so ./test_matrix

GDB avec bibliothèques partagées

Compiler avec symboles de débogage :

fpc -g -olibmatrix.so libmatrix.pas  
fpc -g -o test_matrix test_matrix.pas

Déboguer :

gdb ./test_matrix

Commandes GDB utiles :

# Lister les bibliothèques chargées
info sharedlibrary

# Mettre un point d'arrêt dans la bibliothèque
break matrix_create

# Exécuter
run

# Afficher la pile d'appels
backtrace

# Examiner les symboles
info functions matrix_

# Charger les symboles d'une bibliothèque
sharedlibrary libmatrix.so

Valgrind - Détection de fuites mémoire

Vérifier les fuites :

valgrind --leak-check=full ./test_matrix

Sortie exemple :

==12345== HEAP SUMMARY:
==12345==     in use at exit: 0 bytes in 0 blocks
==12345==   total heap usage: 10 allocs, 10 frees, 1,234 bytes allocated
==12345==
==12345== All heap blocks were freed -- no leaks are possible

Options utiles :

  • --leak-check=full : Détails complets sur les fuites
  • --show-leak-kinds=all : Tous les types de fuites
  • --track-origins=yes : Origine des valeurs non initialisées
  • --verbose : Mode verbeux

Problèmes courants et solutions

Problème 1 : Bibliothèque non trouvée

Erreur :

error while loading shared libraries: libmatrix.so: cannot open shared object file

Solutions :

# Solution 1 : LD_LIBRARY_PATH
export LD_LIBRARY_PATH=/chemin/vers/lib:$LD_LIBRARY_PATH

# Solution 2 : RPATH
fpc -k'-rpath=/chemin/vers/lib' -o test_matrix test_matrix.pas

# Solution 3 : Installation système
sudo cp libmatrix.so /usr/local/lib/  
sudo ldconfig

# Solution 4 : Chemin absolu
# Dans le code
external '/chemin/complet/libmatrix.so';

Problème 2 : Symbole non trouvé

Erreur :

undefined symbol: matrix_create

Diagnostic :

# Vérifier que le symbole existe
nm -D libmatrix.so | grep matrix_create

Solutions :

  • Vérifier que la fonction est dans la directive exports
  • Vérifier l'orthographe exacte
  • S'assurer que la convention d'appel est correcte
  • Recompiler la bibliothèque

Problème 3 : Version incompatible

Erreur :

version `GLIBC_2.34' not found

Diagnostic :

ldd --version  
ldd libmatrix.so

Solutions :

  • Compiler sur la version cible du système
  • Utiliser des bibliothèques statiques pour certaines dépendances
  • Lier statiquement avec libc si nécessaire

Problème 4 : Crash au chargement

Diagnostic avec ldd :

ldd -r libmatrix.so

L'option -r affiche les symboles non résolus.

Diagnostic avec GDB :

gdb ./test_matrix
(gdb) run
# Note le backtrace
(gdb) bt

Packaging et distribution

Créer un paquet Debian (.deb)

Structure du répertoire :

libmatrix-1.0.0/
├── DEBIAN/
│   └── control
├── usr/
│   ├── lib/
│   │   ├── libmatrix.so.1.0.0
│   │   ├── libmatrix.so.1 -> libmatrix.so.1.0.0
│   │   └── libmatrix.so -> libmatrix.so.1
│   ├── include/
│   │   └── matrix.h
│   └── share/
│       └── doc/
│           └── libmatrix/
│               ├── README
│               └── copyright

Fichier DEBIAN/control :

Package: libmatrix  
Version: 1.0.0  
Section: libs  
Priority: optional  
Architecture: amd64  
Depends: libc6 (>= 2.31)  
Maintainer: Votre Nom <email@example.com>  
Description: Bibliothèque de calcul matriciel
 Une bibliothèque pour effectuer des opérations
 sur des matrices en FreePascal.

Créer le paquet :

dpkg-deb --build libmatrix-1.0.0

Installer :

sudo dpkg -i libmatrix-1.0.0.deb

Désinstaller :

sudo dpkg -r libmatrix

Créer un paquet RPM

Fichier libmatrix.spec :

Name:           libmatrix  
Version:        1.0.0  
Release:        1%{?dist}  
Summary:        Bibliothèque de calcul matriciel  
License:        MIT  
URL:            https://example.com/libmatrix  
Source0:        libmatrix-%{version}.tar.gz

%description
Une bibliothèque pour effectuer des opérations  
sur des matrices en FreePascal.

%prep
%setup -q

%build
fpc -olibmatrix.so.1.0.0 libmatrix.pas

%install
mkdir -p %{buildroot}/usr/lib64  
cp libmatrix.so.1.0.0 %{buildroot}/usr/lib64/  
ln -s libmatrix.so.1.0.0 %{buildroot}/usr/lib64/libmatrix.so.1  
ln -s libmatrix.so.1 %{buildroot}/usr/lib64/libmatrix.so

%files
/usr/lib64/libmatrix.so.1.0.0
/usr/lib64/libmatrix.so.1
/usr/lib64/libmatrix.so

%post
/sbin/ldconfig

%postun
/sbin/ldconfig

%changelog
* Mon Jan 01 2025 Votre Nom <email@example.com> - 1.0.0-1
- Version initiale

Construire :

rpmbuild -ba libmatrix.spec

AppImage (portable)

Créer un AppImage autonome :

#!/bin/bash
# build_appimage.sh

APP_NAME="MyMatrixApp"  
APP_DIR="${APP_NAME}.AppDir"

# Créer la structure
mkdir -p $APP_DIR/usr/{bin,lib}

# Copier l'exécutable
cp test_matrix $APP_DIR/usr/bin/

# Copier les bibliothèques
cp libmatrix.so.1.0.0 $APP_DIR/usr/lib/  
ln -s libmatrix.so.1.0.0 $APP_DIR/usr/lib/libmatrix.so.1

# Créer le fichier .desktop
cat > $APP_DIR/${APP_NAME}.desktop << EOF
[Desktop Entry]
Name=MyMatrixApp  
Exec=test_matrix  
Icon=matrix  
Type=Application  
Categories=Utility;  
EOF

# Créer le fichier AppRun
cat > $APP_DIR/AppRun << 'EOF'
#!/bin/bash
SELF=$(readlink -f "$0")  
HERE=${SELF%/*}  
export LD_LIBRARY_PATH="${HERE}/usr/lib:${LD_LIBRARY_PATH}"  
exec "${HERE}/usr/bin/test_matrix" "$@"  
EOF  
chmod +x $APP_DIR/AppRun

# Générer l'AppImage
appimagetool $APP_DIR

Distribution via PPA (Ubuntu)

Pour distribuer via un PPA Ubuntu :

  1. Créer un compte Launchpad
  2. Préparer les fichiers source
  3. Créer le paquet source :
debuild -S -sa
  1. Uploader vers Launchpad :
dput ppa:votre-compte/ppa libmatrix_1.0.0_source.changes

Optimisations spécifiques Linux

Link-Time Optimization (LTO)

Activer LTO pour de meilleures optimisations :

fpc -olibmatrix.so -CX -XX -O3 -Cg -k'-flto' libmatrix.pas

Avantages :

  • Inlining entre unités de compilation
  • Élimination de code mort
  • Optimisations globales

Inconvénients :

  • Temps de compilation plus long
  • Taille de fichiers intermédiaires plus importante

Strip agressif

Réduire la taille au minimum :

# Strip standard
strip --strip-unneeded libmatrix.so

# Strip agressif
strip --strip-all libmatrix.so

# Conserver uniquement les symboles dynamiques
strip --strip-debug libmatrix.so

Comparaison :

Original :                200 KB
--strip-unneeded :        150 KB (-25%)
--strip-all :             120 KB (-40%)
--strip-debug :           180 KB (-10%)

Compression UPX

Compresser avec UPX (Universal Packer for eXecutables) :

upx --best libmatrix.so

Attention : Peut poser problème avec certains systèmes de sécurité.

Optimisations de compilation

Options recommandées pour production :

fpc -olibmatrix.so \
    -O3 \           # Optimisation maximale
    -CX \           # Smart linking
    -XX \           # Smart linking étendu
    -Xs \           # Strip symboles
    -Si \           # Inline automatique
    -Sc \           # Assertions désactivées
    -Sg \           # Goto supporté
    -vew \          # Warnings complets
    libmatrix.pas

Pour la taille minimale :

fpc -olibmatrix.so -Os -CX -XX -Xs libmatrix.pas

Pour la vitesse maximale :

fpc -olibmatrix.so -O3 -Ooregvar -Si libmatrix.pas

Sécurité

Hardening du compilateur

Activer les protections de sécurité :

fpc -olibmatrix.so \
    -k'-Wl,-z,relro' \        # RELRO (Relocation Read-Only)
    -k'-Wl,-z,now' \          # Bind now (pas de lazy binding)
    -k'-fstack-protector-strong' \  # Protection de pile
    libmatrix.pas

Vérifier les protections

Utiliser checksec :

checksec --file=libmatrix.so

Sortie :

RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  
Partial RELRO   Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH

Protections importantes :

  • RELRO : Protection contre l'écrasure de la GOT
  • Stack Canary : Détection de débordement de pile
  • NX : Non-Executable stack
  • PIE : Position Independent Executable
  • No RPATH : Pas de chemins codés en dur (sécurité)

Audit avec scanelf

scanelf -e libmatrix.so

Analyse de sécurité

Scanner les vulnérabilités connues :

# Analyser les dépendances
ldd libmatrix.so | while read lib; do
    dpkg -S $(echo $lib | awk '{print $3}') 2>/dev/null
done

# Vérifier les CVE connus
sudo apt install apt-listbugs  
apt-listbugs list libmatrix

Multi-architecture

Support 32/64 bits

Compiler pour différentes architectures :

64 bits (x86_64) :

fpc -Px86_64 -olibmatrix.so libmatrix.pas

32 bits (i386) :

fpc -Pi386 -olibmatrix.so.32 libmatrix.pas

ARM 64 bits (aarch64) :

fpc -Paarch64 -olibmatrix.so.arm64 libmatrix.pas

Installation multi-arch sur Debian/Ubuntu

Structure :

/usr/lib/
├── x86_64-linux-gnu/
│   └── libmatrix.so.1.0.0
├── i386-linux-gnu/
│   └── libmatrix.so.1.0.0
└── aarch64-linux-gnu/
    └── libmatrix.so.1.0.0

Cross-compilation

Depuis Ubuntu x86_64 vers ARM :

# Installer le cross-compilateur
sudo apt install fpc-3.2.2-arm-linux

# Compiler
fpc -Parm -olibmatrix.so.arm libmatrix.pas

Bonnes pratiques récapitulatives

✅ À faire

  1. Utiliser cdecl pour la compatibilité C
  2. Suivre les conventions de nommage (lib*.so.*)
  3. Définir un SONAME approprié
  4. Créer les liens symboliques correctement
  5. Documenter l'API (fichiers .h)
  6. Gérer les erreurs sans exceptions
  7. Tester sur plusieurs distributions
  8. Utiliser ldconfig après installation
  9. Fournir des fichiers pkg-config
  10. Versionner sémantiquement

❌ À éviter

  1. Exporter des types Pascal complexes
  2. Oublier d'exécuter ldconfig
  3. Modifier l'ABI sans changer SONAME
  4. Utiliser RPATH avec chemins absolus
  5. Négliger la thread-safety
  6. Ignorer les warnings du compilateur
  7. Oublier les symboles de débogage en dev
  8. Ne pas tester la liaison dynamique
  9. Distribuer sans documentation
  10. Coder en dur des chemins système

Checklist de distribution

Avant de distribuer votre bibliothèque partagée :

  • Testée sur Ubuntu, Debian, Fedora, Arch
  • Versions 32 et 64 bits si nécessaire
  • SONAME correctement défini
  • Liens symboliques créés
  • Fichiers .h fournis pour C/C++
  • Documentation API complète
  • Exemples de code pour plusieurs langages
  • Fichier pkg-config (.pc)
  • Tests automatisés
  • Pas de fuites mémoire (Valgrind)
  • Protections de sécurité activées
  • Paquet .deb ou .rpm disponible
  • Licence clairement définie
  • Changelog maintenu

Conclusion

Créer des Shared Objects sur Linux avec FreePascal permet :

  • Intégration parfaite dans l'écosystème Linux
  • Compatibilité avec tous les langages via l'ABI C
  • Distribution facile via les gestionnaires de paquets
  • Performance native optimale

Points essentiels à retenir :

  • ✅ Convention cdecl obligatoire
  • ✅ Nommage lib*.so.* standard
  • ✅ SONAME pour la gestion des versions
  • ✅ Liens symboliques appropriés
  • ✅ Installation via ldconfig
  • ✅ Documentation multilingue

Avec ces connaissances, vous pouvez créer des bibliothèques partagées Linux professionnelles, robustes et largement utilisables dans tout l'écosystème open source.

⏭️ Bindings C/C++ avancés