1 # Helper to print an int32 in decimal. 2 3 == code 4 # instruction effective address register displacement immediate 5 # . op subop mod rm32 base index scale r32 6 # . 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 7 8 print-int32-decimal: # out: (addr stream byte), n: int32 9 # works by generating characters from lowest to highest and pushing them 10 # to the stack, before popping them one by one into the stream 11 # 12 # pseudocode: 13 # push sentinel 14 # eax = abs(n) 15 # while true 16 # sign-extend eax into edx 17 # eax, edx = eax/10, eax%10 18 # edx += '0' 19 # push edx 20 # if (eax == 0) break 21 # if n < 0 22 # push '-' 23 # w = out->write 24 # curr = &out->data[out->write] 25 # max = &out->data[out->length] 26 # while true 27 # pop into eax 28 # if (eax == sentinel) break 29 # if (curr >= max) abort 30 # *curr = AL 31 # ++curr 32 # ++w 33 # out->write = w 34 # (based on K&R itoa: https://en.wikibooks.org/wiki/C_Programming/stdlib.h/itoa) 35 # (this pseudocode contains registers because operations like division 36 # require specific registers in x86) 37 # 38 # . prologue 39 55/push-ebp 40 89/copy 3/mod/direct 5/rm32/ebp . . . 4/r32/esp . . # copy esp to ebp 41 # . save registers 42 50/push-eax 43 51/push-ecx 44 52/push-edx 45 53/push-ebx 46 57/push-edi 47 # const ten/ecx = 10 48 b9/copy-to-ecx 0xa/imm32 49 # push sentinel 50 68/push 0/imm32/sentinel 51 # var eax: int = abs(n) 52 8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 0/r32/eax 0xc/disp8 . # copy *(ebp+12) to eax 53 3d/compare-eax-with 0/imm32 54 7d/jump-if->= $print-int32-decimal:read-loop/disp8 55 $print-int32-decimal:negative: 56 f7 3/subop/negate 3/mod/direct 0/rm32/eax . . . . . . # negate eax 57 $print-int32-decimal:read-loop: 58 # eax, edx = eax / 10, eax % 10 59 99/sign-extend-eax-into-edx 60 f7 7/subop/idiv 3/mod/direct 1/rm32/ecx . . . . . . # divide edx:eax by ecx, storing quotient in eax and remainder in edx 61 # edx += '0' 62 81 0/subop/add 3/mod/direct 2/rm32/edx . . . . . 0x30/imm32 # add to edx 63 # push edx 64 52/push-edx 65 # if (eax == 0) break 66 3d/compare-eax-and 0/imm32 67 7f/jump-if-> $print-int32-decimal:read-loop/disp8 68 $print-int32-decimal:read-break: 69 # if (n < 0) push('-') 70 81 7/subop/compare 1/mod/*+disp8 5/rm32/ebp . . . . 0xc/disp8 0/imm32 # compare *(ebp+12) 71 7d/jump-if->= $print-int32-decimal:write/disp8 72 $print-int32-decimal:push-negative: 73 68/push 0x2d/imm32/- 74 $print-int32-decimal:write: 75 # edi = out 76 8b/copy 1/mod/*+disp8 5/rm32/ebp . . . 7/r32/edi 8/disp8 . # copy *(ebp+8) to edi 77 # var w/edx: int = out->write 78 8b/copy 0/mod/indirect 7/rm32/edi . . . 2/r32/edx . . # copy *edi to edx 79 # var curr/ecx: (addr byte) = &out->data[out->write] 80 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/edi 2/index/edx . 1/r32/ecx 0xc/disp8 . # copy ebx+edx+12 to ecx 81 # var max/ebx: (addr byte) = &out->data[out->length] 82 8b/copy 1/mod/*+disp8 7/rm32/edi . . . 3/r32/ebx 8/disp8 . # copy *(edi+8) to ebx 83 8d/copy-address 1/mod/*+disp8 4/rm32/sib 7/base/edi 3/index/ebx . 3/r32/ebx 0xc/disp8 . # copy edi+ebx+12 to ebx 84 $print-int32-decimal:write-loop: 85 # pop into eax 86 58/pop-to-eax 87 # if (eax == sentinel) break 88 3d/compare-eax-and 0/imm32/sentinel 89 74/jump-if-= $print-int32-decimal:write-break/disp8 90 # if (curr >= max) abort 91 39/compare 3/mod/direct 1/rm32/ecx . . . 3/r32/ebx . . # compare ecx with ebx 92 73/jump-if-addr>= $print-int32-decimal:abort/disp8 93 $print-int32-decimal:write-char: 94 # *curr = AL 95 88/copy-byte 0/mod/indirect 1/rm32/ecx . . . 0/r32/AL . . # copy AL to byte at *ecx 96 # ++curr 97 41/increment-ecx 98 # ++w 99 42/increment-edx 100 eb/jump $print-int32-decimal:write-loop/disp8 101 $print-int32-decimal:write-break: 102 # out->write = w 103 89/copy 0/mod/indirect 7/rm32/edi . . . 2/r32/edx . . # copy edx to *edi 104 $print-int32-decimal:end: 105 # . restore registers 106 5f/pop-to-edi 107 5b/pop-to-ebx 108 5a/pop-to-edx 109 59/pop-to-ecx 110 58/pop-to-eax 111 # . epilogue 112 89/copy 3/mod/direct 4/rm32/esp . . . 5/r32/ebp . . # copy ebp to esp 113 5d/pop-to-ebp 114 c3/return 115 116 $print-int32-decimal:abort: 117 # . _write(2/stderr, error) 118 # . . push args 119 68/push "print-int32-decimal: out of space\n"/imm32 120 68/push 2/imm32/stderr 121 # . . call 122 e8/call _write/disp32 123 # . . discard args 124 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 125 # . syscall(exit, 1) 126 bb/copy-to-ebx 1/imm32 127 b8/copy-to-eax 1/imm32/exit 128 cd/syscall 0x80/imm8 129 # never gets here 130 131 test-print-int32-decimal: 132 # - check that a single-digit number converts correctly 133 # setup 134 # . clear-stream(_test-stream) 135 # . . push args 136 68/push _test-stream/imm32 137 # . . call 138 e8/call clear-stream/disp32 139 # . . discard args 140 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 141 # print-int32-decimal(_test-stream, 9) 142 # . . push args 143 68/push 9/imm32 144 68/push _test-stream/imm32 145 # . . call 146 e8/call print-int32-decimal/disp32 147 # . . discard args 148 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 149 # check-stream-equal(_test-stream, "9", msg) 150 # . . push args 151 68/push "F - test-print-int32-decimal"/imm32 152 68/push "9"/imm32 153 68/push _test-stream/imm32 154 # . . call 155 e8/call check-stream-equal/disp32 156 # . . discard args 157 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 158 # . end 159 c3/return 160 161 test-print-int32-decimal-zero: 162 # - check that 0 converts correctly 163 # setup 164 # . clear-stream(_test-stream) 165 # . . push args 166 68/push _test-stream/imm32 167 # . . call 168 e8/call clear-stream/disp32 169 # . . discard args 170 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 171 # print-int32-decimal(_test-stream, 0) 172 # . . push args 173 68/push 0/imm32 174 68/push _test-stream/imm32 175 # . . call 176 e8/call print-int32-decimal/disp32 177 # . . discard args 178 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 179 # check-stream-equal(_test-stream, "0", msg) 180 # . . push args 181 68/push "F - test-print-int32-decimal-zero"/imm32 182 68/push "0"/imm32 183 68/push _test-stream/imm32 184 # . . call 185 e8/call check-stream-equal/disp32 186 # . . discard args 187 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 188 # . end 189 c3/return 190 191 test-print-int32-decimal-multiple-digits: 192 # - check that a multi-digit number converts correctly 193 # setup 194 # . clear-stream(_test-stream) 195 # . . push args 196 68/push _test-stream/imm32 197 # . . call 198 e8/call clear-stream/disp32 199 # . . discard args 200 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 201 # print-int32-decimal(_test-stream, 10) 202 # . . push args 203 68/push 0xa/imm32 204 68/push _test-stream/imm32 205 # . . call 206 e8/call print-int32-decimal/disp32 207 # . . discard args 208 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 8/imm32 # add to esp 209 # check-stream-equal(_test-stream, "10", msg) 210 # . . push args 211 68/push "F - test-print-int32-decimal-multiple-digits"/imm32 212 68/push "10"/imm32 213 68/push _test-stream/imm32 214 # . . call 215 e8/call check-stream-equal/disp32 216 # . . discard args 217 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 0xc/imm32 # add to esp 218 # . end 219 c3/return 220 221 test-print-int32-decimal-negative: 222 # - check that a negative single-digit number converts correctly 223 # setup 224 # . clear-stream(_test-stream) 225 # . . push args 226 68/push _test-stream/imm32 227 # . . call 228 e8/call clear-stream/disp32 229 # . . discard args 230 81 0/subop/add 3/mod/direct 4/rm32/esp . . . . . 4/imm32 # add to esp 231 # print-int32-decimal(_test-stream, -9) 232 # . . push args 233 68/push -9/imm32 234 68/push _test-stream/imm32 235 # . . call 236 e8/call print-int32-decimal/disp32 237 # . . discard args 238 81 0/subop/add 3/mod/direct 4/rm32/esp . . .bool Hide_warnings = false; struct trace_stream { vector<pair<string, string> > past_lines; // [(layer label, line)] // accumulator for current line ostringstream* curr_stream; string curr_layer; trace_stream() :curr_stream(NULL) {} ~trace_stream() { if (curr_stream) delete curr_stream; } ostringstream& stream(string layer) { newline(); curr_stream = new ostringstream; curr_layer = layer; return *curr_stream; } // be sure to call this before messing with curr_stream or curr_layer void newline() { if (!curr_stream) return; string curr_contents = curr_stream->str(); curr_contents.erase(curr_contents.find_last_not_of("\r\n")+1); past_lines.push_back(pair<string, string>(curr_layer, curr_contents)); delete curr_stream; curr_stream = NULL; } string readable_contents(string layer) { // missing layer = everything newline(); ostringstream output; for (vector<pair<string, string> >::iterator p = past_lines.begin(); p != past_lines.end(); ++p) if (layer.empty() || layer == p->first) output << p->first << ": " << with_newline(p->second); return output.str(); } string with_newline(string s) { if (s[s.size()-1] != '\n') return s+'\n'; return s; } }; trace_stream* Trace_stream = NULL; // Top-level helper. IMPORTANT: can't nest. #define trace(layer) !Trace_stream ? cerr /*print nothing*/ : Trace_stream->stream(layer) // Warnings should go straight to cerr by default since calls to trace() have // some unfriendly constraints (they delay printing, they can't nest) #define raise ((!Trace_stream || !Hide_warnings) ? cerr /*do print*/ : Trace_stream->stream("warn")) << __FILE__ << ":" << __LINE__ << " " // raise << die exits after printing -- unless Hide_warnings is set. struct die {}; ostream& operator<<(ostream& os, __attribute__((unused)) die) { if (Hide_warnings) return os; os << "dying\n"; exit(1); } #define CLEAR_TRACE delete Trace_stream, Trace_stream = new trace_stream; #define DUMP(layer) cerr << Trace_stream->readable_contents(layer) // Trace_stream is a resource, lease_tracer uses RAII to manage it. struct lease_tracer { lease_tracer() { Trace_stream = new trace_stream; } ~lease_tracer() { delete Trace_stream, Trace_stream = NULL; } }; #define START_TRACING_UNTIL_END_OF_SCOPE lease_tracer leased_tracer; bool check_trace_contents(string FUNCTION, string