diff --git a/Makefile b/Makefile index 56e043a..397a6fc 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ EMULATOR = ./amach ASFLAGS = -march=rv32imac -mabi=ilp32 -nostdlib -s -static -fno-pic -fno-pie -mno-relax LDFLAGS = -Wl,-Ttext=0x100b0 -Wl,--build-id=none -all: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_tests.elf rv32imac_syscall_tests.elf +all: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_args_tests.bin rv32imac_tests.elf rv32imac_syscall_tests.elf rv32imac_args_tests.elf rv32imac_tests.elf: rv32imac_tests.s $(CC) $(ASFLAGS) $(LDFLAGS) rv32imac_tests.s -o rv32imac_tests.elf @@ -19,7 +19,13 @@ rv32imac_syscall_tests.elf: rv32imac_syscall_tests.s rv32imac_syscall_tests.bin: rv32imac_syscall_tests.elf $(OBJCOPY) -O binary rv32imac_syscall_tests.elf rv32imac_syscall_tests.bin -run: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_tests.elf rv32imac_syscall_tests.elf +rv32imac_args_tests.elf: rv32imac_args_tests.s + $(CC) $(ASFLAGS) $(LDFLAGS) rv32imac_args_tests.s -o rv32imac_args_tests.elf + +rv32imac_args_tests.bin: rv32imac_args_tests.elf + $(OBJCOPY) -O binary rv32imac_args_tests.elf rv32imac_args_tests.bin + +run: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_args_tests.bin rv32imac_tests.elf rv32imac_syscall_tests.elf rv32imac_args_tests.elf @echo "Running CPU instruction tests (binary)..." $(EMULATOR) rv32imac_tests.bin @echo "Running CPU instruction tests (ELF)..." @@ -32,6 +38,10 @@ run: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_tests.elf rv32imac_s rm -rf test_dir test_link.txt test_output.txt test_output_new.txt ln -sf test_output_new.txt test_link.txt $(EMULATOR) rv32imac_syscall_tests.elf + @echo "Running command line arguments tests (binary)..." + $(EMULATOR) rv32imac_args_tests.bin arg1 arg2 + @echo "Running command line arguments tests (ELF)..." + $(EMULATOR) rv32imac_args_tests.elf arg1 arg2 @echo "Testing standard ebreak..." @echo "115 0 16 0" | awk -f amach.awk 2>&1 | grep -q "Fatal: EBREAK" @echo "Testing compressed c.ebreak..." @@ -39,6 +49,6 @@ run: rv32imac_tests.bin rv32imac_syscall_tests.bin rv32imac_tests.elf rv32imac_s @echo "All tests PASSED!" clean: - rm -rf rv32imac_tests.elf rv32imac_tests.bin rv32imac_syscall_tests.elf rv32imac_syscall_tests.bin test_dir test_link.txt test_output.txt test_output_new.txt + rm -rf rv32imac_tests.elf rv32imac_tests.bin rv32imac_syscall_tests.elf rv32imac_syscall_tests.bin rv32imac_args_tests.elf rv32imac_args_tests.bin test_dir test_link.txt test_output.txt test_output_new.txt .PHONY: all run clean diff --git a/README.md b/README.md index cc9bcb7..8d936fc 100644 --- a/README.md +++ b/README.md @@ -19,17 +19,17 @@ First, decide where your program needs to be loaded in memory. The default LVA ( Then, most probably, you'll want to use the provided wrapper: ```sh -[LVA=...] ./amach path/to/program.bin +[LVA=...] ./amach path/to/program.bin [...arguments] ``` Or you can run the following: ```sh -od -An -v -tu1 path/to/program.bin | POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...] +od -An -v -tu1 path/to/program.bin | POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...] [-v CMD_OPTS=...] ``` But in case you already have a program in the `.dec` format, you can pass it directly to AWK: ```sh -POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...] -- path/to/program.dec +POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...] [-v CMD_OPTS=...] -- path/to/program.dec ``` ### ELF Executables @@ -41,7 +41,7 @@ POSIXLY_CORRECT=1 awk -f amach.awk [-v LVA=...] -- path/to/program.dec You can launch ELF files directly using the wrapper or via the same pipe pipeline: ```sh -./amach path/to/program.elf +./amach path/to/program.elf [...arguments] ``` ## Support diff --git a/amach b/amach index 0a586db..01aa906 100755 --- a/amach +++ b/amach @@ -1,4 +1,16 @@ #!/bin/sh -# A-Machine .bin launcher wrapper +# A-Machine .bin/.elf launcher wrapper [ -z "$LVA" ] && LVA=65712 # 0x100b0 -od -An -v -tu1 "$1" | POSIXLY_CORRECT=1 awk -f amach.awk -v LVA="$LVA" +SEP=$(printf '\001') +CMD_OPTS="" +first=1 +for arg in "$@"; do + if [ "$first" = 1 ]; then + CMD_OPTS="$arg" + first=0 + else + CMD_OPTS="$CMD_OPTS$SEP$arg" + fi +done +[ -z "$AWK" ] && AWK=awk +od -An -v -tu1 "$1" | POSIXLY_CORRECT=1 $AWK -f amach.awk -v LVA="$LVA" -v CMD_OPTS="$CMD_OPTS" diff --git a/amach.awk b/amach.awk index a740317..779f661 100644 --- a/amach.awk +++ b/amach.awk @@ -781,6 +781,35 @@ END { LVA = e_entry } + # Setup command-line arguments on the stack + n = 0 + if(length(CMD_OPTS) > 0) n = split(CMD_OPTS, args, "\001") + else { + n = 1 + args[1] = "amach" + } + + sp = getreg(2) + for(i = n; i >= 1; i--) { + len = length(args[i]) + sp = sp - (len + 1) + for(j = 1; j <= len; j++) + MEM[uint32(sp + j - 1)] = ord(substr(args[i], j, 1)) + MEM[uint32(sp + len)] = 0 + argv_ptr[i] = sp + } + + sp = sp - (sp % 16) + sp = sp - 4 * (n + 3) + sp = sp - (sp % 16) + + write_word(sp, n) + for(i = 1; i <= n; i++) write_word(sp + 4 * i, argv_ptr[i]) + write_word(sp + 4 * (n + 1), 0) + write_word(sp + 4 * (n + 2), 0) + + setreg(2, sp) + memsize = pc - LVA pc = LVA while(pc > -1) { diff --git a/rv32imac_args_tests.s b/rv32imac_args_tests.s new file mode 100644 index 0000000..a2074b7 --- /dev/null +++ b/rv32imac_args_tests.s @@ -0,0 +1,91 @@ +.section .text +.option norvc +.global _start + +# Helper macro to exit with code if condition fails +.macro ASSERT_EQ reg, val, error_code + li t4, \val + beq \reg, t4, .L_ok_\@ + li a0, \error_code + li a7, 93 + ecall +.L_ok_\@: +.endm + +# Compare string at register \str_reg with data label \label, exit with \error_code if not equal +.macro ASSERT_STR_EQ str_reg, label, error_code + la t0, \label + mv t1, \str_reg +.L_loop_\@: + lbu t2, 0(t0) + lbu t3, 0(t1) + bne t2, t3, .L_fail_\@ + beqz t2, .L_ok_\@ + addi t0, t0, 1 + addi t1, t1, 1 + j .L_loop_\@ +.L_fail_\@: + li a0, \error_code + li a7, 93 + ecall +.L_ok_\@: +.endm + +_start: + # sp + 0: argc + # sp + 4: argv[0] + # sp + 8: argv[1] + # sp + 12: argv[2] + + # 1. Print verification start message + li a7, 64 # sys_write + li a0, 1 # stdout + la a1, msg_start + li a2, 32 # message length + ecall + + # 2. Check argc == 3 + lw t0, 0(sp) + ASSERT_EQ t0, 3, 10 + + # 3. Check argv[0] is not null and not empty + lw t0, 4(sp) + beqz t0, .L_argv0_bad + lbu t1, 0(t0) + beqz t1, .L_argv0_bad + j .L_argv0_ok +.L_argv0_bad: + li a0, 11 + li a7, 93 + ecall +.L_argv0_ok: + + # 4. Check argv[1] == "arg1" + lw t0, 8(sp) + ASSERT_STR_EQ t0, str_arg1, 12 + + # 5. Check argv[2] == "arg2" + lw t0, 12(sp) + ASSERT_STR_EQ t0, str_arg2, 13 + + # 6. Print success message + li a7, 64 # sys_write + li a0, 1 # stdout + la a1, msg_success + li a2, 32 # message length + ecall + + # 7. Exit with code 0 on success + li a0, 0 + li a7, 93 + ecall + +.section .rodata +msg_start: + .ascii "Running Command Line Args Tests\n" +msg_success: + .ascii "Command Line Args Tests PASSED!\n" +str_arg1: + .asciz "arg1" +str_arg2: + .asciz "arg2"