# GDB plugin for etnaviv driver debugging. # This needs gdb 7.5+ to work. # # usage (from gdb): # source /path/to/etnaviv_gdb.py # # Commands: # gpu-state (prefix|uniforms) # Show full GPU state (default) or only registers with a certain prefix. # The special prefix 'uniforms' shows only the shader uniforms. # gpu-dis # Disassemble the current shaders. # from __future__ import print_function, division, unicode_literals import sys,os from collections import namedtuple # Add script directory to python path (seems that gdb does not do this automatically) # need this to import etnaviv.* if not os.path.dirname(__file__) in sys.path: sys.path.append(os.path.dirname(__file__)) from etnaviv.util import rnndb_path from etnaviv.parse_rng import parse_rng_file, format_path, BitSet, Domain, Stripe, Register, Array, BaseType from etnaviv.dump_cmdstream_util import int_as_float, fixp_as_float, offset_to_uniform from etnaviv.rnn_domain_visitor import DomainVisitor from etnaviv.parse_command_buffer import parse_command_buffer # (gdb) print ((struct etna_pipe_context*)((struct gl_context*)_glapi_Context)->st->cso_context->pipe) # pipe-> gpu3d has current GPU state # pipe->ctx (etna_ctx) has command buffers # Ideas: # - print changes in current GPU state highlighted # - can we hook etna_flush and display the current command buffer contents? # set breakpoint that invokes python code? is that possible? # (see gdb.Breakpoint) # - dump current command buffers RegStride = namedtuple('RegStride', ['stride', 'length']) RegInfo = namedtuple('RegInfo', ['reg', 'offset', 'strides']) class StateCollector(DomainVisitor): ''' Walk rnndb domain, collect all register names, build a dictionary of register name to object. ''' def __init__(self): self.registers = {} self.path = [(0, [], [])] def extend_path(self, node): offset, name, strides = self.path[-1] offset = offset + node.offset if node.name is not None: # don't append, we want a copy name = name + [node.name] if node.length != 1: # don't append, we want a copy strides = strides + [RegStride(node.stride, node.length)] self.path.append((offset, name, strides)) def visit_stripe(self, node): self.extend_path(node) DomainVisitor.visit_stripe(self, node) self.path.pop() def visit_array(self, node): self.extend_path(node) DomainVisitor.visit_array(self, node) self.path.pop() def visit_register(self, node): self.extend_path(node) offset, name, strides = self.path[-1] name = '_'.join(name) # sort strides by decreasing size to make sure child addresses are in increasing order strides.sort(key=lambda x:-x.stride) self.registers[name] = RegInfo(node, offset, strides) self.path.pop() def build_registers_dict(domain): ''' Build dictionary of GPU registers from their C name (_ delimited) to a RegisterInfo named tuple with (register description, offset, strides). ''' col = StateCollector() col.visit(domain) return col.registers def lookup_etna_state(): ''' Return etna pipe and screen from current Mesa GL context. @returns a tuple (pipe, screen) ''' glapi_context_sym,_ = gdb.lookup_symbol('_glapi_Context') fbs_sym,_ = gdb.lookup_symbol('_fbs') etna_pipe_context_type = gdb.lookup_type('struct etna_pipe_context').pointer() etna_screen_type = gdb.lookup_type('struct etna_screen').pointer() if glapi_context_sym is not None: # Mesa gl_context_type = gdb.lookup_type('struct gl_context').pointer() glapi_context = glapi_context_sym.value() glapi_context = glapi_context.cast(gl_context_type) pipe = glapi_context['st']['cso_context']['pipe'] screen = pipe['screen'] elif fbs_sym is not None: # fbs scaffold fbs_sym = fbs_sym.value() pipe = fbs_sym['pipe'] screen = fbs_sym['screen'] else: print("Unable to find etna context") return (None, None) # cast to specific types pipe = pipe.cast(etna_pipe_context_type) screen = screen.cast(etna_screen_type) return (pipe, screen) ### gpu-state ### # state formatting def hex_and_float(x): return '%08x (%f)' % (x, int_as_float(x)) def hex_and_float_fixp(x): return '%08x (%f)' % (x, fixp_as_float(x)) special_format = { 'VS_UNIFORMS': hex_and_float, 'PS_UNIFORMS': hex_and_float, 'SE_SCISSOR_LEFT': hex_and_float_fixp, 'SE_SCISSOR_RIGHT': hex_and_float_fixp, 'SE_SCISSOR_TOP': hex_and_float_fixp, 'SE_SCISSOR_BOTTOM': hex_and_float_fixp } def format_state(reg, key, val): if key in special_format: return special_format[key](int(val)) else: return reg.describe(int(val)) class GPUState(gdb.Command): """Etnaviv: show GPU state.""" def __init__ (self, state_xml): super(GPUState, self).__init__ ("gpu-state", gdb.COMMAND_USER) self.state_xml = state_xml self.state_map = self.state_xml.lookup_domain('VIVS') self.registers = build_registers_dict(self.state_map) def print_uniforms_for(self, out, stype, uniforms, count): out.write('[%s uniforms]:\n' % stype) base = 0 comps = 'xyzw' while base < count: sub = [] for idx in xrange(0, 4): # last uniform can be partial if (base + idx)= 0x05000 and pos < 0x06000) or (pos >= 0x07000 and pos < 0x08000): num = pos & 0xFFF desc += ' := %f (%s)' % (int_as_float(value), offset_to_uniform(num)) elif path is not None: register = path[-1][0] desc += ' := ' + register.describe(value) return desc def stop(self): commandBuffer_sym,_ = gdb.lookup_symbol('commandBuffer') commandBuffer = commandBuffer_sym.value(gdb.newest_frame()) # offset, startOffset # physical offset = int(commandBuffer['offset']) startOffset = int(commandBuffer['startOffset']) physical = int(commandBuffer['physical'].cast(self.size_t_type)) # GPU address logical = int(commandBuffer['logical'].cast(self.size_t_type)) # CPU address buffer = indirect_memcpy(logical + startOffset, logical + offset) data = struct.unpack_from(b'%dI' % (len(buffer)/4), buffer) # "cast" byte-based buffer to uint32_t # iterate over buffer, one 32 bit word at a time f = sys.stdout if self.output is None else self.output f.write('viv_commit:\n') for rec in parse_command_buffer(data): if rec.state_info is not None: desc = self.format_state(rec.state_info.pos, rec.value, rec.state_info.format) else: desc = rec.desc f.write(' [%08x] %08x %s\n' % (physical + startOffset + rec.ptr*4, rec.value, desc)) f.flush() return self.do_stop class GPUTrace(gdb.Command): """Etnaviv: trace submitted command buffers Usage: gpu-trace Enable/disable cmdbuffer trace gpu-trace stop Enable/disable stopping on commit gpu-trace output stdout Set tracing output to stdout (default) gpu-trace output file Set tracing output to file """ def __init__ (self, state_xml): super(GPUTrace, self).__init__ ("gpu-trace", gdb.COMMAND_USER) self.enabled = False self.state_xml = state_xml self.state_map = self.state_xml.lookup_domain('VIVS') self.bp = None self.stop_on_commit = False self.output = None def invoke(self, arg, from_tty): self.dont_repeat() arg = gdb.string_to_argv(arg) if not arg: pass # just check status elif arg[0] == 'off': # disable if not self.enabled: print("GPU tracing is not enabled") return self.enabled = False self.bp.delete() self.bp = None elif arg[0] == 'on': # enable if self.enabled: print("GPU tracing is already enabled") return self.enabled = True self.bp = CommitBreakpoint(self.state_map, self.stop_on_commit, self.output) elif arg[0] == 'stop': if arg[1] == 'off': self.stop_on_commit = False elif arg[1] == 'on': self.stop_on_commit = True else: print("Unrecognized stop mode %s" % arg[1]) if self.bp: # if breakpoint currently exists, change parameter on the fly self.bp.do_stop = self.stop_on_commit elif arg[0].startswith('out'): # output new_output = self.output if arg[1] == 'file': new_output = open(arg[2],'w') elif arg[1] == 'stdout': new_output = None else: print("Unrecognized output mode %s" % arg[1]) if new_output is not self.output: if self.output is not None: # close old output file self.output.close() self.output = new_output if self.bp: # if breakpoint currently exists, change parameter on the fly self.bp.output = self.output else: print("Unrecognized tracing mode %s" % arg[0]) return print("Etnaviv command buffer tracing %s (stop %s, output to %s)" % ( ['disabled','enabled'][self.enabled], ['disabled','enabled'][self.stop_on_commit], self.output or '')) ### gpu-inspect ### class GPUInspect(gdb.Command): """Etnaviv: inspect etna resource Usage: gpu-inspect """ def __init__ (self): super(GPUInspect, self).__init__ ("gpu-inspect", gdb.COMMAND_USER) def invoke(self, arg, from_tty): self.dont_repeat() arg = gdb.string_to_argv(arg) arg[0] = gdb.parse_and_eval(arg[0]) etna_resource_type = gdb.lookup_type('struct etna_resource').pointer() res = arg[0].cast(etna_resource_type) # this is very, very primitive now # dump first 128 bytes of level 0 by default, as floats # XXX make this more flexible logical = res['levels'][0]['logical'] size = 128 buffer = indirect_memcpy(logical, logical+size) data = struct.unpack_from(b'%df' % (len(buffer)/4), buffer) print(data) state_xml = parse_rng_file(rnndb_path('state.xml')) isa_xml = parse_rng_file(rnndb_path('isa.xml')) GPUState(state_xml) GPUDisassemble(isa_xml) GPUTrace(state_xml) GPUInspect()