Merge branch 'bugfix/cxx_exceptions' into 'master'

Full C++ Exception Support option (& reclaim memory when exceptions disabled)

See merge request !1353
This commit is contained in:
Angus Gratton 2017-10-18 15:08:10 +08:00
commit 78f70d4e79
20 changed files with 595 additions and 87 deletions

14
Kconfig
View file

@ -93,7 +93,19 @@ config OPTIMIZATION_ASSERTIONS_DISABLED
endchoice # assertions
endmenu # Optimization level
config CXX_EXCEPTIONS
bool "Enable C++ exceptions"
default n
help
Enabling this option compiles all IDF C++ files with exception support enabled.
Disabling this option disables C++ exception support in all compiled files, and any libstdc++ code which throws
an exception will abort instead.
Enabling this option currently adds an additional 20KB of heap overhead, and 4KB of additional heap is allocated
the first time an exception is thrown in user code.
endmenu # Compiler Options
menu "Component config"
source "$COMPONENT_KCONFIGS"

View file

@ -1,3 +1,11 @@
# Mark __cxa_guard_dummy as undefined so that implementation of static guards
# is taken from cxx_guards.o instead of libstdc++.a
COMPONENT_ADD_LDFLAGS += -u __cxa_guard_dummy
ifndef CONFIG_CXX_EXCEPTIONS
# If exceptions are disabled, ensure our fatal exception
# hooks are preferentially linked over libstdc++ which
# has full exception support
COMPONENT_ADD_LDFLAGS += -u __cxx_fatal_exception
endif

View file

