libs/libc/misc/lib_backtrace.c (285 lines of code) (raw):

/**************************************************************************** * 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 <execinfo.h> #include <stdio.h> #include <stdbool.h> #include <syslog.h> #include <sys/param.h> #include <nuttx/spinlock.h> #include "libc.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_LIBC_BACKTRACE_BUFFSIZE # define CONFIG_LIBC_BACKTRACE_BUFFSIZE 0 #endif /* Calculate the number of backtrace that can be saved based on * LIBC_BACKTRACE_BUFFSIZE. Each backtrace entry corresponds to a hash * table entry. The maximum number of entries that can be recorded is * BACKTRACE_BUFFSIZE / (backtrace entry size + hash entry size). */ #define BACKTRACE_NUMBER (CONFIG_LIBC_BACKTRACE_BUFFSIZE / \ (sizeof(struct backtrace_entry_s) + \ sizeof(int))) /**************************************************************************** * Private Type Declarations ****************************************************************************/ #if CONFIG_LIBC_BACKTRACE_BUFFSIZE > 0 struct backtrace_entry_s { union { FAR void *stack[CONFIG_LIBC_BACKTRACE_DEPTH]; struct sq_entry_s freenode; }; uint16_t depth; /* Depth of the backtrace */ uint16_t count; /* Count of the backtrace */ int next; /* Next index in the hash chain */ }; struct backtrace_pool_s { /* Pool to store the backtrace record */ struct backtrace_entry_s pool[BACKTRACE_NUMBER]; /* Hash table to store the index of the backtrace record */ int bucket[BACKTRACE_NUMBER]; struct sq_queue_s freelist; spinlock_t lock; bool init; }; /**************************************************************************** * Private Data ****************************************************************************/ static struct backtrace_pool_s g_backtrace_pool; #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_BUFFSIZE > 0 /**************************************************************************** * Name: backtrace_init * * Description: * Initialize the backtrace record * ****************************************************************************/ static void backtrace_init(void) { FAR struct backtrace_pool_s *bp = &g_backtrace_pool; size_t i; sq_init(&bp->freelist); memset(bp->bucket, -1, sizeof(bp->bucket)); for (i = 0; i < nitems(bp->pool); i++) { sq_addlast(&bp->pool[i].freenode, &bp->freelist); } } /**************************************************************************** * Name: backtrace_hash ****************************************************************************/ static int backtrace_hash(FAR struct backtrace_pool_s *bp, FAR const void *backtrace, int depth) { FAR const uint8_t *data = backtrace; uint32_t hash = 5381; int i; for (i = 0; i < depth * sizeof(uintptr_t); i++) { hash = ((hash << 5) + hash) + data[i]; } return hash % nitems(bp->bucket); } /**************************************************************************** * Name: backtrace_exist ****************************************************************************/ static int backtrace_exist(FAR struct backtrace_pool_s *bp, int slot, FAR void *backtrace, int depth) { FAR struct backtrace_entry_s *entry; int index; if (bp->init == false) { bp->init = true; backtrace_init(); return -ENOENT; } index = bp->bucket[slot]; while (index >= 0) { entry = &bp->pool[index]; if (entry->depth == depth && memcmp(backtrace, entry->stack, depth * sizeof(FAR void *)) == 0) { return index; } index = entry->next; } return -ENOENT; } /**************************************************************************** * Name: backtrace_alloc ****************************************************************************/ static int backtrace_alloc(FAR struct backtrace_pool_s *bp) { FAR struct backtrace_entry_s *entry; FAR struct sq_entry_s *sq; /* Get the first entry from the free list */ sq = sq_remfirst(&bp->freelist); if (!sq) { return -ENOMEM; } entry = (FAR struct backtrace_entry_s *)sq; return entry - bp->pool; } /**************************************************************************** * Name: backtrace_free ****************************************************************************/ static void backtrace_free(FAR struct backtrace_pool_s *bp, int index) { FAR struct backtrace_entry_s *entry = &bp->pool[index]; sq_addlast(&entry->freenode, &bp->freelist); entry->depth = 0; } /**************************************************************************** * 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. ****************************************************************************/ int 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; irqstate_t flags; int depth; int index; int slot; depth = sched_backtrace(_SCHED_GETTID(), buffer, CONFIG_LIBC_BACKTRACE_DEPTH, skip); if (depth <= 0) { return -ENOENT; } slot = backtrace_hash(bp, buffer, depth); flags = spin_lock_irqsave(&bp->lock); index = backtrace_exist(bp, slot, buffer, depth); if (index >= 0) { /* If the backtrace record already exists, just increase the count */ entry = &bp->pool[index]; entry->count++; spin_unlock_irqrestore(&bp->lock, flags); return index; } index = backtrace_alloc(bp); if (index < 0) { spin_unlock_irqrestore(&bp->lock, flags); return index; } entry = &bp->pool[index]; memcpy(entry->stack, buffer, depth * sizeof(FAR void *)); entry->depth = depth; entry->count = 1; /* Insert backtrace to the head of the linked list of the * current hash value */ entry->next = bp->bucket[slot]; bp->bucket[slot] = index; spin_unlock_irqrestore(&bp->lock, flags); return index; } /**************************************************************************** * 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. ****************************************************************************/ int backtrace_remove(int index) { FAR struct backtrace_pool_s *bp = &g_backtrace_pool; FAR struct backtrace_entry_s *entry; irqstate_t flags; int slot; if (index < 0 || index >= nitems(bp->pool)) { return -EINVAL; } flags = spin_lock_irqsave(&bp->lock); entry = &bp->pool[index]; if (entry->count > 1) { entry->count--; spin_unlock_irqrestore(&bp->lock, flags); return OK; } slot = backtrace_hash(bp, entry->stack, entry->depth); /* Remove the backtrace record from the linked list */ if (bp->bucket[slot] == index) { /* Remove the head of the singly linked list */ bp->bucket[slot] = entry->next; } else if (entry->next >= 0) { /* Copy and delete the next node of the singly linked list * to achieve O(1) deletion */ FAR struct backtrace_entry_s *next = &bp->pool[entry->next]; index = entry->next; memcpy(entry, next, sizeof(struct backtrace_entry_s)); } else { /* If it is the last node in the linked list, we need to traverse * and find the previous node to delete it. */ int prev = bp->bucket[slot]; while (prev >= 0) { if (bp->pool[prev].next == index) { bp->pool[prev].next = -1; break; } prev = bp->pool[prev].next; } } backtrace_free(bp, index); spin_unlock_irqrestore(&bp->lock, flags); return OK; } /**************************************************************************** * 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(int index, FAR int *size) { FAR struct backtrace_pool_s *bp = &g_backtrace_pool; FAR struct backtrace_entry_s *entry; if (size == NULL || index < 0 || index >= nitems(bp->pool)) { return NULL; } entry = &bp->pool[index]; *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 i; syslog(LOG_INFO, "%-6s %-6s %s", "index", "count", "backtrace"); for (i = 0; i < nitems(bp->pool); i++) { entry = &bp->pool[i]; if (entry->depth) { backtrace_format(buf, sizeof(buf), entry->stack, entry->depth); syslog(LOG_INFO, "%-6zu %-6u %s\n", i, entry->count, buf); } } } #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 (size < 1) { return 0; } buffer[0] = '\0'; for (i = 0; i < depth && backtrace[i]; i++) { if (i * 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; }