| /**************************************************************************** |
| * 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 Types |
| ****************************************************************************/ |
| |
| #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 negative 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 negative 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; |
| } |