Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 05:37
Оценка:
Если я правильно понимаю, при переполнении буфера слишком длинные данные затирают в стеке адреса возврата. Это позволяет подставить и выполнить свой код.

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

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

Это же вроде компилятор решает, где и как хранить данные? Есть такая настройка?
Best regards, Буравчик
Отредактировано 14.05.2019 5:39 Буравчик . Предыдущая версия .
Re: Переполнение буфера
От: wildwind Россия  
Дата: 14.05.19 06:13
Оценка: +1
Здравствуйте, Буравчик, Вы писали:

Б>Но почему-бы не разделить стек? Сделать отдельный стек для данных и отдельный стек для адресов возврата.


Это будет уже другая архитектура процессора.
Re[2]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 06:50
Оценка:
Здравствуйте, wildwind, Вы писали:

W>Это будет уже другая архитектура процессора.


Не, в процессор зашито использование стека только для адреса возврата. Я как раз и предлагаю оставить адрес возврата в обычном стеке (которой SS:SP).
А вот за то, что туда складываются локальные переменные и параметры процедур, отвечает компилятор. В самом процессоре это не регламентировано (x86).
Best regards, Буравчик
Re[3]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 07:31
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Не, в процессор зашито использование стека только для адреса возврата. Я как раз и предлагаю оставить адрес возврата в обычном стеке (которой SS:SP).

Б>А вот за то, что туда складываются локальные переменные и параметры процедур, отвечает компилятор. В самом процессоре это не регламентировано (x86).

Или вообще можно менять SP перед и после каждым CALL/RET. Тогда можно использовать обычные PUSH/POP для данных, а не работать с памятью и кэшем, которые медленные.

Я понимаю, что это все будет чуть медленнее, чем сейчас. Но ведь это позволит убрать наиболее частую и наиболее критичную уязвимость — переполнение буфера.
По крайней мере, для определенных программ это может быть полезно, даже в ущерб скорости. Да и потеря в скорости может быть совсем крошечной.
Best regards, Буравчик
Отредактировано 14.05.2019 7:32 Буравчик . Предыдущая версия .
Re[3]: Переполнение буфера
От: mike_rs Россия  
Дата: 14.05.19 08:00
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Здравствуйте, wildwind, Вы писали:


W>>Это будет уже другая архитектура процессора.


Б>Не, в процессор зашито использование стека только для адреса возврата. Я как раз и предлагаю оставить адрес возврата в обычном стеке (которой SS:SP).

Б>А вот за то, что туда складываются локальные переменные и параметры процедур, отвечает компилятор. В самом процессоре это не регламентировано (x86).

тогда где-то (в куче?) придется выделять память для локальных переменых, это все будет жутко тормозить. А так — ну бекэнд сделай свой к llvm какому-нить, и генерируй другой asm код.
Re[4]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 08:11
Оценка:
Здравствуйте, mike_rs, Вы писали:

_>тогда где-то (в куче?) придется выделять память для локальных переменых, это все будет жутко тормозить. А так — ну бекэнд сделай свой к llvm какому-нить, и генерируй другой asm код.


Cтек тоже находится в той же обычной памяти, рядом с кучей. Тормозить будет только переключение стеков "инструкции/данные", но не сами стеки.

Меня интересует, почему так еще не сделали, какие есть проблемы. Скорее всего, я чего-то не знаю или не понимаю.

P.S. На asm писать я почти не умею, сделать что-то для llvm не смогу.
Best regards, Буравчик
Re: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 08:28
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Простейший способ — организовать где-то в памяти стек и использовать его для хранения локальных переменных и параметров процедур. А адрес возврата хранить в "обычном" стеке.

Б>Это же вроде компилятор решает, где и как хранить данные? Есть такая настройка?

Например, что-то типа такого должно быть:

Сейчас:

PUSH param1
PUSH param2
CALL func
...

func:
POP ax ;param2
POP bx ;param1
MOV ax, result
RET


С разделением стеков:
Каждый раз, когда программа работает с данными, она переключается на свой стек данных.
При выполнении инструкций CALL/RET переключаемся на стек инструкций.

MOV SP, data_stack
PUSH param1
PUSH param2
MOV data_stack, SP
MOV SP, instruction_stack
CALL func

...
func:
MOV instruction_stack, SP
MOV SP, data_stack
POP ax ;param2
POP bx ;param1
MOV ax, result
MOV data_stack, SP
MOV SP, instruction_stack
RET


Оверхэд — из-за MOV SP, ... т.к. адреса стеков хранятся в памяти. Но:
1. Этот участок памяти с очень большой вероятностью будет в L1 кэше
2. Можно хранить указатели в регистрах, например SP и CX. Менять их CHG sp, cx (или как там в asm)
3. Можно менять значения SP, сохраняя предыдущее значение в стеке инструкций (главное, чтобы не в стеке данных, т.к. стек данных уязвим, а стек инструкций нет).

Дополнение:
Посмотрел, есть XCHG reg, mem
т.е. можно делать XCHG SP, ячейка_для_хранения_неиспользуемого_SP, достаточно одной ячейки памяти и одной команды для переключения стека.
Ну и в идеале вместо ячейка_для_хранения_неиспользуемого_SP использовать регистр. Чтоб совсем быстро.
Best regards, Буравчик
Отредактировано 14.05.2019 8:39 Буравчик . Предыдущая версия . Еще …
Отредактировано 14.05.2019 8:33 Буравчик . Предыдущая версия .
Отредактировано 14.05.2019 8:30 Буравчик . Предыдущая версия .
Отредактировано 14.05.2019 8:29 Буравчик . Предыдущая версия .
Re: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 09:49
Оценка: 15 (2)
Здравствуйте, Буравчик, Вы писали:

Б>Если я правильно понимаю, при переполнении буфера слишком длинные данные затирают в стеке адреса возврата. Это позволяет подставить и выполнить свой код.


Это самый простой вариант. Но затирание других данных тоже используется в эксплойтах.
А с приходом address space layout randomization (ASLR) ценность такого метода снизилась практически до нуля, разве что вышибешь процесс.

Б>Но почему-бы не разделить стек? Сделать отдельный стек для данных и отдельный стек для адресов возврата.


Есть архитектуры, где так сделано (IA64), есть программные реализации (такое требуется для Forth).
IA64 практически умер. Forth не используется кроме спецслучаев (загрузчики и т.п.) на процессорах с OoO и кэшированием.
Ссылок не найду, но практика показала, что получается только частичная защита от проблем.
Методы ограничения через явные проверки работают эффективнее, особенно если встроены в язык.
The God is real, unless declared integer.
Re[2]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 09:52
Оценка: 6 (1)
Здравствуйте, Буравчик, Вы писали:

Б>Дополнение:

Б>Посмотрел, есть XCHG reg, mem
Б>т.е. можно делать XCHG SP, ячейка_для_хранения_неиспользуемого_SP, достаточно одной ячейки памяти и одной команды для переключения стека.
Б>Ну и в идеале вместо ячейка_для_хранения_неиспользуемого_SP использовать регистр. Чтоб совсем быстро.

XCHG плохо, потому что Intel давно его испортил:

If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL. (See the LOCK prefix description in this chapter for more information on the locking protocol.)


Но любой другой регистр (особенно в x86-64) вполне может справиться.
The God is real, unless declared integer.
Re: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 14.05.19 10:12
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Если я правильно понимаю, при переполнении буфера слишком длинные данные затирают в стеке адреса возврата. Это позволяет подставить и выполнить свой код.

Б>Но почему-бы не разделить стек? Сделать отдельный стек для данных и отдельный стек для адресов возврата.

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

Во-вторых, такое решение исключит лишь часть возможных атак, но не все. По-прежнему можно будет, организовав переполнение, так поменять данные потока, чтобы он сработал в угоду атакующему. Будет сложнее добиться нужного поведения, но это останется возможным.

Так что игра банально не стоит свеч.
Re: Переполнение буфера
От: Слава  
Дата: 14.05.19 11:39
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Но почему-бы не разделить стек? Сделать отдельный стек для данных и отдельный стек для адресов возврата.


Есть способ получше — верификация алгоритма, гарантирующая, что буфер не будет переполнен.
Re[4]: Переполнение буфера
От: vsb Казахстан  
Дата: 14.05.19 11:41
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Или вообще можно менять SP перед и после каждым CALL/RET. Тогда можно использовать обычные PUSH/POP для данных, а не работать с памятью и кэшем, которые медленные.


