Ceci est une série d'exercices couvrant l'analyse d'erreurs d'exécution et le débogage de code C et Rust.
Exercice 2 : Débogage d'une division par zéro en C
Considérons le programme C suivant :
#include <stdio.h>
int main() {
printf("1 / 0 = %d\n", 1 / 0);
return 0;
}
Compilation Nous compilons ce code à l'aide de GCC :
gcc -o main main.c
Malgré un avertissement concernant la division par zéro, un exécutable nommé main est créé.
Exécution L'exécution du programme génère une "Floating point expection (core dumped)" :
./main
Sortie :
Floating point exception (core dumped)
Analyse avec straceL'outil strace permet de tracer les appels système effectués par le programme :
strace ./main
La sortie de strace montre la séquence d'événements, y compris le signal SIGFPE (exception de virgule flottante) qui provoque l'arrêt du programme :
...
--- SIGFPE {si_signo=SIGFPE, si_code=FPE_INTDIV, si_addr=0x58375af8d15c} ---
+++ killed by SIGFPE (core dumped) +++
Floating point exception (core dumped)
Débogage avec gdbUtilisons gdb pour déboguer le programme :
gdb main
En lançant l'exécution (run), nous obtenons l'exception :
Starting program: /home/winddevil/workspace/homework/homework-0-2/main
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Program received signal SIGFPE, Arithmetic exception.
0x000055555555515c in main ()
La commande disassemble nous montre le code assembleur au point de l'erreur :
Dump of assembler code for function main:
0x0000555555555149 <+0>: endbr64
0x000055555555514d <+4>: push %rbp
0x000055555555514e <+5>: mov %rsp,%rbp
0x0000555555555151 <+8>: mov $0x1,%eax
0x0000555555555156 <+13>: mov $0x0,%ecx
0x000055555555515b <+18>: cltd
=> 0x000055555555515c <+19>: idiv %ecx
0x000055555555515e <+21>: mov %eax,%esi
0x0000555555555160 <+23>: lea 0xe9d(%rip),%rax # 0x555555556004
0x0000555555555167 <+30>: mov %rax,%rdi
0x000055555555516a <+33>: mov $0x0,%eax
0x000055555555516f <+38>: call 0x555555555050 <printf@plt>
0x0000555555555174 <+43>: mov $0x0,%eax
0x0000555555555179 <+48>: pop %rbp
0x000055555555517a <+49>: ret
End of assembler dump.
L'instruction idiv %ecx est responsable de la division par zéro, car %ecx contient la valeur 0.
Exercice 3 : Compaarison C et Rust pour l'I/O et les délais
Version C
#include <stdio.h>
#include <unistd.h> // Définit la fonction sleep
int main() {
sleep(5); // Pause l'exécution pendant 5 secondes
const char* string = "string you want,make you happy\n";
printf("%s", string); // Affiche la chaîne sur la sortie standard
FILE *output_file = fopen("./output.txt", "w"); // Ouvre le fichier en mode écriture
if (output_file != NULL) {
fputs(string, output_file); // Écrit la chaîne dans le fichier
fclose(output_file); // Ferme le fichier
} else {
perror("Erreur lors de l'ouverture du fichier");
}
return 0;
}
Détails La fonction sleep est déclarée dans <unistd.h>. L'ouverture, l'écriture et la fermeture de fichiers sont gérées par les fonctions fopen, fputs et fclose respectivement.
Version Rust
use std::fs::File; // Module pour les opérations sur les fichiers
use std::io::prelude::*; // Trait pour les opérations d'écriture
use std::thread; // Module pour la gestion des threads (comme sleep)
use std::time::Duration; // Structure pour représenter les durées
fn main() {
// Met le thread courant en pause pendant 5 secondes
thread::sleep(Duration::from_secs(5));
let message = "Hello, this message will be printed and saved to a file.";
// Affiche le message sur la sortie standard
println!("{}", message);
// Tente de créer un fichier nommé "output.txt".
// File::create renvoie un Result<File, io::Error>.
match File::create("output.txt") {
Ok(mut file) => {
// Si la création réussit, tente d'écrire le contenu du message dans le fichier.
// write_all prend un slice d'octets (&[u8]).
if let Err(e) = file.write_all(message.as_bytes()) {
// Si l'écriture échoue, affiche une erreur sur la sortie d'erreur standard.
eprintln!("Erreur lors de l'écriture dans le fichier : {}", e);
} else {
// Si l'écriture réussit, affiche un message de confirmation.
println!("Message écrit avec succès dans le fichier.");
}
}
Err(e) => {
// Si la création du fichier échoue, affiche une erreur.
eprintln!("Impossible de créer le fichier : {}", e);
}
}
}
Détails En Rust, les opérations susceptibles d'échouer, comme la création de fichiers, retournent généralement un type Result<T, E>. Ce type est une énumération qui peut être soit Ok(T) (succès, contenant la valeur T), soit Err(E) (échec, contenant une valeur d'erreur E).
L'utilisation de match est la manière idiomatique de gérer les résultats des opérations. Le bloc Ok(mut file) gère le cas où la création du fichier réussit, nous donnant un descripteur de fichier mutable (mut file). Le bloc Err(e) gère le cas où la création échoue, e contenant l'erreur spécifique. Si l'on ignore le Result sans le gérer (par exemple, en n'utilisant pas match, unwrap, ou expect), le compilateur Rust signalera une erreur, car il impose une gestion explicite des erreurs potentielles pour garantir la robustesse du code.