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 read: # f : fd or (address stream), s : (address stream) -> num-bytes-read/EAX 49 # . prolog 50 55/push-EBP 51 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP 52 # if (f < 0x08000000) return _read(f, s) # f can't be a user-mode address, so treat it as a kernel file descriptor 53 81 7/subop/compare 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 0x08000000/imm32 # compare *(EBP+8) 54 7d/jump-if-greater-or-equal $read:fake/disp8 55 # . . push args 56 ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) 57 ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) 58 # . . call 59 e8/call _read/disp32 60 # . . discard args 61 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 62 # return 63 eb/jump $read:end/disp8 64 $read:fake: 65 # otherwise, treat 'f' as a stream to scan from 66 # . save registers 67 56/push-ESI 68 57/push-EDI 69 # ESI = f 70 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI 71 # EDI = s 72 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 7/r32/EDI 0xc/disp8 . # copy *(EBP+12) to ESI 73 # EAX = _append-4(out = &s->data[s->write], outend = &s->data[s->length], 74 # in = &f->data[f->read], inend = &f->data[f->write]) 75 # . . push &f->data[f->write] 76 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX 77 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 78 50/push-EAX 79 # . . push &f->data[f->read] 80 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # copy *(ESI+4) to EAX 81 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 82 50/push-EAX 83 # . . push &s->data[s->length] 84 8b/copy 1/mod/*+disp8 7/rm32/EDI . . . 0/r32/EAX 8/disp8 . # copy *(EDI+8) to EAX 85 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 86 50/push-EAX 87 # . . push &s->data[s->write] 88 8b/copy 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # copy *EDI to EAX 89 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 90 50/push-EAX 91 # . . call 92 e8/call _append-4/disp32 93 # . . discard args 94 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x10/imm32 # add to ESP 95 # s->write += EAX 96 01/add 0/mod/indirect 7/rm32/EDI . . . 0/r32/EAX . . # add EAX to *EDI 97 # f->read += EAX 98 01/add 1/mod/*+disp8 6/rm32/ESI . . . 0/r32/EAX 4/disp8 . # add EAX to *(ESI+4) 99 # . restore registers 100 5f/pop-to-EDI 101 5e/pop-to-ESI 102 $read:end: 103 # . epilog 104 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP 105 5d/pop-to-EBP 106 c3/return 107 108 # - helpers 109 110 # idea: a clear-if-empty method on streams that clears only if f->read == f->write 111 # Unclear how I'd use it, though. Callers seem to need the check anyway. 112 # Maybe a better helper would be 'empty-stream?' 113 114 _read: # fd : int, s : (address stream) -> num-bytes-read/EAX 115 # . prolog 116 55/push-EBP 117 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP 118 # . save registers 119 51/push-ECX 120 52/push-EDX 121 53/push-EBX 122 56/push-ESI 123 # ESI = s 124 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 0xc/disp8 . # copy *(EBP+12) to ESI 125 # EAX = s->write 126 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX 127 # EDX = s->length 128 8b/copy 1/mod/*+disp8 6/rm32/ESI . . . 2/r32/EDX 8/disp8 . # copy *(ESI+8) to EDX 129 # syscall(read, fd, &s->data[s->write], s->length - s->write) 130 # . . fd : EBX 131 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 3/r32/EBX 8/disp8 . # copy *(EBP+8) to EBX 132 # . . data : ECX = &s->data[s->write] 133 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 134 # . . size : EDX = s->length - s->write 135 29/subtract 3/mod/direct 2/rm32/EDX . . . 0/r32/EAX . . # subtract EAX from EDX 136 # . . syscall 137 b8/copy-to-EAX 3/imm32/read 138 cd/syscall 0x80/imm8 139 # add the result EAX to s->write 140 01/add 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # add EAX to *ESI 141 $_read:end: 142 # . restore registers 143 5e/pop-to-ESI 144 5b/pop-to-EBX 145 5a/pop-to-EDX 146 59/pop-to-ECX 147 # . epilog 148 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP 149 5d/pop-to-EBP 150 c3/return 151 152 # Two options: 153 # 1 (what we have above): 154 # ECX = s 155 # EAX = s->write 156 # EDX = s->length 157 # # syscall 158 # ECX = lea ECX+EAX+12 159 # EDX = sub EDX EAX 160 # 161 # 2: 162 # ECX = s 163 # EDX = s->length 164 # ECX = &s->data 165 # # syscall 166 # ECX = add ECX, s->write 167 # EDX = sub EDX, s->write 168 # 169 # Not much to choose between the two? Option 2 performs a duplicate load to 170 # use one less register, but doesn't increase the amount of spilling (ECX 171 # and EDX must be used, and EAX must be clobbered anyway). 172 173 # - tests 174 175 test-read-single: 176 # - write a single character into _test-stream, then read from its buffered-file 177 # clear-stream(_test-stream) 178 # . . push args 179 68/push _test-stream/imm32 180 # . . call 181 e8/call clear-stream/disp32 182 # . . discard args 183 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 184 # clear-stream(_test-tmp-stream) 185 # . . push args 186 68/push _test-tmp-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 # write(_test-stream, "Ab") 192 # . . push args 193 68/push "Ab"/imm32 194 68/push _test-stream/imm32 195 # . . call 196 e8/call write/disp32 197 # . . discard args 198 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 199 # read(_test-stream, _test-tmp-stream) 200 # . . push args 201 68/push _test-tmp-stream/imm32 202 68/push _test-stream/imm32 203 # . . call 204 e8/call read/disp32 205 # . . discard args 206 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 207 # check-ints-equal(EAX, 2, msg) 208 # . . push args 209 68/push "F - test-read-single: return EAX"/imm32 210 68/push 2/imm32 211 50/push-EAX 212 # . . call 213 e8/call check-ints-equal/disp32 214 # . . discard args 215 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 216 # check-stream-equal(_test-tmp-stream, "Ab", msg) 217 # . . push args 218 68/push "F - test-read-single"/imm32 219 68/push "Ab"/imm32 220 68/push _test-tmp-stream/imm32 221 # . . call 222 e8/call check-stream-equal/disp32 223 # . . discard args 224 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 225 # end 226 c3/return 227 228 test-read-is-stateful: 229 # - make two consecutive reads, check that their results are appended 230 # clear-stream(_test-stream) 231 # . . push args 232 68/push _test-stream/imm32 233 # . . call 234 e8/call clear-stream/disp32 235 # . . discard args 236 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 237 # clear-stream(_test-tmp-stream) 238 # . . push args 239 68/push _test-tmp-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 # write(_test-stream, "C") 245 # . . push args 246 68/push "C"/imm32 247 68/push _test-stream/imm32 248 # . . call 249 e8/call write/disp32 250 # . . discard args 251 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 252 # read(_test-stream, _test-tmp-stream) 253 # . . push args 254 68/push _test-tmp-stream/imm32 255 68/push _test-stream/imm32 256 # . . call 257 e8/call read/disp32 258 # . . discard args 259 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 260 # write(_test-stream, "D") 261 # . . push args 262 68/push "D"/imm32 263 68/push _test-stream/imm32 264 # . . call 265 e8/call write/disp32 266 # . . discard args 267 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 268 # read(_test-stream, _test-tmp-stream) 269 # . . push args 270 68/push _test-tmp-stream/imm32 271 68/push _test-stream/imm32 272 # . . call 273 e8/call read/disp32 274 # . . discard args 275 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 276 # check-stream-equal(_test-tmp-stream, "CD", msg) 277 # . . push args 278 68/push "F - test-read-is-stateful"/imm32 279 68/push "CD"/imm32 280 68/push _test-tmp-stream/imm32 281 # . . call 282 e8/call check-stream-equal/disp32 283 # . . discard args 284 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 285 # end 286 c3/return 287 288 test-read-returns-0-on-end-of-file: 289 # - read after hitting end-of-file, check that result is 0 290 # setup 291 # . clear-stream(_test-stream) 292 # . . push args 293 68/push _test-stream/imm32 294 # . . call 295 e8/call clear-stream/disp32 296 # . . discard args 297 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 298 # . clear-stream(_test-tmp-stream) 299 # . . push args 300 68/push _test-tmp-stream/imm32 301 # . . call 302 e8/call clear-stream/disp32 303 # . . discard args 304 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 305 # . write(_test-stream, "Ab") 306 # . . push args 307 68/push "Ab"/imm32 308 68/push _test-stream/imm32 309 # . . call 310 e8/call write/disp32 311 # . . discard args 312 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 313 # first read gets to end-of-file 314 # . read(_test-stream, _test-tmp-stream) 315 # . . push args 316 68/push _test-tmp-stream/imm32 317 68/push _test-stream/imm32 318 # . . call 319 e8/call read/disp32 320 # . . discard args 321 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 322 # second read 323 # . read(_test-stream, _test-tmp-stream) 324 # . . push args 325 68/push _test-tmp-stream/imm32 326 68/push _test-stream/imm32 327 # . . call 328 e8/call read/disp32 329 # . . discard args 330 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 331 # check-ints-equal(EAX, 0, msg) 332 # . . push args 333 68/push "F - test-read-returns-0-on-end-of-file"/imm32 334 68/push 0/imm32 335 50/push-EAX 336 # . . call 337 e8/call check-ints-equal/disp32 338 # . . discard args 339 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 340 # end 341 c3/return 342 343 == data 344 345 _test-tmp-stream: 346 # current write index 347 0/imm32 348 # current read index 349 0/imm32 350 # length 351 8/imm32 352 # data 353 00 00 00 00 00 00 00 00 # 8 bytes 354 355 # . . vim:nowrap:textwidth=0