From 4de57d139dd109b4260822a37d64045732ec5a84 Mon Sep 17 00:00:00 2001
From: Per <Per Lindgren>
Date: Mon, 13 Jan 2020 22:14:44 +0100
Subject: [PATCH] runner refactoring

---
 runner/README.md                  |  11 +
 runner/resources/gdb.py           | 812 ++++++++++++++++++++++++++++++
 runner/resources/test000001.ktest | Bin 0 -> 140 bytes
 runner/src/bin/f401_probe.rs      |  13 +
 runner/src/lib.rs                 | 164 ++++++
 5 files changed, 1000 insertions(+)
 create mode 100644 runner/README.md
 create mode 100644 runner/resources/gdb.py
 create mode 100644 runner/resources/test000001.ktest
 create mode 100644 runner/src/bin/f401_probe.rs
 create mode 100644 runner/src/lib.rs

diff --git a/runner/README.md b/runner/README.md
new file mode 100644
index 0000000..d0d2d5f
--- /dev/null
+++ b/runner/README.md
@@ -0,0 +1,11 @@
+# runner
+
+KLEE test runner, in very early development.
+
+Still the library provides sufficient capabilities to profile execution.
+
+Tested only on the `stm32f401/f411`
+
+## License
+
+Non public release, all rights reserved Per Lindgren 2020.
diff --git a/runner/resources/gdb.py b/runner/resources/gdb.py
new file mode 100644
index 0000000..fa25058
--- /dev/null
+++ b/runner/resources/gdb.py
@@ -0,0 +1,812 @@
+#!/usr/bin/env python
+import gdb
+import os
+import sys
+import struct
+from subprocess import call
+import subprocess
+import glob
+
+""" ktest file version """
+version_no = 3
+
+# debug = False
+debug = True
+autobuild = True
+
+debug_file = "resource"
+
+# klee_out_folder = 'target/x86_64-unknown-linux-gnu/debug/examples/'
+klee_out_folder = 'target/x86_64-unknown-linux-gnu/release/examples/'
+stm_out_folder = 'target/thumbv7em-none-eabihf/release/examples/'
+
+file_list = []
+file_index_current = -1
+object_index_current = 0
+
+tasks = []
+priorities = []
+interarrival = []
+
+task_name = ""
+file_name = ""
+
+priority = 0
+first = True
+
+# [[ Test, Task, Cyccnt, priority/ceiling]]
+outputdata = []
+
+""" Max number of events guard """
+object_index_max = 100
+
+""" Define the original working directory """
+original_pwd = os.getcwd()
+
+
+""" taken from KLEE """
+
+
+class KTestError(Exception):
+    pass
+
+
+class KTest:
+
+    @staticmethod
+    def fromfile(path):
+        if not os.path.exists(path):
+            print("ERROR: file %s not found" % (path))
+            sys.exit(1)
+
+        f = open(path, 'rb')
+        hdr = f.read(5)
+        if len(hdr) != 5 or (hdr != b'KTEST' and hdr != b"BOUT\n"):
+            raise KTestError('unrecognized file')
+        version, = struct.unpack('>i', f.read(4))
+        if version > version_no:
+            raise KTestError('unrecognized version')
+        numArgs, = struct.unpack('>i', f.read(4))
+        args = []
+        for i in range(numArgs):
+            size, = struct.unpack('>i', f.read(4))
+            args.append(str(f.read(size).decode(encoding='ascii')))
+
+        if version >= 2:
+            symArgvs, = struct.unpack('>i', f.read(4))
+            symArgvLen, = struct.unpack('>i', f.read(4))
+        else:
+            symArgvs = 0
+            symArgvLen = 0
+
+        numObjects, = struct.unpack('>i', f.read(4))
+        objects = []
+        for i in range(numObjects):
+            size, = struct.unpack('>i', f.read(4))
+            name = f.read(size)
+            size, = struct.unpack('>i', f.read(4))
+            bytes = f.read(size)
+            objects.append((name, bytes))
+
+        # Create an instance
+        b = KTest(version, args, symArgvs, symArgvLen, objects)
+        # Augment with extra filename field
+        b.filename = path
+        return b
+
+    def __init__(self, version, args, symArgvs, symArgvLen, objects):
+        self.version = version
+        self.symArgvs = symArgvs
+        self.symArgvLen = symArgvLen
+        self.args = args
+        self.objects = objects
+
+        # add a field that represents the name of the program used to
+        # generate this .ktest file:
+        program_full_path = self.args[0]
+        program_name = os.path.basename(program_full_path)
+        # sometimes program names end in .bc, so strip them
+        if program_name.endswith('.bc'):
+            program_name = program_name[:-3]
+        self.programName = program_name
+
+# Event handling
+
+# Ugly hack to avoid race condtitons in the python gdb API
+
+
+class Executor:
+    def __init__(self, cmd):
+        self.__cmd = cmd
+
+    def __call__(self):
+        gdb.execute(self.__cmd)
+
+
+"""
+Every time a breakpoint is hit this function is executed
+"""
+
+
+def stop_event(evt):
+    global outputdata
+    global task_name
+    global priority
+    global file_name
+
+    imm = gdb_bkpt_read()
+    if debug:
+        print("Debug: stop event in file {}".format(file_name))
+        print("Debug: evt %r" % evt)
+        print("Debug: imm = {}".format(imm))
+
+    if imm == 0:
+        print("Ordinary breakpoint, exiting!")
+        sys.exit(1)
+
+    elif imm == 1 or imm == 2:
+        try:
+            ceiling = int(gdb.parse_and_eval(
+                "ceiling").cast(gdb.lookup_type('u8')))
+        except gdb.error:
+            print("No ceiling found, exciting!")
+            sys.exit(1)
+
+        if imm == 1:
+            action = "Enter"
+        elif imm == 2:
+            action = "Exit"
+
+        if debug:
+            print("Debug: Append action {} at cycle {}".format(
+                action, gdb_cyccnt_read()))
+
+        outputdata.append(
+            [file_name, task_name, gdb_cyccnt_read(), ceiling, action])
+
+        gdb.post_event(Executor("continue"))
+
+    elif imm == 3:
+        if debug:
+            print("Debug: found finish bkpt_3 at cycle {}"
+                  .format(gdb_cyccnt_read()))
+
+        gdb.post_event(Executor("si"))
+
+    elif imm == 4:
+        if debug:
+            print("Debug: found finish bkpt_4 at cycle {}"
+                  .format(gdb_cyccnt_read()))
+
+        gdb.post_event(posted_event_init)
+
+    else:
+        print("Unexpected stop event, exiting")
+        sys.exit(1)
+
+
+""" Loads each defined task """
+
+
+def posted_event_init():
+    if debug:
+        print("\nDebug: Entering posted_event_init")
+
+    global tasks
+    global task_name
+    global file_name
+    global file_index_current
+    global file_list
+    global outputdata
+    global priority
+    global priorities
+
+    if file_index_current < 0:
+        if debug:
+            print("Debug: Skipped first measurement")
+
+    else:
+        if debug:
+            print("Debug: Append Finish action at cycle {}"
+                  .format(gdb_cyccnt_read()))
+
+        outputdata.append(
+            [file_name, task_name, gdb_cyccnt_read(), priority, "Finish"])
+
+    """ loop to skip to next task *omitting the dummy* """
+    while True:
+        file_index_current += 1
+        if file_index_current == len(file_list):
+            """ finished """
+            break
+
+        task_to_test = ktest_setdata(file_index_current)
+        if 0 <= task_to_test < len(tasks):
+            """ next """
+            break
+
+    if file_index_current < len(file_list):
+        """ Load the variable data """
+
+        if debug:
+            print("Debug: Task number to test {}".format(task_to_test))
+
+        """
+        Before the call to the next task, reset the cycle counter
+        """
+        gdb_cyccnt_reset()
+
+        file_name = file_list[file_index_current].split('/')[-1]
+        task_name = tasks[task_to_test]
+        priority = priorities[task_to_test]
+
+        outputdata.append([file_name, task_name,
+                           gdb_cyccnt_read(), priority, "Start"])
+
+        print('Task to call: %s \n' % (
+            tasks[task_to_test] + "()"))
+        gdb.execute('call %s' % "stub_" +
+                    tasks[task_to_test] + "()")
+
+    else:
+        """ here we are done, call your analysis here """
+        offset = 1
+        print("\nFinished all ktest files!\n")
+        print("Claims:")
+        for index, obj in enumerate(outputdata):
+            if obj[4] == "Exit":
+                claim_time = (obj[2] -
+                              outputdata[index - (offset)][2])
+                print("%s Claim time: %s" % (obj, claim_time))
+                offset += 2
+            elif obj[4] == "Finish" and not obj[2] == 0:
+                offset = 1
+                tot_time = obj[2]
+                print("%s Total time: %s" % (obj, tot_time))
+            else:
+                print("%s" % (obj))
+        # comment out to prevent gdb from quit on finish, useful to debugging
+        gdb.execute("quit")
+
+
+def trimZeros(str):
+    for i in range(len(str))[::-1]:
+        if str[i] != '\x00':
+            return str[:i + 1]
+
+    return ''
+
+
+def ktest_setdata(file_index):
+    """
+    Substitute every variable found in ktest-file
+    """
+    global file_list
+    global debug
+
+    if debug:
+        print("Debug: ktest_setdata on index{}".format(file_index))
+
+    b = KTest.fromfile(file_list[file_index])
+
+    if debug:
+        # print('ktest filename : %r' % filename)
+        print('Debug: ktest file: %r \n' % file_list[file_index])
+        # print('args       : %r' % b.args)
+        # print('num objects: %r' % len(b.objects))
+    for i, (name, data) in enumerate(b.objects):
+        str = trimZeros(data)
+
+        """ If Name is "task", skip it """
+        if name.decode('UTF-8') == "task":
+            if debug:
+                print('Debug: object %4d: name: %r' % (i, name))
+                print('Debug: object %4d: size: %r' % (i, len(data)))
+            # print(struct.unpack('i', str).repr())
+            # task_to_test = struct.unpack('i', str)[0]
+            # print("str: ", str)
+            # print("str: ", str[0])
+            task_to_test = struct.unpack('i', str)[0]
+            # task_to_test = int(str[0])
+            if debug:
+                print("Debug: Task to test:", task_to_test)
+        else:
+            if debug:
+                print('Debug: object %4d: name: %r' % (i, name))
+                print('Degug: object %4d: size: %r' % (i, len(data)))
+                print(str)
+            # if opts.writeInts and len(data) == 4:
+            obj_data = struct.unpack('i', str)[0]
+            if debug:
+                print('Dubug: object %4d: data: %r' %
+                      (i, obj_data))
+            # gdb.execute('whatis %r' % name.decode('UTF-8'))
+            # gdb.execute('whatis %r' % obj_data)
+            gdb.execute('set variable %s = %r' %
+                        (name.decode('UTF-8'), obj_data))
+            # gdb.write('Variable %s is:' % name.decode('UTF-8'))
+            # gdb.execute('print %s' % name.decode('UTF-8'))
+            # else:
+            # print('object %4d: data: %r' % (i, str))
+
+    if debug:
+        print("Dubug: Done with setdata")
+    return task_to_test
+
+
+def ktest_iterate():
+    """ Get the list of folders in current directory, sort and then grab the
+        last one.
+    """
+    global debug
+    global autobuild
+
+    curdir = os.getcwd()
+    if debug:
+        print("Debug: Current directory {}".format(curdir))
+
+    rustoutputfolder = curdir + "/" + klee_out_folder
+    try:
+        os.chdir(rustoutputfolder)
+    except IOError:
+        print(rustoutputfolder + "not found. Need to run\n")
+        print("xargo build --example " + example_name + " --features" +
+              " klee_mode --target x86_64-unknown-linux-gnu ")
+        if autobuild:
+            xargo_run("klee")
+            klee_run()
+        else:
+            print("Run the above commands before proceeding")
+            sys.exit(1)
+
+    if os.listdir(rustoutputfolder) == []:
+        """
+        The folder is empty, generate some files
+        """
+        xargo_run("klee")
+        klee_run()
+
+    dirlist = next(os.walk("."))[1]
+    dirlist.sort()
+    if debug:
+        print(dirlist)
+
+    if not dirlist:
+        print("No KLEE output, need to run KLEE")
+        print("Running klee...")
+        klee_run()
+
+    """ Ran KLEE, need to update the dirlist """
+    dirlist = next(os.walk("."))[1]
+    dirlist.sort()
+    try:
+        directory = dirlist[-1]
+    except IOError:
+        print("No KLEE output, need to run KLEE")
+        print("Running klee...")
+        klee_run()
+
+    print("Using ktest-files from directory:\n" + rustoutputfolder + directory)
+
+    """ Iterate over all files ending with ktest in the "klee-last" folder """
+    for filename in os.listdir(directory):
+        if filename.endswith(".ktest"):
+            file_list.append(os.path.join(rustoutputfolder + directory,
+                                          filename))
+        else:
+            continue
+
+    file_list.sort()
+    """ Return to the old path """
+    os.chdir(curdir)
+    return file_list
+
+
+def tasklist_get():
+    """ Parse the automatically generated tasklist
+    """
+
+    if debug:
+        print(os.getcwd())
+    with open('klee/tasks.txt') as fin:
+        for line in fin:
+                # print(line)
+            if not line == "// autogenerated file\n":
+                return [x.strip().strip("[]\"").split(' ')
+                        for x in line.split(',')]
+
+
+""" Run xargo for building """
+
+
+def xargo_run(mode):
+
+    if "klee" in mode:
+        xargo_cmd = ("xargo build --release --example " + example_name
+                     + " --features "
+                     + "klee_mode --target x86_64-unknown-linux-gnu ")
+    elif "stm" in mode:
+        xargo_cmd = ("xargo build --release --example " + example_name +
+                     " --features " +
+                     "wcet_bkpt --target thumbv7em-none-eabihf")
+    else:
+        print("Provide either 'klee' or 'stm' as mode")
+        sys.exit(1)
+
+    call(xargo_cmd, shell=True)
+
+
+""" Stub for running KLEE on the LLVM IR """
+
+
+def klee_run():
+    global debug
+    global original_pwd
+
+    PWD = original_pwd
+
+    user_id = subprocess.check_output(['id', '-u']).decode()
+    group_id = subprocess.check_output(['id', '-g']).decode()
+
+    bc_file = (glob.glob(PWD + "/" +
+                         klee_out_folder +
+                         '*.bc', recursive=False))[-1].split('/')[-1].strip(
+                             '\'')
+    if debug:
+        print(PWD + "/" + klee_out_folder)
+        print(bc_file)
+
+    klee_cmd = ("klee %s" % bc_file)
+    if debug:
+        print(klee_cmd)
+    call(klee_cmd, shell=True)
+
+
+def gdb_cyccnt_enable():
+    # Enable cyccnt
+    gdb.execute("mon mww 0xe0001000 1")
+
+
+def gdb_cyccnt_disable():
+    # Disble cyccnt
+    gdb.execute("mon mww 0xe0001000 0")
+
+
+def gdb_cyccnt_reset():
+    # Reset cycle counter to 0
+    gdb.execute("mon mww 0xe0001004 0")
+
+
+def gdb_cyccnt_read():
+    # Read cycle counter
+    return int(gdb.execute("mon mdw 0xe0001004", False, True).strip(
+        '\n').strip('0xe000012004:').strip(',').strip(), 16)
+
+
+def gdb_cyccnt_write(num):
+    # Write to cycle counter
+    gdb.execute('mon mww 0xe0001004 %r' % num)
+
+
+def gdb_bkpt_read():
+    # Read imm field of the current bkpt
+    try:
+        return int(gdb.execute("x/i $pc", False, True).
+                   split("bkpt")[1].strip("\t").strip("\n"), 0)
+    except:
+        if debug:
+            print("Debug: It is not a bkpt so return 4")
+        return 4
+
+
+print("\n\n\nStarting script")
+
+"""Used for making GDB scriptable"""
+gdb.execute("set confirm off")
+gdb.execute("set pagination off")
+gdb.execute("set verbose off")
+gdb.execute("set height 0")
+
+"""
+Setup GDB for remote debugging
+"""
+gdb.execute("target remote :3333")
+gdb.execute("monitor arm semihosting enable")
+
+"""
+Check if the user passed a file to use as the source.
+
+If a file is given, use this as the example_name
+"""
+if gdb.progspaces()[0].filename:
+    """ A filename was given on the gdb command line """
+    example_name = gdb.progspaces()[0].filename.split('/')[-1]
+    print("The resource used for debugging: %s" % example_name)
+    try:
+        os.path.exists(gdb.progspaces()[0].filename)
+    except IOError:
+        """ Compiles the given example """
+        xargo_run("stm")
+        xargo_run("klee")
+else:
+    example_name = debug_file
+    print("Defaulting to example '%s' for debugging." % example_name)
+    try:
+        if example_name not in os.listdir(stm_out_folder):
+            """ Compiles the default example """
+            xargo_run("stm")
+            xargo_run("klee")
+    except IOError:
+        """ Compiles the default example """
+        xargo_run("stm")
+        xargo_run("klee")
+
+""" Tell GDB to load the file """
+gdb.execute("file %s" % (stm_out_folder + example_name))
+gdb.execute("load %s" % (stm_out_folder + example_name))
+
+""" Tell gdb-dashboard to hide """
+# gdb.execute("dashboard -enabled off")
+# gdb.execute("dashboard -output /dev/null")
+
+""" Enable the cycle counter """
+gdb_cyccnt_enable()
+gdb_cyccnt_reset()
+
+""" Save all ktest files into an array """
+file_list = ktest_iterate()
+
+""" Get all the tasks to jump to """
+task_list = tasklist_get()
+
+if debug:
+    print("Debug: file_list {}".format(file_list))
+    print("Debug: task_list {}".format(task_list))
+
+""" Split into tasks and priorities """
+for x in task_list:
+    interarrival.append(x.pop())
+    priorities.append(x.pop())
+    tasks.append(x.pop())
+
+print("Available tasks:")
+for t in tasks:
+    print(t)
+
+print("At priorities:")
+for t in priorities:
+    print(t)
+
+print("At interarrivals:")
+for t in interarrival:
+    print(t)
+
+""" Subscribe stop_event_ignore to Breakpoint notifications """
+gdb.events.stop.connect(stop_event)
+
+"""
+    continue until bkpt 3,
+    this will pick the next task (through a posted_event_init event)
+"""
+gdb.execute("continue")
+
+
+# Home exam, response time analysis
+#
+# Assignment 1.
+# Run the example and study the output.
+# you may need to run xargo clean first
+#
+# It generates `output data`, a list of list, something like:
+# Finished all ktest files!
+# Claims:
+# ['test000002.ktest', 'EXTI1', 0, '1', 'Start']
+# ['test000002.ktest', 'EXTI1', 15, 2, 'Enter']
+# ['test000002.ktest', 'EXTI1', 19, 3, 'Enter']
+# ['test000002.ktest', 'EXTI1', 28, 3, 'Exit'] Claim time: 9
+# ['test000002.ktest', 'EXTI1', 29, 2, 'Exit'] Claim time: 14
+# ['test000002.ktest', 'EXTI1', 36, '1', 'Finish'] Total time: 36
+# ['test000003.ktest', 'EXTI3', 0, '2', 'Start']
+# ['test000003.ktest', 'EXTI3', 8, '2', 'Finish'] Total time: 8
+# ['test000004.ktest', 'EXTI2', 0, '3', 'Start']
+# ['test000004.ktest', 'EXTI2', 11, '3', 'Finish'] Total time: 11
+# ['test000005.ktest', 'EXTI1', 0, '1', 'Start']
+# ['test000005.ktest', 'EXTI1', 15, 2, 'Enter']
+# ['test000005.ktest', 'EXTI1', 19, 3, 'Enter']
+# ['test000005.ktest', 'EXTI1', 29, 3, 'Exit'] Claim time: 10
+# ['test000005.ktest', 'EXTI1', 30, 2, 'Exit'] Claim time: 15
+# ['test000005.ktest', 'EXTI1', 37, '1', 'Finish'] Total time: 37
+#
+# test000001.ktest is a dummy task and skipped
+# ['test000002.ktest', 'EXTI1', 0, 1, 'Start']
+# ['test000002.ktest', 'EXTI2', 15, 2, 'Enter']
+#
+# broken down, the first measurement
+# -'test000002.ktest'       the ktest file
+# -'EXTI1'                  the task
+# -'0'                      the time stamp (start from zero)
+# -'1'                      the threshold (priority 1)
+# -'Start'                  the 'Start' event
+#
+# broken down, the second measurement
+# -'test000002.ktest'       the ktest file
+# -'EXTI1'                  the task
+# -'15'                     the time stamp of the 'Enter'
+# -'2'                      the threshold (ceiling 2) of X
+# -'Enter'                  the 'Enter' event
+#
+# after 19 cycles we clam Y, raising threshold to 3
+# after 28 cycles we exit the Y claim, threshold 3 *before unlock Y*
+# after 29 cycles we exit the X claim, threshold 2 *before unlock X*
+# and finally we finish at 36 clock cycles
+#
+# The differences to the hand made measurements are due to details
+# of the gdb integration regarding the return behavior.
+#
+# Verify that you can repeat the experiment.
+# The order of tasks/test and cycles may differ but it should look similar.
+# Two tests for EXTI1 and one for EXTI2 and one for EXTI3
+#
+# Try follow what is going on in the test bed.
+#
+#
+# Assignment 2.
+#
+# The vector
+# interarrival = [100, 30, 40]
+# should match the arrival time of EXTI1, EXTI2, and EXTI3 respectively
+# you may need to change the order depending or your klee/tasks.txt file
+# (in the future interarrival and deadlines will be in the RTFM model,
+# but for now we introduce them by hand)
+#
+# Implement function that takes output data and computes the CPU demand
+# (total utilization factor) Up of
+# http://www.di.unito.it/~bini/publications/2003BinButBut.pdf
+#
+# For this example it should be
+# EXTI1 = 37/100
+# EXTI2 = 11/30
+# EXTI3 = 8/40
+# ------------
+# sum   = 0.93666
+# So we are inside the total utilization bound <1
+#
+# Your implementation should be generic though
+# Looking up the WCETs from the `output_data`.
+# (It may be a good idea to make first pass and extract wcet per task)
+#
+# The total utilisation bound allows us to discard task sets that are
+# obviously illegal (not the case here though)
+#
+# Assignment 3.
+#
+# Under SRP response time can be computed by equation 7.22 from
+# https://doc.lagout.org/science/0_Computer%20Science/2_Algorithms/Hard%20Real-Time%20Computing%20Systems_%20Predictable%20Scheduling%20Algorithms%20and%20Applications%20%283rd%20ed.%29%20%5BButtazzo%202011-09-15%5D.pdf
+#
+# In general the response time is computed as.
+# Ri =  Ci + Bi + Ii
+# Ci the WCET of task i
+# Bi the blocking time task i is exposed to
+# Ii the interference (preemptions) task is exposed to
+#
+# where
+# Pi the priority of task i
+# Ai the interarrival of task i
+#
+# We assign deadline = interarrival and priorities inverse to deadline
+# (rate monotonic assignment, with fixed/static priorities)
+#
+# Lets start by looking at EXTI2 with the highest priority,
+# so no interference (preemption)
+# R_EXTI2 = 11 + B_EXTI2 + 0
+#
+# In general Bi is the max time of any lower priority task
+# (EXTI1, EXTI3 in our case)
+# holds a resource with a ceiling > Pi (ceiling >= 3 in this case)
+# B_EXTI2 = 10 (EXTI1 holding Y for 10 cycles)
+#
+# Notice 1, single blocking, we can only be blocked ONCE,
+# so bound priority inversion
+#
+# Notice 2, `output_data` does not hold info on WHAT exact resource is held
+# merely timestamp information on each Enter/Exit and associated level.
+# This is however sufficient for the analysis.
+#
+# so
+# R_EXTI2 = 11 + 10 = 21, well below our 30 cycle margin
+#
+# Let's look at EXTI3, our mid prio task.
+# R_EXTI3 = C_EXTI3 + B_EXTI3 + I_EXTI3
+# where I_EXTI3 is the interference (preemptions)
+#
+# Here we can undertake a simple approach to start out.
+# Assuming a deadline equal to our interarrival (40)
+# I_EXTI3 is the sum of ALL preemptions until its deadline.
+# in this case EXTI2 can preempt us 2 times (40/30 *rounded upwards*)
+# I_EXTI3 = 2 * 11
+#
+# The worst case blocking time is 15
+# (caused by the lower prio task EXTI1 holding X)
+# R_EXTI3 = 8 + 2 * 11 + 15 = 45, already here we see that
+# EXTI2 may miss our deadline (40)
+#
+# EXTI1 (our lowest prio task)
+# R_EXTI1 = C_EXTI1 + B_EXTI1 + I_EXTI1
+#
+# Here we cannot be blocked (as we have the lowest prio)
+# I_EXTI1 is the sum of preemptions from EXTI2 and EXTI3
+# our deadline = interarrival is 100
+# we are exposed to 100/30 = 4 (rounded upwards) preemptions by EXTI2
+# and 100/40 = 3 (rounded upwards) preemptions by EXTI3
+#
+# I_EXTI1 = 37 + 4 * 11 + 3 * 8 = 105
+#
+# Ouch, even though we had only a WCET of 37 we might miss our deadline.
+# However we might have overestimated the problem.
+#
+# Implement the algorithm in a generic manner
+# Verify that that the results are correct by hand computation (or make an Excel)
+#
+# Assignment 4.
+#
+# Looking closer at 7.22 we see that its a recurrent equation.
+# Ri(0) indicating the initial value
+# Ri(0) = Ci + Bi
+# while
+# Ri(s) = Ci + Bi + sum ..(Ri(s-1))..
+# so Ri(1) is computed from Ri(0) and so forth,
+# this requires a recursive or looping implementation.
+#
+# One can see that as initially setting a "busy period" to Ci+Bi
+# and compute a new (longer) "busy period" by taking into account preemptions.
+#
+# Termination:
+# Either Ri(s) = Ri(s-1), we have a fixpoint and have the exact response time
+# or we hit Ri(s) > Ai, we have missed our deadline
+#
+# Your final assignment is to implement the exact method.
+#
+# Notice, we have not dealt with the case where tasks have equal priorities
+# in theory this is not a problem (no special case needed)
+#
+# However, for exactly analysing the taskset as it would run on the
+# real hardware requires some (minor) modifications.
+# *Not part of this assignment*
+#
+# Examination for full score.
+# Make a git repo of your solution. (With reasonable comments)
+#
+# It should be possible to compile and run, and for the example
+# Print utilization according to Assignment 2
+# Print response times according to Assignment 3
+# Print response times according to Assignment 4
+#
+# It should work with different assignments of the interarrival vector.
+# test it also for
+# [100, 40, 50]
+# [80, 30, 40]
+# (Verify that your results are correct by hand computations)
+#
+# Grading
+# For this part 1/3 of the exam 35 points
+# Assignment 2, 10 points
+# Assignment 3, 10 points
+# Assignment 4, 15 points
+#
+# To make sure the analysis works in the general case
+# you can make further examles based on 'resource.rs'
+#
+# Notice, KLEE analysis does not work on hardware peripherals
+# (this is not yet supported), so your new examples must NOT access
+# any peripherals.
+#
+# HINTS
+# You may start by cut and paste the output (table) to a file 'x.py'
+#
+# Implement the analysis in a seprate python file 'x.py'
+# (reconstruct the 'outputdata' from the table)
+#
+# When you have your analysis working,
+# integrate it in this script (operating on the real 'outputdata')
+#
+#
diff --git a/runner/resources/test000001.ktest b/runner/resources/test000001.ktest
new file mode 100644
index 0000000000000000000000000000000000000000..e0675e4eaf2e957beb5b60306e08631bf34cad67
GIT binary patch
literal 140
zcmeYcaSaY(U|?WoU|?WmU|@*T&&bbB)i21&(=RG5F3~Rmk(nj>**U4Hx~Ub3xdl0?
z#rh?QMd_&}`bDWZsfop@`f%~0)bz~alGLL3lGNf7-P9Dbl+>hDLz6_q<P-}F3%#5i
S1_S`v##)w~3KnDmi30%2T_-gF

literal 0
HcmV?d00001

diff --git a/runner/src/bin/f401_probe.rs b/runner/src/bin/f401_probe.rs
new file mode 100644
index 0000000..436c786
--- /dev/null
+++ b/runner/src/bin/f401_probe.rs
@@ -0,0 +1,13 @@
+use runner::*;
+// Note, We use le (little-endian) byte order
+fn main() {
+    let mut session = open_session();
+    reset_and_halt(&mut session);
+    run_to_halt(&mut session); // our first breakpoint
+    cycnt_enable(&mut session);
+    cycnt_reset(&mut session);
+
+    run_to_halt(&mut session); // our second breakpoint
+    let cycle_count = cycnt_read(&mut session);
+    println!("cycles {}", cycle_count);
+}
diff --git a/runner/src/lib.rs b/runner/src/lib.rs
new file mode 100644
index 0000000..b034d7e
--- /dev/null
+++ b/runner/src/lib.rs
@@ -0,0 +1,164 @@
+use ktest::{read_ktest, KTEST};
+
+use probe_rs::{
+    config::registry::{Registry, SelectionStrategy},
+    coresight::memory::MI,
+    flash::download::{
+        download_file, download_file_with_progress_reporting, FileDownloadError, Format,
+    },
+    probe::{stlink, DebugProbe, DebugProbeError, DebugProbeType, MasterProbe, WireProtocol},
+    session::Session,
+    target::info::{self, ChipInfo},
+};
+
+/// Returns first found stlink probe as MasterProbe
+pub fn open_probe() -> MasterProbe {
+    let devs = stlink::tools::list_stlink_devices();
+    // just pick the first one
+    let device = devs.get(0).unwrap();
+    println!("device {:?}", device);
+    let mut link = stlink::STLink::new_from_probe_info(&device).unwrap();
+
+    link.attach(Some(WireProtocol::Swd)).unwrap();
+
+    MasterProbe::from_specific_probe(link)
+}
+
+/// Returns a Session from first found stlink probe
+pub fn open_session() -> Session {
+    let mut probe = open_probe();
+    println!("probe connected");
+
+    let strategy = SelectionStrategy::ChipInfo(ChipInfo::read_from_rom_table(&mut probe).unwrap());
+    println!("strategy {:?}", strategy);
+
+    let strategy = SelectionStrategy::TargetIdentifier("stm32f411".into());
+
+    let registry = Registry::from_builtin_families();
+
+    let target = registry.get_target(strategy).unwrap();
+    println!("target {:?}", target);
+
+    Session::new(target, probe)
+}
+
+/// resets the target and run
+pub fn reset_and_run(session: &mut Session) {
+    session.target.core.reset(&mut session.probe).unwrap();
+}
+
+/// resets the target and halts
+pub fn reset_and_halt(session: &mut Session) {
+    session
+        .target
+        .core
+        .reset_and_halt(&mut session.probe)
+        .unwrap();
+}
+
+/// read current instruction and returns
+/// Some(n)     where n is the breakpoint numbr
+/// None        if its not a breakpoint instruction
+pub fn read_bkpt(session: &mut Session) -> Option<u8> {
+    // try to read the program counter
+    let pc_value = session
+        .target
+        .core
+        .read_core_reg(&mut session.probe, session.target.core.registers().PC)
+        .unwrap();
+
+    let mut instr16 = [0u8; 2];
+    session.probe.read_block8(pc_value, &mut instr16).unwrap();
+
+    match instr16[1] {
+        0b10111110 => Some(instr16[0]), // 0b10111110 is the binary repr of `bkpt #n`
+        _ => None,
+    }
+}
+
+/// increments the pc by 2 (useful to step away from breakpoint)
+pub fn increment_pc(session: &mut Session) {
+    // try to read the program counter
+    let pc_value = session
+        .target
+        .core
+        .read_core_reg(&mut session.probe, session.target.core.registers().PC)
+        .unwrap();
+
+    // the bkpt() is a 16 bit instruction, increment pc by 16 bits (i.e. 2 bytes)
+    let new_pc_value = pc_value + 0x2;
+    session
+        .target
+        .core
+        .write_core_reg(
+            &mut session.probe,
+            session.target.core.registers().PC,
+            new_pc_value,
+        )
+        .unwrap();
+}
+
+/// continue execution until target halted or Timeout reached
+pub fn run_to_halt(session: &mut Session) {
+    // check if contineing from breakpoint
+    if read_bkpt(session).is_some() {
+        println!("Continue from breakpoint.");
+        increment_pc(session);
+    } else {
+        println!("Continue");
+    }
+    session.target.core.run(&mut session.probe).unwrap();
+    println!("running");
+    match session.target.core.wait_for_core_halted(&mut session.probe) {
+        Ok(_) => {
+            print!("Hit breakpoint :",);
+        }
+        Err(DebugProbeError::Timeout) => {
+            print!("Timeout :");
+        }
+        Err(err) => panic!("internal error:{:?}", err),
+    }
+
+    let cpu_info = session.target.core.halt(&mut session.probe).unwrap();
+    println!("Core stopped at address 0x{:08x}", cpu_info.pc);
+}
+
+/// set synbolic values at address of R0
+fn set_symbolic(session: &mut Session, data: &[u8]) {
+    let r0 = session
+        .target
+        .core
+        .read_core_reg(&mut session.probe, 0.into())
+        .unwrap();
+
+    println!("r0 0x{:08x}", r0);
+    println!("object {:?}", data);
+    // session.target.core.step(&mut session.probe).unwrap();
+    let r0 = session.probe.write_block8(r0, data).unwrap();
+}
+
+/// DWT_CTRL control register
+const DWT_CTRL: u32 = 0xe000_1000;
+/// DWT_CTRL cycle counter register
+const DWT_CYCCNT: u32 = 0xe000_1004;
+
+/// enable the cycle counter
+pub fn cycnt_enable(session: &mut Session) {
+    session.probe.write32(DWT_CTRL, 0x1).unwrap();
+}
+
+/// stop the cycle counter
+pub fn cycnt_disable(session: &mut Session) {
+    session.probe.write32(DWT_CTRL, 0x0).unwrap();
+}
+
+/// reset the cyclecounter to 0
+pub fn cycnt_reset(session: &mut Session) {
+    // Reset cycle counter to 0
+    session.probe.write32(DWT_CYCCNT, 0x0).unwrap();
+}
+
+/// read cycle counter into u32
+pub fn cycnt_read(session: &mut Session) -> u32 {
+    session.probe.read32(DWT_CYCCNT).unwrap()
+}
-- 
GitLab