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 $_read:end: 149 # . restore registers 150 5e/pop-to-ESI 151 5b/pop-to-EBX 152 5a/pop-to-EDX 153 59/pop-to-ECX 154 # . epilog 155 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP 156 5d/pop-to-EBP 157 c3/return 158 159 # Two options: 160 # 1 (what we have above): 161 # ECX = s 162 # EAX = s->write 163 # EDX = s->length 164 # # syscall 165 # ECX = lea ECX+EAX+12 166 # EDX = sub EDX EAX 167 # 168 # 2: 169 # ECX = s 170 # EDX = s->length 171 # ECX = &s->data 172 # # syscall 173 # ECX = add ECX, s->write 174 # EDX = sub EDX, s->write 175 # 176 # Not much to choose between the two? Option 2 performs a duplicate load to 177 # use one less register, but doesn't increase the amount of spilling (ECX 178 # and EDX must be used, and EAX must be clobbered anyway). 179 180 # - tests 181 182 test-read-single: 183 # - write a single character into _test-stream, then read from its buffered-file 184 # clear-stream(_test-stream) 185 # . . push args 186 68/push _test-stream/imm32 187 # . . call 188 e8/call clear-stream/disp32 189 # . . discard args 190 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 191 # clear-stream(_test-stream-buffer) 192 # . . push args 193 68/push _test-stream-buffer/imm32 194 # . . call 195 e8/call clear-stream/disp32 196 # . . discard args 197 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 198 # write(_test-stream, "Ab") 199 # . . push args 200 68/push "Ab"/imm32 201 68/push _test-stream/imm32 202 # . . call 203 e8/call write/disp32 204 # . . discard args 205 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 206 # read(_test-stream, _test-stream-buffer) 207 # . . push args 208 68/push _test-stream-buffer/imm32 209 68/push _test-stream/imm32 210 # . . call 211 e8/call read/disp32 212 # . . discard args 213 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 214 # check-ints-equal(EAX, 2, msg) 215 # . . push args 216 68/push "F - test-read-single: return EAX"/imm32 217 68/push 2/imm32 218 50/push-EAX 219 # . . call 220 e8/call check-ints-equal/disp32 221 # . . discard args 222 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 223 # check-ints-equal(*_test-stream-buffer->data, 41/A 62/b 00 00, msg) 224 # . . push args 225 68/push "F - test-read-single"/imm32 226 68/push 0x006241/imm32/Ab 227 # . . push *_test-stream-buffer->data 228 b8/copy-to-EAX _test-stream-buffer/imm32 229 ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) 230 # . . call 231 e8/call check-ints-equal/disp32 232 # . . discard args 233 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 234 # end 235 c3/return 236 237 test-read-is-stateful: 238 # - make two consecutive reads, check that their results are appended 239 # clear-stream(_test-stream) 240 # . . push args 241 68/push _test-stream/imm32 242 # . . call 243 e8/call clear-stream/disp32 244 # . . discard args 245 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 246 # clear-stream(_test-stream-buffer) 247 # . . push args 248 68/push _test-stream-buffer/imm32 249 # . . call 250 e8/call clear-stream/disp32 251 # . . discard args 252 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 253 # write(_test-stream, "C") 254 # . . push args 255 68/push "C"/imm32 256 68/push _test-stream/imm32 257 # . . call 258 e8/call write/disp32 259 # . . discard args 260 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 261 # read(_test-stream, _test-stream-buffer) 262 # . . push args 263 68/push _test-stream-buffer/imm32 264 68/push _test-stream/imm32 265 # . . call 266 e8/call read/disp32 267 # . . discard args 268 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 269 # write(_test-stream, "D") 270 # . . push args 271 68/push "D"/imm32 272 68/push _test-stream/imm32 273 # . . call 274 e8/call write/disp32 275 # . . discard args 276 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 277 # read(_test-stream, _test-stream-buffer) 278 # . . push args 279 68/push _test-stream-buffer/imm32 280 68/push _test-stream/imm32 281 # . . call 282 e8/call read/disp32 283 # . . discard args 284 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 285 # check-ints-equal(*_test-stream-buffer->data, 43/C 44/D 00 00, msg) 286 # . . push args 287 68/push "F - test-read-is-stateful"/imm32 288 68/push 0x00004443/imm32/C-D 289 # . . push *_test-stream-buffer->data 290 b8/copy-to-EAX _test-stream-buffer/imm32 291 ff 6/subop/push 1/mod/*+disp8 0/rm32/EAX . . . . 0xc/disp8 . # push *(EAX+12) 292 # . . call 293 e8/call check-ints-equal/disp32 294 # . . discard args 295 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 296 # end 297 c3/return 298 299 test-read-returns-0-on-end-of-file: 300 # - read after hitting end-of-file, check that result is 0 301 # setup 302 # . clear-stream(_test-stream) 303 # . . push args 304 68/push _test-stream/imm32 305 # . . call 306 e8/call clear-stream/disp32 307 # . . discard args 308 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 309 # . clear-stream(_test-stream-buffer) 310 # . . push args 311 68/push _test-stream-buffer/imm32 312 # . . call 313 e8/call clear-stream/disp32 314 # . . discard args 315 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 316 # . write(_test-stream, "Ab") 317 # . . push args 318 68/push "Ab"/imm32 319 68/push _test-stream/imm32 320 # . . call 321 e8/call write/disp32 322 # . . discard args 323 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 324 # first read gets to end-of-file 325 # . read(_test-stream, _test-stream-buffer) 326 # . . push args 327 68/push _test-stream-buffer/imm32 328 68/push _test-stream/imm32 329 # . . call 330 e8/call read/disp32 331 # . . discard args 332 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 333 # second read 334 # . read(_test-stream, _test-stream-buffer) 335 # . . push args 336 68/push _test-stream-buffer/imm32 337 68/push _test-stream/imm32 338 # . . call 339 e8/call read/disp32 340 # . . discard args 341 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 342 # check-ints-equal(EAX, 0, msg) 343 # . . push args 344 68/push "F - test-read-returns-0-on-end-of-file"/imm32 345 68/push 0/imm32 346 50/push-EAX 347 # . . call 348 e8/call check-ints-equal/disp32 349 # . . discard args 350 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 351 # end 352 c3/return 353 354 == data 355 356 _test-stream-buffer: 357 # current write index 358 00 00 00 00 359 # current read index 360 00 00 00 00 361 # length (= 8) 362 08 00 00 00 363 # data 364 00 00 00 00 00 00 00 00 # 8 bytes 365 366 # . . vim:nowrap:textwidth=0