Подозрение на глюк scanf() в newlib

Кто любит RISC в жизни, заходим, не стесняемся.
Ответить
Аватара пользователя
YS
Друг Кота
Сообщения: 7518
Зарегистрирован: Вс мар 29, 2009 22:09:05
Контактная информация:

Подозрение на глюк scanf() в newlib

Сообщение YS »

Коллеги, я столкнулся с ситуацией, которая позволяет мне предполагать, что реализация scanf() в newlib имеет, э-э-э, особенности.

Тестовое окружение:

Em::Blocks 2.10 / arm-gcc / newlib

STM32-vldiscovery / STM32F100RBT6

размер стека - 2 кБ.

Функция _read() реализована в двух вариантах:
Спойлер

Код: Выделить всё

#ifdef CORRECT_READ
int _read(int fd,void *buf, size_t count)
{
    size_t k;

    k=0;
    usart_byte_received=0; //This flag gets set in UART RX interrupt handler

    set_LED(LED_B_PC,1); //Blue LED on Discovery

    sprintf(debug_str,"_read() called with fd = %i, count = %i\r\n",fd,count);
    USART1_TransmitString(debug_str);

    while (k<count)
    {
        if (usart_byte_received)
        {
            ((uint8_t *)buf)[k]=usart_byte;
            k++;

            if (usart_byte==0x0D)
            {
                ((uint8_t *)buf)[k]=0x0A;
                k++;
                usart_byte_received=0;

                return k;
            }

            usart_byte_received=0;
        }
    }

    set_LED(LED_B_PC,0);

    return k;
}
#else
int _read(int fd,void *buf, size_t count)
{
    sprintf(debug_str,"_read() called with fd = %i, count = %i\r\n",fd,count);
    USART1_TransmitString(debug_str);

    while (!usart_byte_received);
    *((uint8_t*)buf)=usart_byte;
    usart_byte_received=0;

    return 1;
}
#endif
Описание проблемы:

попытка считать значения из UART по паттерну "%c%i%i" демонстрирует следующее поведение:

- первый вызов завершается успешно;
- второй вызов завершается после первого прочитанного байта, в процессе scanf(), по всей видимости, портит стек.

Полный код экспериментальной программы (одна из версий, в процессе детали менялись, но общая структура оставалась неизменной):

gpio_macros_f100.h
Спойлер

Код: Выделить всё

#ifndef GPIO_MACROS_F100_H_INCLUDED
#define GPIO_MACROS_F100_H_INCLUDED

//Pin types for input and output
#define IN_ANALOG           0x00
#define IN_FLOATING         0x01
#define IN_PULLED           0x02
#define OUT_PP              0x00
#define OUT_OD              0x01
#define OUT_AF_PP           0x02
#define OUT_AF_OD           0x03

//Pin modes
#define MODE_IN             0x00
#define MODE_OUT_2MHz       0x02
#define MODE_OUT_10MHz      0x01
#define MODE_OUT_50MHz      0x03

//Macros for GPIO_CRL and GPIO_CRH
#define PIN_CONFCRL(pin_no,pin_mode,pin_type)       ((((pin_type) << 2) | (pin_mode)) << ((pin_no)*4))
#define PIN_CONFCRH(pin_no,pin_mode,pin_type)       ((((pin_type) << 2) | (pin_mode)) << ((pin_no - 8)*4))

//Macros for GPIO_BSRR
#define BSRR_SET(pin_no)                            (1<<(pin_no))
#define BSRR_RESET(pin_no)                          ((1<<(pin_no))<<16)

#define PIN_MASK(pin_no)                            (1<<(pin_no))

#endif /* GPIO_MACROS_F100_H_INCLUDED */

main.c
Спойлер

Код: Выделить всё

#include "stm32f10x.h"
#include "stdio.h"
#include "gpio_macros_f100.h"

/**
    HSE:        8MHz
    CPU clock:  PLL, 24 MHz
    USART:      8N1, 921 kBaud or 115.2 kBaud
*/

#define USART_115200        1

//#define CORRECT_READ        1

#define USART1_TX_PA        9
#define USART1_RX_PA        10

#define LED_G_PC            9
#define LED_B_PC            8

volatile uint8_t usart_byte_received=0,usart_byte;
char debug_str[255];

void set_LED(uint8_t led,uint8_t state)
{
    if ((led==LED_B_PC) || (led==LED_G_PC))
    {
        if (state)
        {
            GPIOC->BSRR=PIN_MASK(led);
        }
        else
        {
            GPIOC->BSRR=PIN_MASK(led)<<16;
        }
    }
}

void delay_cycles(uint32_t cycles)
{
    volatile uint32_t k;

    for (k=0; k<cycles; k++)
    {
    }
}

