perform ucontext->pc variants testing in compile-time

As part of cpu profiler we're extracting current PC (program counter)
of out signal's ucontext. Different OS and hardware combinations have
different ways for that. We had a list of variants that we tested at
compile time and populated PC_FROM_UCONTEXT macro into config.h. It
caused duplication and occasional mismatches between our autoconf and
cmake bits.

So this commit changes testing to be compile-time. We remove
complexity from build system and add some to C++ source.

We use SFINAE to find which of those variants compile (and we silently
assume that 'compiles' implies 'works'; this is what config-time
testing did too). Occasionally we'll face situations where several
variants compile. And we couldn't handle this case in pure C++. So we
have a small Ruby program that generates chain of inheritance among
SFINAE-specialized class templates. This handles prioritization among
variants.

List of ucontext->pc extraction variants is mostly same. We dropped
super-obsolete (circa Linux kernel 2.0) arm variant. And NetBSD case
is now improved. We now use their nice architecture-independent macro
instead of x86-specific access.
This commit is contained in:
Aliaksey Kandratsenka 2023-11-26 23:43:24 -05:00
parent 8edeea4e83
commit a9b734e3fa
11 changed files with 666 additions and 205 deletions

View File

@ -60,7 +60,6 @@ include(GNUInstallDirs)
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(DefineTargetVariables)
include(PCFromUContext)
define_target_variables()
@ -191,6 +190,9 @@ check_include_file("grp.h" HAVE_GRP_H) # for heapchecker_unittest
check_include_file("pwd.h" HAVE_PWD_H) # for heapchecker_unittest
check_include_file("sys/resource.h" HAVE_SYS_RESOURCE_H) # for memalign_unittest.cc
check_include_file("sys/cdefs.h" HAVE_SYS_CDEFS_H) # Where glibc defines __THROW
check_include_file("ucontext.h" HAVE_UCONTEXT_H)
check_include_file("sys/ucontext.h" HAVE_SYS_UCONTEXT_H)
check_include_file("cygwin/signal.h" HAVE_CYGWIN_SIGNAL_H)
check_include_file("unistd.h" HAVE_UNISTD_H)
# We also need <ucontext.h>/<sys/ucontext.h>, but we get those from
@ -220,16 +222,6 @@ if(NOT WIN32)
set(HAVE_MMAP 1)
endif()
# We want to access the "PC" (Program Counter) register from a struct
# ucontext. Every system has its own way of doing that. We try all the
# possibilities we know about. Note REG_PC should come first (REG_RIP
# is also defined on solaris, but does the wrong thing). But don't
# bother if we're not doing cpu-profiling.
# [*] means that we've not actually tested one of these systems
if (GPERFTOOLS_BUILD_CPU_PROFILER)
pc_from_ucontext(PC_FROM_UCONTEXT_DEF)
endif ()
# Some tests test the behavior of .so files, and only make sense for dynamic.
option(GPERFTOOLS_BUILD_STATIC "Enable Static" ON)

View File

@ -1,67 +0,0 @@
include(CheckCSourceCompiles)
include(CheckIncludeFile)
macro(pc_from_ucontext variable)
set(HAVE_${variable} OFF)
check_include_file("ucontext.h" HAVE_UCONTEXT_H)
if(EXISTS /etc/redhat-release)
set(redhat7_release_pattern "Red Hat Linux release 7")
file(STRINGS /etc/redhat-release redhat_release_match
LIMIT_COUNT 1
REGEX ${redhat7_release_pattern})
if(redhat_release_match MATCHES ${redhat7_release_pattern})
set(HAVE_SYS_UCONTEXT_H OFF)
else()
check_include_file("sys/ucontext.h" HAVE_SYS_UCONTEXT_H)
endif()
else()
check_include_file("sys/ucontext.h" HAVE_SYS_UCONTEXT_H)
endif()
check_include_file("cygwin/signal.h" HAVE_CYGWIN_SIGNAL_H)
set(pc_fields
"uc_mcontext.gregs[REG_PC]" # Solaris x86 (32 + 64 bit)
"uc_mcontext.gregs[REG_EIP]" # Linux (i386)
"uc_mcontext.gregs[REG_RIP]" # Linux (x86_64)
"uc_mcontext.sc_ip" # Linux (ia64)
"uc_mcontext.__pc" # Linux (loongarch64)
"uc_mcontext.pc" # Linux (mips)
"uc_mcontext.uc_regs->gregs[PT_NIP]" # Linux (ppc)
"uc_mcontext.__gregs[REG_PC]" # Linux (riscv64)
"uc_mcontext.psw.addr" # Linux (s390)
"uc_mcontext.gregs[R15]" # Linux (arm old [untested])
"uc_mcontext.arm_pc" # Linux (arm arch 5)
"uc_mcontext.gp_regs[PT_NIP]" # Suse SLES 11 (ppc64)
"uc_mcontext.mc_eip" # FreeBSD (i386)
"uc_mcontext.mc_srr0" # FreeBSD (powerpc, powerpc64)
"uc_mcontext.mc_rip" # FreeBSD (x86_64)
"uc_mcontext.__gregs[_REG_EIP]" # NetBSD (i386)
"uc_mcontext.__gregs[_REG_RIP]" # NetBSD (x86_64)
"uc_mcontext->ss.eip" # OS X (i386, <=10.4)
"uc_mcontext->__ss.__eip" # OS X (i386, >=10.5)
"uc_mcontext->ss.rip" # OS X (x86_64, <=10.4)
"uc_mcontext->__ss.__rip" # OS X (x86_64, >=10.5)
"uc_mcontext->ss.srr0" # OS X (ppc, ppc64, <=10.4 [untested])
"uc_mcontext->__ss.__srr0" # OS X (ppc, ppc64, >=10.5 [untested])
"uc_mcontext->__ss.__pc") # OS X (arm64, >=11 [untested])
set(CMAKE_REQUIRED_DEFINITIONS -D_GNU_SOURCE=1)
if(HAVE_CYGWIN_SIGNAL_H)
set(_inc "cygwin/signal.h")
elseif(HAVE_SYS_UCONTEXT_H)
set(_inc "sys/ucontext.h")
elseif(HAVE_UCONTEXT_H)
set(_inc "ucontext.h")
endif()
foreach(pc_field IN LISTS pc_fields)
string(MAKE_C_IDENTIFIER ${pc_field} pc_field_id)
check_c_source_compiles(
"#include <${_inc}>\nint main() { ucontext_t u; return u.${pc_field} == 0; }"
HAVE_${pc_field_id})
if(HAVE_${pc_field_id})
set(HAVE_${variable} ON)
set(${variable} ${pc_field})
break()
endif()
endforeach()
endmacro()

