Stack trace (#4691)

* chore(Trace): add dev env settings

* add(Trace): init add Poco::trace and libbacktrace files

* feat(Exception): generate stack trace if enabled at compile time

* chore(DNSSD): remove binaries from git

* fix(Trace): build

* chore(ci): exclude exception text tests for trace build; add debug test script params

* chore(build): mac (dl)

* chore(cmake): Changes to build Trace with CMake.

* chore(cmake): Changes to build Trace on Windows

* chore(cmake): Improvements to include trace sample.

* chore(cmake): Fixes to properly build/link Trace on Linux

* chore(cmake): add_definitions --> add_compile_definitions

* chore(cmake): Build Trace as static and don't export it.

* chore(make): Link Trace with built-in libbacktrace on Linux

* chore(Trace): remove unnecessary sources for libbacktrace.

* chore(github): enable trace on a few github checks

* chore(cmake): Build Trace with clang++ on Linux.

* chore(cmake): Properly set POCO_ENABLE_TRACE globally when needed.

* fix(cmake): Trace: corrected include for clang on Linux

---------

Co-authored-by: Matej Kenda <matejken@gmail.com>
This commit is contained in:
Aleksandar Fabijanic 2024-10-10 03:36:13 -05:00 committed by GitHub
parent a1efeaa72d
commit eaabd3ff8d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
96 changed files with 10433 additions and 21 deletions

View File

@ -67,6 +67,22 @@ jobs:
EXCLUDE_TESTS="Data/ODBC Data/MySQL Data/PostgreSQL MongoDB"
./ci/runtests.sh
linux-gcc-make-trace:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- run: sudo apt -y update && sudo apt -y install libssl-dev unixodbc-dev redis-server libmysqlclient-dev
- run: ./configure --everything --trace && make all -s -j4 && sudo make install
- uses: ./.github/actions/retry-action
with:
timeout_minutes: 90
max_attempts: 3
retry_on: any
command: >-
sudo -s
EXCLUDE_TESTS="Data/ODBC Data/MySQL Data/PostgreSQL MongoDB"
./ci/runtests.sh "" d
linux-gcc-make-cxx20:
runs-on: ubuntu-24.04
steps:
@ -304,7 +320,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: brew install openssl@1.1 mysql-client unixodbc libpq
- run: cmake -S. -Bcmake-build -DENABLE_PDF=OFF -DENABLE_TESTS=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 -DMYSQL_ROOT_DIR=/usr/local/opt/mysql-client && cmake --build cmake-build --target all
- run: cmake -S. -Bcmake-build -DENABLE_PDF=OFF -DENABLE_TESTS=ON -DENABLE_TRACE=ON -DOPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1 -DMYSQL_ROOT_DIR=/usr/local/opt/mysql-client && cmake --build cmake-build --target all
- uses: ./.github/actions/retry-action
with:
timeout_minutes: 90

View File

@ -24,7 +24,8 @@
"${POCO_BASE}/MongoDB/include",
"${POCO_BASE}/ApacheConnector/include",
"${POCO_BASE}/Data/src",
"${POCO_BASE}/Data/DataTest/include"
"${POCO_BASE}/Data/DataTest/include",
"${POCO_BASE}/Trace/include"
]
},
"configurations": [

View File

@ -190,6 +190,8 @@ option(ENABLE_PAGECOMPILER_FILE2PAGE "Enable File2Page" ON)
option(ENABLE_ACTIVERECORD "Enable ActiveRecord" ON)
option(ENABLE_ACTIVERECORD_COMPILER "Enable ActiveRecord Compiler" ON)
option(ENABLE_TRACE "Enable stack tracing" OFF)
if(ENABLE_ACTIVERECORD AND NOT ENABLE_DATA)
set(ENABLE_DATA ON CACHE BOOL "Enable Data" FORCE)
endif()
@ -240,9 +242,13 @@ endif()
option(POCO_ENABLE_STD_MUTEX "Set to OFF|NO using mutex from standard library (default OFF)" OFF)
if (POCO_ENABLE_STD_MUTEX)
add_definitions(-DPOCO_ENABLE_STD_MUTEX)
add_compile_definitions(POCO_ENABLE_STD_MUTEX)
endif ()
if(ENABLE_TRACE)
add_compile_definitions(POCO_ENABLE_TRACE)
endif()
include(DefinePlatformSpecific)
# Collect the built libraries and include dirs, the will be used to create the PocoConfig.cmake file
@ -411,7 +417,7 @@ option(POCO_DATA_NO_SQL_PARSER "Disable SQL parser" OFF)
if(EXISTS ${PROJECT_SOURCE_DIR}/Data AND ENABLE_DATA)
if(POCO_DATA_NO_SQL_PARSER)
add_definitions(-DPOCO_DATA_NO_SQL_PARSER=1)
add_compile_definitions(POCO_DATA_NO_SQL_PARSER=1)
endif()
add_subdirectory(Data)
list(APPEND Poco_COMPONENTS "Data")
@ -480,6 +486,11 @@ if(EXISTS ${PROJECT_SOURCE_DIR}/Encodings/Compiler AND ENABLE_ENCODINGS_COMPILER
list(APPEND Poco_COMPONENTS "EncodingsCompiler")
endif()
if(ENABLE_TRACE)
add_subdirectory(Trace)
list(APPEND Poco_COMPONENTS "Trace")
endif()
#############################################################
# Uninstall stuff see: http://www.vtk.org/Wiki/CMake_FAQ

View File

@ -9,7 +9,7 @@ POCO_SOURCES_AUTO( Bonjour_SRCS ${SRCS_G})
file(GLOB_RECURSE HDRS_G "include/*.h" )
POCO_HEADERS_AUTO( Bonjour_SRCS ${HDRS_G})
add_definitions( ${Bonjour_CFLAGS} -DTHREADSAFE)
add_compile_definitions( ${Bonjour_CFLAGS} THREADSAFE)
add_library( "${LIBNAME}" ${LIB_MODE} ${Bonjour_SRCS} )
add_library( "${POCO_LIBNAME}" ALIAS "${LIBNAME}")

View File

@ -40,7 +40,7 @@ file(GLOB_RECURSE HDRS_G "include/*.h" )
POCO_HEADERS_AUTO( SRCS ${HDRS_G})
if (NOT POCO_STATIC)
add_definitions(-DTHREADSAFE)
add_compile_definitions(THREADSAFE)
endif (NOT POCO_STATIC)
add_library( "${LIBNAME}" ${LIB_MODE} ${SRCS} )

View File

@ -3362,8 +3362,9 @@ void SQLiteTest::testTransaction()
status = trans.execute(sql, &info);
assertFalse (status);
#ifndef POCO_ENABLE_TRACE
assertEqual (info, "Invalid SQL statement: no such table: Pers: no such table: Pers");
#endif
session << "SELECT count(*) FROM Person", into(count), now;
assertTrue (0 == count);

View File

@ -170,6 +170,10 @@ else()
endif(UNIX AND NOT ANDROID)
endif(CYGWIN)
if(ENABLE_TRACE)
target_link_libraries(Foundation PRIVATE Poco::Trace)
endif()
if(CMAKE_SYSTEM MATCHES "SunOS")
target_compile_definitions(Foundation
PUBLIC

View File

@ -6,6 +6,10 @@
include $(POCO_BASE)/build/rules/global
ifdef POCO_ENABLE_TRACE
INCLUDE += -I $(POCO_BASE)/Trace/include/Poco/Trace
endif
objects = ArchiveStrategy Ascii ASCIIEncoding AsyncChannel AsyncNotificationCenter ActiveThreadPool\
Base32Decoder Base32Encoder Base64Decoder Base64Encoder \
BinaryReader BinaryWriter Bugcheck ByteOrder Channel Checksum Clock Configurable ConsoleChannel \
@ -63,6 +67,9 @@ endif
target = PocoFoundation
target_version = $(LIBVERSION)
target_libs =
ifdef POCO_ENABLE_TRACE
target_libs += PocoTrace
endif
ifeq ($(findstring MinGW, $(POCO_CONFIG)), MinGW)
$(shell cd src; $(WINDMC) pocomsg.mc)

View File

@ -197,4 +197,7 @@
// Uncomment to explicitly disable SQLParser
// #define POCO_DATA_NO_SQL_PARSER
// Uncomment to enable stack trace autogeneration in Exception
//#define POCO_ENABLE_TRACE 1
#endif // Foundation_Config_INCLUDED

View File

@ -17,3 +17,4 @@ add_subdirectory(hmacmd5)
add_subdirectory(inflate)
add_subdirectory(md5)
add_subdirectory(uuidgen)
add_subdirectory(trace)

View File

@ -26,3 +26,4 @@ projects:
$(MAKE) -C StringTokenizer $(MAKECMDGOALS)
$(MAKE) -C URI $(MAKECMDGOALS)
$(MAKE) -C uuidgen $(MAKECMDGOALS)
$(MAKE) -C trace $(MAKECMDGOALS)

View File

@ -0,0 +1,2 @@
add_executable(sample-trace src/trace.cpp)
target_link_libraries(sample-trace PUBLIC Poco::Foundation)

View File

@ -0,0 +1,25 @@
#
# Makefile
#
# Makefile for Poco Trace sample
#
ifdef POCO_ENABLE_TRACE
include $(POCO_BASE)/build/rules/global
objects = trace
target = trace
target_version = 1
target_libs = PocoTrace PocoFoundation
target_includes = $(POCO_BASE)/Trace/include
include $(POCO_BASE)/build/rules/exec
else
all:
endif

View File

@ -0,0 +1,11 @@
vc.project.guid = ${vc.project.guidFromName}
vc.project.name = ${vc.project.baseName}
vc.project.target = ${vc.project.name}
vc.project.type = executable
vc.project.pocobase = ..\\..\\..
vc.project.platforms = Win32
vc.project.configurations = debug_shared, release_shared, debug_static_mt, release_static_mt, debug_static_md, release_static_md
vc.project.prototype = ${vc.project.name}_vs90.vcproj
vc.project.compiler.include = ..\\..\\..\\Foundation\\include;..\\..\\..\\JSON\\include
vc.project.compiler.additionalOptions = /Zc:__cplusplus
vc.project.linker.dependencies.Win32 = ws2_32.lib iphlpapi.lib

View File

@ -0,0 +1,447 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
Name="Benchmark"
Version="9.00"
ProjectType="Visual C++"
ProjectGUID="{D0381ECF-E750-32DA-8EEF-92D56B172D15}"
RootNamespace="Benchmark"
Keyword="Win32Proj">
<Platforms>
<Platform
Name="Win32"/>
</Platforms>
<ToolFiles/>
<Configurations>
<Configuration
Name="debug_shared|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCCLCompilerTool"
Optimization="0"
AdditionalIncludeDirectories=".\include;..\..\..\Foundation\include"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;"
StringPooling="true"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="3"
CompileAs="0"
DisableSpecificWarnings=""
AdditionalOptions=""/>
<Tool
Name="VCManagedResourceCompilerTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="ws2_32.lib iphlpapi.lib"
OutputFile="bin\Benchmarkd.exe"
LinkIncremental="2"
AdditionalLibraryDirectories="..\..\..\lib"
SuppressStartupBanner="true"
GenerateDebugInformation="true"
ProgramDatabaseFile="bin\Benchmarkd.pdb"
SubSystem="1"
TargetMachine="1"
AdditionalOptions=""/>
<Tool
Name="VCALinkTool"/>
<Tool
Name="VCManifestTool"/>
<Tool
Name="VCXDCMakeTool"/>
<Tool
Name="VCBscMakeTool"/>
<Tool
Name="VCFxCopTool"/>
<Tool
Name="VCAppVerifierTool"/>
<Tool
Name="VCPostBuildEventTool"/>
</Configuration>
<Configuration
Name="release_shared|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCCLCompilerTool"
Optimization="4"
InlineFunctionExpansion="1"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories=".\include;..\..\..\Foundation\include"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;"
StringPooling="true"
RuntimeLibrary="2"
BufferSecurityCheck="false"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="0"
CompileAs="0"
DisableSpecificWarnings=""
AdditionalOptions=""/>
<Tool
Name="VCManagedResourceCompilerTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="ws2_32.lib iphlpapi.lib"
OutputFile="bin\Benchmark.exe"
LinkIncremental="1"
AdditionalLibraryDirectories="..\..\..\lib"
GenerateDebugInformation="false"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"
AdditionalOptions=""/>
<Tool
Name="VCALinkTool"/>
<Tool
Name="VCManifestTool"/>
<Tool
Name="VCXDCMakeTool"/>
<Tool
Name="VCBscMakeTool"/>
<Tool
Name="VCFxCopTool"/>
<Tool
Name="VCAppVerifierTool"/>
<Tool
Name="VCPostBuildEventTool"/>
</Configuration>
<Configuration
Name="debug_static_mt|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCCLCompilerTool"
Optimization="4"
AdditionalIncludeDirectories=".\include;..\..\..\Foundation\include"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;"
StringPooling="true"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
BufferSecurityCheck="true"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="3"
CompileAs="0"
DisableSpecificWarnings=""
AdditionalOptions=""/>
<Tool
Name="VCManagedResourceCompilerTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="iphlpapi.lib winmm.lib ws2_32.lib iphlpapi.lib"
OutputFile="bin\static_mt\Benchmarkd.exe"
LinkIncremental="2"
AdditionalLibraryDirectories="..\..\..\lib"
SuppressStartupBanner="true"
GenerateDebugInformation="true"
ProgramDatabaseFile="bin\static_mt\Benchmarkd.pdb"
SubSystem="1"
TargetMachine="1"
AdditionalOptions=""/>
<Tool
Name="VCALinkTool"/>
<Tool
Name="VCManifestTool"/>
<Tool
Name="VCXDCMakeTool"/>
<Tool
Name="VCBscMakeTool"/>
<Tool
Name="VCFxCopTool"/>
<Tool
Name="VCAppVerifierTool"/>
<Tool
Name="VCPostBuildEventTool"/>
</Configuration>
<Configuration
Name="release_static_mt|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCCLCompilerTool"
Optimization="4"
InlineFunctionExpansion="1"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories=".\include;..\..\..\Foundation\include"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;"
StringPooling="true"
RuntimeLibrary="0"
BufferSecurityCheck="false"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="0"
CompileAs="0"
DisableSpecificWarnings=""
AdditionalOptions=""/>
<Tool
Name="VCManagedResourceCompilerTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="iphlpapi.lib winmm.lib ws2_32.lib iphlpapi.lib"
OutputFile="bin\static_mt\Benchmark.exe"
LinkIncremental="1"
AdditionalLibraryDirectories="..\..\..\lib"
GenerateDebugInformation="false"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"
AdditionalOptions=""/>
<Tool
Name="VCALinkTool"/>
<Tool
Name="VCManifestTool"/>
<Tool
Name="VCXDCMakeTool"/>
<Tool
Name="VCBscMakeTool"/>
<Tool
Name="VCFxCopTool"/>
<Tool
Name="VCAppVerifierTool"/>
<Tool
Name="VCPostBuildEventTool"/>
</Configuration>
<Configuration
Name="debug_static_md|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCCLCompilerTool"
Optimization="4"
AdditionalIncludeDirectories=".\include;..\..\..\Foundation\include"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;"
StringPooling="true"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="3"
CompileAs="0"
DisableSpecificWarnings=""
AdditionalOptions=""/>
<Tool
Name="VCManagedResourceCompilerTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="iphlpapi.lib winmm.lib ws2_32.lib iphlpapi.lib"
OutputFile="bin\static_md\Benchmarkd.exe"
LinkIncremental="2"
AdditionalLibraryDirectories="..\..\..\lib"
SuppressStartupBanner="true"
GenerateDebugInformation="true"
ProgramDatabaseFile="bin\static_md\Benchmarkd.pdb"
SubSystem="1"
TargetMachine="1"
AdditionalOptions=""/>
<Tool
Name="VCALinkTool"/>
<Tool
Name="VCManifestTool"/>
<Tool
Name="VCXDCMakeTool"/>
<Tool
Name="VCBscMakeTool"/>
<Tool
Name="VCFxCopTool"/>
<Tool
Name="VCAppVerifierTool"/>
<Tool
Name="VCPostBuildEventTool"/>
</Configuration>
<Configuration
Name="release_static_md|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="1"
CharacterSet="2">
<Tool
Name="VCPreBuildEventTool"/>
<Tool
Name="VCCustomBuildTool"/>
<Tool
Name="VCXMLDataGeneratorTool"/>
<Tool
Name="VCWebServiceProxyGeneratorTool"/>
<Tool
Name="VCMIDLTool"/>
<Tool
Name="VCCLCompilerTool"
Optimization="4"
InlineFunctionExpansion="1"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories=".\include;..\..\..\Foundation\include"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;WINVER=0x0500;POCO_STATIC;"
StringPooling="true"
RuntimeLibrary="2"
BufferSecurityCheck="false"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="0"
CompileAs="0"
DisableSpecificWarnings=""
AdditionalOptions=""/>
<Tool
Name="VCManagedResourceCompilerTool"/>
<Tool
Name="VCResourceCompilerTool"/>
<Tool
Name="VCPreLinkEventTool"/>
<Tool
Name="VCLinkerTool"
AdditionalDependencies="iphlpapi.lib winmm.lib ws2_32.lib iphlpapi.lib"
OutputFile="bin\static_md\Benchmark.exe"
LinkIncremental="1"
AdditionalLibraryDirectories="..\..\..\lib"
GenerateDebugInformation="false"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
TargetMachine="1"
AdditionalOptions=""/>
<Tool
Name="VCALinkTool"/>
<Tool
Name="VCManifestTool"/>
<Tool
Name="VCXDCMakeTool"/>
<Tool
Name="VCBscMakeTool"/>
<Tool
Name="VCFxCopTool"/>
<Tool
Name="VCAppVerifierTool"/>
<Tool
Name="VCPostBuildEventTool"/>
</Configuration>
</Configurations>
<References/>
<Files>
<Filter
Name="Header Files"/>
<Filter
Name="Source Files">
<File
RelativePath=".\src\trace.cpp"/>
</Filter>
</Files>
<Globals/>
</VisualStudioProject>

View File

@ -0,0 +1,51 @@
//
// Benchmark.cpp
//
// This sample shows printing of stack trace captured by Poco::Exception.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#include <iostream>
#include <sstream>
#include "Poco/Exception.h"
void functionC(int& i)
{
std::ostringstream ostr;
if (i>0)
functionC(--i);
throw Poco::Exception("thrown from functionC()");
}
void functionB()
{
int i = 5;
functionC(i);
}
void functionA()
{
functionB();
}
int main()
{
try
{
functionA();
}
catch (const Poco::Exception& e)
{
std::cerr << e.displayText() << std::endl;
}
return 0;
}

View File

@ -14,6 +14,10 @@
#include "Poco/Exception.h"
#include <typeinfo>
#ifdef POCO_ENABLE_TRACE
#include <sstream>
#include "cpptrace/cpptrace.hpp"
#endif
namespace Poco {
@ -21,11 +25,23 @@ namespace Poco {
Exception::Exception(int code): _pNested(0), _code(code)
{
#ifdef POCO_ENABLE_TRACE
std::ostringstream ostr;
ostr << '\n';
cpptrace::generate_trace(0,100).print(ostr);
_msg = ostr.str();
#endif
}
Exception::Exception(const std::string& msg, int code): _msg(msg), _pNested(0), _code(code)
{
#ifdef POCO_ENABLE_TRACE
std::ostringstream ostr;
ostr << '\n';
cpptrace::generate_trace(0,100).print(ostr);
_msg += ostr.str();
#endif
}
@ -36,11 +52,23 @@ Exception::Exception(const std::string& msg, const std::string& arg, int code):
_msg.append(": ");
_msg.append(arg);
}
#ifdef POCO_ENABLE_TRACE
std::ostringstream ostr;
ostr << '\n';
cpptrace::generate_trace(0,100).print(ostr);
_msg += ostr.str();
#endif
}
Exception::Exception(const std::string& msg, const Exception& nested, int code): _msg(msg), _pNested(nested.clone()), _code(code)
{
#ifdef POCO_ENABLE_TRACE
std::ostringstream ostr;
ostr << '\n';
cpptrace::generate_trace(0,100).print(ostr);
_msg += ostr.str();
#endif
}

View File

@ -47,5 +47,5 @@ target_libs = PocoFoundation CppUnit
include $(POCO_BASE)/build/rules/exec
ifdef POCO_UNBUNDLED
SYSLIBS += -lz -lpcre2-8 -lutf8proc
SYSLIBS += -lz -lpcre2-8 -lutf8proc
endif

View File

@ -156,8 +156,10 @@ void ActiveDispatcherTest::testFailure()
result.wait();
assertTrue (result.available());
assertTrue (result.failed());
#ifndef POCO_ENABLE_TRACE
std::string msg = result.error();
assertTrue (msg == "n == 100");
assertEqual ("n == 100", msg);
#endif
}

View File

@ -195,8 +195,10 @@ void ActiveMethodTest::testFailure()
result.wait();
assertTrue (result.available());
assertTrue (result.failed());
#ifndef POCO_ENABLE_TRACE
std::string msg = result.error();
assertTrue (msg == "n == 100");
assertEqual ("n == 100", msg);
#endif
}

View File

@ -72,7 +72,7 @@ ifdef POCO_VERBOSE
$(info OSARCH = $(OSARCH))
endif
.PHONY: poco all libexecs cppunit tests samples cleans clean distclean install uninstall
.PHONY: poco all libexecs cppunit trace tests samples cleans clean distclean install uninstall
# TESTS and SAMPLES are set in config.make
poco: libexecs $(if $(TESTS),tests) $(if $(SAMPLES),samples)
@ -87,6 +87,16 @@ cppunit:
CppUnit-clean:
$(MAKE) -C $(POCO_BASE)/CppUnit clean
trace:
ifdef POCO_ENABLE_TRACE
$(MAKE) -C $(POCO_BASE)/Trace
endif
Trace-clean:
ifdef POCO_ENABLE_TRACE
$(MAKE) -C $(POCO_BASE)/Trace clean
endif
install: libexecs
mkdir -p $(INSTALLDIR)/include/Poco
mkdir -p $(INSTALLDIR)/lib
@ -131,7 +141,7 @@ tests: $(filter-out $(foreach f,$(OMIT),$f%),$(tests))
samples: $(filter-out $(foreach f,$(OMIT),$f%),$(samples))
cleans: $(filter-out $(foreach f,$(OMIT),$f%),$(cleans))
Foundation-libexec:
Foundation-libexec: trace
$(MAKE) -C $(POCO_BASE)/Foundation
Foundation-tests: Foundation-libexec cppunit
@ -418,7 +428,7 @@ Prometheus-clean:
$(MAKE) -C $(POCO_BASE)/Prometheus/testsuite clean
$(MAKE) -C $(POCO_BASE)/Prometheus/samples clean
clean: cleans CppUnit-clean
clean: cleans Trace-clean CppUnit-clean
distclean:
rm -rf $(POCO_BUILD)/lib

View File

@ -29,7 +29,7 @@ endif()
if (DEFINED HAVE_SENDFILE)
message(STATUS "OS has native sendfile function")
add_definitions(-DPOCO_HAVE_SENDFILE)
add_compile_definitions(POCO_HAVE_SENDFILE)
endif()
# Version Resource

70
Trace/CMakeLists.txt Normal file
View File

@ -0,0 +1,70 @@
# Sources
file(GLOB SRCS_G "src/*.cpp")
POCO_SOURCES_AUTO(SRCS ${SRCS_G})
# Headers
file(GLOB_RECURSE HDRS_G "include/*.h*")
POCO_HEADERS_AUTO(SRCS ${HDRS_G})
# Version Resource
if(MSVC AND BUILD_SHARED_LIBS)
source_group("Resources" FILES ${PROJECT_SOURCE_DIR}/DLLVersion.rc)
list(APPEND SRCS ${PROJECT_SOURCE_DIR}/DLLVersion.rc)
endif()
add_library(Trace STATIC ${SRCS})
add_library(Poco::Trace ALIAS Trace)
set_target_properties(Trace
PROPERTIES
VERSION ${SHARED_LIBRARY_VERSION} SOVERSION ${SHARED_LIBRARY_VERSION}
OUTPUT_NAME PocoTrace
DEFINE_SYMBOL Trace_EXPORTS
)
#
# Build as static library. Primary usage is from PocoFoundation and is not intended to
# be provided as general purpose Poco module
#
target_compile_definitions (Trace PUBLIC CPPTRACE_STATIC_DEFINE)
#target_compile_definitions (Trace PRIVATE cpptrace_lib_EXPORTS)
set_property(TARGET Trace PROPERTY POSITION_INDEPENDENT_CODE ON)
if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# Find backtrace header provided by gcc with a package like "libgcc-11-dev"
file(GLOB_RECURSE BACKTRACE_H /usr/lib/gcc/*backtrace.h)
if (NOT BACKTRACE_H)
message(FATAL_ERROR "Poco::Trace: backtrace.h not found. GCC development package not installed.")
endif()
list(GET BACKTRACE_H 0 FIRST_BACKTRACE)
cmake_path(GET FIRST_BACKTRACE PARENT_PATH BACKTRACE_INCLUDE)
message(STATUS "Clang on Linux uses libbacktrace provided by gcc. backtrace.h found in ${BACKTRACE_INCLUDE}.")
target_include_directories(Trace PRIVATE ${BACKTRACE_INCLUDE})
endif()
target_compile_definitions (Trace PRIVATE CPPTRACE_DEMANGLE_WITH_CXXABI CPPTRACE_UNWIND_WITH_UNWIND)
target_compile_definitions (Trace PRIVATE CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
target_link_libraries(Trace PRIVATE backtrace)
elseif (APPLE)
target_compile_definitions (Trace PRIVATE CPPTRACE_DEMANGLE_WITH_CXXABI CPPTRACE_UNWIND_WITH_UNWIND)
target_compile_definitions (Trace PRIVATE CPPTRACE_GET_SYMBOLS_WITH_LIBDL)
elseif(WIN32)
target_compile_definitions (Trace PRIVATE CPPTRACE_DEMANGLE_WITH_WINAPI CPPTRACE_UNWIND_WITH_WINAPI)
target_compile_definitions (Trace PRIVATE CPPTRACE_GET_SYMBOLS_WITH_DBGHELP NOMINMAX)
target_link_libraries(Trace PRIVATE dbghelp)
else()
message(FATAL_ERROR "Poco::Trace: Unsupported platform.")
endif()
target_include_directories(Trace
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/Poco/Trace>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src
)
#POCO_INSTALL(Trace)
#POCO_GENERATE_PACKAGE(Trace)

19
Trace/Makefile Normal file
View File

@ -0,0 +1,19 @@
#
# Makefile
#
# Makefile for POCO Trace and dependencies
#
ifndef OSNAME
OSNAME := $(shell uname)
endif
.PHONY: clean distclean trace all
all: trace
trace:
$(MAKE) -f Makefile-Trace $(MAKECMDGOALS) $(MAKEARGS)
clean distclean:
$(MAKE) -f Makefile-Trace $(MAKECMDGOALS) $(MAKEARGS)

36
Trace/Makefile-Trace Normal file
View File

@ -0,0 +1,36 @@
#
# Makefile
#
# Makefile for Poco Trace
#
include $(POCO_BASE)/build/rules/global
INCLUDE += -I $(POCO_BASE)/Trace/src -I $(POCO_BASE)/Trace/src/symbols -I $(POCO_BASE)/Trace/include/Poco/Trace -I $(POCO_BASE)/Trace/include/Poco/Trace/backtrace
LIBRARY += -L$(POCO_BASE)/lib/$(OSNAME)/$(OSARCH)/lib
SYSFLAGS += -DCPPTRACE_DEMANGLE_WITH_CXXABI -DCPPTRACE_UNWIND_WITH_UNWIND
ifeq ($(OSNAME), Linux)
SYSFLAGS += -DCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
SYSLIBS += -lbacktrace
else
ifeq ($(OSNAME), Darwin)
SYSFLAGS += -DCPPTRACE_GET_SYMBOLS_WITH_LIBDL
endif
endif
objects = cpptrace ctrace from_current \
elf mach-o module_base object pe safe_dl \
demangle_with_cxxabi demangle_with_nothing demangle_with_winapi \
snippet symbols_core symbols_with_addr2line symbols_with_dbghelp \
symbols_with_dl symbols_with_libbacktrace symbols_with_libdwarf \
symbols_with_nothing debug_map_resolver dwarf_resolver \
unwind_with_dbghelp unwind_with_execinfo unwind_with_libunwind \
unwind_with_nothing unwind_with_unwind unwind_with_winapi
target = PocoTrace
target_version = $(LIBVERSION)
include $(POCO_BASE)/build/rules/lib

17
Trace/Trace.progen Normal file
View File

@ -0,0 +1,17 @@
vc.project.guid = ${vc.project.guidFromName}
vc.project.name = ${vc.project.baseName}
vc.project.target = Poco${vc.project.name}
vc.project.type = library
vc.project.pocobase = ..
vc.project.outdir = ${vc.project.pocobase}
vc.project.platforms = Win32
vc.project.configurations = debug_shared, release_shared, debug_static_mt, release_static_mt, debug_static_md, release_static_md
vc.project.prototype = ${vc.project.name}_vs90.vcproj
vc.project.compiler.include = ..\\Foundation\\include
vc.project.compiler.defines =
vc.project.compiler.defines.shared = ${vc.project.name}_EXPORTS
vc.project.compiler.defines.debug_shared = ${vc.project.compiler.defines.shared}
vc.project.compiler.defines.release_shared = ${vc.project.compiler.defines.shared}
vc.project.compiler.additionalOptions = /Zc:__cplusplus
vc.solution.create = true
vc.solution.include = testsuite\\TestSuite

60
Trace/Trace_vs90.sln Normal file
View File

@ -0,0 +1,60 @@
Microsoft Visual Studio Solution File, Format Version 10.00
# Visual Studio 2008
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSON", "JSON_vs90.vcproj", "{0E7FE914-0690-3EB4-9119-93A97CC97741}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestSuite", "testsuite\TestSuite_vs90.vcproj", "{96CF3103-E49E-3F5E-A11D-6DBCDA043053}"
ProjectSection(ProjectDependencies) = postProject
{0E7FE914-0690-3EB4-9119-93A97CC97741} = {0E7FE914-0690-3EB4-9119-93A97CC97741}
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
debug_shared|Win32 = debug_shared|Win32
release_shared|Win32 = release_shared|Win32
debug_static_mt|Win32 = debug_static_mt|Win32
release_static_mt|Win32 = release_static_mt|Win32
debug_static_md|Win32 = debug_static_md|Win32
release_static_md|Win32 = release_static_md|Win32
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_shared|Win32.ActiveCfg = debug_shared|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_shared|Win32.Build.0 = debug_shared|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_shared|Win32.Deploy.0 = debug_shared|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_shared|Win32.ActiveCfg = release_shared|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_shared|Win32.Build.0 = release_shared|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_shared|Win32.Deploy.0 = release_shared|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_static_mt|Win32.ActiveCfg = debug_static_mt|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_static_mt|Win32.Build.0 = debug_static_mt|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_static_mt|Win32.Deploy.0 = debug_static_mt|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_static_mt|Win32.ActiveCfg = release_static_mt|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_static_mt|Win32.Build.0 = release_static_mt|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_static_mt|Win32.Deploy.0 = release_static_mt|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_static_md|Win32.ActiveCfg = debug_static_md|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_static_md|Win32.Build.0 = debug_static_md|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.debug_static_md|Win32.Deploy.0 = debug_static_md|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_static_md|Win32.ActiveCfg = release_static_md|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_static_md|Win32.Build.0 = release_static_md|Win32
{0E7FE914-0690-3EB4-9119-93A97CC97741}.release_static_md|Win32.Deploy.0 = release_static_md|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_shared|Win32.ActiveCfg = debug_shared|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_shared|Win32.Build.0 = debug_shared|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_shared|Win32.Deploy.0 = debug_shared|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_shared|Win32.ActiveCfg = release_shared|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_shared|Win32.Build.0 = release_shared|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_shared|Win32.Deploy.0 = release_shared|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_static_mt|Win32.ActiveCfg = debug_static_mt|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_static_mt|Win32.Build.0 = debug_static_mt|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_static_mt|Win32.Deploy.0 = debug_static_mt|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_static_mt|Win32.ActiveCfg = release_static_mt|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_static_mt|Win32.Build.0 = release_static_mt|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_static_mt|Win32.Deploy.0 = release_static_mt|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_static_md|Win32.ActiveCfg = debug_static_md|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_static_md|Win32.Build.0 = debug_static_md|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.debug_static_md|Win32.Deploy.0 = debug_static_md|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_static_md|Win32.ActiveCfg = release_static_md|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_static_md|Win32.Build.0 = release_static_md|Win32
{96CF3103-E49E-3F5E-A11D-6DBCDA043053}.release_static_md|Win32.Deploy.0 = release_static_md|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

523
Trace/Trace_vs90.vcproj Normal file
View File

@ -0,0 +1,523 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="9.00"
Name="Trace"
ProjectGUID="{0E7FE914-0690-3EB4-9119-93A97CC97741}"
RootNamespace="Trace"
Keyword="Win32Proj"
TargetFrameworkVersion="0"
>
<Platforms>
<Platform
Name="Win32"
/>
</Platforms>
<ToolFiles>
</ToolFiles>
<Configurations>
<Configuration
Name="debug_shared|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="2"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions=""
Optimization="0"
AdditionalIncludeDirectories=".\include;..\Foundation\include"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;Trace_EXPORTS"
StringPooling="true"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="3"
CompileAs="0"
DisableSpecificWarnings=""
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalOptions=""
AdditionalDependencies=""
OutputFile="..\bin\PocoTraced.dll"
LinkIncremental="2"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="..\lib"
GenerateDebugInformation="true"
ProgramDatabaseFile="..\bin\PocoTraced.pdb"
SubSystem="1"
ImportLibrary="..\lib\PocoTraced.lib"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="release_shared|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="2"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions=""
Optimization="4"
InlineFunctionExpansion="1"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories=".\include;..\Foundation\include"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;Trace_EXPORTS"
StringPooling="true"
RuntimeLibrary="2"
BufferSecurityCheck="false"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="0"
CompileAs="0"
DisableSpecificWarnings=""
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLinkerTool"
AdditionalOptions=""
AdditionalDependencies=""
OutputFile="..\bin\PocoTrace.dll"
LinkIncremental="1"
SuppressStartupBanner="true"
AdditionalLibraryDirectories="..\lib"
GenerateDebugInformation="false"
SubSystem="1"
OptimizeReferences="2"
EnableCOMDATFolding="2"
ImportLibrary="..\lib\PocoTrace.lib"
TargetMachine="1"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCManifestTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCAppVerifierTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="debug_static_mt|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="4"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions=""
Optimization="0"
AdditionalIncludeDirectories=".\include;..\Foundation\include"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;POCO_STATIC;"
StringPooling="true"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="1"
BufferSecurityCheck="true"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
ProgramDataBaseFileName="..\lib\PocoTraceMTd.pdb"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="3"
CompileAs="0"
DisableSpecificWarnings=""
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLibrarianTool"
OutputFile="..\lib\PocoTraceMTd.lib"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="release_static_mt|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="4"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions=""
Optimization="4"
InlineFunctionExpansion="1"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories=".\include;..\Foundation\include"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;POCO_STATIC;"
StringPooling="true"
RuntimeLibrary="0"
BufferSecurityCheck="false"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="0"
CompileAs="0"
DisableSpecificWarnings=""
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLibrarianTool"
OutputFile="..\lib\PocoTraceMT.lib"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="debug_static_md|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="4"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions=""
Optimization="0"
AdditionalIncludeDirectories=".\include;..\Foundation\include"
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;POCO_STATIC;"
StringPooling="true"
MinimalRebuild="true"
BasicRuntimeChecks="3"
RuntimeLibrary="3"
BufferSecurityCheck="true"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
ProgramDataBaseFileName="..\lib\PocoTraceMDd.pdb"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="3"
CompileAs="0"
DisableSpecificWarnings=""
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLibrarianTool"
OutputFile="..\lib\PocoTraceMDd.lib"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
<Configuration
Name="release_static_md|Win32"
OutputDirectory="obj\$(ConfigurationName)"
IntermediateDirectory="obj\$(ConfigurationName)"
ConfigurationType="4"
CharacterSet="2"
>
<Tool
Name="VCPreBuildEventTool"
/>
<Tool
Name="VCCustomBuildTool"
/>
<Tool
Name="VCXMLDataGeneratorTool"
/>
<Tool
Name="VCWebServiceProxyGeneratorTool"
/>
<Tool
Name="VCMIDLTool"
/>
<Tool
Name="VCCLCompilerTool"
AdditionalOptions=""
Optimization="4"
InlineFunctionExpansion="1"
EnableIntrinsicFunctions="true"
FavorSizeOrSpeed="1"
OmitFramePointers="true"
AdditionalIncludeDirectories=".\include;..\Foundation\include"
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;POCO_STATIC;"
StringPooling="true"
RuntimeLibrary="2"
BufferSecurityCheck="false"
TreatWChar_tAsBuiltInType="true"
ForceConformanceInForLoopScope="true"
RuntimeTypeInfo="true"
UsePrecompiledHeader="0"
WarningLevel="3"
Detect64BitPortabilityProblems="false"
DebugInformationFormat="0"
CompileAs="0"
DisableSpecificWarnings=""
/>
<Tool
Name="VCManagedResourceCompilerTool"
/>
<Tool
Name="VCResourceCompilerTool"
/>
<Tool
Name="VCPreLinkEventTool"
/>
<Tool
Name="VCLibrarianTool"
OutputFile="..\lib\PocoTraceMD.lib"
/>
<Tool
Name="VCALinkTool"
/>
<Tool
Name="VCXDCMakeTool"
/>
<Tool
Name="VCBscMakeTool"
/>
<Tool
Name="VCFxCopTool"
/>
<Tool
Name="VCPostBuildEventTool"
/>
</Configuration>
</Configurations>
<References>
</References>
<Files>
<Filter
Name="Source Files"
>
<File
RelativePath=".\src\StackTrace.cpp"
>
</File>
<File
RelativePath=".\src\StackTraceException.cpp"
>
</File>
</Filter>
<Filter
Name="Header Files"
>
<File
RelativePath=".\include\Poco\Trace\StackTrace.h"
>
</File>
<File
RelativePath=".\include\Poco\Trace\StackTraceException.h"
>
</File>
</Filter>
</Files>
<Globals>
</Globals>
</VisualStudioProject>

View File

@ -0,0 +1,3 @@
include(CMakeFindDependencyMacro)
find_dependency(PocoFoundation)
include("${CMAKE_CURRENT_LIST_DIR}/PocoTraceTargets.cmake")

2
Trace/include/Poco/Trace/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
backtrace

View File

@ -0,0 +1,62 @@
//
// Trace.h
//
// Library: Trace
// Package: Trace
// Module: Trace
//
// Basic definitions for the Poco Trace library.
// This file must be the first file included by every other Trace
// header file.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier: BSL-1.0
//
#ifndef Trace_Trace_INCLUDED
#define Trace_Trace_INCLUDED
#include "Poco/Foundation.h"
//
// The following block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the Trace_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// Trace_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
//
#if defined(_WIN32) && defined(POCO_DLL)
#if defined(Trace_EXPORTS)
#define Trace_API __declspec(dllexport)
#else
#define Trace_API __declspec(dllimport)
#endif
#endif
#if !defined(Trace_API)
#if !defined(POCO_NO_GCC_API_ATTRIBUTE) && defined (__GNUC__) && (__GNUC__ >= 4)
#define Trace_API __attribute__ ((visibility ("default")))
#else
#define Trace_API
#endif
#endif
//
// Automatically link Trace library.
//
#if defined(_MSC_VER)
#if !defined(POCO_NO_AUTOMATIC_LIBS) && !defined(Trace_EXPORTS)
#pragma comment(lib, "PocoTrace" POCO_LIB_SUFFIX)
#endif
#endif
#endif // Trace_Trace_INCLUDED

View File

@ -0,0 +1,496 @@
#ifndef CPPTRACE_HPP
#define CPPTRACE_HPP
#include <cstdint>
#include <exception>
#include <limits>
#include <iosfwd>
#include <string>
#include <system_error>
#include <type_traits>
#include <utility>
#include <vector>
#ifdef _WIN32
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
#else
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
#endif
#ifdef CPPTRACE_STATIC_DEFINE
# define CPPTRACE_EXPORT
# define CPPTRACE_NO_EXPORT
#else
# ifndef CPPTRACE_EXPORT
# ifdef cpptrace_lib_EXPORTS
/* We are building this library */
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
# else
/* We are using this library */
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
# endif
# endif
#endif
#ifndef CPPTRACE_NO_STD_FORMAT
#if __cplusplus >= 202002L
#ifdef __has_include
#if __has_include(<format>)
#define CPPTRACE_STD_FORMAT
#include <format>
#endif
#endif
#endif
#endif
#ifdef _MSC_VER
#define CPPTRACE_FORCE_NO_INLINE __declspec(noinline)
#else
#define CPPTRACE_FORCE_NO_INLINE __attribute__((noinline))
#endif
#ifdef _MSC_VER
#pragma warning(push)
// warning C4251: using non-dll-exported type in dll-exported type, firing on std::vector<frame_ptr> and others for some
// reason
// 4275 is the same thing but for base classes
#pragma warning(disable: 4251; disable: 4275)
#endif
namespace cpptrace {
struct object_trace;
struct stacktrace;
// Some type sufficient for an instruction pointer, currently always an alias to std::uintptr_t
using frame_ptr = std::uintptr_t;
struct CPPTRACE_EXPORT raw_trace {
std::vector<frame_ptr> frames;
static raw_trace current(std::size_t skip = 0);
static raw_trace current(std::size_t skip, std::size_t max_depth);
object_trace resolve_object_trace() const;
stacktrace resolve() const;
void clear();
bool empty() const noexcept;
using iterator = std::vector<frame_ptr>::iterator;
using const_iterator = std::vector<frame_ptr>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator begin() const noexcept { return frames.begin(); }
inline const_iterator end() const noexcept { return frames.end(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};
struct CPPTRACE_EXPORT object_frame {
frame_ptr raw_address;
frame_ptr object_address;
std::string object_path;
};
struct CPPTRACE_EXPORT object_trace {
std::vector<object_frame> frames;
static object_trace current(std::size_t skip = 0);
static object_trace current(std::size_t skip, std::size_t max_depth);
stacktrace resolve() const;
void clear();
bool empty() const noexcept;
using iterator = std::vector<object_frame>::iterator;
using const_iterator = std::vector<object_frame>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator begin() const noexcept { return frames.begin(); }
inline const_iterator end() const noexcept { return frames.end(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
};
// This represents a nullable integer type
// The max value of the type is used as a sentinel
// This is used over std::optional because the library is C++11 and also std::optional is a bit heavy-duty for this
// use.
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
struct nullable {
T raw_value;
nullable& operator=(T value) {
raw_value = value;
return *this;
}
bool has_value() const noexcept {
return raw_value != (std::numeric_limits<T>::max)();
}
T& value() noexcept {
return raw_value;
}
const T& value() const noexcept {
return raw_value;
}
T value_or(T alternative) const noexcept {
return has_value() ? raw_value : alternative;
}
void swap(nullable& other) noexcept {
std::swap(raw_value, other.raw_value);
}
void reset() noexcept {
raw_value = (std::numeric_limits<T>::max)();
}
bool operator==(const nullable& other) const noexcept {
return raw_value == other.raw_value;
}
bool operator!=(const nullable& other) const noexcept {
return raw_value != other.raw_value;
}
constexpr static nullable null() noexcept {
return { (std::numeric_limits<T>::max)() };
}
};
struct CPPTRACE_EXPORT stacktrace_frame {
frame_ptr raw_address;
frame_ptr object_address;
nullable<std::uint32_t> line;
nullable<std::uint32_t> column;
std::string filename;
std::string symbol;
bool is_inline;
bool operator==(const stacktrace_frame& other) const {
return raw_address == other.raw_address
&& object_address == other.object_address
&& line == other.line
&& column == other.column
&& filename == other.filename
&& symbol == other.symbol;
}
bool operator!=(const stacktrace_frame& other) const {
return !operator==(other);
}
object_frame get_object_info() const;
std::string to_string() const;
friend std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame);
};
struct CPPTRACE_EXPORT stacktrace {
std::vector<stacktrace_frame> frames;
static stacktrace current(std::size_t skip = 0);
static stacktrace current(std::size_t skip, std::size_t max_depth);
void print() const;
void print(std::ostream& stream) const;
void print(std::ostream& stream, bool color) const;
void print_with_snippets() const;
void print_with_snippets(std::ostream& stream) const;
void print_with_snippets(std::ostream& stream, bool color) const;
void clear();
bool empty() const noexcept;
std::string to_string(bool color = false) const;
friend std::ostream& operator<<(std::ostream& stream, const stacktrace& trace);
using iterator = std::vector<stacktrace_frame>::iterator;
using const_iterator = std::vector<stacktrace_frame>::const_iterator;
inline iterator begin() noexcept { return frames.begin(); }
inline iterator end() noexcept { return frames.end(); }
inline const_iterator begin() const noexcept { return frames.begin(); }
inline const_iterator end() const noexcept { return frames.end(); }
inline const_iterator cbegin() const noexcept { return frames.cbegin(); }
inline const_iterator cend() const noexcept { return frames.cend(); }
private:
void print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
void print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const;
friend void print_terminate_trace();
};
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip = 0);
CPPTRACE_EXPORT raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth);
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip = 0);
CPPTRACE_EXPORT object_trace generate_object_trace(std::size_t skip, std::size_t max_depth);
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip = 0);
CPPTRACE_EXPORT stacktrace generate_trace(std::size_t skip, std::size_t max_depth);
// Path max isn't so simple, so I'm choosing 4096 which seems to encompass what all major OS's expect and should be
// fine in all reasonable cases.
// https://eklitzke.org/path-max-is-tricky
// https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
#define CPPTRACE_PATH_MAX 4096
// safe tracing interface
// signal-safe
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
frame_ptr* buffer,
std::size_t size,
std::size_t skip = 0
);
// signal-safe
CPPTRACE_EXPORT std::size_t safe_generate_raw_trace(
frame_ptr* buffer,
std::size_t size,
std::size_t skip,
std::size_t max_depth
);
struct CPPTRACE_EXPORT safe_object_frame {
frame_ptr raw_address;
frame_ptr address_relative_to_object_start; // base must still be added
char object_path[CPPTRACE_PATH_MAX + 1];
// To be called outside a signal handler. Not signal safe.
object_frame resolve() const;
};
// signal-safe
CPPTRACE_EXPORT void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
CPPTRACE_EXPORT bool can_signal_safe_unwind();
// utilities:
CPPTRACE_EXPORT std::string demangle(const std::string& name);
CPPTRACE_EXPORT std::string get_snippet(
const std::string& path,
std::size_t line,
std::size_t context_size,
bool color = false
);
CPPTRACE_EXPORT bool isatty(int fd);
CPPTRACE_EXPORT extern const int stdin_fileno;
CPPTRACE_EXPORT extern const int stderr_fileno;
CPPTRACE_EXPORT extern const int stdout_fileno;
CPPTRACE_EXPORT void register_terminate_handler();
// configuration:
CPPTRACE_EXPORT void absorb_trace_exceptions(bool absorb);
CPPTRACE_EXPORT void enable_inlined_call_resolution(bool enable);
enum class cache_mode {
// Only minimal lookup tables
prioritize_memory = 0,
// Build lookup tables but don't keep them around between trace calls
hybrid = 1,
// Build lookup tables as needed
prioritize_speed = 2
};
namespace experimental {
CPPTRACE_EXPORT void set_cache_mode(cache_mode mode);
}
// tracing exceptions:
namespace detail {
// This is a helper utility, if the library weren't C++11 an std::variant would be used
class CPPTRACE_EXPORT lazy_trace_holder {
bool resolved;
union {
raw_trace trace;
stacktrace resolved_trace;
};
public:
// constructors
lazy_trace_holder() : resolved(false), trace() {}
explicit lazy_trace_holder(raw_trace&& _trace) : resolved(false), trace(std::move(_trace)) {}
explicit lazy_trace_holder(stacktrace&& _resolved_trace) : resolved(true), resolved_trace(std::move(_resolved_trace)) {}
// logistics
lazy_trace_holder(const lazy_trace_holder& other);
lazy_trace_holder(lazy_trace_holder&& other) noexcept;
lazy_trace_holder& operator=(const lazy_trace_holder& other);
lazy_trace_holder& operator=(lazy_trace_holder&& other) noexcept;
~lazy_trace_holder();
// access
const raw_trace& get_raw_trace() const;
stacktrace& get_resolved_trace();
const stacktrace& get_resolved_trace() const;
private:
void clear();
};
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth);
CPPTRACE_EXPORT raw_trace get_raw_trace_and_absorb(std::size_t skip = 0);
}
// Interface for a traced exception object
class CPPTRACE_EXPORT exception : public std::exception {
public:
const char* what() const noexcept override = 0;
virtual const char* message() const noexcept = 0;
virtual const stacktrace& trace() const noexcept = 0;
};
// Cpptrace traced exception object
// I hate to have to expose anything about implementation detail but the idea here is that
class CPPTRACE_EXPORT lazy_exception : public exception {
mutable detail::lazy_trace_holder trace_holder;
mutable std::string what_string;
public:
explicit lazy_exception(
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) : trace_holder(std::move(trace)) {}
// std::exception
const char* what() const noexcept override;
// cpptrace::exception
const char* message() const noexcept override;
const stacktrace& trace() const noexcept override;
};
class CPPTRACE_EXPORT exception_with_message : public lazy_exception {
mutable std::string user_message;
public:
explicit exception_with_message(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept : lazy_exception(std::move(trace)), user_message(std::move(message_arg)) {}
const char* message() const noexcept override;
};
class CPPTRACE_EXPORT logic_error : public exception_with_message {
public:
explicit logic_error(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT domain_error : public exception_with_message {
public:
explicit domain_error(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT invalid_argument : public exception_with_message {
public:
explicit invalid_argument(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT length_error : public exception_with_message {
public:
explicit length_error(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT out_of_range : public exception_with_message {
public:
explicit out_of_range(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT runtime_error : public exception_with_message {
public:
explicit runtime_error(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT range_error : public exception_with_message {
public:
explicit range_error(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT overflow_error : public exception_with_message {
public:
explicit overflow_error(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT underflow_error : public exception_with_message {
public:
explicit underflow_error(
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: exception_with_message(std::move(message_arg), std::move(trace)) {}
};
class CPPTRACE_EXPORT nested_exception : public lazy_exception {
std::exception_ptr ptr;
mutable std::string message_value;
public:
explicit nested_exception(
const std::exception_ptr& exception_ptr,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept
: lazy_exception(std::move(trace)), ptr(exception_ptr) {}
const char* message() const noexcept override;
std::exception_ptr nested_ptr() const noexcept;
};
class CPPTRACE_EXPORT system_error : public runtime_error {
std::error_code ec;
public:
explicit system_error(
int error_code,
std::string&& message_arg,
raw_trace&& trace = detail::get_raw_trace_and_absorb()
) noexcept;
const std::error_code& code() const noexcept;
};
// [[noreturn]] must come first due to old clang
[[noreturn]] CPPTRACE_EXPORT void rethrow_and_wrap_if_needed(std::size_t skip = 0);
}
#if defined(CPPTRACE_STD_FORMAT) && defined(__cpp_lib_format)
template <>
struct std::formatter<cpptrace::stacktrace_frame> : std::formatter<std::string> {
auto format(cpptrace::stacktrace_frame frame, format_context& ctx) const {
return formatter<string>::format(frame.to_string(), ctx);
}
};
template <>
struct std::formatter<cpptrace::stacktrace> : std::formatter<std::string> {
auto format(cpptrace::stacktrace trace, format_context& ctx) const {
return formatter<string>::format(trace.to_string(), ctx);
}
};
#endif
// Exception wrapper utilities
#define CPPTRACE_WRAP_BLOCK(statements) do { \
try { \
statements \
} catch(...) { \
::cpptrace::rethrow_and_wrap_if_needed(); \
} \
} while(0)
#define CPPTRACE_WRAP(expression) [&] () -> decltype((expression)) { \
try { \
return expression; \
} catch(...) { \
::cpptrace::rethrow_and_wrap_if_needed(1); \
} \
} ()
#ifdef _MSC_VER
#pragma warning(pop)
#endif
#endif

View File

@ -0,0 +1,131 @@
#ifndef CPPTRACE_FROM_CURRENT_HPP
#define CPPTRACE_FROM_CURRENT_HPP
#include <cpptrace/cpptrace.hpp>
namespace cpptrace {
CPPTRACE_EXPORT const raw_trace& raw_trace_from_current_exception();
CPPTRACE_EXPORT const stacktrace& from_current_exception();
namespace detail {
// Trace switch is to prevent multiple tracing of stacks on call stacks with multiple catches that don't
// immediately match
inline bool& get_trace_switch() {
static thread_local bool trace_switch = true;
return trace_switch;
}
class CPPTRACE_EXPORT try_canary {
public:
~try_canary() {
// Fires when we exit a try block, either via normal means or during unwinding.
// Either way: Flip the switch.
get_trace_switch() = true;
}
};
CPPTRACE_EXPORT CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip);
// this function can be void, however, a char return is used to prevent TCO of the collect_current_trace
CPPTRACE_FORCE_NO_INLINE inline char exception_unwind_interceptor(std::size_t skip) {
if(get_trace_switch()) {
// Done during a search phase. Flip the switch off, no more traces until an unwind happens
get_trace_switch() = false;
collect_current_trace(skip + 1);
}
return 42;
}
#ifdef _MSC_VER
CPPTRACE_FORCE_NO_INLINE inline int exception_filter() {
exception_unwind_interceptor(1);
return 0; // EXCEPTION_CONTINUE_SEARCH
}
CPPTRACE_FORCE_NO_INLINE inline int unconditional_exception_filter() {
collect_current_trace(1);
return 0; // EXCEPTION_CONTINUE_SEARCH
}
#else
class CPPTRACE_EXPORT unwind_interceptor {
public:
virtual ~unwind_interceptor();
};
class CPPTRACE_EXPORT unconditional_unwind_interceptor {
public:
virtual ~unconditional_unwind_interceptor();
};
CPPTRACE_EXPORT void do_prepare_unwind_interceptor(char(*)(std::size_t));
#ifndef CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
__attribute__((constructor)) inline void prepare_unwind_interceptor() {
// __attribute__((constructor)) inline functions can be called for every source file they're #included in
// there is still only one copy of the inline function in the final executable, though
// LTO can make the redundant constructs fire only once
// do_prepare_unwind_interceptor prevents against multiple preparations however it makes sense to guard
// against it here too as a fast path, not that this should matter for performance
static bool did_prepare = false;
if(!did_prepare) {
do_prepare_unwind_interceptor(exception_unwind_interceptor);
did_prepare = true;
}
}
#endif
#endif
}
}
#ifdef _MSC_VER
// this awful double-IILE is due to C2713 "You can't use structured exception handling (__try/__except) and C++
// exception handling (try/catch) in the same function."
#define CPPTRACE_TRY \
try { \
::cpptrace::detail::try_canary cpptrace_try_canary; \
[&]() { \
__try { \
[&]() {
#define CPPTRACE_CATCH(param) \
}(); \
} __except(::cpptrace::detail::exception_filter()) {} \
}(); \
} catch(param)
#define CPPTRACE_TRYZ \
try { \
[&]() { \
__try { \
[&]() {
#define CPPTRACE_CATCHZ(param) \
}(); \
} __except(::cpptrace::detail::unconditional_exception_filter()) {} \
}(); \
} catch(param)
#else
#define CPPTRACE_TRY \
try { \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wshadow\"") \
::cpptrace::detail::try_canary cpptrace_try_canary; \
_Pragma("GCC diagnostic pop") \
try {
#define CPPTRACE_CATCH(param) \
} catch(::cpptrace::detail::unwind_interceptor&) {} \
} catch(param)
#define CPPTRACE_TRYZ \
try { \
try {
#define CPPTRACE_CATCHZ(param) \
} catch(::cpptrace::detail::unconditional_unwind_interceptor&) {} \
} catch(param)
#endif
#define CPPTRACE_CATCH_ALT(param) catch(param)
#ifdef CPPTRACE_UNPREFIXED_TRY_CATCH
#define TRY CPPTRACE_TRY
#define CATCH(param) CPPTRACE_CATCH(param)
#define TRYZ CPPTRACE_TRYZ
#define CATCHZ(param) CPPTRACE_CATCHZ(param)
#define CATCH_ALT(param) CPPTRACE_CATCH_ALT(param)
#endif
#endif

View File

@ -0,0 +1,163 @@
#ifndef CTRACE_H
#define CTRACE_H
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#ifdef _WIN32
#define CPPTRACE_EXPORT_ATTR __declspec(dllexport)
#define CPPTRACE_IMPORT_ATTR __declspec(dllimport)
#else
#define CPPTRACE_EXPORT_ATTR __attribute__((visibility("default")))
#define CPPTRACE_IMPORT_ATTR __attribute__((visibility("default")))
#endif
#ifdef CPPTRACE_STATIC_DEFINE
# define CPPTRACE_EXPORT
# define CPPTRACE_NO_EXPORT
#else
# ifndef CPPTRACE_EXPORT
# ifdef cpptrace_lib_EXPORTS
/* We are building this library */
# define CPPTRACE_EXPORT CPPTRACE_EXPORT_ATTR
# else
/* We are using this library */
# define CPPTRACE_EXPORT CPPTRACE_IMPORT_ATTR
# endif
# endif
#endif
#if defined(__cplusplus)
#define CTRACE_BEGIN_DEFINITIONS extern "C" {
#define CTRACE_END_DEFINITIONS }
#else
#define CTRACE_BEGIN_DEFINITIONS
#define CTRACE_END_DEFINITIONS
#endif
#ifdef _MSC_VER
#define CTRACE_FORCE_NO_INLINE __declspec(noinline)
#else
#define CTRACE_FORCE_NO_INLINE __attribute__((noinline))
#endif
#ifdef _MSC_VER
#define CTRACE_FORCE_INLINE __forceinline
#elif defined(__clang__) || defined(__GNUC__)
#define CTRACE_FORCE_INLINE __attribute__((always_inline)) inline
#else
#define CTRACE_FORCE_INLINE inline
#endif
/* See `CPPTRACE_PATH_MAX` for more info. */
#define CTRACE_PATH_MAX 4096
CTRACE_BEGIN_DEFINITIONS
typedef struct ctrace_raw_trace ctrace_raw_trace;
typedef struct ctrace_object_trace ctrace_object_trace;
typedef struct ctrace_stacktrace ctrace_stacktrace;
/* Represents a boolean value, ensures a consistent ABI. */
typedef int8_t ctrace_bool;
/* A type that can represent a pointer, alias for `uintptr_t`. */
typedef uintptr_t ctrace_frame_ptr;
typedef struct ctrace_object_frame ctrace_object_frame;
typedef struct ctrace_stacktrace_frame ctrace_stacktrace_frame;
typedef struct ctrace_safe_object_frame ctrace_safe_object_frame;
/* Type-safe null-terminated string wrapper */
typedef struct {
const char* data;
} ctrace_owning_string;
struct ctrace_object_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr obj_address;
const char* obj_path;
};
struct ctrace_stacktrace_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr object_address;
uint32_t line;
uint32_t column;
const char* filename;
const char* symbol;
ctrace_bool is_inline;
};
struct ctrace_safe_object_frame {
ctrace_frame_ptr raw_address;
ctrace_frame_ptr relative_obj_address;
char object_path[CTRACE_PATH_MAX + 1];
};
struct ctrace_raw_trace {
ctrace_frame_ptr* frames;
size_t count;
};
struct ctrace_object_trace {
ctrace_object_frame* frames;
size_t count;
};
struct ctrace_stacktrace {
ctrace_stacktrace_frame* frames;
size_t count;
};
/* ctrace::string: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_generate_owning_string(const char* raw_string);
CPPTRACE_EXPORT void ctrace_free_owning_string(ctrace_owning_string* string);
/* ctrace::generation: */
CPPTRACE_EXPORT ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth);
CPPTRACE_EXPORT ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth);
CPPTRACE_EXPORT ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth);
/* ctrace::freeing: */
CPPTRACE_EXPORT void ctrace_free_raw_trace(ctrace_raw_trace* trace);
CPPTRACE_EXPORT void ctrace_free_object_trace(ctrace_object_trace* trace);
CPPTRACE_EXPORT void ctrace_free_stacktrace(ctrace_stacktrace* trace);
/* ctrace::resolve: */
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace);
CPPTRACE_EXPORT ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace);
CPPTRACE_EXPORT ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace);
/* ctrace::safe: */
CPPTRACE_EXPORT size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth);
CPPTRACE_EXPORT void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out);
CPPTRACE_EXPORT ctrace_bool can_signal_safe_unwind(void);
/* ctrace::io: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color);
CPPTRACE_EXPORT void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color);
/* ctrace::utility: */
CPPTRACE_EXPORT ctrace_owning_string ctrace_demangle(const char* mangled);
CPPTRACE_EXPORT int ctrace_stdin_fileno(void);
CPPTRACE_EXPORT int ctrace_stderr_fileno(void);
CPPTRACE_EXPORT int ctrace_stdout_fileno(void);
CPPTRACE_EXPORT ctrace_bool ctrace_isatty(int fd);
CPPTRACE_EXPORT ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame);
/* ctrace::config: */
typedef enum {
/* Only minimal lookup tables */
ctrace_prioritize_memory = 0,
/* Build lookup tables but don't keep them around between trace calls */
ctrace_hybrid = 1,
/* Build lookup tables as needed */
ctrace_prioritize_speed = 2
} ctrace_cache_mode;
CPPTRACE_EXPORT void ctrace_set_cache_mode(ctrace_cache_mode mode);
CPPTRACE_EXPORT void ctrace_enable_inlined_call_resolution(ctrace_bool enable);
CTRACE_END_DEFINITIONS
#endif

20
Trace/src/binary/elf.hpp Normal file
View File

@ -0,0 +1,20 @@
#ifndef ELF_HPP
#define ELF_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_LINUX
#include <cstdint>
#include <string>
namespace cpptrace {
namespace detail {
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path);
}
}
#endif
#endif

137
Trace/src/binary/mach-o.hpp Normal file
View File

@ -0,0 +1,137 @@
#ifndef MACHO_HPP
#define MACHO_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_APPLE
#include <cstdint>
#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <mach-o/arch.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
namespace cpptrace {
namespace detail {
bool file_is_mach_o(const std::string& object_path) noexcept;
struct load_command_entry {
std::uint32_t file_offset;
std::uint32_t cmd;
std::uint32_t cmdsize;
};
class mach_o {
file_wrapper file;
std::string object_path;
std::uint32_t magic;
cpu_type_t cputype;
cpu_subtype_t cpusubtype;
std::uint32_t filetype;
std::uint32_t n_load_commands;
std::uint32_t sizeof_load_commands;
std::uint32_t flags;
std::size_t bits = 0; // 32 or 64 once load_mach is called
std::size_t load_base = 0;
std::size_t fat_index = std::numeric_limits<std::size_t>::max();
std::vector<load_command_entry> load_commands;
struct symtab_info_data {
symtab_command symtab;
std::unique_ptr<char[]> stringtab;
Result<const char*, internal_error> get_string(std::size_t index) const;
};
bool tried_to_load_symtab = false;
optional<symtab_info_data> symtab_info;
mach_o(
file_wrapper file,
const std::string& object_path,
std::uint32_t magic
) :
file(std::move(file)),
object_path(object_path),
magic(magic) {}
Result<monostate, internal_error> load();
public:
static NODISCARD Result<mach_o, internal_error> open_mach_o(const std::string& object_path);
mach_o(mach_o&&) = default;
~mach_o() = default;
Result<std::uintptr_t, internal_error> get_text_vmaddr();
std::size_t get_fat_index() const;
void print_segments() const;
Result<std::reference_wrapper<optional<symtab_info_data>>, internal_error> get_symtab_info();
void print_symbol_table_entry(
const nlist_64& entry,
const std::unique_ptr<char[]>& stringtab,
std::size_t stringsize,
std::size_t j
) const;
void print_symbol_table();
struct debug_map_entry {
uint64_t source_address;
uint64_t size;
std::string name;
};
struct symbol_entry {
uint64_t address;
std::string name;
};
// map from object file to a vector of symbols to resolve
using debug_map = std::unordered_map<std::string, std::vector<debug_map_entry>>;
// produce information similar to dsymutil -dump-debug-map
Result<debug_map, internal_error> get_debug_map();
Result<std::vector<symbol_entry>, internal_error> symbol_table();
// produce information similar to dsymutil -dump-debug-map
static void print_debug_map(const debug_map& debug_map);
private:
template<std::size_t Bits>
Result<monostate, internal_error> load_mach();
Result<monostate, internal_error> load_fat_mach();
template<std::size_t Bits>
Result<segment_command_64, internal_error> load_segment_command(std::uint32_t offset) const;
Result<symtab_command, internal_error> load_symbol_table_command(std::uint32_t offset) const;
template<std::size_t Bits>
Result<nlist_64, internal_error> load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const;
Result<std::unique_ptr<char[]>, internal_error> load_string_table(std::uint32_t offset, std::uint32_t byte_count) const;
bool should_swap() const;
};
Result<bool, internal_error> macho_is_fat(const std::string& object_path);
}
}
#endif
#endif

View File

@ -0,0 +1,16 @@
#ifndef IMAGE_MODULE_BASE_HPP
#define IMAGE_MODULE_BASE_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <cstdint>
#include <string>
namespace cpptrace {
namespace detail {
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path);
}
}
#endif

View File

@ -0,0 +1,21 @@
#ifndef OBJECT_HPP
#define OBJECT_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/module_base.hpp"
#include <string>
#include <vector>
namespace cpptrace {
namespace detail {
object_frame get_frame_object_info(frame_ptr address);
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses);
object_frame resolve_safe_object_frame(const safe_object_frame& frame);
}
}
#endif

