diff --git a/hello-aarch64.s b/hello-aarch64.s new file mode 100644 index 0000000..2f958f3 --- /dev/null +++ b/hello-aarch64.s @@ -0,0 +1,85 @@ +// ARM 8 +// gold linker has smallest binary size. others probably can emit smaller +// binaries with custom linker scripts. Their default ones +// are not optimized for hello world programs +// as -march=armv8-a hello-aarch64.s && ld.gold -s -n -o hello a.out +// +// Arm instruction set reference: +// https://developer.arm.com/documentation/100076/0100/A64-Instruction-Set-Reference +// +// AArch64 has thirty-one 64-bit general-purpose registers X0-X30, +// the bottom halves of which are accessible as W0-W30 +// +// Most A64 integer instructions can operato on either type of register +.data // section declaration +msg: + .string "All your codebase is belong to us\n" // output string + +len = . - msg // length of output string + +.text // section declaration + // we must export the entry point to the ELF linker or + .global _start // loader. They conventionally recognize _start as their + // entry point. Use ld −e foo to override the default. + +square: + // We're using 32 bit register variants here as eventually we will + // return this as the exit syscall, and that syscall will want + // a 32 bit return code + // + // Function entry + sub sp, sp, #16 // Move stack pointer for locals + // Interpret as "sp = sp - 16" + // 16 bytes will take 2 64 bit locals or + // 4 32 bit locals + + // Function arguments + str w0, [sp, #12] // Store our first (and only) argument on the + // stack. Not specifically necessary, but good safety + // mechanism. We could instead simply: + // mov w8, w0 + // mov w9, w0 + // mul w0, w8, w9 + // This would remove memory access completely, + // but this allows us to demonstrate the + // general pattern we can use for functions + // + // Here we are using the first "32 bit slot" of the + // area we carved out for local variables + // above + + // Function body + ldr w8, [sp, #12] // Load first operand with our argument + ldr w9, [sp, #12] // Load second operand with our argument + mul w0, w8, w9 // Do the multiplication + // Interpret as "w0 = w8 * w9" + + // Function exit + add sp, sp, #16 // Restore stack pointer + // Interpret as "sp = sp + 16" + ret // Return + +_start: + +// https://man7.org/linux/man-pages/man2/syscall.2.html +// https://github.com/torvalds/linux/blob/v4.17/include/uapi/asm-generic/unistd.h + # Hello world to stdout + mov x8, #64 // System call (sys_write) + mov x0, #1 // first argument: file handle (stdout) + ldr x1, =msg // second argument: pointer to message to write + ldr x2, =len // third argument: message length + svc #0 // call kernel + + // Square argc + ldr w0, [sp] // argc is on the stack + // this is an eightbyte according to table 3.9 + // of the System V AMD64 psABI + // https://gitlab.com/x86-psABIs/x86-64-ABI + bl square // Square our argc, result in w0 + // w0 will also be the first argument to sys_exit + // w0 is x0 with top 32 bits zeroed + // so no need to load + + // exit + mov w8, #93 // system call number (sys_exit) + svc #0 // call kernel and exit diff --git a/hello-arm7l.s b/hello-arm7l.s new file mode 100644 index 0000000..debeefd --- /dev/null +++ b/hello-arm7l.s @@ -0,0 +1,71 @@ +// ARM 7l - 32 bit arm. Used on 32 bit hardware or 64 bit hardware with 32 bit +// linux (e.g. most raspbian as of 2022) +// +// gold linker has smallest binary size. others probably can emit smaller +// binaries with custom linker scripts. Their default ones +// are not optimized for hello world programs +// as hello-arm7l.s && ld.gold -s -n -o hello a.out +.data // section declaration +msg: + .string "All your codebase is belong to us\n" // output string + +len = . - msg // length of output string + +.text // section declaration + // we must export the entry point to the ELF linker or + .global _start // loader. They conventionally recognize _start as their + // entry point. Use ld −e foo to override the default. + +square: + // Function entry + sub sp, sp, #4 // Move stack pointer for locals + + // Function arguments + str r0, [sp] // Store return address in stack memory + // not specifically necessary, but good safety + // mechanism. We could instead simply ignore + // the str/ldr operations here and just go + // for it + // + // This would remove memory access completely, + // but this allows us to demonstrate the + // general pattern we can use for functions + + + // Function body + ldr r0, [sp] // Load first operand with our argument + mul r1, r0, r0 // Do the multiplication + mov r0, r1 // Move result to return register + + // Function exit + add sp, sp, #4 // Restore stack pointer + bx lr // Return + +_start: + +// https://man7.org/linux/man-pages/man2/syscall.2.html +// Syscall numbers captured from https://syscalls.w3challs.com/?arch=arm_strong +// arm syscall table here: +// https://github.com/torvalds/linux/blob/v4.19/arch/arm/tools/syscall.tbl + # Hello world to stdout + mov r7, #4 // System call (sys_write) + mov r0, #1 // first argument: file handle (stdout) + ldr r1, =msg // second argument: pointer to message to write + ldr r2, =len // third argument: message length + swi #0 // call kernel. We are using Linux's EABI - + // the embedded application binary interface + // which is more consistent with the way it is + // done in other architectures + + // Square argc + ldr r0, [sp] // argc is on the stack + // this is an eightbyte according to table 3.9 + // of the System V AMD64 psABI + // https://gitlab.com/x86-psABIs/x86-64-ABI + bl square // Square our argc, result in r0 + // r0 will also be the first argument to sys_exit + // so no need to load + + // exit + mov r7, #1 // system call number (sys_exit) + swi #0 // call kernel and exit diff --git a/hello-riscv64.s b/hello-riscv64.s new file mode 100644 index 0000000..44817f7 --- /dev/null +++ b/hello-riscv64.s @@ -0,0 +1,73 @@ +# gold linker has smallest binary size. others probably can emit smaller +# binaries with custom linker scripts. Their default ones +# are not optimized for hello world programs +# as hello-riscv64.s && ld -s -n -o hello a.out +.data # section declaration +msg: + .string "All your codebase is belong to us\n" # output string + +len = . - msg # length of output string + +.text # section declaration + # we must export the entry point to the ELF linker or + .global _start # loader. They conventionally recognize _start as their + # entry point. Use ld −e foo to override the default. + +# RISC-V register reference: +# https://xuanxuanblingbling.github.io/assets/pic/riscv/register.png +# RISC-V instruction cheat sheet: +# https://risc-v.guru/instructions/ + +square: + # Function entry + addi sp, sp, -32 # Move stack pointer for locals + # 32 bytes gives us (32/8 =) 4 64 slots to use + # We will only store our return address/frame pointer + sd ra, 24(sp) # Store return address in stack memory + sd s0, 16(sp) # Store frame pointer in stack memory + addi s0, sp, 32 # Capture original stack pointer in s0 + # Interpret as s0 = sp + 32 + + # Function arguments + sw a0, -20(s0) # Save first argument (num) to stack + # Be aware that we're now subtracting rather + # than adding because we just changed s0 + # A long version of this would be sd a0, -24(s0) + + # Function body + lw a0, -20(s0) # Load first argument (num) from stack + # This save/load is to make sure we have a copy + # of the original argument, which is not important + # here, but would be if this was a more serious + # function. We could just have easily comment + # out both instructions and avoid memory access + + mulw a0, a0, a0 # Actually do the multiplication. + + # Function exit + ld ra, 24(sp) # Restore return address from stack + ld s0, 16(sp) # Restore frame pointer from stack memory + addi sp, sp, 32 # Restore stack pointer + ret # Return + +_start: + +# https://man7.org/linux/man-pages/man2/syscall.2.html +# https://github.com/torvalds/linux/blob/v4.17/include/uapi/asm-generic/unistd.h + # Hello world to stdout + li a7, 64 # System call (sys_write) + li a0, 1 # first argument: file handle (stdout) + lla a1, msg # second argument: pointer to message to write + li a2, len # third argument: message length + scall # call kernel + + # Square argc + ld a0, (sp) # argc is on the stack + # https://www.reddit.com/r/RISCV/comments/p2na17/command_line_arguments_in_assembly/ + call square # Square our argc, result in a0 + # a0 will also be the first argument to sys_exit + # so no need to load + + # exit + li a7, 93 # system call number (sys_exit) + scall # call kernel and exit