diff options
| author | Irene Knapp <ireneista@irenes.space> | 2025-10-25 19:05:24 -0700 |
|---|---|---|
| committer | Irene Knapp <ireneista@irenes.space> | 2025-10-25 19:05:24 -0700 |
| commit | fb564c85813f7ccf35e321af939cdb3328f6c18b (patch) | |
| tree | df958d0f28fd488c2c77564cf3fd7f0be80a6bf7 /quine.asm | |
| parent | f5892e07482d2fa9c8cfa53157d671564dc5371d (diff) | |
break up OLD_CODE into separate calls
the goal is to make the assembly monolith, less of one. no individual piece of this is complicated, it was just tightly coupled... now it is less so. Force-Push: yes Change-Id: I4bf5711ee6eae18e68be9d4e1f5107933c45fbbd
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 |