about summary refs log tree commit diff
path: root/absl/debugging/internal/stacktrace_powerpc-inl.inc
blob: 2e7c2f404f20e07a01319d0bd5dbeedcaa6461f9 (plain) (blame)
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
// Copyright 2017 The Abseil Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Produce stack trace.  I'm guessing (hoping!) the code is much like
// for x86.  For apple machines, at least, it seems to be; see
//    https://developer.apple.com/documentation/mac/runtimehtml/RTArch-59.html
//    https://www.linux-foundation.org/spec/ELF/ppc64/PPC-elf64abi-1.9.html#STACK
// Linux has similar code: http://patchwork.ozlabs.org/linuxppc/patch?id=8882

#ifndef ABSL_DEBUGGING_INTERNAL_STACKTRACE_POWERPC_INL_H_
#define ABSL_DEBUGGING_INTERNAL_STACKTRACE_POWERPC_INL_H_

#if defined(__linux__)
#include <asm/ptrace.h>   // for PT_NIP.
#include <ucontext.h>     // for ucontext_t
#endif

#include <unistd.h>
#include <cassert>
#include <cstdint>
#include <cstdio>

#include "absl/base/attributes.h"
#include "absl/base/optimization.h"
#include "absl/base/port.h"
#include "absl/debugging/stacktrace.h"
#include "absl/debugging/internal/address_is_readable.h"
#include "absl/debugging/internal/vdso_support.h"  // a no-op on non-elf or non-glibc systems

// Given a stack pointer, return the saved link register value.
// Note that this is the link register for a callee.
static inline void *StacktracePowerPCGetLR(void **sp) {
  // PowerPC has 3 main ABIs, which say where in the stack the
  // Link Register is.  For DARWIN and AIX (used by apple and
  // linux ppc64), it's in sp[2].  For SYSV (used by linux ppc),
  // it's in sp[1].
#if defined(_CALL_AIX) || defined(_CALL_DARWIN)
  return *(sp+2);
#elif defined(_CALL_SYSV)
  return *(sp+1);
#elif defined(__APPLE__) || defined(__FreeBSD__) || \
    (defined(__linux__) && defined(__PPC64__))
  // This check is in case the compiler doesn't define _CALL_AIX/etc.
  return *(sp+2);
#elif defined(__linux)
  // This check is in case the compiler doesn't define _CALL_SYSV.
  return *(sp+1);
#else
#error Need to specify the PPC ABI for your archiecture.
#endif
}

// Given a pointer to a stack frame, locate and return the calling
// stackframe, or return null if no stackframe can be found. Perform sanity
// checks (the strictness of which is controlled by the boolean parameter
// "STRICT_UNWINDING") to reduce the chance that a bad pointer is returned.
template<bool STRICT_UNWINDING, bool IS_WITH_CONTEXT>
ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS  // May read random elements from stack.
ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY   // May read random elements from stack.
static void **NextStackFrame(void **old_sp, const void *uc) {
  void **new_sp = (void **) *old_sp;
  enum { kStackAlignment = 16 };

  // Check that the transition from frame pointer old_sp to frame
  // pointer new_sp isn't clearly bogus
  if (STRICT_UNWINDING) {
    // With the stack growing downwards, older stack frame must be
    // at a greater address that the current one.
    if (new_sp <= old_sp) return nullptr;
    // Assume stack frames larger than 100,000 bytes are bogus.
    if ((uintptr_t)new_sp - (uintptr_t)old_sp > 100000) return nullptr;
  } else {
    // In the non-strict mode, allow discontiguous stack frames.
    // (alternate-signal-stacks for example).
    if (new_sp == old_sp) return nullptr;
    // And allow frames upto about 1MB.
    if ((new_sp > old_sp)
        && ((uintptr_t)new_sp - (uintptr_t)old_sp > 1000000)) return nullptr;
  }
  if ((uintptr_t)new_sp % kStackAlignment != 0) return nullptr;

#if defined(__linux__)
  enum StackTraceKernelSymbolStatus {
      kNotInitialized = 0, kAddressValid, kAddressInvalid };

  if (IS_WITH_CONTEXT && uc != nullptr) {
    static StackTraceKernelSymbolStatus kernel_symbol_status =
        kNotInitialized;  // Sentinel: not computed yet.
    // Initialize with sentinel value: __kernel_rt_sigtramp_rt64 can not
    // possibly be there.
    static const unsigned char *kernel_sigtramp_rt64_address = nullptr;
    if (kernel_symbol_status == kNotInitialized) {
      absl::debugging_internal::VDSOSupport vdso;
      if (vdso.IsPresent()) {
        absl::debugging_internal::VDSOSupport::SymbolInfo
            sigtramp_rt64_symbol_info;
        if (!vdso.LookupSymbol(
                "__kernel_sigtramp_rt64", "LINUX_2.6.15",
                absl::debugging_internal::VDSOSupport::kVDSOSymbolType,
                &sigtramp_rt64_symbol_info) ||
            sigtramp_rt64_symbol_info.address == nullptr) {
          // Unexpected: VDSO is present, yet the expected symbol is missing
          // or null.
          assert(false && "VDSO is present, but doesn't have expected symbol");
          kernel_symbol_status = kAddressInvalid;
        } else {
          kernel_sigtramp_rt64_address =
              reinterpret_cast<const unsigned char *>(
                  sigtramp_rt64_symbol_info.address);
          kernel_symbol_status = kAddressValid;
        }
      } else {
        kernel_symbol_status = kAddressInvalid;
      }
    }

    if (new_sp != nullptr &&
        kernel_symbol_status == kAddressValid &&
        StacktracePowerPCGetLR(new_sp) == kernel_sigtramp_rt64_address) {
      const ucontext_t* signal_context =
          reinterpret_cast<const ucontext_t*>(uc);
      void **const sp_before_signal =
          reinterpret_cast<void**>(signal_context->uc_mcontext.gp_regs[PT_R1]);
      // Check that alleged sp before signal is nonnull and is reasonably
      // aligned.
      if (sp_before_signal != nullptr &&
          ((uintptr_t)sp_before_signal % kStackAlignment) == 0) {
        // Check that alleged stack pointer is actually readable. This is to
        // prevent a "double fault" in case we hit the first fault due to e.g.
        // a stack corruption.
        if (absl::debugging_internal::AddressIsReadable(sp_before_signal)) {
          // Alleged stack pointer is readable, use it for further unwinding.
          new_sp = sp_before_signal;
        }
      }
    }
  }
#endif

  return new_sp;
}

