Commit 150a936a authored by Guillaume Roguez's avatar Guillaume Roguez

Revert "test: fix pep8 compliance in doombot script"

This reverts commit 8de1d809.
parent 8de1d809
SFL DoomBot
===========
This collection of script make it easy to run a native application in a `gdb`
shell. This allow unit, integration, fuzzy and stress tests to monitor the
process itself in case of a crash or assert. The DoomBot will then create a
report the developers can use to fix the issues.
We created this script to test `sflphone`, a network facing application. The
`SIP` protocol has some corner case and possible race conditions that may leave
the application in an unstable state.
## Installation
**Require:**
* python 3.0+
* gdb 7.7+
* flask-python3
* sqlite3 (+python3 bindings)
## Usage
### Common use case
#### Unit test executable
#### DBus interface
Make a script that call dbus methods until it crash
#### Network packet injection
Make a script that send packets to a port
#### File loading
Add an "load file and exit command line args"
## Web service API
The API is quite simple for now
```sh
# GDB session
curl http://127.0.0.1:5000/run/
# Kill the current GDB session
curl http://127.0.0.1:5000/kill/
```
## Roadmap
* The application is still tightly integrated with sflphone, a better separation is needed.
* Add valgrind support
* Add an API to add metadata
* Add sqlite backend
* Add md5 checksumming
* Use the gdb python API to check frames instead of `threads apply all bt full`
* Add a basic graph of the last 52 weeks
* Add the ability to execute a `gdb` script the next time this happen
## FAQ
### Is it possible to add accounts and passwords?
No, this service is not designed to be published on the internet. It allow
remote code execution by design. Keep this service in your intranet or add
some kind of HTTP authentication.
### Your code look like PHP4!
Yes, it does, the DoomBot frontend was designed as simple and as quick to develop
as possible. It doesn't have huge abstraction or any kind of template system. It
does what it does and that's it. If this ever take of, it would be one of the
first thing to fix.
#!/usr/bin/python3
import sys
import os
import argparse
from config import DefaultConfig
__version__ = "0.3"
config = DefaultConfig()
args = {}
for i in range(len(sys.argv)):
args[sys.argv[i]] = i
########################################################
# #
# Options validation #
# #
########################################################
_USAGE = """Usage: doombot [options] executable_path [executable_args]
Options:
--help (-h) : Show options and exit
--directory (-d) path : Directory full of scripts to run (optional)
--script (-s) path : Run a single script (optional)
--port (-p) port number : Run the server on a specific port (default:5000)
--interface (-i) interface_name : Run the server on a specific network interface
--continue ( ) : Continue execution after a SIGABRT (not recommended)
--version (-v) : Print the version and exit
"""
parser = argparse.ArgumentParser(description='')
parser.add_argument('--version',
help='Print the version and exit',
action='version',
version='SFL DoomBot %s' % __version__)
parser.add_argument('--directory', '-d',
help='Directory full of scripts to run (optional)',
dest='directory')
parser.add_argument('--script', '-s',
help='Run a single script (optional)',
dest='script')
parser.add_argument('--port', '-p',
help='Run the server on a specific port',
dest='port',
type=int,
default=config.port)
parser.add_argument('--interface', '-i',
help='Run the server on a specific network interface',
dest='interface')
parser.add_argument('--continue', '-c',
help='Continue execution after a SIGABRT (not recommended)',
dest='continue',
action='store_true',
default=config.cont)
config.command = config.command.strip()
config.args = sub_range
doombot_path = os.path.dirname(os.path.realpath(__file__)) + "/"
if not os.path.exists(doombot_path+"gdb_wrapper.py"):
print("Wrapper script not found")
exit(1)
########################################################
# #
# Start the server #
# #
########################################################
print("Starting the DoomBot server")
print("Executable : " + config.command )
print("Port : " + str( config.port ))
print("Continue on Asserts : " + str( config.cont ))
print("Using a script directory : " + str( config.directory != ""))
print("Using a script : " + str( config.script != "" ))
print()
# Start the server
import server
server.app.run()
#!/usr/bin/python3
import time
import os
# This file is used to wrap the GDB instances
#
# Doombot master server
# |-----> process_watcher threads
# |----> GDB process
# |----->Doombot wrapper <=== You are here
# -----> Real process
#
# This may seem a little overkill, but using the same
# process for both GDB and the Web server had too many
# limitations.
backtrace_collecton = {}
total_sig = 0
max_run = 10
class Bt_info:
content = ""
count = 1
bt_hash = ""
bt_type = ""
def to_xml(self):
result = ""
result += " <backtrace>\n"
result += " <signature>" + self.bt_hash+"</signature>\n"
result += " <type>" + self.bt_type+"</type>\n"
result += " <count>" + str(self.count) +"</count>\n"
result += " <content>" + self.content +" </content>\n"
result += " </backtrace>\n"
return result
#Generate output
def to_xml():
result = ""
result += "<doombot>\n"
result += " <backtraces>\n"
for key,bt in backtrace_collecton.items():
result += bt.to_xml()
result += " </backtraces>\n"
result += "</doombot>\n"
print(result)
f = open('/tmp/dommbot','w')
f.write(result)
f.close()
def run():
if total_sig <= max_run:
time.sleep(4)
gdb.execute("run 2>&1 > /dev/null")#,False,True)
else:
to_xml()
def get_backtrace_identity(trace):
result = ""
counter = 0
for line in trace.split('\n'):
fields = line.split()
if fields[3][0:2] != "__":
result = result + fields[-1]
counter += 1
if counter >= 3:
break
return result
def get_backtrace(bt_type):
output = gdb.execute("bt",False,True)
bt_hash = get_backtrace_identity(output)
if not bt_hash in backtrace_collecton:
print("\n\n\nADDING "+bt_type+ " "+ bt_hash+" to list")
info = Bt_info()
info.content = output
info.bt_hash = bt_hash
info.bt_type = bt_type
backtrace_collecton[bt_hash] = info
else:
backtrace_collecton[bt_hash].count += 1
print("\n\n\nEXISTING " +bt_type+ " ("+ str(backtrace_collecton[bt_hash].count)+")")
run()
def stop_handler (event):
if isinstance(event,gdb.SignalEvent):
global total_sig
if event.stop_signal == "SIGSEGV":
total_sig +=1
get_backtrace(event.stop_signal)
if event.stop_signal == "SIGABRT":
print("SIGABRT")
total_sig +=1
get_backtrace(event.stop_signal)
elif isinstance(event,gdb.BreakpointEvent):
print("BREAKPOINT "+ str(event.breakpoint.expression) +" " \
+str(event.breakpoint.location)+" "\
+str(event.breakpoint.condition) + " " \
+str(event.breakpoint.commands))
for k,v in event.breakpoints:
print("HERE "+str(k)+" "+str(v))
#Force restart
def exit_handler(event):
if (ExitedEvent.exit_code == 0):
gdb.run("quit")
gdb.events.stop.connect (stop_handler)
gdb.events.exited.connect (exit_handler)
gdb.execute("set confirm off")
gdb.execute("set height 0")
os.system('export MALLOC_CHECK=2')
#gdb.execute("file /home/etudiant/prefix/lib/sflphone/sflphoned")
run()
\ No newline at end of file
import threading
import subprocess
import os
# This file is used to wrap the GDB instances
#
# Doombot master server
# |-----> process_watcher threads <=== You are here
# |----> GDB process
# |----->Doombot wrapper
# -----> Real process
#
# This may seem a little overkill, but using the same
# process for both GDB and the Web server had too many
# limitations.
def launch_process(config):
"""
Runs the given args in a subprocess.Popen, and then calls the function
onExit when the subprocess completes.
onExit is a callable object, and popenArgs is a list/tuple of args that
would give to subprocess.Popen.
"""
def runInThread(onExit, popenArgs):
print(popenArgs)
#print("starting "+popenArgs.executable)
proc = subprocess.Popen(**popenArgs)
proc.wait()
#output = proc.communicate()[0]
#error = proc.communicate()[2]
onExit(output,error)
return
t = { 'args' : ['gdb', '-x', os.path.dirname(os.path.realpath(__file__)) + "/gdb_wrapper.py" ,'--args'] + config.args}#,
#'stdout' : subprocess.PIPE ,
#'stderr' : subprocess.PIPE }
print("foo")
def onExit():
return
thread = threading.Thread(target=runInThread, args=(onExit, t) )
thread.start()
return 4 #session_id TODO
#!/usr/bin/python
# -*- coding: utf-8 -*-
from flask import Flask
import config
import process_watcher
app = Flask(__name__)
# This file is used to wrap the GDB instances
#
# Doombot master server
# |-----> process_watcher threads
# |----> GDB process <=== You are here
# |----->Doombot wrapper
# -----> Real process
#
# This may seem a little overkill, but using the same
# process for both GDB and the Web server had too many
# limitations.
# This file is a hardcoded Web UI for the DoomBot. It print tons inflexible HTML
# is it ugly?: Yes, of course it is. Does it work?: Well enough
def print_banner():
return "<pre>\
##############################################################################\n\
# --SFL-- #\n\
# /¯¯¯¯\ /¯¯¯¯\ /¯¯¯¯\ /¯¯¯\_/¯¯¯\ /¯¯¯¯¯\ /¯¯¯¯\ |¯¯¯¯¯¯¯| #\n\
# / /¯\ | / /\ \ /\ \| /¯\\ | | |¯| | | /\ | ¯¯| |¯¯ #\n\
# / / / |/ | | | | | || | | | | | | ¯ < | | | | | | #\n\
# / /__/ / | ¯ | ¯ || | | | | | | |¯| | | |_| | | | #\n\
# |______/ \_____/ \____/ |_| |_| |_| \_____/ \____/ |_| #\n\
# MASTER #\n\
##############################################################################\n\
</pre>"
def print_head(body):
return "<html><head><title>DoomBot</title></head><body>%s\
<br /><div>Copyright Savoir-faire Linux (2012-2014)</div></body></html>" \
% body
def print_options():
return "<table>\n\
<tr><td>directory </td><td><code>%s</code></td></tr>\n\
<tr><td>script </td><td><code>%s</code></td></tr>\n\
<tr><td>cont </td><td><code>%s</code></td>/tr>\n\
<tr><td>command </td><td><code>%s</code></td></tr>\n\
</table>\n" % (str(config.directory), str(config.script and "true" or "false")
, str(config.cont and "true" or "false"), str(config.command))
def print_actions():
return "<a href='http://127.0.0.1:5000/run/'>Run now</a>\n\
<a href='http://127.0.0.1:5000/kill/'>Kill</a>"
@app.route("/")
def dashboard():
return print_head(
#Print the banner
print_banner () +
#Print the current options values
print_options() +
#Print the possible action
print_actions()
#Print the recent issues
)
@app.route("/run/")
def db_run():
print("BOB")
process_watcher.launch_process(config)
return "Starting the DoomBot"
@app.route("/kill/")
def db_kill():
return "Killing the DoomBot"
This diff is collapsed.
......@@ -181,14 +181,14 @@ def run():
start_daemon()
sys.stdout.write(" ["+str(i+1)+"/"+str(len(suits[k]))+"] Testing \""+v['test_name']+"\": ")
sys.stdout.flush()
#Run the test
retval = meta_test(v['test_func'])
if not k in results:
results[k] = 0
if retval > 0:
results[k]= results[k] + 1
#Stop SFLphone
stop_daemon()
time.sleep(15)
......@@ -200,7 +200,7 @@ def run():
except IOError:
print 'Report not found'
counter = counter + 1
#Print the test summary
print "\n\n\033[1mSummary:\033[0m"
totaltests = 0
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment