// vim: ts=4 sw=4 expandtab ft=c

// Copyright (C) 1998-2008,2011-2012 The University of Melbourne.
// Copyright (C) 2014, 2016, 2018 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.

// This file contains the code that interfaces between the Mercury program
// being debugged and the external trace analysis process.
//
// For the general basis of trace analysis systems, see the paper
// "Opium: An extendable trace analyser for Prolog" by Mireille Ducasse,
// available from http://www.irisa.fr/lande/ducasse.
//
// The code for using an external debugger is conditionalized
// on MR_USE_EXTERNAL_DEBUGGER which is enabled for systems that support
// sockets.
//
// Main authors: Erwan Jahier and Fergus Henderson.

#include "mercury_imp.h"

#ifdef MR_USE_EXTERNAL_DEBUGGER

#include "mercury_trace.h"
#include "mercury_trace_external.h"
#include "mercury_trace_util.h"
#include "mercury_trace_browse.h"
#include "mercury_trace_vars.h"

#include "mdb.debugger_interface.mh"
#include "mdb.collect_lib.mh"

#include "type_desc.mh"

#include "mercury_deep_copy.h"
#include "mercury_runtime_util.h"

#include <stdio.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdlib.h>
#ifdef MR_HAVE_DLFCN_H
  #include <dlfcn.h>
#endif

// This type must match the definition of classify_request in
// browser/debugger_interface.m.

typedef enum {
    // initiate debugging session
    MR_REQUEST_HELLO_REPLY              = 0,

    // go to the next matching trace event
    MR_REQUEST_FORWARD_MOVE             = 1,

    // report data for current_vars query
    MR_REQUEST_CURRENT_VARS             = 2,

    // report data for current_slots query
    MR_REQUEST_CURRENT_SLOTS            = 3,

    // continue to end, not tracing
    MR_REQUEST_NO_TRACE                 = 4,

    // abort the current execution
    MR_REQUEST_ABORT_PROG               = 5,

    // something went wrong
    MR_REQUEST_ERROR                    = 6,

    // report data for current_live_var_names query
    MR_REQUEST_CURRENT_LIVE_VAR_NAMES   = 7,

    // report data for current_nth_var query
    MR_REQUEST_CURRENT_NTH_VAR          = 8,

    // restart the execution to the call port of the current event
    MR_REQUEST_RETRY                    = 9,

    // print the ancestors list
    MR_REQUEST_STACK                    = 10,

    // print the nondet stack
    MR_REQUEST_NONDET_STACK             = 11,

    // prints the contents of the virtual machine registers.
    MR_REQUEST_STACK_REGS               = 12,

    // wait for a normal interactive query
    MR_REQUEST_INTERACTIVE_QUERY_NORMAL = 13,

    // wait for a cc interactive query
    MR_REQUEST_INTERACTIVE_QUERY_CC     = 14,

    // wait for a io interactive query
    MR_REQUEST_INTERACTIVE_QUERY_IO     = 15,
    // pass down new options to compile queries with
    MR_REQUEST_MMC_OPTIONS              = 16,

    // call the term browser
    MR_REQUEST_BROWSE                   = 17,
    // dynamically link the collect module
    MR_REQUEST_LINK_COLLECT             = 18,

    // collecting monitoring informations
    MR_REQUEST_COLLECT                  = 19,

    // retrieving the grade of the current program has been compiled with
    MR_REQUEST_CURRENT_GRADE            = 20,

    // switch the arguments collecting on
    MR_REQUEST_COLLECT_ARG_ON           = 21,

    // switch the arguments collecting off
    MR_REQUEST_COLLECT_ARG_OFF          = 22

} MR_debugger_request_type;

MercuryFile         MR_debugger_socket_in;
MercuryFile         MR_debugger_socket_out;

static MR_String    MR_external_mmc_options;

// Type of a static variable that indicates in which mode the external
// debugger is. When the external debugger is in mode:
// (1) `MR_searching', it tries to find an event that matches a forward
//     move request,
// (2) `MR_reading_request', it reads a new request on the socket,
// (3) `MR_collecting', it is collecting information (after a `collect'
//     request).

typedef enum {
    MR_searching, MR_reading_request, MR_collecting
} MR_external_debugger_mode_type;

static  MR_external_debugger_mode_type
    external_debugger_mode = MR_reading_request;

// Global variable that is used to store the information collected during
// a collect request.

static  MR_Word MR_accumulator_variable;

// Global variable that is sent to collect caller.

static  MR_Word MR_collected_variable;

// Function pointer used to post-process the result of the collect activity

static void (*post_process_ptr)(MR_Word, MR_Word *);

// Function pointer used to sent the collecting variable to the external
// debugger.

static  void    (*send_collect_result_ptr)(MR_Word, MR_Word);

// Variable generated during the dynamic linking that is needed to close
// this linking properly.

static  MR_Word collect_lib_maybe_handle;

// Static variable that tells whether the list of arguments is available
// within a collect module.

static  MR_bool MR_collect_arguments = MR_FALSE;

// Use a GNU C extension to enforce static type checking
// for printf-style functions.
// (See the "Function attributes" section of "C extensions"
// chapter of the GNU C manual for detailed documentation.)

#ifdef __GNUC__
  #define MR_LIKE_PRINTF(format_argnum, vars_argnum) \
    __attribute__ ((format (printf, (format_argnum), (vars_argnum))))
#else
  #define MR_LIKE_PRINTF(n, m) // nothing
#endif

static void     MR_send_message_to_socket_format(const char *format, ...)
                    MR_LIKE_PRINTF(1, 2);

static void     MR_send_message_to_socket(const char *message);
static void     MR_read_request_from_socket(MR_Word *debugger_request_ptr,
                    MR_Integer *debugger_request_type_ptr);

static MR_bool  MR_found_match(const MR_LabelLayout *layout,
                    MR_TracePort port, MR_Unsigned seqno, MR_Unsigned depth,
                    /* XXX registers */ const char *path, MR_Word search_data);
static void     MR_output_current_slots(const MR_LabelLayout *layout,
                    MR_TracePort port, MR_Unsigned seqno,
                    MR_Unsigned depth, const char *path, int lineno);
