1 #-------------------------------------------------------------------------------
2 # elftools: dwarf/lineprogram.py
4 # DWARF line number program
6 # Eli Bendersky (eliben@gmail.com)
7 # This code is in the public domain
8 #-------------------------------------------------------------------------------
11 from collections import namedtuple
13 from ..common.utils import struct_parse
14 from .constants import *
17 # LineProgramEntry - an entry in the line program.
18 # A line program is a sequence of encoded entries. Some of these entries add a
19 # new LineState (mapping between line and address), and some don't.
22 # The command/opcode - always numeric. For standard commands - it's the opcode
23 # that can be matched with one of the DW_LNS_* constants. For extended commands
24 # it's the extended opcode that can be matched with one of the DW_LNE_*
25 # constants. For special commands, it's the opcode itself.
28 # A list of decoded arguments of the command.
31 # Since extended commands are encoded by a zero followed by an extended
32 # opcode, and these extended opcodes overlap with other opcodes, this
33 # flag is needed to mark that the command has an extended opcode.
36 # For commands that add a new state, it's the relevant LineState object.
37 # For commands that don't add a new state, it's None.
39 LineProgramEntry = namedtuple(
40 'LineProgramEntry', 'command is_extended args state')
43 class LineState(object):
44 """ Represents a line program state (or a "row" in the matrix
45 describing debug location information for addresses).
46 The instance variables of this class are the "state machine registers"
47 described in section 6.2.2 of DWARFv3
49 def __init__(self, default_is_stmt):
54 self.is_stmt = default_is_stmt
55 self.basic_block = False
56 self.end_sequence = False
57 self.prologue_end = False
58 self.epilogue_begin = False
62 a = ['<LineState %x:' % id(self)]
63 a.append(' address = 0x%x' % self.address)
64 for attr in ('file', 'line', 'column', 'is_stmt', 'basic_block',
65 'end_sequence', 'prologue_end', 'epilogue_begin', 'isa'):
66 a.append(' %s = %s' % (attr, getattr(self, attr)))
67 return '\n'.join(a) + '>\n'
70 class LineProgram(object):
71 """ Builds a "line table", which is essentially the matrix described
72 in section 6.2 of DWARFv3. It's a list of LineState objects,
73 sorted by increasing address, so it can be used to obtain the
74 state information for each address.
76 def __init__(self, header, stream, structs,
77 program_start_offset, program_end_offset):
80 The header of this line program. Note: LineProgram may modify
81 its header by appending file entries if DW_LNE_define_file
82 instructions are encountered.
85 The stream this program can be read from.
88 A DWARFStructs instance suitable for this line program
90 program_{start|end}_offset:
91 Offset in the debug_line section stream where this program
92 starts (the actual program, after the header), and where it
94 The actual range includes start but not end: [start, end - 1]
98 self.structs = structs
99 self.program_start_offset = program_start_offset
100 self.program_end_offset = program_end_offset
101 self._decoded_entries = None
103 def get_entries(self):
104 """ Get the decoded entries for this line program. Return a list of
105 LineProgramEntry objects.
106 Note that this contains more information than absolutely required
107 for the line table. The line table can be easily extracted from
108 the list of entries by looking only at entries with non-None
109 state. The extra information is mainly for the purposes of display
110 with readelf and debugging.
112 if self._decoded_entries is None:
113 self._decoded_entries = self._decode_line_program()
114 return self._decoded_entries
116 #------ PRIVATE ------#
118 def __getitem__(self, name):
119 """ Implement dict-like access to header entries
121 return self.header[name]
123 def _decode_line_program(self):
125 state = LineState(self.header['default_is_stmt'])
127 def add_entry_new_state(cmd, args, is_extended=False):
128 # Add an entry that sets a new state.
129 # After adding, clear some state registers.
130 entries.append(LineProgramEntry(
131 cmd, is_extended, args, copy.copy(state)))
132 state.basic_block = False
133 state.prologue_end = False
134 state.epilogue_begin = False
136 def add_entry_old_state(cmd, args, is_extended=False):
137 # Add an entry that doesn't visibly set a new state
138 entries.append(LineProgramEntry(cmd, is_extended, args, None))
140 offset = self.program_start_offset
141 while offset < self.program_end_offset:
142 opcode = struct_parse(
143 self.structs.Dwarf_uint8(''),
147 # As an exercise in avoiding premature optimization, if...elif
148 # chains are used here for standard and extended opcodes instead
149 # of dispatch tables. This keeps the code much cleaner. Besides,
150 # the majority of instructions in a typical program are special
152 if opcode >= self.header['opcode_base']:
153 # Special opcode (follow the recipe in 6.2.5.1)
154 adjusted_opcode = opcode - self['opcode_base']
155 address_addend = ((adjusted_opcode // self['line_range']) *
156 self['minimum_instruction_length'])
157 state.address += address_addend
158 line_addend = (self['line_base'] +
159 adjusted_opcode % self['line_range'])
160 state.line += line_addend
161 add_entry_new_state(opcode, [line_addend, address_addend])
163 # Extended opcode: start with a zero byte, followed by
164 # instruction size and the instruction itself.
165 inst_len = struct_parse(self.structs.Dwarf_uleb128(''),
167 ex_opcode = struct_parse(self.structs.Dwarf_uint8(''),
170 if ex_opcode == DW_LNE_end_sequence:
171 state.end_sequence = True
172 add_entry_new_state(ex_opcode, [], is_extended=True)
174 state = LineState(self.header['default_is_stmt'])
175 elif ex_opcode == DW_LNE_set_address:
176 operand = struct_parse(self.structs.Dwarf_target_addr(''),
178 state.address = operand
179 add_entry_old_state(ex_opcode, [operand], is_extended=True)
180 elif ex_opcode == DW_LNE_define_file:
181 operand = struct_parse(
182 self.structs.Dwarf_lineprog_file_entry, self.stream)
183 self['file_entry'].append(operand)
184 add_entry_old_state(ex_opcode, [operand], is_extended=True)
186 # Unknown, but need to roll forward the stream because the
187 # length is specified. Seek forward inst_len - 1 because
188 # we've already read the extended opcode, which takes part
190 self.stream.seek(inst_len - 1, os.SEEK_CUR)
191 else: # 0 < opcode < opcode_base
193 if opcode == DW_LNS_copy:
194 add_entry_new_state(opcode, [])
195 elif opcode == DW_LNS_advance_pc:
196 operand = struct_parse(self.structs.Dwarf_uleb128(''),
199 operand * self.header['minimum_instruction_length'])
200 state.address += address_addend
201 add_entry_old_state(opcode, [address_addend])
202 elif opcode == DW_LNS_advance_line:
203 operand = struct_parse(self.structs.Dwarf_sleb128(''),
205 state.line += operand
206 elif opcode == DW_LNS_set_file:
207 operand = struct_parse(self.structs.Dwarf_uleb128(''),
210 add_entry_old_state(opcode, [operand])
211 elif opcode == DW_LNS_set_column:
212 operand = struct_parse(self.structs.Dwarf_uleb128(''),
214 state.column = operand
215 add_entry_old_state(opcode, [operand])
216 elif opcode == DW_LNS_negate_stmt:
217 state.is_stmt = not state.is_stmt
218 add_entry_old_state(opcode, [operand])
219 elif opcode == DW_LNS_set_basic_block:
220 state.basic_block = True
221 add_entry_old_state(opcode, [operand])
222 elif opcode == DW_LNS_const_add_pc:
223 adjusted_opcode = 255 - self['opcode_base']
224 address_addend = ((adjusted_opcode // self['line_range']) *
225 self['minimum_instruction_length'])
226 state.address += address_addend
227 add_entry_old_state(opcode, [address_addend])
228 elif opcode == DW_LNS_fixed_advance_pc:
229 operand = struct_parse(self.structs.Dwarf_uint16(''),
231 state.address += operand
232 add_entry_old_state(opcode, [operand])
233 elif opcode == DW_LNS_set_prologue_end:
234 state.prologue_end = True
235 add_entry_old_state(opcode, [])
236 elif opcode == DW_LNS_set_epilogue_begin:
237 state.epilogue_begin = True
238 add_entry_old_state(opcode, [])
239 elif opcode == DW_LNS_set_isa:
240 operand = struct_parse(self.structs.Dwarf_uleb128(''),
243 add_entry_old_state(opcode, [operand])
245 dwarf_assert(False, 'Invalid standard line program opcode: %s' % (
247 offset = self.stream.tell()