diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7fa0ebc --- /dev/null +++ b/.clang-format @@ -0,0 +1,9 @@ +BasedOnStyle: Mozilla +Language: Cpp +UseTab: Never +AlwaysBreakAfterReturnType: None +AlwaysBreakAfterDefinitionReturnType: None +AllowShortFunctionsOnASingleLine: None +FixNamespaceComments: true +SpacesBeforeTrailingComments: 2 +ColumnLimit: 80 \ No newline at end of file diff --git a/.github/workflows/0.0.1.yml b/.github/workflows/0.0.1.yml new file mode 100644 index 0000000..ee2435a --- /dev/null +++ b/.github/workflows/0.0.1.yml @@ -0,0 +1,11 @@ +name: 🚀 Deploy 0.0.1 + +on: + workflow_dispatch: + +jobs: + deploy: + uses: libhal/ci/.github/workflows/deploy_all.yml@5.x.y + with: + version: 0.0.1 + secrets: inherit diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..769bb8c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,39 @@ +# Copyright 2024 Khalil Estell +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +name: ✅ CI + +on: + workflow_dispatch: + pull_request: + push: + branches: + - main + schedule: + - cron: "0 12 * * 0" + +jobs: + ci: + uses: libhal/ci/.github/workflows/library_check.yml@5.x.y + secrets: inherit + + deploy_cortex-m4f_check: + uses: libhal/ci/.github/workflows/deploy.yml@5.x.y + with: + arch: cortex-m4f + os: baremetal + compiler: gcc + compiler_version: 12.3 + compiler_package: arm-gnu-toolchain + secrets: inherit diff --git a/.gitignore b/.gitignore index 259148f..b664b01 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,6 @@ *.exe *.out *.app + +build/** +*/CMakeUserPresets.json \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index e69de29..17d2880 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -0,0 +1,37 @@ +# Copyright 2024 Khalil Estell +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.15) + +project(libhal-exceptions LANGUAGES CXX) + + +if("${RUNTIME}" STREQUAL "ARM_CORTEX_GCC") +set(SOURCE_LIST src/arm_cortex/gcc/impl.cpp) +elseif("${RUNTIME}" STREQUAL "ARM_CORTEX_ESTELL") +set(SOURCE_LIST + src/arm_cortex/estell/exception.cpp + src/arm_cortex/estell/wrappers.cpp +) +else() +message(FATAL "Invalid Exception RUNTIME: '${RUNTIME}' provided!") +endif() + +libhal_make_library( + LIBRARY_NAME libhal-exceptions + + SOURCES + src/control.cpp + ${SOURCE_LIST} +) diff --git a/conanfile.py b/conanfile.py index e69de29..aa82288 100644 --- a/conanfile.py +++ b/conanfile.py @@ -0,0 +1,118 @@ +# Copyright 2024 Khalil Estell +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from conan import ConanFile +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout +from conan.tools.files import copy +from conan.tools.build import check_min_cppstd +from conan.errors import ConanInvalidConfiguration +import os + +required_conan_version = ">=2.0.14" + + +class libhal_exceptions_conan(ConanFile): + name = "libhal-exceptions" + license = "Apache-2.0" + url = "https://github.com/conan-io/conan-center-index" + homepage = "https://github.com/libhal/libhal-exceptions" + description = ( + "Exception handling runtime support for the libhal ecosystem.") + topics = ("exceptions", "error", "terminate", "unexpected") + settings = "compiler", "build_type", "os", "arch" + generators = "CMakeDeps", "CMakeToolchain", "VirtualBuildEnv" + exports_sources = ("include/*", "tests/*", "LICENSE", + "CMakeLists.txt", "src/*") + options = { + "default_allocator": [True, False], + "runtime": [ + "builtin", + "estell", + ] + } + default_options = { + "default_allocator": True, + "runtime": "builtin", + } + + @property + def _min_cppstd(self): + return "20" + + @property + def _compilers_minimum_version(self): + return { + "gcc": "11", + "clang": "14", + } + + @property + def _is_arm_cortex(self): + return str(self.settings.arch).startswith("cortex-") + + @property + def _runtime_select(self): + if self._is_arm_cortex() and self.options.runtime == "builtin": + return "ARM_CORTEX_GCC" + elif self._is_arm_cortex() and self.options.runtime == "estell": + return "ARM_CORTEX_ESTELL" + + def validate(self): + if self.settings.get_safe("compiler.cppstd"): + check_min_cppstd(self, self._min_cppstd) + + # Remove this when Estell impl is ready for beta testing + if self.options.runtime != "builtin": + raise ConanInvalidConfiguration( + "Only the 'builtin' exception runtime is supported currently") + + def layout(self): + cmake_layout(self) + + def build_requirements(self): + self.tool_requires("cmake/3.27.1") + self.tool_requires("libhal-cmake-util/[^4.0.3]") + + def build(self): + cmake = CMake(self) + cmake.configure(variables={"RUNTIME": "ARM_CORTEX_GCC"}) + cmake.build() + + def package(self): + copy(self, + "LICENSE", + dst=os.path.join(self.package_folder, "licenses"), + src=self.source_folder) + copy(self, + "*.h", + dst=os.path.join(self.package_folder, "include"), + src=os.path.join(self.source_folder, "include")) + copy(self, + "*.hpp", + dst=os.path.join(self.package_folder, "include"), + src=os.path.join(self.source_folder, "include")) + + cmake = CMake(self) + cmake.install() + + def package_info(self): + self.cpp_info.libs = ["libhal-exceptions"] + self.cpp_info.set_property("cmake_target_name", "libhal::exceptions") + + # Keep this for now, will update this for the runtime select + self.cpp_info.exelinkflags = [ + "-Wl,--wrap=__cxa_allocate_exception", + "-Wl,--wrap=__cxa_free_exception", + "-Wl,--wrap=__cxa_call_unexpected", + ] diff --git a/include/exceptions.hpp b/include/exceptions.hpp deleted file mode 100644 index 6f70f09..0000000 --- a/include/exceptions.hpp +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/include/libhal-exceptions/control.hpp b/include/libhal-exceptions/control.hpp new file mode 100644 index 0000000..f5c84d1 --- /dev/null +++ b/include/libhal-exceptions/control.hpp @@ -0,0 +1,113 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +namespace hal { +/** + * @brief Interface for an object that allocates memory specifically for + * exceptions + * + */ +class exception_allocator +{ +public: + /** + * @brief Allocate/retrieve memory for the exception object allocation + * + * If memory has run out, this function must return an empty span. + * + * @param p_size - Amount of memory to be allocated + * @return std::span - block of memory equal to or greater than + * the size of p_size or and empty span if no memory is available. + */ + std::span allocate(std::size_t p_size) noexcept + { + return do_allocate(p_size); + } + + /** + * @brief Deallocate the memory for the exception object + * + * @param p_exception_object - pointer to the allocated exception object + */ + void deallocate(void* p_exception_object) noexcept + { + do_deallocate(p_exception_object); + } + +private: + virtual std::span do_allocate(std::size_t p_size) noexcept = 0; + virtual void do_deallocate(void* p_exception_object) noexcept = 0; +}; + +/** + * @brief Set the global exception allocator function + * + * More details on how you should use this API to come in the future. + * + * @param p_allocator - exception memory allocator implementation + */ +void set_exception_allocator(exception_allocator& p_allocator) noexcept; + +/** + * @brief Set the terminate handler + * + * @param p_terminate_handler - new global terminate handler + */ +std::terminate_handler set_terminate( + std::terminate_handler p_terminate_handler) noexcept; + +/** + * @brief Get the terminate handler + * + * @return std::terminate_handler - the currently set terminate handler + */ +std::terminate_handler get_terminate() noexcept; + +/** + * @brief Simple single threaded exception allocator + * + * @tparam size - size of the exception object memory buffer. If this is set too + * small (less than 128 bytes), then it is likely that the memory will not be + * enough for any exception runtime and will result in terminate being called. + */ +template +class single_thread_exception_allocator : public exception_allocator +{ +private: + std::span do_allocate(std::size_t p_size) noexcept override + { + if (m_allocated || p_size > m_buffer.size()) { + return {}; + } + m_allocated = true; + return m_buffer; + } + + void do_deallocate( + [[maybe_unused]] void* p_exception_object) noexcept override + { + m_allocated = false; + } + + std::array m_buffer{}; + bool m_allocated = false; +}; +} // namespace hal \ No newline at end of file diff --git a/src/exception.cpp b/src/arm_cortex/estell/exception.cpp similarity index 87% rename from src/exception.cpp rename to src/arm_cortex/estell/exception.cpp index 9ccc02b..71ae8f5 100644 --- a/src/exception.cpp +++ b/src/arm_cortex/estell/exception.cpp @@ -1,3 +1,17 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + #include #include #include @@ -22,45 +36,40 @@ using instructions_t = std::array; namespace { exception_ptr active_exception = nullptr; std::array exception_buffer{}; -} // namespace +} // namespace -su16_t* -get_su16(void* p_ptr) +su16_t* get_su16(void* p_ptr) { return reinterpret_cast(p_ptr); } -lu_t* -get_lu(void* p_ptr) +lu_t* get_lu(void* p_ptr) { return reinterpret_cast(p_ptr); } -exception_ptr -current_exception() noexcept +exception_ptr current_exception() noexcept { return active_exception; } -[[gnu::always_inline]] void -capture_cpu_core(ke::cortex_m_cpu& p_cpu_core) +[[gnu::always_inline]] void capture_cpu_core(ke::cortex_m_cpu& p_cpu_core) { // TODO(kammce): test this - asm volatile("mrs r0, MSP\n" // Move Main Stack Pointer to r0 - "stmia %0, {r0-r12}\n" // Store r0 to r12 into the array - "mov r0, SP\n" // Move SP to r0 - "str r0, [%0, #52]\n" // Store SP at the appropriate index - "mov r0, LR\n" // Move LR to r0 - "str r0, [%0, #56]\n" // Store LR at the appropriate index - "mov r0, PC\n" // Move PC to r0 - "str r0, [%0, #60]\n" // Store PC at the appropriate index - : // no output - : "r"(&p_cpu_core) // input is the address of the array - : "r0" // r0 is being modified + asm volatile("mrs r0, MSP\n" // Move Main Stack Pointer to r0 + "stmia %0, {r0-r12}\n" // Store r0 to r12 into the array + "mov r0, SP\n" // Move SP to r0 + "str r0, [%0, #52]\n" // Store SP at the appropriate index + "mov r0, LR\n" // Move LR to r0 + "str r0, [%0, #56]\n" // Store LR at the appropriate index + "mov r0, PC\n" // Move PC to r0 + "str r0, [%0, #60]\n" // Store PC at the appropriate index + : // no output + : "r"(&p_cpu_core) // input is the address of the array + : "r0" // r0 is being modified ); } -bool -index_entry_t::has_inlined_personality() const +bool index_entry_t::has_inlined_personality() const { // 31st bit is `1` when the personality/unwind information is inlined, other // otherwise, personality_offset is an offset. @@ -68,20 +77,17 @@ index_entry_t::has_inlined_personality() const return hal::bit_extract(personality_offset); } -bool -index_entry_t::is_noexcept() const +bool index_entry_t::is_noexcept() const { return personality_offset == 0x1; } -const std::uint32_t* -index_entry_t::personality() const +const std::uint32_t* index_entry_t::personality() const { return to_absolute_address_ptr(&personality_offset); } -const std::uint32_t* -index_entry_t::lsda_data() const +const std::uint32_t* index_entry_t::lsda_data() const { constexpr auto personality_type = hal::bit_mask::from<24, 27>(); // +1 to skip the prel31 offset to the personality function @@ -98,8 +104,7 @@ index_entry_t::lsda_data() const return header + 3; } -const std::uint32_t* -index_entry_t::descriptor_start() const +const std::uint32_t* index_entry_t::descriptor_start() const { constexpr auto type_mask = hal::bit_mask{ .position = 24, .width = 8 }; @@ -117,8 +122,7 @@ index_entry_t::descriptor_start() const return personality_address + 2; } -function_t -index_entry_t::function() const +function_t index_entry_t::function() const { return function_t(to_absolute_address(&function_offset)); } @@ -140,21 +144,18 @@ struct index_less_than } }; -std::span -get_arm_exception_index() +std::span get_arm_exception_index() { return { reinterpret_cast(&__exidx_start), reinterpret_cast(&__exidx_end) }; } -[[gnu::used]] std::span -get_arm_exception_table() +[[gnu::used]] std::span get_arm_exception_table() { return { &__extab_start, &__extab_end }; } -const index_entry_t& -get_index_entry(std::uint32_t p_program_counter) +const index_entry_t& get_index_entry(std::uint32_t p_program_counter) { const auto index_table = get_arm_exception_index(); const auto& index = std::upper_bound(index_table.begin(), @@ -168,8 +169,7 @@ get_index_entry(std::uint32_t p_program_counter) return *(index - 1); } -void -pop_registers(cortex_m_cpu& p_cpu, std::uint32_t mask) +void pop_registers(cortex_m_cpu& p_cpu, std::uint32_t mask) { // The mask may not demand that the stack pointer be popped, but the // stack pointer will still need to be popped anyway, so this check @@ -190,8 +190,7 @@ pop_registers(cortex_m_cpu& p_cpu, std::uint32_t mask) p_cpu.sp = stack_pointer; } -std::uint32_t -read_uleb128(volatile const std::uint8_t** p_ptr) +std::uint32_t read_uleb128(volatile const std::uint8_t** p_ptr) { std::uint32_t result = 0; std::uint8_t shift_amount = 0; @@ -214,8 +213,7 @@ read_uleb128(volatile const std::uint8_t** p_ptr) } template -volatile const T* -as(volatile const void* p_ptr) +volatile const T* as(volatile const void* p_ptr) { return reinterpret_cast(p_ptr); } @@ -246,23 +244,21 @@ enum class personality_encoding : std::uint8_t omit = 0xff, }; -personality_encoding -operator&(const personality_encoding& p_encoding, const std::uint8_t& p_byte) +personality_encoding operator&(const personality_encoding& p_encoding, + const std::uint8_t& p_byte) { return static_cast( static_cast(p_encoding) & p_byte); } -personality_encoding -operator&(const personality_encoding& p_encoding, - const personality_encoding& p_byte) +personality_encoding operator&(const personality_encoding& p_encoding, + const personality_encoding& p_byte) { return static_cast( static_cast(p_encoding) & static_cast(p_byte)); } -std::uintptr_t -read_encoded_data(volatile const std::uint8_t** p_data, - personality_encoding p_encoding) +std::uintptr_t read_encoded_data(volatile const std::uint8_t** p_data, + personality_encoding p_encoding) { volatile const std::uint8_t* ptr = *p_data; std::uintptr_t result = 0; @@ -379,7 +375,10 @@ class action_decoder return to_type_info(const_cast(current_type)); } - std::uint8_t filter() { return m_filter; } + std::uint8_t filter() + { + return m_filter; + } private: volatile const std::uint32_t* m_type_table_end = nullptr; @@ -389,8 +388,7 @@ class action_decoder int global_int = 0; -void -perform_gcc_lsda_cleanup_catch_phase(exception_object& p_exception_object) +void perform_gcc_lsda_cleanup_catch_phase(exception_object& p_exception_object) { volatile const std::uint8_t* lsda_data = reinterpret_cast( @@ -447,9 +445,8 @@ perform_gcc_lsda_cleanup_catch_phase(exception_object& p_exception_object) } } -void -unwind_frame(const instructions_t& p_instructions, - exception_object& p_exception_object) +void unwind_frame(const instructions_t& p_instructions, + exception_object& p_exception_object) { auto& virtual_cpu = p_exception_object.cpu; bool set_pc = false; @@ -501,7 +498,7 @@ unwind_frame(const instructions_t& p_instructions, // "10101nnn" (Pop r4-r[4+nnn], r14) // const std::uint32_t* sp_ptr = *virtual_cpu.sp; - int nnn = *instruction & 0b111; // Extract the last 3 bits + int nnn = *instruction & 0b111; // Extract the last 3 bits switch (nnn) { case 7: virtual_cpu[11] = sp_ptr[7]; @@ -579,8 +576,7 @@ unwind_frame(const instructions_t& p_instructions, } } -instructions_t -create_instructions_from_entry(const index_entry_t& p_entry) +instructions_t create_instructions_from_entry(const index_entry_t& p_entry) { constexpr auto personality_type = hal::bit_mask::from<24, 27>(); constexpr auto generic = hal::bit_mask::from<31>(); @@ -654,8 +650,7 @@ create_instructions_from_entry(const index_entry_t& p_entry) return instructions; } -void -raise_exception(exception_object& p_exception_object) +void raise_exception(exception_object& p_exception_object) { while (true) { switch (p_exception_object.cache.state()) { @@ -690,18 +685,17 @@ raise_exception(exception_object& p_exception_object) } } } -} // namespace ke +} // namespace ke -[[noreturn]] void -terminate_halt() noexcept +[[noreturn]] void terminate_halt() noexcept { while (true) { continue; } } -namespace __cxxabiv1 { // NOLINT -std::terminate_handler __terminate_handler = terminate_halt; // NOLINT +namespace __cxxabiv1 { // NOLINT +std::terminate_handler __terminate_handler = terminate_halt; // NOLINT } extern "C" @@ -722,7 +716,7 @@ extern "C" ke::exception_buffer.fill(0); } - void __wrap___cxa_call_unexpected(void*) // NOLINT + void __wrap___cxa_call_unexpected(void*) // NOLINT { std::terminate(); } @@ -813,4 +807,4 @@ extern "C" ke::raise_exception(exception_object); std::terminate(); } -} // extern "C" \ No newline at end of file +} // extern "C" \ No newline at end of file diff --git a/src/arm_cortex/estell/internal.hpp b/src/arm_cortex/estell/internal.hpp new file mode 100644 index 0000000..4249d21 --- /dev/null +++ b/src/arm_cortex/estell/internal.hpp @@ -0,0 +1,347 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include +#include +#include +#include + +#include "bit.hpp" + +namespace ke { +using exception_ptr = void*; +exception_ptr current_exception() noexcept; + +using destructor_t = void(void*); + +struct register_t +{ + std::uint32_t data; + + register_t() + : data(0) + { + } + + register_t(std::uint32_t p_data) + : data(p_data) + { + } + + register_t(const void* p_data) + : data(reinterpret_cast(p_data)) + { + } + + operator std::uint32_t() const + { + return data; + } + + const std::uint32_t* operator*() + { + return reinterpret_cast(data); + } +}; + +namespace arm_ehabi { +constexpr std::uint8_t finish = 0xb0; +constexpr std::uint32_t end_descriptor = 0x0000'0000; +constexpr std::uint32_t su16_mask = 0b1111'1111'1111'1110; +} // namespace arm_ehabi + +// This is only to make GDB debugging easier, the functions are never actually +// called so it is not important to include the correct types. +struct function_t +{ + using callable_t = void(); + + callable_t* address; + + function_t(std::uint32_t p_address) + : address(reinterpret_cast(p_address)) + { + } + + operator std::uint32_t() + { + return reinterpret_cast(address); + } + + operator void*() + { + return reinterpret_cast(address); + } + + bool operator<(const function_t& p_other) const + { + return address < p_other.address; + } + + bool operator==(const function_t& p_other) const + { + return address == p_other.address; + } +}; + +struct index_entry_t +{ + std::uint32_t function_offset; + std::uint32_t personality_offset; + + bool has_inlined_personality() const; + bool is_noexcept() const; + bool short_instructions() const; + const std::uint32_t* personality() const; + const std::uint32_t* lsda_data() const; + const std::uint32_t* descriptor_start() const; + function_t function() const; +}; + +struct cortex_m_cpu +{ + register_t r0; + register_t r1; + register_t r2; + register_t r3; + register_t r4; + register_t r5; + register_t r6; + register_t r7; + register_t r8; + register_t r9; + register_t r10; + register_t r11; + register_t ip; + register_t sp; + register_t lr; + register_t pc; + + register_t& operator[](size_t p_size) + { + using register_file = std::array; + static_assert(sizeof(cortex_m_cpu) == sizeof(register_file)); + static_assert(sizeof(std::uint32_t) == sizeof(register_t)); + + register_file* file = reinterpret_cast(this); + return (*file)[p_size]; + } +}; + +namespace su16 { +constexpr auto instruction0 = hal::bit_mask{ .position = 16, .width = 8 }; +constexpr auto instruction1 = hal::bit_mask{ .position = 8, .width = 8 }; +constexpr auto instruction2 = hal::bit_mask{ .position = 0, .width = 8 }; +} // namespace su16 + +namespace lu16_32 { +constexpr auto length = hal::bit_mask::from<16, 23>(); +constexpr auto instruction0 = hal::bit_mask::from<15, 8>(); +constexpr auto instruction1 = hal::bit_mask::from<7, 0>(); +constexpr auto instruction2 = hal::bit_mask::from<31, 24>(); +constexpr auto instruction3 = hal::bit_mask::from<23, 16>(); +constexpr auto instruction4 = hal::bit_mask::from<15, 8>(); +constexpr auto instruction5 = hal::bit_mask::from<7, 0>(); +constexpr auto instruction6 = hal::bit_mask::from<24, 31>(); +} // namespace lu16_32 + +enum class runtime_state : std::uint8_t +{ + get_next_frame = 0, + gcc_lsda_cleanup_catch_phase = 1, + unwind_frame = 2, +}; + +struct cache_t +{ + std::uint32_t state_and_rel_address; + const index_entry_t* entry_ptr = nullptr; + + static constexpr auto state_mask = hal::bit_mask{ + .position = 24, + .width = 4, + }; + static constexpr auto rethrown_mask = hal::bit_mask{ + .position = 28, + .width = 1, + }; + static constexpr auto relative_address_mask = hal::bit_mask{ + .position = 0, + .width = 24, + }; + + constexpr runtime_state state() const + { + auto state_integer = hal::bit_extract(state_and_rel_address); + return static_cast(state_integer); + } + + constexpr std::uint32_t relative_address() const + { + return hal::bit_extract(state_and_rel_address); + } + + constexpr void state(runtime_state p_state) + { + auto state_int = static_cast(p_state); + hal::bit_modify(state_and_rel_address).insert(state_int); + } + + constexpr void rethrown(bool p_rethrown) + { + hal::bit_modify(state_and_rel_address).insert(p_rethrown); + } + + constexpr bool rethrown() const + { + return hal::bit_extract(state_and_rel_address); + } + + constexpr void relative_address(std::uint32_t p_rel_address) + { + hal::bit_modify(state_and_rel_address) + .insert(p_rel_address); + } +}; + +struct exception_object +{ + cortex_m_cpu cpu{}; + std::type_info* type_info = nullptr; + destructor_t* destructor = nullptr; + cache_t cache{}; +}; + +constexpr size_t exception_object_size = sizeof(exception_object); + +exception_object& get_exception_object(void* p_thrown_exception); + +inline exception_object& extract_exception_object(void* p_thrown_exception) +{ + auto thrown_address = reinterpret_cast(p_thrown_exception); + auto start_of_exception_object = thrown_address - sizeof(exception_object); + return *reinterpret_cast(start_of_exception_object); +} + +inline void* extract_thrown_object(void* p_exception_object) +{ + auto object_address = reinterpret_cast(p_exception_object); + auto start_of_thrown = object_address + sizeof(exception_object); + return reinterpret_cast(start_of_thrown); +} + +[[gnu::always_inline]] inline void restore_cpu_core( + ke::cortex_m_cpu& p_cpu_core) +{ + asm volatile( + "ldr r0, [%[regs], #0]\n" // Load R0 + "ldr r1, [%[regs], #4]\n" // Load R1 + "ldr r2, [%[regs], #8]\n" // Load R2 + // "ldr r3, [%[regs], #12]\n" // Load R3 + "ldr r4, [%[regs], #16]\n" // Load R4 + "ldr r5, [%[regs], #20]\n" // Load R5 + "ldr r6, [%[regs], #24]\n" // Load R6 + "ldr r7, [%[regs], #28]\n" // Load R7 + "ldr r8, [%[regs], #32]\n" // Load R8 + "ldr r9, [%[regs], #36]\n" // Load R9 + "ldr r10, [%[regs], #40]\n" // Load R10 + "ldr r11, [%[regs], #44]\n" // Load R11 + "ldr r12, [%[regs], #48]\n" // Load R12 + // Stack Pointer (R13/SP) and Link Register (R14/LR) require special + // handling + "ldr sp, [%[regs], #52]\n" // Load SP + "ldr lr, [%[regs], #56]\n" // Load LR + // PC and xPSR restoration is more complex due to ARM's execution state and + // alignment requirements Directly loading PC can be dangerous and is + // typically managed through a pop or an exception return mechanism + "ldr pc, [%[regs], #60]\n" + : + : [regs] "r"(&p_cpu_core) + : "memory", + "r0", + "r1", + "r2", + // "r3", + "r4", + "r5", + "r6", + "r7", + "fp", + "r8", + "r9", + "r10", + "r11", + "r12"); +} + +constexpr std::uint32_t to_absolute_address(const void* p_object) +{ + constexpr auto signed_bit_31 = hal::bit_mask::from<30>(); + constexpr auto signed_bit_32 = hal::bit_mask::from<31>(); + auto object_address = reinterpret_cast(p_object); + auto offset = *reinterpret_cast(p_object); + + // Sign extend the offset to 32-bits + if (hal::bit_extract(offset)) { + hal::bit_modify(offset).set(); + } else { + hal::bit_modify(offset).clear(); + } + + auto signed_offset = static_cast(offset); + std::int32_t final_address = object_address + signed_offset; + return static_cast(final_address); +} + +[[gnu::used]] inline std::uint32_t runtime_to_absolute_address( + const void* p_object) +{ + return to_absolute_address(p_object); +} + +inline std::uint32_t* to_absolute_address_ptr(const void* p_object) +{ + return reinterpret_cast(to_absolute_address(p_object)); +} +} // namespace ke + +struct [[gnu::packed]] su16_t +{ + std::uint8_t instruction0 : 8; + std::uint8_t instruction1 : 8; + std::uint8_t instruction2 : 8; + std::uint8_t type : 4; + std::uint8_t reserved : 3; + std::uint8_t handler_data_flag : 1; +}; + +struct [[gnu::packed]] lu_t +{ + std::uint8_t instruction0 : 8; + std::uint8_t instruction1 : 8; + std::uint8_t length : 8; + std::uint8_t type : 4; + std::uint8_t reserved : 3; + std::uint8_t handler_data_flag : 1; +}; + +extern "C" +{ + extern std::uint32_t __exidx_start; + extern std::uint32_t __exidx_end; + extern std::uint32_t __extab_start; + extern std::uint32_t __extab_end; +} \ No newline at end of file diff --git a/src/arm_cortex/estell/wrappers.cpp b/src/arm_cortex/estell/wrappers.cpp new file mode 100644 index 0000000..3d09aae --- /dev/null +++ b/src/arm_cortex/estell/wrappers.cpp @@ -0,0 +1,35 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +extern "C" +{ + void __wrap___gnu_unwind_pr_common() + { + } + void __wrap___aeabi_unwind_cpp_pr0() + { + } + void __wrap__sig_func() + { + } + void __wrap___gxx_personality_v0() + { + } + void __wrap_deregister_tm_clones() + { + } + void __wrap_register_tm_clones() + { + } +} // extern "C" \ No newline at end of file diff --git a/src/arm_cortex/gcc/impl.cpp b/src/arm_cortex/gcc/impl.cpp new file mode 100644 index 0000000..2aa56ae --- /dev/null +++ b/src/arm_cortex/gcc/impl.cpp @@ -0,0 +1,68 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include +#include +#include +#include + +#include + +#include "../../internal.hpp" + +extern "C" +{ + void _exit([[maybe_unused]] int rc) // NOLINT + { + std::terminate(); + } + + struct _reent* _impure_ptr = nullptr; // NOLINT + + void* __wrap___cxa_allocate_exception(unsigned int p_size) // NOLINT + { + // Size of the GCC exception object header is 128 bytes. Will have to update + // this if the size of the EO increases. 😅 + // Might need to add some GCC macro flags here to keep track of all of the + // EO sizes over the versions. + constexpr size_t header_size = 128; + + auto exception_memory = + hal::get_exception_allocator().allocate(header_size + p_size); + + if (exception_memory.empty()) { + std::terminate(); + } + + // Required for GCC's impl to work correctly as it assumes that all bytes + // default to 0. The Estell impl will utilize the same technique. + std::ranges::fill(exception_memory.first(header_size + p_size), 0U); + + // Return the pointer to the memory after the header, which is the start of + // the thrown object's allocated memory. + return exception_memory.data() + header_size; + } + + void __wrap___cxa_call_unexpected(void*) // NOLINT + { + std::terminate(); + } + + void __wrap___cxa_free_exception(void* p_exception) noexcept // NOLINT + { + hal::get_exception_allocator().deallocate(p_exception); + } +} // extern "C" diff --git a/src/control.cpp b/src/control.cpp new file mode 100644 index 0000000..3a2feb1 --- /dev/null +++ b/src/control.cpp @@ -0,0 +1,59 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include + +namespace __cxxabiv1 { // NOLINT +std::terminate_handler __terminate_handler = +[]() { // NOLINT + while (true) { + continue; + } +}; +} + +namespace hal { +std::terminate_handler set_terminate( + std::terminate_handler p_terminate_handler) noexcept +{ + auto copy = __cxxabiv1::__terminate_handler; + __cxxabiv1::__terminate_handler = p_terminate_handler; + return copy; +} + +std::terminate_handler get_terminate() noexcept +{ + return __cxxabiv1::__terminate_handler; +} + +// TODO(#11): Add macro to IFDEF this out if the user want to save 256 bytes. +using default_single_thread_exception_allocator = + single_thread_exception_allocator<256>; + +default_single_thread_exception_allocator default_allocator{}; +exception_allocator* __exception_allocator = &default_allocator; // NOLINT + +void set_exception_allocator(exception_allocator& p_allocator) noexcept +{ + __exception_allocator = &p_allocator; +} + +exception_allocator* get_exception_allocator() noexcept +{ + return __exception_allocator; +} +} // namespace hal \ No newline at end of file diff --git a/src/internal.hpp b/src/internal.hpp index 5a18d44..a3f9ebb 100644 --- a/src/internal.hpp +++ b/src/internal.hpp @@ -1,330 +1,21 @@ -#pragma once - -#include -#include -#include -#include - -#include "bit.hpp" - -namespace ke { -using exception_ptr = void*; -exception_ptr -current_exception() noexcept; - -using destructor_t = void(void*); - -struct register_t -{ - std::uint32_t data; - - register_t() - : data(0) - { - } - - register_t(std::uint32_t p_data) - : data(p_data) - { - } - - register_t(const void* p_data) - : data(reinterpret_cast(p_data)) - { - } - - operator std::uint32_t() const { return data; } - - const std::uint32_t* operator*() - { - return reinterpret_cast(data); - } -}; - -namespace arm_ehabi { -constexpr std::uint8_t finish = 0xb0; -constexpr std::uint32_t end_descriptor = 0x0000'0000; -constexpr std::uint32_t su16_mask = 0b1111'1111'1111'1110; -} // namespace arm_ehabi - -// This is only to make GDB debugging easier, the functions are never actually -// called so it is not important to include the correct types. -struct function_t -{ - using callable_t = void(); - - callable_t* address; - - function_t(std::uint32_t p_address) - : address(reinterpret_cast(p_address)) - { - } - - operator std::uint32_t() { return reinterpret_cast(address); } - - operator void*() { return reinterpret_cast(address); } - - bool operator<(const function_t& p_other) const - { - return address < p_other.address; - } - - bool operator==(const function_t& p_other) const - { - return address == p_other.address; - } -}; - -struct index_entry_t -{ - std::uint32_t function_offset; - std::uint32_t personality_offset; - - bool has_inlined_personality() const; - bool is_noexcept() const; - bool short_instructions() const; - const std::uint32_t* personality() const; - const std::uint32_t* lsda_data() const; - const std::uint32_t* descriptor_start() const; - function_t function() const; -}; - -struct cortex_m_cpu -{ - register_t r0; - register_t r1; - register_t r2; - register_t r3; - register_t r4; - register_t r5; - register_t r6; - register_t r7; - register_t r8; - register_t r9; - register_t r10; - register_t r11; - register_t ip; - register_t sp; - register_t lr; - register_t pc; - - register_t& operator[](size_t p_size) - { - using register_file = std::array; - static_assert(sizeof(cortex_m_cpu) == sizeof(register_file)); - static_assert(sizeof(std::uint32_t) == sizeof(register_t)); - - register_file* file = reinterpret_cast(this); - return (*file)[p_size]; - } -}; +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -namespace su16 { -constexpr auto instruction0 = hal::bit_mask{ .position = 16, .width = 8 }; -constexpr auto instruction1 = hal::bit_mask{ .position = 8, .width = 8 }; -constexpr auto instruction2 = hal::bit_mask{ .position = 0, .width = 8 }; -} // namespace su16 - -namespace lu16_32 { -constexpr auto length = hal::bit_mask::from<16, 23>(); -constexpr auto instruction0 = hal::bit_mask::from<15, 8>(); -constexpr auto instruction1 = hal::bit_mask::from<7, 0>(); -constexpr auto instruction2 = hal::bit_mask::from<31, 24>(); -constexpr auto instruction3 = hal::bit_mask::from<23, 16>(); -constexpr auto instruction4 = hal::bit_mask::from<15, 8>(); -constexpr auto instruction5 = hal::bit_mask::from<7, 0>(); -constexpr auto instruction6 = hal::bit_mask::from<24, 31>(); -} // namespace lu16_32 - -enum class runtime_state : std::uint8_t -{ - get_next_frame = 0, - gcc_lsda_cleanup_catch_phase = 1, - unwind_frame = 2, -}; - -struct cache_t -{ - std::uint32_t state_and_rel_address; - const index_entry_t* entry_ptr = nullptr; - - static constexpr auto state_mask = hal::bit_mask{ - .position = 24, - .width = 4, - }; - static constexpr auto rethrown_mask = hal::bit_mask{ - .position = 28, - .width = 1, - }; - static constexpr auto relative_address_mask = hal::bit_mask{ - .position = 0, - .width = 24, - }; - - constexpr runtime_state state() const - { - auto state_integer = hal::bit_extract(state_and_rel_address); - return static_cast(state_integer); - } - - constexpr std::uint32_t relative_address() const - { - return hal::bit_extract(state_and_rel_address); - } - - constexpr void state(runtime_state p_state) - { - auto state_int = static_cast(p_state); - hal::bit_modify(state_and_rel_address).insert(state_int); - } - - constexpr void rethrown(bool p_rethrown) - { - hal::bit_modify(state_and_rel_address).insert(p_rethrown); - } - - constexpr bool rethrown() const - { - return hal::bit_extract(state_and_rel_address); - } - - constexpr void relative_address(std::uint32_t p_rel_address) - { - hal::bit_modify(state_and_rel_address) - .insert(p_rel_address); - } -}; - -struct exception_object -{ - cortex_m_cpu cpu{}; - std::type_info* type_info = nullptr; - destructor_t* destructor = nullptr; - cache_t cache{}; -}; - -constexpr size_t exception_object_size = sizeof(exception_object); - -exception_object& -get_exception_object(void* p_thrown_exception); - -inline exception_object& -extract_exception_object(void* p_thrown_exception) -{ - auto thrown_address = reinterpret_cast(p_thrown_exception); - auto start_of_exception_object = thrown_address - sizeof(exception_object); - return *reinterpret_cast(start_of_exception_object); -} - -inline void* -extract_thrown_object(void* p_exception_object) -{ - auto object_address = reinterpret_cast(p_exception_object); - auto start_of_thrown = object_address + sizeof(exception_object); - return reinterpret_cast(start_of_thrown); -} - -[[gnu::always_inline]] inline void -restore_cpu_core(ke::cortex_m_cpu& p_cpu_core) -{ - asm volatile( - "ldr r0, [%[regs], #0]\n" // Load R0 - "ldr r1, [%[regs], #4]\n" // Load R1 - "ldr r2, [%[regs], #8]\n" // Load R2 - // "ldr r3, [%[regs], #12]\n" // Load R3 - "ldr r4, [%[regs], #16]\n" // Load R4 - "ldr r5, [%[regs], #20]\n" // Load R5 - "ldr r6, [%[regs], #24]\n" // Load R6 - "ldr r7, [%[regs], #28]\n" // Load R7 - "ldr r8, [%[regs], #32]\n" // Load R8 - "ldr r9, [%[regs], #36]\n" // Load R9 - "ldr r10, [%[regs], #40]\n" // Load R10 - "ldr r11, [%[regs], #44]\n" // Load R11 - "ldr r12, [%[regs], #48]\n" // Load R12 - // Stack Pointer (R13/SP) and Link Register (R14/LR) require special - // handling - "ldr sp, [%[regs], #52]\n" // Load SP - "ldr lr, [%[regs], #56]\n" // Load LR - // PC and xPSR restoration is more complex due to ARM's execution state and - // alignment requirements Directly loading PC can be dangerous and is - // typically managed through a pop or an exception return mechanism - "ldr pc, [%[regs], #60]\n" - : - : [regs] "r"(&p_cpu_core) - : "memory", - "r0", - "r1", - "r2", - // "r3", - "r4", - "r5", - "r6", - "r7", - "fp", - "r8", - "r9", - "r10", - "r11", - "r12"); -} - -constexpr std::uint32_t -to_absolute_address(const void* p_object) -{ - constexpr auto signed_bit_31 = hal::bit_mask::from<30>(); - constexpr auto signed_bit_32 = hal::bit_mask::from<31>(); - auto object_address = reinterpret_cast(p_object); - auto offset = *reinterpret_cast(p_object); - - // Sign extend the offset to 32-bits - if (hal::bit_extract(offset)) { - hal::bit_modify(offset).set(); - } else { - hal::bit_modify(offset).clear(); - } - - auto signed_offset = static_cast(offset); - std::int32_t final_address = object_address + signed_offset; - return static_cast(final_address); -} - -[[gnu::used]] inline std::uint32_t -runtime_to_absolute_address(const void* p_object) -{ - return to_absolute_address(p_object); -} - -inline std::uint32_t* -to_absolute_address_ptr(const void* p_object) -{ - return reinterpret_cast(to_absolute_address(p_object)); -} -} // namespace ke - -struct [[gnu::packed]] su16_t -{ - std::uint8_t instruction0 : 8; - std::uint8_t instruction1 : 8; - std::uint8_t instruction2 : 8; - std::uint8_t type : 4; - std::uint8_t reserved : 3; - std::uint8_t handler_data_flag : 1; -}; +#pragma once -struct [[gnu::packed]] lu_t -{ - std::uint8_t instruction0 : 8; - std::uint8_t instruction1 : 8; - std::uint8_t length : 8; - std::uint8_t type : 4; - std::uint8_t reserved : 3; - std::uint8_t handler_data_flag : 1; -}; +#include -extern "C" -{ - extern std::uint32_t __exidx_start; - extern std::uint32_t __exidx_end; - extern std::uint32_t __extab_start; - extern std::uint32_t __extab_end; -} \ No newline at end of file +namespace hal { +exception_allocator& get_exception_allocator() noexcept; +} // namespace hal \ No newline at end of file diff --git a/src/wrappers.cpp b/src/wrappers.cpp deleted file mode 100644 index ec91e35..0000000 --- a/src/wrappers.cpp +++ /dev/null @@ -1,9 +0,0 @@ -extern "C" -{ - void __wrap___gnu_unwind_pr_common() {} - void __wrap___aeabi_unwind_cpp_pr0() {} - void __wrap__sig_func() {} - void __wrap___gxx_personality_v0() {} - void __wrap_deregister_tm_clones() {} - void __wrap_register_tm_clones() {} -} // extern "C" \ No newline at end of file diff --git a/test_package/CMakeLists.txt b/test_package/CMakeLists.txt new file mode 100644 index 0000000..361a5ca --- /dev/null +++ b/test_package/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright 2024 Khalil Estell +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +cmake_minimum_required(VERSION 3.15) +project(test_package LANGUAGES CXX) + +find_package(libhal-exceptions REQUIRED CONFIG) + +add_executable(${PROJECT_NAME} main.cpp) +target_include_directories(${PROJECT_NAME} PUBLIC .) +target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) +set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF) +target_link_libraries(${PROJECT_NAME} PRIVATE libhal::exceptions) diff --git a/test_package/conanfile.py b/test_package/conanfile.py new file mode 100644 index 0000000..eb51713 --- /dev/null +++ b/test_package/conanfile.py @@ -0,0 +1,24 @@ +# Copyright 2024 Khalil Estell +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from conan import ConanFile + + +class TestPackageConan(ConanFile): + settings = "os", "arch", "compiler", "build_type" + python_requires = "libhal-bootstrap/[^0.0.1]" + python_requires_extend = "libhal-bootstrap.library_test_package" + + def requirements(self): + self.requires(self.tested_reference_str) diff --git a/test_package/main.cpp b/test_package/main.cpp new file mode 100644 index 0000000..849ee20 --- /dev/null +++ b/test_package/main.cpp @@ -0,0 +1,19 @@ +// Copyright 2024 Khalil Estell +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +int main() +{ +}