dlinyj: (Default)
[personal profile] dlinyj
Покуда одни при каждом удобном случае мажут хабр грязью, я с большим удовольствием на нём выуживаю полезные для меня статьи, в частности:
Как защититься от переполнения стека (на Cortex M)?

Я, к сожалению, не такой лютый погромист всяких STM32 и прочих 32-х битных МК. Но таки срыв стека ловил, как раз когда переписывал предзагрузчик на одном весёлом MIPS-процессоре. Там я работал вообще на вольных хлебах, в том смысле, что весь стек инициализировался линкерным скриптом, в котором я тогда воообще не волок и брал готовый (сейчас не сильно больше волоку). В общем, суть была такова, что при вложении функции больше трёх - у меня ребутился проц. Понял я это не сразу, а путём экспериментов. В результате я тогда написал свой менеджер памяти, благо переменных было не очень много, но очень много данных и память (32 метра) была вся в моём распоряжении.

Так вот, ещё тогда я задумался, что стек - это больное и узкое место. Даже если у сильных мира сего, на которых бегает линукс, бывает срыв стека, то что бывает с маленькими процами. А что говорить про всяких ардуинщиков, которые даже об этом не думают и уверен, что у них 90% проблем - это срыв стека.

Так вот, исходя из статьи выше, я внезапно открыл для себя замечательные опции компилятора gcc -fstack-usage и... Я немного прифигел. После компиляции с этой опцией, появляется тьма файлов *.su (soviet union stack usage). Заглядываем в них, и тут я немного фигею:

...
reader.c:260:5:run_mode	48	static
reader.c:276:5:get_data_from_reader	1088	static
reader.c:293:6:free_recive_buffer	1056	static
reader.c:300:10:get_raw_data	64	static
...


Открываем функцию free_recive_buffer. Данная функция - это грязный хак-затычка, которая высасывает всё что есть в буфере uart, перед началом работы, чтобы иметь чистый буфер. Исключительно удобна при всяких опытах.

void free_recive_buffer (void) {
	uint8_t free_data [MAX_DATA_SIZE] = {0};
	get_from_reader(free_data,MAX_DATA_SIZE,100);
}


Ничего особенного, но оказывается, что переменная free_data располагается на стеке (MAX_DATA_SIZE = 1024).

Вопрос к знатокам. Я понимаю, что система у меня толстая, стек большой (кстати, а как узнать его размер в линукс?), но ведь это не есть хорошо? А если, вот я укажу там не 1024 в дефайне, а какую-нить дичь, типа 100000000? В чём преимущество размещения на стеке, в чём недостаток?

Так же, накидайте мне ещё каких-нить полезных опций компилятора gcc?

UPD Размер стека в Linux https://stackoverflow.com/questions/2275550/change-stack-size-for-a-c-application-in-linux-during-compilation-with-gnu-com

Date: 2018-10-02 04:11 pm (UTC)
From: [identity profile] mbr.livejournal.com
Данная статья только подтверждает уровень школоты на хабре. Давайте зададим стеку 0x200 байт, а куче 0x400 и будет счастье. И при каждом чихе лезть в файл линковщика подправлять. Офигенно! Руки за такое оторвать.

Самый простой способ узнать максимальное потребление стека, не привязанный к компилятору - при инициализации заполняешь память неким дефолтным значением (например, каноничным 0xcd), далее дергаешь функцию, которая смотрит сколько этой магии от конца кучи (он всегда ползет вперед и никогда назад). Полученный результат - и есть максимальный размер свободной памяти. Если там ничего нет - опа, приплыли, память закончилась.

У стека перед кучей много преимуществ:

- скорость выделения/освобождения
- не вызывает фрагментацию памяти

А недостаток всего один - при говнокоде это потенциальное место для атаки со stack overflow.

Но не всегда стек можно использовать - место на стеке освобождается при выходе из процедуры, его породившей и размер памяти нельзя задать параметром. А если в каком-то компиляторе и можно - опять же руки за такой говнокод оторвать.