19
Trace/src/binary/pe.hpp Normal file
View File

@ -0,0 +1,19 @@
#ifndef PE_HPP
#define PE_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_WINDOWS
#include <cstdint>
#include <string>
namespace cpptrace {
namespace detail {
Result<std::uintptr_t, internal_error> pe_get_module_image_base(const std::string& object_path);
}
}
#endif
#endif

View File

@ -0,0 +1,12 @@
#ifndef SAFE_DL_HPP
#define SAFE_DL_HPP
#include "utils/common.hpp"
namespace cpptrace {
namespace detail {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out);
}
}
#endif

698
Trace/src/cpptrace.cpp Normal file
View File

@ -0,0 +1,698 @@
#include <cpptrace/cpptrace.hpp>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <new>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "platform/exception_type.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/object.hpp"
#include "binary/safe_dl.hpp"
#include "snippets/snippet.hpp"
namespace cpptrace {
CPPTRACE_FORCE_NO_INLINE
raw_trace raw_trace::current(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_raw_trace(skip + 1);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
raw_trace raw_trace::current(std::size_t skip, std::size_t max_depth) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_raw_trace(skip + 1, max_depth);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
object_trace raw_trace::resolve_object_trace() const {
try {
return object_trace{detail::get_frames_object_info(frames)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
stacktrace raw_trace::resolve() const {
try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
void raw_trace::clear() {
frames.clear();
}
bool raw_trace::empty() const noexcept {
return frames.empty();
}
CPPTRACE_FORCE_NO_INLINE
object_trace object_trace::current(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_object_trace(skip + 1);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
object_trace object_trace::current(std::size_t skip, std::size_t max_depth) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_object_trace(skip + 1, max_depth);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
stacktrace object_trace::resolve() const {
try {
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace();
}
}
void object_trace::clear() {
frames.clear();
}
bool object_trace::empty() const noexcept {
return frames.empty();
}
object_frame stacktrace_frame::get_object_info() const {
return detail::get_frame_object_info(raw_address);
}
std::string stacktrace_frame::to_string() const {
std::string str;
if(is_inline) {
str += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else {
str += microfmt::format("0x{>{}:0h}", 2 * sizeof(frame_ptr), raw_address);
}
if(!symbol.empty()) {
str += microfmt::format(" in {}", symbol);
}
if(!filename.empty()) {
str += microfmt::format(" at {}", filename);
if(line.has_value()) {
str += microfmt::format(":{}", line.value());
if(column.has_value()) {
str += microfmt::format(":{}", column.value());
}
}
}
return str;
}
std::ostream& operator<<(std::ostream& stream, const stacktrace_frame& frame) {
return stream << frame.to_string();
}
CPPTRACE_FORCE_NO_INLINE
stacktrace stacktrace::current(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_trace(skip + 1);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
CPPTRACE_FORCE_NO_INLINE
stacktrace stacktrace::current(std::size_t skip, std::size_t max_depth) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_trace(skip + 1, max_depth);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
void stacktrace::print() const {
print(std::cerr, true);
}
void stacktrace::print(std::ostream& stream) const {
print(stream, true);
}
void stacktrace::print(std::ostream& stream, bool color) const {
print(stream, color, true, nullptr);
}
void print_frame(
std::ostream& stream,
bool color,
unsigned frame_number_width,
std::size_t counter,
const stacktrace_frame& frame
) {
const auto reset = color ? RESET : "";
const auto green = color ? GREEN : "";
const auto yellow = color ? YELLOW : "";
const auto blue = color ? BLUE : "";
std::string line = microfmt::format("#{<{}} ", frame_number_width, counter);
if(frame.is_inline) {
line += microfmt::format("{<{}}", 2 * sizeof(frame_ptr) + 2, "(inlined)");
} else {
line += microfmt::format("{}0x{>{}:0h}{}", blue, 2 * sizeof(frame_ptr), frame.raw_address, reset);
}
if(!frame.symbol.empty()) {
line += microfmt::format(" in {}{}{}", yellow, frame.symbol, reset);
}
if(!frame.filename.empty()) {
line += microfmt::format(" at {}{}{}", green, frame.filename, reset);
if(frame.line.has_value()) {
line += microfmt::format(":{}{}{}", blue, frame.line.value(), reset);
if(frame.column.has_value()) {
line += microfmt::format(":{}{}{}", blue, frame.column.value(), reset);
}
}
}
stream << line;
}
void stacktrace::print(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>\n";
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
counter++;
}
}
void stacktrace::print_with_snippets() const {
print_with_snippets(std::cerr, true);
}
void stacktrace::print_with_snippets(std::ostream& stream) const {
print_with_snippets(stream, true);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color) const {
print_with_snippets(stream, color, true, nullptr);
}
void stacktrace::print_with_snippets(std::ostream& stream, bool color, bool newline_at_end, const char* header) const {
if(
color && (
(&stream == &std::cout && isatty(stdout_fileno)) || (&stream == &std::cerr && isatty(stderr_fileno))
)
) {
detail::enable_virtual_terminal_processing_if_needed();
}
stream << (header ? header : "Stack trace (most recent call first):") << '\n';
std::size_t counter = 0;
if(frames.empty()) {
stream << "<empty trace>" << '\n';
return;
}
const auto frame_number_width = detail::n_digits(static_cast<int>(frames.size()) - 1);
for(const auto& frame : frames) {
print_frame(stream, color, frame_number_width, counter, frame);
if(newline_at_end || &frame != &frames.back()) {
stream << '\n';
}
if(frame.line.has_value() && !frame.filename.empty()) {
stream << detail::get_snippet(frame.filename, frame.line.value(), 2, color);
}
counter++;
}
}
void stacktrace::clear() {
frames.clear();
}
bool stacktrace::empty() const noexcept {
return frames.empty();
}
std::string stacktrace::to_string(bool color) const {
std::ostringstream oss;
print(oss, color, false, nullptr);
return std::move(oss).str();
}
std::ostream& operator<<(std::ostream& stream, const stacktrace& trace) {
return stream << trace.to_string();
}
CPPTRACE_FORCE_NO_INLINE
raw_trace generate_raw_trace(std::size_t skip) {
try {
return raw_trace{detail::capture_frames(skip + 1, SIZE_MAX)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
raw_trace generate_raw_trace(std::size_t skip, std::size_t max_depth) {
try {
return raw_trace{detail::capture_frames(skip + 1, max_depth)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_generate_raw_trace(frame_ptr* buffer, std::size_t size, std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return detail::safe_capture_frames(buffer, size, skip + 1, SIZE_MAX);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return 0;
}
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_generate_raw_trace(
frame_ptr* buffer,
std::size_t size,
std::size_t skip,
std::size_t max_depth
) {
try { // try/catch can never be hit but it's needed to prevent TCO
return detail::safe_capture_frames(buffer, size, skip + 1, max_depth);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return 0;
}
}
CPPTRACE_FORCE_NO_INLINE
object_trace generate_object_trace(std::size_t skip) {
try {
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, SIZE_MAX))};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
object_trace generate_object_trace(std::size_t skip, std::size_t max_depth) {
try {
return object_trace{detail::get_frames_object_info(detail::capture_frames(skip + 1, max_depth))};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return object_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
stacktrace generate_trace(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return generate_trace(skip + 1, SIZE_MAX);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace{};
}
}
CPPTRACE_FORCE_NO_INLINE
stacktrace generate_trace(std::size_t skip, std::size_t max_depth) {
try {
std::vector<frame_ptr> frames = detail::capture_frames(skip + 1, max_depth);
std::vector<stacktrace_frame> trace = detail::resolve_frames(frames);
for(auto& frame : trace) {
frame.symbol = detail::demangle(frame.symbol);
}
return {std::move(trace)};
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return stacktrace();
}
}
object_frame safe_object_frame::resolve() const {
return detail::resolve_safe_object_frame(*this);
}
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
detail::get_safe_object_frame(address, out);
}
bool can_signal_safe_unwind() {
return detail::has_safe_unwind();
}
std::string demangle(const std::string& name) {
return detail::demangle(name);
}
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color) {
return detail::get_snippet(path, line, context_size, color);
}
bool isatty(int fd) {
return detail::isatty(fd);
}
extern const int stdin_fileno = detail::fileno(stdin);
extern const int stdout_fileno = detail::fileno(stdout);
extern const int stderr_fileno = detail::fileno(stderr);
CPPTRACE_FORCE_NO_INLINE void print_terminate_trace() {
try { // try/catch can never be hit but it's needed to prevent TCO
generate_trace(1).print(
std::cerr,
isatty(stderr_fileno),
true,
"Stack trace to reach terminate handler (most recent call first):"
);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
}
}
[[noreturn]] void terminate_handler() {
// TODO: Support std::nested_exception?
try {
auto ptr = std::current_exception();
if(ptr == nullptr) {
fputs("terminate called without an active exception", stderr);
print_terminate_trace();
} else {
std::rethrow_exception(ptr);
}
} catch(cpptrace::exception& e) {
microfmt::print(
stderr,
"Terminate called after throwing an instance of {}: {}\n",
demangle(typeid(e).name()),
e.message()
);
e.trace().print(std::cerr, isatty(stderr_fileno));
} catch(std::exception& e) {
microfmt::print(
stderr, "Terminate called after throwing an instance of {}: {}\n", demangle(typeid(e).name()), e.what()
);
print_terminate_trace();
} catch(...) {
microfmt::print(
stderr, "Terminate called after throwing an instance of {}\n", detail::exception_type_name()
);
print_terminate_trace();
}
std::flush(std::cerr);
abort();
}
void register_terminate_handler() {
std::set_terminate(terminate_handler);
}
namespace detail {
std::atomic_bool absorb_trace_exceptions(true); // NOSONAR
std::atomic_bool resolve_inlined_calls(true); // NOSONAR
std::atomic<enum cache_mode> cache_mode(cache_mode::prioritize_speed); // NOSONAR
}
void absorb_trace_exceptions(bool absorb) {
detail::absorb_trace_exceptions = absorb;
}
void enable_inlined_call_resolution(bool enable) {
detail::resolve_inlined_calls = enable;
}
namespace experimental {
void set_cache_mode(cache_mode mode) {
detail::cache_mode = mode;
}
}
namespace detail {
bool should_absorb_trace_exceptions() {
return absorb_trace_exceptions;
}
bool should_resolve_inlined_calls() {
return resolve_inlined_calls;
}
enum cache_mode get_cache_mode() {
return cache_mode;
}
CPPTRACE_FORCE_NO_INLINE
raw_trace get_raw_trace_and_absorb(std::size_t skip, std::size_t max_depth) {
try {
return generate_raw_trace(skip + 1, max_depth);
} catch(const std::exception& e) {
if(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow
std::fprintf(
stderr,
"Cpptrace: Exception occurred while resolving trace in cpptrace::exception object:\n%s\n",
e.what()
);
}
return raw_trace{};
}
}
CPPTRACE_FORCE_NO_INLINE
raw_trace get_raw_trace_and_absorb(std::size_t skip) {
try { // try/catch can never be hit but it's needed to prevent TCO
return get_raw_trace_and_absorb(skip + 1, SIZE_MAX);
} catch(...) {
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
return raw_trace{};
}
}
lazy_trace_holder::lazy_trace_holder(const lazy_trace_holder& other) : resolved(other.resolved) {
if(other.resolved) {
new (&resolved_trace) stacktrace(other.resolved_trace);
} else {
new (&trace) raw_trace(other.trace);
}
}
lazy_trace_holder::lazy_trace_holder(lazy_trace_holder&& other) noexcept : resolved(other.resolved) {
if(other.resolved) {
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
} else {
new (&trace) raw_trace(std::move(other.trace));
}
}
lazy_trace_holder& lazy_trace_holder::operator=(const lazy_trace_holder& other) {
clear();
resolved = other.resolved;
if(other.resolved) {
new (&resolved_trace) stacktrace(other.resolved_trace);
} else {
new (&trace) raw_trace(other.trace);
}
return *this;
}
lazy_trace_holder& lazy_trace_holder::operator=(lazy_trace_holder&& other) noexcept {
clear();
resolved = other.resolved;
if(other.resolved) {
new (&resolved_trace) stacktrace(std::move(other.resolved_trace));
} else {
new (&trace) raw_trace(std::move(other.trace));
}
return *this;
}
lazy_trace_holder::~lazy_trace_holder() {
clear();
}
// access
const raw_trace& lazy_trace_holder::get_raw_trace() const {
if(resolved) {
throw std::logic_error(
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on resolved holder"
);
}
return trace;
}
stacktrace& lazy_trace_holder::get_resolved_trace() {
if(!resolved) {
raw_trace old_trace = std::move(trace);
*this = lazy_trace_holder(stacktrace{});
try {
if(!old_trace.empty()) {
resolved_trace = old_trace.resolve();
}
} catch(const std::exception& e) {
if(!detail::should_absorb_trace_exceptions()) {
// TODO: Append to message somehow?
std::fprintf(
stderr,
"Exception occurred while resolving trace in cpptrace::detail::lazy_trace_holder:\n%s\n",
e.what()
);
}
}
}
return resolved_trace;
}
const stacktrace& lazy_trace_holder::get_resolved_trace() const {
if(!resolved) {
throw std::logic_error(
"cpptrace::detail::lazy_trace_holder::get_resolved_trace called on unresolved const holder"
);
}
return resolved_trace;
}
void lazy_trace_holder::clear() {
if(resolved) {
resolved_trace.~stacktrace();
} else {
trace.~raw_trace();
}
}
}
const char* lazy_exception::what() const noexcept {
if(what_string.empty()) {
what_string = message() + std::string(":\n") + trace_holder.get_resolved_trace().to_string();
}
return what_string.c_str();
}
const char* lazy_exception::message() const noexcept {
return "cpptrace::lazy_exception";
}
const stacktrace& lazy_exception::trace() const noexcept {
return trace_holder.get_resolved_trace();
}
const char* exception_with_message::message() const noexcept {
return user_message.c_str();
}
system_error::system_error(int error_code, std::string&& message_arg, raw_trace&& trace) noexcept
: runtime_error(
message_arg + ": " + std::error_code(error_code, std::generic_category()).message(),
std::move(trace)
),
ec(std::error_code(error_code, std::generic_category())) {}
const std::error_code& system_error::code() const noexcept {
return ec;
}
const char* nested_exception::message() const noexcept {
if(message_value.empty()) {
try {
std::rethrow_exception(ptr);
} catch(std::exception& e) {
message_value = std::string("Nested exception: ") + e.what();
} catch(...) {
message_value = "Nested exception holding instance of " + detail::exception_type_name();
}
}
return message_value.c_str();
}
std::exception_ptr nested_exception::nested_ptr() const noexcept {
return ptr;
}
CPPTRACE_FORCE_NO_INLINE
void rethrow_and_wrap_if_needed(std::size_t skip) {
try {
std::rethrow_exception(std::current_exception());
} catch(cpptrace::exception&) {
throw; // already a cpptrace::exception
} catch(...) {
throw nested_exception(std::current_exception(), detail::get_raw_trace_and_absorb(skip + 1));
}
}
}

442
Trace/src/ctrace.cpp Normal file
View File

@ -0,0 +1,442 @@
#include <ctrace/ctrace.h>
#include <cpptrace/cpptrace.hpp>
#include <algorithm>
#include "symbols/symbols.hpp"
#include "unwind/unwind.hpp"
#include "demangle/demangle.hpp"
#include "platform/exception_type.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/object.hpp"
#include "binary/safe_dl.hpp"
#define ESC "\033["
#define RESET ESC "0m"
#define RED ESC "31m"
#define GREEN ESC "32m"
#define YELLOW ESC "33m"
#define BLUE ESC "34m"
#define MAGENTA ESC "35m"
#define CYAN ESC "36m"
#if defined(__GNUC__) && ((__GNUC__ > 2) || (__GNUC__ == 2 && __GNUC_MINOR__ >= 6))
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
#elif defined(__clang__)
// Probably requires llvm >3.5? Not exactly sure.
# define CTRACE_GNU_FORMAT(...) __attribute__((format(__VA_ARGS__)))
#else
# define CTRACE_GNU_FORMAT(...)
#endif
#if defined(__clang__)
# define CTRACE_FORMAT_PROLOGUE \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wformat-security\"")
# define CTRACE_FORMAT_EPILOGUE \
_Pragma("clang diagnostic pop")
#elif defined(__GNUC_MINOR__)
# define CTRACE_FORMAT_PROLOGUE \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wformat-security\"")
# define CTRACE_FORMAT_EPILOGUE \
_Pragma("GCC diagnostic pop")
#else
# define CTRACE_FORMAT_PROLOGUE
# define CTRACE_FORMAT_EPILOGUE
#endif
namespace ctrace {
static constexpr std::uint32_t invalid_pos = ~0U;
CTRACE_FORMAT_PROLOGUE
template <typename...Args>
CTRACE_GNU_FORMAT(printf, 2, 0)
static void ffprintf(std::FILE* f, const char fmt[], Args&&...args) {
(void)std::fprintf(f, fmt, args...);
(void)fflush(f);
}
CTRACE_FORMAT_EPILOGUE
static bool is_empty(std::uint32_t pos) noexcept {
return pos == invalid_pos;
}
static bool is_empty(const char* str) noexcept {
return !str || std::char_traits<char>::length(str) == 0;
}
static ctrace_owning_string generate_owning_string(const char* raw_string) noexcept {
// Returns length to the null terminator.
std::size_t count = std::char_traits<char>::length(raw_string);
char* new_string = new char[count + 1];
std::char_traits<char>::copy(new_string, raw_string, count);
new_string[count] = '\0';
return { new_string };
}
static ctrace_owning_string generate_owning_string(const std::string& std_string) {
return generate_owning_string(std_string.c_str());
}
static void free_owning_string(const char* owned_string) noexcept {
if(!owned_string) return; // Not necessary but eh
delete[] owned_string;
}
static void free_owning_string(ctrace_owning_string& owned_string) noexcept {
free_owning_string(owned_string.data);
}
static ctrace_object_frame convert_object_frame(const cpptrace::object_frame& frame) {
const char* new_path = generate_owning_string(frame.object_path).data;
return { frame.raw_address, frame.object_address, new_path };
}
static ctrace_object_trace c_convert(const std::vector<cpptrace::object_frame>& trace) {
std::size_t count = trace.size();
auto* frames = new ctrace_object_frame[count];
std::transform(trace.begin(), trace.end(), frames, convert_object_frame);
return { frames, count };
}
static ctrace_stacktrace_frame convert_stacktrace_frame(const cpptrace::stacktrace_frame& frame) {
ctrace_stacktrace_frame new_frame;
new_frame.raw_address = frame.raw_address;
new_frame.object_address = frame.object_address;
new_frame.line = frame.line.value_or(invalid_pos);
new_frame.column = frame.column.value_or(invalid_pos);
new_frame.filename = generate_owning_string(frame.filename).data;
new_frame.symbol = generate_owning_string(cpptrace::detail::demangle(frame.symbol)).data;
new_frame.is_inline = ctrace_bool(frame.is_inline);
return new_frame;
}
static cpptrace::stacktrace_frame convert_stacktrace_frame(const ctrace_stacktrace_frame& frame) {
using nullable_type = cpptrace::nullable<std::uint32_t>;
static constexpr auto null_v = nullable_type::null().raw_value;
cpptrace::stacktrace_frame new_frame;
new_frame.raw_address = frame.raw_address;
new_frame.object_address = frame.object_address;
new_frame.line = nullable_type{is_empty(frame.line) ? null_v : frame.line};
new_frame.column = nullable_type{is_empty(frame.column) ? null_v : frame.column};
new_frame.filename = frame.filename;
new_frame.symbol = frame.symbol;
new_frame.is_inline = bool(frame.is_inline);
return new_frame;
}
static ctrace_stacktrace c_convert(const std::vector<cpptrace::stacktrace_frame>& trace) {
std::size_t count = trace.size();
auto* frames = new ctrace_stacktrace_frame[count];
std::transform(
trace.begin(),
trace.end(), frames,
static_cast<ctrace_stacktrace_frame(*)(const cpptrace::stacktrace_frame&)>(convert_stacktrace_frame)
);
return { frames, count };
}
static cpptrace::stacktrace cpp_convert(const ctrace_stacktrace* ptrace) {
if(!ptrace || !ptrace->frames) {
return { };
}
std::vector<cpptrace::stacktrace_frame> new_frames;
new_frames.reserve(ptrace->count);
for(std::size_t i = 0; i < ptrace->count; ++i) {
new_frames.push_back(convert_stacktrace_frame(ptrace->frames[i]));
}
return cpptrace::stacktrace{std::move(new_frames)};
}
}
extern "C" {
// ctrace::string
ctrace_owning_string ctrace_generate_owning_string(const char* raw_string) {
return ctrace::generate_owning_string(raw_string);
}
void ctrace_free_owning_string(ctrace_owning_string* string) {
if(!string) {
return;
}
ctrace::free_owning_string(*string);
string->data = nullptr;
}
// ctrace::generation:
CTRACE_FORCE_NO_INLINE
ctrace_raw_trace ctrace_generate_raw_trace(size_t skip, size_t max_depth) {
try {
std::vector<cpptrace::frame_ptr> trace = cpptrace::detail::capture_frames(skip + 1, max_depth);
std::size_t count = trace.size();
auto* frames = new ctrace_frame_ptr[count];
std::copy(trace.data(), trace.data() + count, frames);
return { frames, count };
} catch(...) {
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
CTRACE_FORCE_NO_INLINE
ctrace_object_trace ctrace_generate_object_trace(size_t skip, size_t max_depth) {
try {
std::vector<cpptrace::object_frame> trace = cpptrace::detail::get_frames_object_info(
cpptrace::detail::capture_frames(skip + 1, max_depth)
);
return ctrace::c_convert(trace);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
CTRACE_FORCE_NO_INLINE
ctrace_stacktrace ctrace_generate_trace(size_t skip, size_t max_depth) {
try {
std::vector<cpptrace::frame_ptr> frames = cpptrace::detail::capture_frames(skip + 1, max_depth);
std::vector<cpptrace::stacktrace_frame> trace = cpptrace::detail::resolve_frames(frames);
return ctrace::c_convert(trace);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
// ctrace::freeing:
void ctrace_free_raw_trace(ctrace_raw_trace* trace) {
if(!trace) {
return;
}
ctrace_frame_ptr* frames = trace->frames;
delete[] frames;
trace->frames = nullptr;
trace->count = 0;
}
void ctrace_free_object_trace(ctrace_object_trace* trace) {
if(!trace || !trace->frames) {
return;
}
ctrace_object_frame* frames = trace->frames;
for(std::size_t i = 0; i < trace->count; ++i) {
const char* path = frames[i].obj_path;
ctrace::free_owning_string(path);
}
delete[] frames;
trace->frames = nullptr;
trace->count = 0;
}
void ctrace_free_stacktrace(ctrace_stacktrace* trace) {
if(!trace || !trace->frames) {
return;
}
ctrace_stacktrace_frame* frames = trace->frames;
for(std::size_t i = 0; i < trace->count; ++i) {
ctrace::free_owning_string(frames[i].filename);
ctrace::free_owning_string(frames[i].symbol);
}
delete[] frames;
trace->frames = nullptr;
trace->count = 0;
}
// ctrace::resolve:
ctrace_stacktrace ctrace_resolve_raw_trace(const ctrace_raw_trace* trace) {
if(!trace || !trace->frames) {
return { nullptr, 0 };
}
try {
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
return ctrace::c_convert(resolved);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
ctrace_object_trace ctrace_resolve_raw_trace_to_object_trace(const ctrace_raw_trace* trace) {
if(!trace || !trace->frames) {
return { nullptr, 0 };
}
try {
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
std::copy(trace->frames, trace->frames + trace->count, frames.begin());
std::vector<cpptrace::object_frame> obj = cpptrace::detail::get_frames_object_info(frames);
return ctrace::c_convert(obj);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
ctrace_stacktrace ctrace_resolve_object_trace(const ctrace_object_trace* trace) {
if(!trace || !trace->frames) {
return { nullptr, 0 };
}
try {
std::vector<cpptrace::frame_ptr> frames(trace->count, 0);
std::transform(
trace->frames,
trace->frames + trace->count,
frames.begin(),
[] (const ctrace_object_frame& frame) -> cpptrace::frame_ptr {
return frame.raw_address;
}
);
std::vector<cpptrace::stacktrace_frame> resolved = cpptrace::detail::resolve_frames(frames);
return ctrace::c_convert(resolved);
} catch(...) { // NOSONAR
// Don't check rethrow condition, it's risky.
return { nullptr, 0 };
}
}
// ctrace::safe:
size_t ctrace_safe_generate_raw_trace(ctrace_frame_ptr* buffer, size_t size, size_t skip, size_t max_depth) {
return cpptrace::safe_generate_raw_trace(buffer, size, skip, max_depth);
}
void ctrace_get_safe_object_frame(ctrace_frame_ptr address, ctrace_safe_object_frame* out) {
// TODO: change this?
static_assert(sizeof(cpptrace::safe_object_frame) == sizeof(ctrace_safe_object_frame), "");
cpptrace::get_safe_object_frame(address, reinterpret_cast<cpptrace::safe_object_frame*>(out));
}
ctrace_bool can_signal_safe_unwind() {
return cpptrace::can_signal_safe_unwind();
}
// ctrace::io:
ctrace_owning_string ctrace_stacktrace_to_string(const ctrace_stacktrace* trace, ctrace_bool use_color) {
if(!trace || !trace->frames) {
return ctrace::generate_owning_string("<empty trace>");
}
auto cpp_trace = ctrace::cpp_convert(trace);
std::string trace_string = cpp_trace.to_string(bool(use_color));
return ctrace::generate_owning_string(trace_string);
}
void ctrace_print_stacktrace(const ctrace_stacktrace* trace, FILE* to, ctrace_bool use_color) {
if(
use_color && (
(to == stdout && cpptrace::isatty(cpptrace::stdout_fileno)) ||
(to == stderr && cpptrace::isatty(cpptrace::stderr_fileno))
)
) {
cpptrace::detail::enable_virtual_terminal_processing_if_needed();
}
ctrace::ffprintf(to, "Stack trace (most recent call first):\n");
if(trace->count == 0 || !trace->frames) {
ctrace::ffprintf(to, "<empty trace>\n");
return;
}
const auto reset = use_color ? ESC "0m" : "";
const auto green = use_color ? ESC "32m" : "";
const auto yellow = use_color ? ESC "33m" : "";
const auto blue = use_color ? ESC "34m" : "";
const auto frame_number_width = cpptrace::detail::n_digits(unsigned(trace->count - 1));
ctrace_stacktrace_frame* frames = trace->frames;
for(std::size_t i = 0; i < trace->count; ++i) {
static constexpr auto ptr_len = 2 * sizeof(cpptrace::frame_ptr);
ctrace::ffprintf(to, "#%-*llu ", int(frame_number_width), i);
if(frames[i].is_inline) {
(void)std::fprintf(to, "%*s",
int(ptr_len + 2),
"(inlined)");
} else {
(void)std::fprintf(to, "%s0x%0*llx%s",
blue,
int(ptr_len),
cpptrace::detail::to_ull(frames[i].raw_address),
reset);
}
if(!ctrace::is_empty(frames[i].symbol)) {
(void)std::fprintf(to, " in %s%s%s",
yellow,
frames[i].symbol,
reset);
}
if(!ctrace::is_empty(frames[i].filename)) {
(void)std::fprintf(to, " at %s%s%s",
green,
frames[i].filename,
reset);
if(ctrace::is_empty(frames[i].line)) {
ctrace::ffprintf(to, "\n");
continue;
}
(void)std::fprintf(to, ":%s%llu%s",
blue,
cpptrace::detail::to_ull(frames[i].line),
reset);
if(ctrace::is_empty(frames[i].column)) {
ctrace::ffprintf(to, "\n");
continue;
}
(void)std::fprintf(to, ":%s%llu%s",
blue,
cpptrace::detail::to_ull(frames[i].column),
reset);
}
// always print newline at end :M
ctrace::ffprintf(to, "\n");
}
}
// utility::demangle:
ctrace_owning_string ctrace_demangle(const char* mangled) {
if(!mangled) {
return ctrace::generate_owning_string("");
}
std::string demangled = cpptrace::demangle(mangled);
return ctrace::generate_owning_string(demangled);
}
// utility::io
int ctrace_stdin_fileno(void) {
return cpptrace::stdin_fileno;
}
int ctrace_stderr_fileno(void) {
return cpptrace::stderr_fileno;
}
int ctrace_stdout_fileno(void) {
return cpptrace::stdout_fileno;
}
ctrace_bool ctrace_isatty(int fd) {
return cpptrace::isatty(fd);
}
// utility::cache:
void ctrace_set_cache_mode(ctrace_cache_mode mode) {
static constexpr auto cache_max = cpptrace::cache_mode::prioritize_speed;
if(mode > unsigned(cache_max)) {
return;
}
auto cache_mode = static_cast<cpptrace::cache_mode>(mode);
cpptrace::experimental::set_cache_mode(cache_mode);
}
void ctrace_enable_inlined_call_resolution(ctrace_bool enable) {
cpptrace::enable_inlined_call_resolution(enable);
}
ctrace_object_frame ctrace_get_object_info(const ctrace_stacktrace_frame* frame) {
try {
cpptrace::object_frame new_frame = cpptrace::detail::get_frame_object_info(frame->raw_address);
return ctrace::convert_object_frame(new_frame);
} catch(...) {
return {0, 0, nullptr};
}
}
}

View File

@ -0,0 +1,207 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
#include "symbols/dwarf/resolver.hpp"
#include <cpptrace/cpptrace.hpp>
#include "symbols/symbols.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "binary/object.hpp"
#include "binary/mach-o.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
namespace cpptrace {
namespace detail {
namespace libdwarf {
#if IS_APPLE
struct target_object {
std::string object_path;
bool path_ok = true;
optional<std::unordered_map<std::string, uint64_t>> symbols;
std::unique_ptr<symbol_resolver> resolver;
target_object(std::string object_path) : object_path(std::move(object_path)) {}
std::unique_ptr<symbol_resolver>& get_resolver() {
if(!resolver) {
// this seems silly but it's an attempt to not repeatedly try to initialize new dwarf_resolvers if
// exceptions are thrown, e.g. if the path doesn't exist
resolver = std::unique_ptr<null_resolver>(new null_resolver);
resolver = make_dwarf_resolver(object_path);
}
return resolver;
}
std::unordered_map<std::string, uint64_t>& get_symbols() {
if(!symbols) {
// this is an attempt to not repeatedly try to reprocess mach-o files if exceptions are thrown, e.g. if
// the path doesn't exist
std::unordered_map<std::string, uint64_t> symbols;
this->symbols = symbols;
auto obj = mach_o::open_mach_o(object_path);
if(!obj) {
return this->symbols.unwrap();
}
auto symbol_table = obj.unwrap_value().symbol_table();
if(!symbol_table) {
return this->symbols.unwrap();
}
for(const auto& symbol : symbol_table.unwrap_value()) {
symbols[symbol.name] = symbol.address;
}
this->symbols = std::move(symbols);
}
return symbols.unwrap();
}
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
frame_with_inlines resolve_frame(
const object_frame& frame_info,
const std::string& symbol_name,
std::size_t offset
) {
const auto& symbol_table = get_symbols();
auto it = symbol_table.find(symbol_name);
if(it != symbol_table.end()) {
auto frame = frame_info;
// substitute a translated address object for the target file in
frame.object_address = it->second + offset;
auto res = get_resolver()->resolve_frame(frame);
// replace the translated address with the object address in the binary
res.frame.object_address = frame_info.object_address;
return res;
} else {
return {
{
frame_info.raw_address,
frame_info.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
frame_info.object_path,
symbol_name,
false
},
{}
};
}
}
};
struct debug_map_symbol_info {
uint64_t source_address;
uint64_t size;
std::string name;
nullable<uint64_t> target_address; // T(-1) is used as a sentinel
std::size_t object_index;
};
class debug_map_resolver : public symbol_resolver {
std::vector<target_object> target_objects;
std::vector<debug_map_symbol_info> symbols;
public:
debug_map_resolver(const std::string& source_object_path) {
// load mach-o
// TODO: Cache somehow?
auto obj = mach_o::open_mach_o(source_object_path);
if(!obj) {
return;
}
mach_o& source_mach = obj.unwrap_value();
auto source_debug_map = source_mach.get_debug_map();
if(!source_debug_map) {
return;
}
// get symbol entries from debug map, as well as the various object files used to make this binary
for(auto& entry : source_debug_map.unwrap_value()) {
// object it came from
target_objects.push_back({entry.first});
// push the symbols
auto& map_entry_symbols = entry.second;
symbols.reserve(symbols.size() + map_entry_symbols.size());
for(auto& symbol : map_entry_symbols) {
symbols.push_back({
symbol.source_address,
symbol.size,
std::move(symbol.name),
nullable<uint64_t>::null(),
target_objects.size() - 1
});
}
}
// sort for binary lookup later
std::sort(
symbols.begin(),
symbols.end(),
[] (
const debug_map_symbol_info& a,
const debug_map_symbol_info& b
) {
return a.source_address < b.source_address;
}
);
}
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
frame_with_inlines resolve_frame(const object_frame& frame_info) override {
// resolve object frame:
// find the symbol in this executable corresponding to the object address
// resolve the symbol in the object it came from, based on the symbol name
auto closest_symbol_it = first_less_than_or_equal(
symbols.begin(),
symbols.end(),
frame_info.object_address,
[] (
uint64_t pc,
const debug_map_symbol_info& symbol
) {
return pc < symbol.source_address;
}
);
if(closest_symbol_it != symbols.end()) {
if(frame_info.object_address <= closest_symbol_it->source_address + closest_symbol_it->size) {
return target_objects[closest_symbol_it->object_index].resolve_frame(
{
frame_info.raw_address,
// the resolver doesn't care about the object address here, only the offset from the start
// of the symbol and it'll lookup the symbol's base-address
frame_info.object_address,
frame_info.object_path
},
closest_symbol_it->name,
frame_info.object_address - closest_symbol_it->source_address
);
}
}
// There was either no closest symbol or the closest symbol didn't end up containing the address we're
// looking for, so just return a blank frame
return {
{
frame_info.raw_address,
frame_info.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
frame_info.object_path,
"",
false
},
{}
};
};
};
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path) {
return std::unique_ptr<debug_map_resolver>(new debug_map_resolver(object_path));
}
#endif
}
}
}
#endif

View File

@ -0,0 +1,12 @@
#ifndef DEMANGLE_HPP
#define DEMANGLE_HPP
#include <string>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string&);
}
}
#endif

View File

@ -0,0 +1,31 @@
#ifdef CPPTRACE_DEMANGLE_WITH_CXXABI
#include "demangle/demangle.hpp"
#include <cxxabi.h>
#include <cstdlib>
#include <string>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string& name) {
int status;
// presumably thread-safe
// it appears safe to pass nullptr for status however the docs don't explicitly say it's safe so I don't
// want to rely on it
char* const demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
// demangled will always be nullptr on non-zero status, and if __cxa_demangle ever fails for any reason
// we'll just quietly return the mangled name
if(demangled) {
std::string str = demangled;
std::free(demangled);
return str;
} else {
return name;
}
}
}
}
#endif

View File

@ -0,0 +1,15 @@
#ifdef CPPTRACE_DEMANGLE_WITH_NOTHING
#include "demangle/demangle.hpp"
#include <string>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string& name) {
return name;
}
}
}
#endif

View File

@ -0,0 +1,25 @@
#ifdef CPPTRACE_DEMANGLE_WITH_WINAPI
#include "demangle/demangle.hpp"
#include <string>
#include <windows.h>
#include <dbghelp.h>
namespace cpptrace {
namespace detail {
std::string demangle(const std::string& name) {
char buffer[500];
auto ret = UnDecorateSymbolName(name.c_str(), buffer, sizeof(buffer) - 1, 0);
if(ret == 0) {
return name;
} else {
buffer[ret] = 0; // just in case, ms' docs unclear if null terminator inserted
return buffer;
}
}
}
}
#endif

1086
Trace/src/dwarf_resolver.cpp Normal file

File diff suppressed because it is too large Load Diff

100
Trace/src/elf.cpp Normal file
View File

@ -0,0 +1,100 @@
#include "binary/elf.hpp"
#if IS_LINUX
#include <array>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <type_traits>
#include <elf.h>
namespace cpptrace {
namespace detail {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T elf_byteswap_if_needed(T value, bool elf_is_little) {
if(is_little_endian() == elf_is_little) {
return value;
} else {
return byteswap(value);
}
}
template<std::size_t Bits>
static Result<std::uintptr_t, internal_error> elf_get_module_image_base_from_program_table(
const std::string& object_path,
std::FILE* file,
bool is_little_endian
) {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
using Header = typename std::conditional<Bits == 32, Elf32_Ehdr, Elf64_Ehdr>::type;
using PHeader = typename std::conditional<Bits == 32, Elf32_Phdr, Elf64_Phdr>::type;
auto loaded_header = load_bytes<Header>(file, 0);
if(loaded_header.is_error()) {
return std::move(loaded_header).unwrap_error();
}
const Header& file_header = loaded_header.unwrap_value();
if(file_header.e_ehsize != sizeof(Header)) {
return internal_error("ELF file header size mismatch" + object_path);
}
// PT_PHDR will occur at most once
// Should be somewhat reliable https://stackoverflow.com/q/61568612/15675011
// It should occur at the beginning but may as well loop just in case
for(int i = 0; i < file_header.e_phnum; i++) {
auto loaded_ph = load_bytes<PHeader>(file, file_header.e_phoff + file_header.e_phentsize * i);
if(loaded_ph.is_error()) {
return std::move(loaded_ph).unwrap_error();
}
const PHeader& program_header = loaded_ph.unwrap_value();
if(elf_byteswap_if_needed(program_header.p_type, is_little_endian) == PT_PHDR) {
return elf_byteswap_if_needed(program_header.p_vaddr, is_little_endian) -
elf_byteswap_if_needed(program_header.p_offset, is_little_endian);
}
}
// Apparently some objects like shared objects can end up missing this header. 0 as a base seems correct.
return 0;
}
Result<std::uintptr_t, internal_error> elf_get_module_image_base(const std::string& object_path) {
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
if(file == nullptr) {
return internal_error("Unable to read object file " + object_path);
}
// Initial checks/metadata
auto magic = load_bytes<std::array<char, 4>>(file, 0);
if(magic.is_error()) {
return std::move(magic).unwrap_error();
}
if(magic.unwrap_value() != (std::array<char, 4>{0x7F, 'E', 'L', 'F'})) {
return internal_error("File is not ELF " + object_path);
}
auto ei_class = load_bytes<std::uint8_t>(file, 4);
if(ei_class.is_error()) {
return std::move(ei_class).unwrap_error();
}
bool is_64 = ei_class.unwrap_value() == 2;
auto ei_data = load_bytes<std::uint8_t>(file, 5);
if(ei_data.is_error()) {
return std::move(ei_data).unwrap_error();
}
bool is_little_endian = ei_data.unwrap_value() == 1;
auto ei_version = load_bytes<std::uint8_t>(file, 6);
if(ei_version.is_error()) {
return std::move(ei_version).unwrap_error();
}
if(ei_version.unwrap_value() != 1) {
return internal_error("Unexpected ELF version " + object_path);
}
// get image base
if(is_64) {
return elf_get_module_image_base_from_program_table<64>(object_path, file, is_little_endian);
} else {
return elf_get_module_image_base_from_program_table<32>(object_path, file, is_little_endian);
}
}
}
}
#endif

310
Trace/src/from_current.cpp Normal file
View File

@ -0,0 +1,310 @@
#include <cpptrace/cpptrace.hpp>
#define CPPTRACE_DONT_PREPARE_UNWIND_INTERCEPTOR_ON
#include <cpptrace/from_current.hpp>
#include <system_error>
#include <typeinfo>
#include "platform/platform.hpp"
#include "utils/common.hpp"
#include "utils/microfmt.hpp"
#include "utils/utils.hpp"
#ifndef _MSC_VER
#include <array>
#include <string.h>
#if IS_WINDOWS
#include <windows.h>
#else
#include <sys/mman.h>
#include <unistd.h>
#if IS_APPLE
#include <mach/mach.h>
#include <mach/mach_vm.h>
#else
#include <fstream>
#include <iomanip>
#endif
#endif
#endif
namespace cpptrace {
namespace detail {
thread_local lazy_trace_holder current_exception_trace;
CPPTRACE_FORCE_NO_INLINE void collect_current_trace(std::size_t skip) {
current_exception_trace = lazy_trace_holder(cpptrace::generate_raw_trace(skip + 1));
}
#ifndef _MSC_VER
// set only once by do_prepare_unwind_interceptor
char (*intercept_unwind_handler)(std::size_t) = nullptr;
CPPTRACE_FORCE_NO_INLINE
bool intercept_unwind(const std::type_info*, const std::type_info*, void**, unsigned) {
if(intercept_unwind_handler) {
intercept_unwind_handler(1);
}
return false;
}
CPPTRACE_FORCE_NO_INLINE
bool unconditional_exception_unwind_interceptor(const std::type_info*, const std::type_info*, void**, unsigned) {
collect_current_trace(1);
return false;
}
using do_catch_fn = decltype(intercept_unwind);
unwind_interceptor::~unwind_interceptor() = default;
unconditional_unwind_interceptor::~unconditional_unwind_interceptor() = default;
#if IS_LIBSTDCXX
constexpr size_t vtable_size = 11;
#elif IS_LIBCXX
constexpr size_t vtable_size = 10;
#else
#warning "Cpptrace from_current: Unrecognized C++ standard library, from_current() won't be supported"
constexpr size_t vtable_size = 0;
#endif
#if IS_WINDOWS
int get_page_size() {
SYSTEM_INFO info;
GetSystemInfo(&info);
return info.dwPageSize;
}
constexpr auto memory_readonly = PAGE_READONLY;
constexpr auto memory_readwrite = PAGE_READWRITE;
int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) {
DWORD old_protections;
if(!VirtualProtect(page, page_size, protections, &old_protections)) {
throw std::runtime_error(
microfmt::format(
"VirtualProtect call failed: {}",
std::system_error(GetLastError(), std::system_category()).what()
)
);
}
return old_protections;
}
void mprotect_page(void* page, int page_size, int protections) {
mprotect_page_and_return_old_protections(page, page_size, protections);
}
void* allocate_page(int page_size) {
auto page = VirtualAlloc(nullptr, page_size, MEM_COMMIT | MEM_RESERVE, memory_readwrite);
if(!page) {
throw std::runtime_error(
microfmt::format(
"VirtualAlloc call failed: {}",
std::system_error(GetLastError(), std::system_category()).what()
)
);
}
return page;
}
#else
int get_page_size() {
return getpagesize();
}
constexpr auto memory_readonly = PROT_READ;
constexpr auto memory_readwrite = PROT_READ | PROT_WRITE;
#if IS_APPLE
int get_page_protections(void* page) {
// https://stackoverflow.com/a/12627784/15675011
mach_vm_size_t vmsize;
mach_vm_address_t address = (mach_vm_address_t)page;
vm_region_basic_info_data_t info;
mach_msg_type_number_t info_count =
sizeof(size_t) == 8 ? VM_REGION_BASIC_INFO_COUNT_64 : VM_REGION_BASIC_INFO_COUNT;
memory_object_name_t object;
kern_return_t status = mach_vm_region(
mach_task_self(),
&address,
&vmsize,
VM_REGION_BASIC_INFO,
(vm_region_info_t)&info,
&info_count,
&object
);
if(status == KERN_INVALID_ADDRESS) {
throw std::runtime_error("vm_region failed with KERN_INVALID_ADDRESS");
}
int perms = 0;
if(info.protection & VM_PROT_READ) {
perms |= PROT_READ;
}
if(info.protection & VM_PROT_WRITE) {
perms |= PROT_WRITE;
}
if(info.protection & VM_PROT_EXECUTE) {
perms |= PROT_EXEC;
}
return perms;
}
#else
int get_page_protections(void* page) {
auto page_addr = reinterpret_cast<uintptr_t>(page);
std::ifstream stream("/proc/self/maps");
stream>>std::hex;
while(!stream.eof()) {
uintptr_t start;
uintptr_t stop;
stream>>start;
stream.ignore(1); // dash
stream>>stop;
if(stream.eof()) {
break;
}
if(stream.fail()) {
throw std::runtime_error("Failure reading /proc/self/maps");
}
if(page_addr >= start && page_addr < stop) {
stream.ignore(1); // space
char r, w, x; // there's a private/shared flag after these but we don't need it
stream>>r>>w>>x;
if(stream.fail() || stream.eof()) {
throw std::runtime_error("Failure reading /proc/self/maps");
}
int perms = 0;
if(r == 'r') {
perms |= PROT_READ;
}
if(w == 'w') {
perms |= PROT_WRITE;
}
if(x == 'x') {
perms |= PROT_EXEC;
}
// std::cerr<<"--parsed: "<<std::hex<<start<<" "<<stop<<" "<<r<<w<<x<<std::endl;
return perms;
}
stream.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
}
throw std::runtime_error("Failed to find mapping with page in /proc/self/maps");
}
#endif
void mprotect_page(void* page, int page_size, int protections) {
if(mprotect(page, page_size, protections) != 0) {
throw std::runtime_error(microfmt::format("mprotect call failed: {}", strerror(errno)));
}
}
int mprotect_page_and_return_old_protections(void* page, int page_size, int protections) {
auto old_protections = get_page_protections(page);
mprotect_page(page, page_size, protections);
return old_protections;
}
void* allocate_page(int page_size) {
auto page = mmap(nullptr, page_size, memory_readwrite, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if(page == MAP_FAILED) {
throw std::runtime_error(microfmt::format("mmap call failed: {}", strerror(errno)));
}
return page;
}
#endif
void perform_typeinfo_surgery(const std::type_info& info, do_catch_fn* do_catch_function) {
if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with
return;
}
void* type_info_pointer = const_cast<void*>(static_cast<const void*>(&info));
void* type_info_vtable_pointer = *static_cast<void**>(type_info_pointer);
// the type info vtable pointer points to two pointers inside the vtable, adjust it back
type_info_vtable_pointer = static_cast<void*>(static_cast<void**>(type_info_vtable_pointer) - 2);
// for libstdc++ the class type info vtable looks like
// 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00
// [offset ][typeinfo pointer ]
// 0x7ffff7f89d28 <_ZTVN10__cxxabiv117__class_type_infoE+16>: 0x00007ffff7dd65a0 0x00007ffff7dd65c0
// [base destructor ][deleting dtor ]
// 0x7ffff7f89d38 <_ZTVN10__cxxabiv117__class_type_infoE+32>: 0x00007ffff7dd8f10 0x00007ffff7dd8f10
// [__is_pointer_p ][__is_function_p ]
// 0x7ffff7f89d48 <_ZTVN10__cxxabiv117__class_type_infoE+48>: 0x00007ffff7dd6640 0x00007ffff7dd6500
// [__do_catch ][__do_upcast ]
// 0x7ffff7f89d58 <_ZTVN10__cxxabiv117__class_type_infoE+64>: 0x00007ffff7dd65e0 0x00007ffff7dd66d0
// [__do_upcast ][__do_dyncast ]
// 0x7ffff7f89d68 <_ZTVN10__cxxabiv117__class_type_infoE+80>: 0x00007ffff7dd6580 0x00007ffff7f8abe8
// [__do_find_public_src][other ]
// In libc++ the layout is
// [offset ][typeinfo pointer ]
// [base destructor ][deleting dtor ]
// [noop1 ][noop2 ]
// [can_catch ][search_above_dst ]
// [search_below_dst ][has_unambiguous_public_base]
// Relevant documentation/implementation:
// https://itanium-cxx-abi.github.io/cxx-abi/abi.html
// libstdc++
// https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/typeinfo
// https://github.com/gcc-mirror/gcc/blob/b13e34699c7d27e561fcfe1b66ced1e50e69976f/libstdc%252B%252B-v3/libsupc%252B%252B/class_type_info.cc
// libc++
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxx/include/typeinfo
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxxabi/src/private_typeinfo.h
// shouldn't be anything other than 4096 but out of an abundance of caution
auto page_size = get_page_size();
if(page_size <= 0 && (page_size & (page_size - 1)) != 0) {
throw std::runtime_error(
microfmt::format("getpagesize() is not a power of 2 greater than zero (was {})", page_size)
);
}
// allocate a page for the new vtable so it can be made read-only later
// the OS cleans this up, no cleanup done here for it
void* new_vtable_page = allocate_page(page_size);
// make our own copy of the vtable
memcpy(new_vtable_page, type_info_vtable_pointer, vtable_size * sizeof(void*));
// ninja in the custom __do_catch interceptor
auto new_vtable = static_cast<void**>(new_vtable_page);
new_vtable[6] = reinterpret_cast<void*>(do_catch_function);
// make the page read-only
mprotect_page(new_vtable_page, page_size, memory_readonly);
// make the vtable pointer for unwind_interceptor's type_info point to the new vtable
auto type_info_addr = reinterpret_cast<uintptr_t>(type_info_pointer);
auto page_addr = type_info_addr & ~(page_size - 1);
// make sure the memory we're going to set is within the page
if(type_info_addr - page_addr + sizeof(void*) > static_cast<unsigned>(page_size)) {
throw std::runtime_error("pointer crosses page boundaries");
}
auto old_protections = mprotect_page_and_return_old_protections(
reinterpret_cast<void*>(page_addr),
page_size,
memory_readwrite
);
*static_cast<void**>(type_info_pointer) = static_cast<void*>(new_vtable + 2);
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, old_protections);
}
void do_prepare_unwind_interceptor(char(*intercept_unwind_handler)(std::size_t)) {
static bool did_prepare = false;
if(!did_prepare) {
cpptrace::detail::intercept_unwind_handler = intercept_unwind_handler;
try {
perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor), intercept_unwind);
perform_typeinfo_surgery(
typeid(cpptrace::detail::unconditional_unwind_interceptor),
unconditional_exception_unwind_interceptor
);
} catch(std::exception& e) {
std::fprintf(
stderr,
"Cpptrace: Exception occurred while preparing from_current support: %s\n",
e.what()
);
} catch(...) {
std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support\n");
}
did_prepare = true;
}
}
#endif
}
const raw_trace& raw_trace_from_current_exception() {
return detail::current_exception_trace.get_raw_trace();
}
const stacktrace& from_current_exception() {
return detail::current_exception_trace.get_resolved_trace();
}
}

641
Trace/src/mach-o.cpp Normal file
View File

@ -0,0 +1,641 @@
#include "binary/mach-o.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#if IS_APPLE
// A number of mach-o functions are deprecated as of macos 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <cstdio>
#include <cstring>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <vector>
#include <iostream>
#include <iomanip>
#include <mach-o/loader.h>
#include <mach-o/swap.h>
#include <mach-o/fat.h>
#include <crt_externs.h>
#include <mach-o/nlist.h>
#include <mach-o/stab.h>
#include <mach-o/arch.h>
namespace cpptrace {
namespace detail {
bool is_mach_o(std::uint32_t magic) {
switch(magic) {
case FAT_MAGIC:
case FAT_CIGAM:
case MH_MAGIC:
case MH_CIGAM:
case MH_MAGIC_64:
case MH_CIGAM_64:
return true;
default:
return false;
}
}
bool file_is_mach_o(const std::string& object_path) noexcept {
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
if(file == nullptr) {
return false;
}
auto magic = load_bytes<std::uint32_t>(file, 0);
if(magic) {
return is_mach_o(magic.unwrap_value());
} else {
return false;
}
}
bool is_fat_magic(std::uint32_t magic) {
return magic == FAT_MAGIC || magic == FAT_CIGAM;
}
// Based on https://github.com/AlexDenisov/segment_dumper/blob/master/main.c
// and https://lowlevelbits.org/parsing-mach-o-files/
bool is_magic_64(std::uint32_t magic) {
return magic == MH_MAGIC_64 || magic == MH_CIGAM_64;
}
bool should_swap_bytes(std::uint32_t magic) {
return magic == MH_CIGAM || magic == MH_CIGAM_64 || magic == FAT_CIGAM;
}
void swap_mach_header(mach_header_64& header) {
swap_mach_header_64(&header, NX_UnknownByteOrder);
}
void swap_mach_header(mach_header& header) {
swap_mach_header(&header, NX_UnknownByteOrder);
}
void swap_segment_command(segment_command_64& segment) {
swap_segment_command_64(&segment, NX_UnknownByteOrder);
}
void swap_segment_command(segment_command& segment) {
swap_segment_command(&segment, NX_UnknownByteOrder);
}
void swap_nlist(struct nlist& entry) {
swap_nlist(&entry, 1, NX_UnknownByteOrder);
}
void swap_nlist(struct nlist_64& entry) {
swap_nlist_64(&entry, 1, NX_UnknownByteOrder);
}
#ifdef __LP64__
#define LP(x) x##_64
#else
#define LP(x) x
#endif
Result<const char*, internal_error> mach_o::symtab_info_data::get_string(std::size_t index) const {
if(stringtab && index < symtab.strsize) {
return stringtab.get() + index;
} else {
return internal_error("can't retrieve symbol from symtab");
}
}
Result<monostate, internal_error> mach_o::load() {
if(magic == FAT_MAGIC || magic == FAT_CIGAM) {
return load_fat_mach();
} else {
fat_index = 0;
if(is_magic_64(magic)) {
return load_mach<64>();
} else {
return load_mach<32>();
}
}
}
Result<mach_o, internal_error> mach_o::open_mach_o(const std::string& object_path) {
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
if(file == nullptr) {
return internal_error("Unable to read object file {}", object_path);
}
auto magic = load_bytes<std::uint32_t>(file, 0);
if(!magic) {
return magic.unwrap_error();
}
if(!is_mach_o(magic.unwrap_value())) {
return internal_error("File is not mach-o {}", object_path);
}
mach_o obj(std::move(file), object_path, magic.unwrap_value());
auto result = obj.load();
if(result.is_error()) {
return result.unwrap_error();
} else {
return obj;
}
}
Result<std::uintptr_t, internal_error> mach_o::get_text_vmaddr() {
for(const auto& command : load_commands) {
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
auto segment = command.cmd == LC_SEGMENT_64
? load_segment_command<64>(command.file_offset)
: load_segment_command<32>(command.file_offset);
if(segment.is_error()) {
return std::move(segment).unwrap_error();
}
if(std::strcmp(segment.unwrap_value().segname, "__TEXT") == 0) {
return segment.unwrap_value().vmaddr;
}
}
}
// somehow no __TEXT section was found...
return internal_error("Couldn't find __TEXT section while parsing Mach-O object");
}
std::size_t mach_o::get_fat_index() const {
VERIFY(fat_index != std::numeric_limits<std::size_t>::max());
return fat_index;
}
void mach_o::print_segments() const {
int i = 0;
for(const auto& command : load_commands) {
if(command.cmd == LC_SEGMENT_64 || command.cmd == LC_SEGMENT) {
auto segment_load = command.cmd == LC_SEGMENT_64
? load_segment_command<64>(command.file_offset)
: load_segment_command<32>(command.file_offset);
fprintf(stderr, "Load command %d\n", i);
if(segment_load.is_error()) {
fprintf(stderr, " error\n");
segment_load.drop_error();
continue;
}
auto& segment = segment_load.unwrap_value();
fprintf(stderr, " cmd %u\n", segment.cmd);
fprintf(stderr, " cmdsize %u\n", segment.cmdsize);
fprintf(stderr, " segname %s\n", segment.segname);
fprintf(stderr, " vmaddr 0x%llx\n", segment.vmaddr);
fprintf(stderr, " vmsize 0x%llx\n", segment.vmsize);
fprintf(stderr, " off 0x%llx\n", segment.fileoff);
fprintf(stderr, " filesize %llu\n", segment.filesize);
fprintf(stderr, " nsects %u\n", segment.nsects);
}
i++;
}
}
Result<std::reference_wrapper<optional<mach_o::symtab_info_data>>, internal_error> mach_o::get_symtab_info() {
if(!symtab_info.has_value() && !tried_to_load_symtab) {
// don't try to load the symtab again if for some reason loading here fails
tried_to_load_symtab = true;
for(const auto& command : load_commands) {
if(command.cmd == LC_SYMTAB) {
symtab_info_data info;
auto symtab = load_symbol_table_command(command.file_offset);
if(!symtab) {
return std::move(symtab).unwrap_error();
}
info.symtab = symtab.unwrap_value();
auto string = load_string_table(info.symtab.stroff, info.symtab.strsize);
if(!string) {
return std::move(string).unwrap_error();
}
info.stringtab = std::move(string).unwrap_value();
symtab_info = std::move(info);
break;
}
}
}
return std::reference_wrapper<optional<symtab_info_data>>{symtab_info};
}
void mach_o::print_symbol_table_entry(
const nlist_64& entry,
const std::unique_ptr<char[]>& stringtab,
std::size_t stringsize,
std::size_t j
) const {
const char* type = "";
if(entry.n_type & N_STAB) {
switch(entry.n_type) {
case N_SO: type = "N_SO"; break;
case N_OSO: type = "N_OSO"; break;
case N_BNSYM: type = "N_BNSYM"; break;
case N_ENSYM: type = "N_ENSYM"; break;
case N_FUN: type = "N_FUN"; break;
}
} else if((entry.n_type & N_TYPE) == N_SECT) {
type = "N_SECT";
}
fprintf(
stderr,
"%5llu %8llx %2llx %7s %2llu %4llx %16llx %s\n",
to_ull(j),
to_ull(entry.n_un.n_strx),
to_ull(entry.n_type),
type,
to_ull(entry.n_sect),
to_ull(entry.n_desc),
to_ull(entry.n_value),
stringtab == nullptr
? "Stringtab error"
: entry.n_un.n_strx < stringsize
? stringtab.get() + entry.n_un.n_strx
: "String index out of bounds"
);
}
void mach_o::print_symbol_table() {
int i = 0;
for(const auto& command : load_commands) {
if(command.cmd == LC_SYMTAB) {
auto symtab_load = load_symbol_table_command(command.file_offset);
fprintf(stderr, "Load command %d\n", i);
if(symtab_load.is_error()) {
fprintf(stderr, " error\n");
symtab_load.drop_error();
continue;
}
auto& symtab = symtab_load.unwrap_value();
fprintf(stderr, " cmd %llu\n", to_ull(symtab.cmd));
fprintf(stderr, " cmdsize %llu\n", to_ull(symtab.cmdsize));
fprintf(stderr, " symoff 0x%llu\n", to_ull(symtab.symoff));
fprintf(stderr, " nsyms %llu\n", to_ull(symtab.nsyms));
fprintf(stderr, " stroff 0x%llu\n", to_ull(symtab.stroff));
fprintf(stderr, " strsize %llu\n", to_ull(symtab.strsize));
auto stringtab = load_string_table(symtab.stroff, symtab.strsize);
if(!stringtab) {
stringtab.drop_error();
}
for(std::size_t j = 0; j < symtab.nsyms; j++) {
auto entry = bits == 32
? load_symtab_entry<32>(symtab.symoff, j)
: load_symtab_entry<64>(symtab.symoff, j);
if(!entry) {
fprintf(stderr, "error loading symtab entry\n");
entry.drop_error();
continue;
}
print_symbol_table_entry(
entry.unwrap_value(),
std::move(stringtab).value_or(std::unique_ptr<char[]>(nullptr)),
symtab.strsize,
j
);
}
}
i++;
}
}
// produce information similar to dsymutil -dump-debug-map
Result<mach_o::debug_map, internal_error> mach_o::get_debug_map() {
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
// first collect symbols and the objects they come from
debug_map debug_map;
auto symtab_info_res = get_symtab_info();
if(!symtab_info_res) {
return std::move(symtab_info_res).unwrap_error();
}
if(!symtab_info_res.unwrap_value().get()) {
return internal_error("No symtab info");
}
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
const auto& symtab = symtab_info.symtab;
// TODO: Take timestamp into account?
std::string current_module;
optional<debug_map_entry> current_function;
for(std::size_t j = 0; j < symtab.nsyms; j++) {
auto load_entry = bits == 32
? load_symtab_entry<32>(symtab.symoff, j)
: load_symtab_entry<64>(symtab.symoff, j);
if(!load_entry) {
return std::move(load_entry).unwrap_error();
}
auto& entry = load_entry.unwrap_value();
// entry.n_type & N_STAB indicates symbolic debug info
if(!(entry.n_type & N_STAB)) {
continue;
}
switch(entry.n_type) {
case N_SO:
// pass - these encode path and filename for the module, if applicable
break;
case N_OSO:
{
// sets the module
auto str = symtab_info.get_string(entry.n_un.n_strx);
if(!str) {
return std::move(str).unwrap_error();
}
current_module = str.unwrap_value();
}
break;
case N_BNSYM: break; // pass
case N_ENSYM: break; // pass
case N_FUN:
{
auto str = symtab_info.get_string(entry.n_un.n_strx);
if(!str) {
return std::move(str).unwrap_error();
}
if(str.unwrap_value()[0] == 0) {
// end of function scope
if(!current_function) { /**/ }
current_function.unwrap().size = entry.n_value;
debug_map[current_module].push_back(std::move(current_function).unwrap());
} else {
current_function = debug_map_entry{};
current_function.unwrap().source_address = entry.n_value;
current_function.unwrap().name = str.unwrap_value();
}
}
break;
}
}
return debug_map;
}
Result<std::vector<mach_o::symbol_entry>, internal_error> mach_o::symbol_table() {
// we have a bunch of symbols in our binary we need to pair up with symbols from various .o files
// first collect symbols and the objects they come from
std::vector<symbol_entry> symbols;
auto symtab_info_res = get_symtab_info();
if(!symtab_info_res) {
return std::move(symtab_info_res).unwrap_error();
}
if(!symtab_info_res.unwrap_value().get()) {
return internal_error("No symtab info");
}
const auto& symtab_info = symtab_info_res.unwrap_value().get().unwrap();
const auto& symtab = symtab_info.symtab;
// TODO: Take timestamp into account?
for(std::size_t j = 0; j < symtab.nsyms; j++) {
auto load_entry = bits == 32
? load_symtab_entry<32>(symtab.symoff, j)
: load_symtab_entry<64>(symtab.symoff, j);
if(!load_entry) {
return std::move(load_entry).unwrap_error();
}
auto& entry = load_entry.unwrap_value();
if(entry.n_type & N_STAB) {
continue;
}
if((entry.n_type & N_TYPE) == N_SECT) {
auto str = symtab_info.get_string(entry.n_un.n_strx);
if(!str) {
return std::move(str).unwrap_error();
}
symbols.push_back({
entry.n_value,
str.unwrap_value()
});
}
}
return symbols;
}
// produce information similar to dsymutil -dump-debug-map
void mach_o::print_debug_map(const debug_map& debug_map) {
for(const auto& entry : debug_map) {
std::cout<<entry.first<<": "<< '\n';
for(const auto& symbol : entry.second) {
std::cerr
<< " "
<< symbol.name
<< " "
<< std::hex
<< symbol.source_address
<< " "
<< symbol.size
<< std::dec
<< '\n';
}
}
}
template<std::size_t Bits>
Result<monostate, internal_error> mach_o::load_mach() {
static_assert(Bits == 32 || Bits == 64, "Unexpected Bits argument");
bits = Bits;
using Mach_Header = typename std::conditional<Bits == 32, mach_header, mach_header_64>::type;
std::size_t header_size = sizeof(Mach_Header);
auto load_header = load_bytes<Mach_Header>(file, load_base);
if(!load_header) {
return load_header.unwrap_error();
}
Mach_Header& header = load_header.unwrap_value();
magic = header.magic;
if(should_swap()) {
swap_mach_header(header);
}
cputype = header.cputype;
cpusubtype = header.cpusubtype;
filetype = header.filetype;
n_load_commands = header.ncmds;
sizeof_load_commands = header.sizeofcmds;
flags = header.flags;
// handle load commands
std::uint32_t ncmds = header.ncmds;
std::uint32_t load_commands_offset = load_base + header_size;
// iterate load commands
std::uint32_t actual_offset = load_commands_offset;
for(std::uint32_t i = 0; i < ncmds; i++) {
auto load_cmd = load_bytes<load_command>(file, actual_offset);
if(!load_cmd) {
return load_cmd.unwrap_error();
}
load_command& cmd = load_cmd.unwrap_value();
if(should_swap()) {
swap_load_command(&cmd, NX_UnknownByteOrder);
}
load_commands.push_back({ actual_offset, cmd.cmd, cmd.cmdsize });
actual_offset += cmd.cmdsize;
}
return monostate{};
}
Result<monostate, internal_error> mach_o::load_fat_mach() {
std::size_t header_size = sizeof(fat_header);
std::size_t arch_size = sizeof(fat_arch);
auto load_header = load_bytes<fat_header>(file, 0);
if(!load_header) {
return load_header.unwrap_error();
}
fat_header& header = load_header.unwrap_value();
if(should_swap()) {
swap_fat_header(&header, NX_UnknownByteOrder);
}
// thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
// off_t arch_offset = (off_t)header_size;
// for(std::size_t i = 0; i < header.nfat_arch; i++) {
// fat_arch arch = load_bytes<fat_arch>(file, arch_offset);
// if(should_swap()) {
// swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
// }
// off_t mach_header_offset = (off_t)arch.offset;
// arch_offset += arch_size;
// std::uint32_t magic = load_bytes<std::uint32_t>(file, mach_header_offset);
// std::cerr<<"xxx: "<<arch.cputype<<" : "<<mhp->cputype<<std::endl;
// std::cerr<<" "<<arch.cpusubtype<<" : "<<static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK)<<std::endl;
// if(
// arch.cputype == mhp->cputype &&
// static_cast<cpu_subtype_t>(mhp->cpusubtype & ~CPU_SUBTYPE_MASK) == arch.cpusubtype
// ) {
// load_base = mach_header_offset;
// fat_index = i;
// if(is_magic_64(magic)) {
// load_mach<64>(true);
// } else {
// load_mach<32>(true);
// }
// return;
// }
// }
std::vector<fat_arch> fat_arches;
fat_arches.reserve(header.nfat_arch);
off_t arch_offset = (off_t)header_size;
for(std::size_t i = 0; i < header.nfat_arch; i++) {
auto load_arch = load_bytes<fat_arch>(file, arch_offset);
if(!load_arch) {
return load_arch.unwrap_error();
}
fat_arch& arch = load_arch.unwrap_value();
if(should_swap()) {
swap_fat_arch(&arch, 1, NX_UnknownByteOrder);
}
fat_arches.push_back(arch);
arch_offset += arch_size;
}
thread_local static struct LP(mach_header)* mhp = _NSGetMachExecuteHeader();
fat_arch* best = NXFindBestFatArch(
mhp->cputype,
mhp->cpusubtype,
fat_arches.data(),
header.nfat_arch
);
if(best) {
off_t mach_header_offset = (off_t)best->offset;
auto magic = load_bytes<std::uint32_t>(file, mach_header_offset);
if(!magic) {
return magic.unwrap_error();
}
load_base = mach_header_offset;
fat_index = best - fat_arches.data();
if(is_magic_64(magic.unwrap_value())) {
load_mach<64>();
} else {
load_mach<32>();
}
return monostate{};
}
// If this is reached... something went wrong. The cpu we're on wasn't found.
return internal_error("Couldn't find appropriate architecture in fat Mach-O");
}
template<std::size_t Bits>
Result<segment_command_64, internal_error> mach_o::load_segment_command(std::uint32_t offset) const {
using Segment_Command = typename std::conditional<Bits == 32, segment_command, segment_command_64>::type;
auto load_segment = load_bytes<Segment_Command>(file, offset);
if(!load_segment) {
return load_segment.unwrap_error();
}
Segment_Command& segment = load_segment.unwrap_value();
ASSERT(segment.cmd == LC_SEGMENT_64 || segment.cmd == LC_SEGMENT);
if(should_swap()) {
swap_segment_command(segment);
}
// fields match just u64 instead of u32
segment_command_64 common;
common.cmd = segment.cmd;
common.cmdsize = segment.cmdsize;
static_assert(sizeof common.segname == 16 && sizeof segment.segname == 16, "xx");
memcpy(common.segname, segment.segname, 16);
common.vmaddr = segment.vmaddr;
common.vmsize = segment.vmsize;
common.fileoff = segment.fileoff;
common.filesize = segment.filesize;
common.maxprot = segment.maxprot;
common.initprot = segment.initprot;
common.nsects = segment.nsects;
common.flags = segment.flags;
return common;
}
Result<symtab_command, internal_error> mach_o::load_symbol_table_command(std::uint32_t offset) const {
auto load_symtab = load_bytes<symtab_command>(file, offset);
if(!load_symtab) {
return load_symtab.unwrap_error();
}
symtab_command& symtab = load_symtab.unwrap_value();
ASSERT(symtab.cmd == LC_SYMTAB);
if(should_swap()) {
swap_symtab_command(&symtab, NX_UnknownByteOrder);
}
return symtab;
}
template<std::size_t Bits>
Result<nlist_64, internal_error> mach_o::load_symtab_entry(std::uint32_t symbol_base, std::size_t index) const {
using Nlist = typename std::conditional<Bits == 32, struct nlist, struct nlist_64>::type;
uint32_t offset = load_base + symbol_base + index * sizeof(Nlist);
auto load_entry = load_bytes<Nlist>(file, offset);
if(!load_entry) {
return load_entry.unwrap_error();
}
Nlist& entry = load_entry.unwrap_value();
if(should_swap()) {
swap_nlist(entry);
}
// fields match just u64 instead of u32
nlist_64 common;
common.n_un.n_strx = entry.n_un.n_strx;
common.n_type = entry.n_type;
common.n_sect = entry.n_sect;
common.n_desc = entry.n_desc;
common.n_value = entry.n_value;
return common;
}
Result<std::unique_ptr<char[]>, internal_error> mach_o::load_string_table(std::uint32_t offset, std::uint32_t byte_count) const {
std::unique_ptr<char[]> buffer(new char[byte_count + 1]);
if(std::fseek(file, load_base + offset, SEEK_SET) != 0) {
return internal_error("fseek error while loading mach-o symbol table");
}
if(std::fread(buffer.get(), sizeof(char), byte_count, file) != byte_count) {
return internal_error("fread error while loading mach-o symbol table");
}
buffer[byte_count] = 0; // just out of an abundance of caution
return buffer;
}
bool mach_o::should_swap() const {
return should_swap_bytes(magic);
}
Result<bool, internal_error> macho_is_fat(const std::string& object_path) {
auto file = raii_wrap(std::fopen(object_path.c_str(), "rb"), file_deleter);
if(file == nullptr) {
return internal_error("Unable to read object file {}", object_path);
}
auto magic = load_bytes<std::uint32_t>(file, 0);
if(!magic) {
return magic.unwrap_error();
} else {
return is_fat_magic(magic.unwrap_value());
}
}
}
}
#pragma GCC diagnostic pop
#endif

95
Trace/src/module_base.cpp Normal file
View File

@ -0,0 +1,95 @@
#include "binary/module_base.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#if IS_APPLE
#include "binary/mach-o.hpp"
#else
#include "binary/elf.hpp"
#endif
#elif IS_WINDOWS
#include <windows.h>
#include "binary/pe.hpp"
#endif
namespace cpptrace {
namespace detail {
#if IS_LINUX
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, std::uintptr_t> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = elf_get_module_image_base(object_path);
// TODO: Cache the error
if(base.is_error()) {
return std::move(base).unwrap_error();
}
cache.insert(it, {object_path, base.unwrap_value()});
return base;
} else {
return it->second;
}
}
#elif IS_APPLE
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
// We have to parse the Mach-O to find the offset of the text section.....
// I don't know how addresses are handled if there is more than one __TEXT load command. I'm assuming for
// now that there is only one, and I'm using only the first section entry within that load command.
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, std::uintptr_t> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto obj = mach_o::open_mach_o(object_path);
// TODO: Cache the error
if(!obj) {
return obj.unwrap_error();
}
auto base = obj.unwrap_value().get_text_vmaddr();
if(!base) {
return std::move(base).unwrap_error();
}
cache.insert(it, {object_path, base.unwrap_value()});
return base;
} else {
return it->second;
}
}
#else // Windows
Result<std::uintptr_t, internal_error> get_module_image_base(const std::string& object_path) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<std::string, std::uintptr_t> cache;
auto it = cache.find(object_path);
if(it == cache.end()) {
// arguably it'd be better to release the lock while computing this, but also arguably it's good to not
// have two threads try to do the same computation
auto base = pe_get_module_image_base(object_path);
// TODO: Cache the error
if(!base) {
return std::move(base).unwrap_error();
}
cache.insert(it, {object_path, base.unwrap_value()});
return base;
} else {
return it->second;
}
}
#endif
}
}

183
Trace/src/object.cpp Normal file
View File

@ -0,0 +1,183 @@
#include "binary/object.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "binary/module_base.hpp"
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#if IS_LINUX
#include <link.h> // needed for dladdr1's link_map info
#endif
#elif IS_WINDOWS
#include <windows.h>
#endif
namespace cpptrace {
namespace detail {
#if IS_LINUX || IS_APPLE
#if defined(CPPTRACE_HAS_DL_FIND_OBJECT) || defined(CPPTRACE_HAS_DLADDR1)
std::string resolve_l_name(const char* l_name) {
if(l_name != nullptr && l_name[0] != 0) {
return l_name;
} else {
// empty l_name, this means it's the currently running executable
// TODO: Caching and proper handling
char buffer[CPPTRACE_PATH_MAX + 1]{};
auto res = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX);
if(res == -1) {
return ""; // TODO
} else {
return buffer;
}
}
}
#endif
// dladdr queries are needed to get pre-ASLR addresses and targets to run symbol resolution on
// _dl_find_object is preferred if at all possible as it is much faster (added in glibc 2.35)
// dladdr1 is preferred if possible because it allows for a more accurate object path to be resolved (glibc 2.3.3)
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT // we don't even check for this on apple
object_frame get_frame_object_info(frame_ptr address) {
// Use _dl_find_object when we can, it's orders of magnitude faster
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
dl_find_object result;
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread safe
frame.object_path = resolve_l_name(result.dlfo_link_map->l_name);
frame.object_address = address - to_frame_ptr(result.dlfo_link_map->l_addr);
}
return frame;
}
#elif defined(CPPTRACE_HAS_DLADDR1)
object_frame get_frame_object_info(frame_ptr address) {
// https://github.com/bminor/glibc/blob/91695ee4598b39d181ab8df579b888a8863c4cab/elf/dl-addr.c#L26
Dl_info info;
link_map* link_map_info;
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
if(
// thread safe
dladdr1(reinterpret_cast<void*>(address), &info, reinterpret_cast<void**>(&link_map_info), RTLD_DL_LINKMAP)
) {
frame.object_path = resolve_l_name(link_map_info->l_name);
auto base = get_module_image_base(frame.object_path);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
base.drop_error();
}
}
return frame;
}
#else
// glibc dladdr may not return an accurate dli_fname as it uses argv[0] for addresses in the main executable
// https://github.com/bminor/glibc/blob/caed1f5c0b2e31b5f4e0f21fea4b2c9ecd3b5b30/elf/dl-addr.c#L33-L36
// macos doesn't have dladdr1 but its dli_fname behaves more sensibly, same with some other libc's like musl
object_frame get_frame_object_info(frame_ptr address) {
// reference: https://github.com/bminor/glibc/blob/master/debug/backtracesyms.c
Dl_info info;
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
if(dladdr(reinterpret_cast<void*>(address), &info)) { // thread safe
frame.object_path = info.dli_fname;
auto base = get_module_image_base(info.dli_fname);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(info.dli_fbase)
+ base.unwrap_value();
} else {
base.drop_error();
}
}
return frame;
}
#endif
#else
std::string get_module_name(HMODULE handle) {
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
static std::unordered_map<HMODULE, std::string> cache;
auto it = cache.find(handle);
if(it == cache.end()) {
char path[MAX_PATH];
if(GetModuleFileNameA(handle, path, sizeof(path))) {
cache.insert(it, {handle, path});
return path;
} else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
cache.insert(it, {handle, ""});
return "";
}
} else {
return it->second;
}
}
object_frame get_frame_object_info(frame_ptr address) {
object_frame frame;
frame.raw_address = address;
frame.object_address = 0;
HMODULE handle;
// Multithread safe as long as another thread doesn't come along and free the module
if(GetModuleHandleExA(
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
reinterpret_cast<const char*>(address),
&handle
)) {
frame.object_path = get_module_name(handle);
auto base = get_module_image_base(frame.object_path);
if(base.has_value()) {
frame.object_address = address
- reinterpret_cast<std::uintptr_t>(handle)
+ base.unwrap_value();
} else {
base.drop_error();
}
} else {
std::fprintf(stderr, "%s\n", std::system_error(GetLastError(), std::system_category()).what());
}
return frame;
}
#endif
std::vector<object_frame> get_frames_object_info(const std::vector<frame_ptr>& addresses) {
std::vector<object_frame> frames;
frames.reserve(addresses.size());
for(const frame_ptr address : addresses) {
frames.push_back(get_frame_object_info(address));
}
return frames;
}
object_frame resolve_safe_object_frame(const safe_object_frame& frame) {
std::string object_path = frame.object_path;
if(object_path.empty()) {
return {
frame.raw_address,
0,
""
};
}
auto base = get_module_image_base(frame.object_path);
if(base.is_error()) {
throw base.unwrap_error(); // This throw is intentional
}
return {
frame.raw_address,
frame.address_relative_to_object_start + base.unwrap_value(),
std::move(object_path)
};
}
}
}

