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/exit 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 5/rm32/EBP . . . . 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 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) 64 ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 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 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI 78 # EDI = s 79 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 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 5/rm32/EBP . . . 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 5/rm32/EBP . . . 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-stream-equal(_test-stream-buffer, "Ab", msg) 224 # . . push args 225 68/push "F - test-read-single"/imm32 226 68/push "Ab"/imm32 227 68/push _test-stream-buffer/imm32 228 # . . call 229 e8/call check-stream-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-stream-equal(_test-stream-buffer, "CD", msg) 284 # . . push args 285 68/push "F - test-read-is-stateful"/imm32 286 68/push "CD"/imm32 287 68/push _test-stream-buffer/imm32 288 # . . call 289 e8/call check-stream-equal/disp32 290 # . . discard args 291 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 292 # end 293 c3/return 294 295 test-read-returns-0-on-end-of-file: 296 # - read after hitting end-of-file, check that result is 0 297 # setup 298 # . clear-stream(_test-stream) 299 # . . push args 300 68/push _test-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 # . clear-stream(_test-stream-buffer) 306 # . . push args 307 68/push _test-stream-buffer/imm32 308 # . . call 309 e8/call clear-stream/disp32 310 # . . discard args 311 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP 312 # . write(_test-stream, "Ab") 313 # . . push args 314 68/push "Ab"/imm32 315 68/push _test-stream/imm32 316 # . . call 317 e8/call write/disp32 318 # . . discard args 319 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 320 # first read gets to end-of-file 321 # . read(_test-stream, _test-stream-buffer) 322 # . . push args 323 68/push _test-stream-buffer/imm32 324 68/push _test-stream/imm32 325 # . . call 326 e8/call read/disp32 327 # . . discard args 328 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 329 # second read 330 # . read(_test-stream, _test-stream-buffer) 331 # . . push args 332 68/push _test-stream-buffer/imm32 333 68/push _test-stream/imm32 334 # . . call 335 e8/call read/disp32 336 # . . discard args 337 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP 338 # check-ints-equal(EAX, 0, msg) 339 # . . push args 340 68/push "F - test-read-returns-0-on-end-of-file"/imm32 341 68/push 0/imm32 342 50/push-EAX 343 # . . call 344 e8/call check-ints-equal/disp32 345 # . . discard args 346 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP 347 # end 348 c3/return 349 350 == data 351 352 _test-stream-buffer: 353 # current write index 354 00 00 00 00 355 # current read index 356 00 00 00 00 357 # length (= 8) 358 08 00 00 00 359 # data 360 00 00 00 00 00 00 00 00 # 8 bytes 361 362 # . . vim:nowrap:textwidth=0