summary refs log tree commit diff
diff options
context:
space:
mode:
authorIrene Knapp <ireneista@irenes.space>2025-10-10 22:54:31 -0700
committerIrene Knapp <ireneista@irenes.space>2025-10-10 22:54:31 -0700
commita365fd2d8c18a9ceb5babf8e3f512e1a6c72fbcf (patch)
tree8839aed580c9a7fdec407eb8a454ab0008e4e023
parent14d71a07dafa023ba83c2bdc6837d4ecc9de12ba (diff)
sorta outputs itself, but it reads too much of itself to do that, but very good progress
Force-Push: yeah
Change-Id: I14fdd705643af7ae23ea75c778e9671d3787e0e6
-rw-r--r--quine.asm396
1 files changed, 396 insertions, 0 deletions
diff --git a/quine.asm b/quine.asm
new file mode 100644
index 0000000..467613a
--- /dev/null
+++ b/quine.asm
@@ -0,0 +1,396 @@
+; fasmg hello.asm hello && chmod 755 hello && ./hello; echo $?
+;
+; fasmg quine.asm quine && ./quine > quine2; echo "exit code:" $?; echo; hexdump -C quine; echo; hexdump -C quine2; cmp quine quine2; echo; echo "compare:" $?
+; ZydisDisasm -64 quine
+
+
+macro rex.0
+  db 0x40
+end macro
+
+macro rex.w
+  db 0x48
+end macro
+
+macro rex.xb
+  db 0x43
+end macro
+
+macro modrm mod, reg, rm
+  assert mod >= 0 & mod < 4
+  assert reg >= 0 & reg < 8
+  assert rm >= 0 & rm < 8
+  db (mod shl 6) or (reg shl 3) or rm
+end macro
+
+macro sib scale, index, base
+  assert scale >= 0 & scale < 4
+  assert index >= 0 & index < 8
+  assert base >= 0 & index < 8
+  db (scale shl 6) or (index shl 3) or base
+end macro
+
+macro mov.b target, source
+  match =rax?, target
+    db 0xB8
+    dd source
+  else match =rdi?, target
+    db 0xBF
+    dd source
+  else
+    assert 0
+  end match
+end macro
+
+macro qwordreg result, register
+  match =rax?, register
+    result = 0
+  else match =rcx?, regiser
+    result = 1
+  else match =rdx?, register
+    result = 2
+  else match =rbx?, register
+    result = 3
+  else match =rsp?, register
+    result = 4
+  else match =rbp?, register
+    result = 5
+  else match =rsi?, register
+    result = 6
+  else match =rdi?, register
+    result = 7
+  else
+    assert 0
+  end match
+end macro
+
+
+macro mov.d.dimm target, source
+  rex.w
+  db 0xC7
+  qwordreg reg, target
+  modrm 3, 0, reg
+  dd source
+end macro
+
+
+macro mov.q target, source
+  match =rax, target
+    rex.w
+    db 0xB8
+    dq source
+  else match =rdi, target
+    rex.w
+    db 0xBF
+    dq source
+  else match =rsi, target
+    match =rsp, source
+      rex.w
+      db 0x89
+      modrm 3, 4, 6
+    else
+      rex.w
+      db 0xBE
+      dq source
+    end match
+  else match =rdx, target
+    rex.w
+    db 0xBA
+    dq source
+  else
+    assert 0
+  end match
+end macro
+
+
+macro add.b target, source
+  match =rax, target
+    rex.w
+    db 0x83
+    modrm 3, 0, 0
+    db source
+  else
+    assert 0
+  end match
+end macro
+
+
+macro add.q target, source
+  db 0x01
+  qwordreg treg, target
+  qwordreg sreg, source
+  modrm 3, sreg, treg
+end macro
+
+
+macro sub.b target, source
+  match =rsp, target
+    rex.w
+    db 0x83
+    modrm 3, 5, 4
+    db source
+  else
+    assert 0
+  end match
+end macro
+
+
+; Move from an 8-bit immediate value, to a location relative to a 64-bit
+; register, with an 8-bit displacement and no indexing.
+;
+; This uses opcode 0xC6, which has w = 0. Since we run in 64-bit mode, that
+; makes the operand size 8 bits, regardless of the current operand-size
+; attribute. [Intel] volume 2D, section B.1.43, table B-6.
+macro mov.rel.b target, offset, source
+  match =rsp, target
+    db 0xC6
+    modrm 1, 0, 4
+    sib 0, 0, 4
+    db offset
+    db source
+  else
+    assert 0
+  end match
+end macro
+
+; Move from a 16-bit immediate value, to a location relative to a 64-bit
+; register, with an 8-bit displacement and no indexing.
+;
+; This uses opcode 0xC7, which has w = 1. We run in 64-bit mode, so that gives
+; us an operand size of 32 bits by default. [Intel] volume 1, section 3.6.1,
+; table 3-4. We want a 16-bit operand, so we use the operand-size prefix,
+; 0x66, and we leave REX.W unset.
+macro mov.rel.w target, offset, source
+  match =rsp, target
+    db 0x66
+    db 0xC7
+    modrm 1, 0, 4
+    sib 0, 4, 4
+    db offset
+    dw source
+  else
+    assert 0
+  end match
+end macro
+
+; Move from a 32-bit immediate value, to a location relative to a 64-bit
+; register, with an 8-bit displacement and no indexing.
+;
+; This uses opcode 0x67, which has w = 1. We run in 64-bit mode, so that gives
+; us an operand size of 32 by default. [Intel] volume 2D, section B.1.43,
+; table B-6. This is what we want, so we leave it.
+macro mov.rel.d target, offset, source
+  match =rsp, target
+    db 0xC7
+    modrm 1, 0, 4
+    sib 0, 4, 4
+    db offset
+    dd source
+  else
+    assert 0
+  end match
+end macro
+
+; Move from a 64-bit register, to a 64-bit location relative to a 64-bit
+; register, with an 8-bit displacement and no indexing.
+;
+; This uses opcode 0x89.
+macro mov.rel.q target, offset, source
+  match =rsp, target
+    match =rax, source
+      rex.w
+      db 0x89
+      modrm 1, 0, 4
+      sib 0, 4, 4
+      db offset
+    else match =rdx, source
+      ;mov.rel.q rsp, 0x60, rdx                 ; size in file
+      ; todo doesn't work yet
+      rex.w
+      db 0x89
+      ;0x44 0100 0100
+      ;     01 000 100
+      ;0x54 0101 0100
+      ;     01 010 100
+      
+      modrm 1, 2, 4 ; <- changing this reg value should do it, but...
+      sib 0, 4, 4
+      db offset
+    else
+      assert 0
+    end match
+  else
+    assert 0
+  end match
+end macro
+
+; Move from a 32-bit immediate value, to a 64-bit location relative to a
+; 64-bit register, with an 8-bit displacement and no indexing.
+;
+; Note that there is no instruction to move a 64-bit immediate to memory.
+;
+; This uses opcode 0xC7, which has w = 1. We run in 64-bit mode, so that
+; gives us an operand size of 32 by default. [Intel] volume 2D,
+; section B.1.43, table B-6. We want a 64-bit operand, so we use the REX.W
+; prefix, 0x48.
+macro mov.rel.q.d target, offset, source
+  match =rsp, target
+    rex.w
+    db 0xC7
+    modrm 1, 0, 4
+    sib 0, 4, 4
+    db offset
+    dd source
+  else
+    assert 0
+  end match
+end macro
+
+macro syscall
+  db 0x0F, 0x05
+;        0f                      two-byte escape
+;           05                   syscall ^ o64
+end macro
+
+
+
+
+org 0x08000000
+
+elf_header:
+  ; * denotes mandatory fields according to breadbox
+  db 0x7F, "ELF"                 ; *magic number
+  db 2                           ; 64-bit
+  db 1                           ; little-endian
+  db 1                           ; ELF header format version 1
+  db 0                           ; System-V ABI
+  db 8 dup 0                     ; (padding)
+
+  dw 2                           ; *executable
+  dw 0x3E                        ; *Intel x86-64
+  dd 1                           ; ELF format version
+
+  dq _start                      ; *entry point
+  dq program_header - $$         ; *program header offset
+  dq 0                           ; section header offset
+  dd 0                           ; processor flags
+  dw elf_header_size
+  dw program_header_entry_size   ; *
+  dw 1                           ; *number of program header entries
+  dw 0                           ; section header entry size
+  dw 0                           ; number of section header entries
+  dw 0                           ; section name string table index
+elf_header_size = $ - elf_header
+
+program_header:
+  dd 1                           ; *"loadable" segment type
+  dd 0x05                        ; *read+execute permission
+  dq 0                           ; *offset in file
+  dq $$                          ; *virtual address
+                                 ;   required, but can be anything, subject to
+                                 ;   alignment
+  dq 0                           ; physical address (ignored)
+  dq file_size                   ; *size in file
+  dq file_size                   ; *size in memory
+  dq 0                           ; segment alignment
+                                 ;   for relocation - will we be ASLR'd?
+program_header_entry_size = $ - program_header
+
+load_origin = 0x08000000
+
+_start:
+  mov.d.dimm rdx, 0                        ; store running file size here
+  sub.b rsp, 0xFF                          ; reserve stack space
+
+  ; ELF header
+  mov.rel.d rsp, 0x00, 0x7F bappend "ELF"  ; magic number
+  mov.rel.b rsp, 0x04, 2                   ; 64-bit
+  mov.rel.b rsp, 0x05, 1                   ; little-endian
+  mov.rel.b rsp, 0x06, 1                   ; ELF header format version 1
+  mov.rel.b rsp, 0x07, 0                   ; System-V ABI
+  mov.rel.q.d rsp, 0x08, 0                 ; (padding)
+
+  mov.rel.w rsp, 0x10, 2                   ; executable
+  mov.rel.w rsp, 0x12, 0x3E                ; Intel x86-64
+  mov.rel.d rsp, 0x14, 1                   ; ELF format version
+
+  ; Compute the entry pointer.
+  mov.q rax, load_origin
+  add.b rax, 120
+  mov.rel.q rsp, 0x18, rax                 ; entry point
+
+  mov.rel.q.d rsp, 0x20, 64                ; program header offset
+    ; We place the program header immediately after the ELF header. This
+    ; offset is from the start of the file.
+  mov.rel.q.d rsp, 0x28, 0                 ; section header offset
+  mov.rel.d rsp, 0x30, 0                   ; processor flags
+  mov.rel.w rsp, 0x34, 64                  ; ELF header size
+  mov.rel.w rsp, 0x36, 56                  ; program header entry size
+  mov.rel.w rsp, 0x38, 1                   ; number of program header entries
+  mov.rel.w rsp, 0x3a, 0                   ; section header entry size
+  mov.rel.w rsp, 0x3c, 0                   ; number of section header entries
+  mov.rel.w rsp, 0x3e, 0                   ; section name string table index
+
+  ; Program header
+  mov.rel.d rsp, 0x40, 1                   ; "loadable" segment type
+  mov.rel.d rsp, 0x44, 0x05                ; read+execute permission
+  mov.rel.q.d rsp, 0x48, 0                 ; offset in file
+  mov.rel.q.d rsp, 0x50, load_origin       ; virtual address
+    ; required, but can be anything, subject to alignment
+  mov.rel.q.d rsp, 0x58, 0                 ; physical address (ignored)
+
+  ; Fill in 0 as the file size for now, to avoid unitialized memory.
+  mov.rel.q.d rsp, 0x60, 0                 ; size in file
+  mov.rel.q.d rsp, 0x68, 0                 ; size in memory
+
+  mov.rel.q.d rsp, 0x70, 0                 ; segment alignment
+    ;   for relocation - will we be ASLR'd?
+
+  ; Add the size of the ELF header to the running total
+  mov.d.dimm rax, 0x78
+  add.q rdx, rax
+
+  ; Go back and fill in the file size now that we know it.
+  ; so notionally, let's pretend rdx is a variable that keeps track of file
+  ; size. we would do something to populate it, then we would add it to rax...
+  mov.q rax, 0x185
+  add.q rdx, rax
+    ; TODO of course, really we want to for-real track these
+  mov.rel.q rsp, 0x60, rdx                 ; size in file
+  mov.rel.q rsp, 0x68, rdx                 ; size in memory
+    ; TODO it works for rax source but not yet for rdx
+
+  ;;; after the file size is populated, that's the entire ELF header. yay!
+
+  ; write() from stack-allocated buffer
+  mov.b rax,1
+  mov.q rdi, 1
+  mov.q rsi, rsp
+  mov.q rdx, 0x78
+  syscall
+
+  ; write() hardcoded header
+  mov.b rax, 1
+  mov.q rdi, 1
+  mov.q rsi, elf_header + 0x78
+  mov.q rdx, file_size - 0x78
+  syscall
+
+  ; write() greeting
+  mov.b rax, 1
+  mov.q rdi, 2
+  mov.q rsi, greeting
+  mov.q rdx, greeting_size
+  syscall
+
+  ; exit()
+  mov.b rax, 60
+  mov.b rdi, 0
+  syscall
+
+greeting:
+  db "Hello, Irenes!", 0x0A
+greeting_size = $ - greeting
+
+file_size = $ - $$
+