Skip to main content
Software development for researchers

Making it work: debugging

This handout is tied to the following lecture

9. Making it work: debugging
Table of contents

Asserts

Asserts is an interesting concept in programming as it allows you to assert things about your data that should be true at runtime. Asserts can help you find errors in those assumptions or find bugs that produce data that does not follow those assumptions as they will crash the program if the assertion fails.

This is obviously something that you sometimes do not want for e.g. performance or robustness concerns. For example, if you want tests to fail quickly using asserts to find problems but then when you deploy your software, say to run an entire instrument network, you dont want a small erroneous data packet to crash the entire network, but you still want to be able to find this error somewhere in the logs of your system. In this case, error handling via exceptions are the easier choice. Assertions can be turned off at runtime while exceptions can not.

As such, I think a mix between using asserts and proper error handling, depending on your application, is the way to go.

Below is a video that talks about using asserts instead of comments that describe the assumptions which is an interesting way to think about their usage.

Low Level Game Dev - Don’t write comments, do this instead!

Static analysis

One of the static analysis tools that orient towards finding security issues is grype, below is a short podcast that discusses this tool.

Open Source Security - Syft, Grype, and Grant with Alan Pope

Program to debug

In order to have a sufficiently problematic program to debug I have crafted this example. To run the code, first run the script in one terminal with the argument server and then in another with the argument client.

In this example, the error message will not be helpful at all. You will need to somehow inspect the data that is being exchanged to find the error.

One tip is to know what post mortem in pdb is.

The code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import string
import random
import socket
import struct
import argparse


HOST = "localhost"
PORT = 8083
random.seed(2)

msgs = [
    "".join(
        random.choices(
            string.ascii_uppercase + string.digits,
            k=random.randint(1, 257),
        )
    )
    for ind in range(500)
]


def run_server():
    msg_index = 0
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s_sock:
        s_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        s_sock.bind((HOST, PORT))
        s_sock.listen(1)
        server_running = True
        while server_running:
            conn, addr = s_sock.accept()
            with conn:
                while True:
                    msg_len_b_first = conn.recv(1)
                    if len(msg_len_b_first) == 0:
                        continue
                    msg_len_first = struct.unpack(">B", msg_len_b_first)[0]
                    if msg_len_first == 0:
                        server_running = False
                        conn.send(struct.pack("2s", "OK".encode("UTF8")))
                        break
                    elif msg_len_first == 255:
                        msg_len_b = conn.recv(2)
                        msg_len = struct.unpack(">H", msg_len_b)[0]
                    else:
                        msg_len = msg_len_first
                    data = conn.recv(msg_len)
                    conn.send(struct.pack("2s", "OK".encode("UTF8")))
                    msg = struct.unpack(f"{msg_len}s", data)[0].decode("UTF8")
                    assert msg == msgs[msg_index]
                    msg_index += 1
                    print("Server: ", msg)
                    break
    print("Server exiting...")


def run_client(msg):
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((HOST, PORT))
        if len(msg) <= 255:
            data = struct.pack(f">B{len(msg)}s", len(msg), msg.encode("UTF8"))
        else:
            sock.send(struct.pack(">B", 255))
            data = struct.pack(f"<H{len(msg)}s", len(msg), msg.encode("UTF8"))
        sock.send(data)
        data = sock.recv(16)
        print("Client: ", struct.unpack("2s", data)[0].decode("UTF8"))


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parsers = parser.add_subparsers(dest="cmd")
    server_parser = parsers.add_parser("server")
    client_parser = parsers.add_parser("client")

    args = parser.parse_args()

    match args.cmd:
        case "server":
            try:
                run_server()
            except KeyboardInterrupt:
                print("exit server")
        case "client":
            for msg in msgs:
                run_client(msg)
            run_client("")
To top
× Zoomed Image