95
Trace/src/pe.cpp Normal file
View File

@ -0,0 +1,95 @@
#include "binary/pe.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#if IS_WINDOWS
#include <array>
#include <cstddef>
#include <cstdio>
#include <cstring>
#include <string>
#include <windows.h>
namespace cpptrace {
namespace detail {
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T pe_byteswap_if_needed(T value) {
// PE header values are little endian, I think dos e_lfanew should be too
if(!is_little_endian()) {
return byteswap(value);
} else {
return value;
}
}
Result<std::uintptr_t, internal_error> pe_get_module_image_base(const std::string& object_path) {
// https://drive.google.com/file/d/0B3_wGJkuWLytbnIxY1J5WUs4MEk/view?pli=1&resourcekey=0-n5zZ2UW39xVTH8ZSu6C2aQ
// https://0xrick.github.io/win-internals/pe3/
// Endianness should always be little for dos and pe headers
std::FILE* file_ptr;
errno_t ret = fopen_s(&file_ptr, object_path.c_str(), "rb");
auto file = raii_wrap(std::move(file_ptr), file_deleter);
if(ret != 0 || file == nullptr) {
return internal_error("Unable to read object file {}", object_path);
}
auto magic = load_bytes<std::array<char, 2>>(file, 0);
if(!magic) {
return std::move(magic).unwrap_error();
}
if(std::memcmp(magic.unwrap_value().data(), "MZ", 2) != 0) {
return internal_error("File is not a PE file {}", object_path);
}
auto e_lfanew = load_bytes<DWORD>(file, 0x3c); // dos header + 0x3c
if(!e_lfanew) {
return std::move(e_lfanew).unwrap_error();
}
DWORD nt_header_offset = pe_byteswap_if_needed(e_lfanew.unwrap_value());
auto signature = load_bytes<std::array<char, 4>>(file, nt_header_offset); // nt header + 0
if(!signature) {
return std::move(signature).unwrap_error();
}
if(std::memcmp(signature.unwrap_value().data(), "PE\0\0", 4) != 0) {
return internal_error("File is not a PE file {}", object_path);
}
auto size_of_optional_header_raw = load_bytes<WORD>(file, nt_header_offset + 4 + 0x10); // file header + 0x10
if(!size_of_optional_header_raw) {
return std::move(size_of_optional_header_raw).unwrap_error();
}
WORD size_of_optional_header = pe_byteswap_if_needed(size_of_optional_header_raw.unwrap_value());
if(size_of_optional_header == 0) {
return internal_error("Unexpected optional header size for PE file");
}
auto optional_header_magic_raw = load_bytes<WORD>(file, nt_header_offset + 0x18); // optional header + 0x0
if(!optional_header_magic_raw) {
return std::move(optional_header_magic_raw).unwrap_error();
}
WORD optional_header_magic = pe_byteswap_if_needed(optional_header_magic_raw.unwrap_value());
VERIFY(
optional_header_magic == IMAGE_NT_OPTIONAL_HDR_MAGIC,
("PE file does not match expected bit-mode " + object_path).c_str()
);
// finally get image base
if(optional_header_magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) {
// 32 bit
auto bytes = load_bytes<DWORD>(file, nt_header_offset + 0x18 + 0x1c); // optional header + 0x1c
if(!bytes) {
return std::move(bytes).unwrap_error();
}
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
} else {
// 64 bit
// I get an "error: 'QWORD' was not declared in this scope" for some reason when using QWORD
auto bytes = load_bytes<unsigned __int64>(file, nt_header_offset + 0x18 + 0x18); // optional header + 0x18
if(!bytes) {
return std::move(bytes).unwrap_error();
}
return to<std::uintptr_t>(pe_byteswap_if_needed(bytes.unwrap_value()));
}
}
}
}
#endif

