Mailinglist Archive: yast-devel (57 mails)

< Previous Next >
[yast-devel] post mortem debugger for ycp
Hi,

looking at the log produced by running yast with Y2DEBUG set to 1 I
realized that it contained enough information to write a debugger that
parses it.

I saw the announcement of the ycp debugger and must admit that I
haven't looked at it. But working with the recorded trace allows some
fancy stuff like walking backwards and it's written in python so it is
easily hackable.

Well, here it is, it served me well and it might be useful for someone
else. Feel free to put it in the repository if you like.

Justus

~~~ snip ~~~

$ Y2DEBUG=1 /usr/lib/YaST2/bin/y2base -l - -c
/usr/share/YaST2/data/testsuite/log.conf -I ../src -M ../src
tests/Network_YaPI.ycp UI 2> log
$ ycppmd.py log
[...]
pmd> display Devs OriginalDevs
1: 2011-08-30 10:54:26 <0> opensuse(21525) [libycp]
pathsearch.cc(getPaths):68 getPaths /y2update /root/.yast2 /usr/share/YaST2

Devs = UNDEFINED
OriginalDevs = UNDEFINED
[...]
pmd> goto 38176
38176: 2011-08-30 10:54:29 <0> opensuse(21525) [liby2]
SymbolEntry.cc(setValue):107 SymbolEntry::setValue (Devs@0x1507900 =
'$["eth":$["eth0":$["BOOTPROTO":"dhcp4", "STARTMODE":""],
"eth1":$["BOOTPROTO":"static", "IPADDR":"1.2.3.4", "MTU":"1234",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth2":$["BOOTPROTO":"static", "IPADDR":"1.2.3.5", "NETMASK":"255.255.255.0",
"PREFIXLEN":"24", "STARTMODE":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7/24", "STARTMODE":"auto"], "eth4":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":""], "eth5":$["BOOTPROTO":"static", "STARTMODE":""]],
"vlan":$["eth5.23":$["BOOTPROTO":"static", "IPADDR":"1.2.3.8/24",
"STARTMODE":"auto"]]]')

