# rtdrv.s -- call runtime entries, with known arguments, and report what # happened. Assembled by tests/rt_exec.py into a complete .COM image. # # This replaces the Unicorn harness. The expectations did not change; only the # machine did. Everything here exists because qemu, unlike an emulator with a # Python API, gives you one thing: a serial port. So a check has to be # expressed as a program that reports on itself. # # The report is a framed record on the serial port: # # 01 02 03 # 04 # # The four control bytes occur in no expected output, and rt_exec.py REFUSES to # run a case whose expectation contains one -- so a marker can never be mistaken # for output, and a driver that emitted nothing is a parse failure rather than # a silent pass. # # Why one qemu boot per check instead of one boot for all of them: the runtime # keeps state that outlives a call -- the one-character pushback slot, and the # input cursor the boot sector's INT 21h shim owns. One boot for everything # would make each check depend on the ones before it, so a check could fail # because of its neighbour and a green run would prove less than it appears to. # A boot costs about a tenth of a second and 35 of them cost under four, which # buys every check a machine that has provably never executed anything. (The # input descriptor is still re-armed on every boot, because it lives in the # image and the image is the same bytes each time.) # # Two things about the 8086 shape the code below, and both were found by # running it rather than by reading it: # # * Every entry preserves BP and SP and NO other register. So the cursor # into the case record is BP: a loop counter in CX or DI would be destroyed # by the first call. The entry's own BP frame is pushed and restored, so # our BP comes back. # # * `movb n(%bp), %cl' leaves the HIGH byte of CX exactly as it was. Reading # the input length that way and then writing CX to INLEN handed the boot # sector's INT 21h shim a 16-bit length with an undefined top half. The # 8086 has no memory-to-memory move and no zero-extending byte load, so the # high half has to be cleared by hand, every time. # # * `dec' sets the sign flag from the RESULT, so `decw cnt / js done' runs the # body for cnt = 1 (1 -> 0, SF clear) and stops on the wrap (0 -> FFFF). # It reads like an off-by-one and is not; the comment at the loop says so # again because the next person will wonder. # # The image is assembled in ONE piece, here, so every address is a label or an # .org and nothing has to be agreed with Python in two places. What Python does # have to agree about -- the runtime's base, and the input descriptor -- is # checked rather than trusted: it assembles this file and then asserts that the # blob it got from RtProbe really is at RT_BASE in the bytes that came out. .set LOAD_BIAS, 0x0100 # a .COM lives at CS:0100 (that is the PSP) .set SOH, 0x01 .set STX, 0x02 .set ETX, 0x03 .set EOT, 0x04 # Restated from tests/exec/bootcom.s's layout block ON PURPOSE. A harness that # asks the boot sector where its input buffer is proves only that the two agree # with each other; two independent statements of the same layout have to be # written down, and a disagreement is a loud failure rather than a program that # is fed nothing. (Same reasoning, and the same lesson, as the hard-coded # RT_SZ that run_com_tests.sh used to carry: the descriptor has to be where the # shim looks, and the only way to be sure is to have written both down and # checked.) .set INLEN, 0x7000 # word: how many input bytes follow .set INCUR, 0x7002 # word: index into INBUF, NOT an address .set INBUF, 0x7004 # the bytes themselves # The case record, as rt_exec.py writes it. These offsets are the contract; the # two places that read them (this file, and case_bytes() over there) are # separate statements of it on purpose, and the cases only pass if both are # right. SLOT0 is the one that was wrong the first time round. .set OFF_DUMPLEN, 0 # word how many bytes to report .set OFF_DUMPADR, 2 # word image offset to report them from .set OFF_INLEN, 4 # byte bytes of input to hand the shim .set OFF_INBUF, 5 # byte inbuf[8] .set OFF_CASE, 13 # word the index printed in the header (4 hex digits) .set OFF_NCALLS, 15 # byte how many call slots follow .set SLOT0, 16 # call slots, 12 bytes each: # +0 word entry, +2 word arg A, # +4 byte flags, +5 pad, # +6 word arg B, +8 word CX, # +10 pad .set SLOT_SZ, 12 .set CASE_SZ, 64 # 16 + 4 slots # The image layout. RT_BASE is where the runtime blob goes; everything else is # above 0x0800 so that a runtime which grew by a few hundred bytes would not # silently run into the test scaffolding. rt_exec.py asserts # RT_BASE + blob size <= HDR, so the bound is a checked one. .set RT_BASE, 0x0190 .set HDR, 0x1100 .set FMTDEST, 0x1430 # 64-byte destination for real_fmt .set ASCSRC, 0x1470 # ASCIIZ sources for real_val .set DATA, 0x1110 .set DLEN, 32 # bytes of data area for initmem to clear .set STORE, 0x1140 # where a read entry's result is stored .set WSTR, 0x1150 # a string for the wrstr case: length + chars .set WSTR_EMPTY, 0x1158 # the same, with a length of 0 .set CASE, 0x1160 # the case record .set D_DUMPLEN, 0x11A0 # the driver's own three words .set D_DUMPADR, 0x11A2 .set D_CNT, 0x11A4 .set D_AX, 0x11A6 # last call's AX, for the comparison cases .set RDSTR_DST, 0x11B0 # destination for the rdstr cases .set SST_HELLO, 0x11C0 # a short source string for strstore .set SST_LONG, 0x11D0 # a source longer than a small max .set SST_EMPTY, 0x11E0 # an empty source string .set SST_DST, 0x11F0 # destination for the strstore cases .set SST_CAT, 0x1200 # destination for the strcat cases, with a # length to append to -- unlike SST_DST, # which strstore overwrites .set SST_CAT0, 0x1210 # ...and one that starts empty .set SST_WORLD, 0x1220 # a source distinct from SST_HELLO .set R_BLK1, 0x1230 .set R_BLK2, 0x1240 .set R_BLK3, 0x1250 .set R_BLK4, 0x1260 .set R_BLK5, 0x1270 .set R_BLK6, 0x1280 .set R_BLK7, 0x1290 .set R_BLK8, 0x12A0 .set R_BLK9, 0x12B0 .set R_BLK10, 0x12C0 .set R_BLK11, 0x12D0 .set R_BLK12, 0x12E0 .set R_BLK13, 0x12F0 .set R_BLK14, 0x1300 .set R_BLK15, 0x1310 .set R_BLK16, 0x1320 .set R_BLK17, 0x1330 .set R_BLK18, 0x1340 .set R_BLK19, 0x1350 .set R_BLK20, 0x1360 .set R_BLK21, 0x1370 .set R_BLK22, 0x1380 .set R_BLK23, 0x1390 .set R_BLK24, 0x13A0 .set R_BLK25, 0x13B0 .set R_BLK26, 0x13C0 .set R_BLK27, 0x13D0 .set R_BLK28, 0x13E0 .set R_BLK29, 0x13F0 .set R_BLK30, 0x1400 .set R_BLK31, 0x1410 .set R_BLK32, 0x1500 .set R_BLK33, 0x1510 .set R_BLK34, 0x1520 .set R_BLK35, 0x1530 .set R_BLK36, 0x1540 .set FILEBLK, 0x1A00 # the 132-byte file control block .set SETA, 0x1580 .set SETB, 0x1590 .set SETC, 0x15A0 .set SETD, 0x15B0 .set SET2A, 0x15C0 .set SET2B, 0x15D0 .set SET2D, 0x15E0 .code16 .text .globl _start # The entry JMP -- the same three bytes every image this compiler writes opens # with. bootcom.s jumps to 0000:0100, which is this instruction. It is # written as raw bytes rather than `jmp drive' because gas would shorten that to # a two-byte EB 00 short jump, and the byte it dropped would then be executed as # the first instruction of the driver. _start: .byte 0xE9 # JMP rel16 .word drive - _start - 3 # --------------------------------------------------------------- the driver drive: cld # 1. Poison the two places a check is allowed to look. STORE gets EEEH so # a read entry that stores nothing is visible as such, and the data # area gets AAH so initmem has something to clear. The storage below # is zeroed; the poison is what turns a zero into a RESULT. movw $STORE + LOAD_BIAS, %si movw $0xEEEE, %ax movw %ax, (%si) movw $DATA + LOAD_BIAS, %si movw $DLEN, %cx movb $0xAA, %al .Lpoison: movb %al, (%si) incw %si loop .Lpoison # BP is the case-record cursor, and it is the only register that survives a # call into the runtime, so everything the loop needs is read through it # or parked in the driver's own words first. movw $CASE + LOAD_BIAS, %bp # 2. Open the record: SOH, the case index as two hex digits, STX. movb $SOH, %al call putc movw OFF_CASE(%bp), %ax movw %ax, %bx movb %bh, %al call hexb movb %bl, %al call hexb movb $STX, %al call putc # 3. Arm the input. INCUR is an INDEX into INBUF, and zeroing it is what # makes this boot's input start at its first byte. CX is cleared # first: see the note at the top about the high byte of CX. xorw %cx, %cx movb OFF_INLEN(%bp), %cl movw %cx, INLEN movw $0, INCUR movw $CASE + OFF_INBUF + LOAD_BIAS, %si movw $INBUF, %di xorw %ax, %ax repe cmpsb # 4. The calls. dumplen and the report address are needed after the loop, # by which time BP has moved, so they are parked now; so is the count, # because CX is gone by the second iteration. movw OFF_DUMPLEN(%bp), %ax movw %ax, D_DUMPLEN + LOAD_BIAS movw OFF_DUMPADR(%bp), %ax movw %ax, D_DUMPADR + LOAD_BIAS xorw %ax, %ax movb OFF_NCALLS(%bp), %al movw %ax, D_CNT + LOAD_BIAS addw $SLOT0, %bp # BP now points AT the first call slot .Lcall: decw D_CNT + LOAD_BIAS # 1 -> 0 has SF clear, so the body runs once; jns .Lgo # 0 -> FFFF is what ends the loop jmp .Lcalldone .Lgo: call doslot addw $SLOT_SZ, %bp jmp .Lcall .Lcalldone: # 5. Close the output half and report the memory. A store that never # happened is the EEEH left in step 1, which is why it is there. movb $ETX, %al call putc movw D_DUMPLEN + LOAD_BIAS, %cx jcxz .Lnodump movw D_DUMPADR + LOAD_BIAS, %si addw $LOAD_BIAS, %si .Ldump: lodsb call hexb loop .Ldump .Lnodump: movb $EOT, %al call putc # 6. Exit. Reaching here IS the assertion that every entry returned: an # entry that hung produces no EOT, and the harness says so. movw $0x4C00, %ax # INT 21h AH=4Ch, AL=0 int $0x21 # doslot: call the entry described by the 12 bytes at BP, with the same # conventions Compiler.IoCall uses -- 16-bit argument(s) on the stack, popped # by the caller -- except for initmem, which takes the header in AX. # # Flags (the byte at +4): # 1 F_STACK push the word at +2; the caller pops one word. # 2 F_AX take the word at +2 in AX instead. # 4 F_INL the argument follows the call, not on the stack. # 8 F_STACK2 push the word at +6 FIRST and the word at +2 second, so +2 # lands at [BP+4] and +6 at [BP+6]; the caller pops two words. # strstore wants exactly that: dest at [BP+4], source at [BP+6]. # 16 F_CX load CX from the word at +8 before the call. rdstr and # strstore both take their maximum length in CX, a register the # record has no other way to reach, and neither may borrow it # from whatever the previous case left behind. # # The flags are re-read after the call because the entry has just used AX. doslot: movb 4(%bp), %al testb $64, %al jnz .Lfourstack testb $32, %al jnz .Lthreestack testb $8, %al jnz .Ltwostack testb $1, %al jz .Lnostack pushw 2(%bp) jmp .Lnostack .Ltwostack: pushw 6(%bp) # arg B: the source, ends at [BP+6] pushw 2(%bp) # arg A: the destination, ends at [BP+4] jmp .Lnostack .Lthreestack: pushw 8(%bp) # [BP+8] pushw 6(%bp) # [BP+6] pushw 2(%bp) # [BP+4] jmp .Lnostack .Lfourstack: pushw 10(%bp) # [BP+10] pushw 8(%bp) # [BP+8] pushw 6(%bp) # [BP+6] pushw 2(%bp) # [BP+4] .Lnostack: testb $2, %al jz .Lnoax movw 2(%bp), %ax .Lnoax: testb $16, %al jz .Lnocx movw 8(%bp), %cx .Lnocx: movw (%bp), %si # image-absolute, as RT_Entry reports it addw $LOAD_BIAS, %si # so the load bias is added here, not baked in testb $4, %al jnz .Linl call *%si movw %ax, D_AX + LOAD_BIAS # observable return value movb 4(%bp), %al testb $64, %al jnz .Lcleanfour testb $32, %al jnz .Lcleanthree testb $8, %al jnz .Lcleantwo testb $1, %al jz .Lnoclean addw $2, %sp # caller-cleaned .Lnoclean: ret .Lcleantwo: addw $4, %sp ret .Lcleanthree: addw $6, %sp ret .Lcleanfour: addw $8, %sp ret # wrtinl's argument is NOT a stack word: the entry reads a length byte and that # many characters from its own return address. That is the caller's half of # the contract, so it is written literally here -- the length byte and the # characters must be the bytes immediately after the call, which is why this is # a second copy of the instruction rather than a jump. The compiler's own # placement of the same three things is what the string-literal fixtures in # run_com_exec.py check; duplicating a variable one here would be a second # thing to keep right, and a check that cannot fail is not a check. .Linl: call *%si .byte 5 .byte 'h', 'e', 'l', 'l', 'o' ret # putc: AL to the serial port. No status polling -- qemu's 16550 always accepts, # and a poll that never went ready would hang the harness instead of failing it. putc: pushw %ax movw $0x03f8, %dx outb %al, %dx popw %ax ret # hexb: AL as two uppercase hex digits, high nibble first. # # The 8086 has no `SHR r/m16, imm8' and no shift-by-four, so the high nibble # comes from four one-bit shifts through CL. Both facts have to be written # down: gas assembles `shr $4, %ah' without complaint and rejects `shrw $1, %ah' # outright, so the only thing standing between this file and an image that # assembles cleanly and dies on a real 8086 is refusing the modern encodings. hexb: pushw %ax pushw %cx movb %al, %ah movb $1, %cl shrw %cl, %ax shrw %cl, %ax shrw %cl, %ax shrw %cl, %ax popw %cx call putdig popw %ax andb $0x0f, %al movb %al, %ah call putdig ret # putdig: the nibble in AH as one character, moved into AL so that setting AH for # INT 21h AH=02h cannot overwrite the digit it is about to print. putdig: movb %ah, %al cmpb $10, %al jb .Ldigit addb $0x37, %al # 0Ah + 37h = 'A' jmp .Lemit .Ldigit: addb $0x30, %al # '0' .Lemit: movb $2, %ah int $0x21 ret # --------------------------------------------------------------- the image # The runtime blob, from tests/RtProbe.mod built at RT_BASE. The gap is the # checked bound: rt_exec.py asserts that 0x0180..RT_BASE is still all zeros, so # a driver that outgrew its space fails the suite instead of overwriting the # library it is testing. .org RT_BASE .incbin "rtblob.bin" # The program header initmem is handed: +0 flag, +2 code, +4 data base, +6 data # end. Built HERE, from the same DATA and DLEN the poison loop uses, because # the compiler's own header layout is a separate claim with its own check and # this harness is not that check. The two fields initmem READS are memory # addresses, so they carry the bias; the caller passes AX the header's memory # address too, which is the one thing rt_exec.py gets right only by saying so # here and in case_bytes(). .org HDR HDR: .word 1 # hdrFlag .word 0 # hdrCS, unused by initmem .word DATA + LOAD_BIAS # hdrDS = data base .word DATA + DLEN + LOAD_BIAS # hdrHeap = data end .org DATA .zero DLEN # poisoned to AAH by the driver .org STORE .zero 2 # poisoned to EEEH by the driver # A string for the wrstr case: a length byte and that many characters, which is # the layout a compiled `string [n]' variable has. It sits above the poisoned # area so the poison never touches it, and rt_exec.py restates its address - the # two must agree or the case reads whatever bytes are there and prints them. .org WSTR .byte 5 .byte 'h', 'e', 'l', 'l', 'o' .org WSTR_EMPTY .byte 0 # a length of 0: an empty string .org CASE .incbin "rtcase.bin" .org D_DUMPLEN .zero 6 # D_DUMPLEN, D_DUMPADR, D_CNT # --- strings for the string entries ---------------------------------------- # A compiled `string [n]' variable is a length byte and n characters, and that # is the layout rdstr writes and strstore reads. Each DESTINATION is # pre-filled with 0xEE so a byte the entry does NOT write is visibly still # there -- that is how "the tail past the new length is left untouched" becomes # a checked claim rather than a comment. The sources are distinct addresses so # one image can offer a short string, one longer than a small max, and an empty # one, without any case depending on a previous case's leftovers. .org RDSTR_DST .fill 16, 1, 0xEE .org SST_HELLO .byte 5, 'h', 'e', 'l', 'l', 'o' .org SST_LONG .byte 8, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' .org SST_EMPTY .byte 0 # a length of 0: an empty string .org SST_DST .fill 16, 1, 0xEE # strcat reads the DESTINATION's length, so its destinations are seeded with a # real string rather than 0xEE -- and 0xEE would in fact be a 238-character # length, which is exactly the "string too long" error the entry has to raise. # SST_CAT holds `hello', SST_CAT0 is empty, and SST_WORLD is a source that is # not SST_HELLO so an append is visible as an append and not as a coincidence. .org SST_CAT .byte 5, 'h', 'e', 'l', 'l', 'o' .fill 10, 1, 0xEE .org SST_CAT0 .byte 0 # a length of 0: nothing to append to .fill 15, 1, 0xEE .org SST_WORLD .byte 5, 'w', 'o', 'r', 'l', 'd' # 12-byte MBF blocks for real_add/real_sub: a at +0, b at +6. .org R_BLK1 .byte 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK2 .byte 0x82, 0x00, 0x00, 0x00, 0x00, 0x40, 0x83, 0x00, 0x00, 0x00, 0x00, 0x20 .org R_BLK3 .byte 0x83, 0x00, 0x00, 0x00, 0x00, 0x20, 0x82, 0x00, 0x00, 0x00, 0x00, 0x40 .org R_BLK4 .byte 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK5 .byte 0x8A, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK6 .byte 0x82, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK7 .byte 0x86, 0x00, 0x00, 0x00, 0x00, 0x28, 0x8A, 0x00, 0x00, 0x00, 0x00, 0x7A .org R_BLK8 .byte 0x8F, 0x00, 0x00, 0x00, 0xFE, 0x7F, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK9 .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x20 .org R_BLK10 .byte 0x82, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x82, 0x00, 0x00, 0x00, 0x00, 0x80 # Division pairs for real_div. .org R_BLK11 .byte 0x83, 0x00, 0x00, 0x00, 0x00, 0x40, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK12 .byte 0x84, 0x00, 0x00, 0x00, 0x00, 0x20, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK13 .byte 0x84, 0x00, 0x00, 0x00, 0x00, 0x70, 0x82, 0x00, 0x00, 0x00, 0x00, 0x40 .org R_BLK14 .byte 0x8A, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK15 .byte 0x83, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK16 .byte 0x83, 0x00, 0x00, 0x00, 0x00, 0x40, 0x82, 0x00, 0x00, 0x00, 0x00, 0x80 .org R_BLK17 .byte 0x83, 0x00, 0x00, 0x00, 0x00, 0xC0, 0x82, 0x00, 0x00, 0x00, 0x00, 0x80 .org R_BLK18 .byte 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x83, 0x00, 0x00, 0x00, 0x00, 0x20 .org R_BLK19 .byte 0x83, 0x00, 0x00, 0x00, 0x00, 0x20, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK20 .byte 0x86, 0x00, 0x00, 0x00, 0x00, 0x28, 0x83, 0x00, 0x00, 0x00, 0x00, 0x40 # Fractional blocks: real_int truncates `a'; real_cmp compares the pair. .org R_BLK21 .byte 0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0x6C, 0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0x6C .org R_BLK22 .byte 0x82, 0x9A, 0x99, 0x99, 0x99, 0x79, 0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0x6C .org R_BLK23 .byte 0x82, 0xCD, 0xCC, 0xCC, 0xCC, 0xEC, 0x82, 0x9A, 0x99, 0x99, 0x99, 0xF9 .org R_BLK24 .byte 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK25 .byte 0x80, 0x00, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK26 .byte 0x8E, 0x00, 0x00, 0x00, 0xE7, 0x40, 0x8E, 0x00, 0x00, 0x00, 0xE6, 0x40 .org R_BLK27 .byte 0x82, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK28 .byte 0x82, 0x00, 0x00, 0x00, 0x00, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK29 .byte 0x80, 0x0A, 0xD7, 0xA3, 0x70, 0x7D, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK30 .byte 0x81, 0x33, 0x33, 0x33, 0x33, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 # 2^40: above reint's 0A8h early return, so it must come back unchanged. .org R_BLK31 .byte 0xA9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 # ASCIIZ sources for real_val. .org ASCSRC .ascii "3.5\x00" .ascii "-2.25\x00" .ascii "1E3\x00" .ascii "0.5\x00" .ascii "12345.75\x00" .ascii "3.5x\x00" .ascii "1E\x00" .ascii "1E40\x00" .ascii "\x00" .ascii ".5\x00" .ascii "1.2.3\x00" .ascii "35\x00" .ascii "5E-1\x00" .ascii "5.\x00" .ascii "5.0\x00" .ascii "12345\x00" .ascii "1234575\x00" .ascii "1234.5\x00" .ascii "1000\x00" .ascii "123\x00" .ascii "1234\x00" .ascii "12340\x00" .ascii "12341\x00" .ascii "12341E0\x00" .ascii "12345E0\x00" # add-core probes .org R_BLK32 .byte 0x8E, 0x00, 0x00, 0x00, 0xD0, 0x40, 0x83, 0x00, 0x00, 0x00, 0x00, 0x20 .org R_BLK33 .byte 0x8E, 0x00, 0x00, 0x00, 0xD0, 0x40, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00 # multiplication-by-one probes .org R_BLK34 .byte 0x8E, 0x00, 0x00, 0x00, 0xD4, 0x40, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK35 .byte 0x8E, 0x00, 0x00, 0x00, 0xE4, 0x40, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00 .org R_BLK36 .byte 0x8E, 0x00, 0x00, 0x00, 0xD0, 0x40, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00 # Set buffers for set_op/set_cmp/set_copy. .org SETA .byte 0x0A, 0x00, 0x00, 0x00 .org SETB .byte 0x18, 0x00, 0x00, 0x00 .org SETC .byte 0x02, 0x00, 0x00, 0x00 .org SETD .byte 0x00, 0x00, 0x00, 0x00 .org SET2A .byte 0x0A, 0x0F .org SET2B .byte 0x18, 0x03 .org SET2D .byte 0x00, 0x00 # File control block for the file_assign case. .org FILEBLK .zero 132