/* * Copyright (c) 2022 Huawei Technologies Co.,Ltd. All rights reserved. * * 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. * --------------------------------------------------------------------------------------- * * gs_stack.cpp * * IDENTIFICATION * src/gaussdbkernel/cbb/instruments/gs_stack/gs_stack.cpp * * --------------------------------------------------------------------------------------- */ #include "c.h" #include "funcapi.h" #include "pgstat.h" #include #include #include "access/hash.h" #include "utils/elf_parser.h" #include #define MAX_LEN_KEY 30 #define GS_STACK_HASHTBL "stack hash table" const int FINISH_STACK = 2; const int US_PER_WAIT = 5; const int MAX_SIZE_GS_STACK_HASH = 100; const char *const g_gs_stack_signal_file = "gs_stack.cmd"; const char *const g_gs_stack_result_file = "gs_stack.ret"; const char *const g_gs_stack_tmp_file = "gs_stack.ret.tmp"; typedef struct { size_t length_name; char elfname[MAX_LEN_KEY]; } GsStackKey; typedef struct { GsStackKey key; int header_counter; int symbol_counter[SYMHDR_NUM]; bool dyn; Elf64_Sym* sym[SYMHDR_NUM]; char* str_buff[SYMHDR_NUM]; }GsStackEntry; typedef struct { ThreadId tid; pid_t lwtid; }ThreadInfo; uint32 GsStackHashFunc(const void *key, Size keysize) { const GsStackKey *item = (const GsStackKey *) key; uint32 val1 = DatumGetUInt32(hash_any((const unsigned char *)item->elfname, item->length_name)); return val1; } int GsStackMatch(const void* key1, const void* key2, Size keysize) { const GsStackKey* k1 = (const GsStackKey*)key1; const GsStackKey* k2 = (const GsStackKey*)key2; if (k1 != NULL && k2 != NULL && k1->length_name == k2->length_name && (strncmp(k1->elfname, k2->elfname, k1->length_name) == 0)) { return 0; } return 1; } void InitGsStack() { // init memory context if (g_instance.stat_cxt.GsStackContext == NULL) { g_instance.stat_cxt.GsStackContext = AllocSetContextCreate(g_instance.instance_context, "GsStackContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); } HASHCTL ctl; errno_t rc; rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "\0", "\0"); ctl.hcxt = g_instance.stat_cxt.GsStackContext; ctl.keysize = sizeof(GsStackKey); ctl.entrysize = sizeof(GsStackEntry); ctl.hash = GsStackHashFunc; ctl.match = GsStackMatch; g_instance.stat_cxt.GsStackHashTbl = hash_create(GS_STACK_HASHTBL, MAX_SIZE_GS_STACK_HASH, &ctl, HASH_ELEM | HASH_SHRCTX | HASH_FUNCTION | HASH_COMPARE | HASH_NOEXCEPT); } uint get_backtrace_refresh_flag(void) { return (uint)g_instance.stat_cxt.backtrace_info.refresh_flag; } void get_backtrace(void) { g_instance.stat_cxt.backtrace_info.number_pc = backtrace(g_instance.stat_cxt.backtrace_info.backtrace_buff, STACK_PRINT_LIMIT); g_instance.stat_cxt.backtrace_info.refresh_flag = FINISH_STACK; } void print_stack(SIGNAL_ARGS) { get_backtrace(); } bool ready_to_start_backtrace(uint old_refresh_flag) { (void)gs_compare_and_swap_u32(&(g_instance.stat_cxt.backtrace_info.refresh_flag), old_refresh_flag, 1); if (g_instance.stat_cxt.backtrace_info.refresh_flag == 1) { return true; } return false; } void init_backtrace_info(void) { g_instance.stat_cxt.backtrace_info.refresh_flag = 1; int ret = memset_s(g_instance.stat_cxt.backtrace_info.backtrace_buff, STACK_PRINT_LIMIT, 0, STACK_PRINT_LIMIT); securec_check(ret, "\0", "\0"); g_instance.stat_cxt.backtrace_info.number_pc = 0; } void free_entry(GsStackEntry *entry) { if (entry == NULL) { return; } int i; for (i = 0; i < entry->header_counter; i++) { if (entry->sym[i] != NULL) { pfree(entry->sym[i]); entry->sym[i] = NULL; } if (entry->str_buff[i] != NULL) { pfree(entry->str_buff[i]); entry->str_buff[i] = NULL; } } } bool load_string_table(GsStackEntry *entry, int fd, const Elf64_Shdr* symhdr, const Elf64_Ehdr ehdr, Elf64_Shdr shdr) { off_t off; size_t len_str_buff[SYMHDR_NUM] = {0}; for (int ti = 0; ti < entry->header_counter; ti++) { /* sh_offset gives the byte offset from the beginning of the file to the first byte in the section */ off = (off_t)(symhdr[ti].sh_offset); if (lseek(fd, off, SEEK_SET) != off) { return false; } /* symbol table holds a table of fixed-size entries, sh_entsize gives the size in bytes of each entry */ entry->symbol_counter[ti] = (int)(symhdr[ti].sh_size / symhdr[ti].sh_entsize); /* sh_size gives the section's size in bytes */ entry->sym[ti] = (Elf64_Sym*)palloc0(symhdr[ti].sh_size); for (int si = 0; si < entry->symbol_counter[ti]; si++) { if (read(fd, &entry->sym[ti][si], sizeof(Elf64_Sym)) != sizeof(Elf64_Sym)) { return false; } } /* sh_link holds a section header table index link, for UNIX system V, The section header index of the associated string table */ off = (off_t)(ehdr.e_shoff + symhdr[ti].sh_link * ehdr.e_shentsize); if (lseek(fd, off, SEEK_SET) != off) { return false; } if (read(fd, &shdr, sizeof(Elf64_Shdr)) != sizeof(Elf64_Shdr)) { return false; } off = (off_t)(shdr.sh_offset); if (lseek(fd, off, SEEK_SET) != off) { return false; } len_str_buff[ti] = shdr.sh_size + 1; entry->str_buff[ti] = (char*)palloc0(len_str_buff[ti]); if (read(fd, entry->str_buff[ti], shdr.sh_size) == -1) { return false; } } return true; } bool load_symbol_to_hashtbl(GsStackEntry *entry, int fd) { const int limit = 1000; Elf64_Ehdr ehdr; Elf64_Shdr shdr; Elf64_Shdr symhdr[SYMHDR_NUM] = {0}; off_t off; if (read(fd, &ehdr, sizeof(Elf64_Ehdr)) != sizeof(Elf64_Ehdr)) { return false; } entry->dyn = (ehdr.e_type == ET_DYN) ? true : false; /* e_shnum holds the number of entries in the section header table. */ if (ehdr.e_shnum > limit) { return false; } entry->header_counter = 0; for (unsigned int i = 0; i < ehdr.e_shnum; i++) { /* e_shoff holds the section header table's file offset in bytes. */ /* e_shentsize holds a section header's size in bytes. A section header is one entry in the section header table, all entries are the same size */ off = (off_t)(ehdr.e_shoff + i * ehdr.e_shentsize); if (pread(fd, &shdr, sizeof(Elf64_Shdr), off) != sizeof(Elf64_Shdr)) { return false; } /* sh_type categorizes the section's contents and semantics. SHT_SYMTAB hold a symbol table. Currently, an object file may have only one section of each type, but this restriction may be relaxed in the future. Typically, SHT_SYMTAB provides symbols for link editing, though it may also be used for dynamic linking. As a complete symbol table, it may contain many symbols unnecessary for dynamic linking. Consequently, an object file may also contain a SHT_DYNSYM section, which holds a minimal set of dynamic linking symbols, to save space. */ if ((shdr.sh_type == SHT_SYMTAB || shdr.sh_type == SHT_DYNSYM) && entry->header_counter < SYMHDR_NUM) { if (memcpy_s(&(symhdr[entry->header_counter]), sizeof(Elf64_Shdr), &shdr, sizeof(Elf64_Shdr))) { return false; } entry->header_counter++; } } return load_string_table(entry, fd, symhdr, ehdr, shdr); } bool find_symbol_entry(const char *filename, HTAB *gs_stack_hashtbl, GsStackEntry **entry) { Assert(filename); GsStackKey key_gs_stack = {0}; bool ret = true; int n_ret; bool found = false; char* base_name = basename((char*)filename); key_gs_stack.length_name = strlen(base_name); n_ret = snprintf_s(key_gs_stack.elfname, MAX_LEN_KEY, MAX_LEN_KEY - 1, "%s", base_name); if (n_ret < 0) { /* if file name longer than keysize, we just choose first keysize byte from filename as key. */ ereport(LOG, (errmsg("elf name %s is too long.", base_name))); } *entry = (GsStackEntry *)hash_search(gs_stack_hashtbl, (const void*)(&key_gs_stack), HASH_ENTER, &found); if (!found) { int fd = open(filename, O_RDONLY); if (fd != -1) { ret = load_symbol_to_hashtbl(*entry, fd); (void)close(fd); } if (fd == -1 || ret != true) { if (ret != true) { free_entry(*entry); } if (hash_search(gs_stack_hashtbl, (const void*)(&key_gs_stack), HASH_REMOVE, NULL) == NULL) { ereport(ERROR, ( errcode(ERRCODE_DATA_CORRUPTED), errmsg("gs_stack hash table corrupted"))); } return false; } } return true; } void demangle_one_symbol(const char * symbol_name, char* demangled_symbol_name, size_t len) { size_t len_demangle = 0; int status; errno_t errorno; char* demangle_buf = abi::__cxa_demangle(symbol_name, NULL, &len_demangle, &status); if (status != 0) { ereport(LOG, (errmsg("can not demangle %s. status is %d", symbol_name, status))); if (demangle_buf != NULL) { free(demangle_buf); demangle_buf = NULL; } errorno = memcpy_s(demangled_symbol_name, len, symbol_name, len); securec_check(errorno, "\0", "\0"); } else { ereport(LOG, (errmsg("demangle from %s to %s len_demangle is %zu", symbol_name, demangle_buf, len_demangle))); errorno = memcpy_s(demangled_symbol_name, len, demangle_buf, len_demangle); free(demangle_buf); securec_check(errorno, "\0", "\0"); } } bool resolve_addr_from_entry(const GsStackEntry *entry, uintptr_t addr, char *buf, size_t len, uint *offset) { for (int ti = 0; ti < entry->header_counter; ti++) { if (entry->sym[ti] == NULL) { continue; } for (int si = 0; si < entry->symbol_counter[ti]; si++) { if ((ELF64_ST_TYPE(entry->sym[ti][si].st_info) != STT_FUNC) || entry->sym[ti][si].st_value > addr || (entry->sym[ti][si].st_value + entry->sym[ti][si].st_size) < addr) { continue; } *offset = (uint)(addr - entry->sym[ti][si].st_value); demangle_one_symbol(entry->str_buff[ti] + entry->sym[ti][si].st_name, buf, len); return true; } } return false; } void addr_to_name_bin(void* pc, const Dl_info dlinfo, StringInfoData* call_stack) { bool ret_resolve = false; GsStackEntry* symbol_entry = NULL; char tmp_buf[1024] = {0}; uint sym_off; MemoryContext oldcontext = MemoryContextSwitchTo(g_instance.stat_cxt.GsStackContext); uintptr_t dl_off = (uintptr_t)pc - (uintptr_t)dlinfo.dli_fbase; if (find_symbol_entry(dlinfo.dli_fname, g_instance.stat_cxt.GsStackHashTbl, &symbol_entry)) { uintptr_t addr = symbol_entry->dyn ? dl_off : (uintptr_t)pc; ret_resolve = resolve_addr_from_entry(symbol_entry, addr, tmp_buf, sizeof(tmp_buf), &sym_off); (void)MemoryContextSwitchTo(oldcontext); if (ret_resolve) { appendStringInfo(call_stack, "%s + 0x%x\n", tmp_buf, sym_off); } else { appendStringInfo(call_stack, "%p\n", pc); } } else { (void)MemoryContextSwitchTo(oldcontext); appendStringInfo(call_stack, "%p\n", pc); } } void addr_to_name_reuse(void* pc, StringInfoData* call_stack) { Dl_info dlinfo; char tmp_buf[1024] = {0}; if (dladdr(pc, &dlinfo) && dlinfo.dli_fbase && dlinfo.dli_fname) { /* * If a function's name does not in the dynamic symbol table, * dladdr can't resolve the function's symbol. In this case, we * should parse the executable file to find nearest function name. */ uint sym_off; if (dlinfo.dli_saddr && dlinfo.dli_sname) { sym_off = (uint)((uintptr_t)pc - (uintptr_t)dlinfo.dli_saddr); ereport(LOG, (errmsg("dlinfo.dli_sname %s.", dlinfo.dli_sname))); demangle_one_symbol(dlinfo.dli_sname, tmp_buf, sizeof(tmp_buf)); appendStringInfo(call_stack, "%s + 0x%x\n", tmp_buf, sym_off); } else { addr_to_name_bin(pc, dlinfo, call_stack); } } else { appendStringInfo(call_stack, "%p\n", pc); } } bool ready_to_get_backtrace(void) { return (g_instance.stat_cxt.backtrace_info.refresh_flag == FINISH_STACK); } void finish_backtrace(void) { g_instance.stat_cxt.backtrace_info.refresh_flag = 0; } void get_stack(StringInfoData* call_stack) { int i; #ifdef ENABLE_MEMORY_CHECK /* not show __interceptor_backtrace.part.110<-get_backtrace<-print_stack<-gs_signal_handle <-gs_res_signal_handler<-__restore_rt */ const int number_pc_show = 6; #else /* not show get_backtrace<-print_stack<-gs_signal_handle<-gs_res_signal_handler<-__restore_rt */ const int number_pc_show = 5; #endif for (i = number_pc_show; i < g_instance.stat_cxt.backtrace_info.number_pc; i++) { addr_to_name_reuse(g_instance.stat_cxt.backtrace_info.backtrace_buff[i], call_stack); } } void get_stack_according_to_tid(ThreadId tid, StringInfoData* call_stack) { const int MAX_WAIT = 1000; int i; if (tid == 0) { ereport(WARNING, (errmodule(MOD_GSSTACK), errcode(ERRCODE_INVALID_STATUS), (errmsg("can not get backtrace for thread 0."), errdetail("0 is not a valid thread id.")))); appendStringInfo(call_stack, "invalid thread id\n"); return; } (void)LWLockAcquire(GsStackLock, LW_EXCLUSIVE); PG_TRY(); { init_backtrace_info(); signal_child(tid, SIGURG, -1); for (i = 0; i < MAX_WAIT; i++) { if (ready_to_get_backtrace()) { break; } pg_usleep(US_PER_WAIT); } ereport(LOG, (errmsg("wait %d times.", i))); if (i == MAX_WAIT) { ereport(WARNING, (errmodule(MOD_GSSTACK), errcode(ERRCODE_INVALID_STATUS), (errmsg("can not get backtrace for thread %lu.", tid), errdetail("This thread maybe finished," "or the signal handler of this thread had not been registed.")))); appendStringInfo(call_stack, "thread %lu not available\n", tid); } else { get_stack(call_stack); } finish_backtrace(); } PG_CATCH(); { LWLockRelease(GsStackLock); PG_RE_THROW(); } PG_END_TRY(); LWLockRelease(GsStackLock); } bool get_thread_info_from_signal_slot(pid_t lwtid, ThreadInfo** thread_info, int* size) { unsigned int loop; int real_size = 0; (void)pthread_mutex_lock(&(g_instance.signal_base->slots_lock)); GsSignalSlot* signal_slot = g_instance.signal_base->slots; *thread_info = (ThreadInfo*)palloc0(sizeof(ThreadInfo) * g_instance.signal_base->slots_size); unsigned int slot_size = g_instance.signal_base->slots_size; for (loop = 0; loop < slot_size; loop++) { if (lwtid == 0) { if (signal_slot->thread_id != 0) { (*thread_info)[real_size].tid = signal_slot->thread_id; (*thread_info)[real_size].lwtid = signal_slot->lwtid; real_size++; } } else { if (signal_slot->lwtid == lwtid) { (*thread_info)[0].tid = signal_slot->thread_id; (*thread_info)[0].lwtid = signal_slot->lwtid; real_size = 1; break; } } signal_slot++; } (void)pthread_mutex_unlock(&(g_instance.signal_base->slots_lock)); *size = real_size; return (real_size >= 1); } void get_stack_according_to_lwtid(pid_t lwtid, StringInfoData* call_stack) { int loop = 0; ThreadInfo* thread_info = NULL; int slot_size = 0; bool found = get_thread_info_from_signal_slot(lwtid, &thread_info, &slot_size); if (lwtid != 0) { if (found && thread_info[0].tid > 0) { appendStringInfo(call_stack, "tid<%lu> lwtid<%d>\n", thread_info[0].tid, lwtid); get_stack_according_to_tid(thread_info[0].tid, call_stack); } else { ereport(ERROR, (errmodule(MOD_GSSTACK), errcode(ERRCODE_INVALID_STATUS), (errmsg("can not get backtrace for thread %d.", lwtid), errdetail("please check if the thread is alive.")))); } } else { for (loop = 0; loop < slot_size; loop++) { if (thread_info[loop].tid != 0) { appendStringInfo(call_stack, "Thread %d tid<%lu> lwtid<%d>\n", loop, thread_info[loop].tid, thread_info[loop].lwtid); get_stack_according_to_tid(thread_info[loop].tid, call_stack); appendStringInfo(call_stack, "\n"); } } } if (thread_info != NULL) { pfree(thread_info); } } void check_and_process_gs_stack() { struct stat stat_buf; if (stat(g_gs_stack_signal_file, &stat_buf) != 0) { return; } g_instance.stat_cxt.stack_perf_start = true; } void gs_stack_unlink_file(const char* filename) { if (filename == NULL) { return; } int ret = unlink(filename); if (ret != 0 && (errno != ENOENT)) { ereport(WARNING, (errmsg("unlink %s failed.", filename))); } } void gs_stack_read_signal_file(pid_t* lwtid, pid_t* ctl_pid) { size_t ret; FILE* fd = fopen(g_gs_stack_signal_file, "r"); if (fd == NULL) { gs_stack_unlink_file(g_gs_stack_signal_file); ereport(ERROR, (errmsg("open sig file failed."))); } ret = fread(ctl_pid, sizeof(pid_t), 1, fd); if (ret != 1) { (void)fclose(fd); gs_stack_unlink_file(g_gs_stack_signal_file); ereport(ERROR, (errmsg("read ctl pid failed err."))); } ret = fread(lwtid, sizeof(pid_t), 1, fd); if (ret != 1) { (void)fclose(fd); gs_stack_unlink_file(g_gs_stack_signal_file); ereport(ERROR, (errmsg("read ctl lwtid failed err."))); } (void)fclose(fd); gs_stack_unlink_file(g_gs_stack_signal_file); } void gs_stack_write_result_file(const char* result, int length, int ctl_pid) { int mode = O_WRONLY | O_CREAT | PG_BINARY; int flags = 0600; int fd = open(g_gs_stack_tmp_file, mode, flags); if (fd < 0) { ereport(LOG, (errmsg("open result file failed."))); return; } ssize_t ret = write(fd, &ctl_pid, sizeof(int)); if (ret == -1) { ereport(LOG, (errmsg("write ctl pid to file failed."))); (void)close(fd); return; } ret = write(fd, &length, sizeof(int)); if (ret == -1) { ereport(LOG, (errmsg("write stack size to file failed."))); (void)close(fd); return; } ret = write(fd, result, length); if (ret == -1) { ereport(LOG, (errmsg("write stack to file failed."))); (void)close(fd); return; } int iret = fsync(fd); if (iret != 0) { ereport(LOG, (errmsg("sync tmp file failed."))); (void)close(fd); return; } (void)close(fd); gs_stack_unlink_file(g_gs_stack_result_file); iret = rename(g_gs_stack_tmp_file, g_gs_stack_result_file); if (iret != 0) { ereport(LOG, (errmsg("rename tmp file to stack failed."))); return; } } void get_stack_and_write_result() { pid_t lwtid = 0; pid_t ctl_pid = 0; StringInfoData result; MemoryContext oldcontext = MemoryContextSwitchTo(g_instance.stat_cxt.GsStackContext); PG_TRY(); { initStringInfo(&result); gs_stack_read_signal_file(&lwtid, &ctl_pid); get_stack_according_to_lwtid(lwtid, &result); } PG_CATCH(); { /* Must reset elog.c's state */ (void)MemoryContextSwitchTo(g_instance.stat_cxt.GsStackContext); ErrorData* edata = CopyErrorData(); FlushErrorState(); appendStringInfo(&result, "%s", edata->message); /* release edata */ FreeErrorData(edata); } PG_END_TRY(); gs_stack_write_result_file(result.data, result.len, ctl_pid); FreeStringInfo(&result); (void)MemoryContextSwitchTo(oldcontext); } void print_all_stack() { StringInfoData result; MemoryContext oldcontext = MemoryContextSwitchTo(g_instance.stat_cxt.GsStackContext); PG_TRY(); { initStringInfo(&result); get_stack_according_to_lwtid(0, &result); } PG_CATCH(); { /* Must reset elog.c's state */ ErrorData* edata = CopyErrorData(); FlushErrorState(); appendStringInfo(&result, "%s", edata->message); /* release edata */ FreeErrorData(edata); } PG_END_TRY(); ereport(LOG, (errmsg("Print all thread stack \n%s", result.data))); FreeStringInfo(&result); (void)MemoryContextSwitchTo(oldcontext); } void save_all_stack_to_tuple(PG_FUNCTION_ARGS) { const int attrs = 3; int i = 1; ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; MemoryContext oldcontext = MemoryContextSwitchTo(rsinfo->econtext->ecxt_per_query_memory); StringInfoData call_stack; initStringInfo(&call_stack); TupleDesc tupdesc = CreateTemplateTupleDesc(attrs, false); TupleDescInitEntry(tupdesc, (AttrNumber)i++, "tid", INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber)i++, "lwtid", INT8OID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber)i, "stack", TEXTOID, -1, 0); rsinfo->returnMode = SFRM_Materialize; rsinfo->setResult = tuplestore_begin_heap(true, false, u_sess->attr.attr_memory.work_mem); rsinfo->setDesc = BlessTupleDesc(tupdesc); (void)MemoryContextSwitchTo(oldcontext); errno_t rc; Datum values[attrs]; bool nulls[attrs]; ThreadInfo* thread_info = NULL; int slot_size = 0; (void)get_thread_info_from_signal_slot(0, &thread_info, &slot_size); for (i = 0; i <= slot_size; i++) { if (thread_info[i].tid > 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"); values[ARG_0] = Int64GetDatum(thread_info[i].tid); values[ARG_1] = Int32GetDatum(thread_info[i].lwtid); get_stack_according_to_tid(thread_info[i].tid, &call_stack); values[ARG_2] = CStringGetTextDatum(call_stack.data); tuplestore_putvalues(rsinfo->setResult, tupdesc, values, nulls); resetStringInfo(&call_stack); } } pfree(thread_info); FreeStringInfo(&call_stack); tuplestore_donestoring(rsinfo->setResult); } /* print stacks of threads */ Datum gs_stack(PG_FUNCTION_ARGS) { if (!superuser() && (!isMonitoradmin(GetUserId()))) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("Must be system admin or monitor admin to get stack.")))); ThreadId tid; StringInfoData call_stack; initStringInfo(&call_stack); if (PG_NARGS() == 1) { tid = (ThreadId)DatumGetUInt64(PG_GETARG_DATUM(0)); get_stack_according_to_tid(tid, &call_stack); } else { save_all_stack_to_tuple(fcinfo); return (Datum)0; } PG_RETURN_TEXT_P(cstring_to_text(call_stack.data)); }