Почему медленные? Стек это ровно та же память. Не думаю, что PUSH будет существенно отличаться от "ручного" PUSH через любой другой регистр.
Отредактировано 14.05.2019 11:44 vsb . Предыдущая версия .
Re[5]: Переполнение буфера
От: Pzz Россия https://github.com/alexpevzner
Дата: 14.05.19 12:09
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Меня интересует, почему так еще не сделали, какие есть проблемы. Скорее всего, я чего-то не знаю или не понимаю.


Это не решит проблему радикально. Если переполнив буфер, можно попасть в соседнюю область памяти, то совершенно не обязательно целиться именно в адрес возврата. В памяти есть много других мест, куда можно с успехом прицелиться.

Радикально решает проблему вставляемая компилятором явная проверка переполнения буфера при каждом обращении. Всякие там managed языки (Go, Java, C# и т.д.) делают это по умолчанию, некоторые (все?) компиляторы Си тоже так умеют. Но это снижает скорость раза в два.
Re[2]: Переполнение буфера
От: mike_rs Россия  
Дата: 14.05.19 12:12
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>При выполнении инструкций CALL/RET переключаемся на стек инструкций.


Б>[code]

Б>MOV SP, data_stack
Б>PUSH param1
Б>PUSH param2
Б>MOV data_stack, SP

что такое data_stack? если глобальная переменная, то эту функцию нельзя использовать многопоточно, что чревато. Если нет, то как предполагается разруливать многопоточность?
Re[2]: Переполнение буфера
От: watchmaker  
Дата: 14.05.19 12:38
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

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

Кому должен? Почему нельзя расширять? :)
Это не необходимое требование.
Например, в компиляторах clang или gcc уже лет десять доступна опция split-stack. С ней компилятор генерирует код, который не использует непрерывный стек. Сделано это как-раз для того, чтобы не выжирать всю виртуальную память, например если нужно на x86 в одном процессе запустить десяток тысяч потоков.

То есть, если интересно посмотреть как программы могут работать без непрерывного стека, то далеко ходить не надо — это уже давно реализовано. Востребованность этого, конечно, другой вопрос :)
Re[2]: Переполнение буфера
От: IID Россия  
Дата: 14.05.19 12:45
Оценка:
Здравствуйте, netch80, Вы писали:

N>А с приходом address space layout randomization (ASLR) ценность такого метода снизилась практически до нуля, разве что вышибешь процесс.


Снизилась только потому, что теперь надо 2 эксплоита вместо 1.
Сначала ID, потом уже RCE.
kalsarikännit
Re[3]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 14.05.19 12:47
Оценка:
Здравствуйте, watchmaker, Вы писали:

W>Например, в компиляторах clang или gcc уже лет десять доступна опция split-stack. С ней компилятор генерирует код, который не использует непрерывный стек.


А как при этом обеспечивается раскрутка стека средствами ОС в случае исключения? Или эта опция используется только в ОС, не поддерживающих раскрутку стека собственными средствами?
Re: Переполнение буфера
От: IID Россия  
Дата: 14.05.19 12:52
Оценка: 15 (2)
Здравствуйте, Буравчик, Вы писали:

Б>Если я правильно понимаю, при переполнении буфера слишком длинные данные затирают в стеке адреса возврата.


Против этого работают стековые канарейки/печеньки.
(Конечно, бывают исключительные случаи, типа тщательно подобранного зацикливания в рекурсии, чтобы перепрыгивать через них).

Б>Это позволяет подставить и выполнить свой код.


А твой код откуда возьмётся в атакуемом процессе ? Времена исполнения данных давно прошли, сейчас везде NX бит.
Для защиты от ROP/JOP придумали CFI (Control Flow Integrity).

Б>Но почему-бы не разделить стек? Сделать отдельный стек для данных и отдельный стек для адресов возврата.


Потому что tampered данные не менее опасны, чем адреса возврата.
Будет у тебя лежать в стеке структура с коллбеком. Или указатель на структуру с коллбеком. Ну ты понЕл...

Интел пару лет назад что-то рассказывал про двойной стек. Только в другом ключе — при возвратах проверяется теневой стек, что в него действительно заносился адрес и заносился с помощью команды CALL.
Вроде обещали сделать аппаратную реализацию.

Ещё можешь про технологию Pointer Authentication почитать. Она делает подобные атаки бесполезными.
kalsarikännit
Re[5]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 12:55
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Здравствуйте, Буравчик, Вы писали:


Б>>Или вообще можно менять SP перед и после каждым CALL/RET. Тогда можно использовать обычные PUSH/POP для данных, а не работать с памятью и кэшем, которые медленные.


vsb>Почему медленные? Стек это ровно та же память. Не думаю, что PUSH будет существенно отличаться от "ручного" PUSH через любой другой регистр.


Одна операция PUSH/POP это на самом деле две операции — запись в память и изменение регистра SP.
Нам придется явно выполнять две операции вместо одной. Плюс если SP является не регистром а ячейкой памяти, то еще добавится тормозов.
Best regards, Буравчик
Re[4]: Переполнение буфера
От: IID Россия  
Дата: 14.05.19 13:02
Оценка: 6 (1) +1
Здравствуйте, Буравчик, Вы писали:

Б>Но ведь это позволит убрать наиболее частую и наиболее критичную уязвимость — переполнение буфера.


Это очень редкая уязвимость в наши дни.

Гораздо чаще встречаются UAF (use after free) или Double Free (конвертируются в UAF).
Причём не сами-по-себе, а как результат гонки (race conditions).
kalsarikännit
Re[6]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 13:03
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Это не решит проблему радикально. Если переполнив буфер, можно попасть в соседнюю область памяти, то совершенно не обязательно целиться именно в адрес возврата. В памяти есть много других мест, куда можно с успехом прицелиться.


Ну, это одна из возможных защит. Если цена за эту защиту небольшая, почему бы не использовать?
Мне кажется, переполнение буфера и перевод на себя исполнения, это самая "страшная" уязвимость, т.к. позволяет получить рут.

