pax_global_header00006660000000000000000000000064146464513750014531gustar00rootroot0000000000000052 comment=b139e3a96829f04e15874a42ef11cdf7526931d4 pldebugger-1.8/000077500000000000000000000000001464645137500135215ustar00rootroot00000000000000pldebugger-1.8/.github/000077500000000000000000000000001464645137500150615ustar00rootroot00000000000000pldebugger-1.8/.github/README.md000077700000000000000000000000001464645137500221432../README-pldebugger.mdustar00rootroot00000000000000pldebugger-1.8/.gitignore000066400000000000000000000000161464645137500155060ustar00rootroot00000000000000*.o *.so *.dllpldebugger-1.8/Makefile000066400000000000000000000054031464645137500151630ustar00rootroot00000000000000################################################################################ ## ## This Makefile builds plugin_debugger.so. It consists of a PL/pgSQL ## interpreter plugin, and a set of functions that form an SQL interface ## to the PL/pgSQL debugger. ## ## Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. ## ## Licensed under the Artistic License v2.0, see ## https://opensource.org/licenses/artistic-license-2.0 ## for full details EXTENSION = pldbgapi MODULE_big = plugin_debugger OBJS = plpgsql_debugger.o plugin_debugger.o dbgcomm.o pldbgapi.o ifdef INCLUDE_PACKAGE_SUPPORT OBJS += spl_debugger.o endif DATA = pldbgapi--1.1.sql pldbgapi--unpackaged--1.1.sql pldbgapi--1.0--1.1.sql DOCS = README-pldebugger.md # PGXS build needs PostgreSQL 9.2 or later. Earlier versions didn't install # plpgsql.h, so you needed the full source tree to access it. ifdef USE_PGXS PG_CONFIG = pg_config PGXS := $(shell $(PG_CONFIG) --pgxs) include $(PGXS) else subdir = contrib/pldebugger top_builddir = ../.. include $(top_builddir)/src/Makefile.global include $(top_srcdir)/contrib/contrib-global.mk endif # plpgsql_debugger.c needs plpgsql.h. Beginning with server version 9.2, # it is installed into include/server, but when building without pgxs, # with the pldebugger directory being directly in the server source tree's # contrib directory, we need to tell the compiler where to find it. plpgsql_debugger.o: CFLAGS += -I$(top_builddir)/src/pl/plpgsql/src plpgsql_debugger.bc: CPPFLAGS += -I$(top_srcdir)/src/pl/plpgsql/src -I$(top_builddir)/src/pl/plpgsql/src ################################################################################ ## If we're building against EnterpriseDB's Advanced Server, also build a ## debugger module for the SPL language. It's pretty much the same as the ## PL/pgSQL one, but the structs have some extra fields and are thus not ## binary-compatible. We make a copy of the .c file, and pass the ## INCLUDE_PACKAGE_SUPPORT=1 flag to compile it against SPL instead of PL/pgSQL. ## ## To make debugging the debugger itself simpler, all the functions are ## mechanically renamed from plpgsql_* to spl_*. ## ## To enable this, you need to run make as "make INCLUDE_PACKAGE_SUPPORT=1" ## ifdef INCLUDE_PACKAGE_SUPPORT spl_debugger.c: plpgsql_debugger.c sed -e 's/plpgsql_/spl_/g' $(module_srcdir)plpgsql_debugger.c > spl_debugger.c spl_debugger.o: CFLAGS += -DINCLUDE_PACKAGE_SUPPORT=1 -I$(top_builddir)/src/pl/edb-spl/src spl_debugger.bc: CPPFLAGS += -I$(top_srcdir)/src/pl/plpgsql/src -I$(top_builddir)/src/pl/plpgsql/src # There's some tiny differences in plugin_debugger.c, if we're including SPL # language. Pass the INCLUDE_PACKAGE_SUPPORT flag to plugin_debugger.c too. plugin_debugger.o: CFLAGS += -DINCLUDE_PACKAGE_SUPPORT=1 endif pldebugger-1.8/README-pldebugger.md000066400000000000000000000066361464645137500171310ustar00rootroot00000000000000# PostgreSQL pl/pgsql Debugger API This module is a set of shared libraries which implement an API for debugging pl/pgsql functions on PostgreSQL 8.4 and above. The [pgAdmin project](http://www.pgadmin.org/) provides a client user interface. ## Installation - Copy this directory to contrib/ in your PostgreSQL source tree. - Run `make && make install` - Edit your postgresql.conf file, and modify the shared_preload_libraries config option to look like: `shared_preload_libraries = '$libdir/plugin_debugger'` - Restart PostgreSQL for the new setting to take effect. - Run the following command in the database or databases that you wish to debug functions in: `CREATE EXTENSION pldbgapi;` (on server versions older than 9.1, you must instead run the pldbgapi--1.1.sql script directly using psql). ## Usage Connect pgAdmin to the database containing the functions you wish to debug. Right-click the function to debug, and select Debugging->Debug to execute and debug the function immediately, or select Debugging->Set Global Breakpoint to set a breakpoint on the function. This will cause the debugger to wait for another session (such as a backend servicing a web app) to execute the function and allow you to debug in-context. For further information, please see the pgAdmin documentation. ## Troubleshooting The majority of problems we've encountered with the plugin are caused by failing to add (or incorrectly adding) the debugger plugin library to the shared_preload_libraries configuration directive in postgresql.conf (following which, the server *must* be restarted). This will prevent global breakpoints working on all platforms, and on some (notably Windows) may prevent the pldbgapi.sql script from executing correctly. ## Architecture The debugger consists of three parts: 1. The client. This is typically a GUI displays the source code, current stack frame, variables etc, and allows the user to set breakpoints and step throught the code. The client can reside on a different host than the database server. 2. The target backend. This is the backend that runs the code being debugged. The plugin_debugger.so library must be loaded into the target backend. 3. Debugging proxy. This is another backend process that the client is connected to. The API functions, pldbg_* in pldbgapi.so library, are run in this backend. The client is to connected to the debugging proxy using a regular libpq connection. When a debugging session is active, the proxy is connected to the target via a socket. The protocol between the proxy and the target backend is not visible to others, and is subject to change. The pldbg_* API functions form the public interface to the debugging facility. ``` debugger client *------ libpq --------* Proxy backend (pgAdmin) * | pldebugger socket connection | * application client *----- libpq -------* Target backend ``` ## Licence The pl/pgsql debugger API is released under the [Artistic Licence v2.0](https://opensource.org/licenses/artistic-license-2.0). Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. ## Contact For support, please email the pgAdmin support mailing list. See [http://www.pgadmin.org/support/](http://www.pgadmin.org/support/) for more details. pldebugger-1.8/dbgcomm.c000066400000000000000000000451761464645137500153120ustar00rootroot00000000000000/********************************************************************** * dbgcomm.c * * This file contains some helper functions for the proxy - target * communication. * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. * * Licensed under the Artistic License v2.0, see * https://opensource.org/licenses/artistic-license-2.0 * for full details * **********************************************************************/ #include "postgres.h" #include #include #ifdef HAVE_SYS_SELECT_H #include #endif #include #include #include #include #include #include "miscadmin.h" #if (PG_VERSION_NUM < 170000) #include "storage/backendid.h" #endif #include "storage/lwlock.h" #include "storage/pmsignal.h" #include "storage/shmem.h" #include "storage/sinvaladt.h" #include "dbgcomm.h" #include "pldebugger.h" #if (PG_VERSION_NUM < 90200) /* before 9.2, PostmasterIsAlive() had one parameter */ #define PostmasterIsAlive() PostmasterIsAlive(false) #endif /* * Shared memory structure. This is used for authenticating debugger * connections. Each backend has a dedicated slot. * * Whenever a target backend is waiting for a proxy to connect to it * (pldbg_oid_debug()), or trying to connect to a proxy (when it hits a * global breakpoint), it advertises the connection attempt in shared memory. * Each backend has a slot of its own. * * When the proxy initiates the connection and target backend listens * (pldbg_oid_debug()), the backend first sets its status to * LISTENING_FOR_PROXY, and the port it's listening on in 'port'. When the * proxy wants to connect to it, it changes the status to PROXY_CONNECTING * and sets the port to the port it's connecting from. When the target backend * accept()s the connection, it checks that the remote port of the connection * matches the one in the slot. This makes the communication secure, because * only a legitimate proxy backend can access shared memory. * * Target backend connecting to a proxy (when a global breakpoint is hit) works * similarly, except that the LISTENING step is not needed. The backend sets * the port it's connecting from in its slot's port field, and connects. * The proxy accept()s the connection, and scans all the slots for a match * on the port number the connection came from. If it finds the port number * in one of the slots, the connection came from a legitimate target backend. */ #define DBGCOMM_IDLE 0 #define DBGCOMM_LISTENING_FOR_PROXY 1 /* target is listening for a proxy */ #define DBGCOMM_PROXY_CONNECTING 2 /* proxy is connecting to our port */ #define DBGCOMM_CONNECTING_TO_PROXY 3 /* target is connecting to a proxy */ typedef struct { BackendId backendid; int status; int pid; int port; } dbgcomm_target_slot_t; static dbgcomm_target_slot_t *dbgcomm_slots = NULL; /* * Each in-progress connection attempt between proxy and target require * a slot. 50 should be plenty. */ #define NumTargetSlots 50 /********************************************************************** * Prototypes for static functions **********************************************************************/ static void dbgcomm_init(void); static uint32 resolveHostName(const char *hostName); static int findFreeTargetSlot(void); static int findTargetSlot(BackendId backendid); /********************************************************************** * Initialization routines **********************************************************************/ /* * Reserves the right amount of shared memory, when the library is * preloaded by shared_preload_libraries. */ void dbgcomm_reserve(void) { RequestAddinShmemSpace(sizeof(dbgcomm_target_slot_t) * NumTargetSlots); } /* * Initialize slots in shared memory. */ static void dbgcomm_init(void) { bool found; if (dbgcomm_slots) return; LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); dbgcomm_slots = ShmemInitStruct("Debugger Connection slots", sizeof(dbgcomm_target_slot_t) * NumTargetSlots, &found); if (dbgcomm_slots == NULL) elog(ERROR, "out of shared memory"); if (!found) { int i; for (i = 0; i < NumTargetSlots; i++) { dbgcomm_slots[i].backendid = InvalidBackendId; dbgcomm_slots[i].status = DBGCOMM_IDLE; } } LWLockRelease(getPLDebuggerLock()); } /********************************************************************** * Routines called by debugging target * * These routines use ereport(COMMERROR, ...) for errors, so that they * are logged but not sent to the client, and don't abort the * transaction. * **********************************************************************/ /* * dbcomm_connect_to_proxy * * This does socket() + connect(), to connect to a listener. The connection * is authenticated. Returns the file descriptor of the open socket. * * We assume that the proxyPort came from a breakpoint or some other reliable * source, so that we don't allow connecting to any random port in the system. */ int dbgcomm_connect_to_proxy(int proxyPort) { int sockfd; struct sockaddr_in remoteaddr = {0}; struct sockaddr_in localaddr = {0}; socklen_t addrlen = sizeof( remoteaddr ); int reuse_addr_flag = 1; int slot; dbgcomm_init(); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not create socket for connecting to proxy: %m"))); return -1; } /* Sockets seem to be non-blocking by default on Windows.. */ if (!pg_set_block(sockfd)) { closesocket(sockfd); ereport(COMMERROR, (errmsg("could not set socket to blocking mode: %m"))); return -1; } /* * We have to bind the socket before connecting, so that we know the * local port number it will use. We have to store it in shared memory * before connecting, so that the target knows the connection is legit. */ localaddr.sin_family = AF_INET; localaddr.sin_port = htons( 0 ); localaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuse_addr_flag, sizeof(reuse_addr_flag)); if (bind(sockfd, (struct sockaddr *) &localaddr, sizeof(localaddr)) < 0) { ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not bind local port: %m"))); return -1; } /* Get the port number selected by the TCP/IP stack */ getsockname(sockfd, (struct sockaddr *) &localaddr, &addrlen); LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); slot = findFreeTargetSlot(); if (slot < 0) { closesocket(sockfd); LWLockRelease(getPLDebuggerLock()); ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not find a free target slot"))); return -1; } dbgcomm_slots[slot].port = ntohs(localaddr.sin_port); dbgcomm_slots[slot].status = DBGCOMM_CONNECTING_TO_PROXY; dbgcomm_slots[slot].backendid = MyBackendId; dbgcomm_slots[slot].pid = MyProcPid; LWLockRelease(getPLDebuggerLock()); remoteaddr.sin_family = AF_INET; remoteaddr.sin_port = htons(proxyPort); remoteaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); /* Now connect to the other end. */ if (connect(sockfd, (struct sockaddr *) &remoteaddr, sizeof(remoteaddr)) < 0) { ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not connect to debugging proxy at port %d: %m", proxyPort))); /* * Reset our entry in the array. On success, this will be done by * the proxy. */ LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); dbgcomm_slots[slot].status = DBGCOMM_IDLE; dbgcomm_slots[slot].backendid = InvalidBackendId; dbgcomm_slots[slot].port = 0; LWLockRelease(getPLDebuggerLock()); return -1; } return sockfd; } /* * dbcomm_connect_to_proxy * * This does listen() + accept(), to wait for a proxy to connect to us. */ int dbgcomm_listen_for_proxy(void) { struct sockaddr_in remoteaddr = {0}; struct sockaddr_in localaddr = {0}; socklen_t addrlen = sizeof( remoteaddr ); int sockfd; int serverSocket; int localport; bool done; int slot; dbgcomm_init(); sockfd = socket( AF_INET, SOCK_STREAM, 0 ); if (sockfd < 0) { ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not create socket for connecting to proxy: %m"))); return -1; } /* Sockets seem to be non-blocking by default on Windows.. */ if (!pg_set_block(sockfd)) { closesocket(sockfd); ereport(COMMERROR, (errmsg("could not set socket to blocking mode: %m"))); return -1; } /* Bind the listener socket to any available port */ localaddr.sin_family = AF_INET; localaddr.sin_port = htons( 0 ); localaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); if (bind( sockfd, (struct sockaddr *) &localaddr, sizeof(localaddr)) < 0) { ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not bind socket for listening for proxy: %m"))); return -1; } /* Get the port number selected by the TCP/IP stack */ getsockname(sockfd, (struct sockaddr *) &localaddr, &addrlen); localport = ntohs(localaddr.sin_port); /* Get ready to wait for a client. */ if (listen(sockfd, 2) < 0) { ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not listen() for proxy: %m"))); return -1; } LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); slot = findFreeTargetSlot(); if (slot < 0) { closesocket(sockfd); LWLockRelease(getPLDebuggerLock()); ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not find a free target slot"))); return -1; } dbgcomm_slots[slot].port = localport; dbgcomm_slots[slot].status = DBGCOMM_LISTENING_FOR_PROXY; dbgcomm_slots[slot].backendid = MyBackendId; dbgcomm_slots[slot].pid = MyProcPid; LWLockRelease(getPLDebuggerLock()); /* Notify the client application that this backend is waiting for a proxy. */ elog(NOTICE, "PLDBGBREAK:%d", MyBackendId); /* wait for the other end to connect to us */ done = false; while (!done) { serverSocket = accept(sockfd, (struct sockaddr *) &remoteaddr, &addrlen); if (serverSocket < 0) ereport(ERROR, (errmsg("could not accept connection from debugging proxy"))); /* * Authenticate the connection. We do this by checking that the remote * end's port number matches what's posted in the shared memory slot. */ LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); if (dbgcomm_slots[slot].status == DBGCOMM_PROXY_CONNECTING && dbgcomm_slots[slot].port == ntohs(remoteaddr.sin_port)) { dbgcomm_slots[slot].backendid = InvalidBackendId; dbgcomm_slots[slot].status = DBGCOMM_IDLE; done = true; } else closesocket(serverSocket); LWLockRelease(getPLDebuggerLock()); } closesocket(sockfd); return serverSocket; } /********************************************************************** * Routines called by debugging proxy **********************************************************************/ /* * dbgcomm_connect_to_target * * Connect to given target backend that's waiting for us. Returns a socket * that is open for communication. Uses ereport(ERROR) on error. */ int dbgcomm_connect_to_target(BackendId targetBackend) { int sockfd; struct sockaddr_in remoteaddr = {0}; struct sockaddr_in localaddr = {0}; socklen_t addrlen = sizeof( remoteaddr ); int reuse_addr_flag = 1; int localport; int remoteport; int slot; dbgcomm_init(); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) ereport(ERROR, (errcode_for_socket_access(), errmsg("could not create socket for connecting to target: %m"))); /* Sockets seem to be non-blocking by default on Windows.. */ if (!pg_set_block(sockfd)) { int save_errno = errno; closesocket(sockfd); errno = save_errno; ereport(ERROR, (errmsg("could not set socket to blocking mode: %m"))); } /* * We have to bind the socket before connecting, so that we know the * local port number it will use. We have to store it in shared memory * before connecting, so that the target knows the connection is legit. */ localaddr.sin_family = AF_INET; localaddr.sin_port = htons( 0 ); localaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuse_addr_flag, sizeof(reuse_addr_flag)); if (bind(sockfd, (struct sockaddr *) &localaddr, sizeof(localaddr)) < 0) elog(ERROR, "pl_debugger: could not bind local port: %m"); /* Get the port number selected by the TCP/IP stack */ getsockname(sockfd, (struct sockaddr *) &localaddr, &addrlen); localport = ntohs(localaddr.sin_port); /* * Find the target backend's slot. Check which port it's listening on, and * let it know we're connecting to it from this port. */ LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); slot = findTargetSlot(targetBackend); if (slot < 0 || dbgcomm_slots[slot].status != DBGCOMM_LISTENING_FOR_PROXY) { closesocket(sockfd); ereport(ERROR, (errmsg("target backend is not listening for a connection"))); } remoteport = dbgcomm_slots[slot].port; dbgcomm_slots[slot].port = localport; dbgcomm_slots[slot].status = DBGCOMM_PROXY_CONNECTING; LWLockRelease(getPLDebuggerLock()); /* Now connect to the other end. */ remoteaddr.sin_family = AF_INET; remoteaddr.sin_port = htons(remoteport); remoteaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); if (connect(sockfd, (struct sockaddr *) &remoteaddr, sizeof(remoteaddr)) < 0) { ereport(ERROR, (errmsg("could not connect to target backend: %m"))); /* XXX: should we do something about the slot on error? */ } return sockfd; } /* * dbgcomm_accept_target * * Waits for one connection from a target backend to the given socket. Returns * a socket that is open for communication. Uses ereport(ERROR) on error. */ int dbgcomm_accept_target(int sockfd, int *targetPid) { int serverSocket; int i; struct sockaddr_in remoteaddr = {0}; socklen_t addrlen = sizeof(remoteaddr); dbgcomm_init(); /* wait for the target to connect to us */ for (;;) { fd_set rmask; int rc; struct timeval timeout; /* Check for query cancel or termination request */ CHECK_FOR_INTERRUPTS(); if (!PostmasterIsAlive()) { /* Emergency bailout if postmaster has died. */ ereport(FATAL, (errmsg("canceling debugging session because postmaster died"))); } FD_ZERO(&rmask); FD_SET(sockfd, &rmask); /* * Wake up every 1 second to check if we've been killed or * postmaster has died. */ timeout.tv_sec = 1; timeout.tv_usec = 0; rc = select(sockfd + 1, &rmask, NULL, NULL, &timeout); if (rc < 0) { if (errno == EINTR) continue; /* anything else is an error */ ereport(ERROR, (errmsg("select() failed while waiting for target: %m"))); } if (rc == 0) { /* Timeout expired. */ continue; } if (!FD_ISSET(sockfd, &rmask)) continue; serverSocket = accept(sockfd, (struct sockaddr *) &remoteaddr, &addrlen); if (serverSocket < 0) ereport(ERROR, (errmsg("could not accept connection from debugging target: %m"))); /* * Authenticate the connection. We do this by checking that the remote * end's port number is listed in a slot in shared memory. */ LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); for (i = 0; i < NumTargetSlots; i++) { if (dbgcomm_slots[i].status == DBGCOMM_CONNECTING_TO_PROXY && dbgcomm_slots[i].port == ntohs(remoteaddr.sin_port)) { *targetPid = dbgcomm_slots[i].pid; dbgcomm_slots[i].status = DBGCOMM_IDLE; break; } } LWLockRelease(getPLDebuggerLock()); if (i >= NumTargetSlots) { /* * This connection did not come from a backend. Reject and continue * listening. */ closesocket(serverSocket); continue; } else { /* This looks like a legitimate connection. */ break; } } return serverSocket; } int dbgcomm_listen_for_target(int *port) { int sockfd; struct sockaddr_in proxy_addr = {0}; socklen_t proxy_addr_len = sizeof( proxy_addr ); int reuse_addr_flag = 1; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) ereport(ERROR, (errcode_for_socket_access(), errmsg("could not create socket: %m"))); /* Sockets seem to be non-blocking by default on Windows.. */ if (!pg_set_block(sockfd)) { int save_errno = errno; closesocket(sockfd); errno = save_errno; ereport(ERROR, (errmsg("could not set socket to blocking mode: %m"))); } /* Ask the TCP/IP stack for an unused port */ proxy_addr.sin_family = AF_INET; proxy_addr.sin_port = htons(0); proxy_addr.sin_addr.s_addr = resolveHostName("127.0.0.1"); setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *) &reuse_addr_flag, sizeof( reuse_addr_flag )); /* Bind a listener socket to that port */ if (bind( sockfd, (struct sockaddr *) &proxy_addr, sizeof( proxy_addr )) < 0 ) { int save_errno = errno; closesocket(sockfd); errno = save_errno; ereport(ERROR, (errmsg("could not create listener for debugger connection"))); } /* Get the port number selected by the TCP/IP stack */ getsockname( sockfd, (struct sockaddr *)&proxy_addr, &proxy_addr_len ); *port = (int) ntohs( proxy_addr.sin_port ); /* Get ready to wait for a client */ listen(sockfd, 2); elog(DEBUG1, "listening for debugging target at port %d", *port); return sockfd; } /* * Find first available target slot. * * Note: Caller must be holding the lock. */ static int findFreeTargetSlot(void) { int i; for (i = 0; i < NumTargetSlots; i++) { if (dbgcomm_slots[i].backendid == InvalidBackendId) return i; if (dbgcomm_slots[i].backendid == MyBackendId) { /* * If we've failed to deallocate our slot earlier, reuse this slot. * This shouldn't happen. */ elog(LOG, "found leftover debugging target slot for backend %d", MyBackendId); return i; } } return -1; } /* * Find target slot belonging to given backend. * * Note: Caller must be holding the lock. */ static int findTargetSlot(BackendId backendid) { int i; for (i = 0; i < NumTargetSlots; i++) { if (dbgcomm_slots[i].backendid == backendid) return i; } return -1; } /******************************************************************************* * resolveHostName() * * Given the name of a host (hostName), this function returns the IP address * of that host (or 0 if the name does not resolve to an address). * * FIXME: this function should probably be a bit more flexibile. */ #ifndef INADDR_NONE #define INADDR_NONE ((unsigned long int) -1) /* For Solaris */ #endif static uint32 resolveHostName( const char * hostName ) { struct hostent * hostDesc; uint32 hostAddress; if(( hostDesc = gethostbyname( hostName ))) hostAddress = ((struct in_addr *)hostDesc->h_addr )->s_addr; else hostAddress = inet_addr( hostName ); if(( hostAddress == -1 ) || ( hostAddress == INADDR_NONE )) return( 0 ); else return( hostAddress ); } pldebugger-1.8/dbgcomm.h000066400000000000000000000015121464645137500153010ustar00rootroot00000000000000/* * dbgcomm.h * * This file defines the functions used to establish connections between * the debugging proxy and target backend. * * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. * * Licensed under the Artistic License v2.0, see * https://opensource.org/licenses/artistic-license-2.0 * for full details */ #ifndef DBGCOMM_H #define DBGCOMM_H #if (PG_VERSION_NUM >= 170000) #define BackendId ProcNumber #define MyBackendId MyProcNumber #define InvalidBackendId INVALID_PROC_NUMBER #endif extern void dbgcomm_reserve(void); extern int dbgcomm_connect_to_proxy(int proxyPort); extern int dbgcomm_listen_for_proxy(void); extern int dbgcomm_listen_for_target(int *port); extern int dbgcomm_accept_target(int sockfd, int *targetPid); extern int dbgcomm_connect_to_target(BackendId targetBackend); #endif pldebugger-1.8/globalbp.h000066400000000000000000000036741464645137500154660ustar00rootroot00000000000000/* * globalbp.h - * * This file defines the (shared-memory) structures used by the PL debugger * to keep track of global breakpoints. * * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. * * Licensed under the Artistic License v2.0, see * https://opensource.org/licenses/artistic-license-2.0 * for full details */ #ifndef GLOBALBP_H #define GLOBALBP_H #include "utils/hsearch.h" typedef enum { BP_LOCAL = 0, BP_GLOBAL } eBreakpointScope; /* * Stores information pertaining to a global breakpoint. */ typedef struct BreakpointData { bool isTmp; /* tmp breakpoints are removed when hit */ bool busy; /* is this session already in use by a target? */ int proxyPort; /* port number of the proxy listener */ int proxyPid; /* process id of the proxy process */ } BreakpointData; /* * The key of the global breakpoints hash table. For now holds only have an Oid field. * but it may contain more fields in future. */ typedef struct BreakpointKey { Oid databaseId; Oid functionId; int lineNumber; int targetPid; /* -1 means any process */ } BreakpointKey; typedef struct Breakpoint { BreakpointKey key; BreakpointData data; } Breakpoint; extern Breakpoint * BreakpointLookup(eBreakpointScope scope, BreakpointKey *key); extern bool BreakpointInsert(eBreakpointScope scope, BreakpointKey *key, BreakpointData *brkpnt); extern bool BreakpointDelete(eBreakpointScope scope, BreakpointKey *key); extern void BreakpointShowAll(eBreakpointScope scope); extern bool BreakpointInsertOrUpdate(eBreakpointScope scope, BreakpointKey *key, BreakpointData *data); extern bool BreakpointOnId(eBreakpointScope scope, Oid funcOid); extern void BreakpointCleanupProc(int pid); extern void BreakpointGetList(eBreakpointScope scope, HASH_SEQ_STATUS *scan); extern void BreakpointReleaseList(eBreakpointScope scope); extern void BreakpointBusySession(int pid); extern void BreakpointFreeSession(int pid); #endif pldebugger-1.8/pldbgapi--1.0--1.1.sql000066400000000000000000000053631464645137500167560ustar00rootroot00000000000000-- pldbgapi--1.0--1.1.sql -- This script upgrades the API from version 1.0 to 1.1. -- -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. -- -- Licensed under the Artistic License v2.0, see -- https://opensource.org/licenses/artistic-license-2.0 -- for full details DO $do$ declare isedb bool; createstmt text; begin isedb = (SELECT version() LIKE '%EnterpriseDB%'); createstmt := $create_stmt$ CREATE OR REPLACE FUNCTION pldbg_get_target_info(signature text, targetType "char") returns targetinfo AS $$ SELECT p.oid AS target, pronamespace AS schema, pronargs::int4 AS nargs, -- The returned argtypes column is of type oidvector, but unlike -- proargtypes, it's supposed to include OUT params. So we -- essentially have to return proallargtypes, converted to an -- oidvector. There is no oid[] -> oidvector cast, so we have to -- do it via text. CASE WHEN proallargtypes IS NOT NULL THEN translate(proallargtypes::text, ',{}', ' ')::oidvector ELSE proargtypes END AS argtypes, proname AS targetname, proargmodes AS argmodes, proargnames AS proargnames, prolang AS targetlang, quote_ident(nspname) || '.' || quote_ident(proname) AS fqname, proretset AS returnsset, prorettype AS returntype, $create_stmt$; -- Add the three EDB-columns to the query (as dummies if we're installing -- to PostgreSQL) IF isedb THEN createstmt := createstmt || $create_stmt$ p.protype='0' AS isfunc, CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg, edb_get_func_defvals(p.oid) AS argdefvals $create_stmt$; ELSE createstmt := createstmt || $create_stmt$ 't'::bool AS isfunc, 0::oid AS pkg, NULL::text[] AS argdefvals $create_stmt$; END IF; -- End of conditional part createstmt := createstmt || $create_stmt$ FROM pg_proc p, pg_namespace n WHERE p.pronamespace = n.oid AND p.oid = $1::oid -- We used to support querying by function name or trigger name/oid as well, -- but that was never used in the client, so the support for that has been -- removed. The targeType argument remains as a legacy of that. You're -- expected to pass 'o' as target type, but it doesn't do anything. AND $2 = 'o' $$ LANGUAGE SQL; $create_stmt$; execute createstmt; -- Add a couple of EDB specific functions IF isedb THEN CREATE OR REPLACE FUNCTION edb_oid_debug(functionOID oid) RETURNS integer AS $$ select pldbg_oid_debug($1); $$ LANGUAGE SQL; CREATE OR REPLACE FUNCTION pldbg_get_pkg_cons(packageOID oid) RETURNS oid AS $$ select oid from pg_proc where pronamespace=$1 and proname='cons'; $$ LANGUAGE SQL; END IF; end; $do$; pldebugger-1.8/pldbgapi--1.1.sql000066400000000000000000000164541464645137500164100ustar00rootroot00000000000000-- pldbgapi--1.1.sql -- This script creates the data types and functions defined by the PL debugger API -- -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. -- -- Licensed under the Artistic License v2.0, see -- https://opensource.org/licenses/artistic-license-2.0 -- for full details \echo Installing pldebugger as unpackaged objects. If you are using PostgreSQL \echo version 9.1 or above, use "CREATE EXTENSION pldbgapi" instead. CREATE TYPE breakpoint AS ( func OID, linenumber INTEGER, targetName TEXT ); CREATE TYPE frame AS ( level INT, targetname TEXT, func OID, linenumber INTEGER, args TEXT ); CREATE TYPE var AS ( name TEXT, varClass char, lineNumber INTEGER, isUnique bool, isConst bool, isNotNull bool, dtype OID, value TEXT ); CREATE TYPE proxyInfo AS ( serverVersionStr TEXT, serverVersionNum INT, proxyAPIVer INT, serverProcessID INT ); CREATE FUNCTION pldbg_oid_debug( functionOID OID ) RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; -- for backwards-compatibility CREATE FUNCTION plpgsql_oid_debug( functionOID OID ) RETURNS INTEGER AS $$ SELECT pldbg_oid_debug($1) $$ LANGUAGE sql STRICT; CREATE FUNCTION pldbg_abort_target( session INTEGER ) RETURNS SETOF boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_attach_to_port( portNumber INTEGER ) RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_continue( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_create_listener() RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_deposit_value( session INTEGER, varName TEXT, lineNumber INTEGER, value TEXT ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_drop_breakpoint( session INTEGER, func OID, linenumber INTEGER ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_get_breakpoints( session INTEGER ) RETURNS SETOF breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_get_source( session INTEGER, func OID ) RETURNS TEXT AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_get_stack( session INTEGER ) RETURNS SETOF frame AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_get_proxy_info( ) RETURNS proxyInfo AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_get_variables( session INTEGER ) RETURNS SETOF var AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_select_frame( session INTEGER, frame INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_set_breakpoint( session INTEGER, func OID, linenumber INTEGER ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_set_global_breakpoint( session INTEGER, func OID, linenumber INTEGER, targetPID INTEGER ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C; CREATE FUNCTION pldbg_step_into( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_step_over( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_wait_for_breakpoint( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; CREATE FUNCTION pldbg_wait_for_target( session INTEGER ) RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; /* * pldbg_get_target_info() function can be used to return information about * a function. * * Deprecated. This is used by the pgAdmin debugger GUI, but new applications * should just query the catalogs directly. */ CREATE TYPE targetinfo AS ( target OID, schema OID, nargs INT, argTypes oidvector, targetName NAME, argModes "char"[], argNames TEXT[], targetLang OID, fqName TEXT, returnsSet BOOL, returnType OID, -- The following columns are only needed when running in an EnterpriseDB -- server. On PostgreSQL, we return just dummy values for them. -- -- 'isFunc' and 'pkg' only make sense on EnterpriseDB. 'isfunc' is true -- if the function is a regular function, not a stored procedure or a -- function that was created implictly to back a trigger created with the -- Oracle-compatible CREATE TRIGGER syntax. If the function belongs to a -- package, 'pkg' is the package's OID, or 0 otherwise. -- -- 'argDefVals' is a representation of the function's argument DEFAULTs. -- That would be nice to have on PostgreSQL as well. Unfortunately our -- current implementation relies on an EDB-only function to get that -- information, so we cannot just use it as is. TODO: rewrite that using -- pg_get_expr(pg_proc.proargdefaults). isFunc BOOL, pkg OID, argDefVals TEXT[] ); -- Create the pldbg_get_target_info() function. We use an inline code block -- so that we can check and create it slightly differently if running on -- an EnterpriseDB server. DO $do$ declare isedb bool; createstmt text; begin isedb = (SELECT version() LIKE '%EnterpriseDB%'); createstmt := $create_stmt$ CREATE FUNCTION pldbg_get_target_info(signature text, targetType "char") returns targetinfo AS $$ SELECT p.oid AS target, pronamespace AS schema, pronargs::int4 AS nargs, -- The returned argtypes column is of type oidvector, but unlike -- proargtypes, it's supposed to include OUT params. So we -- essentially have to return proallargtypes, converted to an -- oidvector. There is no oid[] -> oidvector cast, so we have to -- do it via text. CASE WHEN proallargtypes IS NOT NULL THEN translate(proallargtypes::text, ',{}', ' ')::oidvector ELSE proargtypes END AS argtypes, proname AS targetname, proargmodes AS argmodes, proargnames AS proargnames, prolang AS targetlang, quote_ident(nspname) || '.' || quote_ident(proname) AS fqname, proretset AS returnsset, prorettype AS returntype, $create_stmt$; -- Add the three EDB-columns to the query (as dummies if we're installing -- to PostgreSQL) IF isedb THEN createstmt := createstmt || $create_stmt$ p.protype='0' AS isfunc, CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg, edb_get_func_defvals(p.oid) AS argdefvals $create_stmt$; ELSE createstmt := createstmt || $create_stmt$ 't'::bool AS isfunc, 0::oid AS pkg, NULL::text[] AS argdefvals $create_stmt$; END IF; -- End of conditional part createstmt := createstmt || $create_stmt$ FROM pg_proc p, pg_namespace n WHERE p.pronamespace = n.oid AND p.oid = $1::oid -- We used to support querying by function name or trigger name/oid as well, -- but that was never used in the client, so the support for that has been -- removed. The targeType argument remains as a legacy of that. You're -- expected to pass 'o' as target type, but it doesn't do anything. AND $2 = 'o' $$ LANGUAGE SQL; $create_stmt$; execute createstmt; -- Add a couple of EDB specific functions IF isedb THEN CREATE FUNCTION edb_oid_debug(functionOID oid) RETURNS integer AS $$ select pldbg_oid_debug($1); $$ LANGUAGE SQL; CREATE FUNCTION pldbg_get_pkg_cons(packageOID oid) RETURNS oid AS $$ select oid from pg_proc where pronamespace=$1 and proname='cons'; $$ LANGUAGE SQL; END IF; end; $do$; pldebugger-1.8/pldbgapi--unpackaged--1.1.sql000066400000000000000000000050131464645137500205520ustar00rootroot00000000000000-- pldbgapi--unpackaged--1.1.sql -- This script converts an existing API installation to an extension. -- -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. -- -- Licensed under the Artistic License v2.0, see -- https://opensource.org/licenses/artistic-license-2.0 -- for full details ALTER EXTENSION pldbgapi ADD TYPE breakpoint; ALTER EXTENSION pldbgapi ADD TYPE frame; ALTER EXTENSION pldbgapi ADD TYPE targetinfo; ALTER EXTENSION pldbgapi ADD TYPE var; ALTER EXTENSION pldbgapi ADD TYPE proxyInfo; ALTER EXTENSION pldbgapi ADD FUNCTION plpgsql_oid_debug( functionOID OID ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_abort_target( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_attach_to_port( portNumber INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_continue( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_create_listener(); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_deposit_value( session INTEGER, varName TEXT, lineNumber INTEGER, value TEXT ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_drop_breakpoint( session INTEGER, func OID, linenumber INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_breakpoints( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_source( session INTEGER, func OID ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_stack( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_proxy_info( ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_variables( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_select_frame( session INTEGER, frame INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_set_breakpoint( session INTEGER, func OID, linenumber INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_set_global_breakpoint( session INTEGER, func OID, linenumber INTEGER, targetPID INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_step_into( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_step_over( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_wait_for_breakpoint( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_wait_for_target( session INTEGER ); ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_target_info( signature TEXT, targetType "char" ); DO $do$ declare isedb bool; begin isedb = (SELECT version() LIKE '%EnterpriseDB%'); -- Add a couple of EDB specific functions IF isedb THEN ALTER EXTENSION pldbgapi ADD edb_oid_debug( functionOID oid ); ALTER EXTENSION pldbgapi ADD pldbg_get_pkg_cons( packageOID oid ); END IF; $do$; pldebugger-1.8/pldbgapi.c000066400000000000000000001357531464645137500154650ustar00rootroot00000000000000/* * pldbgapi.c * * This module defines (and implements) an API for debugging PL * functions and procedures (in particular, functions and procedures * written in PL/pgSQL or edb-spl). * * To debug a function or procedure, you need two backend processes * plus a debugger client (the client could be a command-line client * such as psql but is more likely a graphical client such as pgAdmin). * * The first backend is called the target - it's the process that's * running the code that you want to debug. * * The second backend is a 'proxy' process that shuttles data between * the debugger client and the target. The functions implemented in * this module are called 'proxy functions'. * * The proxy process provides an easy and secure way for the debugger * client to connect to the target - the client opens a normal * libpq-style connection that (presumably) knows how to work it's * way through a firewall and through the authentication maze (once * the connection process completes, the debugger client is connected * to the proxy). * * The debugger client can call any of the functions in this API. * Each function is executed by the proxy process. The proxy * shuttles debugging requests (like 'step into' or 'show call * stack') to the debugger server (running inside of the target * process) and sends the results back to the debugger client. * * There are a few basic rules for using this API: * * You must call one of the connection functions before you can do * anything else (at this point, the only connection function is * 'pldbg_attach_to_port()', but we'll add more as soon as we * implement global breakpoints). Each connection function returns * a session ID that identifies that debugging session (a debugger * client can maintain multiple simultaneous sessions by keeping * track of each session identifier). You pass that session ID * to all of the other proxy functions. * * Once you have opened a session, you must wait for the target * to reach a breakpoint (it may already be stopped at a breakpoint) * by calling pldbg_wait_for_breakpoint( sessionID ) - that function * will hang until the target reaches a breakpoint (or the target * session ends). * * When the target pauses, you can interact with the debugger server * (running inside of the target process) by calling any of the other * proxy functions. For example, to tell the target to "step into" a * function/procedure call, you would call pldbg_step_into() (and that * function would hang until the target pauses). To tell the target * to continue until the next breakpoint, you would call * pldbg_continue() (and, again, that function would hang until the * target pauses). * * Each time the target pauses, it returns a tuple of type 'breakpoint'. * That tuple contains the OID of the function that the target has paused * in, and the line number at which the target has paused. The fact that the * target returns a tuple of type breakpoint does not imply that the target * has paused at a breakpoint - it may have paused because of a step-over or * step-into operation. * * When the target is paused at a breakpoint (or has paused after * a step-over or step-into), you can interrogate the target by calling * pldbg_get_stack(), pldbg_get_source(), pldbg_get_breakpoints(), or * pldbg_get_variables(). * * The debugger server groks the PL call stack and maintains a * 'focus' frame. By default, the debugger server focuses on the most * deeply nested frame (because that's the code that's actually * running). You can shift the debugger's focus to a different frame * by calling pldbg_select_frame(). * * The focus is important because many functions (such as * pldbg_get_variables()) work against the stack frame that has the focus. * * Any of the proxy functions may throw an error - in particular, a proxy * function will throw an error if the target process ends. You're most * likely to encounter an error when you call pldbg_continue() and the * target process runs to completion (without hitting another breakpoint) * * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. * * Licensed under the Artistic License v2.0, see * https://opensource.org/licenses/artistic-license-2.0 * for full details */ #include "postgres.h" #include "funcapi.h" #include "utils/memutils.h" #include "utils/builtins.h" #include "storage/ipc.h" /* For on_shmem_exit() */ #include "storage/proc.h" /* For MyProc */ #include "libpq/libpq-be.h" /* For Port */ #include "miscadmin.h" /* For MyProcPort */ #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "access/htup.h" /* For heap_form_tuple() */ #include "access/hash.h" /* For dynahash stuff */ #include #include /* For close() */ #include #include #include #include "globalbp.h" #include "dbgcomm.h" /* Include header for GETSTRUCT */ #if (PG_VERSION_NUM >= 90300) #include "access/htup_details.h" #endif #if PG_VERSION_NUM >= 110000 #ifndef TRUE #define TRUE true #endif #ifndef FALSE #define FALSE false #endif #endif /* * Let the PG module loader know that we are compiled against * the right version of the PG header files */ #ifdef PG_MODULE_MAGIC PG_MODULE_MAGIC; #endif /******************************************************************************* * Proxy functions *******************************************************************************/ PG_FUNCTION_INFO_V1( pldbg_attach_to_port ); /* Attach to debugger server at the given port */ PG_FUNCTION_INFO_V1( pldbg_wait_for_breakpoint ); /* Wait for the target to reach a breakpoint */ PG_FUNCTION_INFO_V1( pldbg_step_into ); /* Steop into a function/procedure call */ PG_FUNCTION_INFO_V1( pldbg_step_over ); /* Step over a function/procedure call */ PG_FUNCTION_INFO_V1( pldbg_continue ); /* Continue execution until next breakpoint */ PG_FUNCTION_INFO_V1( pldbg_get_source ); /* Get the source code for a function/procedure */ PG_FUNCTION_INFO_V1( pldbg_get_breakpoints ); /* SHOW BREAKPOINTS equivalent (deprecated) */ PG_FUNCTION_INFO_V1( pldbg_get_variables ); /* Get a list of variable names/types/values */ PG_FUNCTION_INFO_V1( pldbg_get_stack ); /* Get the call stack from the target */ PG_FUNCTION_INFO_V1( pldbg_set_breakpoint ); /* CREATE BREAKPOINT equivalent (deprecated) */ PG_FUNCTION_INFO_V1( pldbg_drop_breakpoint ); /* DROP BREAKPOINT equivalent (deprecated) */ PG_FUNCTION_INFO_V1( pldbg_select_frame ); /* Change the focus to a different stack frame */ PG_FUNCTION_INFO_V1( pldbg_deposit_value ); /* Change the value of an in-scope variable */ PG_FUNCTION_INFO_V1( pldbg_abort_target ); /* Abort execution of the target - throws error */ PG_FUNCTION_INFO_V1( pldbg_get_proxy_info ); /* Get server version, proxy API version, ... */ PG_FUNCTION_INFO_V1( pldbg_create_listener ); /* Create a listener for global breakpoints */ PG_FUNCTION_INFO_V1( pldbg_wait_for_target ); /* Wait for a global breakpoint to fire */ PG_FUNCTION_INFO_V1( pldbg_set_global_breakpoint ); /* Create a global breakpoint */ /******************************************************************************* * Structure debugSession * * A debugger client may attach to many target sessions at the same time. We * keep track of each connection in a debugSession structure. When the client * makes a connection, we allocate a new debugSession structure and return * a handle to that structure to the caller. He gives us back the handle * whenever he calls another proxy function. A handle is just a smallish * integer value that we use to track each session - we use a hash to map * handles into debugSession pointers. */ typedef struct { int serverSocket; /* Socket connected to the debugger server */ int serverPort; /* Port number where debugger server is listening */ int listener; /* Socket where we wait for global breakpoints */ char *breakpointString; } debugSession; /******************************************************************************* * Stucture sessionHashEntry * * As mentioned above (see debugSession), a debugger proxy can manage many * debug sessions at once. To keep track of each session, we create a * debugSession object and return a handle to that object to the caller. The * handle is an opaque value - it's just an integer value. To convert a * handle into an actual debugSession pointer, we create a hash that maps * handles into debugSession pointers. * * Each member of the hash is shaped like a sessionHashEntry object. */ typedef int32 sessionHandle; typedef struct { sessionHandle m_handle; debugSession *m_session; } sessionHashEntry; static debugSession * mostRecentSession; static HTAB * sessionHash; /******************************************************************************* * The following symbols represent the magic strings that we send to the * debugger server running in the target process */ #define PLDBG_GET_VARIABLES "i\n" #define PLDBG_GET_BREAKPOINTS "l\n" #define PLDBG_GET_STACK "$\n" #define PLDBG_STEP_INTO "s\n" #define PLDBG_STEP_OVER "o\n" #define PLDBG_CONTINUE "c\n" #define PLDBG_ABORT "x" #define PLDBG_SELECT_FRAME "^" /* Followed by frame number */ #define PLDBG_SET_BREAKPOINT "b" /* Followed by pkgoid:funcoid:linenumber */ #define PLDBG_CLEAR_BREAKPOINT "f" /* Followed by pkgoid:funcoid:linenumber */ #define PLDBG_GET_SOURCE "#" /* Followed by pkgoid:funcoid */ #define PLDBG_DEPOSIT "d" /* Followed by var.line=value */ #define PLDBG_STRING_MAX_LEN 128 #define PROXY_API_VERSION 3 /* API version number */ /******************************************************************************* * We currently define three PostgreSQL data types (all tuples) - the following * symbols correspond to the names for those types. */ #define TYPE_NAME_BREAKPOINT "breakpoint" /* May change to pldbg.breakpoint later */ #define TYPE_NAME_FRAME "frame" /* May change to pldbg.frame later */ #define TYPE_NAME_VAR "var" /* May change to pldbg.var later */ #define GET_STR( textp ) DatumGetCString( DirectFunctionCall1( textout, PointerGetDatum( textp ))) #define PG_GETARG_SESSION( n ) (sessionHandle)PG_GETARG_UINT32( n ) Datum pldbg_select_frame( PG_FUNCTION_ARGS ); Datum pldbg_attach_to_port( PG_FUNCTION_ARGS ); Datum pldbg_get_source( PG_FUNCTION_ARGS ); Datum pldbg_get_breakpoints( PG_FUNCTION_ARGS ); Datum pldbg_get_variables( PG_FUNCTION_ARGS ); Datum pldbg_get_stack( PG_FUNCTION_ARGS ); Datum pldbg_wait_for_breakpoint( PG_FUNCTION_ARGS ); Datum pldbg_set_breakpoint( PG_FUNCTION_ARGS ); Datum pldbg_drop_breakpoint( PG_FUNCTION_ARGS ); Datum pldbg_step_into( PG_FUNCTION_ARGS ); Datum pldbg_step_over( PG_FUNCTION_ARGS ); Datum pldbg_continue( PG_FUNCTION_ARGS ); Datum pldbg_deposit_value( PG_FUNCTION_ARGS ); Datum pldbg_get_proxy_info( PG_FUNCTION_ARGS ); Datum pldbg_get_pkg_cons( PG_FUNCTION_ARGS ); Datum pldbg_abort_target( PG_FUNCTION_ARGS ); Datum pldbg_create_listener( PG_FUNCTION_ARGS ); Datum pldbg_wait_for_target( PG_FUNCTION_ARGS ); Datum pldbg_set_global_breakpoint( PG_FUNCTION_ARGS ); /************************************************************ * Local function forward declarations ************************************************************/ static char * tokenize( char * src, const char * delimiters, char ** ctx ); static void * readn( int serverHandle, void * dst, size_t len ); static void * writen( int serverHandle, void * dst, size_t len ); static void sendBytes( debugSession * session, void * src, size_t len ); static void sendUInt32( debugSession * session, uint32 val ); static void sendString( debugSession * session, char * src ); static bool getBool( debugSession * session ); static uint32 getUInt32( debugSession * session ); static char * getNString( debugSession * session ); static void initializeModule( void ); static void cleanupAtExit( int code, Datum arg ); static void initSessionHash(); static debugSession * defaultSession( sessionHandle handle ); static sessionHandle addSession( debugSession * session ); static debugSession * findSession( sessionHandle handle ); static TupleDesc getResultTupleDesc( FunctionCallInfo fcinfo ); /******************************************************************************* * Exported functions *******************************************************************************/ /******************************************************************************* * pldbg_attach_to_port( portNumber INTEGER ) RETURNS INTEGER * * This function attaches to a debugging target listening on the given port. A * debugger client should invoke this function in response to a PLDBGBREAK * NOTICE (the notice contains the port number that you should connect to). * * This function returns a session handle that identifies this particular debug * session. When you call any of the other pldbg functions, you must supply * the session handle returned by pldbg_attach_to_port(). * * A given debugger client can maintain multiple simultaneous sessions * by calling pldbg_attach_to_port() many times (with different port * numbers) and keeping track of the returned session handles. */ Datum pldbg_attach_to_port( PG_FUNCTION_ARGS ) { int32 targetBackend = PG_GETARG_INT32( 0 ); debugSession *session; initializeModule(); session = MemoryContextAllocZero( TopMemoryContext, sizeof( *session )); session->listener = -1; session->serverSocket = dbgcomm_connect_to_target(targetBackend); if (session->serverSocket < 0) ereport(ERROR, (errcode_for_socket_access(), errmsg("could not connect to debug target"))); /* * After the handshake, the target process will send us information about * the local breakpoint that it hit. Read it. We will hand it to the client * if it calls wait_for_breakpoint(). */ session->breakpointString = MemoryContextStrdup(TopMemoryContext, getNString(session)); /* * For convenience, remember the most recent session - if you call * another pldbg_xxx() function with sessionHandle = 0, we'll use * the most recent session. */ mostRecentSession = session; PG_RETURN_INT32(addSession(session)); } Datum pldbg_create_listener( PG_FUNCTION_ARGS ) { debugSession * session = MemoryContextAllocZero( TopMemoryContext, sizeof( *session )); initializeModule(); session->listener = dbgcomm_listen_for_target(&session->serverPort); session->serverSocket = -1; mostRecentSession = session; PG_RETURN_INT32( addSession( session )); } /******************************************************************************* * pldbg_wait_for_target( ) RETURNS INTEGER * * This function advertises the proxy process as an active debugger, waiting * for global breakpoints. * * This function returns a session handle that identifies this particular debug * session. When you call any of the other pldbg functions, you must supply * this session handle. * * A given debugger client can maintain multiple simultaneous sessions * by calling pldbg_attach_to_port() many times (with different port * numbers) and keeping track of the returned session handles. */ Datum pldbg_wait_for_target( PG_FUNCTION_ARGS ) { debugSession *session = defaultSession(PG_GETARG_SESSION( 0 )); int serverSocket; int serverPID; /* * Now mark all of our global breakpoints as 'available' (that is, not * busy) */ BreakpointFreeSession( MyProc->pid ); serverSocket = dbgcomm_accept_target(session->listener, &serverPID); if (serverSocket < 0) ereport(ERROR, (errmsg("could not accept a connection from debugging target"))); session->serverSocket = serverSocket; /* * After the handshake, the target process will send us information about * the local breakpoint that it hit. Read it. We will hand it to the client * if it calls wait_for_breakpoint(). */ session->breakpointString = MemoryContextStrdup(TopMemoryContext, getNString(session)); PG_RETURN_UINT32( serverPID ); } /******************************************************************************* * pldbg_set_global_breakpoint(sessionID INT, function OID, lineNumber INT) * RETURNS boolean * * This function registers a breakpoint in the global breakpoint table. */ Datum pldbg_set_global_breakpoint( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); Breakpoint breakpoint; if( !superuser()) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be a superuser to create a breakpoint"))); if( session->listener == -1 ) ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("given session is not a listener"))); breakpoint.key.databaseId = MyProc->databaseId; breakpoint.key.functionId = PG_GETARG_OID( 1 ); if( PG_ARGISNULL( 2 )) breakpoint.key.lineNumber = -1; else breakpoint.key.lineNumber = PG_GETARG_INT32( 2 ); if( PG_ARGISNULL( 3 )) breakpoint.key.targetPid = -1; else breakpoint.key.targetPid = PG_GETARG_INT32( 3 ); breakpoint.data.isTmp = TRUE; breakpoint.data.proxyPort = session->serverPort; breakpoint.data.proxyPid = MyProc->pid; if( !BreakpointInsert( BP_GLOBAL, &breakpoint.key, &breakpoint.data )) ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("another debugger is already waiting for that breakpoint"))); PG_RETURN_BOOL( true ); } /******************************************************************************* * pldbg_wait_for_breakpoint( sessionID INTEGER ) RETURNS breakpoint * * This function waits for the debug target to reach a breakpoint. You should * call this function immediately after pldbg_attach_to_port() returns a * session ID. pldbg_wait_for_breakpoint() is nearly identical to * pldbg_step_into(), pldbg_step_over(), and pldbg_continue(), (they all wait * for the target) but this function does not send a command to the target * first. * * This function returns a tuple of type 'breakpoint' - such a tuple contains * the function OID and line number where the target is currently stopped. */ static Datum buildBreakpointDatum( char * breakpointString ) { char * values[3]; char * ctx = NULL; HeapTuple result; TupleDesc tupleDesc = RelationNameGetTupleDesc( TYPE_NAME_BREAKPOINT ); values[0] = tokenize( breakpointString, ":", &ctx ); /* function OID */ values[1] = tokenize( NULL, ":", &ctx ); /* linenumber */ values[2] = tokenize( NULL, ":", &ctx ); /* targetName */ result = BuildTupleFromCStrings( TupleDescGetAttInMetadata( tupleDesc ), values ); return( HeapTupleGetDatum( result )); } Datum pldbg_wait_for_breakpoint( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); char * breakpointString; if (!session->breakpointString) PG_RETURN_NULL(); breakpointString = pstrdup(session->breakpointString); pfree(session->breakpointString); session->breakpointString = NULL; PG_RETURN_DATUM( buildBreakpointDatum( breakpointString )); } /******************************************************************************* * pldbg_step_into( sessionID INTEGER ) RETURNS breakpoint * * This function sends a "step/into" command to the debugger target and then * waits for target to reach the next executable statement. * * This function returns a tuple of type 'breakpoint' that contains the * function OID and line number where the target is currently stopped. */ Datum pldbg_step_into( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); sendString( session, PLDBG_STEP_INTO ); PG_RETURN_DATUM( buildBreakpointDatum( getNString( session ))); } /******************************************************************************* * pldbg_step_over( sessionID INTEGER ) RETURNS breakpoint * * This function sends a "step/over" command to the debugger target and then * waits for target to reach the next executable statement within the current * function. If the target encounters a breakpoint (presumably in a child * invocation) before reaching the next executable line, it will stop at the * breakpoint. * * This function returns a tuple of type 'breakpoint' that contains the * function OID and line number where the target is currently stopped. */ Datum pldbg_step_over( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); sendString( session, PLDBG_STEP_OVER ); PG_RETURN_DATUM( buildBreakpointDatum( getNString( session ))); } /******************************************************************************* * pldbg_continue( sessionID INTEGER ) RETURNS breakpoint * * This function sends a "continue" command to the debugger target and then * waits for target to reach a breakpoint. * * This function returns a tuple of type 'breakpoint' that contains the * function OID and line number where the target is currently stopped. */ Datum pldbg_continue( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); sendString( session, PLDBG_CONTINUE ); PG_RETURN_DATUM( buildBreakpointDatum( getNString( session ))); } /******************************************************************************* * pldbg_abort_target( sessionID INTEGER ) RETURNS breakpoint * * This function sends an "abort" command to the debugger target and then * waits for a reply */ Datum pldbg_abort_target( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); sendString( session, PLDBG_ABORT ); PG_RETURN_BOOL( getBool( session )); } /******************************************************************************* * pldbg_select_frame( sessionID INTEGER, frameNumber INTEGER ) * RETURNS breakpoint * * This function changes the debugger focus to the indicated frame (in the call * stack). Whenever the target stops (at a breakpoint or as the result of a * step/into or step/over), the debugger changes focus to most deeply nested * function in the call stack (because that's the function that's executing). * * You can change the debugger focus to other stack frames - once you do that, * you can examine the source code for that frame, the variable values in that * frame, and the breakpoints in that target. * * The debugger focus remains on the selected frame until you change it or * the target stops at another breakpoint. * * This function returns a tuple of type 'breakpoint' that contains the * function OID, and line number where the target is currently stopped in * the selected frame. */ Datum pldbg_select_frame( PG_FUNCTION_ARGS ) { if( PG_ARGISNULL( 0 )) PG_RETURN_NULL(); else { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); int32 frameNumber = PG_GETARG_INT32( 1 ); char frameString[PLDBG_STRING_MAX_LEN]; char * resultString; Datum result; snprintf( frameString, PLDBG_STRING_MAX_LEN, "%s %d", PLDBG_SELECT_FRAME, frameNumber ); sendString( session, frameString ); resultString = getNString( session ); result = buildBreakpointDatum( resultString ); PG_RETURN_DATUM( result ); } } /******************************************************************************* * pldbg_get_source( sessionID INTEGER, functionOID OID ) * RETURNS CSTRING * * This function returns the source code for the given function. A debugger * client should always retrieve source code using this function instead of * reading pg_proc. If you read pg_proc instead, the source code that you * read may not match the source that the target is actually executing * (because the source code may have been modified in a different transaction). * * pldbg_get_source() always retrieves the source code from the target and * ensures that the source code that you get is the source code that the * target is executing. * */ Datum pldbg_get_source( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); Oid funcOID = PG_GETARG_OID( 1 ); char sourceString[PLDBG_STRING_MAX_LEN]; char * source; snprintf( sourceString, PLDBG_STRING_MAX_LEN, "%s %u", PLDBG_GET_SOURCE, funcOID ); sendString( session, sourceString ); source = getNString( session ); PG_RETURN_TEXT_P(cstring_to_text(source)); } /******************************************************************************* * pldbg_get_breakpoints( sessionID INTEGER ) RETURNS SETOF breakpoint * * This function returns a SETOF breakpoint tuples. Each tuple in the result * set identifies a breakpoint. * * NOTE: the result set returned by this function should be identical to * the result set returned by a SHOW BREAKPOINTS command. This function * may become obsolete when SHOW BREAKPOINTS is complete. */ Datum pldbg_get_breakpoints( PG_FUNCTION_ARGS ) { FuncCallContext * srf; debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); char * breakpointString; if( SRF_IS_FIRSTCALL()) { MemoryContext oldContext; srf = SRF_FIRSTCALL_INIT(); oldContext = MemoryContextSwitchTo( srf->multi_call_memory_ctx ); srf->attinmeta = TupleDescGetAttInMetadata( RelationNameGetTupleDesc( TYPE_NAME_BREAKPOINT )); MemoryContextSwitchTo( oldContext ); sendString( session, PLDBG_GET_BREAKPOINTS ); } else { srf = SRF_PERCALL_SETUP(); } if(( breakpointString = getNString( session )) != NULL ) { SRF_RETURN_NEXT( srf, buildBreakpointDatum( breakpointString )); } else { SRF_RETURN_DONE( srf ); } } /******************************************************************************* * pldbg_get_variables( sessionID INTEGER ) RETURNS SETOF var * * This function returns a SETOF var tuples. Each tuple in the result * set contains information about one local variable (or parameter) in the * stack frame that has the focus. Each tuple contains the name of the * variable, the line number at which the variable was declared, a flag * that tells you whether the name is unique within the scope of the function * (if the name is not unique, a debugger client may use the line number to * distinguish between variables with the same name), a flag that tells you * whether the variables is a CONST, a flag that tells you whether the variable * is NOT NULL, the data type of the variable (the OID of the corresponding * pg_type) and the value of the variable. * * To view variables defined in a different stack frame, call * pldbg_select_frame() to change the debugger's focus to that frame. */ Datum pldbg_get_variables( PG_FUNCTION_ARGS ) { FuncCallContext * srf; debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); char * variableString; if( SRF_IS_FIRSTCALL()) { MemoryContext oldContext; srf = SRF_FIRSTCALL_INIT(); oldContext = MemoryContextSwitchTo( srf->multi_call_memory_ctx ); srf->attinmeta = TupleDescGetAttInMetadata( RelationNameGetTupleDesc( TYPE_NAME_VAR )); MemoryContextSwitchTo( oldContext ); sendString( session, PLDBG_GET_VARIABLES ); } else { srf = SRF_PERCALL_SETUP(); } if(( variableString = getNString( session )) != NULL ) { char * values[8]; char * ctx = NULL; HeapTuple result; /* * variableString points to a string like: * varName:class:lineNumber:unique:isConst:notNull:dataTypeOID */ values[0] = pstrdup( tokenize( variableString, ":", &ctx )); /* variable name */ values[1] = pstrdup( tokenize( NULL, ":", &ctx )); /* var class */ values[2] = pstrdup( tokenize( NULL, ":", &ctx )); /* line number */ values[3] = pstrdup( tokenize( NULL, ":", &ctx )); /* unique */ values[4] = pstrdup( tokenize( NULL, ":", &ctx )); /* isConst */ values[5] = pstrdup( tokenize( NULL, ":", &ctx )); /* notNull */ values[6] = pstrdup( tokenize( NULL, ":", &ctx )); /* data type OID */ values[7] = pstrdup( tokenize( NULL, NULL, &ctx )); /* value (rest of string) */ result = BuildTupleFromCStrings( srf->attinmeta, values ); SRF_RETURN_NEXT( srf, HeapTupleGetDatum( result )); } else { SRF_RETURN_DONE( srf ); } } /******************************************************************************* * pldbg_get_stack( sessionID INTEGER ) RETURNS SETOF frame * * This function returns a SETOF frame tuples. Each tuple in the result * set contains information about one stack frame: the tuple contains the * function OID, and line number within that function. Each tuple also * contains a string that you can use to display the name and value of each * argument to that particular invocation. */ Datum pldbg_get_stack( PG_FUNCTION_ARGS ) { FuncCallContext * srf; debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); char * frameString; if( SRF_IS_FIRSTCALL()) { MemoryContext oldContext; srf = SRF_FIRSTCALL_INIT(); oldContext = MemoryContextSwitchTo( srf->multi_call_memory_ctx ); srf->attinmeta = TupleDescGetAttInMetadata( RelationNameGetTupleDesc( TYPE_NAME_FRAME )); MemoryContextSwitchTo( oldContext ); sendString( session, PLDBG_GET_STACK ); } else { srf = SRF_PERCALL_SETUP(); } if(( frameString = getNString( session )) != NULL ) { char * values[5]; char callCount[PLDBG_STRING_MAX_LEN]; char * ctx = NULL; HeapTuple result; /* * frameString points to a string like: * targetName:funcOID:lineNumber:arguments */ snprintf( callCount, PLDBG_STRING_MAX_LEN, UINT64_FORMAT, (uint64)srf->call_cntr ); values[0] = callCount; values[1] = tokenize( frameString, ":", &ctx ); /* targetName */ values[2] = tokenize( NULL, ":", &ctx ); /* funcOID */ values[3] = tokenize( NULL, ":", &ctx ); /* lineNumber */ values[4] = tokenize( NULL, NULL, &ctx ); /* arguments - rest of string */ result = BuildTupleFromCStrings( srf->attinmeta, values ); SRF_RETURN_NEXT( srf, HeapTupleGetDatum( result )); } else { SRF_RETURN_DONE( srf ); } } /******************************************************************************** * pldbg_get_proxy_info( ) RETURNS proxyInfo * * This function retrieves a small collection of parameters from the server, all * parameters are related to the version of the server and the version of this * proxy API. * * You can call this function (from the debugger client process) to find out * which version of the proxy API you are talking to - if this function does * not exist, you can assume that you are talking to a version 1 proxy server. */ Datum pldbg_get_proxy_info( PG_FUNCTION_ARGS ) { Datum values[4] = {0}; bool nulls[4] = {0}; TupleDesc tupleDesc = getResultTupleDesc( fcinfo ); HeapTuple result; values[0] = DirectFunctionCall1( textin, PointerGetDatum( PG_VERSION_STR )); values[1] = Int32GetDatum( PG_VERSION_NUM ); values[2] = Int32GetDatum( PROXY_API_VERSION ); values[3] = Int32GetDatum( MyProcPid ); result = heap_form_tuple( tupleDesc, values, nulls ); PG_RETURN_DATUM( HeapTupleGetDatum( result )); } /******************************************************************************* * pldbg_set_breakpoint(sessionID INT, function OID, lineNumber INT) * RETURNS boolean * * Sets a *local* breakpoint in the target process. */ Datum pldbg_set_breakpoint( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); Oid funcOID = PG_GETARG_OID( 1 ); int lineNumber = PG_GETARG_INT32( 2 ); char breakpointString[PLDBG_STRING_MAX_LEN]; snprintf( breakpointString, PLDBG_STRING_MAX_LEN, "%s %u:%d", PLDBG_SET_BREAKPOINT, funcOID, lineNumber ); sendString( session, breakpointString ); PG_RETURN_BOOL( getBool( session )); } /******************************************************************************* * pldbg_drop_breakpoint(sessionID INT, function OID, lineNumber INT) * RETURNS boolean */ Datum pldbg_drop_breakpoint( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); Oid funcOID = PG_GETARG_OID( 1 ); int lineNumber = PG_GETARG_INT32( 2 ); char breakpointString[PLDBG_STRING_MAX_LEN]; snprintf( breakpointString, PLDBG_STRING_MAX_LEN, "%s %u:%d", PLDBG_CLEAR_BREAKPOINT, funcOID, lineNumber ); sendString( session, breakpointString ); PG_RETURN_BOOL( getBool( session )); } /******************************************************************************* * pldbg_deposit_value( sessionID INT, varName TEXT, lineNumber INT, value TEXT) * RETURNS boolean * * This function 'deposits' a new value into the given variable (identified by * name and optional line number). 'value' is evaluated as an expression that * must result in a value whose type matches the given variable (or whose type * is coerce'able to the type of the given variable). */ Datum pldbg_deposit_value( PG_FUNCTION_ARGS ) { debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); char * varName = GET_STR( PG_GETARG_TEXT_P( 1 )); int lineNumber = PG_GETARG_INT32( 2 ); char * value = GET_STR( PG_GETARG_TEXT_P( 3 )); StringInfoData buf; initStringInfo( &buf ); appendStringInfo( &buf, "%s %s.%d=%s", PLDBG_DEPOSIT, varName, lineNumber, value ); sendString( session, buf.data ); pfree( buf.data ); PG_RETURN_BOOL( getBool( session )); } /******************************************************************************* * Local supporting (static) functions *******************************************************************************/ /******************************************************************************* * initializeModule() * * Initializes the debugger proxy module. For now, we just register a callback * (cleanupAtExit()) that this backend will invoke on exit - we use that * callback to gracefully close any outstanding connections. * * NOTE: this would also be a good place to load the tuple descriptions for * each of the complex datatypes that we use (breakpoint, var, frame). */ static void initializeModule( void ) { static bool initialized = FALSE; if( !initialized ) { initialized = TRUE; on_shmem_exit( cleanupAtExit, 0 ); } } /******************************************************************************* * defaultSession() * * This function is designed to make it a little easier to build a simple * debugger client. Instead of managing session identifiers, you can simply * pass '0' to each function that requires a session ID. When a proxy function * encounters a session ID of 0, it assumes that you want to work with the most * recently used session. If you have only one session, you can simply pass * '0' to every function. This is particularly handy if you're using the proxy * API from a command line application like psql. * * NOTE: If you give this function an invalid sessionHandle it will throw an * error. A sessionHandle is valid if returned by addSession(). */ static debugSession * defaultSession( sessionHandle handle ) { debugSession * session; if( handle == 0 ) { if( mostRecentSession == NULL ) ereport( ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg( "invalid session handle" ))); else return( mostRecentSession ); } else { session = findSession( handle ); if( session == NULL ) ereport( ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg( "invalid session handle" ))); else { mostRecentSession = session; return( session ); } } return( NULL ); /* keep the compiler happy */ } /******************************************************************************* * initSessionHash() * * Initialize a hash table that we use to map session handles (simple integer * values) into debugSession pointers. * * You should call this function before you use the hash - you can call it * as many times as you like, it will only initialize the hash table on the * first invocation. */ static void initSessionHash() { if( sessionHash ) return; else { HASHCTL ctl = {0}; ctl.keysize = sizeof( sessionHandle ); ctl.entrysize = sizeof( sessionHashEntry ); ctl.hash = tag_hash; sessionHash = hash_create( "Debugger sessions", 5, &ctl, HASH_ELEM | HASH_FUNCTION ); } } /******************************************************************************* * addSession() * * Adds a session (debugSession *) to the hash that we use to map session * handles into debugSession pointers. This function returns a handle that * you should give back to the debugger client process. When the debugger * client calls us again, he gives us the handle and we map that back into * a debugSession pointer. That way, we don't have to expose a pointer to * the debugger client (which can make for nasty denial of service hacks, not * to mention 32-bit vs. 64-bit hassles). */ static sessionHandle addSession( debugSession * session ) { static sessionHandle nextHandle; sessionHashEntry * entry; bool found; sessionHandle handle; initSessionHash(); handle = ++nextHandle; entry = (sessionHashEntry *)hash_search( sessionHash, &handle, HASH_ENTER, &found ); entry->m_handle = handle; entry->m_session = session; return( handle ); } /******************************************************************************* * findSession() * * Given a sessionHandle (integer), this function returns the corresponding * debugSession pointer. If the sessionHandle is invalid (that is, it's a * number not returned by addSession()), this function returns NULL. */ static debugSession * findSession( sessionHandle handle ) { sessionHashEntry * entry; initSessionHash(); if(( entry = hash_search( sessionHash, &handle, HASH_FIND, NULL )) != NULL ) { return( entry->m_session ); } else { return( NULL ); } } /******************************************************************************* * tokenize() * * This is a re-entrant safe version of the standard C strtok() function. * tokenize() will split a string (src) into multiple substrings separated by * any of the characters in the delimiter string (delimiters). Each time you * call tokenize(), it returns the next subtstring (or NULL when all substrings * have been exhausted). The first time you call this function, ctx should be * NULL and src should point to the start of the string you are splitting. * For every subsequent call, src should be NULL and tokenize() will manage * ctx itself. * * NOTE: the search string (src) is brutally altered by this function - make * a copy of the search string before you call tokenize() if you need the * original string. */ static char * tokenize( char * src, const char * delimiters, char ** ctx ) { char * start; char * end; if( src == NULL ) src = *ctx; /* * Special case - if delimiters is NULL, we just return the * remainder of the string. */ if( delimiters == NULL ) return( src ); if( src == NULL ) elog(ERROR, "debugger protocol error: token expected"); /* * Skip past any leading delimiters */ start = src = ( src + strspn( src, delimiters )); if( *src == '\0' ) return( "" ); if(( end = strpbrk( start, delimiters )) == NULL ) { *ctx = strchr( start, '\0' ); } else { *end = '\0'; *ctx = end + 1; } return( start ); } /******************************************************************************* * readn() * * This function reads exactly 'len' bytes from the given socket or it * throws an error (ERRCODE_CONNECTION_FAILURE). readn() will hang until * the proper number of bytes have been read (or an error occurs). * * Note: dst must point to a buffer large enough to hold at least 'len' * bytes. readn() returns dst (for convenience). */ static void * readn( int serverHandle, void * dst, size_t len ) { size_t bytesRemaining = len; char * buffer = (char *)dst; if( serverHandle == -1 ) ereport( ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg( "given session is not connected" ))); while( bytesRemaining > 0 ) { fd_set rmask; ssize_t bytesRead; /* * Note: we want to wait for some number of bytes to arrive from the * target process, but we also want to notice if the client process * disappears. To do that, we'll call select() before we call recv() * and we'll tell select() to return as soon as something interesting * happens on *either* of the sockets. If the target sends us data * first, we're ok (that's what we are expecting to happen). If we * detect any activity on the client-side socket (which is the libpq * socket), we can assume that something's gone horribly wrong (most * likely, the user killed the client by clicking the close button). */ FD_ZERO( &rmask ); FD_SET( serverHandle, &rmask ); FD_SET( MyProcPort->sock, &rmask ); switch( select(( serverHandle > MyProcPort->sock ? serverHandle : MyProcPort->sock ) + 1, &rmask, NULL, NULL, NULL )) { case -1: { ereport( ERROR, ( errcode(ERRCODE_CONNECTION_FAILURE), errmsg( "select() failed waiting for target" ))); break; } case 0: { /* Timer expired */ return( NULL ); break; } default: { /* * We got traffic on one of the two sockets. If we see traffic * from the client (libpq) connection, just return to the * caller so that libpq can process whatever's waiting. * Presumably, the only time we'll see any libpq traffic here * is when the client process has killed itself... */ if( FD_ISSET( MyProcPort->sock, &rmask )) ereport( ERROR, ( errcode(ERRCODE_CONNECTION_FAILURE), errmsg( "debugger connection(client side) terminated" ))); break; } } bytesRead = recv( serverHandle, buffer, bytesRemaining, 0 ); if( bytesRead <= 0 && errno != EINTR ) { ereport( ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg( "debugger connection terminated" ))); return( NULL ); } bytesRemaining -= bytesRead; buffer += bytesRead; } return( dst ); } /******************************************************************************* * writen() * * This function writes exactly 'len' bytes to the given socket or it * throws an error (ERRCODE_CONNECTION_FAILURE). writen() will hang until * the proper number of bytes have been written (or an error occurs). */ static void * writen( int serverHandle, void * src, size_t len ) { size_t bytesRemaining = len; char * buffer = (char *)src; while( bytesRemaining > 0 ) { ssize_t bytesWritten; if(( bytesWritten = send( serverHandle, buffer, bytesRemaining, 0 )) <= 0 ) { ereport( ERROR, ( errcode( ERRCODE_CONNECTION_FAILURE ), errmsg( "debugger connection terminated" ))); return( NULL ); } bytesRemaining -= bytesWritten; buffer += bytesWritten; } return( src ); } /******************************************************************************* * sendBytes() * * This function sends 'len' bytes to the server (identfied by a debugSession * pointer). 'src' should point to the bytes that you want to send to the * server. */ static void sendBytes( debugSession * session, void * src, size_t len ) { writen( session->serverSocket, src, len ); } /******************************************************************************* * sendUInt32() * * This function sends a uint32 value (val) to the debugger server. */ static void sendUInt32( debugSession * session, uint32 val ) { uint32 netVal = htonl( val ); sendBytes( session, &netVal, sizeof( netVal )); } /******************************************************************************* * sendString() * * This function sends a string value (src) to the debugger server. 'src' * should point to a null-terminated string. We send the length of the string * (as a 32-bit unsigned integer), then the bytes that make up the string - we * don't send the null-terminator. */ static void sendString( debugSession * session, char * src ) { size_t len = strlen( src ); sendUInt32( session, len ); sendBytes( session, src, len ); } /******************************************************************************* * getBool() * * getBool() retreives a boolean value (TRUE or FALSE) from the server. We * call this function after we ask the server to do something that returns a * boolean result (like deleting a breakpoint or depositing a new value). */ static bool getBool( debugSession * session ) { char * str; bool result; str = getNString( session ); if (str == NULL) elog(ERROR, "debugger protocol error; bool expected"); if( str[0] == 't' ) result = TRUE; else result = FALSE; pfree( str ); return( result ); } /******************************************************************************* * getUInt32() * * Reads a 32-bit unsigned value from the server (and returns it in the host's * byte ordering) */ static uint32 getUInt32( debugSession * session ) { uint32 result; readn( session->serverSocket, &result, sizeof( result )); return( ntohl( result )); } /****************************************************************************** * getNstring() * * This function is the opposite of sendString() - it reads a string from the * debugger server. The server sends the length of the string and then the * bytes that make up the string (minus the null-terminator). We palloc() * enough space to hold the entire string (including the null-terminator) and * return a pointer to that space (after, of course, reading the string from * the server and tacking on the null-terminator). */ static char * getNString( debugSession * session ) { uint32 len = getUInt32( session ); if( len == 0 ) return( NULL ); else { char * result = palloc( len + 1 ); readn( session->serverSocket, result, len ); result[len] = '\0'; return( result ); } } /******************************************************************************* * closeSession() * * This function closes (in an orderly manner) the connection with the debugger * server. */ static void closeSession( debugSession * session ) { if( session->serverSocket ) closesocket( session->serverSocket ); if( session->listener ) BreakpointCleanupProc( MyProcPid ); if( session->breakpointString ) pfree( session->breakpointString ); pfree( session ); } /****************************************************************************** * cleanupAtExit() * * This is a callback function that the backend invokes when exiting. At exit, * we close any connections that we may still have (connections to debugger * servers, that is). */ static void cleanupAtExit( int code, Datum arg ) { /* * FIXME: we should clean up all of the sessions stored in the * sessionHash. */ if( mostRecentSession ) closeSession( mostRecentSession ); mostRecentSession = NULL; } /******************************************************************************* * getResultTupleDesc() * * If this function returns (without throwing an error), it returns a pointer * to a description of the tuple that should be returned by the caller. * * NOTE: the caller must have been called in a context that can accept a * set, not a context that expects a tuple. That means that you * must invoke our caller with: * select * from foo(); * instead of: * select foo(); */ static TupleDesc getResultTupleDesc( FunctionCallInfo fcinfo ) { ReturnSetInfo * rsinfo = (ReturnSetInfo *)fcinfo->resultinfo; if( rsinfo == NULL ) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("function returning record called in context " "that cannot accept type record"))); } return( rsinfo->expectedDesc ); } pldebugger-1.8/pldbgapi.control000066400000000000000000000002651464645137500167100ustar00rootroot00000000000000# pldebugger extension control file comment = 'server-side support for debugging PL/pgSQL functions' default_version = '1.1' module_pathname = '$libdir/pldbgapi' relocatable = true pldebugger-1.8/pldebugger.h000066400000000000000000000064221464645137500160160ustar00rootroot00000000000000/* * pldebugger.h - * * Main debugger header * * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. * * Licensed under the Artistic License v2.0, see * https://opensource.org/licenses/artistic-license-2.0 * for full details */ #ifndef PLDEBUGGER_H #define PLDEBUGGER_H #include "globalbp.h" #include "storage/lwlock.h" /* * We keep one per_session_ctx structure per backend. This structure holds all * of the stuff that we need to track from one function call to the next. */ typedef struct { bool step_into_next_func; /* Should we step into the next function? */ int client_r; /* Read stream connected to client */ int client_w; /* Write stream connected to client */ } per_session_ctx_t; extern per_session_ctx_t per_session_ctx; /* * errorHandlerCtx * * We use setjmp() and longjmp() to handle network errors. Because we want to * be able to stack setjmp()/longjmp() savepoints, we define a structure to * wrap sigjmp_buf's - we have to do that because sigjmp_buf is defined as an * array on some platforms (like Win32). */ typedef struct { sigjmp_buf m_savepoint; } errorHandlerCtx; extern errorHandlerCtx client_lost; #define PLDBG_HELP '?' #define PLDBG_CONTINUE 'c' #define PLDBG_SET_BREAKPOINT 'b' #define PLDBG_CLEAR_BREAKPOINT 'f' #define PLDBG_PRINT_VAR 'p' #define PLDBG_PRINT_STACK '$' #define PLDBG_LIST_BREAKPOINTS 'l' #define PLDBG_STEP_INTO 's' #define PLDBG_STEP_OVER 'o' #define PLDBG_LIST '#' #define PLDBG_INFO_VARS 'i' #define PLDBG_SELECT_FRAME '^' #define PLDBG_DEPOSIT 'd' #define PLDBG_RESTART 'r' #define PLDBG_STOP 'x' typedef struct { void (* initialize)(void); bool (* frame_belongs_to_me)(ErrorContextCallback *frame); void (* send_stack_frame)(ErrorContextCallback *frame); void (* send_vars)(ErrorContextCallback *frame); void (* select_frame)(ErrorContextCallback *frame); void (* print_var)(ErrorContextCallback *frame, const char *var_name, int lineno); bool (* do_deposit)(ErrorContextCallback *frame, const char *var_name, int line_number, const char *value); Oid (* get_func_oid)(ErrorContextCallback *frame); void (* send_cur_line)(ErrorContextCallback *frame); } debugger_language_t; /* in plugin_debugger.c */ extern void initGlobalBreakpoints(void); extern bool plugin_debugger_main_loop(void); extern bool breakAtThisLine( Breakpoint ** dst, eBreakpointScope * scope, Oid funcOid, int lineNumber ); extern bool attach_to_proxy( Breakpoint * breakpoint ); extern void setBreakpoint( char * command ); extern void clearBreakpoint( char * command ); extern bool breakpointsForFunction( Oid funcOid ); extern void dbg_send( const char *fmt, ... ) #ifdef PG_PRINTF_ATTRIBUTE /* This extension allows gcc to check the format string for consistency with the supplied arguments. */ __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))) #endif ; extern char * dbg_read_str(void); extern LWLockId getPLDebuggerLock(void); /* in plpgsql_debugger.c */ extern void plpgsql_debugger_fini(void); extern debugger_language_t plpgsql_debugger_lang; #ifdef INCLUDE_PACKAGE_SUPPORT extern debugger_language_t spl_debugger_lang; #endif #if PG_VERSION_NUM >= 110000 #ifndef TRUE #define TRUE true #endif #ifndef FALSE #define FALSE false #endif #endif #endif pldebugger-1.8/pldebugger.proj000066400000000000000000000146151464645137500165440ustar00rootroot00000000000000 edb-postgres 1 /D "INCLUDE_PACKAGE_SUPPORT" postgres /Od /MDd /Zi /D "DEBUG=1" /D "_DEBUG" /DEBUG /defaultlib:$(PGPATH)\Debug\$(postgres)\$(postgres).lib /Od /MDd /Zi /D "DEBUG=1" /D "_DEBUG" /DEBUG /defaultlib:$(PGBUILDPATH)\src\backend\$(postgres).lib /Ox /MD /GF /defaultlib:$(PGPATH)\Release\$(postgres)\$(postgres).lib /Ox /MD /GF /defaultlib:$(PGBUILDPATH)\src\backend\$(postgres).lib /MACHINE:X64 /D "_USE_32BIT_TIME_T" /nologo /wd4273 /TC /LD $(XTRA_CFLAGS) /D "WIN32" /D "__WIN32__" $(XTRA_ARCH_CFLAGS) /D "_CRT_SECURE_NO_DEPRECATE" /D "_CRT_NONSTDC_NO_DEPRECATE" /D "_WINDLL" /D "_MBCS" /DLL $(XTRA_LDFLAGS) $(XTRA_ARCH_LDFLAGS) /defaultlib:user32 /defaultlib:netapi32 /defaultlib:advapi32 /defaultlib:shell32 /defaultlib:ws2_32 /defaultlib:Secur32.lib pldebugger-1.8/plpgsql_debugger.c000066400000000000000000001202551464645137500172200ustar00rootroot00000000000000/********************************************************************** * plpgsql_debugger.c - Debugger for the PL/pgSQL procedural language * * Copyright (c) 2004-2018 EnterpriseDB Corporation. All Rights Reserved. * * Licensed under the Artistic License v2.0, see * https://opensource.org/licenses/artistic-license-2.0 * for full details * **********************************************************************/ #include "postgres.h" #include #include #include #include #include #include #include "lib/stringinfo.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #include "globalbp.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/syscache.h" #include "miscadmin.h" #if INCLUDE_PACKAGE_SUPPORT #include "spl.h" #include "catalog/edb_variable.h" #else #include "plpgsql.h" #endif #include "pldebugger.h" /* Include header for GETSTRUCT */ #if (PG_VERSION_NUM >= 90300) #include "access/htup_details.h" #endif /* * We use a var_value structure to record a little extra information about * each variable. */ typedef struct { bool isnull; /* TRUE -> this variable IS NULL */ bool visible; /* hidden or visible? see is_visible_datum() */ bool duplicate_name; /* Is this one of many vars with same name? */ } var_value; /* * When the debugger decides that it needs to step through (or into) a * particular function invocation, it allocates a dbg_ctx and records the * address of that structure in the executor's context structure * (estate->plugin_info). * * The dbg_ctx keeps track of all of the information we need to step through * code and display variable values */ typedef struct { PLpgSQL_function * func; /* Function definition */ bool stepping; /* If TRUE, stop at next statement */ var_value * symbols; /* Extra debugger-private info about variables */ char ** argNames; /* Argument names */ int argNameCount; /* Number of names pointed to by argNames */ void (* error_callback)(void *arg); void (* assign_expr)( PLpgSQL_execstate *estate, PLpgSQL_datum *target, PLpgSQL_expr *expr ); #if INCLUDE_PACKAGE_SUPPORT PLpgSQL_package * package; #endif } dbg_ctx; static void dbg_startup( PLpgSQL_execstate * estate, PLpgSQL_function * func ); static void dbg_newstmt( PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt ); static void initialize_plugin_info( PLpgSQL_execstate * estate, PLpgSQL_function * func ); static char ** fetchArgNames( PLpgSQL_function * func, int * nameCount ); static PLpgSQL_var * find_var_by_name( const PLpgSQL_execstate * estate, const char * var_name, int lineno, int * index ); static bool is_datum_visible( PLpgSQL_datum * datum ); static bool is_var_visible( PLpgSQL_execstate * frame, int var_no ); static bool datumIsNull(PLpgSQL_datum *datum); static bool varIsArgument(const PLpgSQL_execstate *estate, PLpgSQL_function *func, int varNo, char **p_argname); static char * get_text_val( PLpgSQL_var * var, char ** name, char ** type ); #if INCLUDE_PACKAGE_SUPPORT static const char * plugin_name = "spl_plugin"; #else static const char * plugin_name = "PLpgSQL_plugin"; #endif static PLpgSQL_plugin plugin_funcs = { dbg_startup, NULL, NULL, dbg_newstmt, NULL }; /* * pldebugger_language_t interface. */ static void plpgsql_debugger_init(void); static bool plpgsql_frame_belongs_to_me(ErrorContextCallback *frame); static void plpgsql_send_stack_frame(ErrorContextCallback *frame); static void plpgsql_send_vars(ErrorContextCallback *frame); static void plpgsql_select_frame(ErrorContextCallback *frame); static void plpgsql_print_var(ErrorContextCallback *frame, const char *var_name, int lineno); static bool plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name, int line_number, const char *value); static Oid plpgsql_get_func_oid(ErrorContextCallback *frame); static void plpgsql_send_cur_line(ErrorContextCallback *frame); #if INCLUDE_PACKAGE_SUPPORT debugger_language_t spl_debugger_lang = #else debugger_language_t plpgsql_debugger_lang = #endif { plpgsql_debugger_init, plpgsql_frame_belongs_to_me, plpgsql_send_stack_frame, plpgsql_send_vars, plpgsql_select_frame, plpgsql_print_var, plpgsql_do_deposit, plpgsql_get_func_oid, plpgsql_send_cur_line }; /* Install this module as an PL/pgSQL instrumentation plugin */ static void plpgsql_debugger_init(void) { PLpgSQL_plugin ** var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable( plugin_name ); *var_ptr = &plugin_funcs; } /********************************************************************** * Functions implemeting the pldebugger_language_t interface **********************************************************************/ static bool plpgsql_frame_belongs_to_me(ErrorContextCallback *frame) { return (frame->callback == plugin_funcs.error_callback); } /* * plpgsql_send_stack_frame() * * This function sends information about a single stack frame to the debugger * client. This function is called by send_stack() whenever send_stack() * finds a PL/pgSQL call in the stack (remember, the call stack may contain * stack frames for functions written in other languages like PL/Tcl). */ static void plpgsql_send_stack_frame(ErrorContextCallback *frame) { PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; #if (PG_VERSION_NUM >= 80500) PLpgSQL_function * func = estate->func; #else PLpgSQL_function * func = estate->err_func; #endif PLpgSQL_stmt * stmt = estate->err_stmt; int argNameCount; char ** argNames = fetchArgNames( func, &argNameCount ); StringInfo result = makeStringInfo(); char * delimiter = ""; int arg; /* * Send the name, function OID, and line number for this frame */ appendStringInfo( result, "%s:%d:%d:", #if (PG_VERSION_NUM >= 90200) func->fn_signature, #else func->fn_name, #endif func->fn_oid, stmt->lineno ); /* * Now assemble a string that shows the argument names and value for this frame */ for( arg = 0; arg < func->fn_nargs; ++arg ) { int index = func->fn_argvarnos[arg]; PLpgSQL_datum *argDatum = (PLpgSQL_datum *)estate->datums[index]; char *value; /* value should be an empty string if argDatum is null*/ if( datumIsNull( argDatum )) value = pstrdup( "" ); else value = get_text_val((PLpgSQL_var*)argDatum, NULL, NULL ); if( argNames && argNames[arg] && argNames[arg][0] ) appendStringInfo( result, "%s%s=%s", delimiter, argNames[arg], value ); else appendStringInfo( result, "%s$%d=%s", delimiter, arg+1, value ); pfree( value ); delimiter = ", "; } dbg_send( "%s", result->data ); } /* * varIsArgument() : * * Returns true if it's an argument of the function. In case the function is an * EDB-SPL nested function, it returns true only if it is an argument of the * current nested function; in all other cases it returns false, even if it's * an argument of any of the outer nested functions in the current nested * function call stack. * * If the variable is a *named* argument, the argument name is passed * through 'p_argname'. In case of nested functions, p_argname is set if the * variable name is a named argument of any of the nested functions in the * current nested function call stack. * * varNo is the estate->datums index. */ static bool varIsArgument(const PLpgSQL_execstate *estate, PLpgSQL_function *func, int varNo, char **p_argname) { #if INCLUDE_PACKAGE_SUPPORT && (PG_VERSION_NUM >= 90600) /* * If this variable is actually an argument of this function, return the * argument name. Such argument variables have '$n' refname, so we need to * use the function argument name if it's a named argument. */ if (func->fn_nallargs > 0 && varNo >= func->fn_first_argno && varNo < func->fn_first_argno + func->fn_nallargs) { char *argname = func->fn_argnames[varNo - func->fn_first_argno]; if (argname && argname[0]) *p_argname = argname; return true; } /* * Now check if it is an argument of any of the outer functions. If yes, * we need to get the argument name in case it is a named argument. */ if (func->parent) (void) varIsArgument(estate, func->parent, varNo, p_argname); /* It is not an argument of the current function. */ return false; #else dbg_ctx *dbg_info = (dbg_ctx *) estate->plugin_info; bool isArg = false; if (varNo < dbg_info->func->fn_nargs) isArg = true; /* Get it's name if it's a named argument. */ if (varNo < dbg_info->argNameCount) { isArg = true; if (dbg_info->argNames && dbg_info->argNames[varNo] && dbg_info->argNames[varNo][0]) { *p_argname = dbg_info->argNames[varNo]; } } return isArg; #endif } /* * plpgsql_send_vars() * * This function sends a list of variables (names, types, values...) to * the proxy process. We send information about the variables defined in * the given frame (local variables) and parameter values. */ static void plpgsql_send_vars(ErrorContextCallback *frame) { PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info; int i; for( i = 0; i < estate->ndatums; i++ ) { if( is_var_visible( estate, i )) { switch( estate->datums[i]->dtype ) { #if (PG_VERSION_NUM >= 110000) case PLPGSQL_DTYPE_PROMISE: #endif case PLPGSQL_DTYPE_VAR: { PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i]; char * val; char * name = var->refname; bool isArg; isArg = varIsArgument(estate, dbg_info->func, i, &name); if( datumIsNull((PLpgSQL_datum *)var )) val = "NULL"; else val = get_text_val( var, NULL, NULL ); dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s", name, isArg ? 'A' : 'L', var->lineno, dbg_info->symbols[i].duplicate_name ? 'f' : 't', var->isconst ? 't':'f', var->notnull ? 't':'f', var->datatype ? var->datatype->typoid : InvalidOid, val ); break; } #if 0 FIXME: implement other types case PLPGSQL_DTYPE_REC: { PLpgSQL_rec * rec = (PLpgSQL_rec *) estate->datums[i]; int att; char * typeName; if (rec->tupdesc != NULL) { for( att = 0; att < rec->tupdesc->natts; ++att ) { typeName = SPI_gettype( rec->tupdesc, att + 1 ); dbg_send( "o:%s.%s:%d:%d:%d:%d:%s\n", rec->refname, NameStr( rec->tupdesc->attrs[att]->attname ), 0, rec->lineno, 0, rec->tupdesc->attrs[att]->attnotnull, typeName ? typeName : "" ); if( typeName ) pfree( typeName ); } } break; } #endif case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_RECFIELD: #if (PG_VERSION_NUM < 140000) case PLPGSQL_DTYPE_ARRAYELEM: #endif #if (PG_VERSION_NUM < 110000) case PLPGSQL_DTYPE_EXPR: #endif { /* FIXME: implement other types */ break; } } } } #if INCLUDE_PACKAGE_SUPPORT /* If this frame represents a package function/procedure, send the package variables too */ if( dbg_info->package != NULL ) { PLpgSQL_package * package = dbg_info->package; int varIndex; for( varIndex = 0; varIndex < package->ndatums; ++varIndex ) { PLpgSQL_datum * datum = package->datums[varIndex]; switch( datum->dtype ) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var * var = (PLpgSQL_var *) datum; char * val; char * name = var->refname; if( datumIsNull((PLpgSQL_datum *)var )) val = "NULL"; else val = get_text_val( var, NULL, NULL ); dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s", name, 'P', /* variable class - P means package var */ var->lineno, 'f', /* duplicate name? */ var->isconst ? 't':'f', var->notnull ? 't':'f', var->datatype ? var->datatype->typoid : InvalidOid, val ); break; } } } } #endif dbg_send( "%s", "" ); /* empty string indicates end of list */ } static void plpgsql_select_frame(ErrorContextCallback *frame) { PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; /* * When a frame is selected, ensure that we've initialized its * plugin_info. */ if (estate->plugin_info == NULL) { #if (PG_VERSION_NUM >= 80500) initialize_plugin_info(estate, estate->func); #else initialize_plugin_info(estate, estate->err_func); #endif } } /* * --------------------------------------------------------------------- * find_var_by_name() * * This function returns the PLpgSQL_var pointer that corresponds to * named variable (var_name). If the named variable can't be found, * find_var_by_name() returns NULL. * * If the index is non-NULL, this function will set *index to the * named variables index withing estate->datums[] */ static PLpgSQL_var * find_var_by_name(const PLpgSQL_execstate * estate, const char * var_name, int lineno, int * index) { dbg_ctx * dbg_info = (dbg_ctx *)estate->plugin_info; PLpgSQL_function * func = dbg_info->func; int i; for( i = 0; i < func->ndatums; i++ ) { PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i]; size_t len = strlen(var->refname); if(len != strlen(var_name)) continue; if( strncmp( var->refname, var_name, len) == 0 ) { if(( lineno == -1 ) || ( var->lineno == lineno )) { /* Found the named variable - return the index if the caller wants it */ if( index ) *index = i; } return( var ); } } /* We can't find the variable named by the caller - return NULL */ return( NULL ); } static PLpgSQL_datum * find_datum_by_name(const PLpgSQL_execstate *frame, const char *var_name, int lineNo, int *index) { dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; int i; #if INCLUDE_PACKAGE_SUPPORT if( var_name[0] == '@' ) { /* This is a package variable (it's name starts with a '@') */ int varIndex; if( dbg_info == NULL ) return( NULL ); if( dbg_info->package == NULL ) return( NULL ); for( varIndex = 0; varIndex < dbg_info->package->ndatums; ++varIndex ) { PLpgSQL_datum * datum = dbg_info->package->datums[varIndex]; switch( datum->dtype ) { #if (PG_VERSION_NUM >= 110000) case PLPGSQL_DTYPE_PROMISE: #endif case PLPGSQL_DTYPE_VAR: { PLpgSQL_var * var = (PLpgSQL_var *) datum; if( strcmp( var->refname, var_name+1 ) == 0 ) return( datum ); break; } } } return( NULL ); } #endif for( i = 0; i < frame->ndatums; ++i ) { char * datumName = NULL; int datumLineno = -1; switch( frame->datums[i]->dtype ) { #if (PG_VERSION_NUM >= 110000) case PLPGSQL_DTYPE_PROMISE: #endif case PLPGSQL_DTYPE_VAR: case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_REC: { PLpgSQL_variable * var = (PLpgSQL_variable *)frame->datums[i]; datumName = var->refname; datumLineno = var->lineno; (void) varIsArgument(frame, dbg_info->func, i, &datumName); break; } case PLPGSQL_DTYPE_RECFIELD: #if (PG_VERSION_NUM < 140000) case PLPGSQL_DTYPE_ARRAYELEM: #endif #if (PG_VERSION_NUM < 110000) case PLPGSQL_DTYPE_EXPR: #endif #if (PG_VERSION_NUM <= 80400) case PLPGSQL_DTYPE_TRIGARG: #endif { break; } } if( datumName == NULL ) continue; if( strcmp( var_name, datumName ) == 0 ) { if( lineNo == -1 || lineNo == datumLineno ) { if( index ) *index = i; return( frame->datums[i] ); } } } return( NULL ); } /* * --------------------------------------------------------------------- * print_var() * * This function will print (that is, send to the debugger client) the * type and value of the given variable. */ static void print_var(const PLpgSQL_execstate *frame, const char *var_name, int lineno, const PLpgSQL_var *tgt) { char * extval; HeapTuple typeTup; Form_pg_type typeStruct; FmgrInfo finfo_output; dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; if( tgt->isnull ) { if( dbg_info->symbols[tgt->dno].duplicate_name ) dbg_send( "v:%s(%d):NULL\n", var_name, lineno ); else dbg_send( "v:%s:NULL\n", var_name ); return; } /* Find the output function for this data type */ typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( tgt->datatype->typoid ), 0, 0, 0 ); if( !HeapTupleIsValid( typeTup )) { dbg_send( "v:%s(%d):***can't find type\n", var_name, lineno ); return; } typeStruct = (Form_pg_type)GETSTRUCT( typeTup ); /* Now invoke the output function to convert the variable into a null-terminated string */ fmgr_info( typeStruct->typoutput, &finfo_output ); extval = DatumGetCString( FunctionCall3( &finfo_output, tgt->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); /* Send the name:value to the debugger client */ if( dbg_info->symbols[tgt->dno].duplicate_name ) dbg_send( "v:%s(%d):%s\n", var_name, lineno, extval ); else dbg_send( "v:%s:%s\n", var_name, extval ); pfree( extval ); ReleaseSysCache( typeTup ); } static void print_row(const PLpgSQL_execstate *frame, const char *var_name, int lineno, const PLpgSQL_row * tgt) { /* XXX. Shouldn't there be some code here? */ } static void print_rec(const PLpgSQL_execstate *frame, const char *var_name, int lineno, const PLpgSQL_rec *tgt) { int attNo; TupleDesc rec_tupdesc; HeapTuple tuple; #if (PG_VERSION_NUM >= 110000) && !defined(INCLUDE_PACKAGE_SUPPORT) if (tgt->erh == NULL || ExpandedRecordIsEmpty(tgt->erh)) return; rec_tupdesc = expanded_record_get_tupdesc(tgt->erh); tuple = expanded_record_get_tuple(tgt->erh); #else if (tgt->tupdesc == NULL) return; rec_tupdesc = tgt->tupdesc; tuple = tgt->tup; #endif for( attNo = 0; attNo < rec_tupdesc->natts; ++attNo ) { char * extval = SPI_getvalue( tuple, rec_tupdesc, attNo + 1 ); #if (PG_VERSION_NUM >= 110000) dbg_send( "v:%s.%s:%s\n", var_name, NameStr( rec_tupdesc->attrs[attNo].attname ), extval ? extval : "NULL" ); #else dbg_send( "v:%s.%s:%s\n", var_name, NameStr( rec_tupdesc->attrs[attNo]->attname ), extval ? extval : "NULL" ); #endif if( extval ) pfree( extval ); } } static void print_recfield(const PLpgSQL_execstate *frame, const char *var_name, int lineno, const PLpgSQL_recfield *tgt) { /* XXX. Shouldn't there be some code here? */ } static void plpgsql_print_var(ErrorContextCallback *frame, const char *var_name, int lineno) { PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; PLpgSQL_variable * generic = NULL; /* Try to find the given variable */ if(( generic = (PLpgSQL_variable*) find_var_by_name( estate, var_name, lineno, NULL )) == NULL ) { dbg_send( "v:%s(%d):Unknown variable (or not in scope)\n", var_name, lineno ); return; } switch( generic->dtype ) { #if (PG_VERSION_NUM >= 110000) case PLPGSQL_DTYPE_PROMISE: #endif case PLPGSQL_DTYPE_VAR: print_var( estate, var_name, lineno, (PLpgSQL_var *) generic ); break; case PLPGSQL_DTYPE_ROW: print_row( estate, var_name, lineno, (PLpgSQL_row *) generic ); break; case PLPGSQL_DTYPE_REC: print_rec( estate, var_name, lineno, (PLpgSQL_rec *) generic ); break; case PLPGSQL_DTYPE_RECFIELD: print_recfield( estate, var_name, lineno, (PLpgSQL_recfield *) generic ); break; #if (PG_VERSION_NUM < 140000) case PLPGSQL_DTYPE_ARRAYELEM: #endif #if (PG_VERSION_NUM < 110000) case PLPGSQL_DTYPE_EXPR: #endif /** * FIXME:: * Hmm.. Shall we print the values for expression/array element? **/ break; } } /* * --------------------------------------------------------------------- * mark_duplicate_names() * * In a PL/pgSQL function/procedure you can declare many variables with * the same name as long as the name is unique within a scope. The PL * compiler co-mingles all variables into a single symbol table without * indicating (at run-time) when a variable comes into scope. * * When we display a variable to the user, we want to show an undecorated * name unless the given variable has duplicate declarations (in nested * scopes). If we detect that a variable has duplicate declarations, we * decorate the name with the line number at which each instance is * declared. This function detects duplicate names and marks duplicates * in our private symbol table. */ static void mark_duplicate_names(const PLpgSQL_execstate *estate, int var_no) { dbg_ctx * dbg_info = (dbg_ctx *)estate->plugin_info; if( dbg_info->symbols[var_no].duplicate_name ) { /* already detected as a duplicate name - just go home */ return; } /* * FIXME: Handle other dtypes here too - for now, we just assume * that all other types have duplicate names */ if( estate->datums[var_no]->dtype != PLPGSQL_DTYPE_VAR ) { dbg_info->symbols[var_no].duplicate_name = TRUE; return; } else { PLpgSQL_var * var = (PLpgSQL_var *)estate->datums[var_no]; char * var_name = var->refname; int i; for( i = 0; i < estate->ndatums; ++i ) { if( i != var_no ) { if( estate->datums[i]->dtype != PLPGSQL_DTYPE_VAR ) continue; var = (PLpgSQL_var *)estate->datums[i]; if( strcmp( var_name, var->refname ) == 0 ) { dbg_info->symbols[var_no].duplicate_name = TRUE; dbg_info->symbols[i].duplicate_name = TRUE; } } } } } /* * --------------------------------------------------------------------- * completeFrame() * * This function ensures that the given execution frame contains * all of the information we need in order to debug it. In particular, * we create an array that extends the frame->datums[] array. * We need to know which variables should be visible to the * debugger client (we hide some of them by convention) and * we need to figure out which names are unique and which * are duplicates. */ static void completeFrame(PLpgSQL_execstate *frame) { dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; PLpgSQL_function * func = dbg_info->func; int i; if( dbg_info->symbols == NULL ) { dbg_info->symbols = (var_value *) palloc( sizeof( var_value ) * func->ndatums ); for( i = 0; i < func->ndatums; ++i ) { dbg_info->symbols[i].isnull = TRUE; /* * Note: in SPL, we hide a few variables from the debugger since * they are internally generated (that is, not declared by * the user). Decide whether this particular variable should * be visible to the debugger client. */ dbg_info->symbols[i].visible = is_datum_visible( frame->datums[i] ); dbg_info->symbols[i].duplicate_name = FALSE; } for( i = 0; i < func->ndatums; ++i ) mark_duplicate_names( frame, i ); dbg_info->argNames = fetchArgNames( func, &dbg_info->argNameCount ); } } /* ------------------------------------------------------------------ * fetchArgNames() * * This function returns the name of each argument for the given * function or procedure. If the function/procedure does not have * named arguments, this function returns NULL * * The argument names are returned as an array of string pointers */ static char ** fetchArgNames(PLpgSQL_function *func, int *nameCount) { #if INCLUDE_PACKAGE_SUPPORT && (PG_VERSION_NUM >= 90600) /* * Argument names are now available in func, and we anyway can't fetch from * pg_proc in case of a nested function. */ *nameCount = func->fn_nallargs; return func->fn_argnames; #else HeapTuple tup; Datum argnamesDatum; bool isNull; Datum *elems; bool *nulls; char **result; int i; if( func->fn_nargs == 0 ) return( NULL ); tup = SearchSysCache( PROCOID, ObjectIdGetDatum( func->fn_oid ), 0, 0, 0 ); if( !HeapTupleIsValid( tup )) elog( ERROR, "cache lookup for function %u failed", func->fn_oid ); argnamesDatum = SysCacheGetAttr( PROCOID, tup, Anum_pg_proc_proargnames, &isNull ); if( isNull ) { ReleaseSysCache( tup ); return( NULL ); } deconstruct_array( DatumGetArrayTypeP( argnamesDatum ), TEXTOID, -1, false, 'i', &elems, &nulls, nameCount ); result = (char **) palloc( sizeof(char *) * (*nameCount)); for( i = 0; i < (*nameCount); i++ ) result[i] = DatumGetCString( DirectFunctionCall1( textout, elems[i] )); ReleaseSysCache( tup ); return( result ); #endif } static char * get_text_val(PLpgSQL_var *var, char **name, char **type) { HeapTuple typeTup; Form_pg_type typeStruct; FmgrInfo finfo_output; char * text_value = NULL; /* Find the output function for this data type */ typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( var->datatype->typoid ), 0, 0, 0 ); if( !HeapTupleIsValid( typeTup )) return( NULL ); typeStruct = (Form_pg_type)GETSTRUCT( typeTup ); /* Now invoke the output function to convert the variable into a null-terminated string */ fmgr_info( typeStruct->typoutput, &finfo_output ); text_value = DatumGetCString( FunctionCall3( &finfo_output, var->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); ReleaseSysCache( typeTup ); if( name ) *name = var->refname; if( type ) *type = var->datatype->typname; return( text_value ); } static Oid plpgsql_get_func_oid(ErrorContextCallback *frame) { PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info; return dbg_info->func->fn_oid; } static void dbg_startup(PLpgSQL_execstate *estate, PLpgSQL_function *func) { if( func == NULL ) { /* * In general, this should never happen, but it seems to in the * case of package constructors */ estate->plugin_info = NULL; return; } if( !breakpointsForFunction( func->fn_oid ) && !per_session_ctx.step_into_next_func) { estate->plugin_info = NULL; return; } initialize_plugin_info(estate, func); } static void initialize_plugin_info(PLpgSQL_execstate *estate, PLpgSQL_function *func) { dbg_ctx * dbg_info; /* Allocate a context structure and record the address in the estate */ estate->plugin_info = dbg_info = (dbg_ctx *) palloc( sizeof( dbg_ctx )); /* * As soon as we hit the first statement, we'll allocate space for each * local variable. For now, we set symbols to NULL so we know to report all * variables the next time we stop... */ dbg_info->symbols = NULL; dbg_info->stepping = FALSE; dbg_info->func = func; /* * The PL interpreter filled in two member of our plugin_funcs * structure for us - we compare error_callback to the callback * in the error_context_stack to make sure that we only deal with * PL/pgSQL (or SPL) stack frames (hokey, but it works). We use * assign_expr when we need to deposit a value in variable. */ dbg_info->error_callback = plugin_funcs.error_callback; dbg_info->assign_expr = plugin_funcs.assign_expr; #if INCLUDE_PACKAGE_SUPPORT /* * Look up the package this function belongs to. * * Inline code blocks have invalid fn_oid. They never belong to packages. */ if (OidIsValid(dbg_info->func->fn_oid)) { /* * Find the namespace in which this function/procedure is defined */ HeapTuple htup; Oid namespaceOid; htup = SearchSysCache(PROCOID, ObjectIdGetDatum(dbg_info->func->fn_oid), 0, 0, 0); if (!HeapTupleIsValid(htup)) elog(ERROR, "cache lookup failed for procedure %d", dbg_info->func->fn_oid); namespaceOid = ((Form_pg_proc)GETSTRUCT(htup))->pronamespace; ReleaseSysCache(htup); /* * Now figure out if this namespace is a package or a schema * * NOTE: we could read the pg_namespace tuple and check pg_namespace.nspparent, * but it's faster to just search for the namespaceOid in the global * package array instead; we have to do that anyway to find the package */ dbg_info->package = plugin_funcs.get_package( namespaceOid ); } else dbg_info->package = InvalidOid; #endif } /* * --------------------------------------------------------------------- * plpgsql_do_deposit() * * This function handles the 'deposit' feature - that is, this function * sets a given PL variable to a new value, supplied by the client. * * do_deposit() is called when you type a new value into a variable in * the local-variables window. * * NOTE: For the convenience of the user, we first assume that the * provided value is an expression. If it doesn't evaluate, * we convert the value into a literal by surrounding it with * single quotes. That may be surprising if you happen to make * a typo, but it will "do the right thing" in most cases. * * Returns true on success, false on failure. */ static bool plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name, int lineno, const char *value) { PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; dbg_ctx *dbg_info = estate->plugin_info; PLpgSQL_datum *target; char *select; PLpgSQL_expr *expr; MemoryContext curContext = CurrentMemoryContext; ResourceOwner curOwner = CurrentResourceOwner; bool retval = false; target = find_datum_by_name(estate, var_name, lineno, NULL); if (!target) return false; /* * Now build a SELECT statement that returns the requested value * * NOTE: we allocate 2 extra bytes for quoting just in case we * need to later (see the retry logic below) */ select = palloc( strlen( "SELECT " ) + strlen( value ) + 2 + 1 ); sprintf( select, "SELECT %s", value ); /* * Note: we must create a dynamically allocated PLpgSQL_expr here - we * can't create one on the stack because exec_assign_expr() * links this expression into a list (active_simple_exprs) and * this expression must survive until the end of the current * transaction so we don't free it out from under spl_plpgsql_xact_cb() */ expr = (PLpgSQL_expr *) palloc0( sizeof( *expr )); #if (PG_VERSION_NUM < 110000) expr->dtype = PLPGSQL_DTYPE_EXPR; expr->dno = -1; #endif expr->query = select; expr->plan = NULL; #if (PG_VERSION_NUM <= 80400) expr->plan_argtypes = NULL; expr->nparams = 0; #endif expr->expr_simple_expr = NULL; BeginInternalSubTransaction( NULL ); MemoryContextSwitchTo( curContext ); PG_TRY(); { if( target ) dbg_info->assign_expr( estate, target, expr ); /* Commit the inner transaction, return to outer xact context */ ReleaseCurrentSubTransaction(); MemoryContextSwitchTo( curContext ); CurrentResourceOwner = curOwner; /* That worked, don't try again */ retval = true; } PG_CATCH(); { MemoryContextSwitchTo( curContext ); FlushErrorState(); /* Abort the inner transaction */ RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo( curContext ); CurrentResourceOwner = curOwner; /* That failed - try again as a literal */ retval = false; } PG_END_TRY(); /* * If the given value is not a valid expression, try converting * the value into a literal by sinqle-quoting it. */ if (!retval) { sprintf( select, "SELECT '%s'", value ); #if (PG_VERSION_NUM < 110000) expr->dtype = PLPGSQL_DTYPE_EXPR; expr->dno = -1; #endif expr->query = select; expr->plan = NULL; expr->expr_simple_expr = NULL; #if (PG_VERSION_NUM <= 80400) expr->plan_argtypes = NULL; expr->nparams = 0; #endif BeginInternalSubTransaction( NULL ); MemoryContextSwitchTo( curContext ); PG_TRY(); { if( target ) dbg_info->assign_expr( estate, target, expr ); /* Commit the inner transaction, return to outer xact context */ ReleaseCurrentSubTransaction(); MemoryContextSwitchTo( curContext ); CurrentResourceOwner = curOwner; retval = true; } PG_CATCH(); { MemoryContextSwitchTo( curContext ); FlushErrorState(); /* Abort the inner transaction */ RollbackAndReleaseCurrentSubTransaction(); MemoryContextSwitchTo( curContext ); CurrentResourceOwner = curOwner; retval = false; } PG_END_TRY(); } pfree( select ); return retval; } /* * --------------------------------------------------------------------- * is_datum_visible() * * This function determines whether the given datum is 'visible' to the * debugger client. We want to hide a few undocumented/internally * generated variables from the user - this is the function that hides * them. We set a flag in the symbols entry for this datum * to indicate whether this variable is hidden or visible - that way, * only have to do the expensive stuff once per invocation. */ static bool is_datum_visible(PLpgSQL_datum *datum) { static const char * hidden_variables[] = { "found", "rowcount", "sqlcode", "sqlerrm", "_found", "_rowcount", }; /* * All of the hidden variables are scalars at the moment so * assume that anything else is visible regardless of name */ if( datum->dtype != PLPGSQL_DTYPE_VAR ) return( TRUE ); else { PLpgSQL_var * var = (PLpgSQL_var *)datum; int i; for( i = 0; i < sizeof( hidden_variables ) / sizeof( hidden_variables[0] ); ++i ) { if( strcmp( var->refname, hidden_variables[i] ) == 0 ) { /* * We found this variable in our list of hidden names - * this variable is *not* visible */ return( FALSE ); } } /* * The SPL pre-processor generates a few variable names for * DMBS.PUTLINE statements - we want to hide those variables too. * The generated variables are of the form 'txtnnn...' where * 'nnn...' is a sequence of one or more digits. */ if( strncmp( var->refname, "txt", 3 ) == 0 ) { int i; /* * Starts with 'txt' - see if the rest of the string is composed * entirely of digits */ for( i = 3; var->refname[i] != '\0'; ++i ) { if( var->refname[i] < '0' || var->refname[i] > '9' ) return( TRUE ); } return( FALSE ); } return( TRUE ); } } /* * --------------------------------------------------------------------- * is_var_visible() * * This function determines whether the given variable is 'visible' to the * debugger client. We hide some variables from the user (see the * is_datum_visible() function for more info). This function is quick - * we do the slow work in is_datum_visible() and simply check the results * here. */ static bool is_var_visible(PLpgSQL_execstate *frame, int var_no) { dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; if (dbg_info->symbols == NULL) completeFrame(frame); return( dbg_info->symbols[var_no].visible ); } /* * plpgsql_send_cur_line() * * This function sends the current position to the debugger client. We * send the function's OID, xmin, cmin, and the current line number * (we're telling the client which line of code we're about to execute). */ static void plpgsql_send_cur_line(ErrorContextCallback *frame) { PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; PLpgSQL_stmt *stmt = estate->err_stmt; dbg_ctx *dbg_info = (dbg_ctx *) estate->plugin_info; PLpgSQL_function *func = dbg_info->func; dbg_send( "%d:%d:%s", func->fn_oid, stmt->lineno+1, #if (PG_VERSION_NUM >= 90200) func->fn_signature #else func->fn_name #endif ); } /* * --------------------------------------------------------------------- * isFirstStmt() * * Returns true if the given statement is the first statement in the * given function. */ static bool isFirstStmt(PLpgSQL_stmt *stmt, PLpgSQL_function *func) { if( stmt == linitial( func->action->body )) return( TRUE ); else return( FALSE ); } /* * --------------------------------------------------------------------- * dbg_newstmt() * * The PL/pgSQL executor calls plpgsql_dbg_newstmt() just before executing each * statement. * * This function is the heart of the debugger. If you're single-stepping, * or you hit a breakpoint, plpgsql_dbg_newstmt() sends a message to the debugger * client indicating the current line and then waits for a command from * the user. * * NOTE: it is very important that this function should impose negligible * overhead when a debugger client is *not* attached. In other words * if you're running PL/pgSQL code without a debugger, you notice no * performance penalty. */ static void dbg_newstmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) { PLpgSQL_execstate * frame = estate; /* * If there's no debugger attached, go home as quickly as possible. */ if( frame->plugin_info == NULL ) return; else { dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; Breakpoint * breakpoint = NULL; eBreakpointScope breakpointScope = 0; /* * The PL compiler marks certain statements as 'invisible' to the * debugger. In particular, the compiler may generate statements * that do not appear in the source code. Such a statement is * marked with a line number of -1: if we're looking at an invisible * statement, just return to the caller. */ if( stmt->lineno == -1 ) return; /* * Now set up an error handler context so we can intercept any * networking errors (errors communicating with the proxy). */ if( sigsetjmp( client_lost.m_savepoint, 1 ) != 0 ) { /* * The connection to the debugger client has slammed shut - * just pretend like there's no debugger attached and return * * NOTE: we no longer have a connection to the debugger proxy - * that means that we cannot interact with the proxy, we * can't wait for another command, nothing. We let the * executor continue execution - anything else will hang * this backend, waiting for a debugger command that will * never arrive. * * If, however, we hit a breakpoint again, we'll stop and * wait for another debugger proxy to connect to us. If * that's not the behavior you're looking for, you can * drop the breakpoint, or call free_function_breakpoints() * here to get rid of all breakpoints in this backend. */ per_session_ctx.client_w = 0; /* No client connection */ dbg_info->stepping = FALSE; /* No longer stepping */ } if(( dbg_info->stepping ) || breakAtThisLine( &breakpoint, &breakpointScope, dbg_info->func->fn_oid, isFirstStmt( stmt, dbg_info->func ) ? -1 : stmt->lineno )) dbg_info->stepping = TRUE; else return; per_session_ctx.step_into_next_func = FALSE; /* We found a breakpoint for this function (or we're stepping into) */ /* Make contact with the debugger client */ if( !attach_to_proxy( breakpoint )) { /* * Can't attach to the proxy, maybe we found a stale breakpoint? * That can happen if you set a global breakpoint on a function, * invoke that function from a client application, debug the target * kill the debugger client, and then re-invoke the function from * the same client application - we will find the stale global * breakpoint on the second invocation. * * We want to remove that breakpoint so that we don't keep trying * to attach to a phantom proxy process. */ if( breakpoint ) BreakpointDelete( breakpointScope, &(breakpoint->key)); /* * In any case, if we don't have a proxy to work with, we can't * do any debugging so give up. */ pfree( frame->plugin_info ); frame->plugin_info = NULL; /* No debugger context */ per_session_ctx.client_w = 0; /* No client connection */ return; } if( stmt->cmd_type == PLPGSQL_STMT_BLOCK ) return; /* * The PL/pgSQL compiler inserts an automatic RETURN statement at the * end of each function (unless the last statement in the function is * already a RETURN). If we run into that statement, we don't really * want to wait for the user to STEP across it. Remember, the user won't * see the RETURN statement in the source-code listing for his function. * * Fortunately, the automatic RETURN statement has a line-number of 0 * so it's easy to spot. */ if( stmt->lineno == 0 ) return; /* * If we're in step mode, tell the debugger client, read a command from the client and * execute the command */ if( dbg_info->stepping ) { /* * Make sure that we have all of the debug info that we need in this stack frame */ completeFrame( frame ); /* * We're in single-step mode (or at a breakpoint) * send the current line number to the debugger client and report any * variable modifications */ if (!plugin_debugger_main_loop()) dbg_info->stepping = FALSE; } } } /* --------------------------------------------------------------------- * datumIsNull() * * determine whether datum is NULL or not. * TODO: consider datatypes other than PLPGSQL_DTYPE_VAR as well */ static bool datumIsNull(PLpgSQL_datum *datum) { switch (datum->dtype) { case PLPGSQL_DTYPE_VAR: { PLpgSQL_var *var = (PLpgSQL_var *) datum; if (var->isnull) return true; } break; /* other data types are not currently handled, we just return true */ case PLPGSQL_DTYPE_REC: case PLPGSQL_DTYPE_ROW: return true; default: return true; } return false; } pldebugger-1.8/plugin_debugger.c000066400000000000000000001404561464645137500170410ustar00rootroot00000000000000/********************************************************************** * plugin_debugger.c - Language-independent parts of debugger * * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. * * Licensed under the Artistic License v2.0, see * https://opensource.org/licenses/artistic-license-2.0 * for full details * **********************************************************************/ #include "postgres.h" #include #include #include #include #include #include #ifdef WIN32 #include #else #include #include #include #endif #include "access/xact.h" #include "lib/stringinfo.h" #include "catalog/pg_proc.h" #include "catalog/pg_type.h" #if (PG_VERSION_NUM >= 130000) #include "common/hashfn.h" #endif #include "parser/parser.h" #include "parser/parse_func.h" #include "globalbp.h" #include "storage/proc.h" /* For MyProc */ #include "storage/procarray.h" /* For BackendPidGetProc */ #include "utils/array.h" #include "utils/builtins.h" #include "utils/syscache.h" #include "miscadmin.h" #include "pldebugger.h" #include "dbgcomm.h" /* Include header for GETSTRUCT */ #if (PG_VERSION_NUM >= 90300) #include "access/htup_details.h" #endif #define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp))) #define TARGET_PROTO_VERSION "1.1" /********************************************************************** * Type and structure definitions **********************************************************************/ /* * eConnectType * * This enum defines the different ways that we can connect to the * debugger proxy. * * CONNECT_AS_SERVER means that we create a socket, bind an address to * to that socket, send a NOTICE to our client application, and wait for * a debugger proxy to attach to us. That's what happens when your * client application sets a local breakpoint and can handle the * NOTICE that we send. * * CONNECT_AS_CLIENT means that a proxy has already created a socket * and is waiting for a target (that's us) to connect to it. We do * this kind of connection stuff when a debugger client sets a global * breakpoint and we happen to blunder into that breakpoint. * * CONNECT_UNKNOWN indicates a problem, we shouldn't ever see this. */ typedef enum { CONNECT_AS_SERVER, /* Open a server socket and wait for a proxy to connect to us */ CONNECT_AS_CLIENT, /* Connect to a waiting proxy (global breakpoints do this) */ CONNECT_UNKNOWN /* Must already be connected */ } eConnectType; /* Global breakpoint data. */ typedef struct { #if (PG_VERSION_NUM >= 90600) int tranche_id; LWLock lock; #else LWLockId lockid; #endif } GlobalBreakpointData; /********************************************************************** * Local (static) variables **********************************************************************/ per_session_ctx_t per_session_ctx; errorHandlerCtx client_lost; static debugger_language_t *debugger_languages[] = { &plpgsql_debugger_lang, #ifdef INCLUDE_PACKAGE_SUPPORT &spl_debugger_lang, #endif NULL }; #if (PG_VERSION_NUM >= 150000) static shmem_request_hook_type prev_shmem_request_hook = NULL; #endif /********************************************************************** * Function declarations **********************************************************************/ void _PG_init( void ); /* initialize this module when we are dynamically loaded */ /********************************************************************** * Local (hidden) function prototypes **********************************************************************/ #if (PG_VERSION_NUM >= 150000) static void pldebugger_shmem_request( void ); #endif static void * writen( int peer, void * src, size_t len ); static bool connectAsServer( void ); static bool connectAsClient( Breakpoint * breakpoint ); static bool handle_socket_error(void); static bool parseBreakpoint( Oid * funcOID, int * lineNumber, char * breakpointString ); static bool addLocalBreakpoint( Oid funcOID, int lineNo ); static void reserveBreakpoints( void ); static debugger_language_t *language_of_frame(ErrorContextCallback *frame); static char * findSource( Oid oid, HeapTuple * tup ); static void do_deposit(ErrorContextCallback *frame, debugger_language_t *lang, char *command); static void send_breakpoints(Oid funcOid); static void send_stack(void); static void select_frame(int frameNo, ErrorContextCallback **frame_p, debugger_language_t **lang_p); /********************************************************************** * Function definitions **********************************************************************/ void _PG_init( void ) { int i; /* Initialize all the per-language hooks. */ for (i = 0; debugger_languages[i] != NULL; i++) debugger_languages[i]->initialize(); #if (PG_VERSION_NUM >= 150000) prev_shmem_request_hook = shmem_request_hook; shmem_request_hook = pldebugger_shmem_request; #else reserveBreakpoints(); dbgcomm_reserve(); #endif } #if (PG_VERSION_NUM >= 150000) static void pldebugger_shmem_request( void ) { if (prev_shmem_request_hook) prev_shmem_request_hook(); reserveBreakpoints(); dbgcomm_reserve(); } #endif /* * CREATE OR REPLACE FUNCTION pldbg_oid_debug( functionOID OID ) RETURNS INTEGER AS 'pldbg_oid_debug' LANGUAGE C; */ PGDLLEXPORT Datum pldbg_oid_debug(PG_FUNCTION_ARGS); PG_FUNCTION_INFO_V1(pldbg_oid_debug); Datum pldbg_oid_debug(PG_FUNCTION_ARGS) { Oid funcOid; HeapTuple tuple; Oid userid; if(( funcOid = PG_GETARG_OID( 0 )) == InvalidOid ) ereport( ERROR, ( errcode( ERRCODE_UNDEFINED_FUNCTION ), errmsg( "no target specified" ))); /* get the owner of the function */ tuple = SearchSysCache(PROCOID, ObjectIdGetDatum(funcOid), 0, 0, 0); if (!HeapTupleIsValid(tuple)) elog(ERROR, "cache lookup failed for function %u", funcOid); userid = ((Form_pg_proc) GETSTRUCT(tuple))->proowner; ReleaseSysCache(tuple); if( !superuser() && (GetUserId() != userid)) ereport( ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg( "must be owner or superuser to create a breakpoint" ))); addLocalBreakpoint( funcOid, -1 ); PG_RETURN_INT32( 0 ); } /* * --------------------------------------------------------------------- * readn() * * This function reads exactly 'len' bytes from the given socket or it * throws an error. readn() will hang until the proper number of bytes * have been read (or an error occurs). * * Note: dst must point to a buffer large enough to hold at least 'len' * bytes. readn() returns dst (for convenience). */ static void * readn( int peer, void * dst, size_t len ) { size_t bytesRemaining = len; char * buffer = (char *)dst; while( bytesRemaining > 0 ) { ssize_t bytesRead = recv( peer, buffer, bytesRemaining, 0 ); if( bytesRead <= 0 && errno != EINTR ) handle_socket_error(); /* Ignore if we didn't receive anything. */ if ( bytesRead > 0 ) { bytesRemaining -= bytesRead; buffer += bytesRead; } } return( dst ); } /* * --------------------------------------------------------------------- * readUInt32() * * Reads a 32-bit unsigned value from the server (and returns it in the host's * byte ordering) */ static uint32 readUInt32( int channel ) { uint32 netVal; readn( channel, &netVal, sizeof( netVal )); return( ntohl( netVal )); } /* * --------------------------------------------------------------------- * dbg_read_str() * * This function reads a counted string from the given stream * Returns a palloc'd, null-terminated string. * * NOTE: the server-side of the debugger uses this function to read a * string from the client side */ char *dbg_read_str( void ) { uint32 len; char *dst; int sock = per_session_ctx.client_r; len = readUInt32( sock ); dst = palloc(len + 1); readn( sock, dst, len ); dst[len] = '\0'; return dst; } /* * --------------------------------------------------------------------- * writen() * * This function writes exactly 'len' bytes to the given socket or it * throws an error. writen() will hang until the proper number of bytes * have been written (or an error occurs). */ static void * writen( int peer, void * src, size_t len ) { size_t bytesRemaining = len; char * buffer = (char *)src; while( bytesRemaining > 0 ) { ssize_t bytesWritten; if(( bytesWritten = send( peer, buffer, bytesRemaining, 0 )) <= 0 ) handle_socket_error(); bytesRemaining -= bytesWritten; buffer += bytesWritten; } return( src ); } /* * --------------------------------------------------------------------- * sendUInt32() * * This function sends a uint32 value (val) to the debugger server. */ static void sendUInt32( int channel, uint32 val ) { uint32 netVal = htonl( val ); writen( channel, &netVal, sizeof( netVal )); } /* * --------------------------------------------------------------------- * dbg_send() * * This function writes a formatted, counted string to the * given stream. The argument list for this function is identical to * the argument list for the fprintf() function - you provide a socket, * a format string, and then some number of arguments whose meanings * are defined by the format string. * * NOTE: the server-side of the debugger uses this function to send * data to the client side. If the connection drops, dbg_send() * will longjmp() back to the debugger top-level so that the * server-side can respond properly. */ void dbg_send( const char *fmt, ... ) { StringInfoData result; char *data; size_t remaining; int sock = per_session_ctx.client_w; if( !sock ) return; initStringInfo(&result); for (;;) { va_list args; #if (PG_VERSION_NUM >= 90400) int needed; va_start(args, fmt); needed = appendStringInfoVA(&result, fmt, args); va_end(args); if (needed == 0) break; enlargeStringInfo(&result, needed); #else bool success; va_start(args, fmt); success = appendStringInfoVA(&result, fmt, args); va_end(args); if (success) break; enlargeStringInfo(&result, result.maxlen); #endif } data = result.data; remaining = strlen(data); sendUInt32(sock, remaining); while( remaining > 0 ) { int written = send( sock, data, remaining, 0 ); if(written < 0) { handle_socket_error(); continue; } remaining -= written; data += written; } pfree(result.data); } /* * --------------------------------------------------------------------- * dbg_send_src() * * dbg_send_src() sends the source code for a function to the client. * * The client caches the source code that we send it and uses xmin/cmin * to ensure the validity of the cache. */ static void dbg_send_src( char * command ) { HeapTuple tup; char *procSrc; Oid targetOid = InvalidOid; /* Initialize to keep compiler happy */ targetOid = atoi( command + 2 ); /* Find the source code for this function */ procSrc = findSource( targetOid, &tup ); /* Found it - now send the source to the client */ dbg_send( "%s", procSrc ); /* Release the process tuple and send a footer to the client so he knows we're finished */ ReleaseSysCache( tup ); } /* * --------------------------------------------------------------------- * findSource() * * This function locates and returns a pointer to a null-terminated string * that contains the source code for the given function (identified by its * OID). * * In addition to returning a pointer to the requested source code, this * function sets *tup to point to a HeapTuple (that you must release when * you are finished with it). */ static char * findSource( Oid oid, HeapTuple * tup ) { bool isNull; *tup = SearchSysCache( PROCOID, ObjectIdGetDatum( oid ), 0, 0, 0 ); if(!HeapTupleIsValid( *tup )) elog( ERROR, "pldebugger: cache lookup for proc %u failed", oid ); return( DatumGetCString( DirectFunctionCall1( textout, SysCacheGetAttr( PROCOID, *tup, Anum_pg_proc_prosrc, &isNull )))); } /* * --------------------------------------------------------------------- * attach_to_proxy() * * This function creates a connection to the debugger client (via the * proxy process). attach_to_proxy() will hang the PostgreSQL backend * until the debugger client completes the connection. * * We start by asking the TCP/IP stack to allocate an unused port, then we * extract the port number from the resulting socket, send the port number to * the client application (by raising a NOTICE), and finally, we wait for the * client to connect. * * We assume that the client application knows the IP address of the PostgreSQL * backend process - if that turns out to be a poor assumption, we can include * the IP address in the notification string that we send to the client application. */ bool attach_to_proxy( Breakpoint * breakpoint ) { bool result; errorHandlerCtx save; if( per_session_ctx.client_w ) { /* We're already connected to a live proxy, just go home */ return( TRUE ); } if( breakpoint == NULL ) { /* * No breakpoint - that implies that we're 'stepping into'. * We had better already have a connection to a proxy here * (how could we be 'stepping into' if we aren't connected * to a proxy?) */ return( FALSE ); } /* * When a networking error is detected, we longjmp() to the client_lost * error handler - that normally points to a location inside of dbg_newstmt() * but we want to handle any network errors that arise while we are * setting up a link to the proxy. So, we save the original client_lost * error handler context and push our own context on to the stack. */ save = client_lost; if( sigsetjmp( client_lost.m_savepoint, 1 ) != 0 ) { client_lost = save; return( FALSE ); } if( breakpoint->data.proxyPort == -1 ) { /* * proxyPort == -1 implies that this is a local breakpoint, * create a server socket and wait for the proxy to contact * us. */ result = connectAsServer(); } else { /* * proxyPort != -1 implies that this is a global breakpoint, * a debugger proxy is already waiting for us at the given * port (on this host), connect to that proxy. */ result = connectAsClient( breakpoint ); } /* * Now restore the original error handler context so that * dbg_newstmt() can handle any future network errors. */ client_lost = save; return( result ); } /* * --------------------------------------------------------------------- * connectAsServer() * * This function creates a socket, asks the TCP/IP stack to bind it to * an unused port, and then waits for a debugger proxy to connect to * that port. We send a NOTICE to our client process (on the other * end of the fe/be connection) to let the client know that it should * fire up a debugger and attach to that port (the NOTICE includes * the port number) */ static bool connectAsServer( void ) { int client_sock; client_sock = dbgcomm_listen_for_proxy(); if (client_sock < 0) { per_session_ctx.client_w = per_session_ctx.client_r = 0; return( FALSE ); } else { per_session_ctx.client_w = client_sock; per_session_ctx.client_r = client_sock; return( TRUE ); } } /* * --------------------------------------------------------------------- * connectAsClient() * * This function connects to a waiting proxy process over the given * port. We got the port number from a global breakpoint (the proxy * stores it's port number in the breakpoint so we'll know how to * find that proxy). */ static bool connectAsClient( Breakpoint * breakpoint ) { int proxySocket; proxySocket = dbgcomm_connect_to_proxy(breakpoint->data.proxyPort); if (proxySocket < 0 ) { /* dbgcomm_connect_to_proxy already logged the reason */ return false; } else { per_session_ctx.client_w = proxySocket; per_session_ctx.client_r = proxySocket; BreakpointBusySession( breakpoint->data.proxyPid ); return true; } } /* * --------------------------------------------------------------------- * parseBreakpoint() * * Given a string that formatted like "funcOID:linenumber", * this function parses out the components and returns them to the * caller. If the string is well-formatted, this function returns * TRUE, otherwise, we return FALSE. */ static bool parseBreakpoint( Oid * funcOID, int * lineNumber, char * breakpointString ) { int a, b; int n; n = sscanf(breakpointString, "%d:%d", &a, &b); if (n == 2) { *funcOID = a; *lineNumber = b; } else return false; return( TRUE ); } /* * --------------------------------------------------------------------- * addLocalBreakpoint() * * This function adds a local breakpoint for the given function and * line number */ static bool addLocalBreakpoint( Oid funcOID, int lineNo ) { Breakpoint breakpoint; breakpoint.key.databaseId = MyProc->databaseId; breakpoint.key.functionId = funcOID; breakpoint.key.lineNumber = lineNo; breakpoint.key.targetPid = MyProc->pid; breakpoint.data.isTmp = FALSE; breakpoint.data.proxyPort = -1; breakpoint.data.proxyPid = -1; return( BreakpointInsert( BP_LOCAL, &breakpoint.key, &breakpoint.data )); } /* * --------------------------------------------------------------------- * setBreakpoint() * * The debugger client can set a local breakpoint at a given * function/procedure and line number by calling this function * (through the debugger proxy process). */ void setBreakpoint( char * command ) { /* * Format is 'b funcOID:lineNumber' */ int lineNo; Oid funcOID; if( parseBreakpoint( &funcOID, &lineNo, command + 2 )) { if( addLocalBreakpoint( funcOID, lineNo )) dbg_send( "%s", "t" ); else dbg_send( "%s", "f" ); } else { dbg_send( "%s", "f" ); } } /* * --------------------------------------------------------------------- * clearBreakpoint() * * This function deletes the breakpoint at the package, * function/procedure, and line number indicated by the * given command. * * For now, we maintain our own private list of breakpoints - * later, we'll use the same list managed by the CREATE/ * DROP BREAKPOINT commands. */ void clearBreakpoint( char * command ) { /* * Format is 'f funcOID:lineNumber' */ int lineNo; Oid funcOID; if( parseBreakpoint( &funcOID, &lineNo, command + 2 )) { Breakpoint breakpoint; breakpoint.key.databaseId = MyProc->databaseId; breakpoint.key.functionId = funcOID; breakpoint.key.lineNumber = lineNo; breakpoint.key.targetPid = MyProc->pid; if( BreakpointDelete( BP_LOCAL, &breakpoint.key )) dbg_send( "t" ); else dbg_send( "f" ); } else { dbg_send( "f" ); } } bool breakAtThisLine( Breakpoint ** dst, eBreakpointScope * scope, Oid funcOid, int lineNumber ) { BreakpointKey key; key.databaseId = MyProc->databaseId; key.functionId = funcOid; key.lineNumber = lineNumber; if( per_session_ctx.step_into_next_func ) { *dst = NULL; *scope = BP_LOCAL; return( TRUE ); } /* * We conduct 3 searches here. * * First, we look for a global breakpoint at this line, targeting our * specific backend process. * * Next, we look for a global breakpoint (at this line) that does * not target a specific backend process. * * Finally, we look for a local breakpoint at this line (implicitly * targeting our specific backend process). * * NOTE: We must do the local-breakpoint search last because, when the * proxy attaches to our process, it marks all of its global * breakpoints as busy (so other potential targets will ignore * those breakpoints) and we copy all of those global breakpoints * into our local breakpoint hash. If the debugger client exits * and the user starts another debugger session, we want to see the * new breakpoints instead of our obsolete local breakpoints (we * don't have a good way to detect that the proxy has disconnected * until it's inconvenient - we have to read-from or write-to the * proxy before we can tell that it's died). */ key.targetPid = MyProc->pid; /* Search for a global breakpoint targeted at our process ID */ if((( *dst = BreakpointLookup( BP_GLOBAL, &key )) != NULL ) && ((*dst)->data.busy == FALSE )) { *scope = BP_GLOBAL; return( TRUE ); } key.targetPid = -1; /* Search for a global breakpoint targeted at any process ID */ if((( *dst = BreakpointLookup( BP_GLOBAL, &key )) != NULL ) && ((*dst)->data.busy == FALSE )) { *scope = BP_GLOBAL; return( TRUE ); } key.targetPid = MyProc->pid; /* Search for a local breakpoint (targeted at our process ID) */ if(( *dst = BreakpointLookup( BP_LOCAL, &key )) != NULL ) { *scope = BP_LOCAL; return( TRUE ); } return( FALSE ); } bool breakpointsForFunction( Oid funcOid ) { if( BreakpointOnId( BP_LOCAL, funcOid ) || BreakpointOnId( BP_GLOBAL, funcOid )) return( TRUE ); else return( FALSE ); } /* --------------------------------------------------------------------- * handle_socket_error() * * when invoked after a socket operation it would check socket operation's * last error status and invoke siglongjmp incase the error is fatal. */ static bool handle_socket_error(void) { int err; bool fatal_err = TRUE; #ifdef WIN32 err = WSAGetLastError(); switch(err) { case WSAEINTR: case WSAEBADF: case WSAEACCES: case WSAEFAULT: case WSAEINVAL: case WSAEMFILE: /* * Windows Sockets definitions of regular Berkeley error constants */ case WSAEWOULDBLOCK: case WSAEINPROGRESS: case WSAEALREADY: case WSAENOTSOCK: case WSAEDESTADDRREQ: case WSAEMSGSIZE: case WSAEPROTOTYPE: case WSAENOPROTOOPT: case WSAEPROTONOSUPPORT: case WSAESOCKTNOSUPPORT: case WSAEOPNOTSUPP: case WSAEPFNOSUPPORT: case WSAEAFNOSUPPORT: case WSAEADDRINUSE: case WSAEADDRNOTAVAIL: case WSAENOBUFS: case WSAEISCONN: case WSAENOTCONN: case WSAETOOMANYREFS: case WSAETIMEDOUT: case WSAELOOP: case WSAENAMETOOLONG: case WSAEHOSTUNREACH: case WSAENOTEMPTY: case WSAEPROCLIM: case WSAEUSERS: case WSAEDQUOT: case WSAESTALE: case WSAEREMOTE: /* * Extended Windows Sockets error constant definitions */ case WSASYSNOTREADY: case WSAVERNOTSUPPORTED: case WSANOTINITIALISED: case WSAEDISCON: case WSAENOMORE: case WSAECANCELLED: case WSAEINVALIDPROCTABLE: case WSAEINVALIDPROVIDER: case WSAEPROVIDERFAILEDINIT: case WSASYSCALLFAILURE: case WSASERVICE_NOT_FOUND: case WSATYPE_NOT_FOUND: case WSA_E_NO_MORE: case WSA_E_CANCELLED: case WSAEREFUSED: break; /* * Server should shut down its socket on these errors. */ case WSAENETDOWN: case WSAENETUNREACH: case WSAENETRESET: case WSAECONNABORTED: case WSAESHUTDOWN: case WSAEHOSTDOWN: case WSAECONNREFUSED: case WSAECONNRESET: fatal_err = TRUE; break; default: ; } if(fatal_err) { LPVOID lpMsgBuf; FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR) &lpMsgBuf,0, NULL ); elog(COMMERROR,"%s", (char *)lpMsgBuf); LocalFree(lpMsgBuf); siglongjmp(client_lost.m_savepoint, 1); } #else err = errno; switch(err) { case EINTR: case ECONNREFUSED: case EPIPE: case ENOTCONN: fatal_err = TRUE; break; case ENOTSOCK: case EAGAIN: case EFAULT: case ENOMEM: case EINVAL: default: break; } if(fatal_err) { if(( err ) && ( err != EPIPE )) elog(COMMERROR, "%s", strerror(err)); siglongjmp(client_lost.m_savepoint, 1); } errno = err; #endif return fatal_err; } /* * Returns true if we continue stepping in this frame. False otherwise. */ bool plugin_debugger_main_loop(void) { ErrorContextCallback *frame; debugger_language_t *lang; /* language of the selected frame */ bool need_more = TRUE; char *command; bool retval = TRUE; /* Initially, set focus on the topmost frame in the stack */ for( frame = error_context_stack; frame; frame = frame->previous ) { /* * ignore unrecognized stack frames. */ lang = language_of_frame(frame); if (lang) break; } if (frame == NULL) { /* * Oops, couldn't find a frame that we recognize in the stack. This * shouldn't happen since we're stopped at a breakpoint. */ elog(WARNING, "could not find PL/pgSQL frame at the top of the stack"); return false; } /* Report the current location */ lang->send_cur_line(frame); /* * Loop through the following chunk of code until we get a command * from the user that would let us execute this PL/pgSQL statement. */ while( need_more ) { /* Wait for a command from the debugger client */ command = dbg_read_str(); /* * The debugger client sent us a null-terminated command string * * Each command starts with a single character and is * followed by set of optional arguments. */ switch( command[0] ) { case PLDBG_CONTINUE: { /* * Continue (stop single-stepping and just run to the next breakpoint) */ retval = false; need_more = FALSE; break; } case PLDBG_SET_BREAKPOINT: { setBreakpoint( command ); break; } case PLDBG_CLEAR_BREAKPOINT: { clearBreakpoint( command ); break; } case PLDBG_PRINT_VAR: { /* * Print value of given variable */ lang->print_var( frame, &command[2], -1 ); break; } case PLDBG_LIST_BREAKPOINTS: { send_breakpoints( lang->get_func_oid(frame) ); break; } case PLDBG_STEP_INTO: { /* * Single-step/step-into */ per_session_ctx.step_into_next_func = TRUE; need_more = FALSE; break; } case PLDBG_STEP_OVER: { /* * Single-step/step-over */ need_more = FALSE; break; } case PLDBG_LIST: { /* * Send source code for given function */ dbg_send_src( command ); break; } case PLDBG_PRINT_STACK: { send_stack(); break; } case PLDBG_SELECT_FRAME: { select_frame(atoi( &command[2] ), &frame, &lang); /* Report the new location */ lang->send_cur_line( frame ); break; } case PLDBG_DEPOSIT: { /* * Deposit a new value into the given variable */ do_deposit(frame, lang, command); break; } case PLDBG_INFO_VARS: { /* * Send list of variables (and their values) */ lang->send_vars( frame ); break; } case PLDBG_RESTART: case PLDBG_STOP: { /* stop the debugging session */ dbg_send( "%s", "t" ); ereport(ERROR, (errcode(ERRCODE_QUERY_CANCELED), errmsg("canceling statement due to user request"))); break; } default: elog(WARNING, "unrecognized message %c", command[0]); } pfree(command); } return retval; } static void do_deposit(ErrorContextCallback *frame, debugger_language_t *lang, char *command) { char *var_name; char *value; char *lineno_s; int lineno; /* command = d:var.line=expr */ var_name = command + 2; value = strchr( var_name, '=' ); /* FIXME: handle quoted identifiers here */ if (!value) { dbg_send( "%s", "f" ); return; } *value = '\0'; value++; lineno_s = strchr( var_name, '.' ); /* FIXME: handle quoted identifiers here */ if (!lineno_s) { dbg_send( "%s", "f" ); return; } *lineno_s = '\0'; lineno_s++; if (strlen(lineno_s) == 0) lineno = -1; else lineno = atoi(lineno_s); if (lang->do_deposit(frame, var_name, lineno, value)) dbg_send( "%s", "t" ); else dbg_send( "%s", "f" ); } /* * --------------------------------------------------------------------- * sendBreakpoints() * * This function sends a list of breakpoints to the proxy process. * * We only send the breakpoints defined in the given frame. * * For now, we maintain our own private list of breakpoints - * later, we'll use the same list managed by the CREATE/ * DROP BREAKPOINT commands. */ static void send_breakpoints(Oid funcOid) { Breakpoint * breakpoint; HASH_SEQ_STATUS scan; BreakpointGetList( BP_GLOBAL, &scan ); while(( breakpoint = (Breakpoint *) hash_seq_search( &scan )) != NULL ) { if(( breakpoint->key.targetPid == -1 ) || ( breakpoint->key.targetPid == MyProc->pid )) if( breakpoint->key.databaseId == MyProc->databaseId ) if( breakpoint->key.functionId == funcOid ) dbg_send( "%d:%d:%s", funcOid, breakpoint->key.lineNumber, "" ); } BreakpointReleaseList( BP_GLOBAL ); BreakpointGetList( BP_LOCAL, &scan ); while(( breakpoint = (Breakpoint *) hash_seq_search( &scan )) != NULL ) { if(( breakpoint->key.targetPid == -1 ) || ( breakpoint->key.targetPid == MyProc->pid )) if( breakpoint->key.databaseId == MyProc->databaseId ) if( breakpoint->key.functionId == funcOid ) dbg_send( "%d:%d:%s", funcOid, breakpoint->key.lineNumber, "" ); } BreakpointReleaseList( BP_LOCAL ); dbg_send( "%s", "" ); /* empty string indicates end of list */ } /* * --------------------------------------------------------------------- * select_frame() * * This function changes the debugger focus to the indicated frame (in the call * stack). Whenever the target stops (at a breakpoint or as the result of a * step/into or step/over), the debugger changes focus to most deeply nested * function in the call stack (because that's the function that's executing). * * You can change the debugger focus to other stack frames - once you do that, * you can examine the source code for that frame, the variable values in that * frame, and the breakpoints in that target. * * The debugger focus remains on the selected frame until you change it or * the target stops at another breakpoint. */ static void select_frame(int frameNo, ErrorContextCallback **frame_p, debugger_language_t **lang_p) { ErrorContextCallback *frame; for( frame = error_context_stack; frame; frame = frame->previous ) { debugger_language_t *lang = language_of_frame(frame); if (!lang) continue; if( frameNo-- == 0 ) { lang->select_frame(frame); *frame_p = frame; *lang_p = lang; } } /* Not found. Keep frame unchanged */ } /* * Returns the debugger_language_t struct representing the language that * this stack frame belongs to. Or NULL if we don't have a handler for it. */ static debugger_language_t * language_of_frame(ErrorContextCallback *frame) { debugger_language_t *lang; int i; for (i = 0; debugger_languages[i] != NULL; i++) { lang = debugger_languages[i]; if (lang->frame_belongs_to_me(frame)) return lang; } return NULL; } /* ------------------------------------------------------------------ * send_stack() * * This function sends the call stack to the debugger client. For * each PL/pgSQL stack frame that we find, we send the function name, * argument names and values, and the current line number (within * that particular invocation). */ static void send_stack( void ) { ErrorContextCallback * entry; for( entry = error_context_stack; entry; entry = entry->previous ) { /* * ignore frames we don't recognize */ debugger_language_t *lang = language_of_frame(entry); if (lang != NULL) lang->send_stack_frame(entry); } dbg_send( "%s", "" ); /* empty string indicates end of list */ } //////////////////////////////////////////////////////////////////////////////// /*------------------------------------------------------------------------------------- * The shared hash table for global breakpoints. It is protected by * breakpointLock *------------------------------------------------------------------------------------- */ static LWLockId breakpointLock; static HTAB * globalBreakpoints = NULL; static HTAB * localBreakpoints = NULL; /*------------------------------------------------------------------------------------- * The size of Breakpoints is determined by globalBreakpointCount (should be a GUC) *------------------------------------------------------------------------------------- */ static int globalBreakpointCount = 20; static Size breakpoint_hash_size; static Size breakcount_hash_size; /*------------------------------------------------------------------------------------- * Another shared hash table which tracks number of breakpoints created * against each entity. * * It is also protected by breakpointLock, thus making operations on Breakpoints * BreakCounts atomic. *------------------------------------------------------------------------------------- */ static HTAB *globalBreakCounts; static HTAB *localBreakCounts; typedef struct BreakCountKey { Oid databaseId; Oid functionId; } BreakCountKey; typedef struct BreakCount { BreakCountKey key; int count; } BreakCount; /*------------------------------------------------------------------------------------- * Prototypes for functions which operate on GlobalBreakCounts. *------------------------------------------------------------------------------------- */ static void initLocalBreakpoints(void); static void initLocalBreakCounts(void); static void breakCountInsert(eBreakpointScope scope, BreakCountKey *key); static void breakCountDelete(eBreakpointScope scope, BreakCountKey *key); static int breakCountLookup(eBreakpointScope scope, BreakCountKey *key, bool *found); static HTAB * getBreakpointHash(eBreakpointScope scope); static HTAB * getBreakCountHash(eBreakpointScope scope); static void reserveBreakpoints( void ) { breakpoint_hash_size = hash_estimate_size(globalBreakpointCount, sizeof(Breakpoint)); breakcount_hash_size = hash_estimate_size(globalBreakpointCount, sizeof(BreakCount)); RequestAddinShmemSpace( add_size( breakpoint_hash_size, breakcount_hash_size )); RequestAddinShmemSpace(sizeof(GlobalBreakpointData)); #if (PG_VERSION_NUM < 90600) RequestAddinLWLocks( 1 ); #endif } static void initializeHashTables(void) { LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); initGlobalBreakpoints(); LWLockRelease(AddinShmemInitLock); initLocalBreakpoints(); initLocalBreakCounts(); } static void initLocalBreakpoints(void) { HASHCTL ctl = {0}; ctl.keysize = sizeof(BreakpointKey); ctl.entrysize = sizeof(Breakpoint); ctl.hash = tag_hash; localBreakpoints = hash_create("Local Breakpoints", 128, &ctl, HASH_ELEM | HASH_FUNCTION); } void initGlobalBreakpoints(void) { bool found; int tableEntries = globalBreakpointCount; GlobalBreakpointData *gbpd; HASHCTL breakpointCtl = {0}; HASHCTL breakcountCtl = {0}; gbpd = ShmemInitStruct("Global Breakpoint Data", sizeof(GlobalBreakpointData), &found); if (gbpd == NULL) elog(ERROR, "out of shared memory"); #if (PG_VERSION_NUM >= 90600) if (!found) { gbpd->tranche_id = LWLockNewTrancheId(); LWLockInitialize(&gbpd->lock, gbpd->tranche_id); } { #if (PG_VERSION_NUM >= 100000) LWLockRegisterTranche(gbpd->tranche_id, "pldebugger"); #else static LWLockTranche tranche; tranche.name = "pldebugger"; tranche.array_base = &gbpd->lock; tranche.array_stride = sizeof(LWLock); LWLockRegisterTranche(gbpd->tranche_id, &tranche); #endif breakpointLock = &gbpd->lock; } #else if (!found) gbpd->lockid = LWLockAssign(); breakpointLock = gbpd->lockid; #endif /* * Now create a shared-memory hash to hold our global breakpoints */ breakpointCtl.keysize = sizeof(BreakpointKey); breakpointCtl.entrysize = sizeof(Breakpoint); breakpointCtl.hash = tag_hash; globalBreakpoints = ShmemInitHash("Global Breakpoints Table", tableEntries, tableEntries, &breakpointCtl, HASH_ELEM | HASH_FUNCTION); if (!globalBreakpoints) elog(FATAL, "could not initialize global breakpoints hash table"); /* * And create a shared-memory hash to hold our global breakpoint counts */ breakcountCtl.keysize = sizeof(BreakCountKey); breakcountCtl.entrysize = sizeof(BreakCount); breakcountCtl.hash = tag_hash; globalBreakCounts = ShmemInitHash("Global BreakCounts Table", tableEntries, tableEntries, &breakcountCtl, HASH_ELEM | HASH_FUNCTION); if (!globalBreakCounts) elog(FATAL, "could not initialize global breakpoints count hash table"); } /* --------------------------------------------------------- * getPLDebuggerLock() * * Returns the lockid of the lock used to protect pldebugger shared memory * structures. The lock is called breakpointLock in this file, but it's * also shared by dbgcommm.c. */ LWLockId getPLDebuggerLock(void) { if( localBreakpoints == NULL ) initializeHashTables(); return breakpointLock; } /* --------------------------------------------------------- * acquireLock() * * This function waits for a lightweight lock that protects * the breakpoint and breakcount hash tables at the given * scope. If scope is BP_GLOBAL, this function locks * breakpointLock. If scope is BP_LOCAL, this function * doesn't lock anything because local breakpoints are, * well, local (clever naming convention, huh?) */ static void acquireLock(eBreakpointScope scope, LWLockMode mode) { if( localBreakpoints == NULL ) initializeHashTables(); if (scope == BP_GLOBAL) LWLockAcquire(breakpointLock, mode); } /* --------------------------------------------------------- * releaseLock() * * This function releases the lightweight lock that protects * the breakpoint and breakcount hash tables at the given * scope. If scope is BP_GLOBAL, this function releases * breakpointLock. If scope is BP_LOCAL, this function * doesn't do anything because local breakpoints are not * protected by a lwlock. */ static void releaseLock(eBreakpointScope scope) { if (scope == BP_GLOBAL) LWLockRelease(breakpointLock); } /* --------------------------------------------------------- * BreakpointLookup() * * lookup the given global breakpoint hash key. Returns an instance * of Breakpoint structure */ Breakpoint * BreakpointLookup(eBreakpointScope scope, BreakpointKey *key) { Breakpoint *entry; bool found; acquireLock(scope, LW_SHARED); entry = (Breakpoint *) hash_search( getBreakpointHash(scope), (void *) key, HASH_FIND, &found); releaseLock(scope); return entry; } /* --------------------------------------------------------- * BreakpointOnId() * * This is where we see the real advantage of the existence of BreakCounts. * It returns true if there is a global breakpoint on the given id, false * otherwise. The hash key of Global breakpoints table is a composition of Oid * and lineno. Therefore lookups on the basis of Oid only are not possible. * With this function however callers can determine whether a breakpoint is * marked on the given entity id with the cost of one lookup only. * * The check is made by looking up id in BreakCounts. */ bool BreakpointOnId(eBreakpointScope scope, Oid funcOid) { bool found = false; BreakCountKey key; key.databaseId = MyProc->databaseId; key.functionId = funcOid; acquireLock(scope, LW_SHARED); breakCountLookup(scope, &key, &found); releaseLock(scope); return found; } /* --------------------------------------------------------- * BreakpointInsert() * * inserts the global breakpoint (brkpnt) in the global breakpoints * hash table against the supplied key. */ bool BreakpointInsert(eBreakpointScope scope, BreakpointKey *key, BreakpointData *data) { Breakpoint *entry; bool found; acquireLock(scope, LW_EXCLUSIVE); entry = (Breakpoint *) hash_search(getBreakpointHash(scope), (void *)key, HASH_ENTER, &found); if(found) { releaseLock(scope); return FALSE; } entry->data = *data; entry->data.busy = FALSE; /* Assume this breakpoint has not been nabbed by a target */ /* register this insert in the count hash table*/ breakCountInsert(scope, ((BreakCountKey *)key)); releaseLock(scope); return( TRUE ); } /* --------------------------------------------------------- * BreakpointInsertOrUpdate() * * inserts the global breakpoint (brkpnt) in the global breakpoints * hash table against the supplied key. */ bool BreakpointInsertOrUpdate(eBreakpointScope scope, BreakpointKey *key, BreakpointData *data) { Breakpoint *entry; bool found; acquireLock(scope, LW_EXCLUSIVE); entry = (Breakpoint *) hash_search(getBreakpointHash(scope), (void *)key, HASH_ENTER, &found); if(found) { entry->data = *data; releaseLock(scope); return FALSE; } entry->data = *data; entry->data.busy = FALSE; /* Assume this breakpoint has not been nabbed by a target */ /* register this insert in the count hash table*/ breakCountInsert(scope, ((BreakCountKey *)key)); releaseLock(scope); return( TRUE ); } /* --------------------------------------------------------- * BreakpointBusySession() * * This function marks all breakpoints that belong to the given * proxy (identified by pid) as 'busy'. When a potential target * runs into a busy breakpoint, that means that the breakpoint * has already been hit by some other target and that other * target is engaged in a conversation with the proxy (in other * words, the debugger proxy and debugger client are busy). * * We also copy all global breakpoints for the given proxy * to the local breakpoints list - that way, the target that's * actually interacting with the debugger client will continue * to hit those breakpoints until the target process ends. * * When that debugging session ends, the debugger proxy calls * BreakpointFreeSession() to let other potential targets know * that the proxy can handle another target. * * FIXME: it might make more sense to simply move all of the * global breakpoints into the local hash instead, then * the debugger client would have to recreate all of * it's global breakpoints before waiting for another * target. */ void BreakpointBusySession(int pid) { HASH_SEQ_STATUS status; Breakpoint *entry; acquireLock(BP_GLOBAL, LW_EXCLUSIVE); hash_seq_init(&status, getBreakpointHash(BP_GLOBAL)); while((entry = (Breakpoint *) hash_seq_search(&status))) { if( entry->data.proxyPid == pid ) { Breakpoint localCopy = *entry; entry->data.busy = TRUE; /* * Now copy the global breakpoint into the * local breakpoint hash so that the target * process will hit it again (other processes * will ignore it) */ localCopy.key.targetPid = MyProc->pid; BreakpointInsertOrUpdate(BP_LOCAL, &localCopy.key, &localCopy.data ); } } releaseLock(BP_GLOBAL); } /* --------------------------------------------------------- * BreakpointFreeSession() * * This function marks all of the breakpoints that belong to * the given proxy (identified by pid) as 'available'. * * See the header comment for BreakpointBusySession() for * more information */ void BreakpointFreeSession(int pid) { HASH_SEQ_STATUS status; Breakpoint *entry; acquireLock(BP_GLOBAL, LW_EXCLUSIVE); hash_seq_init(&status, getBreakpointHash(BP_GLOBAL)); while((entry = (Breakpoint *) hash_seq_search(&status))) { if( entry->data.proxyPid == pid ) entry->data.busy = FALSE; } releaseLock(BP_GLOBAL); } /* ------------------------------------------------------------ * BreakpointDelete() * * delete the given key from the global breakpoints hash table. */ bool BreakpointDelete(eBreakpointScope scope, BreakpointKey *key) { Breakpoint *entry; acquireLock(scope, LW_EXCLUSIVE); entry = (Breakpoint *) hash_search(getBreakpointHash(scope), (void *) key, HASH_REMOVE, NULL); if (entry) breakCountDelete(scope, ((BreakCountKey *)key)); releaseLock(scope); if(entry == NULL) return( FALSE ); else return( TRUE ); } /* ------------------------------------------------------------ * BreakpointGetList() * * This function returns an iterator (*scan) to the caller. * The caller can use this iterator to scan through the * given hash table (either global or local). The caller * must call BreakpointReleaseList() when finished. */ void BreakpointGetList(eBreakpointScope scope, HASH_SEQ_STATUS * scan) { acquireLock(scope, LW_SHARED); hash_seq_init(scan, getBreakpointHash(scope)); } /* ------------------------------------------------------------ * BreakpointReleaseList() * * This function releases the iterator lock returned by an * earlier call to BreakpointGetList(). */ void BreakpointReleaseList(eBreakpointScope scope) { releaseLock(scope); } /* ------------------------------------------------------------ * BreakpointShowAll() * * sequentially traverse the Global breakpoints hash table and * display all the break points via elog(INFO, ...) * * Note: The display format is still not decided. */ void BreakpointShowAll(eBreakpointScope scope) { HASH_SEQ_STATUS status; Breakpoint *entry; BreakCount *count; acquireLock(scope, LW_SHARED); hash_seq_init(&status, getBreakpointHash(scope)); elog(INFO, "BreakpointShowAll - %s", scope == BP_GLOBAL ? "global" : "local" ); while((entry = (Breakpoint *) hash_seq_search(&status))) { elog(INFO, "Database(%d) function(%d) lineNumber(%d) targetPid(%d) proxyPort(%d) proxyPid(%d) busy(%c) tmp(%c)", entry->key.databaseId, entry->key.functionId, entry->key.lineNumber, entry->key.targetPid, entry->data.proxyPort, entry->data.proxyPid, entry->data.busy ? 'T' : 'F', entry->data.isTmp ? 'T' : 'F' ); } elog(INFO, "BreakpointCounts" ); hash_seq_init(&status, getBreakCountHash(scope)); while((count = (BreakCount *) hash_seq_search(&status))) { elog(INFO, "Database(%d) function(%d) count(%d)", count->key.databaseId, count->key.functionId, count->count ); } releaseLock( scope ); } /* ------------------------------------------------------------ * BreakpointCleanupProc() * * sequentially traverse the Global breakpoints hash table and * delete any breakpoints for the given process (identified by * its process ID). */ void BreakpointCleanupProc(int pid) { HASH_SEQ_STATUS status; Breakpoint *entry; /* * NOTE: we don't care about local breakpoints here, only * global breakpoints */ acquireLock(BP_GLOBAL, LW_SHARED); hash_seq_init(&status, getBreakpointHash(BP_GLOBAL)); while((entry = (Breakpoint *) hash_seq_search(&status))) { if( entry->data.proxyPid == pid ) { entry = (Breakpoint *) hash_search(getBreakpointHash(BP_GLOBAL), &entry->key, HASH_REMOVE, NULL); breakCountDelete(BP_GLOBAL, ((BreakCountKey *)&entry->key)); } } releaseLock(BP_GLOBAL); } /* ========================================================================== * Function definitions for BreakCounts hash table * * Note: All the underneath functions assume that the caller has taken care * of all concurrency issues and thus does not do any locking * ========================================================================== */ static void initLocalBreakCounts(void) { HASHCTL ctl = {0}; ctl.keysize = sizeof(BreakCountKey); ctl.entrysize = sizeof(BreakCount); ctl.hash = tag_hash; localBreakCounts = hash_create("Local Breakpoint Count Table", 32, &ctl, HASH_ELEM | HASH_FUNCTION ); if (!globalBreakCounts) elog(FATAL, "could not initialize global breakpoints count hash table"); } /* --------------------------------------------------------- * breakCountInsert() * * should be invoked when a breakpoint is added in Breakpoints */ void breakCountInsert(eBreakpointScope scope, BreakCountKey *key) { BreakCount *entry; bool found; entry = hash_search(getBreakCountHash(scope), key, HASH_ENTER, &found); if (found) entry->count++; else entry->count = 1; } /* --------------------------------------------------------- * breakCountDelete() * * should be invoked when a breakpoint is removed from Breakpoints */ void breakCountDelete(eBreakpointScope scope, BreakCountKey *key) { BreakCount *entry; entry = hash_search(getBreakCountHash(scope), key, HASH_FIND, NULL); if (entry) { entry->count--; /* remove entry only if entry->count is zero */ if (entry->count == 0 ) hash_search(getBreakCountHash(scope), key, HASH_REMOVE, NULL); } } /* --------------------------------------------------------- * breakCountLookup() * */ static int breakCountLookup(eBreakpointScope scope, BreakCountKey *key, bool *found) { BreakCount *entry; entry = hash_search(getBreakCountHash(scope), key, HASH_FIND, found); if (entry) return entry->count; return -1; } /* --------------------------------------------------------- * getBreakpointHash() * * Returns a pointer to the global or local breakpoint hash, * depending on the given scope. */ static HTAB * getBreakpointHash(eBreakpointScope scope ) { if( localBreakpoints == NULL ) initializeHashTables(); if (scope == BP_GLOBAL) return globalBreakpoints; else return localBreakpoints; } /* --------------------------------------------------------- * getBreakCountHash() * * Returns a pointer to the global or local breakcount hash, * depending on the given scope. */ static HTAB * getBreakCountHash(eBreakpointScope scope) { if( localBreakCounts == NULL ) initializeHashTables(); if (scope == BP_GLOBAL) return globalBreakCounts; else return localBreakCounts; } pldebugger-1.8/plugin_debugger.def000066400000000000000000000013611464645137500173440ustar00rootroot00000000000000EXPORTS BreakpointBusySession BreakpointCleanupProc BreakpointDelete BreakpointFreeSession BreakpointGetList BreakpointInsert BreakpointInsertOrUpdate BreakpointLookup BreakpointOnId BreakpointReleaseList BreakpointShowAll dbgcomm_connect_to_target dbgcomm_listen_for_target dbgcomm_accept_target _PG_init pldbg_oid_debug pldbg_abort_target pldbg_attach_to_port pldbg_continue pldbg_create_listener pldbg_deposit_value pldbg_drop_breakpoint pldbg_get_breakpoints pldbg_get_proxy_info pldbg_get_source pldbg_get_stack pldbg_get_variables pldbg_select_frame pldbg_set_breakpoint pldbg_set_global_breakpoint pldbg_step_into pldbg_step_over pldbg_wait_for_breakpoint pldbg_wait_for_target pldebugger-1.8/settings.projinc000066400000000000000000000020041464645137500167430ustar00rootroot00000000000000 0 C:\pgbuild\buildtrees\postgresql-8.3beta3 C:\pgBuild\gettext C:\pgBuild\OpenSSL C:\pgBuild\krb5 pldebugger-1.8/uninstall_pldbgapi.sql000066400000000000000000000031221464645137500201130ustar00rootroot00000000000000-- uninstall_pldbgapi.sql -- -- This script uninstalls the PL/PGSQL debugger API. -- -- Note: this isn't needed on 9.1 and above, as the functions and types are -- packaged in an extension. You can just drop the extension with -- DROP EXTENSION command. This is still needed to uninstall on older -- versions, however. -- -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. -- -- Licensed under the Artistic License v2.0, see -- https://opensource.org/licenses/artistic-license-2.0 -- for full details DROP FUNCTION pldbg_get_target_info(TEXT, "char"); DROP FUNCTION pldbg_wait_for_target(INTEGER); DROP FUNCTION pldbg_wait_for_breakpoint(INTEGER); DROP FUNCTION pldbg_step_over(INTEGER); DROP FUNCTION pldbg_step_into(INTEGER); DROP FUNCTION pldbg_set_global_breakpoint(INTEGER, OID, INTEGER, INTEGER); DROP FUNCTION pldbg_set_breakpoint(INTEGER, OID, INTEGER); DROP FUNCTION pldbg_select_frame(INTEGER, INTEGER); DROP FUNCTION pldbg_get_variables(INTEGER); DROP FUNCTION pldbg_get_proxy_info(); DROP FUNCTION pldbg_get_stack(INTEGER); DROP FUNCTION pldbg_get_source(INTEGER, OID); DROP FUNCTION pldbg_get_breakpoints(INTEGER); DROP FUNCTION pldbg_drop_breakpoint(INTEGER, OID, INTEGER); DROP FUNCTION pldbg_deposit_value(INTEGER, TEXT, INTEGER, TEXT); DROP FUNCTION pldbg_create_listener(); DROP FUNCTION pldbg_continue(INTEGER); DROP FUNCTION pldbg_attach_to_port(INTEGER); DROP FUNCTION pldbg_abort_target(INTEGER); DROP FUNCTION pldbg_oid_debug(OID); DROP FUNCTION plpgsql_oid_debug(OID); DROP TYPE proxyInfo; DROP TYPE var; DROP TYPE targetinfo; DROP TYPE frame; DROP TYPE breakpoint;