8 Stimmen

C-Funktionsprofilerstellung (Adresse scheinen offsetiert zu sein)

Ich versuche, die Funktionsaufrufe mit der Option -finstrument-functions zu profilieren. Im Grunde genommen habe ich Folgendes in jede kompilierte Quelle geschrieben:

static int __stepper=0;
void __cyg_profile_func_enter(void *this_fn, void *call_site)
                              __attribute__((no_instrument_function));
void __cyg_profile_func_enter(void *this_fn, void *call_site) {
  int i=0;
  for( ; i<__stepper; i++ ) printf(" ");
  printf("E: %p %p\n", this_fn, call_site);
  __stepper ++;
} /* __cyg_profile_func_enter */

void __cyg_profile_func_exit(void *this_fn, void *call_site)
                             __attribute__((no_instrument_function));
void __cyg_profile_func_exit(void *this_fn, void *call_site) {
  int i=0;
  __stepper --;
  for( ; i<__stepper; i++ ) printf(" ");
  printf("L:  %p %p\n", this_fn, call_site);
} /* __cyg_profile_func_enter */

Und habe die folgenden Ergebnisse erhalten:

 E: 0xb7597ea0 0xb75987a8
  E: 0xb7597de0 0xb7597ef5
  L:  0xb7597de0 0xb7597ef5
 L:  0xb7597ea0 0xb75987a8

Alle Adressen der Funktionsaufrufe liegen in dieser Region (0xb7.......). Aber wenn ich versuche, die Symbole für die Funktion mit 'readelf -s' zu lesen, erhalte ich Folgendes:

2157: 00101150   361 FUNC    LOCAL  DEFAULT   13 usb_audio_initfn
2158: 00100940   234 FUNC    LOCAL  DEFAULT   13 usb_audio_handle_reset
2159: 00100de0   867 FUNC    LOCAL  DEFAULT   13 usb_audio_handle_control

Der Adressbereich aller Funktionen im Binärcode liegt bei 0x00...... Also kann ich den Funktionsnamen nicht aus den Funktionszeigern erhalten. Anscheinend erhält der Funktionszeiger irgendwie einen Offset oder etwas ähnliches.

Hat jemand eine Idee?

6voto

Michael Pankov Punkte 3431

Von der Frage her sieht es so aus, als ob Sie eine Bibliotheksfunktion profilieren.

Um zu wissen, welche Funktionen gemessen werden, haben Sie 2 Optionen:

1 Führen Sie das Programm aus, das die Bibliothek unter gdb verwendet und stoppen Sie bei main. Zu diesem Zeitpunkt erhalten Sie die PID des Programms PID=... und führen Sie `cat /proc/$PID/maps' aus. Dort sollten Sie etwas Ähnliches wie dies sehen:

  ~  ps
  PID TTY          TIME CMD
18533 pts/4    00:00:00 zsh
18664 pts/4    00:00:00 ps
  ~  PID=18533
  ~  cat /proc/$PID/maps
00400000-004a2000 r-xp 00000000 08:01 3670052                            /bin/zsh5
006a1000-006a2000 r--p 000a1000 08:01 3670052                            /bin/zsh5
006a2000-006a8000 rw-p 000a2000 08:01 3670052                            /bin/zsh5
006a8000-006bc000 rw-p 00000000 00:00 0 
...
7fa174cc9000-7fa174ccd000 r-xp 00000000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
7fa174ccd000-7fa174ecc000 ---p 00004000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
7fa174ecc000-7fa174ecd000 r--p 00003000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
7fa174ecd000-7fa174ece000 rw-p 00004000 08:01 528003                     /lib/x86_64-linux-gnu/libcap.so.2.22
...

Hier ist 7fa174cc9000 die Basisadresse der Bibliothek /lib/x86_64-linux-gnu/libcap.so.2.22. Alle Adressen, die Sie durch readelf -s erhalten, werden um diesen Wert verschoben sein. Wenn Sie die Basisadresse kennen, können Sie zurückrechnen, was ursprünglich die Verschiebung in der Datei war.

Zum Beispiel, wenn Sie den Wert 7fa174206370 erhalten haben und die Basisadresse der Bibliothek 7fa1741cf000 ist, dann beträgt die Verschiebung 7fa174206370 - 7fa1741cf000 = 37370. In meinem Beispiel handelt es sich um sigsuspend aus GLIBC:

94: 0000000000037370   132 FUNC    WEAK   DEFAULT   12 sigsuspend@@GLIBC_2.2.5

2 Führen Sie gdb auf dem Programm aus, das diese Bibliotheken verwendet. Es wird entweder sofort die geladene Bibliothek im Speicher finden oder auf den .text-Abschnitt der Bibliothek hingewiesen werden müssen.

> gdb
(gdb) attach IHRE_PID
(viele Ausgaben zu Symbolen)
(gdb) x/i 0x00007fa174206386
=> 0x7fa174206386 :  cmp    $0xfffffffffffff000,%rax

Auf diese Weise wissen Sie, dass 0x7fa174206386 sich innerhalb von sigsuspend befindet.

Falls gdb keine Symbole von selbst lädt (keine Ausgabe wie Reading symbols from ... Loading symbols for ... nach dem Anhängen), können Sie die Basisadresse der Bibliothek wie in Option 1 suchen und dann die Verschiebung des .text-Abschnitts hinzufügen

  ~  readelf -S /lib/x86_64-linux-gnu/libcap.so.2.22 | grep '.text.'
  [11] .text             PROGBITS         0000000000001620  00001620

7fa174cc9000 + 0000000000001620 in hexadezimal ergibt 7FA174CCA620, und dann fügen Sie diese wie oben mit gdb hinzu und führen aus

(gdb) add-symbol-file /lib/x86_64-linux-gnu/libcap.so.2.22 7FA174CCA620

Dann sollten Sie Symbole finden können (über x/i ADRESSE wie in Option 1), selbst wenn gdb diese nicht von selbst lädt.

Fragen Sie bitte, wenn etwas unklar ist, ich werde versuchen zu erklären.

Klärung, warum das so ist:

Das beobachtete Verhalten ist auf die Kompilierung der Bibliotheken als Position-Independent Code zurückzuführen. Dies ermöglicht eine einfache Unterstützung von dynamischen Bibliotheken. PIC bedeutet im Wesentlichen, dass die ELF der Bibliothek .plt und .got Abschnitte enthält und an jeder Basisadresse geladen werden kann. PLT ist die Prozedur-Verknüpfungstabelle und sie enthält Fallen für Funktionsaufrufe, die sich in anderen Modulen befinden, die zunächst zum Programm-Interpreter gehen, um ihm zu ermöglichen, die aufgerufene Funktion neu zu lokalisieren und dann einfach zur Funktion nach dem ersten Aufruf zu springen. Es funktioniert, weil der Programm-Interpreter das GOT (Global Offset Table) aktualisiert, das Adressen von Funktionen zum Aufruf enthält. Anfangs wird das GOT so initialisiert, dass bei einem ersten Funktionsaufruf der Sprung zur Funktion des Programminterpreters erfolgt, der die Auflösung der aktuell aufgerufenen Funktion durchführt.

Auf x86-64 sehen PLT-Einträge typischerweise so aus:

0000000000001430 :
    1430:       ff 25 e2 2b 20 00       jmpq   *0x202be2(%rip)        # 204018 <_fini+0x201264>
    1436:       68 00 00 00 00          pushq  $0x0
    143b:       e9 e0 ff ff ff          jmpq   1420 <_init+0x28>

Der erste jmpq ist ein Sprung zu der Adresse, die im GOT an der Position %rip + 0x202be2 gespeichert ist:

  [20] .got              PROGBITS         0000000000203fd0  00003fd0
       0000000000000030  0000000000000008  WA       0     0     8

%rip + 0x202be2 wird 0x204012 sein, und das wird zur Basisadresse der Bibliothek hinzugefügt, um die absolute Adresse zu erzeugen, die relevant für den Ort ist, an dem die Bibliothek tatsächlich geladen ist. Wenn sie also bei 0x7f66dfc03000 geladen ist, dann wird die resultierende Adresse des entsprechenden GOT-Eintrags 0x7F66DFE07012 sein. Die Adresse, die an dieser Stelle gespeichert ist, ist die Adresse der (in diesem Beispiel) free-Funktion. Sie wird vom Programm-Interpreter verwaltet, um auf das eigentliche free in libc zu verweisen.

Weitere Informationen dazu finden Sie hier.

3voto

sirgeorge Punkte 6153

Was du brauchst, ist diese dladdr Funktion. Wenn du im Debug-Modus das Modul (dein Hauptprogramm oder die gemeinsam genutzte Bibliothek) gebaut hast, in dem die fragliche Funktion definiert ist, erhältst du durch Aufruf der dladdr Funktion den Funktionsnamen basierend auf ihrer Adresse und auch die Basisadresse, an der das Modul (z.B. deine gemeinsam genutzte Bibliothek) geladen ist:

#define _GNU_SOURCE
#include 

void find_func(void* pfnFuncAddr)
{
    Dl_info info;
    memset(&info,0,sizeof(info));
    if(dladdr(pfnFuncAddr,&info) && info.dli_fname)
    {
            /*hier: 'info.dli_fname' enthält den Funktionsnamen */
            /*      'info.dli_fbase' enthält die Adresse, an der die gemeinsam genutzte Bibliothek geladen ist */
    }
    else
    {
           /* Wenn wir hierher gelangen, bedeutet dies, dass das Modul nicht mit Debug-Informationen erstellt wurde oder etwas anderes Lustiges passiert ist (z.B. wir haben eine Funktion aufgerufen, die rein in Assembler geschrieben ist) */ 
    }
}

Du musst -ldl beim Verlinken hinzufügen.

Beachte, dass:

  • Die Funktion find_func muss aus deinem Profilierungsprozess aufgerufen werden (d.h. irgendwo aus deinen __cyg_profile_func_enter oder __cyg_profile_func_exit Funktionen), weil die Adresse pfnFuncAddr die tatsächliche Funktionsadresse ist (d.h. sollte gleich den this_fn oder call_site Argumenten der __cyg_* Funktionen sein)

  • Der Funktionsname, den du erhalten wirst, kann verfremdet sein (wenn es sich um eine C++ Funktion/Methode einer Klasse handelt). Du kannst den Namen mit dem Befehlszeilentool namens c++filt entfremden. Wenn du den Namen von deinem Profiler-Code entfremden möchtest, musst du dir die bfd Bibliothek und Funktionen wie bfd_read_minisymbols bfd_demangle und anderen anschauen. Wenn du deinen Code wirklich profilieren möchtest, kann es eine gute Idee sein, später (nach dem Profiling) alle Funktionsnamen zu entfremden.

  • Der Unterschied in den Adresswerten, den du beobachtet hast, ist genau der Unterschied zwischen der tatsächlichen Adresse der fraglichen Funktion(en) und der Basisadresse, an der das Modul geladen wurde (d.h. der info.dli_fbase).

Ich hoffe, das hilft.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X