Pzz>Радикально решает проблему вставляемая компилятором явная проверка переполнения буфера при каждом обращении. Всякие там managed языки (Go, Java, C# и т.д.) делают это по умолчанию, некоторые (все?) компиляторы Си тоже так умеют. Но это снижает скорость раза в два.


А как компилятор Си может проверить переполнение буфера при работе с char[]?
Best regards, Буравчик
Re[3]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 13:06
Оценка:
Здравствуйте, mike_rs, Вы писали:

_>что такое data_stack? если глобальная переменная, то эту функцию нельзя использовать многопоточно, что чревато. Если нет, то как предполагается разруливать многопоточность?


Да, вопрос интересный. Но ответ есть.
Нужно иметь эту переменную для каждого потока. Допустим, хранить ее в самом начале стека инструкций. Ведь стек инструкций есть у каждого потока.
Best regards, Буравчик
Re[6]: Переполнение буфера
От: IID Россия  
Дата: 14.05.19 13:06
Оценка:
Здравствуйте, Pzz, Вы писали:

Pzz>Радикально решает проблему вставляемая компилятором явная проверка переполнения буфера при каждом обращении.


Не решает.
См. сколько уязвимостей находили в javascript движках.
Вплоть до того, что какое-нибудь неочевидное действие ломает внутренние структуры VM — и у тебя в руках появляются arbitrary read/write инструменты, можешь делать с памятью процесса что угодно (реальный баг в Chrome).
Ситуацию усугубляет наличие JIT (у многих языков есть).
kalsarikännit
Re[3]: Переполнение буфера
От: Sharov Россия  
Дата: 14.05.19 13:09
Оценка:
Здравствуйте, mike_rs, Вы писали:

_>Здравствуйте, Буравчик, Вы писали:


Б>>При выполнении инструкций CALL/RET переключаемся на стек инструкций.


Б>>[code]

Б>>MOV SP, data_stack
Б>>PUSH param1
Б>>PUSH param2
Б>>MOV data_stack, SP

_>что такое data_stack? если глобальная переменная, то эту функцию нельзя использовать многопоточно, что чревато. Если нет, то как предполагается разруливать многопоточность?


Так у каждого потока свой стек, чего его разруливать? При создании потока генерируются соовт. data_stack, code_stack.
Кодом людям нужно помогать!
Re[2]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 13:11
Оценка:
Здравствуйте, Слава, Вы писали:

С>Есть способ получше — верификация алгоритма, гарантирующая, что буфер не будет переполнен.


Теоретически. Только Си этого не умеет. И вообще никто не умеет, т.к. NP
Best regards, Буравчик
Re[6]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 13:13
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>>>Или вообще можно менять SP перед и после каждым CALL/RET. Тогда можно использовать обычные PUSH/POP для данных, а не работать с памятью и кэшем, которые медленные.

vsb>>Почему медленные? Стек это ровно та же память. Не думаю, что PUSH будет существенно отличаться от "ручного" PUSH через любой другой регистр.
Б>Одна операция PUSH/POP это на самом деле две операции — запись в память и изменение регистра SP.
Б>Нам придется явно выполнять две операции вместо одной. Плюс если SP является не регистром а ячейкой памяти, то еще добавится тормозов.

1. Это специфика x86. В ARM, например, такие операции могут делаться с любым регистром.
2. Внутри они всё равно разлагаются в пару операций. А оптимальность представления в потоке команд не так критична (всё равно предвыборка и рекодинг заранее).
3. Есть такая оптимизация — вообще поменьше двигать SP и использовать смещения от него. Во времена где-то до середины 2000-х gcc, например, предпочитал делать именно так, потому что цепочки push/pop слишком зависели друг от друга. Потом в процессоры была добавлена специальная оптимизация таких цепочек и необходимость ослабла — но он и сейчас может так делать. Учитывая, что адресация параметров по константе относительно SP — норма, чуть больше таких адресаций существенно картину не портят.
The God is real, unless declared integer.
Re[2]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 13:18
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

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


Это никогда не было так жёстко — и сейчас с работающим split stack есть штатные обходы. Но это, да, дороже, и требует сборки как минимум заметной части кода под такое. Под Windows, вроде, реализаций нет.
Но в 64 битах и так адресов достаточно.

EM> Сейчас и так каждый поток имеет собственные стеки как режима пользователя, так и режима ядра, а пришлось бы все это удвоить.


Удвоение тут это малая цена.

ЕМ>Во-вторых, такое решение исключит лишь часть возможных атак, но не все. По-прежнему можно будет, организовав переполнение, так поменять данные потока, чтобы он сработал в угоду атакующему. Будет сложнее добиться нужного поведения, но это останется возможным.

ЕМ>Так что игра банально не стоит свеч.

Вот тут — да, но в основном по другим причинам.
The God is real, unless declared integer.
Отредактировано 14.05.2019 13:46 netch80 . Предыдущая версия .
Re[4]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 13:23
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

W>>Например, в компиляторах clang или gcc уже лет десять доступна опция split-stack. С ней компилятор генерирует код, который не использует непрерывный стек.


ЕМ>А как при этом обеспечивается раскрутка стека средствами ОС в случае исключения? Или эта опция используется только в ОС, не поддерживающих раскрутку стека собственными средствами?


Имеется в виду Windows с её SEH?
Было бы желание, а метод будет: будут просто во фрейм каждой функции писать адрес предыдущего фрейма. Собственно, оно сейчас так и делается. Для этого метода нет разницы, кто раскручивает стек, ОС или рантайм компилятора, проблемы одинаковы.
The God is real, unless declared integer.
Re[7]: Переполнение буфера
От: Pzz Россия https://github.com/alexpevzner
Дата: 14.05.19 13:35
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Здравствуйте, Pzz, Вы писали:


Pzz>>Это не решит проблему радикально. Если переполнив буфер, можно попасть в соседнюю область памяти, то совершенно не обязательно целиться именно в адрес возврата. В памяти есть много других мест, куда можно с успехом прицелиться.


Б>Ну, это одна из возможных защит. Если цена за эту защиту небольшая, почему бы не использовать?

Б>Мне кажется, переполнение буфера и перевод на себя исполнения, это самая "страшная" уязвимость, т.к. позволяет получить рут.

Ну на самом деле, это одна из 100500 возможных уязвимостей.

Pzz>>Радикально решает проблему вставляемая компилятором явная проверка переполнения буфера при каждом обращении. Всякие там managed языки (Go, Java, C# и т.д.) делают это по умолчанию, некоторые (все?) компиляторы Си тоже так умеют. Но это снижает скорость раза в два.


Б>А как компилятор Си может проверить переполнение буфера при работе с char[]?


Никак. Но если размер массива объявлен явно, или буфер получен явным вызовом аллокатора, то может.
Re[4]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 13:35
Оценка: 6 (1)
Здравствуйте, Буравчик, Вы писали:

_>>что такое data_stack? если глобальная переменная, то эту функцию нельзя использовать многопоточно, что чревато. Если нет, то как предполагается разруливать многопоточность?


Б>Да, вопрос интересный. Но ответ есть.

Б>Нужно иметь эту переменную для каждого потока. Допустим, хранить ее в самом начале стека инструкций. Ведь стек инструкций есть у каждого потока.

С заранее неизвестным смещением? Не годится.

Но современные thread-local storage давно сделаны на отдельном регистре (для x86 это FS или GS), а относительно них уже константное смещение.
The God is real, unless declared integer.
Re[3]: Переполнение буфера
От: Pzz Россия https://github.com/alexpevzner
Дата: 14.05.19 13:41
Оценка:
Здравствуйте, Буравчик, Вы писали:

С>>Есть способ получше — верификация алгоритма, гарантирующая, что буфер не будет переполнен.


Б>Теоретически. Только Си этого не умеет. И вообще никто не умеет, т.к. NP


Я, конечно, не теоретик, но насколько я понимаю, задача верификации алгоритма в общем случае эквиавалентна решению проблемы останова. А она, как известно, алгоритмически не разрешима.
Re[3]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 13:44
Оценка:
Здравствуйте, Буравчик, Вы писали:

С>>Есть способ получше — верификация алгоритма, гарантирующая, что буфер не будет переполнен.

Б>Теоретически. Только Си этого не умеет. И вообще никто не умеет, т.к. NP

Зато можно потребовать писать на таких языках и в таком стиле, что верификатор это осилит.
The God is real, unless declared integer.
Re[4]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 14.05.19 13:46
Оценка:
Здравствуйте, Pzz, Вы писали:

С>>>Есть способ получше — верификация алгоритма, гарантирующая, что буфер не будет переполнен.

Б>>Теоретически. Только Си этого не умеет. И вообще никто не умеет, т.к. NP
Pzz>Я, конечно, не теоретик, но насколько я понимаю, задача верификации алгоритма в общем случае эквиавалентна решению проблемы останова. А она, как известно, алгоритмически не разрешима.

Писать так, чтобы верификатор мог это осилить.
(Хм. Повторяюсь. Но непонятно, кому ты отвечал — мне кажется, что возражать надо было в адрес Слава@)
The God is real, unless declared integer.
Re[4]: Переполнение буфера
От: Буравчик Россия  
Дата: 14.05.19 14:13
Оценка:
Здравствуйте, netch80, Вы писали:

N>Зато можно потребовать писать на таких языках и в таком стиле, что верификатор это осилит.


Тогда человек не осилит

Но можно попробовать так (сделал новый топик)
http://rsdn.org/forum/philosophy/7442996.1
Автор: Буравчик
Дата: 14.05.19
Best regards, Буравчик
Re[6]: Переполнение буфера
От: vsb Казахстан  
Дата: 14.05.19 17:25
Оценка:
Здравствуйте, Буравчик, Вы писали:

vsb>>Почему медленные? Стек это ровно та же память. Не думаю, что PUSH будет существенно отличаться от "ручного" PUSH через любой другой регистр.


Б>Одна операция PUSH/POP это на самом деле две операции — запись в память и изменение регистра SP.

Б>Нам придется явно выполнять две операции вместо одной. Плюс если SP является не регистром а ячейкой памяти, то еще добавится тормозов.

Запись в L1 это 3 такта, насколько я помню. Изменение регистра очевидно один такт. Поэтому разница между 3 тактами и 4 тактами. И то если это реально реализовано в микрокоде. А то может он этот PUSH делает как 2 операции. Тогда вообще разницы не заметишь.
Отредактировано 14.05.2019 17:27 vsb . Предыдущая версия .
Re[3]: Переполнение буфера
От: Mystic Artifact  
Дата: 06.07.19 10:02
Оценка:
Здравствуйте, mike_rs, Вы писали:

Прежде чем касаться многопоточности, хорошо бы вернуть свойство реентрабельности (reenter), что в сущности требует некоторого стэка.
Re[4]: Переполнение буфера
От: mike_rs Россия  
Дата: 24.09.19 15:05
Оценка:
Здравствуйте, Sharov, Вы писали:

_>>что такое data_stack? если глобальная переменная, то эту функцию нельзя использовать многопоточно, что чревато. Если нет, то как предполагается разруливать многопоточность?


S>Так у каждого потока свой стек, чего его разруливать? При создании потока генерируются соовт. data_stack, code_stack.


и дальше что? хранить ты где будешь этот контекст? Сейчас процессор при переключении потока сохраняет контекст (регистры, в том числе и rsp) и он ничего не знает ни про какие переменные data_stack. Вопрос простой — как при переключении контекста переключить и этот твой data_stack, где физически будет это число храниться?
Re[4]: Переполнение буфера
От: mike_rs Россия  
Дата: 24.09.19 15:07
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Да, вопрос интересный. Но ответ есть.

Б>Нужно иметь эту переменную для каждого потока. Допустим, хранить ее в самом начале стека инструкций. Ведь стек инструкций есть у каждого потока.

где физически это будет храниться и как передаваться при переключении контекста потока? Если что, то секция кода при многопоточном исполнении как раз единая, разница именно что в стеке, указатель на который лежит в rsp и его переключение обеспечивается архитектурой.
Re[5]: Переполнение буфера
От: Sharov Россия  
Дата: 24.09.19 17:00
Оценка:
Здравствуйте, mike_rs, Вы писали:


_>и дальше что? хранить ты где будешь этот контекст? Сейчас процессор при переключении потока сохраняет контекст (регистры, в том числе и rsp) и он ничего не знает ни про какие переменные data_stack. Вопрос простой — как при переключении контекста переключить и этот твой data_stack, где физически будет это число храниться?


В sp?
Кодом людям нужно помогать!
Re[5]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 24.09.19 19:17
Оценка:
Здравствуйте, mike_rs, Вы писали:

S>>Так у каждого потока свой стек, чего его разруливать? При создании потока генерируются соовт. data_stack, code_stack.


_>и дальше что? хранить ты где будешь этот контекст? Сейчас процессор при переключении потока сохраняет контекст (регистры, в том числе и rsp) и он ничего не знает ни про какие переменные data_stack. Вопрос простой — как при переключении контекста переключить и этот твой data_stack, где физически будет это число храниться?


Любой выделенный для этого регистр. Какой — зависит от типа процессора и принятого ABI.
X86 ограничивает сейчас регистром *SP только для push/pop/call/ret, для стека данных можно применить любой регистр.
Аналогично на ARM и прочих архитектурах с поддержкой адресации в духе операций со стеком (как пре- и пост-индексные в ARM).
zSeries (S/390) вообще не имеет выделенного регистра стека, стандартное ABI использует для этого R13. Занять, например, R12 в том же стиле — тривиально и требует только доработки кодогенератора.
The God is real, unless declared integer.
Re[5]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 24.09.19 19:19
Оценка:
Здравствуйте, mike_rs, Вы писали:

Б>>Да, вопрос интересный. Но ответ есть.

Б>>Нужно иметь эту переменную для каждого потока. Допустим, хранить ее в самом начале стека инструкций. Ведь стек инструкций есть у каждого потока.

_>где физически это будет храниться и как передаваться при переключении контекста потока? Если что, то секция кода при многопоточном исполнении как раз единая, разница именно что в стеке, указатель на который лежит в rsp и его переключение обеспечивается архитектурой.


При переключении тредов восстанавливаются все регистры общего назначения, а не только rsp.
The God is real, unless declared integer.
Re[6]: Переполнение буфера
От: Sharov Россия  
Дата: 25.09.19 09:27
Оценка:
Здравствуйте, netch80, Вы писали:

N>При переключении тредов восстанавливаются все регистры общего назначения, а не только rsp.


А вот еще вопрос глупы вопрос, понял что не знаю\понимаю: при переключении процессов\потоков процессор случаем не сохраняет содержимое своих кэшей для данного процесса\потока?
Это вообще возможно или это не безопасно? Ибо он, процессор, не знает от каких процессов данные в кэше. Т.е. про контекст процесса\потока я в курсе, что сохраняются, а кэш процессора для данного процесса?
Кодом людям нужно помогать!
Re[7]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.09.19 09:41
Оценка: 10 (1)
Здравствуйте, Sharov, Вы писали:

S>при переключении процессов\потоков процессор случаем не сохраняет содержимое своих кэшей для данного процесса\потока?


Нет. Процессор вообще не знает ни о каких "процессах" и "потоках", как объектах ОС. Для него поток — это то, что исполняется ядром.

S>Это вообще возможно или это не безопасно?


Теоретически возможно, вполне безопасно, но бессмысленно. Смысл кэша — держать наготове наиболее часто используемые код/данные. Переключение контекста — достаточно редкое событие на фоне быстродействия процессора, поэтому оптимизация кэшей на этом уровне эффекта не даст.
Re[8]: Переполнение буфера
От: Sharov Россия  
Дата: 25.09.19 09:45
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Нет. Процессор вообще не знает ни о каких "процессах" и "потоках", как объектах ОС. Для него поток — это то, что исполняется ядром.


Ага, тут глупость сморозил.

S>>Это вообще возможно или это не безопасно?

ЕМ>Теоретически возможно, вполне безопасно, но бессмысленно. Смысл кэша — держать наготове наиболее часто используемые код/данные. Переключение контекста — достаточно редкое событие на фоне быстродействия процессора, поэтому оптимизация кэшей на этом уровне эффекта не даст.

Почему бессмысленно? При переключении на процесс\поток восстанавливаем кэш для него и не теряем на промахах и т.п. Иначе с кэшем все заново...
Кодом людям нужно помогать!
Re[9]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 25.09.19 10:27
Оценка: 5 (1)
Здравствуйте, Sharov, Вы писали:

S>При переключении на процесс\поток восстанавливаем кэш для него и не теряем на промахах и т.п. Иначе с кэшем все заново...


Так с ним в любом случае "все заново". Но при сохранении/восстановлении пришлось бы таскать весь кэш туда-сюда независимо от ситуации, а без сохранения/восстановления таскается лишь то, что было фактически вытеснено.

Например, тяжелый софт полностью забил все кэши активно используемыми кодом/данными, и молотит их от переключения до переключения. А переключения случаются только на обработку прерываний, при которой вытесняется/перегружается лишь небольшая часть кэша, которая потом быстро (и оптимально) восстанавливается. Если бы при каждом переключении сохранялся/восстанавливался весь кэш, то дешевле было бы выкинуть его совсем.
Re[6]: Переполнение буфера
От: IID Россия  
Дата: 26.09.19 22:10
Оценка:
Здравствуйте, netch80, Вы писали:

N>zSeries (S/390) вообще не имеет выделенного регистра стека, стандартное ABI использует для этого R13. Занять, например, R12 в том же стиле — тривиально и требует только доработки кодогенератора.


у 32битного ARM, в целом, то же самое
Cтек — условность, LDMIA/STMIA в качестве базы может брать любой регистр. Если это R13 — трактуем STMIA как PUSH.
CALL/RET в системе команд нету.

в Aarch64 это всё выкинули, вместе с условным исполнением.
Результат очень похож на интел-асм, только вся адресация относительная, команды всегда 32 бита размером, 32 регистра и в стек всё пихается только парами (что логично на самом деле, т.к. выравнивание стека на x64 даже у интела равно двум машинным словам).
kalsarikännit
Re[7]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 27.09.19 10:00
Оценка:
Здравствуйте, IID, Вы писали:

IID>выравнивание стека на x64 даже у интела равно двум машинным словам).


Строго говоря, на x64 машинное слово — 64 бита, так что выравнивание таки по одному.
Re: Переполнение буфера
От: drVanо Россия https://vmpsoft.com
Дата: 27.09.19 10:14
Оценка:
Здравствуйте, Буравчик, Вы писали:

Б>Простейший способ — организовать где-то в памяти стек и использовать его для хранения локальных переменных и параметров процедур. А адрес возврата хранить в "обычном" стеке.

Б>Это же вроде компилятор решает, где и как хранить данные? Есть такая настройка?

Задача решается внутри компилятора — для резервирования места под локальные переменные используем выделение памяти из кучи, при выходе из функции — освобождаем эту память (в виде неявного try/finally). Вот и все.
Re[8]: Переполнение буфера
От: IID Россия  
Дата: 27.09.19 10:43
Оценка: +1
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, IID, Вы писали:


IID>>выравнивание стека на x64 даже у интела равно двум машинным словам).


