Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vs2022.1 #10

Merged
merged 6 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions .github/workflows/buildcommit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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' }}
Expand All @@ -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:
Expand All @@ -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@v4
with:
Expand All @@ -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@v4
with:
path: lib
Expand Down
46 changes: 42 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -29,34 +31,48 @@ 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.
- Custom platform assembly code must only do three things:
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)
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -114,34 +139,42 @@ 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.
In the case of inlined code, it can be specified to prefer in-line assembly and static linkage
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.
3. Provide an assembler version, `switch_ABI.S` by compiling the `gen_asm.c` file for your platform.
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.
Expand All @@ -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
Expand All @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion stackman/platforms/switch_x64_msvc.asm
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 0 additions & 2 deletions tests/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
18 changes: 7 additions & 11 deletions tools/strip-lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand All @@ -208,16 +204,16 @@ def second_lm_read(fp):
# number of members
m = struct.unpack("<L", fp.read(4))[0] # unsigned long, big-endian
offsets = []
for i in range(m):
for _ in range(m):
offsets.append(struct.unpack("<L", fp.read(4))[0])

# number of symbols
n = struct.unpack("<L", fp.read(4))[0] # unsigned long, big-endian
indices = []
for i in range(n):
for _ in range(n):
indices.append(struct.unpack("<H", fp.read(2))[0]) # unsigned short
strings = []
for i in range(n):
for _ in range(n):
strings.append(readcstr(fp))

return {"offsets": offsets, "indices": indices, "strings": strings}
Expand Down Expand Up @@ -261,7 +257,7 @@ def read_optional_nl(fp):


def peek(fp):
""" check the next char """
"""check the next char"""
t = fp.tell()
c = fp.read(1)
fp.seek(t)
Expand Down
44 changes: 44 additions & 0 deletions vs2022/stackman.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30717.126
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "test\test.vcxproj", "{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}"
ProjectSection(ProjectDependencies) = postProject
{BF7D0638-AC4F-4206-B426-66CDDD468281} = {BF7D0638-AC4F-4206-B426-66CDDD468281}
EndProjectSection
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "stackman", "stackman\stackman.vcxproj", "{BF7D0638-AC4F-4206-B426-66CDDD468281}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.ActiveCfg = Debug|ARM
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM.Build.0 = Debug|ARM
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.ActiveCfg = Debug|ARM64
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|ARM64.Build.0 = Debug|ARM64
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.ActiveCfg = Debug|x64
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x64.Build.0 = Debug|x64
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.ActiveCfg = Debug|Win32
{CAFDBD3E-9D0D-4E90-BB6D-6C2A19F5EF53}.Debug|x86.Build.0 = Debug|Win32
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.ActiveCfg = Debug|ARM
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM.Build.0 = Debug|ARM
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.ActiveCfg = Debug|ARM64
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|ARM64.Build.0 = Debug|ARM64
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|x64.ActiveCfg = Debug|x64
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|x64.Build.0 = Debug|x64
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|x86.ActiveCfg = Debug|Win32
{BF7D0638-AC4F-4206-B426-66CDDD468281}.Debug|x86.Build.0 = Debug|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A71982F0-A89C-45AB-9F23-149A1983A11B}
EndGlobalSection
EndGlobal
Loading