View File

@ -0,0 +1,43 @@
#ifndef DBGHELP_SYMINIT_MANAGER_HPP
#define DBGHELP_SYMINIT_MANAGER_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <unordered_set>
#include <windows.h>
#include <dbghelp.h>
namespace cpptrace {
namespace detail {
struct dbghelp_syminit_manager {
std::unordered_set<HANDLE> set;
~dbghelp_syminit_manager() {
for(auto handle : set) {
if(!SymCleanup(handle)) {
ASSERT(false, microfmt::format("Cpptrace SymCleanup failed with code {}\n", GetLastError()).c_str());
}
}
}
void init(HANDLE proc) {
if(set.count(proc) == 0) {
if(!SymInitialize(proc, NULL, TRUE)) {
throw internal_error("SymInitialize failed {}", GetLastError());
}
set.insert(proc);
}
}
};
// Thread-safety: Must only be called from symbols_with_dbghelp while the dbghelp_lock lock is held
inline dbghelp_syminit_manager& get_syminit_manager() {
static dbghelp_syminit_manager syminit_manager;
return syminit_manager;
}
}
}
#endif

View File

@ -0,0 +1,27 @@
#ifndef EXCEPTION_TYPE_HPP
#define EXCEPTION_TYPE_HPP
#include <string>
#include "platform/platform.hpp"
// libstdc++ and libc++
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
#include <cxxabi.h>
#include "demangle/demangle.hpp"
#endif
namespace cpptrace {
namespace detail {
inline std::string exception_type_name() {
#if defined(CPPTRACE_HAS_CXX_EXCEPTION_TYPE) && (IS_LIBSTDCXX || IS_LIBCXX)
const std::type_info* t = abi::__cxa_current_exception_type();
return t ? detail::demangle(t->name()) : "<unknown>";
#else
return "<unknown>";
#endif
}
}
}
#endif

