//===-- FDInterposing.cpp ---------------------------------------*- C++ -*-===// // // The LLVM Compiler Infrastructure // // This file is distributed under the University of Illinois Open Source // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// // // This file helps with catching double close calls on unix integer file // descriptors by interposing functions for all file descriptor create and // close operations. A stack backtrace for every create and close function is // maintained, and every create and close operation is logged. When a double // file descriptor close is encountered, it will be logged. // // To enable the interposing in a darwin program, set the DYLD_INSERT_LIBRARIES // environment variable as follows: // For sh: // DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib /path/to/executable // For tcsh: // (setenv DYLD_INSERT_LIBRARIES=/path/to/FDInterposing.dylib ; /path/to/executable) // // Other environment variables that can alter the default actions of this // interposing shared library include: // // "FileDescriptorStackLoggingNoCompact" // // With this environment variable set, all file descriptor create and // delete operations will be permanantly maintained in the event map. // The default action is to compact the create/delete events by removing // any previous file descriptor create events that are matched with a // corresponding file descriptor delete event when the next valid file // descriptor create event is detected. // // "FileDescriptorMinimalLogging" // // By default every file descriptor create and delete operation is logged // (to STDOUT by default, see the "FileDescriptorLogFile"). This can be // suppressed to only show errors and warnings by setting this environment // variable (the value in not important). // // "FileDescriptorLogFile=<path>" // // By default logging goes to STDOUT_FILENO, but this can be changed by // setting FileDescriptorLogFile. The value is a path to a file that // will be opened and used for logging. //===----------------------------------------------------------------------===// #include <assert.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <execinfo.h> #include <libgen.h> #include <mach-o/dyld.h> #include <mach-o/dyld-interposing.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/event.h> #include <sys/mman.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/time.h> #include <tr1/memory> // for std::tr1::shared_ptr #include <unistd.h> #include <string> #include <vector> #include <map> //---------------------------------------------------------------------- /// @def DISALLOW_COPY_AND_ASSIGN(TypeName) /// Macro definition for easily disallowing copy constructor and /// assignment operators in C++ classes. //---------------------------------------------------------------------- #define DISALLOW_COPY_AND_ASSIGN(TypeName) \ TypeName(const TypeName&); \ const TypeName& operator=(const TypeName&) extern "C" { int accept$NOCANCEL (int, struct sockaddr * __restrict, socklen_t * __restrict); int close$NOCANCEL(int); int open$NOCANCEL(const char *, int, ...); int __open_extended(const char *, int, uid_t, gid_t, int, struct kauth_filesec *); } namespace fd_interposing { //---------------------------------------------------------------------- // String class so we can get formatted strings without having to worry // about the memory storage since it will allocate the memory it needs. //---------------------------------------------------------------------- class String { public: String () : m_str (NULL) {} String (const char *format, ...) : m_str (NULL) { va_list args; va_start (args, format); vprintf (format, args); va_end (args); } ~String() { reset(); } void reset (char *s = NULL) { if (m_str) ::free (m_str); m_str = s; } const char * c_str () const { return m_str; } void printf (const char *format, ...) { va_list args; va_start (args, format); vprintf (format, args); va_end (args); } void vprintf (const char *format, va_list args) { reset(); ::vasprintf (&m_str, format, args); } void log (int log_fd) { if (m_str && log_fd >= 0) { const int len = strlen(m_str); if (len > 0) { write (log_fd, m_str, len); const char last_char = m_str[len-1]; if (!(last_char == '\n' || last_char == '\r')) write (log_fd, "\n", 1); } } } protected: char *m_str; private: DISALLOW_COPY_AND_ASSIGN (String); }; //---------------------------------------------------------------------- // Type definitions //---------------------------------------------------------------------- typedef std::vector<void *> Frames; class FDEvent; typedef std::vector<void *> Frames; typedef std::tr1::shared_ptr<FDEvent> FDEventSP; typedef std::tr1::shared_ptr<String> StringSP; //---------------------------------------------------------------------- // FDEvent // // A class that describes a file desciptor event. // // File descriptor events fall into one of two categories: create events // and delete events. //---------------------------------------------------------------------- class FDEvent { public: FDEvent (int fd, int err, const StringSP &string_sp, bool is_create, const Frames& frames) : m_string_sp (string_sp), m_frames (frames.begin(), frames.end()), m_fd (fd), m_err (err), m_is_create (is_create) {} ~FDEvent () {} bool IsCreateEvent() const { return m_is_create; } bool IsDeleteEvent() const { return !m_is_create; } Frames & GetFrames () { return m_frames; } const Frames & GetFrames () const { return m_frames; } int GetFD () const { return m_fd; } int GetError () const { return m_err; } void Dump (int log_fd) const; void SetCreateEvent (FDEventSP &create_event_sp) { m_create_event_sp = create_event_sp; } private: // A shared pointer to a String that describes this event in // detail (all args and return and error values) StringSP m_string_sp; // The frames for the stack backtrace for this event Frames m_frames; // If this is a file descriptor delete event, this might contain // the correspoding file descriptor create event FDEventSP m_create_event_sp; // The file descriptor for this event int m_fd; // The error code (if any) for this event int m_err; // True if this event is a file descriptor create event, false // if it is a file descriptor delete event bool m_is_create; }; //---------------------------------------------------------------------- // Templatized class that will save errno only if the "value" it is // constructed with is equal to INVALID. When the class goes out of // scope, it will restore errno if it was saved. //---------------------------------------------------------------------- template <int INVALID> class Errno { public: // Save errno only if we are supposed to Errno (int value) : m_saved_errno ((value == INVALID) ? errno : 0), m_restore (value == INVALID) { } // Restore errno only if we are supposed to ~Errno() { if (m_restore) errno = m_saved_errno; } // Accessor for the saved value of errno int get_errno() const { return m_saved_errno; } protected: const int m_saved_errno; const bool m_restore; }; typedef Errno<-1> InvalidFDErrno; typedef Errno<-1> NegativeErrorErrno; typedef std::vector<FDEventSP> FDEventArray; typedef std::map<int, FDEventArray> FDEventMap; //---------------------------------------------------------------------- // Globals //---------------------------------------------------------------------- // Global event map that contains all file descriptor events. As file // descriptor create and close events come in, they will get filled // into this map (protected by g_mutex). When a file descriptor close // event is detected, the open event will be removed and placed into // the close event so if something tries to double close a file // descriptor we can show the previous close event and the file // desctiptor event that created it. When a new file descriptor create // event comes in, we will remove the previous one for that file // desctiptor unless the environment variable "FileDescriptorStackLoggingNoCompact" // is set. The file desctiptor history can be accessed using the // get_fd_history() function. static FDEventMap g_fd_event_map; // A mutex to protect access to our data structures in g_fd_event_map // and also our logging messages static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; // Log all file descriptor create and close events by default. Only log // warnings and erros if the "FileDescriptorMinimalLogging" environment // variable is set. static int g_log_all_calls = 1; // We compact the file descriptor events by default. Set the environment // varible "FileDescriptorStackLoggingNoCompact" to keep a full history. static int g_compact = 1; // The current process ID static int g_pid = -1; static bool g_enabled = true; //---------------------------------------------------------------------- // Mutex class that will lock a mutex when it is constructed, and unlock // it when is goes out of scope //---------------------------------------------------------------------- class Locker { public: Locker (pthread_mutex_t *mutex_ptr) : m_mutex_ptr(mutex_ptr) { ::pthread_mutex_lock (m_mutex_ptr); } // This allows clients to test try and acquire the mutex... Locker (pthread_mutex_t *mutex_ptr, bool &lock_acquired) : m_mutex_ptr(NULL) { lock_acquired = ::pthread_mutex_trylock(mutex_ptr) == 0; if (lock_acquired) m_mutex_ptr = mutex_ptr; } ~Locker () { if (m_mutex_ptr) ::pthread_mutex_unlock (m_mutex_ptr); } protected: pthread_mutex_t *m_mutex_ptr; }; static void log (const char *format, ...) __attribute__ ((format (printf, 1, 2))); static void log (int log_fd, const FDEvent *event, const char *format, ...) __attribute__ ((format (printf, 3, 4))); static void backtrace_log (const char *format, ...) __attribute__ ((format (printf, 1, 2))); static void backtrace_error (const char *format, ...) __attribute__ ((format (printf, 1, 2))); static void log_to_fd (int log_fd, const char *format, ...) __attribute__ ((format (printf, 2, 3))); static inline size_t get_backtrace (Frames &frame_buffer, size_t frames_to_remove) { void *frames[2048]; int count = ::backtrace (&frames[0], sizeof(frames)/sizeof(void*)); if (count > frames_to_remove) frame_buffer.assign (&frames[frames_to_remove], &frames[count]); else frame_buffer.assign (&frames[0], &frames[count]); while (frame_buffer.back() < (void *)1024) frame_buffer.pop_back(); return frame_buffer.size(); } static int g_log_fd = STDOUT_FILENO; static int g_initialized = 0; const char * get_process_fullpath (bool force = false) { static char g_process_fullpath[PATH_MAX] = {0}; if (force || g_process_fullpath[0] == '\0') { // If DST is NULL, then return the number of bytes needed. uint32_t len = sizeof(g_process_fullpath); if (_NSGetExecutablePath (g_process_fullpath, &len) != 0) strncpy (g_process_fullpath, "<error>", sizeof(g_process_fullpath)); } return g_process_fullpath; } // Returns the current process ID, or -1 if inserposing not enabled for // this process static int get_interposed_pid() { if (!g_enabled) return -1; const pid_t pid = getpid(); if (g_pid != pid) { if (g_pid == -1) { g_pid = pid; log ("Interposing file descriptor create and delete functions for %s (pid=%i)\n", get_process_fullpath (true), pid); } else { log ("pid=%i: disabling interposing file descriptor create and delete functions for child process %s (pid=%i)\n", g_pid, get_process_fullpath (true), pid); g_enabled = false; return -1; } // Log when our process changes } return g_pid; } static int get_logging_fd () { if (!g_enabled) return -1; if (!g_initialized) { g_initialized = 1; const pid_t pid = get_interposed_pid(); if (g_enabled) { // Keep all stack info around for all fd create and delete calls. // Otherwise we will remove the fd create call when a corresponding // fd delete call is received if (getenv("FileDescriptorStackLoggingNoCompact")) g_compact = 0; if (getenv("FileDescriptorMinimalLogging")) g_log_all_calls = 0; const char *log_path = getenv ("FileDescriptorLogFile"); if (log_path) g_log_fd = ::creat (log_path, 0660); else g_log_fd = STDOUT_FILENO; // Only let this interposing happen on the first time this matches // and stop this from happening so any child processes don't also // log their file descriptors ::unsetenv ("DYLD_INSERT_LIBRARIES"); } else { log ("pid=%i: logging disabled\n", getpid()); } } return g_log_fd; } void log_to_fd (int log_fd, const char *format, va_list args) { if (format && format[0] && log_fd >= 0) { char buffer[PATH_MAX]; const int count = ::vsnprintf (buffer, sizeof(buffer), format, args); if (count > 0) write (log_fd, buffer, count); } } void log_to_fd (int log_fd, const char *format, ...) { if (format && format[0]) { va_list args; va_start (args, format); log_to_fd (log_fd, format, args); va_end (args); } } void log (const char *format, va_list args) { log_to_fd (get_logging_fd (), format, args); } void log (const char *format, ...) { if (format && format[0]) { va_list args; va_start (args, format); log (format, args); va_end (args); } } void log (int log_fd, const FDEvent *event, const char *format, ...) { if (format && format[0]) { va_list args; va_start (args, format); log_to_fd (log_fd, format, args); va_end (args); } if (event) event->Dump(log_fd); } void FDEvent::Dump (int log_fd) const { if (log_fd >= 0) { log_to_fd (log_fd, "%s\n", m_string_sp->c_str()); if (!m_frames.empty()) ::backtrace_symbols_fd (m_frames.data(), m_frames.size(), log_fd); if (m_create_event_sp) { log_to_fd (log_fd, "\nfd=%i was created with this event:\n", m_fd); m_create_event_sp->Dump (log_fd); log_to_fd (log_fd, "\n"); } } } void backtrace_log (const char *format, ...) { const int log_fd = get_logging_fd (); if (log_fd >= 0) { if (format && format[0]) { va_list args; va_start (args, format); log (format, args); va_end (args); } Frames frames; if (get_backtrace(frames, 2)) ::backtrace_symbols_fd (frames.data(), frames.size(), log_fd); } } void backtrace_error (const char *format, ...) { const int pid = get_interposed_pid(); if (pid >= 0) { const int log_fd = get_logging_fd (); if (log_fd >= 0) { log ("\nerror: %s (pid=%i): ", get_process_fullpath (), pid); if (format && format[0]) { va_list args; va_start (args, format); log (format, args); va_end (args); } Frames frames; if (get_backtrace(frames, 2)) ::backtrace_symbols_fd (frames.data(), frames.size(), log_fd); } } } void save_backtrace (int fd, int err, const StringSP &string_sp, bool is_create) { Frames frames; get_backtrace(frames, 2); FDEventSP fd_event_sp (new FDEvent (fd, err, string_sp, is_create, frames)); FDEventMap::iterator pos = g_fd_event_map.find (fd); if (pos != g_fd_event_map.end()) { // We have history for this fd... FDEventArray &event_array = g_fd_event_map[fd]; if (fd_event_sp->IsCreateEvent()) { // The current fd event is a function that creates // a descriptor, check in case last event was // a create event. if (event_array.back()->IsCreateEvent()) { const int log_fd = get_logging_fd(); // Two fd create functions in a row, we missed // a function that closes a fd... log (log_fd, fd_event_sp.get(), "\nwarning: unmatched file descriptor create event fd=%i (we missed a file descriptor close event):\n", fd); } else if (g_compact) { // We are compacting so we remove previous create event // when we get the correspinding delete event event_array.pop_back(); } } else { // The current fd event is a function that deletes // a descriptor, check in case last event for this // fd was a delete event (double close!) if (event_array.back()->IsDeleteEvent()) { const int log_fd = get_logging_fd(); // Two fd delete functions in a row, we must // have missed some function that opened a descriptor log (log_fd, fd_event_sp.get(), "\nwarning: unmatched file descriptor close event for fd=%d (we missed the file descriptor create event):\n", fd); } else if (g_compact) { // Since this is a close event, we want to remember the open event // that this close if for... fd_event_sp->SetCreateEvent(event_array.back()); // We are compacting so we remove previous create event // when we get the correspinding delete event event_array.pop_back(); } } event_array.push_back(fd_event_sp); } else { g_fd_event_map[fd].push_back(fd_event_sp); } } //---------------------------------------------------------------------- // socket() interpose function //---------------------------------------------------------------------- extern "C" int socket$__interposed__ (int domain, int type, int protocol) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::socket (domain, type, protocol); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String); if (fd == -1) description_sp->printf("pid=%i: socket (domain = %i, type = %i, protocol = %i) => fd=%i errno = %i", pid, domain, type, protocol, fd, fd_errno.get_errno()); else description_sp->printf("pid=%i: socket (domain = %i, type = %i, protocol = %i) => fd=%i", pid, domain, type, protocol, fd); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::socket (domain, type, protocol); } } //---------------------------------------------------------------------- // socketpair() interpose function //---------------------------------------------------------------------- extern "C" int socketpair$__interposed__ (int domain, int type, int protocol, int fds[2]) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); fds[0] = -1; fds[1] = -1; const int err = socketpair (domain, type, protocol, fds); NegativeErrorErrno err_errno(err); StringSP description_sp(new String ("pid=%i: socketpair (domain=%i, type=%i, protocol=%i, {fd=%i, fd=%i}) -> err=%i", pid, domain, type, protocol, fds[0], fds[1], err)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fds[0] >= 0) save_backtrace (fds[0], err_errno.get_errno(), description_sp, true); if (fds[1] >= 0) save_backtrace (fds[1], err_errno.get_errno(), description_sp, true); return err; } else { return socketpair (domain, type, protocol, fds); } } //---------------------------------------------------------------------- // open() interpose function //---------------------------------------------------------------------- extern "C" int open$__interposed__ (const char *path, int oflag, int mode) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); int fd = -2; StringSP description_sp(new String); if (oflag & O_CREAT) { fd = ::open (path, oflag, mode); description_sp->printf("pid=%i: open (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid, path, oflag, mode, fd); } else { fd = ::open (path, oflag); description_sp->printf("pid=%i: open (path = '%s', oflag = %i) -> fd=%i", pid, path, oflag, fd); } InvalidFDErrno fd_errno(fd); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::open (path, oflag, mode); } } //---------------------------------------------------------------------- // open$NOCANCEL() interpose function //---------------------------------------------------------------------- extern "C" int open$NOCANCEL$__interposed__ (const char *path, int oflag, int mode) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::open$NOCANCEL (path, oflag, mode); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: open$NOCANCEL (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid, path, oflag, mode, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::open$NOCANCEL (path, oflag, mode); } } //---------------------------------------------------------------------- // __open_extended() interpose function //---------------------------------------------------------------------- extern "C" int __open_extended$__interposed__ (const char *path, int oflag, uid_t uid, gid_t gid, int mode, struct kauth_filesec *fsacl) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::__open_extended (path, oflag, uid, gid, mode, fsacl); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: __open_extended (path='%s', oflag=%i, uid=%i, gid=%i, mode=%i, fsacl=%p) -> fd=%i", pid, path, oflag, uid, gid, mode, fsacl, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::__open_extended (path, oflag, uid, gid, mode, fsacl); } } //---------------------------------------------------------------------- // kqueue() interpose function //---------------------------------------------------------------------- extern "C" int kqueue$__interposed__ (void) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::kqueue (); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: kqueue () -> fd=%i", pid, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::kqueue (); } } //---------------------------------------------------------------------- // shm_open() interpose function //---------------------------------------------------------------------- extern "C" int shm_open$__interposed__ (const char *path, int oflag, int mode) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::shm_open (path, oflag, mode); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: shm_open (path = '%s', oflag = %i, mode = %i) -> fd=%i", pid, path, oflag, mode, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::shm_open (path, oflag, mode); } } //---------------------------------------------------------------------- // accept() interpose function //---------------------------------------------------------------------- extern "C" int accept$__interposed__ (int socket, struct sockaddr *address, socklen_t *address_len) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::accept (socket, address, address_len); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: accept (socket=%i, ...) -> fd=%i", pid, socket, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::accept (socket, address, address_len); } } //---------------------------------------------------------------------- // accept$NOCANCEL() interpose function //---------------------------------------------------------------------- extern "C" int accept$NOCANCEL$__interposed__ (int socket, struct sockaddr *address, socklen_t *address_len) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::accept$NOCANCEL (socket, address, address_len); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: accept$NOCANCEL (socket=%i, ...) -> fd=%i", pid, socket, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::accept$NOCANCEL (socket, address, address_len); } } //---------------------------------------------------------------------- // dup() interpose function //---------------------------------------------------------------------- extern "C" int dup$__interposed__ (int fd2) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int fd = ::dup (fd2); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: dup (fd2=%i) -> fd=%i", pid, fd2, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::dup (fd2); } } //---------------------------------------------------------------------- // dup2() interpose function //---------------------------------------------------------------------- extern "C" int dup2$__interposed__ (int fd1, int fd2) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); // If "fd2" is already opened, it will be closed during the // dup2 call below, so we need to see if we have fd2 in our // open map and treat it as a close(fd2) FDEventMap::iterator pos = g_fd_event_map.find (fd2); StringSP dup2_close_description_sp(new String ("pid=%i: dup2 (fd1=%i, fd2=%i) -> will close (fd=%i)", pid, fd1, fd2, fd2)); if (pos != g_fd_event_map.end() && pos->second.back()->IsCreateEvent()) save_backtrace (fd2, 0, dup2_close_description_sp, false); const int fd = ::dup2(fd1, fd2); InvalidFDErrno fd_errno(fd); StringSP description_sp(new String ("pid=%i: dup2 (fd1=%i, fd2=%i) -> fd=%i", pid, fd1, fd2, fd)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fd >= 0) save_backtrace (fd, fd_errno.get_errno(), description_sp, true); return fd; } else { return ::dup2(fd1, fd2); } } //---------------------------------------------------------------------- // close() interpose function //---------------------------------------------------------------------- extern "C" int close$__interposed__ (int fd) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int err = close(fd); NegativeErrorErrno err_errno(err); StringSP description_sp (new String); if (err == -1) description_sp->printf("pid=%i: close (fd=%i) => %i errno = %i (%s))", pid, fd, err, err_errno.get_errno(), strerror(err_errno.get_errno())); else description_sp->printf("pid=%i: close (fd=%i) => %i", pid, fd, err); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (err == 0) { if (fd >= 0) save_backtrace (fd, err, description_sp, false); } else if (err == -1) { if (err_errno.get_errno() == EBADF && fd != -1) { backtrace_error ("close (fd=%d) resulted in EBADF:\n", fd); FDEventMap::iterator pos = g_fd_event_map.find (fd); if (pos != g_fd_event_map.end()) { log (get_logging_fd(), pos->second.back().get(), "\nfd=%d was previously %s with this event:\n", fd, pos->second.back()->IsCreateEvent() ? "opened" : "closed"); } } } return err; } else { return close (fd); } } //---------------------------------------------------------------------- // close$NOCANCEL() interpose function //---------------------------------------------------------------------- extern "C" int close$NOCANCEL$__interposed__ (int fd) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); const int err = close$NOCANCEL(fd); NegativeErrorErrno err_errno(err); StringSP description_sp (new String); if (err == -1) description_sp->printf("pid=%i: close$NOCANCEL (fd=%i) => %i errno = %i (%s))", pid, fd, err, err_errno.get_errno(), strerror(err_errno.get_errno())); else description_sp->printf("pid=%i: close$NOCANCEL (fd=%i) => %i", pid, fd, err); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (err == 0) { if (fd >= 0) save_backtrace (fd, err, description_sp, false); } else if (err == -1) { if (err_errno.get_errno() == EBADF && fd != -1) { backtrace_error ("close$NOCANCEL (fd=%d) resulted in EBADF\n:", fd); FDEventMap::iterator pos = g_fd_event_map.find (fd); if (pos != g_fd_event_map.end()) { log (get_logging_fd(), pos->second.back().get(), "\nfd=%d was previously %s with this event:\n", fd, pos->second.back()->IsCreateEvent() ? "opened" : "closed"); } } } return err; } else { return close$NOCANCEL(fd); } } //---------------------------------------------------------------------- // pipe() interpose function //---------------------------------------------------------------------- extern "C" int pipe$__interposed__ (int fds[2]) { const int pid = get_interposed_pid(); if (pid >= 0) { Locker locker (&g_mutex); fds[0] = -1; fds[1] = -1; const int err = pipe (fds); const int saved_errno = errno; StringSP description_sp(new String ("pid=%i: pipe ({fd=%i, fd=%i}) -> err=%i", pid, fds[0], fds[1], err)); if (g_log_all_calls) description_sp->log (get_logging_fd()); if (fds[0] >= 0) save_backtrace (fds[0], saved_errno, description_sp, true); if (fds[1] >= 0) save_backtrace (fds[1], saved_errno, description_sp, true); errno = saved_errno; return err; } else { return pipe (fds); } } //---------------------------------------------------------------------- // get_fd_history() // // This function allows runtime access to the file descriptor history. // // @param[in] log_fd // The file descriptor to log to // // @param[in] fd // The file descriptor whose history should be dumped //---------------------------------------------------------------------- extern "C" void get_fd_history (int log_fd, int fd) { // "create" below needs to be outside of the mutex locker scope if (log_fd >= 0) { bool got_lock = false; Locker locker (&g_mutex, got_lock); if (got_lock) { FDEventMap::iterator pos = g_fd_event_map.find (fd); log_to_fd (log_fd, "Dumping file descriptor history for fd=%i:\n", fd); if (pos != g_fd_event_map.end()) { FDEventArray &event_array = g_fd_event_map[fd]; const size_t num_events = event_array.size(); for (size_t i=0; i<num_events; ++i) event_array[i]->Dump (log_fd); } else { log_to_fd (log_fd, "error: no file descriptor events found for fd=%i\n", fd); } } else { log_to_fd (log_fd, "error: fd event mutex is locked...\n"); } } } //---------------------------------------------------------------------- // Interposing //---------------------------------------------------------------------- // FD creation routines DYLD_INTERPOSE(accept$__interposed__, accept); DYLD_INTERPOSE(accept$NOCANCEL$__interposed__, accept$NOCANCEL); DYLD_INTERPOSE(dup$__interposed__, dup); DYLD_INTERPOSE(dup2$__interposed__, dup2); DYLD_INTERPOSE(kqueue$__interposed__, kqueue); DYLD_INTERPOSE(open$__interposed__, open); DYLD_INTERPOSE(open$NOCANCEL$__interposed__, open$NOCANCEL); DYLD_INTERPOSE(__open_extended$__interposed__, __open_extended); DYLD_INTERPOSE(pipe$__interposed__, pipe); DYLD_INTERPOSE(shm_open$__interposed__, shm_open); DYLD_INTERPOSE(socket$__interposed__, socket); DYLD_INTERPOSE(socketpair$__interposed__, socketpair); // FD deleting routines DYLD_INTERPOSE(close$__interposed__, close); DYLD_INTERPOSE(close$NOCANCEL$__interposed__, close$NOCANCEL); } // namespace fd_interposing