1 /* 2 * [The "BSD licence"] 3 * Copyright (c) 2010 Ben Gruver (JesusFreke) 4 * All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. The name of the author may not be used to endorse or promote products 15 * derived from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 package org.jf.dexlib.Util; 30 31 import org.jf.dexlib.*; 32 33 import java.util.ArrayList; 34 import java.util.List; 35 36 /** 37 * This class is intended to provide an easy to use container to build up a method's debug info. You can easily add 38 * an "event" at a specific address, where an event is something like a line number, start/end local, etc. 39 * The events must be added such that the code addresses increase monotonically. This matches how a parser would 40 * generally behave, and is intended to increase performance. 41 */ 42 public class DebugInfoBuilder 43 { 44 private static final int LINE_BASE = -4; 45 private static final int LINE_RANGE = 15; 46 private static final int FIRST_SPECIAL = 0x0a; 47 48 private int lineStart = 0; 49 private ArrayList<String> parameterNames = new ArrayList<String>(); 50 private ArrayList<Event> events = new ArrayList<Event>(); 51 private int lastAddress = 0; 52 53 private boolean hasData; 54 55 private int currentAddress; 56 private int currentLine; 57 58 public DebugInfoBuilder() { 59 } 60 61 private void checkAddress(int address) { 62 if (lastAddress > address) { 63 throw new RuntimeException("Cannot add an event with an address before the address of the prior event"); 64 } 65 } 66 67 public void addParameterName(String parameterName) { 68 if (parameterName != null) { 69 hasData = true; 70 } 71 72 parameterNames.add(parameterName); 73 } 74 75 public void addLine(int address, int line) { 76 hasData = true; 77 78 checkAddress(address); 79 80 if (lineStart == 0) { 81 lineStart = line; 82 } 83 84 events.add(new LineEvent(address, line)); 85 } 86 87 public void addLocal(int address, int registerNumber, String localName, String localType) { 88 hasData = true; 89 90 checkAddress(address); 91 92 events.add(new StartLocalEvent(address, registerNumber, localName, localType)); 93 } 94 95 public void addLocalExtended(int address, int registerNumber, String localName, String localType, 96 String signature) { 97 hasData = true; 98 99 checkAddress(address); 100 101 events.add(new StartLocalExtendedEvent(address, registerNumber, localName, localType, signature)); 102 } 103 104 public void addEndLocal(int address, int registerNumber) { 105 hasData = true; 106 107 checkAddress(address); 108 109 events.add(new EndLocalEvent(address, registerNumber)); 110 } 111 112 public void addRestartLocal(int address, int registerNumber) { 113 hasData = true; 114 115 checkAddress(address); 116 117 events.add(new RestartLocalEvent(address, registerNumber)); 118 } 119 120 public void addPrologue(int address) { 121 hasData = true; 122 123 checkAddress(address); 124 125 events.add(new PrologueEvent(address)); 126 } 127 128 public void addEpilogue(int address) { 129 hasData = true; 130 131 checkAddress(address); 132 133 events.add(new EpilogueEvent(address)); 134 } 135 136 public void addSetFile(int address, String fileName) { 137 hasData = true; 138 139 checkAddress(address); 140 141 events.add(new SetFileEvent(address, fileName)); 142 } 143 144 public int getParameterNameCount() { 145 return parameterNames.size(); 146 } 147 148 public DebugInfoItem encodeDebugInfo(DexFile dexFile) { 149 if (!hasData) { 150 return null; 151 } 152 153 ByteArrayOutput out = new ByteArrayOutput(); 154 StringIdItem[] parameterNamesArray = new StringIdItem[parameterNames.size()]; 155 ArrayList<Item> referencedItems = new ArrayList<Item>(); 156 157 if (lineStart == 0) { 158 lineStart = 1; 159 } 160 161 currentLine = lineStart; 162 163 for (Event event: events) { 164 event.emit(dexFile, out, referencedItems); 165 } 166 emitEndSequence(out); 167 168 int index = 0; 169 for (String parameterName: parameterNames) { 170 if (parameterName == null) { 171 parameterNamesArray[index++] = null; 172 } else { 173 parameterNamesArray[index++] = StringIdItem.internStringIdItem(dexFile, parameterName); 174 } 175 } 176 177 Item[] referencedItemsArray = new Item[referencedItems.size()]; 178 referencedItems.toArray(referencedItemsArray); 179 return DebugInfoItem.internDebugInfoItem(dexFile, lineStart, parameterNamesArray, out.toByteArray(), 180 referencedItemsArray); 181 } 182 183 public static byte calculateSpecialOpcode(int lineDelta, int addressDelta) { 184 return (byte)(FIRST_SPECIAL + (addressDelta * LINE_RANGE) + (lineDelta - LINE_BASE)); 185 } 186 187 private interface Event 188 { 189 int getAddress(); 190 void emit(DexFile dexFile, Output out, List<Item> referencedItems); 191 } 192 193 private void emitEndSequence(Output out) { 194 out.writeByte(0); 195 } 196 197 private void emitAdvancePC(Output out, int address) { 198 int addressDelta = address-currentAddress; 199 200 if (addressDelta > 0) { 201 out.writeByte(1); 202 out.writeUnsignedLeb128(addressDelta); 203 currentAddress = address; 204 } 205 } 206 207 private void emitAdvanceLine(Output out, int lineDelta) { 208 out.writeByte(2); 209 out.writeSignedLeb128(lineDelta); 210 } 211 212 private void emitStartLocal(Output out, int registerNum) { 213 out.writeByte(3); 214 out.writeUnsignedLeb128(registerNum); 215 out.writeByte(1); 216 out.writeByte(1); 217 } 218 219 private void emitStartLocalExtended(Output out, int registerNum) { 220 out.writeByte(4); 221 out.writeUnsignedLeb128(registerNum); 222 out.writeByte(1); 223 out.writeByte(1); 224 out.writeByte(1); 225 } 226 227 private void emitEndLocal(Output out, int registerNum) { 228 out.writeByte(5); 229 out.writeUnsignedLeb128(registerNum); 230 } 231 232 private void emitRestartLocal(Output out, int registerNum) { 233 out.writeByte(6); 234 out.writeUnsignedLeb128(registerNum); 235 } 236 237 private void emitSetPrologueEnd(Output out) { 238 out.writeByte(7); 239 } 240 241 private void emitSetEpilogueBegin(Output out) { 242 out.writeByte(8); 243 } 244 245 private void emitSetFile(Output out) { 246 out.writeByte(9); 247 out.writeByte(1); 248 } 249 250 private void emitSpecialOpcode(Output out, byte opcode) { 251 out.writeByte(opcode); 252 } 253 254 private class LineEvent implements Event 255 { 256 private final int address; 257 private final int line; 258 259 public LineEvent(int address, int line) { 260 this.address = address; 261 this.line = line; 262 } 263 264 public int getAddress() { 265 return address; 266 } 267 268 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 269 int lineDelta = line - currentLine; 270 int addressDelta = address - currentAddress; 271 272 if (lineDelta < -4 || lineDelta > 10) { 273 emitAdvanceLine(out, lineDelta); 274 lineDelta = 0; 275 } 276 if (lineDelta < 2 && addressDelta > 16 || lineDelta > 1 && addressDelta > 15) { 277 emitAdvancePC(out, address); 278 addressDelta = 0; 279 } 280 281 //TODO: need to handle the case when the line delta is larger than a signed int 282 emitSpecialOpcode(out, calculateSpecialOpcode(lineDelta, addressDelta)); 283 284 currentAddress = address; 285 currentLine = line; 286 } 287 } 288 289 private class StartLocalEvent implements Event 290 { 291 private final int address; 292 private final int registerNum; 293 private final String localName; 294 private final String localType; 295 296 public StartLocalEvent(int address, int registerNum, String localName, String localType) { 297 this.address = address; 298 this.registerNum = registerNum; 299 this.localName = localName; 300 this.localType = localType; 301 } 302 303 public int getAddress() { 304 return address; 305 } 306 307 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 308 emitAdvancePC(out, address); 309 emitStartLocal(out, registerNum); 310 referencedItems.add(localName==null?null:StringIdItem.internStringIdItem(dexFile, localName)); 311 referencedItems.add(localType==null?null:TypeIdItem.internTypeIdItem(dexFile, 312 StringIdItem.internStringIdItem(dexFile, localType))); 313 } 314 } 315 316 private class StartLocalExtendedEvent implements Event 317 { 318 private final int address; 319 private final int registerNum; 320 private final String localName; 321 private final String localType; 322 private final String signature; 323 324 public StartLocalExtendedEvent(int address, int registerNum, String localName, String localType, 325 String signature) { 326 this.address = address; 327 this.registerNum = registerNum; 328 this.localName = localName; 329 this.localType = localType; 330 this.signature = signature; 331 } 332 333 public int getAddress() { 334 return address; 335 } 336 337 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 338 emitAdvancePC(out, address); 339 emitStartLocalExtended(out, registerNum); 340 if (localName != null) { 341 referencedItems.add(StringIdItem.internStringIdItem(dexFile, localName)); 342 } 343 if (localType != null) { 344 referencedItems.add(TypeIdItem.internTypeIdItem(dexFile, 345 StringIdItem.internStringIdItem(dexFile, localType))); 346 } 347 if (signature != null) { 348 referencedItems.add(StringIdItem.internStringIdItem(dexFile, signature)); 349 } 350 } 351 } 352 353 private class EndLocalEvent implements Event 354 { 355 private final int address; 356 private final int registerNum; 357 358 public EndLocalEvent(int address, int registerNum) { 359 this.address = address; 360 this.registerNum = registerNum; 361 } 362 363 public int getAddress() { 364 return address; 365 } 366 367 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 368 emitAdvancePC(out, address); 369 emitEndLocal(out, registerNum); 370 } 371 } 372 373 private class RestartLocalEvent implements Event 374 { 375 private final int address; 376 private final int registerNum; 377 378 public RestartLocalEvent(int address, int registerNum) { 379 this.address = address; 380 this.registerNum = registerNum; 381 } 382 383 public int getAddress() { 384 return address; 385 } 386 387 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 388 emitAdvancePC(out, address); 389 emitRestartLocal(out, registerNum); 390 } 391 } 392 393 private class PrologueEvent implements Event 394 { 395 private final int address; 396 397 public PrologueEvent(int address) { 398 this.address = address; 399 } 400 401 public int getAddress() { 402 return address; 403 } 404 405 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 406 emitAdvancePC(out, address); 407 emitSetPrologueEnd(out); 408 } 409 } 410 411 private class EpilogueEvent implements Event 412 { 413 private final int address; 414 415 public EpilogueEvent(int address) { 416 this.address = address; 417 } 418 419 public int getAddress() { 420 return address; 421 } 422 423 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 424 emitAdvancePC(out, address); 425 emitSetEpilogueBegin(out); 426 } 427 } 428 429 private class SetFileEvent implements Event 430 { 431 private final int address; 432 private final String fileName; 433 434 public SetFileEvent(int address, String fileName) { 435 this.address = address; 436 this.fileName = fileName; 437 } 438 439 public int getAddress() { 440 return address; 441 } 442 443 public void emit(DexFile dexFile, Output out, List<Item> referencedItems) { 444 emitAdvancePC(out, address); 445 emitSetFile(out); 446 if (fileName != null) { 447 referencedItems.add(StringIdItem.internStringIdItem(dexFile, fileName)); 448 } 449 } 450 } 451 } 452