Devs = $["eth":$["eth0":$["BOOTPROTO":"dhcp4", "STARTMODE":""],
"eth1":$["BOOTPROTO":"static", "IPADDR":"1.2.3.4", "MTU":"1234",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth2":$["BOOTPROTO":"static", "IPADDR":"1.2.3.5", "NETMASK":"255.255.255.0",
"PREFIXLEN":"24", "STARTMODE":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7/24", "STARTMODE":"auto"], "eth4":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":""], "eth5":$["BOOTPROTO":"static", "STARTMODE":""]],
"vlan":$["eth5.23":$["BOOTPROTO":"static", "IPADDR":"1.2.3.8/24",
"STARTMODE":"auto"]]]
OriginalDevs = UNDEFINED
[...]
pmd> bt full
0 - [main]
{'READ': '$["init":$["scripts":$["exists":false]],
"network":$["section":$["eth0":$[], "eth1":$[], "eth2":$[], "eth3":$[],
"eth4":$[], "eth5":$[], "eth5.23":$[]],
"value":$["eth0":$["BOOTPROTO":"dhcp4"], "eth1":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.4/24", "MTU":"1234"], "eth2":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.5/24", "PREFIXLEN":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.6", "PREFIXLEN":"24"], "eth4":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7", "NETMASK":"255.255.255.0"], "eth5":$["BOOTPROTO":"static"],
"eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5", "IPADDR":"1.2.3.8/24",
"VLAN_ID":"23"]]], "routes":[$["destination":"default",
"gateway":"10.20.30.40"]],
"sysconfig":$["network":$["config":$["NETCONFIG_DNS_STATIC_SEARCHLIST":"suse.cz
suse.de", "NETCONFIG_DNS_STATIC_SERVERS":"208.67.222.222 208.67.220.220"],
"dhcp":$["DHCLIENT_SET_HOSTNAME":"yes", "WRITE_HOSTNAME_TO_HOSTS":"no"]]],
"target":$["bash_output":"laptop.suse.cz", "size":27,
"string":"laptop.suse.cz"]]', 'dummy_log_string': '"LOGTHIS_SECRET_314 "',
'EXEC': '$["target":$["bash_output":$["exit":0, "stdout":"laptop.suse.cz"]]]'}
1 - TEST
{'FUNCTION': 'YaPI::NETWORK::Write
($["interface":$["eth5.23":$["bootproto":"static", "ipaddr":"1.2.3.8/24",
"vlan_etherdevice":"eth5", "vlan_id":"42"]]])', 'INPUT':
'[$["init":$["scripts":$["exists":false]], "network":$["section":$["eth0":$[],
"eth1":$[], "eth2":$[], "eth3":$[], "eth4":$[], "eth5":$[], "eth5.23":$[]],
"value":$["eth0":$["BOOTPROTO":"dhcp4"], "eth1":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.4/24", "MTU":"1234"], "eth2":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.5/24", "PREFIXLEN":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.6", "PREFIXLEN":"24"], "eth4":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7", "NETMASK":"255.255.255.0"], "eth5":$["BOOTPROTO":"static"],
"eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5", "IPADDR":"1.2.3.8/24",
"VLAN_ID":"23"]]], "routes":[$["destination":"default",
"gateway":"10.20.30.40"]],
"sysconfig":$["network":$["config":$["NETCONFIG_DNS_STATIC_SEARCHLIST":"suse.cz
suse.de", "NETCONFIG_DNS_STATIC_SERVERS":"208.67.222.222 208.67.220.220"],
"dhcp":$["DHCLIENT_SET_HOSTNAME":"yes", "WRITE_HOSTNAME_TO_HOSTS":"no"]]],
"target":$["bash_output":"laptop.suse.cz", "size":27,
"string":"laptop.suse.cz"]]]', 'DEFAULT': 'nil'}
2 - Test
{'FUNCTION': 'YaPI::NETWORK::Write
($["interface":$["eth5.23":$["bootproto":"static", "ipaddr":"1.2.3.8/24",
"vlan_etherdevice":"eth5", "vlan_id":"42"]]])', 'INPUT':
'[$["init":$["scripts":$["exists":false]], "network":$["section":$["eth0":$[],
"eth1":$[], "eth2":$[], "eth3":$[], "eth4":$[], "eth5":$[], "eth5.23":$[]],
"value":$["eth0":$["BOOTPROTO":"dhcp4"], "eth1":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.4/24", "MTU":"1234"], "eth2":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.5/24", "PREFIXLEN":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.6", "PREFIXLEN":"24"], "eth4":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7", "NETMASK":"255.255.255.0"], "eth5":$["BOOTPROTO":"static"],
"eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5", "IPADDR":"1.2.3.8/24",
"VLAN_ID":"23"]]], "routes":[$["destination":"default",
"gateway":"10.20.30.40"]],
"sysconfig":$["network":$["config":$["NETCONFIG_DNS_STATIC_SEARCHLIST":"suse.cz
suse.de", "NETCONFIG_DNS_STATIC_SERVERS":"208.67.222.222 208.67.220.220"],
"dhcp":$["DHCLIENT_SET_HOSTNAME":"yes", "WRITE_HOSTNAME_TO_HOSTS":"no"]]],
"target":$["bash_output":"laptop.suse.cz", "size":27,
"string":"laptop.suse.cz"]]]', 'real_ret': '$["error":"", "exit":"0"]',
'DEFAULT': 'nil'}
3 - Write
{'Current': '$[]', 'Name': '""', 'OriginalDevs':
'$["eth":$["eth0":$["BOOTPROTO":"dhcp4", "STARTMODE":""],
"eth1":$["BOOTPROTO":"static", "IPADDR":"1.2.3.4", "MTU":"1234",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth2":$["BOOTPROTO":"static", "IPADDR":"1.2.3.5", "NETMASK":"255.255.255.0",
"PREFIXLEN":"24", "STARTMODE":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.6", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":""], "eth4":$["BOOTPROTO":"static", "IPADDR":"1.2.3.7",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth5":$["BOOTPROTO":"static", "STARTMODE":""]],
"vlan":$["eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5",
"IPADDR":"1.2.3.8", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":"", "VLAN_ID":"23"]]]', 'devregex': '""', 'chmod': '[]', 'Devs':
'$["eth":$["eth0":$["BOOTPROTO":"dhcp4", "STARTMODE":""],
"eth1":$["BOOTPROTO":"static", "IPADDR":"1.2.3.4", "MTU":"1234",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth2":$["BOOTPROTO":"static", "IPADDR":"1.2.3.5", "NETMASK":"255.255.255.0",
"PREFIXLEN":"24", "STARTMODE":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7/24", "STARTMODE":"auto"], "eth4":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.7", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":""], "eth5":$["BOOTPROTO":"static", "STARTMODE":""]],
"vlan":$["eth5.23":$["BOOTPROTO":"static", "IPADDR":"1.2.3.8/24",
"STARTMODE":"auto"]]]', 'netmask': '"24"', 'has_key': 'false', 'file':
'"/etc/sysconfig/network/ifcfg-eth5.23"', 'operation': 'nil', 'typ': '"vlan"',
'nm':
'"^((128|192|224|240|248|252|254|255).0.0.0|255.(128|192|224|240|248|252|254|255).0.0|255.255.(128|192|224|240|248|252|254|255).0|255.255.255.(128|192|224|240|248|252|254|255))$"',
'devsmap': '$["eth5.23":$["BOOTPROTO":"static", "IPADDR":"1.2.3.8/24",
"STARTMODE":"auto"]]', 'name': '"eth5.23"', 'service': '"network"', 's1':
'"(128|192|224|240|248|252|254|255)"', 'devmap': '$["BOOTPROTO":"static",
"IPADDR":"1.2.3.8/24", "STARTMODE":"auto"]', 'p':
'".network.value.\\"eth5.23\\"."', 'config': '"eth5.23"', 'k':
'"WIRELESS_KEY_3"'}
4 - ConcealSecrets
{'devs': '$["eth":$["eth0":$["BOOTPROTO":"dhcp4", "STARTMODE":""],
"eth1":$["BOOTPROTO":"static", "IPADDR":"1.2.3.4", "MTU":"1234",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth2":$["BOOTPROTO":"static", "IPADDR":"1.2.3.5", "NETMASK":"255.255.255.0",
"PREFIXLEN":"24", "STARTMODE":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.6", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":""], "eth4":$["BOOTPROTO":"static", "IPADDR":"1.2.3.7",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth5":$["BOOTPROTO":"static", "STARTMODE":""]],
"vlan":$["eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5",
"IPADDR":"1.2.3.8", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":"", "VLAN_ID":"23"]]]', 'tout':
'$["eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5", "IPADDR":"1.2.3.8",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":"", "VLAN_ID":"23"]]',
'tdevs': '$["eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5",
"IPADDR":"1.2.3.8", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":"", "VLAN_ID":"23"]]', 'out':
'$["eth":$["eth0":$["BOOTPROTO":"dhcp4", "STARTMODE":""],
"eth1":$["BOOTPROTO":"static", "IPADDR":"1.2.3.4", "MTU":"1234",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth2":$["BOOTPROTO":"static", "IPADDR":"1.2.3.5", "NETMASK":"255.255.255.0",
"PREFIXLEN":"24", "STARTMODE":""], "eth3":$["BOOTPROTO":"static",
"IPADDR":"1.2.3.6", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":""], "eth4":$["BOOTPROTO":"static", "IPADDR":"1.2.3.7",
"NETMASK":"255.255.255.0", "PREFIXLEN":"24", "STARTMODE":""],
"eth5":$["BOOTPROTO":"static", "STARTMODE":""]],
"vlan":$["eth5.23":$["BOOTPROTO":"static", "ETHERDEVICE":"eth5",
"IPADDR":"1.2.3.8", "NETMASK":"255.255.255.0", "PREFIXLEN":"24",
"STARTMODE":"", "VLAN_ID":"23"]]]', 'ifcfg': '$["BOOTPROTO":"static",
"ETHERDEVICE":"eth5", "IPADDR":"1.2.3.8", "NETMASK":"255.255.255.0",
"PREFIXLEN":"24", "STARTMODE":"", "VLAN_ID":"23"]', 'id': '"eth5.23"', 't':
'"vlan"'}
38297: 2011-08-30 10:54:29 <0> opensuse(21525) [libycp]
ExecutionEnvironment.cc(pushframe):105 Push frame ConcealSecrets1
--
Justus Winter winter@xxxxxxxxxxxx

