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 17package com.android.dx.dex.file; 18 19import com.android.dex.util.ByteArrayByteInput; 20import com.android.dex.util.ByteInput; 21import com.android.dex.util.ExceptionWithContext; 22import com.android.dex.Leb128; 23import com.android.dx.dex.code.DalvCode; 24import com.android.dx.dex.code.DalvInsnList; 25import com.android.dx.dex.code.LocalList; 26import com.android.dx.dex.code.PositionList; 27import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_LINE; 28import static com.android.dx.dex.file.DebugInfoConstants.DBG_ADVANCE_PC; 29import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_LOCAL; 30import static com.android.dx.dex.file.DebugInfoConstants.DBG_END_SEQUENCE; 31import static com.android.dx.dex.file.DebugInfoConstants.DBG_FIRST_SPECIAL; 32import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_BASE; 33import static com.android.dx.dex.file.DebugInfoConstants.DBG_LINE_RANGE; 34import static com.android.dx.dex.file.DebugInfoConstants.DBG_RESTART_LOCAL; 35import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_EPILOGUE_BEGIN; 36import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_FILE; 37import static com.android.dx.dex.file.DebugInfoConstants.DBG_SET_PROLOGUE_END; 38import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL; 39import static com.android.dx.dex.file.DebugInfoConstants.DBG_START_LOCAL_EXTENDED; 40import com.android.dx.rop.cst.CstMethodRef; 41import com.android.dx.rop.cst.CstString; 42import com.android.dx.rop.type.Prototype; 43import com.android.dx.rop.type.StdTypeList; 44import com.android.dx.rop.type.Type; 45import java.io.IOException; 46import java.util.ArrayList; 47import 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 */ 54public 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 */ 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 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 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 public String toString() { 186 return String.format("[%x %s v%d %04x %04x %04x]", 187 address, isStart ? "start" : "end", reg, 188 nameIndex, typeIndex, signatureIndex); 189 } 190 } 191 192 /** 193 * Gets the decoded positions list. 194 * Valid after calling {@code decode}. 195 * 196 * @return positions list in ascending address order. 197 */ 198 public List<PositionEntry> getPositionList() { 199 return positions; 200 } 201 202 /** 203 * Gets the decoded locals list, in ascending start-address order. 204 * Valid after calling {@code decode}. 205 * 206 * @return locals list in ascending address order. 207 */ 208 public List<LocalEntry> getLocals() { 209 return locals; 210 } 211 212 /** 213 * Decodes the debug info sequence. 214 */ 215 public void decode() { 216 try { 217 decode0(); 218 } catch (Exception ex) { 219 throw ExceptionWithContext.withContext(ex, 220 "...while decoding debug info"); 221 } 222 } 223 224 /** 225 * Reads a string index. String indicies are offset by 1, and a 0 value 226 * in the stream (-1 as returned by this method) means "null" 227 * 228 * @return index into file's string ids table, -1 means null 229 * @throws IOException 230 */ 231 private int readStringIndex(ByteInput bs) throws IOException { 232 int offsetIndex = Leb128.readUnsignedLeb128(bs); 233 234 return offsetIndex - 1; 235 } 236 237 /** 238 * Gets the register that begins the method's parameter range (including 239 * the 'this' parameter for non-static methods). The range continues until 240 * {@code regSize} 241 * 242 * @return register as noted above. 243 */ 244 private int getParamBase() { 245 return regSize 246 - desc.getParameterTypes().getWordCount() - (isStatic? 0 : 1); 247 } 248 249 private void decode0() throws IOException { 250 ByteInput bs = new ByteArrayByteInput(encoded); 251 252 line = Leb128.readUnsignedLeb128(bs); 253 int szParams = Leb128.readUnsignedLeb128(bs); 254 StdTypeList params = desc.getParameterTypes(); 255 int curReg = getParamBase(); 256 257 if (szParams != params.size()) { 258 throw new RuntimeException( 259 "Mismatch between parameters_size and prototype"); 260 } 261 262 if (!isStatic) { 263 // Start off with implicit 'this' entry 264 LocalEntry thisEntry = 265 new LocalEntry(0, true, curReg, thisStringIdx, 0, 0); 266 locals.add(thisEntry); 267 lastEntryForReg[curReg] = thisEntry; 268 curReg++; 269 } 270 271 for (int i = 0; i < szParams; i++) { 272 Type paramType = params.getType(i); 273 LocalEntry le; 274 275 int nameIdx = readStringIndex(bs); 276 277 if (nameIdx == -1) { 278 /* 279 * Unnamed parameter; often but not always filled in by an 280 * extended start op after the prologue 281 */ 282 le = new LocalEntry(0, true, curReg, -1, 0, 0); 283 } else { 284 // TODO: Final 0 should be idx of paramType.getDescriptor(). 285 le = new LocalEntry(0, true, curReg, nameIdx, 0, 0); 286 } 287 288 locals.add(le); 289 lastEntryForReg[curReg] = le; 290 curReg += paramType.getCategory(); 291 } 292 293 for (;;) { 294 int opcode = bs.readByte() & 0xff; 295 296 switch (opcode) { 297 case DBG_START_LOCAL: { 298 int reg = Leb128.readUnsignedLeb128(bs); 299 int nameIdx = readStringIndex(bs); 300 int typeIdx = readStringIndex(bs); 301 LocalEntry le = new LocalEntry( 302 address, true, reg, nameIdx, typeIdx, 0); 303 304 locals.add(le); 305 lastEntryForReg[reg] = le; 306 } 307 break; 308 309 case DBG_START_LOCAL_EXTENDED: { 310 int reg = Leb128.readUnsignedLeb128(bs); 311 int nameIdx = readStringIndex(bs); 312 int typeIdx = readStringIndex(bs); 313 int sigIdx = readStringIndex(bs); 314 LocalEntry le = new LocalEntry( 315 address, true, reg, nameIdx, typeIdx, sigIdx); 316 317 locals.add(le); 318 lastEntryForReg[reg] = le; 319 } 320 break; 321 322 case DBG_RESTART_LOCAL: { 323 int reg = Leb128.readUnsignedLeb128(bs); 324 LocalEntry prevle; 325 LocalEntry le; 326 327 try { 328 prevle = lastEntryForReg[reg]; 329 330 if (prevle.isStart) { 331 throw new RuntimeException("nonsensical " 332 + "RESTART_LOCAL on live register v" 333 + reg); 334 } 335 336 le = new LocalEntry(address, true, reg, 337 prevle.nameIndex, prevle.typeIndex, 0); 338 } catch (NullPointerException ex) { 339 throw new RuntimeException( 340 "Encountered RESTART_LOCAL on new v" + reg); 341 } 342 343 locals.add(le); 344 lastEntryForReg[reg] = le; 345 } 346 break; 347 348 case DBG_END_LOCAL: { 349 int reg = Leb128.readUnsignedLeb128(bs); 350 LocalEntry prevle; 351 LocalEntry le; 352 353 try { 354 prevle = lastEntryForReg[reg]; 355 356 if (!prevle.isStart) { 357 throw new RuntimeException("nonsensical " 358 + "END_LOCAL on dead register v" + reg); 359 } 360 361 le = new LocalEntry(address, false, reg, 362 prevle.nameIndex, prevle.typeIndex, 363 prevle.signatureIndex); 364 } catch (NullPointerException ex) { 365 throw new RuntimeException( 366 "Encountered END_LOCAL on new v" + reg); 367 } 368 369 locals.add(le); 370 lastEntryForReg[reg] = le; 371 } 372 break; 373 374 case DBG_END_SEQUENCE: 375 // all done 376 return; 377 378 case DBG_ADVANCE_PC: 379 address += Leb128.readUnsignedLeb128(bs); 380 break; 381 382 case DBG_ADVANCE_LINE: 383 line += Leb128.readSignedLeb128(bs); 384 break; 385 386 case DBG_SET_PROLOGUE_END: 387 //TODO do something with this. 388 break; 389 390 case DBG_SET_EPILOGUE_BEGIN: 391 //TODO do something with this. 392 break; 393 394 case DBG_SET_FILE: 395 //TODO do something with this. 396 break; 397 398 default: 399 if (opcode < DBG_FIRST_SPECIAL) { 400 throw new RuntimeException( 401 "Invalid extended opcode encountered " 402 + opcode); 403 } 404 405 int adjopcode = opcode - DBG_FIRST_SPECIAL; 406 407 address += adjopcode / DBG_LINE_RANGE; 408 line += DBG_LINE_BASE + (adjopcode % DBG_LINE_RANGE); 409 410 positions.add(new PositionEntry(address, line)); 411 break; 412 413 } 414 } 415 } 416 417 /** 418 * Validates an encoded debug info stream against data used to encode it, 419 * throwing an exception if they do not match. Used to validate the 420 * encoder. 421 * 422 * @param info encoded debug info 423 * @param file {@code non-null;} file to refer to during decoding 424 * @param ref {@code non-null;} method whose info is being decoded 425 * @param code {@code non-null;} original code object that was encoded 426 * @param isStatic whether the method is static 427 */ 428 public static void validateEncode(byte[] info, DexFile file, 429 CstMethodRef ref, DalvCode code, boolean isStatic) { 430 PositionList pl = code.getPositions(); 431 LocalList ll = code.getLocals(); 432 DalvInsnList insns = code.getInsns(); 433 int codeSize = insns.codeSize(); 434 int countRegisters = insns.getRegistersSize(); 435 436 try { 437 validateEncode0(info, codeSize, countRegisters, 438 isStatic, ref, file, pl, ll); 439 } catch (RuntimeException ex) { 440 System.err.println("instructions:"); 441 insns.debugPrint(System.err, " ", true); 442 System.err.println("local list:"); 443 ll.debugPrint(System.err, " "); 444 throw ExceptionWithContext.withContext(ex, 445 "while processing " + ref.toHuman()); 446 } 447 } 448 449 private static void validateEncode0(byte[] info, int codeSize, 450 int countRegisters, boolean isStatic, CstMethodRef ref, 451 DexFile file, PositionList pl, LocalList ll) { 452 DebugInfoDecoder decoder 453 = new DebugInfoDecoder(info, codeSize, countRegisters, 454 isStatic, ref, file); 455 456 decoder.decode(); 457 458 /* 459 * Go through the decoded position entries, matching up 460 * with original entries. 461 */ 462 463 List<PositionEntry> decodedEntries = decoder.getPositionList(); 464 465 if (decodedEntries.size() != pl.size()) { 466 throw new RuntimeException( 467 "Decoded positions table not same size was " 468 + decodedEntries.size() + " expected " + pl.size()); 469 } 470 471 for (PositionEntry entry : decodedEntries) { 472 boolean found = false; 473 for (int i = pl.size() - 1; i >= 0; i--) { 474 PositionList.Entry ple = pl.get(i); 475 476 if (entry.line == ple.getPosition().getLine() 477 && entry.address == ple.getAddress()) { 478 found = true; 479 break; 480 } 481 } 482 483 if (!found) { 484 throw new RuntimeException ("Could not match position entry: " 485 + entry.address + ", " + entry.line); 486 } 487 } 488 489 /* 490 * Go through the original local list, in order, matching up 491 * with decoded entries. 492 */ 493 494 List<LocalEntry> decodedLocals = decoder.getLocals(); 495 int thisStringIdx = decoder.thisStringIdx; 496 int decodedSz = decodedLocals.size(); 497 int paramBase = decoder.getParamBase(); 498 499 /* 500 * Preflight to fill in any parameters that were skipped in 501 * the prologue (including an implied "this") but then 502 * identified by full signature. 503 */ 504 for (int i = 0; i < decodedSz; i++) { 505 LocalEntry entry = decodedLocals.get(i); 506 int idx = entry.nameIndex; 507 508 if ((idx < 0) || (idx == thisStringIdx)) { 509 for (int j = i + 1; j < decodedSz; j++) { 510 LocalEntry e2 = decodedLocals.get(j); 511 if (e2.address != 0) { 512 break; 513 } 514 if ((entry.reg == e2.reg) && e2.isStart) { 515 decodedLocals.set(i, e2); 516 decodedLocals.remove(j); 517 decodedSz--; 518 break; 519 } 520 } 521 } 522 } 523 524 int origSz = ll.size(); 525 int decodeAt = 0; 526 boolean problem = false; 527 528 for (int i = 0; i < origSz; i++) { 529 LocalList.Entry origEntry = ll.get(i); 530 531 if (origEntry.getDisposition() 532 == LocalList.Disposition.END_REPLACED) { 533 /* 534 * The encoded list doesn't represent replacements, so 535 * ignore them for the sake of comparison. 536 */ 537 continue; 538 } 539 540 LocalEntry decodedEntry; 541 542 do { 543 decodedEntry = decodedLocals.get(decodeAt); 544 if (decodedEntry.nameIndex >= 0) { 545 break; 546 } 547 /* 548 * A negative name index means this is an anonymous 549 * parameter, and we shouldn't expect to see it in the 550 * original list. So, skip it. 551 */ 552 decodeAt++; 553 } while (decodeAt < decodedSz); 554 555 int decodedAddress = decodedEntry.address; 556 557 if (decodedEntry.reg != origEntry.getRegister()) { 558 System.err.println("local register mismatch at orig " + i + 559 " / decoded " + decodeAt); 560 problem = true; 561 break; 562 } 563 564 if (decodedEntry.isStart != origEntry.isStart()) { 565 System.err.println("local start/end mismatch at orig " + i + 566 " / decoded " + decodeAt); 567 problem = true; 568 break; 569 } 570 571 /* 572 * The secondary check here accounts for the fact that a 573 * parameter might not be marked as starting at 0 in the 574 * original list. 575 */ 576 if ((decodedAddress != origEntry.getAddress()) 577 && !((decodedAddress == 0) 578 && (decodedEntry.reg >= paramBase))) { 579 System.err.println("local address mismatch at orig " + i + 580 " / decoded " + decodeAt); 581 problem = true; 582 break; 583 } 584 585 decodeAt++; 586 } 587 588 if (problem) { 589 System.err.println("decoded locals:"); 590 for (LocalEntry e : decodedLocals) { 591 System.err.println(" " + e); 592 } 593 throw new RuntimeException("local table problem"); 594 } 595 } 596} 597