Magaz, The Greek Linux Magazine
Magaz Logo

Επόμενο  Προηγούμενο  Περιεχόμενα

3. Άλλα χρήσιμα εργαλεία

Παρακάτω παρουσιάζονται εν συντομία δύο πολύ χρήσιμα εργαλεία που υπάρχουν σε κάθε σύγχρονο linux σύστημα. Βασίζονται και τα δύο στο ptrace() system call που επιτρέπει τον έλεγχο διεργασιών στο linux.

3.1 strace

To strace (system call trace) καταγράφει τα system calls ενός προγράμματος. Από default καταγράφει όλα τα syscalls αλλά υπάρχει η δυνατότητα να προσδιοριστούν μόνο κάποια συγκεκριμένα. Δέχεται πολλές παραμέτρους και ρυθμίσεις αλλά εδώ θα ασχοληθούμε μόνο με τις πιο βασικές (εξάλλου δεν υπάρχει λόγος να επαναλαμβάνουμε τη manpage!)

Η βασική σύνταξη είναι strace [options] [-o outputfile] [objfile [args]].
Αν δεν καθορίσουμε κάποιο αρχείο για output τα syscalls εμφανίζονται στo stderr.

Χρήσιμες παράμετροι είναι:
-e call1,call2,... ή -e trace=call1,call2,...,callΝ
Καθορίζει ποια syscalls να παρακολουθήσει το πρόγραμμα. Αν πριν από κάποιο όνομα υπάρχει το '!' σημαίνει να μην παρακολουθηθεί το syscall αυτό. H default τιμή είναι -e trace=all.
-p pid
καθορίζει σε ποια διεργασία να "αγκιστρωθεί" το πρόγραμμα ώστε να αρχίσει να παρακολουθεί. Προφανώς στην περίπτωση αυτή είναι περιττό να καθοριστεί το objfile.
-i
Πριν από κάθε syscall εμφανίζεται η τιμή του IP (instruction pointer) τη στιγμή της κλήσης. Η επιλογή αυτή είναι λιγότερο χρήσιμη από ότι φαίνεται, διότι τα syscalls καλούνται μέσα από wrapper συναρτήσεις που βρίσκονται σε βιβλιοθήκες, και έτσι οι διευθύνσεις που θα λαμβάνουμε δεν θα είναι και πολύ χρήσιμες.

πχ

bash$ strace -e trace=write rce1
write(1, "Usage: rce1 <number>\n", 21Usage: rce1 <number>
)  = 21

Το παραπάνω είναι λίγο μπερδεμένο διότι το strace αρχίζει να καταγράφει το syscall write στο stderr(που από default είναι η οθόνη), ύστερα εκτυπώνεται το κείμενο μας και μετά τελειώνει η καταγραφή με την τιμή επιστροφής της write. Αν είχαμε χρησιμοποιήσει το option -ο δε θα υπήρχε πρόβλημα.
bash$ strace -o rce1.trace -e trace=write rce1
Usage: rce1 <number>
bash$ cat rce1.trace
write(1, "Usage: rce1 <number>\n", 21)  = 21

Ας δούμε τώρα το πλήρες trace από το αγαπημένο μας rce1.
bash$ strace -o rce1.trace rce1
Usage: rce1 <number>
bash$ cat -n rce1.trace
     1  execve("./rce1", ["rce1"], [/* 51 vars */]) = 0
     2  brk(0)                                  = 0x8049598
     3  open("/etc/ld.so.preload", O_RDONLY)    = -1 ENOENT (No such file or
directory)
     4  open("/etc/ld.so.cache", O_RDONLY)      = 3
     5  fstat64(3, {st_mode=S_IFREG|0644, st_size=64466, ...}) = 0
     6  old_mmap(NULL, 64466, PROT_READ, MAP_PRIVATE, 3, 0) = 0x40015000
     7  close(3)                                = 0
     8  open("/lib/libc.so.6", O_RDONLY)        = 3
     9  read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0p\\\1\000"...,
1024) = 1024
    10  fstat64(3, {st_mode=S_IFREG|0755, st_size=1435624, ...}) = 0
    11  old_mmap(NULL, 1256740, PROT_READ|PROT_EXEC, MAP_PRIVATE, 3, 0) =
0x40025000
    12  mprotect(0x4014f000, 36132, PROT_NONE)  = 0
    13  old_mmap(0x4014f000, 20480, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED, 3, 0x12a000) = 0x4014f000
    14  old_mmap(0x40154000, 15652, PROT_READ|PROT_WRITE,
MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x40154000
    15  close(3)                                = 0
    16  old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,
-1, 0) = 0x40158000
    17  munmap(0x40015000, 64466)               = 0
    18  fstat64(1, {st_mode=S_IFCHR|0700, st_rdev=makedev(136, 0), ...}) = 0
    19  old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS,
-1, 0) = 0x40015000
    20  write(1, "Usage: rce1 <number>\n", 21)  = 21
    21  munmap(0x40015000, 4096)                = 0
    22  semget(1, 4096, IPC_CREAT|0x40150140|0400) = -1 ENOSYS (Function not
implemented)
    23  _exit(1)                                = ?

1: Εδώ καλείται η execve() ώστε να εκτελεστεί το πρόγραμμα μας. Η επόμενη γραμμή είναι πιο ενδιαφέρουσα.

