X-Git-Url: http://info.iut-bm.univ-fcomte.fr/pub/gitweb/simgrid.git/blobdiff_plain/f242db7697124c32d3dc646961e4aac666239854..47130afb26809fc8c0e3a77cd168874a23abb62d:/src/mc/remote/CheckerSide.cpp diff --git a/src/mc/remote/CheckerSide.cpp b/src/mc/remote/CheckerSide.cpp index c1f10143ec..0b543d5f72 100644 --- a/src/mc/remote/CheckerSide.cpp +++ b/src/mc/remote/CheckerSide.cpp @@ -5,9 +5,14 @@ #include "src/mc/remote/CheckerSide.hpp" #include "src/mc/explo/Exploration.hpp" +#include "src/mc/mc_environ.h" +#include "xbt/config.hpp" +#include "xbt/system_error.hpp" + +#if SIMGRID_HAVE_STATEFUL_MC #include "src/mc/explo/LivenessChecker.hpp" #include "src/mc/sosp/RemoteProcessMemory.hpp" -#include "xbt/system_error.hpp" +#endif #ifdef __linux__ #include @@ -50,14 +55,9 @@ XBT_ATTRIB_NORETURN static void run_child_process(int socket, const std::vector< xbt_assert(prctl(PR_SET_PDEATHSIG, SIGHUP) == 0, "Could not PR_SET_PDEATHSIG"); #endif - // Remove CLOEXEC to pass the socket to the application - int fdflags = fcntl(socket, F_GETFD, 0); - xbt_assert(fdflags != -1 && fcntl(socket, F_SETFD, fdflags & ~FD_CLOEXEC) != -1, - "Could not remove CLOEXEC for socket"); - setenv(MC_ENV_SOCKET_FD, std::to_string(socket).c_str(), 1); if (need_ptrace) - setenv("MC_NEED_PTRACE", "1", 1); + setenv(MC_ENV_NEED_PTRACE, "1", 1); /* Setup the tokenizer that parses the cfg:model-check/setenv parameter */ using Tokenizer = boost::tokenizer>; @@ -110,19 +110,18 @@ static void wait_application_process(pid_t pid) #elif defined BSD ptrace(PT_CONTINUE, pid, (caddr_t)1, 0); #else -#error "no ptrace equivalent coded for this platform" + xbt_die("no ptrace equivalent coded for this platform, stateful model-checking is impossible."); #endif xbt_assert(errno == 0, "Ptrace does not seem to be usable in your setup (errno: %d). " "If you run from within a docker, adding `--cap-add SYS_PTRACE` to the docker line may help. " "If it does not help, please report this bug.", errno); + XBT_DEBUG("%d ptrace correctly setup.", getpid()); } -void CheckerSide::setup_events() +void CheckerSide::setup_events(bool socket_only) { - if (base_ != nullptr) - event_base_free(base_.get()); auto* base = event_base_new(); base_.reset(base); @@ -131,18 +130,22 @@ void CheckerSide::setup_events() [](evutil_socket_t, short events, void* arg) { auto checker = static_cast(arg); if (events == EV_READ) { - std::array buffer; - ssize_t size = recv(checker->get_channel().get_socket(), buffer.data(), buffer.size(), MSG_DONTWAIT); - if (size == -1) { - XBT_ERROR("Channel::receive failure: %s", strerror(errno)); - if (errno != EAGAIN) - throw simgrid::xbt::errno_error(); - } - - if (size == 0) // The app closed the socket. It must be dead by now. - checker->handle_waitpid(); - else if (not checker->handle_message(buffer.data(), size)) - checker->break_loop(); + do { + std::array buffer; + ssize_t size = checker->get_channel().receive(buffer.data(), buffer.size(), MSG_DONTWAIT); + if (size == -1) { + XBT_ERROR("Channel::receive failure: %s", strerror(errno)); + if (errno != EAGAIN) + throw simgrid::xbt::errno_error(); + } + + if (size == 0) // The app closed the socket. It must be dead by now. + checker->handle_waitpid(); + else if (not checker->handle_message(buffer.data(), size)) { + checker->break_loop(); + break; + } + } while (checker->get_channel().has_pending_data()); } else { xbt_die("Unexpected event"); } @@ -150,38 +153,50 @@ void CheckerSide::setup_events() this); event_add(socket_event_, nullptr); - signal_event_ = event_new( - base, SIGCHLD, EV_SIGNAL | EV_PERSIST, - [](evutil_socket_t sig, short events, void* arg) { - auto checker = static_cast(arg); - if (events == EV_SIGNAL) { - if (sig == SIGCHLD) - checker->handle_waitpid(); - else - xbt_die("Unexpected signal: %d", sig); - } else { - xbt_die("Unexpected event"); - } - }, - this); - event_add(signal_event_, nullptr); + if (socket_only) { + signal_event_ = nullptr; + } else { + signal_event_ = event_new( + base, SIGCHLD, EV_SIGNAL | EV_PERSIST, + [](evutil_socket_t sig, short events, void* arg) { + auto checker = static_cast(arg); + if (events == EV_SIGNAL) { + if (sig == SIGCHLD) + checker->handle_waitpid(); + else + xbt_die("Unexpected signal: %d", sig); + } else { + xbt_die("Unexpected event"); + } + }, + this); + event_add(signal_event_, nullptr); + } } +/* When this constructor is called, no other checkerside exists */ CheckerSide::CheckerSide(const std::vector& args, bool need_memory_info) : running_(true) { - bool need_ptrace = not need_memory_info; + XBT_DEBUG("Create a CheckerSide. Needs_meminfo: %s", need_memory_info ? "YES" : "no"); - // Create an AF_LOCAL socketpair used for exchanging messages between the model-checker process (ancestor) + // Create an AF_UNIX socketpair used for exchanging messages between the model-checker process (ancestor) // and the application process (child) int sockets[2]; - xbt_assert(socketpair(AF_LOCAL, SOCK_SEQPACKET | SOCK_CLOEXEC, 0, sockets) != -1, "Could not create socketpair"); + xbt_assert(socketpair(AF_UNIX, +#ifdef __APPLE__ + SOCK_STREAM, /* Mac OSX does not have AF_UNIX + SOCK_SEQPACKET, even if that's faster*/ +#else + SOCK_SEQPACKET, +#endif + 0, sockets) != -1, + "Could not create socketpair: %s", strerror(errno)); pid_ = fork(); - xbt_assert(pid_ >= 0, "Could not fork model-checked process"); + xbt_assert(pid_ >= 0, "Could not fork application process"); if (pid_ == 0) { // Child ::close(sockets[1]); - run_child_process(sockets[0], args, need_ptrace); + run_child_process(sockets[0], args, need_memory_info); // We need ptrace if we need the mem info DIE_IMPOSSIBLE; } @@ -189,23 +204,27 @@ CheckerSide::CheckerSide(const std::vector& args, bool need_memory_info) ::close(sockets[0]); channel_.reset_socket(sockets[1]); - setup_events(); - if (need_ptrace) + setup_events(false); /* we need a signal handler too */ + if (need_memory_info) { +#if SIMGRID_HAVE_STATEFUL_MC + // setup ptrace and sync with the app wait_application_process(pid_); - // Request the initial memory on need - if (need_memory_info) { - channel_.send(MessageType::INITIAL_ADDRESSES); - s_mc_message_initial_addresses_reply_t answer; + // Request the initial memory on need + channel_.send(MessageType::NEED_MEMINFO); + s_mc_message_need_meminfo_reply_t answer; ssize_t answer_size = channel_.receive(answer); xbt_assert(answer_size != -1, "Could not receive message"); - xbt_assert(answer.type == MessageType::INITIAL_ADDRESSES_REPLY, - "The received message is not the INITIAL_ADDRESS_REPLY I was expecting but of type %s", + xbt_assert(answer.type == MessageType::NEED_MEMINFO_REPLY, + "The received message is not the NEED_MEMINFO_REPLY I was expecting but of type %s", to_c_str(answer.type)); xbt_assert(answer_size == sizeof answer, "Broken message (size=%zd; expected %zu)", answer_size, sizeof answer); /* We now have enough info to create the memory address space */ remote_memory_ = std::make_unique(pid_, answer.mmalloc_default_mdp); +#else + xbt_die("Cannot introspect memory without MC support"); +#endif } wait_for_requests(); @@ -215,20 +234,43 @@ CheckerSide::~CheckerSide() { event_del(socket_event_); event_free(socket_event_); - event_del(signal_event_); - event_free(signal_event_); - - if (running()) { - errno = 0; - xbt_assert(kill(get_pid(), SIGKILL) == 0); - do { - errno = 0; - waitpid(get_pid(), nullptr, 0); - xbt_assert(errno == 0); - } while (EAGAIN == errno); + if (signal_event_ != nullptr) { + event_del(signal_event_); + event_free(signal_event_); } } +/* This constructor is called when cloning a checkerside to get its application to fork away */ +CheckerSide::CheckerSide(int socket, CheckerSide* child_checker) + : channel_(socket, child_checker->channel_), running_(true), child_checker_(child_checker) +{ + setup_events(true); // We already have a signal handled in that case + + s_mc_message_int_t answer; + ssize_t s = get_channel().receive(answer); + xbt_assert(s != -1, "Could not receive answer to FORK_REPLY"); + xbt_assert(s == sizeof answer, "Broken message (size=%zd; expected %zu)", s, sizeof answer); + xbt_assert(answer.type == MessageType::FORK_REPLY, + "Received unexpected message %s (%i); expected MessageType::FORK_REPLY (%i)", to_c_str(answer.type), + (int)answer.type, (int)MessageType::FORK_REPLY); + pid_ = answer.value; + + wait_for_requests(); +} + +std::unique_ptr CheckerSide::clone(int master_socket) +{ + s_mc_message_int_t m = {}; + m.type = MessageType::FORK; + m.value = getpid(); + xbt_assert(get_channel().send(m) == 0, "Could not ask the app to fork on need."); + + int sock = accept(master_socket, nullptr /* I know who's connecting*/, nullptr); + xbt_assert(sock > 0, "Cannot accept the incomming connection of the forked app: %s.", strerror(errno)); + + return std::make_unique(sock, this); +} + void CheckerSide::finalize(bool terminate_asap) { s_mc_message_int_t m = {}; @@ -245,7 +287,7 @@ void CheckerSide::finalize(bool terminate_asap) (int)answer.type, (int)MessageType::FINALIZE_REPLY); } -void CheckerSide::dispatch_events() const +void CheckerSide::dispatch_events() { event_base_dispatch(base_.get()); } @@ -258,14 +300,17 @@ void CheckerSide::break_loop() const bool CheckerSide::handle_message(const char* buffer, ssize_t size) { s_mc_message_t base_message; + ssize_t consumed; xbt_assert(size >= (ssize_t)sizeof(base_message), "Broken message. Got only %ld bytes.", size); memcpy(&base_message, buffer, sizeof(base_message)); switch (base_message.type) { case MessageType::IGNORE_HEAP: { + consumed = sizeof(s_mc_message_ignore_heap_t); +#if SIMGRID_HAVE_STATEFUL_MC if (remote_memory_ != nullptr) { s_mc_message_ignore_heap_t message; - xbt_assert(size == sizeof(message), "Broken message"); + xbt_assert(size >= static_cast(sizeof(message)), "Broken message"); memcpy(&message, buffer, sizeof(message)); IgnoredHeapRegion region; @@ -274,75 +319,100 @@ bool CheckerSide::handle_message(const char* buffer, ssize_t size) region.address = message.address; region.size = message.size; get_remote_memory()->ignore_heap(region); - } else { + } else +#endif XBT_INFO("Ignoring a IGNORE_HEAP message because we don't need to introspect memory."); - } break; } case MessageType::UNIGNORE_HEAP: { + consumed = sizeof(s_mc_message_ignore_memory_t); +#if SIMGRID_HAVE_STATEFUL_MC if (remote_memory_ != nullptr) { s_mc_message_ignore_memory_t message; - xbt_assert(size == sizeof(message), "Broken message"); + xbt_assert(size == static_cast(sizeof(message)), "Broken message"); memcpy(&message, buffer, sizeof(message)); get_remote_memory()->unignore_heap((void*)message.addr, message.size); - } else { + } else +#endif XBT_INFO("Ignoring an UNIGNORE_HEAP message because we don't need to introspect memory."); - } break; } case MessageType::IGNORE_MEMORY: { + consumed = sizeof(s_mc_message_ignore_memory_t); +#if SIMGRID_HAVE_STATEFUL_MC if (remote_memory_ != nullptr) { s_mc_message_ignore_memory_t message; - xbt_assert(size == sizeof(message), "Broken message"); + xbt_assert(size >= static_cast(sizeof(message)), "Broken message"); memcpy(&message, buffer, sizeof(message)); get_remote_memory()->ignore_region(message.addr, message.size); - } else { + } else +#endif XBT_INFO("Ignoring an IGNORE_MEMORY message because we don't need to introspect memory."); - } break; } case MessageType::STACK_REGION: { + consumed = sizeof(s_mc_message_stack_region_t); +#if SIMGRID_HAVE_STATEFUL_MC if (remote_memory_ != nullptr) { s_mc_message_stack_region_t message; - xbt_assert(size == sizeof(message), "Broken message"); + xbt_assert(size >= static_cast(sizeof(message)), "Broken message"); memcpy(&message, buffer, sizeof(message)); get_remote_memory()->stack_areas().push_back(message.stack_region); - } else { + } else +#endif XBT_INFO("Ignoring an STACK_REGION message because we don't need to introspect memory."); - } break; } case MessageType::REGISTER_SYMBOL: { + consumed = sizeof(s_mc_message_register_symbol_t); +#if SIMGRID_HAVE_STATEFUL_MC s_mc_message_register_symbol_t message; - xbt_assert(size == sizeof(message), "Broken message"); + xbt_assert(size >= static_cast(sizeof(message)), "Broken message"); memcpy(&message, buffer, sizeof(message)); xbt_assert(not message.callback, "Support for client-side function proposition is not implemented."); XBT_DEBUG("Received symbol: %s", message.name.data()); LivenessChecker::automaton_register_symbol(*get_remote_memory(), message.name.data(), remote((int*)message.data)); +#else + xbt_die("Please don't use liveness properties when MC is compiled out."); +#endif break; } case MessageType::WAITING: + consumed = sizeof(s_mc_message_t); + if (size > consumed) { + XBT_DEBUG("%d reinject %d bytes after a %s message", getpid(), (int)(size - consumed), + to_c_str(base_message.type)); + channel_.reinject(&buffer[consumed], size - consumed); + } + return false; case MessageType::ASSERTION_FAILED: + // report_assertion_failure() is NORETURN, but it may change when we report more than one error per run, + // so please keep the consumed computation even if clang-static detects it as a dead affectation. + consumed = sizeof(s_mc_message_t); Exploration::get_instance()->report_assertion_failure(); break; default: - xbt_die("Unexpected message from model-checked application"); + xbt_die("Unexpected message from the application"); + } + if (size > consumed) { + XBT_DEBUG("%d reinject %d bytes after a %s message", getpid(), (int)(size - consumed), to_c_str(base_message.type)); + channel_.reinject(&buffer[consumed], size - consumed); } return true; } void CheckerSide::wait_for_requests() { - /* Resume the application */ + XBT_DEBUG("Resume the application"); if (get_channel().send(MessageType::CONTINUE) != 0) throw xbt::errno_error(); clear_memory_cache(); @@ -353,60 +423,85 @@ void CheckerSide::wait_for_requests() void CheckerSide::clear_memory_cache() { +#if SIMGRID_HAVE_STATEFUL_MC if (remote_memory_) remote_memory_->clear_cache(); +#endif } -void CheckerSide::handle_waitpid() +void CheckerSide::handle_dead_child(int status) { - XBT_DEBUG("Check for wait event"); - int status; - pid_t pid; - while ((pid = waitpid(-1, &status, WNOHANG)) != 0) { - if (pid == -1) { - if (errno == ECHILD) { // No more children: - xbt_assert(not this->running(), "Inconsistent state"); - break; - } else { - xbt_die("Could not wait for pid: %s", strerror(errno)); - } - } - - if (pid == get_pid()) { - // From PTRACE_O_TRACEEXIT: + // From PTRACE_O_TRACEEXIT: #ifdef __linux__ - if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_EXIT << 8))) { - unsigned long eventmsg; - xbt_assert(ptrace(PTRACE_GETEVENTMSG, pid, 0, &eventmsg) != -1, "Could not get exit status"); - status = static_cast(eventmsg); - if (WIFSIGNALED(status)) { - this->terminate(); - Exploration::get_instance()->report_crash(status); - } - } + if (status >> 8 == (SIGTRAP | (PTRACE_EVENT_EXIT << 8))) { + unsigned long eventmsg; + xbt_assert(ptrace(PTRACE_GETEVENTMSG, pid_, 0, &eventmsg) != -1, "Could not get exit status"); + status = static_cast(eventmsg); + if (WIFSIGNALED(status)) { + this->terminate(); + Exploration::get_instance()->report_crash(status); + } + } #endif - // We don't care about non-lethal signals, just reinject them: - if (WIFSTOPPED(status)) { - XBT_DEBUG("Stopped with signal %i", (int)WSTOPSIG(status)); - errno = 0; + // We don't care about non-lethal signals, just reinject them: + if (WIFSTOPPED(status)) { + XBT_DEBUG("Stopped with signal %i", (int)WSTOPSIG(status)); + errno = 0; #ifdef __linux__ - ptrace(PTRACE_CONT, pid, 0, WSTOPSIG(status)); + ptrace(PTRACE_CONT, pid_, 0, WSTOPSIG(status)); #elif defined BSD - ptrace(PT_CONTINUE, pid, (caddr_t)1, WSTOPSIG(status)); + ptrace(PT_CONTINUE, pid_, (caddr_t)1, WSTOPSIG(status)); #endif - xbt_assert(errno == 0, "Could not PTRACE_CONT"); - } + xbt_assert(errno == 0, "Could not PTRACE_CONT: %s", strerror(errno)); + } - else if (WIFSIGNALED(status)) { - this->terminate(); - Exploration::get_instance()->report_crash(status); - } else if (WIFEXITED(status)) { - XBT_DEBUG("Child process is over"); - this->terminate(); + else if (WIFSIGNALED(status)) { + this->terminate(); + Exploration::get_instance()->report_crash(status); + } else if (WIFEXITED(status)) { + XBT_DEBUG("Child process is over"); + this->terminate(); + } +} + +void CheckerSide::handle_waitpid() +{ + XBT_DEBUG("%d checks for wait event. %s", getpid(), + child_checker_ == nullptr ? "Wait directly." : "Ask our proxy to wait for its child."); + + if (child_checker_ == nullptr) { // Wait directly + int status; + pid_t pid; + while ((pid = waitpid(-1, &status, WNOHANG)) != 0) { + if (pid == -1) { + if (errno == ECHILD) { // No more children: + xbt_assert(not this->running(), "Inconsistent state"); + break; + } else { + xbt_die("Could not wait for pid: %s", strerror(errno)); + } } + + if (pid == get_pid()) + handle_dead_child(status); } + + } else { // Ask our proxy to wait for us + s_mc_message_int_t request = {}; + request.type = MessageType::WAIT_CHILD; + request.value = pid_; + xbt_assert(child_checker_->get_channel().send(request) == 0, + "Could not ask my child to waitpid its child for me: %s", strerror(errno)); + + s_mc_message_int_t answer; + ssize_t answer_size = child_checker_->get_channel().receive(answer); + xbt_assert(answer_size != -1, "Could not receive message"); + xbt_assert(answer.type == MessageType::WAIT_CHILD_REPLY, + "The received message is not the WAIT_CHILD_REPLY I was expecting but of type %s", + to_c_str(answer.type)); + xbt_assert(answer_size == sizeof answer, "Broken message (size=%zd; expected %zu)", answer_size, sizeof answer); + handle_dead_child(answer.value); } } - } // namespace simgrid::mc