ЕМ>Строго говоря, на x64 машинное слово — 64 бита, так что выравнивание таки по одному.


А стек должен быть выровнен на 16 байт ==128 бит, так что нет, все правильно.
kalsarikännit
Re[9]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 27.09.19 13:00
Оценка:
Здравствуйте, IID, Вы писали:

IID>А стек должен быть выровнен на 16 байт ==128 бит, так что нет, все правильно.


"Стек" — в смысле, только самая верхушка? А толку, если любой push/pop пишет/читает минимум по 8 байт?
Re[10]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 27.09.19 13:09
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

IID>>А стек должен быть выровнен на 16 байт ==128 бит, так что нет, все правильно.


ЕМ>"Стек" — в смысле, только самая верхушка? А толку, если любой push/pop пишет/читает минимум по 8 байт?


Чтобы было проще SSE-переменные размещать на выровненной границе.
(Я не поддерживаю тут эту мотивацию, просто факт об источнике нормы.)
The God is real, unless declared integer.
Re[7]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 27.09.19 13:22
Оценка: 10 (1) +1
Здравствуйте, Sharov, Вы писали:

N>>При переключении тредов восстанавливаются все регистры общего назначения, а не только rsp.


S>А вот еще вопрос глупы вопрос, понял что не знаю\понимаю: при переключении процессов\потоков процессор случаем не сохраняет содержимое своих кэшей для данного процесса\потока?

