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 operand 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 # clear-stream(_test-stream) 183 # push args 184 68/push _test-stream/imm32 185 # call 186 e8/call clear-stream/disp32 187 # discard args 188 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 189 # clear-stream(_test-stream-buffer) 190 # push args 191 68/push _test-stream-buffer/imm32 192 # call 193 e8/call clear-stream/disp32 194 # discard args 195 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 196 # write(_test-stream, "Ab") 197 # push args 198 68/push "Ab"/imm32 199 68/push _test-stream/imm32 200 # call 201 e8/call write/disp32 202 # discard args 203 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 204 # read(_test-stream, _test-stream-buffer) 205 # push args 206 68/push _test-stream-buffer/imm32 207 68/push _test-stream/imm32 208 # call 209 e8/call read/disp32 210 # discard args 211 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 212 # check-ints-equal(EAX, 2) 213 # push args 214 68/push "F - test-read-single: return EAX"/imm32 215 68/push 2/imm32 216 50/push-EAX 217 # call 218 e8/call check-ints-equal/disp32 219 # discard args 220 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 221 # check-ints-equal(*_test-stream-buffer->data, 41/A 62/b 00 00, msg) 222 # push args 223 68/push "F - test-read-single"/imm32 224 68/push 0x006241/imm32/Ab 225 # push *_test-stream-buffer->data 226 b8/copy-to-EAX _test-stream-buffer/imm32 227 ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) 228 # call 229 e8/call check-ints-equal/disp32 230 # discard args 231 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 232 # end 233 c3/return 234 235 test-read-is-stateful: 236 ## make two consecutive reads, check that their results are appended 237 # clear-stream(_test-stream) 238 # push args 239 68/push _test-stream/imm32 240 # call 241 e8/call clear-stream/disp32 242 # discard args 243 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 244 # clear-stream(_test-stream-buffer) 245 # push args 246 68/push _test-stream-buffer/imm32 247 # call 248 e8/call clear-stream/disp32 249 # discard args 250 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 251 # write(_test-stream, "C") 252 # push args 253 68/push "C"/imm32 254 68/push _test-stream/imm32 255 # call 256 e8/call write/disp32 257 # discard args 258 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 259 # read(_test-stream, _test-stream-buffer) 260 # push args 261 68/push _test-stream-buffer/imm32 262 68/push _test-stream/imm32 263 # call 264 e8/call read/disp32 265 # discard args 266 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 267 # write(_test-stream, "D") 268 # push args 269 68/push "D"/imm32 270 68/push _test-stream/imm32 271 # call 272 e8/call write/disp32 273 # discard args 274 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 275 # read(_test-stream, _test-stream-buffer) 276 # push args 277 68/push _test-stream-buffer/imm32 278 68/push _test-stream/imm32 279 # call 280 e8/call read/disp32 281 # discard args 282 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 283 # check-ints-equal(*_test-stream-buffer->data, 43/C 44/D 00 00, msg) 284 # push args 285 68/push "F - test-read-is-stateful"/imm32 286 68/push 0x00004443/imm32/C-D 287 # push *_test-stream-buffer->data 288 b8/copy-to-EAX _test-stream-buffer/imm32 289 ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) 290 # call 291 e8/call check-ints-equal/disp32 292 # discard args 293 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 294 # end 295 c3/return 296 297 test-read-returns-0-on-end-of-file: 298 ## read after hitting end-of-file, check that result is 0 299 # clear-stream(_test-stream) 300 # push args 301 68/push _test-stream/imm32 302 # call 303 e8/call clear-stream/disp32 304 # discard args 305 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 306 # clear-stream(_test-stream-buffer) 307 # push args 308 68/push _test-stream-buffer/imm32 309 # call 310 e8/call clear-stream/disp32 311 # discard args 312 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 313 # write(_test-stream, "Ab") 314 # push args 315 68/push "Ab"/imm32 316 68/push _test-stream/imm32 317 # call 318 e8/call write/disp32 319 # discard args 320 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 321 ## first read gets to end-of-file 322 # read(_test-stream, _test-stream-buffer) 323 # push args 324 68/push _test-stream-buffer/imm32 325 68/push _test-stream/imm32 326 # call 327 e8/call read/disp32 328 # discard args 329 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 330 ## second read 331 # read(_test-stream, _test-stream-buffer) 332 # push args 333 68/push _test-stream-buffer/imm32 334 68/push _test-stream/imm32 335 # call 336 e8/call read/disp32 337 # discard args 338 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 339 # check-ints-equal(EAX, 0) 340 # push args 341 68/push "F - test-read-returns-0-on-end-of-file"/imm32 342 68/push 0/imm32 343 50/push-EAX 344 # call 345 e8/call check-ints-equal/disp32 346 # discard args 347 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 348 # end 349 c3/return 350 351 == data 352 353 _test-stream-buffer: 354 # current write index 355 00 00 00 00 356 # current read index 357 00 00 00 00 358 # length (= 8) 359 08 00 00 00 360 # data 361 00 00 00 00 00 00 00 00 # 8 bytes 362 363 # vim:nowrap:textwidth=0