Section Headers: ...Ensuite, avec l'outil od on peut regarder les sections. La section .comment est celle-ci :
000548 \0 G C C : ( G N U ) 4 . 2 ...
int main () { const char * enseignante = "AD"; }Compilez et editez les liens, directement dans le fichier a.out avec
gcc Exo1.cRegardez ensuite le contenu de a.out avec l'outil readelf, option -a, ainsi:
readelf -a a.outRegardez attentivement la partie Section headers. On peut recuperer l'adresse (colonne Off (comme 'Offset' -- i.e. decalage en anglais) -- -- de la partie Section headers) de la section .comment . La colonne Off indique pour la section .comment l'adresse 000548, et on regarde son contenu avec l'outil od ainsi :
od -A x -c -j 0x548 a.out | less
const char * enseignant = "VR";Calculez le début de l'adresse de la zone mémoire occupée par cette nouvelle variable.
const unsigned int AgeEnseignante=40;Calculez le début de l'adresse et aussi la taille de la zone mémoire occupée par cette variable. Comparez la taille de variable AgeEnseignante avec la taille de la variable enseignante. Sont-elles pareilles ? Pourquoi?
gcc -O3 -m32 Exo1.c
nm -f sysv Exo1.runL'affichage ressemble à ceci :
Symbols from Exo1.run: Name Value Class Type Size Line Section enseignante |..........| D | OBJECT|........| |.dataLe champ Value donne l'adresse et non pas le contenu de la variable.
strip -s Exo1.run nm -f sysv Exo1.runLe resultat doit être
nm: a.out: no symbols
#include <unistd.h> int brk(void *nvelleAdresseFin); void *sbrk(intptr_t deplacement);Les deux sont obsolètes en tant que fonctions pour les programmeurs "utilisateur" -- elles sont censées être utilisées uniquement pour le développement du noyau.
+------------------+ | TEXT | x | | | instructions | x | code machine | | | +------------------+ x = etext | DATA | x = globales initialisees | - variables | | initialisees | x = statiques locales initialisees | | | | x = edata | | x = statiques locales non-initialisees | | | --------------- | | - variables | x = | noninitialisees | x = globales noninitialisees | | | | | | | --------------- | | - tas pour | x = pointe par | l'allocation | x = pointe par | dynamique | | | | | +------------------+ x = fin segment(s) données | | V . . . ^ | | +------------------+ | PILE | | - fonction | x = init local | Instruction empilee | | x = init local | pour PtrFct | | | - variables | x = loc | Instruction empilee | locales | | pour main() | automatiques | +------------------+
struct MaStruct { bool qDrapeau; unsigned short nShort; bool qAutreDrapeau; int nInt; };et considérons-la placée en mémoire à partir de zéro. Desinnez chaque variable, et les octets de padding nécessaires, respectant les règles énoncées plus haut, ainsi
| | | | | qDrapeau | PADDING | nShort | .... +----------+----------+---------+---------+--- .... 0 1 2 3 4 ....sachant que le tout occupera douze octets (pourquoi?).
#include <iostream> struct MaStruct { bool qDrapeau; unsigned short nShort; bool qAutreDrapeau; int nInt; }; int main(void) { MaStruct maStruct; std::cout << "taille: " << sizeof(maStruct) << "\n"; return(0); }et en le compilant et exécutant.
#include <iomanip> #include <iostream> using namespace std; int main (int argc, char * argv []) { const char * enseignant = "AD"; cout << enseignant << endl; return 0; }
g++ -o exo3Ini.run exo3Ini.cxxet exécutez-le -- vous devez bien entendu voir la sortie AD.
readelf -a exo3Ini.run(attention -- bien entendu avec cette commande vous n'allez pas retrouver directement le nom de variable 'enseignant', ce n'est pas ainsi qu'on trouve la réponse).
od -A x -c -j 0xAdresseTrouveeParVous a.out | head(donc en y mettant la valeur trouvée au point précédent). Vous devez voir quelque chose du genre
.... A D \0 .....
0008bd A D \0 ..... ...........
g++ -o exo3.run exo3.cxx
int main() { sleep(30); return(0); }Compilez-le en m.run. Ainsi, ceci sera notre X (sans arguments).
strace -f exo4.run 'm.run' 2>&1 | tee log.txtet on commence à voir des choses du genre
[pid 19630] write(2, "My pid is ", 10My pid is ) = 10 [pid 19630] write(2, "19630", 519630) = 5 [pid 19630] write(2, "\n", 1 ) = 1 [pid 19630] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 [pid 19630] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 [pid 19630] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 [pid 19630] nanosleep({1, 0}, 0x7ffff2801c20) = 0 [pid 19630] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 [pid 19630] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 [pid 19630] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 [pid 19630] nanosleep({2, 0}, 0x7ffff2801c20) = 0
less /proc/19630/mapset on voit plein de lignes de ce fichier-là avec les zones mémoires dont le noyau a muni notre processus
00400000-00403000 r-xp 00000000 08:07 14344203 /home/.... 7fc087321000-7fc087477000 r-xp 00000000 08:06 156058 /lib64/libc-2.11.2.so ... 7fffec018000-7fffec039000 rw-p 00000000 00:00 0 [stack] ...Par contre, on ne voit pas de ligne avec le mot heap (pas encore).
... [pid 19630] nanosleep({3, 0}, 0x7ffff2801c20) = 0 [pid 19630] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 [pid 19630] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 [pid 19630] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 [pid 19630] nanosleep({4, 0}, 0x7ffff2801c20) = 0 [pid 19630] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 [pid 19630] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 [pid 19630] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 [pid 19630] nanosleep({1, 0}, 0x7ffff2801c20) = 0 [pid 19630] brk(0) = 0x605000 [pid 19630] brk(0x626000) = 0x626000 [pid 19630] rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0 [pid 19630] rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0 [pid 19630] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 [pid 19630] nanosleep({1, 0}, 0x7ffff2801c20) = 0
00605000-00626000 rw-p 00000000 00:00 0 [heap]qui est apparue suite à l'exécution du brk(0x626000). (Si on ne l'aperçoit pas tout de suite "à l'oeil nu", on peut la chercher en tapant /heap (le / annonce à less qu'on souhaite qu'il nous cherche une chaîne de caractères dans le fichier en cours d'examination)).
[pid 19630] execve("/home/.../m.run", ...) = 0 [pid 19630] brk(0) = 0x602000 [pid 19630] mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7eff6fc8a0 ... [pid 19630] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 [pid 19630] nanosleep({30, 0}, 0x7ffff3e98f50) = 0
ptrace(PTRACE_TRACEME, 0 ,NULL, NULL);Le père doit faire wait() pour que le noyau l'informe ainsi quand le fils tracé est réellement interrompu. Ensuite (bien entendu si wait() ne dit pas qu'en fait le fils tracé est maintenant mort), le père peut utiliser les autres requêtes de ptrace().
ptrace(PTRACE_ATTACH, PID ,NULL, NULL);Si cet appel réussit, le processus auquel le traceur s'est ainsi attaché commence à se comporter "comme un fils" du traceur (sauf que getppid() donne toujours le PID de son vrai père), et donc comme pour le PTRACE_TRACEME. Cet appel ne réussit pas si le processus qu'on souhaite tracer est déjà en train d'être tracé.
#include#include const char str[] = "Ca va\n les enfants?\n"; int main() { write(1, str, strlen(str)); return 0; }
gcc -o exo7.run exo7.cet testez-le. Écrivez un petit programme qui se duplique et dont le fils et tracé par son père et fait exec() de exo7f.run. Le père fait en sorte que le fils n'affiche pas toute la chaîne mais seulement les cinq premiers caractères.