https://github.com/akkartik/mu/blob/main/linux/108write.subx

      1 # write: like _write, but also support in-memory streams in addition to file
      2 # descriptors.
      3 #
      4 # Our first dependency-injected and testable primitive. We can pass it either
      5 # a file descriptor or an address to a stream. If a file descriptor is passed
      6 # in, we _write to it using the right syscall. If a 'fake file descriptor' or
      7 # stream is passed in, we append to the stream. This lets us redirect output
      8 # in tests and check it later.
      9 #
     10 # We assume our data segment will never begin at an address shorter than
     11 # 0x08000000, so any smaller arguments are assumed to be real file descriptors.
     12 #
     13 # A stream looks like this:
     14 #   read: int  # index at which to read next
     15 #   write: int  # index at which writes go
     16 #   data: (array byte)  # prefixed by size as usual
     17
     18 == code
     19 #   instruction                     effective address                                                   register    displacement    immediate
     20 # . op          subop               mod             rm32          base        index         scale       r32
     21 # . 1-3 bytes   3 bits              2 bits          3 bits        3 bits      3 bits        2 bits      2 bits      0/1/2/4 bytes   0/1/2/4 bytes
     22
     23 # TODO: come up with a way to signal when a write to disk fails
     24 write:  # f: fd or (addr stream byte), s: (addr array byte)
     25     # . prologue
     26     55/push-ebp
     27     89/copy                         3/mod/direct    5/rm32/ebp    .           .             .           4/r32/esp   .               .                 # copy esp to ebp
     28     # if (s == 0) return
     29     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       0/imm32           # compare *(ebp+12)
     30     74/jump-if-=  $write:end/disp8
     31     # if (f < 0x08000000) _write(f, s) and return  # f can't be a user-mode address, so treat it as a kernel file descriptor
     32     81          7/subop/compare     1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         0x08000000/imm32  # compare *(ebp+8)
     33     73/jump-if-addr>=  $write:fake/disp8
     34     # . . push args
     35     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
     36     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           8/disp8         .                 # push *(ebp+8)
     37     # . . call
     38     e8/call  _write/disp32
     39     # . . discard args
     40     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     41     eb/jump  $write:end/disp8
     42 $write:fake:
     43     # otherwise, treat 'f' as a stream to append to
     44     # . save registers
     45     50/push-eax
     46     51/push-ecx
     47     52/push-edx
     48     53/push-ebx
     49     # ecx = f
     50     8b/copy                         1/mod/*+disp8   5/rm32/ebp    .           .                         1/r32/ecx   8/disp8         .                 # copy *(ebp+8) to ecx
     51     # edx = f->write
     52     8b/copy                         0/mod/indirect  1/rm32/ecx    .           .             .           2/r32/edx   .               .                 # copy *ecx to edx
     53     # ebx = f->size
     54     8b/copy                         1/mod/*+disp8   1/rm32/ecx    .           .             .           3/r32/ebx   8/disp8         .                 # copy *(ecx+8) to ebx
     55     # eax = _append-3(&f->data[f->write], &f->data[f->size], s)
     56     # . . push s
     57     ff          6/subop/push        1/mod/*+disp8   5/rm32/ebp    .           .             .           .           0xc/disp8       .                 # push *(ebp+12)
     58     # . . push &f->data[f->size]
     59     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  3/index/ebx   .           3/r32/ebx   0xc/disp8       .                 # copy ecx+ebx+12 to ebx
     60     53/push-ebx
     61     # . . push &f->data[f->write]
     62     8d/copy-address                 1/mod/*+disp8   4/rm32/sib    1/base/ecx  2/index/edx   .           3/r32/ebx   0xc/disp8       .                 # copy ecx+edx+12 to ebx
     63     53/push-ebx
     64     # . . call
     65     e8/call  _append-3/disp32
     66     # . . discard args
     67     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
     68     # f->write += eax
     69     01/add                          0/mod/indirect  1/rm32/ecx    .           .             .           0/r32/eax   .               .                 # add eax to *ecx
     70     # . restore registers
     71     5b/pop-to-ebx
     72     5a/pop-to-edx
     73     59/pop-to-ecx
     74     58/pop-to-eax
     75 $write:end:
     76     # . epilogue
     77     89/copy                         3/mod/direct    4/rm32/esp    .           .             .           5/r32/ebp   .               .                 # copy ebp to esp
     78     5d/pop-to-ebp
     79     c3/return
     80
     81 test-write-single:
     82     # clear-stream(_test-stream)
     83     # . . push args
     84     68/push  _test-stream/imm32
     85     # . . call
     86     e8/call  clear-stream/disp32
     87     # . . discard args
     88     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
     89     # write(_test-stream, "Ab")
     90     # . . push args
     91     68/push  "Ab"/imm32
     92     68/push  _test-stream/imm32
     93     # . . call
     94     e8/call  write/disp32
     95     # . . discard args
     96     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
     97     # check-ints-equal(*_test-stream->data, 41/A 62/b 00 00, msg)
     98     # . . push args
     99     68/push  "F - test-write-single"/imm32
    100     68/push  0x006241/imm32/Ab
    101     # . . push *_test-stream->data
    102     b8/copy-to-eax  _test-stream/imm32
    103     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
    104     # . . call
    105     e8/call  check-ints-equal/disp32
    106     # . . discard args
    107     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    108     # end
    109     c3/return
    110
    111 test-write-appends:
    112     # clear-stream(_test-stream)
    113     # . . push args
    114     68/push  _test-stream/imm32
    115     # . . call
    116     e8/call  clear-stream/disp32
    117     # . . discard args
    118     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               4/imm32           # add to esp
    119     # write(_test-stream, "C")
    120     # . . push args
    121     68/push  "C"/imm32
    122     68/push  _test-stream/imm32
    123     # . . call
    124     e8/call  write/disp32
    125     # . . discard args
    126     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    127     # write(_test-stream, "D")
    128     # . . push args
    129     68/push  "D"/imm32
    130     68/push  _test-stream/imm32
    131     # . . call
    132     e8/call  write/disp32
    133     # . . discard args
    134     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               8/imm32           # add to esp
    135     # check-ints-equal(*_test-stream->data, 43/C 44/D 00 00, msg)
    136     # . . push args
    137     68/push  "F - test-write-appends"/imm32
    138     68/push  0x00004443/imm32/C-D
    139     # . . push *_test-stream->data
    140     b8/copy-to-eax  _test-stream/imm32
    141     ff          6/subop/push        1/mod/*+disp8   0/rm32/eax    .           .             .           .           0xc/disp8       .                 # push *(eax+12)
    142     # . . call
    143     e8/call  check-ints-equal/disp32
    144     # . . discard args
    145     81          0/subop/add         3/mod/direct    4/rm32/esp    .           .             .           .           .               0xc/imm32         # add to esp
    146     # end
    147     c3/return
    148
    149 == data
    150
    151 _test-stream:  # (stream byte)
    152     # current write index
    153     0/imm32
    154     # current read index
    155     0/imm32
    156     # size
    157     0x10/imm32
    158     # data (2 lines x 8 bytes/line)
    159     00 00 00 00 00 00 00 00
    160     00 00 00 00 00 00 00 00
    161
    162 # . . vim:nowrap:textwidth=0