static void     MR_output_current_vars(MR_Word var_list, MR_Word string_list);
static void     MR_output_current_nth_var(MR_Word var);
static void     MR_output_current_live_var_names(MR_Word var_names_list,
                     MR_Word type_list);
static MR_Word  MR_trace_make_var_list(void);
static MR_Word  MR_trace_make_var_names_list(void);
static MR_Word  MR_trace_make_type_list(void);
static MR_Word  MR_trace_make_nth_var(MR_Word debugger_request);
static int      MR_get_var_number(MR_Word debugger_request);
static void     MR_print_proc_id_to_socket(const MR_ProcLayout *entry,
                    const char *extra, MR_Word *base_sp, MR_Word *base_curfr);
static void     MR_dump_stack_record_print_to_socket(FILE *fp,
                    MR_bool include_trace_data,
                    const MR_StackFrameDumpInfo *frame_dump_info);
static void     MR_get_list_modules_to_import(MR_Word debugger_request,
                    MR_Integer *modules_list_length_ptr,
                    MR_Word *modules_list_ptr);
static void     MR_get_mmc_options(MR_Word debugger_request,
                    MR_String *mmc_options_ptr);
static void     MR_get_object_file_name(MR_Word debugger_request,
                    MR_String *object_file_name_ptr);
static void     MR_get_variable_name(MR_Word debugger_request,
                    MR_String *var_name_ptr);
static void     MR_trace_browse_one_external(MR_VarSpec which_var);
static void     MR_send_collect_result(void);

#if 0
This pseudocode should go in the debugger process:

#define SOCKET_PATH "/var/tmp/"     // +5 for pid = 14 chars
#define SOCKET_PERM S_IRWXU         // rwx for user only

sprintf(Name, "%s%05d", SOCKET_PATH, getpid());

socket(unix, stream, Sock),
bind(sock, Name, Socket_file),
if (do_it_manually) {
    printf( "user: you must do\n"
            "   setenv MERCURY_INET_DEBUGGER_SOCKET Name\n"
        "and then run the program\n");
    ... just wait for the user do it ...
} else {
    fork()
    if (child) {
        setenv(MERCURY_UNIX_DEBUGGER_SOCKET, Name);
        exec(user program)
    }
}
listen(Sock, 1),
accept(Sock, _, New_Sock).

#endif

#if 0

static void
MR_init_unix_address(const char *name, struct sockaddr_un *unix_addr)
{
    // The code here is adapted from Stevens, "Advanced Programming
    // in the UNIX environment", page 501.
    // Don't blame me, I'm just copying this code from Stevens ;-)

    memset(unix_addr, 0, sizeof(unix_addr));
    unix_addr->sun_family = AF_UNIX;
    strcpy(unix_addr->sun_path, name);
    #ifdef SCM_RIGHTS
        len = sizeof(unix_addr->sun_len) +
            sizeof(unix_addr->sun_family) +
            strlen(unix_addr->sun_path) + 1;
        unix_addr->sun_len = len;
    #else
        len = strlen(unix_addr->sun_path) +
            sizeof(unix_addr->sun_family);
        if (len != 16) {
            MR_fatal_error("unix socket: length != 16");
        }
    #endif
}
#endif

static MR_bool MR_debug_socket = MR_FALSE;