S>Это вообще возможно или это не безопасно? Ибо он, процессор, не знает от каких процессов данные в кэше. Т.е. про контекст процесса\потока я в курсе, что сохраняются, а кэш процессора для данного процесса?

На самом деле вопрос не совсем глупый
В типичных исполнениях, да, не сохраняет — переключение активной задачи (тред/процесс/etc.) приводит к тому, что используется ранее накопленный кэш, а потом он постепенно вымывается. Именно поэтому (включая прочие затраты) шедулеры ОС стараются подольше уходить от такого переключения.

А вот в PentiumPro делали на пробу возможность управления кэшом в духе: делится на 4 части и для каждой можно назначить, обновляется она или нет; можно было делать следующие фокусы:
— Прочитать кусок памяти с кодом, когда активна только некоторая четверть, потом залочить эту четверть => код не будет вымываться;
— Задача работает с активной четвертью, переключили — сделали другую четверть активной, вернули — включили предыдущую четверть — кэш сохранился.
Подробности были под NDA, и я их не видел, но были программы, что использовали это и давали скорость, как на P-II с в 3 раза более высокой тактовой(!) (что-то из видеомонтажа, уже и название не помню).
Но дальше этот эксперимент не пошёл, в P-II это не перенесли, видимо, потому, что тяжело этим управлять и фактически программа, которая управляет таким, монополизирует процессор. Темпы роста тактовой частоты были такими, что это всё было неинтересно.

С полгода назад слышал краем уха про эксперименты с воспроизведением этого в ARM, но ссылку не сохранял, поэтому это только туманные воспоминания. Но при текущей обстановке с практически остановившимся ростом плотности и скорости — это начинает становиться полезным.
The God is real, unless declared integer.
Re[8]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 27.09.19 13:32
Оценка: +1
Здравствуйте, Евгений Музыченко, Вы писали:

S>>при переключении процессов\потоков процессор случаем не сохраняет содержимое своих кэшей для данного процесса\потока?

ЕМ>Нет. Процессор вообще не знает ни о каких "процессах" и "потоках", как объектах ОС. Для него поток — это то, что исполняется ядром.

И вот ядро вполне может оповестить процессор в духе "а тут начинает работать задача номер 9". Процессору пофиг, что этот номер значит, у него просто все задачи номер 9 будут иметь равные права на кэш.
В сообщении рядом я описал, как такие вещи могут использоваться для оптимизации.

ЕМ>Теоретически возможно, вполне безопасно, но бессмысленно. Смысл кэша — держать наготове наиболее часто используемые код/данные.


Стандартно, но в общем неверно. Смысл кэша — держать наготове те данные, которые будут использоваться.
"Наиболее часто используемые" означает, с нынешним железом, тупую LRU организацию, а она далеко не всегда лучшая. Иначе бы не появились команды prefetch и запись с хинтом "эта строка больше не нужна".
Там, где есть возможность применить более хитрые методы, используется, например, 2Q — он не страдает вымыванием данных при однократной обработке большого массива данных. Но его слишком дорого переносить в аппаратную реализацию.

EM> Переключение контекста — достаточно редкое событие на фоне быстродействия процессора, поэтому оптимизация кэшей на этом уровне эффекта не даст.


Этому утверждению есть какое-то обоснование с цифрами? А то я контрпример привёл из истории, да и сейчас теория шедулинга постоянно говорит про ценность кэшей и как следствие — желание сократить переключения.
The God is real, unless declared integer.
Отредактировано 27.09.2019 13:35 netch80 . Предыдущая версия .
Re[8]: Переполнение буфера
От: Sharov Россия  
Дата: 27.09.19 16:00
Оценка:
Здравствуйте, netch80, Вы писали:


S>>Это вообще возможно или это не безопасно? Ибо он, процессор, не знает от каких процессов данные в кэше. Т.е. про контекст процесса\потока я в курсе, что сохраняются, а кэш процессора для данного процесса?

N>На самом деле вопрос не совсем глупый
N>В типичных исполнениях, да, не сохраняет — переключение активной задачи (тред/процесс/etc.) приводит к тому, что используется ранее накопленный кэш, а потом он постепенно вымывается. Именно поэтому (включая прочие затраты) шедулеры ОС стараются подольше уходить от такого переключения.
N>А вот в PentiumPro делали на пробу возможность управления кэшом в духе: делится на 4 части и для каждой можно назначить, обновляется она или нет; можно было делать следующие фокусы:
N>- Прочитать кусок памяти с кодом, когда активна только некоторая четверть, потом залочить эту четверть => код не будет вымываться;
N>- Задача работает с активной четвертью, переключили — сделали другую четверть активной, вернули — включили предыдущую четверть — кэш сохранился.
N>Подробности были под NDA, и я их не видел, но были программы, что использовали это и давали скорость, как на P-II с в 3 раза более высокой тактовой(!) (что-то из видеомонтажа, уже и название не помню).
N>Но дальше этот эксперимент не пошёл, в P-II это не перенесли, видимо, потому, что тяжело этим управлять и фактически программа, которая управляет таким, монополизирует процессор. Темпы роста тактовой частоты были такими, что это всё было неинтересно.

А эти сложности из-за того, что мы не знаем какая часть кэша к какому процессу относится? Т.е. не можем утверждать что все 100% кэша принадлежат рабоатющему процессу, чтобы случайно в его адресное про-во не попали данные
другого процесса? Иначе почему бы не сохранять L1 и L2, они же небольшие? L3 да, уже расход будет серьезный?
Кодом людям нужно помогать!
Re[11]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 27.09.19 16:12
Оценка:
Здравствуйте, netch80, Вы писали:

N>Чтобы было проще SSE-переменные размещать на выровненной границе.

N>(Я не поддерживаю тут эту мотивацию, просто факт об источнике нормы.)

Я не совсем понимаю, каким образом обеспечивается выполнение этой нормы. Если какой-то конкретный поток, возжелавший сохранить в стеке SSE-переменные, скорректирует RSP до кратного 16 байтам, то это будет его личное дело. Как это соотносится с "выравниванием стека у Интела"? По факту-то со стеком работают с точностью до 8 байт, а не до 16.
Re[9]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 27.09.19 16:27
Оценка: 10 (1)
Здравствуйте, netch80, Вы писали:

EM>> Переключение контекста — достаточно редкое событие на фоне быстродействия процессора, поэтому оптимизация кэшей на этом уровне эффекта не даст.


N>Этому утверждению есть какое-то обоснование с цифрами?


Ну, в той же винде типичный квант — 15.6 мс. Для процессора на 2 ГГц это порядка тридцати миллионов операций. Нужны очень специфические условия, чтобы:

— за это время систематически не успевать обрабатывать существенную часть данных, втянутых в кэш;
— при работе параллельного потока/процесса эта существенная часть каждый раз оказывалась выгруженной;
— все это продолжалось бы достаточно долго, и встречалось достаточно часто, чтобы оптимизация дала ощутимый эффект.

N>теория шедулинга постоянно говорит про ценность кэшей и как следствие — желание сократить переключения.


Теория — это хорошо, но к ней бы еще практических примеров из числа не слишком экзотических.