@ -0,0 +1,87 @@
#include <cstdlib>
#include <cstdio>
#include <exception>
#include <bits/functexcept.h>
#include <sdkconfig.h>
#ifndef CONFIG_CXX_EXCEPTIONS
const char *FATAL_EXCEPTION = "Fatal C++ exception: ";
extern "C" void __cxx_fatal_exception(void)
{
abort();
}
extern "C" void __cxx_fatal_exception_message(const char *msg)
{
printf("%s%s\n", FATAL_EXCEPTION, msg);
abort();
}
extern "C" void __cxx_fatal_exception_int(int i)
{
printf("%s (%d)\n", FATAL_EXCEPTION, i);
abort();
}
void std::__throw_bad_exception(void) __attribute__((alias("__cxx_fatal_exception")));
void std::__throw_bad_alloc(void) __attribute__((alias("__cxx_fatal_exception")));
void std::__throw_bad_cast(void) __attribute__((alias("__cxx_fatal_exception")));
void std::__throw_bad_typeid(void) __attribute__((alias("__cxx_fatal_exception")));
void std::__throw_logic_error(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_domain_error(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_invalid_argument(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_length_error(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_out_of_range(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_out_of_range_fmt(const char*, ...) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_runtime_error(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_range_error(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_overflow_error(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_underflow_error(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_ios_failure(const char*) __attribute__((alias("__cxx_fatal_exception_message")));
void std::__throw_system_error(int) __attribute__((alias("__cxx_fatal_exception_int")));
void std::__throw_bad_function_call(void) __attribute__((alias("__cxx_fatal_exception")));
void std::__throw_future_error(int) __attribute__((alias("__cxx_fatal_exception_int")));
/* The following definitions are needed because libstdc++ is also compiled with
__throw_exception_again defined to throw, and some other exception code in a few places.
This cause exception handler code to be emitted in the library even though it's mostly
unreachable (as any libstdc++ "throw" will first call one of the above stubs, which will abort).
If these are left out, a bunch of unwanted exception handler code is linked.
Note: these function prototypes are not correct.
*/
extern "C" void __cxa_allocate_exception(void) __attribute__((alias("__cxx_fatal_exception")));
extern "C" void __cxa_begin_catch(void) __attribute__((alias("__cxx_fatal_exception")));
extern "C" void __cxa_end_catch(void) __attribute__((alias("__cxx_fatal_exception")));
extern "C" void __cxa_get_exception_ptr(void) __attribute__((alias("__cxx_fatal_exception")));
extern "C" void __cxa_free_exception(void) __attribute__((alias("__cxx_fatal_exception")));
extern "C" void __cxa_rethrow(void) __attribute__((alias("__cxx_fatal_exception")));
extern "C" void __cxa_throw(void) __attribute__((alias("__cxx_fatal_exception")));
extern "C" void __cxa_call_terminate(void) __attribute__((alias("__cxx_fatal_exception")));
bool std::uncaught_exception() __attribute__((alias("__cxx_fatal_exception")));
#endif // CONFIG_CXX_EXCEPTIONS

View file

@ -92,7 +92,6 @@ public:
static SlowInit slowinit(taskId);
ESP_LOGD(TAG, "obj=%d after static init, task=%d\n", obj, taskId);
xSemaphoreGive(s_slow_init_sem);
vTaskDelay(10);
vTaskDelete(NULL);
}
private:
@ -133,6 +132,8 @@ TEST_CASE("static initialization guards work as expected", "[cxx]")
TEST_ASSERT_TRUE(xSemaphoreTake(s_slow_init_sem, 500/portTICK_PERIOD_MS));
}
vSemaphoreDelete(s_slow_init_sem);
vTaskDelay(10); // Allow tasks to clean up, avoids race with leak detector
}
struct GlobalInitTest
@ -187,6 +188,27 @@ TEST_CASE("before scheduler has started, static initializers work correctly", "[
TEST_ASSERT_EQUAL(2, StaticInitTestBeforeScheduler::order);
}
#ifdef CONFIG_CXX_EXCEPTIONS
TEST_CASE("c++ exceptions work", "[cxx]")
{
/* Note: This test currently trips the memory leak threshold
as libunwind allocates ~4KB of data on first exception. */
int thrown_value;
try
{
throw 20;
}
catch (int e)
{
thrown_value = e;
}
TEST_ASSERT_EQUAL(20, thrown_value);
printf("OK?\n");
}
#endif
/* These test cases pull a lot of code from libstdc++ and are disabled for now
*/
#if 0

View file

@ -379,8 +379,10 @@ void start_cpu1_default(void)
static void do_global_ctors(void)
{
#ifdef CONFIG_CXX_EXCEPTIONS
static struct object ob;
__register_frame_info( __eh_frame, &ob );
#endif
void (**p)(void);
for (p = &__init_array_end - 1; p >= &__init_array_start; --p) {

View file

@ -114,11 +114,8 @@ config FREERTOS_THREAD_LOCAL_STORAGE_POINTERS
FreeRTOS has the ability to store per-thread pointers in the task
control block. This controls the number of pointers available.
Value 0 turns off this functionality.
If using the LWIP TCP/IP stack (with WiFi or Ethernet), this value must be at least 1. See the
LWIP_THREAD_LOCAL_STORAGE_INDEX config item in LWIP configuration to determine which thread-local-storage
pointer is reserved for LWIP.
This value must be at least 1. Index 0 is reserved for use by the pthreads API
thread-local-storage. Other indexes can be used for any desired purpose.
choice FREERTOS_ASSERT
prompt "FreeRTOS assertions"

View file

@ -3577,17 +3577,16 @@ static void prvCheckTasksWaitingTermination( void )
/* ucTasksDeleted is used to prevent vTaskSuspendAll() being called
too often in the idle task. */
taskENTER_CRITICAL(&xTaskQueueMutex);
while( uxTasksDeleted > ( UBaseType_t ) 0U )
while(uxTasksDeleted > ( UBaseType_t ) 0U )
{
TCB_t *pxTCB = NULL;
taskENTER_CRITICAL(&xTaskQueueMutex);
{
xListIsEmpty = listLIST_IS_EMPTY( &xTasksWaitingTermination );
}
if( xListIsEmpty == pdFALSE )
{
TCB_t *pxTCB;
{
pxTCB = ( TCB_t * ) listGET_OWNER_OF_HEAD_ENTRY( ( &xTasksWaitingTermination ) );
/* We only want to kill tasks that ran on this core because e.g. _xt_coproc_release needs to
@ -3598,30 +3597,31 @@ static void prvCheckTasksWaitingTermination( void )
--uxTasksDeleted;
} else {
/* Need to wait until the idle task on the other processor kills that task first. */
taskEXIT_CRITICAL(&xTaskQueueMutex);
break;
}
}
}
taskEXIT_CRITICAL(&xTaskQueueMutex);
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
if (pxTCB != NULL) {
#if ( configNUM_THREAD_LOCAL_STORAGE_POINTERS > 0 ) && ( configTHREAD_LOCAL_STORAGE_DELETE_CALLBACKS )
int x;
for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
{
int x;
for( x = 0; x < ( UBaseType_t ) configNUM_THREAD_LOCAL_STORAGE_POINTERS; x++ )
if (pxTCB->pvThreadLocalStoragePointersDelCallback[ x ] != NULL)
{
if (pxTCB->pvThreadLocalStoragePointersDelCallback[ x ] != NULL)
{
pxTCB->pvThreadLocalStoragePointersDelCallback[ x ](x, pxTCB->pvThreadLocalStoragePointers[ x ]);
}
pxTCB->pvThreadLocalStoragePointersDelCallback[ x ](x, pxTCB->pvThreadLocalStoragePointers[ x ]);
}
}
#endif
prvDeleteTCB( pxTCB );
#endif
prvDeleteTCB( pxTCB );
}
else
{
mtCOVERAGE_TEST_MARKER();
}
}
taskEXIT_CRITICAL(&xTaskQueueMutex);
}
#endif /* vTaskDelete */
}

View file

@ -26,13 +26,6 @@ config LWIP_MAX_SOCKETS
the maximum amount of sockets here. The valid value is from 1
to 16.
config LWIP_THREAD_LOCAL_STORAGE_INDEX
int "Index for thread-local-storage pointer for lwip"
default 0
help
Specify the thread-local-storage-pointer index for lwip
use.
config LWIP_SO_REUSE
bool "Enable SO_REUSEADDR option"
default y

View file

@ -32,6 +32,7 @@
/* lwIP includes. */
#include <pthread.h>
#include "lwip/debug.h"
#include "lwip/def.h"
#include "lwip/sys.h"
@ -46,6 +47,9 @@
static sys_mutex_t g_lwip_protect_mutex = NULL;
static pthread_key_t sys_thread_sem_key;
static void sys_thread_sem_free(void* data);
#if !LWIP_COMPAT_MUTEX
/** Create a new mutex
* @param mutex pointer to the mutex to create
@ -433,6 +437,10 @@ sys_init(void)
if (ERR_OK != sys_mutex_new(&g_lwip_protect_mutex)) {
ESP_LOGE(TAG, "sys_init: failed to init lwip protect mutex\n");
}
// Create the pthreads key for the per-thread semaphore storage
pthread_key_create(&sys_thread_sem_key, sys_thread_sem_free);
esp_vfs_lwip_sockets_register();
}
@ -483,31 +491,31 @@ sys_arch_unprotect(sys_prot_t pval)
sys_mutex_unlock(&g_lwip_protect_mutex);
}
#define SYS_TLS_INDEX CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX
/*
/*
* get per thread semphore
*/
sys_sem_t* sys_thread_sem_get(void)
{
sys_sem_t *sem = (sys_sem_t*)pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), SYS_TLS_INDEX);
if (!sem){
sem = sys_thread_sem_init();
sys_sem_t *sem = pthread_getspecific(sys_thread_sem_key);
if (!sem) {
sem = sys_thread_sem_init();
}
LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem_get s=%p\n", sem));
return sem;
}
static void sys_thread_tls_free(int index, void* data)
static void sys_thread_sem_free(void* data) // destructor for TLS semaphore
{
sys_sem_t *sem = (sys_sem_t*)(data);
if (sem && *sem){
LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem del, i=%d sem=%p\n", index, *sem));
LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem del, sem=%p\n", *sem));
vSemaphoreDelete(*sem);
}
if (sem){
LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem pointer del, i=%d sem_p=%p\n", index, sem));
if (sem) {
LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem pointer del, sem_p=%p\n", sem));
free(sem);
}
}
@ -528,20 +536,17 @@ sys_sem_t* sys_thread_sem_init(void)
return 0;
}
LWIP_DEBUGF(ESP_THREAD_SAFE_DEBUG, ("sem init sem_p=%p sem=%p cb=%p\n", sem, *sem, sys_thread_tls_free));
vTaskSetThreadLocalStoragePointerAndDelCallback(xTaskGetCurrentTaskHandle(), SYS_TLS_INDEX, sem, (TlsDeleteCallbackFunction_t)sys_thread_tls_free);
pthread_setspecific(sys_thread_sem_key, sem);
return sem;
}
void sys_thread_sem_deinit(void)
{
sys_sem_t *sem = (sys_sem_t*)pvTaskGetThreadLocalStoragePointer(xTaskGetCurrentTaskHandle(), SYS_TLS_INDEX);
sys_thread_tls_free(SYS_TLS_INDEX, (void*)sem);
vTaskSetThreadLocalStoragePointerAndDelCallback(xTaskGetCurrentTaskHandle(), SYS_TLS_INDEX, 0, 0);
return;
sys_sem_t *sem = pthread_getspecific(sys_thread_sem_key);
if (sem != NULL) {
sys_thread_sem_free(sem);
pthread_setspecific(sys_thread_sem_key, NULL);
}
}
void sys_delay_ms(uint32_t ms)

View file

@ -28,6 +28,8 @@
#include "freertos/semphr.h"
#include "freertos/list.h"
#include "pthread_internal.h"
#define LOG_LOCAL_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include "esp_log.h"
const static char *TAG = "esp_pthread";
@ -145,6 +147,10 @@ static void pthread_task_func(void *arg)
ESP_LOGV(TAG, "%s END %p", __FUNCTION__, task_arg->func);
free(task_arg);
/* preemptively clean up thread local storage, rather than
waiting for the idle task to clean up the thread */
pthread_internal_local_storage_destructor_callback();
if (xSemaphoreTake(s_threads_mux, portMAX_DELAY) != pdTRUE) {
assert(false && "Failed to lock threads list!");
}
@ -332,40 +338,6 @@ int pthread_equal(pthread_t t1, pthread_t t2)
return t1 == t2 ? 1 : 0;
}
/***************** KEY ******************/
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*))
{
static int s_created;
//TODO: Key destructors not suppoted!
if (s_created) {
// key API supports just one key necessary by libstdcxx threading implementation
ESP_LOGE(TAG, "%s: multiple keys not supported!", __FUNCTION__);
return ENOSYS;
}
*key = 1;
s_created = 1;
return 0;
}
int pthread_key_delete(pthread_key_t key)
{
ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__);
return ENOSYS;
}
void *pthread_getspecific(pthread_key_t key)
{
ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__);
return NULL;
}
int pthread_setspecific(pthread_key_t key, const void *value)
{
ESP_LOGE(TAG, "%s: not supported!", __FUNCTION__);
return ENOSYS;
}
/***************** ONCE ******************/
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void))
{

View file

@ -0,0 +1,16 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed 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.
#pragma once
void pthread_internal_local_storage_destructor_callback();

View file

@ -0,0 +1,226 @@
// Copyright 2017 Espressif Systems (Shanghai) PTE LTD
//
// Licensed 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.
#include <errno.h>
#include <pthread.h>
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sys/lock.h"
#include "rom/queue.h"
#include "pthread_internal.h"
#define PTHREAD_TLS_INDEX 0
typedef void (*pthread_destructor_t)(void*);
/* This is a very naive implementation of key-indexed thread local storage, using two linked lists
(one is a global list of registered keys, one per thread for thread local storage values).
It won't work well if lots of keys & thread-local values are stored (O(n) lookup for both),
but it should work for small amounts of data.
*/
typedef struct key_entry_t_ {
pthread_key_t key;
pthread_destructor_t destructor;
SLIST_ENTRY(key_entry_t_) next;
} key_entry_t;
// List of all keys created with pthread_key_create()
SLIST_HEAD(key_list_t, key_entry_t_) s_keys = SLIST_HEAD_INITIALIZER(s_keys);
static _lock_t s_keys_lock;
// List of all value entries associated with a thread via pthread_setspecific()
typedef struct value_entry_t_ {
pthread_key_t key;
void *value;
SLIST_ENTRY(value_entry_t_) next;
} value_entry_t;
// Type for the head of the list, as saved as a FreeRTOS thread local storage pointer
SLIST_HEAD(values_list_t_, value_entry_t_);
typedef struct values_list_t_ values_list_t;
int pthread_key_create(pthread_key_t *key, pthread_destructor_t destructor)
{
key_entry_t *new_key = malloc(sizeof(key_entry_t));
if (new_key == NULL) {
return ENOMEM;
}
_lock_acquire_recursive(&s_keys_lock);
const key_entry_t *head = SLIST_FIRST(&s_keys);
new_key->key = (head == NULL) ? 1 : (head->key + 1);
new_key->destructor = destructor;
*key = new_key->key;
SLIST_INSERT_HEAD(&s_keys, new_key, next);
_lock_release_recursive(&s_keys_lock);
return 0;
}
static key_entry_t *find_key(pthread_key_t key)
{
_lock_acquire_recursive(&s_keys_lock);
key_entry_t *result = NULL;;
SLIST_FOREACH(result, &s_keys, next) {
if(result->key == key) {
break;
}
}
_lock_release_recursive(&s_keys_lock);
return result;
}
int pthread_key_delete(pthread_key_t key)
{
_lock_acquire_recursive(&s_keys_lock);
/* Ideally, we would also walk all tasks' thread local storage value_list here
and delete any values associated with this key. We do not do this...
*/
key_entry_t *entry = find_key(key);
if (entry != NULL) {
SLIST_REMOVE(&s_keys, entry, key_entry_t_, next);
free(entry);
}
_lock_release_recursive(&s_keys_lock);
return 0;
}
/* Clean up callback for deleted tasks.
This is called from one of two places:
If the thread was created via pthread_create() then it's called by pthread_task_func() when that thread ends,
and the FreeRTOS thread-local-storage is removed before the FreeRTOS task is deleted.
For other tasks, this is called when the FreeRTOS idle task performs its task cleanup after the task is deleted.
(The reason for calling it early for pthreads is to keep the timing consistent with "normal" pthreads, so after
pthread_join() the task's destructors have all been called even if the idle task hasn't run cleanup yet.)
*/
static void pthread_local_storage_thread_deleted_callback(int index, void *v_tls)
{
values_list_t *tls = (values_list_t *)v_tls;
assert(tls != NULL);
/* Walk the list, freeing all entries and calling destructors if they are registered */
value_entry_t *entry = SLIST_FIRST(tls);
while(entry != NULL) {
// This is a little slow, walking the linked list of keys once per value,
// but assumes that the thread's value list will have less entries
// than the keys list
key_entry_t *key = find_key(entry->key);
if (key != NULL && key->destructor != NULL) {
key->destructor(entry->value);
}
value_entry_t *next_entry = SLIST_NEXT(entry, next);
free(entry);
entry = next_entry;
}
free(tls);
}
/* this function called from pthread_task_func for "early" cleanup of TLS in a pthread */
void pthread_internal_local_storage_destructor_callback()
{
void *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
if (tls != NULL) {
pthread_local_storage_thread_deleted_callback(PTHREAD_TLS_INDEX, tls);
/* remove the thread-local-storage pointer to avoid the idle task cleanup
calling it again...
*/
vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
PTHREAD_TLS_INDEX,
NULL,
NULL);
}
}
static value_entry_t *find_value(const values_list_t *list, pthread_key_t key)
{
value_entry_t *result = NULL;;
SLIST_FOREACH(result, list, next) {
if(result->key == key) {
break;
}
}
return result;
}
void *pthread_getspecific(pthread_key_t key)
{
values_list_t *tls = (values_list_t *) pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
if (tls == NULL) {
return NULL;
}
value_entry_t *entry = find_value(tls, key);
if(entry != NULL) {
return entry->value;
}
return NULL;
}
int pthread_setspecific(pthread_key_t key, const void *value)
{
key_entry_t *key_entry = find_key(key);
if (key_entry == NULL) {
return ENOENT; // this situation is undefined by pthreads standard
}
values_list_t *tls = pvTaskGetThreadLocalStoragePointer(NULL, PTHREAD_TLS_INDEX);
if (tls == NULL) {
tls = calloc(1, sizeof(values_list_t));
if (tls == NULL) {
return ENOMEM;
}
vTaskSetThreadLocalStoragePointerAndDelCallback(NULL,
PTHREAD_TLS_INDEX,
tls,
pthread_local_storage_thread_deleted_callback);
}
value_entry_t *entry = find_value(tls, key);
if (entry != NULL) {
if (value != NULL) {
// cast on next line is necessary as pthreads API uses
// 'const void *' here but elsewhere uses 'void *'
entry->value = (void *) value;
} else { // value == NULL, remove the entry
SLIST_REMOVE(tls, entry, value_entry_t_, next);
free(entry);
}
} else if (value != NULL) {
entry = malloc(sizeof(value_entry_t));
if (entry == NULL) {
return ENOMEM;
}
entry->key = key;
entry->value = (void *) value; // see note above about cast
SLIST_INSERT_HEAD(tls, entry, next);
}
return 0;
}

