# Dump a stack trace when you abort.

== code

abort:  # e: (addr array byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    #
    (set-cursor-position-on-real-screen 0 0)
    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+8) 0xf 0xc)  # 0/real-screen, 0xf/fg=white, 0xc/bg=red
    (dump-call-stack)
    # crash
    {
      eb/jump loop/disp8
    }

# Helpers below this point are not intended to be reused; they assume the
# program will soon crash. In particular, they destroy the heap.

dump-call-stack:
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    # var labels/edx: (addr stream {start-address, label-slice} 0x4000)
    # start addresses are in ascending order
    81 5/subop/subtract %esp 0x30000/imm32  # 0x4000 labels * 12 bytes per label
    68/push  0x30000/imm32
    68/push  0/imm32/read
    68/push  0/imm32/write
    89/<- %edx 4/r32/esp
    #
    (load-debug-symbols %edx)  # destroys the heap
    # traverse the linked list of ebp pointers: https://wiki.osdev.org/Stack_Trace
    8b/-> *ebp 3/r32/ebx
    {
      # loop termination check
      81 7/subop/compare %ebx 0/imm32
      0f 84/jump-if-= break/disp32
      # loop body
      (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "\n" 0 0xc)
      (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebx+4) 0xf 0xc)
      (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 0 0xc)
      (containing-function %edx *(ebx+4))  # => eax, ecx
      (draw-slice-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax %ecx 0 0xc)
      # loop update
      8b/-> *ebx 3/r32/ebx
      #
      e9/jump loop/disp32
    }
$dump-call-stack:end:
    # . reclaim locals
    81 0/subop/add %esp 0x100c/imm32
    # . restore registers
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

load-debug-symbols:  # labels: (addr stream {start-address, label-slice})
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    53/push-ebx
    # create space for a stream on the heap, clobbering any existing data
    # var s/ecx: (addr stream byte)
    b9/copy-to-ecx 0x03000000/imm32
    c7 0/subop/copy *ecx 0/imm32  # write index
    c7 0/subop/copy *(ecx+4) 0/imm32  # read index
    c7 0/subop/copy *(ecx+8) 0x01000000/imm32  # stream capacity = 16MB
    # load 0x400 sectors starting from sector 10080 = 0x2760
    (load-sectors Primary-bus-primary-drive 0x2760 0x100 %ecx)
    (load-sectors Primary-bus-primary-drive 0x2860 0x100 %ecx)
    (load-sectors Primary-bus-primary-drive 0x2960 0x100 %ecx)
    (load-sectors Primary-bus-primary-drive 0x2a60 0x100 %ecx)
    # - parse pointers to portions of this stream into labels
    # var curr/ecx: (addr byte) = s->data
    81 0/subop/add %ecx 0xc/imm32
    {
      # loop termination check
      b8/copy-to-eax 0/imm32
      8a/byte-> *ecx 0/r32/eax
      3d/compare-eax-and 0/imm32
      0f 84/jump-if-= break/disp32
      # loop body
      (skip-to-next-space %ecx)  # => edx
      42/increment-edx
      (skip-to-next-newline %edx)  # => ebx
      (parse-hex-int-helper %edx %ebx)  # => eax
      43/increment-ebx
      (label-append *(ebp+8) %eax %ecx %edx)
      # loop update
      89/<- %ecx 3/r32/ebx
      #
      e9/jump loop/disp32
    }
$load-debug-symbols:end:
    # . restore registers
    5b/pop-to-ebx
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

skip-to-next-space:  # curr: (addr byte) -> _/edx: (addr byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # eax = 0
    b8/copy-to-eax 0/imm32
    #
    8b/-> *(ebp+8) 2/r32/edx
    {
      8a/byte-> *edx 0/r32/eax
      3d/compare-eax-and 0x20/imm32/space
      0f 84/jump-if-= break/disp32
      3d/compare-eax-and 0/imm32
      {
        75/jump-if-!= break/disp8
        (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "done loading" 7 0)
        {
          eb/jump loop/disp8
        }
      }
      3d/compare-eax-and 0xa/imm32/newline
      {
        75/jump-if-!= break/disp8
        (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "unexpected newline" 7 0)
        {
          eb/jump loop/disp8
        }
      }
      42/increment-edx
      e9/jump loop/disp32
    }
$skip-to-next-space:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

skip-to-next-newline:  # curr: (addr byte) -> _/ebx: (addr byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    # eax = 0
    b8/copy-to-eax 0/imm32
    #
    8b/-> *(ebp+8) 3/r32/ebx
    {
      8a/byte-> *ebx 0/r32/eax
      3d/compare-eax-and 0xa/imm32/newline
      0f 84/jump-if-= break/disp32
      3d/compare-eax-and 0/imm32
      {
        75/jump-if-!= break/disp8
        (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "done loading" 7 0)
        {
          eb/jump loop/disp8
        }
      }
      3d/compare-eax-and 0x20/imm32/space
      {
        75/jump-if-!= break/disp8
        (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "unexpected space" 7 0)
        {
          eb/jump loop/disp8
        }
      }
      43/increment-ebx
      e9/jump loop/disp32
    }
