Runtime null pointer dereference/memory corruption issue

I have this code right here:

#include <esp_event.h>
#include <freertos/task.h>
#include <freertos/FreeRTOS.h>
#include <freertos/FreeRTOSConfig.h>
#include <Arduino.h>
#include <Wire.h>
#include <esp_now.h>
#include <WiFi.h>
#include <cstring>
#include <esp_pm.h>
#include <esp32-hal-log.h>
#include <esp32-hal-adc.h>

#ifdef DEBUG
#include <esp_gdbstub.h>
#endif

#ifdef CONFIG_ESP_SYSTEM_PANIC_HALT
#pragma message("using CONFIG_ESP_SYSTEM_PANIC_HALT option")
#endif
#ifdef CONFIG_ESP_SYSTEM_PANIC_REBOOT
#pragma message("using CONFIG_ESP_SYSTEM_PANIC_REBOOT option")
#endif

#define TEST
#pragma message("Building TEST mode")

// TODO: add actual pin number
#define LED_PIN 2
#define VIBRATION_SENSOR_PIN 3
#define IF_SENSOR_PIN 4
#define QUEUE_LEN 64

char buf[100] = {};
const uint8_t *data = (const uint8_t*)"123";

const esp_now_peer_info_t server_info = {
    .peer_addr = {0xF8, 0xB3, 0xB7, 0x45, 0x4E, 0xAC},
    .lmk = {},
    .channel = 1,
    .ifidx = WIFI_IF_STA,
    .encrypt = false,
    .priv = NULL
};

bool should_update_server = false;
bool goal = false;
QueueHandle_t table_queue = NULL, goal_queue = NULL;

void send_callback(const uint8_t *mac_addr, esp_now_send_status_t status) {
    should_update_server = status != esp_now_send_status_t::ESP_NOW_SEND_SUCCESS;
    log_i("should_update_server [%p]: %d\n", &should_update_server, should_update_server);
}

void recv_callback(const uint8_t *mac_addr, const uint8_t *data, int len) {
    std::memcpy(buf, reinterpret_cast<const char*>(data), len);
    digitalWrite(18, HIGH);
    log_i("got data %s\n", buf);
}