2: Η brk() χρησιμοποιείται για να αλλάξει το μέγεθος του data segment. Παίρνει ως παράμετρο το νέο τέλος του data segment και επιστρέφει 0 αν όλα πήγαν καλά και -1 σε περίπτωση λάθους (όλα αυτά σύμφωνα με την man page που έχει ξεμείνει απο το linux 0.9.11). Εδώ η παράμετρος είναι 0 και η τιμή επιστροφής είναι ένας μια διεύθυνση. Μόλις μάθαμε ότι η brk(NULL) επιστρέφει το τρέχον τέλος του data segment :)

Τώρα ο dynamic linker αναλαμβάνει δράση.
3-4: Παρατηρούμε μια προσπάθεια να ανοίξουν δύο αρχεία: το "/etc/ld.so.preload" και το "/etc/ld.so.cache". Το πρώτο περιέχει μια λίστα με το ποιες βιβλιοθήκες να φορτωθούν πριν από οποιαδήποτε βιβλιοθήκη του συγκεκριμένου εκτελέσιμου και στο δικό μου σύστημα δεν υπάρχει (θα ήταν ύποπτο αν υπήρχε...). Το δεύτερο περιέχει μια λίστα με όλες τις βιβλιοθήκες που γνωρίζει ο dynamic linker (δες man ldconfig) και ανοίγει επιτυχώς με file descriptor 3.

5-7: Με την fstat ο linker πληροφορείται για το αρχείο που μόλις άνοιξε. Η πληροφορία που τον ενδιαφέρει πιο πολύ είναι το μέγεθος st_size=64466 (δες man fstat). Ύστερα το αρχείο γίνεται mapped(αντιστοιχείται) στη μνήμη στη διεύθυνση 0x40015000 (δες man mmap) και το αρχείο κλείνει.

8-15: Ο linker βλέπει πως το εκτελέσιμο χρειάζεται τη βιβλιοθήκη libc.so.6. Έχοντας τις πληροφορίες από το προηγούμενο βήμα βρίσκει το path και το ανοίγει. Ύστερα διαβάζει τα πρώτα 1024 bytes που περιέχουν τον ELF(Executable and Linkable Format) header του αρχείου και αφού επιβεβαιώσει ότι όντως πρόκειται για shared βιβλιοθήκη φορτώνει τα διάφορα sections της.

16-17: Γίνονται map ανώνυμα 4Kbytes και ελευθερώνεται(unmapped) ο χώρος που αντιστοιχεί στο "/etc/ld.so.cache".

18-19: Λαμβάνονται πληροφορίες για τον file descriptor 1 (stdout) και γίνονται map ανώνυμα άλλα 4Kbytes

20: Εδώ γράφεται στον fd 1 το κείμενο μας. Εμείς δεν χρησιμοποιήσαμε την write κατευθείαν αλλά η printf τελικά καλεί την write.

21-23: Ελευθερώνεται το block που δεσμεύτηκε στη γραμμή 19, καλείται η semget() η οποία δεν έχει υλοποιηθεί(!) ακόμα στον δικό μου πυρήνα (2.4.20) και το πρόγραμμα τερματίζει.

3.2 ltrace

To ltrace είναι αντίστοιχο με το strace αλλά όπως δηλώνει και το όνομα του (library trace) παρακολουθεί κλήσεις συναρτήσεων από βιβλιοθήκες. Πρόκειται για ένα αρκετά χρήσιμο πρόγραμμα που δίνει μια εποπτική εικόνα της ροής του προγράμματος. Βέβαια όπως και το strace μας δίνει απλώς μια ακολουθία από συμβάντα και καθόλου πληροφορίες για τα κομβικά σημεία της διεργασίας (πχ κάποιος έλεγχος).

H σύνταξη είναι : ltrace [options] [-o outputfile] [objfile [args]]

Χρήσιμες παράμετροι είναι:
-e call1,call2,...
Το πρόγραμμα παρακολουθεί μόνο τις call1,call2... Αν πριν από κάποια συνάρτηση υπάρχει '!' τότε να μην παρακολουθηθεί.
-p pid
Καθορίζει σε ποια διεργασία να "αγκιστρωθεί" το πρόγραμμα ώστε να αρχίσει να παρακολουθεί. Προφανώς στην περίπτωση αυτή είναι περιττό να καθοριστεί το objfile.
-i
Πριν από κάθε call εμφανίζεται η τιμή του IP (instruction pointer) τη στιγμή της κλήσης. Σε αντίθεση με την strace η επιλογή είναι πολύ χρήσιμη, διότι οι κλήσεις συναρτήσεων βιβλιοθήκης γίνονται συνήθως κατευθείαν από τον κώδικα που μας ενδιαφέρει και όχι από κάποια μυστική γωνιά κάποιας απέραντης βιβλιοθήκης.
-l libfilename
Καθορίζει τις συναρτήσεις ποιων βιβλιοθηκών να παρακολουθεί η ltrace. Για περισσότερες από μια βιβλιοθήκες πρέπει να επαναληφθεί το '-l' πχ -l lib1 -l lib2 ...

πχ
bash$ ltrace -orce1.ltrace  -i rce1
Usage: rce1 <number>
bash$ cat rce1.ltrace
[080482fd] __libc_start_main(0x0804838c, 1, 0xbffff784, 0x08048274, 0x08048440
<unfinished ...>
[080483b4] printf("Usage: %s <number<\n", "rce1")                          
= 21
[080483c1] exit(1 <unfinished ...>
[ffffffff] +++ exited (status 1) +++

Επόμενο  Προηγούμενο  Περιεχόμενα


Valid HTML 4.01!   Valid CSS!