aboutsummaryrefslogtreecommitdiffhomepage
path: root/tdutils/td/utils/port/stacktrace.cpp
blob: 485aa6ed97679015cb1d8a69ff4098087179e615 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
//
// Copyright Aliaksei Levin (levlam@telegram.org), Arseny Smirnov (arseny30@gmail.com) 2014-2025
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include "td/utils/port/stacktrace.h"

#include "td/utils/port/signals.h"

#if __GLIBC__
#include <execinfo.h>
#endif

#if TD_LINUX || TD_FREEBSD
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#if TD_LINUX
#include <sys/prctl.h>
#endif
#endif

namespace td {

namespace {

void print_backtrace() {
#if TD_PORT_WINDOWS
  void *buffer[128];
  USHORT nptrs = CaptureStackBackTrace(0, 128, buffer, nullptr);
#elif __GLIBC__
  void *buffer[128];
  int nptrs = backtrace(buffer, 128);
#else
  return;
#endif

  signal_safe_write("------- Stack Backtrace -------\n", false);
#if TD_PORT_WINDOWS
  for (USHORT i = 0; i < nptrs; i++) {
    signal_safe_write_pointer(buffer[i], false);
  }
#elif __GLIBC__
  backtrace_symbols_fd(buffer, nptrs, 2);
#endif
  signal_safe_write("-------------------------------\n", false);
}

void print_backtrace_gdb() {
#if TD_LINUX || TD_FREEBSD
  char pid_buf[30];
  char *pid_buf_begin = pid_buf + sizeof(pid_buf);
  pid_t pid = getpid();
  *--pid_buf_begin = '\0';
  do {
    *--pid_buf_begin = static_cast<char>(pid % 10 + '0');
    pid /= 10;
  } while (pid > 0);

  char name_buf[512];
  ssize_t res = readlink("/proc/self/exe", name_buf, 511);  // TODO works only under Linux
  if (res >= 0) {
    name_buf[res] = 0;

#if TD_LINUX
#if defined(PR_SET_DUMPABLE)
    if (prctl(PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) {
      signal_safe_write("Can't set dumpable\n");
      return;
    }
#endif
#if defined(PR_SET_PTRACER)
    // We can't use event fd because we are in a signal handler
    int fds[2];
    bool need_set_ptracer = true;
    if (pipe(fds) < 0) {
      need_set_ptracer = false;
      signal_safe_write("Can't create a pipe\n");
    }
#endif
#endif

    int child_pid = fork();
    if (child_pid < 0) {
      signal_safe_write("Can't fork() to run gdb\n");
      return;
    }
    if (!child_pid) {
#if TD_LINUX && defined(PR_SET_PTRACER)
      if (need_set_ptracer) {
        char c;
        if (read(fds[0], &c, 1) < 0) {
          signal_safe_write("Failed to read from pipe\n");
        }
      }
#endif
      dup2(2, 1);  // redirect output to stderr
      execlp("gdb", "gdb", "--batch", "-n", "-ex", "thread", "-ex", "thread apply all bt full", name_buf, pid_buf_begin,
             nullptr);
      return;
    } else {
#if TD_LINUX && defined(PR_SET_PTRACER)
      if (need_set_ptracer) {
        if (prctl(PR_SET_PTRACER, child_pid, 0, 0, 0) < 0) {
          signal_safe_write("Can't set ptracer\n");
        }
        if (write(fds[1], "a", 1) != 1) {
          signal_safe_write("Can't write to pipe\n");
        }
      }
#endif
      waitpid(child_pid, nullptr, 0);
    }
  } else {
    signal_safe_write("Can't get name of executable file to pass to gdb\n");
  }
#endif
}

}  // namespace

void Stacktrace::print_to_stderr(const PrintOptions &options) {
  print_backtrace();
  if (options.use_gdb) {
    print_backtrace_gdb();
  }
}

void Stacktrace::init() {
#if __GLIBC__
  // backtrace needs to be called once to ensure that next calls are async-signal-safe
  void *buffer[1];
  backtrace(buffer, 1);
#endif
}

}  // namespace td