Skip to content
Snippets Groups Projects
ltl_debug.ml 7.92 KiB
open Batteries
open BatList
open Batteries
open BatList
open BatBuffer
open Prog
open Elang
open Cfg
open Elang_run
open Cfg_run
open Rtl
open Rtl_run
open Linear
open Ltl
open Ltl_print
open Utils
open Builtins
open Lwt
open Yojson
open Ltl_run



type dbg_cmd =
  | Next
  | NextBp
  | Break of int
  | RmBreak of int
  | Init of int * int list
  | Quit

let get_typed_attribute json attr toX =
  let open Yojson.Basic.Util in
  try
    OK (json |> member attr |> toX)
  with e -> Error (
      Yojson.Basic.to_string json |> fun s ->
      Format.sprintf "In get_typed_attribute\n json=%s\nattr=%s\n%s"
        s
        attr
        (Printexc.to_string e))

let parse_breakpoint j =
  let open Yojson.Basic.Util in
  let open Utils in
  get_typed_attribute j "ip" to_int >>= fun ip ->
  OK ip

let parse_init j =
  let open Yojson.Basic.Util in
  let open Utils in
  get_typed_attribute j "memsize" to_int >>= fun memsize ->
  get_typed_attribute j "params" (fun x -> x |> to_list |> filter_int) >>= fun params ->
  OK (Init (memsize, params))

let parse_dbg_cmd j =
  let open Yojson.Basic.Util in
  let open Utils in
  get_typed_attribute j "cmd" to_string >>= fun cmd ->
  begin match cmd with
    | "next" -> OK Next
    | "next_bp" -> OK NextBp
    | "break" -> parse_breakpoint j >>= fun ip -> OK (Break ip)
    | "rmbreak" -> parse_breakpoint j >>= fun ip -> OK (RmBreak ip)
    | "init" -> parse_init j
    | "quit" -> OK Quit
    | _ -> Error (Format.sprintf "Unknown command '%s'" cmd)
  end