void
MR_trace_init_external(void)
{
    int                 fd;
    size_t              len;
    FILE                *file_in;
    FILE                *file_out;
    int                 addr_family;
    char                *unix_socket;
    char                *inet_socket;
    struct sockaddr_un  unix_address;
    struct sockaddr_in  inet_address;
    struct sockaddr     *addr;
    MR_Word             debugger_request;
    MR_Integer          debugger_request_type;
    char                errbuf[MR_STRERROR_BUF_SIZE];

    // MR_external_mmc_options contains the options to pass to mmc
    // when compiling queries. We initialize it to the MR_String "".

    MR_TRACE_CALL_MERCURY(
        ML_DI_init_mercury_string(&MR_external_mmc_options)
    );

    // We presume that the user's program has been invoked from
    // within the debugger (e.g. Morphine).
    // The debugger (or the user) should set the
    // MERCURY_DEBUGGER_UNIX_SOCKET or MERCURY_DEBUGGER_INET_SOCKET
    // environment variable to tell the user's program which socket
    // it needs to connect to.

    unix_socket = getenv("MERCURY_DEBUGGER_UNIX_SOCKET");
    inet_socket = getenv("MERCURY_DEBUGGER_INET_SOCKET");
    if (unix_socket == NULL && inet_socket == NULL) {
        MR_fatal_error("you must set either the MERCURY_DEBUGGER_UNIX_SOCKET\n"
            "or MERCURY_DEBUGGER_INET_SOCKET environment variable");
    }
    if (unix_socket != NULL && inet_socket != NULL) {
        MR_fatal_error("you must set only one of the "
            "MERCURY_DEBUGGER_UNIX_SOCKET\n"
            "and MERCURY_DEBUGGER_INET_SOCKET environment variables");
    }
    if (unix_socket) {
        // We use `(memset)(...)' rather than `memset'
        // to prevent macro expansion; this is needed because
        // with GNU libc 2.1.2 and gcc 2.95, gcc barfs on the latter,
        // with an error message about impossible asm due to a conflict
        // between our use of global register variables and the inline
        // assembler macro definition of memset.

        addr_family = AF_UNIX;
        (memset)(&unix_address, 0, sizeof(unix_address));
        unix_address.sun_family = AF_UNIX;
        strcpy(unix_address.sun_path, unix_socket);
        addr = (struct sockaddr *) &unix_address;
        len = strlen(unix_address.sun_path) + sizeof(unix_address.sun_family);
    } else {
        char            hostname[255];
        char            port_string[255];
        unsigned short  port;
        int             host_addr;

        // Parse the MERCURY_DEBUGGER_INET_SOCKET environment variable.
        // It should be in the format "<hostname> <port>",
        // where <hostname> is numeric (e.g. "123.456.78.90").

        if (sscanf(inet_socket, "%254s %254s", hostname, port_string) != 2) {
            MR_fatal_error("MERCURY_DEBUGGER_INET_SOCKET invalid");
        }
        host_addr = inet_addr(hostname);
        if (host_addr == -1) {
            MR_fatal_error("MERCURY_DEBUGGER_INET_SOCKET: invalid address");
        }
        if (sscanf(port_string, "%hu", &port) != 1) {
            MR_fatal_error("MERCURY_DEBUGGER_INET_SOCKET: invalid port");
        }

        if (MR_debug_socket) {
            fprintf(stderr, "Mercury runtime: host = %s, port = %d\n",
                hostname, port);
        }
        inet_address.sin_family = AF_INET;
        inet_address.sin_addr.s_addr = host_addr;
        inet_address.sin_port = htons(port);
        addr_family = AF_INET;
        addr = (struct sockaddr *) &inet_address;
        len = sizeof(inet_address);
    }

    // Open the socket.

    fd = socket(addr_family, SOCK_STREAM, 0);
    if (fd < 0) {
        fprintf(stderr, "Mercury runtime: socket() failed: %s\n",
            MR_strerror(errno, errbuf, sizeof(errbuf)));
        MR_fatal_error("cannot open socket for debugger");
    } else if (MR_debug_socket) {
        fprintf(stderr,"Mercury runtime: creation of socket ok\n");
    }

    // Connect to the socket.

    if (connect(fd, addr, len) < 0) {
        fprintf(stderr, "Mercury runtime: connect() failed: %s\n",
            MR_strerror(errno, errbuf, sizeof(errbuf)));
        MR_fatal_error("can't connect to debugger socket");
    } else if (MR_debug_socket) {
        fprintf(stderr, "Mercury runtime: connection to socket: ok\n");
    }

    // Convert the socket fd to a Mercury stream.

    file_in = fdopen(fd, "r");
    file_out = fdopen(fd, "w");
    if ((file_in == NULL)||(file_out == NULL)) {
        fprintf(stderr, "Mercury runtime: fdopen() failed: %s\n",
            MR_strerror(errno, errbuf, sizeof(errbuf)));
        MR_fatal_error("cannot open debugger socket");
    } else if (MR_debug_socket) {
        fprintf(stderr, "Mercury runtime: fdopen(): ok\n");
    }

    MR_mercuryfile_init(file_in, 1, &MR_debugger_socket_in);
    MR_mercuryfile_init(file_out, 1, &MR_debugger_socket_out);

    // Send hello.

    MR_send_message_to_socket("hello");
    if (MR_debug_socket) {
        fprintf(stderr, "Mercury runtime: Send hello\n");
    }

    // Wait for hello_reply.

    MR_read_request_from_socket(&debugger_request, &debugger_request_type);

    if (debugger_request_type != MR_REQUEST_HELLO_REPLY) {
        MR_fatal_error("unexpected command on debugger socket");
    } else if (MR_debug_socket) {
        fprintf(stderr, "Mercury runtime: read hello_reply\n");
    }

    // Send start to start the synchronous communication with the debugger.

    MR_send_message_to_socket("start");
    if (MR_debug_socket) {
        fprintf(stderr, "Mercury runtime: start send\n");
    }
}

void
MR_trace_final_external(void)
{
    // This can only happen during a forward_move or a collect request.
    // In the first case, we want to tell the debugger that no match was found;
    // in the second one we send the result of the collect activity.

    switch(external_debugger_mode) {
        case MR_searching:
            MR_send_message_to_socket("forward_move_match_not_found");
            break;

        case MR_collecting:
            MR_send_collect_result();
            MR_send_message_to_socket("execution_terminated");
            break;

        default:
            MR_fatal_error("Error in the external debugger");
    }

    // Maybe we should loop to process requests from the debugger socket here?
    // Currently we just return, which will result in the debuggee terminating.
    // (This will need to change if the debuggee is to record trace history
    // and support a `move_backward' request.)
}

