├── .github └── README.md ├── .gitignore ├── Makefile ├── README-pldebugger.md ├── dbgcomm.c ├── dbgcomm.h ├── globalbp.h ├── pldbgapi--1.0--1.1.sql ├── pldbgapi--1.1.sql ├── pldbgapi--unpackaged--1.1.sql ├── pldbgapi.c ├── pldbgapi.control ├── pldebugger.h ├── pldebugger.proj ├── plpgsql_debugger.c ├── plugin_debugger.c ├── plugin_debugger.def ├── settings.projinc └── uninstall_pldbgapi.sql /.github/README.md: -------------------------------------------------------------------------------- 1 | ../README-pldebugger.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.so 3 | *.dll -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ## 3 | ## This Makefile builds plugin_debugger.so. It consists of a PL/pgSQL 4 | ## interpreter plugin, and a set of functions that form an SQL interface 5 | ## to the PL/pgSQL debugger. 6 | ## 7 | ## Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 8 | ## 9 | ## Licensed under the Artistic License v2.0, see 10 | ## https://opensource.org/licenses/artistic-license-2.0 11 | ## for full details 12 | 13 | EXTENSION = pldbgapi 14 | MODULE_big = plugin_debugger 15 | 16 | OBJS = plpgsql_debugger.o plugin_debugger.o dbgcomm.o pldbgapi.o 17 | ifdef INCLUDE_PACKAGE_SUPPORT 18 | OBJS += spl_debugger.o 19 | endif 20 | DATA = pldbgapi--1.1.sql pldbgapi--unpackaged--1.1.sql pldbgapi--1.0--1.1.sql 21 | DOCS = README-pldebugger.md 22 | 23 | # PGXS build needs PostgreSQL 9.2 or later. Earlier versions didn't install 24 | # plpgsql.h, so you needed the full source tree to access it. 25 | ifdef USE_PGXS 26 | PG_CONFIG = pg_config 27 | PGXS := $(shell $(PG_CONFIG) --pgxs) 28 | include $(PGXS) 29 | else 30 | subdir = contrib/pldebugger 31 | top_builddir = ../.. 32 | include $(top_builddir)/src/Makefile.global 33 | include $(top_srcdir)/contrib/contrib-global.mk 34 | endif 35 | 36 | # plpgsql_debugger.c needs plpgsql.h. Beginning with server version 9.2, 37 | # it is installed into include/server, but when building without pgxs, 38 | # with the pldebugger directory being directly in the server source tree's 39 | # contrib directory, we need to tell the compiler where to find it. 40 | plpgsql_debugger.o: CFLAGS += -I$(top_builddir)/src/pl/plpgsql/src 41 | plpgsql_debugger.bc: CPPFLAGS += -I$(top_srcdir)/src/pl/plpgsql/src -I$(top_builddir)/src/pl/plpgsql/src 42 | 43 | ################################################################################ 44 | ## If we're building against EnterpriseDB's Advanced Server, also build a 45 | ## debugger module for the SPL language. It's pretty much the same as the 46 | ## PL/pgSQL one, but the structs have some extra fields and are thus not 47 | ## binary-compatible. We make a copy of the .c file, and pass the 48 | ## INCLUDE_PACKAGE_SUPPORT=1 flag to compile it against SPL instead of PL/pgSQL. 49 | ## 50 | ## To make debugging the debugger itself simpler, all the functions are 51 | ## mechanically renamed from plpgsql_* to spl_*. 52 | ## 53 | ## To enable this, you need to run make as "make INCLUDE_PACKAGE_SUPPORT=1" 54 | ## 55 | ifdef INCLUDE_PACKAGE_SUPPORT 56 | spl_debugger.c: plpgsql_debugger.c 57 | sed -e 's/plpgsql_/spl_/g' $(module_srcdir)plpgsql_debugger.c > spl_debugger.c 58 | 59 | spl_debugger.o: CFLAGS += -DINCLUDE_PACKAGE_SUPPORT=1 -I$(top_builddir)/src/pl/edb-spl/src 60 | spl_debugger.bc: CPPFLAGS += -I$(top_srcdir)/src/pl/plpgsql/src -I$(top_builddir)/src/pl/plpgsql/src 61 | 62 | # There's some tiny differences in plugin_debugger.c, if we're including SPL 63 | # language. Pass the INCLUDE_PACKAGE_SUPPORT flag to plugin_debugger.c too. 64 | plugin_debugger.o: CFLAGS += -DINCLUDE_PACKAGE_SUPPORT=1 65 | endif 66 | -------------------------------------------------------------------------------- /README-pldebugger.md: -------------------------------------------------------------------------------- 1 | # PostgreSQL pl/pgsql Debugger API 2 | 3 | This module is a set of shared libraries which implement an API for debugging 4 | pl/pgsql functions on PostgreSQL 8.4 and above. The 5 | [pgAdmin project](http://www.pgadmin.org/) provides a client user interface. 6 | 7 | ## Installation 8 | 9 | - Copy this directory to contrib/ in your PostgreSQL source tree. 10 | 11 | - Run `make && make install` 12 | 13 | - Edit your postgresql.conf file, and modify the shared_preload_libraries config 14 | option to look like: 15 | 16 | `shared_preload_libraries = '$libdir/plugin_debugger'` 17 | 18 | - Restart PostgreSQL for the new setting to take effect. 19 | 20 | - Run the following command in the database or databases that you wish to 21 | debug functions in: 22 | 23 | `CREATE EXTENSION pldbgapi;` 24 | 25 | (on server versions older than 9.1, you must instead run the pldbgapi--1.1.sql 26 | script directly using psql). 27 | 28 | ## Usage 29 | 30 | Connect pgAdmin to the database containing the functions you wish to debug. 31 | Right-click the function to debug, and select Debugging->Debug to execute and 32 | debug the function immediately, or select Debugging->Set Global Breakpoint to 33 | set a breakpoint on the function. This will cause the debugger to wait for 34 | another session (such as a backend servicing a web app) to execute the function 35 | and allow you to debug in-context. 36 | 37 | For further information, please see the pgAdmin documentation. 38 | 39 | ## Troubleshooting 40 | 41 | The majority of problems we've encountered with the plugin are caused by 42 | failing to add (or incorrectly adding) the debugger plugin library to the 43 | shared_preload_libraries configuration directive in postgresql.conf (following 44 | which, the server *must* be restarted). This will prevent global breakpoints 45 | working on all platforms, and on some (notably Windows) may prevent the 46 | pldbgapi.sql script from executing correctly. 47 | 48 | ## Architecture 49 | 50 | The debugger consists of three parts: 51 | 52 | 1. The client. This is typically a GUI displays the source code, current 53 | stack frame, variables etc, and allows the user to set breakpoints and 54 | step throught the code. The client can reside on a different host than 55 | the database server. 56 | 57 | 2. The target backend. This is the backend that runs the code being debugged. 58 | The plugin_debugger.so library must be loaded into the target backend. 59 | 60 | 3. Debugging proxy. This is another backend process that the client is 61 | connected to. The API functions, pldbg_* in pldbgapi.so library, are 62 | run in this backend. 63 | 64 | The client is to connected to the debugging proxy using a regular libpq 65 | connection. When a debugging session is active, the proxy is connected 66 | to the target via a socket. The protocol between the proxy and the target 67 | backend is not visible to others, and is subject to change. The pldbg_* 68 | API functions form the public interface to the debugging facility. 69 | 70 | ``` 71 | debugger client *------ libpq --------* Proxy backend 72 | (pgAdmin) * 73 | | 74 | pldebugger socket connection 75 | | 76 | * 77 | application client *----- libpq -------* Target backend 78 | ``` 79 | 80 | ## Licence 81 | 82 | The pl/pgsql debugger API is released under the 83 | [Artistic Licence v2.0](https://opensource.org/licenses/artistic-license-2.0). 84 | 85 | Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 86 | 87 | 88 | ## Contact 89 | 90 | For support, please email the pgAdmin support mailing list. See 91 | [http://www.pgadmin.org/support/](http://www.pgadmin.org/support/) for more 92 | details. 93 | -------------------------------------------------------------------------------- /dbgcomm.c: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * dbgcomm.c 3 | * 4 | * This file contains some helper functions for the proxy - target 5 | * communication. 6 | * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 7 | * 8 | * Licensed under the Artistic License v2.0, see 9 | * https://opensource.org/licenses/artistic-license-2.0 10 | * for full details 11 | * 12 | **********************************************************************/ 13 | 14 | #include "postgres.h" 15 | 16 | #include 17 | #include 18 | #ifdef HAVE_SYS_SELECT_H 19 | #include 20 | #endif 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "miscadmin.h" 28 | #if (PG_VERSION_NUM < 170000) 29 | #include "storage/backendid.h" 30 | #endif 31 | #include "storage/lwlock.h" 32 | #include "storage/pmsignal.h" 33 | #include "storage/shmem.h" 34 | #include "storage/sinvaladt.h" 35 | 36 | #include "dbgcomm.h" 37 | #include "pldebugger.h" 38 | 39 | #if (PG_VERSION_NUM < 90200) 40 | /* before 9.2, PostmasterIsAlive() had one parameter */ 41 | #define PostmasterIsAlive() PostmasterIsAlive(false) 42 | #endif 43 | 44 | /* 45 | * Shared memory structure. This is used for authenticating debugger 46 | * connections. Each backend has a dedicated slot. 47 | * 48 | * Whenever a target backend is waiting for a proxy to connect to it 49 | * (pldbg_oid_debug()), or trying to connect to a proxy (when it hits a 50 | * global breakpoint), it advertises the connection attempt in shared memory. 51 | * Each backend has a slot of its own. 52 | * 53 | * When the proxy initiates the connection and target backend listens 54 | * (pldbg_oid_debug()), the backend first sets its status to 55 | * LISTENING_FOR_PROXY, and the port it's listening on in 'port'. When the 56 | * proxy wants to connect to it, it changes the status to PROXY_CONNECTING 57 | * and sets the port to the port it's connecting from. When the target backend 58 | * accept()s the connection, it checks that the remote port of the connection 59 | * matches the one in the slot. This makes the communication secure, because 60 | * only a legitimate proxy backend can access shared memory. 61 | * 62 | * Target backend connecting to a proxy (when a global breakpoint is hit) works 63 | * similarly, except that the LISTENING step is not needed. The backend sets 64 | * the port it's connecting from in its slot's port field, and connects. 65 | * The proxy accept()s the connection, and scans all the slots for a match 66 | * on the port number the connection came from. If it finds the port number 67 | * in one of the slots, the connection came from a legitimate target backend. 68 | */ 69 | #define DBGCOMM_IDLE 0 70 | #define DBGCOMM_LISTENING_FOR_PROXY 1 /* target is listening for a proxy */ 71 | #define DBGCOMM_PROXY_CONNECTING 2 /* proxy is connecting to our port */ 72 | #define DBGCOMM_CONNECTING_TO_PROXY 3 /* target is connecting to a proxy */ 73 | 74 | typedef struct 75 | { 76 | BackendId backendid; 77 | int status; 78 | int pid; 79 | int port; 80 | } dbgcomm_target_slot_t; 81 | 82 | static dbgcomm_target_slot_t *dbgcomm_slots = NULL; 83 | 84 | /* 85 | * Each in-progress connection attempt between proxy and target require 86 | * a slot. 50 should be plenty. 87 | */ 88 | #define NumTargetSlots 50 89 | 90 | /********************************************************************** 91 | * Prototypes for static functions 92 | **********************************************************************/ 93 | static void dbgcomm_init(void); 94 | static uint32 resolveHostName(const char *hostName); 95 | static int findFreeTargetSlot(void); 96 | static int findTargetSlot(BackendId backendid); 97 | 98 | /********************************************************************** 99 | * Initialization routines 100 | **********************************************************************/ 101 | 102 | /* 103 | * Reserves the right amount of shared memory, when the library is 104 | * preloaded by shared_preload_libraries. 105 | */ 106 | void 107 | dbgcomm_reserve(void) 108 | { 109 | RequestAddinShmemSpace(sizeof(dbgcomm_target_slot_t) * NumTargetSlots); 110 | } 111 | 112 | /* 113 | * Initialize slots in shared memory. 114 | */ 115 | static void 116 | dbgcomm_init(void) 117 | { 118 | bool found; 119 | 120 | if (dbgcomm_slots) 121 | return; 122 | 123 | LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); 124 | dbgcomm_slots = ShmemInitStruct("Debugger Connection slots", sizeof(dbgcomm_target_slot_t) * NumTargetSlots, &found); 125 | if (dbgcomm_slots == NULL) 126 | elog(ERROR, "out of shared memory"); 127 | 128 | if (!found) 129 | { 130 | int i; 131 | for (i = 0; i < NumTargetSlots; i++) 132 | { 133 | dbgcomm_slots[i].backendid = InvalidBackendId; 134 | dbgcomm_slots[i].status = DBGCOMM_IDLE; 135 | } 136 | } 137 | LWLockRelease(getPLDebuggerLock()); 138 | } 139 | 140 | 141 | /********************************************************************** 142 | * Routines called by debugging target 143 | * 144 | * These routines use ereport(COMMERROR, ...) for errors, so that they 145 | * are logged but not sent to the client, and don't abort the 146 | * transaction. 147 | * 148 | **********************************************************************/ 149 | 150 | /* 151 | * dbcomm_connect_to_proxy 152 | * 153 | * This does socket() + connect(), to connect to a listener. The connection 154 | * is authenticated. Returns the file descriptor of the open socket. 155 | * 156 | * We assume that the proxyPort came from a breakpoint or some other reliable 157 | * source, so that we don't allow connecting to any random port in the system. 158 | */ 159 | int 160 | dbgcomm_connect_to_proxy(int proxyPort) 161 | { 162 | int sockfd; 163 | struct sockaddr_in remoteaddr = {0}; 164 | struct sockaddr_in localaddr = {0}; 165 | socklen_t addrlen = sizeof( remoteaddr ); 166 | int reuse_addr_flag = 1; 167 | int slot; 168 | 169 | dbgcomm_init(); 170 | 171 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 172 | if (sockfd < 0) 173 | { 174 | ereport(COMMERROR, 175 | (errcode_for_socket_access(), 176 | errmsg("could not create socket for connecting to proxy: %m"))); 177 | return -1; 178 | } 179 | /* Sockets seem to be non-blocking by default on Windows.. */ 180 | if (!pg_set_block(sockfd)) 181 | { 182 | closesocket(sockfd); 183 | ereport(COMMERROR, 184 | (errmsg("could not set socket to blocking mode: %m"))); 185 | return -1; 186 | } 187 | 188 | /* 189 | * We have to bind the socket before connecting, so that we know the 190 | * local port number it will use. We have to store it in shared memory 191 | * before connecting, so that the target knows the connection is legit. 192 | */ 193 | localaddr.sin_family = AF_INET; 194 | localaddr.sin_port = htons( 0 ); 195 | localaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); 196 | 197 | setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 198 | (const char *) &reuse_addr_flag, sizeof(reuse_addr_flag)); 199 | 200 | if (bind(sockfd, (struct sockaddr *) &localaddr, sizeof(localaddr)) < 0) 201 | { 202 | ereport(COMMERROR, 203 | (errcode_for_socket_access(), 204 | errmsg("could not bind local port: %m"))); 205 | return -1; 206 | } 207 | /* Get the port number selected by the TCP/IP stack */ 208 | getsockname(sockfd, (struct sockaddr *) &localaddr, &addrlen); 209 | 210 | LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); 211 | slot = findFreeTargetSlot(); 212 | if (slot < 0) 213 | { 214 | closesocket(sockfd); 215 | LWLockRelease(getPLDebuggerLock()); 216 | ereport(COMMERROR, 217 | (errcode_for_socket_access(), 218 | errmsg("could not find a free target slot"))); 219 | return -1; 220 | } 221 | dbgcomm_slots[slot].port = ntohs(localaddr.sin_port); 222 | dbgcomm_slots[slot].status = DBGCOMM_CONNECTING_TO_PROXY; 223 | dbgcomm_slots[slot].backendid = MyBackendId; 224 | dbgcomm_slots[slot].pid = MyProcPid; 225 | LWLockRelease(getPLDebuggerLock()); 226 | 227 | remoteaddr.sin_family = AF_INET; 228 | remoteaddr.sin_port = htons(proxyPort); 229 | remoteaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); 230 | 231 | /* Now connect to the other end. */ 232 | if (connect(sockfd, (struct sockaddr *) &remoteaddr, 233 | sizeof(remoteaddr)) < 0) 234 | { 235 | ereport(COMMERROR, 236 | (errcode_for_socket_access(), 237 | errmsg("could not connect to debugging proxy at port %d: %m", proxyPort))); 238 | /* 239 | * Reset our entry in the array. On success, this will be done by 240 | * the proxy. 241 | */ 242 | LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); 243 | dbgcomm_slots[slot].status = DBGCOMM_IDLE; 244 | dbgcomm_slots[slot].backendid = InvalidBackendId; 245 | dbgcomm_slots[slot].port = 0; 246 | LWLockRelease(getPLDebuggerLock()); 247 | return -1; 248 | } 249 | 250 | return sockfd; 251 | } 252 | 253 | /* 254 | * dbcomm_connect_to_proxy 255 | * 256 | * This does listen() + accept(), to wait for a proxy to connect to us. 257 | */ 258 | int 259 | dbgcomm_listen_for_proxy(void) 260 | { 261 | struct sockaddr_in remoteaddr = {0}; 262 | struct sockaddr_in localaddr = {0}; 263 | socklen_t addrlen = sizeof( remoteaddr ); 264 | int sockfd; 265 | int serverSocket; 266 | int localport; 267 | bool done; 268 | int slot; 269 | 270 | dbgcomm_init(); 271 | 272 | sockfd = socket( AF_INET, SOCK_STREAM, 0 ); 273 | if (sockfd < 0) 274 | { 275 | ereport(COMMERROR, 276 | (errcode_for_socket_access(), 277 | errmsg("could not create socket for connecting to proxy: %m"))); 278 | return -1; 279 | } 280 | /* Sockets seem to be non-blocking by default on Windows.. */ 281 | if (!pg_set_block(sockfd)) 282 | { 283 | closesocket(sockfd); 284 | ereport(COMMERROR, 285 | (errmsg("could not set socket to blocking mode: %m"))); 286 | return -1; 287 | } 288 | 289 | /* Bind the listener socket to any available port */ 290 | localaddr.sin_family = AF_INET; 291 | localaddr.sin_port = htons( 0 ); 292 | localaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); 293 | if (bind( sockfd, (struct sockaddr *) &localaddr, sizeof(localaddr)) < 0) 294 | { 295 | ereport(COMMERROR, 296 | (errcode_for_socket_access(), 297 | errmsg("could not bind socket for listening for proxy: %m"))); 298 | return -1; 299 | } 300 | 301 | /* Get the port number selected by the TCP/IP stack */ 302 | getsockname(sockfd, (struct sockaddr *) &localaddr, &addrlen); 303 | localport = ntohs(localaddr.sin_port); 304 | 305 | /* Get ready to wait for a client. */ 306 | if (listen(sockfd, 2) < 0) 307 | { 308 | ereport(COMMERROR, 309 | (errcode_for_socket_access(), 310 | errmsg("could not listen() for proxy: %m"))); 311 | return -1; 312 | } 313 | 314 | LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); 315 | slot = findFreeTargetSlot(); 316 | if (slot < 0) 317 | { 318 | closesocket(sockfd); 319 | LWLockRelease(getPLDebuggerLock()); 320 | ereport(COMMERROR, 321 | (errcode_for_socket_access(), 322 | errmsg("could not find a free target slot"))); 323 | return -1; 324 | } 325 | dbgcomm_slots[slot].port = localport; 326 | dbgcomm_slots[slot].status = DBGCOMM_LISTENING_FOR_PROXY; 327 | dbgcomm_slots[slot].backendid = MyBackendId; 328 | dbgcomm_slots[slot].pid = MyProcPid; 329 | LWLockRelease(getPLDebuggerLock()); 330 | 331 | /* Notify the client application that this backend is waiting for a proxy. */ 332 | elog(NOTICE, "PLDBGBREAK:%d", MyBackendId); 333 | 334 | /* wait for the other end to connect to us */ 335 | done = false; 336 | while (!done) 337 | { 338 | serverSocket = accept(sockfd, (struct sockaddr *) &remoteaddr, &addrlen); 339 | if (serverSocket < 0) 340 | ereport(ERROR, 341 | (errmsg("could not accept connection from debugging proxy"))); 342 | 343 | /* 344 | * Authenticate the connection. We do this by checking that the remote 345 | * end's port number matches what's posted in the shared memory slot. 346 | */ 347 | LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); 348 | if (dbgcomm_slots[slot].status == DBGCOMM_PROXY_CONNECTING && 349 | dbgcomm_slots[slot].port == ntohs(remoteaddr.sin_port)) 350 | { 351 | dbgcomm_slots[slot].backendid = InvalidBackendId; 352 | dbgcomm_slots[slot].status = DBGCOMM_IDLE; 353 | done = true; 354 | } 355 | else 356 | closesocket(serverSocket); 357 | LWLockRelease(getPLDebuggerLock()); 358 | } 359 | 360 | closesocket(sockfd); 361 | 362 | return serverSocket; 363 | } 364 | 365 | /********************************************************************** 366 | * Routines called by debugging proxy 367 | **********************************************************************/ 368 | 369 | /* 370 | * dbgcomm_connect_to_target 371 | * 372 | * Connect to given target backend that's waiting for us. Returns a socket 373 | * that is open for communication. Uses ereport(ERROR) on error. 374 | */ 375 | int 376 | dbgcomm_connect_to_target(BackendId targetBackend) 377 | { 378 | int sockfd; 379 | struct sockaddr_in remoteaddr = {0}; 380 | struct sockaddr_in localaddr = {0}; 381 | socklen_t addrlen = sizeof( remoteaddr ); 382 | int reuse_addr_flag = 1; 383 | int localport; 384 | int remoteport; 385 | int slot; 386 | 387 | dbgcomm_init(); 388 | 389 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 390 | if (sockfd < 0) 391 | ereport(ERROR, 392 | (errcode_for_socket_access(), 393 | errmsg("could not create socket for connecting to target: %m"))); 394 | /* Sockets seem to be non-blocking by default on Windows.. */ 395 | if (!pg_set_block(sockfd)) 396 | { 397 | int save_errno = errno; 398 | closesocket(sockfd); 399 | errno = save_errno; 400 | ereport(ERROR, 401 | (errmsg("could not set socket to blocking mode: %m"))); 402 | } 403 | 404 | /* 405 | * We have to bind the socket before connecting, so that we know the 406 | * local port number it will use. We have to store it in shared memory 407 | * before connecting, so that the target knows the connection is legit. 408 | */ 409 | localaddr.sin_family = AF_INET; 410 | localaddr.sin_port = htons( 0 ); 411 | localaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); 412 | 413 | setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 414 | (const char *) &reuse_addr_flag, sizeof(reuse_addr_flag)); 415 | 416 | if (bind(sockfd, (struct sockaddr *) &localaddr, sizeof(localaddr)) < 0) 417 | elog(ERROR, "pl_debugger: could not bind local port: %m"); 418 | 419 | /* Get the port number selected by the TCP/IP stack */ 420 | getsockname(sockfd, (struct sockaddr *) &localaddr, &addrlen); 421 | localport = ntohs(localaddr.sin_port); 422 | 423 | /* 424 | * Find the target backend's slot. Check which port it's listening on, and 425 | * let it know we're connecting to it from this port. 426 | */ 427 | LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); 428 | slot = findTargetSlot(targetBackend); 429 | if (slot < 0 || dbgcomm_slots[slot].status != DBGCOMM_LISTENING_FOR_PROXY) 430 | { 431 | closesocket(sockfd); 432 | ereport(ERROR, 433 | (errmsg("target backend is not listening for a connection"))); 434 | } 435 | remoteport = dbgcomm_slots[slot].port; 436 | dbgcomm_slots[slot].port = localport; 437 | dbgcomm_slots[slot].status = DBGCOMM_PROXY_CONNECTING; 438 | LWLockRelease(getPLDebuggerLock()); 439 | 440 | /* Now connect to the other end. */ 441 | remoteaddr.sin_family = AF_INET; 442 | remoteaddr.sin_port = htons(remoteport); 443 | remoteaddr.sin_addr.s_addr = resolveHostName( "127.0.0.1" ); 444 | if (connect(sockfd, (struct sockaddr *) &remoteaddr, 445 | sizeof(remoteaddr)) < 0) 446 | { 447 | ereport(ERROR, 448 | (errmsg("could not connect to target backend: %m"))); 449 | /* XXX: should we do something about the slot on error? */ 450 | } 451 | 452 | return sockfd; 453 | } 454 | 455 | /* 456 | * dbgcomm_accept_target 457 | * 458 | * Waits for one connection from a target backend to the given socket. Returns 459 | * a socket that is open for communication. Uses ereport(ERROR) on error. 460 | */ 461 | int 462 | dbgcomm_accept_target(int sockfd, int *targetPid) 463 | { 464 | int serverSocket; 465 | int i; 466 | struct sockaddr_in remoteaddr = {0}; 467 | socklen_t addrlen = sizeof(remoteaddr); 468 | 469 | dbgcomm_init(); 470 | 471 | /* wait for the target to connect to us */ 472 | for (;;) 473 | { 474 | fd_set rmask; 475 | int rc; 476 | struct timeval timeout; 477 | 478 | /* Check for query cancel or termination request */ 479 | CHECK_FOR_INTERRUPTS(); 480 | if (!PostmasterIsAlive()) 481 | { 482 | /* Emergency bailout if postmaster has died. */ 483 | ereport(FATAL, 484 | (errmsg("canceling debugging session because postmaster died"))); 485 | } 486 | 487 | FD_ZERO(&rmask); 488 | FD_SET(sockfd, &rmask); 489 | 490 | /* 491 | * Wake up every 1 second to check if we've been killed or 492 | * postmaster has died. 493 | */ 494 | timeout.tv_sec = 1; 495 | timeout.tv_usec = 0; 496 | 497 | rc = select(sockfd + 1, &rmask, NULL, NULL, &timeout); 498 | if (rc < 0) 499 | { 500 | if (errno == EINTR) 501 | continue; 502 | /* anything else is an error */ 503 | ereport(ERROR, 504 | (errmsg("select() failed while waiting for target: %m"))); 505 | } 506 | if (rc == 0) 507 | { 508 | /* Timeout expired. */ 509 | continue; 510 | } 511 | if (!FD_ISSET(sockfd, &rmask)) 512 | continue; 513 | 514 | serverSocket = accept(sockfd, (struct sockaddr *) &remoteaddr, &addrlen); 515 | if (serverSocket < 0) 516 | ereport(ERROR, 517 | (errmsg("could not accept connection from debugging target: %m"))); 518 | 519 | /* 520 | * Authenticate the connection. We do this by checking that the remote 521 | * end's port number is listed in a slot in shared memory. 522 | */ 523 | LWLockAcquire(getPLDebuggerLock(), LW_EXCLUSIVE); 524 | for (i = 0; i < NumTargetSlots; i++) 525 | { 526 | if (dbgcomm_slots[i].status == DBGCOMM_CONNECTING_TO_PROXY && 527 | dbgcomm_slots[i].port == ntohs(remoteaddr.sin_port)) 528 | { 529 | *targetPid = dbgcomm_slots[i].pid; 530 | dbgcomm_slots[i].status = DBGCOMM_IDLE; 531 | break; 532 | } 533 | } 534 | LWLockRelease(getPLDebuggerLock()); 535 | if (i >= NumTargetSlots) 536 | { 537 | /* 538 | * This connection did not come from a backend. Reject and continue 539 | * listening. 540 | */ 541 | closesocket(serverSocket); 542 | continue; 543 | } 544 | else 545 | { 546 | /* This looks like a legitimate connection. */ 547 | break; 548 | } 549 | } 550 | 551 | return serverSocket; 552 | } 553 | 554 | 555 | int 556 | dbgcomm_listen_for_target(int *port) 557 | { 558 | int sockfd; 559 | struct sockaddr_in proxy_addr = {0}; 560 | socklen_t proxy_addr_len = sizeof( proxy_addr ); 561 | int reuse_addr_flag = 1; 562 | 563 | sockfd = socket(AF_INET, SOCK_STREAM, 0); 564 | if (sockfd < 0) 565 | ereport(ERROR, 566 | (errcode_for_socket_access(), 567 | errmsg("could not create socket: %m"))); 568 | /* Sockets seem to be non-blocking by default on Windows.. */ 569 | if (!pg_set_block(sockfd)) 570 | { 571 | int save_errno = errno; 572 | closesocket(sockfd); 573 | errno = save_errno; 574 | ereport(ERROR, 575 | (errmsg("could not set socket to blocking mode: %m"))); 576 | } 577 | 578 | /* Ask the TCP/IP stack for an unused port */ 579 | proxy_addr.sin_family = AF_INET; 580 | proxy_addr.sin_port = htons(0); 581 | proxy_addr.sin_addr.s_addr = resolveHostName("127.0.0.1"); 582 | 583 | setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 584 | (const char *) &reuse_addr_flag, sizeof( reuse_addr_flag )); 585 | 586 | /* Bind a listener socket to that port */ 587 | if (bind( sockfd, (struct sockaddr *) &proxy_addr, sizeof( proxy_addr )) < 0 ) 588 | { 589 | int save_errno = errno; 590 | closesocket(sockfd); 591 | errno = save_errno; 592 | ereport(ERROR, 593 | (errmsg("could not create listener for debugger connection"))); 594 | } 595 | 596 | /* Get the port number selected by the TCP/IP stack */ 597 | getsockname( sockfd, (struct sockaddr *)&proxy_addr, &proxy_addr_len ); 598 | 599 | *port = (int) ntohs( proxy_addr.sin_port ); 600 | 601 | /* Get ready to wait for a client */ 602 | listen(sockfd, 2); 603 | 604 | elog(DEBUG1, "listening for debugging target at port %d", *port); 605 | 606 | return sockfd; 607 | } 608 | 609 | /* 610 | * Find first available target slot. 611 | * 612 | * Note: Caller must be holding the lock. 613 | */ 614 | static int 615 | findFreeTargetSlot(void) 616 | { 617 | int i; 618 | 619 | for (i = 0; i < NumTargetSlots; i++) 620 | { 621 | if (dbgcomm_slots[i].backendid == InvalidBackendId) 622 | return i; 623 | if (dbgcomm_slots[i].backendid == MyBackendId) 624 | { 625 | /* 626 | * If we've failed to deallocate our slot earlier, reuse this slot. 627 | * This shouldn't happen. 628 | */ 629 | elog(LOG, "found leftover debugging target slot for backend %d", 630 | MyBackendId); 631 | return i; 632 | } 633 | } 634 | return -1; 635 | } 636 | 637 | 638 | /* 639 | * Find target slot belonging to given backend. 640 | * 641 | * Note: Caller must be holding the lock. 642 | */ 643 | static int 644 | findTargetSlot(BackendId backendid) 645 | { 646 | int i; 647 | 648 | for (i = 0; i < NumTargetSlots; i++) 649 | { 650 | if (dbgcomm_slots[i].backendid == backendid) 651 | return i; 652 | } 653 | return -1; 654 | } 655 | 656 | 657 | /******************************************************************************* 658 | * resolveHostName() 659 | * 660 | * Given the name of a host (hostName), this function returns the IP address 661 | * of that host (or 0 if the name does not resolve to an address). 662 | * 663 | * FIXME: this function should probably be a bit more flexibile. 664 | */ 665 | 666 | #ifndef INADDR_NONE 667 | #define INADDR_NONE ((unsigned long int) -1) /* For Solaris */ 668 | #endif 669 | 670 | static uint32 resolveHostName( const char * hostName ) 671 | { 672 | struct hostent * hostDesc; 673 | uint32 hostAddress; 674 | 675 | if(( hostDesc = gethostbyname( hostName ))) 676 | hostAddress = ((struct in_addr *)hostDesc->h_addr )->s_addr; 677 | else 678 | hostAddress = inet_addr( hostName ); 679 | 680 | if(( hostAddress == -1 ) || ( hostAddress == INADDR_NONE )) 681 | return( 0 ); 682 | else 683 | return( hostAddress ); 684 | } 685 | -------------------------------------------------------------------------------- /dbgcomm.h: -------------------------------------------------------------------------------- 1 | /* 2 | * dbgcomm.h 3 | * 4 | * This file defines the functions used to establish connections between 5 | * the debugging proxy and target backend. 6 | * 7 | * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 8 | * 9 | * Licensed under the Artistic License v2.0, see 10 | * https://opensource.org/licenses/artistic-license-2.0 11 | * for full details 12 | */ 13 | #ifndef DBGCOMM_H 14 | #define DBGCOMM_H 15 | 16 | #if (PG_VERSION_NUM >= 170000) 17 | #define BackendId ProcNumber 18 | #define MyBackendId MyProcNumber 19 | #define InvalidBackendId INVALID_PROC_NUMBER 20 | #endif 21 | 22 | extern void dbgcomm_reserve(void); 23 | 24 | extern int dbgcomm_connect_to_proxy(int proxyPort); 25 | extern int dbgcomm_listen_for_proxy(void); 26 | 27 | extern int dbgcomm_listen_for_target(int *port); 28 | extern int dbgcomm_accept_target(int sockfd, int *targetPid); 29 | extern int dbgcomm_connect_to_target(BackendId targetBackend); 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /globalbp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * globalbp.h - 3 | * 4 | * This file defines the (shared-memory) structures used by the PL debugger 5 | * to keep track of global breakpoints. 6 | * 7 | * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 8 | * 9 | * Licensed under the Artistic License v2.0, see 10 | * https://opensource.org/licenses/artistic-license-2.0 11 | * for full details 12 | */ 13 | #ifndef GLOBALBP_H 14 | #define GLOBALBP_H 15 | 16 | #include "utils/hsearch.h" 17 | 18 | typedef enum 19 | { 20 | BP_LOCAL = 0, 21 | BP_GLOBAL 22 | } eBreakpointScope; 23 | 24 | /* 25 | * Stores information pertaining to a global breakpoint. 26 | */ 27 | typedef struct BreakpointData 28 | { 29 | bool isTmp; /* tmp breakpoints are removed when hit */ 30 | bool busy; /* is this session already in use by a target? */ 31 | int proxyPort; /* port number of the proxy listener */ 32 | int proxyPid; /* process id of the proxy process */ 33 | } BreakpointData; 34 | 35 | /* 36 | * The key of the global breakpoints hash table. For now holds only have an Oid field. 37 | * but it may contain more fields in future. 38 | */ 39 | typedef struct BreakpointKey 40 | { 41 | Oid databaseId; 42 | Oid functionId; 43 | int lineNumber; 44 | int targetPid; /* -1 means any process */ 45 | } BreakpointKey; 46 | 47 | typedef struct Breakpoint 48 | { 49 | BreakpointKey key; 50 | BreakpointData data; 51 | } Breakpoint; 52 | 53 | extern Breakpoint * BreakpointLookup(eBreakpointScope scope, BreakpointKey *key); 54 | extern bool BreakpointInsert(eBreakpointScope scope, BreakpointKey *key, BreakpointData *brkpnt); 55 | extern bool BreakpointDelete(eBreakpointScope scope, BreakpointKey *key); 56 | extern void BreakpointShowAll(eBreakpointScope scope); 57 | extern bool BreakpointInsertOrUpdate(eBreakpointScope scope, BreakpointKey *key, BreakpointData *data); 58 | extern bool BreakpointOnId(eBreakpointScope scope, Oid funcOid); 59 | extern void BreakpointCleanupProc(int pid); 60 | extern void BreakpointGetList(eBreakpointScope scope, HASH_SEQ_STATUS *scan); 61 | extern void BreakpointReleaseList(eBreakpointScope scope); 62 | extern void BreakpointBusySession(int pid); 63 | extern void BreakpointFreeSession(int pid); 64 | #endif 65 | -------------------------------------------------------------------------------- /pldbgapi--1.0--1.1.sql: -------------------------------------------------------------------------------- 1 | -- pldbgapi--1.0--1.1.sql 2 | -- This script upgrades the API from version 1.0 to 1.1. 3 | -- 4 | -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 5 | -- 6 | -- Licensed under the Artistic License v2.0, see 7 | -- https://opensource.org/licenses/artistic-license-2.0 8 | -- for full details 9 | 10 | DO $do$ 11 | 12 | declare 13 | isedb bool; 14 | createstmt text; 15 | begin 16 | 17 | isedb = (SELECT version() LIKE '%EnterpriseDB%'); 18 | 19 | createstmt := $create_stmt$ 20 | 21 | CREATE OR REPLACE FUNCTION pldbg_get_target_info(signature text, targetType "char") returns targetinfo AS $$ 22 | SELECT p.oid AS target, 23 | pronamespace AS schema, 24 | pronargs::int4 AS nargs, 25 | -- The returned argtypes column is of type oidvector, but unlike 26 | -- proargtypes, it's supposed to include OUT params. So we 27 | -- essentially have to return proallargtypes, converted to an 28 | -- oidvector. There is no oid[] -> oidvector cast, so we have to 29 | -- do it via text. 30 | CASE WHEN proallargtypes IS NOT NULL THEN 31 | translate(proallargtypes::text, ',{}', ' ')::oidvector 32 | ELSE 33 | proargtypes 34 | END AS argtypes, 35 | proname AS targetname, 36 | proargmodes AS argmodes, 37 | proargnames AS proargnames, 38 | prolang AS targetlang, 39 | quote_ident(nspname) || '.' || quote_ident(proname) AS fqname, 40 | proretset AS returnsset, 41 | prorettype AS returntype, 42 | $create_stmt$; 43 | 44 | -- Add the three EDB-columns to the query (as dummies if we're installing 45 | -- to PostgreSQL) 46 | IF isedb THEN 47 | createstmt := createstmt || 48 | $create_stmt$ 49 | p.protype='0' AS isfunc, 50 | CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg, 51 | edb_get_func_defvals(p.oid) AS argdefvals 52 | $create_stmt$; 53 | ELSE 54 | createstmt := createstmt || 55 | $create_stmt$ 56 | 't'::bool AS isfunc, 57 | 0::oid AS pkg, 58 | NULL::text[] AS argdefvals 59 | $create_stmt$; 60 | END IF; 61 | -- End of conditional part 62 | 63 | createstmt := createstmt || 64 | $create_stmt$ 65 | FROM pg_proc p, pg_namespace n 66 | WHERE p.pronamespace = n.oid 67 | AND p.oid = $1::oid 68 | -- We used to support querying by function name or trigger name/oid as well, 69 | -- but that was never used in the client, so the support for that has been 70 | -- removed. The targeType argument remains as a legacy of that. You're 71 | -- expected to pass 'o' as target type, but it doesn't do anything. 72 | AND $2 = 'o' 73 | $$ LANGUAGE SQL; 74 | $create_stmt$; 75 | 76 | execute createstmt; 77 | 78 | -- Add a couple of EDB specific functions 79 | IF isedb THEN 80 | CREATE OR REPLACE FUNCTION edb_oid_debug(functionOID oid) RETURNS integer AS $$ 81 | select pldbg_oid_debug($1); 82 | $$ LANGUAGE SQL; 83 | 84 | CREATE OR REPLACE FUNCTION pldbg_get_pkg_cons(packageOID oid) RETURNS oid AS $$ 85 | select oid from pg_proc where pronamespace=$1 and proname='cons'; 86 | $$ LANGUAGE SQL; 87 | END IF; 88 | 89 | end; 90 | $do$; 91 | -------------------------------------------------------------------------------- /pldbgapi--1.1.sql: -------------------------------------------------------------------------------- 1 | -- pldbgapi--1.1.sql 2 | -- This script creates the data types and functions defined by the PL debugger API 3 | -- 4 | -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 5 | -- 6 | -- Licensed under the Artistic License v2.0, see 7 | -- https://opensource.org/licenses/artistic-license-2.0 8 | -- for full details 9 | 10 | \echo Installing pldebugger as unpackaged objects. If you are using PostgreSQL 11 | \echo version 9.1 or above, use "CREATE EXTENSION pldbgapi" instead. 12 | 13 | CREATE TYPE breakpoint AS ( func OID, linenumber INTEGER, targetName TEXT ); 14 | CREATE TYPE frame AS ( level INT, targetname TEXT, func OID, linenumber INTEGER, args TEXT ); 15 | 16 | CREATE TYPE var AS ( name TEXT, varClass char, lineNumber INTEGER, isUnique bool, isConst bool, isNotNull bool, dtype OID, value TEXT ); 17 | CREATE TYPE proxyInfo AS ( serverVersionStr TEXT, serverVersionNum INT, proxyAPIVer INT, serverProcessID INT ); 18 | 19 | CREATE FUNCTION pldbg_oid_debug( functionOID OID ) RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 20 | 21 | -- for backwards-compatibility 22 | CREATE FUNCTION plpgsql_oid_debug( functionOID OID ) RETURNS INTEGER AS $$ SELECT pldbg_oid_debug($1) $$ LANGUAGE sql STRICT; 23 | 24 | CREATE FUNCTION pldbg_abort_target( session INTEGER ) RETURNS SETOF boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 25 | CREATE FUNCTION pldbg_attach_to_port( portNumber INTEGER ) RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 26 | CREATE FUNCTION pldbg_continue( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 27 | CREATE FUNCTION pldbg_create_listener() RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 28 | CREATE FUNCTION pldbg_deposit_value( session INTEGER, varName TEXT, lineNumber INTEGER, value TEXT ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 29 | CREATE FUNCTION pldbg_drop_breakpoint( session INTEGER, func OID, linenumber INTEGER ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 30 | CREATE FUNCTION pldbg_get_breakpoints( session INTEGER ) RETURNS SETOF breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 31 | CREATE FUNCTION pldbg_get_source( session INTEGER, func OID ) RETURNS TEXT AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 32 | CREATE FUNCTION pldbg_get_stack( session INTEGER ) RETURNS SETOF frame AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 33 | CREATE FUNCTION pldbg_get_proxy_info( ) RETURNS proxyInfo AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 34 | CREATE FUNCTION pldbg_get_variables( session INTEGER ) RETURNS SETOF var AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 35 | CREATE FUNCTION pldbg_select_frame( session INTEGER, frame INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 36 | CREATE FUNCTION pldbg_set_breakpoint( session INTEGER, func OID, linenumber INTEGER ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 37 | CREATE FUNCTION pldbg_set_global_breakpoint( session INTEGER, func OID, linenumber INTEGER, targetPID INTEGER ) RETURNS boolean AS '$libdir/plugin_debugger' LANGUAGE C; 38 | CREATE FUNCTION pldbg_step_into( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 39 | CREATE FUNCTION pldbg_step_over( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 40 | CREATE FUNCTION pldbg_wait_for_breakpoint( session INTEGER ) RETURNS breakpoint AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 41 | CREATE FUNCTION pldbg_wait_for_target( session INTEGER ) RETURNS INTEGER AS '$libdir/plugin_debugger' LANGUAGE C STRICT; 42 | 43 | /* 44 | * pldbg_get_target_info() function can be used to return information about 45 | * a function. 46 | * 47 | * Deprecated. This is used by the pgAdmin debugger GUI, but new applications 48 | * should just query the catalogs directly. 49 | */ 50 | 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, 51 | 52 | -- The following columns are only needed when running in an EnterpriseDB 53 | -- server. On PostgreSQL, we return just dummy values for them. 54 | -- 55 | -- 'isFunc' and 'pkg' only make sense on EnterpriseDB. 'isfunc' is true 56 | -- if the function is a regular function, not a stored procedure or a 57 | -- function that was created implictly to back a trigger created with the 58 | -- Oracle-compatible CREATE TRIGGER syntax. If the function belongs to a 59 | -- package, 'pkg' is the package's OID, or 0 otherwise. 60 | -- 61 | -- 'argDefVals' is a representation of the function's argument DEFAULTs. 62 | -- That would be nice to have on PostgreSQL as well. Unfortunately our 63 | -- current implementation relies on an EDB-only function to get that 64 | -- information, so we cannot just use it as is. TODO: rewrite that using 65 | -- pg_get_expr(pg_proc.proargdefaults). 66 | isFunc BOOL, 67 | pkg OID, 68 | argDefVals TEXT[] 69 | ); 70 | 71 | -- Create the pldbg_get_target_info() function. We use an inline code block 72 | -- so that we can check and create it slightly differently if running on 73 | -- an EnterpriseDB server. 74 | 75 | DO $do$ 76 | 77 | declare 78 | isedb bool; 79 | createstmt text; 80 | begin 81 | 82 | isedb = (SELECT version() LIKE '%EnterpriseDB%'); 83 | 84 | createstmt := $create_stmt$ 85 | 86 | CREATE FUNCTION pldbg_get_target_info(signature text, targetType "char") returns targetinfo AS $$ 87 | SELECT p.oid AS target, 88 | pronamespace AS schema, 89 | pronargs::int4 AS nargs, 90 | -- The returned argtypes column is of type oidvector, but unlike 91 | -- proargtypes, it's supposed to include OUT params. So we 92 | -- essentially have to return proallargtypes, converted to an 93 | -- oidvector. There is no oid[] -> oidvector cast, so we have to 94 | -- do it via text. 95 | CASE WHEN proallargtypes IS NOT NULL THEN 96 | translate(proallargtypes::text, ',{}', ' ')::oidvector 97 | ELSE 98 | proargtypes 99 | END AS argtypes, 100 | proname AS targetname, 101 | proargmodes AS argmodes, 102 | proargnames AS proargnames, 103 | prolang AS targetlang, 104 | quote_ident(nspname) || '.' || quote_ident(proname) AS fqname, 105 | proretset AS returnsset, 106 | prorettype AS returntype, 107 | $create_stmt$; 108 | 109 | -- Add the three EDB-columns to the query (as dummies if we're installing 110 | -- to PostgreSQL) 111 | IF isedb THEN 112 | createstmt := createstmt || 113 | $create_stmt$ 114 | p.protype='0' AS isfunc, 115 | CASE WHEN n.nspparent <> 0 THEN n.oid ELSE 0 END AS pkg, 116 | edb_get_func_defvals(p.oid) AS argdefvals 117 | $create_stmt$; 118 | ELSE 119 | createstmt := createstmt || 120 | $create_stmt$ 121 | 't'::bool AS isfunc, 122 | 0::oid AS pkg, 123 | NULL::text[] AS argdefvals 124 | $create_stmt$; 125 | END IF; 126 | -- End of conditional part 127 | 128 | createstmt := createstmt || 129 | $create_stmt$ 130 | FROM pg_proc p, pg_namespace n 131 | WHERE p.pronamespace = n.oid 132 | AND p.oid = $1::oid 133 | -- We used to support querying by function name or trigger name/oid as well, 134 | -- but that was never used in the client, so the support for that has been 135 | -- removed. The targeType argument remains as a legacy of that. You're 136 | -- expected to pass 'o' as target type, but it doesn't do anything. 137 | AND $2 = 'o' 138 | $$ LANGUAGE SQL; 139 | $create_stmt$; 140 | 141 | execute createstmt; 142 | 143 | -- Add a couple of EDB specific functions 144 | IF isedb THEN 145 | CREATE FUNCTION edb_oid_debug(functionOID oid) RETURNS integer AS $$ 146 | select pldbg_oid_debug($1); 147 | $$ LANGUAGE SQL; 148 | 149 | CREATE FUNCTION pldbg_get_pkg_cons(packageOID oid) RETURNS oid AS $$ 150 | select oid from pg_proc where pronamespace=$1 and proname='cons'; 151 | $$ LANGUAGE SQL; 152 | END IF; 153 | 154 | end; 155 | $do$; 156 | -------------------------------------------------------------------------------- /pldbgapi--unpackaged--1.1.sql: -------------------------------------------------------------------------------- 1 | -- pldbgapi--unpackaged--1.1.sql 2 | -- This script converts an existing API installation to an extension. 3 | -- 4 | -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 5 | -- 6 | -- Licensed under the Artistic License v2.0, see 7 | -- https://opensource.org/licenses/artistic-license-2.0 8 | -- for full details 9 | 10 | ALTER EXTENSION pldbgapi ADD TYPE breakpoint; 11 | ALTER EXTENSION pldbgapi ADD TYPE frame; 12 | 13 | ALTER EXTENSION pldbgapi ADD TYPE targetinfo; 14 | 15 | ALTER EXTENSION pldbgapi ADD TYPE var; 16 | ALTER EXTENSION pldbgapi ADD TYPE proxyInfo; 17 | 18 | ALTER EXTENSION pldbgapi ADD FUNCTION plpgsql_oid_debug( functionOID OID ); 19 | 20 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_abort_target( session INTEGER ); 21 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_attach_to_port( portNumber INTEGER ); 22 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_continue( session INTEGER ); 23 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_create_listener(); 24 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_deposit_value( session INTEGER, varName TEXT, lineNumber INTEGER, value TEXT ); 25 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_drop_breakpoint( session INTEGER, func OID, linenumber INTEGER ); 26 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_breakpoints( session INTEGER ); 27 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_source( session INTEGER, func OID ); 28 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_stack( session INTEGER ); 29 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_proxy_info( ); 30 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_variables( session INTEGER ); 31 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_select_frame( session INTEGER, frame INTEGER ); 32 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_set_breakpoint( session INTEGER, func OID, linenumber INTEGER ); 33 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_set_global_breakpoint( session INTEGER, func OID, linenumber INTEGER, targetPID INTEGER ); 34 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_step_into( session INTEGER ); 35 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_step_over( session INTEGER ); 36 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_wait_for_breakpoint( session INTEGER ); 37 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_wait_for_target( session INTEGER ); 38 | ALTER EXTENSION pldbgapi ADD FUNCTION pldbg_get_target_info( signature TEXT, targetType "char" ); 39 | 40 | DO $do$ 41 | 42 | declare 43 | isedb bool; 44 | begin 45 | 46 | isedb = (SELECT version() LIKE '%EnterpriseDB%'); 47 | 48 | -- Add a couple of EDB specific functions 49 | IF isedb THEN 50 | ALTER EXTENSION pldbgapi ADD edb_oid_debug( functionOID oid ); 51 | ALTER EXTENSION pldbgapi ADD pldbg_get_pkg_cons( packageOID oid ); 52 | END IF; 53 | 54 | $do$; 55 | -------------------------------------------------------------------------------- /pldbgapi.c: -------------------------------------------------------------------------------- 1 | /* 2 | * pldbgapi.c 3 | * 4 | * This module defines (and implements) an API for debugging PL 5 | * functions and procedures (in particular, functions and procedures 6 | * written in PL/pgSQL or edb-spl). 7 | * 8 | * To debug a function or procedure, you need two backend processes 9 | * plus a debugger client (the client could be a command-line client 10 | * such as psql but is more likely a graphical client such as pgAdmin). 11 | * 12 | * The first backend is called the target - it's the process that's 13 | * running the code that you want to debug. 14 | * 15 | * The second backend is a 'proxy' process that shuttles data between 16 | * the debugger client and the target. The functions implemented in 17 | * this module are called 'proxy functions'. 18 | * 19 | * The proxy process provides an easy and secure way for the debugger 20 | * client to connect to the target - the client opens a normal 21 | * libpq-style connection that (presumably) knows how to work it's 22 | * way through a firewall and through the authentication maze (once 23 | * the connection process completes, the debugger client is connected 24 | * to the proxy). 25 | * 26 | * The debugger client can call any of the functions in this API. 27 | * Each function is executed by the proxy process. The proxy 28 | * shuttles debugging requests (like 'step into' or 'show call 29 | * stack') to the debugger server (running inside of the target 30 | * process) and sends the results back to the debugger client. 31 | * 32 | * There are a few basic rules for using this API: 33 | * 34 | * You must call one of the connection functions before you can do 35 | * anything else (at this point, the only connection function is 36 | * 'pldbg_attach_to_port()', but we'll add more as soon as we 37 | * implement global breakpoints). Each connection function returns 38 | * a session ID that identifies that debugging session (a debugger 39 | * client can maintain multiple simultaneous sessions by keeping 40 | * track of each session identifier). You pass that session ID 41 | * to all of the other proxy functions. 42 | * 43 | * Once you have opened a session, you must wait for the target 44 | * to reach a breakpoint (it may already be stopped at a breakpoint) 45 | * by calling pldbg_wait_for_breakpoint( sessionID ) - that function 46 | * will hang until the target reaches a breakpoint (or the target 47 | * session ends). 48 | * 49 | * When the target pauses, you can interact with the debugger server 50 | * (running inside of the target process) by calling any of the other 51 | * proxy functions. For example, to tell the target to "step into" a 52 | * function/procedure call, you would call pldbg_step_into() (and that 53 | * function would hang until the target pauses). To tell the target 54 | * to continue until the next breakpoint, you would call 55 | * pldbg_continue() (and, again, that function would hang until the 56 | * target pauses). 57 | * 58 | * Each time the target pauses, it returns a tuple of type 'breakpoint'. 59 | * That tuple contains the OID of the function that the target has paused 60 | * in, and the line number at which the target has paused. The fact that the 61 | * target returns a tuple of type breakpoint does not imply that the target 62 | * has paused at a breakpoint - it may have paused because of a step-over or 63 | * step-into operation. 64 | * 65 | * When the target is paused at a breakpoint (or has paused after 66 | * a step-over or step-into), you can interrogate the target by calling 67 | * pldbg_get_stack(), pldbg_get_source(), pldbg_get_breakpoints(), or 68 | * pldbg_get_variables(). 69 | * 70 | * The debugger server groks the PL call stack and maintains a 71 | * 'focus' frame. By default, the debugger server focuses on the most 72 | * deeply nested frame (because that's the code that's actually 73 | * running). You can shift the debugger's focus to a different frame 74 | * by calling pldbg_select_frame(). 75 | * 76 | * The focus is important because many functions (such as 77 | * pldbg_get_variables()) work against the stack frame that has the focus. 78 | * 79 | * Any of the proxy functions may throw an error - in particular, a proxy 80 | * function will throw an error if the target process ends. You're most 81 | * likely to encounter an error when you call pldbg_continue() and the 82 | * target process runs to completion (without hitting another breakpoint) 83 | * 84 | * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 85 | * 86 | * Licensed under the Artistic License v2.0, see 87 | * https://opensource.org/licenses/artistic-license-2.0 88 | * for full details 89 | */ 90 | 91 | #include "postgres.h" 92 | 93 | #include "funcapi.h" 94 | #include "utils/memutils.h" 95 | #include "utils/builtins.h" 96 | #include "storage/ipc.h" /* For on_shmem_exit() */ 97 | #include "storage/proc.h" /* For MyProc */ 98 | #include "libpq/libpq-be.h" /* For Port */ 99 | #include "miscadmin.h" /* For MyProcPort */ 100 | #include "catalog/pg_proc.h" 101 | #include "catalog/pg_type.h" 102 | #include "access/htup.h" /* For heap_form_tuple() */ 103 | #include "access/hash.h" /* For dynahash stuff */ 104 | 105 | #include 106 | #include /* For close() */ 107 | #include 108 | #include 109 | #include 110 | 111 | #include "globalbp.h" 112 | #include "dbgcomm.h" 113 | 114 | /* Include header for GETSTRUCT */ 115 | #if (PG_VERSION_NUM >= 90300) 116 | #include "access/htup_details.h" 117 | #endif 118 | 119 | #if PG_VERSION_NUM >= 110000 120 | #ifndef TRUE 121 | #define TRUE true 122 | #endif 123 | #ifndef FALSE 124 | #define FALSE false 125 | #endif 126 | #endif 127 | 128 | /* 129 | * Let the PG module loader know that we are compiled against 130 | * the right version of the PG header files 131 | */ 132 | 133 | #ifdef PG_MODULE_MAGIC 134 | PG_MODULE_MAGIC; 135 | #endif 136 | 137 | /******************************************************************************* 138 | * Proxy functions 139 | *******************************************************************************/ 140 | 141 | PG_FUNCTION_INFO_V1( pldbg_attach_to_port ); /* Attach to debugger server at the given port */ 142 | PG_FUNCTION_INFO_V1( pldbg_wait_for_breakpoint ); /* Wait for the target to reach a breakpoint */ 143 | PG_FUNCTION_INFO_V1( pldbg_step_into ); /* Steop into a function/procedure call */ 144 | PG_FUNCTION_INFO_V1( pldbg_step_over ); /* Step over a function/procedure call */ 145 | PG_FUNCTION_INFO_V1( pldbg_continue ); /* Continue execution until next breakpoint */ 146 | PG_FUNCTION_INFO_V1( pldbg_get_source ); /* Get the source code for a function/procedure */ 147 | PG_FUNCTION_INFO_V1( pldbg_get_breakpoints ); /* SHOW BREAKPOINTS equivalent (deprecated) */ 148 | PG_FUNCTION_INFO_V1( pldbg_get_variables ); /* Get a list of variable names/types/values */ 149 | PG_FUNCTION_INFO_V1( pldbg_get_stack ); /* Get the call stack from the target */ 150 | PG_FUNCTION_INFO_V1( pldbg_set_breakpoint ); /* CREATE BREAKPOINT equivalent (deprecated) */ 151 | PG_FUNCTION_INFO_V1( pldbg_drop_breakpoint ); /* DROP BREAKPOINT equivalent (deprecated) */ 152 | PG_FUNCTION_INFO_V1( pldbg_select_frame ); /* Change the focus to a different stack frame */ 153 | PG_FUNCTION_INFO_V1( pldbg_deposit_value ); /* Change the value of an in-scope variable */ 154 | PG_FUNCTION_INFO_V1( pldbg_abort_target ); /* Abort execution of the target - throws error */ 155 | PG_FUNCTION_INFO_V1( pldbg_get_proxy_info ); /* Get server version, proxy API version, ... */ 156 | 157 | PG_FUNCTION_INFO_V1( pldbg_create_listener ); /* Create a listener for global breakpoints */ 158 | PG_FUNCTION_INFO_V1( pldbg_wait_for_target ); /* Wait for a global breakpoint to fire */ 159 | PG_FUNCTION_INFO_V1( pldbg_set_global_breakpoint ); /* Create a global breakpoint */ 160 | 161 | /******************************************************************************* 162 | * Structure debugSession 163 | * 164 | * A debugger client may attach to many target sessions at the same time. We 165 | * keep track of each connection in a debugSession structure. When the client 166 | * makes a connection, we allocate a new debugSession structure and return 167 | * a handle to that structure to the caller. He gives us back the handle 168 | * whenever he calls another proxy function. A handle is just a smallish 169 | * integer value that we use to track each session - we use a hash to map 170 | * handles into debugSession pointers. 171 | */ 172 | 173 | typedef struct 174 | { 175 | int serverSocket; /* Socket connected to the debugger server */ 176 | int serverPort; /* Port number where debugger server is listening */ 177 | int listener; /* Socket where we wait for global breakpoints */ 178 | char *breakpointString; 179 | } debugSession; 180 | 181 | /******************************************************************************* 182 | * Stucture sessionHashEntry 183 | * 184 | * As mentioned above (see debugSession), a debugger proxy can manage many 185 | * debug sessions at once. To keep track of each session, we create a 186 | * debugSession object and return a handle to that object to the caller. The 187 | * handle is an opaque value - it's just an integer value. To convert a 188 | * handle into an actual debugSession pointer, we create a hash that maps 189 | * handles into debugSession pointers. 190 | * 191 | * Each member of the hash is shaped like a sessionHashEntry object. 192 | */ 193 | typedef int32 sessionHandle; 194 | 195 | typedef struct 196 | { 197 | sessionHandle m_handle; 198 | debugSession *m_session; 199 | } sessionHashEntry; 200 | 201 | static debugSession * mostRecentSession; 202 | static HTAB * sessionHash; 203 | 204 | /******************************************************************************* 205 | * The following symbols represent the magic strings that we send to the 206 | * debugger server running in the target process 207 | */ 208 | 209 | #define PLDBG_GET_VARIABLES "i\n" 210 | #define PLDBG_GET_BREAKPOINTS "l\n" 211 | #define PLDBG_GET_STACK "$\n" 212 | #define PLDBG_STEP_INTO "s\n" 213 | #define PLDBG_STEP_OVER "o\n" 214 | #define PLDBG_CONTINUE "c\n" 215 | #define PLDBG_ABORT "x" 216 | #define PLDBG_SELECT_FRAME "^" /* Followed by frame number */ 217 | #define PLDBG_SET_BREAKPOINT "b" /* Followed by pkgoid:funcoid:linenumber */ 218 | #define PLDBG_CLEAR_BREAKPOINT "f" /* Followed by pkgoid:funcoid:linenumber */ 219 | #define PLDBG_GET_SOURCE "#" /* Followed by pkgoid:funcoid */ 220 | #define PLDBG_DEPOSIT "d" /* Followed by var.line=value */ 221 | 222 | #define PLDBG_STRING_MAX_LEN 128 223 | 224 | #define PROXY_API_VERSION 3 /* API version number */ 225 | 226 | /******************************************************************************* 227 | * We currently define three PostgreSQL data types (all tuples) - the following 228 | * symbols correspond to the names for those types. 229 | */ 230 | 231 | #define TYPE_NAME_BREAKPOINT "breakpoint" /* May change to pldbg.breakpoint later */ 232 | #define TYPE_NAME_FRAME "frame" /* May change to pldbg.frame later */ 233 | #define TYPE_NAME_VAR "var" /* May change to pldbg.var later */ 234 | 235 | #define GET_STR( textp ) DatumGetCString( DirectFunctionCall1( textout, PointerGetDatum( textp ))) 236 | #define PG_GETARG_SESSION( n ) (sessionHandle)PG_GETARG_UINT32( n ) 237 | 238 | Datum pldbg_select_frame( PG_FUNCTION_ARGS ); 239 | Datum pldbg_attach_to_port( PG_FUNCTION_ARGS ); 240 | Datum pldbg_get_source( PG_FUNCTION_ARGS ); 241 | Datum pldbg_get_breakpoints( PG_FUNCTION_ARGS ); 242 | Datum pldbg_get_variables( PG_FUNCTION_ARGS ); 243 | Datum pldbg_get_stack( PG_FUNCTION_ARGS ); 244 | Datum pldbg_wait_for_breakpoint( PG_FUNCTION_ARGS ); 245 | Datum pldbg_set_breakpoint( PG_FUNCTION_ARGS ); 246 | Datum pldbg_drop_breakpoint( PG_FUNCTION_ARGS ); 247 | Datum pldbg_step_into( PG_FUNCTION_ARGS ); 248 | Datum pldbg_step_over( PG_FUNCTION_ARGS ); 249 | Datum pldbg_continue( PG_FUNCTION_ARGS ); 250 | Datum pldbg_deposit_value( PG_FUNCTION_ARGS ); 251 | Datum pldbg_get_proxy_info( PG_FUNCTION_ARGS ); 252 | Datum pldbg_get_pkg_cons( PG_FUNCTION_ARGS ); 253 | Datum pldbg_abort_target( PG_FUNCTION_ARGS ); 254 | 255 | Datum pldbg_create_listener( PG_FUNCTION_ARGS ); 256 | Datum pldbg_wait_for_target( PG_FUNCTION_ARGS ); 257 | Datum pldbg_set_global_breakpoint( PG_FUNCTION_ARGS ); 258 | 259 | /************************************************************ 260 | * Local function forward declarations 261 | ************************************************************/ 262 | static char * tokenize( char * src, const char * delimiters, char ** ctx ); 263 | static void * readn( int serverHandle, void * dst, size_t len ); 264 | static void * writen( int serverHandle, void * dst, size_t len ); 265 | static void sendBytes( debugSession * session, void * src, size_t len ); 266 | static void sendUInt32( debugSession * session, uint32 val ); 267 | static void sendString( debugSession * session, char * src ); 268 | static bool getBool( debugSession * session ); 269 | static uint32 getUInt32( debugSession * session ); 270 | static char * getNString( debugSession * session ); 271 | static void initializeModule( void ); 272 | static void cleanupAtExit( int code, Datum arg ); 273 | static void initSessionHash(); 274 | static debugSession * defaultSession( sessionHandle handle ); 275 | static sessionHandle addSession( debugSession * session ); 276 | static debugSession * findSession( sessionHandle handle ); 277 | static TupleDesc getResultTupleDesc( FunctionCallInfo fcinfo ); 278 | 279 | 280 | /******************************************************************************* 281 | * Exported functions 282 | *******************************************************************************/ 283 | 284 | /******************************************************************************* 285 | * pldbg_attach_to_port( portNumber INTEGER ) RETURNS INTEGER 286 | * 287 | * This function attaches to a debugging target listening on the given port. A 288 | * debugger client should invoke this function in response to a PLDBGBREAK 289 | * NOTICE (the notice contains the port number that you should connect to). 290 | * 291 | * This function returns a session handle that identifies this particular debug 292 | * session. When you call any of the other pldbg functions, you must supply 293 | * the session handle returned by pldbg_attach_to_port(). 294 | * 295 | * A given debugger client can maintain multiple simultaneous sessions 296 | * by calling pldbg_attach_to_port() many times (with different port 297 | * numbers) and keeping track of the returned session handles. 298 | */ 299 | 300 | Datum pldbg_attach_to_port( PG_FUNCTION_ARGS ) 301 | { 302 | int32 targetBackend = PG_GETARG_INT32( 0 ); 303 | debugSession *session; 304 | 305 | initializeModule(); 306 | 307 | session = MemoryContextAllocZero( TopMemoryContext, sizeof( *session )); 308 | session->listener = -1; 309 | 310 | session->serverSocket = dbgcomm_connect_to_target(targetBackend); 311 | 312 | if (session->serverSocket < 0) 313 | ereport(ERROR, 314 | (errcode_for_socket_access(), 315 | errmsg("could not connect to debug target"))); 316 | 317 | /* 318 | * After the handshake, the target process will send us information about 319 | * the local breakpoint that it hit. Read it. We will hand it to the client 320 | * if it calls wait_for_breakpoint(). 321 | */ 322 | session->breakpointString = MemoryContextStrdup(TopMemoryContext, 323 | getNString(session)); 324 | 325 | /* 326 | * For convenience, remember the most recent session - if you call 327 | * another pldbg_xxx() function with sessionHandle = 0, we'll use 328 | * the most recent session. 329 | */ 330 | mostRecentSession = session; 331 | 332 | PG_RETURN_INT32(addSession(session)); 333 | } 334 | 335 | Datum pldbg_create_listener( PG_FUNCTION_ARGS ) 336 | { 337 | debugSession * session = MemoryContextAllocZero( TopMemoryContext, sizeof( *session )); 338 | 339 | initializeModule(); 340 | 341 | session->listener = dbgcomm_listen_for_target(&session->serverPort); 342 | session->serverSocket = -1; 343 | 344 | mostRecentSession = session; 345 | 346 | PG_RETURN_INT32( addSession( session )); 347 | } 348 | 349 | /******************************************************************************* 350 | * pldbg_wait_for_target( ) RETURNS INTEGER 351 | * 352 | * This function advertises the proxy process as an active debugger, waiting 353 | * for global breakpoints. 354 | * 355 | * This function returns a session handle that identifies this particular debug 356 | * session. When you call any of the other pldbg functions, you must supply 357 | * this session handle. 358 | * 359 | * A given debugger client can maintain multiple simultaneous sessions 360 | * by calling pldbg_attach_to_port() many times (with different port 361 | * numbers) and keeping track of the returned session handles. 362 | */ 363 | 364 | Datum pldbg_wait_for_target( PG_FUNCTION_ARGS ) 365 | { 366 | debugSession *session = defaultSession(PG_GETARG_SESSION( 0 )); 367 | int serverSocket; 368 | int serverPID; 369 | 370 | /* 371 | * Now mark all of our global breakpoints as 'available' (that is, not 372 | * busy) 373 | */ 374 | BreakpointFreeSession( MyProc->pid ); 375 | 376 | serverSocket = dbgcomm_accept_target(session->listener, &serverPID); 377 | if (serverSocket < 0) 378 | ereport(ERROR, 379 | (errmsg("could not accept a connection from debugging target"))); 380 | 381 | session->serverSocket = serverSocket; 382 | 383 | /* 384 | * After the handshake, the target process will send us information about 385 | * the local breakpoint that it hit. Read it. We will hand it to the client 386 | * if it calls wait_for_breakpoint(). 387 | */ 388 | session->breakpointString = MemoryContextStrdup(TopMemoryContext, 389 | getNString(session)); 390 | 391 | PG_RETURN_UINT32( serverPID ); 392 | } 393 | 394 | /******************************************************************************* 395 | * pldbg_set_global_breakpoint(sessionID INT, function OID, lineNumber INT) 396 | * RETURNS boolean 397 | * 398 | * This function registers a breakpoint in the global breakpoint table. 399 | */ 400 | 401 | Datum pldbg_set_global_breakpoint( PG_FUNCTION_ARGS ) 402 | { 403 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 404 | Breakpoint breakpoint; 405 | 406 | if( !superuser()) 407 | ereport(ERROR, 408 | (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), 409 | errmsg("must be a superuser to create a breakpoint"))); 410 | 411 | if( session->listener == -1 ) 412 | ereport(ERROR, 413 | (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), 414 | errmsg("given session is not a listener"))); 415 | 416 | breakpoint.key.databaseId = MyProc->databaseId; 417 | breakpoint.key.functionId = PG_GETARG_OID( 1 ); 418 | 419 | if( PG_ARGISNULL( 2 )) 420 | breakpoint.key.lineNumber = -1; 421 | else 422 | breakpoint.key.lineNumber = PG_GETARG_INT32( 2 ); 423 | 424 | if( PG_ARGISNULL( 3 )) 425 | breakpoint.key.targetPid = -1; 426 | else 427 | breakpoint.key.targetPid = PG_GETARG_INT32( 3 ); 428 | 429 | breakpoint.data.isTmp = TRUE; 430 | breakpoint.data.proxyPort = session->serverPort; 431 | breakpoint.data.proxyPid = MyProc->pid; 432 | 433 | if( !BreakpointInsert( BP_GLOBAL, &breakpoint.key, &breakpoint.data )) 434 | ereport(ERROR, 435 | (errcode(ERRCODE_OBJECT_IN_USE), 436 | errmsg("another debugger is already waiting for that breakpoint"))); 437 | 438 | PG_RETURN_BOOL( true ); 439 | } 440 | 441 | /******************************************************************************* 442 | * pldbg_wait_for_breakpoint( sessionID INTEGER ) RETURNS breakpoint 443 | * 444 | * This function waits for the debug target to reach a breakpoint. You should 445 | * call this function immediately after pldbg_attach_to_port() returns a 446 | * session ID. pldbg_wait_for_breakpoint() is nearly identical to 447 | * pldbg_step_into(), pldbg_step_over(), and pldbg_continue(), (they all wait 448 | * for the target) but this function does not send a command to the target 449 | * first. 450 | * 451 | * This function returns a tuple of type 'breakpoint' - such a tuple contains 452 | * the function OID and line number where the target is currently stopped. 453 | */ 454 | 455 | static Datum buildBreakpointDatum( char * breakpointString ) 456 | { 457 | char * values[3]; 458 | char * ctx = NULL; 459 | HeapTuple result; 460 | TupleDesc tupleDesc = RelationNameGetTupleDesc( TYPE_NAME_BREAKPOINT ); 461 | 462 | values[0] = tokenize( breakpointString, ":", &ctx ); /* function OID */ 463 | values[1] = tokenize( NULL, ":", &ctx ); /* linenumber */ 464 | values[2] = tokenize( NULL, ":", &ctx ); /* targetName */ 465 | 466 | result = BuildTupleFromCStrings( TupleDescGetAttInMetadata( tupleDesc ), values ); 467 | 468 | return( HeapTupleGetDatum( result )); 469 | } 470 | 471 | Datum pldbg_wait_for_breakpoint( PG_FUNCTION_ARGS ) 472 | { 473 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 474 | char * breakpointString; 475 | 476 | if (!session->breakpointString) 477 | PG_RETURN_NULL(); 478 | 479 | breakpointString = pstrdup(session->breakpointString); 480 | pfree(session->breakpointString); 481 | session->breakpointString = NULL; 482 | 483 | PG_RETURN_DATUM( buildBreakpointDatum( breakpointString )); 484 | } 485 | 486 | /******************************************************************************* 487 | * pldbg_step_into( sessionID INTEGER ) RETURNS breakpoint 488 | * 489 | * This function sends a "step/into" command to the debugger target and then 490 | * waits for target to reach the next executable statement. 491 | * 492 | * This function returns a tuple of type 'breakpoint' that contains the 493 | * function OID and line number where the target is currently stopped. 494 | */ 495 | 496 | Datum pldbg_step_into( PG_FUNCTION_ARGS ) 497 | { 498 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 499 | 500 | sendString( session, PLDBG_STEP_INTO ); 501 | 502 | PG_RETURN_DATUM( buildBreakpointDatum( getNString( session ))); 503 | } 504 | 505 | /******************************************************************************* 506 | * pldbg_step_over( sessionID INTEGER ) RETURNS breakpoint 507 | * 508 | * This function sends a "step/over" command to the debugger target and then 509 | * waits for target to reach the next executable statement within the current 510 | * function. If the target encounters a breakpoint (presumably in a child 511 | * invocation) before reaching the next executable line, it will stop at the 512 | * breakpoint. 513 | * 514 | * This function returns a tuple of type 'breakpoint' that contains the 515 | * function OID and line number where the target is currently stopped. 516 | */ 517 | 518 | Datum pldbg_step_over( PG_FUNCTION_ARGS ) 519 | { 520 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 521 | 522 | sendString( session, PLDBG_STEP_OVER ); 523 | 524 | PG_RETURN_DATUM( buildBreakpointDatum( getNString( session ))); 525 | } 526 | 527 | /******************************************************************************* 528 | * pldbg_continue( sessionID INTEGER ) RETURNS breakpoint 529 | * 530 | * This function sends a "continue" command to the debugger target and then 531 | * waits for target to reach a breakpoint. 532 | * 533 | * This function returns a tuple of type 'breakpoint' that contains the 534 | * function OID and line number where the target is currently stopped. 535 | */ 536 | 537 | Datum pldbg_continue( PG_FUNCTION_ARGS ) 538 | { 539 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 540 | 541 | sendString( session, PLDBG_CONTINUE ); 542 | 543 | PG_RETURN_DATUM( buildBreakpointDatum( getNString( session ))); 544 | } 545 | 546 | /******************************************************************************* 547 | * pldbg_abort_target( sessionID INTEGER ) RETURNS breakpoint 548 | * 549 | * This function sends an "abort" command to the debugger target and then 550 | * waits for a reply 551 | */ 552 | 553 | Datum pldbg_abort_target( PG_FUNCTION_ARGS ) 554 | { 555 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 556 | 557 | sendString( session, PLDBG_ABORT ); 558 | 559 | PG_RETURN_BOOL( getBool( session )); 560 | 561 | } 562 | 563 | /******************************************************************************* 564 | * pldbg_select_frame( sessionID INTEGER, frameNumber INTEGER ) 565 | * RETURNS breakpoint 566 | * 567 | * This function changes the debugger focus to the indicated frame (in the call 568 | * stack). Whenever the target stops (at a breakpoint or as the result of a 569 | * step/into or step/over), the debugger changes focus to most deeply nested 570 | * function in the call stack (because that's the function that's executing). 571 | * 572 | * You can change the debugger focus to other stack frames - once you do that, 573 | * you can examine the source code for that frame, the variable values in that 574 | * frame, and the breakpoints in that target. 575 | * 576 | * The debugger focus remains on the selected frame until you change it or 577 | * the target stops at another breakpoint. 578 | * 579 | * This function returns a tuple of type 'breakpoint' that contains the 580 | * function OID, and line number where the target is currently stopped in 581 | * the selected frame. 582 | */ 583 | 584 | Datum pldbg_select_frame( PG_FUNCTION_ARGS ) 585 | { 586 | if( PG_ARGISNULL( 0 )) 587 | PG_RETURN_NULL(); 588 | else 589 | { 590 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 591 | int32 frameNumber = PG_GETARG_INT32( 1 ); 592 | char frameString[PLDBG_STRING_MAX_LEN]; 593 | char * resultString; 594 | Datum result; 595 | 596 | snprintf( 597 | frameString, PLDBG_STRING_MAX_LEN, "%s %d", PLDBG_SELECT_FRAME, 598 | frameNumber 599 | ); 600 | 601 | sendString( session, frameString ); 602 | 603 | resultString = getNString( session ); 604 | 605 | result = buildBreakpointDatum( resultString ); 606 | 607 | PG_RETURN_DATUM( result ); 608 | } 609 | } 610 | 611 | /******************************************************************************* 612 | * pldbg_get_source( sessionID INTEGER, functionOID OID ) 613 | * RETURNS CSTRING 614 | * 615 | * This function returns the source code for the given function. A debugger 616 | * client should always retrieve source code using this function instead of 617 | * reading pg_proc. If you read pg_proc instead, the source code that you 618 | * read may not match the source that the target is actually executing 619 | * (because the source code may have been modified in a different transaction). 620 | * 621 | * pldbg_get_source() always retrieves the source code from the target and 622 | * ensures that the source code that you get is the source code that the 623 | * target is executing. 624 | * 625 | */ 626 | 627 | Datum pldbg_get_source( PG_FUNCTION_ARGS ) 628 | { 629 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 630 | Oid funcOID = PG_GETARG_OID( 1 ); 631 | char sourceString[PLDBG_STRING_MAX_LEN]; 632 | char * source; 633 | 634 | snprintf( 635 | sourceString, PLDBG_STRING_MAX_LEN, "%s %u", 636 | PLDBG_GET_SOURCE, funcOID 637 | ); 638 | 639 | sendString( session, sourceString ); 640 | 641 | source = getNString( session ); 642 | 643 | PG_RETURN_TEXT_P(cstring_to_text(source)); 644 | } 645 | 646 | /******************************************************************************* 647 | * pldbg_get_breakpoints( sessionID INTEGER ) RETURNS SETOF breakpoint 648 | * 649 | * This function returns a SETOF breakpoint tuples. Each tuple in the result 650 | * set identifies a breakpoint. 651 | * 652 | * NOTE: the result set returned by this function should be identical to 653 | * the result set returned by a SHOW BREAKPOINTS command. This function 654 | * may become obsolete when SHOW BREAKPOINTS is complete. 655 | */ 656 | 657 | Datum pldbg_get_breakpoints( PG_FUNCTION_ARGS ) 658 | { 659 | FuncCallContext * srf; 660 | 661 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 662 | char * breakpointString; 663 | 664 | if( SRF_IS_FIRSTCALL()) 665 | { 666 | MemoryContext oldContext; 667 | 668 | srf = SRF_FIRSTCALL_INIT(); 669 | 670 | oldContext = MemoryContextSwitchTo( srf->multi_call_memory_ctx ); 671 | srf->attinmeta = TupleDescGetAttInMetadata( RelationNameGetTupleDesc( TYPE_NAME_BREAKPOINT )); 672 | MemoryContextSwitchTo( oldContext ); 673 | 674 | sendString( session, PLDBG_GET_BREAKPOINTS ); 675 | } 676 | else 677 | { 678 | srf = SRF_PERCALL_SETUP(); 679 | } 680 | 681 | if(( breakpointString = getNString( session )) != NULL ) 682 | { 683 | SRF_RETURN_NEXT( srf, buildBreakpointDatum( breakpointString )); 684 | } 685 | else 686 | { 687 | SRF_RETURN_DONE( srf ); 688 | } 689 | } 690 | 691 | /******************************************************************************* 692 | * pldbg_get_variables( sessionID INTEGER ) RETURNS SETOF var 693 | * 694 | * This function returns a SETOF var tuples. Each tuple in the result 695 | * set contains information about one local variable (or parameter) in the 696 | * stack frame that has the focus. Each tuple contains the name of the 697 | * variable, the line number at which the variable was declared, a flag 698 | * that tells you whether the name is unique within the scope of the function 699 | * (if the name is not unique, a debugger client may use the line number to 700 | * distinguish between variables with the same name), a flag that tells you 701 | * whether the variables is a CONST, a flag that tells you whether the variable 702 | * is NOT NULL, the data type of the variable (the OID of the corresponding 703 | * pg_type) and the value of the variable. 704 | * 705 | * To view variables defined in a different stack frame, call 706 | * pldbg_select_frame() to change the debugger's focus to that frame. 707 | */ 708 | 709 | Datum pldbg_get_variables( PG_FUNCTION_ARGS ) 710 | { 711 | FuncCallContext * srf; 712 | 713 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 714 | char * variableString; 715 | 716 | if( SRF_IS_FIRSTCALL()) 717 | { 718 | MemoryContext oldContext; 719 | 720 | srf = SRF_FIRSTCALL_INIT(); 721 | 722 | oldContext = MemoryContextSwitchTo( srf->multi_call_memory_ctx ); 723 | srf->attinmeta = TupleDescGetAttInMetadata( RelationNameGetTupleDesc( TYPE_NAME_VAR )); 724 | MemoryContextSwitchTo( oldContext ); 725 | 726 | sendString( session, PLDBG_GET_VARIABLES ); 727 | } 728 | else 729 | { 730 | srf = SRF_PERCALL_SETUP(); 731 | } 732 | 733 | if(( variableString = getNString( session )) != NULL ) 734 | { 735 | char * values[8]; 736 | char * ctx = NULL; 737 | HeapTuple result; 738 | 739 | /* 740 | * variableString points to a string like: 741 | * varName:class:lineNumber:unique:isConst:notNull:dataTypeOID 742 | */ 743 | values[0] = pstrdup( tokenize( variableString, ":", &ctx )); /* variable name */ 744 | values[1] = pstrdup( tokenize( NULL, ":", &ctx )); /* var class */ 745 | values[2] = pstrdup( tokenize( NULL, ":", &ctx )); /* line number */ 746 | values[3] = pstrdup( tokenize( NULL, ":", &ctx )); /* unique */ 747 | values[4] = pstrdup( tokenize( NULL, ":", &ctx )); /* isConst */ 748 | values[5] = pstrdup( tokenize( NULL, ":", &ctx )); /* notNull */ 749 | values[6] = pstrdup( tokenize( NULL, ":", &ctx )); /* data type OID */ 750 | values[7] = pstrdup( tokenize( NULL, NULL, &ctx )); /* value (rest of string) */ 751 | 752 | result = BuildTupleFromCStrings( srf->attinmeta, values ); 753 | 754 | SRF_RETURN_NEXT( srf, HeapTupleGetDatum( result )); 755 | } 756 | else 757 | { 758 | SRF_RETURN_DONE( srf ); 759 | } 760 | } 761 | 762 | /******************************************************************************* 763 | * pldbg_get_stack( sessionID INTEGER ) RETURNS SETOF frame 764 | * 765 | * This function returns a SETOF frame tuples. Each tuple in the result 766 | * set contains information about one stack frame: the tuple contains the 767 | * function OID, and line number within that function. Each tuple also 768 | * contains a string that you can use to display the name and value of each 769 | * argument to that particular invocation. 770 | */ 771 | 772 | Datum pldbg_get_stack( PG_FUNCTION_ARGS ) 773 | { 774 | FuncCallContext * srf; 775 | 776 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 777 | char * frameString; 778 | 779 | if( SRF_IS_FIRSTCALL()) 780 | { 781 | MemoryContext oldContext; 782 | 783 | srf = SRF_FIRSTCALL_INIT(); 784 | 785 | oldContext = MemoryContextSwitchTo( srf->multi_call_memory_ctx ); 786 | srf->attinmeta = TupleDescGetAttInMetadata( RelationNameGetTupleDesc( TYPE_NAME_FRAME )); 787 | MemoryContextSwitchTo( oldContext ); 788 | 789 | sendString( session, PLDBG_GET_STACK ); 790 | } 791 | else 792 | { 793 | srf = SRF_PERCALL_SETUP(); 794 | } 795 | 796 | if(( frameString = getNString( session )) != NULL ) 797 | { 798 | char * values[5]; 799 | char callCount[PLDBG_STRING_MAX_LEN]; 800 | char * ctx = NULL; 801 | HeapTuple result; 802 | 803 | /* 804 | * frameString points to a string like: 805 | * targetName:funcOID:lineNumber:arguments 806 | */ 807 | snprintf( 808 | callCount, PLDBG_STRING_MAX_LEN, UINT64_FORMAT, 809 | (uint64)srf->call_cntr 810 | ); 811 | 812 | values[0] = callCount; 813 | values[1] = tokenize( frameString, ":", &ctx ); /* targetName */ 814 | values[2] = tokenize( NULL, ":", &ctx ); /* funcOID */ 815 | values[3] = tokenize( NULL, ":", &ctx ); /* lineNumber */ 816 | values[4] = tokenize( NULL, NULL, &ctx ); /* arguments - rest of string */ 817 | 818 | result = BuildTupleFromCStrings( srf->attinmeta, values ); 819 | 820 | SRF_RETURN_NEXT( srf, HeapTupleGetDatum( result )); 821 | } 822 | else 823 | { 824 | SRF_RETURN_DONE( srf ); 825 | } 826 | } 827 | 828 | /******************************************************************************** 829 | * pldbg_get_proxy_info( ) RETURNS proxyInfo 830 | * 831 | * This function retrieves a small collection of parameters from the server, all 832 | * parameters are related to the version of the server and the version of this 833 | * proxy API. 834 | * 835 | * You can call this function (from the debugger client process) to find out 836 | * which version of the proxy API you are talking to - if this function does 837 | * not exist, you can assume that you are talking to a version 1 proxy server. 838 | */ 839 | 840 | Datum pldbg_get_proxy_info( PG_FUNCTION_ARGS ) 841 | { 842 | Datum values[4] = {0}; 843 | bool nulls[4] = {0}; 844 | TupleDesc tupleDesc = getResultTupleDesc( fcinfo ); 845 | HeapTuple result; 846 | 847 | values[0] = DirectFunctionCall1( textin, PointerGetDatum( PG_VERSION_STR )); 848 | values[1] = Int32GetDatum( PG_VERSION_NUM ); 849 | values[2] = Int32GetDatum( PROXY_API_VERSION ); 850 | values[3] = Int32GetDatum( MyProcPid ); 851 | 852 | result = heap_form_tuple( tupleDesc, values, nulls ); 853 | 854 | PG_RETURN_DATUM( HeapTupleGetDatum( result )); 855 | } 856 | 857 | /******************************************************************************* 858 | * pldbg_set_breakpoint(sessionID INT, function OID, lineNumber INT) 859 | * RETURNS boolean 860 | * 861 | * Sets a *local* breakpoint in the target process. 862 | */ 863 | 864 | Datum pldbg_set_breakpoint( PG_FUNCTION_ARGS ) 865 | { 866 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 867 | Oid funcOID = PG_GETARG_OID( 1 ); 868 | int lineNumber = PG_GETARG_INT32( 2 ); 869 | char breakpointString[PLDBG_STRING_MAX_LEN]; 870 | 871 | snprintf( 872 | breakpointString, PLDBG_STRING_MAX_LEN, "%s %u:%d", 873 | PLDBG_SET_BREAKPOINT, funcOID, lineNumber 874 | ); 875 | 876 | sendString( session, breakpointString ); 877 | 878 | PG_RETURN_BOOL( getBool( session )); 879 | } 880 | 881 | /******************************************************************************* 882 | * pldbg_drop_breakpoint(sessionID INT, function OID, lineNumber INT) 883 | * RETURNS boolean 884 | */ 885 | 886 | Datum pldbg_drop_breakpoint( PG_FUNCTION_ARGS ) 887 | { 888 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 889 | Oid funcOID = PG_GETARG_OID( 1 ); 890 | int lineNumber = PG_GETARG_INT32( 2 ); 891 | char breakpointString[PLDBG_STRING_MAX_LEN]; 892 | 893 | snprintf( 894 | breakpointString, PLDBG_STRING_MAX_LEN, "%s %u:%d", 895 | PLDBG_CLEAR_BREAKPOINT, funcOID, lineNumber 896 | ); 897 | 898 | sendString( session, breakpointString ); 899 | 900 | PG_RETURN_BOOL( getBool( session )); 901 | } 902 | 903 | /******************************************************************************* 904 | * pldbg_deposit_value( sessionID INT, varName TEXT, lineNumber INT, value TEXT) 905 | * RETURNS boolean 906 | * 907 | * This function 'deposits' a new value into the given variable (identified by 908 | * name and optional line number). 'value' is evaluated as an expression that 909 | * must result in a value whose type matches the given variable (or whose type 910 | * is coerce'able to the type of the given variable). 911 | */ 912 | 913 | Datum pldbg_deposit_value( PG_FUNCTION_ARGS ) 914 | { 915 | debugSession * session = defaultSession( PG_GETARG_SESSION( 0 )); 916 | char * varName = GET_STR( PG_GETARG_TEXT_P( 1 )); 917 | int lineNumber = PG_GETARG_INT32( 2 ); 918 | char * value = GET_STR( PG_GETARG_TEXT_P( 3 )); 919 | StringInfoData buf; 920 | 921 | initStringInfo( &buf ); 922 | 923 | appendStringInfo( &buf, "%s %s.%d=%s", PLDBG_DEPOSIT, varName, lineNumber, value ); 924 | 925 | sendString( session, buf.data ); 926 | 927 | pfree( buf.data ); 928 | 929 | PG_RETURN_BOOL( getBool( session )); 930 | 931 | } 932 | 933 | /******************************************************************************* 934 | * Local supporting (static) functions 935 | *******************************************************************************/ 936 | 937 | /******************************************************************************* 938 | * initializeModule() 939 | * 940 | * Initializes the debugger proxy module. For now, we just register a callback 941 | * (cleanupAtExit()) that this backend will invoke on exit - we use that 942 | * callback to gracefully close any outstanding connections. 943 | * 944 | * NOTE: this would also be a good place to load the tuple descriptions for 945 | * each of the complex datatypes that we use (breakpoint, var, frame). 946 | */ 947 | 948 | static void initializeModule( void ) 949 | { 950 | static bool initialized = FALSE; 951 | 952 | if( !initialized ) 953 | { 954 | initialized = TRUE; 955 | 956 | on_shmem_exit( cleanupAtExit, 0 ); 957 | } 958 | } 959 | 960 | /******************************************************************************* 961 | * defaultSession() 962 | * 963 | * This function is designed to make it a little easier to build a simple 964 | * debugger client. Instead of managing session identifiers, you can simply 965 | * pass '0' to each function that requires a session ID. When a proxy function 966 | * encounters a session ID of 0, it assumes that you want to work with the most 967 | * recently used session. If you have only one session, you can simply pass 968 | * '0' to every function. This is particularly handy if you're using the proxy 969 | * API from a command line application like psql. 970 | * 971 | * NOTE: If you give this function an invalid sessionHandle it will throw an 972 | * error. A sessionHandle is valid if returned by addSession(). 973 | */ 974 | 975 | static debugSession * defaultSession( sessionHandle handle ) 976 | { 977 | debugSession * session; 978 | 979 | if( handle == 0 ) 980 | { 981 | if( mostRecentSession == NULL ) 982 | ereport( ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg( "invalid session handle" ))); 983 | else 984 | return( mostRecentSession ); 985 | } 986 | else 987 | { 988 | session = findSession( handle ); 989 | 990 | if( session == NULL ) 991 | ereport( ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg( "invalid session handle" ))); 992 | else 993 | { 994 | mostRecentSession = session; 995 | return( session ); 996 | } 997 | } 998 | 999 | return( NULL ); /* keep the compiler happy */ 1000 | } 1001 | 1002 | /******************************************************************************* 1003 | * initSessionHash() 1004 | * 1005 | * Initialize a hash table that we use to map session handles (simple integer 1006 | * values) into debugSession pointers. 1007 | * 1008 | * You should call this function before you use the hash - you can call it 1009 | * as many times as you like, it will only initialize the hash table on the 1010 | * first invocation. 1011 | */ 1012 | 1013 | static void initSessionHash() 1014 | { 1015 | if( sessionHash ) 1016 | return; 1017 | else 1018 | { 1019 | HASHCTL ctl = {0}; 1020 | 1021 | ctl.keysize = sizeof( sessionHandle ); 1022 | ctl.entrysize = sizeof( sessionHashEntry ); 1023 | ctl.hash = tag_hash; 1024 | 1025 | sessionHash = hash_create( "Debugger sessions", 5, &ctl, HASH_ELEM | HASH_FUNCTION ); 1026 | } 1027 | } 1028 | 1029 | /******************************************************************************* 1030 | * addSession() 1031 | * 1032 | * Adds a session (debugSession *) to the hash that we use to map session 1033 | * handles into debugSession pointers. This function returns a handle that 1034 | * you should give back to the debugger client process. When the debugger 1035 | * client calls us again, he gives us the handle and we map that back into 1036 | * a debugSession pointer. That way, we don't have to expose a pointer to 1037 | * the debugger client (which can make for nasty denial of service hacks, not 1038 | * to mention 32-bit vs. 64-bit hassles). 1039 | */ 1040 | 1041 | static sessionHandle addSession( debugSession * session ) 1042 | { 1043 | static sessionHandle nextHandle; 1044 | sessionHashEntry * entry; 1045 | bool found; 1046 | sessionHandle handle; 1047 | 1048 | initSessionHash(); 1049 | 1050 | handle = ++nextHandle; 1051 | 1052 | entry = (sessionHashEntry *)hash_search( sessionHash, &handle, HASH_ENTER, &found ); 1053 | 1054 | entry->m_handle = handle; 1055 | entry->m_session = session; 1056 | 1057 | return( handle ); 1058 | } 1059 | 1060 | /******************************************************************************* 1061 | * findSession() 1062 | * 1063 | * Given a sessionHandle (integer), this function returns the corresponding 1064 | * debugSession pointer. If the sessionHandle is invalid (that is, it's a 1065 | * number not returned by addSession()), this function returns NULL. 1066 | */ 1067 | 1068 | static debugSession * findSession( sessionHandle handle ) 1069 | { 1070 | sessionHashEntry * entry; 1071 | 1072 | initSessionHash(); 1073 | 1074 | if(( entry = hash_search( sessionHash, &handle, HASH_FIND, NULL )) != NULL ) 1075 | { 1076 | return( entry->m_session ); 1077 | } 1078 | else 1079 | { 1080 | return( NULL ); 1081 | } 1082 | } 1083 | 1084 | 1085 | /******************************************************************************* 1086 | * tokenize() 1087 | * 1088 | * This is a re-entrant safe version of the standard C strtok() function. 1089 | * tokenize() will split a string (src) into multiple substrings separated by 1090 | * any of the characters in the delimiter string (delimiters). Each time you 1091 | * call tokenize(), it returns the next subtstring (or NULL when all substrings 1092 | * have been exhausted). The first time you call this function, ctx should be 1093 | * NULL and src should point to the start of the string you are splitting. 1094 | * For every subsequent call, src should be NULL and tokenize() will manage 1095 | * ctx itself. 1096 | * 1097 | * NOTE: the search string (src) is brutally altered by this function - make 1098 | * a copy of the search string before you call tokenize() if you need the 1099 | * original string. 1100 | */ 1101 | 1102 | static char * tokenize( char * src, const char * delimiters, char ** ctx ) 1103 | { 1104 | char * start; 1105 | char * end; 1106 | 1107 | if( src == NULL ) 1108 | src = *ctx; 1109 | 1110 | /* 1111 | * Special case - if delimiters is NULL, we just return the 1112 | * remainder of the string. 1113 | */ 1114 | 1115 | if( delimiters == NULL ) 1116 | return( src ); 1117 | 1118 | if( src == NULL ) 1119 | elog(ERROR, "debugger protocol error: token expected"); 1120 | 1121 | /* 1122 | * Skip past any leading delimiters 1123 | */ 1124 | 1125 | start = src = ( src + strspn( src, delimiters )); 1126 | 1127 | if( *src == '\0' ) 1128 | return( "" ); 1129 | 1130 | if(( end = strpbrk( start, delimiters )) == NULL ) 1131 | { 1132 | *ctx = strchr( start, '\0' ); 1133 | } 1134 | else 1135 | { 1136 | *end = '\0'; 1137 | *ctx = end + 1; 1138 | } 1139 | 1140 | return( start ); 1141 | } 1142 | 1143 | /******************************************************************************* 1144 | * readn() 1145 | * 1146 | * This function reads exactly 'len' bytes from the given socket or it 1147 | * throws an error (ERRCODE_CONNECTION_FAILURE). readn() will hang until 1148 | * the proper number of bytes have been read (or an error occurs). 1149 | * 1150 | * Note: dst must point to a buffer large enough to hold at least 'len' 1151 | * bytes. readn() returns dst (for convenience). 1152 | */ 1153 | 1154 | static void * readn( int serverHandle, void * dst, size_t len ) 1155 | { 1156 | size_t bytesRemaining = len; 1157 | char * buffer = (char *)dst; 1158 | 1159 | if( serverHandle == -1 ) 1160 | ereport( ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg( "given session is not connected" ))); 1161 | 1162 | while( bytesRemaining > 0 ) 1163 | { 1164 | fd_set rmask; 1165 | ssize_t bytesRead; 1166 | 1167 | /* 1168 | * Note: we want to wait for some number of bytes to arrive from the 1169 | * target process, but we also want to notice if the client process 1170 | * disappears. To do that, we'll call select() before we call recv() 1171 | * and we'll tell select() to return as soon as something interesting 1172 | * happens on *either* of the sockets. If the target sends us data 1173 | * first, we're ok (that's what we are expecting to happen). If we 1174 | * detect any activity on the client-side socket (which is the libpq 1175 | * socket), we can assume that something's gone horribly wrong (most 1176 | * likely, the user killed the client by clicking the close button). 1177 | */ 1178 | 1179 | FD_ZERO( &rmask ); 1180 | FD_SET( serverHandle, &rmask ); 1181 | FD_SET( MyProcPort->sock, &rmask ); 1182 | 1183 | switch( select(( serverHandle > MyProcPort->sock ? serverHandle : MyProcPort->sock ) + 1, &rmask, NULL, NULL, NULL )) 1184 | { 1185 | case -1: 1186 | { 1187 | ereport( ERROR, ( errcode(ERRCODE_CONNECTION_FAILURE), errmsg( "select() failed waiting for target" ))); 1188 | break; 1189 | } 1190 | 1191 | case 0: 1192 | { 1193 | /* Timer expired */ 1194 | return( NULL ); 1195 | break; 1196 | } 1197 | 1198 | default: 1199 | { 1200 | /* 1201 | * We got traffic on one of the two sockets. If we see traffic 1202 | * from the client (libpq) connection, just return to the 1203 | * caller so that libpq can process whatever's waiting. 1204 | * Presumably, the only time we'll see any libpq traffic here 1205 | * is when the client process has killed itself... 1206 | */ 1207 | 1208 | if( FD_ISSET( MyProcPort->sock, &rmask )) 1209 | ereport( ERROR, ( errcode(ERRCODE_CONNECTION_FAILURE), errmsg( "debugger connection(client side) terminated" ))); 1210 | break; 1211 | } 1212 | } 1213 | 1214 | bytesRead = recv( serverHandle, buffer, bytesRemaining, 0 ); 1215 | 1216 | if( bytesRead <= 0 && errno != EINTR ) 1217 | { 1218 | ereport( ERROR, (errcode(ERRCODE_CONNECTION_FAILURE), errmsg( "debugger connection terminated" ))); 1219 | return( NULL ); 1220 | } 1221 | 1222 | bytesRemaining -= bytesRead; 1223 | buffer += bytesRead; 1224 | } 1225 | 1226 | return( dst ); 1227 | } 1228 | 1229 | /******************************************************************************* 1230 | * writen() 1231 | * 1232 | * This function writes exactly 'len' bytes to the given socket or it 1233 | * throws an error (ERRCODE_CONNECTION_FAILURE). writen() will hang until 1234 | * the proper number of bytes have been written (or an error occurs). 1235 | */ 1236 | 1237 | static void * writen( int serverHandle, void * src, size_t len ) 1238 | { 1239 | size_t bytesRemaining = len; 1240 | char * buffer = (char *)src; 1241 | 1242 | while( bytesRemaining > 0 ) 1243 | { 1244 | ssize_t bytesWritten; 1245 | 1246 | if(( bytesWritten = send( serverHandle, buffer, bytesRemaining, 0 )) <= 0 ) 1247 | { 1248 | ereport( ERROR, ( errcode( ERRCODE_CONNECTION_FAILURE ), errmsg( "debugger connection terminated" ))); 1249 | return( NULL ); 1250 | } 1251 | 1252 | bytesRemaining -= bytesWritten; 1253 | buffer += bytesWritten; 1254 | } 1255 | 1256 | return( src ); 1257 | } 1258 | 1259 | /******************************************************************************* 1260 | * sendBytes() 1261 | * 1262 | * This function sends 'len' bytes to the server (identfied by a debugSession 1263 | * pointer). 'src' should point to the bytes that you want to send to the 1264 | * server. 1265 | */ 1266 | 1267 | static void sendBytes( debugSession * session, void * src, size_t len ) 1268 | { 1269 | writen( session->serverSocket, src, len ); 1270 | } 1271 | 1272 | 1273 | /******************************************************************************* 1274 | * sendUInt32() 1275 | * 1276 | * This function sends a uint32 value (val) to the debugger server. 1277 | */ 1278 | 1279 | static void sendUInt32( debugSession * session, uint32 val ) 1280 | { 1281 | uint32 netVal = htonl( val ); 1282 | 1283 | sendBytes( session, &netVal, sizeof( netVal )); 1284 | } 1285 | 1286 | /******************************************************************************* 1287 | * sendString() 1288 | * 1289 | * This function sends a string value (src) to the debugger server. 'src' 1290 | * should point to a null-terminated string. We send the length of the string 1291 | * (as a 32-bit unsigned integer), then the bytes that make up the string - we 1292 | * don't send the null-terminator. 1293 | */ 1294 | 1295 | static void sendString( debugSession * session, char * src ) 1296 | { 1297 | size_t len = strlen( src ); 1298 | 1299 | sendUInt32( session, len ); 1300 | sendBytes( session, src, len ); 1301 | } 1302 | 1303 | /******************************************************************************* 1304 | * getBool() 1305 | * 1306 | * getBool() retreives a boolean value (TRUE or FALSE) from the server. We 1307 | * call this function after we ask the server to do something that returns a 1308 | * boolean result (like deleting a breakpoint or depositing a new value). 1309 | */ 1310 | 1311 | static bool getBool( debugSession * session ) 1312 | { 1313 | char * str; 1314 | bool result; 1315 | 1316 | str = getNString( session ); 1317 | 1318 | if (str == NULL) 1319 | elog(ERROR, "debugger protocol error; bool expected"); 1320 | 1321 | if( str[0] == 't' ) 1322 | result = TRUE; 1323 | else 1324 | result = FALSE; 1325 | 1326 | pfree( str ); 1327 | 1328 | return( result ); 1329 | } 1330 | 1331 | 1332 | /******************************************************************************* 1333 | * getUInt32() 1334 | * 1335 | * Reads a 32-bit unsigned value from the server (and returns it in the host's 1336 | * byte ordering) 1337 | */ 1338 | 1339 | static uint32 getUInt32( debugSession * session ) 1340 | { 1341 | uint32 result; 1342 | 1343 | readn( session->serverSocket, &result, sizeof( result )); 1344 | 1345 | return( ntohl( result )); 1346 | } 1347 | 1348 | /****************************************************************************** 1349 | * getNstring() 1350 | * 1351 | * This function is the opposite of sendString() - it reads a string from the 1352 | * debugger server. The server sends the length of the string and then the 1353 | * bytes that make up the string (minus the null-terminator). We palloc() 1354 | * enough space to hold the entire string (including the null-terminator) and 1355 | * return a pointer to that space (after, of course, reading the string from 1356 | * the server and tacking on the null-terminator). 1357 | */ 1358 | 1359 | static char * getNString( debugSession * session ) 1360 | { 1361 | uint32 len = getUInt32( session ); 1362 | 1363 | if( len == 0 ) 1364 | return( NULL ); 1365 | else 1366 | { 1367 | char * result = palloc( len + 1 ); 1368 | 1369 | readn( session->serverSocket, result, len ); 1370 | 1371 | result[len] = '\0'; 1372 | 1373 | return( result ); 1374 | } 1375 | } 1376 | 1377 | /******************************************************************************* 1378 | * closeSession() 1379 | * 1380 | * This function closes (in an orderly manner) the connection with the debugger 1381 | * server. 1382 | */ 1383 | 1384 | static void closeSession( debugSession * session ) 1385 | { 1386 | if( session->serverSocket ) 1387 | closesocket( session->serverSocket ); 1388 | 1389 | if( session->listener ) 1390 | BreakpointCleanupProc( MyProcPid ); 1391 | 1392 | if( session->breakpointString ) 1393 | pfree( session->breakpointString ); 1394 | 1395 | pfree( session ); 1396 | } 1397 | 1398 | /****************************************************************************** 1399 | * cleanupAtExit() 1400 | * 1401 | * This is a callback function that the backend invokes when exiting. At exit, 1402 | * we close any connections that we may still have (connections to debugger 1403 | * servers, that is). 1404 | */ 1405 | 1406 | static void cleanupAtExit( int code, Datum arg ) 1407 | { 1408 | /* 1409 | * FIXME: we should clean up all of the sessions stored in the 1410 | * sessionHash. 1411 | */ 1412 | 1413 | if( mostRecentSession ) 1414 | closeSession( mostRecentSession ); 1415 | 1416 | mostRecentSession = NULL; 1417 | } 1418 | 1419 | /******************************************************************************* 1420 | * getResultTupleDesc() 1421 | * 1422 | * If this function returns (without throwing an error), it returns a pointer 1423 | * to a description of the tuple that should be returned by the caller. 1424 | * 1425 | * NOTE: the caller must have been called in a context that can accept a 1426 | * set, not a context that expects a tuple. That means that you 1427 | * must invoke our caller with: 1428 | * select * from foo(); 1429 | * instead of: 1430 | * select foo(); 1431 | */ 1432 | 1433 | static TupleDesc getResultTupleDesc( FunctionCallInfo fcinfo ) 1434 | { 1435 | ReturnSetInfo * rsinfo = (ReturnSetInfo *)fcinfo->resultinfo; 1436 | 1437 | if( rsinfo == NULL ) 1438 | { 1439 | ereport(ERROR, 1440 | (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), 1441 | errmsg("function returning record called in context " 1442 | "that cannot accept type record"))); 1443 | } 1444 | return( rsinfo->expectedDesc ); 1445 | } 1446 | -------------------------------------------------------------------------------- /pldbgapi.control: -------------------------------------------------------------------------------- 1 | # pldebugger extension control file 2 | comment = 'server-side support for debugging PL/pgSQL functions' 3 | default_version = '1.1' 4 | module_pathname = '$libdir/pldbgapi' 5 | relocatable = true 6 | -------------------------------------------------------------------------------- /pldebugger.h: -------------------------------------------------------------------------------- 1 | /* 2 | * pldebugger.h - 3 | * 4 | * Main debugger header 5 | * 6 | * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 7 | * 8 | * Licensed under the Artistic License v2.0, see 9 | * https://opensource.org/licenses/artistic-license-2.0 10 | * for full details 11 | */ 12 | 13 | #ifndef PLDEBUGGER_H 14 | #define PLDEBUGGER_H 15 | 16 | #include "globalbp.h" 17 | #include "storage/lwlock.h" 18 | 19 | /* 20 | * We keep one per_session_ctx structure per backend. This structure holds all 21 | * of the stuff that we need to track from one function call to the next. 22 | */ 23 | typedef struct 24 | { 25 | bool step_into_next_func; /* Should we step into the next function? */ 26 | int client_r; /* Read stream connected to client */ 27 | int client_w; /* Write stream connected to client */ 28 | } per_session_ctx_t; 29 | 30 | extern per_session_ctx_t per_session_ctx; 31 | 32 | 33 | /* 34 | * errorHandlerCtx 35 | * 36 | * We use setjmp() and longjmp() to handle network errors. Because we want to 37 | * be able to stack setjmp()/longjmp() savepoints, we define a structure to 38 | * wrap sigjmp_buf's - we have to do that because sigjmp_buf is defined as an 39 | * array on some platforms (like Win32). 40 | */ 41 | 42 | typedef struct 43 | { 44 | sigjmp_buf m_savepoint; 45 | } errorHandlerCtx; 46 | 47 | extern errorHandlerCtx client_lost; 48 | 49 | #define PLDBG_HELP '?' 50 | #define PLDBG_CONTINUE 'c' 51 | #define PLDBG_SET_BREAKPOINT 'b' 52 | #define PLDBG_CLEAR_BREAKPOINT 'f' 53 | #define PLDBG_PRINT_VAR 'p' 54 | #define PLDBG_PRINT_STACK '$' 55 | #define PLDBG_LIST_BREAKPOINTS 'l' 56 | #define PLDBG_STEP_INTO 's' 57 | #define PLDBG_STEP_OVER 'o' 58 | #define PLDBG_LIST '#' 59 | #define PLDBG_INFO_VARS 'i' 60 | #define PLDBG_SELECT_FRAME '^' 61 | #define PLDBG_DEPOSIT 'd' 62 | #define PLDBG_RESTART 'r' 63 | #define PLDBG_STOP 'x' 64 | 65 | typedef struct 66 | { 67 | void (* initialize)(void); 68 | bool (* frame_belongs_to_me)(ErrorContextCallback *frame); 69 | void (* send_stack_frame)(ErrorContextCallback *frame); 70 | void (* send_vars)(ErrorContextCallback *frame); 71 | void (* select_frame)(ErrorContextCallback *frame); 72 | void (* print_var)(ErrorContextCallback *frame, const char *var_name, int lineno); 73 | bool (* do_deposit)(ErrorContextCallback *frame, const char *var_name, 74 | int line_number, const char *value); 75 | Oid (* get_func_oid)(ErrorContextCallback *frame); 76 | void (* send_cur_line)(ErrorContextCallback *frame); 77 | } debugger_language_t; 78 | 79 | /* in plugin_debugger.c */ 80 | extern void initGlobalBreakpoints(void); 81 | extern bool plugin_debugger_main_loop(void); 82 | 83 | extern bool breakAtThisLine( Breakpoint ** dst, eBreakpointScope * scope, Oid funcOid, int lineNumber ); 84 | extern bool attach_to_proxy( Breakpoint * breakpoint ); 85 | extern void setBreakpoint( char * command ); 86 | extern void clearBreakpoint( char * command ); 87 | extern bool breakpointsForFunction( Oid funcOid ); 88 | 89 | extern void dbg_send( const char *fmt, ... ) 90 | #ifdef PG_PRINTF_ATTRIBUTE 91 | /* This extension allows gcc to check the format string for consistency with 92 | the supplied arguments. */ 93 | __attribute__((format(PG_PRINTF_ATTRIBUTE, 1, 2))) 94 | #endif 95 | ; 96 | extern char * dbg_read_str(void); 97 | 98 | extern LWLockId getPLDebuggerLock(void); 99 | 100 | /* in plpgsql_debugger.c */ 101 | extern void plpgsql_debugger_fini(void); 102 | 103 | extern debugger_language_t plpgsql_debugger_lang; 104 | #ifdef INCLUDE_PACKAGE_SUPPORT 105 | extern debugger_language_t spl_debugger_lang; 106 | #endif 107 | 108 | #if PG_VERSION_NUM >= 110000 109 | #ifndef TRUE 110 | #define TRUE true 111 | #endif 112 | #ifndef FALSE 113 | #define FALSE false 114 | #endif 115 | #endif 116 | 117 | #endif 118 | -------------------------------------------------------------------------------- /pldebugger.proj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | edb-postgres 28 | 29 | 1 30 | /D "INCLUDE_PACKAGE_SUPPORT" 31 | 32 | 33 | 34 | 35 | postgres 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | /Od /MDd /Zi /D "DEBUG=1" /D "_DEBUG" 45 | /DEBUG /defaultlib:$(PGPATH)\Debug\$(postgres)\$(postgres).lib 46 | 47 | 48 | /Od /MDd /Zi /D "DEBUG=1" /D "_DEBUG" 49 | /DEBUG /defaultlib:$(PGBUILDPATH)\src\backend\$(postgres).lib 50 | 51 | 52 | 53 | 54 | /Ox /MD /GF 55 | /defaultlib:$(PGPATH)\Release\$(postgres)\$(postgres).lib 56 | 57 | 58 | /Ox /MD /GF 59 | /defaultlib:$(PGBUILDPATH)\src\backend\$(postgres).lib 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | /MACHINE:X64 70 | 71 | 72 | 73 | 74 | /D "_USE_32BIT_TIME_T" 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | /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" 83 | 84 | 85 | /DLL $(XTRA_LDFLAGS) $(XTRA_ARCH_LDFLAGS) /defaultlib:user32 /defaultlib:netapi32 /defaultlib:advapi32 /defaultlib:shell32 /defaultlib:ws2_32 /defaultlib:Secur32.lib 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 119 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 132 | 133 | 134 | 135 | 137 | 139 | 140 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /plpgsql_debugger.c: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * plpgsql_debugger.c - Debugger for the PL/pgSQL procedural language 3 | * 4 | * Copyright (c) 2004-2018 EnterpriseDB Corporation. All Rights Reserved. 5 | * 6 | * Licensed under the Artistic License v2.0, see 7 | * https://opensource.org/licenses/artistic-license-2.0 8 | * for full details 9 | * 10 | **********************************************************************/ 11 | 12 | #include "postgres.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "lib/stringinfo.h" 22 | #include "catalog/pg_proc.h" 23 | #include "catalog/pg_type.h" 24 | #include "globalbp.h" 25 | #include "utils/array.h" 26 | #include "utils/builtins.h" 27 | #include "utils/syscache.h" 28 | #include "miscadmin.h" 29 | 30 | #if INCLUDE_PACKAGE_SUPPORT 31 | #include "spl.h" 32 | #include "catalog/edb_variable.h" 33 | #else 34 | #include "plpgsql.h" 35 | #endif 36 | 37 | #include "pldebugger.h" 38 | 39 | /* Include header for GETSTRUCT */ 40 | #if (PG_VERSION_NUM >= 90300) 41 | #include "access/htup_details.h" 42 | #endif 43 | 44 | /* 45 | * We use a var_value structure to record a little extra information about 46 | * each variable. 47 | */ 48 | 49 | typedef struct 50 | { 51 | bool isnull; /* TRUE -> this variable IS NULL */ 52 | bool visible; /* hidden or visible? see is_visible_datum() */ 53 | bool duplicate_name; /* Is this one of many vars with same name? */ 54 | } var_value; 55 | 56 | /* 57 | * When the debugger decides that it needs to step through (or into) a 58 | * particular function invocation, it allocates a dbg_ctx and records the 59 | * address of that structure in the executor's context structure 60 | * (estate->plugin_info). 61 | * 62 | * The dbg_ctx keeps track of all of the information we need to step through 63 | * code and display variable values 64 | */ 65 | 66 | typedef struct 67 | { 68 | PLpgSQL_function * func; /* Function definition */ 69 | bool stepping; /* If TRUE, stop at next statement */ 70 | var_value * symbols; /* Extra debugger-private info about variables */ 71 | char ** argNames; /* Argument names */ 72 | int argNameCount; /* Number of names pointed to by argNames */ 73 | void (* error_callback)(void *arg); 74 | void (* assign_expr)( PLpgSQL_execstate *estate, PLpgSQL_datum *target, PLpgSQL_expr *expr ); 75 | #if INCLUDE_PACKAGE_SUPPORT 76 | PLpgSQL_package * package; 77 | #endif 78 | } dbg_ctx; 79 | 80 | static void dbg_startup( PLpgSQL_execstate * estate, PLpgSQL_function * func ); 81 | static void dbg_newstmt( PLpgSQL_execstate * estate, PLpgSQL_stmt * stmt ); 82 | static void initialize_plugin_info( PLpgSQL_execstate * estate, PLpgSQL_function * func ); 83 | 84 | static char ** fetchArgNames( PLpgSQL_function * func, int * nameCount ); 85 | static PLpgSQL_var * find_var_by_name( const PLpgSQL_execstate * estate, const char * var_name, int lineno, int * index ); 86 | 87 | static bool is_datum_visible( PLpgSQL_datum * datum ); 88 | static bool is_var_visible( PLpgSQL_execstate * frame, int var_no ); 89 | static bool datumIsNull(PLpgSQL_datum *datum); 90 | static bool varIsArgument(const PLpgSQL_execstate *estate, PLpgSQL_function *func, int varNo, char **p_argname); 91 | static char * get_text_val( PLpgSQL_var * var, char ** name, char ** type ); 92 | 93 | #if INCLUDE_PACKAGE_SUPPORT 94 | static const char * plugin_name = "spl_plugin"; 95 | #else 96 | static const char * plugin_name = "PLpgSQL_plugin"; 97 | #endif 98 | 99 | static PLpgSQL_plugin plugin_funcs = { dbg_startup, NULL, NULL, dbg_newstmt, NULL }; 100 | 101 | /* 102 | * pldebugger_language_t interface. 103 | */ 104 | static void plpgsql_debugger_init(void); 105 | static bool plpgsql_frame_belongs_to_me(ErrorContextCallback *frame); 106 | static void plpgsql_send_stack_frame(ErrorContextCallback *frame); 107 | static void plpgsql_send_vars(ErrorContextCallback *frame); 108 | static void plpgsql_select_frame(ErrorContextCallback *frame); 109 | static void plpgsql_print_var(ErrorContextCallback *frame, const char *var_name, int lineno); 110 | static bool plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name, int line_number, const char *value); 111 | static Oid plpgsql_get_func_oid(ErrorContextCallback *frame); 112 | static void plpgsql_send_cur_line(ErrorContextCallback *frame); 113 | 114 | #if INCLUDE_PACKAGE_SUPPORT 115 | debugger_language_t spl_debugger_lang = 116 | #else 117 | debugger_language_t plpgsql_debugger_lang = 118 | #endif 119 | { 120 | plpgsql_debugger_init, 121 | plpgsql_frame_belongs_to_me, 122 | plpgsql_send_stack_frame, 123 | plpgsql_send_vars, 124 | plpgsql_select_frame, 125 | plpgsql_print_var, 126 | plpgsql_do_deposit, 127 | plpgsql_get_func_oid, 128 | plpgsql_send_cur_line 129 | }; 130 | 131 | /* Install this module as an PL/pgSQL instrumentation plugin */ 132 | static void 133 | plpgsql_debugger_init(void) 134 | { 135 | PLpgSQL_plugin ** var_ptr = (PLpgSQL_plugin **) find_rendezvous_variable( plugin_name ); 136 | 137 | *var_ptr = &plugin_funcs; 138 | } 139 | 140 | 141 | /********************************************************************** 142 | * Functions implemeting the pldebugger_language_t interface 143 | **********************************************************************/ 144 | 145 | static bool 146 | plpgsql_frame_belongs_to_me(ErrorContextCallback *frame) 147 | { 148 | return (frame->callback == plugin_funcs.error_callback); 149 | } 150 | 151 | /* 152 | * plpgsql_send_stack_frame() 153 | * 154 | * This function sends information about a single stack frame to the debugger 155 | * client. This function is called by send_stack() whenever send_stack() 156 | * finds a PL/pgSQL call in the stack (remember, the call stack may contain 157 | * stack frames for functions written in other languages like PL/Tcl). 158 | */ 159 | static void 160 | plpgsql_send_stack_frame(ErrorContextCallback *frame) 161 | { 162 | PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; 163 | #if (PG_VERSION_NUM >= 80500) 164 | PLpgSQL_function * func = estate->func; 165 | #else 166 | PLpgSQL_function * func = estate->err_func; 167 | #endif 168 | PLpgSQL_stmt * stmt = estate->err_stmt; 169 | int argNameCount; 170 | char ** argNames = fetchArgNames( func, &argNameCount ); 171 | StringInfo result = makeStringInfo(); 172 | char * delimiter = ""; 173 | int arg; 174 | 175 | /* 176 | * Send the name, function OID, and line number for this frame 177 | */ 178 | 179 | appendStringInfo( result, "%s:%d:%d:", 180 | #if (PG_VERSION_NUM >= 90200) 181 | func->fn_signature, 182 | #else 183 | func->fn_name, 184 | #endif 185 | func->fn_oid, 186 | stmt->lineno ); 187 | 188 | /* 189 | * Now assemble a string that shows the argument names and value for this frame 190 | */ 191 | 192 | for( arg = 0; arg < func->fn_nargs; ++arg ) 193 | { 194 | int index = func->fn_argvarnos[arg]; 195 | PLpgSQL_datum *argDatum = (PLpgSQL_datum *)estate->datums[index]; 196 | char *value; 197 | 198 | /* value should be an empty string if argDatum is null*/ 199 | if( datumIsNull( argDatum )) 200 | value = pstrdup( "" ); 201 | else 202 | value = get_text_val((PLpgSQL_var*)argDatum, NULL, NULL ); 203 | 204 | if( argNames && argNames[arg] && argNames[arg][0] ) 205 | appendStringInfo( result, "%s%s=%s", delimiter, argNames[arg], value ); 206 | else 207 | appendStringInfo( result, "%s$%d=%s", delimiter, arg+1, value ); 208 | 209 | pfree( value ); 210 | 211 | delimiter = ", "; 212 | } 213 | 214 | dbg_send( "%s", result->data ); 215 | } 216 | 217 | /* 218 | * varIsArgument() : 219 | * 220 | * Returns true if it's an argument of the function. In case the function is an 221 | * EDB-SPL nested function, it returns true only if it is an argument of the 222 | * current nested function; in all other cases it returns false, even if it's 223 | * an argument of any of the outer nested functions in the current nested 224 | * function call stack. 225 | * 226 | * If the variable is a *named* argument, the argument name is passed 227 | * through 'p_argname'. In case of nested functions, p_argname is set if the 228 | * variable name is a named argument of any of the nested functions in the 229 | * current nested function call stack. 230 | * 231 | * varNo is the estate->datums index. 232 | */ 233 | static bool 234 | varIsArgument(const PLpgSQL_execstate *estate, PLpgSQL_function *func, 235 | int varNo, char **p_argname) 236 | { 237 | #if INCLUDE_PACKAGE_SUPPORT && (PG_VERSION_NUM >= 90600) 238 | 239 | /* 240 | * If this variable is actually an argument of this function, return the 241 | * argument name. Such argument variables have '$n' refname, so we need to 242 | * use the function argument name if it's a named argument. 243 | */ 244 | if (func->fn_nallargs > 0 && 245 | varNo >= func->fn_first_argno && 246 | varNo < func->fn_first_argno + func->fn_nallargs) 247 | { 248 | char *argname = func->fn_argnames[varNo - func->fn_first_argno]; 249 | 250 | if (argname && argname[0]) 251 | *p_argname = argname; 252 | 253 | return true; 254 | } 255 | 256 | /* 257 | * Now check if it is an argument of any of the outer functions. If yes, 258 | * we need to get the argument name in case it is a named argument. 259 | */ 260 | if (func->parent) 261 | (void) varIsArgument(estate, func->parent, varNo, p_argname); 262 | 263 | /* It is not an argument of the current function. */ 264 | return false; 265 | 266 | #else 267 | 268 | dbg_ctx *dbg_info = (dbg_ctx *) estate->plugin_info; 269 | bool isArg = false; 270 | 271 | if (varNo < dbg_info->func->fn_nargs) 272 | isArg = true; 273 | 274 | /* Get it's name if it's a named argument. */ 275 | if (varNo < dbg_info->argNameCount) 276 | { 277 | isArg = true; 278 | 279 | if (dbg_info->argNames && dbg_info->argNames[varNo] && 280 | dbg_info->argNames[varNo][0]) 281 | { 282 | *p_argname = dbg_info->argNames[varNo]; 283 | } 284 | } 285 | 286 | return isArg; 287 | 288 | #endif 289 | } 290 | 291 | /* 292 | * plpgsql_send_vars() 293 | * 294 | * This function sends a list of variables (names, types, values...) to 295 | * the proxy process. We send information about the variables defined in 296 | * the given frame (local variables) and parameter values. 297 | */ 298 | static void 299 | plpgsql_send_vars(ErrorContextCallback *frame) 300 | { 301 | PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; 302 | dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info; 303 | int i; 304 | 305 | for( i = 0; i < estate->ndatums; i++ ) 306 | { 307 | if( is_var_visible( estate, i )) 308 | { 309 | switch( estate->datums[i]->dtype ) 310 | { 311 | #if (PG_VERSION_NUM >= 110000) 312 | case PLPGSQL_DTYPE_PROMISE: 313 | #endif 314 | case PLPGSQL_DTYPE_VAR: 315 | { 316 | PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i]; 317 | char * val; 318 | char * name = var->refname; 319 | bool isArg; 320 | 321 | isArg = varIsArgument(estate, dbg_info->func, i, &name); 322 | 323 | if( datumIsNull((PLpgSQL_datum *)var )) 324 | val = "NULL"; 325 | else 326 | val = get_text_val( var, NULL, NULL ); 327 | 328 | dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s", 329 | name, 330 | isArg ? 'A' : 'L', 331 | var->lineno, 332 | dbg_info->symbols[i].duplicate_name ? 'f' : 't', 333 | var->isconst ? 't':'f', 334 | var->notnull ? 't':'f', 335 | var->datatype ? var->datatype->typoid : InvalidOid, 336 | val ); 337 | 338 | break; 339 | } 340 | #if 0 341 | FIXME: implement other types 342 | 343 | case PLPGSQL_DTYPE_REC: 344 | { 345 | PLpgSQL_rec * rec = (PLpgSQL_rec *) estate->datums[i]; 346 | int att; 347 | char * typeName; 348 | 349 | if (rec->tupdesc != NULL) 350 | { 351 | for( att = 0; att < rec->tupdesc->natts; ++att ) 352 | { 353 | typeName = SPI_gettype( rec->tupdesc, att + 1 ); 354 | 355 | dbg_send( "o:%s.%s:%d:%d:%d:%d:%s\n", 356 | rec->refname, NameStr( rec->tupdesc->attrs[att]->attname ), 357 | 0, rec->lineno, 0, rec->tupdesc->attrs[att]->attnotnull, typeName ? typeName : "" ); 358 | 359 | if( typeName ) 360 | pfree( typeName ); 361 | } 362 | } 363 | break; 364 | } 365 | #endif 366 | case PLPGSQL_DTYPE_ROW: 367 | case PLPGSQL_DTYPE_REC: 368 | case PLPGSQL_DTYPE_RECFIELD: 369 | #if (PG_VERSION_NUM < 140000) 370 | case PLPGSQL_DTYPE_ARRAYELEM: 371 | #endif 372 | #if (PG_VERSION_NUM < 110000) 373 | case PLPGSQL_DTYPE_EXPR: 374 | #endif 375 | { 376 | /* FIXME: implement other types */ 377 | break; 378 | } 379 | } 380 | } 381 | } 382 | 383 | #if INCLUDE_PACKAGE_SUPPORT 384 | /* If this frame represents a package function/procedure, send the package variables too */ 385 | if( dbg_info->package != NULL ) 386 | { 387 | PLpgSQL_package * package = dbg_info->package; 388 | int varIndex; 389 | 390 | for( varIndex = 0; varIndex < package->ndatums; ++varIndex ) 391 | { 392 | PLpgSQL_datum * datum = package->datums[varIndex]; 393 | 394 | switch( datum->dtype ) 395 | { 396 | case PLPGSQL_DTYPE_VAR: 397 | { 398 | PLpgSQL_var * var = (PLpgSQL_var *) datum; 399 | char * val; 400 | char * name = var->refname; 401 | 402 | if( datumIsNull((PLpgSQL_datum *)var )) 403 | val = "NULL"; 404 | else 405 | val = get_text_val( var, NULL, NULL ); 406 | 407 | dbg_send( "%s:%c:%d:%c:%c:%c:%d:%s", 408 | name, 409 | 'P', /* variable class - P means package var */ 410 | var->lineno, 411 | 'f', /* duplicate name? */ 412 | var->isconst ? 't':'f', 413 | var->notnull ? 't':'f', 414 | var->datatype ? var->datatype->typoid : InvalidOid, 415 | val ); 416 | 417 | break; 418 | } 419 | } 420 | } 421 | } 422 | #endif 423 | 424 | dbg_send( "%s", "" ); /* empty string indicates end of list */ 425 | } 426 | 427 | static void 428 | plpgsql_select_frame(ErrorContextCallback *frame) 429 | { 430 | PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; 431 | 432 | /* 433 | * When a frame is selected, ensure that we've initialized its 434 | * plugin_info. 435 | */ 436 | if (estate->plugin_info == NULL) 437 | { 438 | #if (PG_VERSION_NUM >= 80500) 439 | initialize_plugin_info(estate, estate->func); 440 | #else 441 | initialize_plugin_info(estate, estate->err_func); 442 | #endif 443 | } 444 | } 445 | 446 | /* 447 | * --------------------------------------------------------------------- 448 | * find_var_by_name() 449 | * 450 | * This function returns the PLpgSQL_var pointer that corresponds to 451 | * named variable (var_name). If the named variable can't be found, 452 | * find_var_by_name() returns NULL. 453 | * 454 | * If the index is non-NULL, this function will set *index to the 455 | * named variables index withing estate->datums[] 456 | */ 457 | 458 | static PLpgSQL_var * 459 | find_var_by_name(const PLpgSQL_execstate * estate, const char * var_name, int lineno, int * index) 460 | { 461 | dbg_ctx * dbg_info = (dbg_ctx *)estate->plugin_info; 462 | PLpgSQL_function * func = dbg_info->func; 463 | int i; 464 | 465 | for( i = 0; i < func->ndatums; i++ ) 466 | { 467 | PLpgSQL_var * var = (PLpgSQL_var *) estate->datums[i]; 468 | size_t len = strlen(var->refname); 469 | 470 | if(len != strlen(var_name)) 471 | continue; 472 | 473 | if( strncmp( var->refname, var_name, len) == 0 ) 474 | { 475 | if(( lineno == -1 ) || ( var->lineno == lineno )) 476 | { 477 | /* Found the named variable - return the index if the caller wants it */ 478 | 479 | if( index ) 480 | *index = i; 481 | } 482 | 483 | return( var ); 484 | } 485 | } 486 | 487 | /* We can't find the variable named by the caller - return NULL */ 488 | 489 | return( NULL ); 490 | 491 | } 492 | 493 | static PLpgSQL_datum * 494 | find_datum_by_name(const PLpgSQL_execstate *frame, const char *var_name, 495 | int lineNo, int *index) 496 | { 497 | dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; 498 | int i; 499 | 500 | #if INCLUDE_PACKAGE_SUPPORT 501 | 502 | if( var_name[0] == '@' ) 503 | { 504 | /* This is a package variable (it's name starts with a '@') */ 505 | int varIndex; 506 | 507 | if( dbg_info == NULL ) 508 | return( NULL ); 509 | 510 | if( dbg_info->package == NULL ) 511 | return( NULL ); 512 | 513 | for( varIndex = 0; varIndex < dbg_info->package->ndatums; ++varIndex ) 514 | { 515 | PLpgSQL_datum * datum = dbg_info->package->datums[varIndex]; 516 | 517 | switch( datum->dtype ) 518 | { 519 | #if (PG_VERSION_NUM >= 110000) 520 | case PLPGSQL_DTYPE_PROMISE: 521 | #endif 522 | case PLPGSQL_DTYPE_VAR: 523 | { 524 | PLpgSQL_var * var = (PLpgSQL_var *) datum; 525 | 526 | if( strcmp( var->refname, var_name+1 ) == 0 ) 527 | return( datum ); 528 | break; 529 | } 530 | } 531 | } 532 | 533 | return( NULL ); 534 | } 535 | #endif 536 | 537 | for( i = 0; i < frame->ndatums; ++i ) 538 | { 539 | char * datumName = NULL; 540 | int datumLineno = -1; 541 | 542 | switch( frame->datums[i]->dtype ) 543 | { 544 | #if (PG_VERSION_NUM >= 110000) 545 | case PLPGSQL_DTYPE_PROMISE: 546 | #endif 547 | case PLPGSQL_DTYPE_VAR: 548 | case PLPGSQL_DTYPE_ROW: 549 | case PLPGSQL_DTYPE_REC: 550 | { 551 | PLpgSQL_variable * var = (PLpgSQL_variable *)frame->datums[i]; 552 | 553 | datumName = var->refname; 554 | datumLineno = var->lineno; 555 | 556 | (void) varIsArgument(frame, dbg_info->func, i, &datumName); 557 | 558 | break; 559 | } 560 | 561 | case PLPGSQL_DTYPE_RECFIELD: 562 | #if (PG_VERSION_NUM < 140000) 563 | case PLPGSQL_DTYPE_ARRAYELEM: 564 | #endif 565 | #if (PG_VERSION_NUM < 110000) 566 | case PLPGSQL_DTYPE_EXPR: 567 | #endif 568 | #if (PG_VERSION_NUM <= 80400) 569 | case PLPGSQL_DTYPE_TRIGARG: 570 | #endif 571 | { 572 | break; 573 | } 574 | } 575 | 576 | if( datumName == NULL ) 577 | continue; 578 | 579 | if( strcmp( var_name, datumName ) == 0 ) 580 | { 581 | if( lineNo == -1 || lineNo == datumLineno ) 582 | { 583 | if( index ) 584 | *index = i; 585 | 586 | return( frame->datums[i] ); 587 | } 588 | } 589 | } 590 | 591 | return( NULL ); 592 | } 593 | 594 | /* 595 | * --------------------------------------------------------------------- 596 | * print_var() 597 | * 598 | * This function will print (that is, send to the debugger client) the 599 | * type and value of the given variable. 600 | */ 601 | static void 602 | print_var(const PLpgSQL_execstate *frame, const char *var_name, int lineno, 603 | const PLpgSQL_var *tgt) 604 | { 605 | char * extval; 606 | HeapTuple typeTup; 607 | Form_pg_type typeStruct; 608 | FmgrInfo finfo_output; 609 | dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; 610 | 611 | if( tgt->isnull ) 612 | { 613 | if( dbg_info->symbols[tgt->dno].duplicate_name ) 614 | dbg_send( "v:%s(%d):NULL\n", var_name, lineno ); 615 | else 616 | dbg_send( "v:%s:NULL\n", var_name ); 617 | return; 618 | } 619 | 620 | /* Find the output function for this data type */ 621 | 622 | typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( tgt->datatype->typoid ), 0, 0, 0 ); 623 | 624 | if( !HeapTupleIsValid( typeTup )) 625 | { 626 | dbg_send( "v:%s(%d):***can't find type\n", var_name, lineno ); 627 | return; 628 | } 629 | 630 | typeStruct = (Form_pg_type)GETSTRUCT( typeTup ); 631 | 632 | /* Now invoke the output function to convert the variable into a null-terminated string */ 633 | 634 | fmgr_info( typeStruct->typoutput, &finfo_output ); 635 | 636 | extval = DatumGetCString( FunctionCall3( &finfo_output, tgt->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); 637 | 638 | /* Send the name:value to the debugger client */ 639 | 640 | if( dbg_info->symbols[tgt->dno].duplicate_name ) 641 | dbg_send( "v:%s(%d):%s\n", var_name, lineno, extval ); 642 | else 643 | dbg_send( "v:%s:%s\n", var_name, extval ); 644 | 645 | pfree( extval ); 646 | ReleaseSysCache( typeTup ); 647 | } 648 | 649 | static void 650 | print_row(const PLpgSQL_execstate *frame, const char *var_name, int lineno, 651 | const PLpgSQL_row * tgt) 652 | { 653 | /* XXX. Shouldn't there be some code here? */ 654 | } 655 | 656 | static void 657 | print_rec(const PLpgSQL_execstate *frame, const char *var_name, int lineno, 658 | const PLpgSQL_rec *tgt) 659 | { 660 | int attNo; 661 | 662 | TupleDesc rec_tupdesc; 663 | HeapTuple tuple; 664 | 665 | #if (PG_VERSION_NUM >= 110000) && !defined(INCLUDE_PACKAGE_SUPPORT) 666 | if (tgt->erh == NULL || 667 | ExpandedRecordIsEmpty(tgt->erh)) 668 | return; 669 | 670 | rec_tupdesc = expanded_record_get_tupdesc(tgt->erh); 671 | tuple = expanded_record_get_tuple(tgt->erh); 672 | #else 673 | if (tgt->tupdesc == NULL) 674 | return; 675 | 676 | rec_tupdesc = tgt->tupdesc; 677 | tuple = tgt->tup; 678 | #endif 679 | 680 | for( attNo = 0; attNo < rec_tupdesc->natts; ++attNo ) 681 | { 682 | char * extval = SPI_getvalue( tuple, rec_tupdesc, attNo + 1 ); 683 | 684 | dbg_send( "v:%s.%s:%s\n", var_name, NameStr( TupleDescAttr(rec_tupdesc, attNo)->attname ), extval ? extval : "NULL" ); 685 | 686 | if( extval ) 687 | pfree( extval ); 688 | } 689 | } 690 | 691 | static void 692 | print_recfield(const PLpgSQL_execstate *frame, const char *var_name, 693 | int lineno, const PLpgSQL_recfield *tgt) 694 | { 695 | /* XXX. Shouldn't there be some code here? */ 696 | } 697 | 698 | static void 699 | plpgsql_print_var(ErrorContextCallback *frame, const char *var_name, 700 | int lineno) 701 | { 702 | PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; 703 | PLpgSQL_variable * generic = NULL; 704 | 705 | /* Try to find the given variable */ 706 | 707 | if(( generic = (PLpgSQL_variable*) find_var_by_name( estate, var_name, lineno, NULL )) == NULL ) 708 | { 709 | dbg_send( "v:%s(%d):Unknown variable (or not in scope)\n", var_name, lineno ); 710 | return; 711 | } 712 | 713 | switch( generic->dtype ) 714 | { 715 | #if (PG_VERSION_NUM >= 110000) 716 | case PLPGSQL_DTYPE_PROMISE: 717 | #endif 718 | case PLPGSQL_DTYPE_VAR: 719 | print_var( estate, var_name, lineno, (PLpgSQL_var *) generic ); 720 | break; 721 | 722 | case PLPGSQL_DTYPE_ROW: 723 | print_row( estate, var_name, lineno, (PLpgSQL_row *) generic ); 724 | break; 725 | 726 | case PLPGSQL_DTYPE_REC: 727 | print_rec( estate, var_name, lineno, (PLpgSQL_rec *) generic ); 728 | break; 729 | 730 | case PLPGSQL_DTYPE_RECFIELD: 731 | print_recfield( estate, var_name, lineno, (PLpgSQL_recfield *) generic ); 732 | break; 733 | 734 | #if (PG_VERSION_NUM < 140000) 735 | case PLPGSQL_DTYPE_ARRAYELEM: 736 | #endif 737 | #if (PG_VERSION_NUM < 110000) 738 | case PLPGSQL_DTYPE_EXPR: 739 | #endif 740 | /** 741 | * FIXME:: 742 | * Hmm.. Shall we print the values for expression/array element? 743 | **/ 744 | break; 745 | } 746 | } 747 | 748 | /* 749 | * --------------------------------------------------------------------- 750 | * mark_duplicate_names() 751 | * 752 | * In a PL/pgSQL function/procedure you can declare many variables with 753 | * the same name as long as the name is unique within a scope. The PL 754 | * compiler co-mingles all variables into a single symbol table without 755 | * indicating (at run-time) when a variable comes into scope. 756 | * 757 | * When we display a variable to the user, we want to show an undecorated 758 | * name unless the given variable has duplicate declarations (in nested 759 | * scopes). If we detect that a variable has duplicate declarations, we 760 | * decorate the name with the line number at which each instance is 761 | * declared. This function detects duplicate names and marks duplicates 762 | * in our private symbol table. 763 | */ 764 | static void 765 | mark_duplicate_names(const PLpgSQL_execstate *estate, int var_no) 766 | { 767 | dbg_ctx * dbg_info = (dbg_ctx *)estate->plugin_info; 768 | 769 | if( dbg_info->symbols[var_no].duplicate_name ) 770 | { 771 | /* already detected as a duplicate name - just go home */ 772 | return; 773 | } 774 | 775 | /* 776 | * FIXME: Handle other dtypes here too - for now, we just assume 777 | * that all other types have duplicate names 778 | */ 779 | 780 | if( estate->datums[var_no]->dtype != PLPGSQL_DTYPE_VAR ) 781 | { 782 | dbg_info->symbols[var_no].duplicate_name = TRUE; 783 | return; 784 | } 785 | else 786 | { 787 | PLpgSQL_var * var = (PLpgSQL_var *)estate->datums[var_no]; 788 | char * var_name = var->refname; 789 | int i; 790 | 791 | for( i = 0; i < estate->ndatums; ++i ) 792 | { 793 | if( i != var_no ) 794 | { 795 | if( estate->datums[i]->dtype != PLPGSQL_DTYPE_VAR ) 796 | continue; 797 | 798 | var = (PLpgSQL_var *)estate->datums[i]; 799 | 800 | if( strcmp( var_name, var->refname ) == 0 ) 801 | { 802 | dbg_info->symbols[var_no].duplicate_name = TRUE; 803 | dbg_info->symbols[i].duplicate_name = TRUE; 804 | } 805 | } 806 | } 807 | } 808 | } 809 | 810 | /* 811 | * --------------------------------------------------------------------- 812 | * completeFrame() 813 | * 814 | * This function ensures that the given execution frame contains 815 | * all of the information we need in order to debug it. In particular, 816 | * we create an array that extends the frame->datums[] array. 817 | * We need to know which variables should be visible to the 818 | * debugger client (we hide some of them by convention) and 819 | * we need to figure out which names are unique and which 820 | * are duplicates. 821 | */ 822 | static void 823 | completeFrame(PLpgSQL_execstate *frame) 824 | { 825 | dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; 826 | PLpgSQL_function * func = dbg_info->func; 827 | int i; 828 | 829 | if( dbg_info->symbols == NULL ) 830 | { 831 | dbg_info->symbols = (var_value *) palloc( sizeof( var_value ) * func->ndatums ); 832 | 833 | for( i = 0; i < func->ndatums; ++i ) 834 | { 835 | dbg_info->symbols[i].isnull = TRUE; 836 | 837 | /* 838 | * Note: in SPL, we hide a few variables from the debugger since 839 | * they are internally generated (that is, not declared by 840 | * the user). Decide whether this particular variable should 841 | * be visible to the debugger client. 842 | */ 843 | 844 | dbg_info->symbols[i].visible = is_datum_visible( frame->datums[i] ); 845 | dbg_info->symbols[i].duplicate_name = FALSE; 846 | } 847 | 848 | for( i = 0; i < func->ndatums; ++i ) 849 | mark_duplicate_names( frame, i ); 850 | 851 | dbg_info->argNames = fetchArgNames( func, &dbg_info->argNameCount ); 852 | } 853 | } 854 | 855 | 856 | /* ------------------------------------------------------------------ 857 | * fetchArgNames() 858 | * 859 | * This function returns the name of each argument for the given 860 | * function or procedure. If the function/procedure does not have 861 | * named arguments, this function returns NULL 862 | * 863 | * The argument names are returned as an array of string pointers 864 | */ 865 | static char ** 866 | fetchArgNames(PLpgSQL_function *func, int *nameCount) 867 | { 868 | #if INCLUDE_PACKAGE_SUPPORT && (PG_VERSION_NUM >= 90600) 869 | /* 870 | * Argument names are now available in func, and we anyway can't fetch from 871 | * pg_proc in case of a nested function. 872 | */ 873 | *nameCount = func->fn_nallargs; 874 | return func->fn_argnames; 875 | #else 876 | HeapTuple tup; 877 | Datum argnamesDatum; 878 | bool isNull; 879 | Datum *elems; 880 | bool *nulls; 881 | char **result; 882 | int i; 883 | 884 | if( func->fn_nargs == 0 ) 885 | return( NULL ); 886 | 887 | tup = SearchSysCache( PROCOID, ObjectIdGetDatum( func->fn_oid ), 0, 0, 0 ); 888 | 889 | if( !HeapTupleIsValid( tup )) 890 | elog( ERROR, "cache lookup for function %u failed", func->fn_oid ); 891 | 892 | argnamesDatum = SysCacheGetAttr( PROCOID, tup, Anum_pg_proc_proargnames, &isNull ); 893 | 894 | if( isNull ) 895 | { 896 | ReleaseSysCache( tup ); 897 | return( NULL ); 898 | } 899 | 900 | deconstruct_array( DatumGetArrayTypeP( argnamesDatum ), TEXTOID, -1, false, 'i', &elems, &nulls, nameCount ); 901 | 902 | result = (char **) palloc( sizeof(char *) * (*nameCount)); 903 | 904 | for( i = 0; i < (*nameCount); i++ ) 905 | result[i] = DatumGetCString( DirectFunctionCall1( textout, elems[i] )); 906 | 907 | ReleaseSysCache( tup ); 908 | 909 | return( result ); 910 | #endif 911 | } 912 | 913 | static char * 914 | get_text_val(PLpgSQL_var *var, char **name, char **type) 915 | { 916 | HeapTuple typeTup; 917 | Form_pg_type typeStruct; 918 | FmgrInfo finfo_output; 919 | char * text_value = NULL; 920 | 921 | /* Find the output function for this data type */ 922 | typeTup = SearchSysCache( TYPEOID, ObjectIdGetDatum( var->datatype->typoid ), 0, 0, 0 ); 923 | 924 | if( !HeapTupleIsValid( typeTup )) 925 | return( NULL ); 926 | 927 | typeStruct = (Form_pg_type)GETSTRUCT( typeTup ); 928 | 929 | /* Now invoke the output function to convert the variable into a null-terminated string */ 930 | fmgr_info( typeStruct->typoutput, &finfo_output ); 931 | 932 | text_value = DatumGetCString( FunctionCall3( &finfo_output, var->value, ObjectIdGetDatum(typeStruct->typelem), Int32GetDatum(-1))); 933 | 934 | ReleaseSysCache( typeTup ); 935 | 936 | if( name ) 937 | *name = var->refname; 938 | 939 | if( type ) 940 | *type = var->datatype->typname; 941 | 942 | return( text_value ); 943 | } 944 | 945 | static Oid 946 | plpgsql_get_func_oid(ErrorContextCallback *frame) 947 | { 948 | PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; 949 | dbg_ctx * dbg_info = (dbg_ctx *) estate->plugin_info; 950 | 951 | return dbg_info->func->fn_oid; 952 | } 953 | 954 | static void 955 | dbg_startup(PLpgSQL_execstate *estate, PLpgSQL_function *func) 956 | { 957 | if( func == NULL ) 958 | { 959 | /* 960 | * In general, this should never happen, but it seems to in the 961 | * case of package constructors 962 | */ 963 | estate->plugin_info = NULL; 964 | return; 965 | } 966 | 967 | if( !breakpointsForFunction( func->fn_oid ) && !per_session_ctx.step_into_next_func) 968 | { 969 | estate->plugin_info = NULL; 970 | return; 971 | } 972 | initialize_plugin_info(estate, func); 973 | } 974 | 975 | static void 976 | initialize_plugin_info(PLpgSQL_execstate *estate, PLpgSQL_function *func) 977 | { 978 | dbg_ctx * dbg_info; 979 | 980 | /* Allocate a context structure and record the address in the estate */ 981 | estate->plugin_info = dbg_info = (dbg_ctx *) palloc( sizeof( dbg_ctx )); 982 | 983 | /* 984 | * As soon as we hit the first statement, we'll allocate space for each 985 | * local variable. For now, we set symbols to NULL so we know to report all 986 | * variables the next time we stop... 987 | */ 988 | dbg_info->symbols = NULL; 989 | dbg_info->stepping = FALSE; 990 | dbg_info->func = func; 991 | 992 | /* 993 | * The PL interpreter filled in two member of our plugin_funcs 994 | * structure for us - we compare error_callback to the callback 995 | * in the error_context_stack to make sure that we only deal with 996 | * PL/pgSQL (or SPL) stack frames (hokey, but it works). We use 997 | * assign_expr when we need to deposit a value in variable. 998 | */ 999 | dbg_info->error_callback = plugin_funcs.error_callback; 1000 | dbg_info->assign_expr = plugin_funcs.assign_expr; 1001 | 1002 | #if INCLUDE_PACKAGE_SUPPORT 1003 | /* 1004 | * Look up the package this function belongs to. 1005 | * 1006 | * Inline code blocks have invalid fn_oid. They never belong to packages. 1007 | */ 1008 | if (OidIsValid(dbg_info->func->fn_oid)) 1009 | { 1010 | /* 1011 | * Find the namespace in which this function/procedure is defined 1012 | */ 1013 | HeapTuple htup; 1014 | Oid namespaceOid; 1015 | 1016 | htup = SearchSysCache(PROCOID, ObjectIdGetDatum(dbg_info->func->fn_oid), 0, 0, 0); 1017 | 1018 | if (!HeapTupleIsValid(htup)) 1019 | elog(ERROR, "cache lookup failed for procedure %d", dbg_info->func->fn_oid); 1020 | 1021 | namespaceOid = ((Form_pg_proc)GETSTRUCT(htup))->pronamespace; 1022 | 1023 | ReleaseSysCache(htup); 1024 | 1025 | /* 1026 | * Now figure out if this namespace is a package or a schema 1027 | * 1028 | * NOTE: we could read the pg_namespace tuple and check pg_namespace.nspparent, 1029 | * but it's faster to just search for the namespaceOid in the global 1030 | * package array instead; we have to do that anyway to find the package 1031 | */ 1032 | 1033 | dbg_info->package = plugin_funcs.get_package( namespaceOid ); 1034 | } 1035 | else 1036 | dbg_info->package = InvalidOid; 1037 | #endif 1038 | } 1039 | 1040 | 1041 | /* 1042 | * --------------------------------------------------------------------- 1043 | * plpgsql_do_deposit() 1044 | * 1045 | * This function handles the 'deposit' feature - that is, this function 1046 | * sets a given PL variable to a new value, supplied by the client. 1047 | * 1048 | * do_deposit() is called when you type a new value into a variable in 1049 | * the local-variables window. 1050 | * 1051 | * NOTE: For the convenience of the user, we first assume that the 1052 | * provided value is an expression. If it doesn't evaluate, 1053 | * we convert the value into a literal by surrounding it with 1054 | * single quotes. That may be surprising if you happen to make 1055 | * a typo, but it will "do the right thing" in most cases. 1056 | * 1057 | * Returns true on success, false on failure. 1058 | */ 1059 | static bool 1060 | plpgsql_do_deposit(ErrorContextCallback *frame, const char *var_name, 1061 | int lineno, const char *value) 1062 | { 1063 | PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; 1064 | dbg_ctx *dbg_info = estate->plugin_info; 1065 | PLpgSQL_datum *target; 1066 | char *select; 1067 | PLpgSQL_expr *expr; 1068 | MemoryContext curContext = CurrentMemoryContext; 1069 | ResourceOwner curOwner = CurrentResourceOwner; 1070 | bool retval = false; 1071 | 1072 | target = find_datum_by_name(estate, var_name, lineno, NULL); 1073 | if (!target) 1074 | return false; 1075 | 1076 | /* 1077 | * Now build a SELECT statement that returns the requested value 1078 | * 1079 | * NOTE: we allocate 2 extra bytes for quoting just in case we 1080 | * need to later (see the retry logic below) 1081 | */ 1082 | 1083 | select = palloc( strlen( "SELECT " ) + strlen( value ) + 2 + 1 ); 1084 | 1085 | sprintf( select, "SELECT %s", value ); 1086 | 1087 | /* 1088 | * Note: we must create a dynamically allocated PLpgSQL_expr here - we 1089 | * can't create one on the stack because exec_assign_expr() 1090 | * links this expression into a list (active_simple_exprs) and 1091 | * this expression must survive until the end of the current 1092 | * transaction so we don't free it out from under spl_plpgsql_xact_cb() 1093 | */ 1094 | 1095 | expr = (PLpgSQL_expr *) palloc0( sizeof( *expr )); 1096 | 1097 | #if (PG_VERSION_NUM < 110000) 1098 | expr->dtype = PLPGSQL_DTYPE_EXPR; 1099 | expr->dno = -1; 1100 | #endif 1101 | expr->query = select; 1102 | expr->plan = NULL; 1103 | #if (PG_VERSION_NUM <= 80400) 1104 | expr->plan_argtypes = NULL; 1105 | expr->nparams = 0; 1106 | #endif 1107 | expr->expr_simple_expr = NULL; 1108 | 1109 | BeginInternalSubTransaction( NULL ); 1110 | 1111 | MemoryContextSwitchTo( curContext ); 1112 | 1113 | PG_TRY(); 1114 | { 1115 | if( target ) 1116 | dbg_info->assign_expr( estate, target, expr ); 1117 | 1118 | /* Commit the inner transaction, return to outer xact context */ 1119 | ReleaseCurrentSubTransaction(); 1120 | MemoryContextSwitchTo( curContext ); 1121 | CurrentResourceOwner = curOwner; 1122 | 1123 | /* That worked, don't try again */ 1124 | retval = true; 1125 | } 1126 | PG_CATCH(); 1127 | { 1128 | MemoryContextSwitchTo( curContext ); 1129 | 1130 | FlushErrorState(); 1131 | 1132 | /* Abort the inner transaction */ 1133 | RollbackAndReleaseCurrentSubTransaction(); 1134 | MemoryContextSwitchTo( curContext ); 1135 | CurrentResourceOwner = curOwner; 1136 | 1137 | /* That failed - try again as a literal */ 1138 | retval = false; 1139 | } 1140 | PG_END_TRY(); 1141 | 1142 | /* 1143 | * If the given value is not a valid expression, try converting 1144 | * the value into a literal by sinqle-quoting it. 1145 | */ 1146 | 1147 | if (!retval) 1148 | { 1149 | sprintf( select, "SELECT '%s'", value ); 1150 | 1151 | #if (PG_VERSION_NUM < 110000) 1152 | expr->dtype = PLPGSQL_DTYPE_EXPR; 1153 | expr->dno = -1; 1154 | #endif 1155 | expr->query = select; 1156 | expr->plan = NULL; 1157 | expr->expr_simple_expr = NULL; 1158 | #if (PG_VERSION_NUM <= 80400) 1159 | expr->plan_argtypes = NULL; 1160 | expr->nparams = 0; 1161 | #endif 1162 | 1163 | BeginInternalSubTransaction( NULL ); 1164 | 1165 | MemoryContextSwitchTo( curContext ); 1166 | 1167 | PG_TRY(); 1168 | { 1169 | if( target ) 1170 | dbg_info->assign_expr( estate, target, expr ); 1171 | 1172 | /* Commit the inner transaction, return to outer xact context */ 1173 | ReleaseCurrentSubTransaction(); 1174 | MemoryContextSwitchTo( curContext ); 1175 | CurrentResourceOwner = curOwner; 1176 | 1177 | retval = true; 1178 | } 1179 | PG_CATCH(); 1180 | { 1181 | MemoryContextSwitchTo( curContext ); 1182 | 1183 | FlushErrorState(); 1184 | 1185 | /* Abort the inner transaction */ 1186 | RollbackAndReleaseCurrentSubTransaction(); 1187 | MemoryContextSwitchTo( curContext ); 1188 | CurrentResourceOwner = curOwner; 1189 | 1190 | retval = false; 1191 | } 1192 | PG_END_TRY(); 1193 | } 1194 | 1195 | pfree( select ); 1196 | 1197 | return retval; 1198 | } 1199 | 1200 | /* 1201 | * --------------------------------------------------------------------- 1202 | * is_datum_visible() 1203 | * 1204 | * This function determines whether the given datum is 'visible' to the 1205 | * debugger client. We want to hide a few undocumented/internally 1206 | * generated variables from the user - this is the function that hides 1207 | * them. We set a flag in the symbols entry for this datum 1208 | * to indicate whether this variable is hidden or visible - that way, 1209 | * only have to do the expensive stuff once per invocation. 1210 | */ 1211 | static bool 1212 | is_datum_visible(PLpgSQL_datum *datum) 1213 | { 1214 | static const char * hidden_variables[] = 1215 | { 1216 | "found", 1217 | "rowcount", 1218 | "sqlcode", 1219 | "sqlerrm", 1220 | "_found", 1221 | "_rowcount", 1222 | }; 1223 | 1224 | /* 1225 | * All of the hidden variables are scalars at the moment so 1226 | * assume that anything else is visible regardless of name 1227 | */ 1228 | 1229 | if( datum->dtype != PLPGSQL_DTYPE_VAR ) 1230 | return( TRUE ); 1231 | else 1232 | { 1233 | PLpgSQL_var * var = (PLpgSQL_var *)datum; 1234 | int i; 1235 | 1236 | for( i = 0; i < sizeof( hidden_variables ) / sizeof( hidden_variables[0] ); ++i ) 1237 | { 1238 | if( strcmp( var->refname, hidden_variables[i] ) == 0 ) 1239 | { 1240 | /* 1241 | * We found this variable in our list of hidden names - 1242 | * this variable is *not* visible 1243 | */ 1244 | 1245 | return( FALSE ); 1246 | } 1247 | } 1248 | 1249 | /* 1250 | * The SPL pre-processor generates a few variable names for 1251 | * DMBS.PUTLINE statements - we want to hide those variables too. 1252 | * The generated variables are of the form 'txtnnn...' where 1253 | * 'nnn...' is a sequence of one or more digits. 1254 | */ 1255 | 1256 | if( strncmp( var->refname, "txt", 3 ) == 0 ) 1257 | { 1258 | int i; 1259 | 1260 | /* 1261 | * Starts with 'txt' - see if the rest of the string is composed 1262 | * entirely of digits 1263 | */ 1264 | 1265 | for( i = 3; var->refname[i] != '\0'; ++i ) 1266 | { 1267 | if( var->refname[i] < '0' || var->refname[i] > '9' ) 1268 | return( TRUE ); 1269 | } 1270 | 1271 | return( FALSE ); 1272 | } 1273 | 1274 | return( TRUE ); 1275 | } 1276 | } 1277 | 1278 | /* 1279 | * --------------------------------------------------------------------- 1280 | * is_var_visible() 1281 | * 1282 | * This function determines whether the given variable is 'visible' to the 1283 | * debugger client. We hide some variables from the user (see the 1284 | * is_datum_visible() function for more info). This function is quick - 1285 | * we do the slow work in is_datum_visible() and simply check the results 1286 | * here. 1287 | */ 1288 | static bool 1289 | is_var_visible(PLpgSQL_execstate *frame, int var_no) 1290 | { 1291 | dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; 1292 | 1293 | if (dbg_info->symbols == NULL) 1294 | completeFrame(frame); 1295 | 1296 | return( dbg_info->symbols[var_no].visible ); 1297 | } 1298 | 1299 | 1300 | /* 1301 | * plpgsql_send_cur_line() 1302 | * 1303 | * This function sends the current position to the debugger client. We 1304 | * send the function's OID, xmin, cmin, and the current line number 1305 | * (we're telling the client which line of code we're about to execute). 1306 | */ 1307 | static void 1308 | plpgsql_send_cur_line(ErrorContextCallback *frame) 1309 | { 1310 | PLpgSQL_execstate *estate = (PLpgSQL_execstate *) frame->arg; 1311 | PLpgSQL_stmt *stmt = estate->err_stmt; 1312 | dbg_ctx *dbg_info = (dbg_ctx *) estate->plugin_info; 1313 | PLpgSQL_function *func = dbg_info->func; 1314 | 1315 | dbg_send( "%d:%d:%s", 1316 | func->fn_oid, 1317 | stmt->lineno+1, 1318 | #if (PG_VERSION_NUM >= 90200) 1319 | func->fn_signature 1320 | #else 1321 | func->fn_name 1322 | #endif 1323 | ); 1324 | } 1325 | 1326 | /* 1327 | * --------------------------------------------------------------------- 1328 | * isFirstStmt() 1329 | * 1330 | * Returns true if the given statement is the first statement in the 1331 | * given function. 1332 | */ 1333 | static bool 1334 | isFirstStmt(PLpgSQL_stmt *stmt, PLpgSQL_function *func) 1335 | { 1336 | if( stmt == linitial( func->action->body )) 1337 | return( TRUE ); 1338 | else 1339 | return( FALSE ); 1340 | } 1341 | 1342 | /* 1343 | * --------------------------------------------------------------------- 1344 | * dbg_newstmt() 1345 | * 1346 | * The PL/pgSQL executor calls plpgsql_dbg_newstmt() just before executing each 1347 | * statement. 1348 | * 1349 | * This function is the heart of the debugger. If you're single-stepping, 1350 | * or you hit a breakpoint, plpgsql_dbg_newstmt() sends a message to the debugger 1351 | * client indicating the current line and then waits for a command from 1352 | * the user. 1353 | * 1354 | * NOTE: it is very important that this function should impose negligible 1355 | * overhead when a debugger client is *not* attached. In other words 1356 | * if you're running PL/pgSQL code without a debugger, you notice no 1357 | * performance penalty. 1358 | */ 1359 | static void 1360 | dbg_newstmt(PLpgSQL_execstate *estate, PLpgSQL_stmt *stmt) 1361 | { 1362 | PLpgSQL_execstate * frame = estate; 1363 | 1364 | /* 1365 | * If there's no debugger attached, go home as quickly as possible. 1366 | */ 1367 | if( frame->plugin_info == NULL ) 1368 | return; 1369 | else 1370 | { 1371 | dbg_ctx * dbg_info = (dbg_ctx *)frame->plugin_info; 1372 | Breakpoint * breakpoint = NULL; 1373 | eBreakpointScope breakpointScope = 0; 1374 | 1375 | /* 1376 | * The PL compiler marks certain statements as 'invisible' to the 1377 | * debugger. In particular, the compiler may generate statements 1378 | * that do not appear in the source code. Such a statement is 1379 | * marked with a line number of -1: if we're looking at an invisible 1380 | * statement, just return to the caller. 1381 | */ 1382 | 1383 | if( stmt->lineno == -1 ) 1384 | return; 1385 | 1386 | /* 1387 | * Now set up an error handler context so we can intercept any 1388 | * networking errors (errors communicating with the proxy). 1389 | */ 1390 | 1391 | if( sigsetjmp( client_lost.m_savepoint, 1 ) != 0 ) 1392 | { 1393 | /* 1394 | * The connection to the debugger client has slammed shut - 1395 | * just pretend like there's no debugger attached and return 1396 | * 1397 | * NOTE: we no longer have a connection to the debugger proxy - 1398 | * that means that we cannot interact with the proxy, we 1399 | * can't wait for another command, nothing. We let the 1400 | * executor continue execution - anything else will hang 1401 | * this backend, waiting for a debugger command that will 1402 | * never arrive. 1403 | * 1404 | * If, however, we hit a breakpoint again, we'll stop and 1405 | * wait for another debugger proxy to connect to us. If 1406 | * that's not the behavior you're looking for, you can 1407 | * drop the breakpoint, or call free_function_breakpoints() 1408 | * here to get rid of all breakpoints in this backend. 1409 | */ 1410 | per_session_ctx.client_w = 0; /* No client connection */ 1411 | dbg_info->stepping = FALSE; /* No longer stepping */ 1412 | } 1413 | 1414 | if(( dbg_info->stepping ) || breakAtThisLine( &breakpoint, &breakpointScope, dbg_info->func->fn_oid, isFirstStmt( stmt, dbg_info->func ) ? -1 : stmt->lineno )) 1415 | dbg_info->stepping = TRUE; 1416 | else 1417 | return; 1418 | 1419 | per_session_ctx.step_into_next_func = FALSE; 1420 | 1421 | /* We found a breakpoint for this function (or we're stepping into) */ 1422 | /* Make contact with the debugger client */ 1423 | 1424 | if( !attach_to_proxy( breakpoint )) 1425 | { 1426 | /* 1427 | * Can't attach to the proxy, maybe we found a stale breakpoint? 1428 | * That can happen if you set a global breakpoint on a function, 1429 | * invoke that function from a client application, debug the target 1430 | * kill the debugger client, and then re-invoke the function from 1431 | * the same client application - we will find the stale global 1432 | * breakpoint on the second invocation. 1433 | * 1434 | * We want to remove that breakpoint so that we don't keep trying 1435 | * to attach to a phantom proxy process. 1436 | */ 1437 | if( breakpoint ) 1438 | BreakpointDelete( breakpointScope, &(breakpoint->key)); 1439 | 1440 | /* 1441 | * In any case, if we don't have a proxy to work with, we can't 1442 | * do any debugging so give up. 1443 | */ 1444 | pfree( frame->plugin_info ); 1445 | frame->plugin_info = NULL; /* No debugger context */ 1446 | per_session_ctx.client_w = 0; /* No client connection */ 1447 | 1448 | return; 1449 | } 1450 | 1451 | if( stmt->cmd_type == PLPGSQL_STMT_BLOCK ) 1452 | return; 1453 | 1454 | /* 1455 | * The PL/pgSQL compiler inserts an automatic RETURN statement at the 1456 | * end of each function (unless the last statement in the function is 1457 | * already a RETURN). If we run into that statement, we don't really 1458 | * want to wait for the user to STEP across it. Remember, the user won't 1459 | * see the RETURN statement in the source-code listing for his function. 1460 | * 1461 | * Fortunately, the automatic RETURN statement has a line-number of 0 1462 | * so it's easy to spot. 1463 | */ 1464 | if( stmt->lineno == 0 ) 1465 | return; 1466 | 1467 | /* 1468 | * If we're in step mode, tell the debugger client, read a command from the client and 1469 | * execute the command 1470 | */ 1471 | 1472 | if( dbg_info->stepping ) 1473 | { 1474 | /* 1475 | * Make sure that we have all of the debug info that we need in this stack frame 1476 | */ 1477 | completeFrame( frame ); 1478 | 1479 | /* 1480 | * We're in single-step mode (or at a breakpoint) 1481 | * send the current line number to the debugger client and report any 1482 | * variable modifications 1483 | */ 1484 | 1485 | if (!plugin_debugger_main_loop()) 1486 | dbg_info->stepping = FALSE; 1487 | } 1488 | } 1489 | } 1490 | 1491 | /* --------------------------------------------------------------------- 1492 | * datumIsNull() 1493 | * 1494 | * determine whether datum is NULL or not. 1495 | * TODO: consider datatypes other than PLPGSQL_DTYPE_VAR as well 1496 | */ 1497 | static bool 1498 | datumIsNull(PLpgSQL_datum *datum) 1499 | { 1500 | switch (datum->dtype) 1501 | { 1502 | case PLPGSQL_DTYPE_VAR: 1503 | { 1504 | PLpgSQL_var *var = (PLpgSQL_var *) datum; 1505 | 1506 | if (var->isnull) 1507 | return true; 1508 | } 1509 | break; 1510 | 1511 | /* other data types are not currently handled, we just return true */ 1512 | case PLPGSQL_DTYPE_REC: 1513 | case PLPGSQL_DTYPE_ROW: 1514 | return true; 1515 | 1516 | default: 1517 | return true; 1518 | } 1519 | 1520 | return false; 1521 | } 1522 | -------------------------------------------------------------------------------- /plugin_debugger.c: -------------------------------------------------------------------------------- 1 | /********************************************************************** 2 | * plugin_debugger.c - Language-independent parts of debugger 3 | * 4 | * Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 5 | * 6 | * Licensed under the Artistic License v2.0, see 7 | * https://opensource.org/licenses/artistic-license-2.0 8 | * for full details 9 | * 10 | **********************************************************************/ 11 | 12 | #include "postgres.h" 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #ifdef WIN32 22 | #include 23 | #else 24 | #include 25 | #include 26 | #include 27 | #endif 28 | 29 | #include "access/xact.h" 30 | #include "lib/stringinfo.h" 31 | #include "catalog/pg_proc.h" 32 | #include "catalog/pg_type.h" 33 | #if (PG_VERSION_NUM >= 130000) 34 | #include "common/hashfn.h" 35 | #endif 36 | #include "parser/parser.h" 37 | #include "parser/parse_func.h" 38 | #include "globalbp.h" 39 | #include "storage/proc.h" /* For MyProc */ 40 | #include "storage/procarray.h" /* For BackendPidGetProc */ 41 | #include "utils/array.h" 42 | #include "utils/builtins.h" 43 | #include "utils/syscache.h" 44 | #include "miscadmin.h" 45 | 46 | #include "pldebugger.h" 47 | #include "dbgcomm.h" 48 | 49 | /* Include header for GETSTRUCT */ 50 | #if (PG_VERSION_NUM >= 90300) 51 | #include "access/htup_details.h" 52 | #endif 53 | 54 | #define GET_STR(textp) DatumGetCString(DirectFunctionCall1(textout, PointerGetDatum(textp))) 55 | 56 | #define TARGET_PROTO_VERSION "1.1" 57 | 58 | /********************************************************************** 59 | * Type and structure definitions 60 | **********************************************************************/ 61 | 62 | /* 63 | * eConnectType 64 | * 65 | * This enum defines the different ways that we can connect to the 66 | * debugger proxy. 67 | * 68 | * CONNECT_AS_SERVER means that we create a socket, bind an address to 69 | * to that socket, send a NOTICE to our client application, and wait for 70 | * a debugger proxy to attach to us. That's what happens when your 71 | * client application sets a local breakpoint and can handle the 72 | * NOTICE that we send. 73 | * 74 | * CONNECT_AS_CLIENT means that a proxy has already created a socket 75 | * and is waiting for a target (that's us) to connect to it. We do 76 | * this kind of connection stuff when a debugger client sets a global 77 | * breakpoint and we happen to blunder into that breakpoint. 78 | * 79 | * CONNECT_UNKNOWN indicates a problem, we shouldn't ever see this. 80 | */ 81 | 82 | typedef enum 83 | { 84 | CONNECT_AS_SERVER, /* Open a server socket and wait for a proxy to connect to us */ 85 | CONNECT_AS_CLIENT, /* Connect to a waiting proxy (global breakpoints do this) */ 86 | CONNECT_UNKNOWN /* Must already be connected */ 87 | } eConnectType; 88 | 89 | /* Global breakpoint data. */ 90 | typedef struct 91 | { 92 | #if (PG_VERSION_NUM >= 90600) 93 | int tranche_id; 94 | LWLock lock; 95 | #else 96 | LWLockId lockid; 97 | #endif 98 | } GlobalBreakpointData; 99 | 100 | /********************************************************************** 101 | * Local (static) variables 102 | **********************************************************************/ 103 | 104 | 105 | per_session_ctx_t per_session_ctx; 106 | 107 | errorHandlerCtx client_lost; 108 | 109 | static debugger_language_t *debugger_languages[] = { 110 | &plpgsql_debugger_lang, 111 | #ifdef INCLUDE_PACKAGE_SUPPORT 112 | &spl_debugger_lang, 113 | #endif 114 | NULL 115 | }; 116 | 117 | #if (PG_VERSION_NUM >= 150000) 118 | static shmem_request_hook_type prev_shmem_request_hook = NULL; 119 | #endif 120 | 121 | /********************************************************************** 122 | * Function declarations 123 | **********************************************************************/ 124 | 125 | void _PG_init( void ); /* initialize this module when we are dynamically loaded */ 126 | 127 | /********************************************************************** 128 | * Local (hidden) function prototypes 129 | **********************************************************************/ 130 | 131 | #if (PG_VERSION_NUM >= 150000) 132 | static void pldebugger_shmem_request( void ); 133 | #endif 134 | 135 | static void * writen( int peer, void * src, size_t len ); 136 | static bool connectAsServer( void ); 137 | static bool connectAsClient( Breakpoint * breakpoint ); 138 | static bool handle_socket_error(void); 139 | static bool parseBreakpoint( Oid * funcOID, int * lineNumber, char * breakpointString ); 140 | static bool addLocalBreakpoint( Oid funcOID, int lineNo ); 141 | static void reserveBreakpoints( void ); 142 | static debugger_language_t *language_of_frame(ErrorContextCallback *frame); 143 | static char * findSource( Oid oid, HeapTuple * tup ); 144 | 145 | static void do_deposit(ErrorContextCallback *frame, debugger_language_t *lang, 146 | char *command); 147 | static void send_breakpoints(Oid funcOid); 148 | static void send_stack(void); 149 | static void select_frame(int frameNo, ErrorContextCallback **frame_p, debugger_language_t **lang_p); 150 | 151 | 152 | /********************************************************************** 153 | * Function definitions 154 | **********************************************************************/ 155 | 156 | void _PG_init( void ) 157 | { 158 | int i; 159 | 160 | /* Initialize all the per-language hooks. */ 161 | for (i = 0; debugger_languages[i] != NULL; i++) 162 | debugger_languages[i]->initialize(); 163 | 164 | #if (PG_VERSION_NUM >= 150000) 165 | prev_shmem_request_hook = shmem_request_hook; 166 | shmem_request_hook = pldebugger_shmem_request; 167 | #else 168 | reserveBreakpoints(); 169 | dbgcomm_reserve(); 170 | #endif 171 | } 172 | 173 | #if (PG_VERSION_NUM >= 150000) 174 | static void pldebugger_shmem_request( void ) 175 | { 176 | if (prev_shmem_request_hook) 177 | prev_shmem_request_hook(); 178 | 179 | reserveBreakpoints(); 180 | dbgcomm_reserve(); 181 | } 182 | #endif 183 | 184 | /* 185 | * CREATE OR REPLACE FUNCTION pldbg_oid_debug( functionOID OID ) RETURNS INTEGER AS 'pldbg_oid_debug' LANGUAGE C; 186 | */ 187 | 188 | PGDLLEXPORT Datum pldbg_oid_debug(PG_FUNCTION_ARGS); 189 | PG_FUNCTION_INFO_V1(pldbg_oid_debug); 190 | 191 | Datum pldbg_oid_debug(PG_FUNCTION_ARGS) 192 | { 193 | Oid funcOid; 194 | HeapTuple tuple; 195 | Oid userid; 196 | 197 | if(( funcOid = PG_GETARG_OID( 0 )) == InvalidOid ) 198 | ereport( ERROR, ( errcode( ERRCODE_UNDEFINED_FUNCTION ), errmsg( "no target specified" ))); 199 | 200 | /* get the owner of the function */ 201 | tuple = SearchSysCache(PROCOID, 202 | ObjectIdGetDatum(funcOid), 203 | 0, 0, 0); 204 | if (!HeapTupleIsValid(tuple)) 205 | elog(ERROR, "cache lookup failed for function %u", 206 | funcOid); 207 | userid = ((Form_pg_proc) GETSTRUCT(tuple))->proowner; 208 | ReleaseSysCache(tuple); 209 | 210 | if( !superuser() && (GetUserId() != userid)) 211 | ereport( ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg( "must be owner or superuser to create a breakpoint" ))); 212 | 213 | addLocalBreakpoint( funcOid, -1 ); 214 | 215 | PG_RETURN_INT32( 0 ); 216 | } 217 | 218 | /* 219 | * --------------------------------------------------------------------- 220 | * readn() 221 | * 222 | * This function reads exactly 'len' bytes from the given socket or it 223 | * throws an error. readn() will hang until the proper number of bytes 224 | * have been read (or an error occurs). 225 | * 226 | * Note: dst must point to a buffer large enough to hold at least 'len' 227 | * bytes. readn() returns dst (for convenience). 228 | */ 229 | 230 | static void * readn( int peer, void * dst, size_t len ) 231 | { 232 | size_t bytesRemaining = len; 233 | char * buffer = (char *)dst; 234 | 235 | while( bytesRemaining > 0 ) 236 | { 237 | ssize_t bytesRead = recv( peer, buffer, bytesRemaining, 0 ); 238 | 239 | if( bytesRead <= 0 && errno != EINTR ) 240 | handle_socket_error(); 241 | 242 | /* Ignore if we didn't receive anything. */ 243 | if ( bytesRead > 0 ) 244 | { 245 | bytesRemaining -= bytesRead; 246 | buffer += bytesRead; 247 | } 248 | } 249 | 250 | return( dst ); 251 | } 252 | 253 | /* 254 | * --------------------------------------------------------------------- 255 | * readUInt32() 256 | * 257 | * Reads a 32-bit unsigned value from the server (and returns it in the host's 258 | * byte ordering) 259 | */ 260 | 261 | static uint32 readUInt32( int channel ) 262 | { 263 | uint32 netVal; 264 | 265 | readn( channel, &netVal, sizeof( netVal )); 266 | 267 | return( ntohl( netVal )); 268 | } 269 | 270 | /* 271 | * --------------------------------------------------------------------- 272 | * dbg_read_str() 273 | * 274 | * This function reads a counted string from the given stream 275 | * Returns a palloc'd, null-terminated string. 276 | * 277 | * NOTE: the server-side of the debugger uses this function to read a 278 | * string from the client side 279 | */ 280 | 281 | char *dbg_read_str( void ) 282 | { 283 | uint32 len; 284 | char *dst; 285 | int sock = per_session_ctx.client_r; 286 | 287 | len = readUInt32( sock ); 288 | 289 | dst = palloc(len + 1); 290 | readn( sock, dst, len ); 291 | 292 | dst[len] = '\0'; 293 | return dst; 294 | } 295 | 296 | /* 297 | * --------------------------------------------------------------------- 298 | * writen() 299 | * 300 | * This function writes exactly 'len' bytes to the given socket or it 301 | * throws an error. writen() will hang until the proper number of bytes 302 | * have been written (or an error occurs). 303 | */ 304 | 305 | static void * writen( int peer, void * src, size_t len ) 306 | { 307 | size_t bytesRemaining = len; 308 | char * buffer = (char *)src; 309 | 310 | while( bytesRemaining > 0 ) 311 | { 312 | ssize_t bytesWritten; 313 | 314 | if(( bytesWritten = send( peer, buffer, bytesRemaining, 0 )) <= 0 ) 315 | handle_socket_error(); 316 | 317 | bytesRemaining -= bytesWritten; 318 | buffer += bytesWritten; 319 | } 320 | 321 | return( src ); 322 | } 323 | 324 | 325 | /* 326 | * --------------------------------------------------------------------- 327 | * sendUInt32() 328 | * 329 | * This function sends a uint32 value (val) to the debugger server. 330 | */ 331 | 332 | static void sendUInt32( int channel, uint32 val ) 333 | { 334 | uint32 netVal = htonl( val ); 335 | 336 | writen( channel, &netVal, sizeof( netVal )); 337 | } 338 | 339 | /* 340 | * --------------------------------------------------------------------- 341 | * dbg_send() 342 | * 343 | * This function writes a formatted, counted string to the 344 | * given stream. The argument list for this function is identical to 345 | * the argument list for the fprintf() function - you provide a socket, 346 | * a format string, and then some number of arguments whose meanings 347 | * are defined by the format string. 348 | * 349 | * NOTE: the server-side of the debugger uses this function to send 350 | * data to the client side. If the connection drops, dbg_send() 351 | * will longjmp() back to the debugger top-level so that the 352 | * server-side can respond properly. 353 | */ 354 | 355 | void dbg_send( const char *fmt, ... ) 356 | { 357 | StringInfoData result; 358 | char *data; 359 | size_t remaining; 360 | int sock = per_session_ctx.client_w; 361 | 362 | if( !sock ) 363 | return; 364 | 365 | initStringInfo(&result); 366 | 367 | for (;;) 368 | { 369 | va_list args; 370 | #if (PG_VERSION_NUM >= 90400) 371 | int needed; 372 | 373 | va_start(args, fmt); 374 | needed = appendStringInfoVA(&result, fmt, args); 375 | va_end(args); 376 | 377 | if (needed == 0) 378 | break; 379 | 380 | enlargeStringInfo(&result, needed); 381 | #else 382 | bool success; 383 | 384 | va_start(args, fmt); 385 | success = appendStringInfoVA(&result, fmt, args); 386 | va_end(args); 387 | 388 | if (success) 389 | break; 390 | 391 | enlargeStringInfo(&result, result.maxlen); 392 | #endif 393 | } 394 | 395 | data = result.data; 396 | remaining = strlen(data); 397 | 398 | sendUInt32(sock, remaining); 399 | 400 | while( remaining > 0 ) 401 | { 402 | int written = send( sock, data, remaining, 0 ); 403 | 404 | if(written < 0) 405 | { 406 | handle_socket_error(); 407 | continue; 408 | } 409 | 410 | remaining -= written; 411 | data += written; 412 | } 413 | 414 | pfree(result.data); 415 | } 416 | 417 | 418 | /* 419 | * --------------------------------------------------------------------- 420 | * dbg_send_src() 421 | * 422 | * dbg_send_src() sends the source code for a function to the client. 423 | * 424 | * The client caches the source code that we send it and uses xmin/cmin 425 | * to ensure the validity of the cache. 426 | */ 427 | 428 | static void dbg_send_src( char * command ) 429 | { 430 | HeapTuple tup; 431 | char *procSrc; 432 | Oid targetOid = InvalidOid; /* Initialize to keep compiler happy */ 433 | 434 | targetOid = atoi( command + 2 ); 435 | 436 | /* Find the source code for this function */ 437 | procSrc = findSource( targetOid, &tup ); 438 | 439 | /* Found it - now send the source to the client */ 440 | 441 | dbg_send( "%s", procSrc ); 442 | 443 | /* Release the process tuple and send a footer to the client so he knows we're finished */ 444 | 445 | ReleaseSysCache( tup ); 446 | } 447 | 448 | /* 449 | * --------------------------------------------------------------------- 450 | * findSource() 451 | * 452 | * This function locates and returns a pointer to a null-terminated string 453 | * that contains the source code for the given function (identified by its 454 | * OID). 455 | * 456 | * In addition to returning a pointer to the requested source code, this 457 | * function sets *tup to point to a HeapTuple (that you must release when 458 | * you are finished with it). 459 | */ 460 | 461 | static char * findSource( Oid oid, HeapTuple * tup ) 462 | { 463 | bool isNull; 464 | 465 | *tup = SearchSysCache( PROCOID, ObjectIdGetDatum( oid ), 0, 0, 0 ); 466 | 467 | if(!HeapTupleIsValid( *tup )) 468 | elog( ERROR, "pldebugger: cache lookup for proc %u failed", oid ); 469 | 470 | return( DatumGetCString( DirectFunctionCall1( textout, SysCacheGetAttr( PROCOID, *tup, Anum_pg_proc_prosrc, &isNull )))); 471 | } 472 | 473 | 474 | /* 475 | * --------------------------------------------------------------------- 476 | * attach_to_proxy() 477 | * 478 | * This function creates a connection to the debugger client (via the 479 | * proxy process). attach_to_proxy() will hang the PostgreSQL backend 480 | * until the debugger client completes the connection. 481 | * 482 | * We start by asking the TCP/IP stack to allocate an unused port, then we 483 | * extract the port number from the resulting socket, send the port number to 484 | * the client application (by raising a NOTICE), and finally, we wait for the 485 | * client to connect. 486 | * 487 | * We assume that the client application knows the IP address of the PostgreSQL 488 | * backend process - if that turns out to be a poor assumption, we can include 489 | * the IP address in the notification string that we send to the client application. 490 | */ 491 | 492 | bool attach_to_proxy( Breakpoint * breakpoint ) 493 | { 494 | bool result; 495 | errorHandlerCtx save; 496 | 497 | if( per_session_ctx.client_w ) 498 | { 499 | /* We're already connected to a live proxy, just go home */ 500 | return( TRUE ); 501 | } 502 | 503 | if( breakpoint == NULL ) 504 | { 505 | /* 506 | * No breakpoint - that implies that we're 'stepping into'. 507 | * We had better already have a connection to a proxy here 508 | * (how could we be 'stepping into' if we aren't connected 509 | * to a proxy?) 510 | */ 511 | return( FALSE ); 512 | } 513 | 514 | /* 515 | * When a networking error is detected, we longjmp() to the client_lost 516 | * error handler - that normally points to a location inside of dbg_newstmt() 517 | * but we want to handle any network errors that arise while we are 518 | * setting up a link to the proxy. So, we save the original client_lost 519 | * error handler context and push our own context on to the stack. 520 | */ 521 | 522 | save = client_lost; 523 | 524 | if( sigsetjmp( client_lost.m_savepoint, 1 ) != 0 ) 525 | { 526 | client_lost = save; 527 | return( FALSE ); 528 | } 529 | 530 | if( breakpoint->data.proxyPort == -1 ) 531 | { 532 | /* 533 | * proxyPort == -1 implies that this is a local breakpoint, 534 | * create a server socket and wait for the proxy to contact 535 | * us. 536 | */ 537 | result = connectAsServer(); 538 | } 539 | else 540 | { 541 | /* 542 | * proxyPort != -1 implies that this is a global breakpoint, 543 | * a debugger proxy is already waiting for us at the given 544 | * port (on this host), connect to that proxy. 545 | */ 546 | 547 | result = connectAsClient( breakpoint ); 548 | } 549 | 550 | /* 551 | * Now restore the original error handler context so that 552 | * dbg_newstmt() can handle any future network errors. 553 | */ 554 | 555 | client_lost = save; 556 | return( result ); 557 | } 558 | 559 | /* 560 | * --------------------------------------------------------------------- 561 | * connectAsServer() 562 | * 563 | * This function creates a socket, asks the TCP/IP stack to bind it to 564 | * an unused port, and then waits for a debugger proxy to connect to 565 | * that port. We send a NOTICE to our client process (on the other 566 | * end of the fe/be connection) to let the client know that it should 567 | * fire up a debugger and attach to that port (the NOTICE includes 568 | * the port number) 569 | */ 570 | 571 | static bool connectAsServer( void ) 572 | { 573 | int client_sock; 574 | 575 | client_sock = dbgcomm_listen_for_proxy(); 576 | if (client_sock < 0) 577 | { 578 | per_session_ctx.client_w = per_session_ctx.client_r = 0; 579 | return( FALSE ); 580 | } 581 | else 582 | { 583 | per_session_ctx.client_w = client_sock; 584 | per_session_ctx.client_r = client_sock; 585 | return( TRUE ); 586 | } 587 | } 588 | 589 | /* 590 | * --------------------------------------------------------------------- 591 | * connectAsClient() 592 | * 593 | * This function connects to a waiting proxy process over the given 594 | * port. We got the port number from a global breakpoint (the proxy 595 | * stores it's port number in the breakpoint so we'll know how to 596 | * find that proxy). 597 | */ 598 | 599 | static bool connectAsClient( Breakpoint * breakpoint ) 600 | { 601 | int proxySocket; 602 | 603 | proxySocket = dbgcomm_connect_to_proxy(breakpoint->data.proxyPort); 604 | 605 | if (proxySocket < 0 ) 606 | { 607 | /* dbgcomm_connect_to_proxy already logged the reason */ 608 | return false; 609 | } 610 | else 611 | { 612 | per_session_ctx.client_w = proxySocket; 613 | per_session_ctx.client_r = proxySocket; 614 | 615 | BreakpointBusySession( breakpoint->data.proxyPid ); 616 | return true; 617 | } 618 | } 619 | 620 | /* 621 | * --------------------------------------------------------------------- 622 | * parseBreakpoint() 623 | * 624 | * Given a string that formatted like "funcOID:linenumber", 625 | * this function parses out the components and returns them to the 626 | * caller. If the string is well-formatted, this function returns 627 | * TRUE, otherwise, we return FALSE. 628 | */ 629 | 630 | static bool parseBreakpoint( Oid * funcOID, int * lineNumber, char * breakpointString ) 631 | { 632 | int a, b; 633 | int n; 634 | 635 | n = sscanf(breakpointString, "%d:%d", &a, &b); 636 | if (n == 2) 637 | { 638 | *funcOID = a; 639 | *lineNumber = b; 640 | } 641 | else 642 | return false; 643 | 644 | return( TRUE ); 645 | } 646 | 647 | /* 648 | * --------------------------------------------------------------------- 649 | * addLocalBreakpoint() 650 | * 651 | * This function adds a local breakpoint for the given function and 652 | * line number 653 | */ 654 | 655 | static bool addLocalBreakpoint( Oid funcOID, int lineNo ) 656 | { 657 | Breakpoint breakpoint; 658 | 659 | breakpoint.key.databaseId = MyProc->databaseId; 660 | breakpoint.key.functionId = funcOID; 661 | breakpoint.key.lineNumber = lineNo; 662 | breakpoint.key.targetPid = MyProc->pid; 663 | breakpoint.data.isTmp = FALSE; 664 | breakpoint.data.proxyPort = -1; 665 | breakpoint.data.proxyPid = -1; 666 | 667 | return( BreakpointInsert( BP_LOCAL, &breakpoint.key, &breakpoint.data )); 668 | } 669 | 670 | /* 671 | * --------------------------------------------------------------------- 672 | * setBreakpoint() 673 | * 674 | * The debugger client can set a local breakpoint at a given 675 | * function/procedure and line number by calling this function 676 | * (through the debugger proxy process). 677 | */ 678 | 679 | void setBreakpoint( char * command ) 680 | { 681 | /* 682 | * Format is 'b funcOID:lineNumber' 683 | */ 684 | int lineNo; 685 | Oid funcOID; 686 | 687 | if( parseBreakpoint( &funcOID, &lineNo, command + 2 )) 688 | { 689 | if( addLocalBreakpoint( funcOID, lineNo )) 690 | dbg_send( "%s", "t" ); 691 | else 692 | dbg_send( "%s", "f" ); 693 | } 694 | else 695 | { 696 | dbg_send( "%s", "f" ); 697 | } 698 | } 699 | 700 | /* 701 | * --------------------------------------------------------------------- 702 | * clearBreakpoint() 703 | * 704 | * This function deletes the breakpoint at the package, 705 | * function/procedure, and line number indicated by the 706 | * given command. 707 | * 708 | * For now, we maintain our own private list of breakpoints - 709 | * later, we'll use the same list managed by the CREATE/ 710 | * DROP BREAKPOINT commands. 711 | */ 712 | 713 | void clearBreakpoint( char * command ) 714 | { 715 | /* 716 | * Format is 'f funcOID:lineNumber' 717 | */ 718 | int lineNo; 719 | Oid funcOID; 720 | 721 | if( parseBreakpoint( &funcOID, &lineNo, command + 2 )) 722 | { 723 | Breakpoint breakpoint; 724 | 725 | breakpoint.key.databaseId = MyProc->databaseId; 726 | breakpoint.key.functionId = funcOID; 727 | breakpoint.key.lineNumber = lineNo; 728 | breakpoint.key.targetPid = MyProc->pid; 729 | 730 | if( BreakpointDelete( BP_LOCAL, &breakpoint.key )) 731 | dbg_send( "t" ); 732 | else 733 | dbg_send( "f" ); 734 | } 735 | else 736 | { 737 | dbg_send( "f" ); 738 | } 739 | } 740 | 741 | bool breakAtThisLine( Breakpoint ** dst, eBreakpointScope * scope, Oid funcOid, int lineNumber ) 742 | { 743 | BreakpointKey key; 744 | 745 | key.databaseId = MyProc->databaseId; 746 | key.functionId = funcOid; 747 | key.lineNumber = lineNumber; 748 | 749 | if( per_session_ctx.step_into_next_func ) 750 | { 751 | *dst = NULL; 752 | *scope = BP_LOCAL; 753 | return( TRUE ); 754 | } 755 | 756 | /* 757 | * We conduct 3 searches here. 758 | * 759 | * First, we look for a global breakpoint at this line, targeting our 760 | * specific backend process. 761 | * 762 | * Next, we look for a global breakpoint (at this line) that does 763 | * not target a specific backend process. 764 | * 765 | * Finally, we look for a local breakpoint at this line (implicitly 766 | * targeting our specific backend process). 767 | * 768 | * NOTE: We must do the local-breakpoint search last because, when the 769 | * proxy attaches to our process, it marks all of its global 770 | * breakpoints as busy (so other potential targets will ignore 771 | * those breakpoints) and we copy all of those global breakpoints 772 | * into our local breakpoint hash. If the debugger client exits 773 | * and the user starts another debugger session, we want to see the 774 | * new breakpoints instead of our obsolete local breakpoints (we 775 | * don't have a good way to detect that the proxy has disconnected 776 | * until it's inconvenient - we have to read-from or write-to the 777 | * proxy before we can tell that it's died). 778 | */ 779 | 780 | key.targetPid = MyProc->pid; /* Search for a global breakpoint targeted at our process ID */ 781 | 782 | if((( *dst = BreakpointLookup( BP_GLOBAL, &key )) != NULL ) && ((*dst)->data.busy == FALSE )) 783 | { 784 | *scope = BP_GLOBAL; 785 | return( TRUE ); 786 | } 787 | 788 | key.targetPid = -1; /* Search for a global breakpoint targeted at any process ID */ 789 | 790 | if((( *dst = BreakpointLookup( BP_GLOBAL, &key )) != NULL ) && ((*dst)->data.busy == FALSE )) 791 | { 792 | *scope = BP_GLOBAL; 793 | return( TRUE ); 794 | } 795 | 796 | key.targetPid = MyProc->pid; /* Search for a local breakpoint (targeted at our process ID) */ 797 | 798 | if(( *dst = BreakpointLookup( BP_LOCAL, &key )) != NULL ) 799 | { 800 | *scope = BP_LOCAL; 801 | return( TRUE ); 802 | } 803 | 804 | return( FALSE ); 805 | } 806 | 807 | bool breakpointsForFunction( Oid funcOid ) 808 | { 809 | if( BreakpointOnId( BP_LOCAL, funcOid ) || BreakpointOnId( BP_GLOBAL, funcOid )) 810 | return( TRUE ); 811 | else 812 | return( FALSE ); 813 | 814 | } 815 | 816 | /* --------------------------------------------------------------------- 817 | * handle_socket_error() 818 | * 819 | * when invoked after a socket operation it would check socket operation's 820 | * last error status and invoke siglongjmp incase the error is fatal. 821 | */ 822 | static bool handle_socket_error(void) 823 | { 824 | int err; 825 | bool fatal_err = TRUE; 826 | 827 | #ifdef WIN32 828 | err = WSAGetLastError(); 829 | 830 | switch(err) 831 | { 832 | 833 | case WSAEINTR: 834 | case WSAEBADF: 835 | case WSAEACCES: 836 | case WSAEFAULT: 837 | case WSAEINVAL: 838 | case WSAEMFILE: 839 | 840 | /* 841 | * Windows Sockets definitions of regular Berkeley error constants 842 | */ 843 | case WSAEWOULDBLOCK: 844 | case WSAEINPROGRESS: 845 | case WSAEALREADY: 846 | case WSAENOTSOCK: 847 | case WSAEDESTADDRREQ: 848 | case WSAEMSGSIZE: 849 | case WSAEPROTOTYPE: 850 | case WSAENOPROTOOPT: 851 | case WSAEPROTONOSUPPORT: 852 | case WSAESOCKTNOSUPPORT: 853 | case WSAEOPNOTSUPP: 854 | case WSAEPFNOSUPPORT: 855 | case WSAEAFNOSUPPORT: 856 | case WSAEADDRINUSE: 857 | case WSAEADDRNOTAVAIL: 858 | case WSAENOBUFS: 859 | case WSAEISCONN: 860 | case WSAENOTCONN: 861 | case WSAETOOMANYREFS: 862 | case WSAETIMEDOUT: 863 | case WSAELOOP: 864 | case WSAENAMETOOLONG: 865 | case WSAEHOSTUNREACH: 866 | case WSAENOTEMPTY: 867 | case WSAEPROCLIM: 868 | case WSAEUSERS: 869 | case WSAEDQUOT: 870 | case WSAESTALE: 871 | case WSAEREMOTE: 872 | 873 | /* 874 | * Extended Windows Sockets error constant definitions 875 | */ 876 | case WSASYSNOTREADY: 877 | case WSAVERNOTSUPPORTED: 878 | case WSANOTINITIALISED: 879 | case WSAEDISCON: 880 | case WSAENOMORE: 881 | case WSAECANCELLED: 882 | case WSAEINVALIDPROCTABLE: 883 | case WSAEINVALIDPROVIDER: 884 | case WSAEPROVIDERFAILEDINIT: 885 | case WSASYSCALLFAILURE: 886 | case WSASERVICE_NOT_FOUND: 887 | case WSATYPE_NOT_FOUND: 888 | case WSA_E_NO_MORE: 889 | case WSA_E_CANCELLED: 890 | case WSAEREFUSED: 891 | break; 892 | 893 | /* 894 | * Server should shut down its socket on these errors. 895 | */ 896 | case WSAENETDOWN: 897 | case WSAENETUNREACH: 898 | case WSAENETRESET: 899 | case WSAECONNABORTED: 900 | case WSAESHUTDOWN: 901 | case WSAEHOSTDOWN: 902 | case WSAECONNREFUSED: 903 | case WSAECONNRESET: 904 | fatal_err = TRUE; 905 | break; 906 | 907 | default: 908 | ; 909 | } 910 | 911 | if(fatal_err) 912 | { 913 | LPVOID lpMsgBuf; 914 | FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, NULL,err, 915 | MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),(LPTSTR) &lpMsgBuf,0, NULL ); 916 | 917 | elog(COMMERROR,"%s", (char *)lpMsgBuf); 918 | LocalFree(lpMsgBuf); 919 | 920 | siglongjmp(client_lost.m_savepoint, 1); 921 | } 922 | 923 | #else 924 | 925 | err = errno; 926 | switch(err) 927 | { 928 | case EINTR: 929 | case ECONNREFUSED: 930 | case EPIPE: 931 | case ENOTCONN: 932 | fatal_err = TRUE; 933 | break; 934 | 935 | case ENOTSOCK: 936 | case EAGAIN: 937 | case EFAULT: 938 | case ENOMEM: 939 | case EINVAL: 940 | default: 941 | break; 942 | } 943 | 944 | if(fatal_err) 945 | { 946 | if(( err ) && ( err != EPIPE )) 947 | elog(COMMERROR, "%s", strerror(err)); 948 | 949 | siglongjmp(client_lost.m_savepoint, 1); 950 | } 951 | 952 | errno = err; 953 | #endif 954 | 955 | return fatal_err; 956 | } 957 | 958 | /* 959 | * Returns true if we continue stepping in this frame. False otherwise. 960 | */ 961 | bool 962 | plugin_debugger_main_loop(void) 963 | { 964 | ErrorContextCallback *frame; 965 | debugger_language_t *lang; /* language of the selected frame */ 966 | bool need_more = TRUE; 967 | char *command; 968 | bool retval = TRUE; 969 | 970 | /* Initially, set focus on the topmost frame in the stack */ 971 | 972 | for( frame = error_context_stack; frame; frame = frame->previous ) 973 | { 974 | /* 975 | * ignore unrecognized stack frames. 976 | */ 977 | lang = language_of_frame(frame); 978 | if (lang) 979 | break; 980 | } 981 | if (frame == NULL) 982 | { 983 | /* 984 | * Oops, couldn't find a frame that we recognize in the stack. This 985 | * shouldn't happen since we're stopped at a breakpoint. 986 | */ 987 | elog(WARNING, "could not find PL/pgSQL frame at the top of the stack"); 988 | return false; 989 | } 990 | 991 | /* Report the current location */ 992 | lang->send_cur_line(frame); 993 | 994 | /* 995 | * Loop through the following chunk of code until we get a command 996 | * from the user that would let us execute this PL/pgSQL statement. 997 | */ 998 | while( need_more ) 999 | { 1000 | /* Wait for a command from the debugger client */ 1001 | command = dbg_read_str(); 1002 | 1003 | /* 1004 | * The debugger client sent us a null-terminated command string 1005 | * 1006 | * Each command starts with a single character and is 1007 | * followed by set of optional arguments. 1008 | */ 1009 | switch( command[0] ) 1010 | { 1011 | case PLDBG_CONTINUE: 1012 | { 1013 | /* 1014 | * Continue (stop single-stepping and just run to the next breakpoint) 1015 | */ 1016 | retval = false; 1017 | need_more = FALSE; 1018 | break; 1019 | } 1020 | 1021 | case PLDBG_SET_BREAKPOINT: 1022 | { 1023 | setBreakpoint( command ); 1024 | break; 1025 | } 1026 | 1027 | case PLDBG_CLEAR_BREAKPOINT: 1028 | { 1029 | clearBreakpoint( command ); 1030 | break; 1031 | } 1032 | 1033 | case PLDBG_PRINT_VAR: 1034 | { 1035 | /* 1036 | * Print value of given variable 1037 | */ 1038 | 1039 | lang->print_var( frame, &command[2], -1 ); 1040 | break; 1041 | } 1042 | 1043 | case PLDBG_LIST_BREAKPOINTS: 1044 | { 1045 | send_breakpoints( lang->get_func_oid(frame) ); 1046 | break; 1047 | } 1048 | 1049 | case PLDBG_STEP_INTO: 1050 | { 1051 | /* 1052 | * Single-step/step-into 1053 | */ 1054 | per_session_ctx.step_into_next_func = TRUE; 1055 | need_more = FALSE; 1056 | break; 1057 | } 1058 | 1059 | case PLDBG_STEP_OVER: 1060 | { 1061 | /* 1062 | * Single-step/step-over 1063 | */ 1064 | need_more = FALSE; 1065 | break; 1066 | } 1067 | 1068 | case PLDBG_LIST: 1069 | { 1070 | /* 1071 | * Send source code for given function 1072 | */ 1073 | dbg_send_src( command ); 1074 | break; 1075 | } 1076 | 1077 | case PLDBG_PRINT_STACK: 1078 | { 1079 | send_stack(); 1080 | break; 1081 | } 1082 | 1083 | case PLDBG_SELECT_FRAME: 1084 | { 1085 | select_frame(atoi( &command[2] ), &frame, &lang); 1086 | /* Report the new location */ 1087 | lang->send_cur_line( frame ); 1088 | break; 1089 | 1090 | } 1091 | 1092 | case PLDBG_DEPOSIT: 1093 | { 1094 | /* 1095 | * Deposit a new value into the given variable 1096 | */ 1097 | do_deposit(frame, lang, command); 1098 | break; 1099 | } 1100 | 1101 | case PLDBG_INFO_VARS: 1102 | { 1103 | /* 1104 | * Send list of variables (and their values) 1105 | */ 1106 | lang->send_vars( frame ); 1107 | break; 1108 | } 1109 | 1110 | case PLDBG_RESTART: 1111 | case PLDBG_STOP: 1112 | { 1113 | /* stop the debugging session */ 1114 | dbg_send( "%s", "t" ); 1115 | 1116 | ereport(ERROR, 1117 | (errcode(ERRCODE_QUERY_CANCELED), 1118 | errmsg("canceling statement due to user request"))); 1119 | break; 1120 | } 1121 | 1122 | default: 1123 | elog(WARNING, "unrecognized message %c", command[0]); 1124 | } 1125 | pfree(command); 1126 | } 1127 | 1128 | return retval; 1129 | } 1130 | 1131 | static void 1132 | do_deposit(ErrorContextCallback *frame, debugger_language_t *lang, 1133 | char *command) 1134 | { 1135 | char *var_name; 1136 | char *value; 1137 | char *lineno_s; 1138 | int lineno; 1139 | 1140 | /* command = d:var.line=expr */ 1141 | 1142 | var_name = command + 2; 1143 | value = strchr( var_name, '=' ); /* FIXME: handle quoted identifiers here */ 1144 | if (!value) 1145 | { 1146 | dbg_send( "%s", "f" ); 1147 | return; 1148 | } 1149 | *value = '\0'; 1150 | value++; 1151 | 1152 | lineno_s = strchr( var_name, '.' ); /* FIXME: handle quoted identifiers here */ 1153 | if (!lineno_s) 1154 | { 1155 | dbg_send( "%s", "f" ); 1156 | return; 1157 | } 1158 | *lineno_s = '\0'; 1159 | lineno_s++; 1160 | if (strlen(lineno_s) == 0) 1161 | lineno = -1; 1162 | else 1163 | lineno = atoi(lineno_s); 1164 | 1165 | if (lang->do_deposit(frame, var_name, lineno, value)) 1166 | dbg_send( "%s", "t" ); 1167 | else 1168 | dbg_send( "%s", "f" ); 1169 | } 1170 | 1171 | 1172 | /* 1173 | * --------------------------------------------------------------------- 1174 | * sendBreakpoints() 1175 | * 1176 | * This function sends a list of breakpoints to the proxy process. 1177 | * 1178 | * We only send the breakpoints defined in the given frame. 1179 | * 1180 | * For now, we maintain our own private list of breakpoints - 1181 | * later, we'll use the same list managed by the CREATE/ 1182 | * DROP BREAKPOINT commands. 1183 | */ 1184 | static void 1185 | send_breakpoints(Oid funcOid) 1186 | { 1187 | Breakpoint * breakpoint; 1188 | HASH_SEQ_STATUS scan; 1189 | 1190 | BreakpointGetList( BP_GLOBAL, &scan ); 1191 | 1192 | while(( breakpoint = (Breakpoint *) hash_seq_search( &scan )) != NULL ) 1193 | { 1194 | if(( breakpoint->key.targetPid == -1 ) || ( breakpoint->key.targetPid == MyProc->pid )) 1195 | if( breakpoint->key.databaseId == MyProc->databaseId ) 1196 | if( breakpoint->key.functionId == funcOid ) 1197 | dbg_send( "%d:%d:%s", funcOid, breakpoint->key.lineNumber, "" ); 1198 | } 1199 | 1200 | BreakpointReleaseList( BP_GLOBAL ); 1201 | 1202 | BreakpointGetList( BP_LOCAL, &scan ); 1203 | 1204 | while(( breakpoint = (Breakpoint *) hash_seq_search( &scan )) != NULL ) 1205 | { 1206 | if(( breakpoint->key.targetPid == -1 ) || ( breakpoint->key.targetPid == MyProc->pid )) 1207 | if( breakpoint->key.databaseId == MyProc->databaseId ) 1208 | if( breakpoint->key.functionId == funcOid ) 1209 | dbg_send( "%d:%d:%s", funcOid, breakpoint->key.lineNumber, "" ); 1210 | } 1211 | 1212 | BreakpointReleaseList( BP_LOCAL ); 1213 | 1214 | dbg_send( "%s", "" ); /* empty string indicates end of list */ 1215 | } 1216 | 1217 | 1218 | /* 1219 | * --------------------------------------------------------------------- 1220 | * select_frame() 1221 | * 1222 | * This function changes the debugger focus to the indicated frame (in the call 1223 | * stack). Whenever the target stops (at a breakpoint or as the result of a 1224 | * step/into or step/over), the debugger changes focus to most deeply nested 1225 | * function in the call stack (because that's the function that's executing). 1226 | * 1227 | * You can change the debugger focus to other stack frames - once you do that, 1228 | * you can examine the source code for that frame, the variable values in that 1229 | * frame, and the breakpoints in that target. 1230 | * 1231 | * The debugger focus remains on the selected frame until you change it or 1232 | * the target stops at another breakpoint. 1233 | */ 1234 | static void 1235 | select_frame(int frameNo, ErrorContextCallback **frame_p, debugger_language_t **lang_p) 1236 | { 1237 | ErrorContextCallback *frame; 1238 | 1239 | for( frame = error_context_stack; frame; frame = frame->previous ) 1240 | { 1241 | debugger_language_t *lang = language_of_frame(frame); 1242 | if (!lang) 1243 | continue; 1244 | 1245 | if( frameNo-- == 0 ) 1246 | { 1247 | lang->select_frame(frame); 1248 | 1249 | *frame_p = frame; 1250 | *lang_p = lang; 1251 | } 1252 | } 1253 | 1254 | /* Not found. Keep frame unchanged */ 1255 | } 1256 | 1257 | /* 1258 | * Returns the debugger_language_t struct representing the language that 1259 | * this stack frame belongs to. Or NULL if we don't have a handler for it. 1260 | */ 1261 | static debugger_language_t * 1262 | language_of_frame(ErrorContextCallback *frame) 1263 | { 1264 | debugger_language_t *lang; 1265 | int i; 1266 | 1267 | for (i = 0; debugger_languages[i] != NULL; i++) 1268 | { 1269 | lang = debugger_languages[i]; 1270 | if (lang->frame_belongs_to_me(frame)) 1271 | return lang; 1272 | } 1273 | return NULL; 1274 | } 1275 | 1276 | /* ------------------------------------------------------------------ 1277 | * send_stack() 1278 | * 1279 | * This function sends the call stack to the debugger client. For 1280 | * each PL/pgSQL stack frame that we find, we send the function name, 1281 | * argument names and values, and the current line number (within 1282 | * that particular invocation). 1283 | */ 1284 | static void 1285 | send_stack( void ) 1286 | { 1287 | ErrorContextCallback * entry; 1288 | 1289 | for( entry = error_context_stack; entry; entry = entry->previous ) 1290 | { 1291 | /* 1292 | * ignore frames we don't recognize 1293 | */ 1294 | debugger_language_t *lang = language_of_frame(entry); 1295 | if (lang != NULL) 1296 | lang->send_stack_frame(entry); 1297 | } 1298 | 1299 | dbg_send( "%s", "" ); /* empty string indicates end of list */ 1300 | } 1301 | 1302 | //////////////////////////////////////////////////////////////////////////////// 1303 | 1304 | 1305 | /*------------------------------------------------------------------------------------- 1306 | * The shared hash table for global breakpoints. It is protected by 1307 | * breakpointLock 1308 | *------------------------------------------------------------------------------------- 1309 | */ 1310 | static LWLockId breakpointLock; 1311 | static HTAB * globalBreakpoints = NULL; 1312 | static HTAB * localBreakpoints = NULL; 1313 | 1314 | /*------------------------------------------------------------------------------------- 1315 | * The size of Breakpoints is determined by globalBreakpointCount (should be a GUC) 1316 | *------------------------------------------------------------------------------------- 1317 | */ 1318 | static int globalBreakpointCount = 20; 1319 | static Size breakpoint_hash_size; 1320 | static Size breakcount_hash_size; 1321 | 1322 | /*------------------------------------------------------------------------------------- 1323 | * Another shared hash table which tracks number of breakpoints created 1324 | * against each entity. 1325 | * 1326 | * It is also protected by breakpointLock, thus making operations on Breakpoints 1327 | * BreakCounts atomic. 1328 | *------------------------------------------------------------------------------------- 1329 | */ 1330 | static HTAB *globalBreakCounts; 1331 | static HTAB *localBreakCounts; 1332 | 1333 | typedef struct BreakCountKey 1334 | { 1335 | Oid databaseId; 1336 | Oid functionId; 1337 | } BreakCountKey; 1338 | 1339 | typedef struct BreakCount 1340 | { 1341 | BreakCountKey key; 1342 | int count; 1343 | } BreakCount; 1344 | 1345 | /*------------------------------------------------------------------------------------- 1346 | * Prototypes for functions which operate on GlobalBreakCounts. 1347 | *------------------------------------------------------------------------------------- 1348 | */ 1349 | static void initLocalBreakpoints(void); 1350 | static void initLocalBreakCounts(void); 1351 | 1352 | static void breakCountInsert(eBreakpointScope scope, BreakCountKey *key); 1353 | static void breakCountDelete(eBreakpointScope scope, BreakCountKey *key); 1354 | static int breakCountLookup(eBreakpointScope scope, BreakCountKey *key, bool *found); 1355 | static HTAB * getBreakpointHash(eBreakpointScope scope); 1356 | static HTAB * getBreakCountHash(eBreakpointScope scope); 1357 | 1358 | static void reserveBreakpoints( void ) 1359 | { 1360 | breakpoint_hash_size = hash_estimate_size(globalBreakpointCount, sizeof(Breakpoint)); 1361 | breakcount_hash_size = hash_estimate_size(globalBreakpointCount, sizeof(BreakCount)); 1362 | 1363 | RequestAddinShmemSpace( add_size( breakpoint_hash_size, breakcount_hash_size )); 1364 | RequestAddinShmemSpace(sizeof(GlobalBreakpointData)); 1365 | #if (PG_VERSION_NUM < 90600) 1366 | RequestAddinLWLocks( 1 ); 1367 | #endif 1368 | } 1369 | 1370 | static void 1371 | initializeHashTables(void) 1372 | { 1373 | LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE); 1374 | 1375 | initGlobalBreakpoints(); 1376 | 1377 | LWLockRelease(AddinShmemInitLock); 1378 | 1379 | initLocalBreakpoints(); 1380 | initLocalBreakCounts(); 1381 | } 1382 | 1383 | static void 1384 | initLocalBreakpoints(void) 1385 | { 1386 | HASHCTL ctl = {0}; 1387 | 1388 | ctl.keysize = sizeof(BreakpointKey); 1389 | ctl.entrysize = sizeof(Breakpoint); 1390 | ctl.hash = tag_hash; 1391 | 1392 | localBreakpoints = hash_create("Local Breakpoints", 128, &ctl, HASH_ELEM | HASH_FUNCTION); 1393 | } 1394 | 1395 | void 1396 | initGlobalBreakpoints(void) 1397 | { 1398 | bool found; 1399 | int tableEntries = globalBreakpointCount; 1400 | GlobalBreakpointData *gbpd; 1401 | HASHCTL breakpointCtl = {0}; 1402 | HASHCTL breakcountCtl = {0}; 1403 | 1404 | gbpd = ShmemInitStruct("Global Breakpoint Data", 1405 | sizeof(GlobalBreakpointData), &found); 1406 | if (gbpd == NULL) 1407 | elog(ERROR, "out of shared memory"); 1408 | 1409 | #if (PG_VERSION_NUM >= 90600) 1410 | if (!found) 1411 | { 1412 | gbpd->tranche_id = LWLockNewTrancheId(); 1413 | LWLockInitialize(&gbpd->lock, gbpd->tranche_id); 1414 | } 1415 | { 1416 | #if (PG_VERSION_NUM >= 100000) 1417 | LWLockRegisterTranche(gbpd->tranche_id, "pldebugger"); 1418 | #else 1419 | static LWLockTranche tranche; 1420 | 1421 | tranche.name = "pldebugger"; 1422 | tranche.array_base = &gbpd->lock; 1423 | tranche.array_stride = sizeof(LWLock); 1424 | LWLockRegisterTranche(gbpd->tranche_id, &tranche); 1425 | #endif 1426 | 1427 | breakpointLock = &gbpd->lock; 1428 | } 1429 | #else 1430 | if (!found) 1431 | gbpd->lockid = LWLockAssign(); 1432 | breakpointLock = gbpd->lockid; 1433 | #endif 1434 | 1435 | /* 1436 | * Now create a shared-memory hash to hold our global breakpoints 1437 | */ 1438 | breakpointCtl.keysize = sizeof(BreakpointKey); 1439 | breakpointCtl.entrysize = sizeof(Breakpoint); 1440 | breakpointCtl.hash = tag_hash; 1441 | 1442 | globalBreakpoints = ShmemInitHash("Global Breakpoints Table", tableEntries, tableEntries, &breakpointCtl, HASH_ELEM | HASH_FUNCTION); 1443 | 1444 | if (!globalBreakpoints) 1445 | elog(FATAL, "could not initialize global breakpoints hash table"); 1446 | 1447 | /* 1448 | * And create a shared-memory hash to hold our global breakpoint counts 1449 | */ 1450 | breakcountCtl.keysize = sizeof(BreakCountKey); 1451 | breakcountCtl.entrysize = sizeof(BreakCount); 1452 | breakcountCtl.hash = tag_hash; 1453 | 1454 | globalBreakCounts = ShmemInitHash("Global BreakCounts Table", tableEntries, tableEntries, &breakcountCtl, HASH_ELEM | HASH_FUNCTION); 1455 | 1456 | if (!globalBreakCounts) 1457 | elog(FATAL, "could not initialize global breakpoints count hash table"); 1458 | } 1459 | 1460 | 1461 | /* --------------------------------------------------------- 1462 | * getPLDebuggerLock() 1463 | * 1464 | * Returns the lockid of the lock used to protect pldebugger shared memory 1465 | * structures. The lock is called breakpointLock in this file, but it's 1466 | * also shared by dbgcommm.c. 1467 | */ 1468 | 1469 | LWLockId 1470 | getPLDebuggerLock(void) 1471 | { 1472 | if( localBreakpoints == NULL ) 1473 | initializeHashTables(); 1474 | 1475 | return breakpointLock; 1476 | } 1477 | 1478 | /* --------------------------------------------------------- 1479 | * acquireLock() 1480 | * 1481 | * This function waits for a lightweight lock that protects 1482 | * the breakpoint and breakcount hash tables at the given 1483 | * scope. If scope is BP_GLOBAL, this function locks 1484 | * breakpointLock. If scope is BP_LOCAL, this function 1485 | * doesn't lock anything because local breakpoints are, 1486 | * well, local (clever naming convention, huh?) 1487 | */ 1488 | 1489 | static void 1490 | acquireLock(eBreakpointScope scope, LWLockMode mode) 1491 | { 1492 | if( localBreakpoints == NULL ) 1493 | initializeHashTables(); 1494 | 1495 | if (scope == BP_GLOBAL) 1496 | LWLockAcquire(breakpointLock, mode); 1497 | } 1498 | 1499 | /* --------------------------------------------------------- 1500 | * releaseLock() 1501 | * 1502 | * This function releases the lightweight lock that protects 1503 | * the breakpoint and breakcount hash tables at the given 1504 | * scope. If scope is BP_GLOBAL, this function releases 1505 | * breakpointLock. If scope is BP_LOCAL, this function 1506 | * doesn't do anything because local breakpoints are not 1507 | * protected by a lwlock. 1508 | */ 1509 | 1510 | static void 1511 | releaseLock(eBreakpointScope scope) 1512 | { 1513 | if (scope == BP_GLOBAL) 1514 | LWLockRelease(breakpointLock); 1515 | } 1516 | 1517 | /* --------------------------------------------------------- 1518 | * BreakpointLookup() 1519 | * 1520 | * lookup the given global breakpoint hash key. Returns an instance 1521 | * of Breakpoint structure 1522 | */ 1523 | Breakpoint * 1524 | BreakpointLookup(eBreakpointScope scope, BreakpointKey *key) 1525 | { 1526 | Breakpoint *entry; 1527 | bool found; 1528 | 1529 | acquireLock(scope, LW_SHARED); 1530 | entry = (Breakpoint *) hash_search( getBreakpointHash(scope), (void *) key, HASH_FIND, &found); 1531 | releaseLock(scope); 1532 | 1533 | return entry; 1534 | } 1535 | 1536 | /* --------------------------------------------------------- 1537 | * BreakpointOnId() 1538 | * 1539 | * This is where we see the real advantage of the existence of BreakCounts. 1540 | * It returns true if there is a global breakpoint on the given id, false 1541 | * otherwise. The hash key of Global breakpoints table is a composition of Oid 1542 | * and lineno. Therefore lookups on the basis of Oid only are not possible. 1543 | * With this function however callers can determine whether a breakpoint is 1544 | * marked on the given entity id with the cost of one lookup only. 1545 | * 1546 | * The check is made by looking up id in BreakCounts. 1547 | */ 1548 | bool 1549 | BreakpointOnId(eBreakpointScope scope, Oid funcOid) 1550 | { 1551 | bool found = false; 1552 | BreakCountKey key; 1553 | 1554 | key.databaseId = MyProc->databaseId; 1555 | key.functionId = funcOid; 1556 | 1557 | acquireLock(scope, LW_SHARED); 1558 | breakCountLookup(scope, &key, &found); 1559 | releaseLock(scope); 1560 | 1561 | return found; 1562 | } 1563 | 1564 | /* --------------------------------------------------------- 1565 | * BreakpointInsert() 1566 | * 1567 | * inserts the global breakpoint (brkpnt) in the global breakpoints 1568 | * hash table against the supplied key. 1569 | */ 1570 | bool 1571 | BreakpointInsert(eBreakpointScope scope, BreakpointKey *key, BreakpointData *data) 1572 | { 1573 | Breakpoint *entry; 1574 | bool found; 1575 | 1576 | acquireLock(scope, LW_EXCLUSIVE); 1577 | 1578 | entry = (Breakpoint *) hash_search(getBreakpointHash(scope), (void *)key, HASH_ENTER, &found); 1579 | 1580 | if(found) 1581 | { 1582 | releaseLock(scope); 1583 | return FALSE; 1584 | } 1585 | 1586 | entry->data = *data; 1587 | entry->data.busy = FALSE; /* Assume this breakpoint has not been nabbed by a target */ 1588 | 1589 | 1590 | /* register this insert in the count hash table*/ 1591 | breakCountInsert(scope, ((BreakCountKey *)key)); 1592 | 1593 | releaseLock(scope); 1594 | 1595 | return( TRUE ); 1596 | } 1597 | 1598 | /* --------------------------------------------------------- 1599 | * BreakpointInsertOrUpdate() 1600 | * 1601 | * inserts the global breakpoint (brkpnt) in the global breakpoints 1602 | * hash table against the supplied key. 1603 | */ 1604 | 1605 | bool 1606 | BreakpointInsertOrUpdate(eBreakpointScope scope, BreakpointKey *key, BreakpointData *data) 1607 | { 1608 | Breakpoint *entry; 1609 | bool found; 1610 | 1611 | acquireLock(scope, LW_EXCLUSIVE); 1612 | 1613 | entry = (Breakpoint *) hash_search(getBreakpointHash(scope), (void *)key, HASH_ENTER, &found); 1614 | 1615 | if(found) 1616 | { 1617 | entry->data = *data; 1618 | releaseLock(scope); 1619 | return FALSE; 1620 | } 1621 | 1622 | entry->data = *data; 1623 | entry->data.busy = FALSE; /* Assume this breakpoint has not been nabbed by a target */ 1624 | 1625 | 1626 | /* register this insert in the count hash table*/ 1627 | breakCountInsert(scope, ((BreakCountKey *)key)); 1628 | 1629 | releaseLock(scope); 1630 | 1631 | return( TRUE ); 1632 | } 1633 | 1634 | /* --------------------------------------------------------- 1635 | * BreakpointBusySession() 1636 | * 1637 | * This function marks all breakpoints that belong to the given 1638 | * proxy (identified by pid) as 'busy'. When a potential target 1639 | * runs into a busy breakpoint, that means that the breakpoint 1640 | * has already been hit by some other target and that other 1641 | * target is engaged in a conversation with the proxy (in other 1642 | * words, the debugger proxy and debugger client are busy). 1643 | * 1644 | * We also copy all global breakpoints for the given proxy 1645 | * to the local breakpoints list - that way, the target that's 1646 | * actually interacting with the debugger client will continue 1647 | * to hit those breakpoints until the target process ends. 1648 | * 1649 | * When that debugging session ends, the debugger proxy calls 1650 | * BreakpointFreeSession() to let other potential targets know 1651 | * that the proxy can handle another target. 1652 | * 1653 | * FIXME: it might make more sense to simply move all of the 1654 | * global breakpoints into the local hash instead, then 1655 | * the debugger client would have to recreate all of 1656 | * it's global breakpoints before waiting for another 1657 | * target. 1658 | */ 1659 | 1660 | void 1661 | BreakpointBusySession(int pid) 1662 | { 1663 | HASH_SEQ_STATUS status; 1664 | Breakpoint *entry; 1665 | 1666 | acquireLock(BP_GLOBAL, LW_EXCLUSIVE); 1667 | 1668 | hash_seq_init(&status, getBreakpointHash(BP_GLOBAL)); 1669 | 1670 | while((entry = (Breakpoint *) hash_seq_search(&status))) 1671 | { 1672 | if( entry->data.proxyPid == pid ) 1673 | { 1674 | Breakpoint localCopy = *entry; 1675 | 1676 | entry->data.busy = TRUE; 1677 | 1678 | /* 1679 | * Now copy the global breakpoint into the 1680 | * local breakpoint hash so that the target 1681 | * process will hit it again (other processes 1682 | * will ignore it) 1683 | */ 1684 | 1685 | localCopy.key.targetPid = MyProc->pid; 1686 | 1687 | BreakpointInsertOrUpdate(BP_LOCAL, &localCopy.key, &localCopy.data ); 1688 | } 1689 | } 1690 | 1691 | releaseLock(BP_GLOBAL); 1692 | } 1693 | 1694 | /* --------------------------------------------------------- 1695 | * BreakpointFreeSession() 1696 | * 1697 | * This function marks all of the breakpoints that belong to 1698 | * the given proxy (identified by pid) as 'available'. 1699 | * 1700 | * See the header comment for BreakpointBusySession() for 1701 | * more information 1702 | */ 1703 | 1704 | void 1705 | BreakpointFreeSession(int pid) 1706 | { 1707 | HASH_SEQ_STATUS status; 1708 | Breakpoint *entry; 1709 | 1710 | acquireLock(BP_GLOBAL, LW_EXCLUSIVE); 1711 | 1712 | hash_seq_init(&status, getBreakpointHash(BP_GLOBAL)); 1713 | 1714 | while((entry = (Breakpoint *) hash_seq_search(&status))) 1715 | { 1716 | if( entry->data.proxyPid == pid ) 1717 | entry->data.busy = FALSE; 1718 | } 1719 | 1720 | releaseLock(BP_GLOBAL); 1721 | } 1722 | /* ------------------------------------------------------------ 1723 | * BreakpointDelete() 1724 | * 1725 | * delete the given key from the global breakpoints hash table. 1726 | */ 1727 | bool 1728 | BreakpointDelete(eBreakpointScope scope, BreakpointKey *key) 1729 | { 1730 | Breakpoint *entry; 1731 | 1732 | acquireLock(scope, LW_EXCLUSIVE); 1733 | 1734 | entry = (Breakpoint *) hash_search(getBreakpointHash(scope), (void *) key, HASH_REMOVE, NULL); 1735 | 1736 | if (entry) 1737 | breakCountDelete(scope, ((BreakCountKey *)key)); 1738 | 1739 | releaseLock(scope); 1740 | 1741 | if(entry == NULL) 1742 | return( FALSE ); 1743 | else 1744 | return( TRUE ); 1745 | } 1746 | 1747 | /* ------------------------------------------------------------ 1748 | * BreakpointGetList() 1749 | * 1750 | * This function returns an iterator (*scan) to the caller. 1751 | * The caller can use this iterator to scan through the 1752 | * given hash table (either global or local). The caller 1753 | * must call BreakpointReleaseList() when finished. 1754 | */ 1755 | 1756 | void 1757 | BreakpointGetList(eBreakpointScope scope, HASH_SEQ_STATUS * scan) 1758 | { 1759 | acquireLock(scope, LW_SHARED); 1760 | hash_seq_init(scan, getBreakpointHash(scope)); 1761 | } 1762 | 1763 | /* ------------------------------------------------------------ 1764 | * BreakpointReleaseList() 1765 | * 1766 | * This function releases the iterator lock returned by an 1767 | * earlier call to BreakpointGetList(). 1768 | */ 1769 | 1770 | void 1771 | BreakpointReleaseList(eBreakpointScope scope) 1772 | { 1773 | releaseLock(scope); 1774 | } 1775 | 1776 | /* ------------------------------------------------------------ 1777 | * BreakpointShowAll() 1778 | * 1779 | * sequentially traverse the Global breakpoints hash table and 1780 | * display all the break points via elog(INFO, ...) 1781 | * 1782 | * Note: The display format is still not decided. 1783 | */ 1784 | 1785 | void 1786 | BreakpointShowAll(eBreakpointScope scope) 1787 | { 1788 | HASH_SEQ_STATUS status; 1789 | Breakpoint *entry; 1790 | BreakCount *count; 1791 | 1792 | acquireLock(scope, LW_SHARED); 1793 | 1794 | hash_seq_init(&status, getBreakpointHash(scope)); 1795 | 1796 | elog(INFO, "BreakpointShowAll - %s", scope == BP_GLOBAL ? "global" : "local" ); 1797 | 1798 | while((entry = (Breakpoint *) hash_seq_search(&status))) 1799 | { 1800 | elog(INFO, "Database(%d) function(%d) lineNumber(%d) targetPid(%d) proxyPort(%d) proxyPid(%d) busy(%c) tmp(%c)", 1801 | entry->key.databaseId, 1802 | entry->key.functionId, 1803 | entry->key.lineNumber, 1804 | entry->key.targetPid, 1805 | entry->data.proxyPort, 1806 | entry->data.proxyPid, 1807 | entry->data.busy ? 'T' : 'F', 1808 | entry->data.isTmp ? 'T' : 'F' ); 1809 | } 1810 | 1811 | elog(INFO, "BreakpointCounts" ); 1812 | 1813 | hash_seq_init(&status, getBreakCountHash(scope)); 1814 | 1815 | while((count = (BreakCount *) hash_seq_search(&status))) 1816 | { 1817 | elog(INFO, "Database(%d) function(%d) count(%d)", 1818 | count->key.databaseId, 1819 | count->key.functionId, 1820 | count->count ); 1821 | } 1822 | releaseLock( scope ); 1823 | } 1824 | 1825 | /* ------------------------------------------------------------ 1826 | * BreakpointCleanupProc() 1827 | * 1828 | * sequentially traverse the Global breakpoints hash table and 1829 | * delete any breakpoints for the given process (identified by 1830 | * its process ID). 1831 | */ 1832 | 1833 | void BreakpointCleanupProc(int pid) 1834 | { 1835 | HASH_SEQ_STATUS status; 1836 | Breakpoint *entry; 1837 | 1838 | /* 1839 | * NOTE: we don't care about local breakpoints here, only 1840 | * global breakpoints 1841 | */ 1842 | 1843 | acquireLock(BP_GLOBAL, LW_SHARED); 1844 | 1845 | hash_seq_init(&status, getBreakpointHash(BP_GLOBAL)); 1846 | 1847 | while((entry = (Breakpoint *) hash_seq_search(&status))) 1848 | { 1849 | if( entry->data.proxyPid == pid ) 1850 | { 1851 | entry = (Breakpoint *) hash_search(getBreakpointHash(BP_GLOBAL), &entry->key, HASH_REMOVE, NULL); 1852 | 1853 | breakCountDelete(BP_GLOBAL, ((BreakCountKey *)&entry->key)); 1854 | } 1855 | } 1856 | 1857 | releaseLock(BP_GLOBAL); 1858 | } 1859 | 1860 | /* ========================================================================== 1861 | * Function definitions for BreakCounts hash table 1862 | * 1863 | * Note: All the underneath functions assume that the caller has taken care 1864 | * of all concurrency issues and thus does not do any locking 1865 | * ========================================================================== 1866 | */ 1867 | 1868 | static void 1869 | initLocalBreakCounts(void) 1870 | { 1871 | HASHCTL ctl = {0}; 1872 | 1873 | ctl.keysize = sizeof(BreakCountKey); 1874 | ctl.entrysize = sizeof(BreakCount); 1875 | ctl.hash = tag_hash; 1876 | 1877 | localBreakCounts = hash_create("Local Breakpoint Count Table", 1878 | 32, 1879 | &ctl, 1880 | HASH_ELEM | HASH_FUNCTION ); 1881 | 1882 | if (!globalBreakCounts) 1883 | elog(FATAL, "could not initialize global breakpoints count hash table"); 1884 | } 1885 | 1886 | /* --------------------------------------------------------- 1887 | * breakCountInsert() 1888 | * 1889 | * should be invoked when a breakpoint is added in Breakpoints 1890 | */ 1891 | void 1892 | breakCountInsert(eBreakpointScope scope, BreakCountKey *key) 1893 | { 1894 | BreakCount *entry; 1895 | bool found; 1896 | 1897 | entry = hash_search(getBreakCountHash(scope), key, HASH_ENTER, &found); 1898 | 1899 | if (found) 1900 | entry->count++; 1901 | else 1902 | entry->count = 1; 1903 | } 1904 | 1905 | /* --------------------------------------------------------- 1906 | * breakCountDelete() 1907 | * 1908 | * should be invoked when a breakpoint is removed from Breakpoints 1909 | */ 1910 | void 1911 | breakCountDelete(eBreakpointScope scope, BreakCountKey *key) 1912 | { 1913 | BreakCount *entry; 1914 | 1915 | entry = hash_search(getBreakCountHash(scope), key, HASH_FIND, NULL); 1916 | 1917 | if (entry) 1918 | { 1919 | entry->count--; 1920 | 1921 | /* remove entry only if entry->count is zero */ 1922 | if (entry->count == 0 ) 1923 | hash_search(getBreakCountHash(scope), key, HASH_REMOVE, NULL); 1924 | } 1925 | 1926 | } 1927 | 1928 | /* --------------------------------------------------------- 1929 | * breakCountLookup() 1930 | * 1931 | */ 1932 | static int 1933 | breakCountLookup(eBreakpointScope scope, BreakCountKey *key, bool *found) 1934 | { 1935 | BreakCount *entry; 1936 | 1937 | entry = hash_search(getBreakCountHash(scope), key, HASH_FIND, found); 1938 | 1939 | if (entry) 1940 | return entry->count; 1941 | 1942 | return -1; 1943 | } 1944 | 1945 | /* --------------------------------------------------------- 1946 | * getBreakpointHash() 1947 | * 1948 | * Returns a pointer to the global or local breakpoint hash, 1949 | * depending on the given scope. 1950 | */ 1951 | 1952 | static HTAB * 1953 | getBreakpointHash(eBreakpointScope scope ) 1954 | { 1955 | if( localBreakpoints == NULL ) 1956 | initializeHashTables(); 1957 | 1958 | if (scope == BP_GLOBAL) 1959 | return globalBreakpoints; 1960 | else 1961 | return localBreakpoints; 1962 | } 1963 | 1964 | /* --------------------------------------------------------- 1965 | * getBreakCountHash() 1966 | * 1967 | * Returns a pointer to the global or local breakcount hash, 1968 | * depending on the given scope. 1969 | */ 1970 | 1971 | static HTAB * 1972 | getBreakCountHash(eBreakpointScope scope) 1973 | { 1974 | if( localBreakCounts == NULL ) 1975 | initializeHashTables(); 1976 | 1977 | if (scope == BP_GLOBAL) 1978 | return globalBreakCounts; 1979 | else 1980 | return localBreakCounts; 1981 | } 1982 | -------------------------------------------------------------------------------- /plugin_debugger.def: -------------------------------------------------------------------------------- 1 | EXPORTS 2 | BreakpointBusySession 3 | BreakpointCleanupProc 4 | BreakpointDelete 5 | BreakpointFreeSession 6 | BreakpointGetList 7 | BreakpointInsert 8 | BreakpointInsertOrUpdate 9 | BreakpointLookup 10 | BreakpointOnId 11 | BreakpointReleaseList 12 | BreakpointShowAll 13 | dbgcomm_connect_to_target 14 | dbgcomm_listen_for_target 15 | dbgcomm_accept_target 16 | _PG_init 17 | pldbg_oid_debug 18 | pldbg_abort_target 19 | pldbg_attach_to_port 20 | pldbg_continue 21 | pldbg_create_listener 22 | pldbg_deposit_value 23 | pldbg_drop_breakpoint 24 | pldbg_get_breakpoints 25 | pldbg_get_proxy_info 26 | pldbg_get_source 27 | pldbg_get_stack 28 | pldbg_get_variables 29 | pldbg_select_frame 30 | pldbg_set_breakpoint 31 | pldbg_set_global_breakpoint 32 | pldbg_step_into 33 | pldbg_step_over 34 | pldbg_wait_for_breakpoint 35 | pldbg_wait_for_target 36 | -------------------------------------------------------------------------------- /settings.projinc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | 19 | 0 20 | 21 | 22 | C:\pgbuild\buildtrees\postgresql-8.3beta3 23 | 24 | 25 | C:\pgBuild\gettext 26 | 27 | 28 | C:\pgBuild\OpenSSL 29 | 30 | 31 | C:\pgBuild\krb5 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /uninstall_pldbgapi.sql: -------------------------------------------------------------------------------- 1 | -- uninstall_pldbgapi.sql 2 | -- 3 | -- This script uninstalls the PL/PGSQL debugger API. 4 | -- 5 | -- Note: this isn't needed on 9.1 and above, as the functions and types are 6 | -- packaged in an extension. You can just drop the extension with 7 | -- DROP EXTENSION command. This is still needed to uninstall on older 8 | -- versions, however. 9 | -- 10 | -- Copyright (c) 2004-2024 EnterpriseDB Corporation. All Rights Reserved. 11 | -- 12 | -- Licensed under the Artistic License v2.0, see 13 | -- https://opensource.org/licenses/artistic-license-2.0 14 | -- for full details 15 | 16 | 17 | DROP FUNCTION pldbg_get_target_info(TEXT, "char"); 18 | DROP FUNCTION pldbg_wait_for_target(INTEGER); 19 | DROP FUNCTION pldbg_wait_for_breakpoint(INTEGER); 20 | DROP FUNCTION pldbg_step_over(INTEGER); 21 | DROP FUNCTION pldbg_step_into(INTEGER); 22 | DROP FUNCTION pldbg_set_global_breakpoint(INTEGER, OID, INTEGER, INTEGER); 23 | DROP FUNCTION pldbg_set_breakpoint(INTEGER, OID, INTEGER); 24 | DROP FUNCTION pldbg_select_frame(INTEGER, INTEGER); 25 | DROP FUNCTION pldbg_get_variables(INTEGER); 26 | DROP FUNCTION pldbg_get_proxy_info(); 27 | DROP FUNCTION pldbg_get_stack(INTEGER); 28 | DROP FUNCTION pldbg_get_source(INTEGER, OID); 29 | DROP FUNCTION pldbg_get_breakpoints(INTEGER); 30 | DROP FUNCTION pldbg_drop_breakpoint(INTEGER, OID, INTEGER); 31 | DROP FUNCTION pldbg_deposit_value(INTEGER, TEXT, INTEGER, TEXT); 32 | DROP FUNCTION pldbg_create_listener(); 33 | DROP FUNCTION pldbg_continue(INTEGER); 34 | DROP FUNCTION pldbg_attach_to_port(INTEGER); 35 | DROP FUNCTION pldbg_abort_target(INTEGER); 36 | DROP FUNCTION pldbg_oid_debug(OID); 37 | DROP FUNCTION plpgsql_oid_debug(OID); 38 | 39 | DROP TYPE proxyInfo; 40 | DROP TYPE var; 41 | DROP TYPE targetinfo; 42 | DROP TYPE frame; 43 | DROP TYPE breakpoint; 44 | --------------------------------------------------------------------------------