Date: 2018-10-02 04:18 pm (UTC)
From: [identity profile] mbr.livejournal.com
А!!!!

uint8_t free_data [MAX_DATA_SIZE] = {0};
get_from_reader(free_data,MAX_DATA_SIZE,100);

Ну что за говнокод? А если 1025 байт там? Все, вешаемся?

Я так понимаю, что get_from_reader возвращает количество байт полученных? Тогда правильно так:

uint8_t free_data;
while (get_from_reader(&free_data, 1, 100)) {}

И вот эта твоя конструкция:

uint8_t free_data [MAX_DATA_SIZE] = {0};

Если не будет оптимизирована компилятором будет еще дополнительно килобайт данных заполнять нулями. Совершенно непонятно зачем.

Date: 2018-10-02 04:29 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
>>Ну что за говнокод? А если 1025 байт там? Все, вешаемся?

Ну ты всё равно не высосешь больше байт, чем просишь.

Вариант, что ты предлагаешь хорош, относительно. Но как мне кажется, хавать по байту - не есть зергуд. Но в целом-то ты прав.

З.Ы. Внедрил :)
Edited Date: 2018-10-02 04:32 pm (UTC)

Date: 2018-10-02 04:34 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
>>Данная статья только подтверждает уровень школоты на хабре. Давайте зададим стеку 0x200 байт, а куче 0x400 и будет счастье. И при каждом чихе лезть в файл линковщика подправлять. Офигенно! Руки за такое оторвать.

Как надо?

>>Самый простой способ узнать максимальное потребление стека, не привязанный к компилятору - при инициализации заполняешь память неким дефолтным значением (например, каноничным 0xcd), далее дергаешь функцию, которая смотрит сколько этой магии от конца кучи (он всегда ползет вперед и никогда назад). Полученный результат - и есть максимальный размер свободной памяти. Если там ничего нет - опа, приплыли, память закончилась.

Каким образом это сделать? Вот в линуксе виртуальная память, огромная. Но стек таки ограниченый (хотя уверен что он чудовищен).

Date: 2018-10-02 04:35 pm (UTC)
From: [identity profile] mbr.livejournal.com
Ага. Но байты могут остаться, если дергать функцию без цикла, как у тебя.

Уарт относительно тормозной интерфейс. Накладные расходы на лишний вызов функции на порядки ниже того, сколько времени идет один байт. А ждать тебе один фиг тут 100 миллисекунд. Так что ты ничего не теряешь, но экономишь место на стеке.

Date: 2018-10-02 04:37 pm (UTC)
From: [identity profile] mbr.livejournal.com
Как в линуксе и с виртуальной памятью - я хз. Написал тебе микроконтроллерный вариант.

Date: 2018-10-02 04:38 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
В любом случае большое спасибо, полезный опыт.

Полезный опыт задачки твои решать ;)

Date: 2018-10-02 04:41 pm (UTC)
From: [identity profile] mbr.livejournal.com
обращайся :)

Date: 2018-10-02 04:42 pm (UTC)
From: [identity profile] Шура Люберецкий (from livejournal.com)
> размер памяти нельзя задать параметром

Ты что имеешь в виду?

Date: 2018-10-02 04:52 pm (UTC)
From: [identity profile] mbr.livejournal.com
void foo(unsigned sz)
{
unsigned x[sz];
some_stuff(x);
}

