diff --git a/.github/workflows/buildcommit.yml b/.github/workflows/buildcommit.yml index cfc7faf..6255c19 100644 --- a/.github/workflows/buildcommit.yml +++ b/.github/workflows/buildcommit.yml @@ -30,7 +30,7 @@ jobs: PLATFORM_PREFIX: ${{matrix.platformtools}} EMULATOR: ${{matrix.emulator}} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: install multilib run: sudo apt-get install --no-install-recommends -y gcc-multilib g++-multilib if: ${{ matrix.name == 'i386' }} @@ -53,7 +53,7 @@ jobs: path: lib/${{ env.abiname }}/libstackman.a build-windows: - runs-on: windows-2019 + runs-on: windows-latest strategy: fail-fast: true matrix: @@ -71,17 +71,17 @@ jobs: folder: arm64 steps: - - uses: actions/checkout@v2 - - uses: microsoft/setup-msbuild@v1.0.2 + - uses: actions/checkout@v4 + - uses: microsoft/setup-msbuild@v2 - name: build - run: msbuild.exe vs2019\stackman.sln /p:Platform=${{matrix.platform}} + run: msbuild.exe vs2022\stackman.sln /p:Platform=${{matrix.platform}} - name: strip timestamps from lib run: python tools/strip-lib.py lib/win_${{matrix.platform}}/stackman.lib - name: rebuild after stripping - run: msbuild.exe vs2019\stackman.sln /p:Platform=${{matrix.platform}} + run: msbuild.exe vs2022\stackman.sln /p:Platform=${{matrix.platform}} - name: test if: ${{ matrix.native == 'yes' }} - run: vs2019\${{matrix.folder}}\Debug\test.exe + run: vs2022\${{matrix.folder}}\Debug\test.exe - name: Upload build artifacts uses: actions/upload-artifact@v2 with: @@ -93,7 +93,7 @@ jobs: needs: [build-linux-gnu, build-windows] if: ${{ github.event_name == 'push' }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - uses: actions/download-artifact@v2 with: path: lib diff --git a/README.md b/README.md index 4ab8b04..36e4c46 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ [![build test and commit](https://github.com/kristjanvalur/stackman/actions/workflows/buildcommit.yml/badge.svg)](https://github.com/kristjanvalur/stackman/actions/workflows/buildcommit.yml) # stackman + Simple low-level stack manipulation API and implementation for common platforms ## Purpose + This library aims to provide a basic API to perfom stack manipulation on various platforms. Stack manipulation involves changing the machine stack pointer while optionally saving and restoring the stack contents. @@ -29,10 +31,14 @@ Additionally, it provides a set of pre-assembled libraries for the most common platforms so that no assembly steps are required by users. ## Features + - Simple api + - `stackman_switch()` and `stackman_call()` are the only functions. - The caller provides a callback and context pointer to customize behaviour. - Simple implementation + +- Simple implementation + - The code involving assembly is as simple as possible, allowing for straightforward implementation on most platforms. - Complex logic and branching is delegated to the C callback. @@ -40,23 +46,33 @@ platforms so that no assembly steps are required by users. 1. Save and restore volatile registers and stack state on the stack 2. Call the callback twice with the current stack pointer 3. Set the stack pointer to the value returned by the first callback. -- Assembly support + +- Assembly support + The straightforward and application-agnostic switching allows the switching function to be implemented in full assembler. This removes the risk of inline-assembler doing any sort of unexpected things such as in-lining the function or otherwise change the assumptions that the function makes about its environment. This assembly code can be created by the in-line assembler in a controlled environment. + - No dependencies + The library merely provides stack switching. It consist only of a couple of functions with no dependencies. + - Stable + There is no need to add or modify functionality. + - Libraries provided. + The aim is to provide pre-assembled libraries for the most popular platforms. This relieves other tools that want to do stack manipulation from doing any sort of assembly or complex linkage. Just include the headers and link to the appropriate library. ## Supported platforms + The current code is distilled out of other work, with the aim of simplifying and standardizing the api. A number of ABI specifications is supported, meaning architecture and calling convention, plus archive format: + - win_x86 (32 bits) - win_x64 - win_ARM64 (experimental) @@ -65,7 +81,8 @@ calling convention, plus archive format: - AAPCS (32 bit arm) - AAPCS64 (64 bit arm) -Supported toolchains: +### Supported toolchains: + - Gnu C - clang - Microsoft Visual Studio @@ -74,16 +91,21 @@ Other platforms can be easily adapted from both existing implementations for oth projects as well as from example code provided. ## API + There are two functions that make up the stackman library: `stakman_switch()` and `stackman_call()` who both take a `stackman_cb_t` callback: + ```C typedef void *(*stackman_cb_t)( void *context, int opcode, void *stack_pointer); void *stackman_switch(stackman_cb_t callback, void *context); void *stackman_call(stackman_cb_t callback, void *context, void *stack); ``` + ### stackman_switch() + This is the main _stack manipulation_ API. When called, it will call `callback` function twice: + 1. First it calls it with the current opcode `STACKMAN_OP_SAVE`, passing the current `stack_pointer` to the callback. This gives the callback the opportunity to _save_ the stack data somewhere. The callback can then return a **different** stack pointer. @@ -98,14 +120,17 @@ Depending on how the callback function is implemented, this API can be used for saving a copy of the stack, perform a stack switch, query the stack pointer, and so on. ### stackman_call() + This is a helper function to call a callback function, optionally providing it with a different stack to use. + 1. It saves the current CPU stack pointer. If `stack` is non-zero, it will replace the stackpointer with that value. 2. It calls the callback function with the opcode `STACKMAN_OP_CALL`. 3. It replaces the stack pointer with the previously saved value and returns the return value from the callback. This function is useful for at least three things: + - To move the call chain into a custom stack area, some heap-allocated block, for example. - To query the current stack pointer - To enforce an actual function call with stack pointer information. @@ -114,17 +139,21 @@ The last feature is useful to bypass any in-lining that a compiler may do, when a proper function call with stack, for example, when setting up a new stack entry point. ## Usage + - Include `stackman.h` for a decleration of the `stackman_switch()` function and the definition of various platform specific macros. See the documentation in the header file for the various macros. - Implement switching semantics via the callback and call `stackman_switch()` from your program as appropriate. See tests/test.c for examples. -There are two basic ways to add the library to your project: +There are two basic ways to add the library to your project: Using a static library or inlining the code. + ### static library (preferred) + - You link with the `libstackman.a` or `stackman.lib` libraries provided for your platform. ### inlined code + - You inlude `stackman_impl.h` in one of your .c source files to provide inline assembly. - You include `stackman_impl.h` in an assembly (.S) file in your project to include assembly code. - (windows) You include `stackman_s.asm` in an assemby (.asm) file in your project. @@ -132,7 +161,9 @@ There are two basic ways to add the library to your project: over separate assembly language source. ## Development + ### Adding new platforms + 1. Modify `platform.h` to identif the platform environment. Define an ABI name and include custom header files. 2. Use the `switch_template.h` to help build a `switch_ABI.h` file for your ABI. @@ -140,8 +171,10 @@ There are two basic ways to add the library to your project: 4. Provide cross-compilation tools for linux if possible, by modifying the `Makefile` ### Cross-compilation + Linux on x86-64 can be used to cross compile for x86 and ARM targets. This is most useful to generate assembly code, e.g. when compiling stackman/platform/gen_asm.c + - x86 requires the -m32 flag to compilers and linkers. - arm32 requires to use the arm-linux-gnueabi-* tools, including cc and linker - aarch64 requires the aarch64-linux-gnu-* tools. @@ -151,23 +184,27 @@ The x86 tools require the **gcc-multilib** and **g++-multilib** packages to be i platforms may need to be done independently. #### Cross compiling for x86 (32 bit) on Linux + - install __gcc-multilib__ and __g++-multilib__ - *compile* **gen_asm.c** using `gcc -m32` - *make* using `make PLATFORMFLAGS=-m32 test` #### Cross compiling for ARM (32 bit) on Linux + - install __gcc-arm-linux-gnueabi__ and __g++-arm-linux-gnueabi__ - install __qemu-user__ for hardware emulation - *compile* **gen_asm.c** using `arm-linux-gnueabi-gcc` - *make* using `make PLATFORM_PREFIX=arm-linux-gnueabi- EMULATOR=qemu-arm test` #### Cross compiling for Arm64 on Linux + - install **gcc-aarch64-linux-gnu** and **g++-aarch64-linux-gnu** - install __qemu-user__ for hardware emulation - *compile* using `aarch64-linux-gnu-gcc` - *make* using `make PLATFORM_PREFIX=aarch64-linux-gnu- EMULATOR=qemu-aarch64 test` ## A note about Intel CET + Intel's *Control-Flow Enforcement Technology* is incompatible with stack switching because it employs a secondary *Shadow Stack*, that the user-mode program cannot modify. Unexpected return flow after a stack switch would cause the processor @@ -179,6 +216,7 @@ code may be run in such a protected environment. See https://software.intel.com/content/www/us/en/develop/articles/technical-look-control-flow-enforcement-technology.html for more information ## History + This works was originally inspired by *Stackless Python* by [Christian Tismer](https://github.com/ctismer), where the original switching code was developed. diff --git a/lib/aarch64/libstackman.a b/lib/aarch64/libstackman.a index a2316bd..c30dcd0 100644 Binary files a/lib/aarch64/libstackman.a and b/lib/aarch64/libstackman.a differ diff --git a/lib/arm32/libstackman.a b/lib/arm32/libstackman.a index ce4199d..426030f 100644 Binary files a/lib/arm32/libstackman.a and b/lib/arm32/libstackman.a differ diff --git a/lib/sysv_amd64/libstackman.a b/lib/sysv_amd64/libstackman.a index 3025f81..8b38241 100644 Binary files a/lib/sysv_amd64/libstackman.a and b/lib/sysv_amd64/libstackman.a differ diff --git a/lib/sysv_i386/libstackman.a b/lib/sysv_i386/libstackman.a index d5e9771..1d91d87 100644 Binary files a/lib/sysv_i386/libstackman.a and b/lib/sysv_i386/libstackman.a differ diff --git a/lib/win_arm/stackman.lib b/lib/win_arm/stackman.lib index 2b515b5..b45daad 100644 Binary files a/lib/win_arm/stackman.lib and b/lib/win_arm/stackman.lib differ diff --git a/lib/win_arm64/stackman.lib b/lib/win_arm64/stackman.lib index 68c06e0..427de82 100644 Binary files a/lib/win_arm64/stackman.lib and b/lib/win_arm64/stackman.lib differ diff --git a/lib/win_x64/stackman.lib b/lib/win_x64/stackman.lib index 56f06dd..532bf92 100644 Binary files a/lib/win_x64/stackman.lib and b/lib/win_x64/stackman.lib differ diff --git a/lib/win_x86/stackman.lib b/lib/win_x86/stackman.lib index f34ec99..36b28c6 100644 Binary files a/lib/win_x86/stackman.lib and b/lib/win_x86/stackman.lib differ diff --git a/stackman/platforms/switch_x64_msvc.asm b/stackman/platforms/switch_x64_msvc.asm index 5143276..998eb24 100644 --- a/stackman/platforms/switch_x64_msvc.asm +++ b/stackman/platforms/switch_x64_msvc.asm @@ -104,7 +104,7 @@ NESTED_END stackman_switch, _TEXT$00 stackman_call PROC FRAME push rbp .pushreg rbp - ; now our stack is 16 byte aligned. don't need additional spacle + ; now our stack is 16 byte aligned. don't need additional space ;sub rsp, 040h ;.allocstack 040h lea rbp, [rsp+00h] diff --git a/tests/test.c b/tests/test.c index e6b726e..a8beffc 100644 --- a/tests/test.c +++ b/tests/test.c @@ -254,8 +254,6 @@ void test_04(void) /* test stackman_call() with a null stack pointer */ void test_05(void) { - char *block, *stack, *stack2; - int i, cnt; ctxt01 ctxt; assert(STACKMAN_STACK_FULL_DESCENDING); diff --git a/tools/strip-lib.py b/tools/strip-lib.py index a64aa97..f783d81 100644 --- a/tools/strip-lib.py +++ b/tools/strip-lib.py @@ -7,8 +7,8 @@ # There are some additional fixes added for reproducability, such as fixing the zero-padding of names in the coff # section headers. -import sys import struct +import sys verbose = True @@ -96,8 +96,6 @@ def read_lib(fp): print("coff length:", len(result["o"][-1])) h = None - return result - def write_lib(fp, lib): fp.write(libheader) @@ -184,15 +182,13 @@ def first_lm_read(fp): offsets = [] strings = [] - for i in range(nos): + for _ in range(nos): offset = fp.read(4) offsets.append(struct.unpack(">L", offset)[0]) - for i in range(nos): + for _ in range(nos): strings.append(readcstr(fp)) return {"offsets": offsets, "strings": strings} # sometimes there is an extra \0a after the strings - p = peek(fp) - return zip(offsets, strings) def first_lm_write(fp, lm): @@ -208,16 +204,16 @@ def second_lm_read(fp): # number of members m = struct.unpack(" + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + + 16.0 + Win32Proj + {bf7d0638-ac4f-4206-b426-66cddd468281} + stackman + 10.0 + stackman + + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + true + v143 + Unicode + + + StaticLibrary + true + v143 + Unicode + + + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)\..\lib\win_x86\ + $(Platform)\$(Configuration)\ + + + true + $(SolutionDir)\..\lib\win_x64\ + + + true + $(SolutionDir)\..\lib\win_arm\ + + + true + $(SolutionDir)\..\lib\win_arm64\ + + + + Level3 + true + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + OldStyle + + + + + true + + + /Brepro %(AdditionalOptions) + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + OldStyle + + + + + true + + + /Brepro %(AdditionalOptions) + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + OldStyle + + + + + true + + + /Brepro %(AdditionalOptions) + + + + + Level3 + true + _DEBUG;_LIB;%(PreprocessorDefinitions) + true + NotUsing + pch.h + OldStyle + + + + + true + + + /Brepro %(AdditionalOptions) + + + + + + + + + + + + + + + false + Default + + + AssemblyCode + AssemblyCode + AssemblyCode + false + false + false + Default + Default + Default + false + false + false + true + true + false + true + + + + + false + true + true + true + + + Document + true + true + true + + + true + true + true + + + true + true + true + + + + + + + + \ No newline at end of file diff --git a/vs2022/stackman/stackman.vcxproj.filters b/vs2022/stackman/stackman.vcxproj.filters new file mode 100644 index 0000000..9f57a1f --- /dev/null +++ b/vs2022/stackman/stackman.vcxproj.filters @@ -0,0 +1,67 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/vs2022/stackman/template.c b/vs2022/stackman/template.c new file mode 100644 index 0000000..2fe7dbd --- /dev/null +++ b/vs2022/stackman/template.c @@ -0,0 +1,15 @@ +#include "..\..\stackman\stackman.h" + +/* + * template file to create assembly code (template.asm) to modify and add to real assembler. + */ + +void* stackman_call_templ(stackman_cb_t callback, void* context, void* stack) +{ + // We use this variabl here for the template generation. Int the modified assembly + // code, we will store the ebp (base pointer) in that place on the stack, + // before storing the original unmodified stack pointer there. + void* localvar = stack; + return callback(context, 2, localvar); + +} \ No newline at end of file diff --git a/vs2022/test/test.vcxproj b/vs2022/test/test.vcxproj new file mode 100644 index 0000000..266eb72 --- /dev/null +++ b/vs2022/test/test.vcxproj @@ -0,0 +1,152 @@ + + + + + Debug + ARM + + + Debug + ARM64 + + + Debug + Win32 + + + Debug + x64 + + + + 16.0 + Win32Proj + {cafdbd3e-9d0d-4e90-bb6d-6c2a19f5ef53} + test + 10.0 + + + + Application + true + v143 + Unicode + + + Application + true + v143 + Unicode + + + Application + true + v143 + Unicode + + + Application + true + v143 + Unicode + + + + + + + + + + + + + + + + + + + + + true + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + true + + + true + + + true + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\..\stackman + + + Console + true + ..\..\lib\win_x86;%(AdditionalLibraryDirectories) + stackman.lib;%(AdditionalDependencies) + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\..\stackman + + + Console + true + stackman.lib;%(AdditionalDependencies) + ..\..\lib\win_x64;%(AdditionalLibraryDirectories) + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\..\stackman + + + Console + true + stackman.lib;%(AdditionalDependencies) + ..\..\lib\win_arm;%(AdditionalLibraryDirectories) + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + ..\..\stackman + + + Console + true + stackman.lib;%(AdditionalDependencies) + ..\..\lib\win_arm64;%(AdditionalLibraryDirectories) + + + + + + + + + \ No newline at end of file diff --git a/vs2022/test/test.vcxproj.filters b/vs2022/test/test.vcxproj.filters new file mode 100644 index 0000000..154d04b --- /dev/null +++ b/vs2022/test/test.vcxproj.filters @@ -0,0 +1,22 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + \ No newline at end of file