1 // Copyright Yazan Dabain 2014. 2 // Distributed under the Boost Software License, Version 1.0. 3 // (See accompanying file LICENSE_1_0.txt or copy at 4 // http://www.boost.org/LICENSE_1_0.txt) 5 6 module elf.sections.debugline; 7 8 // this implementation follows the DWARF v3 documentation 9 10 import std.exception; 11 import std.range; 12 import std.conv : to; 13 import elf, elf.meta; 14 15 private import elf.sections.debugline.debugline32, elf.sections.debugline.debugline64; 16 17 static if (__VERSION__ >= 2079) 18 alias elfEnforce = enforce!ELFException; 19 else 20 alias elfEnforce = enforceEx!ELFException; 21 22 struct DebugLine { 23 private LineProgram[] m_lps; 24 25 private enum uint DWARF_64BIT_FLAG = 0xffff_ffff; 26 27 this(ELFSection section) { 28 this.m_lps = new LineProgram[0]; 29 30 ubyte[] lineProgramContents = section.contents(); 31 32 while (!lineProgramContents.empty) { 33 LineProgram lp; 34 35 // detect dwarf 32bit or 64bit 36 uint initialLength = * cast(uint*) lineProgramContents.ptr; 37 if (initialLength == DWARF_64BIT_FLAG) { 38 LineProgramHeader64L data = * cast(LineProgramHeader64L*) lineProgramContents.ptr; 39 lp.m_header = new LineProgramHeader64(data); 40 } else { 41 LineProgramHeader32L data = * cast(LineProgramHeader32L*) lineProgramContents.ptr; 42 lp.m_header = new LineProgramHeader32(data); 43 } 44 45 // start reading sections 46 lp.m_standardOpcodeLengths = new ubyte[lp.m_header.opcodeBase - 1]; 47 foreach (i; 0 .. lp.m_standardOpcodeLengths.length) { 48 lp.m_standardOpcodeLengths[i] = lineProgramContents[lp.m_header.datasize + i .. lp.m_header.datasize + i + 1][0]; 49 } 50 51 lp.m_files = new FileInfo[0]; 52 lp.m_dirs = new string[0]; 53 54 auto pathData = lineProgramContents[lp.m_header.datasize + lp.m_standardOpcodeLengths.length .. $]; 55 56 while (pathData[0] != 0) { 57 lp.m_dirs ~= (cast(char*) pathData.ptr).to!string(); 58 pathData = pathData[lp.m_dirs[$ - 1].length + 1 .. $]; 59 } 60 61 pathData.popFront(); 62 63 while (pathData[0] != 0) { 64 string file = (cast(char*) pathData.ptr).to!string(); 65 pathData = pathData[file.length + 1 .. $]; 66 67 auto dirIndex = pathData.readULEB128(); 68 auto lastMod = pathData.readULEB128(); // unused 69 auto fileLength = pathData.readULEB128(); // unused 70 71 lp.m_files ~= FileInfo(file, dirIndex); 72 } 73 74 static if (__VERSION__ < 2065) { // bug workaround for older versions 75 auto startOffset = lp.m_header.is32bit() ? uint.sizeof * 2 + ushort.sizeof : uint.sizeof + 2 * ulong.sizeof + ushort.sizeof; 76 auto endOffset = lp.m_header.is32bit() ? uint.sizeof : uint.sizeof + ulong.sizeof; 77 } else { 78 auto startOffset = lp.m_header.bits == 32 ? LineProgramHeader32L.minimumInstructionLength.offsetof : LineProgramHeader64L.minimumInstructionLength.offsetof; 79 auto endOffset = lp.m_header.bits == 32 ? LineProgramHeader32L.unitLength.sizeof : LineProgramHeader64L.unitLength.offsetof + LineProgramHeader64L.unitLength.sizeof; 80 } 81 82 auto program = lineProgramContents[startOffset + lp.m_header.headerLength() .. endOffset + lp.m_header.unitLength()]; 83 84 buildMachine(lp, program); 85 m_lps ~= lp; 86 87 lineProgramContents = lineProgramContents[endOffset + lp.m_header.unitLength() .. $]; 88 } 89 90 } 91 92 private void buildMachine(ref LineProgram lp, ubyte[] program) { 93 import std.range; 94 95 Machine m; 96 m.isStatement = lp.m_header.defaultIsStatement(); 97 98 lp.m_addresses = new AddressInfo[0]; 99 100 // import std.stdio, std.string; 101 // alias trace = writeln; 102 103 while (!program.empty) { 104 ubyte opcode = program.read!ubyte(); 105 106 if (opcode < lp.m_header.opcodeBase) { 107 108 switch (opcode) with (StandardOpcode) { 109 case extendedOp: 110 ulong len = program.readULEB128(); 111 ubyte eopcode = program.read!ubyte(); 112 113 switch (eopcode) with (ExtendedOpcode) { 114 case endSequence: 115 m.isEndSequence = true; 116 // trace("endSequence ", "0x%x".format(m.address)); 117 lp.m_addresses ~= AddressInfo(m.line, m.fileIndex, m.address); 118 m = Machine.init; 119 m.isStatement = lp.m_header.defaultIsStatement; 120 break; 121 122 case setAddress: 123 ulong address = program.read!ulong(); 124 // trace("setAddress ", "0x%x".format(address)); 125 m.address = address; 126 break; 127 128 case defineFile: 129 auto file = (cast(char*) program.ptr).to!string(); 130 program = program[file.length + 1 .. $]; 131 auto dirIndex = program.readULEB128(); // unused 132 auto fileMod = program.readULEB128(); // unused 133 auto fileSize = program.readULEB128(); // unused 134 // trace("defineFile"); 135 break; 136 137 default: 138 // unknown opcode 139 // trace("unknown extended opcode ", eopcode); 140 program = program[len - 1 .. $]; 141 break; 142 143 } 144 145 break; 146 147 case copy: 148 // trace("copy"); 149 lp.m_addresses ~= AddressInfo(m.line, m.fileIndex, m.address); 150 m.isBasicBlock = false; 151 m.isPrologueEnd = false; 152 m.isEpilogueBegin = false; 153 break; 154 155 case advancePC: 156 ulong op = readULEB128(program); 157 // trace("advancePC ", op * lp.m_header.minimumInstructionLength); 158 m.address += op * lp.m_header.minimumInstructionLength; 159 break; 160 161 case advanceLine: 162 long ad = readSLEB128(program); 163 // trace("advanceLine ", ad); 164 m.line += ad; 165 break; 166 167 case setFile: 168 uint index = readULEB128(program).to!uint(); 169 // trace("setFile to ", index); 170 m.fileIndex = index; 171 break; 172 173 case setColumn: 174 uint col = readULEB128(program).to!uint(); 175 // trace("setColumn ", col); 176 m.column = col; 177 break; 178 179 case negateStatement: 180 // trace("negateStatement"); 181 m.isStatement = !m.isStatement; 182 break; 183 184 case setBasicBlock: 185 // trace("setBasicBlock"); 186 m.isBasicBlock = true; 187 break; 188 189 case constAddPC: 190 m.address += (255 - lp.m_header.opcodeBase) / lp.m_header.lineRange * lp.m_header.minimumInstructionLength; 191 // trace("constAddPC ", "0x%x".format(m.address)); 192 break; 193 194 case fixedAdvancePC: 195 uint add = program.read!uint(); 196 // trace("fixedAdvancePC ", add); 197 m.address += add; 198 break; 199 200 case setPrologueEnd: 201 m.isPrologueEnd = true; 202 // trace("setPrologueEnd"); 203 break; 204 205 case setEpilogueBegin: 206 m.isEpilogueBegin = true; 207 // trace("setEpilogueBegin"); 208 break; 209 210 case setISA: 211 m.isa = readULEB128(program).to!uint(); 212 // trace("setISA ", m.isa); 213 break; 214 215 default: 216 throw new ELFException("unimplemented/invalid opcode " ~ opcode.to!string); 217 } 218 219 } else { 220 opcode -= lp.m_header.opcodeBase; 221 auto ainc = (opcode / lp.m_header.lineRange) * lp.m_header.minimumInstructionLength; 222 m.address += ainc; 223 auto linc = lp.m_header.lineBase + (opcode % lp.m_header.lineRange); 224 m.line += linc; 225 226 // trace("special ", ainc, " ", linc); 227 lp.m_addresses ~= AddressInfo(m.line, m.fileIndex, m.address); 228 } 229 } 230 } 231 232 const(LineProgram)[] programs() const { 233 return m_lps; 234 } 235 } 236 237 abstract class LineProgramHeader { 238 @property: 239 @ReadFrom("unitLength") ulong unitLength(); 240 @ReadFrom("dwarfVersion") ushort dwarfVersion(); 241 @ReadFrom("headerLength") ulong headerLength(); 242 @ReadFrom("minimumInstructionLength") ubyte minimumInstructionLength(); 243 @ReadFrom("defaultIsStatement") bool defaultIsStatement(); 244 @ReadFrom("lineBase") byte lineBase(); 245 @ReadFrom("lineRange") ubyte lineRange(); 246 @ReadFrom("opcodeBase") ubyte opcodeBase(); 247 248 size_t datasize(); 249 ubyte bits(); 250 } 251 252 final class LineProgramHeader32 : LineProgramHeader { 253 private LineProgramHeader32L m_data; 254 mixin(generateVirtualReads!(LineProgramHeader, "m_data")); 255 256 this(LineProgramHeader32L lph) { 257 this.m_data = lph; 258 } 259 260 @property override size_t datasize() { 261 return LineProgramHeader32L.sizeof; 262 } 263 264 @property override ubyte bits() { 265 return 32; 266 } 267 } 268 269 final class LineProgramHeader64 : LineProgramHeader { 270 private LineProgramHeader64L m_data; 271 mixin(generateVirtualReads!(LineProgramHeader, "m_data")); 272 273 this(LineProgramHeader64L lph) { 274 this.m_data = lph; 275 } 276 277 @property override size_t datasize() { 278 return LineProgramHeader64L.sizeof; 279 } 280 281 @property override ubyte bits() { 282 return 64; 283 } 284 } 285 286 private T read(T)(ref ubyte[] buffer) { 287 T result = *(cast(T*) buffer[0 .. T.sizeof].ptr); 288 buffer.popFrontExactly(T.sizeof); 289 return result; 290 } 291 292 private ulong readULEB128(ref ubyte[] buffer) { 293 import std.array; 294 ulong val = 0; 295 ubyte b; 296 uint shift = 0; 297 298 while (true) { 299 b = buffer.read!ubyte(); 300 301 val |= (b & 0x7f) << shift; 302 if ((b & 0x80) == 0) break; 303 shift += 7; 304 } 305 306 return val; 307 } 308 309 unittest { 310 ubyte[] data = [0xe5, 0x8e, 0x26, 0xDE, 0xAD, 0xBE, 0xEF]; 311 assert(readULEB128(data) == 624_485); 312 assert(data[] == [0xDE, 0xAD, 0xBE, 0xEF]); 313 } 314 315 private long readSLEB128(ref ubyte[] buffer) { 316 import std.array; 317 long val = 0; 318 uint shift = 0; 319 ubyte b; 320 int size = 8 << 3; 321 322 while (true) { 323 b = buffer.read!ubyte(); 324 val |= (b & 0x7f) << shift; 325 shift += 7; 326 if ((b & 0x80) == 0) 327 break; 328 } 329 330 if (shift < size && (b & 0x40) != 0) val |= -(1 << shift); 331 return val; 332 } 333 334 private enum StandardOpcode : ubyte { 335 extendedOp = 0, 336 copy = 1, 337 advancePC = 2, 338 advanceLine = 3, 339 setFile = 4, 340 setColumn = 5, 341 negateStatement = 6, 342 setBasicBlock = 7, 343 constAddPC = 8, 344 fixedAdvancePC = 9, 345 setPrologueEnd = 10, 346 setEpilogueBegin = 11, 347 setISA = 12, 348 } 349 350 private enum ExtendedOpcode : ubyte { 351 endSequence = 1, 352 setAddress = 2, 353 defineFile = 3, 354 } 355 356 private struct Machine { 357 ulong address = 0; 358 uint operationIndex = 0; 359 uint fileIndex = 1; 360 uint line = 1; 361 uint column = 0; 362 bool isStatement; 363 bool isBasicBlock = false; 364 bool isEndSequence = false; 365 bool isPrologueEnd = false; 366 bool isEpilogueBegin = false; 367 uint isa = 0; 368 uint discriminator = 0; 369 } 370 371 struct LineProgram { 372 private { 373 LineProgramHeader m_header; 374 375 ubyte[] m_standardOpcodeLengths; 376 FileInfo[] m_files; 377 string[] m_dirs; 378 379 AddressInfo[] m_addresses; 380 } 381 382 const(AddressInfo)[] addressInfo() const { 383 return m_addresses; 384 } 385 386 string fileFromIndex(ulong fileIndex) const { 387 import std.path : buildPath; 388 389 FileInfo f = m_files[fileIndex - 1]; 390 if (f.dirIndex == 0) return f.file; 391 else return buildPath(m_dirs[f.dirIndex - 1], f.file); 392 } 393 394 string[] allFiles() const { 395 import std.path : buildPath; 396 397 string[] result; 398 foreach (file; m_files) 399 { 400 if (file.dirIndex == 0) result ~= file.file; 401 else result ~= buildPath(m_dirs[file.dirIndex - 1], file.file); 402 } 403 404 return result; 405 } 406 } 407 408 struct FileInfo { 409 string file; 410 size_t dirIndex; 411 } 412 413 struct AddressInfo { 414 ulong line; 415 ulong fileIndex; 416 ulong address; 417 }