Date: 2018-10-02 04:58 pm (UTC)
From: [identity profile] Шура Люберецкий (from livejournal.com)
В C99 можно, там добавили variable-length arrays. Плюс во многих компиляторах есть реализация функции alloca(), которая как раз выделяет память на стеке (но там начинаются свои приколы: https://stackoverflow.com/questions/1018853/why-is-the-use-of-alloca-not-considered-good-practice ).

Date: 2018-10-02 05:08 pm (UTC)
From: [identity profile] mbr.livejournal.com
Нельзя. Потому что это самый простой способ выстрелить себе в ногу. Например:

size = 0, undefined behavior
unsigned size = (unsigned)-1, гарантированный stack overflow

Date: 2018-10-02 05:15 pm (UTC)
From: [identity profile] minimumlaw.livejournal.com
Не так уж чудовищен размер стека в Linux (во всяком случае, если не предпринять специальных мер).

Подробности о том "как узнать" здесь https://www.linuxquestions.org/questions/linux-newbie-8/how-do-i-get-stack-size-of-running-process-875432/#post4327017

MBR крут. Со всем согласен. Кроме разве что категорического нежелания лезть в Link Script. Очень толковый инструмент. Особенно в части собственных секций для специального применения. Тем более, что в мире STM32 он все равно у каждого проекта свои. Что с IAR, что с GCC.

А исходная статья так себе. Вообще ситуация с переполнением стека у опытного программера-контроллерщика редкость. Очень быстро нарабатываются необходимые "профессиональные деформации". Ну, а люди пришедшие из Windows в контроллеры да, страдают. Но не долго. Часть убегает в панике назад в винды, часть обзаводится необходимыми "деформациями".

Date: 2018-10-02 05:24 pm (UTC)
From: [identity profile] Шура Люберецкий (from livejournal.com)
Надо переходить на терминологию из RFC 2119 :)

Date: 2018-10-02 05:41 pm (UTC)
From: [identity profile] mbr.livejournal.com
Возможно :)

Я изначально учился на ANSI C, С99 появился сильно позже. Некоторые вещи там добавлены правильно, но вот variable-length arrays это плохая практика. Как и alloca. Как и возможность определения переменных где угодно.

Date: 2018-10-02 05:43 pm (UTC)
From: [identity profile] mbr.livejournal.com
Я не говорю про нежелание лезть в линкерный скрипт. Лишь про то, что делить память на два прибитых гвоздями участка - это плохая практика. Каждый раз чего-нибудь будет нехватать.

Date: 2018-10-02 06:51 pm (UTC)
From: [identity profile] Шура Люберецкий (from livejournal.com)
Я вот от этой книжки и советов оттуда местами просто охуевал мрачно: http://shop.oreilly.com/product/0636920025108.do

Date: 2018-10-02 10:06 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
uint8_t free_data;
while (get_from_reader(&free_data, 1, 100)) {}

Отличный способ повесить программу постоянно засирая уарт данными, либо тем что ими выглядит %)))

ИМХО эта херь вообще не нужна. А буфер должен сбрасываться во время инициализации уарта желаемыми параметрами.

Ну и в нормальном протоколе обмена обычно старт/стоп предусматривают как раз на такой случай.

А вот масив там и в самом деле и нахер не нужен, тем более заполнение его нулями %))

Date: 2018-10-02 10:08 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
Да ну, очевидна же лажа.
Должно быть "some_stuff(x, sz);" и никаких проблем.

Date: 2018-10-02 10:14 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
unsigned size = (unsigned)-1, гарантированный stack overflow
И? В чем проблема то?
Ну получите вы stack overflow на системах где размер сегмента меньше или равен ансигнед инт, что такого то?
Это отловить - на раз-два, если степс то репродьюс есть.

Date: 2018-10-02 10:15 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
А что не так с alloca и тем болеее с возможностью определения переменных где угодно?
Какая разница в каком месте функции SP менять?

Date: 2018-10-02 10:17 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
А разве уарт не умеет буфер чистить по команде?

Сори, лет 20 с ним не работал, но мне казалось что он такое умеет....

Date: 2018-10-03 05:14 am (UTC)
From: [identity profile] Шура Люберецкий (from livejournal.com)
Эээ, много ли у нас систем, где не произойдет stack overflow из-за alloca(0xFFFFFFFF)?

Date: 2018-10-03 07:34 am (UTC)
From: [identity profile] mbr.livejournal.com
Слушай, ну ты вот сам пишешь, что не разбираешься, а лезешь учить. Знаешь, как это называется? Если ты не работал с уартом 20 лет, о каких "нормальных" протоколах обмена ты вообще имеешь представление?

