Linux에서 로컬 기능 호출을 추적하는 도구
문제
나는 같은 도구를 찾고 있습니다 ltrace 또는 스트라스 이는 실행 파일에서 로컬로 정의 된 기능을 추적 할 수 있습니다. 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
), 당신은 당신이 좋아하는 모든 것을 할 수 있고, 전 세계의 가치를 인쇄하거나, 일부 쉘 명령을 실행하거나, 무언가를 누르면 무언가를 우편으로 할 수 있습니다. 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
사용 errobes (Linux 3.5 이후)
모든 기능을 추적하고 싶다고 가정합니다 ~/Desktop/datalog-2.2/datalog
매개 변수로 호출 할 때 -l ~/Desktop/datalog-2.2/add.lua ~/Desktop/datalog-2.2/test.dl
cd /usr/src/linux-`uname -r`/tools/perf
for i in `./perf probe -F -x ~/Desktop/datalog-2.2/datalog`; do sudo ./perf probe -x ~/Desktop/datalog-2.2/datalog $i; done
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
sudo ./perf report -G
GCC 옵션으로 추적하려는 코드를 다시 컴파일 할 수 있다고 가정합니다. -finstrument-functions
, 당신이 사용할 수있는 에트레이스 함수를 얻으려면 그래프를 호출합니다.
출력의 모습은 다음과 같습니다.
\-- 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
Solaris에서 Truss (Strace Equivalent)는 라이브러리를 추적 할 수있는 능력을 가지고 있습니다. Strace에 그런 능력이 없다는 것을 알게되면 놀랐습니다.
$ sudo yum install frysk
$ ftrace -sym:'*' -- ./a.out
더: ftrace.1
해당 기능을 외부 라이브러리로 외부화하는 경우 (LTRACE와 함께) 호출되는 것을 볼 수 있어야합니다.
이것이 작동하는 이유는 ltrace가 앱과 라이브러리 사이에 자체적으로 참여하고 모든 코드가 하나의 파일로 내재화되면 통화를 가로 채울 수 없기 때문입니다.
IE : ltrace xterm
X 라이브러리에서 물건을 뿌리고 X는 거의 시스템이 아닙니다.
이 외부에서는이를 수행하는 유일한 방법은 Prof 플래그 또는 디버그 기호를 통한 컴파일 타임 절편입니다.
방금이 앱을 뛰어 넘었는데 흥미로워 보입니다.
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
실행 된 기능 호출 만 표시합니다.
GDB로 추적 함수 호출을 자동화하기위한 쉘 스크립트가 있습니다. 그러나 실행 프로세스에 첨부 할 수는 없습니다.
blog.superadditive.com/2007/12/01/call-graphs-using-the-gnu-project-debugger/
도구 사본 -Callgraph.tar.gz
http://web.archive.org/web/20090317091725/http://superadditive.com/software/callgraph.tar.gz
프로그램에서 모든 함수를 덤프하고 각 함수에서 중단 점으로 GDB 명령 파일을 생성합니다. 각 중단 점에서 "Backtrace 2"및 "Contoint"가 실행됩니다.
이 스크립트는 큰 porject (~ 수천 개의 함수)에서는 다소 느리므로 기능 목록에 필터를 추가합니다 (EGREP를 통해). 매우 쉬웠 으며이 스크립트를 거의 Evry Day를 사용합니다.
GPROF 당신이 원하는 것일 수 있습니다
Linux C/C ++ 응용 프로그램의 추적 프레임 워크 인 Traces를 참조하십시오.https://github.com/baruch/traces#readme
코드를 인스트루먼트로 다시 컴파일해야하지만 모든 기능, 매개 변수 및 리턴 값의 목록을 제공합니다. 대형 데이터 샘플을 쉽게 탐색 할 수있는 대화식이 있습니다.
Kcachegrind
https://kcachegrind.github.io/html/home.html
테스트 프로그램 :
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;
}
용법:
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
당신은 이제 많은 흥미로운 성능 데이터를 포함하는 멋진 GUI 프로그램 안에 남아 있습니다.
오른쪽 하단에서 "Call Graph"탭을 선택하십시오. 이것은 함수를 클릭 할 때 다른 Windows의 성능 메트릭과 관련된 대화식 통화 그래프를 보여줍니다.
그래프를 내보내려면 마우스 오른쪽 버튼을 클릭하고 "그래프 내보내기"를 선택하십시오. 내보낸 PNG는 다음과 같습니다.
그로부터 우리는 그것을 볼 수 있습니다.
- 루트 노드는입니다
_start
, 실제 ELF 진입 지점이며 GLIBC 초기화 보일러 플레이트가 포함되어 있습니다. f0
,f1
그리고f2
서로 예상대로 호출됩니다pointed
기능 포인터로 호출했지만 표시됩니다. 우리가 명령 줄 인수를 통과 한 경우에 전화되지 않았을 수도 있습니다.not_called
우리는 추가 명령 줄 인수를 통과하지 못했기 때문에 달리기에서 호출되지 않았기 때문에 표시되지 않습니다.
멋진 것 valgrind
특별한 컴파일 옵션이 필요하지 않습니다.
따라서 소스 코드가없고 실행 파일 만 있으면 사용할 수 있습니다.
valgrind
경량 "가상 머신"을 통해 코드를 실행하여이를 수행합니다.
우분투 18.04에서 테스트.
바라건대 Callgrind 또는 Cachegrind 도구 ~을 위한 Valgrind 당신이 원하는 정보를 줄 것입니다.
참고 : 이것은 Linux 커널 기반 FTRACE가 아니라 최근 로컬 기능 추적 및 제어 흐름을 달성하도록 설계된 도구입니다. Linux ELF X86_64/X86_32는 공개적으로 지원됩니다.