From 5fea55711f7d79334b762ebe6cf745d3e8651f70 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Thu, 26 Mar 2026 20:59:35 +0800 Subject: [PATCH 01/11] Define ELF_MACHINE macros for supported architectures Introduce ELF_MACHINE_ARM32 (0x28) and ELF_MACHINE_RV32 (0xf3) to support architecture-specific logic in future developments. --- src/arch-lower.c | 4 ++-- src/defs.h | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/arch-lower.c b/src/arch-lower.c index 1c70e7d2..96f7ea85 100644 --- a/src/arch-lower.c +++ b/src/arch-lower.c @@ -59,9 +59,9 @@ void riscv_lower(void) /* Entry point: dispatch to the active architecture. */ void arch_lower(void) { -#if ELF_MACHINE == 0x28 /* ARM */ +#if ELF_MACHINE == ELF_MACHINE_ARM32 arm_lower(); -#elif ELF_MACHINE == 0xf3 /* RISC-V */ +#elif ELF_MACHINE == ELF_MACHINE_RV32 riscv_lower(); #else /* Unknown architecture: keep behavior as-is. */ diff --git a/src/defs.h b/src/defs.h index b0b79340..7fdf996e 100644 --- a/src/defs.h +++ b/src/defs.h @@ -101,6 +101,9 @@ #define ALIGN_UP(val, align) (((val) + (align) - 1) & ~((align) - 1)) #endif +#define ELF_MACHINE_ARM32 0x28 +#define ELF_MACHINE_RV32 0xf3 + /* Common data structures */ typedef struct arena_block { char *memory; From ddcbb8e52875dd4a1c4b5166e6d02aefc02805f7 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sat, 21 Mar 2026 21:18:12 +0800 Subject: [PATCH 02/11] Implement dynamic linking for the RISC-V architecture This commit primarily improves the ELF handling and code generator to enable the compiler to produce a dynamically linked executable targeting the RISC-V architecture. - Update the selected dynamic linker path and the ABI flag. - Allow the ELF handling to generate RELA relocation table. - Use REL relocation when the target architecture is Arm. Othereise, use RELA relocation for RISC-V. - Improve GOT generation process. - Arm: reserve three entries. - RISC-V: reserve two entries. - Implement PLT generation for RISC-V. - The generation process follows the RISC-V ABI. The first PLT entry uses 8 instructions to call '_dl_runtime_resolve'. The subsequent entry uses 4 instructions to perform an indirect function call via GOT. - Refine the function call handling for the RISC-V code generator. - Perform a direct call for internal functions - Otherwise, use PLT table to peform an indirect call for external functions. - Enhance the build system: - Allow the build system to generate dynamically linked compilers when targeting the RISC-V architecture. - Detect the sysroot path of the RISC-V GNU toolchain automatically. - Remove the unnecessary 'got_offset' field from 'struct func'. --- Makefile | 4 - mk/arm.mk | 1 + mk/riscv.mk | 42 ++++- src/defs.h | 22 ++- src/elf.c | 371 +++++++++++++++++++++++++------------ src/globals.c | 19 +- src/riscv-codegen.c | 439 ++++++++++++++++++++++++++++++++++++++++---- 7 files changed, 726 insertions(+), 172 deletions(-) diff --git a/Makefile b/Makefile index f79d6650..71059229 100644 --- a/Makefile +++ b/Makefile @@ -49,10 +49,6 @@ STAGE0_FLAGS ?= --dump-ir STAGE1_FLAGS ?= DYNLINK ?= 0 ifeq ($(DYNLINK),1) - ifeq ($(ARCH),riscv) - # TODO: implement dynamic linking for RISC-V. - $(error "Dynamic linking mode is not implemented for RISC-V") - endif STAGE0_FLAGS += --dynlink STAGE1_FLAGS += --dynlink endif diff --git a/mk/arm.mk b/mk/arm.mk index 53fe0899..1210ce18 100644 --- a/mk/arm.mk +++ b/mk/arm.mk @@ -16,6 +16,7 @@ ARCH_DEFS = \ \#define LIBC_SO \"libc.so.6\"\n$\ \#define PLT_FIXUP_SIZE 20\n$\ \#define PLT_ENT_SIZE 12\n$\ + \#define RESERVED_GOT_NUM 3\n$\ \#define R_ARCH_JUMP_SLOT 0x16\n$\ \#define MAX_ARGS_IN_REG 4\n$\ " diff --git a/mk/riscv.mk b/mk/riscv.mk index 536d2c0e..fde18180 100644 --- a/mk/riscv.mk +++ b/mk/riscv.mk @@ -1,19 +1,49 @@ # Enforce the use qemu of by setting the ALLOW_MACHINES variable to empty ALLOW_MACHINES = ARCH_RUNNER = qemu-riscv32 + +# Since the selected dynamic linker uses 'double-float ABI', +# ELF_FLAGS is set to 0x04 here to match this ABI. ARCH_DEFS = \ "/* target: RISCV */\n$\ \#pragma once\n$\ \#define ARCH_PREDEFINED \"__riscv\" /* Older versions of the GCC toolchain defined __riscv__ */\n$\ \#define ELF_MACHINE 0xf3\n$\ - \#define ELF_FLAGS 0\n$\ - \#define DYN_LINKER \"/lib/ld-linux.so.3\"\n$\ + \#define ELF_FLAGS 0x4\n$\ + \#define DYN_LINKER \"/lib/ld-linux-riscv32-ilp32d.so.1\"\n$\ \#define LIBC_SO \"libc.so.6\"\n$\ - \#define PLT_FIXUP_SIZE 20\n$\ - \#define PLT_ENT_SIZE 12\n$\ + \#define PLT_FIXUP_SIZE 32\n$\ + \#define PLT_ENT_SIZE 16\n$\ + \#define RESERVED_GOT_NUM 2\n$\ \#define R_ARCH_JUMP_SLOT 0x5\n$\ \#define MAX_ARGS_IN_REG 8\n$\ " -# TODO: Set this variable for RISC-V architecture -RUNNER_LD_PREFIX= +ifeq ($(USE_QEMU),1) + ifeq ($(DYNLINK),1) + CROSS_COMPILE = riscv32-unknown-linux-gnu- + RISCV_CC = $(CROSS_COMPILE)gcc + RISCV_CC := $(shell which $(RISCV_CC)) + ifndef RISCV_CC + $(error "Unable to find RISC-V GNU toolchain.") + endif + + LD_LINUX_PATH := $(shell cd $(shell $(RISCV_CC) --print-sysroot) 2>/dev/null && pwd) + ifeq ("$(LD_LINUX_PATH)","/") + LD_LINUX_PATH := $(shell dirname "$(shell which $(RISCV_CC))")/.. + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + LD_LINUX_PATH := $(LD_LINUX_PATH)/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//')/libc + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + ifndef LD_LINUX_PATH + LD_LINUX_PATH = /usr/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//') + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + endif + endif + + ifndef LD_LINUX_PATH + $(error "Dynamic linking mode requires ld-linux.so") + endif + + RUNNER_LD_PREFIX = -L $(LD_LINUX_PATH) + endif +endif diff --git a/src/defs.h b/src/defs.h index 7fdf996e..761b70b7 100644 --- a/src/defs.h +++ b/src/defs.h @@ -43,6 +43,7 @@ #define MAX_DYNSYM 1024 #define MAX_DYNSTR 1024 #define MAX_RELPLT 1024 +#define MAX_RELAPLT 1024 #define MAX_PLT 1024 #define MAX_GOTPLT 1024 #define MAX_CONSTANTS 1024 @@ -612,7 +613,7 @@ struct func { /* Information used for dynamic linking */ bool is_used; - int plt_offset, got_offset; + int plt_offset; struct func *next; }; @@ -685,15 +686,28 @@ typedef struct { strbuf_t *elf_dynsym; strbuf_t *elf_dynstr; strbuf_t *elf_relplt; + strbuf_t *elf_relaplt; strbuf_t *elf_plt; strbuf_t *elf_got; int elf_interp_start; int elf_relplt_start; + int elf_relaplt_start; int elf_plt_start; int elf_got_start; int relplt_size; + int relaplt_size; int plt_size; int got_size; + + /* Currently, we don't consider the scenarios involving + * a mixture of REL and RELA relocation entries. + * + * Therefore, use a flag to determine the type of + * relocation entries to be processed: + * - true: use RELA relocation entries + * - false: use REL relocation entries + */ + bool use_relaplt; } dynamic_sections_t; /* For .dynsym section. */ @@ -712,6 +726,12 @@ typedef struct { int r_info; } elf32_rel_t; +typedef struct { + int r_offset; + int r_info; + int r_addend; +} elf32_rela_t; + /* For .dynamic section */ typedef struct { int d_tag; diff --git a/src/elf.c b/src/elf.c index 17e2244a..b588d143 100644 --- a/src/elf.c +++ b/src/elf.c @@ -75,17 +75,20 @@ void elf_generate_header(void) * - number of section headers = 15 * - section header index of .shstrtab = 14 */ + int elf_relplt_size = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt->size + : dynamic_sections.elf_relplt->size; phnum = 4; shnum = 15; shstrndx = 14; - shoff = - elf_header_len + elf_code->size + elf_data->size + - elf_rodata->size + elf_symtab->size + elf_strtab->size + - elf_shstrtab->size + dynamic_sections.elf_interp->size + - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size + - dynamic_sections.elf_got->size + dynamic_sections.elf_dynstr->size + - dynamic_sections.elf_dynsym->size + - dynamic_sections.elf_dynamic->size; + shoff = elf_header_len + elf_code->size + elf_data->size + + elf_rodata->size + elf_symtab->size + elf_strtab->size + + elf_shstrtab->size + dynamic_sections.elf_interp->size + + elf_relplt_size + dynamic_sections.elf_plt->size + + dynamic_sections.elf_got->size + + dynamic_sections.elf_dynstr->size + + dynamic_sections.elf_dynsym->size + + dynamic_sections.elf_dynamic->size; } else { /* In static linking mode: * - number of program headers = 2 @@ -187,9 +190,12 @@ void elf_generate_header(void) void elf_generate_program_headers(void) { + strbuf_t *elf_relplt = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt + : dynamic_sections.elf_relplt; if (!elf_program_header || !elf_code || !elf_data || !elf_rodata || (dynlink && - (!dynamic_sections.elf_interp || !dynamic_sections.elf_relplt || + (!dynamic_sections.elf_interp || !elf_relplt || !dynamic_sections.elf_plt || !dynamic_sections.elf_got || !dynamic_sections.elf_dynstr || !dynamic_sections.elf_dynsym || !dynamic_sections.elf_dynamic))) { @@ -232,10 +238,8 @@ void elf_generate_program_headers(void) phdr.p_flags = 5; /* flags */ phdr.p_align = PAGESIZE; /* alignment */ if (dynlink) { - phdr.p_filesz += - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size; - phdr.p_memsz += - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size; + phdr.p_filesz += elf_relplt->size + dynamic_sections.elf_plt->size; + phdr.p_memsz += elf_relplt->size + dynamic_sections.elf_plt->size; } elf_write_blk(elf_program_header, &phdr, sizeof(elf32_phdr_t)); @@ -250,8 +254,7 @@ void elf_generate_program_headers(void) phdr.p_flags = 6; /* flags */ phdr.p_align = PAGESIZE; /* alignment */ if (dynlink) { - phdr.p_offset += - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size; + phdr.p_offset += elf_relplt->size + dynamic_sections.elf_plt->size; phdr.p_vaddr = dynamic_sections.elf_interp_start; phdr.p_paddr = dynamic_sections.elf_interp_start; phdr.p_filesz += dynamic_sections.elf_interp->size + @@ -272,7 +275,7 @@ void elf_generate_program_headers(void) /* program header - program interpreter (.interp section) */ phdr.p_type = 3; /* PT_INTERP */ phdr.p_offset = elf_header_len + elf_code->size + elf_rodata->size + - dynamic_sections.elf_relplt->size + + elf_relplt->size + dynamic_sections.elf_plt->size; /* offset of segment */ phdr.p_vaddr = dynamic_sections.elf_interp_start; /* virtual address */ phdr.p_paddr = dynamic_sections.elf_interp_start; /* physical address */ @@ -286,7 +289,7 @@ void elf_generate_program_headers(void) phdr.p_type = 2; /* PT_DYNAMIC */ phdr.p_offset = elf_header_len + elf_code->size + elf_rodata->size + - dynamic_sections.elf_relplt->size + dynamic_sections.elf_plt->size + + elf_relplt->size + dynamic_sections.elf_plt->size + dynamic_sections.elf_interp->size + dynamic_sections.elf_got->size + dynamic_sections.elf_dynstr->size + dynamic_sections.elf_dynsym->size; /* offset of segment */ @@ -308,11 +311,14 @@ void elf_generate_program_headers(void) void elf_generate_section_headers(void) { + strbuf_t *elf_relplt = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt + : dynamic_sections.elf_relplt; /* Check for null pointers to prevent crashes */ if (!elf_section_header || !elf_code || !elf_data || !elf_rodata || !elf_symtab || !elf_strtab || !elf_shstrtab || (dynlink && - (!dynamic_sections.elf_interp || !dynamic_sections.elf_relplt || + (!dynamic_sections.elf_interp || !elf_relplt || !dynamic_sections.elf_plt || !dynamic_sections.elf_got || !dynamic_sections.elf_dynstr || !dynamic_sections.elf_dynsym || !dynamic_sections.elf_dynamic))) { @@ -396,20 +402,37 @@ void elf_generate_section_headers(void) sh_name += strlen(".rodata") + 1; if (dynlink) { - /* .rel.plt */ + /* .rel.plt or .rela.plt */ + int sh_type, sh_addr, sh_size, sh_entsize, __ofs, __sh_name; + + if (dynamic_sections.use_relaplt) { + sh_type = 4; /* SHT_RELA */ + sh_addr = dynamic_sections.elf_relaplt_start; + sh_size = dynamic_sections.elf_relaplt->size; + sh_entsize = sizeof(elf32_rela_t); + __ofs = dynamic_sections.elf_relaplt->size; + __sh_name = strlen(".rela.plt") + 1; + } else { + sh_type = 9; /* SHT_REL */ + sh_addr = dynamic_sections.elf_relplt_start; + sh_size = dynamic_sections.elf_relplt->size; + sh_entsize = sizeof(elf32_rel_t); + __ofs = dynamic_sections.elf_relplt->size; + __sh_name = strlen(".rel.plt") + 1; + } shdr.sh_name = sh_name; - shdr.sh_type = 9; /* SHT_REL */ + shdr.sh_type = sh_type; shdr.sh_flags = 0x42; /* 0x40 | SHF_ALLOC */ - shdr.sh_addr = dynamic_sections.elf_relplt_start; + shdr.sh_addr = sh_addr; shdr.sh_offset = ofs; - shdr.sh_size = dynamic_sections.elf_relplt->size; + shdr.sh_size = sh_size; shdr.sh_link = 8; /* The section header index of .dynsym. */ shdr.sh_info = 6; /* The section header index of .got. */ shdr.sh_addralign = 4; - shdr.sh_entsize = sizeof(elf32_rel_t); + shdr.sh_entsize = sh_entsize; elf_write_blk(elf_section_header, &shdr, sizeof(elf32_shdr_t)); - ofs += dynamic_sections.elf_relplt->size; - sh_name += strlen(".rel.plt") + 1; + ofs += __ofs; + sh_name += __sh_name; /* .plt */ shdr.sh_name = sh_name; @@ -597,9 +620,12 @@ void elf_align(strbuf_t *elf_array) void elf_generate_sections(void) { + strbuf_t *elf_relplt = dynamic_sections.use_relaplt + ? dynamic_sections.elf_relaplt + : dynamic_sections.elf_relplt; if (!elf_shstrtab || (dynlink && - (!dynamic_sections.elf_interp || !dynamic_sections.elf_relplt || + (!dynamic_sections.elf_interp || !elf_relplt || !dynamic_sections.elf_plt || !dynamic_sections.elf_got || !dynamic_sections.elf_dynstr || !dynamic_sections.elf_dynsym || !dynamic_sections.elf_dynamic))) { @@ -609,21 +635,20 @@ void elf_generate_sections(void) if (dynlink) { /* In dynamic linking mode, elf_generate_sections() also generates - * .interp, .dynsym, .dynstr, .relplt, .got and dynamic sections. + * .interp, .dynsym, .dynstr, .rel.plt (.rela.plt), .got and dynamic + * sections. * * .plt section is generated at the code generation phase. - * - * TODO: - * Define a new structure named 'elf32_rela_t' and use it to generate - * relocation entries for RISC-V architecture. */ elf32_sym_t sym; elf32_dyn_t dyn; elf32_rel_t rel; - int dymsym_idx = 1, func_plt_ofs, func_got_ofs, st_name = 0; + elf32_rela_t rela; + int dymsym_idx = 1, func_plt_ofs, st_name = 0; memset(&sym, 0, sizeof(elf32_sym_t)); memset(&dyn, 0, sizeof(elf32_dyn_t)); memset(&rel, 0, sizeof(elf32_rel_t)); + memset(&rela, 0, sizeof(elf32_rela_t)); /* .interp section */ elf_write_str(dynamic_sections.elf_interp, DYN_LINKER); @@ -646,14 +671,25 @@ void elf_generate_sections(void) * - Add a new PLT relocation entry to .relplt section. * - Add a new dynamic symbol entry to .dynsym section. * - Append the external function name to .dynstr section. - * - Set plt_offset and got_offset for the external function. + * - Set plt_offset for the external function. * * Since __libc_start_main is not added to the function list, * it must be handled additionally first. */ - rel.r_offset = dynamic_sections.elf_got_start + PTR_SIZE * 3; - rel.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; - elf_write_blk(dynamic_sections.elf_relplt, &rel, sizeof(elf32_rel_t)); + if (dynamic_sections.use_relaplt) { + rela.r_offset = + dynamic_sections.elf_got_start + PTR_SIZE * RESERVED_GOT_NUM; + rela.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; + rela.r_addend = 0; + elf_write_blk(dynamic_sections.elf_relaplt, &rela, + sizeof(elf32_rela_t)); + } else { + rel.r_offset = + dynamic_sections.elf_got_start + PTR_SIZE * RESERVED_GOT_NUM; + rel.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; + elf_write_blk(dynamic_sections.elf_relplt, &rel, + sizeof(elf32_rel_t)); + } sym.st_name = st_name; sym.st_info = ELF32_ST_INFO(1, 2); /* STB_GLOBAL = 1, STT_FUNC = 2 */ @@ -664,118 +700,181 @@ void elf_generate_sections(void) elf_write_byte(dynamic_sections.elf_dynstr, 0); st_name += strlen("__libc_start_main") + 1; - /* Because PLT[1] and GOT[3] are reserved for __libc_start_main, - * its plt_offset and got_offset must be PLT_FIXUP_SIZE and - * PTR_SIZE * 3, respectively. Therefore, no offset assignment is + /* Because PLT[1] is reserved for __libc_start_main, its plt_offset + * must be PLT_FIXUP_SIZE. Therefore, no offset assignment is * required for this function. */ func_plt_ofs = PLT_FIXUP_SIZE + PLT_ENT_SIZE; - func_got_ofs = PTR_SIZE << 2; for (func_t *func = FUNC_LIST.head; func; func = func->next) { - if (func->is_used && !func->bbs) { + if (!func->is_used || func->bbs) + continue; + /* If the function is used and has no basic block, + * consider it to be an external function. + */ + + if (dynamic_sections.use_relaplt) { + rela.r_offset += PTR_SIZE; + rela.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; + rela.r_addend = 0; + elf_write_blk(dynamic_sections.elf_relaplt, &rela, + sizeof(elf32_rela_t)); + } else { rel.r_offset += PTR_SIZE; rel.r_info = (dymsym_idx << 8) | R_ARCH_JUMP_SLOT; elf_write_blk(dynamic_sections.elf_relplt, &rel, sizeof(elf32_rel_t)); + } - sym.st_name = st_name; - sym.st_info = - ELF32_ST_INFO(1, 2); /* STB_GLOBAL = 1, STT_FUNC = 2 */ - elf_write_blk(dynamic_sections.elf_dynsym, &sym, - sizeof(elf32_sym_t)); - dymsym_idx += 1; + sym.st_name = st_name; + sym.st_info = + ELF32_ST_INFO(1, 2); /* STB_GLOBAL = 1, STT_FUNC = 2 */ + elf_write_blk(dynamic_sections.elf_dynsym, &sym, + sizeof(elf32_sym_t)); + dymsym_idx += 1; - elf_write_str(dynamic_sections.elf_dynstr, - func->return_def.var_name); - elf_write_byte(dynamic_sections.elf_dynstr, 0); - st_name += strlen(func->return_def.var_name) + 1; + elf_write_str(dynamic_sections.elf_dynstr, + func->return_def.var_name); + elf_write_byte(dynamic_sections.elf_dynstr, 0); + st_name += strlen(func->return_def.var_name) + 1; - func->plt_offset = func_plt_ofs; - func->got_offset = func_got_ofs; + func->plt_offset = func_plt_ofs; - func_plt_ofs += PLT_ENT_SIZE; - func_got_ofs += PTR_SIZE; - } + func_plt_ofs += PLT_ENT_SIZE; } /* Ensure proper alignment for .dynstr section. */ elf_align(dynamic_sections.elf_dynstr); /* .got section * - * - GOT[0] holds the virtual address of .dynamic section. - * - GOT[1] and GOT[2] are reserved for link_map and resolver, - * and are initialized to 0. + * - Arm architecture: + * - GOT[0] holds the virtual address of .dynamic section. + * - GOT[1] and GOT[2] are reserved for link_map and resolver, + * and are initialized to 0. + * - RISC-V architecture: + * - GOT[0] and GOT[1] are reserved for resolver and link_map, + * and are initialized to 0. * - The remaining entries are initialized to &PLT[0]. */ - elf_write_int(dynamic_sections.elf_got, - dynamic_sections.elf_got_start + - dynamic_sections.got_size + - dynamic_sections.elf_dynstr->size + - dynamic_sections.elf_dynsym->size); - elf_write_int(dynamic_sections.elf_got, 0); - elf_write_int(dynamic_sections.elf_got, 0); - for (int i = PTR_SIZE * 3; i < dynamic_sections.got_size; i += PTR_SIZE) + switch (ELF_MACHINE) { + case ELF_MACHINE_ARM32: + elf_write_int(dynamic_sections.elf_got, + dynamic_sections.elf_got_start + + dynamic_sections.got_size + + dynamic_sections.elf_dynstr->size + + dynamic_sections.elf_dynsym->size); + elf_write_int(dynamic_sections.elf_got, 0); + elf_write_int(dynamic_sections.elf_got, 0); + break; + case ELF_MACHINE_RV32: + elf_write_int(dynamic_sections.elf_got, 0); + elf_write_int(dynamic_sections.elf_got, 0); + break; + } + for (int i = PTR_SIZE * RESERVED_GOT_NUM; i < dynamic_sections.got_size; + i += PTR_SIZE) elf_write_int(dynamic_sections.elf_got, dynamic_sections.elf_plt_start); /* .dynamic section */ - dyn.d_tag = 0x5; /* STRTAB */ + dyn.d_tag = 0x5; /* DT_STRTAB */ dyn.d_un = dynamic_sections.elf_got_start + dynamic_sections.got_size; /* The virtual address of .dynstr. */ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0xa; /* STRSZ */ + dyn.d_tag = 0xa; /* DT_STRSZ */ dyn.d_un = dynamic_sections.elf_dynstr->size; elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0x6; /* SYMTAB */ + dyn.d_tag = 0x6; /* DT_SYMTAB */ dyn.d_un = dynamic_sections.elf_got_start + dynamic_sections.got_size + dynamic_sections.elf_dynstr ->size; /* The virtual address of .dynsym. */ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0xb; /* SYMENT */ + dyn.d_tag = 0xb; /* DT_SYMENT */ dyn.d_un = sizeof(elf32_sym_t); /* Size of an entry. */ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0x11; /* REL */ - dyn.d_un = dynamic_sections - .elf_relplt_start; /* The virtual address of .rel.plt. */ - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x12; /* RELSZ */ - dyn.d_un = dynamic_sections.relplt_size; - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x13; /* RELENT */ - dyn.d_un = sizeof(elf32_rel_t); - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); + if (dynamic_sections.use_relaplt) { + dyn.d_tag = 0x7; /* DT_RELA */ + dyn.d_un = + dynamic_sections + .elf_relaplt_start; /* The virtual address of .rela.plt. */ + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x8; /* DT_RELASZ */ + dyn.d_un = dynamic_sections.relaplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x9; /* DT_RELAENT */ + dyn.d_un = sizeof(elf32_rela_t); + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x2; /* DT_PLTRELSZ */ + dyn.d_un = dynamic_sections.relaplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x14; /* DT_PLTREL */ + dyn.d_un = 0x7; /* DT_RELA */ + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + /* The virtual address of .rela.plt. */ + dyn.d_tag = 0x17; /* DT_JMPREL */ + dyn.d_un = dynamic_sections.elf_relaplt_start; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + } else { + dyn.d_tag = 0x11; /* DT_REL */ + dyn.d_un = + dynamic_sections + .elf_relplt_start; /* The virtual address of .rel.plt. */ + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x12; /* DT_RELSZ */ + dyn.d_un = dynamic_sections.relplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x13; /* DT_RELENT */ + dyn.d_un = sizeof(elf32_rel_t); + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x2; /* DT_PLTRELSZ */ + dyn.d_un = dynamic_sections.relplt_size; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + dyn.d_tag = 0x14; /* DT_PLTREL */ + dyn.d_un = 0x11; /* DT_REL */ + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + + /* The virtual address of .rel.plt. */ + dyn.d_tag = 0x17; /* DT_JMPREL */ + dyn.d_un = dynamic_sections.elf_relplt_start; + elf_write_blk(dynamic_sections.elf_dynamic, &dyn, + sizeof(elf32_dyn_t)); + } - dyn.d_tag = 0x3; /* PLTGOT */ + dyn.d_tag = 0x3; /* DT_PLTGOT */ dyn.d_un = dynamic_sections.elf_got_start; /* The virtual address of .got.*/ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0x2; /* PLTRELSZ */ - dyn.d_un = dynamic_sections.relplt_size; - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x14; /* PLTREL */ - dyn.d_un = 0x11; - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x17; /* JMPREL */ - dyn.d_un = dynamic_sections - .elf_relplt_start; /* The virtual address of .rel.plt. */ - elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - - dyn.d_tag = 0x1; /* NEEDED */ + dyn.d_tag = 0x1; /* DT_NEEDED */ dyn.d_un = 0x1; /* The index of "libc.so.6" in .dynstr. */ elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); - dyn.d_tag = 0x0; /* NULL */ + dyn.d_tag = 0x0; /* DT_NULL */ dyn.d_un = 0x0; elf_write_blk(dynamic_sections.elf_dynamic, &dyn, sizeof(elf32_dyn_t)); } @@ -789,7 +888,10 @@ void elf_generate_sections(void) elf_write_str(elf_shstrtab, ".rodata"); elf_write_byte(elf_shstrtab, 0); if (dynlink) { - elf_write_str(elf_shstrtab, ".rel.plt"); + if (dynamic_sections.use_relaplt) + elf_write_str(elf_shstrtab, ".rela.plt"); + else + elf_write_str(elf_shstrtab, ".rel.plt"); elf_write_byte(elf_shstrtab, 0); elf_write_str(elf_shstrtab, ".plt"); elf_write_byte(elf_shstrtab, 0); @@ -844,47 +946,67 @@ void elf_preprocess(void) elf_code_start = ELF_START + elf_header_len; elf_rodata_start = elf_code_start + elf_offset; if (dynlink) { - /* Precalculate the sizes of .rel.plt, .plt and .got sections. + /* Precalculate the sizes of .rel.plt (.rela.plt), .plt and .got + * sections. * * Suppose the compiled program has n external functions: - * - .rel.plt contains n entries. + * - .rel.plt (.rela.plt) contains n entries. * - .plt has n entries plus one fixup entry. - * - .got includes n + 3 entries - * - GOT[0] holds the virtual address of .dynamic section. - * - GOT[1] and GOT[2] are reserved for link_map and resolver - * (both set to 0). - * - The remaining entries correspond to all external functions. + * - .got includes n + RESERVED_GOT_NUM entries + * - Arm architecture: + * - GOT[0] holds the virtual address of .dynamic section. + * - GOT[1] and GOT[2] are reserved for link_map and resolver + * (both set to 0). + * - RISC-V architecture: + * - GOT[0] and GOT[1] are reserved for resolver and link_map. + * - Common: + * - The remaining entries correspond to all external functions. * * Next, consider the case of __libc_start_main before initializing * the sizes: - * - .rel.plt has the one entry for __libc_start_main. + * - .rel.plt (.rela.plt) has the one entry for __libc_start_main. * - .plt includes one fixup entry plus one entry for __libc_start_main. - * - .got has 3 + 1 entries. - * - 3 entries for GOT[0] - GOT[2]. - * - 1 entry (GOT[3]) reserved for __libc_start_main. + * - .got has RESERVED_GOT_NUM + 1 entries. + * - RESERVED_GOT_NUM entries for GOT[0] - GOT[RESERVED_GOT_NUM - 1]. + * - 1 entry (GOT[RESERVED_GOT_NUM]) reserved for __libc_start_main. * * Therefore, the following code initialize the section sizes based on * the layout described above, and then traverse the function list in a * for loop to increment the sizes for each newly found external * function. */ - dynamic_sections.relplt_size = sizeof(elf32_rel_t); + if (dynamic_sections.use_relaplt) + dynamic_sections.relaplt_size = sizeof(elf32_rela_t); + else + dynamic_sections.relplt_size = sizeof(elf32_rel_t); dynamic_sections.plt_size = PLT_FIXUP_SIZE + PLT_ENT_SIZE; - dynamic_sections.got_size = PTR_SIZE * 3 + PTR_SIZE; + dynamic_sections.got_size = PTR_SIZE * RESERVED_GOT_NUM + PTR_SIZE; for (func_t *func = FUNC_LIST.head; func; func = func->next) { - if (func->is_used && !func->bbs) { + if (!func->is_used || func->bbs) + continue; + if (dynamic_sections.use_relaplt) + dynamic_sections.relaplt_size += sizeof(elf32_rela_t); + else dynamic_sections.relplt_size += sizeof(elf32_rel_t); - dynamic_sections.plt_size += PLT_ENT_SIZE; - dynamic_sections.got_size += PTR_SIZE; - } + dynamic_sections.plt_size += PLT_ENT_SIZE; + dynamic_sections.got_size += PTR_SIZE; } /* Set the starting addresses of the three sections. */ int elf_interp_size = strlen(DYN_LINKER) + 1; elf_interp_size = ALIGN_UP(elf_interp_size, 4); - dynamic_sections.elf_relplt_start = elf_rodata_start + elf_rodata->size; - dynamic_sections.elf_plt_start = - dynamic_sections.elf_relplt_start + dynamic_sections.relplt_size; + if (dynamic_sections.use_relaplt) { + dynamic_sections.elf_relaplt_start = + elf_rodata_start + elf_rodata->size; + dynamic_sections.elf_plt_start = + dynamic_sections.elf_relaplt_start + + dynamic_sections.relaplt_size; + } else { + dynamic_sections.elf_relplt_start = + elf_rodata_start + elf_rodata->size; + dynamic_sections.elf_plt_start = dynamic_sections.elf_relplt_start + + dynamic_sections.relplt_size; + } /* Since the first section of the second load segment is .interp * when using dynamic linking mode, adding PAGESIZE to elf_interp_start * is to ensure that two load segments don't share a common page. @@ -944,8 +1066,13 @@ void elf_generate(const char *outfile) if (dynlink) { /* Read-only sections */ - for (int i = 0; i < dynamic_sections.elf_relplt->size; i++) - fputc(dynamic_sections.elf_relplt->elements[i], fp); + if (dynamic_sections.use_relaplt) + for (int i = 0; i < dynamic_sections.elf_relaplt->size; i++) + fputc(dynamic_sections.elf_relaplt->elements[i], fp); + else { + for (int i = 0; i < dynamic_sections.elf_relplt->size; i++) + fputc(dynamic_sections.elf_relplt->elements[i], fp); + } for (int i = 0; i < dynamic_sections.elf_plt->size; i++) fputc(dynamic_sections.elf_plt->elements[i], fp); /* Readable and writable sections */ diff --git a/src/globals.c b/src/globals.c index f284d24c..98cfeb36 100644 --- a/src/globals.c +++ b/src/globals.c @@ -1308,11 +1308,23 @@ void global_init(void) elf_bss_size = 0; elf_shstrtab = strbuf_create(MAX_SHSTR); elf_section_header = strbuf_create(MAX_SECTION_HEADER); + + switch (ELF_MACHINE) { + case ELF_MACHINE_ARM32: + dynamic_sections.use_relaplt = false; + break; + case ELF_MACHINE_RV32: + dynamic_sections.use_relaplt = true; + break; + } dynamic_sections.elf_interp = strbuf_create(MAX_INTERP); dynamic_sections.elf_dynamic = strbuf_create(MAX_DYNAMIC); dynamic_sections.elf_dynsym = strbuf_create(MAX_DYNSYM); dynamic_sections.elf_dynstr = strbuf_create(MAX_DYNSTR); - dynamic_sections.elf_relplt = strbuf_create(MAX_RELPLT); + if (dynamic_sections.use_relaplt) + dynamic_sections.elf_relaplt = strbuf_create(MAX_RELAPLT); + else + dynamic_sections.elf_relplt = strbuf_create(MAX_RELPLT); dynamic_sections.elf_plt = strbuf_create(MAX_PLT); dynamic_sections.elf_got = strbuf_create(MAX_GOTPLT); } @@ -1449,7 +1461,10 @@ void global_release(void) strbuf_free(dynamic_sections.elf_dynamic); strbuf_free(dynamic_sections.elf_dynsym); strbuf_free(dynamic_sections.elf_dynstr); - strbuf_free(dynamic_sections.elf_relplt); + if (dynamic_sections.use_relaplt) + strbuf_free(dynamic_sections.elf_relaplt); + else + strbuf_free(dynamic_sections.elf_relplt); strbuf_free(dynamic_sections.elf_plt); strbuf_free(dynamic_sections.elf_got); } diff --git a/src/riscv-codegen.c b/src/riscv-codegen.c index ee641b60..99907de2 100644 --- a/src/riscv-codegen.c +++ b/src/riscv-codegen.c @@ -10,6 +10,76 @@ #include "globals.c" #include "riscv.c" +#define RV32_ALIGNMENT 16 + +/* Explanation: registers preservation/restoration + * + * The following table illustrates which registers are caller-saved + * or callee-saved: + * +-----------+--------+ + * | Register | Saver | + * | ABI Name | | + * +-----------+--------+ + * | zero, | | + * | gp, | (None) | + * | tp | | + * +-----------+--------+ + * | ra | Caller | + * +-----------+--------+ + * | sp | Callee | + * +-----------+--------+ + * | a0 - a7 | Caller | + * +-----------+--------+ + * | s0 - s11 | Callee | + * +-----------+--------+ + * | t0 - t6 | Caller | + * +-----------+--------+ + * + * - ra and sp: are properly handled by the code generator. + * + * - a0 - a7: are implicitly handled in register allocation phase. + * + * Register allocation use 8 virtual registers (vreg0 - vreg7) to + * allocate registers from IR. When encountering a function call, + * all virtual registers are spilled properly before the call, and + * their contents will also be restored if they are used after the + * call. + * + * Since vreg0 - vreg7 map to a0 - a7 directly, these physical + * registers are also preserved/restored naturally whan a function + * is invoked or returned. + * + * - s0 and s1: handling depends on the linking mode. + * + * Static linking: + * For s0 register, it is used at the program entry point under + * static linking. The program entry point will use s0 to store + * the value of sp register and obtain 'argc' and 'argv' via s0 + * before calling the main function. Then, the content of s0 is + * no longer used after calling the main function, so its + * preservation and restoration can also be ignored. + * + * s1 is not used under static linking, so it is not necessary to + * be handled. + * + * Dynamic linking: + * Both registers are used to preserve 'argc' and 'argv' and pass + * them to the main function. Therefore, the code generator ensures + * that proper instructions are generated to preserve/restore + * their contents via stack. + * + * - s2 - s11: are not necessary to be preserved or restored. + * + * The current code generator does not use s2 - s11 to generate + * instructions, so these registers are not needed to be processed. + * + * - t0 - t6: are not necessary to be handled. + * + * These registers are used to store certain temporary values; + * however, the code generator ensures that these values do not + * persist across function calls. + */ + void update_elf_offset(ph2_ir_t *ph2_ir) { switch (ph2_ir->op) { @@ -118,12 +188,30 @@ void update_elf_offset(ph2_ir_t *ph2_ir) void cfg_flatten(void) { - func_t *func = find_func("__syscall"); - /* Prologue ~ 6 instructions (24 bytes). Place __syscall right after. */ - func->bbs->elf_offset = 24; + func_t *func; + + if (dynlink) { + /* When using dynamic linking, 20 instructions are generated at + * the program entry point to perform the following operations: + * - prepare arguments and call __libc_start_main() + * - preserve a0 ('argc'), a1 ('argv') and sp. + * - allocate a global stack and jump to global init function. + */ + elf_offset = 80; + } else { + /* Under static linking, "__syscall" must be generated to allow + * the program to invoke system calls. + * + * "__syscall" consists of 9 instructions, preceded by 6 initial + * instructions. Consequently, the elf offset for "__syscall" is + * is 24 bytes, and the offset for the subsequent function + * (GLOBAL_FUNC) is 60 bytes. + */ + func = find_func("__syscall"); + func->bbs->elf_offset = 24; + elf_offset = 60; + } - /* Reserve space for prologue (24) + syscall trampoline (36) = 60 bytes. */ - elf_offset = 60; GLOBAL_FUNC->bbs->elf_offset = elf_offset; for (ph2_ir_t *ph2_ir = GLOBAL_FUNC->bbs->ph2_ir_list.head; ph2_ir; @@ -132,7 +220,10 @@ void cfg_flatten(void) } /* prepare 'argc' and 'argv', then proceed to 'main' function */ - elf_offset += 24; + if (dynlink) + elf_offset += 44; + else + elf_offset += 24; for (func = FUNC_LIST.head; func; func = func->next) { /* Skip function declarations without bodies */ @@ -144,6 +235,14 @@ void cfg_flatten(void) flatten_ir->src0 = func->stack_size; strncpy(flatten_ir->func_name, func->return_def.var_name, MAX_VAR_LEN); + /* Except for local variables, it must allocate additional space + * to preserve the content of ra at each function entry point. + * + * 'stack_size' doesn't include the additional space, so an extra + * number '4' is added to 'stack_size'. + */ + int stack_top_ofs = ALIGN_UP(func->stack_size + 4, RV32_ALIGNMENT); + for (basic_block_t *bb = func->bbs; bb; bb = bb->rpo_next) { bb->elf_offset = elf_offset; @@ -154,9 +253,23 @@ void cfg_flatten(void) for (ph2_ir_t *insn = bb->ph2_ir_list.head; insn; insn = insn->next) { - /* TODO: recalculate the offset for instructions with the - * 'ofs_based_on_stack_top' flag set. - */ + if (insn->ofs_based_on_stack_top) { + switch (insn->op) { + case OP_load: + case OP_address_of: + insn->src0 = insn->src0 + stack_top_ofs; + break; + case OP_store: + insn->src1 = insn->src1 + stack_top_ofs; + break; + default: + /* Ignore opcodes with the ofs_based_on_stack_top + * flag set since only the three opcodes above needs + * to access a variable's address. + */ + break; + } + } flatten_ir = add_existed_ph2_ir(insn); if (insn->op == OP_return) { @@ -193,9 +306,10 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) switch (ph2_ir->op) { case OP_define: + ofs = ALIGN_UP(ph2_ir->src0 + 4, RV32_ALIGNMENT); emit(__sw(__ra, __sp, -4)); - emit(__lui(__t0, rv_hi(ph2_ir->src0 + 4))); - emit(__addi(__t0, __t0, rv_lo(ph2_ir->src0 + 4))); + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__sub(__sp, __sp, __t0)); return; case OP_load_constant: @@ -274,7 +388,16 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_call: func = find_func(ph2_ir->func_name); - emit(__jal(__ra, func->bbs->elf_offset - elf_code->size)); + if (func->bbs) + ofs = func->bbs->elf_offset - elf_code->size; + else if (dynlink) { + ofs = (dynamic_sections.elf_plt_start + func->plt_offset) - + (elf_code_start + elf_code->size); + } else { + printf("The '%s' function is not implemented\n", ph2_ir->func_name); + abort(); + } + emit(__jal(__ra, ofs)); return; case OP_load_data_address: emit(__lui(rd, rv_hi(elf_data_start + ph2_ir->src0))); @@ -286,7 +409,14 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) return; case OP_address_of_func: func = find_func(ph2_ir->func_name); - ofs = elf_code_start + func->bbs->elf_offset; + if (func->bbs) + ofs = elf_code_start + func->bbs->elf_offset; + else if (dynlink) + ofs = dynamic_sections.elf_plt_start + func->plt_offset; + else { + printf("The '%s' function is not implemented\n", ph2_ir->func_name); + abort(); + } emit(__lui(__t0, rv_hi(ofs))); emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__sw(__t0, rs1, 0)); @@ -302,8 +432,9 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) emit(__addi(__zero, __zero, 0)); else emit(__addi(__a0, rs1, 0)); - emit(__lui(__t0, rv_hi(ph2_ir->src1 + 4))); - emit(__addi(__t0, __t0, rv_lo(ph2_ir->src1 + 4))); + ofs = ALIGN_UP(ph2_ir->src1 + 4, RV32_ALIGNMENT); + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__add(__sp, __sp, __t0)); emit(__lw(__ra, __sp, -4)); emit(__jalr(__zero, __ra, 0)); @@ -482,26 +613,104 @@ void emit_ph2_ir(ph2_ir_t *ph2_ir) } } +void plt_generate(void); void code_generate(void) { - /* start: save original sp in s0; allocate global stack; run init */ - emit(__addi(__s0, __sp, 0)); - emit(__lui(__t0, rv_hi(GLOBAL_FUNC->stack_size))); - emit(__addi(__t0, __t0, rv_lo(GLOBAL_FUNC->stack_size))); + int ofs; + + if (dynlink) { + plt_generate(); + /* - Initial stack layout when the program starts: + * + * +----------------+ (high address) + * | ... | + * +----------------+ + * | argv[argc - 1] | + * +----------------+ + * | ... | + * +----------------+ + * | argv[0] | + * +----------------+ + * | argc | + * +----------------+ <- sp points to this location. + * + * - At the program entry point, it must call __libc_start_main() + * under dynamic linking. The function prototype is as follows: + * + * int __libc_start_main(int (*main) (int, char **, char **), + * int argc, char **argv, + * void (*init) (void), + * void (*fini) (void), + * void (*rtld_fini) (void), + * void (*stack_end)); + * + * Currently, to execute a dynamically linked program with the + * minimal effort required, we perform the following call: + * -> __libc_start_main(main_wrapper, argc, argv, NULL, + * NULL, NULL, stack_end) + */ + emit(__lui(__a0, rv_hi(elf_code_start + 36))); + emit(__addi(__a0, __a0, rv_lo(elf_code_start + 36))); + emit(__lw(__a1, __sp, 0)); + emit(__addi(__a2, __sp, 4)); + emit(__addi(__a3, __zero, 0)); + emit(__addi(__a4, __zero, 0)); + emit(__addi(__a5, __zero, 0)); + emit(__addi(__a6, __sp, 0)); + + /* Call __libc_start_main() via PLT[1] */ + ofs = (dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE) - + (elf_code_start + elf_code->size); + emit(__jal(__ra, ofs)); + + /* The main wrapper is located here under the dynamic linking mode + * + * Use s0 and s1 registers to temporarily store 'argc' and 'argv', + * while preserving ra on the stack. + * + * After the main function completes its execution, it must use + * the original content of ra to transfer control back to + * __libc_start_main(). + */ + emit(__addi(__sp, __sp, -12)); + emit(__sw(__ra, __sp, 8)); + emit(__sw(__s1, __sp, 4)); /* callee-saved */ + emit(__sw(__s0, __sp, 0)); /* callee-saved */ + emit(__addi(__s0, __a0, 0)); /* argc */ + emit(__addi(__s1, __a1, 0)); /* argv */ + ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, RV32_ALIGNMENT) + 4; + } else { + /* When using static linking, the starting address + * of the main wrapper is here. + * + * Save original sp in s0 first. + */ + ofs = ALIGN_UP(GLOBAL_FUNC->stack_size, RV32_ALIGNMENT); + emit(__addi(__s0, __sp, 0)); + } + /* Next, the main wrapper performs: + * 1. allocate global stack + * 2. jump to global init function + * 3. call the main function + */ + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); emit(__sub(__sp, __sp, __t0)); emit(__addi(__gp, __sp, 0)); /* Set up global pointer */ emit(__jal(__ra, GLOBAL_FUNC->bbs->elf_offset - elf_code->size)); - /* syscall trampoline for __syscall - must be at offset 24 */ - emit(__addi(__a7, __a0, 0)); - emit(__addi(__a0, __a1, 0)); - emit(__addi(__a1, __a2, 0)); - emit(__addi(__a2, __a3, 0)); - emit(__addi(__a3, __a4, 0)); - emit(__addi(__a4, __a5, 0)); - emit(__addi(__a5, __a6, 0)); - emit(__ecall()); - emit(__jalr(__zero, __ra, 0)); + if (!dynlink) { + /* syscall trampoline for __syscall */ + emit(__addi(__a7, __a0, 0)); + emit(__addi(__a0, __a1, 0)); + emit(__addi(__a1, __a2, 0)); + emit(__addi(__a2, __a3, 0)); + emit(__addi(__a3, __a4, 0)); + emit(__addi(__a4, __a5, 0)); + emit(__addi(__a5, __a6, 0)); + emit(__ecall()); + emit(__jalr(__zero, __ra, 0)); + } ph2_ir_t *ph2_ir; for (ph2_ir = GLOBAL_FUNC->bbs->ph2_ir_list.head; ph2_ir; @@ -509,16 +718,35 @@ void code_generate(void) emit_ph2_ir(ph2_ir); /* prepare 'argc' and 'argv', then proceed to 'main' function */ - /* use original sp saved in s0 to get argc/argv */ if (MAIN_BB) { - emit(__addi(__t0, __s0, 0)); - emit(__lw(__a0, __t0, 0)); - emit(__addi(__a1, __t0, 4)); - emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size)); + if (dynlink) { + emit(__addi(__a0, __s0, 0)); + emit(__addi(__a1, __s1, 0)); + emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size)); - /* exit with main's return value in a0 */ - emit(__addi(__a7, __zero, 93)); - emit(__ecall()); + /* - Restore sp, s0 and s1. + * - Transfer control back to __libc_start_main() using + * the preserved ra. + */ + emit(__lui(__t0, rv_hi(ofs))); + emit(__addi(__t0, __t0, rv_lo(ofs))); + emit(__add(__sp, __sp, __t0)); + emit(__lw(__ra, __sp, 8)); + emit(__lw(__s1, __sp, 4)); + emit(__lw(__s0, __sp, 0)); + emit(__addi(__sp, __sp, 12)); + emit(__jalr(__zero, __ra, 0)); + } else { + /* use original sp saved in s0 to get argc/argv */ + emit(__addi(__t0, __s0, 0)); + emit(__lw(__a0, __t0, 0)); + emit(__addi(__a1, __t0, 4)); + emit(__jal(__ra, MAIN_BB->elf_offset - elf_code->size)); + + /* exit with main's return value in a0 */ + emit(__addi(__a7, __zero, 93)); + emit(__ecall()); + } } for (int i = 0; i < ph2_ir_idx; i++) { @@ -526,3 +754,140 @@ void code_generate(void) emit_ph2_ir(ph2_ir); } } + +void plt_generate() +{ + int addr_of_plt = dynamic_sections.elf_plt_start; + int addr_of_got = dynamic_sections.elf_got_start; + int end = dynamic_sections.plt_size - PLT_FIXUP_SIZE; + int ofs, pcrel_hi, pcrel_lo; + + ofs = addr_of_got - addr_of_plt; + pcrel_hi = ofs & ~0xFFF; + pcrel_lo = ofs & 0xFFF; + if (pcrel_lo > 2047) { + pcrel_hi += 0x1000; + pcrel_lo -= 0x1000; + } + + /* Accroding the RISC-V ABI specification, the first PLT entry should + * contains the following instructions: + * + * 1: auipc t2, %pcrel_hi(.got) + * sub t1, t1, t3 + * lw t3, %pcrel_lo(1b)(t2) + * addi t1, t1 -(PLT0_SIZE + 12) # PLT0_SIZE is 32 bytes. + * addi t0, t2, %pcrel_lo(1b) + * srli t1, t1, log2(16 / PTRSIZE) # PTRSIZE is 4 bytes. + * lw t0, PTRSIZE(t0) + * jr t3 + * + * +-----------------------------------+-----------------------+ + * | Instruction | Contents of registers | + * +-----------------------------------+-----------------------+ + * | auipc t2, %pcrel_hi(.got) | t0: | + * | | t1: &PLT[N] + 12 | + * | | t2: %pcrel_hi(.got) | + * | | t3: &PLT[0] | + * +-----------------------------------+-----------------------+ + * | sub t1, t1, t3 | t0: | + * | | t1: (N - 1) * 16 + | + * | | 32 + 12 | + * | | t2: %pcrel_hi(.got) | + * | | t3: &PLT[0] | + * +-----------------------------------+-----------------------+ + * | lw t3, %pcrel_lo(1b)(t2) | t0: | + * | | t1: (N - 1) * 16 + | + * | | 32 + 12 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | addi t1, t1 -(PLT0_SIZE + 12) | t0: | + * | | t1: (N - 1) * 16 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | addi t0, t2, %pcrel_lo(1b) | t0: &GOT[0] | + * | | t1: (N - 1) * 16 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | srli t1, t1, log2(16 / PTRSIZE) | t0: &GOT[0] | + * | | t1: (N - 1) * 4 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | lw t0, PTRSIZE(t0) | t0: GOT[1] | + * | | t1: (N - 1) * 4 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * | jr t3 | t0: GOT[1] | + * | | t1: (N - 1) * 4 | + * | | t2: %pcrel_hi(.got) | + * | | t3: GOT[0] | + * +-----------------------------------+-----------------------+ + * + * Note: + * - N >= 1, and it means the N-th external function. + * - &PLT[N] - &PLT[0] = size of PLT[0] + size of several PLT stubs. + * = 32 + (N - 1) * 16 + */ + elf_write_int(dynamic_sections.elf_plt, __auipc(__t2, pcrel_hi)); + elf_write_int(dynamic_sections.elf_plt, __sub(__t1, __t1, __t3)); + elf_write_int(dynamic_sections.elf_plt, __lw(__t3, __t2, pcrel_lo)); + elf_write_int(dynamic_sections.elf_plt, __addi(__t1, __t1, -44)); + elf_write_int(dynamic_sections.elf_plt, __addi(__t0, __t2, pcrel_lo)); + elf_write_int(dynamic_sections.elf_plt, __srli(__t1, __t1, 2)); + elf_write_int(dynamic_sections.elf_plt, __lw(__t0, __t0, 4)); + elf_write_int(dynamic_sections.elf_plt, __jalr(__zero, __t3, 0)); + for (int i = 0; i * PLT_ENT_SIZE < end; i++) { + /* elf_generate() ensures that the .got section is placed + * a higher memory address than the plt section. As a result, + * 'ofs' must always be positive. + * + * addr_of_plt: the starting address of PLT[N]. (N >= 1) + * addr_of_got: the starting address of GOT[N + 1]. + */ + addr_of_plt = + dynamic_sections.elf_plt_start + PLT_FIXUP_SIZE + PLT_ENT_SIZE * i; + addr_of_got = dynamic_sections.elf_got_start + PTR_SIZE * (i + 2); + ofs = addr_of_got - addr_of_plt; + + /* In RISC-V ABI, a PLT stub takes up 4 instructions to load GOT[N + 2]: + * + * 1: auipc t3, %pcrel_hi(function@.got) + * lw t3, %pcrel_lo(1b)(t3) + * jalr t1, t3 + * nop + * + * Each PLT stub uses auipc and lw instructions to perform a + * PC-relative addressing to obtain GOT[N + 1], and then perform + * an unconditional jump. + * + * +-------------------------------------+----------------------------+ + * | Instruction | Contents of registers | + * +-------------------------------------+----------------------------+ + * | auipc t3, %pcrel_hi(function@.got) | t1: | + * | | t3: pcrel_hi(function%got) | + * +-------------------------------------+----------------------------+ + * | lw t3, %pcrel_lo(1b)(t3) | t1: | + * | | t3: GOT[N + 1] | + * +-------------------------------------+----------------------------+ + * | jalr t1, t3 | t1: addr of nop | + * | | t3: GOT[N + 1] | + * +-------------------------------------+----------------------------+ + */ + pcrel_hi = ofs & ~0xFFF; + pcrel_lo = ofs & 0xFFF; + if (pcrel_lo > 2047) { + pcrel_hi += 0x1000; + pcrel_lo -= 0x1000; + } + + elf_write_int(dynamic_sections.elf_plt, __auipc(__t3, pcrel_hi)); + elf_write_int(dynamic_sections.elf_plt, __lw(__t3, __t3, pcrel_lo)); + elf_write_int(dynamic_sections.elf_plt, __jalr(__t1, __t3, 0)); + elf_write_int(dynamic_sections.elf_plt, __addi(__zero, __zero, 0)); + } +} From a85032085a59990d3445c544ebf310afeda0b7ee Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Thu, 23 Apr 2026 20:50:57 +0800 Subject: [PATCH 03/11] Introduce RISC-V dynamic linking snapshots Modify the 'update-snapshots' and 'check-snapshots' make targets to include generation and validation of new snapshots for the RISC-V architecture using dynamic linking. --- Makefile | 8 ++++++-- tests/snapshots/fib-riscv-dynamic.json | 1 + tests/snapshots/hello-riscv-dynamic.json | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 tests/snapshots/fib-riscv-dynamic.json create mode 100644 tests/snapshots/hello-riscv-dynamic.json diff --git a/Makefile b/Makefile index 71059229..8e2e8ebf 100644 --- a/Makefile +++ b/Makefile @@ -104,8 +104,10 @@ check-sanitizer: $(OUT)/$(STAGE0)-sanitizer tests/driver.sh $(Q)rm $(OUT)/shecc check-snapshots: $(OUT)/$(STAGE0) $(SNAPSHOTS) tests/check-snapshots.sh + # static linking $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config check-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=0 --silent;) - $(Q)$(MAKE) distclean config check-snapshot ARCH=arm DYNLINK=1 --silent + # dynamic linking + $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config check-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=1 --silent;) $(VECHO) "Switching backend back to %s (DYNLINK=0)\n" arm $(Q)$(MAKE) distclean config ARCH=arm DYNLINK=0 --silent @@ -130,8 +132,10 @@ check-abi-stage2: $(OUT)/$(STAGE2) fi update-snapshots: tests/update-snapshots.sh + # static linking $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config update-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=0 --silent;) - $(Q)$(MAKE) distclean config update-snapshot ARCH=arm DYNLINK=1 --silent + # dynamic linking + $(Q)$(foreach SNAPSHOT_ARCH, $(ARCHS), $(MAKE) distclean config update-snapshot ARCH=$(SNAPSHOT_ARCH) DYNLINK=1 --silent;) $(VECHO) "Switching backend back to %s (DYNLINK=0)\n" arm $(Q)$(MAKE) distclean config ARCH=arm DYNLINK=0 --silent diff --git a/tests/snapshots/fib-riscv-dynamic.json b/tests/snapshots/fib-riscv-dynamic.json new file mode 100644 index 00000000..34eede5f --- /dev/null +++ b/tests/snapshots/fib-riscv-dynamic.json @@ -0,0 +1 @@ +{"_subgraph_cnt":13,"directed":true,"edges":[{"_gvid":0,"head":14,"headport":"n","tail":13,"tailport":"s"},{"_gvid":1,"head":15,"tail":14,"weight":"100"},{"_gvid":2,"head":16,"tail":15,"weight":"100"},{"_gvid":3,"head":17,"headport":"n","tail":16,"tailport":"sw"},{"_gvid":4,"head":22,"headport":"n","tail":16,"tailport":"se"},{"_gvid":5,"head":18,"tail":17,"weight":"100"},{"_gvid":6,"head":19,"headport":"n","tail":18,"tailport":"s"},{"_gvid":7,"head":19,"headport":"n","tail":20,"tailport":"s"},{"_gvid":8,"head":19,"headport":"n","tail":21,"tailport":"s"},{"_gvid":9,"head":23,"headport":"n","tail":22,"tailport":"s"},{"_gvid":10,"head":24,"tail":23,"weight":"100"},{"_gvid":11,"head":25,"tail":24,"weight":"100"},{"_gvid":12,"head":26,"headport":"n","tail":25,"tailport":"sw"},{"_gvid":13,"head":27,"headport":"n","tail":25,"tailport":"se"},{"_gvid":14,"head":20,"tail":26,"weight":"100"},{"_gvid":15,"head":28,"headport":"n","tail":27,"tailport":"s"},{"_gvid":16,"head":29,"tail":28,"weight":"100"},{"_gvid":17,"head":30,"tail":29,"weight":"100"},{"_gvid":18,"head":31,"tail":30,"weight":"100"},{"_gvid":19,"head":32,"tail":31,"weight":"100"},{"_gvid":20,"head":33,"tail":32,"weight":"100"},{"_gvid":21,"head":34,"tail":33,"weight":"100"},{"_gvid":22,"head":35,"tail":34,"weight":"100"},{"_gvid":23,"head":36,"tail":35,"weight":"100"},{"_gvid":24,"head":37,"tail":36,"weight":"100"},{"_gvid":25,"head":38,"tail":37,"weight":"100"},{"_gvid":26,"head":21,"tail":38,"weight":"100"},{"_gvid":27,"head":40,"tail":39,"weight":"100"},{"_gvid":28,"head":41,"tail":40,"weight":"100"},{"_gvid":29,"head":42,"tail":41,"weight":"100"},{"_gvid":30,"head":43,"tail":42,"weight":"100"},{"_gvid":31,"head":44,"tail":43,"weight":"100"},{"_gvid":32,"head":45,"tail":44,"weight":"100"},{"_gvid":33,"head":46,"tail":45,"weight":"100"},{"_gvid":34,"head":47,"tail":46,"weight":"100"},{"_gvid":35,"head":48,"tail":47,"weight":"100"},{"_gvid":36,"head":49,"headport":"n","tail":48,"tailport":"s"}],"label":"","name":"CFG","objects":[{"_gvid":0,"edges":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"nodes":[13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38],"subgraphs":[1,2,3,4,5,6,7,8,9]},{"_gvid":1,"edges":[],"nodes":[13],"subgraphs":[]},{"_gvid":2,"edges":[1,2],"nodes":[14,15,16],"subgraphs":[]},{"_gvid":3,"edges":[5],"nodes":[17,18],"subgraphs":[]},{"_gvid":4,"edges":[],"nodes":[19],"subgraphs":[]},{"_gvid":5,"edges":[],"nodes":[22],"subgraphs":[]},{"_gvid":6,"edges":[10,11],"nodes":[23,24,25],"subgraphs":[]},{"_gvid":7,"edges":[14],"nodes":[20,26],"subgraphs":[]},{"_gvid":8,"edges":[],"nodes":[27],"subgraphs":[]},{"_gvid":9,"edges":[16,17,18,19,20,21,22,23,24,25,26],"nodes":[21,28,29,30,31,32,33,34,35,36,37,38],"subgraphs":[]},{"_gvid":10,"edges":[27,28,29,30,31,32,33,34,35,36],"nodes":[39,40,41,42,43,44,45,46,47,48,49],"subgraphs":[11,12]},{"_gvid":11,"edges":[27,28,29,30,31,32,33,34,35],"nodes":[39,40,41,42,43,44,45,46,47,48],"subgraphs":[]},{"_gvid":12,"edges":[],"nodes":[49],"subgraphs":[]},{"_gvid":13,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":14,"edges":[],"label":".t00 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":15,"edges":[],"label":".t10 := n0 == .t00","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":16,"edges":[],"label":"BRANCH .t10","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":17,"edges":[],"label":".t20 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":18,"edges":[],"label":"RETURN .t20","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":19,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":20,"edges":[],"label":"RETURN .t50","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":21,"edges":[],"label":"RETURN .t120","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":22,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":23,"edges":[],"label":".t30 := CONST 1","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":24,"edges":[],"label":".t40 := n0 == .t30","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":25,"edges":[],"label":"BRANCH .t40","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":26,"edges":[],"label":".t50 := CONST 1","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":27,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":28,"edges":[],"label":".t60 := CONST 1","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":29,"edges":[],"label":".t70 := n0 - .t60","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":30,"edges":[],"label":"PUSH .t70","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":31,"edges":[],"label":"CALL @fib","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":32,"edges":[],"label":".t80 := RETURN VALUE","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":33,"edges":[],"label":".t90 := CONST 2","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":34,"edges":[],"label":".t100 := n0 - .t90","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":35,"edges":[],"label":"PUSH .t100","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":36,"edges":[],"label":"CALL @fib","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":37,"edges":[],"label":".t110 := RETURN VALUE","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":38,"edges":[],"label":".t120 := .t80 + .t110","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":39,"edges":[],"label":".t130 := [.rodata] + 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":40,"edges":[],"label":".t140 := CONST 10","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":41,"edges":[],"label":"PUSH .t140","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":42,"edges":[],"label":"CALL @fib","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":43,"edges":[],"label":".t150 := RETURN VALUE","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":44,"edges":[],"label":"PUSH .t130","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":45,"edges":[],"label":"PUSH .t150","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":46,"edges":[],"label":"CALL @printf","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":47,"edges":[],"label":".t160 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":48,"edges":[],"label":"RETURN .t160","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":49,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]}],"strict":true} diff --git a/tests/snapshots/hello-riscv-dynamic.json b/tests/snapshots/hello-riscv-dynamic.json new file mode 100644 index 00000000..f005855f --- /dev/null +++ b/tests/snapshots/hello-riscv-dynamic.json @@ -0,0 +1 @@ +{"_subgraph_cnt":3,"directed":true,"edges":[{"_gvid":0,"head":4,"tail":3,"weight":"100"},{"_gvid":1,"head":5,"tail":4,"weight":"100"},{"_gvid":2,"head":6,"tail":5,"weight":"100"},{"_gvid":3,"head":7,"tail":6,"weight":"100"},{"_gvid":4,"head":8,"tail":7,"weight":"100"},{"_gvid":5,"head":9,"tail":8,"weight":"100"},{"_gvid":6,"head":10,"tail":9,"weight":"100"},{"_gvid":7,"head":11,"tail":10,"weight":"100"},{"_gvid":8,"head":12,"headport":"n","tail":11,"tailport":"s"}],"label":"","name":"CFG","objects":[{"_gvid":0,"edges":[0,1,2,3,4,5,6,7,8],"nodes":[3,4,5,6,7,8,9,10,11,12],"subgraphs":[1,2]},{"_gvid":1,"edges":[0,1,2,3,4,5,6,7],"nodes":[3,4,5,6,7,8,9,10,11],"subgraphs":[]},{"_gvid":2,"edges":[],"nodes":[12],"subgraphs":[]},{"_gvid":3,"edges":[],"label":".t00 := [.rodata] + 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":4,"edges":[],"label":"PUSH .t00","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":5,"edges":[],"label":"PUSH argc0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":6,"edges":[],"label":"CALL @printf","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":7,"edges":[],"label":".t10 := [.rodata] + 4","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":8,"edges":[],"label":"PUSH .t10","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":9,"edges":[],"label":"CALL @printf","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":10,"edges":[],"label":".t20 := CONST 0","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":11,"edges":[],"label":"RETURN .t20","nodes":[],"shape":"box","subgraphs":[]},{"_gvid":12,"edges":[],"label":"pseudo","nodes":[],"shape":"box","subgraphs":[]}],"strict":true} From a9056f4dca5fbcdb06e64896a2c59fbfbabbc204 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Thu, 23 Apr 2026 21:24:37 +0800 Subject: [PATCH 04/11] Enable GitHub Actions to validate RISC-V targets with dynamic linking The update workflow now downloads a RISC-V GNU toolchain to provide necessary dependencies and validate the dynamically linked compiler targeting the RISC-V architecture. This update also improves the download step so that it retrieves the ARM/RISC-V GNU toolchain only when verifying the dynamic linking mode. --- .github/workflows/main.yml | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a8e2ef9d..33a82a7b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -9,14 +9,7 @@ jobs: matrix: compiler: [gcc, clang] architecture: [arm, riscv] - link_mode: [static] - include: - - compiler: gcc - architecture: arm - link_mode: dynamic - - compiler: clang - architecture: arm - link_mode: dynamic + link_mode: [static, dynamic] steps: - name: Checkout code uses: actions/checkout@v4 @@ -26,7 +19,23 @@ jobs: sudo apt-get install -q -y graphviz jq sudo apt-get install -q -y qemu-user sudo apt-get install -q -y build-essential - sudo apt-get install -q -y gcc-arm-linux-gnueabihf + if [ "${{ matrix.link_mode }}" = "dynamic" ]; then + if [ "${{ matrix.architecture }}" = "arm" ]; then + sudo apt-get install -q -y gcc-arm-linux-gnueabihf + else + EXPECTED_SHA256=8eef759d13cb71c332cb4b4faabd522918bb6d694e67a24aa7641453567ff140 + FILE=/tmp/riscv32-glibc-ubuntu-24.04-gcc.tar.xz + set -eo pipefail + + sudo wget -qO $FILE https://github.com/riscv-collab/riscv-gnu-toolchain/releases/download/2026.07.15/riscv32-glibc-ubuntu-24.04-gcc.tar.xz + + # Verify sha256 + echo "$EXPECTED_SHA256 $FILE" | sha256sum -c - + + sudo tar Jxf $FILE -C /opt + echo "/opt/riscv/bin" >> "$GITHUB_PATH" + fi + fi - name: Determine static or dynamic linking mode id: determine-mode run: | From c4583c573c585d5a12fb00c0dc6b3508e6fff31e Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Mon, 18 May 2026 22:50:27 +0800 Subject: [PATCH 05/11] Refactor the search process for the cross-compilation toolchain Because two architecture-specific makefile fragments contain similar snippets for locating the cross-compilation toolchain path, this commit consolidates them into a shared build logic, thereby reducing code duplication. --- mk/arm.mk | 43 +------------------------------------------ mk/common.mk | 41 +++++++++++++++++++++++++++++++++++++++++ mk/riscv.mk | 29 +---------------------------- 3 files changed, 43 insertions(+), 70 deletions(-) diff --git a/mk/arm.mk b/mk/arm.mk index 1210ce18..7ef7d7e4 100644 --- a/mk/arm.mk +++ b/mk/arm.mk @@ -54,45 +54,4 @@ ifneq ($(shell which fastfetch),) endif endif -# Find the sysroot of the ARM GNU toolchain if using dynamic linking. -# -# Since developers may install the toolchain manually instead of -# using a package manager such as apt, we cannot assume that the -# path of ld-linux is always "/usr/arm-linux-gnueabihf". -# -# Therefore, the following process first locates find the correct -# sysroot of the toolchain, and then generate the ELF interpreter -# prefix for later use. -ifeq ($(USE_QEMU),1) - ifeq ($(DYNLINK),1) - CROSS_COMPILE = arm-none-linux-gnueabihf- - ARM_CC = $(CROSS_COMPILE)gcc - ARM_CC := $(shell which $(ARM_CC)) - ifndef ARM_CC - CROSS_COMPILE = arm-linux-gnueabihf- - ARM_CC = $(CROSS_COMPILE)gcc - ARM_CC := $(shell which $(ARM_CC)) - ifndef ARM_CC - $(error "Unable to find ARM GNU toolchain.") - endif - endif - - LD_LINUX_PATH := $(shell cd $(shell $(ARM_CC) --print-sysroot) 2>/dev/null && pwd) - ifeq ("$(LD_LINUX_PATH)","/") - LD_LINUX_PATH := $(shell dirname "$(shell which $(ARM_CC))")/.. - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - LD_LINUX_PATH := $(LD_LINUX_PATH)/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//')/libc - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - ifndef LD_LINUX_PATH - LD_LINUX_PATH = /usr/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//') - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - endif - endif - - ifndef LD_LINUX_PATH - $(error "Dynamic linking mode requires ld-linux.so") - endif - - RUNNER_LD_PREFIX = -L $(LD_LINUX_PATH) - endif -endif +TOOLCHAIN_CANDIDATES := arm-none-linux-gnueabihf- arm-linux-gnueabihf- diff --git a/mk/common.mk b/mk/common.mk index ef47ee0f..40b8f2f9 100644 --- a/mk/common.mk +++ b/mk/common.mk @@ -22,6 +22,47 @@ NO_COLOR = \e[0m pass = $(PRINTF) "$(PASS_COLOR)$1 Passed$(NO_COLOR)\n" +# Find the sysroot of the ARM/RISC-V GNU toolchain if using dynamic linking. +# +# Since developers may install the toolchain manually instead of +# using a package manager such as apt, we cannot assume that the +# path of ld-linux is always "/usr/arm-linux-gnueabihf" or other +# similar paths. +# +# Therefore, the following process first locates find the correct +# sysroot of the toolchain, and then generate the ELF interpreter +# prefix for later use. +ifeq ($(USE_QEMU),1) + ifeq ($(DYNLINK),1) + AVAILABLE_TOOLCHAINS := $(foreach tc, $(TOOLCHAIN_CANDIDATES), $(if $(shell which $(tc)gcc), $(tc))) + CROSS_COMPILE := $(firstword $(AVAILABLE_TOOLCHAINS)) + + ifndef CROSS_COMPILE + $(error "Unable to find a proper GNU toolchain.") + endif + + ARCH_CC = $(CROSS_COMPILE)gcc + + LD_LINUX_PATH := $(shell cd $(shell $(ARCH_CC) --print-sysroot) 2>/dev/null && pwd) + ifeq ("$(LD_LINUX_PATH)","/") + LD_LINUX_PATH := $(shell dirname "$(shell which $(ARCH_CC))")/.. + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + LD_LINUX_PATH := $(LD_LINUX_PATH)/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//')/libc + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + ifndef LD_LINUX_PATH + LD_LINUX_PATH = /usr/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//') + LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) + endif + endif + + ifndef LD_LINUX_PATH + $(error "Dynamic linking mode requires ld-linux.so") + endif + + RUNNER_LD_PREFIX = -L $(LD_LINUX_PATH) + endif +endif + # Check the prerequisites PREREQ_LIST := dot jq TARGET_EXEC ?= diff --git a/mk/riscv.mk b/mk/riscv.mk index fde18180..ee87ce1e 100644 --- a/mk/riscv.mk +++ b/mk/riscv.mk @@ -19,31 +19,4 @@ ARCH_DEFS = \ \#define MAX_ARGS_IN_REG 8\n$\ " -ifeq ($(USE_QEMU),1) - ifeq ($(DYNLINK),1) - CROSS_COMPILE = riscv32-unknown-linux-gnu- - RISCV_CC = $(CROSS_COMPILE)gcc - RISCV_CC := $(shell which $(RISCV_CC)) - ifndef RISCV_CC - $(error "Unable to find RISC-V GNU toolchain.") - endif - - LD_LINUX_PATH := $(shell cd $(shell $(RISCV_CC) --print-sysroot) 2>/dev/null && pwd) - ifeq ("$(LD_LINUX_PATH)","/") - LD_LINUX_PATH := $(shell dirname "$(shell which $(RISCV_CC))")/.. - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - LD_LINUX_PATH := $(LD_LINUX_PATH)/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//')/libc - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - ifndef LD_LINUX_PATH - LD_LINUX_PATH = /usr/$(shell echo $(CROSS_COMPILE) | sed s'/.$$//') - LD_LINUX_PATH := $(shell cd $(LD_LINUX_PATH) 2>/dev/null && pwd) - endif - endif - - ifndef LD_LINUX_PATH - $(error "Dynamic linking mode requires ld-linux.so") - endif - - RUNNER_LD_PREFIX = -L $(LD_LINUX_PATH) - endif -endif +TOOLCHAIN_CANDIDATES = riscv32-unknown-linux-gnu- From 98f25ccaa73e4cb060273cf9d74a150f546f5e12 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Thu, 28 May 2026 22:13:29 +0800 Subject: [PATCH 06/11] Introduce ABI conformance test suite for the RISC-V architecture A new shell script is introduced to validate whether generated executables targeting RISC-V correct comply with the RISC-V ABI. The tests include: - Parameter Passing: tests function calls with different numbers of arguments. - Stack Alignment: validates whether the stack is always 16-byte aligned when calling a function. - Return Values: confirms if the return value is correct after a function returns. - External Calls: verifies whether dynamically linked programs can call external functions. - Register Preservation: verify whether the contents of function argument registers are properly preserved when calling a function. - Structure Passing: validates if a small structure object can be passed correctly. --- Makefile | 13 +- tests/riscv-abi.sh | 608 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 610 insertions(+), 11 deletions(-) create mode 100755 tests/riscv-abi.sh diff --git a/Makefile b/Makefile index 8e2e8ebf..8859d632 100644 --- a/Makefile +++ b/Makefile @@ -116,20 +116,11 @@ check-snapshot: $(OUT)/$(STAGE0) tests/check-snapshots.sh tests/check-snapshots.sh $(ARCH) $(DYNLINK) $(VECHO) " OK\n" -# TODO: Add an ABI conformance test suite for the RISC-V architecture check-abi-stage0: $(OUT)/$(STAGE0) - $(Q)if [ "$(ARCH)" = "arm" ]; then \ - tests/$(ARCH)-abi.sh 0 $(DYNLINK); \ - else \ - echo "Skip ABI compliance validation"; \ - fi + tests/$(ARCH)-abi.sh 0 $(DYNLINK); check-abi-stage2: $(OUT)/$(STAGE2) - $(Q)if [ "$(ARCH)" = "arm" ]; then \ - tests/$(ARCH)-abi.sh 2 $(DYNLINK); \ - else \ - echo "Skip ABI compliance validation"; \ - fi + tests/$(ARCH)-abi.sh 2 $(DYNLINK); update-snapshots: tests/update-snapshots.sh # static linking diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh new file mode 100755 index 00000000..b00b9f5a --- /dev/null +++ b/tests/riscv-abi.sh @@ -0,0 +1,608 @@ +#!/usr/bin/env bash + +# RISC-V Calling Convention Compliance Test Suite + +set -u + +# Test Configuration +readonly VERBOSE_MODE="${VERBOSE:-1}" +readonly SHOW_SUMMARY="${SHOW_SUMMARY:-1}" +readonly SHOW_PROGRESS="${SHOW_PROGRESS:-1}" +readonly COLOR_OUTPUT="${COLOR_OUTPUT:-1}" + +# Test Counters +TOTAL_TESTS=0 +PASSED_TESTS=0 +FAILED_TESTS=0 +SKIPPED_TESTS=0 + +# Category Tracking +declare -A CATEGORY_TESTS +declare -A CATEGORY_PASSED +declare -A CATEGORY_FAILED +CURRENT_CATEGORY="Parameter Passing" + +# Performance Metrics +TEST_START_TIME=$(date +%s) +PROGRESS_COUNT=0 + +# Colors +if [[ "$COLOR_OUTPUT" == "1" && -t 1 ]]; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + BLUE='\033[0;34m' + CYAN='\033[0;36m' + BOLD='\033[1m' + NC='\033[0m' +else + RED='' GREEN='' YELLOW='' BLUE='' CYAN='' BOLD='' NC='' +fi + +# Command Line Arguments +if [ "$#" -lt 1 ]; then + echo "Usage: $0 []" + echo " stage: 0 (host compiler), 1 (stage1), or 2 (stage2)" + echo " dynlink: 0 (static linking), 1 (dynamic linking)" + echo "" + echo "Environment Variables:" + echo " VERBOSE=1 Enable verbose output" + echo " SHOW_SUMMARY=1 Show category summaries (default)" + echo " SHOW_PROGRESS=1 Show progress dots (default)" + echo " COLOR_OUTPUT=1 Enable colored output (default)" + exit 1 +fi + +case "$1" in + "0") + readonly SHECC="$PWD/out/shecc" + readonly STAGE="Stage 0 (Host Compiler)" ;; + "1") + readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage1.elf" + readonly STAGE="Stage 1 (Cross-compiled)" ;; + "2") + readonly SHECC="${TARGET_EXEC:-} $PWD/out/shecc-stage2.elf" + readonly STAGE="Stage 2 (Self-hosted)" ;; + *) + echo "Error: Invalid stage '$1'. Use 0, 1, or 2." + exit 1 ;; +esac + +DYNLINK="${2:-0}" + +# Banner +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "${BLUE}${BOLD}RISC-V Calling Convention Compliance Test Suite${NC}" +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "Stage: $STAGE" +echo -e "Link Mode: $([ "$DYNLINK" == "1" ] && echo "Dynamic" || echo "Static")" +echo -e "Compiler: $SHECC" +echo "" + +# Helper Functions +update_category_stats() { + local category="$1" + local result="$2" # "pass" or "fail" + + if [[ -z "${CATEGORY_TESTS[$category]:-}" ]]; then + CATEGORY_TESTS[$category]=0 + CATEGORY_PASSED[$category]=0 + CATEGORY_FAILED[$category]=0 + fi + + CATEGORY_TESTS[$category]=$((${CATEGORY_TESTS[$category]} + 1)) + + if [[ "$result" == "pass" ]]; then + CATEGORY_PASSED[$category]=$((${CATEGORY_PASSED[$category]} + 1)) + else + CATEGORY_FAILED[$category]=$((${CATEGORY_FAILED[$category]} + 1)) + fi +} + +show_progress() { + if [[ "$SHOW_PROGRESS" == "1" ]]; then + echo -n "." + PROGRESS_COUNT=$((PROGRESS_COUNT + 1)) + if [[ $((PROGRESS_COUNT % 50)) -eq 0 ]]; then + echo "" + fi + fi +} + +# Test execution function +run_abi_test() { + local test_name="$1" + local category="$2" + local source_code="$3" + local expected_output="$4" + local skip_static="${5:-0}" + + CURRENT_CATEGORY="$category" + TOTAL_TESTS=$((TOTAL_TESTS + 1)) + + # Skip if dynamic linking required but we're in static mode + if [[ "$skip_static" == "1" && "$DYNLINK" == "0" ]]; then + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${YELLOW}SKIP${NC}: $test_name (requires dynamic linking)" + fi + SKIPPED_TESTS=$((SKIPPED_TESTS + 1)) + show_progress + return + fi + + # Create temporary test file + local test_file="/tmp/shecc_abi_test_$$.c" + echo "$source_code" > "$test_file" + + # Compile + local compile_cmd="$SHECC" + if [[ "$DYNLINK" == "1" ]]; then + compile_cmd="$compile_cmd --dynlink" + fi + compile_cmd="$compile_cmd -o /tmp/shecc_abi_test_$$.elf $test_file" + + local compile_output + if ! compile_output=$(eval "$compile_cmd" 2>&1); then + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${RED}FAIL${NC}: $test_name (compilation failed)" + echo "$compile_output" | sed 's/^/ /' + fi + FAILED_TESTS=$((FAILED_TESTS + 1)) + update_category_stats "$category" "fail" + rm -f "$test_file" + show_progress + return + fi + + # Run + chmod +x "/tmp/shecc_abi_test_$$.elf" + local run_cmd="${TARGET_EXEC:-}" + run_cmd="$run_cmd /tmp/shecc_abi_test_$$.elf" + + local run_output + local exit_code + run_output=$(eval "$run_cmd" 2>&1) + exit_code=$? + + # Check result + if [[ $exit_code -eq 0 ]]; then + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${GREEN}PASS${NC}: $test_name" + if [[ -n "$expected_output" && "$run_output" != *"$expected_output"* ]]; then + echo -e "${YELLOW}Warning: Output mismatch${NC}" + echo "Expected: $expected_output" + echo "Got: $run_output" + fi + fi + PASSED_TESTS=$((PASSED_TESTS + 1)) + update_category_stats "$category" "pass" + else + if [[ "$VERBOSE_MODE" == "1" ]]; then + echo -e "${RED}FAIL${NC}: $test_name (exit code $exit_code)" + echo "$run_output" | sed 's/^/ /' + fi + FAILED_TESTS=$((FAILED_TESTS + 1)) + update_category_stats "$category" "fail" + fi + + # Cleanup + rm -f "$test_file" "/tmp/shecc_abi_test_$$.elf" + show_progress +} + +# Parameter Passing Tests + +test_one_arg() { + run_abi_test "One argument (a0)" "Parameter Passing" ' +#include +int add_42(int x) { return x + 42; } +int main() { + int result = add_42(8); + if (result == 50) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 50, got %d\n", result); + return 1; +} +' "PASS" +} + +test_two_args() { + run_abi_test "Two arguments (a0, a1)" "Parameter Passing" ' +#include +int add(int a, int b) { return a + b; } +int main() { + int result = add(10, 20); + if (result == 30) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 30, got %d\n", result); + return 1; +} +' "PASS" +} + +test_four_args() { + run_abi_test "Four arguments (a0-a3)" "Parameter Passing" ' +#include +int sum4(int a, int b, int c, int d) { return a + b + c + d; } +int main() { + int result = sum4(10, 20, 30, 40); + if (result == 100) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 100, got %d\n", result); + return 1; +} +' "PASS" +} + +test_five_args() { + run_abi_test "Five arguments (a0-a4)" "Parameter Passing" ' +#include +int sum5(int a, int b, int c, int d, int e) { return a + b + c + d + e; } +int main() { + int result = sum5(1, 2, 3, 4, 5); + if (result == 15) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 15, got %d\n", result); + return 1; +} +' "PASS" +} + +test_eight_args() { + run_abi_test "Eight arguments" "Parameter Passing" ' +#include +int sum8(int a, int b, int c, int d, int e, int f, int g, int h) { + return a + b + c + d + e + f + g + h; +} +int main() { + int result = sum8(1, 2, 3, 4, 5, 6, 7, 8); + if (result == 36) { + printf("PASS\n"); + return 0; + } + printf("FAIL: expected 36, got %d\n", result); + return 1; +} +' "PASS" +} + +# Stack Alignment Tests + +test_stack_alignment_basic() { + run_abi_test "Basic stack alignment" "Stack Alignment" ' +#include +int is_aligned(void *ptr) { + int addr = (int)ptr; + return (addr & 0xf) == 0; +} +int check_alignment(int a, int b) { + int local; + return !is_aligned(&local); +} +int main() { + if (check_alignment(1, 2) == 0) { + printf("PASS\n"); + return 0; + } + printf("FAIL: stack not aligned\n"); + return 1; +} +' "PASS" +} + +test_stack_alignment_extended() { + run_abi_test "Stack alignment with extended args" "Stack Alignment" ' +#include +int is_aligned(void *ptr) { + int addr = (int)ptr; + return (addr & 0xf) == 0; +} +int check_extended(int a, int b, int c, int d, int e, int f) { + int local; + return is_aligned(&local) ? (a+b+c+d+e+f) : -1; +} +int main() { + int result = check_extended(1, 2, 3, 4, 5, 6); + if (result == 21) { + printf("PASS\n"); + return 0; + } + printf("FAIL: result=%d\n", result); + return 1; +} +' "PASS" +} + +# Return Value Tests + +test_return_char() { + run_abi_test "Return char value" "Return Values" ' +#include +char get_char(void) { return '\''A'\''; } +int main() { + if (get_char() == '\''A'\'') { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_return_int() { + run_abi_test "Return int value" "Return Values" ' +#include +int get_value(void) { return 12345; } +int main() { + if (get_value() == 12345) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +test_return_pointer() { + run_abi_test "Return pointer value" "Return Values" ' +#include +int *return_ptr(int *p) { return p; } +int main() { + int x = 42; + int *ptr = return_ptr(&x); + if (ptr == &x && *ptr == 42) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# External Function Call Tests (Dynamic Linking Only) + +test_printf_one_arg() { + run_abi_test "printf with 1 argument" "External Calls" ' +#include +int main() { + printf("PASS\n"); + return 0; +} +' "PASS" 1 +} + +test_printf_multi_args() { + run_abi_test "printf with 5 arguments" "External Calls" ' +#include +int main() { + printf("Values: %d %d %d %d\n", 1, 2, 3, 4); + printf("PASS\n"); + return 0; +} +' "PASS" 1 +} + +test_strlen() { + run_abi_test "strlen external call" "External Calls" ' +#include +#include +int main() { + char str[] = "Hello"; + if (strlen(str) == 5) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" 1 +} + +test_strcpy() { + run_abi_test "strcpy external call" "External Calls" ' +#include +#include +int main() { + char dest[20]; + char src[] = "Test"; + strcpy(dest, src); + if (strcmp(dest, "Test") == 0) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" 1 +} + +test_memcpy() { + run_abi_test "memcpy external call" "External Calls" ' +#include +#include +int main() { + int src[3] = {1, 2, 3}; + int dst[3]; + memcpy(dst, src, 3 * sizeof(int)); + if (dst[0] == 1 && dst[1] == 2 && dst[2] == 3) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" 1 +} + +# Register Preservation Tests + +test_local_vars_preserved() { + run_abi_test "Local variables preserved across calls" "Register Preservation" ' +#include +int dummy(int a, int b, int c, int d, int e, int f, int g, int h) { + return a + b + c + d + e + f + g + h; +} +int main() { + int v1 = 100, v2 = 200, v3 = 300, v4 = 400, + v5 = 500, v6 = 600, v7 = 700, v8 = 800; + dummy(1, 2, 3, 4, 5, 6, 7, 8); + if (v1 == 100 && v2 == 200 && v3 == 300 && v4 == 400 && + v5 == 500 && v6 == 600 && v7 == 700 && v8 == 800) { + printf("PASS\n"); + return 0; + } + printf("FAIL: locals corrupted\n"); + return 1; +} +' "PASS" +} + +test_recursive_preservation() { + run_abi_test "Register preservation in recursion" "Register Preservation" ' +#include +int factorial(int n) { + if (n <= 1) return 1; + int local = n; + int result = factorial(n - 1); + return (local == n) ? n * result : -1; +} +int main() { + if (factorial(5) == 120) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# Structure Passing Tests + +test_small_struct() { + run_abi_test "Small struct passing (≤4 bytes)" "Structure Passing" ' +#include +typedef struct { char a; char b; short c; } SmallStruct; +int sum_struct(SmallStruct s) { return s.a + s.b + s.c; } +int main() { + SmallStruct s = {10, 20, 30}; + if (sum_struct(s) == 60) { + printf("PASS\n"); + return 0; + } + printf("FAIL\n"); + return 1; +} +' "PASS" +} + +# Run all tests + +echo -e "${CYAN}Running Parameter Passing Tests...${NC}" +test_one_arg +test_two_args +test_four_args +test_five_args +test_eight_args + +echo "" +echo -e "${CYAN}Running Stack Alignment Tests...${NC}" +test_stack_alignment_basic +test_stack_alignment_extended + +echo "" +echo -e "${CYAN}Running Return Value Tests...${NC}" +test_return_char +test_return_int +test_return_pointer + +echo "" +if [[ "$DYNLINK" == "1" ]]; then + echo -e "${CYAN}Running External Function Call Tests...${NC}" + test_printf_one_arg + test_printf_multi_args + test_strlen + test_strcpy + test_memcpy +else + echo -e "${YELLOW}Skipping External Function Call Tests (requires dynamic linking)${NC}" + SKIPPED_TESTS=$((SKIPPED_TESTS + 5)) +fi + +echo "" +echo -e "${CYAN}Running Register Preservation Tests...${NC}" +test_local_vars_preserved +test_recursive_preservation + +echo "" +echo -e "${CYAN}Running Structure Passing Tests...${NC}" +test_small_struct + +# SUMMARY + +echo "" +echo "" + +if [[ "$SHOW_SUMMARY" == "1" ]]; then + echo -e "${BLUE}${BOLD}========================================${NC}" + echo -e "${BLUE}${BOLD}Category Summary${NC}" + echo -e "${BLUE}${BOLD}========================================${NC}" + + for category in "${!CATEGORY_TESTS[@]}"; do + total="${CATEGORY_TESTS[$category]}" + passed="${CATEGORY_PASSED[$category]}" + failed="${CATEGORY_FAILED[$category]}" + pct=0 + if [[ $total -gt 0 ]]; then + pct=$((passed * 100 / total)) + fi + + printf "%-25s: " "$category" + if [[ $failed -eq 0 ]]; then + echo -e "${GREEN}$passed/$total PASSED${NC} (${pct}%%)" + else + echo -e "${RED}$passed/$total PASSED${NC}, ${RED}$failed FAILED${NC} (${pct}%%)" + fi + done + echo "" +fi + +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "${BLUE}${BOLD}Overall Test Results${NC}" +echo -e "${BLUE}${BOLD}========================================${NC}" +echo -e "Total Tests: $TOTAL_TESTS" +echo -e "${GREEN}Passed: $PASSED_TESTS${NC}" + +if [[ $FAILED_TESTS -gt 0 ]]; then + echo -e "${RED}Failed: $FAILED_TESTS${NC}" +else + echo -e "Failed: $FAILED_TESTS" +fi + +if [[ $SKIPPED_TESTS -gt 0 ]]; then + echo -e "${YELLOW}Skipped: $SKIPPED_TESTS${NC}" +fi + +TEST_END_TIME=$(date +%s) +TEST_DURATION=$((TEST_END_TIME - TEST_START_TIME)) +echo -e "Duration: ${TEST_DURATION}s" +echo "" + +if [[ $FAILED_TESTS -gt 0 ]]; then + echo -e "${RED}${BOLD}Some ABI tests FAILED!${NC}" + exit 1 +else + echo -e "${GREEN}${BOLD}All ABI tests PASSED!${NC}" + exit 0 +fi From ef6494d95ab8253d0002d207808784b8a690429d Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sat, 13 Jun 2026 21:34:45 +0800 Subject: [PATCH 07/11] Consolidate dynamic linking documentation - Expand instructions on utilizing dynamic linking for both the Arm and RISC-V architectures. - Describe the stack frame layout for the RISC-V implementation. - Explain caller and callee behaviors when targeting the RISC-V architecture. - Illustrate the RISC-V PLT stub implementation, including assembly code snippets and design intentions. - Add reference links about RISC-V. - glibc implementation of '__dl_runtime_resolve' for RISC-V. - RISC-V ABIs specifications. - Improve the explanation of the runtime execution flow of a dynamically linked program. - Correct the description of callee behavior for the Arm architecture. - Clarify that registers r4-r11 are callee-saved, not caller-saved. - Explain that the saved lr is loaded into pc to return to the caller. --- docs/dynamic-linking.md | 143 ++++++++++++++++++++++++++++++++++------ 1 file changed, 123 insertions(+), 20 deletions(-) diff --git a/docs/dynamic-linking.md b/docs/dynamic-linking.md index e1ebe67d..a9f1c283 100644 --- a/docs/dynamic-linking.md +++ b/docs/dynamic-linking.md @@ -2,10 +2,11 @@ ## Build dynamically linked shecc and programs -Build the dynamically linked version of shecc, but notice that shecc currently doesn't support dynamic linking for the RISC-V architecture: +Build the dynamically linked version of shecc: ```shell $ make ARCH=arm DYNLINK=1 +$ make ARCH=riscv DYNLINK=1 ``` Next, you can use shecc to build dynamically linked programs by adding the `--dynlink` flag: @@ -15,12 +16,16 @@ Next, you can use shecc to build dynamically linked programs by adding the `--dy $ out/shecc --dynlink -o # Use the stage 1 or stage 2 compiler $ qemu-arm -L out/shecc-stage2.elf --dynlink -o +$ qemu-riscv32 -L out/shecc-stage2.elf --dynlink -o # Execute the compiled program $ qemu-arm -L +$ qemu-riscv32 -L ``` -When executing a dynamically linked program, you should set the ELF interpreter prefix so that `ld.so` can be invoked. Generally, it should be `/usr/arm-linux-gnueabihf` if you have installed the ARM GNU toolchain by `apt`. Otherwise, you should find and specify the correct path if you manually installed the toolchain. +When executing a dynamically linked program, you should set the ELF interpreter prefix so that `ld.so` can be invoked. + +Generally, the prefix should be `/usr/arm-linux-gnueabihf` for the Arm architecture if you have installed the ARM GNU toolchain by `apt`. Otherwise, you should find and specify the correct path if you manually installed the toolchain. For RISC-V, you must manually download a 32-bit RISC-V GNU toolchain since `apt` may not provide any package to install the necessary toolchain. ## Stack frame layout @@ -69,30 +74,69 @@ Low Address ### RISC-V -(Currently not supported) +``` +High Address ++------------------+ +| ... | ++------------------+ <- sp + total_size +| preserved return | +| address | ++------------------+ +| (padding) | ++------------------+ +| local variables | ++------------------+ <- sp + (MAX_PARAMS - MAX_ARGS_IN_REG) * 4 +| (unused space) | ++------------------+ <- sp (MUST be aligned to 16 bytes) +Low Address +``` + +`total_size`: includes the size of the following elements: + +* `unused space`: a fixed size - `(MAX_PARAMS - MAX_ARGS_IN_REG) * 4` bytes +* `local variables` +* `preserved return address`: a fixed size - 4 bytes ## Calling Convention +Regardless of which mode is used, callers are ensured to perform a collection of required operations for complying with the ABI of the target architecture when calling a function. + ### Arm32 -Regardless of which mode is used, the caller performs the following operations to comply with the Arm Architecture Procedure Call Standard (AAPCS) when calling a function. +Caller's behavior: -* The first four arguments are put into registers `r0` - `r3` +* The first four arguments are put into registers `r0` - `r3`. * Any additional arguments are passed on the stack. Arguments are pushed onto the stack starting from the last argument, so the fifth argument resides at a lower address and the last argument at a higher address. -* Align the stack pointer to 8 bytes, as external functions may access 8-byte objects that require such alignment. -Then, the callee will perform these operations: +Callee's behavior: - Preserve the contents of registers `r4` - `r11` on the stack upon function entry. - The callee also pushes the content of `lr` onto the stack to preserve the return address; however, this operation is not required by the AAPCS. - -- Restore these registers from the stack upon returning. +- Allocate necessary space on the stack and align the stack pointer to 8-byte, as external functions may access 8-byte objects that require such alignment. +- Restore registers `r4` - `r11` from the stack upon returning, and load the saved `lr` to `pc` to return. ### RISC-V -In the RISC-V architecture, registers `a0` - `a7` are used as argument registers; that is, the first eight arguments are passed into these registers. +Caller's behavior: + +- Preserve caller-saved registers: + - `a0` - `a7`. + - `ra` is always saved upon caller's entry. + - Exception: `t0` - `t6` are always used to store temporary values by the code generator, so these temporary registers are not necessary to be saved. + +- The first eight arguments are passed into registers `a0` - `a7`. +- Since the current implementation of shecc supports up to 8 arguments, no argument needs to be passed onto the stack. + +Callee's behavior -Since the current implementation of shecc supports up to 8 arguments, no argument needs to be passed onto the stack. +- Allocate necessary space on the stack and align the stack pointer to 128-bit (16-byte). +- Preserve callee-saved registers: + - Although `sp` is not explicitly saved onto the stack after allocating space for local variables, the code generator guarantees that `sp` is correctly restored for the caller prior to returning. Therefore, `sp` is not necessary to be additionally handled. + - `s0` - `s1`: + - Static linking mode: only `s0` is used at the program entry point; its original value does not need to be saved because no caller exist. + - Dynamic linking mode: both registers are used to hold the `argc` and `argv` values prior to global initialization. The code generator ensures that their original values are saved to the stack and restored before transferring control back to `__libc_start_main`. + - `s2` - `s11` are not used by the code generator, so they are unnecessary to be processed. +- Restore the return address and the stack pointer before returning. ## Runtime execution flow of a dynamically linked program @@ -136,14 +180,20 @@ kernel | | +--->| +--->| | 2. Kernel validates the executable and creates a process image if the validation passes. 3. Dynamic linker (`ld.so`) is invoked by the kernel's program loader. * For the Arm architecture, the dynamic linker is `/lib/ld-linux-armhf.so.3`. + * For the RISC-V architecture, the dynamic linker is `/lib/ld-linux-riscv32-ilp32d.so.1`. 4. Linker loads shared libraries such as `libc.so`. 5. Linker resolves symbols and fills global offset table (GOT). 6. Control transfers to the program, which starts at the entry point. 7. Program executes `__libc_start_main` at the beginning. -8. `__libc_start_main` calls the *main wrapper*, which pushes registers r4-r11 and lr onto the stack, sets up a global stack for all global variables (excluding read-only variables), and initializes them. +8. `__libc_start_main` calls the *main wrapper*, which includes the following operations: + * Architecture-specific behavior: + * Arm: push registers `r4`-`r11` and `lr` onto the stack. + * RISC-V: store register `ra` onto the stack (preserve the address back to `__libc_start_main`). + * Preserve `argc` and `argv` for the main function. + * Set up a global stack for all global variables (excluding read-only variables) and initialize them. 9. Execute the *main wrapper*, and then invoke the main function. 10. After the `main` function returns, the *main wrapper* restores the necessary registers and passes control back to `__libc_start_main`, which implicitly calls `exit(3)` to terminate the program. - * Or, the `main` function can also call `exit(3)` or `_exit(2)` to directly terminate itself. + * Alternatively, the `main` function can also call `exit(3)` or `_exit(2)` to directly terminate itself. ## Dynamic sections @@ -159,10 +209,14 @@ When using dynamic linking, the following sections are generated for compiled pr ### Initialization of all GOT entries -* `GOT[0]` is set to the starting address of the `.dynamic` section. -* `GOT[1]` and `GOT[2]` are initialized to zero and reserved for the `link_map` and the resolver (`__dl_runtimer_resolve`). - * The dynamic linker modifies them to point to the actual addresses at runtime. -* `GOT[3]` - `GOT[N]` are initially set to the address of `PLT[0]` at compile time, causing the first call to an external function to invoke the resolver at runtime. +* Arm: + * `GOT[0]` is set to the starting address of the `.dynamic` section. + * `GOT[1]` and `GOT[2]` are initialized to zero and reserved for `link_map` and resolver (`__dl_runtimer_resolve`), and they are modified to point to the actual addresses by the dynamic linker at runtime. + +* RISC-V: + * `GOT[0]` and `GOT[1]` are initialized to zero and reserved for resolver (`__dl_runtimer_resolve`) and `link_map`, and they are modified to point to the actual addresses by the dynamic linker at runtime. + +* The remaining entries are initially set to the address of `PLT[0]` at compile time, causing the first call to an external function to invoke the resolver at runtime. ### Explanation for PLT stubs (Arm32) @@ -187,6 +241,8 @@ ldr pc, [lr] 3. Move the value of `sl` to `lr`. 4. Load the value located at `[lr]` into the program counter (`pc`) +------ + The remaining PLT entries correspond to all external functions, and each entry includes the following instructions to fulfill the second requirement: ``` @@ -198,6 +254,49 @@ ldr pc, [ip] 1. Set register `ip` to the address of `GOT[x]`. 2. Assign register `pc` to the value of `GOT[x]`. That is, set `pc` to the address of the callee. +### Explanation for PLT stubs (RISC-V) + +In the RISC-V ABI document, the first entry of PLT can be produced as follows: + +``` +1: auipc t2, %pcrel_hi(.got) + sub t1, t1, t3 + lw t3, %pcrel_lo(1b)(t2) + addi t1, t1 -(PLT0_SIZE + 12) # PLT0_SIZE is 32 bytes. + addi t0, t2, %pcrel_lo(1b) + srli t1, t1, log2(16 / PTRSIZE) # PTRSIZE is 4 bytes. + lw t0, PTRSIZE(t0) + jr t3 +``` + +- `t0` is set to `GOT[1]`, which is the `link_map` pointer. + +- `t1` is a `.got` offset: + + | External Function | Corresponding GOT element | `.got` offset | + | ----------------- | ------------------------- | ------------- | + | 1st function | `GOT[2]` | `0` | + | 2nd function | `GOT[3]` | `4` | + | ... | ... | ... | + | N-th function | `GOT[N + 1]` | `(N - 1) * 4` | + +- `t2` is `%hi(%pcrel(.got))`, but it is not used by `__dl_runtime_resolve()`. + +- `t3` is `GOT[0]` (a pointer to `__dl_runtime_resolve()`), and `PLT[0]` finally uses `t3` to jump to the resolver. + +------ + +Each of the remaining entries can be generated with the following instructions: + +``` +1: auipc t3, %pcrel_hi(function@.got) + lw t3, %pcrel_lo(1b)(t3) + jalr t1, t3 + nop +``` + +This instruction sequence sets `t1` and `t3` to the address of `nop` and `GOT[N]` respectively, and performs a jump via `t3` to call an external function. + ## PLT execution path and performance overhead Since calling an external function needs a PLT stub for indirect invocation, the execution path of the first call is as follows: @@ -243,7 +342,11 @@ This implies that: * man page: `ld(1)` * man page: `ld.so(8)` -* glibc - [`__dl_runtime_resolve`](https://elixir.bootlin.com/glibc/glibc-2.41.9000/source/sysdeps/arm/dl-trampoline.S#L30) implementation (for Arm32) +* glibc implementation + * [`__dl_runtime_resolve`](https://elixir.bootlin.com/glibc/glibc-2.41.9000/source/sysdeps/arm/dl-trampoline.S#L30) (Arm32) + * [`__dl_runtime_resolve`](https://elixir.bootlin.com/glibc/glibc-2.41.9000/source/sysdeps/riscv/dl-trampoline.S#L34) (for RISC-V) * Application Binary Interface for the Arm Architecture - [`abi-aa`](https://github.com/ARM-software/abi-aa) - * `aaelf32` - * `aapcs32` + * `aaelf32.pdf` + * `aapcs32.pdf` +* RISC-V ABIs Specification - [`riscv-elf-psabi-doc`](https://github.com/riscv-non-isa/riscv-elf-psabi-doc) + * `riscv-abi.pdf` From 89fd50d364ad5b0389627e939db4343b4573f603 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sat, 13 Jun 2026 23:10:32 +0800 Subject: [PATCH 08/11] Update README about dynamic linking Since the dynamic linking is now supported for the RISC-V architecture, this updates the relevant introductions and usage guides. --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b9699b1f..577068a8 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Despite its simplistic nature, it is capable of performing basic optimization st while the second pass translates these operations into Arm/RISC-V machine code. * Develop a register allocation system that is compatible with RISC-style architectures. * Implement an architecture-independent, [static single assignment](https://en.wikipedia.org/wiki/Static_single-assignment_form) (SSA)-based middle-end for enhanced optimizations. +* Support dynamic linking to allow generated executables to run with glibc. ## Compatibility @@ -62,6 +63,7 @@ Code generator in `shecc` does not rely on external utilities. You only need ordinary C compilers such as `gcc` and `clang`. However, `shecc` would bootstrap itself, and Arm/RISC-V ISA emulation is required. Install QEMU for Arm/RISC-V user emulation on GNU/Linux: + ```shell $ sudo apt-get install qemu-user ``` @@ -79,14 +81,23 @@ To execute the snapshot test, install the packages below: $ sudo apt-get install graphviz jq ``` -Additionally, because `shecc` supports the dynamic linking mode for the Arm architecture, -it needs to install the ARM GNU toolchain to obtain the ELF interpreter and other dependencies: +### Additional packages + +Because `shecc` supports the dynamic linking mode for both the Arm and RISC-V architectures, +it needs to install cross-compile GNU toolchains to obtain the ELF interpreter and other dependencies. + +For the Arm architecture, you can install the ARM GNU toolchain using `apt-get`: + ```shell $ sudo apt-get install gcc-arm-linux-gnueabihf ``` Another approach is to manually download and install the toolchain from [ARM Developer website](https://developer.arm.com/downloads/-/arm-gnu-toolchain-downloads). +Select "x86_64 Linux hosted cross toolchains" - "AArch32 GNU/Linux target with hard float (arm-none-linux-gnueabihf)" +to download the toolchain. -Select "x86_64 Linux hosted cross toolchains" - "AArch32 GNU/Linux target with hard float (arm-none-linux-gnueabihf)" to download the toolchain. +Since `apt-get` does not provide the necessary RISC-V GNU toolchain, it must be downloaded manually if you want to +run a dynamically linked `shecc` targeting the RISC-V architecture. For instance, you can download and extract the +`riscv32-glibc-ubuntu-22.04-gcc.tar.xz` package from the [riscv-gnu-gcc](https://github.com/riscv-collab/riscv-gnu-toolchain) repository. ## Build and Verify @@ -113,6 +124,7 @@ $ make Run `make DYNLINK=1` to use the dynamic linking mode and generate the dynamically linked compiler: ```shell # If using the dynamic linking mode, you should add 'DYNLINK=1' for each 'make' command. +# Append 'ARCH=arm' or 'ARCH=riscv' to specify the target architecture (default: arm). $ make DYNLINK=1 CC+LD out/inliner GEN out/libc.inc @@ -152,7 +164,7 @@ $ qemu-arm fib Example 2: dynamic linking mode -Notice that `/usr/arm-linux-gnueabihf` is the ELF interpreter prefix. Since the path may be different if you manually install the ARM GNU toolchain instead of using `apt-get`, you should set the prefix to the actual path. +Notice that `/usr/arm-linux-gnueabihf` is the ELF interpreter prefix. Since the path may be different if you manually install the ARM/RISC-V GNU toolchain instead of using `apt-get`, you should set the prefix to the actual path. ```shell $ out/shecc --dynlink -o fib tests/fib.c $ chmod +x fib From dc51ff2143cd3dffa1048c44ab0ce736d0c96ff3 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Thu, 30 Jul 2026 21:29:58 +0800 Subject: [PATCH 09/11] Add range check for the RISC-V jump instructions encoding Since the jump offset of a 'jal' instruction is restricted to between -1MB and +1MB, this commit adds a validation check to ensure the compiler reports an error if the jump offset is out of range. --- src/riscv.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/riscv.c b/src/riscv.c index ae4f3e60..e35ddd09 100644 --- a/src/riscv.c +++ b/src/riscv.c @@ -172,6 +172,10 @@ int rv_encode_J(rv_op op, rv_reg rd, int imm) { bool sign = false; + /* valid jump range: -1MB to 1MB */ + if (imm > 1048575 || imm < -1048576) + fatal("Offset too large"); + if (imm < 0) { sign = true; imm = -imm; From 14daac8f94697a44c7ad93bfbddf546fadf495ed Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Fri, 31 Jul 2026 23:41:57 +0800 Subject: [PATCH 10/11] Fix error handling for the ABI validation scripts Previously, the scripts only validated the standard output of each case when verbose mode was enabled. This may lead to false positives if the exit code is zero but the output is incorrect. This commit improves all ABI validation scripts to resolve this issue. The updated validation flow now sets a status flag based on both the exit code and the output, using it to accurately determine whether the test case passes or fails. --- tests/arm-abi.sh | 22 +++++++++++++++------- tests/riscv-abi.sh | 22 +++++++++++++++------- 2 files changed, 30 insertions(+), 14 deletions(-) diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index a45b9df9..d34c0405 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -165,21 +165,29 @@ run_abi_test() { exit_code=$? # Check result - if [[ $exit_code -eq 0 ]]; then + # If the exit code is not zero or the output is not expected, + # set 'run_status' to "FAILED". + run_status="SUCCESS" + if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + run_status="FAILED" + fi + + if [[ $run_status == "SUCCESS" ]]; then if [[ "$VERBOSE_MODE" == "1" ]]; then echo -e "${GREEN}PASS${NC}: $test_name" - if [[ -n "$expected_output" && "$run_output" != *"$expected_output"* ]]; then - echo -e "${YELLOW}Warning: Output mismatch${NC}" - echo "Expected: $expected_output" - echo "Got: $run_output" - fi fi PASSED_TESTS=$((PASSED_TESTS + 1)) update_category_stats "$category" "pass" else if [[ "$VERBOSE_MODE" == "1" ]]; then echo -e "${RED}FAIL${NC}: $test_name (exit code $exit_code)" - echo "$run_output" | sed 's/^/ /' + if [[ -n "$expected_output" && "$run_output" != *"$expected_output"* ]]; then + echo -e "${YELLOW}Warning: Output mismatch${NC}" + echo "Expected: $expected_output" + echo "Got: $run_output" + else + echo "$run_output" | sed 's/^/ /' + fi fi FAILED_TESTS=$((FAILED_TESTS + 1)) update_category_stats "$category" "fail" diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index b00b9f5a..b2ce82ac 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -165,21 +165,29 @@ run_abi_test() { exit_code=$? # Check result - if [[ $exit_code -eq 0 ]]; then + # If the exit code is not zero or the output is not expected, + # set 'run_status' to "FAILED". + run_status="SUCCESS" + if [[ $exit_code -ne 0 || ( -n "$expected_output" && "$run_output" != *"$expected_output"* ) ]]; then + run_status="FAILED" + fi + + if [[ $run_status == "SUCCESS" ]]; then if [[ "$VERBOSE_MODE" == "1" ]]; then echo -e "${GREEN}PASS${NC}: $test_name" - if [[ -n "$expected_output" && "$run_output" != *"$expected_output"* ]]; then - echo -e "${YELLOW}Warning: Output mismatch${NC}" - echo "Expected: $expected_output" - echo "Got: $run_output" - fi fi PASSED_TESTS=$((PASSED_TESTS + 1)) update_category_stats "$category" "pass" else if [[ "$VERBOSE_MODE" == "1" ]]; then echo -e "${RED}FAIL${NC}: $test_name (exit code $exit_code)" - echo "$run_output" | sed 's/^/ /' + if [[ -n "$expected_output" && "$run_output" != *"$expected_output"* ]]; then + echo -e "${YELLOW}Warning: Output mismatch${NC}" + echo "Expected: $expected_output" + echo "Got: $run_output" + else + echo "$run_output" | sed 's/^/ /' + fi fi FAILED_TESTS=$((FAILED_TESTS + 1)) update_category_stats "$category" "fail" From 6f43590f20230e994dfadb9965958dbd7dfd8140 Mon Sep 17 00:00:00 2001 From: Yu-En Hsiao Date: Sat, 1 Aug 2026 00:08:21 +0800 Subject: [PATCH 11/11] Fix expected output and test programs for the ABI test suites Previously, the test_printf_multi_args() test case called printf() twice to output "Values: 1 2 3 4" and "PASS", respectively. However, the test suites only checked whether the entire standard output contained the "PASS" string. This approach failed to verify if the first call printed the correct string. Thus, this commit updates the test case across all ABI validation scripts. It preserves only the first printf() call and sets the expected output to "Values: 1 2 3 4", ensuring the scripts can correctly validate function calls with multiple arguments. --- tests/arm-abi.sh | 3 +-- tests/riscv-abi.sh | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/arm-abi.sh b/tests/arm-abi.sh index d34c0405..b4aff266 100755 --- a/tests/arm-abi.sh +++ b/tests/arm-abi.sh @@ -395,10 +395,9 @@ test_printf_multi_args() { #include int main() { printf("Values: %d %d %d %d\n", 1, 2, 3, 4); - printf("PASS\n"); return 0; } -' "PASS" 1 +' "Values: 1 2 3 4" 1 } test_strlen() { diff --git a/tests/riscv-abi.sh b/tests/riscv-abi.sh index b2ce82ac..c71b5a6a 100755 --- a/tests/riscv-abi.sh +++ b/tests/riscv-abi.sh @@ -395,10 +395,9 @@ test_printf_multi_args() { #include int main() { printf("Values: %d %d %d %d\n", 1, 2, 3, 4); - printf("PASS\n"); return 0; } -' "PASS" 1 +' "Values: 1 2 3 4" 1 } test_strlen() {