diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000..f245c19dcf --- /dev/null +++ b/.dockerignore @@ -0,0 +1,21 @@ +# Ignore target directory +target/ + +# Ignore any build artifacts +**/*.rs.bk + +# Ignore any generated files +**/Cargo.lock +**/Cargo.toml.orig +**/Cargo.toml.bk + +# Ignore any IDE-specific files +.vscode/ +.idea/ + +# Ignore Dockerfile and dockerignore file +Dockerfile +.dockerignore + +# Workflow +.github \ No newline at end of file diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000000..5585960b80 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,64 @@ +name: Docker Image CI + +on: + push: + branches: [ '*' ] + +env: + IMAGE_NAME: rusty + +jobs: + # Push image to GitHub Packages. + # See also https://docs.docker.com/docker-hub/builds/ + build-linux: + runs-on: ${{ matrix.config.os }} + strategy: + matrix: + config: + - { + os: "ubuntu-latest", + version: "linux", + arch: "x86_64" + } + permissions: + packages: write + contents: read + + steps: + - uses: actions/checkout@v3 + + - name: Build image + shell: bash + run: docker buildx build . --platform ${{matrix.config.version}}/${{matrix.config.arch}} --file Dockerfile --tag $IMAGE_NAME + + - name: Log in to registry + if: ${{ github.event_name != 'pull_request' }} + # This is where you will update the PAT to GITHUB_TOKEN + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Push image + shell: bash + if: ${{ github.event_name != 'pull_request' }} + run: | + IMAGE_ID=ghcr.io/${{ github.repository_owner }}/$IMAGE_NAME + + # Extract branch name + BRANCH_NAME=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + + # Append branch name to image ID + IMAGE_ID=$IMAGE_ID-$BRANCH_NAME + + # Change all uppercase to lowercase + IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') + + # Strip git ref prefix from version + VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') + + # Use Docker `latest` tag convention + [ "$VERSION" == "main" ] && VERSION=latest + #Add the platform to the version + VERSION=$VERSION-${{ matrix.config.arch }} + echo IMAGE_ID=$IMAGE_ID + echo VERSION=$VERSION + docker tag $IMAGE_NAME $IMAGE_ID:$VERSION + docker push $IMAGE_ID:$VERSION diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000..2d1665b7c6 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# FROM mcr.microsoft.com/vscode/devcontainers/rust:latest +FROM ghcr.io/plc-lang/rust-llvm:latest + +# Avoid warnings by switching to noninteractive +ENV DEBIAN_FRONTEND=noninteractive + +# This Dockerfile adds a non-root user with sudo access. Use the "remoteUser" +# property in devcontainer.json to use it. On Linux, the container user's GID/UIDs +# will be updated to match your local UID/GID (when using the dockerFile property). +# See https://aka.ms/vscode-remote/containers/non-root-user for details. +# ARG USERNAME=vscode +# ARG USER_UID=1000 +# ARG USER_GID=$USER_UID + +# # Create a non-root user to use if preferred - see https://aka.ms/vscode-remote/containers/non-root-user. +# RUN groupadd --gid $USER_GID $USERNAME \ +# && useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME \ +# # [Optional] Add sudo support for the non-root user +# && apt-get install -y sudo \ +# && echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME\ +# && chmod 0440 /etc/sudoers.d/$USERNAME + +# RUN apt-get -y update +# RUN apt-get -y install git gdb docker.io + +# RUN cargo install cargo-insta cargo-watch + +# Give all users access to cargo and rust home +RUN chmod -R a+rw $CARGO_HOME \ + && chmod -R a+rw $RUSTUP_HOME + +# Switch back to dialog for any ad-hoc use of apt-get +ENV DEBIAN_FRONTEND=dialog +ENV LLVM_VER=14 + +# Required if we want to use `lld` as the default linker for RuSTy +RUN ln -sf /usr/bin/ld.lld-$LLVM_VER /usr/bin/ld.lld + +# Install the local RuSTy version +WORKDIR /rusty +COPY . . +RUN sed -i 's/build=0/build=1/' ./scripts/build.sh && \ + ./scripts/build.sh + +# Allow invoking `plc` from anywhere +ENV PATH="/rusty/target/debug:${PATH}" + +ENTRYPOINT [ "/bin/bash", "-c" ] +CMD ["plc", "--help"] diff --git a/examples/buffer_overflow.st b/examples/buffer_overflow.st new file mode 100644 index 0000000000..152ad4ae03 --- /dev/null +++ b/examples/buffer_overflow.st @@ -0,0 +1,14 @@ +FUNCTION main : DINT + buf_overflow(); +END_FUNCTION + +FUNCTION buf_overflow : DINT +VAR + i : DINT; + buf : ARRAY [0..2] OF BYTE; +END_VAR + +FOR i := 0 TO 3 DO + buf[i] := 1; +END_FOR +END_FUNCTION \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh index ab431fc90f..bc9e9c73d2 100755 --- a/scripts/build.sh +++ b/scripts/build.sh @@ -5,7 +5,7 @@ vendor=0 offline=0 check=0 check_style=0 -build=0 +build=1 doc=0 test=0 lit=0 @@ -128,7 +128,7 @@ function run_doc() { log "Building book" log "Building preprocessor for the book" cargo build --release -p errorcode_book_generator - cd book && mdbook build + cd book && mdbook build # test is disabled because not all files in the book exist. The pre-processor for error codes adds new files # mdbook test } diff --git a/src/codegen.rs b/src/codegen.rs index b141c368ef..9ec88eed19 100644 --- a/src/codegen.rs +++ b/src/codegen.rs @@ -25,6 +25,7 @@ use crate::{ use super::index::*; use inkwell::{ + attributes::{Attribute, AttributeLoc}, context::Context, execution_engine::{ExecutionEngine, JitFunction}, memory_buffer::MemoryBuffer, @@ -195,6 +196,33 @@ impl<'ink> CodeGen<'ink> { global_index: &Index, llvm_index: &LlvmTypedIndex, ) -> Result, Diagnostic> { + // This loops through functions and adds the `sanitize_address` attribute. + // The AddressSanitizer LLVM passes will then use this to identify which + // functions need to be instrumented. + // + // There's likely somewhere else this should go. Placing it here for now. + // TODO: Once it's in it's proper place, we'll add a flag to enable/disable it. + for implementation in &unit.implementations { + // Skip non-internal functions (external links + built-ins) + if implementation.linkage != LinkageType::Internal { + continue; + } + + // Skip no-definition functions + // TODO - investigate which functions don't have definitions and why + // TODO - better way to log this than println + let func = match llvm_index.find_associated_implementation(&implementation.name) { + Some(func) => func, + None => { + println!("Skipping undefined function: {}", &implementation.name); + continue; + } + }; + let sanitizer_attribute_id = Attribute::get_named_enum_kind_id("sanitize_address"); + let sanitizer_attribute = context.create_enum_attribute(sanitizer_attribute_id, 0); + func.add_attribute(AttributeLoc::Function, sanitizer_attribute); + } + //generate all pous let llvm = Llvm::new(context, context.create_builder()); let pou_generator = PouGenerator::new(llvm, global_index, annotations, llvm_index); @@ -330,11 +358,23 @@ impl<'ink> GeneratedModule<'ink> { if let Some(parent) = output.parent() { std::fs::create_dir_all(parent)?; } - ////Run the passes + // Run the passes + // TODO - Add asan as optional cli parameter + // TODO - Currently doesn't add linking parameters for the asan runtime + // + // NOTE - these pass names have changed in newer versions of LLVM. + // - [Latest](https://github.com/llvm/llvm-project/blob/main/llvm/lib/Passes/PassRegistry.def) + // - [LLVM 14](https://github.com/llvm/llvm-project/blob/release/14.x/llvm/lib/Passes/PassRegistry.def) + // It should also be noted that the ASAN pass requies: + // - Specific target architectures [see here](https://github.com/google/sanitizers/wiki/AddressSanitizer). + // - Output is NOT IR (otherwise the pass will run twice with the next compiler (i.e. clang) and it will cause a bad time) + + let passes = format!("{},asan-module,function(asan)", optimization_level.opt_params()); + machine .and_then(|it| { self.module - .run_passes(optimization_level.opt_params(), &it, PassBuilderOptions::create()) + .run_passes(&passes, &it, PassBuilderOptions::create()) .map_err(|it| { Diagnostic::llvm_error(output.to_str().unwrap_or_default(), &it.to_string_lossy()) })