// This ensures that absl::GetStackTrace sets up the Link Register properly.
ABSL_ATTRIBUTE_NOINLINE static void AbslStacktracePowerPCDummyFunction() {
  ABSL_BLOCK_TAIL_CALL_OPTIMIZATION();
}

template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT>
ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS  // May read random elements from stack.
ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY   // May read random elements from stack.
static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count,
                      const void *ucp, int *min_dropped_frames) {
  void **sp;
  // Apple macOS uses an old version of gnu as -- both Darwin 7.9.0 (Panther)
  // and Darwin 8.8.1 (Tiger) use as 1.38.  This means we have to use a
  // different asm syntax.  I don't know quite the best way to discriminate
  // systems using the old as from the new one; I've gone with __APPLE__.
#ifdef __APPLE__
  __asm__ volatile ("mr %0,r1" : "=r" (sp));
#else
  __asm__ volatile ("mr %0,1" : "=r" (sp));
#endif

  // On PowerPC, the "Link Register" or "Link Record" (LR), is a stack
  // entry that holds the return address of the subroutine call (what
  // instruction we run after our function finishes).  This is the
  // same as the stack-pointer of our parent routine, which is what we
  // want here.  While the compiler will always(?) set up LR for
  // subroutine calls, it may not for leaf functions (such as this one).
  // This routine forces the compiler (at least gcc) to push it anyway.
  AbslStacktracePowerPCDummyFunction();

  // The LR save area is used by the callee, so the top entry is bogus.
  skip_count++;

  int n = 0;

  // Unlike ABIs of X86 and ARM, PowerPC ABIs say that return address (in
  // the link register) of a function call is stored in the caller's stack
  // frame instead of the callee's.  When we look for the return address
  // associated with a stack frame, we need to make sure that there is a
  // caller frame before it.  So we call NextStackFrame before entering the
  // loop below and check next_sp instead of sp for loop termination.
  // The outermost frame is set up by runtimes and it does not have a
  // caller frame, so it is skipped.

  // The absl::GetStackFrames routine is called when we are in some
  // informational context (the failure signal handler for example).
  // Use the non-strict unwinding rules to produce a stack trace
  // that is as complete as possible (even if it contains a few
  // bogus entries in some rare cases).
  void **next_sp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(sp, ucp);

  while (next_sp && n < max_depth) {
    if (skip_count > 0) {
      skip_count--;
    } else {
      result[n] = StacktracePowerPCGetLR(sp);
      if (IS_STACK_FRAMES) {
        if (next_sp > sp) {
          sizes[n] = (uintptr_t)next_sp - (uintptr_t)sp;
        } else {
          // A frame-size of 0 is used to indicate unknown frame size.
          sizes[n] = 0;
        }
      }
      n++;
    }

    sp = next_sp;
    next_sp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(sp, ucp);
  }

  if (min_dropped_frames != nullptr) {
    // Implementation detail: we clamp the max of frames we are willing to
    // count, so as not to spend too much time in the loop below.
    const int kMaxUnwind = 1000;
    int j = 0;
    for (; next_sp != nullptr && j < kMaxUnwind; j++) {
      next_sp = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(next_sp, ucp);
    }
    *min_dropped_frames = j;
  }
  return n;
}

namespace absl {
ABSL_NAMESPACE_BEGIN
namespace debugging_internal {
bool StackTraceWorksForTest() {
  return true;
}
}  // namespace debugging_internal
ABSL_NAMESPACE_END
}  // namespace absl

#endif  // ABSL_DEBUGGING_INTERNAL_STACKTRACE_POWERPC_INL_H_