[system]: Made longjmp save for context switch

* Patched longjmp to be context-switch safe
  longjmp modifies the windowbase and windowstart
  registers, which isn't safe if a context switch
  occurs during the modification. After a context
  switch, windowstart and windowbase will be
  different, leading to a wrongly set windowstart
  bit due to longjmp writing it based on the
  windowbase before the context switch. This
  corrupts the registers at the next window
  overflow reaching that wrongly set bit.

  The solution is to disable interrupts during
  this code. It is only 6 instructions long,
  the impact shouldn't be significant.

  The fix is implemented as a wrapper which
  replaces the original first instructions of
  longjmp which are buggy. Then, it jumps back
  to execute the rest of the original longjmp
  function.

  Added a comparably reliable test to the
  test apps.
This commit is contained in:
Jakob Hasse
2021-04-08 12:16:17 +08:00
parent 2e64d234c4
commit a37c20b417
13 changed files with 269 additions and 4 deletions

View File

@@ -0,0 +1,6 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(hello-world)

View File

@@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := longjmp_test
include $(IDF_PATH)/make/project.mk

View File

@@ -0,0 +1,13 @@
| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 |
| ----------------- | ----- | -------- | -------- |
# Building
Example building for ESP32:
```
idf.py set-target esp32
cp sdkconfig.defaults sdkconfig
idf.py build
```
# Running
All the setup needs to be done as described in the [test apps README](../../README.md).

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env python
import ttfw_idf
from tiny_test_fw import Utility
@ttfw_idf.idf_custom_test(env_tag='Example_GENERIC', target=['esp32', 'esp32s2'], group='test-apps')
def test_longjmp(env, _):
dut = env.get_dut('longjmp_test', 'tools/test_apps/system/longjmp_test')
dut.start_app()
dut.expect('Test successful', 15)
Utility.console_log('longjmp test done.')
if __name__ == '__main__':
test_longjmp()

View File

@@ -0,0 +1,2 @@
idf_component_register(SRCS "hello_world_main.c"
INCLUDE_DIRS "")

View File

@@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@@ -0,0 +1,118 @@
/* test longjmp
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <stdio.h>
#include "sdkconfig.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_spi_flash.h"
#include <esp_task.h>
#include <setjmp.h>
#define LUAI_NOIPA __attribute__((__noipa__))
#define LUAI_THROW(c) longjmp((c)->b, 1)
#define LUAI_TRY(c,a) if (setjmp((c)->b) == 0) { a }
#define TIMEOUT 50
#define RECURSION 19
static esp_timer_handle_t crash_timer;
static uint32_t result = 0;
uint32_t calc_fac(uint32_t n) {
if (n == 1 || n == 0) {
return 1;
} else {
return n * calc_fac(n - 1);
}
}
static void timer_cb(void *arg) {
result = calc_fac(RECURSION);
}
typedef struct {
jmp_buf b;
} jmp_ctx;
LUAI_NOIPA
static void pret(jmp_ctx *jc) {
LUAI_THROW(jc);
}
LUAI_NOIPA
static void precurse(jmp_ctx *jc, int n) {
if (n) precurse(jc, n - 1);
else pret(jc);
}
LUAI_NOIPA
static void ptest(jmp_ctx *jc) {
precurse(jc, 64);
}
LUAI_NOIPA
void pcall(void (*func)(jmp_ctx *ctx)) {
jmp_ctx jc;
LUAI_TRY(&jc,
ptest(&jc);
);
}
static void sjlj_task(void *ctx) {
uint32_t start = xTaskGetTickCount();
for (;;) {
pcall(ptest);
uint32_t end = xTaskGetTickCount();
uint32_t dt = end - start;
if (dt >= 1000) {
start = end;
printf("[%u] sjlj tick %d\n", end, (int)ctx);
}
if (end > 9800) {
break;
}
}
vTaskDelete(NULL);
}
void app_main(void)
{
const esp_timer_create_args_t timer_args = {
timer_cb,
NULL,
ESP_TIMER_TASK,
"crash_timer",
true,
};
esp_timer_create(&timer_args, &crash_timer);
esp_timer_start_periodic(crash_timer, TIMEOUT);
printf("Hello world!\n");
printf("Free heap: %d\n", esp_get_free_heap_size());
for (size_t i = 0; i < 16; i++) {
xTaskCreate(sjlj_task, "sjlj_task", 4096, (void *) i, tskIDLE_PRIORITY + 0, NULL);
}
vTaskDelay(10000);
printf("stopping timers...\n");
esp_timer_stop(crash_timer);
esp_timer_delete(crash_timer);
printf("Test successful\n");
}

View File

@@ -0,0 +1,11 @@
CONFIG_COMPILER_OPTIMIZATION_PERF=y
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y
CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584
CONFIG_FREERTOS_UNICORE=y
CONFIG_FREERTOS_HZ=1000