void USART1_IRQHandler(void)
{
    volatile uint8_t b;

    b=USART1->DR;

    if (!usart_byte_received)
    {
        usart_byte=b;
        usart_byte_received=1;
    }
}

void USART1_TransmitByte(uint8_t byte)
{
    while (!(USART1->SR & USART_SR_TXE))
    {
    }

    USART1->DR=byte;
}

void USART1_TransmitBuffer(uint8_t* buffer,uint32_t len)
{
    volatile uint32_t k;

    for (k=0; k<len; k++)
    {
        USART1_TransmitByte(buffer[k]);
    }
}

void USART1_TransmitString(uint8_t* str)
{
    int16_t counter;

    counter=0;

    while ((str[counter]!=0) && (counter<255))
    {
        USART1_TransmitByte(str[counter]);
        counter++;
    }
}

int _write(int fd, const void *buf, size_t count)
{
    USART1_TransmitBuffer((uint8_t *)buf,count);

    return count;
}

#define CORRECT_READ    1

#ifdef CORRECT_READ
int _read(int fd,void *buf, size_t count)
{
    size_t k;

    k=0;
    usart_byte_received=0;

    set_LED(LED_B_PC,1);

    sprintf(debug_str,"_read() called with fd = %i, count = %i\r\n",fd,count);
    USART1_TransmitString(debug_str);

    while (k<count)
    {
        if (usart_byte_received)
        {
            ((uint8_t *)buf)[k]=usart_byte;
            k++;

            if (usart_byte==0x0D)
            {
                ((uint8_t *)buf)[k]=0x0A;
                k++;
                usart_byte_received=0;

                return k;
            }

            usart_byte_received=0;
        }
    }

    set_LED(LED_B_PC,0);

    return k;
}
#else
int _read(int fd,void *buf, size_t count)
{
    sprintf(debug_str,"_read() called with fd = %i, count = %i\r\n",fd,count);
    USART1_TransmitString(debug_str);

    while (!usart_byte_received);
    *((uint8_t*)buf)=usart_byte;
    usart_byte_received=0;

    return 1;
}
#endif

int main(void)
{
    int p1,p2,p3;
    char s[255];
    char c;
    int scanf_return_value;

    RCC->APB2ENR=RCC_APB2ENR_USART1EN | RCC_APB2ENR_IOPDEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPAEN | RCC_APB2ENR_AFIOEN;
    //Free JTAG pins, except those used for SWD
    AFIO->MAPR=AFIO_MAPR_SWJ_CFG_1;

    GPIOA->CRH=PIN_CONFCRH(USART1_TX_PA,MODE_OUT_10MHz,OUT_AF_PP) | PIN_CONFCRH(USART1_RX_PA,MODE_IN,IN_PULLED);
    GPIOA->ODR=PIN_MASK(USART1_RX_PA); //Enable pullup on USART RX pin

    GPIOC->CRH=PIN_CONFCRH(LED_G_PC,MODE_OUT_2MHz,OUT_PP) | PIN_CONFCRH(LED_B_PC,MODE_OUT_2MHz,OUT_PP);

    //USART1 setup
    USART1->CR1=USART_CR1_UE;
    USART1->CR2=0;
#ifdef USART_115200
    USART1->BRR=(13 << 4) | 0;
#else
    //k = 1.63 ~= 1 + (10/16)
    //baud = 921000
    USART1->BRR=(1 << 4) | 10;
#endif
    USART1->CR1|=USART_CR1_TE | USART_CR1_RE | USART_CR1_RXNEIE;
    NVIC_EnableIRQ(USART1_IRQn);

    set_LED(LED_G_PC,1);

    while (1)
    {
        printf("Enter data, char/int/int: \r\n");
        scanf_return_value=scanf("%c%i%i",&c,&p1,&p2);

        if (scanf_return_value<3)
        {
            printf("Weird scanf()! Returned %i \r\n",scanf_return_value);
        }
        else
        {
            printf("scanf() returned %i. Values: %c,%i,%i\r\n",scanf_return_value,c,p1,p2);
        }
        delay_cycles(500000);
    }
}
Отладочный вывод:

- с использованием второй версии _read(), возвращающей по байту за раз. Видно, что второй вызов scanf() вылетает после первого принятого байта.

Код: Выделить всё

Enter data, char/int/int:
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
scanf() returned 3. Values: a,12,34
Enter data, char/int/int:
_read() called with fd = 0, count = 1024
Weird scanf()! Returned 1
Enter data, char/int/int:
_read() called with fd = 0, count = 1024
_read() called with fd = 0, count = 1024
Если вместо символа использовать строку ("%s%i%i"), все работает корректно, включая случай, когда строка состоит из одного символа, при любой версии _read().