View File

@ -0,0 +1,42 @@
#ifndef PATH_HPP
#define PATH_HPP
#include "utils/common.hpp"
#include "platform/platform.hpp"
#if IS_WINDOWS
#include <windows.h>
#endif
namespace cpptrace {
namespace detail {
#if IS_WINDOWS
constexpr char PATH_SEP = '\\';
inline bool is_absolute(const std::string& path) {
// I don't want to bring in shlwapi as a dependency just for PathIsRelativeA so I'm following the guidance of
// https://stackoverflow.com/a/71941552/15675011 and
// https://github.com/wine-mirror/wine/blob/b210a204137dec8d2126ca909d762454fd47e963/dlls/kernelbase/path.c#L982
if(path.empty() || IsDBCSLeadByte(path[0])) {
return false;
}
if(path[0] == '\\') {
return true;
}
if(path.size() >= 2 && std::isalpha(path[0]) && path[1] == ':') {
return true;
}
return false;
}
#else
constexpr char PATH_SEP = '/';
inline bool is_absolute(const std::string& path) {
if(path.empty()) {
return false;
}
return path[0] == '/';
}
#endif
}
}
#endif

View File

@ -0,0 +1,48 @@
#ifndef PLATFORM_HPP
#define PLATFORM_HPP
#define IS_WINDOWS 0
#define IS_LINUX 0
#define IS_APPLE 0
#if defined(_WIN32)
#undef IS_WINDOWS
#define IS_WINDOWS 1
#elif defined(__linux)
#undef IS_LINUX
#define IS_LINUX 1
#elif defined(__APPLE__)
#undef IS_APPLE
#define IS_APPLE 1
#else
#error "Unexpected platform"
#endif
#define IS_CLANG 0
#define IS_GCC 0
#define IS_MSVC 0
#if defined(__clang__)
#undef IS_CLANG
#define IS_CLANG 1
#elif defined(__GNUC__) || defined(__GNUG__)
#undef IS_GCC
#define IS_GCC 1
#elif defined(_MSC_VER)
#undef IS_MSVC
#define IS_MSVC 1
#else
#error "Unsupported compiler"
#endif
#define IS_LIBSTDCXX 0
#define IS_LIBCXX 0
#if defined(__GLIBCXX__) || defined(__GLIBCPP__)
#undef IS_LIBSTDCXX
#define IS_LIBSTDCXX 1
#elif defined(_LIBCPP_VERSION)
#undef IS_LIBCXX
#define IS_LIBCXX 1
#endif
#endif

View File

@ -0,0 +1,100 @@
#ifndef PROGRAM_NAME_HPP
#define PROGRAM_NAME_HPP
#include <mutex>
#include <string>
#include "platform/platform.hpp"
#if IS_WINDOWS
#include <windows.h>
#define CPPTRACE_MAX_PATH MAX_PATH
namespace cpptrace {
namespace detail {
inline std::string program_name() {
static std::mutex mutex;
const std::lock_guard<std::mutex> lock(mutex);
static std::string name;
static bool did_init = false;
static bool valid = false;
if(!did_init) {
did_init = true;
char buffer[MAX_PATH + 1];
int res = GetModuleFileNameA(nullptr, buffer, MAX_PATH);
if(res) {
name = buffer;
valid = true;
}
}
return valid && !name.empty() ? name.c_str() : nullptr;
}
}
}
#elif IS_APPLE
#include <cstdint>
#include <mach-o/dyld.h>
#include <sys/syslimits.h>
#define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX
namespace cpptrace {
namespace detail {
inline const char* program_name() {
static std::mutex mutex;
const std::lock_guard<std::mutex> lock(mutex);
static std::string name;
static bool did_init = false;
static bool valid = false;
if(!did_init) {
did_init = true;
char buffer[CPPTRACE_PATH_MAX + 1];
std::uint32_t bufferSize = sizeof buffer;
if(_NSGetExecutablePath(buffer, &bufferSize) == 0) {
name.assign(buffer, bufferSize);
valid = true;
}
}
return valid && !name.empty() ? name.c_str() : nullptr;
}
}
}
#elif IS_LINUX
#include <linux/limits.h>
#include <sys/types.h>
#include <unistd.h>
#define CPPTRACE_MAX_PATH CPPTRACE_PATH_MAX
namespace cpptrace {
namespace detail {
inline const char* program_name() {
static std::mutex mutex;
const std::lock_guard<std::mutex> lock(mutex);
static std::string name;
static bool did_init = false;
static bool valid = false;
if(!did_init) {
did_init = true;
char buffer[CPPTRACE_PATH_MAX + 1];
const ssize_t size = readlink("/proc/self/exe", buffer, CPPTRACE_PATH_MAX);
if(size == -1) {
return nullptr;
}
buffer[size] = 0;
name = buffer;
valid = true;
}
return valid && !name.empty() ? name.c_str() : nullptr;
}
}
}
#endif
#endif

68
Trace/src/safe_dl.cpp Normal file
View File

@ -0,0 +1,68 @@
#include "binary/safe_dl.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "platform/program_name.hpp"
#include <string>
#include <vector>
#include <mutex>
#include <unordered_map>
#include <cstring>
#include <iostream>
#ifdef CPPTRACE_HAS_DL_FIND_OBJECT
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <dlfcn.h>
#include <link.h>
#endif
namespace cpptrace {
namespace detail {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
out->raw_address = address;
dl_find_object result;
if(_dl_find_object(reinterpret_cast<void*>(address), &result) == 0) { // thread-safe, signal-safe
out->address_relative_to_object_start = address - to_frame_ptr(result.dlfo_link_map->l_addr);
if(result.dlfo_link_map->l_name != nullptr && result.dlfo_link_map->l_name[0] != 0) {
std::size_t path_length = std::strlen(result.dlfo_link_map->l_name);
std::memcpy(
out->object_path,
result.dlfo_link_map->l_name,
std::min(path_length + 1, std::size_t(CPPTRACE_PATH_MAX + 1))
);
} else {
// empty l_name, this means it's the currently running executable
memset(out->object_path, 0, CPPTRACE_PATH_MAX + 1);
// signal-safe
auto res = readlink("/proc/self/exe", out->object_path, CPPTRACE_PATH_MAX);
if(res == -1) {
// error handling?
}
// TODO: Special handling for /proc/pid/exe unlink edge case
}
} else {
// std::cout<<"error"<<std::endl;
out->address_relative_to_object_start = 0;
out->object_path[0] = 0;
}
// TODO: Handle this part of the documentation?
// The address can be a code address or data address. On architectures using function descriptors, no attempt is
// made to decode the function descriptor. Depending on how these descriptors are implemented, _dl_find_object
// may return the object that defines the function descriptor (and not the object that contains the code
// implementing the function), or fail to find any object at all.
}
}
}
#else
namespace cpptrace {
namespace detail {
void get_safe_object_frame(frame_ptr address, safe_object_frame* out) {
out->raw_address = address;
out->address_relative_to_object_start = 0;
out->object_path[0] = 0;
}
}
}
#endif

142
Trace/src/snippet.cpp Normal file
View File

@ -0,0 +1,142 @@
#include "snippets/snippet.hpp"
#include <algorithm>
#include <cstdint>
#include <mutex>
#include <unordered_map>
#include <vector>
#include <fstream>
#include <iostream>
#include "utils/common.hpp"
#include "utils/utils.hpp"
namespace cpptrace {
namespace detail {
constexpr std::int64_t max_size = 1024 * 1024 * 10; // 10 MiB
struct line_range {
std::size_t begin;
std::size_t end; // one past the end
};
class snippet_manager {
bool loaded_contents;
std::string contents;
// 1-based indexing
std::vector<line_range> line_table;
public:
snippet_manager(const std::string& path) : loaded_contents(false) {
std::ifstream file;
try {
file.open(path, std::ios::ate);
if(file.is_open()) {
std::ifstream::pos_type size = file.tellg();
if(size == std::ifstream::pos_type(-1) || size > max_size) {
return;
}
// else load file
file.seekg(0, std::ios::beg);
contents.resize(to<std::size_t>(size));
if(!file.read(&contents[0], size)) {
// error ...
}
build_line_table();
loaded_contents = true;
}
} catch(const std::ifstream::failure&) {
// ...
}
}
// takes a 1-index line
std::string get_line(std::size_t line) const {
if(!loaded_contents || line > line_table.size()) {
return "";
} else {
return contents.substr(line_table[line].begin, line_table[line].end - line_table[line].begin);
}
}
std::size_t num_lines() const {
return line_table.size();
}
bool ok() const {
return loaded_contents;
}
private:
void build_line_table() {
line_table.push_back({0, 0});
std::size_t pos = 0; // stores the start of the current line
while(true) {
// find the end of the current line
std::size_t terminator_pos = contents.find('\n', pos);
if(terminator_pos == std::string::npos) {
line_table.push_back({pos, contents.size()});
break;
} else {
std::size_t end_pos = terminator_pos; // one past the end of the current line
if(end_pos > 0 && contents[end_pos - 1] == '\r') {
end_pos--;
}
line_table.push_back({pos, end_pos});
pos = terminator_pos + 1;
}
}
}
};
std::mutex snippet_manager_mutex;
std::unordered_map<std::string, const snippet_manager> snippet_managers;
const snippet_manager& get_manager(const std::string& path) {
std::unique_lock<std::mutex> lock(snippet_manager_mutex);
auto it = snippet_managers.find(path);
if(it == snippet_managers.end()) {
return snippet_managers.insert({path, snippet_manager(path)}).first->second;
} else {
return it->second;
}
}
// how wide the margin for the line number should be
constexpr std::size_t margin_width = 8;
// 1-indexed line
std::string get_snippet(const std::string& path, std::size_t target_line, std::size_t context_size, bool color) {
const auto& manager = get_manager(path);
if(!manager.ok()) {
return "";
}
auto begin = target_line <= context_size + 1 ? 1 : target_line - context_size;
auto original_begin = begin;
auto end = std::min(target_line + context_size, manager.num_lines() - 1);
std::vector<std::string> lines;
for(auto line = begin; line <= end; line++) {
lines.push_back(manager.get_line(line));
}
// trim blank lines
while(begin < target_line && lines[begin - original_begin].empty()) {
begin++;
}
while(end > target_line && lines[end - original_begin].empty()) {
end--;
}
// make the snippet
std::string snippet;
for(auto line = begin; line <= end; line++) {
if(color && line == target_line) {
snippet += YELLOW;
}
auto line_str = std::to_string(line);
snippet += microfmt::format("{>{}}: ", margin_width, line_str);
if(color && line == target_line) {
snippet += RESET;
}
snippet += lines[line - original_begin] + "\n";
}
return snippet;
}
}
}

View File

@ -0,0 +1,14 @@
#ifndef SNIPPET_HPP
#define SNIPPET_HPP
#include <cstddef>
#include <string>
namespace cpptrace {
namespace detail {
// 1-indexed line
std::string get_snippet(const std::string& path, std::size_t line, std::size_t context_size, bool color);
}
}
#endif

View File