View File

@ -206,9 +206,6 @@
/* Define to the version of this package. */
#define PACKAGE_VERSION "@PROJECT_VERSION@"
/* How to access the PC from a struct ucontext */
#define PC_FROM_UCONTEXT @PC_FROM_UCONTEXT_DEF@
/* Always the empty-string on non-windows systems. On windows, should be
"__declspec(dllexport)". This way, when we compile the dll, we export our
functions/classes. It's safe to define this here because config.h is only

View File

@ -270,8 +270,11 @@ AC_CHECK_HEADERS(grp.h) # for heapchecker_unittest
AC_CHECK_HEADERS(pwd.h) # for heapchecker_unittest
AC_CHECK_HEADERS(sys/resource.h) # for memalign_unittest.cc
AC_CHECK_HEADERS(sys/cdefs.h) # Where glibc defines __THROW
# We also need <ucontext.h>/<sys/ucontext.h>, but we get those from
# AC_PC_FROM_UCONTEXT, below.
AC_CHECK_HEADERS(sys/ucontext.h)
AC_CHECK_HEADERS(ucontext.h)
AC_CHECK_HEADERS(cygwin/signal.h) # ucontext on cywgin
AC_CHECK_HEADERS(asm/ptrace.h) # get ptrace macros, e.g. PT_NIP
# check for socketpair, some system, such as QNX, need link in an socket library to use it
AC_SEARCH_LIBS([socketpair], [socket])
@ -305,14 +308,14 @@ case "$host" in
esac
# We want to access the "PC" (Program Counter) register from a struct
# ucontext. Every system has its own way of doing that. We try all the
# possibilities we know about. Note REG_PC should come first (REG_RIP
# is also defined on solaris, but does the wrong thing). But don't
# bother if we're not doing cpu-profiling.
# [*] means that we've not actually tested one of these systems
# ucontext. Every system has its own way of doing that. But in case
# we're dealing with unknown system, we have to check if GetPC
# actually works. But don't bother if we're not doing cpu-profiling.
if test "$enable_cpu_profiler" = yes; then
AC_PC_FROM_UCONTEXT(AC_MSG_WARN(Could not find the PC. Will not try to compile libprofiler...);
enable_cpu_profiler=no)
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include "src/getpc.h"]], [GetPC({})])],
[],
[AC_MSG_WARN(Could not find the PC. Will not try to compile libprofiler...)
enable_cpu_profiler=no])
fi
# Some tests test the behavior of .so files, and only make sense for dynamic.

View File

@ -1,100 +0,0 @@
# We want to access the "PC" (Program Counter) register from a struct
# ucontext. Every system has its own way of doing that. We try all the
# possibilities we know about. Note REG_PC should come first (REG_RIP
# is also defined on solaris, but does the wrong thing).
# OpenBSD doesn't have ucontext.h, but we can get PC from ucontext_t
# by using signal.h.
# The first argument of AC_PC_FROM_UCONTEXT will be invoked when we
# cannot find a way to obtain PC from ucontext.
AC_DEFUN([AC_PC_FROM_UCONTEXT],
[AC_CHECK_HEADERS(ucontext.h)
# Redhat 7 has <sys/ucontext.h>, but it barfs if we #include it directly
# (this was fixed in later redhats). <ucontext.h> works fine, so use that.
if grep "Red Hat Linux release 7" /etc/redhat-release >/dev/null 2>&1; then
AC_DEFINE(HAVE_SYS_UCONTEXT_H, 0, [<sys/ucontext.h> is broken on redhat 7])
ac_cv_header_sys_ucontext_h=no
else
AC_CHECK_HEADERS(sys/ucontext.h) # ucontext on OS X 10.6 (at least)
fi
AC_CHECK_HEADERS(cygwin/signal.h) # ucontext on cywgin
AC_CHECK_HEADERS(asm/ptrace.h) # get ptrace macros, e.g. PT_NIP
AC_MSG_CHECKING([how to access the program counter from a struct ucontext])
pc_fields=" uc_mcontext.gregs[[REG_PC]]" # Solaris x86 (32 + 64 bit)
pc_fields="$pc_fields uc_mcontext.gregs[[REG_EIP]]" # Linux (i386)
pc_fields="$pc_fields uc_mcontext.gregs[[REG_RIP]]" # Linux (x86_64)
pc_fields="$pc_fields uc_mcontext.sc_ip" # Linux (ia64)
pc_fields="$pc_fields uc_mcontext.__pc" # Linux (loongarch64)
pc_fields="$pc_fields uc_mcontext.pc" # Linux (mips)
pc_fields="$pc_fields uc_mcontext.uc_regs->gregs[[PT_NIP]]" # Linux (ppc)
pc_fields="$pc_fields uc_mcontext.__gregs[[REG_PC]]" # Linux (riscv64)
pc_fields="$pc_fields uc_mcontext.psw.addr" # Linux (s390)
pc_fields="$pc_fields uc_mcontext.gregs[[R15]]" # Linux (arm old [untested])
pc_fields="$pc_fields uc_mcontext.arm_pc" # Linux (arm arch 5)
pc_fields="$pc_fields uc_mcontext.gp_regs[[PT_NIP]]" # Suse SLES 11 (ppc64)
pc_fields="$pc_fields uc_mcontext.mc_eip" # FreeBSD (i386)
pc_fields="$pc_fields uc_mcontext.mc_srr0" # FreeBSD (powerpc, powerpc64)
pc_fields="$pc_fields uc_mcontext.mc_rip" # FreeBSD (x86_64)
pc_fields="$pc_fields uc_mcontext.__gregs[[_REG_EIP]]" # NetBSD (i386)
pc_fields="$pc_fields uc_mcontext.__gregs[[_REG_RIP]]" # NetBSD (x86_64)
pc_fields="$pc_fields uc_mcontext->ss.eip" # OS X (i386, <=10.4)
pc_fields="$pc_fields uc_mcontext->__ss.__eip" # OS X (i386, >=10.5)
pc_fields="$pc_fields uc_mcontext->ss.rip" # OS X (x86_64)
pc_fields="$pc_fields uc_mcontext->__ss.__rip" # OS X (>=10.5 [untested])
pc_fields="$pc_fields uc_mcontext->ss.srr0" # OS X (ppc, ppc64 [untested])
pc_fields="$pc_fields uc_mcontext->__ss.__srr0" # OS X (>=10.5 [untested])
pc_fields="$pc_fields uc_mcontext->__ss.__pc" # OS X (arm64)
pc_field_found=false
for pc_field in $pc_fields; do
if ! $pc_field_found; then
# Prefer sys/ucontext.h to ucontext.h, for OS X's sake.
if test "x$ac_cv_header_cygwin_signal_h" = xyes; then
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#define _GNU_SOURCE 1
#include <cygwin/signal.h>]], [[ucontext_t u; return u.$pc_field == 0;]])],[AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT($pc_field)
pc_field_found=true],[])
elif test "x$ac_cv_header_asm_ptrace_h" = xyes -a "x$ac_cv_header_sys_ucontext_h" = xyes; then
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#define _GNU_SOURCE 1
#include <asm/ptrace.h>
#include <sys/ucontext.h>]], [[ucontext_t u; return u.$pc_field == 0;]])],[AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT($pc_field)
pc_field_found=true],[])
elif test "x$ac_cv_header_sys_ucontext_h" = xyes; then
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#define _GNU_SOURCE 1
#include <sys/ucontext.h>]], [[ucontext_t u; return u.$pc_field == 0;]])],[AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT($pc_field)
pc_field_found=true],[])
elif test "x$ac_cv_header_ucontext_h" = xyes; then
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#define _GNU_SOURCE 1
#include <ucontext.h>]], [[ucontext_t u; return u.$pc_field == 0;]])],[AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT($pc_field)
pc_field_found=true],[])
else # hope some standard header gives it to us
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[]], [[ucontext_t u; return u.$pc_field == 0;]])],[AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT($pc_field)
pc_field_found=true],[])
fi
fi
done
if ! $pc_field_found; then
pc_fields=" sc_eip" # OpenBSD (i386)
pc_fields="$pc_fields sc_rip" # OpenBSD (x86_64)
for pc_field in $pc_fields; do
if ! $pc_field_found; then
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[#include <signal.h>]], [[ucontext_t u; return u.$pc_field == 0;]])],[AC_DEFINE_UNQUOTED(PC_FROM_UCONTEXT, $pc_field,
How to access the PC from a struct ucontext)
AC_MSG_RESULT($pc_field)
pc_field_found=true],[])
fi
done
fi
if ! $pc_field_found; then
[$1]
fi])

197
src/gen_getpc.rb Executable file
View File

@ -0,0 +1,197 @@
#!/usr/bin/env ruby
# Copyright (c) 2023, gperftools Contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following disclaimer
# in the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Google Inc. nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
require 'digest'
# This is main logic. If you want to add new ucontext-to-pc accessor, add it here
def dwm!(d)
d.template "NetBSD has really nice portable macros", :_UC_MACHINE_PC do |uc|
"_UC_MACHINE_PC(#{uc})"
end
d.with_prefix "uc_mcontext." do
# first arg is ifdef, second is field (with prefix prepended) and third is comment
d.ifdef :REG_PC, "gregs[REG_PC]", "Solaris/x86"
d.ifdef :REG_EIP, "gregs[REG_EIP]", "Linux/i386"
d.ifdef :REG_RIP, "gregs[REG_RIP]", "Linux/amd64"
d.field "sc_ip", "Linux/ia64"
d.field "__pc", "Linux/loongarch64"
d.field "pc", "Linux/{mips,aarch64}"
d.ifdef :PT_NIP, "uc_regs->gregs[PT_NIP]", "Linux/ppc"
d.ifdef :PT_NIP, "gp_regs[PT_NIP]", "Linux/ppc"
d.ifdef :REG_PC, "__gregs[REG_PC]", "Linux/riscv"
d.field "psw.addr", "Linux/s390"
d.field "arm_pc", "Linux/arm (32-bit; legacy)"
d.field "mc_eip", "FreeBSD/i386"
d.field "mc_srr0", "FreeBSD/ppc"
d.field "mc_rip", "FreeBSD/x86_64"
end
d.with_prefix "uc_mcontext->" do
d.field "ss.eip", "OS X (i386, <=10.4)"
d.field "__ss.__eip", "OS X (i386, >=10.5)"
d.field "ss.rip", "OS X (x86_64)"
d.field "__ss.__rip", "OS X (>=10.5 [untested])"
d.field "ss.srr0", "OS X (ppc, ppc64 [untested])"
d.field "__ss.__srr0", "OS X (>=10.5 [untested])"
d.field "__ss.__pc", "OS X (arm64)"
end
d.field "sc_eip", "OpenBSD/i386"
d.field "sc_rip", "OpenBSD/x86_64"
end
# this is generator logic
class Definer
def initialize
@prefix = ""
@accessors = {}
puts(<<HERE)
// -*- eval: (read-only-mode) -*-
// WARNING: this file is autogenerated.
// Change and run src/gen_getpc.rb if you want to
// update. (And submit both files)
// What this file does? We have several possible ways of fetching PC
// (program counter) of signal's ucontext. We explicitly choose to
// avoid ifdef-ing specific OSes (or even specific versions), to
// increase our chances that stuff simply works. Comments below refer
// to OS/architecture combos for documentation purposes, but what
// works is what is used.
// How it does it? It uses lightweight C++ template magic where
// "wrong" ucontext_t{nullptr}-><field access> combos are
// automagically filtered out (via SFINAE).
// Each known case is represented as a template class. For SFINAE
// reasons we masquerade ucontext_t type behind U template
// parameter. And we also parameterize by parent class. This allows us
// to arrange all template instantiations in a single ordered chain of
// inheritance. See RawUCToPC below.
// Note, we do anticipate that most times exactly one of those access
// methods works. But we're prepared there could be several. In
// particular, according to previous comments Solaris/x86 also has
// REG_RIP defined, but it is somehow wrong. So we're careful about
// preserving specific order. We couldn't handle this "multiplicity"
// aspect in pure C++, so we use code generation.
namespace internal {
struct Empty {
#ifdef DEFINE_TRIVIAL_GET
#define HAVE_TRIVIAL_GET
// special thing for stacktrace_generic_fp-inl which wants no-op case
static void* Get(...) {
return nullptr;
}
#endif
};
HERE
end
def with_prefix(prefix)
old_prefix = @prefix
@prefix = @prefix.dup + prefix
yield
ensure
@prefix = old_prefix
end
def ifdef define, field, comment
field field, comment, define
end
def template comment, define = nil, &block
field block, comment, define
end
def field field, comment, define = nil
tmpl = if field.kind_of? Proc
raise unless @prefix.empty?
field
else
proc do |uc|
"#{uc}->#{@prefix + field}"
end
end
fingerprint = Digest::MD5.hexdigest(tmpl["%"] + "@" + comment)[0,8]
maybe_open_ifdef = "\n#ifdef #{define}" if define
maybe_close_ifdef = "\n#endif // #{define}" if define
raise "conflict!" if @accessors.include? fingerprint
if define
@accessors[fingerprint] = comment + " (with #ifdef #{define})"
else
@accessors[fingerprint] = comment
end
puts(<<HERE)
// #{comment}
template <class U, class P, class = void>
struct get_#{fingerprint} : public P {
};#{maybe_open_ifdef}
template <class U, class P>
struct get_#{fingerprint}<U, P, void_t<decltype(#{tmpl["((U*){})"]})>> : public P {
static void* Get(const U* uc) {
// #{comment}
return (void*)(#{tmpl[:uc]});
}
};#{maybe_close_ifdef}
HERE
end
def finalize!
puts
puts(<<HERE)
inline void* RawUCToPC(const ucontext_t* uc) {
HERE
prev = "Empty"
@accessors.each_pair.reverse_each do |(fingerprint, comment)|
puts " // #{comment}"
puts " using g_#{fingerprint} = get_#{fingerprint}<ucontext_t, #{prev}>;"
prev = "g_#{fingerprint}"
end
puts(<<HERE)
return #{prev}::Get(uc);
}
HERE
puts
puts("} // namespace internal")
end
end
path = File.join(File.dirname(__FILE__), "getpc-inl.h")
STDOUT.reopen(IO.popen(["tee", path], "w"))
Definer.new.tap {|instance| dwm!(instance)}.finalize!

396
src/getpc-inl.h Normal file
View File

@ -0,0 +1,396 @@
// -*- eval: (read-only-mode) -*-
// WARNING: this file is autogenerated.
// Change and run gen_getpc.rb >getpc-inl.h if you want to
// update. (And submit both files)
// What this file does? We have several possible ways of fetching PC
// (program counter) of signal's ucontext. We explicitly choose to
// avoid ifdef-ing specific OSes (or even specific versions), to
// increase our chances that stuff simply works. Comments below refer
// to OS/architecture combos for documentation purposes, but what
// works is what is used.
// How it does it? It uses lightweight C++ template magic where
// "wrong" ucontext_t{nullptr}-><field access> combos are
// automagically filtered out (via SFINAE).
// Each known case is represented as a template class. For SFINAE
// reasons we masquerade ucontext_t type behind U template
// parameter. And we also parameterize by parent class. This allows us
// to arrange all template instantiations in a single ordered chain of
// inheritance. See RawUCToPC below.
// Note, we do anticipate that most times exactly one of those access
// methods works. But we're prepared there could be several. In
// particular, according to previous comments Solaris/x86 also has
// REG_RIP defined, but it is somehow wrong. So we're careful about
// preserving specific order. We couldn't handle this "multiplicity"
// aspect in pure C++, so we use code generation.
namespace internal {
struct Empty {
#ifdef DEFINE_TRIVIAL_GET
#define HAVE_TRIVIAL_GET
// special thing for stacktrace_generic_fp-inl which wants no-op case
static void* Get(...) {
return nullptr;
}
#endif
};
// NetBSD has really nice portable macros
template <class U, class P, class = void>
struct get_c47a30af : public P {
};
#ifdef _UC_MACHINE_PC
template <class U, class P>
struct get_c47a30af<U, P, void_t<decltype(_UC_MACHINE_PC(((U*){})))>> : public P {
static void* Get(const U* uc) {
// NetBSD has really nice portable macros
return (void*)(_UC_MACHINE_PC(uc));
}
};
#endif // _UC_MACHINE_PC
// Solaris/x86
template <class U, class P, class = void>
struct get_c4719e8d : public P {
};
#ifdef REG_PC
template <class U, class P>
struct get_c4719e8d<U, P, void_t<decltype(((U*){})->uc_mcontext.gregs[REG_PC])>> : public P {
static void* Get(const U* uc) {
// Solaris/x86
return (void*)(uc->uc_mcontext.gregs[REG_PC]);
}
};
#endif // REG_PC
// Linux/i386
template <class U, class P, class = void>
struct get_278cba85 : public P {
};
#ifdef REG_EIP
template <class U, class P>
struct get_278cba85<U, P, void_t<decltype(((U*){})->uc_mcontext.gregs[REG_EIP])>> : public P {
static void* Get(const U* uc) {
// Linux/i386
return (void*)(uc->uc_mcontext.gregs[REG_EIP]);
}
};
#endif // REG_EIP
// Linux/amd64
template <class U, class P, class = void>
struct get_b49f2593 : public P {
};
#ifdef REG_RIP
template <class U, class P>
struct get_b49f2593<U, P, void_t<decltype(((U*){})->uc_mcontext.gregs[REG_RIP])>> : public P {
static void* Get(const U* uc) {
// Linux/amd64
return (void*)(uc->uc_mcontext.gregs[REG_RIP]);
}
};
#endif // REG_RIP
// Linux/ia64
template <class U, class P, class = void>
struct get_8fda99d3 : public P {
};
template <class U, class P>
struct get_8fda99d3<U, P, void_t<decltype(((U*){})->uc_mcontext.sc_ip)>> : public P {
static void* Get(const U* uc) {
// Linux/ia64
return (void*)(uc->uc_mcontext.sc_ip);
}
};
// Linux/loongarch64
template <class U, class P, class = void>
struct get_4e9b682d : public P {
};
template <class U, class P>
struct get_4e9b682d<U, P, void_t<decltype(((U*){})->uc_mcontext.__pc)>> : public P {
static void* Get(const U* uc) {
// Linux/loongarch64
return (void*)(uc->uc_mcontext.__pc);
}
};
// Linux/{mips,aarch64}
template <class U, class P, class = void>
struct get_b94b7246 : public P {
};
template <class U, class P>
struct get_b94b7246<U, P, void_t<decltype(((U*){})->uc_mcontext.pc)>> : public P {
static void* Get(const U* uc) {
// Linux/{mips,aarch64}
return (void*)(uc->uc_mcontext.pc);
}
};
// Linux/ppc
template <class U, class P, class = void>
struct get_d0eeceae : public P {
};
#ifdef PT_NIP
template <class U, class P>
struct get_d0eeceae<U, P, void_t<decltype(((U*){})->uc_mcontext.uc_regs->gregs[PT_NIP])>> : public P {
static void* Get(const U* uc) {
// Linux/ppc
return (void*)(uc->uc_mcontext.uc_regs->gregs[PT_NIP]);
}
};
#endif // PT_NIP
// Linux/ppc
template <class U, class P, class = void>
struct get_a81f6801 : public P {
};
#ifdef PT_NIP
template <class U, class P>
struct get_a81f6801<U, P, void_t<decltype(((U*){})->uc_mcontext.gp_regs[PT_NIP])>> : public P {
static void* Get(const U* uc) {
// Linux/ppc
return (void*)(uc->uc_mcontext.gp_regs[PT_NIP]);
}
};
#endif // PT_NIP
// Linux/riscv
template <class U, class P, class = void>
struct get_24e794ef : public P {
};
#ifdef REG_PC
template <class U, class P>
struct get_24e794ef<U, P, void_t<decltype(((U*){})->uc_mcontext.__gregs[REG_PC])>> : public P {
static void* Get(const U* uc) {
// Linux/riscv
return (void*)(uc->uc_mcontext.__gregs[REG_PC]);
}
};
#endif // REG_PC
// Linux/s390
template <class U, class P, class = void>
struct get_d9a75ed3 : public P {
};
template <class U, class P>
struct get_d9a75ed3<U, P, void_t<decltype(((U*){})->uc_mcontext.psw.addr)>> : public P {
static void* Get(const U* uc) {
// Linux/s390
return (void*)(uc->uc_mcontext.psw.addr);
}
};
// Linux/arm (32-bit; legacy)
template <class U, class P, class = void>
struct get_07114491 : public P {
};
template <class U, class P>
struct get_07114491<U, P, void_t<decltype(((U*){})->uc_mcontext.arm_pc)>> : public P {
static void* Get(const U* uc) {
// Linux/arm (32-bit; legacy)
return (void*)(uc->uc_mcontext.arm_pc);
}
};
// FreeBSD/i386
template <class U, class P, class = void>
struct get_9be162e6 : public P {
};
template <class U, class P>
struct get_9be162e6<U, P, void_t<decltype(((U*){})->uc_mcontext.mc_eip)>> : public P {
static void* Get(const U* uc) {
// FreeBSD/i386
return (void*)(uc->uc_mcontext.mc_eip);
}
};
// FreeBSD/ppc
template <class U, class P, class = void>
struct get_2812b129 : public P {
};
template <class U, class P>
struct get_2812b129<U, P, void_t<decltype(((U*){})->uc_mcontext.mc_srr0)>> : public P {
static void* Get(const U* uc) {
// FreeBSD/ppc
return (void*)(uc->uc_mcontext.mc_srr0);
}
};
// FreeBSD/x86_64
template <class U, class P, class = void>
struct get_5bb1da03 : public P {
};
template <class U, class P>
struct get_5bb1da03<U, P, void_t<decltype(((U*){})->uc_mcontext.mc_rip)>> : public P {
static void* Get(const U* uc) {
// FreeBSD/x86_64
return (void*)(uc->uc_mcontext.mc_rip);
}
};
// OS X (i386, <=10.4)
template <class U, class P, class = void>
struct get_880f83fe : public P {
};
template <class U, class P>
struct get_880f83fe<U, P, void_t<decltype(((U*){})->uc_mcontext->ss.eip)>> : public P {
static void* Get(const U* uc) {
// OS X (i386, <=10.4)
return (void*)(uc->uc_mcontext->ss.eip);
}
};
// OS X (i386, >=10.5)
template <class U, class P, class = void>
struct get_92fcd89a : public P {
};
template <class U, class P>
struct get_92fcd89a<U, P, void_t<decltype(((U*){})->uc_mcontext->__ss.__eip)>> : public P {
static void* Get(const U* uc) {
// OS X (i386, >=10.5)
return (void*)(uc->uc_mcontext->__ss.__eip);
}
};
// OS X (x86_64)
template <class U, class P, class = void>
struct get_773e27c8 : public P {
};
template <class U, class P>
struct get_773e27c8<U, P, void_t<decltype(((U*){})->uc_mcontext->ss.rip)>> : public P {
static void* Get(const U* uc) {
// OS X (x86_64)
return (void*)(uc->uc_mcontext->ss.rip);
}
};
// OS X (>=10.5 [untested])
template <class U, class P, class = void>
struct get_6627078a : public P {
};
template <class U, class P>
struct get_6627078a<U, P, void_t<decltype(((U*){})->uc_mcontext->__ss.__rip)>> : public P {
static void* Get(const U* uc) {
// OS X (>=10.5 [untested])
return (void*)(uc->uc_mcontext->__ss.__rip);
}
};
// OS X (ppc, ppc64 [untested])
template <class U, class P, class = void>
struct get_da992aca : public P {
};
template <class U, class P>
struct get_da992aca<U, P, void_t<decltype(((U*){})->uc_mcontext->ss.srr0)>> : public P {
static void* Get(const U* uc) {
// OS X (ppc, ppc64 [untested])
return (void*)(uc->uc_mcontext->ss.srr0);
}
};
// OS X (>=10.5 [untested])
template <class U, class P, class = void>
struct get_cce47a40 : public P {
};
template <class U, class P>
struct get_cce47a40<U, P, void_t<decltype(((U*){})->uc_mcontext->__ss.__srr0)>> : public P {
static void* Get(const U* uc) {
// OS X (>=10.5 [untested])
return (void*)(uc->uc_mcontext->__ss.__srr0);
}
};
// OS X (arm64)
template <class U, class P, class = void>
struct get_0a082e42 : public P {
};
template <class U, class P>
struct get_0a082e42<U, P, void_t<decltype(((U*){})->uc_mcontext->__ss.__pc)>> : public P {
static void* Get(const U* uc) {
// OS X (arm64)
return (void*)(uc->uc_mcontext->__ss.__pc);
}
};
// OpenBSD/i386
template <class U, class P, class = void>
struct get_3baa113a : public P {
};
template <class U, class P>
struct get_3baa113a<U, P, void_t<decltype(((U*){})->sc_eip)>> : public P {
static void* Get(const U* uc) {
// OpenBSD/i386
return (void*)(uc->sc_eip);
}
};
// OpenBSD/x86_64
template <class U, class P, class = void>
struct get_79f33851 : public P {
};
template <class U, class P>
struct get_79f33851<U, P, void_t<decltype(((U*){})->sc_rip)>> : public P {
static void* Get(const U* uc) {
// OpenBSD/x86_64
return (void*)(uc->sc_rip);
}
};
inline void* RawUCToPC(const ucontext_t* uc) {
// OpenBSD/x86_64
using g_79f33851 = get_79f33851<ucontext_t, Empty>;
// OpenBSD/i386
using g_3baa113a = get_3baa113a<ucontext_t, g_79f33851>;
// OS X (arm64)
using g_0a082e42 = get_0a082e42<ucontext_t, g_3baa113a>;
// OS X (>=10.5 [untested])
using g_cce47a40 = get_cce47a40<ucontext_t, g_0a082e42>;
// OS X (ppc, ppc64 [untested])
using g_da992aca = get_da992aca<ucontext_t, g_cce47a40>;
// OS X (>=10.5 [untested])
using g_6627078a = get_6627078a<ucontext_t, g_da992aca>;
// OS X (x86_64)
using g_773e27c8 = get_773e27c8<ucontext_t, g_6627078a>;
// OS X (i386, >=10.5)
using g_92fcd89a = get_92fcd89a<ucontext_t, g_773e27c8>;
// OS X (i386, <=10.4)
using g_880f83fe = get_880f83fe<ucontext_t, g_92fcd89a>;
// FreeBSD/x86_64
using g_5bb1da03 = get_5bb1da03<ucontext_t, g_880f83fe>;
// FreeBSD/ppc
using g_2812b129 = get_2812b129<ucontext_t, g_5bb1da03>;
// FreeBSD/i386
using g_9be162e6 = get_9be162e6<ucontext_t, g_2812b129>;
// Linux/arm (32-bit; legacy)
using g_07114491 = get_07114491<ucontext_t, g_9be162e6>;
// Linux/s390
using g_d9a75ed3 = get_d9a75ed3<ucontext_t, g_07114491>;
// Linux/riscv (with #ifdef REG_PC)
using g_24e794ef = get_24e794ef<ucontext_t, g_d9a75ed3>;
// Linux/ppc (with #ifdef PT_NIP)
using g_a81f6801 = get_a81f6801<ucontext_t, g_24e794ef>;
// Linux/ppc (with #ifdef PT_NIP)
using g_d0eeceae = get_d0eeceae<ucontext_t, g_a81f6801>;
// Linux/{mips,aarch64}
using g_b94b7246 = get_b94b7246<ucontext_t, g_d0eeceae>;
// Linux/loongarch64
using g_4e9b682d = get_4e9b682d<ucontext_t, g_b94b7246>;
// Linux/ia64
using g_8fda99d3 = get_8fda99d3<ucontext_t, g_4e9b682d>;
// Linux/amd64 (with #ifdef REG_RIP)
using g_b49f2593 = get_b49f2593<ucontext_t, g_8fda99d3>;
// Linux/i386 (with #ifdef REG_EIP)
using g_278cba85 = get_278cba85<ucontext_t, g_b49f2593>;
// Solaris/x86 (with #ifdef REG_PC)
using g_c4719e8d = get_c4719e8d<ucontext_t, g_278cba85>;
// NetBSD has really nice portable macros (with #ifdef _UC_MACHINE_PC)
using g_c47a30af = get_c47a30af<ucontext_t, g_c4719e8d>;
return g_c47a30af::Get(uc);
}
} // namespace internal

View File

@ -44,7 +44,12 @@
#ifndef BASE_GETPC_H_
#define BASE_GETPC_H_
#include "config.h"
// Note: we include this from one of configure script C++ tests as
// part of verifying that we're able to build CPU profiler. I.e. we
// cannot include config.h as we normally do, since it isn't produced
// yet, but those HAVE_XYZ defines are available, so including
// ucontext etc stuff works. It's usage from profiler.cc (and
// stacktrace_generic_fp-inl.h) is after config.h is included.
// On many linux systems, we may need _GNU_SOURCE to get access to
// the defined constants that define the register we want to see (eg
@ -63,17 +68,32 @@
typedef ucontext ucontext_t;
#endif
// If this doesn't compile, it's probably because
// PC_FROM_UCONTEXT is the empty string. You need to figure out
// the right value for your system, and add it to the list in
// configure.ac (or set it manually in your config.h).
namespace tcmalloc {
namespace getpc {
// std::void_t is C++ 14. So we steal this from
// https://en.cppreference.com/w/cpp/types/void_t
template<typename... Ts>
struct make_void { typedef void type; };
template <typename... Ts>
using void_t = typename make_void<Ts...>::type;
#include "getpc-inl.h"
} // namespace getpc
} // namespace tcmalloc
// If this doesn't compile, you need to figure out the right value for
// your system, and add it to the list above.
inline void* GetPC(const ucontext_t& signal_ucontext) {
void* retval = tcmalloc::getpc::internal::RawUCToPC(&signal_ucontext);
#if defined(__s390__) && !defined(__s390x__)
// Mask out the AMODE31 bit from the PC recorded in the context.
return (void*)((unsigned long)signal_ucontext.PC_FROM_UCONTEXT & 0x7fffffffUL);
#else
return (void*)signal_ucontext.PC_FROM_UCONTEXT; // defined in config.h
retval = (void*)((unsigned long)retval & 0x7fffffffUL);
#endif
return retval;
}
#endif // BASE_GETPC_H_

View File

@ -44,8 +44,15 @@
// This is only used on OS-es with mmap support.
#include <sys/mman.h>
#if defined(PC_FROM_UCONTEXT) && (HAVE_SYS_UCONTEXT_H || HAVE_UCONTEXT_H)
#if HAVE_SYS_UCONTEXT_H || HAVE_UCONTEXT_H
#define DEFINE_TRIVIAL_GET
#include "getpc.h"
#if !defined(HAVE_TRIVIAL_GET) && !defined(__NetBSD__)
#error sanity
#endif
#define HAVE_GETPC 1
#endif
@ -164,9 +171,9 @@ int capture(void **result, int max_depth, int skip_count,
#ifdef __arm__
// note, (32-bit, legacy) arm support is not entirely functional
// w.r.t. frame-pointer-bases backtracing. Only recent clangs
// w.r.t. frame-pointer-based backtracing. Only recent clangs
// generate "right" frame pointer setup and only with
// --enable-frame-pointers. Current gcc's are hopeless (somewhat
// --enable-frame-pointers. Current gcc-s are hopeless (somewhat
// older gcc's (circa gcc 6 or so) did something that looks right,
// but not recent ones).
constexpr uintptr_t kAlignment = 4;
@ -309,6 +316,9 @@ static int GET_STACK_TRACE_OR_FRAMES {
SETUP_FRAME(&uc->uc_mcontext.__gregs[REG_PC], uc->uc_mcontext.__gregs[REG_S0]);
#elif __linux__ && __aarch64__
SETUP_FRAME(&uc->uc_mcontext.pc, uc->uc_mcontext.regs[29]);
#elif __linux__ && __arm__
// Note: arm's frame pointer support is borked in recent GCC-s.
SETUP_FRAME(&uc->uc_mcontext.arm_pc, uc->uc_mcontext.arm_fp);
#elif __linux__ && __i386__
SETUP_FRAME(&uc->uc_mcontext.gregs[REG_EIP], uc->uc_mcontext.gregs[REG_EBP]);
#elif __linux__ && __x86_64__
@ -335,7 +345,12 @@ static int GET_STACK_TRACE_OR_FRAMES {
// frame we need. Also, this is how our CPU profiler is built. It
// always places "pc from ucontext" first and then if necessary
// deduplicates it from backtrace.
result[0] = GetPC(*uc);
if (result[0] == nullptr) {
// This OS/HW combo actually lacks known way to extract PC.
ucp = nullptr;
}
#else
ucp = nullptr;
#endif

View File

@ -37,6 +37,9 @@
#include "config.h"
#include "getpc.h" // should be first to get the _GNU_SOURCE dfn
#include "base/basictypes.h"
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
@ -46,9 +49,17 @@
static volatile void* getpc_retval = NULL; // what GetPC returns
static volatile bool prof_handler_called = false;
extern "C" {
// This helps us inspect codegen of GetPC function, just in case.
ATTRIBUTE_NOINLINE
void* DoGetPC(const ucontext_t* uc) {
return GetPC(*uc);
}
}
static void prof_handler(int sig, siginfo_t*, void* signal_ucontext) {
if (!prof_handler_called)
getpc_retval = GetPC(*reinterpret_cast<ucontext_t*>(signal_ucontext));
getpc_retval = DoGetPC(reinterpret_cast<ucontext_t*>(signal_ucontext));
prof_handler_called = true; // only store the retval once
}

View File

@ -223,9 +223,6 @@
/* Define to the version of this package. */
#define PACKAGE_VERSION "2.13"
/* How to access the PC from a struct ucontext */
/* #undef PC_FROM_UCONTEXT */
/* Always the empty-string on non-windows systems. On windows, should be
"__declspec(dllexport)". This way, when we compile the dll, we export our
functions/classes. It's safe to define this here because config.h is only