let rec json_summary j =
  match j with
    `Assoc l ->
    let ks = ["regs"; "memwrite"; "memread"; "start"; "end"; "params"] in
    let l = List.filter_map (fun (k,v) -> if List.mem k ks then None
                              else if k = "code" then Some (k, `List [])
                              else Some (k, json_summary v)) l in
    `Assoc l
  | `List l -> `List (List.map json_summary l)
  | _ -> j
let trace_regs st =
  Hashtbl.fold (fun r v acc ->
      (string_of_reg r, `Int v) :: acc
    ) st.regs []
  |> fun l -> `Assoc l

let make_trace ip (st: ltl_state) out () =
  let m = Mem.write_log st.mem () in
  let mread = Mem.read_log st.mem () in
  let out = if out = "" then [] else [("output", `String (String.escaped out))] in
  `Assoc ([
      ("step", `Int !(st.numstep));
      ("ip", `Int ip);
      ("regs", trace_regs st);
      ("memwrite", `Assoc (List.map (fun (addr,v) -> (string_of_int addr, `Int v)) m));
      ("memread", `List (List.map (fun addr -> `Int addr) mread))
    ] @ out)

let step_until state st send_json cond =
  let rec aux () =
    match !state with
    | Some  ip ->
      begin match exec_ltl_instr Format.str_formatter ip st with
          OK r ->
          st.numstep := !(st.numstep) + 1;
          state := r;
          begin match r with
              Some ip ->
              let o = Format.flush_str_formatter () in
              make_trace ip st o () |> send_json >>= fun _ ->
              if cond ip
              then `Assoc [("currentstep", `Int !(st.numstep))] |> send_json
              else aux ()
            | None ->
              `Assoc [("currentstep", `Int (!(st.numstep) - 1))] |> send_json >>= fun _ ->
              `Assoc [("finished", `Bool true)] |> send_json
          end
        | Error msg ->
          let s = String.escaped (string_of_error msg) in
          `Assoc [("error", `String s)] |>
          send_json
      end
    | None -> return ()
  in aux ()

open Websocket
open Websocket_lwt_unix

let json_loc (l: Regalloc.loc) : Yojson.t =
  match l with
  | Regalloc.Reg r -> `Assoc [("reg", `String(string_of_reg r))]
  | Regalloc.Stk o -> `Assoc [("stk", `Int(o))]

let debugger_message progname breaks state st prog rstop client : unit Lwt.t =
  let open Frame in
  let send msg = Connected_client.send client (Frame.create ~opcode:Opcode.Text ~content:msg ())in
  let send_json j =
    Logs_lwt.info
      (fun m -> m "Sending %s\n" (json_summary j |> to_string)) >>= fun _ ->
    let s = to_string j ^ "@" in send s
  in
  let rec loop () =
    let react fr =
      Lwt_log.debug_f "<- %s" (Frame.show fr) >>= fun () ->
      match fr.opcode with
      | Opcode.Text ->
        begin
          let msg = fr.content in
          Logs.info (fun m -> m "received command '%s'\n" msg);
          let open Yojson.Basic in
          let j = from_string msg in
          match parse_dbg_cmd j with
          | OK Next ->
            step_until state !st send_json (fun ip -> true)
          | OK NextBp ->
            step_until state !st send_json
              (fun ip -> List.mem ip !breaks)
          | OK (Break ip) ->
            breaks := ip :: !breaks; Lwt.return_unit
          | OK (RmBreak ip) ->
            breaks := List.remove_all !breaks ip; Lwt.return_unit
          | OK (Init (memsize, params)) ->
            st := init_state memsize prog params;
            breaks := [];
            begin match Hashtbl.find_option !st.funs "main" with
              | Some floc ->
                let ip = floc.funstart in
                state := Some ip;
                Hashtbl.fold (fun fname { funstart; funend; funinfo; funregalloc}
                               (* (fstart,fend,var2reg, alloc) *) acc ->
                    (`Assoc [("fname",`String fname);
                             ("start",`Int funstart);
                             ("end", `Int funend);
                             ("vars", `Assoc (List.filter_map (fun (k,v) ->
                                  match List.assoc_opt v funregalloc with
                                  | None -> None
                                  | Some l -> Some (k,json_loc l)
                                ) funinfo))
                            ]) :: acc
                  ) !st.funs [] |> fun funboundaries ->
                Hashtbl.fold (fun ip ins acc ->
                    (Format.fprintf Format.str_formatter "%a" dump_ltl_instr ins);
                    (string_of_int ip, `String (Format.flush_str_formatter ())) :: acc
                  ) !st.code [] |> fun code ->
                `List ( [`Assoc [("progname", `String progname);
                                ("params", `List (List.map (fun x -> `Int x) params))];
                         `Assoc [("funboundaries", `List funboundaries)];
                         `Assoc [("code", `Assoc code)];
                         make_trace ip !st "" ();
                         `Assoc [("currentstep", `Int !(!st.numstep))]])
                |> send_json
              | None -> fail (failwith (Format.sprintf "Could not find 'main' entry point..."))
            end
          | OK Quit ->
            Lwt.wakeup rstop (); Lwt.return_unit
          | Error errmsg ->
            `Assoc [("error", `String (Format.sprintf "Error while parsing %s" msg));
                   ("errorMsg", `String (string_of_error errmsg))
                  ] |>
            send_json
        end
      | _ -> Lwt.return_unit
    in
    Websocket_lwt_unix.Connected_client.recv client >>= react >>= loop
  in
  Lwt.catch
    loop
    (fun exn ->
       Lwt_log.info_f  "Connection to client lost" >>= fun () ->
       Lwt.fail exn)
let debug_ltl_prog progname lp memsize params : unit=
  let () = Logs.set_reporter (Logs.format_reporter ()) in
  let () = Logs.set_level (Some Logs.Info) in
  let st = ref (init_state memsize lp params) in
  let state = ref (None) in
  let breaks = ref [] in
  let ctx = Conduit_lwt_unix.default_ctx in
  let uri = "http://localhost:8080" in
  let (pstop, rstop) = Lwt.task () in
  let server () =
    Resolver_lwt.resolve_uri (Uri.of_string uri) Resolver_lwt_unix.system >>= fun endp ->
    Conduit_lwt_unix.endp_to_server ~ctx endp >>= fun server ->
    Websocket_lwt_unix.establish_server
      ~ctx ~mode:server
      ~check_request:(fun req -> true)
      ~on_exn:(fun exn ->
          match exn with
          End_of_file -> Logs.info (fun m -> m "Client disconnected\n")
          | _ -> Logs.err (fun m -> m "Received exception : %s" (Printexc.to_string exn))
        )
      (debugger_message progname breaks state st lp rstop) in
  Lwt_main.run (Lwt.pick [server () ; pstop])