mirror of
https://github.com/pocoproject/poco.git
synced 2024-12-12 10:13:51 +01:00
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:
parent
a1efeaa72d
commit
eaabd3ff8d
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@ -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
|
||||
|
3
.vscode/c_cpp_properties.json
vendored
3
.vscode/c_cpp_properties.json
vendored
@ -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": [
|
||||
|
@ -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
|
||||
|
@ -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}")
|
||||
|
@ -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} )
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -17,3 +17,4 @@ add_subdirectory(hmacmd5)
|
||||
add_subdirectory(inflate)
|
||||
add_subdirectory(md5)
|
||||
add_subdirectory(uuidgen)
|
||||
add_subdirectory(trace)
|
||||
|
@ -26,3 +26,4 @@ projects:
|
||||
$(MAKE) -C StringTokenizer $(MAKECMDGOALS)
|
||||
$(MAKE) -C URI $(MAKECMDGOALS)
|
||||
$(MAKE) -C uuidgen $(MAKECMDGOALS)
|
||||
$(MAKE) -C trace $(MAKECMDGOALS)
|
||||
|
2
Foundation/samples/trace/CMakeLists.txt
Normal file
2
Foundation/samples/trace/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_executable(sample-trace src/trace.cpp)
|
||||
target_link_libraries(sample-trace PUBLIC Poco::Foundation)
|
25
Foundation/samples/trace/Makefile
Normal file
25
Foundation/samples/trace/Makefile
Normal 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
|
11
Foundation/samples/trace/Trace.progen
Normal file
11
Foundation/samples/trace/Trace.progen
Normal 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
|
447
Foundation/samples/trace/Trace_vs90.vcproj
Normal file
447
Foundation/samples/trace/Trace_vs90.vcproj
Normal 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>
|
51
Foundation/samples/trace/src/trace.cpp
Normal file
51
Foundation/samples/trace/src/trace.cpp
Normal 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;
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
||||
|
16
Makefile
16
Makefile
@ -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
|
||||
|
@ -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
70
Trace/CMakeLists.txt
Normal 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
19
Trace/Makefile
Normal 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
36
Trace/Makefile-Trace
Normal 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
17
Trace/Trace.progen
Normal 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
60
Trace/Trace_vs90.sln
Normal 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
523
Trace/Trace_vs90.vcproj
Normal 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>
|
3
Trace/cmake/PocoTraceConfig.cmake
Normal file
3
Trace/cmake/PocoTraceConfig.cmake
Normal 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
2
Trace/include/Poco/Trace/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
backtrace
|
||||
|
62
Trace/include/Poco/Trace/Trace.h
Normal file
62
Trace/include/Poco/Trace/Trace.h
Normal 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
|
496
Trace/include/Poco/Trace/cpptrace/cpptrace.hpp
Normal file
496
Trace/include/Poco/Trace/cpptrace/cpptrace.hpp
Normal 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
|
131
Trace/include/Poco/Trace/cpptrace/from_current.hpp
Normal file
131
Trace/include/Poco/Trace/cpptrace/from_current.hpp
Normal 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
|
163
Trace/include/Poco/Trace/ctrace/ctrace.h
Normal file
163
Trace/include/Poco/Trace/ctrace/ctrace.h
Normal 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
20
Trace/src/binary/elf.hpp
Normal 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
137
Trace/src/binary/mach-o.hpp
Normal 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
|
16
Trace/src/binary/module_base.hpp
Normal file
16
Trace/src/binary/module_base.hpp
Normal 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
|
21
Trace/src/binary/object.hpp
Normal file
21
Trace/src/binary/object.hpp
Normal 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
19
Trace/src/binary/pe.hpp
Normal 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
|
12
Trace/src/binary/safe_dl.hpp
Normal file
12
Trace/src/binary/safe_dl.hpp
Normal 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
698
Trace/src/cpptrace.cpp
Normal 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
442
Trace/src/ctrace.cpp
Normal 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};
|
||||
}
|
||||
}
|
||||
}
|
207
Trace/src/debug_map_resolver.cpp
Normal file
207
Trace/src/debug_map_resolver.cpp
Normal 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
|
12
Trace/src/demangle/demangle.hpp
Normal file
12
Trace/src/demangle/demangle.hpp
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef DEMANGLE_HPP
|
||||
#define DEMANGLE_HPP
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace cpptrace {
|
||||
namespace detail {
|
||||
std::string demangle(const std::string&);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
31
Trace/src/demangle_with_cxxabi.cpp
Normal file
31
Trace/src/demangle_with_cxxabi.cpp
Normal 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
|
15
Trace/src/demangle_with_nothing.cpp
Normal file
15
Trace/src/demangle_with_nothing.cpp
Normal 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
|
25
Trace/src/demangle_with_winapi.cpp
Normal file
25
Trace/src/demangle_with_winapi.cpp
Normal 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
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
100
Trace/src/elf.cpp
Normal 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
310
Trace/src/from_current.cpp
Normal 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
641
Trace/src/mach-o.cpp
Normal 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
95
Trace/src/module_base.cpp
Normal 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
183
Trace/src/object.cpp
Normal 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
95
Trace/src/pe.cpp
Normal 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
|
43
Trace/src/platform/dbghelp_syminit_manager.hpp
Normal file
43
Trace/src/platform/dbghelp_syminit_manager.hpp
Normal 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
|
27
Trace/src/platform/exception_type.hpp
Normal file
27
Trace/src/platform/exception_type.hpp
Normal 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
|
42
Trace/src/platform/path.hpp
Normal file
42
Trace/src/platform/path.hpp
Normal 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
|
48
Trace/src/platform/platform.hpp
Normal file
48
Trace/src/platform/platform.hpp
Normal 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
|
100
Trace/src/platform/program_name.hpp
Normal file
100
Trace/src/platform/program_name.hpp
Normal 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
68
Trace/src/safe_dl.cpp
Normal 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
142
Trace/src/snippet.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
14
Trace/src/snippets/snippet.hpp
Normal file
14
Trace/src/snippets/snippet.hpp
Normal 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
|
539
Trace/src/symbols/dwarf/dwarf.hpp
Normal file
539
Trace/src/symbols/dwarf/dwarf.hpp
Normal 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
|
56
Trace/src/symbols/dwarf/resolver.hpp
Normal file
56
Trace/src/symbols/dwarf/resolver.hpp
Normal 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
|
72
Trace/src/symbols/symbols.hpp
Normal file
72
Trace/src/symbols/symbols.hpp
Normal 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
151
Trace/src/symbols_core.cpp
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
322
Trace/src/symbols_with_addr2line.cpp
Normal file
322
Trace/src/symbols_with_addr2line.cpp
Normal 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
|
456
Trace/src/symbols_with_dbghelp.cpp
Normal file
456
Trace/src/symbols_with_dbghelp.cpp
Normal 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
|
55
Trace/src/symbols_with_dl.cpp
Normal file
55
Trace/src/symbols_with_dl.cpp
Normal 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
|
106
Trace/src/symbols_with_libbacktrace.cpp
Normal file
106
Trace/src/symbols_with_libbacktrace.cpp
Normal 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
|
145
Trace/src/symbols_with_libdwarf.cpp
Normal file
145
Trace/src/symbols_with_libdwarf.cpp
Normal 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
|
22
Trace/src/symbols_with_nothing.cpp
Normal file
22
Trace/src/symbols_with_nothing.cpp
Normal 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
|
28
Trace/src/unwind/unwind.hpp
Normal file
28
Trace/src/unwind/unwind.hpp
Normal 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
|
171
Trace/src/unwind_with_dbghelp.cpp
Normal file
171
Trace/src/unwind_with_dbghelp.cpp
Normal 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
|
45
Trace/src/unwind_with_execinfo.cpp
Normal file
45
Trace/src/unwind_with_execinfo.cpp
Normal 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
|
87
Trace/src/unwind_with_libunwind.cpp
Normal file
87
Trace/src/unwind_with_libunwind.cpp
Normal 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
|
25
Trace/src/unwind_with_nothing.cpp
Normal file
25
Trace/src/unwind_with_nothing.cpp
Normal 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
|
75
Trace/src/unwind_with_unwind.cpp
Normal file
75
Trace/src/unwind_with_unwind.cpp
Normal 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
|
53
Trace/src/unwind_with_winapi.cpp
Normal file
53
Trace/src/unwind_with_winapi.cpp
Normal 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
|
47
Trace/src/utils/common.hpp
Normal file
47
Trace/src/utils/common.hpp
Normal 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
171
Trace/src/utils/error.hpp
Normal 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
|
305
Trace/src/utils/microfmt.hpp
Normal file
305
Trace/src/utils/microfmt.hpp
Normal 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
588
Trace/src/utils/utils.hpp
Normal 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*)#
|
||||
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
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
#
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -30,3 +30,4 @@ DNSSD/Avahi
|
||||
DNSSD/Bonjour
|
||||
PocoDoc
|
||||
ProGen
|
||||
Trace
|
||||
|
19
configure
vendored
19
configure
vendored
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user