$skip-to-next-newline:end:
    # . restore registers
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

label-append:  # labels: (addr stream {start-address, label-slice}), address: int, start: int, end: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    56/push-esi
    # esi = labels
    8b/-> *(ebp+8) 6/r32/esi
    # ecx = labels->write
    8b/-> *esi 1/r32/ecx
    # labels->data[labels->write] = address
    8b/-> *(ebp+0xc) 0/r32/eax
    89/<- *(esi+ecx+0xc) 0/r32/eax
    # labels->data[labels->write+4] = start
    8b/-> *(ebp+0x10) 0/r32/eax
    89/<- *(esi+ecx+0x10) 0/r32/eax
    # labels->data[labels->write+8] = end
    8b/-> *(ebp+0x14) 0/r32/eax
    89/<- *(esi+ecx+0x14) 0/r32/eax
    # labels->write += 12
    81 0/subop/add *esi 0xc/imm32
$label-append:end:
    # . restore registers
    5e/pop-to-esi
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

containing-function:  # labels: (addr stream {start-address, label-slice}), address: int -> start/eax: (addr byte), end/ecx: (addr byte)
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    52/push-edx
    53/push-ebx
    56/push-esi
    # esi = labels
    8b/-> *(ebp+8) 6/r32/esi
    # var curr/ecx: (addr byte) = labels->data
    8d/copy-address *(esi+0xc) 1/r32/ecx
    # var max/edx: (addr byte) = labels->data + labels->write
    8b/-> *esi 2/r32/edx
    01/add-to %edx 1/r32/ecx
    # var previous-function-name/ebx: (addr slice) = 0
    bb/copy-to-ebx 0/imm32
    {
      # abort if not found
      39/compare %ecx 2/r32/edx
      {
        0f 82/jump-if-addr< break/disp32
        (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "failed to find function for address " 7 0)
        (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0xc) 7 0)
        {
          eb/jump loop/disp8
        }
      }
      # if *curr > address, break
      8b/-> *ecx 0/r32/eax
      3b/compare 0/r32/eax *(ebp+0xc)
      0f 87/jump-if-addr> break/disp32
      # if **(curr+4) not '$' or '@', save curr to previous-function-name
      {
        8b/-> *(ecx+4) 0/r32/eax
        8a/byte-> *eax 0/r32/eax
        25/and-with-eax 0xff/imm32
        3d/compare-eax-and 0x24/imm32/$
        74/jump-if-= break/disp8
        3d/compare-eax-and 0x40/imm32/@
        74/jump-if-= break/disp8
        8d/copy-address *(ecx+4) 3/r32/ebx
      }
      # loop update
      81 0/subop/add %ecx 0xc/imm32
      #
      e9/jump loop/disp32
    }
    8b/-> *ebx 0/r32/eax
    8b/-> *(ebx+4) 1/r32/ecx
$containing-function:end:
    # . restore registers
    5e/pop-to-esi
    5b/pop-to-ebx
    5a/pop-to-edx
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return

# unlike variants in .mu files, this only supports ASCII
draw-slice-wrapping-right-then-down-from-cursor-over-full-screen:  # screen: (addr screen), start: (addr byte), end: (addr byte), color: int, background-color: int
    # . prologue
    55/push-ebp
    89/<- %ebp 4/r32/esp
    # . save registers
    50/push-eax
    51/push-ecx
    52/push-edx
    # var curr/ecx: (addr byte) = start
    8b/-> *(ebp+0xc) 1/r32/ecx
    # edx = end
    8b/-> *(ebp+0x10) 2/r32/edx
    # eax = 0
    b8/copy-to-eax 0/imm32
    {
      # if (curr >= end) break
      39/compare %ecx 2/r32/edx
      73/jump-if-addr>= break/disp8
      # print *curr
      8a/byte-> *ecx 0/r32/eax
      (draw-grapheme-at-cursor *(ebp+8) %eax *(ebp+0x14) *(ebp+0x18))
      (move-cursor-rightward-and-downward *(ebp+8))
      #
      41/increment-ecx
      #
      eb/jump loop/disp8
    }
$draw-slice-wrapping-right-then-down-from-cursor-over-full-screen:end:
    # . restore registers
    5a/pop-to-edx
    59/pop-to-ecx
    58/pop-to-eax
    # . epilogue
    89/<- %esp 5/r32/ebp
    5d/pop-to-ebp
    c3/return