Вот где оптимизация была бы крайне полезна, но ее так и не доперли сделать — это на управлении HDD при параллельной обработке достаточно больших (от десятков мегабайт) наборов файлов. Когда группы файлов расположены на диске компактно, и каждый из параллельных процессов работает со своей группой — общая скорость получается в несколько раз меньше, чем если бы давать каждому из процессов поработать хотя бы полсекунды, но одному, а остальные на это время тормозить. Обычная перегруппировка операций типа NCQ с такой ситуацией не справляется, если каждый из процессов выдает только один запрос в единицу времени. Я в разное время порывался это сделать, но руки так и не дошли.
Re[12]: Переполнение буфера
От: IID Россия  
Дата: 28.09.19 17:30
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Я не совсем понимаю, каким образом обеспечивается выполнение этой нормы.


компилятором

ЕМ>Если какой-то конкретный поток, возжелавший сохранить в стеке SSE-переменные, скорректирует RSP до кратного 16 байтам, то это будет его личное дело.


Ну т.е. выровняет. О чём и речь:

EM>Как это соотносится с "выравниванием стека у Интела"?


EM>По факту-то со стеком работают с точностью до 8 байт, а не до 16.


16 байт — требование на выравнивание стекфрейма.
Внутри ты можешь делать что угодно, но при следующем вызове CALL получишь исключение, если вершина стека не выровнена.

Из-за кривой системы команд, с кучей костылей, стек иногда оказывается в невыровненном состоянии. Потому что ISA содержит пуш для одиночного регистра, и CALL помещает в стек тоже только 1 запись.
Т.е. уже на входе в функцию мы имеем дело с нарушенным выравниванием, и вынуждены исправлять его, чтобы вызывать вложенные функции.

Замечу ещё что PUSH/POP в явном виде сейчас используются редко, особенно в ICC. Компилятор предпочитает работать со стеком напрямую.
Т.е. даже основная роль стека, CALL/RET оказывается ущербной by design.

  А теперь сравним с ситуацией в Aarch64
