From 7a22a21995001cbdf72c5e3b92f52c9abcee3202 Mon Sep 17 00:00:00 2001 From: Kartik Agaram Date: Fri, 8 Mar 2019 17:15:19 -0800 Subject: 4996 - back on pack.subx Yet another redrawing of responsibilities between convert and its helpers. In the process I discovered a bug in `write-stream-buffered` which ended up taking me through a detour to extract `browse_trace` into its own tool. It turns out just having long buffers is enough to need browse_trace. Simple operations like clearing a stream swamp a flat view of the trace. --- subx/012elf.cc | 6 +- subx/058stream-equal.subx | 3 +- subx/071read-line.subx | 94 ++----- subx/072slice.subx | 2 + subx/apps/crenshaw2-1 | Bin 18028 -> 17760 bytes subx/apps/crenshaw2-1b | Bin 18587 -> 18319 bytes subx/apps/factorial | Bin 16946 -> 16678 bytes subx/apps/handle | Bin 17721 -> 17453 bytes subx/apps/hex | Bin 21007 -> 20739 bytes subx/apps/pack | Bin 21184 -> 21169 bytes subx/apps/pack.subx | 616 ++++++++++++++++++++++++++++------------------ 11 files changed, 412 insertions(+), 309 deletions(-) diff --git a/subx/012elf.cc b/subx/012elf.cc index c9bbfc70..0f058504 100644 --- a/subx/012elf.cc +++ b/subx/012elf.cc @@ -145,10 +145,10 @@ void dump_stack() { ostringstream out; trace(Callstack_depth+1, "run") << "stack:" << end(); for (uint32_t a = AFTER_STACK-4; a > Reg[ESP].u; a -= 4) - trace(Callstack_depth+1, "run") << " 0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end(); - trace(Callstack_depth+1, "run") << " 0x" << HEXWORD << Reg[ESP].u << " => 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << " <=== ESP" << end(); + trace(Callstack_depth+2, "run") << " 0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end(); + trace(Callstack_depth+2, "run") << " 0x" << HEXWORD << Reg[ESP].u << " => 0x" << HEXWORD << read_mem_u32(Reg[ESP].u) << " <=== ESP" << end(); for (uint32_t a = Reg[ESP].u-4; a > Reg[ESP].u-40; a -= 4) - trace(Callstack_depth+1, "run") << " 0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end(); + trace(Callstack_depth+2, "run") << " 0x" << HEXWORD << a << " => 0x" << HEXWORD << read_mem_u32(a) << end(); } inline uint32_t u32_in(uint8_t* p) { diff --git a/subx/058stream-equal.subx b/subx/058stream-equal.subx index 88a09d9c..4716db39 100644 --- a/subx/058stream-equal.subx +++ b/subx/058stream-equal.subx @@ -24,8 +24,9 @@ stream-data-equal?: # f : (address stream), s : (address string) -> EAX : boole 57/push-EDI # ESI = f 8b/copy 1/mod/*+disp8 5/rm32/EBP . . . 6/r32/ESI 8/disp8 . # copy *(EBP+8) to ESI - # max/EDX = f->data + f->write + # EAX = f->write 8b/copy 0/mod/indirect 6/rm32/ESI . . . 0/r32/EAX . . # copy *ESI to EAX + # max/EDX = f->data + f->write 8d/copy-address 1/mod/*+disp8 4/rm32/sib 6/base/ESI 0/index/EAX . 2/r32/EDX 0xc/disp8 . # copy ESI+EAX+12 to EDX # currf/ESI = f->data 81 0/subop/add 3/mod/direct 6/rm32/ESI . . . . . 0xc/imm32 # add to ESI diff --git a/subx/071read-line.subx b/subx/071read-line.subx index eff6f9c4..b16170b6 100644 --- a/subx/071read-line.subx +++ b/subx/071read-line.subx @@ -3,10 +3,17 @@ # . op subop mod rm32 base index scale r32 # . 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 +#? Entry: # run a single test, while debugging +#? e8/call test-read-line/disp32 +#? # syscall(exit, Num-test-failures) +#? 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX +#? b8/copy-to-EAX 1/imm32/exit +#? cd/syscall 0x80/imm8 + # read bytes from 'f' until (and including) a newline and store them into 's' -# return true if no data found, false otherwise +# 's' fails to grow if and only if no data found # just abort if 's' is too small -read-line: # f : (address buffered-file), s : (address stream byte) -> eof?/EAX +read-line: # f : (address buffered-file), s : (address stream byte) -> # pseudocode: # loop: # if (s->write >= s->length) abort @@ -21,6 +28,7 @@ read-line: # f : (address buffered-file), s : (address stream byte) -> eof?/EAX 55/push-EBP 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP # . save registers + 50/push-EAX 51/push-ECX 52/push-EDX 56/push-ESI @@ -58,13 +66,11 @@ $read-line:loop: e8/call read/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # if (f->write == 0) return true + # if (f->write == 0) break # since f->read was initially 0, EAX is the same as f->write # . if (EAX == 0) return true 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0/imm32 # compare EAX - 75/jump-if-not-equal $read-line:from-stream/disp8 - b8/copy-to-EAX 0xffffffff/imm32/Eof - eb/jump $read-line:end/disp8 + 74/jump-if-equal $read-line:end/disp8 $read-line:from-stream: # AL = f->data[f->read] 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX @@ -75,10 +81,9 @@ $read-line:from-stream: 41/increment-ECX # ++s->write 42/increment-EDX - # if (AL == '\n') return false + # if (AL == '\n') return 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0xa/imm32 # compare EAX 75/jump-if-not-equal $read-line:loop/disp8 - 31/xor 3/mod/direct 0/rm32/EAX . . . 0/r32/EAX . . # clear EAX $read-line:end: # save f->read 89/copy 1/mod/*+disp8 6/rm32/ESI . . . 1/r32/ECX 8/disp8 . # copy ECX to *(ESI+8) @@ -89,6 +94,7 @@ $read-line:end: 5e/pop-to-ESI 5a/pop-to-EDX 59/pop-to-ECX + 58/pop-to-EAX # . epilog 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP 5d/pop-to-EBP @@ -103,6 +109,14 @@ $read-line:abort: e8/call _write/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . _write(2/stderr, Newline) + # . . push args + 68/push Newline/imm32 + 68/push 2/imm32/stderr + # . . call + e8/call _write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP # . syscall(exit, 1) bb/copy-to-EBX 1/imm32 b8/copy-to-EAX 1/imm32/exit @@ -169,15 +183,6 @@ test-read-line: e8/call read-line/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, 0/not-at-Eof, msg) - # . . push args - 68/push "F - test-read-line: return value"/imm32 - 68/push 0/imm32/not-at-Eof - 50/push-EAX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP # check-next-stream-line-equal(_test-tmp-stream, "ab", msg) # . . push args 68/push "F - test-read-line"/imm32 @@ -190,52 +195,6 @@ test-read-line: # end c3/return -test-read-line-returns-true-on-Eof: - # setup - # . clear-stream(_test-stream) - # . . push args - 68/push _test-stream/imm32 - # . . call - e8/call clear-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-buffered-file+4) - # . . push args - b8/copy-to-EAX _test-buffered-file/imm32 - 05/add-to-EAX 4/imm32 - 50/push-EAX - # . . call - e8/call clear-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-tmp-stream) - # . . push args - 68/push _test-tmp-stream/imm32 - # . . call - e8/call clear-stream/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # write nothing - # EAX = read-line(_test-buffered-file, _test-tmp-stream) - # . . push args - 68/push _test-tmp-stream/imm32 - 68/push _test-buffered-file/imm32 - # . . call - e8/call read-line/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, Eof, msg) - # . . push args - 68/push "F - test-read-line-returns-true-on-Eof"/imm32 - 68/push 0xffffffff/imm32/Eof - 50/push-EAX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP - # end - c3/return - test-read-line-reads-final-line-until-Eof: # setup # . clear-stream(_test-stream) @@ -278,15 +237,6 @@ test-read-line-reads-final-line-until-Eof: e8/call read-line/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # check-ints-equal(EAX, Eof, msg) - # . . push args - 68/push "F - test-read-line-reads-final-line-until-Eof: return value"/imm32 - 68/push 0xffffffff/imm32/Eof - 50/push-EAX - # . . call - e8/call check-ints-equal/disp32 - # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0xc/imm32 # add to ESP # check-stream-equal(_test-tmp-stream, "cd", msg) # . . push args 68/push "F - test-read-line-reads-final-line-until-Eof"/imm32 diff --git a/subx/072slice.subx b/subx/072slice.subx index c8834719..a47ef493 100644 --- a/subx/072slice.subx +++ b/subx/072slice.subx @@ -828,6 +828,8 @@ write-stream-buffered: # f : (address buffered-file), s : (address stream) # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP $write-stream-buffered:end: + # . restore locals + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP # . restore registers 5e/pop-to-ESI 58/pop-to-EAX diff --git a/subx/apps/crenshaw2-1 b/subx/apps/crenshaw2-1 index 44887a93..0a3d59ee 100755 Binary files a/subx/apps/crenshaw2-1 and b/subx/apps/crenshaw2-1 differ diff --git a/subx/apps/crenshaw2-1b b/subx/apps/crenshaw2-1b index 668b60f3..931bff4c 100755 Binary files a/subx/apps/crenshaw2-1b and b/subx/apps/crenshaw2-1b differ diff --git a/subx/apps/factorial b/subx/apps/factorial index 7361a59f..9bef95a7 100755 Binary files a/subx/apps/factorial and b/subx/apps/factorial differ diff --git a/subx/apps/handle b/subx/apps/handle index 63a94ad1..84b1bcf1 100755 Binary files a/subx/apps/handle and b/subx/apps/handle differ diff --git a/subx/apps/hex b/subx/apps/hex index a9bfcde9..3b183506 100755 Binary files a/subx/apps/hex and b/subx/apps/hex differ diff --git a/subx/apps/pack b/subx/apps/pack index 77aec7b0..69af4226 100755 Binary files a/subx/apps/pack and b/subx/apps/pack differ diff --git a/subx/apps/pack.subx b/subx/apps/pack.subx index c6cdaf14..003bc5d5 100644 --- a/subx/apps/pack.subx +++ b/subx/apps/pack.subx @@ -23,7 +23,7 @@ Entry: # run tests if necessary, convert stdin if not #? # for debugging: run a single test -#? e8/call test-convert-instruction-passes-labels-through/disp32 +#? e8/call test-convert-passes-segment-headers-through/disp32 #? 8b/copy 0/mod/indirect 5/rm32/.disp32 . . 3/r32/EBX Num-test-failures/disp32 # copy *Num-test-failures to EBX #? eb/jump $main:end/disp8 @@ -86,19 +86,22 @@ $main:end: convert: # in : (address buffered-file), out : (address buffered-file) -> # pseudocode: # var line = new-stream(512, 1) + # var in-code? = false # repeatedly # clear-stream(line) - # EAX = read-line(in, line) - # if (EAX == Eof) break - # word-slice = next-word(line) - # if (slice-empty?(word-slice)) continue # just whitespace - # if (starts-with(word-slice, "#")) continue # ignore comments - # assert(slice-equal?(word-slice, "==")) - # word-slice = next-word(line) - # if (slice-equal?(word-slice, "code")) - # convert-code-segment(in, out) + # read-line(in, line) + # if (line->write == 0) break # end of file + # var word-slice = next-word(line) + # if (slice-empty?(word-slice)) # whitespace + # write-stream-buffered(out, line) + # else if (slice-equal?(word-slice, "==")) + # word-slice = next-word(line) + # in-code? = slice-equal?(word-slice, "code") + # write-stream-buffered(out, line) + # else if (in-code?) + # convert-instruction(line, out) # else - # convert-data-segment(in, out) + # convert-data(line, out) # flush(out) # # . prolog @@ -106,233 +109,194 @@ convert: # in : (address buffered-file), out : (address buffered-file) -> write == 0) break + 81 7/subop/compare 0/mod/indirect 1/rm32/ECX . . . . . 0/imm32 # compare *ECX 74/jump-if-equal $convert:break/disp8 - # convert-instruction(line, out) + # next-word(line, word-slice) # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + 52/push-EDX 51/push-ECX # . . call - e8/call convert-instruction/disp32 + e8/call next-word/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP -$convert:break: - # flush(out) +$convert:check1: + # if (slice-empty?(word-slice)) write-stream-buffered(out, line) + # . EAX = slice-empty?(word-slice) # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + 52/push-EDX # . . call - e8/call flush/disp32 + e8/call slice-empty?/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP -$convert:end: - # . reclaim locals - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x20c/imm32 # add to ESP - # . restore registers - 59/pop-to-ECX - # . epilog - 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP - 5d/pop-to-EBP - c3/return - -convert-code-segment: # in : (address buffered-file), out : (address buffered-file) -> - # pseudocode: - # var line = new-stream(512, 1) - # repeatedly - # clear-stream(line) - # EAX = read-line(in, line) - # if (EAX == Eof) break - # word-slice = next-word(line) - # if (slice-equal?(word-slice, "==")) - # return - # convert-instruction(line, out) - -convert-data-segment: # in : (address buffered-file), out : (address buffered-file) -> - # pseudocode: - # var line = new-stream(512, 1) - # repeatedly - # clear-stream(line) - # EAX = read-line(in, line) - # if (EAX == Eof) break - # word-slice = next-word(line) - # if (slice-equal?(word-slice, "==")) - # return - # convert-data-word(line, out) - -# - To pack an instruction, following the C++ version: -# read first word as opcode and write-slice -# if 0f or f2 or f3 read second opcode and write-slice -# if 'f2 0f' or 'f3 0f' read third opcode and write-slice -# while true: -# word-slice = next-word -# if empty(word-slice) break -# if has metadata 'mod', parse into mod -# if has metadata 'rm32', parse into rm32 -# if has metadata 'r32', parse into r32 -# if has metadata 'subop', parse into r32 -# if at least one of the 3 was present, print-byte -# while true: -# word-slice = next-word -# if empty(word-slice) break -# if has metadata 'base', parse into base -# if has metadata 'index', parse into index -# if has metadata 'scale', parse into scale -# if at least one of the 3 was present, print-byte -# parse errors => -# while true: -# word-slice = next-word -# if empty(word-slice) break -# if has metadata 'disp8', emit as 1 byte -# if has metadata 'disp16', emit as 2 bytes -# if has metadata 'disp32', emit as 4 bytes -# while true: -# word-slice = next-word -# if empty(word-slice) break -# if has metadata 'imm8', emit -# if has metadata 'imm32', emit as 4 bytes -# finally, emit line prefixed with a ' # ' - -# simplifications since we perform zero error handling (continuing to rely on the C++ version for that): -# missing fields are always 0-filled -# bytes never mentioned are silently dropped; if you don't provide /mod, /rm32 or /r32 you don't get a 0 modrm byte. You get *no* modrm byte. -# in case of conflict, last operand with a name is recognized -# silently drop extraneous operands -# unceremoniously abort on non-numeric operands except disp or imm - -# conceptual hierarchy within a line: -# line = words separated by ' ', maybe followed by comment starting with '#' -# word = name until '/', then 0 or more metadata separated by '/' -# -# we won't bother saving the internal structure of lines; reparsing should be cheap using three primitives: -# next-token(stream, delim char) -> slice (start, end pointers) -# next-token(stream, slice, delim char) -> slice' -# slice-equal?(slice, string) - -convert-instruction: # line : (address stream byte), out : (address buffered-file) -> - # pseudocode: - # word-slice = next-word - # if (slice-starts-with?(word-slice, '#')) - # write-stream-buffered(out, line) - # return - # if (slice-starts-with?(word-slice, '==')) - # write-stream-buffered(out, line) - # return - # - # . prolog - 55/push-EBP - 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP - # . save registers - 51/push-ECX - # var word-slice/ECX = {0, 0} - 68/push 0/imm32/end - 68/push 0/imm32/start - 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX - # next-word(line, word-slice) + # . if (EAX != 0) write-stream-buffered(out, line) + 3d/compare-EAX 0/imm32 + 75/jump-if-not-equal $convert:pass-through/disp8 +$convert:check2: + # if (slice-equal?(word-slice, "==) && slice-equal?(next-word(line))) write-stream-buffered(out, line) + # . EAX = slice-equal?(word-slice, "==") + # . . push args + 68/push "=="/imm32 + 52/push-EDX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . if (EAX == 0) goto check3 + 81 7/subop/compare 3/mod/direct 0/rm32/EAX . . . . . 0/imm32 # compare EAX + 74/jump-if-equal $convert:check3/disp8 + # . next-word(line, word-slice) # . . push args + 52/push-EDX 51/push-ECX - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) # . . call e8/call next-word/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP -$convert-instruction:pass-line-through: + # . in-code? = slice-equal?(word-slice, "code") + # . . push args + 68/push "code"/imm32 + 52/push-EDX + # . . call + e8/call slice-equal?/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # . . in-code? = EAX + 89/copy 3/mod/direct 3/rm32/EBX . . . 0/r32/EAX . . # copy EAX to EBX + # . goto pass-through + eb/jump $convert:pass-through/disp8 +$convert:check3: +#? # convert-instruction(line, out) +#? # . . push args +#? ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) +#? 51/push-ECX +#? # . . call +#? e8/call convert-instruction/disp32 +#? # . . discard args +#? 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + e9/jump $convert:loop/disp32 +$convert:pass-through: # write-stream-buffered(out, line) # . . push args - ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + 51/push-ECX ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) # . . call e8/call write-stream-buffered/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP -$convert-instruction:end: + e9/jump $convert:loop/disp32 +$convert:break: + # flush(out) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call flush/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP +$convert:end: + # . reclaim locals + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 0x218/imm32 # add to ESP # . restore registers + 5b/pop-to-EBX + 5a/pop-to-EDX 59/pop-to-ECX # . epilog 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP 5d/pop-to-EBP c3/return -test-convert-instruction-passes-comments-through: - # if a line starts with '#', pass it along unchanged +test-convert-passes-empty-lines-through: + # if a line is empty, pass it along unchanged # . prolog 55/push-EBP 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP # setup - # . clear-stream(_test-stream) + # . clear-stream(_test-input-stream) # . . push args - 68/push _test-stream/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-buffered-file+4) + # . clear-stream(_test-input-buffered-file+4) # . . push args - b8/copy-to-EAX _test-buffered-file/imm32 + b8/copy-to-EAX _test-input-buffered-file/imm32 05/add-to-EAX 4/imm32 50/push-EAX # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-tmp-stream) + # . clear-stream(_test-output-stream) # . . push args - 68/push _test-tmp-stream/imm32 + 68/push _test-output-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # initialize input - # . write(_test-tmp-stream, "# abcd") + # . clear-stream(_test-output-buffered-file+4) # . . push args - 68/push "# abcd"/imm32 - 68/push _test-tmp-stream/imm32 + b8/copy-to-EAX _test-output-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX # . . call - e8/call write/disp32 + e8/call clear-stream/disp32 # . . discard args - 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # convert-instruction(_test-tmp-stream, _test-buffered-file) + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # write nothing to input + # convert(_test-input-buffered-file, _test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 - 68/push _test-tmp-stream/imm32 + 68/push _test-output-buffered-file/imm32 + 68/push _test-input-buffered-file/imm32 # . . call - e8/call convert-instruction/disp32 + e8/call convert/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP # check that the write happened as expected - # . flush(_test-buffered-file) + # . flush(_test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 + 68/push _test-output-buffered-file/imm32 # . . call e8/call flush/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . check-stream-equal(_test-stream, "# abcd", msg) + # . check-stream-equal(_test-output-stream, "", msg) # . . push args - 68/push "F - test-convert-instruction-passes-comments-through"/imm32 - 68/push "# abcd"/imm32 - 68/push _test-stream/imm32 + 68/push "F - test-convert-passes-empty-lines-through"/imm32 + 68/push ""/imm32 + 68/push _test-output-stream/imm32 # . . call e8/call check-stream-equal/disp32 # . . discard args @@ -342,65 +306,74 @@ test-convert-instruction-passes-comments-through: 5d/pop-to-EBP c3/return -test-convert-instruction-passes-segment-headers-through: - # if a line starts with '==', pass it along unchanged +test-convert-passes-lines-with-just-whitespace-through: + # if a line is empty, pass it along unchanged # . prolog 55/push-EBP 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP # setup - # . clear-stream(_test-stream) + # . clear-stream(_test-input-stream) # . . push args - 68/push _test-stream/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-buffered-file+4) + # . clear-stream(_test-input-buffered-file+4) # . . push args - b8/copy-to-EAX _test-buffered-file/imm32 + b8/copy-to-EAX _test-input-buffered-file/imm32 05/add-to-EAX 4/imm32 50/push-EAX # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-tmp-stream) + # . clear-stream(_test-output-stream) # . . push args - 68/push _test-tmp-stream/imm32 + 68/push _test-output-stream/imm32 + # . . call + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # . clear-stream(_test-output-buffered-file+4) + # . . push args + b8/copy-to-EAX _test-output-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP # initialize input - # . write(_test-tmp-stream, "== abcd") + # . write(_test-input-stream, " ") # . . push args - 68/push "== abcd"/imm32 - 68/push _test-tmp-stream/imm32 + 68/push " "/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call write/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # convert-instruction(_test-tmp-stream, _test-buffered-file) + # convert(_test-input-buffered-file, _test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 - 68/push _test-tmp-stream/imm32 + 68/push _test-output-buffered-file/imm32 + 68/push _test-input-buffered-file/imm32 # . . call - e8/call convert-instruction/disp32 + e8/call convert/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP # check that the write happened as expected - # . flush(_test-buffered-file) + # . flush(_test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 + 68/push _test-output-buffered-file/imm32 # . . call e8/call flush/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . check-stream-equal(_test-stream, "== abcd", msg) + # . check-stream-equal(_test-output-stream, " ", msg) # . . push args - 68/push "F - test-convert-instruction-passes-segment-headers-through"/imm32 - 68/push "== abcd"/imm32 - 68/push _test-stream/imm32 + 68/push "F - test-convert-passes-with-just-whitespace-through"/imm32 + 68/push " "/imm32 + 68/push _test-output-stream/imm32 # . . call e8/call check-stream-equal/disp32 # . . discard args @@ -410,57 +383,74 @@ test-convert-instruction-passes-segment-headers-through: 5d/pop-to-EBP c3/return -test-convert-instruction-passes-empty-lines-through: - # if a line is empty, pass it along unchanged +test-convert-passes-segment-headers-through: + # if a line starts with '==', pass it along unchanged # . prolog 55/push-EBP 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP # setup - # . clear-stream(_test-stream) + # . clear-stream(_test-input-stream) # . . push args - 68/push _test-stream/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-buffered-file+4) + # . clear-stream(_test-input-buffered-file+4) # . . push args - b8/copy-to-EAX _test-buffered-file/imm32 + b8/copy-to-EAX _test-input-buffered-file/imm32 05/add-to-EAX 4/imm32 50/push-EAX # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-tmp-stream) + # . clear-stream(_test-output-stream) # . . push args - 68/push _test-tmp-stream/imm32 + 68/push _test-output-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # write nothing to input - # convert-instruction(_test-tmp-stream, _test-buffered-file) + # . clear-stream(_test-output-buffered-file+4) # . . push args - 68/push _test-buffered-file/imm32 - 68/push _test-tmp-stream/imm32 + b8/copy-to-EAX _test-output-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX # . . call - e8/call convert-instruction/disp32 + e8/call clear-stream/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP + # initialize input + # . write(_test-input-stream, "== abcd") + # . . push args + 68/push "== abcd"/imm32 + 68/push _test-input-stream/imm32 + # . . call + e8/call write/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP + # convert(_test-input-buffered-file, _test-output-buffered-file) + # . . push args + 68/push _test-output-buffered-file/imm32 + 68/push _test-input-buffered-file/imm32 + # . . call + e8/call convert/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP # check that the write happened as expected - # . flush(_test-buffered-file) + # . flush(_test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 + 68/push _test-output-buffered-file/imm32 # . . call e8/call flush/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . check-stream-equal(_test-stream, "", msg) + # . check-stream-equal(_test-output-stream, "== abcd", msg) # . . push args - 68/push "F - test-convert-instruction-passes-empty-lines-through"/imm32 - 68/push ""/imm32 - 68/push _test-stream/imm32 + 68/push "F - test-convert-passes-segment-headers-through"/imm32 + 68/push "== abcd"/imm32 + 68/push _test-output-stream/imm32 # . . call e8/call check-stream-equal/disp32 # . . discard args @@ -470,65 +460,179 @@ test-convert-instruction-passes-empty-lines-through: 5d/pop-to-EBP c3/return -test-convert-instruction-passes-lines-with-just-whitespace-through: - # if a line is empty, pass it along unchanged +convert-code-segment: # in : (address buffered-file), out : (address buffered-file) -> + # pseudocode: + # var line = new-stream(512, 1) + # repeatedly + # clear-stream(line) + # EAX = read-line(in, line) + # if (EAX == Eof) break + # word-slice = next-word(line) + # if (slice-equal?(word-slice, "==")) + # return + # convert-instruction(line, out) + +convert-data-segment: # in : (address buffered-file), out : (address buffered-file) -> + # pseudocode: + # var line = new-stream(512, 1) + # repeatedly + # clear-stream(line) + # EAX = read-line(in, line) + # if (EAX == Eof) break + # word-slice = next-word(line) + # if (slice-equal?(word-slice, "==")) + # return + # convert-data-word(line, out) + +# - To pack an instruction, following the C++ version: +# read first word as opcode and write-slice +# if 0f or f2 or f3 read second opcode and write-slice +# if 'f2 0f' or 'f3 0f' read third opcode and write-slice +# while true: +# word-slice = next-word +# if empty(word-slice) break +# if has metadata 'mod', parse into mod +# if has metadata 'rm32', parse into rm32 +# if has metadata 'r32', parse into r32 +# if has metadata 'subop', parse into r32 +# if at least one of the 3 was present, print-byte +# while true: +# word-slice = next-word +# if empty(word-slice) break +# if has metadata 'base', parse into base +# if has metadata 'index', parse into index +# if has metadata 'scale', parse into scale +# if at least one of the 3 was present, print-byte +# parse errors => +# while true: +# word-slice = next-word +# if empty(word-slice) break +# if has metadata 'disp8', emit as 1 byte +# if has metadata 'disp16', emit as 2 bytes +# if has metadata 'disp32', emit as 4 bytes +# while true: +# word-slice = next-word +# if empty(word-slice) break +# if has metadata 'imm8', emit +# if has metadata 'imm32', emit as 4 bytes +# finally, emit line prefixed with a ' # ' + +# simplifications since we perform zero error handling (continuing to rely on the C++ version for that): +# missing fields are always 0-filled +# bytes never mentioned are silently dropped; if you don't provide /mod, /rm32 or /r32 you don't get a 0 modrm byte. You get *no* modrm byte. +# in case of conflict, last operand with a name is recognized +# silently drop extraneous operands +# unceremoniously abort on non-numeric operands except disp or imm + +# conceptual hierarchy within a line: +# line = words separated by ' ', maybe followed by comment starting with '#' +# word = name until '/', then 0 or more metadata separated by '/' +# +# we won't bother saving the internal structure of lines; reparsing should be cheap using three primitives: +# next-token(stream, delim char) -> slice (start, end pointers) +# next-token(stream, slice, delim char) -> slice' +# slice-equal?(slice, string) + +convert-instruction: # line : (address stream byte), out : (address buffered-file) -> + # pseudocode: + # word-slice = next-word + # if (starts-with(word-slice, "#")) # comments + # write-stream-buffered(out, line) + # ... + # + # . prolog + 55/push-EBP + 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP + # . save registers + 51/push-ECX + # var word-slice/ECX = {0, 0} + 68/push 0/imm32/end + 68/push 0/imm32/start + 89/copy 3/mod/direct 1/rm32/ECX . . . 4/r32/ESP . . # copy ESP to ECX + # next-word(line, word-slice) + # . . push args + 51/push-ECX + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + # . . call + e8/call next-word/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP +$convert-instruction:pass-line-through: + # write-stream-buffered(out, line) + # . . push args + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 8/disp8 . # push *(EBP+8) + ff 6/subop/push 1/mod/*+disp8 5/rm32/EBP . . . . 0xc/disp8 . # push *(EBP+12) + # . . call + e8/call write-stream-buffered/disp32 + # . . discard args + 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP +$convert-instruction:end: + # . restore registers + 59/pop-to-ECX + # . epilog + 89/copy 3/mod/direct 4/rm32/ESP . . . 5/r32/EBP . . # copy EBP to ESP + 5d/pop-to-EBP + c3/return + +test-convert-instruction-passes-comments-through: + # if a line starts with '#', pass it along unchanged # . prolog 55/push-EBP 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP # setup - # . clear-stream(_test-stream) + # . clear-stream(_test-input-stream) # . . push args - 68/push _test-stream/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-buffered-file+4) + # . clear-stream(_test-output-stream) # . . push args - b8/copy-to-EAX _test-buffered-file/imm32 - 05/add-to-EAX 4/imm32 - 50/push-EAX + 68/push _test-output-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-tmp-stream) + # . clear-stream(_test-output-buffered-file+4) # . . push args - 68/push _test-tmp-stream/imm32 + b8/copy-to-EAX _test-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP # initialize input - # . write(_test-tmp-stream, " ") + # . write(_test-input-stream, "# abcd") # . . push args - 68/push " "/imm32 - 68/push _test-tmp-stream/imm32 + 68/push "# abcd"/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call write/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # convert-instruction(_test-tmp-stream, _test-buffered-file) + # convert-instruction(_test-input-stream, _test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 - 68/push _test-tmp-stream/imm32 + 68/push _test-output-buffered-file/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call convert-instruction/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP # check that the write happened as expected - # . flush(_test-buffered-file) + # . flush(_test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 + 68/push _test-output-buffered-file/imm32 # . . call e8/call flush/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . check-stream-equal(_test-stream, " ", msg) + # . check-stream-equal(_test-output-stream, "# abcd", msg) # . . push args - 68/push "F - test-convert-instruction-passes-with-just-whitespace-through"/imm32 - 68/push " "/imm32 - 68/push _test-stream/imm32 + 68/push "F - test-convert-instruction-passes-comments-through"/imm32 + 68/push "# abcd"/imm32 + 68/push _test-output-stream/imm32 # . . call e8/call check-stream-equal/disp32 # . . discard args @@ -544,59 +648,59 @@ test-convert-instruction-passes-labels-through: 55/push-EBP 89/copy 3/mod/direct 5/rm32/EBP . . . 4/r32/ESP . . # copy ESP to EBP # setup - # . clear-stream(_test-stream) + # . clear-stream(_test-input-stream) # . . push args - 68/push _test-stream/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-buffered-file+4) + # . clear-stream(_test-output-stream) # . . push args - b8/copy-to-EAX _test-buffered-file/imm32 - 05/add-to-EAX 4/imm32 - 50/push-EAX + 68/push _test-output-stream/imm32 # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . clear-stream(_test-tmp-stream) + # . clear-stream(_test-output-buffered-file+4) # . . push args - 68/push _test-tmp-stream/imm32 + b8/copy-to-EAX _test-output-buffered-file/imm32 + 05/add-to-EAX 4/imm32 + 50/push-EAX # . . call e8/call clear-stream/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP # initialize input - # . write(_test-tmp-stream, "ab: # cd") + # . write(_test-input-stream, "ab: # cd") # . . push args 68/push "ab: # cd"/imm32 - 68/push _test-tmp-stream/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call write/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP - # convert-instruction(_test-tmp-stream, _test-buffered-file) + # convert-instruction(_test-input-stream, _test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 - 68/push _test-tmp-stream/imm32 + 68/push _test-output-buffered-file/imm32 + 68/push _test-input-stream/imm32 # . . call e8/call convert-instruction/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 8/imm32 # add to ESP # check that the write happened as expected - # . flush(_test-buffered-file) + # . flush(_test-output-buffered-file) # . . push args - 68/push _test-buffered-file/imm32 + 68/push _test-output-buffered-file/imm32 # . . call e8/call flush/disp32 # . . discard args 81 0/subop/add 3/mod/direct 4/rm32/ESP . . . . . 4/imm32 # add to ESP - # . check-stream-equal(_test-stream, "ab: # cd", msg) + # . check-stream-equal(_test-output-stream, "ab: # cd", msg) # . . push args 68/push "F - test-convert-instruction-passes-labels-through"/imm32 68/push "ab: # cd"/imm32 - 68/push _test-stream/imm32 + 68/push _test-output-stream/imm32 # . . call e8/call check-stream-equal/disp32 # . . discard args @@ -1695,4 +1799,50 @@ _test-slice-non-number-word-end: 2f/slash _test-slice-non-number-word-metadata-end: +_test-input-stream: + # current write index + 0/imm32 + # current read index + 0/imm32 + # length + 8/imm32 + # data + 00 00 00 00 00 00 00 00 # 8 bytes + +# a test buffered file for _test-input-stream +_test-input-buffered-file: + # file descriptor or (address stream) + _test-input-stream/imm32 + # current write index + 0/imm32 + # current read index + 0/imm32 + # length + 6/imm32 + # data + 00 00 00 00 00 00 # 6 bytes + +_test-output-stream: + # current write index + 0/imm32 + # current read index + 0/imm32 + # length + 8/imm32 + # data + 00 00 00 00 00 00 00 00 # 8 bytes + +# a test buffered file for _test-output-stream +_test-output-buffered-file: + # file descriptor or (address stream) + _test-output-stream/imm32 + # current write index + 0/imm32 + # current read index + 0/imm32 + # length + 6/imm32 + # data + 00 00 00 00 00 00 # 6 bytes + # . . vim:nowrap:textwidth=0 -- cgit 1.4.1-2-gfad0