Squash commits for public release
This commit is contained in:
16
utils/qprof/AddrResolver.py
Normal file
16
utils/qprof/AddrResolver.py
Normal file
@@ -0,0 +1,16 @@
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
|
||||
class AddrResolver:
|
||||
def __init__(self, path):
|
||||
self.path = path
|
||||
self.cache = {}
|
||||
|
||||
def get(self, addr):
|
||||
if addr not in self.cache:
|
||||
s = subprocess.check_output(
|
||||
"i686-elf-addr2line --demangle -fsp -e " + self.path + " " + hex(addr), shell=True)
|
||||
s = s.decode("ascii")
|
||||
self.cache[addr] = s.split(" ")[0]
|
||||
return self.cache[addr]
|
||||
61
utils/qprof/ProfNode.py
Normal file
61
utils/qprof/ProfNode.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from AddrResolver import AddrResolver
|
||||
|
||||
|
||||
class Profiler:
|
||||
def __init__(self, filename):
|
||||
self.addr_resolver = AddrResolver(filename)
|
||||
|
||||
def resolver(self):
|
||||
return self.addr_resolver
|
||||
|
||||
|
||||
class ProfNode:
|
||||
def __init__(self, profiler, pc=0):
|
||||
self.profiler = profiler
|
||||
self.pc = pc
|
||||
self.name = "NOT SET"
|
||||
self.call_count = 0
|
||||
self.children = {}
|
||||
self.children_by_name = {}
|
||||
|
||||
def merge(self, node):
|
||||
self.call_count += node.call_count
|
||||
self.children = {**self.children, **node.children}
|
||||
|
||||
def add_stacktrace(self, stacktrace):
|
||||
if len(stacktrace) == 0:
|
||||
return
|
||||
child_pc = stacktrace[-1]
|
||||
if child_pc in self.children.keys():
|
||||
self.children[child_pc].call_count += 1
|
||||
else:
|
||||
self.children[child_pc] = ProfNode(self.profiler, child_pc)
|
||||
self.children[child_pc].call_count += 1
|
||||
|
||||
self.children[child_pc].add_stacktrace(stacktrace[:-1])
|
||||
|
||||
def process_node(self):
|
||||
named_nodes = {}
|
||||
|
||||
for k, v in self.children.items():
|
||||
v.process_node()
|
||||
|
||||
func_name = self.profiler.resolver().get(k)
|
||||
if func_name in named_nodes.keys():
|
||||
named_nodes[func_name].merge(v)
|
||||
else:
|
||||
named_nodes[func_name] = ProfNode(self.profiler)
|
||||
named_nodes[func_name].name = func_name
|
||||
named_nodes[func_name].merge(v)
|
||||
self.children = named_nodes
|
||||
|
||||
def print(self, tabs=0):
|
||||
print('{} {:>4} {}'.format(" "*tabs, self.call_count, self.name))
|
||||
|
||||
def trace(self, tabs=0):
|
||||
self.print(tabs)
|
||||
|
||||
rd = sorted(self.children.values(),
|
||||
key=lambda node: node.call_count, reverse=True)
|
||||
for v in rd:
|
||||
v.trace(tabs + 1)
|
||||
39
utils/qprof/QConn.py
Normal file
39
utils/qprof/QConn.py
Normal file
@@ -0,0 +1,39 @@
|
||||
from QMP import QEMUMonitorProtocol
|
||||
import re
|
||||
|
||||
|
||||
class QConn:
|
||||
def __init__(self, path):
|
||||
self.qconn = QEMUMonitorProtocol(path)
|
||||
self.qconn.connect()
|
||||
self.runs = True
|
||||
self.regs_cache = ""
|
||||
|
||||
def drop_cache(self):
|
||||
self.regs_cache = ""
|
||||
|
||||
def stop(self):
|
||||
self.human_cmd("stop")
|
||||
self.runs = False
|
||||
self.drop_cache()
|
||||
|
||||
def cont(self):
|
||||
self.human_cmd("cont")
|
||||
self.runs = True
|
||||
|
||||
def gpreg(self, name):
|
||||
if self.runs or self.regs_cache == "":
|
||||
self.regs_cache, err = self.human_cmd("info registers")
|
||||
name = name.upper()
|
||||
fd = re.search("{}=([\w]+)".format(name), self.regs_cache)
|
||||
if fd is None:
|
||||
return ""
|
||||
return fd.group(1)
|
||||
|
||||
def human_cmd(self, line):
|
||||
args = {}
|
||||
args["command-line"] = line
|
||||
resp = self.qconn.cmd("human-monitor-command", args)
|
||||
if "error" in resp:
|
||||
return (resp["error"]["desc"], 1)
|
||||
return (resp["return"], 0)
|
||||
256
utils/qprof/QMP.py
Normal file
256
utils/qprof/QMP.py
Normal file
@@ -0,0 +1,256 @@
|
||||
# QEMU Monitor Protocol Python class
|
||||
#
|
||||
# Copyright (C) 2009, 2010 Red Hat Inc.
|
||||
#
|
||||
# Authors:
|
||||
# Luiz Capitulino <lcapitulino@redhat.com>
|
||||
#
|
||||
# This work is licensed under the terms of the GNU GPL, version 2. See
|
||||
# the COPYING file in the top-level directory.
|
||||
|
||||
import json
|
||||
import errno
|
||||
import socket
|
||||
import logging
|
||||
|
||||
|
||||
class QMPError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class QMPConnectError(QMPError):
|
||||
pass
|
||||
|
||||
|
||||
class QMPCapabilitiesError(QMPError):
|
||||
pass
|
||||
|
||||
|
||||
class QMPTimeoutError(QMPError):
|
||||
pass
|
||||
|
||||
|
||||
class QEMUMonitorProtocol(object):
|
||||
|
||||
#: Logger object for debugging messages
|
||||
logger = logging.getLogger('QMP')
|
||||
#: Socket's error class
|
||||
error = socket.error
|
||||
#: Socket's timeout
|
||||
timeout = socket.timeout
|
||||
|
||||
def __init__(self, address, server=False):
|
||||
"""
|
||||
Create a QEMUMonitorProtocol class.
|
||||
|
||||
@param address: QEMU address, can be either a unix socket path (string)
|
||||
or a tuple in the form ( address, port ) for a TCP
|
||||
connection
|
||||
@param server: server mode listens on the socket (bool)
|
||||
@raise socket.error on socket connection errors
|
||||
@note No connection is established, this is done by the connect() or
|
||||
accept() methods
|
||||
"""
|
||||
self.__events = []
|
||||
self.__address = address
|
||||
self.__sock = self.__get_sock()
|
||||
self.__sockfile = None
|
||||
if server:
|
||||
self.__sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self.__sock.bind(self.__address)
|
||||
self.__sock.listen(1)
|
||||
|
||||
def __get_sock(self):
|
||||
if isinstance(self.__address, tuple):
|
||||
family = socket.AF_INET
|
||||
else:
|
||||
family = socket.AF_UNIX
|
||||
return socket.socket(family, socket.SOCK_STREAM)
|
||||
|
||||
def __negotiate_capabilities(self):
|
||||
greeting = self.__json_read()
|
||||
if greeting is None or "QMP" not in greeting:
|
||||
raise QMPConnectError
|
||||
# Greeting seems ok, negotiate capabilities
|
||||
resp = self.cmd('qmp_capabilities')
|
||||
if "return" in resp:
|
||||
return greeting
|
||||
raise QMPCapabilitiesError
|
||||
|
||||
def __json_read(self, only_event=False):
|
||||
while True:
|
||||
data = self.__sockfile.readline()
|
||||
if not data:
|
||||
return
|
||||
resp = json.loads(data)
|
||||
if 'event' in resp:
|
||||
self.logger.debug("<<< %s", resp)
|
||||
self.__events.append(resp)
|
||||
if not only_event:
|
||||
continue
|
||||
return resp
|
||||
|
||||
def __get_events(self, wait=False):
|
||||
"""
|
||||
Check for new events in the stream and cache them in __events.
|
||||
|
||||
@param wait (bool): block until an event is available.
|
||||
@param wait (float): If wait is a float, treat it as a timeout value.
|
||||
|
||||
@raise QMPTimeoutError: If a timeout float is provided and the timeout
|
||||
period elapses.
|
||||
@raise QMPConnectError: If wait is True but no events could be
|
||||
retrieved or if some other error occurred.
|
||||
"""
|
||||
|
||||
# Check for new events regardless and pull them into the cache:
|
||||
self.__sock.setblocking(0)
|
||||
try:
|
||||
self.__json_read()
|
||||
except socket.error as err:
|
||||
if err[0] == errno.EAGAIN:
|
||||
# No data available
|
||||
pass
|
||||
self.__sock.setblocking(1)
|
||||
|
||||
# Wait for new events, if needed.
|
||||
# if wait is 0.0, this means "no wait" and is also implicitly false.
|
||||
if not self.__events and wait:
|
||||
if isinstance(wait, float):
|
||||
self.__sock.settimeout(wait)
|
||||
try:
|
||||
ret = self.__json_read(only_event=True)
|
||||
except socket.timeout:
|
||||
raise QMPTimeoutError("Timeout waiting for event")
|
||||
except:
|
||||
raise QMPConnectError("Error while reading from socket")
|
||||
if ret is None:
|
||||
raise QMPConnectError("Error while reading from socket")
|
||||
self.__sock.settimeout(None)
|
||||
|
||||
def connect(self, negotiate=True):
|
||||
"""
|
||||
Connect to the QMP Monitor and perform capabilities negotiation.
|
||||
|
||||
@return QMP greeting dict
|
||||
@raise socket.error on socket connection errors
|
||||
@raise QMPConnectError if the greeting is not received
|
||||
@raise QMPCapabilitiesError if fails to negotiate capabilities
|
||||
"""
|
||||
self.__sock.connect(self.__address)
|
||||
self.__sockfile = self.__sock.makefile()
|
||||
if negotiate:
|
||||
return self.__negotiate_capabilities()
|
||||
|
||||
def accept(self):
|
||||
"""
|
||||
Await connection from QMP Monitor and perform capabilities negotiation.
|
||||
|
||||
@return QMP greeting dict
|
||||
@raise socket.error on socket connection errors
|
||||
@raise QMPConnectError if the greeting is not received
|
||||
@raise QMPCapabilitiesError if fails to negotiate capabilities
|
||||
"""
|
||||
self.__sock.settimeout(15)
|
||||
self.__sock, _ = self.__sock.accept()
|
||||
self.__sockfile = self.__sock.makefile()
|
||||
return self.__negotiate_capabilities()
|
||||
|
||||
def cmd_obj(self, qmp_cmd):
|
||||
"""
|
||||
Send a QMP command to the QMP Monitor.
|
||||
|
||||
@param qmp_cmd: QMP command to be sent as a Python dict
|
||||
@return QMP response as a Python dict or None if the connection has
|
||||
been closed
|
||||
"""
|
||||
self.logger.debug(">>> %s", qmp_cmd)
|
||||
try:
|
||||
self.__sock.sendall(json.dumps(qmp_cmd).encode('utf-8'))
|
||||
except socket.error as err:
|
||||
if err[0] == errno.EPIPE:
|
||||
return
|
||||
raise socket.error(err)
|
||||
resp = self.__json_read()
|
||||
self.logger.debug("<<< %s", resp)
|
||||
return resp
|
||||
|
||||
def cmd(self, name, args=None, cmd_id=None):
|
||||
"""
|
||||
Build a QMP command and send it to the QMP Monitor.
|
||||
|
||||
@param name: command name (string)
|
||||
@param args: command arguments (dict)
|
||||
@param cmd_id: command id (dict, list, string or int)
|
||||
"""
|
||||
qmp_cmd = {'execute': name}
|
||||
if args:
|
||||
qmp_cmd['arguments'] = args
|
||||
if cmd_id:
|
||||
qmp_cmd['id'] = cmd_id
|
||||
return self.cmd_obj(qmp_cmd)
|
||||
|
||||
def command(self, cmd, **kwds):
|
||||
"""
|
||||
Build and send a QMP command to the monitor, report errors if any
|
||||
"""
|
||||
ret = self.cmd(cmd, kwds)
|
||||
if "error" in ret:
|
||||
raise Exception(ret['error']['desc'])
|
||||
return ret['return']
|
||||
|
||||
def pull_event(self, wait=False):
|
||||
"""
|
||||
Pulls a single event.
|
||||
|
||||
@param wait (bool): block until an event is available.
|
||||
@param wait (float): If wait is a float, treat it as a timeout value.
|
||||
|
||||
@raise QMPTimeoutError: If a timeout float is provided and the timeout
|
||||
period elapses.
|
||||
@raise QMPConnectError: If wait is True but no events could be
|
||||
retrieved or if some other error occurred.
|
||||
|
||||
@return The first available QMP event, or None.
|
||||
"""
|
||||
self.__get_events(wait)
|
||||
|
||||
if self.__events:
|
||||
return self.__events.pop(0)
|
||||
return None
|
||||
|
||||
def get_events(self, wait=False):
|
||||
"""
|
||||
Get a list of available QMP events.
|
||||
|
||||
@param wait (bool): block until an event is available.
|
||||
@param wait (float): If wait is a float, treat it as a timeout value.
|
||||
|
||||
@raise QMPTimeoutError: If a timeout float is provided and the timeout
|
||||
period elapses.
|
||||
@raise QMPConnectError: If wait is True but no events could be
|
||||
retrieved or if some other error occurred.
|
||||
|
||||
@return The list of available QMP events.
|
||||
"""
|
||||
self.__get_events(wait)
|
||||
return self.__events
|
||||
|
||||
def clear_events(self):
|
||||
"""
|
||||
Clear current list of pending events.
|
||||
"""
|
||||
self.__events = []
|
||||
|
||||
def close(self):
|
||||
self.__sock.close()
|
||||
self.__sockfile.close()
|
||||
|
||||
def settimeout(self, timeout):
|
||||
self.__sock.settimeout(timeout)
|
||||
|
||||
def get_sock_fd(self):
|
||||
return self.__sock.fileno()
|
||||
|
||||
def is_scm_available(self):
|
||||
return self.__sock.family == socket.AF_UNIX
|
||||
97
utils/qprof/qprof
Normal file
97
utils/qprof/qprof
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import code
|
||||
import argparse
|
||||
from time import sleep
|
||||
from ProfNode import ProfNode, Profiler
|
||||
from QConn import QConn
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("path", type=str, help="Monitor address (required)")
|
||||
parser.add_argument("--symbols", type=str, help="Symbols (required)")
|
||||
parser.add_argument("--duration", type=int, default=10,
|
||||
help="Duration (default=10)")
|
||||
parser.add_argument("--frequency", type=float, default=0.05,
|
||||
help="Frequency (default=0.05)")
|
||||
parser.add_argument("--nointeractive", action="store_true",
|
||||
help="Not run interactive mode")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.symbols):
|
||||
print("Can't open symbols: {}", args.symbols)
|
||||
exit(1)
|
||||
|
||||
if not os.path.exists(args.path):
|
||||
print("Can't open qemu monitor socket: {}", args.path)
|
||||
exit(1)
|
||||
|
||||
|
||||
qconn = QConn(args.path)
|
||||
|
||||
|
||||
def get_stacktrace():
|
||||
PAGE_SIZE = 0x1000
|
||||
|
||||
stacktrace = []
|
||||
|
||||
qconn.stop()
|
||||
ip = int(qconn.gpreg("eip"), 16)
|
||||
sp = int(qconn.gpreg("esp"), 16)
|
||||
bp = int(qconn.gpreg("ebp"), 16)
|
||||
stacktrace.append(ip)
|
||||
|
||||
bottomsp = sp & ~(PAGE_SIZE - 1)
|
||||
memdata, err = qconn.human_cmd(
|
||||
"x/{}x ".format(PAGE_SIZE // 4) + hex(bottomsp))
|
||||
if err:
|
||||
return []
|
||||
|
||||
qconn.cont()
|
||||
|
||||
lines = memdata.split("\r\n")
|
||||
memmap = {}
|
||||
for i in lines:
|
||||
data = i.split(" ")
|
||||
if (len(data) != 5):
|
||||
continue
|
||||
addr = int(data[0][:-1], 16)
|
||||
memmap[addr] = int(data[1], 0)
|
||||
memmap[addr+4] = int(data[2], 0)
|
||||
memmap[addr+8] = int(data[3], 0)
|
||||
memmap[addr+12] = int(data[4], 0)
|
||||
|
||||
visited = set()
|
||||
while True:
|
||||
pc = memmap.get(bp + 4)
|
||||
if pc is not None:
|
||||
stacktrace.append(pc)
|
||||
visited.add(bp)
|
||||
bp = memmap.get(bp, None)
|
||||
if bp is None:
|
||||
break
|
||||
if bp in visited:
|
||||
break
|
||||
|
||||
return stacktrace
|
||||
|
||||
|
||||
profiler = Profiler(args.symbols)
|
||||
root = ProfNode(profiler)
|
||||
root.name = "root"
|
||||
|
||||
runs = int(args.duration / args.frequency)
|
||||
for i in range(runs):
|
||||
root.add_stacktrace(get_stacktrace())
|
||||
sleep(args.frequency)
|
||||
|
||||
|
||||
root.process_node()
|
||||
|
||||
if not args.nointeractive:
|
||||
print("Entering interactive mode, use root object to access profile data.")
|
||||
print(" * root.trace() - prints trace")
|
||||
code.interact(local=locals())
|
||||
else:
|
||||
root.trace()
|
||||
Reference in New Issue
Block a user