From 69d3088259049ddcc17cc21a92bd2d224b96bc40 Mon Sep 17 00:00:00 2001 From: Willy Zhang Date: Thu, 17 Sep 2026 02:59:41 +0000 Subject: [PATCH] build,test: replace lowRISC/crt with llvm-mingw for Win32 ATE DLL Replace the legacy lowRISC/crt MXE GCC 11.3.0 toolchain and its vendored Starlark rules in third_party/crt/ with llvm-mingw (Clang/LLD targeting i686-w64-mingw32-msvcrt), and add containerized Wine tests for ate.dll. Key changes: - Fix Win32 gRPC crash: GCC 11.3.0 (SJLJ exceptions) miscompiled gRPC's ServiceConfigParser::Builder::RegisterParser() at -O1/-O2, causing CreateClient() to fault with 0xC0000005. Building with llvm-mingw (DWARF unwinding) resolves this compiler bug cleanly. - Self-contained DLL: Statically link libc++, libunwind, and winpthread into ate.dll so it depends only on standard Windows OS DLLs (msvcrt.dll, KERNEL32.dll, WS2_32.dll, CRYPT32.dll). This eliminates companion MinGW runtime DLLs and allows deleting pkg_win.py and all 1,675 lines of third_party/crt/. - ABI & release hardening: Enable -mstackrealign for 32-bit MSVC/LabVIEW caller stack compatibility (4-byte vs 16-byte stack alignment) and define -DNDEBUG in non-debug builds. - Automated Wine test suite: Add a Win32 test harness (ate_dll_smoke.cc) that loads ate.dll via LoadLibrary/GetProcAddress and exercises CreateClient(), InitSession(), and CloseSession() under Wine in both unit tests (//src/ate/test_programs:ate_dll_smoke_test) and live PA integration tests (tests/run_ate_dll_test.sh). --- .github/workflows/main.yml | 15 + .gitignore | 1 + MODULE.bazel | 16 +- rules/windows_binary.bzl | 37 +- src/ate/BUILD.bazel | 13 +- src/ate/test_programs/BUILD.bazel | 47 +++ src/ate/test_programs/ate_dll_smoke.cc | 193 ++++++++++ .../test_programs/run_ate_dll_smoke_test.sh | 81 +++++ src/transport/BUILD.bazel | 1 - src/transport/auth_service/BUILD.bazel | 1 - tests/run_ate_dll_test.sh | 145 ++++++++ third_party/crt/README.md | 23 -- third_party/crt/config/BUILD.bazel | 5 - third_party/crt/config/compiler.bzl | 155 -------- third_party/crt/config/device.bzl | 35 -- third_party/crt/config/execution.bzl | 29 -- third_party/crt/config/features.bzl | 235 ------------ third_party/crt/features/README.md | 61 ---- third_party/crt/features/common/BUILD.bazel | 333 ------------------ third_party/crt/features/embedded/BUILD.bazel | 119 ------- third_party/crt/features/windows/BUILD.bazel | 99 ------ third_party/crt/platforms/x86_32/BUILD.bazel | 15 - third_party/crt/platforms/x86_32/windows.bzl | 21 -- third_party/crt/rules/BUILD.bazel | 5 - third_party/crt/rules/pkg_win.bzl | 81 ----- third_party/crt/rules/transition.bzl | 44 --- .../toolchains/gcc_mxe_mingw32/BUILD.bazel | 50 --- .../gcc_mxe_mingw32/wrappers/driver.sh | 37 -- third_party/crt/util/BUILD.bazel | 10 - third_party/crt/util/pkg_win.py | 225 ------------ third_party/llvm_mingw/BUILD.bazel | 63 ++++ .../llvm_mingw/cc_toolchain_config.bzl | 187 ++++++++++ .../BUILD => llvm_mingw/wrappers/BUILD.bazel} | 20 +- .../wrappers/ar | 0 .../wrappers/cpp | 0 third_party/llvm_mingw/wrappers/driver.sh | 59 ++++ .../wrappers/gcc | 0 .../wrappers/gcov | 0 .../wrappers/ld | 0 .../wrappers/nm | 0 .../wrappers/objcopy | 0 .../wrappers/objdump | 0 .../llvm_mingw/wrappers/pb_defaults_stub.c | 24 ++ .../wrappers/strip | 0 util/BUILD.bazel | 1 + util/ate_client_container.sh | 148 ++++++++ util/containers/ate_client/BUILD.bazel | 22 ++ util/containers/ate_client/Dockerfile | 51 +++ util/containers/ate_client/Dockerfile.bundle | 17 + util/containers/ate_client/build_container.sh | 23 ++ util/containers/ate_client/entrypoint.sh | 28 ++ 51 files changed, 1150 insertions(+), 1625 deletions(-) create mode 100644 src/ate/test_programs/ate_dll_smoke.cc create mode 100755 src/ate/test_programs/run_ate_dll_smoke_test.sh create mode 100755 tests/run_ate_dll_test.sh delete mode 100644 third_party/crt/README.md delete mode 100644 third_party/crt/config/BUILD.bazel delete mode 100644 third_party/crt/config/compiler.bzl delete mode 100644 third_party/crt/config/device.bzl delete mode 100644 third_party/crt/config/execution.bzl delete mode 100644 third_party/crt/config/features.bzl delete mode 100644 third_party/crt/features/README.md delete mode 100644 third_party/crt/features/common/BUILD.bazel delete mode 100644 third_party/crt/features/embedded/BUILD.bazel delete mode 100644 third_party/crt/features/windows/BUILD.bazel delete mode 100644 third_party/crt/platforms/x86_32/BUILD.bazel delete mode 100644 third_party/crt/platforms/x86_32/windows.bzl delete mode 100644 third_party/crt/rules/BUILD.bazel delete mode 100644 third_party/crt/rules/pkg_win.bzl delete mode 100644 third_party/crt/rules/transition.bzl delete mode 100644 third_party/crt/toolchains/gcc_mxe_mingw32/BUILD.bazel delete mode 100755 third_party/crt/toolchains/gcc_mxe_mingw32/wrappers/driver.sh delete mode 100644 third_party/crt/util/BUILD.bazel delete mode 100644 third_party/crt/util/pkg_win.py create mode 100644 third_party/llvm_mingw/BUILD.bazel create mode 100644 third_party/llvm_mingw/cc_toolchain_config.bzl rename third_party/{crt/toolchains/gcc_mxe_mingw32/wrappers/BUILD => llvm_mingw/wrappers/BUILD.bazel} (57%) rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/ar (100%) rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/cpp (100%) create mode 100755 third_party/llvm_mingw/wrappers/driver.sh rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/gcc (100%) rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/gcov (100%) rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/ld (100%) rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/nm (100%) rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/objcopy (100%) rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/objdump (100%) create mode 100644 third_party/llvm_mingw/wrappers/pb_defaults_stub.c rename third_party/{crt/toolchains/gcc_mxe_mingw32 => llvm_mingw}/wrappers/strip (100%) create mode 100644 util/ate_client_container.sh create mode 100644 util/containers/ate_client/BUILD.bazel create mode 100644 util/containers/ate_client/Dockerfile create mode 100644 util/containers/ate_client/Dockerfile.bundle create mode 100755 util/containers/ate_client/build_container.sh create mode 100755 util/containers/ate_client/entrypoint.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 190aa7b9..d6b0cbb4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,6 +42,12 @@ jobs: - name: Test everything # Skip running lint tests as those are run in a previous stage. run: bazelisk test --test_tag_filters=-lint,-fpga //... + # Tagged `manual` (it needs podman and builds a container image), so it is + # not covered by the wildcard above. This is the only check that actually + # executes the Win32 artifact we ship. + - name: Test Win32 ATE DLL + if: always() + run: bazelisk test //src/ate/test_programs:ate_dll_smoke_test --test_output=errors # Run integration tests. integration_tests: @@ -70,6 +76,15 @@ jobs: - name: Run TLS test (RSA) if: always() run: OPENTITAN_VAR_DIR=$(pwd)/.otvar-dev-tls-rsa ./tests/run_tls_test.sh + # Same coverage as the TLS tests above, but driving the Win32 `ate.dll` + # we ship to ATE vendors, from a container that stands in for a Windows + # ATE machine. + - name: Run Windows ATE DLL test (PQ) + if: always() + run: OPENTITAN_VAR_DIR=$(pwd)/.otvar-dev-windll-pq ./tests/run_ate_dll_test.sh --pq + - name: Run Windows ATE DLL test (RSA) + if: always() + run: OPENTITAN_VAR_DIR=$(pwd)/.otvar-dev-windll-rsa ./tests/run_ate_dll_test.sh - name: Run integration tests (SoftHSM2, PQ) if: always() run: OT_PROV_ORCHESTRATOR_PATH="${OT_PROV_ORCHESTRATOR_PATH}" OT_PROV_ORCHESTRATOR_UNPACK="${OT_PROV_ORCHESTRATOR_UNPACK}" OPENTITAN_VAR_DIR=$(pwd)/.otvar-dev-ate-pq ./tests/run_ate_test.sh --pq diff --git a/.gitignore b/.gitignore index 25245f31..e25c46c3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ .opentitan .otdev .otprod +.otvar* bazel-* *.swp result diff --git a/MODULE.bazel b/MODULE.bazel index 0dde7726..ff658b95 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -356,8 +356,8 @@ http_archive( # Legacy HTTP Archives & Cross Compilation # ------------------------------------------------------------------------- -# GCC MXE MinGW32 Toolchain -_MXE_BUILD_FILE = """ +# LLVM-MinGW Win32 Toolchain +_LLVM_MINGW_BUILD_FILE = """ package(default_visibility = ["//visibility:public"]) filegroup( name = "all", @@ -367,14 +367,14 @@ exports_files(glob(["bin/**"])) """ http_archive( - name = "gcc_mxe_mingw32_files", - build_file_content = _MXE_BUILD_FILE, - sha256 = "215b9dfa070687dafab29453b04a075e7955c2c7f3b8d16960207cfde730f126", - strip_prefix = "mxe", - urls = ["https://github.com/lowRISC/crt/releases/download/v0.4.14/mxe-binaries-win32.tar.xz"], + name = "llvm_mingw_files", + build_file_content = _LLVM_MINGW_BUILD_FILE, + sha256 = "4d905bae713182f1a2b4d33875fe5aa544ce9fc04cc153acb47755a90ca62f16", + strip_prefix = "llvm-mingw-20260908-msvcrt-ubuntu-22.04-x86_64", + urls = ["https://github.com/mstorsjo/llvm-mingw/releases/download/20260908/llvm-mingw-20260908-msvcrt-ubuntu-22.04-x86_64.tar.xz"], ) -register_toolchains("//third_party/crt/toolchains/gcc_mxe_mingw32:cc_toolchain_pc-win32") +register_toolchains("//third_party/llvm_mingw:cc_toolchain_win32") # Protobuf matchers http_archive( diff --git a/rules/windows_binary.bzl b/rules/windows_binary.bzl index 5d9a3963..67a1f98f 100644 --- a/rules/windows_binary.bzl +++ b/rules/windows_binary.bzl @@ -4,13 +4,15 @@ def _windows_platform_transition_impl(settings, attr): return { - "//command_line_option:platforms": "//third_party/crt/platforms/x86_32:win32", + "//command_line_option:platforms": "//third_party/llvm_mingw:win32", } windows_platform_transition = transition( implementation = _windows_platform_transition_impl, inputs = [], - outputs = ["//command_line_option:platforms"], + outputs = [ + "//command_line_option:platforms", + ], ) def _windows_binary_impl(ctx): @@ -18,30 +20,24 @@ def _windows_binary_impl(ctx): # ctx.attr.dep is a list because the transition is 1:1. dep = ctx.attr.dep[0] - # Determine the output file name based on the target name. - # We expect a DLL for Windows binaries (linkshared=True). - output = ctx.actions.declare_file(ctx.label.name + ".dll") + # Determine the output file name based on the target name. DLLs are + # produced by cc_binary targets with linkshared = True, executables by + # regular cc_binary targets. + extension = ctx.attr.extension + output = ctx.actions.declare_file(ctx.label.name + "." + extension) found = False for f in dep[DefaultInfo].files.to_list(): - if f.extension == "dll": + if f.extension == extension: ctx.actions.symlink(output = output, target_file = f) found = True break if not found: - # Fallback: just forward everything if no DLL found (though unexpected for this specific use case) - # Or fail. Let's fail to be explicit, as we expect a DLL. - # But maybe it's an exe? - for f in dep[DefaultInfo].files.to_list(): - if f.extension == "exe": - ctx.actions.declare_file(ctx.label.name + ".exe") - ctx.actions.symlink(output = output, target_file = f) - found = True - break - - if not found: - fail("Could not find .dll or .exe in dependency output. Files: " + str(dep[DefaultInfo].files.to_list())) + fail("Could not find .{} in dependency output. Files: {}".format( + extension, + str(dep[DefaultInfo].files.to_list()), + )) return [ DefaultInfo( @@ -54,6 +50,11 @@ windows_binary = rule( implementation = _windows_binary_impl, attrs = { "dep": attr.label(cfg = windows_platform_transition), + "extension": attr.string( + default = "dll", + values = ["dll", "exe"], + doc = "Artifact extension produced by `dep`.", + ), "_allowlist_function_transition": attr.label( default = "@bazel_tools//tools/allowlists/function_transition_allowlist", ), diff --git a/src/ate/BUILD.bazel b/src/ate/BUILD.bazel index ad93ed72..d93a1e04 100644 --- a/src/ate/BUILD.bazel +++ b/src/ate/BUILD.bazel @@ -2,7 +2,7 @@ # Licensed under the Apache License, Version 2.0, see LICENSE for details. # SPDX-License-Identifier: Apache-2.0 -load("//third_party/crt/rules:pkg_win.bzl", "pkg_win") +load("@rules_pkg//pkg:zip.bzl", "pkg_zip") load("//rules:windows_binary.bzl", "windows_binary") load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") @@ -16,6 +16,14 @@ WINDOWS_LIBS = [ "-lstdc++", # Standard C++ library ] +# Header-only view of the public DLL C API, for consumers that load `ate.dll` +# dynamically (e.g. the Win32 smoke test) and must not link the client itself. +cc_library( + name = "ate_api_hdr", + hdrs = ["ate_api.h"], + includes = ["."], +) + cc_library( name = "ate_client", srcs = ["ate_client.cc"], @@ -126,14 +134,13 @@ windows_binary( dep = ":ate_bin", ) -pkg_win( +pkg_zip( name = "windows", srcs = [ "ate_api.h", "ate_perso_blob.h", ":ate", ], - platform = "//third_party/crt/platforms/x86_32:win32", ) go_library( diff --git a/src/ate/test_programs/BUILD.bazel b/src/ate/test_programs/BUILD.bazel index 60652a69..96fd103a 100644 --- a/src/ate/test_programs/BUILD.bazel +++ b/src/ate/test_programs/BUILD.bazel @@ -5,6 +5,7 @@ package(default_visibility = ["//visibility:public"]) load("@io_bazel_rules_go//go:def.bzl", "go_binary") +load("//rules:windows_binary.bzl", "windows_binary") cc_binary( name = "cp", @@ -54,3 +55,49 @@ go_binary( "@org_golang_google_grpc//resolver:go_default_library", ], ) + +# Win32 host that loads the shipped `ate.dll` and calls `CreateClient()`. Built +# for Windows only; it is executed under wine by `:ate_dll_smoke_test`. +cc_binary( + name = "ate_dll_smoke_bin", + srcs = ["ate_dll_smoke.cc"], + linkopts = ["-lstdc++"], + target_compatible_with = [ + "@platforms//os:windows", + ], + deps = ["//src/ate:ate_api_hdr"], +) + +windows_binary( + name = "ate_dll_smoke", + dep = ":ate_dll_smoke_bin", + extension = "exe", +) + +# Loads the Win32 ATE DLL inside a Wine container and exercises `CreateClient()` +# in both insecure and mTLS modes. +# +# Tagged `manual` because it requires `podman` and `openssl` on the host, and +# builds a container image on first run; run it explicitly with: +# bazelisk test //src/ate/test_programs:ate_dll_smoke_test +sh_test( + name = "ate_dll_smoke_test", + srcs = ["run_ate_dll_smoke_test.sh"], + data = [ + ":ate_dll_smoke", + "//src/ate", + "//util:ate_client_container.sh", + "//util/containers/ate_client:container_files", + ], + env = { + "SMOKE_EXE": "$(rootpath :ate_dll_smoke)", + "ATE_DLL": "$(rootpath //src/ate)", + }, + tags = [ + "external", + "local", + "manual", + "no-remote", + "no-sandbox", + ], +) diff --git a/src/ate/test_programs/ate_dll_smoke.cc b/src/ate/test_programs/ate_dll_smoke.cc new file mode 100644 index 00000000..7e503b09 --- /dev/null +++ b/src/ate/test_programs/ate_dll_smoke.cc @@ -0,0 +1,193 @@ +// Copyright lowRISC contributors (OpenTitan project). +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +// Win32 smoke-test host for the shipped ATE DLL. +// +// This mirrors how an ATE vendor consumes the release artifact: it loads +// `ate.dll` with LoadLibrary() and calls the exported `CreateClient()` entry +// point. No provisioning appliance is required: `CreateClient()` only builds +// the gRPC channel (connection establishment is lazy), which is enough to +// catch crashes in gRPC's static initialization / channel construction code +// on the 32-bit MinGW toolchain. +// +// Exit codes: +// 0 - CreateClient() succeeded. +// 1 - CreateClient() returned an error status. +// 2 - harness setup error (DLL missing, symbol missing, ...). +// 42 - the process took an unhandled exception (e.g. access violation). + +#include + +#include +#include + +#include "ate_api.h" + +namespace { + +typedef int (*CreateClientFn)(ate_client_ptr *, client_options_t *); +typedef void (*DestroyClientFn)(ate_client_ptr); +typedef int (*InitSessionFn)(ate_client_ptr, const char *, const char *); +typedef int (*CloseSessionFn)(ate_client_ptr); + +constexpr int kSetupError = 2; +constexpr int kCrashExitCode = 42; + +// Reports unhandled exceptions (such as the 0xC0000005 access violation seen +// with miscompiled gRPC code) with a deterministic exit code instead of +// popping up a debugger. +LONG WINAPI CrashFilter(EXCEPTION_POINTERS *info) { + std::printf("[ate_dll_smoke] CRASH code=0x%08lx addr=%p\n", + static_cast(info->ExceptionRecord->ExceptionCode), + info->ExceptionRecord->ExceptionAddress); + if (info->ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION && + info->ExceptionRecord->NumberParameters >= 2) { + std::printf( + "[ate_dll_smoke] access violation %s address 0x%08lx\n", + info->ExceptionRecord->ExceptionInformation[0] ? "writing" : "reading", + static_cast( + info->ExceptionRecord->ExceptionInformation[1])); + } + std::fflush(stdout); + TerminateProcess(GetCurrentProcess(), kCrashExitCode); + return EXCEPTION_EXECUTE_HANDLER; +} + +void PrintUsage(const char *argv0) { + std::printf( + "usage: %s [--dll=ate.dll] [--mtls] [--mlkem] [--mldsa]\n" + " [--cert=PATH] [--key=PATH] [--ca=PATH] [--target=ADDR]\n" + " [--sku=NAME --sku_auth=PASSWORD]\n" + "\n" + "When --sku and --sku_auth are given, the harness additionally runs\n" + "InitSession()/CloseSession() against the target, which requires a live\n" + "Provisioning Appliance.\n", + argv0); +} + +const char *FlagValue(const char *arg, const char *name) { + size_t len = std::strlen(name); + if (std::strncmp(arg, name, len) == 0 && arg[len] == '=') { + return arg + len + 1; + } + return nullptr; +} + +} // namespace + +int main(int argc, char **argv) { + const char *dll_path = "ate.dll"; + const char *cert = "certs/ate-client-cert.pem"; + const char *key = "certs/ate-client-key.pem"; + const char *ca = "certs/ca-cert.pem"; + const char *target = "localhost:5000"; + const char *sku = ""; + const char *sku_auth = ""; + bool mtls = false; + bool mlkem = false; + bool mldsa = false; + + for (int i = 1; i < argc; ++i) { + const char *value = nullptr; + if (std::strcmp(argv[i], "--mtls") == 0) { + mtls = true; + } else if (std::strcmp(argv[i], "--mlkem") == 0) { + mlkem = true; + } else if (std::strcmp(argv[i], "--mldsa") == 0) { + mldsa = true; + } else if ((value = FlagValue(argv[i], "--dll")) != nullptr) { + dll_path = value; + } else if ((value = FlagValue(argv[i], "--cert")) != nullptr) { + cert = value; + } else if ((value = FlagValue(argv[i], "--key")) != nullptr) { + key = value; + } else if ((value = FlagValue(argv[i], "--ca")) != nullptr) { + ca = value; + } else if ((value = FlagValue(argv[i], "--target")) != nullptr) { + target = value; + } else if ((value = FlagValue(argv[i], "--sku")) != nullptr) { + sku = value; + } else if ((value = FlagValue(argv[i], "--sku_auth")) != nullptr) { + sku_auth = value; + } else { + PrintUsage(argv[0]); + return kSetupError; + } + } + const bool run_session = sku[0] != '\0'; + + SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX); + SetUnhandledExceptionFilter(CrashFilter); + + std::printf("[ate_dll_smoke] dll=%s mtls=%d mlkem=%d mldsa=%d\n", dll_path, + mtls, mlkem, mldsa); + std::fflush(stdout); + + HMODULE dll = LoadLibraryA(dll_path); + if (dll == nullptr) { + std::printf("[ate_dll_smoke] LoadLibraryA(%s) failed: %lu\n", dll_path, + GetLastError()); + return kSetupError; + } + auto create_client = + reinterpret_cast(GetProcAddress(dll, "CreateClient")); + auto destroy_client = + reinterpret_cast(GetProcAddress(dll, "DestroyClient")); + auto init_session = + reinterpret_cast(GetProcAddress(dll, "InitSession")); + auto close_session = + reinterpret_cast(GetProcAddress(dll, "CloseSession")); + if (create_client == nullptr || destroy_client == nullptr || + init_session == nullptr || close_session == nullptr) { + std::printf("[ate_dll_smoke] GetProcAddress failed: %lu\n", GetLastError()); + return kSetupError; + } + + client_options_t options; + std::memset(&options, 0, sizeof(options)); + options.pa_target = target; + options.load_balancing_policy = ""; + options.pem_cert_chain = cert; + options.pem_private_key = key; + options.pem_root_certs = ca; + options.sku_tokens = ""; + options.enable_mtls = mtls; + options.enable_mlkem_tls = mlkem; + options.enable_mldsa_tls = mldsa; + + std::printf("[ate_dll_smoke] calling CreateClient...\n"); + std::fflush(stdout); + + ate_client_ptr client = nullptr; + int result = create_client(&client, &options); + std::printf("[ate_dll_smoke] CreateClient returned %d (client=%p)\n", result, + reinterpret_cast(client)); + std::fflush(stdout); + + // Exercising a session requires a live Provisioning Appliance: this is what + // actually drives the mTLS handshake and a round-trip RPC. + if (result == 0 && run_session) { + std::printf("[ate_dll_smoke] calling InitSession(sku=%s)...\n", sku); + std::fflush(stdout); + result = init_session(client, sku, sku_auth); + std::printf("[ate_dll_smoke] InitSession returned %d\n", result); + std::fflush(stdout); + + if (result == 0) { + int close_result = close_session(client); + std::printf("[ate_dll_smoke] CloseSession returned %d\n", close_result); + std::fflush(stdout); + if (close_result != 0) { + result = close_result; + } + } + } + + if (client != nullptr) { + destroy_client(client); + } + std::printf("[ate_dll_smoke] DONE\n"); + std::fflush(stdout); + return result == 0 ? 0 : 1; +} diff --git a/src/ate/test_programs/run_ate_dll_smoke_test.sh b/src/ate/test_programs/run_ate_dll_smoke_test.sh new file mode 100755 index 00000000..eaca5520 --- /dev/null +++ b/src/ate/test_programs/run_ate_dll_smoke_test.sh @@ -0,0 +1,81 @@ +#!/bin/bash +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Hermetic Win32 ATE DLL smoke test. +# +# Installs the Windows bundle into the simulated Windows ATE machine container +# and calls `CreateClient()` there in both insecure and mTLS mode. No +# provisioning appliance is needed: channel creation is lazy, so this exercises +# gRPC channel construction only -- which is where the 32-bit MinGW toolchain +# has been observed to miscompile gRPC (0xC0000005). The container runs with +# networking disabled to keep the test hermetic. For a test that also performs +# RPCs against a live PA, see tests/run_ate_dll_test.sh. + +set -euo pipefail + +source util/ate_client_container.sh + +readonly OPENSSL_BIN="${OPENSSL:-openssl}" + +ate_client_require_podman +if ! command -v "${OPENSSL_BIN}" >/dev/null 2>&1; then + echo "ERROR: '${OPENSSL_BIN}' not found in PATH." + exit 1 +fi + +readonly SMOKE_EXE="${SMOKE_EXE:?SMOKE_EXE must point at ate_dll_smoke.exe}" +readonly ATE_DLL="${ATE_DLL:?ATE_DLL must point at ate.dll}" + +readonly REPO_TOP="${PWD}" +readonly WORK_DIR="${TEST_TMPDIR:-$(mktemp -d)}/ate_dll_smoke" +readonly CERTS_DIR="${WORK_DIR}/certs" + +ate_client_ensure_base_image "${REPO_TOP}" + +ate_client_stage_bundle "${WORK_DIR}" "${SMOKE_EXE}" "${ATE_DLL}" +ate_client_build_image "${REPO_TOP}" "${WORK_DIR}" + +# Throwaway credentials. They only need to parse: no handshake is performed. +echo "Generating test credentials..." +mkdir -p "${CERTS_DIR}" +"${OPENSSL_BIN}" req -x509 -newkey rsa:2048 -nodes -days 1 \ + -keyout "${CERTS_DIR}/ca-key.pem" \ + -out "${CERTS_DIR}/ca-cert.pem" \ + -subj "/CN=ate-dll-smoke-ca" 2>/dev/null +"${OPENSSL_BIN}" req -newkey rsa:2048 -nodes \ + -keyout "${CERTS_DIR}/ate-client-key.pem" \ + -out "${CERTS_DIR}/ate-client.csr" \ + -subj "/CN=ate-dll-smoke-client" 2>/dev/null +"${OPENSSL_BIN}" x509 -req -days 1 \ + -in "${CERTS_DIR}/ate-client.csr" \ + -CA "${CERTS_DIR}/ca-cert.pem" \ + -CAkey "${CERTS_DIR}/ca-key.pem" \ + -CAcreateserial \ + -out "${CERTS_DIR}/ate-client-cert.pem" 2>/dev/null +rm -f "${CERTS_DIR}/ate-client.csr" + +failures=0 +run_case() { + local name="$1" + shift + echo "==============================================================" + echo "== ${name}" + echo "==============================================================" + local status=0 + ate_client_run "none" "${CERTS_DIR}" "$@" || status=$? + ate_client_report_status "${name}" "${status}" || failures=$((failures + 1)) +} + +run_case "insecure" --target=localhost:5000 +run_case "mtls" --target=localhost:5000 --mtls \ + --cert=certs/ate-client-cert.pem \ + --key=certs/ate-client-key.pem \ + --ca=certs/ca-cert.pem + +if [[ "${failures}" -ne 0 ]]; then + echo "${failures} case(s) failed." + exit 1 +fi +echo "All cases passed." diff --git a/src/transport/BUILD.bazel b/src/transport/BUILD.bazel index 114aff37..5f717cd1 100644 --- a/src/transport/BUILD.bazel +++ b/src/transport/BUILD.bazel @@ -3,7 +3,6 @@ # SPDX-License-Identifier: Apache-2.0 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") -load("//third_party/crt/rules:pkg_win.bzl", "pkg_win") package(default_visibility = ["//visibility:public"]) diff --git a/src/transport/auth_service/BUILD.bazel b/src/transport/auth_service/BUILD.bazel index ad4f01cd..4dbb7485 100755 --- a/src/transport/auth_service/BUILD.bazel +++ b/src/transport/auth_service/BUILD.bazel @@ -3,7 +3,6 @@ # SPDX-License-Identifier: Apache-2.0 load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") -load("//third_party/crt/rules:pkg_win.bzl", "pkg_win") package(default_visibility = ["//visibility:public"]) diff --git a/tests/run_ate_dll_test.sh b/tests/run_ate_dll_test.sh new file mode 100755 index 00000000..44ece83c --- /dev/null +++ b/tests/run_ate_dll_test.sh @@ -0,0 +1,145 @@ +#!/bin/bash +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Windows ATE client integration test. +# +# This is the Win32 counterpart of `tests/run_tls_test.sh`. Instead of the +# native Linux client, it drives the *shipped* `ate.dll` from a container that +# stands in for a Windows ATE machine: the DLL is installed into a Wine image +# and reaches the live Provisioning Appliance from its own network namespace, +# over a real interface, using the same deployment credentials. +# +# It covers what no Linux-side test can: that the cross-compiled artifact we +# hand to ATE vendors actually runs. The 32-bit MinGW toolchain has miscompiled +# gRPC's channel setup in the past, which is invisible to a cross-compile-only +# CI. + +set -e + +# Explicitly enable job control so that we can run the SPM server +# in the background and still be able to run other commands in parallel. +set -m + +# Ensure we are running from the repository root +cd "$(dirname "$0")/.." + +source util/ate_client_container.sh + +ate_client_require_podman + +# The ATE container reaches the appliance through podman's host alias rather +# than over loopback. Naming it here (before the deployment generates +# certificates) puts it in the PA certificate's SAN list, so TLS hostname +# verification succeeds. `OTPROV_DNS_PA` feeds only that SAN; host-side clients +# keep working through the unchanged `IP.1 = ${OTPROV_IP_PA}` entry. +readonly ATE_CLIENT_PA_HOST="host.containers.internal" +export OTPROV_DNS_PA="${ATE_CLIENT_PA_HOST}" + +# Build and deploy the provisioning infrastructure. +source util/integration_test_setup.sh + +# Dump PA logs on failure +dump_pa_logs() { + echo "----------------------------------------------------------------" + echo "Dumping PA logs (provapp-paserver-1)..." + podman logs provapp-paserver-1 + echo "----------------------------------------------------------------" +} +trap dump_pa_logs ERR + +echo "Building Win32 ATE client artifacts ..." +bazelisk build //src/ate:ate //src/ate/test_programs:ate_dll_smoke + +# Enabled only from here on: the deployment scripts sourced above are not +# written with pipefail in mind. +set -o pipefail + +# `cquery --output=files` reports workspace-relative paths for local artifacts +# but output-base-relative paths for external repositories. +OUTPUT_BASE="$(bazelisk info output_base)" +WORKSPACE_ROOT="${PWD}" +artifact_path() { + if [[ "$1" == external/* ]]; then + echo "${OUTPUT_BASE}/$1" + else + echo "${WORKSPACE_ROOT}/$1" + fi +} + +# Resolves a single-output target to an absolute path. The query output is +# assigned to a variable first so that a cquery failure is fatal: inside a +# pipeline or a process substitution its exit status would be discarded and we +# would carry on with an empty path, failing much later and less obviously. +resolve_artifact() { + local label="$1" + local files + files="$(bazelisk cquery --output=files "${label}")" + if [[ -z "${files}" ]]; then + echo "ERROR: cquery returned no output files for ${label}." >&2 + return 1 + fi + artifact_path "$(echo "${files}" | tail -n 1)" +} + +ATE_DLL="$(resolve_artifact //src/ate:ate)" +SMOKE_EXE="$(resolve_artifact //src/ate/test_programs:ate_dll_smoke)" + +# Install the freshly built bundle into the ATE machine image. +WORK_DIR="${OPENTITAN_VAR_DIR}/ate_dll_container" +ate_client_ensure_base_image "${WORKSPACE_ROOT}" +ate_client_stage_bundle "${WORK_DIR}" "${SMOKE_EXE}" "${ATE_DLL}" +ate_client_build_image "${WORKSPACE_ROOT}" "${WORK_DIR}" + +# Hand the ATE machine the same credentials the native client uses. +CERTS_DIR="${WORK_DIR}/certs" +mkdir -p "${CERTS_DIR}" +cp -f "${DEPLOYMENT_DIR}/certs/out/ate-client-cert.pem" \ + "${DEPLOYMENT_DIR}/certs/out/ate-client-key.pem" \ + "${DEPLOYMENT_DIR}/certs/out/ca-cert.pem" \ + "${CERTS_DIR}/" + +# Prefer an isolated network namespace, which exercises a real network path to +# the appliance. Older podman releases do not provide the host alias, so fall +# back to sharing the host's namespace (the PA certificate also carries +# `IP.1 = ${OTPROV_IP_PA}`, so loopback still verifies). +NETWORK="bridge" +PA_HOST="${ATE_CLIENT_PA_HOST}" +if ! podman run --rm --network=bridge --entrypoint=/bin/bash \ + "${ATE_CLIENT_IMAGE}" \ + -c "timeout 5 bash -c '/dev/null 2>&1; then + echo "WARNING: ${ATE_CLIENT_PA_HOST}:${OTPROV_PORT_PA} is unreachable from a" + echo " bridged container; falling back to host networking." + NETWORK="host" + PA_HOST="${OTPROV_IP_PA}" +fi + +PQ_FLAGS=() +if [[ "${ENABLE_MLKEM_TLS}" == "true" ]]; then + PQ_FLAGS+=("--mlkem") +fi +if [[ "${ENABLE_MLDSA_TLS}" == "true" ]]; then + PQ_FLAGS+=("--mldsa") +fi + +echo "Running Windows ATE DLL test in the ATE machine container ..." +echo " target: ${PA_HOST}:${OTPROV_PORT_PA}" +echo " network: ${NETWORK}" +echo " mlkem: ${ENABLE_MLKEM_TLS} mldsa: ${ENABLE_MLDSA_TLS}" + +status=0 +ate_client_run "${NETWORK}" "${CERTS_DIR}" \ + --mtls \ + "${PQ_FLAGS[@]}" \ + --cert=certs/ate-client-cert.pem \ + --key=certs/ate-client-key.pem \ + --ca=certs/ca-cert.pem \ + --target="${PA_HOST}:${OTPROV_PORT_PA}" \ + --sku="sival" \ + --sku_auth="test_password" || status=$? + +ate_client_report_status "windows-ate-dll-mtls" "${status}" + +echo "Done." diff --git a/third_party/crt/README.md b/third_party/crt/README.md deleted file mode 100644 index bd8df6d4..00000000 --- a/third_party/crt/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# CRT Toolchain Vendor Directory - -This directory contains the vendored configuration, rules, and platform definitions for the `crt` (Compiler Runtime) toolchain, specifically configured for OpenTitan provisioning tasks (e.g., Windows cross-compilation). - -## Origin - -The contents of this directory were consolidated from various parts of the repository to create a self-contained toolchain definition. - -- **Upstream Project:** [lowRISC/crt](https://github.com/lowRISC/crt) -- **Binaries:** The actual toolchain binaries (GCC MinGW) are fetched as an `http_archive` defined in `MODULE.bazel`. - -## Structure - -- `config/`: Compiler and device configuration Starlark files (formerly in `//config`). -- `features/`: Feature definitions for the toolchain (formerly in `//features`). -- `platforms/`: Platform definitions, specifically for `x86_32` Windows (formerly in `//platforms`). -- `rules/`: Custom Bazel rules for the toolchain, such as `pkg_win` (formerly in `//rules`). -- `toolchains/`: Toolchain definitions and wrappers (formerly in `//toolchains`). -- `util/`: Helper scripts and utilities (formerly in `//util`). - -## Usage - -These files are used to configure the C++ toolchain for cross-compiling to Windows. The entry points are primarily in `MODULE.bazel` (registering the toolchain) and the `BUILD.bazel` files within this directory structure. diff --git a/third_party/crt/config/BUILD.bazel b/third_party/crt/config/BUILD.bazel deleted file mode 100644 index c85882e6..00000000 --- a/third_party/crt/config/BUILD.bazel +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -package(default_visibility = ["//visibility:public"]) diff --git a/third_party/crt/config/compiler.bzl b/third_party/crt/config/compiler.bzl deleted file mode 100644 index a3cc989b..00000000 --- a/third_party/crt/config/compiler.bzl +++ /dev/null @@ -1,155 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load("//third_party/crt/config:features.bzl", "FeatureSetInfo", "feature_set_subst") -load( - "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", - "artifact_name_pattern", - "tool_path", -) -load("@rules_cc//cc:defs.bzl", "cc_toolchain") - -PARAM_DEFAULTS = { - "host_system_name": "x86_64-unknown-linux-gnu", - "target_system_name": "unknown", - "target_libc": "unknown", - "compiler": "unknown", - "abi_version": "unknown", - "abi_libc_version": "unknown", -} - -# This provider wraps the opaque artifact_name_pattern object so it can be passed -# from the 'artifact_name' rule to the 'toolchain_config' rule. -ArtifactNamePatternInfo = provider( - fields = ["pattern"], - doc = "Wraps an artifact_name_pattern object for passing between rules.", -) - -def listify_flags(flag, args = [], spaces_in_args = False): - args = [flag.format(p) for p in args] - value = "|".join(args) - if not spaces_in_args: - value = value.replace(" ", "|") - return value - -def union(*args, **kwargs): - d = {} - for a in args: - d.update(a) - d.update(kwargs) - return d - -def _toolchain_config_impl(ctx): - tool_paths = [ - tool_path(name = k, path = v) - for k, v in ctx.attr.tools.items() - ] - params = dict(**PARAM_DEFAULTS) - params.update(ctx.attr.params) - features = feature_set_subst(ctx.attr.feature_set[FeatureSetInfo], **ctx.attr.substitutions) - artifact_name_patterns = [a[ArtifactNamePatternInfo].pattern for a in ctx.attr.artifact_naming] - - return cc_common.create_cc_toolchain_config_info( - ctx = ctx, - toolchain_identifier = ctx.attr.toolchain_identifier, - target_cpu = ctx.attr.architecture, - cxx_builtin_include_directories = ctx.attr.include_directories, - host_system_name = params["host_system_name"], - target_system_name = params["target_system_name"], - target_libc = params["target_libc"], - compiler = params["compiler"], - abi_version = params["abi_version"], - abi_libc_version = params["abi_libc_version"], - tool_paths = tool_paths, - features = features.values(), - artifact_name_patterns = artifact_name_patterns, - ) - -toolchain_config = rule( - implementation = _toolchain_config_impl, - attrs = { - "architecture": attr.string(doc = "Target architecture"), - "artifact_naming": attr.label_list(providers = [ArtifactNamePatternInfo], doc = "Naming conventions"), - "feature_set": attr.label(providers = [FeatureSetInfo], doc = "Features of this toolchain"), - "substitutions": attr.string_dict(doc = "Substitutions for the feature_set"), - "tools": attr.string_dict(doc = "Mapping of tool names to their wrapper paths"), - "toolchain_identifier": attr.string( - mandatory = True, - doc = "Indentifier used by the toolchain, this should be consistent with the cc_toolchain rule attribute", - ), - "include_directories": attr.string_list(doc = "Compiler-specific include directories"), - "params": attr.string_dict(doc = "Toolchain config parameters", default = {}), - }, - provides = [CcToolchainConfigInfo], -) - -def _artifact_name_impl(ctx): - return [ArtifactNamePatternInfo(pattern = artifact_name_pattern( - category_name = ctx.attr.category, - prefix = ctx.attr.prefix, - extension = ctx.attr.extension, - ))] - -artifact_name = rule( - implementation = _artifact_name_impl, - attrs = { - "category": attr.string(mandatory = True, doc = "The category of artifacts that this selection applies to."), - "prefix": attr.string(doc = "The prefix for creating the artifact for this selection."), - "extension": attr.string(doc = "The extension for creating the artifact for this selection."), - }, - provides = [ArtifactNamePatternInfo], -) - -def setup( - name, - architecture, - artifact_naming, - feature_set, - tools, - compiler_components, - include_directories, - constraints, - isystem = "-isystem{}", - substitutions = {}, - params = {}): - subst = { - "[SYSTEM_INCLUDES]": listify_flags(isystem, include_directories), - } - subst.update(substitutions) - - toolchain_config( - name = name + "_config", - architecture = architecture, - artifact_naming = artifact_naming, - feature_set = feature_set, - tools = tools, - toolchain_identifier = name, - include_directories = include_directories, - params = params, - substitutions = subst, - ) - - cc_toolchain( - name = name, - all_files = compiler_components, - compiler_files = compiler_components, - dwp_files = compiler_components, - linker_files = compiler_components, - objcopy_files = compiler_components, - strip_files = compiler_components, - as_files = compiler_components, - ar_files = compiler_components, - supports_param_files = False, - toolchain_config = ":{}_config".format(name), - ) - - native.toolchain( - name = "cc_toolchain_" + name, - exec_compatible_with = [ - "@platforms//cpu:x86_64", - ], - target_compatible_with = constraints, - toolchain = ":" + name, - toolchain_type = "@bazel_tools//tools/cpp:toolchain_type", - ) diff --git a/third_party/crt/config/device.bzl b/third_party/crt/config/device.bzl deleted file mode 100644 index 7f54a61e..00000000 --- a/third_party/crt/config/device.bzl +++ /dev/null @@ -1,35 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -def device_config( - name, - architecture, - constraints, - feature_set = "//third_party/crt/features/common", - artifact_naming = [], - substitutions = {}): - """Creates a device_config struct. - - The device_config struct is an instantiation of a given platform. The - device config structs are used to configure toolchains. As such, there - should be a one-to-one correspondence between named platforms and - device configs. - - Args: - name: str; the name of the device configuration. - architecture: str; the name of the device architecture (e.g. x86_64). - constraints: list[label]; the platform constraints for this device. - artifact_naming: list[label]; a list of artifact naming conventions for - this device (e.g. ".exe" for windows platforms). - substitutions: dict[str, str]; a set of substitutions to apply to the - feature_set when bulding the toolchain configuration. - """ - return struct( - name = name, - architecture = architecture, - constraints = constraints, - feature_set = feature_set, - artifact_naming = artifact_naming, - substitutions = substitutions, - ) diff --git a/third_party/crt/config/execution.bzl b/third_party/crt/config/execution.bzl deleted file mode 100644 index 016e1bfc..00000000 --- a/third_party/crt/config/execution.bzl +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -ExecConfigInfo = provider(fields = ["program", "params", "data", "preparation", "substitutions"]) - -def _exec_config_impl(ctx): - return [ExecConfigInfo( - program = ctx.attr.program, - params = [ctx.expand_location(p, ctx.attr.data) for p in ctx.attr.params], - data = [f for f in ctx.files.data], - preparation = ctx.attr.preparation, - substitutions = {k: ctx.expand_location(v, ctx.attr.data) for k, v in ctx.attr.substitutions.items()}, - )] - -exec_config = rule( - implementation = _exec_config_impl, - attrs = { - "program": attr.label( - doc = "Program providing execution services for a platform", - executable = True, - cfg = "exec", - ), - "data": attr.label_list(allow_files = True, doc = "Files needed at runtime."), - "params": attr.string_list(doc = "Parameters for the program"), - "preparation": attr.string(default = "none", values = ["none", "windows"], doc = "Special preparation type"), - "substitutions": attr.string_dict(doc = "Substitutions to apply at runtime"), - }, -) diff --git a/third_party/crt/config/features.bzl b/third_party/crt/config/features.bzl deleted file mode 100644 index cb979039..00000000 --- a/third_party/crt/config/features.bzl +++ /dev/null @@ -1,235 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load( - "@bazel_tools//tools/cpp:cc_toolchain_config_lib.bzl", - __feature = "feature", - __flag_group = "flag_group", - __flag_set = "flag_set", - __with_feature_set = "with_feature_set", -) -load("@bazel_tools//tools/build_defs/cc:action_names.bzl", "ACTION_NAMES") - -with_feature_set = __with_feature_set - -CPP_ALL_COMPILE_ACTIONS = [ - ACTION_NAMES.assemble, - ACTION_NAMES.preprocess_assemble, - ACTION_NAMES.linkstamp_compile, - ACTION_NAMES.cpp_compile, - ACTION_NAMES.cpp_header_parsing, - ACTION_NAMES.cpp_module_compile, - ACTION_NAMES.cpp_module_codegen, - ACTION_NAMES.lto_backend, - ACTION_NAMES.clif_match, -] - -C_ALL_COMPILE_ACTIONS = [ - ACTION_NAMES.assemble, - ACTION_NAMES.c_compile, -] - -LD_ALL_ACTIONS = [ - ACTION_NAMES.cpp_link_executable, - ACTION_NAMES.cpp_link_dynamic_library, - ACTION_NAMES.cpp_link_nodeps_dynamic_library, -] - -FeatureSetInfo = provider(fields = ["features", "subst"]) - -# This provider wraps the feature configuration so it can be passed from the 'feature' -# rule to the 'feature_set' rule. -FeatureInfo = provider( - fields = ["feature_config"], - doc = "Wraps a C++ feature configuration object for passing between rules.", -) - -def reify_flag_group( - flags = [], - flag_groups = [], - iterate_over = None, - expand_if_available = None, - expand_if_not_available = None, - expand_if_true = None, - expand_if_false = None, - expand_if_equal = None, - type_name = None, - subst = {}): - flags2 = [] - for f in flags: - if f in subst: - if f.startswith("[") and f.endswith("]"): - flags2.extend(subst[f].split("|")) - else: - for k, v in subst.items(): - f = f.replace(k, v) - flags2.append(f) - - return __flag_group( - flags2, - flag_groups, - iterate_over, - expand_if_available, - expand_if_not_available, - expand_if_true, - expand_if_false, - expand_if_equal, - ) - -def reify_with_features_set( - features, - not_features, - type_name): - if type_name != "with_feature_set": - fail("the argument to with_features must be an array of values created by with_feature_set") - return with_feature_set( - features, - not_features, - ) - -def reify_flag_set( - actions = [], - with_features = [], - flag_groups = [], - type_name = None): - return __flag_set( - actions, - with_features = [reify_with_features_set(**v) for v in with_features], - flag_groups = [reify_flag_group(**v) for v in flag_groups], - ) - -def feature_set_subst(fs, **kwargs): - subst = dict(fs.subst) - subst.update(kwargs) - features = {} - for name, feature in fs.features.items(): - flag_sets = [ - __flag_set( - f.actions, - f.with_features, - [ - reify_flag_group( - g.flags, - g.flag_groups, - g.iterate_over, - g.expand_if_available, - g.expand_if_not_available, - g.expand_if_true, - g.expand_if_false, - g.expand_if_equal, - subst = subst, - ) - for g in f.flag_groups - ], - ) - for f in feature.flag_sets - ] - features[name] = __feature( - name = feature.name, - enabled = feature.enabled, - flag_sets = flag_sets, - requires = feature.requires, - implies = feature.implies, - provides = feature.provides, - ) - return features - -def flag_group( - flags = [], - flag_groups = [], - iterate_over = None, - expand_if_available = None, - expand_if_not_available = None, - expand_if_true = None, - expand_if_false = None, - expand_if_equal = None): - return { - "flags": flags, - "flag_groups": flag_groups, - "iterate_over": iterate_over, - "expand_if_available": expand_if_available, - "expand_if_not_available": expand_if_not_available, - "expand_if_true": expand_if_true, - "expand_if_false": expand_if_false, - "expand_if_equal": expand_if_equal, - } - -def flag_set( - actions = [], - with_features = [], - flag_groups = []): - return json.encode({ - "actions": actions, - "with_features": with_features, - "flag_groups": flag_groups, - }) - -def _feature_impl(ctx): - f = __feature( - name = ctx.attr.name, - enabled = ctx.attr.enabled, - flag_sets = [reify_flag_set(**json.decode(v)) for v in ctx.attr.flag_sets], - requires = ctx.attr.requires, - implies = ctx.attr.implies, - provides = ctx.attr.provides, - ) - return [ - FeatureInfo(feature_config = f), - ] - -feature = rule( - implementation = _feature_impl, - attrs = { - "enabled": attr.bool(mandatory = True, doc = "Whether the feature is enabled."), - "flag_sets": attr.string_list(default = [], doc = "Flag sets for this feature."), - "requires": attr.string_list(default = [], doc = "A list of feature sets defining when this feature is supported by the toolchain."), - "implies": attr.string_list(default = [], doc = "A string list of features or action configs that are automatically enabled when this feature is enabled."), - "provides": attr.string_list(default = [], doc = "A list of names this feature conflicts with."), - }, - provides = [FeatureInfo], -) - -def feature_single_flag_c_cpp(name, flag, c_only = False, enabled = True): - """This macro produces a C/C++ feature() that enables a single flag.""" - feature( - name = name, - enabled = enabled, - flag_sets = [ - flag_set( - actions = C_ALL_COMPILE_ACTIONS + ([] if c_only else CPP_ALL_COMPILE_ACTIONS), - flag_groups = [ - flag_group( - flags = [flag], - ), - ], - ), - ], - ) - -def _feature_set_impl(ctx): - features = {} - subst = {} - for base in ctx.attr.base: - features.update(base[FeatureSetInfo].features) - subst.update(base[FeatureSetInfo].subst) - for feature in ctx.attr.feature: - # Unwrap the feature from FeatureInfo - f = feature[FeatureInfo].feature_config - features[f.name] = f - subst.update(ctx.attr.substitutions) - - #print(json.encode_indent(features)) - return [ - FeatureSetInfo(features = features, subst = subst), - ] - -feature_set = rule( - implementation = _feature_set_impl, - attrs = { - "base": attr.label_list(default = [], providers = [FeatureSetInfo], doc = "A base feature set to derive a new set"), - "feature": attr.label_list(mandatory = True, providers = [FeatureInfo], doc = "A list of features in this set"), - "substitutions": attr.string_dict(doc = "Substitutions to apply to features"), - }, - provides = [FeatureSetInfo], -) diff --git a/third_party/crt/features/README.md b/third_party/crt/features/README.md deleted file mode 100644 index c313b406..00000000 --- a/third_party/crt/features/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Using Feature Sets - -Bazel configures the different modes and ways of invoking the compiler and -tools with `feature`s. Each feature describes a set of flags to apply to -the toolchain (such as optimization level) for different actions (such -as compiling or linking). A collection of features a gathered into a -`feature_set` and given to bazel to configure the toolchain. - -CRT configures features and `feature_set`s using bazel rules. A feature -set can reference any number of base feature sets. All referenced -`feature`s are aggreated together in the feature set. Features are named -by the last part of their label (the part after the colon). In a feature -set, the last referenced feature of a given name take precedence: if -you have a feature set containing a feature named `opt` and you provide -a new feature named `opt`, the later referenced `opt` wins. - -Consider a `common` feature set: -``` -feature( - name = "opt", - flag_sets = [ - flag_set( - flag_groups = flag_group(flags = ["-O2"]) - ) - ], -) - -feature_set( - name = "common", - feature = [ - ":opt", - .... - ], -) -``` - -Now consider an embedded compiler configuration where the desired optimization -flag should be `-Os` instead of `-O2`. We want to inherit all of the features -from `common`, but override the `opt` feature to use an alternate flag: -``` -feature( - name = "opt", - flag_sets = [ - flag_set( - flag_groups = flag_group(flags = ["-Os"]) - ) - ], -) - -feature_set( - name = "embedded", - base = ["//third_party/crt/features/common"] - feature = [ - ":opt", - .... - ], -) -``` - -Feature sets and overrides are processed in order: A given `feature_set` will -process the list of `base`sn order and then apply each of the listed `feature`s. diff --git a/third_party/crt/features/common/BUILD.bazel b/third_party/crt/features/common/BUILD.bazel deleted file mode 100644 index 9285db5c..00000000 --- a/third_party/crt/features/common/BUILD.bazel +++ /dev/null @@ -1,333 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load( - "//third_party/crt/config:features.bzl", - "CPP_ALL_COMPILE_ACTIONS", - "C_ALL_COMPILE_ACTIONS", - "LD_ALL_ACTIONS", - "feature", - "feature_set", - "feature_single_flag_c_cpp", - "flag_group", - "flag_set", -) - -package(default_visibility = ["//visibility:public"]) - -feature( - name = "includes", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-nostdinc", - "[SYSTEM_INCLUDES]", - ], - ), - flag_group( - expand_if_available = "includes", - flags = [ - "-include", - "%{includes}", - ], - iterate_over = "includes", - ), - ], - ), - ], -) - -feature_single_flag_c_cpp( - name = "all_warnings", - flag = "-Wall", -) - -feature_single_flag_c_cpp( - name = "all_warnings_as_errors", - flag = "-Werror", -) - -feature_single_flag_c_cpp( - name = "extra_warnings", - flag = "-Wextra", -) - -feature_single_flag_c_cpp( - name = "pedantic_warnings", - flag = "-Wpedantic", -) - -feature_single_flag_c_cpp( - name = "clang_covered_switch_default_warnings", - flag = "-Wno-covered-switch-default", -) - -feature_single_flag_c_cpp( - name = "implicit_conversion_warnings", - flag = "-Wconversion", -) - -feature_single_flag_c_cpp( - name = "implicit_fallthrough_warnings", - flag = "-Wimplicit-fallthrough", -) - -feature_single_flag_c_cpp( - name = "invalid_pch_warnings", - flag = "-Winvalid-pch", -) - -feature_single_flag_c_cpp( - name = "strict_prototypes_warnings", - c_only = True, - flag = "-Wstrict-prototypes", -) - -feature_single_flag_c_cpp( - name = "switch_default_warnings", - flag = "-Wswitch-default", -) - -feature_single_flag_c_cpp( - name = "no_missing_field_initializers_warning", - flag = "-Wno-missing-field-initializers", -) - -feature_single_flag_c_cpp( - name = "no_sign_compare_warning", - flag = "-Wno-sign-compare", -) - -feature_single_flag_c_cpp( - name = "no_unused_function_warning", - flag = "-Wno-error=unused-function", -) - -feature_single_flag_c_cpp( - name = "no_unused_parameter_warning", - flag = "-Wno-unused-parameter", -) - -feature_single_flag_c_cpp( - name = "type_limits_warning", - flag = "-Wtype-limits", -) - -feature( - name = "reproducible", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = ["-Werror=date-time"], - ), - ], - ), - ], -) - -feature( - name = "exceptions", - enabled = False, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-fno-exceptions", - "-fno-non-call-exceptions", - ], - ), - ], - ), - ], -) - -feature( - name = "use_lld", - enabled = False, - flag_sets = [ - flag_set( - actions = LD_ALL_ACTIONS, - flag_groups = [ - flag_group( - flags = ["-fuse-ld=lld"], - ), - ], - ), - ], -) - -feature( - name = "lto", - enabled = False, - flag_sets = [ - flag_set( - actions = C_ALL_COMPILE_ACTIONS + CPP_ALL_COMPILE_ACTIONS + LD_ALL_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-flto", - ], - ), - ], - ), - ], -) - -feature( - name = "symbol_garbage_collection", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-ffunction-sections", - "-fdata-sections", - ], - ), - ], - ), - flag_set( - actions = LD_ALL_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-Wl,--gc-sections", - ], - ), - ], - ), - ], -) - -feature( - name = "dbg", - enabled = False, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-O0", - "-g3", - ], - ), - ], - ), - ], - provides = ["compilation_mode"], -) - -feature( - name = "fastbuild", - enabled = False, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-O1", - "-g3", - ], - ), - ], - ), - ], - provides = ["compilation_mode"], -) - -feature( - name = "opt", - enabled = False, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-O2", - "-finline-small-functions", - "-flto", - ], - ), - ], - ), - flag_set( - actions = LD_ALL_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-flto", - ], - ), - ], - ), - ], - provides = ["compilation_mode"], -) - -feature( - name = "architecture", - enabled = False, -) - -feature( - name = "output_format", - enabled = False, -) - -feature( - name = "misc", - enabled = False, -) - -feature( - name = "coverage", - enabled = False, -) - -feature_set( - name = "common", - feature = [ - ":includes", - ":architecture", - ":all_warnings", - ":all_warnings_as_errors", - ":extra_warnings", - ":pedantic_warnings", - ":clang_covered_switch_default_warnings", - ":implicit_conversion_warnings", - ":implicit_fallthrough_warnings", - ":invalid_pch_warnings", - ":strict_prototypes_warnings", - ":switch_default_warnings", - ":no_missing_field_initializers_warning", - ":no_sign_compare_warning", - ":no_unused_function_warning", - ":no_unused_parameter_warning", - ":reproducible", - ":exceptions", - ":use_lld", - ":lto", - ":symbol_garbage_collection", - ":dbg", - ":fastbuild", - ":opt", - ":output_format", - ":misc", - ":coverage", - ], -) diff --git a/third_party/crt/features/embedded/BUILD.bazel b/third_party/crt/features/embedded/BUILD.bazel deleted file mode 100644 index 366baec2..00000000 --- a/third_party/crt/features/embedded/BUILD.bazel +++ /dev/null @@ -1,119 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load( - "//third_party/crt/config:features.bzl", - "CPP_ALL_COMPILE_ACTIONS", - "C_ALL_COMPILE_ACTIONS", - "LD_ALL_ACTIONS", - "feature", - "feature_set", - "feature_single_flag_c_cpp", - "flag_group", - "flag_set", -) - -package(default_visibility = ["//visibility:public"]) - -feature( - name = "runtime_type_information", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - # Disable RTTI - "-fno-rtti", - ], - ), - ], - ), - ], -) - -feature( - name = "exceptions", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - # Disable Exceptions - "-fno-exceptions", - "-fno-non-call-exceptions", - ], - ), - ], - ), - ], -) - -feature( - name = "cc_constructor_destructor", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - # Indicate that this program may not neccesarily be able to use standard system calls - "-ffreestanding", - # Instantiate global variables only once - "-fno-common", - # Emits guards against functions that have references to local array definitions - "[STACK_PROTECTOR]", - ], - ), - ], - ), - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - # Disable teardown/destructors for static variables - "-fno-use-cxa-atexit", - # Disable threadsafe statics - "-fno-threadsafe-statics", - ], - ), - ], - ), - ], -) - -feature_single_flag_c_cpp( - name = "clang_gnu_warnings", - flag = "-Wgnu", -) - -feature_single_flag_c_cpp( - name = "no_gnu_zero_variadic_macro_arguments_warning", - flag = "-Wno-gnu-zero-variadic-macro-arguments", -) - -feature_single_flag_c_cpp( - name = "no_gnu_statement_expression_from_macro_expansion", - flag = "-Wno-gnu-statement-expression-from-macro-expansion", -) - -feature_set( - name = "embedded", - feature = [ - "runtime_type_information", - "exceptions", - "cc_constructor_destructor", - "clang_gnu_warnings", - "no_gnu_zero_variadic_macro_arguments_warning", - "no_gnu_statement_expression_from_macro_expansion", - ], - substitutions = { - "[STACK_PROTECTOR]": "-fstack-protector-strong", - }, -) diff --git a/third_party/crt/features/windows/BUILD.bazel b/third_party/crt/features/windows/BUILD.bazel deleted file mode 100644 index de641d49..00000000 --- a/third_party/crt/features/windows/BUILD.bazel +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load("//third_party/crt/config:compiler.bzl", "artifact_name") -load( - "//third_party/crt/config:features.bzl", - "CPP_ALL_COMPILE_ACTIONS", - "C_ALL_COMPILE_ACTIONS", - "LD_ALL_ACTIONS", - "feature", - "feature_set", - "flag_group", - "flag_set", -) - -package(default_visibility = ["//visibility:public"]) - -artifact_name( - name = "exe", - category = "executable", - extension = ".exe", -) - -artifact_name( - name = "dll", - category = "dynamic_library", - extension = ".dll", -) - -feature( - name = "default_link_flags", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS + LD_ALL_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-lstdc++", - ], - ), - ], - ), - ], -) - -feature( - name = "opt", - enabled = False, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-O2", - "-finline-small-functions", - ], - ), - ], - ), - ], -) - -feature( - name = "reproducible", - enabled = True, - flag_sets = [ - flag_set( - actions = CPP_ALL_COMPILE_ACTIONS + C_ALL_COMPILE_ACTIONS, - flag_groups = [ - flag_group( - flags = ["-Werror=date-time"], - ), - ], - ), - flag_set( - actions = LD_ALL_ACTIONS, - flag_groups = [ - flag_group( - flags = [ - "-Wl,--no-insert-timestamp", - ], - ), - ], - ), - ], -) - -feature_set( - name = "windows", - base = ["//third_party/crt/features/common"], - feature = [ - ":default_link_flags", - ":opt", - ":reproducible", - ], -) diff --git a/third_party/crt/platforms/x86_32/BUILD.bazel b/third_party/crt/platforms/x86_32/BUILD.bazel deleted file mode 100644 index f915eae8..00000000 --- a/third_party/crt/platforms/x86_32/BUILD.bazel +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -package(default_visibility = ["//visibility:public"]) - -load("//third_party/crt/config:execution.bzl", "exec_config") - -platform( - name = "win32", - constraint_values = [ - "@platforms//cpu:x86_32", - "@platforms//os:windows", - ], -) diff --git a/third_party/crt/platforms/x86_32/windows.bzl b/third_party/crt/platforms/x86_32/windows.bzl deleted file mode 100644 index e8e68f78..00000000 --- a/third_party/crt/platforms/x86_32/windows.bzl +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load("//third_party/crt/config:device.bzl", "device_config") - -DEVICES = [ - device_config( - name = "pc-win32", - architecture = "x86_32", - feature_set = "//third_party/crt/features/windows", - constraints = [ - "@platforms//cpu:x86_32", - "@platforms//os:windows", - ], - artifact_naming = [ - "//third_party/crt/features/windows:exe", - "//third_party/crt/features/windows:dll", - ], - ), -] diff --git a/third_party/crt/rules/BUILD.bazel b/third_party/crt/rules/BUILD.bazel deleted file mode 100644 index c85882e6..00000000 --- a/third_party/crt/rules/BUILD.bazel +++ /dev/null @@ -1,5 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -package(default_visibility = ["//visibility:public"]) diff --git a/third_party/crt/rules/pkg_win.bzl b/third_party/crt/rules/pkg_win.bzl deleted file mode 100644 index 4fe99080..00000000 --- a/third_party/crt/rules/pkg_win.bzl +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load("//third_party/crt/rules:transition.bzl", "platform_rule") -load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain") - -def _get_toolchain_dir(cc_toolchain): - workspace = [f for f in cc_toolchain.all_files.to_list() if f.basename == "WORKSPACE" or f.basename == "MODULE.bazel"] - if not workspace: - fail("Could not find the WORKSPACE or MODULE.bazel of the cc_toolchain") - return workspace[0] - -def _pkg_win_impl(ctx): - out = ctx.actions.declare_file(ctx.attr.name + ".zip") - if ctx.attr.platform == "//platforms/x86_64:win64": - target = "win64" - elif ctx.attr.platform == "//third_party/crt/platforms/x86_32:win32": - target = "win32" - else: - fail("Unknown platform:", ctx.attr.platform) - - toolchain = find_cc_toolchain(ctx) - cc_toolchain = getattr(toolchain, "cc", toolchain) - mxe = _get_toolchain_dir(cc_toolchain) - - args = [ - "--target={}".format(target), - "--mxe={}".format(mxe.dirname), - "--out={}".format(out.path), - ] - if ctx.attr.skip_dlls: - args.append("--skip_dlls={}".format(",".join([dll for dll in ctx.attr.skip_dlls]))) - if ctx.attr.zips: - args.append("--combine_zips={}".format(",".join([z.path for z in ctx.files.zips]))) - args.extend([src.path for src in ctx.files.srcs]) - - ctx.actions.run( - executable = ctx.executable._tool, - arguments = args, - inputs = ctx.files.srcs + ctx.files.zips + cc_toolchain.all_files.to_list(), - outputs = [out], - progress_message = "Packaging files into {}".format(out.basename), - mnemonic = "PkgWin", - ) - return DefaultInfo( - files = depset([out]), - runfiles = ctx.runfiles(files = [out]), - ) - -pkg_win = platform_rule( - implementation = _pkg_win_impl, - attrs = { - "srcs": attr.label_list( - doc = "List of targets to include in this package", - allow_files = True, - ), - "skip_dlls": attr.string_list( - doc = "Names of DLLs that can be ignored in dependency analysis", - ), - "platform": attr.string( - default = "//platforms/x86_64:win64", - doc = "The target platform", - ), - "zips": attr.label_list( - doc = "List of additional ZIP archives to re-pack into this archive", - allow_files = True, - ), - "_tool": attr.label( - default = "//third_party/crt/util:pkg_win", - cfg = "host", - executable = True, - ), - "_cc_toolchain": attr.label( - default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"), - ), - }, - fragments = ["cpp"], - toolchains = ["@rules_cc//cc:toolchain_type"], - incompatible_use_toolchain_transition = True, -) diff --git a/third_party/crt/rules/transition.bzl b/third_party/crt/rules/transition.bzl deleted file mode 100644 index 334e6ccf..00000000 --- a/third_party/crt/rules/transition.bzl +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -def _platform_transition_impl(settings, attr): - return {"//command_line_option:platforms": attr.platform} - -platform_transition = transition( - implementation = _platform_transition_impl, - inputs = [], - outputs = ["//command_line_option:platforms"], -) - -def platform_rule(**kwargs): - "A wrapper over rule() for creating new rules that trigger the platform transition." - - attrs = kwargs.pop("attrs", {}) - if "platform" not in attrs: - attrs["platform"] = attr.string(doc = "Platform configuration") - attrs["_allowlist_function_transition"] = attr.label( - default = "@bazel_tools//tools/allowlists/function_transition_allowlist", - ) - - return rule( - cfg = platform_transition, - attrs = attrs, - **kwargs - ) - -def _platform_target_impl(ctx): - info = ctx.attr.target[DefaultInfo] - return [ - DefaultInfo( - files = info.files, - data_runfiles = info.data_runfiles, - ), - ] - -platform_target = platform_rule( - implementation = _platform_target_impl, - attrs = { - "target": attr.label(), - }, -) diff --git a/third_party/crt/toolchains/gcc_mxe_mingw32/BUILD.bazel b/third_party/crt/toolchains/gcc_mxe_mingw32/BUILD.bazel deleted file mode 100644 index 922912ca..00000000 --- a/third_party/crt/toolchains/gcc_mxe_mingw32/BUILD.bazel +++ /dev/null @@ -1,50 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -load("//third_party/crt/config:compiler.bzl", "setup") -load("//third_party/crt/platforms/x86_32:windows.bzl", "DEVICES") - -package(default_visibility = ["//visibility:public"]) - -SYSTEM_INCLUDE_PATHS = [ - "external/+_repo_rules+gcc_mxe_mingw32_files/lib/gcc/i686-w64-mingw32.shared/11.3.0/include", - "external/+_repo_rules+gcc_mxe_mingw32_files/lib/gcc/i686-w64-mingw32.shared/11.3.0/include-fixed", - "external/+_repo_rules+gcc_mxe_mingw32_files/lib/gcc/i686-w64-mingw32.shared/11.3.0/include/c++", - "external/+_repo_rules+gcc_mxe_mingw32_files/lib/gcc/i686-w64-mingw32.shared/11.3.0/include/c++/backward", - "external/+_repo_rules+gcc_mxe_mingw32_files/lib/gcc/i686-w64-mingw32.shared/11.3.0/include/c++/i686-w64-mingw32.shared", - "external/+_repo_rules+gcc_mxe_mingw32_files/i686-w64-mingw32.shared/include", -] - -filegroup( - name = "compiler_components", - srcs = [ - "//third_party/crt/toolchains/gcc_mxe_mingw32/wrappers:all", - "@gcc_mxe_mingw32_files//:all", - ], -) - -[setup( - name = device.name, - architecture = device.architecture, - artifact_naming = device.artifact_naming, - compiler_components = ":compiler_components", - constraints = device.constraints, - feature_set = device.feature_set, - include_directories = SYSTEM_INCLUDE_PATHS, - params = { - "compiler": "gcc", - }, - substitutions = device.substitutions, - tools = { - "ar": "wrappers/ar", - "cpp": "wrappers/cpp", - "gcc": "wrappers/gcc", - "gcov": "wrappers/gcov", - "ld": "wrappers/ld", - "nm": "wrappers/nm", - "objcopy": "wrappers/objcopy", - "objdump": "wrappers/objdump", - "strip": "wrappers/strip", - }, -) for device in DEVICES] diff --git a/third_party/crt/toolchains/gcc_mxe_mingw32/wrappers/driver.sh b/third_party/crt/toolchains/gcc_mxe_mingw32/wrappers/driver.sh deleted file mode 100755 index 2cc272fa..00000000 --- a/third_party/crt/toolchains/gcc_mxe_mingw32/wrappers/driver.sh +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/bash --norc -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -PROG=${0##*/} -DRIVER_DIR=${0%/*} -MXE="gcc_mxe_mingw32_files" - -# Bzlmod compatibility: Find the actual directory name in external/ -if [ ! -d "external/${MXE}" ]; then - # Look for directory ending with ~gcc_mxe_mingw32_files or +gcc_mxe_mingw32_files - # Bazel 7 uses ~ as a separator in Bzlmod canonical names. - # Bazel 8 uses + as a separator. - FOUND=$(find external -maxdepth 1 \( -name "*~${MXE}" -o -name "*+${MXE}" \) -type d | head -n 1) - if [ -n "$FOUND" ]; then - MXE=${FOUND#external/} - fi -fi - -VERSION="11.3.0" -PREFIX="i686-w64-mingw32.shared" -export COMPILER_PATH="external/${MXE}/libexec/gcc/${PREFIX}/${VERSION}:external/${MXE}/bin:${DRIVER_DIR}" -export LIBRARY_PATH="external/${MXE}/${PREFIX}/lib:external/${MXE}/lib/gcc/${PREFIX}/${VERSION}" - -ARGS=() -POSTARGS=() -case "${PROG}" in - gcc) - ARGS+=("-B" "external/${MXE}/bin/${PREFIX}-") - ;; -esac - -exec "external/${MXE}/bin/${PREFIX}-${PROG}" \ - "${ARGS[@]}" \ - "$@"\ - "${POSTARGS[@]}" diff --git a/third_party/crt/util/BUILD.bazel b/third_party/crt/util/BUILD.bazel deleted file mode 100644 index b3b2692f..00000000 --- a/third_party/crt/util/BUILD.bazel +++ /dev/null @@ -1,10 +0,0 @@ -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -package(default_visibility = ["//visibility:public"]) - -py_binary( - name = "pkg_win", - srcs = ["pkg_win.py"], -) diff --git a/third_party/crt/util/pkg_win.py b/third_party/crt/util/pkg_win.py deleted file mode 100644 index c33a31ad..00000000 --- a/third_party/crt/util/pkg_win.py +++ /dev/null @@ -1,225 +0,0 @@ -#!/usr/bin/env python3 -# Copyright lowRISC contributors (OpenTitan project). -# Licensed under the Apache License, Version 2.0, see LICENSE for details. -# SPDX-License-Identifier: Apache-2.0 - -############################################################################# -# Create a Windows ZIP archive with needed binary resources. -# -############################################################################# -import argparse -import errno -import logging -import os -import os.path -import struct -import subprocess -import sys -import zipfile - -flags = argparse.ArgumentParser(description='Windows Packaging Tool') -flags.add_argument('files', metavar='FILE', type=str, nargs='*', help='Files') -flags.add_argument('--ignore_missing_dlls', - default=False, - type=bool, - help='Ignore missing DLLs') -flags.add_argument('--objdump_bin', default=None, help='objdump binary') -flags.add_argument('--out', default=None, help='Output ZIP file.') -flags.add_argument('--mxe', default=None, help='Location of MXE install.') -flags.add_argument('--skip_dlls', - default=None, - help='DLL dependencies to ignore.') -flags.add_argument('--combine_zips', - default=None, - help='Combine extra ZIP archives into this one.') -flags.add_argument('--target', - default='win64', - help='Target platform (win32 or win64).') -flags.add_argument('--debuglog', - default=None, - help='File to write debug log into') - -# Target name mappings to mxe directory names. -TARGET = { - 'win32': 'i686-w64-mingw32.shared', - 'win64': 'x86_64-w64-mingw32.shared', -} - -# ELF target name mappings to mxe directory names. -PREFIX = { - 'pei-i386': 'i686-w64-mingw32.shared', - 'pei-x86-64': 'x86_64-w64-mingw32.shared', -} - -# PE-COFF identifiers per architecture. -ARCH_TO_TARGET = { - 0x8664: 'win64', - 0x014c: 'win32', -} - -# DLLs that are part of Windows. If a program has a dependency on one of -# of these DLLs, we assume Windows will provide the DLL. -SKIP_DLLS = [ - 'api-ms-win-crt-conio-l1-1-0.dll', - 'api-ms-win-crt-convert-l1-1-0.dll', - 'api-ms-win-crt-environment-l1-1-0.dll', - 'api-ms-win-crt-filesystem-l1-1-0.dll', - 'api-ms-win-crt-heap-l1-1-0.dll', - 'api-ms-win-crt-locale-l1-1-0.dll', - 'api-ms-win-crt-math-l1-1-0.dll', - 'api-ms-win-crt-process-l1-1-0.dll', - 'api-ms-win-crt-runtime-l1-1-0.dll', - 'api-ms-win-crt-stdio-l1-1-0.dll', - 'api-ms-win-crt-string-l1-1-0.dll', - 'api-ms-win-crt-time-l1-1-0.dll', - 'api-ms-win-crt-utility-l1-1-0.dll', - 'ADVAPI32.DLL', - 'BCRYPT.DLL', - 'CABINET.DLL', - 'CRYPT32.DLL', - 'DBGHELP.DLL', - 'GDI32.DLL', - 'IMM32.DLL', - 'KERNEL32.DLL', - 'MSI.DLL', - 'MSVCRT.DLL', - 'OLE32.DLL', - 'OLEAUT32.DLL', - 'OPENGL32.DLL', - 'RPCRT4.DLL', - 'SETUPAPI.DLL', - 'SHELL32.DLL', - 'SHLWAPI.DLL', - 'USER32.DLL', - 'VCRUNTIME140.DLL', - 'VERSION.DLL', - 'WINMM.DLL', - 'WS2_32.DLL', -] - -# The following DLLs are part of how exception handling is implemented. -REQUIRED_DLLS = { - 'pei-i386': [ - 'libgcc_s_sjlj-1.dll', - ], - 'pei-x86-64': [ - 'libgcc_s_seh-1.dll', - ], -} - - -class PkgWin(object): - - def __init__(self, args): - self.missing_dlls = 0 - self.mxe = args.mxe - self.target = args.target - self.objdump_bin = args.objdump_bin if args.objdump_bin else os.path.join( - self.mxe, 'bin', TARGET[self.target] + '-objdump') - - def detect_exe(self, filename): - """Detect whether `filename` is a PE-COFF executable. - - Args: - filename: str; Path to a file. - Returns: - If an executabe: str "win32" or "win64". - If not an executable: None - """ - with open(filename, 'rb') as f: - hdr = f.read(4096) - if (hdr[:2] == b'MZ' - and b'This program cannot be run in DOS mode' in hdr): - logging.info('Detected legacy DOS header in %r', filename) - (pehdr, ) = struct.unpack('/dev/null 2>&1; then + echo "ERROR: 'podman' not found in PATH." + echo "Please install via 'sudo apt install podman'." + return 1 + fi +} + +# ate_client_build_base_image +# +# Builds the Wine base image unconditionally. Use this to pick up Dockerfile +# changes; `ate_client_ensure_base_image` will not rebuild an image that +# already exists. +ate_client_build_base_image() { + local repo_top="$1" + + podman build -t "${ATE_CLIENT_BASE_IMAGE}" \ + -f "${repo_top}/util/containers/ate_client/Dockerfile" \ + "${repo_top}/util/containers/ate_client" +} + +# ate_client_ensure_base_image +# +# Builds the Wine base image if it is not already in the local registry. +ate_client_ensure_base_image() { + local repo_top="$1" + + if podman image exists "${ATE_CLIENT_BASE_IMAGE}"; then + echo "Using cached base image ${ATE_CLIENT_BASE_IMAGE}." + return 0 + fi + echo "Base image ${ATE_CLIENT_BASE_IMAGE} not found; building it (slow, once)." + ate_client_build_base_image "${repo_top}" +} + +# ate_client_stage_bundle +# +# Lays out the Windows bundle that gets installed into the container image. +# Because ate.dll links its C++/threading runtime statically, only the harness +# executable and ate.dll are staged into the container directory. +ate_client_stage_bundle() { + local context_dir="$1" + local harness_exe="$2" + local ate_dll="$3" + + if [[ -z "${context_dir}" ]]; then + echo "ERROR: ate_client_stage_bundle called with an empty context dir." + return 1 + fi + rm -rf "${context_dir}" + mkdir -p "${context_dir}/bundle" + cp -f "${harness_exe}" "${context_dir}/bundle/${ATE_CLIENT_HARNESS_EXE}" + cp -f "${ate_dll}" "${context_dir}/bundle/ate.dll" + chmod u+w "${context_dir}/bundle"/*.exe "${context_dir}/bundle"/*.dll +} + +# ate_client_build_image +# +# Installs the staged bundle onto the base image. +ate_client_build_image() { + local repo_top="$1" + local context_dir="$2" + + cp -f "${repo_top}/util/containers/ate_client/Dockerfile.bundle" \ + "${context_dir}/Dockerfile" + podman build -t "${ATE_CLIENT_IMAGE}" \ + --build-arg "BASE_IMAGE=${ATE_CLIENT_BASE_IMAGE}" \ + -f "${context_dir}/Dockerfile" "${context_dir}" +} + +# ate_client_run [harness_arg...] +# +# Runs the harness inside the container. is a podman network mode: +# `none` when no appliance is involved, `bridge` to reach one over a real +# network interface, or `host` to share the host's network namespace. +# is bind mounted read-only at /opt/ate/certs; pass an empty string +# to skip it. +ate_client_run() { + local network="$1" + local certs_dir="$2" + shift 2 + + local -a args=( + run --rm + --network="${network}" + --name "ate-client-$$" + ) + if [[ -n "${certs_dir}" ]]; then + args+=(--volume "${certs_dir}:/opt/ate/certs:ro") + fi + args+=("${ATE_CLIENT_IMAGE}" "$@") + + podman "${args[@]}" +} + +# Exit status the harness uses for an unhandled exception. Must match +# `kCrashExitCode` in src/ate/test_programs/ate_dll_smoke.cc. +readonly ATE_CLIENT_CRASH_STATUS=42 + +# ate_client_report_status +# +# Translates the harness exit status into a readable verdict. Returns non-zero +# when the case failed. +ate_client_report_status() { + local name="$1" + local status="$2" + + if [[ "${status}" -eq 0 ]]; then + echo "PASS[${name}]" + return 0 + fi + if [[ "${status}" -eq "${ATE_CLIENT_CRASH_STATUS}" ]]; then + echo "FAIL[${name}]: ate.dll took an unhandled exception (access violation)." + echo " Known 32-bit MinGW toolchain hazard: gRPC's channel setup has been" + echo " miscompiled by this toolchain before." + else + echo "FAIL[${name}]: ${ATE_CLIENT_HARNESS_EXE} exited with status ${status}." + fi + return 1 +} diff --git a/util/containers/ate_client/BUILD.bazel b/util/containers/ate_client/BUILD.bazel new file mode 100644 index 00000000..58db91d4 --- /dev/null +++ b/util/containers/ate_client/BUILD.bazel @@ -0,0 +1,22 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +package(default_visibility = ["//visibility:public"]) + +exports_files([ + "Dockerfile", + "Dockerfile.bundle", + "entrypoint.sh", +]) + +# Everything needed to build the simulated Windows ATE machine image from +# within a test. +filegroup( + name = "container_files", + srcs = [ + "Dockerfile", + "Dockerfile.bundle", + "entrypoint.sh", + ], +) diff --git a/util/containers/ate_client/Dockerfile b/util/containers/ate_client/Dockerfile new file mode 100644 index 00000000..8eb6363d --- /dev/null +++ b/util/containers/ate_client/Dockerfile @@ -0,0 +1,51 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Base image for the simulated Windows ATE machine. +# +# In production the ATE client is a Windows host that loads `ate.dll` and talks +# to the provisioning appliance over the network. This image stands in for that +# host: a Wine runtime with a pre-initialized prefix, and nothing else. The +# Windows artifacts themselves are layered on top at test time by +# `Dockerfile.bundle`, so that rebuilding the DLL does not rebuild Wine. +FROM debian:trixie-slim + +LABEL version="1.0" +LABEL description="OpenTitan provisioning Windows ATE client (Wine) container." + +# Everything runs as root: rootless podman without a subuid range maps a single +# uid into the container, so apt's privilege separation (and any other user +# switch) fails with EPERM. +RUN echo 'APT::Sandbox::User "root";' > /etc/apt/apt.conf.d/00-no-sandbox + +# Wine needs the i386 architecture to run the 32-bit MinGW artifacts, so enable +# multiarch before installing. +RUN dpkg --add-architecture i386 \ + && apt-get update \ + && DEBIAN_FRONTEND=noninteractive apt-get install -y \ + --no-install-recommends \ + ca-certificates \ + wine \ + wine32:i386 \ + && rm -rf /var/lib/apt/lists/* + +ENV WINEPREFIX=/opt/wineprefix +ENV WINEDEBUG=-all +# Neither Mono nor Gecko is needed; suppress the interactive install prompts. +ENV WINEDLLOVERRIDES="mscoree,mshtml=" +# wineserver needs a writable runtime directory; the image has no logged-in +# user, so point it at a scratch location. +ENV XDG_RUNTIME_DIR=/run/wine +RUN mkdir -p /run/wine && chmod 700 /run/wine + + +# Initialize the prefix at build time so the first test run does not pay for it +# (prefix creation takes tens of seconds). +RUN wineboot --init \ + && wineserver --wait + +COPY entrypoint.sh /usr/local/bin/entrypoint.sh + +WORKDIR /opt/ate +ENTRYPOINT [ "/usr/local/bin/entrypoint.sh" ] diff --git a/util/containers/ate_client/Dockerfile.bundle b/util/containers/ate_client/Dockerfile.bundle new file mode 100644 index 00000000..8eb77f06 --- /dev/null +++ b/util/containers/ate_client/Dockerfile.bundle @@ -0,0 +1,17 @@ +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +# Layers the Win32 ATE artifacts onto the Wine base image, mirroring how a +# release bundle is installed onto a real Windows ATE machine. +# +# Built at test time by `util/ate_client_container.sh`; the `BASE_IMAGE` build +# argument selects the (cached) Wine base image. +ARG BASE_IMAGE +FROM ${BASE_IMAGE} + +# `ate.dll`, the MinGW runtime DLLs and the harness must all share a directory, +# matching the layout produced by `//src/ate:windows`. +COPY bundle/ /opt/ate/ + +WORKDIR /opt/ate diff --git a/util/containers/ate_client/build_container.sh b/util/containers/ate_client/build_container.sh new file mode 100755 index 00000000..161c6d47 --- /dev/null +++ b/util/containers/ate_client/build_container.sh @@ -0,0 +1,23 @@ +#!/bin/bash +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Rebuilds the Wine base image used to simulate a Windows ATE machine. +# +# The tests build this image automatically when it is missing, so this script +# is only needed to refresh it after editing the Dockerfile, or to publish it. +# +# TODO: publish to us-docker.pkg.dev and pull it by digest from +# `third_party/docker/extensions.bzl`, as is done for the SoftHSM2 image, so +# that CI does not rebuild it from Debian packages. + +set -e + +REPO_TOP="$(cd "$(dirname "$0")/../../.." && pwd)" +source "${REPO_TOP}/util/ate_client_container.sh" + +ate_client_require_podman + +echo "Rebuilding ${ATE_CLIENT_BASE_IMAGE} ..." +ate_client_build_base_image "${REPO_TOP}" diff --git a/util/containers/ate_client/entrypoint.sh b/util/containers/ate_client/entrypoint.sh new file mode 100755 index 00000000..205f81e8 --- /dev/null +++ b/util/containers/ate_client/entrypoint.sh @@ -0,0 +1,28 @@ +#!/bin/bash +# Copyright lowRISC contributors (OpenTitan project). +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 +# +# Entrypoint for the simulated Windows ATE machine. +# +# Runs the Win32 ATE harness under Wine and normalizes its exit status. All +# arguments are forwarded verbatim to the harness. + +set -uo pipefail + +readonly EXE="${ATE_CLIENT_EXE:-ate_dll_smoke.exe}" + +if [[ ! -f "${EXE}" ]]; then + echo "ERROR: ${EXE} not found in $(pwd)." >&2 + exit 2 +fi + +# Wine emits unrelated driver probe noise on hosts without a GPU. +wine "${EXE}" "$@" 2> >(grep -v -E '^(TU|MESA|wine: Read access denied)' >&2) +status=$? + +# Reap the wineserver so the container exits promptly instead of lingering on +# the background daemon. +wineserver --kill >/dev/null 2>&1 || true + +exit "${status}"