1#!/usr/bin/env python3
2#
3# Copyright (c) 2020 Intel Corporation
4#
5# SPDX-License-Identifier: Apache-2.0
6
7import argparse
8import logging
9import os
10import socket
11import sys
12
13from coredump_parser.log_parser import CoredumpLogFile
14from coredump_parser.elf_parser import CoredumpElfFile
15
16import gdbstubs
17
18LOGGING_FORMAT = "[%(levelname)s][%(name)s] %(message)s"
19
20# Only bind to local host
21GDBSERVER_HOST = ""
22
23
24class FakeSocket:
25    def __init__(self) -> None:
26        self.in_stream = sys.stdin.buffer
27        self.out_stream = sys.stdout.buffer
28
29    def recv(self, bufsize):
30        return self.in_stream.read(bufsize)
31
32    def send(self, data):
33        n = self.out_stream.write(data)
34        self.out_stream.flush()
35        return n
36
37    def close(self):
38        pass
39
40
41def parse_args():
42    parser = argparse.ArgumentParser(allow_abbrev=False)
43
44    parser.add_argument("elffile", help="Zephyr ELF binary")
45    parser.add_argument("logfile", help="Coredump binary log file")
46    parser.add_argument("--debug", action="store_true",
47                        help="Print extra debugging information")
48    parser.add_argument("--port", type=int, default=1234,
49                        help="GDB server port")
50    parser.add_argument("--pipe", action="store_true",
51                        help="Use stdio to communicate with gdb")
52    parser.add_argument("-v", "--verbose", action="store_true",
53                        help="Print more information")
54
55    return parser.parse_args()
56
57
58def main():
59    args = parse_args()
60
61    # Setup logging
62    logging.basicConfig(format=LOGGING_FORMAT)
63
64    # Setup logging for "parser"
65    logger = logging.getLogger("parser")
66    if args.debug:
67        logger.setLevel(logging.DEBUG)
68    elif args.verbose:
69        logger.setLevel(logging.INFO)
70    else:
71        logger.setLevel(logging.WARNING)
72
73    # Setup logging for follow code
74    logger = logging.getLogger("gdbserver")
75    if args.debug:
76        logger.setLevel(logging.DEBUG)
77    else:
78        # Use INFO as default since we need to let user
79        # know what is going on
80        logger.setLevel(logging.INFO)
81
82    # Setup logging for "gdbstub"
83    logger = logging.getLogger("gdbstub")
84    if args.debug:
85        logger.setLevel(logging.DEBUG)
86    elif args.verbose:
87        logger.setLevel(logging.INFO)
88    else:
89        logger.setLevel(logging.WARNING)
90
91    if not os.path.isfile(args.elffile):
92        logger.error(f"Cannot find file {args.elffile}, exiting...")
93        sys.exit(1)
94
95    if not os.path.isfile(args.logfile):
96        logger.error(f"Cannot find file {args.logfile}, exiting...")
97        sys.exit(1)
98
99    logger.info(f"Log file: {args.logfile}")
100    logger.info(f"ELF file: {args.elffile}")
101
102    # Parse the coredump binary log file
103    logf = CoredumpLogFile(args.logfile)
104    logf.open()
105    if not logf.parse():
106        logger.error("Cannot parse log file, exiting...")
107        logf.close()
108        sys.exit(1)
109
110    # Parse ELF file for code and read-only data
111    elff = CoredumpElfFile(args.elffile)
112    elff.open()
113    if not elff.parse():
114        logger.error("Cannot parse ELF file, exiting...")
115        elff.close()
116        logf.close()
117        sys.exit(1)
118
119    gdbstub = gdbstubs.get_gdbstub(logf, elff)
120
121    if not args.pipe:
122        # Start a GDB server
123        gdbserver = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
124
125        # Reuse address so we don't have to wait for socket to be
126        # close before we can bind to the port again
127        gdbserver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
128
129        gdbserver.bind((GDBSERVER_HOST, args.port))
130        gdbserver.listen(1)
131
132        logger.info(f"Waiting GDB connection on port {args.port}...")
133
134        conn, remote = gdbserver.accept()
135    else:
136        conn = FakeSocket()
137        remote = "pipe"
138
139    if conn:
140        logger.info(f"Accepted GDB connection from {remote}")
141
142        gdbstub.run(conn)
143
144        conn.close()
145
146    gdbserver.close()
147
148    logger.info("GDB session finished.")
149
150    elff.close()
151    logf.close()
152
153
154if __name__ == "__main__":
155    main()
156