Files
openGauss-server/contrib/gms_debug/gms_debug.cpp
2024-11-21 14:43:03 +08:00

520 lines
20 KiB
C++

/*
* Copyright (c) 2024 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
* --------------------------------------------------------------------------------------
*
* gms_debug.cpp
* gms_debug can effectively estimate statistical data.
*
*
* IDENTIFICATION
* contrib/gms_debug/gms_debug.cpp
*
* --------------------------------------------------------------------------------------
*/
#include "postgres.h"
#include "utils/plpgsql_domain.h"
#include "commands/copy.h"
#include "funcapi.h"
#include "utils/plpgsql.h"
#include <sys/socket.h>
#include "utils/pl_debug.h"
#include "lib/stringinfo.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "catalog/pg_authid.h"
#include "miscadmin.h"
#include "gms_debug.h"
#include <bitset>
PG_MODULE_MAGIC;
PG_FUNCTION_INFO_V1(gms_debug_attach_session);
PG_FUNCTION_INFO_V1(gms_debug_detach_session);
PG_FUNCTION_INFO_V1(gms_debug_get_runtime_info);
PG_FUNCTION_INFO_V1(gms_debug_initialize);
PG_FUNCTION_INFO_V1(gms_debug_off);
PG_FUNCTION_INFO_V1(gms_debug_continue);
PG_FUNCTION_INFO_V1(gms_debug_set_breakpoint);
static void gms_attach_session(int commidx, uint64 sid)
{
PlDebuggerComm* debug_comm = &g_instance.pldebug_cxt.debug_comm[commidx];
AutoMutexLock debuglock(&debug_comm->mutex);
debuglock.lock();
if (debug_comm->Used()) {
if (debug_comm->hasClient()) {
debuglock.unLock();
ereport(ERROR, (errmodule(MOD_PLDEBUGGER), errcode(ERRCODE_TARGET_SERVER_ALREADY_ATTACHED),
(errmsg("target session already attached on other client."))));
}
if (debug_comm->hasClientErrorOccured || debug_comm->hasServerErrorOccured) {
debuglock.unLock();
ereport(ERROR, (errmodule(MOD_PLDEBUGGER), errcode(ERRCODE_INVALID_OPERATION),
(errmsg("target function is not running in expected way."))));
}
debug_comm->clientId = sid;
} else {
debuglock.unLock();
ereport(ERROR, (errmodule(MOD_PLDEBUGGER), errcode(ERRCODE_INVALID_OPERATION),
(errmsg("target session should be init first."))));
}
debuglock.unLock();
}
static bool GMSInterfaceCheck(const char* funcname, bool needAttach)
{
#ifdef ENABLE_MULTIPLE_NODES
PLDEBUG_FEATURE_NOT_SUPPORT_IN_DISTRIBUTED();
#endif
if (!superuser() && !is_member_of_role(GetUserId(), DEFAULT_ROLE_PLDEBUGGER)) {
ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
(errmsg("must be system admin to execute", funcname))));
return false;
}
if (u_sess->plsql_cxt.debug_client != NULL && needAttach){
int commIdx = u_sess->plsql_cxt.debug_client->comm_idx;
CHECK_DEBUG_COMM_VALID(commIdx);
/* if current debug index is not myself during debug, clean up my self */
PlDebuggerComm* debug_comm = &g_instance.pldebug_cxt.debug_comm[commIdx];
DebugClientInfo* client = u_sess->plsql_cxt.debug_client;
AutoMutexLock debuglock(&debug_comm->mutex);
debuglock.lock();
if (debug_comm == nullptr) {
client->comm_idx = -1;
MemoryContextDelete(client->context);
u_sess->plsql_cxt.debug_client = NULL;
debuglock.unLock();
ereport(ERROR,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("must attach a execute func before execute", funcname),
errhint("attach a execute func and retry")));
return false;
}
if (!debug_comm->isRunning()) {
debuglock.unLock();
return false;
}
debuglock.unLock();
}
return true;
}
/*
* This function initializes the target session for debugging.
*/
Datum gms_debug_initialize(PG_FUNCTION_ARGS)
{
StringInfoData buf;
bool found = false;
// TupleDesc tupdesc;
int commIdx = -1;
const int DEBUG_TURN_ON_ATTR_NUM = 1;
GMSInterfaceCheck("gms_debug.initialize", false);
if (unlikely(u_sess->plsql_cxt.debug_proc_htbl == NULL)) {
init_pldebug_htcl();
}
/* return nodename & socket idx as port */
if (!PG_ARGISNULL(0)) {
// check if the debug_session_id valid
char *debug_session_id = text_to_cstring(PG_GETARG_VARCHAR_PP(0));
char *psave = NULL;
char *nodename = strtok_r(debug_session_id, "-", &psave);
char *fir = AssignStr(psave, false);
char *new_fir = TrimStr(fir);
if (new_fir == NULL) {
ereport(ERROR, ( (errmsg("invalid debug_session_id %s", debug_session_id))));
}
commIdx = pg_strtoint32(new_fir);
if (commIdx < 0 || commIdx >= PG_MAX_DEBUG_CONN) {
ereport(ERROR, ( (errmsg("invalid debug_session_id %s", debug_session_id))));
}
if (!AcquireDebugCommIdx(commIdx)) {
ereport(ERROR,
( errmsg("debug_session_id %s has already been used", debug_session_id)));
}
} else {
commIdx = GetValidDebugCommIdx();
if (commIdx == -1) {
ereport(ERROR, (
(errmsg("max debug function is %d, turn_on function is out of range", PG_MAX_DEBUG_CONN))));
}
}
SetDebugCommGmsUsed(commIdx, true);
// dms_debug indicates that session debugging functionality should be enabled.
u_sess->plsql_cxt.gms_debug_idx = commIdx;
initStringInfo(&buf);
// simple concatenate node_name and port with an underscore
appendStringInfo(&buf, "%s-%d", g_instance.attr.attr_common.PGXCNodeName, commIdx);
PG_RETURN_VARCHAR_P(cstring_to_text(buf.data));
}
/*
* This procedure notifies the debug session about the target program.
*/
Datum gms_debug_attach_session(PG_FUNCTION_ARGS)
{
GMSInterfaceCheck("gms_debug.attach_session", false);
char *debug_session_id = text_to_cstring(PG_GETARG_VARCHAR_PP(0));
int32 diagnostics = PG_GETARG_INT32(1);
char *psave = NULL;
int commidx = -1;
char *nodename = strtok_r(debug_session_id, "-", &psave);
char *fir = AssignStr(psave, false);
char *new_fir = TrimStr(fir);
if (new_fir == NULL) {
ereport(ERROR, ( (errmsg("invalid debug_session_id %s", debug_session_id))));
}
commidx = pg_strtoint32(new_fir);
/* if is attach to some other function, just clean up it */
clean_up_debug_client(true);
/* this nodename check is only for single node */
nodename = TrimStr(nodename);
if (nodename == NULL || strcasecmp(nodename, g_instance.attr.attr_common.PGXCNodeName) != 0) {
ereport(ERROR, ( errcode(ERRCODE_AMBIGUOUS_PARAMETER),
(errmsg("wrong debug nodename, should be %s.", g_instance.attr.attr_common.PGXCNodeName))));
}
if (commidx < 0 || commidx >= PG_MAX_DEBUG_CONN) {
ereport(ERROR, ( errcode(ERRCODE_AMBIGUOUS_PARAMETER),
(errmsg("invalid debug port id %d.", commidx))));
}
/* only can attach when comm satisfy contidion */
gms_attach_session(commidx, u_sess->session_id);
u_sess->plsql_cxt.debug_client = InitDebugClient(commidx);
PG_RETURN_VOID();
}
/**
* This function sets a breakpoint in a program unit, which persists for the current session.
*/
Datum gms_debug_set_breakpoint(PG_FUNCTION_ARGS)
{
const int DEBUG_BREAK_TUPLE_ATTR_NUM = 2;
Oid funcOid = PG_GETARG_OID(0);
int32 lineno = PG_GETARG_INT32(1);
int32 fuzzy = PG_GETARG_INT32(2);
int32 iterations = PG_GETARG_INT32(3);
int headerlines = 0;
uint32 nLine = 0;
CodeLine *lines = NULL;
CodeLine cl;
cl.code = NULL;
bool found = false;
DebugClientInfo *client = u_sess->plsql_cxt.debug_client;
StringInfoData str;
if(client == nullptr) {
ereport(ERROR,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("error happened in debug session, please reattach target session and try")));
}
TupleDesc tupdesc;
MemoryContext oldcontext = MemoryContextSwitchTo(client->context);
tupdesc = CreateTemplateTupleDesc(DEBUG_BREAK_TUPLE_ATTR_NUM, false);
TupleDescInitEntry(tupdesc, (AttrNumber)1, "status", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)2, "breakpoint", INT4OID, -1, 0);
TupleDesc tuple_desc = BlessTupleDesc(tupdesc);
Datum values[DEBUG_BREAK_TUPLE_ATTR_NUM];
bool nulls[DEBUG_BREAK_TUPLE_ATTR_NUM];
HeapTuple tuple;
errno_t rc = 0;
rc = memset_s(values, sizeof(values), 0, sizeof(values));
securec_check(rc, "\0", "\0");
rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls));
securec_check(rc, "\0", "\0");
(void)MemoryContextSwitchTo(oldcontext);
values[0] = Int32GetDatum(0);
values[1] = Int32GetDatum(-1);
if (OidIsValid(funcOid)) {
bool checked = GMSInterfaceCheck("gms_debug.add_breakpoint", true);
if(!checked) {
ereport(WARNING,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("target func not attached")));
values[0] = Int32GetDatum(ERROR_BAD_HANDLE);
tuple = heap_form_tuple(tuple_desc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
} else {
ereport(WARNING, (errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("pl debugger only support function with language plpgsql"),
errdetail("the given function is %lu", funcOid),
errcause("pl debugger do not support the given function"),
erraction("use pl debugger with only plpgsql function")));
values[0] = Int32GetDatum(ERROR_BAD_HANDLE);
tuple = heap_form_tuple(tuple_desc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
if (unlikely(u_sess->plsql_cxt.debug_proc_htbl == NULL)) {
init_pldebug_htcl();
}
PlDebugEntry *entry =
(PlDebugEntry *)hash_search(u_sess->plsql_cxt.debug_proc_htbl, (void *)(&funcOid), HASH_ENTER, &found);
entry->key = funcOid;
if (!found) {
entry->commIdx = client->comm_idx;
entry->func = NULL;
}
initStringInfo(&str);
uint64 sid = ENABLE_THREAD_POOL ? u_sess->session_id : t_thrd.proc_cxt.MyProcPid;
appendStringInfo(&str, "%lu:%u:%d:%s", sid, funcOid, lineno, cl.code == NULL ? "NULL" : cl.code);
debug_client_send_msg(client, GMS_DEBUG_ADDBREAKPOINT_HEADER, str.data, str.len);
debug_client_rec_msg(client);
int32 ans = pg_strtoint32(client->rec_buffer);
pfree_ext(lines);
pfree_ext(str.data);
if (ans == ADD_BP_ERR_ALREADY_EXISTS) {
ereport(WARNING, (errcode(ERRCODE_WARNING),
errmsg("the given line number already contains a valid breakpoint.")));
values[0] = Int32GetDatum(ERROR_ALREADY_EXISTS);
tuple = heap_form_tuple(tuple_desc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
} else if (ans == ADD_BP_ERR_OUT_OF_RANGE) {
ereport(WARNING, (errcode(ERRCODE_WARNING),
errmsg("lineno must be within the range of [1, MaxLineNumber]." )));
values[0] = Int32GetDatum(ERROR_ILLEGAL_LINE);
tuple = heap_form_tuple(tuple_desc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
} else if (ans == ADD_BP_ERR_INVALID_BP_POS) {
ereport(WARNING, (errcode(ERRCODE_WARNING),
errmsg("the given line number does not name a valid breakpoint.")));
values[0] = Int32GetDatum(ERROR_ILLEGAL_LINE);
tuple = heap_form_tuple(tuple_desc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
values[0] = Int32GetDatum(0);
values[1] = Int32GetDatum(ans);
tuple = heap_form_tuple(tuple_desc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
static char* parse_and_valid(char **psave, const char* rec_buf)
{
char *fir = strtok_r(NULL, ":", psave);
char *new_fir = TrimStr(fir);
if (new_fir == NULL) {
ReportInvalidMsg(rec_buf);
return NULL;
}
return new_fir;
}
static Datum build_runtime_info(DebugClientInfo *client, int err_code)
{
const int DEBUG_RUNTIME_TUPLE_ATTR_NUM = 8;
int i = 0;
TupleDesc tupdesc;
MemoryContext oldcontext = MemoryContextSwitchTo(client->context);
tupdesc = CreateTemplateTupleDesc(DEBUG_RUNTIME_TUPLE_ATTR_NUM, false);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "err_code", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "run_line", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "run_breakpoint", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "run_stackdepth", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "run_reason", INT4OID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "pro_namespace", OIDOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "pro_name", TEXTOID, -1, 0);
TupleDescInitEntry(tupdesc, (AttrNumber)++i, "pro_owner", OIDOID, -1, 0);
TupleDesc tuple_desc = BlessTupleDesc(tupdesc);
/* Received buffer will be in the form of <namespaceoid, owneroid, lineno, breakpoint, stackdepth, reason,
* pkgfuncname> */
char *psave = NULL;
char *fir = strtok_r(client->rec_buffer, ":", &psave);
const int int64Size = 10;
Oid namespaceoid;
Oid funcoid;
Oid owneroid;
int run_line = -1;
int run_breakpoint = -1;
int run_stackdepth = -1;
int run_reason = 0;
char *pro_name = NULL;
Datum values[DEBUG_RUNTIME_TUPLE_ATTR_NUM];
bool nulls[DEBUG_RUNTIME_TUPLE_ATTR_NUM];
HeapTuple tuple;
errno_t rc = 0;
if(err_code == 0) {
char *new_fir = TrimStr(fir);
if (new_fir == NULL) {
ReportInvalidMsg(client->rec_buffer);
PG_RETURN_DATUM(0);
}
funcoid = (Oid)pg_strtouint64(new_fir, NULL, int64Size);
new_fir = parse_and_valid(&psave, client->rec_buffer);
CHECK_RETURN_DATUM(new_fir);
namespaceoid = (Oid)pg_strtouint64(new_fir, NULL, int64Size);
new_fir = parse_and_valid(&psave, client->rec_buffer);
CHECK_RETURN_DATUM(new_fir);
owneroid = (Oid)pg_strtouint64(new_fir, NULL, int64Size);
new_fir = parse_and_valid(&psave, client->rec_buffer);
CHECK_RETURN_DATUM(new_fir);
run_line = pg_strtoint32(new_fir);
new_fir = parse_and_valid(&psave, client->rec_buffer);
CHECK_RETURN_DATUM(new_fir);
run_breakpoint = pg_strtoint32(new_fir);
new_fir = parse_and_valid(&psave, client->rec_buffer);
CHECK_RETURN_DATUM(new_fir);
run_stackdepth = pg_strtoint32(new_fir);
new_fir = parse_and_valid(&psave, client->rec_buffer);
CHECK_RETURN_DATUM(new_fir);
run_reason = pg_strtoint32(new_fir);
pro_name = AssignStr(psave, false);
}
(void)MemoryContextSwitchTo(oldcontext);
rc = memset_s(values, sizeof(values), 0, sizeof(values));
securec_check(rc, "\0", "\0");
rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls));
securec_check(rc, "\0", "\0");
i = 0;
values[i++] = Int32GetDatum(err_code);
values[i++] = Int32GetDatum(run_line);
values[i++] = Int32GetDatum(run_breakpoint);
values[i++] = Int32GetDatum(run_stackdepth);
values[i++] = Int32GetDatum(run_reason);
values[i++] = ObjectIdGetDatum(namespaceoid);
values[i++] = CStringGetTextDatum(pro_name);
values[i++] = ObjectIdGetDatum(owneroid);
tuple = heap_form_tuple(tuple_desc, values, nulls);
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
}
/**
* This function passes the given breakflags
* (a mask of the events that are of interest) to Probe in the target process.
* It tells Probe to continue execution of the target process,
* and it waits until the target process runs to completion or signals an event.
* If info_requested is not NULL, then calls GET_RUNTIME_INFO.
*/
Datum gms_debug_continue(PG_FUNCTION_ARGS)
{
DebugClientInfo *client = u_sess->plsql_cxt.debug_client;
if(client == nullptr) {
ereport(ERROR,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("error happened in debug session, please reattach target session and try")));
}
const int DEBUG_ACTION_ATTR_NUM = 5;
const char actions[DEBUG_ACTION_ATTR_NUM] = {
GMS_DEBUG_CONTINUE_HEADER,
GMS_DEBUG_NEXT_HEADER,
GMS_DEBUG_STEP_INTO_HEADER,
GMS_DEBUG_FINISH_HEADER,
GMS_DEBUG_ABORT_HEADER
};
int err_code = 0;
bool checked = GMSInterfaceCheck("gms_debug.continue", true);
if(!checked) {
ereport(WARNING,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("target func not attached")));
err_code = ERROR_FUNC_NOT_ATTACHED;
return build_runtime_info(client,err_code);
}
int32 breakflags = PG_GETARG_INT32(0);
int32 info_requested = PG_GETARG_INT32(1);
char action;
std::bitset<DEBUG_ACTION_ATTR_NUM> breakflags_bitset(breakflags);
for (int i = 0; i < breakflags_bitset.size(); ++i) {
if (breakflags_bitset.test(i)) {
action = actions[i];
}
}
debug_client_send_msg(client, action, NULL, 0);
debug_client_rec_msg(client);
return build_runtime_info(client,err_code);
}
/***
* This function returns information about the current program.
* It is only needed if the info_requested parameter to SYNCHRONIZE
* or CONTINUE was set to 0.
*/
Datum gms_debug_get_runtime_info(PG_FUNCTION_ARGS)
{
int err_code = 0;
DebugClientInfo *client = u_sess->plsql_cxt.debug_client;
if(client == nullptr) {
ereport(ERROR,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("error happened in debug session, please reattach target session and try")));
}
bool checked = GMSInterfaceCheck("gms_debug.runtime_info",true);
if(!checked) {
ereport(WARNING,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("target func not attached")));
err_code = ERROR_FUNC_NOT_ATTACHED;
return build_runtime_info(client, err_code);
}
int32 info_requested = PG_GETARG_INT32(0);
debug_client_send_msg(client, GMS_DEBUG_RUNTIMEINFO_HEADER, NULL, 0);
debug_client_rec_msg(client);
return build_runtime_info(client, err_code);
}
/***
* This procedure notifies the target session that
* debugging should no longer take place in that session.
* It is not necessary to call this function before ending the session.
*/
Datum gms_debug_off(PG_FUNCTION_ARGS)
{
GMSInterfaceCheck("gms_debug.debug_off",false);
// dms_debug indicates that session debugging functionality should be enabled.
SetDebugCommGmsUsed(u_sess->plsql_cxt.gms_debug_idx, false);
u_sess->plsql_cxt.gms_debug_idx = -1;
PG_RETURN_VOID();
}
/***
* This procedure stops debugging the target program.
* This procedure may be called at any time,
* but it does not notify the target session that the debug session is detaching itself,
* and it does not terminate execution of the target session.
* Therefore, care should be taken to ensure that the target session does not hang itself.
*/
Datum gms_debug_detach_session(PG_FUNCTION_ARGS)
{
DebugClientInfo *client = u_sess->plsql_cxt.debug_client;
if(client == nullptr) {
ereport(ERROR,
(errcode(ERRCODE_TARGET_SERVER_NOT_ATTACHED),
errmsg("error happened in debug session, please reattach target session and try")));
}
GMSInterfaceCheck("gms_debug.detach_session",true);
debug_client_send_msg(client, DEBUG_ABORT_HEADER, NULL, 0);
debug_client_rec_msg(client);
clean_up_debug_client();
PG_RETURN_VOID();
}