PRESENSE Technologies GmbH Sachsenstr. 5, D-20097 HH
USt-IdNr.: DE263765024
Geschäftsführer/Managing Directors AG Hamburg, HRB 107844
Till Dörges Jürgen Sander Axel Theilmann

#!/usr/bin/env python

# Copyright (c) 2011 Justus Winter (PRESENSE Technologies GmbH)
<winter@xxxxxxxxxxxx>
#
# Permission to use, copy, modify, and distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

import sys
import os
import re
import cmd
import atexit
import readline
import functools

class StackFrame(object):
def __init__(self, parent, name, handle, lineno = 0, position = 0):
self.parent = parent
self.name = name
self.handle = handle
self.lineno = lineno
self.position = position
self.lines = []
self.variables = {}

def handle_push_frame(self, name = None):
frame = StackFrame(self, name, self.handle, self.lineno,
len(self.lines))
self.lineno = frame.parse()
return frame, True

def handle_pop_frame(self, address = None):
return None, False

def handle_set_value(self, name = None, address = None, value = None):
self.variables = self.variables.copy()
self.variables[name] = value
return None, True

line_re = re.compile(r'''.*\[(?P<module>[^\]]*)\]
(?P<c_source_file>.*)\((?P<c_function>.*)\):(?P<c_lineno>\d*) (?P<body>.*)''')