void listen_goal(void *param) {
    uint8_t res = 0;
    uint8_t a = 0;
    while (1) {
        log_i("queue: %p\n", table_queue);
        auto k = xQueueReceive(table_queue, &res, portMAX_DELAY);
        if (k) {
            log_i("received %c\n", res);
            auto start_cycle = cpu_ll_get_cycle_count();
            log_i("a: %p\n", &a);
            log_i("res: %p\n", &res);
            res = 0;
            // Try to read IF sensor for 100 microseconds.
            while (cpu_ll_get_cycle_count() - start_cycle < microsecondsToClockCycles(100)) {
                log_i("%u\n", cpu_ll_get_cycle_count() - start_cycle);
                if (digitalRead(IF_SENSOR_PIN)) {
                    res = 1;
                    break;
                }
            }
            #ifdef TEST
            auto l = esp_random();
            res = (uint8_t)l & 1;
            log_i("received %d\n", res);
            #endif
            if (res) {
                char msg = 'G';
                auto x = xQueueSend(goal_queue, &msg, portMAX_DELAY);
                log_i("got %d\n", x);
            }
            log_i("Finished sending.\n");
        }
        log_i("Finished.\n");
        log_i("stack bytes remaining: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
        vTaskDelay(1);
    }
}

void listen_table(void *param) {
    const char msg = 'T';
    uint8_t res;
    while (1) {
        res = 0;
        // Try to read vibration sensor for 100 microseconds.
        auto start_cycle = cpu_ll_get_cycle_count();
        while (cpu_ll_get_cycle_count() - start_cycle < microsecondsToClockCycles(100)) {
            log_i("%u\n", cpu_ll_get_cycle_count() - start_cycle);
            if (digitalRead(VIBRATION_SENSOR_PIN)) {
                res = 1;
                break;
            }
        }
        #ifdef TEST
        auto k = esp_random();
        res = (uint8_t)k & 1;
        #endif
        if (res) {
            log_i("queue: %p\n", table_queue);
            auto x = xQueueSend(table_queue, &msg, portMAX_DELAY);
            auto u = uxQueueSpacesAvailable(table_queue);
            log_i("got %d, space remaining: %u\n", x, u);
        }
        auto u =  uxQueueMessagesWaiting(table_queue);
        log_i("table: %u messages waiting.\n", u);
        u = uxQueueMessagesWaiting(goal_queue);
        log_i("goal: %u messages waiting.\n", u);
        log_i("stack bytes remaining: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
        vTaskDelay(1);
    }
}

void send_packet(void *param) {
    uint8_t res = 0;
    while (1) {
        uint8_t a = 0;
        log_i("%u messages waiting.\n", uxQueueMessagesWaiting(goal_queue));
        log_i("goal_queue: %p\n", goal_queue);
        if ((a = xQueuePeek(goal_queue, &res, portMAX_DELAY))) {
            esp_now_send(server_info.peer_addr, data, strlen("123"));
            #ifdef TEST
            log_i("a: %p\n", &a);
            log_i("res: %p\n", &res);
            log_i("data: %p\n", data);
            log_i("got %c\n", res);
            log_i("goal_queue: %p", goal_queue);
            auto u = uxQueueSpacesAvailable(goal_queue);
            log_i("%u goal queue bytes remaining\n", u);
            auto x = xQueueReceive(goal_queue, &res, 0);
            log_i("received data: %d\n", x);
            #endif
            if (!should_update_server) {
                // auto x = xQueueReceive(goal_queue, &res, 0);
            }
            log_i("Finished sending.\n");
        }
        log_i("a: %d, Finished.\n", a);
        log_i("stack bytes remaining: %u bytes\n", uxTaskGetStackHighWaterMark(NULL));
        vTaskDelay(1);
    }
}

void setup() {
    Serial.begin(115200);
    WiFi.mode(WIFI_STA);
    #ifdef DEBUG
    esp_gdbstub_init();
    #endif
    pinMode(IF_SENSOR_PIN, INPUT);
    pinMode(LED_PIN, OUTPUT);
    pinMode(VIBRATION_SENSOR_PIN, INPUT);
    ESP_ERROR_CHECK(esp_now_init());
    ESP_ERROR_CHECK(esp_now_add_peer(&server_info));
    ESP_ERROR_CHECK(esp_now_register_send_cb(esp_now_send_cb_t(send_callback)));
    ESP_ERROR_CHECK(esp_now_register_recv_cb(esp_now_recv_cb_t(recv_callback)));
    #ifdef ENABLE_SLEEP
    ESP_ERROR_CHECK(esp_sleep_enable_wifi_wakeup());
    esp_pm_config_esp32_t pm_cfg;
    ESP_ERROR_CHECK(esp_pm_get_configuration(&pm_cfg));
    pm_cfg.light_sleep_enable = true;
    ESP_ERROR_CHECK(esp_pm_configure(&pm_cfg));
    #endif
    if (xTaskCreatePinnedToCore(
        listen_table,
        "listen_table",
        4096,
        NULL,
        0,
        NULL,
        1
    ) == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY) {
        log_e("Failed to allocate memory for %s task.\n", "listen_table");
        abort();
    };
    if (xTaskCreatePinnedToCore(
        listen_goal,
        "listen_goal",
        4096,
        NULL,
        0,
        NULL,
        0
    ) == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY) {
        log_e("Failed to allocate memory for %s task.\n", "listen_goal");
        abort();
    };
    if (xTaskCreatePinnedToCore(
        send_packet,
        "send_packet",
        4096,
        NULL,
        0,
        NULL,
        1
    ) == errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY) {
        log_e("Failed to allocate memory for %s task.\n", "send_packet");
        abort();
    };
    table_queue = xQueueCreate(QUEUE_LEN, (sizeof(char)));
    if (!table_queue) {
        log_e("Failed to allocate memory for table_queue.\n");
        abort();
    }
    goal_queue = xQueueCreate(QUEUE_LEN, (sizeof(char)));
    if (!goal_queue) {
        log_e("Failed to allocate memory for goal_queue.\n");
        abort();
    }
}

void loop() {
    // Delete unused loop task
    vTaskDelete(NULL);
}

From what I can see on the crash coredump, the code always store a garbage value on EXECVADDR (around 0x0) so I assume there is an invalid pointer dereference somewhere, but I can’t seem to find it since I allocate everything in global/local scope with no calls to malloc() and other memory allocation functions. The crash always happens when calling send_packet(). Can anyone point out the issues in my code? Thanks in advance.

I took a (very quick) look.
What I see that both tasks are using queues.
But the queues are created after the tasks where started.
What happens when you create the queues first, and after that starting the tasks?

I’m sorry for the late reply, It now works but the output still has some errors:

[ 97557][I][main.cpp:71] listen_goal)0:; 3r2ems[:  907x535f7f]c[aI9]2[0m
a[n0.mcp
p:127] listen_table(): stack bytes remaining: 2484 bytes

can you explain why this is the case? Is there an explicit order for doing things when using FreeRTOS features? Again, thank you for the help.

The output contains ANSI codes.

Use monitor_filters = direct

See

The output is garbled due to the behaviour of the log_i and other log_x functions. By design they are thread safe, meaning that you can have two tasks write a log at the same time, and the program will not crash. The side affect of this is that the output of both statements can be mixed up.

One way you can solve this is by using a binary semaphore to ensure that only one task may log at any given time. You can find the documentation from FreeRTOS here.

Thank you for the answer. I assumed that thread-safe implies “uninterruptible” but I suppose that is not the case.

I have a hypothesis that since the tasks are immediately executed after it is created, which then tries to access the queues which were not allocated before. Previously, I thought that the setup() function will return before the tasks starts executing, but I guess the scheduler does not wait for that. Thanks for everyone who participated in answering this issue.

It’s not a hypothesis, it’s exact this way - See post #2