diff --git a/.clang-tidy b/.clang-tidy index 8b4d3ae..616d471 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,7 +1,10 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,performance-*,readability-*,modernize-*,bugprone-*,misc-*' +Checks: +'clang-diagnostic-*,clang-analyzer-*,performance-*,readability-*,modernize-*,bugprone-*,misc-*, +-modernize-use-trailing-return-type,-bugprone-easily-swappable-parameters,-readability-identifier-length' WarningsAsErrors: '*' -HeaderFilterRegex: '' +HeaderFilterRegex: 'include/aws/.*\.h$' +ExcludeHeaderFilter: 'build/_deps/gtest-src.*' FormatStyle: 'none' CheckOptions: - key: modernize-pass-by-value.ValuesOnly @@ -10,6 +13,31 @@ CheckOptions: value: '1' - key: readability-implicit-bool-conversion.AllowIntegerConditions value: '1' - + - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: readability-identifier-naming.ClassCase + value: 'lower_case' + - key: readability-identifier-naming.StructCase + value: 'lower_case' + - key: readability-identifier-naming.StructCase + value: 'lower_case' + - key: readability-identifier-naming.ParameterCase + value: 'lower_case' + - key: readability-identifier-naming.PrivateMemberCase + value: 'lower_case' + - key: readability-identifier-naming.LocalVariableCase + value: 'lower_case' + - key: readability-identifier-naming.TypeAliasCase + value: 'lower_case' + - key: readability-identifier-naming.UnionCase + value: 'lower_case' + - key: readability-identifier-naming.FunctionCase + value: 'lower_case' + - key: readability-identifier-naming.NamespaceCase + value: 'lower_case' + - key: readability-identifier-naming.GlobalConstantCase + value: 'UPPER_CASE' + - key: readability-identifier-naming.PrivateMemberPrefix + value: 'm_' ... diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..495f6a6 --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,58 @@ +name: "Code Quality" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '32 14 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'cpp' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Install libcurl dependency + - name: Install dependencies + run: sudo apt-get update && sudo apt install -y libcurl4-openssl-dev + + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + - run: | + echo "Run, CMake build script" + cmake -B ${{github.workspace}}/build -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" + diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 0000000..7da0628 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,63 @@ +name: Validate Project + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Debug + +jobs: + build: + strategy: + matrix: + arch: [ubuntu-latest, ubuntu-24.04-arm] + runs-on: ${{ matrix.arch }} + + steps: + - uses: actions/checkout@v3 + - name: Install Dependencies + run: sudo apt-get update && sudo apt-get install -y clang-tidy libcurl4-openssl-dev + + - name: Configure CMake + run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_CLANG_TIDY=clang-tidy -DENABLE_TESTS=ON + + - name: Build It + run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} + + - name: Test It + run: cd build && make && ctest + + build-demo: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Install Dependencies + run: sudo apt-get update && sudo apt-get install -y clang-tidy libcurl4-openssl-dev + + - name: Build and install lambda runtime + run: | + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install + make + make install + + - name: Build and package demo project + run: | + cd examples/demo + mkdir build && cd build + cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=~/lambda-install + make + make aws-lambda-package-demo + + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Check Formatting + run: ./ci/codebuild/format-check.sh diff --git a/.gitignore b/.gitignore index 1f476fc..647f449 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ build tags TODO +compile_commands.json +.clangd diff --git a/CMakeLists.txt b/CMakeLists.txt index a83a8b8..09e226f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,14 +1,12 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.9) set(CMAKE_CXX_STANDARD 11) project(aws-lambda-runtime - VERSION 0.2.3 + VERSION 0.0.0 LANGUAGES CXX) +option(ENABLE_LTO "Enables link-time optimization, requires compiler support." OFF) option(ENABLE_TESTS "Enables building the test project, requires AWS C++ SDK." OFF) - -include(CheckCXXCompilerFlag) - -check_cxx_compiler_flag("-Wl,-flto" LTO_CAPABLE) +option(ENABLE_SANITIZERS "Enables ASan and UBSan." OFF) add_library(${PROJECT_NAME} "src/logging.cpp" @@ -17,12 +15,29 @@ add_library(${PROJECT_NAME} "${CMAKE_CURRENT_BINARY_DIR}/version.cpp" ) -set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION}) +set_target_properties(${PROJECT_NAME} PROPERTIES + SOVERSION 0 + VERSION ${PROJECT_VERSION}-dev) target_include_directories(${PROJECT_NAME} PUBLIC - $ + $ $) +if (ENABLE_SANITIZERS) + target_compile_options(${PROJECT_NAME} PUBLIC "-fsanitize=address,undefined") + target_link_libraries(${PROJECT_NAME} PUBLIC "-fsanitize=address,undefined") +endif() + +if (ENABLE_LTO) + include(CheckIPOSupported) + check_ipo_supported(RESULT has_lto OUTPUT lto_check_output) + if(has_lto) + set_property(TARGET ${PROJECT_NAME} PROPERTY INTERPROCEDURAL_OPTIMIZATION TRUE) + else() + message(WARNING "Link-time optimization (LTO) is not supported: ${lto_check_output}") + endif() +endif() + find_package(CURL REQUIRED) if (CMAKE_VERSION VERSION_LESS 3.12) target_link_libraries(${PROJECT_NAME} PRIVATE ${CURL_LIBRARIES}) @@ -32,30 +47,41 @@ endif() target_include_directories(${PROJECT_NAME} PRIVATE ${CURL_INCLUDE_DIRS}) +find_package(Backtrace QUIET) +if (${Backtrace_FOUND}) + target_link_libraries(${PROJECT_NAME} PRIVATE ${Backtrace_LIBRARIES}) + + find_library(DW_LIB NAMES dw) + if (NOT DW_LIB STREQUAL DW_LIB-NOTFOUND) + message("-- Enhanced stack-traces are enabled via libdw: ${DW_LIB}") + target_compile_definitions(${PROJECT_NAME} PRIVATE "BACKWARD_HAS_DW=1") + target_link_libraries(${PROJECT_NAME} PUBLIC "${DW_LIB}") + else() + find_library(BFD_LIB NAMES bfd) + if (NOT BFD_LIB STREQUAL BFD_LIB-NOTFOUND) + message("-- Enhanced stack-traces are enabled via libbfd: ${BFD_LIB}") + target_compile_definitions(${PROJECT_NAME} PRIVATE "BACKWARD_HAS_BFD=1") + target_link_libraries(${PROJECT_NAME} PRIVATE "${BFD_LIB}") + endif() + endif() + +else() + message("-- libbacktrace was not installed. Stacktracing will be disabled") + add_definitions(-Dno_backtrace) +endif() + + target_compile_options(${PROJECT_NAME} PRIVATE "-fno-exceptions" "-fno-rtti" "-fvisibility=hidden" + "-fvisibility-inlines-hidden" "-Wall" "-Wextra" "-Werror" "-Wconversion" "-Wno-sign-conversion") -find_library(DW_LIB NAMES dw) -if (NOT DW_LIB STREQUAL DW_LIB-NOTFOUND) - message("-- Enhanced stack-traces are enabled via libdw: ${DW_LIB}") - target_compile_definitions(${PROJECT_NAME} PRIVATE "BACKWARD_HAS_DW=1") - target_link_libraries(${PROJECT_NAME} PUBLIC "${DW_LIB}") -else() - find_library(BFD_LIB NAMES bfd) - if (NOT BFD_LIB STREQUAL BFD_LIB-NOTFOUND) - message("-- Enhanced stack-traces are enabled via libbfd: ${BFD_LIB}") - target_compile_definitions(${PROJECT_NAME} PRIVATE "BACKWARD_HAS_BFD=1") - target_link_libraries(${PROJECT_NAME} PRIVATE "${BFD_LIB}") - endif() -endif() - if (LOG_VERBOSITY) target_compile_definitions(${PROJECT_NAME} PRIVATE "AWS_LAMBDA_LOG=${LOG_VERBOSITY}") elseif(CMAKE_BUILD_TYPE STREQUAL Debug) @@ -64,11 +90,6 @@ else () target_compile_definitions(${PROJECT_NAME} PRIVATE "AWS_LAMBDA_LOG=0") endif() -if ((BUILD_SHARED_LIBS) AND (LTO_CAPABLE)) - target_compile_options(${PROJECT_NAME} PRIVATE "-flto") - target_link_libraries(${PROJECT_NAME} PRIVATE "-flto") -endif() - #tests if (ENABLE_TESTS) enable_testing() @@ -81,32 +102,47 @@ configure_file( "${CMAKE_CURRENT_BINARY_DIR}/version.cpp" NEWLINE_STYLE LF) +include (CMakePackageConfigHelpers) + +write_basic_package_version_file("${PROJECT_NAME}-config-version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + # installation -install(FILES "include/aws/lambda-runtime/runtime.h" "include/aws/lambda-runtime/version.h" +install(FILES "include/aws/http/response.h" + DESTINATION "include/aws/http") + +install(FILES + "include/aws/lambda-runtime/runtime.h" + "include/aws/lambda-runtime/version.h" + "include/aws/lambda-runtime/outcome.h" DESTINATION "include/aws/lambda-runtime") install(FILES "include/aws/logging/logging.h" DESTINATION "include/aws/logging") +include(GNUInstallDirs) install(TARGETS ${PROJECT_NAME} EXPORT ${PROJECT_NAME}-targets - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib - RUNTIME DESTINATION bin) + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + ) -configure_file("${CMAKE_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake" +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/${PROJECT_NAME}-config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" @ONLY) export(EXPORT "${PROJECT_NAME}-targets" NAMESPACE AWS::) install(EXPORT "${PROJECT_NAME}-targets" - DESTINATION "lib/${PROJECT_NAME}/cmake/" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake/" NAMESPACE AWS::) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config.cmake" - DESTINATION "lib/${PROJECT_NAME}/cmake/") + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake/") -install(PROGRAMS "${CMAKE_SOURCE_DIR}/packaging/packager" - DESTINATION "lib/${PROJECT_NAME}/cmake/") +install(PROGRAMS "${CMAKE_CURRENT_SOURCE_DIR}/packaging/packager" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/${PROJECT_NAME}/cmake/") diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8c3aa5..d6b300a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,6 +43,24 @@ GitHub provides additional document on [forking a repository](https://help.githu ## Finding contributions to work on Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-lambda-cpp-runtime/labels/help%20wanted) issues is a great place to start. +## Running the Integration Tests Locally + +The integration testing for the project creates, invokes, and deletes, Lambda functions. +These tests typically run AWS CodeBuild, but may also be executed locally + +Prerequisites: +* install Docker +* configure AWS credentials, need at least permissions to Create, Delete, and Invoke Lambda functions +* an IAM role, named exactly `integration-tests`, must exist in the account. + * The role must also be assumable by Lambda. + * (optional) attach AWSLambdaBasicExecutionRole managed policy to the role, so that the test function logs are saved to CloudWatch + +Then, to iterate on a single workflow: +``` +docker build -t lambda-cpp-amazon-linux-2 -f ./ci/docker/amazon-linux-2 . +./ci/codebuild_build.sh -c -a /tmp -i lambda-cpp-amazon-linux-2 -b ./ci/codebuild/amazon-linux-2.yml +``` + ## Code of Conduct This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). diff --git a/README.md b/README.md index 7fde5d9..0f58b28 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,15 @@ -[![GitHub](https://img.shields.io/github/license/awslabs/aws-c-common.svg)](https://github.com/awslabs/aws-c-common/blob/master/LICENSE) -![CodeBuild](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiQkN1b0srbWtnUjNibFVyL2psNmdaM0l4RnVQNzVBeG84QnQvUjRmOEJVdXdHUXMxZ25iWnFZQUtGTkUxVGJhcGZaVEhXY2JOSTFHTlkvaGF2RDRIZlpVPSIsIml2UGFyYW1ldGVyU3BlYyI6IjRiS3hlRjFxVFZHSWViQmQiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master) -[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/awslabs/aws-lambda-cpp.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/awslabs/aws-lambda-cpp/context:cpp) +[![GitHub](https://img.shields.io/github/license/awslabs/aws-lambda-cpp.svg)](https://github.com/awslabs/aws-lambda-cpp/blob/master/LICENSE) +![Code Quality badge](https://github.com/awslabs/aws-lambda-cpp/actions/workflows/code-quality.yml/badge.svg) + +| OS | Arch | Status | +|----|------|--------| +| Amazon Linux 2 | x86_64 | [![](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiQ1EvQXE0ODBLK0VnQitMaVdvZ1J0QkhTMlpNbk8wS0lRbWZvRDlPSHB0V0VXb1VLazdSdzRMWHhMeUdpYjdOT1hCc1hjL3BKei96ZVpzeTdrMVd4c3BRPSIsIml2UGFyYW1ldGVyU3BlYyI6IkhjTTNoSzJwb1hldk9zZFYiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master)](https://us-west-2.codebuild.aws.amazon.com/project/eyJlbmNyeXB0ZWREYXRhIjoicnpvbytDV0grMHh2c09ONi9kQ3ZuOVVwckxISElKRENEVy9CL0pvd3VvLzQwZ21pdzBOdGtNWUFLRy9VRkw1NldSMmRlVXV5R0NhN1k1OWI0bDY1N2MyMzR2SmhseWlma0hmWTlBUkwzcVp0TEJlQm1RPT0iLCJpdlBhcmFtZXRlclNwZWMiOiI3MzM4WDUybk9hSkl1bllRIiwibWF0ZXJpYWxTZXRTZXJpYWwiOjF9) | +| Amazon Linux 2 | aarch64 | [![](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoicWNGSmJtaGdPSCtqR25KQ1k3RWNZS1pwWlZScGZ3WU1JM0lISnZJVkhVNy8zbVIyVHp6RlBmRjN4cjZJd2xWNEd0eWZmUy9JaE1vRzBYWFcrbnpFdDUwPSIsIml2UGFyYW1ldGVyU3BlYyI6ImVoeHl5TTNtMmdERjJuWisiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master)](https://us-west-2.codebuild.aws.amazon.com/project/eyJlbmNyeXB0ZWREYXRhIjoiVUVaNzBYMXVjUUl1djdlS3pTSXVxMUhKcHB4ZC96ZjlDOWM3bUxiRmtITnVGYzdxTDJveFY3eVFqanpHbzhYRUdWVjVhZFhnOGt0NldETEVMamN0alRoZzYwMyszU1lVMjJNR0lUWGNCQjVYNzhuUzZwZ0ptZz09IiwiaXZQYXJhbWV0ZXJTcGVjIjoicmtKaUVoM2pmUVdibVZuOSIsIm1hdGVyaWFsU2V0U2VyaWFsIjoxfQ%3D%3D) | +| Amazon Linux (ALAMI) | x86_64 | [![](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiWUNqeG9FcmUyQzVSaUkydFd6UkU5Sm42cTViSExXOFZURHRBQlM0dDJDOThMWEFYLzN4NitQR0w1ZzNKcjAwOVNUYXY5ZUljU1hzcEtrU0N0dEhUN0M0PSIsIml2UGFyYW1ldGVyU3BlYyI6ImtYU0ZjSzh3ekFKazlBVVUiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master)](https://us-west-2.codebuild.aws.amazon.com/project/eyJlbmNyeXB0ZWREYXRhIjoiTEJJVVFIOXp6VjUvWExqODN1K1NPQmRTVm9iQy9ZK2tmKzkrbVdTNlh1LzV1UlpQL2lPN1Faak0yc0pOaGpEVlRpai9yS3JCRjBRQU5lMVFVU1hRU1hyekxpVi8yNWV0ZE44SElWdlRpNld4bmkwdE1oQjcxN0NtIiwiaXZQYXJhbWV0ZXJTcGVjIjoiZnBBUi9uOU8yVjJ4RENpRyIsIm1hdGVyaWFsU2V0U2VyaWFsIjoxfQ%3D%3D) | +| Alpine | x86_64 | [![](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiTkhhOEJGNjVOTG5NZWVNWDNjSGNEdWEwY0J2ZUNLMkE2aU83UVdYc3VMU0V5b1JqdXY0OXUxNkxYRDUxU0VJOTByL3NLUTE3djBMNWh2VldXdk0xamJZPSIsIml2UGFyYW1ldGVyU3BlYyI6ImQxSjc2Vnd3czF2QWphRS8iLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master)](https://us-west-2.codebuild.aws.amazon.com/project/eyJlbmNyeXB0ZWREYXRhIjoiQzJVUzZML1dLTkpRNGcxSjVyUXVEd1BCY2poZUhydWZLeGE5MGU1c05vNDVObG44bnpKZFhlZVJKSm50ZnpaalRENUxxOHpPNGdPTDRlTGc4WW81UHd4L3hCeTgyTm5vRVR0RW5FempKdk00aDlPRk02WGQiLCJpdlBhcmFtZXRlclNwZWMiOiJUMFhCQktLMExQMXc3Q0lHIiwibWF0ZXJpYWxTZXRTZXJpYWwiOjF9) | +| Arch Linux | x86_64 | [![](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoib2cxaHp3bE5ndWhWR0RIRkxxQzRwR1dHa05DWmQ0bENnWGNHYzM2YmR3OFRHNWpPYStGYUM1WXBQVUNoZjJRa2xrZVpuRXVyWVVvQVNzNExqSlN5TGEwPSIsIml2UGFyYW1ldGVyU3BlYyI6Ii9zSjVybGNsNEJMUEZwSlUiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master)](https://us-west-2.codebuild.aws.amazon.com/project/eyJlbmNyeXB0ZWREYXRhIjoiRWVOYlA5OHZqUVVLUTZLYlJzZmdOQkR5dmpVSTBPS1h1M3RxQkxXa3pyMC9OOUw5dDJlUDcyYm05Q3pBOEZ1aWJFYkFBajFGZ3RJWUM5WkpoZUE4K0IrdFIvYytyNVRYREpQTUNHL05vTXlLQ0E9PSIsIml2UGFyYW1ldGVyU3BlYyI6InFuS1hJY3JTaWpSWENLM1EiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D) | +| Ubuntu 18.04 | x86_64 | [![](https://codebuild.us-west-2.amazonaws.com/badges?uuid=eyJlbmNyeXB0ZWREYXRhIjoiVkhsbmdlYkk3M1JESVdiTHc0elpobXEvUk4wRWlBZUpEZzdmem1QbGJRZ3dMbVE2RWZpbHZjNmVCd0dJaUFSZ1pzQVlyZ1dvdndWTjZSRjg0WDRYRFh3PSIsIml2UGFyYW1ldGVyU3BlYyI6IjJic2dnR3ZpTEQyMmRPMXQiLCJtYXRlcmlhbFNldFNlcmlhbCI6MX0%3D&branch=master)](https://us-west-2.codebuild.aws.amazon.com/project/eyJlbmNyeXB0ZWREYXRhIjoiSlNPak1vQmVBR3JnUlAwRWg2N3hHRHF1U2Z6RkQvY1NHRHM4RTJ0WEFBdjFTSzBzY21kZEpPMDk2QXdwRStUWUZmWWFmTkRkU1FGa0lQUGoxbU9GNU45QVJ1YVkzZkY0dmsxV2FRZVljakt3UmJpdTM2a0JnQT09IiwiaXZQYXJhbWV0ZXJTcGVjIjoieE5LSUlmNVN1UWdqbWg0cSIsIm1hdGVyaWFsU2V0U2VyaWFsIjoxfQ%3D%3D) | + ## AWS Lambda C++ Runtime C++ implementation of the lambda runtime API @@ -14,11 +23,11 @@ Since AWS Lambda runs on GNU/Linux, you should build this runtime library and yo ### Prerequisites Make sure you have the following packages installed first: -1. CMake (version 3.5 or later) +1. CMake (version 3.9 or later) 1. git 1. Make or Ninja 1. zip -1. libcurl-devel (on Debian-basded distros it's libcurl4-openssl-dev) +1. libcurl-devel (on Debian-based distros it's libcurl4-openssl-dev) In a terminal, run the following commands: ```bash @@ -30,10 +39,27 @@ $ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=~/lambda-install $ make && make install ``` +### Running Unit Tests Locally + +To run the unit tests locally, follow these steps to build: + +```bash +$ cd aws-lambda-cpp +$ mkdir build +$ cd build +$ cmake .. -DCMAKE_BUILD_TYPE=Debug -DENABLE_TESTS=ON +$ make +``` + +Run unit tests: +```bash +$ ctest +``` + To consume this library in a project that is also using CMake, you would do: ```cmake -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.9) set(CMAKE_CXX_STANDARD 11) project(demo LANGUAGES CXX) find_package(aws-lambda-runtime) @@ -59,7 +85,7 @@ static invocation_response my_handler(invocation_request const& req) "error type here" /*error_type*/); } - return invocation_response::success("json payload here" /*payload*/, + return invocation_response::success("{\"message:\":\"I fail if body length is bigger than 42!\"}" /*payload*/, "application/json" /*MIME type*/); } @@ -121,13 +147,19 @@ And finally, create the Lambda function: ``` $ aws lambda create-function --function-name demo \ --role \ ---runtime provided --timeout 15 --memory-size 128 \ +--runtime provided.al2023 --timeout 15 --memory-size 128 \ --handler demo --zip-file fileb://demo.zip ``` +> **N.B.** If you are building on `arm64`, you have to explicitly add the param `--architectures arm64`, so that you are setting up the proper architecture on AWS to run your supplied Lambda function. And to invoke the function: ```bash -$ aws lambda invoke --function-name demo --payload '{"answer":42}' output.txt +$ aws lambda invoke --function-name demo --cli-binary-format raw-in-base64-out --payload '{"answer":42}' output.txt +``` + +You can update your supplied function: +```bash +$ aws lambda update-function-code --function-name demo --zip-file fileb://demo.zip ``` ## Using the C++ SDK for AWS with this runtime @@ -141,7 +173,7 @@ Any *fully* compliant C++11 compiler targeting GNU/Linux x86-64 should work. Ple - Use Clang v3.3 or above ## Packaging, ABI, GNU C Library, Oh My! -Lambda runs your code on some version of Amazon Linux. It would be a less than ideal customer experience if you are forced to build your application on that platform and that platform only. +Lambda runs your code on some version of Amazon Linux. It would be a less than ideal customer experience if you are forced to build your application on that platform and that platform only. However, the freedom to build on any linux distro brings a challenge. The GNU C Library ABI. There is no guarantee the platform used to build the Lambda function has the same GLIBC version as the one used by AWS Lambda. In fact, you might not even be using GNU's implementation. For example you could build a C++ Lambda function using musl libc. @@ -187,10 +219,12 @@ curl_easy_setopt(curl_handle, CURLOPT_CAINFO, "/etc/pki/tls/certs/ca-bundle.crt" ```bash $ aws lambda create-function --function-name demo \ --role \ - --runtime provided --timeout 15 --memory-size 128 \ + --runtime provided.al2023 --timeout 15 --memory-size 128 \ --handler demo --code "S3Bucket=mys3bucket,S3Key=demo.zip" ``` +> **N.B.** See hint above if you are building on `arm64`. + 1. **My code is crashing, how can I debug it?** - Starting with [v0.2.0](https://github.com/awslabs/aws-lambda-cpp/releases/tag/v0.2.0) you should see a stack-trace of the crash site in the logs (which are typically stored in CloudWatch). diff --git a/ci/README.md b/ci/README.md new file mode 100644 index 0000000..9e50181 --- /dev/null +++ b/ci/README.md @@ -0,0 +1,14 @@ +## AWS CodeBuild Stack Setup + +create or update the stack +``` +aws cloudformation deploy --capabilities CAPABILITY_IAM --stack-name aws-lambda-cpp-ci --template-file codebuild.yml +``` + +(optional) trigger docker build and docker push of the build environment images. +A project to do this is pre-configured in the deployed stack. +``` +aws cloudformation describe-stacks --stack-name aws-lambda-cpp-ci --query "Stacks[].Outputs[].OutputValue" +# run command output from above, will look like: +# aws codebuild start-build --project-name +``` diff --git a/ci/codebuild.yml b/ci/codebuild.yml new file mode 100644 index 0000000..1205407 --- /dev/null +++ b/ci/codebuild.yml @@ -0,0 +1,412 @@ + +Parameters: + GitHub: + Type: String + Default: https://github.com/awslabs/aws-lambda-cpp.git + +Resources: + + ECR: + Type: AWS::ECR::Repository + + LambdaTestRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: can-log + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Resource: + - !Join [':', [ arn:aws:logs, !Ref AWS::Region, !Ref AWS::AccountId, log-group:/aws/lambda/lambda-cpp-* ] ] + - !Join [':', [ arn:aws:logs, !Ref AWS::Region, !Ref AWS::AccountId, log-group:/aws/lambda/lambda-cpp-*:* ] ] + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + + LogsAccessRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: + - sts:AssumeRole + Policies: + - PolicyName: readthelogs + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Resource: + - !Join [':', [ arn:aws:logs, !Ref AWS::Region, !Ref AWS::AccountId, log-group:/aws/codebuild/aws-lambda-cpp-ci:* ] ] + Action: + - logs:GetLogEvents + + CodeBuildRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Principal: + Service: codebuild.amazonaws.com + Action: sts:AssumeRole + Policies: + - PolicyName: thepolicy + PolicyDocument: + Version: 2012-10-17 + Statement: + - Effect: Allow + Resource: + - !Join [':', [ arn:aws:logs, !Ref AWS::Region, !Ref AWS::AccountId, log-group:/aws/codebuild/aws-lambda-cpp-ci ] ] + - !Join [':', [ arn:aws:logs, !Ref AWS::Region, !Ref AWS::AccountId, log-group:/aws/codebuild/aws-lambda-cpp-ci:* ] ] + Action: + - logs:CreateLogGroup + - logs:CreateLogStream + - logs:PutLogEvents + - Effect: Allow + Resource: + - !Join [ '', [ arn:aws:s3:::codepipeline-, !Ref AWS::Region, -* ] ] + Action: + - s3:PutObject + - s3:GetObject + - s3:GetObjectVersion + - s3:GetBucketAcl + - s3:GetBucketLocation + - Effect: Allow + Resource: + - !Join [ ':', [ arn:aws:codebuild, !Ref AWS::Region, !Ref AWS::AccountId, report-group/test-* ] ] + Action: + - codebuild:CreateReportGroup + - codebuild:CreateReport + - codebuild:UpdateReport + - codebuild:BatchPutTestCases + - codebuild:BatchPutCodeCoverages + - Effect: Allow + Resource: + - '*' + Action: + - ecr:GetAuthorizationToken + - Effect: Allow + Resource: + - !GetAtt ECR.Arn + Action: + # pulling + - ecr:BatchCheckLayerAvailability + - ecr:GetDownloadUrlForLayer + - ecr:BatchGetImage + # pushing + - ecr:CompleteLayerUpload + - ecr:GetAuthorizationToken + - ecr:UploadLayerPart + - ecr:InitiateLayerUpload + - ecr:BatchCheckLayerAvailability + - ecr:PutImage + - Effect: Allow + Resource: + - !GetAtt LambdaTestRole.Arn + Action: + - iam:GetRole + - iam:PassRole + - Effect: Allow + Resource: + - !Join [':', [ arn:aws:lambda, !Ref AWS::Region, !Ref AWS::AccountId, function:lambda-cpp-* ] ] + Action: + - lambda:CreateFunction + - lambda:DeleteFunction + - lambda:InvokeFunction + + UpdateArmBuildEnvironments: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Environment: + ImagePullCredentialsType: CODEBUILD + ComputeType: BUILD_GENERAL1_SMALL + Image: aws/codebuild/amazonlinux2-aarch64-standard:2.0 + Type: ARM_CONTAINER + PrivilegedMode: True + EnvironmentVariables: + - Name: ECR_NAME + Type: PLAINTEXT + Value: !Ref ECR + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: | + version: 0.2 + phases: + build: + commands: + - ./ci/update-images.sh + + UpdateX86BuildEnvironments: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Environment: + ImagePullCredentialsType: CODEBUILD + ComputeType: BUILD_GENERAL1_MEDIUM + Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0 + Type: LINUX_CONTAINER + PrivilegedMode: True + EnvironmentVariables: + - Name: ECR_NAME + Type: PLAINTEXT + Value: !Ref ECR + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: | + version: 0.2 + phases: + build: + commands: + - ./ci/update-images.sh + + + Amazon2Arm: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Triggers: + BuildType: BUILD + Webhook: True + FilterGroups: + - - Type: EVENT + Pattern: PUSH,PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED + Environment: + ImagePullCredentialsType: SERVICE_ROLE + ComputeType: BUILD_GENERAL1_SMALL + Type: ARM_CONTAINER + Image: !Join [ ':', [ !GetAtt ECR.RepositoryUri, amazon-linux-2-linux-arm64 ]] + EnvironmentVariables: + - Name: LAMBDA_TEST_ROLE + Type: PLAINTEXT + Value: !Ref LambdaTestRole + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: ci/codebuild/amazonlinux-2.yml + + Amazon2: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Triggers: + BuildType: BUILD + Webhook: True + FilterGroups: + - - Type: EVENT + Pattern: PUSH,PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED + Environment: + ImagePullCredentialsType: SERVICE_ROLE + ComputeType: BUILD_GENERAL1_SMALL + Type: LINUX_CONTAINER + Image: !Join [ ':', [ !GetAtt ECR.RepositoryUri, amazon-linux-2-linux-amd64 ]] + EnvironmentVariables: + - Name: LAMBDA_TEST_ROLE + Type: PLAINTEXT + Value: !Ref LambdaTestRole + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: ci/codebuild/amazonlinux-2.yml + + Amazon201803: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Triggers: + BuildType: BUILD + Webhook: True + FilterGroups: + - - Type: EVENT + Pattern: PUSH,PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED + Environment: + ImagePullCredentialsType: SERVICE_ROLE + ComputeType: BUILD_GENERAL1_SMALL + Type: LINUX_CONTAINER + Image: !Join [ ':', [ !GetAtt ECR.RepositoryUri, amazon-linux-2018.03-linux-amd64 ]] + EnvironmentVariables: + - Name: LAMBDA_TEST_ROLE + Type: PLAINTEXT + Value: !Ref LambdaTestRole + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: ci/codebuild/amazonlinux-2018.03.yml + + Ubuntu1804: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Triggers: + BuildType: BUILD + Webhook: True + FilterGroups: + - - Type: EVENT + Pattern: PUSH,PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED + Environment: + ImagePullCredentialsType: SERVICE_ROLE + ComputeType: BUILD_GENERAL1_SMALL + Type: LINUX_CONTAINER + Image: !Join [ ':', [ !GetAtt ECR.RepositoryUri, ubuntu-linux-18.04-linux-amd64 ]] + EnvironmentVariables: + - Name: LAMBDA_TEST_ROLE + Type: PLAINTEXT + Value: !Ref LambdaTestRole + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: ci/codebuild/ubuntu-18.04.yml + + Alpine315: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Triggers: + BuildType: BUILD + Webhook: True + FilterGroups: + - - Type: EVENT + Pattern: PUSH,PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED + Environment: + ImagePullCredentialsType: SERVICE_ROLE + ComputeType: BUILD_GENERAL1_SMALL + Type: LINUX_CONTAINER + Image: !Join [ ':', [ !GetAtt ECR.RepositoryUri, alpine-linux-3.15-linux-amd64 ]] + EnvironmentVariables: + - Name: LAMBDA_TEST_ROLE + Type: PLAINTEXT + Value: !Ref LambdaTestRole + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: ci/codebuild/alpine-3.15.yml + + Arch: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + Type: NO_ARTIFACTS + BadgeEnabled: True + Visibility: PUBLIC_READ + ConcurrentBuildLimit: 1 + ServiceRole: !GetAtt CodeBuildRole.Arn + ResourceAccessRole: !GetAtt LogsAccessRole.Arn + LogsConfig: + CloudWatchLogs: + Status: ENABLED + GroupName: /aws/codebuild/aws-lambda-cpp-ci + Triggers: + BuildType: BUILD + Webhook: True + FilterGroups: + - - Type: EVENT + Pattern: PUSH,PULL_REQUEST_CREATED,PULL_REQUEST_UPDATED + Environment: + ImagePullCredentialsType: SERVICE_ROLE + ComputeType: BUILD_GENERAL1_SMALL + Type: LINUX_CONTAINER + Image: !Join [ ':', [ !GetAtt ECR.RepositoryUri, arch-linux-linux-amd64 ]] + EnvironmentVariables: + - Name: LAMBDA_TEST_ROLE + Type: PLAINTEXT + Value: !Ref LambdaTestRole + Source: + Type: GITHUB + Location: !Ref GitHub + BuildSpec: ci/codebuild/arch-linux.yml + +Outputs: + BootstrapArmImages: + Description: to bootstrap or update the arm images, run the command! + Value: !Join [' ', [ aws codebuild start-build --project-name, !Ref UpdateArmBuildEnvironments ] ] + BootstrapX86Images: + Description: to bootstrap or update the arm images, run the command! + Value: !Join [' ', [ aws codebuild start-build --project-name, !Ref UpdateX86BuildEnvironments ] ] diff --git a/ci/codebuild/alpine-3.15.yml b/ci/codebuild/alpine-3.15.yml new file mode 100644 index 0000000..616fed9 --- /dev/null +++ b/ci/codebuild/alpine-3.15.yml @@ -0,0 +1,9 @@ +version: 0.2 +# This uses the docker image specified in ci/docker/alpine-linux-3.15 +phases: + build: + commands: + - echo Build started on `date` + - ./ci/codebuild/build.sh -DTEST_RESOURCE_PREFIX=lambda-cpp-alpine315 + - ./ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun + - echo Build completed on `date` diff --git a/ci/codebuild/amazonlinux-2.yml b/ci/codebuild/amazonlinux-2.yml new file mode 100644 index 0000000..e2f6d76 --- /dev/null +++ b/ci/codebuild/amazonlinux-2.yml @@ -0,0 +1,9 @@ +version: 0.2 +phases: + build: + commands: + - echo Build started on `date` + - ./ci/codebuild/build.sh -DTEST_RESOURCE_PREFIX=lambda-cpp-al2_$(arch) + - ./ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun + - ./ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun-no-glibc + - echo Build completed on `date` \ No newline at end of file diff --git a/ci/codebuild/amazonlinux-2017.03.yml b/ci/codebuild/amazonlinux-2017.03.yml deleted file mode 100644 index eab1baf..0000000 --- a/ci/codebuild/amazonlinux-2017.03.yml +++ /dev/null @@ -1,18 +0,0 @@ -version: 0.1 -# This uses the docker image specified in ci/docker/amazon-linux-2017.03 -phases: - pre_build: - commands: - - alias cmake=cmake3 - - pip install awscli - - ci/codebuild/build-cpp-sdk.sh - build: - commands: - - echo Build started on `date` - - ci/codebuild/build.sh -DENABLE_TESTS=ON -DTEST_RESOURCE_PREFIX=amzn201703 - - ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun amzn201703 - - ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun-no-glibc amzn201703 - post_build: - commands: - - echo Build completed on `date` - diff --git a/ci/codebuild/amazonlinux-2018.03.yml b/ci/codebuild/amazonlinux-2018.03.yml new file mode 100644 index 0000000..d4d0b5c --- /dev/null +++ b/ci/codebuild/amazonlinux-2018.03.yml @@ -0,0 +1,12 @@ +version: 0.2 +# This uses the docker image specified in ci/docker/amazon-linux-2017.03 +phases: + build: + commands: + - echo Build started on `date` + - yum install -y binutils + - ./ci/codebuild/build.sh -DTEST_RESOURCE_PREFIX=lambda-cpp-amzn201703 + - ./ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun + - ./ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun-no-glibc + - echo Build completed on `date` + diff --git a/ci/codebuild/arch-linux.yml b/ci/codebuild/arch-linux.yml new file mode 100644 index 0000000..0a12a5d --- /dev/null +++ b/ci/codebuild/arch-linux.yml @@ -0,0 +1,9 @@ +version: 0.2 +# This uses the docker image specified in ci/docker/arch-linux +phases: + build: + commands: + - echo Build started on `date` + - ./ci/codebuild/build.sh -DTEST_RESOURCE_PREFIX=lambda-cpp-archbtw -DENABLE_SANITIZERS=ON + - ./ci/codebuild/run-tests.sh aws-lambda-package-lambda-test-fun + - echo Build completed on `date` diff --git a/ci/codebuild/build-cpp-sdk.sh b/ci/codebuild/build-cpp-sdk.sh index 93ae7eb..7f84aab 100755 --- a/ci/codebuild/build-cpp-sdk.sh +++ b/ci/codebuild/build-cpp-sdk.sh @@ -4,12 +4,10 @@ set -euo pipefail # build the AWS C++ SDK cd /aws-sdk-cpp -git pull mkdir build cd build cmake .. -GNinja -DBUILD_ONLY="lambda" \ -DCMAKE_BUILD_TYPE=Release \ - -DENABLE_UNITY_BUILD=ON \ -DBUILD_SHARED_LIBS=ON \ -DENABLE_TESTING=OFF \ -DCMAKE_INSTALL_PREFIX=/install $@ diff --git a/ci/codebuild/build.sh b/ci/codebuild/build.sh index 53a9544..8a90d30 100755 --- a/ci/codebuild/build.sh +++ b/ci/codebuild/build.sh @@ -6,6 +6,10 @@ set -euo pipefail cd $CODEBUILD_SRC_DIR mkdir build cd build -cmake .. -GNinja -DBUILD_SHARED_LIBS=ON -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=/install $@ +cmake .. -GNinja \ + -DBUILD_SHARED_LIBS=ON \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_TESTS=ON \ + -DCMAKE_INSTALL_PREFIX=/install $@ ninja ninja install diff --git a/ci/codebuild/format-check.sh b/ci/codebuild/format-check.sh index 3afb802..035bd54 100755 --- a/ci/codebuild/format-check.sh +++ b/ci/codebuild/format-check.sh @@ -4,7 +4,7 @@ set -euo pipefail CLANG_FORMAT=clang-format -if NOT type $CLANG_FORMAT > /dev/null 2>&1; then +if ! type $CLANG_FORMAT > /dev/null 2>&1; then echo "No appropriate clang-format found." exit 1 fi @@ -13,6 +13,11 @@ FAIL=0 SOURCE_FILES=$(find src include tests -type f -name "*.h" -o -name "*.cpp") for i in $SOURCE_FILES do + if [[ "$i" == *"gtest.h" || "$i" == *"backward.h" ]]; then + continue + fi + + echo "$i\n" if [ $($CLANG_FORMAT -output-replacements-xml $i | grep -c ": format for secondary source" + echo " * For sourceIdentifier, use a value that is fewer than 128 characters and contains only alphanumeric characters and underscores" + echo " -c Use the AWS configuration and credentials from your local host. This includes ~/.aws and any AWS_* environment variables." + echo " -p Used to specify the AWS CLI Profile." + echo " -b FILE Used to specify a buildspec override file. Defaults to buildspec.yml in the source directory." + echo " -m Used to mount the source directory to the customer build container directly." + echo " -d Used to run the build container in docker privileged mode." + echo " -e FILE Used to specify a file containing environment variables." + echo " (-e) File format expectations:" + echo " * Each line is in VAR=VAL format" + echo " * Lines beginning with # are processed as comments and ignored" + echo " * Blank lines are ignored" + echo " * File can be of type .env or .txt" + echo " * There is no special handling of quotation marks, meaning they will be part of the VAL" + exit 1 +} + +image_flag=false +artifact_flag=false +awsconfig_flag=false +mount_src_dir_flag=false +docker_privileged_mode_flag=false + +while getopts "cmdi:a:r:s:b:e:l:p:h" opt; do + case $opt in + i ) image_flag=true; image_name=$OPTARG;; + a ) artifact_flag=true; artifact_dir=$OPTARG;; + r ) report_dir=$OPTARG;; + b ) buildspec=$OPTARG;; + c ) awsconfig_flag=true;; + m ) mount_src_dir_flag=true;; + d ) docker_privileged_mode_flag=true;; + s ) source_dirs+=("$OPTARG");; + e ) environment_variable_file=$OPTARG;; + l ) local_agent_image=$OPTARG;; + p ) aws_profile=$OPTARG;; + h ) usage; exit;; + \? ) echo "Unknown option: -$OPTARG" >&2; exit 1;; + : ) echo "Missing option argument for -$OPTARG" >&2; exit 1;; + * ) echo "Invalid option: -$OPTARG" >&2; exit 1;; + esac +done + +if ! $image_flag +then + echo "The image name flag (-i) must be included for a build to run" >&2 +fi + +if ! $artifact_flag +then + echo "The artifact directory (-a) must be included for a build to run" >&2 +fi + +if ! $image_flag || ! $artifact_flag +then + exit 1 +fi + +docker_command="docker run -it " +if isOSWindows +then + docker_command+="-v //var/run/docker.sock:/var/run/docker.sock -e " +else + docker_command+="-v /var/run/docker.sock:/var/run/docker.sock -e " +fi + +docker_command+="\"IMAGE_NAME=$image_name\" -e \ + \"ARTIFACTS=$(allOSRealPath "$artifact_dir")\"" + +if [ -n "$report_dir" ] +then + docker_command+=" -e \"REPORTS=$(allOSRealPath "$report_dir")\"" +fi + +if [ -z "$source_dirs" ] +then + docker_command+=" -e \"SOURCE=$(allOSRealPath "$PWD")\"" +else + for index in "${!source_dirs[@]}"; do + if [ $index -eq 0 ] + then + docker_command+=" -e \"SOURCE=$(allOSRealPath "${source_dirs[$index]}")\"" + else + identifier=${source_dirs[$index]%%:*} + src_dir=$(allOSRealPath "${source_dirs[$index]#*:}") + + docker_command+=" -e \"SECONDARY_SOURCE_$index=$identifier:$src_dir\"" + fi + done +fi + +if [ -n "$buildspec" ] +then + docker_command+=" -e \"BUILDSPEC=$(allOSRealPath "$buildspec")\"" +fi + +if [ -n "$environment_variable_file" ] +then + environment_variable_file_path=$(allOSRealPath "$environment_variable_file") + environment_variable_file_dir=$(dirname "$environment_variable_file_path") + environment_variable_file_basename=$(basename "$environment_variable_file") + docker_command+=" -v \"$environment_variable_file_dir:/LocalBuild/envFile/\" -e \"ENV_VAR_FILE=$environment_variable_file_basename\"" +fi + +if [ -n "$local_agent_image" ] +then + docker_command+=" -e \"LOCAL_AGENT_IMAGE_NAME=$local_agent_image\"" +fi + +if $awsconfig_flag +then + if [ -d "$HOME/.aws" ] + then + configuration_file_path=$(allOSRealPath "$HOME/.aws") + docker_command+=" -e \"AWS_CONFIGURATION=$configuration_file_path\"" + else + docker_command+=" -e \"AWS_CONFIGURATION=NONE\"" + fi + + if [ -n "$aws_profile" ] + then + docker_command+=" -e \"AWS_PROFILE=$aws_profile\"" + fi + + docker_command+="$(env | grep ^AWS_ | while read -r line; do echo " -e \"$line\""; done )" +fi + +if $mount_src_dir_flag +then + docker_command+=" -e \"MOUNT_SOURCE_DIRECTORY=TRUE\"" +fi + +if $docker_privileged_mode_flag +then + docker_command+=" -e \"DOCKER_PRIVILEGED_MODE=TRUE\"" +fi + +if isOSWindows +then + docker_command+=" -e \"INITIATOR=$USERNAME\"" +else + docker_command+=" -e \"INITIATOR=$USER\"" +fi + +if [ -n "$local_agent_image" ] +then + docker_command+=" $local_agent_image" +else + docker_command+=" public.ecr.aws/codebuild/local-builds:latest" +fi + +# Note we do not expose the AWS_SECRET_ACCESS_KEY or the AWS_SESSION_TOKEN +exposed_command=$docker_command +secure_variables=( "AWS_SECRET_ACCESS_KEY=" "AWS_SESSION_TOKEN=") +for variable in "${secure_variables[@]}" +do + exposed_command="$(echo $exposed_command | sed "s/\($variable\)[^ ]*/\1********\"/")" +done + +echo "Build Command:" +echo "" +echo $exposed_command +echo "" + +eval $docker_command diff --git a/ci/docker/alpine-linux-3.15 b/ci/docker/alpine-linux-3.15 new file mode 100644 index 0000000..c8d33b5 --- /dev/null +++ b/ci/docker/alpine-linux-3.15 @@ -0,0 +1,19 @@ +FROM public.ecr.aws/docker/library/alpine:3.15 + +RUN apk add --no-cache \ + bash \ + cmake \ + curl-dev \ + g++ \ + git \ + libexecinfo-dev \ + ninja \ + openssl-libs-static \ + zlib-dev \ + zip + +RUN git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp.git +RUN cmake -Saws-sdk-cpp -Baws-sdk-cpp/build -GNinja \ + -DBUILD_ONLY=lambda \ + -DENABLE_TESTING=OFF +RUN cd aws-sdk-cpp/build && ninja && ninja install diff --git a/ci/docker/alpine-linux-3.8 b/ci/docker/alpine-linux-3.8 deleted file mode 100644 index a042e4c..0000000 --- a/ci/docker/alpine-linux-3.8 +++ /dev/null @@ -1,5 +0,0 @@ -FROM alpine:latest - -RUN apk update; apk add g++ cmake git ninja curl-dev zlib-dev - -RUN git clone https://github.com/aws/aws-sdk-cpp.git diff --git a/ci/docker/amazon-linux-2 b/ci/docker/amazon-linux-2 new file mode 100644 index 0000000..452d1f4 --- /dev/null +++ b/ci/docker/amazon-linux-2 @@ -0,0 +1,16 @@ +FROM public.ecr.aws/amazonlinux/amazonlinux:2 +RUN yum install -y \ + cmake3 \ + ninja-build \ + git \ + gcc-c++ \ + openssl-devel \ + curl-devel \ + openssl-static \ + zip +RUN git clone https://github.com/aws/aws-sdk-cpp --recurse-submodules +RUN cmake3 -Saws-sdk-cpp -Baws-sdk-cpp/build -DBUILD_ONLY=lambda -DENABLE_TESTING=OFF -GNinja +RUN cd aws-sdk-cpp/build && ninja-build && ninja-build install +RUN ln -s /usr/bin/cmake3 /usr/local/bin/cmake +RUN ln -s /usr/bin/ctest3 /usr/local/bin/ctest +RUN ln -s /usr/bin/ninja-build /usr/local/bin/ninja diff --git a/ci/docker/amazon-linux-2017.03 b/ci/docker/amazon-linux-2017.03 deleted file mode 100644 index f66919a..0000000 --- a/ci/docker/amazon-linux-2017.03 +++ /dev/null @@ -1,12 +0,0 @@ -FROM amazonlinux:2017.03 - -RUN yum install gcc64-c++ git ninja-build curl-devel openssl-devel zlib-devel gtest-devel python36-pip zip -y -RUN git clone https://github.com/aws/aws-sdk-cpp.git - -RUN curl -fLo cmake-install https://github.com/Kitware/CMake/releases/download/v3.13.0/cmake-3.13.0-Linux-x86_64.sh; \ -sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; - -RUN pip-3.6 install --upgrade pip - -RUN git clone https://github.com/aws/aws-sdk-cpp.git - diff --git a/ci/docker/amazon-linux-2018.03 b/ci/docker/amazon-linux-2018.03 new file mode 100644 index 0000000..142dec2 --- /dev/null +++ b/ci/docker/amazon-linux-2018.03 @@ -0,0 +1,18 @@ +FROM public.ecr.aws/amazonlinux/amazonlinux:2018.03 + +RUN yum install -y \ + gcc-c++ \ + git \ + ninja-build \ + curl-devel \ + openssl-devel \ + openssl-static \ + zlib-devel \ + gtest-devel \ + zip +RUN curl -fLo cmake-install https://github.com/Kitware/CMake/releases/download/v3.13.0/cmake-3.13.0-Linux-x86_64.sh && \ + sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; +RUN git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp +RUN cmake -Saws-sdk-cpp -Baws-sdk-cpp/build -DBUILD_ONLY=lambda -DENABLE_TESTING=OFF -GNinja +RUN cd aws-sdk-cpp/build && ninja-build && ninja-build install +RUN ln -s /usr/bin/ninja-build /usr/local/bin/ninja diff --git a/ci/docker/arch-linux b/ci/docker/arch-linux new file mode 100644 index 0000000..dce1a50 --- /dev/null +++ b/ci/docker/arch-linux @@ -0,0 +1,22 @@ +FROM public.ecr.aws/docker/library/archlinux:latest + +RUN pacman -Sy --noconfirm git +RUN git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp.git +RUN pacman -Sy --noconfirm \ + cmake \ + ninja \ + clang \ + curl \ + zip + + +# Note: (2022-08-23) +# Using -DUSE_OPENSSL=OFF as a workaround to an AWS SDK dependency issue with this distro. +# The current SDK version has a dependency on a static build version of openssl, not available through pacman. +# ref: https://github.com/aws/aws-sdk-cpp/issues/1910 +RUN CC=/usr/bin/clang CXX=/usr/bin/clang++ cmake -Saws-sdk-cpp -Baws-sdk-cpp/build -GNinja \ + -DBUILD_ONLY=lambda \ + -DUSE_OPENSSL=OFF \ + -DENABLE_TESTING=OFF +RUN cmake --build aws-sdk-cpp/build -t install + diff --git a/ci/docker/ubuntu-linux-18.04 b/ci/docker/ubuntu-linux-18.04 index 175ea82..3730f57 100644 --- a/ci/docker/ubuntu-linux-18.04 +++ b/ci/docker/ubuntu-linux-18.04 @@ -1,15 +1,22 @@ -FROM ubuntu:18.04 +FROM public.ecr.aws/ubuntu/ubuntu:18.04 -RUN apt-get update; apt-get install git clang clang-tidy clang-format zlib1g-dev libssl-dev libcurl4-openssl-dev wget \ -ninja-build python3-pip zip -y +RUN apt-get update +RUN apt-get install -y \ + git \ + clang \ + zlib1g-dev \ + libssl-dev \ + libcurl4-openssl-dev \ + wget \ + ninja-build \ + zip - -RUN wget -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.13.0/cmake-3.13.0-Linux-x86_64.sh; \ -sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; - -RUN pip3 install --upgrade pip - -RUN git clone https://github.com/aws/aws-sdk-cpp.git +RUN wget -O cmake-install https://github.com/Kitware/CMake/releases/download/v3.13.0/cmake-3.13.0-Linux-x86_64.sh && \ + sh cmake-install --skip-license --prefix=/usr --exclude-subdirectory; RUN update-alternatives --set cc /usr/bin/clang RUN update-alternatives --set c++ /usr/bin/clang++ + +RUN git clone https://github.com/aws/aws-sdk-cpp.git --recurse-submodules +RUN cmake -Saws-sdk-cpp -Baws-sdk-cpp/build -DBUILD_ONLY=lambda -DENABLE_TESTING=OFF -GNinja +RUN cd aws-sdk-cpp/build && ninja && ninja install diff --git a/ci/update-images.sh b/ci/update-images.sh new file mode 100755 index 0000000..ffff6e5 --- /dev/null +++ b/ci/update-images.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +set -euo pipefail + +PRJ_ROOT=$(git rev-parse --show-toplevel) +ECR_NAME=${ECR_NAME:-aws-lambda-cpp-ci} +REGION=${AWS_DEFAULT_REGION:-us-west-2} +ACCOUNT_ID=$(aws sts get-caller-identity --output text --query "Account") + +aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com + +# on Linux, if buildx is giving trouble - run: +# docker run --rm --privileged multiarch/qemu-user-static --reset -p yes + +build-and-push () { + TAG=$ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/$ECR_NAME:$1-$(echo $2 | sed 's|/|-|g') + docker build --platform $2 -t $TAG -f "$PRJ_ROOT/ci/docker/$1" . + docker push $TAG +} + +if [[ $(arch) == "aarch64" ]]; then + build-and-push amazon-linux-2 linux/arm64 +else + build-and-push ubuntu-linux-18.04 linux/amd64 + build-and-push alpine-linux-3.15 linux/amd64 + build-and-push amazon-linux-2018.03 linux/amd64 + build-and-push amazon-linux-2 linux/amd64 + build-and-push arch-linux linux/amd64 +fi diff --git a/examples/Dockerfile b/examples/Dockerfile index aabb4dd..1aabd59 100644 --- a/examples/Dockerfile +++ b/examples/Dockerfile @@ -1,3 +1,3 @@ FROM alpine:latest -RUN apk update && apk add cmake make git g++ bash curl-dev zlib-dev +RUN apk add --no-cache cmake make g++ git bash zip curl-dev zlib-dev libexecinfo-dev diff --git a/examples/api-gateway/CMakeLists.txt b/examples/api-gateway/CMakeLists.txt new file mode 100644 index 0000000..02da6cc --- /dev/null +++ b/examples/api-gateway/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.5) +set(CMAKE_CXX_STANDARD 11) + +project(api LANGUAGES CXX) + +find_package(aws-lambda-runtime REQUIRED) +find_package(AWSSDK COMPONENTS core) + +add_executable(${PROJECT_NAME} "main.cpp") +target_link_libraries(${PROJECT_NAME} PUBLIC AWS::aws-lambda-runtime ${AWSSDK_LINK_LIBRARIES}) + +aws_lambda_package_target(${PROJECT_NAME}) diff --git a/examples/api-gateway/README.md b/examples/api-gateway/README.md new file mode 100644 index 0000000..720f875 --- /dev/null +++ b/examples/api-gateway/README.md @@ -0,0 +1,79 @@ +# Example using the AWS C++ Lambda runtime and Amazon API Gateway + +In this example, we'll build a simple "Hello, World" lambda function that can be invoked using an api endpoint created using Amazon API gateway. This example can be viewed as the C++ counterpart to the NodeJS "Hello, World" API example as viewed [here](https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html). At the end of this example, you should be able to invoke your lambda via an api endpoint and receive a raw JSON response. This example employs the use of the AWS C++ SDK to parse the request and write the necessary response. + +## Build the AWS C++ SDK +Start by building the SDK from source. + +```bash +$ mkdir ~/install +$ git clone https://github.com/aws/aws-sdk-cpp.git +$ cd aws-sdk-cpp +$ mkdir build +$ cd build +$ cmake .. -DBUILD_ONLY="core" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DCUSTOM_MEMORY_MANAGEMENT=OFF \ + -DCMAKE_INSTALL_PREFIX=~/install +$ make +$ make install +``` + +## Build the Runtime +We need to build the C++ Lambda runtime as outlined in the other examples. + +```bash +$ git clone https://github.com/awslabs/aws-lambda-cpp-runtime.git +$ cd aws-lambda-cpp-runtime +$ mkdir build +$ cd build +$ cmake .. -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=OFF \ + -DCMAKE_INSTALL_PREFIX=~/install \ +$ make +$ make install +``` + +## Build the application +The next step is to build the Lambda function in `main.cpp` and run the packaging command as follows: + +```bash +$ mkdir build +$ cd build +$ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=~/install +$ make +$ make aws-lambda-package-api +``` + +You should now have a zip file called `api.zip`. Follow the instructions in the main README to upload it and return here once complete. + +## Using Amazon API Gateway +For the rest of this example, we will use the AWS Management Console to create the API endpoint using Amazon API Gateway. + +1. Navigate to AWS Lambda within the console [here](https://console.aws.amazon.com/lambda/home) +1. Select the newly created function. Within the specific function, the "Designer" window should appear. +1. Simply click "Add trigger" -> "API Gateway" -> "Create an API". Please view the settings below. + * API Type: HTTP API + * Security: Open + * API name: Hello-World-API (or desired name) + * Deployment stage: default +1. Once you have added the API gateway, locate the newly created endpoint. View how to test the endpoint below. + +## Test the endpoint +Feel free to test the endpoint any way you desire. Below is a way to test using cURL: + +``` +curl -v -X POST \ + '?name=Bradley&city=Chicago' \ + -H 'content-type: application/json' \ + -H 'day: Sunday' \ + -d '{ "time": "evening" }' +``` + +With the expected response being: +``` +{ + "message": "Good evening, Bradley of Chicago. Happy Sunday!" +} +``` diff --git a/examples/api-gateway/main.cpp b/examples/api-gateway/main.cpp new file mode 100644 index 0000000..90f1035 --- /dev/null +++ b/examples/api-gateway/main.cpp @@ -0,0 +1,61 @@ +#include +#include +#include + +using namespace aws::lambda_runtime; + +invocation_response my_handler(invocation_request const& request) +{ + + using namespace Aws::Utils::Json; + + JsonValue json(request.payload); + if (!json.WasParseSuccessful()) { + return invocation_response::failure("Failed to parse input JSON", "InvalidJSON"); + } + + auto v = json.View(); + Aws::SimpleStringStream ss; + ss << "Good "; + + if (v.ValueExists("body") && v.GetObject("body").IsString()) { + auto body = v.GetString("body"); + JsonValue body_json(body); + + if (body_json.WasParseSuccessful()) { + auto body_v = body_json.View(); + ss << (body_v.ValueExists("time") && body_v.GetObject("time").IsString() ? body_v.GetString("time") : ""); + } + } + ss << ", "; + + if (v.ValueExists("queryStringParameters")) { + auto query_params = v.GetObject("queryStringParameters"); + ss << (query_params.ValueExists("name") && query_params.GetObject("name").IsString() + ? query_params.GetString("name") + : "") + << " of "; + ss << (query_params.ValueExists("city") && query_params.GetObject("city").IsString() + ? query_params.GetString("city") + : "") + << ". "; + } + + if (v.ValueExists("headers")) { + auto headers = v.GetObject("headers"); + ss << "Happy " + << (headers.ValueExists("day") && headers.GetObject("day").IsString() ? headers.GetString("day") : "") + << "!"; + } + + JsonValue resp; + resp.WithString("message", ss.str()); + + return invocation_response::success(resp.View().WriteCompact(), "application/json"); +} + +int main() +{ + run_handler(my_handler); + return 0; +} diff --git a/examples/demo/CMakeLists.txt b/examples/demo/CMakeLists.txt new file mode 100644 index 0000000..06aad51 --- /dev/null +++ b/examples/demo/CMakeLists.txt @@ -0,0 +1,11 @@ +cmake_minimum_required(VERSION 3.9) +set(CMAKE_CXX_STANDARD 11) +project(demo LANGUAGES CXX) +find_package(aws-lambda-runtime) +add_executable(${PROJECT_NAME} "main.cpp") +target_link_libraries(${PROJECT_NAME} PRIVATE AWS::aws-lambda-runtime) +target_compile_features(${PROJECT_NAME} PRIVATE "cxx_std_11") +target_compile_options(${PROJECT_NAME} PRIVATE "-Wall" "-Wextra") + +# this line creates a target that packages your binary and zips it up +aws_lambda_package_target(${PROJECT_NAME}) diff --git a/examples/demo/main.cpp b/examples/demo/main.cpp new file mode 100644 index 0000000..5381311 --- /dev/null +++ b/examples/demo/main.cpp @@ -0,0 +1,20 @@ +#include + +using namespace aws::lambda_runtime; + +static invocation_response my_handler(invocation_request const& req) +{ + if (req.payload.length() > 42) { + return invocation_response::failure("error message here"/*error_message*/, + "error type here" /*error_type*/); + } + + return invocation_response::success("{\"message:\":\"I fail if body length is bigger than 42!\"}" /*payload*/, + "application/json" /*MIME type*/); +} + +int main() +{ + run_handler(my_handler); + return 0; +} diff --git a/examples/dynamodb/README.md b/examples/dynamodb/README.md index 24167c5..ebd6316 100644 --- a/examples/dynamodb/README.md +++ b/examples/dynamodb/README.md @@ -13,13 +13,11 @@ $ git clone https://github.com/aws/aws-sdk-cpp.git $ cd aws-sdk-cpp $ mkdir build $ cd build -$ cmake .. -DBUILD_ONLY="s3" \ +$ cmake .. -DBUILD_ONLY="dynamodb" \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ - -DENABLE_UNITY_BUILD=ON \ -DCUSTOM_MEMORY_MANAGEMENT=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ - -DENABLE_UNITY_BUILD=ON + -DCMAKE_INSTALL_PREFIX=~/install $ make -j 4 $ make install diff --git a/examples/dynamodb/main.cpp b/examples/dynamodb/main.cpp index a8b8662..6089fa3 100644 --- a/examples/dynamodb/main.cpp +++ b/examples/dynamodb/main.cpp @@ -173,9 +173,9 @@ aws::lambda_runtime::invocation_response my_handler( if (cr.error_msg) { JsonValue response; response.WithString("body", cr.error_msg).WithInteger("statusCode", 400); - auto const apig_response = response.View().WriteCompact(); + auto apig_response = response.View().WriteCompact(); AWS_LOGSTREAM_ERROR(TAG, "Validation failed. " << apig_response); - return aws::lambda_runtime::invocation_response::success(apig_response, "application/json"); + return aws::lambda_runtime::invocation_response::success(std::move(apig_response), "application/json"); } auto result = query(cr, client); @@ -190,10 +190,10 @@ aws::lambda_runtime::invocation_response my_handler( response.WithString("body", "No data found for this product.").WithInteger("statusCode", 400); } - auto const apig_response = response.View().WriteCompact(); + auto apig_response = response.View().WriteCompact(); AWS_LOGSTREAM_DEBUG(TAG, "api gateway response: " << apig_response); - return aws::lambda_runtime::invocation_response::success(apig_response, "application/json"); + return aws::lambda_runtime::invocation_response::success(std::move(apig_response), "application/json"); } std::function()> GetConsoleLoggerFactory() diff --git a/examples/s3/README.md b/examples/s3/README.md index 8bc3255..efc0c77 100644 --- a/examples/s3/README.md +++ b/examples/s3/README.md @@ -17,8 +17,7 @@ $ cmake .. -DBUILD_ONLY="s3" \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ -DCUSTOM_MEMORY_MANAGEMENT=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ - -DENABLE_UNITY_BUILD=ON + -DCMAKE_INSTALL_PREFIX=~/install $ make $ make install @@ -34,7 +33,7 @@ $ mkdir build $ cd build $ cmake .. -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ - -DCMAKE_INSTALL_PREFIX=~/install \ + -DCMAKE_INSTALL_PREFIX=~/install $ make $ make install ``` diff --git a/examples/s3/main.cpp b/examples/s3/main.cpp index 45b935b..892560c 100644 --- a/examples/s3/main.cpp +++ b/examples/s3/main.cpp @@ -50,7 +50,7 @@ static invocation_response my_handler(invocation_request const& req, Aws::S3::S3 return invocation_response::failure(err, "DownloadFailure"); } - return invocation_response::success(base64_encoded_file, "application/base64"); + return invocation_response::success(std::move(base64_encoded_file), "application/base64"); } std::function()> GetConsoleLoggerFactory() @@ -73,8 +73,7 @@ int main() config.region = Aws::Environment::GetEnv("AWS_REGION"); config.caFile = "/etc/pki/tls/certs/ca-bundle.crt"; - auto credentialsProvider = Aws::MakeShared(TAG); - S3::S3Client client(credentialsProvider, config); + S3::S3Client client(config); auto handler_fn = [&client](aws::lambda_runtime::invocation_request const& req) { return my_handler(req, client); }; diff --git a/include/aws/http/response.h b/include/aws/http/response.h index 9b8cbda..6ef0c20 100644 --- a/include/aws/http/response.h +++ b/include/aws/http/response.h @@ -14,6 +14,8 @@ * permissions and limitations under the License. */ +#include "aws/lambda-runtime/outcome.h" + #include #include #include @@ -31,7 +33,7 @@ class response { inline void add_header(std::string name, std::string const& value); inline void append_body(const char* p, size_t sz); inline bool has_header(char const* header) const; - inline std::string const& get_header(char const* header) const; + inline lambda_runtime::outcome get_header(char const* header) const; inline response_code get_response_code() const { return m_response_code; } inline void set_response_code(aws::http::response_code c); inline void set_content_type(char const* ct); @@ -114,7 +116,7 @@ enum class response_code { GATEWAY_TIMEOUT = 504, HTTP_VERSION_NOT_SUPPORTED = 505, VARIANT_ALSO_NEGOTIATES = 506, - INSUFFICIENT_STORAGE = 506, + INSUFFICIENT_STORAGE = 507, LOOP_DETECTED = 508, BANDWIDTH_LIMIT_EXCEEDED = 509, NOT_EXTENDED = 510, @@ -140,7 +142,7 @@ inline std::string const& response::get_body() const inline void response::add_header(std::string name, std::string const& value) { std::transform(name.begin(), name.end(), name.begin(), ::tolower); - m_headers.emplace_back(name, value); + m_headers.emplace_back(std::move(name), value); } inline void response::append_body(const char* p, size_t sz) @@ -161,12 +163,15 @@ inline bool response::has_header(char const* header) const }); } -inline std::string const& response::get_header(char const* header) const +inline lambda_runtime::outcome response::get_header(char const* header) const { auto it = std::find_if(m_headers.begin(), m_headers.end(), [header](std::pair const& p) { return p.first == header; }); - assert(it != m_headers.end()); + + if (it == m_headers.end()) { + return false; + } return it->second; } diff --git a/include/aws/lambda-runtime/outcome.h b/include/aws/lambda-runtime/outcome.h index 5d0af4a..9982f5d 100644 --- a/include/aws/lambda-runtime/outcome.h +++ b/include/aws/lambda-runtime/outcome.h @@ -15,6 +15,7 @@ */ #include +#include namespace aws { namespace lambda_runtime { @@ -22,50 +23,90 @@ namespace lambda_runtime { template class outcome { public: - outcome(TResult const& s) : s(s), success(true) {} + outcome(TResult const& s) : m_s(s), m_success(true) {} + outcome(TResult&& s) : m_s(std::move(s)), m_success(true) {} - outcome(TFailure const& f) : f(f), success(false) {} + outcome(TFailure const& f) : m_f(f), m_success(false) {} + outcome(TFailure&& f) : m_f(std::move(f)), m_success(false) {} - outcome(outcome&& other) : success(other.success) + outcome(outcome const& other) : m_success(other.m_success) { - if (success) { - s = std::move(other.s); + if (m_success) { + new (&m_s) TResult(other.m_s); } else { - f = std::move(other.f); + new (&m_f) TFailure(other.m_f); } } - ~outcome() + outcome(outcome&& other) noexcept : m_success(other.m_success) { - if (success) { - s.~TResult(); + if (m_success) { + new (&m_s) TResult(std::move(other.m_s)); } else { - f.~TFailure(); + new (&m_f) TFailure(std::move(other.m_f)); } } - TResult const& get_result() const + ~outcome() { destroy(); } + + outcome& operator=(outcome&& other) noexcept + { + assert(this != &other); + destroy(); + if (other.m_success) { + new (&m_s) TResult(std::move(other.m_s)); + } + else { + new (&m_f) TFailure(std::move(other.m_f)); + } + m_success = other.m_success; + return *this; + } + + TResult const& get_result() const& { - assert(success); - return s; + assert(m_success); + return m_s; } - TFailure const& get_failure() const + TResult&& get_result() && { - assert(!success); - return f; + assert(m_success); + return std::move(m_s); } - bool is_success() const { return success; } + TFailure const& get_failure() const& + { + assert(!m_success); + return m_f; + } + + TFailure&& get_failure() && + { + assert(!m_success); + return std::move(m_f); + } + + bool is_success() const { return m_success; } private: + void destroy() + { + if (m_success) { + m_s.~TResult(); + } + else { + m_f.~TFailure(); + } + } + union { - TResult s; - TFailure f; + TResult m_s; + TFailure m_f; }; - bool success; + bool m_success; }; } // namespace lambda_runtime } // namespace aws diff --git a/include/aws/lambda-runtime/runtime.h b/include/aws/lambda-runtime/runtime.h index d73c2e8..46b8817 100644 --- a/include/aws/lambda-runtime/runtime.h +++ b/include/aws/lambda-runtime/runtime.h @@ -14,9 +14,13 @@ * permissions and limitations under the License. */ +#include #include #include #include +#include +#include "aws/lambda-runtime/outcome.h" +#include "aws/http/response.h" namespace aws { namespace lambda_runtime { @@ -88,10 +92,20 @@ class invocation_response { invocation_response() = default; public: + // Create a success or failure response. Typically, you should use the static functions invocation_response::success + // and invocation_response::failure, however, invocation_response::failure doesn't allow for arbitrary payloads. + // To support clients that need to control the entire error response body (e.g. adding a stack trace), this + // constructor should be used instead. + // Note: adding an overload to invocation_response::failure is not feasible since the parameter types are the same. + invocation_response(std::string const& payload, std::string const& content_type, bool success) + : m_payload(payload), m_content_type(content_type), m_success(success) + { + } + /** * Create a successful invocation response with the given payload and content-type. */ - static invocation_response success(std::string const& payload, std::string const& content_type); + static invocation_response success(std::string payload, std::string content_type); /** * Create a failure response with the given error message and error type. @@ -115,6 +129,44 @@ class invocation_response { bool is_success() const { return m_success; } }; +struct no_result {}; + +class runtime { +public: + using next_outcome = aws::lambda_runtime::outcome; + using post_outcome = aws::lambda_runtime::outcome; + + runtime(std::string const& endpoint, std::string const& user_agent); + runtime(std::string const& endpoint); + ~runtime(); + + /** + * Ask lambda for an invocation. + */ + next_outcome get_next(); + + /** + * Tells lambda that the function has succeeded. + */ + post_outcome post_success(std::string const& request_id, invocation_response const& handler_response); + + /** + * Tells lambda that the function has failed. + */ + post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response); + +private: + void set_curl_next_options(); + void set_curl_post_result_options(); + post_outcome do_post( + std::string const& url, + std::string const& request_id, + invocation_response const& handler_response); + std::string const m_user_agent_header; + std::array const m_endpoints; + CURL* const m_curl_handle; +}; + inline std::chrono::milliseconds invocation_request::get_time_remaining() const { using namespace std::chrono; diff --git a/include/aws/logging/logging.h b/include/aws/logging/logging.h index a3d9f50..0b5d0ef 100644 --- a/include/aws/logging/logging.h +++ b/include/aws/logging/logging.h @@ -27,7 +27,7 @@ enum class verbosity { void log(verbosity v, char const* tag, char const* msg, va_list args); -[[gnu::format(printf, 2, 3)]] static inline void log_error(char const* tag, char const* msg, ...) +[[gnu::format(printf, 2, 3)]] inline void log_error(char const* tag, char const* msg, ...) { va_list args; va_start(args, msg); @@ -37,7 +37,7 @@ void log(verbosity v, char const* tag, char const* msg, va_list args); (void)msg; } -[[gnu::format(printf, 2, 3)]] static inline void log_info(char const* tag, char const* msg, ...) +[[gnu::format(printf, 2, 3)]] inline void log_info(char const* tag, char const* msg, ...) { #if AWS_LAMBDA_LOG >= 1 va_list args; @@ -50,7 +50,7 @@ void log(verbosity v, char const* tag, char const* msg, va_list args); #endif } -[[gnu::format(printf, 2, 3)]] static inline void log_debug(char const* tag, char const* msg, ...) +[[gnu::format(printf, 2, 3)]] inline void log_debug(char const* tag, char const* msg, ...) { #if AWS_LAMBDA_LOG >= 2 va_list args; diff --git a/packaging/packager b/packaging/packager index efcafcc..c050696 100755 --- a/packaging/packager +++ b/packaging/packager @@ -55,26 +55,43 @@ if ! type zip > /dev/null 2>&1; then echo "zip utility is not found. Please install it and re-run this script" exit 1 fi -function package_libc_via_pacman { - if [[ $(sed '/ID_LIKE/!d;s/ID_LIKE=//' < /etc/os-release) == "archlinux" ]]; then + +function pluck_so_files() { + sed -E '/\.so$|\.so\.[0-9]+$/!d' +} + +function package_libc_alpine() { + # -F matches a fixed string rather than a regex (grep that comes with busybox doesn't know --fixed-strings) + if grep -F "Alpine Linux" < /etc/os-release > /dev/null; then + if type apk > /dev/null 2>&1; then + apk info --contents musl 2>/dev/null | pluck_so_files | sed 's/^/\//' + fi + fi +} + +function package_libc_pacman() { + if grep --extended-regexp "Arch Linux|Manjaro Linux" < /etc/os-release > /dev/null 2>&1; then if type pacman > /dev/null 2>&1; then - pacman --files --list --quiet glibc | sed -E '/\.so$|\.so\.[0-9]+$/!d' + pacman --query --list --quiet glibc | pluck_so_files fi fi } -function package_libc_via_dpkg() { +function package_libc_dpkg() { if type dpkg-query > /dev/null 2>&1; then - if [[ $(dpkg-query --listfiles libc6 | wc -l) -gt 0 ]]; then - dpkg-query --listfiles libc6 | sed -E '/\.so$|\.so\.[0-9]+$/!d' + architecture=$(dpkg --print-architecture) + if [[ $(dpkg-query --listfiles libc6:$architecture | wc -l) -gt 0 ]]; then + dpkg-query --listfiles libc6:$architecture | pluck_so_files fi fi } -function package_libc_via_rpm() { +function package_libc_rpm() { + arch=$(uname -m) + if type rpm > /dev/null 2>&1; then - if [[ $(rpm --query --list glibc | wc -l) -gt 1 ]]; then - rpm --query --list glibc | sed -E '/\.so$|\.so\.[0-9]+$/!d' + if [[ $(rpm --query --list glibc.$arch | wc -l) -gt 1 ]]; then + rpm --query --list glibc.$arch | pluck_so_files fi fi } @@ -98,9 +115,10 @@ PKG_LD="" list=$(ldd "$PKG_BIN_PATH" | awk '{print $(NF-1)}') libc_libs=() -libc_libs+=($(package_libc_via_dpkg)) -libc_libs+=($(package_libc_via_rpm)) -libc_libs+=($(package_libc_via_pacman)) +libc_libs+=($(package_libc_dpkg)) +libc_libs+=($(package_libc_rpm)) +libc_libs+=($(package_libc_pacman)) +libc_libs+=($(package_libc_alpine)) mkdir -p "$PKG_DIR/bin" "$PKG_DIR/lib" @@ -115,7 +133,7 @@ do filename=$(basename "$i") if [[ -z "${filename##ld-*}" ]]; then PKG_LD=$filename # Use this file as the loader - cp "$i" $PKG_DIR/lib + cp "$i" "$PKG_DIR/lib" fi continue fi @@ -128,12 +146,23 @@ if [[ $INCLUDE_LIBC == true ]]; then do filename=$(basename "$i") if [[ -z "${filename##ld-*}" ]]; then + # if the loader is empty, then the binary is probably linked to a symlink of the loader. The symlink will + # not show up when quering the package manager for libc files. So, in this case, we want to copy the loader + if [[ -z "$PKG_LD" ]]; then + PKG_LD=$filename + cp "$i" "$PKG_DIR/lib" # we want to follow the symlink (default behavior) + fi continue # We don't want the dynamic loader's symlink because its target is an absolute path (/lib/ld-*). fi cp --no-dereference "$i" "$PKG_DIR/lib" done fi +if [[ -z "$PKG_LD" ]]; then + echo "Failed to identify, locate or package the loader. Please file an issue on Github!" 1>&2 + exit 1 +fi + bootstrap_script=$(cat < "$PKG_DIR/bootstrap" else - echo -e "$bootstrap_script_no_libc" > "$PKG_DIR/bootstrap" + cp "$PKG_BIN_PATH" "$PKG_DIR/bootstrap" fi chmod +x "$PKG_DIR/bootstrap" # some shenanigans to create the right layout in the zip file without extraneous directories diff --git a/src/backward.cpp b/src/backward.cpp index cc64abd..8649fd5 100644 --- a/src/backward.cpp +++ b/src/backward.cpp @@ -23,10 +23,14 @@ // - g++/clang++ -lbfd ... // #define BACKWARD_HAS_BFD 1 -#include "backward.h" +#ifndef no_backtrace + +# include "backward.h" namespace backward { backward::SignalHandling sh; } // namespace backward + +#endif diff --git a/include/backward.h b/src/backward.h similarity index 71% rename from include/backward.h rename to src/backward.h index e065783..832c452 100644 --- a/include/backward.h +++ b/src/backward.h @@ -22,65 +22,82 @@ */ #ifndef H_6B9572DA_A64B_49E6_B234_051480991C89 -# define H_6B9572DA_A64B_49E6_B234_051480991C89 - -# ifndef __cplusplus -# error "It's not going to compile without a C++ compiler..." -# endif - -# if defined(BACKWARD_CXX11) -# elif defined(BACKWARD_CXX98) -# else -# if __cplusplus >= 201103L -# define BACKWARD_CXX11 -# define BACKWARD_ATLEAST_CXX11 -# define BACKWARD_ATLEAST_CXX98 -# else -# define BACKWARD_CXX98 -# define BACKWARD_ATLEAST_CXX98 +#define H_6B9572DA_A64B_49E6_B234_051480991C89 + +#ifndef __cplusplus +# error "It's not going to compile without a C++ compiler..." +#endif + +#ifdef no_backtrace +# pragma message "Disabling stacktracing" +#else + +#if defined(BACKWARD_CXX11) +#elif defined(BACKWARD_CXX98) +#else +# if __cplusplus >= 201103L || (defined(_MSC_VER) && _MSC_VER >= 1800) +# define BACKWARD_CXX11 +# define BACKWARD_ATLEAST_CXX11 +# define BACKWARD_ATLEAST_CXX98 +# if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +# define BACKWARD_ATLEAST_CXX17 # endif +# else +# define BACKWARD_CXX98 +# define BACKWARD_ATLEAST_CXX98 # endif +#endif // You can define one of the following (or leave it to the auto-detection): // -# define BACKWARD_SYSTEM_LINUX +// #define BACKWARD_SYSTEM_LINUX // - specialization for linux // // #define BACKWARD_SYSTEM_DARWIN // - specialization for Mac OS X 10.5 and later. // +// #define BACKWARD_SYSTEM_WINDOWS +// - specialization for Windows (Clang 9 and MSVC2017) +// // #define BACKWARD_SYSTEM_UNKNOWN // - placebo implementation, does nothing. // -# if defined(BACKWARD_SYSTEM_LINUX) -# elif defined(BACKWARD_SYSTEM_DARWIN) -# elif defined(BACKWARD_SYSTEM_UNKNOWN) +#if defined(BACKWARD_SYSTEM_LINUX) +#elif defined(BACKWARD_SYSTEM_DARWIN) +#elif defined(BACKWARD_SYSTEM_UNKNOWN) +#elif defined(BACKWARD_SYSTEM_WINDOWS) +#else +# if defined(__linux) || defined(__linux__) +# define BACKWARD_SYSTEM_LINUX +# elif defined(__APPLE__) +# define BACKWARD_SYSTEM_DARWIN +# elif defined(_WIN32) +# define BACKWARD_SYSTEM_WINDOWS # else -# if defined(__linux) || defined(__linux__) -# define BACKWARD_SYSTEM_LINUX -# elif defined(__APPLE__) -# define BACKWARD_SYSTEM_DARWIN -# else -# define BACKWARD_SYSTEM_UNKNOWN -# endif +# define BACKWARD_SYSTEM_UNKNOWN # endif - -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include -# include - -# if defined(BACKWARD_SYSTEM_LINUX) +#endif + +#define NOINLINE __attribute__((noinline)) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BACKWARD_SYSTEM_LINUX) // On linux, backtrace can back-trace or "walk" the stack using the following // libraries: @@ -92,6 +109,11 @@ // exception. // - normally libgcc is already linked to your program by default. // +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// // #define BACKWARD_HAS_BACKTRACE == 1 // - backtrace seems to be a little bit more portable than libunwind, but on // linux, it uses unwind anyway, but abstract away a tiny information that is @@ -103,14 +125,17 @@ // // Note that only one of the define should be set to 1 at a time. // -# if BACKWARD_HAS_UNWIND == 1 -# elif BACKWARD_HAS_BACKTRACE == 1 -# else -# undef BACKWARD_HAS_UNWIND -# define BACKWARD_HAS_UNWIND 1 -# undef BACKWARD_HAS_BACKTRACE -# define BACKWARD_HAS_BACKTRACE 0 -# endif +# if BACKWARD_HAS_UNWIND == 1 +# elif BACKWARD_HAS_LIBUNWIND == 1 +# elif BACKWARD_HAS_BACKTRACE == 1 +# else +# undef BACKWARD_HAS_UNWIND +# define BACKWARD_HAS_UNWIND 1 +# undef BACKWARD_HAS_LIBUNWIND +# define BACKWARD_HAS_LIBUNWIND 0 +# undef BACKWARD_HAS_BACKTRACE +# define BACKWARD_HAS_BACKTRACE 0 +# endif // On linux, backward can extract detailed information about a stack trace // using one of the following libraries: @@ -120,9 +145,10 @@ // - object filename // - function name // - source filename -// - line and column numbers -// - source code snippet (assuming the file is accessible) -// - variables name and values (if not optimized out) +// - line and column numbers +// - source code snippet (assuming the file is accessible) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) // - You need to link with the lib "dw": // - apt-get install libdw-dev // - g++/clang++ -ldw ... @@ -132,8 +158,8 @@ // - object filename // - function name // - source filename -// - line numbers -// - source code snippet (assuming the file is accessible) +// - line numbers +// - source code snippet (assuming the file is accessible) // - You need to link with the lib "bfd": // - apt-get install binutils-dev // - g++/clang++ -lbfd ... @@ -145,7 +171,8 @@ // - source filename // - line and column numbers // - source code snippet (assuming the file is accessible) -// - variables name and values (if not optimized out) +// - variable names (if not optimized out) +// - variable values (not supported by backward-cpp) // - You need to link with the lib "dwarf": // - apt-get install libdwarf-dev // - g++/clang++ -ldwarf ... @@ -161,86 +188,88 @@ // // Note that only one of the define should be set to 1 at a time. // -# if BACKWARD_HAS_DW == 1 -# elif BACKWARD_HAS_BFD == 1 -# elif BACKWARD_HAS_DWARF == 1 -# elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -# else -# undef BACKWARD_HAS_DW -# define BACKWARD_HAS_DW 0 -# undef BACKWARD_HAS_BFD -# define BACKWARD_HAS_BFD 0 -# undef BACKWARD_HAS_DWARF -# define BACKWARD_HAS_DWARF 0 -# undef BACKWARD_HAS_BACKTRACE_SYMBOL -# define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -# endif +# if BACKWARD_HAS_DW == 1 +# elif BACKWARD_HAS_BFD == 1 +# elif BACKWARD_HAS_DWARF == 1 +# elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +# else +# undef BACKWARD_HAS_DW +# define BACKWARD_HAS_DW 0 +# undef BACKWARD_HAS_BFD +# define BACKWARD_HAS_BFD 0 +# undef BACKWARD_HAS_DWARF +# define BACKWARD_HAS_DWARF 0 +# undef BACKWARD_HAS_BACKTRACE_SYMBOL +# define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +# endif -# include -# include -# ifdef __ANDROID__ -// Old Android API levels define _Unwind_Ptr in both link.h and unwind.h -// Rename the one in link.h as we are not going to be using it -# define _Unwind_Ptr _Unwind_Ptr_Custom -# include -# undef _Unwind_Ptr -# else -# include -# endif -# include -# include -# include -# include +# include +# include +# ifdef __ANDROID__ +// Old Android API levels define _Unwind_Ptr in both link.h and +// unwind.h Rename the one in link.h as we are not going to be using +// it +# define _Unwind_Ptr _Unwind_Ptr_Custom +# include +# undef _Unwind_Ptr +# else +# include +# endif +# include +# include +# include +# include -# if BACKWARD_HAS_BFD == 1 +# if BACKWARD_HAS_BFD == 1 // NOTE: defining PACKAGE{,_VERSION} is required before including // bfd.h on some platforms, see also: // https://sourceware.org/bugzilla/show_bug.cgi?id=14243 -# ifndef PACKAGE -# define PACKAGE -# endif -# ifndef PACKAGE_VERSION -# define PACKAGE_VERSION -# endif -# include -# ifndef _GNU_SOURCE -# define _GNU_SOURCE -# include -# undef _GNU_SOURCE -# else -# include -# endif +# ifndef PACKAGE +# define PACKAGE # endif - -# if BACKWARD_HAS_DW == 1 -# include -# include -# include +# ifndef PACKAGE_VERSION +# define PACKAGE_VERSION +# endif +# include +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# include +# undef _GNU_SOURCE +# else +# include # endif +# endif -# if BACKWARD_HAS_DWARF == 1 -# include -# include -# include -# include -# include -# ifndef _GNU_SOURCE -# define _GNU_SOURCE -# include -# undef _GNU_SOURCE -# else -# include -# endif +# if BACKWARD_HAS_DW == 1 +# include +# include +# include +# endif + +# if BACKWARD_HAS_DWARF == 1 +# include +# include +# include +# include +# include +# ifndef _GNU_SOURCE +# define _GNU_SOURCE +# include +# undef _GNU_SOURCE +# else +# include # endif +# endif -# if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) +# if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) // then we shall rely on backtrace -# include -# endif +# include +# include +# endif -# endif // defined(BACKWARD_SYSTEM_LINUX) +#endif // defined(BACKWARD_SYSTEM_LINUX) -# if defined(BACKWARD_SYSTEM_DARWIN) +#if defined(BACKWARD_SYSTEM_DARWIN) // On Darwin, backtrace can back-trace or "walk" the stack using the following // libraries: // @@ -251,23 +280,32 @@ // exception. // - normally libgcc is already linked to your program by default. // +// #define BACKWARD_HAS_LIBUNWIND 1 +// - libunwind comes from clang, which implements an API compatible version. +// - libunwind provides, in some cases, a more accurate stacktrace as it knows +// to decode signal handler frames and lets us edit the context registers when +// unwinding, allowing stack traces over bad function references. +// // #define BACKWARD_HAS_BACKTRACE == 1 -// - backtrace is available by default, though it does not produce as much information -// as another library might. +// - backtrace is available by default, though it does not produce as much +// information as another library might. // // The default is: // #define BACKWARD_HAS_UNWIND == 1 // // Note that only one of the define should be set to 1 at a time. // -# if BACKWARD_HAS_UNWIND == 1 -# elif BACKWARD_HAS_BACKTRACE == 1 -# else -# undef BACKWARD_HAS_UNWIND -# define BACKWARD_HAS_UNWIND 1 -# undef BACKWARD_HAS_BACKTRACE -# define BACKWARD_HAS_BACKTRACE 0 -# endif +# if BACKWARD_HAS_UNWIND == 1 +# elif BACKWARD_HAS_BACKTRACE == 1 +# elif BACKWARD_HAS_LIBUNWIND == 1 +# else +# undef BACKWARD_HAS_UNWIND +# define BACKWARD_HAS_UNWIND 1 +# undef BACKWARD_HAS_BACKTRACE +# define BACKWARD_HAS_BACKTRACE 0 +# undef BACKWARD_HAS_LIBUNWIND +# define BACKWARD_HAS_LIBUNWIND 0 +# endif // On Darwin, backward can extract detailed information about a stack trace // using one of the following libraries: @@ -280,27 +318,73 @@ // The default is: // #define BACKWARD_HAS_BACKTRACE_SYMBOL == 1 // -# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 -# else -# undef BACKWARD_HAS_BACKTRACE_SYMBOL -# define BACKWARD_HAS_BACKTRACE_SYMBOL 1 -# endif +# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +# else +# undef BACKWARD_HAS_BACKTRACE_SYMBOL +# define BACKWARD_HAS_BACKTRACE_SYMBOL 1 +# endif +# include +# include +# include +# include +# include +# include + +# if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) +# include +# endif +#endif // defined(BACKWARD_SYSTEM_DARWIN) -# include -# include -# include -# include -# include -# include +#if defined(BACKWARD_SYSTEM_WINDOWS) -# if (BACKWARD_HAS_BACKTRACE == 1) || (BACKWARD_HAS_BACKTRACE_SYMBOL == 1) -# include -# endif -# endif // defined(BACKWARD_SYSTEM_DARWIN) +# include +# include +# include -# if BACKWARD_HAS_UNWIND == 1 +# include +typedef SSIZE_T ssize_t; + +# ifndef NOMINMAX +# define NOMINMAX +# endif +# include +# include -# include +# include +# include + +# ifndef __clang__ +# undef NOINLINE +# define NOINLINE __declspec(noinline) +# endif + +# ifdef _MSC_VER +# pragma comment(lib, "psapi.lib") +# pragma comment(lib, "dbghelp.lib") +# endif + +// Comment / packing is from stackoverflow: +// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 +// Some versions of imagehlp.dll lack the proper packing directives themselves +// so we need to do it. +# pragma pack(push, before_imagehlp, 8) +# include +# pragma pack(pop, before_imagehlp) + +// TODO maybe these should be undefined somewhere else? +# undef BACKWARD_HAS_UNWIND +# undef BACKWARD_HAS_BACKTRACE +# if BACKWARD_HAS_PDB_SYMBOL == 1 +# else +# undef BACKWARD_HAS_PDB_SYMBOL +# define BACKWARD_HAS_PDB_SYMBOL 1 +# endif + +#endif + +#if BACKWARD_HAS_UNWIND == 1 + +# include // while gcc's unwind.h defines something like that: // extern _Unwind_Ptr _Unwind_GetIP (struct _Unwind_Context *); // extern _Unwind_Ptr _Unwind_GetIPInfo (struct _Unwind_Context *, int *); @@ -313,18 +397,23 @@ // anyway. // // Luckily we can play on the fact that the guard macros have a different name: -# ifdef __CLANG_UNWIND_H +# ifdef __CLANG_UNWIND_H // In fact, this function still comes from libgcc (on my different linux boxes, // clang links against libgcc). -# include +# include extern "C" uintptr_t _Unwind_GetIPInfo(_Unwind_Context*, int*); -# endif +# endif -# endif // BACKWARD_HAS_UNWIND == 1 +#endif // BACKWARD_HAS_UNWIND == 1 -# ifdef BACKWARD_ATLEAST_CXX11 -# include -# include // for std::swap +#if BACKWARD_HAS_LIBUNWIND == 1 +# define UNW_LOCAL_ONLY +# include +#endif // BACKWARD_HAS_LIBUNWIND == 1 + +#ifdef BACKWARD_ATLEAST_CXX11 +# include +# include // for std::swap namespace backward { namespace details { template @@ -334,10 +423,10 @@ struct hashtable { using std::move; } // namespace details } // namespace backward -# else // NOT BACKWARD_ATLEAST_CXX11 -# define nullptr NULL -# define override -# include +#else // NOT BACKWARD_ATLEAST_CXX11 +# define nullptr NULL +# define override +# include namespace backward { namespace details { template @@ -356,7 +445,17 @@ T& move(T& v) } } // namespace details } // namespace backward -# endif // BACKWARD_ATLEAST_CXX11 +#endif // BACKWARD_ATLEAST_CXX11 + +namespace backward { +namespace details { +#if defined(BACKWARD_SYSTEM_WINDOWS) +const char kBackwardPathDelimiter[] = ";"; +#else +const char kBackwardPathDelimiter[] = ":"; +#endif +} // namespace details +} // namespace backward namespace backward { @@ -364,46 +463,56 @@ namespace system_tag { struct linux_tag; // seems that I cannot call that "linux" because the name // is already defined... so I am adding _tag everywhere. struct darwin_tag; +struct windows_tag; struct unknown_tag; -# if defined(BACKWARD_SYSTEM_LINUX) +#if defined(BACKWARD_SYSTEM_LINUX) typedef linux_tag current_tag; -# elif defined(BACKWARD_SYSTEM_DARWIN) +#elif defined(BACKWARD_SYSTEM_DARWIN) typedef darwin_tag current_tag; -# elif defined(BACKWARD_SYSTEM_UNKNOWN) +#elif defined(BACKWARD_SYSTEM_WINDOWS) +typedef windows_tag current_tag; +#elif defined(BACKWARD_SYSTEM_UNKNOWN) typedef unknown_tag current_tag; -# else -# error "May I please get my system defines?" -# endif +#else +# error "May I please get my system defines?" +#endif } // namespace system_tag namespace trace_resolver_tag { -# if defined(BACKWARD_SYSTEM_LINUX) +#if defined(BACKWARD_SYSTEM_LINUX) struct libdw; struct libbfd; struct libdwarf; struct backtrace_symbol; -# if BACKWARD_HAS_DW == 1 +# if BACKWARD_HAS_DW == 1 typedef libdw current; -# elif BACKWARD_HAS_BFD == 1 +# elif BACKWARD_HAS_BFD == 1 typedef libbfd current; -# elif BACKWARD_HAS_DWARF == 1 +# elif BACKWARD_HAS_DWARF == 1 typedef libdwarf current; -# elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +# elif BACKWARD_HAS_BACKTRACE_SYMBOL == 1 typedef backtrace_symbol current; -# else -# error "You shall not pass, until you know what you want." -# endif -# elif defined(BACKWARD_SYSTEM_DARWIN) +# else +# error "You shall not pass, until you know what you want." +# endif +#elif defined(BACKWARD_SYSTEM_DARWIN) struct backtrace_symbol; -# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 typedef backtrace_symbol current; -# else -# error "You shall not pass, until you know what you want." -# endif +# else +# error "You shall not pass, until you know what you want." +# endif +#elif defined(BACKWARD_SYSTEM_WINDOWS) +struct pdb_symbol; +# if BACKWARD_HAS_PDB_SYMBOL == 1 +typedef pdb_symbol current; +# else +# error "You shall not pass, until you know what you want." # endif +#endif } // namespace trace_resolver_tag namespace details { @@ -443,10 +552,10 @@ class handle { T _val; bool _empty; -# ifdef BACKWARD_ATLEAST_CXX11 +#ifdef BACKWARD_ATLEAST_CXX11 handle(const handle&) = delete; handle& operator=(const handle&) = delete; -# endif +#endif public: ~handle() @@ -463,14 +572,14 @@ class handle { _empty = true; } -# ifdef BACKWARD_ATLEAST_CXX11 +#ifdef BACKWARD_ATLEAST_CXX11 handle(handle&& from) : _empty(true) { swap(from); } handle& operator=(handle&& from) { swap(from); return *this; } -# else +#else explicit handle(const handle& from) : _empty(true) { // some sort of poor man's move semantic. @@ -482,13 +591,20 @@ class handle { swap(const_cast(from)); return *this; } -# endif +#endif void reset(T new_val) { handle tmp(new_val); swap(tmp); } + + void update(T new_val) + { + _val = new_val; + _empty = !static_cast(new_val); + } + operator const dummy*() const { if (_empty) { @@ -510,7 +626,7 @@ class handle { // bools without throwing... It's a lost cause anyway! } - T operator->() { return _val; } + T& operator->() { return _val; } const T& operator->() const { return _val; } typedef typename rm_ptr::type& ref_t; @@ -533,7 +649,7 @@ struct demangler_impl { static std::string demangle(const char* funcname) { return funcname; } }; -# if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) template <> struct demangler_impl { @@ -542,9 +658,9 @@ struct demangler_impl { std::string demangle(const char* funcname) { using namespace details; - char* result = abi::__cxa_demangle(funcname, _demangle_buffer.release(), &_demangle_buffer_length, nullptr); + char* result = abi::__cxa_demangle(funcname, _demangle_buffer.get(), &_demangle_buffer_length, nullptr); if (result) { - _demangle_buffer.reset(result); + _demangle_buffer.update(result); return result; } return funcname; @@ -555,11 +671,35 @@ struct demangler_impl { size_t _demangle_buffer_length; }; -# endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN +#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN struct demangler : public demangler_impl { }; +// Split a string on the platform's PATH delimiter. Example: if delimiter +// is ":" then: +// "" --> [] +// ":" --> ["",""] +// "::" --> ["","",""] +// "/a/b/c" --> ["/a/b/c"] +// "/a/b/c:/d/e/f" --> ["/a/b/c","/d/e/f"] +// etc. +inline std::vector split_source_prefixes(const std::string& s) +{ + std::vector out; + size_t last = 0; + size_t next = 0; + size_t delimiter_size = sizeof(kBackwardPathDelimiter) - 1; + while ((next = s.find(kBackwardPathDelimiter, last)) != std::string::npos) { + out.push_back(s.substr(last, next - last)); + last = next + delimiter_size; + } + if (last <= s.length()) { + out.push_back(s.substr(last)); + } + return out; +} + } // namespace details /*************** A TRACE ***************/ @@ -621,16 +761,16 @@ template class StackTraceImpl { public: size_t size() const { return 0; } - Trace operator[](size_t) { return Trace(); } + Trace operator[](size_t) const { return Trace(); } size_t load_here(size_t = 0) { return 0; } - size_t load_from(void*, size_t = 0) { return 0; } + size_t load_from(void*, size_t = 0, void* = nullptr, void* = nullptr) { return 0; } size_t thread_id() const { return 0; } void skip_n_firsts(size_t) {} }; class StackTraceImplBase { public: - StackTraceImplBase() : _thread_id(0), _skip(0) {} + StackTraceImplBase() : _thread_id(0), _skip(0), _context(nullptr), _error_addr(nullptr) {} size_t thread_id() const { return _thread_id; } @@ -639,36 +779,44 @@ class StackTraceImplBase { protected: void load_thread_info() { -# ifdef BACKWARD_SYSTEM_LINUX -# ifndef __ANDROID__ +#ifdef BACKWARD_SYSTEM_LINUX +# ifndef __ANDROID__ _thread_id = static_cast(syscall(SYS_gettid)); -# else +# else _thread_id = static_cast(gettid()); -# endif +# endif if (_thread_id == static_cast(getpid())) { // If the thread is the main one, let's hide that. // I like to keep little secret sometimes. _thread_id = 0; } -# elif defined(BACKWARD_SYSTEM_DARWIN) +#elif defined(BACKWARD_SYSTEM_DARWIN) _thread_id = reinterpret_cast(pthread_self()); if (pthread_main_np() == 1) { // If the thread is the main one, let's hide that. _thread_id = 0; } -# endif +#endif } + void set_context(void* context) { _context = context; } + void* context() const { return _context; } + + void set_error_addr(void* error_addr) { _error_addr = error_addr; } + void* error_addr() const { return _error_addr; } + size_t skip_n_firsts() const { return _skip; } private: size_t _thread_id; size_t _skip; + void* _context; + void* _error_addr; }; class StackTraceImplHolder : public StackTraceImplBase { public: - size_t size() const { return _stacktrace.size() ? _stacktrace.size() - skip_n_firsts() : 0; } + size_t size() const { return (_stacktrace.size() >= skip_n_firsts()) ? _stacktrace.size() - skip_n_firsts() : 0; } Trace operator[](size_t idx) const { if (idx >= size()) { @@ -688,7 +836,7 @@ class StackTraceImplHolder : public StackTraceImplBase { std::vector _stacktrace; }; -# if BACKWARD_HAS_UNWIND == 1 +#if BACKWARD_HAS_UNWIND == 1 namespace details { @@ -723,12 +871,15 @@ class Unwinder { uintptr_t ip = _Unwind_GetIPInfo(ctx, &ip_before_instruction); if (!ip_before_instruction) { - // calculating 0-1 for unsigned, looks like a possible bug to sanitiziers, so let's do it explicitly: + // calculating 0-1 for unsigned, looks like a possible bug to sanitiziers, + // so let's do it explicitly: if (ip == 0) { - ip = std::numeric_limits::max(); // set it to 0xffff... (as from casting 0-1) + ip = std::numeric_limits::max(); // set it to 0xffff... (as + // from casting 0-1) } else { - ip -= 1; // else just normally decrement it (no overflow/underflow will happen) + ip -= 1; // else just normally decrement it (no overflow/underflow will + // happen) } } @@ -752,11 +903,12 @@ size_t unwind(F f, size_t depth) template <> class StackTraceImpl : public StackTraceImplHolder { public: - __attribute__((noinline)) // TODO use some macro - size_t - load_here(size_t depth = 32) + NOINLINE + size_t load_here(size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { load_thread_info(); + set_context(context); + set_error_addr(error_addr); if (depth == 0) { return 0; } @@ -766,9 +918,9 @@ class StackTraceImpl : public StackTraceImplHolder { skip_n_firsts(0); return size(); } - size_t load_from(void* addr, size_t depth = 32) + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { - load_here(depth + 8); + load_here(depth + 8, context, error_addr); for (size_t i = 0; i < _stacktrace.size(); ++i) { if (_stacktrace[i] == addr) { @@ -790,15 +942,182 @@ class StackTraceImpl : public StackTraceImplHolder { }; }; -# else // BACKWARD_HAS_UNWIND == 0 +#elif BACKWARD_HAS_LIBUNWIND == 1 + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + __attribute__((noinline)) size_t load_here(size_t depth = 32, void* _context = nullptr, void* _error_addr = nullptr) + { + set_context(_context); + set_error_addr(_error_addr); + load_thread_info(); + if (depth == 0) { + return 0; + } + _stacktrace.resize(depth + 1); + + int result = 0; + + unw_context_t ctx; + size_t index = 0; + + // Add the tail call. If the Instruction Pointer is the crash address it + // means we got a bad function pointer dereference, so we "unwind" the + // bad pointer manually by using the return address pointed to by the + // Stack Pointer as the Instruction Pointer and letting libunwind do + // the rest + + if (context()) { + ucontext_t* uctx = reinterpret_cast(context()); +# ifdef REG_RIP // x86_64 + if (uctx->uc_mcontext.gregs[REG_RIP] == reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_RIP] = *reinterpret_cast(uctx->uc_mcontext.gregs[REG_RSP]); + } + _stacktrace[index] = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); + ++index; + ctx = *reinterpret_cast(uctx); +# elif defined(REG_EIP) // x86_32 + if (uctx->uc_mcontext.gregs[REG_EIP] == reinterpret_cast(error_addr())) { + uctx->uc_mcontext.gregs[REG_EIP] = *reinterpret_cast(uctx->uc_mcontext.gregs[REG_ESP]); + } + _stacktrace[index] = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); + ++index; + ctx = *reinterpret_cast(uctx); +# elif defined(__arm__) + // libunwind uses its own context type for ARM unwinding. + // Copy the registers from the signal handler's context so we can + // unwind + unw_getcontext(&ctx); + ctx.regs[UNW_ARM_R0] = uctx->uc_mcontext.arm_r0; + ctx.regs[UNW_ARM_R1] = uctx->uc_mcontext.arm_r1; + ctx.regs[UNW_ARM_R2] = uctx->uc_mcontext.arm_r2; + ctx.regs[UNW_ARM_R3] = uctx->uc_mcontext.arm_r3; + ctx.regs[UNW_ARM_R4] = uctx->uc_mcontext.arm_r4; + ctx.regs[UNW_ARM_R5] = uctx->uc_mcontext.arm_r5; + ctx.regs[UNW_ARM_R6] = uctx->uc_mcontext.arm_r6; + ctx.regs[UNW_ARM_R7] = uctx->uc_mcontext.arm_r7; + ctx.regs[UNW_ARM_R8] = uctx->uc_mcontext.arm_r8; + ctx.regs[UNW_ARM_R9] = uctx->uc_mcontext.arm_r9; + ctx.regs[UNW_ARM_R10] = uctx->uc_mcontext.arm_r10; + ctx.regs[UNW_ARM_R11] = uctx->uc_mcontext.arm_fp; + ctx.regs[UNW_ARM_R12] = uctx->uc_mcontext.arm_ip; + ctx.regs[UNW_ARM_R13] = uctx->uc_mcontext.arm_sp; + ctx.regs[UNW_ARM_R14] = uctx->uc_mcontext.arm_lr; + ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_pc; + + // If we have crashed in the PC use the LR instead, as this was + // a bad function dereference + if (reinterpret_cast(error_addr()) == uctx->uc_mcontext.arm_pc) { + ctx.regs[UNW_ARM_R15] = uctx->uc_mcontext.arm_lr - sizeof(unsigned long); + } + _stacktrace[index] = reinterpret_cast(ctx.regs[UNW_ARM_R15]); + ++index; +# elif defined(__APPLE__) && defined(__x86_64__) + unw_getcontext(&ctx); + // OS X's implementation of libunwind uses its own context object + // so we need to convert the passed context to libunwind's format + // (information about the data layout taken from unw_getcontext.s + // in Apple's libunwind source + ctx.data[0] = uctx->uc_mcontext->__ss.__rax; + ctx.data[1] = uctx->uc_mcontext->__ss.__rbx; + ctx.data[2] = uctx->uc_mcontext->__ss.__rcx; + ctx.data[3] = uctx->uc_mcontext->__ss.__rdx; + ctx.data[4] = uctx->uc_mcontext->__ss.__rdi; + ctx.data[5] = uctx->uc_mcontext->__ss.__rsi; + ctx.data[6] = uctx->uc_mcontext->__ss.__rbp; + ctx.data[7] = uctx->uc_mcontext->__ss.__rsp; + ctx.data[8] = uctx->uc_mcontext->__ss.__r8; + ctx.data[9] = uctx->uc_mcontext->__ss.__r9; + ctx.data[10] = uctx->uc_mcontext->__ss.__r10; + ctx.data[11] = uctx->uc_mcontext->__ss.__r11; + ctx.data[12] = uctx->uc_mcontext->__ss.__r12; + ctx.data[13] = uctx->uc_mcontext->__ss.__r13; + ctx.data[14] = uctx->uc_mcontext->__ss.__r14; + ctx.data[15] = uctx->uc_mcontext->__ss.__r15; + ctx.data[16] = uctx->uc_mcontext->__ss.__rip; + + // If the IP is the same as the crash address we have a bad function + // dereference The caller's address is pointed to by %rsp, so we + // dereference that value and set it to be the next frame's IP. + if (uctx->uc_mcontext->__ss.__rip == reinterpret_cast<__uint64_t>(error_addr())) { + ctx.data[16] = *reinterpret_cast<__uint64_t*>(uctx->uc_mcontext->__ss.__rsp); + } + _stacktrace[index] = reinterpret_cast(ctx.data[16]); + ++index; +# elif defined(__APPLE__) + unw_getcontext(&ctx) + // TODO: Convert the ucontext_t to libunwind's unw_context_t like + // we do in 64 bits + if (ctx.uc_mcontext->__ss.__eip == reinterpret_cast(error_addr())) + { + ctx.uc_mcontext->__ss.__eip = ctx.uc_mcontext->__ss.__esp; + } + _stacktrace[index] = reinterpret_cast(ctx.uc_mcontext->__ss.__eip); + ++index; +# endif + } + + unw_cursor_t cursor; + if (context()) { +# if defined(UNW_INIT_SIGNAL_FRAME) + result = unw_init_local2(&cursor, &ctx, UNW_INIT_SIGNAL_FRAME); +# else + result = unw_init_local(&cursor, &ctx); +# endif + } + else { + unw_getcontext(&ctx); + ; + result = unw_init_local(&cursor, &ctx); + } + + if (result != 0) + return 1; + + unw_word_t ip = 0; + + while (index <= depth && unw_step(&cursor) > 0) { + result = unw_get_reg(&cursor, UNW_REG_IP, &ip); + if (result == 0) { + _stacktrace[index] = reinterpret_cast(--ip); + ++index; + } + } + --index; + + _stacktrace.resize(index + 1); + skip_n_firsts(0); + return size(); + } + + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) + { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + _stacktrace[i] = (void*)((uintptr_t)_stacktrace[i]); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } +}; + +#elif defined(BACKWARD_HAS_BACKTRACE) template <> class StackTraceImpl : public StackTraceImplHolder { public: - __attribute__((noinline)) // TODO use some macro - size_t - load_here(size_t depth = 32) + NOINLINE + size_t load_here(size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { + set_context(context); + set_error_addr(error_addr); load_thread_info(); if (depth == 0) { return 0; @@ -810,9 +1129,9 @@ class StackTraceImpl : public StackTraceImplHolder { return size(); } - size_t load_from(void* addr, size_t depth = 32) + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) { - load_here(depth + 8); + load_here(depth + 8, context, error_addr); for (size_t i = 0; i < _stacktrace.size(); ++i) { if (_stacktrace[i] == addr) { @@ -827,31 +1146,129 @@ class StackTraceImpl : public StackTraceImplHolder { } }; -# endif // BACKWARD_HAS_UNWIND +#elif defined(BACKWARD_SYSTEM_WINDOWS) + +template <> +class StackTraceImpl : public StackTraceImplHolder { +public: + // We have to load the machine type from the image info + // So we first initialize the resolver, and it tells us this info + void set_machine_type(DWORD machine_type) { machine_type_ = machine_type; } + void set_context(CONTEXT* ctx) { ctx_ = ctx; } + void set_thread_handle(HANDLE handle) { thd_ = handle; } + + NOINLINE + size_t load_here(size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) + { + set_context(static_cast(context)); + set_error_addr(error_addr); + CONTEXT localCtx; // used when no context is provided + + if (depth == 0) { + return 0; + } + + if (!ctx_) { + ctx_ = &localCtx; + RtlCaptureContext(ctx_); + } + + if (!thd_) { + thd_ = GetCurrentThread(); + } + + HANDLE process = GetCurrentProcess(); + + STACKFRAME64 s; + memset(&s, 0, sizeof(STACKFRAME64)); + + // TODO: 32 bit context capture + s.AddrStack.Mode = AddrModeFlat; + s.AddrFrame.Mode = AddrModeFlat; + s.AddrPC.Mode = AddrModeFlat; +# ifdef _M_X64 + s.AddrPC.Offset = ctx_->Rip; + s.AddrStack.Offset = ctx_->Rsp; + s.AddrFrame.Offset = ctx_->Rbp; +# else + s.AddrPC.Offset = ctx_->Eip; + s.AddrStack.Offset = ctx_->Esp; + s.AddrFrame.Offset = ctx_->Ebp; +# endif + + if (!machine_type_) { +# ifdef _M_X64 + machine_type_ = IMAGE_FILE_MACHINE_AMD64; +# else + machine_type_ = IMAGE_FILE_MACHINE_I386; +# endif + } + + for (;;) { + // NOTE: this only works if PDBs are already loaded! + SetLastError(0); + if (!StackWalk64( + machine_type_, process, thd_, &s, ctx_, NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) + break; + + if (s.AddrReturn.Offset == 0) + break; + + _stacktrace.push_back(reinterpret_cast(s.AddrPC.Offset)); + + if (size() >= depth) + break; + } + + return size(); + } + + size_t load_from(void* addr, size_t depth = 32, void* context = nullptr, void* error_addr = nullptr) + { + load_here(depth + 8, context, error_addr); + + for (size_t i = 0; i < _stacktrace.size(); ++i) { + if (_stacktrace[i] == addr) { + skip_n_firsts(i); + break; + } + } + + _stacktrace.resize(std::min(_stacktrace.size(), skip_n_firsts() + depth)); + return size(); + } + +private: + DWORD machine_type_ = 0; + HANDLE thd_ = 0; + CONTEXT* ctx_ = nullptr; +}; + +#endif class StackTrace : public StackTraceImpl { }; /*************** TRACE RESOLVER ***************/ -template -class TraceResolverImpl; +class TraceResolverImplBase { +public: + virtual ~TraceResolverImplBase() {} -# ifdef BACKWARD_SYSTEM_UNKNOWN + virtual void load_addresses(void* const* addresses, int address_count) + { + (void)addresses; + (void)address_count; + } -template <> -class TraceResolverImpl { -public: template - void load_stacktrace(ST&) + void load_stacktrace(ST& st) { + load_addresses(st.begin(), (int)st.size()); } - ResolvedTrace resolve(ResolvedTrace t) { return t; } -}; -# endif + virtual ResolvedTrace resolve(ResolvedTrace t) { return t; } -class TraceResolverImplBase { protected: std::string demangle(const char* funcname) { return _demangler.demangle(funcname); } @@ -859,59 +1276,59 @@ class TraceResolverImplBase { details::demangler _demangler; }; -# ifdef BACKWARD_SYSTEM_LINUX - -template -class TraceResolverLinuxImpl; +template +class TraceResolverImpl; -# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 +#ifdef BACKWARD_SYSTEM_UNKNOWN template <> -class TraceResolverLinuxImpl : public TraceResolverImplBase { -public: - template - void load_stacktrace(ST& st) - { - using namespace details; - if (st.size() == 0) { - return; - } - _symbols.reset(backtrace_symbols(st.begin(), (int)st.size())); - } +class TraceResolverImpl : public TraceResolverImplBase { +}; + +#endif - ResolvedTrace resolve(ResolvedTrace trace) +#ifdef BACKWARD_SYSTEM_LINUX + +class TraceResolverLinuxBase : public TraceResolverImplBase { +public: + TraceResolverLinuxBase() : argv0_(get_argv0()), exec_path_(read_symlink("/proc/self/exe")) {} + std::string resolve_exec_path(Dl_info& symbol_info) const { - char* filename = _symbols[trace.idx]; - char* funcname = filename; - while (*funcname && *funcname != '(') { - funcname += 1; + // mutates symbol_info.dli_fname to be filename to open and returns filename + // to display + if (symbol_info.dli_fname == argv0_) { + // dladdr returns argv[0] in dli_fname for symbols contained in + // the main executable, which is not a valid path if the + // executable was found by a search of the PATH environment + // variable; In that case, we actually open /proc/self/exe, which + // is always the actual executable (even if it was deleted/replaced!) + // but display the path that /proc/self/exe links to. + // However, this right away reduces probability of successful symbol + // resolution, because libbfd may try to find *.debug files in the + // same dir, in case symbols are stripped. As a result, it may try + // to find a file /proc/self/.debug, which obviously does + // not exist. /proc/self/exe is a last resort. First load attempt + // should go for the original executable file path. + symbol_info.dli_fname = "/proc/self/exe"; + return exec_path_; } - trace.object_filename.assign(filename, funcname); // ok even if funcname is the ending \0 (then we assign entire - // string) - - if (*funcname) { // if it's not end of string (e.g. from last frame ip==0) - funcname += 1; - char* funcname_end = funcname; - while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') { - funcname_end += 1; - } - *funcname_end = '\0'; - trace.object_function = this->demangle(funcname); - trace.source.function = trace.object_function; // we cannot do better. + else { + return symbol_info.dli_fname; } - return trace; } private: - details::handle _symbols; -}; - -# endif // BACKWARD_HAS_BACKTRACE_SYMBOL == 1 + std::string argv0_; + std::string exec_path_; -# if BACKWARD_HAS_BFD == 1 + static std::string get_argv0() + { + std::string argv0; + std::ifstream ifs("/proc/self/cmdline"); + std::getline(ifs, argv0, '\0'); + return argv0; + } -template <> -class TraceResolverLinuxImpl : public TraceResolverImplBase { static std::string read_symlink(std::string const& symlink_path) { std::string path; @@ -930,19 +1347,66 @@ class TraceResolverLinuxImpl : public TraceResolverI break; } } - - return path; + + return path; + } +}; + +template +class TraceResolverLinuxImpl; + +# if BACKWARD_HAS_BACKTRACE_SYMBOL == 1 + +template <> +class TraceResolverLinuxImpl : public TraceResolverLinuxBase { +public: + void load_addresses(void* const* addresses, int address_count) override + { + if (address_count == 0) { + return; + } + _symbols.reset(backtrace_symbols(addresses, address_count)); + } + + ResolvedTrace resolve(ResolvedTrace trace) override + { + char* filename = _symbols[trace.idx]; + char* funcname = filename; + while (*funcname && *funcname != '(') { + funcname += 1; + } + trace.object_filename.assign( + filename, + funcname); // ok even if funcname is the ending + // \0 (then we assign entire string) + + if (*funcname) { // if it's not end of string (e.g. from last frame ip==0) + funcname += 1; + char* funcname_end = funcname; + while (*funcname_end && *funcname_end != ')' && *funcname_end != '+') { + funcname_end += 1; + } + *funcname_end = '\0'; + trace.object_function = this->demangle(funcname); + trace.source.function = trace.object_function; // we cannot do better. + } + return trace; } +private: + details::handle _symbols; +}; + +# endif // BACKWARD_HAS_BACKTRACE_SYMBOL == 1 + +# if BACKWARD_HAS_BFD == 1 + +template <> +class TraceResolverLinuxImpl : public TraceResolverLinuxBase { public: TraceResolverLinuxImpl() : _bfd_loaded(false) {} - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { Dl_info symbol_info; @@ -953,17 +1417,6 @@ class TraceResolverLinuxImpl : public TraceResolverI return trace; // dat broken trace... } - std::string argv0; - { - std::ifstream ifs("/proc/self/cmdline"); - std::getline(ifs, argv0, '\0'); - } - std::string tmp; - if (symbol_info.dli_fname == argv0) { - tmp = read_symlink("/proc/self/exe"); - symbol_info.dli_fname = tmp.c_str(); - } - // Now we get in symbol_info: // .dli_fname: // pathname of the shared object that contains the address. @@ -983,10 +1436,46 @@ class TraceResolverLinuxImpl : public TraceResolverI return trace; } - trace.object_filename = symbol_info.dli_fname; - bfd_fileobject& fobj = load_object_with_bfd(symbol_info.dli_fname); - if (!fobj.handle) { - return trace; // sad, we couldn't load the object :( + trace.object_filename = resolve_exec_path(symbol_info); + bfd_fileobject* fobj; + // Before rushing to resolution need to ensure the executable + // file still can be used. For that compare inode numbers of + // what is stored by the executable's file path, and in the + // dli_fname, which not necessarily equals to the executable. + // It can be a shared library, or /proc/self/exe, and in the + // latter case has drawbacks. See the exec path resolution for + // details. In short - the dli object should be used only as + // the last resort. + // If inode numbers are equal, it is known dli_fname and the + // executable file are the same. This is guaranteed by Linux, + // because if the executable file is changed/deleted, it will + // be done in a new inode. The old file will be preserved in + // /proc/self/exe, and may even have inode 0. The latter can + // happen if the inode was actually reused, and the file was + // kept only in the main memory. + // + struct stat obj_stat; + struct stat dli_stat; + if (stat(trace.object_filename.c_str(), &obj_stat) == 0 && stat(symbol_info.dli_fname, &dli_stat) == 0 && + obj_stat.st_ino == dli_stat.st_ino) { + // The executable file, and the shared object containing the + // address are the same file. Safe to use the original path. + // this is preferable. Libbfd will search for stripped debug + // symbols in the same directory. + fobj = load_object_with_bfd(trace.object_filename); + } + else { + // The original object file was *deleted*! The only hope is + // that the debug symbols are either inside the shared + // object file, or are in the same directory, and this is + // not /proc/self/exe. + fobj = nullptr; + } + if (fobj == nullptr || !fobj->handle) { + fobj = load_object_with_bfd(symbol_info.dli_fname); + if (!fobj->handle) { + return trace; + } } find_sym_result* details_selected; // to be filled. @@ -999,7 +1488,7 @@ class TraceResolverLinuxImpl : public TraceResolverI find_sym_result details_call_site = find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); details_selected = &details_call_site; -# if BACKWARD_HAS_UNWIND == 0 +# if BACKWARD_HAS_UNWIND == 0 // ...this is why we also try to resolve the symbol that is right // before the return address. If we are lucky enough, we will get the // line of the function that was called. But if the code is optimized, @@ -1023,7 +1512,7 @@ class TraceResolverLinuxImpl : public TraceResolverI // thereafter... details_call_site = find_symbol_details(fobj, trace.addr, symbol_info.dli_fbase); } -# endif // BACKWARD_HAS_UNWIND +# endif // BACKWARD_HAS_UNWIND if (details_selected->found) { if (details_selected->filename) { @@ -1052,7 +1541,7 @@ class TraceResolverLinuxImpl : public TraceResolverI // calls along the way up to the initial call site. trace.inliners = backtrace_inliners(fobj, *details_selected); -# if 0 +# if 0 if (trace.inliners.size() == 0) { // Maybe the trace was not inlined... or maybe it was and we // are lacking the debug information. Let's try to make the @@ -1094,7 +1583,7 @@ class TraceResolverLinuxImpl : public TraceResolverI } } } -# endif +# endif } return trace; @@ -1117,7 +1606,7 @@ class TraceResolverLinuxImpl : public TraceResolverI typedef details::hashtable::type fobj_bfd_map_t; fobj_bfd_map_t _fobj_bfd_map; - bfd_fileobject& load_object_with_bfd(const std::string& filename_object) + bfd_fileobject* load_object_with_bfd(const std::string& filename_object) { using namespace details; @@ -1129,11 +1618,11 @@ class TraceResolverLinuxImpl : public TraceResolverI fobj_bfd_map_t::iterator it = _fobj_bfd_map.find(filename_object); if (it != _fobj_bfd_map.end()) { - return it->second; + return &it->second; } // this new object is empty for now. - bfd_fileobject& r = _fobj_bfd_map[filename_object]; + bfd_fileobject* r = &_fobj_bfd_map[filename_object]; // we do the work temporary in this one; bfd_handle_t bfd_handle; @@ -1178,9 +1667,9 @@ class TraceResolverLinuxImpl : public TraceResolverI return r; // damned, that's a stripped file that you got there! } - r.handle = move(bfd_handle); - r.symtab = move(symtab); - r.dynamic_symtab = move(dynamic_symtab); + r->handle = move(bfd_handle); + r->symtab = move(symtab); + r->dynamic_symtab = move(dynamic_symtab); return r; } @@ -1199,15 +1688,15 @@ class TraceResolverLinuxImpl : public TraceResolverI find_sym_result result; }; - find_sym_result find_symbol_details(bfd_fileobject& fobj, void* addr, void* base_addr) + find_sym_result find_symbol_details(bfd_fileobject* fobj, void* addr, void* base_addr) { find_sym_context context; context.self = this; - context.fobj = &fobj; + context.fobj = fobj; context.addr = addr; context.base_addr = base_addr; context.result.found = false; - bfd_map_over_sections(fobj.handle.get(), &find_in_section_trampoline, static_cast(&context)); + bfd_map_over_sections(fobj->handle.get(), &find_in_section_trampoline, static_cast(&context)); return context.result; } @@ -1217,7 +1706,7 @@ class TraceResolverLinuxImpl : public TraceResolverI context->self->find_in_section( reinterpret_cast(context->addr), reinterpret_cast(context->base_addr), - *context->fobj, + context->fobj, section, context->result); } @@ -1225,18 +1714,30 @@ class TraceResolverLinuxImpl : public TraceResolverI void find_in_section( bfd_vma addr, bfd_vma base_addr, - bfd_fileobject& fobj, + bfd_fileobject* fobj, asection* section, find_sym_result& result) { if (result.found) return; - if ((bfd_get_section_flags(fobj.handle.get(), section) & SEC_ALLOC) == 0) +# ifdef bfd_get_section_flags + if ((bfd_get_section_flags(fobj->handle.get(), section) & SEC_ALLOC) == 0) +# else + if ((bfd_section_flags(section) & SEC_ALLOC) == 0) +# endif return; // a debug section is never loaded automatically. - bfd_vma sec_addr = bfd_get_section_vma(fobj.handle.get(), section); +# ifdef bfd_get_section_vma + bfd_vma sec_addr = bfd_get_section_vma(fobj->handle.get(), section); +# else + bfd_vma sec_addr = bfd_section_vma(section); +# endif +# ifdef bfd_get_section_size bfd_size_type size = bfd_get_section_size(section); +# else + bfd_size_type size = bfd_section_size(section); +# endif // are we in the boundaries of the section? if (addr < sec_addr || addr >= sec_addr + size) { @@ -1246,50 +1747,51 @@ class TraceResolverLinuxImpl : public TraceResolverI } } -# if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" -# endif - if (!result.found && fobj.symtab) { +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +# endif + if (!result.found && fobj->symtab) { result.found = bfd_find_nearest_line( - fobj.handle.get(), + fobj->handle.get(), section, - fobj.symtab.get(), + fobj->symtab.get(), addr - sec_addr, &result.filename, &result.funcname, &result.line); } - if (!result.found && fobj.dynamic_symtab) { + if (!result.found && fobj->dynamic_symtab) { result.found = bfd_find_nearest_line( - fobj.handle.get(), + fobj->handle.get(), section, - fobj.dynamic_symtab.get(), + fobj->dynamic_symtab.get(), addr - sec_addr, &result.filename, &result.funcname, &result.line); } -# if defined(__clang__) -# pragma clang diagnostic pop -# endif +# if defined(__clang__) +# pragma clang diagnostic pop +# endif } - ResolvedTrace::source_locs_t backtrace_inliners(bfd_fileobject& fobj, find_sym_result previous_result) + ResolvedTrace::source_locs_t backtrace_inliners(bfd_fileobject* fobj, find_sym_result previous_result) { // This function can be called ONLY after a SUCCESSFUL call to // find_symbol_details. The state is global to the bfd_handle. ResolvedTrace::source_locs_t results; while (previous_result.found) { find_sym_result result; - result.found = bfd_find_inliner_info(fobj.handle.get(), &result.filename, &result.funcname, &result.line); + result.found = bfd_find_inliner_info(fobj->handle.get(), &result.filename, &result.funcname, &result.line); if (result.found) /* and not ( - cstrings_eq(previous_result.filename, result.filename) - and cstrings_eq(previous_result.funcname, result.funcname) - and result.line == previous_result.line - )) */ + cstrings_eq(previous_result.filename, + result.filename) and + cstrings_eq(previous_result.funcname, result.funcname) + and result.line == previous_result.line + )) */ { ResolvedTrace::SourceLoc src_loc; src_loc.line = result.line; @@ -1314,21 +1816,16 @@ class TraceResolverLinuxImpl : public TraceResolverI return strcmp(a, b) == 0; } }; -# endif // BACKWARD_HAS_BFD == 1 +# endif // BACKWARD_HAS_BFD == 1 -# if BACKWARD_HAS_DW == 1 +# if BACKWARD_HAS_DW == 1 template <> -class TraceResolverLinuxImpl : public TraceResolverImplBase { +class TraceResolverLinuxImpl : public TraceResolverLinuxBase { public: TraceResolverLinuxImpl() : _dwfl_handle_initialized(false) {} - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { using namespace details; @@ -1392,7 +1889,7 @@ class TraceResolverLinuxImpl : public TraceResolverIm Dwarf_Addr mod_bias = 0; Dwarf_Die* cudie = dwfl_module_addrdie(mod, trace_addr, &mod_bias); -# if 1 +# if 1 if (!cudie) { // Sadly clang does not generate the section .debug_aranges, thus // dwfl_module_addrdie will fail early. Clang doesn't either set @@ -1412,10 +1909,10 @@ class TraceResolverLinuxImpl : public TraceResolverIm } } } -# endif +# endif //#define BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE -# ifdef BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE +# ifdef BACKWARD_I_DO_NOT_RECOMMEND_TO_ENABLE_THIS_HORRIBLE_PIECE_OF_CODE if (!cudie) { // If it's still not enough, lets dive deeper in the shit, and try // to save the world again: for every compilation unit, we will @@ -1443,7 +1940,7 @@ class TraceResolverLinuxImpl : public TraceResolverIm } } } -# endif +# endif if (!cudie) { return trace; // this time we lost the game :/ @@ -1619,9 +2116,9 @@ class TraceResolverLinuxImpl : public TraceResolverIm static const char* die_call_file(Dwarf_Die* die) { Dwarf_Attribute attr_mem; - Dwarf_Sword file_idx = 0; + Dwarf_Word file_idx = 0; - dwarf_formsdata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); + dwarf_formudata(dwarf_attr(die, DW_AT_call_file, &attr_mem), &file_idx); if (file_idx == 0) { return 0; @@ -1643,43 +2140,16 @@ class TraceResolverLinuxImpl : public TraceResolverIm return dwarf_filesrc(files, file_idx, 0, 0); } }; -# endif // BACKWARD_HAS_DW == 1 +# endif // BACKWARD_HAS_DW == 1 -# if BACKWARD_HAS_DWARF == 1 +# if BACKWARD_HAS_DWARF == 1 template <> -class TraceResolverLinuxImpl : public TraceResolverImplBase { - static std::string read_symlink(std::string const& symlink_path) - { - std::string path; - path.resize(100); - - while (true) { - ssize_t len = ::readlink(symlink_path.c_str(), &*path.begin(), path.size()); - if (len < 0) { - return ""; - } - if ((size_t)len == path.size()) { - path.resize(path.size() * 2); - } - else { - path.resize(len); - break; - } - } - - return path; - } - +class TraceResolverLinuxImpl : public TraceResolverLinuxBase { public: TraceResolverLinuxImpl() : _dwarf_loaded(false) {} - template - void load_stacktrace(ST&) - { - } - - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { // trace.addr is a virtual address in memory pointing to some code. // Let's try to find from which loaded object it comes from. @@ -1687,29 +2157,18 @@ class TraceResolverLinuxImpl : public TraceResolve Dl_info symbol_info; int dladdr_result = 0; -# ifndef __ANDROID__ +# if defined(__GLIBC__) link_map* link_map; // We request the link map so we can get information about offsets dladdr_result = dladdr1(trace.addr, &symbol_info, reinterpret_cast(&link_map), RTLD_DL_LINKMAP); -# else +# else // Android doesn't have dladdr1. Don't use the linker map. dladdr_result = dladdr(trace.addr, &symbol_info); -# endif +# endif if (!dladdr_result) { return trace; // dat broken trace... } - std::string argv0; - { - std::ifstream ifs("/proc/self/cmdline"); - std::getline(ifs, argv0, '\0'); - } - std::string tmp; - if (symbol_info.dli_fname == argv0) { - tmp = read_symlink("/proc/self/exe"); - symbol_info.dli_fname = tmp.c_str(); - } - // Now we get in symbol_info: // .dli_fname: // pathname of the shared object that contains the address. @@ -1736,19 +2195,19 @@ class TraceResolverLinuxImpl : public TraceResolve return trace; } - trace.object_filename = symbol_info.dli_fname; + trace.object_filename = resolve_exec_path(symbol_info); dwarf_fileobject& fobj = load_object_with_dwarf(symbol_info.dli_fname); if (!fobj.dwarf_handle) { return trace; // sad, we couldn't load the object :( } -# ifndef __ANDROID__ +# if defined(__GLIBC__) // Convert the address to a module relative one by looking at // the module's loading address in the link map Dwarf_Addr address = reinterpret_cast(trace.addr) - reinterpret_cast(link_map->l_addr); -# else +# else Dwarf_Addr address = reinterpret_cast(trace.addr); -# endif +# endif if (trace.object_function.empty()) { symbol_cache_t::iterator it = fobj.symbol_cache.lower_bound(address); @@ -1913,7 +2372,7 @@ class TraceResolverLinuxImpl : public TraceResolve dwarf_file_t file_handle; file_handle.reset(open(filename_object.c_str(), O_RDONLY)); - if (file_handle < 0) { + if (file_handle.get() < 0) { return r; } @@ -1950,72 +2409,72 @@ class TraceResolverLinuxImpl : public TraceResolve // We go the preprocessor way to avoid having to create templated // classes or using gelf (which might throw a compiler error if 64 bit // is not supported -# define ELF_GET_DATA(ARCH) \ - Elf_Scn* elf_section = 0; \ - Elf_Data* elf_data = 0; \ - Elf##ARCH##_Shdr* section_header = 0; \ - Elf_Scn* symbol_section = 0; \ - size_t symbol_count = 0; \ - size_t symbol_strings = 0; \ - Elf##ARCH##_Sym* symbol = 0; \ - const char* section_name = 0; \ +# define ELF_GET_DATA(ARCH) \ + Elf_Scn* elf_section = 0; \ + Elf_Data* elf_data = 0; \ + Elf##ARCH##_Shdr* section_header = 0; \ + Elf_Scn* symbol_section = 0; \ + size_t symbol_count = 0; \ + size_t symbol_strings = 0; \ + Elf##ARCH##_Sym* symbol = 0; \ + const char* section_name = 0; \ \ - while ((elf_section = elf_nextscn(elf_handle.get(), elf_section)) != NULL) { \ - section_header = elf##ARCH##_getshdr(elf_section); \ - if (section_header == NULL) { \ - return r; \ - } \ + while ((elf_section = elf_nextscn(elf_handle.get(), elf_section)) != NULL) { \ + section_header = elf##ARCH##_getshdr(elf_section); \ + if (section_header == NULL) { \ + return r; \ + } \ \ - if ((section_name = elf_strptr(elf_handle.get(), shdrstrndx, section_header->sh_name)) == NULL) { \ - return r; \ - } \ + if ((section_name = elf_strptr(elf_handle.get(), shdrstrndx, section_header->sh_name)) == NULL) { \ + return r; \ + } \ \ - if (cstrings_eq(section_name, ".gnu_debuglink")) { \ - elf_data = elf_getdata(elf_section, NULL); \ - if (elf_data && elf_data->d_size > 0) { \ - debuglink = std::string(reinterpret_cast(elf_data->d_buf)); \ - } \ + if (cstrings_eq(section_name, ".gnu_debuglink")) { \ + elf_data = elf_getdata(elf_section, NULL); \ + if (elf_data && elf_data->d_size > 0) { \ + debuglink = std::string(reinterpret_cast(elf_data->d_buf)); \ } \ + } \ + \ + switch (section_header->sh_type) { \ + case SHT_SYMTAB: \ + symbol_section = elf_section; \ + symbol_count = section_header->sh_size / section_header->sh_entsize; \ + symbol_strings = section_header->sh_link; \ + break; \ \ - switch (section_header->sh_type) { \ - case SHT_SYMTAB: \ + /* We use .dynsyms as a last resort, we prefer .symtab */ \ + case SHT_DYNSYM: \ + if (!symbol_section) { \ symbol_section = elf_section; \ symbol_count = section_header->sh_size / section_header->sh_entsize; \ symbol_strings = section_header->sh_link; \ - break; \ - \ - /* We use .dynsyms as a last resort, we prefer .symtab */ \ - case SHT_DYNSYM: \ - if (!symbol_section) { \ - symbol_section = elf_section; \ - symbol_count = section_header->sh_size / section_header->sh_entsize; \ - symbol_strings = section_header->sh_link; \ - } \ - break; \ - } \ + } \ + break; \ } \ + } \ \ - if (symbol_section && symbol_count && symbol_strings) { \ - elf_data = elf_getdata(symbol_section, NULL); \ - symbol = reinterpret_cast(elf_data->d_buf); \ - for (size_t i = 0; i < symbol_count; ++i) { \ - int type = ELF##ARCH##_ST_TYPE(symbol->st_info); \ - if (type == STT_FUNC && symbol->st_value > 0) { \ - r.symbol_cache[symbol->st_value] = \ - std::string(elf_strptr(elf_handle.get(), symbol_strings, symbol->st_name)); \ - } \ - ++symbol; \ + if (symbol_section && symbol_count && symbol_strings) { \ + elf_data = elf_getdata(symbol_section, NULL); \ + symbol = reinterpret_cast(elf_data->d_buf); \ + for (size_t i = 0; i < symbol_count; ++i) { \ + int type = ELF##ARCH##_ST_TYPE(symbol->st_info); \ + if (type == STT_FUNC && symbol->st_value > 0) { \ + r.symbol_cache[symbol->st_value] = \ + std::string(elf_strptr(elf_handle.get(), symbol_strings, symbol->st_name)); \ } \ - } + ++symbol; \ + } \ + } if (e_ident[EI_CLASS] == ELFCLASS32) { ELF_GET_DATA(32) } else if (e_ident[EI_CLASS] == ELFCLASS64) { // libelf might have been built without 64 bit support -# if __LIBELF64 +# if __LIBELF64 ELF_GET_DATA(64) -# endif +# endif } if (!debuglink.empty()) { @@ -2692,7 +3151,7 @@ class TraceResolverLinuxImpl : public TraceResolve trace.object_function = demangler.demangle(linkage); dwarf_dealloc(dwarf, linkage, DW_DLA_STRING); } - dwarf_dealloc(dwarf, name, DW_DLA_ATTR); + dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); } break; @@ -2903,12 +3362,12 @@ class TraceResolverLinuxImpl : public TraceResolve { Dwarf_Attribute attr_mem; Dwarf_Error error = DW_DLE_NE; - Dwarf_Signed file_index; + Dwarf_Unsigned file_index; std::string file; if (dwarf_attr(die, DW_AT_call_file, &attr_mem, &error) == DW_DLV_OK) { - if (dwarf_formsdata(attr_mem, &file_index, &error) != DW_DLV_OK) { + if (dwarf_formudata(attr_mem, &file_index, &error) != DW_DLV_OK) { file_index = 0; } dwarf_dealloc(dwarf, attr_mem, DW_DLA_ATTR); @@ -2920,8 +3379,9 @@ class TraceResolverLinuxImpl : public TraceResolve char** srcfiles = 0; Dwarf_Signed file_count = 0; if (dwarf_srcfiles(cu_die, &srcfiles, &file_count, &error) == DW_DLV_OK) { - if (file_index <= file_count) + if (file_count > 0 && file_index <= static_cast(file_count)) { file = std::string(srcfiles[file_index - 1]); + } // Deallocate all strings! for (int i = 0; i < file_count; ++i) { @@ -3034,15 +3494,15 @@ class TraceResolverLinuxImpl : public TraceResolve return NULL; } }; -# endif // BACKWARD_HAS_DWARF == 1 +# endif // BACKWARD_HAS_DWARF == 1 template <> class TraceResolverImpl : public TraceResolverLinuxImpl { }; -# endif // BACKWARD_SYSTEM_LINUX +#endif // BACKWARD_SYSTEM_LINUX -# ifdef BACKWARD_SYSTEM_DARWIN +#ifdef BACKWARD_SYSTEM_DARWIN template class TraceResolverDarwinImpl; @@ -3050,17 +3510,15 @@ class TraceResolverDarwinImpl; template <> class TraceResolverDarwinImpl : public TraceResolverImplBase { public: - template - void load_stacktrace(ST& st) + void load_addresses(void* const* addresses, int address_count) override { - using namespace details; - if (st.size() == 0) { + if (address_count == 0) { return; } - _symbols.reset(backtrace_symbols(st.begin(), st.size())); + _symbols.reset(backtrace_symbols(addresses, address_count)); } - ResolvedTrace resolve(ResolvedTrace trace) + ResolvedTrace resolve(ResolvedTrace trace) override { // parse: // + @@ -3105,8 +3563,8 @@ class TraceResolverDarwinImpl : public Tra filename_end = filename + strlen(filename); funcname = filename_end; } - trace.object_filename.assign(filename, filename_end); // ok even if filename_end is the ending \0 (then we - // assign entire string) + trace.object_filename.assign(filename, filename_end); // ok even if filename_end is the ending \0 + // (then we assign entire string) if (*funcname) { // if it's not end of string *funcname_end = '\0'; @@ -3127,7 +3585,136 @@ template <> class TraceResolverImpl : public TraceResolverDarwinImpl { }; -# endif // BACKWARD_SYSTEM_DARWIN +#endif // BACKWARD_SYSTEM_DARWIN + +#ifdef BACKWARD_SYSTEM_WINDOWS + +// Load all symbol info +// Based on: +// https://stackoverflow.com/questions/6205981/windows-c-stack-trace-from-a-running-app/28276227#28276227 + +struct module_data { + std::string image_name; + std::string module_name; + void* base_address; + DWORD load_size; +}; + +class get_mod_info { + HANDLE process; + static const int buffer_length = 4096; + +public: + get_mod_info(HANDLE h) : process(h) {} + + module_data operator()(HMODULE module) + { + module_data ret; + char temp[buffer_length]; + MODULEINFO mi; + + GetModuleInformation(process, module, &mi, sizeof(mi)); + ret.base_address = mi.lpBaseOfDll; + ret.load_size = mi.SizeOfImage; + + GetModuleFileNameExA(process, module, temp, sizeof(temp)); + ret.image_name = temp; + GetModuleBaseNameA(process, module, temp, sizeof(temp)); + ret.module_name = temp; + std::vector img(ret.image_name.begin(), ret.image_name.end()); + std::vector mod(ret.module_name.begin(), ret.module_name.end()); + SymLoadModule64(process, 0, &img[0], &mod[0], (DWORD64)ret.base_address, ret.load_size); + return ret; + } +}; + +template <> +class TraceResolverImpl : public TraceResolverImplBase { +public: + TraceResolverImpl() + { + + HANDLE process = GetCurrentProcess(); + + std::vector modules; + DWORD cbNeeded; + std::vector module_handles(1); + SymInitialize(process, NULL, false); + DWORD symOptions = SymGetOptions(); + symOptions |= SYMOPT_LOAD_LINES | SYMOPT_UNDNAME; + SymSetOptions(symOptions); + EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); + module_handles.resize(cbNeeded / sizeof(HMODULE)); + EnumProcessModules(process, &module_handles[0], module_handles.size() * sizeof(HMODULE), &cbNeeded); + std::transform( + module_handles.begin(), module_handles.end(), std::back_inserter(modules), get_mod_info(process)); + void* base = modules[0].base_address; + IMAGE_NT_HEADERS* h = ImageNtHeader(base); + image_type = h->FileHeader.Machine; + } + + static const int max_sym_len = 255; + struct symbol_t { + SYMBOL_INFO sym; + char buffer[max_sym_len]; + } sym; + + DWORD64 displacement; + + ResolvedTrace resolve(ResolvedTrace t) override + { + HANDLE process = GetCurrentProcess(); + + char name[256]; + + memset(&sym, 0, sizeof(sym)); + sym.sym.SizeOfStruct = sizeof(SYMBOL_INFO); + sym.sym.MaxNameLen = max_sym_len; + + if (!SymFromAddr(process, (ULONG64)t.addr, &displacement, &sym.sym)) { + // TODO: error handling everywhere + char* lpMsgBuf; + DWORD dw = GetLastError(); + + if (FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + dw, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (char*)&lpMsgBuf, + 0, + NULL)) { + std::fprintf(stderr, "%s\n", lpMsgBuf); + LocalFree(lpMsgBuf); + } + + // abort(); + } + UnDecorateSymbolName(sym.sym.Name, (PSTR)name, 256, UNDNAME_COMPLETE); + + DWORD offset = 0; + IMAGEHLP_LINE line; + if (SymGetLineFromAddr(process, (ULONG64)t.addr, &offset, &line)) { + t.object_filename = line.FileName; + t.source.filename = line.FileName; + t.source.line = line.LineNumber; + t.source.col = offset; + } + + t.source.function = name; + t.object_filename = ""; + t.object_function = name; + + return t; + } + + DWORD machine_type() const { return image_type; } + +private: + DWORD image_type; +}; + +#endif class TraceResolver : public TraceResolverImpl { }; @@ -3139,7 +3726,24 @@ class SourceFile { typedef std::vector> lines_t; SourceFile() {} - SourceFile(const std::string& path) : _file(new std::ifstream(path.c_str())) {} + SourceFile(const std::string& path) + { + // 1. If BACKWARD_CXX_SOURCE_PREFIXES is set then assume it contains + // a colon-separated list of path prefixes. Try prepending each + // to the given path until a valid file is found. + const std::vector& prefixes = get_paths_from_env_variable(); + for (size_t i = 0; i < prefixes.size(); ++i) { + // Double slashes (//) should not be a problem. + std::string new_path = prefixes[i] + '/' + path; + _file.reset(new std::ifstream(new_path.c_str())); + if (is_open()) + break; + } + // 2. If no valid file found then fallback to opening the path as-is. + if (!_file || !is_open()) { + _file.reset(new std::ifstream(path.c_str())); + } + } bool is_open() const { return _file->is_open(); } lines_t& get_lines(unsigned line_start, unsigned line_count, lines_t& lines) @@ -3213,14 +3817,14 @@ class SourceFile { void swap(SourceFile& b) { _file.swap(b._file); } -# ifdef BACKWARD_ATLEAST_CXX11 +#ifdef BACKWARD_ATLEAST_CXX11 SourceFile(SourceFile&& from) : _file(nullptr) { swap(from); } SourceFile& operator=(SourceFile&& from) { swap(from); return *this; } -# else +#else explicit SourceFile(const SourceFile& from) { // some sort of poor man's move semantic. @@ -3232,15 +3836,31 @@ class SourceFile { swap(const_cast(from)); return *this; } -# endif +#endif private: details::handle> _file; -# ifdef BACKWARD_ATLEAST_CXX11 + std::vector get_paths_from_env_variable_impl() + { + std::vector paths; + const char* prefixes_str = std::getenv("BACKWARD_CXX_SOURCE_PREFIXES"); + if (prefixes_str && prefixes_str[0]) { + paths = details::split_source_prefixes(prefixes_str); + } + return paths; + } + + const std::vector& get_paths_from_env_variable() + { + static std::vector paths = get_paths_from_env_variable_impl(); + return paths; + } + +#ifdef BACKWARD_ATLEAST_CXX11 SourceFile(const SourceFile&) = delete; SourceFile& operator=(const SourceFile&) = delete; -# endif +#endif }; class SnippetFactory { @@ -3316,7 +3936,7 @@ class cfile_streambuf : public std::streambuf { int_type underflow() override { return traits_type::eof(); } int_type overflow(int_type ch) override { - if (traits_type::not_eof(ch) && fwrite(&ch, sizeof ch, 1, sink) == 1) { + if (traits_type::not_eof(ch) && fputc(ch, sink) != EOF) { return ch; } return traits_type::eof(); @@ -3327,22 +3947,22 @@ class cfile_streambuf : public std::streambuf { return static_cast(fwrite(s, sizeof *s, static_cast(count), sink)); } -# ifdef BACKWARD_ATLEAST_CXX11 +#ifdef BACKWARD_ATLEAST_CXX11 public: cfile_streambuf(const cfile_streambuf&) = delete; cfile_streambuf& operator=(const cfile_streambuf&) = delete; -# else +#else private: cfile_streambuf(const cfile_streambuf&); cfile_streambuf& operator=(const cfile_streambuf&); -# endif +#endif private: FILE* sink; std::vector buffer; }; -# ifdef BACKWARD_SYSTEM_LINUX +#ifdef BACKWARD_SYSTEM_LINUX namespace Color { enum type { yellow = 33, purple = 35, reset = 39 }; @@ -3385,7 +4005,7 @@ class Colorize { bool _enabled; }; -# else // ndef BACKWARD_SYSTEM_LINUX +#else // ndef BACKWARD_SYSTEM_LINUX namespace Color { enum type { yellow = 0, purple = 0, reset = 0 }; @@ -3399,7 +4019,7 @@ class Colorize { void set_color(Color::type) {} }; -# endif // BACKWARD_SYSTEM_LINUX +#endif // BACKWARD_SYSTEM_LINUX class Printer { public: @@ -3456,6 +4076,8 @@ class Printer { return os; } + TraceResolver const& resolver() const { return _resolver; } + private: TraceResolver _resolver; SnippetFactory _snippets; @@ -3569,7 +4191,7 @@ class Printer { /*************** SIGNALS HANDLING ***************/ -# if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) +#if defined(BACKWARD_SYSTEM_LINUX) || defined(BACKWARD_SYSTEM_DARWIN) class SignalHandling { public: @@ -3588,9 +4210,9 @@ class SignalHandling { SIGTRAP, // Trace/breakpoint trap SIGXCPU, // CPU time limit exceeded (4.2BSD) SIGXFSZ, // File size limit exceeded (4.2BSD) -# if defined(BACKWARD_SYSTEM_DARWIN) +# if defined(BACKWARD_SYSTEM_DARWIN) SIGEMT, // emulation instruction executed -# endif +# endif }; return std::vector(posix_signals, posix_signals + sizeof posix_signals / sizeof posix_signals[0]); } @@ -3620,14 +4242,14 @@ class SignalHandling { action.sa_flags = static_cast(SA_SIGINFO | SA_ONSTACK | SA_NODEFER | SA_RESETHAND); sigfillset(&action.sa_mask); sigdelset(&action.sa_mask, posix_signals[i]); -# if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" -# endif +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +# endif action.sa_sigaction = &sig_handler; -# if defined(__clang__) -# pragma clang diagnostic pop -# endif +# if defined(__clang__) +# pragma clang diagnostic pop +# endif int r = sigaction(posix_signals[i], &action, nullptr); if (r < 0) @@ -3645,50 +4267,58 @@ class SignalHandling { StackTrace st; void* error_addr = nullptr; -# ifdef REG_RIP // x86_64 +# ifdef REG_RIP // x86_64 error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_RIP]); -# elif defined(REG_EIP) // x86_32 +# elif defined(REG_EIP) // x86_32 error_addr = reinterpret_cast(uctx->uc_mcontext.gregs[REG_EIP]); -# elif defined(__arm__) +# elif defined(__arm__) error_addr = reinterpret_cast(uctx->uc_mcontext.arm_pc); -# elif defined(__aarch64__) +# elif defined(__aarch64__) +# if defined(__APPLE__) + error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__pc); +# else error_addr = reinterpret_cast(uctx->uc_mcontext.pc); -# elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) +# endif +# elif defined(__mips__) + error_addr = reinterpret_cast(reinterpret_cast(&uctx->uc_mcontext)->sc_pc); +# elif defined(__ppc__) || defined(__powerpc) || defined(__powerpc__) || defined(__POWERPC__) error_addr = reinterpret_cast(uctx->uc_mcontext.regs->nip); -# elif defined(__s390x__) +# elif defined(__riscv) + error_addr = reinterpret_cast(uctx->uc_mcontext.__gregs[REG_PC]); +# elif defined(__s390x__) error_addr = reinterpret_cast(uctx->uc_mcontext.psw.addr); -# elif defined(__APPLE__) && defined(__x86_64__) +# elif defined(__APPLE__) && defined(__x86_64__) error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__rip); -# elif defined(__APPLE__) +# elif defined(__APPLE__) error_addr = reinterpret_cast(uctx->uc_mcontext->__ss.__eip); -# else -# warning ":/ sorry, ain't know no nothing none not of your architecture!" -# endif +# else +# warning ":/ sorry, ain't know no nothing none not of your architecture!" +# endif if (error_addr) { - st.load_from(error_addr, 32); + st.load_from(error_addr, 32, reinterpret_cast(uctx), info->si_addr); } else { - st.load_here(32); + st.load_here(32, reinterpret_cast(uctx), info->si_addr); } Printer printer; printer.address = true; printer.print(st, stderr); -# if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L +# if _XOPEN_SOURCE >= 700 || _POSIX_C_SOURCE >= 200809L psiginfo(info, nullptr); -# else +# else (void)info; -# endif +# endif } private: details::handle _stack_content; bool _loaded; -# ifdef __GNUC__ +# ifdef __GNUC__ __attribute__((noreturn)) -# endif +# endif static void sig_handler(int signo, siginfo_t* info, void* _ctx) { @@ -3703,9 +4333,204 @@ class SignalHandling { } }; -# endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN +#endif // BACKWARD_SYSTEM_LINUX || BACKWARD_SYSTEM_DARWIN + +#ifdef BACKWARD_SYSTEM_WINDOWS + +class SignalHandling { +public: + SignalHandling(const std::vector& = std::vector()) + : reporter_thread_([]() { + /* We handle crashes in a utility thread: + backward structures and some Windows functions called here + need stack space, which we do not have when we encounter a + stack overflow. + To support reporting stack traces during a stack overflow, + we create a utility thread at startup, which waits until a + crash happens or the program exits normally. */ + + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::running; }); + } + if (crashed() == crash_status::crashed) { + handle_stacktrace(skip_recs()); + } + { + std::unique_lock lk(mtx()); + crashed() = crash_status::ending; + } + cv().notify_one(); + }) + { + SetUnhandledExceptionFilter(crash_handler); + + signal(SIGABRT, signal_handler); + _set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT); + + std::set_terminate(&terminator); +# ifndef BACKWARD_ATLEAST_CXX17 + std::set_unexpected(&terminator); +# endif + _set_purecall_handler(&terminator); + _set_invalid_parameter_handler(&invalid_parameter_handler); + } + bool loaded() const { return true; } + + ~SignalHandling() + { + { + std::unique_lock lk(mtx()); + crashed() = crash_status::normal_exit; + } + + cv().notify_one(); + + reporter_thread_.join(); + } + +private: + static CONTEXT* ctx() + { + static CONTEXT data; + return &data; + } + + enum class crash_status { running, crashed, normal_exit, ending }; + + static crash_status& crashed() + { + static crash_status data; + return data; + } + + static std::mutex& mtx() + { + static std::mutex data; + return data; + } + + static std::condition_variable& cv() + { + static std::condition_variable data; + return data; + } + + static HANDLE& thread_handle() + { + static HANDLE handle; + return handle; + } + + std::thread reporter_thread_; + + // TODO: how not to hardcode these? + static const constexpr int signal_skip_recs = +# ifdef __clang__ + // With clang, RtlCaptureContext also captures the stack frame of the + // current function Below that, there ar 3 internal Windows functions + 4 +# else + // With MSVC cl, RtlCaptureContext misses the stack frame of the current + // function The first entries during StackWalk are the 3 internal Windows + // functions + 3 +# endif + ; + + static int& skip_recs() + { + static int data; + return data; + } + + static inline void terminator() + { + crash_handler(signal_skip_recs); + abort(); + } + + static inline void signal_handler(int) + { + crash_handler(signal_skip_recs); + abort(); + } + + static inline void __cdecl invalid_parameter_handler( + const wchar_t*, + const wchar_t*, + const wchar_t*, + unsigned int, + uintptr_t) + { + crash_handler(signal_skip_recs); + abort(); + } + + NOINLINE static LONG WINAPI crash_handler(EXCEPTION_POINTERS* info) + { + // The exception info supplies a trace from exactly where the issue was, + // no need to skip records + crash_handler(0, info->ContextRecord); + return EXCEPTION_CONTINUE_SEARCH; + } + + NOINLINE static void crash_handler(int skip, CONTEXT* ct = nullptr) + { + + if (ct == nullptr) { + RtlCaptureContext(ctx()); + } + else { + memcpy(ctx(), ct, sizeof(CONTEXT)); + } + DuplicateHandle( + GetCurrentProcess(), + GetCurrentThread(), + GetCurrentProcess(), + &thread_handle(), + 0, + FALSE, + DUPLICATE_SAME_ACCESS); + + skip_recs() = skip; + + { + std::unique_lock lk(mtx()); + crashed() = crash_status::crashed; + } + + cv().notify_one(); + + { + std::unique_lock lk(mtx()); + cv().wait(lk, [] { return crashed() != crash_status::crashed; }); + } + } + + static void handle_stacktrace(int skip_frames = 0) + { + // printer creates the TraceResolver, which can supply us a machine type + // for stack walking. Without this, StackTrace can only guess using some + // macros. + // StackTrace also requires that the PDBs are already loaded, which is done + // in the constructor of TraceResolver + Printer printer; + + StackTrace st; + st.set_machine_type(printer.resolver().machine_type()); + st.set_thread_handle(thread_handle()); + st.load_here(32 + skip_frames, ctx()); + st.skip_n_firsts(skip_frames); + + printer.address = true; + printer.print(st, std::cerr); + } +}; + +#endif // BACKWARD_SYSTEM_WINDOWS -# ifdef BACKWARD_SYSTEM_UNKNOWN +#ifdef BACKWARD_SYSTEM_UNKNOWN class SignalHandling { public: @@ -3714,8 +4539,10 @@ class SignalHandling { bool loaded() { return false; } }; -# endif // BACKWARD_SYSTEM_UNKNOWN +#endif // BACKWARD_SYSTEM_UNKNOWN } // namespace backward +#endif /* no_backtrace */ + #endif /* H_GUARD */ diff --git a/src/logging.cpp b/src/logging.cpp index b01a4a2..e68aafa 100644 --- a/src/logging.cpp +++ b/src/logging.cpp @@ -13,6 +13,7 @@ * permissions and limitations under the License. */ #include "aws/logging/logging.h" +#include #include #include @@ -46,9 +47,10 @@ void log(verbosity v, char const* tag, char const* msg, va_list args) va_end(copy); return; } - char buf[512]; - char* out = buf; - if (sz >= static_cast(sizeof(buf))) { + constexpr int max_stack_buffer_size = 512; + std::array buf; + char* out = buf.data(); + if (sz >= max_stack_buffer_size) { out = new char[sz]; } @@ -60,7 +62,7 @@ void log(verbosity v, char const* tag, char const* msg, va_list args) // stdout is not line-buffered when redirected (for example to a file or to another process) so we must flush it // manually. fflush(stdout); - if (out != buf) { + if (out != buf.data()) { delete[] out; } } diff --git a/src/runtime.cpp b/src/runtime.cpp index 0af68b2..1013a19 100644 --- a/src/runtime.cpp +++ b/src/runtime.cpp @@ -25,20 +25,22 @@ #include #include #include +#include #include // for strtoul +#include #define AWS_LAMBDA_RUNTIME_API __attribute__((visibility("default"))) namespace aws { namespace lambda_runtime { -static char const LOG_TAG[] = "LAMBDA_RUNTIME"; -static char const REQUEST_ID_HEADER[] = "lambda-runtime-aws-request-id"; -static char const TRACE_ID_HEADER[] = "lambda-runtime-trace-id"; -static char const CLIENT_CONTEXT_HEADER[] = "lambda-runtime-client-context"; -static char const COGNITO_IDENTITY_HEADER[] = "lambda-runtime-cognito-identity"; -static char const DEADLINE_MS_HEADER[] = "lambda-runtime-deadline-ms"; -static char const FUNCTION_ARN_HEADER[] = "lambda-runtime-invoked-function-arn"; +static constexpr auto LOG_TAG = "LAMBDA_RUNTIME"; +static constexpr auto REQUEST_ID_HEADER = "lambda-runtime-aws-request-id"; +static constexpr auto TRACE_ID_HEADER = "lambda-runtime-trace-id"; +static constexpr auto CLIENT_CONTEXT_HEADER = "lambda-runtime-client-context"; +static constexpr auto COGNITO_IDENTITY_HEADER = "lambda-runtime-cognito-identity"; +static constexpr auto DEADLINE_MS_HEADER = "lambda-runtime-deadline-ms"; +static constexpr auto FUNCTION_ARN_HEADER = "lambda-runtime-invoked-function-arn"; enum Endpoints { INIT, @@ -48,8 +50,10 @@ enum Endpoints { static bool is_success(aws::http::response_code httpcode) { + constexpr auto http_first_success_error_code = 200; + constexpr auto http_last_success_error_code = 299; auto const code = static_cast(httpcode); - return code >= 200 && code <= 299; + return code >= http_first_success_error_code && code <= http_last_success_error_code; } static size_t write_data(char* ptr, size_t size, size_t nmemb, void* userdata) @@ -58,7 +62,7 @@ static size_t write_data(char* ptr, size_t size, size_t nmemb, void* userdata) return 0; } - auto const resp = static_cast(userdata); + auto* const resp = static_cast(userdata); assert(size == 1); (void)size; // avoid warning in release builds assert(resp); @@ -66,21 +70,36 @@ static size_t write_data(char* ptr, size_t size, size_t nmemb, void* userdata) return nmemb; } -static inline bool IsSpace(int ch) +// std::isspace has a few edge cases that would trigger UB. In particular, the documentation says: +// "The behavior is undefined if the value of the input is not representable as unsigned char and is not equal to EOF." +// So, this function does the simple obvious thing instead. +static inline bool is_whitespace(int ch) { - if (ch < -1 || ch > 255) { - return false; + constexpr int space = 0x20; // space (0x20, ' ') + constexpr int form_feed = 0x0c; // form feed (0x0c, '\f') + constexpr int line_feed = 0x0a; // line feed (0x0a, '\n') + constexpr int carriage_return = 0x0d; // carriage return (0x0d, '\r') + constexpr int horizontal_tab = 0x09; // horizontal tab (0x09, '\t') + constexpr int vertical_tab = 0x0b; // vertical tab (0x0b, '\v') + switch (ch) { + case space: + case form_feed: + case line_feed: + case carriage_return: + case horizontal_tab: + case vertical_tab: + return true; + default: + return false; } - - return ::isspace(ch) != 0; } static inline std::string trim(std::string s) { // trim right - s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !IsSpace(ch); }).base(), s.end()); + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !is_whitespace(ch); }).base(), s.end()); // trim left - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !IsSpace(ch); })); + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !is_whitespace(ch); })); return s; } @@ -92,30 +111,22 @@ static size_t write_header(char* ptr, size_t size, size_t nmemb, void* userdata) logging::log_debug(LOG_TAG, "received header: %s", std::string(ptr, nmemb).c_str()); - auto const resp = static_cast(userdata); + auto* const resp = static_cast(userdata); assert(resp); for (size_t i = 0; i < nmemb; i++) { if (ptr[i] != ':') { continue; } - std::string key{ptr, i}; - std::string value{ptr + i + 1, nmemb - i - 1}; - resp->add_header(trim(key), trim(value)); + resp->add_header(trim({ptr, i}), trim({ptr + i + 1, nmemb - i - 1})); break; } return size * nmemb; } -static std::string const& get_user_agent_header() -{ - static std::string user_agent = std::string("User-Agent: AWS_Lambda_Cpp/") + get_version(); - return user_agent; -} - static size_t read_data(char* buffer, size_t size, size_t nitems, void* userdata) { auto const limit = size * nitems; - auto ctx = static_cast*>(userdata); + auto* ctx = static_cast*>(userdata); assert(ctx); auto const unread = ctx->first.length() - ctx->second; if (0 == unread) { @@ -123,12 +134,16 @@ static size_t read_data(char* buffer, size_t size, size_t nitems, void* userdata } if (unread <= limit) { - std::copy_n(ctx->first.begin() + ctx->second, unread, buffer); + auto from = ctx->first.begin(); + std::advance(from, ctx->second); + std::copy_n(from, unread, buffer); ctx->second += unread; return unread; } - std::copy_n(ctx->first.begin() + ctx->second, limit, buffer); + auto from = ctx->first.begin(); + std::advance(from, ctx->second); + std::copy_n(from, limit, buffer); ctx->second += limit; return limit; } @@ -145,49 +160,14 @@ static int rt_curl_debug_callback(CURL* handle, curl_infotype type, char* data, } #endif -struct no_result { -}; +runtime::runtime(std::string const& endpoint) : runtime(endpoint, "AWS_Lambda_Cpp/" + std::string(get_version())) {} -class runtime { -public: - using next_outcome = aws::lambda_runtime::outcome; - using post_outcome = aws::lambda_runtime::outcome; - - runtime(std::string const& endpoint); - ~runtime(); - - /** - * Ask lambda for an invocation. - */ - next_outcome get_next(); - - /** - * Tells lambda that the function has succeeded. - */ - post_outcome post_success(std::string const& request_id, invocation_response const& handler_response); - - /** - * Tells lambda that the function has failed. - */ - post_outcome post_failure(std::string const& request_id, invocation_response const& handler_response); - -private: - void set_curl_next_options(); - void set_curl_post_result_options(); - post_outcome do_post( - std::string const& url, - std::string const& request_id, - invocation_response const& handler_response); - -private: - std::array const m_endpoints; - CURL* const m_curl_handle; -}; - -runtime::runtime(std::string const& endpoint) - : m_endpoints{{endpoint + "/2018-06-01/runtime/init/error", - endpoint + "/2018-06-01/runtime/invocation/next", - endpoint + "/2018-06-01/runtime/invocation/"}}, +runtime::runtime(std::string const& endpoint, std::string const& user_agent) + : m_user_agent_header("User-Agent: " + user_agent), + m_endpoints{ + {endpoint + "/2018-06-01/runtime/init/error", + endpoint + "/2018-06-01/runtime/invocation/next", + endpoint + "/2018-06-01/runtime/invocation/"}}, m_curl_handle(curl_easy_init()) { if (!m_curl_handle) { @@ -217,6 +197,8 @@ void runtime::set_curl_next_options() curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + #ifndef NDEBUG curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); @@ -237,6 +219,8 @@ void runtime::set_curl_post_result_options() curl_easy_setopt(m_curl_handle, CURLOPT_WRITEFUNCTION, write_data); curl_easy_setopt(m_curl_handle, CURLOPT_HEADERFUNCTION, write_header); + curl_easy_setopt(m_curl_handle, CURLOPT_PROXY, ""); + #ifndef NDEBUG curl_easy_setopt(m_curl_handle, CURLOPT_VERBOSE, 1); curl_easy_setopt(m_curl_handle, CURLOPT_DEBUGFUNCTION, rt_curl_debug_callback); @@ -251,7 +235,8 @@ runtime::next_outcome runtime::get_next() curl_easy_setopt(m_curl_handle, CURLOPT_HEADERDATA, &resp); curl_slist* headers = nullptr; - headers = curl_slist_append(headers, get_user_agent_header().c_str()); + headers = curl_slist_append(headers, m_user_agent_header.c_str()); + curl_easy_setopt(m_curl_handle, CURLOPT_HTTPHEADER, headers); logging::log_debug(LOG_TAG, "Making request to %s", m_endpoints[Endpoints::NEXT].c_str()); CURLcode curl_code = curl_easy_perform(m_curl_handle); @@ -260,7 +245,10 @@ runtime::next_outcome runtime::get_next() if (curl_code != CURLE_OK) { logging::log_debug(LOG_TAG, "CURL returned error code %d - %s", curl_code, curl_easy_strerror(curl_code)); - logging::log_error(LOG_TAG, "Failed to get next invocation. No Response from endpoint"); + logging::log_error( + LOG_TAG, + "Failed to get next invocation. No Response from endpoint \"%s\"", + m_endpoints[Endpoints::NEXT].c_str()); return aws::http::response_code::REQUEST_NOT_MADE; } @@ -284,43 +272,50 @@ runtime::next_outcome runtime::get_next() return resp.get_response_code(); } - if (!resp.has_header(REQUEST_ID_HEADER)) { + auto out = resp.get_header(REQUEST_ID_HEADER); + if (!out.is_success()) { logging::log_error(LOG_TAG, "Failed to find header %s in response", REQUEST_ID_HEADER); return aws::http::response_code::REQUEST_NOT_MADE; } invocation_request req; req.payload = resp.get_body(); - req.request_id = resp.get_header(REQUEST_ID_HEADER); + req.request_id = std::move(out).get_result(); - if (resp.has_header(TRACE_ID_HEADER)) { - req.xray_trace_id = resp.get_header(TRACE_ID_HEADER); + out = resp.get_header(TRACE_ID_HEADER); + if (out.is_success()) { + req.xray_trace_id = std::move(out).get_result(); } - if (resp.has_header(CLIENT_CONTEXT_HEADER)) { - req.client_context = resp.get_header(CLIENT_CONTEXT_HEADER); + out = resp.get_header(CLIENT_CONTEXT_HEADER); + if (out.is_success()) { + req.client_context = std::move(out).get_result(); } - if (resp.has_header(COGNITO_IDENTITY_HEADER)) { - req.cognito_identity = resp.get_header(COGNITO_IDENTITY_HEADER); + out = resp.get_header(COGNITO_IDENTITY_HEADER); + if (out.is_success()) { + req.cognito_identity = std::move(out).get_result(); } - if (resp.has_header(FUNCTION_ARN_HEADER)) { - req.function_arn = resp.get_header(FUNCTION_ARN_HEADER); + out = resp.get_header(FUNCTION_ARN_HEADER); + if (out.is_success()) { + req.function_arn = std::move(out).get_result(); } - if (resp.has_header(DEADLINE_MS_HEADER)) { - auto const& deadline_string = resp.get_header(DEADLINE_MS_HEADER); - unsigned long ms = strtoul(deadline_string.c_str(), nullptr, 10); + out = resp.get_header(DEADLINE_MS_HEADER); + if (out.is_success()) { + auto const& deadline_string = std::move(out).get_result(); + constexpr int base = 10; + unsigned long ms = strtoul(deadline_string.c_str(), nullptr, base); assert(ms > 0); assert(ms < ULONG_MAX); req.deadline += std::chrono::milliseconds(ms); logging::log_info( LOG_TAG, - "Received payload: %s\nTime remaining: %ld", + "Received payload: %s\nTime remaining: %" PRId64, req.payload.c_str(), - req.get_time_remaining().count()); + static_cast(req.get_time_remaining().count())); } - return next_outcome(req); + return {req}; } runtime::post_outcome runtime::post_success(std::string const& request_id, invocation_response const& handler_response) @@ -354,7 +349,7 @@ runtime::post_outcome runtime::do_post( headers = curl_slist_append(headers, "Expect:"); headers = curl_slist_append(headers, "transfer-encoding:"); - headers = curl_slist_append(headers, get_user_agent_header().c_str()); + headers = curl_slist_append(headers, m_user_agent_header.c_str()); auto const& payload = handler_response.get_payload(); logging::log_debug( LOG_TAG, "calculating content length... %s", ("content-length: " + std::to_string(payload.length())).c_str()); @@ -413,9 +408,9 @@ static bool handle_post_outcome(runtime::post_outcome const& o, std::string cons AWS_LAMBDA_RUNTIME_API void run_handler(std::function const& handler) { - logging::log_info(LOG_TAG, "Initializing the C++ Lambda Runtime."); + logging::log_info(LOG_TAG, "Initializing the C++ Lambda Runtime version %s", aws::lambda_runtime::get_version()); std::string endpoint("http://"); - if (auto ep = std::getenv("AWS_LAMBDA_RUNTIME_API")) { + if (auto* ep = std::getenv("AWS_LAMBDA_RUNTIME_API")) { assert(ep); logging::log_debug(LOG_TAG, "LAMBDA_SERVER_ADDRESS defined in environment as: %s", ep); endpoint += ep; @@ -427,7 +422,7 @@ void run_handler(std::function c size_t const max_retries = 3; while (retries < max_retries) { - const auto next_outcome = rt.get_next(); + auto next_outcome = rt.get_next(); if (!next_outcome.is_success()) { if (next_outcome.get_failure() == aws::http::response_code::REQUEST_NOT_MADE) { ++retries; @@ -444,7 +439,7 @@ void run_handler(std::function c retries = 0; - auto const& req = next_outcome.get_result(); + auto const req = std::move(next_outcome).get_result(); logging::log_info(LOG_TAG, "Invoking user handler"); invocation_response res = handler(req); logging::log_info(LOG_TAG, "Invoking user handler completed."); @@ -471,10 +466,11 @@ void run_handler(std::function c static std::string json_escape(std::string const& in) { + constexpr char last_non_printable_character = 31; std::string out; out.reserve(in.length()); // most strings will end up identical for (char ch : in) { - if (ch > 31 && ch != '\"' && ch != '\\') { + if (ch > last_non_printable_character && ch != '\"' && ch != '\\') { out.append(1, ch); } else { @@ -503,9 +499,10 @@ static std::string json_escape(std::string const& in) break; default: // escape and print as unicode codepoint - char buf[6]; // 4 hex + letter 'u' + \0 - sprintf(buf, "u%04x", ch); - out.append(buf, 5); // add only five, discarding the null terminator. + constexpr int printed_unicode_length = 6; // 4 hex + letter 'u' + \0 + std::array buf; + snprintf(buf.data(), buf.size(), "u%04x", ch); + out.append(buf.data(), buf.size() - 1); // add only five, discarding the null terminator. break; } } @@ -514,12 +511,12 @@ static std::string json_escape(std::string const& in) } AWS_LAMBDA_RUNTIME_API -invocation_response invocation_response::success(std::string const& payload, std::string const& content_type) +invocation_response invocation_response::success(std::string payload, std::string content_type) { invocation_response r; r.m_success = true; - r.m_content_type = content_type; - r.m_payload = payload; + r.m_content_type = std::move(content_type); + r.m_payload = std::move(payload); return r; } @@ -530,7 +527,7 @@ invocation_response invocation_response::failure(std::string const& error_messag r.m_success = false; r.m_content_type = "application/json"; r.m_payload = R"({"errorMessage":")" + json_escape(error_message) + R"(","errorType":")" + json_escape(error_type) + - R"(", "stackTrace":[]})"; + R"(","stackTrace":[]})"; return r; } diff --git a/src/version.cpp.in b/src/version.cpp.in index cb91ad4..e13b1e1 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -22,19 +22,19 @@ namespace lambda_runtime { AWS_LAMBDA_RUNTIME_API unsigned get_version_major() { - return @PROJECT_VERSION_MAJOR@; + return @PROJECT_VERSION_MAJOR@; // NOLINT } AWS_LAMBDA_RUNTIME_API unsigned get_version_minor() { - return @PROJECT_VERSION_MINOR@; + return @PROJECT_VERSION_MINOR@; // NOLINT } AWS_LAMBDA_RUNTIME_API unsigned get_version_patch() { - return @PROJECT_VERSION_PATCH@; + return @PROJECT_VERSION_PATCH@; // NOLINT } /* clang-format on */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ddec1a8..7406096 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,16 +1,50 @@ +cmake_minimum_required(VERSION 3.11) project(aws-lambda-runtime-tests LANGUAGES CXX) -find_package(AWSSDK COMPONENTS lambda iam) -add_executable(${PROJECT_NAME} - main.cpp - runtime_tests.cpp - version_tests.cpp - gtest/gtest-all.cc) +if(DEFINED ENV{GITHUB_ACTIONS}) + # Fetch Google Test for unit tests + include(FetchContent) + FetchContent_Declare(gtest + URL https://github.com/google/googletest/archive/v1.12.0.tar.gz + DOWNLOAD_EXTRACT_TIMESTAMP TRUE + ) + # Configure build of googletest + set(gtest_force_shared_crt ON CACHE BOOL "" FORCE) + set(BUILD_GMOCK OFF CACHE BOOL "" FORCE) + set(INSTALL_GTEST OFF) + FetchContent_MakeAvailable(gtest) -target_link_libraries(${PROJECT_NAME} PRIVATE ${AWSSDK_LINK_LIBRARIES} aws-lambda-runtime) + add_executable(unit_tests + unit/no_op_test.cpp) + target_link_libraries(unit_tests PRIVATE gtest_main aws-lambda-runtime) -include(GoogleTest) -gtest_discover_tests(${PROJECT_NAME} EXTRA_ARGS "--aws_prefix=${TEST_RESOURCE_PREFIX}") # requires CMake 3.10 or later + # Register unit tests + include(GoogleTest) + gtest_discover_tests(unit_tests + PROPERTIES + LABELS "unit" + DISCOVERY_TIMEOUT 10) +else() + message(STATUS "Unit tests skipped: Not in GitHub Actions environment") +endif() -add_subdirectory(resources) + +find_package(AWSSDK COMPONENTS lambda iam QUIET) + +if(AWSSDK_FOUND) + add_executable(${PROJECT_NAME} + integration/main.cpp + integration/runtime_tests.cpp + integration/version_tests.cpp + gtest/gtest-all.cc) + + target_link_libraries(${PROJECT_NAME} PRIVATE ${AWSSDK_LINK_LIBRARIES} aws-lambda-runtime) + + include(GoogleTest) + gtest_discover_tests(${PROJECT_NAME} EXTRA_ARGS "--aws_prefix=${TEST_RESOURCE_PREFIX}") + + add_subdirectory(resources) +else() + message(STATUS "Integration tests skipped: AWS SDK not found or not in GitHub Actions environment") +endif() diff --git a/tests/gtest/gtest.h b/tests/gtest/gtest.h index 844c9b7..deeb98d 100644 --- a/tests/gtest/gtest.h +++ b/tests/gtest/gtest.h @@ -1,3 +1,4 @@ +// clang-format off // Copyright 2005, Google Inc. // All rights reserved. // diff --git a/tests/main.cpp b/tests/integration/main.cpp similarity index 64% rename from tests/main.cpp rename to tests/integration/main.cpp index 33c882f..2a112d3 100644 --- a/tests/main.cpp +++ b/tests/integration/main.cpp @@ -1,8 +1,8 @@ #include #include -#include "gtest/gtest.h" +#include "../gtest/gtest.h" -std::function()> GetConsoleLoggerFactory() +std::function()> get_console_logger_factory() { return [] { return Aws::MakeShared( @@ -14,11 +14,11 @@ std::string aws_prefix; void parse_args(int argc, char** argv) { - const std::string resourcePrefixOption = "--aws_prefix="; + const std::string resource_prefix_option = "--aws_prefix="; for (int i = 1; i < argc; i++) { std::string arg = argv[i]; - if (arg.find(resourcePrefixOption) == 0) { - aws_prefix = arg.substr(resourcePrefixOption.length()); // get whatever value after the '=' + if (arg.find(resource_prefix_option) == 0) { + aws_prefix = arg.substr(resource_prefix_option.length()); // get whatever value after the '=' break; } } @@ -29,10 +29,10 @@ int main(int argc, char** argv) parse_args(argc, argv); Aws::SDKOptions options; options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Warn; - options.loggingOptions.logger_create_fn = GetConsoleLoggerFactory(); + options.loggingOptions.logger_create_fn = get_console_logger_factory(); Aws::InitAPI(options); ::testing::InitGoogleTest(&argc, argv); - int exitCode = RUN_ALL_TESTS(); + int exit_code = RUN_ALL_TESTS(); Aws::ShutdownAPI(options); - return exitCode; + return exit_code; } diff --git a/tests/integration/runtime_tests.cpp b/tests/integration/runtime_tests.cpp new file mode 100644 index 0000000..843f815 --- /dev/null +++ b/tests/integration/runtime_tests.cpp @@ -0,0 +1,249 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include + +extern std::string aws_prefix; + +namespace { + +using namespace Aws::Lambda; + +constexpr auto ZIP_FILE_PATH = "resources/lambda-test-fun.zip"; +constexpr auto REQUEST_TIMEOUT = 15 * 1000; + +struct LambdaRuntimeTest : public ::testing::Test { + LambdaClient m_lambda_client; + Aws::IAM::IAMClient m_iam_client; + static Aws::Client::ClientConfiguration create_iam_config() + { + Aws::Client::ClientConfiguration config; + config.requestTimeoutMs = REQUEST_TIMEOUT; + config.region = Aws::Region::US_EAST_1; + return config; + } + + static Aws::Client::ClientConfiguration create_lambda_config() + { + Aws::Client::ClientConfiguration config; + config.requestTimeoutMs = REQUEST_TIMEOUT; + config.region = Aws::Environment::GetEnv("AWS_REGION"); + if (config.region.empty()) { + throw std::invalid_argument("environment variable AWS_REGION not set"); + } + return config; + } + + static Aws::String build_resource_name(Aws::String const& name) + { + return aws_prefix.c_str() + name; // NOLINT + } + + LambdaRuntimeTest() : m_lambda_client(create_lambda_config()), m_iam_client(create_iam_config()) {} + + ~LambdaRuntimeTest() override + { + // clean up in case we exited one test abnormally + delete_function(build_resource_name("echo_success"), false /*assert*/); + delete_function(build_resource_name("echo_failure"), false /*assert*/); + delete_function(build_resource_name("binary_response"), false /*assert*/); + delete_function(build_resource_name("crash_backtrace"), false /*assert*/); + } + + Aws::String get_role_arn(Aws::String const& role_name) + { + using namespace Aws::IAM; + using namespace Aws::IAM::Model; + GetRoleRequest request; + request.WithRoleName(role_name); + auto outcome = m_iam_client.GetRole(request); + EXPECT_TRUE(outcome.IsSuccess()); + if (outcome.IsSuccess()) { + return outcome.GetResult().GetRole().GetArn(); + } + return {}; + } + + void create_function(Aws::String const& function_name, Aws::String const& handler_name) + { + Model::CreateFunctionRequest create_function_request; + create_function_request.SetHandler(handler_name); + create_function_request.SetFunctionName(function_name); + // I ran into eventual-consistency issues when creating the role dynamically as part of the test. + auto exec_role = Aws::Environment::GetEnv("LAMBDA_TEST_ROLE"); + if (exec_role.empty()) { + exec_role = "integration-tests"; + } + create_function_request.SetRole(get_role_arn(exec_role)); + + struct stat s; + auto rc = stat(ZIP_FILE_PATH, &s); + ASSERT_EQ(rc, 0) << std::string("file does not exist: ") + ZIP_FILE_PATH; + Aws::Utils::CryptoBuffer zip_file_bytes(s.st_size); + auto* zip_file = fopen(ZIP_FILE_PATH, "r"); + fread(zip_file_bytes.GetUnderlyingData(), sizeof(unsigned char), s.st_size, zip_file); + fclose(zip_file); + + Model::FunctionCode funcode; + funcode.SetZipFile(std::move(zip_file_bytes)); + create_function_request.SetCode(std::move(funcode)); + create_function_request.SetRuntime(Aws::Lambda::Model::Runtime::provided_al2); + + std::vector lambda_architectures = {Aws::Lambda::Model::Architecture::x86_64}; +#ifdef __aarch64__ + lambda_architectures[0] = Aws::Lambda::Model::Architecture::arm64; +#endif + create_function_request.SetArchitectures(lambda_architectures); + + auto outcome = m_lambda_client.CreateFunction(create_function_request); + ASSERT_TRUE(outcome.IsSuccess()) << "Failed to create function " << function_name; + + // work around Lambda function pending creation state + sleep(5); + } + + void delete_function(Aws::String const& function_name, bool assert = true) + { + Model::DeleteFunctionRequest delete_function_request; + delete_function_request.SetFunctionName(function_name); + auto outcome = m_lambda_client.DeleteFunction(delete_function_request); + if (assert) { + ASSERT_TRUE(outcome.IsSuccess()) << "Failed to delete function " << function_name; + } + } +}; + +TEST_F(LambdaRuntimeTest, echo_success) +{ + Aws::String const funcname = build_resource_name("echo_success"); + constexpr auto payload_content = "Hello, Lambda!"; + create_function(funcname, "echo_success" /*handler_name*/); + Model::InvokeRequest invoke_request; + invoke_request.SetFunctionName(funcname); + invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); + invoke_request.SetContentType("application/json"); + + std::shared_ptr payload = Aws::MakeShared("FunctionTest"); + Aws::Utils::Json::JsonValue json_payload; + json_payload.WithString("barbaz", payload_content); + *payload << json_payload.View().WriteCompact(); + invoke_request.SetBody(payload); + + Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); + EXPECT_TRUE(invoke_outcome.IsSuccess()); + Aws::StringStream output; + if (!invoke_outcome.IsSuccess()) { + delete_function(funcname); + return; + } + EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); + EXPECT_TRUE(invoke_outcome.GetResult().GetFunctionError().empty()); + auto const json_response = Aws::Utils::Json::JsonValue(invoke_outcome.GetResult().GetPayload()); + EXPECT_TRUE(json_response.WasParseSuccessful()); + EXPECT_STREQ(payload_content, json_response.View().GetString("barbaz").c_str()); + delete_function(funcname); +} + +TEST_F(LambdaRuntimeTest, echo_unicode) +{ + Aws::String const funcname = build_resource_name("echo_success"); // re-use the echo method but with Unicode input + constexpr auto payload_content = "画像は1000語の価値がある"; + create_function(funcname, "echo_success" /*handler_name*/); + Model::InvokeRequest invoke_request; + invoke_request.SetFunctionName(funcname); + invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); + invoke_request.SetContentType("application/json"); + + std::shared_ptr payload = Aws::MakeShared("FunctionTest"); + Aws::Utils::Json::JsonValue json_payload; + json_payload.WithString("UnicodeText", payload_content); + *payload << json_payload.View().WriteCompact(); + invoke_request.SetBody(payload); + + Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); + EXPECT_TRUE(invoke_outcome.IsSuccess()); + Aws::StringStream output; + if (!invoke_outcome.IsSuccess()) { + delete_function(funcname); + return; + } + EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); + EXPECT_TRUE(invoke_outcome.GetResult().GetFunctionError().empty()); + auto const json_response = Aws::Utils::Json::JsonValue(invoke_outcome.GetResult().GetPayload()); + EXPECT_TRUE(json_response.WasParseSuccessful()); + EXPECT_STREQ(payload_content, json_response.View().GetString("UnicodeText").c_str()); + delete_function(funcname); +} + +TEST_F(LambdaRuntimeTest, echo_failure) +{ + Aws::String const funcname = build_resource_name("echo_failure"); + create_function(funcname, "echo_failure" /*handler_name*/); + Model::InvokeRequest invoke_request; + invoke_request.SetFunctionName(funcname); + invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); + + Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); + EXPECT_TRUE(invoke_outcome.IsSuccess()); + EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); + EXPECT_STREQ("Unhandled", invoke_outcome.GetResult().GetFunctionError().c_str()); + delete_function(funcname); +} + +TEST_F(LambdaRuntimeTest, binary_response) +{ + Aws::String const funcname = build_resource_name("binary_response"); + unsigned long constexpr expected_length = 1451; + create_function(funcname, "binary_response" /*handler_name*/); + Model::InvokeRequest invoke_request; + invoke_request.SetFunctionName(funcname); + invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); + + Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); + EXPECT_TRUE(invoke_outcome.IsSuccess()); + EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); + EXPECT_TRUE(invoke_outcome.GetResult().GetFunctionError().empty()); + EXPECT_EQ(expected_length, invoke_outcome.GetResult().GetPayload().tellp()); + delete_function(funcname); +} + +TEST_F(LambdaRuntimeTest, crash) +{ + Aws::String const funcname = build_resource_name("crash_backtrace"); + create_function(funcname, "crash_backtrace" /*handler_name*/); + Model::InvokeRequest invoke_request; + invoke_request.SetFunctionName(funcname); + invoke_request.SetInvocationType(Model::InvocationType::RequestResponse); + invoke_request.SetLogType(Model::LogType::Tail); + + Model::InvokeOutcome invoke_outcome = m_lambda_client.Invoke(invoke_request); + EXPECT_TRUE(invoke_outcome.IsSuccess()); + EXPECT_EQ(200, invoke_outcome.GetResult().GetStatusCode()); + EXPECT_STREQ("Unhandled", invoke_outcome.GetResult().GetFunctionError().c_str()); + Aws::Utils::Base64::Base64 base64; + auto decoded = base64.Decode(invoke_outcome.GetResult().GetLogResult()); + std::string tail_logs(reinterpret_cast(decoded.GetUnderlyingData()), decoded.GetLength()); + EXPECT_NE(tail_logs.find("Stack trace (most recent call last):"), std::string::npos); + delete_function(funcname); +} + +} // namespace diff --git a/tests/version_tests.cpp b/tests/integration/version_tests.cpp similarity index 87% rename from tests/version_tests.cpp rename to tests/integration/version_tests.cpp index 862c680..a0f546e 100644 --- a/tests/version_tests.cpp +++ b/tests/integration/version_tests.cpp @@ -1,5 +1,5 @@ #include -#include "gtest/gtest.h" +#include "../gtest/gtest.h" using namespace aws::lambda_runtime; @@ -12,7 +12,7 @@ TEST(VersionTests, get_version_major) TEST(VersionTests, get_version_minor) { auto version = get_version_minor(); - ASSERT_GE(version, 1); + ASSERT_GE(version, 0); } TEST(VersionTests, get_version_patch) diff --git a/tests/resources/lambda_function.cpp b/tests/resources/lambda_function.cpp index 6fbc588..bf12fc0 100644 --- a/tests/resources/lambda_function.cpp +++ b/tests/resources/lambda_function.cpp @@ -1,14 +1,15 @@ #include #include +#include #include #include #include using namespace aws::lambda_runtime; -constexpr unsigned int awslogo_png_len = 1451; -extern unsigned char awslogo_png[awslogo_png_len]; +constexpr unsigned int AWSLOGO_PNG_LEN = 1451; +extern unsigned char awslogo_png[AWSLOGO_PNG_LEN]; // NOLINT invocation_response echo_success(invocation_request const& request) { @@ -22,8 +23,14 @@ invocation_response echo_failure(invocation_request const& /*request*/) invocation_response binary_response(invocation_request const& /*request*/) { - const std::string png((char*)awslogo_png, awslogo_png_len); - return invocation_response::success(png, "image/png"); + std::string png((char*)awslogo_png, AWSLOGO_PNG_LEN); + return invocation_response::success(std::move(png), "image/png"); +} + +invocation_response crash_backtrace(invocation_request const& /*request*/) +{ + throw std::runtime_error("barf"); + return invocation_response::failure("unreachable", "unreachable"); } int main(int argc, char* argv[]) @@ -32,13 +39,11 @@ int main(int argc, char* argv[]) handlers.emplace("echo_success", echo_success); handlers.emplace("echo_failure", echo_failure); handlers.emplace("binary_response", binary_response); + handlers.emplace("crash_backtrace", crash_backtrace); - if (argc < 2) { - aws::logging::log_error("lambda_fun", "Missing handler argument. Exiting."); - return -1; - } - - auto it = handlers.find(argv[1]); + // Read the handler from the environment variable + const char* handler_name = std::getenv("_HANDLER"); + auto it = handlers.find(handler_name == nullptr ? "" : handler_name); if (it == handlers.end()) { aws::logging::log_error("lambda_fun", "Handler %s not found. Exiting.", argv[1]); return -2; @@ -47,81 +52,83 @@ int main(int argc, char* argv[]) return 0; } -unsigned char awslogo_png[] = { - 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, - 0x18, 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x35, 0xf8, 0xdc, 0x7e, 0x00, 0x00, 0x00, 0x04, 0x67, - 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4d, - 0x00, 0x00, 0x7a, 0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x80, 0xe8, 0x00, 0x00, 0x75, - 0x30, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, 0x00, - 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, - 0x00, 0x00, 0x01, 0x59, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, - 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, - 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, - 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, - 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, - 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, - 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, - 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, - 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, - 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, - 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, - 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, - 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, - 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, - 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c, 0x2f, 0x74, 0x69, - 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, - 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x0a, 0x3c, 0x2f, - 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x0a, 0x4c, 0xc2, 0x27, 0x59, 0x00, 0x00, 0x03, 0xbc, - 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0x65, 0x54, 0x6f, 0x6c, 0x53, 0x55, 0x14, 0x3f, 0xe7, 0xbe, 0xd7, 0x75, 0xf2, - 0x18, 0x1a, 0x24, 0xc4, 0xc1, 0x20, 0x0a, 0x6b, 0x27, 0x1b, 0xa3, 0x85, 0xa4, 0x92, 0x29, 0x5d, 0xbb, 0x25, 0x82, - 0xc6, 0x48, 0x0c, 0x61, 0x43, 0x0d, 0x4a, 0x98, 0x91, 0x19, 0xa3, 0x7e, 0x22, 0x41, 0x09, 0xc9, 0xe0, 0x83, 0x06, - 0x35, 0x31, 0x46, 0x8c, 0xc1, 0x05, 0x25, 0xc1, 0xc8, 0x97, 0xc5, 0xc4, 0x38, 0xd0, 0x84, 0x60, 0x52, 0xba, 0x2d, - 0x68, 0xd0, 0x6c, 0x65, 0x56, 0xb1, 0xab, 0x3a, 0x70, 0x09, 0x68, 0x84, 0x0f, 0xba, 0xd5, 0xba, 0xbe, 0x77, 0x8f, - 0xbf, 0x7b, 0xcb, 0x32, 0xff, 0x9c, 0xe6, 0xdc, 0x73, 0xcf, 0xb9, 0xbf, 0xf3, 0xf7, 0xde, 0x57, 0x26, 0x50, 0x74, - 0x7d, 0x47, 0x4c, 0x74, 0xf0, 0x2a, 0xb6, 0x2b, 0x88, 0x69, 0x21, 0x6b, 0x1a, 0xd5, 0x25, 0x6f, 0x07, 0x79, 0x33, - 0x29, 0x26, 0xea, 0xad, 0xf1, 0xd5, 0xae, 0x7c, 0x3e, 0x33, 0x1d, 0x8d, 0xa7, 0x1f, 0x21, 0x09, 0x7a, 0x1d, 0x97, - 0x7a, 0xbe, 0xfb, 0x7a, 0xe8, 0x6a, 0x34, 0x9e, 0x4a, 0x88, 0x48, 0x9f, 0x2a, 0x07, 0x8f, 0x05, 0x61, 0xa7, 0x43, - 0x31, 0xbd, 0x6e, 0xe2, 0x09, 0xd1, 0x34, 0x2b, 0xb5, 0xaf, 0x30, 0x9a, 0x39, 0xab, 0xac, 0x41, 0xcb, 0x0a, 0x04, - 0x1a, 0x55, 0x4a, 0xed, 0x12, 0x96, 0x27, 0x85, 0x69, 0x33, 0x7b, 0x33, 0x6f, 0x6a, 0xd2, 0x93, 0x8e, 0x1b, 0xda, - 0x56, 0x76, 0x25, 0x61, 0x71, 0xa2, 0x77, 0x86, 0x6a, 0xbd, 0x07, 0xfc, 0x0a, 0xdd, 0x5b, 0x0d, 0xa4, 0x77, 0x43, - 0xc6, 0xb4, 0xe7, 0x34, 0x30, 0xd3, 0xc7, 0x9a, 0xf8, 0xa8, 0x62, 0xb5, 0x85, 0x99, 0xdf, 0xd5, 0x5a, 0xdb, 0xd8, - 0x8a, 0xba, 0xba, 0x9c, 0x89, 0x5c, 0xe6, 0x54, 0x21, 0x97, 0x7d, 0xa9, 0xfe, 0x56, 0x1a, 0x2b, 0x8e, 0x0e, 0x9d, - 0x43, 0x09, 0x6f, 0xa0, 0x8a, 0xce, 0x1f, 0x73, 0xc3, 0x85, 0xc0, 0xaf, 0x14, 0x1d, 0xd2, 0x69, 0x13, 0x10, 0xb4, - 0x74, 0xb6, 0x5c, 0xba, 0x40, 0xc4, 0x49, 0xab, 0x09, 0xa7, 0x44, 0xe8, 0x98, 0x2f, 0x7e, 0xc9, 0x71, 0x5c, 0x56, - 0x22, 0x37, 0x2e, 0x8d, 0x65, 0x26, 0x0b, 0x63, 0xe7, 0xfa, 0x8b, 0xb9, 0xec, 0x19, 0x13, 0x1b, 0x85, 0x63, 0x44, - 0xad, 0x9b, 0x56, 0x91, 0x52, 0xc7, 0x10, 0xb4, 0x01, 0xea, 0x15, 0xc8, 0x16, 0x1c, 0x5c, 0x9d, 0xc8, 0x65, 0x37, - 0x44, 0xe2, 0xc9, 0x77, 0x88, 0x54, 0x84, 0x39, 0xd8, 0x2b, 0x5a, 0x1d, 0x11, 0x92, 0x7e, 0x26, 0xde, 0xa3, 0xc5, - 0xdf, 0xae, 0xd8, 0x2d, 0x90, 0xa6, 0xad, 0x13, 0xe3, 0xd9, 0x6c, 0x34, 0x96, 0x3c, 0x24, 0xc4, 0x07, 0xe0, 0xff, - 0x3b, 0xf8, 0x74, 0xc5, 0x57, 0x7b, 0x27, 0xf3, 0x99, 0x6b, 0xb6, 0x0d, 0xad, 0xd4, 0x09, 0x4d, 0xb4, 0x44, 0x09, - 0x6d, 0x91, 0x19, 0xef, 0x21, 0x62, 0x3e, 0x05, 0x50, 0x18, 0x4c, 0x1c, 0xa8, 0x41, 0x12, 0x69, 0xd0, 0xe2, 0xec, - 0x40, 0xe0, 0xcb, 0x61, 0xdf, 0xff, 0x14, 0x23, 0x58, 0xc0, 0xec, 0xf6, 0xe0, 0xf8, 0x67, 0xf9, 0xd3, 0xfb, 0xd2, - 0xe0, 0x0a, 0xb9, 0xa1, 0x3e, 0xdc, 0xd5, 0x72, 0x8c, 0xf7, 0x39, 0xa8, 0xf7, 0x87, 0x42, 0xf2, 0x81, 0xb1, 0xdb, - 0x04, 0xa8, 0x76, 0x0d, 0x78, 0xe4, 0xfb, 0x8b, 0xd9, 0x9f, 0x8a, 0xc5, 0xcf, 0xfe, 0x82, 0x71, 0x3d, 0xba, 0x58, - 0x6c, 0x00, 0x21, 0xbd, 0xf4, 0x73, 0x88, 0x32, 0x8b, 0xec, 0x43, 0xf5, 0xc3, 0xf9, 0xfc, 0xf9, 0x1b, 0xb8, 0xd8, - 0xeb, 0xc0, 0xbf, 0x0c, 0x3e, 0x6d, 0xf0, 0xd1, 0x58, 0xe7, 0xf2, 0xa6, 0x75, 0xed, 0x77, 0xe1, 0x21, 0x5c, 0x2b, - 0x8e, 0x65, 0x3f, 0x0c, 0x84, 0x4e, 0x6a, 0x2d, 0xeb, 0x8c, 0xbf, 0x6b, 0x16, 0x12, 0x3e, 0x8c, 0x54, 0xaf, 0x45, - 0x62, 0xed, 0x1b, 0xf0, 0x8a, 0xca, 0x98, 0xeb, 0x38, 0x9c, 0x17, 0x47, 0x5b, 0xd3, 0x77, 0xe7, 0xc7, 0x07, 0x2e, - 0xc1, 0x9e, 0x55, 0xca, 0x89, 0xfb, 0x81, 0x8c, 0x54, 0xf1, 0x74, 0xc1, 0x09, 0x85, 0x36, 0x57, 0x82, 0xca, 0x27, - 0x55, 0xf7, 0xa0, 0x19, 0x05, 0x1d, 0x8f, 0xc4, 0x92, 0x53, 0x0b, 0x1c, 0xf6, 0x6a, 0x58, 0x56, 0xdf, 0x1e, 0xa6, - 0x9e, 0x1f, 0x2c, 0xf8, 0xe6, 0x92, 0x48, 0xa4, 0x12, 0x2b, 0xd7, 0x26, 0x77, 0x9a, 0x4a, 0x8c, 0xe9, 0xce, 0x96, - 0xf4, 0x1d, 0x10, 0xb6, 0xc3, 0xa6, 0xa6, 0xfb, 0xea, 0x1a, 0xd7, 0x76, 0xae, 0x9e, 0xd3, 0x1b, 0x1b, 0x37, 0x2e, - 0x5a, 0x15, 0x6f, 0x8f, 0x40, 0x47, 0x1d, 0x55, 0x5a, 0x03, 0xbd, 0xa1, 0xb5, 0xfd, 0x71, 0xa2, 0x8e, 0x6d, 0x44, - 0x4f, 0x78, 0x73, 0x76, 0x46, 0x66, 0xbc, 0x30, 0xf3, 0x74, 0xe7, 0xe9, 0xe0, 0x41, 0x52, 0x60, 0x5c, 0x8b, 0x25, - 0x13, 0xe4, 0x9f, 0xe7, 0xff, 0xd5, 0x6f, 0xc2, 0xaa, 0x42, 0xbe, 0xa2, 0x7a, 0x1a, 0xa7, 0xb6, 0xa0, 0x1c, 0xf2, - 0x1c, 0xae, 0x7c, 0xc1, 0x72, 0x94, 0x9e, 0xc5, 0xd1, 0x34, 0x3f, 0x43, 0x27, 0xe6, 0x90, 0x82, 0x04, 0xdf, 0x52, - 0x8b, 0xdb, 0x4c, 0x79, 0x93, 0xc4, 0x26, 0xea, 0xce, 0x77, 0xf1, 0xc0, 0xc0, 0x40, 0x60, 0x30, 0x18, 0x21, 0x1f, - 0xea, 0x48, 0x3b, 0x7d, 0xe9, 0x8c, 0xa6, 0x66, 0xdb, 0x85, 0xc3, 0xdd, 0x34, 0x5b, 0x7a, 0xbb, 0xae, 0x2d, 0xe4, - 0x96, 0xb7, 0x2b, 0x0a, 0x1c, 0xc5, 0xfa, 0x61, 0x40, 0x67, 0x58, 0xde, 0xa2, 0x45, 0x78, 0x2f, 0xaf, 0x40, 0xd9, - 0x08, 0x36, 0x37, 0x7f, 0x92, 0x7b, 0xe9, 0x37, 0xc8, 0xff, 0x91, 0x49, 0x6c, 0x8d, 0x7d, 0xb8, 0x35, 0xfe, 0x57, - 0x57, 0xd6, 0x8c, 0x58, 0x61, 0xaa, 0xa5, 0x6e, 0x94, 0x74, 0x19, 0x86, 0x85, 0xe0, 0x17, 0x4d, 0xbb, 0x96, 0xa4, - 0x9f, 0xb6, 0xc2, 0xc5, 0x7c, 0xea, 0x66, 0xd6, 0xc3, 0xe0, 0x41, 0xd4, 0x76, 0x1e, 0xcf, 0x60, 0x82, 0xae, 0xd0, - 0x75, 0x9e, 0x1f, 0x99, 0xc5, 0x9b, 0x45, 0xde, 0xa3, 0x3a, 0x0a, 0x68, 0x25, 0x02, 0xde, 0x03, 0xec, 0x83, 0x30, - 0xb5, 0x21, 0xc6, 0x14, 0xe4, 0x53, 0x28, 0xe5, 0x79, 0xec, 0xbf, 0x61, 0x5b, 0x15, 0xda, 0x44, 0x8b, 0xd5, 0xf6, - 0xfb, 0xe9, 0x51, 0x1c, 0xbc, 0x80, 0xc0, 0x6d, 0xf6, 0x4b, 0x28, 0x99, 0x48, 0xf4, 0x0b, 0xd6, 0x5f, 0xc1, 0x7f, - 0x80, 0xcd, 0xc8, 0x6e, 0x01, 0x2f, 0x01, 0xd7, 0x53, 0x0d, 0x7e, 0xb3, 0xd6, 0x36, 0x84, 0x24, 0x47, 0x78, 0x0f, - 0x7d, 0x84, 0x98, 0x2e, 0x2d, 0xa3, 0xfd, 0x74, 0x1b, 0x1d, 0x9e, 0xef, 0x00, 0x46, 0x54, 0xe9, 0xc3, 0xc9, 0x92, - 0xbc, 0x8f, 0x3f, 0xbe, 0x59, 0x4a, 0xc1, 0x69, 0x13, 0x0c, 0x2d, 0x48, 0xb2, 0x0c, 0x7b, 0xd3, 0xb6, 0xb9, 0xf0, - 0x69, 0xf0, 0x14, 0x76, 0x17, 0x21, 0x47, 0xc8, 0xa1, 0x0c, 0x3f, 0x6d, 0x8b, 0x80, 0x0a, 0xc0, 0x71, 0xaa, 0xe5, - 0xdd, 0x54, 0x36, 0xfb, 0xbf, 0x01, 0x02, 0x9d, 0x70, 0x74, 0xcd, 0x2a, 0x03, 0x15, 0x00, 0x00, 0x00, 0x00, 0x49, - 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; +/* clang-format off */ +unsigned char awslogo_png[] = { // NOLINT + 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, // NOLINT + 0x18, 0x00, 0x00, 0x00, 0x0e, 0x08, 0x06, 0x00, 0x00, 0x00, 0x35, 0xf8, 0xdc, 0x7e, 0x00, 0x00, 0x00, 0x04, 0x67, // NOLINT + 0x41, 0x4d, 0x41, 0x00, 0x00, 0xb1, 0x8f, 0x0b, 0xfc, 0x61, 0x05, 0x00, 0x00, 0x00, 0x20, 0x63, 0x48, 0x52, 0x4d, // NOLINT + 0x00, 0x00, 0x7a, 0x26, 0x00, 0x00, 0x80, 0x84, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x80, 0xe8, 0x00, 0x00, 0x75, // NOLINT + 0x30, 0x00, 0x00, 0xea, 0x60, 0x00, 0x00, 0x3a, 0x98, 0x00, 0x00, 0x17, 0x70, 0x9c, 0xba, 0x51, 0x3c, 0x00, 0x00, // NOLINT + 0x00, 0x09, 0x70, 0x48, 0x59, 0x73, 0x00, 0x00, 0x0b, 0x13, 0x00, 0x00, 0x0b, 0x13, 0x01, 0x00, 0x9a, 0x9c, 0x18, // NOLINT + 0x00, 0x00, 0x01, 0x59, 0x69, 0x54, 0x58, 0x74, 0x58, 0x4d, 0x4c, 0x3a, 0x63, 0x6f, 0x6d, 0x2e, 0x61, 0x64, 0x6f, // NOLINT + 0x62, 0x65, 0x2e, 0x78, 0x6d, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, // NOLINT + 0x74, 0x61, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x78, 0x3d, 0x22, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x3a, 0x6e, // NOLINT + 0x73, 0x3a, 0x6d, 0x65, 0x74, 0x61, 0x2f, 0x22, 0x20, 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x74, 0x6b, 0x3d, 0x22, 0x58, // NOLINT + 0x4d, 0x50, 0x20, 0x43, 0x6f, 0x72, 0x65, 0x20, 0x35, 0x2e, 0x34, 0x2e, 0x30, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, // NOLINT + 0x3c, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x20, 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x72, 0x64, 0x66, 0x3d, // NOLINT + 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x77, 0x33, 0x2e, 0x6f, 0x72, 0x67, 0x2f, // NOLINT + 0x31, 0x39, 0x39, 0x39, 0x2f, 0x30, 0x32, 0x2f, 0x32, 0x32, 0x2d, 0x72, 0x64, 0x66, 0x2d, 0x73, 0x79, 0x6e, 0x74, // NOLINT + 0x61, 0x78, 0x2d, 0x6e, 0x73, 0x23, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x72, 0x64, 0x66, // NOLINT + 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x72, 0x64, 0x66, 0x3a, 0x61, 0x62, // NOLINT + 0x6f, 0x75, 0x74, 0x3d, 0x22, 0x22, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, // NOLINT + 0x78, 0x6d, 0x6c, 0x6e, 0x73, 0x3a, 0x74, 0x69, 0x66, 0x66, 0x3d, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, // NOLINT + 0x6e, 0x73, 0x2e, 0x61, 0x64, 0x6f, 0x62, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x74, 0x69, 0x66, 0x66, 0x2f, 0x31, // NOLINT + 0x2e, 0x30, 0x2f, 0x22, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x3c, 0x74, 0x69, 0x66, // NOLINT + 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x31, 0x3c, 0x2f, 0x74, 0x69, // NOLINT + 0x66, 0x66, 0x3a, 0x4f, 0x72, 0x69, 0x65, 0x6e, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, // NOLINT + 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, // NOLINT + 0x6e, 0x3e, 0x0a, 0x20, 0x20, 0x20, 0x3c, 0x2f, 0x72, 0x64, 0x66, 0x3a, 0x52, 0x44, 0x46, 0x3e, 0x0a, 0x3c, 0x2f, // NOLINT + 0x78, 0x3a, 0x78, 0x6d, 0x70, 0x6d, 0x65, 0x74, 0x61, 0x3e, 0x0a, 0x4c, 0xc2, 0x27, 0x59, 0x00, 0x00, 0x03, 0xbc, // NOLINT + 0x49, 0x44, 0x41, 0x54, 0x38, 0x11, 0x65, 0x54, 0x6f, 0x6c, 0x53, 0x55, 0x14, 0x3f, 0xe7, 0xbe, 0xd7, 0x75, 0xf2, // NOLINT + 0x18, 0x1a, 0x24, 0xc4, 0xc1, 0x20, 0x0a, 0x6b, 0x27, 0x1b, 0xa3, 0x85, 0xa4, 0x92, 0x29, 0x5d, 0xbb, 0x25, 0x82, // NOLINT + 0xc6, 0x48, 0x0c, 0x61, 0x43, 0x0d, 0x4a, 0x98, 0x91, 0x19, 0xa3, 0x7e, 0x22, 0x41, 0x09, 0xc9, 0xe0, 0x83, 0x06, // NOLINT + 0x35, 0x31, 0x46, 0x8c, 0xc1, 0x05, 0x25, 0xc1, 0xc8, 0x97, 0xc5, 0xc4, 0x38, 0xd0, 0x84, 0x60, 0x52, 0xba, 0x2d, // NOLINT + 0x68, 0xd0, 0x6c, 0x65, 0x56, 0xb1, 0xab, 0x3a, 0x70, 0x09, 0x68, 0x84, 0x0f, 0xba, 0xd5, 0xba, 0xbe, 0x77, 0x8f, // NOLINT + 0xbf, 0x7b, 0xcb, 0x32, 0xff, 0x9c, 0xe6, 0xdc, 0x73, 0xcf, 0xb9, 0xbf, 0xf3, 0xf7, 0xde, 0x57, 0x26, 0x50, 0x74, // NOLINT + 0x7d, 0x47, 0x4c, 0x74, 0xf0, 0x2a, 0xb6, 0x2b, 0x88, 0x69, 0x21, 0x6b, 0x1a, 0xd5, 0x25, 0x6f, 0x07, 0x79, 0x33, // NOLINT + 0x29, 0x26, 0xea, 0xad, 0xf1, 0xd5, 0xae, 0x7c, 0x3e, 0x33, 0x1d, 0x8d, 0xa7, 0x1f, 0x21, 0x09, 0x7a, 0x1d, 0x97, // NOLINT + 0x7a, 0xbe, 0xfb, 0x7a, 0xe8, 0x6a, 0x34, 0x9e, 0x4a, 0x88, 0x48, 0x9f, 0x2a, 0x07, 0x8f, 0x05, 0x61, 0xa7, 0x43, // NOLINT + 0x31, 0xbd, 0x6e, 0xe2, 0x09, 0xd1, 0x34, 0x2b, 0xb5, 0xaf, 0x30, 0x9a, 0x39, 0xab, 0xac, 0x41, 0xcb, 0x0a, 0x04, // NOLINT + 0x1a, 0x55, 0x4a, 0xed, 0x12, 0x96, 0x27, 0x85, 0x69, 0x33, 0x7b, 0x33, 0x6f, 0x6a, 0xd2, 0x93, 0x8e, 0x1b, 0xda, // NOLINT + 0x56, 0x76, 0x25, 0x61, 0x71, 0xa2, 0x77, 0x86, 0x6a, 0xbd, 0x07, 0xfc, 0x0a, 0xdd, 0x5b, 0x0d, 0xa4, 0x77, 0x43, // NOLINT + 0xc6, 0xb4, 0xe7, 0x34, 0x30, 0xd3, 0xc7, 0x9a, 0xf8, 0xa8, 0x62, 0xb5, 0x85, 0x99, 0xdf, 0xd5, 0x5a, 0xdb, 0xd8, // NOLINT + 0x8a, 0xba, 0xba, 0x9c, 0x89, 0x5c, 0xe6, 0x54, 0x21, 0x97, 0x7d, 0xa9, 0xfe, 0x56, 0x1a, 0x2b, 0x8e, 0x0e, 0x9d, // NOLINT + 0x43, 0x09, 0x6f, 0xa0, 0x8a, 0xce, 0x1f, 0x73, 0xc3, 0x85, 0xc0, 0xaf, 0x14, 0x1d, 0xd2, 0x69, 0x13, 0x10, 0xb4, // NOLINT + 0x74, 0xb6, 0x5c, 0xba, 0x40, 0xc4, 0x49, 0xab, 0x09, 0xa7, 0x44, 0xe8, 0x98, 0x2f, 0x7e, 0xc9, 0x71, 0x5c, 0x56, // NOLINT + 0x22, 0x37, 0x2e, 0x8d, 0x65, 0x26, 0x0b, 0x63, 0xe7, 0xfa, 0x8b, 0xb9, 0xec, 0x19, 0x13, 0x1b, 0x85, 0x63, 0x44, // NOLINT + 0xad, 0x9b, 0x56, 0x91, 0x52, 0xc7, 0x10, 0xb4, 0x01, 0xea, 0x15, 0xc8, 0x16, 0x1c, 0x5c, 0x9d, 0xc8, 0x65, 0x37, // NOLINT + 0x44, 0xe2, 0xc9, 0x77, 0x88, 0x54, 0x84, 0x39, 0xd8, 0x2b, 0x5a, 0x1d, 0x11, 0x92, 0x7e, 0x26, 0xde, 0xa3, 0xc5, // NOLINT + 0xdf, 0xae, 0xd8, 0x2d, 0x90, 0xa6, 0xad, 0x13, 0xe3, 0xd9, 0x6c, 0x34, 0x96, 0x3c, 0x24, 0xc4, 0x07, 0xe0, 0xff, // NOLINT + 0x3b, 0xf8, 0x74, 0xc5, 0x57, 0x7b, 0x27, 0xf3, 0x99, 0x6b, 0xb6, 0x0d, 0xad, 0xd4, 0x09, 0x4d, 0xb4, 0x44, 0x09, // NOLINT + 0x6d, 0x91, 0x19, 0xef, 0x21, 0x62, 0x3e, 0x05, 0x50, 0x18, 0x4c, 0x1c, 0xa8, 0x41, 0x12, 0x69, 0xd0, 0xe2, 0xec, // NOLINT + 0x40, 0xe0, 0xcb, 0x61, 0xdf, 0xff, 0x14, 0x23, 0x58, 0xc0, 0xec, 0xf6, 0xe0, 0xf8, 0x67, 0xf9, 0xd3, 0xfb, 0xd2, // NOLINT + 0xe0, 0x0a, 0xb9, 0xa1, 0x3e, 0xdc, 0xd5, 0x72, 0x8c, 0xf7, 0x39, 0xa8, 0xf7, 0x87, 0x42, 0xf2, 0x81, 0xb1, 0xdb, // NOLINT + 0x04, 0xa8, 0x76, 0x0d, 0x78, 0xe4, 0xfb, 0x8b, 0xd9, 0x9f, 0x8a, 0xc5, 0xcf, 0xfe, 0x82, 0x71, 0x3d, 0xba, 0x58, // NOLINT + 0x6c, 0x00, 0x21, 0xbd, 0xf4, 0x73, 0x88, 0x32, 0x8b, 0xec, 0x43, 0xf5, 0xc3, 0xf9, 0xfc, 0xf9, 0x1b, 0xb8, 0xd8, // NOLINT + 0xeb, 0xc0, 0xbf, 0x0c, 0x3e, 0x6d, 0xf0, 0xd1, 0x58, 0xe7, 0xf2, 0xa6, 0x75, 0xed, 0x77, 0xe1, 0x21, 0x5c, 0x2b, // NOLINT + 0x8e, 0x65, 0x3f, 0x0c, 0x84, 0x4e, 0x6a, 0x2d, 0xeb, 0x8c, 0xbf, 0x6b, 0x16, 0x12, 0x3e, 0x8c, 0x54, 0xaf, 0x45, // NOLINT + 0x62, 0xed, 0x1b, 0xf0, 0x8a, 0xca, 0x98, 0xeb, 0x38, 0x9c, 0x17, 0x47, 0x5b, 0xd3, 0x77, 0xe7, 0xc7, 0x07, 0x2e, // NOLINT + 0xc1, 0x9e, 0x55, 0xca, 0x89, 0xfb, 0x81, 0x8c, 0x54, 0xf1, 0x74, 0xc1, 0x09, 0x85, 0x36, 0x57, 0x82, 0xca, 0x27, // NOLINT + 0x55, 0xf7, 0xa0, 0x19, 0x05, 0x1d, 0x8f, 0xc4, 0x92, 0x53, 0x0b, 0x1c, 0xf6, 0x6a, 0x58, 0x56, 0xdf, 0x1e, 0xa6, // NOLINT + 0x9e, 0x1f, 0x2c, 0xf8, 0xe6, 0x92, 0x48, 0xa4, 0x12, 0x2b, 0xd7, 0x26, 0x77, 0x9a, 0x4a, 0x8c, 0xe9, 0xce, 0x96, // NOLINT + 0xf4, 0x1d, 0x10, 0xb6, 0xc3, 0xa6, 0xa6, 0xfb, 0xea, 0x1a, 0xd7, 0x76, 0xae, 0x9e, 0xd3, 0x1b, 0x1b, 0x37, 0x2e, // NOLINT + 0x5a, 0x15, 0x6f, 0x8f, 0x40, 0x47, 0x1d, 0x55, 0x5a, 0x03, 0xbd, 0xa1, 0xb5, 0xfd, 0x71, 0xa2, 0x8e, 0x6d, 0x44, // NOLINT + 0x4f, 0x78, 0x73, 0x76, 0x46, 0x66, 0xbc, 0x30, 0xf3, 0x74, 0xe7, 0xe9, 0xe0, 0x41, 0x52, 0x60, 0x5c, 0x8b, 0x25, // NOLINT + 0x13, 0xe4, 0x9f, 0xe7, 0xff, 0xd5, 0x6f, 0xc2, 0xaa, 0x42, 0xbe, 0xa2, 0x7a, 0x1a, 0xa7, 0xb6, 0xa0, 0x1c, 0xf2, // NOLINT + 0x1c, 0xae, 0x7c, 0xc1, 0x72, 0x94, 0x9e, 0xc5, 0xd1, 0x34, 0x3f, 0x43, 0x27, 0xe6, 0x90, 0x82, 0x04, 0xdf, 0x52, // NOLINT + 0x8b, 0xdb, 0x4c, 0x79, 0x93, 0xc4, 0x26, 0xea, 0xce, 0x77, 0xf1, 0xc0, 0xc0, 0x40, 0x60, 0x30, 0x18, 0x21, 0x1f, // NOLINT + 0xea, 0x48, 0x3b, 0x7d, 0xe9, 0x8c, 0xa6, 0x66, 0xdb, 0x85, 0xc3, 0xdd, 0x34, 0x5b, 0x7a, 0xbb, 0xae, 0x2d, 0xe4, // NOLINT + 0x96, 0xb7, 0x2b, 0x0a, 0x1c, 0xc5, 0xfa, 0x61, 0x40, 0x67, 0x58, 0xde, 0xa2, 0x45, 0x78, 0x2f, 0xaf, 0x40, 0xd9, // NOLINT + 0x08, 0x36, 0x37, 0x7f, 0x92, 0x7b, 0xe9, 0x37, 0xc8, 0xff, 0x91, 0x49, 0x6c, 0x8d, 0x7d, 0xb8, 0x35, 0xfe, 0x57, // NOLINT + 0x57, 0xd6, 0x8c, 0x58, 0x61, 0xaa, 0xa5, 0x6e, 0x94, 0x74, 0x19, 0x86, 0x85, 0xe0, 0x17, 0x4d, 0xbb, 0x96, 0xa4, // NOLINT + 0x9f, 0xb6, 0xc2, 0xc5, 0x7c, 0xea, 0x66, 0xd6, 0xc3, 0xe0, 0x41, 0xd4, 0x76, 0x1e, 0xcf, 0x60, 0x82, 0xae, 0xd0, // NOLINT + 0x75, 0x9e, 0x1f, 0x99, 0xc5, 0x9b, 0x45, 0xde, 0xa3, 0x3a, 0x0a, 0x68, 0x25, 0x02, 0xde, 0x03, 0xec, 0x83, 0x30, // NOLINT + 0xb5, 0x21, 0xc6, 0x14, 0xe4, 0x53, 0x28, 0xe5, 0x79, 0xec, 0xbf, 0x61, 0x5b, 0x15, 0xda, 0x44, 0x8b, 0xd5, 0xf6, // NOLINT + 0xfb, 0xe9, 0x51, 0x1c, 0xbc, 0x80, 0xc0, 0x6d, 0xf6, 0x4b, 0x28, 0x99, 0x48, 0xf4, 0x0b, 0xd6, 0x5f, 0xc1, 0x7f, // NOLINT + 0x80, 0xcd, 0xc8, 0x6e, 0x01, 0x2f, 0x01, 0xd7, 0x53, 0x0d, 0x7e, 0xb3, 0xd6, 0x36, 0x84, 0x24, 0x47, 0x78, 0x0f, // NOLINT + 0x7d, 0x84, 0x98, 0x2e, 0x2d, 0xa3, 0xfd, 0x74, 0x1b, 0x1d, 0x9e, 0xef, 0x00, 0x46, 0x54, 0xe9, 0xc3, 0xc9, 0x92, // NOLINT + 0xbc, 0x8f, 0x3f, 0xbe, 0x59, 0x4a, 0xc1, 0x69, 0x13, 0x0c, 0x2d, 0x48, 0xb2, 0x0c, 0x7b, 0xd3, 0xb6, 0xb9, 0xf0, // NOLINT + 0x69, 0xf0, 0x14, 0x76, 0x17, 0x21, 0x47, 0xc8, 0xa1, 0x0c, 0x3f, 0x6d, 0x8b, 0x80, 0x0a, 0xc0, 0x71, 0xaa, 0xe5, // NOLINT + 0xdd, 0x54, 0x36, 0xfb, 0xbf, 0x01, 0x02, 0x9d, 0x70, 0x74, 0xcd, 0x2a, 0x03, 0x15, 0x00, 0x00, 0x00, 0x00, 0x49, // NOLINT + 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82}; // NOLINT +/* clang-format on */ diff --git a/tests/runtime_tests.cpp b/tests/runtime_tests.cpp deleted file mode 100644 index c5aaa74..0000000 --- a/tests/runtime_tests.cpp +++ /dev/null @@ -1,193 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "gtest/gtest.h" - -extern std::string aws_prefix; - -namespace { - -using namespace Aws::Lambda; - -const char S3BUCKET[] = "aws-lambda-cpp-tests"; -const char S3KEY[] = "lambda-test-fun.zip"; - -struct LambdaRuntimeTest : public ::testing::Test { - LambdaClient m_lambda_client; - Aws::IAM::IAMClient m_iam_client; - static Aws::Client::ClientConfiguration create_iam_config() - { - Aws::Client::ClientConfiguration config; - config.requestTimeoutMs = 15 * 1000; - config.region = Aws::Region::US_EAST_1; - return config; - } - - static Aws::Client::ClientConfiguration create_lambda_config() - { - Aws::Client::ClientConfiguration config; - config.requestTimeoutMs = 15 * 1000; - config.region = Aws::Environment::GetEnv("AWS_REGION"); - return config; - } - - static Aws::String build_resource_name(Aws::String const& name) - { - return aws_prefix.c_str() + name; // NOLINT - } - - LambdaRuntimeTest() : m_lambda_client(create_lambda_config()), m_iam_client(create_iam_config()) {} - - ~LambdaRuntimeTest() override - { - // clean up in case we exited one test abnormally - delete_function(build_resource_name("echo_success"), false /*assert*/); - delete_function(build_resource_name("echo_failure"), false /*assert*/); - delete_function(build_resource_name("binary_response"), false /*assert*/); - } - - Aws::String get_role_arn(Aws::String const& role_name) - { - using namespace Aws::IAM; - using namespace Aws::IAM::Model; - GetRoleRequest request; - request.WithRoleName(role_name); - auto outcome = m_iam_client.GetRole(request); - EXPECT_TRUE(outcome.IsSuccess()); - if (outcome.IsSuccess()) { - return outcome.GetResult().GetRole().GetArn(); - } - return {}; - } - - void create_function(Aws::String const& function_name, Aws::String const& handler_name) - { - Model::CreateFunctionRequest createFunctionRequest; - createFunctionRequest.SetHandler(handler_name); - createFunctionRequest.SetFunctionName(function_name); - // I ran into eventual-consistency issues when creating the role dynamically as part of the test. - createFunctionRequest.SetRole(get_role_arn("integration-tests")); - Model::FunctionCode funcode; - funcode.WithS3Bucket(S3BUCKET).WithS3Key(build_resource_name(S3KEY)); - createFunctionRequest.SetCode(funcode); - createFunctionRequest.SetRuntime(Aws::Lambda::Model::Runtime::provided); - - auto outcome = m_lambda_client.CreateFunction(createFunctionRequest); - ASSERT_TRUE(outcome.IsSuccess()) << "Failed to create function " << function_name; - } - - void delete_function(Aws::String const& function_name, bool assert = true) - { - Model::DeleteFunctionRequest deleteFunctionRequest; - deleteFunctionRequest.SetFunctionName(function_name); - auto outcome = m_lambda_client.DeleteFunction(deleteFunctionRequest); - if (assert) { - ASSERT_TRUE(outcome.IsSuccess()) << "Failed to delete function " << function_name; - } - } -}; - -TEST_F(LambdaRuntimeTest, echo_success) -{ - Aws::String const funcname = build_resource_name("echo_success"); - char const payloadContent[] = "Hello, Lambda!"; - create_function(funcname, "echo_success" /*handler_name*/); - Model::InvokeRequest invokeRequest; - invokeRequest.SetFunctionName(funcname); - invokeRequest.SetInvocationType(Model::InvocationType::RequestResponse); - invokeRequest.SetContentType("application/json"); - - std::shared_ptr payload = Aws::MakeShared("FunctionTest"); - Aws::Utils::Json::JsonValue jsonPayload; - jsonPayload.WithString("barbaz", payloadContent); - *payload << jsonPayload.View().WriteCompact(); - invokeRequest.SetBody(payload); - - Model::InvokeOutcome invokeOutcome = m_lambda_client.Invoke(invokeRequest); - EXPECT_TRUE(invokeOutcome.IsSuccess()); - Aws::StringStream output; - if (!invokeOutcome.IsSuccess()) { - delete_function(funcname); - return; - } - EXPECT_EQ(200, invokeOutcome.GetResult().GetStatusCode()); - EXPECT_TRUE(invokeOutcome.GetResult().GetFunctionError().empty()); - auto const jsonResponse = Aws::Utils::Json::JsonValue(invokeOutcome.GetResult().GetPayload()); - EXPECT_TRUE(jsonResponse.WasParseSuccessful()); - EXPECT_STREQ(payloadContent, jsonResponse.View().GetString("barbaz").c_str()); - delete_function(funcname); -} - -TEST_F(LambdaRuntimeTest, echo_unicode) -{ - Aws::String const funcname = build_resource_name("echo_success"); // re-use the echo method but with Unicode input - char const payloadContent[] = "画像は1000語の価値がある"; - create_function(funcname, "echo_success" /*handler_name*/); - Model::InvokeRequest invokeRequest; - invokeRequest.SetFunctionName(funcname); - invokeRequest.SetInvocationType(Model::InvocationType::RequestResponse); - invokeRequest.SetContentType("application/json"); - - std::shared_ptr payload = Aws::MakeShared("FunctionTest"); - Aws::Utils::Json::JsonValue jsonPayload; - jsonPayload.WithString("UnicodeText", payloadContent); - *payload << jsonPayload.View().WriteCompact(); - invokeRequest.SetBody(payload); - - Model::InvokeOutcome invokeOutcome = m_lambda_client.Invoke(invokeRequest); - EXPECT_TRUE(invokeOutcome.IsSuccess()); - Aws::StringStream output; - if (!invokeOutcome.IsSuccess()) { - delete_function(funcname); - return; - } - EXPECT_EQ(200, invokeOutcome.GetResult().GetStatusCode()); - EXPECT_TRUE(invokeOutcome.GetResult().GetFunctionError().empty()); - auto const jsonResponse = Aws::Utils::Json::JsonValue(invokeOutcome.GetResult().GetPayload()); - EXPECT_TRUE(jsonResponse.WasParseSuccessful()); - EXPECT_STREQ(payloadContent, jsonResponse.View().GetString("UnicodeText").c_str()); - delete_function(funcname); -} - -TEST_F(LambdaRuntimeTest, echo_failure) -{ - Aws::String const funcname = build_resource_name("echo_failure"); - create_function(funcname, "echo_failure" /*handler_name*/); - Model::InvokeRequest invokeRequest; - invokeRequest.SetFunctionName(funcname); - invokeRequest.SetInvocationType(Model::InvocationType::RequestResponse); - - Model::InvokeOutcome invokeOutcome = m_lambda_client.Invoke(invokeRequest); - EXPECT_TRUE(invokeOutcome.IsSuccess()); - EXPECT_EQ(200, invokeOutcome.GetResult().GetStatusCode()); - EXPECT_STREQ("Unhandled", invokeOutcome.GetResult().GetFunctionError().c_str()); - delete_function(funcname); -} - -TEST_F(LambdaRuntimeTest, binary_response) -{ - Aws::String const funcname = build_resource_name("binary_response"); - unsigned long constexpr expected_length = 1451; - create_function(funcname, "binary_response" /*handler_name*/); - Model::InvokeRequest invokeRequest; - invokeRequest.SetFunctionName(funcname); - invokeRequest.SetInvocationType(Model::InvocationType::RequestResponse); - - Model::InvokeOutcome invokeOutcome = m_lambda_client.Invoke(invokeRequest); - EXPECT_TRUE(invokeOutcome.IsSuccess()); - EXPECT_EQ(200, invokeOutcome.GetResult().GetStatusCode()); - EXPECT_TRUE(invokeOutcome.GetResult().GetFunctionError().empty()); - EXPECT_EQ(expected_length, invokeOutcome.GetResult().GetPayload().tellp()); - delete_function(funcname); -} -} // namespace diff --git a/tests/unit/no_op_test.cpp b/tests/unit/no_op_test.cpp new file mode 100644 index 0000000..c9a3b7d --- /dev/null +++ b/tests/unit/no_op_test.cpp @@ -0,0 +1,6 @@ +#include + +TEST(noop, dummy_test) +{ + ASSERT_EQ(0, 0); +}