diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 81a7c242..42a3eff5 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -21,7 +21,7 @@ jobs: # Bootstrap each target and run the consolidated behavioral suite against # the stage 0 and stage 2 compilers. host-x86: - name: ${{ matrix.architecture }}/${{ matrix.link_mode }} (${{ matrix.compiler }}) + name: ${{ matrix.architecture }}/${{ matrix.link_mode }}/${{ matrix.link_mode == 'dynamic' && (matrix.binding_mode == 'immediate' && 'immediate' || 'default') || 'none' }} (${{ matrix.compiler }}) runs-on: ubuntu-24.04 timeout-minutes: 30 strategy: @@ -32,10 +32,28 @@ jobs: compiler: [gcc, clang] architecture: [arm, arm64, riscv, x64] link_mode: [static, dynamic] + include: + - compiler: gcc + architecture: arm + link_mode: dynamic + binding_mode: immediate + - compiler: clang + architecture: arm + link_mode: dynamic + binding_mode: immediate + - compiler: gcc + architecture: riscv + link_mode: dynamic + binding_mode: immediate + - compiler: clang + architecture: riscv + link_mode: dynamic + binding_mode: immediate env: CC: ${{ matrix.compiler }} ARCH: ${{ matrix.architecture }} DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -49,14 +67,14 @@ jobs: # environment: the build session the tree records assigns ARCH outright, # and a makefile assignment wins over the environment. - name: Build artifacts - run: make ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Unit tests - run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Upload the test logs if: failure() uses: actions/upload-artifact@v7 with: - name: logs-${{ matrix.compiler }}-${{ matrix.architecture }}-${{ matrix.link_mode }} + name: logs-${{ matrix.compiler }}-${{ matrix.architecture }}-${{ matrix.link_mode }}-${{ matrix.link_mode == 'dynamic' && (matrix.binding_mode == 'immediate' && 'immediate' || 'default') || 'none' }} path: | out/*.log if-no-files-found: ignore @@ -68,7 +86,7 @@ jobs: # sanitizer step of host-x86 used to do. Building "sanitizer" first, in a # job that builds nothing else, is what puts the instrumentation in. sanitizer: - name: Sanitizers ${{ matrix.architecture }}/${{ matrix.link_mode }} + name: Sanitizers ${{ matrix.architecture }}/${{ matrix.link_mode }}/${{ matrix.link_mode == 'dynamic' && (matrix.binding_mode == 'immediate' && 'immediate' || 'default') || 'none' }} runs-on: ubuntu-24.04 timeout-minutes: 30 strategy: @@ -82,9 +100,17 @@ jobs: # why first. architecture: [arm, arm64, riscv, x64] link_mode: [static, dynamic] + include: + - architecture: arm + link_mode: dynamic + binding_mode: immediate + - architecture: riscv + link_mode: dynamic + binding_mode: immediate env: ARCH: ${{ matrix.architecture }} DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -95,9 +121,9 @@ jobs: link-mode: ${{ matrix.link_mode }} github-token: ${{ github.token }} - name: Build the stage 0 compiler with sanitizers - run: make sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Sanitizer-enabled stage 0 tests - run: make check-sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make check-sanitizer ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" # Preprocess shecc with itself, then compile the result: the stage 1 source # is the largest input the preprocessor gets, and the only one that exercises @@ -135,7 +161,7 @@ jobs: # AArch64 that is the only place the real loader is exercised: QEMU-user maps # the image itself, and on an x86-64 host it always presents 4 KiB pages. host-arm: - name: ${{ matrix.architecture }}/${{ matrix.link_mode }} on Arm64 + name: ${{ matrix.architecture }}/${{ matrix.link_mode }}/${{ matrix.link_mode == 'dynamic' && (matrix.binding_mode == 'immediate' && 'immediate' || 'default') || 'none' }} on Arm64 runs-on: ubuntu-24.04-arm timeout-minutes: 30 strategy: @@ -143,9 +169,14 @@ jobs: matrix: architecture: [arm, arm64] link_mode: [static, dynamic] + include: + - architecture: arm + link_mode: dynamic + binding_mode: immedaite env: ARCH: ${{ matrix.architecture }} DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -156,14 +187,14 @@ jobs: link-mode: ${{ matrix.link_mode }} github-token: ${{ github.token }} - name: Build artifacts - run: make ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Unit tests - run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" + run: make check ARCH="$ARCH" DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Upload the test logs if: failure() uses: actions/upload-artifact@v7 with: - name: logs-arm64-host-${{ matrix.architecture }}-${{ matrix.link_mode }} + name: logs-arm64-host-${{ matrix.architecture }}-${{ matrix.link_mode }}-${{ matrix.link_mode == 'dynamic' && (matrix.binding_mode == 'immediate' && 'immediate' || 'default') || 'none' }} path: | out/*.log out/tests/*.log @@ -174,15 +205,19 @@ jobs: # build that produces the object files, and that job has already made them # without instrumentation. host-arm-sanitizer: - name: Sanitizers arm/${{ matrix.link_mode }} on Arm64 + name: Sanitizers arm/${{ matrix.link_mode }}/${{ matrix.link_mode == 'dynamic' && (matrix.binding_mode == 'immediate' && 'immediate' || 'default') || 'none' }} on Arm64 runs-on: ubuntu-24.04-arm timeout-minutes: 30 strategy: fail-fast: false matrix: link_mode: [static, dynamic] + include: + - link_mode: dynamic + binding_mode: immediate env: DYNLINK: ${{ matrix.link_mode == 'dynamic' && '1' || '0' }} + BINDING: ${{ matrix.binding_mode == 'immediate' && 'now' || 'lazy' }} steps: - name: Checkout code uses: actions/checkout@v7 @@ -193,9 +228,9 @@ jobs: link-mode: ${{ matrix.link_mode }} github-token: ${{ github.token }} - name: Build the stage 0 compiler with sanitizers - run: make sanitizer ARCH=arm DYNLINK="$DYNLINK" + run: make sanitizer ARCH=arm DYNLINK="$DYNLINK" BINDING="$BINDING" - name: Sanitizer-enabled stage 0 tests - run: make check-sanitizer ARCH=arm DYNLINK="$DYNLINK" + run: make check-sanitizer ARCH=arm DYNLINK="$DYNLINK" BINDING="$BINDING" coding-style: name: Coding style diff --git a/Makefile b/Makefile index 434ca363..594a60f3 100644 --- a/Makefile +++ b/Makefile @@ -84,12 +84,13 @@ TRANSLATION_DEFS = "\#define SHECC_TRANSLATION_DATE \"$(TRANSLATION_DATE)\"\n\#d STAGE0_FLAGS ?= --dump-ir STAGE1_FLAGS ?= DYNLINK ?= 0 +BINDING ?= lazy COMMENTFLOW ?= commentflow SHFMT ?= shfmt ifeq ($(DYNLINK),1) - STAGE0_FLAGS += --dynlink - STAGE1_FLAGS += --dynlink + STAGE0_FLAGS += --dynlink -z $(BINDING) + STAGE1_FLAGS += --dynlink -z $(BINDING) endif SRCS := $(wildcard $(patsubst %,%/main.c, $(SRCDIR))) @@ -236,11 +237,11 @@ uninstall-hooks: check-stage0: $(OUT)/$(STAGE0) tests/driver.sh $(VECHO) " TEST STAGE 0\n" - tests/driver.sh 0 $(DYNLINK) + tests/driver.sh 0 $(DYNLINK) $(BINDING) check-stage2: $(OUT)/$(STAGE2) tests/driver.sh $(VECHO) " TEST STAGE 2\n" - tests/driver.sh 2 $(DYNLINK) + tests/driver.sh 2 $(DYNLINK) $(BINDING) check-sanitizer: $(OUT)/$(STAGE0)-sanitizer tests/driver.sh $(VECHO) " TEST STAGE 0 (with sanitizers)\n" @@ -249,10 +250,10 @@ check-sanitizer: $(OUT)/$(STAGE0)-sanitizer tests/driver.sh $(Q)rm $(OUT)/shecc check-abi-stage0: $(OUT)/$(STAGE0) - tests/$(ARCH)-abi.sh 0 $(DYNLINK); + tests/$(ARCH)-abi.sh 0 $(DYNLINK) $(BINDING); check-abi-stage2: $(OUT)/$(STAGE2) - tests/$(ARCH)-abi.sh 2 $(DYNLINK); + tests/$(ARCH)-abi.sh 2 $(DYNLINK) $(BINDING); # Both prerequisites are order-only, and both exist because "make -j" would # otherwise let a compile start beside the thing it reads. Selecting a target diff --git a/mk/arm64.mk b/mk/arm64.mk index a3a4151a..c2d96bc2 100644 --- a/mk/arm64.mk +++ b/mk/arm64.mk @@ -26,7 +26,6 @@ ARCH_DEFS = \ \#define PLT_ENT_SIZE 16\n$\ \#define RESERVED_GOT_NUM 3\n$\ \#define R_ARCH_JUMP_SLOT 1026 /* R_AARCH64_JUMP_SLOT */\n$\ - \#define DYN_BIND_NOW 1\n$\ " # An Arm64 Linux host runs this target's output itself, so nothing has to stand diff --git a/mk/x64.mk b/mk/x64.mk index 98c73210..877c1ed8 100644 --- a/mk/x64.mk +++ b/mk/x64.mk @@ -17,7 +17,6 @@ ARCH_DEFS = \ \#define RESERVED_GOT_NUM 3\n$\ \#define R_ARCH_JUMP_SLOT 7 /* R_X86_64_JUMP_SLOT */\n$\ \#define REG_CNT 11 /* rdi rsi rdx rcx r8 r9 rax rbx r14 r12 r13 */\n$\ - \#define DYN_BIND_NOW 1 /* this PLT has no lazy-resolution path */\n$\ \#define HAVE_COND_MOVE 1 /* CMOVcc */\n$\ \#define CALLEE_SAVED_REGS 4 /* the file ends rbx r14 r12 r13 */\n$\ " diff --git a/src/defs.h b/src/defs.h index 647b6799..a3be7731 100644 --- a/src/defs.h +++ b/src/defs.h @@ -192,13 +192,6 @@ #define ALIGN_UP(val, align) (((val) + (align) - 1) & ~((align) - 1)) #endif -/* Targets whose PLT has no lazy-resolution path ask the loader to bind every - * PLT entry at load time. - */ -#ifndef DYN_BIND_NOW -#define DYN_BIND_NOW 0 -#endif - #define ELF_MACHINE_ARM32 0x28 #define ELF_MACHINE_RV32 0xf3 #define ELF_MACHINE_X86_64 0x3e diff --git a/src/elf.c b/src/elf.c index c2ff605f..17098dee 100644 --- a/src/elf.c +++ b/src/elf.c @@ -1112,14 +1112,17 @@ void elf_generate_dynamic_sections(void) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, 0x1); if (libdl_name) elf_write_dyn(dynamic_sections.elf_dynamic, 0x1, libdl_name); -#if DYN_BIND_NOW == 1 + /* Resolve every PLT entry at load time. This target's PLT[0] does not * arrange the GOT[1]/GOT[2] hand-off the lazy resolver needs, so the loader * writes the final addresses straight into the GOT instead. */ - elf_write_dyn(dynamic_sections.elf_dynamic, 0x18, 0x0); /* DT_BIND_NOW */ - elf_write_dyn(dynamic_sections.elf_dynamic, 0x1e, 0x8); /* DF_BIND_NOW */ -#endif + if (imm_binding) { + elf_write_dyn(dynamic_sections.elf_dynamic, 0x18, + 0x0); /* DT_BIND_NOW */ + elf_write_dyn(dynamic_sections.elf_dynamic, 0x1e, + 0x8); /* DF_BIND_NOW */ + } elf_write_dyn(dynamic_sections.elf_dynamic, 0x0, 0x0); } diff --git a/src/globals.c b/src/globals.c index 2dad38be..3ba7e0c9 100644 --- a/src/globals.c +++ b/src/globals.c @@ -116,6 +116,7 @@ dynamic_sections_t dynamic_sections; /* Command line compilation flags */ bool dynlink = false; +bool imm_binding = false; bool libc = true; bool expand_only = false; bool dump_ir = false; diff --git a/src/main.c b/src/main.c index 4b3aa5d6..49d36311 100644 --- a/src/main.c +++ b/src/main.c @@ -121,7 +121,18 @@ int main(int argc, char *argv[]) libc = false; else if (!strcmp(argv[i], "--dynlink")) dynlink = true; - else if (!strcmp(argv[i], "-E")) + else if (!strcmp(argv[i], "-z")) { + if (i + 1 >= argc) + usage_error("-z requires \"lazy\" or \"now\""); + + if (!strcmp(argv[i + 1], "lazy")) + imm_binding = false; + else if (!strcmp(argv[i + 1], "now")) + imm_binding = true; + else + usage_error("-z requires \"lazy\" or \"now\""); + i++; + } else if (!strcmp(argv[i], "-E")) expand_only = true; else if (!strcmp(argv[i], "-I")) { if (i + 1 >= argc) @@ -145,11 +156,22 @@ int main(int argc, char *argv[]) in = argv[i]; } + if (dynlink) { + switch (ELF_MACHINE) { + /* The following 64-bit targets have no lazy-resolution path, so + * immediate binding must be used. + */ + case ELF_MACHINE_X86_64: + case ELF_MACHINE_AARCH64: + imm_binding = true; + } + } + if (!in) { printf( "Usage: shecc [-I directory] [-o output] [+m] [--dot] [--dump-ir] " "[--warn-string-literals] [--std=c99] [--no-libc] " - "[--dynlink] [-E] \n"); + "[--dynlink] [-z ] [-E] \n"); usage_error("Missing source file"); } diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index 263ccc19..911245f7 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -41,9 +41,10 @@ fi # Command Line Arguments if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" + echo "Usage: $0 [ []]" echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo " binding: lazy, now" echo "" echo "Environment Variables:" echo " VERBOSE=1 Enable verbose output" @@ -73,6 +74,7 @@ case "$1" in esac DYNLINK="${2:-0}" +BINDING="${3:-lazy}" # Banner echo -e "${BLUE}${BOLD}========================================${NC}" @@ -144,7 +146,7 @@ run_abi_test() # Compile local compile_cmd="$SHECC" if [[ "$DYNLINK" == "1" ]]; then - compile_cmd="$compile_cmd --dynlink" + compile_cmd="$compile_cmd --dynlink -z $BINDING" fi compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file" diff --git a/tests/arm64-abi.sh b/tests/arm64-abi.sh index 9a986cb4..5ad6c546 100755 --- a/tests/arm64-abi.sh +++ b/tests/arm64-abi.sh @@ -4,7 +4,7 @@ set -eu if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" >&2 + echo "Usage: $0 [ []]" >&2 exit 2 fi @@ -23,7 +23,7 @@ case "$1" in esac if [ "${2:-0}" = 1 ]; then - shecc+=(--dynlink) + shecc+=(--dynlink -z "${3:-lazy}") link_mode=dynamic else link_mode=static diff --git a/tests/driver.sh b/tests/driver.sh index db80f40f..ea3380bd 100755 --- a/tests/driver.sh +++ b/tests/driver.sh @@ -106,7 +106,7 @@ case "$1" in esac if [ $# -ge 2 ] && [ "$2" = "1" ]; then - readonly SHECC_CFLAGS="--dynlink" + readonly SHECC_CFLAGS="--dynlink ${3:-lazy}" readonly LINK_MODE="dynamic" else readonly SHECC_CFLAGS="" diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index 34c3aadb..1ea3ca51 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -41,9 +41,10 @@ fi # Command Line Arguments if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" + echo "Usage: $0 [ []]" echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo " binding: lazy, now" echo "" echo "Environment Variables:" echo " VERBOSE=1 Enable verbose output" @@ -73,6 +74,7 @@ case "$1" in esac DYNLINK="${2:-0}" +BINDING="${3:-lazy}" # Banner echo -e "${BLUE}${BOLD}========================================${NC}" @@ -144,7 +146,7 @@ run_abi_test() # Compile local compile_cmd="$SHECC" if [[ "$DYNLINK" == "1" ]]; then - compile_cmd="$compile_cmd --dynlink" + compile_cmd="$compile_cmd --dynlink -z $BINDING" fi compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file" diff --git a/tests/x64-abi.sh b/tests/x64-abi.sh index cf91f280..12e75fc2 100755 --- a/tests/x64-abi.sh +++ b/tests/x64-abi.sh @@ -42,9 +42,10 @@ fi # Command Line Arguments if [ "$#" -lt 1 ]; then - echo "Usage: $0 []" + echo "Usage: $0 [ []]" echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo " binding: lazy, now" echo "" echo "Environment Variables:" echo " VERBOSE=1 Enable verbose output" @@ -74,6 +75,7 @@ case "$1" in esac DYNLINK="${2:-0}" +BINDING="${3:-lazy}" # Banner echo -e "${BLUE}${BOLD}========================================${NC}" @@ -149,7 +151,7 @@ run_abi_test() # Compile local compile_cmd="$SHECC" if [[ "$DYNLINK" == "1" ]]; then - compile_cmd="$compile_cmd --dynlink" + compile_cmd="$compile_cmd --dynlink -z $BINDING" fi compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file"