> For the complete documentation index, see [llms.txt](https://sansong.gitbook.io/cyber/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sansong.gitbook.io/cyber/rev/prologue.md).

# Prologue

Petit récap de pleins de choses utiles

**Que se passe-t-il quand j'exécute un programme sur mon ordinateur ?**

{% hint style="success" %}
Pour plus de détails sur le kernel, les appels systèmes, la pagination, le format ELF... allez lire le super article ["Putting the 'You' in CPU"](https://cpu.land/) de [Lexi Mattick](https://github.com/kognise) dont un certain nombre de schéma sont tirés.
{% endhint %}

## Sommaire

* Programme et assembleur
* Cycle fetch-execute
* Les registres
  * Architecture x86
* La mémoire
  * La pile (stack)
  * Le tas (heap)
  * Mémoire virtuelle
* Kernel, appels systèmes et interruptions
* Librairies et linking
* Pagination et mémoire virtuelle

## Exécution d'un programme

### Programme et assembleur

Quand on écrit un programme on utilise un **langage de programmation** par exemple (C, C++, Rust, Java, Python, etc.). Ces langages sont compris par les humains mais pas par les ordinateurs.

Exemple d'un programme simple en C:

```c
#include <stdio.h>

void hello(char* name) {
   printf("Hello %s", name);
}

int main(int argc, char** argv) {
   char name[] = "sam";
   hello(name);
   return 0;
}
```

Ainsi, un programme écrit dans un langage de programmation quelconque doit être traduit dans un langage compréhensible par l’ordinateur afin d’être exécuté : c’est le rôle du **compilateur**. Ce dernier transforme le code source d’un programme de haut niveau d’abstraction en **langage assembleur**. Il s’agit du plus bas niveau d’abstraction lisible par un humain et de l’unique langage exécutable par le processeur.

&#x20;Code assembleur de la fonction hello:

```c
0x0040155c:    push   ebp
0x0040155d:    mov    ebp,esp
0x0040155f:    sub    esp,0x18
0x00401562:    mov    eax,DWORD PTR [ebp+0x8]
0x00401565:    mov    DWORD PTR [esp+0x4],eax
0x00401569:    mov    DWORD PTR [esp],0x40a000
0x00401570:    call   0x401520 <printf>
0x00401575:    nop
0x00401576:    leave
0x00401577:    ret
```

Chaque famille de processeurs (x86, ARM, Mips, SPARC, PowerPC, etc.) possède un jeu d’instructions spécifique, l’architecture la plus représentée sur les ordinateurs personnels est x86 (développée par Intel et AMD). Le compilateur fournit ainsi un fichier exécutable par le processeur.

<figure><picture><source srcset="/files/Pa67fggfB6rlV7YxdnGt" media="(prefers-color-scheme: dark)"><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2F6jFASfHCzjoL7URQZYrh%2Fniveaux-de-langage.svg?alt=media&amp;token=a6645374-a363-4c24-b0cd-e7f4ad42eedb" alt=""></picture><figcaption><p>Différentes niveaux de langage</p></figcaption></figure>

{% hint style="info" %}
En réalité il existe 2 types de langage de programmation:

* les langages compilés (C, C++, Rust...)
* les langages interprétés (Python, Java...)

Les langages compilés produisent en sortie d'un compilateur un fichier exécutable par le système d'exploitation contrairement aux langages interprétés qui doivent fournir le programme compilé à un autre programme: l'interpréteur. Dans ce cas, l'interpréteur doit être installé sur la machine et le code est compatible avec tous les systèmes d'exploitation qui supportent l'interpréteur. Avec un langage compilé il faut à priori recompiler le code pour l'exécuter sur un autre ordinateur (l'exécutable n'étant pas compatible avec tous les OS).
{% endhint %}

### Cycle fetch-execute

Aujourd'hui l'architecture de la plupart des ordinateurs suit le schéma de von Neumann.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2F83izJ8qCPuYYOcjSghVM%2FArchitecture%20de%20Von%20Neumann.svg?alt=media&amp;token=5ef874f8-7f60-40e0-bdb8-d56073343af5" alt="" width="347"><figcaption><p>Architecture de Von Neumann</p></figcaption></figure>

Mon ordinateur est constitué de 3 composantes matérielles principales:

* le **processeur** (CPU) qui exécute du code
* la **mémoire** qui contient des données et du code
* des systèmes d'entrée/sortie pour intéragir avec des composantes extérieures (clavier, etc.)

Le processeur contient lui-même 3 composants principaux:

* une unité de contrôle
* des unités de stockage appelées **registres**
* une unité arithmétique et logique (UAL)

L’unité de contrôle récupère les instructions (langage machine) depuis la mémoire, les registres sont utilisés pour gagner du temps en évitant les accès mémoire et l’UAL exécute les instructions récupérées. Durant l’exécution d’un programme le processus consistant à récupérer et exécuter des instructions est répété jusqu’à la fin de celle-ci: c'est le **cycle fetch-execute (ou fetch-decode-execute)**.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FaBhbVoo6NX6o2dPxq2rb%2Ffetch-execute.svg?alt=media&amp;token=fd001046-4ec6-4ef8-9577-3e33eea2fe7a" alt="" width="359"><figcaption><p>Cycle fetch-execute</p></figcaption></figure>

### Les registres

On les a mentionné dans la partie d'avant, les **registres** sont des emplacements de mémoire situés directement sur le CPU. C'est la zone mémoire la **plus rapide d'accès de l'ordinateur**, ils permettent de gagner du temps en évitant des accès trop longs à la mémoire.

Voici quelques types de registres couramment présents sur un CPU:

1. **Registre d'instruction (IR)** : Il contient l'instruction actuellement en cours d'exécution par le CPU.
2. **Registre de données (DR)** : Il stocke les données qui sont lues depuis la mémoire ou écrites dans la mémoire.
3. **Registres d'adresse** : Ils contiennent les adresses mémoire des données sur lesquelles le CPU doit travailler.
4. **Registre de base (BX), Registre de pile (SP), Registre de pointeur d'instruction (IP)** : Ces registres sont utilisés dans diverses opérations de calcul et de gestion mémoire.
5. **Registre d'état (FLAGS)** : Il contient des indicateurs de statut tels que le drapeau de zéro (Z) indiquant si le résultat d'une opération est zéro, le drapeau de signe (S) indiquant si le résultat est négatif, etc.

Ces registres permettent au CPU d'effectuer des opérations rapidement en stockant temporairement les données et les instructions nécessaires pour l'exécution des programmes. Chaque type de CPU peut avoir des registres spécifiques et leur nombre peut varier en fonction de l'architecture du processeur.

Un registre spécial contient l'adresse de la prochaine instruction à exécuter par le CPU (eip pour x86).

#### Architecture x86

Les processeurs x86 sont une famille de processeurs compatibles avec l'architecture x86, développée par Intel et AMD, qui est utilisée dans la plupart des ordinateurs personnels et serveurs.

Voici une brève présentation des principaux registres x86 :

1. **Registres généraux (GPR)** :
   * **EAX, EBX, ECX, EDX** : Ces registres sont utilisés pour des opérations arithmétiques et logiques.
   * **ESI, EDI** : Utilisés pour les opérations de chaînes (copies de mémoire).
   * **ESP** : Pointeur de pile, utilisé pour gérer la pile de la mémoire.
   * **EBP** : Pointeur de base de pile, utilisé pour les fonctions et le passage de paramètres.
2. **Registres de segment** :
   * **CS, DS, SS, ES, FS, GS** : Utilisés pour l'adressage mémoire segmenté dans les anciennes versions de l'architecture x86.
3. **Registres d'index et de pointeur** :
   * **EIP** : Pointeur d'instruction, utilisé pour stocker l'adresse de l'instruction en cours d'exécution.
   * **EFLAGS** : Registre de drapeaux, utilisé pour stocker des indicateurs de statut, tels que les drapeaux de zéro, de signe, de retenu, etc.
4. **Registres d'extension 64 bits** :
   * **RAX, RBX, RCX, RDX** : Versions 64 bits des registres EAX, EBX, ECX et EDX, introduits dans les architectures x86-64.

Tous les registres sont accessibles en mode 16 bits et 32 bits. Par exemple, 'EAX' est le registre de l'accumulateur en tant que valeur 32 bits.

De même, dans la version 64 bits, le "E" est remplacé par un "R" (registre), de sorte que la version 64 bits de "EAX" est appelée "RAX".

Il est également possible d'adresser les quatre premiers registres (AX, CX, DX et BX) dans leur taille de 16 bits en deux moitiés de 8 bits. L'octet le moins significatif (LSB), ou moitié basse, est identifié en remplaçant le "X" par un "L". L'octet le plus significatif (MSB), ou moitié haute, est identifié par un "H". Par exemple, CL est le LSB du registre des compteurs, tandis que CH est son MSB.

Au total, cela nous donne cinq façons d'accéder aux registres de l'accumulateur, du compteur, des données et de la base : 64 bits, 32 bits, 16 bits, 8 bits LSB et 8 bits MSB. Les quatre autres registres ne sont accessibles que de quatre façons : 64 bits, 32 bits, 16 bits et 8 bits.

| Nom du Registre | Taille (bits) | Taille (octets) | Registre 64 bits | Registre 32 bits | Registre 16 bits | Registre 8 bits (haut) | Registre 8 bits (bas) |
| :-------------: | :-----------: | :-------------: | :--------------: | :--------------: | :--------------: | :--------------------: | :-------------------: |
|       RAX       |       64      |        8        |        RAX       |        EAX       |        AX        |           AH           |           AL          |
|       RBX       |       64      |        8        |        RBX       |        EBX       |        BX        |           BH           |           BL          |
|       RCX       |       64      |        8        |        RCX       |        ECX       |        CX        |           CH           |           CL          |
|       RDX       |       64      |        8        |        RDX       |        EDX       |        DX        |           DH           |           DL          |
|       RSI       |       64      |        8        |        RSI       |        ESI       |        SI        |            -           |           -           |
|       RDI       |       64      |        8        |        RDI       |        EDI       |        DI        |            -           |           -           |
|       RBP       |       64      |        8        |        RBP       |        EBP       |        BP        |            -           |           -           |
|       RSP       |       64      |        8        |        RSP       |        ESP       |        SP        |            -           |           -           |
|       RIP       |       64      |        8        |        RIP       |        EIP       |        IP        |            -           |           -           |

### La mémoire

La mémoire est adressable et divisée en 5 parties:

* la pile (stack)
* le tas (heap)
* le segment BSS
* le segment de données
* le segment de code

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FPYR2fB0CQJYmKxxU3JU9%2FSegmentation%20de%20la%20m%C3%A9moire%20pour%20un%20programme.svg?alt=media&amp;token=55387af2-83ce-4523-be1b-cd2f58900a13" alt="" width="303"><figcaption><p>Segmentation de la mémoire pour un programme</p></figcaption></figure>

Ici l'adresse la plus basse est **0x00000000** et la plus haute est **0xFFFFFFFF**.

**La pile:** c'est la zone mémoire utilisée pour stocker les variables locales et les paramètres des fonctions. Elle suit une structure LIFO (Last In First Out) et évolue des adresses hautes vers les adresses basses.

**Le tas:** c'est la zone mémoire utilisée pour l’allocation dynamique (allocation et libération de zones mémoire avec les fonctions malloc, free). Il évolue vers les adresses hautes. Lorsqu’il rencontre la pile, la quantitée de mémoire libre est épuisée.

**Segment BSS:** (Block Started by Symbol) il contient les données statiques non initialisées (par exemple `static char str[10]` en C).

**Segment des données:** il contient les données statiques initialisées (par exemple `static char str[] = "Hello World"` en C).

**Segment de code:** il contient les instructions assembleur pour exécuter le programme.

#### La pile (stack)

La pile est utilisée pour stocker tout le nécessaire à l'exécution d'une fonction et au retour à l'état initial (ses arguments, variables locales, adresse de retour *(on va y revenir)*, etc.).

Pendant l'exécution d'une fonction, une partie au sommet de la pile lui est réservée: on parle de **stack frame**. Deux registres (**base pointer** et **stack pointer**) contiennent respectivement les adresses de la base et du sommet de la **stack frame** (ebp et esp pour x86). La stack frame est préparée à l'appel de la fonction lors de son **prologue**.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FYEy0060BcaNwP9N5zjUx%2FStack%20frame.svg?alt=media&amp;token=933494b1-d715-433d-84d9-f06bbfae9ddc" alt="" width="518"><figcaption><p>Stack frame</p></figcaption></figure>

Quand une fonction se termine, le programme doit reprendre son exécution à la ligne suivant l'appel de cette fonction, cette partie se fait lors de son **épilogue**.

```c
int main(int argc, char** argv) {
	char name[] = "sam";
	hello(name); 
	return 0;  <== prochaine ligne de code à exécuter après hello()
}
```

Concrètement, le CPU doit savoir quelle instruction exécuter une fois la fonction terminée.

Code assembleur de la fonction `main`:

```c
0x00401578:    push   ebp
0x00401579:    mov    ebp,esp
0x0040157b:    and    esp,0xfffffff0
0x0040157e:    sub    esp,0x20
0x00401581:    call   0x401650 <__main>
0x00401586:    mov    DWORD PTR [esp+0x1c],0x6d6173
0x0040158e:    lea    eax,[esp+0x1c]
0x00401592:    mov    DWORD PTR [esp],eax
0x00401595:    call   0x40155c <hello>
0x0040159a:    mov    eax,0x0  <== instruction à exécuter au retour de hello()
0x0040159f:    leave
0x004015a0:    ret
```

Lorsqu'une fonction est appelée il faut donc:

* connaître **l'adresse de retour**
* préparer la **stack frame**
* mettre les arguments de la fonction sur la pile

Regardons tout ça avec notre code. D'abord l'argument `"sam"` est placé sur la pile et la fonction `hello` est appelée.

```c
0x0040158e:    lea    eax,[esp+0x1c]  <== place "sam" dans le registre eax
0x00401592:    mov    DWORD PTR [esp],eax  <== place "sam" au sommet de la pile
0x00401595:    call   0x40155c <hello>  <== appel à hello()
```

L'instruction *call* fait 2 choses:

* elle place l'adresse de retour (ici 0x40159a) au sommet de la pile
* elle dis au CPU de sauter à l'adresse de hello (0x40155c dans eip)

Il faut ensuite préparer la stack frame. Regardons le code de `hello`:

```c
---------------------------------+
0x0040155c:    push   ebp        |
0x0040155d:    mov    ebp,esp    |  <== prologue 
0x0040155f:    sub    esp,0x18   |
---------------------------------+-------------+
0x00401562:    mov    eax,DWORD PTR [ebp+0x8]  |
0x00401565:    mov    DWORD PTR [esp+0x4],eax  |
0x00401569:    mov    DWORD PTR [esp],0x40a000 |  <== code de la fonction
0x00401570:    call   0x401520 <printf>        |
0x00401575:    nop                             |
---------------------------------+-------------+
0x00401576:    leave             |  <== épilogue
0x00401577:    ret               |
---------------------------------+
```

1. Le prologue de hello() sauvegarde la valeur du base pointer (ebp) de la stack frame de la fonction appelante (main) en le plaçant au sommet de la pile
2. Il place ensuite la valeur du stack pointer (esp) dans le base pointer (ebp)
3. Il fait de la place sur la pile pour les variables locales de la fonction

La pile ressemble actuellement à ça:

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FVNNRQp8mh2IIjaEHayWE%2FPrologue.svg?alt=media&amp;token=1b6fd75c-1a2e-472d-b081-8fef8a97ea99" alt="" width="563"><figcaption><p>Prologue</p></figcaption></figure>

Ensuite la fonction s'exécute (ici de 0x401562 à 0x401575) et l'épilogue fait 2 choses:

1. l'instruction *leave* copie le base pointer (`ebp`) dans le stack pointer (`esp`), ce qui libère l'espace de pile alloué à la stack frame

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FWeiS9kJrsRkaHEkE16f5%2FEpilogue.svg?alt=media&amp;token=42191139-1ce9-44b8-91f4-89bde1424b9d" alt="" width="563"><figcaption><p>Epilogue</p></figcaption></figure>

2. l'instruction *ret* retire la valeur au sommet de la pile (l'adresse de retour) et la place dans `eip` ce qui dis au CPU de retourner à la fonction appelante

{% hint style="info" %}
Pour cet exemple j'ai compilé le programme pour l'architecture x86 (32-bit) mais pour x64 (x86\_64 64-bit) les arguments des fonctions ne sont pas placés sur la pile mais sont passés par registres.
{% endhint %}

#### Le tas (heap)

Parlons un peu du tas maintenant. Comme on l'a vu précédemment c'est la partie de la mémoire où sont stockées les variables allouées dynamiquement. Contrairement à la pile, le tas croît dans le sens des adresses croissantes. N’importe quel bloc mémoire peut être alloué à tout moment de l’exécution. Des blocs mémoires sont alloués sur le tas avec la fonction [malloc()](https://koor.fr/C/cstdlib/malloc.wp) et sont libérés avec la fonction [free()](https://koor.fr/C/cstdlib/free.wp).

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FiwQI5qGiUYQW7h7z9nbj%2FAllocation%20et%20lib%C3%A9ration%20de%20blocs%20m%C3%A9moire%20sur%20le%20tas.svg?alt=media&amp;token=d23c4c5e-26fb-48e3-a0ed-e0bdb340dcba" alt="" width="563"><figcaption></figcaption></figure>

`malloc()` renvoie un pointeur vers l’adresse de début du bloc de mémoire alloué.

Quand de la mémoire est allouée dynamiquement avec des fonctions telles que `malloc()`, la libc C conserve généralement des blocs de mémoire préalloués, appelés "bins", pour répondre rapidement aux futures demandes d'allocation de mémoire de taille similaire. Ces bins sont organisés en fonction de la taille des blocs de mémoire qu'ils contiennent.

Ces bins sont des listes chaînées contenant des blocs de mémoire de même taille. Lorsqu'une demande de désallocation de mémoire est effectuée avec la fonction `free()`, la bibliothèque malloc vérifie d'abord si le bloc de mémoire à libérer peut être placé dans le bin correspondant. Cela permet d'accélérer le processus de désallocation et de réallocation de petits blocs de mémoire.

```c
a = malloc(10);  // 0xfff00000        bin: head -> tail                 
b = malloc(10);  // 0xfff00010

free(a);                           // bin: head -> 0xfff00000 -> tail
free(b);                           // bin: head -> 0xfff00010 -> 0xfff00000 -> tail
 
// la tête de la liste chaînée (0xfff00010) est sélectionnée 
// pour l'adresse de c car la taille demandée est la même
c = malloc(10);  // 0xfff00010        bin: head -> 0xfff00000 -> tail 
// même principe
d = malloc(10);  // 0xfff00000        bin: head -> tail 
```

#### Mémoire virtuelle

Les adresses mémoire accédées par le CPU ne sont pas des adresses physiques de la RAM mais des **adresses virtuelles**. Les **adresses virtuelles** sont liées aux physiques avec le **MMU (memory management unit)**. La table des pages est stockée dans la RAM.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FofhkU2x5A85fadpFMfJa%2FTraduction%20des%20adresses%20m%C3%A9moire%20vers%20les%20adresses%20physiques%20par%20le%20MMU.svg?alt=media&amp;token=f6deac25-b194-4967-9082-7eec6bddb708" alt="" width="563"><figcaption><p>Traduction des adresses mémoire vers les adresses physiques par le MMU</p></figcaption></figure>

Chaque processus a son mapping différent car la table des pages peut être éditée pendant que l'ordinateur tourne, quand l'OS change de processus les adresses virtuelles sont "remappées" vers une zone mémoire physique différente. Un processus ne peut pas ainsi accéder à la mémoire d'un autre.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2F066myZI1PQfB17xBTRQl%2FCorrespondance%20des%20adresses%20virtuelles%20et%20physiques%20pour%202%20processus%20diff%C3%A9rents.svg?alt=media&amp;token=1612ae59-c739-48d1-bd1e-dd5824ae9dd4" alt="" width="517"><figcaption><p>Correspondance des adresses virtuelles et physiques pour 2 processus différents</p></figcaption></figure>

Sur Windows et Linux une partie fixe des adresses virtuelles est réservée au kernel (pour qu'en mode kernel, le CPU puisse accéder aux zones mémoires réservées aux données du kernel). Les pages sont protégées par des permissions.

{% hint style="info" %}
La table des pages est dans la zone mémoire du kernel.
{% endhint %}

### Kernel, appels systèmes et interruptions

Quand votre ordinateur démarre, le premier programme appelée est le **kernel (ou noyau)**. Il a un accès complet à la mémoire, périphériques... et s'occupe de lancer les programmes installés (userland). Un CPU a plusieurs modes de fonctionnement (au moins 2):

* kernel/supervisor (kernel/drivers)
* user (applications)

Ils se différencient par leurs droits:

* **kernel mode:** le CPU peut exécuter toutes les instructions et accéder à toute la mémoire
* **user mode:** le CPU a accès à un nombre limité d'instructions, d'entrée/sortie et son accès à la mémoire est restreint. Certaines options du CPU sont bloquées.

{% hint style="info" %}
Pour l'architecture x64 le mode actuel est stocké dans le registre "cs" (dans les 2 bits de poids faible). 0 pour kernel et 3 pour user, on parle de ring 0 ou ring 3.
{% endhint %}

**Mais si les accès sont limités pour les applications, comment peuvent elles interagir avec le système d'exploitation (OS) ?**

Ce problème est résolu grâce aux **appels systèmes**. Le programme demande de l'aide à l'OS et une transition de user space à kernel space à lieu. Au démarrage de l'ordinateur, l'OS place dans la RAM un tableau qui fait le lien entre des numéros d'interruptions avec des adresses (celles des handlers): **l'interrupt vector table**.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FXqz1cZlyqhRUtdqAlSle%2FInterrupt%20vector%20table.svg?alt=media&amp;token=332e3514-47e7-4657-94f1-f952c5db28c3" alt="" width="301"><figcaption><p>Interrupt vector table</p></figcaption></figure>

Une application en mode user peut utiliser une instruction *(x86: int, x64: syscall)* pour dire au CPU de regarder le numéro de l'interruption dans le tableau. Celui-ci passe alors en mode kernel et saute à l'adresse du handler. Une fois le code exécuté, il repasse en mode user et retourne à l'endroit où l'interruption a été appelée.

{% hint style="success" %}
De cette manière, l'OS implémente ses propres mesures de sécurité pour empêcher des potentielles actions malicieuses d'un programme.
{% endhint %}

Lorsqu'un exécutable est lancé, le CPU est en mode user. Un appel système à [**execve**](https://man7.org/linux/man-pages/man2/execve.2.html) est fait et le CPU passe en mode kernel. Le programme est chargé, le format vérifié et si tout se passe bien un nouveau processus pour ce programme est créé.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2FcDJ5H4FySWWsxyXN7lSH%2FEx%C3%A9cution%20d'un%20programme%20sur%20Linux.svg?alt=media&amp;token=d84f7803-28de-4c97-ab26-eecdbfe7fe5d" alt="" width="563"><figcaption><p>Exécution d'un programme sur Linux</p></figcaption></figure>

{% hint style="info" %}
Pour plus d'informations, voir [le tableau des appels systèmes sur Linux](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md).
{% endhint %}

**Parlons un peu plus des interruptions...**

Une interruption est une demande au CPU *d'interrompre* l'exécution du code actuel pour en exécuter un autre. Le CPU doit sauvegarder tout ce qui permet l'exécution du processus courant pour pouvoir y revenir une fois l'interruption gérée (on parle de *context switching*). Comme on l'a vu juste avant il exécute alors le code du handler correspondant à l'exécution.

Il y a 2 types d'interruptions:

* **software:** interruption demandée par le CPU suite à un certains type d'instruction exécutée (division par zéro par exemple)
* **hardware:** interruption demandée par un périphérique extérieur (quand on appuie sur une touche du clavier ou qu'on déplace la souris par exemple)

Avant de sauter au code correspondant à l'interruption, le CPU doit donc sauvegarder l'état du processus. Il sauvegarde par exemple les registres généraux, EIP (pour savoir où retourner après l'interruption), les registres de segment, etc.

### Librairies et linking

Les programmeurs utilisent souvent des librairies (libc...). Pendant la compilation, un **linker** ajoute le code des librairies référencés à l'exécutable.

```c
#include <stdio.h>   <== importation de la librairie <stdio.h>

// son code sera ajouté ici à la compilation

void hello(char* name) {
	printf("Hello %s", name);
}

int main(int argc, char** argv) {
	char name[] = "sam";
	hello(name);
	return 0;
}
```

{% hint style="info" %}
La librairie [\<stdio.h>](https://koor.fr/C/cstdio/cstdio.wp) contient le code des fonctions suivantes: [clearerr](https://koor.fr/C/cstdio/clearerr.wp), [fclose](https://koor.fr/C/cstdio/fclose.wp), [feof](https://koor.fr/C/cstdio/feof.wp), [ferror](https://koor.fr/C/cstdio/ferror.wp), [fflush](https://koor.fr/C/cstdio/fflush.wp), [fgetc](https://koor.fr/C/cstdio/fgetc.wp), [fgetpos](https://koor.fr/C/cstdio/fgetpos.wp), [fgets](https://koor.fr/C/cstdio/fgets.wp), [FILE \*](https://koor.fr/C/cstdio/file.wp), [fopen](https://koor.fr/C/cstdio/fopen.wp), [fpos\_t](https://koor.fr/C/cstdio/fpos_t.wp), [fprintf](https://koor.fr/C/cstdio/fprintf.wp), [fputc](https://koor.fr/C/cstdio/fputc.wp), [fputs](https://koor.fr/C/cstdio/fputs.wp), [fread](https://koor.fr/C/cstdio/fread.wp), [freopen](https://koor.fr/C/cstdio/freopen.wp), [fscanf](https://koor.fr/C/cstdio/fscanf.wp), [fseek](https://koor.fr/C/cstdio/fseek.wp), [fsetpos](https://koor.fr/C/cstdio/fsetpos.wp), [ftell](https://koor.fr/C/cstdio/ftell.wp), [fwrite](https://koor.fr/C/cstdio/fwrite.wp), [getc](https://koor.fr/C/cstdio/fgetc.wp), [getchar](https://koor.fr/C/cstdio/fgetc.wp), [gets](https://koor.fr/C/cstdio/fgets.wp), [perror](https://koor.fr/C/cstdio/perror.wp), [printf](https://koor.fr/C/cstdio/fprintf.wp), [putc](https://koor.fr/C/cstdio/fputc.wp), [putchar](https://koor.fr/C/cstdio/fputc.wp), [puts](https://koor.fr/C/cstdio/fputs.wp), [remove](https://koor.fr/C/cstdio/remove.wp), [rename](https://koor.fr/C/cstdio/rename.wp), [rewind](https://koor.fr/C/cstdio/rewind.wp), [scanf](https://koor.fr/C/cstdio/fscanf.wp), [SEEK\_CUR](https://koor.fr/C/cstdio/fseek.wp), [SEEK\_END](https://koor.fr/C/cstdio/fseek.wp), [SEEK\_SET](https://koor.fr/C/cstdio/fseek.wp), [sprintf](https://koor.fr/C/cstdio/fprintf.wp), [sscanf](https://koor.fr/C/cstdio/fscanf.wp), [stderr](https://koor.fr/C/cstdio/file.wp), [stdin](https://koor.fr/C/cstdio/file.wp), [stdout](https://koor.fr/C/cstdio/file.wp), [tmpfile](https://koor.fr/C/cstdio/tmpfile.wp), [tmpnam](https://koor.fr/C/cstdio/tmpnam.wp).
{% endhint %}

Il existe 2 types de **linking**:

* **statique:** le code référencé est ajouté à l'exécutable (seulement les fonctions demandées: ici [printf](https://koor.fr/C/cstdio/fprintf.wp)). Les fonctions de la librairie sont copiées de l'ordinateur *du développeur* dans chaque binaire à la compilation.
* **dynamique:** une référence vers la librairie est ajoutée à l'exécutable. A l'exécution du programme, le code est récupéré depuis la librairie. Les binaires font référence aux noms des fonctions de la bibliothèque, qui sont chargées à partir de l'ordinateur *de l'utilisateur* au moment de l'exécution. Si la libraire est mise à jour sur l'ordinateur de l'utilisateur, le code à jour sera ajouté à l'exécution.

<figure><img src="https://1813806532-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FZRRTPIEA4wb6exZozwS0%2Fuploads%2Fku19KpC6GymPTjT3vzPh%2FStatique%20et%20dynamique%20linking.svg?alt=media&amp;token=2c77c9db-53df-4067-bc9c-ef8b1158a98d" alt="" width="563"><figcaption><p>Statique et dynamique "linking"</p></figcaption></figure>

{% hint style="info" %}
Sur Linux les librairies "linkable" dynamiquement sont stockées dans des fichiers ".so" (shared object). Sur Windows elles le sont dans des fichiers ".dll" (dynamic link library).
{% endhint %}