MR_Code *
MR_trace_event_external(MR_TraceCmdInfo *cmd, MR_EventInfo *event_info)
{
    static MR_Word          search_data;
    static void             (*initialize_ptr)(MR_Word *);
    static void             (*get_collect_var_type_ptr)(MR_Word *);
    static MR_bool          collect_linked = MR_FALSE;
    MR_bool                 stop_collecting = MR_FALSE;
    MR_Integer              debugger_request_type;
    MR_Word                 debugger_request;
    MR_Word                 var_list;
    MR_Word                 var_names_list;
    MR_Word                 type_list;
    MR_Word                 var;
    MR_Code                 *jumpaddr = NULL;
    const char              *message;
    MR_bool                 include_trace_data = MR_TRUE;
    const MR_LabelLayout    *layout = event_info->MR_event_sll;
    MR_Unsigned             seqno = event_info->MR_call_seqno;
    MR_Unsigned             depth = event_info->MR_call_depth;
    MR_TracePort            port = event_info->MR_trace_port;
    const char              *path = event_info->MR_event_path;
    MR_Word                 *saved_regs = event_info->MR_saved_regs;
    MR_Integer              modules_list_length;
    MR_Word                 modules_list;
    MR_RetryResult          retry_result;
    static MR_String        MR_object_file_name;
    int                     lineno = 0;
    MR_bool                 unsafe_retry;

    MR_debug_enabled = MR_FALSE;
    MR_update_trace_func_enabled();

    MR_trace_init_point_vars(event_info->MR_event_sll,
        event_info->MR_saved_regs, event_info->MR_saved_f_regs,
        event_info->MR_trace_port, MR_FALSE);

    switch(external_debugger_mode) {
        case MR_searching:
            // XXX Should also pass the registers here, since they are needed
            // for checking for matches with the arguments.

            if (MR_found_match(layout, port, seqno, depth,
                /* XXX registers, */ path, search_data))
            {
                MR_send_message_to_socket("forward_move_match_found");
                external_debugger_mode = MR_reading_request;
            } else {
                goto done;
            }
            break;

        case MR_collecting:
            MR_send_collect_result();
            MR_send_message_to_socket("execution_continuing");
            break;

        case MR_reading_request:
            break;

        default:
            MR_fatal_error("Software error in the debugger.\n");
    }

    lineno = MR_get_line_number(event_info->MR_saved_regs, layout, port);

    // Loop to process requests read from the debugger socket.
    for(;;) {
        MR_read_request_from_socket(&debugger_request, &debugger_request_type);
        switch((int) debugger_request_type) {
            case MR_REQUEST_ABORT_PROG:
                exit(EXIT_SUCCESS);

            case MR_REQUEST_FORWARD_MOVE:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: FORWARD_MOVE\n");
                }
                search_data = debugger_request;
                external_debugger_mode = MR_searching;
                goto done;

            case MR_REQUEST_CURRENT_LIVE_VAR_NAMES:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: "
                        "MR_REQUEST_CURRENT_LIVE_VAR_NAMES\n");
                }
                var_names_list = MR_trace_make_var_names_list();
                type_list = MR_trace_make_type_list();
                MR_output_current_live_var_names(var_names_list, type_list);
                break;

            case MR_REQUEST_CURRENT_VARS:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_CURRENT_VARS\n");
                }
                var_list = MR_trace_make_var_list();
                var_names_list = MR_trace_make_var_names_list();
                MR_output_current_vars(var_list, var_names_list);
                break;

            case MR_REQUEST_CURRENT_NTH_VAR:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_NTH_CURRENT_VAR\n");
                }
                var = MR_trace_make_nth_var(debugger_request);
                MR_output_current_nth_var(var);
                break;

            case MR_REQUEST_CURRENT_SLOTS:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_CURRENT_SLOTS\n");
                }
                MR_output_current_slots(layout, port, seqno, depth, path,
                    lineno);
                break;

            case MR_REQUEST_RETRY:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: REQUEST_RETRY\n");
                }

                retry_result = MR_trace_retry(event_info, 0,
                    MR_RETRY_IO_ONLY_IF_SAFE, MR_FALSE, "", &unsafe_retry,
                    &message, NULL, NULL, &jumpaddr);
                if (retry_result == MR_RETRY_OK_DIRECT) {
                    MR_send_message_to_socket("ok");
                    cmd->MR_trace_cmd = MR_CMD_STEP;
                    goto done;
                } else {
                    MR_send_message_to_socket_format("error(\"%s\").\n",
                        message);
                }
                break;

            case MR_REQUEST_STACK:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: REQUEST_STACK\n");
                }

                MR_trace_init_modules();
                message = MR_dump_stack_from_layout(stdout, layout,
                    MR_saved_sp(saved_regs), MR_saved_curfr(saved_regs),
                    include_trace_data, MR_FALSE, 0, 0,
                    &MR_dump_stack_record_print_to_socket);

                MR_send_message_to_socket("end_stack");
                if (message != NULL) {
                    MR_send_message_to_socket_format("error(\"%s\").\n",
                        message);
                } else {
                    MR_send_message_to_socket("ok");
                }
                break;

            case MR_REQUEST_NONDET_STACK:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_NONDET_STACK\n");
                }
                MR_trace_init_modules();
                // XXX As in stack dump, we could send the output of this
                // function on the socket. But the outputs are done via
                // fprintf() and printlabel(), so we would need to define new
                // fprintf() and printlabel() and pass them down as parameters
                // of MR_dump_nondet_stack() (as we do with
                // MR_dump_stack_record_print()).

                MR_dump_nondet_stack(stdout, 0, 0, MR_saved_maxfr(saved_regs));
                MR_send_message_to_socket("ok");
                break;

            case MR_REQUEST_STACK_REGS:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_STACK_REGS\n");
                }
                MR_send_message_to_socket_format(
                    "stack_regs(%lu, %lu, %lu).\n",
                    (unsigned long) MR_saved_sp(saved_regs),
                    (unsigned long) MR_saved_curfr(saved_regs),
                    (unsigned long) MR_saved_maxfr(saved_regs));
                break;

            case MR_REQUEST_INTERACTIVE_QUERY_NORMAL:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: "
                        "REQUEST_INTERACTIVE_QUERY_NORMAL\n");
                }
                MR_get_list_modules_to_import(debugger_request,
                    &modules_list_length, &modules_list);
                MR_trace_query_external(MR_NORMAL_QUERY,
                    MR_external_mmc_options, modules_list_length,
                    modules_list);
                break;

            case MR_REQUEST_INTERACTIVE_QUERY_IO:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: "
                        "REQUEST_INTERACTIVE_QUERY_IO\n");
                }
                MR_get_list_modules_to_import(debugger_request,
                    &modules_list_length, &modules_list);
                MR_trace_query_external(MR_IO_QUERY, MR_external_mmc_options,
                    modules_list_length, modules_list);
                break;

            case MR_REQUEST_INTERACTIVE_QUERY_CC:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: "
                        "REQUEST_INTERACTIVE_QUERY_CC\n");
                }
                MR_get_list_modules_to_import(debugger_request,
                    &modules_list_length, &modules_list);
                MR_trace_query_external(MR_CC_QUERY, MR_external_mmc_options,
                    modules_list_length, modules_list);
                break;

            case MR_REQUEST_MMC_OPTIONS:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_MMC_OPTIONS\n");
                }
                MR_get_mmc_options(debugger_request, &MR_external_mmc_options);
                MR_send_message_to_socket("mmc_options_ok");
                break;

            case MR_REQUEST_BROWSE:
                {
                    char        *var_name;
                    MR_VarSpec  var_spec;

                    if (MR_debug_socket) {
                        fprintf(stderr,
                            "\nMercury runtime: REQUEST_BROWSE\n");
                    }
                    MR_get_variable_name(debugger_request, &var_name);
                    var_spec.MR_var_spec_kind = MR_VAR_SPEC_NAME;
                    var_spec.MR_var_spec_name = var_name;
                    MR_trace_browse_one_external(var_spec);
                    MR_send_message_to_socket("browser_end");
                    break;
                }

            case MR_REQUEST_NO_TRACE:
                cmd->MR_trace_cmd = MR_CMD_TO_END;
                external_debugger_mode = MR_searching;
                goto done;

            case MR_REQUEST_LINK_COLLECT:
                {
                    MR_Char result;
                    MR_Word MR_accumulator_variable_type;

                    if (MR_debug_socket) {
                        fprintf(stderr,
                            "\nMercury runtime: REQUEST_LINK_COLLECT\n");
                    }
                    MR_get_object_file_name(debugger_request,
                        &MR_object_file_name);
                    MR_TRACE_CALL_MERCURY(
                        ML_CL_link_collect(MR_object_file_name,
                            (MR_Word *) &cmd->MR_filter_ptr,
                            (MR_Word *) &initialize_ptr,
                            (MR_Word *) &post_process_ptr,
                            (MR_Word *) &send_collect_result_ptr,
                            (MR_Word *) &get_collect_var_type_ptr,
                            &collect_lib_maybe_handle,
                            &result
                        )
                    );
                    collect_linked = (result == 'y');
                    if (collect_linked) {
                        MR_send_message_to_socket("link_collect_succeeded");
                        MR_TRACE_CALL_MERCURY(
                            (*get_collect_var_type_ptr)(
                                &MR_accumulator_variable_type);
                        );
                        MR_accumulator_variable = MR_make_permanent(
                            MR_accumulator_variable,
                            (MR_TypeInfo) MR_accumulator_variable_type);
                    } else {
                        MR_send_message_to_socket("link_collect_failed");
                    }
                    break;
                  }
            case MR_REQUEST_COLLECT:
                if (MR_debug_socket) {
                    fprintf(stderr, "\nMercury runtime: REQUEST_COLLECT\n");
                }
                if (collect_linked) {
                    MR_send_message_to_socket("collect_linked");
                    external_debugger_mode = MR_collecting;
                    MR_TRACE_CALL_MERCURY(
                        (*initialize_ptr)(&MR_accumulator_variable);
                    );

                    // In order to perform the collect from the current
                    // event, we need to call filter once here.

                    MR_COLLECT_filter(cmd->MR_filter_ptr, seqno, depth,
                        port, layout, path, lineno, &stop_collecting);

                    if (stop_collecting) {
                        MR_send_collect_result();
                        MR_send_message_to_socket("execution_continuing");
                        break;
                    } else {
                        // For efficiency, the remaining calls
                        // to MR_COLLECT_filter() are done in MR_trace_real().

                        cmd->MR_trace_cmd = MR_CMD_COLLECT;
                        cmd->MR_trace_must_check = MR_FALSE;
                        cmd->MR_trace_strict = MR_TRUE;
                        MR_init_trace_check_integrity(cmd);
                        cmd->MR_trace_print_level_specified = MR_TRUE;
                        cmd->MR_trace_print_level = MR_PRINT_LEVEL_NONE;
                        goto done;
                    }
                } else {
                    MR_send_message_to_socket("collect_not_linked");
                    break;
                }

            case MR_REQUEST_CURRENT_GRADE:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_CURRENT_GRADE\n");
                }
                MR_send_message_to_socket_format("grade(\"%s\").\n",
                    MR_GRADE_OPT);
                break;

            case MR_REQUEST_COLLECT_ARG_ON:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_COLLECT_ARG_ON\n");
                }
                MR_collect_arguments = MR_TRUE;
                MR_send_message_to_socket("collect_arg_on_ok");
                break;

            case MR_REQUEST_COLLECT_ARG_OFF:
                if (MR_debug_socket) {
                    fprintf(stderr,
                        "\nMercury runtime: REQUEST_COLLECT_ARG_OFF\n");
                }
                MR_collect_arguments = MR_FALSE;
                MR_send_message_to_socket("collect_arg_off_ok");
                break;

            default:
                MR_fatal_error("unexpected request read from "
                    "debugger socket");
        }
    }

