1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.dx.dex.file; 18 19 import com.android.dex.Leb128; 20 import com.android.dex.util.ByteArrayByteInput; 21 import com.android.dex.util.ByteInput; 22 import com.android.dex.util.ExceptionWithContext; 23 import com.android.dx.dex.code.DalvCode; 24 import com.android.dx.dex.code.DalvInsnList; 25 import com.android.dx.dex.code.LocalList; 26 import com.android.dx.dex.code.PositionList; 27 import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE; 28 import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC; 29 import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL; 30 import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE; 31 import static com.android.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL; 32 import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE; 33 import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE; 34 import static com.android.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL; 35 import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_EPILOGUE_BEGIN; 36 import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_FILE; 37 import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END; 38 import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL; 39 import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED; 40 import com.android.dx.rop.cst.CstMethodRef; 41 import com.android.dx.rop.cst.CstString; 42 import com.android.dx.rop.type.Prototype; 43 import com.android.dx.rop.type.StdTypeList; 44 import com.android.dx.rop.type.Type; 45 import java.io.IOException; 46 import java.util.ArrayList; 47 import java.util.List; 48 49 /** 50 * A decoder for the dex debug info state machine format. 51 * This code exists mostly as a reference implementation and test for 52 * for the {@code DebugInfoEncoder} 53 */ 54 public class DebugInfoDecoder { 55 /** encoded debug info */ 56 private final byte[] encoded; 57 58 /** positions decoded */ 59 private final ArrayList<PositionEntry> positions; 60 61 /** locals decoded */ 62 private final ArrayList<LocalEntry> locals; 63 64 /** size of code block in code units */ 65 private final int codesize; 66 67 /** indexed by register, the last local variable live in a reg */ 68 private final LocalEntry[] lastEntryForReg; 69 70 /** method descriptor of method this debug info is for */ 71 private final Prototype desc; 72 73 /** true if method is static */ 74 private final boolean isStatic; 75 76 /** dex file this debug info will be stored in */ 77 private final DexFile file; 78 79 /** 80 * register size, in register units, of the register space 81 * used by this method 82 */ 83 private final int regSize; 84 85 /** current decoding state: line number */ 86 private int line = 1; 87 88 /** current decoding state: bytecode address */ 89 private int address = 0; 90 91 /** string index of the string "this" */ 92 private final int thisStringIdx; 93 94 /** 95 * Constructs an instance. 96 * 97 * @param encoded encoded debug info 98 * @param codesize size of code block in code units 99 * @param regSize register size, in register units, of the register space 100 * used by this method 101 * @param isStatic true if method is static 102 * @param ref method descriptor of method this debug info is for 103 * @param file dex file this debug info will be stored in 104 */ DebugInfoDecoder(byte[] encoded, int codesize, int regSize, boolean isStatic, CstMethodRef ref, DexFile file)105 DebugInfoDecoder(byte[] encoded, int codesize, int regSize, 106 boolean isStatic, CstMethodRef ref, DexFile file) { 107 if (encoded == null) { 108 throw new NullPointerException("encoded == null"); 109 } 110 111 this.encoded = encoded; 112 this.isStatic = isStatic; 113 this.desc = ref.getPrototype(); 114 this.file = file; 115 this.regSize = regSize; 116 117 positions = new ArrayList<PositionEntry>(); 118 locals = new ArrayList<LocalEntry>(); 119 this.codesize = codesize; 120 lastEntryForReg = new LocalEntry[regSize]; 121 122 int idx = -1; 123 124 try { 125 idx = file.getStringIds().indexOf(new CstString("this")); 126 } catch (IllegalArgumentException ex) { 127 /* 128 * Silently tolerate not finding "this". It just means that 129 * no method has local variable info that looks like 130 * a standard instance method. 131 */ 132 } 133 134 thisStringIdx = idx; 135 } 136 137 /** 138 * An entry in the resulting postions table 139 */ 140 static private class PositionEntry { 141 /** bytecode address */ 142 public int address; 143 144 /** line number */ 145 public int line; 146 PositionEntry(int address, int line)147 public PositionEntry(int address, int line) { 148 this.address = address; 149 this.line = line; 150 } 151 } 152 153 /** 154 * An entry in the resulting locals table 155 */ 156 static private class LocalEntry { 157 /** address of event */ 158 public int address; 159 160 /** {@code true} iff it's a local start */ 161 public boolean isStart; 162 163 /** register number */ 164 public int reg; 165 166 /** index of name in strings table */ 167 public int nameIndex; 168 169 /** index of type in types table */ 170 public int typeIndex; 171 172 /** index of type signature in strings table */ 173 public int signatureIndex; 174 LocalEntry(int address, boolean isStart, int reg, int nameIndex, int typeIndex, int signatureIndex)175 public LocalEntry(int address, boolean isStart, int reg, int nameIndex, 176 int typeIndex, int signatureIndex) { 177 this.address = address; 178 this.isStart = isStart; 179 this.reg = reg; 180 this.nameIndex = nameIndex; 181 this.typeIndex = typeIndex; 182 this.signatureIndex = signatureIndex; 183 } 184 185 @Override toString()186 public String toString() { 187 return String.format("[%x %s v%d %04x %04x %04x]", 188 address, isStart ? "start" : "end", reg, 189 nameIndex, typeIndex, signatureIndex); 190 } 191 } 192 193 /** 194 * Gets the decoded positions list. 195 * Valid after calling {@code decode}. 196 * 197 * @return positions list in ascending address order. 198 */ getPositionList()199 public List<PositionEntry> getPositionList() { 200 return positions; 201 } 202 203 /** 204 * Gets the decoded locals list, in ascending start-address order. 205 * Valid after calling {@code decode}. 206 * 207 * @return locals list in ascending address order. 208 */ getLocals()209 public List<LocalEntry> getLocals() { 210 return locals; 211 } 212 213 /** 214 * Decodes the debug info sequence. 215 */ decode()216 public void decode() { 217 try { 218 decode0(); 219 } catch (Exception ex) { 220 throw ExceptionWithContext.withContext(ex, 221 "...while decoding debug info"); 222 } 223 } 224 225 /** 226 * Reads a string index. String indicies are offset by 1, and a 0 value 227 * in the stream (-1 as returned by this method) means "null" 228 * 229 * @return index into file's string ids table, -1 means null 230 * @throws IOException 231 */ readStringIndex(ByteInput bs)232 private int readStringIndex(ByteInput bs) throws IOException { 233 int offsetIndex = Leb128.readUnsignedLeb128(bs); 234 235 return offsetIndex - 1; 236 } 237 238 /** 239 * Gets the register that begins the method's parameter range (including 240 * the 'this' parameter for non-static methods). The range continues until 241 * {@code regSize} 242 * 243 * @return register as noted above. 244 */ getParamBase()245 private int getParamBase() { 246 return regSize 247 - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); 248 } 249 decode0()250 private void decode0() throws IOException { 251 ByteInput bs = new ByteArrayByteInput(encoded); 252 253 line = Leb128.readUnsignedLeb128(bs); 254 int szParams = Leb128.readUnsignedLeb128(bs); 255 StdTypeList params = desc.getParameterTypes(); 256 int curReg = getParamBase(); 257 258 if (szParams != params.size()) { 259 throw new RuntimeException( 260 "Mismatch between parameters_size and prototype"); 261 } 262 263 if (!isStatic) { 264 // Start off with implicit 'this' entry 265 LocalEntry thisEntry = 266 new LocalEntry(0, true, curReg, thisStringIdx, 0, 0); 267 locals.add(thisEntry); 268 lastEntryForReg[curReg] = thisEntry; 269 curReg++; 270 } 271 272 for (int i = 0; i < szParams; i++) { 273 Type paramType = params.getType(i); 274 LocalEntry le; 275 276 int nameIdx = readStringIndex(bs); 277 278 if (nameIdx == -1) { 279 /* 280 * Unnamed parameter; often but not always filled in by an 281 * extended start op after the prologue 282 */ 283 le = new LocalEntry(0, true, curReg, -1, 0, 0); 284 } else { 285 // TODO: Final 0 should be idx of paramType.getDescriptor(). 286 le = new LocalEntry(0, true, curReg, nameIdx, 0, 0); 287 } 288 289 locals.add(le); 290 lastEntryForReg[curReg] = le; 291 curReg += paramType.getCategory(); 292 } 293 294 for (;;) { 295 int opcode = bs.readByte() & 0xff; 296 297 switch (opcode) { 298 case DBG_START_LOCAL: { 299 int reg = Leb128.readUnsignedLeb128(bs); 300 int nameIdx = readStringIndex(bs); 301 int typeIdx = readStringIndex(bs); 302 LocalEntry le = new LocalEntry( 303 address, true, reg, nameIdx, typeIdx, 0); 304 305 locals.add(le); 306 lastEntryForReg[reg] = le; 307 } 308 break; 309 310 case DBG_START_LOCAL_EXTENDED: { 311 int reg = Leb128.readUnsignedLeb128(bs); 312 int nameIdx = readStringIndex(bs); 313 int typeIdx = readStringIndex(bs); 314 int sigIdx = readStringIndex(bs); 315 LocalEntry le = new LocalEntry( 316 address, true, reg, nameIdx, typeIdx, sigIdx); 317 318 locals.add(le); 319 lastEntryForReg[reg] = le; 320 } 321 break; 322 323 case DBG_RESTART_LOCAL: { 324 int reg = Leb128.readUnsignedLeb128(bs); 325 LocalEntry prevle; 326 LocalEntry le; 327 328 try { 329 prevle = lastEntryForReg[reg]; 330 331 if (prevle.isStart) { 332 throw new RuntimeException("nonsensical " 333 + "RESTART_LOCAL on live register v" 334 + reg); 335 } 336 337 le = new LocalEntry(address, true, reg, 338 prevle.nameIndex, prevle.typeIndex, 0); 339 } catch (NullPointerException ex) { 340 throw new RuntimeException( 341 "Encountered RESTART_LOCAL on new v" + reg); 342 } 343 344 locals.add(le); 345 lastEntryForReg[reg] = le; 346 } 347 break; 348 349 case DBG_END_LOCAL: { 350 int reg = Leb128.readUnsignedLeb128(bs); 351 LocalEntry prevle; 352 LocalEntry le; 353 354 try { 355 prevle = lastEntryForReg[reg]; 356 357 if (!prevle.isStart) { 358 throw new RuntimeException("nonsensical " 359 + "END_LOCAL on dead register v" + reg); 360 } 361 362 le = new LocalEntry(address, false, reg, 363 prevle.nameIndex, prevle.typeIndex, 364 prevle.signatureIndex); 365 } catch (NullPointerException ex) { 366 throw new RuntimeException( 367 "Encountered END_LOCAL on new v" + reg); 368 } 369 370 locals.add(le); 371 lastEntryForReg[reg] = le; 372 } 373 break; 374 375 case DBG_END_SEQUENCE: 376 // all done 377 return; 378 379 case DBG_ADVANCE_PC: 380 address += Leb128.readUnsignedLeb128(bs); 381 break; 382 383 case DBG_ADVANCE_LINE: 384 line += Leb128.readSignedLeb128(bs); 385 break; 386 387 case DBG_SET_PROLOGUE_END: 388 //TODO do something with this. 389 break; 390 391 case DBG_SET_EPILOGUE_BEGIN: 392 //TODO do something with this. 393 break; 394 395 case DBG_SET_FILE: 396 //TODO do something with this. 397 break; 398 399 default: 400 if (opcode < DBG_FIRST_SPECIAL) { 401 throw new RuntimeException( 402 "Invalid extended opcode encountered " 403 + opcode); 404 } 405 406 int adjopcode = opcode - DBG_FIRST_SPECIAL; 407 408 address += adjopcode / DBG_LINE_RANGE; 409 line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); 410 411 positions.add(new PositionEntry(address, line)); 412 break; 413 414 } 415 } 416 } 417 418 /** 419 * Validates an encoded debug info stream against data used to encode it, 420 * throwing an exception if they do not match. Used to validate the 421 * encoder. 422 * 423 * @param info encoded debug info 424 * @param file {@code non-null;} file to refer to during decoding 425 * @param ref {@code non-null;} method whose info is being decoded 426 * @param code {@code non-null;} original code object that was encoded 427 * @param isStatic whether the method is static 428 */ validateEncode(byte[] info, DexFile file, CstMethodRef ref, DalvCode code, boolean isStatic)429 public static void validateEncode(byte[] info, DexFile file, 430 CstMethodRef ref, DalvCode code, boolean isStatic) { 431 PositionList pl = code.getPositions(); 432 LocalList ll = code.getLocals(); 433 DalvInsnList insns = code.getInsns(); 434 int codeSize = insns.codeSize(); 435 int countRegisters = insns.getRegistersSize(); 436 437 try { 438 validateEncode0(info, codeSize, countRegisters, 439 isStatic, ref, file, pl, ll); 440 } catch (RuntimeException ex) { 441 System.err.println("instructions:"); 442 insns.debugPrint(System.err, " ", true); 443 System.err.println("local list:"); 444 ll.debugPrint(System.err, " "); 445 throw ExceptionWithContext.withContext(ex, 446 "while processing " + ref.toHuman()); 447 } 448 } 449 validateEncode0(byte[] info, int codeSize, int countRegisters, boolean isStatic, CstMethodRef ref, DexFile file, PositionList pl, LocalList ll)450 private static void validateEncode0(byte[] info, int codeSize, 451 int countRegisters, boolean isStatic, CstMethodRef ref, 452 DexFile file, PositionList pl, LocalList ll) { 453 DebugInfoDecoder decoder 454 = new DebugInfoDecoder(info, codeSize, countRegisters, 455 isStatic, ref, file); 456 457 decoder.decode(); 458 459 /* 460 * Go through the decoded position entries, matching up 461 * with original entries. 462 */ 463 464 List<PositionEntry> decodedEntries = decoder.getPositionList(); 465 466 if (decodedEntries.size() != pl.size()) { 467 throw new RuntimeException( 468 "Decoded positions table not same size was " 469 + decodedEntries.size() + " expected " + pl.size()); 470 } 471 472 for (PositionEntry entry : decodedEntries) { 473 boolean found = false; 474 for (int i = pl.size() - 1; i >= 0; i--) { 475 PositionList.Entry ple = pl.get(i); 476 477 if (entry.line == ple.getPosition().getLine() 478 && entry.address == ple.getAddress()) { 479 found = true; 480 break; 481 } 482 } 483 484 if (!found) { 485 throw new RuntimeException ("Could not match position entry: " 486 + entry.address + ", " + entry.line); 487 } 488 } 489 490 /* 491 * Go through the original local list, in order, matching up 492 * with decoded entries. 493 */ 494 495 List<LocalEntry> decodedLocals = decoder.getLocals(); 496 int thisStringIdx = decoder.thisStringIdx; 497 int decodedSz = decodedLocals.size(); 498 int paramBase = decoder.getParamBase(); 499 500 /* 501 * Preflight to fill in any parameters that were skipped in 502 * the prologue (including an implied "this") but then 503 * identified by full signature. 504 */ 505 for (int i = 0; i < decodedSz; i++) { 506 LocalEntry entry = decodedLocals.get(i); 507 int idx = entry.nameIndex; 508 509 if ((idx < 0) || (idx == thisStringIdx)) { 510 for (int j = i + 1; j < decodedSz; j++) { 511 LocalEntry e2 = decodedLocals.get(j); 512 if (e2.address != 0) { 513 break; 514 } 515 if ((entry.reg == e2.reg) && e2.isStart) { 516 decodedLocals.set(i, e2); 517 decodedLocals.remove(j); 518 decodedSz--; 519 break; 520 } 521 } 522 } 523 } 524 525 int origSz = ll.size(); 526 int decodeAt = 0; 527 boolean problem = false; 528 529 for (int i = 0; i < origSz; i++) { 530 LocalList.Entry origEntry = ll.get(i); 531 532 if (origEntry.getDisposition() 533 == LocalList.Disposition.END_REPLACED) { 534 /* 535 * The encoded list doesn't represent replacements, so 536 * ignore them for the sake of comparison. 537 */ 538 continue; 539 } 540 541 LocalEntry decodedEntry; 542 543 do { 544 decodedEntry = decodedLocals.get(decodeAt); 545 if (decodedEntry.nameIndex >= 0) { 546 break; 547 } 548 /* 549 * A negative name index means this is an anonymous 550 * parameter, and we shouldn't expect to see it in the 551 * original list. So, skip it. 552 */ 553 decodeAt++; 554 } while (decodeAt < decodedSz); 555 556 int decodedAddress = decodedEntry.address; 557 558 if (decodedEntry.reg != origEntry.getRegister()) { 559 System.err.println("local register mismatch at orig " + i + 560 " / decoded " + decodeAt); 561 problem = true; 562 break; 563 } 564 565 if (decodedEntry.isStart != origEntry.isStart()) { 566 System.err.println("local start/end mismatch at orig " + i + 567 " / decoded " + decodeAt); 568 problem = true; 569 break; 570 } 571 572 /* 573 * The secondary check here accounts for the fact that a 574 * parameter might not be marked as starting at 0 in the 575 * original list. 576 */ 577 if ((decodedAddress != origEntry.getAddress()) 578 && !((decodedAddress == 0) 579 && (decodedEntry.reg >= paramBase))) { 580 System.err.println("local address mismatch at orig " + i + 581 " / decoded " + decodeAt); 582 problem = true; 583 break; 584 } 585 586 decodeAt++; 587 } 588 589 if (problem) { 590 System.err.println("decoded locals:"); 591 for (LocalEntry e : decodedLocals) { 592 System.err.println(" " + e); 593 } 594 throw new RuntimeException("local table problem"); 595 } 596 } 597 } 598