سؤال

أنا أبحث عن أداة مثل ltrace أو strace التي يمكن أن تتبع وظائف محددة محليا في تنفيذ. يتتبع Ltrace فقط مكالمات المكتبة الديناميكية و Strace فقط يتتبع مكالمات النظام. على سبيل المثال ، بالنظر إلى برنامج C التالي:

#include <stdio.h>

int triple ( int x )
{
  return 3 * x;
}

int main (void)
{
  printf("%d\n", triple(10));
  return 0;
}

تشغيل البرنامج مع ltrace سوف تظهر المكالمة إلى printf نظرًا لأن هذه وظيفة مكتبة قياسية (وهي مكتبة ديناميكية على نظامي) و strace سيعرض جميع مكالمات النظام من رمز بدء التشغيل ، ومكالمات النظام المستخدمة لتنفيذ printf ، ورمز الإغلاق ، لكني أريد شيئًا سيظهر لي أن الوظيفة triple كان يسمى. على افتراض أن الوظائف المحلية لم تُضطر من قبل برنامج التحويل البرمجي الأمثل وأن الثنائي لم يتم تجريده (تم إزالة الرموز) ، هل هناك أداة يمكنها القيام بذلك؟

يحرر

بعض التوضيحات:

  • لا بأس إذا توفر الأداة أيضًا معلومات تتبع للوظائف غير المحلية.
  • لا أرغب في إعادة ترجمة البرنامج (البرامج) بدعم لأدوات محددة ، يجب أن تكون معلومات الرمز في القابلة للتنفيذ كافية.
  • سأكون لطيفًا حقًا إذا كان بإمكاني استخدام الأداة لربط العمليات الحالية كما أستطيع مع LTRACE/Strace.
هل كانت مفيدة؟

المحلول

على افتراض أنك تريد فقط أن يتم إخطارها لوظائف محددة ، يمكنك القيام بذلك على هذا النحو:

تجميع معلومات التصحيح (بما أن لديك بالفعل معلومات رمزية ، وربما يكون لديك أيضًا ما يكفي من الأخطاء)

معطى

#include <iostream>

int fac(int n) {
    if(n == 0)
        return 1;
    return n * fac(n-1);
}

int main()
{
    for(int i=0;i<4;i++)
        std::cout << fac(i) << std::endl;
}

استخدم GDB لتتبع:

[js@HOST2 cpp]$ g++ -g3 test.cpp
[js@HOST2 cpp]$ gdb ./a.out
(gdb) b fac
Breakpoint 1 at 0x804866a: file test.cpp, line 4.
(gdb) commands 1
Type commands for when breakpoint 1 is hit, one per line.
End with a line saying just "end".
>silent
>bt 1
>c
>end
(gdb) run
Starting program: /home/js/cpp/a.out
#0  fac (n=0) at test.cpp:4
1
#0  fac (n=1) at test.cpp:4
#0  fac (n=0) at test.cpp:4
1
#0  fac (n=2) at test.cpp:4
#0  fac (n=1) at test.cpp:4
#0  fac (n=0) at test.cpp:4
2
#0  fac (n=3) at test.cpp:4
#0  fac (n=2) at test.cpp:4
#0  fac (n=1) at test.cpp:4
#0  fac (n=0) at test.cpp:4
6

Program exited normally.
(gdb)

إليك ما أقوم به لجمع عناوين جميع الوظائف:

tmp=$(mktemp)
readelf -s ./a.out | gawk '
{ 
  if($4 == "FUNC" && $2 != 0) { 
    print "# code for " $NF; 
    print "b *0x" $2; 
    print "commands"; 
    print "silent"; 
    print "bt 1"; 
    print "c"; 
    print "end"; 
    print ""; 
  } 
}' > $tmp; 
gdb --command=$tmp ./a.out; 
rm -f $tmp

لاحظ أنه بدلاً من مجرد طباعة الإطار الحالي (bt 1) ، يمكنك أن تفعل أي شيء تريده ، أو طباعة قيمة بعض أوامر SHELL أو تنفيذ بعض الأوامر أو إرسال شيء ما إذا كان يضرب fatal_bomb_exploded الوظيفة :) للأسف ، يقوم GCC بإخراج بعض الرسائل "اللغة الحالية". ولكن هذا هو بسهولة خبث. ليس مهما.

نصائح أخرى

صنبور النظام يمكن استخدامها على صندوق Linux الحديث (Fedora 10 ، Rhel 5 ، إلخ).

أول تنزيل Para-Callgraph.Stp النصي.

ثم اركض:

$ sudo stap para-callgraph.stp 'process("/bin/ls").function("*")' -c /bin/ls
0    ls(12631):->main argc=0x1 argv=0x7fff1ec3b038
276  ls(12631): ->human_options spec=0x0 opts=0x61a28c block_size=0x61a290
365  ls(12631): <-human_options return=0x0
496  ls(12631): ->clone_quoting_options o=0x0
657  ls(12631):  ->xmemdup p=0x61a600 s=0x28
815  ls(12631):   ->xmalloc n=0x28
908  ls(12631):   <-xmalloc return=0x1efe540
950  ls(12631):  <-xmemdup return=0x1efe540
990  ls(12631): <-clone_quoting_options return=0x1efe540
1030 ls(12631): ->get_quoting_style o=0x1efe540

أنظر أيضا: مراقبة التحديثات ، SystemTap و Oprofile

استخدام uprobes (منذ Linux 3.5)

على افتراض أنك تريد تتبع جميع الوظائف في ~/Desktop/datalog-2.2/datalog عند تسميتها بالمعلمات -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl

  1. cd /usr/src/linux-`uname -r`/tools/perf
  2. for i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; done
  3. sudo ./perf record -agR $(for j in $(sudo ./perf probe -l | cut -d' ' -f3); do echo "-e $j"; done) ~/Desktop/datalog-2.2/datalog -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl
  4. sudo ./perf report -G

list of functions in datalog binary call tree when selecting dl_pushlstring, showing how main called loadfile called dl_load called program called rule which called literal which in turn called other functions that ended up calling dl_pushlstring, scan (parent: program, that is, the third scan from the top) which called dl_pushstring and so on

على افتراض أنه يمكنك إعادة ترجمة (لا يوجد تغيير في المصدر) الرمز الذي تريد تتبعه مع خيار GCC -finstrument-functions, ، يمكنك استخدام etrace للحصول على الرسم البياني لدعوة الوظيفة.

هذا هو ما يبدو عليه الإخراج:

\-- main
|   \-- Crumble_make_apple_crumble
|   |   \-- Crumble_buy_stuff
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   |   \-- Crumble_buy
|   |   \-- Crumble_prepare_apples
|   |   |   \-- Crumble_skin_and_dice
|   |   \-- Crumble_mix
|   |   \-- Crumble_finalize
|   |   |   \-- Crumble_put
|   |   |   \-- Crumble_put
|   |   \-- Crumble_cook
|   |   |   \-- Crumble_put
|   |   |   \-- Crumble_bake

على سولاريس ، يتمتع الجمالون (مكافئ Strace) بالقدرة على تصفية المكتبة ليتم تتبعها. لقد فوجئت عندما اكتشفت أن Strace ليس لديه مثل هذه القدرة.

$ sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out

أكثر: ftrace.1

إذا قمت بإجراء هذه الوظيفة الخارجية في مكتبة خارجية ، فيجب أن تكون قادرًا أيضًا على رؤيتها يتم استدعاؤها ، (مع LTRACE).

السبب في أن هذا يعمل لأن Ltrace يضع نفسه بين تطبيقك والمكتبة ، وعندما يتم استيعاب جميع التعليمات البرمجية مع الملف الوحيد ، لا يمكن اعتراض المكالمة.

أي: ltrace xterm

ينشر الأشياء من مكتبات X ، و X بالكاد نظام.

خارج هذا ، فإن الطريقة الحقيقية الوحيدة للقيام بذلك هي اعتراض وقت الترجمة عبر أعلام البروفيسور أو رموز التصحيح.

لقد ركضت للتو على هذا التطبيق ، والذي يبدو مثيرًا للاهتمام:

http://www.gnu.org/software/cflow/

لكني لا أعتقد أن هذا ما تريد.

إذا لم تكن الوظائف محصورة ، فقد يكون لديك محظوظ باستخدام objdump -d <program>.

على سبيل المثال ، دعنا نأخذ نهبًا في بداية GCC 4.3.2 main نمط:

$ objdump `which gcc` -d | grep '\(call\|main\)' 

08053270 <main>:
8053270:    8d 4c 24 04             lea    0x4(%esp),%ecx
--
8053299:    89 1c 24                mov    %ebx,(%esp)
805329c:    e8 8f 60 ff ff          call   8049330 <strlen@plt>
80532a1:    8d 04 03                lea    (%ebx,%eax,1),%eax
--
80532cf:    89 04 24                mov    %eax,(%esp)
80532d2:    e8 b9 c9 00 00          call   805fc90 <xmalloc_set_program_name>
80532d7:    8b 5d 9c                mov    0xffffff9c(%ebp),%ebx
--
80532e4:    89 04 24                mov    %eax,(%esp)
80532e7:    e8 b4 a7 00 00          call   805daa0 <expandargv>
80532ec:    8b 55 9c                mov    0xffffff9c(%ebp),%edx
--
8053302:    89 0c 24                mov    %ecx,(%esp)
8053305:    e8 d6 2a 00 00          call   8055de0 <prune_options>
805330a:    e8 71 ac 00 00          call   805df80 <unlock_std_streams>
805330f:    e8 4c 2f 00 00          call   8056260 <gcc_init_libintl>
8053314:    c7 44 24 04 01 00 00    movl   $0x1,0x4(%esp)
--
805331c:    c7 04 24 02 00 00 00    movl   $0x2,(%esp)
8053323:    e8 78 5e ff ff          call   80491a0 <signal@plt>
8053328:    83 e8 01                sub    $0x1,%eax

