Files
mercury/trace/mercury_trace_external.c
Julien Fischer f8d188fda8 Fix minor documentation problems.
deep_profiler/display_report.m:
deep_profiler/message.m:
deep_profiler/recursion_patterns.m:
deep_profiler/var_use_analsis.m:
java/runtime/UnreachableDefault.java:
runtime/mercury_engine.c:
runtime/mercury_minimal_model.c:
runtime/mercury_signal.h:
runtime/mercury_stack_layout.h:
runtime/mercury_wrapper.c:
runtime/mercury_threadscope.c:
trace/mercury_trace_external.c:
HISTORY:
    As above.
2018-10-09 05:27:36 +00:00

1465 lines
50 KiB
C

// 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_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_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_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_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