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 }