Initial commit
This commit is contained in:
commit
b3474b50c3
41
.github/workflows/build.yml
vendored
Normal file
41
.github/workflows/build.yml
vendored
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
name: build c++20
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-gcc:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: dependencies
|
||||||
|
run: sudo apt install gcc-10 g++-10
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=g++-10
|
||||||
|
make
|
||||||
|
build-clang:
|
||||||
|
runs-on: ubuntu-22.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=clang++-14
|
||||||
|
make
|
||||||
|
build-msvc:
|
||||||
|
runs-on: windows-2019
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Enable Developer Command Prompt
|
||||||
|
uses: ilammy/msvc-dev-cmd@v1.10.0
|
||||||
|
- name: build
|
||||||
|
run: |
|
||||||
|
mkdir -p build
|
||||||
|
cd build
|
||||||
|
cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=cl
|
||||||
|
make
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.vscode
|
||||||
|
build
|
||||||
|
a.out
|
||||||
|
test/build
|
||||||
261
CMakeLists.txt
Normal file
261
CMakeLists.txt
Normal file
@ -0,0 +1,261 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.8...3.23)
|
||||||
|
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||||
|
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(
|
||||||
|
libcpptrace
|
||||||
|
VERSION 1.0.0
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
|
||||||
|
|
||||||
|
#set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
include(CheckCXXSourceCompiles)
|
||||||
|
|
||||||
|
file(GLOB_RECURSE sources src/*.cpp)
|
||||||
|
#message(STATUS "Sources" ${sources})
|
||||||
|
add_library(cpptrace ${sources} include/cpptrace/cpptrace.hpp)
|
||||||
|
|
||||||
|
target_include_directories(
|
||||||
|
cpptrace
|
||||||
|
PUBLIC
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include/>
|
||||||
|
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/cpptrace/cpptrace>
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_features(
|
||||||
|
cpptrace
|
||||||
|
PUBLIC
|
||||||
|
cxx_std_11
|
||||||
|
)
|
||||||
|
|
||||||
|
set_target_properties(
|
||||||
|
cpptrace
|
||||||
|
PROPERTIES
|
||||||
|
CXX_STANDARD_REQUIRED TRUE
|
||||||
|
CXX_EXTENSIONS OFF
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_options(
|
||||||
|
cpptrace
|
||||||
|
PRIVATE
|
||||||
|
$<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-Wall -Wextra -Werror=return-type -Wshadow>
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>:/W4 /WX /permissive->
|
||||||
|
)
|
||||||
|
|
||||||
|
function(check_support var source includes libraries definitions)
|
||||||
|
set(CMAKE_REQUIRED_INCLUDES "${includes}")
|
||||||
|
list(APPEND CMAKE_REQUIRED_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||||
|
set(CMAKE_REQUIRED_LIBRARIES "${libraries}")
|
||||||
|
set(CMAKE_REQUIRED_DEFINITIONS "${definitions}")
|
||||||
|
check_cxx_source_compiles("#include \"${source}\"" ${var})
|
||||||
|
set(${var} ${${var}} PARENT_SCOPE)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
option(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE "" OFF)
|
||||||
|
|
||||||
|
option(LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE "" OFF)
|
||||||
|
option(LIBCPPTRACE_GET_SYMBOLS_WITH_LIBDL "" OFF)
|
||||||
|
option(LIBCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE "" OFF)
|
||||||
|
option(LIBCPPTRACE_GET_SYMBOLS_WITH_DBGHELP "" OFF)
|
||||||
|
option(LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING "" OFF)
|
||||||
|
|
||||||
|
option(LIBCPPTRACE_UNWIND_WITH_EXECINFO "" OFF)
|
||||||
|
option(LIBCPPTRACE_UNWIND_WITH_WINAPI "" OFF)
|
||||||
|
option(LIBCPPTRACE_UNWIND_WITH_NOTHING "" OFF)
|
||||||
|
|
||||||
|
option(LIBCPPTRACE_DEMANGLE_WITH_CXXABI "" OFF)
|
||||||
|
option(LIBCPPTRACE_DEMANGLE_WITH_NOTHING "" OFF)
|
||||||
|
|
||||||
|
# =============================================== Autoconfig full dump ===============================================
|
||||||
|
# If nothing is specified, attempt to use libbacktrace's full dump
|
||||||
|
if(
|
||||||
|
NOT (
|
||||||
|
LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
|
||||||
|
LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
|
||||||
|
LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING OR
|
||||||
|
LIBCPPTRACE_UNWIND_WITH_EXECINFO OR
|
||||||
|
LIBCPPTRACE_UNWIND_WITH_WINAPI OR
|
||||||
|
LIBCPPTRACE_UNWIND_WITH_NOTHING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Attempt to auto-config
|
||||||
|
check_support(HAS_BACKTRACE has_backtrace.cpp "" "backtrace" "")
|
||||||
|
|
||||||
|
if(HAS_BACKTRACE)
|
||||||
|
set(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
|
||||||
|
message(STATUS "Cpptrace auto config: Using libbacktrace for the full trace")
|
||||||
|
endif()
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# =============================================== Autoconfig unwinding ===============================================
|
||||||
|
# Unwind back-ends (If not doing LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
|
||||||
|
if(
|
||||||
|
NOT (
|
||||||
|
LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
|
||||||
|
LIBCPPTRACE_UNWIND_WITH_EXECINFO OR
|
||||||
|
LIBCPPTRACE_UNWIND_WITH_WINAPI OR
|
||||||
|
LIBCPPTRACE_UNWIND_WITH_NOTHING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Attempt to auto-config
|
||||||
|
if(UNIX)
|
||||||
|
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
|
||||||
|
|
||||||
|
if(HAS_EXECINFO)
|
||||||
|
set(LIBCPPTRACE_UNWIND_WITH_EXECINFO On)
|
||||||
|
message(STATUS "Cpptrace auto config: Using execinfo.h for unwinding")
|
||||||
|
else()
|
||||||
|
set(LIBCPPTRACE_UNWIND_WITH_NOTHING On)
|
||||||
|
message(WARNING "Cpptrace auto config: <execinfo.h> doesn't seem to be supported, stack tracing will not work.")
|
||||||
|
endif()
|
||||||
|
elseif(WIN32)
|
||||||
|
set(LIBCPPTRACE_UNWIND_WITH_WINAPI On)
|
||||||
|
message(STATUS "Cpptrace auto config: Using winapi for unwinding")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
#message(STATUS "MANUAL CONFIG SPECIFIED")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# =============================================== Autoconfig symbols ===============================================
|
||||||
|
# Symbol back-ends (If not doing LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
|
||||||
|
if(
|
||||||
|
NOT (
|
||||||
|
LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE OR
|
||||||
|
LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE OR
|
||||||
|
LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Attempt to auto-config
|
||||||
|
if(UNIX)
|
||||||
|
check_support(HAS_EXECINFO has_execinfo.cpp "" "" "")
|
||||||
|
|
||||||
|
if(HAS_EXECINFO)
|
||||||
|
set(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE On)
|
||||||
|
message(STATUS "Cpptrace auto config: Using libbacktrace for symbols")
|
||||||
|
else()
|
||||||
|
message(WARNING "Cpptrace auto config: No symbol back end could be automatically configured")
|
||||||
|
endif()
|
||||||
|
elseif(WIN32)
|
||||||
|
#set(LIBCPPTRACE_UNWIND_WITH_WINAPI On)
|
||||||
|
#message(STATUS "Cpptrace auto config: Using winapi for unwinding")
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
#message(STATUS "MANUAL CONFIG SPECIFIED")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# =============================================== Autoconfig demangling ===============================================
|
||||||
|
# Handle demangle configuration
|
||||||
|
if(
|
||||||
|
NOT (
|
||||||
|
LIBCPPTRACE_DEMANGLE_WITH_CXXABI OR
|
||||||
|
LIBCPPTRACE_DEMANGLE_WITH_NOTHING
|
||||||
|
)
|
||||||
|
)
|
||||||
|
check_support(HAS_CXXABI has_cxxabi.cpp "" "" "")
|
||||||
|
if(HAS_CXXABI)
|
||||||
|
set(LIBCPPTRACE_DEMANGLE_WITH_CXXABI On)
|
||||||
|
else()
|
||||||
|
set(LIBCPPTRACE_DEMANGLE_WITH_NOTHING On)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
#message(STATUS "Manual demangling back-end specified")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# =============================================== Apply options to build ===============================================
|
||||||
|
|
||||||
|
# Full
|
||||||
|
if(LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
|
||||||
|
target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE)
|
||||||
|
target_link_libraries(cpptrace PRIVATE backtrace)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Symbols
|
||||||
|
if(LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
|
||||||
|
target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Unwinding
|
||||||
|
if(LIBCPPTRACE_UNWIND_WITH_EXECINFO)
|
||||||
|
target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_UNWIND_WITH_EXECINFO)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LIBCPPTRACE_UNWIND_WITH_WINAPI)
|
||||||
|
target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_UNWIND_WITH_WINAPI)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Demangling
|
||||||
|
if(LIBCPPTRACE_DEMANGLE_WITH_CXXABI)
|
||||||
|
target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_DEMANGLE_WITH_CXXABI)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(LIBCPPTRACE_DEMANGLE_WITH_NOTHING)
|
||||||
|
target_compile_definitions(cpptrace PUBLIC LIBCPPTRACE_DEMANGLE_WITH_NOTHING)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(
|
||||||
|
cpptrace
|
||||||
|
PRIVATE
|
||||||
|
$<$<CXX_COMPILER_ID:MSVC>:dbghelp>
|
||||||
|
${CMAKE_DL_LIBS}
|
||||||
|
)
|
||||||
|
|
||||||
|
if(CMAKE_BUILD_TYPE STREQUAL "")
|
||||||
|
message(FATAL_ERROR "Setting CMAKE_BUILD_TYPE is required")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(NOT CMAKE_SKIP_INSTALL_RULES)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
|
install(
|
||||||
|
TARGETS cpptrace
|
||||||
|
EXPORT cpptrace_targets
|
||||||
|
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
|
||||||
|
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
include/cpptrace.hpp
|
||||||
|
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/cpptrace/cpptrace
|
||||||
|
)
|
||||||
|
|
||||||
|
export(
|
||||||
|
EXPORT cpptrace_targets
|
||||||
|
FILE ${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace_targets.cmake
|
||||||
|
NAMESPACE cpptrace::
|
||||||
|
)
|
||||||
|
|
||||||
|
configure_package_config_file(
|
||||||
|
cmake/cpptrace-config.cmake.in
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config.cmake
|
||||||
|
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace
|
||||||
|
)
|
||||||
|
|
||||||
|
write_basic_package_version_file(
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config-version.cmake
|
||||||
|
VERSION ${PACKAGE_VERSION}
|
||||||
|
COMPATIBILITY SameMajorVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
EXPORT cpptrace_targets
|
||||||
|
FILE cpptrace_targets.cmake
|
||||||
|
NAMESPACE cpptrace::
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace
|
||||||
|
)
|
||||||
|
|
||||||
|
install(
|
||||||
|
FILES
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config.cmake
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/cpptrace/cpptrace-config-version.cmake
|
||||||
|
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/cpptrace
|
||||||
|
)
|
||||||
|
endif()
|
||||||
18
LICENSE
Normal file
18
LICENSE
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2023 Jeremy Rifkin
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||||
|
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||||
|
including without limitation the rights to use, copy, modify, merge, publish, distribute,
|
||||||
|
sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||||
|
portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
||||||
|
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES
|
||||||
|
OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
74
README.md
Normal file
74
README.md
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
# libcpptrace
|
||||||
|
|
||||||
|
Libcpptrace is a lightweight C++ stacktrace library supporting C++11 and greater on Linux, Unix, MacOS, and Windows
|
||||||
|
(including cygwin / mingw environments). The goal: Make traces simple for once.
|
||||||
|
|
||||||
|
*Some day C++23's `<stacktrace>` will be ubiquitous*
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
|
||||||
|
- [libcpptrace](#libcpptrace)
|
||||||
|
- [Table of contents](#table-of-contents)
|
||||||
|
- [Docs](#docs)
|
||||||
|
- [Backends](#backends)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
`cpptrace::print_trace()` can be used to print a stacktrace at the current call site, `cpptrace::generate_trace()` can
|
||||||
|
be used to get raw frame information for custom use.
|
||||||
|
|
||||||
|
**Note:** Debug info (`-g`) is generally required for good trace information. Some back-ends read symbols from dynamic
|
||||||
|
export information which requires `-rdynamic` or manually marking symbols for exporting.
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
namespace cpptrace {
|
||||||
|
struct stacktrace_frame {
|
||||||
|
size_t line;
|
||||||
|
size_t col;
|
||||||
|
std::string filename;
|
||||||
|
std::string symbol;
|
||||||
|
};
|
||||||
|
std::vector<stacktrace_frame> generate_trace();
|
||||||
|
void print_trace();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Back-ends
|
||||||
|
|
||||||
|
Back-end libraries are required for unwinding the stack and resolving symbol information (name and source location) in
|
||||||
|
order to generate a stacktrace.
|
||||||
|
|
||||||
|
The CMake script attempts to automatically choose a good back-end based on what is available on your system. You can
|
||||||
|
also manually set which back-end you want used.
|
||||||
|
|
||||||
|
**Unwinding**
|
||||||
|
|
||||||
|
| Library | CMake config | Windows | Linux | Info |
|
||||||
|
|---------|--------------|---------|-------|------|
|
||||||
|
| execinfo.h | `LIBCPPTRACE_UNWIND_WITH_EXECINFO` | | ✔️ | Frames are captured with `execinfo.h`'s `backtrace`, part of libc. |
|
||||||
|
| winapi | `LIBCPPTRACE_UNWIND_WITH_WINAPI` | ✔️ | | Frames are captured with `CaptureStackBackTrace`. |
|
||||||
|
| N/A | `LIBCPPTRACE_UNWIND_WITH_NOTHING` | ✔️ | ✔️ | Unwinding is not done, stack traces will be empty. |
|
||||||
|
|
||||||
|
**Symbol resolution**
|
||||||
|
|
||||||
|
| Library | CMake config | Windows | Linux | Info |
|
||||||
|
|---------|--------------|---------|-------|------|
|
||||||
|
| libbacktrace | `LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE` | ❌ | ✔️ | Libbacktrace is already installed on most systems, or available through the compiler directly. |
|
||||||
|
| libdl | `LIBCPPTRACE_GET_SYMBOLS_WITH_LIBDL` | | ✔️ | Libdl uses dynamic export information. Compiling with `-rdynamic` is often needed. |
|
||||||
|
| addr2line | `LIBCPPTRACE_GET_SYMBOLS_WITH_ADDR2LINE` | | ✔️ | Symbols are resolved by invoking `addr2line` via `fork()`. |
|
||||||
|
| dbghelp.h | `LIBCPPTRACE_GET_SYMBOLS_WITH_DBGHELP` | ✔️ | ❌ | Dbghelp.h allows access to symbols via debug info. |
|
||||||
|
| N/A | `LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING` | ✔️ | ✔️ | Don't attempt to resolve symbols. |
|
||||||
|
|
||||||
|
Lastly, C++ symbol demangling is done with `<cxxabi.h>`. Under dbghelp.h this is not needed, the symbols aren't mangled
|
||||||
|
when they are first extracted.
|
||||||
|
|
||||||
|
Libbacktrace can generate a full stack trace itself, both unwinding and resolving symbols, and this can be chosen with
|
||||||
|
`LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE`.
|
||||||
|
|
||||||
|
There are plenty more libraries that can be used for unwinding, parsing debug information, and demangling. In the future
|
||||||
|
more options may be added.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The library is under the MIT license.
|
||||||
3
cmake/cpptrace-config.cmake.in
Normal file
3
cmake/cpptrace-config.cmake.in
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@PACKAGE_INIT@
|
||||||
|
|
||||||
|
include(${CMAKE_CURRENT_LIST_DIR}/cpptrace_targets.cmake)
|
||||||
5
cmake/has_backtrace.cpp
Normal file
5
cmake/has_backtrace.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include <backtrace.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
backtrace_state* state = backtrace_create_state(nullptr, true, nullptr, nullptr);
|
||||||
|
}
|
||||||
6
cmake/has_cxxabi.cpp
Normal file
6
cmake/has_cxxabi.cpp
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
#include <execinfo.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
void* frames[10];
|
||||||
|
int size = backtrace(frames, 10);
|
||||||
|
}
|
||||||
5
cmake/has_execinfo.cpp
Normal file
5
cmake/has_execinfo.cpp
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
#include <backtrace.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
backtrace_state* state = backtrace_create_state(nullptr, true, nullptr, nullptr);
|
||||||
|
}
|
||||||
20
include/cpptrace/cpptrace.hpp
Normal file
20
include/cpptrace/cpptrace.hpp
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef CPPTRACE_HPP
|
||||||
|
#define CPPTRACE_HPP
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
struct stacktrace_frame {
|
||||||
|
uintptr_t address;
|
||||||
|
int line;
|
||||||
|
int col;
|
||||||
|
std::string filename;
|
||||||
|
std::string symbol;
|
||||||
|
};
|
||||||
|
std::vector<stacktrace_frame> generate_trace();
|
||||||
|
void print_trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
64
src/cpptrace.cpp
Normal file
64
src/cpptrace.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#ifndef LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE
|
||||||
|
|
||||||
|
#include "symbols/libcpp_symbols.hpp"
|
||||||
|
#include "unwind/libcpp_unwind.hpp"
|
||||||
|
#include "demangle/libcpp_demangle.hpp"
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
std::vector<stacktrace_frame> generate_trace() {
|
||||||
|
std::vector<void*> frames = detail::capture_frames();
|
||||||
|
std::vector<stacktrace_frame> trace;
|
||||||
|
detail::symbolizer symbolizer;
|
||||||
|
for(const auto frame : frames) {
|
||||||
|
auto entry = symbolizer.resolve_frame(frame);
|
||||||
|
entry.symbol = detail::demangle(entry.symbol);
|
||||||
|
trace.push_back(entry);
|
||||||
|
}
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
// full trace
|
||||||
|
|
||||||
|
#include "full/libcpp_full_trace.hpp"
|
||||||
|
#include "demangle/libcpp_demangle.hpp"
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
std::vector<stacktrace_frame> generate_trace() {
|
||||||
|
auto trace = detail::generate_trace();
|
||||||
|
for(auto& entry : trace) {
|
||||||
|
entry.symbol = detail::demangle(entry.symbol);
|
||||||
|
}
|
||||||
|
return trace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
void print_trace() {
|
||||||
|
std::cerr<<"Stack trace (most recent call first):"<<std::endl;
|
||||||
|
std::size_t i = 0;
|
||||||
|
for(const auto& frame : generate_trace()) {
|
||||||
|
std::cerr
|
||||||
|
<< i++
|
||||||
|
<< " "
|
||||||
|
<< frame.filename
|
||||||
|
<< " at "
|
||||||
|
<< frame.line
|
||||||
|
<< (frame.col > 0 ? ":" + std::to_string(frame.col) : "")
|
||||||
|
<< " "
|
||||||
|
<< frame.symbol
|
||||||
|
<< std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
src/demangle/demangle_with_cxxabi.cpp
Normal file
27
src/demangle/demangle_with_cxxabi.cpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#ifdef LIBCPPTRACE_DEMANGLE_WITH_CXXABI
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_demangle.hpp"
|
||||||
|
|
||||||
|
#include <cxxabi.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
std::string demangle(const std::string& name) {
|
||||||
|
int status;
|
||||||
|
char* demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
|
||||||
|
if(demangled) {
|
||||||
|
std::string s = demangled;
|
||||||
|
free(demangled);
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
19
src/demangle/demangle_with_nothing.cpp
Normal file
19
src/demangle/demangle_with_nothing.cpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifdef LIBCPPTRACE_DEMANGLE_WITH_NOTHING
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_demangle.hpp"
|
||||||
|
|
||||||
|
#include <cxxabi.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
std::string demangle(const std::string& name) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
14
src/demangle/libcpp_demangle.hpp
Normal file
14
src/demangle/libcpp_demangle.hpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef LIBCPP_DEMANGLE_HPP
|
||||||
|
#define LIBCPP_DEMANGLE_HPP
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
std::string demangle(const std::string&);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
47
src/full/full_trace_with_libbacktrace.cpp
Normal file
47
src/full/full_trace_with_libbacktrace.cpp
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
#ifdef LIBCPPTRACE_FULL_TRACE_WITH_LIBBACKTRACE
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_full_trace.hpp"
|
||||||
|
#include "../platform/libcpp_program_name.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <backtrace.h>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) {
|
||||||
|
reinterpret_cast<std::vector<stacktrace_frame>*>(data)->push_back({
|
||||||
|
address,
|
||||||
|
line,
|
||||||
|
-1,
|
||||||
|
file ? file : "",
|
||||||
|
symbol ? symbol : ""
|
||||||
|
});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_callback(void*, const char*, int) {
|
||||||
|
// nothing for now
|
||||||
|
}
|
||||||
|
|
||||||
|
backtrace_state* get_backtrace_state() {
|
||||||
|
// 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().c_str(), true, error_callback, nullptr);
|
||||||
|
called = true;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<stacktrace_frame> generate_trace() {
|
||||||
|
std::vector<stacktrace_frame> frames;
|
||||||
|
backtrace_full(get_backtrace_state(), 0, full_callback, error_callback, &frames);
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
14
src/full/libcpp_full_trace.hpp
Normal file
14
src/full/libcpp_full_trace.hpp
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
#ifndef LIBCPP_FULL_TRACE_HPP
|
||||||
|
#define LIBCPP_FULL_TRACE_HPP
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
std::vector<stacktrace_frame> generate_trace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
35
src/platform/libcpp_program_name.hpp
Normal file
35
src/platform/libcpp_program_name.hpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#ifndef LIBCPP_PROGRAM_NAME_HPP
|
||||||
|
#define LIBCPP_PROGRAM_NAME_HPP
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
inline std::string program_name() {
|
||||||
|
char buffer[MAX_PATH + 1];
|
||||||
|
int res = GetModuleFileNameA(nullptr, buffer, MAX_PATH);
|
||||||
|
if(res) {
|
||||||
|
return buffer;
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
inline std::string program_name() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif
|
||||||
22
src/symbols/libcpp_symbols.hpp
Normal file
22
src/symbols/libcpp_symbols.hpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#ifndef LIBCPP_SYMBOLIZE_HPP
|
||||||
|
#define LIBCPP_SYMBOLIZE_HPP
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
class symbolizer {
|
||||||
|
struct impl;
|
||||||
|
std::unique_ptr<impl> impl;
|
||||||
|
public:
|
||||||
|
symbolizer();
|
||||||
|
~symbolizer();
|
||||||
|
stacktrace_frame resolve_frame(void* addr);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
0
src/symbols/symbols_with_addr2line.cpp
Normal file
0
src/symbols/symbols_with_addr2line.cpp
Normal file
0
src/symbols/symbols_with_dbghelp.cpp
Normal file
0
src/symbols/symbols_with_dbghelp.cpp
Normal file
0
src/symbols/symbols_with_dl.cpp
Normal file
0
src/symbols/symbols_with_dl.cpp
Normal file
63
src/symbols/symbols_with_libbacktrace.cpp
Normal file
63
src/symbols/symbols_with_libbacktrace.cpp
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#ifdef LIBCPPTRACE_GET_SYMBOLS_WITH_LIBBACKTRACE
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_symbolize.hpp"
|
||||||
|
#include "../platform/libcpp_program_name.hpp"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <backtrace.h>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
backtrace_state* get_backtrace_state() {
|
||||||
|
// 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().c_str(), true, error_callback, nullptr);
|
||||||
|
called = true;
|
||||||
|
}
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
int full_callback(void* data, uintptr_t address, const char* file, int line, const char* symbol) {
|
||||||
|
stacktrace_frame& frame = *static_cast<stacktrace_frame*>(data);
|
||||||
|
data.address = address;
|
||||||
|
data.line = line;
|
||||||
|
data.filename = file ? file : "";
|
||||||
|
data.symbol = symbol ? symbol : "";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void error_callback(void*, const char*, int) {
|
||||||
|
// nothing at the moment
|
||||||
|
}
|
||||||
|
|
||||||
|
symbolizer::symbolizer() : impl(std::make_unique<impl>()) {}
|
||||||
|
symbolizer::~symbolizer() = default;
|
||||||
|
|
||||||
|
stacktrace_frame symbolizer::resolve_frame(void* addr) {
|
||||||
|
impl->resolve_frame(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Handle backtrace_pcinfo calling the callback multiple times on inlined functions
|
||||||
|
struct symbolizer::impl {
|
||||||
|
stacktrace_frame resolve_frame(void* addr) {
|
||||||
|
stacktrace_frame frame;
|
||||||
|
frame.col = -1;
|
||||||
|
backtrace_pcinfo(
|
||||||
|
get_backtrace_state(),
|
||||||
|
reinterpret_cast<uintptr_t>(addr),
|
||||||
|
full_callback,
|
||||||
|
error_callback,
|
||||||
|
&frame
|
||||||
|
);
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
28
src/symbols/symbols_with_nothing.cpp
Normal file
28
src/symbols/symbols_with_nothing.cpp
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifdef LIBCPPTRACE_GET_SYMBOLS_WITH_NOTHING
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_symbolize.hpp"
|
||||||
|
#include "../platform/libcpp_program_name.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
symbolizer::symbolizer() = default;
|
||||||
|
symbolizer::~symbolizer() = default;
|
||||||
|
|
||||||
|
stacktrace_frame symbolizer::resolve_frame(void*) {
|
||||||
|
return {
|
||||||
|
0,
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct symbolizer::impl {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
19
src/unwind/libcpp_unwind.hpp
Normal file
19
src/unwind/libcpp_unwind.hpp
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
#ifndef LIBCPP_UNWIND_HPP
|
||||||
|
#define LIBCPP_UNWIND_HPP
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
#ifdef LIBCPPTRACE_HARD_MAX_FRAMES
|
||||||
|
constexpr size_t hard_max_frames = LIBCPPTRACE_HARD_MAX_FRAMES;
|
||||||
|
#else
|
||||||
|
constexpr size_t hard_max_frames = 100;
|
||||||
|
#endif
|
||||||
|
std::vector<void*> capture_frames();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
22
src/unwind/unwind_with_execinfo.cpp
Normal file
22
src/unwind/unwind_with_execinfo.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#ifdef LIBCPPTRACE_UNWIND_WITH_EXECINFO
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_unwind.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <execinfo.h>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
std::vector<void*> capture_frames() {
|
||||||
|
std::vector<void*> frames(hard_max_frames, nullptr);
|
||||||
|
int n_frames = backtrace(bt.data(), hard_max_frames);
|
||||||
|
frames.resize(n_frames);
|
||||||
|
frames.shrink_to_fit();
|
||||||
|
return frames;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
16
src/unwind/unwind_with_nothing.cpp
Normal file
16
src/unwind/unwind_with_nothing.cpp
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#ifdef LIBCPPTRACE_UNWIND_WITH_NOTHING
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_unwind.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
std::vector<void*> capture_frames() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
26
src/unwind/unwind_with_winapi.cpp
Normal file
26
src/unwind/unwind_with_winapi.cpp
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifdef LIBCPPTRACE_UNWIND_WITH_WINAPI
|
||||||
|
|
||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
#include "libcpp_unwind.hpp"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
namespace cpptrace {
|
||||||
|
namespace detail {
|
||||||
|
std::vector<void*> capture_frames() {
|
||||||
|
// TODO: When does this need to be called? Can it be moved to the symbolizer?
|
||||||
|
SymSetOptions(SYMOPT_ALLOW_ABSOLUTE_SYMBOLS);
|
||||||
|
HANDLE proc = GetCurrentProcess();
|
||||||
|
if(!SymInitialize(proc, NULL, TRUE)) return {};
|
||||||
|
std::vector<PVOID> addrs(hard_max_frames, nullptr);
|
||||||
|
int frames = CaptureStackBackTrace(n_skip, hard_max_frames, addrs.data(), NULL);
|
||||||
|
addrs.resize(frames);
|
||||||
|
addrs.shrink_to_fit();
|
||||||
|
return addrs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
16
test/CMakeLists.txt
Normal file
16
test/CMakeLists.txt
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.8...3.23)
|
||||||
|
|
||||||
|
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||||
|
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
project(
|
||||||
|
test-libcpptrace
|
||||||
|
VERSION 1.0.0
|
||||||
|
LANGUAGES CXX
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(test test.cpp)
|
||||||
|
target_link_libraries(test PRIVATE cpptrace)
|
||||||
|
target_include_directories(test PRIVATE ../include)
|
||||||
|
target_link_directories(test PRIVATE ../build)
|
||||||
30
test/test.cpp
Normal file
30
test/test.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include <cpptrace/cpptrace.hpp>
|
||||||
|
|
||||||
|
void trace() {
|
||||||
|
cpptrace::print_trace();
|
||||||
|
}
|
||||||
|
|
||||||
|
void foo(int n) {
|
||||||
|
if(n == 0) {
|
||||||
|
trace();
|
||||||
|
} else {
|
||||||
|
foo(n - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... Args>
|
||||||
|
void foo(int x, Args... args) {
|
||||||
|
foo(args...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void function_two(int, float) {
|
||||||
|
foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void function_one(int) {
|
||||||
|
function_two(0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
function_one(0);
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user