View file

@ -85,6 +85,8 @@ TEST_CASE("pthread C++", "[pthread]")
std::cout << "Join thread " << std::hex << t4.get_id() << std::endl;
t4.join();
}
global_sp.reset(); // avoid reported leak
}
static void task_test_sandbox(void *arg)

View file

@ -0,0 +1,130 @@
// Test pthread_create_key, pthread_delete_key, pthread_setspecific, pthread_getspecific
#include <pthread.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
TEST_CASE("pthread local storage basics", "[pthread]")
{
pthread_key_t key;
TEST_ASSERT_EQUAL(0, pthread_key_create(&key, NULL));
TEST_ASSERT_NULL(pthread_getspecific(key));
int val = 3;
printf("Setting to %p...\n", &val);
TEST_ASSERT_EQUAL(0, pthread_setspecific(key, &val));
printf("Reading back...\n");
TEST_ASSERT_EQUAL_PTR(&val, pthread_getspecific(key));
printf("Setting to NULL...\n");
TEST_ASSERT_EQUAL(0, pthread_setspecific(key, NULL));
printf("Reading back...\n");
TEST_ASSERT_NULL(pthread_getspecific(key));
TEST_ASSERT_EQUAL(0, pthread_key_delete(key));
}
TEST_CASE("pthread local storage unique keys", "[pthread]")
{
const int NUM_KEYS = 10;
pthread_key_t keys[NUM_KEYS];
for (int i = 0; i < NUM_KEYS; i++) {
TEST_ASSERT_EQUAL(0, pthread_key_create(&keys[i], NULL));
printf("New key %d = %d\n", i, keys[i]);
}
for (int i = 0; i < NUM_KEYS; i++) {
for (int j = 0; j < NUM_KEYS; j++) {
if (i != j) {
TEST_ASSERT_NOT_EQUAL(keys[i], keys[j]);
}
}
}
for (int i = 0; i < NUM_KEYS; i++) {
TEST_ASSERT_EQUAL(0, pthread_key_delete(keys[i]));
}
}
static void test_pthread_destructor(void *);
static void *expected_destructor_ptr;
static void *actual_destructor_ptr;
static void *thread_test_pthread_destructor(void *);
TEST_CASE("pthread local storage destructor", "[pthread]")
{
pthread_t thread;
pthread_key_t key = -1;
expected_destructor_ptr = NULL;
actual_destructor_ptr = NULL;
TEST_ASSERT_EQUAL(0, pthread_key_create(&key, test_pthread_destructor));
TEST_ASSERT_EQUAL(0, pthread_create(&thread, NULL, thread_test_pthread_destructor, (void *)key));
TEST_ASSERT_EQUAL(0, pthread_join(thread, NULL));
printf("Joined...\n");
TEST_ASSERT_NOT_NULL(expected_destructor_ptr);
TEST_ASSERT_NOT_NULL(actual_destructor_ptr);
TEST_ASSERT_EQUAL_PTR(expected_destructor_ptr, actual_destructor_ptr);
TEST_ASSERT_EQUAL(0, pthread_key_delete(key));
}
static void task_test_pthread_destructor(void *v_key);
TEST_CASE("pthread local storage destructor in FreeRTOS task", "[pthread]")
{
// Same as previous test case, but doesn't use pthread APIs therefore must wait
// for the idle task to call the destructor
pthread_key_t key = -1;
expected_destructor_ptr = NULL;
actual_destructor_ptr = NULL;
TEST_ASSERT_EQUAL(0, pthread_key_create(&key, test_pthread_destructor));
xTaskCreate(task_test_pthread_destructor,
"ptdest", 8192, (void *)key, UNITY_FREERTOS_PRIORITY+1,
NULL);
// Above task has higher priority to us, so should run immediately
// but we need to wait for the idle task cleanup to run
vTaskDelay(20);
TEST_ASSERT_NOT_NULL(expected_destructor_ptr);
TEST_ASSERT_NOT_NULL(actual_destructor_ptr);
TEST_ASSERT_EQUAL_PTR(expected_destructor_ptr, actual_destructor_ptr);
TEST_ASSERT_EQUAL(0, pthread_key_delete(key));
}
static void *thread_test_pthread_destructor(void *v_key)
{
printf("Local storage thread running...\n");
pthread_key_t key = (pthread_key_t) v_key;
expected_destructor_ptr = &key; // address of stack variable in the task...
pthread_setspecific(key, expected_destructor_ptr);
printf("Local storage thread done.\n");
return NULL;
}
static void test_pthread_destructor(void *value)
{
printf("Destructor called...\n");
actual_destructor_ptr = value;
}
static void task_test_pthread_destructor(void *v_key)
{
/* call the pthread main routine, then delete ourselves... */
thread_test_pthread_destructor(v_key);
vTaskDelete(NULL);
}

