diff options
Diffstat (limited to 'quine.asm')
| -rw-r--r-- | quine.asm | 112 |
1 files changed, 83 insertions, 29 deletions
diff --git a/quine.asm b/quine.asm index b8cf76c..f44f608 100644 --- a/quine.asm +++ b/quine.asm @@ -951,6 +951,25 @@ macro push.qreg source opcodereg 0x50, sreg end macro +macro push.bimm source + db 0x6A + db source +end macro + +; Operand-size prefix makes it 16-bit. +macro push.wimm source + db 0x66 + db 0x68 + dw source +end macro + +; There is no 64-bit immediate push. To fake it, push the low half, then the +; high half. [Intel] volume 1, chapter 9, section 9-2.4, "Memory Data Formats". +macro push.dimm source + db 0x68 + dd source +end macro + ; Pop a 64-bit value into a register from the stack (the one pointed to by ; rsp). Read the value from the old location, then increment rsp. ; @@ -1206,7 +1225,7 @@ code_start: ;;; grows downwards in memory. Since values are kept separately, the only ;;; thing on the control stack is return addresses, one per layer of call. ;;; -;;; * esp points to the top of the value stack +;;; * rsp points to the top of the value stack ;;; The value stack has no specific format, but it grows downwards in ;;; memory. In particular there's no concept of stack frames, because items ;;; on the stack don't belong to any particular word; the value stack in @@ -1923,8 +1942,22 @@ defword QUIT, 0 dq SYS_EXIT +;;;;;;;;;;;;;;;;;;;; +;;; System calls ;;; +;;;;;;;;;;;;;;;;;;;; ;;; -;;; This does the Linux exit() system call, passing it exit code zero. +;;; The kernel preserves every register except rax, rcx, and r11. The system +;;; call number goes in rax, as does the return value. Parameters go in rdi, +;;; rsi, rdx, r10, r8, and r9, in that order. [SysV] A.2.1. +;;; +;;; Notice that rsi is our control stack, so we have to save it (for +;;; syscalls with at least two parameters). We can use the value stack to do +;;; that, since rsp is preserved. We don't save other registers because our +;;; caller should do that, if it cares. +;;; + +;;; +;;; This does the Linux exit() system call, passing it exit code zero. ;;; defword SYS_EXIT, 0 dq $ + 0x8 ; codeword @@ -1937,15 +1970,59 @@ defword SYS_EXIT, 0 hlt +;;; +;;; This does the Linux write() system call, passing it an address from the +;;; top of the stack and a length from the second position on the stack. It +;;; writes to file descriptor 1, which is stdout. +;;; +;;; For our length parameter, we can pop directly from the stack into rdx, +;;; which directly becomes the syscall parameter. For our address parameter, +;;; the syscall wants it in rsi, which we also care about, so we have to do a +;;; little juggling. +;;; +defword SYS_WRITE, 0 + dq $ + 0x8 ; codeword + pop.qreg rcx ; address from stack + pop.qreg rdx ; length from stack, passed directly + push.qreg rsi ; save rsi + mov.b rax, 1 ; syscall number + mov.qreg.qimm rdi, 1 ; file descriptor + mov.qreg.qreg rsi, rcx ; pass address + syscall + pop.qreg rsi ; restore rsi + NEXT + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; (new) Implementation strategy ;;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; defword QUINE, 0 dq DOCOL ; codeword + + ; This stack-allocates a buffer, then finishes by pushing its length and + ; address on the value stack. Thus we don't need to care about how it + ; internally uses registers. dq OLD_CODE + + ; write() from stack-allocated buffer + dq SYS_WRITE + + ; write() the machine code by using self-reference + ; TODO do this in a "real" quine way + dq WRITE_SELF_RAW_H + dq SYS_WRITE + dq EXIT +defword WRITE_SELF_RAW_H, 0 + dq $ + 0x8 ; codeword + mov.qreg.qimm rax, file_size - 0x78 + push.qreg rax + mov.qreg.qimm rax, elf_header + 0x78 + push.qreg rax + NEXT + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; (old) Implementation strategy ;;; @@ -2053,36 +2130,13 @@ defword OLD_CODE, 0 mov.qreg.disp8.qreg rcx, 0x68, rdx ; size in memory ;;; - ;;; The buffer is ready; output the file. + ;;; The buffer is ready; push its length and address on the value stack, so + ;;; our caller can handle write()ing it out. ;;; - ; write() from stack-allocated buffer - ; we have real stuff using rsi now, so don't forget to save and restore it - ; also rcx is the only "low" register the kernel doesn't preserve - push.qreg rsi + push.dimm 0 + push.dimm 0x78 push.qreg rcx - mov.b rax,1 - mov.qreg.qimm rdi, 1 - mov.qreg.qreg rsi, rcx - mov.qreg.qimm rdx, 0x78 - syscall - pop.qreg rcx - pop.qreg rsi - - ; write() the machine code by using self-reference - ; we have real stuff using rsi now, so don't forget to save and restore it - ; also rcx is the only "low" register the kernel doesn't preserve - ; - ; TODO do this in a "real" quine way - push.qreg rsi - push.qreg rcx - mov.b rax, 1 - mov.qreg.qimm rdi, 1 - mov.qreg.qimm rsi, elf_header + 0x78 - mov.qreg.qimm rdx, file_size - 0x78 - syscall - pop.qreg rcx - pop.qreg rsi NEXT |