Мой друг m08pvv, тоже заинтересовавшись описанным явлением, обнаружил, что, по-видимому, для чтения паттернов "%c" и "%s" применяется один и тот же код. Возможно, это является источником проблемы.
Разница между теорией и практикой на практике гораздо больше, чем в теории.
Реклама
Аватара пользователя
Shapa
Встал на лапы
Сообщения: 127
Зарегистрирован: Пт июн 20, 2008 09:38:05
Откуда: Харьков
Контактная информация:

Re: Подозрение на глюк scanf() в newlib

Сообщение Shapa »

Косяк в вашей реализации _read (в целом всего кода приема-передачи). Замените реализацию стабами:

Код: Выделить всё

int _read(const int fd, void *buf, const size_t count) {
    static const char *items[] = {
        "pattern1",
        ....
        "patternN"
    };
    static const itemsCount = sizeof(items)/sizeof(*items);
    static size_t cur = 0;
    strncpy((char*) buf, items[cur++], count);
    if (cur >= itemsCount)
        cur = 0;
    return strlen(buf);
}
И вы увидите, что все паттерны парсятся верно. Значит ваш _read возвращает меньше чем вы ждете.
В противном случае - репортите бажину авторам вашего toolchain'a
Почему?
Потому, что race condition
Внутри _read у вас есть блокирующий вызов USART1_TransmitBuffer. Во время передачи, прилетают вам на вход байты, они успешно прошляплеваются, т.к. никакой буферизации у вас нет.
YS писал(а): - первый вызов завершается успешно;
- второй вызов завершается после первого прочитанного байта, в процессе scanf(), по всей видимости, портит стек.
Последовательно:
1) Вход в _read. дамп вашей строчки debug_str
2) Чтение на прерываниях - ок.
3) Парсинг - ок
4) Дамп пропаршеного и приглашение ввести ещё (!)
5) возвращаемся в п 1
и вот тут начинаются проблемы, т.к. в данный момент идет отправка и чтение невозможно
YS писал(а): - с использованием второй версии _read(), возвращающей по байту за раз. Видно, что второй вызов scanf() вылетает после первого принятого байта.
Даже если сейчас, вы по этому сценарию не встречаете багов, то позже встретите =)
На сколько я знаю, scnaf не имеет внутренней буферизации, а значит, на вход должна придти вся строка
YS писал(а):что реализация scanf() в newlib имеет
с чего вы взяли что scanf реализована в newlib? (а не stdlibc)

Для упрощения тестов воспользуйтесь sscanf'ом

UPD:
Подскажите пожалуйста: scanf("%c%i%i",&c,&p1,&p2); а каким образом scanf сможет дифференцировать, первый инт, от второго? Положим, я ожидаю получить c = A, p1 = 12, p2 = 15 => соответсвенно по вашему паттерну: printf("%c%i%i",c,p1,p2); A1215 - это число вообще на hexidecimal похоже. Введите раздилетель и будет вам счастье ("%c %i %i")
Реклама
Аватара пользователя
YS
Друг Кота
Сообщения: 7518
Зарегистрирован: Вс мар 29, 2009 22:09:05
Контактная информация:

Re: Подозрение на глюк scanf() в newlib

Сообщение YS »

Внутри _read у вас есть блокирующий вызов USART1_TransmitBuffer.
Э-э-э, точно. По ошибке пропостил один из отладочных вариантов. Конечно, этот вызов надо убрать. В исходной версии, с которой возникли проблемы его, естесственно, не было.
Во время передачи, прилетают вам на вход байты, они успешно прошляплеваются, т.к. никакой буферизации у вас нет.
На самом деле, проблема оказалась в том, что она таки есть. Но не у меня, а в недрах newlib. Наблюдаемые явления оказались родственными вот этому случаю.
с чего вы взяли что scanf реализована в newlib? (а не stdlibc)
Википедия: Newlib — реализация стандартной библиотеки языка Си, предназначенная для использования во встраиваемых системах.
Подскажите пожалуйста: scanf("%c%i%i",&c,&p1,&p2); а каким образом scanf сможет дифференцировать, первый инт, от второго?
Потому что между ними должен стоять, например, пробел.

For example, most scanf conversions skip over any amount of “white space” (including spaces, tabs, and newlines) in the input file, and there is no concept of precision for the numeric input conversions as there is for the corresponding output conversions. Ordinarily, non-whitespace characters in the template are expected to match characters in the input stream exactly, but a matching failure is distinct from an input error on the stream.
Разница между теорией и практикой на практике гораздо больше, чем в теории.
Ответить

Вернуться в «ARM»