يتطلب الأمر بعض الجهد للتجول من خلال جميع المجمع ، ولكن يمكنك رؤية جميع المكالمات الممكنة من وظيفة معينة. ليس من السهل استخدامه مثل gprof أو بعض المرافق الأخرى المذكورة ، ولكن لديها العديد من المزايا المتميزة:

  • عمومًا لا تحتاج إلى إعادة ترجمة تطبيق لاستخدامه
  • إنه يعرض جميع مكالمات الوظائف الممكنة ، في حين أن شيئًا مثل gprof سوف تظهر فقط مكالمات الوظيفة المنفذة.

يوجد برنامج نصي Shell لمكالمات دالة تتبع التتبع مع GDB. لكنها لا يمكن أن تعلق على عملية التشغيل.

blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger/

نسخة من الصفحة - http://web.archive.org/web/20090317091725/http://blog.superadditive.com/2007/12/01/call-graphs-using-the-project-debugger/

نسخة من الأداة - callgraph.tar.gz

http://web.archive.org/web/20090317091725/http://superadditive.com/software/callgraph.tar.gz

يتفريغ جميع الوظائف من البرنامج وإنشاء ملف أمر GDB مع نقاط التوقف على كل وظيفة. في كل نقطة انقطاع ، يتم تنفيذ "Backtrace 2" و "Contens".

هذا البرنامج النصي بطيء إلى حد ما على الشفرة الكبيرة (~ آلاف الوظائف) ، لذلك أقوم بإضافة مرشح في قائمة الوظائف (عبر EGREP). كان الأمر سهلاً للغاية ، وأنا أستخدم هذا البرنامج النصي تقريبًا.

GPROF قد يكون ما تريد

انظر Traces ، إطار تتبع لتطبيقات Linux C/C ++:https://github.com/baruch/traces#readme

إنه يتطلب إعادة تجميع الكود الخاص بك مع أداةها ، ولكنها ستوفر قائمة بجميع الوظائف ومعلماتها وقيم الإرجاع. هناك تفاعلي للسماح بسهولة التنقل في عينات البيانات الكبيرة.

KcacheGrind

https://kcachegrind.github.io/html/Home.html

Test program:

int f2(int i) { return i + 2; }
int f1(int i) { return f2(2) + i + 1; }
int f0(int i) { return f1(1) + f2(2); }
int pointed(int i) { return i; }
int not_called(int i) { return 0; }

int main(int argc, char **argv) {
    int (*f)(int);
    f0(1);
    f1(1);
    f = pointed;
    if (argc == 1)
        f(1);
    if (argc == 2)
        not_called(1);
    return 0;
}

Usage:

sudo apt-get install -y kcachegrind valgrind

# Compile the program as usual, no special flags.
gcc -ggdb3 -O0 -o main -std=c99 main.c

# Generate a callgrind.out.<PID> file.
valgrind --tool=callgrind ./main

# Open a GUI tool to visualize callgrind data.
kcachegrind callgrind.out.1234

You are now left inside an awesome GUI program that contains a lot of interesting performance data.

On the bottom right, select the "Call graph" tab. This shows an interactive call graph that correlates to performance metrics in other windows as you click the functions.

To export the graph, right click it and select "Export Graph". The exported PNG looks like this:

From that we can see that:

  • the root node is _start, which is the actual ELF entry point, and contains glibc initialization boilerplate
  • f0, f1 and f2 are called as expected from one another
  • pointed is also shown, even though we called it with a function pointer. It might not have been called if we had passed a command line argument.
  • not_called is not shown because it didn't get called in the run, because we didn't pass an extra command line argument.

The cool thing about valgrind is that it does not require any special compilation options.

Therefore, you could use it even if you don't have the source code, only the executable.

valgrind manages to do that by running your code through a lightweight "virtual machine".

Tested on Ubuntu 18.04.

Hopefully the callgrind or cachegrind tools for Valgrind will give you the information you seek.

NOTE: This is not the linux kernel based ftrace, but rather a tool I recently designed to accomplish local function tracing and control flow. Linux ELF x86_64/x86_32 are supported publicly.

https://github.com/leviathansecurity/ftrace

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top