Протоколы поверх уарта, если нет специального физического уровня со своими хаками, базируются на двух принципах - разделение пакетов по гвард-тайму, либо стаффинг. Стаффинг это дополнительные накладные расходы, в 99.9% будут гвард-таймы между пакетами. Ты обязан полностью вычитать предыдущий пакет из буфера перед приемом следующего. Если в процессе приема произошла ошибка - а у классического уарта нет возможности уведомить источник об ошибке, используются вот такие функции.

Остальные комментарии мне, честно говоря, комментировать лень - частично тебе уже ответили, частично есть хорошая ссылка на stack overflow. Что я думаю о твоем коде, я высказал уже выше ;)

Date: 2018-10-03 09:11 am (UTC)
From: [identity profile] dlinyj.livejournal.com
Давай так, прежде чем написать херню в моём блоге, ты её тщательно обдумаешь. А потом ещё раз обдумаешь.

>>Отличный способ повесить программу постоянно засирая уарт данными, либо тем что ими выглядит %)))

Объясни каким образом этот код приёмного буффера может повесить программу, а ещё что более важно, как может засрать uart?

>>А буфер должен сбрасываться во время инициализации уарта желаемыми параметрами.

Код в студию, Linux.

>>А вот масив там и в самом деле и нахер не нужен, тем более заполнение его нулями %))
Единственное с чем согласен.

Date: 2018-10-03 09:39 am (UTC)
From: [identity profile] arush-damage.livejournal.com
Че такого?
Всего четыре гига %)))

Date: 2018-10-03 09:55 am (UTC)
From: [identity profile] dlinyj.livejournal.com
чудненько... А как на счёт систем, где всего 32 метра?

Date: 2018-10-03 10:29 am (UTC)
From: [identity profile] arush-damage.livejournal.com
>Объясни каким образом этот код приёмного буффера может повесить программу, а ещё что более важно, как может засрать uart?
Наоборот.
Код который постоянно читает данные пока они есть рискует читать их до конца жизни если с другого конца в уарт будут срать данными без перерыва.

>>А буфер должен сбрасываться во время инициализации уарта желаемыми параметрами.
> Код в студию, Linux.
Флаг TCSAFLUSH для tcsetattr.

Ну и tcflush если так уж нужно чистить буфер потом.

Так то по уму послу инициализации порта постоянно читаем данные и если они нам не нравятся - ждем нужных.
Я так даже сходу и не соображу когда чистить буфер может иметь смысл.

Date: 2018-10-03 10:31 am (UTC)
From: [identity profile] arush-damage.livejournal.com
https://dlinyj.livejournal.com/774065.html?thread=10210737#t10210737

Date: 2018-10-03 11:17 am (UTC)
From: [identity profile] dlinyj.livejournal.com
В данном, конкретно моём случае, я мог дропнуть прогу до того, как мне устройство вернёт весь свой шлак. Поэтому для отладки я сделал такую заглушку. Если у тебя непрерывный поток, то так делать конечно не стоит.

Сделал проверку.

В инициализации uart изменил строки:
	//tcsetattr(fd, TCSANOW, &options);
	tcsetattr(fd,TCSAFLUSH, &options);
	tcflush(fd, TCIOFLUSH ); //clear buffer


В код функции очистки буффера закинул:

void free_recive_buffer (void) {
	uint8_t free_data;
	int counter = 0;
	while (get_from_reader(&free_data, 1, 100)) {
		counter++;
	};
	printf("counter = %d\n", counter);
}


Запускаю программу, которая требует овердофига байт и дропаю её, и потом снова запускаю. Вначале стоит запуск очистки буффера, после инита юарта. И опа...
$ sudo ./name_of_my_prog -d /dev/ttyUSB0 -o
counter = 0
^C
$ sudo ./name_of_my_prog -d /dev/ttyUSB0 -o
counter = 262
^C


Таки откуда-то набегают байты... Так что очищать всё равно надо.
Edited Date: 2018-10-03 11:30 am (UTC)