done:
    // Recalculate the `must_check' flag, in case we changed the command
    // strictness or print-level.

    cmd->MR_trace_must_check = (! cmd->MR_trace_strict) ||
        (cmd->MR_trace_print_level != MR_PRINT_LEVEL_NONE);

#ifdef  MR_TRACE_CHECK_INTEGRITY
    cmd->MR_trace_must_check = cmd->MR_trace_must_check
        || cmd->MR_trace_check_integrity;
#endif

    MR_debug_enabled = MR_TRUE;
    MR_update_trace_func_enabled();

    return jumpaddr;
}

static void
MR_output_current_slots(const MR_LabelLayout *layout,
    MR_TracePort port, MR_Unsigned seqno, MR_Unsigned depth,
    const char *path, int lineno)
{
    if (MR_PROC_LAYOUT_IS_UCI(layout->MR_sll_entry)) {
        MR_TRACE_CALL_MERCURY(
            ML_DI_output_current_slots_comp(
                MR_trace_event_number,
                seqno,
                depth,
                port,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_type_name,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_type_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_def_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_pred_name,
                // is the type_ctor's arity what is wanted? XXX
                layout->MR_sll_entry->MR_sle_uci.MR_uci_type_arity,
                layout->MR_sll_entry->MR_sle_uci.MR_uci_mode,
                layout->MR_sll_entry->MR_sle_detism,
                (MR_String) (MR_Word) path,
                lineno,
                MR_wrap_output_stream(&MR_debugger_socket_out));
        );
    } else {
        MR_TRACE_CALL_MERCURY(
            ML_DI_output_current_slots_user(
                MR_trace_event_number,
                seqno,
                depth,
                port,
                layout->MR_sll_entry->MR_sle_user.MR_user_pred_or_func,
                (MR_String)
                layout->MR_sll_entry->MR_sle_user.MR_user_decl_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_user.MR_user_def_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_user.MR_user_name,
                layout->MR_sll_entry->MR_sle_user.MR_user_pred_form_arity,
                layout->MR_sll_entry->MR_sle_user.MR_user_mode,
                layout->MR_sll_entry->MR_sle_detism,
                (MR_String) (MR_Word) path,
                lineno,
                MR_wrap_output_stream(&MR_debugger_socket_out));
        );
    }
}

static void
MR_output_current_vars(MR_Word var_list, MR_Word string_list)
{
    MR_TRACE_CALL_MERCURY(
        ML_DI_output_current_vars(
            var_list,
            string_list,
            MR_wrap_output_stream(&MR_debugger_socket_out));
    );
}

