1 # read: analogously to write, support reading from in-memory streams in 2 # addition to file descriptors. 3 # 4 # We can pass it either a file descriptor or an address to a stream. If a 5 # file descriptor is passed in, we _read from it using the right syscall. If a 6 # stream is passed in (a fake file descriptor), we read from it instead. This 7 # lets us initialize input for tests. 8 # 9 # A little counter-intuitively, the output of 'read' ends up in.. a stream. So 10 # tests end up doing a redundant copy. Why? Well, consider the alternatives: 11 # 12 # a) Reading into a string, and returning a pointer to the end of the read 13 # region, or a count of bytes written. Now this count or end pointer must be 14 # managed separately by the caller, which can be error-prone. 15 # 16 # b) Having 'read' return a buffer that it allocates. But there's no way to 17 # know in advance how large to make the buffer. If you read less than the 18 # size of the buffer you again end up needing to manage initialized vs 19 # uninitialized memory. 20 # 21 # c) Creating more helpful variants like 'read-byte' or 'read-until' which 22 # also can take a file descriptor or stream, just like 'write'. But such 23 # primitives don't exist in the Linux kernel, so we'd be implementing them 24 # somehow, either with more internal buffering or by making multiple 25 # syscalls. 26 # 27 # Reading into a stream avoids these problems. The buffer is externally 28 # provided and the caller has control over where it's allocated, its lifetime, 29 # and so on. The buffer's read and write pointers are internal to it so it's 30 # easier to keep in a consistent state. And it can now be passed directly to 31 # helpers like 'read-byte' or 'read-until' that only need to support streams, 32 # never file descriptors. 33 # 34 # Like with 'write', we assume our data segment will never begin at an address 35 # shorter than 0x08000000, so any smaller arguments are assumed to be real 36 # file descriptors. 37 # 38 # As a reminder, a stream looks like this: 39 # write: int # index at which to write to next 40 # read: int # index at which to read next 41 # data: (array byte) # prefixed by length as usual 42 43 == code 44 # instruction effective address register displacement immediate 45 # . op subop mod rm32 base index scale r32 46 # . 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 47 48 # main: 49 e8/call run-tests/disp32 # 'run-tests' is a function created automatically by SubX. It calls all functions that start with 'test-'. 50 # syscall(exit, Num-test-failures) 51 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX 52 b8/copy-to-EAX 1/imm32 53 cd/syscall 0x80/imm8 54 55 read: # f : fd or (address stream), s : (address stream) -> num-bytes-read/EAX 56 # . prolog 57 55/push-EBP 58 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP 59 # if (f < 0x08000000) return _read(f, s) # f can't be a user-mode address, so treat it as a kernel file descriptor 60 81 7/subop/compare 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . . 8/disp8 0x08000000/imm32 # compare *(EBP+8) 61 7d/jump-if-greater-or-equal $read:fake/disp8 62 # . . push args 63 ff 6/subop/push 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . . 0xc/disp8 . # push *(EBP+12) 64 ff 6/subop/push 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . . 8/disp8 . # push *(EBP+8) 65 # . . call 66 e8/call _read/disp32 67 # . . discard args 68 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 69 # return 70 eb/jump $read:end/disp8 71 $read:fake: 72 # otherwise, treat 'f' as a stream to scan from 73 # . save registers 74 56/push-ESI 75 57/push-EDI 76 # ESI = f 77 8b/copy 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI 78 # EDI = s 79 8b/copy 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . 7/r32/EDI 0xc/disp8 . # copy *(EBP+12) to ESI 80 # EAX = _append-4(out = &s->data[s->write], outend = &s->data[s->length], 81 # in = &f->data[f->read], inend = &f->data[f->write]) 82 # . . push &f->data[f->write] 83 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX 84 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX 85 50/push-EAX 86 # . . push &f->data[f->read] 87 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # copy *(ESI+4) to EAX 88 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy ESI+EAX+12 to EAX 89 50/push-EAX 90 # . . push &s->data[s->length] 91 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 8/disp8 . # copy *(EDI+8) to EAX 92 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX 93 50/push-EAX 94 # . . push &s->data[s->write] 95 8b/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy *EDI to EAX 96 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/EDI 0/index/EAX . 0/r32/EAX 0xc/disp8 . # copy EDI+EAX+12 to EAX 97 50/push-EAX 98 # . . call 99 e8/call _append-4/disp32 100 # . . discard args 101 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP 102 # s->write += EAX 103 01/add 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # add EAX to *EDI 104 # f->read += EAX 105 01/add 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # add EAX to *(ESI+4) 106 # . restore registers 107 5f/pop-to-EDI 108 5e/pop-to-ESI 109 $read:end: 110 # . epilog 111 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP 112 5d/pop-to-EBP 113 c3/return 114 115 # - helpers 116 117 # idea: a clear-if-empty method on streams that clears only if f->read == f->write 118 # Unclear how I'd use it, though. Callers seem to need the check anyway. 119 # Maybe a better helper would be 'empty-stream?' 120 121 _read: # fd : int, s : (address stream) -> num-bytes-read/EAX 122 # . prolog 123 55/push-EBP 124 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP 125 # . save registers 126 51/push-ECX 127 52/push-EDX 128 53/push-EBX 129 56/push-ESI 130 # ESI = s 131 8b/copy 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI 132 # EAX = s->write 133 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX 134 # EDX = s->length 135 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 2/r32/EDX 8/disp8 . # copy *(ESI+8) to EDX 136 # syscall(read, fd, &s->data[s->write], s->length - s->write) 137 # . . fd : EBX 138 8b/copy 1/mod/*+disp8 4/rm32/sib 5/base/EBP 4/index/none . 3/r32/EBX 8/disp8 . # copy *(EBP+8) to EBX 139 # . . data : ECX = &s->data[s->write] 140 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 1/r32/ECX 0xc/disp8 . # copy ESI+EAX+12 to ECX 141 # . . size : EDX = s->length - s->write 142 29/subtract 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # subtract EAX from EDX 143 # . . syscall 144 b8/copy-to-EAX 3/imm32/read 145 cd/syscall 0x80/imm8 146 # add the result EAX to s->write 147 01/add 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # add EAX to *ESI 148 # . restore registers 149 5e/pop-to-ESI 150 5b/pop-to-EBX 151 5a/pop-to-EDX 152 59/pop-to-ECX 153 # . epilog 154 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP 155 5d/pop-to-EBP 156 c3/return 157 158 # Two options: 159 # 1 (what we have above): 160 # ECX = s 161 # EAX = s->write 162 # EDX = s->length 163 # # syscall 164 # ECX = lea ECX+EAX+12 165 # EDX = sub EDX EAX 166 # 167 # 2: 168 # ECX = s 169 # EDX = s->length 170 # ECX = &s->data 171 # # syscall 172 # ECX = add ECX, s->write 173 # EDX = sub EDX, s->write 174 # 175 # Not much to choose between the two? Option 2 performs a duplicate load to 176 # use one less register, but doesn't increase the amount of spilling (ECX 177 # and EDX must be used, and EAX must be clobbered anyway). 178 179 # - tests 180 181 test-read-single: 182 # - write a single character into _test-stream, then read from its buffered-file 183 # clear-stream(_test-stream) 184 # . . push args 185 68/push _test-stream/imm32 186 # . . call 187 e8/call clear-stream/disp32 188 # . . discard args 189 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 190 # clear-stream(_test-stream-buffer) 191 # . . push args 192 68/push _test-stream-buffer/imm32 193 # . . call 194 e8/call clear-stream/disp32 195 # . . discard args 196 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 197 # write(_test-stream, "Ab") 198 # . . push args 199 68/push "Ab"/imm32 200 68/push _test-stream/imm32 201 # . . call 202 e8/call write/disp32 203 # . . discard args 204 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 205 # read(_test-stream, _test-stream-buffer) 206 # . . push args 207 68/push _test-stream-buffer/imm32 208 68/push _test-stream/imm32 209 # . . call 210 e8/call read/disp32 211 # . . discard args 212 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 213 # check-ints-equal(EAX, 2, msg) 214 # . . push args 215 68/push "F - test-read-single: return EAX"/imm32 216 68/push 2/imm32 217 50/push-EAX 218 # . . call 219 e8/call check-ints-equal/disp32 220 # . . discard args 221 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 222 # check-ints-equal(*_test-stream-buffer->data, 41/A 62/b 00 00, msg) 223 # . . push args 224 68/push "F - test-read-single"/imm32 225 68/push 0x006241/imm32/Ab 226 # . . push *_test-stream-buffer->data 227 b8/copy-to-EAX _test-stream-buffer/imm32 228 ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) 229 # . . call 230 e8/call check-ints-equal/disp32 231 # . . discard args 232 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 233 # end 234 c3/return 235 236 test-read-is-stateful: 237 # - make two consecutive reads, check that their results are appended 238 # clear-stream(_test-stream) 239 # . . push args 240 68/push _test-stream/imm32 241 # . . call 242 e8/call clear-stream/disp32 243 # . . discard args 244 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 245 # clear-stream(_test-stream-buffer) 246 # . . push args 247 68/push _test-stream-buffer/imm32 248 # . . call 249 e8/call clear-stream/disp32 250 # . . discard args 251 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 252 # write(_test-stream, "C") 253 # . . push args 254 68/push "C"/imm32 255 68/push _test-stream/imm32 256 # . . call 257 e8/call write/disp32 258 # . . discard args 259 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 260 # read(_test-stream, _test-stream-buffer) 261 # . . push args 262 68/push _test-stream-buffer/imm32 263 68/push _test-stream/imm32 264 # . . call 265 e8/call read/disp32 266 # . . discard args 267 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 268 # write(_test-stream, "D") 269 # . . push args 270 68/push "D"/imm32 271 68/push _test-stream/imm32 272 # . . call 273 e8/call write/disp32 274 # . . discard args 275 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 276 # read(_test-stream, _test-stream-buffer) 277 # . . push args 278 68/push _test-stream-buffer/imm32 279 68/push _test-stream/imm32 280 # . . call 281 e8/call read/disp32 282 # . . discard args 283 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 284 # check-ints-equal(*_test-stream-buffer->data, 43/C 44/D 00 00, msg) 285 # . . push args 286 68/push "F - test-read-is-stateful"/imm32 287 68/push 0x00004443/imm32/C-D 288 # . . push *_test-stream-buffer->data 289 b8/copy-to-EAX _test-stream-buffer/imm32 290 ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) 291 # . . call 292 e8/call check-ints-equal/disp32 293 # . . discard args 294 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 295 # end 296 c3/return 297 298 test-read-returns-0-on-end-of-file: 299 # - read after hitting end-of-file, check that result is 0 300 # setup 301 # . clear-stream(_test-stream) 302 # . . push args 303 68/push _test-stream/imm32 304 # . . call 305 e8/call clear-stream/disp32 306 # . . discard args 307 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 308 # . clear-stream(_test-stream-buffer) 309 # . . push args 310 68/push _test-stream-buffer/imm32 311 # . . call 312 e8/call clear-stream/disp32 313 # . . discard args 314 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 315 # . write(_test-stream, "Ab") 316 # . . push args 317 68/push "Ab"/imm32 318 68/push _test-stream/imm32 319 # . . call 320 e8/call write/disp32 321 # . . discard args 322 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 323 # first read gets to end-of-file 324 # . read(_test-stream, _test-stream-buffer) 325 # . . push args 326 68/push _test-stream-buffer/imm32 327 68/push _test-stream/imm32 328 # . . call 329 e8/call read/disp32 330 # . . discard args 331 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 332 # second read 333 # . read(_test-stream, _test-stream-buffer) 334 # . . push args 335 68/push _test-stream-buffer/imm32 336 68/push _test-stream/imm32 337 # . . call 338 e8/call read/disp32 339 # . . discard args 340 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 341 # check-ints-equal(EAX, 0, msg) 342 # . . push args 343 68/push "F - test-read-returns-0-on-end-of-file"/imm32 344 68/push 0/imm32 345 50/push-EAX 346 # . . call 347 e8/call check-ints-equal/disp32 348 # . . discard args 349 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 350 # end 351 c3/return 352 353 == data 354 355 _test-stream-buffer: 356 # current write index 357 00 00 00 00 358 # current read index 359 00 00 00 00 360 # length (= 8) 361 08 00 00 00 362 # data 363 00 00 00 00 00 00 00 00 # 8 bytes 364 365 # . . vim:nowrap:textwidth=0