1 //: Labels are defined by ending names with a ':'. This layer will compute
  2 //: displacements for labels, and compute the offset for instructions using them.
  3 //:
  4 //: We won't check this, but our convention will be that jump targets will
  5 //: start with a '$', while functions will not. Function names will never be
  6 //: jumped to, and jump targets will never be called.
  7 
  8 //: We're introducing non-number names for the first time, so it's worth
  9 //: laying down some ground rules all transforms will follow, so things don't
 10 //: get too confusing:
 11 //:   - if it starts with a digit, it's treated as a number. If it can't be
 12 //:     parsed as hex it will raise an error.
 13 //:   - if it starts with '-' it's treated as a number.
 14 //:   - if it starts with '0x' it's treated as a number.
 15 //:   - if it's two characters long, it can't be a name. Either it's a hex
 16 //:     byte, or it raises an error.
 17 //: That's it. Names can start with any non-digit that isn't a dash. They can
 18 //: be a single character long. 'a' is not a hex number, it's a variable.
 19 //: Later layers may add more conventions partitioning the space of names. But
 20 //: the above rules will remain inviolate.
 21 
 22 :(before "End looks_like_hex_int(s) Detectors")
 23 if (SIZE(s) == 2) return true;
 24 
 25 :(scenarios transform)
 26 :(scenario pack_immediate_ignores_single_byte_nondigit_operand)
 27 % Hide_errors = true;
 28 == 0x1
 29 b9/copy a/imm32  # copy to ECX
 30 +transform: packing instruction 'b9/copy a/imm32'
 31 # no change (we're just not printing metadata to the trace)
 32 +transform: instruction after packing: 'b9 a'
 33 
 34 :(scenario pack_immediate_ignores_3_hex_digit_operand)
 35 % Hide_errors = true;
 36 == 0x1
 37 b9/copy aaa/imm32  # copy to ECX
 38 +transform: packing instruction 'b9/copy aaa/imm32'
 39 # no change (we're just not printing metadata to the trace)
 40 +transform: instruction after packing: 'b9 aaa'
 41 
 42 :(scenario pack_immediate_ignores_non_hex_operand)
 43 % Hide_errors = true;
 44 == 0x1
 45 b9/copy xxx/imm32  # copy to ECX
 46 +transform: packing instruction 'b9/copy xxx/imm32'
 47 # no change (we're just not printing metadata to the trace)
 48 +transform: instruction after packing: 'b9 xxx'
 49 
 50 //: a helper we'll find handy later
 51 :(code)
 52 void check_valid_name(const string& s) {
 53   if (s.empty()) {
 54     raise << "empty name!\n" << end();
 55     return;
 56   }
 57   if (s.at(0) == '-')
 58     raise << "'" << s << "' starts with '-', which can be confused with a negative number; use a different name\n" << end();
 59   if (s.substr(0, 2) == "0x") {
 60     raise << "'" << s << "' looks like a hex number; use a different name\n" << end();
 61     return;
 62   }
 63   if (isdigit(s.at(0)))
 64     raise << "'" << s << "' starts with a digit, and so can be confused with a negative number; use a different name.\n" << end();
 65   if (SIZE(s) == 2)
 66     raise << "'" << s << "' is two characters long which can look like raw hex bytes at a glance; use a different name\n" << end();
 67 }
 68 
 69 //: Now that that's done, let's start using names as labels.
 70 
 71 :(scenario map_label)
 72 == 0x1
 73           # instruction                     effective address                                                   operand     displacement    immediate
 74           # op          subop               mod             rm32          base        index         scale       r32
 75           # 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
 76 loop:
 77             05                                                                                                                              0x0d0c0b0a/imm32  # add to EAX
 78 +transform: label 'loop' is at address 1
 79 
 80 :(before "End Level-2 Transforms")
 81 Transform.push_back(rewrite_labels);
 82 :(code)
 83 void rewrite_labels(program& p) {
 84   trace(99, "transform") << "-- rewrite labels" << end();
 85   if (p.segments.empty()) return;
 86   segment& code = p.segments.at(0);
 87   map<string, int32_t> byte_index;  // values are unsigned, but we're going to do subtractions on them so they need to fit in 31 bits
 88   compute_byte_indices_for_labels(code, byte_index);
 89   if (trace_contains_errors()) return;
 90   drop_labels(code);
 91   if (trace_contains_errors()) return;
 92   replace_labels_with_displacements(code, byte_index);
 93 }
 94 
 95 void compute_byte_indices_for_labels(const segment& code, map<string, int32_t>& byte_index) {
 96   int current_byte = 0;
 97   for (int i = 0;  i < SIZE(code.lines);  ++i) {
 98     const line& inst = code.lines.at(i);
 99     for (int j = 0;  j < SIZE(inst.words);  ++j) {
100       const word& curr = inst.words.at(j);
101       // hack: if we have any operand metadata left after previous transforms,
102       // deduce its size
103       // Maybe we should just move this transform to before instruction
104       // packing, and deduce the size of *all* operands. But then we'll also
105       // have to deal with bitfields.
106       if (has_operand_metadata(curr, "disp32") || has_operand_metadata(curr, "imm32")) {
107         if (*curr.data.rbegin() == ':')
108           raise << "'" << to_string(inst) << "': don't use ':' when jumping to labels\n" << end();
109         current_byte += 4;
110       }
111       // automatically handle /disp8 and /imm8 here
112       else if (*curr.data.rbegin() != ':') {
113         ++current_byte;
114       }
115       else {
116         string label = drop_last(curr.data);
117         // ensure labels look sufficiently different from raw hex
118         check_valid_name(label);
119         if (trace_contains_errors()) return;
120         if (contains_any_operand_metadata(curr))
121           raise << "'" << to_string(inst) << "': label definition (':') not allowed in operand\n" << end();
122         if (j > 0)
123           raise << "'" << to_string(inst) << "': labels can only be the first word in a line.\n" << end();
124         if (Dump_map)
125           cerr << "0x" << HEXWORD << (code.start + current_byte) << ' ' << label << '\n';
126         put(byte_index, label, current_byte);
127         trace(99, "transform") << "label '" << label << "' is at address " << (current_byte+code.start) << end();
128         // no modifying current_byte; label definitions won't be in the final binary
129       }
130     }
131   }
132 }
133 
134 :(before "End Globals")
135 bool Dump_map = false;  // currently used only by 'subx translate'
136 :(before "End Commandline Options")
137 else if (is_equal(*arg, "--map")) {
138   Dump_map = true;
139 }
140 
141 :(code)
142 void drop_labels(segment& code) {
143   for (int i = 0;  i < SIZE(code.lines);  ++i) {
144     line& inst = code.lines.at(i);
145     vector<word>::iterator new_end = remove_if(inst.words.begin(), inst.words.end(), is_label);
146     inst.words.erase(new_end, inst.words.end());
147   }
148 }
149 
150 bool is_label(const word& w) {
151   return *w.data.rbegin() == ':';
152 }
153 
154 void replace_labels_with_displacements(segment& code, const map<string, int32_t>& byte_index) {
155   int32_t byte_index_next_instruction_starts_at = 0;
156   for (int i = 0;  i < SIZE(code.lines);  ++i) {
157     line& inst = code.lines.at(i);
158     byte_index_next_instruction_starts_at += num_bytes(inst);
159     line new_inst;
160     for (int j = 0;  j < SIZE(inst.words);  ++j) {
161       const word& curr = inst.words.at(j);
162       if (contains_key(byte_index, curr.data)) {
163         int32_t displacement = static_cast<int32_t>(get(byte_index, curr.data)) - byte_index_next_instruction_starts_at;
164         if (has_operand_metadata(curr, "disp8")) {
165           if (displacement > 0xff || displacement < -0x7f)
166             raise << "'" << to_string(inst) << "': label too far away for displacement " << std::hex << displacement << " to fit in 8 bits\n" << end();
167           else
168             emit_hex_bytes(new_inst, displacement, 1);
169         }
170         else if (has_operand_metadata(curr, "disp16")) {
171           if (displacement > 0xffff || displacement < -0x7fff)
172             raise << "'" << to_string(inst) << "': label too far away for displacement " << std::hex << displacement << " to fit in 16 bits\n" << end();
173           else
174             emit_hex_bytes(new_inst, displacement, 2);
175         }
176         else if (has_operand_metadata(curr, "disp32")) {
177           emit_hex_bytes(new_inst, displacement, 4);
178         }
179       }
180       else {
181         new_inst.words.push_back(curr);
182       }
183     }
184     inst.words.swap(new_inst.words);
185     trace(99, "transform") << "instruction after transform: '" << data_to_string(inst) << "'" << end();
186   }
187 }
188 
189 string data_to_string(const line& inst) {
190   ostringstream out;
191   for (int i = 0;  i < SIZE(inst.words);  ++i) {
192     if (i > 0) out << ' ';
193     out << inst.words.at(i).data;
194   }
195   return out.str();
196 }
197 
198 string drop_last(const string& s) {
199   return string(s.begin(), --s.end());
200 }
201 
202 //: Label definitions must be the first word on a line. No jumping inside
203 //: instructions.
204 //: They should also be the only word on a line.
205 //: However, you can absolutely have multiple labels map to the same address,
206 //: as long as they're on separate lines.
207 
208 :(scenario multiple_labels_at)
209 == 0x1
210           # instruction                     effective address                                                   operand     displacement    immediate
211           # op          subop               mod             rm32          base        index         scale       r32
212           # 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
213 # address 1
214 loop:
215  $loop2:
216 # address 1 (labels take up no space)
217             05                                                                                                                              0x0d0c0b0a/imm32  # add to EAX
218 # address 6
219             eb                                                                                                              $loop2/disp8
220 # address 8
221             eb                                                                                                              $loop3/disp8
222 # address 0xa
223  $loop3:
224 +transform: label 'loop' is at address 1
225 +transform: label '$loop2' is at address 1
226 +transform: label '$loop3' is at address a
227 # first jump is to -7
228 +transform: instruction after transform: 'eb f9'
229 # second jump is to 0 (fall through)
230 +transform: instruction after transform: 'eb 00'
231 
232 :(scenario label_too_short)
233 % Hide_errors = true;
234 == 0x1
235           # instruction                     effective address                                                   operand     displacement    immediate
236           # op          subop               mod             rm32          base        index         scale       r32
237           # 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
238 xz:
239             05                                                                                                                              0x0d0c0b0a/imm32  # add to EAX
240 +error: 'xz' is two characters long which can look like raw hex bytes at a glance; use a different name
241 
242 :(scenario label_hex)
243 % Hide_errors = true;
244 == 0x1
245           # instruction                     effective address                                                   operand     displacement    immediate
246           # op          subop               mod             rm32          base        index         scale       r32
247           # 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
248 0xab:
249             05                                                                                                                              0x0d0c0b0a/imm32  # add to EAX
250 +error: '0xab' looks like a hex number; use a different name
251 
252 :(scenario label_negative_hex)
253 % Hide_errors = true;
254 == 0x1
255           # instruction                     effective address                                                   operand     displacement    immediate
256           # op          subop               mod             rm32          base        index         scale       r32
257           # 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
258  -a:  # indent to avoid looking like a trace_should_not_contain command for this scenario
259             05                                                                                                                              0x0d0c0b0a/imm32  # add to EAX
260 +error: '-a' starts with '-', which can be confused with a negative number; use a different name
261 
262 //: now that we have labels, we need to adjust segment size computation to
263 //: ignore them.
264 
265 :(scenario segment_size_ignores_labels)
266 == code  # 0x08048074
267 05/add 0x0d0c0b0a/imm32  # 5 bytes
268 foo:                     # 0 bytes
269 == data  # 0x08049079
270 bar:
271 00
272 +transform: segment 1 begins at address 0x08049079
273 
274 :(before "End num_bytes(curr) Special-cases")
275 else if (is_label(curr))
276   ;  // don't count it