static void
MR_output_current_nth_var(MR_Word var)
{
    MR_TRACE_CALL_MERCURY(
        ML_DI_output_current_nth_var(
            var,
            MR_wrap_output_stream(&MR_debugger_socket_out));
    );
}

static void
MR_output_current_live_var_names(MR_Word var_names_list, MR_Word type_list)
{
    MR_TRACE_CALL_MERCURY(
        ML_DI_output_current_live_var_names(
            var_names_list,
            type_list,
            MR_wrap_output_stream(&MR_debugger_socket_out));
    );
}

static void
MR_read_request_from_socket(MR_Word *debugger_request_ptr,
    MR_Integer *debugger_request_type_ptr)
{
    fflush(MR_file(MR_debugger_socket_in));

    MR_TRACE_CALL_MERCURY(
        ML_DI_read_request_from_socket(
            MR_wrap_input_stream(&MR_debugger_socket_in),
            debugger_request_ptr,
            debugger_request_type_ptr);
    );
}

static MR_bool
MR_found_match(const MR_LabelLayout *layout,
    MR_TracePort port, MR_Unsigned seqno, MR_Unsigned depth,
    // XXX live vars
    const char *path, MR_Word search_data)
{
    MR_bool result;

    // XXX Get live vars from registers.
    MR_Word arguments = 0;                                  // XXX FIXME!!!
    if (MR_PROC_LAYOUT_IS_UCI(layout->MR_sll_entry)) {
        MR_TRACE_CALL_MERCURY(
            result = ML_DI_found_match_comp(
                MR_trace_event_number,
                seqno,
                depth,
                port,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_type_name,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_type_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_def_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_uci.MR_uci_pred_name,
                // Is the type_ctor's arity what is wanted? XXX
                layout->MR_sll_entry->MR_sle_uci.MR_uci_type_arity,
                layout->MR_sll_entry->MR_sle_uci.MR_uci_mode,
                layout->MR_sll_entry->MR_sle_detism,
                arguments,
                (MR_String) (MR_Word) path,
                search_data);
        );
    } else {
        MR_TRACE_CALL_MERCURY(
            result = ML_DI_found_match_user(
                MR_trace_event_number,
                seqno,
                depth,
                port,
                layout->MR_sll_entry->MR_sle_user.MR_user_pred_or_func,
                (MR_String)
                layout->MR_sll_entry->MR_sle_user.MR_user_decl_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_user.MR_user_def_module,
                (MR_String)
                layout->MR_sll_entry->MR_sle_user.MR_user_name,
                layout->MR_sll_entry->MR_sle_user.MR_user_pred_form_arity,
                layout->MR_sll_entry->MR_sle_user.MR_user_mode,
                layout->MR_sll_entry->MR_sle_detism,
                arguments,
                (MR_String) path,
                search_data);
        );
    }

    return result;
}

static void
MR_send_message_to_socket_format(const char *format, ...)
{
    va_list args;

    va_start(args, format);
    vfprintf(MR_file(MR_debugger_socket_out), format, args);
    va_end(args);
    fflush(MR_file(MR_debugger_socket_out));
    MR_line_number(MR_debugger_socket_out)++;
}

static void
MR_send_message_to_socket(const char *message)
{
    fprintf(MR_file(MR_debugger_socket_out), "%s.\n", message);
    fflush(MR_file(MR_debugger_socket_out));
    MR_line_number(MR_debugger_socket_out)++;
}

// This function returns the list of the currently live variables
// as a list of univs.
// The memory needed will be allocated on the Mercury heap.
//
// If no information on live variables is available, or if there
// are no live variables, return the empty list.

static MR_Word
MR_trace_make_var_list(void)
{
    const char  *problem;
    int         var_count;
    int         i;
    MR_TypeInfo type_info;
    MR_Word     value;
    MR_Word     univ;
    MR_Word     var_list;

    var_count = MR_trace_var_count();

    MR_TRACE_USE_HP(
        var_list = MR_list_empty();
    );

    for (i = var_count; i > 0; i--) {
        problem = MR_trace_return_var_info(i, NULL, &type_info, &value);
        if (problem != NULL) {
            MR_fatal_error("%s", problem);
        }

        MR_TRACE_USE_HP(
            MR_new_univ_on_hp(univ, type_info, value);
        );

        MR_TRACE_USE_HP(
            var_list = MR_univ_list_cons(univ, var_list);
        );
    }

    return var_list;
}

// This function returns the list of the internal names of currently live
// variables.
// The memory needed will be allocated on the Mercury heap.
//
// If no information on live variables is available, or if there
// are no live variables, return the empty list.

static MR_Word
MR_trace_make_var_names_list(void)
{
    const char  *problem;
    int         var_count;
    int         i;
    const char  *name;
    MR_Word     var_names_list;

    var_count = MR_trace_var_count();

    MR_TRACE_USE_HP(
        var_names_list = MR_list_empty();
    );

    for (i = var_count; i > 0; i--) {
        problem = MR_trace_return_var_info(i, &name, NULL, NULL);
        if (problem != NULL) {
            MR_fatal_error("%s", problem);
        }

        MR_TRACE_USE_HP(
            var_names_list = MR_string_list_cons((MR_Word) name,
                var_names_list);
        );
    }

    return var_names_list;
}

// This function returns the list of types of currently live variables.
// The memory needed will be allocated on the Mercury heap.
//
// If no information on live variables is available, or if there
// are no live variables, return the empty list.

static MR_Word
MR_trace_make_type_list(void)
{
    const char  *problem;
    int         var_count;
    int         i;
    MR_TypeInfo type_info;
    MR_String   type_info_string;
    MR_Word     type_list;

    var_count = MR_trace_var_count();

    MR_TRACE_USE_HP(
        type_list = MR_list_empty();
    );

    for (i = var_count; i > 0; i--) {
        problem = MR_trace_return_var_info(i, NULL, &type_info, NULL);
        if (problem != NULL) {
            MR_fatal_error("%s", problem);
        }

        MR_TRACE_CALL_MERCURY(
            type_info_string = ML_type_name((MR_Word) type_info);
        );
        MR_TRACE_USE_HP(
            type_list = MR_string_list_cons((MR_Word) type_info_string,
                type_list);
        );
    }

    return type_list;
}