Date: 2018-10-03 03:25 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
> Таки откуда-то набегают байты...
Вероятно это остатки ответа на первый запуск, не?

Date: 2018-10-03 03:26 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
Бинго! Как от них избавиться?

Date: 2018-10-03 03:53 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
> Слушай, ну ты вот сам пишешь, что не разбираешься, а лезешь учить. Знаешь, как это называется? Если ты не работал с уартом 20 лет, о каких "нормальных" протоколах обмена ты вообще имеешь представление?
А что с 2000-го сильно много нового придумали?
Что например?

ЗЫ. "Что я думаю о твоем коде, я высказал уже выше" - вы наверно меня с кем-то путаете.
Можно линк?


ЗЫЫ. Посмотрел в ваш профиль, все понятно - радиолюбитель %))
Насмотрелся я в свое время на поделки радиолюбителей "тоже программистов" %)))
Вопросов больше не имею - продолжайте использовать циклы while(true) {} %))

Date: 2018-10-03 04:00 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
Мне кажется до его уровня "радиолюбительства" тебе как до луны пешком.

Date: 2018-10-03 04:03 pm (UTC)
From: [identity profile] mbr.livejournal.com
После того, как ты меня обозвал радиолюбителем, имею полное право обозвать тебя тупым дебилом. Твой код и твои познания это только подтверждают. За сим диалог окончен - приличных эпитетов у меня к тебе нет, а за неприличные автор блога обидится.

Date: 2018-10-03 04:10 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
Вот и поговорили... :).

Да ладно тебе, чувак выплыл из двухтысячных

Date: 2018-10-03 04:13 pm (UTC)
From: [identity profile] mbr.livejournal.com
Извини, не сдержался. Просто выбешивают люди, которые не в теме, но лезут учить.

Date: 2018-10-03 04:17 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
Я полностью разделяю твои чувства, и он уж очень не прав.

Date: 2018-10-03 04:35 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
Читать и ждать пока с той стороны перестанут данные слать?
Серьезно?
Может стоит посмотреть что там пришло, а?
Может там хендшейка попытки например.

Date: 2018-10-03 04:37 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
Все же интересно какой такой "мой код" вы имеете в виду %))
Хотелось бы его посмотреть %)))

Date: 2018-10-03 04:56 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
Мда. В общем-то [livejournal.com profile] mbr был прав, посылая тебя далеко и надолго. Я уж думал будет нечто полезное.

Date: 2018-10-03 04:56 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
Иди флуди в другое место.

Date: 2018-10-03 05:02 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
Мда, по ходу ты так и не понял что я пытаюсь донести %(

Данные в буфер могут валиться по куче причин, например контроллер полетел.
Просто читать данные и ожидать что они когда нибудь кончатся - не вариант.

Но послать конечно проще, че уж тут %)

Date: 2018-10-03 05:07 pm (UTC)
From: [identity profile] dlinyj.livejournal.com
Так наверное надо внятно объяснять, а не обвинять человека в тупости.

Такой вариант возможен. Но маловероятен.

Date: 2018-10-03 05:15 pm (UTC)
From: [identity profile] arush-damage.livejournal.com
>Так наверное надо внятно объяснять, а не обвинять человека в тупости.

Я вроде с самого начала так и написал:
>>while (get_from_reader(&free_data, 1, 100)) {}
>Отличный способ повесить программу постоянно засирая уарт данными, либо тем что ими выглядит %)))

Ладно, может не вполне внятно, но потом же вроде нормально объяснил:

>>Объясни каким образом этот код приёмного буффера может повесить программу, а ещё что более важно, как может засрать uart?
>Наоборот.
>Код который постоянно читает данные пока они есть рискует читать их до конца жизни если с другого конца в уарт будут срать данными без перерыва.


И да, хотелось бы увидеть где и кого я обвинил в тупости?

January 2026

S M T W T F S
    123
456 78910
11121314151617
18192021222324
25262728293031

Most Popular Tags

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Jan. 22nd, 2026 01:10 pm
Powered by Dreamwidth Studios