handlers = {
re.compile(r'''Push frame (?P<name>.*)'''): handle_push_frame,
re.compile(r'''Pop frame (?P<address>.*)'''): handle_pop_frame,
re.compile(r'''SymbolEntry::setValue \((?P<name>.*)@(?P<address>.*) =
'(?P<value>.*)'\)'''): handle_set_value,
}

def parse(self):
keep_going = True
for line in self.handle:
line = line.strip()
self.lineno += 1
lineno = self.lineno

line_match = self.line_re.match(line)

result = None
if line_match:
for handler_re, handler in self.handlers.items():
match = handler_re.match(line_match.group('body'))

if match:
result, keep_going = handler(self, **match.groupdict())
break

self.lines.append((lineno, line, self.variables, result,
line_match))

if not keep_going:
break

return self.lineno

def get_stack_trace(self):
result = []

pointer = self
while pointer:
result.insert(0, pointer)
pointer = pointer.parent

return result

def int_arg(fun):
@functools.wraps(fun)
def wrapper(self, args):
if args:
try:
n = int(args)
except ValueError as e:
print 'Not a valid integer: %s' % args
else:
return fun(self, n)
else:
return fun(self)
return wrapper

class PostMortemDebugger(cmd.Cmd):
intro = '''
Welcome to the interactive post mortem ycp debugger!
'''
prompt = 'pmd> '

def __init__(self, trace, *args, **kwargs):
cmd.Cmd.__init__(self, *args, **kwargs)
self.trace = trace
self.current_stack_frame = trace
self.current_position = 0
self.last_search = None
self.display_variables = set()

@property
def current_line(self):
return self.current_stack_frame.lines[self.current_position]

def dump_variables(self, names):
name_width = max(len(name) for name in names)
for name in sorted(names):
print ' %s = %s' % (name.rjust(name_width),
self.current_line[2].get(name, 'UNDEFINED'))

def postcmd(self, stop, line):
print '% 6i: %s' % self.current_line[0:2]

if self.display_variables:
print
self.dump_variables(self.display_variables)
print

return stop

def do_EOF(self, args):
print 'Bye!'
return True

def do_bt(self, args):
'Displays a stack trace'
for i, frame in enumerate(self.current_stack_frame.get_stack_trace()):
print '% 3i - %s' % (i, frame.name)
if args == 'full':
print '%r' % frame.variables

def do_up(self, args):
'Return to the previous stack frame'
try:
self.up()
except StopIteration as e:
print 'Cannot go further up'

