diff --git a/BUILD.gn b/BUILD.gn index 979810cf..93a507d4 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -37,6 +37,7 @@ shared_library("zoslib") { "include/zos-sys-info.h", "include/zos-tls.h", "src/zos.cc", + "src/zos-allocs-trace.cc", "src/zos-bpx.cc", "src/zos-char-util.cc", "src/zos-getentropy.cc", @@ -45,6 +46,7 @@ shared_library("zoslib") { "src/zos-spawn.cc", "src/zos-sys-info.cc", "src/zos-tls.cc", + "src/zos-tsearch.c", ] configs += [ ":zoslib_config" ] diff --git a/CMakeLists.txt b/CMakeLists.txt index 96581251..294d087a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,6 +11,7 @@ project(libzoslib CXX C ASM) if(${CMAKE_C_COMPILER} MATCHES xlclang) include_directories(BEFORE include) + include_directories(BEFORE include include/c++) else() include_directories(BEFORE include include/c++/v1) endif() diff --git a/include/c++/cstdlib b/include/c++/cstdlib new file mode 100644 index 00000000..35887f8b --- /dev/null +++ b/include/c++/cstdlib @@ -0,0 +1,27 @@ +/////////////////////////////////////////////////////////////////////////////// +// Licensed Materials - Property of IBM +// ZOSLIB +// (C) Copyright IBM Corp. 2024. All Rights Reserved. +// US Government Users Restricted Rights - Use, duplication +// or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +/////////////////////////////////////////////////////////////////////////////// + +#pragma once +/* + * Works around error with xlclang: + * /c++/cstdlib:line:col: error: no member named '__calloc_prtsrc' in the global namespace + * /include/stdlib.h:line:col: note: expanded from macro 'calloc' + * #define calloc __calloc_prtsrc + * Same for malloc and realloc. +*/ + +#if defined(ZOSLIB_TRACE_ALLOCS) && defined(__clang__) && defined(__ibmxl__) +#undef __calloc_prtsrc +#undef __malloc_prtsrc +#undef __realloc_prtsrc +void *__calloc_prtsrc(size_t, size_t); +void *__malloc_prtsrc(size_t); +void *__realloc_prtsrc(void *, size_t); +#endif + +#include_next diff --git a/include/stdlib.h b/include/stdlib.h index b5e7d599..ec860a53 100644 --- a/include/stdlib.h +++ b/include/stdlib.h @@ -38,6 +38,50 @@ __Z_EXPORT int __mkstemp_ascii(char*); #define getenv __getenv_replaced #endif +#ifdef ZOSLIB_TRACE_ALLOCS + +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +void *__calloc_orig(size_t, size_t) __asm("calloc"); +void *__malloc_orig(size_t) __asm("malloc"); +void *__realloc_orig(void *, size_t) __asm("realloc"); +void __free_orig(void *) __asm("free"); +void *__malloc31_orig(size_t) __asm("__malloc31"); + +__Z_EXPORT void * __calloc_trace(size_t, size_t, const char *pfname, int linenum); +__Z_EXPORT void * __malloc_trace(size_t, const char *pfname, int linenum); +__Z_EXPORT void * __realloc_trace(void *, size_t, const char *pfname, int linenum); +__Z_EXPORT void * __reallocf_trace(void *, size_t, const char *pfname, int linenum); +__Z_EXPORT void __free_trace(void *); +__Z_EXPORT void * __malloc31_trace(size_t, const char *pfname, int linenum); + +#define __calloc_prtsrc(x, y) __calloc_trace(x, y, __FILE__, __LINE__) +#define __malloc_prtsrc(x) __malloc_trace(x, __FILE__, __LINE__) +#define __realloc_prtsrc(x, y) __realloc_trace(x, y, __FILE__, __LINE__) +#define __reallocf_prtsrc(x, y) __reallocf_trace(x, y, __FILE__, __LINE__) +#define __malloc31_prtsrc(x) __malloc31_trace(x, __FILE__, __LINE__) + +#if defined(__cplusplus) +} +#endif + +#undef calloc +#undef malloc +#undef realloc +#undef free +#undef __malloc31 + +#define calloc __calloc_replaced +#define malloc __malloc_replaced +#define realloc __realloc_replaced +#define free __free_replaced +#define __malloc31 __malloc31_replaced + +#endif /* ZOSLIB_TRACE_ALLOCS */ #include_next @@ -83,6 +127,27 @@ __Z_EXPORT char* getenv(const char*) __asm("@@A00423"); #endif #endif +#ifdef ZOSLIB_TRACE_ALLOCS +#undef calloc +#undef malloc +#undef realloc +#undef free +#undef __malloc31 + +#define calloc __calloc_prtsrc +#define malloc __malloc_prtsrc +#define realloc __realloc_prtsrc +#define __malloc31 __malloc31_prtsrc +// #define free __free_prtsrc +// fails with clang: +// .../include/c++/v1/locale:283:68: error: reference to unresolved using +// declaration +// unique_ptr __stat_hold(nullptr, free); + +void free(void *) __asm("__free_trace"); + +#endif /* ZOSLIB_TRACE_ALLOCS */ + #if defined(__cplusplus) extern "C" { #endif diff --git a/include/string.h b/include/string.h index 918db1bc..b388fb08 100644 --- a/include/string.h +++ b/include/string.h @@ -11,15 +11,51 @@ #include "zos-macros.h" +#if defined(ZOSLIB_TRACE_ALLOCS) +#include + +#if defined(__cplusplus) +extern "C" { +#endif + +char *__strdup_orig(const char *) __asm("strdup"); + +__Z_EXPORT char * __strdup_trace(const char *, const char *pfname, int linenum); +__Z_EXPORT char * __strndup_trace(const char *, size_t n, const char *pfname, int linenum); + +#if defined(__cplusplus) +} +#endif + +#define __strdup_prtsrc(x) __strdup_trace(x, __FILE__, __LINE__) +#define __strndup_prtsrc(x,n) __strndup_trace(x, n, __FILE__, __LINE__) + +#undef strdup +#undef strndup + +#define strdup __strdup_replaced +#define strndup __strndup_replaced + +#endif // ZOSLIB_TRACE_ALLOCS + #include_next +#if defined(ZOSLIB_TRACE_ALLOCS) +#undef strdup +#undef strndup +#define strdup __strdup_prtsrc +#define strndup __strndup_prtsrc +#endif + #ifdef __cplusplus extern "C" { #endif __Z_EXPORT size_t strnlen(const char *, size_t ); __Z_EXPORT char *strpcpy(char *, const char *); +#if !defined(ZOSLIB_TRACE_ALLOCS) __Z_EXPORT char *strndup(const char *s, size_t n); +#endif __Z_EXPORT char *strsignal(int ); __Z_EXPORT const char *sigdescr_np(int); diff --git a/include/zos-base.h b/include/zos-base.h index 25e2f891..ea4fc8fd 100644 --- a/include/zos-base.h +++ b/include/zos-base.h @@ -571,12 +571,6 @@ unsigned long __get_libvec_base(void); */ __Z_EXPORT int __update_envar_names(zoslib_config_t *const config); -/** - * Tell zoslib that the main process is terminating, for its diagnostics. - * - */ -__Z_EXPORT void __mainTerminating(); - /** * Returns the program's directory as an absolute path */ @@ -703,4 +697,39 @@ template __Z_EXPORT std::bitset __zinit* __get_instance(); #endif // __cplusplus +#ifdef ZOSLIB_TRACE_ALLOCS +#ifdef __cplusplus +extern "C" { +#endif +__Z_EXPORT void heapreport(); +__Z_EXPORT void *__zalloc_trace(size_t len, size_t alignment, + const char *pfname, int linenum); +__Z_EXPORT void *__zalloc_for_fd_trace(size_t len, const char *filename, int fd, + off_t offset, + const char *pfname, int linenum); +__Z_EXPORT void *anon_mmap_trace(void *_, size_t len, + const char *pfname, int linenum); +__Z_EXPORT void *roanon_mmap_trace(void *_, size_t len, int prot, int flags, + const char *filename, int fd, off_t offset, + const char *pfname, int linenum); +__Z_EXPORT void __display_alloc_stats(bool bDestroy, bool bDisplayAll); +#ifdef __cplusplus +} +#endif + +#undef __zalloc +#undef __zalloc_for_fd +#undef anon_mmap +#undef roanon_mmap + +#define __zalloc(len,alignment) \ + __zalloc_trace(len,alignment,__FILE__,__LINE__) +#define __zalloc_for_fd(len,filename,fd,offset) \ + __zalloc_for_fd_trace(len,filename,fd,offset,__FILE__,__LINE__) +#define anon_mmap(addr,len) \ + anon_mmap_trace(addr,len,__FILE__,__LINE__) +#define roanon_mmap(addr,len,prot,flags,filename,fd,offset) \ + roanon_mmap_trace(addr,len,prot,flags,filename,fd,offset,__FILE__,__LINE__) + +#endif // ZOSLIB_TRACE_ALLOCS #endif // ZOS_BASE_H_ diff --git a/include/zos-io.h b/include/zos-io.h index 9a858a26..eda641cb 100644 --- a/include/zos-io.h +++ b/include/zos-io.h @@ -148,30 +148,30 @@ __Z_EXPORT int __getfdccsid(int fd); __Z_EXPORT int __setfdccsid(int fd, int t_ccsid); /** - * Returns true if logging of memory allocation and release is specified. + * Returns true if logging of memory [de]allocation is specified. */ __Z_EXPORT bool __doLogMemoryUsage(); /** * Returns the file name, including "stdout" or "stderr", used to log memory - * allocation and release to. + * [de]allocation to. */ __Z_EXPORT char *__getMemoryUsageLogFile(); /** - * Returns true if all messages from memory allocation and release are being + * Returns true if all messages from memory [de]allocation are being * displayed. */ __Z_EXPORT bool __doLogMemoryAll(); /** - * Returns true if only warnings from memory allocation and release are being + * Returns true if only warnings from memory [de]allocation are being * displayed. Errors are always included if memory logging in on. */ __Z_EXPORT bool __doLogMemoryWarning(); /** - * Returns true if memory allocation should be displayed when curval increases + * Returns true if memory allocation should be displayed when curvar increases * by the value set in environment variable __MEMORY_USAGE_LOG_INC since the last * currently allocated size was displayed. */ @@ -184,12 +184,29 @@ __Z_EXPORT bool __doLogMemoryInc(size_t curval, size_t *plastval); __Z_EXPORT int __getLogMemoryFileNo(); /** - * Logs memory allocation and release to the file name specified - * in the environment variable zoslib_config_t.MEMORY_USAGE_LOG_FILE_ENVAR. + * Logs memory [de]allocation to the file name specified in the environment + * variable zoslib_config_t.MEMORY_USAGE_LOG_FILE_ENVAR. * \param [in] same as C's printf() parameters */ __Z_EXPORT void __memprintf(const char *format, ...); +/** + * Logs memory [de]allocation to the file name specified in the environment + * variable zoslib_config_t.MEMORY_USAGE_LOG_FILE_ENVAR, but without the + * process-id or thread-id prefix as __memprintf does. + * \param [in] same as C's printf() parameters + */ +__Z_EXPORT void __memprintfx(const char *format, ...); + +/** + * Returns the file name component of a given path. + * Similar to basename(path), but doesn't modify path, and if path ends with /, + * which is unexpected for a filename path, then it returns a pointer to that /. + * \param [in] path of a file. + * \return the file name component of a given path. + */ +__Z_EXPORT const char *__file_basename(const char *path); + #ifdef __cplusplus } #endif diff --git a/include/zos-sys-info.h b/include/zos-sys-info.h index a54fcd04..f614114c 100644 --- a/include/zos-sys-info.h +++ b/include/zos-sys-info.h @@ -154,6 +154,14 @@ __Z_EXPORT bool __is_vef1_available(); */ __Z_EXPORT char *__get_cpu_model(char *buffer, size_t size); +/** + * Returns the current date and time as yyyy-mm-dd hh:mm:ss, so ts should + * be minimum char ts[20]. + * \param ts pointer to the buffer where the timestemp is to be stored + * \return pointer to the buffer, or NULL if `sprintf()` failed. + */ +__Z_EXPORT char *__get_timestamp(char *ts); + __Z_EXPORT int getloadavg(double loadavg[], int nelem); #ifdef __cplusplus diff --git a/include/zos-tsearch.h b/include/zos-tsearch.h new file mode 100644 index 00000000..8cd933c3 --- /dev/null +++ b/include/zos-tsearch.h @@ -0,0 +1,70 @@ +/////////////////////////////////////////////////////////////////////////////// +// Licensed Materials - Property of IBM +// ZOSLIB +// (C) Copyright IBM Corp. 2024. All Rights Reserved. +// US Government Users Restricted Rights - Use, duplication +// or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef ZOS_TSEARCH_H_ +#define ZOS_TSEARCH_H_ + +#if defined(ZOSLIB_TRACE_ALLOCS) + +#include + +typedef enum { + __MEMSPACE_64 = 1, + __MEMSPACE_64V, + __MEMSPACE_31 +} __MEMSPACE; + +typedef struct { + const void *addr; + size_t nbytes; + char *psrc; + size_t callnum; // the i'th time an alloc from location psrc was made + __MEMSPACE memspace; +} __taddr_t; + +typedef struct { + char *psrc; + size_t curbytes; // # of bytes currently still allocated from this location + size_t maxbytes; // max # of bytes allocated from this location + size_t nallocs; // # of times an alloc has been called from this location + size_t nfrees; // # of times an alloc from this location has been freed + size_t lastrpt_curbytes; // last curbytes value displayed for stats + __MEMSPACE memspace; +} __tsrc_t; + +#if defined(__cplusplus) +extern "C" { +#endif + +const char *__bt_memspace_str(__MEMSPACE memspace); + +// __bt_addr_* manipulate the binary search tree with memory address as key +void __bt_addr_add(void **pproot_addr, void **pproot_src, __taddr_t **ppnode, + __MEMSPACE memspace, + const void *addr, size_t nbytes, + const char *pfname, int linenum); +void __bt_addr_delete(void **pproot_addr, void **pproot_src, + const void *addr); +int __bt_addr_compare(const void *node1, const void *node2); +void __bt_addr_free_node( __taddr_t *pn); +__taddr_t *__bt_addr_find(void **pproot, const void *addr); + +// __bt_src_* manipulate the binary search tree with alloc source:linenum as key +void __bt_src_add(void **pproot, __taddr_t *paddr_node); +int __bt_src_compare(const void *node1, const void *node2); +void __bt_src_free_node( __tsrc_t *pn); +void __bt_src_dec(void **pproot_src, const __taddr_t *paddr_node); +__tsrc_t *__bt_src_find(void **pproot, char *psrc); + + +#if defined(__cplusplus) +} +#endif + +#endif +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 984d13cc..bee9cc90 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -7,6 +7,7 @@ ############################################################################### set(libsrc + zos-allocs-trace.cc zos-bpx.cc zos-char-util.cc zos-getentropy.cc @@ -16,10 +17,12 @@ set(libsrc zos-string.c zos-sys-info.cc zos-tls.cc + zos-tsearch.c zos.cc zos-mount.c zos-mkdtemp.c ) + set(zoslib-help zoslib-help.cc) set(CELQUOPT_OBJECT "${CMAKE_CURRENT_BINARY_DIR}/celquopt.s.o") diff --git a/src/zos-allocs-trace.cc b/src/zos-allocs-trace.cc new file mode 100644 index 00000000..34c4ae8a --- /dev/null +++ b/src/zos-allocs-trace.cc @@ -0,0 +1,808 @@ +/////////////////////////////////////////////////////////////////////////////// +// Licensed Materials - Property of IBM +// ZOSLIB +// (C) Copyright IBM Corp. 2024. All Rights Reserved. +// US Government Users Restricted Rights - Use, duplication +// or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +/////////////////////////////////////////////////////////////////////////////// + +#define _AE_BIMODAL 1 +#include "zos-base.h" +#include "zos-io.h" +#include "zos-tsearch.h" + +#include + +#include +#include +#include +#include +#include +#include + +extern "C" bool __isZoslibInitialized(); + +extern "C" void heapreport() { + // Called first from __zinit::__zinit (after envars for memory trace are + // processed), and again after the report has been generated by + // __display_alloc_stats(). + static long start__uheap_bytes_alloc = -1; + hreport_t r; + if (__heaprpt(&r) != 0) { + int sverrno = errno; + perror("__heaprpt"); + __memprintfx("ERROR: __heaprpt() failed errno=%d\n", sverrno); + return; + } + char ts[20] = "(error)"; // yyyy-mm-dd hh:mm:ss + __get_timestamp(ts); + if (start__uheap_bytes_alloc < 0) { + __memprintfx("__heaprpt() at %s:\n" \ + " Total amount of user heap storage : %ld\n" \ + " Amount of user heap storage in use : %ld\n" \ + " Amount of available user heap storage: %ld\n\n", ts, + r.__uheap_size, r.__uheap_bytes_alloc, r.__uheap_bytes_free); + } else { + char s[64] = ""; + if (r.__uheap_bytes_alloc > start__uheap_bytes_alloc) + snprintf(s, sizeof(s), "(+%zu vs start %zu)", r.__uheap_bytes_alloc - + start__uheap_bytes_alloc, start__uheap_bytes_alloc); + else if (r.__uheap_bytes_alloc < start__uheap_bytes_alloc) + snprintf(s, sizeof(s), "(%ld vs start %zu)", r.__uheap_bytes_alloc - + start__uheap_bytes_alloc, start__uheap_bytes_alloc); + + __memprintfx("__heaprpt() at %s:\n" \ + " Total amount of user heap storage : %ld\n" \ + " Amount of user heap storage in use : %ld%s\n" \ + " Amount of available user heap storage: %ld\n\n", ts, + r.__uheap_size, r.__uheap_bytes_alloc, s, + r.__uheap_bytes_free); + } + if (start__uheap_bytes_alloc < 0) + start__uheap_bytes_alloc = r.__uheap_bytes_alloc; +} + + +// For use by IARV64 allocation/free: +namespace { +char gxttoken[16] = ""; +unsigned short asid; +bool oktouse; + +void getxttoken(char *out) { + void *p; + asm volatile(" l %0,1208 \n" + " llgtr %0,%0 \n" + " lg %0,304(%0)\n" + : "+r"(p) + : + : "r0"); + memcpy(out, (char *)p + 0x14, 16); +} + +__attribute__((constructor)) void init_iarv64() { + getxttoken(gxttoken); + asid = ((unsigned short *)(*(char *__ptr32 *)(0x224)))[18]; + + // LE level is 220 or above + oktouse = + (*(int *)(80 + ((char ****__ptr32 *)1208)[0][11][1][123]) >= 0x04020200); + assert(oktouse); +} +} // namespace + +#if defined(ZOSLIB_TRACE_ALLOCS) +namespace { +/* + * It's possible that the overridden allocation functions here get called + * before the constructors init_* are invoked (e.g. during init of static + * objects), hence the check for access_lock before it's used. +*/ +pthread_mutex_t access_lock = {0}; + +// Display tracaback for warnings if envar is set: +bool gDoWarningTB = false; + +// Determines if only changed src nodes should be displayed (display_src); it's +// global because it's used by display_src callback funciton. +bool gbDisplayAllAllocStats = true; + +bool doTraceback(const char *pfname_linenum) { + // for debugging + static bool binit = false; + static char srclinebuf[PATH_MAX] = ""; + static unsigned long fromi = 0u, toj = 0u; + static unsigned long curi = 0u; + if (!binit) { + const char *penv = getenv("__MEMORY_USAGE_ALLOC_TB_SOURCE"); + if (penv && *penv) + strncpy(srclinebuf, penv, sizeof(srclinebuf)); + + penv = getenv("__MEMORY_USAGE_ALLOC_TB_SOURCE_I"); + if (penv && *penv) { + sscanf(penv, "%ld", &fromi); + penv = getenv("__MEMORY_USAGE_ALLOC_TB_SOURCE_J"); + if (penv && *penv) + sscanf(penv, "%ld", &toj); + if (fromi > toj) + toj = fromi; + } + binit = true; + } + if (!*srclinebuf) + return false; + if (strcmp(srclinebuf, pfname_linenum) != 0) + return false; + if (fromi == 0u && toj == 0u) + return true; + else if (fromi > 0u && toj >= fromi) { + if (++curi >= fromi && curi <= toj) + return true; + } else if (fromi > 0u && ++curi >= fromi) + return true; + + return false; +} + +extern "C" size_t __get_btree_bytes_current(); +extern "C" size_t __get_btree_bytes_max(); +extern "C" int __get_btree_max_src_namelen(); + +// These are updated by display_node() comparator when display_debris() runs. +size_t gUnfreed64, gCountAllocs64, gCountFrees64; +size_t gUnfreed64v, gCountAllocs64v, gCountFrees64v; +size_t gUnfreed31, gCountAllocs31, gCountFrees31; + + +void display_node(const void *ptr, VISIT order, int level) { + const __taddr_t *p = *(const __taddr_t**) ptr; + + if (order == postorder || order == leaf) { + __memprintfx("DEBRIS from %s-%d addr=%lX size=%lu\n", + p->psrc, p->callnum, p->addr, p->nbytes); + } +} + +void display_src(const void *ptr, VISIT order, int level) { + __tsrc_t *p = *(__tsrc_t**) ptr; + static int max_width = __get_btree_max_src_namelen(); + + if (order != postorder && order != leaf) + return; + + if (p->memspace == __MEMSPACE_64) { + gUnfreed64 += p->curbytes; + gCountAllocs64 += p->nallocs; + gCountFrees64 += p->nfrees; + } else if (p->memspace == __MEMSPACE_64V) { + gUnfreed64v += p->curbytes; + gCountAllocs64v += p->nallocs; + gCountFrees64v += p->nfrees; + } else if (p->memspace == __MEMSPACE_31) { + gUnfreed31 += p->curbytes; + gCountAllocs31 += p->nallocs; + gCountFrees31 += p->nfrees; + } else + assert(0); + + if (gbDisplayAllAllocStats) { + __memprintfx("%s %*s: unfreed=%zu max-allocated=%zu "\ + "count-allocations=%zu count-frees=%zu count-diff=%zu\n", + __bt_memspace_str(p->memspace), max_width, p->psrc, + p->curbytes, p->maxbytes, p->nallocs, + p->nfrees, p->nallocs - p->nfrees); + p->lastrpt_curbytes = p->curbytes; + } else if (p->lastrpt_curbytes != p->curbytes) { + char s[64] = ""; + if (p->curbytes > p->lastrpt_curbytes) + snprintf(s, sizeof(s), "(+%zu)", p->curbytes - p->lastrpt_curbytes); + else if (p->curbytes < p->lastrpt_curbytes) + snprintf(s, sizeof(s), "(%ld)", (signed long)p->curbytes - \ + (signed long)p->lastrpt_curbytes); + + __memprintfx("%s %*s: unfreed=%zu%s max-allocated=%zu " \ + "count-allocations=%zu count-frees=%zu " \ + "count-diff=%zu\n", + __bt_memspace_str(p->memspace), max_width, p->psrc, + p->curbytes, s, p->maxbytes, p->nallocs, p->nfrees, + p->nallocs - p->nfrees); + + p->lastrpt_curbytes = p->curbytes; + } +} + +int delete_root(const void *node1, const void *node2) { + // Used when explicitly deleting a root node, so no comparison is needed. + return 0; +} + +size_t curheap64 = 0u; // 64-bit heap malloc, calloc, etc. +size_t maxheap64 = 0u; // also includes allocations from C++ new +size_t lastvaldisp64 = 0u; // last curheap64 value that was displayed +size_t lastrptheap64 = 0u; // last curheap64 value displayed for stats + +size_t curmem31 = 0u; // memory below the bar +size_t maxmem31 = 0u; +size_t lastvaldisp31 = 0u; +size_t lastrptmem31 = 0u; + +size_t curmem64v = 0u; // 64-bit virtual storage (IARV64) +size_t maxmem64v = 0u; +size_t lastvaldisp64v = 0u; +size_t lastrptmem64v = 0u; + +void *proot_addr = NULL; // btree root of allocated addresses +void *proot_src = NULL; // btree root of allocations source (filename:linenum) + + +void addptr(__taddr_t **ppn, const __MEMSPACE memspace, const void *ptr, + size_t v, const char *pfname, int linenum, bool *pisCached = NULL) { + if (access_lock.__m > 0 && pthread_mutex_lock(&access_lock) != 0) { + perror("pthread_mutex_lock"); + abort(); + } + if (pisCached) + *pisCached = false; + __taddr_t *pn = __bt_addr_find(&proot_addr, ptr); + if (pn != NULL) { + assert(pn->addr == ptr); + if (pisCached) + *pisCached = true; + else if (__doLogMemoryWarning()) { + __memprintf("WARN addr=%p size=%zu memspace=%d already in cache, " \ + "new-size=%zu memspace=%d (%s)\n", + ptr, pn->nbytes, pn->memspace, v, memspace, pn->psrc); + if (gDoWarningTB) + __display_backtrace(__getLogMemoryFileNo()); + } + } else { + __bt_addr_add(&proot_addr, &proot_src, &pn, memspace, ptr, v, pfname, + linenum); + if (memspace == __MEMSPACE_64) { + curheap64 += v; + maxheap64 = std::max(maxheap64, curheap64); + } else if (memspace == __MEMSPACE_64V) { + curmem64v += v; + maxmem64v = std::max(maxmem64v, curmem64v); + } else if (memspace == __MEMSPACE_31) { + curmem31 += v; + maxmem31 = std::max(maxmem31, curmem31); + } else { + assert(0); + } + } + assert (pn != NULL); + if (access_lock.__m > 0 && pthread_mutex_unlock(&access_lock) != 0) { + perror("pthread_mutex_unlock"); + abort(); + } + *ppn = pn; +} + +size_t freeptr(const void *ptr) { + if (access_lock.__m > 0 && pthread_mutex_lock(&access_lock) != 0) { + perror("pthread_mutex_lock"); + abort(); + } + __taddr_t *pn = __bt_addr_find(&proot_addr, ptr); + size_t size = 0u; + if (pn != NULL) { + size = pn->nbytes; + if (pn->memspace == __MEMSPACE_64) { + curheap64 -= size; + } else if (pn->memspace == __MEMSPACE_64V) { + curmem64v -= size; + } else if (pn->memspace == __MEMSPACE_31) { + curmem31 -= size; + } else { + assert(0); + } + __bt_addr_delete(&proot_addr, &proot_src, ptr); + } else if (__doLogMemoryWarning()) { + __memprintf("WARN free addr=%p not in cache, return size 0\n", ptr); + if (gDoWarningTB) + __display_backtrace(__getLogMemoryFileNo()); + } + if (access_lock.__m > 0 && pthread_mutex_unlock(&access_lock) != 0) { + perror("pthread_mutex_unlock"); + abort(); + } + return size; +} + +size_t getsize(const void *ptr, const char *pfname, int linenum) { + if (access_lock.__m > 0 && pthread_mutex_lock(&access_lock) != 0) { + perror("pthread_mutex_lock"); + abort(); + } + __taddr_t *pn = __bt_addr_find(&proot_addr, ptr); + size_t size = 0u; + if (pn != NULL) + size = pn->nbytes; + if (access_lock.__m > 0 && pthread_mutex_unlock(&access_lock) != 0) { + perror("pthread_mutex_unlock"); + abort(); + } + if (pn != NULL) + return size; + if (__doLogMemoryWarning()) { + __memprintf("WARN getsize addr=%p not in cache, returning size 0 (%s:%d)\n", + ptr, __file_basename(pfname), linenum); + if (gDoWarningTB) + __display_backtrace(__getLogMemoryFileNo()); + } + return 0u; +} + +void display_stats() { + if (!__doLogMemoryUsage()) + return; + + if (access_lock.__m > 0 && pthread_mutex_lock(&access_lock) != 0) { + perror("pthread_mutex_lock"); + abort(); + } + + char ts[20] = "(error)"; // yyyy-mm-dd hh:mm:ss + __get_timestamp(ts); + if (!gbDisplayAllAllocStats) { + __memprintfx("\nMEMORY ALLOCATIONS (only those with an updated 'unfreed')" \ + " at %s:\n", ts); + } else { + __memprintfx("\nMEMORY ALLOCATIONS at %s:\n", ts); + } + + if (proot_addr && __doLogMemoryWarning()) { + // display all unfreed pointers + twalk(proot_addr, display_node); + } + + if (!proot_src) + return; + + // These global variables are updated by display_src: + gUnfreed64 = gCountAllocs64 = gCountFrees64 = 0u; + gUnfreed64v = gCountAllocs64v = gCountFrees64v = 0u; + gUnfreed31 = gCountAllocs31 = gCountFrees31 = 0u; + + twalk(proot_src, display_src); + + char w[64] = ""; + char s64[64] = ""; + char s64v[64] = ""; + char s31[64] = ""; + + if (gUnfreed64 != curheap64) + sprintf(w, " (WARN curheap64=%zu)", curheap64); + if (gbDisplayAllAllocStats) { + __memprintfx("\nTOTAL 64-heap: unfreed=%zu%s " \ + "max-allocated=%zu count-allocations=%zu count-frees=%zu " \ + "count-diff=%zu\n", gUnfreed64, w, maxheap64, + gCountAllocs64, gCountFrees64, + gCountAllocs64 - gCountFrees64); + } else { + if (curheap64 > lastrptheap64) + snprintf(s64, sizeof(s64), "(+%zu)", curheap64 - lastrptheap64); + else if (curheap64 < lastrptheap64) + snprintf(s64, sizeof(s64), "(%ld)", (signed long)curheap64 - \ + (signed long)lastrptheap64); + + __memprintfx("\nTOTAL 64-heap: unfreed=%zu%s%s " \ + "max-allocated=%zu count-allocations=%zu count-frees=%zu " \ + "count-diff=%zu\n", gUnfreed64, s64, w, maxheap64, + gCountAllocs64, gCountFrees64, + gCountAllocs64 - gCountFrees64); + } + + *w = 0; + if (gUnfreed64v != curmem64v) + sprintf(w, " (WARN curheap64v=%zu)", curmem64v); + if (gbDisplayAllAllocStats) { + __memprintfx("TOTAL 64-vs: unfreed=%zu%s " \ + "max-allocated=%zu count-allocations=%zu count-frees=%zu " \ + "count-diff=%zu\n", gUnfreed64v, w, maxmem64v, + gCountAllocs64v, gCountFrees64v, + gCountAllocs64v - gCountFrees64v); + } else { + if (curmem64v > lastrptmem64v) + snprintf(s64v, sizeof(s64v), "(+%zu)", curmem64v - lastrptmem64v); + else if (curmem64v < lastrptmem64v) + snprintf(s64v, sizeof(s64v), "(%ld)", (signed long)curmem64v - \ + (signed long)lastrptmem64v); + + __memprintfx("TOTAL 64-vs: unfreed=%zu%s%s " \ + "max-allocated=%zu count-allocations=%zu count-frees=%zu " \ + "count-diff=%zu\n", gUnfreed64v, s64v, w, maxmem64v, + gCountAllocs64v, gCountFrees64v, + gCountAllocs64v - gCountFrees64v); + } + + *w = 0; + if (gUnfreed31 != curmem31) + sprintf(w, " (WARN curmem31=%zu)", curmem31); + if (gbDisplayAllAllocStats) { + __memprintfx("TOTAL 31-bit: unfreed=%zu%s " \ + "max-allocated=%zu count-allocations=%zu count-frees=%zu " \ + "count-diff=%zu\n", gUnfreed31, w, maxmem31, + gCountAllocs31, gCountFrees31, + gCountAllocs31 - gCountFrees31); + } else { + if (curmem31 > lastrptmem31) + snprintf(s31, sizeof(s31), "(+%zu)", curmem31 - lastrptmem31); + else if (curmem31 < lastrptmem31) + snprintf(s31, sizeof(s31), "(%ld)", (signed long)curmem31 - \ + (signed long)lastrptmem31); + + __memprintfx("TOTAL 31-bit: unfreed=%zu%s%s " \ + "max-allocated=%zu count-allocations=%zu count-frees=%zu " \ + "count-diff=%zu\n", gUnfreed31, s31, w, maxmem31, + gCountAllocs31, gCountFrees31, + gCountAllocs31 - gCountFrees31); + } + + __memprintfx("\n"); + // summary: + size_t h64 = curheap64; + if (gbDisplayAllAllocStats) { + __memprintfx("SUMMARY: unfreed64=%zu, max64=%zu, " \ + "unfreed64v=%zu, max64v=%zu, unfreed31=%zu, max31=%zu\n", + curheap64, maxheap64, curmem64v, maxmem64v, curmem31, maxmem31); + } else { + __memprintfx("SUMMARY: unfreed64=%zu%s, max64=%zu, " \ + "unfreed64v=%zu%s, max64v=%zu, unfreed31=%zu%s, max31=%zu\n", + curheap64, s64, maxheap64, curmem64v, s64v, maxmem64v, + curmem31, s31, maxmem31); + } + lastrptheap64 = curheap64; + lastrptmem64v = curmem64v; + lastrptmem31 = curmem31; + + if (access_lock.__m > 0 && pthread_mutex_unlock(&access_lock) != 0) { + perror("pthread_mutex_unlock"); + abort(); + } +} + +void destroy_nodes() { + // delete all addr tree nodes: + __taddr_t *paddr_node; + while (proot_addr != NULL) { + paddr_node = *(__taddr_t**)proot_addr; + tdelete((void *)paddr_node, &proot_addr, delete_root); + __bt_addr_free_node(paddr_node); + } + + // delete all src tree nodes: + __tsrc_t *psrc_node; + while (proot_src != NULL) { + psrc_node = *(__tsrc_t**)proot_src; + tdelete((void *)psrc_node, &proot_src, delete_root); + __bt_src_free_node(psrc_node); + } + + size_t btree_cur = __get_btree_bytes_current(); + size_t btree_max = __get_btree_bytes_max(); + const char *btleak = (btree_cur != 0) ? "(LEAK)" : ""; + __memprintfx("BTREE%s unfreed64=%zu, max64=%zu\n", btleak, btree_cur, + btree_max); +} +} // namespace + + +void *__malloc_trace(size_t size, const char *pfname, int linenum) { + if (size == 0u) + size = 1u; + void *p = __malloc_orig(size); + int sverrno = errno; + if (__isZoslibInitialized() && !__doLogMemoryUsage()) + return p; + + if (p == NULL) { + __memprintf("ERROR malloc failed, errno=%d size=%zu " \ + "(heap=%zu max=%zu) (%s:%d)\n", sverrno, size, + curheap64, maxheap64, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + } else { + __taddr_t *pn; + bool isCached = false; + addptr(&pn, __MEMSPACE_64, p, size, pfname, linenum, &isCached); + if (isCached) { + __free_trace(p); + p = __malloc_orig(size); + if (p == NULL) { + __memprintf("ERROR malloc failed, errno=%d size=%zu " \ + "(heap=%zu max=%zu) (%s:%d)\n", sverrno, size, + curheap64, maxheap64, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + return NULL; + } + } + if (__doLogMemoryAll() || __doLogMemoryInc(curheap64, &lastvaldisp64)) { + __memprintf("addr=%p size=%zu malloc OK (heap=%zu max=%zu) (%s-%d)\n", + p, size, curheap64, maxheap64, pn->psrc, pn->callnum); + } + if (doTraceback(pn->psrc)) { + __display_backtrace(__getLogMemoryFileNo()); + } + } + return p; +} + +void *__calloc_trace(size_t num, size_t size, const char *pfname, int linenum) { + void *p = __calloc_orig(num, size); + int sverrno = errno; + if (__isZoslibInitialized() && !__doLogMemoryUsage()) + return p; + + if (p == NULL) { + __memprintf("ERROR calloc failed, errno=%d nitems=%zu size=%zu total=%zu " \ + "(heap=%zu max=%zu) (%s:%d)\n", sverrno, num, size, num*size, + curheap64, maxheap64, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + } else { + __taddr_t *pn; + bool isCached = false; + addptr(&pn, __MEMSPACE_64, p, num*size, pfname, linenum, &isCached); + if (isCached) { + __free_trace(p); + p = __calloc_orig(num, size); + if (p == NULL) { + __memprintf("ERROR calloc failed, errno=%d nitems=%zu size=%zu " \ + "total=%zu (heap=%zu max=%zu) (%s:%d)\n", sverrno, num, + size, num*size, curheap64, maxheap64, + __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + return NULL; + } + } + if (__doLogMemoryAll() || __doLogMemoryInc(curheap64, &lastvaldisp64)) { + __memprintf("addr=%p nitems=%zu size=%zu total=%zu calloc OK " \ + "(heap=%zu max=%zu) (%s-%d)\n", p, num, size, + num*size, curheap64, maxheap64, pn->psrc, pn->callnum); + } + if (doTraceback(pn->psrc)) { + __display_backtrace(__getLogMemoryFileNo()); + } + } + return p; +} + +char *__strdup_trace(const char *ptr, const char *pfname, int linenum) { + char *p = __strdup_orig(ptr); + int sverrno = errno; + if (__isZoslibInitialized() && !__doLogMemoryUsage()) + return p; + + size_t size = strlen(ptr); + if (p == NULL) { + __memprintf("ERROR strdup failed errno=%d size=%zu\n", + "(heap=%zu max=%zu) (%s:%d)\n", sverrno, size, + curheap64, maxheap64, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + } else { + __taddr_t *pn; + bool isCached = false; + addptr(&pn, __MEMSPACE_64, p, size, pfname, linenum, &isCached); + if (isCached) { + __free_trace(p); + p = __strdup_orig(ptr); + if (p == NULL) { + __memprintf("ERROR strdup failed errno=%d size=%zu\n", + "(heap=%zu max=%zu) (%s:%d)\n", sverrno, size, + curheap64, maxheap64, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + return NULL; + } + } + if (__doLogMemoryAll() || __doLogMemoryInc(curheap64, &lastvaldisp64)) { + __memprintf("addr=%p size=%zu strdup OK (heap=%zu max=%zu) (%s-%d)\n", + p, size, curheap64, maxheap64, pn->psrc, pn->callnum); + } + if (doTraceback(pn->psrc)) { + __display_backtrace(__getLogMemoryFileNo()); + } + } + return p; +} + +char *__strndup_trace(const char *s, size_t n, + const char *pfname, int linenum) { + size_t len = strnlen(s, n); + char *dupStr = static_cast(__malloc_trace(len + 1, pfname, linenum)); + if (dupStr == NULL) + return NULL; + dupStr[len] = '\0'; + return static_cast(memcpy(dupStr, s, len)); +} + +void __free_trace(void *ptr) { + if (ptr == NULL) + return; + if (__isZoslibInitialized() && !__doLogMemoryUsage()) { + __free_orig(ptr); + return; + } + size_t size = freeptr(ptr); + __free_orig(ptr); + if (__doLogMemoryAll()) { + __memprintf("addr=%p size=%zu free OK (heap=%zu max=%zu)\n", + ptr, size, curheap64, maxheap64); + } +} + +void *__realloc_trace(void *ptr, size_t new_size, const char *pfname, int linenum) { + if (__isZoslibInitialized() && !__doLogMemoryUsage()) + return __realloc_orig(ptr, new_size); + + // If size is 0 and ptr is not NULL, the storage pointed to by ptr is freed + // and NULL is returned. + if (ptr == NULL && new_size == 0u) { + if (__doLogMemoryWarning()) { + __memprintf("WARN realloc called with ptr=0 new-size=0 " \ + "(heap=%zu max=%zu) (%s:%d)\n", + curheap64, maxheap64, __file_basename(pfname), linenum); + if (gDoWarningTB) + __display_backtrace(__getLogMemoryFileNo()); + } + return NULL; + } + void *newptr = NULL; + if (new_size == 0u) { + __free_trace(ptr); + } else if (ptr != NULL) { + newptr = __malloc_trace(new_size, pfname, linenum); + int sverrno = errno; + if (newptr != NULL) { + size_t old_size = getsize(ptr, pfname, linenum); + memcpy(newptr, ptr, std::min(new_size, old_size)); + __free_trace(ptr); + } else { + __free_trace(ptr); + size_t old_size = getsize(ptr, pfname, linenum); + __memprintf("ERROR realloc failed errno=%d ptr=%p, size=%zu " \ + "new-size=%zu (heap=%zu max=%zu) (%s:%d)\n", + sverrno, ptr, old_size, new_size, + curheap64, maxheap64, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + } + } else { + newptr = __malloc_trace(new_size, pfname, linenum); + } + return newptr; +} + +void *__reallocf_trace(void *ptr, size_t new_size, + const char *pfname, int linenum) { + void *newptr = __realloc_trace(ptr, new_size, pfname, linenum); + if (newptr == NULL && new_size > 0) + __free_trace(ptr); + return newptr; +} + +void *__malloc31_trace(size_t size, const char *pfname, int linenum) { + void *p = __malloc31_orig(size); + int sverrno = errno; + if (__isZoslibInitialized() && !__doLogMemoryUsage()) + return p; + + if (p == NULL) { + __memprintf("ERROR malloc31 failed, errno=%d size=%zu " \ + "(m31=%zu max=%zu) (%s:%d)\n", sverrno, size, + curmem31, maxmem31, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + } else { + __taddr_t *pn; + addptr(&pn, __MEMSPACE_31, p, size, pfname, linenum); + if (__doLogMemoryAll() || __doLogMemoryInc(curmem31, &lastvaldisp31)) { + __memprintf("addr=%p size=%zu malloc31 OK (m31=%zu max=%zu) (%s-%d)\n", + p, size, curmem31, maxmem31, pn->psrc, pn->callnum); + } + if (doTraceback(pn->psrc)) { + __display_backtrace(__getLogMemoryFileNo()); + } + } + return p; +} + +extern "C" void __display_alloc_stats(bool bDestroy, bool bDisplayAll) { + // This is called from destruct() or can be called by the app (e.g. on every + // SIGUSR2, in which case bDisplayAll can be passed as false to display only + // changes in unfreed memory). + if (bDisplayAll) + gbDisplayAllAllocStats = true; + display_stats(); +#if defined(ZOSLIB_TRACE_ALLOCS) + if (bDestroy) + destroy_nodes(); +#endif + heapreport(); + if (bDestroy) + pthread_mutex_destroy(&access_lock); + gbDisplayAllAllocStats = false; +} + +__attribute__((constructor)) void init_allocs() { + char *penv = getenv("__MEMORY_USAGE_ALLOC_TB_WARNING"); + gDoWarningTB = (penv && *penv == '1'); + + if (pthread_mutex_init(&access_lock, NULL) != 0) { + perror("pthread_mutex_init"); + abort(); + } +} + +__attribute__((destructor)) void destruct() { + // This can only be called if the process terminated gracefully; generate + // allocations report and cleanup. + + __display_alloc_stats(true, true); +} +#endif // !ZOSLIB_TRACE_ALLOCS + +// TODO(gabylb): move all IARV64 and 31-bit allocs from zos-base.h and zos.cc +// to separate files and move these declarations to the new .h: +extern "C" void *__iarv64_alloc(int segs, const char *token, + long long *prc, long long *preason); +extern "C" long long __iarv64_free(void *ptr, const char *token, + long long *preason); + +#ifndef ZOSLIB_TRACE_ALLOCS +extern "C" void *__alloc_seg(size_t segs) { + long long rc, reason; + return __iarv64_alloc(segs, gxttoken, &rc, &reason); +} +#else +extern "C" void *__alloc_seg_trace(size_t segs, + const char *pfname, int linenum) { + long long rc, reason; + void *p = __iarv64_alloc(segs, gxttoken, &rc, &reason); + int sverrno = errno; + if (__isZoslibInitialized() && !__doLogMemoryUsage()) + return p; + size_t size = segs * 1024u * 1024u; + if (p == NULL) { + __memprintf("ERROR __iarv64_alloc failed, rc=%llx reason=%llx " \ + " errno=%d size=%zu " \ + "(64v=%zu max=%zu) (%s:%d)\n", rc, reason, sverrno, size, + curmem64v, maxmem64v, __file_basename(pfname), linenum); + __display_backtrace(__getLogMemoryFileNo()); + } else { + __taddr_t *pn; + addptr(&pn, __MEMSPACE_64V, p, size, pfname, linenum); + if (__doLogMemoryAll() || __doLogMemoryInc(curmem64v, &lastvaldisp64v)) { + __memprintf("addr=%p size=%zu iarv64_alloc OK (v64=%zu max=%zu) (%s-%d)\n", + p, size, curmem64v, maxmem64v, pn->psrc, pn->callnum); + } + if (doTraceback(pn->psrc)) { + __display_backtrace(__getLogMemoryFileNo()); + } + } + return p; +} +#endif + +extern "C" int __free_seg(void *ptr, size_t reqsize) { + if (ptr == NULL) + return 0; + long long rc, reason; +#ifndef ZOSLIB_TRACE_ALLOCS + return __iarv64_free(ptr, gxttoken, &reason); +#else + rc = __iarv64_free(ptr, gxttoken, &reason); + int sverrno = errno; + if (!__doLogMemoryUsage() && __isZoslibInitialized()) + return rc; + + size_t size = freeptr(ptr); + if (rc) { + __memprintf("ERROR __iarv64_free failed, rc=%llx reason=%llx " \ + "errno=%d size=%zu (64v=%zu max=%zu)\n", + rc, reason, sverrno, size, curmem64v, maxmem64v); + __display_backtrace(__getLogMemoryFileNo()); + } else if (__doLogMemoryAll()) { + __memprintf("addr=%p size=%zu iarv4_free OK (heap=%zu max=%zu)\n", + ptr, size, curmem64v, maxmem64v); + } + return rc; +#endif +} diff --git a/src/zos-io.cc b/src/zos-io.cc index 80f55804..809d8010 100644 --- a/src/zos-io.cc +++ b/src/zos-io.cc @@ -789,6 +789,48 @@ void __memprintf(const char *format, ...) { fflush(fp_memprintf); } +void __memprintfx(const char *format, ...) { + // Same as __memprintf but doesn't display process-id and thread-id; + // used by memory tracking functions. + if (!__doLogMemoryUsage()) + return; + + va_list args; + va_start(args, format); + + static const char *fname = __getMemoryUsageLogFile(); + static bool isstderr = !strcmp(fname, "stderr"); + static bool isstdout = !strcmp(fname, "stdout"); + if (!fp_memprintf) { + fp_memprintf = isstderr ? stderr : \ + isstdout ? stdout : \ + fopen(fname, "a+"); + } + if (!fp_memprintf) { + va_end(args); + perror(fname); + __setLogMemoryUsage(false); + return; + } + vfprintf(fp_memprintf, format, args); + va_end(args); + fflush(fp_memprintf); +} + +const char *__file_basename(const char *path) { + // Similar to basename(char*), but doesn't modify the given string. + // The difference is if path ends with /, which is unexpected for a filename, + // then it returns a pointer to that /. + static const char *dot = "."; + static const char *slash = "/"; + if (!path || !*path) return dot; + if (path[0] == '/' && path[1] == '\0') return slash; + const char *p = strrchr(path, '/'); + if (p && p[1] == '\0') return p; + if (p) return p+1; + return path; +} + // C Library Overrides //----------------------------------------------------------------- int __pipe_orig(int [2]) asm("pipe"); diff --git a/src/zos-string.c b/src/zos-string.c index 75f8d517..6b20bc87 100644 --- a/src/zos-string.c +++ b/src/zos-string.c @@ -127,6 +127,8 @@ char *strpcpy(char *dest, const char *src) { return ptr + strlen(ptr); } +#if !defined(ZOSLIB_TRACE_ALLOCS) +// else it's defined in zos-allocs-trace.cc char *strndup(const char *s, size_t n) { size_t len = strnlen(s, n); char *dupStr = malloc(len + 1); @@ -136,6 +138,7 @@ char *strndup(const char *s, size_t n) { } return dupStr; } +#endif #if TEST int main() { diff --git a/src/zos-sys-info.cc b/src/zos-sys-info.cc index 2d9d8968..b7e6ad10 100644 --- a/src/zos-sys-info.cc +++ b/src/zos-sys-info.cc @@ -11,6 +11,7 @@ #include #include #include +#include #include // Byte 6 of CVTOSLVL @@ -136,6 +137,17 @@ char *__get_cpu_model(char *buffer, size_t size) { return buffer; } +char *__get_timestamp(char *ts) { + // returns timestamp as yyyy-mm-dd hh:mm:ss, so char ts[20] + time_t lt = time(NULL); + struct tm *tm = localtime(<); + if (sprintf(ts,"%04d-%02d-%02d %02d:%02d:%02d", tm->tm_year + 1900, + tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec) + < 0) + return NULL; + return ts; +} + #ifdef __cplusplus } #endif diff --git a/src/zos-tsearch.c b/src/zos-tsearch.c new file mode 100644 index 00000000..ab0d9568 --- /dev/null +++ b/src/zos-tsearch.c @@ -0,0 +1,232 @@ +/////////////////////////////////////////////////////////////////////////////// +// Licensed Materials - Property of IBM +// ZOSLIB +// (C) Copyright IBM Corp. 2024. All Rights Reserved. +// US Government Users Restricted Rights - Use, duplication +// or disclosure restricted by GSA ADP Schedule Contract with IBM Corp. +/////////////////////////////////////////////////////////////////////////////// + +#ifdef ZOSLIB_TRACE_ALLOCS +#include "zos-tsearch.h" +#include "zos-base.h" + +#include +#include +#include +#include +#include +#include + +#define MAX(a, b) ((a) > (b) ? (a) : (b)) + +// These are used to track the allocated memory for the btrees. +static size_t gBytesCurrent= 0u; +static size_t gBytesMaxAllocated = 0u; +// Used when writing the stats report: +static int gMaxSrcNameLen = 0; + +size_t __get_btree_bytes_current() { return gBytesCurrent; } +size_t __get_btree_bytes_max() { return gBytesMaxAllocated; } +int __get_btree_max_src_namelen() { return gMaxSrcNameLen; } + + +int get_num_len(int linenum) { + int x = 0; + if (linenum < 0) { + linenum = abs(linenum); + x = 1; + } + int len = 1; + while(linenum > 9) { + ++len; + linenum /= 10; + } + return len + x; +} + +const char *__bt_memspace_str(__MEMSPACE memspace) { + switch(memspace) { + case __MEMSPACE_64: { + static const char s[] = "64"; + return s; + } case __MEMSPACE_64V: { + static const char s[] = "VS"; + return s; + } case __MEMSPACE_31: { + static const char s[] = "31"; + return s; + } default: { + static const char s[] = "UNKNOWN-ADDRESS-SPACE"; + return s; + } + } +} + +//------------------------------------------------------------------------------ +// __bt_addr_* store each memory address allocated as a node in a btree. +// +void __bt_addr_free_node(__taddr_t *pn) { + if (!pn) + return; + if (pn->psrc) { + gBytesCurrent -= (strlen(pn->psrc) + 1); + __free_orig(pn->psrc); + } + gBytesCurrent -= sizeof(*pn); + __free_orig(pn); +} + +int __bt_addr_compare(const void *node1, const void *node2) { + const void *addr1 = ((const __taddr_t*)node1)->addr; + const void *addr2 = ((const __taddr_t*)node2)->addr; + if (addr1 < addr2) return -1; + if (addr1 == addr2) return 0; + if (addr1 > addr2) return 1; + assert(0); + return 0; +} + +void __bt_addr_add(void **pproot_addr, void **pproot_src, __taddr_t **ppnode, + __MEMSPACE memspace, const void *addr, size_t nbytes, + const char *pfname, int linenum) { + int linenum_len = get_num_len(linenum); + if (linenum_len <= 0) { + linenum = 0; + linenum_len = 1; + } + __taddr_t *pn = (__taddr_t*)__malloc_orig(sizeof(__taddr_t)); + gBytesCurrent += sizeof(__taddr_t); + pn->addr = addr; + pn->nbytes = nbytes; + pn->memspace = memspace; + const char *bn = __file_basename(pfname); + int len = strlen(bn) + linenum_len + 1; + gMaxSrcNameLen = MAX(gMaxSrcNameLen, len); + pn->psrc = (char*)__malloc_orig(len + 1); + pn->callnum = 1; + gBytesCurrent += len + 1; + gBytesMaxAllocated = MAX(gBytesMaxAllocated, gBytesCurrent); + snprintf(pn->psrc, len + 1, "%s:%d", bn, linenum); + + if (ppnode != NULL) + *ppnode = NULL; + void *node = tsearch((void*)pn, pproot_addr, __bt_addr_compare); + if (node == NULL) { + __memprintf("ERROR in __bt_addr_add:%d tsearch failed: errno=%d\n", + __LINE__, errno); + abort(); + } else if (*(__taddr_t**)node != pn) { + __memprintf("ERROR in __bt_addr_add:%d addr=%p already exists\n", + __LINE__, addr); + abort(); + } else { + __bt_src_add(pproot_src, pn); + if (ppnode != NULL) + *ppnode = pn; + } +} + +void __bt_addr_delete(void **pproot_addr, void **pproot_src, const void *addr) { + if (pproot_addr == NULL || *pproot_addr == NULL) { + __memprintf("ERROR in __bt_addr_delete:%d addr=%p: pproot_addr=%p is NULL\n", + __LINE__, addr, pproot_addr); + abort(); + } + __taddr_t *paddr_node = __bt_addr_find(pproot_addr, addr); + if (paddr_node == NULL) { + __memprintf("ERROR in __bt_addr_delete:%d pproot_addr=%p addr=%p not found\n", + __LINE__, pproot_addr, addr); + abort(); + } + __bt_src_dec(pproot_src, paddr_node); + void *pret = tdelete(paddr_node, pproot_addr, __bt_addr_compare); + if (pret == NULL) { + __memprintf("ERROR in __bt_addr_delete:%d: addr=%p not found.\n", __LINE__, addr); + abort(); + } + __bt_addr_free_node(paddr_node); +} + +__taddr_t *__bt_addr_find(void **pproot, const void *addr) { + __taddr_t node; + node.addr = addr; + void *pnode = tfind((const void*)&node, pproot, __bt_addr_compare); + if (pnode == NULL) + return NULL; + return *(__taddr_t**)pnode; +} + +//------------------------------------------------------------------------------ +// __bt_src_* store the source file locations (base filename:line-num) where each +// memory allocation call was made, for the stats report. + +void __bt_src_free_node(__tsrc_t *pn) { + if (!pn) + return; + if (pn->psrc) { + gBytesCurrent -= (strlen(pn->psrc) + 1); + __free_orig(pn->psrc); + } + gBytesCurrent -= sizeof(*pn); + __free_orig(pn); +} + +int __bt_src_compare(const void *node1, const void *node2) { + const char *psrc1 = ((const __tsrc_t*)node1)->psrc; + const char *psrc2 = ((const __tsrc_t*)node2)->psrc; + return strcmp(psrc1, psrc2); +} + +void __bt_src_add(void **pproot, __taddr_t *paddr_node) { + __tsrc_t *pn = (__tsrc_t*)__malloc_orig(sizeof(__tsrc_t)); + gBytesCurrent += sizeof(__tsrc_t); + pn->psrc = __strdup_orig(paddr_node->psrc); + gBytesCurrent += strlen(pn->psrc) + 1; + gBytesMaxAllocated = MAX(gBytesMaxAllocated, gBytesCurrent); + pn->curbytes = paddr_node->nbytes; + pn->maxbytes = paddr_node->nbytes; + pn->memspace = paddr_node->memspace; + pn->lastrpt_curbytes = 0u; + pn->nallocs = 1u; + pn->nfrees = 0u; + + void *node = tsearch((void*)pn, pproot, __bt_src_compare); + if (node == NULL) { + __memprintf("ERROR in __bt_src_add:%d tsearch failed: errno=%d\n", + __LINE__, errno); + abort(); + } else if (*(__tsrc_t**)node != pn) { + // a node containing this source location already exists, update it. + __bt_src_free_node(pn); + pn = *(__tsrc_t**)node; + assert(pn->memspace == paddr_node->memspace); + pn->curbytes += paddr_node->nbytes; + pn->maxbytes = MAX(pn->maxbytes, pn->curbytes); + pn->nallocs++; + paddr_node->callnum = pn->nallocs; + } +} + +void __bt_src_dec(void **pproot_src, const __taddr_t *paddr_node) { + // Called from __bt_addr_delete after an address is freed, to update + // the alloc info of the node in the src tree from which the allocation + // call was made. + __tsrc_t *pnode = __bt_src_find(pproot_src, paddr_node->psrc); + if (pnode == NULL) { + __memprintf("ERROR in __bt_src_dec:%d: src=%s not found.\n", + __LINE__, paddr_node->psrc); + return; + } + pnode->curbytes -= paddr_node->nbytes; + pnode->nfrees++; +} + +__tsrc_t *__bt_src_find(void **pproot, char *psrc) { + __tsrc_t node; + node.psrc = psrc; + void *pnode = tfind((const void*)&node, pproot, __bt_src_compare); + if (pnode == NULL) + return NULL; + return *(__tsrc_t**)pnode; +} +#endif // ZOSLIB_TRACE_ALLOCS diff --git a/src/zos.cc b/src/zos.cc index 85f4eaf8..a99c24ce 100644 --- a/src/zos.cc +++ b/src/zos.cc @@ -60,7 +60,6 @@ extern "builtin" void *_gdsa(); #endif - #ifndef dsa #define dsa() ((unsigned long *)_gdsa()) #endif @@ -87,14 +86,18 @@ int (*nanosleep)(const struct timespec*, struct timespec*) = 0; #define MIN(a, b) ((a) < (b) ? (a) : (b)) +// These couldn't be in a zoslib namespace in a header, as the header may also be +// accessed from C. +const size_t kMB = 1024u * 1024u; +const size_t kGB = kMB * 1024u; + namespace { char **__argv = nullptr; int __argc = -1; pthread_t _timer_tid; int *__main_thread_stack_top_address = 0; bool __is_backtrace_on_abort = true; -bool __zoslib_terminated = false; -bool __gMainTerminating = false; +bool __gZoslibInitialized = false; int __gMainThreadId = -1; pthread_t __gMainThreadSelf = {-1u}; @@ -115,17 +118,19 @@ const char *__zoslib_version = DEFAULT_BUILD_STRING; #endif extern "C" void __set_ccsid_guess_buf_size(int nbytes); + +#ifdef ZOSLIB_TRACE_ALLOCS +extern "C" void *__alloc_seg_trace(size_t segs, const char *pfname, int linenum); +#else +extern "C" void *__alloc_seg(size_t segs); +#endif +extern "C" int __free_seg(void *ptr, size_t reqsize); extern "C" void update_memlogging(__zinit *, const char *envar); extern "C" void update_memlogging_level(__zinit *, const char *envar); extern "C" void update_memlogging_inc(__zinit *, const char *envar); - -#ifndef max -#define max(a, b) (((a) > (b)) ? (a) : (b)) -#endif +extern "C" bool __isZoslibInitialized() { return __gZoslibInitialized; } __zinit* __get_instance() { - if (__zoslib_terminated) - return 0; return &__instance; } @@ -972,17 +977,6 @@ static int mem_account(void) { return res; } -static void getxttoken(char *out) { - void *p; - asm volatile(" l %0,1208 \n" - " llgtr %0,%0 \n" - " lg %0,304(%0)\n" - : "+r"(p) - : - : "r0"); - memcpy(out, (char *)p + 0x14, 16); -} - struct iarv64parm { unsigned char xversion __attribute__((__aligned__(16))); // 0 unsigned char xrequest; // 1 @@ -1113,12 +1107,13 @@ static long long __iarv64(void *parm, long long *reason_code_ptr) { // memory allocations with the LE enclave. // See // https://www.ibm.com/support/knowledgecenter/en/SSLTBW_2.1.0/com.ibm.zos.v2r1.ceev100/mout.htm + unsigned long getipttoken(void) { return ((unsigned long)((char *__ptr32 *__ptr32 *__ptr32)(1208))[0][82]) << 32; } -static void *__iarv64_alloc(int segs, const char *token, +extern "C" void *__iarv64_alloc(int segs, const char *token, long long *prc, long long *preason) { if (segs == 0) { // process gets killed if __iarv64(&parm,..) is called with parm.xsegments=0 @@ -1152,8 +1147,7 @@ static void *__iarv64_alloc(int segs, const char *token, return nullptr; } -#define __USE_IARV64 1 // 0=moservices, 1=iarv64 -static long long __iarv64_free(void *ptr, const char *token, +extern "C" long long __iarv64_free(void *ptr, const char *token, long long *preason) { struct iarv64parm parm __attribute__((__aligned__(16))); memset(&parm, 0, sizeof(parm)); @@ -1167,243 +1161,27 @@ static long long __iarv64_free(void *ptr, const char *token, return __iarv64(&parm, preason); } -#if !__USE_IARV64 -static void *__mo_alloc(int segs) { - __mopl_t moparm; - void *p = 0; - memset(&moparm, 0, sizeof(moparm)); - moparm.__mopldumppriority = __MO_DUMP_PRIORITY_STACK + 5; - moparm.__moplrequestsize = segs; - moparm.__moplgetstorflags = __MOPL_PAGEFRAMESIZE_PAGEABLE1MEG; - int rc = __moservices(__MO_GETSTOR, sizeof(moparm), &moparm, &p); - if (mem_account()) { - fprintf(stderr, - "__moservices-alloc: pid %d tid %d ptr=%p size=%lu(0x%lx) rc=%d, " - "iarv64_rc=%d\n", - getpid(), gettid(), p, - (unsigned long)segs * kMegaByte, - (unsigned long)segs * kMegaByte, rc, moparm.__mopl_iarv64_rc); - } - if (rc == 0 && moparm.__mopl_iarv64_rc == 0) { - return p; - } - perror("__moservices GETSTOR"); - return 0; -} - -static int __mo_free(void *ptr) { - int rc = __moservices(__MO_DETACH, 0, NULL, &ptr); - if (mem_account()) { - fprintf(stderr, "__moservices-free: pid %d tid %d ptr=%p rc=%d\n", getpid(), - gettid(), ptr, rc); - } - if (rc) { - perror("__moservices DETACH"); - } - return rc; -} +#ifdef ZOSLIB_TRACE_ALLOCS +void *__zalloc_trace(size_t len, size_t alignment, + const char *pfname, int linenum) { +#else +void *__zalloc(size_t len, size_t alignment) { #endif - -typedef unsigned long value_type; -typedef unsigned long key_type; - -struct __hash_func { - size_t operator()(const key_type &k) const { - size_t s = 0; - key_type n = k; - while (0 == (n & 1) && s < (sizeof(key_type) - 1)) { - n = n >> 1; - ++s; - } - return s + (n * 0x744dcf5364d7d667UL); - } -}; - -typedef std::unordered_map::const_iterator - mem_cursor_t; - -class __Cache { - std::unordered_map cache; - std::mutex access_lock; - char xttoken[16]; - unsigned short asid; - int oktouse; - size_t curmem31; - size_t curmem64; - size_t maxmem31; - size_t maxmem64; - -public: - __Cache() { -#if __USE_IARV64 - getxttoken(xttoken); - asid = ((unsigned short *)(*(char *__ptr32 *)(0x224)))[18]; + if (len % kMB == 0) { + size_t req_size = len / kMB; +#ifdef ZOSLIB_TRACE_ALLOCS + return __alloc_seg_trace(req_size, pfname, linenum); +#else + return __alloc_seg(req_size); #endif - oktouse = - (*(int *)(80 + ((char ****__ptr32 *)1208)[0][11][1][123]) >= 0x04020200); - // LE level is 220 or above - curmem31 = curmem64 = maxmem31 = maxmem64 = 0u; - } - - size_t getCurrentMem31() { return curmem31; } - size_t getCurrentMem64() { return curmem64; } - size_t getMaxMem31() { return maxmem31; } - size_t getMaxMem64() { return maxmem64; } - - void addptr31(const void *ptr, size_t v) { - unsigned long k = (unsigned long)ptr; - std::lock_guard guard(access_lock); - cache[k] = (unsigned long)v; - curmem31 += v; - maxmem31 = max(maxmem31, curmem31); - if (__doLogMemoryAll()) { - __memprintf("addr=%p, size=%zu: malloc31 OK (current=%zu, max=%zu)\n", - ptr, v, curmem31, maxmem31); - } - } -#if __USE_IARV64 - void *alloc_seg(size_t segs) { - long long rc, reason; - std::lock_guard guard(access_lock); - void *p = __iarv64_alloc(segs, xttoken, &rc, &reason); - size_t size = segs * kMegaByte; - if (p) { - unsigned long k = (unsigned long)p; - cache[k] = size; - curmem64 += size; - maxmem64 = max(maxmem64, curmem64); - if (__doLogMemoryAll()) { - __memprintf("addr=%p, size=%zu: iarv64_alloc OK (current=%zu, " \ - "max=%zu)\n", p, size, curmem64, maxmem64); - } - } else if (__doLogMemoryUsage()) { - __memprintf("ERROR: size=%zu: iarv64_alloc failed, rc=%llx, " \ - "reason=%llx (current=%zu, max=%zu)\n", - size, rc, reason, curmem64, maxmem64); - } - return p; - } - int free_seg(void *ptr, size_t reqsize) { - unsigned long k = (unsigned long)ptr; - long long rc, reason; - { - std::lock_guard guard(access_lock); - mem_cursor_t c = cache.find(k); - if (c != cache.end()) { - size_t size = c->second; - cache.erase(c); - rc = __iarv64_free(ptr, xttoken, &reason); - if (rc == 0) { - curmem64 -= size; - if (__doLogMemoryUsage()) { - const char *w = size != reqsize ? " VWARN size vs req-size" : ""; - if (__doLogMemoryAll() || (*w && __doLogMemoryWarning())) - __memprintf("addr=%p size=%zu req-size=%zu iarv64_free OK " \ - "(v64=%zu)%s\n", ptr, size, reqsize, curmem64, w); - } - } else if (__doLogMemoryUsage()) { - __memprintf("VERROR addr=%p size=%zu iarv64_free failed " \ - "rc=%llx, reason=%llx (v64=%zu)\n", ptr, size, - rc, reason, curmem64); - } - return rc; - } - } - - return -1; - } + } else if (len > (2UL * kGB)) { + size_t req_size = __round_up(len, kMB) / kMB; +#ifdef ZOSLIB_TRACE_ALLOCS + return __alloc_seg_trace(req_size, pfname, linenum); #else - void *alloc_seg(int segs) { - void *p = __mo_alloc(segs); - std::lock_guard guard(access_lock); - if (p) { - unsigned long k = (unsigned long)p; - cache[k] = (unsigned long)segs * kMegaByte; - if (mem_account()) - dprintf(2, "MEM_CACHE INSERTED: @%lx size %lu RMODE64\n", k, - (unsigned long)segs * kMegaByte); - } - return p; - } - int free_seg(void *ptr) { - unsigned long k = (unsigned long)ptr; - int rc = __mo_free(ptr); - std::lock_guard guard(access_lock); - if (rc == 0) { - mem_cursor_t c = cache.find(k); - if (c != cache.end()) { - unsigned long s = c->second; - cache.erase(c); - if (mem_account()) { - dprintf(2, "MEM_CACHE DELETED: @%lx size %lu RMODE64\n", k, s); - } - } - } - return rc; - } + return __alloc_seg(req_size); #endif - int is_rmode64(const void *ptr) { - unsigned long k = (unsigned long)ptr; - std::lock_guard guard(access_lock); - mem_cursor_t c = cache.find(k); - if (c != cache.end()) { - if (0 != (k & 0xffffffff80000000UL)) - return 1; - else - return 0; - } - return 0; - } - void freeptr31(const void *ptr, size_t reqsize) { - unsigned long k = (unsigned long)ptr; - std::lock_guard guard(access_lock); - mem_cursor_t c = cache.find(k); - if (c != cache.end()) { - curmem31 -= c->second; - if (__doLogMemoryUsage()) { - const char *w = c->second != reqsize ? " WARNING: size vs req-size" : ""; - if (__doLogMemoryAll() || (*w && __doLogMemoryWarning())) - __memprintf("addr=%p, size=%zu, req-size=%zu: free31 OK " \ - "(current=%zu)%s\n", ptr, c->second, reqsize, curmem31, w); - } - cache.erase(c); - } else { - if (__doLogMemoryWarning()) { - __memprintf("WARNING: addr=%p, req-size=%zu free31 OK but not found " \ - "in cache\n", ptr, reqsize); - } - } - } - void displayDebris() { - // This should only be called during exit-time, so there's no lock. - for (mem_cursor_t it = cache.begin(); it != cache.end(); ++it) { - __memprintf("WARNING: addr=%lx, size=%lu: DEBRIS (allocated but not " \ - "freed)\n", it->first, it->second); - } - } - ~__Cache() { - // This should never be called as we deliberately don't destroy it. - // See ~__zinit() - assert(0); - } -}; - -static __Cache* __galloc_info = nullptr; - -static __Cache * __get_galloc_info() { - assert(__galloc_info != nullptr); - return __galloc_info; -} - -extern "C" void *__zalloc(size_t len, size_t alignment) { - if (len % kMegaByte == 0) { - size_t request_size = len / kMegaByte; - return __get_galloc_info()->alloc_seg(request_size); - } else if (len > (2UL * kGigaByte)) { - size_t request_size = __round_up(len, kMegaByte) / kMegaByte; - return __get_galloc_info()->alloc_seg(request_size); } else { - void *p; // The following solution allocates memory 2gb below the bar whose length // is a multiple of 8 and whose memory is aligned on a page (4096) boundary. // The below solution is similar to: @@ -1413,50 +1191,70 @@ extern "C" void *__zalloc(size_t len, size_t alignment) { size_t extra_size = alignment - 1 + sizeof(void *); // Allocate the required size and a bit extra +#ifdef ZOSLIB_TRACE_ALLOCS + void *mem_default = __malloc31_trace(len + extra_size, pfname, linenum); +#else void *mem_default = __malloc31(len + extra_size); +#endif if (mem_default == NULL) { if (__doLogMemoryUsage()) { - __memprintf("ERROR: size=%zu: malloc31 failed, errno=%d " \ - "(current=%zu), will try to allocate from virtual storage\n", - len + extra_size, errno, - __get_galloc_info()->getCurrentMem31()); + __memprintf("ERROR: size=%zu: malloc31 failed, errno=%d, " \ + "will try to allocate from virtual storage\n", + len + extra_size, errno); } size_t up_size = __round_up(len + extra_size, kMegaByte); - size_t request_size = up_size / kMegaByte; - return __get_galloc_info()->alloc_seg(request_size); + size_t req_size = up_size / kMegaByte; +#ifdef ZOSLIB_TRACE_ALLOCS + return __alloc_seg_trace(req_size, pfname, linenum); +#else + return __alloc_seg(req_size); +#endif } void **mem_aligned = (void **)(((size_t)(mem_default) + extra_size) & ~(alignment - 1)); mem_aligned[-1] = mem_default; - p = (void *)mem_aligned; - __get_galloc_info()->addptr31(p, len); + void *p = (void*)mem_aligned; memset(p, 0, len); return p; } } +#ifdef ZOSLIB_TRACE_ALLOCS +void *anon_mmap_trace(void *_, size_t len, const char *pfname, int linenum) { + void *p = __zalloc_trace(len, PAGE_SIZE, pfname, linenum); + return (p == nullptr) ? MAP_FAILED : p; +} +#else void *anon_mmap(void *_, size_t len) { void *p = __zalloc(len, PAGE_SIZE); return (p == nullptr) ? MAP_FAILED : p; } - -extern "C" int __zfree(void *addr, int len) { - if (__get_galloc_info()->is_rmode64(addr)) { - return __get_galloc_info()->free_seg(addr, len); - } - // Free the original unaligned memory returned by __malloc31. Since free() - // doesn't return a value, simply return 0. - free(((void **)addr)[-1]); - __get_galloc_info()->freeptr31(addr, len); - return 0; -} +#endif int anon_munmap(void *addr, size_t len) { return __zfree(addr, len); } +bool __is_rmode64(const void *ptr) { + unsigned long k = (unsigned long)ptr; + if ((k & 0xffffffff80000000UL) != 0u) + return true; + return false; +} + +int __zfree(void *addr, int len) { + int m = __is_rmode64(addr); + if (m == 1) { + return __free_seg(addr, len); + } else { + // Free the original unaligned memory returned by __malloc31. + free(((void **)addr)[-1]); + return 0; + } +} + extern "C" int execvpe(const char *name, char *const argv[], char *const envp[]) { // Absolute or Relative Path Name @@ -2153,8 +1951,14 @@ unsigned long long __registerProduct(const char *major_version, return ifausage_rc; } -extern "C" void *__zalloc_for_fd(size_t len, const char *filename, int fd, - off_t offset) { +#ifdef ZOSLIB_TRACE_ALLOCS +void *__zalloc_for_fd_trace(size_t len, const char *filename, int fd, + off_t offset, + const char *psrcfname, int linenum) { +#else +void *__zalloc_for_fd(size_t len, const char *filename, int fd, + off_t offset) { +#endif // Allocate memory to read contents of given file at the given offset; // handles conversion if file contains EBCDIC data. // TODO(gabylb): mmap() could be used and the mapped memory contents converted @@ -2173,7 +1977,11 @@ extern "C" void *__zalloc_for_fd(size_t len, const char *filename, int fd, } static const int pgsize = sysconf(_SC_PAGESIZE); size_t size = __round_up(len, pgsize); +#ifdef ZOSLIB_TRACE_ALLOCS + void *memory = __zalloc_trace(size, pgsize, psrcfname, linenum); +#else void *memory = __zalloc(size, pgsize); +#endif if (memory == nullptr) { return memory; } @@ -2192,11 +2000,20 @@ extern "C" void *__zalloc_for_fd(size_t len, const char *filename, int fd, return memory; } -extern "C" void *roanon_mmap(void *_, size_t len, int prot, int flags, - const char *filename, int fd, off_t offset) { +#ifdef ZOSLIB_TRACE_ALLOCS +void *roanon_mmap_trace(void *_, size_t len, int prot, int flags, + const char *filename, int fd, off_t offset, + const char *psrcfname, int linenum) { + void *p = __zalloc_for_fd_trace(len, filename, fd, offset, psrcfname, linenum); + return (p == nullptr) ? MAP_FAILED : p; +} +#else +void *roanon_mmap(void *_, size_t len, int prot, int flags, + const char *filename, int fd, off_t offset) { void *p = __zalloc_for_fd(len, filename, fd, offset); return (p == nullptr) ? MAP_FAILED : p; } +#endif int __print_zoslib_help(FILE *fp, const char *title) { __zinit *zinit_ptr = __get_instance(); @@ -2284,10 +2101,10 @@ int __update_envar_settings(const char *envar) { if (force_update_all || strcmp(envar, config.MEMORY_USAGE_LOG_FILE_ENVAR) == 0) update_memlogging(zinit_ptr, envar); - + if (force_update_all || strcmp(envar, config.MEMORY_USAGE_LOG_LEVEL_ENVAR) == 0) update_memlogging_level(zinit_ptr, envar); - + if (force_update_all || strcmp(envar, config.MEMORY_USAGE_LOG_INC_ENVAR) == 0) update_memlogging_inc(zinit_ptr, envar); @@ -2482,8 +2299,14 @@ __zinit::__zinit() { update_memlogging(__get_instance(), nullptr); update_memlogging_level(__get_instance(), nullptr); + update_memlogging_inc(__get_instance(), nullptr); + + __gZoslibInitialized = true; if (__doLogMemoryUsage()) { +#if defined(ZOSLIB_TRACE_ALLOCS) + heapreport(); +#endif int len = 0; __gArgsStr[0] = 0; for (int i=0; i<__getargc(); ++i) { @@ -2501,7 +2324,7 @@ __zinit::__zinit() { snprintf(__gChildInfo, sizeof(__gChildInfo), "?(%d)-CHILD", ppid); } else { const char *parentname = strrchr(argv[0], '/'); - parentname = (parentname != nullptr) ? parentname + 1 : argv[0]; + parentname = (parentname != nullptr) ? parentname + 1 : argv[0]; free((void*)argv); snprintf(__gChildInfo, sizeof(__gChildInfo), "%s(%d)-CHILD", parentname, ppid); @@ -2513,41 +2336,14 @@ __zinit::__zinit() { __zinit:: ~__zinit() { ::__cleanupipc(0); - // Don't delete __galloc_info (__Cache), as during exit-time a process may - // still be allocating memory using __zalloc(), which call its alloc_seg(). - if (__doLogMemoryUsage()) { - if (__gMainTerminating && __doLogMemoryWarning()) - __get_galloc_info()->displayDebris(); - int ppid = getppid(); - char childInfo[32] = ""; - // Include (parent-pid) in the termination message: - int argc; - char **argv = nullptr; - if (__getargcv(&argc, &argv, ppid) != 0) { - snprintf(childInfo, sizeof(childInfo), "?(%d)-CHILD ", ppid); - } else { - const char *parentname = strrchr(argv[0], '/'); - parentname = (parentname != nullptr) ? parentname + 1 : argv[0]; - snprintf(childInfo, sizeof(childInfo), "%s(%d)-CHILD ", parentname, ppid); + // global __argv is for current process: + if (__argv != nullptr) { + free((void*)__argv); + __argv = nullptr; } - if (argv != nullptr) - free((char*)argv); - const char *leak = __gMainTerminating && - (__get_galloc_info()->getCurrentMem31() != 0 || - __get_galloc_info()->getCurrentMem64() != 0) ? - "LEAK: " : ""; - - __memprintf("%s%sPROCESS TERMINATING (current31=%zu, max31=%zu, " \ - "current64=%zu, max64=%zu): %s\n", - leak, childInfo, - __get_galloc_info()->getCurrentMem31(), - __get_galloc_info()->getMaxMem31(), - __get_galloc_info()->getCurrentMem64(), - __get_galloc_info()->getMaxMem64(), - __gArgsStr); - } - __zoslib_terminated = true; + __memprintf("%s PROCESS TERMINATING: %s\n", __gChildInfo, __gArgsStr); + } } __init_zoslib::__init_zoslib(const zoslib_config_t &config) { @@ -2606,7 +2402,6 @@ static void setProcessEnvars() { int __zinit::initialize(const zoslib_config_t &aconfig) { memcpy(&config, &aconfig, sizeof(config)); - __galloc_info = new __Cache; mode = __ae_thread_swapmode(__AE_ASCII_MODE); cvstate = __ae_autoconvert_state(_CVTSTATE_QUERY); @@ -2644,7 +2439,6 @@ int __zinit::initialize(const zoslib_config_t &aconfig) { setenv("_BPXK_AUTOCVT", "ON", 1); } - std::string ccsid; if (get_env_var("__STDIN_CCSID", ccsid)) __set_autocvt_on_fd_stream(STDIN_FILENO, std::stoul(ccsid), 1, false); @@ -3043,8 +2837,6 @@ extern "C" int __check_le_func(void *addr, char *funcname, size_t len) { return 1; } -extern "C" void __mainTerminating() { __gMainTerminating = true; } - //TODO: Implement chdir_long properly, for now call chdir extern "C" int chdir_long(char *dir) { return chdir(dir); diff --git a/test/test-allocs.cc b/test/test-allocs.cc new file mode 100644 index 00000000..b21d3af6 --- /dev/null +++ b/test/test-allocs.cc @@ -0,0 +1,73 @@ +#ifdef ZOSLIB_TRACE_ALLOCS + +#include "test-args.h" +#include "zos.h" +#include "gtest/gtest.h" + +#include +#include + +const size_t kMB = 1024u * 1024u; + +TEST(AllocsTest, CAllocs) { + const int N = 100; + void *ptrs[N*5]; + int i; + + for (i=0; i(ptrs[i]), str1); + } + + const char *str2 = "1234567890"; + for (; i(ptrs[i]), "12345678"); + } + for (i=0; i