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