C 또는 C++로 콜스택 인쇄
특정 함수가 호출될 때마다 C 또는 C++에서 실행 중인 프로세스로 콜스택을 덤프하는 방법이 있습니까?제가 생각하고 있는 것은 다음과 같습니다.
void foo()
{
print_stack_trace();
// foo's body
return
}
서 ★★★★★print_stack_trace
는 Perl과 동일하게 동작합니다.
또는 다음과 같은 것이 있습니다.
int main (void)
{
// will print out debug info every time foo() is called
register_stack_trace_function(foo);
// etc...
}
서 ''는register_stack_trace_function
는 스택 .foo
출됩니니다다
스탠다드 C 도서관에 이런 게 있나요?
저는 GCC를 사용하여 Linux에서 작업하고 있습니다.
배경
이 동작에 영향을 주지 않는 명령줄 스위치에 따라 다르게 동작하는 테스트를 실시했습니다.내 코드에는 의사 난수 생성기가 있으며, 이러한 스위치에 따라 다르게 호출되고 있다고 생각합니다.각 스위치 세트에 대해 테스트를 실행하여 난수 발생기가 각각 다르게 호출되는지 확인할 수 있기를 원합니다.
스택 트레이스 강화
문서: https://www.boost.org/doc/libs/1_66_0/doc/html/stacktrace/getting_started.html#stacktrace.getting_started.how_to_print_current_call_stack
이것은 지금까지 본 옵션 중 가장 편리한 옵션입니다.그 이유는 다음과 같습니다.
실제로 라인 번호를 출력할 수 있습니다.
다만, 에 콜을 발신하는 것에 의해서, 추악한 외부 의존 관계가 추가되어 대량의 트레이스를 작성하면, 코드의 속도가 큰폭으로 저하합니다.
디폴트 디망글
Boost는 헤더뿐이므로 빌드 시스템을 변경할 필요가 거의 없습니다.
boost_stacktrace.cpp
#include <iostream>
#define BOOST_STACKTRACE_USE_ADDR2LINE
#include <boost/stacktrace.hpp>
void my_func_2(void) {
std::cout << boost::stacktrace::stacktrace() << std::endl;
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
}
int main(int argc, char **argv) {
long long unsigned int n;
if (argc > 1) {
n = strtoul(argv[1], NULL, 0);
} else {
n = 1;
}
for (long long unsigned int i = 0; i < n; ++i) {
my_func_1(1); // line 28
my_func_1(2.0); // line 29
}
}
더에 추가된.패키지 「」, 「」, 「」, 「」, 「」입니다.libboost-stacktrace-dev
Ubuntu 16.04:
sudo apt-get install libboost-stacktrace-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o boost_stacktrace.out -std=c++11 \
-Wall -Wextra -pedantic-errors boost_stacktrace.cpp -ldl
./boost_stacktrace.out
, 그럼 이제 더해야 요.-ldl
그렇지 않으면 컴파일이 실패합니다.
출력:
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
1# my_func_1(int) at /home/ciro/test/boost_stacktrace.cpp:18
2# main at /home/ciro/test/boost_stacktrace.cpp:29 (discriminator 2)
3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
4# _start in ./boost_stacktrace.out
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::basic_stacktrace() at /usr/include/boost/stacktrace/stacktrace.hpp:129
1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:13
2# main at /home/ciro/test/boost_stacktrace.cpp:27 (discriminator 2)
3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
4# _start in ./boost_stacktrace.out
출력 및 자세한 내용은 다음 "glibc 역추적" 섹션에서 설명합니다.
note에 주의해 주세요.my_func_1(int)
★★★★★★★★★★★★★★★★★」my_func_1(float)
기능 과부하로 엉망이 된 것은 우리에게 잘 어울렸습니다.
번째 " " "는int
콜이 1 회선 오프(27 회선이 아닌 28 회선), 2 회선 오프(29 회선이 아닌 27 회선)입니다.코멘트에서는, 다음의 명령 주소가 고려되고 있기 때문에, 27이 28이 되어, 29가 루프에서 뛰어내려 27이 되고 있기 때문이라고 하고 있습니다.
그것을 합니다.-O3
출력은 완전히 중단됩니다.
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
1# my_func_1(double) at /home/ciro/test/boost_stacktrace.cpp:12
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in ./boost_stacktrace.out
0# boost::stacktrace::basic_stacktrace<std::allocator<boost::stacktrace::frame> >::size() const at /usr/include/boost/stacktrace/stacktrace.hpp:215
1# main at /home/ciro/test/boost_stacktrace.cpp:31
2# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
3# _start in ./boost_stacktrace.out
일반적으로 백트레이스는 최적화에 의해 복구할 수 없을 정도로 훼손됩니다.테일 콜 최적화는 그 대표적인 예입니다.테일콜 최적화란 무엇입니까?
치치 on의 -O3
:
time ./boost_stacktrace.out 1000 >/dev/null
출력:
real 0m43.573s
user 0m30.799s
sys 0m13.665s
이을 알 수 .addr2line
는 제한된 수의 콜이 발신되는 경우에만 가능합니다.
각 역추적 인쇄에는 수백 밀리초가 걸릴 수 있으므로 역추적이 자주 발생할 경우 프로그램 성능이 크게 저하된다는 점에 유의하십시오.
Ubuntu 19.10, GCC 9.2.1, 부스트 1.67.0으로 테스트 완료.
backtrace
문서: https://www.gnu.org/software/libc/manual/html_node/Backtraces.html
메인
#include <stdio.h>
#include <stdlib.h>
/* Paste this on the file you want to debug. */
#include <stdio.h>
#include <execinfo.h>
void print_trace(void) {
char **strings;
size_t i, size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
strings = backtrace_symbols(array, size);
for (i = 0; i < size; i++)
printf("%s\n", strings[i]);
puts("");
free(strings);
}
void my_func_3(void) {
print_trace();
}
void my_func_2(void) {
my_func_3();
}
void my_func_1(void) {
my_func_3();
}
int main(void) {
my_func_1(); /* line 33 */
my_func_2(); /* line 34 */
return 0;
}
컴파일:
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -rdynamic -std=c99 \
-Wall -Wextra -pedantic-errors main.c
-rdynamic
필수 옵션입니다.
실행:
./main.out
출력:
./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0x9) [0x4008f9]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]
./main.out(print_trace+0x2d) [0x400a3d]
./main.out(main+0xe) [0x4008fe]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f35a5aad830]
./main.out(_start+0x29) [0x400939]
즉, 인라인 최적화가 이루어지고 일부 기능이 트레이스에서 손실된 것을 알 수 있습니다.
주소를 취득하려고 하면,
addr2line -e main.out 0x4008f9 0x4008fe
입수처:
/home/ciro/main.c:21
/home/ciro/main.c:36
완전히 빗나갔죠
-O0
★★★★★★★★★★★★★★★★../main.out
트레이스를 합니다.
./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_1+0x9) [0x400a68]
./main.out(main+0x9) [0x400a74]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]
./main.out(print_trace+0x2e) [0x4009a4]
./main.out(my_func_3+0x9) [0x400a50]
./main.out(my_func_2+0x9) [0x400a5c]
./main.out(main+0xe) [0x400a79]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0) [0x7f4711677830]
./main.out(_start+0x29) [0x4008a9]
그 후:
addr2line -e main.out 0x400a74 0x400a79
다음과 같은 기능이 있습니다.
/home/cirsan01/test/main.c:34
/home/cirsan01/test/main.c:35
그럼 한 명만 끊긴 거네요, 왜?그래도 쓸 수 있을 것 같아요.
는 ""로만 될 수 .-O0
최적화에서는 원래 역추적이 컴파일된 코드로 근본적으로 변경됩니다.
C++ 기호를 자동으로 해독하는 간단한 방법을 찾을 수 없었습니다만, 여기 몇 가지 해커가 있습니다.
- https://panthema.net/2008/0901-stacktrace-demangled/
- https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
Ubuntu 16.04, GCC 6.4.0, libc 2.23에서 테스트 완료.
backtrace_symbols_fd
는 조금 더 합니다.backtrace_symbols
는 기본적으로 출력을
/* Paste this on the file you want to debug. */
#include <execinfo.h>
#include <stdio.h>
#include <unistd.h>
void print_trace(void) {
size_t i, size;
enum Constexpr { MAX_SIZE = 1024 };
void *array[MAX_SIZE];
size = backtrace(array, MAX_SIZE);
backtrace_symbols_fd(array, size, STDOUT_FILENO);
puts("");
}
Ubuntu 16.04, GCC 6.4.0, libc 2.23에서 테스트 완료.
backtrace
1: C++ 디멀링 해킹 1:-export-dynamic
+dladdr
제품명 : https://gist.github.com/fmela/591333/c64f4eb86037bb237862a8283df70cdfc25f01d3
를 '해킹'으로 해야 하기 에 '입니다.이는 ELF를 다음 주소로 변경해야 하기 때문입니다.-export-dynamic
.
glibc_ldl.cpp
#include <dlfcn.h> // for dladdr
#include <cxxabi.h> // for __cxa_demangle
#include <cstdio>
#include <string>
#include <sstream>
#include <iostream>
// This function produces a stack backtrace with demangled function & method names.
std::string backtrace(int skip = 1)
{
void *callstack[128];
const int nMaxFrames = sizeof(callstack) / sizeof(callstack[0]);
char buf[1024];
int nFrames = backtrace(callstack, nMaxFrames);
char **symbols = backtrace_symbols(callstack, nFrames);
std::ostringstream trace_buf;
for (int i = skip; i < nFrames; i++) {
Dl_info info;
if (dladdr(callstack[i], &info)) {
char *demangled = NULL;
int status;
demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, &status);
std::snprintf(
buf,
sizeof(buf),
"%-3d %*p %s + %zd\n",
i,
(int)(2 + sizeof(void*) * 2),
callstack[i],
status == 0 ? demangled : info.dli_sname,
(char *)callstack[i] - (char *)info.dli_saddr
);
free(demangled);
} else {
std::snprintf(buf, sizeof(buf), "%-3d %*p\n",
i, (int)(2 + sizeof(void*) * 2), callstack[i]);
}
trace_buf << buf;
std::snprintf(buf, sizeof(buf), "%s\n", symbols[i]);
trace_buf << buf;
}
free(symbols);
if (nFrames == nMaxFrames)
trace_buf << "[truncated]\n";
return trace_buf.str();
}
void my_func_2(void) {
std::cout << backtrace() << std::endl;
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
}
int main() {
my_func_1(1);
my_func_1(2.0);
}
컴파일 및 실행:
g++ -fno-pie -ggdb3 -O0 -no-pie -o glibc_ldl.out -std=c++11 -Wall -Wextra \
-pedantic-errors -fpic glibc_ldl.cpp -export-dynamic -ldl
./glibc_ldl.out
출력:
1 0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2 0x40139e my_func_1(int) + 16
./glibc_ldl.out(_Z9my_func_1i+0x10) [0x40139e]
3 0x4013b3 main + 18
./glibc_ldl.out(main+0x12) [0x4013b3]
4 0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5 0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]
1 0x40130a my_func_2() + 41
./glibc_ldl.out(_Z9my_func_2v+0x29) [0x40130a]
2 0x40138b my_func_1(double) + 18
./glibc_ldl.out(_Z9my_func_1d+0x12) [0x40138b]
3 0x4013c8 main + 39
./glibc_ldl.out(main+0x27) [0x4013c8]
4 0x7f7594552b97 __libc_start_main + 231
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe7) [0x7f7594552b97]
5 0x400f3a _start + 42
./glibc_ldl.out(_start+0x2a) [0x400f3a]
Ubuntu 18.04로 테스트.
backtrace
해석 C++ " " 2: " " " "
표시처: https://panthema.net/2008/0901-stacktrace-demangled/
파싱이 필요하기 때문에 해킹입니다.
TODO가 컴파일하여 여기에 표시하도록 합니다.
libunwind(리본윈드)
TODO는 glibc 역추적보다 장점이 있습니까?매우 유사한 출력으로 build 명령도 수정해야 하지만 glibc의 일부가 아니므로 추가 패키지 설치가 필요합니다.
코드 : https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/ 에서 수정
메인
/* This must be on top. */
#define _XOPEN_SOURCE 700
#include <stdio.h>
#include <stdlib.h>
/* Paste this on the file you want to debug. */
#define UNW_LOCAL_ONLY
#include <libunwind.h>
#include <stdio.h>
void print_trace() {
char sym[256];
unw_context_t context;
unw_cursor_t cursor;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == 0) {
break;
}
printf("0x%lx:", pc);
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
printf(" (%s+0x%lx)\n", sym, offset);
} else {
printf(" -- error: unable to obtain symbol name for this frame\n");
}
}
puts("");
}
void my_func_3(void) {
print_trace();
}
void my_func_2(void) {
my_func_3();
}
void my_func_1(void) {
my_func_3();
}
int main(void) {
my_func_1(); /* line 46 */
my_func_2(); /* line 47 */
return 0;
}
컴파일 및 실행:
sudo apt-get install libunwind-dev
gcc -fno-pie -ggdb3 -O3 -no-pie -o main.out -std=c99 \
-Wall -Wextra -pedantic-errors main.c -lunwind
중 하나#define _XOPEN_SOURCE 700
위에 , 아니면 맨 위에 있어야 한다를 써야 . 그렇지 않으면-std=gnu99
:
실행:
./main.out
출력:
0x4007db: (main+0xb)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
0x4007e2: (main+0x12)
0x7f4ff50aa830: (__libc_start_main+0xf0)
0x400819: (_start+0x29)
또, 다음과 같이 합니다.
addr2line -e main.out 0x4007db 0x4007e2
다음과 같은 기능이 있습니다.
/home/ciro/main.c:34
/home/ciro/main.c:49
★★★★★★★★★★★★★★★★ -O0
:
0x4009cf: (my_func_3+0xe)
0x4009e7: (my_func_1+0x9)
0x4009f3: (main+0x9)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)
0x4009cf: (my_func_3+0xe)
0x4009db: (my_func_2+0x9)
0x4009f8: (main+0xe)
0x7f7b84ad7830: (__libc_start_main+0xf0)
0x4007d9: (_start+0x29)
또, 다음과 같이 합니다.
addr2line -e main.out 0x4009f3 0x4009f8
다음과 같은 기능이 있습니다.
/home/ciro/main.c:47
/home/ciro/main.c:48
Ubuntu 16.04, GCC 6.4.0, libunwind 1.1에서 테스트 완료.
C++ 이름의 demangling을 사용한 libunwind
코드 : https://eli.thegreenplace.net/2015/programmatic-access-to-the-call-stack-in-c/ 에서 수정
언와인드.cpp
#define UNW_LOCAL_ONLY
#include <cxxabi.h>
#include <libunwind.h>
#include <cstdio>
#include <cstdlib>
#include <iostream>
void backtrace() {
unw_cursor_t cursor;
unw_context_t context;
// Initialize cursor to current frame for local unwinding.
unw_getcontext(&context);
unw_init_local(&cursor, &context);
// Unwind frames one by one, going up the frame stack.
while (unw_step(&cursor) > 0) {
unw_word_t offset, pc;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
if (pc == 0) {
break;
}
std::printf("0x%lx:", pc);
char sym[256];
if (unw_get_proc_name(&cursor, sym, sizeof(sym), &offset) == 0) {
char* nameptr = sym;
int status;
char* demangled = abi::__cxa_demangle(sym, nullptr, nullptr, &status);
if (status == 0) {
nameptr = demangled;
}
std::printf(" (%s+0x%lx)\n", nameptr, offset);
std::free(demangled);
} else {
std::printf(" -- error: unable to obtain symbol name for this frame\n");
}
}
}
void my_func_2(void) {
backtrace();
std::cout << std::endl; // line 43
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
} // line 54
int main() {
my_func_1(1);
my_func_1(2.0);
}
컴파일 및 실행:
sudo apt-get install libunwind-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o unwind.out -std=c++11 \
-Wall -Wextra -pedantic-errors unwind.cpp -lunwind -pthread
./unwind.out
출력:
0x400c80: (my_func_2()+0x9)
0x400cb7: (my_func_1(int)+0x10)
0x400ccc: (main+0x12)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)
0x400c80: (my_func_2()+0x9)
0x400ca4: (my_func_1(double)+0x12)
0x400ce1: (main+0x27)
0x7f4c68926b97: (__libc_start_main+0xe7)
0x400a3a: (_start+0x2a)
에 '하다, 하다, 하다, 하다, 하다'의 행이 수 있어요.my_func_2
★★★★★★★★★★★★★★★★★」my_func_1(int)
라이선스:
addr2line -e unwind.out 0x400c80 0x400cb7
그 결과, 다음과 같습니다.
/home/ciro/test/unwind.cpp:43
/home/ciro/test/unwind.cpp:54
TODO: 왜 줄이 1개씩 끊어졌지?
Ubuntu 18.04, GCC 7.4.0, libunwind 1.2.1에서 테스트 완료.
GDB 자동화
GDB를 사용하여 재컴파일하지 않고 다음을 사용하여 이 작업을 수행할 수도 있습니다.GDB에서 특정 중단점이 히트했을 때 특정 액션을 수행하려면 어떻게 해야 합니까?
옵션보다 빠를 수 있지만, 할 수 .compile code
지금 테스트하는 것은 귀찮습니다.gdb에서 어셈블리를 호출하는 방법
main.cpp
void my_func_2(void) {}
void my_func_1(double f) {
my_func_2();
}
void my_func_1(int i) {
my_func_2();
}
int main() {
my_func_1(1);
my_func_1(2.0);
}
main.gdb
start
break my_func_2
commands
silent
backtrace
printf "\n"
continue
end
continue
컴파일 및 실행:
g++ -ggdb3 -o main.out main.cpp
gdb -nh -batch -x main.gdb main.out
출력:
Temporary breakpoint 1 at 0x1158: file main.cpp, line 12.
Temporary breakpoint 1, main () at main.cpp:12
12 my_func_1(1);
Breakpoint 2 at 0x555555555129: file main.cpp, line 1.
#0 my_func_2 () at main.cpp:1
#1 0x0000555555555151 in my_func_1 (i=1) at main.cpp:8
#2 0x0000555555555162 in main () at main.cpp:12
#0 my_func_2 () at main.cpp:1
#1 0x000055555555513e in my_func_1 (f=2) at main.cpp:4
#2 0x000055555555516f in main () at main.cpp:13
[Inferior 1 (process 14193) exited normally]
나는 TODO만으로 .-ex
에서, 「 라인」을 됩니다.main.gdb
나는 그 말을 듣지 못했어요.commands
★★★★★★★★★★★★★★★★★★★★★★★★★★★
Ubuntu 19.04, GDB 8.2에서 테스트 완료.
Linux 커널
Linux 커널 내에서 현재 스레드 스택 트레이스를 인쇄하려면 어떻게 해야 합니까?
libdwfl
이는 당초 https://stackoverflow.com/a/60713161/895245에서 언급되었으며, 이것이 가장 좋은 방법일 수 있지만, 저는 좀 더 벤치마크를 해야 합니다만, 그 답변을 확인해 주십시오.
TODO: 저는 그 답변의 코드를 하나의 함수로 최소화하려고 했습니다만, segfault입니다.그 이유를 찾을 수 있는 사람이 있으면 알려주세요.
dwfl.cpp
#include <cassert>
#include <iostream>
#include <memory>
#include <sstream>
#include <string>
#include <cxxabi.h> // __cxa_demangle
#include <elfutils/libdwfl.h> // Dwfl*
#include <execinfo.h> // backtrace
#include <unistd.h> // getpid
// https://stackoverflow.com/questions/281818/unmangling-the-result-of-stdtype-infoname
std::string demangle(const char* name) {
int status = -4;
std::unique_ptr<char, void(*)(void*)> res {
abi::__cxa_demangle(name, NULL, NULL, &status),
std::free
};
return (status==0) ? res.get() : name ;
}
std::string debug_info(Dwfl* dwfl, void* ip) {
std::string function;
int line = -1;
char const* file;
uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
char const* name = dwfl_module_addrname(module, ip2);
function = name ? demangle(name) : "<unknown>";
if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
Dwarf_Addr addr;
file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
}
std::stringstream ss;
ss << ip << ' ' << function;
if (file)
ss << " at " << file << ':' << line;
ss << std::endl;
return ss.str();
}
std::string stacktrace() {
// Initialize Dwfl.
Dwfl* dwfl = nullptr;
{
Dwfl_Callbacks callbacks = {};
char* debuginfo_path = nullptr;
callbacks.find_elf = dwfl_linux_proc_find_elf;
callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
callbacks.debuginfo_path = &debuginfo_path;
dwfl = dwfl_begin(&callbacks);
assert(dwfl);
int r;
r = dwfl_linux_proc_report(dwfl, getpid());
assert(!r);
r = dwfl_report_end(dwfl, nullptr, nullptr);
assert(!r);
static_cast<void>(r);
}
// Loop over stack frames.
std::stringstream ss;
{
void* stack[512];
int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
for (int i = 0; i < stack_size; ++i) {
ss << i << ": ";
// Works.
ss << debug_info(dwfl, stack[i]);
#if 0
// TODO intended to do the same as above, but segfaults,
// so possibly UB In above function that does not blow up by chance?
void *ip = stack[i];
std::string function;
int line = -1;
char const* file;
uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
Dwfl_Module* module = dwfl_addrmodule(dwfl, ip2);
char const* name = dwfl_module_addrname(module, ip2);
function = name ? demangle(name) : "<unknown>";
// TODO if I comment out this line it does not blow up anymore.
if (Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
Dwarf_Addr addr;
file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
}
ss << ip << ' ' << function;
if (file)
ss << " at " << file << ':' << line;
ss << std::endl;
#endif
}
}
dwfl_end(dwfl);
return ss.str();
}
void my_func_2() {
std::cout << stacktrace() << std::endl;
std::cout.flush();
}
void my_func_1(double f) {
(void)f;
my_func_2();
}
void my_func_1(int i) {
(void)i;
my_func_2();
}
int main(int argc, char **argv) {
long long unsigned int n;
if (argc > 1) {
n = strtoul(argv[1], NULL, 0);
} else {
n = 1;
}
for (long long unsigned int i = 0; i < n; ++i) {
my_func_1(1);
my_func_1(2.0);
}
}
컴파일 및 실행:
sudo apt install libdw-dev
g++ -fno-pie -ggdb3 -O0 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
./dwfl.out
출력:
0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d7d my_func_1(int) at /home/ciro/test/dwfl.cpp:112
3: 0x402de0 main at /home/ciro/test/dwfl.cpp:123
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1
0: 0x402b74 stacktrace[abi:cxx11]() at /home/ciro/test/dwfl.cpp:65
1: 0x402ce0 my_func_2() at /home/ciro/test/dwfl.cpp:100
2: 0x402d66 my_func_1(double) at /home/ciro/test/dwfl.cpp:107
3: 0x402df1 main at /home/ciro/test/dwfl.cpp:121
4: 0x7f7efabbe1e3 __libc_start_main at ../csu/libc-start.c:342
5: 0x40253e _start at ../csu/libc-start.c:-1
벤치마크 실행:
g++ -fno-pie -ggdb3 -O3 -no-pie -o dwfl.out -std=c++11 -Wall -Wextra -pedantic-errors dwfl.cpp -ldw
time ./dwfl.out 1000 >/dev/null
출력:
real 0m3.751s
user 0m2.822s
sys 0m0.928s
따라서 이 방법은 Boost의 스택 추적보다 10배 더 빠르기 때문에 더 많은 사용 사례에 적용할 수 있습니다.
Ubuntu 19.10 amd64, libdw-dev 0.176-1.1에서 테스트 완료.
libbacktrace(라이브백 트레이스)
https://github.com/ianlancetaylor/libbacktrace
하코어 도서관의 저자를 생각하면, 이것을 시도해 볼 가치가 있다, 아마도 The One일 것이다.TODO가 확인해봐.
C/C++ 프로그램에 링크되어 심볼릭 백트레이스를 생성할 수 있는 C 라이브러리
2020년 10월 현재 libbacktrace는 DWARF 디버깅 정보가 포함된 ELF, PE/COFF, Mach-O 및 XCOFF 실행 파일을 지원합니다.즉, GNU/Linux, *BSD, macOS, Windows 및 AIX를 지원합니다.라이브러리는 다른 개체 파일 및 디버깅 형식을 쉽게 추가할 수 있도록 작성되었습니다.
라이브러리는 https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html에서 정의된 C++ unwind API에 의존합니다.이 API는 GCC와 clang에 의해 제공됩니다.
「 」를 참조해 주세요.
- C에서 스택 트레이스를 취득하려면 어떻게 해야 합니까?
- backtrace()/backtrace_symbols()가 함수 이름을 인쇄하도록 하려면 어떻게 해야 합니까?
- 파일명과 아마포를 스택트레이스로 취득하는 포터블/표준 준거 방법이 있나요?
- 스택 트레이스를 인쇄하기 위해 내부 프로그램에서 gdb를 호출하는 가장 좋은 방법?
- 「 」 「 」 、 「 」 、 「 」
Linux 전용 솔루션에서는 백트레이스(3)를 사용할 수 있습니다.백트레이스(3)를 사용하면void *
(실제로 이들 각 포인트는 대응하는 스택프레임으로부터의 리턴 주소를 가리키고 있습니다).이것들을 유용한 것으로 변환하려면 , backtrace_symbols(3)가 있습니다.
특별한 링커 옵션을 사용하지 않으면 기호 이름을 사용할 수 없습니다.GNU 링커를 사용하는 시스템에서는 -rdynamic linker 옵션을 사용해야 합니다."static" 함수의 이름은 공개되지 않으며 백트레이스에서는 사용할 수 없습니다.
에는 C++23이 .<stacktrace>
을 사용하다
#include <stacktrace>
/* ... */
std::cout << std::stacktrace::current();
★★★★
• https://en.cppreference.com/w/cpp/header/stacktrace
• https://en.cppreference.com/w/cpp/utility/basic_stacktrace/operator_ltlt
Linux 고유, TLDR:
backtrace
에glibc
는, 「스택 트레이스」가 「스택 트레이스」를 유효하게 하는 경우에 해, 정확한 합니다.-lunwind
는 링크되어 있습니다(플랫폼 고유의 기능).- 함수 이름, 소스 파일 및 회선 번호를 출력하려면 를 사용합니다(이 라이브러리는 헤더 파일에만 기재되어 있습니다).
backtrace_symbols
★★★★★★★★★★★★★★★★★」backtrace_symbolsd_fd
가장 유익하지 않습니다.
최신 Linux에서는 함수를 사용하여 스택 트레이스 주소를 얻을 수 있습니다.문서화되어 있지 않은 방법으로backtrace
일반적인 플랫폼에서 보다 정확한 주소를 생성하는 것은 (과) 링크하는 것입니다.libunwind-dev
Ubuntu 18.04) (Ubuntu 18.04) (Ubuntu 18.04) backtrace
사용_Unwind_Backtrace
는 ""에서 .libgcc_s.so.1
그 실장은 휴대성이 가장 뛰어납니다.-lunwind
되어 있어 보다 합니다._Unwind_Backtrace
단, 이 라이브러리는 이동성이 낮습니다(의 지원되는 아키텍처를 참조).
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★.backtrace_symbolsd
★★★★★★★★★★★★★★★★★」backtrace_symbols_fd
이 함수는 현재 약 10년 동안 스택 트레이스 주소를 소스 파일 이름과 행 번호를 가진 함수 이름으로 해결할 수 없었습니다(아래 출력 예 참조).
그러나 주소를 기호로 해결하는 다른 방법이 있으며 함수 이름, 소스 파일 및 줄 번호를 사용하여 가장 유용한 트레이스를 생성합니다.방법은#include <elfutils/libdwfl.h>
with에 합니다.-ldw
)libdw-dev
우분투 18.04)입니다.
C의 예C++)test.cc
#include <stdexcept>
#include <iostream>
#include <cassert>
#include <cstdlib>
#include <string>
#include <boost/core/demangle.hpp>
#include <execinfo.h>
#include <elfutils/libdwfl.h>
struct DebugInfoSession {
Dwfl_Callbacks callbacks = {};
char* debuginfo_path = nullptr;
Dwfl* dwfl = nullptr;
DebugInfoSession() {
callbacks.find_elf = dwfl_linux_proc_find_elf;
callbacks.find_debuginfo = dwfl_standard_find_debuginfo;
callbacks.debuginfo_path = &debuginfo_path;
dwfl = dwfl_begin(&callbacks);
assert(dwfl);
int r;
r = dwfl_linux_proc_report(dwfl, getpid());
assert(!r);
r = dwfl_report_end(dwfl, nullptr, nullptr);
assert(!r);
static_cast<void>(r);
}
~DebugInfoSession() {
dwfl_end(dwfl);
}
DebugInfoSession(DebugInfoSession const&) = delete;
DebugInfoSession& operator=(DebugInfoSession const&) = delete;
};
struct DebugInfo {
void* ip;
std::string function;
char const* file;
int line;
DebugInfo(DebugInfoSession const& dis, void* ip)
: ip(ip)
, file()
, line(-1)
{
// Get function name.
uintptr_t ip2 = reinterpret_cast<uintptr_t>(ip);
Dwfl_Module* module = dwfl_addrmodule(dis.dwfl, ip2);
char const* name = dwfl_module_addrname(module, ip2);
function = name ? boost::core::demangle(name) : "<unknown>";
// Get source filename and line number.
if(Dwfl_Line* dwfl_line = dwfl_module_getsrc(module, ip2)) {
Dwarf_Addr addr;
file = dwfl_lineinfo(dwfl_line, &addr, &line, nullptr, nullptr, nullptr);
}
}
};
std::ostream& operator<<(std::ostream& s, DebugInfo const& di) {
s << di.ip << ' ' << di.function;
if(di.file)
s << " at " << di.file << ':' << di.line;
return s;
}
void terminate_with_stacktrace() {
void* stack[512];
int stack_size = ::backtrace(stack, sizeof stack / sizeof *stack);
// Print the exception info, if any.
if(auto ex = std::current_exception()) {
try {
std::rethrow_exception(ex);
}
catch(std::exception& e) {
std::cerr << "Fatal exception " << boost::core::demangle(typeid(e).name()) << ": " << e.what() << ".\n";
}
catch(...) {
std::cerr << "Fatal unknown exception.\n";
}
}
DebugInfoSession dis;
std::cerr << "Stacktrace of " << stack_size << " frames:\n";
for(int i = 0; i < stack_size; ++i) {
std::cerr << i << ": " << DebugInfo(dis, stack[i]) << '\n';
}
std::cerr.flush();
std::_Exit(EXIT_FAILURE);
}
int main() {
std::set_terminate(terminate_with_stacktrace);
throw std::runtime_error("test exception");
}
Ubuntu 18.04.4 LTS와 gcc-8로 컴파일.3:
g++ -o test.o -c -m{arch,tune}=native -std=gnu++17 -W{all,extra,error} -g -Og -fstack-protector-all test.cc
g++ -o test -g test.o -ldw -lunwind
출력:
Fatal exception std::runtime_error: test exception.
Stacktrace of 7 frames:
0: 0x55f3837c1a8c terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7fbc1c845ae5 <unknown>
2: 0x7fbc1c845b20 std::terminate()
3: 0x7fbc1c845d53 __cxa_throw
4: 0x55f3837c1a43 main at /home/max/src/test/test.cc:103
5: 0x7fbc1c3e3b96 __libc_start_main at ../csu/libc-start.c:310
6: 0x55f3837c17e9 _start
★★★-lunwind
링크되어 있기 때문에 .스택 트레이스 「스택 트레이스」가 됩니다.
0: 0x5591dd9d1a4d terminate_with_stacktrace() at /home/max/src/test/test.cc:76
1: 0x7f3c18ad6ae6 <unknown>
2: 0x7f3c18ad6b21 <unknown>
3: 0x7f3c18ad6d54 <unknown>
4: 0x5591dd9d1a04 main at /home/max/src/test/test.cc:103
5: 0x7f3c1845cb97 __libc_start_main at ../csu/libc-start.c:344
6: 0x5591dd9d17aa _start
위해 「 」를 참조해 주세요.backtrace_symbols_fd
같은 스택 트레이스에서의 출력은 정보량이 가장 적습니다.
/home/max/src/test/debug/gcc/test(+0x192f)[0x5601c5a2092f]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(+0x92ae5)[0x7f95184f5ae5]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(_ZSt9terminatev+0x10)[0x7f95184f5b20]
/usr/lib/x86_64-linux-gnu/libstdc++.so.6(__cxa_throw+0x43)[0x7f95184f5d53]
/home/max/src/test/debug/gcc/test(+0x1ae7)[0x5601c5a20ae7]
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xe6)[0x7f9518093b96]
/home/max/src/test/debug/gcc/test(+0x1849)[0x5601c5a20849]
도 마찬가지)에서는, 「」( 「C」)나 「C」( 「C」)를 치환하는 으로써, 이 더 하게 할 수 .boost::core::demangle
,std::string
★★★★★★★★★★★★★★★★★」std::cout
기본 통화와 함께요
덮어쓸 수도 있습니다.__cxa_throw
예외가 발생했을 때 스택 트레이스를 캡처하고 예외가 검출되었을 때 인쇄합니다.「」에 catch
에 block을 호출하기에는 .backtrace
throw
이는 함수에 의해 구현됩니다.멀티 스레드 프로그램에서는__cxa_throw
스레드로 할 수 를 글로벌어레이로 캡처해야 .thread_local
.
스택 트레이스 인쇄 기능을 비동기 시그널로 안전하게 할 수도 있습니다.이것에 의해, 유저로부터 직접 기동할 수 있습니다.SIGSEGV
,SIGBUS
시그널 핸들러(고장성을 위해 자체 스택을 사용해야 합니다).함수 이름, 소스 파일 및 라인 번호 가져오기libdwfl
비동기 시그널의 세이프가 아니거나 프로세스의 주소 공간이 실질적으로 파손되어 있는 경우는, 시그널 핸들러로부터의 에러가 발생할 가능성이 있습니다만, 실제로는 99% 성공합니다(실패하는 것은 본 적이 없습니다.
요약하면 자동 스택 트레이스 출력을 위한 완전한 실가동 가능 라이브러리는 다음과 같습니다.
-
throw
이치노 - 처리되지 않은 예외에 대해 스택 트레이스를 자동으로 인쇄합니다.
- 스택 트레이스를 비동기 시그널 세이프 방식으로 인쇄합니다.
- 비동기 시그널 세이프 방식으로 스택트레이스를 인쇄하는 자체 스택을 사용하는 견고한 신호 핸들러 기능을 제공합니다.는 이 을 "Signal " ( 핸들러)로할 수 .
SIGSEGV
,SIGBUS
,SIGFPE
개요 - 시을 CPU에서 .
ucontext_t
인수 레지스터), messagessignal function " ( " " " " " " " " " " ) 、 " ops " " " " " " 입니다.
오래된 스레드에 대한 또 다른 답변입니다.
할 는 보통 요.system()
★★★★★★★★★★★★★★★★★」pstack
예를 들어 다음과 같습니다.
#include <sys/types.h>
#include <unistd.h>
#include <string>
#include <sstream>
#include <cstdlib>
void f()
{
pid_t myPid = getpid();
std::string pstackCommand = "pstack ";
std::stringstream ss;
ss << myPid;
pstackCommand += ss.str();
system(pstackCommand.c_str());
}
void g()
{
f();
}
void h()
{
g();
}
int main()
{
h();
}
이 출력은
#0 0x00002aaaab62d61e in waitpid () from /lib64/libc.so.6
#1 0x00002aaaab5bf609 in do_system () from /lib64/libc.so.6
#2 0x0000000000400c3c in f() ()
#3 0x0000000000400cc5 in g() ()
#4 0x0000000000400cd1 in h() ()
#5 0x0000000000400cdd in main ()
Linux, FreeBSD 및 Solaris에서 동작합니다.macOS에는 pstack이나 단순한 동등한 기능이 없다고 생각합니다만, 이 스레드는 대체 기능이 있는 것 같습니다.
「 」를 사용하고 C
하면 C
functionsstring 。
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
void f()
{
pid_t myPid = getpid();
/*
length of command 7 for 'pstack ', 7 for the PID, 1 for nul
*/
char pstackCommand[7+7+1];
sprintf(pstackCommand, "pstack %d", (int)myPid);
system(pstackCommand);
}
이 투고에 근거해, PID 의 최대 자리수에 7 을 사용했습니다.
이 실이 오래되었다는 것은 알지만, 다른 사람에게도 도움이 될 수 있다고 생각해요.gcc를 사용하는 경우 해당 계측기 기능(-finstruction-functions 옵션)을 사용하여 함수 호출(엔트리 및 종료)을 기록할 수 있습니다.상세한 것에 대하여는, http://hacktalks.blogspot.fr/2013/08/gcc-instrument-functions.html 를 참조해 주세요.
예를 들어 모든 콜을 스택에 푸시하여 팝할 수 있습니다.인쇄할 때는 스택에 저장되어 있는 것을 확인합니다.
테스트를 해봤는데, 완벽하게 작동하며 매우 편리합니다.
업데이트: -finstruction-module 컴파일 옵션에 대한 정보는 다음 GCC 문서에서 찾을 수 있습니다.https://gcc.gnu.org/onlinedocs/gcc/Instrumentation-Options.html
특정 함수가 호출될 때마다 C 또는 C++에서 실행 중인 프로세스로 콜스택을 덤프하는 방법이 있습니까?
특정 함수에서 return 스테이트먼트 대신 매크로 함수를 사용할 수 있습니다.
예를 들어, 반환을 사용하는 대신
int foo(...)
{
if (error happened)
return -1;
... do something ...
return 0
}
매크로 기능을 사용할 수 있습니다.
#include "c-callstack.h"
int foo(...)
{
if (error happened)
NL_RETURN(-1);
... do something ...
NL_RETURN(0);
}
함수에서 오류가 발생할 때마다 다음과 같이 Java 스타일의 콜스택이 표시됩니다.
Error(code:-1) at : so_topless_ranking_server (sample.c:23)
Error(code:-1) at : nanolat_database (sample.c:31)
Error(code:-1) at : nanolat_message_queue (sample.c:39)
Error(code:-1) at : main (sample.c:47)
전체 소스 코드는 여기에서 확인할 수 있습니다.
c-callstack(https://github.com/Nanolat)
Boost 라이브러리를 사용하여 현재 콜스택을 인쇄할 수 있습니다.
#include <boost/stacktrace.hpp>
// ... somewhere inside the `bar(int)` function that is called recursively:
std::cout << boost::stacktrace::stacktrace();
담당자 : https://www.boost.org/doc/libs/1_65_1/doc/html/stacktrace.html
다음과 같은 기능을 직접 구현할 수 있습니다.
글로벌(string) 스택을 사용하여 각 함수의 시작 시 함수 이름 및 기타 값(파라미터 등)을 이 스택에 푸시하고 함수 종료 시 다시 팝합니다.
호출 시 스택콘텐츠를 출력하는 함수를 작성하여 콜스택을 표시하는 함수로 사용합니다.
이것은 많은 일처럼 들리겠지만 꽤 유용하다.
그것을 할 수 있는 표준화된 방법은 없다.Windows의 경우 이 기능은 DbgHelp 라이브러리에 제공됩니다.
물론 다음 질문은 이것만으로 충분할까요?
스택 트레이스의 주요 단점은 정확한 함수를 호출하는 이유에는 인수 값 등 디버깅에 매우 유용한 다른 기능이 없다는 것입니다.
및 수 gcc gdb를 하는 것이 .assert
특정 상태를 체크하고 충족되지 않은 경우 메모리 덤프를 생성합니다.물론 이는 프로세스가 중지됨을 의미하지만 단순한 스택 트레이스가 아니라 완전한 보고서가 작성됩니다.
방해받지 않는 방법을 원할 경우 언제든지 로깅을 사용할 수 있습니다.예를 들어 판테이오스와 같은 매우 효율적인 벌목 시설이 있습니다.다시 한 번 말씀드리면 무슨 일이 일어나고 있는지 훨씬 더 정확하게 알 수 있습니다.
특정 함수가 호출될 때마다 C 또는 C++에서 실행 중인 프로세스로 콜스택을 덤프하는 방법이 있습니까?
아니요, 플랫폼 의존형 솔루션이 존재할 수 있지만 없습니다.
GNU の gnu gnu gnu gnu gnu gnu gnu gnu gnu gnu gnu gnu gnu 。는 '콜그래프'입니다.명령어는gprof
옵션과 함께 코드를 컴파일해야 합니다.
파피로 이걸 쓸 수갑시다.보통 크래시 중에 스택트레이스를 수집하기 위해 사용되지만 실행 중인 프로그램용으로도 출력할 수 있습니다.
여기서 좋은 점은 스택 상의 각 함수에 대한 실제 파라미터 값 및 로컬 변수, 루프 카운터 등을 출력할 수 있다는 것입니다.
언급URL : https://stackoverflow.com/questions/3899870/print-call-stack-in-c-or-c
'programing' 카테고리의 다른 글
Vue에서 (클릭된 이미지를 확대/축소하기 위해) 모달 대화 상자에 이미지 src를 전달하려면 어떻게 해야 합니까? (0) | 2022.08.12 |
---|---|
컴포넌트에 믹스인이 전달되었는지 어떻게 확인합니까? (0) | 2022.08.12 |
이름이 지정된 EntityManager의 지속성 제공자가 없습니다. (0) | 2022.08.12 |
재사용 가능한 컴포넌트를 사용할 때 텍스트 영역의 값을 변경하려면 어떻게 해야 합니까? (0) | 2022.08.12 |
전면에서 사용자 역할을 안전하게 저장할 위치 (0) | 2022.08.12 |