* libs/libc/misc/lib_backtrace.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
* Included Files
****************************************************************************/
#include <arch/irq.h>
#include <debug.h>
#include <execinfo.h>
#include <stdio.h>
#include <stdbool.h>
#include <syslog.h>
#include <nuttx/init.h>
#include <nuttx/nuttx.h>
#include <nuttx/sched.h>
#include <nuttx/spinlock.h>
#include <nuttx/mutex.h>
#include "libc.h"
* Pre-processor Definitions
****************************************************************************/
#if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)
# define backtrace_lock(bp) spin_lock_irqsave_notrace(&(bp)->lock)
# define backtrace_unlock(bp, flags) \
spin_unlock_irqrestore_notrace(&(bp)->lock, flags)
#else
# define backtrace_lock(bp) ((irqstate_t)nxmutex_lock(&(bp)->lock))
# define backtrace_unlock(bp, flags) \
((void)flags, nxmutex_unlock(&(bp)->lock))
#endif
* Private Type Declarations
****************************************************************************/
#if CONFIG_LIBC_BACKTRACE_DEPTH > 0
struct backtrace_entry_s
{
union
{
FAR void *stack[CONFIG_LIBC_BACKTRACE_DEPTH];
struct sq_entry_s freenode;
};
FAR void *next;
uint32_t depth;
uint32_t ref;
};
struct backtrace_pool_s
{
FAR struct backtrace_entry_s **bucket;
struct sq_queue_s freelist;
#if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)
spinlock_t lock;
#else
mutex_t lock;
#endif
size_t capacity;
size_t used;
bool expanding;
};
* Private Data
****************************************************************************/
static struct backtrace_pool_s g_backtrace_pool =
{
#if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)
.lock = SP_UNLOCKED,
#else
.lock = NXMUTEX_INITIALIZER,
#endif
};
#endif
* Private Functions
****************************************************************************/
static FAR char **backtrace_malloc(FAR void *const *buffer, int size)
{
size_t length = 0;
if (size <= 0)
{
return NULL;
}
while (size-- > 0)
{
int ret = snprintf(NULL, 0, "%pS", *buffer++);
if (ret < 0)
{
return NULL;
}
length += sizeof(FAR char *) + ret + 1;
}
return lib_malloc(length);
}
#if CONFIG_LIBC_BACKTRACE_DEPTH > 0
* Name: backtrace_hash
****************************************************************************/
static uint32_t backtrace_hash(FAR const void *backtrace, int depth)
{
FAR const uintptr_t *data = backtrace;
uint32_t hash = 5381;
int i;
for (i = 0; i < depth; i++)
{
hash = ((hash << 5) + hash) + data[i];
}
return hash;
}
* Name: backtrace_rehash
****************************************************************************/
static void backtrace_rehash(FAR struct backtrace_pool_s *bp,
FAR struct backtrace_entry_s **bucket)
{
FAR struct backtrace_entry_s *entry;
size_t capacity = bp->capacity;
size_t i;
* bucket
*/
for (i = 0; i < capacity; i++)
{
entry = bp->bucket[i];
while (entry)
{
uint32_t slot = backtrace_hash(entry->stack, entry->depth);
FAR struct backtrace_entry_s *next = entry->next;
slot = slot % (capacity * 2);
entry->next = bucket[slot];
bucket[slot] = entry;
entry = next;
}
}
}
* Name: backtrace_expand
****************************************************************************/
static int backtrace_expand(FAR struct backtrace_pool_s *bp)
{
FAR struct backtrace_entry_s **oldbucket;
FAR struct backtrace_entry_s **bucket;
FAR struct backtrace_entry_s *pool;
size_t capacity = bp->capacity;
size_t poolsize;
irqstate_t flags;
size_t i;
* initialized, we will initialize it with the default size
*/
if (capacity == 0)
{
poolsize = CONFIG_LIBC_BACKTRACE_INIT_SIZE;
capacity = CONFIG_LIBC_BACKTRACE_INIT_SIZE;
}
else
{
* the old bunket.
*/
poolsize = capacity;
capacity = capacity * 2;
}
bucket = lib_zalloc(sizeof(FAR struct backtrace_entry_s *) * capacity);
pool = lib_zalloc(sizeof(struct backtrace_entry_s) * poolsize);
if (bucket == NULL || pool == NULL)
{
lib_free(bucket);
lib_free(pool);
return -ENOMEM;
}
* bucket
*/
flags = backtrace_lock(bp);
if (bp->capacity)
{
backtrace_rehash(bp, bucket);
}
else
{
sq_init(&bp->freelist);
}
* to the free list
*/
for (i = 0; i < poolsize; i++)
{
sq_addlast(&pool[i].freenode, &bp->freelist);
}
oldbucket = bp->bucket;
bp->bucket = bucket;
bp->capacity = capacity;
backtrace_unlock(bp, flags);
lib_free(oldbucket);
return 0;
}
* Name: backtrace_exist
****************************************************************************/
static FAR struct backtrace_entry_s *
backtrace_find(FAR struct backtrace_pool_s *bp, uint32_t slot,
FAR const void *stack, int depth)
{
FAR struct backtrace_entry_s *entry = bp->bucket[slot];
while (entry != NULL)
{
if (entry->depth == depth &&
memcmp(stack, entry->stack, depth * sizeof(FAR void *)) == 0)
{
* reference count
*/
entry->ref++;
return entry;
}
entry = entry->next;
}
return NULL;
}
* Name: backtrace_alloc
****************************************************************************/
static FAR struct backtrace_entry_s *
backtrace_alloc(FAR struct backtrace_pool_s *bp, uint32_t slot,
const FAR void *stack, int depth)
{
FAR struct backtrace_entry_s *entry;
entry = (FAR struct backtrace_entry_s *)sq_remfirst(&bp->freelist);
if (entry == NULL)
{
return NULL;
}
entry = container_of(entry, struct backtrace_entry_s, freenode);
DEBUGASSERT(entry->ref == 0);
bp->used++;
memcpy(entry->stack, stack, depth * sizeof(FAR void *));
entry->depth = depth;
entry->ref = 1;
entry->next = bp->bucket[slot];
bp->bucket[slot] = entry;
return entry;
}
* Public Functions
****************************************************************************/
* Name: backtrace_record
*
* Description:
* Record the backtrace of the current task
*
* Returned Value:
* Return the index of the backtrace record if success, otherwise return
* a negtive value.
****************************************************************************/
FAR void *backtrace_record(int skip)
{
FAR void *buffer[CONFIG_LIBC_BACKTRACE_DEPTH];
FAR struct backtrace_pool_s *bp = &g_backtrace_pool;
FAR struct backtrace_entry_s *entry = NULL;
irqstate_t flags;
uint32_t slot;
int depth;
depth = sched_backtrace(_SCHED_GETTID(), buffer,
CONFIG_LIBC_BACKTRACE_DEPTH, skip);
if (depth <= 0)
{
return NULL;
}
slot = backtrace_hash(buffer, depth);
flags = backtrace_lock(bp);
if (bp->used >= bp->capacity * CONFIG_LIBC_BACKTRACE_LOAD_FACTOR / 100 &&
!bp->expanding
#if defined(CONFIG_BUILD_FLAT) || defined(__KERNEL__)
&& OSINIT_MM_READY() && !up_interrupt_context() &&
(!is_idle_task(nxsched_self()) || !OSINIT_IDLELOOP())
#endif
)
{
bp->expanding = true;
backtrace_unlock(bp, flags);
backtrace_expand(bp);
flags = backtrace_lock(bp);
bp->expanding = false;
}
if (bp->capacity)
{
slot = slot % bp->capacity;
entry = backtrace_find(bp, slot, buffer, depth);
if (entry == NULL)
{
entry = backtrace_alloc(bp, slot, buffer, depth);
}
}
backtrace_unlock(bp, flags);
return entry;
}
* Name: backtrace_remove
*
* Description:
* Remove the backtrace record
*
* Input Parameters:
* index - The index of the backtrace record
*
* Returned Value:
* Return 0 if success, otherwise return a negtive value.
****************************************************************************/
void backtrace_remove(FAR void *index)
{
FAR struct backtrace_pool_s *bp = &g_backtrace_pool;
FAR struct backtrace_entry_s *entry = index;
irqstate_t flags;
uint32_t slot;
if (entry == NULL)
{
return;
}
flags = backtrace_lock(bp);
DEBUGASSERT(entry->ref > 0);
entry->ref--;
if (entry->ref > 0)
{
backtrace_unlock(bp, flags);
return;
}
slot = backtrace_hash(entry->stack, entry->depth);
slot = slot % bp->capacity;
if (bp->bucket[slot] == entry)
{
bp->bucket[slot] = entry->next;
}
else
{
FAR struct backtrace_entry_s *prev = bp->bucket[slot];
while (prev)
{
if (prev->next == entry)
{
prev->next = entry->next;
break;
}
prev = prev->next;
}
* record entry is an invalid value.
*/
DEBUGASSERT(prev != NULL);
}
bp->used--;
sq_addlast(&entry->freenode, &bp->freelist);
backtrace_unlock(bp, flags);
}
* Name: backtrace_get
*
* Description:
* Find the backtrace record by index
*
* Input Parameters:
* index - The index of the backtrace record
* size - The size of the backtrace record
*
* Returned Value:
* Return the backtrace record if success, otherwise return NULL
****************************************************************************/
FAR void **backtrace_get(FAR void *index, FAR int *size)
{
FAR struct backtrace_entry_s *entry = index;
if (size == NULL || entry == NULL)
{
return NULL;
}
*size = entry->depth;
return entry->stack;
}
void backtrace_dump(void)
{
char buf[BACKTRACE_BUFFER_SIZE(CONFIG_LIBC_BACKTRACE_DEPTH)];
FAR struct backtrace_pool_s *bp = &g_backtrace_pool;
FAR struct backtrace_entry_s *entry;
size_t conflict = 0;
size_t slot;
syslog(LOG_INFO, "%-8s %-8s %s", "slot", "refcount", "backtrace");
for (slot = 0; slot < bp->capacity; slot++)
{
entry = bp->bucket[slot];
while (entry)
{
backtrace_format(buf, sizeof(buf), entry->stack, entry->depth);
syslog(LOG_INFO, "%-8zu %-8d %s\n", slot, (int)entry->ref, buf);
conflict += entry->next != NULL;
entry = entry->next;
}
}
syslog(LOG_INFO, "capacity: %zu, used: %zu, conflict: %zu\n",
bp->capacity, bp->used, conflict);
}
#endif
FAR char **backtrace_symbols(FAR void *const *buffer, int size)
{
FAR char **syms;
FAR char *buf;
int i;
syms = backtrace_malloc(buffer, size);
if (syms != NULL)
{
buf = (FAR char *)&syms[size];
for (i = 0; i < size; i++)
{
syms[i] = buf;
buf += sprintf(buf, "%pS", buffer[i]);
buf += 1;
}
}
return syms;
}
void backtrace_symbols_fd(FAR void *const *buffer, int size, int fd)
{
int i;
for (i = 0; i < size; i++)
{
dprintf(fd, "%pS\n", buffer[i]);
}
}
* Name: backtrace_format
*
* Description:
* Format a backtrace into a buffer for dumping.
*
****************************************************************************/
nosanitize_address
int backtrace_format(FAR char *buffer, int size,
FAR void *backtrace[], int depth)
{
FAR const char *format = "%0*p ";
int len = 0;
int i;
if (buffer == NULL || backtrace == NULL || size < 1)
{
return 0;
}
buffer[0] = '\0';
for (i = 0; i < depth && backtrace[i]; i++)
{
if ((i + 1) * BACKTRACE_PTR_FMT_WIDTH >= size)
{
break;
}
len += snprintf(buffer + i * BACKTRACE_PTR_FMT_WIDTH,
size - i * BACKTRACE_PTR_FMT_WIDTH,
format, BACKTRACE_PTR_FMT_WIDTH - 1, backtrace[i]);
}
return len;
}