View file

@ -340,6 +340,11 @@ defined to call ``vTaskSetThreadLocalStoragePointerAndDelCallback()`` with a
``NULL`` pointer as the deletion call back. This results in the selected Thread
Local Storage Pointer to have no deletion call back.
In IDF the FreeRTOS thread local storage at index 0 is reserved and is used to implement
the pthreads API thread local storage (pthread_getspecific() & pthread_setspecific()).
Other indexes can be used for any purpose, provided
:ref:`CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS` is set to a high enough value.
For more details see :component_file:`freertos/include/freertos/task.h`
.. _esp-idf-freertos-configuration:

View file

@ -56,7 +56,7 @@ idf_monitor will augment the dump::
Behind the scenes, the command idf_monitor runs to decode each address is::
xtensa-esp32-elf-addr2line -pfia -e build/PROJECT.elf ADDRESS
xtensa-esp32-elf-addr2line -pfiaC -e build/PROJECT.elf ADDRESS
Launch GDB for GDBStub

View file

@ -303,6 +303,12 @@ CXXFLAGS := $(strip \
$(CXXFLAGS) \
$(EXTRA_CXXFLAGS))
ifdef CONFIG_CXX_EXCEPTIONS
CXXFLAGS += -fexceptions
else
CXXFLAGS += -fno-exceptions
endif
export CFLAGS CPPFLAGS CXXFLAGS
# Set host compiler and binutils