// This function returns the requested live variable, as a univ.
// Any memory needed will be allocated on the Mercury heap.

static MR_Word
MR_trace_make_nth_var(MR_Word debugger_request)
{
    const char  *problem;
    int         var_number;
    MR_TypeInfo type_info;
    MR_Word     value;
    MR_Word     univ;

    var_number = MR_get_var_number(debugger_request);
    // debugger_request should be of the form: current_nth_var(var_number)

    problem = MR_trace_return_var_info(var_number, NULL, &type_info, &value);
    if (problem == NULL) {
        MR_TRACE_USE_HP(
            MR_new_univ_on_hp(univ, type_info, value);
        );
    } else {
        // Should never occur since we check in the external debugger process
        // if a variable is live before retrieving it.

        MR_fatal_error("%s", problem);
    }

    return univ;
}

// This function is called only when debugger_request = current_nth_var(n).
// It returns the integer 'n'.

static int
MR_get_var_number(MR_Word debugger_request)
{
    int num;
    MR_TRACE_CALL_MERCURY(
        num = ML_DI_get_var_number(debugger_request);
    );
    return num;
}

// The protocol between the debugged Mercury program and the external debugger
// is the following:
// 1) The debugger sends "stack";
// 2) For each procedure in the stack that is not generated by the compiler, the
//    debuggee sends:
//  - level(int) (the level of the procedure in the stack)
//  - detail(unsigned long, unsigned long, unsigned long) if available
//    (the call event number, call sequence number and depth of the goal
//    of the procedure)
//  - the atom 'pred' or 'func' depending if the procedure is a function
//    or not
//  - proc(string, string, long, long) (the procedure)
//  - det(string) (the determinism of the procedure)
//  - def_module(string) (the name of the defining module) if different from
//    the current one.
//
//    For each compiler generated procedures, the debuggee sends:
//  - level(int) (as above)
//  - detail(unsigned long, unsigned long, unsigned long) (as above)
//  - proc(string, string, string, long, long) (the name of the
//    compiler-generated procedure)
//  - det(string) (as above)
//  - def_module(string) (as above)
// 3) The debuggee sends "end_stack"

static void
MR_dump_stack_record_print_to_socket(FILE *fp, MR_bool include_trace_data,
    const MR_StackFrameDumpInfo *frame_dump_info)
{
    // XXX If the external debugger is ever needed again, it should be updated
    // to send information across the socket about any reuse of a stack
    // frame by tail recursion events.

    if (frame_dump_info->MR_sdi_min_level != frame_dump_info->MR_sdi_max_level)
    {
        MR_fatal_error(
            "dumping stack frames of multiple calls to external debugger");
    }

    MR_send_message_to_socket_format(
        "level(%" MR_INTEGER_LENGTH_MODIFIER "u).\n",
        frame_dump_info->MR_sdi_min_level);
    MR_print_proc_id_to_socket(frame_dump_info->MR_sdi_proc_layout, NULL,
        frame_dump_info->MR_sdi_base_sp, frame_dump_info->MR_sdi_base_curfr);
}

static void
MR_print_proc_id_to_socket(const MR_ProcLayout *entry,
    const char *extra, MR_Word *base_sp, MR_Word *base_curfr)
{
    if (! MR_PROC_LAYOUT_HAS_PROC_ID(entry)) {
        MR_fatal_error("cannot retrieve procedure id without layout");
    }

    if (base_sp != NULL && base_curfr != NULL) {
        MR_bool print_details = MR_FALSE;
        if (MR_PROC_LAYOUT_HAS_EXEC_TRACE(entry)) {
            MR_Integer maybe_from_full = entry->MR_sle_maybe_from_full;
            if (maybe_from_full > 0) {
                // For procedures compiled with shallow tracing, the details
                // will be valid only if the value of MR_from_full saved in
                // the appropriate stack slot was MR_TRUE.

                if (MR_DETISM_DET_STACK(entry->MR_sle_detism)) {
                    print_details = MR_based_stackvar(base_sp,
                        maybe_from_full);
                } else {
                    print_details = MR_based_framevar(base_curfr,
                        maybe_from_full);
                }
            } else {
                // For procedures compiled with full tracing,
                // always print out the details.

                print_details = MR_TRUE;
            }
        }
        if (print_details) {
            if (MR_DETISM_DET_STACK(entry->MR_sle_detism)) {
                MR_send_message_to_socket_format(
                    "detail(%lu, %lu, %lu).\n",
                    (unsigned long) MR_event_num_stackvar(base_sp) + 1,
                    (unsigned long) MR_call_num_stackvar(base_sp),
                    (unsigned long) MR_call_depth_stackvar(base_sp));
            } else {
                MR_send_message_to_socket_format(
                    "detail(%lu, %lu, %lu).\n",
                    (unsigned long) MR_event_num_framevar(base_curfr) + 1,
                    (unsigned long) MR_call_num_framevar(base_curfr),
                    (unsigned long) MR_call_depth_framevar(base_curfr));
            }
        }
    }

    if (MR_PROC_LAYOUT_IS_UCI(entry)) {
        MR_send_message_to_socket_format(
            // XXX Names with " may cause some problems here.
            "proc(\"%s\",\"%s\",\"%s\",%ld,%ld).\n",
            entry->MR_sle_uci.MR_uci_pred_name,
            entry->MR_sle_uci.MR_uci_type_module,
            entry->MR_sle_uci.MR_uci_type_name,
            (long) entry->MR_sle_uci.MR_uci_type_arity,
            (long) entry->MR_sle_uci.MR_uci_mode);

        if (strcmp(entry->MR_sle_uci.MR_uci_type_module,
            entry->MR_sle_uci.MR_uci_def_module) != 0)
        {
            MR_send_message_to_socket_format("def_module(\"%s\").\n",
                entry->MR_sle_uci.MR_uci_def_module);
        }
    } else {
        if (entry->MR_sle_user.MR_user_pred_or_func == MR_PREDICATE) {
            MR_send_message_to_socket("pred");
        } else if (entry->MR_sle_user.MR_user_pred_or_func == MR_FUNCTION) {
            MR_send_message_to_socket("func");
        } else {
            MR_fatal_error("procedure is not pred or func");
        }

        MR_send_message_to_socket_format(
            // XXX Names with " may cause some problems here.
            "proc(\"%s\",\"%s\",%ld,%ld).\n",
            entry->MR_sle_user.MR_user_decl_module,
            entry->MR_sle_user.MR_user_name,
            (long) entry->MR_sle_user.MR_user_pred_form_arity,
            (long) entry->MR_sle_user.MR_user_mode);

        if (strcmp(entry->MR_sle_user.MR_user_decl_module,
            entry->MR_sle_user.MR_user_def_module) != 0)
        {
            MR_send_message_to_socket_format("def_module(\"%s\").\n",
                entry->MR_sle_user.MR_user_def_module);
        }
    }

    MR_send_message_to_socket_format("det(\"%s\").\n",
        MR_detism_names[entry->MR_sle_detism]);
}