@ -0,0 +1,539 @@
#ifndef DWARF_HPP
#define DWARF_HPP
#include <cpptrace/cpptrace.hpp>
#include "utils/error.hpp"
#include "utils/utils.hpp"
#include <functional>
#include <stdexcept>
#include <type_traits>
#ifdef CPPTRACE_USE_NESTED_LIBDWARF_HEADER_PATH
#include <libdwarf/libdwarf.h>
#include <libdwarf/dwarf.h>
#else
#include <libdwarf.h>
#include <dwarf.h>
#endif
namespace cpptrace {
namespace detail {
namespace libdwarf {
static_assert(std::is_pointer<Dwarf_Die>::value, "Dwarf_Die not a pointer");
static_assert(std::is_pointer<Dwarf_Debug>::value, "Dwarf_Debug not a pointer");
using rangelist_entries = std::vector<std::pair<Dwarf_Addr, Dwarf_Addr>>;
[[noreturn]] inline void handle_dwarf_error(Dwarf_Debug dbg, Dwarf_Error error) {
Dwarf_Unsigned ev = dwarf_errno(error);
char* msg = dwarf_errmsg(error);
(void)dbg;
// dwarf_dealloc_error(dbg, error);
throw internal_error("dwarf error {} {}", ev, msg);
}
struct die_object {
Dwarf_Debug dbg = nullptr;
Dwarf_Die die = nullptr;
// Error handling helper
// For some reason R (*f)(Args..., void*)-style deduction isn't possible, seems like a bug in all compilers
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=56190
template<
typename... Args,
typename... Args2,
typename std::enable_if<
std::is_same<
decltype(
(void)std::declval<int(Args...)>()(std::forward<Args2>(std::declval<Args2>())..., nullptr)
),
void
>::value,
int
>::type = 0
>
int wrap(int (*f)(Args...), Args2&&... args) const {
Dwarf_Error error = nullptr;
int ret = f(std::forward<Args2>(args)..., &error);
if(ret == DW_DLV_ERROR) {
handle_dwarf_error(dbg, error);
}
return ret;
}
die_object(Dwarf_Debug dbg, Dwarf_Die die) : dbg(dbg), die(die) {
ASSERT(dbg != nullptr);
}
~die_object() {
if(die) {
dwarf_dealloc_die(die);
}
}
die_object(const die_object&) = delete;
die_object& operator=(const die_object&) = delete;
die_object(die_object&& other) noexcept : dbg(other.dbg), die(other.die) {
// done for finding mistakes, attempts to use the die_object after this should segfault
// a valid use otherwise would be moved_from.get_sibling() which would get the next CU
other.dbg = nullptr;
other.die = nullptr;
}
die_object& operator=(die_object&& other) noexcept {
std::swap(dbg, other.dbg);
std::swap(die, other.die);
return *this;
}
die_object clone() const {
Dwarf_Off global_offset = get_global_offset();
Dwarf_Bool is_info = dwarf_get_die_infotypes_flag(die);
Dwarf_Die die_copy = nullptr;
VERIFY(wrap(dwarf_offdie_b, dbg, global_offset, is_info, &die_copy) == DW_DLV_OK);
return {dbg, die_copy};
}
die_object get_child() const {
Dwarf_Die child = nullptr;
int ret = wrap(dwarf_child, die, &child);
if(ret == DW_DLV_OK) {
return die_object(dbg, child);
} else if(ret == DW_DLV_NO_ENTRY) {
return die_object(dbg, nullptr);
} else {
PANIC();
}
}
die_object get_sibling() const {
Dwarf_Die sibling = nullptr;
int ret = wrap(dwarf_siblingof_b, dbg, die, true, &sibling);
if(ret == DW_DLV_OK) {
return die_object(dbg, sibling);
} else if(ret == DW_DLV_NO_ENTRY) {
return die_object(dbg, nullptr);
} else {
PANIC();
}
}
operator bool() const {
return die != nullptr;
}
Dwarf_Die get() const {
return die;
}
std::string get_name() const {
char empty[] = "";
char* name = empty;
int ret = wrap(dwarf_diename, die, &name);
auto wrapper = raii_wrap(name, [this] (char* str) { dwarf_dealloc(dbg, str, DW_DLA_STRING); });
std::string str;
if(ret != DW_DLV_NO_ENTRY) {
str = name;
}
return name;
}
optional<std::string> get_string_attribute(Dwarf_Half attr_num) const {
Dwarf_Attribute attr;
if(wrap(dwarf_attr, die, attr_num, &attr) == DW_DLV_OK) {
auto attwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
char* raw_str;
VERIFY(wrap(dwarf_formstring, attr, &raw_str) == DW_DLV_OK);
auto strwrapper = raii_wrap(raw_str, [this] (char* str) { dwarf_dealloc(dbg, str, DW_DLA_STRING); });
std::string str = raw_str;
return str;
} else {
return nullopt;
}
}
optional<Dwarf_Unsigned> get_unsigned_attribute(Dwarf_Half attr_num) const {
Dwarf_Attribute attr;
if(wrap(dwarf_attr, die, attr_num, &attr) == DW_DLV_OK) {
auto attwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
// Dwarf_Half form = 0;
// VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
Dwarf_Unsigned val;
VERIFY(wrap(dwarf_formudata, attr, &val) == DW_DLV_OK);
return val;
} else {
return nullopt;
}
}
bool has_attr(Dwarf_Half attr_num) const {
Dwarf_Bool present = false;
VERIFY(wrap(dwarf_hasattr, die, attr_num, &present) == DW_DLV_OK);
return present;
}
Dwarf_Half get_tag() const {
Dwarf_Half tag = 0;
VERIFY(wrap(dwarf_tag, die, &tag) == DW_DLV_OK);
return tag;
}
const char* get_tag_name() const {
const char* tag_name;
if(dwarf_get_TAG_name(get_tag(), &tag_name) == DW_DLV_OK) {
return tag_name;
} else {
return "<unknown tag name>";
}
}
Dwarf_Off get_global_offset() const {
Dwarf_Off off;
VERIFY(wrap(dwarf_dieoffset, die, &off) == DW_DLV_OK);
return off;
}
die_object resolve_reference_attribute(Dwarf_Half attr_num) const {
Dwarf_Attribute attr;
VERIFY(dwarf_attr(die, attr_num, &attr, nullptr) == DW_DLV_OK);
auto wrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
Dwarf_Half form = 0;
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
switch(form) {
case DW_FORM_ref1:
case DW_FORM_ref2:
case DW_FORM_ref4:
case DW_FORM_ref8:
case DW_FORM_ref_udata:
{
Dwarf_Off off = 0;
Dwarf_Bool is_info = dwarf_get_die_infotypes_flag(die);
VERIFY(wrap(dwarf_formref, attr, &off, &is_info) == DW_DLV_OK);
Dwarf_Off global_offset = 0;
VERIFY(wrap(dwarf_convert_to_global_offset, attr, off, &global_offset) == DW_DLV_OK);
Dwarf_Die target = nullptr;
VERIFY(wrap(dwarf_offdie_b, dbg, global_offset, is_info, &target) == DW_DLV_OK);
return die_object(dbg, target);
}
case DW_FORM_ref_addr:
{
Dwarf_Off off;
VERIFY(wrap(dwarf_global_formref, attr, &off) == DW_DLV_OK);
int is_info = dwarf_get_die_infotypes_flag(die);
Dwarf_Die target = nullptr;
VERIFY(wrap(dwarf_offdie_b, dbg, off, is_info, &target) == DW_DLV_OK);
return die_object(dbg, target);
}
case DW_FORM_ref_sig8:
{
Dwarf_Sig8 signature;
VERIFY(wrap(dwarf_formsig8, attr, &signature) == DW_DLV_OK);
Dwarf_Die target = nullptr;
Dwarf_Bool targ_is_info = false;
VERIFY(wrap(dwarf_find_die_given_sig8, dbg, &signature, &target, &targ_is_info) == DW_DLV_OK);
return die_object(dbg, target);
}
default:
PANIC(microfmt::format("unknown form for attribute {} {}\n", attr_num, form));
}
}
Dwarf_Unsigned get_ranges_base_address(const die_object& cu_die) const {
// After libdwarf v0.11.0 this can use dwarf_get_ranges_baseaddress, however, in the interest of not
// requiring v0.11.0 just yet the logic is implemented here too.
// The base address is:
// - If the die has a rangelist, use the low_pc for that die
// - Otherwise use the low_pc from the CU if present
// - Otherwise 0
if(has_attr(DW_AT_ranges)) {
if(has_attr(DW_AT_low_pc)) {
Dwarf_Addr lowpc;
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
return lowpc;
}
}
}
if(cu_die.has_attr(DW_AT_low_pc)) {
Dwarf_Addr lowpc;
if(wrap(dwarf_lowpc, cu_die.get(), &lowpc) == DW_DLV_OK) {
return lowpc;
}
}
return 0;
}
Dwarf_Unsigned get_ranges_offset(Dwarf_Attribute attr) const {
Dwarf_Unsigned off = 0;
Dwarf_Half form = 0;
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
if (form == DW_FORM_rnglistx) {
VERIFY(wrap(dwarf_formudata, attr, &off) == DW_DLV_OK);
} else {
VERIFY(wrap(dwarf_global_formref, attr, &off) == DW_DLV_OK);
}
return off;
}
template<typename F>
// callback should return true to keep going
void dwarf5_ranges(F callback) const {
Dwarf_Attribute attr = nullptr;
if(wrap(dwarf_attr, die, DW_AT_ranges, &attr) != DW_DLV_OK) {
return;
}
auto attrwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
Dwarf_Unsigned offset = get_ranges_offset(attr);
Dwarf_Half form = 0;
VERIFY(wrap(dwarf_whatform, attr, &form) == DW_DLV_OK);
// get .debug_rnglists info
Dwarf_Rnglists_Head head = nullptr;
Dwarf_Unsigned rnglists_entries = 0;
Dwarf_Unsigned dw_global_offset_of_rle_set = 0;
int res = wrap(
dwarf_rnglists_get_rle_head,
attr,
form,
offset,
&head,
&rnglists_entries,
&dw_global_offset_of_rle_set
);
auto headwrapper = raii_wrap(head, [] (Dwarf_Rnglists_Head head) { dwarf_dealloc_rnglists_head(head); });
if(res == DW_DLV_NO_ENTRY) {
return;
}
VERIFY(res == DW_DLV_OK);
for(std::size_t i = 0 ; i < rnglists_entries; i++) {
unsigned entrylen = 0;
unsigned rle_value_out = 0;
Dwarf_Unsigned raw1 = 0;
Dwarf_Unsigned raw2 = 0;
Dwarf_Bool unavailable = 0;
Dwarf_Unsigned cooked1 = 0;
Dwarf_Unsigned cooked2 = 0;
res = wrap(
dwarf_get_rnglists_entry_fields_a,
head,
i,
&entrylen,
&rle_value_out,
&raw1,
&raw2,
&unavailable,
&cooked1,
&cooked2
);
if(res == DW_DLV_NO_ENTRY) {
continue;
}
VERIFY(res == DW_DLV_OK);
if(unavailable) {
continue;
}
switch(rle_value_out) {
// Following the same scheme from libdwarf-addr2line
case DW_RLE_end_of_list:
case DW_RLE_base_address:
case DW_RLE_base_addressx:
// Already handled
break;
case DW_RLE_offset_pair:
case DW_RLE_startx_endx:
case DW_RLE_start_end:
case DW_RLE_startx_length:
case DW_RLE_start_length:
if(!callback(cooked1, cooked2)) {
return;
}
break;
default:
PANIC("Something is wrong");
break;
}
}
}
template<typename F>
// callback should return true to keep going
void dwarf4_ranges(Dwarf_Addr baseaddr, F callback) const {
Dwarf_Attribute attr = nullptr;
if(wrap(dwarf_attr, die, DW_AT_ranges, &attr) != DW_DLV_OK) {
return;
}
auto attrwrapper = raii_wrap(attr, [] (Dwarf_Attribute attr) { dwarf_dealloc_attribute(attr); });
Dwarf_Unsigned offset;
if(wrap(dwarf_global_formref, attr, &offset) != DW_DLV_OK) {
return;
}
Dwarf_Addr baseaddr_original = baseaddr;
Dwarf_Ranges* ranges = nullptr;
Dwarf_Signed count = 0;
VERIFY(
wrap(
dwarf_get_ranges_b,
dbg,
offset,
die,
nullptr,
&ranges,
&count,
nullptr
) == DW_DLV_OK
);
auto rangeswrapper = raii_wrap(
ranges,
[this, count] (Dwarf_Ranges* ranges) { dwarf_dealloc_ranges(dbg, ranges, count); }
);
for(int i = 0; i < count; i++) {
if(ranges[i].dwr_type == DW_RANGES_ENTRY) {
if(!callback(baseaddr + ranges[i].dwr_addr1, baseaddr + ranges[i].dwr_addr2)) {
return;
}
} else if(ranges[i].dwr_type == DW_RANGES_ADDRESS_SELECTION) {
baseaddr = ranges[i].dwr_addr2;
} else {
ASSERT(ranges[i].dwr_type == DW_RANGES_END);
baseaddr = baseaddr_original;
}
}
}
template<typename F>
// callback should return true to keep going
void dwarf_ranges(const die_object& cu_die, int version, F callback) const {
Dwarf_Addr lowpc;
if(wrap(dwarf_lowpc, die, &lowpc) == DW_DLV_OK) {
Dwarf_Addr highpc = 0;
enum Dwarf_Form_Class return_class;
if(wrap(dwarf_highpc_b, die, &highpc, nullptr, &return_class) == DW_DLV_OK) {
if(return_class == DW_FORM_CLASS_CONSTANT) {
highpc += lowpc;
}
if(!callback(lowpc, highpc)) {
return;
}
}
}
if(version >= 5) {
dwarf5_ranges(callback);
} else {
dwarf4_ranges(get_ranges_base_address(cu_die), callback);
}
}
rangelist_entries get_rangelist_entries(const die_object& cu_die, int version) const {
rangelist_entries vec;
dwarf_ranges(cu_die, version, [&vec] (Dwarf_Addr low, Dwarf_Addr high) {
// Simple coalescing optimization:
// Sometimes the range list entries are really continuous: [100, 200), [200, 300)
// Other times there's just one byte of separation [300, 399), [400, 500)
// Those are the main two cases I've observed.
// This will not catch all cases, presumably, as the range lists aren't sorted. But compilers/linkers
// seem to like to emit the ranges in sorted order.
if(!vec.empty() && low - vec.back().second <= 1) {
vec.back().second = high;
} else {
vec.push_back({low, high});
}
return true;
});
return vec;
}
Dwarf_Bool pc_in_die(const die_object& cu_die, int version, Dwarf_Addr pc) const {
bool found = false;
dwarf_ranges(cu_die, version, [&found, pc] (Dwarf_Addr low, Dwarf_Addr high) {
if(pc >= low && pc < high) {
found = true;
return false;
}
return true;
});
return found;
}
void print() const {
std::fprintf(
stderr,
"%08llx %s %s\n",
to_ull(get_global_offset()),
get_tag_name(),
get_name().c_str()
);
}
};
// walk die list, callback is called on each die and should return true to
// continue traversal
// returns true if traversal should continue
inline bool walk_die_list(
const die_object& die,
const std::function<bool(const die_object&)>& fn
) {
// TODO: Refactor so there is only one fn call
bool continue_traversal = true;
if(fn(die)) {
die_object current = die.get_sibling();
while(current) {
if(fn(current)) {
current = current.get_sibling();
} else {
continue_traversal = false;
break;
}
}
}
return continue_traversal;
}
// walk die list, recursing into children, callback is called on each die
// and should return true to continue traversal
// returns true if traversal should continue
inline bool walk_die_list_recursive(
const die_object& die,
const std::function<bool(const die_object&)>& fn
) {
return walk_die_list(
die,
[&fn](const die_object& die) {
auto child = die.get_child();
if(child) {
if(!walk_die_list_recursive(child, fn)) {
return false;
}
}
return fn(die);
}
);
}
class maybe_owned_die_object {
// Hacky... I wish std::variant existed.
optional<die_object> owned_die;
optional<const die_object*> ref_die;
maybe_owned_die_object(die_object&& die) : owned_die(std::move(die)) {}
maybe_owned_die_object(const die_object& die) : ref_die(&die) {}
public:
static maybe_owned_die_object owned(die_object&& die) {
return maybe_owned_die_object{std::move(die)};
}
static maybe_owned_die_object ref(const die_object& die) {
return maybe_owned_die_object{die};
}
const die_object& get() {
ASSERT(owned_die || ref_die, "Mal-formed maybe_owned_die_object");
if(owned_die) {
return owned_die.unwrap();
} else {
return *ref_die.unwrap();
}
}
};
}
}
}
#endif

View File

@ -0,0 +1,56 @@
#ifndef SYMBOL_RESOLVER_HPP
#define SYMBOL_RESOLVER_HPP
#include <cpptrace/cpptrace.hpp>
#include "symbols/symbols.hpp"
#include "utils/common.hpp"
#include <memory>
#if false
#define CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING CPPTRACE_FORCE_NO_INLINE
#else
#define CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
#endif
namespace cpptrace {
namespace detail {
namespace libdwarf {
class symbol_resolver {
public:
virtual ~symbol_resolver() = default;
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
virtual frame_with_inlines resolve_frame(const object_frame& frame_info) = 0;
};
class null_resolver : public symbol_resolver {
public:
null_resolver() = default;
null_resolver(const std::string&) {}
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
frame_with_inlines resolve_frame(const object_frame& frame_info) override {
return {
{
frame_info.raw_address,
frame_info.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
frame_info.object_path,
"",
false
},
{}
};
};
};
std::unique_ptr<symbol_resolver> make_dwarf_resolver(const std::string& object_path);
#if IS_APPLE
std::unique_ptr<symbol_resolver> make_debug_map_resolver(const std::string& object_path);
#endif
}
}
}
#endif

View File

@ -0,0 +1,72 @@
#ifndef SYMBOLS_HPP
#define SYMBOLS_HPP
#include <cpptrace/cpptrace.hpp>
#include <memory>
#include <vector>
#include <unordered_map>
#include "binary/object.hpp"
namespace cpptrace {
namespace detail {
using collated_vec = std::vector<
std::pair<std::reference_wrapper<const object_frame>, std::reference_wrapper<stacktrace_frame>>
>;
struct frame_with_inlines {
stacktrace_frame frame;
std::vector<stacktrace_frame> inlines;
};
using collated_vec_with_inlines = std::vector<
std::pair<std::reference_wrapper<const object_frame>, std::reference_wrapper<frame_with_inlines>>
>;
// These two helpers create a map from a target object to a vector of frames to resolve
std::unordered_map<std::string, collated_vec> collate_frames(
const std::vector<object_frame>& frames,
std::vector<stacktrace_frame>& trace
);
std::unordered_map<std::string, collated_vec_with_inlines> collate_frames(
const std::vector<object_frame>& frames,
std::vector<frame_with_inlines>& trace
);
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
namespace libbacktrace {
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
namespace libdwarf {
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
namespace libdl {
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
namespace addr2line {
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
namespace dbghelp {
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
namespace nothing {
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
}
#endif
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames);
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames);
}
}
#endif

151
Trace/src/symbols_core.cpp Normal file
View File

@ -0,0 +1,151 @@
#include "symbols/symbols.hpp"
#include <vector>
#include <unordered_map>
#include "utils/common.hpp"
#include "binary/object.hpp"
namespace cpptrace {
namespace detail {
template<typename CollatedVec, typename Entry>
std::unordered_map<std::string, CollatedVec> collate_frames(
const std::vector<object_frame>& frames,
std::vector<Entry>& trace
) {
std::unordered_map<std::string, CollatedVec> entries;
for(std::size_t i = 0; i < frames.size(); i++) {
const auto& entry = frames[i];
// If libdl fails to find the shared object for a frame, the path will be empty. I've observed this
// on macos when looking up the shared object containing `start`.
if(!entry.object_path.empty()) {
entries[entry.object_path].emplace_back(
entry,
trace[i]
);
}
}
return entries;
}
std::unordered_map<std::string, collated_vec> collate_frames(
const std::vector<object_frame>& frames,
std::vector<stacktrace_frame>& trace
) {
return collate_frames<collated_vec>(frames, trace);
}
std::unordered_map<std::string, collated_vec_with_inlines> collate_frames(
const std::vector<object_frame>& frames,
std::vector<frame_with_inlines>& trace
) {
return collate_frames<collated_vec_with_inlines>(frames, trace);
}
/*
*
*
* All the code here is awful and I'm not proud of it.
*
*
*
*/
// Resolver must not support walking inlines
void fill_blanks(
std::vector<stacktrace_frame>& vec,
std::vector<stacktrace_frame> (*resolver)(const std::vector<frame_ptr>&)
) {
std::vector<frame_ptr> addresses;
for(const auto& frame : vec) {
if(frame.symbol.empty() || frame.filename.empty()) {
addresses.push_back(frame.raw_address);
}
}
std::vector<stacktrace_frame> new_frames = resolver(addresses);
std::size_t i = 0;
for(auto& frame : vec) {
if(frame.symbol.empty() || frame.filename.empty()) {
// three cases to handle, either partially overwrite or fully overwrite
if(frame.symbol.empty() && frame.filename.empty()) {
frame = new_frames[i];
} else if(frame.symbol.empty() && !frame.filename.empty()) {
frame.symbol = new_frames[i].symbol;
} else {
ASSERT(!frame.symbol.empty() && frame.filename.empty());
frame.filename = new_frames[i].filename;
frame.line = new_frames[i].line;
frame.column = new_frames[i].column;
}
i++;
}
}
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(frames);
fill_blanks(trace, dbghelp::resolve_frames);
return trace;
#else
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDL) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
// actually need to go backwards to a void*
std::vector<frame_ptr> raw_frames(frames.size());
for(std::size_t i = 0; i < frames.size(); i++) {
raw_frames[i] = frames[i].raw_address;
}
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
return libdl::resolve_frames(raw_frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
return libdwarf::resolve_frames(frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
return dbghelp::resolve_frames(raw_frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
return addr2line::resolve_frames(frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
return libbacktrace::resolve_frames(raw_frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
return nothing::resolve_frames(frames);
#endif
#endif
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) \
|| defined(CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE)
auto dlframes = get_frames_object_info(frames);
#endif
#if defined(CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF) && defined(CPPTRACE_GET_SYMBOLS_WITH_DBGHELP)
std::vector<stacktrace_frame> trace = libdwarf::resolve_frames(dlframes);
fill_blanks(trace, dbghelp::resolve_frames);
return trace;
#else
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
return libdl::resolve_frames(frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
return libdwarf::resolve_frames(dlframes);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
return dbghelp::resolve_frames(frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
return addr2line::resolve_frames(dlframes);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
return libbacktrace::resolve_frames(frames);
#endif
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
return nothing::resolve_frames(frames);
#endif
#endif
}
}
}

View File

@ -0,0 +1,322 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE
#include <cpptrace/cpptrace.hpp>
#include "symbols/symbols.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <cstdint>
#include <cstdio>
#include <functional>
#include <mutex>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#if IS_LINUX || IS_APPLE
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#endif
#include "binary/object.hpp"
namespace cpptrace {
namespace detail {
namespace addr2line {
#if IS_LINUX || IS_APPLE
bool has_addr2line() {
static std::mutex mutex;
static bool has_addr2line = false;
static bool checked = false;
std::lock_guard<std::mutex> lock(mutex);
if(!checked) {
checked = true;
// Detects if addr2line exists by trying to invoke addr2line --help
constexpr int magic = 42;
const pid_t pid = fork();
if(pid == -1) { return false; }
if(pid == 0) { // child
close(STDOUT_FILENO);
close(STDERR_FILENO); // atos --help writes to stderr
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
#if !IS_APPLE
execlp("addr2line", "addr2line", "--help", nullptr);
#else
execlp("atos", "atos", "--help", nullptr);
#endif
#else
#ifndef CPPTRACE_ADDR2LINE_PATH
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
#endif
execl(CPPTRACE_ADDR2LINE_PATH, CPPTRACE_ADDR2LINE_PATH, "--help", nullptr);
#endif
_exit(magic);
}
int status;
waitpid(pid, &status, 0);
has_addr2line = WEXITSTATUS(status) == 0;
}
return has_addr2line;
}
struct pipe_ends {
int read;
int write;
};
struct pipe_t {
union {
pipe_ends end;
int data[2];
};
};
static_assert(sizeof(pipe_t) == 2 * sizeof(int), "Unexpected struct packing");
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
pipe_t output_pipe;
pipe_t input_pipe;
VERIFY(pipe(output_pipe.data) == 0);
VERIFY(pipe(input_pipe.data) == 0);
const pid_t pid = fork();
if(pid == -1) { return ""; } // error? TODO: Diagnostic
if(pid == 0) { // child
dup2(output_pipe.end.write, STDOUT_FILENO);
dup2(input_pipe.end.read, STDIN_FILENO);
close(output_pipe.end.read);
close(output_pipe.end.write);
close(input_pipe.end.read);
close(input_pipe.end.write);
close(STDERR_FILENO); // TODO: Might be worth conditionally enabling or piping
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
#if !IS_APPLE
execlp("addr2line", "addr2line", "-e", executable.c_str(), "-f", "-C", "-p", nullptr);
#else
execlp("atos", "atos", "-o", executable.c_str(), "-fullPath", nullptr);
#endif
#else
#ifndef CPPTRACE_ADDR2LINE_PATH
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
#endif
#if !IS_APPLE
execl(
CPPTRACE_ADDR2LINE_PATH,
CPPTRACE_ADDR2LINE_PATH,
"-e",
executable.c_str(),
"-f",
"-C",
"-p",
nullptr
);
#else
execl(
CPPTRACE_ADDR2LINE_PATH,
CPPTRACE_ADDR2LINE_PATH,
"-o", executable.c_str(),
"-fullPath",
nullptr
);
#endif
#endif
_exit(1); // TODO: Diagnostic?
}
VERIFY(write(input_pipe.end.write, addresses.data(), addresses.size()) != -1);
close(input_pipe.end.read);
close(input_pipe.end.write);
close(output_pipe.end.write);
std::string output;
constexpr int buffer_size = 4096;
char buffer[buffer_size];
std::size_t count = 0;
while((count = read(output_pipe.end.read, buffer, buffer_size)) > 0) {
output.insert(output.end(), buffer, buffer + count);
}
// TODO: check status from addr2line?
waitpid(pid, nullptr, 0);
return output;
}
#elif IS_WINDOWS
bool has_addr2line() {
static std::mutex mutex;
static bool has_addr2line = false;
static bool checked = false;
std::lock_guard<std::mutex> lock(mutex);
if(!checked) {
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
checked = true;
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
std::FILE* p = popen("addr2line --version", "r");
#else
#ifndef CPPTRACE_ADDR2LINE_PATH
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
#endif
std::FILE* p = popen(CPPTRACE_ADDR2LINE_PATH " --version", "r");
#endif
if(p) {
has_addr2line = pclose(p) == 0;
}
}
return has_addr2line;
}
std::string resolve_addresses(const std::string& addresses, const std::string& executable) {
// TODO: Popen is a hack. Implement properly with CreateProcess and pipes later.
///fprintf(stderr, ("addr2line -e " + executable + " -fCp " + addresses + "\n").c_str());
#ifdef CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH
std::FILE* p = popen(("addr2line -e \"" + executable + "\" -fCp " + addresses).c_str(), "r");
#else
#ifndef CPPTRACE_ADDR2LINE_PATH
#error "CPPTRACE_ADDR2LINE_PATH must be defined if CPPTRACE_ADDR2LINE_SEARCH_SYSTEM_PATH is not"
#endif
std::FILE* p = popen(
(CPPTRACE_ADDR2LINE_PATH " -e \"" + executable + "\" -fCp " + addresses).c_str(),
"r"
);
#endif
std::string output;
constexpr int buffer_size = 4096;
char buffer[buffer_size];
std::size_t count = 0;
while((count = std::fread(buffer, 1, buffer_size, p)) > 0) {
output.insert(output.end(), buffer, buffer + count);
}
pclose(p);
///fprintf(stderr, "%s\n", output.c_str());
return output;
}
#endif
void update_trace(const std::string& line, std::size_t entry_index, const collated_vec& entries_vec) {
#if !IS_APPLE
// Result will be of the form "<symbol> at path:line"
// The path may be ?? if addr2line cannot resolve, line may be ?
// Edge cases:
// ?? ??:0
// symbol :?
const std::size_t at_location = line.find(" at ");
std::size_t symbol_end;
std::size_t filename_start;
if(at_location != std::string::npos) {
symbol_end = at_location;
filename_start = at_location + 4;
} else {
VERIFY(line.find("?? ") == 0, "Unexpected edge case while processing addr2line output");
symbol_end = 2;
filename_start = 3;
}
auto symbol = line.substr(0, symbol_end);
auto colon = line.rfind(':');
VERIFY(colon != std::string::npos);
VERIFY(colon >= filename_start); // :? to deal with "symbol :?" edge case
auto filename = line.substr(filename_start, colon - filename_start);
auto line_number = line.substr(colon + 1);
if(line_number != "?") {
entries_vec[entry_index].second.get().line = std::stoi(line_number);
}
if(!filename.empty() && filename != "??") {
entries_vec[entry_index].second.get().filename = filename;
}
if(!symbol.empty()) {
entries_vec[entry_index].second.get().symbol = symbol;
}
#else
// Result will be of the form "<symbol> (in <object name>) (file:line)"
// The symbol may just be the given address if atos can't resolve it
// Examples:
// trace() (in demo) (demo.cpp:8)
// 0x100003b70 (in demo)
// 0xffffffffffffffff
// foo (in bar) + 14
// I'm making some assumptions here. Support may need to be improved later. This is tricky output to
// parse.
const std::size_t in_location = line.find(" (in ");
if(in_location == std::string::npos) {
// presumably the 0xffffffffffffffff case
return;
}
const std::size_t symbol_end = in_location;
entries_vec[entry_index].second.get().symbol = line.substr(0, symbol_end);
const std::size_t object_end = line.find(")", in_location);
VERIFY(
object_end != std::string::npos,
"Unexpected edge case while processing addr2line/atos output"
);
const std::size_t filename_start = line.find(") (", object_end);
if(filename_start == std::string::npos) {
// presumably something like 0x100003b70 (in demo) or foo (in bar) + 14
return;
}
const std::size_t filename_end = line.find(":", filename_start);
VERIFY(
filename_end != std::string::npos,
"Unexpected edge case while processing addr2line/atos output"
);
entries_vec[entry_index].second.get().filename = line.substr(
filename_start + 3,
filename_end - filename_start - 3
);
const std::size_t line_start = filename_end + 1;
const std::size_t line_end = line.find(")", filename_end);
VERIFY(
line_end == line.size() - 1,
"Unexpected edge case while processing addr2line/atos output"
);
entries_vec[entry_index].second.get().line = std::stoi(line.substr(line_start, line_end - line_start));
#endif
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
// TODO: Refactor better
std::vector<stacktrace_frame> trace(frames.size(), null_frame);
for(std::size_t i = 0; i < frames.size(); i++) {
trace[i].raw_address = frames[i].raw_address;
trace[i].object_address = frames[i].object_address;
// Set what is known for now, and resolutions from addr2line should overwrite
trace[i].filename = frames[i].object_path;
}
if(has_addr2line()) {
const auto entries = collate_frames(frames, trace);
for(const auto& entry : entries) {
try {
const auto& object_name = entry.first;
const auto& entries_vec = entry.second;
// You may ask why it'd ever happen that there could be an empty entries_vec array, if there're
// no addresses why would get_addr2line_targets do anything? The reason is because if things in
// get_addr2line_targets fail it will silently skip. This is partly an optimization but also an
// assertion below will fail if addr2line is given an empty input.
if(entries_vec.empty()) {
continue;
}
std::string address_input;
for(const auto& pair : entries_vec) {
address_input += microfmt::format(
"{:h}{}",
pair.first.get().object_address,
#if !IS_WINDOWS
'\n'
#else
' '
#endif
);
}
auto output = split(trim(resolve_addresses(address_input, object_name)), "\n");
VERIFY(output.size() == entries_vec.size());
for(std::size_t i = 0; i < output.size(); i++) {
update_trace(output[i], i, entries_vec);
}
} catch(...) { // NOSONAR
if(!should_absorb_trace_exceptions()) {
throw;
}
}
}
}
return trace;
}
}
}
}
#endif

View File

@ -0,0 +1,456 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_DBGHELP
#include <cpptrace/cpptrace.hpp>
#include "symbols/symbols.hpp"
#include "platform/dbghelp_syminit_manager.hpp"
#include <memory>
#include <mutex>
#include <regex>
#include <stdexcept>
#include <system_error>
#include <vector>
#include <windows.h>
#include <dbghelp.h>
namespace cpptrace {
namespace detail {
namespace dbghelp {
// SymFromAddr only returns the function's name. In order to get information about parameters,
// important for C++ stack traces where functions may be overloaded, we have to manually use
// Windows DIA to walk debug info structures. Resources:
// https://web.archive.org/web/20201027025750/http://www.debuginfo.com/articles/dbghelptypeinfo.html
// https://web.archive.org/web/20201203160805/http://www.debuginfo.com/articles/dbghelptypeinfofigures.html
// https://github.com/DynamoRIO/dynamorio/blob/master/ext/drsyms/drsyms_windows.c#L1370-L1439
// TODO: Currently unable to detect rvalue references
// TODO: Currently unable to detect const
enum class SymTagEnum {
SymTagNull, SymTagExe, SymTagCompiland, SymTagCompilandDetails, SymTagCompilandEnv,
SymTagFunction, SymTagBlock, SymTagData, SymTagAnnotation, SymTagLabel, SymTagPublicSymbol,
SymTagUDT, SymTagEnum, SymTagFunctionType, SymTagPointerType, SymTagArrayType,
SymTagBaseType, SymTagTypedef, SymTagBaseClass, SymTagFriend, SymTagFunctionArgType,
SymTagFuncDebugStart, SymTagFuncDebugEnd, SymTagUsingNamespace, SymTagVTableShape,
SymTagVTable, SymTagCustom, SymTagThunk, SymTagCustomType, SymTagManagedType,
SymTagDimension, SymTagCallSite, SymTagInlineSite, SymTagBaseInterface, SymTagVectorType,
SymTagMatrixType, SymTagHLSLType, SymTagCaller, SymTagCallee, SymTagExport,
SymTagHeapAllocationSite, SymTagCoffGroup, SymTagMax
};
enum class IMAGEHLP_SYMBOL_TYPE_INFO {
TI_GET_SYMTAG, TI_GET_SYMNAME, TI_GET_LENGTH, TI_GET_TYPE, TI_GET_TYPEID, TI_GET_BASETYPE,
TI_GET_ARRAYINDEXTYPEID, TI_FINDCHILDREN, TI_GET_DATAKIND, TI_GET_ADDRESSOFFSET,
TI_GET_OFFSET, TI_GET_VALUE, TI_GET_COUNT, TI_GET_CHILDRENCOUNT, TI_GET_BITPOSITION,
TI_GET_VIRTUALBASECLASS, TI_GET_VIRTUALTABLESHAPEID, TI_GET_VIRTUALBASEPOINTEROFFSET,
TI_GET_CLASSPARENTID, TI_GET_NESTED, TI_GET_SYMINDEX, TI_GET_LEXICALPARENT, TI_GET_ADDRESS,
TI_GET_THISADJUST, TI_GET_UDTKIND, TI_IS_EQUIV_TO, TI_GET_CALLING_CONVENTION,
TI_IS_CLOSE_EQUIV_TO, TI_GTIEX_REQS_VALID, TI_GET_VIRTUALBASEOFFSET,
TI_GET_VIRTUALBASEDISPINDEX, TI_GET_IS_REFERENCE, TI_GET_INDIRECTVIRTUALBASECLASS,
TI_GET_VIRTUALBASETABLETYPE, TI_GET_OBJECTPOINTERTYPE, IMAGEHLP_SYMBOL_TYPE_INFO_MAX
};
enum class BasicType {
btNoType = 0, btVoid = 1, btChar = 2, btWChar = 3, btInt = 6, btUInt = 7, btFloat = 8,
btBCD = 9, btBool = 10, btLong = 13, btULong = 14, btCurrency = 25, btDate = 26,
btVariant = 27, btComplex = 28, btBit = 29, btBSTR = 30, btHresult = 31
};
// SymGetTypeInfo utility
template<typename T, IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
T get_info(ULONG type_index, HANDLE proc, ULONG64 modbase) {
T info;
if(
!SymGetTypeInfo(
proc,
modbase,
type_index,
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType),
&info
)
) {
if(FAILABLE) {
return (T)-1;
} else {
throw internal_error(
"SymGetTypeInfo failed: {}", std::system_error(GetLastError(), std::system_category()).what()
);
}
}
return info;
}
template<IMAGEHLP_SYMBOL_TYPE_INFO SymType, bool FAILABLE = false>
std::string get_info_wchar(ULONG type_index, HANDLE proc, ULONG64 modbase) {
WCHAR* info;
if(
!SymGetTypeInfo(proc, modbase, type_index, static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(SymType), &info)
) {
throw internal_error(
"SymGetTypeInfo failed: {}", std::system_error(GetLastError(), std::system_category()).what()
);
}
// special case to properly free a buffer and convert string to narrow chars, only used for
// TI_GET_SYMNAME
static_assert(
SymType == IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME,
"get_info_wchar called with unexpected IMAGEHLP_SYMBOL_TYPE_INFO"
);
std::wstring wstr(info);
std::string str;
str.reserve(wstr.size());
for(const auto c : wstr) {
str.push_back(static_cast<char>(c));
}
LocalFree(info);
return str;
}
// Translate basic types to string
static std::string get_basic_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
auto basic_type = get_info<BasicType, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_BASETYPE>(
type_index,
proc,
modbase
);
//auto length = get_info<ULONG64, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_LENGTH>(type_index, proc, modbase);
switch(basic_type) {
case BasicType::btNoType:
return "<no basic type>";
case BasicType::btVoid:
return "void";
case BasicType::btChar:
return "char";
case BasicType::btWChar:
return "wchar_t";
case BasicType::btInt:
return "int";
case BasicType::btUInt:
return "unsigned int";
case BasicType::btFloat:
return "float";
case BasicType::btBool:
return "bool";
case BasicType::btLong:
return "long";
case BasicType::btULong:
return "unsigned long";
default:
return "<unknown basic type>";
}
}
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase);
struct class_name_result {
bool has_class_name;
std::string name;
};
// Helper for member pointers
static class_name_result lookup_class_name(ULONG type_index, HANDLE proc, ULONG64 modbase) {
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
type_index,
proc,
modbase
);
if(class_parent_id == (DWORD)-1) {
return {false, ""};
} else {
return {true, resolve_type(class_parent_id, proc, modbase)};
}
}
struct type_result {
std::string base;
std::string extent;
};
// Resolve more complex types
// returns [base, extent]
static type_result lookup_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
auto tag = get_info<SymTagEnum, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMTAG>(type_index, proc, modbase);
switch(tag) {
case SymTagEnum::SymTagBaseType:
return {get_basic_type(type_index, proc, modbase), ""};
case SymTagEnum::SymTagPointerType: {
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
type_index,
proc,
modbase
);
bool is_ref = get_info<BOOL, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_IS_REFERENCE>(
type_index,
proc,
modbase
);
std::string pp = is_ref ? "&" : "*"; // pointer punctuator
auto class_name_res = lookup_class_name(type_index, proc, modbase);
if(class_name_res.has_class_name) {
pp = class_name_res.name + "::" + pp;
}
const auto type = lookup_type(underlying_type_id, proc, modbase);
if(type.extent.empty()) {
return {type.base + (pp.size() > 1 ? " " : "") + pp, ""};
} else {
return {type.base + "(" + pp, ")" + type.extent};
}
}
case SymTagEnum::SymTagArrayType: {
DWORD underlying_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
type_index,
proc,
modbase
);
DWORD length = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT>(
type_index,
proc,
modbase
);
const auto type = lookup_type(underlying_type_id, proc, modbase);
return {type.base, "[" + std::to_string(length) + "]" + type.extent};
}
case SymTagEnum::SymTagFunctionType: {
DWORD return_type_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(
type_index,
proc,
modbase
);
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
type_index,
proc,
modbase
);
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
type_index,
proc,
modbase
);
int n_ignore = class_parent_id != (DWORD)-1; // ignore this param
// this must be ignored before TI_FINDCHILDREN_PARAMS::Count is set, else error
n_children -= n_ignore;
// return type
const auto return_type = lookup_type(return_type_id, proc, modbase);
if(n_children == 0) {
return {return_type.base, "()" + return_type.extent};
} else {
// alignment should be fine
std::size_t sz = sizeof(TI_FINDCHILDREN_PARAMS) +
(n_children) * sizeof(TI_FINDCHILDREN_PARAMS::ChildId[0]);
TI_FINDCHILDREN_PARAMS* children = (TI_FINDCHILDREN_PARAMS*) new char[sz];
children->Start = 0;
children->Count = n_children;
if(
!SymGetTypeInfo(
proc, modbase, type_index,
static_cast<::IMAGEHLP_SYMBOL_TYPE_INFO>(
IMAGEHLP_SYMBOL_TYPE_INFO::TI_FINDCHILDREN
),
children
)
) {
throw internal_error(
"SymGetTypeInfo failed: {}",
std::system_error(GetLastError(), std::system_category()).what()
);
}
// get children type
std::string extent = "(";
if(children->Start != 0) {
throw internal_error("Error: children->Start == 0");
}
for(std::size_t i = 0; i < n_children; i++) {
extent += (i == 0 ? "" : ", ") + resolve_type(children->ChildId[i], proc, modbase);
}
extent += ")";
delete[] (char*) children;
return {return_type.base, extent + return_type.extent};
}
}
case SymTagEnum::SymTagFunctionArgType: {
DWORD underlying_type_id =
get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_TYPEID>(type_index, proc, modbase);
return {resolve_type(underlying_type_id, proc, modbase), ""};
}
case SymTagEnum::SymTagTypedef:
case SymTagEnum::SymTagEnum:
case SymTagEnum::SymTagUDT:
case SymTagEnum::SymTagBaseClass:
return {
get_info_wchar<IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_SYMNAME>(type_index, proc, modbase), ""
};
default:
return {
"<unknown type " +
std::to_string(static_cast<std::underlying_type<SymTagEnum>::type>(tag)) +
">",
""
};
};
}
static std::string resolve_type(ULONG type_index, HANDLE proc, ULONG64 modbase) {
const auto type = lookup_type(type_index, proc, modbase);
return type.base + type.extent;
}
struct function_info {
HANDLE proc;
ULONG64 modbase;
int counter;
int n_children;
int n_ignore;
std::string str;
};
// Enumerates function parameters
static BOOL __stdcall enumerator_callback(
PSYMBOL_INFO symbol_info,
ULONG,
PVOID data
) {
function_info* ctx = (function_info*)data;
if(ctx->counter++ >= ctx->n_children) {
return false;
}
if(ctx->n_ignore-- > 0) {
return true; // just skip
}
ctx->str += resolve_type(symbol_info->TypeIndex, ctx->proc, ctx->modbase);
if(ctx->counter < ctx->n_children) {
ctx->str += ", ";
}
return true;
}
std::recursive_mutex dbghelp_lock;
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
stacktrace_frame resolve_frame(HANDLE proc, frame_ptr addr) {
// The get_frame_object_info() ends up being inexpensive, at on my machine
// debug release
// uncached trace resolution (29 frames) 1.9-2.1 ms 1.4-1.8 ms
// cached trace resolution (29 frames) 1.1-1.2 ms 0.2-0.4 ms
// get_frame_object_info() 0.001-0.002 ms 0.0003-0.0006 ms
// At some point it might make sense to make an option to control this.
auto object_frame = get_frame_object_info(addr);
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
alignas(SYMBOL_INFO) char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)];
SYMBOL_INFO* symbol = (SYMBOL_INFO*)buffer;
symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
symbol->MaxNameLen = MAX_SYM_NAME;
union { DWORD64 a; DWORD b; } displacement;
IMAGEHLP_LINE64 line;
bool got_line = SymGetLineFromAddr64(proc, addr, &displacement.b, &line);
if(SymFromAddr(proc, addr, &displacement.a, symbol)) {
if(got_line) {
IMAGEHLP_STACK_FRAME frame;
frame.InstructionOffset = symbol->Address;
// https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetcontext
// "If you call SymSetContext to set the context to its current value, the
// function fails but GetLastError returns ERROR_SUCCESS."
// This is the stupidest fucking api I've ever worked with.
if(SymSetContext(proc, &frame, nullptr) == FALSE && GetLastError() != ERROR_SUCCESS) {
std::fprintf(stderr, "Stack trace: Internal error while calling SymSetContext\n");
return {
addr,
object_frame.object_address,
{ static_cast<std::uint32_t>(line.LineNumber) },
nullable<std::uint32_t>::null(),
line.FileName,
symbol->Name,
false
};
}
DWORD n_children = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_COUNT, true>(
symbol->TypeIndex,
proc,
symbol->ModBase
);
DWORD class_parent_id = get_info<DWORD, IMAGEHLP_SYMBOL_TYPE_INFO::TI_GET_CLASSPARENTID, true>(
symbol->TypeIndex,
proc,
symbol->ModBase
);
function_info fi {
proc,
symbol->ModBase,
0,
int(n_children),
class_parent_id != (DWORD)-1,
""
};
SymEnumSymbols(proc, 0, nullptr, enumerator_callback, &fi);
std::string signature = symbol->Name + std::string("(") + fi.str + ")";
// There's a phenomina with DIA not inserting commas after template parameters. Fix them here.
static std::regex comma_re(R"(,(?=\S))");
signature = std::regex_replace(signature, comma_re, ", ");
return {
addr,
object_frame.object_address,
{ static_cast<std::uint32_t>(line.LineNumber) },
nullable<std::uint32_t>::null(),
line.FileName,
signature,
false,
};
} else {
return {
addr,
object_frame.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
"",
symbol->Name,
false
};
}
} else {
return {
addr,
object_frame.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
"",
"",
false
};
}
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
const std::lock_guard<std::recursive_mutex> lock(dbghelp_lock); // all dbghelp functions are not thread safe
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());
// TODO: When does this need to be called? Can it be moved to the symbolizer?
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
HANDLE proc = GetCurrentProcess();
if(get_cache_mode() == cache_mode::prioritize_speed) {
get_syminit_manager().init(proc);
} else {
if(!SymInitialize(proc, NULL, TRUE)) {
throw internal_error("SymInitialize failed");
}
}
for(const auto frame : frames) {
try {
trace.push_back(resolve_frame(proc, frame));
} catch(...) { // NOSONAR
if(!detail::should_absorb_trace_exceptions()) {
throw;
}
auto entry = null_frame;
entry.raw_address = frame;
trace.push_back(entry);
}
}
if(get_cache_mode() != cache_mode::prioritize_speed) {
if(!SymCleanup(proc)) {
throw internal_error("SymCleanup failed");
}
}
return trace;
}
}
}
}
#endif