View file

@ -395,7 +395,7 @@ class Monitor(object):
def lookup_pc_address(self, pc_addr):
translation = subprocess.check_output(
["%saddr2line" % self.toolchain_prefix,
"-pfia", "-e", self.elf_file, pc_addr],
"-pfiaC", "-e", self.elf_file, pc_addr],
cwd=".")
if not "?? ??:0" in translation:
yellow_print(translation)

View file

@ -10,6 +10,7 @@
#include "esp_log.h"
#include "soc/cpu.h"
#include "esp_heap_caps.h"
#include "esp_heap_trace.h"
#include "test_utils.h"
#define unity_printf ets_printf
@ -37,11 +38,26 @@ const size_t CRITICAL_LEAK_THRESHOLD = 4096;
/* setUp runs before every test */
void setUp(void)
{
// If heap tracing is enabled in kconfig, leak trace the test
#ifdef CONFIG_HEAP_TRACING
const size_t num_heap_records = 80;
static heap_trace_record_t *record_buffer;
if (!record_buffer) {
record_buffer = malloc(sizeof(heap_trace_record_t) * num_heap_records);
assert(record_buffer);
heap_trace_init_standalone(record_buffer, num_heap_records);
}
#endif
printf("%s", ""); /* sneakily lazy-allocate the reent structure for this test task */
get_test_data_partition(); /* allocate persistent partition table structures */
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
#ifdef CONFIG_HEAP_TRACING
heap_trace_start(HEAP_TRACE_LEAKS);
#endif
}
static void check_leak(size_t before_free, size_t after_free, const char *type)
@ -59,7 +75,7 @@ static void check_leak(size_t before_free, size_t after_free, const char *type)
leaked < CRITICAL_LEAK_THRESHOLD ? "potential" : "critical",
before_free, after_free, leaked);
fflush(stdout);
TEST_ASSERT(leaked < CRITICAL_LEAK_THRESHOLD); /* fail the test if it leaks too much */
TEST_ASSERT_MESSAGE(leaked < CRITICAL_LEAK_THRESHOLD, "The test leaked too much memory");
}
/* tearDown runs after every test */
@ -68,15 +84,25 @@ void tearDown(void)
/* some FreeRTOS stuff is cleaned up by idle task */
vTaskDelay(5);
/* We want the teardown to have this file in the printout if TEST_ASSERT fails */
const char *real_testfile = Unity.TestFile;
Unity.TestFile = __FILE__;
/* check if unit test has caused heap corruption in any heap */
TEST_ASSERT( heap_caps_check_integrity(MALLOC_CAP_INVALID, true) );
TEST_ASSERT_MESSAGE( heap_caps_check_integrity(MALLOC_CAP_INVALID, true), "The test has corrupted the heap");
/* check for leaks */
#ifdef CONFIG_HEAP_TRACING
heap_trace_stop();
heap_trace_dump();
#endif
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
Unity.TestFile = real_testfile; // go back to the real filename
}
void unity_putc(int c)

View file

@ -91,6 +91,7 @@ CONFIG_OPTIMIZATION_LEVEL_RELEASE=
CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
CONFIG_OPTIMIZATION_ASSERTIONS_SILENT=
CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED=
CONFIG_CXX_EXCEPTIONS=
#
# Component config
@ -277,8 +278,7 @@ CONFIG_FREERTOS_DEBUG_INTERNALS=
CONFIG_HEAP_POISONING_DISABLED=
CONFIG_HEAP_POISONING_LIGHT=
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_HEAP_TRACING=y
CONFIG_HEAP_TRACING_STACK_DEPTH=2
CONFIG_HEAP_TRACING=
#
# libsodium
@ -302,7 +302,6 @@ CONFIG_LOG_COLORS=y
#
CONFIG_L2_TO_L3_COPY=
CONFIG_LWIP_MAX_SOCKETS=4
CONFIG_LWIP_THREAD_LOCAL_STORAGE_INDEX=0
CONFIG_LWIP_SO_REUSE=
CONFIG_LWIP_SO_RCVBUF=
CONFIG_LWIP_DHCP_MAX_NTP_SERVERS=1