- BL[X/R] (aka CALL) не нарушает выравнивания, потому что не трогает SP — адрес возврата помещается в LR.
— регистры пушатся только парами, что тоже не нарушает выравнивание.
Пуш это всего лишь алиас на "STR r_1, r_2, [#r_base]!" (индексная запись с инкрементом базы) когда r_base это SP. Базой может быть любой регистр.
Аналогично POP — алиас на LDR.
— RET может иметь параметром любой регистр (по-умолчанию LR) и тоже не трогает стек.
Отличие от команды BR (бранч по значению в регистре) только в одном бите (#22) — это хинт железу, какого рода бранч использован. Влияет только на внутренние процессы, типа кеширования, если вообще на что-то влияет.
— 31 регистр общего назначения! (XZR очень условно можно считать регистром) Против 16 у AMD64.

R13/X29 называют стеком (SP) только для определённости (и для соблюдения calling conventions).
Аналогично R14/X30 это BL (BranchLink), тоже ничем не отличается от регистров общего назначения, кроме использования в команде BL{X/R}.

Жестко задана только роль PC, и то в 32-битных ARM он (R15) входил в общее поле регистров и с ним можно было вытворять много весёлого.
В 64 битных X31 это XZR — нулевой регистр, а использование PC кодируется самой командой.
kalsarikännit
Re[12]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.09.19 18:05
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, netch80, Вы писали:


N>>Чтобы было проще SSE-переменные размещать на выровненной границе.

N>>(Я не поддерживаю тут эту мотивацию, просто факт об источнике нормы.)

ЕМ>Я не совсем понимаю, каким образом обеспечивается выполнение этой нормы.


Перед вызовом первой функции, которая работает в принятом соглашении о вызовах — стек выравнивается явно. Например, через and rsp, -16.
Последующие функции должны при необходимости добавить 1 "eightbyte" на стек, или не добавлять, смотря по тому, как они вызывают другие функции. Но в момент выполнения call — изволь выровнять стек.

Если какой-то конкретный поток, возжелавший сохранить в стеке SSE-переменные, скорректирует RSP до кратного 16 байтам, то это будет его личное дело. Как это соотносится с "выравниванием стека у Интела"? По факту-то со стеком работают с точностью до 8 байт, а не до 16.

У нас в общем 3 варианта:
1. Всё выравнивается на 8 байт, на 16 ничего не выравнивается.
Работать будет, но рекомендации Intel+AMD говорят, что будет медленнее. И aligned-команды будут неприменимы на таком. Почему-то ж они хотели, чтобы было выравнивание?
2. Выравнивание обеспечивает любая функция. Это как сейчас в ABI по крайней мере SysV варианта для x86-64. По факту это приводит к тому, что rsp сдвигается на другое смещение, чем было бы без такого выравнивания. В самом простом случае — функция зовёт другую функцию без других манипуляций со стеком, только через регистры — rsp уменьшается на 8 в прологе и увеличивается обратно в эпилоге. В общем-то копейки.
3. Выравнивание обеспечивает себе любая функция, которая работает с SSE переменными. Тогда ей придётся завести ещё один регистр, через который будут адресоваться такие переменные, или же через который будут адресоваться аргументы функции — это уже как удобно. Например, локальные переменные — через rsp, аргументы — через rbp. Возможно, но уже был уход от обязательности rbp на x86-64 — и что, снова возвращаться к нему?
Да и игры с указателями в прологе — сомнительные действия.
The God is real, unless declared integer.
Re[13]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 28.09.19 18:21
Оценка:
Здравствуйте, IID, Вы писали:

IID>Внутри ты можешь делать что угодно, но при следующем вызове CALL получишь исключение, если вершина стека не выровнена.


Как добиться исключения? Сделал на ассемблере несколько вложенных процедур, каждая из которых тупо делает call на следующую. RSP каждый раз меняется на 8, младшая цифра — то 0, то 8, исключений нет (в том числе и под отладчиком). Процессор i7.

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


Ни разу не видел, чтобы MSVC генерил код, проверяющий, в каком состоянии RSP. Он в прологе тупо уменьшает 16*n+8 (очевидно, исходя из того, что call выполнялся на выровненном стеке), но фактически это не проверяется.

IID>Замечу ещё что PUSH/POP в явном виде сейчас используются редко, особенно в ICC. Компилятор предпочитает работать со стеком напрямую.


Да, MSVC тоже так делает.
Re[13]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 28.09.19 18:23
Оценка:
Здравствуйте, netch80, Вы писали:

N>1. Всё выравнивается на 8 байт, на 16 ничего не выравнивается.

N>Работать будет, но рекомендации Intel+AMD говорят, что будет медленнее. И aligned-команды будут неприменимы на таком.

Это само собой. Меня удивило упоминание о якобы безусловном требовании выравнивания, без которого не будет работать даже call.
Re[13]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.09.19 18:37
Оценка:
Здравствуйте, IID, Вы писали:

IID>16 байт — требование на выравнивание стекфрейма.

IID>Внутри ты можешь делать что угодно, но при следующем вызове CALL получишь исключение, если вершина стека не выровнена.

Это всё ещё про x86? И где это вы такое нашли?

The processor does not check stack pointer alignment. It is the responsibility of the programs, tasks, and system procedures running on the processor to maintain proper alignment of stack pointers. Misaligning a stack pointer can cause serious performance degradation and in some instances program failures.

(Intel SDM, том 1, глава 6.2.2)

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

IID>Из-за кривой системы команд, с кучей костылей, стек иногда оказывается в невыровненном состоянии. Потому что ISA содержит пуш для одиночного регистра, и CALL помещает в стек тоже только 1 запись.

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

Никто не вынужден.

IID>Замечу ещё что PUSH/POP в явном виде сейчас используются редко, особенно в ICC. Компилятор предпочитает работать со стеком напрямую.

IID>Т.е. даже основная роль стека, CALL/RET оказывается ущербной by design.

Эти данные какой давности?
Начиная где-то с Nehalem, а может, и раньше, цепочки push/pop оптимизированы и в них нет тупых зависимостей. Если в районе 2005-2010 GCC ещё предпочитал тоже делать доступ по смещению от esp/rsp, то в новых версиях можно массово увидеть всё те же классические push/pop.

IID>[А теперь сравним с ситуацией в Aarch64]


Stack alignment checking может быть включён или выключен (ARMv8-A, раздел D1.8.2).
Да, для включенного случая есть поддержка.

IID> Пуш это всего лишь алиас на "STR r_1, r_2, [#r_base]!" (индексная запись с инкрементом базы) когда r_base это SP. Базой может быть любой регистр.


STP и LDP, если быть точным.

IID>R13/X29 называют стеком (SP) только для определённости (и для соблюдения calling conventions).


Но аппаратная проверка выравнивания работает только для этого регистра.

IID>Жестко задана только роль PC,


Ограничение выравнивания это не фиксация роли? А то, что для каждого exception level свой SP?

Вы недокурили документацию и додумываете какую-то фантастику.
The God is real, unless declared integer.
Re[14]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.09.19 18:46
Оценка: +1
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, netch80, Вы писали:


N>>1. Всё выравнивается на 8 байт, на 16 ничего не выравнивается.

N>>Работать будет, но рекомендации Intel+AMD говорят, что будет медленнее. И aligned-команды будут неприменимы на таком.

ЕМ>Это само собой. Меня удивило упоминание о якобы безусловном требовании выравнивания, без которого не будет работать даже call.


Это очевидно неверно.
Более того, я проверил даже со смещением в 1 байт — всё работает!

#include <stdio.h>

int x = 0;
unsigned long seen_sp = 0;

void f() {
  x = 1;
  asm volatile("mov %%rsp,%0" : "=r" (seen_sp) : : );
}

int main() {
  asm volatile(
    "sub $1, %%rsp\n\t"
    "call f\n\t"
    "add $1, %%rsp\n\t"
    : : : "memory");
  printf("x=%d seen_sp=%lx\n", x, seen_sp);
}


выводит без исключения, например:
x=1 seen_sp=7ffe5ac07aff


Повторюсь, выравнивание стека в принятом соглашении это только для облегчения компилятору размещения того, что действительно может взорваться (как SSE с командами типа movdqa, movaps, movapd и тому подобным). Откуда IID@ взял что-то иное — пусть сам расскажет.
The God is real, unless declared integer.
Re[9]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.09.19 18:53
Оценка: 5 (1)
Здравствуйте, Sharov, Вы писали:

N>>Но дальше этот эксперимент не пошёл, в P-II это не перенесли, видимо, потому, что тяжело этим управлять и фактически программа, которая управляет таким, монополизирует процессор. Темпы роста тактовой частоты были такими, что это всё было неинтересно.

S>А эти сложности из-за того, что мы не знаем какая часть кэша к какому процессу относится?

Ну примерно так. Для полной реализации надо было бы извратиться по типу — доступы из user land идут в один кэш, из kernel land в другой... — сильно заморочно, не видна была польза.

S> Т.е. не можем утверждать что все 100% кэша принадлежат рабоатющему процессу, чтобы случайно в его адресное про-во не попали данные другого процесса? Иначе почему бы не сохранять L1 и L2, они же небольшие? L3 да, уже расход будет серьезный?


Сохранять — в смысле выгружать целиком и потом загружать? Или замораживать, как в PPro?
Я боюсь, с этими Meltdown/Spectre/etc. ещё вернутся к этому. Но до того предпочитали, что эффекты вымывания и задержек после возврата задачи в работу, даже если есть, не были заметны за счёт из размазывания по течению времени.
The God is real, unless declared integer.
Re[10]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 28.09.19 19:09
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

EM>>> Переключение контекста — достаточно редкое событие на фоне быстродействия процессора, поэтому оптимизация кэшей на этом уровне эффекта не даст.

N>>Этому утверждению есть какое-то обоснование с цифрами?
ЕМ>Ну, в той же винде типичный квант — 15.6 мс. Для процессора на 2 ГГц это порядка тридцати миллионов операций.

По-моему, такой квант нормально был где-то до 7ки. После — массово стали переходить на кванты около 1 мс.
Тоже заметно, но уже другой порядок.
Естественно, не на каждый такой квант делается переключение — его и во времена 15.625мс квантов затягивали на подольше (могли пачками и по 10-20 квантов).
Но надо мерять не в количестве тактов процессора, а во временах задержки DRAM на переключение на другую строку (закрыть старую, открыть новую) плюс прогон через все кэши и ещё и координацию с соседними ядрами. Для современного железа это можно оценить в цифрах порядка 100 нс. Параллельное (out of order) исполнение может организовать много подобных доступов одновременно, что они друг другу почти не мешают, но в результате вместо 30 миллионов остаётся около 10 тысяч (в худшем случае, когда все запросы друг от друга зависят), до миллиона, если получится их максимально запараллелить. 10000 это уже маловато.
В общем, дьявол в мелочах, но возможность убить производительность на порядки слишком частым переключением — она в полный рост.

EM> Нужны очень специфические условия, чтобы:

ЕМ>- за это время систематически не успевать обрабатывать существенную часть данных, втянутых в кэш;

Если от таких переключений эффективность кэша теряется даже на 3-5%, найдутся те, кому эта цифра будет заметной.

ЕМ>Вот где оптимизация была бы крайне полезна, но ее так и не доперли сделать — это на управлении HDD при параллельной обработке достаточно больших (от десятков мегабайт) наборов файлов. Когда группы файлов расположены на диске компактно, и каждый из параллельных процессов работает со своей группой — общая скорость получается в несколько раз меньше, чем если бы давать каждому из процессов поработать хотя бы полсекунды, но одному, а остальные на это время тормозить. Обычная перегруппировка операций типа NCQ с такой ситуацией не справляется, если каждый из процессов выдает только один запрос в единицу времени. Я в разное время порывался это сделать, но руки так и не дошли.


Я бы тут начал с предоставления приложениям API, чтобы они объявляли характер своей работы, типа "я неинтерактивный, рекомендуемый квант доступа — 2 секунды и больше". Тогда и управлять таким будет легче.
The God is real, unless declared integer.
Re[15]: Переполнение буфера
От: IID Россия  
Дата: 28.09.19 19:19
Оценка:
Здравствуйте, netch80, Вы писали:

N>Это очевидно неверно.

N>Более того, я проверил даже со смещением в 1 байт — всё работает!

Да, я ошибся.
Тоже проверил — был не прав.

Несколько (>5) лет назад наткнулся на исключение при невыровненном стеке, и почему-то запомнилась именно связь невыровненности -> CALL, а не доступа в SSE аргументам.
kalsarikännit
Re[11]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 28.09.19 19:22
Оценка:
Здравствуйте, netch80, Вы писали:

N>По-моему, такой квант нормально был где-то до 7ки. После — массово стали переходить на кванты около 1 мс.


Погуглил, но навскидку не нашел. Глянул в десятку 1903 — у нее разрешение таймера по умолчанию 15.6, как и всегда.

N>Я бы тут начал с предоставления приложениям API, чтобы они объявляли характер своей работы, типа "я неинтерактивный, рекомендуемый квант доступа — 2 секунды и больше".


Ага, и завтра же 100500 интерактивных станут делать то же самое, при этом каждое будет надеяться, что такое умное оно одно.
Re[10]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 28.09.19 19:24
Оценка: +1
Здравствуйте, netch80, Вы писали:

N>Я боюсь, с этими Meltdown/Spectre/etc. ещё вернутся к этому.


В очередной раз спрошу: где можно взять реально работающий пример Meltdown/Spectre? Я пробовал пару прошлогодних примеров, один делал сам — ни один не работает.
Re[14]: Переполнение буфера
От: IID Россия  
Дата: 28.09.19 20:36
Оценка:
Здравствуйте, netch80, Вы писали:

IID>>Замечу ещё что PUSH/POP в явном виде сейчас используются редко, особенно в ICC. Компилятор предпочитает работать со стеком напрямую.

IID>>Т.е. даже основная роль стека, CALL/RET оказывается ущербной by design.

N>Эти данные какой давности?

N>Начиная где-то с Nehalem, а может, и раньше, цепочки push/pop оптимизированы и в них нет тупых зависимостей. Если в районе 2005-2010 GCC ещё предпочитал тоже делать доступ по смещению от esp/rsp, то в новых версиях можно массово увидеть всё те же классические push/pop.

Это наблюдения за сгенерированным в ICC кодом. Статискику по годам не вёл.

IID>>[А теперь сравним с ситуацией в Aarch64]


N>Stack alignment checking может быть включён или выключен (ARMv8-A, раздел D1.8.2).

N>Да, для включенного случая есть поддержка.

Data address alignment fault тоже может быть включён или выключен (раздел B2.5.2).

IID>> Пуш это всего лишь алиас на "STR r_1, r_2, [#r_base]!" (индексная запись с инкрементом базы) когда r_base это SP. Базой может быть любой регистр.


N>STP и LDP, если быть точным.


Это всё равно STR: StoRe Pair.
Как и 32 битный STM[FIA]: — StoRe Multiple[...]

IID>>R13/X29 называют стеком (SP) только для определённости (и для соблюдения calling conventions).


N>Но аппаратная проверка выравнивания работает только для этого регистра.


Не только.

IID>>Жестко задана только роль PC,


N>Ограничение выравнивания это не фиксация роли? А то, что для каждого exception level свой SP?


Не особо. ОК, есть отдельный бит контроля выравнивания. Есть calling convetions. И... всё ?

Зато сделать PUSH/POP по другой базе — запросто, в отличие от Intel/AMD.
Вкупе с безстековым CALL/RET разница между R29 и любой другой базой не имеет значения.

ЗЫ: В 32 битах было ещё круче — одна команда грузила сразу кучу регистров (для набора первых 8 в THUMB 2х-байтный опкод). Что очень удобно для их инициализации.

N>Вы недокурили документацию и додумываете какую-то фантастику.


kalsarikännit
Re[2]: Переполнение буфера
От: IID Россия  
Дата: 28.09.19 21:44
Оценка:
Здравствуйте, drVanо, Вы писали:

V>Задача решается внутри компилятора — для резервирования места под локальные переменные используем выделение памяти из кучи, при выходе из функции — освобождаем эту память (в виде неявного try/finally). Вот и все.


Ну будет тормознее + переполнение в куче, чем это лучше-то ?
kalsarikännit
Re[15]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.09.19 06:03
Оценка:
Здравствуйте, IID, Вы писали:

IID>>>Замечу ещё что PUSH/POP в явном виде сейчас используются редко, особенно в ICC. Компилятор предпочитает работать со стеком напрямую.

IID>>>Т.е. даже основная роль стека, CALL/RET оказывается ущербной by design.

N>>Эти данные какой давности?

N>>Начиная где-то с Nehalem, а может, и раньше, цепочки push/pop оптимизированы и в них нет тупых зависимостей. Если в районе 2005-2010 GCC ещё предпочитал тоже делать доступ по смещению от esp/rsp, то в новых версиях можно массово увидеть всё те же классические push/pop.

IID>Это наблюдения за сгенерированным в ICC кодом. Статискику по годам не вёл.


Ну ясно. Сейчас, скорее всего, и в новом коде он не циклится на этом подходе со смещением. Надо проверить, но у меня нет ICC под рукой. GCC, Clang современные — оба спокойно смешивают push/pop и доступ пачками по смещению, причём в прологах/эпилогах push и pop идут цепочками на несколько регистров.

IID>>> Пуш это всего лишь алиас на "STR r_1, r_2, [#r_base]!" (индексная запись с инкрементом базы) когда r_base это SP. Базой может быть любой регистр.

N>>STP и LDP, если быть точным.
IID>Это всё равно STR: StoRe Pair.
IID>Как и 32 битный STM[FIA]: — StoRe Multiple[...]

Но название таки другое.

IID>>>R13/X29 называют стеком (SP) только для определённости (и для соблюдения calling conventions).

N>>Но аппаратная проверка выравнивания работает только для этого регистра.
IID>Не только.

Из связанных с регистром — только для этого? Или где-то ещё видели?
(4 байта для PC не считаю, она и не управляется)
Включать вообще для всего — можно, но это 1) не по 16 байт, 2) мера совсем другого уровня.