@int_arg
def do_next(self, n = 1):
'''next [n]

Advance one [n] line[s] in the log treating function calls as one instruction'''
try:
for x in range(n):
self.next()
except StopIteration as e:
print 'Reached end of trace'

@int_arg
def do_skip(self, n = 1):
'''skip [n]

Advance one [n] line[s] in the log treating function calls as one instruction
ignoring any lines that do not match the line regexp (usually script code)'''
try:
for x in range(n):
self.next()
while not self.current_line[4]:
self.next()
except StopIteration as e:
print 'Reached end of trace'

@int_arg
def do_prev(self, n = 1):
'''prev [n]

Move back [n] line[s] in the log treating function calls as one instruction'''
try:
for x in range(n):
self.prev()
except StopIteration as e:
print 'Reached beginning of trace'

@int_arg
def do_step(self, n = 1):
'''step [n]

Advance one [n] line[s] in the log descending into functions'''
try:
for x in range(n):
self.step()
except StopIteration as e:
print 'Reached end of trace'

def up(self):
if self.current_stack_frame.parent:
self.current_position = self.current_stack_frame.position + 1
self.current_stack_frame = self.current_stack_frame.parent
else:
raise StopIteration()

def step(self):
if self.current_line[3]:
self.current_stack_frame = self.current_line[3]
self.current_position = 0
else:
self.next()

def next(self):
self.current_position += 1
if self.current_position >= len(self.current_stack_frame.lines):
self.up()

def prev(self):
self.current_position -= 1
if self.current_position < 0:
self.up()

def do_reset(self, args):
'Rewind to the beginning of the trace'
self.current_position = 0
self.current_stack_frame = self.trace

def do_search(self, args, advance = None):
'''[r]search

Search for the given regular expression from the current position. Rerun
without argument to repeat the last search.'''
if args:
try:
self.last_search = re.compile(args)
except re.error as e:
print 'Invalid regular expression: %s' % e
return
elif not self.last_search:
print 'No search pattern supplied'
return

advance = advance or self.step
current = self.current_stack_frame, self.current_position
try:
advance()
while not self.last_search.search(self.current_line[1]):
sys.stdout.write('\r%i' % self.current_line[0])
advance()
except StopIteration as e:
self.current_stack_frame, self.current_position = current
print '\rPattern not found'
else:
sys.stdout.write('\r')

@functools.wraps(do_search)
def do_rsearch(self, args):
self.do_search(args, advance = self.prev)

@int_arg
def do_goto(self, n = None):
'''goto n

go to line n'''
if n == None:
print 'I need a line number to go to'
else:
try:
while self.current_line[0] != n:
if self.current_line[0] > n:
self.prev()
else:
self.step()
except StopIteration as e:
print 'Reached %s trace' % ('beginning' if self.current_line[0]
== 0 else 'end')

def do_print(self, args):
'''print <var> [<var> ...]

Print the value[s] of the given variable name[s]'''
self.dump_variables(args.split())

def complete_print(self, text, line, begidx, endidx):
return [name for name in self.current_line[2].keys() if
name.startswith(text)]

def do_display(self, args):
'''display <var>[-] [<var>[-] ...]

Add [remove] the given variable name[s] to the list of variables to display
after each command.'''
for name in args.split():
if name.endswith('-'):
self.display_variables.discard(name[:-1])
else:
self.display_variables.add(name)

complete_display = complete_print

def main(options, args):
main = StackFrame(None, '[main]', open(args[0]))
main.parse()
debugger = PostMortemDebugger(main)
debugger.cmdloop()

if __name__ == '__main__':
histfile = os.environ.get('YCPPMDHISTORY',
os.path.join(os.environ.get('XDG_CONFIG_HOME',

os.path.join(os.environ['HOME'], '.config')),
'ycppmd', 'history'))

if not os.path.isdir(os.path.dirname(histfile)):
os.mkdir(os.path.dirname(histfile))

try:
readline.read_history_file(histfile)
except IOError as e:
pass
atexit.register(readline.write_history_file, histfile)

main(None, sys.argv[1:])
< Previous Next >
This Thread
  • No further messages