View File

@ -0,0 +1,55 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDL
#include <cpptrace/cpptrace.hpp>
#include "symbols/symbols.hpp"
#include <cstdint>
#include <memory>
#include <vector>
#include <dlfcn.h>
namespace cpptrace {
namespace detail {
namespace libdl {
stacktrace_frame resolve_frame(const frame_ptr addr) {
Dl_info info;
if(dladdr(reinterpret_cast<void*>(addr), &info)) { // thread-safe
auto base = get_module_image_base(info.dli_fname);
return {
addr,
base.has_value()
? addr - reinterpret_cast<std::uintptr_t>(info.dli_fbase) + base.unwrap_value()
: 0,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
info.dli_fname ? info.dli_fname : "",
info.dli_sname ? info.dli_sname : "",
false
};
} else {
return {
addr,
0,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
"",
"",
false
};
}
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());
for(const auto frame : frames) {
trace.push_back(resolve_frame(frame));
}
return trace;
}
}
}
}
#endif

View File

@ -0,0 +1,106 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
#include <cpptrace/cpptrace.hpp>
#include "symbols/symbols.hpp"
#include "platform/program_name.hpp"
#include <cstdint>
#include <cstdio>
#include <memory>
#include <mutex>
#include <stdexcept>
#include <vector>
#ifdef CPPTRACE_BACKTRACE_PATH
#include CPPTRACE_BACKTRACE_PATH
#else
#include <backtrace.h>
#endif
namespace cpptrace {
namespace detail {
namespace libbacktrace {
int full_callback(void* data, std::uintptr_t address, const char* file, int line, const char* symbol) {
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
frame.raw_address = address;
frame.line = line;
frame.filename = file ? file : "";
frame.symbol = symbol ? symbol : "";
return 0;
}
void syminfo_callback(void* data, std::uintptr_t address, const char* symbol, std::uintptr_t, std::uintptr_t) {
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
frame.raw_address = address;
frame.line = 0;
frame.filename = "";
frame.symbol = symbol ? symbol : "";
}
void error_callback(void*, const char* msg, int errnum) {
if(msg == std::string("no debug info in ELF executable")) {
// https://github.com/jeremy-rifkin/cpptrace/issues/114
// https://github.com/ianlancetaylor/libbacktrace/blob/ae1e707dbacd4a5cc82fcf2d3816f410e9c5fec4/elf.c#L592
// not a critical error, just return
return;
}
throw internal_error("Libbacktrace error: {}, code {}", msg, errnum);
}
backtrace_state* get_backtrace_state() {
static std::mutex mutex;
const std::lock_guard<std::mutex> lock(mutex);
// backtrace_create_state must be called only one time per program
static backtrace_state* state = nullptr;
static bool called = false;
if(!called) {
state = backtrace_create_state(program_name(), true, error_callback, nullptr);
called = true;
}
return state;
}
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
stacktrace_frame resolve_frame(const frame_ptr addr) {
try {
stacktrace_frame frame = null_frame;
frame.raw_address = addr;
backtrace_pcinfo(
get_backtrace_state(),
addr,
full_callback,
error_callback,
&frame
);
if(frame.symbol.empty()) {
// fallback, try to at least recover the symbol name with backtrace_syminfo
backtrace_syminfo(
get_backtrace_state(),
addr,
syminfo_callback,
error_callback,
&frame
);
}
return frame;
} catch(...) { // NOSONAR
if(!should_absorb_trace_exceptions()) {
throw;
}
return null_frame;
}
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
std::vector<stacktrace_frame> trace;
trace.reserve(frames.size());
for(const auto frame : frames) {
trace.push_back(resolve_frame(frame));
}
return trace;
}
}
}
}
#endif

View File

@ -0,0 +1,145 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_LIBDWARF
#include "symbols/symbols.hpp"
#include <cpptrace/cpptrace.hpp>
#include "dwarf/resolver.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#include <cstdint>
#include <cstdio>
#include <memory>
#include <mutex>
#include <unordered_map>
#include <vector>
#include <iostream>
#include <iomanip>
namespace cpptrace {
namespace detail {
namespace libdwarf {
std::unique_ptr<symbol_resolver> get_resolver_for_object(const std::string& object_path) {
#if IS_APPLE
// Check if dSYM exist, if not fallback to debug map
if(!directory_exists(object_path + ".dSYM")) {
return make_debug_map_resolver(object_path);
}
#endif
return make_dwarf_resolver(object_path);
}
// not thread-safe, replies on caller to lock
maybe_owned<symbol_resolver> get_resolver(const std::string& object_name) {
// cache resolvers since objects are likely to be traced more than once
static std::unordered_map<std::string, std::unique_ptr<symbol_resolver>> resolver_map;
auto it = resolver_map.find(object_name);
if(it != resolver_map.end()) {
return it->second.get();
} else {
std::unique_ptr<symbol_resolver> resolver_object = get_resolver_for_object(object_name);
if(get_cache_mode() == cache_mode::prioritize_speed) {
// .emplace needed, for some reason .insert tries to copy <= gcc 7.2
return resolver_map.emplace(object_name, std::move(resolver_object)).first->second.get();
} else {
// gcc 4 has trouble with automatic moves of locals here https://godbolt.org/z/9oWdWjbf8
return maybe_owned<symbol_resolver>{std::move(resolver_object)};
}
}
}
// flatten trace with inlines
std::vector<stacktrace_frame> flatten_inlines(std::vector<frame_with_inlines>& trace) {
std::vector<stacktrace_frame> final_trace;
for(auto& entry : trace) {
// most recent call first
if(!entry.inlines.empty()) {
// insert in reverse order
final_trace.insert(
final_trace.end(),
std::make_move_iterator(entry.inlines.rbegin()),
std::make_move_iterator(entry.inlines.rend())
);
}
final_trace.push_back(std::move(entry.frame));
if(!entry.inlines.empty()) {
// rotate line info due to quirk of how dwarf stores this stuff
// inclusive range
auto begin = final_trace.end() - (1 + entry.inlines.size());
auto end = final_trace.end() - 1;
auto carry_line = end->line;
auto carry_column = end->column;
std::string carry_filename = std::move(end->filename);
for(auto it = end; it != begin; it--) {
it->line = (it - 1)->line;
it->column = (it - 1)->column;
it->filename = std::move((it - 1)->filename);
}
begin->line = carry_line;
begin->column = carry_column;
begin->filename = std::move(carry_filename);
}
}
return final_trace;
}
CPPTRACE_FORCE_NO_INLINE_FOR_PROFILING
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
std::vector<frame_with_inlines> trace(frames.size(), {null_frame, {}});
// Locking around all libdwarf interaction per https://github.com/davea42/libdwarf-code/discussions/184
// And also locking for interactions with get_resolver
static std::mutex mutex;
const std::lock_guard<std::mutex> lock(mutex);
for(const auto& group : collate_frames(frames, trace)) {
try {
const auto& object_name = group.first;
auto resolver = get_resolver(object_name);
for(const auto& entry : group.second) {
const auto& dlframe = entry.first.get();
auto& frame = entry.second.get();
try {
frame = resolver->resolve_frame(dlframe);
} catch(...) {
frame.frame.raw_address = dlframe.raw_address;
frame.frame.object_address = dlframe.object_address;
frame.frame.filename = dlframe.object_path;
if(!should_absorb_trace_exceptions()) {
throw;
}
}
}
} catch(...) { // NOSONAR
if(!should_absorb_trace_exceptions()) {
throw;
}
}
}
// fill in basic info for any frames where there were resolution issues
for(std::size_t i = 0; i < frames.size(); i++) {
const auto& dlframe = frames[i];
auto& frame = trace[i];
if(frame.frame == null_frame) {
frame = {
{
dlframe.raw_address,
dlframe.object_address,
nullable<std::uint32_t>::null(),
nullable<std::uint32_t>::null(),
dlframe.object_path,
"",
false
},
{}
};
}
}
// flatten and finish
return flatten_inlines(trace);
}
}
}
}
#endif

View File

@ -0,0 +1,22 @@
#ifdef CPPTRACE_GET_SYMBOLS_WITH_NOTHING
#include <cpptrace/cpptrace.hpp>
#include "symbols/symbols.hpp"
#include <vector>
namespace cpptrace {
namespace detail {
namespace nothing {
std::vector<stacktrace_frame> resolve_frames(const std::vector<frame_ptr>& frames) {
return std::vector<stacktrace_frame>(frames.size(), null_frame);
}
std::vector<stacktrace_frame> resolve_frames(const std::vector<object_frame>& frames) {
return std::vector<stacktrace_frame>(frames.size(), null_frame);
}
}
}
}
#endif

View File

@ -0,0 +1,28 @@
#ifndef UNWIND_HPP
#define UNWIND_HPP
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <cstddef>
#include <vector>
namespace cpptrace {
namespace detail {
#ifdef CPPTRACE_HARD_MAX_FRAMES
constexpr std::size_t hard_max_frames = CPPTRACE_HARD_MAX_FRAMES;
#else
constexpr std::size_t hard_max_frames = 200;
#endif
CPPTRACE_FORCE_NO_INLINE
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth);
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth);
bool has_safe_unwind();
}
}
#endif

View File

@ -0,0 +1,171 @@
#ifdef CPPTRACE_UNWIND_WITH_DBGHELP
#include <cpptrace/cpptrace.hpp>
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include "platform/dbghelp_syminit_manager.hpp"
#include <algorithm>
#include <cstdint>
#include <vector>
#include <mutex>
#include <windows.h>
#include <dbghelp.h>
// Fucking windows headers
#ifdef min
#undef min
#endif
namespace cpptrace {
namespace detail {
#if IS_MSVC
#pragma warning(push)
#pragma warning(disable: 4740) // warning C4740: flow in or out of inline asm code suppresses global optimization
#endif
CPPTRACE_FORCE_NO_INLINE
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
skip++;
// https://jpassing.com/2008/03/12/walking-the-stack-of-the-current-thread/
// Get current thread context
// GetThreadContext cannot be used on the current thread.
// RtlCaptureContext doesn't work on i386
CONTEXT context;
#if defined(_M_IX86) || defined(__i386__)
ZeroMemory(&context, sizeof(CONTEXT));
context.ContextFlags = CONTEXT_CONTROL;
#if IS_MSVC
__asm {
label:
mov [context.Ebp], ebp;
mov [context.Esp], esp;
mov eax, [label];
mov [context.Eip], eax;
}
#else
asm(
"label:\n\t"
"mov{l %%ebp, %[cEbp] | %[cEbp], ebp};\n\t"
"mov{l %%esp, %[cEsp] | %[cEsp], esp};\n\t"
"mov{l $label, %%eax | eax, OFFSET label};\n\t"
"mov{l %%eax, %[cEip] | %[cEip], eax};\n\t"
: [cEbp] "=r" (context.Ebp),
[cEsp] "=r" (context.Esp),
[cEip] "=r" (context.Eip)
);
#endif
#else
RtlCaptureContext(&context);
#endif
// Setup current frame
STACKFRAME64 frame;
ZeroMemory(&frame, sizeof(STACKFRAME64));
DWORD machine_type;
#if defined(_M_IX86) || defined(__i386__)
machine_type = IMAGE_FILE_MACHINE_I386;
frame.AddrPC.Offset = context.Eip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Ebp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Esp;
frame.AddrStack.Mode = AddrModeFlat;
#elif defined(_M_X64) || defined(__x86_64__)
machine_type = IMAGE_FILE_MACHINE_AMD64;
frame.AddrPC.Offset = context.Rip;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.Rsp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.Rsp;
frame.AddrStack.Mode = AddrModeFlat;
#elif defined(_M_IA64) || defined(__aarch64__)
machine_type = IMAGE_FILE_MACHINE_IA64;
frame.AddrPC.Offset = context.StIIP;
frame.AddrPC.Mode = AddrModeFlat;
frame.AddrFrame.Offset = context.IntSp;
frame.AddrFrame.Mode = AddrModeFlat;
frame.AddrBStore.Offset= context.RsBSP;
frame.AddrBStore.Mode = AddrModeFlat;
frame.AddrStack.Offset = context.IntSp;
frame.AddrStack.Mode = AddrModeFlat;
#else
#error "Cpptrace: StackWalk64 not supported for this platform yet"
#endif
std::vector<frame_ptr> trace;
// Dbghelp is is single-threaded, so acquire a lock.
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
// For some reason SymInitialize must be called before StackWalk64
// Note that the code assumes that
// SymInitialize( GetCurrentProcess(), NULL, TRUE ) has
// already been called.
//
HANDLE proc = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
if(get_cache_mode() == cache_mode::prioritize_speed) {
get_syminit_manager().init(proc);
} else {
if(!SymInitialize(proc, NULL, TRUE)) {
throw internal_error("SymInitialize failed");
}
}
while(trace.size() < max_depth) {
if(
!StackWalk64(
machine_type,
proc,
thread,
&frame,
machine_type == IMAGE_FILE_MACHINE_I386 ? NULL : &context,
NULL,
SymFunctionTableAccess64,
SymGetModuleBase64,
NULL
)
) {
// Either failed or finished walking
break;
}
if(frame.AddrPC.Offset != 0) {
// Valid frame
if(skip) {
skip--;
} else {
// On x86/x64/arm, as far as I can tell, the frame return address is always one after the call
// So we just decrement to get the pc back inside the `call` / `bl`
// This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo.
trace.push_back(to_frame_ptr(frame.AddrPC.Offset) - 1);
}
} else {
// base
break;
}
}
if(get_cache_mode() != cache_mode::prioritize_speed) {
if(!SymCleanup(proc)) {
throw internal_error("SymCleanup failed");
}
}
return trace;
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
// Can't safe trace with dbghelp
return 0;
}
#if IS_MSVC
#pragma warning(pop)
#endif
bool has_safe_unwind() {
return false;
}
}
}
#endif

View File

@ -0,0 +1,45 @@
#ifdef CPPTRACE_UNWIND_WITH_EXECINFO
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <climits>
#include <cstddef>
#include <vector>
#include <execinfo.h>
namespace cpptrace {
namespace detail {
CPPTRACE_FORCE_NO_INLINE
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
skip++;
std::vector<void*> addrs(skip + std::min(hard_max_frames, max_depth), nullptr);
// thread safe
const int n_frames = backtrace(addrs.data(), static_cast<int>(addrs.size()));
// I hate the copy here but it's the only way that isn't UB
std::vector<frame_ptr> frames(n_frames - skip, 0);
for(int i = skip; i < n_frames; i++) {
// On x86/x64/arm, as far as I can tell, the frame return address is always one after the call
// So we just decrement to get the pc back inside the `call` / `bl`
// This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo.
frames[i - skip] = reinterpret_cast<frame_ptr>(addrs[i]) - 1;
}
return frames;
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
// Can't safe trace with execinfo
return 0;
}
bool has_safe_unwind() {
return false;
}
}
}
#endif

View File

@ -0,0 +1,87 @@
#ifdef CPPTRACE_UNWIND_WITH_LIBUNWIND
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <vector>
#include <libunwind.h>
namespace cpptrace {
namespace detail {
CPPTRACE_FORCE_NO_INLINE
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
skip++;
std::vector<frame_ptr> frames;
unw_context_t context;
unw_cursor_t cursor;
unw_getcontext(&context);
unw_init_local(&cursor, &context);
do {
unw_word_t pc;
unw_word_t sp;
unw_get_reg(&cursor, UNW_REG_IP, &pc);
unw_get_reg(&cursor, UNW_REG_SP, &sp);
if(skip) {
skip--;
} else {
// pc is the instruction after the `call`, adjust back to the previous instruction
frames.push_back(to_frame_ptr(pc) - 1);
}
} while(unw_step(&cursor) > 0 && frames.size() < max_depth);
return frames;
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_capture_frames(frame_ptr* buffer, std::size_t size, std::size_t skip, std::size_t max_depth) {
// some code duplication, but whatever
skip++;
unw_context_t context;
unw_cursor_t cursor;
// thread and signal-safe https://www.nongnu.org/libunwind/man/unw_getcontext(3).html
unw_getcontext(&context);
// thread and signal-safe https://www.nongnu.org/libunwind/man/unw_init_local(3).html
unw_init_local(&cursor, &context);
size_t i = 0;
while(i < size && i < max_depth) {
unw_word_t pc;
unw_word_t sp;
// thread and signal-safe https://www.nongnu.org/libunwind/man/unw_get_reg(3).html
unw_get_reg(&cursor, UNW_REG_IP, &pc);
unw_get_reg(&cursor, UNW_REG_SP, &sp);
if(skip) {
skip--;
} else {
// thread and signal-safe
if(unw_is_signal_frame(&cursor)) {
// pc is the instruction that caused the signal
// just a cast, thread and signal safe
buffer[i] = to_frame_ptr(pc);
} else {
// pc is the instruction after the `call`, adjust back to the previous instruction
// just a cast, thread and signal safe
buffer[i] = to_frame_ptr(pc) - 1;
}
i++;
}
// thread and signal-safe as long as the cursor is in the local address space, which it is
// https://www.nongnu.org/libunwind/man/unw_step(3).html
if(unw_step(&cursor) <= 0) {
break;
}
}
return i;
}
bool has_safe_unwind() {
return true;
}
}
}
#endif

View File

@ -0,0 +1,25 @@
#ifdef CPPTRACE_UNWIND_WITH_NOTHING
#include "unwind/unwind.hpp"
#include <cstddef>
#include <vector>
namespace cpptrace {
namespace detail {
std::vector<frame_ptr> capture_frames(std::size_t, std::size_t) {
return {};
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
return 0;
}
bool has_safe_unwind() {
return false;
}
}
}
#endif

View File

@ -0,0 +1,75 @@
#ifdef CPPTRACE_UNWIND_WITH_UNWIND
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <vector>
#include <unwind.h>
namespace cpptrace {
namespace detail {
struct unwind_state {
std::size_t skip;
std::size_t max_depth;
std::vector<frame_ptr>& vec;
};
_Unwind_Reason_Code unwind_callback(_Unwind_Context* context, void* arg) {
unwind_state& state = *static_cast<unwind_state*>(arg);
if(state.skip) {
state.skip--;
if(_Unwind_GetIP(context) == frame_ptr(0)) {
return _URC_END_OF_STACK;
} else {
return _URC_NO_REASON;
}
}
ASSERT(
state.vec.size() < state.max_depth,
"Somehow cpptrace::detail::unwind_callback is being called beyond the max_depth"
);
int is_before_instruction = 0;
frame_ptr ip = _Unwind_GetIPInfo(context, &is_before_instruction);
if(!is_before_instruction && ip != frame_ptr(0)) {
ip--;
}
if (ip == frame_ptr(0)) {
return _URC_END_OF_STACK;
} else {
state.vec.push_back(ip);
if(state.vec.size() >= state.max_depth) {
return _URC_END_OF_STACK;
} else {
return _URC_NO_REASON;
}
}
}
CPPTRACE_FORCE_NO_INLINE
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
std::vector<frame_ptr> frames;
unwind_state state{skip + 1, max_depth, frames};
_Unwind_Backtrace(unwind_callback, &state); // presumably thread-safe
return frames;
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
// Can't safe trace with _Unwind
return 0;
}
bool has_safe_unwind() {
return false;
}
}
}
#endif

View File

@ -0,0 +1,53 @@
#ifdef CPPTRACE_UNWIND_WITH_WINAPI
#include <cpptrace/cpptrace.hpp>
#include "unwind/unwind.hpp"
#include "utils/common.hpp"
#include "utils/utils.hpp"
#include <algorithm>
#include <cstdint>
#include <vector>
#include <windows.h>
// Fucking windows headers
#ifdef min
#undef min
#endif
namespace cpptrace {
namespace detail {
CPPTRACE_FORCE_NO_INLINE
std::vector<frame_ptr> capture_frames(std::size_t skip, std::size_t max_depth) {
std::vector<void*> addrs(skip + std::min(hard_max_frames, max_depth), nullptr);
std::size_t n_frames = CaptureStackBackTrace(
static_cast<ULONG>(skip + 1),
static_cast<ULONG>(addrs.size()),
addrs.data(),
NULL
);
// I hate the copy here but it's the only way that isn't UB
std::vector<frame_ptr> frames(n_frames, 0);
for(std::size_t i = 0; i < n_frames; i++) {
// On x86/x64/arm, as far as I can tell, the frame return address is always one after the call
// So we just decrement to get the pc back inside the `call` / `bl`
// This is done with _Unwind too but conditionally based on info from _Unwind_GetIPInfo.
frames[i] = reinterpret_cast<frame_ptr>(addrs[i]) - 1;
}
return frames;
}
CPPTRACE_FORCE_NO_INLINE
std::size_t safe_capture_frames(frame_ptr*, std::size_t, std::size_t, std::size_t) {
// Can't safe trace with winapi
return 0;
}
bool has_safe_unwind() {
return false;
}
}
}
#endif

View File

@ -0,0 +1,47 @@
#ifndef COMMON_HPP
#define COMMON_HPP
#include <cstdio>
#include <stdexcept>
#include <string>
#include <cpptrace/cpptrace.hpp>
#include "platform/platform.hpp"
#define ESC "\033["
#define RESET ESC "0m"
#define RED ESC "31m"
#define GREEN ESC "32m"
#define YELLOW ESC "33m"
#define BLUE ESC "34m"
#define MAGENTA ESC "35m"
#define CYAN ESC "36m"
#if IS_GCC || IS_CLANG
#define NODISCARD __attribute__((warn_unused_result))
// #elif IS_MSVC && _MSC_VER >= 1700
// #define NODISCARD _Check_return_
#else
#define NODISCARD
#endif
namespace cpptrace {
namespace detail {
static const stacktrace_frame null_frame {
0,
0,
nullable<uint32_t>::null(),
nullable<uint32_t>::null(),
"",
"",
false
};
bool should_absorb_trace_exceptions();
bool should_resolve_inlined_calls();
enum cache_mode get_cache_mode();
}
}
#endif

171
Trace/src/utils/error.hpp Normal file
View File