IID>>>Жестко задана только роль PC,

N>>Ограничение выравнивания это не фиксация роли? А то, что для каждого exception level свой SP?
IID>Не особо. ОК, есть отдельный бит контроля выравнивания. Есть calling convetions. И... всё ?

А что ещё нужно? Вернитесь к контексту, пожалуйста Вопрос EM был про ваше странное утверждение про выравнивание стека у Intel. Мы нашли в итоге, что в x86-64 это требование уровня ABI, но не процессора, а в AArch64 — может быть (и рекомендуется) форсировано процессором, но при использовании именно SP.

В сочетании с раздельными SP для каждого EL, это таки даёт ему особую роль выделенного стека (одного, не двух).
Если делать два стека, то один из них не будет иметь такой поддержки в железе, и придётся делать закат солнца вручную.

IID>Зато сделать PUSH/POP по другой базе — запросто, в отличие от Intel/AMD.

IID>Вкупе с безстековым CALL/RET разница между R29 и любой другой базой не имеет значения.

Кстати, почему вы всё время R29 упоминаете? AArch64 — у SP номер 31 (а будет 31 пониматься как SP или ZR — зависит от команды).

N>>Вы недокурили документацию и додумываете какую-то фантастику.


IID>


Ну таки да. Сказки про x86, куча странных неверных мелочей для ARM... в сумме достаточно, чтобы отправить на доработку. И главное, что исходный вопрос ну никак не решает.
The God is real, unless declared integer.
Re[11]: Переполнение буфера
От: Masterspline  
Дата: 29.09.19 09:13
Оценка:
ЕМ>>Вот где оптимизация была бы крайне полезна, но ее так и не доперли сделать — это на управлении HDD при параллельной обработке достаточно больших (от десятков мегабайт) наборов файлов. Когда группы файлов расположены на диске компактно, и каждый из параллельных процессов работает со своей группой — общая скорость получается в несколько раз меньше, чем если бы давать каждому из процессов поработать хотя бы полсекунды, но одному, а остальные на это время тормозить. Обычная перегруппировка операций типа NCQ с такой ситуацией не справляется, если каждый из процессов выдает только один запрос в единицу времени. Я в разное время порывался это сделать, но руки так и не дошли.

N>Я бы тут начал с предоставления приложениям API, чтобы они объявляли характер своей работы, типа "я неинтерактивный, рекомендуемый квант доступа — 2 секунды и больше". Тогда и управлять таким будет легче.


Асинхронные неблокирующие запросы к диску (пачка запросов) решают эту задачу.
Re[12]: Переполнение буфера
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 29.09.19 09:57
Оценка:
Здравствуйте, Masterspline, Вы писали:

ЕМ>>>Вот где оптимизация была бы крайне полезна, но ее так и не доперли сделать — это на управлении HDD при параллельной обработке достаточно больших (от десятков мегабайт) наборов файлов. Когда группы файлов расположены на диске компактно, и каждый из параллельных процессов работает со своей группой — общая скорость получается в несколько раз меньше, чем если бы давать каждому из процессов поработать хотя бы полсекунды, но одному, а остальные на это время тормозить. Обычная перегруппировка операций типа NCQ с такой ситуацией не справляется, если каждый из процессов выдает только один запрос в единицу времени. Я в разное время порывался это сделать, но руки так и не дошли.


N>>Я бы тут начал с предоставления приложениям API, чтобы они объявляли характер своей работы, типа "я неинтерактивный, рекомендуемый квант доступа — 2 секунды и больше". Тогда и управлять таким будет легче.


M>Асинхронные неблокирующие запросы к диску (пачка запросов) решают эту задачу.


Нет, не решают.
The God is real, unless declared integer.
Re[10]: Переполнение буфера
От: Sharov Россия  
Дата: 29.09.19 12:26
Оценка:
Здравствуйте, netch80, Вы писали:

N>Сохранять — в смысле выгружать целиком и потом загружать? Или замораживать, как в PPro?


Именно выгружать целиком, ну это самый простой вариант. Вероятно, есть более сложные, типа заморозок и т.д.
Кодом людям нужно помогать!
Re[11]: Переполнение буфера
От: lpd Черногория  
Дата: 29.09.19 12:32
Оценка:
Здравствуйте, Евгений Музыченко, Вы писали:

ЕМ>Здравствуйте, netch80, Вы писали:


N>>Я боюсь, с этими Meltdown/Spectre/etc. ещё вернутся к этому.


ЕМ>В очередной раз спрошу: где можно взять реально работающий пример Meltdown/Spectre? Я пробовал пару прошлогодних примеров, один делал сам — ни один не работает.


Для Meltdown, есть в составе linux test project: Meltdown
Также есть exploit
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 29.09.2019 12:37 lpd . Предыдущая версия .
Re[12]: Переполнение буфера
От: Евгений Музыченко Франция https://software.muzychenko.net/ru
Дата: 29.09.19 20:10
Оценка:
Здравствуйте, lpd, Вы писали:

lpd>Для Meltdown, есть в составе linux test project: Meltdown

lpd>Также есть exploit

И ни одного без линукса не собрать. Я, пожалуй, поленюсь ставить только для проверки.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.