static void
MR_get_list_modules_to_import(MR_Word debugger_request,
    MR_Integer *modules_list_length_ptr, MR_Word *modules_list_ptr)
{
    MR_TRACE_CALL_MERCURY(
        ML_DI_get_list_modules_to_import(debugger_request,
            modules_list_length_ptr, modules_list_ptr);
    );
}

static void
MR_get_mmc_options(MR_Word debugger_request, MR_String *mmc_options_ptr)
{
    MR_TRACE_CALL_MERCURY(
        ML_DI_get_mmc_options(debugger_request, mmc_options_ptr);
    );
}

static void
MR_get_object_file_name(MR_Word debugger_request,
    MR_String *object_file_name_ptr)
{
    MR_TRACE_CALL_MERCURY(
        ML_DI_get_object_file_name(debugger_request, object_file_name_ptr);
    );
}

static void
MR_get_variable_name(MR_Word debugger_request, MR_String *var_name_ptr)
{
    MR_TRACE_CALL_MERCURY(
        ML_DI_get_variable_name(debugger_request, var_name_ptr);
    );
}

// This function does the same thing as MR_trace_browse_one() defined in
// mercury_trace_internal.c except it sends/receives program-readable terms
// from/to the socket instead of sending human-readable strings from/to
// mdb_in/mdb_out.

static void
MR_trace_browse_one_external(MR_VarSpec var_spec)
{
    const char  *problem;

    problem = MR_trace_browse_one(NULL, MR_FALSE, var_spec,
        MR_trace_browse_external, MR_BROWSE_CALLER_BROWSE,
        MR_BROWSE_DEFAULT_FORMAT, MR_TRUE);

    if (problem != NULL) {
        MR_send_message_to_socket_format("error(\"%s\").\n", problem);
    }
}

// This function calls the collect filtering predicate defined by the user
// and dynamically link with the execution.

void
MR_COLLECT_filter(MR_FilterFuncPtr filter_ptr, MR_Unsigned seqno,
    MR_Unsigned depth, MR_TracePort port, const MR_LabelLayout *layout,
    const char *path, int lineno, MR_bool *stop_collecting)
{
    MR_Char result;
    MR_Word arguments;

    // Only pass the arguments list down filter if required,
    // i.e. if MR_collect_arguments is set to MR_TRUE. We need to do that
    // in order to not penalize the performance of collect in the cases where
    // the argument list (which might be very big) is not used.

    if (MR_collect_arguments) {
        arguments = MR_trace_make_var_list();
    } else {
        MR_TRACE_USE_HP(
            arguments = MR_list_empty()
        );
    }
    MR_TRACE_CALL_MERCURY(
        (*filter_ptr)(
            MR_trace_event_number,
            seqno,
            depth,
            port,
            layout->MR_sll_entry->MR_sle_user.MR_user_pred_or_func,
            (MR_String) layout->MR_sll_entry->MR_sle_user.MR_user_decl_module,
            (MR_String) layout->MR_sll_entry->MR_sle_user.MR_user_def_module,
            (MR_String) layout->MR_sll_entry->MR_sle_user.MR_user_name,
            layout->MR_sll_entry->MR_sle_user.MR_user_pred_form_arity,
            layout->MR_sll_entry->MR_sle_user.MR_user_mode,
            arguments,
            layout->MR_sll_entry->MR_sle_detism,
            (MR_String) path,
            lineno,
            MR_accumulator_variable,
            &MR_accumulator_variable,
            &result)
    );
    *stop_collecting = (result == 'y');
}

// This function retrieves the line number of the current goal.

int
MR_get_line_number(MR_Word *saved_regs, const MR_LabelLayout *layout,
    MR_TracePort port)
{
    const char              *filename;
    const MR_LabelLayout    *parent_layout;
    const char              *problem;
    int                     lineno = 0;
    MR_Word                 *base_sp;
    MR_Word                 *base_curfr;
    MR_Unsigned             reused_frames;

    if MR_port_is_interface(port) {
        // At external events, we want the line number where the call is made,
        // not the one where the procedure is defined.

        base_sp = MR_saved_sp(saved_regs);
        base_curfr = MR_saved_curfr(saved_regs);
        parent_layout = MR_find_nth_ancestor(layout, 1, &base_sp, &base_curfr,
            &reused_frames, &problem);
        // The external debugger does not (yet) know about reused frames.
        assert(reused_frames == 0);
        if (parent_layout != NULL) {
            (void) MR_find_context(parent_layout, &filename, &lineno);
        }
    } else {
        (void) MR_find_context(layout, &filename, &lineno);
    }

    return lineno;
}

static void
MR_send_collect_result(void)
{
    MR_TRACE_CALL_MERCURY(
        (*post_process_ptr)(MR_accumulator_variable, &MR_collected_variable);

        (*send_collect_result_ptr)(MR_collected_variable,
            (MR_Word) &MR_debugger_socket_out)
    );

#if defined(MR_HAVE_DLFCN_H) && defined(MR_HAVE_DLCLOSE)
    MR_TRACE_CALL_MERCURY(
        ML_CL_unlink_collect(collect_lib_maybe_handle)
    );
#endif
}

#endif // MR_USE_EXTERNAL_DEBUGGER