@ -0,0 +1,171 @@
#ifndef ERROR_HPP
#define ERROR_HPP
#include <exception>
#include <stdexcept>
#include <string>
#include <utility>
#include "utils/common.hpp"
#include "utils/microfmt.hpp"
#if IS_MSVC
#define CPPTRACE_PFUNC __FUNCSIG__
#else
#define CPPTRACE_PFUNC __extension__ __PRETTY_FUNCTION__
#endif
namespace cpptrace {
namespace detail {
class internal_error : public std::exception {
std::string msg;
public:
internal_error(std::string message) : msg("Cpptrace internal error: " + std::move(message)) {}
template<typename... Args>
internal_error(const char* format, Args&&... args) : internal_error(microfmt::format(format, args...)) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
// Lightweight std::source_location.
struct source_location {
const char* const file;
//const char* const function; // disabled for now due to static constexpr restrictions
const int line;
constexpr source_location(
//const char* _function /*= __builtin_FUNCTION()*/,
const char* _file = __builtin_FILE(),
int _line = __builtin_LINE()
) : file(_file), /*function(_function),*/ line(_line) {}
};
enum class assert_type {
assert,
verify,
panic,
};
constexpr const char* assert_actions[] = {"assertion", "verification", "panic"};
constexpr const char* assert_names[] = {"ASSERT", "VERIFY", "PANIC"};
[[noreturn]] inline void assert_fail(
assert_type type,
const char* expression,
const char* signature,
source_location location,
const char* message
) {
const char* action = assert_actions[static_cast<std::underlying_type<assert_type>::type>(type)];
const char* name = assert_names[static_cast<std::underlying_type<assert_type>::type>(type)];
if(message == nullptr) {
throw internal_error(
"Cpptrace {} failed at {}:{}: {}\n"
" {}({});\n",
action, location.file, location.line, signature,
name, expression
);
} else {
throw internal_error(
"Cpptrace {} failed at {}:{}: {}: {}\n"
" {}({});\n",
action, location.file, location.line, signature, message,
name, expression
);
}
}
[[noreturn]] inline void panic(
const char* signature,
source_location location,
const std::string& message = ""
) {
if(message == "") {
throw internal_error(
"Cpptrace panic {}:{}: {}\n",
location.file, location.line, signature
);
} else {
throw internal_error(
"Cpptrace panic {}:{}: {}: {}\n",
location.file, location.line, signature, message.c_str()
);
}
}
template<typename T>
void nullfn() {
// this method doesn't do anything and is never called.
}
#define PHONY_USE(...) (nullfn<decltype(__VA_ARGS__)>())
// Work around a compiler warning
template<typename T>
bool as_bool(T&& value) {
return static_cast<bool>(std::forward<T>(value));
}
// Work around a compiler warning
template<typename T>
std::string as_string(T&& value) {
return std::string(std::forward<T>(value));
}
inline std::string as_string() {
return "";
}
// Check condition in both debug and release. std::runtime_error on failure.
#define PANIC(...) ((::cpptrace::detail::panic)(CPPTRACE_PFUNC, {}, ::cpptrace::detail::as_string(__VA_ARGS__)))
template<typename T>
void assert_impl(
T condition,
const char* message,
assert_type type,
const char* args,
const char* signature,
source_location location
) {
if(!as_bool(condition)) {
assert_fail(type, args, signature, location, message);
}
}
template<typename T>
void assert_impl(
T condition,
assert_type type,
const char* args,
const char* signature,
source_location location
) {
assert_impl(
condition,
nullptr,
type,
args,
signature,
location
);
}
// Check condition in both debug and release. std::runtime_error on failure.
#define VERIFY(...) ( \
assert_impl(__VA_ARGS__, ::cpptrace::detail::assert_type::verify, #__VA_ARGS__, CPPTRACE_PFUNC, {}) \
)
#ifndef NDEBUG
// Check condition in both debug. std::runtime_error on failure.
#define ASSERT(...) ( \
assert_impl(__VA_ARGS__, ::cpptrace::detail::assert_type::assert, #__VA_ARGS__, CPPTRACE_PFUNC, {}) \
)
#else
// Check condition in both debug. std::runtime_error on failure.
#define ASSERT(...) PHONY_USE(__VA_ARGS__)
#endif
}
}
#endif

View File

@ -0,0 +1,305 @@
#ifndef MICROFMT_HPP
#define MICROFMT_HPP
#include <algorithm>
#include <array>
#include <cstdint>
#include <cstring>
#include <iostream>
#include <string>
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
#include <string_view>
#endif
#ifdef _MSC_VER
#include <intrin.h>
#endif
// https://github.com/jeremy-rifkin/microfmt
// Format: {[align][width][:[fill][base]]} # width: number or {}
namespace microfmt {
namespace detail {
inline std::uint64_t clz(std::uint64_t value) {
#ifdef _MSC_VER
unsigned long out = 0;
#ifdef _WIN64
_BitScanReverse64(&out, value);
#else
if(_BitScanReverse(&out, std::uint32_t(value >> 32))) {
return 63 - int(out + 32);
}
_BitScanReverse(&out, std::uint32_t(value));
#endif
return 63 - out;
#else
return __builtin_clzll(value);
#endif
}
template<typename U, typename V> U to(V v) {
return static_cast<U>(v); // A way to cast to U without "warning: useless cast to type"
}
enum class alignment { left, right };
struct format_options {
alignment align = alignment::left;
char fill = ' ';
size_t width = 0;
char base = 'd';
};
template<typename It> void do_write(std::string& out, It begin, It end, const format_options& options) {
auto size = end - begin;
if(static_cast<std::size_t>(size) >= options.width) {
out.append(begin, end);
} else {
auto out_size = out.size();
out.resize(out_size + options.width);
if(options.align == alignment::left) {
std::copy(begin, end, out.begin() + out_size);
std::fill(out.begin() + out_size + size, out.end(), options.fill);
} else {
std::fill(out.begin() + out_size, out.begin() + out_size + (options.width - size), options.fill);
std::copy(begin, end, out.begin() + out_size + (options.width - size));
}
}
}
template<int shift, int mask>
std::string to_string(std::uint64_t value, const char* digits = "0123456789abcdef") {
if(value == 0) {
return "0";
} else {
// digits = floor(1 + log_base(x))
// log_base(x) = log_2(x) / log_2(base)
// log_2(x) == 63 - clz(x)
// 1 + (63 - clz(value)) / (63 - clz(1 << shift))
// 63 - clz(1 << shift) is the same as shift
auto n_digits = to<std::size_t>(1 + (63 - clz(value)) / shift);
std::string number;
number.resize(n_digits);
std::size_t i = n_digits - 1;
while(value > 0) {
number[i--] = digits[value & mask];
value >>= shift;
}
return number;
}
}
inline std::string to_string(std::uint64_t value, const format_options& options) {
switch(options.base) {
case 'H': return to_string<4, 0xf>(value, "0123456789ABCDEF");
case 'h': return to_string<4, 0xf>(value);
case 'o': return to_string<3, 0x7>(value);
case 'b': return to_string<1, 0x1>(value);
default: return std::to_string(value); // failure: decimal
}
}
class format_value {
enum class value_type {
char_value,
int64_value,
uint64_value,
string_value,
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
string_view_value,
#endif
c_string_value,
};
union {
char char_value;
std::int64_t int64_value;
std::uint64_t uint64_value;
const std::string* string_value;
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
std::string_view string_view_value;
#endif
const char* c_string_value;
};
value_type value;
public:
format_value(char c) : char_value(c), value(value_type::char_value) {}
format_value(short int_val) : int64_value(int_val), value(value_type::int64_value) {}
format_value(int int_val) : int64_value(int_val), value(value_type::int64_value) {}
format_value(long int_val) : int64_value(int_val), value(value_type::int64_value) {}
format_value(long long int_val) : int64_value(int_val), value(value_type::int64_value) {}
format_value(unsigned char int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
format_value(unsigned short int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
format_value(unsigned int int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
format_value(unsigned long int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
format_value(unsigned long long int_val) : uint64_value(int_val), value(value_type::uint64_value) {}
format_value(const std::string& string) : string_value(&string), value(value_type::string_value) {}
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
format_value(std::string_view sv) : string_view_value(sv), value(value_type::string_view_value) {}
#endif
format_value(const char* c_string) : c_string_value(c_string), value(value_type::c_string_value) {}
int unwrap_int() const {
switch(value) {
case value_type::int64_value: return static_cast<int>(int64_value);
case value_type::uint64_value: return static_cast<int>(uint64_value);
default: return 0; // failure: just 0
}
}
public:
void write(std::string& out, const format_options& options) const {
switch(value) {
case value_type::char_value:
do_write(out, &char_value, &char_value + 1, options);
break;
case value_type::int64_value:
{
std::string str;
std::int64_t val = int64_value;
if(val < 0) {
str += '-';
val *= -1;
}
str += to_string(static_cast<std::uint64_t>(val), options);
do_write(out, str.begin(), str.end(), options);
}
break;
case value_type::uint64_value:
{
std::string str = to_string(uint64_value, options);
do_write(out, str.begin(), str.end(), options);
}
break;
case value_type::string_value:
do_write(out, string_value->begin(), string_value->end(), options);
break;
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
case value_type::string_view_value:
do_write(out, string_view_value.begin(), string_view_value.end(), options);
break;
#endif
case value_type::c_string_value:
do_write(out, c_string_value, c_string_value + std::strlen(c_string_value), options);
break;
} // failure: nop
}
};
template<std::size_t N, typename It>
std::string format(It fmt_begin, It fmt_end, std::array<format_value, N> args) {
std::string str;
std::size_t arg_i = 0;
auto it = fmt_begin;
auto peek = [&] (std::size_t dist) -> char { // 0 on failure
return fmt_end - it > signed(dist) ? *(it + dist) : 0;
};
auto read_number = [&] () -> int { // -1 on failure
auto scan = it;
int num = 0;
while(scan != fmt_end && isdigit(*scan)) {
num *= 10;
num += *scan - '0';
scan++;
}
if(scan != it) {
it = scan;
return num;
} else {
return -1;
}
};
for(; it != fmt_end; it++) {
if((*it == '{' || *it == '}') && peek(1) == *it) { // parse {{ and }} escapes
it++;
} else if(*it == '{' && it + 1 != fmt_end) {
auto saved_it = it;
auto handle_formatter = [&] () {
it++;
format_options options;
// try to parse alignment
if(*it == '<' || *it == '>') {
options.align = *it++ == '<' ? alignment::left : alignment::right;
}
// try to parse width
auto width = read_number(); // handles fmt_end check
if(width != -1) {
options.width = width;
} else if(it != fmt_end && *it == '{') { // try to parse variable width
if(peek(1) != '}') {
return false;
}
it += 2;
options.width = arg_i < args.size() ? args[arg_i++].unwrap_int() : 0;
}
// try to parse fill/base
if(it != fmt_end && *it == ':') {
it++;
if(fmt_end - it > 1 && *it != '}' && peek(1) != '}') { // two chars before the }, fill+base
options.fill = *it++;
options.base = *it++;
} else if(it != fmt_end && *it != '}') { // one char before the }, just base
if(*it == 'd' || *it == 'h' || *it == 'H' || *it == 'o' || *it == 'b') {
options.base = *it++;
} else {
options.fill = *it++;
}
}
}
if(it == fmt_end || *it != '}') {
return false;
}
if(arg_i < args.size()) {
args[arg_i++].write(str, options);
}
return true;
};
if(handle_formatter()) {
continue; // If reached here, successfully parsed and wrote a formatter. Don't write *it.
}
it = saved_it; // go back
}
str += *it;
}
return str;
}
}
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || __cplusplus >= 201703L)
template<typename... Args>
std::string format(std::string_view fmt, Args&&... args) {
return detail::format<sizeof...(args)>(fmt.begin(), fmt.end(), {detail::format_value(args)...});
}
// working around an old msvc bug https://godbolt.org/z/88T8hrzzq mre: https://godbolt.org/z/drd8echbP
inline std::string format(std::string_view fmt) {
return detail::format<1>(fmt.begin(), fmt.end(), {detail::format_value(1)});
}
#endif
template<typename... Args>
std::string format(const char* fmt, Args&&... args) {
return detail::format<sizeof...(args)>(fmt, fmt + std::strlen(fmt), {detail::format_value(args)...});
}
inline std::string format(const char* fmt) {
return detail::format<1>(fmt, fmt + std::strlen(fmt), {detail::format_value(1)});
}
template<typename S, typename... Args>
void print(const S& fmt, Args&&... args) {
std::cout<<format(fmt, args...);
}
template<typename S, typename... Args>
void print(std::ostream& ostream, const S& fmt, Args&&... args) {
ostream<<format(fmt, args...);
}
template<typename S, typename... Args>
void print(std::FILE* stream, const S& fmt, Args&&... args) {
auto str = format(fmt, args...);
fwrite(str.data(), 1, str.size(), stream);
}
}
#endif

588
Trace/src/utils/utils.hpp Normal file
View File

@ -0,0 +1,588 @@
#ifndef UTILS_HPP
#define UTILS_HPP
#include <algorithm>
#include <atomic>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <ios>
#include <memory>
#include <new>
#include <sstream>
#include <stdexcept>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include "utils/common.hpp"
#include "utils/error.hpp"
#include "utils/microfmt.hpp"
#if IS_WINDOWS
#include <windows.h>
#include <io.h>
#else
#include <sys/stat.h>
#include <unistd.h>
#endif
namespace cpptrace {
namespace detail {
inline bool isatty(int fd) {
#if IS_WINDOWS
return _isatty(fd);
#else
return ::isatty(fd);
#endif
}
inline int fileno(std::FILE* stream) {
#if IS_WINDOWS
return _fileno(stream);
#else
return ::fileno(stream);
#endif
}
inline std::vector<std::string> split(const std::string& str, const std::string& delims) {
std::vector<std::string> vec;
std::size_t old_pos = 0;
std::size_t pos = 0;
while((pos = str.find_first_of(delims, old_pos)) != std::string::npos) {
vec.emplace_back(str.substr(old_pos, pos - old_pos));
old_pos = pos + 1;
}
vec.emplace_back(str.substr(old_pos));
return vec;
}
template<typename C>
inline std::string join(const C& container, const std::string& delim) {
auto iter = std::begin(container);
auto end = std::end(container);
std::string str;
if(std::distance(iter, end) > 0) {
str += *iter;
while(++iter != end) {
str += delim;
str += *iter;
}
}
return str;
}
// first value in a sorted range such that *it <= value
template<typename ForwardIt, typename T>
ForwardIt first_less_than_or_equal(ForwardIt begin, ForwardIt end, const T& value) {
auto it = std::upper_bound(begin, end, value);
// it is first > value, we want first <= value
if(it != begin) {
return --it;
}
return end;
}
// first value in a sorted range such that *it <= value
template<typename ForwardIt, typename T, typename Compare>
ForwardIt first_less_than_or_equal(ForwardIt begin, ForwardIt end, const T& value, Compare compare) {
auto it = std::upper_bound(begin, end, value, compare);
// it is first > value, we want first <= value
if(it != begin) {
return --it;
}
return end;
}
constexpr const char* const whitespace = " \t\n\r\f\v";
inline std::string trim(const std::string& str) {
if(str.empty()) {
return "";
}
const std::size_t left = str.find_first_not_of(whitespace);
const std::size_t right = str.find_last_not_of(whitespace) + 1;
return str.substr(left, right - left);
}
inline bool is_little_endian() {
std::uint16_t num = 0x1;
const auto* ptr = (std::uint8_t*)&num;
return ptr[0] == 1;
}
// Modified from
// https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c
template<typename T, std::size_t N>
struct byte_swapper;
template<typename T>
struct byte_swapper<T, 1> {
T operator()(T val) {
return val;
}
};
template<typename T>
struct byte_swapper<T, 2> {
T operator()(T val) {
return (((val >> 8) & 0xff) | ((val & 0xff) << 8));
}
};
template<typename T>
struct byte_swapper<T, 4> {
T operator()(T val) {
return (((val & 0xff000000) >> 24) |
((val & 0x00ff0000) >> 8) |
((val & 0x0000ff00) << 8) |
((val & 0x000000ff) << 24));
}
};
template<typename T>
struct byte_swapper<T, 8> {
T operator()(T val) {
return (((val & 0xff00000000000000ULL) >> 56) |
((val & 0x00ff000000000000ULL) >> 40) |
((val & 0x0000ff0000000000ULL) >> 24) |
((val & 0x000000ff00000000ULL) >> 8 ) |
((val & 0x00000000ff000000ULL) << 8 ) |
((val & 0x0000000000ff0000ULL) << 24) |
((val & 0x000000000000ff00ULL) << 40) |
((val & 0x00000000000000ffULL) << 56));
}
};
template<typename T, typename std::enable_if<std::is_integral<T>::value, int>::type = 0>
T byteswap(T value) {
return byte_swapper<T, sizeof(T)>{}(value);
}
inline void enable_virtual_terminal_processing_if_needed() noexcept {
// enable colors / ansi processing if necessary
#if IS_WINDOWS
// https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences#example-of-enabling-virtual-terminal-processing
#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
constexpr DWORD ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4;
#endif
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD dwMode = 0;
if(hOut == INVALID_HANDLE_VALUE) return;
if(!GetConsoleMode(hOut, &dwMode)) return;
if(dwMode != (dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING))
if(!SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) return;
#endif
}
constexpr unsigned n_digits(unsigned value) noexcept {
return value < 10 ? 1 : 1 + n_digits(value / 10);
}
static_assert(n_digits(1) == 1, "n_digits utility producing the wrong result");
static_assert(n_digits(9) == 1, "n_digits utility producing the wrong result");
static_assert(n_digits(10) == 2, "n_digits utility producing the wrong result");
static_assert(n_digits(11) == 2, "n_digits utility producing the wrong result");
static_assert(n_digits(1024) == 4, "n_digits utility producing the wrong result");
struct nullopt_t {};
static constexpr nullopt_t nullopt;
template<
typename T,
typename std::enable_if<!std::is_same<typename std::decay<T>::type, void>::value, int>::type = 0
>
class optional {
union {
char x;
T uvalue;
};
bool holds_value = false;
public:
optional() noexcept {}
optional(nullopt_t) noexcept {}
~optional() {
reset();
}
optional(const optional& other) : holds_value(other.holds_value) {
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(other.uvalue);
}
}
optional(optional&& other)
noexcept(std::is_nothrow_move_constructible<T>::value)
: holds_value(other.holds_value)
{
if(holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
}
}
optional& operator=(const optional& other) {
optional copy(other);
swap(copy);
return *this;
}
optional& operator=(optional&& other)
noexcept(std::is_nothrow_move_assignable<T>::value && std::is_nothrow_move_constructible<T>::value)
{
reset();
if(other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
holds_value = true;
}
return *this;
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional(U&& value) : holds_value(true) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::forward<U>(value));
}
template<
typename U = T,
typename std::enable_if<!std::is_same<typename std::decay<U>::type, optional<T>>::value, int>::type = 0
>
optional& operator=(U&& value) {
optional o(std::forward<U>(value));
swap(o);
return *this;
}
optional& operator=(nullopt_t) noexcept {
reset();
return *this;
}
void swap(optional& other) noexcept {
if(holds_value && other.holds_value) {
std::swap(uvalue, other.uvalue);
} else if(holds_value && !other.holds_value) {
new (&other.uvalue) T(std::move(uvalue));
uvalue.~T();
} else if(!holds_value && other.holds_value) {
new (static_cast<void*>(std::addressof(uvalue))) T(std::move(other.uvalue));
other.uvalue.~T();
}
std::swap(holds_value, other.holds_value);
}
bool has_value() const {
return holds_value;
}
explicit operator bool() const {
return holds_value;
}
void reset() {
if(holds_value) {
uvalue.~T();
}
holds_value = false;
}
NODISCARD T& unwrap() & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD const T& unwrap() const & {
ASSERT(holds_value, "Optional does not contain a value");
return uvalue;
}
NODISCARD T&& unwrap() && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
NODISCARD const T&& unwrap() const && {
ASSERT(holds_value, "Optional does not contain a value");
return std::move(uvalue);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return holds_value ? uvalue : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return holds_value ? std::move(uvalue) : static_cast<T>(std::forward<U>(default_value));
}
};
extern std::atomic_bool absorb_trace_exceptions;
template<typename T, typename E, typename std::enable_if<!std::is_same<T, E>::value, int>::type = 0>
class Result {
union {
T value_;
E error_;
};
enum class member { value, error };
member active;
public:
Result(T&& value) : value_(std::move(value)), active(member::value) {}
Result(E&& error) : error_(std::move(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(T& value) : value_(T(value)), active(member::value) {}
Result(E& error) : error_(E(error)), active(member::error) {
if(!absorb_trace_exceptions.load()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
Result(Result&& other) : active(other.active) {
if(other.active == member::value) {
new (&value_) T(std::move(other.value_));
} else {
new (&error_) E(std::move(other.error_));
}
}
~Result() {
if(active == member::value) {
value_.~T();
} else {
error_.~E();
}
}
bool has_value() const {
return active == member::value;
}
bool is_error() const {
return active == member::error;
}
explicit operator bool() const {
return has_value();
}
NODISCARD optional<T> value() const & {
return has_value() ? value_ : nullopt;
}
NODISCARD optional<E> error() const & {
return is_error() ? error_ : nullopt;
}
NODISCARD optional<T> value() && {
return has_value() ? std::move(value_) : nullopt;
}
NODISCARD optional<E> error() && {
return is_error() ? std::move(error_) : nullopt;
}
NODISCARD T& unwrap_value() & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD const T& unwrap_value() const & {
ASSERT(has_value(), "Result does not contain a value");
return value_;
}
NODISCARD T unwrap_value() && {
ASSERT(has_value(), "Result does not contain a value");
return std::move(value_);
}
NODISCARD E& unwrap_error() & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD const E& unwrap_error() const & {
ASSERT(is_error(), "Result does not contain an error");
return error_;
}
NODISCARD E unwrap_error() && {
ASSERT(is_error(), "Result does not contain an error");
return std::move(error_);
}
template<typename U>
NODISCARD T value_or(U&& default_value) const & {
return has_value() ? value_ : static_cast<T>(std::forward<U>(default_value));
}
template<typename U>
NODISCARD T value_or(U&& default_value) && {
return has_value() ? std::move(value_) : static_cast<T>(std::forward<U>(default_value));
}
void drop_error() const {
if(is_error()) {
std::fprintf(stderr, "%s\n", unwrap_error().what());
}
}
};
struct monostate {};
// TODO: Re-evaluate use of off_t
template<typename T, typename std::enable_if<std::is_trivial<T>::value, int>::type = 0>
Result<T, internal_error> load_bytes(std::FILE* object_file, off_t offset) {
T object;
if(std::fseek(object_file, offset, SEEK_SET) != 0) {
return internal_error("fseek error");
}
if(std::fread(&object, sizeof(T), 1, object_file) != 1) {
return internal_error("fread error");
}
return object;
}
// shamelessly stolen from stackoverflow
inline bool directory_exists(const std::string& path) {
#if IS_WINDOWS
DWORD dwAttrib = GetFileAttributesA(path.c_str());
return dwAttrib != INVALID_FILE_ATTRIBUTES && (dwAttrib & FILE_ATTRIBUTE_DIRECTORY);
#else
struct stat sb;
return stat(path.c_str(), &sb) == 0 && S_ISDIR(sb.st_mode);
#endif
}
inline std::string basename(const std::string& path) {
// Assumes no trailing /'s
auto pos = path.rfind('/');
if(pos == std::string::npos) {
return path;
} else {
return path.substr(pos + 1);
}
}
// A way to cast to unsigned long long without "warning: useless cast to type"
template<typename T>
unsigned long long to_ull(T t) {
return static_cast<unsigned long long>(t);
}
template<typename T>
frame_ptr to_frame_ptr(T t) {
return static_cast<frame_ptr>(t);
}
// A way to cast to U without "warning: useless cast to type"
template<typename U, typename V>
U to(V v) {
return static_cast<U>(v);
}
// TODO: Rework some stuff here. Not sure deleters should be optional or moved.
// Also allow file_wrapper file = std::fopen(object_path.c_str(), "rb");
template<
typename T,
typename D
// workaround a msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
#if !defined(_MSC_VER) || _MSC_VER != 1938
,
typename std::enable_if<
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
int
>::type = 0,
typename std::enable_if<
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
int
>::type = 0,
typename std::enable_if<
std::is_nothrow_move_constructible<T>::value,
int
>::type = 0
#endif
>
class raii_wrapper {
T obj;
optional<D> deleter;
public:
raii_wrapper(T obj, D deleter) : obj(obj), deleter(deleter) {}
raii_wrapper(raii_wrapper&& other) noexcept : obj(std::move(other.obj)), deleter(std::move(other.deleter)) {
other.deleter = nullopt;
}
raii_wrapper(const raii_wrapper&) = delete;
raii_wrapper& operator=(raii_wrapper&&) = delete;
raii_wrapper& operator=(const raii_wrapper&) = delete;
~raii_wrapper() {
if(deleter.has_value()) {
deleter.unwrap()(obj);
}
}
operator T&() {
return obj;
}
operator const T&() const {
return obj;
}
T& get() {
return obj;
}
const T& get() const {
return obj;
}
};
template<
typename T,
typename D
// workaround a msvc bug https://developercommunity.visualstudio.com/t/MSVC-1938331290-preview-fails-to-comp/10505565
#if !defined(_MSC_VER) || _MSC_VER != 1938
,
typename std::enable_if<
std::is_same<decltype(std::declval<D>()(std::declval<T>())), void>::value,
int
>::type = 0,
typename std::enable_if<
std::is_standard_layout<T>::value && std::is_trivial<T>::value,
int
>::type = 0
#endif
>
raii_wrapper<typename std::remove_reference<T>::type, D> raii_wrap(T obj, D deleter) {
return raii_wrapper<typename std::remove_reference<T>::type, D>(obj, deleter);
}
inline void file_deleter(std::FILE* ptr) {
if(ptr) {
fclose(ptr);
}
}
using file_wrapper = raii_wrapper<std::FILE*, void(*)(std::FILE*)>;
template<typename T>
class maybe_owned {
std::unique_ptr<T> owned;
T* ptr;
public:
maybe_owned(T* ptr) : ptr(ptr) {}
maybe_owned(std::unique_ptr<T>&& owned) : owned(std::move(owned)), ptr(this->owned.get()) {}
T* operator->() {
return ptr;
}
};
}
}
#endif

View File

@ -96,8 +96,10 @@ void IniFileConfigurationTest::testLoad()
}
catch (Poco::IOException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "Broken input stream");
#endif
}
}

View File

@ -94,8 +94,10 @@ void PropertyFileConfigurationTest::testLoad()
}
catch (Poco::IOException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "Broken input stream");
#endif
}
}

View File

@ -51,8 +51,10 @@ void ValidatorTest::testRegExpValidator()
}
catch (InvalidArgumentException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "argument for option does not match regular expression [0-9]+");
#endif
}
try
@ -62,8 +64,10 @@ void ValidatorTest::testRegExpValidator()
}
catch (InvalidArgumentException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "argument for option does not match regular expression [0-9]+");
#endif
}
try
@ -73,8 +77,10 @@ void ValidatorTest::testRegExpValidator()
}
catch (InvalidArgumentException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "argument for option does not match regular expression [0-9]+");
#endif
}
try
@ -84,8 +90,10 @@ void ValidatorTest::testRegExpValidator()
}
catch (InvalidArgumentException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "argument for option does not match regular expression [0-9]+");
#endif
}
}
@ -106,8 +114,10 @@ void ValidatorTest::testIntValidator()
}
catch (InvalidArgumentException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "argument for option must be in range 0 to 100");
#endif
}
try
@ -117,8 +127,10 @@ void ValidatorTest::testIntValidator()
}
catch (InvalidArgumentException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "argument for option must be in range 0 to 100");
#endif
}
try
@ -128,8 +140,10 @@ void ValidatorTest::testIntValidator()
}
catch (InvalidArgumentException& exc)
{
#ifndef POCO_ENABLE_TRACE
std::string s(exc.message());
assertTrue (s == "argument for option must be an integer");
#endif
}
}

View File

@ -14,9 +14,11 @@
# POCO_TARGET_OSARCH: Target system architecture (forr cross builds)
#
ifndef POCO_ENABLE_TRACE
target := $(strip $(target))
target_version := $(strip $(target_version))
target_libs := $(strip $(target_libs))
endif
#
# Check for POCO_BASE
@ -338,12 +340,15 @@ LIBRARY = -L$(LIBPATH) $(POCO_LIBRARY) $(MACCHINA_LIBRARY) $(foreach p,$(POCO_AD
#
# Strip Command definition
#
ifndef POCO_ENABLE_TRACE
ifeq ($(strip $(STRIP)),)
STRIPCMD =
else
STRIPCMD = $(STRIP) $@$(BINEXT)
endif
else
STRIPCMD =
endif
#
# Make CC and CXX environment vars
#

View File

@ -2,7 +2,7 @@
#
# A script for running the POCO testsuites.
#
# usage: runtests [component [test] ]
# usage: runtests [component [test] [d]]
#
# If the environment variable EXCLUDE_TESTS is set, containing
# a space-separated list of project names (as found in the
@ -87,7 +87,7 @@ do
echo ""
runs=$((runs + 1))
if ! sh -c "export POCO_BASE='$POCO_BASE'; export OSNAME='$OSNAME'; export OSARCH='$OSARCH'; cd $POCO_BUILD/$comp/testsuite/$BINDIR && PATH=.:$PATH && LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $TESTRUNNER $IGNORE $TESTRUNNERARGS";
if ! sh -c "export POCO_BASE='$POCO_BASE'; export OSNAME='$OSNAME'; export OSARCH='$OSARCH'; cd $POCO_BUILD/$comp/testsuite/$BINDIR && PATH=.:$PATH && LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH $TESTRUNNER$2 $IGNORE $TESTRUNNERARGS";
then
failures=$((failures + 1))
failedTests="$failedTests $comp"

View File

@ -12,4 +12,4 @@ if [ "TSAN" = "$1" ]
then
export TSAN_OPTIONS="suppressions=$POCO_BASE/tsan.suppress,second_deadlock_stack=1"
fi
build/script/runtests.sh
build/script/runtests.sh "" "$2"

View File

@ -30,3 +30,4 @@ DNSSD/Avahi
DNSSD/Bonjour
PocoDoc
ProGen
Trace

19
configure vendored
View File

@ -138,6 +138,9 @@ $(ls -C "$base"/build/config/)
Use system-provided zlib, pcre, expat and sqlite instead of
bundled ones.
--trace
Enable stack trace. Implicitly disables stripping in release build.
--static
Build static libraries. Overrides default mode, which
depends upon target. Can be specified together
@ -168,6 +171,7 @@ librarypath=""
odbclib=""
odbcinclude=""
unbundled=""
trace=""
static=""
shared=""
nosqlparser=
@ -261,8 +265,13 @@ while [ $# -ge 1 ]; do
flags="$flags -DPOCO_NO_FILECHANNEL -DPOCO_NO_SPLITTERCHANNEL -DPOCO_NO_SYSLOGCHANNEL -DPOCO_UTIL_NO_INIFILECONFIGURATION -DPOCO_UTIL_NO_JSONCONFIGURATION -DPOCO_UTIL_NO_XMLCONFIGURATION" ;;
--unbundled)
flags="$flags -DPOCO_UNBUNDLED"
unbundled=1
flags="$flags -DPOCO_UNBUNDLED"
unbundled=1
;;
--trace)
flags="$flags -DPOCO_ENABLE_TRACE"
trace=1
;;
--minimal)
@ -383,6 +392,9 @@ fi
if [ -n "$unbundled" ] ; then
echo "POCO_UNBUNDLED = 1" >>"$build"/config.make
fi
if [ -n "$trace" ] ; then
echo "POCO_ENABLE_TRACE = 1" >>"$build"/config.make
fi
if [ -n "$linkmode" ] ; then
echo "LINKMODE = $linkmode" >>"$build"/config.make
fi
@ -431,6 +443,9 @@ fi
if [ -n "$unbundled" ] ; then
echo "export POCO_UNBUNDLED" >>"$build"/config.make
fi
if [ -n "$trace" ] ; then
echo "export POCO_ENABLE_TRACE" >>"$build"/config.make
fi
if [ -n "$linkmode" ] ; then
echo "export LINKMODE" >>"$build"/config.make
fi

View File

@ -24,7 +24,7 @@ export OSARCH
POCO_BASE="$basedir"
export POCO_BASE
POCO_LIB_PATH=$POCO_BASE/lib/$OSNAME/$OSARCH
POCO_LIB_PATH=$POCO_BASE/lib/$OSNAME/$OSARCH:$POCO_BASE/lib/$OSNAME/$OSARCH/lib
POCO_BIN_PATH=$POCO_BASE/Foundation/testsuite/bin/$OSNAME/$OSARCH
POCO_PATHS=$POCO_BASE:$POCO_LIB_PATH:$POCO_BIN_PATH
if [[ "$PATH" != *"$POCO_PATHS"* ]]; then